diff --git a/.changeset/README.md b/.changeset/README.md index 4f3b76b096..423552ee75 100644 --- a/.changeset/README.md +++ b/.changeset/README.md @@ -6,3 +6,58 @@ find the full documentation for it [in our repository](https://github.com/change We have a quick list of common questions to get you started engaging with this project in [our documentation](https://github.com/changesets/changesets/blob/master/docs/common-questions.md) + +--- + +## Backstage UI Changesets + +For `@backstage/ui` changesets, use this format: + +```markdown +--- +'@backstage/ui': patch +--- + +Brief summary + +Optional description with code examples. + +**Migration:** + +Migration instructions (breaking changes only). + +**Affected components:** Button, Card +``` + +**Required:** + +- End with `**Affected components:**` + comma-separated component names +- For breaking changes: Add `**Migration:**` section +- No headings (`##`, `###`) inside - use bold markers + +**Examples:** + +```markdown +Fixed button hover state + +**Affected components:** Button +``` + +````markdown +**BREAKING**: New Table API + +**Migration:** + +Update imports: + +```diff +- import { Table } from '@backstage/ui'; ++ import { Table, type ColumnConfig } from '@backstage/ui'; +``` +```` + +**Affected components:** Table + +``` + +``` diff --git a/.changeset/add-missing-transitive-deps.md b/.changeset/add-missing-transitive-deps.md new file mode 100644 index 0000000000..83f87fcc7e --- /dev/null +++ b/.changeset/add-missing-transitive-deps.md @@ -0,0 +1,8 @@ +--- +'@backstage/core-components': patch +'@backstage/ui': patch +'@backstage/plugin-notifications': patch +'@backstage/plugin-scaffolder-backend-module-github': patch +--- + +Added missing dependencies that were previously only available transitively. diff --git a/.changeset/add-service-unavailable-error-name.md b/.changeset/add-service-unavailable-error-name.md new file mode 100644 index 0000000000..78647f7a56 --- /dev/null +++ b/.changeset/add-service-unavailable-error-name.md @@ -0,0 +1,5 @@ +--- +'@backstage/errors': patch +--- + +Added explicit `name` property to `ServiceUnavailableError` for consistency with all other error classes, making it resilient to minification. diff --git a/.changeset/all-camels-agree.md b/.changeset/all-camels-agree.md deleted file mode 100644 index 44d8a4f2c9..0000000000 --- a/.changeset/all-camels-agree.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Fix broken external links in Backstage UI Header component. diff --git a/.changeset/all-parrots-change.md b/.changeset/all-parrots-change.md deleted file mode 100644 index 65d5c37658..0000000000 --- a/.changeset/all-parrots-change.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-org': patch ---- - -Improved responsiveness of GroupProfileCard component diff --git a/.changeset/better-hats-cross.md b/.changeset/better-hats-cross.md deleted file mode 100644 index 99444b7fbf..0000000000 --- a/.changeset/better-hats-cross.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-defaults': patch ---- - -Adjusted the log line wording of task worker starting diff --git a/.changeset/better-steaks-act.md b/.changeset/better-steaks-act.md deleted file mode 100644 index b3ce601245..0000000000 --- a/.changeset/better-steaks-act.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -The templates executed with the `yarn new` command now supports templating filenames. diff --git a/.changeset/breezy-times-ring.md b/.changeset/breezy-times-ring.md deleted file mode 100644 index f65dd6ff55..0000000000 --- a/.changeset/breezy-times-ring.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/repo-tools': patch ---- - -Updated knip-reports to detect dependencies in dev/alpha pattern diff --git a/.changeset/bright-ears-send.md b/.changeset/bright-ears-send.md deleted file mode 100644 index 5104f2377b..0000000000 --- a/.changeset/bright-ears-send.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': minor ---- - -Enable YAML merge keys in yamlPlaceholderResolver diff --git a/.changeset/busy-goats-create.md b/.changeset/busy-goats-create.md deleted file mode 100644 index 78b8f65571..0000000000 --- a/.changeset/busy-goats-create.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -'@backstage/plugin-search-backend-module-elasticsearch': patch -'@backstage/plugin-kubernetes-backend': patch -'@backstage/plugin-techdocs-backend': patch -'@backstage/plugin-signals-backend': patch -'@backstage/plugin-techdocs': patch -'@backstage/plugin-signals': patch ---- - -Removed/moved unused dependencies diff --git a/.changeset/chubby-candies-cry.md b/.changeset/chubby-candies-cry.md new file mode 100644 index 0000000000..75394f823e --- /dev/null +++ b/.changeset/chubby-candies-cry.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-app': patch +--- + +Migrated React Aria imports from individual packages (`@react-aria/toast`, `@react-aria/button`, `@react-stately/toast`) to the monopackages (`react-aria`, `react-stately`). diff --git a/.changeset/clamp-react-aria-deps.md b/.changeset/clamp-react-aria-deps.md new file mode 100644 index 0000000000..521670e40d --- /dev/null +++ b/.changeset/clamp-react-aria-deps.md @@ -0,0 +1,8 @@ +--- +'@backstage/ui': patch +'@backstage/plugin-app': patch +'@backstage/plugin-app-visualizer': patch +'@backstage/plugin-notifications': patch +--- + +Tightened React Aria dependency version ranges from `^` to `~` to prevent unintended minor version upgrades. diff --git a/.changeset/clever-boats-clap.md b/.changeset/clever-boats-clap.md deleted file mode 100644 index 6b40f05a76..0000000000 --- a/.changeset/clever-boats-clap.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@backstage/plugin-search-react': minor -'@backstage/plugin-techdocs': minor -'@backstage/plugin-catalog': minor -'@backstage/plugin-search': minor ---- - -Add support for customizable icons in `SearchResultListItemBlueprint` and related components diff --git a/.changeset/create-app-1761312116.md b/.changeset/create-app-1761312116.md deleted file mode 100644 index b50d431d4b..0000000000 --- a/.changeset/create-app-1761312116.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Bumped create-app version. diff --git a/.changeset/create-app-1762897504.md b/.changeset/create-app-1762897504.md deleted file mode 100644 index b50d431d4b..0000000000 --- a/.changeset/create-app-1762897504.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Bumped create-app version. diff --git a/.changeset/cruel-items-dig.md b/.changeset/cruel-items-dig.md deleted file mode 100644 index 65f07956ac..0000000000 --- a/.changeset/cruel-items-dig.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -'@backstage/ui': minor ---- - -**BREAKING**: Migrated Avatar component from Base UI to custom implementation with size changes: - -- Base UI-specific props are no longer supported -- Size values have been updated: - - New `x-small` size added (1.25rem / 20px) - - `small` size unchanged (1.5rem / 24px) - - `medium` size unchanged (2rem / 32px, default) - - `large` size **changed from 3rem to 2.5rem** (40px) - - New `x-large` size added (3rem / 48px) - -Migration: - -```diff -# Remove Base UI-specific props -- -+ - -# Update large size usage to x-large for same visual size -- -+ -``` - -Added `purpose` prop for accessibility control (`'informative'` or `'decoration'`). diff --git a/.changeset/cruel-plums-talk.md b/.changeset/cruel-plums-talk.md deleted file mode 100644 index bc7a30b048..0000000000 --- a/.changeset/cruel-plums-talk.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -'@backstage/ui': minor ---- - -**BREAKING**: Migrated Checkbox component from Base UI to React Aria Components. - -API changes required: - -- `checked` → `isSelected` -- `defaultChecked` → `defaultSelected` -- `disabled` → `isDisabled` -- `required` → `isRequired` -- `label` prop removed - use `children` instead -- CSS: `bui-CheckboxLabel` class removed -- Data attribute: `data-checked` → `data-selected` -- Use without label is no longer supported - -Migration examples: - -Before: - -```tsx - -``` - -After: - -```tsx - - Accept terms - -``` - -Before: - -```tsx - -``` - -After: - -```tsx -Option -``` - -Before: - -```tsx - -``` - -After: - -```tsx - - Accessible label - -``` diff --git a/.changeset/deduplicate-joinpaths-routing.md b/.changeset/deduplicate-joinpaths-routing.md new file mode 100644 index 0000000000..8a17fbc17b --- /dev/null +++ b/.changeset/deduplicate-joinpaths-routing.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': patch +--- + +Internal cleanup of routing utilities. diff --git a/.changeset/deep-gifts-slide.md b/.changeset/deep-gifts-slide.md deleted file mode 100644 index f3be77e74d..0000000000 --- a/.changeset/deep-gifts-slide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Fixed CSS issues in Select component including popover width constraints, focus outline behavior, and overflow handling. diff --git a/.changeset/delegate-attach-mock-api-factory.md b/.changeset/delegate-attach-mock-api-factory.md new file mode 100644 index 0000000000..0d1f47bd64 --- /dev/null +++ b/.changeset/delegate-attach-mock-api-factory.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-test-utils': patch +--- + +Removed internal `mockWithApiFactory` helper in favor of using `attachMockApiFactory` directly. diff --git a/.changeset/early-berries-bet.md b/.changeset/early-berries-bet.md deleted file mode 100644 index 7540c41efd..0000000000 --- a/.changeset/early-berries-bet.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': patch ---- - -Fix React warnings for elements not having stable keys. diff --git a/.changeset/easy-hands-grow.md b/.changeset/easy-hands-grow.md deleted file mode 100644 index fc3cfd96e1..0000000000 --- a/.changeset/easy-hands-grow.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': minor -'@backstage/plugin-catalog-node': minor ---- - -Make YAML merge (<<:) support configurable in the Backstage Catalog instead of always being enabled diff --git a/.changeset/eighty-mails-leave.md b/.changeset/eighty-mails-leave.md deleted file mode 100644 index e8266f6459..0000000000 --- a/.changeset/eighty-mails-leave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-home': patch ---- - -Allow customization of `VisitList` by adding optional `enrichVisit`, `transformPathname`, `canSave` functions to `VisitsStorageApi`, along with `VisitDisplayProvider` for colors, labels diff --git a/.changeset/eighty-results-prove.md b/.changeset/eighty-results-prove.md deleted file mode 100644 index 6df2d2f11a..0000000000 --- a/.changeset/eighty-results-prove.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-app-visualizer': patch ---- - -Improve tree visualizer to use a horizontal layout and fill the content space. diff --git a/.changeset/eleven-carpets-win.md b/.changeset/eleven-carpets-win.md deleted file mode 100644 index 69d677786a..0000000000 --- a/.changeset/eleven-carpets-win.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-defaults': patch ---- - -Fix a bug in the Gitlab URL reader where `search` did not handle multiple globs diff --git a/.changeset/eleven-lights-taste.md b/.changeset/eleven-lights-taste.md deleted file mode 100644 index e575b909e6..0000000000 --- a/.changeset/eleven-lights-taste.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-react': patch ---- - -Added missing i18n diff --git a/.changeset/every-ants-count.md b/.changeset/every-ants-count.md deleted file mode 100644 index 963c3a521a..0000000000 --- a/.changeset/every-ants-count.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/backend-test-utils': minor ---- - -Added support for Postgres 18 to the available `TestDatabases`. - -Note that the set of _default_ databases to test against for users of the `TestDatabases` class was also updated to include Postgres 14 and 18, instead of 13 and 17. If you need to override this, you can pass in an explicit `ids` argument, for example `ids: ['POSTGRES_17', 'POSTGRES_13', 'SQLITE_3']`. diff --git a/.changeset/every-clocks-arrive.md b/.changeset/every-clocks-arrive.md deleted file mode 100644 index d2061913e3..0000000000 --- a/.changeset/every-clocks-arrive.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/dev-utils': patch ---- - -Fixed styling of the dev app by adding a lazy import of `@backstage/ui/css/styles.css`. diff --git a/.changeset/extension-point-middleware-backend-app-api.md b/.changeset/extension-point-middleware-backend-app-api.md new file mode 100644 index 0000000000..2d0ea0dfbc --- /dev/null +++ b/.changeset/extension-point-middleware-backend-app-api.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-app-api': minor +--- + +Added `ExtensionPointFactoryMiddleware` type and `createExtensionPointFactoryMiddleware` helper to reimplement extension point outputs at backend creation time. diff --git a/.changeset/extension-point-middleware-backend-defaults.md b/.changeset/extension-point-middleware-backend-defaults.md new file mode 100644 index 0000000000..73add7bc7f --- /dev/null +++ b/.changeset/extension-point-middleware-backend-defaults.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': patch +--- + +Exported `defaultServiceFactories` to allow use with `createSpecializedBackend` for advanced configuration like `extensionPointFactoryMiddleware`. diff --git a/.changeset/fancy-years-camp.md b/.changeset/fancy-years-camp.md deleted file mode 100644 index 41782d4e0c..0000000000 --- a/.changeset/fancy-years-camp.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@backstage/plugin-notifications-backend': minor -'@backstage/plugin-notifications-common': minor ---- - -Adds support for default configuration for an entire notification channel. -This setting will also be inherited down to origins and topics while still respecting the users individual choices. - -This will be handy if you want to use a "opt-in" strategy. diff --git a/.changeset/fast-tables-sink.md b/.changeset/fast-tables-sink.md deleted file mode 100644 index a7c240fc5d..0000000000 --- a/.changeset/fast-tables-sink.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Improved visual consistency of PasswordField, SearchField, and MenuAutocomplete components. diff --git a/.changeset/fast-tools-mate.md b/.changeset/fast-tools-mate.md deleted file mode 100644 index 2e212ce1b3..0000000000 --- a/.changeset/fast-tools-mate.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -'@backstage/plugin-notifications-backend': patch ---- - -Show default settings for notifications even before receiving first notification. - -Previously, it was not possible for the users to see or modify their notification settings until they had received at -least one notification from specific origin or topic. -This update ensures that default settings are displayed from the outset, -allowing users to customize their preferences immediately. diff --git a/.changeset/fine-hands-return.md b/.changeset/fine-hands-return.md deleted file mode 100644 index d3bf69c9b9..0000000000 --- a/.changeset/fine-hands-return.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Fix default text color in Backstage UI diff --git a/.changeset/five-seas-jam.md b/.changeset/five-seas-jam.md deleted file mode 100644 index 4aa481422f..0000000000 --- a/.changeset/five-seas-jam.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications-backend-module-email': patch ---- - -Updated `AWS SES` client to version 2 to support `nodemailer` version 7. diff --git a/.changeset/fix-alter-target-nullability.md b/.changeset/fix-alter-target-nullability.md new file mode 100644 index 0000000000..9116b9d968 --- /dev/null +++ b/.changeset/fix-alter-target-nullability.md @@ -0,0 +1,11 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Fixed several database migration `down` functions that were not properly reversible, causing the SQL report to show warnings: + +- `20241003170511_alter_target_in_locations.js`: both `up` and `down` now include `.notNullable()` when altering the `locations.target` column, preventing the `NOT NULL` constraint from being accidentally dropped when widening the column type from `varchar(255)` to `text`. +- `20220116144621_remove_legacy.js`: the `down` function now properly recreates the three dropped legacy tables (`entities`, `entities_search`, `entities_relations`) with correct columns and indices. +- `20210302150147_refresh_state.js`: the `down` function now drops dependent tables in the correct order (avoiding a FK constraint violation) and fixes a typo where the table was referred to as `references` instead of `refresh_state_references`. +- `20201005122705_add_entity_full_name.js`: the `down` function now drops the `full_name` column from `entities` (not `entities_search`), and restores the `entities_unique_name` index with the correct column order `(kind, name, namespace)`. +- `20200702153613_entities.js`: the `down` function now uses `table.integer('generation')` instead of `table.string('generation')`, restoring the correct column type. diff --git a/.changeset/fix-dialog-dark-theme-selector.md b/.changeset/fix-dialog-dark-theme-selector.md new file mode 100644 index 0000000000..287278d3a5 --- /dev/null +++ b/.changeset/fix-dialog-dark-theme-selector.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Fixed dark mode background for Dialog component by correcting the theme attribute selector from `data-theme` to `data-theme-mode`. + +**Affected components:** Dialog diff --git a/.changeset/fix-embedded-postgres-config-paths.md b/.changeset/fix-embedded-postgres-config-paths.md new file mode 100644 index 0000000000..7697636fb5 --- /dev/null +++ b/.changeset/fix-embedded-postgres-config-paths.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli-module-build': patch +--- + +Fixed config path resolution for the embedded-postgres database client detection to resolve paths relative to the target package directory rather than the workspace root. diff --git a/.changeset/fix-implicit-any-renderInTestApp.md b/.changeset/fix-implicit-any-renderInTestApp.md new file mode 100644 index 0000000000..1db848a202 --- /dev/null +++ b/.changeset/fix-implicit-any-renderInTestApp.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-test-utils': patch +--- + +Added explicit type annotations to `.map()` callback parameters in `renderInTestApp` to avoid implicit `any` errors with newer TypeScript versions. diff --git a/.changeset/fix-tabs-active-indicator-disappearing.md b/.changeset/fix-tabs-active-indicator-disappearing.md new file mode 100644 index 0000000000..c8537940c9 --- /dev/null +++ b/.changeset/fix-tabs-active-indicator-disappearing.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Fixed an issue where the active tab indicator would disappear shortly after page load for uncontrolled Tabs. + +**Affected components:** Tabs diff --git a/.changeset/fix-text-truncate-prop.md b/.changeset/fix-text-truncate-prop.md deleted file mode 100644 index 26aee3280c..0000000000 --- a/.changeset/fix-text-truncate-prop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Fixed Text component to prevent `truncate` prop from being spread to the underlying DOM element. diff --git a/.changeset/fix-widget-resize-after-edit.md b/.changeset/fix-widget-resize-after-edit.md new file mode 100644 index 0000000000..d1f2a1b017 --- /dev/null +++ b/.changeset/fix-widget-resize-after-edit.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-home': patch +--- + +Fixed widgets not being movable or resizable after saved edits. Previously, entering edit mode didn't restore `isDraggable` and `isResizable`. diff --git a/.changeset/flat-paws-do.md b/.changeset/flat-paws-do.md deleted file mode 100644 index 13d78c1423..0000000000 --- a/.changeset/flat-paws-do.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-openapi': patch ---- - -fix wrong dereferencing for AsyncApi 3 documents diff --git a/.changeset/free-ways-flow.md b/.changeset/free-ways-flow.md new file mode 100644 index 0000000000..4359076b60 --- /dev/null +++ b/.changeset/free-ways-flow.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Made the TechDocs sidebar positioning at tablet breakpoints configurable via CSS custom properties, allowing apps with custom sidebar widths to override the defaults. The available properties are `--techdocs-sidebar-closed-offset-pinned`, `--techdocs-sidebar-closed-offset-collapsed`, and `--techdocs-sidebar-open-translate`. diff --git a/.changeset/fruity-snails-laugh.md b/.changeset/fruity-snails-laugh.md deleted file mode 100644 index 3b7775efac..0000000000 --- a/.changeset/fruity-snails-laugh.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-gitlab': minor ---- - -fix scaffolder action createDeployToken to allow usage of oauth tokens diff --git a/.changeset/funny-stars-open.md b/.changeset/funny-stars-open.md deleted file mode 100644 index 336e68bac1..0000000000 --- a/.changeset/funny-stars-open.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Fix inconsistent behavior in the `new` command for the `@internal` scope: it now consistently defaults to the `backstage-plugin-` infix whether the `--scope` option is not set or it's set to `internal`. diff --git a/.changeset/gentle-bikes-relax.md b/.changeset/gentle-bikes-relax.md deleted file mode 100644 index 7a4aeda5f8..0000000000 --- a/.changeset/gentle-bikes-relax.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs-module-addons-contrib': patch ---- - -ExpandableCollapse Techdocs Addon was breaking native sidebar collapse on Firefox diff --git a/.changeset/giant-lamps-happen.md b/.changeset/giant-lamps-happen.md deleted file mode 100644 index 1bc4ae6eab..0000000000 --- a/.changeset/giant-lamps-happen.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Improved the Link component structure in Backstage UI. diff --git a/.changeset/gold-drinks-poke.md b/.changeset/gold-drinks-poke.md new file mode 100644 index 0000000000..46b0440bed --- /dev/null +++ b/.changeset/gold-drinks-poke.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +Updated React Aria dependencies to v1.17.0 and migrated imports from individual `@react-aria/*` and `@react-stately/*` packages to the monopackages (`react-aria`, `react-stately`). This fixes a type resolution error for `@react-types/table` that occurred in new app installations. diff --git a/.changeset/grumpy-planes-bet.md b/.changeset/grumpy-planes-bet.md deleted file mode 100644 index bf7d36be41..0000000000 --- a/.changeset/grumpy-planes-bet.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-react': patch ---- - -Fixed a bug in the Scaffolder's template parsing in the `useTemplateSchema` hook by removing the title instead of setting it to `undefined` diff --git a/.changeset/heavy-cars-wash.md b/.changeset/heavy-cars-wash.md deleted file mode 100644 index f3f80d9cc8..0000000000 --- a/.changeset/heavy-cars-wash.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-ldap': minor ---- - -Moved from `ldapjs` dependency to `ldapts` - -### Breaking Changes - -**Type Migration** - -Custom transformers must now accept `Entry` from `ldapts` instead of `SearchEntry` -from `ldapjs` The Entry type provides direct property access without need for -`.object()` or `.raw()` methods. - -If you have custom user or group transformers, update the signature from: - -```typescript -(vendor: LdapVendor, config: UserConfig, entry: SearchEntry) => - Promise; -``` - -to - -```typescript -(vendor: LdapVendor, config: UserConfig, entry: Entry) => - Promise; -``` - -**Search Options** - -Updated LDAP search configuration `typesOnly: false` → `attributeValues: true` -This inverts the boolean logic: `ldapjs` used negative form while `ldapts` uses -positive form. Both achieve the same result: retrieving attribute values rather -than just attribute names. - -Update LDAP search options in configuration from - -```yaml -options: - typesOnly: false -``` - -to - -```yaml -options: - attributeValues: true -``` - -**API Changes** Removed `LdapClient.searchStreaming()` method. Users should -migrate to `LdapClient.search()` instead - -If you're using `searchStreaming` directly: - -```typescript -// Before -await client.searchStreaming(dn, options, async entry => { - // process each entry -}); - -// After -const entries = await client.search(dn, options); -for (const entry of entries) { - // process each entry -} -``` - -> **_NOTE:_**: Both methods have always loaded all entries into memory. The -> `searchStreaming` method was only needed internally to handle `ldapjs`'s -> event-based API. diff --git a/.changeset/honest-lamps-occur.md b/.changeset/honest-lamps-occur.md deleted file mode 100644 index 27b2eb44f5..0000000000 --- a/.changeset/honest-lamps-occur.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Fixed dialog backdrop appearance in dark mode. diff --git a/.changeset/honest-pandas-win.md b/.changeset/honest-pandas-win.md deleted file mode 100644 index a9dc5d7e9d..0000000000 --- a/.changeset/honest-pandas-win.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-plugin-api': minor ---- - -Promote `instanceMetadata` service to main entrypoint. diff --git a/.changeset/huge-taxis-grab.md b/.changeset/huge-taxis-grab.md deleted file mode 100644 index a021fcb82c..0000000000 --- a/.changeset/huge-taxis-grab.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Migrated CellProfile component from Base UI Avatar to Backstage UI Avatar component. diff --git a/.changeset/itchy-bars-smell.md b/.changeset/itchy-bars-smell.md deleted file mode 100644 index b82779502e..0000000000 --- a/.changeset/itchy-bars-smell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': minor ---- - -Fixing styles on SearchField in Backstage UI after migration to CSS modules. `SearchField` has now its own set of class names. We previously used class names from `TextField` but this approach was creating some confusion so going forward in your theme you'll be able to theme `TextField` and `SearchField` separately. diff --git a/.changeset/legal-weeks-walk.md b/.changeset/legal-weeks-walk.md deleted file mode 100644 index 2068ff40b2..0000000000 --- a/.changeset/legal-weeks-walk.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Added `@backstage/plugin-app-visualizer` to the app in the `--next` template. diff --git a/.changeset/lemon-spies-sleep.md b/.changeset/lemon-spies-sleep.md deleted file mode 100644 index 15ec451f62..0000000000 --- a/.changeset/lemon-spies-sleep.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-react': patch ---- - -Apply default ordering of templates diff --git a/.changeset/little-dingos-study.md b/.changeset/little-dingos-study.md deleted file mode 100644 index 2defb8ed1f..0000000000 --- a/.changeset/little-dingos-study.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -'@backstage/ui': minor ---- - -**BREAKING**: Removed central `componentDefinitions` object and related type utilities (`ComponentDefinitionName`, `ComponentClassNames`). - -Component definitions are primarily intended for documenting the CSS class API for theming purposes, not for programmatic use in JavaScript/TypeScript. - -**Migration Guide:** - -If you were using component definitions or class names to build custom components, we recommend migrating to either: - -- Use Backstage UI components directly as building blocks, or -- Duplicate the component CSS in your own stylesheets instead of relying on internal class names diff --git a/.changeset/long-humans-sink.md b/.changeset/long-humans-sink.md deleted file mode 100644 index 9959caf1b1..0000000000 --- a/.changeset/long-humans-sink.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Avatar components in x-small and small sizes now display only one initial instead of two, improving readability at smaller dimensions. diff --git a/.changeset/long-impalas-burn.md b/.changeset/long-impalas-burn.md deleted file mode 100644 index ef70f65f78..0000000000 --- a/.changeset/long-impalas-burn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/repo-tools': minor ---- - -The `package-docs` command will now automatically use a `typedoc.json` file if one exists at the root of your project. diff --git a/.changeset/loud-carpets-throw.md b/.changeset/loud-carpets-throw.md deleted file mode 100644 index 169703d815..0000000000 --- a/.changeset/loud-carpets-throw.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Added a template for the `yarn new` command to create an catalog entity provider. To add this template to an explicit list in the root `package.json`, use `@backstage/cli/templates/catalog-provider-module`. diff --git a/.changeset/lucky-parrots-yell.md b/.changeset/lucky-parrots-yell.md deleted file mode 100644 index 7533ed6e56..0000000000 --- a/.changeset/lucky-parrots-yell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': patch ---- - -Fixed responsiveness issues with catalog entity content layout using the new frontend system. diff --git a/.changeset/mighty-flowers-travel.md b/.changeset/mighty-flowers-travel.md deleted file mode 100644 index 86c5706b90..0000000000 --- a/.changeset/mighty-flowers-travel.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-backend': minor ---- - -Fix `user_created_at` migration causing `SQLiteError` regarding use of non-constants for defaults diff --git a/.changeset/moody-plums-add.md b/.changeset/moody-plums-add.md deleted file mode 100644 index a88e11b347..0000000000 --- a/.changeset/moody-plums-add.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-gateway-backend': minor ---- - -Update usage of the `instanceMetadata` service. diff --git a/.changeset/move-registermswtesthooks-to-test-utils.md b/.changeset/move-registermswtesthooks-to-test-utils.md new file mode 100644 index 0000000000..aa4de48ab4 --- /dev/null +++ b/.changeset/move-registermswtesthooks-to-test-utils.md @@ -0,0 +1,5 @@ +--- +'@backstage/integration': patch +--- + +Moved `registerMswTestHooks` to test files. diff --git a/.changeset/ninety-cobras-feel.md b/.changeset/ninety-cobras-feel.md deleted file mode 100644 index e287ef33ed..0000000000 --- a/.changeset/ninety-cobras-feel.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs': patch ---- - -Fixed an issue where the entire TechDocs page would re-render when navigating between pages within the same entity's documentation. diff --git a/.changeset/odd-states-melt.md b/.changeset/odd-states-melt.md deleted file mode 100644 index 514e890bab..0000000000 --- a/.changeset/odd-states-melt.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-plugin-api': minor ---- - -Explicitly mark `coreServices.rootInstanceMetadata` as a root service. diff --git a/.changeset/open-cloths-listen.md b/.changeset/open-cloths-listen.md deleted file mode 100644 index 3af368c7ce..0000000000 --- a/.changeset/open-cloths-listen.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-test-utils': patch ---- - -Tweak some of the mock services to have more precise types diff --git a/.changeset/open-items-open.md b/.changeset/open-items-open.md deleted file mode 100644 index 12fcefab4a..0000000000 --- a/.changeset/open-items-open.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-app-api': minor ---- - -Updates API for `instanceMetadata` service to return a list of plugins not features. diff --git a/.changeset/plenty-spiders-lead.md b/.changeset/plenty-spiders-lead.md deleted file mode 100644 index 48db0d5522..0000000000 --- a/.changeset/plenty-spiders-lead.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-plugin-api': minor ---- - -Renamed `ExtensionDefinition` to `OverridableExtensionDefinition` and introduced a slimmer `ExtensionDefinition` type that does not include override methods. The overridable type is generally used as an output type, while plain `ExtensionDefinition`s are used for input. This reduces type conflicts across different of `@backstage/frontend-plugin-api`, improving long-term compatibility. diff --git a/.changeset/plugin-relative-extensions.md b/.changeset/plugin-relative-extensions.md deleted file mode 100644 index dc27a2283f..0000000000 --- a/.changeset/plugin-relative-extensions.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -Added support for plugin-relative `attachTo` declarations for extension definitions. This allows for the creation of extension and extension blueprints that attach to other extensions of a particular `kind` in the same plugin, rather than needing to provide the exact extension ID. This is particularly useful when wanting to provide extension blueprints with a built-in hierarchy where the extensions created from one blueprint attach to extensions created from the other blueprint, for example: - -```ts -// kind: 'tabbed-page' -const parentPage = TabbedPageBlueprint.make({ - params: {....} -}) -// attachTo: { kind: 'tabbed-page', input: 'tabs' } -const child1 = TabContentBlueprint.make({ - name: 'tab1', - params: {....} -}) -``` diff --git a/.changeset/polite-eggs-dance.md b/.changeset/polite-eggs-dance.md deleted file mode 100644 index e9725be567..0000000000 --- a/.changeset/polite-eggs-dance.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Removed `@base-ui-components/react` dependency as all components now use React Aria Components. diff --git a/.changeset/polite-seas-divide.md b/.changeset/polite-seas-divide.md deleted file mode 100644 index e265e6df90..0000000000 --- a/.changeset/polite-seas-divide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Fix the default font size in Backstage UI. diff --git a/.changeset/pre.json b/.changeset/pre.json index f65042692e..2b8754aea1 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -1,293 +1,229 @@ { - "mode": "exit", + "mode": "pre", "tag": "next", "initialVersions": { - "example-app": "0.2.114", - "@backstage/app-defaults": "1.7.1", - "example-app-next": "0.0.28", - "app-next-example-plugin": "0.0.28", - "example-backend": "0.0.43", - "@backstage/backend-app-api": "1.2.8", - "@backstage/backend-defaults": "0.13.0", - "@backstage/backend-dev-utils": "0.1.5", - "@backstage/backend-dynamic-feature-service": "0.7.5", - "@backstage/backend-openapi-utils": "0.6.2", - "@backstage/backend-plugin-api": "1.4.4", - "@backstage/backend-test-utils": "1.9.1", - "@backstage/catalog-client": "1.12.0", - "@backstage/catalog-model": "1.7.5", - "@backstage/cli": "0.34.4", - "@backstage/cli-common": "0.1.15", - "@backstage/cli-node": "0.2.14", - "@backstage/codemods": "0.1.52", - "@backstage/config": "1.3.5", - "@backstage/config-loader": "1.10.5", - "@backstage/core-app-api": "1.19.1", - "@backstage/core-compat-api": "0.5.3", - "@backstage/core-components": "0.18.2", - "@backstage/core-plugin-api": "1.11.1", - "@backstage/create-app": "0.7.5", - "@backstage/dev-utils": "1.1.15", - "e2e-test": "0.2.33", - "@backstage/e2e-test-utils": "0.1.1", - "@backstage/errors": "1.2.7", - "@backstage/eslint-plugin": "0.1.12", - "@backstage/frontend-app-api": "0.13.1", - "@backstage/frontend-defaults": "0.3.2", - "@backstage/frontend-dynamic-feature-loader": "0.1.6", - "@internal/frontend": "0.0.14", - "@backstage/frontend-plugin-api": "0.12.1", - "@backstage/frontend-test-utils": "0.4.0", - "@backstage/integration": "1.18.1", - "@backstage/integration-aws-node": "0.1.18", - "@backstage/integration-react": "1.2.11", + "example-app": "0.0.34", + "@backstage/app-defaults": "1.7.7", + "app-example-plugin": "0.0.34", + "example-app-legacy": "0.2.120", + "example-backend": "0.0.49", + "@backstage/backend-app-api": "1.6.1", + "@backstage/backend-defaults": "0.17.0", + "@backstage/backend-dev-utils": "0.1.7", + "@backstage/backend-dynamic-feature-service": "0.8.1", + "@internal/backend": "0.0.1", + "@backstage/backend-openapi-utils": "0.6.8", + "@backstage/backend-plugin-api": "1.9.0", + "@backstage/backend-test-utils": "1.11.2", + "@backstage/catalog-client": "1.15.0", + "@backstage/catalog-model": "1.8.0", + "@backstage/cli": "0.36.1", + "@backstage/cli-common": "0.2.1", + "@backstage/cli-defaults": "0.1.1", + "@internal/cli": "0.0.3", + "@backstage/cli-module-actions": "0.1.0", + "@backstage/cli-module-auth": "0.1.1", + "@backstage/cli-module-build": "0.1.1", + "@backstage/cli-module-config": "0.1.1", + "@backstage/cli-module-github": "0.1.1", + "@backstage/cli-module-info": "0.1.1", + "@backstage/cli-module-lint": "0.1.1", + "@backstage/cli-module-maintenance": "0.1.1", + "@backstage/cli-module-migrate": "0.1.1", + "@backstage/cli-module-new": "0.1.2", + "@backstage/cli-module-test-jest": "0.1.1", + "@backstage/cli-module-translations": "0.1.1", + "@backstage/cli-node": "0.3.1", + "@backstage/codemods": "0.1.56", + "@backstage/config": "1.3.7", + "@backstage/config-loader": "1.10.10", + "@backstage/core-app-api": "1.20.0", + "@backstage/core-compat-api": "0.5.10", + "@backstage/core-components": "0.18.9", + "@backstage/core-plugin-api": "1.12.5", + "@backstage/create-app": "0.8.2", + "@backstage/dev-utils": "1.1.22", + "e2e-test": "0.2.39", + "@backstage/e2e-test-utils": "0.1.2", + "@backstage/errors": "1.3.0", + "@backstage/eslint-plugin": "0.2.3", + "@backstage/filter-predicates": "0.1.2", + "@backstage/frontend-app-api": "0.16.2", + "@backstage/frontend-defaults": "0.5.1", + "@backstage/frontend-dev-utils": "0.1.1", + "@backstage/frontend-dynamic-feature-loader": "0.1.11", + "@internal/frontend": "0.0.19", + "@backstage/frontend-plugin-api": "0.16.0", + "@backstage/frontend-test-utils": "0.5.2", + "@backstage/integration": "2.0.1", + "@backstage/integration-aws-node": "0.1.21", + "@backstage/integration-react": "1.2.17", + "@backstage/module-federation-common": "0.1.3", "@internal/opaque": "0.0.1", "@backstage/release-manifests": "0.0.13", - "@backstage/repo-tools": "0.15.3", - "@internal/scaffolder": "0.0.14", - "@techdocs/cli": "1.10.0", - "techdocs-cli-embedded-app": "0.2.113", - "@backstage/test-utils": "1.7.12", - "@backstage/theme": "0.7.0", + "@backstage/repo-tools": "0.17.1", + "@internal/scaffolder": "0.0.20", + "@techdocs/cli": "1.10.7", + "techdocs-cli-embedded-app": "0.2.119", + "@backstage/test-utils": "1.7.17", + "@backstage/theme": "0.7.3", "@backstage/types": "1.2.2", - "@backstage/ui": "0.8.0", - "@backstage/version-bridge": "1.0.11", - "yarn-plugin-backstage": "0.0.7", - "@backstage/plugin-api-docs": "0.13.0", - "@backstage/plugin-api-docs-module-protoc-gen-doc": "0.1.10", - "@backstage/plugin-app": "0.3.1", - "@backstage/plugin-app-backend": "0.5.7", - "@backstage/plugin-app-node": "0.1.38", - "@backstage/plugin-app-visualizer": "0.1.24", - "@backstage/plugin-auth": "0.1.1", - "@backstage/plugin-auth-backend": "0.25.5", - "@backstage/plugin-auth-backend-module-atlassian-provider": "0.4.8", - "@backstage/plugin-auth-backend-module-auth0-provider": "0.2.8", - "@backstage/plugin-auth-backend-module-aws-alb-provider": "0.4.8", - "@backstage/plugin-auth-backend-module-azure-easyauth-provider": "0.2.13", - "@backstage/plugin-auth-backend-module-bitbucket-provider": "0.3.8", - "@backstage/plugin-auth-backend-module-bitbucket-server-provider": "0.2.8", - "@backstage/plugin-auth-backend-module-cloudflare-access-provider": "0.4.8", - "@backstage/plugin-auth-backend-module-gcp-iap-provider": "0.4.8", - "@backstage/plugin-auth-backend-module-github-provider": "0.3.8", - "@backstage/plugin-auth-backend-module-gitlab-provider": "0.3.8", - "@backstage/plugin-auth-backend-module-google-provider": "0.3.8", - "@backstage/plugin-auth-backend-module-guest-provider": "0.2.13", - "@backstage/plugin-auth-backend-module-microsoft-provider": "0.3.8", - "@backstage/plugin-auth-backend-module-oauth2-provider": "0.4.8", - "@backstage/plugin-auth-backend-module-oauth2-proxy-provider": "0.2.13", - "@backstage/plugin-auth-backend-module-oidc-provider": "0.4.8", - "@backstage/plugin-auth-backend-module-okta-provider": "0.2.8", - "@backstage/plugin-auth-backend-module-onelogin-provider": "0.3.8", - "@backstage/plugin-auth-backend-module-openshift-provider": "0.1.1", - "@backstage/plugin-auth-backend-module-pinniped-provider": "0.3.8", - "@backstage/plugin-auth-backend-module-vmware-cloud-provider": "0.5.8", - "@backstage/plugin-auth-node": "0.6.8", - "@backstage/plugin-auth-react": "0.1.20", - "@backstage/plugin-bitbucket-cloud-common": "0.3.3", - "@backstage/plugin-catalog": "1.31.4", - "@backstage/plugin-catalog-backend": "3.1.2", - "@backstage/plugin-catalog-backend-module-aws": "0.4.16", - "@backstage/plugin-catalog-backend-module-azure": "0.3.10", - "@backstage/plugin-catalog-backend-module-backstage-openapi": "0.5.7", - "@backstage/plugin-catalog-backend-module-bitbucket-cloud": "0.5.4", - "@backstage/plugin-catalog-backend-module-bitbucket-server": "0.5.4", - "@backstage/plugin-catalog-backend-module-gcp": "0.3.13", - "@backstage/plugin-catalog-backend-module-gerrit": "0.3.7", - "@backstage/plugin-catalog-backend-module-gitea": "0.1.5", - "@backstage/plugin-catalog-backend-module-github": "0.11.1", - "@backstage/plugin-catalog-backend-module-github-org": "0.3.15", - "@backstage/plugin-catalog-backend-module-gitlab": "0.7.4", - "@backstage/plugin-catalog-backend-module-gitlab-org": "0.2.14", - "@backstage/plugin-catalog-backend-module-incremental-ingestion": "0.7.5", - "@backstage/plugin-catalog-backend-module-ldap": "0.11.10", - "@backstage/plugin-catalog-backend-module-logs": "0.1.15", - "@backstage/plugin-catalog-backend-module-msgraph": "0.8.1", - "@backstage/plugin-catalog-backend-module-openapi": "0.2.15", - "@backstage/plugin-catalog-backend-module-puppetdb": "0.2.15", - "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "0.2.13", - "@backstage/plugin-catalog-backend-module-unprocessed": "0.6.5", - "@backstage/plugin-catalog-common": "1.1.6", - "@backstage/plugin-catalog-graph": "0.5.2", - "@backstage/plugin-catalog-import": "0.13.6", - "@backstage/plugin-catalog-node": "1.19.1", - "@backstage/plugin-catalog-react": "1.21.2", - "@backstage/plugin-catalog-unprocessed-entities": "0.2.22", - "@backstage/plugin-catalog-unprocessed-entities-common": "0.0.10", - "@backstage/plugin-config-schema": "0.1.73", - "@backstage/plugin-devtools": "0.1.32", - "@backstage/plugin-devtools-backend": "0.5.10", - "@backstage/plugin-devtools-common": "0.1.18", - "@backstage/plugin-events-backend": "0.5.7", - "@backstage/plugin-events-backend-module-aws-sqs": "0.4.16", - "@backstage/plugin-events-backend-module-azure": "0.2.25", - "@backstage/plugin-events-backend-module-bitbucket-cloud": "0.2.25", - "@backstage/plugin-events-backend-module-bitbucket-server": "0.1.6", - "@backstage/plugin-events-backend-module-gerrit": "0.2.25", - "@backstage/plugin-events-backend-module-github": "0.4.5", - "@backstage/plugin-events-backend-module-gitlab": "0.3.6", - "@backstage/plugin-events-backend-module-google-pubsub": "0.1.5", - "@backstage/plugin-events-backend-module-kafka": "0.1.4", - "@backstage/plugin-events-backend-test-utils": "0.1.49", - "@backstage/plugin-events-node": "0.4.16", - "@internal/plugin-todo-list": "1.0.44", - "@internal/plugin-todo-list-backend": "1.0.44", - "@internal/plugin-todo-list-common": "1.0.27", - "@backstage/plugin-gateway-backend": "1.0.6", - "@backstage/plugin-home": "0.8.13", - "@backstage/plugin-home-react": "0.1.31", - "@backstage/plugin-kubernetes": "0.12.12", - "@backstage/plugin-kubernetes-backend": "0.20.3", - "@backstage/plugin-kubernetes-cluster": "0.0.30", - "@backstage/plugin-kubernetes-common": "0.9.7", - "@backstage/plugin-kubernetes-node": "0.3.5", - "@backstage/plugin-kubernetes-react": "0.5.12", - "@backstage/plugin-mcp-actions-backend": "0.1.4", - "@backstage/plugin-mui-to-bui": "0.2.0", - "@backstage/plugin-notifications": "0.5.10", - "@backstage/plugin-notifications-backend": "0.5.11", - "@backstage/plugin-notifications-backend-module-email": "0.3.14", - "@backstage/plugin-notifications-backend-module-slack": "0.2.0", - "@backstage/plugin-notifications-common": "0.1.1", - "@backstage/plugin-notifications-node": "0.2.20", - "@backstage/plugin-org": "0.6.45", - "@backstage/plugin-org-react": "0.1.43", - "@backstage/plugin-permission-backend": "0.7.5", - "@backstage/plugin-permission-backend-module-allow-all-policy": "0.2.13", - "@backstage/plugin-permission-common": "0.9.2", - "@backstage/plugin-permission-node": "0.10.5", - "@backstage/plugin-permission-react": "0.4.37", - "@backstage/plugin-proxy-backend": "0.6.7", - "@backstage/plugin-proxy-node": "0.1.9", - "@backstage/plugin-scaffolder": "1.34.2", - "@backstage/plugin-scaffolder-backend": "3.0.0", - "@backstage/plugin-scaffolder-backend-module-azure": "0.2.14", - "@backstage/plugin-scaffolder-backend-module-bitbucket": "0.3.15", - "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud": "0.2.14", - "@backstage/plugin-scaffolder-backend-module-bitbucket-server": "0.2.14", - "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "0.3.14", - "@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.3.16", - "@backstage/plugin-scaffolder-backend-module-gcp": "0.2.14", - "@backstage/plugin-scaffolder-backend-module-gerrit": "0.2.14", - "@backstage/plugin-scaffolder-backend-module-gitea": "0.2.14", - "@backstage/plugin-scaffolder-backend-module-github": "0.9.1", - "@backstage/plugin-scaffolder-backend-module-gitlab": "0.9.6", - "@backstage/plugin-scaffolder-backend-module-notifications": "0.1.15", - "@backstage/plugin-scaffolder-backend-module-rails": "0.5.14", - "@backstage/plugin-scaffolder-backend-module-sentry": "0.2.14", - "@backstage/plugin-scaffolder-backend-module-yeoman": "0.4.15", - "@backstage/plugin-scaffolder-common": "1.7.2", - "@backstage/plugin-scaffolder-node": "0.12.0", - "@backstage/plugin-scaffolder-node-test-utils": "0.3.4", - "@backstage/plugin-scaffolder-react": "1.19.2", - "@backstage/plugin-search": "1.4.31", - "@backstage/plugin-search-backend": "2.0.7", - "@backstage/plugin-search-backend-module-catalog": "0.3.9", - "@backstage/plugin-search-backend-module-elasticsearch": "1.7.7", - "@backstage/plugin-search-backend-module-explore": "0.3.8", - "@backstage/plugin-search-backend-module-pg": "0.5.49", - "@backstage/plugin-search-backend-module-stack-overflow-collator": "0.3.14", - "@backstage/plugin-search-backend-module-techdocs": "0.4.7", - "@backstage/plugin-search-backend-node": "1.3.16", - "@backstage/plugin-search-common": "1.2.20", - "@backstage/plugin-search-react": "1.9.5", - "@backstage/plugin-signals": "0.0.24", - "@backstage/plugin-signals-backend": "0.3.9", - "@backstage/plugin-signals-node": "0.1.25", - "@backstage/plugin-signals-react": "0.0.16", - "@backstage/plugin-techdocs": "1.15.1", - "@backstage/plugin-techdocs-addons-test-utils": "1.1.1", - "@backstage/plugin-techdocs-backend": "2.1.1", + "@backstage/ui": "0.14.0", + "@backstage/version-bridge": "1.0.12", + "yarn-plugin-backstage": "0.0.11", + "@backstage/plugin-api-docs": "0.14.0", + "@backstage/plugin-api-docs-module-protoc-gen-doc": "0.1.11", + "@backstage/plugin-app": "0.4.3", + "@backstage/plugin-app-backend": "0.5.13", + "@backstage/plugin-app-node": "0.1.44", + "@backstage/plugin-app-react": "0.2.2", + "@backstage/plugin-app-visualizer": "0.2.2", + "@backstage/plugin-auth": "0.1.7", + "@backstage/plugin-auth-backend": "0.28.0", + "@backstage/plugin-auth-backend-module-atlassian-provider": "0.4.14", + "@backstage/plugin-auth-backend-module-auth0-provider": "0.4.0", + "@backstage/plugin-auth-backend-module-aws-alb-provider": "0.4.15", + "@backstage/plugin-auth-backend-module-azure-easyauth-provider": "0.2.19", + "@backstage/plugin-auth-backend-module-bitbucket-provider": "0.3.14", + "@backstage/plugin-auth-backend-module-bitbucket-server-provider": "0.2.14", + "@backstage/plugin-auth-backend-module-cloudflare-access-provider": "0.4.14", + "@backstage/plugin-auth-backend-module-gcp-iap-provider": "0.4.14", + "@backstage/plugin-auth-backend-module-github-provider": "0.5.2", + "@backstage/plugin-auth-backend-module-gitlab-provider": "0.4.2", + "@backstage/plugin-auth-backend-module-google-provider": "0.3.14", + "@backstage/plugin-auth-backend-module-guest-provider": "0.2.18", + "@backstage/plugin-auth-backend-module-microsoft-provider": "0.3.14", + "@backstage/plugin-auth-backend-module-oauth2-provider": "0.4.14", + "@backstage/plugin-auth-backend-module-oauth2-proxy-provider": "0.2.19", + "@backstage/plugin-auth-backend-module-oidc-provider": "0.4.15", + "@backstage/plugin-auth-backend-module-okta-provider": "0.2.14", + "@backstage/plugin-auth-backend-module-onelogin-provider": "0.3.14", + "@backstage/plugin-auth-backend-module-openshift-provider": "0.1.6", + "@backstage/plugin-auth-backend-module-pinniped-provider": "0.3.13", + "@backstage/plugin-auth-backend-module-vmware-cloud-provider": "0.5.13", + "@backstage/plugin-auth-node": "0.7.0", + "@backstage/plugin-auth-react": "0.1.26", + "@backstage/plugin-bitbucket-cloud-common": "0.3.9", + "@backstage/plugin-catalog": "2.0.2", + "@backstage/plugin-catalog-backend": "3.6.0", + "@backstage/plugin-catalog-backend-module-aws": "0.4.22", + "@backstage/plugin-catalog-backend-module-azure": "0.3.16", + "@backstage/plugin-catalog-backend-module-backstage-openapi": "0.5.13", + "@backstage/plugin-catalog-backend-module-bitbucket-cloud": "0.5.10", + "@backstage/plugin-catalog-backend-module-bitbucket-server": "0.5.10", + "@backstage/plugin-catalog-backend-module-gcp": "0.3.18", + "@backstage/plugin-catalog-backend-module-gerrit": "0.3.13", + "@backstage/plugin-catalog-backend-module-gitea": "0.1.11", + "@backstage/plugin-catalog-backend-module-github": "0.13.1", + "@backstage/plugin-catalog-backend-module-github-org": "0.3.21", + "@backstage/plugin-catalog-backend-module-gitlab": "0.8.2", + "@backstage/plugin-catalog-backend-module-gitlab-org": "0.2.20", + "@backstage/plugin-catalog-backend-module-incremental-ingestion": "0.7.11", + "@backstage/plugin-catalog-backend-module-ldap": "0.12.4", + "@backstage/plugin-catalog-backend-module-logs": "0.1.21", + "@backstage/plugin-catalog-backend-module-msgraph": "0.9.2", + "@backstage/plugin-catalog-backend-module-openapi": "0.2.21", + "@backstage/plugin-catalog-backend-module-puppetdb": "0.2.21", + "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "0.2.19", + "@backstage/plugin-catalog-backend-module-unprocessed": "0.6.10", + "@backstage/plugin-catalog-common": "1.1.9", + "@backstage/plugin-catalog-graph": "0.6.1", + "@backstage/plugin-catalog-import": "0.13.12", + "@backstage/plugin-catalog-node": "2.2.0", + "@backstage/plugin-catalog-react": "2.1.2", + "@backstage/plugin-catalog-unprocessed-entities": "0.2.29", + "@backstage/plugin-catalog-unprocessed-entities-common": "0.0.14", + "@backstage/plugin-config-schema": "0.1.79", + "@backstage/plugin-devtools": "0.1.38", + "@backstage/plugin-devtools-backend": "0.5.16", + "@backstage/plugin-devtools-common": "0.1.24", + "@backstage/plugin-devtools-react": "0.2.1", + "@backstage/plugin-events-backend": "0.6.1", + "@backstage/plugin-events-backend-module-aws-sqs": "0.4.21", + "@backstage/plugin-events-backend-module-azure": "0.2.30", + "@backstage/plugin-events-backend-module-bitbucket-cloud": "0.2.30", + "@backstage/plugin-events-backend-module-bitbucket-server": "0.1.11", + "@backstage/plugin-events-backend-module-gerrit": "0.2.30", + "@backstage/plugin-events-backend-module-github": "0.4.11", + "@backstage/plugin-events-backend-module-gitlab": "0.3.11", + "@backstage/plugin-events-backend-module-google-pubsub": "0.2.2", + "@backstage/plugin-events-backend-module-kafka": "0.3.3", + "@backstage/plugin-events-backend-test-utils": "0.1.54", + "@backstage/plugin-events-node": "0.4.21", + "@internal/plugin-todo-list": "1.0.50", + "@internal/plugin-todo-list-backend": "1.0.49", + "@internal/plugin-todo-list-common": "1.0.30", + "@backstage/plugin-gateway-backend": "1.1.4", + "@backstage/plugin-home": "0.9.4", + "@backstage/plugin-home-react": "0.1.37", + "@backstage/plugin-kubernetes": "0.12.18", + "@backstage/plugin-kubernetes-backend": "0.21.3", + "@backstage/plugin-kubernetes-cluster": "0.0.36", + "@backstage/plugin-kubernetes-common": "0.9.11", + "@backstage/plugin-kubernetes-node": "0.4.3", + "@backstage/plugin-kubernetes-react": "0.5.18", + "@backstage/plugin-mcp-actions-backend": "0.1.12", + "@backstage/plugin-mui-to-bui": "0.2.6", + "@backstage/plugin-notifications": "0.5.16", + "@backstage/plugin-notifications-backend": "0.6.4", + "@backstage/plugin-notifications-backend-module-email": "0.3.20", + "@backstage/plugin-notifications-backend-module-slack": "0.4.1", + "@backstage/plugin-notifications-common": "0.2.2", + "@backstage/plugin-notifications-node": "0.2.25", + "@backstage/plugin-org": "0.7.1", + "@backstage/plugin-org-react": "0.1.49", + "@backstage/plugin-permission-backend": "0.7.11", + "@backstage/plugin-permission-backend-module-allow-all-policy": "0.2.18", + "@backstage/plugin-permission-common": "0.9.8", + "@backstage/plugin-permission-node": "0.10.12", + "@backstage/plugin-permission-react": "0.5.0", + "@backstage/plugin-proxy-backend": "0.6.12", + "@backstage/plugin-proxy-node": "0.1.14", + "@backstage/plugin-scaffolder": "1.36.2", + "@backstage/plugin-scaffolder-backend": "3.4.0", + "@backstage/plugin-scaffolder-backend-module-azure": "0.2.20", + "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud": "0.3.5", + "@backstage/plugin-scaffolder-backend-module-bitbucket-server": "0.2.20", + "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "0.3.20", + "@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.3.22", + "@backstage/plugin-scaffolder-backend-module-gcp": "0.2.20", + "@backstage/plugin-scaffolder-backend-module-gerrit": "0.2.20", + "@backstage/plugin-scaffolder-backend-module-gitea": "0.2.20", + "@backstage/plugin-scaffolder-backend-module-github": "0.9.8", + "@backstage/plugin-scaffolder-backend-module-gitlab": "0.11.5", + "@backstage/plugin-scaffolder-backend-module-notifications": "0.1.21", + "@backstage/plugin-scaffolder-backend-module-rails": "0.5.20", + "@backstage/plugin-scaffolder-backend-module-sentry": "0.3.3", + "@backstage/plugin-scaffolder-backend-module-yeoman": "0.4.21", + "@backstage/plugin-scaffolder-common": "2.1.0", + "@backstage/plugin-scaffolder-node": "0.13.2", + "@backstage/plugin-scaffolder-node-test-utils": "0.3.10", + "@backstage/plugin-scaffolder-react": "1.20.1", + "@backstage/plugin-search": "1.7.1", + "@backstage/plugin-search-backend": "2.1.1", + "@backstage/plugin-search-backend-module-catalog": "0.3.14", + "@backstage/plugin-search-backend-module-elasticsearch": "1.8.2", + "@backstage/plugin-search-backend-module-explore": "0.3.13", + "@backstage/plugin-search-backend-module-pg": "0.5.54", + "@backstage/plugin-search-backend-module-stack-overflow-collator": "0.3.19", + "@backstage/plugin-search-backend-module-techdocs": "0.4.13", + "@backstage/plugin-search-backend-node": "1.4.3", + "@backstage/plugin-search-common": "1.2.23", + "@backstage/plugin-search-react": "1.11.1", + "@backstage/plugin-signals": "0.0.30", + "@backstage/plugin-signals-backend": "0.3.14", + "@backstage/plugin-signals-node": "0.2.0", + "@backstage/plugin-signals-react": "0.0.21", + "@backstage/plugin-techdocs": "1.17.3", + "@backstage/plugin-techdocs-addons-test-utils": "2.0.4", + "@backstage/plugin-techdocs-backend": "2.1.7", "@backstage/plugin-techdocs-common": "0.1.1", - "@backstage/plugin-techdocs-module-addons-contrib": "1.1.29", - "@backstage/plugin-techdocs-node": "1.13.8", - "@backstage/plugin-techdocs-react": "1.3.4", - "@backstage/plugin-user-settings": "0.8.27", - "@backstage/plugin-user-settings-backend": "0.3.7", - "@backstage/plugin-user-settings-common": "0.0.1" + "@backstage/plugin-techdocs-module-addons-contrib": "1.1.35", + "@backstage/plugin-techdocs-node": "1.14.5", + "@backstage/plugin-techdocs-react": "1.3.10", + "@backstage/plugin-user-settings": "0.9.2", + "@backstage/plugin-user-settings-backend": "0.4.2", + "@backstage/plugin-user-settings-common": "0.1.0" }, - "changesets": [ - "all-camels-agree", - "all-parrots-change", - "better-hats-cross", - "better-steaks-act", - "breezy-times-ring", - "bright-ears-send", - "busy-goats-create", - "clever-boats-clap", - "create-app-1761312116", - "create-app-1762897504", - "cruel-items-dig", - "cruel-plums-talk", - "deep-gifts-slide", - "easy-hands-grow", - "eighty-mails-leave", - "eighty-results-prove", - "eleven-carpets-win", - "eleven-lights-taste", - "every-ants-count", - "every-clocks-arrive", - "fancy-years-camp", - "fast-tables-sink", - "fast-tools-mate", - "fine-hands-return", - "five-seas-jam", - "fix-text-truncate-prop", - "flat-paws-do", - "fruity-snails-laugh", - "funny-stars-open", - "gentle-bikes-relax", - "giant-lamps-happen", - "grumpy-planes-bet", - "heavy-cars-wash", - "honest-lamps-occur", - "honest-pandas-win", - "huge-taxis-grab", - "itchy-bars-smell", - "legal-weeks-walk", - "lemon-spies-sleep", - "long-humans-sink", - "long-impalas-burn", - "loud-carpets-throw", - "moody-plums-add", - "ninety-cobras-feel", - "odd-states-melt", - "open-cloths-listen", - "open-items-open", - "plugin-relative-extensions", - "polite-eggs-dance", - "polite-seas-divide", - "pretty-kids-allow", - "purple-deer-bet", - "quiet-singers-pick", - "renovate-cd75086", - "rich-streets-rule", - "ripe-crabs-care", - "seven-cycles-pick", - "sharp-dragons-relate", - "short-sides-feel", - "silver-garlics-thank", - "sixty-clowns-run", - "smart-donuts-teach", - "solid-bees-agree", - "solid-dancers-march", - "stupid-doodles-love", - "ten-houses-attack", - "tender-regions-know", - "thirty-hoops-own", - "tough-sloths-spend", - "true-taxes-cover", - "twelve-spoons-feel", - "typescript-constructor-refactor", - "upset-teeth-add", - "warm-carrots-lead", - "warm-ducks-notice", - "warm-moments-repeat", - "warm-shrimps-clap", - "wicked-cycles-enter", - "wide-papers-run", - "wild-donkeys-sneeze", - "wild-owls-divide", - "yummy-socks-brake" - ] + "changesets": [] } diff --git a/.changeset/pretty-kids-allow.md b/.changeset/pretty-kids-allow.md deleted file mode 100644 index 6b3b11a895..0000000000 --- a/.changeset/pretty-kids-allow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-github': patch ---- - -Added configurable `pageSizes` for GitHub GraphQL API queries to prevent `RESOURCE_LIMITS_EXCEEDED` errors with organizations with large number of repositories. Please see the [GitHub Discovery documentation](https://backstage.io/docs/integrations/github/discovery#configuration) for new configuration options. diff --git a/.changeset/purple-deer-bet.md b/.changeset/purple-deer-bet.md deleted file mode 100644 index c60f2ada7a..0000000000 --- a/.changeset/purple-deer-bet.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-graph': patch ---- - -Ensure the catalog graph entity card respects the height prop so the visualization scales down properly on wide screens. diff --git a/.changeset/purple-pants-speak.md b/.changeset/purple-pants-speak.md deleted file mode 100644 index b46c4b991f..0000000000 --- a/.changeset/purple-pants-speak.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': patch ---- - -Fixed missing margins for entity warning content area on catalog entity page in NFS. diff --git a/.changeset/quiet-singers-pick.md b/.changeset/quiet-singers-pick.md deleted file mode 100644 index cc66db1bcd..0000000000 --- a/.changeset/quiet-singers-pick.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-signals': patch ---- - -Fixes a bug where the `SignalClient` would try to subscribe to the same channel twice after an error, instead of just once. diff --git a/.changeset/remove-duplicate-deps.md b/.changeset/remove-duplicate-deps.md new file mode 100644 index 0000000000..d4fd31e037 --- /dev/null +++ b/.changeset/remove-duplicate-deps.md @@ -0,0 +1,13 @@ +--- +'@backstage/backend-defaults': patch +'@backstage/cli': patch +'@backstage/core-compat-api': patch +'@backstage/plugin-app-backend': patch +'@backstage/plugin-auth-backend-module-oidc-provider': patch +'@backstage/plugin-auth-node': patch +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-catalog-react': patch +'@backstage/plugin-notifications-backend-module-slack': patch +--- + +Removed duplicated entries that appeared in both `dependencies` and `devDependencies`. diff --git a/.changeset/remove-portable-schema-deprecated-prop.md b/.changeset/remove-portable-schema-deprecated-prop.md new file mode 100644 index 0000000000..aea8426f65 --- /dev/null +++ b/.changeset/remove-portable-schema-deprecated-prop.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': minor +--- + +**BREAKING**: Removed the deprecated property form of `PortableSchema.schema`. The `schema` member is now a plain method that must be called as `schema()` — direct property access like `schema.type` or `schema.properties` is no longer supported. diff --git a/.changeset/remove-unused-deps.md b/.changeset/remove-unused-deps.md new file mode 100644 index 0000000000..028021bd28 --- /dev/null +++ b/.changeset/remove-unused-deps.md @@ -0,0 +1,22 @@ +--- +'@backstage/core-components': patch +'@backstage/plugin-app-backend': patch +'@backstage/plugin-catalog': patch +'@backstage/plugin-catalog-backend-module-gitlab': patch +'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch +'@backstage/plugin-catalog-graph': patch +'@backstage/plugin-devtools-backend': patch +'@backstage/plugin-kubernetes-node': patch +'@backstage/plugin-notifications-common': patch +'@backstage/plugin-notifications-node': patch +'@backstage/plugin-permission-backend': patch +'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch +'@backstage/plugin-scaffolder-backend-module-yeoman': patch +'@backstage/plugin-search-backend': patch +'@backstage/plugin-signals-node': patch +'@backstage/plugin-techdocs-react': patch +'@backstage/plugin-user-settings-backend': patch +'@techdocs/cli': patch +--- + +Removed unused dependencies that had no imports in source code. diff --git a/.changeset/remove-unused-getgithubintegrationconfig.md b/.changeset/remove-unused-getgithubintegrationconfig.md new file mode 100644 index 0000000000..243fc8759c --- /dev/null +++ b/.changeset/remove-unused-getgithubintegrationconfig.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-import': patch +--- + +Internal refactor diff --git a/.changeset/renovate-cd75086.md b/.changeset/renovate-cd75086.md deleted file mode 100644 index 5bc38af431..0000000000 --- a/.changeset/renovate-cd75086.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@backstage/plugin-kubernetes-backend': patch -'@backstage/plugin-kubernetes-common': patch -'@backstage/plugin-kubernetes-node': patch -'@backstage/plugin-kubernetes-react': patch ---- - -Updated dependency `@kubernetes/client-node` to `1.4.0`. diff --git a/.changeset/replace-duplicate-error-utilities.md b/.changeset/replace-duplicate-error-utilities.md new file mode 100644 index 0000000000..4e7d3d4318 --- /dev/null +++ b/.changeset/replace-duplicate-error-utilities.md @@ -0,0 +1,6 @@ +--- +'@backstage/repo-tools': patch +'@backstage/create-app': patch +--- + +Replaced internal error utilities with shared ones from `@backstage/cli-common`. diff --git a/.changeset/rich-streets-rule.md b/.changeset/rich-streets-rule.md deleted file mode 100644 index 07f431b6a7..0000000000 --- a/.changeset/rich-streets-rule.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Fix CSS layer ordering in Backstage UI to make sure component styles are loaded after tokens and base declarations. diff --git a/.changeset/ripe-crabs-care.md b/.changeset/ripe-crabs-care.md deleted file mode 100644 index d8cdc27652..0000000000 --- a/.changeset/ripe-crabs-care.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Fixed RadioGroup radio button ellipse distortion by preventing flex shrink and grow. diff --git a/.changeset/sad-rockets-rule.md b/.changeset/sad-rockets-rule.md deleted file mode 100644 index fe15580f99..0000000000 --- a/.changeset/sad-rockets-rule.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications-backend-module-slack': patch ---- - -When an error message is logged due to inability to send a message with the Slack SDK, include the Slack Channel ID in the message to aid debugging. diff --git a/.changeset/seven-cycles-pick.md b/.changeset/seven-cycles-pick.md deleted file mode 100644 index fbd1a3895f..0000000000 --- a/.changeset/seven-cycles-pick.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -'@backstage/ui': minor ---- - -**BREAKING**: Changed className prop behavior to augment default styles instead of being ignored or overriding them. - -Affected components: - -- Menu, MenuListBox, MenuAutocomplete, MenuAutocompleteListbox, MenuItem, MenuListBoxItem, MenuSection, MenuSeparator -- Switch -- Skeleton -- FieldLabel -- Header, HeaderToolbar -- HeaderPage -- Tabs, TabList, Tab, TabPanel - -If you were passing custom className values to any of these components that relied on the previous behavior, you may need to adjust your styles to account for the default classes now being applied alongside your custom classes. diff --git a/.changeset/sharp-dragons-relate.md b/.changeset/sharp-dragons-relate.md deleted file mode 100644 index 1f7e5e0d4a..0000000000 --- a/.changeset/sharp-dragons-relate.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-app-visualizer': patch ---- - -Migrated to use `@backstage/ui`. diff --git a/.changeset/short-sides-feel.md b/.changeset/short-sides-feel.md deleted file mode 100644 index a7e3542ba1..0000000000 --- a/.changeset/short-sides-feel.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Added the new `@backstage/cli/templates/catalog-provider-module` template to the explicit template configuration for the `next-app` template. diff --git a/.changeset/shy-ways-lay.md b/.changeset/shy-ways-lay.md new file mode 100644 index 0000000000..2582c95944 --- /dev/null +++ b/.changeset/shy-ways-lay.md @@ -0,0 +1,13 @@ +--- +'@backstage/frontend-plugin-api': patch +'@backstage/plugin-catalog-graph': patch +'@backstage/plugin-catalog-react': patch +'@backstage/plugin-search-react': patch +'@backstage/plugin-techdocs': patch +'@backstage/plugin-catalog': patch +'@backstage/plugin-search': patch +'@backstage/plugin-app': patch +'@backstage/plugin-org': patch +--- + +Replaced old config schema values from existing extensions and blueprints. diff --git a/.changeset/silver-garlics-thank.md b/.changeset/silver-garlics-thank.md deleted file mode 100644 index d95781eb04..0000000000 --- a/.changeset/silver-garlics-thank.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Fix font smoothing as default in Backstage UI. diff --git a/.changeset/sixty-clowns-run.md b/.changeset/sixty-clowns-run.md deleted file mode 100644 index 595049df8f..0000000000 --- a/.changeset/sixty-clowns-run.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-components': patch ---- - -Line numbers in LogViewer will not be selectable in UI anymore diff --git a/.changeset/slack-scope-message-updates.md b/.changeset/slack-scope-message-updates.md new file mode 100644 index 0000000000..0bea25c1d8 --- /dev/null +++ b/.changeset/slack-scope-message-updates.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications-backend-module-slack': patch +--- + +Added scope-based message update support. When a notification is re-sent with the same `scope` and `notification.updated` is set, the processor now calls `chat.update()` on the existing Slack message instead of sending a duplicate via `chat.postMessage()`. Message timestamps are persisted in a new `slack_message_timestamps` database table with automatic daily cleanup. The processor gracefully degrades to the previous behavior when no database is provided. diff --git a/.changeset/smart-donuts-teach.md b/.changeset/smart-donuts-teach.md deleted file mode 100644 index f258dee394..0000000000 --- a/.changeset/smart-donuts-teach.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Enable tree-shaking of imports other than `*.css`. diff --git a/.changeset/solid-bees-agree.md b/.changeset/solid-bees-agree.md deleted file mode 100644 index 8feecaf6d4..0000000000 --- a/.changeset/solid-bees-agree.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/eslint-plugin': minor ---- - -Added `@backstage/no-ui-css-imports-in-non-frontend` rule, which ensures that CSS from `@backstage/ui` is not imported outside of the frontend app. diff --git a/.changeset/solid-dancers-march.md b/.changeset/solid-dancers-march.md deleted file mode 100644 index 5633bb3e2e..0000000000 --- a/.changeset/solid-dancers-march.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-user-settings': patch ---- - -Revert `storageApiRef` implementation diff --git a/.changeset/stupid-doodles-love.md b/.changeset/stupid-doodles-love.md deleted file mode 100644 index 878ad853cd..0000000000 --- a/.changeset/stupid-doodles-love.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@techdocs/cli': patch ---- - -Fix for missing styles due to move to BUI. diff --git a/.changeset/ten-houses-attack.md b/.changeset/ten-houses-attack.md deleted file mode 100644 index 9bb1ad2565..0000000000 --- a/.changeset/ten-houses-attack.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-gitlab': patch -'@backstage/plugin-scaffolder-backend-module-rails': patch -'@backstage/plugin-permission-backend': patch -'@backstage/plugin-permission-common': patch -'@backstage/core-components': patch -'@backstage/core-plugin-api': patch -'@backstage/plugin-devtools-common': patch -'@backstage/plugin-search-backend': patch -'@backstage/core-app-api': patch -'@backstage/plugin-catalog-graph': patch -'@backstage/cli': patch ---- - -Convert all enums to erasable-syntax compliant patterns diff --git a/.changeset/tender-regions-know.md b/.changeset/tender-regions-know.md deleted file mode 100644 index 8f59567597..0000000000 --- a/.changeset/tender-regions-know.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth': patch ---- - -Authentication content screen now uses application title. diff --git a/.changeset/thirty-hoops-own.md b/.changeset/thirty-hoops-own.md deleted file mode 100644 index 88b55d5f98..0000000000 --- a/.changeset/thirty-hoops-own.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -Updated to `ExtensionInput` to make all type parameters optional. diff --git a/.changeset/tidy-lines-hear.md b/.changeset/tidy-lines-hear.md deleted file mode 100644 index 4cb28dfbac..0000000000 --- a/.changeset/tidy-lines-hear.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-import': patch ---- - -Fixed missing `catalog.entity.create` permission authorization in the default page extension. diff --git a/.changeset/tough-sloths-spend.md b/.changeset/tough-sloths-spend.md deleted file mode 100644 index ed6a4ab366..0000000000 --- a/.changeset/tough-sloths-spend.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-github-org': patch ---- - -Added configurable `pageSizes` for GitHub GraphQL API queries to prevent `RESOURCE_LIMITS_EXCEEDED` errors with organizations with large number of teams and members. Please see the [GitHub Org Data documentation](https://backstage.io/docs/integrations/github/org#configuration-details) for new configuration options. diff --git a/.changeset/true-taxes-cover.md b/.changeset/true-taxes-cover.md deleted file mode 100644 index ec204084d0..0000000000 --- a/.changeset/true-taxes-cover.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Added `loading` prop to Button and ButtonIcon components for displaying spinner during async operations. diff --git a/.changeset/twelve-spoons-feel.md b/.changeset/twelve-spoons-feel.md deleted file mode 100644 index 21298b4e09..0000000000 --- a/.changeset/twelve-spoons-feel.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': minor -'@backstage/plugin-catalog-node': minor ---- - -Enable YAML merge keys in yamlPlaceholderResolver diff --git a/.changeset/typescript-constructor-refactor.md b/.changeset/typescript-constructor-refactor.md deleted file mode 100644 index b24cb5dbd0..0000000000 --- a/.changeset/typescript-constructor-refactor.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch -'@backstage/plugin-kubernetes-react': patch -'@backstage/plugin-notifications-backend': patch -'@backstage/plugin-notifications-node': patch -'@backstage/plugin-permission-react': patch -'@backstage/plugin-scaffolder-backend-module-gcp': patch -'@backstage/plugin-scaffolder-backend': patch -'@backstage/plugin-search-backend-module-elasticsearch': patch -'@backstage/plugin-search-backend-module-pg': patch -'@backstage/plugin-search-backend': patch -'@backstage/plugin-search-backend-node': patch -'@backstage/plugin-search-react': patch -'@backstage/plugin-signals': patch -'@backstage/plugin-kubernetes-backend': patch -'@backstage/plugin-events-backend': patch -'@backstage/plugin-catalog-backend-module-backstage-openapi': patch -'@backstage/plugin-events-node': patch -'@backstage/plugin-user-settings': patch -'@backstage/plugin-auth-backend': patch -'@backstage/plugin-auth-backend-module-cloudflare-access-provider': patch -'@backstage/plugin-catalog-react': patch -'@backstage/plugin-notifications-backend-module-email': patch -'@backstage/plugin-events-backend-module-aws-sqs': patch -'@backstage/plugin-devtools-backend': patch -'@backstage/plugin-catalog-backend-module-unprocessed': patch -'@backstage/plugin-catalog-backend-module-azure': patch -'@backstage/plugin-catalog-backend-module-aws': patch -'@backstage/plugin-scaffolder': patch -'@backstage/plugin-techdocs-node': patch -'@backstage/plugin-catalog-unprocessed-entities': patch -'@backstage/plugin-catalog-backend-module-msgraph': patch -'@backstage/plugin-catalog-backend-module-ldap': patch -'@backstage/plugin-bitbucket-cloud-common': patch -'@backstage/plugin-auth-node': patch -'@backstage/backend-app-api': patch -'@backstage/backend-defaults': patch -'@backstage/config-loader': patch -'@backstage/core-app-api': patch -'@backstage/core-plugin-api': patch -'@backstage/frontend-app-api': patch -'@backstage/config': patch -'@backstage/backend-dynamic-feature-service': patch -'@backstage/core-components': patch -'@backstage/cli': patch -'@backstage/cli-node': patch -'@backstage/catalog-model': patch -'@backstage/integration': patch -'@backstage/frontend-plugin-api': patch -'@backstage/integration-aws-node': patch -'@backstage/repo-tools': patch -'@backstage/backend-test-utils': patch -'@backstage/plugin-catalog-import': patch -'@backstage/plugin-events-backend-module-kafka': patch -'@backstage/plugin-kubernetes-node': patch -'@backstage/plugin-mcp-actions-backend': patch -'@backstage/plugin-scaffolder-node-test-utils': patch -'@backstage/plugin-scaffolder-node': patch -'@backstage/plugin-scaffolder-react': patch -'@backstage/plugin-user-settings-backend': patch ---- - -Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. diff --git a/.changeset/upgrade-module-federation-v2.md b/.changeset/upgrade-module-federation-v2.md new file mode 100644 index 0000000000..951f2e95e0 --- /dev/null +++ b/.changeset/upgrade-module-federation-v2.md @@ -0,0 +1,8 @@ +--- +'@backstage/cli-module-build': patch +'@backstage/frontend-dynamic-feature-loader': patch +'@backstage/module-federation-common': patch +'@backstage/backend-dynamic-feature-service': patch +--- + +Upgraded `@module-federation/enhanced`, `@module-federation/runtime`, and `@module-federation/sdk` from `^0.21.6` to `^2.3.3` to address known vulnerabilities. diff --git a/.changeset/upset-teeth-add.md b/.changeset/upset-teeth-add.md deleted file mode 100644 index 834ce7be2c..0000000000 --- a/.changeset/upset-teeth-add.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-mui-to-bui': patch ---- - -Updated BUI checkbox preview example to align with new component API. diff --git a/.changeset/warm-carrots-lead.md b/.changeset/warm-carrots-lead.md deleted file mode 100644 index 81a254a6a3..0000000000 --- a/.changeset/warm-carrots-lead.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Fixed Table Row component to properly support opening links in new tabs via right-click or Cmd+Click when using the `href` prop. diff --git a/.changeset/warm-dancers-battle.md b/.changeset/warm-dancers-battle.md deleted file mode 100644 index 8aa3f765e8..0000000000 --- a/.changeset/warm-dancers-battle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-react': patch ---- - -Fixed catalog filter "all" not appearing as selected when set as the initially selected filter. diff --git a/.changeset/warm-ducks-notice.md b/.changeset/warm-ducks-notice.md deleted file mode 100644 index 95e28b0836..0000000000 --- a/.changeset/warm-ducks-notice.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -'@backstage/ui': minor ---- - -**BREAKING**: Removed `Collapsible` component. Migrate to `Accordion` or use React Aria `Disclosure`. - -## Migration Path 1: Accordion (Opinionated Styled Component) - -Accordion provides preset styling with a similar component structure. - -```diff -- import { Collapsible } from '@backstage/ui'; -+ import { Accordion, AccordionTrigger, AccordionPanel } from '@backstage/ui'; - -- -- } /> -- Content -- - -+ -+ -+ Content -+ -``` - -CSS classes: `.bui-CollapsibleRoot` → `.bui-Accordion`, `.bui-CollapsibleTrigger` → `.bui-AccordionTrigger` (now on heading element), `.bui-CollapsiblePanel` → `.bui-AccordionPanel` - -## Migration Path 2: React Aria Disclosure (Full Customization) - -For custom styling without preset styles: - -```tsx -import { Disclosure, Button, DisclosurePanel } from 'react-aria-components'; - - - - Content -; -``` diff --git a/.changeset/warm-moments-repeat.md b/.changeset/warm-moments-repeat.md deleted file mode 100644 index a72387d720..0000000000 --- a/.changeset/warm-moments-repeat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-defaults': patch ---- - -Fix #31348 issue where BitbucketUrlReader ignored provided token and instead always used integration credentials diff --git a/.changeset/warm-shrimps-clap.md b/.changeset/warm-shrimps-clap.md deleted file mode 100644 index 0386df05de..0000000000 --- a/.changeset/warm-shrimps-clap.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch ---- - -Correctly handle entity removal computation when DB count query returns string diff --git a/.changeset/wicked-cycles-enter.md b/.changeset/wicked-cycles-enter.md deleted file mode 100644 index cea5cff371..0000000000 --- a/.changeset/wicked-cycles-enter.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@backstage/ui': minor ---- - -**BREAKING**: The `SelectProps` interface now accepts a generic type parameter for selection mode. - -Added searchable and multiple selection support to Select component. The component now accepts `searchable`, `selectionMode`, and `searchPlaceholder` props to enable filtering and multi-selection modes. - -Migration: If you're using `SelectProps` type directly, update from `SelectProps` to `SelectProps<'single' | 'multiple'>`. Component usage remains backward compatible. diff --git a/.changeset/wide-papers-run.md b/.changeset/wide-papers-run.md deleted file mode 100644 index e0e16d71af..0000000000 --- a/.changeset/wide-papers-run.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Set the color-scheme property depending on theme diff --git a/.changeset/wild-donkeys-sneeze.md b/.changeset/wild-donkeys-sneeze.md deleted file mode 100644 index 5e2c4474c5..0000000000 --- a/.changeset/wild-donkeys-sneeze.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Added new VisuallyHidden component for hiding content visually while keeping it accessible to screen readers. diff --git a/.changeset/wild-owls-divide.md b/.changeset/wild-owls-divide.md deleted file mode 100644 index d903b76bd0..0000000000 --- a/.changeset/wild-owls-divide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Fix default font wight and font family in Backstage UI. diff --git a/.changeset/yummy-socks-brake.md b/.changeset/yummy-socks-brake.md deleted file mode 100644 index 4f3dcfb598..0000000000 --- a/.changeset/yummy-socks-brake.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-auth-backend': patch ---- - -Allow configuring dynamic client registration token expiration with config `auth.experimentalDynamicClientRegistration.tokenExpiration`. - -Maximum expiration for the DCR token is 24 hours. Default expiration is 1 hour. diff --git a/.changeset/zod-v3-config-schema-docs.md b/.changeset/zod-v3-config-schema-docs.md new file mode 100644 index 0000000000..11fafd67d9 --- /dev/null +++ b/.changeset/zod-v3-config-schema-docs.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Updated error messages and deprecation warnings to clarify that the `zod/v4` subpath export from the Zod v3 package is not supported by `configSchema`, since it does not include JSON Schema conversion. The `zod` dependency has been bumped to `^4.0.0`. diff --git a/.changeset/zod-v4-dep-bump.md b/.changeset/zod-v4-dep-bump.md new file mode 100644 index 0000000000..f9b298f8c0 --- /dev/null +++ b/.changeset/zod-v4-dep-bump.md @@ -0,0 +1,12 @@ +--- +'@backstage/plugin-app': patch +'@backstage/plugin-catalog': patch +'@backstage/plugin-catalog-react': patch +'@backstage/plugin-catalog-graph': patch +'@backstage/plugin-techdocs': patch +'@backstage/plugin-search': patch +'@backstage/plugin-search-react': patch +'@backstage/plugin-org': patch +--- + +The `zod` dependency has been bumped from `^3.25.76 || ^4.0.0` to `^4.0.0`, since `configSchema` requires the full Zod v4 package for JSON Schema support. diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md new file mode 120000 index 0000000000..be77ac83a1 --- /dev/null +++ b/.claude/CLAUDE.md @@ -0,0 +1 @@ +../AGENTS.md \ No newline at end of file diff --git a/.cursor/rules/tests/backend-test-utils.mdc b/.cursor/rules/tests/backend-test-utils.mdc new file mode 100644 index 0000000000..857798380d --- /dev/null +++ b/.cursor/rules/tests/backend-test-utils.mdc @@ -0,0 +1,91 @@ +--- +description: Enforce usage of @backstage/backend-test-utils in backend tests +globs: ['**/*.test.*', '**/*.spec.*'] +alwaysApply: false +--- +# Backstage Backend Testing Rules + +**CRITICAL**: When writing tests for Backstage backend code, you MUST use utilities from `@backstage/backend-test-utils` wherever possible. Do NOT create custom mocks, test databases, caches, or service implementations when equivalent utilities exist in backend-test-utils. + +## Required Usage + +1. **Service Mocking**: Always use `mockServices` from `@backstage/backend-test-utils` instead of creating custom service mocks. This includes: + - `mockServices.auth()` - For authentication service mocking + - `mockServices.httpAuth()` - For HTTP authentication service mocking + - `mockServices.database()` - For database service mocking + - `mockServices.cache()` - For cache service mocking + - `mockServices.logger()` - For logger service mocking + - `mockServices.permissions()` - For permissions service mocking + - `mockServices.scheduler()` - For scheduler service mocking + - `mockServices.userInfo()` - For user info service mocking + - `mockServices.events()` - For events service mocking + - `mockServices.urlReader()` - For URL reader service mocking + - `mockServices.rootConfig()` - For root config service mocking + - `mockServices.rootLogger()` - For root logger service mocking + - `mockServices.rootHealth()` - For root health service mocking + - `mockServices.rootLifecycle()` - For root lifecycle service mocking + - `mockServices.httpRouter()` - For HTTP router service mocking + - `mockServices.lifecycle()` - For lifecycle service mocking + - `mockServices.auditor()` - For auditor service mocking + - `mockServices.permissionsRegistry()` - For permissions registry service mocking + +2. **Credentials**: Always use `mockCredentials` from `@backstage/backend-test-utils` for creating test credentials instead of manually constructing credential objects. + +3. **Test Backend**: Always use `startTestBackend` and `TestBackend` from `@backstage/backend-test-utils` when testing backend features, plugins, or modules. This provides a fully configured test backend with all necessary services. + +4. **Service Factory Testing**: Use `ServiceFactoryTester` from `@backstage/backend-test-utils` when testing individual service factories in isolation. + +5. **Database Testing**: Always use `TestDatabases` from `@backstage/backend-test-utils` for database testing. This supports multiple database engines (PostgreSQL, MySQL, SQLite) and handles ephemeral test database creation and cleanup automatically. + +6. **Cache Testing**: Always use `TestCaches` from `@backstage/backend-test-utils` for cache testing. This supports multiple cache backends (Redis, Valkey, Memcache) and handles ephemeral test cache creation and cleanup automatically. + +7. **Filesystem Testing**: Use `createMockDirectory` from `@backstage/backend-test-utils` when testing code that interacts with the filesystem. This provides a safe, isolated temporary directory for tests. + +8. **MSW Integration**: Use `registerMswTestHooks` from `@backstage/backend-test-utils` when setting up Mock Service Worker for HTTP request mocking in tests. + +9. **Error Handler**: Use `mockErrorHandler` from `@backstage/backend-test-utils` when testing Express routers that need error handling middleware. + +10. **Alpha Services**: For alpha/experimental services, use the utilities from `@backstage/backend-test-utils/alpha` (e.g., `actionsRegistryServiceMock`, `actionsServiceMock`). + +## Import Patterns + +Always import from the main package: + +```typescript +import { + mockServices, + mockCredentials, + startTestBackend, + TestDatabases, + TestCaches, + createMockDirectory, + registerMswTestHooks, + mockErrorHandler, + ServiceFactoryTester +} from '@backstage/backend-test-utils'; +``` + +For alpha services: + +```typescript +import { + actionsRegistryServiceMock, + actionsServiceMock +} from '@backstage/backend-test-utils/alpha'; +``` + +## When to Use + +- **Always** use these utilities when writing backend tests +- **Never** create custom mocks for services that have equivalents in backend-test-utils +- **Never** manually set up test databases or caches when TestDatabases/TestCaches are available +- **Never** create custom test backends when startTestBackend is available + +## Benefits + +Using backend-test-utils ensures: +- Consistent test setup across the codebase +- Proper cleanup and resource management +- Support for multiple database/cache backends +- Well-tested and maintained utilities +- Reduced boilerplate in test code diff --git a/.cursor/rules/tests/test-utils.mdc b/.cursor/rules/tests/test-utils.mdc new file mode 100644 index 0000000000..dd66e1f8fa --- /dev/null +++ b/.cursor/rules/tests/test-utils.mdc @@ -0,0 +1,100 @@ +--- +description: Enforce usage of @backstage/test-utils in frontend tests +globs: ['**/*.test.*', '**/*.spec.*'] +alwaysApply: false +--- +# Backstage Frontend/Plugin Testing Rules + +**CRITICAL**: When writing tests for Backstage frontend code, plugins, or React components, you MUST use utilities from `@backstage/test-utils` wherever possible. Do NOT create custom mocks, test wrappers, or API implementations when equivalent utilities exist in test-utils. + +## Required Usage + +1. **API Mocking**: Always use `mockApis` from `@backstage/test-utils` instead of creating custom API mocks. This includes: + - `mockApis.analytics()` - For AnalyticsApi mocking + - `mockApis.config()` - For ConfigApi mocking with optional configuration data + - `mockApis.discovery()` - For DiscoveryApi mocking + - `mockApis.identity()` - For IdentityApi mocking with user entity ref, token, profile info + - `mockApis.permission()` - For PermissionApi mocking with authorization control + - `mockApis.storage()` - For StorageApi mocking with in-memory storage + - `mockApis.translation()` - For TranslationApi mocking (from alpha) + - Each API also provides `.mock()` for jest mocks and `.factory()` for API factories + +2. **Test App Wrappers**: Always use `wrapInTestApp` or `renderInTestApp` from `@backstage/test-utils` when testing React components that need Backstage app context (routing, theme, APIs). Use `createTestAppWrapper` for custom wrapper creation. + +3. **Test API Provider**: Always use `TestApiProvider` or `TestApiRegistry` from `@backstage/test-utils` when testing components that depend on Backstage APIs. This allows you to provide only the APIs needed for your test. + +4. **Async Component Rendering**: Use `renderWithEffects` from `@backstage/test-utils` when testing components that perform asynchronous operations (e.g., useEffect with fetch). This properly handles async rendering with React's act. + +5. **Log Collection**: Use `withLogCollector` from `@backstage/test-utils` when you need to capture and assert on console logs, warnings, or errors during tests. + +6. **MSW Integration**: Use `registerMswTestHooks` from `@backstage/test-utils` when setting up Mock Service Worker for HTTP request mocking in frontend tests. + +7. **Text Content Matcher**: Use `textContentMatcher` from `@backstage/test-utils` as a custom matcher for testing-library queries when matching text content. + +8. **Mock Breakpoint**: Use `mockBreakpoint` from `@backstage/test-utils` when testing components that use Material-UI's `useMediaQuery` hook (though this is deprecated in favor of core-components/testUtils). + +9. **Individual API Mocks**: Use individual mock APIs when you need more control: + - `MockAnalyticsApi` - Analytics API mock + - `MockConfigApi` - Config API mock + - `MockErrorApi` - Error API mock + - `MockFetchApi` - Fetch API mock + - `MockPermissionApi` - Permission API mock + - `MockStorageApi` - Storage API mock + - `MockTranslationApi` - Translation API mock (from alpha) + +10. **Alpha APIs**: For alpha/experimental APIs, use the utilities from `@backstage/test-utils/alpha` (e.g., `MockTranslationApi`). + +## Import Patterns + +Always import from the main package: + +```typescript +import { + mockApis, + wrapInTestApp, + renderInTestApp, + createTestAppWrapper, + TestApiProvider, + TestApiRegistry, + renderWithEffects, + withLogCollector, + registerMswTestHooks, + textContentMatcher, + mockBreakpoint, + MockAnalyticsApi, + MockConfigApi, + MockErrorApi, + MockFetchApi, + MockPermissionApi, + MockStorageApi +} from '@backstage/test-utils'; +``` + +For alpha APIs: + +```typescript +import { + MockTranslationApi +} from '@backstage/test-utils/alpha'; +``` + +## When to Use + +- **Always** use these utilities when writing frontend/plugin tests +- **Always** use `renderInTestApp` or `wrapInTestApp` for components that need Backstage context +- **Always** use `TestApiProvider` or `TestApiRegistry` when components depend on Backstage APIs +- **Always** use `mockApis` instead of creating custom API implementations +- **Never** manually set up React Router, theme providers, or API contexts when test wrappers are available +- **Never** create custom API mocks when mockApis provides equivalents +- **Never** use raw `render` from testing-library when `renderWithEffects` is needed for async components + +## Benefits + +Using test-utils ensures: +- Consistent test setup across frontend codebase +- Proper Backstage app context (routing, theme, APIs) +- Simplified API mocking with realistic implementations +- Proper async rendering handling +- Well-tested and maintained utilities +- Reduced boilerplate in test code +- Better integration with Backstage's plugin architecture diff --git a/contrib/.devcontainer/Dockerfile b/.devcontainer/Dockerfile similarity index 78% rename from contrib/.devcontainer/Dockerfile rename to .devcontainer/Dockerfile index 6407aa26dd..9fcd4ba572 100644 --- a/contrib/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/devcontainers/typescript-node:20 +FROM mcr.microsoft.com/devcontainers/typescript-node:22 RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ && apt-get -y install chromium \ diff --git a/.devcontainer/devcontainer.env b/.devcontainer/devcontainer.env new file mode 100644 index 0000000000..65350b61d4 --- /dev/null +++ b/.devcontainer/devcontainer.env @@ -0,0 +1 @@ +NODE_OPTIONS=--no-node-snapshot diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..e6dc0e98f5 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,61 @@ +{ + "name": "Backstage", + "forwardPorts": [3000, 7007], + "build": { "dockerfile": "Dockerfile" }, + "features": { + "ghcr.io/devcontainers/features/common-utils:2.5.7": {}, + "ghcr.io/devcontainers-extra/features/mkdocs:2": {} + }, + "postCreateCommand": "bash .devcontainer/setup.sh", + "hostRequirements": { + "cpus": 2, + "memory": "4gb", + "storage": "32gb" + }, + "runArgs": [ + "--env-file", + ".devcontainer/devcontainer.env", + "--sysctl", + "net.ipv6.conf.all.disable_ipv6=1" + ], + "portsAttributes": { + "3000": { + "label": "Frontend port", + "onAutoForward": "silent", + "requireLocalPort": true + }, + "7007": { + "label": "Backend port", + "onAutoForward": "silent", + "requireLocalPort": true + }, + "9464": { + "onAutoForward": "silent" + } + }, + "customizations": { + "vscode": { + "extensions": [ + "esbenp.prettier-vscode", + "dbaeumer.vscode-eslint", + "Intility.vscode-backstage" + ], + "settings": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "eslint.validate": [ + "javascript", + "javascriptreact", + "typescript", + "typescriptreact" + ], + "files.eol": "\n", + "prettier.endOfLine": "lf", + "editor.codeActionsOnSave": { + "source.fixAll": "explicit", + "source.sortMembers": "explicit" + } + } + } + } +} diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh new file mode 100755 index 0000000000..7615e2b3cc --- /dev/null +++ b/.devcontainer/setup.sh @@ -0,0 +1,28 @@ +#!/bin/bash +echo "Installing Backstage dependencies..." + +yarn install +export VIRTUAL_ENV=$HOME/venv +python3 -m venv $VIRTUAL_ENV +export PATH="$VIRTUAL_ENV/bin:$PATH" +python3 -m pip install mkdocs-techdocs-core + +echo "" +echo "╔════════════════════════════════════════════════════════╗ " +echo "║ 🚀 Setup Complete! Ready to launch Backstage! ║ " +echo "╠════════════════════════════════════════════════════════╣ " +echo "║ ║ " +echo "║ Open a new terminal and run: ║ " +echo "║ ║ " +echo "║ yarn start ║ " +echo "║ ║ " +echo "║ Then access Backstage at: ║ " +echo "║ ║ " +echo "║ http://localhost:3000 ║ " +echo "║ ║ " +echo "║ You might need to refresh the page once backend ║ " +echo "║ is ready. ║ " +echo "║ ║ " +echo "║ Happy coding! 🎉 ║ " +echo "╚════════════════════════════════════════════════════════╝ " +echo "" \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index 54f9e69576..4f7c5afa5e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -14,12 +14,13 @@ * limitations under the License. */ -var path = require('path'); +var path = require('node:path'); module.exports = { root: true, - plugins: ['@spotify', 'notice', 'react', 'testing-library', '@backstage'], + plugins: ['@spotify', 'notice', 'react', 'testing-library', '@backstage', 'node-import'], rules: { + 'node-import/prefer-node-protocol': 1, '@backstage/no-mixed-plugin-imports': [ 'error', { diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8491b1a260..7e5cc36bdf 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,15 +4,18 @@ # The last matching pattern takes precedence. # https://help.github.com/articles/about-codeowners/ -* @backstage/maintainers @backstage/reviewers +* @backstage/maintainers yarn.lock @backstage/maintainers @backstage-service */yarn.lock @backstage/maintainers @backstage-service /.changeset @backstage/operations-maintainers /.changeset/*.md /.github @backstage/operations-maintainers -/.github/vale @backstage/documentation-maintainers +/.github/vale @backstage/maintainers @backstage/documentation-maintainers +/.patches @backstage/operations-maintainers +/.storybook @backstage/design-system-maintainers /beps/0001-notifications-system @backstage/maintainers @backstage/notifications-maintainers /docs @backstage/maintainers @backstage/documentation-maintainers +/docs-ui @backstage/design-system-maintainers /docs/assets/search @backstage/search-maintainers /docs/auth @backstage/auth-maintainers /docs/backend-system @backstage/framework-maintainers @@ -24,11 +27,11 @@ yarn.lock @backstage/maintainers @backst /docs/plugins/integrating-search-into-plugins.md @backstage/search-maintainers /docs/releases @backstage/operations-maintainers /docs/tooling @backstage/tooling-maintainers -/microsite @backstage/documentation-maintainers +/microsite @backstage/maintainers @backstage/documentation-maintainers /microsite/data/plugins @backstage/maintainers /microsite/static @backstage/maintainers @backstage/documentation-maintainers /packages @backstage/framework-maintainers -/packages/backend-openapi-utils @backstage/maintainers @backstage/reviewers @backstage/openapi-tooling-maintainers +/packages/backend-openapi-utils @backstage/maintainers @backstage/openapi-tooling-maintainers /packages/catalog-client @backstage/catalog-maintainers /packages/catalog-model @backstage/catalog-maintainers /packages/cli @backstage/tooling-maintainers @@ -46,21 +49,21 @@ yarn.lock @backstage/maintainers @backst /plugins/app-* @backstage/framework-maintainers /plugins/auth @backstage/auth-maintainers /plugins/auth-* @backstage/auth-maintainers -/plugins/api-docs @backstage/maintainers @backstage/reviewers @backstage/sda-se-reviewers -/plugins/bitbucket-cloud-common @backstage/maintainers @backstage/reviewers @pjungermann -/plugins/catalog @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers -/plugins/catalog-* @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers -/plugins/catalog-backend-module-aws @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers @pjungermann -/plugins/catalog-backend-module-bitbucket-cloud @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers @pjungermann -/plugins/catalog-backend-module-backstage-openapi @backstage/maintainers @backstage/reviewers @backstage/openapi-tooling-maintainers -/plugins/catalog-backend-module-gitea @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers -/plugins/catalog-backend-module-msgraph @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers @pjungermann -/plugins/catalog-backend-module-puppetdb @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers -/plugins/catalog-graph @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers @backstage/sda-se-reviewers -/plugins/devtools @backstage/maintainers @backstage/reviewers @awanlin -/plugins/devtools-backend @backstage/maintainers @backstage/reviewers @awanlin -/plugins/devtools-common @backstage/maintainers @backstage/reviewers @awanlin -/plugins/events-* @backstage/maintainers @backstage/reviewers @backstage/events-maintainers +/plugins/api-docs @backstage/maintainers @backstage/sda-se-reviewers +/plugins/bitbucket-cloud-common @backstage/maintainers @pjungermann +/plugins/catalog @backstage/maintainers @backstage/catalog-maintainers +/plugins/catalog-* @backstage/maintainers @backstage/catalog-maintainers +/plugins/catalog-backend-module-aws @backstage/maintainers @backstage/catalog-maintainers @pjungermann +/plugins/catalog-backend-module-bitbucket-cloud @backstage/maintainers @backstage/catalog-maintainers @pjungermann +/plugins/catalog-backend-module-backstage-openapi @backstage/maintainers @backstage/openapi-tooling-maintainers +/plugins/catalog-backend-module-gitea @backstage/maintainers @backstage/catalog-maintainers +/plugins/catalog-backend-module-msgraph @backstage/maintainers @backstage/catalog-maintainers @pjungermann +/plugins/catalog-backend-module-puppetdb @backstage/maintainers @backstage/catalog-maintainers +/plugins/catalog-graph @backstage/maintainers @backstage/catalog-maintainers @backstage/sda-se-reviewers +/plugins/devtools @backstage/maintainers @awanlin +/plugins/devtools-backend @backstage/maintainers @awanlin +/plugins/devtools-common @backstage/maintainers @awanlin +/plugins/events-* @backstage/maintainers @backstage/events-maintainers /plugins/home @backstage/home-maintainers /plugins/home-* @backstage/home-maintainers /plugins/kubernetes @backstage/kubernetes-maintainers @@ -69,17 +72,17 @@ yarn.lock @backstage/maintainers @backst /plugins/notifications-* @backstage/maintainers @backstage/notifications-maintainers /plugins/scaffolder-backend-module-notifications @backstage/maintainers @backstage/notifications-maintainers /plugins/permission-* @backstage/permission-maintainers -/plugins/scaffolder @backstage/maintainers @backstage/reviewers @backstage/scaffolder-maintainers -/plugins/scaffolder-* @backstage/maintainers @backstage/reviewers @backstage/scaffolder-maintainers +/plugins/scaffolder @backstage/maintainers @backstage/scaffolder-maintainers +/plugins/scaffolder-* @backstage/maintainers @backstage/scaffolder-maintainers /plugins/search @backstage/search-maintainers /plugins/search-* @backstage/search-maintainers /plugins/signals @backstage/maintainers @backstage/notifications-maintainers /plugins/signals-* @backstage/maintainers @backstage/notifications-maintainers /plugins/techdocs @backstage/techdocs-maintainers /plugins/techdocs-* @backstage/techdocs-maintainers -/plugins/user-settings @backstage/maintainers @backstage/reviewers @backstage/sda-se-reviewers -/plugins/user-settings-backend @backstage/maintainers @backstage/reviewers @backstage/sda-se-reviewers -/plugins/user-settings-common @backstage/maintainers @backstage/reviewers @backstage/sda-se-reviewers +/plugins/user-settings @backstage/maintainers @backstage/sda-se-reviewers +/plugins/user-settings-backend @backstage/maintainers @backstage/sda-se-reviewers +/plugins/user-settings-common @backstage/maintainers @backstage/sda-se-reviewers /scripts @backstage/operations-maintainers diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 98073c5128..9ded15067a 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,31 +1,11 @@ -This is a TypeScript project structured as a monorepo using Yarn workspaces. +## Pull Request Reviews -## Code Standards +When reviewing a pull request you MUST calculate a priority score between 0 and 100, where a higher score indicates higher priority. You MUST include the score as an HTML comment at the very beginning of the "PR Overview" section, using the following format: ``. -The following files contain guidelines for the project: +The score MUST be calculated as a sum of the following: -- `/STYLE.md`: guidelines for code style. -- `/REVIEWING.md`: guidelines for pull requests. -- `/SECURITY.md`: guidelines for security. -- `/docs/architecture-decisions/`: contains the architecture decisions for the project. +- **60% Guideline Adherence**: How well does the pull request follow the project guidelines in `/STYLE.md`, `/REVIEWING.md`, `/CONTRIBUTING.md`, and `/SECURITY.md`? A pull request that fully adheres to all guidelines scores 60 points, while one that violates multiple guidelines scores closer to 0. -## Development Flow +- **40% Complexity**: How straightforward is the pull request? A simple bug fix or documentation update scores the full 40 points. A complex API change, new feature, or architectural modification scores closer to 0. -Before any of these commands can be run, you need to run `yarn install` in the project root. - -- Build: There is no need to build the project during development, and it is verified automatically in the CI pipeline. -- Test: Use `yarn test --no-watch ` in the project root to run tests. The path can be either a single file or a directory. Always provide a path, avoid running all tests. -- Type checking: Use `yarn tsc` in the project root to run the type checker. -- Code formatting: Use `yarn prettier --write ` to format code. -- Lint: Use `yarn lint --fix` in the project root to run the linter. -- API reports: Before submitting a pull request with changes to any package in the workspace, run `yarn build:api-reports` in the project root to generate API reports for all packages. - -You MUST NOT create a release by running `yarn changesets version` or `yarn release` as part of any changes. Releases are created by separate workflows. - -All changes that affect the published version of packages in the `/packages` and `/plugins` directories must be accompanied by a changeset. See the guidelines in `/REVIEWING.md` for information on how to write good changesets. Changesets are stored in the `/.changeset` directory. Breaking changes must be accompanied by a `minor` version bump for packages that below version `1.0.0`, or a `major` version bump for packages that are at version `1.0.0` or higher. - -Never update ESLint, Prettier, or TypeScript configuration files unless specifically requested. - -## Repository Structure - -See `/docs/contribute/project-structure.md` for a detailed description of the repository structure. +Higher priority pull requests (higher scores) are those that are both well-crafted according to project standards AND easy to review and merge quickly. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..1fb8ca3ca3 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +version: 2 +updates: + - package-ecosystem: 'npm' + directory: '/' + schedule: + interval: 'weekly' + ignore: + # These need to stay pinned to match the React/Node versions we support + - dependency-name: '@types/react' + - dependency-name: '@types/node' + # Ignored due to licensing issues. See #10992 + - dependency-name: '@elastic/elasticsearch' + - dependency-name: 'event-source-polyfill' + # Disable version updates since we use Renovate for that + open-pull-requests-limit: 0 diff --git a/.github/renovate.json5 b/.github/renovate.json5 index da3cf3e8fb..4e5ca932c0 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -18,6 +18,9 @@ // ignored due to licensing issues. See #10992 '@elastic/elasticsearch', 'event-source-polyfill', + // These need to stay pinned to match the React/Node versions we support + '@types/react', + '@types/node', ], packageRules: [ { diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt index 7c780c1079..353f878ae2 100644 --- a/.github/vale/config/vocabularies/Backstage/accept.txt +++ b/.github/vale/config/vocabularies/Backstage/accept.txt @@ -30,6 +30,7 @@ backend's backported backporting BEPs +bg bigint Bigtable Billett @@ -42,22 +43,24 @@ breakpoint Brex broadcasted bugfixes +BUIProvider builtins bundler bundlers bursty callout +callouts camelCase CDNs Chai Chainguard changeset changesets -Changesets chanwit Chanwit CI/CD classname +cleye cli CLIs cloudbuild @@ -68,12 +71,10 @@ Cobertura codeblocks Codecov codehilite -Codehilite codemod codemods codeowners codescene -CodeScene composability composable config @@ -99,6 +100,7 @@ dataflow dataloader dayjs debounce +debounced debounces debuggability declaratively @@ -124,7 +126,6 @@ devs dialogs disambiguator discoverability -Discoverability dls Dockerfile dockerfiles @@ -135,11 +136,11 @@ Dominik DOMPurify don'ts dynatrace -Dynatrace dyno ecco elasticsearch Entra +enums env Env esbuild @@ -156,12 +157,10 @@ faqs featureful Figma firehydrant -FireHydrant Firekube Firestore Fiverr flightcontrol -Flightcontrol Francesco Frontside Gaurav @@ -187,7 +186,6 @@ haproxy hardcoded hardcoding harness -Harness Helidon Henneke Heroku @@ -252,6 +250,8 @@ Levenshtein lightbox Lightsail limitranges +liveness +LLMs LocalStack lockdown lockfile @@ -268,6 +268,7 @@ maintainer's maintainership makefile Matomo +matchers md memcache memoization @@ -279,7 +280,6 @@ microsite microtasks middleware minikube -Minikube Minio misattributed misconfiguration @@ -287,8 +287,9 @@ misconfigured mkdocs Mkdocs modularization +monopackage +monopackages monorepo -Monorepo monorepos monospace morgan @@ -325,15 +326,14 @@ Olausson Oldsberg onboarded onboarding -Onboarding onelogin openapi OpenSearch OpenShift openssl +opentelemetry orgs overridable -Overridable padding paddings pagerduty @@ -343,12 +343,13 @@ parallelization param params parseable +passthrough +passwordless Patrik pattison Peloton PEP performant -Performant periskop Periskop permissioned @@ -399,6 +400,7 @@ repos requestors rerender rerenders +resizable resourcequotas retryable reusability @@ -431,7 +433,9 @@ SCM SCMs scrollable scrollbar +scrollbars sdks +SearchField seb semlas semver @@ -501,6 +505,7 @@ templater Templater templaters Templaters +TextField TFRecord theia Themer @@ -509,6 +514,7 @@ thumbsup todo todos togglable +tokenizer tolerations Tolerations toolchain @@ -516,25 +522,33 @@ toolsets tooltip tooltips touchpoint +touchpoints transpilation transpile transpiled transpiler transpilers +trixie truthy tsconfig +TTLs +Turbopack TSDoc typeahead ui +unassign unbreak Unconference undici +ungrouped unicode unmanaged unmount unregister unregistering +unregisters unregistration +unreviewed unstarred untemplated untracked @@ -550,11 +564,15 @@ validator validators Valkey varchar +viewport +virtualized vite VMware Vodafone VPCs VSCode +walkthrough +walkthroughs Wayfair Weaveworks Webpack @@ -573,5 +591,3 @@ zod Zolotusky zoomable zsh -resizable -enums diff --git a/.github/workflows/api-breaking-changes-comment.yml b/.github/workflows/api-breaking-changes-comment.yml index 9b20cf80fb..d416916bb0 100644 --- a/.github/workflows/api-breaking-changes-comment.yml +++ b/.github/workflows/api-breaking-changes-comment.yml @@ -23,7 +23,7 @@ jobs: comment-cache-key: ${{ steps.hash.outputs.COMMENT_FILE_HASH }} steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: disable-sudo: true egress-policy: block @@ -32,7 +32,7 @@ jobs: - name: 'Download artifacts' # Fetch output (zip archive) from the workflow run that triggered this workflow. - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ @@ -74,7 +74,7 @@ jobs: - name: Cache Comment if: ${{ steps.event.outputs.ACTION != 'closed' }} - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: comment.md key: ${{ steps.hash.outputs.COMMENT_FILE_HASH }} @@ -99,11 +99,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Fetch cached Manifests File id: cache - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 with: path: comment.md key: ${{ needs.setup.outputs.comment-cache-key }} @@ -115,13 +115,13 @@ jobs: with: issue-number: ${{ needs.setup.outputs.pr-number }} comment-author: 'github-actions[bot]' - body-includes: API changes + body-includes: api-breaking-changes-comment direction: last - name: Create or Update Comment with API Changes uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4 with: comment-id: ${{ steps.find-comment.outputs.comment-id }} - issue-number: ${{ github.event.pull_request.number }} + issue-number: ${{ needs.setup.outputs.pr-number }} body-path: comment.md edit-mode: replace diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index 458aa621dc..53c90cb066 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -14,11 +14,11 @@ jobs: if: ${{ github.event_name != 'pull_request' || github.event.action != 'closed' }} steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: # Fetch the commit that's merged into the base rather than the target ref # This will let us diff only the contents of the PR, without fetching more history @@ -27,26 +27,27 @@ jobs: run: git fetch --depth 1 origin ${{ github.base_ref }} - name: setup-node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - node-version: 20.x + node-version: 22.x registry-url: https://registry.npmjs.org/ - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8 with: - cache-prefix: linux-v20 + cache-prefix: linux-v22 - name: breaking changes check run: | yarn backstage-repo-tools repo schema openapi diff --since origin/${{ github.base_ref }} > comment.md + echo "" >> comment.md - name: clone artifacts to current directory run: | cat ${{ github.event_path }} > event.json - name: Upload Artifacts - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: name: preview-spec path: | diff --git a/.github/workflows/automate_area-labels.yml b/.github/workflows/automate_area-labels.yml index b1663d1629..90b8ea9d16 100644 --- a/.github/workflows/automate_area-labels.yml +++ b/.github/workflows/automate_area-labels.yml @@ -13,11 +13,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit - - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0 + - uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1 with: repo-token: '${{ secrets.GITHUB_TOKEN }}' sync-labels: true diff --git a/.github/workflows/automate_changeset_feedback.yml b/.github/workflows/automate_changeset_feedback.yml index 99ffe2f62d..34b1791ca8 100644 --- a/.github/workflows/automate_changeset_feedback.yml +++ b/.github/workflows/automate_changeset_feedback.yml @@ -23,18 +23,18 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: # Fetch the commit that's merged into the base rather than the target ref # This will let us diff only the contents of the PR, without fetching more history ref: 'refs/pull/${{ github.event.pull_request.number }}/merge' - name: fetch base run: git fetch --depth 1 origin ${{ github.base_ref }} - - uses: backstage/actions/changeset-feedback@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + - uses: backstage/actions/changeset-feedback@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8 name: Generate feedback with: diff-ref: 'origin/master' diff --git a/.github/workflows/automate_merge_message.yml b/.github/workflows/automate_merge_message.yml index 3dfb3e222e..62db24ce17 100644 --- a/.github/workflows/automate_merge_message.yml +++ b/.github/workflows/automate_merge_message.yml @@ -24,11 +24,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: '${{ github.event.pull_request.merge_commit_sha }}' @@ -44,7 +44,7 @@ jobs: node generate.js ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} > message.txt - name: Post Message - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: ISSUE_NUMBER: ${{ github.event.pull_request.number }} with: diff --git a/.github/workflows/automate_stale.yml b/.github/workflows/automate_stale.yml index cbdab4414a..4bed28af3a 100644 --- a/.github/workflows/automate_stale.yml +++ b/.github/workflows/automate_stale.yml @@ -15,12 +15,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit - name: Stale check - base - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0 + uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0 with: stale-issue-message: > This issue has been automatically marked as stale because it has not had @@ -42,7 +42,7 @@ jobs: operations-per-run: 100 - name: Stale check - bugs without repro - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0 + uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0 with: stale-issue-message: > This bug report has been automatically marked as stale because it has not had diff --git a/.github/workflows/ci-noop.yml b/.github/workflows/ci-noop.yml index d046686002..71b0c68791 100644 --- a/.github/workflows/ci-noop.yml +++ b/.github/workflows/ci-noop.yml @@ -6,6 +6,7 @@ on: pull_request: paths: - 'microsite/**' + - '!microsite/data/**' - 'beps/**' permissions: @@ -19,7 +20,7 @@ jobs: strategy: matrix: - node-version: [20.x, 22.x] + node-version: [22.x, 24.x] name: Verify ${{ matrix.node-version }} steps: @@ -35,12 +36,12 @@ jobs: strategy: matrix: - node-version: [20.x, 22.x] + node-version: [22.x, 24.x] name: Test ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index db71826ef4..d35644e152 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,11 @@ name: CI on: # NOTE: If you change these you must update ci-noop.yml as well pull_request: - paths-ignore: - - 'microsite/**' - - 'beps/**' + paths: + - '**' + - '!microsite/**' + - 'microsite/data/**' + - '!beps/**' concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -19,7 +21,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [20.x, 22.x] + node-version: [22.x, 24.x] env: CI: true @@ -28,20 +30,32 @@ jobs: name: Install ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + # Store the PR number for the Sync Pull Requests workflow + - name: Save PR context + if: matrix.node-version == '22.x' + run: | + mkdir -p ./context + echo "${{ github.event.pull_request.number }}" > ./context/pr-number + - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + if: matrix.node-version == '22.x' + with: + name: pr-context + path: context/ + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} @@ -55,7 +69,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [20.x, 22.x] + node-version: [22.x, 24.x] env: CI: true @@ -64,20 +78,20 @@ jobs: name: Verify ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} @@ -121,12 +135,19 @@ jobs: - name: verify doc links run: node scripts/verify-links.js + - name: verify plugin directory + run: node scripts/verify-plugin-directory.js - name: build all packages run: yarn backstage-cli repo build --all - # For now BUI has a custom build script and needs to be built separately - - name: build BUI - run: yarn --cwd packages/ui build + # Build docs-ui to ensure documentation site builds successfully + - name: install docs-ui dependencies + working-directory: docs-ui + run: yarn install + + - name: build docs-ui + working-directory: docs-ui + run: yarn build - name: verify type dependencies run: yarn lint:type-deps @@ -154,7 +175,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [20.x, 22.x] + node-version: [22.x, 24.x] name: Test ${{ matrix.node-version }} services: @@ -203,14 +224,14 @@ jobs: env: CI: true - NODE_OPTIONS: --max-old-space-size=8192 --no-node-snapshot --experimental-vm-modules + NODE_OPTIONS: --max-old-space-size=8192 --experimental-vm-modules INTEGRATION_TEST_GITHUB_TOKEN: ${{ secrets.INTEGRATION_TEST_GITHUB_TOKEN }} INTEGRATION_TEST_GITLAB_TOKEN: ${{ secrets.INTEGRATION_TEST_GITLAB_TOKEN }} INTEGRATION_TEST_BITBUCKET_TOKEN: ${{ secrets.INTEGRATION_TEST_BITBUCKET_TOKEN }} INTEGRATION_TEST_AZURE_TOKEN: ${{ secrets.INTEGRATION_TEST_AZURE_TOKEN }} steps: - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: fetch master branch run: git fetch origin master @@ -220,13 +241,13 @@ jobs: run: git fetch origin ${{ github.event.pull_request.base.ref }} - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} @@ -236,7 +257,7 @@ jobs: # Use the lower-level cache actions for the success cache, so that we can store the cache even on failed builds - name: restore backstage-cli cache - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 with: path: .cache/backstage-cli key: ${{ runner.os }}-v${{ matrix.node-version }}-backstage-cli-${{ github.run_id }} @@ -247,10 +268,10 @@ jobs: run: yarn prettier:check --cache --cache-location .cache/backstage-cli/.prettier-cache - name: lint changed packages - run: yarn backstage-cli repo lint --since origin/master --successCache --successCacheDir .cache/backstage-cli + run: yarn backstage-cli repo lint --since origin/master --success-cache --success-cache-dir .cache/backstage-cli - name: test changed packages - run: yarn backstage-cli repo test --maxWorkers=3 --workerIdleMemoryLimit=1300M --since origin/master --successCache --successCacheDir .cache/backstage-cli + run: yarn backstage-cli repo test --maxWorkers=3 --workerIdleMemoryLimit=1300M --since origin/master --success-cache --success-cache-dir .cache/backstage-cli env: BACKSTAGE_TEST_DISABLE_DOCKER: 1 BACKSTAGE_TEST_DATABASE_POSTGRES18_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres18.ports[5432] }} @@ -260,7 +281,7 @@ jobs: # Always save success cache even if there were failures, that way it can be used in re-triggered builds - name: save backstage-cli cache - uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 if: always() with: path: .cache/backstage-cli diff --git a/.github/workflows/cron.yml b/.github/workflows/cron.yml index bb759971b7..61f8a2ce51 100644 --- a/.github/workflows/cron.yml +++ b/.github/workflows/cron.yml @@ -10,11 +10,11 @@ jobs: timeout-minutes: 10 steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit - - uses: backstage/actions/cron@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + - uses: backstage/actions/cron@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8 with: app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }} private-key: ${{ secrets.BACKSTAGE_GOALIE_PRIVATE_KEY }} diff --git a/.github/workflows/deploy_docker-image.yml b/.github/workflows/deploy_docker-image.yml index ee4866d7af..a1e0b49c7a 100644 --- a/.github/workflows/deploy_docker-image.yml +++ b/.github/workflows/deploy_docker-image.yml @@ -16,28 +16,28 @@ jobs: strategy: fail-fast: false matrix: - node-version: [20.x] + node-version: [22.x] steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit - name: checkout - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: backstage ref: ${{ github.event.client_payload.version && env.RELEASE_VERSION || github.ref }} - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} @@ -52,17 +52,17 @@ jobs: working-directory: ./example-app - name: Login to GitHub Container Registry - uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Build and push - uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 + uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0 with: context: './example-app' file: ./example-app/packages/backend/Dockerfile diff --git a/.github/workflows/deploy_microsite.yml b/.github/workflows/deploy_microsite.yml index acf0cb7776..e07aa4409b 100644 --- a/.github/workflows/deploy_microsite.yml +++ b/.github/workflows/deploy_microsite.yml @@ -23,86 +23,76 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit - - name: find latest release - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + - name: find latest release branch + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 id: find-release with: script: | - const { data } = await github.rest.repos.listTags({ + const data = await octokit.paginate(github.rest.repos.listBranches, { owner: context.repo.owner, repo: context.repo.repo, per_page: 100, }) - const [{tag}] = data + const [{branch}] = data .map(i => i.name) - .filter(tag => tag.match(/^v\d+\.\d+\.\d+$/)) - .map(tag => ({ - tag, - val: tag + .filter(branch => branch.match(/^patch\/v\d+\.\d+\.\d+$/)) + .map(branch => ({ + branch, + val: branch + .split('/')[1] .slice(1) .split('.') .reduce((val, part) => Number(val) * 1000 + Number(part)) })) .sort((a, b) => b.val - a.val) - return tag + return branch result-encoding: string - name: checkout latest release - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - ref: refs/tags/${{ steps.find-release.outputs.result }} + ref: refs/heads/${{ steps.find-release.outputs.result }} - - name: Use Node.js 20.x - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + - name: Use Node.js 22.x + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - node-version: 20.x + node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8 with: - cache-prefix: ${{ runner.os }}-v20.x - - - name: build API reference - run: yarn build:api-docs - - - name: upload API reference - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: stable-reference - path: docs/reference/ - if-no-files-found: error - retention-days: 1 + cache-prefix: ${{ runner.os }}-v22.x # Use the lower-level cache actions for the success cache, so that we can store the cache even on failed builds - name: restore package-docs cache - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 with: path: .cache/package-docs key: ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-stable-${{ github.run_id }} restore-keys: | ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-stable- - - name: build API reference (beta) + - name: build API reference run: yarn backstage-repo-tools package-docs - - name: upload API reference (beta) - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + - name: upload API reference + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: - name: stable-reference-beta + name: stable-reference path: type-docs/ if-no-files-found: error retention-days: 1 # Always save success cache even if there were failures, that way it can be used in re-triggered builds - name: save package-docs cache - uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 if: always() with: path: .cache/package-docs @@ -117,7 +107,7 @@ jobs: run: yarn docusaurus gen-api-docs all - name: upload OpenAPI API docs - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: name: stable-openapi-docs path: docs/**/api/**/* @@ -135,58 +125,47 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit - name: checkout master - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Use Node.js 20.x - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + - name: Use Node.js 22.x + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - node-version: 20.x + node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8 with: - cache-prefix: ${{ runner.os }}-v20.x - - - name: build API reference - run: yarn build:api-docs - - - name: upload API reference - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: next-reference - path: docs/reference/ - if-no-files-found: error - retention-days: 1 + cache-prefix: ${{ runner.os }}-v22.x # Use the lower-level cache actions for the success cache, so that we can store the cache even on failed builds - name: restore package-docs cache - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 with: path: .cache/package-docs key: ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-${{ github.run_id }} restore-keys: | ${{ runner.os }}-v${{ matrix.node-version }}-package-docs- - - name: build API reference (beta) + - name: build API reference run: yarn backstage-repo-tools package-docs - - name: upload API reference (beta) - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + - name: upload API reference + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: - name: next-reference-beta + name: next-reference path: type-docs/ if-no-files-found: error retention-days: 1 # Always save success cache even if there were failures, that way it can be used in re-triggered builds - name: save package-docs cache - uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 if: always() with: path: .cache/package-docs @@ -196,7 +175,7 @@ jobs: run: yarn build-storybook - name: Upload Storybook - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: name: storybook path: dist-storybook @@ -212,7 +191,7 @@ jobs: run: yarn docusaurus gen-api-docs all - name: upload OpenAPI API docs - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: name: next-openapi-docs path: docs/**/api/**/* @@ -240,43 +219,51 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit - - name: Use Node.js 20.x - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + - name: Use Node.js 22.x + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - node-version: 20.x + node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth # Stable docs - name: checkout latest release - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - ref: refs/tags/${{ needs.stable.outputs.release }} + ref: refs/heads/${{ needs.stable.outputs.release }} - name: microsite yarn install run: yarn install --immutable working-directory: microsite - - name: download stable reference - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - name: stable-reference - path: docs/reference - - name: download stable OpenAPI API docs - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: stable-openapi-docs path: docs - - name: grab lastest releases docs + - name: grab latest releases docs run: | git fetch origin master --depth 1 git checkout FETCH_HEAD -- docs/releases + - name: create missing generated file + run: | + mkdir -p docs/reference + cat > docs/reference/index.md <> $GITHUB_ENV - name: Update GitHub Issue - uses: actions/github-script@v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | @@ -62,7 +62,7 @@ jobs: issue_number: issueNumber, body: reportBody }); - + console.log(`✅ Successfully updated issue #${issueNumber}`); } catch (error) { console.error(`❌ Error updating issue: ${error.message}`); diff --git a/.github/workflows/notify_discord-pr.yml b/.github/workflows/notify_discord-pr.yml new file mode 100644 index 0000000000..f7be8d3d3b --- /dev/null +++ b/.github/workflows/notify_discord-pr.yml @@ -0,0 +1,85 @@ +name: Discord PR notification + +on: + pull_request_target: + types: [opened, ready_for_review] + +permissions: + actions: none + checks: none + contents: none + deployments: none + issues: none + packages: none + pages: none + pull-requests: none + repository-projects: none + security-events: none + statuses: none + +jobs: + notify: + # Only run for non-draft PRs in the main repo + if: github.repository == 'backstage/backstage' && github.event.pull_request.draft == false + runs-on: ubuntu-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + with: + egress-policy: audit + + - name: Send Discord notification + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + DISCORD_PULL_REQUESTS_WEBHOOK: ${{ secrets.DISCORD_PULL_REQUESTS_WEBHOOK }} + with: + script: | + if (!process.env.DISCORD_PULL_REQUESTS_WEBHOOK) { + throw new Error('DISCORD_PULL_REQUESTS_WEBHOOK secret is not set'); + } + + const { pull_request: pr, action } = context.payload; + + let description = (pr.body || '').slice(0, 300); + if ((pr.body || '').length > 300) { + description += '...'; + } + + // Discord embed titles are limited to 256 characters + const titlePrefix = `#${pr.number}: `; + const maxTitleLength = 256 - titlePrefix.length; + let prTitle = pr.title || ''; + if (prTitle.length > maxTitleLength) { + prTitle = prTitle.slice(0, maxTitleLength - 3) + '...'; + } + + const isOpened = action === 'opened'; + const color = isOpened ? 5763719 : 3447003; // green : blue + const footer = isOpened ? 'New pull request' : 'Ready for review'; + + const payload = { + embeds: [{ + title: `${titlePrefix}${prTitle}`, + url: pr.html_url, + description, + color, + author: { + name: pr.user.login, + url: pr.user.html_url, + icon_url: pr.user.avatar_url, + }, + footer: { text: footer }, + }], + allowed_mentions: { parse: [] }, + }; + + const response = await fetch(process.env.DISCORD_PULL_REQUESTS_WEBHOOK, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }); + + if (!response.ok) { + const body = await response.text().catch(() => ''); + throw new Error(`Discord webhook failed: ${response.status} ${response.statusText} - ${body}`); + } diff --git a/.github/workflows/pr-review-comment-trigger.yaml b/.github/workflows/pr-review-comment-trigger.yaml deleted file mode 100644 index 968bc4ab12..0000000000 --- a/.github/workflows/pr-review-comment-trigger.yaml +++ /dev/null @@ -1,37 +0,0 @@ -# This workflow is used to trigger the "PR Review Comment" workflow. This chaining is needed -# because workflows triggered by pull_request_review_comment do not have access to secrets. - -name: PR Review Comment Trigger -on: - pull_request_review_comment: - types: - - created - -permissions: - contents: read - -jobs: - trigger: - runs-on: ubuntu-latest - - # The "PR Review Comment" workflow will check the success status of this workflow and only mark - # the PR for re-review if this workflow was successful, which is when the author leaves a review comment. - if: github.repository == 'backstage/backstage' && github.event.comment.user.id == github.event.pull_request.user.id - - steps: - - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 - with: - egress-policy: audit - - - name: Save PR number - env: - PR_NUMBER: ${{ github.event.pull_request.number }} - run: | - mkdir -p ./pr - echo $PR_NUMBER > ./pr/pr_number - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: pr_number-${{ github.event.pull_request.number }} - path: pr/ - overwrite: true diff --git a/.github/workflows/pr-review-comment.yaml b/.github/workflows/pr-review-comment.yaml deleted file mode 100644 index 122eb4fcce..0000000000 --- a/.github/workflows/pr-review-comment.yaml +++ /dev/null @@ -1,49 +0,0 @@ -# This workflow is triggered by "PR Review Comment Trigger" - -name: PR Review Comment -on: - workflow_run: - workflows: [PR Review Comment Trigger] - types: - - completed - -jobs: - re-review: - runs-on: ubuntu-latest - - # The triggering workflow will report success if the PR needs re-review - if: github.repository == 'backstage/backstage' && github.event.workflow_run.conclusion == 'success' - - steps: - # Inspired by https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#using-data-from-the-triggering-workflow - - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 - with: - egress-policy: audit - - - name: Read PR Number - id: pr-number - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: context.payload.workflow_run.id, - }); - const [artifact] = allArtifacts.data.artifacts.filter(artifact => artifact.name.startsWith('pr_number-')) - if (!artifact) { - throw new Error('No PR Number artifact available') - } - - const prNumber = artifact.name.slice('pr_number-'.length) - core.setOutput('pr-number', prNumber); - - - uses: backstage/actions/re-review@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 - with: - app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }} - private-key: ${{ secrets.BACKSTAGE_GOALIE_PRIVATE_KEY }} - installation-id: ${{ secrets.BACKSTAGE_GOALIE_INSTALLATION_ID }} - project-id: PVT_kwDOBFKqdc02LQ - issue-number: ${{ steps.pr-number.outputs.pr-number }} diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml deleted file mode 100644 index 4067326890..0000000000 --- a/.github/workflows/pr.yaml +++ /dev/null @@ -1,34 +0,0 @@ -name: PR -on: - pull_request_target: - types: - - opened - - synchronize - - reopened - - closed - issue_comment: - types: - - created - -jobs: - sync: - runs-on: ubuntu-latest - - # Avoid running on issue comments - if: github.repository == 'backstage/backstage' && ( github.event.pull_request || github.event.issue.pull_request ) - steps: - - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 - with: - egress-policy: audit - - - name: PR sync - uses: backstage/actions/pr-sync@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 - with: - github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} - app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }} - private-key: ${{ secrets.BACKSTAGE_GOALIE_PRIVATE_KEY }} - installation-id: ${{ secrets.BACKSTAGE_GOALIE_INSTALLATION_ID }} - project-id: PVT_kwDOBFKqdc02LQ - auto-assign: false - owning-teams: '@backstage/techdocs-core' diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index d42fb0af6c..29c96b03d4 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -29,12 +29,12 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit - name: 'Checkout code' - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false @@ -58,7 +58,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: 'Upload artifact' - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: SARIF file path: results.sarif @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: 'Upload to code-scanning' - uses: github/codeql-action/upload-sarif@a4e1a019f5e24960714ff6296aee04b736cbc3cf # v3.29.6 + uses: github/codeql-action/upload-sarif@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1 with: sarif_file: results.sarif diff --git a/.github/workflows/sync_canon.yml b/.github/workflows/sync_bui-docs.yml similarity index 70% rename from .github/workflows/sync_canon.yml rename to .github/workflows/sync_bui-docs.yml index db8bfb6b19..58491479cf 100644 --- a/.github/workflows/sync_canon.yml +++ b/.github/workflows/sync_bui-docs.yml @@ -8,26 +8,26 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit - name: Checkout - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Use Node.js 20.x - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + - name: Use Node.js 22.x + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - node-version: 20.x + node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8 with: - cache-prefix: ${{ runner.os }}-v20.x + cache-prefix: ${{ runner.os }}-v22.x - name: Checkout backstage/docs-ui - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: backstage/docs-ui path: bui-external-docs diff --git a/.github/workflows/sync_code-formatting.yml b/.github/workflows/sync_code-formatting.yml index 478c8a0cf2..38a119edbc 100644 --- a/.github/workflows/sync_code-formatting.yml +++ b/.github/workflows/sync_code-formatting.yml @@ -10,22 +10,22 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: # Fetch changes to previous commit - required for 'only_changed' in Prettier action fetch-depth: 0 - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} diff --git a/.github/workflows/sync_dependabot-changesets.yml b/.github/workflows/sync_dependabot-changesets.yml index 2ddde5ca4b..2b1dd1098f 100644 --- a/.github/workflows/sync_dependabot-changesets.yml +++ b/.github/workflows/sync_dependabot-changesets.yml @@ -11,22 +11,81 @@ jobs: if: github.actor == 'dependabot[bot]' && github.repository == 'backstage/backstage' steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit + - name: Approve yarn.lock-only PRs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} + script: | + const { data: files } = await github.rest.pulls.listFiles({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + }); + + if (files.some(file => !file.filename.endsWith("yarn.lock"))) { + console.log("Skipping approval since some files are not yarn.lock"); + return; + } + + await github.rest.pulls.createReview({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + event: "APPROVE", + }); + - name: Checkout - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 2 ref: ${{ github.head_ref }} token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} + - name: Verify Dependabot branch + id: verify + run: | + if git branch --show-current | grep -q '^dependabot/'; then + echo "is_dependabot=true" >> "$GITHUB_OUTPUT" + else + echo "Not a dependabot branch, skipping" + echo "is_dependabot=false" >> "$GITHUB_OUTPUT" + fi - name: Configure Git + if: steps.verify.outputs.is_dependabot == 'true' run: | git config --global user.email noreply@backstage.io git config --global user.name 'Github changeset workflow' + - name: Setup Node + if: steps.verify.outputs.is_dependabot == 'true' + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: 22.x + - name: Dedupe lockfiles + if: steps.verify.outputs.is_dependabot == 'true' + id: dedupe + run: | + corepack enable + changed=false + + for dir in . docs-ui microsite; do + if [ "$dir" = "." ]; then lockfile=yarn.lock; else lockfile="$dir/yarn.lock"; fi + if git diff --name-only HEAD~1 | grep -q "^${lockfile}$"; then + yarn --cwd "$dir" dedupe + if ! git diff --quiet "$lockfile"; then + git add "$lockfile" + changed=true + fi + fi + done + + echo "changed=$changed" >> "$GITHUB_OUTPUT" - name: Generate changeset - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + if: steps.verify.outputs.is_dependabot == 'true' + id: changeset + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const { promises: fs } = require('fs'); @@ -50,12 +109,6 @@ jobs: await fs.writeFile(fileName, body); } - const branch = await exec.getExecOutput('git branch --show-current'); - if (!branch.stdout.startsWith('dependabot/')) { - console.log('Not a dependabot branch, skipping'); - return; - } - const diffOutput = await exec.getExecOutput('git diff --name-only HEAD~1'); const diffFiles = diffOutput.stdout.split('\n'); @@ -79,5 +132,9 @@ jobs: const { stdout: commitMessage } = await exec.getExecOutput('git show --pretty=format:%s -s HEAD'); await createChangeset(fileName, commitMessage, packageNames); await exec.exec('git', ['add', fileName]); - await exec.exec('git commit -C HEAD --amend --no-edit'); - await exec.exec('git push --force'); + core.setOutput('changed', 'true'); + - name: Commit and push + if: steps.verify.outputs.is_dependabot == 'true' && (steps.dedupe.outputs.changed == 'true' || steps.changeset.outputs.changed == 'true') + run: | + git commit -C HEAD --amend --no-edit --no-verify + git push --force diff --git a/.github/workflows/sync_patch-release.yml b/.github/workflows/sync_patch-release.yml new file mode 100644 index 0000000000..628635af1d --- /dev/null +++ b/.github/workflows/sync_patch-release.yml @@ -0,0 +1,253 @@ +name: Sync Patch Release +on: + push: + branches: + - master + paths: + - '.patches/**' + +concurrency: + group: sync-patch-release + cancel-in-progress: true + +jobs: + sync-patch-release: + name: Sync Patch Release + runs-on: ubuntu-latest + if: github.repository == 'backstage/backstage' + permissions: + contents: write + pull-requests: write + steps: + - name: Harden Runner + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 20000 + fetch-tags: true + token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} + + - name: Use Node.js 22.x + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: 22.x + registry-url: https://registry.npmjs.org/ # Needed for auth + + - name: Configure Git + run: | + git config --global user.email noreply@backstage.io + git config --global user.name 'Github patch release workflow' + + - name: Check for patch files + id: check-patches + run: | + if [ -d ".patches" ] && [ "$(ls -A .patches/pr-*.txt 2>/dev/null)" ]; then + echo "has_patches=true" >> $GITHUB_OUTPUT + echo "Found patch files" + else + echo "has_patches=false" >> $GITHUB_OUTPUT + echo "No patch files found" + fi + + - name: Find existing PR + id: find-pr + if: steps.check-patches.outputs.has_patches == 'true' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} + script: | + const branchName = 'patch-release'; + const owner = context.repo.owner; + const repo = context.repo.repo; + + // Find PR with the branch as head + const { data: pulls } = await github.rest.pulls.list({ + owner, + repo, + head: `${owner}:${branchName}`, + state: 'open', + }); + + if (pulls.length > 0) { + console.log(`Found existing PR: #${pulls[0].number}`); + core.setOutput('pr_number', pulls[0].number.toString()); + core.setOutput('pr_exists', 'true'); + } else { + console.log('No existing PR found'); + core.setOutput('pr_exists', 'false'); + } + + - name: Close PR and delete branch if no patches + if: steps.check-patches.outputs.has_patches == 'false' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} + script: | + const branchName = 'patch-release'; + const owner = context.repo.owner; + const repo = context.repo.repo; + + // Find PR with the branch as head + const { data: pulls } = await github.rest.pulls.list({ + owner, + repo, + head: `${owner}:${branchName}`, + state: 'open', + }); + + if (pulls.length > 0) { + console.log(`Closing PR #${pulls[0].number}`); + await github.rest.pulls.update({ + owner, + repo, + pull_number: pulls[0].number, + state: 'closed', + }); + } + + // Delete the branch + try { + await github.rest.git.deleteRef({ + owner, + repo, + ref: `heads/${branchName}`, + }); + console.log(`Deleted branch ${branchName}`); + } catch (error) { + if (error.status === 422) { + console.log(`Branch ${branchName} does not exist, skipping deletion`); + } else { + throw error; + } + } + + - name: Install Dependencies + if: steps.check-patches.outputs.has_patches == 'true' + run: yarn --immutable + + - name: Read patch files for PR metadata + if: steps.check-patches.outputs.has_patches == 'true' + id: read-patches + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} + script: | + // Read patch files from master branch to generate PR body + const fs = require('fs').promises; + const path = require('path'); + + const patchesDir = path.join(process.cwd(), '.patches'); + let files = []; + try { + files = await fs.readdir(patchesDir); + } catch (error) { + console.log(`Error reading patches directory: ${error.message}`); + core.setOutput('pr_body', 'This patch release fixes issues.'); + return; + } + + const patchFiles = files.filter(f => /^pr-\d+\.txt$/.test(f)); + + const patches = []; + for (const file of patchFiles) { + const match = file.match(/^pr-(\d+)\.txt$/); + if (match) { + const prNumber = parseInt(match[1], 10); + const filePath = path.join(patchesDir, file); + let content = ''; + try { + content = await fs.readFile(filePath, 'utf8'); + } catch (error) { + console.log(`Error reading ${file}: ${error.message}`); + } + const description = content.trim() || ''; + patches.push({ prNumber, description }); + } + } + + if (patches.length === 0) { + console.log('No patches found, using default values'); + core.setOutput('pr_body', 'This patch release fixes issues.'); + core.setOutput('pr_numbers', '[]'); + return; + } + + patches.sort((a, b) => a.prNumber - b.prNumber); + const prNumbers = patches.map(p => p.prNumber); + const descriptions = patches.map(p => p.description); + + const descriptionList = descriptions.map(desc => `- ${desc}`).join('\n'); + const body = `This patch release fixes the following issues:\n\n${descriptionList}`; + + core.setOutput('pr_body', body); + core.setOutput('pr_numbers', JSON.stringify(prNumbers)); + + - name: Run patch release script + if: steps.check-patches.outputs.has_patches == 'true' + id: run-script + env: + GITHUB_TOKEN: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} + HUSKY: '0' + PATCH_RELEASE_BRANCH: patch-release + run: | + # The script will handle finding the release version, creating the patch branch, + # and deleting patch files. Run the patch script with --from-dir flag to read from .patches/ directory + # The script will write the patch branch to GITHUB_OUTPUT + node scripts/patch-release-for-pr.js --from-dir + + # Ensure the branch is pushed (script pushes it, but force push in case of conflicts) + git push origin patch-release --force + + - name: Create or update PR + if: steps.check-patches.outputs.has_patches == 'true' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + PR_EXISTS: ${{ steps.find-pr.outputs.pr_exists }} + PR_NUMBER: ${{ steps.find-pr.outputs.pr_number }} + PR_BODY: ${{ steps.read-patches.outputs.pr_body }} + PATCH_BRANCH: ${{ steps.run-script.outputs.patch_branch }} + with: + github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} + script: | + const branchName = 'patch-release'; + const owner = context.repo.owner; + const repo = context.repo.repo; + + const title = 'Patch Release'; + const body = process.env.PR_BODY; + const prExists = process.env.PR_EXISTS === 'true'; + const prNumber = process.env.PR_NUMBER; + const patchBranch = process.env.PATCH_BRANCH; + + if (prExists && prNumber) { + console.log(`Updating PR #${prNumber}`); + await github.rest.pulls.update({ + owner, + repo, + pull_number: parseInt(prNumber, 10), + title, + body, + }); + } else { + // Use the patch branch from the script + if (!patchBranch) { + throw new Error('Patch branch not provided from script. Make sure the script outputs the patch branch name.'); + } + + const baseBranch = patchBranch; + console.log(`Creating new PR with base branch: ${baseBranch}`); + + const { data: pr } = await github.rest.pulls.create({ + owner, + repo, + title, + body, + head: branchName, + base: baseBranch, + }); + console.log(`Created PR #${pr.number}`); + } diff --git a/.github/workflows/sync_pull-requests-scheduled.yml b/.github/workflows/sync_pull-requests-scheduled.yml new file mode 100644 index 0000000000..3854a0f4df --- /dev/null +++ b/.github/workflows/sync_pull-requests-scheduled.yml @@ -0,0 +1,94 @@ +name: Sync Pull Requests Scheduled + +on: + schedule: + # Every 30 minutes, processing a rotating batch of PRs + - cron: '*/30 * * * *' + workflow_dispatch: + +permissions: {} + +jobs: + plan: + permissions: + pull-requests: read + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + matrix: ${{ steps.batch.outputs.matrix }} + count: ${{ steps.batch.outputs.count }} + steps: + - name: Harden Runner + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + with: + egress-policy: audit + + - name: Determine PR batch + id: batch + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + script: | + // 48 slots per day (one per 30-minute cron window). + // Each PR is assigned a fixed slot via pr.number % 48, so + // opening/closing other PRs never shifts the assignment. + const totalSlots = 48; + const now = new Date(); + const currentSlot = + now.getUTCHours() * 2 + (now.getUTCMinutes() >= 30 ? 1 : 0); + + const prs = await github.paginate(github.rest.pulls.list, { + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + per_page: 100, + }); + + const batch = prs.filter( + pr => pr.number % totalSlots === currentSlot, + ); + + core.info( + `Slot ${currentSlot}/${totalSlots}: ` + + `${batch.length} PRs to process out of ${prs.length} open`, + ); + + const matrix = { + include: batch.map(pr => ({ prNumber: String(pr.number) })), + }; + + core.setOutput('matrix', JSON.stringify(matrix)); + core.setOutput('count', String(batch.length)); + + sync: + needs: plan + if: needs.plan.outputs.count > 0 + runs-on: ubuntu-latest + timeout-minutes: 5 + strategy: + matrix: ${{ fromJSON(needs.plan.outputs.matrix) }} + fail-fast: false + max-parallel: 1 + steps: + - name: Harden Runner + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + with: + egress-policy: audit + + - name: Backstage PR automation + uses: backstage/actions/pr-automation@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8 + with: + app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }} + private-key: ${{ secrets.BACKSTAGE_GOALIE_PRIVATE_KEY }} + installation-id: ${{ secrets.BACKSTAGE_GOALIE_INSTALLATION_ID }} + project-owner: backstage + project-number: '14' + pr-number: ${{ matrix.prNumber }} + action: synchronize + required-checks: | + DCO + E2E Linux 22.x + E2E Linux 24.x + Test 22.x + Test 24.x + Verify 22.x + Verify 24.x diff --git a/.github/workflows/sync_pull-requests-trigger.yml b/.github/workflows/sync_pull-requests-trigger.yml new file mode 100644 index 0000000000..fe87517bd1 --- /dev/null +++ b/.github/workflows/sync_pull-requests-trigger.yml @@ -0,0 +1,51 @@ +name: Sync Pull Requests Trigger + +on: + pull_request_target: + types: + - opened + - closed + - reopened + - synchronize + - labeled + - unlabeled + - ready_for_review + - converted_to_draft + pull_request_review: + types: [submitted, dismissed] + issue_comment: + types: [created] + +jobs: + trigger: + if: > + github.repository == 'backstage/backstage' && + github.event.sender.type != 'Bot' && + (github.event.pull_request || github.event.issue.pull_request) + runs-on: ubuntu-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + with: + egress-policy: audit + + - name: Save PR context + env: + PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }} + LABEL_ADDED: ${{ github.event.action == 'labeled' && github.event.label.name || '' }} + REVIEW_STATE: ${{ github.event.review.state }} + ACTOR: ${{ github.actor }} + ACTION: ${{ github.event.action }} + run: | + mkdir -p ./context + echo "$PR_NUMBER" > ./context/pr-number + echo "$LABEL_ADDED" > ./context/label-added + echo "$REVIEW_STATE" > ./context/review-state + echo "$ACTOR" > ./context/actor + echo "$ACTION" > ./context/action + + - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: pr-context + path: context/ + overwrite: true diff --git a/.github/workflows/sync_pull-requests.yml b/.github/workflows/sync_pull-requests.yml new file mode 100644 index 0000000000..da67b613d7 --- /dev/null +++ b/.github/workflows/sync_pull-requests.yml @@ -0,0 +1,70 @@ +name: Sync Pull Requests + +on: + workflow_run: + workflows: ['Sync Pull Requests Trigger', 'CI', 'E2E Linux'] + types: + - completed + +jobs: + run: + runs-on: ubuntu-latest + # Only run on the trigger workflow, or if the source workflow was triggered by a pull request + if: > + github.event.workflow_run.name == 'Sync Pull Requests Trigger' || + github.event.workflow_run.event == 'pull_request' + steps: + - name: Harden Runner + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + with: + egress-policy: audit + + - name: Download PR context + id: download + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + continue-on-error: true + with: + name: pr-context + path: ./context + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + + - name: Read context + if: steps.download.outcome == 'success' + id: context + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + script: | + const fs = require('fs'); + function read(path, fallback) { + try { return fs.readFileSync(path, 'utf8').trim(); } + catch { return fallback; } + } + core.setOutput('pr-number', read('./context/pr-number', '')); + core.setOutput('label-added', read('./context/label-added', '')); + core.setOutput('review-state', read('./context/review-state', '')); + core.setOutput('actor', read('./context/actor', '')); + core.setOutput('action', read('./context/action', 'synchronize')); + + - name: Backstage PR automation + if: steps.context.outputs.pr-number + uses: backstage/actions/pr-automation@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8 + with: + app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }} + private-key: ${{ secrets.BACKSTAGE_GOALIE_PRIVATE_KEY }} + installation-id: ${{ secrets.BACKSTAGE_GOALIE_INSTALLATION_ID }} + project-owner: backstage + project-number: '14' + pr-number: ${{ steps.context.outputs.pr-number }} + label-added: ${{ steps.context.outputs.label-added }} + review-state: ${{ steps.context.outputs.review-state }} + actor: ${{ steps.context.outputs.actor }} + action: ${{ steps.context.outputs.action }} + required-checks: | + DCO + E2E Linux 22.x + E2E Linux 24.x + Test 22.x + Test 24.x + Verify 22.x + Verify 24.x diff --git a/.github/workflows/sync_release-manifest.yml b/.github/workflows/sync_release-manifest.yml index 0e9aa213c0..62478a7891 100644 --- a/.github/workflows/sync_release-manifest.yml +++ b/.github/workflows/sync_release-manifest.yml @@ -8,26 +8,26 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit - name: Checkout - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: # 'v' prefix is added here for the tag, we keep it out of the manifest logic ref: v${{ github.event.client_payload.version }} - - name: Use Node.js 20.x - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + - name: Use Node.js 22.x + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - node-version: 20.x + node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8 with: - cache-prefix: ${{ runner.os }}-v20.x + cache-prefix: ${{ runner.os }}-v22.x - name: Build yarn plugin working-directory: packages/yarn-plugin @@ -35,7 +35,7 @@ jobs: # Checkout backstage/versions into /backstage/versions, which is where store the output - name: Checkout versions - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: backstage/versions path: versions @@ -71,7 +71,7 @@ jobs: git push - name: Dispatch update-helper update - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} # TODO(Rugvip): Remove the create-app dispatch once we've been on the release version for a while @@ -81,9 +81,5 @@ jobs: owner: 'backstage', repo: 'upgrade-helper-diff', workflow_id: 'release.yml', - ref: 'master', - inputs: { - version: require('./packages/create-app/package.json').version, - releaseVersion: require('./package.json').version - }, + ref: 'master' }); diff --git a/.github/workflows/sync_renovate-changesets.yml b/.github/workflows/sync_renovate-changesets.yml index 5f98f80615..04495e606b 100644 --- a/.github/workflows/sync_renovate-changesets.yml +++ b/.github/workflows/sync_renovate-changesets.yml @@ -11,12 +11,35 @@ jobs: if: github.actor == 'renovate[bot]' && github.repository == 'backstage/backstage' steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit + - name: Approve yarn.lock-only PRs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} + script: | + const { data: files } = await github.rest.pulls.listFiles({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + }); + + if (files.some(file => !file.filename.endsWith("yarn.lock"))) { + console.log("Skipping approval since some files are not yarn.lock"); + return; + } + + await github.rest.pulls.createReview({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + event: "APPROVE", + }); + - name: Checkout - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 2 ref: ${{ github.head_ref }} @@ -26,7 +49,7 @@ jobs: git config --global user.email noreply@backstage.io git config --global user.name 'Github changeset workflow' - name: Generate changeset - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const { promises: fs } = require("fs"); diff --git a/.github/workflows/sync_snyk-github-issues.yml b/.github/workflows/sync_snyk-github-issues.yml index d56fac654a..b04c5b9ab7 100644 --- a/.github/workflows/sync_snyk-github-issues.yml +++ b/.github/workflows/sync_snyk-github-issues.yml @@ -12,21 +12,21 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Use Node.js 20.x - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + - name: Use Node.js 22.x + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - node-version: 20.x + node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8 with: - cache-prefix: ${{ runner.os }}-v20.x + cache-prefix: ${{ runner.os }}-v22.x - name: Create Snyk report uses: snyk/actions/node@9adf32b1121593767fc3c057af55b55db032dc04 # master diff --git a/.github/workflows/sync_snyk-monitor.yml b/.github/workflows/sync_snyk-monitor.yml index 0d7befd3b7..a5f5707a52 100644 --- a/.github/workflows/sync_snyk-monitor.yml +++ b/.github/workflows/sync_snyk-monitor.yml @@ -25,11 +25,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Monitor and Synchronize Snyk Policies uses: snyk/actions/node@9adf32b1121593767fc3c057af55b55db032dc04 # master with: @@ -58,6 +58,6 @@ jobs: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} NODE_OPTIONS: --max-old-space-size=7168 - name: Upload Snyk report - uses: github/codeql-action/upload-sarif@a4e1a019f5e24960714ff6296aee04b736cbc3cf # v3.29.6 + uses: github/codeql-action/upload-sarif@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1 with: sarif_file: snyk.sarif diff --git a/.github/workflows/sync_version-packages.yml b/.github/workflows/sync_version-packages.yml index 7ca74c6aeb..ad6239d149 100644 --- a/.github/workflows/sync_version-packages.yml +++ b/.github/workflows/sync_version-packages.yml @@ -12,20 +12,32 @@ jobs: create-release-pr: name: Create Changeset PR runs-on: ubuntu-latest + + env: + CI: true + NODE_OPTIONS: --max-old-space-size=8192 + steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 20000 fetch-tags: true token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} + - name: Use Node.js 22.x + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: 22.x + registry-url: https://registry.npmjs.org/ # Needed for auth + - name: Install Dependencies run: yarn --immutable + - name: Create Release Pull Request uses: backstage/changesets-action@a39baf18913e669734ffb00c2fd9900472cfa240 # v2.3.2 with: @@ -37,8 +49,23 @@ jobs: - name: Discord notification if: ${{ failure() }} - uses: Ilshidur/action-discord@d2594079a10f1d6739ee50a2471f0ca57418b554 # 0.4.0 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} with: - args: 'Version Packages Sync Failed https://github.com/{{GITHUB_REPOSITORY}}/actions/runs/{{GITHUB_RUN_ID}}' + script: | + const webhook = process.env.DISCORD_WEBHOOK; + if (!webhook) { + throw new Error('DISCORD_WEBHOOK secret is not set'); + } + const repo = context.repo.owner + '/' + context.repo.repo; + const runId = context.runId; + const message = `Version Packages Sync Failed https://github.com/${repo}/actions/runs/${runId}`; + const response = await fetch(webhook, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ content: message }), + }); + if (!response.ok) { + throw new Error(`Discord webhook request failed: ${response.status} ${response.statusText}`); + } diff --git a/.github/workflows/verify_accessibility-noop.yml b/.github/workflows/verify_accessibility-noop.yml index abb3abc665..8cf164ecfa 100644 --- a/.github/workflows/verify_accessibility-noop.yml +++ b/.github/workflows/verify_accessibility-noop.yml @@ -26,7 +26,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit diff --git a/.github/workflows/verify_accessibility.yml b/.github/workflows/verify_accessibility.yml index 3b624c96bc..83d27612ae 100644 --- a/.github/workflows/verify_accessibility.yml +++ b/.github/workflows/verify_accessibility.yml @@ -20,19 +20,19 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - - name: Use Node.js 20.x - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Use Node.js 22.x + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - node-version: 20.x + node-version: 22.x - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8 with: - cache-prefix: ${{ runner.os }}-v20.x + cache-prefix: ${{ runner.os }}-v22.x - name: run Lighthouse CI run: | yarn dlx @lhci/cli@0.11.x autorun diff --git a/.github/workflows/verify_chromatic-noop.yml b/.github/workflows/verify_chromatic-noop.yml index 2a2ad7d43d..5fe97c8903 100644 --- a/.github/workflows/verify_chromatic-noop.yml +++ b/.github/workflows/verify_chromatic-noop.yml @@ -20,7 +20,7 @@ jobs: name: Chromatic steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit diff --git a/.github/workflows/verify_chromatic.yml b/.github/workflows/verify_chromatic.yml index e9f34e35ac..60cc1745d0 100644 --- a/.github/workflows/verify_chromatic.yml +++ b/.github/workflows/verify_chromatic.yml @@ -19,28 +19,28 @@ jobs: strategy: matrix: os: [ubuntu-latest] - node-version: [20.x] + node-version: [22.x] name: Chromatic steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 10000 # Required to retrieve git history - name: Use node.js ${{ matrix.node-version }} - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: Install dependencies - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} @@ -49,7 +49,7 @@ jobs: - name: Run Chromatic id: chromatic - uses: chromaui/action@latest + uses: chromaui/action@f191a0224b10e1a38b2091cefb7b7a2337009116 # latest with: token: ${{ secrets.GITHUB_TOKEN }} # projectToken intentionally shared to allow collaborators to run Chromatic on forks @@ -82,7 +82,7 @@ jobs: - name: Post Chromatic Link in PR Comment if: github.event_name == 'pull_request' && steps.chromatic.outputs.url && github.event.pull_request.head.repo.full_name == github.repository - uses: mshick/add-pr-comment@v2 + uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2 with: message: | ## 🎨 Visual Testing with Chromatic diff --git a/.github/workflows/verify_codeql.yml b/.github/workflows/verify_codeql.yml index 7dad08fa01..b9b1c734cf 100644 --- a/.github/workflows/verify_codeql.yml +++ b/.github/workflows/verify_codeql.yml @@ -42,12 +42,12 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit - name: Checkout repository - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. @@ -55,7 +55,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@a4e1a019f5e24960714ff6296aee04b736cbc3cf # v3.29.6 + uses: github/codeql-action/init@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -66,7 +66,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@a4e1a019f5e24960714ff6296aee04b736cbc3cf # v3.29.6 + uses: github/codeql-action/autobuild@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -80,4 +80,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@a4e1a019f5e24960714ff6296aee04b736cbc3cf # v3.29.6 + uses: github/codeql-action/analyze@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1 diff --git a/.github/workflows/verify_docs-quality.yml b/.github/workflows/verify_docs-quality.yml index 5f31c7a289..792402f7ad 100644 --- a/.github/workflows/verify_docs-quality.yml +++ b/.github/workflows/verify_docs-quality.yml @@ -7,29 +7,48 @@ on: - '**.md' jobs: - check-all-files: + check-docs: + permissions: + contents: read + pull-requests: read runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - # Vale does not support file excludes, so we use the script to generate a list of files instead - # The action also does not allow args or a local config file to be passed in, so the files array - # also contains an "--config=.github/vale/config.ini" option - - name: generate vale args - id: generate - run: echo "args=$(node scripts/check-docs-quality.js --ci-args)" >> $GITHUB_OUTPUT + - name: Install vale + run: | + VALE_VERSION="3.7.1" + VALE_CHECKSUM="ba4924bf2c5884499f09b02a6eb3318b29df40a3e81701c0804b9b1aefcd9483" + VALE_DIST_DIR="/tmp/vale-dist" + VALE_ARCHIVE="vale_${VALE_VERSION}_Linux_64-bit.tar.gz" - - name: documentation quality check - uses: errata-ai/vale-action@d89dee975228ae261d22c15adcd03578634d429c # v2.1.1 - with: - # This also contains --config=.github/vale/config.ini ... :/ - files: '${{ steps.generate.outputs.args }}' - version: latest + mkdir -p "$VALE_DIST_DIR" + + # Download pinned Vale binary + curl -fsSL -H "Authorization: token $GH_TOKEN" "https://github.com/errata-ai/vale/releases/download/v${VALE_VERSION}/${VALE_ARCHIVE}" -o "$VALE_DIST_DIR/$VALE_ARCHIVE" + + # Verify the integrity of the downloaded archive + echo "$VALE_CHECKSUM $VALE_DIST_DIR/$VALE_ARCHIVE" | sha256sum --check + + mkdir -p "$HOME/.local/bin" + tar -xzf "$VALE_DIST_DIR/$VALE_ARCHIVE" -C "$HOME/.local/bin" vale + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + vale --version env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ github.token }} + + - name: Get changed files + run: | + gh api "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files" \ + --paginate -q '.[] | select(.status != "removed") | .filename' > /tmp/pr-files.txt + env: + GH_TOKEN: ${{ github.token }} + + - name: Documentation quality check + run: node scripts/check-docs-quality.js --ci /tmp/pr-files.txt diff --git a/.github/workflows/verify_e2e-linux-noop.yml b/.github/workflows/verify_e2e-linux-noop.yml index cc25b2db58..dbba242bae 100644 --- a/.github/workflows/verify_e2e-linux-noop.yml +++ b/.github/workflows/verify_e2e-linux-noop.yml @@ -24,12 +24,12 @@ jobs: strategy: matrix: - node-version: [20.x, 22.x] + node-version: [22.x, 24.x] name: E2E Linux ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit diff --git a/.github/workflows/verify_e2e-linux.yml b/.github/workflows/verify_e2e-linux.yml index 25d71eb641..1a0eea2884 100644 --- a/.github/workflows/verify_e2e-linux.yml +++ b/.github/workflows/verify_e2e-linux.yml @@ -34,7 +34,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [20.x, 22.x] + node-version: [22.x, 24.x] env: CI: true @@ -43,11 +43,23 @@ jobs: name: E2E Linux ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + # Store the PR number for the Sync Pull Requests workflow + - name: Save PR context + if: matrix.node-version == '22.x' && github.event_name == 'pull_request' + run: | + mkdir -p ./context + echo "${{ github.event.pull_request.number }}" > ./context/pr-number + - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + if: matrix.node-version == '22.x' && github.event_name == 'pull_request' + with: + name: pr-context + path: context/ + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Configure Git run: | @@ -55,12 +67,12 @@ jobs: git config --global user.name 'GitHub e2e user' - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} diff --git a/.github/workflows/verify_e2e-techdocs.yml b/.github/workflows/verify_e2e-techdocs.yml index 6bb8f11651..370d10310d 100644 --- a/.github/workflows/verify_e2e-techdocs.yml +++ b/.github/workflows/verify_e2e-techdocs.yml @@ -23,7 +23,7 @@ jobs: strategy: matrix: - node-version: [20.x, 22.x] + node-version: [22.x, 24.x] env: CI: true @@ -32,21 +32,21 @@ jobs: name: Techdocs steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.9' - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ matrix.node-version }} - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} diff --git a/.github/workflows/verify_e2e-windows-noop.yml b/.github/workflows/verify_e2e-windows-noop.yml index f8681e8b10..a04da45600 100644 --- a/.github/workflows/verify_e2e-windows-noop.yml +++ b/.github/workflows/verify_e2e-windows-noop.yml @@ -16,16 +16,16 @@ permissions: jobs: noop: - runs-on: windows-2022 + runs-on: windows-2025 strategy: matrix: - node-version: [20.x, 22.x] + node-version: [22.x, 24.x] name: E2E Windows ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit diff --git a/.github/workflows/verify_e2e-windows.yml b/.github/workflows/verify_e2e-windows.yml index f98fc98745..0bce3fd594 100644 --- a/.github/workflows/verify_e2e-windows.yml +++ b/.github/workflows/verify_e2e-windows.yml @@ -20,20 +20,20 @@ concurrency: jobs: build: - runs-on: windows-2022 + runs-on: windows-2025 strategy: matrix: - node-version: [20.x, 22.x] + node-version: [22.x, 24.x] env: CI: true - NODE_OPTIONS: --max-old-space-size=8192 --no-node-snapshot --experimental-vm-modules + NODE_OPTIONS: --max-old-space-size=8192 --experimental-vm-modules name: E2E Windows ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit @@ -44,7 +44,7 @@ jobs: git config --global core.autocrlf false git config --global core.eol lf - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Configure Git run: | @@ -52,18 +52,18 @@ jobs: git config --global user.name 'GitHub e2e user' - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: setup python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.10' - name: Add msbuild to PATH - uses: microsoft/setup-msbuild@6fb02220983dee41ce7ae257b6f4d8f9bf5ed4ce # v2.0.0 + uses: microsoft/setup-msbuild@30375c66a4eea26614e0d39710365f22f8b0af57 # v3.0.0 - name: Setup gyp env run: | @@ -80,7 +80,7 @@ jobs: uses: browser-actions/setup-chrome@803ef6dfb4fdf22089c9563225d95e4a515820a0 # latest - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} diff --git a/.github/workflows/verify_fossa.yml b/.github/workflows/verify_fossa.yml index 732e53988a..80cd7907e2 100644 --- a/.github/workflows/verify_fossa.yml +++ b/.github/workflows/verify_fossa.yml @@ -14,12 +14,12 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit - name: Checkout - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install Fossa run: "curl -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/fossas/fossa-cli/master/install.sh | bash" diff --git a/.github/workflows/verify_microsite-noop.yml b/.github/workflows/verify_microsite-noop.yml index a6edeb8415..08eeac4431 100644 --- a/.github/workflows/verify_microsite-noop.yml +++ b/.github/workflows/verify_microsite-noop.yml @@ -21,7 +21,7 @@ jobs: name: Microsite steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit diff --git a/.github/workflows/verify_microsite.yml b/.github/workflows/verify_microsite.yml index 689b85fe9f..8578f0d41a 100644 --- a/.github/workflows/verify_microsite.yml +++ b/.github/workflows/verify_microsite.yml @@ -28,84 +28,74 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit - - name: find latest release - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + - name: find latest release branch + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 id: find-release with: script: | - const { data } = await github.rest.repos.listTags({ + const data = await octokit.paginate(github.rest.repos.listBranches, { owner: context.repo.owner, repo: context.repo.repo, per_page: 100, }) - const [{tag}] = data + const [{branch}] = data .map(i => i.name) - .filter(tag => tag.match(/^v\d+\.\d+\.\d+$/)) - .map(tag => ({ - tag, - val: tag + .filter(branch => branch.match(/^patch\/v\d+\.\d+\.\d+$/)) + .map(branch => ({ + branch, + val: branch + .split('/')[1] .slice(1) .split('.') .reduce((val, part) => Number(val) * 1000 + Number(part)) })) .sort((a, b) => b.val - a.val) - return tag + return branch result-encoding: string - name: checkout latest release - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - ref: refs/tags/${{ steps.find-release.outputs.result }} + ref: refs/heads/${{ steps.find-release.outputs.result }} - - name: Use Node.js 20.x - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + - name: Use Node.js 22.x + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - node-version: 20.x + node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8 with: - cache-prefix: ${{ runner.os }}-v20.x - - - name: build API reference - run: yarn build:api-docs - - - name: upload API reference - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: stable-reference - path: docs/reference/ - if-no-files-found: error - retention-days: 1 + cache-prefix: ${{ runner.os }}-v22.x # Use the lower-level cache actions for the success cache, so that we can store the cache even on failed builds - name: restore package-docs cache - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 with: path: .cache/package-docs key: ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-stable-${{ github.run_id }} restore-keys: | ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-stable- - - name: build API reference (beta) + - name: build API reference run: yarn backstage-repo-tools package-docs - - name: upload API reference (beta) - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + - name: upload API reference + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: - name: stable-reference-beta + name: stable-reference path: type-docs/ # Always save success cache even if there were failures, that way it can be used in re-triggered builds - name: save package-docs cache - uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 if: always() with: path: .cache/package-docs @@ -120,7 +110,7 @@ jobs: run: yarn docusaurus gen-api-docs all - name: upload OpenAPI API docs - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: name: stable-openapi-docs path: docs/**/api/**/* @@ -137,56 +127,45 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit - name: checkout master - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Use Node.js 20.x - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + - name: Use Node.js 22.x + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - node-version: 20.x + node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8 with: - cache-prefix: ${{ runner.os }}-v20.x - - - name: build API reference - run: yarn build:api-docs - - - name: upload API reference - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: next-reference - path: docs/reference/ - if-no-files-found: error - retention-days: 1 + cache-prefix: ${{ runner.os }}-v22.x # Use the lower-level cache actions for the success cache, so that we can store the cache even on failed builds - name: restore package-docs cache - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 with: path: .cache/package-docs key: ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-next-${{ github.run_id }} restore-keys: | ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-next- - - name: build API reference (beta) + - name: build API reference run: yarn backstage-repo-tools package-docs - - name: upload API reference (beta) - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + - name: upload API reference + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: - name: next-reference-beta + name: next-reference path: type-docs/ # Always save success cache even if there were failures, that way it can be used in re-triggered builds - name: save package-docs cache - uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 if: always() with: path: .cache/package-docs @@ -196,7 +175,7 @@ jobs: run: yarn build-storybook - name: Upload Storybook - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: name: storybook path: dist-storybook @@ -212,7 +191,7 @@ jobs: run: yarn docusaurus gen-api-docs all - name: upload OpenAPI API docs - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: name: next-openapi-docs path: docs/**/api/**/* @@ -234,17 +213,17 @@ jobs: name: Microsite steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Use Node.js 20.x - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + - name: Use Node.js 22.x + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - node-version: 20.x - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + node-version: 22.x + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version: '3.9' @@ -267,23 +246,31 @@ jobs: - name: verify yarn dependency duplicates run: node scripts/verify-lockfile-duplicates.js - - name: download stable reference - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - name: stable-reference - path: docs/reference - - name: download stable OpenAPI API docs - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: stable-openapi-docs path: docs - - name: grab lastest releases docs + - name: grab latest releases docs run: | git fetch origin master --depth 1 git checkout FETCH_HEAD -- docs/releases + - name: create missing generated file + run: | + mkdir -p docs/reference + cat > docs/reference/index.md <.txt` that define which pull requests should be included in the next patch release, where the content of the file is the description of the fix. + +The [sync_patch-release.yml](/.github/workflows/sync_patch-release.yml) workflow will automatically create a "Patch Release" PR with the patches in this directory. + +## Usage + +To add a PR to the set of patches, run `yarn patch-pr ` in the root of the repository. + +Once a patch has been applied and merged, manually delete the corresponding patch file from this directory. The patch script will automatically skip patches that have already been applied to the target branch, so it's safe to re-run the script even if some patches are already present. + +## GitHub Workflow + +A GitHub workflow automatically keeps a "Patch Release" PR in sync with the patches listed in this directory. When you add, modify, or remove patch files, the workflow will: + +- Update the existing PR if patch files exist +- Close and delete the PR branch if no patch files exist diff --git a/.patches/create-pr-patch.js b/.patches/create-pr-patch.js new file mode 100755 index 0000000000..381810a6ff --- /dev/null +++ b/.patches/create-pr-patch.js @@ -0,0 +1,15 @@ +#!/usr/bin/env node + +const fs = require('node:fs'); +const path = require('node:path'); + +const prNumber = process.argv[2]; +const description = process.argv.slice(3).join(' '); + +if (!prNumber || !description) { + console.error('Usage: yarn patch-pr '); + process.exit(1); +} + +const patchFilePath = path.join(__dirname, `pr-${prNumber}.txt`); +fs.writeFileSync(patchFilePath, description); diff --git a/.patches/pr-33908.txt b/.patches/pr-33908.txt new file mode 100644 index 0000000000..5d0dd14d5a --- /dev/null +++ b/.patches/pr-33908.txt @@ -0,0 +1 @@ +Make TechDocs sidebar positioning configurable via CSS custom properties \ No newline at end of file diff --git a/.patches/pr-33952.txt b/.patches/pr-33952.txt new file mode 100644 index 0000000000..3244566d2e --- /dev/null +++ b/.patches/pr-33952.txt @@ -0,0 +1 @@ +Bump zod dependency to v4 for packages using configSchema and clarify that zod/v4 subpath from v3 is not supported \ No newline at end of file diff --git a/.patches/pr-33975.txt b/.patches/pr-33975.txt new file mode 100644 index 0000000000..5cc55be263 --- /dev/null +++ b/.patches/pr-33975.txt @@ -0,0 +1 @@ +Clamp React Aria dependency ranges to patch-only updates to prevent unintended minor version upgrades \ No newline at end of file diff --git a/.patches/pr-33984.txt b/.patches/pr-33984.txt new file mode 100644 index 0000000000..522b8121d9 --- /dev/null +++ b/.patches/pr-33984.txt @@ -0,0 +1 @@ +Fix active tab indicator disappearing on uncontrolled Tabs in @backstage/ui \ No newline at end of file diff --git a/.storybook/main.ts b/.storybook/main.ts index 788df83ba9..865ae90848 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -1,16 +1,24 @@ -import type { StorybookConfig } from '@storybook/react-vite'; +// This file has been automatically migrated to valid ESM format by Storybook. +import { defineMain } from '@storybook/react-vite/node'; +import { fileURLToPath } from 'node:url'; +import { createRequire } from 'node:module'; import { join, dirname, posix } from 'path'; +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const require = createRequire(import.meta.url); + const isChromatic = process.env.STORYBOOK_STORY_SET === 'chromatic'; // All stories for full development const allStories = isChromatic - ? ['packages/ui'] + ? ['packages/ui', 'plugins/app'] : [ 'packages/ui', 'packages/core-components', 'packages/app', + 'plugins/app', 'plugins/org', 'plugins/search', 'plugins/search-react', @@ -19,34 +27,37 @@ const allStories = isChromatic ]; const rootPath = '../'; -const storiesSrcMdx = 'src/**/*.mdx'; const storiesSrcGlob = 'src/**/*.stories.@(js|jsx|mjs|ts|tsx)'; const getStoriesPath = (element: string, pattern: string) => posix.join(rootPath, element, pattern); -const stories = allStories.flatMap(element => [ - getStoriesPath(element, storiesSrcMdx), +const stories = allStories.map(element => getStoriesPath(element, storiesSrcGlob), -]); +); // Resolve absolute path of a package. Needed in monorepos. function getAbsolutePath(value: string): any { return dirname(require.resolve(join(value, 'package.json'))); } -const config: StorybookConfig = { +export default defineMain({ stories, addons: [ getAbsolutePath('@storybook/addon-links'), getAbsolutePath('@storybook/addon-themes'), getAbsolutePath('@storybook/addon-docs'), getAbsolutePath('@storybook/addon-a11y'), + getAbsolutePath('@storybook/addon-vitest'), + getAbsolutePath('@storybook/addon-mcp'), ], framework: { name: getAbsolutePath('@storybook/react-vite'), options: {}, }, + typescript: { + reactDocgen: 'react-docgen', + }, viteFinal: async (config, { configType }) => { // Add Node.js polyfills for browser compatibility // @@ -122,6 +133,4 @@ const config: StorybookConfig = { return config; }, -}; - -export default config; +}); diff --git a/.storybook/modes.ts b/.storybook/modes.ts index 7087fa35eb..ae4ff76a7c 100644 --- a/.storybook/modes.ts +++ b/.storybook/modes.ts @@ -15,4 +15,19 @@ export const allModes = { themeMode: 'dark', themeName: 'spotify', }, + 'light spotify neutral-1': { + themeMode: 'light', + themeName: 'spotify', + background: 'neutral-1', + }, + 'light spotify neutral-2': { + themeMode: 'light', + themeName: 'spotify', + background: 'neutral-2', + }, + 'light spotify neutral-3': { + themeMode: 'light', + themeName: 'spotify', + background: 'neutral-3', + }, } as const; diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 8f987ebab1..916c48951d 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -1,8 +1,12 @@ +import addonA11y from '@storybook/addon-a11y'; +import addonDocs from '@storybook/addon-docs'; +import addonThemes from '@storybook/addon-themes'; +import addonLinks from '@storybook/addon-links'; +import { definePreview } from '@storybook/react-vite'; import React, { useEffect } from 'react'; import { TestApiProvider } from '@backstage/test-utils'; -import { Content, AlertDisplay } from '@backstage/core-components'; -import { apis } from './support/apis'; -import type { Decorator, Preview } from '@storybook/react-vite'; +import { AlertDisplay } from '@backstage/core-components'; +import { apis, appThemeApi } from './support/apis'; import { useGlobals } from 'storybook/preview-api'; import { UnifiedThemeProvider, themes } from '@backstage/theme'; import { allModes } from './modes'; @@ -15,8 +19,10 @@ import './storybook.css'; // Custom themes import './themes/spotify.css'; +import { Box } from '../packages/ui/src/components/Box'; -const preview: Preview = { +export default definePreview({ + tags: ['manifest'], globalTypes: { themeMode: { name: 'Theme Mode', @@ -25,10 +31,9 @@ const preview: Preview = { toolbar: { icon: 'circlehollow', items: [ - { value: 'light', icon: 'circlehollow', title: 'Light' }, - { value: 'dark', icon: 'circle', title: 'Dark' }, + { value: 'light', icon: 'sun', title: 'Light' }, + { value: 'dark', icon: 'moon', title: 'Dark' }, ], - showName: true, dynamicTitle: true, }, }, @@ -42,18 +47,32 @@ const preview: Preview = { { value: 'backstage', title: 'Backstage' }, { value: 'spotify', title: 'Spotify' }, ], - showName: true, dynamicTitle: true, }, }, + background: { + name: 'Background', + description: 'Global background for components', + defaultValue: 'app', + toolbar: { + icon: 'contrast', + items: [ + { value: 'app', title: 'App Background' }, + { value: 'neutral-1', title: 'Neutral 1 Background' }, + { value: 'neutral-2', title: 'Neutral 2 Background' }, + { value: 'neutral-3', title: 'Neutral 3 Background' }, + ], + }, + }, }, + initialGlobals: { themeMode: 'light', themeName: 'backstage', + background: 'app', }, - parameters: { - layout: 'fullscreen', + parameters: { backgrounds: { disable: true, }, @@ -67,12 +86,19 @@ const preview: Preview = { options: { storySort: { - order: ['Backstage UI', 'Plugins', 'Layout', 'Navigation'], + order: [ + 'Backstage UI', + 'Recipes', + 'Guidelines', + 'Plugins', + 'Layout', + 'Navigation', + ], }, }, viewport: { - viewports: { + options: { initial: { name: 'Initial', styles: { width: '320px', height: '100%' }, @@ -101,14 +127,24 @@ const preview: Preview = { // 'dark spotify': allModes['dark spotify'], }, }, + + a11y: { + // 'todo' - show a11y violations in the test UI only + // 'error' - fail CI on a11y violations + // 'off' - skip a11y checks entirely + test: 'todo', + }, }, + decorators: [ - Story => { + (Story, context) => { const [globals] = useGlobals(); const selectedTheme = globals.themeMode === 'light' ? themes.light : themes.dark; const selectedThemeMode = globals.themeMode || 'light'; const selectedThemeName = globals.themeName || 'backstage'; + const selectedBackground = globals.background || 'app'; + const isFullscreen = context.parameters.layout === 'fullscreen'; useEffect(() => { document.body.removeAttribute('data-theme-mode'); @@ -121,10 +157,16 @@ const preview: Preview = { }; }, [selectedTheme, selectedThemeName]); - document.body.style.backgroundColor = 'var(--bui-bg)'; + useEffect(() => { + appThemeApi.setActiveThemeId(selectedThemeMode); + }, [selectedThemeMode]); + + document.body.style.backgroundColor = 'var(--bui-bg-app)'; + document.body.style.padding = + isFullscreen && selectedBackground !== 'app' ? '1rem' : ''; const docsStoryElements = document.getElementsByClassName('docs-story'); Array.from(docsStoryElements).forEach(element => { - (element as HTMLElement).style.backgroundColor = 'var(--bui-bg)'; + (element as HTMLElement).style.backgroundColor = 'var(--bui-bg-app)'; }); return ( @@ -132,14 +174,24 @@ const preview: Preview = { {/* @ts-ignore */} - - - + {Array.from({ + length: + selectedBackground === 'app' + ? 0 + : parseInt(selectedBackground.split('-')[1], 10), + }).reduce( + children => ( + + {children} + + ), + , + )} ); }, ], -}; -export default preview; + addons: [addonLinks(), addonThemes(), addonDocs(), addonA11y()], +}); diff --git a/.storybook/storybook.css b/.storybook/storybook.css index e69c4a8eb1..e0f0b68678 100644 --- a/.storybook/storybook.css +++ b/.storybook/storybook.css @@ -5,15 +5,15 @@ --sb-panel-left: 0; --sb-panel-right: 0; --sb-sidebar-border: none; - --sb-sidebar-border-right: 1px solid var(--bui-border); + --sb-sidebar-border-right: 1px solid var(--bui-border-2); --sb-sidebar-bg: #000; --sb-options-border: none; - --sb-options-border-left: 1px solid var(--bui-border); + --sb-options-border-left: 1px solid var(--bui-border-2); --sb-content-padding-inline: 250px; } [data-theme-mode='dark'] { - --sb-sidebar-bg: var(--bui-bg-surface-1); + --sb-sidebar-bg: var(--bui-bg-neutral-1); } [data-theme-name='spotify'] { @@ -24,15 +24,15 @@ --sb-panel-right: 8px; --sb-sidebar-border: none; --sb-sidebar-border-right: none; - --sb-sidebar-bg: var(--bui-bg-surface-1); + --sb-sidebar-bg: var(--bui-bg-neutral-1); --sb-options-border: none; --sb-options-border-left: none; --sb-content-padding-inline: 258px; } [data-theme-name='spotify'][data-theme-mode='light'] { - --sb-sidebar-border: 1px solid var(--bui-border); - --sb-sidebar-border-right: 1px solid var(--bui-border); - --sb-options-border: 1px solid var(--bui-border); - --sb-options-border-left: 1px solid var(--bui-border); + --sb-sidebar-border: 1px solid var(--bui-border-2); + --sb-sidebar-border-right: 1px solid var(--bui-border-2); + --sb-options-border: 1px solid var(--bui-border-2); + --sb-options-border-left: 1px solid var(--bui-border-2); } diff --git a/.storybook/support/apis.js b/.storybook/support/apis.js index 905c5f2dc8..42085591c0 100644 --- a/.storybook/support/apis.js +++ b/.storybook/support/apis.js @@ -1,5 +1,6 @@ import { AlertApiForwarder, + AppThemeSelector, ErrorAlerter, ErrorApiForwarder, GithubAuth, @@ -13,6 +14,7 @@ import { import { alertApiRef, + appThemeApiRef, errorApiRef, githubAuthApiRef, gitlabAuthApiRef, @@ -24,10 +26,16 @@ import { featureFlagsApiRef, } from '@backstage/core-plugin-api'; +import { themes } from '@backstage/theme'; + import { translationApiRef } from '@backstage/core-plugin-api/alpha'; import { MockTranslationApi } from '@backstage/test-utils/alpha'; const configApi = new ConfigReader({}); +export const appThemeApi = AppThemeSelector.createWithStorage([ + { id: 'light', title: 'Light', variant: 'light', theme: themes.light }, + { id: 'dark', title: 'Dark', variant: 'dark', theme: themes.dark }, +]); const featureFlagsApi = new LocalStorageFeatureFlags(); const alertApi = new AlertApiForwarder(); const errorApi = new ErrorAlerter(alertApi, new ErrorApiForwarder()); @@ -62,6 +70,7 @@ const translationApi = MockTranslationApi.create(); export const apis = [ [configApiRef, configApi], + [appThemeApiRef, appThemeApi], [featureFlagsApiRef, featureFlagsApi], [alertApiRef, alertApi], [errorApiRef, errorApi], diff --git a/.storybook/themes/spotify.css b/.storybook/themes/spotify.css index bcb1ded9ff..30aa0274d0 100644 --- a/.storybook/themes/spotify.css +++ b/.storybook/themes/spotify.css @@ -174,7 +174,7 @@ } .bui-TableHeader .bui-TableRow { - border-bottom: 1px solid var(--bui-border); + border-bottom: 1px solid var(--bui-border-2); } .bui-TableHead { @@ -183,29 +183,6 @@ font-weight: var(--bui-font-weight-regular); } - .bui-HeaderToolbar { - padding-top: var(--bui-space-2); - padding-inline: var(--bui-space-2); - } - - .bui-HeaderToolbarWrapper { - border-radius: var(--bui-radius-3); - padding-inline: var(--bui-space-3); - border: none; - } - - .bui-HeaderToolbarControls { - right: calc(var(--bui-space-3) - 1px); - } - - .bui-HeaderTabsWrapper { - margin-top: var(--bui-space-2); - margin-inline: var(--bui-space-2); - border-radius: var(--bui-radius-3); - padding-inline: var(--bui-space-1); - border: none; - } - .bui-Input { border-radius: var(--bui-radius-3); } @@ -213,76 +190,77 @@ .bui-Tag { border-radius: var(--bui-radius-full); } + + .bui-Container { + padding-inline: 0; + } } [data-theme-mode='light'][data-theme-name='spotify'] { - --bui-bg: var(--bui-gray-1); - --bui-bg-surface-1: var(--bui-white); - --bui-bg-surface-2: var(--bui-gray-2); --bui-bg-solid: #1ed760; --bui-bg-solid-hover: #3be477; --bui-bg-solid-pressed: #1abc54; - --bui-bg-solid-disabled: var(--bui-gray-2); + --bui-bg-solid-disabled: #0f6c30; --bui-fg-primary: var(--bui-black); - --bui-fg-secondary: var(--bui-gray-7); + --bui-fg-secondary: #757575; --bui-fg-solid: var(--bui-black); + --bui-fg-solid-disabled: #62ab7c; - --bui-border: var(--bui-gray-3); - --bui-border-hover: rgba(0, 0, 0, 0.3); - --bui-border-pressed: rgba(0, 0, 0, 0.5); - --bui-border-disabled: rgba(0, 0, 0, 0.1); + --bui-border-2: #d9d9d9; --bui-border-danger: #f87a7a; --bui-border-warning: #e36d05; --bui-border-success: #53db83; --bui-ring: rgba(0, 0, 0, 0.2); - - .bui-HeaderToolbarWrapper { - border: 1px solid var(--bui-border); - } - - .bui-HeaderTabsWrapper { - border: 1px solid var(--bui-border); - } } [data-theme-mode='dark'][data-theme-name='spotify'] { - --bui-bg: var(--bui-black); - --bui-bg-surface-1: var(--bui-gray-1); - --bui-bg-surface-2: var(--bui-gray-2); + --bui-bg-app: var(--bui-black); + --bui-bg-solid: #1ed760; --bui-bg-solid-hover: #3be477; --bui-bg-solid-pressed: #1abc54; --bui-bg-solid-disabled: #0f6c30; - --bui-bg-tint: #242424; - --bui-bg-tint-hover: #202020; - --bui-bg-tint-pressed: #292929; - --bui-bg-tint-disabled: rgba(255, 255, 255, 0.8); + --bui-bg-danger: #3b1219; --bui-bg-warning: #302008; --bui-bg-success: #132d21; --bui-fg-primary: var(--bui-white); - --bui-fg-secondary: var(--bui-gray-7); - --bui-fg-link: var(--bui-white); - --bui-fg-link-hover: var(--bui-white); - --bui-fg-disabled: var(--bui-gray-5); + --bui-fg-secondary: #9e9e9e; + --bui-fg-disabled: #575757; --bui-fg-solid: var(--bui-black); - --bui-fg-solid-disabled: #083618; - --bui-fg-tint: var(--bui-white); - --bui-fg-tint-disabled: var(--bui-gray-5); + --bui-fg-solid-disabled: #072f15; --bui-fg-danger: #e22b2b; --bui-fg-warning: #e36d05; --bui-fg-success: #1db954; - --bui-border: var(--bui-gray-3); - --bui-border-hover: rgba(255, 255, 255, 0.4); - --bui-border-pressed: rgba(255, 255, 255, 0.5); - --bui-border-disabled: rgba(255, 255, 255, 0.2); + --bui-border-2: #373737; --bui-border-danger: #f87a7a; --bui-border-warning: #e36d05; --bui-border-success: #53db83; --bui-ring: rgba(255, 255, 255, 0.2); } + +/* + * Plugin header (@backstage/ui) and story shell header — kept at the bottom of + * this file for easier scanning alongside other component overrides above. + */ +[data-theme-name='spotify'] { + .bui-PluginHeaderToolbar { + padding: 0; + height: 32px; + border: none; + background: none; + margin-bottom: var(--bui-space-2); + } + + .bui-PluginHeaderTabsWrapper { + padding: 0; + border: none; + background: none; + margin-left: -8px; + } +} diff --git a/ADOPTERS.md b/ADOPTERS.md index 208cd370ba..a9d75babf0 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -91,10 +91,10 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [HP Inc](https://www.hp.com) | [Damon Kaswell](https://github.com/dekoding) | DevEx engagement hub (dev portal: docs, standards, Q&A) and extensive assets catalog (APIs, services, code, data, etc.) for the pan-HP internal developer community. | | [VMware](https://www.vmware.com) | [Waldir Montoya](https://github.com/waldirmontoya25), [Kris Applegate](https://github.com/krisapplegate), [Jamie Klassen](https://github.com/jamieklassen) | Part of [Tanzu Application Platform](https://docs.vmware.com/en/VMware-Tanzu-Application-Platform/index.html) offering; internal developer portal | | [Ualá](https://www.uala.com.ar/) | [Santiago Bernal](https://github.com/sabernal) | Initial work being done to centralize documentation for all our microservices and APIs, as well as scaffolding new services and tracking code quality | -| [IKEA IT AB](https://www.ingka.com) | [@bjornramberg](https://github.com/bjornramberg), [@supriyachitale](https://github.com/supriyachitale) | Supporting engineers at scale with self serve access and connecting the dots of our engineering platform and services, enabling product teams to move faster and go further, and unleashing innovation, reuse and co-creation across the organisation. | +| [IKEA Retail](https://www.ingka.com) | [Björn Ramberg](https://github.com/bjornramberg), [Martin Norin](https://github.com/martin-norin) | Centralized developer portal, supporting the internal engineering community with self serve access, documentation, templates, catalog and more. | | [Invitae](https://www.invitae.com/en) | [@ryan-hanchett](https://github.com/ryan-hanchett), [@gmandler42](https://github.com/gmandler42) | Centralized Developer Experience portal, putting all of our tooling behind a single pane of glass and creating a living service catalog. | | [Fortum](https://www.fortum.com/) | [@brunoamaroalmeida](https://github.com/brunoamaroalmeida), [@dhaval-vithalani](https://github.com/dhaval-vithalani), [@sunilkumarmohanty](https://github.com/sunilkumarmohanty) | A central portal containing information about our applications, services, processes and other software assets to be used by Fortum software engineering community. | -| [Procore](https://www.procore.com/jobs/engineering) | [@shayon](https://github.com/Shayon), [@jamesdabbs-procore](https://github.com/jamesdabbs-procore) | Developer portal - centralized software catalog and templates to enable self serve and reduce cognitive load. | +| [Procore](https://www.procore.com/jobs/engineering) | [@shayon](https://github.com/Shayon), [@kurtaking](https://github.com/kurtaking), [@pc-bob](https://github.com/pc-bob) | Developer portal - centralized software catalog and templates to enable self serve and reduce cognitive load. | | [Bradesco](https://banco.bradesco/html/classic/sobre/index.shtm) | [@danilosoarescardoso](https://github.com/danilosoarescardoso) | Centralized developer portal with software catalog, software templates, techdocs and some plugins from community and by our own. | | [SeatGeek](https://seatgeek.com) | [@zhammer](https://github.com/zhammer) | Software catalog and documentation platform | | [Grupo Boticário](https://www.grupoboticario.com.br/) | [@chicoribas](https://github.com/chicoribas), [@fndiaz](https://github.com/fndiaz), [@digogid](https://github.com/digogid), [@manumbs](https://github.com/manumbs), [@haooliveira84](https://github.com/haooliveira84), [@Rhullyam](https://github.com/Rhullyam) | Centralized developer portal with catalog, documentation, and software templates to build a ready to go application, including pipelines (CI/CD) and cloud services provisioning | @@ -287,3 +287,7 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [Syngenta Digital](https://www.syngentadigital.com) | [Bitan Mallick](https://www.linkedin.com/in/bitanmallick) | Internal developer portal, designed to empower developers and streamline workflows. We use software catalog, tech-radar, software templates, tech-docs and various custom plugins to ensure an efficient and collaborative development experience. | | [Sophotech](https://sopho.tech) | [@archy-rock3t-cloud](https://github.com/archy-rock3t-cloud), [Artem Muterko](mailto:artem@sopho.tech) | Custom Developer Platform based on Backstage, providing a service catalog, infrastructure templates, and integrated tooling to give developers a self-service experience. | | [Swiss Mobiliar Insurance Company Ltd.](https://www.mobiliar.ch/) | [Patrick Wyler](mailto:patrick.wyler@mobiliar.ch) and [Beat Winistörfer](mailto:beat.winistoerfer@mobiliar.ch) | The portal provides a unified interface for accessing all relevant DevOps information previously scattered across various locations, enhancing accessibility and clarity for all IT employees. It relies on an internal graph database that enhances the Backstage software catalog with many additional elements. Significant effort has been invested in the visual representation of information through graphs and diagrams, facilitating analysis and improving the understanding of dependencies. | +| [DB Systel GmbH](https://www.dbsystel.de/dbsystel-en/) | [DB Systel GmbH](https://github.com/dbsystel) | Deutsche Bahn's Internal Developer Portal leverages Backstage and Crossplane.io to deliver a fully GitOps-driven onboarding experience for engineering teams across the company. Our Scaffolder ecosystem accelerates platform setup (Artifactory, GitLab, OpenShift), service bootstrapping, automated testing, AI-ready backends, and modern frontend development. We also make heavy use of custom catalog modules and frontend plugins that support developers in checking their provisioned resources or security and compliance of their code. | +| [Danske StatsBaner (DSB)](https://www.dsb.dk/om-dsb/job-og-karriere/) | [Isaac Livingston](mailto:isjl@dsb.dk), [Paula-Catalina Curca](mailto:PACU@DSB.DK), [Muhammad Salman Khan](mailto:MUSK@DSB.DK) | DSB's portal is a central catalog of all our microservices, APIs, and systems. Developers can easily create new components with templates that run in our CNCF based developer platform | +| [NN Group](https://www.nn-group.com/) | [Flavia Naezer (Product Owner)](mailto:flavia.naezer@nn-group.com), [Vijai Ramcharan (Principal Engineer)](mailto:vijai.ramcharan@nn-group.com) | Our Backstage powered IDP supports engineers and other makers and shapers in our large financial enterprise with the Software Catalog, a Store for infrastructure, TechDocs and Portal Platform with mini App-Store.| +| [Puzzle ITC](https://www.puzzle.ch/) | [Christoph Raaflaub](https://github.com/chrira) | Portal to our developer platform. | \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..ae39378648 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,57 @@ +Backstage is an open platform for building developer portals. This is a TypeScript monorepo using Yarn workspaces. + +## Key Directories + +- `/packages`: Core framework packages (prefixed `@backstage/`) +- `/plugins`: Plugin packages (prefixed `@backstage/plugin-*`) +- `/packages/app`: Main example app using the new frontend system +- `/packages/app-legacy`: Example app using the old frontend system +- `/packages/backend`: Example backend for local development +- `/docs`: Documentation files + +Packages prefixed with `core-` (e.g., `@backstage/core-plugin-api`) are part of the old frontend system. Packages prefixed with `frontend-` (e.g., `@backstage/frontend-plugin-api`) are part of the new frontend system. Packages prefixed with `backend-` (e.g., `@backstage/backend-plugin-api`) are part of the backend system. + +## Writing Standards + +Changes to the docs should follow the documentation style guide at `/docs/contribute/doc-style-guide.md`. + +## Code Standards + +The following files contain guidelines for the project: + +- `/CONTRIBUTING.md`: comprehensive contribution guidelines. +- `/STYLE.md`: guidelines for code style. +- `/REVIEWING.md`: guidelines for pull requests and writing changesets. +- `/SECURITY.md`: guidelines for security. +- `/docs/architecture-decisions/`: contains the architecture decisions for the project. + +When writing or generating code, always match the existing coding style of each individual package and file. Different packages in the monorepo may have different conventions — consistency within a package is more important than consistency across the repo. + +When writing or generating tests, prefer fewer thorough tests with multiple assertions over many small tests. When using React Testing Library, prefer using `screen` and `.findBy*` queries over `waitFor`, and avoid adding test IDs to the implementation. + +## Development Flow + +Before any of these commands can be run, you need to run `yarn install` in the project root. + +- Build: There is no need to build the project during development, and it is verified automatically in the CI pipeline. +- Test: Use `CI=1 yarn test ` in the project root to run tests. The path can be either a single file or a directory. Always provide a path, avoid running all tests. +- Type checking: Use `yarn tsc` in the project root to run the type checker. Do not try to run it somewhere else than the project root and do not supply any options. +- Code formatting: Use `yarn prettier --write <...paths>` to format code. Run it explicitly for file paths that you know are changed, not for entire folders - otherwise it may change formatting of unrelated files. +- Lint: Use `yarn lint --fix` in the project root to run the linter. +- API reports: Before submitting a pull request with changes to any package in the workspace, run `yarn build:api-reports` in the project root to generate API reports for all packages. +- Dev server: Use `yarn start` to run the example app locally (frontend on :3000, backend on :7007). +- Create: Use `yarn new` to scaffold new plugins, packages, or modules. + +You MUST NOT run builds or create a release by running `yarn build`, `yarn changesets version`, or `yarn release` as part of any changes. Builds and releases are made by separate workflows. + +All changes that affect the published version of packages in the `/packages` and `/plugins` directories must be accompanied by a changeset. Only non-private packages require changesets. See the guidelines in `/CONTRIBUTING.md#creating-changesets` for information on how to write good changesets. Changesets are stored in the `/.changeset` directory and should be created by writing changeset files directly — never use the changeset CLI. Breaking changes must be accompanied by a `minor` version bump for packages below version `1.0.0`, or a `major` version bump for packages at version `1.0.0` or higher. For non-breaking changes that introduce new APIs or features, use `minor` for packages at version `1.0.0` or higher, and `patch` for packages below `1.0.0`. Each changeset message should be relevant to the specific package it targets and written for Backstage adopters as the audience — describe user-facing behavior changes in plain language. Never reference internal implementation details such as function names, class names, variable names, or other code symbols that are not part of the public API. If a change spans multiple packages you often need to create separate changesets to make sure they are tailored to each package. + +When creating pull requests, use the template at `/.github/PULL_REQUEST_TEMPLATE.md`. + +Never update ESLint, Prettier, or TypeScript configuration files unless specifically requested. + +Never make changes to the release notes in `/docs/releases` unless explicitly asked. These document past releases and should not be updated based on newer changes. + +## Repository Structure + +See `/docs/contribute/project-structure.md` for a detailed description of the repository structure. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ba40be86c8..fc7b1dc2f9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,6 +17,7 @@ If you need help, just jump into our [Discord chatroom](https://discord.gg/backs - [Accessibility](#accessibility) - [Get Started!](#get-started) - [Coding Guidelines](#coding-guidelines) +- [AI Use Policy and Guidelines](#ai-use-policy-and-guidelines) - [Documentation Guidelines](#documentation-guidelines) - [Package Scripts](#package-scripts) - [Local configuration](#local-configuration) @@ -128,6 +129,37 @@ If there are any updates in `markdown` file please make sure to run `yarn run li The Backstage development environment does not require any specific editor, but it is intended to be used with one that has built-in linting and type-checking. The development server does not include any checks by default, but they can be enabled using the `--check` flag. Note that using the flag may consume more system resources and slow things down. +## AI Use Policy and Guidelines + +Our goal in the Backstage project is to develop an excellent software system. This requires careful attention to detail in every change we integrate. Maintainer time and attention is very limited, so it's important that changes you ask us to review represent your _best_ work. + +You are encouraged to use tools that help you write good code, including AI tools. However, you always need to understand and explain the changes you're proposing to make, whether or not you used an LLM as part of your process to produce them. The answer to "Why did you make change X?" should never be "I'm not sure. The AI did it." + +**Do not submit an AI-generated PR you haven't personally understood and tested**, as this wastes maintainers' time. PRs that appear to violate this guideline will be closed without review. If you do submit a largely AI-generated PR, clearly mark it as such in the description—maintainers may still close it without further review if it does not seem worthwhile. + +### Using AI as a Coding Assistant + +1. Don't skip **becoming familiar with the part of the codebase** you're working on. This will let you write better prompts and validate their output if you use an LLM. Code assistants can be a useful search engine/discovery tool in this process, but don't trust claims they make about how Backstage works. LLMs are often wrong, even about details that are clearly answered in the [Backstage documentation](https://backstage.io/docs). +2. Split up your changes into **coherent commits**, even if an LLM generates them all in one go. See our section on [Creating Changesets](#creating-changesets) for guidance on documenting your changes. +3. Don't simply ask an LLM to add **code comments**, as it will likely produce a bunch of text that unnecessarily explains what's already clear from the code. If using an LLM to generate comments, be really specific in your request, demand succinctness, and carefully edit the result. + +### Using AI for Communication + +Backstage contributors are expected to communicate with intention, to avoid wasting maintainer time with long, sloppy writing. We strongly prefer clear and concise communication about points that actually require discussion over long AI-generated comments. + +When you use an LLM to write a message for you, it remains **your responsibility** to read through the whole thing and make sure that it makes sense to you and represents your ideas concisely. A good rule of thumb is that if you can't make yourself carefully read some LLM output that you generated, nobody else wants to read it either. + +Here are some concrete guidelines for using LLMs as part of your communication workflows: + +1. When writing a pull request description, **do not include anything that's obvious** from looking at your changes directly (e.g., files changed, functions updated, etc.). Instead, focus on the _why_ behind your changes. Don't ask an LLM to generate a PR description on your behalf based on your code changes, as it will simply regurgitate the information that's already there. +2. Similarly, when responding to a pull request comment, **explain _your_ reasoning**. Don't prompt an LLM to re-describe what can already be seen from the code. +3. Verify that **everything you write is accurate**, whether or not an LLM generated any part of it. Backstage's maintainers will be unable to review your contributions if you misrepresent your work (e.g., wrongly describing your code changes, their effect, or your testing process). +4. Complete all parts of the **PR description template**, including screenshots and the checklist. Don't simply overwrite the template with LLM output. +5. **Clarity and succinctness** are much more important than perfect grammar, so you shouldn't feel obliged to pass your writing through an LLM. If you do ask an LLM to clean up your writing style, be sure it does _not_ make it longer in the process. Demand succinctness in your prompt. +6. Quoting an LLM answer is usually less helpful than linking to **relevant primary sources**, like source code, [Backstage documentation](https://backstage.io/docs), or reference materials. If you do need to quote an LLM answer in a discussion, clearly distinguish LLM output from your own thoughts. + +If you have questions about AI usage or need help, feel free to ask in [Discord](https://discord.gg/backstage-687207715902193673). + ## Documentation Guidelines Contributing to the docs is one of the best ways to start getting involved with Backstage. The documentation site is often the first stop for anyone using or exploring Backstage, so even small improvements can have a big impact! @@ -138,7 +170,9 @@ To help your changes get reviewed and merged smoothly, please keep the following - We really appreciate contributions that improve clarity or fix outdated information. That said, we generally don’t accept changes that are purely stylistic (e.g., rewording a sentence just to tweak the tone or phrasing). If something is **unclear**, **confusing**, or **factually inaccurate**, those are great opportunities to help! -Ready to get started? You can find all the documentation files in the [docs](docs) directory! If you have any questions or need help, feel free to reach out in the [Backstage Discord Docs Channel](https://discord.com/channels/687207715902193673/687994765559463940) +Ready to get started? You can find all the documentation files in the [docs](docs) directory! +Please take a look through [our style guide](./docs/contribute/doc-style-guide.md) as well for writing style tips. +If you have any questions or need help, feel free to reach out in the [Backstage Discord Docs Channel](https://discord.com/channels/687207715902193673/687994765559463940). Thank you in advance for your contributions! We really appreciate it. 🙏 @@ -214,6 +248,10 @@ Changesets are an important part of the development process. They are used to ge Here are some important do's and don'ts when writing changesets: +### Changeset messages should describe user-facing behavior in plain language + +Changeset messages are read by Backstage adopters, not contributors. They should describe what changed from the user's perspective, not how the code was changed internally. Never reference internal implementation details such as function names, class names, variable names, or other code symbols that are not part of the public API. Public API names (exported functions, components, types, etc.) are fine to mention, but internal code structure should not appear in changeset messages. + ### Changeset should give a clear description to what has changed #### Bad @@ -349,7 +387,7 @@ Or from the Settings UI look for the "Git: Always Sign Off" setting and check th ## API Reports -Backstage uses [API Extractor](https://api-extractor.com/) and TSDoc comments to generate API Reports in Markdown format. These reports are what drive the [API Reference documentation](https://backstage.io/docs/reference/). What this means is that if you are making changes to the API or adding a new plugin then you will need either generate a new API Report or update an existing API Report. If you don't do this the CI build will fail when you create your Pull Request. +Backstage uses [API Extractor](https://api-extractor.com/) and TSDoc comments to generate API Reports in Markdown format. What this means is that if you are making changes to the API or adding a new plugin then you will need either generate a new API Report or update an existing API Report. If you don't do this the CI build will fail when you create your Pull Request. There are two ways you can do this: @@ -400,7 +438,7 @@ Once you've submitted a Pull Request (PR) the various bots will come out and do - checking for commits for their DCO (Developer Certificate of Origin) - kick off the various CI builds -Once these steps are completed, it's just a matter of being patient. As the reviewers have time, they will begin reviewing your PR. When the review process begins, there may be a few layers to this, but the general rule is that you need approval from one of the core maintainers and one from the specific area impacted by your PR. You may also have someone from the community review your changes. This can really help speed things up as they may catch some early items making the review for the maintainers simpler. Once you have the two (2) approvals, it's ready to be merged, a task that is also performed by the maintainers. +Once these steps are completed, it's just a matter of being patient. Reviews are coordinated by maintainers and reviewers based on project area ownership. Prioritization is driven by a number of different factors, but some important ones are size (smaller get higher priority) and alignment with current roadmap priorities. Timely responses to review comments also help keep the pull request moving faster. You may also have someone from the reviewers group review your pull request and request changes. Approval from a member of the reviewers group will greatly increase the priority of your pull request. ### Review Tips diff --git a/LABELS.md b/LABELS.md index f1af06d4fa..cd2d19e6d5 100644 --- a/LABELS.md +++ b/LABELS.md @@ -97,6 +97,27 @@ These labels indicate a workflow status for the owners. - `stale` - The issue or pull request has not seen any activity for a while and will be closed if no further activity is seen. - `no stale` - The issue or pull request should not be closed due to inactivity. +## Pull Request Labels + +The following labels indicate the status of a pull request: + +- `waiting-for:review` - The pull request needs a review and will be visible in the review queue unless already assigned an owner. +- `waiting-for:author` - Changes have been requested by a reviewer and the pull request will not receive a review until the changes are made. A comment made on the pull request by the author will also push it back into the review queue. +- `waiting-for:decision` - The pull request has been marked as more complex and needs a decision from the owners. Progress can still be made and discussion can continue, but expect the review to take longer. These pull requests are often good candidates to bring to a [SIG](https://github.com/backstage/community/tree/main/sigs) meeting. +- `waiting-for:merge` - The pull request has been approved and is awaiting merge. If you have write access and authored the pull request you can merge it yourself. If you do not have access to merge and the pull request has not been merged within a day of approval, please notify the assigned reviewer. + +The following labels indicate the size of a pull request: + +- `size:tiny` - Tiny pull requests receive a higher priority for reviews. +- `size:small` - Small pull requests receive a slightly higher priority for reviews. +- `size:medium` - Medium-sized pull requests receive no change in priority for reviews. +- `size:large` - Large pull requests receive a slightly lower priority for reviews. +- `size:huge` - Huge pull requests receive a lower priority for reviews. + +The following additional labels also apply to pull requests: + +- `reviewer-approved` - The pull request has been approved by a member of the reviewers group. This pull request receives a much higher priority for reviews. + ## Common Issue Filters This is a collection of common issue filters that can help you find issues that you are looking for or that match your interests and skills. @@ -106,7 +127,7 @@ This is a collection of common issue filters that can help you find issues that These issues are ideal for new contributors to get started and don't require much familiarity with Backstage. - [Backend](https://github.com/backstage/backstage/issues?q=is%3Aopen%20is%3Aissue%20label%3A%22good%20first%20issue%22%20label%3A%22domain%3Abackend%22%20) -- [Documentation](https://github.com/backstage/backstage/issues?q=is%3Aopen%20is%3Aissue%20label%3A%22good%20first%20issue%22%20label%3A%22domain%3Adocs%22%20) +- [Documentation](https://github.com/backstage/backstage/issues?q=is%3Aopen%20is%3Aissue%20label%3A%22good%20first%20issue%22%20label%3A%22area%3Adocumentation%22) - [Tooling](https://github.com/backstage/backstage/issues?q=is%3Aopen%20is%3Aissue%20label%3A%22good%20first%20issue%22%20label%3A%22domain%3Atooling%22%20) - [Web](https://github.com/backstage/backstage/issues?q=is%3Aopen%20is%3Aissue%20label%3A%22good%20first%20issue%22%20label%3A%22domain%3Aweb%22%20) @@ -115,7 +136,7 @@ These issues are ideal for new contributors to get started and don't require muc These issues generally require some familiarity with Backstage and the codebase, and are either open for or require contributions from the community. - [Backend]() -- [Documentation]() +- [Documentation]() - [Tooling]() - [Web]() diff --git a/OWNERS.md b/OWNERS.md index d4ee3c6400..4d535fc4b2 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -5,11 +5,13 @@ Team: @backstage/maintainers -| Maintainer | Organization | GitHub | Discord | -| --------------- | ------------ | ----------------------------------------------- | ------------- | -| Patrik Oldsberg | Spotify | [Rugvip](https://github.com/Rugvip) | `Rugvip#0019` | -| Fredrik Adelöw | Spotify | [freben](https://github.com/freben) | `freben#3926` | -| Ben Lambert | Spotify | [benjdlambert](https://github.com/benjdlambert) | `blam#2159` | +| Maintainer | Organization | GitHub | Discord | +| --------------- | ------------ | ----------------------------------------------------- | ------------- | +| André Wanlin | Spotify | [awanlin](https://github.com/awanlin) | `ahhhndre` | +| Aramis Sennyey | DoorDash | [aramissennyeydd](https://github.com/aramissennyeydd) | `Aramis#7984` | +| Ben Lambert | Spotify | [benjdlambert](https://github.com/benjdlambert) | `blam#2159` | +| Fredrik Adelöw | Spotify | [freben](https://github.com/freben) | `freben#3926` | +| Patrik Oldsberg | Spotify | [Rugvip](https://github.com/Rugvip) | `Rugvip#0019` | ## Project Areas @@ -17,7 +19,7 @@ These are the separate project areas of Backstage, each with their own project a ### Auth -Team: @backstage/auth-maintainers +Team: @backstage/auth-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Aauth-maintainers) Scope: The Backstage auth plugin and modules, as well as client-side implementations. @@ -32,7 +34,7 @@ Scope: The Backstage auth plugin and modules, as well as client-side implementat ### Catalog -Team: @backstage/catalog-maintainers +Team: @backstage/catalog-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Acatalog-maintainers) Scope: The catalog plugin and catalog model @@ -45,9 +47,23 @@ Scope: The catalog plugin and catalog model | Patrik Oldsberg | Spotify | Cubic Belugas | [Rugvip](https://github.com/Rugvip) | `Rugvip#0019` | | Vincenzo Scamporlino | Spotify | Cubic Belugas | [vinzscam](http://github.com/vinzscam) | `vinzscam#6944` | +### Community Plugins + +Team: @backstage/community-plugins-maintainers + +Scope: Tooling and Community Repo Maintainers for the Backstage [Community Plugins repository](https://github.com/backstage/community-plugins) + +| Name | Organization | GitHub | Discord | +| -------------------- | ------------- | ------------------------------------------- | ------------ | +| André Wanlin | Spotify | [awanlin](https://github.com/awanlin) | `ahhhndre` | +| Bethany Griggs | Red Hat | [BethGriggs](https://github.com/BethGriggs) | `bethgriggs` | +| Kashish Mittal | Red Hat | [04kash](https://github.com/04kash) | `kashh._.` | +| Vincenzo Scamporlino | Spotify | [vinzscam](https://github.com/vinzscam) | `vinzscam` | +| Peter Macdonald | VodafoneZiggo | [Parsifal-M](https://github.com/Parsifal-M) | `parsifal` | + ### Design System -Team: @backstage/design-system-maintainers +Team: @backstage/design-system-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Adesign-system-maintainers) Scope: The Backstage design system, component library, as well as surrounding tooling such as Storybook. @@ -55,22 +71,23 @@ Scope: The Backstage design system, component library, as well as surrounding to | ------------------- | ------------ | ------------- | --------------------------------------------- | ------------- | | Charles de Dreuille | Spotify | | [cdedreuille](https://github.com/cdedreuille) | `cdedreuille` | | Patrik Oldsberg | Spotify | Cubic Belugas | [Rugvip](https://github.com/Rugvip) | `Rugvip` | +| Johan Persson | Spotify | | [Naycon](https://github.com/Naycon) | `naycon` | ### Documentation -Team: @backstage/documentation-maintainers +Team: @backstage/documentation-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Adocumentation-maintainers) Scope: The Backstage Documentation and Microsite, excluding the plugins listing | Name | Organization | GitHub | Discord | | --------------- | ------------- | ----------------------------------------------------- | ------------- | -| Andre Wanlin | Spotify | [awanlin](https://github.com/awanlin) | `ahhhndre` | +| André Wanlin | Spotify | [awanlin](https://github.com/awanlin) | `ahhhndre` | | Aramis Sennyey | DoorDash | [aramissennyeydd](https://github.com/aramissennyeydd) | `Aramis#7984` | | Peter Macdonald | VodafoneZiggo | [Parsifal-M](https://github.com/Parsifal-M) | `parsifal` | ### Framework -Team: @backstage/framework-maintainers +Team: @backstage/framework-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Aframework-maintainers) Scope: The Backstage core framework, including all revisions of the frontend and backend systems, as well as the App plugin. @@ -97,7 +114,7 @@ Scope: The Backstage [Helm Chart(s)](https://github.com/backstage/charts). ### Home -Team: @backstage/home-maintainers +Team: @backstage/home-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Ahome-maintainers) Scope: The Backstage home page and information architecture @@ -112,7 +129,7 @@ Scope: The Backstage home page and information architecture ### Kubernetes -Team: @backstage/kubernetes-maintainers +Team: @backstage/kubernetes-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Akubernetes-maintainers) Scope: The Kubernetes plugin and the base it provides for other plugins to build upon. @@ -122,7 +139,7 @@ Scope: The Kubernetes plugin and the base it provides for other plugins to build ### Operations -Team: @backstage/operations-maintainers +Team: @backstage/operations-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Aoperations-maintainers) Scope: The management and operation of the main Backstage repository and release process, along with the surrounding tooling. @@ -137,7 +154,7 @@ Scope: The management and operation of the main Backstage repository and release ### Permission Framework -Team: @backstage/permission-maintainers +Team: @backstage/permission-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Apermission-maintainers) Scope: The Permission Framework and plugins integrating with the permission framework @@ -152,7 +169,7 @@ Scope: The Permission Framework and plugins integrating with the permission fram ### Search -Team: @backstage/search-maintainers +Team: @backstage/search-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Asearch-maintainers) Scope: The Backstage Search plugin @@ -164,7 +181,7 @@ Scope: The Backstage Search plugin ### TechDocs -Team: @backstage/techdocs-maintainers +Team: @backstage/techdocs-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Atechdocs-maintainers) Scope: The TechDocs plugin and related tooling @@ -178,7 +195,7 @@ Scope: The TechDocs plugin and related tooling ### Tooling -Team: @backstage/tooling-maintainers +Team: @backstage/tooling-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Atooling-maintainers) Scope: All published Backstage CLI tools in the main `backstage` repository that do not belong to other areas, including `@backstage/cli` and `@backstage/repo-tools`. @@ -198,7 +215,7 @@ These incubating project areas have shared ownership with @backstage/maintainers ### Auditor -Team: @backstage/auditor-maintainers +Team: @backstage/auditor-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Aauditor-maintainers) Scope: The auditor core service, along with auditor usage in the main repository @@ -206,23 +223,9 @@ Scope: The auditor core service, along with auditor usage in the main repository | ------------ | ------------ | ----------------------------------------------- | -------------- | | Paul Schultz | Red Hat | [schultzp2020](https://github.com/schultzp2020) | `schultzp2020` | -### Community Plugins - -Team: @backstage/community-plugins-maintainers - -Scope: Tooling and Community Repo Maintainers for the Backstage [Community Plugins repository](https://github.com/backstage/community-plugins) - -| Name | Organization | GitHub | Discord | -| -------------------- | ------------- | ------------------------------------------- | ------------ | -| André Wanlin | Spotify | [awanlin](https://github.com/awanlin) | `ahhhndre` | -| Bethany Griggs | Red Hat | [BethGriggs](https://github.com/BethGriggs) | `bethgriggs` | -| Kashish Mittal | Red Hat | [04kash](https://github.com/04kash) | `kashh._.` | -| Vincenzo Scamporlino | Spotify | [vinzscam](https://github.com/vinzscam) | `vinzscam` | -| Peter Macdonald | VodafoneZiggo | [Parsifal-M](https://github.com/Parsifal-M) | `parsifal` | - ### Events -Team: @backstage/events-maintainers +Team: @backstage/events-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Aevents-maintainers) Scope: The Events plugin and library, along with events modules in the main repository @@ -232,7 +235,7 @@ Scope: The Events plugin and library, along with events modules in the main repo ### Notifications -Team: @backstage/notifications-maintainers +Team: @backstage/notifications-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Anotifications-maintainers) Scope: The Notifications and Signals plugins and libraries @@ -242,7 +245,7 @@ Scope: The Notifications and Signals plugins and libraries ### OpenAPI Tooling -Team: @backstage/openapi-tooling-maintainers +Team: @backstage/openapi-tooling-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Aopenapi-tooling-maintainers) Scope: Tooling for frontend and backend schema-first OpenAPI development. @@ -252,7 +255,7 @@ Scope: Tooling for frontend and backend schema-first OpenAPI development. ### Scaffolder -Team: @backstage/scaffolder-maintainers +Team: @backstage/scaffolder-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Ascaffolder-maintainers) Scope: The Scaffolder frontend and backend plugins, and related tooling. @@ -267,6 +270,7 @@ Scope: The Scaffolder frontend and backend plugins, and related tooling. | Niklas Gustavsson | Spotify | [protocol7](https://github.com/protocol7) | | | Dave Zolotusky | Spotify | [dzolotusky](https://github.com/dzolotusky) | | | Pia Nilsson | Spotify | [pianilsson](https://github.com/pianilsson) | | +| Stefan Särne | Spotify | [ssarne](https://github.com/ssarne) | | ## Organization Members @@ -274,13 +278,13 @@ Scope: The Scaffolder frontend and backend plugins, and related tooling. | ------------------------------ | ------------------------- | ----------------------------------------------------- | ------------------------------ | | Adam Harvey | Okta | [adamdmharvey](https://github.com/adamdmharvey) | `adamharvey_` | | Alex Crome | | [afscrome](https://github.com/afscrome) | `afscrome` | -| Andre Wanlin | Spotify | [awanlin](https://github.com/awanlin) | `ahhhndre` | | Andrew Thauer | Wealthsimple | [andrewthauer](https://github.com/andrewthauer) | `andrewthauer#3060` | -| Aramis Sennyey | DoorDash | [aramissennyeydd](https://github.com/aramissennyeydd) | `Aramis#7984` | | Brian Fletcher | Roadie.io | [punkle](https://github.com/punkle) | `Brian Fletcher#7051` | | Carlos Esteban Lopez Jaramillo | VMWare | [luchillo17](https://github.com/luchillo17) | `luchillo17#8777` | | David Tuite | Roadie.io | [dtuite](https://github.com/dtuite) | `David Tuite (roadie.io)#1010` | | Deepankumar Loganathan | | [deepan10](https://github.com/deepan10) | `deepan10` | +| Elaine Mattos | DB Systel | [elaine-mattos](https://github.com/elaine-mattos) | `elaine_mattos` | +| Gabriel Dugny | Believe | [GabDug](https://github.com/GabDug) | `GabDug` | | Heikki Hellgren | OP Financial Group | [drodil](https://github.com/drodil) | `deathammer` | | Himanshu Mishra | Harness.io | [OrkoHunter](https://github.com/OrkoHunter) | `OrkoHunter#1520` | | Irma Solakovic | Roadie.io | [Irma12](https://github.com/Irma12) | `Irma#7629` | diff --git a/README-fr_FR.md b/README-fr_FR.md index a54e9c8c49..50646617c4 100644 --- a/README-fr_FR.md +++ b/README-fr_FR.md @@ -10,7 +10,6 @@ Français \| [English](README.md) \| [한국어](README-ko_kr.md) \| [中文版] ![Code style](https://img.shields.io/badge/code_style-prettier-ff69b4.svg) [![Codecov](https://img.shields.io/codecov/c/github/backstage/backstage)](https://codecov.io/gh/backstage/backstage) [![](https://img.shields.io/github/v/release/backstage/backstage)](https://github.com/backstage/backstage/releases) -[![Uffizzi](https://img.shields.io/endpoint?url=https%3A%2F%2Fapp.uffizzi.com%2Fapi%2Fv1%2Fpublic%2Fshields%2Fgithub.com%2Fbackstage%2Fbackstage)](https://app.uffizzi.com/ephemeral-environments/backstage/backstage) [![OpenSSF Best Practices](https://bestpractices.coreinfrastructure.org/projects/7678/badge)](https://bestpractices.coreinfrastructure.org/projects/7678) [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/backstage/backstage/badge)](https://securityscorecards.dev/viewer/?uri=github.com/backstage/backstage) @@ -41,7 +40,7 @@ Pour commencer à utiliser Backstage, consultez [Getting Started documentation]( ## Documentation -La documentation de Backstage inclus: +La documentation de Backstage inclut: - [Main documentation](https://backstage.io/docs) - [Software Catalog](https://backstage.io/docs/features/software-catalog/) @@ -56,7 +55,7 @@ Si vous voulez contribuer et vous impliquer dans notre communauté, voici les re - [Discord chatroom](https://discord.gg/backstage-687207715902193673) - Pour obtenir de l'aide ou discuter du projet - [Contributing to Backstage](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md) - Rendez-vous ici si vous souhaitez contribuer - [RFCs](https://github.com/backstage/backstage/labels/rfc) - Contribuez à définir la direction technique -- [FAQ](https://backstage.io/docs/FAQ) +- [FAQ](https://backstage.io/docs/faq) - [Code of Conduct](CODE_OF_CONDUCT.md) - C'est comme ça qu'on fonctionne - [Adopters](ADOPTERS.md) - Les entreprises utilisant déjà Backstage - [Blog](https://backstage.io/blog/) - Annonces et mises à jour @@ -66,7 +65,7 @@ Si vous voulez contribuer et vous impliquer dans notre communauté, voici les re ## Licence -Copyright 2020-2025 © Les auteurs de Backstage. Tous droits réservés. La Linux Foundation détient des marques déposées et utilise des marques commerciales. Pour une liste des marques de commerce de la Linux Foundation, veuillez consulter notre page d'utilisation des marques: https://www.linuxfoundation.org/trademark-usage +Copyright 2020-2026 © Les auteurs de Backstage. Tous droits réservés. La Linux Foundation détient des marques déposées et utilise des marques commerciales. Pour une liste des marques de commerce de la Linux Foundation, veuillez consulter notre page d'utilisation des marques: https://www.linuxfoundation.org/trademark-usage Sous licence Apache, version 2.0: http://www.apache.org/licenses/LICENSE-2.0 diff --git a/README-ko_kr.md b/README-ko_kr.md index e43aa60c43..2b063a39e7 100644 --- a/README-ko_kr.md +++ b/README-ko_kr.md @@ -2,7 +2,7 @@ # [Backstage](https://backstage.io) -[English](README.md) \| 한국어 +한국어 \| [English](README.md) \| [中文版](README-zh_Hans.md) \| [Français](README-fr_FR.md) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![CNCF Status](https://img.shields.io/badge/cncf%20status-incubation-blue.svg)](https://www.cncf.io/projects) @@ -65,7 +65,7 @@ Backstage의 문서는 다음을 포함합니다: ## License -Copyright 2020-2025 © The Backstage Authors. All rights reserved. The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, please see our Trademark Usage page: https://www.linuxfoundation.org/trademark-usage +Copyright 2020-2026 © The Backstage Authors. All rights reserved. The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, please see our Trademark Usage page: https://www.linuxfoundation.org/trademark-usage Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 diff --git a/README-zh_Hans.md b/README-zh_Hans.md index 4661564725..cb8a4bc38f 100644 --- a/README-zh_Hans.md +++ b/README-zh_Hans.md @@ -2,7 +2,7 @@ # [Backstage](https://backstage.io) -简体中文\| [English](README.md) \| [한국어](README-ko_kr.md) +简体中文 \| [English](README.md) \| [한국어](README-ko_kr.md) \| [Français](README-fr_FR.md) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![CNCF Status](https://img.shields.io/badge/cncf%20status-incubation-blue.svg)](https://www.cncf.io/projects) @@ -55,7 +55,7 @@ Backstage 的文档包括: - [Discord 聊天室](https://discord.gg/backstage-687207715902193673) - 获得支持或讨论项目 - [参与贡献 Backstage](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md) - 如果您想做出贡献,请从这里开始 - [RFCs](https://github.com/backstage/backstage/labels/rfc) - 帮助制定技术方向 -- [FAQ](https://backstage.io/docs/FAQ) - 常问问题 +- [FAQ](https://backstage.io/docs/faq) - 常问问题 - [行为准则](CODE_OF_CONDUCT.md) - 这是我们的行事方式 - [采纳者](ADOPTERS.md) - 已经在使用 Backstage 的公司 - [博客](https://backstage.io/blog/) - 公告和更新 @@ -65,7 +65,7 @@ Backstage 的文档包括: ## 许可 -版权所有 2020-2025 © Backstage 作者。版权所有。Linux 基金会已注册商标并使用商标。有关 Linux 基金会的商标列表,请参阅我们的商标使用页面:https://www.linuxfoundation.org/trademark-usage +版权所有 2020-2026 © Backstage 作者。版权所有。Linux 基金会已注册商标并使用商标。有关 Linux 基金会的商标列表,请参阅我们的商标使用页面:https://www.linuxfoundation.org/trademark-usage 采用 Apache v2.0 许可:http://www.apache.org/licenses/LICENSE-2.0 diff --git a/README.md b/README.md index 545874adf9..68931a3fde 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,3 @@ -> [!NOTE] -> ✈️ From Monday November 10th through November 14rd, several maintainers and Spotify employees will be at KubeCon / BackstageCon! Expect the project to move a little slower than normal, and support to be limited. Normal service will resume after that! And do come visit our booth if you are there. ✈️ - [![headline](docs/assets/headline.png)](https://backstage.io/) # [Backstage](https://backstage.io) @@ -72,7 +69,7 @@ See the [GOVERNANCE.md](https://github.com/backstage/community/blob/main/GOVERNA ## License -Copyright 2020-2025 © The Backstage Authors. All rights reserved. The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, please see our Trademark Usage page: https://www.linuxfoundation.org/trademark-usage +Copyright 2020-2026 © The Backstage Authors. All rights reserved. The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, please see our Trademark Usage page: https://www.linuxfoundation.org/trademark-usage Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 diff --git a/REVIEWING.md b/REVIEWING.md index 66af880f43..803cf35682 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -2,6 +2,85 @@ This file provides pointers for reviewing pull requests. While the main audience are reviewers, this can also be useful if you are contributing to this repository. +## Quick Links + +- [Incoming Reviews](https://github.com/orgs/backstage/projects/14/views/1) +- [Personal Reviews](https://github.com/orgs/backstage/projects/14/views/2) +- Project area boards in [OWNERS.md](./OWNERS.md) + +## Review Workflow + +Pull request reviews are coordinated by maintainers and reviewers based on project area ownership. Reviews are prioritized and tracked using automated labels and a GitHub Project board. This review process applies to all pull requests in the [main Backstage repository](https://github.com/backstage/backstage), other repositories may have their own review processes. + +All incoming pull request reviews are tracked on the [Incoming Reviews board](https://github.com/orgs/backstage/projects/14/views/1). This board can be used by members of the `@backstage/reviewers` group to find pull requests to review, as well as maintainers that want an overview of all incoming pull requests. + +Project area maintainers can use the same board but with additional filters applied to only show incoming pull requests for their project area. These filtered boards are linked to for each project area in [OWNERS.md](./OWNERS.md). There is also a [personal review board](https://github.com/orgs/backstage/projects/14/views/2) that can be used to track reviews that you have been assigned to. + +### Submitting Reviews + +When reviewing a pull request, always submit a formal review using either **"Approve"** or **"Request changes"**. Do not use "Comment" as a substitute for either of these — the review automation relies on the review state to manage labels and the review board, and only formal approvals and change requests are tracked. + +- Use **"Approve"** when you are satisfied with the changes and believe they are ready to be merged (for your area, at least). +- Use **"Request changes"** when changes are necessary before the pull request can be merged. This signals clearly to the author what is expected and moves the pull request out of the review queue until the author responds. +- Use **"Comment"** only to contribute to discussion, ask questions, or leave optional or minor suggestions that do not block the pull request. + +Being explicit with "Approve" or "Request changes" is important both for transparent communication with the author and for the automation to function correctly. The automation reads these review states to update labels and the review board accordingly, meaning a "Comment" review will not cause any status change. + +### Closing Pull Requests + +Pull requests that have been superseded, are no longer relevant, or otherwise are not worth moving forward should be closed with a comment explaining why. Closed pull requests are automatically removed from the review board by the automation. + +### Review Process for @backstage/reviewers + +Members of the `@backstage/reviewers` do not have any specific areas that they should focus on. They can choose to filter and focus on reviews from any part of the project. They do not assign themselves specific pull requests, but instead leave reviews directly on pull requests without any further process. + +Reviews from this group still have meaningful impact on the review process, as they are picked up by the automation. Approving reviews from a member of this group will add the `reviewer-approved` label to the pull request, which greatly increases its priority and visibility for project area maintainers. Likewise, requesting changes will add the `waiting-for:author` label to the pull request, removing it from the review queue until the author has commented or pushed new changes. + +As a reward for helping out with reviews, members who actively review pull requests will have their own pull requests prioritized higher on the review board. The more reviews you contribute, the higher the priority boost your own pull requests receive. + +### Review Process for Project Area Maintainers + +Project area maintainers are responsible for reviewing and ultimately merging or closing pull requests that target their project area. They should use the global or filtered board for their project area to find pull requests to review. When they find a pull request that they want to review, they should assign themselves to the pull request, removing it from the review queue and placing it on their personal review board. + +Once a pull request has been assigned a single owner, it is their responsibility to review and eventually merge or close the pull request. They manage all ongoing requests on their personal review board, typically prioritizing the ones at the top of the board marked with `waiting-for:review`. If a pull request remains assigned but unreviewed for 14 days, the automation will automatically unassign the reviewer and return the pull request to the review queue. + +### Merging Pull Requests + +When a pull request has been approved, the way it gets merged depends on who the author is: + +- **Outside contributions:** Project area maintainers and core maintainers should merge the pull request themselves once approved, since outside contributors do not have merge access. +- **Maintainer contributions:** Within maintainer teams, it is common to let the author choose when to merge the pull request themselves, unless there is a reason to merge it immediately. The `waiting-for:merge` label will be applied automatically when the pull request is approved. + +### Cross-Area Pull Requests + +Some pull requests may require review from multiple project areas. In these cases the most relevant owner should assign themselves and coordinate with the other owners for additional reviews. If the most relevant owner is not clear, this is preferably solved in a discussion among the owners. Frequent conflicts should lead to a discussion whether `CODEOWNERS` should be updated to simplify the review process. If some owners are currently unavailable, other owners can assign themselves to the pull request and bring it to the point where they approve the changes for their area, and then send the pull request back to the review queue. + +## Pull Request Automation + +Pull request labels and the [review board](https://github.com/orgs/backstage/projects/14) are fully managed by automation. All fields on the review board are computed automatically and **should never be updated manually**. Likewise, pull request labels that are part of the automation should not be added or removed by hand, with one exception described below. + +The automation is triggered by pull request events (opened, updated, reopened, labeled, unlabeled), pull request reviews (submitted, dismissed), issue comments, and CI workflow completions. The source code is available in the [`backstage/actions`](https://github.com/backstage/actions) repository, with the workflow definitions in [`.github/workflows/sync_pull-requests-trigger.yml`](.github/workflows/sync_pull-requests-trigger.yml) and [`.github/workflows/sync_pull-requests.yml`](.github/workflows/sync_pull-requests.yml). + +### Labels + +The `waiting-for:*` status labels, `reviewer-approved` label, and `size:*` labels are all **automated** based on pull request reviews, author activity, and pull request size. See [LABELS.md](./LABELS.md#pull-request-labels) for the meaning of each label. + +The only exception is `waiting-for:decision`, which can and should be set **manually**. Use it when discussion or a decision is needed before the pull request can move forward, for example when the approach needs to be discussed in a [SIG](https://github.com/backstage/community/tree/main/sigs) meeting. Once set, this label will **stick until manually removed** — the automation will not override it. Only remove it once a decision has been made and the pull request is ready to proceed. + +### Review Board Priority + +The priority field on the review board is a number calculated automatically by the automation. It determines the sort order of pull requests on the board. The priority is influenced by several factors: + +- **Pull request size** — Smaller pull requests receive higher priority, where the priority reduces significantly for roughly every 500 lines added. +- **Reviewer approval** — Pull requests with the `reviewer-approved` label receive a large priority boost. +- **Author review contributions** — Authors who have themselves reviewed other pull requests in the repository receive a priority boost proportional to their review activity. +- **Draft status** — Draft pull requests receive significantly reduced priority. +- **CI status** — Pull requests where required CI checks are failing or still pending receive reduced priority. The required checks include DCO, tests, verification, and E2E tests. + +### Stale Review Handling + +If a pull request with the `waiting-for:review` label has been assigned to a reviewer but has not received a review for 14 days, the automation will automatically unassign the reviewer and return the pull request to the review queue. This ensures that pull requests do not get stuck due to reviewer unavailability. + ## Code Style See our code style documented at [STYLE.md](./STYLE.md). @@ -56,11 +135,42 @@ Each changeset should be written in a way that describes the impact of the chang Some things that changeset should NOT contain are: -- Internal architecture details - these are generally not interesting to users, focus on the impact towards users of the package instead. +- Internal implementation details such as internal function names, class names, variable names, or other code symbols that are not part of the public API. Changeset messages should describe user-facing behavior changes in plain language. Focus on the impact towards users of the package, not on how the code is structured internally. - Information related to a different package. - A large amount of content, consider for example a separate migration guide instead, either in the package README or [./docs/](./docs/), and then link to that instead. - Documentation - changesets can describe new features, but it should not be relied on for documenting them. Documentation should either be placed in [TSDoc](https://tsdoc.org) comments, package README, or [./docs/](./docs/). -- Diffs of internal code, for example mirroring what the pull request changes _inside_ a plugin rather than public surfaces. This is not of interest to the reader of a package changelog. Sometimes, however, a small and concise diff can be used in a changeset to illustrate changes that the user will have to make in _their own_ Backstage installation as part of an upgrade, specifically when breaking changes are made to a package. +- Diffs of internal code, for example mirroring what the pull request changes _inside_ a plugin rather than public surfaces. This is not of interest to the reader of a package changelog, and internal function names, class names, or variable names should never appear in changeset messages. Sometimes, however, a small and concise diff can be used in a changeset to illustrate changes that the user will have to make in _their own_ Backstage installation as part of an upgrade, specifically when breaking changes are made to a package. + +### Backstage UI Changeset Format + +Changesets for `@backstage/ui` must follow a standardized format to enable proper documentation generation. See [`.changeset/README.md`](.changeset/README.md#backstage-ui-changesets) for the complete guide. + +**Required structure:** + +```markdown +--- +'@backstage/ui': patch +--- + +Brief one-line summary of the change + +Optional detailed description with any markdown content. + +**Migration:** + +Migration instructions (only for breaking changes) + +**Affected components:** Button, ButtonIcon +``` + +**Key requirements:** + +1. **Affected components marker** - Must end with `**Affected components:**` followed by comma-separated component names +2. **Migration marker** - Use `**Migration:**` (bold, with colon) for breaking changes only +3. **No headings** - Never use `##`, `###`, or `####` inside changeset entries (these break semantic structure when the entry becomes a list item in CHANGELOG.md) +4. **Bold markers** - Use bold text markers, not plain text, for reliable parsing + +These markers enable the documentation system to extract metadata for per-component changelogs and separate migration guides. ### When is a changeset needed? diff --git a/SECURITY.md b/SECURITY.md index 5c6c8106d5..46f36f0a67 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -8,7 +8,7 @@ See our [Release & Versioning Policy](https://backstage.io/docs/overview/version Please report sensitive security issues via Spotify's [bug-bounty program](https://hackerone.com/spotify) rather than GitHub. -If you have questions about a potential vulnerability, please reach out on Discord by asking for a maintainer in the `#support` channel, or via direct message to a maintainer. +If you have questions about a potential vulnerability, please reach out on Discord by asking for a maintainer in the `#security` channel, or via direct message to a maintainer. ## Remediation and Notification Process diff --git a/app-config.yaml b/app-config.yaml index b45255d26c..86391154f3 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -27,6 +27,128 @@ app: packageName: example-app + routes: + bindings: + catalog.viewTechDoc: techdocs.docRoot + org.catalogIndex: catalog.catalogIndex + + pluginOverrides: + - match: + pluginId: pages + info: + description: 'This description was overridden in app-config.yaml' + - match: + pluginId: /^catalog(-.*)?$/ + info: + ownerEntityRefs: [cubic-belugas] + - match: + packageName: '@backstage/plugin-scaffolder' + info: + ownerEntityRefs: [cubic-belugas] + + extensions: + # set availableLanguages example + - api:app/app-language: + config: + availableLanguages: ['en', 'es', 'fr', 'de', 'ja'] + defaultLanguage: 'en' + - entity-card:org/members-list: + config: + showAggregateMembersToggle: true + initialRelationAggregation: aggregated + - entity-card:org/ownership: + config: + ownedKinds: ['Component', 'API', 'System'] + + # - apis.plugin.graphiql.browse.gitlab: true + # - graphiql-endpoint:graphiql/gitlab: true + + - nav-item:search: false + - nav-item:user-settings: false + - nav-item:catalog + - nav-item:api-docs + - nav-item:scaffolder + - nav-item:app-visualizer + + # Pages + - page:catalog/entity: + config: + showNavItemIcons: true + # default content order for all groups, can be 'title' or 'natural' + # defaultContentOrder: title + groups: + # placing a tab at the beginning + - overview: + title: Overview + # example disabling a default group + # - development: false + # example overriding a default group title + - documentation: + title: Docs + icon: docs + # example aliasing a group + # aliases: + # - docs + - deployment: + title: Deployments + # example adding a new group + - custom: + title: Custom + + # Entity page cards + - entity-card:catalog/about: + config: + type: info + - entity-card:catalog/labels + - entity-card:catalog/links: + config: + filter: + kind: component + metadata.links: + $exists: true + # filter: kind:component has:links + type: info + # - entity-card:linguist/languages + - entity-card:catalog-graph/relations: + config: + height: 300 + - entity-card:api-docs/has-apis + - entity-card:api-docs/consumed-apis + - entity-card:api-docs/provided-apis + - entity-card:api-docs/providing-components + - entity-card:api-docs/consuming-components + # Org Plugin + - entity-card:org/group-profile + - entity-card:org/members-list + - entity-card:org/ownership + - entity-card:org/user-profile: + config: + maxRelations: 5 + hideIcons: true + # - entity-card:azure-devops/readme + + # Entity page contents + - entity-content:catalog/overview + - entity-content:api-docs/definition + - entity-content:api-docs/apis: + config: + # example overriding the default group + group: documentation + icon: kind:api + - entity-content:techdocs: + config: + icon: techdocs + - entity-content:kubernetes/kubernetes: + config: + # example disassociating from the default group + group: false + # - entity-content:azure-devops/pipelines + # - entity-content:azure-devops/pull-requests + # - entity-content:azure-devops/git-tags + + # Disable the catalog-unprocessed-entities element outside devtools + - page:catalog-unprocessed-entities: false + backend: # Used for enabling authentication, secret is shared by all backend plugins # See https://backstage.io/docs/auth/service-to-service-auth for @@ -68,6 +190,7 @@ backend: actions: pluginSources: - catalog + - scaffolder # See README.md in the proxy-backend plugin for information on the configuration format proxy: endpoints: @@ -109,8 +232,12 @@ integrations: token: ${GITLAB_TOKEN} ### Example for how to add a bitbucket cloud integration # bitbucketCloud: + # # Using API token # - username: ${BITBUCKET_USERNAME} - # appPassword: ${BITBUCKET_APP_PASSWORD} + # token: ${BITBUCKET_API_TOKEN} + # # Using OAuth + # - clientId: ${BITBUCKET_CLIENT_ID} + # clientSecret: ${BITBUCKET_CLIENT_SECRET} ### Example for how to add your bitbucket server instance using the API: # - host: server.bitbucket.com # apiBaseUrl: server.bitbucket.com @@ -185,15 +312,9 @@ catalog: target: ../catalog-model/examples/acme-corp.yaml rules: - allow: [User, Group] - # Example component for github-actions and TechDocs - - type: file - target: ../../plugins/github-actions/examples/sample.yaml # Example component for TechDocs - type: file target: ../../plugins/techdocs-backend/examples/documented-component/catalog-info.yaml - # Example component for ADRs - - type: file - target: ../../plugins/adr/examples/component/catalog-info.yaml # Backstage example templates - type: file target: ../../plugins/scaffolder-backend/sample-templates/all-templates.yaml @@ -209,12 +330,24 @@ scaffolder: email: scaffolder@backstage.io # Use to customize the default commit message when new components are created defaultCommitMessage: 'Initial commit' + defaultEnvironment: + parameters: + region: eu-west-1 + secrets: + environment: ${NODE_ENV} auth: experimentalDynamicClientRegistration: enabled: true allowedRedirectUriPatterns: - cursor://* + - http://localhost:* + - http://127.0.0.1:* + experimentalClientIdMetadataDocuments: + enabled: true + allowedRedirectUriPatterns: + - http://127.0.0.1:* + - http://localhost:* ### Add auth.keyStore.provider to more granularly control how to store JWK data when running # the auth-backend. @@ -306,3 +439,8 @@ auth: permission: enabled: true + +devTools: + scheduledTasks: + plugins: + - catalog diff --git a/beps/0001-notifications-system/notifications-architecture.drawio.svg b/beps/0001-notifications-system/notifications-architecture.drawio.svg index 9c6bb00119..25b27642ca 100644 --- a/beps/0001-notifications-system/notifications-architecture.drawio.svg +++ b/beps/0001-notifications-system/notifications-architecture.drawio.svg @@ -1,537 +1 @@ - - - - - - - - - - -
-
-
- Notification Backend -
-
-
-
- - Notification Backend - -
-
- - - - - - - - -
-
-
- Notification Frontend -
-
-
-
- - Notification Frontend - -
-
- - - - -
-
-
- Signal Backend -
-
-
-
- - Signal Backend - -
-
- - - - - - - - - - -
-
-
- Signal Frontend -
-
-
-
- - Signal Frontend - -
-
- - - - - - -
-
-
- Plugin X Backend -
-
-
-
- - Plugin X Backend - -
-
- - - - -
-
-
- Plugin X Frontend -
-
-
-
- - Plugin X Frontend - -
-
- - - - - - - - - - - - -
-
-
- EventsService -
-
-
-
- - EventsService - -
-
- - - - - - -
-
-
- ... -
-
-
-
- - ... - -
-
- - - - -
-
-
- ... -
-
-
-
- - ... - -
-
- - - - -
-
-
- publish event -
-
-
-
- - publish event - -
-
- - - - -
-
-
- broadcast -
-
-
-
- - broadcast - -
-
- - - - -
-
-
- ??? -
-
-
-
- - ??? - -
-
- - - - -
-
-
- server push -
-
-
-
- - server push - -
-
- - - - -
-
-
- POST -
-
-
-
- - POST - -
-
- - - - - - -
-
-
- External System -
-
-
-
- - External System - -
-
- - - - -
-
-
- email/slack/teams, etc. -
-
-
-
- - email/slack/teams, etc. - -
-
- - - - -
-
-
- Render -
-
-
-
- - Render - -
-
- - - - -
-
-
- - Processor - -
-
-
-
- - Processor - -
-
- - - - -
-
-
- GET -
-
-
-
- - GET - -
-
- - - - -
-
-
- 1 -
-
-
-
- - 1 - -
-
- - - - -
-
-
- 3 -
-
-
-
- - 3 - -
-
- - - - -
-
-
- 1b -
-
-
-
- - 1b - -
-
- - - - -
-
-
- 4 -
-
-
-
- - 4 - -
-
- - - - -
-
-
- 5 -
-
-
-
- - 5 - -
-
- - - - -
-
-
- 6 -
-
-
-
- - 6 - -
-
- - - - -
-
-
- 7b -
-
-
-
- - 7b - -
-
- - - - -
-
-
- 7 -
-
-
-
- - 7 - -
-
- - - - - - -
-
-
- 2 -
-
-
-
- - 2 - -
-
- - - - -
-
-
- Channel Callback -
-
-
-
- - Channel Callback - -
-
-
- - - - - Text is not SVG - cannot display - - - -
+
Notification Backend
Notification Backend
Notification Frontend
Notification Frontend
Signal Backend
Signal Backend
Signal Frontend
Signal Frontend
Plugin X Backend
Plugin X Backend
Plugin X Frontend
Plugin X Frontend
EventsService
EventsService
...
...
...
...
publish event
publish event
broadcast
broadcast
???
???
server push
server push
POST
POST
External System
External System
email/slack/teams, etc.
email/slack/teams, etc.
Render
Render
Processor
Processor
GET
GET
1
1
3
3
1b
1b
4
4
5
5
6
6
7b
7b
7
7
2
2
Channel Callback
Channel Callback
Text is not SVG - cannot display
\ No newline at end of file diff --git a/beps/0002-dynamic-frontend-plugins/README.md b/beps/0002-dynamic-frontend-plugins/README.md index 4787f0fb7a..583da8b174 100644 --- a/beps/0002-dynamic-frontend-plugins/README.md +++ b/beps/0002-dynamic-frontend-plugins/README.md @@ -301,7 +301,7 @@ An example of [integration with scalprum](https://github.com/backstage/backstage **Dynamic Feature configuration** -The dynamic remote loading can be added directly into the [`createApp`](https://backstage.io/docs/reference/frontend-defaults.createapp) function. +The dynamic remote loading can be added directly into the [`createApp`](https://backstage.io/api/stable/functions/_backstage_frontend-defaults.createApp.html) function. The current `feature` type can be expanded with a `DynamicFrontendFeature` type: @@ -351,7 +351,7 @@ const scalprum = initialize({ }); ``` -Because the [`appLoader`](https://backstage.io/docs/reference/frontend-defaults.createapp) is already async, it is a perfect place to load the plugin registry and init the dynamic plugins. +Because the [`appLoader`](https://backstage.io/api/stable/functions/_backstage_frontend-defaults.createApp.html) is already async, it is a perfect place to load the plugin registry and init the dynamic plugins. Initializing the dynamic feature is just a case of mapping the `DynamicFrontendFeature` to `FrontendFeature` via Scalprum: @@ -633,7 +633,7 @@ Chunk optimization should be disabled for the initial implementation. The `publicPath` output config in webpack is a mandatory attribute for federated modules. However, at build time, it is impossible to guess where the assets are served from. From origin to the pathname, this is specific to each installation. -We can leverage the [auto](https://webpack.js.org/guides/public-path/#automatic-publicpath) option. However this means that some manifest transformation has to happen at runtime when entry scripts are loaded into the browser. More on that in the [Plugin manifest](#plugin-manifest), [CDN Plugin](#dynamic-assets-server-plugin), and [Plugin loading](#plugin-loading) sections. +We can leverage the [auto](https://webpack.js.org/guides/public-path/#automatic-publicpath) option. However this means that some manifest transformation has to happen at runtime when entry scripts are loaded into the browser. More on that in the [Plugin manifest](#plugin-manifest), [CDN Plugin](#dynamic-assets-server), and [Plugin loading](#plugin-loading) sections. #### Sample webpack configuration diff --git a/beps/0003-auth-architecture-evolution/README.md b/beps/0003-auth-architecture-evolution/README.md index 33bf80a099..eedd63e850 100644 --- a/beps/0003-auth-architecture-evolution/README.md +++ b/beps/0003-auth-architecture-evolution/README.md @@ -36,7 +36,7 @@ The changes to the service-to-service auth are aimed to be the minimum needed to ## Motivation -This proposal aims to address several of the points in the [Auth Meta issue](https://github.com/backstage/backstage/issues/15999), with the overarching goal being to replace the existing [API request authentication](https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/authenticate-api-requests.md) tutorial in `contrib/` with a more robust and secure built-in solution. The tutorial exists for two purposes: to add authentication of API requests as part of using the permission system in Backstage, and to protect a Backstage instance from external access. It does a fairly good job of the former, although we want to avoid placing user tokens in cookies, but it does a quite poor job of the latter, which we want to fix. +This proposal aims to address several of the points in the [Auth Meta issue](https://github.com/backstage/backstage/issues/15999), with the overarching goal being to replace the existing [API request authentication](https://github.com/backstage/backstage/blob/a93b7fdafb6789277661ba77b8cc3390de82db27/contrib/docs/tutorials/authenticate-api-requests.md) tutorial in `contrib/` with a more robust and secure built-in solution. The tutorial exists for two purposes: to add authentication of API requests as part of using the permission system in Backstage, and to protect a Backstage instance from external access. It does a fairly good job of the former, although we want to avoid placing user tokens in cookies, but it does a quite poor job of the latter, which we want to fix. A secondary goal is to do this work before stabilizing the APIs in the new Backend system, as it will have some impact on how plugin backends are built. This will inevitably also lead to the need to improve the way that service-to-service auth is handled in Backstage, although that is not the primary goal of this work. diff --git a/beps/0003-auth-architecture-evolution/token-sequence-cookie.drawio.svg b/beps/0003-auth-architecture-evolution/token-sequence-cookie.drawio.svg index b20b528c35..29d95b44bf 100644 --- a/beps/0003-auth-architecture-evolution/token-sequence-cookie.drawio.svg +++ b/beps/0003-auth-architecture-evolution/token-sequence-cookie.drawio.svg @@ -1,203 +1 @@ - - - - - - - - -
-
-
- cookie flow -
-
-
-
- - cookie flow - -
-
- - - - - -
-
-
- Browser -
-
-
-
- - Browser - -
-
- - - - - - -
-
-
- Techdocs -
- Backend -
-
-
-
- - Techdocs... - -
-
- - - - - - -
-
-
- GET /cookie -
-
-
-
- - GET /cookie - -
-
- - - - - -
-
-
- set cookie on response -
-
-
-
- - set cookie on response - -
-
- - - - - -
-
-
- browser static content requests -
-
-
-
- - browser static content requests - -
-
- - - - -
-
-
- cookie token -
-
-
-
- - cookie token - -
-
- - - - -
-
-
- cookie token -
-
-
-
- - cookie token - -
-
- - - - -
-
-
- user identity token -
-
-
-
- - user identity token - -
-
- - - - - -
-
-
- - acquire cookie token -
- based on user token -
-
-
-
-
-
- - acquire cookie token... - -
-
- - - - -
- - - - - Text is not SVG - cannot display - - - -
+
cookie flow
cookie flow
Browser
Browser
Techdocs
Backend
Techdocs...
GET /cookie
GET /cookie
set cookie on response
set cookie on response
browser static content requests
browser static content requests
cookie token
cookie token
cookie token
cookie token
user identity token
user identity token
acquire cookie token
based on user token
acquire cookie token...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/beps/0003-auth-architecture-evolution/token-sequence-obo.drawio.svg b/beps/0003-auth-architecture-evolution/token-sequence-obo.drawio.svg index f337f8b572..517d2c4d19 100644 --- a/beps/0003-auth-architecture-evolution/token-sequence-obo.drawio.svg +++ b/beps/0003-auth-architecture-evolution/token-sequence-obo.drawio.svg @@ -1,203 +1 @@ - - - - - - - - -
-
-
- Browser -
-
-
-
- - Browser - -
-
- - - - - - -
-
-
- Scaffolder -
- Backend -
-
-
-
- - Scaffolder... - -
-
- - - - - - -
-
-
- - acquire service obo token -
- based on user token -
-
-
-
-
-
- - acquire service obo token... - -
-
- - - - - -
-
-
- POST /tasks -
-
-
-
- - POST /tasks - -
-
- - - - - -
-
-
- success -
-
-
-
- - success - -
-
- - - - - -
-
-
- Catalog -
- Backend -
-
-
-
- - Catalog... - -
-
- - - - - - -
-
-
- GET /entities/... -
-
-
-
- - GET /entities/... - -
-
- - - - - -
-
-
- template entity -
-
-
-
- - template entity - -
-
- - - - -
-
-
- user identity token -
-
-
-
- - user identity token - -
-
- - - - -
-
-
- service obo token -
-
-
-
- - service obo token - -
-
-
- - - - - Text is not SVG - cannot display - - - -
+
Browser
Browser
Scaffolder
Backend
Scaffolder...
acquire service obo token
based on user token
acquire service obo token...
POST /tasks
POST /tasks
success
success
Catalog
Backend
Catalog...
GET /entities/...
GET /entities/...
template entity
template entity
user identity token
user identity token
service obo token
service obo token
Text is not SVG - cannot display
\ No newline at end of file diff --git a/beps/0012-metrics-service/README.md b/beps/0012-metrics-service/README.md index 59096d11d8..87b2059f02 100644 --- a/beps/0012-metrics-service/README.md +++ b/beps/0012-metrics-service/README.md @@ -23,7 +23,6 @@ creation-date: 2025-06-23 - [Integration with OpenTelemetry Auto-Instrumentation](#integration-with-opentelemetry-auto-instrumentation) - [Configuration](#configuration) - [Interface](#interface) - - [Root Metrics Service](#root-metrics-service) - [Plugin Metrics Service](#plugin-metrics-service) - [Example](#example) - [Release Plan](#release-plan) @@ -36,7 +35,7 @@ Add a core `MetricsService` to Backstage's framework to provide a unified interf ## Motivation -While individual plugins may implement their own metrics, there's no standardized approach leading to inconsistent metrics patterns across the ecosystem. For example, both `catalog_entities_count` and `catalog.processed.entities.count` are examples of existing metric patterns. Ideally, these would be standardized to `backstage.plugin.catalog.entities.count` and `backstage.plugin.catalog.entities.processed.total` respectively. +While individual plugins may implement their own metrics, there's no standardized approach leading to inconsistent metrics patterns across the ecosystem and incompatibility with OpenTelemetry semantic conventions. For example, a plugin implementing MCP functionality might incorrectly namespace metrics as `backstage_mcp_client_duration` when OpenTelemetry semantic conventions explicitly define `mcp.client.operation.duration` as the standard. By providing a core metrics service: @@ -45,7 +44,7 @@ By providing a core metrics service: ### Goals -- Plugin-scoped metric namespacing +- Plugin identification via OpenTelemetry Instrumentation Scope - Consistent metrics patterns across all plugins - Aligned with OpenTelemetry industry standards - Provide a familiar interface as other core services @@ -124,9 +123,12 @@ The `MetricsService` **complements** rather than duplicates auto-instrumentation // MetricsService provides (manually): const entityMetrics = metricsService.createCounter('entities.processed.total'); -entityMetrics.add(entities.length, { operation: 'refresh', kind: 'Component' }); +entityMetrics.add(entities.length, { + operation: 'refresh', + 'entity.kind': 'Component', +}); -// Metric is now available as `backstage.plugin.catalog.entities.processed.total` +// Metric is now available as `entities.processed.total` ``` ### Configuration @@ -162,43 +164,21 @@ interface MetricsService { } ``` -#### Root Metrics Service - -The `RootMetricsService` is responsible for providing metrics to other root services and creating both plugin-scoped and core-scoped `MetricsService` instances. - -```ts -interface RootMetricsService { - // note: no config is provided to the root service. - static forRoot(): RootMetricsService; - forPlugin(pluginId: string): MetricsService; - - // final implementation will be similar to - forService(serviceName: string, scope: 'plugin' | 'core'): MetricsService; -} - -export const rootMetricsServiceFactory = createServiceFactory({ - // depends on as little as possible so that it can be initialized as early as possible. - service: rootMetricsServiceRef, - deps: {}, - factory: () => { - return DefaultRootMetricsService.forRoot(); - }, -}); -``` - #### Plugin Metrics Service -Each plugin receives a metrics service that automatically namespaces all metrics to match the naming conventions. +Each plugin receives a metrics service that automatically configures the Instrumentation Scope to identify the plugin. The scope name follows the pattern `backstage-plugin-{pluginId}`. ```ts -const metricsServiceFactory = createServiceFactory({ - service: metricsServiceRef, +export const metricsServiceFactory = createServiceFactory({ + service: coreServices.metrics, deps: { - rootMetrics: coreServices.rootMetrics, pluginMetadata: coreServices.pluginMetadata, }, - factory: ({ rootMetrics, pluginMetadata }) => { - return rootMetrics.forPlugin(pluginMetadata.getId()); + factory: ({ pluginMetadata }) => { + const pluginId = pluginMetadata.getId(); + const scopeName = `backstage-plugin-${pluginId}`; + + return new DefaultMetricsService(scopeName, version, ...); }, }); ``` @@ -248,3 +228,22 @@ entitiesProcessed.add(100); - Plugin authors continue to implement their own metrics as they see fit. - A combined TelemetryService that provides both metrics and tracing. + +### Rejected: Forced Namespace Prefixes + +Prepend `backstage.plugin.{pluginId}.` to all metric names. This was the original proposal but conflicts with OpenTelemetry semantic conventions. + +**Problems:** + +- Makes it impossible to use standard semantic conventions like `mcp.*`, `gen_ai.*`, `http.*` +- Breaks compatibility with industry-standard observability tooling +- Prevents cross-service metric aggregation +- Goes against OpenTelemetry best practices and official guidance + +**Example of conflict:** + +```ts +// Plugin wants to emit: mcp.client.operation.duration +// Framework forces: backstage.plugin.mcp-actions.mcp.client.operation.duration +// This violates the semantic convention and breaks tooling +``` diff --git a/beps/0013-backstage-ai/README.md b/beps/0013-backstage-ai/README.md new file mode 100644 index 0000000000..84e7e31027 --- /dev/null +++ b/beps/0013-backstage-ai/README.md @@ -0,0 +1,236 @@ +--- +title: Backstage AI Skills Publishing and Discovery +status: implementable +authors: + - '@drodil' +owners: + - '@backstage/maintainers' +project-areas: + - core +creation-date: 2026-03-06 +--- + +# BEP: AI skills for Backstage development + +- [Summary](#summary) +- [Motivation](#motivation) + - [Goals](#goals) + - [Non-Goals](#non-goals) +- [Proposal](#proposal) +- [Design Details](#design-details) +- [Release Plan](#release-plan) +- [Dependencies](#dependencies) +- [Alternatives](#alternatives) + +## Summary + +This BEP proposes that curated Backstage AI skills are authored in the main Backstage +repository and published separately to a well-known endpoint on `backstage.io`. Instead +of creating a new Backstage-owned repository and custom discovery protocol, Backstage +will publish skills using the well-known layout already supported by [`skills.sh`](https://skills.sh/). + +The published output can be released on its own publishing cadence even though the source +content lives in the main monorepo. + +The primary consumer workflow becomes: + +- `npx skills add https://backstage.io` to discover and install all published Backstage skills. +- `npx skills add https://backstage.io/.well-known/skills/` to install a specific skill. + +This keeps Backstage aligned with an existing ecosystem convention, reduces maintenance +surface area, and still gives adopters a clear, official source for reusable AI guidance. +Prompt templates, instructions, and supporting files can still be shipped, but they are +packaged within skill directories instead of through a separate Backstage-specific asset system. + +## Motivation + +Backstage adopters are increasingly using AI-assisted workflows for plugin development, +migration, maintenance, and documentation. Today, prompt and skill quality varies +significantly across adopters, and there is no common, well-known channel for sharing proven +guidance. A lot of adopters already have assets that help AI-assisted development and +could be shared with the community. + +As a result: + +- Teams duplicate effort by recreating similar prompts and instructions. +- Effective practices are hard to discover and spread. +- Shared guidance is often published in tool-specific or organization-specific layouts. +- Security and governance concerns are often handled ad hoc. + +Backstage should solve this in the simplest interoperable way. Creating a dedicated asset +repository, a Backstage-specific discovery index, and custom install/update tooling would +increase maintenance cost while duplicating functionality that `skills.sh` already provides. + +By publishing official Backstage skills through a well-known endpoint on `backstage.io`, +Backstage can provide a reliable baseline for AI-enabled development while preserving local +extension and customization and staying aligned with an existing ecosystem workflow. + +### Goals + +- Author official Backstage AI skills in the main Backstage repository. +- Publish those skills separately to a well-known endpoint on `backstage.io`. +- Reuse `skills.sh` discovery and installation behavior instead of inventing a Backstage-specific protocol. +- Define a stable source layout and review process for Backstage-authored skills. +- Allow skill directories to include prompts, instructions, and supporting files needed by each skill. +- Publish contributor and review guidelines so assets remain high quality and safe. +- Provide clear docs for adopters on how to install and customize the published skills. + +### Non-Goals + +- Building or hosting a new LLM inference platform. +- Standardizing model provider APIs across all Backstage AI integrations. +- Automatically executing AI-generated code in adopter repositories. +- Enforcing centralized telemetry collection from adopter environments. +- Replacing existing organization-specific prompt repositories. Local assets remain supported. +- Solving all possible AI governance policy differences between organizations. +- Creating a separate Backstage-owned repository just for AI skills. +- Building a Backstage-specific discovery index, manifest, or asset registry. +- Supporting bundles, lockfiles, version pinning, or custom update flows in this first iteration. + +## Proposal + +Introduce three deliverables: + +1. A Backstage monorepo location that serves as the source of truth for Backstage-authored AI skills. +1. A publishing workflow that exposes approved skills at `https://backstage.io/.well-known/skills/`. +1. Documentation for consuming those skills through `skills.sh` + +The Backstage source content will contain: + +- Reusable skills for common Backstage engineering tasks. +- Skill definitions, instructions, prompt fragments, and supporting files packaged per skill. +- Lightweight metadata needed for publication and review. +- Validation and quality checks executed in CI. + +The primary consumer workflow will use `skills.sh` directly: + +- `npx skills add https://backstage.io` +- `npx skills add https://backstage.io/.well-known/skills/` + +If Backstage later adds CLI support, it should remain intentionally thin: + +- It should default to the Backstage well-known endpoint. +- It should delegate to `skills.sh` semantics rather than defining its own registry or manifest. +- It should not introduce separate bundle, version, or update concepts. + +This approach keeps discovery and installation interoperable with the broader ecosystem, +while still letting Backstage publish curated guidance from an official source. + +## Design Details + +### Source and publishing design + +The source of truth for Backstage skills will live in the Backstage monorepo. The exact +directory can be finalized during implementation, but it should provide a predictable layout +for authoring, review, and publication. + +Each skill directory should be self-contained and may include: + +- `SKILL.md` as the primary skill entry point. +- Supporting prompt or instruction files referenced by the skill. +- Optional metadata files used by Backstage publishing workflows. + +Published output on `backstage.io` will follow the `skills.sh` well-known layout: + +- `https://backstage.io/.well-known/skills/index.json` +- `https://backstage.io/.well-known/skills//SKILL.md` +- Additional files under `https://backstage.io/.well-known/skills//...` + +The published index should contain entries with the current `skills.sh` well-known shape: + +- `name`: skill identifier, matching the published directory name. +- `description`: short description of the skill. +- `files`: all published files in the skill directory. + +Example published layout: + +```text +.well-known/skills/index.json +.well-known/skills/plugin-authoring/SKILL.md +.well-known/skills/plugin-authoring/prompts/review.md +``` + +Example index structure: + +```json +{ + "skills": [ + { + "name": "plugin-authoring", + "description": "Guidance for creating and reviewing Backstage plugins.", + "files": ["SKILL.md", "prompts/review.md"] + } + ] +} +``` + +Backstage CI should validate the source skill layout, ensure published file references are +correct, and generate the well-known endpoint contents as part of the publishing workflow. + +### Consumer workflow + +The main adoption path is documentation, not new tooling: + +- Users install Backstage skills with `npx skills add https://backstage.io`. +- Users can install a specific published skill by pointing at its well-known skill URL. +- Any install behavior, target path decisions, or local merge semantics remain the responsibility of `skills.sh`. + +### Extensibility + +Adopters can: + +- Keep local-only skills alongside installed Backstage skills. +- Customize installed files after adding them to their repository. +- Publish their own well-known skills endpoints independently of Backstage. + +Backstage should not block these local workflows by requiring a proprietary asset format. + +## Release Plan + +Phase 1: Repository bootstrap + +- Define the Backstage monorepo location and review guidelines for skills. +- Seed the source tree with a small set of high-value Backstage skills. +- Validate contributor workflow and CI checks for source content. + +Phase 2: Publishing rollout + +- Implement a publishing workflow that generates `/.well-known/skills/` content for `backstage.io`. +- Publish `index.json` and the initial skill directories. +- Validate the published endpoint against `skills.sh` consumption behavior. + +Phase 3: General availability + +- Publish adoption documentation centered on `npx skills add https://backstage.io`. +- Define support and ownership model for ongoing skill curation and publishing. +- Evaluate whether an optional thin `backstage-cli` wrapper adds enough value to justify maintenance. + +## Dependencies + +- `skills.sh` continuing to support the current well-known endpoint format. +- A Backstage publishing workflow that can emit static files to `backstage.io/.well-known/skills/`. +- Agreement on long-term ownership and governance model for Backstage-authored skills. +- Documentation updates describing authoring, publishing, and adopter usage patterns. + +## Alternatives + +1. Keep AI assets fully decentralized in individual adopter repositories. + +This preserves autonomy but does not solve discovery, reuse, or consistency. It also increases duplicated maintenance +effort across adopters. + +2. Create a separate Backstage-owned repository for AI assets. + +This was the original direction, but it adds repository sprawl and still requires a distribution mechanism. Keeping the +source in the main monorepo simplifies governance and keeps the content closer to the Backstage code and docs it +describes. + +3. Build a Backstage-specific discovery and installation protocol. + +This would duplicate functionality already available through `skills.sh` and create a long-term maintenance burden for +limited short-term benefit. + +4. Publish assets only as documentation in the Backstage docs site. + +This improves discoverability, but without the well-known `skills.sh` layout it does not provide a direct installation +path for users. diff --git a/contrib/.devcontainer/README.md b/contrib/.devcontainer/README.md deleted file mode 100644 index 4fca39b3ad..0000000000 --- a/contrib/.devcontainer/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Devcontainer Configuration - -[Devcontainer](https://code.visualstudio.com/docs/devcontainers/containers) is a VSCode feature that allows developers -to share a common workspace definition, by leveraging Docker to run the workspace, and using VSCode Remote Development extension, you can make sure the developer experience is the same across different computers - -## Benefits - -- Developers only need to have VSCode and Docker installed -- All dependencies necessary to work in the repository are contained in the docker image -- Makes it easier for new developers to start contributing to the project -- No extra setup required - -## Known Issues - -- Performance is a known issue. Working inside devcontainers require developers to have a powerful computer. See [System Requirements](https://code.visualstudio.com/docs/devcontainers/containers#_system-requirements) - -## Usage - -Just copy the .devcontainer folder to the root of the directory, and VSCode will automatically detect the configuration and suggest that you re-open the repository using Devcontainer diff --git a/contrib/.devcontainer/devcontainer.json b/contrib/.devcontainer/devcontainer.json deleted file mode 100644 index 14e7d7f0ae..0000000000 --- a/contrib/.devcontainer/devcontainer.json +++ /dev/null @@ -1,27 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node -{ - "name": "Base Backstage Workspace", - "build": { "dockerfile": "Dockerfile" }, - "features": { - "ghcr.io/devcontainers/features/common-utils:1": {}, - "ghcr.io/devcontainers/features/docker-from-docker:1": {}, - "ghcr.io/devcontainers-contrib/features/mkdocs:2": {} - }, - - // Use 'forwardPorts' to make a list of ports inside the container available locally. - "forwardPorts": [3000, 7007], - - // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "bash .devcontainer/postCreate.sh", - - // Configure tool-specific properties. - "customizations": { - "vscode": { - "extensions": ["Intility.vscode-backstage"] - } - } - - // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. - // "remoteUser": "root" -} diff --git a/contrib/.devcontainer/postCreate.sh b/contrib/.devcontainer/postCreate.sh deleted file mode 100755 index 88f403de5e..0000000000 --- a/contrib/.devcontainer/postCreate.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -yarn install -export VIRTUAL_ENV=$HOME/venv -python3 -m venv $VIRTUAL_ENV -export PATH="$VIRTUAL_ENV/bin:$PATH" -python3 -m pip install mkdocs-techdocs-core \ No newline at end of file diff --git a/contrib/catalog/ImmediateEntityProvider.ts b/contrib/catalog/ImmediateEntityProvider.ts index b5580b775a..ba17f19c9d 100644 --- a/contrib/catalog/ImmediateEntityProvider.ts +++ b/contrib/catalog/ImmediateEntityProvider.ts @@ -1,3 +1,8 @@ +import { + coreServices, + createBackendModule, + LoggerService, +} from '@backstage/backend-plugin-api'; import { ANNOTATION_LOCATION, ANNOTATION_ORIGIN_LOCATION, @@ -10,50 +15,18 @@ import { EntityProvider, EntityProviderConnection, } from '@backstage/plugin-catalog-node'; -import { parseEntityYaml } from '@backstage/plugin-catalog-backend'; +import { parseEntityYaml } from '@backstage/plugin-catalog-node'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; import bodyParser from 'body-parser'; import express from 'express'; import Router from 'express-promise-router'; import lodash from 'lodash'; -import { Logger } from 'winston'; /** * An entity provider attached to a router, that lets users perform direct * manipulation of a set of entities using REST requests. - * - * @remarks - * - * Installation: - * - * Add it to the catalog builder in your - * `packages/backend/src/plugins/catalog.ts`. Note that it BOTH adds a provider - * and amends the catalog router: - * - * ``` - * const immediate = new ImmediateEntityProvider({ - * logger: env.logger, - * handleEntity: (deferred) => { - * // Optionally modify the incoming entity - * }, - * }); - * builder.addEntityProvider(immediate); - * - * // ... - * - * return router.use('/immediate', immediate.getRouter()); - * ``` - * - * API (assume a catalog prefix, e.g. `/api/catalog`): - * - * - `POST /immediate/entities`: Accepts a YAML document of entities, and - * inserts or updates the entities that match that document. Returns 201 OK on - * success. - * - * - `PUT /immediate/entities`: Accepts a YAML document of entities, and - * replaces the entire set of entities managed by the provider with those - * entities. Returns 201 OK on success. */ -export class ImmediateEntityProvider implements EntityProvider { +class ImmediateEntityProvider implements EntityProvider { private connection?: EntityProviderConnection; private readonly entityValidator: (data: unknown) => Entity; @@ -76,7 +49,7 @@ export class ImmediateEntityProvider implements EntityProvider { router.use(bodyParser.raw({ type: '*/*' })); - router.post('/entities', async (req, res) => { + router.post('/immediate/entities', async (req, res) => { if (!this.connection) { throw new Error(`Service is not yet initialized`); } @@ -89,7 +62,7 @@ export class ImmediateEntityProvider implements EntityProvider { res.status(201).end(); }); - router.put('/entities', async (req, res) => { + router.put('/immediate/entities', async (req, res) => { if (!this.connection) { throw new Error(`Service is not yet initialized`); } @@ -151,19 +124,59 @@ export class ImmediateEntityProvider implements EntityProvider { /** * Options for {@link ImmediateEntityProvider}. */ -export interface ImmediateEntityProviderOptions { +interface ImmediateEntityProviderOptions { /** - * The logger to use. + * The logger. */ - logger: Logger; + logger: LoggerService; /** - * An optional function to perform adjustments to, or validate, an incoming - * entity before being stored. It is permitted to modify the deferred entity, - * but the request is static and has had its body consumed. + * An optional callback function to perform adjustments to, or validate, an + * incoming entity before being stored. It is permitted to modify the deferred + * entity, but the request is static and has had its body consumed. */ handleEntity?: ( request: express.Request, deferred: DeferredEntity, ) => void | Promise; } + +/** + * Backend module that installs an immediate entity provider. + * + * @remarks + * + * Install it by doing `backend.add(immediateEntityProviderModule)` in your `packages/backend/src/index.ts` file. + * + * API: + * + * - `POST /api/catalog/immediate/entities`: Accepts a YAML document of entities, and + * inserts or updates the entities that match that document. Returns 201 OK on + * success. + * + * - `PUT /api/catalog/immediate/entities`: Accepts a YAML document of entities, and + * replaces the entire set of entities managed by the provider with those + * entities. Returns 201 OK on success. + */ +export const immediateEntityProviderModule = createBackendModule({ + pluginId: 'catalog', + moduleId: 'immediate-entity-provider', + register(env) { + env.registerInit({ + deps: { + logger: coreServices.logger, + router: coreServices.httpRouter, + catalogProcessing: catalogProcessingExtensionPoint, + }, + async init({ logger, router, catalogProcessing }) { + const provider = new ImmediateEntityProvider({ + logger, + // add handleEntity here if you need to modify incoming entities before they are stored + }); + + catalogProcessing.addEntityProvider(provider); + router.use(provider.getRouter()); + }, + }); + }, +}); diff --git a/contrib/catalog/read-write-split/workspace/packages/backend/src/catalogService.ts b/contrib/catalog/read-write-split/workspace/packages/backend/src/catalogService.ts index 44119947e6..dea29a9f54 100644 --- a/contrib/catalog/read-write-split/workspace/packages/backend/src/catalogService.ts +++ b/contrib/catalog/read-write-split/workspace/packages/backend/src/catalogService.ts @@ -142,9 +142,13 @@ export class ReadWriteSplitCatalogService implements CatalogService { } async getLocations( - options: CatalogRequestOptions, + request: {} | undefined, + options: CatalogServiceRequestOptions, ): Promise { - return this.#catalogRead.getLocations(options); + return this.#catalogRead.getLocations( + request, + await this.#getOptions(options), + ); } async getLocationById( diff --git a/contrib/docs/tutorials/authenticate-api-requests.md b/contrib/docs/tutorials/authenticate-api-requests.md deleted file mode 100644 index 8c858bea0f..0000000000 --- a/contrib/docs/tutorials/authenticate-api-requests.md +++ /dev/null @@ -1,459 +0,0 @@ -# Authenticate API requests - -> [!CAUTION] -> This entire guide MUST NOT BE USED by users of Backstage 1.26 and -> newer. If you have applied the changes in this guide, you need to remove them -> again as you upgrade to recent versions of Backstage. When [the new auth changes](https://github.com/backstage/backstage/tree/master/beps/0003-auth-architecture-evolution) -> landed backends became natively secured through the framework, and the -> instructions outlined in here can interfere with the backend functioning -> correctly. - -The Backstage backend APIs are by default available without authentication. To avoid evil-doers from accessing or modifying data, one might use a network protection mechanism such as a firewall or an authenticating reverse proxy. For Backstage instances that are available on the Internet one can instead use the experimental IdentityClient as outlined below. - -API requests from frontend plugins include an authorization header with a Backstage identity token acquired when the user logs in. By adding a middleware that verifies said token to be valid and signed by Backstage, non-authenticated requests can be blocked with a 401 Unauthorized response. - -**NOTE**: Enabling this means that Backstage will stop working for guests, as no token is issued for them. If you have not done so already, you will also need to implement [service-to-service auth](https://backstage.io/docs/auth/service-to-service-auth). - -As techdocs HTML pages load assets without an Authorization header the code below also sets a token cookie when the user logs in (and when the token is about to expire). - -## Old Backend System Setup - -Create `packages/backend/src/authMiddleware.ts`: - -```typescript -import type { Config } from '@backstage/config'; -import { getBearerTokenFromAuthorizationHeader } from '@backstage/plugin-auth-node'; -import { NextFunction, Request, Response, RequestHandler } from 'express'; -import { decodeJwt } from 'jose'; -import { URL } from 'url'; -import { PluginEnvironment } from './types'; - -function setTokenCookie( - res: Response, - options: { token: string; secure: boolean; cookieDomain: string }, -) { - try { - const payload = decodeJwt(options.token); - res.cookie('token', options.token, { - expires: new Date(payload.exp ? payload.exp * 1000 : 0), - secure: options.secure, - sameSite: 'lax', - domain: options.cookieDomain, - path: '/', - httpOnly: true, - }); - } catch (_err) { - // Ignore - } -} - -export const createAuthMiddleware = async ( - config: Config, - appEnv: PluginEnvironment, -) => { - const baseUrl = config.getString('backend.baseUrl'); - const secure = baseUrl.startsWith('https://'); - const cookieDomain = new URL(baseUrl).hostname; - const authMiddleware: RequestHandler = async ( - req: Request, - res: Response, - next: NextFunction, - ) => { - try { - const token = - getBearerTokenFromAuthorizationHeader(req.headers.authorization) || - (req.cookies?.token as string | undefined); - if (!token) { - res.status(401).send('Unauthorized'); - return; - } - try { - req.user = await appEnv.identity.getIdentity({ request: req }); - } catch { - await appEnv.tokenManager.authenticate(token); - } - if (!req.headers.authorization) { - // Authorization header may be forwarded by plugin requests - req.headers.authorization = `Bearer ${token}`; - } - if (token && token !== req.cookies?.token) { - setTokenCookie(res, { - token, - secure, - cookieDomain, - }); - } - next(); - } catch (error) { - res.status(401).send('Unauthorized'); - } - }; - return authMiddleware; -}; -``` - -Install cookie-parser: - -```bash -# From your Backstage root directory -yarn --cwd packages/backend add cookie-parser -``` - -Update routes in `packages/backend/src/index.ts`: - -```typescript -// packages/backend/src/index.ts from a create-app deployment - -import { createAuthMiddleware } from './authMiddleware'; -import cookieParser from 'cookie-parser'; - -// ... - -async function main() { - // ... - - const authMiddleware = await createAuthMiddleware(config, appEnv); - - const apiRouter = Router(); - apiRouter.use(cookieParser()); - // The auth route must be publicly available as it is used during login - apiRouter.use('/auth', await auth(authEnv)); - // Add a simple endpoint to be used when setting a token cookie - apiRouter.use('/cookie', authMiddleware, (_req, res) => { - res.status(200).send(`Coming right up`); - }); - // Only authenticated requests are allowed to the routes below - apiRouter.use('/catalog', authMiddleware, await catalog(catalogEnv)); - apiRouter.use('/techdocs', authMiddleware, await techdocs(techdocsEnv)); - apiRouter.use('/proxy', authMiddleware, await proxy(proxyEnv)); - apiRouter.use(authMiddleware, notFoundHandler()); - - // ... -} -``` - -## New Backend System Setup - -Create `packages/backend/src/authMiddlewareFactory.ts`: - -```typescript -import { HostDiscovery } from '@backstage/backend-app-api'; -import { ServerTokenManager } from '@backstage/backend-common'; -import { - LoggerService, - RootConfigService, -} from '@backstage/backend-plugin-api'; -import { - DefaultIdentityClient, - getBearerTokenFromAuthorizationHeader, -} from '@backstage/plugin-auth-node'; -import { NextFunction, Request, RequestHandler, Response } from 'express'; -import { decodeJwt } from 'jose'; -import lzstring from 'lz-string'; -import { URL } from 'url'; - -type AuthMiddlewareFactoryOptions = { - config: RootConfigService; - logger: LoggerService; -}; - -export const authMiddlewareFactory = ({ - config, - logger, -}: AuthMiddlewareFactoryOptions): RequestHandler => { - const baseUrl = config.getString('backend.baseUrl'); - const discovery = HostDiscovery.fromConfig(config); - const identity = DefaultIdentityClient.create({ discovery }); - const tokenManager = ServerTokenManager.fromConfig(config, { logger }); - - return async (req: Request, res: Response, next: NextFunction) => { - const fullPath = `${req.baseUrl}${req.path}`; - - // Only apply auth to /api routes & skip auth for the following endpoints - // Add any additional plugin routes you want to whitelist eg. events - const nonAuthWhitelist = ['app', 'auth']; - const nonAuthRegex = new RegExp( - `^\/api\/(${nonAuthWhitelist.join('|')})(?=\/|$)\S*`, - ); - if (!fullPath.startsWith('/api/') || nonAuthRegex.test(fullPath)) { - next(); - return; - } - - try { - // Token cookies are compressed to reduce size - const cookieToken = lzstring.decompressFromEncodedURIComponent( - req.cookies.token, - ); - const token = - getBearerTokenFromAuthorizationHeader(req.headers.authorization) ?? - cookieToken; - - try { - // Attempt to authenticate as a frontend request token - await identity.authenticate(token); - } catch (err) { - // Attempt to authenticate as a backend request token - await tokenManager.authenticate(token); - } - - if (!req.headers.authorization) { - // Authorization header may be forwarded by plugin requests - req.headers.authorization = `Bearer ${token}`; - } - - if (token !== cookieToken) { - try { - const payload = decodeJwt(token); - res.cookie('token', token, { - // Compress token to reduce cookie size - encode: lzstring.compressToEncodedURIComponent, - expires: new Date((payload?.exp ?? 0) * 1000), - secure: baseUrl.startsWith('https://'), - sameSite: 'lax', - domain: new URL(baseUrl).hostname, - path: '/', - httpOnly: true, - }); - } catch { - // Ignore - } - } - next(); - } catch { - res.status(401).send(`Unauthorized`); - } - }; -}; -``` - -Install cookie-parser: - -```bash -# From your Backstage root directory -yarn --cwd packages/backend add cookie-parser @types/cookie-parser -``` - -Create a custom configured `rootHttpRouterService` in `packages/backend/src/customRootHttpRouterService.ts`: - -```typescript -import { rootHttpRouterServiceFactory } from '@backstage/backend-app-api'; -import cookieParser from 'cookie-parser'; -import { authMiddlewareFactory } from './authMiddlewareFactory'; - -export default rootHttpRouterServiceFactory({ - configure: ({ app, config, logger, middleware, routes }) => { - app.use(middleware.helmet()); - app.use(middleware.cors()); - app.use(middleware.compression()); - app.use(cookieParser()); - app.use(middleware.logging()); - - app.use(authMiddlewareFactory({ config, logger })); - - // Simple handler to set auth cookie for user - app.use('/api/cookie', (_, res) => { - res.status(200).send(); - }); - - app.use(routes); - - app.use(middleware.notFound()); - app.use(middleware.error()); - }, -}); -``` - -Update `packages/backend/src/index.ts` to add the custom `rootHttpRouterService` and override the default: - -```typescript -// ... -const backend = createBackend(); - -backend.add(import('./customRootHttpRouterService')); - -// ... -``` - -## Frontend Setup - -Create `packages/app/src/cookieAuth.ts`: - -```typescript -import type { IdentityApi } from '@backstage/core-plugin-api'; - -// Parses supplied JWT token and returns the payload -function parseJwt(token: string): { exp: number } { - const base64Url = token.split('.')[1]; - const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); - const jsonPayload = decodeURIComponent( - atob(base64) - .split('') - .map( - c => - // eslint-disable-next-line prefer-template - '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2), - ) - .join(''), - ); - - return JSON.parse(jsonPayload); -} - -// Returns milliseconds until the supplied JWT token expires -function msUntilExpiry(token: string): number { - const payload = parseJwt(token); - const remaining = - new Date(payload.exp * 1000).getTime() - new Date().getTime(); - return remaining; -} - -// Calls the specified url regularly using an auth token to set a token cookie -// to authorize regular HTTP requests when loading techdocs -export async function setTokenCookie(url: string, identityApi: IdentityApi) { - const { token } = await identityApi.getCredentials(); - if (!token) { - return; - } - - await fetch(url, { - mode: 'cors', - credentials: 'include', - headers: { - Authorization: `Bearer ${token}`, - }, - }); - - // Call this function again a few minutes before the token expires - const ms = msUntilExpiry(token) - 4 * 60 * 1000; - setTimeout( - () => { - setTokenCookie(url, identityApi); - }, - ms > 0 ? ms : 10000, - ); -} -``` - -```typescript -// required types and packages for example below - -import type { IdentityApi } from '@backstage/core-plugin-api'; -import { discoveryApiRef, useApi } from '@backstage/core-plugin-api'; - -// additional packages/app/src/App.tsx from a create-app deployment - -import { setTokenCookie } from './cookieAuth'; - -// ... - -const app = createApp({ - // ... - - components: { - SignInPage: props => { - const discoveryApi = useApi(discoveryApiRef); - return ( - { - setTokenCookie( - await discoveryApi.getBaseUrl('cookie'), - identityApi, - ); - - props.onSignInSuccess(identityApi); - }} - /> - ); - }, - }, - - // ... -}); - -// ... -``` - -**NOTE**: Most Backstage frontend plugins come with the support for the `IdentityApi`. -In case you already have a dozen of internal ones, you may need to update those too. -Assuming you follow the common plugin structure, the changes to your front-end may look like: - -```diff -// plugins/internal-plugin/src/api.ts -- import { createApiRef } from '@backstage/core-plugin-api'; -+ import { createApiRef, IdentityApi } from '@backstage/core-plugin-api'; -import { Config } from '@backstage/config'; -// ... - -type MyApiOptions = { - configApi: Config; -+ identityApi: IdentityApi; - // ... -} - -interface MyInterface { - getData(): Promise; -} - -export class MyApi implements MyInterface { - private configApi: Config; -+ private identityApi: IdentityApi; - // ... - - constructor(options: MyApiOptions) { - this.configApi = options.configApi; -+ this.identityApi = options.identityApi; - } - - async getMyData() { - const backendUrl = this.configApi.getString('backend.baseUrl'); - -+ const { token } = await this.identityApi.getCredentials(); - const requestUrl = `${backendUrl}/api/data/`; -- const response = await fetch(requestUrl); -+ const response = await fetch( - requestUrl, - { headers: { Authorization: `Bearer ${token}` } }, - ); - // ... - } -``` - -and - -```diff -// plugins/internal-plugin/src/plugin.ts - -import { - configApiRef, - createApiFactory, - createPlugin, -+ identityApiRef, -} from '@backstage/core-plugin-api'; -import { myPluginPageRouteRef } from './routeRefs'; -import { MyApi, myApiRef } from './api'; - -export const plugin = createPlugin({ - id: 'my-plugin', - routes: { - mainPage: myPluginPageRouteRef, - }, - apis: [ - createApiFactory({ - api: myApiRef, - deps: { - configApi: configApiRef, -+ identityApi: identityApiRef, - }, -- factory: ({ configApi }) => -- new MyApi({ configApi }), -+ factory: ({ configApi, identityApi }) => -+ new MyApi({ configApi, identityApi }), - }), - ], -}); -``` diff --git a/contrib/docs/tutorials/devcontainer.md b/contrib/docs/tutorials/devcontainer.md new file mode 100644 index 0000000000..00027de289 --- /dev/null +++ b/contrib/docs/tutorials/devcontainer.md @@ -0,0 +1,199 @@ +# Developing Backstage with Dev Containers + +This guide will help you get started with developing Backstage using [Dev Containers](https://containers.dev/). Dev +Containers provide a consistent, containerized development environment that works across different machines and +operating systems. + +## What are Dev Containers? + +Dev Containers (Development Containers) allow you to use a Docker container as a full-featured development environment. +All dependencies, tools, and configurations are defined in the container, so you don't need to install Node.js, Python, +or other tools on your local machine. + +## Prerequisites + +Before you begin, make sure you have the following installed: + +1. **Docker Desktop** (or Docker Engine on Linux) + +- [Docker Desktop for Mac](https://docs.docker.com/desktop/install/mac-install/) +- [Docker Desktop for Windows](https://docs.docker.com/desktop/install/windows-install/) +- [Docker Engine for Linux](https://docs.docker.com/engine/install/) + +2. **Visual Studio Code (or IntelliJ IDEA Ultimate)** + +- [Download VS Code](https://code.visualstudio.com/) +- [Download IntelliJ IDEA](https://www.jetbrains.com/idea/download/) + +**Note**: IntelliJ IDEA Community Edition does not support Dev Containers, so you will need the Ultimate (paid) edition +to use this feature. + +3. **Dev Containers Extension** + +**For VS Code**: + +- Install from + the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) +- Or open VS Code and search for "Dev Containers" in the Extensions view (`Ctrl/Cmd + Shift + X`) + +**For IntelliJ IDEA**: + +- Dev Containers support is available in IntelliJ IDEA Ultimate edition out of the box, + no additional plugin is required + +### System Requirements + +The dev container requires: + +- **CPU**: At least 2 cores +- **Memory**: At least 4GB of RAM +- **Storage**: At least 32GB of free disk space + +## Getting Started + +### Option 1: Open the Repository in a Dev Container + +1. **Fork and clone the Backstage repository** (if you haven't already): + +- Go to https://github.com/backstage/backstage and click "Fork" +- Clone your fork: + + ```shell + git clone https://github.com//backstage.git + cd backstage + ``` + +2. **Open in VS Code**: + +```shell +code . +``` + +3. **Reopen in Container**: + +- VS Code should detect the `.devcontainer` folder and show a notification asking if you want to reopen the folder in + a container +- Click "Reopen in Container" +- Alternatively, open the Command Palette (`Ctrl/Cmd + Shift + P`) and run "Dev Containers: Reopen in Container" + +4. **Wait for the container to build**: + +- The first time you open the container, it will take several minutes to build the Docker image and install all + dependencies +- VS Code will show progress in the bottom-right corner +- The setup script will automatically run `yarn install` and install Python dependencies for TechDocs + +5. **You're ready to develop!** + +- Once the setup is complete, you'll see a welcome message in the terminal +- Open a new terminal in VS Code (`Ctrl/Cmd + ~`) and run `yarn start` to launch Backstage + +### Option 2: Build and Open from Command Palette + +1. Open VS Code with the Backstage repository +2. Open the Command Palette (`Ctrl/Cmd + Shift + P`) +3. Run "Dev Containers: Rebuild and Reopen in Container" + +## What's Included + +The dev container comes pre-configured with: + +### Tools and Runtime + +- **Node.js**: Version suitable for Backstage development +- **Python 3**: For building and previewing TechDocs +- **Yarn**: Package manager +- **Git**: Version control +- **Chromium**: For running end-to-end tests +- **MkDocs with TechDocs core**: For TechDocs development + +### VS Code Extensions + +The following extensions are automatically installed: + +- **Prettier**: Code formatter (format on save enabled) +- **ESLint**: JavaScript/TypeScript linter +- **Backstage**: Official Backstage extension for syntax highlighting and snippets + +### Port Forwarding + +The following ports are automatically forwarded to your local machine: + +- **3000**: Frontend application +- **7007**: Backend API + +Additional ports, such as **9464** for the metrics endpoint, can be forwarded manually from the Ports panel in your +editor, or may be auto-forwarded when detected. + +## Using the Dev Container + +### Starting Backstage + +Once the container is ready and dependencies are installed: + +```shell +yarn start +``` + +This will start both the frontend (on port 3000) and backend (on port 7007). Access Backstage at: + +- **Frontend**: http://localhost:3000 +- **Backend API**: http://localhost:7007 + +> **Note**: The first time you start Backstage, the backend may take a minute to fully start. You might need to refresh +> your browser once the backend is ready. + +## Troubleshooting + +### Container build fails + +If the container fails to build, try: + +1. Make sure Docker Desktop is running +2. Rebuild the container: Command Palette → "Dev Containers: Rebuild Container" +3. Check Docker has enough resources allocated (CPU, memory, disk space) + +### Port already in use + +If ports 3000 or 7007 are already in use on your local machine: + +1. Stop any other Backstage instances or services using those ports +2. Reload the VS Code window: Command Palette → "Developer: Reload Window" + +### Dependencies not installed + +If `yarn start` fails with missing dependencies: + +1. Run `yarn install` manually in the terminal +2. Or rebuild the container to run the setup script again + +### Performance issues + +If the dev container feels slow: + +1. Increase Docker Desktop's resource allocation (Settings → Resources) +2. Make sure your Docker disk image isn't full +3. On Windows, ensure WSL 2 is enabled for better performance + +### Cannot connect to backend + +If the frontend shows connection errors: + +1. Wait a minute for the backend to fully start (check terminal logs) +2. Ensure port 7007 is properly forwarded (check Ports panel in VS Code) +3. Refresh your browser + +## Additional Resources + +- [Dev Containers Documentation](https://containers.dev/) +- [VS Code Dev Containers](https://code.visualstudio.com/docs/devcontainers/containers) +- [Backstage Getting Started Guide](https://backstage.io/docs/getting-started/) +- [Backstage Contributing Guide](../../../CONTRIBUTING.md) + +## Need Help? + +If you encounter issues not covered in this guide: + +- Check the [Backstage Discord](https://discord.gg/backstage) community +- Search or create an issue on [GitHub](https://github.com/backstage/backstage/issues) +- Review the [Backstage documentation](https://backstage.io/docs/) diff --git a/contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md b/contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md deleted file mode 100644 index 4852443ecf..0000000000 --- a/contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md +++ /dev/null @@ -1,101 +0,0 @@ -# Running the backend behind a Corporate Proxy - -This article helps you get your backend installation up and running making calls through corporate proxies. - -## Background - -Let's admit it, we've all been there. Sometimes you have to run stuff with no way out to the public internet, except via the smallest of corporate proxy tunnels. It's most likely that you're going to run into these issues from the backend part of Backstage as that's the part that isn't helped by your browser or OS settings for the corporate proxy. - -Unfortunately, neither the Node.js native `fetch` nor the other frequently used library `node-fetch` (see [ADR013](https://backstage.io/docs/architecture-decisions/adrs-adr013)) respect `HTTP(S)_PROXY` environment variables by default. As an additional complication, there is no single solution for configuring both native `fetch` and `node-fetch` at once, uniformly. - -There are however some ways to get this to work without too much effort. - -## Installation - -**Note:** You're going to want to be in your backend working directory for these solutions as that's where the requests come from that don't go through this proxy. - -1. Install the required packages in your backend, by running the following command inside your backend directory (typically `packages/backend` under your repository root). - - ```bash - yarn add undici global-agent - ``` - - `undici` exposes the settings for native `fetch`, and `global-agent` can set things up for `node-fetch`. - -1. Go to the entry file for the backend (typically `packages/backend/src/index.ts`), and add the following at the VERY top, before all other imports etc: - - ```ts - import 'global-agent/bootstrap'; - import { setGlobalDispatcher, EnvHttpProxyAgent } from 'undici'; - - setGlobalDispatcher(new EnvHttpProxyAgent()); - ``` - - The first import automatically bootstraps `global-agent`, which addresses `node-fetch` proxying. The lines below that set up the `undici` package which affects native `fetch`. - -1. Start the backend with the correct environment variables set. For example: - - ```sh - export HTTP_PROXY=http://username:password@proxy.example.net:8888 - export GLOBAL_AGENT_HTTP_PROXY=${HTTP_PROXY} - yarn start - ``` - - The default for `global-agent` is to have a prefix on the variable names, hence the need for specifying it twice. For further information about `HTTP(S)_PROXY` and `NO_PROXY` excludes, see [the global-agent documentation](https://github.com/gajus/global-agent) and [undici documentation](https://github.com/nodejs/undici). - -## Configuration - -If your development environment is in the cloud (like with [AWS Cloud9](https://aws.amazon.com/cloud9/) or an instance of [Theia](https://theia-ide.org/)), you will need to update your configuration. - -You will probably need to make some changes in `app-config.yaml` (or another config file like `app-config.local.yaml` if you've created it, see the [configuration doc](https://backstage.io/docs/conf/#supplying-configuration)). -The exact values will depend on your setup but for instance, if your public URL is `https://your-public-url.com` and the port `3000` and `8080` are open: - -```yaml -app: - baseUrl: https://your-public-url.com:3000 - listen: - host: 0.0.0.0 # This makes the dev server bind to all IPv4 interfaces and not just the baseUrl hostname - -backend: - baseUrl: https://your-public-url.com:8080 - listen: - port: 8080 - cors: - origin: https://your-public-url.com:3000 -``` - -The app port must proxy web socket connections in order to make hot reloading work. - -## Alternatives to `global-agent` - -The `proxy-agent` package can be used as an alternative to `global-agent` (do not install both!), and also ensures that the `node-fetch` library correctly respects proxy settings, but [does NOT work](https://github.com/TooTallNate/proxy-agents/issues/239) for modern `undici` based native Node.js `fetch`, so you'll still have to also do the `undici` steps in the section above in addition to this. - -`proxy-agent` is a library that you can use to override the `globalAgents` of `node` land with a tunnel to use for each request. - -1. Install `proxy-agent` using `yarn add proxy-agent` -2. Go to the entry file for the backend (`src/index.ts`) -3. At the top of the file paste the following: - - ```ts - import ProxyAgent from 'proxy-agent'; - import http from 'http'; - import https from 'https'; - - /* - Something to note here, this might need different configuration depending on your own setup. - If you only have an http_proxy then you'll need to set that as both the http and https globalAgent instead. - */ - if (process.env.HTTP_PROXY) { - http.globalAgent = new ProxyAgent(process.env.HTTP_PROXY); - } - - if (process.env.HTTPS_PROXY) { - https.globalAgent = new ProxyAgent(process.env.HTTPS_PROXY); - } - ``` - -4. Start the backend with `yarn start` - -## Backstage CLI - -The Backstage CLI [versions:bump](https://backstage.io/docs/tooling/cli/commands#versionsbump) command also supports proxies via `global-agent` environment variable configuration. See the [keeping Backstage updated](https://backstage.io/docs/getting-started/keeping-backstage-updated/#proxy) docs for more information. diff --git a/contrib/docs/tutorials/jsx-migration-codemod.md b/contrib/docs/tutorials/jsx-migration-codemod.md deleted file mode 100644 index 14ccd44080..0000000000 --- a/contrib/docs/tutorials/jsx-migration-codemod.md +++ /dev/null @@ -1,468 +0,0 @@ -# Migrating to the New JSX Transform using a Codemod - -## Using the Codemod - -While a codemod for the New JSX Transform was originally introduced in the [Introducing the New JSX Transform](https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html) article, it is no longer functional. A working solution, inspired by the original, is detailed below: - -1. **Create the transform file** - - Create a file named `transform.js` in the root directory of your Backstage project. - - ```js - /** - * (c) Facebook, Inc. and its affiliates. Confidential and proprietary. - * - * @format - */ - - module.exports = function (file, api, options) { - const j = api.jscodeshift; - const printOptions = options.printOptions || {}; - const root = j(file.source); - const destructureNamespaceImports = options.destructureNamespaceImports; - - // - function getFirstNode() { - return root.find(j.Program).get('body', 0).node; - } - - // Save the comments attached to the first node - const firstNode = getFirstNode(); - const { comments } = firstNode; - - function isVariableDeclared(variable) { - return ( - root - .find(j.Identifier, { - name: variable, - }) - .filter( - path => - path.parent.value.type !== 'MemberExpression' && - path.parent.value.type !== 'QualifiedTypeIdentifier' && - // Added this - path.parent.value.type !== 'TSQualifiedName' && - path.parent.value.type !== 'JSXMemberExpression', - ) - .size() > 0 - ); - } - - // Get all paths that import from React - const reactImportPaths = root - .find(j.ImportDeclaration, { - type: 'ImportDeclaration', - }) - .filter(path => { - return ( - (path.value.source.type === 'Literal' || - path.value.source.type === 'StringLiteral') && - (path.value.source.value === 'React' || - path.value.source.value === 'react') - ); - }); - - // get all namespace/default React imports - const reactPaths = reactImportPaths.filter(path => { - return ( - path.value.specifiers.length > 0 && - path.value.importKind === 'value' && - path.value.specifiers.some( - specifier => specifier.local.name === 'React', - ) - ); - }); - - if (reactPaths.size() > 1) { - throw Error( - 'There should only be one React import. Please remove the duplicate import and try again.', - ); - } - - if (reactPaths.size() === 0) { - return null; - } - - const reactPath = reactPaths.paths()[0]; - // Reuse the node so that we can preserve quoting style. - const reactLiteral = reactPath.value.source; - - const isDefaultImport = reactPath.value.specifiers.some( - specifier => - specifier.type === 'ImportDefaultSpecifier' && - specifier.local.name === 'React', - ); - - // Check to see if we should keep the React import - const isReactImportUsed = - root - .find(j.Identifier, { - name: 'React', - }) - .filter(path => { - return path.parent.parent.value.type !== 'ImportDeclaration'; - }) - .size() > 0; - - // local: imported - const reactIdentifiers = {}; - const reactTypeIdentifiers = {}; - let canDestructureReactVariable = false; - if ( - isReactImportUsed && - (isDefaultImport || destructureNamespaceImports) - ) { - // Checks to see if the react variable is used itself (rather than used to access its properties) - canDestructureReactVariable = - root - .find(j.Identifier, { - name: 'React', - }) - .filter(path => { - return path.parent.parent.value.type !== 'ImportDeclaration'; - }) - .filter( - path => - !( - path.parent.value.type === 'MemberExpression' && - path.parent.value.object.name === 'React' - ) && - !( - path.parent.value.type === 'QualifiedTypeIdentifier' && - path.parent.value.qualification.name === 'React' - ) && - !( - // Added this - ( - path.parent.value.type === 'TSQualifiedName' && - path.parent.value.left.name === 'React' - ) - ) && - !( - path.parent.value.type === 'JSXMemberExpression' && - path.parent.value.object.name === 'React' - ), - ) - .size() === 0; - - if (canDestructureReactVariable) { - // Add React identifiers to separate object so we can destructure the imports - // later if we can. If a type variable that we are trying to import has already - // been declared, do not try to destructure imports - // (ex. Element is declared and we are using React.Element) - root - .find(j.QualifiedTypeIdentifier, { - qualification: { - type: 'Identifier', - name: 'React', - }, - }) - .forEach(path => { - const id = path.value.id.name; - if (path.parent.parent.value.type === 'TypeofTypeAnnotation') { - // This is a typeof import so it isn't actually a type - reactIdentifiers[id] = id; - - if (reactTypeIdentifiers[id]) { - canDestructureReactVariable = false; - } - } else { - reactTypeIdentifiers[id] = id; - - if (reactIdentifiers[id]) { - canDestructureReactVariable = false; - } - } - - if (isVariableDeclared(id)) { - canDestructureReactVariable = false; - } - }); - - // Added this - root - .find(j.TSQualifiedName, { - left: { - type: 'Identifier', - name: 'React', - }, - }) - .forEach(path => { - const id = path.value.right.name; - reactIdentifiers[id] = id; - // We don't tend to use type imports - // Comment line above out and uncomment this to use type imports - // Also ignoring typeof imports? - // reactTypeIdentifiers[id] = id - - // if (reactIdentifiers[id]) { - // canDestructureReactVariable = false - // } - - if (isVariableDeclared(id)) { - canDestructureReactVariable = false; - } - }); - - // Add React identifiers to separate object so we can destructure the imports - // later if we can. If a variable that we are trying to import has already - // been declared, do not try to destructure imports - // (ex. createElement is declared and we are using React.createElement) - root - .find(j.MemberExpression, { - object: { - type: 'Identifier', - name: 'React', - }, - }) - .forEach(path => { - const property = path.value.property.name; - reactIdentifiers[property] = property; - - if ( - isVariableDeclared(property) || - reactTypeIdentifiers[property] - ) { - canDestructureReactVariable = false; - } - }); - - // Add React identifiers to separate object so we can destructure the imports - // later if we can. If a JSX variable that we are trying to import has already - // been declared, do not try to destructure imports - // (ex. Fragment is declared and we are using React.Fragment) - root - .find(j.JSXMemberExpression, { - object: { - type: 'JSXIdentifier', - name: 'React', - }, - }) - .forEach(path => { - const property = path.value.property.name; - reactIdentifiers[property] = property; - - if ( - isVariableDeclared(property) || - reactTypeIdentifiers[property] - ) { - canDestructureReactVariable = false; - } - }); - } - } - - if (canDestructureReactVariable) { - // replace react identifiers - root - .find(j.QualifiedTypeIdentifier, { - qualification: { - type: 'Identifier', - name: 'React', - }, - }) - .forEach(path => { - const id = path.value.id.name; - - j(path).replaceWith(j.identifier(id)); - }); - - // Added this - root - .find(j.TSQualifiedName, { - left: { - type: 'Identifier', - name: 'React', - }, - }) - .forEach(path => { - const id = path.value.right.name; - - j(path).replaceWith(j.identifier(id)); - }); - - root - .find(j.MemberExpression, { - object: { - type: 'Identifier', - name: 'React', - }, - }) - .forEach(path => { - const property = path.value.property.name; - - j(path).replaceWith(j.identifier(property)); - }); - - root - .find(j.JSXMemberExpression, { - object: { - type: 'JSXIdentifier', - name: 'React', - }, - }) - .forEach(path => { - const property = path.value.property.name; - - j(path).replaceWith(j.jsxIdentifier(property)); - }); - - // Add existing React imports to map - reactImportPaths.forEach(path => { - const specifiers = path.value.specifiers; - for (let i = 0; i < specifiers.length; i++) { - const specifier = specifiers[i]; - // get all type and regular imports that are imported - // from React - if (specifier.type === 'ImportSpecifier') { - if ( - path.value.importKind === 'type' || - specifier.importKind === 'type' - ) { - reactTypeIdentifiers[specifier.local.name] = - specifier.imported.name; - } else { - reactIdentifiers[specifier.local.name] = specifier.imported.name; - } - } - } - }); - - const regularImports = []; - Object.keys(reactIdentifiers).forEach(local => { - const imported = reactIdentifiers[local]; - regularImports.push( - j.importSpecifier(j.identifier(imported), j.identifier(local)), - ); - }); - - const typeImports = []; - Object.keys(reactTypeIdentifiers).forEach(local => { - const imported = reactTypeIdentifiers[local]; - typeImports.push( - j.importSpecifier(j.identifier(imported), j.identifier(local)), - ); - }); - - if (regularImports.length > 0) { - j(reactPath).insertAfter( - j.importDeclaration(regularImports, reactLiteral), - ); - } - if (typeImports.length > 0) { - j(reactPath).insertAfter( - j.importDeclaration(typeImports, reactLiteral, 'type'), - ); - } - - // remove all old react imports - reactImportPaths.forEach(path => { - // This is for import type React from 'react' which shouldn't - // be removed - if ( - path.value.specifiers.some( - specifier => - specifier.type === 'ImportDefaultSpecifier' && - specifier.local.name === 'React' && - (specifier.importKind === 'type' || - path.value.importKind === 'type'), - ) - ) { - j(path).insertAfter( - j.importDeclaration( - [j.importDefaultSpecifier(j.identifier('React'))], - reactLiteral, - 'type', - ), - ); - } - j(path).remove(); - }); - } else { - // Remove the import because it's not being used - // If we should keep the React import, just convert - // default imports to named imports - let isImportRemoved = false; - const specifiers = reactPath.value.specifiers; - for (let i = 0; i < specifiers.length; i++) { - const specifier = specifiers[i]; - if (specifier.type === 'ImportNamespaceSpecifier') { - if (!isReactImportUsed) { - isImportRemoved = true; - j(reactPath).remove(); - } - } else if (specifier.type === 'ImportDefaultSpecifier') { - if (isReactImportUsed) { - j(reactPath).insertAfter( - j.importDeclaration( - [j.importNamespaceSpecifier(j.identifier('React'))], - reactLiteral, - ), - ); - } - - if (specifiers.length > 1) { - const typeImports = []; - const regularImports = []; - for (let x = 0; x < specifiers.length; x++) { - if (specifiers[x].type !== 'ImportDefaultSpecifier') { - if (specifiers[x].importKind === 'type') { - typeImports.push(specifiers[x]); - } else { - regularImports.push(specifiers[x]); - } - } - } - if (regularImports.length > 0) { - j(reactPath).insertAfter( - j.importDeclaration(regularImports, reactLiteral), - ); - } - if (typeImports.length > 0) { - j(reactPath).insertAfter( - j.importDeclaration(typeImports, reactLiteral, 'type'), - ); - } - } - - isImportRemoved = true; - j(reactPath).remove(); - } - } - - if (!isImportRemoved) { - return null; - } - } - - // If the first node has been modified or deleted, reattach the comments - const firstNode2 = getFirstNode(); - if (firstNode2 !== firstNode) { - firstNode2.comments = comments; - } - - return root.toSource(printOptions); - }; - ``` - -2. **Execute the transformation** - - To apply the necessary changes, execute the following command twice from your root Backstage directory. First, run it for your packages, and then again for your plugins and any additional directories. Remember to adjust the paths to the transform script and parser source accordingly. - - ```console - npx jscodeshift --verbose=2 --ignore-pattern="**/node_modules/**" --parser ts --extensions=tsx,ts,jsx,js --transform ./path/to/the/transform.js --destructureNamespaceImports=true --parser=tsx ./path/to/src/ - ``` - -3. **Verify and clean up imports** - - Review the codebase for any remaining instances of `import * as React from 'react'` or `import React from 'react'`. Replace these with named imports where possible, such as: - - ```tsx - import { useState, useEffect } from 'react'; - ``` - - If retaining the default React import is absolutely necessary, use the following syntax instead: - - ```tsx - import { default as React } from 'react'; - ``` diff --git a/contrib/docs/tutorials/prometheus-metrics-output.png b/contrib/docs/tutorials/prometheus-metrics-output.png deleted file mode 100644 index f7fac739ac..0000000000 Binary files a/contrib/docs/tutorials/prometheus-metrics-output.png and /dev/null differ diff --git a/contrib/docs/tutorials/prometheus-metrics.md b/contrib/docs/tutorials/prometheus-metrics.md deleted file mode 100644 index 0db97c649e..0000000000 --- a/contrib/docs/tutorials/prometheus-metrics.md +++ /dev/null @@ -1,115 +0,0 @@ -# Prometheus - -> [!NOTE] -> The Prometheus metrics have been marked as deprecated and will be removed at a later point. The recommendation is to use the OpenTelemetry metrics by following the [Setup OpenTelemetry](https://backstage.io/docs/tutorials/setup-opentelemetry) documentation - -## Overview - -This is a small tutorial that goes over how to setup your Backstage instance to output metrics in a format that can be pulled in by Prometheus. - -## How to Setup Prometheus Metrics - -1. First we need to add the needed dependencies to the `package.json` in the `\packages\backend`: - - ```diff - // packages/backend/package.json - "dependencies": { - + "express-prom-bundle": "^7.0.0", - + "prom-client": "^15.0.0", - ``` - -2. Now we want to run `yarn install` from the root of the project to get those dependencies in place -3. Then we need to add a handler for the metrics by creating a file called `metrics.ts` in the `\packages\backend\src` folder -4. Next we add the following content to the `metrics.ts` file: - - ```ts - // packages/backend/src/metrics.ts - import { useHotCleanup } from '@backstage/backend-common'; - import { RequestHandler } from 'express'; - import promBundle from 'express-prom-bundle'; - import prom from 'prom-client'; - import * as url from 'url'; - - const rootRegEx = new RegExp('^/([^/]*)/.*'); - const apiRegEx = new RegExp('^/api/([^/]*)/.*'); - - export function normalizePath(req: any): string { - const path = url.parse(req.originalUrl || req.url).pathname || '/'; - - // Capture /api/ and the plugin name - if (apiRegEx.test(path)) { - return path.replace(apiRegEx, '/api/$1'); - } - - // Only the first path segment at root level - return path.replace(rootRegEx, '/$1'); - } - - /** - * Adds a /metrics endpoint, register default runtime metrics and instrument the router. - */ - export function metricsHandler(): RequestHandler { - // We can only initialize the metrics once and have to clean them up between hot reloads - useHotCleanup(module, () => prom.register.clear()); - - return promBundle({ - includeMethod: true, - includePath: true, - // Using includePath alone is problematic, as it will include path labels with high - // cardinality (e.g. path params). Instead we would have to template them. However, this - // is difficult, as every backend plugin might use different routes. Instead we only take - // the first directory of the path, to have at least an idea how each plugin performs: - normalizePath, - promClient: { collectDefaultMetrics: {} }, - }); - } - ``` - -5. Now we will extend the router configuration with the `metricsHandler`: - - ```diff - +import { metricsHandler } from './metrics'; - - ... - - const service = createServiceBuilder(module) - .loadConfig(config) - .addRouter('', await healthcheck(healthcheckEnv)) - + .addRouter('', metricsHandler()) - .addRouter('/api', apiRouter); - ``` - -6. You now have everything setup, from the `\packages\backend` folder run `yarn start` this will start up the backend -7. Now in a browser load up `http://localhost:7007/metrics`, if everything went smoothly you should see metrics in your browser something like this: - - ![Prometheus Metrics Example Output](prometheus-metrics-output.png) - -## Metrics - -The following sections goes over the included and experimental metrics available once you have completed this tutorial - -## Included - -This tutorials uses the [`express-prom-bundle`](https://github.com/jochen-schweizer/express-prom-bundle) and the [`prom-client`](https://github.com/siimon/prom-client) to make this all work. They both come with some built in metrics: - -- `express-prom-bundle` comes with 2 metrics: - - `up`: this normally will be just 1 - - `http_request_duration_seconds`: http latency histogram/summary labeled with `status_code`, `method` and `path` -- `prom-client` comes with a collection of metrics around memory, CPU, processes, etc. You can see the supported metrics in the `prom-client's` [`lib/metrics`](https://github.com/siimon/prom-client/tree/master/lib/metrics) folder. - -### Experimental - -There are some custom metrics that have been added to Backstage will be output for you, these are currently deemed experimental and may be changed or removed in a future release. Here is a rough list, again subject to changes: - -- `catalog_entities_count`: Total amount of entities in the catalog -- `catalog_registered_locations_count`: Total amount of registered locations in the catalog -- `catalog_relations_count`: Total amount of relations between entities -- `catalog_stitched_entities_count`: Amount of entities stitched -- `catalog_processed_entities_count`: Amount of entities processed -- `catalog_processing_duration_seconds`: Time spent executing the full processing flow -- `catalog_processors_duration_seconds`: Time spent executing catalog processors -- `catalog_processing_queue_delay_seconds`: The amount of delay between being scheduled for processing, and the start of actually being processed -- `scaffolder_task_count`: Tracks successful task runs. -- `scaffolder_task_duration`: a histogram which tracks the duration of a task run -- `scaffolder_step_count`: a count that tracks each step run -- `scaffolder_step_duration`: a histogram which tracks the duration of each step run diff --git a/contrib/docs/tutorials/quickstart-app-plugin/ExampleComponent.md b/contrib/docs/tutorials/quickstart-app-plugin/ExampleComponent.md deleted file mode 100644 index 78b76cd1a2..0000000000 --- a/contrib/docs/tutorials/quickstart-app-plugin/ExampleComponent.md +++ /dev/null @@ -1,53 +0,0 @@ -### Source repo: https://github.com/johnson-jesse/simple-backstage-app-plugin - -ExampleComponent.tsx reference - -```tsx -import { Typography, Grid } from '@material-ui/core'; -import { identityApiRef, useApi } from '@backstage/core-plugin-api'; -import { - InfoCard, - Header, - Page, - Content, - ContentHeader, - HeaderLabel, - SupportButton, -} from '@backstage/core-components'; -import { ExampleFetchComponent } from '../ExampleFetchComponent'; - -export const ExampleComponent = () => { - const identityApi = useApi(identityApiRef); - const userId = identityApi.getUserId(); - const profile = identityApi.getProfile(); - - return ( - -
- - -
- - - A description of your plugin goes here. - - - - - - {`${profile.displayName} | ${profile.email}`} - - - - - - - - -
- ); -}; -``` diff --git a/contrib/docs/tutorials/quickstart-app-plugin/ExampleFetchComponent.md b/contrib/docs/tutorials/quickstart-app-plugin/ExampleFetchComponent.md deleted file mode 100644 index d2c0553e34..0000000000 --- a/contrib/docs/tutorials/quickstart-app-plugin/ExampleFetchComponent.md +++ /dev/null @@ -1,103 +0,0 @@ -### Source repo: https://github.com/johnson-jesse/simple-backstage-app-plugin - -ExampleFetchComponent.tsx reference - -```tsx -import useAsync from 'react-use/lib/useAsync'; -import Alert from '@material-ui/lab/Alert'; -import { githubAuthApiRef, useApi } from '@backstage/core-plugin-api'; -import { Table, TableColumn, Progress } from '@backstage/core-components'; -import { graphql } from '@octokit/graphql'; - -const query = `{ - viewer { - repositories(first: 100) { - totalCount - nodes { - name - createdAt - description - diskUsage - isFork - } - pageInfo { - endCursor - hasNextPage - } - } - } -}`; - -type Node = { - name: string; - createdAt: string; - description: string; - diskUsage: number; - isFork: boolean; -}; - -type Viewer = { - repositories: { - totalCount: number; - nodes: Node[]; - pageInfo: { - endCursor: string; - hasNextPage: boolean; - }; - }; -}; - -type DenseTableProps = { - viewer: Viewer; -}; - -export const DenseTable = ({ viewer }: DenseTableProps) => { - const columns: TableColumn[] = [ - { title: 'Name', field: 'name' }, - { title: 'Created', field: 'createdAt' }, - { title: 'Description', field: 'description' }, - { title: 'Disk Usage', field: 'diskUsage' }, - { title: 'Fork', field: 'isFork' }, - ]; - - return ( - - ); -}; - -export const ExampleFetchComponent = () => { - const auth = useApi(githubAuthApiRef); - - const { value, loading, error } = useAsync(async (): Promise => { - const token = await auth.getAccessToken(); - - const gqlEndpoint = graphql.defaults({ - // Uncomment baseUrl if using enterprise - // baseUrl: 'https://github.MY-BIZ.com/api', - headers: { - authorization: `token ${token}`, - }, - }); - const { viewer } = await gqlEndpoint(query); - return viewer; - }, []); - - if (loading) return ; - if (error) return {error.message}; - if (value && value.repositories) return ; - - return ( -
- ); -}; -``` diff --git a/contrib/kubernetes/basic_kubernetes_example_with_helm/README.md b/contrib/kubernetes/basic_kubernetes_example_with_helm/README.md index c18329f59e..e4e4117b9c 100644 --- a/contrib/kubernetes/basic_kubernetes_example_with_helm/README.md +++ b/contrib/kubernetes/basic_kubernetes_example_with_helm/README.md @@ -1,3 +1,6 @@ # Basic Kubernetes example with Helm +> [!NOTE] +> This documentation is deprecated and will be removed at a future date, please use the well-maintained [Backstage Helm Charts](https://github.com/backstage/charts) for this. + Note that these examples aim to show a minimal setup and do not include best practices for secure Kubernetes deployments. See the [Kubernetes documentation](https://kubernetes.io/docs/concepts/security/) for more information, or resources provided by your own organization. diff --git a/contrib/search/confluence/README.md b/contrib/search/confluence/README.md index 80beb9c169..95d74f6b2b 100644 --- a/contrib/search/confluence/README.md +++ b/contrib/search/confluence/README.md @@ -1,5 +1,8 @@ # Confluence +> [!NOTE] +> This documentation is deprecated and will be removed at a future date. Please use the well-maintained [`@backstage-community/plugin-search-backend-module-confluence-collator` Community Plugin](https://github.com/backstage/community-plugins/tree/main/workspaces/confluence/plugins/search-backend-module-confluence-collator) instead. + These files help you add Confluence as a source to the Backstage Search plugin. To do so, add both files in this directory under the packages/backend/src/plugins/search/ pathway in your Backstage app. Then, add the following code to your packages/app/src/components/search/SearchPage.tsx: diff --git a/docker-compose.deps.yml b/docker-compose.deps.yml index 2ad65d6085..5b29031058 100644 --- a/docker-compose.deps.yml +++ b/docker-compose.deps.yml @@ -2,7 +2,7 @@ name: backstage services: psql: - image: postgres:17.6 + image: postgres:17.9 environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres @@ -24,7 +24,7 @@ services: timeout: 3s retries: 5 opensearch: - image: opensearchproject/opensearch:2.19.4 + image: opensearchproject/opensearch:2.19.5 environment: plugins.security.disabled: true discovery.type: single-node diff --git a/docs-ui/.eslintrc.json b/docs-ui/.eslintrc.json deleted file mode 100644 index be99640f75..0000000000 --- a/docs-ui/.eslintrc.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": ["next/core-web-vitals", "next/typescript"], - "rules": { - "notice/notice": "off", - "react/forbid-elements": "off", - "jsx-a11y/alt-text": "off" - } -} diff --git a/docs-ui/eslint.config.mjs b/docs-ui/eslint.config.mjs new file mode 100644 index 0000000000..2cb66fd509 --- /dev/null +++ b/docs-ui/eslint.config.mjs @@ -0,0 +1,16 @@ +import { defineConfig, globalIgnores } from 'eslint/config'; +import nextCoreWebVitals from 'eslint-config-next/core-web-vitals'; + +const eslintConfig = defineConfig([ + ...nextCoreWebVitals, + globalIgnores(['.next/**', 'dist/**', 'node_modules/**']), + { + rules: { + 'notice/notice': 'off', + 'react/forbid-elements': 'off', + 'jsx-a11y/alt-text': 'off', + }, + }, +]); + +export default eslintConfig; diff --git a/docs-ui/package.json b/docs-ui/package.json index 319c0e130f..30b6d28b45 100644 --- a/docs-ui/package.json +++ b/docs-ui/package.json @@ -3,19 +3,16 @@ "version": "0.1.0", "private": true, "scripts": { - "prebuild": "yarn sync:css", "build": "next build", - "lint": "next lint", - "prestart": "yarn sync:css", + "lint": "eslint .", "start": "next dev", "sync:changelog": "node scripts/sync-changelog.mjs", "sync:changelog:dry-run": "node scripts/sync-changelog.mjs --dry-run", - "sync:css": "node scripts/sync-css.js", - "sync:css:watch": "node scripts/sync-css.js --watch" + "sync:changelog:force": "node scripts/sync-changelog.mjs --force" }, "resolutions": { - "@types/react": "19.1.9", - "@types/react-dom": "19.1.7" + "@types/react": "19.2.10", + "@types/react-dom": "19.2.3" }, "dependencies": { "@base-ui-components/react": "^1.0.0-beta.4", @@ -24,33 +21,31 @@ "@lezer/highlight": "^1.2.1", "@mdx-js/loader": "^3.1.0", "@mdx-js/react": "^3.1.0", - "@next/mdx": "15.3.4", + "@next/mdx": "16.2.1", "@remixicon/react": "^4.6.0", - "@storybook/react": "^8.6.12", "@uiw/codemirror-themes": "^4.23.7", "@uiw/react-codemirror": "^4.23.7", "clsx": "^2.1.1", "html-react-parser": "^5.2.5", "motion": "^12.4.1", - "next": "15.4.7", + "next": "16.2.1", "next-mdx-remote-client": "^2.1.2", "prop-types": "^15.8.1", - "react": "19.1.1", - "react-dom": "19.1.1", - "shiki": "^3.13.0", - "storybook": "^8.6.12" + "react": "19.2.4", + "react-dom": "19.2.4", + "shiki": "^3.13.0" }, "devDependencies": { "@octokit/rest": "^22.0.1", "@shikijs/transformers": "^3.13.0", "@types/mdx": "^2.0.13", - "@types/node": "^20", - "@types/react": "19.1.9", - "@types/react-dom": "19.1.7", - "chokidar": "^3.6.0", - "eslint": "^8", - "eslint-config-next": "15.3.4", - "lightningcss": "^1.28.2", + "@types/node": "^22.13.14", + "@types/react": "19.2.10", + "@types/react-dom": "19.2.3", + "eslint": "^9", + "eslint-config-next": "16.2.1", + "postcss": "^8.5.6", + "postcss-import": "^16.1.1", "typescript": "^5", "unified": "^11.0.4" } diff --git a/docs-ui/postcss.config.mjs b/docs-ui/postcss.config.mjs new file mode 100644 index 0000000000..bd2bb9bb7c --- /dev/null +++ b/docs-ui/postcss.config.mjs @@ -0,0 +1,5 @@ +export default { + plugins: { + 'postcss-import': {}, + }, +}; diff --git a/docs-ui/public/theme-backstage.css b/docs-ui/public/theme-backstage.css deleted file mode 100644 index 59a5b986be..0000000000 --- a/docs-ui/public/theme-backstage.css +++ /dev/null @@ -1 +0,0 @@ -@layer tokens{:root{--bui-font-regular:system-ui;--bui-font-monospace:ui-monospace,"Menlo","Monaco","Consolas","Liberation Mono","Courier New",monospace;--bui-font-weight-regular:400;--bui-font-weight-bold:600;--bui-font-size-1:.625rem;--bui-font-size-2:.75rem;--bui-font-size-3:.875rem;--bui-font-size-4:1rem;--bui-font-size-5:1.25rem;--bui-font-size-6:1.5rem;--bui-font-size-7:2rem;--bui-font-size-8:3rem;--bui-font-size-9:4rem;--bui-font-size-10:5.75rem;--bui-space:.25rem;--bui-space-0_5:calc(var(--bui-space)*.5);--bui-space-1:var(--bui-space);--bui-space-1_5:calc(var(--bui-space)*1.5);--bui-space-2:calc(var(--bui-space)*2);--bui-space-3:calc(var(--bui-space)*3);--bui-space-4:calc(var(--bui-space)*4);--bui-space-5:calc(var(--bui-space)*5);--bui-space-6:calc(var(--bui-space)*6);--bui-space-7:calc(var(--bui-space)*7);--bui-space-8:calc(var(--bui-space)*8);--bui-space-9:calc(var(--bui-space)*9);--bui-space-10:calc(var(--bui-space)*10);--bui-space-11:calc(var(--bui-space)*11);--bui-space-12:calc(var(--bui-space)*12);--bui-space-13:calc(var(--bui-space)*13);--bui-space-14:calc(var(--bui-space)*14);--bui-radius-1:calc(.125rem);--bui-radius-2:calc(.25rem);--bui-radius-3:calc(.5rem);--bui-radius-4:calc(.75rem);--bui-radius-5:calc(1rem);--bui-radius-6:calc(1.25rem);--bui-radius-full:9999px;--bui-black:#000;--bui-white:#fff;--bui-gray-1:#f8f8f8;--bui-gray-2:#ececec;--bui-gray-3:#d9d9d9;--bui-gray-4:#c1c1c1;--bui-gray-5:#9e9e9e;--bui-gray-6:#8c8c8c;--bui-gray-7:#757575;--bui-gray-8:#595959;--bui-bg:var(--bui-gray-1);--bui-bg-surface-1:var(--bui-white);--bui-bg-surface-2:var(--bui-gray-2);--bui-bg-solid:#1f5493;--bui-bg-solid-hover:#163a66;--bui-bg-solid-pressed:#0f2b4e;--bui-bg-solid-disabled:#ebebeb;--bui-bg-tint:transparent;--bui-bg-tint-hover:#1f549366;--bui-bg-tint-pressed:#1f549399;--bui-bg-tint-disabled:#ebebeb;--bui-bg-danger:#feebe7;--bui-bg-warning:#fff2b2;--bui-bg-success:#e6f6eb;--bui-fg-primary:var(--bui-black);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:#1f5493;--bui-fg-link-hover:#1f2d5c;--bui-fg-disabled:#9e9e9e;--bui-fg-solid:var(--bui-white);--bui-fg-solid-disabled:#9c9c9c;--bui-fg-tint:#1f5493;--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#e22b2b;--bui-fg-warning:#e36d05;--bui-fg-success:#1db954;--bui-border:#0000001a;--bui-border-hover:#0003;--bui-border-pressed:#0006;--bui-border-disabled:#0000001a;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#1f5493;--bui-scrollbar:#a0a0a03b;--bui-scrollbar-thumb:#a0a0a0;--bui-animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite}[data-theme-mode=dark]{--bui-gray-1:#191919;--bui-gray-2:#242424;--bui-gray-3:#373737;--bui-gray-4:#464646;--bui-gray-5:#575757;--bui-gray-6:#7b7b7b;--bui-gray-7:#9e9e9e;--bui-gray-8:#b4b4b4;--bui-bg:#333;--bui-bg-surface-1:#424242;--bui-bg-surface-2:var(--bui-gray-2);--bui-bg-solid:#9cc9ff;--bui-bg-solid-hover:#83b9fd;--bui-bg-solid-pressed:#83b9fd;--bui-bg-solid-disabled:#222;--bui-bg-tint:transparent;--bui-bg-tint-hover:#9cc9ff1f;--bui-bg-tint-pressed:#9cc9ff29;--bui-bg-tint-disabled:transparent;--bui-bg-danger:#3b1219;--bui-bg-warning:#302008;--bui-bg-success:#132d21;--bui-fg-primary:var(--bui-white);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:#9cc9ff;--bui-fg-link-hover:#7eb5f7;--bui-fg-disabled:var(--bui-gray-7);--bui-fg-solid:#101821;--bui-fg-solid-disabled:var(--bui-gray-5);--bui-fg-tint:#9cc9ff;--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#e22b2b;--bui-fg-warning:#e36d05;--bui-fg-success:#1db954;--bui-border:#ffffff1f;--bui-border-hover:#fff6;--bui-border-pressed:#ffffff80;--bui-border-disabled:#fff3;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#1f5493;--bui-scrollbar:#3636363a;--bui-scrollbar-thumb:#575757}}@layer base{*,:before,:after{box-sizing:border-box}html{-webkit-text-size-adjust:100%;tab-size:4;font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;line-height:1.15}body{margin:0}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{border-color:currentColor}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:100%;line-height:1.15}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}legend{padding:0}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}:where(a){color:inherit;text-decoration:none}@keyframes pulse{50%{opacity:.5}}body{background-color:var(--bui-bg);color:var(--bui-fg-primary);font-family:var(--bui-font-regular);font-weight:var(--bui-font-weight-regular);font-size:var(--bui-font-size-3);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}}@layer components;@layer utilities{.bui-p{padding:var(--p)}.bui-p-0\.5{padding:var(--bui-space-0_5)}.bui-p-1{padding:var(--bui-space-1)}.bui-p-1\.5{padding:var(--bui-space-1_5)}.bui-p-2{padding:var(--bui-space-2)}.bui-p-3{padding:var(--bui-space-3)}.bui-p-4{padding:var(--bui-space-4)}.bui-p-5{padding:var(--bui-space-5)}.bui-p-6{padding:var(--bui-space-6)}.bui-p-7{padding:var(--bui-space-7)}.bui-p-8{padding:var(--bui-space-8)}.bui-p-9{padding:var(--bui-space-9)}.bui-p-10{padding:var(--bui-space-10)}.bui-p-11{padding:var(--bui-space-11)}.bui-p-12{padding:var(--bui-space-12)}.bui-p-13{padding:var(--bui-space-13)}.bui-p-14{padding:var(--bui-space-14)}@media (width>=640px){.xs\:bui-p{padding:var(--p-xs)}.xs\:bui-p-0\.5{padding:var(--bui-space-0_5)}.xs\:bui-p-1{padding:var(--bui-space-1)}.xs\:bui-p-1\.5{padding:var(--bui-space-1_5)}.xs\:bui-p-2{padding:var(--bui-space-2)}.xs\:bui-p-3{padding:var(--bui-space-3)}.xs\:bui-p-4{padding:var(--bui-space-4)}.xs\:bui-p-5{padding:var(--bui-space-5)}.xs\:bui-p-6{padding:var(--bui-space-6)}.xs\:bui-p-7{padding:var(--bui-space-7)}.xs\:bui-p-8{padding:var(--bui-space-8)}.xs\:bui-p-9{padding:var(--bui-space-9)}.xs\:bui-p-10{padding:var(--bui-space-10)}.xs\:bui-p-11{padding:var(--bui-space-11)}.xs\:bui-p-12{padding:var(--bui-space-12)}.xs\:bui-p-13{padding:var(--bui-space-13)}.xs\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-p{padding:var(--p-sm)}.sm\:bui-p-0\.5{padding:var(--bui-space-0_5)}.sm\:bui-p-1{padding:var(--bui-space-1)}.sm\:bui-p-1\.5{padding:var(--bui-space-1_5)}.sm\:bui-p-2{padding:var(--bui-space-2)}.sm\:bui-p-3{padding:var(--bui-space-3)}.sm\:bui-p-4{padding:var(--bui-space-4)}.sm\:bui-p-5{padding:var(--bui-space-5)}.sm\:bui-p-6{padding:var(--bui-space-6)}.sm\:bui-p-7{padding:var(--bui-space-7)}.sm\:bui-p-8{padding:var(--bui-space-8)}.sm\:bui-p-9{padding:var(--bui-space-9)}.sm\:bui-p-10{padding:var(--bui-space-10)}.sm\:bui-p-11{padding:var(--bui-space-11)}.sm\:bui-p-12{padding:var(--bui-space-12)}.sm\:bui-p-13{padding:var(--bui-space-13)}.sm\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-p{padding:var(--p-md)}.md\:bui-p-0\.5{padding:var(--bui-space-0_5)}.md\:bui-p-1{padding:var(--bui-space-1)}.md\:bui-p-1\.5{padding:var(--bui-space-1_5)}.md\:bui-p-2{padding:var(--bui-space-2)}.md\:bui-p-3{padding:var(--bui-space-3)}.md\:bui-p-4{padding:var(--bui-space-4)}.md\:bui-p-5{padding:var(--bui-space-5)}.md\:bui-p-6{padding:var(--bui-space-6)}.md\:bui-p-7{padding:var(--bui-space-7)}.md\:bui-p-8{padding:var(--bui-space-8)}.md\:bui-p-9{padding:var(--bui-space-9)}.md\:bui-p-10{padding:var(--bui-space-10)}.md\:bui-p-11{padding:var(--bui-space-11)}.md\:bui-p-12{padding:var(--bui-space-12)}.md\:bui-p-13{padding:var(--bui-space-13)}.md\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-p{padding:var(--p-lg)}.lg\:bui-p-0\.5{padding:var(--bui-space-0_5)}.lg\:bui-p-1{padding:var(--bui-space-1)}.lg\:bui-p-1\.5{padding:var(--bui-space-1_5)}.lg\:bui-p-2{padding:var(--bui-space-2)}.lg\:bui-p-3{padding:var(--bui-space-3)}.lg\:bui-p-4{padding:var(--bui-space-4)}.lg\:bui-p-5{padding:var(--bui-space-5)}.lg\:bui-p-6{padding:var(--bui-space-6)}.lg\:bui-p-7{padding:var(--bui-space-7)}.lg\:bui-p-8{padding:var(--bui-space-8)}.lg\:bui-p-9{padding:var(--bui-space-9)}.lg\:bui-p-10{padding:var(--bui-space-10)}.lg\:bui-p-11{padding:var(--bui-space-11)}.lg\:bui-p-12{padding:var(--bui-space-12)}.lg\:bui-p-13{padding:var(--bui-space-13)}.lg\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-p{padding:var(--p-xl)}.xl\:bui-p-0\.5{padding:var(--bui-space-0_5)}.xl\:bui-p-1{padding:var(--bui-space-1)}.xl\:bui-p-1\.5{padding:var(--bui-space-1_5)}.xl\:bui-p-2{padding:var(--bui-space-2)}.xl\:bui-p-3{padding:var(--bui-space-3)}.xl\:bui-p-4{padding:var(--bui-space-4)}.xl\:bui-p-5{padding:var(--bui-space-5)}.xl\:bui-p-6{padding:var(--bui-space-6)}.xl\:bui-p-7{padding:var(--bui-space-7)}.xl\:bui-p-8{padding:var(--bui-space-8)}.xl\:bui-p-9{padding:var(--bui-space-9)}.xl\:bui-p-10{padding:var(--bui-space-10)}.xl\:bui-p-11{padding:var(--bui-space-11)}.xl\:bui-p-12{padding:var(--bui-space-12)}.xl\:bui-p-13{padding:var(--bui-space-13)}.xl\:bui-p-14{padding:var(--bui-space-14)}}.bui-pl{padding-left:var(--pl)}.bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.bui-pl-1{padding-left:var(--bui-space-1)}.bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.bui-pl-2{padding-left:var(--bui-space-2)}.bui-pl-3{padding-left:var(--bui-space-3)}.bui-pl-4{padding-left:var(--bui-space-4)}.bui-pl-5{padding-left:var(--bui-space-5)}.bui-pl-6{padding-left:var(--bui-space-6)}.bui-pl-7{padding-left:var(--bui-space-7)}.bui-pl-8{padding-left:var(--bui-space-8)}.bui-pl-9{padding-left:var(--bui-space-9)}.bui-pl-10{padding-left:var(--bui-space-10)}.bui-pl-11{padding-left:var(--bui-space-11)}.bui-pl-12{padding-left:var(--bui-space-12)}.bui-pl-13{padding-left:var(--bui-space-13)}.bui-pl-14{padding-left:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pl{padding-left:var(--pl-xs)}.xs\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.xs\:bui-pl-1{padding-left:var(--bui-space-1)}.xs\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.xs\:bui-pl-2{padding-left:var(--bui-space-2)}.xs\:bui-pl-3{padding-left:var(--bui-space-3)}.xs\:bui-pl-4{padding-left:var(--bui-space-4)}.xs\:bui-pl-5{padding-left:var(--bui-space-5)}.xs\:bui-pl-6{padding-left:var(--bui-space-6)}.xs\:bui-pl-7{padding-left:var(--bui-space-7)}.xs\:bui-pl-8{padding-left:var(--bui-space-8)}.xs\:bui-pl-9{padding-left:var(--bui-space-9)}.xs\:bui-pl-10{padding-left:var(--bui-space-10)}.xs\:bui-pl-11{padding-left:var(--bui-space-11)}.xs\:bui-pl-12{padding-left:var(--bui-space-12)}.xs\:bui-pl-13{padding-left:var(--bui-space-13)}.xs\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pl{padding-left:var(--pl-sm)}.sm\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.sm\:bui-pl-1{padding-left:var(--bui-space-1)}.sm\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.sm\:bui-pl-2{padding-left:var(--bui-space-2)}.sm\:bui-pl-3{padding-left:var(--bui-space-3)}.sm\:bui-pl-4{padding-left:var(--bui-space-4)}.sm\:bui-pl-5{padding-left:var(--bui-space-5)}.sm\:bui-pl-6{padding-left:var(--bui-space-6)}.sm\:bui-pl-7{padding-left:var(--bui-space-7)}.sm\:bui-pl-8{padding-left:var(--bui-space-8)}.sm\:bui-pl-9{padding-left:var(--bui-space-9)}.sm\:bui-pl-10{padding-left:var(--bui-space-10)}.sm\:bui-pl-11{padding-left:var(--bui-space-11)}.sm\:bui-pl-12{padding-left:var(--bui-space-12)}.sm\:bui-pl-13{padding-left:var(--bui-space-13)}.sm\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pl{padding-left:var(--pl-md)}.md\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.md\:bui-pl-1{padding-left:var(--bui-space-1)}.md\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.md\:bui-pl-2{padding-left:var(--bui-space-2)}.md\:bui-pl-3{padding-left:var(--bui-space-3)}.md\:bui-pl-4{padding-left:var(--bui-space-4)}.md\:bui-pl-5{padding-left:var(--bui-space-5)}.md\:bui-pl-6{padding-left:var(--bui-space-6)}.md\:bui-pl-7{padding-left:var(--bui-space-7)}.md\:bui-pl-8{padding-left:var(--bui-space-8)}.md\:bui-pl-9{padding-left:var(--bui-space-9)}.md\:bui-pl-10{padding-left:var(--bui-space-10)}.md\:bui-pl-11{padding-left:var(--bui-space-11)}.md\:bui-pl-12{padding-left:var(--bui-space-12)}.md\:bui-pl-13{padding-left:var(--bui-space-13)}.md\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pl{padding-left:var(--pl-lg)}.lg\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.lg\:bui-pl-1{padding-left:var(--bui-space-1)}.lg\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.lg\:bui-pl-2{padding-left:var(--bui-space-2)}.lg\:bui-pl-3{padding-left:var(--bui-space-3)}.lg\:bui-pl-4{padding-left:var(--bui-space-4)}.lg\:bui-pl-5{padding-left:var(--bui-space-5)}.lg\:bui-pl-6{padding-left:var(--bui-space-6)}.lg\:bui-pl-7{padding-left:var(--bui-space-7)}.lg\:bui-pl-8{padding-left:var(--bui-space-8)}.lg\:bui-pl-9{padding-left:var(--bui-space-9)}.lg\:bui-pl-10{padding-left:var(--bui-space-10)}.lg\:bui-pl-11{padding-left:var(--bui-space-11)}.lg\:bui-pl-12{padding-left:var(--bui-space-12)}.lg\:bui-pl-13{padding-left:var(--bui-space-13)}.lg\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pl{padding-left:var(--pl-xl)}.xl\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.xl\:bui-pl-1{padding-left:var(--bui-space-1)}.xl\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.xl\:bui-pl-2{padding-left:var(--bui-space-2)}.xl\:bui-pl-3{padding-left:var(--bui-space-3)}.xl\:bui-pl-4{padding-left:var(--bui-space-4)}.xl\:bui-pl-5{padding-left:var(--bui-space-5)}.xl\:bui-pl-6{padding-left:var(--bui-space-6)}.xl\:bui-pl-7{padding-left:var(--bui-space-7)}.xl\:bui-pl-8{padding-left:var(--bui-space-8)}.xl\:bui-pl-9{padding-left:var(--bui-space-9)}.xl\:bui-pl-10{padding-left:var(--bui-space-10)}.xl\:bui-pl-11{padding-left:var(--bui-space-11)}.xl\:bui-pl-12{padding-left:var(--bui-space-12)}.xl\:bui-pl-13{padding-left:var(--bui-space-13)}.xl\:bui-pl-14{padding-left:var(--bui-space-14)}}.bui-pr{padding-right:var(--pr)}.bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.bui-pr-1{padding-right:var(--bui-space-1)}.bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.bui-pr-2{padding-right:var(--bui-space-2)}.bui-pr-3{padding-right:var(--bui-space-3)}.bui-pr-4{padding-right:var(--bui-space-4)}.bui-pr-5{padding-right:var(--bui-space-5)}.bui-pr-6{padding-right:var(--bui-space-6)}.bui-pr-7{padding-right:var(--bui-space-7)}.bui-pr-8{padding-right:var(--bui-space-8)}.bui-pr-9{padding-right:var(--bui-space-9)}.bui-pr-10{padding-right:var(--bui-space-10)}.bui-pr-11{padding-right:var(--bui-space-11)}.bui-pr-12{padding-right:var(--bui-space-12)}.bui-pr-13{padding-right:var(--bui-space-13)}.bui-pr-14{padding-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pr{padding-right:var(--pr-xs)}.xs\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.xs\:bui-pr-1{padding-right:var(--bui-space-1)}.xs\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.xs\:bui-pr-2{padding-right:var(--bui-space-2)}.xs\:bui-pr-3{padding-right:var(--bui-space-3)}.xs\:bui-pr-4{padding-right:var(--bui-space-4)}.xs\:bui-pr-5{padding-right:var(--bui-space-5)}.xs\:bui-pr-6{padding-right:var(--bui-space-6)}.xs\:bui-pr-7{padding-right:var(--bui-space-7)}.xs\:bui-pr-8{padding-right:var(--bui-space-8)}.xs\:bui-pr-9{padding-right:var(--bui-space-9)}.xs\:bui-pr-10{padding-right:var(--bui-space-10)}.xs\:bui-pr-11{padding-right:var(--bui-space-11)}.xs\:bui-pr-12{padding-right:var(--bui-space-12)}.xs\:bui-pr-13{padding-right:var(--bui-space-13)}.xs\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pr{padding-right:var(--pr-sm)}.sm\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.sm\:bui-pr-1{padding-right:var(--bui-space-1)}.sm\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.sm\:bui-pr-2{padding-right:var(--bui-space-2)}.sm\:bui-pr-3{padding-right:var(--bui-space-3)}.sm\:bui-pr-4{padding-right:var(--bui-space-4)}.sm\:bui-pr-5{padding-right:var(--bui-space-5)}.sm\:bui-pr-6{padding-right:var(--bui-space-6)}.sm\:bui-pr-7{padding-right:var(--bui-space-7)}.sm\:bui-pr-8{padding-right:var(--bui-space-8)}.sm\:bui-pr-9{padding-right:var(--bui-space-9)}.sm\:bui-pr-10{padding-right:var(--bui-space-10)}.sm\:bui-pr-11{padding-right:var(--bui-space-11)}.sm\:bui-pr-12{padding-right:var(--bui-space-12)}.sm\:bui-pr-13{padding-right:var(--bui-space-13)}.sm\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pr{padding-right:var(--pr-md)}.md\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.md\:bui-pr-1{padding-right:var(--bui-space-1)}.md\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.md\:bui-pr-2{padding-right:var(--bui-space-2)}.md\:bui-pr-3{padding-right:var(--bui-space-3)}.md\:bui-pr-4{padding-right:var(--bui-space-4)}.md\:bui-pr-5{padding-right:var(--bui-space-5)}.md\:bui-pr-6{padding-right:var(--bui-space-6)}.md\:bui-pr-7{padding-right:var(--bui-space-7)}.md\:bui-pr-8{padding-right:var(--bui-space-8)}.md\:bui-pr-9{padding-right:var(--bui-space-9)}.md\:bui-pr-10{padding-right:var(--bui-space-10)}.md\:bui-pr-11{padding-right:var(--bui-space-11)}.md\:bui-pr-12{padding-right:var(--bui-space-12)}.md\:bui-pr-13{padding-right:var(--bui-space-13)}.md\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pr{padding-right:var(--pr-lg)}.lg\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.lg\:bui-pr-1{padding-right:var(--bui-space-1)}.lg\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.lg\:bui-pr-2{padding-right:var(--bui-space-2)}.lg\:bui-pr-3{padding-right:var(--bui-space-3)}.lg\:bui-pr-4{padding-right:var(--bui-space-4)}.lg\:bui-pr-5{padding-right:var(--bui-space-5)}.lg\:bui-pr-6{padding-right:var(--bui-space-6)}.lg\:bui-pr-7{padding-right:var(--bui-space-7)}.lg\:bui-pr-8{padding-right:var(--bui-space-8)}.lg\:bui-pr-9{padding-right:var(--bui-space-9)}.lg\:bui-pr-10{padding-right:var(--bui-space-10)}.lg\:bui-pr-11{padding-right:var(--bui-space-11)}.lg\:bui-pr-12{padding-right:var(--bui-space-12)}.lg\:bui-pr-13{padding-right:var(--bui-space-13)}.lg\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pr{padding-right:var(--pr-xl)}.xl\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.xl\:bui-pr-1{padding-right:var(--bui-space-1)}.xl\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.xl\:bui-pr-2{padding-right:var(--bui-space-2)}.xl\:bui-pr-3{padding-right:var(--bui-space-3)}.xl\:bui-pr-4{padding-right:var(--bui-space-4)}.xl\:bui-pr-5{padding-right:var(--bui-space-5)}.xl\:bui-pr-6{padding-right:var(--bui-space-6)}.xl\:bui-pr-7{padding-right:var(--bui-space-7)}.xl\:bui-pr-8{padding-right:var(--bui-space-8)}.xl\:bui-pr-9{padding-right:var(--bui-space-9)}.xl\:bui-pr-10{padding-right:var(--bui-space-10)}.xl\:bui-pr-11{padding-right:var(--bui-space-11)}.xl\:bui-pr-12{padding-right:var(--bui-space-12)}.xl\:bui-pr-13{padding-right:var(--bui-space-13)}.xl\:bui-pr-14{padding-right:var(--bui-space-14)}}.bui-pt{padding-top:var(--pt)}.bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.bui-pt-1{padding-top:var(--bui-space-1)}.bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.bui-pt-2{padding-top:var(--bui-space-2)}.bui-pt-3{padding-top:var(--bui-space-3)}.bui-pt-4{padding-top:var(--bui-space-4)}.bui-pt-5{padding-top:var(--bui-space-5)}.bui-pt-6{padding-top:var(--bui-space-6)}.bui-pt-7{padding-top:var(--bui-space-7)}.bui-pt-8{padding-top:var(--bui-space-8)}.bui-pt-9{padding-top:var(--bui-space-9)}.bui-pt-10{padding-top:var(--bui-space-10)}.bui-pt-11{padding-top:var(--bui-space-11)}.bui-pt-12{padding-top:var(--bui-space-12)}.bui-pt-13{padding-top:var(--bui-space-13)}.bui-pt-14{padding-top:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pt{padding-top:var(--pt-xs)}.xs\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.xs\:bui-pt-1{padding-top:var(--bui-space-1)}.xs\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.xs\:bui-pt-2{padding-top:var(--bui-space-2)}.xs\:bui-pt-3{padding-top:var(--bui-space-3)}.xs\:bui-pt-4{padding-top:var(--bui-space-4)}.xs\:bui-pt-5{padding-top:var(--bui-space-5)}.xs\:bui-pt-6{padding-top:var(--bui-space-6)}.xs\:bui-pt-7{padding-top:var(--bui-space-7)}.xs\:bui-pt-8{padding-top:var(--bui-space-8)}.xs\:bui-pt-9{padding-top:var(--bui-space-9)}.xs\:bui-pt-10{padding-top:var(--bui-space-10)}.xs\:bui-pt-11{padding-top:var(--bui-space-11)}.xs\:bui-pt-12{padding-top:var(--bui-space-12)}.xs\:bui-pt-13{padding-top:var(--bui-space-13)}.xs\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pt{padding-top:var(--pt-sm)}.sm\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.sm\:bui-pt-1{padding-top:var(--bui-space-1)}.sm\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.sm\:bui-pt-2{padding-top:var(--bui-space-2)}.sm\:bui-pt-3{padding-top:var(--bui-space-3)}.sm\:bui-pt-4{padding-top:var(--bui-space-4)}.sm\:bui-pt-5{padding-top:var(--bui-space-5)}.sm\:bui-pt-6{padding-top:var(--bui-space-6)}.sm\:bui-pt-7{padding-top:var(--bui-space-7)}.sm\:bui-pt-8{padding-top:var(--bui-space-8)}.sm\:bui-pt-9{padding-top:var(--bui-space-9)}.sm\:bui-pt-10{padding-top:var(--bui-space-10)}.sm\:bui-pt-11{padding-top:var(--bui-space-11)}.sm\:bui-pt-12{padding-top:var(--bui-space-12)}.sm\:bui-pt-13{padding-top:var(--bui-space-13)}.sm\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pt{padding-top:var(--pt-md)}.md\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.md\:bui-pt-1{padding-top:var(--bui-space-1)}.md\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.md\:bui-pt-2{padding-top:var(--bui-space-2)}.md\:bui-pt-3{padding-top:var(--bui-space-3)}.md\:bui-pt-4{padding-top:var(--bui-space-4)}.md\:bui-pt-5{padding-top:var(--bui-space-5)}.md\:bui-pt-6{padding-top:var(--bui-space-6)}.md\:bui-pt-7{padding-top:var(--bui-space-7)}.md\:bui-pt-8{padding-top:var(--bui-space-8)}.md\:bui-pt-9{padding-top:var(--bui-space-9)}.md\:bui-pt-10{padding-top:var(--bui-space-10)}.md\:bui-pt-11{padding-top:var(--bui-space-11)}.md\:bui-pt-12{padding-top:var(--bui-space-12)}.md\:bui-pt-13{padding-top:var(--bui-space-13)}.md\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pt{padding-top:var(--pt-lg)}.lg\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.lg\:bui-pt-1{padding-top:var(--bui-space-1)}.lg\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.lg\:bui-pt-2{padding-top:var(--bui-space-2)}.lg\:bui-pt-3{padding-top:var(--bui-space-3)}.lg\:bui-pt-4{padding-top:var(--bui-space-4)}.lg\:bui-pt-5{padding-top:var(--bui-space-5)}.lg\:bui-pt-6{padding-top:var(--bui-space-6)}.lg\:bui-pt-7{padding-top:var(--bui-space-7)}.lg\:bui-pt-8{padding-top:var(--bui-space-8)}.lg\:bui-pt-9{padding-top:var(--bui-space-9)}.lg\:bui-pt-10{padding-top:var(--bui-space-10)}.lg\:bui-pt-11{padding-top:var(--bui-space-11)}.lg\:bui-pt-12{padding-top:var(--bui-space-12)}.lg\:bui-pt-13{padding-top:var(--bui-space-13)}.lg\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pt{padding-top:var(--pt-xl)}.xl\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.xl\:bui-pt-1{padding-top:var(--bui-space-1)}.xl\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.xl\:bui-pt-2{padding-top:var(--bui-space-2)}.xl\:bui-pt-3{padding-top:var(--bui-space-3)}.xl\:bui-pt-4{padding-top:var(--bui-space-4)}.xl\:bui-pt-5{padding-top:var(--bui-space-5)}.xl\:bui-pt-6{padding-top:var(--bui-space-6)}.xl\:bui-pt-7{padding-top:var(--bui-space-7)}.xl\:bui-pt-8{padding-top:var(--bui-space-8)}.xl\:bui-pt-9{padding-top:var(--bui-space-9)}.xl\:bui-pt-10{padding-top:var(--bui-space-10)}.xl\:bui-pt-11{padding-top:var(--bui-space-11)}.xl\:bui-pt-12{padding-top:var(--bui-space-12)}.xl\:bui-pt-13{padding-top:var(--bui-space-13)}.xl\:bui-pt-14{padding-top:var(--bui-space-14)}}.bui-pb{padding-bottom:var(--pb)}.bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.bui-pb-1{padding-bottom:var(--bui-space-1)}.bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.bui-pb-2{padding-bottom:var(--bui-space-2)}.bui-pb-3{padding-bottom:var(--bui-space-3)}.bui-pb-4{padding-bottom:var(--bui-space-4)}.bui-pb-5{padding-bottom:var(--bui-space-5)}.bui-pb-6{padding-bottom:var(--bui-space-6)}.bui-pb-7{padding-bottom:var(--bui-space-7)}.bui-pb-8{padding-bottom:var(--bui-space-8)}.bui-pb-9{padding-bottom:var(--bui-space-9)}.bui-pb-10{padding-bottom:var(--bui-space-10)}.bui-pb-11{padding-bottom:var(--bui-space-11)}.bui-pb-12{padding-bottom:var(--bui-space-12)}.bui-pb-13{padding-bottom:var(--bui-space-13)}.bui-pb-14{padding-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pb{padding-bottom:var(--pb-xs)}.xs\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.xs\:bui-pb-1{padding-bottom:var(--bui-space-1)}.xs\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.xs\:bui-pb-2{padding-bottom:var(--bui-space-2)}.xs\:bui-pb-3{padding-bottom:var(--bui-space-3)}.xs\:bui-pb-4{padding-bottom:var(--bui-space-4)}.xs\:bui-pb-5{padding-bottom:var(--bui-space-5)}.xs\:bui-pb-6{padding-bottom:var(--bui-space-6)}.xs\:bui-pb-7{padding-bottom:var(--bui-space-7)}.xs\:bui-pb-8{padding-bottom:var(--bui-space-8)}.xs\:bui-pb-9{padding-bottom:var(--bui-space-9)}.xs\:bui-pb-10{padding-bottom:var(--bui-space-10)}.xs\:bui-pb-11{padding-bottom:var(--bui-space-11)}.xs\:bui-pb-12{padding-bottom:var(--bui-space-12)}.xs\:bui-pb-13{padding-bottom:var(--bui-space-13)}.xs\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pb{padding-bottom:var(--pb-sm)}.sm\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.sm\:bui-pb-1{padding-bottom:var(--bui-space-1)}.sm\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.sm\:bui-pb-2{padding-bottom:var(--bui-space-2)}.sm\:bui-pb-3{padding-bottom:var(--bui-space-3)}.sm\:bui-pb-4{padding-bottom:var(--bui-space-4)}.sm\:bui-pb-5{padding-bottom:var(--bui-space-5)}.sm\:bui-pb-6{padding-bottom:var(--bui-space-6)}.sm\:bui-pb-7{padding-bottom:var(--bui-space-7)}.sm\:bui-pb-8{padding-bottom:var(--bui-space-8)}.sm\:bui-pb-9{padding-bottom:var(--bui-space-9)}.sm\:bui-pb-10{padding-bottom:var(--bui-space-10)}.sm\:bui-pb-11{padding-bottom:var(--bui-space-11)}.sm\:bui-pb-12{padding-bottom:var(--bui-space-12)}.sm\:bui-pb-13{padding-bottom:var(--bui-space-13)}.sm\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pb{padding-bottom:var(--pb-md)}.md\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.md\:bui-pb-1{padding-bottom:var(--bui-space-1)}.md\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.md\:bui-pb-2{padding-bottom:var(--bui-space-2)}.md\:bui-pb-3{padding-bottom:var(--bui-space-3)}.md\:bui-pb-4{padding-bottom:var(--bui-space-4)}.md\:bui-pb-5{padding-bottom:var(--bui-space-5)}.md\:bui-pb-6{padding-bottom:var(--bui-space-6)}.md\:bui-pb-7{padding-bottom:var(--bui-space-7)}.md\:bui-pb-8{padding-bottom:var(--bui-space-8)}.md\:bui-pb-9{padding-bottom:var(--bui-space-9)}.md\:bui-pb-10{padding-bottom:var(--bui-space-10)}.md\:bui-pb-11{padding-bottom:var(--bui-space-11)}.md\:bui-pb-12{padding-bottom:var(--bui-space-12)}.md\:bui-pb-13{padding-bottom:var(--bui-space-13)}.md\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pb{padding-bottom:var(--pb-lg)}.lg\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.lg\:bui-pb-1{padding-bottom:var(--bui-space-1)}.lg\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.lg\:bui-pb-2{padding-bottom:var(--bui-space-2)}.lg\:bui-pb-3{padding-bottom:var(--bui-space-3)}.lg\:bui-pb-4{padding-bottom:var(--bui-space-4)}.lg\:bui-pb-5{padding-bottom:var(--bui-space-5)}.lg\:bui-pb-6{padding-bottom:var(--bui-space-6)}.lg\:bui-pb-7{padding-bottom:var(--bui-space-7)}.lg\:bui-pb-8{padding-bottom:var(--bui-space-8)}.lg\:bui-pb-9{padding-bottom:var(--bui-space-9)}.lg\:bui-pb-10{padding-bottom:var(--bui-space-10)}.lg\:bui-pb-11{padding-bottom:var(--bui-space-11)}.lg\:bui-pb-12{padding-bottom:var(--bui-space-12)}.lg\:bui-pb-13{padding-bottom:var(--bui-space-13)}.lg\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pb{padding-bottom:var(--pb-xl)}.xl\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.xl\:bui-pb-1{padding-bottom:var(--bui-space-1)}.xl\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.xl\:bui-pb-2{padding-bottom:var(--bui-space-2)}.xl\:bui-pb-3{padding-bottom:var(--bui-space-3)}.xl\:bui-pb-4{padding-bottom:var(--bui-space-4)}.xl\:bui-pb-5{padding-bottom:var(--bui-space-5)}.xl\:bui-pb-6{padding-bottom:var(--bui-space-6)}.xl\:bui-pb-7{padding-bottom:var(--bui-space-7)}.xl\:bui-pb-8{padding-bottom:var(--bui-space-8)}.xl\:bui-pb-9{padding-bottom:var(--bui-space-9)}.xl\:bui-pb-10{padding-bottom:var(--bui-space-10)}.xl\:bui-pb-11{padding-bottom:var(--bui-space-11)}.xl\:bui-pb-12{padding-bottom:var(--bui-space-12)}.xl\:bui-pb-13{padding-bottom:var(--bui-space-13)}.xl\:bui-pb-14{padding-bottom:var(--bui-space-14)}}.bui-py{padding-top:var(--py);padding-bottom:var(--py)}.bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-py{padding-top:var(--py-xs);padding-bottom:var(--py-xs)}.xs\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.xs\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.xs\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.xs\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.xs\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.xs\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.xs\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.xs\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.xs\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.xs\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.xs\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.xs\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.xs\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.xs\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.xs\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.xs\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-py{padding-top:var(--py-sm);padding-bottom:var(--py-sm)}.sm\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.sm\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.sm\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.sm\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.sm\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.sm\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.sm\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.sm\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.sm\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.sm\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.sm\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.sm\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.sm\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.sm\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.sm\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.sm\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-py{padding-top:var(--py-md);padding-bottom:var(--py-md)}.md\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.md\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.md\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.md\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.md\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.md\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.md\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.md\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.md\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.md\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.md\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.md\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.md\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.md\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.md\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.md\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-py{padding-top:var(--py-lg);padding-bottom:var(--py-lg)}.lg\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.lg\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.lg\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.lg\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.lg\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.lg\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.lg\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.lg\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.lg\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.lg\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.lg\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.lg\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.lg\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.lg\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.lg\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.lg\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-py{padding-top:var(--py-xl);padding-bottom:var(--py-xl)}.xl\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.xl\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.xl\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.xl\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.xl\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.xl\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.xl\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.xl\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.xl\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.xl\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.xl\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.xl\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.xl\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.xl\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.xl\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.xl\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}.bui-px{padding-left:var(--px);padding-right:var(--px)}.bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-px{padding-left:var(--px-xs);padding-right:var(--px-xs)}.xs\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.xs\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.xs\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.xs\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.xs\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.xs\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.xs\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.xs\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.xs\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.xs\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.xs\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.xs\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.xs\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.xs\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.xs\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.xs\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-px{padding-left:var(--px-sm);padding-right:var(--px-sm)}.sm\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.sm\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.sm\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.sm\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.sm\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.sm\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.sm\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.sm\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.sm\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.sm\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.sm\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.sm\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.sm\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.sm\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.sm\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.sm\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-px{padding-left:var(--px-md);padding-right:var(--px-md)}.md\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.md\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.md\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.md\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.md\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.md\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.md\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.md\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.md\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.md\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.md\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.md\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.md\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.md\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.md\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.md\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-px{padding-left:var(--px-lg);padding-right:var(--px-lg)}.lg\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.lg\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.lg\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.lg\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.lg\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.lg\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.lg\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.lg\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.lg\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.lg\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.lg\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.lg\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.lg\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.lg\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.lg\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.lg\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-px{padding-left:var(--px-xl);padding-right:var(--px-xl)}.xl\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.xl\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.xl\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.xl\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.xl\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.xl\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.xl\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.xl\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.xl\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.xl\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.xl\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.xl\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.xl\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.xl\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.xl\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.xl\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}.bui-m{margin:var(--m)}.bui-m-0\.5{margin:var(--bui-space-0_5)}.bui-m-1{margin:var(--bui-space-1)}.bui-m-1\.5{margin:var(--bui-space-1_5)}.bui-m-2{margin:var(--bui-space-2)}.bui-m-3{margin:var(--bui-space-3)}.bui-m-4{margin:var(--bui-space-4)}.bui-m-5{margin:var(--bui-space-5)}.bui-m-6{margin:var(--bui-space-6)}.bui-m-7{margin:var(--bui-space-7)}.bui-m-8{margin:var(--bui-space-8)}.bui-m-9{margin:var(--bui-space-9)}.bui-m-10{margin:var(--bui-space-10)}.bui-m-11{margin:var(--bui-space-11)}.bui-m-12{margin:var(--bui-space-12)}.bui-m-13{margin:var(--bui-space-13)}.bui-m-14{margin:var(--bui-space-14)}@media (width>=640px){.xs\:bui-m{margin:var(--p-xs)}.xs\:bui-m-0\.5{margin:var(--bui-space-0_5)}.xs\:bui-m-1{margin:var(--bui-space-1)}.xs\:bui-m-1\.5{margin:var(--bui-space-1_5)}.xs\:bui-m-2{margin:var(--bui-space-2)}.xs\:bui-m-3{margin:var(--bui-space-3)}.xs\:bui-m-4{margin:var(--bui-space-4)}.xs\:bui-m-5{margin:var(--bui-space-5)}.xs\:bui-m-6{margin:var(--bui-space-6)}.xs\:bui-m-7{margin:var(--bui-space-7)}.xs\:bui-m-8{margin:var(--bui-space-8)}.xs\:bui-m-9{margin:var(--bui-space-9)}.xs\:bui-m-10{margin:var(--bui-space-10)}.xs\:bui-m-11{margin:var(--bui-space-11)}.xs\:bui-m-12{margin:var(--bui-space-12)}.xs\:bui-m-13{margin:var(--bui-space-13)}.xs\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-m{margin:var(--p-sm)}.sm\:bui-m-0\.5{margin:var(--bui-space-0_5)}.sm\:bui-m-1{margin:var(--bui-space-1)}.sm\:bui-m-1\.5{margin:var(--bui-space-1_5)}.sm\:bui-m-2{margin:var(--bui-space-2)}.sm\:bui-m-3{margin:var(--bui-space-3)}.sm\:bui-m-4{margin:var(--bui-space-4)}.sm\:bui-m-5{margin:var(--bui-space-5)}.sm\:bui-m-6{margin:var(--bui-space-6)}.sm\:bui-m-7{margin:var(--bui-space-7)}.sm\:bui-m-8{margin:var(--bui-space-8)}.sm\:bui-m-9{margin:var(--bui-space-9)}.sm\:bui-m-10{margin:var(--bui-space-10)}.sm\:bui-m-11{margin:var(--bui-space-11)}.sm\:bui-m-12{margin:var(--bui-space-12)}.sm\:bui-m-13{margin:var(--bui-space-13)}.sm\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-m{margin:var(--p-md)}.md\:bui-m-0\.5{margin:var(--bui-space-0_5)}.md\:bui-m-1{margin:var(--bui-space-1)}.md\:bui-m-1\.5{margin:var(--bui-space-1_5)}.md\:bui-m-2{margin:var(--bui-space-2)}.md\:bui-m-3{margin:var(--bui-space-3)}.md\:bui-m-4{margin:var(--bui-space-4)}.md\:bui-m-5{margin:var(--bui-space-5)}.md\:bui-m-6{margin:var(--bui-space-6)}.md\:bui-m-7{margin:var(--bui-space-7)}.md\:bui-m-8{margin:var(--bui-space-8)}.md\:bui-m-9{margin:var(--bui-space-9)}.md\:bui-m-10{margin:var(--bui-space-10)}.md\:bui-m-11{margin:var(--bui-space-11)}.md\:bui-m-12{margin:var(--bui-space-12)}.md\:bui-m-13{margin:var(--bui-space-13)}.md\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-m{margin:var(--p-lg)}.lg\:bui-m-0\.5{margin:var(--bui-space-0_5)}.lg\:bui-m-1{margin:var(--bui-space-1)}.lg\:bui-m-1\.5{margin:var(--bui-space-1_5)}.lg\:bui-m-2{margin:var(--bui-space-2)}.lg\:bui-m-3{margin:var(--bui-space-3)}.lg\:bui-m-4{margin:var(--bui-space-4)}.lg\:bui-m-5{margin:var(--bui-space-5)}.lg\:bui-m-6{margin:var(--bui-space-6)}.lg\:bui-m-7{margin:var(--bui-space-7)}.lg\:bui-m-8{margin:var(--bui-space-8)}.lg\:bui-m-9{margin:var(--bui-space-9)}.lg\:bui-m-10{margin:var(--bui-space-10)}.lg\:bui-m-11{margin:var(--bui-space-11)}.lg\:bui-m-12{margin:var(--bui-space-12)}.lg\:bui-m-13{margin:var(--bui-space-13)}.lg\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-m{margin:var(--p-xl)}.xl\:bui-m-0\.5{margin:var(--bui-space-0_5)}.xl\:bui-m-1{margin:var(--bui-space-1)}.xl\:bui-m-1\.5{margin:var(--bui-space-1_5)}.xl\:bui-m-2{margin:var(--bui-space-2)}.xl\:bui-m-3{margin:var(--bui-space-3)}.xl\:bui-m-4{margin:var(--bui-space-4)}.xl\:bui-m-5{margin:var(--bui-space-5)}.xl\:bui-m-6{margin:var(--bui-space-6)}.xl\:bui-m-7{margin:var(--bui-space-7)}.xl\:bui-m-8{margin:var(--bui-space-8)}.xl\:bui-m-9{margin:var(--bui-space-9)}.xl\:bui-m-10{margin:var(--bui-space-10)}.xl\:bui-m-11{margin:var(--bui-space-11)}.xl\:bui-m-12{margin:var(--bui-space-12)}.xl\:bui-m-13{margin:var(--bui-space-13)}.xl\:bui-m-14{margin:var(--bui-space-14)}}.bui-ml{margin-left:var(--ml)}.bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.bui-ml-1{margin-left:var(--bui-space-1)}.bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.bui-ml-2{margin-left:var(--bui-space-2)}.bui-ml-3{margin-left:var(--bui-space-3)}.bui-ml-4{margin-left:var(--bui-space-4)}.bui-ml-5{margin-left:var(--bui-space-5)}.bui-ml-6{margin-left:var(--bui-space-6)}.bui-ml-7{margin-left:var(--bui-space-7)}.bui-ml-8{margin-left:var(--bui-space-8)}.bui-ml-9{margin-left:var(--bui-space-9)}.bui-ml-10{margin-left:var(--bui-space-10)}.bui-ml-11{margin-left:var(--bui-space-11)}.bui-ml-12{margin-left:var(--bui-space-12)}.bui-ml-13{margin-left:var(--bui-space-13)}.bui-ml-14{margin-left:var(--bui-space-14)}@media (width>=640px){.xs\:bui-ml{margin-left:var(--ml-xs)}.xs\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.xs\:bui-ml-1{margin-left:var(--bui-space-1)}.xs\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.xs\:bui-ml-2{margin-left:var(--bui-space-2)}.xs\:bui-ml-3{margin-left:var(--bui-space-3)}.xs\:bui-ml-4{margin-left:var(--bui-space-4)}.xs\:bui-ml-5{margin-left:var(--bui-space-5)}.xs\:bui-ml-6{margin-left:var(--bui-space-6)}.xs\:bui-ml-7{margin-left:var(--bui-space-7)}.xs\:bui-ml-8{margin-left:var(--bui-space-8)}.xs\:bui-ml-9{margin-left:var(--bui-space-9)}.xs\:bui-ml-10{margin-left:var(--bui-space-10)}.xs\:bui-ml-11{margin-left:var(--bui-space-11)}.xs\:bui-ml-12{margin-left:var(--bui-space-12)}.xs\:bui-ml-13{margin-left:var(--bui-space-13)}.xs\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-ml{margin-left:var(--ml-sm)}.sm\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.sm\:bui-ml-1{margin-left:var(--bui-space-1)}.sm\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.sm\:bui-ml-2{margin-left:var(--bui-space-2)}.sm\:bui-ml-3{margin-left:var(--bui-space-3)}.sm\:bui-ml-4{margin-left:var(--bui-space-4)}.sm\:bui-ml-5{margin-left:var(--bui-space-5)}.sm\:bui-ml-6{margin-left:var(--bui-space-6)}.sm\:bui-ml-7{margin-left:var(--bui-space-7)}.sm\:bui-ml-8{margin-left:var(--bui-space-8)}.sm\:bui-ml-9{margin-left:var(--bui-space-9)}.sm\:bui-ml-10{margin-left:var(--bui-space-10)}.sm\:bui-ml-11{margin-left:var(--bui-space-11)}.sm\:bui-ml-12{margin-left:var(--bui-space-12)}.sm\:bui-ml-13{margin-left:var(--bui-space-13)}.sm\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-ml{margin-left:var(--ml-md)}.md\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.md\:bui-ml-1{margin-left:var(--bui-space-1)}.md\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.md\:bui-ml-2{margin-left:var(--bui-space-2)}.md\:bui-ml-3{margin-left:var(--bui-space-3)}.md\:bui-ml-4{margin-left:var(--bui-space-4)}.md\:bui-ml-5{margin-left:var(--bui-space-5)}.md\:bui-ml-6{margin-left:var(--bui-space-6)}.md\:bui-ml-7{margin-left:var(--bui-space-7)}.md\:bui-ml-8{margin-left:var(--bui-space-8)}.md\:bui-ml-9{margin-left:var(--bui-space-9)}.md\:bui-ml-10{margin-left:var(--bui-space-10)}.md\:bui-ml-11{margin-left:var(--bui-space-11)}.md\:bui-ml-12{margin-left:var(--bui-space-12)}.md\:bui-ml-13{margin-left:var(--bui-space-13)}.md\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-ml{margin-left:var(--ml-lg)}.lg\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.lg\:bui-ml-1{margin-left:var(--bui-space-1)}.lg\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.lg\:bui-ml-2{margin-left:var(--bui-space-2)}.lg\:bui-ml-3{margin-left:var(--bui-space-3)}.lg\:bui-ml-4{margin-left:var(--bui-space-4)}.lg\:bui-ml-5{margin-left:var(--bui-space-5)}.lg\:bui-ml-6{margin-left:var(--bui-space-6)}.lg\:bui-ml-7{margin-left:var(--bui-space-7)}.lg\:bui-ml-8{margin-left:var(--bui-space-8)}.lg\:bui-ml-9{margin-left:var(--bui-space-9)}.lg\:bui-ml-10{margin-left:var(--bui-space-10)}.lg\:bui-ml-11{margin-left:var(--bui-space-11)}.lg\:bui-ml-12{margin-left:var(--bui-space-12)}.lg\:bui-ml-13{margin-left:var(--bui-space-13)}.lg\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-ml{margin-left:var(--ml-xl)}.xl\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.xl\:bui-ml-1{margin-left:var(--bui-space-1)}.xl\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.xl\:bui-ml-2{margin-left:var(--bui-space-2)}.xl\:bui-ml-3{margin-left:var(--bui-space-3)}.xl\:bui-ml-4{margin-left:var(--bui-space-4)}.xl\:bui-ml-5{margin-left:var(--bui-space-5)}.xl\:bui-ml-6{margin-left:var(--bui-space-6)}.xl\:bui-ml-7{margin-left:var(--bui-space-7)}.xl\:bui-ml-8{margin-left:var(--bui-space-8)}.xl\:bui-ml-9{margin-left:var(--bui-space-9)}.xl\:bui-ml-10{margin-left:var(--bui-space-10)}.xl\:bui-ml-11{margin-left:var(--bui-space-11)}.xl\:bui-ml-12{margin-left:var(--bui-space-12)}.xl\:bui-ml-13{margin-left:var(--bui-space-13)}.xl\:bui-ml-14{margin-left:var(--bui-space-14)}}.bui-mr{margin-right:var(--mr)}.bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.bui-mr-1{margin-right:var(--bui-space-1)}.bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.bui-mr-2{margin-right:var(--bui-space-2)}.bui-mr-3{margin-right:var(--bui-space-3)}.bui-mr-4{margin-right:var(--bui-space-4)}.bui-mr-5{margin-right:var(--bui-space-5)}.bui-mr-6{margin-right:var(--bui-space-6)}.bui-mr-7{margin-right:var(--bui-space-7)}.bui-mr-8{margin-right:var(--bui-space-8)}.bui-mr-9{margin-right:var(--bui-space-9)}.bui-mr-10{margin-right:var(--bui-space-10)}.bui-mr-11{margin-right:var(--bui-space-11)}.bui-mr-12{margin-right:var(--bui-space-12)}.bui-mr-13{margin-right:var(--bui-space-13)}.bui-mr-14{margin-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mr{margin-right:var(--mr-xs)}.xs\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.xs\:bui-mr-1{margin-right:var(--bui-space-1)}.xs\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.xs\:bui-mr-2{margin-right:var(--bui-space-2)}.xs\:bui-mr-3{margin-right:var(--bui-space-3)}.xs\:bui-mr-4{margin-right:var(--bui-space-4)}.xs\:bui-mr-5{margin-right:var(--bui-space-5)}.xs\:bui-mr-6{margin-right:var(--bui-space-6)}.xs\:bui-mr-7{margin-right:var(--bui-space-7)}.xs\:bui-mr-8{margin-right:var(--bui-space-8)}.xs\:bui-mr-9{margin-right:var(--bui-space-9)}.xs\:bui-mr-10{margin-right:var(--bui-space-10)}.xs\:bui-mr-11{margin-right:var(--bui-space-11)}.xs\:bui-mr-12{margin-right:var(--bui-space-12)}.xs\:bui-mr-13{margin-right:var(--bui-space-13)}.xs\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mr{margin-right:var(--mr-sm)}.sm\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.sm\:bui-mr-1{margin-right:var(--bui-space-1)}.sm\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.sm\:bui-mr-2{margin-right:var(--bui-space-2)}.sm\:bui-mr-3{margin-right:var(--bui-space-3)}.sm\:bui-mr-4{margin-right:var(--bui-space-4)}.sm\:bui-mr-5{margin-right:var(--bui-space-5)}.sm\:bui-mr-6{margin-right:var(--bui-space-6)}.sm\:bui-mr-7{margin-right:var(--bui-space-7)}.sm\:bui-mr-8{margin-right:var(--bui-space-8)}.sm\:bui-mr-9{margin-right:var(--bui-space-9)}.sm\:bui-mr-10{margin-right:var(--bui-space-10)}.sm\:bui-mr-11{margin-right:var(--bui-space-11)}.sm\:bui-mr-12{margin-right:var(--bui-space-12)}.sm\:bui-mr-13{margin-right:var(--bui-space-13)}.sm\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mr{margin-right:var(--mr-md)}.md\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.md\:bui-mr-1{margin-right:var(--bui-space-1)}.md\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.md\:bui-mr-2{margin-right:var(--bui-space-2)}.md\:bui-mr-3{margin-right:var(--bui-space-3)}.md\:bui-mr-4{margin-right:var(--bui-space-4)}.md\:bui-mr-5{margin-right:var(--bui-space-5)}.md\:bui-mr-6{margin-right:var(--bui-space-6)}.md\:bui-mr-7{margin-right:var(--bui-space-7)}.md\:bui-mr-8{margin-right:var(--bui-space-8)}.md\:bui-mr-9{margin-right:var(--bui-space-9)}.md\:bui-mr-10{margin-right:var(--bui-space-10)}.md\:bui-mr-11{margin-right:var(--bui-space-11)}.md\:bui-mr-12{margin-right:var(--bui-space-12)}.md\:bui-mr-13{margin-right:var(--bui-space-13)}.md\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mr{margin-right:var(--mr-lg)}.lg\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.lg\:bui-mr-1{margin-right:var(--bui-space-1)}.lg\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.lg\:bui-mr-2{margin-right:var(--bui-space-2)}.lg\:bui-mr-3{margin-right:var(--bui-space-3)}.lg\:bui-mr-4{margin-right:var(--bui-space-4)}.lg\:bui-mr-5{margin-right:var(--bui-space-5)}.lg\:bui-mr-6{margin-right:var(--bui-space-6)}.lg\:bui-mr-7{margin-right:var(--bui-space-7)}.lg\:bui-mr-8{margin-right:var(--bui-space-8)}.lg\:bui-mr-9{margin-right:var(--bui-space-9)}.lg\:bui-mr-10{margin-right:var(--bui-space-10)}.lg\:bui-mr-11{margin-right:var(--bui-space-11)}.lg\:bui-mr-12{margin-right:var(--bui-space-12)}.lg\:bui-mr-13{margin-right:var(--bui-space-13)}.lg\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mr{margin-right:var(--mr-xl)}.xl\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.xl\:bui-mr-1{margin-right:var(--bui-space-1)}.xl\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.xl\:bui-mr-2{margin-right:var(--bui-space-2)}.xl\:bui-mr-3{margin-right:var(--bui-space-3)}.xl\:bui-mr-4{margin-right:var(--bui-space-4)}.xl\:bui-mr-5{margin-right:var(--bui-space-5)}.xl\:bui-mr-6{margin-right:var(--bui-space-6)}.xl\:bui-mr-7{margin-right:var(--bui-space-7)}.xl\:bui-mr-8{margin-right:var(--bui-space-8)}.xl\:bui-mr-9{margin-right:var(--bui-space-9)}.xl\:bui-mr-10{margin-right:var(--bui-space-10)}.xl\:bui-mr-11{margin-right:var(--bui-space-11)}.xl\:bui-mr-12{margin-right:var(--bui-space-12)}.xl\:bui-mr-13{margin-right:var(--bui-space-13)}.xl\:bui-mr-14{margin-right:var(--bui-space-14)}}.bui-mt{margin-top:var(--mt)}.bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.bui-mt-1{margin-top:var(--bui-space-1)}.bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.bui-mt-2{margin-top:var(--bui-space-2)}.bui-mt-3{margin-top:var(--bui-space-3)}.bui-mt-4{margin-top:var(--bui-space-4)}.bui-mt-5{margin-top:var(--bui-space-5)}.bui-mt-6{margin-top:var(--bui-space-6)}.bui-mt-7{margin-top:var(--bui-space-7)}.bui-mt-8{margin-top:var(--bui-space-8)}.bui-mt-9{margin-top:var(--bui-space-9)}.bui-mt-10{margin-top:var(--bui-space-10)}.bui-mt-11{margin-top:var(--bui-space-11)}.bui-mt-12{margin-top:var(--bui-space-12)}.bui-mt-13{margin-top:var(--bui-space-13)}.bui-mt-14{margin-top:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mt{margin-top:var(--mt-xs)}.xs\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.xs\:bui-mt-1{margin-top:var(--bui-space-1)}.xs\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.xs\:bui-mt-2{margin-top:var(--bui-space-2)}.xs\:bui-mt-3{margin-top:var(--bui-space-3)}.xs\:bui-mt-4{margin-top:var(--bui-space-4)}.xs\:bui-mt-5{margin-top:var(--bui-space-5)}.xs\:bui-mt-6{margin-top:var(--bui-space-6)}.xs\:bui-mt-7{margin-top:var(--bui-space-7)}.xs\:bui-mt-8{margin-top:var(--bui-space-8)}.xs\:bui-mt-9{margin-top:var(--bui-space-9)}.xs\:bui-mt-10{margin-top:var(--bui-space-10)}.xs\:bui-mt-11{margin-top:var(--bui-space-11)}.xs\:bui-mt-12{margin-top:var(--bui-space-12)}.xs\:bui-mt-13{margin-top:var(--bui-space-13)}.xs\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mt{margin-top:var(--mt-sm)}.sm\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.sm\:bui-mt-1{margin-top:var(--bui-space-1)}.sm\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.sm\:bui-mt-2{margin-top:var(--bui-space-2)}.sm\:bui-mt-3{margin-top:var(--bui-space-3)}.sm\:bui-mt-4{margin-top:var(--bui-space-4)}.sm\:bui-mt-5{margin-top:var(--bui-space-5)}.sm\:bui-mt-6{margin-top:var(--bui-space-6)}.sm\:bui-mt-7{margin-top:var(--bui-space-7)}.sm\:bui-mt-8{margin-top:var(--bui-space-8)}.sm\:bui-mt-9{margin-top:var(--bui-space-9)}.sm\:bui-mt-10{margin-top:var(--bui-space-10)}.sm\:bui-mt-11{margin-top:var(--bui-space-11)}.sm\:bui-mt-12{margin-top:var(--bui-space-12)}.sm\:bui-mt-13{margin-top:var(--bui-space-13)}.sm\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mt{margin-top:var(--mt-md)}.md\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.md\:bui-mt-1{margin-top:var(--bui-space-1)}.md\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.md\:bui-mt-2{margin-top:var(--bui-space-2)}.md\:bui-mt-3{margin-top:var(--bui-space-3)}.md\:bui-mt-4{margin-top:var(--bui-space-4)}.md\:bui-mt-5{margin-top:var(--bui-space-5)}.md\:bui-mt-6{margin-top:var(--bui-space-6)}.md\:bui-mt-7{margin-top:var(--bui-space-7)}.md\:bui-mt-8{margin-top:var(--bui-space-8)}.md\:bui-mt-9{margin-top:var(--bui-space-9)}.md\:bui-mt-10{margin-top:var(--bui-space-10)}.md\:bui-mt-11{margin-top:var(--bui-space-11)}.md\:bui-mt-12{margin-top:var(--bui-space-12)}.md\:bui-mt-13{margin-top:var(--bui-space-13)}.md\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mt{margin-top:var(--mt-lg)}.lg\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.lg\:bui-mt-1{margin-top:var(--bui-space-1)}.lg\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.lg\:bui-mt-2{margin-top:var(--bui-space-2)}.lg\:bui-mt-3{margin-top:var(--bui-space-3)}.lg\:bui-mt-4{margin-top:var(--bui-space-4)}.lg\:bui-mt-5{margin-top:var(--bui-space-5)}.lg\:bui-mt-6{margin-top:var(--bui-space-6)}.lg\:bui-mt-7{margin-top:var(--bui-space-7)}.lg\:bui-mt-8{margin-top:var(--bui-space-8)}.lg\:bui-mt-9{margin-top:var(--bui-space-9)}.lg\:bui-mt-10{margin-top:var(--bui-space-10)}.lg\:bui-mt-11{margin-top:var(--bui-space-11)}.lg\:bui-mt-12{margin-top:var(--bui-space-12)}.lg\:bui-mt-13{margin-top:var(--bui-space-13)}.lg\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mt{margin-top:var(--mt-xl)}.xl\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.xl\:bui-mt-1{margin-top:var(--bui-space-1)}.xl\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.xl\:bui-mt-2{margin-top:var(--bui-space-2)}.xl\:bui-mt-3{margin-top:var(--bui-space-3)}.xl\:bui-mt-4{margin-top:var(--bui-space-4)}.xl\:bui-mt-5{margin-top:var(--bui-space-5)}.xl\:bui-mt-6{margin-top:var(--bui-space-6)}.xl\:bui-mt-7{margin-top:var(--bui-space-7)}.xl\:bui-mt-8{margin-top:var(--bui-space-8)}.xl\:bui-mt-9{margin-top:var(--bui-space-9)}.xl\:bui-mt-10{margin-top:var(--bui-space-10)}.xl\:bui-mt-11{margin-top:var(--bui-space-11)}.xl\:bui-mt-12{margin-top:var(--bui-space-12)}.xl\:bui-mt-13{margin-top:var(--bui-space-13)}.xl\:bui-mt-14{margin-top:var(--bui-space-14)}}.bui-mb{margin-bottom:var(--mb)}.bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.bui-mb-1{margin-bottom:var(--bui-space-1)}.bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.bui-mb-2{margin-bottom:var(--bui-space-2)}.bui-mb-3{margin-bottom:var(--bui-space-3)}.bui-mb-4{margin-bottom:var(--bui-space-4)}.bui-mb-5{margin-bottom:var(--bui-space-5)}.bui-mb-6{margin-bottom:var(--bui-space-6)}.bui-mb-7{margin-bottom:var(--bui-space-7)}.bui-mb-8{margin-bottom:var(--bui-space-8)}.bui-mb-9{margin-bottom:var(--bui-space-9)}.bui-mb-10{margin-bottom:var(--bui-space-10)}.bui-mb-11{margin-bottom:var(--bui-space-11)}.bui-mb-12{margin-bottom:var(--bui-space-12)}.bui-mb-13{margin-bottom:var(--bui-space-13)}.bui-mb-14{margin-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mb{margin-bottom:var(--mb-xs)}.xs\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.xs\:bui-mb-1{margin-bottom:var(--bui-space-1)}.xs\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.xs\:bui-mb-2{margin-bottom:var(--bui-space-2)}.xs\:bui-mb-3{margin-bottom:var(--bui-space-3)}.xs\:bui-mb-4{margin-bottom:var(--bui-space-4)}.xs\:bui-mb-5{margin-bottom:var(--bui-space-5)}.xs\:bui-mb-6{margin-bottom:var(--bui-space-6)}.xs\:bui-mb-7{margin-bottom:var(--bui-space-7)}.xs\:bui-mb-8{margin-bottom:var(--bui-space-8)}.xs\:bui-mb-9{margin-bottom:var(--bui-space-9)}.xs\:bui-mb-10{margin-bottom:var(--bui-space-10)}.xs\:bui-mb-11{margin-bottom:var(--bui-space-11)}.xs\:bui-mb-12{margin-bottom:var(--bui-space-12)}.xs\:bui-mb-13{margin-bottom:var(--bui-space-13)}.xs\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mb{margin-bottom:var(--mb-sm)}.sm\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.sm\:bui-mb-1{margin-bottom:var(--bui-space-1)}.sm\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.sm\:bui-mb-2{margin-bottom:var(--bui-space-2)}.sm\:bui-mb-3{margin-bottom:var(--bui-space-3)}.sm\:bui-mb-4{margin-bottom:var(--bui-space-4)}.sm\:bui-mb-5{margin-bottom:var(--bui-space-5)}.sm\:bui-mb-6{margin-bottom:var(--bui-space-6)}.sm\:bui-mb-7{margin-bottom:var(--bui-space-7)}.sm\:bui-mb-8{margin-bottom:var(--bui-space-8)}.sm\:bui-mb-9{margin-bottom:var(--bui-space-9)}.sm\:bui-mb-10{margin-bottom:var(--bui-space-10)}.sm\:bui-mb-11{margin-bottom:var(--bui-space-11)}.sm\:bui-mb-12{margin-bottom:var(--bui-space-12)}.sm\:bui-mb-13{margin-bottom:var(--bui-space-13)}.sm\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mb{margin-bottom:var(--mb-md)}.md\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.md\:bui-mb-1{margin-bottom:var(--bui-space-1)}.md\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.md\:bui-mb-2{margin-bottom:var(--bui-space-2)}.md\:bui-mb-3{margin-bottom:var(--bui-space-3)}.md\:bui-mb-4{margin-bottom:var(--bui-space-4)}.md\:bui-mb-5{margin-bottom:var(--bui-space-5)}.md\:bui-mb-6{margin-bottom:var(--bui-space-6)}.md\:bui-mb-7{margin-bottom:var(--bui-space-7)}.md\:bui-mb-8{margin-bottom:var(--bui-space-8)}.md\:bui-mb-9{margin-bottom:var(--bui-space-9)}.md\:bui-mb-10{margin-bottom:var(--bui-space-10)}.md\:bui-mb-11{margin-bottom:var(--bui-space-11)}.md\:bui-mb-12{margin-bottom:var(--bui-space-12)}.md\:bui-mb-13{margin-bottom:var(--bui-space-13)}.md\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mb{margin-bottom:var(--mb-lg)}.lg\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.lg\:bui-mb-1{margin-bottom:var(--bui-space-1)}.lg\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.lg\:bui-mb-2{margin-bottom:var(--bui-space-2)}.lg\:bui-mb-3{margin-bottom:var(--bui-space-3)}.lg\:bui-mb-4{margin-bottom:var(--bui-space-4)}.lg\:bui-mb-5{margin-bottom:var(--bui-space-5)}.lg\:bui-mb-6{margin-bottom:var(--bui-space-6)}.lg\:bui-mb-7{margin-bottom:var(--bui-space-7)}.lg\:bui-mb-8{margin-bottom:var(--bui-space-8)}.lg\:bui-mb-9{margin-bottom:var(--bui-space-9)}.lg\:bui-mb-10{margin-bottom:var(--bui-space-10)}.lg\:bui-mb-11{margin-bottom:var(--bui-space-11)}.lg\:bui-mb-12{margin-bottom:var(--bui-space-12)}.lg\:bui-mb-13{margin-bottom:var(--bui-space-13)}.lg\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mb{margin-bottom:var(--mb-xl)}.xl\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.xl\:bui-mb-1{margin-bottom:var(--bui-space-1)}.xl\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.xl\:bui-mb-2{margin-bottom:var(--bui-space-2)}.xl\:bui-mb-3{margin-bottom:var(--bui-space-3)}.xl\:bui-mb-4{margin-bottom:var(--bui-space-4)}.xl\:bui-mb-5{margin-bottom:var(--bui-space-5)}.xl\:bui-mb-6{margin-bottom:var(--bui-space-6)}.xl\:bui-mb-7{margin-bottom:var(--bui-space-7)}.xl\:bui-mb-8{margin-bottom:var(--bui-space-8)}.xl\:bui-mb-9{margin-bottom:var(--bui-space-9)}.xl\:bui-mb-10{margin-bottom:var(--bui-space-10)}.xl\:bui-mb-11{margin-bottom:var(--bui-space-11)}.xl\:bui-mb-12{margin-bottom:var(--bui-space-12)}.xl\:bui-mb-13{margin-bottom:var(--bui-space-13)}.xl\:bui-mb-14{margin-bottom:var(--bui-space-14)}}.bui-my{margin-top:var(--my);margin-bottom:var(--my)}.bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-my{margin-top:var(--my-xs);margin-bottom:var(--my-xs)}.xs\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.xs\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.xs\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.xs\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.xs\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.xs\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.xs\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.xs\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.xs\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.xs\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.xs\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.xs\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.xs\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.xs\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.xs\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.xs\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-my{margin-top:var(--my-sm);margin-bottom:var(--my-sm)}.sm\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.sm\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.sm\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.sm\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.sm\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.sm\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.sm\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.sm\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.sm\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.sm\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.sm\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.sm\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.sm\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.sm\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.sm\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.sm\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-my{margin-top:var(--my-md);margin-bottom:var(--my-md)}.md\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.md\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.md\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.md\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.md\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.md\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.md\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.md\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.md\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.md\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.md\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.md\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.md\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.md\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.md\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.md\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-my{margin-top:var(--my-lg);margin-bottom:var(--my-lg)}.lg\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.lg\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.lg\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.lg\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.lg\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.lg\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.lg\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.lg\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.lg\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.lg\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.lg\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.lg\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.lg\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.lg\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.lg\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.lg\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-my{margin-top:var(--my-xl);margin-bottom:var(--my-xl)}.xl\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.xl\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.xl\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.xl\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.xl\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.xl\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.xl\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.xl\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.xl\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.xl\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.xl\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.xl\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.xl\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.xl\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.xl\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.xl\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}.bui-mx{margin-left:var(--mx);margin-right:var(--mx)}.bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mx{margin-left:var(--mx-xs);margin-right:var(--mx-xs)}.xs\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.xs\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.xs\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.xs\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.xs\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.xs\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.xs\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.xs\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.xs\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.xs\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.xs\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.xs\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.xs\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.xs\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.xs\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.xs\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mx{margin-left:var(--mx-sm);margin-right:var(--mx-sm)}.sm\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.sm\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.sm\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.sm\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.sm\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.sm\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.sm\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.sm\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.sm\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.sm\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.sm\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.sm\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.sm\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.sm\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.sm\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.sm\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mx{margin-left:var(--mx-md);margin-right:var(--mx-md)}.md\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.md\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.md\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.md\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.md\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.md\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.md\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.md\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.md\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.md\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.md\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.md\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.md\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.md\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.md\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.md\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mx{margin-left:var(--mx-lg);margin-right:var(--mx-lg)}.lg\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.lg\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.lg\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.lg\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.lg\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.lg\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.lg\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.lg\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.lg\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.lg\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.lg\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.lg\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.lg\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.lg\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.lg\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.lg\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mx{margin-left:var(--mx-xl);margin-right:var(--mx-xl)}.xl\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.xl\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.xl\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.xl\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.xl\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.xl\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.xl\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.xl\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.xl\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.xl\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.xl\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.xl\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.xl\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.xl\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.xl\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.xl\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}.bui-display-none{display:none}.bui-display-inline{display:inline}.bui-display-inline-block{display:inline-block}.bui-display-block{display:block}@media (width>=640px){.xs\:bui-display-none{display:none}.xs\:bui-display-inline{display:inline}.xs\:bui-display-inline-block{display:inline-block}.xs\:bui-display-block{display:block}}@media (width>=768px){.sm\:bui-display-none{display:none}.sm\:bui-display-inline{display:inline}.sm\:bui-display-inline-block{display:inline-block}.sm\:bui-display-block{display:block}}@media (width>=1024px){.md\:bui-display-none{display:none}.md\:bui-display-inline{display:inline}.md\:bui-display-inline-block{display:inline-block}.md\:bui-display-block{display:block}}@media (width>=1280px){.lg\:bui-display-none{display:none}.lg\:bui-display-inline{display:inline}.lg\:bui-display-inline-block{display:inline-block}.lg\:bui-display-block{display:block}}@media (width>=1536px){.xl\:bui-display-none{display:none}.xl\:bui-display-inline{display:inline}.xl\:bui-display-inline-block{display:inline-block}.xl\:bui-display-block{display:block}}.bui-w{width:var(--width)}.bui-min-w{min-width:var(--min-width)}.bui-max-w{max-width:var(--max-width)}@media (width>=640px){.xs\:bui-w{width:var(--width)}.xs\:bui-min-w{min-width:var(--min-width)}.xs\:bui-max-w{max-width:var(--max-width)}}@media (width>=768px){.sm\:bui-w{width:var(--width)}.sm\:bui-min-w{min-width:var(--min-width)}.sm\:bui-max-w{max-width:var(--max-width)}}@media (width>=1024px){.md\:bui-w{width:var(--width)}.md\:bui-min-w{min-width:var(--min-width)}.md\:bui-max-w{max-width:var(--max-width)}}@media (width>=1280px){.lg\:bui-w{width:var(--width)}.lg\:bui-min-w{min-width:var(--min-width)}.lg\:bui-max-w{max-width:var(--max-width)}}@media (width>=1536px){.xl\:bui-w{width:var(--width)}.xl\:bui-min-w{min-width:var(--min-width)}.xl\:bui-max-w{max-width:var(--max-width)}}.bui-h{height:var(--height)}.bui-min-h{min-height:var(--min-height)}.bui-max-h{max-height:var(--max-height)}@media (width>=640px){.xs\:bui-h{height:var(--height)}.xs\:bui-min-h{min-height:var(--min-height)}.xs\:bui-max-h{max-height:var(--max-height)}}@media (width>=768px){.sm\:bui-h{height:var(--height)}.sm\:bui-min-h{min-height:var(--min-height)}.sm\:bui-max-h{max-height:var(--max-height)}}@media (width>=1024px){.md\:bui-h{height:var(--height)}.md\:bui-min-h{min-height:var(--min-height)}.md\:bui-max-h{max-height:var(--max-height)}}@media (width>=1280px){.lg\:bui-h{height:var(--height)}.lg\:bui-min-h{min-height:var(--min-height)}.lg\:bui-max-h{max-height:var(--max-height)}}@media (width>=1536px){.xl\:bui-h{height:var(--height)}.xl\:bui-min-h{min-height:var(--min-height)}.xl\:bui-max-h{max-height:var(--max-height)}}.bui-position-absolute{position:absolute}.bui-position-fixed{position:fixed}.bui-position-sticky{position:sticky}.bui-position-relative{position:relative}.bui-position-static{position:static}@media (width>=640px){.xs\:bui-position-absolute{position:absolute}.xs\:bui-position-fixed{position:fixed}.xs\:bui-position-sticky{position:sticky}.xs\:bui-position-relative{position:relative}.xs\:bui-position-static{position:static}}@media (width>=768px){.sm\:bui-position-absolute{position:absolute}.sm\:bui-position-fixed{position:fixed}.sm\:bui-position-sticky{position:sticky}.sm\:bui-position-relative{position:relative}.sm\:bui-position-static{position:static}}@media (width>=1024px){.md\:bui-position-absolute{position:absolute}.md\:bui-position-fixed{position:fixed}.md\:bui-position-sticky{position:sticky}.md\:bui-position-relative{position:relative}.md\:bui-position-static{position:static}}@media (width>=1280px){.lg\:bui-position-absolute{position:absolute}.lg\:bui-position-fixed{position:fixed}.lg\:bui-position-sticky{position:sticky}.lg\:bui-position-relative{position:relative}.lg\:bui-position-static{position:static}}@media (width>=1536px){.xl\:bui-position-absolute{position:absolute}.xl\:bui-position-fixed{position:fixed}.xl\:bui-position-sticky{position:sticky}.xl\:bui-position-relative{position:relative}.xl\:bui-position-static{position:static}}.bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.bui-col-span-1{grid-column:span 1/span 1}.bui-col-span-2{grid-column:span 2/span 2}.bui-col-span-3{grid-column:span 3/span 3}.bui-col-span-4{grid-column:span 4/span 4}.bui-col-span-5{grid-column:span 5/span 5}.bui-col-span-6{grid-column:span 6/span 6}.bui-col-span-7{grid-column:span 7/span 7}.bui-col-span-8{grid-column:span 8/span 8}.bui-col-span-9{grid-column:span 9/span 9}.bui-col-span-10{grid-column:span 10/span 10}.bui-col-span-11{grid-column:span 11/span 11}.bui-col-span-12{grid-column:span 12/span 12}.bui-col-span-auto{grid-column:span auto/span auto}.bui-col-start-1{grid-column-start:1}.bui-col-start-2{grid-column-start:2}.bui-col-start-3{grid-column-start:3}.bui-col-start-4{grid-column-start:4}.bui-col-start-5{grid-column-start:5}.bui-col-start-6{grid-column-start:6}.bui-col-start-7{grid-column-start:7}.bui-col-start-8{grid-column-start:8}.bui-col-start-9{grid-column-start:9}.bui-col-start-10{grid-column-start:10}.bui-col-start-11{grid-column-start:11}.bui-col-start-12{grid-column-start:12}.bui-col-start-13{grid-column-start:13}.bui-col-start-auto{grid-column-start:auto}.bui-col-end-1{grid-column-end:1}.bui-col-end-2{grid-column-end:2}.bui-col-end-3{grid-column-end:3}.bui-col-end-4{grid-column-end:4}.bui-col-end-5{grid-column-end:5}.bui-col-end-6{grid-column-end:6}.bui-col-end-7{grid-column-end:7}.bui-col-end-8{grid-column-end:8}.bui-col-end-9{grid-column-end:9}.bui-col-end-10{grid-column-end:10}.bui-col-end-11{grid-column-end:11}.bui-col-end-12{grid-column-end:12}.bui-col-end-13{grid-column-end:13}.bui-col-end-auto{grid-column-end:auto}.bui-row-span-1{grid-row:span 1/span 1}.bui-row-span-2{grid-row:span 2/span 2}.bui-row-span-3{grid-row:span 3/span 3}.bui-row-span-4{grid-row:span 4/span 4}.bui-row-span-5{grid-row:span 5/span 5}.bui-row-span-6{grid-row:span 6/span 6}.bui-row-span-7{grid-row:span 7/span 7}.bui-row-span-8{grid-row:span 8/span 8}.bui-row-span-9{grid-row:span 9/span 9}.bui-row-span-10{grid-row:span 10/span 10}.bui-row-span-11{grid-row:span 11/span 11}.bui-row-span-12{grid-row:span 12/span 12}.bui-row-span-auto{grid-row:span auto/span auto}@media (width>=640px){.xs\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xs\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xs\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xs\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xs\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xs\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xs\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.xs\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.xs\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.xs\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xs\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.xs\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xs\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.xs\:bui-col-span-1{grid-column:span 1/span 1}.xs\:bui-col-span-2{grid-column:span 2/span 2}.xs\:bui-col-span-3{grid-column:span 3/span 3}.xs\:bui-col-span-4{grid-column:span 4/span 4}.xs\:bui-col-span-5{grid-column:span 5/span 5}.xs\:bui-col-span-6{grid-column:span 6/span 6}.xs\:bui-col-span-7{grid-column:span 7/span 7}.xs\:bui-col-span-8{grid-column:span 8/span 8}.xs\:bui-col-span-9{grid-column:span 9/span 9}.xs\:bui-col-span-10{grid-column:span 10/span 10}.xs\:bui-col-span-11{grid-column:span 11/span 11}.xs\:bui-col-span-12{grid-column:span 12/span 12}.xs\:bui-col-span-auto{grid-column:span auto/span auto}.xs\:bui-col-start-1{grid-column-start:1}.xs\:bui-col-start-2{grid-column-start:2}.xs\:bui-col-start-3{grid-column-start:3}.xs\:bui-col-start-4{grid-column-start:4}.xs\:bui-col-start-5{grid-column-start:5}.xs\:bui-col-start-6{grid-column-start:6}.xs\:bui-col-start-7{grid-column-start:7}.xs\:bui-col-start-8{grid-column-start:8}.xs\:bui-col-start-9{grid-column-start:9}.xs\:bui-col-start-10{grid-column-start:10}.xs\:bui-col-start-11{grid-column-start:11}.xs\:bui-col-start-12{grid-column-start:12}.xs\:bui-col-start-13{grid-column-start:13}.xs\:bui-col-start-auto{grid-column-start:auto}.xs\:bui-col-end-1{grid-column-end:1}.xs\:bui-col-end-2{grid-column-end:2}.xs\:bui-col-end-3{grid-column-end:3}.xs\:bui-col-end-4{grid-column-end:4}.xs\:bui-col-end-5{grid-column-end:5}.xs\:bui-col-end-6{grid-column-end:6}.xs\:bui-col-end-7{grid-column-end:7}.xs\:bui-col-end-8{grid-column-end:8}.xs\:bui-col-end-9{grid-column-end:9}.xs\:bui-col-end-10{grid-column-end:10}.xs\:bui-col-end-11{grid-column-end:11}.xs\:bui-col-end-12{grid-column-end:12}.xs\:bui-col-end-13{grid-column-end:13}.xs\:bui-col-end-auto{grid-column-end:auto}.xs\:bui-row-span-1{grid-row:span 1/span 1}.xs\:bui-row-span-2{grid-row:span 2/span 2}.xs\:bui-row-span-3{grid-row:span 3/span 3}.xs\:bui-row-span-4{grid-row:span 4/span 4}.xs\:bui-row-span-5{grid-row:span 5/span 5}.xs\:bui-row-span-6{grid-row:span 6/span 6}.xs\:bui-row-span-7{grid-row:span 7/span 7}.xs\:bui-row-span-8{grid-row:span 8/span 8}.xs\:bui-row-span-9{grid-row:span 9/span 9}.xs\:bui-row-span-10{grid-row:span 10/span 10}.xs\:bui-row-span-11{grid-row:span 11/span 11}.xs\:bui-row-span-12{grid-row:span 12/span 12}.xs\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=768px){.sm\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.sm\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.sm\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.sm\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.sm\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.sm\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.sm\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.sm\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.sm\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.sm\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.sm\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.sm\:bui-col-span-1{grid-column:span 1/span 1}.sm\:bui-col-span-2{grid-column:span 2/span 2}.sm\:bui-col-span-3{grid-column:span 3/span 3}.sm\:bui-col-span-4{grid-column:span 4/span 4}.sm\:bui-col-span-5{grid-column:span 5/span 5}.sm\:bui-col-span-6{grid-column:span 6/span 6}.sm\:bui-col-span-7{grid-column:span 7/span 7}.sm\:bui-col-span-8{grid-column:span 8/span 8}.sm\:bui-col-span-9{grid-column:span 9/span 9}.sm\:bui-col-span-10{grid-column:span 10/span 10}.sm\:bui-col-span-11{grid-column:span 11/span 11}.sm\:bui-col-span-12{grid-column:span 12/span 12}.sm\:bui-col-span-auto{grid-column:span auto/span auto}.sm\:bui-col-start-1{grid-column-start:1}.sm\:bui-col-start-2{grid-column-start:2}.sm\:bui-col-start-3{grid-column-start:3}.sm\:bui-col-start-4{grid-column-start:4}.sm\:bui-col-start-5{grid-column-start:5}.sm\:bui-col-start-6{grid-column-start:6}.sm\:bui-col-start-7{grid-column-start:7}.sm\:bui-col-start-8{grid-column-start:8}.sm\:bui-col-start-9{grid-column-start:9}.sm\:bui-col-start-10{grid-column-start:10}.sm\:bui-col-start-11{grid-column-start:11}.sm\:bui-col-start-12{grid-column-start:12}.sm\:bui-col-start-13{grid-column-start:13}.sm\:bui-col-start-auto{grid-column-start:auto}.sm\:bui-col-end-1{grid-column-end:1}.sm\:bui-col-end-2{grid-column-end:2}.sm\:bui-col-end-3{grid-column-end:3}.sm\:bui-col-end-4{grid-column-end:4}.sm\:bui-col-end-5{grid-column-end:5}.sm\:bui-col-end-6{grid-column-end:6}.sm\:bui-col-end-7{grid-column-end:7}.sm\:bui-col-end-8{grid-column-end:8}.sm\:bui-col-end-9{grid-column-end:9}.sm\:bui-col-end-10{grid-column-end:10}.sm\:bui-col-end-11{grid-column-end:11}.sm\:bui-col-end-12{grid-column-end:12}.sm\:bui-col-end-13{grid-column-end:13}.sm\:bui-col-end-auto{grid-column-end:auto}.sm\:bui-row-span-1{grid-row:span 1/span 1}.sm\:bui-row-span-2{grid-row:span 2/span 2}.sm\:bui-row-span-3{grid-row:span 3/span 3}.sm\:bui-row-span-4{grid-row:span 4/span 4}.sm\:bui-row-span-5{grid-row:span 5/span 5}.sm\:bui-row-span-6{grid-row:span 6/span 6}.sm\:bui-row-span-7{grid-row:span 7/span 7}.sm\:bui-row-span-8{grid-row:span 8/span 8}.sm\:bui-row-span-9{grid-row:span 9/span 9}.sm\:bui-row-span-10{grid-row:span 10/span 10}.sm\:bui-row-span-11{grid-row:span 11/span 11}.sm\:bui-row-span-12{grid-row:span 12/span 12}.sm\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1024px){.md\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.md\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.md\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.md\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.md\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.md\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.md\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.md\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.md\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.md\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.md\:bui-col-span-1{grid-column:span 1/span 1}.md\:bui-col-span-2{grid-column:span 2/span 2}.md\:bui-col-span-3{grid-column:span 3/span 3}.md\:bui-col-span-4{grid-column:span 4/span 4}.md\:bui-col-span-5{grid-column:span 5/span 5}.md\:bui-col-span-6{grid-column:span 6/span 6}.md\:bui-col-span-7{grid-column:span 7/span 7}.md\:bui-col-span-8{grid-column:span 8/span 8}.md\:bui-col-span-9{grid-column:span 9/span 9}.md\:bui-col-span-10{grid-column:span 10/span 10}.md\:bui-col-span-11{grid-column:span 11/span 11}.md\:bui-col-span-12{grid-column:span 12/span 12}.md\:bui-col-span-auto{grid-column:span auto/span auto}.md\:bui-col-start-1{grid-column-start:1}.md\:bui-col-start-2{grid-column-start:2}.md\:bui-col-start-3{grid-column-start:3}.md\:bui-col-start-4{grid-column-start:4}.md\:bui-col-start-5{grid-column-start:5}.md\:bui-col-start-6{grid-column-start:6}.md\:bui-col-start-7{grid-column-start:7}.md\:bui-col-start-8{grid-column-start:8}.md\:bui-col-start-9{grid-column-start:9}.md\:bui-col-start-10{grid-column-start:10}.md\:bui-col-start-11{grid-column-start:11}.md\:bui-col-start-12{grid-column-start:12}.md\:bui-col-start-13{grid-column-start:13}.md\:bui-col-start-auto{grid-column-start:auto}.md\:bui-col-end-1{grid-column-end:1}.md\:bui-col-end-2{grid-column-end:2}.md\:bui-col-end-3{grid-column-end:3}.md\:bui-col-end-4{grid-column-end:4}.md\:bui-col-end-5{grid-column-end:5}.md\:bui-col-end-6{grid-column-end:6}.md\:bui-col-end-7{grid-column-end:7}.md\:bui-col-end-8{grid-column-end:8}.md\:bui-col-end-9{grid-column-end:9}.md\:bui-col-end-10{grid-column-end:10}.md\:bui-col-end-11{grid-column-end:11}.md\:bui-col-end-12{grid-column-end:12}.md\:bui-col-end-13{grid-column-end:13}.md\:bui-col-end-auto{grid-column-end:auto}.md\:bui-row-span-1{grid-row:span 1/span 1}.md\:bui-row-span-2{grid-row:span 2/span 2}.md\:bui-row-span-3{grid-row:span 3/span 3}.md\:bui-row-span-4{grid-row:span 4/span 4}.md\:bui-row-span-5{grid-row:span 5/span 5}.md\:bui-row-span-6{grid-row:span 6/span 6}.md\:bui-row-span-7{grid-row:span 7/span 7}.md\:bui-row-span-8{grid-row:span 8/span 8}.md\:bui-row-span-9{grid-row:span 9/span 9}.md\:bui-row-span-10{grid-row:span 10/span 10}.md\:bui-row-span-11{grid-row:span 11/span 11}.md\:bui-row-span-12{grid-row:span 12/span 12}.md\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1280px){.lg\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.lg\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.lg\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.lg\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.lg\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.lg\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.lg\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.lg\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.lg\:bui-col-span-1{grid-column:span 1/span 1}.lg\:bui-col-span-2{grid-column:span 2/span 2}.lg\:bui-col-span-3{grid-column:span 3/span 3}.lg\:bui-col-span-4{grid-column:span 4/span 4}.lg\:bui-col-span-5{grid-column:span 5/span 5}.lg\:bui-col-span-6{grid-column:span 6/span 6}.lg\:bui-col-span-7{grid-column:span 7/span 7}.lg\:bui-col-span-8{grid-column:span 8/span 8}.lg\:bui-col-span-9{grid-column:span 9/span 9}.lg\:bui-col-span-10{grid-column:span 10/span 10}.lg\:bui-col-span-11{grid-column:span 11/span 11}.lg\:bui-col-span-12{grid-column:span 12/span 12}.lg\:bui-col-span-auto{grid-column:span auto/span auto}.lg\:bui-col-start-1{grid-column-start:1}.lg\:bui-col-start-2{grid-column-start:2}.lg\:bui-col-start-3{grid-column-start:3}.lg\:bui-col-start-4{grid-column-start:4}.lg\:bui-col-start-5{grid-column-start:5}.lg\:bui-col-start-6{grid-column-start:6}.lg\:bui-col-start-7{grid-column-start:7}.lg\:bui-col-start-8{grid-column-start:8}.lg\:bui-col-start-9{grid-column-start:9}.lg\:bui-col-start-10{grid-column-start:10}.lg\:bui-col-start-11{grid-column-start:11}.lg\:bui-col-start-12{grid-column-start:12}.lg\:bui-col-start-13{grid-column-start:13}.lg\:bui-col-start-auto{grid-column-start:auto}.lg\:bui-col-end-1{grid-column-end:1}.lg\:bui-col-end-2{grid-column-end:2}.lg\:bui-col-end-3{grid-column-end:3}.lg\:bui-col-end-4{grid-column-end:4}.lg\:bui-col-end-5{grid-column-end:5}.lg\:bui-col-end-6{grid-column-end:6}.lg\:bui-col-end-7{grid-column-end:7}.lg\:bui-col-end-8{grid-column-end:8}.lg\:bui-col-end-9{grid-column-end:9}.lg\:bui-col-end-10{grid-column-end:10}.lg\:bui-col-end-11{grid-column-end:11}.lg\:bui-col-end-12{grid-column-end:12}.lg\:bui-col-end-13{grid-column-end:13}.lg\:bui-col-end-auto{grid-column-end:auto}.lg\:bui-row-span-1{grid-row:span 1/span 1}.lg\:bui-row-span-2{grid-row:span 2/span 2}.lg\:bui-row-span-3{grid-row:span 3/span 3}.lg\:bui-row-span-4{grid-row:span 4/span 4}.lg\:bui-row-span-5{grid-row:span 5/span 5}.lg\:bui-row-span-6{grid-row:span 6/span 6}.lg\:bui-row-span-7{grid-row:span 7/span 7}.lg\:bui-row-span-8{grid-row:span 8/span 8}.lg\:bui-row-span-9{grid-row:span 9/span 9}.lg\:bui-row-span-10{grid-row:span 10/span 10}.lg\:bui-row-span-11{grid-row:span 11/span 11}.lg\:bui-row-span-12{grid-row:span 12/span 12}.lg\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1536px){.xl\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xl\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xl\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xl\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xl\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xl\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xl\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.xl\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.xl\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.xl\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.xl\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xl\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.xl\:bui-col-span-1{grid-column:span 1/span 1}.xl\:bui-col-span-2{grid-column:span 2/span 2}.xl\:bui-col-span-3{grid-column:span 3/span 3}.xl\:bui-col-span-4{grid-column:span 4/span 4}.xl\:bui-col-span-5{grid-column:span 5/span 5}.xl\:bui-col-span-6{grid-column:span 6/span 6}.xl\:bui-col-span-7{grid-column:span 7/span 7}.xl\:bui-col-span-8{grid-column:span 8/span 8}.xl\:bui-col-span-9{grid-column:span 9/span 9}.xl\:bui-col-span-10{grid-column:span 10/span 10}.xl\:bui-col-span-11{grid-column:span 11/span 11}.xl\:bui-col-span-12{grid-column:span 12/span 12}.xl\:bui-col-span-auto{grid-column:span auto/span auto}.xl\:bui-col-start-1{grid-column-start:1}.xl\:bui-col-start-2{grid-column-start:2}.xl\:bui-col-start-3{grid-column-start:3}.xl\:bui-col-start-4{grid-column-start:4}.xl\:bui-col-start-5{grid-column-start:5}.xl\:bui-col-start-6{grid-column-start:6}.xl\:bui-col-start-7{grid-column-start:7}.xl\:bui-col-start-8{grid-column-start:8}.xl\:bui-col-start-9{grid-column-start:9}.xl\:bui-col-start-10{grid-column-start:10}.xl\:bui-col-start-11{grid-column-start:11}.xl\:bui-col-start-12{grid-column-start:12}.xl\:bui-col-start-13{grid-column-start:13}.xl\:bui-col-start-auto{grid-column-start:auto}.xl\:bui-col-end-1{grid-column-end:1}.xl\:bui-col-end-2{grid-column-end:2}.xl\:bui-col-end-3{grid-column-end:3}.xl\:bui-col-end-4{grid-column-end:4}.xl\:bui-col-end-5{grid-column-end:5}.xl\:bui-col-end-6{grid-column-end:6}.xl\:bui-col-end-7{grid-column-end:7}.xl\:bui-col-end-8{grid-column-end:8}.xl\:bui-col-end-9{grid-column-end:9}.xl\:bui-col-end-10{grid-column-end:10}.xl\:bui-col-end-11{grid-column-end:11}.xl\:bui-col-end-12{grid-column-end:12}.xl\:bui-col-end-13{grid-column-end:13}.xl\:bui-col-end-auto{grid-column-end:auto}.xl\:bui-row-span-1{grid-row:span 1/span 1}.xl\:bui-row-span-2{grid-row:span 2/span 2}.xl\:bui-row-span-3{grid-row:span 3/span 3}.xl\:bui-row-span-4{grid-row:span 4/span 4}.xl\:bui-row-span-5{grid-row:span 5/span 5}.xl\:bui-row-span-6{grid-row:span 6/span 6}.xl\:bui-row-span-7{grid-row:span 7/span 7}.xl\:bui-row-span-8{grid-row:span 8/span 8}.xl\:bui-row-span-9{grid-row:span 9/span 9}.xl\:bui-row-span-10{grid-row:span 10/span 10}.xl\:bui-row-span-11{grid-row:span 11/span 11}.xl\:bui-row-span-12{grid-row:span 12/span 12}.xl\:bui-row-span-auto{grid-row:span auto/span auto}}.bui-gap{gap:var(--gap)}.bui-gap-0\.5{gap:var(--bui-space-0_5)}.bui-gap-1{gap:var(--bui-space-1)}.bui-gap-1\.5{gap:var(--bui-space-1_5)}.bui-gap-2{gap:var(--bui-space-2)}.bui-gap-3{gap:var(--bui-space-3)}.bui-gap-4{gap:var(--bui-space-4)}.bui-gap-5{gap:var(--bui-space-5)}.bui-gap-6{gap:var(--bui-space-6)}.bui-gap-7{gap:var(--bui-space-7)}.bui-gap-8{gap:var(--bui-space-8)}.bui-gap-9{gap:var(--bui-space-9)}.bui-gap-10{gap:var(--bui-space-10)}.bui-gap-11{gap:var(--bui-space-11)}.bui-gap-12{gap:var(--bui-space-12)}.bui-gap-13{gap:var(--bui-space-13)}.bui-gap-14{gap:var(--bui-space-14)}@media (width>=640px){.xs\:bui-gap{gap:var(--gap-xs)}.xs\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.xs\:bui-gap-1{gap:var(--bui-space-1)}.xs\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.xs\:bui-gap-2{gap:var(--bui-space-2)}.xs\:bui-gap-3{gap:var(--bui-space-3)}.xs\:bui-gap-4{gap:var(--bui-space-4)}.xs\:bui-gap-5{gap:var(--bui-space-5)}.xs\:bui-gap-6{gap:var(--bui-space-6)}.xs\:bui-gap-7{gap:var(--bui-space-7)}.xs\:bui-gap-8{gap:var(--bui-space-8)}.xs\:bui-gap-9{gap:var(--bui-space-9)}.xs\:bui-gap-10{gap:var(--bui-space-10)}.xs\:bui-gap-11{gap:var(--bui-space-11)}.xs\:bui-gap-12{gap:var(--bui-space-12)}.xs\:bui-gap-13{gap:var(--bui-space-13)}.xs\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-gap{gap:var(--gap-sm)}.sm\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.sm\:bui-gap-1{gap:var(--bui-space-1)}.sm\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.sm\:bui-gap-2{gap:var(--bui-space-2)}.sm\:bui-gap-3{gap:var(--bui-space-3)}.sm\:bui-gap-4{gap:var(--bui-space-4)}.sm\:bui-gap-5{gap:var(--bui-space-5)}.sm\:bui-gap-6{gap:var(--bui-space-6)}.sm\:bui-gap-7{gap:var(--bui-space-7)}.sm\:bui-gap-8{gap:var(--bui-space-8)}.sm\:bui-gap-9{gap:var(--bui-space-9)}.sm\:bui-gap-10{gap:var(--bui-space-10)}.sm\:bui-gap-11{gap:var(--bui-space-11)}.sm\:bui-gap-12{gap:var(--bui-space-12)}.sm\:bui-gap-13{gap:var(--bui-space-13)}.sm\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-gap{gap:var(--gap-md)}.md\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.md\:bui-gap-1{gap:var(--bui-space-1)}.md\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.md\:bui-gap-2{gap:var(--bui-space-2)}.md\:bui-gap-3{gap:var(--bui-space-3)}.md\:bui-gap-4{gap:var(--bui-space-4)}.md\:bui-gap-5{gap:var(--bui-space-5)}.md\:bui-gap-6{gap:var(--bui-space-6)}.md\:bui-gap-7{gap:var(--bui-space-7)}.md\:bui-gap-8{gap:var(--bui-space-8)}.md\:bui-gap-9{gap:var(--bui-space-9)}.md\:bui-gap-10{gap:var(--bui-space-10)}.md\:bui-gap-11{gap:var(--bui-space-11)}.md\:bui-gap-12{gap:var(--bui-space-12)}.md\:bui-gap-13{gap:var(--bui-space-13)}.md\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-gap{gap:var(--gap-lg)}.lg\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.lg\:bui-gap-1{gap:var(--bui-space-1)}.lg\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.lg\:bui-gap-2{gap:var(--bui-space-2)}.lg\:bui-gap-3{gap:var(--bui-space-3)}.lg\:bui-gap-4{gap:var(--bui-space-4)}.lg\:bui-gap-5{gap:var(--bui-space-5)}.lg\:bui-gap-6{gap:var(--bui-space-6)}.lg\:bui-gap-7{gap:var(--bui-space-7)}.lg\:bui-gap-8{gap:var(--bui-space-8)}.lg\:bui-gap-9{gap:var(--bui-space-9)}.lg\:bui-gap-10{gap:var(--bui-space-10)}.lg\:bui-gap-11{gap:var(--bui-space-11)}.lg\:bui-gap-12{gap:var(--bui-space-12)}.lg\:bui-gap-13{gap:var(--bui-space-13)}.lg\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-gap{gap:var(--gap-xl)}.xl\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.xl\:bui-gap-1{gap:var(--bui-space-1)}.xl\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.xl\:bui-gap-2{gap:var(--bui-space-2)}.xl\:bui-gap-3{gap:var(--bui-space-3)}.xl\:bui-gap-4{gap:var(--bui-space-4)}.xl\:bui-gap-5{gap:var(--bui-space-5)}.xl\:bui-gap-6{gap:var(--bui-space-6)}.xl\:bui-gap-7{gap:var(--bui-space-7)}.xl\:bui-gap-8{gap:var(--bui-space-8)}.xl\:bui-gap-9{gap:var(--bui-space-9)}.xl\:bui-gap-10{gap:var(--bui-space-10)}.xl\:bui-gap-11{gap:var(--bui-space-11)}.xl\:bui-gap-12{gap:var(--bui-space-12)}.xl\:bui-gap-13{gap:var(--bui-space-13)}.xl\:bui-gap-14{gap:var(--bui-space-14)}}.bui-align-start{align-items:start}.bui-align-center{align-items:center}.bui-align-end{align-items:end}.bui-align-baseline{align-items:baseline}.bui-align-stretch{align-items:stretch}.bui-jc-start{justify-content:start}.bui-jc-center{justify-content:center}.bui-jc-end{justify-content:end}.bui-jc-between{justify-content:space-between}.bui-fd-row{flex-direction:row}.bui-fd-column{flex-direction:column}.bui-fd-row-reverse{flex-direction:row-reverse}.bui-fd-column-reverse{flex-direction:column-reverse}@media (width>=640px){.xs\:bui-align-start{align-items:start}.xs\:bui-align-center{align-items:center}.xs\:bui-align-end{align-items:end}.xs\:bui-align-stretch{align-items:stretch}.xs\:bui-jc-start{justify-content:start}.xs\:bui-jc-center{justify-content:center}.xs\:bui-jc-end{justify-content:end}.xs\:bui-jc-between{justify-content:space-between}.xs\:bui-fd-row{flex-direction:row}.xs\:bui-fd-column{flex-direction:column}.xs\:bui-fd-row-reverse{flex-direction:row-reverse}.xs\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=768px){.sm\:bui-align-start{align-items:start}.sm\:bui-align-center{align-items:center}.sm\:bui-align-end{align-items:end}.sm\:bui-align-stretch{align-items:stretch}.sm\:bui-jc-start{justify-content:start}.sm\:bui-jc-center{justify-content:center}.sm\:bui-jc-end{justify-content:end}.sm\:bui-jc-between{justify-content:space-between}.sm\:bui-fd-row{flex-direction:row}.sm\:bui-fd-column{flex-direction:column}.sm\:bui-fd-row-reverse{flex-direction:row-reverse}.sm\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1024px){.md\:bui-align-start{align-items:start}.md\:bui-align-center{align-items:center}.md\:bui-align-end{align-items:end}.md\:bui-align-stretch{align-items:stretch}.md\:bui-jc-start{justify-content:start}.md\:bui-jc-center{justify-content:center}.md\:bui-jc-end{justify-content:end}.md\:bui-jc-between{justify-content:space-between}.md\:bui-fd-row{flex-direction:row}.md\:bui-fd-column{flex-direction:column}.md\:bui-fd-row-reverse{flex-direction:row-reverse}.md\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1280px){.lg\:bui-align-start{align-items:start}.lg\:bui-align-center{align-items:center}.lg\:bui-align-end{align-items:end}.lg\:bui-align-stretch{align-items:stretch}.lg\:bui-jc-start{justify-content:start}.lg\:bui-jc-center{justify-content:center}.lg\:bui-jc-end{justify-content:end}.lg\:bui-jc-between{justify-content:space-between}.lg\:bui-fd-row{flex-direction:row}.lg\:bui-fd-column{flex-direction:column}.lg\:bui-fd-row-reverse{flex-direction:row-reverse}.lg\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1536px){.xl\:bui-align-start{align-items:start}.xl\:bui-align-center{align-items:center}.xl\:bui-align-end{align-items:end}.xl\:bui-align-stretch{align-items:stretch}.xl\:bui-jc-start{justify-content:start}.xl\:bui-jc-center{justify-content:center}.xl\:bui-jc-end{justify-content:end}.xl\:bui-jc-between{justify-content:space-between}.xl\:bui-fd-row{flex-direction:row}.xl\:bui-fd-column{flex-direction:column}.xl\:bui-fd-row-reverse{flex-direction:row-reverse}.xl\:bui-fd-column-reverse{flex-direction:column-reverse}}} \ No newline at end of file diff --git a/docs-ui/public/theme-spotify.css b/docs-ui/public/theme-spotify.css deleted file mode 100644 index b0bf517701..0000000000 --- a/docs-ui/public/theme-spotify.css +++ /dev/null @@ -1 +0,0 @@ -@font-face{font-family:CircularSpTitle;font-weight:900;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSpTitle-Black-4588c99025b967475c31695b0743dd1a.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSpTitle-Black-506746f387a26f25aa3d023b3e501d34.woff)format("woff")}@font-face{font-family:CircularSpTitle;font-weight:700;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSpTitle-Bold-b2586b06a2e1522e9d879d84c2792a58.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSpTitle-Bold-1624fb2df28c20d7203d7fb86ce8b481.woff)format("woff")}@font-face{font-family:CircularSp;font-weight:700;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSp-Bold-602e7aefc706aa36c6ec1324b9bbc461.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSp-Bold-856afe2da4ba4e61239b129e2c16d633.woff)format("woff")}@font-face{font-family:CircularSp;font-weight:400;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSp-Book-a00e99ef9996a3a157fb6b746856d04f.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSp-Book-3f73da7d35bd81c706bce7bbb84964de.woff)format("woff")}@font-face{font-family:CircularSp;font-weight:700;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSp-Bold-602e7aefc706aa36c6ec1324b9bbc461.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSp-Bold-856afe2da4ba4e61239b129e2c16d633.woff)format("woff")}[data-theme-name=spotify]{--bui-font-text:CircularSp,CircularSp-Arab,CircularSp-Hebr,CircularSp-Cyrl,CircularSp-Grek,CircularSp-Deva;--bui-font-title:CircularSpTitle,CircularSp-Arab,CircularSp-Hebr,CircularSp-Cyrl,CircularSp-Grek,CircularSp-Deva;--bui-font-regular:CircularSp,CircularSp-Arab,CircularSp-Hebr,CircularSp-Cyrl,CircularSp-Grek,CircularSp-Deva;& .bui-Button{border-radius:var(--bui-radius-3);padding-inline:var(--bui-space-3)}& .bui-ButtonIcon{padding:0}& .bui-MenuPopup{box-sizing:border-box;max-width:21.25rem}& .bui-MenuSubmenuTrigger,& .bui-MenuItem{height:auto;min-height:2rem}& .bui-Text{font-family:var(--bui-font-text)}& .bui-Heading{font-family:var(--bui-font-title)}& .bui-TableRow{border-radius:var(--bui-radius-4);border:none}& .bui-TableRow:hover td:first-child{border-top-left-radius:var(--bui-radius-2);border-bottom-left-radius:var(--bui-radius-2)}& .bui-TableRow:hover td:last-child{border-top-right-radius:var(--bui-radius-2);border-bottom-right-radius:var(--bui-radius-2)}& .bui-TableBody:before,& .bui-TableBody:after{line-height:var(--bui-space-1);content:"‌";display:block}& .bui-TableHeader .bui-TableRow{border-bottom:1px solid var(--bui-border)}& .bui-TableHead{font-size:var(--bui-font-size-2);color:var(--bui-fg-secondary);font-weight:var(--bui-font-weight-regular)}& .bui-HeaderToolbar{padding-top:var(--bui-space-2);padding-inline:var(--bui-space-2)}& .bui-HeaderToolbarWrapper{border-radius:var(--bui-radius-3);padding-inline:var(--bui-space-3);border:none}& .bui-HeaderToolbarControls{right:calc(var(--bui-space-3) - 1px)}& .bui-HeaderTabsWrapper{margin-top:var(--bui-space-2);margin-inline:var(--bui-space-2);border-radius:var(--bui-radius-3);padding-inline:var(--bui-space-1);border:none}& .bui-Input{border-radius:var(--bui-radius-3)}& .bui-Tag{border-radius:var(--bui-radius-full)}}[data-theme-mode=light][data-theme-name=spotify]{--bui-bg:var(--bui-gray-1);--bui-bg-surface-1:var(--bui-white);--bui-bg-surface-2:var(--bui-gray-2);--bui-bg-solid:#1ed760;--bui-bg-solid-hover:#3be477;--bui-bg-solid-pressed:#1abc54;--bui-bg-solid-disabled:var(--bui-gray-2);--bui-fg-primary:var(--bui-black);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-solid:var(--bui-black);--bui-border:var(--bui-gray-3);--bui-border-hover:#0000004d;--bui-border-pressed:#00000080;--bui-border-disabled:#0000001a;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#0003;& .bui-HeaderToolbarWrapper,& .bui-HeaderTabsWrapper{border:1px solid var(--bui-border)}}[data-theme-mode=dark][data-theme-name=spotify]{--bui-bg:var(--bui-black);--bui-bg-surface-1:var(--bui-gray-1);--bui-bg-surface-2:var(--bui-gray-2);--bui-bg-solid:#1ed760;--bui-bg-solid-hover:#3be477;--bui-bg-solid-pressed:#1abc54;--bui-bg-solid-disabled:#0f6c30;--bui-bg-tint:#242424;--bui-bg-tint-hover:#202020;--bui-bg-tint-pressed:#292929;--bui-bg-tint-disabled:#fffc;--bui-bg-danger:#3b1219;--bui-bg-warning:#302008;--bui-bg-success:#132d21;--bui-fg-primary:var(--bui-white);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:var(--bui-white);--bui-fg-link-hover:var(--bui-white);--bui-fg-disabled:var(--bui-gray-5);--bui-fg-solid:var(--bui-black);--bui-fg-solid-disabled:#083618;--bui-fg-tint:var(--bui-white);--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#e22b2b;--bui-fg-warning:#e36d05;--bui-fg-success:#1db954;--bui-border:var(--bui-gray-3);--bui-border-hover:#fff6;--bui-border-pressed:#ffffff80;--bui-border-disabled:#fff3;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#fff3} \ No newline at end of file diff --git a/docs-ui/scripts/sync-changelog.mjs b/docs-ui/scripts/sync-changelog.mjs index 5eab3616dd..7b54c9e9fe 100644 --- a/docs-ui/scripts/sync-changelog.mjs +++ b/docs-ui/scripts/sync-changelog.mjs @@ -1,5 +1,4 @@ #!/usr/bin/env node -/* eslint-disable no-restricted-syntax */ import fs from 'fs'; import path from 'path'; @@ -13,22 +12,18 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); /** - * Compare two semantic versions. + * Compare two semantic versions for sorting. * Returns: 1 if versionA > versionB, -1 if versionA < versionB, 0 if equal - * Handles pre-release versions (e.g., "0.9.0-next.2") + * Example: compareVersions("0.11.0", "0.10.0") => 1 */ function compareVersions(versionA, versionB) { // Strip 'v' prefix if present versionA = versionA.replace(/^v/, ''); versionB = versionB.replace(/^v/, ''); - // Split into [major.minor.patch, prerelease] - const [versionABase, versionAPre] = versionA.split('-'); - const [versionBBase, versionBPre] = versionB.split('-'); - - // Compare base versions - const versionAParts = versionABase.split('.').map(Number); - const versionBParts = versionBBase.split('.').map(Number); + // Compare major.minor.patch versions + const versionAParts = versionA.split('.').map(Number); + const versionBParts = versionB.split('.').map(Number); for ( let i = 0; @@ -42,13 +37,7 @@ function compareVersions(versionA, versionB) { if (versionAPart < versionBPart) return -1; } - // If base versions equal, compare pre-release - if (!versionAPre && !versionBPre) return 0; - if (!versionAPre) return 1; // versionA is release, versionB is pre-release - if (!versionBPre) return -1; // versionA is pre-release, versionB is release - - // Both have pre-release, compare them - return versionAPre.localeCompare(versionBPre); + return 0; } /** @@ -58,15 +47,6 @@ function isNewerVersion(versionA, versionB) { return compareVersions(versionA, versionB) > 0; } -/** - * Normalize version to base version (remove pre-release suffix) - * "0.9.0-next.3" → "0.9.0" - * "0.9.0" → "0.9.0" - */ -function normalizeVersion(version) { - return version.split('-')[0]; -} - /** * Read the existing changelog.ts and find the highest version number * by scanning the changelogs/ directory for version files @@ -118,6 +98,8 @@ function pascalToKebab(str) { TextField: 'textfield', DataTable: 'datatable', ScrollArea: 'scrollarea', + 'Grid.Item': 'grid-item', + useTable: 'use-table', }; if (specialCases[str]) { @@ -169,6 +151,7 @@ function getValidComponents(changelogPath) { /** * Parse CHANGELOG.md and extract entries newer than sinceVersion + * Only processes main release versions (e.g., 0.11.0), skips pre-releases (e.g., 0.11.0-next.1) */ async function parseChangelogMd(changelogPath, sinceVersion, validComponents) { const content = fs.readFileSync(changelogPath, 'utf-8'); @@ -181,14 +164,17 @@ async function parseChangelogMd(changelogPath, sinceVersion, validComponents) { // Walk through the markdown AST async function walk(node, depth = 0) { - // Version headers (## 0.9.0) + // Version headers (## 0.11.0) if (node.type === 'heading' && node.depth === 2) { const versionText = extractText(node).trim(); - // Validate that this is actually a version number (X.Y.Z or X.Y.Z-pre.N) - // Skip headings that are just markdown content within changelog entries - if (/^\d+\.\d+\.\d+/.test(versionText)) { + // Only process main release versions (X.Y.Z format) + if (/^\d+\.\d+\.\d+$/.test(versionText)) { currentVersion = versionText; currentSection = null; + } else { + // Skip pre-release versions (e.g., 0.11.0-next.1) and non-version headings + currentVersion = null; + currentSection = null; } return; } @@ -301,65 +287,93 @@ async function parseListItem( // Remove the commit SHA prefix from markdown let description = fullMarkdown.replace(/^-?\s*[a-f0-9]+:\s*/, '').trim(); - // Auto-detect components from "Affected components:" line + // Extract components from "Affected components" line + // Supports: **Affected components:** X, **Affected components**: X, Affected components: X let components = []; - const componentMatch = description.match(/Affected components?:\s*([^\n]+)/i); + const unknownNames = []; + const affectedComponentsRe = /\*{2}Affected components?:?\*{2}:?\s*([^\n]+)/i; + const affectedComponentsPlainRe = /Affected components?:\s*([^\n]+)/i; + const componentMatch = + description.match(affectedComponentsRe) || + description.match(affectedComponentsPlainRe); if (componentMatch) { const componentNames = componentMatch[1] .split(',') - .map(name => name.trim()) + .map(name => name.trim().replace(/`/g, '')) .filter(Boolean); - components = componentNames - .map(name => mapComponentName(name, validComponents)) - .filter(Boolean); + componentNames.forEach(name => { + const mapped = mapComponentName(name, validComponents); + if (mapped) { + components.push(mapped); + } else { + unknownNames.push(name); + } + }); - // Optionally strip "Affected components:" line from description + // Deduplicate + components = [...new Set(components)]; + + // Strip "Affected components" line from description (all formats) description = description - .replace(/\n*Affected components?:[ \t]*[^\n]+/i, '') + .replace(/\n*\*{2}Affected components?:?\*{2}:?\s*[^\n]+/gi, '') + .replace(/\n*Affected components?:\s*[^\n]+/gi, '') + .trim(); + } + + // Extract migration notes using bold marker (standard format) + let migration = null; + const migrationMatch = description.match( + /\*\*Migration:\*\*\s*\n([\s\S]+?)(?=\n\s*$|$)/, + ); + if (migrationMatch) { + // Clean up indentation from list format (remove leading 2 spaces from each line) + migration = migrationMatch[1] + .split('\n') + .map(line => line.replace(/^ /, '')) + .join('\n') + .trim(); + // Strip migration section from description + description = description + .replace(/\n*\*\*Migration:\*\*[\s\S]+$/, '') .trim(); } const prs = []; // Will be populated later by fetchPRNumbers() - // Infer type from section and description - const type = inferChangeType(section, description, version); + // Check if this is a breaking change + const breaking = isBreakingChange(section, description, version); return { - version: normalizeVersion(version), + version, section, commitSha, description, components, + unknownNames, prs, - type, + breaking, + migration, }; } /** - * Infer change type from section and description + * Determine if a change is breaking based on semver rules + * Breaking change rules (semver): + * - version >= 1.0.0: Major changes are breaking + * - version < 1.0.0: Major and Minor changes are breaking */ -function inferChangeType(section, description, version) { - const isPre1 = version.startsWith('0.'); +function isBreakingChange(section, description, version) { + // Parse version to determine breaking change rules based on semver + const [major] = version.split('.').map(Number); - // Check description keywords first - if (description.match(/^(New|Add(ed)?)\s/i)) { - return 'new'; - } - if (description.includes('BREAKING')) { - return 'breaking'; + // Version >= 1.0.0: Only Major Changes are breaking + if (major >= 1) { + return section === 'Major Changes'; } - // Infer from section - if (section === 'Minor Changes') { - return isPre1 ? 'breaking' : undefined; - } else if (section === 'Major Changes') { - return 'breaking'; - } else if (section === 'Patch Changes') { - return 'fix'; - } - - return undefined; + // Version < 1.0.0: Both Major and Minor Changes are breaking + return section === 'Major Changes' || section === 'Minor Changes'; } /** @@ -414,8 +428,13 @@ async function fetchPRNumbers(entries, dryRun = false) { /** * Generate per-version changelog files in changelogs/ directory */ -async function generateVersionFiles(entries, changelogsDir, dryRun = false) { - // Group entries by normalized version +async function generateVersionFiles( + entries, + changelogsDir, + dryRun = false, + force = false, +) { + // Group entries by version const byVersion = {}; entries.forEach(entry => { const version = entry.version; @@ -434,8 +453,8 @@ async function generateVersionFiles(entries, changelogsDir, dryRun = false) { const filePath = path.join(changelogsDir, fileName); const varName = `changelog_${version.replace(/\./g, '_')}`; - // Check if file already exists - skip if it does - if (fs.existsSync(filePath)) { + // Check if file already exists - skip if it does (unless force flag) + if (fs.existsSync(filePath) && !force) { skippedVersions.push(version); // Still add to versionFiles array so main changelog.ts can import it versionFiles.push({ @@ -456,7 +475,7 @@ async function generateVersionFiles(entries, changelogsDir, dryRun = false) { .map(c => `'${c}'`) .join(', ')}]`; const prsStr = `[${entry.prs.map(pr => `'${pr}'`).join(', ')}]`; - const typeStr = entry.type ? `type: '${entry.type}',` : ''; + const breakingStr = entry.breaking ? `breaking: true,` : ''; const shaStr = entry.commitSha ? `commitSha: '${entry.commitSha}',` : ''; @@ -467,12 +486,20 @@ async function generateVersionFiles(entries, changelogsDir, dryRun = false) { .replace(/`/g, '\\`') .replace(/\${/g, '\\${'); + // Escape migration notes if present + const migrationStr = entry.migration + ? `migration: \`${entry.migration + .replace(/\\/g, '\\\\') + .replace(/`/g, '\\`') + .replace(/\${/g, '\\${')}\`,\n ` + : ''; + return ` { components: ${componentsStr}, version: '${entry.version}', prs: ${prsStr}, description: \`${descEscaped}\`, - ${typeStr} + ${migrationStr}${breakingStr} ${shaStr} }`; }) @@ -802,6 +829,8 @@ async function findPRNumber(commitSha) { async function main() { const args = process.argv.slice(2); const dryRun = args.includes('--dry-run'); + const force = args.includes('--force'); + const skipPrs = args.includes('--skip-prs'); const changelogTsPath = path.join(__dirname, '../src/utils/changelog.ts'); const changelogMdPath = path.join( @@ -818,10 +847,15 @@ async function main() { } console.log('📋 Syncing UI component changelogs...\n'); + if (force) { + console.log('⚠️ Force mode: Will overwrite existing version files\n'); + } - // Get last synced version - const lastVersion = getLastSyncedVersion(changelogTsPath); - console.log(`Last synced version: ${lastVersion || '(none)'}`); + // Get last synced version (null if force mode to process all) + const lastVersion = force ? null : getLastSyncedVersion(changelogTsPath); + console.log( + `Last synced version: ${lastVersion || '(none - processing all versions)'}`, + ); // Get valid components const validComponents = getValidComponents(changelogTsPath); @@ -834,26 +868,26 @@ async function main() { lastVersion, validComponents, ); - console.log(`Found ${allEntries.length} total entries since ${lastVersion}`); + console.log( + `Found ${allEntries.length} total entries${ + force ? '' : ` since ${lastVersion}` + }`, + ); // Read existing changelog content for duplicate detection const existingContent = fs.readFileSync(changelogTsPath, 'utf-8'); // Filter to only new, non-duplicate entries - const relevantEntries = allEntries.filter(e => { - const hasComponents = e.components.length > 0 || e.components.length === 0; - const notDuplicate = !isDuplicate(e, existingContent); - return hasComponents && notDuplicate; - }); + const relevantEntries = allEntries.filter( + e => !isDuplicate(e, existingContent), + ); const duplicatesCount = allEntries.length - relevantEntries.length; if (duplicatesCount > 0) { console.log(`Skipped ${duplicatesCount} duplicate entries`); } - console.log( - `Relevant entries (with or without components): ${relevantEntries.length}`, - ); + console.log(`Relevant entries: ${relevantEntries.length}`); if (relevantEntries.length === 0) { console.log('\n✅ No new entries to sync'); @@ -861,7 +895,9 @@ async function main() { } // Fetch PR numbers for new entries (lazy fetch - only for entries that will be written) - await fetchPRNumbers(relevantEntries, dryRun); + if (!skipPrs) { + await fetchPRNumbers(relevantEntries, dryRun); + } // Show summary console.log('\n📝 New entries by component:'); @@ -879,23 +915,8 @@ async function main() { console.log(` - ${comp}: ${count} ${count === 1 ? 'entry' : 'entries'}`); }); - // Warn about unknown components - const unknownComponents = []; - allEntries.forEach(entry => { - const fullText = entry.description; - const componentMatch = fullText.match( - /Affected components?:[ \t]*([^\n]+)/i, - ); - if (componentMatch) { - const names = componentMatch[1].split(',').map(n => n.trim()); - names.forEach(name => { - if (!mapComponentName(name, validComponents)) { - unknownComponents.push(name); - } - }); - } - }); - + // Warn about unknown components (collected during extraction) + const unknownComponents = allEntries.flatMap(e => e.unknownNames || []); if (unknownComponents.length > 0) { console.log('\n⚠️ Unknown components (skipped):'); [...new Set(unknownComponents)].forEach(name => { @@ -920,6 +941,7 @@ async function main() { relevantEntries, changelogsDir, dryRun, + force, ); // Generate main changelog.ts diff --git a/docs-ui/scripts/sync-css.js b/docs-ui/scripts/sync-css.js deleted file mode 100644 index 4763fdecc1..0000000000 --- a/docs-ui/scripts/sync-css.js +++ /dev/null @@ -1,149 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const { bundle } = require('lightningcss'); -const chokidar = require('chokidar'); - -// Configuration -const config = { - UIPath: '../../packages/ui', - publicPath: '../public', - files: [ - { - source: 'css/styles.css', - destination: 'theme-backstage.css', - name: 'Main Styles', - }, - { - source: '../../.storybook/themes/spotify.css', - destination: 'theme-spotify.css', - name: 'Spotify Theme', - }, - ], -}; - -class CSSSync { - constructor() { - this.UIPath = path.resolve(__dirname, config.UIPath); - this.publicPath = path.resolve(__dirname, config.publicPath); - this.isWatching = process.argv.includes('--watch'); - } - - async syncFile(fileConfig) { - const sourcePath = path.join(this.UIPath, fileConfig.source); - const destPath = path.join(this.publicPath, fileConfig.destination); - - try { - // Check if source file exists - if (!fs.existsSync(sourcePath)) { - console.warn(`⚠️ Source file not found: ${sourcePath}`); - return false; - } - - // Ensure destination directory exists - fs.mkdirSync(path.dirname(destPath), { recursive: true }); - - // Bundle and optimize CSS - const result = await bundle({ - filename: sourcePath, - minify: true, - }); - - // Write to destination - fs.writeFileSync(destPath, result.code); - - console.log( - `✅ ${fileConfig.name}: ${fileConfig.source} → ${fileConfig.destination}`, - ); - return true; - } catch (error) { - console.error(`❌ Error syncing ${fileConfig.name}:`, error.message); - return false; - } - } - - async syncAll() { - console.log('🔄 Syncing CSS files...\n'); - - let successCount = 0; - for (const fileConfig of config.files) { - if (await this.syncFile(fileConfig)) { - successCount++; - } - } - - console.log( - `\n✨ Synced ${successCount}/${config.files.length} CSS files successfully!`, - ); - - if (successCount > 0) { - console.log('\n📁 Available CSS files in public/:'); - config.files.forEach(file => { - const destPath = path.join(this.publicPath, file.destination); - if (fs.existsSync(destPath)) { - const stats = fs.statSync(destPath); - const size = (stats.size / 1024).toFixed(2); - console.log(` • ${file.destination} (${size} KB)`); - } - }); - } - } - - startWatching() { - console.log('👀 Watching for CSS changes...\n'); - - // Watch all source files - const watchPaths = config.files.map(file => - path.join(this.UIPath, file.source), - ); - - const watcher = chokidar.watch(watchPaths, { - ignored: /node_modules/, - persistent: true, - }); - - watcher.on('change', async filePath => { - console.log( - `\n🔄 Change detected: ${path.relative(this.UIPath, filePath)}`, - ); - - // Find which file changed and sync it - const fileConfig = config.files.find(file => - filePath.endsWith(file.source.replace(/\//g, path.sep)), - ); - - if (fileConfig) { - await this.syncFile(fileConfig); - } - }); - - watcher.on('error', error => console.error('❌ Watch error:', error)); - - // Handle process termination - process.on('SIGINT', () => { - console.log('\n👋 Stopping CSS sync...'); - watcher.close(); - process.exit(0); - }); - } - - async run() { - console.log('🎨 BUI CSS Sync Tool\n'); - console.log(`📂 BUI path: ${this.UIPath}`); - console.log(`📂 Public path: ${this.publicPath}\n`); - - // Initial sync - await this.syncAll(); - - // Watch for changes if requested - if (this.isWatching) { - this.startWatching(); - } - } -} - -// Run the sync tool -const cssSync = new CSSSync(); -cssSync.run().catch(error => { - console.error('❌ CSS Sync failed:', error); - process.exit(1); -}); diff --git a/docs-ui/src/app/components/[slug]/page.tsx b/docs-ui/src/app/components/[slug]/page.tsx deleted file mode 100644 index 1422334471..0000000000 --- a/docs-ui/src/app/components/[slug]/page.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { components, layoutComponents } from '@/utils/data'; - -export default async function Page({ - params, -}: { - params: Promise<{ slug: string }>; -}) { - const { slug } = await params; - - const { default: Component } = await import(`@/content/${slug}.mdx`); - - return ; -} - -export function generateStaticParams() { - const list = [...components, ...layoutComponents]; - - return list.map(component => ({ - slug: component.slug, - })); -} - -export const dynamicParams = false; diff --git a/docs-ui/src/app/components/accordion/components.tsx b/docs-ui/src/app/components/accordion/components.tsx new file mode 100644 index 0000000000..95b312d17c --- /dev/null +++ b/docs-ui/src/app/components/accordion/components.tsx @@ -0,0 +1,130 @@ +'use client'; + +import { + Accordion, + AccordionTrigger, + AccordionPanel, + AccordionGroup, +} from '../../../../../packages/ui/src/components/Accordion/Accordion'; +import { Box } from '../../../../../packages/ui/src/components/Box/Box'; +import { Text } from '../../../../../packages/ui/src/components/Text/Text'; + +const Content = () => ( + + + It's the edge of the world and all of Western civilization + + + The sun may rise in the East, at least it settled in a final location + + + It's understood that Hollywood sells Californication + + +); + +export const Default = () => { + return ( + + + + + + + ); +}; + +export const WithSubtitle = () => { + return ( + + + + + + + ); +}; + +export const CustomTrigger = () => { + return ( + + + + + Custom Multi-line Trigger + + + Click to expand additional details and configuration options + + + + + + + + ); +}; + +export const DefaultExpanded = () => { + return ( + + + + + + + ); +}; + +export const GroupSingleOpen = () => { + return ( + + + + + + + + + + + + + + + + + + + + + ); +}; + +export const GroupMultipleOpen = () => { + return ( + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/docs-ui/src/app/components/accordion/page.mdx b/docs-ui/src/app/components/accordion/page.mdx new file mode 100644 index 0000000000..90c573b087 --- /dev/null +++ b/docs-ui/src/app/components/accordion/page.mdx @@ -0,0 +1,157 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { + accordionPropDefs, + accordionTriggerPropDefs, + accordionPanelPropDefs, + accordionGroupPropDefs, +} from './props-definition'; +import { + accordionUsageSnippet, + defaultSnippet, + withSubtitleSnippet, + customTriggerSnippet, + defaultExpandedSnippet, + groupSingleOpenSnippet, + groupMultipleOpenSnippet, +} from './snippets'; +import { + Default, + WithSubtitle, + CustomTrigger, + DefaultExpanded, + GroupSingleOpen, + GroupMultipleOpen, +} from './components'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { AccordionDefinition } from '../../../utils/definitions'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; + +export const reactAriaUrls = { + disclosure: 'https://react-aria.adobe.com/Disclosure', + disclosureGroup: 'https://react-aria.adobe.com/DisclosureGroup', +}; + + + +} + code={defaultSnippet} +/> + +## Usage + + + +## API reference + +### Accordion + +Root container for the accordion. Renders a `
` element. + + + + + +### AccordionTrigger + +Trigger component with built-in animated chevron icon. Renders a heading element (defaults to `

`, configurable via `level` prop) wrapping a ` + } + /> + } + aria-label="Close" + /> + } + /> + + ); +}; + +export const WithActionsAndDescriptions = () => { + return ( + + + + + } + /> + ); +}; + +export const LoadingStates = () => { + return ( + + + + + + ); +}; + +export const WithoutIcons = () => { + return ( + + + + + ); +}; + +export const CustomIcon = () => { + return ( + } + title="This alert uses a custom cloud icon instead of the default info icon." + /> + ); +}; diff --git a/docs-ui/src/app/components/alert/page.mdx b/docs-ui/src/app/components/alert/page.mdx new file mode 100644 index 0000000000..cdfdf184e0 --- /dev/null +++ b/docs-ui/src/app/components/alert/page.mdx @@ -0,0 +1,120 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { alertPropDefs } from './props-definition'; +import { + alertUsageSnippet, + defaultSnippet, + statusVariantsSnippet, + withDescriptionSnippet, + withActionsSnippet, + loadingStatesSnippet, + withoutIconsSnippet, + customIconSnippet, +} from './snippets'; +import { + Default, + StatusVariants, + WithDescription, + WithActions, + LoadingStates, + WithoutIcons, + CustomIcon, +} from './components'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { AlertDefinition } from '../../../utils/definitions'; + + + +} code={defaultSnippet} /> + +## Usage + + + +## API reference + + + +## Examples + +### Status Variants + +The Alert component supports four status variants, each with its own color theme. + +} + code={statusVariantsSnippet} +/> + +### With Description + +Add a description to provide additional context or details. + +} + code={withDescriptionSnippet} +/> + +### With Actions + +Include custom actions like buttons for interactive alerts. + +} + code={withActionsSnippet} +/> + +### Loading States + +The loading spinner replaces the icon to indicate an ongoing process. + +} + code={loadingStatesSnippet} +/> + +### Without Icons + +Disable icons for a simpler appearance. + +} + code={withoutIconsSnippet} +/> + +### Custom Icon + +Provide a custom icon element instead of the default status icon. + +} + code={customIconSnippet} +/> + + + + diff --git a/docs-ui/src/app/components/alert/props-definition.ts b/docs-ui/src/app/components/alert/props-definition.ts new file mode 100644 index 0000000000..6f2650fe42 --- /dev/null +++ b/docs-ui/src/app/components/alert/props-definition.ts @@ -0,0 +1,76 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const alertPropDefs: Record = { + status: { + type: 'enum', + values: ['info', 'success', 'warning', 'danger'], + responsive: true, + default: 'info', + }, + icon: { + type: 'enum', + values: ['boolean', 'React.ReactElement'], + responsive: false, + }, + loading: { + type: 'enum', + values: ['boolean'], + responsive: false, + }, + title: { + type: 'enum', + values: ['React.ReactNode'], + responsive: false, + }, + description: { + type: 'enum', + values: ['React.ReactNode'], + responsive: false, + }, + customActions: { + type: 'enum', + values: ['React.ReactNode'], + responsive: false, + }, + m: { + type: 'enum', + values: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + responsive: true, + }, + mx: { + type: 'enum', + values: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + responsive: true, + }, + my: { + type: 'enum', + values: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + responsive: true, + }, + mt: { + type: 'enum', + values: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + responsive: true, + }, + mb: { + type: 'enum', + values: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + responsive: true, + }, + ml: { + type: 'enum', + values: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + responsive: true, + }, + mr: { + type: 'enum', + values: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + responsive: true, + }, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/alert/snippets.ts b/docs-ui/src/app/components/alert/snippets.ts new file mode 100644 index 0000000000..739dfde9db --- /dev/null +++ b/docs-ui/src/app/components/alert/snippets.ts @@ -0,0 +1,131 @@ +export const alertUsageSnippet = `import { Alert } from '@backstage/ui'; + +`; + +export const defaultSnippet = ``; + +export const statusVariantsSnippet = ` + + + + +`; + +export const withDescriptionSnippet = ` + + + + +`; + +export const withActionsSnippet = ` + + Dismiss + + } + /> + } + aria-label="Close" + /> + } + /> +`; + +export const withActionsAndDescriptionsSnippet = ` + + + + } +/>`; + +export const loadingStatesSnippet = ` + + + +`; + +export const withoutIconsSnippet = ` + + +`; + +export const customIconSnippet = `import { RiCloudLine } from '@remixicon/react'; + +} + title="This alert uses a custom cloud icon instead of the default info icon." +/>`; diff --git a/docs-ui/src/app/components/avatar/components.tsx b/docs-ui/src/app/components/avatar/components.tsx new file mode 100644 index 0000000000..da257f13b9 --- /dev/null +++ b/docs-ui/src/app/components/avatar/components.tsx @@ -0,0 +1,100 @@ +'use client'; + +import { Avatar } from '../../../../../packages/ui/src/components/Avatar/Avatar'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; +import { Text } from '../../../../../packages/ui/src/components/Text/Text'; + +export const Default = () => { + return ( + + ); +}; + +export const Fallback = () => { + return ( + + ); +}; + +export const Sizes = () => { + return ( + + + + + + + + + + + + + + + + + ); +}; + +export const Purpose = () => { + return ( + + + Informative (default) + + Use when avatar appears alone. Announced as "Charles de + Dreuille" to screen readers: + + + + + + + Decoration + + Use when avatar appears with adjacent text. Hidden from screen + readers: + + + + Charles de Dreuille + + + + ); +}; diff --git a/docs-ui/src/content/avatar.mdx b/docs-ui/src/app/components/avatar/page.mdx similarity index 50% rename from docs-ui/src/content/avatar.mdx rename to docs-ui/src/app/components/avatar/page.mdx index 047485c2cc..7020eb90c4 100644 --- a/docs-ui/src/content/avatar.mdx +++ b/docs-ui/src/app/components/avatar/page.mdx @@ -1,39 +1,37 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { AvatarSnippet } from '@/snippets/stories-snippets'; +import { avatarPropDefs } from './props-definition'; import { - avatarPropDefs, - snippetUsage, - snippetSizes, - snippetFallback, - snippetPurpose, -} from './avatar.props'; + avatarUsageSnippet, + defaultSnippet, + sizesSnippet, + fallbackSnippet, + purposeSnippet, +} from './snippets'; +import { Default, Sizes, Fallback, Purpose } from './components'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; -import { AvatarDefinition } from '../utils/definitions'; +import { AvatarDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; -} - code={``} -/> +} code={defaultSnippet} /> ## Usage - + ## API reference +Avatar also accepts all standard HTML div attributes (`onClick`, `onMouseEnter`, etc.) since it extends `React.ComponentPropsWithoutRef<'div'>`. + ## Examples ### Sizes @@ -44,8 +42,9 @@ Avatar sizes can be set using the `size` prop. align="center" py={4} open - preview={} - code={snippetSizes} + preview={} + code={sizesSnippet} + layout="side-by-side" /> ### Fallback @@ -56,20 +55,16 @@ If the image is not available, the avatar will show the initials of the name. align="center" py={4} open - preview={} - code={snippetFallback} + preview={} + code={fallbackSnippet} + layout="side-by-side" /> ### The `purpose` prop Control how the avatar is announced to screen readers using the `purpose` prop. -} - code={snippetPurpose} -/> +} code={purposeSnippet} /> diff --git a/docs-ui/src/app/components/avatar/props-definition.tsx b/docs-ui/src/app/components/avatar/props-definition.tsx new file mode 100644 index 0000000000..4b56b94be5 --- /dev/null +++ b/docs-ui/src/app/components/avatar/props-definition.tsx @@ -0,0 +1,41 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +export const avatarPropDefs: Record = { + src: { + type: 'string', + description: + 'URL of the image to display. Pass an empty string to show initials fallback. Falls back to initials if the image fails to load.', + }, + name: { + type: 'string', + required: true, + description: + 'Name of the person. Used for generating initials fallback and accessibility label.', + }, + size: { + type: 'enum', + values: ['x-small', 'small', 'medium', 'large', 'x-large'], + default: 'medium', + responsive: true, + description: + 'Visual size. Smaller sizes show 1 initial, larger sizes show 2.', + }, + purpose: { + type: 'enum', + values: ['informative', 'decoration'], + default: 'informative', + description: ( + <> + Accessibility behavior. Use decoration when name appears in + adjacent text. + + ), + }, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/avatar/snippets.ts b/docs-ui/src/app/components/avatar/snippets.ts new file mode 100644 index 0000000000..2deb8037a6 --- /dev/null +++ b/docs-ui/src/app/components/avatar/snippets.ts @@ -0,0 +1,52 @@ +export const avatarUsageSnippet = `import { Avatar } from '@backstage/ui'; + +`; + +export const defaultSnippet = ``; + +export const fallbackSnippet = ``; + +export const sizesSnippet = ` + + + + + + + + + + + + + + +`; + +export const purposeSnippet = ` + + Informative (default) + + Use when avatar appears alone. Announced as "Charles de Dreuille" to screen readers: + + + + + + + Decoration + + Use when avatar appears with adjacent text. Hidden from screen readers: + + + + Charles de Dreuille + + +`; diff --git a/docs-ui/src/app/components/badge/components.tsx b/docs-ui/src/app/components/badge/components.tsx new file mode 100644 index 0000000000..a77a6b09d7 --- /dev/null +++ b/docs-ui/src/app/components/badge/components.tsx @@ -0,0 +1,16 @@ +'use client'; + +import { Badge } from '../../../../../packages/ui/src/components/Badge/Badge'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; +import { RiBugLine } from '@remixicon/react'; + +export const Default = () => Banana; + +export const WithIcon = () => }>Banana; + +export const Sizes = () => ( + + Banana + Banana + +); diff --git a/docs-ui/src/app/components/badge/page.mdx b/docs-ui/src/app/components/badge/page.mdx new file mode 100644 index 0000000000..fb934c57ad --- /dev/null +++ b/docs-ui/src/app/components/badge/page.mdx @@ -0,0 +1,41 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { Default, WithIcon, Sizes } from './components'; +import { badgePropDefs } from './props-definition'; +import { usage, preview, withIcons, sizes } from './snippets'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { BadgeDefinition } from '../../../utils/definitions'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + + + +} code={preview} /> + +## Usage + + + +## API reference + +### Badge + + + +## Examples + +### With icons + +} code={withIcons} /> + +### Sizes + +} code={sizes} /> + + + + diff --git a/docs-ui/src/app/components/badge/props-definition.tsx b/docs-ui/src/app/components/badge/props-definition.tsx new file mode 100644 index 0000000000..b193c1cef2 --- /dev/null +++ b/docs-ui/src/app/components/badge/props-definition.tsx @@ -0,0 +1,27 @@ +import { + classNamePropDefs, + childrenPropDefs, + type PropDef, +} from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +export const badgePropDefs: Record = { + icon: { + type: 'enum', + values: ['ReactNode'], + description: 'Icon displayed before the badge text.', + }, + size: { + type: 'enum', + values: ['small', 'medium'], + default: 'small', + description: ( + <> + Visual size of the badge. Use small for inline or dense + layouts, medium for standalone badges. + + ), + }, + ...childrenPropDefs, + ...classNamePropDefs, +}; diff --git a/docs-ui/src/app/components/badge/snippets.ts b/docs-ui/src/app/components/badge/snippets.ts new file mode 100644 index 0000000000..dc765d2b7d --- /dev/null +++ b/docs-ui/src/app/components/badge/snippets.ts @@ -0,0 +1,12 @@ +export const usage = `import { Badge } from '@backstage/ui'; + +Badge`; + +export const preview = `Banana`; + +export const withIcons = `}>Banana`; + +export const sizes = ` + Banana + Banana +`; diff --git a/docs-ui/src/app/components/box/components.tsx b/docs-ui/src/app/components/box/components.tsx new file mode 100644 index 0000000000..dac66f1f59 --- /dev/null +++ b/docs-ui/src/app/components/box/components.tsx @@ -0,0 +1,43 @@ +'use client'; + +import { Box } from '../../../../../packages/ui/src/components/Box/Box'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; +import { DecorativeBox } from '@/components/DecorativeBox'; + +export const Default = () => { + return ( + + + + ); +}; + +export const Surface = () => { + return ( + + + Surface 0 + + + Surface 1 + + + Surface 2 + + + Surface 3 + + + ); +}; + +export const Responsive = () => { + return ( + + Resize to see change + + ); +}; diff --git a/docs-ui/src/app/components/box/page.mdx b/docs-ui/src/app/components/box/page.mdx new file mode 100644 index 0000000000..9b60e5244a --- /dev/null +++ b/docs-ui/src/app/components/box/page.mdx @@ -0,0 +1,53 @@ +import { CodeBlock } from '@/components/CodeBlock'; +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { boxPropDefs } from './props-definition'; +import { + snippetUsage, + defaultSnippet, + boxSurfaceSnippet, + boxResponsiveSnippet, +} from './snippets'; +import { Default, Surface, Responsive } from './components'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { BoxDefinition } from '../../../utils/definitions'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + + + +} code={defaultSnippet} align="center" /> + +## Usage + + + +## API reference + + + +## Examples + +### Surface + +Use surface levels to create visual hierarchy. + +} code={boxSurfaceSnippet} layout="side-by-side" /> + +### Responsive props + +Props can accept breakpoint objects for responsive behavior. + +} + code={boxResponsiveSnippet} + layout="side-by-side" +/> + + + + diff --git a/docs-ui/src/app/components/box/props-definition.tsx b/docs-ui/src/app/components/box/props-definition.tsx new file mode 100644 index 0000000000..3809bb9993 --- /dev/null +++ b/docs-ui/src/app/components/box/props-definition.tsx @@ -0,0 +1,48 @@ +import { + classNamePropDefs, + heightPropDefs, + positionPropDefs, + stylePropDefs, + widthPropDefs, + spacingGroupAll, + type PropDef, +} from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +export const boxPropDefs: Record = { + as: { + type: 'string', + default: 'div', + description: + 'HTML element to render. Accepts any valid HTML tag (div, span, section, etc.).', + }, + surface: { + type: 'enum', + values: ['0', '1', '2', '3', 'danger', 'warning', 'success', 'auto'], + responsive: true, + description: + 'Background surface level for visual hierarchy. Higher numbers create elevation.', + }, + children: { + type: 'enum', + values: ['ReactNode'], + description: 'Content to render inside the box.', + }, + ...widthPropDefs, + ...heightPropDefs, + ...positionPropDefs, + display: { + type: 'enum', + values: ['none', 'flex', 'block', 'inline'], + responsive: true, + description: ( + <> + Controls layout behavior. Use flex for flexbox layouts,{' '} + none to hide. + + ), + }, + ...classNamePropDefs, + ...stylePropDefs, + spacing: spacingGroupAll, +}; diff --git a/docs-ui/src/app/components/box/snippets.ts b/docs-ui/src/app/components/box/snippets.ts new file mode 100644 index 0000000000..e9af07dec5 --- /dev/null +++ b/docs-ui/src/app/components/box/snippets.ts @@ -0,0 +1,23 @@ +export const snippetUsage = `import { Box } from '@backstage/ui'; + + + Content with padding and background +`; + +export const defaultSnippet = ` + +`; + +export const boxSurfaceSnippet = ` + Surface 0 + Surface 1 + Surface 2 + Surface 3 +`; + +export const boxResponsiveSnippet = ` + Content +`; diff --git a/docs-ui/src/app/components/button-icon/components.tsx b/docs-ui/src/app/components/button-icon/components.tsx new file mode 100644 index 0000000000..3ed421289c --- /dev/null +++ b/docs-ui/src/app/components/button-icon/components.tsx @@ -0,0 +1,68 @@ +'use client'; + +import { ButtonIcon } from '../../../../../packages/ui/src/components/ButtonIcon/ButtonIcon'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; +import { RiCloudLine } from '@remixicon/react'; + +export const Variants = () => { + return ( + + } variant="primary" aria-label="Cloud" /> + } + variant="secondary" + aria-label="Cloud" + /> + } + variant="tertiary" + aria-label="Cloud" + /> + + ); +}; + +export const Sizes = () => { + return ( + + } size="small" aria-label="Cloud" /> + } size="medium" aria-label="Cloud" /> + + ); +}; + +export const Disabled = () => { + return ( + + } + variant="primary" + aria-label="Cloud" + /> + } + variant="secondary" + aria-label="Cloud" + /> + } + variant="tertiary" + aria-label="Cloud" + /> + + ); +}; + +export const Loading = () => { + return ( + } + variant="primary" + loading + aria-label="Cloud" + /> + ); +}; diff --git a/docs-ui/src/app/components/button-icon/page.mdx b/docs-ui/src/app/components/button-icon/page.mdx new file mode 100644 index 0000000000..51ff0ea5a0 --- /dev/null +++ b/docs-ui/src/app/components/button-icon/page.mdx @@ -0,0 +1,80 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { buttonIconPropDefs } from './props-definition'; +import { + buttonIconUsageSnippet, + variantsSnippet, + sizesSnippet, + disabledSnippet, + loadingSnippet, +} from './snippets'; +import { Variants, Sizes, Disabled, Loading } from './components'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ButtonIconDefinition } from '../../../utils/definitions'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; + +export const reactAriaUrls = { + button: 'https://react-aria.adobe.com/Button', +}; + + + +} code={variantsSnippet} /> + +## Usage + + + +## API reference + + + + + +## Examples + +### Variants + +} + code={variantsSnippet} +/> + +### Sizes + +} code={sizesSnippet} /> + +### Disabled + +} + code={disabledSnippet} +/> + +### Loading + +Shows a spinner during async operations. + +} + code={loadingSnippet} +/> + + + + diff --git a/docs-ui/src/app/components/button-icon/props-definition.tsx b/docs-ui/src/app/components/button-icon/props-definition.tsx new file mode 100644 index 0000000000..05917cc2b4 --- /dev/null +++ b/docs-ui/src/app/components/button-icon/props-definition.tsx @@ -0,0 +1,64 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +export const buttonIconPropDefs: Record = { + variant: { + type: 'enum', + values: ['primary', 'secondary', 'tertiary'], + default: 'primary', + responsive: true, + description: ( + <> + Visual style. Use primary for main actions,{' '} + secondary for alternatives, tertiary for + low-emphasis. + + ), + }, + size: { + type: 'enum', + values: ['small', 'medium'], + default: 'small', + responsive: true, + description: ( + <> + Button size. Use small for toolbars, medium{' '} + for standalone actions. + + ), + }, + icon: { + type: 'enum', + values: ['ReactElement'], + description: + 'Icon element to display. Required for accessibility via aria-label.', + }, + isDisabled: { + type: 'boolean', + default: 'false', + description: 'Prevents interaction and applies disabled styling.', + }, + loading: { + type: 'boolean', + default: 'false', + description: 'Shows a spinner and disables the button.', + }, + type: { + type: 'enum', + values: ['button', 'submit', 'reset'], + default: 'button', + description: 'HTML button type attribute.', + }, + onSurface: { + type: 'enum', + values: ['0', '1', '2', '3', 'danger', 'warning', 'success', 'auto'], + responsive: true, + description: 'Surface context for correct color contrast.', + }, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/button-icon/snippets.ts b/docs-ui/src/app/components/button-icon/snippets.ts new file mode 100644 index 0000000000..b1ab27b77b --- /dev/null +++ b/docs-ui/src/app/components/button-icon/snippets.ts @@ -0,0 +1,23 @@ +export const buttonIconUsageSnippet = `import { ButtonIcon } from '@backstage/ui'; +import { RiCloseLine } from '@remixicon/react'; + +} aria-label="Close" />`; + +export const variantsSnippet = ` + } variant="primary" aria-label="Cloud" /> + } variant="secondary" aria-label="Cloud" /> + } variant="tertiary" aria-label="Cloud" /> +`; + +export const sizesSnippet = ` + } size="small" aria-label="Cloud" /> + } size="medium" aria-label="Cloud" /> +`; + +export const disabledSnippet = ` + } variant="primary" aria-label="Cloud" /> + } variant="secondary" aria-label="Cloud" /> + } variant="tertiary" aria-label="Cloud" /> +`; + +export const loadingSnippet = `} variant="primary" loading aria-label="Cloud" />`; diff --git a/docs-ui/src/app/components/button-link/components.tsx b/docs-ui/src/app/components/button-link/components.tsx new file mode 100644 index 0000000000..4282ebf7dd --- /dev/null +++ b/docs-ui/src/app/components/button-link/components.tsx @@ -0,0 +1,122 @@ +'use client'; + +import { ButtonLink } from '../../../../../packages/ui/src/components/ButtonLink/ButtonLink'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; +import { MemoryRouter } from 'react-router-dom'; +import { RiArrowRightSLine, RiCloudLine } from '@remixicon/react'; + +export const Variants = () => { + return ( + + + } + variant="primary" + href="https://ui.backstage.io" + target="_blank" + > + Button + + } + variant="secondary" + href="https://ui.backstage.io" + target="_blank" + > + Button + + } + variant="tertiary" + href="https://ui.backstage.io" + target="_blank" + > + Button + + + + ); +}; + +export const Sizes = () => { + return ( + + + + Small + + + Medium + + + + ); +}; + +export const WithIcons = () => { + return ( + + + } + href="https://ui.backstage.io" + target="_blank" + > + Button + + } + href="https://ui.backstage.io" + target="_blank" + > + Button + + } + iconEnd={} + href="https://ui.backstage.io" + target="_blank" + > + Button + + + + ); +}; + +export const Disabled = () => { + return ( + + + + Primary + + + Secondary + + + Tertiary + + + + ); +}; diff --git a/docs-ui/src/app/components/button-link/page.mdx b/docs-ui/src/app/components/button-link/page.mdx new file mode 100644 index 0000000000..35e4de68c5 --- /dev/null +++ b/docs-ui/src/app/components/button-link/page.mdx @@ -0,0 +1,68 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { buttonLinkPropDefs } from './props-definition'; +import { + buttonLinkUsageSnippet, + variantsSnippet, + sizesSnippet, + withIconsSnippet, + disabledSnippet, +} from './snippets'; +import { Variants, Sizes, WithIcons, Disabled } from './components'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ButtonLinkDefinition } from '../../../utils/definitions'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; + +export const reactAriaUrls = { + link: 'https://react-aria.adobe.com/Link', +}; + + + +} code={variantsSnippet} /> + +## Usage + + + +## API reference + + + + + +## Examples + +### Sizes + +} code={sizesSnippet} /> + +### With Icons + +} + code={withIconsSnippet} +/> + +### Disabled + +} + code={disabledSnippet} +/> + + + + diff --git a/docs-ui/src/app/components/button-link/props-definition.tsx b/docs-ui/src/app/components/button-link/props-definition.tsx new file mode 100644 index 0000000000..e85a363b5a --- /dev/null +++ b/docs-ui/src/app/components/button-link/props-definition.tsx @@ -0,0 +1,79 @@ +import { + classNamePropDefs, + stylePropDefs, + childrenPropDefs, + type PropDef, +} from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +export const buttonLinkPropDefs: Record = { + variant: { + type: 'enum', + values: ['primary', 'secondary', 'tertiary'], + default: 'primary', + responsive: true, + description: ( + <> + Visual style. Use primary for main actions,{' '} + secondary for alternatives, tertiary for + low-emphasis. + + ), + }, + size: { + type: 'enum', + values: ['small', 'medium'], + default: 'small', + responsive: true, + description: ( + <> + Link size. Use small for inline contexts,{' '} + medium for standalone. + + ), + }, + iconStart: { + type: 'enum', + values: ['ReactElement'], + description: 'Icon displayed before the link text.', + }, + iconEnd: { + type: 'enum', + values: ['ReactElement'], + description: 'Icon displayed after the link text.', + }, + isDisabled: { + type: 'boolean', + default: 'false', + description: 'Prevents interaction and applies disabled styling.', + }, + href: { + type: 'string', + required: true, + description: 'URL the link navigates to.', + }, + target: { + type: 'enum', + values: ['_self', '_blank', '_parent', '_top'], + description: ( + <> + Where to open the linked URL. Use _blank for external + links. + + ), + }, + noTrack: { + type: 'boolean', + description: + 'Suppresses the automatic analytics click event, e.g. if you already have custom tracking.', + }, + onSurface: { + type: 'enum', + values: ['0', '1', '2', '3', 'danger', 'warning', 'success', 'auto'], + responsive: true, + description: 'Surface context for correct color contrast.', + }, + ...childrenPropDefs, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/button-link/snippets.ts b/docs-ui/src/app/components/button-link/snippets.ts new file mode 100644 index 0000000000..7f1a69027c --- /dev/null +++ b/docs-ui/src/app/components/button-link/snippets.ts @@ -0,0 +1,68 @@ +export const buttonLinkUsageSnippet = `import { ButtonLink } from '@backstage/ui'; + +Button`; + +export const variantsSnippet = ` + } + variant="primary" + href="https://ui.backstage.io" + target="_blank" + > + Button + + } + variant="secondary" + href="https://ui.backstage.io" + target="_blank" + > + Button + + } + variant="tertiary" + href="https://ui.backstage.io" + target="_blank" + > + Button + +`; + +export const sizesSnippet = ` + + Small + + + Medium + +`; + +export const withIconsSnippet = ` + } href="https://ui.backstage.io" target="_blank"> + Button + + } href="https://ui.backstage.io" target="_blank"> + Button + + } + iconEnd={} + href="https://ui.backstage.io" + target="_blank" + > + Button + +`; + +export const disabledSnippet = ` + + Primary + + + Secondary + + + Tertiary + +`; diff --git a/docs-ui/src/app/components/button/components.tsx b/docs-ui/src/app/components/button/components.tsx new file mode 100644 index 0000000000..1fe13f6630 --- /dev/null +++ b/docs-ui/src/app/components/button/components.tsx @@ -0,0 +1,94 @@ +'use client'; + +import { Button } from '../../../../../packages/ui/src/components/Button/Button'; +import { ButtonLink } from '../../../../../packages/ui/src/components/ButtonLink/ButtonLink'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; +import { RiArrowRightSLine, RiCloudLine } from '@remixicon/react'; +import { MemoryRouter } from 'react-router-dom'; + +export const Variants = () => { + return ( + + + + + + ); +}; + +export const Sizes = () => { + return ( + + + + + ); +}; + +export const WithIcons = () => { + return ( + + + + + + ); +}; + +export const Disabled = () => { + return ( + + + + + + ); +}; + +export const Destructive = () => { + return ( + + + + + + ); +}; + +export const Loading = () => { + return ( + + ); +}; + +export const AsLink = () => { + return ( + + + Button + + + ); +}; diff --git a/docs-ui/src/app/components/button/page.mdx b/docs-ui/src/app/components/button/page.mdx new file mode 100644 index 0000000000..6434a7f4a4 --- /dev/null +++ b/docs-ui/src/app/components/button/page.mdx @@ -0,0 +1,147 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { buttonPropDefs } from './props-definition'; +import { + variantsSnippet, + sizesSnippet, + withIconsSnippet, + disabledSnippet, + destructiveSnippet, + loadingSnippet, + asLinkSnippet, + buttonSnippetUsage, + buttonResponsiveSnippet, +} from './snippets'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ButtonDefinition } from '../../../utils/definitions'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; +import { + Variants, + Sizes, + WithIcons, + Disabled, + Destructive, + Loading, + AsLink, +} from './components'; + +export const reactAriaUrls = { + button: 'https://react-spectrum.adobe.com/react-aria/Button.html', +}; + + + +} code={variantsSnippet} /> + +## Usage + + + +## API reference + + + + + +## Examples + +### Variants + +} + code={variantsSnippet} + layout="side-by-side" +/> + +### Sizes + +} + code={sizesSnippet} + layout="side-by-side" +/> + +### With Icons + +Icons can appear before or after the label. + +} + code={withIconsSnippet} + layout="side-by-side" +/> + +### Disabled + +} + code={disabledSnippet} + layout="side-by-side" +/> + +### Destructive + +Use the `destructive` prop for dangerous actions like delete or remove. + +} + code={destructiveSnippet} + layout="side-by-side" +/> + +### Loading + +Shows a spinner and disables interaction during async operations. + +} + code={loadingSnippet} + layout="side-by-side" +/> + +### Responsive + +Button props accept responsive breakpoint objects. + + + +### As Link + +If you want to use a button as a link, please use the `ButtonLink` component. + +} + code={asLinkSnippet} + layout="side-by-side" +/> + + + + diff --git a/docs-ui/src/app/components/button/props-definition.tsx b/docs-ui/src/app/components/button/props-definition.tsx new file mode 100644 index 0000000000..0f94fcffaf --- /dev/null +++ b/docs-ui/src/app/components/button/props-definition.tsx @@ -0,0 +1,75 @@ +import { classNamePropDefs, stylePropDefs } from '@/utils/propDefs'; +import type { PropDef } from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +export const buttonPropDefs: Record = { + variant: { + type: 'enum', + values: ['primary', 'secondary', 'tertiary'], + default: 'primary', + responsive: true, + description: ( + <> + Visual style. Use primary for main actions,{' '} + secondary for alternatives, tertiary for + low-emphasis. + + ), + }, + destructive: { + type: 'boolean', + default: 'false', + description: + 'Applies destructive styling for dangerous actions like delete or remove.', + }, + size: { + type: 'enum', + values: ['small', 'medium'], + default: 'small', + responsive: true, + description: ( + <> + Button size. Use small for dense layouts. + + ), + }, + iconStart: { + type: 'enum', + values: ['ReactElement'], + description: 'Icon displayed before the button text.', + }, + iconEnd: { + type: 'enum', + values: ['ReactElement'], + description: 'Icon displayed after the button text.', + }, + isDisabled: { + type: 'boolean', + default: 'false', + description: 'Prevents interaction and applies disabled styling.', + }, + loading: { + type: 'boolean', + default: 'false', + description: 'Shows a spinner and disables the button.', + }, + children: { + type: 'enum', + values: ['ReactNode'], + description: 'Button label text or content.', + }, + type: { + type: 'enum', + values: ['button', 'submit', 'reset'], + default: 'button', + description: 'HTML button type attribute.', + }, + onSurface: { + type: 'enum', + values: ['0', '1', '2', '3', 'danger', 'warning', 'success', 'auto'], + responsive: true, + description: 'Surface context for correct color contrast.', + }, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/button/snippets.ts b/docs-ui/src/app/components/button/snippets.ts new file mode 100644 index 0000000000..6b8aba7e26 --- /dev/null +++ b/docs-ui/src/app/components/button/snippets.ts @@ -0,0 +1,66 @@ +export const buttonSnippetUsage = `import { Button } from '@backstage/ui'; + +`; + +export const buttonResponsiveSnippet = ``; + +export const variantsSnippet = ` + + + +`; + +export const sizesSnippet = ` + + +`; + +export const withIconsSnippet = ` + + + +`; + +export const disabledSnippet = ` + + + +`; + +export const destructiveSnippet = ` + + + +`; + +export const loadingSnippet = ``; + +export const asLinkSnippet = ` + + Button + +`; diff --git a/docs-ui/src/app/components/card/components.tsx b/docs-ui/src/app/components/card/components.tsx new file mode 100644 index 0000000000..c4d1f809c2 --- /dev/null +++ b/docs-ui/src/app/components/card/components.tsx @@ -0,0 +1,131 @@ +'use client'; + +import { + Card, + CardHeader, + CardBody, + CardFooter, +} from '../../../../../packages/ui/src/components/Card/Card'; +import { Text } from '../../../../../packages/ui/src/components/Text/Text'; +import { Button } from '../../../../../packages/ui/src/components/Button/Button'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; + +export const Default = () => { + return ( + + Header + Body + Footer + + ); +}; + +export const HeaderAndBody = () => { + return ( + + Header + Body content without a footer + + ); +}; + +export const InteractiveButton = () => { + return ( + {}} + label="View component details" + > + Interactive Card + + Click anywhere on this card to trigger the press handler. + + + + Click to interact + + + + ); +}; + +export const InteractiveLink = () => { + return ( + + Link Card + This card navigates to a URL when clicked. + + + Opens backstage.io + + + + ); +}; + +export const InteractiveWithNestedButtons = () => { + return ( + {}} + label="View plugin details" + > + Card with Actions + + Clicking the card background triggers the card press handler. The + buttons below remain independently interactive. + + + + + + + + + ); +}; + +export const WithLongBody = () => { + return ( + + + Header + + + + This is the first paragraph of a long body text that demonstrates how + the Card component handles extensive content. The card should adjust + accordingly to display all the text properly while maintaining its + structure. + + + Here's a second paragraph that adds more content to our card + body. Having multiple paragraphs helps to visualize how spacing works + within the card component. + + + This third paragraph continues to add more text to ensure we have a + proper demonstration of a card with significant content. This makes it + easier to test scrolling behavior and overall layout when content + exceeds the initial view. + + + + Footer + + + ); +}; diff --git a/docs-ui/src/app/components/card/page.mdx b/docs-ui/src/app/components/card/page.mdx new file mode 100644 index 0000000000..eb7ea2f7e4 --- /dev/null +++ b/docs-ui/src/app/components/card/page.mdx @@ -0,0 +1,141 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { + cardPropDefs, + cardHeaderPropDefs, + cardBodyPropDefs, + cardFooterPropDefs, +} from './props-definition'; +import { + cardUsageSnippet, + defaultSnippet, + headerAndBodySnippet, + withLongBodySnippet, + interactiveButtonSnippet, + interactiveLinkSnippet, + interactiveWithNestedButtonsSnippet, +} from './snippets'; +import { + Default, + HeaderAndBody, + WithLongBody, + InteractiveButton, + InteractiveLink, + InteractiveWithNestedButtons, +} from './components'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { CardDefinition } from '../../../utils/definitions'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + + + +} code={defaultSnippet} /> + +## Usage + + + +## API reference + +All Card components extend `HTMLDivElement` attributes. + +### Card + + + +### CardHeader + +Fixed at the top of the card. + + + +### CardBody + +Scrollable content area that fills available space. + + + +### CardFooter + +Fixed at the bottom of the card. + + + +## Examples + +### Header and body only + +Cards can omit the footer section. + +} + code={headerAndBodySnippet} +/> + +### Scrollable body + +When body content exceeds the available height, CardBody scrolls while header and footer remain fixed. + +} + code={withLongBodySnippet} +/> + +## Interactive cards + +Cards can be made interactive without wrapping the entire card in a button or link — which would conflict with any interactive elements inside. Instead, a transparent overlay covers the card surface, and nested buttons and links remain independently clickable above it. + +### Button + +Pass `onPress` and a `label` (used as the accessible name for screen readers) to make the whole card surface pressable. + +} + code={interactiveButtonSnippet} +/> + +### Link + +Pass `href` to make the card surface navigate to a URL. `label` is required — it provides the accessible name for the invisible overlay link read by screen readers. + +} + code={interactiveLinkSnippet} +/> + +### With nested buttons + +Buttons and links inside the card remain independently interactive. Clicking them does not trigger the card's `onPress` handler. + +} + code={interactiveWithNestedButtonsSnippet} +/> + + + + diff --git a/docs-ui/src/app/components/card/props-definition.ts b/docs-ui/src/app/components/card/props-definition.ts new file mode 100644 index 0000000000..7e68d890b6 --- /dev/null +++ b/docs-ui/src/app/components/card/props-definition.ts @@ -0,0 +1,57 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; + +const optionalChildrenPropDef: Record = { + children: { + type: 'enum', + values: ['ReactNode'], + responsive: false, + description: 'Content to display inside the component.', + }, +}; + +export const cardPropDefs: Record = { + ...optionalChildrenPropDef, + onPress: { + type: 'enum', + values: ['() => void'], + responsive: false, + description: + 'Handler called when the card is pressed. Makes the card interactive as a button. Requires label.', + }, + href: { + type: 'string', + responsive: false, + description: + 'URL to navigate to. Makes the card interactive as a link. Mutually exclusive with onPress.', + }, + label: { + type: 'string', + responsive: false, + description: + 'Accessible label announced by screen readers for the interactive overlay. Required when onPress or href is provided.', + }, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const cardHeaderPropDefs: Record = { + ...optionalChildrenPropDef, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const cardBodyPropDefs: Record = { + ...optionalChildrenPropDef, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const cardFooterPropDefs: Record = { + ...optionalChildrenPropDef, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/card/snippets.ts b/docs-ui/src/app/components/card/snippets.ts new file mode 100644 index 0000000000..86b9df5e0a --- /dev/null +++ b/docs-ui/src/app/components/card/snippets.ts @@ -0,0 +1,92 @@ +export const cardUsageSnippet = `import { Card, CardHeader, CardBody, CardFooter } from '@backstage/ui'; + + + Header + Body + Footer +`; + +export const defaultSnippet = ` + Header + Body + Footer +`; + +export const headerAndBodySnippet = ` + Header + Body content without a footer +`; + +export const interactiveButtonSnippet = ` console.log('Card pressed')} + label="View component details" +> + Interactive Card + Click anywhere on this card to trigger the press handler. + Click to interact +`; + +export const interactiveLinkSnippet = ` + Link Card + This card navigates to a URL when clicked. + Opens backstage.io +`; + +export const interactiveWithNestedButtonsSnippet = `import { Button, Flex } from '@backstage/ui'; + + console.log('Card pressed')} + label="View plugin details" +> + Card with Actions + + Clicking the card background triggers the card press handler. + The buttons below remain independently interactive. + + + + + + + +`; + +export const withLongBodySnippet = `import { Text } from '@backstage/ui'; + + + + Header + + + + This is the first paragraph of a long body text that demonstrates how + the Card component handles extensive content. The card should adjust + accordingly to display all the text properly while maintaining its + structure. + + + Here's a second paragraph that adds more content to our card body. + Having multiple paragraphs helps to visualize how spacing works within + the card component. + + + This third paragraph continues to add more text to ensure we have a + proper demonstration of a card with significant content. This makes it + easier to test scrolling behavior and overall layout when content + exceeds the initial view. + + + + Footer + +`; diff --git a/docs-ui/src/app/components/checkbox-group/components.tsx b/docs-ui/src/app/components/checkbox-group/components.tsx new file mode 100644 index 0000000000..4d32bdaf75 --- /dev/null +++ b/docs-ui/src/app/components/checkbox-group/components.tsx @@ -0,0 +1,81 @@ +'use client'; + +import { CheckboxGroup } from '../../../../../packages/ui/src/components/CheckboxGroup/CheckboxGroup'; +import { Checkbox } from '../../../../../packages/ui/src/components/Checkbox/Checkbox'; + +export const Default = () => { + return ( + + GitHub + Slack + Email + + ); +}; + +export const Horizontal = () => ( + + GitHub + Slack + Email + +); + +export const Disabled = () => ( + + GitHub + Slack + Email + +); + +export const DisabledSingle = () => ( + + GitHub + + Slack + + Email + +); + +export const Validation = () => ( + + value.includes('slack') ? 'Slack is not available in your region.' : null + } + > + GitHub + Slack + Email + +); + +export const ReadOnly = () => ( + + GitHub + Slack + Email + +); diff --git a/docs-ui/src/app/components/checkbox-group/page.mdx b/docs-ui/src/app/components/checkbox-group/page.mdx new file mode 100644 index 0000000000..2ff41a5d9b --- /dev/null +++ b/docs-ui/src/app/components/checkbox-group/page.mdx @@ -0,0 +1,110 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; +import { checkboxGroupPropDefs } from './props-definition'; +import { + checkboxGroupUsageSnippet, + defaultSnippet, + horizontalSnippet, + disabledSnippet, + disabledSingleSnippet, + validationSnippet, + readOnlySnippet, +} from './snippets'; +import { + Default, + Horizontal, + Disabled, + DisabledSingle, + Validation, + ReadOnly, +} from './components'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { CodeBlock } from '@/components/CodeBlock'; +import { CheckboxGroupDefinition } from '../../../utils/definitions'; + +export const reactAriaUrls = { + checkboxGroup: 'https://react-aria.adobe.com/CheckboxGroup', +}; + + + +} code={defaultSnippet} /> + +## Usage + + + +## API reference + +### CheckboxGroup + + + + + +## Examples + +### Horizontal + +} + code={horizontalSnippet} +/> + +### Disabled + +} + code={disabledSnippet} +/> + +### Disabled single checkbox + +} + code={disabledSingleSnippet} +/> + +### Validation + +} + code={validationSnippet} +/> + +### Read only + +} + code={readOnlySnippet} +/> + + + + diff --git a/docs-ui/src/app/components/checkbox-group/props-definition.tsx b/docs-ui/src/app/components/checkbox-group/props-definition.tsx new file mode 100644 index 0000000000..09b5aeda4b --- /dev/null +++ b/docs-ui/src/app/components/checkbox-group/props-definition.tsx @@ -0,0 +1,82 @@ +import { + classNamePropDefs, + childrenPropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +export const checkboxGroupPropDefs: Record = { + label: { + type: 'string', + description: 'The visible label for the checkbox group.', + }, + 'aria-label': { + type: 'string', + description: + 'Accessible label when a visible label is not provided. Either label, aria-label, or aria-labelledby is required.', + }, + 'aria-labelledby': { + type: 'string', + description: + 'ID of an element that labels the checkbox group. Either label, aria-label, or aria-labelledby is required.', + }, + secondaryLabel: { + type: 'string', + description: ( + <> + Secondary label text. Defaults to Required when isRequired + is true. + + ), + }, + description: { + type: 'string', + description: 'Helper text displayed below the label.', + }, + orientation: { + type: 'enum', + values: ['horizontal', 'vertical'], + default: 'vertical', + description: 'The axis the checkboxes should align with.', + }, + value: { + type: 'enum', + values: ['string[]'], + description: 'The selected values (controlled).', + }, + defaultValue: { + type: 'enum', + values: ['string[]'], + description: 'The initial selected values (uncontrolled).', + }, + onChange: { + type: 'enum', + values: ['(value: string[]) => void'], + description: 'Handler called when the selected values change.', + }, + isDisabled: { + type: 'boolean', + description: 'Whether all checkboxes in the group are disabled.', + }, + isReadOnly: { + type: 'boolean', + description: 'Whether all checkboxes in the group are read-only.', + }, + isRequired: { + type: 'boolean', + description: + 'Whether at least one selection is required for form submission.', + }, + isInvalid: { + type: 'boolean', + description: 'Whether the checkbox group is in an invalid state.', + }, + name: { + type: 'string', + description: 'The name used for form submission.', + }, + ...childrenPropDefs, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/checkbox-group/snippets.ts b/docs-ui/src/app/components/checkbox-group/snippets.ts new file mode 100644 index 0000000000..70104a2458 --- /dev/null +++ b/docs-ui/src/app/components/checkbox-group/snippets.ts @@ -0,0 +1,65 @@ +export const checkboxGroupUsageSnippet = `import { CheckboxGroup, Checkbox } from '@backstage/ui'; + + + GitHub + Slack + Email +`; + +export const defaultSnippet = ` + GitHub + Slack + Email +`; + +export const horizontalSnippet = ` + GitHub + Slack + Email +`; + +export const disabledSnippet = ` + GitHub + Slack + Email +`; + +export const disabledSingleSnippet = ` + GitHub + Slack + Email +`; + +export const validationSnippet = ` + value.includes('slack') ? 'Slack is not available in your region.' : null + } +> + GitHub + Slack + Email +`; + +export const readOnlySnippet = ` + GitHub + Slack + Email +`; diff --git a/docs-ui/src/app/components/checkbox/components.tsx b/docs-ui/src/app/components/checkbox/components.tsx new file mode 100644 index 0000000000..a826c9e02f --- /dev/null +++ b/docs-ui/src/app/components/checkbox/components.tsx @@ -0,0 +1,21 @@ +'use client'; + +import { Checkbox } from '../../../../../packages/ui/src/components/Checkbox/Checkbox'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; + +export const Default = () => { + return Accept terms and conditions; +}; + +export const AllVariants = () => { + return ( + + Unchecked + Checked + Disabled + + Checked & Disabled + + + ); +}; diff --git a/docs-ui/src/content/checkbox.mdx b/docs-ui/src/app/components/checkbox/page.mdx similarity index 50% rename from docs-ui/src/content/checkbox.mdx rename to docs-ui/src/app/components/checkbox/page.mdx index c574e8fed4..9657b471da 100644 --- a/docs-ui/src/content/checkbox.mdx +++ b/docs-ui/src/app/components/checkbox/page.mdx @@ -1,29 +1,29 @@ import { PropsTable } from '@/components/PropsTable'; -import { CheckboxSnippet } from '@/snippets/stories-snippets'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; +import { checkboxPropDefs } from './props-definition'; import { - checkboxPropDefs, checkboxUsageSnippet, - checkboxDefaultSnippet, - checkboxVariantsSnippet, -} from './checkbox.props'; + defaultSnippet, + allVariantsSnippet, +} from './snippets'; +import { Default, AllVariants } from './components'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; -import { CheckboxDefinition } from '../utils/definitions'; +import { CheckboxDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; + +export const reactAriaUrls = { + checkbox: 'https://react-aria.adobe.com/Checkbox', +}; -} - code={checkboxDefaultSnippet} -/> +} code={defaultSnippet} /> ## Usage @@ -33,18 +33,18 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; + + ## Examples ### All variants -Here's a view when checkboxes have different variants. - } - code={checkboxVariantsSnippet} + preview={} + code={allVariantsSnippet} /> diff --git a/docs-ui/src/app/components/checkbox/props-definition.ts b/docs-ui/src/app/components/checkbox/props-definition.ts new file mode 100644 index 0000000000..fd4b47ff8d --- /dev/null +++ b/docs-ui/src/app/components/checkbox/props-definition.ts @@ -0,0 +1,49 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const checkboxPropDefs: Record = { + children: { + type: 'enum', + values: ['ReactNode'], + required: true, + description: 'Label displayed next to the checkbox.', + }, + isSelected: { + type: 'boolean', + description: 'Controls checked state (controlled mode).', + }, + defaultSelected: { + type: 'boolean', + description: 'Initial checked state (uncontrolled mode).', + }, + onChange: { + type: 'enum', + values: ['(isSelected: boolean) => void'], + description: 'Called when the checked state changes.', + }, + isDisabled: { + type: 'boolean', + description: 'Prevents interaction and applies disabled styling.', + }, + isRequired: { + type: 'boolean', + description: 'Marks the checkbox as required for form validation.', + }, + isIndeterminate: { + type: 'boolean', + description: 'Shows a mixed state, typically for "select all" checkboxes.', + }, + name: { + type: 'string', + description: 'Name attribute for form submission.', + }, + value: { + type: 'string', + description: 'Value attribute for form submission.', + }, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/checkbox/snippets.ts b/docs-ui/src/app/components/checkbox/snippets.ts new file mode 100644 index 0000000000..bef496195f --- /dev/null +++ b/docs-ui/src/app/components/checkbox/snippets.ts @@ -0,0 +1,12 @@ +export const checkboxUsageSnippet = `import { Checkbox } from '@backstage/ui'; + +Accept terms`; + +export const defaultSnippet = `Accept terms and conditions`; + +export const allVariantsSnippet = ` + Unchecked + Checked + Disabled + Checked & Disabled +`; diff --git a/docs-ui/src/app/components/container/components.tsx b/docs-ui/src/app/components/container/components.tsx new file mode 100644 index 0000000000..a11bbd9463 --- /dev/null +++ b/docs-ui/src/app/components/container/components.tsx @@ -0,0 +1,20 @@ +'use client'; + +import { Container } from '../../../../../packages/ui/src/components/Container/Container'; +import { DecorativeBox } from '@/components/DecorativeBox'; + +export const Default = () => { + return ( + + Page content goes here + + ); +}; + +export const ResponsiveSpacing = () => { + return ( + + Content with vertical spacing + + ); +}; diff --git a/docs-ui/src/app/components/container/page.mdx b/docs-ui/src/app/components/container/page.mdx new file mode 100644 index 0000000000..e0f4014a07 --- /dev/null +++ b/docs-ui/src/app/components/container/page.mdx @@ -0,0 +1,46 @@ +import { CodeBlock } from '@/components/CodeBlock'; +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { containerPropDefs } from './props-definition'; +import { + containerUsageSnippet, + defaultSnippet, + containerResponsiveSnippet, +} from './snippets'; +import { Default, ResponsiveSpacing } from './components'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ContainerDefinition } from '../../../utils/definitions'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + + + +} code={defaultSnippet} /> + +## Usage + + + +## Core Concepts + +Container provides the standard page layout for plugin content. It constrains content to a maximum width, centers it horizontally, and adds consistent horizontal gutters. Use it once per page to wrap your main content area. + +## API Reference + + + +## Examples + +### Responsive spacing + +Vertical spacing props accept breakpoint objects. + +} code={containerResponsiveSnippet} /> + + + + diff --git a/docs-ui/src/app/components/container/props-definition.ts b/docs-ui/src/app/components/container/props-definition.ts new file mode 100644 index 0000000000..89a51eac5c --- /dev/null +++ b/docs-ui/src/app/components/container/props-definition.ts @@ -0,0 +1,20 @@ +import { + classNamePropDefs, + stylePropDefs, + createSpacingGroup, + type PropDef, +} from '@/utils/propDefs'; + +export const containerPropDefs: Record = { + children: { + type: 'enum', + values: ['ReactNode'], + description: 'Content to render inside the container.', + }, + ...classNamePropDefs, + ...stylePropDefs, + spacing: createSpacingGroup( + ['my', 'mt', 'mb', 'py', 'pt', 'pb'], + 'Vertical spacing properties for controlling margin and padding.', + ), +}; diff --git a/docs-ui/src/app/components/container/snippets.ts b/docs-ui/src/app/components/container/snippets.ts new file mode 100644 index 0000000000..a314b52387 --- /dev/null +++ b/docs-ui/src/app/components/container/snippets.ts @@ -0,0 +1,13 @@ +export const containerUsageSnippet = `import { Container } from "@backstage/ui"; + + + {/* Your plugin's main content */} +`; + +export const defaultSnippet = ` + Page content goes here +`; + +export const containerResponsiveSnippet = ` + Content with vertical spacing +`; diff --git a/docs-ui/src/app/components/dialog/components.tsx b/docs-ui/src/app/components/dialog/components.tsx new file mode 100644 index 0000000000..18ab1bf9c5 --- /dev/null +++ b/docs-ui/src/app/components/dialog/components.tsx @@ -0,0 +1,118 @@ +'use client'; + +import { + Dialog, + DialogTrigger, + DialogHeader, + DialogBody, + DialogFooter, +} from '../../../../../packages/ui/src/components/Dialog/Dialog'; +import { Button } from '../../../../../packages/ui/src/components/Button/Button'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; +import { Text } from '../../../../../packages/ui/src/components/Text/Text'; +import { TextField } from '../../../../../packages/ui/src/components/TextField/TextField'; +import { Select } from '../../../../../packages/ui/src/components/Select/Select'; + +export const Default = () => ( + + + + Example Dialog + + This is a basic dialog example. + + + + + + + +); + +export const PreviewFixedWidthAndHeight = () => ( + + + + Long Content Dialog + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do + eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim + ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut + aliquip ex ea commodo consequat. + + + Duis aute irure dolor in reprehenderit in voluptate velit esse + cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat + cupidatat non proident, sunt in culpa qui officia deserunt mollit + anim id est laborum. + + + Sed ut perspiciatis unde omnis iste natus error sit voluptatem + accusantium doloremque laudantium, totam rem aperiam, eaque ipsa + quae ab illo inventore veritatis et quasi architecto beatae vitae + dicta sunt explicabo. + + + Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut + fugit, sed quia consequuntur magni dolores eos qui ratione + voluptatem sequi nesciunt. + + + Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, + consectetur, adipisci velit, sed quia non numquam eius modi tempora + incidunt ut labore et dolore magnam aliquam quaerat voluptatem. + + + Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis + suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur. + + + + + + + + + +); + +export const PreviewWithForm = () => ( + + + + Create New User + + + + + + + + + + + + +`; + +export const dialogWithNoTriggerSnippet = `const [isOpen, setIsOpen] = useState(false); + + + Create New User + + Your content + + + + + +`; + +export const dialogCloseSnippet = ``; diff --git a/docs-ui/src/app/components/flex/components.tsx b/docs-ui/src/app/components/flex/components.tsx new file mode 100644 index 0000000000..ce6cc8c05f --- /dev/null +++ b/docs-ui/src/app/components/flex/components.tsx @@ -0,0 +1,44 @@ +'use client'; + +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; +import { DecorativeBox } from '@/components/DecorativeBox'; + +export const Default = () => { + return ( + + + + + + ); +}; + +export const DirectionExample = () => { + return ( + + First + Second + Third + + ); +}; + +export const ResponsiveExample = () => { + return ( + + 1 + 2 + 3 + + ); +}; + +export const AlignExample = () => { + return ( + + Start + Middle + End + + ); +}; diff --git a/docs-ui/src/app/components/flex/page.mdx b/docs-ui/src/app/components/flex/page.mdx new file mode 100644 index 0000000000..f321b54583 --- /dev/null +++ b/docs-ui/src/app/components/flex/page.mdx @@ -0,0 +1,75 @@ +import { PropsTable } from '@/components/PropsTable'; +import { CodeBlock } from '@/components/CodeBlock'; +import { Snippet } from '@/components/Snippet'; +import { flexPropDefs } from './props-definition'; +import { + flexUsageSnippet, + defaultSnippet, + flexResponsiveSnippet, + flexAlignSnippet, + flexDirectionSnippet, +} from './snippets'; +import { + Default, + ResponsiveExample, + AlignExample, + DirectionExample, +} from './components'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { FlexDefinition } from '../../../utils/definitions'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + + + +} code={defaultSnippet} /> + +## Usage + + + +## API reference + + + +## Examples + +### Direction + +} + code={flexDirectionSnippet} + layout="side-by-side" +/> + +### Responsive gap + +Gap values can be responsive using breakpoint objects. + +} + code={flexResponsiveSnippet} + layout="side-by-side" +/> + +### Alignment + +} + code={flexAlignSnippet} + layout="side-by-side" +/> + + + + diff --git a/docs-ui/src/app/components/flex/props-definition.tsx b/docs-ui/src/app/components/flex/props-definition.tsx new file mode 100644 index 0000000000..2136dd0498 --- /dev/null +++ b/docs-ui/src/app/components/flex/props-definition.tsx @@ -0,0 +1,66 @@ +import { + classNamePropDefs, + stylePropDefs, + gapPropDefs, + spacingGroupAll, + type PropDef, +} from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +export const flexPropDefs: Record = { + direction: { + type: 'enum', + values: ['row', 'column', 'row-reverse', 'column-reverse'], + responsive: true, + description: ( + <> + Main axis direction. Use row for horizontal,{' '} + column for vertical layouts. + + ), + }, + align: { + type: 'enum', + values: ['start', 'center', 'end', 'baseline', 'stretch'], + responsive: true, + description: + 'Cross-axis alignment. Controls how children align perpendicular to the main axis.', + }, + justify: { + type: 'enum', + values: ['start', 'center', 'end', 'between'], + responsive: true, + description: ( + <> + Main-axis distribution. Use between to space children + evenly with no edge gaps. + + ), + }, + gap: { + ...gapPropDefs.gap, + default: '4', + description: + 'Space between children. Accepts spacing scale values or responsive objects.', + }, + surface: { + type: 'enum', + values: ['0', '1', '2', '3', 'danger', 'warning', 'success', 'auto'], + responsive: true, + description: ( + <> + Surface level for theming. Use auto to increment from + parent context. + + ), + }, + children: { + type: 'enum', + values: ['ReactNode'], + responsive: false, + description: 'Content to render inside the flex container.', + }, + ...classNamePropDefs, + ...stylePropDefs, + spacing: spacingGroupAll, +}; diff --git a/docs-ui/src/app/components/flex/snippets.ts b/docs-ui/src/app/components/flex/snippets.ts new file mode 100644 index 0000000000..8770880c79 --- /dev/null +++ b/docs-ui/src/app/components/flex/snippets.ts @@ -0,0 +1,31 @@ +export const flexUsageSnippet = `import { Flex } from '@backstage/ui'; + + +
Item 1
+
Item 2
+
Item 3
+
`; + +export const defaultSnippet = ` + + + +`; + +export const flexDirectionSnippet = ` + First + Second + Third +`; + +export const flexResponsiveSnippet = ` + 1 + 2 + 3 +`; + +export const flexAlignSnippet = ` + Start + Middle + End +`; diff --git a/docs-ui/src/app/components/grid/components.tsx b/docs-ui/src/app/components/grid/components.tsx new file mode 100644 index 0000000000..0c0e5a0546 --- /dev/null +++ b/docs-ui/src/app/components/grid/components.tsx @@ -0,0 +1,40 @@ +'use client'; + +import { Grid } from '../../../../../packages/ui/src/components/Grid/Grid'; +import { DecorativeBox } from '@/components/DecorativeBox'; + +export const Default = () => { + return ( + + + + + + + + + ); +}; + +export const ResponsiveExample = () => { + return ( + + 1 + 2 + 3 + 4 + + ); +}; + +export const GridItemExample = () => { + return ( + + + Spans 2 columns + + 1 column + 1 column + + ); +}; diff --git a/docs-ui/src/app/components/grid/page.mdx b/docs-ui/src/app/components/grid/page.mdx new file mode 100644 index 0000000000..488a67ca0e --- /dev/null +++ b/docs-ui/src/app/components/grid/page.mdx @@ -0,0 +1,64 @@ +import { CodeBlock } from '@/components/CodeBlock'; +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { gridPropDefs, gridItemPropDefs } from './props-definition'; +import { + gridUsageSnippet, + defaultSnippet, + gridResponsiveSnippet, + gridItemSnippet, +} from './snippets'; +import { Default, ResponsiveExample, GridItemExample } from './components'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { GridDefinition } from '../../../utils/definitions'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + + + +} code={defaultSnippet} /> + +## Usage + + + +## API reference + +### Grid.Root + +The grid container. Defines column count and gap between items. + + + +### Grid.Item + +A grid child with column and row spanning control. + + + +## Examples + +### Responsive columns + +Column count can change at different breakpoints. + +} + code={gridResponsiveSnippet} + open +/> + +### Column spanning + +Use Grid.Item to span multiple columns. + +} code={gridItemSnippet} open /> + + + + diff --git a/docs-ui/src/app/components/grid/props-definition.tsx b/docs-ui/src/app/components/grid/props-definition.tsx new file mode 100644 index 0000000000..4622f8f614 --- /dev/null +++ b/docs-ui/src/app/components/grid/props-definition.tsx @@ -0,0 +1,115 @@ +import { + childrenPropDefs, + classNamePropDefs, + gapPropDefs, + stylePropDefs, + spacingGroupAll, + type PropDef, +} from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +const columnValues = [ + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + '10', + '11', + '12', + 'auto', +]; + +const surfaceValues = [ + '0', + '1', + '2', + '3', + 'danger', + 'warning', + 'success', + 'auto', +]; + +export const gridPropDefs: Record = { + columns: { + type: 'enum', + values: columnValues, + default: 'auto', + responsive: true, + description: ( + <> + Number of columns. Use 1-12 for fixed layouts, auto to fit + content. + + ), + }, + gap: { + ...gapPropDefs.gap, + default: '4', + description: + 'Space between items. Use higher values for separated layouts, lower for compact.', + }, + surface: { + type: 'enum', + values: surfaceValues, + responsive: true, + description: ( + <> + Surface level for theming. Use auto to increment from + parent context. + + ), + }, + ...childrenPropDefs, + ...classNamePropDefs, + ...stylePropDefs, + spacing: spacingGroupAll, +}; + +export const gridItemPropDefs: Record = { + colSpan: { + type: 'enum', + values: columnValues, + responsive: true, + description: 'Number of columns the item spans across.', + }, + colStart: { + type: 'enum', + values: columnValues, + responsive: true, + description: + 'Starting column position. Use with colEnd for explicit placement.', + }, + colEnd: { + type: 'enum', + values: columnValues, + responsive: true, + description: + 'Ending column position. Use with colStart for explicit placement.', + }, + rowSpan: { + type: 'enum', + values: columnValues, + responsive: true, + description: 'Number of rows the item spans. Useful for tall content.', + }, + surface: { + type: 'enum', + values: surfaceValues, + responsive: true, + description: ( + <> + Surface level for theming. Use auto to increment from + parent context. + + ), + }, + ...childrenPropDefs, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/grid/snippets.ts b/docs-ui/src/app/components/grid/snippets.ts new file mode 100644 index 0000000000..2915a2ba4b --- /dev/null +++ b/docs-ui/src/app/components/grid/snippets.ts @@ -0,0 +1,31 @@ +export const gridUsageSnippet = `import { Grid } from '@backstage/ui'; + + +
Item 1
+
Item 2
+
Item 3
+
`; + +export const defaultSnippet = ` + + + + + + +`; + +export const gridResponsiveSnippet = ` + 1 + 2 + 3 + 4 +`; + +export const gridItemSnippet = ` + + Spans 2 columns + + 1 column + 1 column +`; diff --git a/docs-ui/src/app/components/header/components.tsx b/docs-ui/src/app/components/header/components.tsx new file mode 100644 index 0000000000..26dc715a5d --- /dev/null +++ b/docs-ui/src/app/components/header/components.tsx @@ -0,0 +1,81 @@ +'use client'; + +import { Header } from '../../../../../packages/ui/src/components/Header/Header'; +import { Button } from '../../../../../packages/ui/src/components/Button/Button'; +import { ButtonIcon } from '../../../../../packages/ui/src/components/ButtonIcon/ButtonIcon'; +import { + MenuTrigger, + Menu, + MenuItem, +} from '../../../../../packages/ui/src/components/Menu/Menu'; +import { MemoryRouter } from 'react-router-dom'; +import { RiMore2Line } from '@remixicon/react'; + +const tabs = [ + { id: 'overview', label: 'Overview', href: '/overview' }, + { id: 'checks', label: 'Checks', href: '/checks' }, + { id: 'tracks', label: 'Tracks', href: '/tracks' }, + { id: 'campaigns', label: 'Campaigns', href: '/campaigns' }, + { id: 'integrations', label: 'Integrations', href: '/integrations' }, +]; + +const breadcrumbs = [ + { label: 'Home', href: '/' }, + { label: 'Long Breadcrumb Name', href: '/long-breadcrumb' }, + { label: 'Another Long Breadcrumb', href: '/another-long-breadcrumb' }, + { + label: 'Yet Another Long Breadcrumb', + href: '/yet-another-long-breadcrumb', + }, +]; + +export const WithEverything = () => ( + +
+ + + + } + /> + +); + +export const WithLongBreadcrumbs = () => ( + +
+ +); + +export const WithTabs = () => ( + +
+ +); + +export const WithCustomActions = () => ( + +
Custom action} /> + +); + +export const WithMenu = () => ( + +
+ } /> + + Settings + alert('logout')}>Logout + + + } + /> + +); diff --git a/docs-ui/src/app/components/header/page.mdx b/docs-ui/src/app/components/header/page.mdx new file mode 100644 index 0000000000..9e02b70a70 --- /dev/null +++ b/docs-ui/src/app/components/header/page.mdx @@ -0,0 +1,66 @@ +import { PropsTable } from '@/components/PropsTable'; +import { CodeBlock } from '@/components/CodeBlock'; +import { Snippet } from '@/components/Snippet'; +import { + WithEverything, + WithLongBreadcrumbs, + WithTabs, + WithCustomActions, + WithMenu, +} from './components'; +import { headerPagePropDefs } from './props-definition'; +import { + usage, + defaultSnippet, + withTabs, + withBreadcrumbs, + withCustomActions, + withMenu, +} from './snippets'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { HeaderDefinition } from '../../../utils/definitions'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + + + +} code={defaultSnippet} /> + +## Usage + + + +## API reference + + + +## Examples + +### Breadcrumbs + +Labels are truncated at 240px. + +} code={withBreadcrumbs} /> + +### Tabs + +Tabs auto-detect the active tab from the current route when `activeTabId` is omitted. Pass an explicit `activeTabId` to override, or `null` for no active tab. + +} code={withTabs} /> + +### Custom actions + +} code={withCustomActions} /> + +### With menu + +Use `customActions` to add a dropdown menu. + +} code={withMenu} /> + + + + diff --git a/docs-ui/src/app/components/header/props-definition.tsx b/docs-ui/src/app/components/header/props-definition.tsx new file mode 100644 index 0000000000..0acc7d30ae --- /dev/null +++ b/docs-ui/src/app/components/header/props-definition.tsx @@ -0,0 +1,70 @@ +import { classNamePropDefs, type PropDef } from '@/utils/propDefs'; + +export const headerPagePropDefs: Record = { + title: { + type: 'string', + description: 'Page heading displayed in the header.', + }, + customActions: { + type: 'enum', + values: ['ReactNode'], + description: 'Custom elements rendered in the actions area.', + }, + tabs: { + type: 'complex', + description: 'Navigation items displayed below the title.', + complexType: { + name: 'HeaderNavTabItem[]', + properties: { + id: { + type: 'string', + required: true, + description: 'Unique identifier for the tab.', + }, + label: { + type: 'string', + required: true, + description: 'Display text for the tab.', + }, + href: { + type: 'string', + required: false, + description: + 'URL to navigate to when tab is clicked. Present on flat tabs, absent on groups.', + }, + items: { + type: 'HeaderNavTab[]', + required: false, + description: + 'Child tabs rendered as a dropdown menu. Present on groups, absent on flat tabs.', + }, + }, + }, + }, + activeTabId: { + type: 'enum', + values: ['string', 'null'], + description: + 'ID of the currently active tab. Omit to auto-detect from the current route. Set to null for no active tab.', + }, + breadcrumbs: { + type: 'complex', + description: 'Breadcrumb trail displayed above the title.', + complexType: { + name: 'HeaderBreadcrumb[]', + properties: { + label: { + type: 'string', + required: true, + description: 'Display text for the breadcrumb. Truncated at 240px.', + }, + href: { + type: 'string', + required: true, + description: 'URL for the breadcrumb link.', + }, + }, + }, + }, + ...classNamePropDefs, +}; diff --git a/docs-ui/src/app/components/header/snippets.ts b/docs-ui/src/app/components/header/snippets.ts new file mode 100644 index 0000000000..8dfef0e69e --- /dev/null +++ b/docs-ui/src/app/components/header/snippets.ts @@ -0,0 +1,56 @@ +export const usage = `import { Header } from '@backstage/ui'; + +
`; + +export const defaultSnippet = `
+ + + + } +/>`; + +export const withBreadcrumbs = `
`; + +export const withTabs = `
`; + +export const withCustomActions = `
Custom action} +/>`; + +export const withMenu = `
+ } /> + + Settings + {}}>Logout + + + } +/>`; diff --git a/docs-ui/src/app/components/link/components.tsx b/docs-ui/src/app/components/link/components.tsx new file mode 100644 index 0000000000..320dd52517 --- /dev/null +++ b/docs-ui/src/app/components/link/components.tsx @@ -0,0 +1,115 @@ +'use client'; + +import { Link } from '../../../../../packages/ui/src/components/Link/Link'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; +import { MemoryRouter } from 'react-router-dom'; + +export const Default = () => { + return ( + + + Sign up for Backstage + + + ); +}; + +export const ExternalLink = () => { + return ( + + + Sign up for Backstage + + + ); +}; + +export const AllVariants = () => { + return ( + + + + title-large + + + title-medium + + + title-small + + + title-x-small + + + body-large + + + body-medium + + + body-small + + + body-x-small + + + + ); +}; + +export const AllColors = () => { + return ( + + + + Primary + + + Secondary + + + Danger + + + Warning + + + Success + + + Info + + + + ); +}; + +export const Weight = () => { + return ( + + + + Regular + + + Bold + + + + ); +}; + +export const Standalone = () => { + return ( + + + + Default link + + + Standalone link + + + + ); +}; diff --git a/docs-ui/src/app/components/link/page.mdx b/docs-ui/src/app/components/link/page.mdx new file mode 100644 index 0000000000..8715bb270b --- /dev/null +++ b/docs-ui/src/app/components/link/page.mdx @@ -0,0 +1,107 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; +import { linkPropDefs } from './props-definition'; +import { + linkUsageSnippet, + defaultSnippet, + externalLinkSnippet, + allVariantsSnippet, + allColorsSnippet, + weightSnippet, + standaloneSnippet, +} from './snippets'; +import { + Default, + ExternalLink, + AllVariants, + AllColors, + Weight, + Standalone, +} from './components'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { LinkDefinition } from '../../../utils/definitions'; + +export const reactAriaUrls = { + link: 'https://react-aria.adobe.com/Link', +}; + + + +} code={defaultSnippet} /> + +## Usage + + + +## API reference + + + + + +## Examples + +### External link + +Use `target="_blank"` to open links in a new tab. + +} + code={externalLinkSnippet} + layout="side-by-side" +/> + +### Variants + +} + code={allVariantsSnippet} +/> + +### Colors + +Status colors for contextual links. + +} + code={allColorsSnippet} +/> + +### Weight + +} + code={weightSnippet} + layout="side-by-side" +/> + +### Standalone + +Use `standalone` to remove the underline by default. The underline will appear on hover. + +} + code={standaloneSnippet} + layout="side-by-side" +/> + + + + diff --git a/docs-ui/src/app/components/link/props-definition.tsx b/docs-ui/src/app/components/link/props-definition.tsx new file mode 100644 index 0000000000..951d67e8b4 --- /dev/null +++ b/docs-ui/src/app/components/link/props-definition.tsx @@ -0,0 +1,84 @@ +import { + childrenPropDefs, + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +export const linkPropDefs: Record = { + href: { + type: 'string', + description: + 'URL the link navigates to. Supports internal and external URLs.', + }, + target: { + type: 'enum', + values: ['_self', '_blank', '_parent', '_top'], + description: ( + <> + Where to open the link. Use _blank for external links that + open in new tabs. + + ), + }, + title: { + type: 'string', + description: 'Tooltip text shown on hover. Useful for accessibility.', + }, + variant: { + type: 'enum', + values: [ + 'title-large', + 'title-medium', + 'title-small', + 'title-x-small', + 'body-large', + 'body-medium', + 'body-small', + 'body-x-small', + ], + default: 'body-medium', + responsive: true, + description: + 'Typography style. Title variants for headings, body for paragraph text.', + }, + weight: { + type: 'enum', + values: ['regular', 'bold'], + default: 'regular', + responsive: true, + description: ( + <> + Font weight. Use bold for emphasis. + + ), + }, + color: { + type: 'enum', + values: ['primary', 'secondary', 'danger', 'warning', 'success', 'info'], + default: 'primary', + responsive: true, + description: + 'Text color. Status colors (danger, warning, success) for contextual links.', + }, + truncate: { + type: 'boolean', + description: + 'Truncates text with ellipsis when it overflows its container.', + default: 'false', + }, + noTrack: { + type: 'boolean', + description: + 'Suppresses the automatic analytics click event, e.g. if you already have custom tracking.', + }, + standalone: { + type: 'boolean', + description: 'Removes underline by default. Underline appears on hover.', + default: 'false', + }, + ...childrenPropDefs, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/link/snippets.ts b/docs-ui/src/app/components/link/snippets.ts new file mode 100644 index 0000000000..386ad9f1a0 --- /dev/null +++ b/docs-ui/src/app/components/link/snippets.ts @@ -0,0 +1,39 @@ +export const linkUsageSnippet = `import { Link } from '@backstage/ui'; + +Sign up for Backstage`; + +export const defaultSnippet = `Sign up for Backstage`; + +export const externalLinkSnippet = ` + Sign up for Backstage +`; + +export const allVariantsSnippet = ` + title-large + title-medium + title-small + title-x-small + body-large + body-medium + body-small + body-x-small +`; + +export const allColorsSnippet = ` + Primary + Secondary + Danger + Warning + Success + Info +`; + +export const weightSnippet = ` + Regular + Bold +`; + +export const standaloneSnippet = ` + Default link + Standalone link +`; diff --git a/docs-ui/src/app/components/list/components.tsx b/docs-ui/src/app/components/list/components.tsx new file mode 100644 index 0000000000..5630027f94 --- /dev/null +++ b/docs-ui/src/app/components/list/components.tsx @@ -0,0 +1,159 @@ +'use client'; + +import { + List, + ListRow, +} from '../../../../../packages/ui/src/components/List/List'; +import { MenuItem } from '../../../../../packages/ui/src/components/Menu/Menu'; +import { + TagGroup, + Tag, +} from '../../../../../packages/ui/src/components/TagGroup/TagGroup'; +import { useState } from 'react'; +import type { Selection } from 'react-aria-components'; +import { + RiJavascriptLine, + RiReactjsLine, + RiShipLine, + RiTerminalLine, + RiCodeLine, + RiDeleteBinLine, + RiEdit2Line, + RiShareBoxLine, +} from '@remixicon/react'; + +const items = [ + { + id: 'react', + label: 'React', + description: 'A JavaScript library for building user interfaces', + icon: , + tags: ['frontend', 'ui'], + }, + { + id: 'typescript', + label: 'TypeScript', + description: 'Typed superset of JavaScript', + icon: , + tags: ['typed', 'js'], + }, + { + id: 'javascript', + label: 'JavaScript', + description: 'The language of the web', + icon: , + tags: ['web'], + }, + { + id: 'rust', + label: 'Rust', + description: 'Systems programming with memory safety', + icon: , + tags: ['systems', 'fast'], + }, + { + id: 'go', + label: 'Go', + description: 'Simple, fast, and reliable', + icon: , + tags: ['backend'], + }, +]; + +const menuItems = ( + <> + }>Edit + }>Share + } color="danger"> + Delete + + +); + +export const Default = () => ( + + {item => ( + + {item.tags.map(tag => ( + {tag} + ))} + + } + > + {item.label} + + )} + +); + +export const WithIcons = () => ( + + {item => ( + + {item.label} + + )} + +); + +export const WithDescription = () => ( + + {item => ( + + {item.label} + + )} + +); + +export const SelectionModeSingle = () => { + const [selected, setSelected] = useState(new Set(['react'])); + + return ( + + {item => {item.label}} + + ); +}; + +export const SelectionModeMultiple = () => { + const [selected, setSelected] = useState( + new Set(['react', 'typescript']), + ); + + return ( + + {item => {item.label}} + + ); +}; + +export const Disabled = () => ( + + {item => {item.label}} + +); diff --git a/docs-ui/src/app/components/list/page.mdx b/docs-ui/src/app/components/list/page.mdx new file mode 100644 index 0000000000..26353de81c --- /dev/null +++ b/docs-ui/src/app/components/list/page.mdx @@ -0,0 +1,103 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; +import { + Default, + WithIcons, + WithDescription, + SelectionModeSingle, + SelectionModeMultiple, + Disabled, +} from './components'; +import { listPropDefs, listRowPropDefs } from './props-definition'; +import { + usage, + preview, + withIcons, + withDescription, + selectionModeSingle, + selectionModeMultiple, + disabled, +} from './snippets'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ListDefinition, ListRowDefinition } from '../../../utils/definitions'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + +export const reactAriaUrls = { + gridList: 'https://react-aria.adobe.com/GridList', +}; + + + +} code={preview} /> + +## Usage + + + +## API reference + +### List + +Container for a list of interactive rows. + + + + + +### ListRow + +Individual row within a List. + + + + + +## Examples + +### With icons + +} code={withIcons} /> + +### With description + +} + code={withDescription} +/> + +### Single selection + +} + code={selectionModeSingle} +/> + +### Multiple selection + +} + code={selectionModeMultiple} +/> + +### Disabled items + +} code={disabled} /> + + + + diff --git a/docs-ui/src/app/components/list/props-definition.tsx b/docs-ui/src/app/components/list/props-definition.tsx new file mode 100644 index 0000000000..b021fc6e0a --- /dev/null +++ b/docs-ui/src/app/components/list/props-definition.tsx @@ -0,0 +1,84 @@ +import { + classNamePropDefs, + childrenPropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const listPropDefs: Record = { + items: { + type: 'enum', + values: ['Iterable'], + description: 'Item objects in the collection.', + }, + renderEmptyState: { + type: 'enum', + values: ['(props: GridListRenderProps) => ReactNode'], + description: 'Content to display when the collection is empty.', + }, + selectionMode: { + type: 'enum', + values: ['none', 'single', 'multiple'], + description: 'The type of selection allowed.', + }, + selectedKeys: { + type: 'enum', + values: ['all', 'Iterable'], + description: 'The currently selected keys (controlled).', + }, + defaultSelectedKeys: { + type: 'enum', + values: ['all', 'Iterable'], + description: 'The initial selected keys (uncontrolled).', + }, + disabledKeys: { + type: 'enum', + values: ['Iterable'], + description: 'Keys of items that should be disabled.', + }, + onSelectionChange: { + type: 'enum', + values: ['(keys: Selection) => void'], + description: 'Handler called when the selection changes.', + }, + ...childrenPropDefs, + ...classNamePropDefs, +}; + +export const listRowPropDefs: Record = { + id: { + type: 'string', + description: 'Unique identifier for the row.', + }, + textValue: { + type: 'string', + description: + 'Text value for accessibility. Derived from children if string.', + }, + icon: { + type: 'enum', + values: ['ReactNode'], + description: 'Icon displayed before the row label.', + }, + description: { + type: 'string', + description: 'Secondary description text displayed below the label.', + }, + isDisabled: { + type: 'boolean', + description: 'Whether the row is disabled.', + }, + menuItems: { + type: 'enum', + values: ['ReactNode'], + description: + 'Menu items rendered inside an automatically managed dropdown. Pass MenuItem nodes.', + }, + customActions: { + type: 'enum', + values: ['ReactNode'], + description: + 'Custom action elements displayed on the right side of the row, e.g. tags.', + }, + ...childrenPropDefs, + ...classNamePropDefs, +}; diff --git a/docs-ui/src/app/components/list/snippets.ts b/docs-ui/src/app/components/list/snippets.ts new file mode 100644 index 0000000000..ccaaf7e28d --- /dev/null +++ b/docs-ui/src/app/components/list/snippets.ts @@ -0,0 +1,72 @@ +export const usage = `import { List, ListRow } from '@backstage/ui'; + + + {item => {item.label}} +`; + +export const preview = ` + {item => ( + + {item.tags.map(tag => ( + {tag} + ))} + + } + > + {item.label} + + )} +`; + +export const withIcons = ` + {item => ( + + {item.label} + + )} +`; + +export const withDescription = ` + {item => ( + + {item.label} + + )} +`; + +export const selectionModeSingle = `const [selected, setSelected] = useState(new Set(['react'])); + + + {item => {item.label}} +`; + +export const selectionModeMultiple = `const [selected, setSelected] = useState(new Set(['react', 'typescript'])); + + + {item => {item.label}} +`; + +export const disabled = ` + {item => {item.label}} +`; diff --git a/docs-ui/src/app/components/menu/components.tsx b/docs-ui/src/app/components/menu/components.tsx new file mode 100644 index 0000000000..fbb481f5ef --- /dev/null +++ b/docs-ui/src/app/components/menu/components.tsx @@ -0,0 +1,169 @@ +'use client'; + +import { + MenuTrigger, + Menu, + MenuItem, + MenuSection, + MenuSeparator, + SubmenuTrigger, + MenuAutocomplete, + MenuAutocompleteListbox, + MenuListBoxItem, +} from '../../../../../packages/ui/src/components/Menu/Menu'; +import { Button } from '../../../../../packages/ui/src/components/Button/Button'; +import { MemoryRouter } from 'react-router-dom'; +import { + RiChat1Line, + RiFileLine, + RiFolderLine, + RiImageLine, + RiSettingsLine, + RiShareBoxLine, +} from '@remixicon/react'; + +export const Preview = () => ( + + + + + Edit + Duplicate + Rename + + }>Share + }>Feedback + + + }>Settings + + Edit + Duplicate + Rename + + + + + +); + +export const PreviewSubmenu = () => ( + + + + + New File + + Open Recent + + File 1.txt + File 2.txt + + + Save + + + +); + +export const PreviewIcons = () => ( + + + + + }>New File + }>New Folder + }>New Image + + + +); + +export const PreviewLinks = () => ( + + + + + Home + About + Contact + + + +); + +export const PreviewSections = () => ( + + + + + + New + Open + + + Cut + Copy + Paste + + + + +); + +export const PreviewSeparators = () => ( + + + + + New + Open + + Save + Save As... + + + +); + +export const PreviewAutocompleteMenu = () => ( + + + + + Option 1 + Option 2 + Option 3 + + + +); + +export const PreviewAutocompleteListbox = () => ( + + + + + Option 1 + Option 2 + Option 3 + + + +); + +export const PreviewAutocompleteListboxMultiple = () => ( + + + + + Option 1 + Option 2 + Option 3 + + + +); diff --git a/docs-ui/src/app/components/menu/page.mdx b/docs-ui/src/app/components/menu/page.mdx new file mode 100644 index 0000000000..158598fe71 --- /dev/null +++ b/docs-ui/src/app/components/menu/page.mdx @@ -0,0 +1,242 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { + Preview, + PreviewSubmenu, + PreviewIcons, + PreviewLinks, + PreviewSections, + PreviewSeparators, + PreviewAutocompleteMenu, + PreviewAutocompleteListbox, + PreviewAutocompleteListboxMultiple, +} from './components'; +import { + menuTriggerPropDefs, + submenuTriggerPropDefs, + menuPropDefs, + menuListBoxPropDefs, + menuAutocompletePropDefs, + menuAutocompleteListboxPropDefs, + menuItemPropDefs, + menuListBoxItemPropDefs, + menuSectionPropDefs, + menuSeparatorPropDefs, +} from './props-definition'; +import { + usage, + preview, + submenu, + icons, + sections, + separators, + links, + autocomplete, + autocompleteListbox, + autocompleteListboxMultiple, +} from './snippets'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { MenuDefinition } from '../../../utils/definitions'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; + +export const reactAriaUrls = { + menuTrigger: 'https://react-aria.adobe.com/Menu#menutrigger', + submenuTrigger: 'https://react-aria.adobe.com/Menu#submenutrigger', + menu: 'https://react-aria.adobe.com/Menu', + menuItem: 'https://react-aria.adobe.com/Menu#menuitem', + menuSection: 'https://react-aria.adobe.com/Menu#menusection', + listBox: 'https://react-aria.adobe.com/ListBox', + listBoxItem: 'https://react-aria.adobe.com/ListBox#listboxitem', +}; + + + +} code={preview} /> + +## Usage + + + +### Triggers + +- `MenuTrigger` combines a trigger element with a menu popover. +- `SubmenuTrigger` combines a `MenuItem` with a nested submenu. + +### Containers + +- `Menu` contains menu items or sections. +- `MenuListBox` supports selection with checkmarks. +- `MenuAutocomplete` adds a search input to filter items. +- `MenuAutocompleteListbox` combines search with selection. + +### Items + +- `MenuItem` is an interactive action in the menu. +- `MenuListBoxItem` is a selectable item in a list box. + +### Grouping + +- `MenuSection` groups items with a title. +- `MenuSeparator` adds a horizontal divider. + +## API reference + +### MenuTrigger + +Accepts two children: the trigger element and a menu container. + + + + + +### SubmenuTrigger + +Accepts two children: a `MenuItem` and a menu container. + + + + + +### Menu + + + + + +### MenuListBox + + + + + +### MenuAutocomplete + + + + + +### MenuAutocompleteListbox + + + + + +### MenuItem + + + + + +### MenuListBoxItem + + + + + +### MenuSection + + + + + +### MenuSeparator + + + + + +## Examples + +### Nested navigation + +Submenus open to the right of their parent item. + +} + code={submenu} +/> + +### With icons + +} code={icons} /> + +### With links + +The `href` prop works with both internal and external links. + +} code={links} /> + +### With sections + +} + code={sections} +/> + +### With separators + +} + code={separators} +/> + +### With autocomplete + +} + code={autocomplete} +/> + +### With list box + +} + code={autocompleteListbox} +/> + +### Multiple selection + +Set `selectionMode="multiple"` to allow multiple selections. + +} + code={autocompleteListboxMultiple} +/> + + + + diff --git a/docs-ui/src/app/components/menu/props-definition.tsx b/docs-ui/src/app/components/menu/props-definition.tsx new file mode 100644 index 0000000000..f6cb88b3e2 --- /dev/null +++ b/docs-ui/src/app/components/menu/props-definition.tsx @@ -0,0 +1,369 @@ +import { classNamePropDefs, type PropDef } from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +export const menuTriggerPropDefs: Record = { + isOpen: { + type: 'boolean', + description: 'Controlled open state of the menu.', + }, + defaultOpen: { + type: 'boolean', + description: 'Whether the menu is open by default.', + }, + onOpenChange: { + type: 'enum', + values: ['(isOpen: boolean) => void'], + description: 'Handler called when the open state changes.', + }, +}; + +export const submenuTriggerPropDefs: Record = { + delay: { + type: 'number', + default: '200', + description: 'Delay in milliseconds before the submenu opens on hover.', + }, +}; + +export const menuPropDefs: Record = { + placement: { + type: 'enum', + values: [ + 'top', + 'bottom', + 'left', + 'right', + 'top start', + 'top end', + 'bottom start', + 'bottom end', + 'left start', + 'left end', + 'right start', + 'right end', + ], + description: 'Position of the menu relative to the trigger.', + }, + onAction: { + type: 'enum', + values: ['(key: Key) => void'], + description: + 'Handler called when an item is activated. Receives the item key.', + }, + selectionMode: { + type: 'enum', + values: ['none', 'single', 'multiple'], + description: 'How items can be selected.', + }, + selectedKeys: { + type: 'enum', + values: ['Iterable'], + description: 'Controlled selected keys.', + }, + defaultSelectedKeys: { + type: 'enum', + values: ['Iterable'], + description: 'Default selected keys for uncontrolled usage.', + }, + disabledKeys: { + type: 'enum', + values: ['Iterable'], + description: 'Keys of items that are disabled.', + }, + virtualized: { + type: 'boolean', + default: false, + description: 'Enable virtualization for large lists.', + }, + maxWidth: { + type: 'string', + description: 'Maximum width of the menu popover.', + }, + maxHeight: { + type: 'string', + description: 'Maximum height of the menu popover.', + }, + ...classNamePropDefs, +}; + +export const menuListBoxPropDefs: Record = { + placement: { + type: 'enum', + values: [ + 'top', + 'bottom', + 'left', + 'right', + 'top start', + 'top end', + 'bottom start', + 'bottom end', + 'left start', + 'left end', + 'right start', + 'right end', + ], + description: 'Position of the list box relative to the trigger.', + }, + selectionMode: { + type: 'enum', + values: ['none', 'single', 'multiple'], + description: 'How items can be selected.', + }, + selectedKeys: { + type: 'enum', + values: ['Iterable'], + description: 'Controlled selected keys.', + }, + defaultSelectedKeys: { + type: 'enum', + values: ['Iterable'], + description: 'Default selected keys for uncontrolled usage.', + }, + onSelectionChange: { + type: 'enum', + values: ['(keys: Selection) => void'], + description: 'Handler called when selection changes.', + }, + disabledKeys: { + type: 'enum', + values: ['Iterable'], + description: 'Keys of items that are disabled.', + }, + virtualized: { + type: 'boolean', + default: false, + description: 'Enable virtualization for large lists.', + }, + maxWidth: { + type: 'string', + description: 'Maximum width of the list box popover.', + }, + maxHeight: { + type: 'string', + description: 'Maximum height of the list box popover.', + }, + ...classNamePropDefs, +}; + +export const menuAutocompletePropDefs: Record = { + placeholder: { + type: 'string', + description: 'Placeholder text for the search input.', + }, + placement: { + type: 'enum', + values: [ + 'top', + 'bottom', + 'left', + 'right', + 'top start', + 'top end', + 'bottom start', + 'bottom end', + 'left start', + 'left end', + 'right start', + 'right end', + ], + description: 'Position of the menu relative to the trigger.', + }, + onAction: { + type: 'enum', + values: ['(key: Key) => void'], + description: + 'Handler called when an item is activated. Receives the item key.', + }, + selectionMode: { + type: 'enum', + values: ['none', 'single', 'multiple'], + description: 'How items can be selected.', + }, + selectedKeys: { + type: 'enum', + values: ['Iterable'], + description: 'Controlled selected keys.', + }, + defaultSelectedKeys: { + type: 'enum', + values: ['Iterable'], + description: 'Default selected keys for uncontrolled usage.', + }, + disabledKeys: { + type: 'enum', + values: ['Iterable'], + description: 'Keys of items that are disabled.', + }, + onSelectionChange: { + type: 'enum', + values: ['(keys: Selection) => void'], + description: 'Handler called when selection changes.', + }, + virtualized: { + type: 'boolean', + default: false, + description: 'Enable virtualization for large lists.', + }, + maxWidth: { + type: 'string', + description: 'Maximum width of the menu popover.', + }, + maxHeight: { + type: 'string', + description: 'Maximum height of the menu popover.', + }, + ...classNamePropDefs, +}; + +export const menuAutocompleteListboxPropDefs: Record = { + placeholder: { + type: 'string', + description: 'Placeholder text for the search input.', + }, + placement: { + type: 'enum', + values: [ + 'top', + 'bottom', + 'left', + 'right', + 'top start', + 'top end', + 'bottom start', + 'bottom end', + 'left start', + 'left end', + 'right start', + 'right end', + ], + description: 'Position of the list box relative to the trigger.', + }, + selectionMode: { + type: 'enum', + values: ['none', 'single', 'multiple'], + description: 'How items can be selected.', + }, + selectedKeys: { + type: 'enum', + values: ['Iterable'], + description: 'Controlled selected keys.', + }, + defaultSelectedKeys: { + type: 'enum', + values: ['Iterable'], + description: 'Default selected keys for uncontrolled usage.', + }, + onSelectionChange: { + type: 'enum', + values: ['(keys: Selection) => void'], + description: 'Handler called when selection changes.', + }, + virtualized: { + type: 'boolean', + default: false, + description: 'Enable virtualization for large lists.', + }, + maxWidth: { + type: 'string', + description: 'Maximum width of the list box popover.', + }, + maxHeight: { + type: 'string', + description: 'Maximum height of the list box popover.', + }, + ...classNamePropDefs, +}; + +export const menuItemPropDefs: Record = { + children: { + type: 'enum', + values: ['ReactNode'], + required: true, + description: 'Content displayed in the menu item.', + }, + id: { + type: 'enum', + values: ['Key'], + description: 'Unique key for the item.', + }, + iconStart: { + type: 'enum', + values: ['ReactNode'], + description: 'Icon displayed before the item content.', + }, + color: { + type: 'enum', + values: ['primary', 'danger'], + description: ( + <> + Color variant. Use danger for destructive actions. + + ), + }, + href: { + type: 'string', + description: 'URL to navigate to when the item is clicked.', + }, + isDisabled: { + type: 'boolean', + description: 'Whether the item is disabled.', + }, + noTrack: { + type: 'boolean', + description: + 'Suppresses the automatic analytics click event, e.g. if you already have custom tracking.', + }, + textValue: { + type: 'string', + description: 'Text used for typeahead and accessibility.', + }, + onAction: { + type: 'enum', + values: ['() => void'], + description: 'Handler called when the item is activated.', + }, + ...classNamePropDefs, +}; + +export const menuListBoxItemPropDefs: Record = { + children: { + type: 'enum', + values: ['ReactNode'], + required: true, + description: 'Content displayed in the list box item.', + }, + id: { + type: 'enum', + values: ['Key'], + description: 'Unique key for the item.', + }, + textValue: { + type: 'string', + description: 'Text used for typeahead and accessibility.', + }, + isDisabled: { + type: 'boolean', + description: 'Whether the item is disabled.', + }, + ...classNamePropDefs, +}; + +export const menuSectionPropDefs: Record = { + title: { + type: 'string', + required: true, + description: 'Heading displayed above the section.', + }, + children: { + type: 'enum', + values: ['ReactNode'], + required: true, + description: 'Menu items within the section.', + }, + ...classNamePropDefs, +}; + +export const menuSeparatorPropDefs: Record = { + ...classNamePropDefs, +}; diff --git a/docs-ui/src/app/components/menu/snippets.ts b/docs-ui/src/app/components/menu/snippets.ts new file mode 100644 index 0000000000..ee7e77e8c2 --- /dev/null +++ b/docs-ui/src/app/components/menu/snippets.ts @@ -0,0 +1,119 @@ +export const usage = `import { MenuTrigger, Menu, MenuItem } from '@backstage/ui'; + + + + + Item 1 + Item 2 + +`; + +export const preview = ` + + + Edit + Duplicate + Rename + + }>Share + }>Feedback + + + }>Settings + + Edit + Duplicate + Rename + + + +`; + +export const submenu = ` + + + New File + + Open Recent + + File 1.txt + File 2.txt + + + Save + +`; + +export const icons = ` + + + }>New File + }>New Folder + }>New Image + +`; + +export const sections = ` + + + + New + Open + + + Cut + Copy + Paste + + +`; + +export const separators = ` + + + New + Open + + Save + Save As... + +`; + +export const links = ` + + + Home + About + Contact + +`; + +export const autocomplete = ` + + + Option 1 + Option 2 + Option 3 + +`; + +export const autocompleteListbox = ` + + + Option 1 + Option 2 + Option 3 + +`; + +export const autocompleteListboxMultiple = ` + + + Option 1 + Option 2 + Option 3 + +`; diff --git a/docs-ui/src/app/components/page.mdx b/docs-ui/src/app/components/page.mdx index bd18005d19..399d9aafbf 100644 --- a/docs-ui/src/app/components/page.mdx +++ b/docs-ui/src/app/components/page.mdx @@ -1,173 +1,9 @@ -import { ComponentCards, ComponentCard } from '@/components/ComponentCards'; -import { LayoutComponents } from '@/components/LayoutComponents'; -import { CodeBlock } from '@/components/CodeBlock'; +import { ComponentGrid } from '@/components/ComponentGrid'; # Components -## Layout Components +Below is the full list of components available in the library. Each component is designed to be flexible, accessible, +and easy to integrate into your plugins. We are actively working on adding more components, so check back regularly +for updates. -We built a couple of layout components to help you build responsive elements -that will be consistent with the rest of your Backstage instance. These -components are opinionated and use TypeScript to ensure that the props you -provide are the ones coming from the theme. - - - Hello World - - Project 1 - Project 2 - - -`} -/> - - - -## Components - -### Actions - - - - - - - - -### Content display - - - - - - -### Selection and inputs - - - - - - - - - - - -### Navigation - - - - - - - - -### Images and icons - - - - - - -### Feedback indicators - - - - - - -### Typography - - - - + diff --git a/docs-ui/src/app/components/password-field/components.tsx b/docs-ui/src/app/components/password-field/components.tsx new file mode 100644 index 0000000000..a9f96b9267 --- /dev/null +++ b/docs-ui/src/app/components/password-field/components.tsx @@ -0,0 +1,63 @@ +'use client'; + +import { PasswordField } from '../../../../../packages/ui/src/components/PasswordField/PasswordField'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; +import { RiLockLine } from '@remixicon/react'; + +export const WithLabel = () => { + return ( + + ); +}; + +export const Sizes = () => { + return ( + + + + + ); +}; + +export const WithDescription = () => { + return ( + + ); +}; + +export const WithIcon = () => { + return ( + } + style={{ maxWidth: '300px' }} + /> + ); +}; + +export const Validation = () => { + return ( + + ); +}; diff --git a/docs-ui/src/app/components/password-field/page.mdx b/docs-ui/src/app/components/password-field/page.mdx new file mode 100644 index 0000000000..2b79425453 --- /dev/null +++ b/docs-ui/src/app/components/password-field/page.mdx @@ -0,0 +1,87 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { passwordFieldPropDefs } from './props-definition'; +import { + passwordFieldUsageSnippet, + withLabelSnippet, + sizesSnippet, + withDescriptionSnippet, + withIconSnippet, + validationSnippet, +} from './snippets'; +import { + WithLabel, + Sizes, + WithDescription, + WithIcon, + Validation, +} from './components'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { CodeBlock } from '@/components/CodeBlock'; +import { PasswordFieldDefinition } from '../../../utils/definitions'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; + +export const reactAriaUrls = { + textField: 'https://react-aria.adobe.com/TextField', +}; + + + +} + code={withLabelSnippet} +/> + +## Usage + + + +## API reference + + + + + +## Examples + +### Sizes + +} code={sizesSnippet} /> + +### With description + +} + code={withDescriptionSnippet} +/> + +### With icon + +} + code={withIconSnippet} +/> + +### Validation + +} + code={validationSnippet} +/> + + + + diff --git a/docs-ui/src/app/components/password-field/props-definition.tsx b/docs-ui/src/app/components/password-field/props-definition.tsx new file mode 100644 index 0000000000..63f0f56d25 --- /dev/null +++ b/docs-ui/src/app/components/password-field/props-definition.tsx @@ -0,0 +1,77 @@ +import { classNamePropDefs, type PropDef } from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +export const passwordFieldPropDefs: Record = { + size: { + type: 'enum', + values: ['small', 'medium'], + default: 'small', + responsive: true, + description: ( + <> + Visual size of the input. Use small for dense layouts,{' '} + medium for prominent fields. + + ), + }, + label: { + type: 'string', + description: 'Visible label displayed above the input.', + }, + secondaryLabel: { + type: 'string', + description: ( + <> + Secondary text shown next to the label. If not provided and isRequired + is true, displays Required. + + ), + }, + description: { + type: 'string', + description: 'Help text displayed below the label.', + }, + icon: { + type: 'enum', + values: ['ReactNode'], + description: 'Icon rendered before the input.', + }, + placeholder: { + type: 'string', + description: 'Text displayed when the input is empty.', + }, + name: { + type: 'string', + description: 'Form field name for submission.', + }, + isRequired: { + type: 'boolean', + description: 'Whether the field is required for form submission.', + }, + isDisabled: { + type: 'boolean', + description: 'Whether the input is disabled.', + }, + isReadOnly: { + type: 'boolean', + description: 'Whether the input is read-only.', + }, + value: { + type: 'string', + description: 'Controlled value of the input.', + }, + defaultValue: { + type: 'string', + description: 'Default value for uncontrolled usage.', + }, + onChange: { + type: 'enum', + values: ['(value: string) => void'], + description: 'Handler called when the input value changes.', + }, + isInvalid: { + type: 'boolean', + description: 'Whether the field is in an invalid state.', + }, + ...classNamePropDefs, +}; diff --git a/docs-ui/src/app/components/password-field/snippets.ts b/docs-ui/src/app/components/password-field/snippets.ts new file mode 100644 index 0000000000..6bc4b9760b --- /dev/null +++ b/docs-ui/src/app/components/password-field/snippets.ts @@ -0,0 +1,33 @@ +export const passwordFieldUsageSnippet = `import { PasswordField } from '@backstage/ui'; + +`; + +export const withLabelSnippet = ``; + +export const sizesSnippet = ` + + +`; + +export const withDescriptionSnippet = ``; + +export const withIconSnippet = `} +/>`; + +export const validationSnippet = ``; diff --git a/docs-ui/src/app/components/plugin-header/components.tsx b/docs-ui/src/app/components/plugin-header/components.tsx new file mode 100644 index 0000000000..045d0956c4 --- /dev/null +++ b/docs-ui/src/app/components/plugin-header/components.tsx @@ -0,0 +1,72 @@ +'use client'; + +import { PluginHeader } from '../../../../../packages/ui/src/components/PluginHeader/PluginHeader'; +import { Header } from '../../../../../packages/ui/src/components/Header/Header'; +import { ButtonIcon } from '../../../../../packages/ui/src/components/ButtonIcon/ButtonIcon'; +import { Button } from '../../../../../packages/ui/src/components/Button/Button'; +import { MemoryRouter } from 'react-router-dom'; +import { + RiHeartLine, + RiEmotionHappyLine, + RiCloudy2Line, +} from '@remixicon/react'; + +const tabs = [ + { id: 'overview', label: 'Overview', href: '/overview' }, + { id: 'checks', label: 'Checks', href: '/checks' }, + { id: 'tracks', label: 'Tracks', href: '/tracks' }, + { id: 'campaigns', label: 'Campaigns', href: '/campaigns' }, + { id: 'integrations', label: 'Integrations', href: '/integrations' }, +]; + +const tabs2 = [ + { id: 'banana', label: 'Banana', href: '/banana' }, + { id: 'apple', label: 'Apple', href: '/apple' }, + { id: 'orange', label: 'Orange', href: '/orange' }, +]; + +export const WithAllOptionsAndTabs = () => ( + + + } /> + } /> + } /> + + } + /> + +); + +export const WithAllOptions = () => ( + + + } /> + } /> + } /> + + } + /> + +); + +export const WithHeader = () => ( + + <> + +
Custom action} + /> + + +); diff --git a/docs-ui/src/app/components/plugin-header/page.mdx b/docs-ui/src/app/components/plugin-header/page.mdx new file mode 100644 index 0000000000..cbe4dfe141 --- /dev/null +++ b/docs-ui/src/app/components/plugin-header/page.mdx @@ -0,0 +1,57 @@ +import { PropsTable } from '@/components/PropsTable'; +import { CodeBlock } from '@/components/CodeBlock'; +import { Snippet } from '@/components/Snippet'; +import { + WithAllOptionsAndTabs, + WithAllOptions, + WithHeader, +} from './components'; +import { headerPropDefs } from './props-definition'; +import { + usage, + simple, + defaultSnippet, + withTabs, + withHeader, +} from './snippets'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { PluginHeaderDefinition } from '../../../utils/definitions'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + + + +} code={defaultSnippet} /> + +## Usage + + + +## API reference + + + +## Examples + +### Simple plugin header + +} code={simple} open /> + +### Plugin header with tabs + +Tabs use React Router and highlight automatically based on the current route. + +} code={withTabs} open /> + +### Plugin header with Header + +Combine with [Header](/components/header) for multi-level navigation. + +} code={withHeader} open /> + + + + diff --git a/docs-ui/src/app/components/plugin-header/props-definition.tsx b/docs-ui/src/app/components/plugin-header/props-definition.tsx new file mode 100644 index 0000000000..4a651cdb72 --- /dev/null +++ b/docs-ui/src/app/components/plugin-header/props-definition.tsx @@ -0,0 +1,63 @@ +import { classNamePropDefs, type PropDef } from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +export const headerPropDefs: Record = { + icon: { + type: 'enum', + values: ['ReactNode'], + description: 'Icon displayed before the title.', + }, + title: { + type: 'string', + description: 'Main heading text for the header.', + }, + titleLink: { + type: 'string', + description: 'URL the title links to when clicked.', + }, + customActions: { + type: 'enum', + values: ['ReactNode'], + description: 'Custom elements rendered in the toolbar area.', + }, + tabs: { + type: 'complex', + description: 'Navigation tabs displayed below the toolbar.', + complexType: { + name: 'HeaderTab[]', + properties: { + id: { + type: 'string', + required: true, + description: 'Unique identifier for the tab.', + }, + label: { + type: 'string', + required: true, + description: 'Display text for the tab.', + }, + href: { + type: 'string', + required: true, + description: 'URL to navigate to when tab is clicked.', + }, + matchStrategy: { + type: "'exact' | 'prefix'", + required: false, + description: ( + <> + Route matching strategy. Use exact for exact path + match, prefix if pathname starts with href. + + ), + }, + }, + }, + }, + onTabSelectionChange: { + type: 'enum', + values: ['(key: Key) => void'], + description: 'Handler called when the selected tab changes.', + }, + ...classNamePropDefs, +}; diff --git a/docs-ui/src/app/components/plugin-header/snippets.ts b/docs-ui/src/app/components/plugin-header/snippets.ts new file mode 100644 index 0000000000..874a16184a --- /dev/null +++ b/docs-ui/src/app/components/plugin-header/snippets.ts @@ -0,0 +1,61 @@ +export const usage = `import { PluginHeader } from '@backstage/ui'; + +`; + +export const defaultSnippet = ` + } /> + } /> + } /> + + } +/>`; + +export const simple = ` + } /> + } /> + } /> + + } +/>`; + +export const withTabs = ``; + +export const withHeader = ` +
Custom action} +/>`; diff --git a/docs-ui/src/app/components/popover/components.tsx b/docs-ui/src/app/components/popover/components.tsx new file mode 100644 index 0000000000..4135d88059 --- /dev/null +++ b/docs-ui/src/app/components/popover/components.tsx @@ -0,0 +1,60 @@ +'use client'; + +import { Popover } from '../../../../../packages/ui/src/components/Popover/Popover'; +import { DialogTrigger } from '../../../../../packages/ui/src/components/Dialog/Dialog'; +import { Button } from '../../../../../packages/ui/src/components/Button/Button'; +import { Text } from '../../../../../packages/ui/src/components/Text/Text'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; + +export const Default = () => { + return ( + + + + Popover content + + + ); +}; + +export const Placement = () => { + return ( + + + + + Content above trigger + + + + + + Content to the right + + + + + + Content below trigger + + + + + + Content to the left + + + + ); +}; + +export const HideArrow = () => { + return ( + + + + Popover without arrow + + + ); +}; diff --git a/docs-ui/src/app/components/popover/page.mdx b/docs-ui/src/app/components/popover/page.mdx new file mode 100644 index 0000000000..0d546cdc05 --- /dev/null +++ b/docs-ui/src/app/components/popover/page.mdx @@ -0,0 +1,74 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; +import { dialogTriggerPropDefs, popoverPropDefs } from './props-definition'; +import { + popoverUsageSnippet, + defaultSnippet, + placementSnippet, + hideArrowSnippet, +} from './snippets'; +import { Default, Placement, HideArrow } from './components'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { PopoverDefinition } from '../../../utils/definitions'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + +export const reactAriaUrls = { + dialogTrigger: 'https://react-aria.adobe.com/Modal#dialogtrigger', + popover: 'https://react-aria.adobe.com/Popover', +}; + + + +} code={defaultSnippet} /> + +## Usage + + + +## API reference + +### DialogTrigger + +Wraps the trigger button and popover content. + + + + + +### Popover + +Displays floating content with automatic positioning. + + + + + +## Examples + +### Placement + +} + code={placementSnippet} +/> + +### Hidden arrow + +} + code={hideArrowSnippet} +/> + + + + diff --git a/docs-ui/src/app/components/popover/props-definition.ts b/docs-ui/src/app/components/popover/props-definition.ts new file mode 100644 index 0000000000..563bfa7d66 --- /dev/null +++ b/docs-ui/src/app/components/popover/props-definition.ts @@ -0,0 +1,47 @@ +import { childrenPropDefs, classNamePropDefs } from '@/utils/propDefs'; +import type { PropDef } from '@/utils/propDefs'; + +export const dialogTriggerPropDefs: Record = { + defaultOpen: { + type: 'boolean', + description: 'Whether the popover is open by default (uncontrolled).', + }, + isOpen: { + type: 'boolean', + description: 'Whether the popover is open (controlled).', + }, + onOpenChange: { + type: 'enum', + values: ['(isOpen: boolean) => void'], + description: 'Handler called when the popover open state changes.', + }, + ...childrenPropDefs, +}; + +export const popoverPropDefs: Record = { + placement: { + type: 'enum', + values: ['top', 'right', 'bottom', 'left'], + description: + 'The placement of the popover relative to the trigger element.', + }, + offset: { + type: 'number', + default: '8', + description: + 'The distance in pixels between the popover and the trigger element.', + }, + containerPadding: { + type: 'number', + default: '12', + description: + 'The minimum distance in pixels from the edge of the viewport.', + }, + hideArrow: { + type: 'boolean', + default: 'false', + description: 'Whether to hide the arrow pointing to the trigger element.', + }, + ...childrenPropDefs, + ...classNamePropDefs, +}; diff --git a/docs-ui/src/app/components/popover/snippets.ts b/docs-ui/src/app/components/popover/snippets.ts new file mode 100644 index 0000000000..986b33c770 --- /dev/null +++ b/docs-ui/src/app/components/popover/snippets.ts @@ -0,0 +1,49 @@ +export const popoverUsageSnippet = `import { DialogTrigger, Popover, Button, Text } from '@backstage/ui'; + + + + + Popover content + +`; + +export const defaultSnippet = ` + + + Popover content + +`; + +export const placementSnippet = ` + + + + Content above trigger + + + + + + Content to the right + + + + + + Content below trigger + + + + + + Content to the left + + +`; + +export const hideArrowSnippet = ` + + + Popover without arrow + +`; diff --git a/docs-ui/src/app/components/radio-group/components.tsx b/docs-ui/src/app/components/radio-group/components.tsx new file mode 100644 index 0000000000..56870c0166 --- /dev/null +++ b/docs-ui/src/app/components/radio-group/components.tsx @@ -0,0 +1,78 @@ +'use client'; + +import { + RadioGroup, + Radio, +} from '../../../../../packages/ui/src/components/RadioGroup/RadioGroup'; + +export const Default = () => { + return ( + + Bulbasaur + Charmander + Squirtle + + ); +}; + +export const Horizontal = () => { + return ( + + Bulbasaur + Charmander + Squirtle + + ); +}; + +export const Disabled = () => { + return ( + + Bulbasaur + Charmander + Squirtle + + ); +}; + +export const DisabledSingle = () => { + return ( + + Bulbasaur + + Charmander + + Squirtle + + ); +}; + +export const Validation = () => { + return ( + (value === 'charmander' ? 'Nice try!' : null)} + > + Bulbasaur + Charmander + Squirtle + + ); +}; + +export const ReadOnly = () => { + return ( + + Bulbasaur + Charmander + Squirtle + + ); +}; diff --git a/docs-ui/src/app/components/radio-group/page.mdx b/docs-ui/src/app/components/radio-group/page.mdx new file mode 100644 index 0000000000..c666a1f872 --- /dev/null +++ b/docs-ui/src/app/components/radio-group/page.mdx @@ -0,0 +1,118 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; +import { radioGroupPropDefs, radioPropDefs } from './props-definition'; +import { + radioGroupUsageSnippet, + defaultSnippet, + horizontalSnippet, + disabledSnippet, + disabledSingleSnippet, + validationSnippet, + readOnlySnippet, +} from './snippets'; +import { + Default, + Horizontal, + Disabled, + DisabledSingle, + Validation, + ReadOnly, +} from './components'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { CodeBlock } from '@/components/CodeBlock'; +import { RadioGroupDefinition } from '../../../utils/definitions'; + +export const reactAriaUrls = { + radioGroup: 'https://react-aria.adobe.com/RadioGroup', +}; + + + +} code={defaultSnippet} /> + +## Usage + + + +## API reference + +### RadioGroup + + + + + +### Radio + +Individual radio button within a group. + + + + + +## Examples + +### Horizontal + +} + code={horizontalSnippet} +/> + +### Disabled + +} + code={disabledSnippet} +/> + +### Disabled single radio + +} + code={disabledSingleSnippet} +/> + +### Validation + +} + code={validationSnippet} +/> + +### Read only + +} + code={readOnlySnippet} +/> + + + + diff --git a/docs-ui/src/app/components/radio-group/props-definition.tsx b/docs-ui/src/app/components/radio-group/props-definition.tsx new file mode 100644 index 0000000000..54da42d93a --- /dev/null +++ b/docs-ui/src/app/components/radio-group/props-definition.tsx @@ -0,0 +1,91 @@ +import { + classNamePropDefs, + childrenPropDefs, + type PropDef, +} from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +export const radioGroupPropDefs: Record = { + label: { + type: 'string', + description: 'The visible label for the radio group.', + }, + 'aria-label': { + type: 'string', + description: + 'Accessible label when a visible label is not provided. Either label, aria-label, or aria-labelledby is required.', + }, + 'aria-labelledby': { + type: 'string', + description: + 'ID of an element that labels the radio group. Either label, aria-label, or aria-labelledby is required.', + }, + secondaryLabel: { + type: 'string', + description: ( + <> + Secondary label text. Defaults to Required when isRequired + is true. + + ), + }, + description: { + type: 'string', + description: 'Helper text displayed below the label.', + }, + orientation: { + type: 'enum', + values: ['horizontal', 'vertical'], + default: 'vertical', + description: 'The axis the radio buttons should align with.', + }, + value: { + type: 'string', + description: 'The current value (controlled).', + }, + defaultValue: { + type: 'string', + description: 'The default value (uncontrolled).', + }, + onChange: { + type: 'enum', + values: ['(value: string) => void'], + description: 'Handler called when the value changes.', + }, + name: { + type: 'string', + description: 'The name of the radio group for form submission.', + }, + isDisabled: { + type: 'boolean', + description: 'Whether all radio buttons in the group are disabled.', + }, + isReadOnly: { + type: 'boolean', + description: 'Whether the radio group is read-only.', + }, + isRequired: { + type: 'boolean', + description: 'Whether a selection is required before form submission.', + }, + isInvalid: { + type: 'boolean', + description: 'Whether the radio group is in an invalid state.', + }, + ...childrenPropDefs, + ...classNamePropDefs, +}; + +export const radioPropDefs: Record = { + value: { + type: 'string', + required: true, + description: 'The value of the radio button.', + }, + isDisabled: { + type: 'boolean', + description: 'Whether this radio button is disabled.', + }, + ...childrenPropDefs, + ...classNamePropDefs, +}; diff --git a/docs-ui/src/app/components/radio-group/snippets.ts b/docs-ui/src/app/components/radio-group/snippets.ts new file mode 100644 index 0000000000..d0b14ef5c4 --- /dev/null +++ b/docs-ui/src/app/components/radio-group/snippets.ts @@ -0,0 +1,57 @@ +export const radioGroupUsageSnippet = `import { RadioGroup, Radio } from '@backstage/ui'; + + + Option 1 + Option 2 +`; + +export const defaultSnippet = ` + Bulbasaur + Charmander + Squirtle +`; + +export const horizontalSnippet = ` + Bulbasaur + Charmander + Squirtle +`; + +export const disabledSnippet = ` + Bulbasaur + Charmander + Squirtle +`; + +export const disabledSingleSnippet = ` + Bulbasaur + + Charmander + + Squirtle +`; + +export const validationSnippet = ` (value === 'charmander' ? 'Nice try!' : null)} +> + Bulbasaur + Charmander + Squirtle +`; + +export const readOnlySnippet = ` + Bulbasaur + Charmander + Squirtle +`; diff --git a/docs-ui/src/app/components/search-autocomplete/components.tsx b/docs-ui/src/app/components/search-autocomplete/components.tsx new file mode 100644 index 0000000000..4052ee0e3f --- /dev/null +++ b/docs-ui/src/app/components/search-autocomplete/components.tsx @@ -0,0 +1,156 @@ +'use client'; + +import { useState } from 'react'; +import { + SearchAutocomplete, + SearchAutocompleteItem, +} from '../../../../../packages/ui/src/components/SearchAutocomplete/SearchAutocomplete'; +import { PluginHeader } from '../../../../../packages/ui/src/components/PluginHeader/PluginHeader'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; +import { Text } from '../../../../../packages/ui/src/components/Text/Text'; +import { MemoryRouter } from 'react-router-dom'; + +const fruits = [ + { id: 'apple', name: 'Apple', description: 'A round fruit' }, + { id: 'banana', name: 'Banana', description: 'A yellow curved fruit' }, + { id: 'blueberry', name: 'Blueberry', description: 'A small blue berry' }, + { id: 'cherry', name: 'Cherry', description: 'A small red stone fruit' }, + { id: 'grape', name: 'Grape', description: 'Grows in clusters on vines' }, + { id: 'lemon', name: 'Lemon', description: 'A sour yellow citrus fruit' }, + { id: 'mango', name: 'Mango', description: 'A tropical stone fruit' }, + { id: 'orange', name: 'Orange', description: 'A citrus fruit' }, + { id: 'peach', name: 'Peach', description: 'A fuzzy stone fruit' }, + { + id: 'strawberry', + name: 'Strawberry', + description: 'A red fruit with seeds on its surface', + }, +]; + +export const WithItems = () => { + const [inputValue, setInputValue] = useState(''); + + const filtered = fruits.filter(fruit => + fruit.name.toLowerCase().includes(inputValue.toLowerCase()), + ); + + return ( + + {filtered.map(fruit => ( + + {fruit.name} + + ))} + + ); +}; + +export const WithRichContent = () => { + const [inputValue, setInputValue] = useState(''); + + const filtered = fruits.filter(fruit => + fruit.name.toLowerCase().includes(inputValue.toLowerCase()), + ); + + return ( + + {filtered.map(fruit => ( + + {fruit.name} + + {fruit.description} + + + ))} + + ); +}; + +export const InHeader = () => { + const [inputValue, setInputValue] = useState(''); + + const filtered = fruits.filter(fruit => + fruit.name.toLowerCase().includes(inputValue.toLowerCase()), + ); + + return ( + + + + {filtered.map(fruit => ( + + {fruit.name} + + ))} + + + } + /> + + ); +}; + +export const WithSelection = () => { + const [inputValue, setInputValue] = useState(''); + const [selected, setSelected] = useState(null); + + const filtered = fruits.filter(fruit => + fruit.name.toLowerCase().includes(inputValue.toLowerCase()), + ); + + return ( + + + {filtered.map(fruit => ( + { + setSelected(fruit.name); + setInputValue(''); + }} + > + {fruit.name} + + ))} + + Last selected: {selected ?? 'none'} + + ); +}; diff --git a/docs-ui/src/app/components/search-autocomplete/page.mdx b/docs-ui/src/app/components/search-autocomplete/page.mdx new file mode 100644 index 0000000000..ed307f92e2 --- /dev/null +++ b/docs-ui/src/app/components/search-autocomplete/page.mdx @@ -0,0 +1,97 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; +import { + searchAutocompletePropDefs, + searchAutocompleteItemPropDefs, +} from './props-definition'; +import { + usage, + defaultSnippet, + withRichContent, + inHeader, + withSelection, +} from './snippets'; +import { + WithItems, + WithRichContent, + InHeader, + WithSelection, +} from './components'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { CodeBlock } from '@/components/CodeBlock'; +import { + SearchAutocompleteDefinition, + SearchAutocompleteItemDefinition, +} from '../../../utils/definitions'; + +export const reactAriaUrls = { + autocomplete: 'https://react-aria.adobe.com/Autocomplete', +}; + + + +} code={defaultSnippet} /> + +## Usage + + + +## API reference + +### SearchAutocomplete + + + + + +### SearchAutocompleteItem + +Individual result item within the autocomplete list. + + + +## Examples + +### With rich content + +Here's a view when items include a title and description. + +} + code={withRichContent} +/> + +### In header + +Use `popoverWidth` to control the dropdown width when placed in constrained layouts like `PluginHeader`. + +} code={inHeader} /> + +### With selection + +Use `onAction` on items to handle selection and reset the input. + +} + code={withSelection} +/> + + + + + + diff --git a/docs-ui/src/app/components/search-autocomplete/props-definition.tsx b/docs-ui/src/app/components/search-autocomplete/props-definition.tsx new file mode 100644 index 0000000000..7ddaea61e2 --- /dev/null +++ b/docs-ui/src/app/components/search-autocomplete/props-definition.tsx @@ -0,0 +1,97 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +export const searchAutocompletePropDefs: Record = { + 'aria-label': { + type: 'string', + description: 'Accessible label for the search input.', + }, + 'aria-labelledby': { + type: 'string', + description: 'ID of the element that labels the search input.', + }, + inputValue: { + type: 'string', + description: 'The current input value (controlled).', + }, + onInputChange: { + type: 'enum', + values: ['(value: string) => void'], + description: 'Handler called when the input value changes.', + }, + placeholder: { + type: 'string', + default: 'Search', + description: + 'Placeholder text shown when the input is empty. Also used as the accessible label when neither aria-label nor aria-labelledby is provided.', + }, + size: { + type: 'enum', + values: ['small', 'medium'], + default: 'small', + responsive: true, + description: ( + <> + Visual size of the input. Use small for inline or dense + layouts, medium for standalone fields. + + ), + }, + isLoading: { + type: 'boolean', + default: 'false', + description: + 'Whether results are currently loading. Dims existing results and announces loading state to screen readers.', + }, + popoverWidth: { + type: 'string', + description: + 'Width of the results popover. Accepts any CSS width value. Matches the input width when not set.', + }, + popoverPlacement: { + type: 'enum', + values: ['bottom start', 'bottom end', 'top start', 'top end'], + default: 'bottom start', + description: 'Placement of the results popover relative to the input.', + }, + defaultOpen: { + type: 'boolean', + default: 'false', + description: 'Whether the results popover is open by default.', + }, + children: { + type: 'enum', + values: ['ReactNode'], + description: 'The result items to render inside the autocomplete.', + }, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const searchAutocompleteItemPropDefs: Record = { + id: { + type: 'string', + description: 'Unique identifier for the item.', + }, + textValue: { + type: 'string', + description: + 'Plain text value used for keyboard navigation and accessibility.', + }, + onAction: { + type: 'enum', + values: ['() => void'], + description: 'Handler called when the item is selected.', + }, + children: { + type: 'enum', + values: ['ReactNode'], + required: true, + description: 'Content to render inside the item.', + }, + ...classNamePropDefs, +}; diff --git a/docs-ui/src/app/components/search-autocomplete/snippets.ts b/docs-ui/src/app/components/search-autocomplete/snippets.ts new file mode 100644 index 0000000000..fe799ff828 --- /dev/null +++ b/docs-ui/src/app/components/search-autocomplete/snippets.ts @@ -0,0 +1,139 @@ +export const usage = `import { SearchAutocomplete, SearchAutocompleteItem } from '@backstage/ui'; + + + {items.map(item => ( + + {item.name} + + ))} +`; + +export const defaultSnippet = `const fruits = [ + { id: 'apple', name: 'Apple' }, + { id: 'banana', name: 'Banana' }, + { id: 'cherry', name: 'Cherry' }, + { id: 'grape', name: 'Grape' }, + { id: 'orange', name: 'Orange' }, +]; + +function Example() { + const [inputValue, setInputValue] = useState(''); + + const filtered = fruits.filter(fruit => + fruit.name.toLowerCase().includes(inputValue.toLowerCase()), + ); + + return ( + + {filtered.map(fruit => ( + + {fruit.name} + + ))} + + ); +}`; + +export const withRichContent = `const fruits = [ + { id: 'apple', name: 'Apple', description: 'A round fruit' }, + { id: 'banana', name: 'Banana', description: 'A yellow curved fruit' }, + { id: 'cherry', name: 'Cherry', description: 'A small red stone fruit' }, +]; + +function Example() { + const [inputValue, setInputValue] = useState(''); + + const filtered = fruits.filter(fruit => + fruit.name.toLowerCase().includes(inputValue.toLowerCase()), + ); + + return ( + + {filtered.map(fruit => ( + + {fruit.name} + + {fruit.description} + + + ))} + + ); +}`; + +export const inHeader = ` + + {filtered.map(fruit => ( + + {fruit.name} + + ))} + + + } +/>`; + +export const withSelection = `function Example() { + const [inputValue, setInputValue] = useState(''); + const [selected, setSelected] = useState(null); + + const filtered = fruits.filter(fruit => + fruit.name.toLowerCase().includes(inputValue.toLowerCase()), + ); + + return ( + + + {filtered.map(fruit => ( + { + setSelected(fruit.name); + setInputValue(''); + }} + > + {fruit.name} + + ))} + + Last selected: {selected ?? 'none'} + + ); +}`; diff --git a/docs-ui/src/app/components/search-field/components.tsx b/docs-ui/src/app/components/search-field/components.tsx new file mode 100644 index 0000000000..84a4c6018d --- /dev/null +++ b/docs-ui/src/app/components/search-field/components.tsx @@ -0,0 +1,38 @@ +'use client'; + +import { SearchField } from '../../../../../packages/ui/src/components/SearchField/SearchField'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; + +export const WithLabel = () => { + return ( + + ); +}; + +export const Sizes = () => { + return ( + + + + + ); +}; + +export const WithDescription = () => { + return ( + + ); +}; + +export const StartCollapsed = () => { + return ( + + + + + ); +}; diff --git a/docs-ui/src/app/components/search-field/page.mdx b/docs-ui/src/app/components/search-field/page.mdx new file mode 100644 index 0000000000..425dbc8f62 --- /dev/null +++ b/docs-ui/src/app/components/search-field/page.mdx @@ -0,0 +1,91 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; +import { searchFieldPropDefs } from './props-definition'; +import { + searchFieldUsageSnippet, + withLabelSnippet, + sizesSnippet, + withDescriptionSnippet, + startCollapsedSnippet, +} from './snippets'; +import { + WithLabel, + Sizes, + WithDescription, + StartCollapsed, +} from './components'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { CodeBlock } from '@/components/CodeBlock'; +import { SearchFieldDefinition } from '../../../utils/definitions'; + +export const reactAriaUrls = { + searchField: 'https://react-aria.adobe.com/SearchField', +}; + + + +} + code={withLabelSnippet} +/> + +## Usage + + + +## API reference + + + + + +## Examples + +### Sizes + +} + code={sizesSnippet} + layout="side-by-side" +/> + +### With description + +Add context below the input with the `description` prop. + +} + code={withDescriptionSnippet} + layout="side-by-side" +/> + +### Collapsible + +Use `startCollapsed` for space-constrained layouts where the field expands on focus. + +} + code={startCollapsedSnippet} + layout="side-by-side" +/> + + + + diff --git a/docs-ui/src/app/components/search-field/props-definition.tsx b/docs-ui/src/app/components/search-field/props-definition.tsx new file mode 100644 index 0000000000..58d2cb0988 --- /dev/null +++ b/docs-ui/src/app/components/search-field/props-definition.tsx @@ -0,0 +1,85 @@ +import { classNamePropDefs, type PropDef } from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +export const searchFieldPropDefs: Record = { + size: { + type: 'enum', + values: ['small', 'medium'], + default: 'small', + responsive: true, + description: ( + <> + Visual size of the input. Use small for inline or dense + layouts, medium for standalone fields. + + ), + }, + label: { + type: 'string', + description: 'The visible label for the search field.', + }, + secondaryLabel: { + type: 'string', + description: ( + <> + Secondary label text. Defaults to Required when isRequired + is true. + + ), + }, + description: { + type: 'string', + description: 'Helper text displayed below the label.', + }, + placeholder: { + type: 'string', + default: 'Search', + description: 'Placeholder text shown when the field is empty.', + }, + icon: { + type: 'enum', + values: ['ReactNode', 'false'], + description: + 'Icon displayed before the input. Set to false to hide the icon.', + }, + startCollapsed: { + type: 'boolean', + default: 'false', + description: + 'Whether the search field starts in a collapsed state. Expands on focus.', + }, + name: { + type: 'string', + description: 'The name of the input for form submission.', + }, + value: { + type: 'string', + description: 'The current value (controlled).', + }, + defaultValue: { + type: 'string', + description: 'The default value (uncontrolled).', + }, + onChange: { + type: 'enum', + values: ['(value: string) => void'], + description: 'Handler called when the value changes.', + }, + isDisabled: { + type: 'boolean', + description: 'Whether the search field is disabled.', + }, + isReadOnly: { + type: 'boolean', + description: 'Whether the search field is read-only.', + }, + isRequired: { + type: 'boolean', + description: 'Whether a value is required before form submission.', + }, + isInvalid: { + type: 'boolean', + description: 'Whether the search field is in an invalid state.', + }, + ...classNamePropDefs, +}; diff --git a/docs-ui/src/app/components/search-field/snippets.ts b/docs-ui/src/app/components/search-field/snippets.ts new file mode 100644 index 0000000000..e54bca1bf4 --- /dev/null +++ b/docs-ui/src/app/components/search-field/snippets.ts @@ -0,0 +1,20 @@ +export const searchFieldUsageSnippet = `import { SearchField } from '@backstage/ui'; + +`; + +export const withLabelSnippet = ``; + +export const sizesSnippet = ` + + +`; + +export const withDescriptionSnippet = ``; + +export const startCollapsedSnippet = ` + + +`; diff --git a/docs-ui/src/app/components/select/components.tsx b/docs-ui/src/app/components/select/components.tsx new file mode 100644 index 0000000000..1cbc323b84 --- /dev/null +++ b/docs-ui/src/app/components/select/components.tsx @@ -0,0 +1,150 @@ +'use client'; + +import { Select } from '../../../../../packages/ui/src/components/Select/Select'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; +import { RiCloudLine } from '@remixicon/react'; + +const fontOptions = [ + { value: 'sans', label: 'Sans-serif' }, + { value: 'serif', label: 'Serif' }, + { value: 'mono', label: 'Monospace' }, + { value: 'cursive', label: 'Cursive' }, +]; + +const countries = [ + { value: 'us', label: 'United States' }, + { value: 'ca', label: 'Canada' }, + { value: 'mx', label: 'Mexico' }, + { value: 'uk', label: 'United Kingdom' }, + { value: 'fr', label: 'France' }, + { value: 'de', label: 'Germany' }, + { value: 'it', label: 'Italy' }, + { value: 'es', label: 'Spain' }, + { value: 'jp', label: 'Japan' }, + { value: 'cn', label: 'China' }, + { value: 'in', label: 'India' }, + { value: 'br', label: 'Brazil' }, + { value: 'au', label: 'Australia' }, +]; + +const skills = [ + { value: 'react', label: 'React' }, + { value: 'typescript', label: 'TypeScript' }, + { value: 'javascript', label: 'JavaScript' }, + { value: 'python', label: 'Python' }, + { value: 'java', label: 'Java' }, + { value: 'csharp', label: 'C#' }, + { value: 'go', label: 'Go' }, + { value: 'rust', label: 'Rust' }, + { value: 'kotlin', label: 'Kotlin' }, + { value: 'swift', label: 'Swift' }, +]; + +export const Preview = () => ( + +); + +export const Sizes = () => ( + + + +); + +export const WithIcon = () => ( + +); + +export const DisabledOption = () => ( + +); + +export const MultipleSelection = () => ( + +); diff --git a/docs-ui/src/app/components/select/page.mdx b/docs-ui/src/app/components/select/page.mdx new file mode 100644 index 0000000000..8e7df65ef9 --- /dev/null +++ b/docs-ui/src/app/components/select/page.mdx @@ -0,0 +1,147 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; +import { + Preview, + WithLabelAndDescription, + Sizes, + WithIcon, + Disabled, + DisabledOption, + Searchable, + MultipleSelection, + SearchableMultiple, +} from './components'; +import { selectPropDefs } from './props-definition'; +import { + selectUsageSnippet, + selectDefaultSnippet, + selectDescriptionSnippet, + selectSizesSnippet, + selectDisabledSnippet, + selectResponsiveSnippet, + selectIconSnippet, + selectSearchableSnippet, + selectMultipleSnippet, + selectSearchableMultipleSnippet, + selectDisabledOptionsSnippet, +} from './snippets'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { SelectDefinition } from '../../../utils/definitions'; + +export const reactAriaUrls = { + select: 'https://react-aria.adobe.com/Select', +}; + + + +} + code={selectDefaultSnippet} +/> + +## Usage + + + +## API reference + + + + + +## Examples + +### Label and description + +} + code={selectDescriptionSnippet} +/> + +### Sizes + +} + code={selectSizesSnippet} +/> + +### With icon + +} + code={selectIconSnippet} +/> + +### Disabled + +} + code={selectDisabledSnippet} +/> + +### Disabled options + +} + code={selectDisabledOptionsSnippet} +/> + +### Searchable + +Enable filtering with the `searchable` prop. + +} + code={selectSearchableSnippet} +/> + +### Multiple selection + +} + code={selectMultipleSnippet} +/> + +### Searchable multiple + +Combine search and multiple selection. + +} + code={selectSearchableMultipleSnippet} +/> + +### Responsive + +Size can change at different breakpoints. + + + + + + diff --git a/docs-ui/src/app/components/select/props-definition.tsx b/docs-ui/src/app/components/select/props-definition.tsx new file mode 100644 index 0000000000..0d1ab22202 --- /dev/null +++ b/docs-ui/src/app/components/select/props-definition.tsx @@ -0,0 +1,137 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +export const selectPropDefs: Record = { + options: { + type: 'complex', + description: 'Array of options to display in the dropdown.', + complexType: { + name: 'SelectOption[]', + properties: { + value: { + type: 'string', + required: true, + description: 'Unique value for the option.', + }, + label: { + type: 'string', + required: true, + description: 'Display text for the option.', + }, + disabled: { + type: 'boolean', + required: false, + description: 'Whether the option is disabled.', + }, + }, + }, + }, + selectionMode: { + type: 'enum', + values: ['single', 'multiple'], + default: 'single', + description: 'Single or multiple selection mode.', + }, + value: { + type: 'enum', + values: ['string', 'string[]'], + description: + 'Controlled selected value. String for single, array for multiple.', + }, + defaultValue: { + type: 'enum', + values: ['string', 'string[]'], + description: + 'Initial value for uncontrolled usage. String for single, array for multiple.', + }, + onSelectionChange: { + type: 'enum', + values: ['(key: Key | null) => void', '(keys: Selection) => void'], + description: 'Called when selection changes.', + }, + label: { + type: 'string', + description: 'Visible label above the select.', + }, + secondaryLabel: { + type: 'string', + description: ( + <> + Secondary text shown next to the label. If not provided and isRequired + is true, displays Required. + + ), + }, + description: { + type: 'string', + description: 'Helper text displayed below the label.', + }, + placeholder: { + type: 'string', + default: 'Select an option', + description: 'Text shown when no option is selected.', + }, + size: { + type: 'enum', + values: ['small', 'medium'], + default: 'small', + responsive: true, + description: 'Visual size of the select field.', + }, + icon: { + type: 'enum', + values: ['ReactNode'], + description: 'Icon displayed before the selected value.', + }, + searchable: { + type: 'boolean', + default: false, + description: 'Enables search/filter functionality in the dropdown.', + }, + searchPlaceholder: { + type: 'string', + default: 'Search...', + description: + 'Placeholder text for the search input when searchable is true.', + }, + isOpen: { + type: 'boolean', + description: 'Controlled open state. Use with onOpenChange.', + }, + defaultOpen: { + type: 'boolean', + description: 'Initial open state for uncontrolled usage.', + }, + onOpenChange: { + type: 'enum', + values: ['(isOpen: boolean) => void'], + description: 'Called when the dropdown opens or closes.', + }, + isDisabled: { + type: 'boolean', + description: 'Prevents user interaction when true.', + }, + disabledKeys: { + type: 'enum', + values: ['Iterable'], + description: 'Keys of options that should be disabled.', + }, + isRequired: { + type: 'boolean', + description: 'Marks the field as required for form validation.', + }, + isInvalid: { + type: 'boolean', + description: 'Displays the select in an error state.', + }, + name: { + type: 'string', + description: 'Form field name for form submission.', + }, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/content/select.props.ts b/docs-ui/src/app/components/select/snippets.ts similarity index 51% rename from docs-ui/src/content/select.props.ts rename to docs-ui/src/app/components/select/snippets.ts index 05f0d895c9..ca16481eaa 100644 --- a/docs-ui/src/content/select.props.ts +++ b/docs-ui/src/app/components/select/snippets.ts @@ -1,115 +1,3 @@ -import { - classNamePropDefs, - stylePropDefs, - type PropDef, -} from '@/utils/propDefs'; - -export const selectPropDefs: Record = { - label: { - type: 'string', - responsive: false, - }, - description: { - type: 'string', - responsive: false, - }, - name: { - type: 'string', - responsive: false, - required: true, - }, - options: { - type: 'enum', - values: ['Array<{ value: string, label: string }>'], - required: true, - }, - selectionMode: { - type: 'enum', - values: ['single', 'multiple'], - default: 'single', - responsive: false, - }, - placeholder: { - type: 'string', - default: 'Select an item', - responsive: false, - }, - icon: { - type: 'enum', - values: ['ReactNode'], - responsive: false, - }, - value: { - type: 'enum', - values: ['string', 'string[]'], - responsive: false, - description: - 'Selected value (controlled). String for single selection, array for multiple.', - }, - defaultValue: { - type: 'enum', - values: ['string', 'string[]'], - responsive: false, - description: - 'Initial value (uncontrolled). String for single selection, array for multiple.', - }, - size: { - type: 'enum', - values: ['small', 'medium'], - default: 'small', - responsive: true, - }, - isOpen: { - type: 'boolean', - responsive: false, - }, - defaultOpen: { - type: 'boolean', - responsive: false, - }, - disabledKeys: { - type: 'enum', - values: ['Iterable'], - responsive: false, - }, - isDisabled: { - type: 'boolean', - responsive: false, - }, - isRequired: { - type: 'boolean', - responsive: false, - }, - isInvalid: { - type: 'boolean', - responsive: false, - }, - onOpenChange: { - type: 'enum', - values: ['(isOpen: boolean) => void'], - responsive: false, - }, - onSelectionChange: { - type: 'enum', - values: ['(key: Key | null) => void', '(keys: Selection) => void'], - responsive: false, - description: - 'Handler called when selection changes. Single mode: receives Key | null. Multiple mode: receives Selection.', - }, - searchable: { - type: 'boolean', - default: 'false', - responsive: false, - }, - searchPlaceholder: { - type: 'string', - default: 'Search...', - responsive: false, - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - export const selectUsageSnippet = `import { Select } from '@backstage/ui'; `; @@ -209,3 +97,16 @@ export const selectSearchableMultipleSnippet = ``; diff --git a/docs-ui/src/app/components/skeleton/components.tsx b/docs-ui/src/app/components/skeleton/components.tsx new file mode 100644 index 0000000000..9f83a013db --- /dev/null +++ b/docs-ui/src/app/components/skeleton/components.tsx @@ -0,0 +1,44 @@ +'use client'; + +import { Skeleton } from '../../../../../packages/ui/src/components/Skeleton/Skeleton'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; +import { Box } from '../../../../../packages/ui/src/components/Box/Box'; + +export const CardPlaceholder = () => { + return ( + + + + + + + + ); +}; + +export const AvatarWithText = () => { + return ( + + + + + + + + + + + ); +}; + +export const Rounded = () => { + return ( + + + + + + + + ); +}; diff --git a/docs-ui/src/content/skeleton.mdx b/docs-ui/src/app/components/skeleton/page.mdx similarity index 52% rename from docs-ui/src/content/skeleton.mdx rename to docs-ui/src/app/components/skeleton/page.mdx index 07cd4b3332..b7d218af89 100644 --- a/docs-ui/src/content/skeleton.mdx +++ b/docs-ui/src/app/components/skeleton/page.mdx @@ -1,18 +1,18 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { SkeletonSnippet } from '@/snippets/stories-snippets'; +import { skeletonPropDefs } from './props-definition'; import { - skeletonPropDefs, skeletonUsageSnippet, - skeletonDefaultSnippet, - skeletonDemo1Snippet, - skeletonDemo2Snippet, -} from './skeleton.props'; + cardPlaceholderSnippet, + avatarWithTextSnippet, + roundedSnippet, +} from './snippets'; +import { CardPlaceholder, AvatarWithText, Rounded } from './components'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ChangelogComponent } from '@/components/ChangelogComponent'; -import { SkeletonDefinition } from '../utils/definitions'; +import { SkeletonDefinition } from '../../../utils/definitions'; } - code={skeletonDefaultSnippet} + preview={} + code={cardPlaceholderSnippet} /> ## Usage @@ -32,32 +32,28 @@ import { SkeletonDefinition } from '../utils/definitions'; ## API reference +Skeleton extends standard HTML div attributes. + ## Examples -### Demo 1 - -You can use a mix of different sizes to create a more complex skeleton. +### Rounded } - code={skeletonDemo1Snippet} + layout="side-by-side" open + preview={} + code={roundedSnippet} /> -### Demo 2 - -You can use a mix of different sizes to create a more complex skeleton. +### Avatar with text } - code={skeletonDemo2Snippet} + layout="side-by-side" open + preview={} + code={avatarWithTextSnippet} /> diff --git a/docs-ui/src/app/components/skeleton/props-definition.ts b/docs-ui/src/app/components/skeleton/props-definition.ts new file mode 100644 index 0000000000..8c14c4c0ec --- /dev/null +++ b/docs-ui/src/app/components/skeleton/props-definition.ts @@ -0,0 +1,28 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const skeletonPropDefs: Record = { + width: { + type: 'string', + default: '80', + description: + 'The width of the skeleton. Accepts a number (pixels) or CSS string value.', + }, + height: { + type: 'string', + default: '24', + description: + 'The height of the skeleton. Accepts a number (pixels) or CSS string value.', + }, + rounded: { + type: 'boolean', + default: 'false', + description: + 'Whether to apply fully rounded corners (for circular shapes).', + }, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/skeleton/snippets.ts b/docs-ui/src/app/components/skeleton/snippets.ts new file mode 100644 index 0000000000..84bbec1616 --- /dev/null +++ b/docs-ui/src/app/components/skeleton/snippets.ts @@ -0,0 +1,30 @@ +export const skeletonUsageSnippet = `import { Skeleton } from '@backstage/ui'; + +`; + +export const cardPlaceholderSnippet = ` + + + + + +`; + +export const avatarWithTextSnippet = ` + + + + + + + + +`; + +export const roundedSnippet = ` + + + + + +`; diff --git a/docs-ui/src/app/components/slider/components.tsx b/docs-ui/src/app/components/slider/components.tsx new file mode 100644 index 0000000000..55da7279c1 --- /dev/null +++ b/docs-ui/src/app/components/slider/components.tsx @@ -0,0 +1,85 @@ +'use client'; + +import { Slider } from '../../../../../packages/ui/src/components/Slider'; + +export const SingleValue = () => { + return ( + + ); +}; + +export const Default = () => { + return ( + + ); +}; + +export const WithCustomRange = () => { + return ( + + ); +}; + +export const WithFormattedValues = () => { + return ( + + ); +}; + +export const WithDescription = () => { + return ( + + ); +}; + +export const Required = () => { + return ( + + ); +}; + +export const Disabled = () => { + return ( + + ); +}; diff --git a/docs-ui/src/app/components/slider/page.mdx b/docs-ui/src/app/components/slider/page.mdx new file mode 100644 index 0000000000..c46185f9f6 --- /dev/null +++ b/docs-ui/src/app/components/slider/page.mdx @@ -0,0 +1,109 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; +import { sliderPropDefs } from './props-definition'; +import { + snippetUsage, + singleValueSnippet, + defaultSnippet, + withCustomRangeSnippet, + withFormattedValuesSnippet, + withDescriptionSnippet, + requiredSnippet, + disabledSnippet, +} from './snippets'; +import { + SingleValue, + Default, + WithCustomRange, + WithFormattedValues, + WithDescription, + Required, + Disabled, +} from './components'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { SliderDefinition } from '../../../utils/definitions'; + +export const reactAriaUrls = { + slider: 'https://react-spectrum.adobe.com/react-aria/Slider.html', +}; + + + +} code={defaultSnippet} /> + +## Usage + + + +## API reference + + + + + +## Examples + +### Single value + +Use a single number as the default value to create a single-thumb slider. + +} + code={singleValueSnippet} +/> + +### Custom range + +Define custom minimum, maximum, and step values for specific use cases. + +} + code={withCustomRangeSnippet} +/> + +### Formatted values + +Use the `formatOptions` prop with standard Intl.NumberFormat options to customize how values are displayed. + +} + code={withFormattedValuesSnippet} +/> + +### With description + +Add helpful context with a description below the label. + +} + code={withDescriptionSnippet} +/> + +### Required + +Mark a field as required to show a "Required" indicator in the label. + +} code={requiredSnippet} /> + +### Disabled + +} code={disabledSnippet} /> + + + + diff --git a/docs-ui/src/app/components/slider/props-definition.ts b/docs-ui/src/app/components/slider/props-definition.ts new file mode 100644 index 0000000000..98bddc3bf9 --- /dev/null +++ b/docs-ui/src/app/components/slider/props-definition.ts @@ -0,0 +1,92 @@ +import { classNamePropDefs, stylePropDefs } from '@/utils/propDefs'; +import type { PropDef } from '@/utils/propDefs'; + +export const sliderPropDefs: Record = { + label: { + type: 'string', + description: 'The label text for the slider.', + }, + description: { + type: 'string', + description: 'Additional description text displayed below the label.', + }, + secondaryLabel: { + type: 'string', + description: + 'Optional secondary label displayed next to the main label (e.g., "Optional").', + }, + isRequired: { + type: 'boolean', + description: + 'Whether the field is required. Displays "Required" in the label if true.', + }, + minValue: { + type: 'number', + description: 'The minimum value of the slider.', + default: '0', + }, + maxValue: { + type: 'number', + description: 'The maximum value of the slider.', + default: '100', + }, + step: { + type: 'number', + description: 'The step increment for slider values.', + default: '1', + }, + value: { + type: 'enum', + values: ['number', '[number, number]'], + description: + 'Controlled value. Use a single number for a single-thumb slider, or an array [min, max] for a range slider. Use with onChange for controlled behavior.', + }, + defaultValue: { + type: 'enum', + values: ['number', '[number, number]'], + description: + 'Initial value for uncontrolled usage. Use a single number for a single-thumb slider, or an array [min, max] for a range slider.', + default: 'minValue or [minValue, maxValue]', + }, + onChange: { + type: 'enum', + values: ['(value: number | [number, number]) => void'], + description: 'Called when the slider value changes.', + }, + onChangeEnd: { + type: 'enum', + values: ['(value: number | [number, number]) => void'], + description: + 'Called when the user stops dragging, useful for triggering actions only on final values.', + }, + formatOptions: { + type: 'object', + description: + 'Intl.NumberFormat options for formatting the displayed value (e.g., { style: "currency", currency: "USD" }).', + }, + isDisabled: { + type: 'boolean', + description: 'Prevents user interaction when true.', + }, + orientation: { + type: 'enum', + values: ['horizontal', 'vertical'], + description: 'The orientation of the slider.', + default: 'horizontal', + }, + name: { + type: 'string', + description: 'Form field name for form submission.', + }, + 'aria-label': { + type: 'string', + description: + 'Accessible label for screen readers when no visible label is provided.', + }, + 'aria-labelledby': { + type: 'string', + description: 'ID of an element that labels the slider for accessibility.', + }, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/slider/snippets.ts b/docs-ui/src/app/components/slider/snippets.ts new file mode 100644 index 0000000000..02a4271bd7 --- /dev/null +++ b/docs-ui/src/app/components/slider/snippets.ts @@ -0,0 +1,67 @@ +export const snippetUsage = `import { Slider } from '@backstage/ui'; + +`; + +export const singleValueSnippet = ``; + +export const defaultSnippet = ``; + +export const withCustomRangeSnippet = ``; + +export const withFormattedValuesSnippet = ``; + +export const withDescriptionSnippet = ``; + +export const requiredSnippet = ``; + +export const disabledSnippet = ``; diff --git a/docs-ui/src/app/components/switch/components.tsx b/docs-ui/src/app/components/switch/components.tsx new file mode 100644 index 0000000000..604d9d68b1 --- /dev/null +++ b/docs-ui/src/app/components/switch/components.tsx @@ -0,0 +1,11 @@ +'use client'; + +import { Switch } from '../../../../../packages/ui/src/components/Switch/Switch'; + +export const Default = () => { + return ; +}; + +export const Disabled = () => { + return ; +}; diff --git a/docs-ui/src/app/components/switch/page.mdx b/docs-ui/src/app/components/switch/page.mdx new file mode 100644 index 0000000000..1f893aea36 --- /dev/null +++ b/docs-ui/src/app/components/switch/page.mdx @@ -0,0 +1,42 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; +import { switchPropDefs } from './props-definition'; +import { snippetUsage, defaultSnippet, disabledSnippet } from './snippets'; +import { Default, Disabled } from './components'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { SwitchDefinition } from '../../../utils/definitions'; + +export const reactAriaUrls = { + switch: 'https://react-aria.adobe.com/Switch', +}; + + + +} code={defaultSnippet} /> + +## Usage + + + +## API reference + + + + + +## Examples + +### Disabled + +} code={disabledSnippet} /> + + + + diff --git a/docs-ui/src/app/components/switch/props-definition.ts b/docs-ui/src/app/components/switch/props-definition.ts new file mode 100644 index 0000000000..eeab3e64b8 --- /dev/null +++ b/docs-ui/src/app/components/switch/props-definition.ts @@ -0,0 +1,45 @@ +import { classNamePropDefs, stylePropDefs } from '@/utils/propDefs'; +import type { PropDef } from '@/utils/propDefs'; + +export const switchPropDefs: Record = { + label: { + type: 'string', + description: 'Text label displayed next to the switch.', + }, + isSelected: { + type: 'boolean', + description: + 'Controlled selected state. Use with onChange for controlled behavior.', + }, + defaultSelected: { + type: 'boolean', + description: 'Initial selected state for uncontrolled usage.', + }, + onChange: { + type: 'enum', + values: ['(isSelected: boolean) => void'], + description: 'Called when the switch state changes.', + }, + isDisabled: { + type: 'boolean', + description: 'Prevents user interaction when true.', + }, + isReadOnly: { + type: 'boolean', + description: 'Makes the switch non-interactive but still focusable.', + }, + name: { + type: 'string', + description: 'Form field name for form submission.', + }, + value: { + type: 'string', + description: 'Form field value submitted when selected.', + }, + autoFocus: { + type: 'boolean', + description: 'Focuses the switch on mount.', + }, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/switch/snippets.ts b/docs-ui/src/app/components/switch/snippets.ts new file mode 100644 index 0000000000..694f15106c --- /dev/null +++ b/docs-ui/src/app/components/switch/snippets.ts @@ -0,0 +1,7 @@ +export const snippetUsage = `import { Switch } from '@backstage/ui'; + +`; + +export const defaultSnippet = ``; + +export const disabledSnippet = ``; diff --git a/docs-ui/src/app/components/table/components.tsx b/docs-ui/src/app/components/table/components.tsx new file mode 100644 index 0000000000..d26deeb5d3 --- /dev/null +++ b/docs-ui/src/app/components/table/components.tsx @@ -0,0 +1,554 @@ +'use client'; + +import { useState } from 'react'; +import { + Table, + TableRoot, + TableHeader, + TableBody, + Column, + Row, + CellProfile, + CellText, + useTable, + type ColumnConfig, +} from '../../../../../packages/ui/src/components/Table'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; +import { Text } from '../../../../../packages/ui/src/components/Text/Text'; +import { SearchField } from '../../../../../packages/ui/src/components/SearchField/SearchField'; +import { + RadioGroup, + Radio, +} from '../../../../../packages/ui/src/components/RadioGroup'; +import { data as rockBandData } from '../../../../../packages/ui/src/components/Table/stories/mocked-data4'; +import { data as catalogData } from '../../../../../packages/ui/src/components/Table/stories/mocked-data1'; +import { MemoryRouter } from 'react-router-dom'; + +// ============================================================================= +// Types +// ============================================================================= + +type RockBandItem = (typeof rockBandData)[0]; +type CatalogItem = (typeof catalogData)[0]; + +// ============================================================================= +// Hero Example +// ============================================================================= + +const heroColumns: ColumnConfig[] = [ + { + id: 'name', + label: 'Band name', + isRowHeader: true, + defaultWidth: '3fr', + cell: item => ( + + ), + }, + { + id: 'genre', + label: 'Genre', + defaultWidth: '3fr', + cell: item => , + }, + { + id: 'yearFormed', + label: 'Year formed', + defaultWidth: '1fr', + cell: item => , + }, + { + id: 'albums', + label: 'Albums', + defaultWidth: '1fr', + cell: item => , + }, +]; + +export function HeroExample() { + const { tableProps } = useTable({ + mode: 'complete', + getData: () => rockBandData, + paginationOptions: { pageSize: 5 }, + }); + + return ( + +

+ + ); +} + +// ============================================================================= +// Sorting Example +// ============================================================================= + +const sortingColumns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + isSortable: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner', + isSortable: true, + cell: item => , + }, + { + id: 'type', + label: 'Type', + isSortable: true, + cell: item => , + }, + { + id: 'lifecycle', + label: 'Lifecycle', + isSortable: true, + cell: item => , + }, +]; + +export function SortingExample() { + const { tableProps } = useTable({ + mode: 'complete', + getData: () => catalogData, + paginationOptions: { pageSize: 5 }, + initialSort: { column: 'name', direction: 'ascending' }, + sortFn: (items, { column, direction }) => { + return [...items].sort((a, b) => { + let aVal: string; + let bVal: string; + if (column === 'owner') { + aVal = a.owner.name; + bVal = b.owner.name; + } else { + aVal = String(a[column as keyof CatalogItem]); + bVal = String(b[column as keyof CatalogItem]); + } + const cmp = aVal.localeCompare(bVal); + return direction === 'descending' ? -cmp : cmp; + }); + }, + }); + + return ( + +
+ + ); +} + +// ============================================================================= +// Search Example +// ============================================================================= + +const searchColumns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + { id: 'type', label: 'Type', cell: item => }, +]; + +export function SearchExample() { + const { tableProps, search } = useTable({ + mode: 'complete', + getData: () => catalogData, + paginationOptions: { pageSize: 5 }, + searchFn: (items, query) => { + const lowerQuery = query.toLocaleLowerCase('en-US'); + return items.filter( + item => + item.name.toLocaleLowerCase('en-US').includes(lowerQuery) || + item.owner.name.toLocaleLowerCase('en-US').includes(lowerQuery) || + item.type.toLocaleLowerCase('en-US').includes(lowerQuery), + ); + }, + }); + + return ( + + + +
No results found for "{search.value}" + ) : ( + No data available + ) + } + {...tableProps} + /> + + + ); +} + +// ============================================================================= +// Selection Example +// ============================================================================= + +const selectionColumns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + { id: 'type', label: 'Type', cell: item => }, +]; + +export function SelectionExample() { + const [selectionMode, setSelectionMode] = useState<'single' | 'multiple'>( + 'multiple', + ); + const [selectionBehavior, setSelectionBehavior] = useState< + 'toggle' | 'replace' + >('toggle'); + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => catalogData.slice(0, 5), + }); + + return ( + + +
+ +
+ + Selection mode: + + { + setSelectionMode(value as 'single' | 'multiple'); + setSelected(new Set()); + }} + > + single + multiple + +
+
+ + Selection behavior: + + { + setSelectionBehavior(value as 'toggle' | 'replace'); + setSelected(new Set()); + }} + > + toggle + replace + +
+
+ + + ); +} + +// ============================================================================= +// Row Actions Example +// ============================================================================= + +export function RowActionsExample() { + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => catalogData.slice(0, 5), + }); + + return ( + +
alert(`Clicked: ${item.name}`), + }} + selection={{ + mode: 'multiple', + behavior: 'toggle', + selected, + onSelectionChange: setSelected, + }} + {...tableProps} + /> + + ); +} + +// ============================================================================= +// Empty State Example +// ============================================================================= + +const emptyStateColumns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + { id: 'type', label: 'Type', cell: item => }, +]; + +export function EmptyStateExample() { + const emptyData: CatalogItem[] = []; + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => emptyData, + }); + + return ( + +
No items yet. Create one to get started.} + {...tableProps} + /> + + ); +} + +// ============================================================================= +// Combined Example +// ============================================================================= + +const combinedColumns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + isSortable: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner', + isSortable: true, + cell: item => , + }, + { + id: 'type', + label: 'Type', + isSortable: true, + cell: item => , + }, +]; + +export function CombinedExample() { + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps, search } = useTable({ + mode: 'offset', + getData: async ({ offset, pageSize, search: query, sort }) => { + // Simulate network delay + await new Promise(resolve => setTimeout(resolve, 1000)); + + // Filter data + let filtered = catalogData; + if (query) { + const lowerQuery = query.toLocaleLowerCase('en-US'); + filtered = filtered.filter( + item => + item.name.toLocaleLowerCase('en-US').includes(lowerQuery) || + item.owner.name.toLocaleLowerCase('en-US').includes(lowerQuery) || + item.type.toLocaleLowerCase('en-US').includes(lowerQuery), + ); + } + + // Sort data + if (sort?.column) { + filtered = [...filtered].sort((a, b) => { + let aVal: string; + let bVal: string; + if (sort.column === 'owner') { + aVal = a.owner.name; + bVal = b.owner.name; + } else { + aVal = String(a[sort.column as keyof CatalogItem]); + bVal = String(b[sort.column as keyof CatalogItem]); + } + const cmp = aVal.localeCompare(bVal); + return sort.direction === 'descending' ? -cmp : cmp; + }); + } + + // Paginate + const data = filtered.slice(offset, offset + pageSize); + + return { data, totalCount: filtered.length }; + }, + paginationOptions: { pageSize: 5, pageSizeOptions: [5, 10, 20] }, + initialSort: { column: 'name', direction: 'ascending' }, + }); + + return ( + + + +
alert(`Clicked: ${item.name}`), + }} + selection={{ + mode: 'multiple', + behavior: 'toggle', + selected, + onSelectionChange: setSelected, + }} + emptyState={ + search.value ? ( + No results match your search + ) : ( + No items available + ) + } + {...tableProps} + /> + + + ); +} + +// ============================================================================= +// Custom Row Example +// ============================================================================= + +const customRowColumns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + { + id: 'lifecycle', + label: 'Lifecycle', + cell: item => , + }, +]; + +export function CustomRowExample() { + const { tableProps } = useTable({ + mode: 'complete', + getData: () => catalogData.slice(0, 5), + }); + + return ( + +
( + + {column => column.cell(item)} + + )} + {...tableProps} + /> + + ); +} + +// ============================================================================= +// Primitives Example +// ============================================================================= + +export function PrimitivesExample() { + const items = catalogData.slice(0, 5); + + return ( + + + + Name + Owner + Type + + + {items.map(item => ( + + + + + + ))} + + + + ); +} diff --git a/docs-ui/src/app/components/table/page.mdx b/docs-ui/src/app/components/table/page.mdx new file mode 100644 index 0000000000..d3ed0d5350 --- /dev/null +++ b/docs-ui/src/app/components/table/page.mdx @@ -0,0 +1,330 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { + HeroExample, + SortingExample, + SearchExample, + SelectionExample, + RowActionsExample, + EmptyStateExample, + CombinedExample, + CustomRowExample, + PrimitivesExample, +} from './components'; +import { + tableReturnColumns, + useTableOptionsPropDefs, + useTableReturnPropDefs, + tablePropDefs, + tablePaginationPropDefs, + columnConfigPropDefs, + cellTextPropDefs, + cellProfilePropDefs, + tableRootPropDefs, + columnPropDefs, + rowPropDefs, + tableBodySkeletonPropDefs, +} from './props-definition'; +import { + tableUsageSnippet, + tableHeroSnippet, + tableConceptsSnippet, + tableSortingSnippet, + tablePaginationSnippet, + tableSearchSnippet, + tableSelectionSnippet, + tableRowActionsHrefSnippet, + tableRowActionsClickSnippet, + tableRowActionsDisabledSnippet, + tableEmptyStateSnippet, + tableOffsetPaginationSnippet, + tableCursorPaginationSnippet, + tableCombinedSnippet, + tableCustomRowSnippet, + tablePrimitivesSnippet, + tableCellRequirementSnippet, +} from './snippets'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; +import { TableDefinition } from '../../../utils/definitions'; + +export const reactAriaUrls = { + table: 'https://react-aria.adobe.com/Table#table', + tableHeader: 'https://react-aria.adobe.com/Table#tableheader', + tableBody: 'https://react-aria.adobe.com/Table#tablebody', + column: 'https://react-aria.adobe.com/Table#column', + row: 'https://react-aria.adobe.com/Table#row', + cell: 'https://react-aria.adobe.com/Table#cell', +}; + + + +} code={tableHeroSnippet} /> + +## Usage + + + +## Core Concepts + +The Table component is designed around a hook + component pattern: + +- **`useTable`** manages data fetching, pagination state, sorting, and filtering. It returns `tableProps` that you spread onto the Table component. +- **`Table`** handles rendering - columns, rows, cells, and interactions. + +The hook supports three modes for different data scenarios: + +- **`complete`** - You have all data available upfront (client-side) +- **`offset`** - Your API uses offset/limit pagination +- **`cursor`** - Your API uses cursor-based pagination + + + +**Controlled vs uncontrolled state** + +By default, `useTable` manages sort, search, and filter state internally. Pass `initialSort`, `initialSearch`, or `initialFilter` to set starting values. + +For full control over state, use the controlled props instead: + +- `sort` / `onSortChange` +- `search` / `onSearchChange` +- `filter` / `onFilterChange` + +### Cell Requirement + +Every cell rendered via `ColumnConfig.cell` (or inside a custom `RowRenderFn`) **must** return a cell component as the top-level element. The available cell components are: + +- **`CellText`** — displays a title with optional description and icon. +- **`CellProfile`** — displays an avatar with a name and optional description. +- **`Cell`** — a generic wrapper for fully custom cell content. + +Returning bare text, React fragments, or other elements without wrapping them in one of these cell components will break the table layout. + + + +## Common Patterns + +### Sorting + +Columns can be made sortable by adding `isSortable: true` to the column configuration. When sortable, clicking a column header cycles through ascending, descending, and unsorted states. A visual indicator shows the current sort direction. + +With `mode: 'complete'`, sorting happens client-side. Provide a `sortFn` that receives the full dataset and the current sort descriptor, and returns the sorted array. You can also set an `initialSort` to define the default sort when the table first renders. For server-side sorting with `offset` or `cursor` modes, see [Server-Side Data](#server-side-data). + +} code={tableSortingSnippet} /> + +### Pagination + +Configure page size and available options through `paginationOptions`. The table displays navigation controls automatically. In `complete` mode, set `type: 'none'` to disable pagination and show all rows. + + + +### Search + +The `useTable` hook returns a `search` object with `value` and `onChange` properties, ready to connect to a search input. With `mode: 'complete'`, provide a `searchFn` that filters the dataset based on the search query. + +The search state is debounced internally, so rapid typing doesn't trigger excessive re-filtering. When the search query changes, pagination resets to the first page automatically. + +For server-side search with `offset` or `cursor` modes, the search query is passed to your `getData` function. See [Server-Side Data](#server-side-data). + +} code={tableSearchSnippet} /> + +### Row Selection + +Tables support row selection with two configuration options: `mode` and `behavior`. + +Selection mode controls how many rows can be selected: + +- `single` - Only one row can be selected at a time +- `multiple` - Any number of rows can be selected, with a header checkbox for select-all + +Selection behavior controls the interaction style: + +- `toggle` - Checkboxes appear for selection. Click a checkbox to select/deselect. +- `replace` - No checkboxes. Click a row to select it (replacing previous selection). Use Cmd/Ctrl+click to select multiple rows. + +Selection state can be managed in two ways: + +- **Controlled** - Pass both `selected` and `onSelectionChange` to fully manage state externally +- **Uncontrolled** - Pass only `onSelectionChange` to let the Table manage state while receiving change notifications + +} code={tableSelectionSnippet} /> + +### Row Actions + +Rows can respond to user interaction in two ways: navigating to a URL or triggering a callback. + +Use `getHref` when rows should link to detail pages. The entire row becomes clickable and navigates on click. Links within cells remain independently clickable. + + + +Use `onClick` for custom actions like opening a panel or triggering a dialog. + + + +When combining row actions with selection, the interaction depends on selection behavior: + +- **`toggle`**: Clicking a row triggers its action when nothing is selected. Once any row is selected, clicking anywhere on the row toggles selection instead. +- **`replace`**: Single click selects the row. Double-click triggers the row action. + +You can also disable specific rows from being clicked using `getIsDisabled`: + + + +} code={tableRowActionsHrefSnippet} /> + +### Empty State + +When the table has no data to display, provide an `emptyState` to show a helpful message instead of an empty table body. Consider providing different messages depending on context - an empty search result is different from a table with no data at all. + +} code={tableEmptyStateSnippet} /> + +## Server-Side Data + +When your data comes from an API with server-side pagination, use `offset` or `cursor` mode instead of `complete`. The key difference: instead of providing all data upfront, you provide a `getData` function that fetches data for the current page. + +### Offset Pagination + +Use `mode: 'offset'` when your API accepts `offset` and `limit` (or `skip` and `take`) parameters. + + + +The `signal` parameter is an `AbortSignal` - pass it to `fetch` so in-flight requests are cancelled when the user navigates away or triggers a new query. + +### Cursor Pagination + +Use `mode: 'cursor'` when your API uses cursor-based pagination (common with GraphQL or APIs that return `nextCursor`/`prevCursor` tokens). + + + +### Loading States + +When fetching data, the table shows a loading state. If the user triggers a new query (by paginating, sorting, or searching) while previous data is displayed, the table enters a "stale" state where it continues showing the previous data until new data arrives. This prevents jarring layout shifts. + +You can access these states via `tableProps.loading` and `tableProps.isStale` if you need to render additional loading indicators. + +## Combining Features + +Real-world tables often combine multiple features. Here's an example with search, sorting, pagination, and row selection working together. + +} code={tableCombinedSnippet} /> + +## Custom Tables + +For most use cases, `useTable` + `Table` provides everything you need. When you need more control, there are several ways you can customize your table. + +### Custom Row and Header Rendering + +Use a render function for `rowConfig` when you need to customize individual rows based on their data - for example, highlighting rows that require attention. Use `header` in column config to customize header cell content. + +} code={tableCustomRowSnippet} /> + +### Using Primitives Directly + +When the `Table` component doesn't support your use case, you can compose with the lower-level primitives: `TableRoot`, `TableHeader`, `TableBody`, `Column`, and `Row`. + +} code={tablePrimitivesSnippet} /> + +## API Reference + +### useTable + +The `useTable` hook manages data fetching, pagination, sorting, and filtering. + +**Options** + + + +**Return Value** + + + +### Table + +The main table component. + + + +### ColumnConfig + + + +### CellText + + + + + +### CellProfile + + + + + +### TablePagination + + + +### Primitives + +Low-level components for building custom table layouts. + +#### TableRoot + + + + + +#### TableHeader + + + +#### TableBody + + + +#### Column + + + + + +#### Row + + + + + +#### Cell + + + +#### TableBodySkeleton + +A table body that renders animated skeleton rows as a loading placeholder. Useful when composing with primitives and you need a loading state without the full `Table` component. + + + + + + diff --git a/docs-ui/src/app/components/table/props-definition.tsx b/docs-ui/src/app/components/table/props-definition.tsx new file mode 100644 index 0000000000..30381e30f7 --- /dev/null +++ b/docs-ui/src/app/components/table/props-definition.tsx @@ -0,0 +1,522 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +// ============================================================================= +// PropsTable Column Configuration (Table docs use description instead of responsive) +// ============================================================================= + +// For return values (no default column) +export const tableReturnColumns = [ + { key: 'prop' as const, width: '15%' }, + { key: 'type' as const, width: '25%' }, + { key: 'description' as const, width: '60%' }, +]; + +// ============================================================================= +// useTable Hook +// ============================================================================= + +export const useTableOptionsPropDefs: Record = { + mode: { + type: 'enum', + values: ['complete', 'offset', 'cursor'], + description: ( + <> + Data fetching strategy (required). Use complete for + client-side data, offset for offset/limit APIs,{' '} + cursor for cursor-based pagination. + + ), + }, + getData: { + type: 'enum', + values: ['function'], + description: + 'Function that returns or fetches data (required for "offset" and "cursor" modes). For the "complete" mode, either this or `data` must be provided. Signature varies by mode.', + }, + data: { + type: 'enum', + values: ['T[]'], + description: + 'The data for the table. Only applicable for "complete" mode, and either this or `getData` must be provided.', + }, + paginationOptions: { + type: 'complex', + description: 'Pagination configuration.', + complexType: { + name: 'PaginationOptions', + properties: { + type: { + type: "'page' | 'none'", + description: + "Pagination mode. Set to 'none' to disable pagination and show all rows (complete mode only). Defaults to 'page'.", + }, + pageSize: { + type: 'number', + description: 'Number of items per page. Defaults to 20.', + }, + pageSizeOptions: { + type: 'number[]', + description: 'Available page size options for the dropdown.', + }, + initialOffset: { + type: 'number', + description: 'Starting offset for the first page.', + }, + showPageSizeOptions: { + type: 'boolean', + description: + 'Whether to show the page size dropdown. Defaults to true.', + }, + showPaginationLabel: { + type: 'boolean', + description: + "Whether to display the pagination label (e.g., '1 - 20 of 150').", + }, + getLabel: { + type: '(props) => string', + description: 'Custom function to generate the pagination label text.', + }, + }, + }, + }, + // Uncontrolled state + initialSort: { + type: 'enum', + values: ['SortDescriptor'], + description: 'Default sort configuration on first render (uncontrolled).', + }, + initialSearch: { + type: 'string', + description: 'Default search value on first render (uncontrolled).', + }, + initialFilter: { + type: 'enum', + values: ['TFilter'], + description: 'Default filter value on first render (uncontrolled).', + }, + // Controlled state + sort: { + type: 'enum', + values: ['SortDescriptor'], + description: 'Current sort state (controlled).', + }, + onSortChange: { + type: 'enum', + values: ['(sort: SortDescriptor) => void'], + description: 'Sort change handler (controlled).', + }, + search: { + type: 'string', + description: 'Current search value (controlled).', + }, + onSearchChange: { + type: 'enum', + values: ['(search: string) => void'], + description: 'Search change handler (controlled).', + }, + filter: { + type: 'enum', + values: ['TFilter'], + description: 'Current filter value (controlled).', + }, + onFilterChange: { + type: 'enum', + values: ['(filter: TFilter) => void'], + description: 'Filter change handler (controlled).', + }, + // Client-side functions + sortFn: { + type: 'enum', + values: ['(data, sort) => data'], + description: ( + <> + Client-side sort function. Only used with complete mode. + + ), + }, + searchFn: { + type: 'enum', + values: ['(data, query) => data'], + description: ( + <> + Client-side search function. Only used with complete mode. + + ), + }, + filterFn: { + type: 'enum', + values: ['(data, filter) => data'], + description: ( + <> + Client-side filter function. Only used with complete mode. + + ), + }, +}; + +export const useTableReturnPropDefs: Record = { + tableProps: { + type: 'enum', + values: ['object'], + description: ( + <> + Props to spread onto the Table component. Includes data, + loading, error, pagination, and sort state. + + ), + }, + reload: { + type: 'enum', + values: ['() => void'], + description: 'Function to trigger a data refetch.', + }, + search: { + type: 'enum', + values: ['{ value, onChange }'], + description: ( + <> + Search state object for binding to a SearchField component. + + ), + }, + filter: { + type: 'enum', + values: ['{ value, onChange }'], + description: 'Filter state object for binding to filter controls.', + }, +}; + +// ============================================================================= +// Table Component +// ============================================================================= + +export const tablePropDefs: Record = { + columnConfig: { + type: 'enum', + values: ['ColumnConfig[]'], + description: + 'Array of column configurations defining how each column renders.', + }, + data: { + type: 'enum', + values: ['T[]'], + description: 'Array of data items to display in the table.', + }, + loading: { + type: 'boolean', + default: 'false', + description: 'Whether the table is in a loading state.', + }, + isStale: { + type: 'boolean', + default: 'false', + description: + 'Whether the displayed data is stale while new data is loading.', + }, + error: { + type: 'enum', + values: ['Error'], + description: 'Error object if data fetching failed.', + }, + pagination: { + type: 'enum', + values: ['TablePaginationType'], + description: ( + <> + Pagination configuration (required). Use{' '} + {'{ type: "none" }'} to disable or{' '} + {'{ type: "page", ...props }'} for pagination. + + ), + }, + sort: { + type: 'enum', + values: ['SortState'], + description: 'Sort state including current descriptor and change handler.', + }, + rowConfig: { + type: 'enum', + values: ['RowConfig | RowRenderFn'], + description: ( + <> + Row configuration object with getHref, onClick + , getIsDisabled, or a render function for custom rows. + + ), + }, + selection: { + type: 'enum', + values: ['TableSelection'], + description: + 'Selection configuration including mode, behavior, selected keys, and change handler.', + }, + emptyState: { + type: 'enum', + values: ['ReactNode'], + description: 'Content to display when the table has no data.', + }, + ...classNamePropDefs, + ...stylePropDefs, +}; + +// ============================================================================= +// ColumnConfig +// ============================================================================= + +export const columnConfigPropDefs: Record = { + id: { + type: 'string', + description: 'Unique identifier for the column.', + }, + label: { + type: 'string', + description: 'Display label for the column header.', + }, + cell: { + type: 'enum', + values: ['(item) => ReactNode'], + description: 'Render function for cell content.', + }, + header: { + type: 'enum', + values: ['() => ReactNode'], + description: 'Optional custom render function for the header cell.', + }, + isSortable: { + type: 'boolean', + default: 'false', + description: 'Whether the column supports sorting.', + }, + isHidden: { + type: 'boolean', + default: 'false', + description: 'Whether the column is hidden.', + }, + isRowHeader: { + type: 'boolean', + default: 'false', + description: 'Whether this column is the row header for accessibility.', + }, + width: { + type: 'enum', + values: ['ColumnSize'], + description: 'Current width of the column.', + }, + defaultWidth: { + type: 'enum', + values: ['ColumnSize'], + description: ( + <> + Default width of the column (e.g., 1fr, 200px + ). + + ), + }, + minWidth: { + type: 'enum', + values: ['ColumnStaticSize'], + description: 'Minimum width of the column.', + }, + maxWidth: { + type: 'enum', + values: ['ColumnStaticSize'], + description: 'Maximum width of the column.', + }, +}; + +// ============================================================================= +// CellText +// Note: Extends React Aria Cell props +// ============================================================================= + +export const cellTextPropDefs: Record = { + title: { + type: 'string', + description: 'Primary text content of the cell.', + }, + description: { + type: 'string', + description: 'Secondary description text displayed below the title.', + }, + color: { + type: 'enum', + values: ['TextColors'], + description: 'Text color variant.', + }, + leadingIcon: { + type: 'enum', + values: ['ReactNode'], + description: 'Icon displayed before the text content.', + }, + href: { + type: 'string', + description: 'URL to navigate to when the cell is clicked.', + }, +}; + +// ============================================================================= +// CellProfile +// Note: Extends React Aria Cell props +// ============================================================================= + +export const cellProfilePropDefs: Record = { + name: { + type: 'string', + description: 'Name to display.', + }, + src: { + type: 'string', + description: 'URL of the avatar image.', + }, + description: { + type: 'string', + description: 'Secondary text displayed below the name.', + }, + href: { + type: 'string', + description: 'URL to navigate to when clicked.', + }, + color: { + type: 'enum', + values: ['TextColors'], + description: 'Text color variant.', + }, +}; + +// ============================================================================= +// TablePagination +// ============================================================================= + +export const tablePaginationPropDefs: Record = { + pageSize: { + type: 'number', + description: 'Number of items per page.', + }, + pageSizeOptions: { + type: 'enum', + values: ['number[]'], + description: 'Available page size options for the dropdown.', + }, + offset: { + type: 'number', + description: 'Current offset (starting index) in the data.', + }, + totalCount: { + type: 'number', + description: 'Total number of items across all pages.', + }, + hasNextPage: { + type: 'boolean', + description: 'Whether there is a next page available.', + }, + hasPreviousPage: { + type: 'boolean', + description: 'Whether there is a previous page available.', + }, + onNextPage: { + type: 'enum', + values: ['() => void'], + description: 'Handler called when navigating to the next page.', + }, + onPreviousPage: { + type: 'enum', + values: ['() => void'], + description: 'Handler called when navigating to the previous page.', + }, + onPageSizeChange: { + type: 'enum', + values: ['(size: number) => void'], + description: 'Handler called when the page size changes.', + }, + showPageSizeOptions: { + type: 'boolean', + default: 'true', + description: 'Whether to show the page size dropdown.', + }, + getLabel: { + type: 'enum', + values: ['(props) => string'], + description: 'Custom function to generate the pagination label text.', + }, + showPaginationLabel: { + type: 'boolean', + default: 'true', + description: + 'Whether to display the pagination label (e.g., "1 - 20 of 150"). When false, only navigation controls are shown.', + }, +}; + +// ============================================================================= +// Primitives +// ============================================================================= + +export const tableRootPropDefs: Record = { + stale: { + type: 'boolean', + default: 'false', + description: ( + <> + Whether the table data is stale (e.g., while fetching new data). Adds{' '} + aria-busy attribute. + + ), + }, + loading: { + type: 'boolean', + default: 'false', + description: ( + <> + Whether the table is in a loading state (e.g., initial data fetch). Adds{' '} + aria-busy attribute and data-loading data + attribute for styling. + + ), + }, +}; + +export const columnPropDefs: Record = { + isRowHeader: { + type: 'boolean', + description: 'Whether this column is a row header for accessibility.', + }, + children: { + type: 'enum', + values: ['ReactNode'], + description: 'Column header content.', + }, +}; + +export const tableBodySkeletonPropDefs: Record = { + columns: { + type: 'enum', + values: ['{ id: string }[]'], + description: + 'Array of column objects. Each item must have an `id` property. Compatible with `ColumnConfig` and custom column types.', + }, +}; + +export const rowPropDefs: Record = { + id: { + type: 'enum', + values: ['string | number'], + description: 'Unique identifier for the row.', + }, + children: { + type: 'enum', + values: ['ReactNode | ((column) => ReactNode)'], + description: + 'Row content. Can be a render function receiving column config.', + }, + noTrack: { + type: 'boolean', + description: + 'Suppresses the automatic analytics click event, e.g. if you already have custom tracking.', + }, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/table/snippets.ts b/docs-ui/src/app/components/table/snippets.ts new file mode 100644 index 0000000000..39f266f2ea --- /dev/null +++ b/docs-ui/src/app/components/table/snippets.ts @@ -0,0 +1,376 @@ +// ============================================================================= +// Usage +// ============================================================================= + +export const tableUsageSnippet = `import { Table, useTable, CellText, type ColumnConfig } from '@backstage/ui'; + +const columns: ColumnConfig[] = [ + { id: 'name', label: 'Name', isRowHeader: true, cell: item => }, + { id: 'owner', label: 'Owner', cell: item => }, +]; + +function MyTable() { + const { tableProps } = useTable({ + mode: 'complete', + data, + }); + + return
; +}`; + +// ============================================================================= +// Hero / Quick Start +// ============================================================================= + +export const tableHeroSnippet = `import { Table, CellText, useTable, type ColumnConfig } from '@backstage/ui'; + +const data = [ + { id: 1, name: 'Service A', owner: 'Team Alpha', type: 'service' }, + { id: 2, name: 'Service B', owner: 'Team Beta', type: 'website' }, + { id: 3, name: 'Library C', owner: 'Team Gamma', type: 'library' }, +]; + +const columns: ColumnConfig[] = [ + { id: 'name', label: 'Name', isRowHeader: true, cell: item => }, + { id: 'owner', label: 'Owner', cell: item => }, + { id: 'type', label: 'Type', cell: item => }, +]; + +function MyTable() { + const { tableProps } = useTable({ + mode: 'complete', + data, + }); + + return
; +}`; + +// ============================================================================= +// Core Concepts +// ============================================================================= + +export const tableConceptsSnippet = `// What useTable returns +const { + tableProps, // Spread onto
+ reload, // Trigger data refetch + search, // { value, onChange } for search input + filter, // { value, onChange } for filters +} = useTable({ ... });`; + +// ============================================================================= +// Cell Requirement +// ============================================================================= + +export const tableCellRequirementSnippet = `// ✅ Correct — CellText is the top-level element +cell: item => ; + +// ✅ Correct — Cell wraps custom content +cell: item => ( + + + +); + +// ❌ Wrong — bare text without a cell wrapper +cell: item => {item.name};`; + +// ============================================================================= +// Common Patterns +// ============================================================================= + +export const tableSortingSnippet = `const columns: ColumnConfig[] = [ + { id: 'name', label: 'Name', isSortable: true, cell: item => }, + { id: 'owner', label: 'Owner', isSortable: true, cell: item => }, + { id: 'type', label: 'Type', cell: item => }, +]; + +const { tableProps } = useTable({ + mode: 'complete', + data, + initialSort: { column: 'name', direction: 'ascending' }, + sortFn: (items, { column, direction }) => { + return [...items].sort((a, b) => { + const aVal = String(a[column]); + const bVal = String(b[column]); + const cmp = aVal.localeCompare(bVal); + return direction === 'descending' ? -cmp : cmp; + }); + }, +}); + +return
;`; + +export const tablePaginationSnippet = `const { tableProps } = useTable({ + mode: 'complete', + data, + paginationOptions: { + pageSize: 10, + pageSizeOptions: [10, 25, 50], + }, +});`; + +export const tableSearchSnippet = `const { tableProps, search } = useTable({ + mode: 'complete', + data, + searchFn: (items, query) => { + const lowerQuery = query.toLowerCase(); + return items.filter(item => + item.name.toLowerCase().includes(lowerQuery) || + item.owner.toLowerCase().includes(lowerQuery) + ); + }, +}); + +return ( + <> + +
+ +);`; + +export const tableSelectionSnippet = `const [selected, setSelected] = useState | 'all'>(new Set()); + +const { tableProps } = useTable({ + mode: 'complete', + data, +}); + +return ( +
+);`; + +export const tableRowActionsHrefSnippet = `
\`/catalog/\${item.namespace}/\${item.name}\` + }} + {...tableProps} +/>`; + +export const tableRowActionsClickSnippet = `
openDetailPanel(item) + }} + {...tableProps} +/>`; + +export const tableRowActionsDisabledSnippet = `
openDetailPanel(item), + getIsDisabled: item => item.status === 'archived', + }} + {...tableProps} +/>`; + +export const tableEmptyStateSnippet = `const { tableProps, search } = useTable({ + mode: 'complete', + data, + searchFn: (items, query) => { /* ... */ }, +}); + +return ( +
No results match "{search.value}" + : No items yet. Create one to get started. + } + {...tableProps} + /> +);`; + +// ============================================================================= +// Server-Side Data +// ============================================================================= + +export const tableOffsetPaginationSnippet = `const { tableProps } = useTable({ + mode: 'offset', + getData: async ({ offset, pageSize, sort, search, filter, signal }) => { + const response = await fetch( + \`/api/items?offset=\${offset}&limit=\${pageSize}&q=\${search}\`, + { signal } + ); + const { items, totalCount } = await response.json(); + + return { + data: items, + totalCount, + }; + }, + paginationOptions: { + pageSize: 20, + pageSizeOptions: [20, 50, 100], + }, +});`; + +export const tableCursorPaginationSnippet = `const { tableProps } = useTable({ + mode: 'cursor', + getData: async ({ cursor, pageSize, sort, search, signal }) => { + const response = await fetch( + \`/api/items?cursor=\${cursor ?? ''}&limit=\${pageSize}&q=\${search}\`, + { signal } + ); + const { items, nextCursor, prevCursor, totalCount } = await response.json(); + + return { + data: items, + nextCursor, + prevCursor, + totalCount, // optional - enables "X of Y" display + }; + }, + paginationOptions: { + pageSize: 20, + pageSizeOptions: [20, 50, 100], + }, +});`; + +// ============================================================================= +// Combining Features +// ============================================================================= + +export const tableCombinedSnippet = `interface TypeFilter { + type: string | null; +} + +const columns: ColumnConfig[] = [ + { id: 'name', label: 'Name', isRowHeader: true, isSortable: true, + cell: item => }, + { id: 'owner', label: 'Owner', isSortable: true, + cell: item => }, + { id: 'type', label: 'Type', isSortable: true, + cell: item => }, +]; + +function ItemsTable() { + const [selected, setSelected] = useState | 'all'>(new Set()); + + const { tableProps, search, filter } = useTable({ + mode: 'offset', + initialSort: { column: 'name', direction: 'ascending' }, + getData: async ({ offset, pageSize, sort, search, filter, signal }) => { + const params = new URLSearchParams({ + offset: String(offset), + limit: String(pageSize), + q: search, + ...(sort && { sortBy: sort.column, sortDir: sort.direction }), + ...(filter?.type && { type: filter.type }), + }); + + const response = await fetch(\`/api/items?\${params}\`, { signal }); + const { items, totalCount } = await response.json(); + + return { data: items, totalCount }; + }, + }); + + return ( + + + +
openDetailPanel(item), + }} + selection={{ + mode: 'multiple', + behavior: 'toggle', + selected, + onSelectionChange: setSelected, + }} + emptyState={ + search.value || filter.value?.type + ? No results match your filters + : No items available + } + {...tableProps} + /> + + ); +}`; + +// ============================================================================= +// Custom Tables +// ============================================================================= + +export const tableCustomRowSnippet = `import { Fragment } from 'react'; + +const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + header: () => Name (required), + cell: item => , + }, + { id: 'owner', label: 'Owner', cell: item => }, + { id: 'lifecycle', label: 'Lifecycle', cell: item => }, +]; + +const { tableProps } = useTable({ + mode: 'complete', + getData: () => data, +}); + +
( + + {column => ( + {column.cell(item)} + )} + + )} + {...tableProps} +/>`; + +export const tablePrimitivesSnippet = ` + + Name + Owner + Type + + + {items.map(item => ( + + + + + + ))} + +`; diff --git a/docs-ui/src/app/components/tabs/components.tsx b/docs-ui/src/app/components/tabs/components.tsx new file mode 100644 index 0000000000..8fbf3d4924 --- /dev/null +++ b/docs-ui/src/app/components/tabs/components.tsx @@ -0,0 +1,104 @@ +'use client'; + +import { + Tabs, + TabList, + Tab, + TabPanel, +} from '../../../../../packages/ui/src/components/Tabs'; +import { Text } from '../../../../../packages/ui/src/components/Text/Text'; +import { MemoryRouter } from 'react-router-dom'; + +export const Default = () => { + return ( + + + + Tab 1 + Tab 2 + Tab 3 + + + Content for Tab 1 + + + Content for Tab 2 + + + Content for Tab 3 + + + + ); +}; + +export const DefaultSelectedKey = () => { + return ( + + + + Tab 1 + Tab 2 + Tab 3 + + + Content for Tab 1 + + + Content for Tab 2 + + + Content for Tab 3 + + + + ); +}; + +export const DisabledTabs = () => { + return ( + + + + Tab 1 + + Tab 2 (Disabled) + + Tab 3 + + + Content for Tab 1 + + + Content for Tab 2 + + + Content for Tab 3 + + + + ); +}; + +export const Orientation = () => { + return ( + + + + Tab 1 + Tab 2 + Tab 3 + + + Content for Tab 1 + + + Content for Tab 2 + + + Content for Tab 3 + + + + ); +}; diff --git a/docs-ui/src/app/components/tabs/page.mdx b/docs-ui/src/app/components/tabs/page.mdx new file mode 100644 index 0000000000..a07b70dcd3 --- /dev/null +++ b/docs-ui/src/app/components/tabs/page.mdx @@ -0,0 +1,116 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; +import { + tabsPropDefs, + tabListPropDefs, + tabPropDefs, + tabPanelPropDefs, +} from './props-definition'; +import { + tabsUsageSnippet, + defaultSnippet, + defaultSelectedKeySnippet, + disabledTabsSnippet, + orientationSnippet, + urlNavigationSnippet, +} from './snippets'; +import { + Default, + DefaultSelectedKey, + DisabledTabs, + Orientation, +} from './components'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { TabsDefinition } from '../../../utils/definitions'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + +export const reactAriaUrls = { + tabs: 'https://react-aria.adobe.com/Tabs', +}; + + + +} code={defaultSnippet} /> + +## Usage + + + +## API reference + +### Tabs + +Container that groups the tab list and panels. + + + + + +### TabList + +Container for the tab buttons. + + + + + +### Tab + +Individual tab button. Add `href` to enable URL-based tab selection. + + + + + +### TabPanel + +Content panel associated with a tab. + + + + + +## Examples + +### Default selected + +} + code={defaultSelectedKeySnippet} +/> + +### Disabled tabs + +} + code={disabledTabsSnippet} +/> + +### Vertical orientation + +} + code={orientationSnippet} +/> + +### URL-based navigation + +Add `href` to Tab components to enable URL-based tab selection. The active tab is determined by the current route. + + + + + + diff --git a/docs-ui/src/app/components/tabs/props-definition.ts b/docs-ui/src/app/components/tabs/props-definition.ts new file mode 100644 index 0000000000..92d1c3d258 --- /dev/null +++ b/docs-ui/src/app/components/tabs/props-definition.ts @@ -0,0 +1,88 @@ +import { + childrenPropDefs, + classNamePropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const tabsPropDefs: Record = { + orientation: { + type: 'enum', + values: ['horizontal', 'vertical'], + default: 'horizontal', + description: + 'Layout direction. Use horizontal for top navigation, vertical for sidebar-style.', + }, + selectedKey: { + type: 'string', + description: 'The currently selected tab key (controlled).', + }, + defaultSelectedKey: { + type: 'string', + description: 'The default selected tab key (uncontrolled).', + }, + onSelectionChange: { + type: 'enum', + values: ['(key: Key) => void'], + description: 'Handler called when the selected tab changes.', + }, + isDisabled: { + type: 'boolean', + default: 'false', + description: + 'Disables all tabs. Use when an entire section is unavailable.', + }, + disabledKeys: { + type: 'enum', + values: ['Iterable'], + description: 'Keys of tabs that should be disabled.', + }, + ...childrenPropDefs, + ...classNamePropDefs, +}; + +export const tabListPropDefs: Record = { + ...childrenPropDefs, + ...classNamePropDefs, +}; + +export const tabPropDefs: Record = { + id: { + type: 'string', + required: true, + description: 'Unique identifier matching the corresponding TabPanel.', + }, + href: { + type: 'string', + description: + 'URL to navigate to. When set, tab selection is controlled by the current route.', + }, + matchStrategy: { + type: 'enum', + values: ['exact', 'prefix'], + default: 'exact', + description: + 'URL matching strategy. Use exact for leaf routes, prefix for parent routes.', + }, + isDisabled: { + type: 'boolean', + default: 'false', + description: 'Disables this tab. Use for temporarily unavailable options.', + }, + noTrack: { + type: 'boolean', + description: + 'Suppresses the automatic analytics click event, e.g. if you already have custom tracking.', + }, + ...childrenPropDefs, + ...classNamePropDefs, +}; + +export const tabPanelPropDefs: Record = { + id: { + type: 'string', + required: true, + description: 'Unique identifier matching the corresponding Tab.', + }, + ...childrenPropDefs, + ...classNamePropDefs, +}; diff --git a/docs-ui/src/app/components/tabs/snippets.ts b/docs-ui/src/app/components/tabs/snippets.ts new file mode 100644 index 0000000000..651a0a89af --- /dev/null +++ b/docs-ui/src/app/components/tabs/snippets.ts @@ -0,0 +1,71 @@ +export const tabsUsageSnippet = `import { Tabs, TabList, Tab, TabPanel } from '@backstage/ui'; + + + + Tab 1 + Tab 2 + + Content 1 + Content 2 +`; + +export const defaultSnippet = ` + + Tab 1 + Tab 2 + Tab 3 + + + Content for Tab 1 + + + Content for Tab 2 + + + Content for Tab 3 + +`; + +export const defaultSelectedKeySnippet = ` + + Tab 1 + Tab 2 + Tab 3 + + Content 1 + Content 2 + Content 3 +`; + +export const disabledTabsSnippet = ` + + Tab 1 + Tab 2 (Disabled) + Tab 3 + + Content 1 + Content 2 + Content 3 +`; + +export const orientationSnippet = ` + + Tab 1 + Tab 2 + Tab 3 + + Content 1 + Content 2 + Content 3 +`; + +export const urlNavigationSnippet = ` + + Overview + Profile + Security + + Overview content + Profile content + Security content +`; diff --git a/docs-ui/src/app/components/tag-group/components.tsx b/docs-ui/src/app/components/tag-group/components.tsx new file mode 100644 index 0000000000..a8455c1d1f --- /dev/null +++ b/docs-ui/src/app/components/tag-group/components.tsx @@ -0,0 +1,127 @@ +'use client'; + +import { + TagGroup, + Tag, +} from '../../../../../packages/ui/src/components/TagGroup/TagGroup'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; +import { MemoryRouter } from 'react-router-dom'; +import { useState } from 'react'; +import { useListData } from 'react-stately'; +import type { Selection } from 'react-aria-components'; +import { + RiAccountCircleLine, + RiBugLine, + RiEyeLine, + RiHeartLine, +} from '@remixicon/react'; + +interface ListItem { + id: string; + name: string; + icon: React.ReactNode; + isDisabled?: boolean; +} + +const initialList: ListItem[] = [ + { id: 'banana', name: 'Banana', icon: }, + { + id: 'apple', + name: 'Apple', + icon: , + isDisabled: true, + }, + { id: 'orange', name: 'Orange', icon: , isDisabled: true }, + { id: 'pear', name: 'Pear', icon: }, + { id: 'grape', name: 'Grape', icon: }, + { id: 'pineapple', name: 'Pineapple', icon: }, + { id: 'strawberry', name: 'Strawberry', icon: }, +]; + +export const Default = () => ( + + + {initialList.map(item => ( + {item.name} + ))} + + +); + +export const WithLink = () => ( + + + {initialList.map(item => ( + + {item.name} + + ))} + + +); + +export const WithIcon = () => ( + + + {initialList.map(item => ( + + {item.name} + + ))} + + +); + +export const Sizes = () => ( + + + + {initialList.map(item => ( + + {item.name} + + ))} + + + {initialList.map(item => ( + + {item.name} + + ))} + + + +); + +export const RemovingTags = () => { + const [selected, setSelected] = useState(new Set(['travel'])); + const list = useListData({ + initialItems: initialList, + }); + + return ( + + + aria-label="Tag Group" + items={list.items} + onRemove={keys => list.remove(...keys)} + selectedKeys={selected} + onSelectionChange={setSelected} + > + {item => {item.name}} + + + ); +}; + +export const Disabled = () => ( + + + {initialList.map(item => ( + + {item.name} + + ))} + + +); diff --git a/docs-ui/src/app/components/tag-group/page.mdx b/docs-ui/src/app/components/tag-group/page.mdx new file mode 100644 index 0000000000..18551ffed7 --- /dev/null +++ b/docs-ui/src/app/components/tag-group/page.mdx @@ -0,0 +1,91 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; +import { + Default, + WithLink, + WithIcon, + Sizes, + RemovingTags, + Disabled, +} from './components'; +import { tagGroupPropDefs, tagPropDefs } from './props-definition'; +import { + usage, + preview, + withLink, + disabled, + withIcons, + sizes, + removingTags, +} from './snippets'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { TagGroupDefinition } from '../../../utils/definitions'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + +export const reactAriaUrls = { + tagGroup: 'https://react-aria.adobe.com/TagGroup', +}; + + + +} code={preview} /> + +## Usage + + + +## API reference + +### TagGroup + +Container for a collection of tags. + + + + + +### Tag + +Individual tag item within a group. + + + + + +## Examples + +### With links + +} code={withLink} /> + +### With icons + +} code={withIcons} /> + +### Sizes + +} code={sizes} /> + +### Removable + +} + code={removingTags} +/> + +### Disabled + +} code={disabled} /> + + + + diff --git a/docs-ui/src/app/components/tag-group/props-definition.tsx b/docs-ui/src/app/components/tag-group/props-definition.tsx new file mode 100644 index 0000000000..dc10d5f7e7 --- /dev/null +++ b/docs-ui/src/app/components/tag-group/props-definition.tsx @@ -0,0 +1,94 @@ +import { + classNamePropDefs, + childrenPropDefs, + type PropDef, +} from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +export const tagGroupPropDefs: Record = { + items: { + type: 'enum', + values: ['Iterable'], + description: 'Item objects in the collection.', + }, + renderEmptyState: { + type: 'enum', + values: ['() => ReactNode'], + description: 'Content to display when the collection is empty.', + }, + selectionMode: { + type: 'enum', + values: ['none', 'single', 'multiple'], + description: 'The type of selection allowed.', + }, + selectedKeys: { + type: 'enum', + values: ['all', 'Iterable'], + description: 'The currently selected keys (controlled).', + }, + defaultSelectedKeys: { + type: 'enum', + values: ['all', 'Iterable'], + description: 'The initial selected keys (uncontrolled).', + }, + disabledKeys: { + type: 'enum', + values: ['Iterable'], + description: 'Keys of tags that should be disabled.', + }, + onRemove: { + type: 'enum', + values: ['(keys: Set) => void'], + description: 'Handler called when a tag is removed.', + }, + onSelectionChange: { + type: 'enum', + values: ['(keys: Selection) => void'], + description: 'Handler called when the selection changes.', + }, + ...childrenPropDefs, + ...classNamePropDefs, +}; + +export const tagPropDefs: Record = { + id: { + type: 'string', + description: 'Unique identifier for the tag.', + }, + textValue: { + type: 'string', + description: + 'Text value for accessibility. Derived from children if string.', + }, + href: { + type: 'string', + description: 'URL to navigate to when the tag is clicked.', + }, + icon: { + type: 'enum', + values: ['ReactNode'], + description: 'Icon displayed before the tag text.', + }, + size: { + type: 'enum', + values: ['small', 'medium'], + default: 'small', + description: ( + <> + Visual size of the tag. Use small for inline or dense + layouts, medium for standalone tags. + + ), + }, + isDisabled: { + type: 'boolean', + description: 'Whether the tag is disabled.', + }, + noTrack: { + type: 'boolean', + description: + 'Suppresses the automatic analytics click event, e.g. if you already have custom tracking.', + }, + ...childrenPropDefs, + ...classNamePropDefs, +}; diff --git a/docs-ui/src/app/components/tag-group/snippets.ts b/docs-ui/src/app/components/tag-group/snippets.ts new file mode 100644 index 0000000000..c939c69d14 --- /dev/null +++ b/docs-ui/src/app/components/tag-group/snippets.ts @@ -0,0 +1,62 @@ +export const usage = `import { TagGroup, Tag } from '@backstage/ui'; + + + Tag 1 + Tag 2 +`; + +export const preview = ` + Banana + Apple + Orange + Pear + Grape + Pineapple + Strawberry +`; + +export const withLink = ` + Banana + Apple + Orange +`; + +export const disabled = ` + Banana + Apple + Orange + Pear +`; + +export const withIcons = ` + }>Banana + }>Apple + }>Orange + }>Pear +`; + +export const sizes = ` + + }>Banana + }>Apple + + + }>Banana + }>Apple + +`; + +export const removingTags = `const list = useListData({ + initialItems: [ + { id: 'banana', name: 'Banana' }, + { id: 'apple', name: 'Apple' }, + { id: 'orange', name: 'Orange' }, + ], +}); + + list.remove(...keys)} +> + {item => {item.name}} +`; diff --git a/docs-ui/src/app/components/text-field/components.tsx b/docs-ui/src/app/components/text-field/components.tsx new file mode 100644 index 0000000000..6f6c8019ea --- /dev/null +++ b/docs-ui/src/app/components/text-field/components.tsx @@ -0,0 +1,51 @@ +'use client'; + +import { TextField } from '../../../../../packages/ui/src/components/TextField/TextField'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; +import { RiSparklingLine } from '@remixicon/react'; + +export const WithLabel = () => { + return ( + + ); +}; + +export const Sizes = () => { + return ( + + } + /> + } + /> + + ); +}; + +export const WithDescription = () => { + return ( + + ); +}; diff --git a/docs-ui/src/app/components/text-field/page.mdx b/docs-ui/src/app/components/text-field/page.mdx new file mode 100644 index 0000000000..87d1087814 --- /dev/null +++ b/docs-ui/src/app/components/text-field/page.mdx @@ -0,0 +1,70 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { textFieldPropDefs } from './props-definition'; +import { + textFieldUsageSnippet, + withLabelSnippet, + sizesSnippet, + withDescriptionSnippet, +} from './snippets'; +import { WithLabel, Sizes, WithDescription } from './components'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { TextFieldDefinition } from '../../../utils/definitions'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { CodeBlock } from '@/components/CodeBlock'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; + +export const reactAriaUrls = { + textField: 'https://react-aria.adobe.com/TextField', +}; + + + +} + code={withLabelSnippet} +/> + +## Usage + + + +## API reference + + + + + +## Examples + +### Sizes + +} + code={sizesSnippet} + layout="side-by-side" +/> + +### With description + +} + code={withDescriptionSnippet} + layout="side-by-side" +/> + + + + diff --git a/docs-ui/src/app/components/text-field/props-definition.tsx b/docs-ui/src/app/components/text-field/props-definition.tsx new file mode 100644 index 0000000000..54589e1dab --- /dev/null +++ b/docs-ui/src/app/components/text-field/props-definition.tsx @@ -0,0 +1,85 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +export const textFieldPropDefs: Record = { + size: { + type: 'enum', + values: ['small', 'medium'], + default: 'small', + responsive: true, + description: ( + <> + Visual size of the input. Use small for dense layouts,{' '} + medium for prominent fields. + + ), + }, + label: { + type: 'string', + description: 'Visible label displayed above the input.', + }, + secondaryLabel: { + type: 'string', + description: ( + <> + Secondary text shown next to the label. If not provided and isRequired + is true, displays Required. + + ), + }, + description: { + type: 'string', + description: 'Help text displayed below the label.', + }, + icon: { + type: 'enum', + values: ['ReactNode'], + description: 'Icon rendered before the input.', + }, + type: { + type: 'enum', + values: ['text', 'email', 'tel', 'url'], + default: 'text', + description: + 'HTML input type. Use SearchField for search inputs and PasswordField for passwords.', + }, + placeholder: { + type: 'string', + description: 'Text displayed when the input is empty.', + }, + name: { + type: 'string', + description: 'Form field name for submission.', + }, + isRequired: { + type: 'boolean', + description: 'Whether the field is required for form submission.', + }, + isDisabled: { + type: 'boolean', + description: 'Whether the input is disabled.', + }, + isReadOnly: { + type: 'boolean', + description: 'Whether the input is read-only.', + }, + value: { + type: 'string', + description: 'Controlled value of the input.', + }, + defaultValue: { + type: 'string', + description: 'Default value for uncontrolled usage.', + }, + onChange: { + type: 'enum', + values: ['(value: string) => void'], + description: 'Handler called when the input value changes.', + }, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/text-field/snippets.ts b/docs-ui/src/app/components/text-field/snippets.ts new file mode 100644 index 0000000000..617884dcfe --- /dev/null +++ b/docs-ui/src/app/components/text-field/snippets.ts @@ -0,0 +1,31 @@ +export const textFieldUsageSnippet = `import { TextField } from '@backstage/ui'; + +`; + +export const withLabelSnippet = ``; + +export const sizesSnippet = ` + } + /> + } + /> +`; + +export const withDescriptionSnippet = ``; diff --git a/docs-ui/src/app/components/text/components.tsx b/docs-ui/src/app/components/text/components.tsx new file mode 100644 index 0000000000..8021a1dde6 --- /dev/null +++ b/docs-ui/src/app/components/text/components.tsx @@ -0,0 +1,66 @@ +'use client'; + +import { Text } from '../../../../../packages/ui/src/components/Text/Text'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; + +export const Default = () => { + return This is a text component; +}; + +export const Variants = () => { + return ( + + Title Large + Title Medium + Title Small + Title X-Small + Body Large + Body Medium + Body Small + Body X-Small + + ); +}; + +export const Colors = () => { + return ( + + Primary + Secondary + Danger + Warning + Success + Info + + ); +}; + +export const Weights = () => { + return ( + + Regular weight + Bold weight + + ); +}; + +export const AsElement = () => { + return ( + + Paragraph element + Span element + Div element + + ); +}; + +export const Truncate = () => { + return ( +
+ + This is a very long text that will be truncated when it exceeds the + container width + +
+ ); +}; diff --git a/docs-ui/src/app/components/text/page.mdx b/docs-ui/src/app/components/text/page.mdx new file mode 100644 index 0000000000..9a93f38160 --- /dev/null +++ b/docs-ui/src/app/components/text/page.mdx @@ -0,0 +1,56 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { textPropDefs } from './props-definition'; +import { + textUsageSnippet, + defaultSnippet, + variantsSnippet, + weightsSnippet, + colorsSnippet, + truncateSnippet, +} from './snippets'; +import { Default, Variants, Weights, Colors, Truncate } from './components'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { TextDefinition } from '../../../utils/definitions'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + + + +} code={defaultSnippet} /> + +## Usage + + + +## API reference + + + +## Examples + +### Variants + +} code={variantsSnippet} /> + +### Weights + +} code={weightsSnippet} /> + +### Colors + +Status colors for contextual messaging. + +} code={colorsSnippet} /> + +### Truncate + +} code={truncateSnippet} /> + + + + diff --git a/docs-ui/src/app/components/text/props-definition.tsx b/docs-ui/src/app/components/text/props-definition.tsx new file mode 100644 index 0000000000..8a25af2de7 --- /dev/null +++ b/docs-ui/src/app/components/text/props-definition.tsx @@ -0,0 +1,81 @@ +import { + childrenPropDefs, + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +export const textPropDefs: Record = { + variant: { + type: 'enum', + values: [ + 'title-large', + 'title-medium', + 'title-small', + 'title-x-small', + 'body-large', + 'body-medium', + 'body-small', + 'body-x-small', + ], + default: 'body-medium', + responsive: true, + description: + 'Typography style. Title variants for headings, body for paragraph text.', + }, + weight: { + type: 'enum', + values: ['regular', 'bold'], + default: 'regular', + responsive: true, + description: ( + <> + Font weight. Use bold for emphasis. + + ), + }, + color: { + type: 'enum', + values: ['primary', 'secondary', 'danger', 'warning', 'success'], + default: 'primary', + responsive: true, + description: + 'Text color. Status colors (danger, warning, success) for contextual messaging.', + }, + as: { + type: 'enum', + values: [ + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'p', + 'span', + 'label', + 'div', + 'strong', + 'em', + 'small', + 'legend', + ], + default: 'span', + description: + 'HTML element to render. Use heading tags for semantic structure.', + }, + truncate: { + type: 'boolean', + default: 'false', + description: ( + <> + Truncates text with ellipsis when it overflows its container. Requires{' '} + display: block to work. + + ), + }, + ...childrenPropDefs, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/text/snippets.ts b/docs-ui/src/app/components/text/snippets.ts new file mode 100644 index 0000000000..b8cc26bd6b --- /dev/null +++ b/docs-ui/src/app/components/text/snippets.ts @@ -0,0 +1,43 @@ +export const textUsageSnippet = `import { Text } from '@backstage/ui'; + +This is a text component`; + +export const defaultSnippet = `This is a text component`; + +export const variantsSnippet = ` + Title Large + Title Medium + Title Small + Title X-Small + Body Large + Body Medium + Body Small + Body X-Small +`; + +export const colorsSnippet = ` + Primary + Secondary + Danger + Warning + Success + Info +`; + +export const weightsSnippet = ` + Regular weight + Bold weight +`; + +export const asElementSnippet = ` + Paragraph element + Span element + Div element +`; + +export const truncateSnippet = `
+ + This is a very long text that will be truncated when it exceeds the + container width + +
`; diff --git a/docs-ui/src/app/components/toggle-button-group/components.tsx b/docs-ui/src/app/components/toggle-button-group/components.tsx new file mode 100644 index 0000000000..a6da51f862 --- /dev/null +++ b/docs-ui/src/app/components/toggle-button-group/components.tsx @@ -0,0 +1,154 @@ +'use client'; + +import { ToggleButtonGroup } from '../../../../../packages/ui/src/components/ToggleButtonGroup/ToggleButtonGroup'; +import { ToggleButton } from '../../../../../packages/ui/src/components/ToggleButton/ToggleButton'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; +import { Text } from '../../../../../packages/ui/src/components/Text/Text'; +import { + RiCloudLine, + RiStarFill, + RiStarLine, + RiArrowRightSLine, +} from '@remixicon/react'; + +export const ToggleButtonGroupSurfaces = () => ( + + + Default + + + Option 1 + Option 2 + Option 3 + + + + + On Surface 0 + + + Option 1 + Option 2 + Option 3 + + + + + On Surface 1 + + + Option 1 + Option 2 + Option 3 + + + + + On Surface 2 + + + Option 1 + Option 2 + Option 3 + + + + + On Surface 3 + + + Option 1 + Option 2 + Option 3 + + + + +); + +export const ToggleButtonGroupSingle = () => ( + + Dogs + Cats + Birds + +); + +export const ToggleButtonGroupMultiple = () => ( + + Frontend + Backend + Platform + +); + +export const ToggleButtonGroupVertical = () => ( + + Morning + Afternoon + Evening + +); + +export const ToggleButtonGroupDisabled = () => ( + + Cat + Dog + Bird + +); + +export const ToggleButtonGroupDisallowEmpty = () => ( + + One + Two + Three + +); + +export const ToggleButtonGroupIcons = () => ( + + } /> + } + /> + }> + Star + + }> + Next + + +); + +export const ToggleButtonGroupIconsOnly = () => ( + + } /> + } /> + } /> + +); diff --git a/docs-ui/src/app/components/toggle-button-group/page.mdx b/docs-ui/src/app/components/toggle-button-group/page.mdx new file mode 100644 index 0000000000..462ee8680b --- /dev/null +++ b/docs-ui/src/app/components/toggle-button-group/page.mdx @@ -0,0 +1,152 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; +import { toggleButtonGroupPropDefs } from './props-definition'; +import { + toggleButtonGroupHeroSnippet, + toggleButtonGroupUsageSnippet, + toggleButtonGroupSurfacesSnippet, + toggleButtonGroupSingleSnippet, + toggleButtonGroupMultipleSnippet, + toggleButtonGroupIconsSnippet, + toggleButtonGroupIconsOnlySnippet, + toggleButtonGroupDisallowEmptySnippet, + toggleButtonGroupVerticalSnippet, + toggleButtonGroupDisabledSnippet, +} from './snippets'; +import { + ToggleButtonGroupSingle, + ToggleButtonGroupMultiple, + ToggleButtonGroupVertical, + ToggleButtonGroupDisabled, + ToggleButtonGroupDisallowEmpty, + ToggleButtonGroupIcons, + ToggleButtonGroupIconsOnly, + ToggleButtonGroupSurfaces, +} from './components'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { ToggleButtonGroupDefinition } from '../../../utils/definitions'; + +export const reactAriaUrls = { + toggleButtonGroup: 'https://react-aria.adobe.com/ToggleButtonGroup', +}; + + + +} + code={toggleButtonGroupHeroSnippet} +/> + +## Usage + + + +## API reference + + + + + +## Examples + +### Surfaces + +} + code={toggleButtonGroupSurfacesSnippet} +/> + +### Single selection + +} + code={toggleButtonGroupSingleSnippet} + layout="side-by-side" +/> + +### Multiple selection + +} + code={toggleButtonGroupMultipleSnippet} + layout="side-by-side" +/> + +### Icons and text + +} + code={toggleButtonGroupIconsSnippet} + layout="side-by-side" +/> + +### Icons only + +} + code={toggleButtonGroupIconsOnlySnippet} + layout="side-by-side" +/> + +### Disallow empty selection + +} + code={toggleButtonGroupDisallowEmptySnippet} + layout="side-by-side" +/> + +### Vertical orientation + +} + code={toggleButtonGroupVerticalSnippet} + layout="side-by-side" +/> + +### Disabled + +} + code={toggleButtonGroupDisabledSnippet} + layout="side-by-side" +/> + + + + diff --git a/docs-ui/src/app/components/toggle-button-group/props-definition.ts b/docs-ui/src/app/components/toggle-button-group/props-definition.ts new file mode 100644 index 0000000000..8c8632709e --- /dev/null +++ b/docs-ui/src/app/components/toggle-button-group/props-definition.ts @@ -0,0 +1,51 @@ +import { + childrenPropDefs, + classNamePropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const toggleButtonGroupPropDefs: Record = { + selectionMode: { + type: 'enum', + values: ['single', 'multiple'], + description: 'Whether to allow single or multiple selection.', + }, + selectedKeys: { + type: 'enum', + values: ['Iterable'], + description: 'The currently selected keys (controlled).', + }, + defaultSelectedKeys: { + type: 'enum', + values: ['Iterable'], + description: 'The initial selected keys (uncontrolled).', + }, + onSelectionChange: { + type: 'enum', + values: ['(keys: Selection) => void'], + description: 'Handler called when the selection changes.', + }, + orientation: { + type: 'enum', + values: ['horizontal', 'vertical'], + default: 'horizontal', + description: 'The layout orientation of the button group.', + }, + disallowEmptySelection: { + type: 'boolean', + description: + 'Whether to prevent deselecting when at least one button is selected.', + }, + isDisabled: { + type: 'boolean', + default: 'false', + description: 'Disables all buttons in the group.', + }, + disabledKeys: { + type: 'enum', + values: ['Iterable'], + description: 'Keys of buttons that should be disabled.', + }, + ...childrenPropDefs, + ...classNamePropDefs, +}; diff --git a/docs-ui/src/app/components/toggle-button-group/snippets.ts b/docs-ui/src/app/components/toggle-button-group/snippets.ts new file mode 100644 index 0000000000..ce5d70784a --- /dev/null +++ b/docs-ui/src/app/components/toggle-button-group/snippets.ts @@ -0,0 +1,121 @@ +export const toggleButtonGroupHeroSnippet = ` + Dogs + Cats + Birds +`; + +export const toggleButtonGroupUsageSnippet = `import { ToggleButtonGroup, ToggleButton } from '@backstage/ui'; + + + Dogs + Cats + Birds +`; + +export const toggleButtonGroupSingleSnippet = ` + Dogs + Cats + Birds +`; + +export const toggleButtonGroupMultipleSnippet = ` + Frontend + Backend + Platform +`; + +export const toggleButtonGroupVerticalSnippet = ` + Morning + Afternoon + Evening +`; + +export const toggleButtonGroupDisabledSnippet = ` + Cat + Dog + Bird +`; + +export const toggleButtonGroupDisallowEmptySnippet = ` + One + Two + Three +`; + +export const toggleButtonGroupIconsSnippet = `import { RiCloudLine, RiStarFill, RiStarLine, RiArrowRightSLine } from '@remixicon/react'; + + + } /> + } + /> + }> + Star + + }> + Next + +`; + +export const toggleButtonGroupIconsOnlySnippet = `import { RiCloudLine, RiStarLine, RiArrowRightSLine } from '@remixicon/react'; + + + } /> + } /> + } /> +`; + +export const toggleButtonGroupSurfacesSnippet = ` + + Default + + + Option 1 + Option 2 + Option 3 + + + + + On Surface 0 + + + Option 1 + Option 2 + Option 3 + + + + + On Surface 1 + + + Option 1 + Option 2 + Option 3 + + + + + On Surface 2 + + + Option 1 + Option 2 + Option 3 + + + + + On Surface 3 + + + Option 1 + Option 2 + Option 3 + + + +`; diff --git a/docs-ui/src/app/components/toggle-button/components.tsx b/docs-ui/src/app/components/toggle-button/components.tsx new file mode 100644 index 0000000000..6fa5aada98 --- /dev/null +++ b/docs-ui/src/app/components/toggle-button/components.tsx @@ -0,0 +1,57 @@ +'use client'; + +import { ToggleButton } from '../../../../../packages/ui/src/components/ToggleButton/ToggleButton'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; +import { RiCheckLine } from '@remixicon/react'; +import { useState } from 'react'; + +export const Default = () => { + const [isSelected, setIsSelected] = useState(false); + return ( + + Toggle + + ); +}; + +export const WithIcon = () => { + const [isSelected, setIsSelected] = useState(false); + return ( + } + > + With Icon + + ); +}; + +export const Sizes = () => { + const [small, setSmall] = useState(false); + const [medium, setMedium] = useState(false); + + return ( + + + Small + + + Medium + + + ); +}; + +export const Disabled = () => { + return ( + + {}}> + Disabled Off + + {}}> + Disabled On + + + ); +}; diff --git a/docs-ui/src/app/components/toggle-button/page.mdx b/docs-ui/src/app/components/toggle-button/page.mdx new file mode 100644 index 0000000000..4b6aad90d1 --- /dev/null +++ b/docs-ui/src/app/components/toggle-button/page.mdx @@ -0,0 +1,68 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; +import { toggleButtonPropDefs } from './props-definition'; +import { + toggleButtonUsageSnippet, + defaultSnippet, + withIconSnippet, + sizesSnippet, + disabledSnippet, +} from './snippets'; +import { Default, WithIcon, Sizes, Disabled } from './components'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { ToggleButtonDefinition } from '../../../utils/definitions'; + +export const reactAriaUrls = { + toggleButton: 'https://react-aria.adobe.com/ToggleButton', +}; + + + +} code={defaultSnippet} /> + +## Usage + + + +## API reference + + + + + +## Examples + +### With icon + +} + code={withIconSnippet} +/> + +### Sizes + +} code={sizesSnippet} /> + +### Disabled + +} + code={disabledSnippet} +/> + + + + diff --git a/docs-ui/src/app/components/toggle-button/props-definition.ts b/docs-ui/src/app/components/toggle-button/props-definition.ts new file mode 100644 index 0000000000..7af4521bcb --- /dev/null +++ b/docs-ui/src/app/components/toggle-button/props-definition.ts @@ -0,0 +1,55 @@ +import { classNamePropDefs, type PropDef } from '@/utils/propDefs'; + +export const toggleButtonPropDefs: Record = { + isSelected: { + type: 'boolean', + description: + 'Whether the button is selected. Controls the visual pressed state.', + }, + defaultSelected: { + type: 'boolean', + description: 'The default selected state (uncontrolled).', + }, + onChange: { + type: 'enum', + values: ['(isSelected: boolean) => void'], + description: 'Handler called when the button is pressed.', + }, + size: { + type: 'enum', + values: ['small', 'medium'], + default: 'small', + responsive: true, + description: + 'Visual weight. Use small for compact layouts, medium for emphasis.', + }, + iconStart: { + type: 'enum', + values: ['ReactElement'], + description: + 'Icon displayed before the button text. Sized to match button.', + }, + iconEnd: { + type: 'enum', + values: ['ReactElement'], + description: 'Icon displayed after the button text. Sized to match button.', + }, + isDisabled: { + type: 'boolean', + default: 'false', + description: 'Prevents interaction and dims the button.', + }, + onSurface: { + type: 'enum', + values: ['Surface'], + responsive: true, + description: + 'Surface the button is placed on. Defaults to context surface.', + }, + children: { + type: 'enum', + values: ['ReactNode', '(renderProps) => ReactNode'], + description: 'Button label. Can be a function for dynamic content.', + }, + ...classNamePropDefs, +}; diff --git a/docs-ui/src/app/components/toggle-button/snippets.ts b/docs-ui/src/app/components/toggle-button/snippets.ts new file mode 100644 index 0000000000..d913e0702e --- /dev/null +++ b/docs-ui/src/app/components/toggle-button/snippets.ts @@ -0,0 +1,42 @@ +export const toggleButtonUsageSnippet = `import { ToggleButton } from '@backstage/ui'; +import { useState } from 'react'; + +const [isSelected, setIsSelected] = useState(false); + + + Toggle +`; + +export const defaultSnippet = `const [isSelected, setIsSelected] = useState(false); + + + Toggle +`; + +export const withIconSnippet = `const [isSelected, setIsSelected] = useState(false); + +} +> + With Icon +`; + +export const sizesSnippet = ` + + Small + + + Medium + +`; + +export const disabledSnippet = ` + {}}> + Disabled Off + + {}}> + Disabled On + +`; diff --git a/docs-ui/src/app/components/tooltip/components.tsx b/docs-ui/src/app/components/tooltip/components.tsx new file mode 100644 index 0000000000..67fc8c8d30 --- /dev/null +++ b/docs-ui/src/app/components/tooltip/components.tsx @@ -0,0 +1,40 @@ +'use client'; + +import { + TooltipTrigger, + Tooltip, +} from '../../../../../packages/ui/src/components/Tooltip/Tooltip'; +import { Button } from '../../../../../packages/ui/src/components/Button/Button'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; + +export const Default = () => { + return ( + + + Helpful information + + ); +}; + +export const Placement = () => { + return ( + + + + Top tooltip + + + + Right tooltip + + + + Bottom tooltip + + + + Left tooltip + + + ); +}; diff --git a/docs-ui/src/app/components/tooltip/page.mdx b/docs-ui/src/app/components/tooltip/page.mdx new file mode 100644 index 0000000000..feccc05225 --- /dev/null +++ b/docs-ui/src/app/components/tooltip/page.mdx @@ -0,0 +1,65 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; +import { tooltipTriggerPropDefs, tooltipPropDefs } from './props-definition'; +import { + tooltipUsageSnippet, + defaultSnippet, + placementSnippet, +} from './snippets'; +import { Default, Placement } from './components'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { TooltipDefinition } from '../../../utils/definitions'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + +export const reactAriaUrls = { + tooltip: 'https://react-aria.adobe.com/Tooltip', +}; + + + +} code={defaultSnippet} align="center" /> + +## Usage + + + +## API reference + +### TooltipTrigger + +Wraps the trigger element and tooltip content. + + + + + +### Tooltip + +The tooltip content that appears on hover or focus. + + + + + +## Examples + +### Placement + +The tooltip appears in the specified direction relative to the trigger. + +} + code={placementSnippet} + align="center" +/> + + + + diff --git a/docs-ui/src/app/components/tooltip/props-definition.tsx b/docs-ui/src/app/components/tooltip/props-definition.tsx new file mode 100644 index 0000000000..16dc9133dd --- /dev/null +++ b/docs-ui/src/app/components/tooltip/props-definition.tsx @@ -0,0 +1,79 @@ +import { + childrenPropDefs, + classNamePropDefs, + stylePropDefs, +} from '@/utils/propDefs'; +import type { PropDef } from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +export const tooltipTriggerPropDefs: Record = { + delay: { + type: 'number', + default: 600, + description: 'Milliseconds before tooltip appears on hover.', + }, + closeDelay: { + type: 'number', + default: 0, + description: 'Milliseconds before tooltip hides after leaving trigger.', + }, + trigger: { + type: 'enum', + values: ['focus'], + description: ( + <> + Set to focus for focus-only tooltips that do not appear on + hover. + + ), + }, + isOpen: { + type: 'boolean', + description: 'Controlled open state. Use with onOpenChange.', + }, + defaultOpen: { + type: 'boolean', + description: 'Initial open state for uncontrolled usage.', + }, + isDisabled: { + type: 'boolean', + description: 'Prevents the tooltip from appearing.', + }, + ...childrenPropDefs, +}; + +export const tooltipPropDefs: Record = { + placement: { + type: 'enum', + values: [ + 'top', + 'top start', + 'top end', + 'bottom', + 'bottom start', + 'bottom end', + 'left', + 'left top', + 'left bottom', + 'right', + 'right top', + 'right bottom', + ], + default: 'top', + description: + 'Position relative to the trigger element. Compound placements include alignment.', + }, + offset: { + type: 'number', + default: 4, + description: 'Distance in pixels from the trigger element.', + }, + containerPadding: { + type: 'number', + default: 12, + description: 'Padding from viewport edge when repositioning.', + }, + ...childrenPropDefs, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/tooltip/snippets.ts b/docs-ui/src/app/components/tooltip/snippets.ts new file mode 100644 index 0000000000..68b7d1b0c7 --- /dev/null +++ b/docs-ui/src/app/components/tooltip/snippets.ts @@ -0,0 +1,30 @@ +export const tooltipUsageSnippet = `import { TooltipTrigger, Tooltip, Button } from '@backstage/ui'; + + + + Helpful information +`; + +export const defaultSnippet = ` + + Helpful information +`; + +export const placementSnippet = ` + + + Top tooltip + + + + Right tooltip + + + + Bottom tooltip + + + + Left tooltip + +`; diff --git a/docs-ui/src/app/components/visually-hidden/components.tsx b/docs-ui/src/app/components/visually-hidden/components.tsx new file mode 100644 index 0000000000..8298465240 --- /dev/null +++ b/docs-ui/src/app/components/visually-hidden/components.tsx @@ -0,0 +1,39 @@ +'use client'; + +import { VisuallyHidden } from '../../../../../packages/ui/src/components/VisuallyHidden/VisuallyHidden'; +import { Text } from '../../../../../packages/ui/src/components/Text/Text'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; + +export const Default = () => { + return ( + + + This text is followed by a paragraph that is visually hidden but + accessible to screen readers. Try using a screen reader to hear it, or + inspect the DOM to see it's there. + + + This content is visually hidden but accessible to screen readers + + + ); +}; + +export const ExampleUsage = () => { + return ( + + + Footer links + + + About us + + + Jobs + + + Terms and Conditions + + + ); +}; diff --git a/docs-ui/src/content/visually-hidden.mdx b/docs-ui/src/app/components/visually-hidden/page.mdx similarity index 56% rename from docs-ui/src/content/visually-hidden.mdx rename to docs-ui/src/app/components/visually-hidden/page.mdx index 72ffc7a1d4..c72250231f 100644 --- a/docs-ui/src/content/visually-hidden.mdx +++ b/docs-ui/src/app/components/visually-hidden/page.mdx @@ -1,16 +1,16 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { VisuallyHiddenSnippet } from '@/snippets/stories-snippets'; +import { visuallyHiddenPropDefs } from './props-definition'; import { - visuallyHiddenPropDefs, visuallyHiddenUsageSnippet, - visuallyHiddenDefaultSnippet, - visuallyHiddenExampleUsageSnippet, -} from './visually-hidden.props'; + defaultSnippet, + exampleUsageSnippet, +} from './snippets'; +import { Default, ExampleUsage } from './components'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; -import { VisuallyHiddenDefinition } from '../utils/definitions'; +import { VisuallyHiddenDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; -} - code={visuallyHiddenDefaultSnippet} -/> +} code={defaultSnippet} /> ## Usage @@ -33,17 +28,17 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; +This component also accepts all standard HTML `div` attributes. + ## Examples -### Example Usage - -Here's an example of providing screen reader context for a list of links in a footer. +### Hidden headings } - code={visuallyHiddenExampleUsageSnippet} + preview={} + code={exampleUsageSnippet} open /> diff --git a/docs-ui/src/app/components/visually-hidden/props-definition.ts b/docs-ui/src/app/components/visually-hidden/props-definition.ts new file mode 100644 index 0000000000..140ed45fa4 --- /dev/null +++ b/docs-ui/src/app/components/visually-hidden/props-definition.ts @@ -0,0 +1,17 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const visuallyHiddenPropDefs: Record = { + children: { + type: 'enum', + values: ['ReactNode'], + responsive: false, + description: + 'Content to hide visually while remaining accessible to screen readers.', + }, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/content/visually-hidden.props.ts b/docs-ui/src/app/components/visually-hidden/snippets.ts similarity index 63% rename from docs-ui/src/content/visually-hidden.props.ts rename to docs-ui/src/app/components/visually-hidden/snippets.ts index 18a1c503d1..21ce6a04de 100644 --- a/docs-ui/src/content/visually-hidden.props.ts +++ b/docs-ui/src/app/components/visually-hidden/snippets.ts @@ -1,26 +1,10 @@ -import { - classNamePropDefs, - stylePropDefs, - type PropDef, -} from '@/utils/propDefs'; - -export const visuallyHiddenPropDefs: Record = { - children: { - type: 'enum', - values: ['ReactNode'], - responsive: false, - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - export const visuallyHiddenUsageSnippet = `import { VisuallyHidden } from '@backstage/ui'; This content is visually hidden but accessible to screen readers `; -export const visuallyHiddenDefaultSnippet = ` +export const defaultSnippet = ` This text is followed by a paragraph that is visually hidden but accessible to screen readers. Try using a screen reader to hear it, or @@ -31,7 +15,7 @@ export const visuallyHiddenDefaultSnippet = ` `; -export const visuallyHiddenExampleUsageSnippet = ` +export const exampleUsageSnippet = ` Footer links diff --git a/docs-ui/src/app/get-started/installation/page.mdx b/docs-ui/src/app/get-started/installation/page.mdx new file mode 100644 index 0000000000..d87c2bcba4 --- /dev/null +++ b/docs-ui/src/app/get-started/installation/page.mdx @@ -0,0 +1,77 @@ +import { CodeBlock } from '@/components/CodeBlock'; +import { Banner } from '@/components/Banner'; +import { + snippet, + analyticsSetupSnippet, + analyticsNoTrackSnippet, +} from './snippets'; + +# Installation + +## Import BUI's global styles + +Backstage UI works by importing a global CSS file at the root of your application. This file includes all the default styles for the components. +First, you'll need to install the package using a package manager. For example, if you're using Yarn: + + + +);`} +/> + + + +## Use BUI components + +As a plugin maintainer, you can use BUI components in your plugin. As mentioned above, you should not import the styles +again in your plugin as this will be handled at the root of your application. To get started, just add the library to +your plugin and import the components you need. + + + + + +## BUIProvider + +`BUIProvider` provides routing and analytics integration for all BUI components. It must be rendered inside a React Router context for client-side navigation to work in components like Link, ButtonLink, Tabs, Menu, TagGroup, and Table. + +### Setup + +If you're using the **new frontend system**, the provider is wired automatically via `@backstage/plugin-app` — no setup required. + +For the **old frontend system**, the `BUIProvider` is included in the app shell from `@backstage/core-app-api` and works out of the box. + +If you need to set up the provider manually (e.g. in a custom app shell), wrap your app content with the `BUIProvider` inside your Router and pass in Backstage's `useAnalytics` hook: + + + + + +### Analytics + +Once configured, BUI components with navigation behavior — Link, ButtonLink, Tab, MenuItem, Tag, and Row — fire a `click` event through Backstage's analytics system when a user navigates. Events include the link text as the subject and the destination URL in the attributes, along with any `AnalyticsContext` metadata (such as `pluginId`) from the component's position in the tree. + +To suppress tracking on an individual component, use the `noTrack` prop: + + diff --git a/docs-ui/src/app/get-started/installation/snippets.ts b/docs-ui/src/app/get-started/installation/snippets.ts new file mode 100644 index 0000000000..61edb266b3 --- /dev/null +++ b/docs-ui/src/app/get-started/installation/snippets.ts @@ -0,0 +1,22 @@ +export const snippet = `import { Flex, Button, Text } from '@backstage/ui'; + + + Hello World + +;`; + +export const analyticsSetupSnippet = `import { BUIProvider } from '@backstage/ui'; +import { useAnalytics } from '@backstage/core-plugin-api'; +import { BrowserRouter } from 'react-router-dom'; + +// BUIProvider must be inside a Router for client-side navigation + + + + +`; + +export const analyticsNoTrackSnippet = `// Suppress analytics for a specific link + + Skip tracking +`; diff --git a/docs-ui/src/app/hooks/page.mdx b/docs-ui/src/app/hooks/page.mdx new file mode 100644 index 0000000000..ea73f07ce3 --- /dev/null +++ b/docs-ui/src/app/hooks/page.mdx @@ -0,0 +1,7 @@ +import { HookGrid } from '@/components/HookGrid'; + +# Hooks + +Backstage UI custom hooks provide easy access to design system features, style management, and responsive interface creation. + + diff --git a/docs-ui/src/app/hooks/use-breakpoint/components.tsx b/docs-ui/src/app/hooks/use-breakpoint/components.tsx new file mode 100644 index 0000000000..c58010c66f --- /dev/null +++ b/docs-ui/src/app/hooks/use-breakpoint/components.tsx @@ -0,0 +1,17 @@ +'use client'; + +import { useBreakpoint } from '@backstage/ui'; + +export function UseBreakpointExample() { + const { breakpoint, up, down } = useBreakpoint(); + + return ( +
+

Current Breakpoint: {breakpoint}

+ {(up('md') &&

The viewport is larger than 1024px.

) || + (down('sm') &&

The viewport is smaller than 768px.

) || ( +

The viewport is between 768px and 1024px.

+ )} +
+ ); +} diff --git a/docs-ui/src/app/hooks/use-breakpoint/page.mdx b/docs-ui/src/app/hooks/use-breakpoint/page.mdx new file mode 100644 index 0000000000..f7f3378a41 --- /dev/null +++ b/docs-ui/src/app/hooks/use-breakpoint/page.mdx @@ -0,0 +1,47 @@ +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { PageTitle } from '@/components/PageTitle'; +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { Banner } from '@/components/Banner'; +import { useBreakpointReturnDefs } from './props-definition'; +import { useBreakpointExampleSnippet } from './snippets'; +import { UseBreakpointExample } from './components'; + + + +## Usage + + + +The `useBreakpoint` hook returns the active breakpoint and two functions, `up` and `down`, to check if the viewport is above, or below a given breakpoint, letting you adjust your UI responsively. + +} + code={useBreakpointExampleSnippet} +/> + +## Breakpoints + +The default breakpoints are: + +- **initial**: < 640px +- **xs**: ≥ 640px < 768px +- **sm**: ≥ 768px < 1024px +- **md**: ≥ 1024px < 1280px +- **lg**: ≥ 1280px < 1536px +- **xl**: ≥ 1536px + +## Return Value + + + + diff --git a/docs-ui/src/app/hooks/use-breakpoint/props-definition.ts b/docs-ui/src/app/hooks/use-breakpoint/props-definition.ts new file mode 100644 index 0000000000..b1702a82cc --- /dev/null +++ b/docs-ui/src/app/hooks/use-breakpoint/props-definition.ts @@ -0,0 +1,23 @@ +import { type PropDef } from '@/utils/propDefs'; + +const Breakpoint = ['"initial"', '"xs"', '"sm"', '"md"', '"lg"', '"xl"']; + +export const useBreakpointReturnDefs: Record = { + breakpoint: { + type: 'enum', + values: Breakpoint, + description: 'The current active breakpoint based on screen width', + }, + up: { + type: 'enum', + values: [`(breakpoint: ${Breakpoint.join(' | ')}) => boolean`], + description: + 'Function that takes a breakpoint and returns true if the screen width is at or above that breakpoint', + }, + down: { + type: 'enum', + values: [`(breakpoint: ${Breakpoint.join(' | ')}) => boolean`], + description: + 'Function that takes a breakpoint and returns true if the screen width is at or below that breakpoint', + }, +}; diff --git a/docs-ui/src/app/hooks/use-breakpoint/snippets.ts b/docs-ui/src/app/hooks/use-breakpoint/snippets.ts new file mode 100644 index 0000000000..19e6debe65 --- /dev/null +++ b/docs-ui/src/app/hooks/use-breakpoint/snippets.ts @@ -0,0 +1,13 @@ +export const useBreakpointExampleSnippet = `import { useBreakpoint } from '@backstage/ui'; + +function ResponsiveComponent() { + const { breakpoint, up, down } = useBreakpoint(); + + return ( +
+

Current Breakpoint: {breakpoint}

+ {up('md') &&

The viewport is medium or larger.

} + {down('sm') &&

The viewport is small or smaller.

} +
+ ); +}`; diff --git a/docs-ui/src/app/icon.svg b/docs-ui/src/app/icon.svg index b50d29e902..18824daaa7 100644 --- a/docs-ui/src/app/icon.svg +++ b/docs-ui/src/app/icon.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/docs-ui/src/app/layout.tsx b/docs-ui/src/app/layout.tsx index ad056626d8..015ee8346c 100644 --- a/docs-ui/src/app/layout.tsx +++ b/docs-ui/src/app/layout.tsx @@ -8,8 +8,8 @@ import { MobileBottomNav } from '@/components/MobileBottomNav'; import styles from './layout.module.css'; import '../css/globals.css'; -import '/public/theme-backstage.css'; -import '/public/theme-spotify.css'; +import '../css/theme-backstage.css'; +import '../css/theme-spotify.css'; export const metadata: Metadata = { title: 'Backstage UI', diff --git a/docs-ui/src/app/page.mdx b/docs-ui/src/app/page.mdx index 9a796b401b..712a5ac5c6 100644 --- a/docs-ui/src/app/page.mdx +++ b/docs-ui/src/app/page.mdx @@ -1,59 +1,78 @@ +import { ComponentGrid } from '@/components/ComponentGrid'; import { CodeBlock } from '@/components/CodeBlock'; -import { Banner } from '@/components/Banner'; -import { snippet } from './snippets'; +import { ColorFamily } from '@/components/ColorFamily'; +import { + surfacesSnippet, + adaptiveSnippet, + customCardSnippet, + customTokensSnippet, + colorPickerSnippet, +} from './snippets'; -# Welcome to Backstage UI +# Get Started with BUI Backstage UI is a design system created specifically for Backstage, built with React, TypeScript, and vanilla CSS. This open-source library is hosted in the Backstage monorepo. While it can be used in other projects, Backstage UI is designed to deliver a consistent, accessible, and extensible experience tailored to Backstage users. -## Import BUI's global styles +Backstage UI is installed by default on every instance of Backstage, so you can start using it right away. +If your setup doesn't include it yet, follow the [installation guide](/get-started/installation) to get started. -Backstage UI works by importing a global CSS file at the root of your application. This file includes all the default styles for the components. -First, you'll need to install the package using a package manager. For example, if you're using Yarn: +## Layout containers - +[`Box`](/components/box), [`Flex`](/components/flex), [`Grid`](/components/grid), and [`Card`](/components/card) are the foundation of every layout in Backstage UI. +Each one offers a set of utility props that map directly to our design tokens, so you can build consistent +layouts without writing any CSS. When nested, they also act as surfaces and automatically increment the +background depth so visual hierarchy is handled for you. );`} + title="Nested surfaces with automatic styling" + code={surfacesSnippet} /> - +## Adaptive components -## Use BUI components - -As a plugin maintainer, you can use BUI components in your plugin. As mentioned above, you should not import the styles -again in your plugin as this will be handled at the root of your application. To get started, just add the library to -your plugin and import the components you need. +Components like [`Card`](/components/card), [`Button`](/components/button), [`Text`](/components/text), and others are **adaptive components**. They +automatically adjust their colors, borders, and backgrounds to match the surface they live on. Drop a +[`Button`](/components/button) inside a [`Card`](/components/card) inside a [`Box`](/components/box) and each component styles itself appropriately +without any extra configuration. - +## The neutral scale background colors -## Support + -Now that you have the basics down, you can start building your plugin using the new design system. -Please familiarise yourself first with our theming principles. This will help you understand the core concepts of the design system. -If you have any questions, please reach out to us on [Discord](https://discord.gg/MUpMjP2). +## Creating custom components + +As much as possible we would like you to use components directly without creating custom components. If you need to create a custom component, you should use the components provided by Backstage UI. + + + +If you need to build custom components outside of BUI, you can use our [design tokens](/tokens) as CSS variables to ensure your styles stay consistent with the rest of the system. + + + +When building custom interactive components, we strongly recommend using [React Aria](https://react-spectrum.adobe.com/react-aria/) as your foundation. React Aria provides all the necessary accessibility features out of the box — keyboard navigation, focus management, ARIA attributes, and screen reader support — so you can focus on styling and logic without worrying about compliance. + + ## Philosophy diff --git a/docs-ui/src/app/snippets.ts b/docs-ui/src/app/snippets.ts index 7e63005172..4a93c31831 100644 --- a/docs-ui/src/app/snippets.ts +++ b/docs-ui/src/app/snippets.ts @@ -1,6 +1,39 @@ -export const snippet = `import { Flex, Button, Text } from '@backstage/ui'; +export const surfacesSnippet = ` + + + + + + +`; - +export const adaptiveSnippet = ` + {/* automatically set background to neutral-2 */} + + +`; + +export const customCardSnippet = ` Hello World - -;`; +`; + +export const customTokensSnippet = `
+
Hello World
+
`; + +export const colorPickerSnippet = `import { ColorPicker, ColorArea, ColorSlider, ColorField } from 'react-aria-components'; + +function MyColorPicker() { + return ( + + + + + + ); +}`; diff --git a/docs-ui/src/app/tokens/page.mdx b/docs-ui/src/app/tokens/page.mdx index 8e9031c81c..f359c10525 100644 --- a/docs-ui/src/app/tokens/page.mdx +++ b/docs-ui/src/app/tokens/page.mdx @@ -107,8 +107,6 @@ the value, you add an object with the value and the breakpoint prefix. ## Base colors -These colors are used for special purposes like ring, scrollbar, ... - @@ -133,62 +131,15 @@ These colors are used for special purposes like ring, scrollbar, ... Pure white color. This one should be the same in light and dark themes. - - - --bui-gray-1 - - You can use these mostly for backgrounds colors. - - - - --bui-gray-2 - - You can use these mostly for backgrounds colors. - - - - --bui-gray-3 - - You can use these mostly for backgrounds colors. - - - - --bui-gray-4 - - You can use these mostly for backgrounds colors. - - - - --bui-gray-5 - - You can use these mostly for backgrounds colors. - - - - --bui-gray-6 - - You can use these mostly for backgrounds colors. - - - - --bui-gray-7 - - You can use these mostly for backgrounds colors. - - - - --bui-gray-8 - - You can use these mostly for backgrounds colors. - -## Core background colors +## Neutral background colors -These colors are used for the background of your application. We are mostly using for now a -single elevated background for panels. `--bui-bg` should mostly use as the main background -color of your app. +These colors form a layered neutral scale for your application backgrounds. +`--bui-bg-app` is the base background color of your app. Each subsequent level +(1 through 4) represents an elevated layer on top of the previous one, with hover, +pressed, and disabled variants for interactive states. @@ -200,22 +151,129 @@ color of your app. - --bui-bg + --bui-bg-app + + + The base background color of your Backstage instance. - The background color of your Backstage instance. - --bui-bg-surface-1 + --bui-bg-neutral-1 + + + First elevated layer. Use for cards, dialogs, and panels. - Use for any panels or elevated surfaces. - --bui-bg-surface-2 + --bui-bg-neutral-1-hover - Use for any panels or elevated surfaces. + Hover state for elements on neutral-1. + + + --bui-bg-neutral-1-pressed + + Pressed state for elements on neutral-1. + + + + --bui-bg-neutral-1-disabled + + Disabled state for elements on neutral-1. + + + + --bui-bg-neutral-2 + + + Second elevated layer. Use for elements on top of neutral-1. + + + + + --bui-bg-neutral-2-hover + + Hover state for elements on neutral-2. + + + + --bui-bg-neutral-2-pressed + + Pressed state for elements on neutral-2. + + + + --bui-bg-neutral-2-disabled + + Disabled state for elements on neutral-2. + + + + --bui-bg-neutral-3 + + + Third elevated layer. Use for elements on top of neutral-2. + + + + + --bui-bg-neutral-3-hover + + Hover state for elements on neutral-3. + + + + --bui-bg-neutral-3-pressed + + Pressed state for elements on neutral-3. + + + + --bui-bg-neutral-3-disabled + + Disabled state for elements on neutral-3. + + + + --bui-bg-neutral-4 + + + Fourth elevated layer. Use for elements on top of neutral-3. + + + + + --bui-bg-neutral-4-hover + + Hover state for elements on neutral-4. + + + + --bui-bg-neutral-4-pressed + + Pressed state for elements on neutral-4. + + + + --bui-bg-neutral-4-disabled + + Disabled state for elements on neutral-4. + + + + +## Solid background colors + + + + + Prop + Description + + + --bui-bg-solid @@ -240,30 +298,19 @@ color of your app. Used for solid background colors when disabled. - - - --bui-bg-tint - - Used for tint background colors. - - - - --bui-bg-tint-hover - - Used for tint background colors when hovered. - - - - --bui-bg-tint-focus - - Used for tint background colors when active. - - - - --bui-bg-tint-disabled - - Used for tint background colors when disabled. - + + + +## Status background colors + + + + + Prop + Description + + + --bui-bg-danger @@ -282,6 +329,12 @@ color of your app. Used to show success information. + + + --bui-bg-info + + Used to show informational content. + @@ -315,22 +368,6 @@ are prefixed with `fg` to make it easier to identify. It should be used on top of main background surfaces. - - - --bui-fg-link - - - It should be used on top of main background surfaces. - - - - - --bui-fg-link-hover - - - It should be used on top of main background surfaces. - - --bui-fg-disabled @@ -347,33 +384,45 @@ are prefixed with `fg` to make it easier to identify. It should be used on top of solid background colors. - - - --bui-fg-tint - - - It should be used on top of tint background colors. - - - - - --bui-fg-tint-disabled - - - It should be used on top of tint background colors when disabled. - - --bui-fg-danger + Used for error states and destructive actions. + + + + --bui-fg-warning + + + Used for warning states and cautionary information. + + + + + --bui-fg-success + + Used for success states and positive feedback. + + + + --bui-fg-info + + + Used for informational content and neutral status. + + + + + --bui-fg-danger-on-bg + It should be used on top of danger background colors. - --bui-fg-warning + --bui-fg-warning-on-bg It should be used on top of warning background colors. @@ -381,12 +430,20 @@ are prefixed with `fg` to make it easier to identify. - --bui-fg-success + --bui-fg-success-on-bg It should be used on top of success background colors. + + + --bui-fg-info-on-bg + + + It should be used on top of info background colors. + + @@ -405,31 +462,15 @@ low contrast to help as a separator with the different background colors. - --bui-border + --bui-border-1 - It should be used on top of `--bui-bg-surface-1`. + Subtle border for low-contrast separators. - --bui-border-hover + --bui-border-2 - - Used when the component is interactive and hovered. - - - - - --bui-border-pressed - - - Used when the component is interactive and focused. - - - - - --bui-border-disabled - - Used when the component is disabled. + It should be used on top of `--bui-bg-neutral-1`. @@ -449,6 +490,12 @@ low contrast to help as a separator with the different background colors. It should be used on top of `--bui-bg-success`. + + + --bui-border-info + + It should be used on top of `--bui-bg-info`. + diff --git a/docs-ui/src/components/Banner/styles.module.css b/docs-ui/src/components/Banner/styles.module.css index 9666c5d521..d9d3323376 100644 --- a/docs-ui/src/components/Banner/styles.module.css +++ b/docs-ui/src/components/Banner/styles.module.css @@ -16,13 +16,13 @@ .info { background-color: #f2f2f2; border-color: #cdcdcd; - color: #888888; + color: #4b4b4b; } .warning { background-color: #fff2b9; - border-color: #ffd000; - color: #d79927; + border-color: #d79927; + color: #8a3d09; } .icon { diff --git a/docs-ui/src/components/Changelog/index.tsx b/docs-ui/src/components/Changelog/index.tsx index d7a547b901..c1f3a5660b 100644 --- a/docs-ui/src/components/Changelog/index.tsx +++ b/docs-ui/src/components/Changelog/index.tsx @@ -1,76 +1,21 @@ import { changelog } from '@/utils/changelog'; import { MDXRemote } from 'next-mdx-remote-client/rsc'; import { formattedMDXComponents } from '@/mdx-components'; +import { Badge, BreakingBadge, generateChangelogMarkdown } from './utils'; export function Changelog() { - // Group changelog entries by version - const groupedChangelog = changelog.reduce((acc, entry) => { - if (!acc[entry.version]) { - acc[entry.version] = []; - } - acc[entry.version].push(entry); - return acc; - }, {} as Record); + const content = generateChangelogMarkdown(changelog, { + showComponentBadges: true, + }); - // Sort versions in descending order - const sortedVersions = Object.keys(groupedChangelog).sort((a, b) => - b.localeCompare(a), + return ( + ); - - const content = sortedVersions - .map(version => { - const entries = groupedChangelog[version]; - - // Group entries by bump type - const groupedByBump = entries.reduce((acc, entry) => { - const bumpType = entry.type || 'other'; - if (!acc[bumpType]) { - acc[bumpType] = []; - } - acc[bumpType].push(entry); - return acc; - }, {} as Record); - - // Define the order of bump types - const bumpOrder = ['breaking', 'new', 'fix', 'other']; - - const bumpSections = bumpOrder - .filter(bumpType => groupedByBump[bumpType]?.length > 0) - .map(bumpType => { - const bumpEntries = groupedByBump[bumpType]; - let sectionTitle = 'Other Changes'; - if (bumpType === 'breaking') { - sectionTitle = 'Breaking Changes'; - } else if (bumpType === 'new') { - sectionTitle = 'New Features'; - } else if (bumpType === 'fix') { - sectionTitle = 'Bug Fixes'; - } - - return `### ${sectionTitle} - - ${bumpEntries - .map(e => { - const prs = - e.prs.length > 0 - ? e.prs - .map( - pr => - `[#${pr}](https://github.com/backstage/backstage/pull/${pr})`, - ) - .join(', ') - : ''; - return `- ${e.description}${prs ? ` ${prs}` : ''}`; - }) - .join('\n')}`; - }) - .join('\n\n'); - - return `## Version ${version} - - ${bumpSections}`; - }) - .join('\n'); - - return ; } diff --git a/docs-ui/src/components/Changelog/utils.tsx b/docs-ui/src/components/Changelog/utils.tsx new file mode 100644 index 0000000000..278072f37f --- /dev/null +++ b/docs-ui/src/components/Changelog/utils.tsx @@ -0,0 +1,212 @@ +import type { ChangelogProps, Component } from '@/utils/types'; + +// Badge Components +export const Badge = ({ + children, + variant = 'gray', +}: { + children: React.ReactNode; + variant?: 'red' | 'gray'; +}) => { + const colors = { + red: { + backgroundColor: 'var(--badge-red-bg)', + color: 'var(--badge-red-color)', + }, + gray: { + backgroundColor: 'var(--badge-gray-bg)', + color: 'var(--badge-gray-color)', + }, + }; + + return ( + + {children} + + ); +}; + +export const BreakingBadge = () => Breaking; + +// Utility Functions +export const toTitleCase = (kebabCase: string): string => { + // Hook slugs (e.g. "use-table") display as camelCase (e.g. "useTable") + if (/^use-[A-Za-z]/.test(kebabCase)) { + const [first, ...rest] = kebabCase.split('-'); + return ( + first + + rest + .map(word => word.charAt(0).toLocaleUpperCase('en-US') + word.slice(1)) + .join('') + ); + } + return kebabCase + .split('-') + .map(word => word.charAt(0).toLocaleUpperCase('en-US') + word.slice(1)) + .join(''); +}; + +export const groupByVersion = ( + entries: ChangelogProps[], +): Record => { + return entries.reduce((acc, entry) => { + if (!acc[entry.version]) { + acc[entry.version] = []; + } + acc[entry.version].push(entry); + return acc; + }, {} as Record); +}; + +export const sortVersions = (versions: string[]): string[] => { + return versions.sort((a, b) => { + const aParts = a.split('.').map(Number); + const bParts = b.split('.').map(Number); + + for (let i = 0; i < 3; i++) { + if (bParts[i] !== aParts[i]) { + return bParts[i] - aParts[i]; // Descending order + } + } + return 0; + }); +}; + +export const formatPRLinks = (prs: string[]): string => { + if (prs.length === 0) return ''; + + return prs + .map(pr => `[#${pr}](https://github.com/backstage/backstage/pull/${pr})`) + .join(', '); +}; + +export const generateEntryMarkdown = ( + entry: ChangelogProps, + options: { + showComponentBadges?: boolean; + componentBadgeFilter?: Component[]; + } = {}, +): string => { + const { showComponentBadges = true, componentBadgeFilter } = options; + const prs = formatPRLinks(entry.prs); + + // Prepend component names as badges if available and requested + const entryComponents = entry.components ?? []; + const badgeComponents = componentBadgeFilter + ? entryComponents.filter(c => componentBadgeFilter.includes(c)) + : entryComponents; + const componentBadges = + showComponentBadges && badgeComponents.length > 0 + ? badgeComponents + .map(c => `${toTitleCase(c)}`) + .join(' ') + ' ' + : ''; + + // Add breaking badge if this is a breaking change + const breakingBadge = entry.breaking ? ' ' : ''; + + // Remove **BREAKING**: text from description since we show it as a badge + const description = entry.description.replace(/\*\*BREAKING\*\*:?\s*/, ''); + + // Build the entry + let entryMarkdown = `- ${componentBadges}${breakingBadge}${description}`; + if (prs) { + entryMarkdown += ` ${prs}`; + } + + // Add migration if present (should already be in description, but check) + if (entry.migration && !entry.description.includes('**Migration:**')) { + entryMarkdown += `\n\n Migration Guide:\n\n ${entry.migration + .split('\n') + .join('\n ')}`; + } + + return entryMarkdown; +}; + +export interface GenerateChangelogOptions { + showComponentBadges?: boolean; + componentBadgeFilter?: Component[]; + headingLevel?: number; +} + +export const generateChangelogMarkdown = ( + entries: ChangelogProps[], + options: GenerateChangelogOptions = {}, +): string => { + const { + showComponentBadges = true, + componentBadgeFilter, + headingLevel = 2, + } = options; + + // Group changelog entries by version + const groupedChangelog = groupByVersion(entries); + + // Sort versions in descending order (semantic versioning) + const sortedVersions = sortVersions(Object.keys(groupedChangelog)); + + // Generate heading prefix based on level (e.g., "##" for level 2, "###" for level 3) + const versionHeading = '#'.repeat(headingLevel); + const sectionHeading = '#'.repeat(headingLevel + 1); + + const content = sortedVersions + .map(version => { + const versionEntries = groupedChangelog[version]; + + // Group entries: Breaking vs Everything Else + const breakingChanges = versionEntries.filter(e => e.breaking); + const otherChanges = versionEntries.filter(e => !e.breaking); + + const sections = []; + + // Breaking changes section + if (breakingChanges.length > 0) { + sections.push({ + title: 'Breaking Changes', + entries: breakingChanges, + }); + } + + // All other changes section + if (otherChanges.length > 0) { + sections.push({ + title: 'Changes', + entries: otherChanges, + }); + } + + const bumpSections = sections + .map(({ title, entries: sectionEntries }) => { + const entriesMarkdown = sectionEntries + .map(e => + generateEntryMarkdown(e, { + showComponentBadges, + componentBadgeFilter, + }), + ) + .join('\n\n'); + + return `${sectionHeading} ${title}\n\n${entriesMarkdown}`; + }) + .join('\n\n'); + + return `${versionHeading} Version ${version} + + ${bumpSections}`; + }) + .join('\n'); + + return content; +}; diff --git a/docs-ui/src/components/ChangelogComponent/index.tsx b/docs-ui/src/components/ChangelogComponent/index.tsx index ff86433268..013764dbca 100644 --- a/docs-ui/src/components/ChangelogComponent/index.tsx +++ b/docs-ui/src/components/ChangelogComponent/index.tsx @@ -1,35 +1,45 @@ import { changelog } from '@/utils/changelog'; import { MDXRemote } from 'next-mdx-remote-client/rsc'; import { formattedMDXComponents } from '@/mdx-components'; -import type { Component } from '@/utils/changelog'; +import type { AtLeastOne, Component, Hook } from '@/utils/changelog'; +import { + Badge, + BreakingBadge, + generateChangelogMarkdown, +} from '../Changelog/utils'; -export const ChangelogComponent = ({ component }: { component: Component }) => { - const componentChangelog = changelog.filter(c => - c.components.includes(component), +type ChangelogComponentProps = AtLeastOne<{ + component: Component | Component[]; + hook: Hook; +}>; + +export const ChangelogComponent = ({ + component, + hook, +}: Readonly) => { + const components = Array.isArray(component) ? component : [component]; + const componentChangelog = changelog.filter( + c => + c.components?.some(cc => components.includes(cc)) || + c.hooks?.includes(hook), ); + const content = `## Changelog + +${generateChangelogMarkdown(componentChangelog, { + showComponentBadges: components.length > 1, + componentBadgeFilter: components.length > 1 ? components : undefined, + headingLevel: 3, +})}`; + return ( { - const prs = - change.prs.length > 0 - ? change.prs - .map( - pr => - `[#${pr}](https://github.com/backstage/backstage/pull/${pr})`, - ) - .join(', ') - : ''; - return `- \`${change.version}\` - ${change.description}${ - prs ? ` ${prs}` : '' - }`; - }) - .join('\n')}`} + components={{ + ...formattedMDXComponents, + Badge, + BreakingBadge, + }} + source={content} /> ); }; diff --git a/docs-ui/src/components/Chip/styles.module.css b/docs-ui/src/components/Chip/styles.module.css index 01ee29311d..d752ae2602 100644 --- a/docs-ui/src/components/Chip/styles.module.css +++ b/docs-ui/src/components/Chip/styles.module.css @@ -1,11 +1,9 @@ .chip { - display: inline-flex; - align-items: center; + display: inline-block; font-family: monospace; font-size: 13px; border-radius: 6px; - padding: 0px 8px; - height: 24px; + padding: 4px 8px; margin-right: 4px; background-color: #f0f0f0; color: #5d5d5d; diff --git a/docs-ui/src/components/ColorFamily/ColorFamily.module.css b/docs-ui/src/components/ColorFamily/ColorFamily.module.css new file mode 100644 index 0000000000..f38e481383 --- /dev/null +++ b/docs-ui/src/components/ColorFamily/ColorFamily.module.css @@ -0,0 +1,94 @@ +.wrapper { + display: flex; + flex-direction: column; + gap: 2rem; + margin-top: 1rem; + margin-bottom: 2rem; +} + +@media (min-width: 768px) { + .wrapper { + flex-direction: row; + align-items: flex-start; + } +} + +.visual { + flex: 1; + min-width: 0; +} + +.text { + flex: 1; + min-width: 0; +} + +.base { + border-radius: 12px; + padding: 16px; +} + +.baseLabel { + display: block; + font-size: 13px; + color: var(--bui-fg-primary); + margin-bottom: 12px; +} + +.level { + border-radius: 10px; + padding: 12px; +} + +.levelHeader { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 12px; +} + +.level > .level > .levelHeader:last-child, +.level:not(:has(> .level)) > .levelHeader { + margin-bottom: 0; +} + +.levelLabel { + font-size: 13px; + color: var(--bui-fg-primary); + white-space: nowrap; +} + +.chips { + display: flex; + gap: 6px; +} + +.chip { + padding: 6px 10px; + border-radius: 6px; + font-size: 11px; + font-weight: 500; + color: var(--bui-fg-primary); + white-space: nowrap; +} + +.description { + color: var(--primary); + margin-top: 0; + margin-bottom: 1rem; + font-size: 1rem; + line-height: 1.5; +} + +.description:last-child { + margin-bottom: 0; +} + +.link { + color: var(--link); + text-decoration: none; + + &:hover { + text-decoration: underline; + } +} diff --git a/docs-ui/src/components/ColorFamily/ColorFamily.tsx b/docs-ui/src/components/ColorFamily/ColorFamily.tsx new file mode 100644 index 0000000000..07b8779503 --- /dev/null +++ b/docs-ui/src/components/ColorFamily/ColorFamily.tsx @@ -0,0 +1,97 @@ +'use client'; + +import styles from './ColorFamily.module.css'; + +const StateChip = ({ + level, + state, + label, +}: { + level: number; + state: string; + label: string; +}) => ( +
+ {label} +
+); + +const NeutralLevel = ({ + level, + children, + height, +}: { + level: number; + children?: React.ReactNode; + height?: number; +}) => ( +
+
+ Neutral {level} +
+ + + +
+
+ {children} +
+); + +export const ColorFamily = () => { + return ( +
+
+
+ Neutral 0 + + + + + + + +
+
+
+

+ BUI uses a layered neutral scale from 0 to 4. Each level nests inside + the previous one, automatically incrementing the background depth. + This creates clear visual hierarchy without manually picking colors. +

+

+ Neutral 0 is the application background and should only be used once, + at the root of your app. All other surfaces build on top of it. +

+

+ Each level can be interactive or{' '} + non-interactive. A Card, for example, can be flat + (just a surface) or fully clickable with hover and pressed states. The + three interaction states (hover, pressed, disabled) are built into + every neutral level. +

+

+ Explore the full list of color tokens on the{' '} + + tokens page + + . +

+
+
+ ); +}; diff --git a/docs-ui/src/components/ColorFamily/index.ts b/docs-ui/src/components/ColorFamily/index.ts new file mode 100644 index 0000000000..12f7fb4008 --- /dev/null +++ b/docs-ui/src/components/ColorFamily/index.ts @@ -0,0 +1 @@ +export { ColorFamily } from './ColorFamily'; diff --git a/docs-ui/src/components/CommandPalette/CommandPalette.module.css b/docs-ui/src/components/CommandPalette/CommandPalette.module.css new file mode 100644 index 0000000000..dbbdb72ceb --- /dev/null +++ b/docs-ui/src/components/CommandPalette/CommandPalette.module.css @@ -0,0 +1,154 @@ +.overlay { + position: fixed; + inset: 0; + z-index: 200; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + align-items: flex-start; + justify-content: center; + padding-top: 15vh; + + &[data-entering] { + animation: overlayFadeIn 200ms ease-out; + } + + &[data-exiting] { + animation: overlayFadeOut 150ms ease-in; + } +} + +@keyframes overlayFadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes overlayFadeOut { + from { + opacity: 1; + } + to { + opacity: 0; + } +} + +.modal { + width: min(90vw, 500px); + max-height: min(70vh, 400px); + background-color: var(--bg); + border: 1px solid var(--border); + border-radius: 12px; + box-shadow: 0 16px 48px rgba(0, 0, 0, 0.2); + display: flex; + flex-direction: column; + outline: none; + overflow: hidden; + + &[data-entering] { + animation: modalScaleIn 200ms ease-out; + } + + &[data-exiting] { + animation: modalScaleOut 150ms ease-in; + } +} + +@keyframes modalScaleIn { + from { + opacity: 0; + transform: scale(0.95) translateY(-8px); + } + to { + opacity: 1; + transform: scale(1) translateY(0); + } +} + +@keyframes modalScaleOut { + from { + opacity: 1; + transform: scale(1) translateY(0); + } + to { + opacity: 0; + transform: scale(0.95) translateY(-8px); + } +} + +.dialog { + display: flex; + flex-direction: column; + height: 100%; + max-height: min(70vh, 400px); + outline: none; + overflow: hidden; +} + +.searchField { + display: flex; + align-items: center; + padding: 12px 16px; + border-bottom: 1px solid var(--border); + flex-shrink: 0; +} + +.srOnly { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} + +.input { + width: 100%; + background: transparent; + border: none; + outline: none; + font-size: 1rem; + color: var(--primary); + font-family: inherit; + + &::placeholder { + color: var(--secondary); + } +} + +.menu { + flex: 1; + overflow-y: auto; + padding: 8px; + outline: none; + border: none; +} + +.menuItem { + padding: 8px 12px; + border-radius: 8px; + color: var(--primary); + cursor: pointer; + font-size: 0.875rem; + outline: none; + + &[data-focused] { + background-color: var(--action); + } + + &[data-pressed] { + background-color: var(--action); + } +} + +.empty { + padding: 24px 12px; + text-align: center; + color: var(--secondary); + font-size: 0.875rem; +} diff --git a/docs-ui/src/components/CommandPalette/CommandPalette.tsx b/docs-ui/src/components/CommandPalette/CommandPalette.tsx new file mode 100644 index 0000000000..bcc534372a --- /dev/null +++ b/docs-ui/src/components/CommandPalette/CommandPalette.tsx @@ -0,0 +1,76 @@ +'use client'; + +import { + Autocomplete, + Dialog, + Input, + Label, + Menu, + MenuItem, + Modal, + ModalOverlay, + SearchField, + useFilter, +} from 'react-aria-components'; +import { useRouter } from 'next/navigation'; +import { components } from '@/utils/data'; +import styles from './CommandPalette.module.css'; + +interface CommandPaletteProps { + isOpen: boolean; + onOpenChange: (isOpen: boolean) => void; +} + +const items = components.map(c => ({ id: c.slug, ...c })); + +export const CommandPalette = ({ + isOpen, + onOpenChange, +}: CommandPaletteProps) => { + const router = useRouter(); + const { contains } = useFilter({ sensitivity: 'base' }); + + return ( + + + + + + + + + { + router.push(`/components/${key}`); + onOpenChange(false); + }} + renderEmptyState={() => ( +
No components found.
+ )} + > + {item => ( + + {item.title} + + )} +
+
+
+
+
+ ); +}; diff --git a/docs-ui/src/components/CommandPalette/index.ts b/docs-ui/src/components/CommandPalette/index.ts new file mode 100644 index 0000000000..95c1305982 --- /dev/null +++ b/docs-ui/src/components/CommandPalette/index.ts @@ -0,0 +1 @@ +export { CommandPalette } from './CommandPalette'; diff --git a/docs-ui/src/components/ComponentCards/ComponentCards.module.css b/docs-ui/src/components/ComponentCards/ComponentCards.module.css deleted file mode 100644 index 8eb9dbd3d0..0000000000 --- a/docs-ui/src/components/ComponentCards/ComponentCards.module.css +++ /dev/null @@ -1,54 +0,0 @@ -.grid { - display: grid; - grid-template-columns: repeat(1, 1fr); - gap: 1rem; - margin-top: 1rem; - margin-bottom: 3rem; -} - -@media (min-width: 768px) { - .grid { - grid-template-columns: repeat(2, 1fr); - } -} - -@media (min-width: 1024px) { - .grid { - grid-template-columns: repeat(3, 1fr); - } -} - -@media (min-width: 1280px) { - .grid { - grid-template-columns: repeat(4, 1fr); - } -} - -.card { - display: flex; - flex-direction: column; - justify-content: flex-end; - background-color: var(--bg); - border-radius: 8px; - border: 1px solid var(--border); - padding: 16px; - gap: 4px; - min-height: 120px; - - &:hover { - background-color: var(--bg-hover); - } -} - -.title { - margin: 0; - font-size: 16px; - font-weight: 600; - color: var(--primary); -} - -.description { - margin: 0; - font-size: 14px; - color: var(--secondary); -} diff --git a/docs-ui/src/components/ComponentCards/ComponentCards.tsx b/docs-ui/src/components/ComponentCards/ComponentCards.tsx deleted file mode 100644 index 0e4d22c02a..0000000000 --- a/docs-ui/src/components/ComponentCards/ComponentCards.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import Link from 'next/link'; -import styles from './ComponentCards.module.css'; - -export const ComponentCards = ({ children }: { children: React.ReactNode }) => { - return
{children}
; -}; - -export const ComponentCard = ({ - title, - description, - href, -}: { - title: string; - description: string; - href: string; -}) => { - return ( - -

{title}

-

{description}

- - ); -}; diff --git a/docs-ui/src/components/ComponentCards/index.ts b/docs-ui/src/components/ComponentCards/index.ts deleted file mode 100644 index 4bc4beaae3..0000000000 --- a/docs-ui/src/components/ComponentCards/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ComponentCards'; diff --git a/docs-ui/src/components/ComponentGrid/ComponentGrid.module.css b/docs-ui/src/components/ComponentGrid/ComponentGrid.module.css new file mode 100644 index 0000000000..a6dd54a864 --- /dev/null +++ b/docs-ui/src/components/ComponentGrid/ComponentGrid.module.css @@ -0,0 +1,29 @@ +.grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + margin-top: 1rem; + margin-bottom: 2rem; +} + +@media (min-width: 768px) { + .grid { + grid-template-columns: repeat(4, 1fr); + } +} + +.item { + display: flex; + align-items: center; + gap: 6px; + padding: 10px 0; + font-size: 14px; + font-weight: 400; + color: var(--primary); + text-decoration: none; + + &:hover { + color: var(--link); + text-decoration: underline; + text-underline-offset: 2px; + } +} diff --git a/docs-ui/src/components/ComponentGrid/ComponentGrid.tsx b/docs-ui/src/components/ComponentGrid/ComponentGrid.tsx new file mode 100644 index 0000000000..eb0f043420 --- /dev/null +++ b/docs-ui/src/components/ComponentGrid/ComponentGrid.tsx @@ -0,0 +1,21 @@ +'use client'; + +import Link from 'next/link'; +import { components } from '@/utils/data'; +import styles from './ComponentGrid.module.css'; + +export const ComponentGrid = () => { + return ( +
+ {components.map(item => ( + + {item.title} + + ))} +
+ ); +}; diff --git a/docs-ui/src/components/ComponentGrid/index.ts b/docs-ui/src/components/ComponentGrid/index.ts new file mode 100644 index 0000000000..f0fe06477a --- /dev/null +++ b/docs-ui/src/components/ComponentGrid/index.ts @@ -0,0 +1 @@ +export * from './ComponentGrid'; diff --git a/docs-ui/src/components/CustomTheme/customTheme.tsx b/docs-ui/src/components/CustomTheme/customTheme.tsx index ae765d7d88..539113140a 100644 --- a/docs-ui/src/components/CustomTheme/customTheme.tsx +++ b/docs-ui/src/components/CustomTheme/customTheme.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useEffect, useState, useCallback } from 'react'; +import { useEffect, useState, useCallback, useSyncExternalStore } from 'react'; import CodeMirror from '@uiw/react-codemirror'; import { sass } from '@codemirror/lang-sass'; import styles from './styles.module.css'; @@ -14,6 +14,10 @@ const defaultTheme = `:root { --bui-bg-solid: #000; }`; +// Stable server snapshots for useSyncExternalStore +const serverIsClient = false; +const serverDefaultTheme = defaultTheme; + const myTheme = createTheme({ theme: 'light', settings: { @@ -46,12 +50,41 @@ const myTheme = createTheme({ }); export const CustomTheme = () => { - const [isClient, setIsClient] = useState(false); const [open, setOpen] = useState(true); - const [customTheme, setCustomTheme] = useState(undefined); const { selectedThemeName } = usePlayground(); const [savedMessage, setSavedMessage] = useState('Save'); + // SSR-safe client detection + const isClient = useSyncExternalStore( + () => () => {}, + () => true, + () => serverIsClient, + ); + + // SSR-safe localStorage access for custom theme + const customThemeFromStorage = useSyncExternalStore( + callback => { + window.addEventListener('storage', callback); + return () => window.removeEventListener('storage', callback); + }, + () => { + const stored = localStorage.getItem('customThemeCss'); + if (!stored) { + localStorage.setItem('customThemeCss', defaultTheme); + return defaultTheme; + } + return stored; + }, + () => serverDefaultTheme, + ); + + const [customTheme, setCustomTheme] = useState(customThemeFromStorage); + + // Sync from storage when it changes + useEffect(() => { + setCustomTheme(customThemeFromStorage); + }, [customThemeFromStorage]); + const updateStyleElement = (theme: string) => { let styleElement = document.getElementById( 'custom-theme-style', @@ -66,16 +99,11 @@ export const CustomTheme = () => { styleElement.textContent = theme; }; + // Apply custom theme to DOM useEffect(() => { - if (selectedThemeName === 'custom') { - let storedTheme = localStorage.getItem('customThemeCss'); - if (!storedTheme) { - storedTheme = defaultTheme; - localStorage.setItem('customThemeCss', storedTheme); - } - setCustomTheme(storedTheme); - updateStyleElement(storedTheme); - } else { + if (selectedThemeName === 'custom' && customTheme && isClient) { + updateStyleElement(customTheme); + } else if (isClient) { const styleElement = document.getElementById( 'custom-theme-style', ) as HTMLStyleElement; @@ -83,11 +111,7 @@ export const CustomTheme = () => { styleElement.remove(); } } - }, [selectedThemeName]); - - useEffect(() => { - setIsClient(true); - }, []); + }, [selectedThemeName, customTheme, isClient]); const handleSave = () => { if (customTheme) { diff --git a/docs-ui/src/components/DecorativeBox/index.tsx b/docs-ui/src/components/DecorativeBox/index.tsx index a5c20725bc..8af067459e 100644 --- a/docs-ui/src/components/DecorativeBox/index.tsx +++ b/docs-ui/src/components/DecorativeBox/index.tsx @@ -1,5 +1,17 @@ +'use client'; + +import { CSSProperties, ReactNode } from 'react'; import styles from './styles.module.css'; -export const DecorativeBox = () => { - return
; +interface DecorativeBoxProps { + children?: ReactNode; + style?: CSSProperties; +} + +export const DecorativeBox = ({ children, style }: DecorativeBoxProps) => { + return ( +
+ {children} +
+ ); }; diff --git a/docs-ui/src/components/DecorativeBox/styles.module.css b/docs-ui/src/components/DecorativeBox/styles.module.css index 84a6b579a0..a2a5c9407a 100644 --- a/docs-ui/src/components/DecorativeBox/styles.module.css +++ b/docs-ui/src/components/DecorativeBox/styles.module.css @@ -1,8 +1,14 @@ .box { min-width: 64px; min-height: 64px; + padding: 8px 12px; background-color: #eaf2fd; border-radius: 4px; box-shadow: 0 0 0 1px #2563eb; background-image: url("data:image/svg+xml,%3Csvg width='6' height='6' viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%232563eb' fill-opacity='0.3' fill-rule='evenodd'%3E%3Cpath d='M5 0h1L0 6V5zM6 5v1H5z'/%3E%3C/g%3E%3C/svg%3E"); + color: #2563eb; + font-weight: 500; + display: flex; + align-items: center; + justify-content: center; } diff --git a/docs-ui/src/components/HookGrid/HookGrid.module.css b/docs-ui/src/components/HookGrid/HookGrid.module.css new file mode 100644 index 0000000000..a6dd54a864 --- /dev/null +++ b/docs-ui/src/components/HookGrid/HookGrid.module.css @@ -0,0 +1,29 @@ +.grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + margin-top: 1rem; + margin-bottom: 2rem; +} + +@media (min-width: 768px) { + .grid { + grid-template-columns: repeat(4, 1fr); + } +} + +.item { + display: flex; + align-items: center; + gap: 6px; + padding: 10px 0; + font-size: 14px; + font-weight: 400; + color: var(--primary); + text-decoration: none; + + &:hover { + color: var(--link); + text-decoration: underline; + text-underline-offset: 2px; + } +} diff --git a/docs-ui/src/components/HookGrid/HookGrid.tsx b/docs-ui/src/components/HookGrid/HookGrid.tsx new file mode 100644 index 0000000000..871ee885d2 --- /dev/null +++ b/docs-ui/src/components/HookGrid/HookGrid.tsx @@ -0,0 +1,21 @@ +'use client'; + +import Link from 'next/link'; +import { hooks } from '@/utils/data'; +import styles from './HookGrid.module.css'; + +export const HookGrid = () => { + return ( +
+ {hooks.map(item => ( + + {item.title} + + ))} +
+ ); +}; diff --git a/docs-ui/src/components/HookGrid/index.ts b/docs-ui/src/components/HookGrid/index.ts new file mode 100644 index 0000000000..934f73aacf --- /dev/null +++ b/docs-ui/src/components/HookGrid/index.ts @@ -0,0 +1 @@ +export * from './HookGrid'; diff --git a/docs-ui/src/components/LayoutComponents/LayoutComponents.module.css b/docs-ui/src/components/LayoutComponents/LayoutComponents.module.css deleted file mode 100644 index 9009cdb6a4..0000000000 --- a/docs-ui/src/components/LayoutComponents/LayoutComponents.module.css +++ /dev/null @@ -1,48 +0,0 @@ -.layoutComponents { - display: flex; - justify-content: flex-start; - gap: 1rem; - flex-wrap: wrap; - margin-top: 2rem; - - & svg rect { - transition: fill 0.2s ease-in-out; - } - - & .box { - display: flex; - flex-direction: column; - width: calc(50% - 0.5rem); - margin-bottom: 1rem; - align-items: flex-start; - } - - & .content { - flex: none; - background-color: var(--bg); - border: 1px solid var(--border); - border-radius: 4px; - width: 100%; - height: 180px; - transition: all 0.2s ease-in-out; - margin-bottom: 0.75rem; - display: flex; - align-items: center; - justify-content: center; - - &:hover { - transform: translateY(-4px); - } - } - - & .title { - font-size: 16px; - transition: color 0.2s ease-in-out; - margin-bottom: 0.25rem; - } - - & .description { - font-size: 16px; - color: var(--secondary); - } -} diff --git a/docs-ui/src/components/LayoutComponents/LayoutComponents.tsx b/docs-ui/src/components/LayoutComponents/LayoutComponents.tsx deleted file mode 100644 index 0bf822526e..0000000000 --- a/docs-ui/src/components/LayoutComponents/LayoutComponents.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { BoxSvg } from './svgs/box'; -import { FlexSvg } from './svgs/flex'; -import { GridSvg } from './svgs/grid'; -import { ContainerSvg } from './svgs/container'; -import styles from './LayoutComponents.module.css'; -import Link from 'next/link'; - -export const LayoutComponents = () => { - return ( -
-
- - - -
Box
-
- The most basic layout component -
-
-
- - - -
Flex
-
- Arrange your components vertically -
-
-
- - - -
Grid
-
- Arrange your components in a grid -
-
-
- - - -
Container
-
- A container for your components -
-
-
- ); -}; diff --git a/docs-ui/src/components/LayoutComponents/index.ts b/docs-ui/src/components/LayoutComponents/index.ts deleted file mode 100644 index 8efb793bbd..0000000000 --- a/docs-ui/src/components/LayoutComponents/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { LayoutComponents } from './LayoutComponents'; diff --git a/docs-ui/src/components/Navigation/Navigation.module.css b/docs-ui/src/components/Navigation/Navigation.module.css index f5e1312ab1..efba109f97 100644 --- a/docs-ui/src/components/Navigation/Navigation.module.css +++ b/docs-ui/src/components/Navigation/Navigation.module.css @@ -49,16 +49,13 @@ } .sectionTitle { - font-size: 0.6875rem; - font-weight: 500; - padding: 0 12px 4px; - color: var(--secondary); + display: flex; + align-items: center; + gap: 8px; + color: var(--primary); margin-top: 40px; - text-transform: uppercase; - - &:first-child { - margin-top: 12px; - } + padding: 8px 12px; + margin-bottom: 8px; } .line { diff --git a/docs-ui/src/components/Navigation/Navigation.tsx b/docs-ui/src/components/Navigation/Navigation.tsx index 13fa87661f..d27303d94b 100644 --- a/docs-ui/src/components/Navigation/Navigation.tsx +++ b/docs-ui/src/components/Navigation/Navigation.tsx @@ -2,36 +2,22 @@ import Link from 'next/link'; import { usePathname } from 'next/navigation'; -import { Fragment } from 'react'; import clsx from 'clsx'; import { - RiCollageLine, + RiCodeSSlashLine, RiFileHistoryLine, RiHazeLine, RiPaletteLine, RiServiceLine, RiStackLine, } from '@remixicon/react'; -import { components, layoutComponents } from '@/utils/data'; +import { components, hooks } from '@/utils/data'; import styles from './Navigation.module.css'; interface NavigationProps { onLinkClick?: () => void; } -const data = [ - { - title: 'Layout Components', - content: layoutComponents, - url: '/components', - }, - { - title: 'Components', - content: components, - url: '/components', - }, -]; - export const Navigation = ({ onLinkClick }: NavigationProps) => { const pathname = usePathname(); @@ -55,16 +41,6 @@ export const Navigation = ({ onLinkClick }: NavigationProps) => { Tokens -
  • - - - Components - -
  • @@ -89,35 +65,54 @@ export const Navigation = ({ onLinkClick }: NavigationProps) => {
  • - {data.map(section => { +
    + + Components +
    + {components.map(item => { + const isActive = pathname === `/components/${item.slug}`; + return ( - -
    {section.title}
    - - {section.content.map(item => { - const isActive = pathname === `${section.url}/${item.slug}`; - - return ( - -
    {item.title}
    -
    - {item.status === 'alpha' && 'Alpha'} - {item.status === 'beta' && 'Beta'} - {item.status === 'inProgress' && 'In Progress'} - {item.status === 'stable' && 'Stable'} - {item.status === 'deprecated' && 'Deprecated'} -
    - - ); + + onClick={onLinkClick} + > +
    {item.title}
    +
    + {item.status === 'alpha' && 'Alpha'} + {item.status === 'beta' && 'Beta'} + {item.status === 'inProgress' && 'In Progress'} + {item.status === 'stable' && 'Stable'} + {item.status === 'deprecated' && 'Deprecated'} +
    + + ); + })} +
    + Hooks +
    + {hooks.map(item => { + const isActive = pathname === `/hooks/${item.slug}`; + return ( + +
    {item.title}
    +
    + {item.status === 'alpha' && 'Alpha'} + {item.status === 'beta' && 'Beta'} + {item.status === 'inProgress' && 'In Progress'} + {item.status === 'stable' && 'Stable'} + {item.status === 'deprecated' && 'Deprecated'} +
    + ); })} diff --git a/docs-ui/src/components/PropsTable/PropsTable.tsx b/docs-ui/src/components/PropsTable/PropsTable.tsx index 86b7b274ef..a8af2ad116 100644 --- a/docs-ui/src/components/PropsTable/PropsTable.tsx +++ b/docs-ui/src/components/PropsTable/PropsTable.tsx @@ -3,77 +3,133 @@ import * as Table from '../Table'; import { Chip } from '../Chip'; import { TypePopup } from './TypePopup'; +import { SpacingPopup } from './SpacingPopup'; +import { SpacingGroupRow } from './SpacingGroupRow'; import { PropDef } from '@/utils/propDefs'; -// Use the proper PropDef type type PropData = PropDef; -// Modify the PropsTable component to use the new type +type ColumnType = 'prop' | 'type' | 'default' | 'description' | 'responsive'; + +interface ColumnConfig { + key: ColumnType; + width: string; +} + +const defaultColumns: ColumnConfig[] = [ + { key: 'prop' as const, width: '15%' }, + { key: 'type' as const, width: '25%' }, + { key: 'default' as const, width: '15%' }, + { key: 'description' as const, width: '45%' }, +]; + +const columnLabels: Record = { + prop: 'Prop', + type: 'Type', + default: 'Default', + description: 'Description', + responsive: 'Responsive', +}; + export const PropsTable = >({ data, + columns = defaultColumns, }: { data: T; + columns?: ColumnConfig[]; }) => { if (!data) return null; + const renderCell = ( + propName: string, + propData: PropData, + column: ColumnType, + ) => { + const enumValues = + Array.isArray(propData.values) && + propData.values.map(t => {t}); + + switch (column) { + case 'prop': + return {propName}; + + case 'type': + return ( +
    + {propData.type === 'string' && string} + {propData.type === 'number' && number} + {propData.type === 'boolean' && boolean} + {propData.type === 'enum' && enumValues} + {propData.type === 'spacing' && Array.isArray(propData.values) && ( + + )} + {propData.type === 'complex' && propData.complexType && ( + + )} + {propData.type === 'enum | string' && ( + <> + {enumValues} + string + + )} +
    + ); + + case 'default': + return propData.default ? {propData.default} : '-'; + + case 'description': + return propData.description ? ( + {propData.description} + ) : null; + + case 'responsive': + return {propData.responsive ? 'Yes' : 'No'}; + + default: + return null; + } + }; + return ( - Prop - Type - Default - - Responsive - + {columns.map(col => ( + + {columnLabels[col.key]} + + ))} {Object.keys(data).map(n => { - const enumValues = - Array.isArray(data[n].values) && - data[n].values.map(t => {t}); + const propData = data[n]; + // Handle spacing-group type + if (propData.type === 'spacing-group' && propData.spacingGroup) { + return ( + + ); + } + + // Handle regular props return ( - - {n} - - -
    - {data[n].type === 'string' && string} - {data[n].type === 'number' && number} - {data[n].type === 'boolean' && boolean} - {data[n].type === 'enum' && enumValues} - {data[n].type === 'spacing' && ( - <> - 0.5, 1, 1.5, 2, 3, ..., 14 - string - - )} - {data[n].type === 'complex' && data[n].complexType && ( - - )} - {data[n].type === 'enum | string' && ( - <> - {enumValues} - string - - )} -
    -
    - - {data[n].default ? data[n].default : '-'} - - - {data[n].responsive ? 'Yes' : 'No'} - + {columns.map(col => ( + + {renderCell(n, propData, col.key)} + + ))}
    ); })} diff --git a/docs-ui/src/components/PropsTable/SpacingGroupRow.module.css b/docs-ui/src/components/PropsTable/SpacingGroupRow.module.css new file mode 100644 index 0000000000..71ade80b47 --- /dev/null +++ b/docs-ui/src/components/PropsTable/SpacingGroupRow.module.css @@ -0,0 +1,96 @@ +.expandButton { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + background: none; + border: none; + padding: 0; + cursor: pointer; + font-family: inherit; + color: inherit; + text-align: left; + gap: 0.5rem; +} + +.expandButton:hover { + opacity: 0.8; +} + +.propName { + font-family: monospace; + font-size: 13px; + font-weight: 500; + white-space: nowrap; + flex: 1; +} + +.count { + font-size: 12px; + color: var(--text-secondary); + font-weight: normal; +} + +.icon { + color: var(--text-secondary); + flex-shrink: 0; + display: flex; + align-items: center; +} + +.expandedContent { + padding: 0.5rem 0; +} + +.twoColumnLayout { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 2rem; +} + +.column { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.columnTitle { + font-size: 13px; + font-weight: 600; + color: var(--text-primary); + margin-bottom: 0.25rem; + padding-bottom: 0.5rem; + border-bottom: 1px solid var(--border); +} + +.propsGrid { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.propRow { + display: grid; + grid-template-columns: auto 1fr auto; + gap: 1rem; + align-items: start; +} + +.propRowName { + min-width: 80px; +} + +.propRowDescription { + font-size: 14px; + color: var(--text-secondary); + line-height: 1.5; +} + +.propRowDefault { + font-size: 12px; + color: var(--text-secondary); + white-space: nowrap; + display: flex; + align-items: center; + gap: 0.25rem; +} diff --git a/docs-ui/src/components/PropsTable/SpacingGroupRow.tsx b/docs-ui/src/components/PropsTable/SpacingGroupRow.tsx new file mode 100644 index 0000000000..b3cc8de26d --- /dev/null +++ b/docs-ui/src/components/PropsTable/SpacingGroupRow.tsx @@ -0,0 +1,142 @@ +'use client'; + +import { useState } from 'react'; +import * as Table from '../Table'; +import { Chip } from '../Chip'; +import { SpacingPopup } from './SpacingPopup'; +import type { SpacingGroupDef } from '@/utils/propDefs'; +import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react'; +import styles from './SpacingGroupRow.module.css'; + +interface SpacingGroupRowProps { + spacingGroup: SpacingGroupDef; + description?: React.ReactNode; + columns: Array<{ key: string; width: string }>; +} + +export const SpacingGroupRow = ({ + spacingGroup, + description, + columns, +}: SpacingGroupRowProps) => { + const [isExpanded, setIsExpanded] = useState(false); + + // Separate padding and margin props + const paddingProps = spacingGroup.props.filter(prop => + prop.name.startsWith('p'), + ); + const marginProps = spacingGroup.props.filter(prop => + prop.name.startsWith('m'), + ); + + const renderCell = (columnKey: string) => { + switch (columnKey) { + case 'prop': + return ( + + ); + + case 'type': + return ; + + case 'default': + return '-'; + + case 'description': + return description ? ( + {description} + ) : null; + + case 'responsive': + return {spacingGroup.responsive ? 'Yes' : 'No'}; + + default: + return null; + } + }; + + return ( + <> + + {columns.map(col => ( + + {renderCell(col.key)} + + ))} + + {isExpanded && ( + + +
    +
    + {/* Padding Column */} + {paddingProps.length > 0 && ( +
    +
    Padding
    +
    + {paddingProps.map(prop => ( +
    +
    + {prop.name} +
    +
    + {prop.description} +
    + {prop.default && ( +
    + Default: {prop.default} +
    + )} +
    + ))} +
    +
    + )} + + {/* Margin Column */} + {marginProps.length > 0 && ( +
    +
    Margin
    +
    + {marginProps.map(prop => ( +
    +
    + {prop.name} +
    +
    + {prop.description} +
    + {prop.default && ( +
    + Default: {prop.default} +
    + )} +
    + ))} +
    +
    + )} +
    +
    +
    +
    + )} + + ); +}; diff --git a/docs-ui/src/components/PropsTable/SpacingPopup.module.css b/docs-ui/src/components/PropsTable/SpacingPopup.module.css new file mode 100644 index 0000000000..892bc14bc6 --- /dev/null +++ b/docs-ui/src/components/PropsTable/SpacingPopup.module.css @@ -0,0 +1,77 @@ +.button { + display: inline-flex; + align-items: center; + font-family: monospace; + font-size: 13px; + border-radius: 6px; + padding: 0px 8px; + height: 24px; + margin-right: 4px; + background-color: #f0f0f0; + color: #5d5d5d; + cursor: pointer; + border: none; + outline: none; + box-shadow: none; + transition: background-color 150ms ease; +} + +.button:hover { + background-color: #e0e0e0; +} + +[data-theme-mode='dark'] .button { + background-color: #2c2c2c; + color: #fff; +} + +[data-theme-mode='dark'] .button:hover { + background-color: #3c3c3c; +} + +.popover { + border: 1px solid var(--border); + box-shadow: 0 8px 20px rgba(0 0 0 / 0.1); + border-radius: 6px; + background: var(--bg); + color: var(--primary); + outline: none; + transition: transform 200ms, opacity 200ms; + padding: 1rem; + margin-top: 6px; + max-width: 400px; + --origin: translateY(-8px); + + &[data-entering], + &[data-exiting] { + transform: var(--origin); + opacity: 0; + } +} + +.arrow svg { + display: block; + fill: var(--bg); + stroke: var(--border); + stroke-width: 1px; + transform: rotate(180deg); +} + +.title { + font-size: 14px; + font-weight: 500; + margin-bottom: 0.75rem; +} + +.grid { + display: flex; + flex-wrap: wrap; + gap: 0.375rem; + margin-bottom: 0.75rem; +} + +.note { + font-size: 12px; + color: var(--text-secondary); + font-style: italic; +} diff --git a/docs-ui/src/components/PropsTable/SpacingPopup.tsx b/docs-ui/src/components/PropsTable/SpacingPopup.tsx new file mode 100644 index 0000000000..9515ec4e5a --- /dev/null +++ b/docs-ui/src/components/PropsTable/SpacingPopup.tsx @@ -0,0 +1,47 @@ +import { Chip } from '../Chip'; +import { + Button, + Dialog, + DialogTrigger, + OverlayArrow, + Popover, +} from 'react-aria-components'; +import styles from './SpacingPopup.module.css'; + +interface SpacingPopupProps { + values: string[]; +} + +export const SpacingPopup = ({ values }: SpacingPopupProps) => { + // Display abbreviated list: first, second, ..., last + const firstValue = values[0]; + const secondValue = values[1]; + const lastValue = values[values.length - 1]; + + return ( +
    + {firstValue} + {secondValue} + + + + + + + + + +
    All spacing values
    +
    + {values.map(value => ( + {value} + ))} +
    +
    Also accepts custom string values
    +
    +
    +
    + {lastValue} +
    + ); +}; diff --git a/docs-ui/src/components/ReactAriaLink/ReactAriaLink.tsx b/docs-ui/src/components/ReactAriaLink/ReactAriaLink.tsx new file mode 100644 index 0000000000..37656c482d --- /dev/null +++ b/docs-ui/src/components/ReactAriaLink/ReactAriaLink.tsx @@ -0,0 +1,26 @@ +import styles from './styles.module.css'; + +interface ReactAriaLinkProps { + /** The React Aria component name (e.g., "Cell", "Table") */ + component: string; + /** The documentation URL */ + href: string; +} + +/** + * Displays a standardized note linking to React Aria documentation. + * + * Usage: + * + */ +export function ReactAriaLink({ component, href }: ReactAriaLinkProps) { + return ( +

    + Inherits all{' '} + + React Aria {component} + {' '} + props. +

    + ); +} diff --git a/docs-ui/src/components/ReactAriaLink/index.ts b/docs-ui/src/components/ReactAriaLink/index.ts new file mode 100644 index 0000000000..8fa7b70e73 --- /dev/null +++ b/docs-ui/src/components/ReactAriaLink/index.ts @@ -0,0 +1 @@ +export { ReactAriaLink } from './ReactAriaLink'; diff --git a/docs-ui/src/components/ReactAriaLink/styles.module.css b/docs-ui/src/components/ReactAriaLink/styles.module.css new file mode 100644 index 0000000000..b74436fee3 --- /dev/null +++ b/docs-ui/src/components/ReactAriaLink/styles.module.css @@ -0,0 +1,15 @@ +.note { + font-size: 1rem; + color: var(--secondary); + margin-top: 0.5rem; + margin-bottom: 1rem; +} + +.note a { + color: var(--link); + text-decoration: none; +} + +.note a:hover { + text-decoration: underline; +} diff --git a/docs-ui/src/components/Roadmap/Roadmap.tsx b/docs-ui/src/components/Roadmap/Roadmap.tsx deleted file mode 100644 index 52f96e0f76..0000000000 --- a/docs-ui/src/components/Roadmap/Roadmap.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { RoadmapItem } from './list'; - -export const Roadmap = ({ list }: { list: RoadmapItem[] }) => { - const orderList = ['inProgress', 'notStarted', 'completed']; - return ( -
    - {list - .sort( - (a, b) => orderList.indexOf(a.status) - orderList.indexOf(b.status), - ) - .map(Item)} -
    - ); -}; - -const Item = ({ - title, - status = 'notStarted', -}: { - title: string; - status: 'notStarted' | 'inProgress' | 'inReview' | 'completed'; -}) => { - return ( -
    -
    -
    -
    {title}
    -
    - - {status === 'notStarted' && 'Not Started'} - {status === 'inProgress' && 'In Progress'} - {status === 'inReview' && 'Ready for Review'} - {status === 'completed' && 'Completed'} - -
    - ); -}; diff --git a/docs-ui/src/components/Roadmap/index.ts b/docs-ui/src/components/Roadmap/index.ts deleted file mode 100644 index c7006073d6..0000000000 --- a/docs-ui/src/components/Roadmap/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { Roadmap } from './Roadmap'; diff --git a/docs-ui/src/components/Roadmap/list.ts b/docs-ui/src/components/Roadmap/list.ts deleted file mode 100644 index 24b9197cf5..0000000000 --- a/docs-ui/src/components/Roadmap/list.ts +++ /dev/null @@ -1,47 +0,0 @@ -export type RoadmapItem = { - title: string; - status: 'notStarted' | 'inProgress' | 'inReview' | 'completed'; -}; - -export const list: RoadmapItem[] = [ - { - title: 'Remove Vanilla Extract and use pure CSS instead', - status: 'inProgress', - }, - { - title: 'Add collapsing across breakpoints for the Inline component', - status: 'notStarted', - }, - { - title: 'Add reversing the order for the Inline component', - status: 'notStarted', - }, - { - title: 'Set up Storybook', - status: 'completed', - }, - { - title: 'Set up iconography', - status: 'completed', - }, - { - title: 'Set up global tokens', - status: 'inProgress', - }, - { - title: 'Set up theming system', - status: 'inProgress', - }, - { - title: 'Create first pass at box component', - status: 'completed', - }, - { - title: 'Create first pass at stack component', - status: 'completed', - }, - { - title: 'Create first pass at inline component', - status: 'completed', - }, -]; diff --git a/docs-ui/src/components/Roadmap/styles.css b/docs-ui/src/components/Roadmap/styles.css deleted file mode 100644 index 63f32312e7..0000000000 --- a/docs-ui/src/components/Roadmap/styles.css +++ /dev/null @@ -1,100 +0,0 @@ -.roadmap { - display: flex; - flex-direction: column; -} - -.roadmap .roadmap-item { - display: flex; - align-items: center; - justify-content: space-between; - border-bottom: 1px solid #e0e0e0; - padding: 8px 0px; -} - -.roadmap .roadmap-item .left { - display: flex; - align-items: center; - padding-left: 12px; - gap: 12px; -} - -.roadmap .roadmap-item .dot { - width: 8px; - height: 8px; - border-radius: 50%; - background-color: #e0e0e0; -} - -.roadmap .roadmap-item.notStarted { - color: #000; -} - -.roadmap .roadmap-item.inProgress { - color: #000; -} - -.roadmap .roadmap-item.inReview { - color: #000; -} - -.roadmap .roadmap-item.completed .title { - color: #a2a2a2; - text-decoration: line-through; -} - -.roadmap .roadmap-item.notStarted .dot { - background-color: #d1d1d1; -} - -.roadmap .roadmap-item.inProgress .dot { - background-color: #ffd000; -} - -.roadmap .roadmap-item.inReview .dot { - background-color: #4ed14a; -} - -.roadmap .roadmap-item.completed .dot { - background-color: #4ed14a; -} - -.roadmap .roadmap-item .title { - font-size: 16px; -} - -.roadmap .roadmap-item .pill { - display: inline-flex; - align-items: center; - height: 24px; - padding: 0px 8px; - border-radius: 40px; - font-size: 12px; - font-weight: 600; - margin-left: 8px; - border-style: solid; - border-width: 1px; -} - -.roadmap .roadmap-item.notStarted .pill { - background-color: #f2f2f2; - border-color: #cdcdcd; - color: #888888; -} - -.roadmap .roadmap-item.inProgress .pill { - background-color: #fff2b9; - border-color: #ffd000; - color: #d79927; -} - -.roadmap .roadmap-item.inReview .pill { - background-color: #d7f9d7; - border-color: #4ed14a; - color: #3a9837; -} - -.roadmap .roadmap-item.completed .pill { - background-color: #d7f9d7; - border-color: #4ed14a; - color: #3a9837; -} diff --git a/docs-ui/src/components/Snippet/client.tsx b/docs-ui/src/components/Snippet/client.tsx index 04284c3249..2041218a12 100644 --- a/docs-ui/src/components/Snippet/client.tsx +++ b/docs-ui/src/components/Snippet/client.tsx @@ -12,6 +12,7 @@ interface SnippetProps { py?: number; open?: boolean; height?: string | number; + layout?: 'stacked' | 'side-by-side'; } export const SnippetClient = ({ @@ -22,9 +23,28 @@ export const SnippetClient = ({ py = 2, open = false, height = 'auto', + layout = 'stacked', }: SnippetProps) => { const [isOpen, setIsOpen] = useState(open); + if (layout === 'side-by-side') { + return ( +
    +
    +
    {codeContent}
    +
    +
    +
    + {preview} +
    +
    +
    + ); + } + return ( { return ( ); }; diff --git a/docs-ui/src/components/Snippet/styles.module.css b/docs-ui/src/components/Snippet/styles.module.css index 3141564293..c6270670a7 100644 --- a/docs-ui/src/components/Snippet/styles.module.css +++ b/docs-ui/src/components/Snippet/styles.module.css @@ -7,7 +7,7 @@ .preview { border-radius: 8px; box-shadow: inset 0 0 0 1px var(--border); - background-color: var(--bui-bg); + background-color: var(--bui-bg-app); padding: 1px; position: relative; } @@ -60,3 +60,43 @@ opacity: 1; } } + +/* Side-by-side layout */ +.sideBySide { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; + align-items: stretch; +} + +.sideBySideCode { + min-width: 0; + overflow: auto; + display: flex; + flex-direction: column; +} + +.codeWrapper { + flex: 1; + display: flex; + flex-direction: column; +} + +.codeWrapper > div { + flex: 1; + margin-bottom: 0; +} + +.sideBySidePreview { + border-radius: 8px; + box-shadow: inset 0 0 0 1px var(--border); + background-color: var(--bui-bg-app); + padding: 1px; + min-width: 0; + display: flex; + flex-direction: column; +} + +.sideBySidePreview > .previewContent { + flex: 1; +} diff --git a/docs-ui/src/components/Table/Table.tsx b/docs-ui/src/components/Table/Table.tsx index 4d0e48dbce..a43bcd766a 100644 --- a/docs-ui/src/components/Table/Table.tsx +++ b/docs-ui/src/components/Table/Table.tsx @@ -45,12 +45,14 @@ export const Row = ({ children }: { children: ReactNode }) => { export const Cell = ({ children, style, + colSpan, }: { children: ReactNode; style?: CSSProperties; + colSpan?: number; }) => { return ( -
    ); diff --git a/docs-ui/src/components/TableOfContents/TableOfContents.tsx b/docs-ui/src/components/TableOfContents/TableOfContents.tsx index d18265415b..429d677b80 100644 --- a/docs-ui/src/components/TableOfContents/TableOfContents.tsx +++ b/docs-ui/src/components/TableOfContents/TableOfContents.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useEffect, useState } from 'react'; +import { useEffect, useState, useLayoutEffect } from 'react'; import { usePathname } from 'next/navigation'; import styles from './TableOfContents.module.css'; @@ -17,6 +17,29 @@ export function TableOfContents() { const [indicatorHeight, setIndicatorHeight] = useState(0); const pathname = usePathname(); + // Update indicator position when activeId changes + useLayoutEffect(() => { + if (!activeId) return; + + // Use requestAnimationFrame to defer setState call + const rafId = requestAnimationFrame(() => { + const activeElement = document.querySelector( + `[data-toc-id="${activeId}"]`, + ) as HTMLElement; + if (activeElement) { + const list = activeElement.closest('ul'); + if (list) { + const listRect = list.getBoundingClientRect(); + const elementRect = activeElement.getBoundingClientRect(); + setIndicatorTop(elementRect.top - listRect.top); + setIndicatorHeight(elementRect.height); + } + } + }); + + return () => cancelAnimationFrame(rafId); + }, [activeId, headings]); + useEffect(() => { // Extract all H2 and H3 headings from the document const elements = Array.from( @@ -29,18 +52,6 @@ export function TableOfContents() { level: parseInt(element.tagName.substring(1)), })); - setHeadings(headingData); - - // Set initial active heading (first visible heading or first heading) - if (headingData.length > 0) { - const viewportTop = window.scrollY + 100; // offset for header - const visibleHeading = elements.find(element => { - const rect = element.getBoundingClientRect(); - return rect.top + window.scrollY >= viewportTop - 200; - }); - setActiveId(visibleHeading?.id || headingData[0].id); - } - // Set up IntersectionObserver to track visible headings const observerOptions = { rootMargin: '-80px 0px -80% 0px', @@ -62,29 +73,26 @@ export function TableOfContents() { elements.forEach(element => observer.observe(element)); + // Initialize headings and active ID after observer is set up + requestAnimationFrame(() => { + setHeadings(headingData); + + // Set initial active heading (first visible heading or first heading) + if (headingData.length > 0) { + const viewportTop = window.scrollY + 100; // offset for header + const visibleHeading = elements.find(element => { + const rect = element.getBoundingClientRect(); + return rect.top + window.scrollY >= viewportTop - 200; + }); + setActiveId(visibleHeading?.id || headingData[0].id); + } + }); + return () => { elements.forEach(element => observer.unobserve(element)); }; }, [pathname]); - // Update indicator position when activeId changes - useEffect(() => { - if (activeId) { - const activeElement = document.querySelector( - `[data-toc-id="${activeId}"]`, - ) as HTMLElement; - if (activeElement) { - const list = activeElement.closest('ul'); - if (list) { - const listRect = list.getBoundingClientRect(); - const elementRect = activeElement.getBoundingClientRect(); - setIndicatorTop(elementRect.top - listRect.top); - setIndicatorHeight(elementRect.height); - } - } - } - }, [activeId, headings]); - const handleClick = (id: string) => { const element = document.getElementById(id); if (element) { diff --git a/docs-ui/src/components/Theming/index.tsx b/docs-ui/src/components/Theming/index.tsx index d06f51348b..9f67c3a111 100644 --- a/docs-ui/src/components/Theming/index.tsx +++ b/docs-ui/src/components/Theming/index.tsx @@ -1,11 +1,16 @@ -import { MDXRemote } from 'next-mdx-remote-client/rsc'; -import { formattedMDXComponents } from '@/mdx-components'; -import type { - ComponentDefinition, - DataAttributeValues, -} from '../../../../packages/ui/src/types'; +'use client'; -export function Theming({ definition }: { definition: ComponentDefinition }) { +import { formattedMDXComponents } from '@/mdx-components'; +import type { DataAttributeValues } from '../../../../packages/ui/src/types'; + +interface ThemingProps { + definition: { + classNames: Record; + dataAttributes?: Record; + }; +} + +export function Theming({ definition }: ThemingProps) { const classNames = definition.classNames; const dataAttributes = definition.dataAttributes; @@ -33,15 +38,28 @@ export function Theming({ definition }: { definition: ComponentDefinition }) { ...Object.values(classNames).slice(1), ]; + // Use the same styled components from MDX, with fallbacks to HTML elements + const H2 = formattedMDXComponents.h2 || 'h2'; + const P = formattedMDXComponents.p || 'p'; + const Ul = formattedMDXComponents.ul || 'ul'; + const Li = formattedMDXComponents.li || 'li'; + const Code = formattedMDXComponents.code || 'code'; + return ( - `- \`${selector}\``).join('\n')} - `} - /> +
    +

    Theming

    +

    + Our theming system is based on a mix between CSS classes, CSS variables + and data attributes. If you want to customise this component, you can + use one of these class names below. +

    +
      + {classNamesArray.map((selector, index) => ( +
    • + {selector} +
    • + ))} +
    +
    ); } diff --git a/docs-ui/src/components/Toolbar/Toolbar.module.css b/docs-ui/src/components/Toolbar/Toolbar.module.css index 581fc599b3..4f067bd7bd 100644 --- a/docs-ui/src/components/Toolbar/Toolbar.module.css +++ b/docs-ui/src/components/Toolbar/Toolbar.module.css @@ -10,12 +10,10 @@ } } -.breadcrumb { +.left { display: flex; align-items: center; gap: 0.5rem; - font-size: 0.875rem; - font-weight: 500; } .logoMobile { @@ -26,39 +24,6 @@ } } -.breadcrumbDesktop { - display: none; - - @media (min-width: 768px) { - display: flex; - align-items: center; - gap: 0.5rem; - } -} - -.breadcrumbLink { - color: var(--secondary); - text-decoration: none; - cursor: pointer; - - &:hover { - color: var(--primary); - text-decoration: underline; - transition: color 0.2s ease-in-out; - text-decoration-thickness: 1px; - text-underline-offset: 4px; - } -} - -.breadcrumbSeparator { - color: var(--secondary); - flex-shrink: 0; -} - -.breadcrumbCurrent { - color: var(--primary); -} - .actions { display: none; @@ -119,6 +84,51 @@ } } +.searchButton { + display: none; + align-items: center; + gap: 6px; + background-color: var(--bg); + border: 1px solid var(--border); + border-radius: 32px; + padding-inline: 16px 12px; + min-width: 180px; + color: var(--secondary); + font-size: 0.8125rem; + font-weight: 500; + height: 32px; + cursor: pointer; + font-family: inherit; + white-space: nowrap; + + &:hover { + background-color: var(--action); + color: var(--primary); + transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out; + } + + @media (min-width: 768px) { + display: flex; + } +} + +.searchLabel { + display: inline; + flex: 1; + text-align: left; +} + +.searchKbd { + display: inline; + font-family: inherit; + font-size: 0.6875rem; + font-weight: 500; + background-color: var(--action); + border-radius: 4px; + padding: 2px 5px; + color: var(--secondary); +} + .buttonGroup { display: flex; align-items: center; diff --git a/docs-ui/src/components/Toolbar/Toolbar.tsx b/docs-ui/src/components/Toolbar/Toolbar.tsx index e7defd5e8a..3eb711558d 100644 --- a/docs-ui/src/components/Toolbar/Toolbar.tsx +++ b/docs-ui/src/components/Toolbar/Toolbar.tsx @@ -1,10 +1,12 @@ 'use client'; +import { useState, useEffect } from 'react'; import { RiArrowDownSLine, - RiArrowRightSLine, + RiDiscordLine, RiGithubLine, RiMoonLine, + RiSearchLine, RiSunLine, } from '@remixicon/react'; import { @@ -19,10 +21,8 @@ import { } from 'react-aria-components'; import styles from './Toolbar.module.css'; import { usePlayground } from '@/utils/playground-context'; -import { usePathname } from 'next/navigation'; -import Link from 'next/link'; -import { components, layoutComponents } from '@/utils/data'; import { Logo } from '@/components/Sidebar/Logo'; +import { CommandPalette } from '@/components/CommandPalette'; interface ToolbarProps { version: string; @@ -42,81 +42,43 @@ export const Toolbar = ({ version }: ToolbarProps) => { setSelectedThemeName, } = usePlayground(); - const pathname = usePathname(); + const [isCommandPaletteOpen, setIsCommandPaletteOpen] = useState(false); - // Determine breadcrumb content based on current path - const getBreadcrumb = () => { - const allComponents = [...components, ...layoutComponents]; + useEffect(() => { + const isMac = /mac(os|intosh)/i.test(navigator.userAgent); + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'k' && (isMac ? e.metaKey : e.ctrlKey)) { + e.preventDefault(); + setIsCommandPaletteOpen(prev => !prev); + } + }; - // Root page - if (pathname === '/') { - return { section: null, title: 'Getting Started' }; - } - - // Components index page - if (pathname === '/components') { - return { section: null, title: 'Components' }; - } - - // Component detail pages - if (pathname?.startsWith('/components/')) { - const slug = pathname.split('/components/')[1]; - const component = allComponents.find(c => c.slug === slug); - return { - section: 'Components', - sectionLink: '/components', - title: component?.title || slug, - }; - } - - // Tokens page - if (pathname === '/tokens') { - return { section: null, title: 'Tokens' }; - } - - // Changelog page - if (pathname === '/changelog') { - return { section: null, title: 'Changelog' }; - } - - return { section: null, title: '' }; - }; - - const breadcrumb = getBreadcrumb(); + document.addEventListener('keydown', handleKeyDown); + return () => document.removeEventListener('keydown', handleKeyDown); + }, []); return (
    -
    +
    -
    - {breadcrumb.section && breadcrumb.sectionLink ? ( - <> - - {breadcrumb.section} - - - - {breadcrumb.title} - - - ) : ( - {breadcrumb.title} - )} -
    +
    - Admin - User - Viewer - - - - - - - - -`; - -export const dialogWithNoTriggerSnippet = `const [isOpen, setIsOpen] = useState(false); - - - Create New User - - Your content - - - - - -`; - -export const dialogCloseSnippet = ``; diff --git a/docs-ui/src/content/flex.mdx b/docs-ui/src/content/flex.mdx deleted file mode 100644 index 36284f1f0b..0000000000 --- a/docs-ui/src/content/flex.mdx +++ /dev/null @@ -1,69 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { CodeBlock } from '@/components/CodeBlock'; -import { Snippet } from '@/components/Snippet'; -import { FlexSnippet } from '@/snippets/stories-snippets'; -import { - flexPropDefs, - flexUsageSnippet, - flexDefaultSnippet, - flexFAQ1Snippet, - flexSimpleSnippet, - flexResponsiveSnippet, - flexAlignSnippet, -} from './flex.props'; -import { spacingPropDefs } from '@/utils/propDefs'; -import { PageTitle } from '@/components/PageTitle'; -import { Theming } from '@/components/Theming'; -import { FlexDefinition } from '../utils/definitions'; -import { ChangelogComponent } from '@/components/ChangelogComponent'; - - - -} - code={flexDefaultSnippet} -/> - -## Usage - - - -## API reference - - - -The grid component also accepts all the spacing props from the Box component. - - - -## Examples - -### Simple - -A simple example of how to use the Flex component. - - - -### Responsive - -The Flex component also supports responsive values, making it easy to create -responsive designs. - - - -### Align - -The Flex component also supports responsive alignment, making it easy to -create responsive designs. - - - - - - diff --git a/docs-ui/src/content/flex.props.ts b/docs-ui/src/content/flex.props.ts deleted file mode 100644 index cc4c2d5a75..0000000000 --- a/docs-ui/src/content/flex.props.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { - childrenPropDefs, - classNamePropDefs, - stylePropDefs, - type PropDef, -} from '@/utils/propDefs'; - -export const flexPropDefs: Record = { - align: { - type: 'enum', - values: ['start', 'center', 'end', 'baseline', 'stretch'], - responsive: true, - }, - direction: { - type: 'enum', - values: ['row', 'column', 'row-reverse', 'column-reverse'], - responsive: true, - }, - justify: { - type: 'enum', - values: ['start', 'center', 'end', 'between'], - responsive: true, - }, - ...childrenPropDefs, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const flexUsageSnippet = `import { Flex } from '@backstage/ui'; - -`; - -export const flexDefaultSnippet = ` - - - -`; - -export const flexSimpleSnippet = ` - Hello World - Hello World - Hello World -`; - -export const flexResponsiveSnippet = ` - Hello World - Hello World - Hello World -`; - -export const flexAlignSnippet = ` - Hello World - Hello World - Hello World -`; diff --git a/docs-ui/src/content/grid.mdx b/docs-ui/src/content/grid.mdx deleted file mode 100644 index 5f179b9e14..0000000000 --- a/docs-ui/src/content/grid.mdx +++ /dev/null @@ -1,99 +0,0 @@ -import { CodeBlock } from '@/components/CodeBlock'; -import { PropsTable } from '@/components/PropsTable'; -import { Snippet } from '@/components/Snippet'; -import { GridSnippet } from '@/snippets/stories-snippets'; -import { spacingPropDefs } from '@/utils/propDefs'; -import { - gridPropDefs, - gridItemPropDefs, - gridUsageSnippet, - gridDefaultSnippet, - gridSimpleSnippet, - gridComplexSnippet, - gridMixingRowsSnippet, - gridResponsiveSnippet, - gridStartEndSnippet, -} from './grid.props'; -import { PageTitle } from '@/components/PageTitle'; -import { Theming } from '@/components/Theming'; -import { GridDefinition } from '../utils/definitions'; -import { ChangelogComponent } from '@/components/ChangelogComponent'; - - - -} - code={gridDefaultSnippet} -/> - -## Usage - - - -## API reference - -### Grid.Root - -This is the grid container component. It will help to define the number of -columns that will be used in the grid. You can also define the gap between the -columns. All values are responsive. - - - -The grid component also accepts all the spacing props from the Box component. - - - -### Grid.Item - -If you need more control over the columns, you can use the grid item -component. This will give you access to `rowSpan`, `colSpan`, `start` and -`end`. All values are responsive. This component is optional, you can use any -elements directly if you prefer. - - - -## Examples - -### Simple grid - -A simple grid with 3 columns and a gap of md. - - - -### Complex grid - -You can also use the grid item to create more complex layouts. In this example -the first column will span 1 column and the second column will span 2 columns. - - - -### Mixing rows and columns - -The grid item component also supports the `rowSpan` prop, which allows you to -span multiple rows within the grid layout. In this example, the first item -will span 2 rows to achieve a dynamic and flexible grid structure. - - - -### Responsive grid - -The grid component also supports responsive values, making it easy to create -responsive designs. - - - -### Start and End - -The start and end props can be used to position the item in the grid. - - - - - - diff --git a/docs-ui/src/content/grid.props.ts b/docs-ui/src/content/grid.props.ts deleted file mode 100644 index 6f037cd64f..0000000000 --- a/docs-ui/src/content/grid.props.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { - childrenPropDefs, - classNamePropDefs, - stylePropDefs, - type PropDef, -} from '@/utils/propDefs'; - -const columnsValues = [ - '1', - '2', - '3', - '4', - '5', - '6', - '7', - '8', - '9', - '10', - '11', - '12', -]; - -export const gridPropDefs: Record = { - columns: { - type: 'enum | string', - values: [...columnsValues, 'auto'], - responsive: true, - default: 'auto', - }, - ...childrenPropDefs, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const gridItemPropDefs: Record = { - colSpan: { - type: 'enum | string', - values: [...columnsValues, 'full'], - responsive: true, - }, - rowSpan: { - type: 'enum | string', - values: [...columnsValues, 'full'], - responsive: true, - }, - colStart: { - type: 'enum | string', - values: [...columnsValues, 'auto'], - responsive: true, - }, - colEnd: { - type: 'enum | string', - values: [...columnsValues, 'auto'], - responsive: true, - }, - ...childrenPropDefs, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const gridUsageSnippet = `import { Grid } from '@backstage/ui'; - -`; - -export const gridDefaultSnippet = ` - - - -`; - -export const gridSimpleSnippet = ` - Hello World - Hello World - Hello World -`; - -export const gridComplexSnippet = ` - - Hello World - - - Hello World - -`; - -export const gridMixingRowsSnippet = ` - - Hello World - - - Hello World - - - Hello World - -`; - -export const gridResponsiveSnippet = ` - - - - Hello World - -`; - -export const gridStartEndSnippet = ` - - Hello World - -`; diff --git a/docs-ui/src/content/header-page.mdx b/docs-ui/src/content/header-page.mdx deleted file mode 100644 index 12ef2c32ee..0000000000 --- a/docs-ui/src/content/header-page.mdx +++ /dev/null @@ -1,88 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { CodeBlock } from '@/components/CodeBlock'; -import { Snippet } from '@/components/Snippet'; -import { HeaderPageSnippet } from '@/snippets/stories-snippets'; -import { - propDefs, - usage, - simple, - defaultSnippet, - withTabs, - withBreadcrumbs, - withCustomActions, - withMenuItems, -} from './header-page.props'; -import { PageTitle } from '@/components/PageTitle'; -import { Theming } from '@/components/Theming'; -import { HeaderPageDefinition } from '../utils/definitions'; -import { ChangelogComponent } from '@/components/ChangelogComponent'; - - - -} - code={defaultSnippet} -/> - -## Usage - - - -## API reference - - - -## Examples - -### With Breadcrumbs - -You can add breadcrumbs to the header page to help users navigate to the previous page. The `breadcrumbs` -prop is an array of objects with a `label` and `href` property. By default we truncate the breadcrumb label to 240px. - -} - code={withBreadcrumbs} -/> - -### With Tabs - -You can add tabs to the header page to help users navigate to the different sections of the page. The `tabs` -prop is an array of objects with a `label` and `href` property. - -} - code={withTabs} -/> - -### With Custom Actions - -You can add custom actions to the header page to help users navigate to the different sections of the page. The `customActions` -prop is a React node. - -} - code={withCustomActions} -/> - -### With Menu Items - -You can add menu items to the header page to help users navigate to the different sections of the page. The `menuItems` -prop is an array of objects with a `label`, `value` and `onClick` property. - -} - code={withMenuItems} -/> - - - - diff --git a/docs-ui/src/content/header-page.props.ts b/docs-ui/src/content/header-page.props.ts deleted file mode 100644 index fca9497b58..0000000000 --- a/docs-ui/src/content/header-page.props.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { - childrenPropDefs, - classNamePropDefs, - stylePropDefs, - type PropDef, -} from '@/utils/propDefs'; - -export const propDefs: Record = { - title: { - type: 'string', - default: 'Your plugin', - }, - customActions: { - type: 'enum', - values: ['ReactNode'], - }, - menuItems: { - type: 'complex', - complexType: { - name: 'MenuItem[]', - properties: { - label: { - type: 'string', - required: true, - description: 'Display text for the menu item', - }, - value: { - type: 'string', - required: true, - description: 'Unique value for the menu item', - }, - onClick: { - type: '() => void', - required: false, - description: 'Callback function when menu item is clicked', - }, - }, - }, - }, - tabs: { - type: 'complex', - complexType: { - name: 'HeaderTab[]', - properties: { - id: { - type: 'string', - required: true, - description: 'Unique identifier for the tab', - }, - label: { - type: 'string', - required: true, - description: 'Display text for the tab', - }, - href: { - type: 'string', - required: false, - description: 'URL to navigate to when tab is clicked', - }, - matchStrategy: { - type: "'exact' | 'prefix'", - required: false, - description: 'How to match the current route to highlight the tab', - }, - }, - }, - }, - breadcrumbs: { - type: 'complex', - complexType: { - name: 'Breadcrumb[]', - properties: { - label: { - type: 'string', - required: true, - description: 'Display text for the breadcrumb', - }, - href: { - type: 'string', - required: true, - description: 'URL for the breadcrumb link', - }, - }, - }, - }, - ...childrenPropDefs, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const usage = `import { HeaderPage } from '@backstage/ui'; - -`; - -export const defaultSnippet = `Custom action} -/>`; - -export const withBreadcrumbs = ``; - -export const withTabs = ``; - -export const withCustomActions = `Custom action} -/>`; - -export const withMenuItems = ` {} }, - { label: 'Invite new members', value: 'invite-new-members', onClick: () => {} }, - ]} -/>`; diff --git a/docs-ui/src/content/header.mdx b/docs-ui/src/content/header.mdx deleted file mode 100644 index eb505d7a62..0000000000 --- a/docs-ui/src/content/header.mdx +++ /dev/null @@ -1,86 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { CodeBlock } from '@/components/CodeBlock'; -import { Snippet } from '@/components/Snippet'; -import { HeaderSnippet } from '@/snippets/stories-snippets'; -import { - propDefs, - usage, - simple, - defaultSnippet, - withTabs, - withBreadcrumbs, - withHeaderPage, -} from './header.props'; -import { PageTitle } from '@/components/PageTitle'; -import { Theming } from '@/components/Theming'; -import { HeaderDefinition } from '../utils/definitions'; -import { ChangelogComponent } from '@/components/ChangelogComponent'; - - - -} - code={defaultSnippet} -/> - -## Usage - - - -## API reference - - - -## Examples - -### Simple header - -A simple example of how to use the Header component. - -} - code={simple} - open -/> - -### Header with tabs - -A simple example of how to use the Header component with tabs. All links are using React Router -under the hood and will be active when you are on the corresponding page. - -} -code={withTabs} -open -/> - -### Header with breadcrumbs - -Breacrumbs should appear when you scroll down (and not directly visible as it is in the demo below). - -} - code={withBreadcrumbs} - open -/> - -### Header with HeaderPage - -You can use the `Header` component inside the [HeaderPage](/components/header-page) component to compose your multi-level navigation. - -} - code={withHeaderPage} - open -/> - - - - diff --git a/docs-ui/src/content/header.props.ts b/docs-ui/src/content/header.props.ts deleted file mode 100644 index af9a745fc5..0000000000 --- a/docs-ui/src/content/header.props.ts +++ /dev/null @@ -1,200 +0,0 @@ -import { - childrenPropDefs, - classNamePropDefs, - stylePropDefs, - type PropDef, -} from '@/utils/propDefs'; - -export const propDefs: Record = { - icon: { - type: 'enum', - values: ['ReactNode'], - }, - title: { - type: 'string', - default: 'Your plugin', - }, - titleLink: { - type: 'string', - default: '/', - }, - customActions: { - type: 'enum', - values: ['ReactNode'], - }, - menuItems: { - type: 'complex', - complexType: { - name: 'MenuItem[]', - properties: { - label: { - type: 'string', - required: true, - description: 'Display text for the menu item', - }, - value: { - type: 'string', - required: true, - description: 'Unique value for the menu item', - }, - onClick: { - type: '() => void', - required: false, - description: 'Callback function when menu item is clicked', - }, - }, - }, - }, - tabs: { - type: 'complex', - complexType: { - name: 'HeaderTab[]', - properties: { - id: { - type: 'string', - required: true, - description: 'Unique identifier for the tab', - }, - label: { - type: 'string', - required: true, - description: 'Display text for the tab', - }, - href: { - type: 'string', - required: false, - description: 'URL to navigate to when tab is clicked', - }, - matchStrategy: { - type: "'exact' | 'prefix'", - required: false, - description: 'How to match the current route to highlight the tab', - }, - }, - }, - }, - onTabSelectionChange: { - type: 'enum', - values: ['(key: string) => void'], - }, - ...childrenPropDefs, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const usage = `import { Header } from '@backstage/ui'; - -
    `; - -export const defaultSnippet = `
    - } /> - } /> - } /> - - } -/>`; - -export const simple = `
    - } /> - } /> - } /> - - } -/>`; - -export const withTabs = ` -
    - } /> - } /> - } /> - - } - tabs={[ - { id: 'overview', label: 'Overview' }, - { id: 'checks', label: 'Checks' }, - { id: 'tracks', label: 'Tracks' }, - { id: 'campaigns', label: 'Campaigns' }, - { id: 'integrations', label: 'Integrations' }, - ]} -/> -`; - -export const withBreadcrumbs = `
    `; - -export const withHeaderPage = `
    -Custom action} -/>`; diff --git a/docs-ui/src/content/link.mdx b/docs-ui/src/content/link.mdx deleted file mode 100644 index 486c06b298..0000000000 --- a/docs-ui/src/content/link.mdx +++ /dev/null @@ -1,103 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { Snippet } from '@/components/Snippet'; -import { CodeBlock } from '@/components/CodeBlock'; -import { - MenuSnippet, - ButtonSnippet, - LinkSnippet, -} from '@/snippets/stories-snippets'; -import { - linkPropDefs, - linkUsageSnippet, - linkDefaultSnippet, - linkVariantsSnippet, - linkWeightsSnippet, - linkColorsSnippet, - linkRouterSnippet, - linkTruncateSnippet, -} from './link.props'; -import { PageTitle } from '@/components/PageTitle'; -import { Theming } from '@/components/Theming'; -import { ChangelogComponent } from '@/components/ChangelogComponent'; -import { LinkDefinition } from '../utils/definitions'; - - - -} - code={linkDefaultSnippet} -/> - -## Usage - - - -## API reference - - - -## Router Integration - -The `Link` component handles both internal and external navigation. It automatically detects whether the provided URL is internal (relative path) or external (absolute URL with protocol) and renders the appropriate element: - -- **Internal routes**: Uses `react-router-dom`'s `Link` component for client-side navigation -- **External URLs**: Renders a standard `` element for traditional navigation - - - -## Examples - -### Variants - -Here's a view when links have different variants. - -} - code={linkVariantsSnippet} -/> - -### Weights - -Here's a view when links have different weights. - -} - code={linkWeightsSnippet} -/> - -### Colors - -Here's a view when links have different colors. - -} - code={linkColorsSnippet} -/> - -### Truncate - -The `Link` component has a `truncate` prop that can be used to truncate the text. - -} - code={linkTruncateSnippet} -/> - - - - diff --git a/docs-ui/src/content/link.props.ts b/docs-ui/src/content/link.props.ts deleted file mode 100644 index 748e64cc15..0000000000 --- a/docs-ui/src/content/link.props.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { - classNamePropDefs, - stylePropDefs, - type PropDef, -} from '@/utils/propDefs'; - -export const linkPropDefs: Record = { - href: { - type: 'string', - }, - variant: { - type: 'enum', - values: [ - 'title-large', - 'title-medium', - 'title-small', - 'title-x-small', - 'body-large', - 'body-medium', - 'body-small', - 'body-x-small', - ], - default: 'body-medium', - responsive: true, - }, - weight: { - type: 'enum', - values: ['regular', 'bold'], - default: 'regular', - responsive: true, - }, - color: { - type: 'enum', - values: ['primary', 'secondary', 'danger', 'warning', 'success'], - default: 'primary', - responsive: true, - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const linkUsageSnippet = `import { Link } from '@backstage/ui'; - -Sign up for Backstage`; - -export const linkDefaultSnippet = `Sign up for Backstage`; - -export const linkVariantsSnippet = ` - ... - ... - ... - ... - ... - ... - ... - ... -`; - -export const linkWeightsSnippet = ` - - -`; - -export const linkColorsSnippet = ` - I am primary - I am secondary - I am danger - I am warning - I am success -`; - -export const linkRouterSnippet = `import { Link } from '@backstage/ui'; - -// Internal route -Home - -// External URL -Backstage -`; - -export const linkTruncateSnippet = `...`; diff --git a/docs-ui/src/content/menu.mdx b/docs-ui/src/content/menu.mdx deleted file mode 100644 index 9ede0885e2..0000000000 --- a/docs-ui/src/content/menu.mdx +++ /dev/null @@ -1,237 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { Snippet } from '@/components/Snippet'; -import { CodeBlock } from '@/components/CodeBlock'; -import { MenuSnippet } from '@/snippets/stories-snippets'; -import { - usage, - preview, - submenu, - icons, - sections, - separators, - links, - autocomplete, - autocompleteListbox, - autocompleteListboxMultiple, - menuTriggerPropDefs, - submenuTriggerPropDefs, - menuPropDefs, - menuListBoxPropDefs, - menuAutocompletePropDefs, - menuAutocompleteListboxPropDefs, - menuItemPropDefs, - menuListBoxItemPropDefs, - menuSectionPropDefs, - menuSeparatorPropDefs, -} from './menu.props'; -import { PageTitle } from '@/components/PageTitle'; -import { Theming } from '@/components/Theming'; -import { ChangelogComponent } from '@/components/ChangelogComponent'; -import { MenuDefinition } from '../utils/definitions'; - - - -} - code={preview} -/> - -## Usage - - - -### Triggers - -- `MenuTrigger` is a wrapper component that combines a button or other trigger element with a menu displayed in a popover. -- `SubmenuTrigger` is a wrapper component that combines a `MenuItem` with a menu displayed in a popover. - -### Containers - -- `Menu` is a container component that contains a list of menu items or sections. -- `MenuListBox` is a container component that contains a list of menu items or sections. -- `MenuAutocomplete` is a container component that contains a list of menu items or sections. -- `MenuAutocompleteListbox` is a container component that contains a list of menu items or sections. - -### Items - -- `MenuItem` is an individual interactive item in the menu. -- `MenuListBoxItem` is an individual interactive item in the menu list box. - -### Separators - -- `MenuSeparator` is a component that renders a horizontal line to separate menu items. -- `MenuSection` is a component that renders a section in the menu. - -## API reference - -### MenuTrigger - -`MenuTrigger` accepts exactly two children: the first child should be the trigger element, and second child should be -one of the menu containers containing the menu. - - - -### SubmenuTrigger - -The `SubmenuTrigger` accepts exactly two children: the first child should be the `MenuItem` which triggers opening -of the submenu, and second child should be one of the menu containers containing the submenu. - - - -### Menu - -`Menu` is a container component that contains a list of menu items or sections. - - - -### MenuListBox - -`MenuListBox` is a container component that contains a list of menu items or sections. - - - -### MenuAutocomplete - -`MenuAutocomplete` is a container component that contains a list of menu items or sections. - - - -### MenuAutocompleteListbox - -`MenuAutocompleteListbox` is a container component that contains a list of menu items or sections. - - - -### MenuItem - -`MenuItem` is an individual interactive item in the menu. - - - -### MenuListBoxItem - -`MenuListBoxItem` is an individual interactive item in the menu list box. - - - -### MenuSection - -`MenuSection` is a component that renders a section in the menu. - - - -### MenuSeparator - -`MenuSeparator` is a component that renders a horizontal line to separate menu items. - - - -## Examples - -### Nested navigation - -You can nest menus to create a more complex navigation structure. It is important to use the `placement` prop to ensure -the submenu is displayed in the correct position. The best practice is to use the `right top` placement for the submenu. - -} - code={submenu} -/> - -### With icons - -You can use the `iconStart` prop to add an icon to the menu item. - -} - code={icons} -/> - -### With links - -You can use the `href` prop to add a link to the menu item. This is using our router provider under the hood -to work for both internal and external links. - -} - code={links} -/> - -### With sections - -You can use the `MenuSection` component to add a section to the menu. - -} - code={sections} -/> - -### With separators - -You can use the `MenuSeparator` component to add a separator to the menu. - -} - code={separators} -/> - -### With autocomplete - -You can use the `MenuAutocomplete` component to add a autocomplete to the menu. - -} - code={autocomplete} -/> - -### With list box - -You can use the `MenuListBox` component to add a list box to the menu. - -} - code={autocompleteListbox} -/> - -### With list box with multiple selection - -You can use the `MenuListBox` component to add a list box to the menu. You can also use the `selectionMode` prop to -allow multiple selection. - -} - code={autocompleteListboxMultiple} -/> - - - - diff --git a/docs-ui/src/content/menu.props.ts b/docs-ui/src/content/menu.props.ts deleted file mode 100644 index 2c7d76dd5d..0000000000 --- a/docs-ui/src/content/menu.props.ts +++ /dev/null @@ -1,372 +0,0 @@ -import { - classNamePropDefs, - stylePropDefs, - type PropDef, -} from '@/utils/propDefs'; - -const placementValues = [ - 'bottom', - 'bottom left', - 'bottom right', - 'bottom start', - 'bottom end', - 'top', - 'top left', - 'top right', - 'top start', - 'top end', - 'left', - 'left top', - 'left bottom', - 'start', - 'start top', - 'start bottom', - 'right', - 'right top', - 'right bottom', - 'end', - 'end top', - 'end bottom', -]; - -export const menuTriggerPropDefs: Record = { - isOpen: { - type: 'boolean', - }, - defaultOpen: { - type: 'boolean', - }, - onOpenChange: { - type: 'enum', - values: ['(isOpen: boolean) => void'], - }, -}; - -export const submenuTriggerPropDefs: Record = { - delay: { - type: 'number', - default: '200', - }, -}; - -export const menuPropDefs: Record = { - disabledKeys: { - type: 'enum', - values: ['Iterable'], - }, - selectionMode: { - type: 'enum', - values: ['none', 'single', 'multiple'], - }, - selectedKeys: { - type: 'enum', - values: ['all', 'Iterable'], - }, - defaultSelectedKeys: { - type: 'enum', - values: ['all', 'Iterable'], - }, - placement: { - type: 'enum', - values: placementValues, - }, - virtualized: { - type: 'boolean', - default: 'false', - }, - maxWidth: { - type: 'number', - }, - maxHeight: { - type: 'number', - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const menuListBoxPropDefs: Record = { - disabledKeys: { - type: 'enum', - values: ['Iterable'], - }, - selectionMode: { - type: 'enum', - values: ['none', 'single', 'multiple'], - }, - selectedKeys: { - type: 'enum', - values: ['all', 'Iterable'], - }, - defaultSelectedKeys: { - type: 'enum', - values: ['all', 'Iterable'], - }, - placement: { - type: 'enum', - values: placementValues, - }, - virtualized: { - type: 'boolean', - default: 'false', - }, - maxWidth: { - type: 'number', - }, - maxHeight: { - type: 'number', - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const menuAutocompletePropDefs: Record = { - placement: { - type: 'enum', - values: placementValues, - }, - virtualized: { - type: 'boolean', - default: 'false', - }, - maxWidth: { - type: 'number', - }, - maxHeight: { - type: 'number', - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const menuAutocompleteListboxPropDefs: Record = { - placement: { - type: 'enum', - values: placementValues, - }, - virtualized: { - type: 'boolean', - default: 'false', - }, - maxWidth: { - type: 'number', - }, - maxHeight: { - type: 'number', - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const menuItemPropDefs: Record = { - id: { - type: 'enum', - values: ['Key'], - }, - value: { - type: 'string', - }, - textValue: { - type: 'string', - }, - isDisabled: { - type: 'boolean', - }, - href: { - type: 'string', - }, - onAction: { - type: 'enum', - values: ['(event) => void'], - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const menuListBoxItemPropDefs: Record = { - id: { - type: 'enum', - values: ['Key'], - }, - value: { - type: 'string', - }, - textValue: { - type: 'string', - }, - isDisabled: { - type: 'boolean', - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const menuSectionPropDefs: Record = { - title: { - type: 'string', - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const menuSeparatorPropDefs: Record = { - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const usage = `import { MenuTrigger, Menu, MenuItem } from '@backstage/ui'; - - - - - Apple - Banana - Blueberry - - - Vegetables - - Carrot - Tomato - Potato - - - -`; - -export const preview = ` - - - {options.map(option => ( - {option.label} - ))} - -`; - -export const submenu = ` - - - Edit - Duplicate - - Submenu - - Edit - Duplicate - Rename - - Share - Move - - }>Feedback - - - -`; - -export const icons = ` - - - }>Copy - }>Rename - }>Send feedback - -`; - -export const sections = ` - - - - }>Profile - }>Settings - - - }>Help Center - }> - Contact Support - - }>Feedback - - -`; - -export const separators = ` - - - Edit - Duplicate - Rename - - Share - Move - - }>Feedback - -`; - -export const links = ` - - - Internal link - - External link - - Email link - -`; - -export const autocomplete = ` - - - Create new file... - Create new folder... - Assign to... - Assign to me - Change status... - Change priority... - Add label... - Remove label... - -`; - -export const autocompleteListbox = `const [selected, setSelected] = useState( - new Set(['blueberry']), -); - - - Selected: {Array.from(selected).join(', ')} - - - - {options.map(option => ( - - {option.label} - - ))} - - -`; - -export const autocompleteListboxMultiple = `const [selected, setSelected] = useState( - new Set(['blueberry', 'cherry']), -); - - - Selected: {Array.from(selected).join(', ')} - - - - {options.map(option => ( - - {option.label} - - ))} - - -`; diff --git a/docs-ui/src/content/password-field.mdx b/docs-ui/src/content/password-field.mdx deleted file mode 100644 index 8dda2b4f2a..0000000000 --- a/docs-ui/src/content/password-field.mdx +++ /dev/null @@ -1,65 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { Snippet } from '@/components/Snippet'; -import { PasswordFieldSnippet } from '@/snippets/stories-snippets'; -import { - inputPropDefs, - passwordFieldUsageSnippet, - passwordFieldDefaultSnippet, - passwordFieldSizesSnippet, - passwordFieldDescriptionSnippet, -} from './password-field.props'; -import { PageTitle } from '@/components/PageTitle'; -import { Theming } from '@/components/Theming'; -import { ChangelogComponent } from '@/components/ChangelogComponent'; -import { CodeBlock } from '@/components/CodeBlock'; -import { PasswordFieldDefinition } from '../utils/definitions'; - - - -} - code={passwordFieldDefaultSnippet} -/> - -## Usage - - - -## API reference - - - -## Examples - -### Sizes - -We support two different sizes: `small`, `medium`. - -} - code={passwordFieldSizesSnippet} -/> - -### With description - -Here's a simple PasswordField with a description. - -} - code={passwordFieldDescriptionSnippet} -/> - - - - diff --git a/docs-ui/src/content/password-field.props.ts b/docs-ui/src/content/password-field.props.ts deleted file mode 100644 index 493405b5fd..0000000000 --- a/docs-ui/src/content/password-field.props.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { - classNamePropDefs, - stylePropDefs, - type PropDef, -} from '@/utils/propDefs'; - -export const inputPropDefs: Record = { - size: { - type: 'enum', - values: ['small', 'medium'], - default: 'small', - responsive: true, - }, - label: { - type: 'string', - }, - icon: { - type: 'enum', - values: ['ReactNode'], - }, - description: { - type: 'string', - }, - name: { - type: 'string', - required: true, - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const passwordFieldUsageSnippet = `import { PasswordField } from '@backstage/ui'; - -`; - -export const passwordFieldDefaultSnippet = ``; - -export const passwordFieldSizesSnippet = ` - } /> - } /> -`; - -export const passwordFieldDescriptionSnippet = ``; diff --git a/docs-ui/src/content/radio-group.mdx b/docs-ui/src/content/radio-group.mdx deleted file mode 100644 index ba9352c026..0000000000 --- a/docs-ui/src/content/radio-group.mdx +++ /dev/null @@ -1,105 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { Snippet } from '@/components/Snippet'; -import { RadioGroupSnippet } from '@/snippets/stories-snippets'; -import { - radioGroupPropDefs, - radioGroupUsageSnippet, - radioGroupDefaultSnippet, - radioGroupDescriptionSnippet, - radioGroupHorizontalSnippet, - radioGroupDisabledSnippet, - radioGroupDisabledSingleSnippet, - radioGroupValidationSnippet, - radioGroupReadOnlySnippet, -} from './radio-group.props'; -import { PageTitle } from '@/components/PageTitle'; -import { Theming } from '@/components/Theming'; -import { ChangelogComponent } from '@/components/ChangelogComponent'; -import { CodeBlock } from '@/components/CodeBlock'; -import { RadioGroupDefinition } from '../utils/definitions'; - - - -} - code={radioGroupDefaultSnippet} -/> - -## Usage - - - -## API reference - - - -## Examples - -### Horizontal - -Here's a simple TextField with a description. - -} - code={radioGroupHorizontalSnippet} -/> - -### Disabled - -You can disable the entire radio group by adding the `isDisabled` prop to the `RadioGroup` component. - -} - code={radioGroupDisabledSnippet} -/> - -### Disabled Single radio - -You can disable a single radio by adding the `isDisabled` prop to the `Radio` component. - -} - code={radioGroupDisabledSingleSnippet} -/> - -### Validation - -Here's an example of a radio group with errors. - -} - code={radioGroupValidationSnippet} -/> - -### Read only - -You can make the radio group read only by adding the `isReadOnly` prop to the `RadioGroup` component. - -} - code={radioGroupReadOnlySnippet} -/> - - - - diff --git a/docs-ui/src/content/radio-group.props.ts b/docs-ui/src/content/radio-group.props.ts deleted file mode 100644 index 3ed08f1053..0000000000 --- a/docs-ui/src/content/radio-group.props.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { - classNamePropDefs, - stylePropDefs, - type PropDef, -} from '@/utils/propDefs'; - -export const radioGroupPropDefs: Record = { - size: { - type: 'enum', - values: ['small', 'medium'], - default: 'small', - responsive: true, - }, - label: { - type: 'string', - }, - icon: { - type: 'enum', - values: ['ReactNode'], - }, - description: { - type: 'string', - }, - name: { - type: 'string', - required: true, - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const radioGroupUsageSnippet = `import { RadioGroup } from '@backstage/ui'; - -`; - -export const radioGroupDefaultSnippet = ``; - -export const radioGroupDescriptionSnippet = ``; - -export const radioGroupHorizontalSnippet = ``; - -export const radioGroupDisabledSnippet = ` - Bulbasaur - Charmander - Squirtle -`; - -export const radioGroupDisabledSingleSnippet = ` - Bulbasaur - Charmander - Squirtle -`; - -export const radioGroupValidationSnippet = ` (value === \'charmander\' ? \'Nice try!\' : null)> - Bulbasaur - Charmander - Squirtle -`; - -export const radioGroupReadOnlySnippet = ` - Bulbasaur - Charmander - Squirtle -`; diff --git a/docs-ui/src/content/search-field.mdx b/docs-ui/src/content/search-field.mdx deleted file mode 100644 index 6b33feea42..0000000000 --- a/docs-ui/src/content/search-field.mdx +++ /dev/null @@ -1,78 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { Snippet } from '@/components/Snippet'; -import { SearchFieldSnippet } from '@/snippets/stories-snippets'; -import { - searchFieldPropDefs, - searchFieldUsageSnippet, - searchFieldDefaultSnippet, - searchFieldSizesSnippet, - searchFieldDescriptionSnippet, - searchFieldCollapsibleSnippet, -} from './search-field.props'; -import { PageTitle } from '@/components/PageTitle'; -import { Theming } from '@/components/Theming'; -import { ChangelogComponent } from '@/components/ChangelogComponent'; -import { CodeBlock } from '@/components/CodeBlock'; -import { SearchFieldDefinition } from '../utils/definitions'; - - - -} - code={searchFieldDefaultSnippet} -/> - -## Usage - - - -## API reference - - - -## Examples - -### Sizes - -We support two different sizes: `small`, `medium`. - -} - code={searchFieldSizesSnippet} -/> - -### With description - -Here's a simple SearchField with a description. - -} - code={searchFieldDescriptionSnippet} -/> - -### Collapsible - -You can make the SearchField collapsible by setting the `startCollapsed` prop to `true`. - -} - code={searchFieldCollapsibleSnippet} -/> - - - - diff --git a/docs-ui/src/content/search-field.props.ts b/docs-ui/src/content/search-field.props.ts deleted file mode 100644 index b11ae3931c..0000000000 --- a/docs-ui/src/content/search-field.props.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { - classNamePropDefs, - stylePropDefs, - type PropDef, -} from '@/utils/propDefs'; - -export const searchFieldPropDefs: Record = { - size: { - type: 'enum', - values: ['small', 'medium'], - default: 'small', - responsive: true, - }, - label: { - type: 'string', - }, - icon: { - type: 'enum', - values: ['ReactNode'], - }, - description: { - type: 'string', - }, - name: { - type: 'string', - required: true, - }, - startCollapsed: { - type: 'boolean', - default: 'false', - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const searchFieldUsageSnippet = `import { SearchField } from '@backstage/ui'; - -`; - -export const searchFieldDefaultSnippet = ``; - -export const searchFieldSizesSnippet = ` - } /> - } /> -`; - -export const searchFieldDescriptionSnippet = ``; - -export const searchFieldCollapsibleSnippet = ``; diff --git a/docs-ui/src/content/select.mdx b/docs-ui/src/content/select.mdx deleted file mode 100644 index 436129d749..0000000000 --- a/docs-ui/src/content/select.mdx +++ /dev/null @@ -1,138 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { Snippet } from '@/components/Snippet'; -import { CodeBlock } from '@/components/CodeBlock'; -import { SelectSnippet } from '@/snippets/stories-snippets'; -import { - selectPropDefs, - selectUsageSnippet, - selectDefaultSnippet, - selectDescriptionSnippet, - selectSizesSnippet, - selectDisabledSnippet, - selectResponsiveSnippet, - selectIconSnippet, - selectSearchableSnippet, - selectMultipleSnippet, - selectSearchableMultipleSnippet, -} from './select.props'; -import { PageTitle } from '@/components/PageTitle'; -import { Theming } from '@/components/Theming'; -import { ChangelogComponent } from '@/components/ChangelogComponent'; -import { SelectDefinition } from '../utils/definitions'; - - - -} - code={selectDefaultSnippet} -/> - -## Usage - - - -## API reference - - - -## Examples - -### With Label and description - -Select component with label and description. - -} - code={selectDescriptionSnippet} -/> - -### Sizes - -Here's a view when the selects have different sizes. - -} - code={selectSizesSnippet} -/> - -### With Icon - -Here's a view when the select has an icon. - -} -code={selectIconSnippet} -/> - -### Disabled - -Here's a view when the select is disabled. - -} - code={selectDisabledSnippet} -/> - -### Searchable - -Here's a view when the select has searchable filtering. - -} - code={selectSearchableSnippet} -/> - -### Multiple Selection - -Here's a view when the select allows multiple selections. - -} - code={selectMultipleSnippet} -/> - -### Searchable with Multiple Selection - -Here's a view when the select combines search and multiple selection. - -} - code={selectSearchableMultipleSnippet} -/> - -### Responsive - -Here's a view when the select is responsive. - - - - - - diff --git a/docs-ui/src/content/skeleton.props.ts b/docs-ui/src/content/skeleton.props.ts deleted file mode 100644 index ab79268d15..0000000000 --- a/docs-ui/src/content/skeleton.props.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { - classNamePropDefs, - stylePropDefs, - type PropDef, -} from '@/utils/propDefs'; - -export const skeletonPropDefs: Record = { - width: { - type: 'number', - default: '80', - responsive: false, - }, - height: { - type: 'number', - default: '24', - responsive: false, - }, - rounded: { - type: 'boolean', - default: 'false', - responsive: false, - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const skeletonUsageSnippet = `import { Flex, Skeleton } from '@backstage/ui'; - - - - - -`; - -export const skeletonDefaultSnippet = ` - - - -`; - -export const skeletonDemo1Snippet = ` - - - - - - - - - - -`; - -export const skeletonDemo2Snippet = ` - - - -`; diff --git a/docs-ui/src/content/switch.mdx b/docs-ui/src/content/switch.mdx deleted file mode 100644 index 965cc7832b..0000000000 --- a/docs-ui/src/content/switch.mdx +++ /dev/null @@ -1,47 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { Snippet } from '@/components/Snippet'; -import { CodeBlock } from '@/components/CodeBlock'; -import { SwitchSnippet } from '@/snippets/stories-snippets'; -import { switchPropDefs, snippetUsage } from './switch.props'; -import { PageTitle } from '@/components/PageTitle'; -import { Theming } from '@/components/Theming'; -import { ChangelogComponent } from '@/components/ChangelogComponent'; -import { SwitchDefinition } from '../utils/definitions'; - - - -} - code={``} -/> - -## Usage - - - -## API reference - - - -## Examples - -### Disabled - -A switch can be disabled using the `isDisabled` prop. - -} - code={``} -/> - - - - diff --git a/docs-ui/src/content/switch.props.ts b/docs-ui/src/content/switch.props.ts deleted file mode 100644 index 5576436102..0000000000 --- a/docs-ui/src/content/switch.props.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { classNamePropDefs, stylePropDefs } from '@/utils/propDefs'; -import type { PropDef } from '@/utils/propDefs'; - -export const switchPropDefs: Record = { - autoFocus: { - type: 'boolean', - }, - defaultSelected: { - type: 'boolean', - }, - ...classNamePropDefs, - isDisabled: { - type: 'boolean', - }, - isReadOnly: { - type: 'boolean', - }, - isSelected: { - type: 'boolean', - }, - label: { - type: 'string', - }, - name: { - type: 'string', - }, - onChange: { - type: 'enum', - values: ['(isSelected: boolean) => void'], - }, - onFocus: { - type: 'enum', - values: ['(e: FocusEvent) => void'], - }, - onBlur: { - type: 'enum', - values: ['(e: FocusEvent) => void'], - }, - onFocusChange: { - type: 'enum', - values: ['(isFocused: boolean) => void'], - }, - onKeyDown: { - type: 'enum', - values: ['(e: KeyboardEvent) => void'], - }, - onKeyUp: { - type: 'enum', - values: ['(e: KeyboardEvent) => void'], - }, - onHoverStart: { - type: 'enum', - values: ['(e: HoverEvent) => void'], - }, - onHoverEnd: { - type: 'enum', - values: ['(e: HoverEvent) => void'], - }, - onHoverChange: { - type: 'enum', - values: ['(isHovered: boolean) => void'], - }, - ...stylePropDefs, - value: { - type: 'string', - }, -}; - -export const snippetUsage = `import { Switch } from '@backstage/ui'; - -`; diff --git a/docs-ui/src/content/table.mdx b/docs-ui/src/content/table.mdx deleted file mode 100644 index d1a190e9dd..0000000000 --- a/docs-ui/src/content/table.mdx +++ /dev/null @@ -1,134 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { Snippet } from '@/components/Snippet'; -import { CodeBlock } from '@/components/CodeBlock'; -import { TableSnippet } from '@/snippets/stories-snippets'; -import { - tablePropDefs, - tableHeaderPropDefs, - tableBodyPropDefs, - tablePaginationPropDefs, - tableUsageSnippet, - tableBasicSnippet, - tableRowClickSnippet, - tableHybridSnippet, - tableCellInteractionsSnippet, - tablePaginationSnippet, - tableSelectionSnippet, - tableSortingSnippet, - columnPropDefs, - rowPropDefs, - cellPropDefs, -} from './table.props'; -import { ChangelogComponent } from '@/components/ChangelogComponent'; -import { PageTitle } from '@/components/PageTitle'; -import { Theming } from '@/components/Theming'; -import { TableDefinition } from '../utils/definitions'; - - - -} - code={tableBasicSnippet} -/> - -## Usage - - - -## API reference - -### Table - -The main table component that renders data in a structured grid format. - - - -### TableHeader - -The header section of the table that contains the column definitions. - - - -### Column - -A column definition that describes how a column should be rendered. - - - -### TableBody - -The body section of the table that contains the rows. - - - -### Row - -A row definition that describes how a row should be rendered. - - - -### Cell - -A cell definition that describes how a cell should be rendered. - - - -### TablePagination - -A pagination component designed to work with the Table component. - - - -## Examples - -### Basic Table - -Coming soon. - -### Row selection - -Coming soon. - -### Row Clicks - -Coming soon. - -### Pagination - -Coming soon. - -### Sorting - -Coming soon. - -### Asynchronous loading - -Coming soon. - -### Empty state - -Coming soon. - -### Column resizing - -This feature is not available yet — let us know if you'd like us to explore it! - -### Column reordering - -This feature is not available yet — let us know if you'd like us to explore it! - -### Column pinning - -This feature is not available yet — let us know if you'd like us to explore it! - -### Column visibility - -This feature is not available yet — let us know if you'd like us to explore it! - - - - diff --git a/docs-ui/src/content/table.props.ts b/docs-ui/src/content/table.props.ts deleted file mode 100644 index 443526182e..0000000000 --- a/docs-ui/src/content/table.props.ts +++ /dev/null @@ -1,328 +0,0 @@ -import { - classNamePropDefs, - stylePropDefs, - type PropDef, -} from '@/utils/propDefs'; - -export const tablePropDefs: Record = { - selectionBehavior: { - type: 'enum', - values: ['toggle', 'replace'], - default: 'toggle', - description: 'How multiple selection should behave in the collection.', - }, - disabledBehavior: { - type: 'enum', - values: ['selection', 'all'], - default: 'selection', - description: - 'Whether disabledKeys applies to all interactions, or only selection.', - }, - disabledKeys: { - type: 'enum', - values: ['Iterable'], - description: 'A list of row keys to disable.', - }, - selectionMode: { - type: 'enum', - values: ['single', 'multiple'], - description: 'The type of selection that is allowed in the collection.', - }, - selectedKeys: { - type: 'enum', - values: ['all', 'Iterable'], - description: 'The currently selected keys in the collection (controlled).', - }, - defaultSelectedKeys: { - type: 'enum', - values: ['all', 'Iterable'], - description: 'The initial selected keys in the collection (uncontrolled).', - }, - onRowAction: { - type: 'enum', - values: ['(key: Key) => void'], - description: - 'Handler that is called when a user performs an action on the row.', - }, - onSelectionChange: { - type: 'enum', - values: ['(keys: Selection) => void'], - description: 'Handler that is called when the selection changes.', - }, - onSortChange: { - type: 'enum', - values: ['(descriptor: SortDescriptor) => any'], - description: - 'Handler that is called when the sorted column or direction changes.', - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const tableHeaderPropDefs: Record = { - onHoverStart: { - type: 'enum', - values: ['(e: HoverEvent) => void'], - description: 'Handler that is called when a hover interaction starts.', - }, - onHoverEnd: { - type: 'enum', - values: ['(e: HoverEvent) => void'], - description: 'Handler that is called when a hover interaction ends.', - }, - onHoverChange: { - type: 'enum', - values: ['(isHovering: boolean) => void'], - description: 'Handler that is called when the hover state changes.', - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const columnPropDefs: Record = { - id: { - type: 'enum', - values: ['Key'], - description: 'The unique id of the column.', - }, - allowsSorting: { - type: 'boolean', - description: 'Whether the column allows sorting.', - }, - isRowHeader: { - type: 'boolean', - description: - 'Whether a column is a row header and should be announced by assistive technology during row navigation.', - }, - textValue: { - type: 'string', - description: - "A string representation of the column's contents, used for accessibility announcements.", - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const tableBodyPropDefs: Record = { - renderEmptyState: { - type: 'enum', - values: ['(props) => ReactNode'], - description: - 'Provides content to display when there are no rows in the table.', - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const rowPropDefs: Record = { - textValue: { - type: 'string', - description: - "A string representation of the row's contents, used for accessibility announcements.", - }, - isDisabled: { - type: 'boolean', - description: 'Whether the row is disabled.', - }, - id: { - type: 'enum', - values: ['Key'], - description: 'The unique id of the row.', - }, - href: { - type: 'string', - description: 'The URL to navigate to when the row is clicked.', - }, - hrefLang: { - type: 'string', - description: - 'The language of the URL to navigate to when the row is clicked.', - }, - target: { - type: 'string', - description: - 'The target of the URL to navigate to when the row is clicked.', - }, - rel: { - type: 'string', - description: - 'The relationship of the URL to navigate to when the row is clicked.', - }, - onAction: { - type: 'enum', - values: ['() => void'], - description: - "Handler that is called when a user performs an action on the row. The exact user event depends on the collection's selectionBehavior prop and the interaction modality.", - }, - onHoverStart: { - type: 'enum', - values: ['(e: HoverEvent) => void'], - description: 'Handler that is called when a hover interaction starts.', - }, - onHoverEnd: { - type: 'enum', - values: ['(e: HoverEvent) => void'], - description: 'Handler that is called when a hover interaction ends.', - }, - onHoverChange: { - type: 'enum', - values: ['(isHovering: boolean) => void'], - description: 'Handler that is called when the hover state changes.', - }, - onPress: { - type: 'enum', - values: ['(e: PressEvent) => void'], - description: - 'Handler that is called when the press is released over the target.', - }, - onPressStart: { - type: 'enum', - values: ['(e: PressEvent) => void'], - description: 'Handler that is called when a press interaction starts.', - }, - onPressEnd: { - type: 'enum', - values: ['(e: PressEvent) => void'], - description: - 'Handler that is called when a press interaction ends, either over the target or when the pointer leaves the target.', - }, - onPressChange: { - type: 'enum', - values: ['(isPressed: boolean) => void'], - description: 'Handler that is called when the press state changes.', - }, - onPressUp: { - type: 'enum', - values: ['(e: PressEvent) => void'], - description: - 'Handler that is called when a press is released over the target, regardless of whether it started on the target or not.', - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const cellPropDefs: Record = { - id: { - type: 'enum', - values: ['Key'], - description: 'The unique id of the cell.', - }, - textValue: { - type: 'string', - description: - "A string representation of the cell's contents, used for features like typeahead.", - }, - leadingIcon: { - type: 'enum', - values: ['ReactNode'], - description: 'Optional icon to display before the cell content.', - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const tablePaginationPropDefs: Record = { - offset: { - type: 'number', - description: 'The current offset (starting index) for pagination.', - }, - pageSize: { - type: 'number', - description: 'The number of items per page.', - }, - setOffset: { - type: 'enum', - values: ['(offset: number) => void'], - description: 'Handler that is called when the offset changes.', - }, - setPageSize: { - type: 'enum', - values: ['(pageSize: number) => void'], - description: 'Handler that is called when the page size changes.', - }, - rowCount: { - type: 'number', - description: 'The total number of rows in the table.', - }, - onNextPage: { - type: 'enum', - values: ['() => void'], - description: 'Handler that is called when the next page is requested.', - }, - onPreviousPage: { - type: 'enum', - values: ['() => void'], - description: 'Handler that is called when the previous page is requested.', - }, - onPageSizeChange: { - type: 'enum', - values: ['(pageSize: number) => void'], - description: 'Handler that is called when the page size changes.', - }, - showPageSizeOptions: { - type: 'boolean', - description: 'Whether to show the page size options.', - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const tableUsageSnippet = `import { Cell, ..., TableHeader, TablePagination } from '@backstage/ui'; - -
    + {children}
    - - - - - - - - - -
    -`; - -export const tableBasicSnippet = `import { Table, TableHeader, Column, TableBody, Row, Cell, CellProfile, TablePagination, useTable } from '@backstage/ui'; - -const data = [ - { - name: 'The Beatles', - image: 'https://upload.wikimedia.org/wikipedia/en/thumb/4/42/Beatles_-...jpg', - genre: 'Rock, Pop, Psychedelic Rock', - yearFormed: 1960, - albums: 13 - }, - // ... more data -]; - -// Uncontrolled pagination (easiest) -const { data: paginatedData, paginationProps } = useTable({ - data, - pagination: { - defaultPageSize: 5, - }, -}); - - - - Band name - Genre - Year formed - Albums - - - {paginatedData?.map(item => ( - - - - - - - ))} - -
    -`; diff --git a/docs-ui/src/content/tabs.mdx b/docs-ui/src/content/tabs.mdx deleted file mode 100644 index 1fc7448eba..0000000000 --- a/docs-ui/src/content/tabs.mdx +++ /dev/null @@ -1,85 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { TabsSnippet } from '@/snippets/stories-snippets'; -import { Snippet } from '@/components/Snippet'; -import { CodeBlock } from '@/components/CodeBlock'; -import { - tabsPropDefs, - tabPropDefs, - tabsUsageSnippet, - tabsDefaultSnippet, - tabsWithTabPanelsSnippet, - tabsWithLinksSnippet, - tabsWithDeeplyNestedRoutesSnippet, -} from './tabs.props'; -import { PageTitle } from '@/components/PageTitle'; -import { Theming } from '@/components/Theming'; -import { TabsDefinition } from '../utils/definitions'; -import { ChangelogComponent } from '@/components/ChangelogComponent'; - - - -} - code={tabsDefaultSnippet} -/> - -## Usage - - - -## API reference - -### Tabs - -Groups the tabs and the corresponding panels. Renders a `
    ` element. - - - -### Tab - -An individual interactive tab button that toggles the corresponding panel. Renders a ` - I am a tooltip -`; - -export const tooltipDefaultSnippet = ` - - I am a tooltip -`; diff --git a/docs-ui/src/css/globals.css b/docs-ui/src/css/globals.css index dcc348a9ee..407138fd61 100644 --- a/docs-ui/src/css/globals.css +++ b/docs-ui/src/css/globals.css @@ -17,6 +17,10 @@ --surface-1: #f4f4f4; --code-bg: #3e444f; --code-title: #505865; + --badge-red-bg: color(display-p3 0.831 0.184 0.012/0.091); + --badge-red-color: color(display-p3 0.755 0.259 0.152); + --badge-gray-bg: #dee3ec; + --badge-gray-color: #35363d; } [data-theme-mode='dark'] { diff --git a/docs-ui/src/css/mdx.module.css b/docs-ui/src/css/mdx.module.css index 7cc2422ecd..ea6e3c6549 100644 --- a/docs-ui/src/css/mdx.module.css +++ b/docs-ui/src/css/mdx.module.css @@ -27,18 +27,24 @@ line-height: 1.5; margin-top: 0; margin-bottom: 1rem; - color: var(--secondary); + color: var(--primary); } .ul { - margin: 0; + margin-top: 0; + margin-bottom: 1rem; padding-left: 2rem; } .li { - color: var(--secondary); + color: var(--primary); margin-bottom: 0.5rem; line-height: 1.5; + padding-left: 0.4rem; +} + +.li::marker { + color: var(--text-secondary); } .a { @@ -73,3 +79,34 @@ .anchorLink:hover .anchorHash { opacity: 1; } + +.table { + width: 100%; + border-collapse: collapse; + margin-bottom: 1rem; + font-size: 0.875rem; +} + +.th { + text-align: left; + padding: 0.75rem 1rem; + border-bottom: 1px solid var(--border); + color: var(--primary); + font-weight: 500; +} + +.td { + padding: 0.75rem 1rem; + border-bottom: 1px solid var(--border); + color: var(--secondary); +} + +.td code { + font-family: var(--font-mono); + background-color: var(--bg); + padding: 0.2rem 0.375rem; + border-radius: 0.25rem; + color: var(--secondary); + border: 1px solid var(--border); + font-size: 0.8125rem; +} diff --git a/docs-ui/src/css/theme-backstage.css b/docs-ui/src/css/theme-backstage.css new file mode 100644 index 0000000000..9527f243b9 --- /dev/null +++ b/docs-ui/src/css/theme-backstage.css @@ -0,0 +1 @@ +@import '../../../packages/ui/src/css/styles.css'; diff --git a/docs-ui/src/css/theme-spotify.css b/docs-ui/src/css/theme-spotify.css new file mode 100644 index 0000000000..ec1aa8bb29 --- /dev/null +++ b/docs-ui/src/css/theme-spotify.css @@ -0,0 +1 @@ +@import '../../../.storybook/themes/spotify.css'; diff --git a/docs-ui/src/mdx-components.tsx b/docs-ui/src/mdx-components.tsx index bae798bf7c..75959f2e47 100644 --- a/docs-ui/src/mdx-components.tsx +++ b/docs-ui/src/mdx-components.tsx @@ -81,7 +81,7 @@ export const formattedMDXComponents: MDXComponents = { backgroundColor: 'var(--bg)', padding: '0.2rem 0.375rem', borderRadius: '0.25rem', - color: 'var(--secondary)', + color: 'var(--primary)', border: '1px solid var(--border)', fontSize: '0.875rem', }} @@ -97,6 +97,11 @@ export const formattedMDXComponents: MDXComponents = { {...(rest as Omit)} /> ), + table: ({ children }) => ( + {children as ReactNode}
    + ), + th: ({ children }) => {children as ReactNode}, + td: ({ children }) => {children as ReactNode}, }; export function useMDXComponents(components: MDXComponents): MDXComponents { diff --git a/docs-ui/src/snippets/code-snippets.ts b/docs-ui/src/snippets/code-snippets.ts index 1a22a24340..30d036461f 100644 --- a/docs-ui/src/snippets/code-snippets.ts +++ b/docs-ui/src/snippets/code-snippets.ts @@ -4,8 +4,8 @@ export const customTheme = `:root { --bui-font-regular: system-ui; --bui-font-weight-regular: 400; --bui-font-weight-bold: 600; - --bui-bg: #f8f8f8; - --bui-bg-surface-1: #fff; + --bui-bg-app: #f8f8f8; + --bui-bg-neutral-1: #fff; /* ... other CSS variables */ /* Add your custom components styles here */ @@ -19,8 +19,8 @@ export const customTheme = `:root { --bui-font-regular: system-ui; --bui-font-weight-regular: 400; --bui-font-weight-bold: 600; - --bui-bg: #f8f8f8; - --bui-bg-surface-1: #fff; + --bui-bg-app: #f8f8f8; + --bui-bg-neutral-1: #fff; /* ... other CSS variables */ /* Add your custom components styles here */ diff --git a/docs-ui/src/snippets/stories-snippets.tsx b/docs-ui/src/snippets/stories-snippets.tsx deleted file mode 100644 index 0cb6119ce6..0000000000 --- a/docs-ui/src/snippets/stories-snippets.tsx +++ /dev/null @@ -1,80 +0,0 @@ -'use client'; - -import { composeStories } from '@storybook/react'; -import * as BoxStories from '../../../packages/ui/src/components/Box/Box.stories'; -import * as ButtonStories from '../../../packages/ui/src/components/Button/Button.stories'; -import * as ButtonIconStories from '../../../packages/ui/src/components/ButtonIcon/ButtonIcon.stories'; -import * as ButtonLinkStories from '../../../packages/ui/src/components/ButtonLink/ButtonLink.stories'; -import * as CheckboxStories from '../../../packages/ui/src/components/Checkbox/Checkbox.stories'; -import * as ContainerStories from '../../../packages/ui/src/components/Container/Container.stories'; -import * as GridStories from '../../../packages/ui/src/components/Grid/Grid.stories'; -import * as TextFieldStories from '../../../packages/ui/src/components/TextField/TextField.stories'; -import * as TextStories from '../../../packages/ui/src/components/Text/Text.stories'; -import * as FlexStories from '../../../packages/ui/src/components/Flex/Flex.stories'; -import * as SelectStories from '../../../packages/ui/src/components/Select/Select.stories'; -import * as MenuStories from '../../../packages/ui/src/components/Menu/Menu.stories'; -import * as LinkStories from '../../../packages/ui/src/components/Link/Link.stories'; -import * as AvatarStories from '../../../packages/ui/src/components/Avatar/Avatar.stories'; -import * as AccordionStories from '../../../packages/ui/src/components/Accordion/Accordion.stories'; -import * as DialogStories from '../../../packages/ui/src/components/Dialog/Dialog.stories'; -import * as RadioGroupStories from '../../../packages/ui/src/components/RadioGroup/RadioGroup.stories'; -import * as TabsStories from '../../../packages/ui/src/components/Tabs/Tabs.stories'; -import * as SwitchStories from '../../../packages/ui/src/components/Switch/Switch.stories'; -import * as SearchFieldStories from '../../../packages/ui/src/components/SearchField/SearchField.stories'; -import * as TooltipStories from '../../../packages/ui/src/components/Tooltip/Tooltip.stories'; -import * as SkeletonStories from '../../../packages/ui/src/components/Skeleton/Skeleton.stories'; -import * as CardStories from '../../../packages/ui/src/components/Card/Card.stories'; -import * as HeaderStories from '../../../packages/ui/src/components/Header/Header.stories'; -import * as HeaderPageStories from '../../../packages/ui/src/components/HeaderPage/HeaderPage.stories'; -import * as TableStories from '../../../packages/ui/src/components/Table/Table.stories'; -import * as TagGroupStories from '../../../packages/ui/src/components/TagGroup/TagGroup.stories'; -import * as PasswordFieldStories from '../../../packages/ui/src/components/PasswordField/PasswordField.stories'; -import * as VisuallyHiddenStories from '../../../packages/ui/src/components/VisuallyHidden/VisuallyHidden.stories'; - -// Helper function to create snippet components -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const createSnippetComponent = (stories: any) => { - return function SnippetComponent({ story }: { story: string }) { - const composedStories = composeStories(stories); - const StoryComponent = composedStories[ - story as keyof typeof composedStories - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ] as any; - - return StoryComponent ? : null; - }; -}; - -// Create snippet components using the helper function -export const BoxSnippet = createSnippetComponent(BoxStories); -export const ButtonSnippet = createSnippetComponent(ButtonStories); -export const ButtonIconSnippet = createSnippetComponent(ButtonIconStories); -export const ButtonLinkSnippet = createSnippetComponent(ButtonLinkStories); -export const CheckboxSnippet = createSnippetComponent(CheckboxStories); -export const ContainerSnippet = createSnippetComponent(ContainerStories); -export const GridSnippet = createSnippetComponent(GridStories); -export const TextFieldSnippet = createSnippetComponent(TextFieldStories); -export const PasswordFieldSnippet = - createSnippetComponent(PasswordFieldStories); -export const TextSnippet = createSnippetComponent(TextStories); -export const FlexSnippet = createSnippetComponent(FlexStories); -export const SelectSnippet = createSnippetComponent(SelectStories); -export const MenuSnippet = createSnippetComponent(MenuStories); -export const LinkSnippet = createSnippetComponent(LinkStories); -export const AvatarSnippet = createSnippetComponent(AvatarStories); -export const AccordionSnippet = createSnippetComponent(AccordionStories); -export const DialogSnippet = createSnippetComponent(DialogStories); -export const RadioGroupSnippet = createSnippetComponent(RadioGroupStories); -export const TabsSnippet = createSnippetComponent(TabsStories); -export const SwitchSnippet = createSnippetComponent(SwitchStories); -export const SearchFieldSnippet = createSnippetComponent(SearchFieldStories); -export const TooltipSnippet = createSnippetComponent(TooltipStories); -export const SkeletonSnippet = createSnippetComponent(SkeletonStories); -export const CardSnippet = createSnippetComponent(CardStories); -export const HeaderSnippet = createSnippetComponent(HeaderStories); -export const HeaderPageSnippet = createSnippetComponent(HeaderPageStories); -export const TableSnippet = createSnippetComponent(TableStories); -export const TagGroupSnippet = createSnippetComponent(TagGroupStories); -export const VisuallyHiddenSnippet = createSnippetComponent( - VisuallyHiddenStories, -); diff --git a/docs-ui/src/utils/changelog.ts b/docs-ui/src/utils/changelog.ts index 1179d7e189..54a772793b 100644 --- a/docs-ui/src/utils/changelog.ts +++ b/docs-ui/src/utils/changelog.ts @@ -1,4 +1,11 @@ export * from './types'; +import { changelog_0_14_0 } from './changelogs/v0.14.0'; +import { changelog_0_13_0 } from './changelogs/v0.13.0'; +import { changelog_0_12_0 } from './changelogs/v0.12.0'; +import { changelog_0_11_0 } from './changelogs/v0.11.0'; +import { changelog_0_10_0 } from './changelogs/v0.10.0'; +import { changelog_0_9_1 } from './changelogs/v0.9.1'; +import { changelog_0_9_0 } from './changelogs/v0.9.0'; import { changelog_0_8_2 } from './changelogs/v0.8.2'; import { changelog_0_8_0 } from './changelogs/v0.8.0'; import { changelog_0_7_2 } from './changelogs/v0.7.2'; @@ -14,6 +21,13 @@ import { changelog_0_2_0 } from './changelogs/v0.2.0'; import { changelog_0_1_0 } from './changelogs/v0.1.0'; export const changelog = [ + ...changelog_0_14_0, + ...changelog_0_13_0, + ...changelog_0_12_0, + ...changelog_0_11_0, + ...changelog_0_10_0, + ...changelog_0_9_1, + ...changelog_0_9_0, ...changelog_0_8_2, ...changelog_0_8_0, ...changelog_0_7_2, diff --git a/docs-ui/src/utils/changelogs/v0.1.0.ts b/docs-ui/src/utils/changelogs/v0.1.0.ts index cf333e92e9..046bb24136 100644 --- a/docs-ui/src/utils/changelogs/v0.1.0.ts +++ b/docs-ui/src/utils/changelogs/v0.1.0.ts @@ -4,113 +4,57 @@ export const changelog_0_1_0: ChangelogProps[] = [ { components: [], version: '0.1.0', - description: `**BREAKING**: Merged the Stack and Inline component into a single component called Flex.`, prs: ['28634'], - type: 'breaking', + description: `**BREAKING**: Merged the Stack and Inline component into a single component called Flex.`, + breaking: true, commitSha: '72c9800', }, { components: [], version: '0.1.0', - description: `This is the first alpha release for Canon. As part of this release we are introducing 5 layout components and 7 components. All theming is done through CSS variables.`, prs: ['28562'], - type: 'breaking', + description: `**BREAKING**: This is the first alpha release for Canon. As part of this release we are introducing 5 layout components and 7 components. All theming is done through CSS variables.`, + breaking: true, commitSha: '65f4acc', }, { components: [], version: '0.1.0', - description: `**BREAKING**: Fixing css structure and making sure that props are applying the correct styles for all responsive values.`, prs: ['28630'], - type: 'breaking', + description: `**BREAKING**: Fixing css structure and making sure that props are applying the correct styles for all responsive values.`, + breaking: true, commitSha: '1e4ccce', }, { components: [], version: '0.1.0', - description: `Updated core CSS tokens and fixing the Button component accordingly.`, prs: ['28789'], - type: 'breaking', + description: `**BREAKING**: Updated core CSS tokens and fixing the Button component accordingly.`, + breaking: true, commitSha: '8309bdb', }, { components: [], version: '0.1.0', - description: `Removed client directive as they are not needed in React 18.`, prs: ['28626'], - type: 'fix', + description: `Removed client directive as they are not needed in React 18.`, + commitSha: '989af25', }, { components: [], version: '0.1.0', - description: `Fix spacing props not being applied for custom values.`, prs: ['28770'], - type: 'fix', + description: `Fix spacing props not being applied for custom values.`, + commitSha: 'f44e5cf', }, { components: [], version: '0.1.0', - description: `Removed older versions of React packages as a preparatory step for upgrading to React 19. This commit does not introduce any functional changes, but removes dependencies on previous React versions, allowing for a cleaner upgrade path in subsequent commits.`, prs: ['28579'], - type: 'fix', - commitSha: '58ec9e7', - }, - { - components: [], - version: '0.1.0', - description: `Updated core CSS tokens and fixing the Button component accordingly.`, - prs: ['28789'], - type: 'breaking', - commitSha: '8309bdb', - }, - { - components: [], - version: '0.1.0', - description: `Fix spacing props not being applied for custom values.`, - prs: ['28770'], - type: 'fix', - commitSha: 'f44e5cf', - }, - { - components: [], - version: '0.1.0', - description: `**BREAKING**: Merged the Stack and Inline component into a single component called Flex.`, - prs: ['28634'], - type: 'breaking', - commitSha: '72c9800', - }, - { - components: [], - version: '0.1.0', - description: `**BREAKING**: Fixing css structure and making sure that props are applying the correct styles for all responsive values.`, - prs: ['28630'], - type: 'breaking', - commitSha: '1e4ccce', - }, - { - components: [], - version: '0.1.0', - description: `Removed client directive as they are not needed in React 18.`, - prs: ['28626'], - type: 'fix', - commitSha: '989af25', - }, - { - components: [], - version: '0.1.0', description: `Removed older versions of React packages as a preparatory step for upgrading to React 19. This commit does not introduce any functional changes, but removes dependencies on previous React versions, allowing for a cleaner upgrade path in subsequent commits.`, - prs: ['28579'], - type: 'fix', + commitSha: '58ec9e7', }, - { - components: [], - version: '0.1.0', - description: `This is the first alpha release for Canon. As part of this release we are introducing 5 layout components and 7 components. All theming is done through CSS variables.`, - prs: ['28562'], - type: 'breaking', - commitSha: '65f4acc', - }, ]; diff --git a/docs-ui/src/utils/changelogs/v0.10.0.ts b/docs-ui/src/utils/changelogs/v0.10.0.ts new file mode 100644 index 0000000000..6b6eb87563 --- /dev/null +++ b/docs-ui/src/utils/changelogs/v0.10.0.ts @@ -0,0 +1,89 @@ +import type { ChangelogProps } from '../types'; + +export const changelog_0_10_0: ChangelogProps[] = [ + { + components: [], + version: '0.10.0', + prs: ['31917'], + description: `**BREAKING**: The \`Cell\` component has been refactored to be a generic wrapper component that accepts \`children\` for custom cell content. The text-specific functionality (previously part of \`Cell\`) has been moved to a new \`CellText\` component.`, + migration: `If you were using \`Cell\` with text-specific props (\`title\`, \`description\`, \`leadingIcon\`, \`href\`), you need to update your code to use \`CellText\` instead: + +**Before:** + +\`\`\`tsx +} + href="/path" +/> +\`\`\` + +**After:** + +\`\`\`tsx +} + href="/path" +/> +\`\`\` + +For custom cell content, use the new generic \`Cell\` component: + +\`\`\`tsx +{/* Your custom content */} +\`\`\``, + breaking: true, + commitSha: '16543fa', + }, + { + components: ['checkbox'], + version: '0.10.0', + prs: ['31904'], + description: `Fixed Checkbox indicator showing checkmark color when unchecked.`, + + commitSha: '50b7927', + }, + { + components: ['button-icon'], + version: '0.10.0', + prs: ['31900'], + description: `Fixed \`ButtonIcon\` incorrectly applying \`className\` to inner elements instead of only the root element.`, + + commitSha: '5bacf55', + }, + { + components: [], + version: '0.10.0', + prs: ['31843'], + description: `Fixed Table Row component to correctly handle cases where no \`href\` is provided, preventing unnecessary router provider wrapping and fixing the cursor incorrectly showing as a pointer despite the element not being a link.`, + + commitSha: 'b3ad928', + }, + { + components: ['table'], + version: '0.10.0', + prs: ['31907'], + description: `Added row selection support with visual state styling for hover, selected, and pressed states. Fixed checkbox rendering to only show for multi-select toggle mode.`, + + commitSha: 'a20d317', + }, + { + components: [], + version: '0.10.0', + prs: ['31817'], + description: `Fixed \`useTable\` hook to prioritize \`providedRowCount\` over data length for accurate row count in server-side pagination scenarios.`, + + commitSha: 'fe7c751', + }, + { + components: [], + version: '0.10.0', + prs: ['31844'], + description: `Fixed Table column sorting indicator to show up arrow when no sort is active, correctly indicating that clicking will sort ascending.`, + + commitSha: 'c145031', + }, +]; diff --git a/docs-ui/src/utils/changelogs/v0.11.0.ts b/docs-ui/src/utils/changelogs/v0.11.0.ts new file mode 100644 index 0000000000..a7dd7ab1ca --- /dev/null +++ b/docs-ui/src/utils/changelogs/v0.11.0.ts @@ -0,0 +1,196 @@ +import type { ChangelogProps } from '../types'; + +export const changelog_0_11_0: ChangelogProps[] = [ + { + components: ['table'], + version: '0.11.0', + prs: ['32050'], + description: `**BREAKING**: Redesigned Table component with new \`useTable\` hook API. + + - The \`Table\` component (React Aria wrapper) is renamed to \`TableRoot\` + - New high-level \`Table\` component that handles data display, pagination, sorting, and selection + - The \`useTable\` hook is completely redesigned with a new API supporting three pagination modes (complete, offset, cursor) + - New types: \`ColumnConfig\`, \`TableProps\`, \`TableItem\`, \`UseTableOptions\`, \`UseTableResult\` + + New features include unified pagination modes, debounced query changes, stale data preservation during reloads, and row selection with toggle/replace behaviors.`, + migration: `1. Update imports and use the new \`useTable\` hook: + +\`\`\`diff +-import { Table, useTable } from '@backstage/ui'; +-const { data, paginationProps } = useTable({ data: items, pagination: {...} }); ++import { Table, useTable, type ColumnConfig } from '@backstage/ui'; ++const { tableProps } = useTable({ ++ mode: 'complete', ++ getData: () => items, ++}); +\`\`\` + +2. Define columns and render with the new Table API: + +\`\`\`diff +- +- ... +- ... +-
    +- ++const columns: ColumnConfig[] = [ ++ { id: 'name', label: 'Name', isRowHeader: true, cell: item => }, ++ { id: 'type', label: 'Type', cell: item => }, ++]; ++ ++ +\`\`\``, + breaking: true, + commitSha: '243e5e7', + }, + { + components: [], + version: '0.11.0', + prs: ['32202'], + description: `**BREAKING**: Updating color tokens to match the new neutral style on different surfaces.`, + migration: `There's no direct replacement for the old tint tokens but you can use the new neutral set of color tokens on surface 0 or 1 as a replacement. + +- \`--bui-bg-tint\` can be replaced by \`--bui-bg-neutral-on-surface-0\` +- \`--bui-bg-tint-hover\` can be replaced by \`--bui-bg-neutral-on-surface-0-hover\` +- \`--bui-bg-tint-pressed\` can be replaced by \`--bui-bg-neutral-on-surface-0-pressed\` +- \`--bui-bg-tint-disabled\` can be replaced by \`--bui-bg-neutral-on-surface-0-disabled\``, + breaking: true, + commitSha: '95246eb', + }, + { + components: [], + version: '0.11.0', + prs: ['32232'], + description: `**BREAKING**: Introduce new \`ToggleButton\` & \`ToggleButtonGroup\` components in Backstage UI`, + breaking: true, + commitSha: 'ea0c6d8', + }, + { + components: [], + version: '0.11.0', + prs: ['32200'], + description: `**BREAKING**: Renamed CSS variable \`--bui-bg\` to \`--bui-bg-surface-0\` for consistency.`, + breaking: true, + commitSha: '4ea1d15', + }, + { + components: ['box'], + version: '0.11.0', + prs: ['32203'], + description: `Fixes app background color on dark mode.`, + + commitSha: '1880402', + }, + { + components: ['checkbox'], + version: '0.11.0', + prs: ['32371'], + description: `Added indeterminate state support to the Checkbox component for handling partial selection scenarios like table header checkboxes.`, + + commitSha: 'd2fdded', + }, + { + components: ['select'], + version: '0.11.0', + prs: ['32337'], + description: `Added missing \`aria-label\` attributes to \`SearchField\` components in \`Select\`, \`MenuAutocomplete\`, and \`MenuAutocompleteListbox\` to fix accessibility warnings.`, + + commitSha: '4fb15d2', + }, + { + components: ['button'], + version: '0.11.0', + prs: ['32297'], + description: `Fixes disabled state in primary and secondary buttons in Backstage UI.`, + + commitSha: '21c87cc', + }, + { + components: [], + version: '0.11.0', + prs: ['32185'], + description: `build(deps-dev): bump \`storybook\` from 10.1.9 to 10.1.10`, + + commitSha: '9c76682', + }, + { + components: ['button'], + version: '0.11.0', + prs: ['32385'], + description: `Fixed disabled tertiary buttons incorrectly showing hover effects on surfaces.`, + + commitSha: 'de80336', + }, + { + components: [], + version: '0.11.0', + prs: ['32313'], + description: `Added new Popover component for Backstage UI with automatic overflow handling, and full placement support. Also introduced \`--bui-shadow\` token for consistent elevation styling across overlay components (Popover, Tooltip, Menu).`, + + commitSha: '133d5c6', + }, + { + components: ['table'], + version: '0.11.0', + prs: ['32350'], + description: `Fixed Table sorting indicator not being visible when a column is actively sorted.`, + + commitSha: '973c839', + }, + { + components: ['menu'], + version: '0.11.0', + prs: ['32347'], + description: `Fixed Menu component trigger button not toggling correctly. Removed custom click-outside handler that was interfering with React Aria's built-in state management, allowing the menu to properly open and close when clicking the trigger button.`, + + commitSha: 'df40cfc', + }, + { + components: ['table'], + version: '0.11.0', + prs: ['32336'], + description: `Added support for column width configuration in Table component. Columns now accept \`width\`, \`defaultWidth\`, \`minWidth\`, and \`maxWidth\` props for responsive layout control.`, + + commitSha: 'b01ab96', + }, + { + components: ['searchfield'], + version: '0.11.0', + prs: ['32123'], + description: `Fixed SearchField \`startCollapsed\` prop not working correctly in Backstage UI. The field now properly starts in a collapsed state, expands when clicked and focused, and collapses back when unfocused with no input. Also fixed CSS logic to work correctly in all layout contexts (flex row, flex column, and regular containers).`, + + commitSha: 'b4a4911', + }, + { + components: [], + version: '0.11.0', + prs: ['32265'], + description: `Fixed \`Link\` component causing hard page refreshes for internal routes. The component now properly uses React Router's navigation instead of full page reloads.`, + + commitSha: 'b3253b6', + }, + { + components: ['table'], + version: '0.11.0', + prs: ['32321'], + description: `Added support for custom pagination options in \`useTable\` hook and \`Table\` component. You can now configure \`pageSizeOptions\` to customize the page size dropdown, and hook into pagination events via \`onPageSizeChange\`, \`onNextPage\`, and \`onPreviousPage\` callbacks. When \`pageSize\` doesn't match any option, the first option is used and a warning is logged.`, + + commitSha: 'fe7fe69', + }, + { + components: ['table'], + version: '0.11.0', + prs: ['32369'], + description: `Fixed missing border styles on table selection cells in multi-select mode.`, + + commitSha: 'cfac8a4', + }, + { + components: ['table'], + version: '0.11.0', + prs: ['32342'], + description: `Added \`className\` and \`style\` props to the \`Table\` component.`, + + commitSha: '2532d2a', + }, +]; diff --git a/docs-ui/src/utils/changelogs/v0.12.0.ts b/docs-ui/src/utils/changelogs/v0.12.0.ts new file mode 100644 index 0000000000..f83920c36f --- /dev/null +++ b/docs-ui/src/utils/changelogs/v0.12.0.ts @@ -0,0 +1,526 @@ +import type { ChangelogProps } from '../types'; + +export const changelog_0_12_0: ChangelogProps[] = [ + { + components: ['alert'], + version: '0.12.0', + prs: ['32711'], + description: `**BREAKING**: Alert no longer accepts a \`surface\` prop + + The Alert component's background is now driven entirely by its \`status\` prop. The \`surface\` prop has been removed. + + \`\`\`diff + - + + + \`\`\``, + breaking: true, + commitSha: '46a9adc', + }, + { + components: [], + version: '0.12.0', + prs: ['32723'], + description: `**BREAKING**: Removed gray scale tokens and renamed background surface tokens to neutral tokens + + The \`--bui-gray-1\` through \`--bui-gray-8\` tokens have been removed. The \`--bui-bg-surface-*\` and \`--bui-bg-neutral-on-surface-*\` tokens have been replaced by a unified \`--bui-bg-neutral-*\` scale.`, + migration: `Replace surface tokens directly: + +\`\`\`diff +- background: var(--bui-bg-surface-0); ++ background: var(--bui-bg-neutral-0); +\`\`\` + +Replace on-surface tokens shifted by +1: + +\`\`\`diff +- background: var(--bui-bg-neutral-on-surface-0); ++ background: var(--bui-bg-neutral-1); +\`\`\` + +Replace gray tokens 1-4 with neutral equivalents (\`--bui-gray-5\` through \`--bui-gray-8\` have no direct replacement): + +\`\`\`diff +- background: var(--bui-gray-1); ++ background: var(--bui-bg-neutral-1); +\`\`\``, + breaking: true, + commitSha: 'b63c25b', + }, + { + components: [ + 'box', + 'button', + 'button-icon', + 'button-link', + 'toggle-button', + 'card', + 'flex', + 'grid', + ], + version: '0.12.0', + prs: ['32711'], + description: `**BREAKING**: Replaced \`Surface\` / \`onSurface\` system with new provider/consumer background system + + The old \`Surface\` type (\`'0'\`–\`'3'\`, \`'auto'\`) and its associated props (\`surface\`, \`onSurface\`) have been replaced by a provider/consumer \`bg\` architecture. + + **Types:** + + - \`ContainerBg\` — \`'neutral-1'\` | \`'neutral-2'\` | \`'neutral-3'\` | \`'danger'\` | \`'warning'\` | \`'success'\` + - \`ProviderBg\` — \`ContainerBg | 'neutral-auto'\` + + Consumer components (e.g. Button) inherit the parent's \`bg\` via \`data-on-bg\`, and CSS handles the visual step-up. See "Neutral level capping" below for details on how levels are bounded. + + **Hooks:** + + - \`useBgProvider(bg?)\` — for provider components. Returns \`{ bg: undefined }\` when no \`bg\` is given (transparent). Supports \`'neutral-auto'\` to auto-increment from the parent context. + - \`useBgConsumer()\` — for consumer components. Returns the parent container's \`bg\` unchanged. + + **Component roles:** + + - **Provider-only** (Box, Flex, Grid): set \`data-bg\`, wrap children in \`BgProvider\`. **Transparent by default** — they do _not_ auto-increment; pass \`bg="neutral-auto"\` explicitly if you want automatic neutral stepping. + - **Consumer-only** (Button, ButtonIcon, ButtonLink): set \`data-on-bg\`, inherit the parent container's \`bg\` unchanged. + - **Provider + Consumer** (Card): sets both \`data-bg\` and \`data-on-bg\`, wraps children. Card passes \`bg="neutral-auto"\` to its inner Box, so it auto-increments from the parent context. + + **Neutral level capping:** + + Provider components cap at \`neutral-3\`. There is no \`neutral-4\` prop value. The \`neutral-4\` level exists only in consumer component CSS — for example, a Button sitting on a \`neutral-3\` surface uses \`neutral-4\` tokens internally via \`data-on-bg\`.`, + migration: `Rename the \`surface\` prop to \`bg\` on provider components and update values: + +\`\`\`diff +- ++ + +- ++ + +- ++ + +- ++ +\`\`\` + +Remove \`onSurface\` from consumer components — they now always inherit from the parent container: + +\`\`\`diff +- ++ +\`\`\``, + breaking: true, + commitSha: '058ffd9', + }, + { + components: ['link'], + version: '0.12.0', + prs: ['32608'], + description: `**BREAKING**: Removed link and tint color tokens, added new status foreground tokens, and improved Link component styling + + The following color tokens have been removed: + + - \`--bui-fg-link\` (and all related tokens: \`-hover\`, \`-pressed\`, \`-disabled\`) + - \`--bui-fg-tint\` (and all related tokens: \`-hover\`, \`-pressed\`, \`-disabled\`) + - \`--bui-bg-tint\` (and all related tokens: \`-hover\`, \`-pressed\`, \`-disabled\`) + - \`--bui-border-tint\` (and all related tokens) + + **New Status Tokens:** + + Added dedicated tokens for status colors that distinguish between usage on status backgrounds vs. standalone usage: + + - \`--bui-fg-danger-on-bg\` / \`--bui-fg-danger\` + - \`--bui-fg-warning-on-bg\` / \`--bui-fg-warning\` + - \`--bui-fg-success-on-bg\` / \`--bui-fg-success\` + - \`--bui-fg-info-on-bg\` / \`--bui-fg-info\` + + The \`-on-bg\` variants are designed for text on colored backgrounds, while the base variants are for standalone status indicators with improved visibility and contrast.`, + migration: `For link colors, migrate to one of the following alternatives: + +\`\`\`diff +.custom-link { +- color: var(--bui-fg-link); ++ color: var(--bui-fg-info); /* For informational links */ ++ /* or */ ++ color: var(--bui-fg-primary); /* For standard text links */ +} +\`\`\` + +For tint colors (backgrounds, foregrounds, borders), migrate to appropriate status or neutral colors: + +\`\`\`diff +.info-section { +- background: var(--bui-bg-tint); ++ background: var(--bui-bg-info); /* For informational sections */ ++ /* or */ ++ background: var(--bui-bg-neutral-1); /* For neutral emphasis */ +} +\`\`\` + +If you're using status foreground colors on colored backgrounds, update to the new \`-on-bg\` tokens: + +\`\`\`diff +.error-badge { +- color: var(--bui-fg-danger); ++ color: var(--bui-fg-danger-on-bg); + background: var(--bui-bg-danger); +} +\`\`\``, + breaking: true, + commitSha: '110fec0', + }, + { + components: ['full-page'], + version: '0.12.0', + prs: ['32822'], + description: `Added a new \`FullPage\` component that fills the remaining viewport height below the \`PluginHeader\`. + + \`\`\`tsx + + + {/* content fills remaining height */} + + \`\`\``, + + commitSha: '644e303', + }, + { + components: [], + version: '0.12.0', + prs: ['32825'], + description: `Fixed dark theme \`--bui-fg-secondary\` and \`--bui-fg-disabled\` tokens using black-based \`oklch(0% ...)\` instead of white-based \`oklch(100% ...)\`, making secondary and disabled text visible on dark backgrounds.`, + + commitSha: '44877e4', + }, + { + components: ['box'], + version: '0.12.0', + prs: ['32536'], + description: `Fixed Box component to forward HTML attributes to the underlying div element.`, + + commitSha: '350c948', + }, + { + components: [], + version: '0.12.0', + prs: ['32516'], + description: `Use node prefix on native imports`, + + commitSha: '7455dae', + }, + { + components: ['accordion'], + version: '0.12.0', + prs: ['32488'], + description: `Fixed nested Accordion icon state issue where the inner accordion's arrow icon would incorrectly show as expanded when only the outer accordion was expanded. The CSS selector now uses a direct parent selector to ensure the icon only responds to its own accordion's expanded state.`, + + commitSha: 'c8ae765', + }, + { + components: ['popover', 'tooltip'], + version: '0.12.0', + prs: ['32575'], + description: `Fixed CSS Module syntax to comply with Next.js 16 Turbopack validation by flattening nested dark theme selectors.`, + + commitSha: '4d1b7f4', + }, + { + components: ['button'], + version: '0.12.0', + prs: ['32554'], + description: `Added \`destructive\` prop to Button for dangerous actions like delete or remove. Works with all variants (primary, secondary, tertiary).`, + + commitSha: '2c219b9', + }, + { + components: [], + version: '0.12.0', + prs: ['32553'], + description: `Fixed \`useDefinition\` hook adding literal "undefined" class name when no className prop was passed.`, + + commitSha: '5af9e14', + }, + { + components: ['tag'], + version: '0.12.0', + prs: ['32742'], + description: `Allow \`ref\` as a prop on the \`Tag\` component`, + + commitSha: '5c76d13', + }, + { + components: [], + version: '0.12.0', + prs: ['32799'], + description: `Cleaned up \`useDefinition\` \`ownProps\` types to remove never-typed ghost properties from autocomplete.`, + + commitSha: 'ab25658', + }, + { + components: ['table'], + version: '0.12.0', + prs: ['32685'], + description: `Allow data to be passed directly to the \`useTable\` hook using the property \`data\` instead of \`getData()\` for mode \`"complete"\`. + + This simplifies usage as data changes, rather than having to perform a \`useEffect\` when data changes, and then reloading the data. It also happens immediately, so stale data won't remain until a rerender (with an internal async state change), so less flickering.`, + + commitSha: '741a98d', + }, + { + components: ['table'], + version: '0.12.0', + prs: ['32684'], + description: `Fixed changing columns after first render from crashing. It now renders the table with the new column layout as columns change.`, + + commitSha: 'a0fe1b2', + }, + { + components: ['alert'], + version: '0.12.0', + prs: ['32520'], + description: `Added new \`Alert\` component with support for status variants (info, success, warning, danger), icons, loading states, and custom actions. + + Updated status color tokens for improved contrast and consistency across light and dark themes: + + - Added new \`--bui-bg-info\` and \`--bui-fg-info\` tokens for info status + - Updated \`--bui-bg-danger\`, \`--bui-fg-danger\` tokens + - Updated \`--bui-bg-warning\`, \`--bui-fg-warning\` tokens + - Updated \`--bui-bg-success\`, \`--bui-fg-success\` tokens`, + + commitSha: '508bd1a', + }, + { + components: [ + 'tabs', + 'tab', + 'tag-group', + 'tag', + 'menu', + 'menu-item', + 'menu-autocomplete', + ], + version: '0.12.0', + prs: ['32373'], + description: `Fixed client-side navigation for container components by wrapping the container (not individual items) in RouterProvider. Components now conditionally provide routing context only when children have internal links, removing the Router context requirement when not needed. This also removes the need to wrap these components in MemoryRouter during tests when they are not using the \`href\` prop. + + Additionally, when multiple tabs match the current URL via prefix matching, the tab with the most specific path (highest segment count) is now selected. For example, with URL \`/catalog/users/john\`, a tab with path \`/catalog/users\` is now selected over a tab with path \`/catalog\`.`, + + commitSha: 'da30862', + }, + { + components: [], + version: '0.12.0', + prs: ['32540'], + description: `Fixed an infinite render loop in Tabs when navigating to a URL that doesn't match any tab \`href\`.`, + + commitSha: '092c453', + }, + { + components: [], + version: '0.12.0', + prs: ['32643'], + description: `export PasswordField component`, + + commitSha: 'becf851', + }, + { + components: [], + version: '0.12.0', + prs: ['32843'], + description: `Migrated Accordion components to use \`useDefinition\` instead of \`useStyles\`, and added automatic background adaptation based on parent container context.`, + + commitSha: 'becee36', + }, + { + components: ['link', 'button-link', 'row'], + version: '0.12.0', + prs: ['32373'], + description: `Fixed components to not require a Router context when rendering without internal links.`, + + commitSha: '5320aa8', + }, + { + components: [], + version: '0.12.0', + prs: ['31818'], + description: `Updated \`react-router-dom\` peer dependency to \`^6.30.2\` and explicitly disabled v7 future flags to suppress deprecation warnings.`, + + commitSha: 'a7e0d50', + }, + { + components: ['table'], + version: '0.12.0', + prs: ['32686'], + description: `The Table component now wraps the react-aria-components \`Table\` with a \`ResizableTableContainer\` only if any column has a width property set. This means that column widths can adapt to the content otherwise (if no width is explicitly set).`, + + commitSha: '8c39412', + }, + { + components: [], + version: '0.12.0', + prs: ['32537'], + description: `Bump react-aria-components to v1.14.0`, + + commitSha: 'cb090b4', + }, + { + components: [], + version: '0.12.0', + prs: ['32428'], + description: `Fixed React 17 compatibility by using \`useId\` from \`react-aria\` instead of the built-in React hook which is only available in React 18+.`, + + commitSha: 'c429101', + }, + { + components: ['switch'], + version: '0.12.0', + prs: ['32551'], + description: `Fixed Switch component disabled state styling to show \`not-allowed\` cursor and disabled text color.`, + + commitSha: '74c5a76', + }, + { + components: [], + version: '0.12.0', + prs: ['32761'], + description: `Migrated to use the standard \`backstage-cli package build\` for CSS bundling instead of a custom build script.`, + + commitSha: '20131c5', + }, +]; diff --git a/docs-ui/src/utils/changelogs/v0.13.0.ts b/docs-ui/src/utils/changelogs/v0.13.0.ts new file mode 100644 index 0000000000..8b4f93afda --- /dev/null +++ b/docs-ui/src/utils/changelogs/v0.13.0.ts @@ -0,0 +1,527 @@ +import type { ChangelogProps } from '../types'; + +export const changelog_0_13_0: ChangelogProps[] = [ + { + components: [ + 'box', + 'flex', + 'grid', + 'card', + 'accordion', + 'popover', + 'tooltip', + 'dialog', + 'menu', + ], + version: '0.13.0', + prs: ['33002'], + description: `**BREAKING**: Simplified the neutral background prop API for container components. The explicit \`neutral-1\`, \`neutral-2\`, \`neutral-3\`, and \`neutral-auto\` values have been removed from \`ProviderBg\`. They are replaced by a single \`'neutral'\` value that always auto-increments from the parent context, making it impossible to skip or pin to an explicit neutral level.`, + migration: `Replace any explicit \`bg="neutral-1"\`, \`bg="neutral-2"\`, \`bg="neutral-3"\`, or \`bg="neutral-auto"\` props with \`bg="neutral"\`. To achieve a specific neutral level in stories or tests, use nested containers — each additional \`bg="neutral"\` wrapper increments by one level. + +\`\`\`tsx +// Before +... + +// After + + ... + +\`\`\``, + breaking: true, + commitSha: '768f09d', + }, + { + components: ['popover', 'tooltip', 'menu', 'dialog'], + version: '0.13.0', + prs: ['32979'], + description: `**BREAKING**: Removed \`--bui-bg-popover\` CSS token. Popover, Tooltip, Menu, and Dialog now use \`--bui-bg-app\` for their outer shell and \`Box bg="neutral-1"\` for content areas, providing better theme consistency and eliminating a redundant token.`, + migration: `Replace any usage of \`--bui-bg-popover\` with \`--bui-bg-neutral-1\` (for content surfaces) or \`--bui-bg-app\` (for outer shells): + +\`\`\`diff +- background: var(--bui-bg-popover); ++ background: var(--bui-bg-neutral-1); +\`\`\``, + breaking: true, + commitSha: 'b42fcdc', + }, + { + components: ['searchfield'], + version: '0.13.0', + prs: ['33050'], + description: `**BREAKING**: Data attributes rendered by components are now always lowercase. This affects CSS selectors targeting camelCase data attributes.`, + migration: `Update any custom CSS selectors that target camelCase data attributes to use lowercase instead: + +\`\`\`diff +- [data-startCollapsed='true'] { ... } ++ [data-startcollapsed='true'] { ... } +\`\`\``, + breaking: true, + commitSha: 'bd3a76e', + }, + { + components: [], + version: '0.13.0', + prs: ['33050'], + description: `**BREAKING**: Removed deprecated types \`ComponentDefinition\`, \`ClassNamesMap\`, \`DataAttributeValues\`, and \`DataAttributesMap\` from the public API. These were internal styling infrastructure types that have been replaced by the \`defineComponent\` system.`, + migration: `Remove any direct usage of these types. Component definitions now use \`defineComponent()\` and their shapes are not part of the public API contract. + +\`\`\`diff +- import type { ComponentDefinition, ClassNamesMap } from '@backstage/ui'; +\`\`\` + +If you were reading \`definition.dataAttributes\`, use \`definition.propDefs\` instead — props with \`dataAttribute: true\` in \`propDefs\` are the equivalent.`, + breaking: true, + commitSha: '95702ab', + }, + { + components: ['link', 'button-link', 'tabs', 'menu', 'tag-group', 'table'], + version: '0.13.0', + prs: ['33267'], + description: `**BREAKING**: Centralized client-side routing in \`BUIProvider\`. Components like Link, ButtonLink, Tabs, Menu, TagGroup, and Table now require a \`BUIProvider\` rendered inside a React Router context for client-side navigation to work.`, + migration: `This change requires updating \`@backstage/plugin-app\` and \`@backstage/core-app-api\` alongside \`@backstage/ui\`. If you only upgrade \`@backstage/ui\`, BUI components will fall back to full-page navigation. + +If you cannot upgrade all packages together, or if you have a custom app shell, add a \`BUIProvider\` inside your Router: + +\`\`\`diff ++ import { BUIProvider } from '@backstage/ui'; + + ++ + ++ + +\`\`\``, + breaking: true, + commitSha: '42f8c9b', + }, + { + components: ['header'], + version: '0.13.0', + prs: ['33354'], + description: `**BREAKING**: Renamed internal CSS classes to match the \`Header\` component name. + + **Migration:** If you are targeting these classes directly in your styles, update the following: + + - \`bui-HeaderPage\` → \`bui-Header\` + - \`bui-HeaderPageContent\` → \`bui-HeaderContent\` + - \`bui-HeaderPageBreadcrumbs\` → \`bui-HeaderBreadcrumbs\` + - \`bui-HeaderPageTabsWrapper\` → \`bui-HeaderTabsWrapper\` + - \`bui-HeaderPageControls\` → \`bui-HeaderControls\``, + breaking: true, + commitSha: '17d6398', + }, + { + components: ['checkbox'], + version: '0.13.0', + prs: ['33323'], + description: `Removed redundant \`selected\` and \`indeterminate\` props from the \`Checkbox\` component. Use the \`isSelected\` and \`isIndeterminate\` props instead, which are the standard React Aria props and already handle both the checkbox behaviour and the corresponding CSS data attributes.`, + migration: `Replace any usage of the \`selected\` and \`indeterminate\` props on \`Checkbox\` with the \`isSelected\` and \`isIndeterminate\` props. Note that the checked state and related CSS data attributes (such as \`data-selected\` and \`data-indeterminate\`) are now driven by React Aria, so any custom logic that previously inspected or set these via the old props should instead rely on the React Aria-managed state and attributes exposed through the new props.`, + breaking: true, + commitSha: '9d5f3ba', + }, + { + components: ['list', 'list-row'], + version: '0.13.0', + prs: ['33358'], + description: `Added \`List\` and \`ListRow\` components. These provide a standalone, accessible list of interactive rows built on top of React Aria's \`GridList\` and \`GridListItem\` primitives. Rows support icons, descriptions, actions, menus, and single or multiple selection modes.`, + + commitSha: '04d9d8d', + }, + { + components: ['accordion'], + version: '0.13.0', + prs: ['32935'], + description: `Made Accordion a \`bg\` provider so nested components like Button auto-increment their background level. Updated \`useDefinition\` to resolve \`bg\` \`propDef\` defaults for provider components.`, + + commitSha: 'a1f4bee', + }, + { + components: ['card'], + version: '0.13.0', + prs: ['33116'], + description: `Added interactive support to the \`Card\` component. Pass \`onPress\` to make the entire card surface pressable, or \`href\` to make it navigate to a URL. A transparent overlay handles the interaction while nested buttons and links remain independently clickable.`, + + commitSha: 'db92751', + }, + { + components: ['link', 'button-link', 'tab', 'menu-item', 'tag', 'row'], + version: '0.13.0', + prs: ['33150'], + description: `Added analytics capabilities to the component library. Components with navigation behavior (Link, ButtonLink, Tab, MenuItem, Tag, Row) now fire analytics events on click when a \`BUIProvider\` is present. + + New exports: \`BUIProvider\`, \`useAnalytics\`, \`getNodeText\`, and associated types (\`AnalyticsTracker\`, \`UseAnalyticsFn\`, \`BUIProviderProps\`, \`AnalyticsEventAttributes\`). + + Components with analytics support now accept a \`noTrack\` prop to suppress event firing.`, + + commitSha: '12d8afe', + }, + { + components: ['table-root'], + version: '0.13.0', + prs: ['33322'], + description: `Added a \`loading\` prop and \`data-loading\` data attribute to \`TableRoot\`, allowing consumers to distinguish between stale data and initial loading states. Both \`stale\` and \`loading\` set \`aria-busy\` on the table.`, + + commitSha: 'b838cc9', + }, + { + components: ['table'], + version: '0.13.0', + prs: ['33322'], + description: `Improved the \`Table\` component loading state to show a skeleton UI with visible headers instead of plain "Loading..." text. The table now renders its full structure during loading, with animated skeleton rows in place of data. The loading state includes proper accessibility support with \`aria-busy\` on the table and screen reader announcements.`, + + commitSha: '690786f', + }, + { + components: [], + version: '0.13.0', + prs: ['33078'], + description: `Fixed neutral-1 hover & pressed state in light mode.`, + + commitSha: '58224d3', + }, + { + components: [ + 'avatar', + 'checkbox', + 'container', + 'dialog', + 'field-error', + 'field-label', + 'flex', + 'full-page', + 'grid', + 'header-page', + 'link', + 'menu', + 'password-field', + 'plugin-header', + 'popover', + 'radio-group', + 'searchfield', + 'select', + 'skeleton', + 'switch', + 'table', + 'table-pagination', + 'tabs', + 'tag-group', + 'text', + 'textfield', + 'toggle-button', + 'toggle-button-group', + 'tooltip', + 'visually-hidden', + ], + version: '0.13.0', + prs: ['33050'], + description: `Migrated all components from \`useStyles\` to \`useDefinition\` hook. Exported \`OwnProps\` types for each component, enabling better type composition for consumers.`, + + commitSha: '95702ab', + }, + { + components: ['card'], + version: '0.13.0', + prs: ['33151'], + description: `Fixed interactive cards so that CardBody can scroll when the card has a constrained height. Previously, the overlay element blocked scroll events.`, + + commitSha: '430d5ed', + }, + { + components: ['container'], + version: '0.13.0', + prs: ['32984'], + description: `Removed the \`transition\` on \`Container\` padding to prevent an unwanted animation when the viewport is resized.`, + + commitSha: '4c2c350', + }, + { + components: [], + version: '0.13.0', + prs: ['32542'], + description: `Fixed --bui-fg-success token in light mode to be more accessible.`, + + commitSha: 'e0b7eb0', + }, + { + components: [], + version: '0.13.0', + prs: ['33188'], + description: `Deprecated the \`HeaderPage\` component name in favor of \`Header\`. The old \`HeaderPage\`, \`HeaderPageProps\`, \`HeaderPageOwnProps\`, \`HeaderPageBreadcrumb\`, and \`HeaderPageDefinition\` exports are still available as deprecated aliases.`, + + commitSha: 'ad7c883', + }, + { + components: [], + version: '0.13.0', + prs: ['33379'], + description: `Added documentation for the table cell wrapper requirement to TSDoc comments for \`Cell\`, \`CellText\`, \`CellProfile\`, \`ColumnConfig\`, and \`RowRenderFn\`.`, + + commitSha: '0ebde15', + }, + { + components: ['search-autocomplete', 'search-autocomplete-item'], + version: '0.13.0', + prs: ['33340'], + description: `Added \`SearchAutocomplete\` and \`SearchAutocompleteItem\` components for building accessible search-with-results patterns. Built on React Aria's Autocomplete with keyboard navigation and screen reader support. Designed for async/external search results with a configurable popover width.`, + + commitSha: 'd9d2dd6', + }, + { + components: ['checkbox'], + version: '0.13.0', + prs: ['33394'], + description: `Made Checkbox \`children\` optional and added a dev warning when neither a visible label, \`aria-label\`, nor \`aria-labelledby\` is provided. The label wrapper div is no longer rendered when there are no children, removing the unnecessary gap.`, + + commitSha: 'a6b84e1', + }, + { + components: ['dialog'], + version: '0.13.0', + prs: ['33352'], + description: `Fixed \`Dialog\` content overflowing when no \`height\` prop is set. The dialog now grows with its content and scrolls when content exceeds the viewport height.`, + + commitSha: 'b99f6d5', + }, + { + components: [ + 'accordion', + 'button', + 'button-icon', + 'button-link', + 'card', + 'list', + 'menu', + 'select', + 'toggle-button-group', + ], + version: '0.13.0', + prs: ['33358'], + description: `Fixed focus ring styles to use React Aria's \`[data-focus-visible]\` data attribute instead of the native CSS \`:focus-visible\` pseudo-class. This ensures keyboard focus rings render reliably when focus is managed programmatically by React Aria (e.g. inside a GridList, Menu, or Select).`, + + commitSha: '2f581de', + }, + { + components: ['container', 'header'], + version: '0.13.0', + prs: ['33354'], + description: `Fixed incorrect bottom spacing caused by \`Container\` using \`padding-bottom\` for its default bottom spacing. Changed to \`margin-bottom\` and prevented it from applying when \`Container\` is used as the \`Header\` root element.`, + + commitSha: '17d6398', + }, + { + components: ['plugin-header'], + version: '0.13.0', + prs: ['33368'], + description: `Fixed \`PluginHeader\` to avoid triggering \`ResizeObserver loop completed with undelivered notifications\` warnings when used in layouts that react to the header height, such as pages that use \`FullPage\`.`, + + commitSha: '2e5c651', + }, + { + components: ['tabs', 'plugin-header'], + version: '0.13.0', + prs: ['33047'], + description: `Fixed tab \`matchStrategy\` matching to ignore query parameters and hash fragments in tab \`href\` values. Previously, tabs with query params in their \`href\` (e.g., \`/page?group=foo\`) would never show as active since matching compared the full \`href\` string against \`location.pathname\` which never includes query params.`, + + commitSha: 'd4fa5b4', + }, + { + components: [], + version: '0.13.0', + prs: ['33363'], + description: `Fixed Table component to use current \`--bui-bg-neutral-1\` tokens instead of the removed \`--bui-bg-tint\` tokens, restoring row hover, selected, pressed, and disabled background colors.`, + + commitSha: 'bc42b60', + }, + { + components: [], + version: '0.13.0', + prs: ['33378'], + description: `Fixed a bug in the \`useTable\` hook where the loading skeleton was never shown for \`complete\` mode when using \`getData\`. The initial data state was an empty array instead of \`undefined\`, causing the \`Table\` component to skip the loading state.`, + + commitSha: '9314ff5', + }, + { + components: ['table'], + version: '0.13.0', + prs: ['33256'], + description: `Fixed Table column headers overflowing and wrapping when there is not enough space. Headers now truncate with ellipsis instead.`, + + commitSha: 'f42f4cc', + }, + { + components: ['row'], + version: '0.13.0', + prs: ['33394'], + description: `Fixed Table row hover, selected, pressed, and disabled background states to use the correct neutral token level based on the container background.`, + + commitSha: '1f9682b', + }, + { + components: ['row'], + version: '0.13.0', + prs: ['33256'], + description: `Fixed Table rows showing a pointer cursor when not interactive. Rows now only show \`cursor: pointer\` when they have an \`href\`, are selectable, or are pressable.`, + + commitSha: 'fbd5c5a', + }, + { + components: ['row'], + version: '0.13.0', + prs: ['33353'], + description: `Fixed \`Table\` rows with external \`href\` values to open in a new tab by automatically applying \`target="_blank"\` and \`rel="noopener noreferrer"\`.`, + + commitSha: '612c217', + }, + { + components: ['table'], + version: '0.13.0', + prs: ['33394'], + description: `Updated Table selection checkboxes to use \`aria-label\` instead of empty fragment children, improving accessibility and removing the unnecessary label gap in the selection cells.`, + + commitSha: '545129a', + }, + { + components: ['button', 'button-icon', 'button-link'], + version: '0.13.0', + prs: ['33095'], + description: `Fixed handling of the \`style\` prop on \`Button\`, \`ButtonIcon\`, and \`ButtonLink\` so that it is now correctly forwarded to the underlying element instead of being silently dropped.`, + + commitSha: '36987db', + }, + { + components: ['link'], + version: '0.13.0', + prs: ['33050'], + description: `Fixed Link variant default from \`'body'\` to \`'body-medium'\` to match actual CSS selectors. The previous default did not correspond to a valid variant value.`, + + commitSha: '95702ab', + }, + { + components: ['menu', 'select'], + version: '0.13.0', + prs: ['33049'], + description: `Fixed scroll overflow in Menu and Select popover content when constrained by viewport height.`, + + commitSha: '9027b10', + }, + { + components: ['flex', 'grid', 'grid-item'], + version: '0.13.0', + prs: ['33136'], + description: `Added support for native HTML div attributes on the \`Flex\`, \`Grid\`, and \`Grid.Item\` components.`, + + commitSha: '7960d54', + }, + { + components: [], + version: '0.13.0', + prs: ['33246'], + description: `Added \`virtualized\` prop to \`Table\` component for virtualized rendering of large datasets. Accepts \`true\` for default row height, \`{ rowHeight: number }\` for fixed height, or \`{ estimatedRowHeight: number }\` for variable height rows.`, + + commitSha: '0559408', + }, + { + components: ['menu', 'select'], + version: '0.13.0', + prs: ['32983'], + description: `Fixed focus-visible outline styles for Menu and Select components.`, + + commitSha: '8909359', + }, + { + components: [], + version: '0.13.0', + prs: ['33150'], + description: `Fixed MenuItem \`onAction\` prop ordering so user-provided \`onAction\` handlers are chained rather than silently overwritten.`, + + commitSha: '12d8afe', + }, + { + components: [], + version: '0.13.0', + prs: ['33389'], + description: `Pages created with \`PageBlueprint\` now render the plugin header by default in the new frontend system.`, + + commitSha: 'aa29b50', + }, + { + components: ['select'], + version: '0.13.0', + prs: ['33102'], + description: `The \`Select\` trigger now automatically adapts its background colour based on the parent background context.`, + + commitSha: 'bb66b86', + }, + { + components: ['plugin-header'], + version: '0.13.0', + prs: ['33085'], + description: `Merged the internal \`PluginHeaderToolbar\` component into \`PluginHeader\`, removing the separate component and its associated types (\`PluginHeaderToolbarOwnProps\`, \`PluginHeaderToolbarProps\`) and definition (\`PluginHeaderToolbarDefinition\`). This is an internal refactor with no changes to the public API of \`PluginHeader\`.`, + + commitSha: '4105a78', + }, + { + components: [], + version: '0.13.0', + prs: ['33251'], + description: `Updated dependency \`globals\` to \`^17.0.0\`.`, + + commitSha: '9599697', + }, + { + components: [], + version: '0.13.0', + prs: ['32971'], + description: `Improved type safety in \`useDefinition\` by centralizing prop resolution and strengthening the \`BgPropsConstraint\` to require that \`bg\` provider components declare \`children\` as a required prop in their OwnProps type.`, + + commitSha: '0f462f8', + }, + { + components: ['radio-group'], + version: '0.13.0', + prs: ['32983'], + description: `Added proper cursor styles for RadioGroup items.`, + + commitSha: '8909359', + }, + { + components: ['card'], + version: '0.13.0', + prs: ['33343'], + description: `Fixed \`Card\` interactive cards not firing the \`onPress\` handler when clicking the card surface.`, + + commitSha: 'fcaac3b', + }, + { + components: ['textfield', 'searchfield', 'password-field'], + version: '0.13.0', + prs: ['33050'], + description: `Fixed \`isRequired\` prop not being passed to the underlying React Aria field components in TextField, SearchField, and PasswordField. Previously, \`isRequired\` was consumed locally for the secondary label text but never forwarded, which meant the input elements lacked \`aria-required="true"\` and React Aria's built-in required validation was not activated.`, + + commitSha: 'b303857', + }, + { + components: ['searchfield', 'textfield'], + version: '0.13.0', + prs: ['33081'], + description: `\`SearchField\` and \`TextField\` now automatically adapt their background color based on the parent bg context, stepping up one neutral level (e.g. neutral-1 → neutral-2) when placed on a neutral background. \`TextField\` also gains a focus ring using the \`--bui-ring\` token.`, + + commitSha: '934ac03', + }, + { + components: [], + version: '0.13.0', + prs: ['33045'], + description: `Improved \`useBreakpoint\` performance by sharing a single set of \`matchMedia\` listeners across all component instances instead of creating independent listeners per hook call.`, + + commitSha: 'cd3cb0f', + }, + { + components: ['alert', 'container', 'dialog-body', 'field-label'], + version: '0.13.0', + prs: ['33095'], + description: `Extended \`AlertProps\`, \`ContainerProps\`, \`DialogBodyProps\`, and \`FieldLabelProps\` with native div element props to allow passing attributes like \`aria-*\` and \`data-*\`.`, + + commitSha: '36987db', + }, +]; diff --git a/docs-ui/src/utils/changelogs/v0.14.0.ts b/docs-ui/src/utils/changelogs/v0.14.0.ts new file mode 100644 index 0000000000..462231e05d --- /dev/null +++ b/docs-ui/src/utils/changelogs/v0.14.0.ts @@ -0,0 +1,250 @@ +import type { ChangelogProps } from '../types'; + +export const changelog_0_14_0: ChangelogProps[] = [ + { + components: ['header'], + version: '0.14.0', + prs: ['33527'], + description: `**BREAKING**: The \`Header\` component's \`tabs\` prop now uses \`HeaderNavTabItem[]\` instead of \`HeaderTab[]\`. Tabs render as a \` @@ -94,7 +107,7 @@ description: Documentation landing page.
  • Architecture Decision Records (ADRs)
  • Deprecations
  • Utility APIs
  • -
  • Package Index
  • +
  • API References
  • FAQ
  • @@ -102,7 +115,7 @@ description: Documentation landing page. diff --git a/docs/notifications/index--old.md b/docs/notifications/index--old.md new file mode 100644 index 0000000000..d99f5144bc --- /dev/null +++ b/docs/notifications/index--old.md @@ -0,0 +1,299 @@ +--- +id: index--old +title: Getting Started (Old Frontend System) +description: How to get started with the notifications and signals +--- + +::::info +This documentation is for Backstage apps that still use the old frontend +system. If your app uses the new frontend system, read the +[current guide](./index.md) instead. +:::: + +The Backstage Notifications System provides a way for plugins and external services to send notifications to Backstage users. +These notifications are displayed in the dedicated page of the Backstage frontend UI or by frontend plugins per specific scenarios. +Additionally, notifications can be sent to external channels (like email) via "processors" implemented within plugins. + +Notifications can be optionally extended with the signals plugin, which provides a push mechanism to ensure users receive notifications immediately. + +### Upgrade to the latest version of Backstage + +To ensure your version of Backstage has all the latest notifications and signals related functionality, it's important to upgrade to the latest version. The [Backstage upgrade helper](https://backstage.github.io/upgrade-helper/) is a great tool to help ensure that you've made all the necessary changes during the upgrade! + +## About notifications + +Notifications are messages sent to either individual users or groups. +They are not intended for inter-process communication of any kind. + +There are two basic types of notifications: + +- **Broadcast**: Messages sent to all users of Backstage. +- **Entity**: Messages delivered to specific listed entities, such as Users or Groups. + +Example of use-cases: + +- System-wide announcements or alerts +- Notifications for component owners, e.g. build failures, successful deployments, new vulnerabilities +- Notifications for individuals, e.g. updates you have subscribed to, new required training courses +- Notifications pertaining to a particular entity in the catalog: A notification might apply to an entity and the owning team. + +## Installation + +:::note + +As of the `1.42.0` release of Backstage, Notifications and Signals are installed as part of the default `@backstage/create-app` instance which means you won't need to follow the installation steps outlined here. The only exception to this is adding the [Notifications tab to User Settings](#user-specific-notification-settings) to allow managing these settings. + +::: + +The following sections will walk you through the installation of the various parts of the Backstage Notification System. + +### Add Notifications Backend + +First we need to add the backend package: + +```bash title="From your Backstage root directory" +yarn --cwd packages/backend add @backstage/plugin-notifications-backend +``` + +Then we need to add it to our backend: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); +// ... +backend.add(import('@backstage/plugin-notifications-backend')); +``` + +### Add Notifications Frontend + +First we need to add the frontend package: + +```bash title="From your Backstage root directory" +yarn --cwd packages/app add @backstage/plugin-notifications +``` + +To add the notifications main menu, add the following: + +```tsx title="packages/app/src/components/Root/Root.tsx" +import { NotificationsSidebarItem } from '@backstage/plugin-notifications'; + + + + + // ... + + + +; +``` + +Also add the route to notifications: + +```tsx title="packages/app/src/App.tsx" +import { NotificationsPage } from '@backstage/plugin-notifications'; + + + // ... + } /> +; +``` + +### Optional: Add Signals + +The use of signals is optional but improves the user experience. + +#### Optional: Add Signals Backend + +Add signals to your backend by first adding the backend package: + +```bash title="From your Backstage root directory" +yarn --cwd packages/backend add @backstage/plugin-signals-backend +``` + +Then add the signals plugin to your backend: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); +// ... +backend.add(import('@backstage/plugin-signals-backend')); +``` + +#### Optional: Signals Frontend + +Start with adding the frontend package: + +```bash title="From your Backstage root directory" +yarn --cwd packages/app add @backstage/plugin-signals +``` + +To install the plugin, add the `SignalsDisplay` to your app root: + +```tsx title="packages/app/src/App.tsx" +import { SignalsDisplay } from '@backstage/plugin-signals'; + +export default app.createRoot( + <> + + + {/* highlight-add-next-line */} + + + + {routes} + + , +); +``` + +If the signals plugin is properly configured, it will be automatically discovered by the notifications plugin and used. + +### User-specific notification settings + +The notifications plugin provides a way for users to manage their notification settings. To enable this, you must +add the `UserNotificationSettingsCard` to your frontend. + +```tsx title="packages/app/src/App.tsx" +}> + + + + +``` + +![Notification Settings](notificationSettings.png) + +You can customize the origin names shown in the UI by passing an object where the keys are the origins and the values are the names you want to show in the UI. + +Each notification processor will receive its own row in the settings page, where the user can enable or disable notifications from that processor. + +### Default notification settings + +You can configure default notification settings for all users in your `app-config.yaml` file. This allows you to set up notification preferences globally, such as disabling specific channels or origins by default, implementing an opt-in strategy instead of opt-out. + +#### Channel-level defaults + +You can set a default enabled state for an entire channel. When set to `false`, the channel uses an opt-in strategy where notifications are disabled by default unless explicitly enabled by the user or for specific origins. + +```yaml +notifications: + defaultSettings: + channels: + - id: 'Web' + enabled: false # Opt-in strategy: channel disabled by default + - id: 'Email' + enabled: true # Opt-out strategy: channel enabled by default (default behavior) +``` + +#### Origin-level defaults + +You can also configure defaults for specific origins within a channel: + +```yaml +notifications: + defaultSettings: + channels: + - id: 'Web' + enabled: true # Channel is enabled by default + origins: + - id: 'plugin:scaffolder' + enabled: false # Disable scaffolder notifications by default + - id: 'plugin:catalog' + enabled: true # Enable catalog notifications by default +``` + +#### Topic-level defaults + +For even more granular control, you can set defaults for specific topics within origins: + +```yaml +notifications: + defaultSettings: + channels: + - id: 'Email' + enabled: false # Email is opt-in by default + origins: + - id: 'plugin:catalog' + enabled: true # But catalog notifications are enabled + topics: + - id: 'entity:validation:error' + enabled: false # Except validation errors +``` + +**Note:** If a channel's `enabled` flag is not set, it defaults to `true` for backwards compatibility. When a channel is set to `enabled: false`, all origins within that channel default to disabled unless explicitly enabled. + +### Automatic notification cleanup + +Notifications are deleted automatically after a certain period of time to prevent the database from growing indefinitely +and to keep the user interface clean. The default retention period is set to 1 year, meaning that notifications older +than that will be deleted automatically. + +The retention period can be configured by setting the `notifications.retention` in the `app-config.yaml` file. + +```yaml +notifications: + retention: 1y +``` + +If the retention is set to false, notifications will not be automatically deleted. + +## Scaffolder Action + +:::note + +As of the `1.42.0` release of Backstage, the Notifications Scaffolder action is installed as part of the default `@backstage/create-app` instance which means you won't need to follow the installations steps outlined here. Feel free to skip to the [Basic Example](#basic-example). + +::: + +There is also a Scaffolder action that you can use to send a notification as part of your Software Template. + +First we need to add the backend package: + +```bash title="From your Backstage root directory" +yarn --cwd packages/backend add @backstage/plugin-scaffolder-backend-module-notifications +``` + +Then we need to add it to our backend: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); +// ... +backend.add( + import('@backstage/plugin-scaffolder-backend-module-notifications'), +); +``` + +### Basic Example + +Here's an example of how you can use it in your Software Template, more details and examples can be found in the "Installed actions" screen in your Backstage instances: + +```yaml title="template.yaml" +steps: + - id: notify + name: Notify + action: notification:send + input: + recipients: entity + entityRefs: + - user:default/guest + title: 'Template executed' + info: 'Your template has been executed' + severity: 'normal' +``` + +The example above would send a notification to the Guest user (`user:default/guest`) + +## Additional info + +An example of a backend plugin sending notifications can be found in the [`@backstage/plugin-scaffolder-backend-module-notifications` package](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-notifications). + +Sources of the notifications and signals plugins: + +- [notifications](https://github.com/backstage/backstage/blob/master/plugins/notifications) +- [notifications-backend](https://github.com/backstage/backstage/blob/master/plugins/notifications-backend) +- [notifications-common](https://github.com/backstage/backstage/blob/master/plugins/notifications-common) +- [notifications-node](https://github.com/backstage/backstage/blob/master/plugins/notifications-node) +- [signals-backend](https://github.com/backstage/backstage/blob/master/plugins/signals-backend) +- [signals](https://github.com/backstage/backstage/blob/master/plugins/signals) +- [signals-node](https://github.com/backstage/backstage/blob/master/plugins/signals-node) +- [signals-react](https://github.com/backstage/backstage/blob/master/plugins/signals-react) diff --git a/docs/notifications/index.md b/docs/notifications/index.md index dce58d3214..fde24db7cb 100644 --- a/docs/notifications/index.md +++ b/docs/notifications/index.md @@ -4,6 +4,13 @@ title: Getting Started description: How to get started with the notifications and signals --- +::::info +This documentation is written for the new frontend system, which is the default +in new Backstage apps. If your Backstage app still uses the old frontend system, +read the [old frontend system version of this guide](./index--old.md) +instead. +:::: + The Backstage Notifications System provides a way for plugins and external services to send notifications to Backstage users. These notifications are displayed in the dedicated page of the Backstage frontend UI or by frontend plugins per specific scenarios. Additionally, notifications can be sent to external channels (like email) via "processors" implemented within plugins. @@ -12,7 +19,7 @@ Notifications can be optionally extended with the signals plugin, which provides ### Upgrade to the latest version of Backstage -To ensure your version of Backstage has all the latest notifications and signals related functionality, it’s important to upgrade to the latest version. The [Backstage upgrade helper](https://backstage.github.io/upgrade-helper/) is a great tool to help ensure that you’ve made all the necessary changes during the upgrade! +To ensure your version of Backstage has all the latest notifications and signals related functionality, it's important to upgrade to the latest version. The [Backstage upgrade helper](https://backstage.github.io/upgrade-helper/) is a great tool to help ensure that you've made all the necessary changes during the upgrade! ## About notifications @@ -35,7 +42,7 @@ Example of use-cases: :::note -As of the `1.42.0` release of Backstage, Notifications and Signals are installed as part of the default `@backstage/create-app` instance which means you won't need to follow the installations steps outlined here. The only exception to this is adding the [Notifications tab to User Settings](#user-specific-notification-settings) to allow managing these settings. +As of the `1.42.0` release of Backstage, Notifications and Signals are installed as part of the default `@backstage/create-app` instance which means you won't need to follow the installation steps outlined here. The only exception to this is adding the [Notifications sidebar item](#add-notifications-sidebar-item) and optionally [Notifications tab to User Settings](#user-specific-notification-settings). ::: @@ -65,35 +72,29 @@ First we need to add the frontend package: yarn --cwd packages/app add @backstage/plugin-notifications ``` -To add the notifications main menu, add the following: +Once installed, the notifications plugin is automatically available in your app through the default feature discovery. It provides a notifications page at `/notifications` and the notifications API. For more details and alternative installation methods, see [installing plugins](../frontend-system/building-apps/05-installing-plugins.md). -```tsx title="packages/app/src/components/Root/Root.tsx" +### Add Notifications Sidebar Item + +The notifications plugin does not yet include a built-in navigation item, so you need to add the `NotificationsSidebarItem` component to your sidebar manually. If you have a custom sidebar through a `NavContentBlueprint`, add the component there: + +```tsx import { NotificationsSidebarItem } from '@backstage/plugin-notifications'; - - - - // ... - - - -; -``` - -Also add the route to notifications: - -```tsx title="packages/app/src/App.tsx" -import { NotificationsPage } from '@backstage/plugin-notifications'; - - - // ... - } /> -; +// Inside your NavContentBlueprint component: + + }> + {/* ... other items ... */} + + } to="/settings"> + + +; ``` ### Optional: Add Signals -The use of signals is optional but improves the user experience. +The use of signals is optional but improves the user experience by providing real-time push updates. #### Optional: Add Signals Backend @@ -119,45 +120,51 @@ Start with adding the frontend package: yarn --cwd packages/app add @backstage/plugin-signals ``` -To install the plugin, add the `SignalsDisplay` to your app root: - -```tsx title="packages/app/src/App.tsx" -import { SignalsDisplay } from '@backstage/plugin-signals'; - -export default app.createRoot( - <> - - - {/* highlight-add-next-line */} - - - - {routes} - - , -); -``` - -If the signals plugin is properly configured, it will be automatically discovered by the notifications plugin and used. +Once installed, the signals plugin is automatically available in your app through the default feature discovery. No additional configuration is required. If the signals plugin is properly configured, it will be automatically discovered by the notifications plugin and used. ### User-specific notification settings -The notifications plugin provides a way for users to manage their notification settings. To enable this, you must -add the `UserNotificationSettingsCard` to your frontend. +The notifications plugin provides a way for users to manage their notification settings. To enable this, you can create a frontend module that adds a settings tab to the user-settings plugin using the `SubPageBlueprint`: -```tsx title="packages/app/src/App.tsx" -}> - - - - - - - +```tsx title="packages/app/src/modules/NotificationSettingsPage.tsx" +import { Content } from '@backstage/core-components'; +import { UserNotificationSettingsCard } from '@backstage/plugin-notifications'; + +export function NotificationSettingsPage() { + return ( + + + + ); +} ``` +```tsx title="packages/app/src/modules/notificationSettings.tsx" +import { createFrontendModule } from '@backstage/frontend-plugin-api'; +import { SubPageBlueprint } from '@backstage/frontend-plugin-api'; + +export const notificationSettingsModule = createFrontendModule({ + pluginId: 'user-settings', + extensions: [ + SubPageBlueprint.make({ + name: 'notifications', + params: { + path: 'notifications', + title: 'Notifications', + loader: () => + import('./NotificationSettingsPage').then(m => ( + + )), + }, + }), + ], +}); +``` + +Then install the module in your app by adding it to the features array of `createApp`, or through default feature discovery if your app is set up for it. + ![Notification Settings](notificationSettings.png) You can customize the origin names shown in the UI by passing an object where the keys are the origins and the values are the names you want to show in the UI. diff --git a/docs/notifications/processors.md b/docs/notifications/processors.md index c25b669ed6..501678d107 100644 --- a/docs/notifications/processors.md +++ b/docs/notifications/processors.md @@ -149,11 +149,91 @@ notifications: broadcastChannels: # Optional, if you wish to support broadcast notifications. - C12345678 username: 'Backstage Bot' # Optional, defaults to the name of the Slack App. + concurrencyLimit: 20 # Optional, number of messages allowed per interval. Defaults to 10. + throttleInterval: 1m # Optional, Accepts ISO-8601 duration, ms-style ("1m", "30s"), or HumanDuration ({ minutes: 2 }). Defaults to 1 minute ``` Multiple instances can be added in the `slack` array, allowing you to have multiple configurations if you need to send messages to more than one Slack workspace. Org-Wide App installation is not currently supported. +### Customize Slack Message Structure + +You can customize how notifications look in Slack by providing your own message layout through the `notificationsSlackBlockKitExtensionPoint` + +```ts +import { createBackendModule } from '@backstage/backend-plugin-api'; +import { notificationsSlackBlockKitExtensionPoint } from '@backstage/plugin-notifications-backend-module-slack'; + +export const notificationsSlackFormattingModule = createBackendModule({ + pluginId: 'notifications', + moduleId: 'slack-formatting', + register(reg) { + reg.registerInit({ + deps: { + slackBlockKit: notificationsSlackBlockKitExtensionPoint, + }, + async init({ slackBlockKit }) { + slackBlockKit.setBlockKitRenderer(payload => [ + // Custom block kit layout + ]); + }, + }); + }, +}); +``` + +If you do not register a custom renderer, the default renderer is used. + +### Broadcast Channel Routing + +For more granular control over where broadcast notifications are sent, you can use `broadcastRoutes` to route notifications to different Slack channels based on their origin and/or topic. This is useful when you want different types of notifications to go to different channels. + +```yaml +notifications: + processors: + slack: + - token: xoxb-XXXXXXXXX + # Legacy option - used as fallback when no routes match + broadcastChannels: + - general-notifications + # Route broadcasts based on origin and/or topic + broadcastRoutes: + # Most specific: matches both origin AND topic + - origin: plugin:catalog + topic: alerts + channel: catalog-alerts + # Origin only: all notifications from this origin + - origin: plugin:catalog + channel: catalog-updates + # Topic only: all notifications with this topic (any origin) + - topic: security + channel: security-team + # Multiple channels: send to several channels at once + - origin: external:monitoring + channel: + - ops-team + - on-call-alerts +``` + +#### Route Matching Precedence + +Routes are evaluated in the following order of priority: + +1. **Origin + Topic match** (most specific) - A route that specifies both `origin` and `topic` will match first +2. **Origin-only match** - A route with only `origin` specified (no `topic`) +3. **Topic-only match** - A route with only `topic` specified (no `origin`) +4. **Default fallback** - If no routes match, falls back to `broadcastChannels` + +The first matching route wins. If no routes match and no `broadcastChannels` are configured, the broadcast notification will not be sent to Slack. + +#### Configuration Options + +| Property | Type | Description | +| --------- | ---------------------- | ------------------------------------------------------------------------------------------ | +| `origin` | `string` | Optional. The notification origin to match (e.g., `plugin:catalog`, `external:my-service`) | +| `topic` | `string` | Optional. The notification topic to match (e.g., `alerts`, `updates`, `security`) | +| `channel` | `string` or `string[]` | Required. The Slack channel(s) to send to. Can be channel IDs, channel names, or user IDs | + ### Entity Requirements Entities must be annotated with the following annotation: diff --git a/docs/notifications/usage--old.md b/docs/notifications/usage--old.md new file mode 100644 index 0000000000..8e64e37c4d --- /dev/null +++ b/docs/notifications/usage--old.md @@ -0,0 +1,283 @@ +--- +id: usage--old +title: Usage (Old Frontend System) +description: How to use the notifications and signals +--- + +::::info +This documentation is for Backstage apps that still use the old frontend +system. If your app uses the new frontend system, read the +[current guide](./usage.md) instead. +:::: + +## Notifications Backend + +The notifications backend plugin provides an API to create notifications, list notifications per logged-in user, and search based on parameters. + +The plugin uses a relational [database](https://backstage.io/docs/getting-started/config/database) for persistence; no specifics are introduced in this context. + +No additional configuration in the app-config is needed, except for optional additional modules for `processors`. + +## Notifications Frontend + +The recipients of notifications have to be entities in the catalog, e.g., of the User or Group kind. + +Otherwise, no specific configuration is needed for the front-end notifications plugin. + +All parametrization is done through component properties, such as the `NotificationsSidebarItem`, which can be used as an active left-side menu item in the front-end. + +![Notifications Page](notificationsPage.png) + +In the `packages/app/src/components/Root/Root.tsx`, tweak the [properties](https://backstage.io/api/stable/functions/_backstage_plugin-notifications.index.NotificationsSidebarItem.html) of the `` per specific needs. + +## Usage + +New notifications can be sent either by a backend plugin or by an external service through the REST API. + +## Backend + +Regardless of technical feasibility, a backend plugin should avoid directly accessing the notifications REST API. +Instead, it should integrate with the `@backstage/plugin-notifications-node` to `send` (create) a new notification. + +The reasons for this approach include the propagation of authorization in the API request and improved maintenance and backward compatibility in the future. + +```ts +import { notificationService } from '@backstage/plugin-notifications-node'; + +export const myPlugin = createBackendPlugin({ + pluginId: 'myPlugin', + register(env) { + env.registerInit({ + deps: { + // ... + notificationService: notificationService, + }, + async init({ + // ... + notificationService, + }) { + httpRouter.use( + await createRouter({ + // ... + notificationService, + }), + ); + }, + }); + }, +}); +``` + +To emit a new notification: + +```ts +await notificationService.send({ + recipients /* of the broadcast or entity type */, + payload /* actual message */, +}); +``` + +Consult the [API documentation](https://github.com/backstage/backstage/blob/master/plugins/notifications-node/report.api.md) for further details. + +### External Services + +When the emitter of a notification is a Backstage backend plugin, it is mandatory to use the integration via `@backstage/plugin-notifications-node` as described above. + +If the emitter is a service external to Backstage, an HTTP POST request can be issued directly to the API, assuming that authentication is properly configured. +Refer to the [service-to-service auth documentation](https://backstage.io/docs/auth/service-to-service-auth) for more details, focusing on the Static Tokens section for the simplest setup option. + +An example request for creating a broadcast notification might look like: + +```bash +curl -X POST https://[BACKSTAGE_BACKEND]/api/notifications -H "Content-Type: application/json" -H "Authorization: Bearer YOUR_BASE64_SHARED_KEY_TOKEN" -d '{"recipients":{"type":"broadcast"},"payload": {"title": "Title of broadcast message","link": "http://foo.com/bar","severity": "high","topic": "The topic"}}' +``` + +### Scaffolder Templates + +You can use the `@backstage/plugin-scaffolder-backend-module-notifications` to send notifications when scaffolder templates are run. To install the module, add it to your backend plugin: + +```bash +yarn workspace backend add @backstage/plugin-scaffolder-backend-module-notifications +``` + +Then, add the module to your backend: + +```ts +const backend = createBackend(); +// ... +backend.add( + import('@backstage/plugin-scaffolder-backend-module-notifications'), +); +``` + +In your template you can now use `notification:send` action as part of the steps: + +```yaml +steps: + - id: notify + name: Notify + action: notification:send + input: + recipients: entity + entityRefs: + - component:default/backstage + title: 'Template executed' + info: 'Your template has been executed' + severity: 'info' + link: https://backstage.io +``` + +## Signals + +The use of signals with notifications is optional but generally enhances user experience and performance. + +When a notification is created, a new signal is emitted to a general-purpose message bus to announce it to subscribed listeners. + +The frontend maintains a persistent connection (WebSocket) to receive these announcements from the notifications channel. +The specific details of the updated or created notification should be retrieved via a request to the notifications API, except for new notifications, where the payload is included in the signal for performance reasons. + +In a frontend plugin, to subscribe to notifications' signals: + +```ts +import { useSignal } from '@backstage/plugin-signals-react'; + +const { lastSignal } = useSignal('notifications'); + +React.useEffect(() => { + /* ... */ +}, [lastSignal, notificationsApi]); +``` + +#### Using signals in your own plugin + +It's possible to use signals in your own plugin to deliver data from the backend to the frontend in near real-time. + +To use signals in your own frontend plugin, you need to add the `useSignal` hook from `@backstage/plugin-signals-react` from `@backstage/plugin-notifications-common` with optional generic type of the signal. + +```ts +// To use the same type of signal in the backend, this should be placed in a shared common package +export type MySignalType = { + user: string; + data: string; + // .... +}; + +const { lastSignal } = useSignal('my-plugin'); + +useEffect(() => { + if (lastSignal) { + // Do something with the signal + } +}, [lastSignal]); +``` + +To send signals from the backend plugin, you must add the `signalsServiceRef` to your plugin or module as a dependency. + +```ts +import { signalsServiceRef } from '@backstage/plugin-signals-node'; +export const myPlugin = createBackendPlugin({ + pluginId: 'my', + register(env) { + env.registerInit({ + deps: { + httpRouter: coreServices.httpRouter, + signals: signalsServiceRef, + }, + async init({ httpRouter, signals }) { + httpRouter.use( + await createRouter({ + signals, + }), + ); + }, + }); + }, +}); +``` + +To send the signal using the service, you can use the `publish` method. + +```ts +signals.publish({ user: 'user', data: 'test' }); +``` + +## Consuming Notifications + +In a front-end plugin, the simplest way to query a notification is by its ID: + +```ts +import { useApi } from '@backstage/core-plugin-api'; +import { notificationsApiRef } from '@backstage/plugin-notifications'; + +const notificationsApi = useApi(notificationsApiRef); + +notificationsApi.getNotification(yourId); + +// or with connection to signals: +notificationsApi.getNotification(lastSignal.notification_id); +``` + +## Metadata Field + +The metadata field is a freeform object that is designed to be used by processors. + +### Well-known Notification Metadata Fields + +Below are metadata fields that will be commonly used between processors and have defined schematics. + +#### backstage.io/body.markdown + +```ts +# Example: +const payload = { + title: 'Entities Require Attention', + description: 'Entities: Service A, Service B', + metadata: { + 'backstage.io/body.markdown': ` + # Entities + - Service A + - System B + ` + } +} +``` + +This value of this metadata field should be the notification message in markdown format. This allows additional formatting options for processors that support markdown. + +### Usage + +Below is an example of using the `backstage.io/body.markdown` metadata field in a custom processor. + +When sending a notification: + +```ts +notificationService.send({ + recipients: { type: 'entity', entityRef: 'group/default:team-a' }, + payload: { + title: 'Notification', + description: 'Description' + metadata: { + 'backstage.io/body.markdown': ` + ### Notification + Description + `, + }, + }, +}); +``` + +In the processor, you can then use the metadata field accordingly: + +```ts +async postProcess(notification: Notification): Promise { + // We suggest you parse the metadata field with a schema, i.e. Zod + const parseResult = CustomProcessorMetadataSchema.safeParse(notification.payload.metadata ?? {}); + const metadata = parseResult.success ? parseResult.data : {}; + + customNotificationSender.send({ + to: getUsers(notification.recipients), + subject: notification.payload.title, + markdownText: metadata['backstage.io/body.markdown'] ?? notification.payload.description, + }); +} +``` diff --git a/docs/notifications/usage.md b/docs/notifications/usage.md index 94de585d21..279a1953e1 100644 --- a/docs/notifications/usage.md +++ b/docs/notifications/usage.md @@ -4,6 +4,13 @@ title: Usage description: How to use the notifications and signals --- +::::info +This documentation is written for the new frontend system, which is the default +in new Backstage apps. If your Backstage app still uses the old frontend system, +read the [old frontend system version of this guide](./usage--old.md) +instead. +:::: + ## Notifications Backend The notifications backend plugin provides an API to create notifications, list notifications per logged-in user, and search based on parameters. @@ -18,11 +25,11 @@ The recipients of notifications have to be entities in the catalog, e.g., of the Otherwise, no specific configuration is needed for the front-end notifications plugin. -All parametrization is done through component properties, such as the `NotificationsSidebarItem`, which can be used as an active left-side menu item in the front-end. +The `NotificationsSidebarItem` component can be used as an active left-side menu item in the front-end. Since the notifications plugin does not yet include a built-in navigation item, it needs to be added manually to your sidebar through a `NavContentBlueprint` in a custom app module. See the [Getting Started](./index.md#add-notifications-sidebar-item) guide for setup instructions. ![Notifications Page](notificationsPage.png) -In the `packages/app/src/components/Root/Root.tsx`, tweak the [properties](https://backstage.io/docs/reference/plugin-notifications.notificationssidebaritem) of the `` per specific needs. +You can customize the sidebar item using its [properties](https://backstage.io/api/stable/functions/_backstage_plugin-notifications.index.NotificationsSidebarItem.html) to fit your specific needs. ## Usage @@ -225,7 +232,7 @@ Below are metadata fields that will be commonly used between processors and have # Example: const payload = { title: 'Entities Require Attention', - description: 'Entities: Service A, Service B' + description: 'Entities: Service A, Service B', metadata: { 'backstage.io/body.markdown': ` # Entities diff --git a/docs/openapi/01-getting-started.md b/docs/openapi/01-getting-started.md index 875a369dbf..c300ac1370 100644 --- a/docs/openapi/01-getting-started.md +++ b/docs/openapi/01-getting-started.md @@ -26,7 +26,14 @@ This tutorial assumes that you're already familiar with the following, 1. How to build a Backstage plugin. 2. `Express.js` and `Typescript` -3. OpenAPI 3.0 schemas +3. OpenAPI 3.1 schemas + +:::note OpenAPI Version Support +Backstage supports both OpenAPI 3.0 and 3.1 specifications. If you have existing OpenAPI 3.0 specs, we recommend that you migrate them to 3.1. The main changes are: + +- Replace `nullable: true` with `type: ['string', 'null']` or use `anyOf`/`oneOf` +- Remove `allowReserved` from path parameters (only valid on query/cookie parameters in 3.1) + ::: ### Setting up diff --git a/docs/overview/roadmap.md b/docs/overview/roadmap.md index d9a03d9668..d1a290e14f 100644 --- a/docs/overview/roadmap.md +++ b/docs/overview/roadmap.md @@ -55,4 +55,4 @@ guidelines to get started. If you have specific questions about the roadmap, please create an [issue](https://github.com/backstage/backstage/issues/new/choose), ping us on -[Discord](https://discord.gg/backstage-687207715902193673), or [book time](https://info.backstage.spotify.com/office-hours) with the Spotify team. +[Discord](https://discord.gg/backstage-687207715902193673), or [book time](https://spoti.fi/backstageofficehours) with the Spotify team. diff --git a/docs/overview/support.md b/docs/overview/support.md index 0433683406..9ff2e302b8 100644 --- a/docs/overview/support.md +++ b/docs/overview/support.md @@ -15,7 +15,7 @@ description: Support and Community Details and Links - [Code of Conduct](https://github.com/backstage/backstage/blob/master/CODE_OF_CONDUCT.md) - This is how we roll. - [Blog](https://backstage.io/blog/) - Announcements and updates. -- [Newsletter](https://info.backstage.spotify.com/newsletter_subscribe) - Subscribe to +- [Newsletter](https://spoti.fi/backstagenewsletter) - Subscribe to our email newsletter. - Give us a star ⭐️ - If you are using Backstage or think it is an interesting project, we would love a star! ❤️ diff --git a/docs/overview/technical-overview.md b/docs/overview/technical-overview.md index 63c20dbf2f..1fbc22cfc7 100644 --- a/docs/overview/technical-overview.md +++ b/docs/overview/technical-overview.md @@ -10,7 +10,7 @@ Backstage is an open source framework for building developer portals that was cr Backstage is powered by a centralized [software catalog](#software-catalog-system-model) and utilizes an abstraction layer that sits on top of all of your infrastructure and developer tooling, allowing you to manage all of your software, services, tooling, and testing in one place. -Backstage uses a [plugin-architecture](#plugin-architecture-overview) which allows you customize the functionality of your Backstage application using a wide variety of available plugins or you can write your own. It also includes automated templates that your teams can use to create new microservices, helping to ensure consistency and adherence to your best practices. Backstage also provides the ability to create, maintain, and find the documentation for all of your software. +Backstage uses a [plugin-architecture](#plugin-architecture-overview) which allows you to customize the functionality of your Backstage application using a wide variety of available plugins or you can write your own. It also includes automated templates that your teams can use to create new microservices, helping to ensure consistency and adherence to your best practices. Backstage also provides the ability to create, maintain, and find the documentation for all of your software. Backstage is now a [CNCF incubation project](https://backstage.io/blog/2022/03/16/backstage-turns-two#out-of-the-sandbox-and-into-incubation). diff --git a/docs/overview/threat-model.md b/docs/overview/threat-model.md index 129bd85930..5c33c5f8e6 100644 --- a/docs/overview/threat-model.md +++ b/docs/overview/threat-model.md @@ -19,18 +19,18 @@ An **operator** is a user responsible for configuring and maintaining an instanc A **builder** is an internal or external code contributor and end up having a similar level of access as operators. When installing Backstage plugins you should vet them just like any other package from an external source. While it’s possible to limit the impact of for example a supply chain attack by splitting the deployment into separate services with different plugins, the Backstage project itself does not aim to prevent these kinds of attacks or in any other way sandbox or limit the access of plugins. -An **external user** is a user that does not belong to the other two groups, for example a malicious actor outside of the organization. The security model of Backstage currently assumes that this group does not have any direct access to Backstage, and it is the responsibility of each adopter of Backstage to make sure this is the case. +An **external user** is a user that does not belong to the other three groups, for example a malicious actor outside of the organization. The security model of Backstage currently assumes that this group does not have any direct access to Backstage, and it is the responsibility of each adopter of Backstage to make sure this is the case. ## Operator Responsibilities :::info This section assumes that you are using the -[new backend system](../backend-system/index.md) and at least Backstage release [version 1.24](../releases/v1.24.0.md). Before that Backstage did not come with built-in protection against unauthorized access and you were required to deploy it in a protected environment. +[backend system](../backend-system/index.md) and at least Backstage release [version 1.24](../releases/v1.24.0.md). Before that Backstage did not come with built-in protection against unauthorized access and you were required to deploy it in a protected environment. ::: Backstage is primarily designed to be deployed in a protected environment rather than being exposed to the public internet. From a confidentiality and integrity perspective, Backstage is designed to protect against unauthorized access to data and to ensure that data is not tampered with. However, Backstage does not provide more than rudimentary protection against denial of service attacks, and it is the responsibility of the operator to ensure that the Backstage deployment is protected against such attacks. A common and recommended way to protect a Backstage deployment from unauthorized access is to deploy it behind an authenticating proxy such as AWS’s ALB, GCP’s IAP, or Cloudflare Access. -Users that are signed-in in to Backstage generally have full access to all information and actions. If more fine-grained control is required, the [permissions system](../permissions/overview.md) should be enabled and configured to restrict access as necessary. +Users that are signed in to Backstage generally have full access to all information and actions. If more fine-grained control is required, the [permissions system](../permissions/overview.md) should be enabled and configured to restrict access as necessary. An operator is responsible for protecting the integrity of configuration files as it may otherwise be possible to introduce vulnerable configurations, as well as the confidentiality of configured secrets related to Backstage as these typically include authentication details to third party systems. diff --git a/docs/overview/versioning-policy.md b/docs/overview/versioning-policy.md index 669a982e1a..8247889c1c 100644 --- a/docs/overview/versioning-policy.md +++ b/docs/overview/versioning-policy.md @@ -177,7 +177,7 @@ When we say _Supporting_ a Node.js release, that means the following: - New Backstage projects created with `@backstage/create-app` will have their `engines.node` version set accordingly. - Dropping compatibility with unsupported releases is not considered a breaking change. This includes using new syntax or APIs, as well as bumping dependencies that drop support for these versions. -Based on the above Backstage supports Node.js 20 and 22 as of the `1.33.0` release. +Based on the above Backstage supports Node.js 22 and 24 as of the `1.46.0` release. ## TypeScript Releases @@ -195,4 +195,4 @@ The Backstage project recommends and supports using PostgreSQL for persistent st The PostgreSQL [versioning policy](https://www.postgresql.org/support/versioning/) is to release a new major version every year with new features which is then supported for 5 years after its initial release. -Our policy mirrors the PostgreSQL versioning policy - we will support the last 5 major versions. We will also test the newest and oldest versions in that range. For example, if the range we support is currently 13 to 17, then we would only test 13 and 17 explicitly. +Our policy mirrors the PostgreSQL versioning policy - we will support the last 5 major versions. We will also test the newest and oldest versions in that range. For example, if the range we support is currently 14 to 18, then we would only test 14 and 18 explicitly. diff --git a/docs/overview/what-is-backstage.md b/docs/overview/what-is-backstage.md index fa1c75de44..024bea8f81 100644 --- a/docs/overview/what-is-backstage.md +++ b/docs/overview/what-is-backstage.md @@ -39,8 +39,6 @@ Out of the box, Backstage includes: Backstage is a CNCF Incubation project after graduating from Sandbox. Read the announcement [here](https://backstage.io/blog/2022/03/16/backstage-turns-two#out-of-the-sandbox-and-into-incubation). -CNCF logo - ## Benefits - For _engineering managers_, it allows you to maintain standards and best diff --git a/docs/permissions/concepts.md b/docs/permissions/concepts.md index 7c8b9e9f69..e43c944eff 100644 --- a/docs/permissions/concepts.md +++ b/docs/permissions/concepts.md @@ -22,6 +22,6 @@ In many cases, a permission represents a user's interaction with another object. ### Conditional decisions -[Rules](../references/glossary.md#rule-permission-plugin) need additional data before they can be used in a decision. Once a [rule](../references/glossary.md#rule-permission-plugin) is bound to relevant information it forms a [condition](../references/glossary.md#condition-permission-plugin). Conditional decisions tell the [permission framework](#permission) to delegate evaluation to the [plugin](#plugin) that owns the corresponding [resource](#resource-permission-plugin). Permission requests that result in a conditional decision are allowed if all of the provided conditions evaluate to be true. +[Rules](../references/glossary.md#rule-permission-plugin) need additional data before they can be used in a decision. Once a [rule](../references/glossary.md#rule-permission-plugin) is bound to relevant information it forms a [condition](../references/glossary.md#condition-permission-plugin). Conditional decisions tell the [permission framework](#permission) to delegate evaluation to the [plugin](../references/glossary.md#plugin) that owns the corresponding [resource](../references/glossary.md#resource-permission-plugin). Permission requests that result in a conditional decision are allowed if all of the provided conditions evaluate to be true. A good example would be the catalog plugin's "has annotation" rule which needs to know what annotation to look for on a given entity. The permission framework would respond to a request by the catalog plugin in this case with a condition decision. The catalog plugin would then need to correctly filter for entities matching the "has annotations" condition. This conditional behavior avoids coupling between policies and resource schemas, and allows plugins to evaluate complex rules in an efficient way. For example, a plugin may convert a conditional decision to a database query instead of loading and filtering objects in memory. diff --git a/docs/permissions/custom-rules.md b/docs/permissions/custom-rules.md index 7bfcd1220a..039805bcb4 100644 --- a/docs/permissions/custom-rules.md +++ b/docs/permissions/custom-rules.md @@ -4,29 +4,26 @@ title: Defining custom permission rules description: How to define custom permission rules for existing resources --- -:::info -This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/custom-rules--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! -::: - For some use cases, you may want to define custom [rules](../references/glossary.md#rule-permission-plugin) in addition to the ones provided by a plugin. In the [previous section](./writing-a-policy.md) we used the `isEntityOwner` rule to control access for catalog entities. Let's extend this policy with a custom rule that checks what [system](https://backstage.io/docs/features/software-catalog/system-model#system) an entity is part of. ## Define a custom rule -Plugins should export a rule factory that provides type-safety that ensures compatibility with the plugin's backend. The catalog plugin exports `createCatalogPermissionRule` from `@backstage/plugin-catalog-backend/alpha` for this purpose. Note: the `/alpha` path segment is temporary until this API is marked as stable. For this example, we'll define the rule and create a condition in `packages/backend/src/extensions/permissionsPolicyExtension.ts`. +Plugins should export a rule factory that provides type-safety that ensures compatibility with the plugin's backend. The catalog plugin exports `createCatalogPermissionRule` from `@backstage/plugin-catalog-backend/alpha` for this purpose. Note: the `/alpha` path segment is temporary until this API is marked as stable. For this example, we'll define the rule and create a condition in `packages/backend/src/permissionRules.ts`. We use `zod` and `@backstage/catalog-model` in our example below. To install them run: ```bash title="from your Backstage root directory" -yarn --cwd packages/backend add zod @backstage/catalog-model +yarn --cwd packages/backend add zod@3 @backstage/catalog-model ``` -```ts title="packages/backend/src/extensions/permissionsPolicyExtension.ts" -... - +```ts title="packages/backend/src/permissionRules.ts" import type { Entity } from '@backstage/catalog-model'; -import { catalogEntityPermissionResourceRef } from '@backstage/plugin-catalog-backend/alpha'; -import { createConditionFactory, createPermissionRule } from '@backstage/plugin-permission-node'; -import { z } from 'zod'; +import { catalogEntityPermissionResourceRef } from '@backstage/plugin-catalog-node/alpha'; +import { + createConditionFactory, + createPermissionRule, +} from '@backstage/plugin-permission-node'; +import { z } from 'zod/v3'; export const isInSystemRule = createPermissionRule({ name: 'IS_IN_SYSTEM', @@ -59,48 +56,13 @@ const isInSystem = createConditionFactory(isInSystemRule); For a more detailed explanation on defining rules, refer to the [documentation for plugin authors](./plugin-authors/03-adding-a-resource-permission-check.md#adding-support-for-conditional-decisions). -Still in the `packages/backend/src/extensions/permissionsPolicyExtension.ts` file, let's use the condition we just created in our `CustomPermissionPolicy`. +In the `packages/backend/src/extensions/permissionsPolicyExtension.ts` file created in the [Writing a Policy](./writing-a-policy.md) section, let's use the condition we just created: ```ts title="packages/backend/src/extensions/permissionsPolicyExtension.ts" ... -/* highlight-remove-next-line */ -import { catalogEntityPermissionResourceRef } from '@backstage/plugin-catalog-backend/alpha'; +import { policyExtensionPoint } from '@backstage/plugin-permission-node/alpha'; /* highlight-add-next-line */ -import { catalogEntityPermissionResourceRef, createCatalogConditionalDecision, catalogConditions } from '@backstage/plugin-catalog-backend/alpha'; -/* highlight-remove-next-line */ -import { createConditionFactory, createPermissionRule } from '@backstage/plugin-permission-node'; -/* highlight-add-next-line */ -import { createConditionFactory, createPermissionRule, PermissionPolicy, PolicyQuery, PolicyQueryUser } from '@backstage/plugin-permission-node'; -/* highlight-add-start */ -import { AuthorizeResult, PolicyDecision, isResourcePermission } from '@backstage/plugin-permission-common'; -/* highlight-add-end */ -... - -export const isInSystemRule = createPermissionRule({ - name: 'IS_IN_SYSTEM', - description: 'Checks if an entity is part of the system provided', - resourceRef: catalogEntityPermissionResourceRef, - paramsSchema: z.object({ - systemRef: z - .string() - .describe('SystemRef to check the resource is part of'), - }), - apply: (resource: Entity, { systemRef }) => { - if (!resource.relations) { - return false; - } - - return resource.relations - .filter(relation => relation.type === 'partOf') - .some(relation => relation.targetRef === systemRef); - }, - toQuery: ({ systemRef }) => ({ - key: 'relations.partOf', - values: [systemRef], - }), -}); - -const isInSystem = createConditionFactory(isInSystemRule); +import { isInSystem } from '../permissionRules'; class CustomPermissionPolicy implements PermissionPolicy { async handle( @@ -131,8 +93,6 @@ class CustomPermissionPolicy implements PermissionPolicy { return { result: AuthorizeResult.ALLOW }; } } - -... ``` ## Provide the rule during plugin setup @@ -147,19 +107,14 @@ The `PermissionsRegistryService` is a fairly new addition and not yet supported To install custom rules in a plugin, we need to use the [`PermissionsRegistryService`](../backend-system/core-services/permissionsRegistry.md). Here's the steps you'll need to take to add the `isInSystemRule` we created above to the catalog: -1. We will be using the `@backstage/plugin-catalog-node` package as it contains the extension point we need. Run this to add it: +1. Create a `catalogPermissionRules.ts` file in the `packages/backend/src/extensions` folder with the following content: - ```bash title="from your Backstage root directory" - yarn --cwd packages/backend add @backstage/plugin-catalog-node - ``` - -2. Next create a `catalogPermissionRules.ts` file in the `packages/backend/src/modules` folder. -3. Then add this as the contents of the new `catalogPermissionRules.ts` file: - - ```typescript title="packages/backend/src/modules/catalogPermissionRules.ts" - import { createBackendModule } from '@backstage/backend-plugin-api'; - import { catalogPermissionExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; - import { isInSystemRule } from './permissionPolicyExtension'; + ```typescript title="packages/backend/src/extensions/catalogPermissionRules.ts" + import { + coreServices, + createBackendModule, + } from '@backstage/backend-plugin-api'; + import { isInSystemRule } from '../permissionRules'; export default createBackendModule({ pluginId: 'catalog', @@ -175,7 +130,7 @@ To install custom rules in a plugin, we need to use the [`PermissionsRegistrySer }); ``` -4. Next we need to add this to the backend by adding the following line: +2. Next we need to add this to the backend by adding the following line: ```ts title="packages/backend/src/index.ts" // catalog plugin @@ -184,10 +139,10 @@ To install custom rules in a plugin, we need to use the [`PermissionsRegistrySer import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ); /* highlight-add-next-line */ - backend.add(import('./modules/catalogPermissionRules')); + backend.add(import('./extensions/catalogPermissionRules')); ``` -5. Now when you run you Backstage instance - `yarn start` - the rule will be added to the catalog plugin. +3. Now when you run your Backstage instance - `yarn start` - the rule will be added to the catalog plugin. The updated policy will allow catalog entity resource permissions if any of the following are true: diff --git a/docs/permissions/getting-started.md b/docs/permissions/getting-started.md index f5b9092ec5..3e40b4597c 100644 --- a/docs/permissions/getting-started.md +++ b/docs/permissions/getting-started.md @@ -4,10 +4,6 @@ title: Getting Started description: How to get started with the permission framework as an integrator --- -:::info -This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/getting-started--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! -::: - Backstage integrators control permissions by writing a policy. In general terms, a policy is simply an async function which receives a request to authorize a specific action for a user and (optional) resource, and returns a decision on whether to authorize that permission. Integrators can implement their own policies from scratch, or adopt reusable policies written by others. ## Prerequisites diff --git a/docs/permissions/plugin-authors/01-setup.md b/docs/permissions/plugin-authors/01-setup.md index 743c5c9718..4ed348dae6 100644 --- a/docs/permissions/plugin-authors/01-setup.md +++ b/docs/permissions/plugin-authors/01-setup.md @@ -4,10 +4,6 @@ title: 1. Tutorial setup description: How to get started with the permission framework as a plugin author --- -:::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/plugin-authors/01-setup--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - The following tutorial is designed to help plugin authors add support for permissions to their plugins. We'll add support for permissions to example `todo-list` and `todo-list-backend` plugins, but the process should be similar for other plugins! The rest of this page is focused on adding the `todo-list` and `todo-list-backend` plugins to your Backstage instance. If you want to add support for permissions to your own plugin instead, feel free to skip to the [next section](./02-adding-a-basic-permission-check.md). diff --git a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md index 8ba3ac3152..3790fc8577 100644 --- a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md +++ b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md @@ -4,10 +4,6 @@ title: 2. Adding a basic permission check description: Explains how to add a basic permission check to a Backstage plugin --- -:::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/plugin-authors/02-adding-a-basic-permission-check--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - If the outcome of a permission check doesn't need to change for different [resources](../../references/glossary.md#resource-permission-plugin), you can use a _basic permission check_. For this kind of check, we simply need to define a permission, and call `authorize` with it. For this tutorial, we'll use a basic permission check to authorize the `create` endpoint in our todo-backend. This will allow Backstage integrators to control whether each of their users is authorized to create todos by adjusting their [permission policy](../../references/glossary.md#policy-permission-plugin). diff --git a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md index afa56307ce..afdb3b8c62 100644 --- a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md +++ b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md @@ -4,10 +4,6 @@ title: 3. Adding a resource permission check description: Explains how to add a resource permission check to a Backstage plugin --- -:::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/plugin-authors/03-adding-a-resource-permission-check--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - When performing updates (or other operations) on specific [resources](../../references/glossary.md#resource-permission-plugin), the permissions framework allows for the decision to be based on characteristics of the resource itself. This means that it's possible to write policies that (for example) allow the operation for users that own a resource, and deny the operation otherwise. ## Creating the update permission @@ -130,7 +126,7 @@ import { createPermissionRule, } from '@backstage/plugin-permission-node'; import { TODO_LIST_RESOURCE_TYPE } from '@internal/plugin-todo-list-common'; -import { z } from 'zod'; +import { z } from 'zod/v3'; import { Todo, TodoFilter } from './todos'; export const todoListPermissionResourceRef = createPermissionResourceRef< diff --git a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md index b81382ee16..28ed56608c 100644 --- a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md +++ b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md @@ -4,10 +4,6 @@ title: 4. Authorizing access to paginated data description: Explains how to authorize access to paginated data in a Backstage plugin --- -:::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - Authorizing `GET /todos` is similar to the update endpoint, in that it should be possible to authorize access based on the characteristics of each resource. However, we'll need to authorize a list of resources for this endpoint. One possible solution may leverage the batching functionality to authorize all of the todos, and then returning only the ones for which the decision was `ALLOW`: @@ -43,7 +39,7 @@ To avoid this situation, the permissions framework has support for filtering ite :::note Note -In order to perform authorization filtering in this way, the data source must allow filters to be logically combined with AND, OR, and NOT operators. The conditional decisions returned by the permissions framework use a [nested object](https://backstage.io/docs/reference/plugin-permission-common.permissioncriteria) to combine conditions. If you're implementing a filter API from scratch, we recommend using the same shape for ease of interoperability. If not, you'll need to implement a function which transforms the nested object into your own format. +In order to perform authorization filtering in this way, the data source must allow filters to be logically combined with AND, OR, and NOT operators. The conditional decisions returned by the permissions framework use a [nested object](https://backstage.io/api/stable/types/_backstage_plugin-permission-common.PermissionCriteria.html) to combine conditions. If you're implementing a filter API from scratch, we recommend using the same shape for ease of interoperability. If not, you'll need to implement a function which transforms the nested object into your own format. ::: diff --git a/docs/permissions/plugin-authors/05-frontend-authorization.md b/docs/permissions/plugin-authors/05-frontend-authorization.md index 4d673e3b4f..5aadae1d23 100644 --- a/docs/permissions/plugin-authors/05-frontend-authorization.md +++ b/docs/permissions/plugin-authors/05-frontend-authorization.md @@ -79,7 +79,7 @@ function AddTodo({ onAdd }: { onAdd: (title: string) => any }) { } ``` -Here we are using the [`usePermission` hook](https://backstage.io/docs/reference/plugin-permission-react.usepermission) to communicate with the permission policy and receive a decision on whether this user is authorized to create a todo list item. +Here we are using the [`usePermission` hook](https://backstage.io/api/stable/functions/_backstage_plugin-permission-react.usePermission.html) to communicate with the permission policy and receive a decision on whether this user is authorized to create a todo list item. It's really that simple! Let's change our policy to test the disabled button: @@ -98,7 +98,7 @@ And now you should see that you are not able to create a todo item from the fron ## Using `RequirePermission` -Providing a disabled state can be a helpful signal to users, but there may be cases where hiding the element is preferred. For such cases, you can use the provided [`RequirePermission` component](https://backstage.io/docs/reference/plugin-permission-react.requirepermission): +Providing a disabled state can be a helpful signal to users, but there may be cases where hiding the element is preferred. For such cases, you can use the provided [`RequirePermission` component](https://backstage.io/api/stable/functions/_backstage_plugin-permission-react.RequirePermission.html): ```tsx title="plugins/todo-list/src/components/TodoListPage/TodoListPage.tsx" import { diff --git a/docs/permissions/writing-a-policy.md b/docs/permissions/writing-a-policy.md index 576afcac21..652c698be2 100644 --- a/docs/permissions/writing-a-policy.md +++ b/docs/permissions/writing-a-policy.md @@ -4,10 +4,6 @@ title: Writing a permission policy description: How to write your own permission policy as a Backstage integrator --- -:::info -This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/writing-a-policy--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! -::: - In the [previous section](./getting-started.md), we were able to set up the permission framework and make a simple change to our `TestPermissionPolicy` to confirm that policy is indeed wired up correctly. That policy looked like this: @@ -66,9 +62,9 @@ Now with this policy in place the ability to delete entities in the Catalog is n ## What's in a policy? -Let's break this down a bit further. The request object of type [PolicyQuery](https://backstage.io/docs/reference/plugin-permission-node.policyquery) is a simple wrapper around [the Permission object](https://backstage.io/docs/reference/plugin-permission-common.permission). This permission object encapsulates information about the action that the user is attempting to perform (See [the Concepts page](./concepts.md) for more details). +Let's break this down a bit further. The request object of type [PolicyQuery](https://backstage.io/api/stable/types/_backstage_plugin-permission-node.index.PolicyQuery.html) is a simple wrapper around [the Permission object](https://backstage.io/api/stable/types/_backstage_plugin-permission-common.Permission.html). This permission object encapsulates information about the action that the user is attempting to perform (See [the Concepts page](./concepts.md) for more details). -In the policy above, we are checking to see if the provided action is a catalog entity delete action, which is the permission that the catalog plugin authors have created to represent the action of unregistering a catalog entity. If this is the case, we return a [Definitive Policy Decision](https://backstage.io/docs/reference/plugin-permission-common.definitivepolicydecision) of DENY. In all other cases, we return ALLOW (resulting in an allow-by-default behavior). +In the policy above, we are checking to see if the provided action is a catalog entity delete action, which is the permission that the catalog plugin authors have created to represent the action of unregistering a catalog entity. If this is the case, we return a [Definitive Policy Decision](https://backstage.io/api/stable/types/_backstage_plugin-permission-common.DefinitivePolicyDecision.html) of DENY. In all other cases, we return ALLOW (resulting in an allow-by-default behavior). As we confirmed in the previous section, we know that this now prevents us from unregistering catalog components. Hooray! But you may notice that this prevents _anyone_ from unregistering a component, which is not a very realistic policy. Let's improve this policy by disabling the unregister action _unless you are the owner of this component_. @@ -115,7 +111,7 @@ class CustomPermissionPolicy implements PermissionPolicy { if (isPermission(request.permission, catalogEntityDeletePermission)) { /* highlight-remove-start */ return { - result: AuthorizeResult.DENY, + result: AuthorizeResult.DENY, }; /* highlight-remove-end */ /* highlight-add-start */ @@ -134,9 +130,9 @@ class CustomPermissionPolicy implements PermissionPolicy { Let's walk through the new code that we just added. -Instead of returning an Definitive Policy Decision, we use factory methods to construct a [Conditional Policy Decision](https://backstage.io/docs/reference/plugin-permission-common.conditionalpolicydecision) (See the [Concepts page](./concepts.md) for more details). Since the policy doesn't have enough information to determine if `user` is the entity owner, this criteria is encapsulated within the conditional decision. However, `createCatalogConditionalDecision` will not compile unless `request.permission` is a catalog entity [`ResourcePermission`](https://backstage.io/docs/reference/plugin-permission-common.resourcepermission). This type constraint ensures that policies return conditional decisions that are compatible with the requested permission. To address this, we use [`isPermission`](https://backstage.io/docs/reference/plugin-permission-common.ispermission) to ["narrow"](https://www.typescriptlang.org/docs/handbook/2/narrowing.html) the type of `request.permission` to `ResourcePermission<'catalog-entity'>`. This matches the runtime behavior that was in place before, but you'll notice that the type of `request.permission` has changed within the scope of that `if` statement. +Instead of returning an Definitive Policy Decision, we use factory methods to construct a [Conditional Policy Decision](https://backstage.io/api/stable/types/_backstage_plugin-permission-common.ConditionalPolicyDecision.html) (See the [Concepts page](./concepts.md) for more details). Since the policy doesn't have enough information to determine if `user` is the entity owner, this criteria is encapsulated within the conditional decision. However, `createCatalogConditionalDecision` will not compile unless `request.permission` is a catalog entity [`ResourcePermission`](https://backstage.io/api/stable/types/_backstage_plugin-permission-common.ResourcePermission.html). This type constraint ensures that policies return conditional decisions that are compatible with the requested permission. To address this, we use [`isPermission`](https://backstage.io/api/stable/functions/_backstage_plugin-permission-common.isPermission.html) to ["narrow"](https://www.typescriptlang.org/docs/handbook/2/narrowing.html) the type of `request.permission` to `ResourcePermission<'catalog-entity'>`. This matches the runtime behavior that was in place before, but you'll notice that the type of `request.permission` has changed within the scope of that `if` statement. -The `catalogConditions` object contains all of the rules defined by the catalog plugin. These rules can be combined to form a [`PermissionCriteria`](https://backstage.io/docs/reference/plugin-permission-common.permissioncriteria) object, but for this case we only need to use the `isEntityOwner` rule. This rule accepts a list of entity refs that represent User identity and Group membership used to determine ownership. The second argument to `PermissionPolicy#handle` provides us with a `PolicyQueryUser` object, from which we can grab the user's `ownershipEntityRefs`. We provide an empty array as a fallback since the user may be anonymous. +The `catalogConditions` object contains all of the rules defined by the catalog plugin. These rules can be combined to form a [`PermissionCriteria`](https://backstage.io/api/stable/types/_backstage_plugin-permission-common.PermissionCriteria.html) object, but for this case we only need to use the `isEntityOwner` rule. This rule accepts a list of entity refs that represent User identity and Group membership used to determine ownership. The second argument to `PermissionPolicy#handle` provides us with a `PolicyQueryUser` object, from which we can grab the user's `ownershipEntityRefs`. We provide an empty array as a fallback since the user may be anonymous. You should now be able to see in your Backstage app that the unregister entity button is enabled for entities that you own, but disabled for all other entities! @@ -190,7 +186,7 @@ class CustomPermissionPolicy implements PermissionPolicy { } ``` -In this example, we use [`isResourcePermission`](https://backstage.io/docs/reference/plugin-permission-common.isresourcepermission) to match all permissions with a resource type of `catalog-entity`. Just like `isPermission`, this helper will "narrow" the type of `request.permission` and enable the use of `createCatalogConditionalDecision`. In addition to the behavior you observed before, you should also see that catalog entities are no longer visible unless you are the owner - success! +In this example, we use [`isResourcePermission`](https://backstage.io/api/stable/functions/_backstage_plugin-permission-common.isResourcePermission.html) to match all permissions with a resource type of `catalog-entity`. Just like `isPermission`, this helper will "narrow" the type of `request.permission` and enable the use of `createCatalogConditionalDecision`. In addition to the behavior you observed before, you should also see that catalog entities are no longer visible unless you are the owner - success! :::note Note diff --git a/docs/plugins/add-to-directory.md b/docs/plugins/add-to-directory.md index 8be0d764dd..a87062e207 100644 --- a/docs/plugins/add-to-directory.md +++ b/docs/plugins/add-to-directory.md @@ -4,12 +4,15 @@ title: Add to Directory description: Documentation on Adding Plugin to Plugin Directory --- +:::caution Legacy Documentation + +This section is part of the legacy plugins documentation. The process for adding plugins to the directory described here is still current. + +::: + ## Adding a Plugin to the Directory -To add a new plugin to the [plugin directory](https://backstage.io/plugins) -create a file in -[`microsite/data/plugins`](https://github.com/backstage/backstage/tree/master/microsite/data/plugins) -with your plugin's information. Example: +To add a new plugin to the [plugin directory](https://backstage.io/plugins) create a file with the following pattern `.yaml` where `` is the name of your plugin. This file will go in [`microsite/data/plugins`](https://github.com/backstage/backstage/tree/master/microsite/data/plugins) with your plugin's information. Example: ```yaml --- @@ -26,13 +29,24 @@ npmPackageName: # Your npm package name E.g. '@backstage/plugin-' quotes ar addedDate: # The date plugin added to directory E.g. '2022-10-01' quotes are required ``` +:::tip + +You can validate your YAML file is correct by running the following from the root of the repo: + +1. First run `yarn install` +2. Then run `node ./scripts/verify-plugin-directory.js` + +If there are any errors they will be listed and you will need to correct them. We run this same check as part of the CI. + +::: + ## Submission Tips Here are a few tips to help speed up the review process when you submit your plugin: -- For any icon that you use make sure you have the proper rights to use it. +- For any icon that you use make sure you have the proper rights to use it. If you don't have an icon then it will default to `iconUrl: '/img/logo-gradient-on-dark.svg'`. - Make sure that your package had been published on the NPM registry and that it's public. - Make sure your package on NPM has a link back to your code repo, this helps provide confidence that it's the right package. -- Where possible, please use an [NPM scope](https://docs.npmjs.com/about-scopes) that matches either your Organization name or user name, this provides trust in the plugin +- Where possible, please use an [NPM scope](https://docs.npmjs.com/about-scopes) that matches either your Organization name or user name, this provides trust in the plugin. - If your plugin has both a frontend and backend link the documentation to the frontend package but make sure it mentioned needing to install the backend package. - Where possible include a screenshot of the features in you plugin documentation, it really does help when deciding to use a plugin. diff --git a/docs/plugins/analytics.md b/docs/plugins/analytics.md index f383e8881c..784895ef49 100644 --- a/docs/plugins/analytics.md +++ b/docs/plugins/analytics.md @@ -4,6 +4,12 @@ title: Plugin Analytics description: Measuring usage of your Backstage instance. --- +:::caution Legacy Documentation + +This section is part of the legacy plugins documentation. For the new frontend system version, see [Plugin Analytics](../frontend-system/building-plugins/08-analytics.md). The concepts and events described here apply to both the old and new frontend systems. + +::: + Setting up, maintaining, and iterating on an instance of Backstage can be a large investment. To help measure return on this investment, Backstage comes with an event-based Analytics API that grants app integrators the flexibility to @@ -36,7 +42,6 @@ choice below. | Analytics Tool | Support Status | | ------------------------------------- | -------------- | -| [Google Analytics][ga] | Yes ✅ | | [Google Analytics 4][ga4] | Yes ✅ | | [New Relic Browser][newrelic-browser] | Community ✅ | | [Matomo][matomo] | Community ✅ | @@ -47,14 +52,13 @@ To suggest an integration, please [open an issue][add-tool] for the analytics tool your organization uses. Or jump to [Writing Integrations][int-howto] to learn how to contribute the integration yourself! -[ga]: https://github.com/backstage/community-plugins/blob/main/workspaces/analytics/plugins/analytics-module-ga/README.md [ga4]: https://github.com/backstage/community-plugins/blob/main/workspaces/analytics/plugins/analytics-module-ga4/README.md [newrelic-browser]: https://github.com/backstage/community-plugins/blob/main/workspaces/analytics/plugins/analytics-module-newrelic-browser/README.md [qm]: https://github.com/quantummetric/analytics-module-qm/blob/main/README.md [matomo]: https://github.com/backstage/community-plugins/blob/main/workspaces/analytics/plugins/analytics-module-matomo/README.md [add-tool]: https://github.com/backstage/backstage/issues/new?assignees=&labels=plugin&template=plugin_template.md&title=%5BAnalytics+Module%5D+THE+ANALYTICS+TOOL+TO+INTEGRATE [int-howto]: #writing-integrations -[analytics-api-type]: https://backstage.io/docs/reference/core-plugin-api.analyticsapi +[analytics-api-type]: https://backstage.io/api/stable/types/_backstage_core-plugin-api.index.AnalyticsApi.html [generic-http]: https://github.com/pfeifferj/backstage-plugin-analytics-generic/blob/main/README.md ## Key Events diff --git a/docs/plugins/backend-plugin.md b/docs/plugins/backend-plugin.md index 9b8b63fc89..91a3e04839 100644 --- a/docs/plugins/backend-plugin.md +++ b/docs/plugins/backend-plugin.md @@ -4,6 +4,12 @@ title: Backend plugins description: Creating and Developing Backend plugins --- +:::caution Legacy Documentation + +This section is part of the legacy plugins documentation. While this page already describes the new backend system patterns, the canonical documentation for building backend plugins has moved to [Building Backend Plugins and Modules](../backend-system/building-plugins-and-modules/01-index.md). + +::: + This page describes the process of creating and managing backend plugins in your Backstage repository. @@ -38,12 +44,6 @@ cd plugins/carmen-backend yarn start ``` -:::note Note - -This documentation assumes you are using the latest version of Backstage and the new backend system. If you are not, please upgrade and migrate your backend using the [Migration Guide](../backend-system/building-backends/08-migrating.md) - -::: - This will think for a bit, and then say `Listening on :7007`. In a different terminal window, now run diff --git a/docs/plugins/call-existing-api.md b/docs/plugins/call-existing-api.md index 35a6d8d599..5b5a91b708 100644 --- a/docs/plugins/call-existing-api.md +++ b/docs/plugins/call-existing-api.md @@ -4,6 +4,12 @@ title: Call Existing API description: Describes the various options that Backstage frontend plugins have, in communicating with service APIs that already exist --- +:::caution Legacy Documentation + +This section is part of the legacy plugins documentation. The frontend code examples on this page use the old frontend system APIs (`discoveryApiRef`, `fetchApiRef` from `@backstage/core-plugin-api`). The same APIs are available in the new frontend system via `@backstage/frontend-plugin-api`. The general guidance on when to use direct requests vs. the proxy vs. a backend plugin remains valid for both systems. + +::: + This article describes the various options that Backstage frontend plugins have, in communicating with service APIs that already exist. Each section below describes a possible choice, and the circumstances under which it fits. diff --git a/docs/plugins/composability.md b/docs/plugins/composability.md index d6c9abc98f..35da80a5b2 100644 --- a/docs/plugins/composability.md +++ b/docs/plugins/composability.md @@ -4,6 +4,12 @@ title: Composability System description: Documentation for the Backstage plugin composability APIs. --- +:::caution Legacy Documentation + +This page describes the composability system for the **old frontend system**, including `createRoutableExtension`, `createComponentExtension`, `RouteRef`, `ExternalRouteRef`, and component data. For the new frontend system, see [Extensions](../frontend-system/architecture/20-extensions.md), [Extension Blueprints](../frontend-system/architecture/23-extension-blueprints.md), and [Routes](../frontend-system/architecture/36-routes.md). + +::: + ## Summary This page describes the composability system that helps bring together content diff --git a/docs/plugins/create-a-plugin.md b/docs/plugins/create-a-plugin.md index ea0b088af7..8c97a3d30d 100644 --- a/docs/plugins/create-a-plugin.md +++ b/docs/plugins/create-a-plugin.md @@ -4,6 +4,12 @@ title: Create a Backstage Plugin description: Documentation on How to Create a Backstage Plugin --- +:::caution Legacy Documentation + +This page describes creating plugins for the **old frontend system**. For creating plugins using the new frontend system, see [Building Frontend Plugins](../frontend-system/building-plugins/01-index.md). For creating backend plugins, see [Building Backend Plugins and Modules](../backend-system/building-plugins-and-modules/01-index.md). + +::: + A Backstage Plugin adds functionality to Backstage. ## Create a Plugin diff --git a/docs/plugins/existing-plugins.md b/docs/plugins/existing-plugins.md deleted file mode 100644 index 181650adc1..0000000000 --- a/docs/plugins/existing-plugins.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -id: existing-plugins -title: Existing plugins -description: Lists of existing open source plugins ---- - -## The Plugin Directory - -Open source plugins that you can add to your Backstage deployment can be found -at: - -https://backstage.io/plugins - -![](https://backstage.io/blog/assets/marketplace.png) - -## Links - -- [[blog] The Plugin Directory is open](https://backstage.io/blog/2020/09/30/plugin-marketplace) diff --git a/docs/plugins/feature-flags.md b/docs/plugins/feature-flags.md index 3afb8081d5..25251e3db5 100644 --- a/docs/plugins/feature-flags.md +++ b/docs/plugins/feature-flags.md @@ -4,6 +4,12 @@ title: Feature Flags description: Details the process of defining setting and reading a feature flag. --- +:::caution Legacy Documentation + +This page describes feature flags using the **old frontend system** APIs (`createPlugin` from `@backstage/core-plugin-api` and `createApp` from `@backstage/app-defaults`). For the new frontend system version, see [Feature Flags](../frontend-system/building-plugins/09-feature-flags.md). The `FeatureFlagged` component and `featureFlagsApiRef` work the same way in both systems. + +::: + Backstage offers the ability to define feature flags inside a plugin or during application creation. This allows you to restrict parts of your plugin to those individual users who have toggled the feature flag to on. This page describes the process of defining, setting and reading a feature flag. If you are looking for using feature flags specifically with software templates please see [Writing Templates](https://backstage.io/docs/features/software-templates/writing-templates#remove-sections-or-fields-based-on-feature-flags). @@ -19,11 +25,18 @@ import { createPlugin } from '@backstage/core-plugin-api'; export const examplePlugin = createPlugin({ // ... - featureFlags: [{ name: 'show-example-feature' }], + featureFlags: [ + { + name: 'show-example-feature', + description: 'Enables the new beta dashboard view', + }, + ], // ... }); ``` +Note that the `description` property is optional. If not provided, the default "Registered in {pluginId} plugin" message is shown. + ### In the application Defining a feature flag in the application is done by adding feature flags in `featureFlags` array in the @@ -55,7 +68,7 @@ The user's selection is saved in the user's browser local storage. Once a featur ## FeatureFlagged Component -The easiest way to control content based on the state of a feature flag is to use the [FeatureFlagged](https://backstage.io/docs/reference/core-app-api.featureflagged) component. +The easiest way to control content based on the state of a feature flag is to use the [FeatureFlagged](https://backstage.io/api/stable/functions/_backstage_core-app-api.FeatureFlagged.html) component. ```ts import { FeatureFlagged } from '@backstage/core-app-api'; @@ -73,7 +86,7 @@ import { FeatureFlagged } from '@backstage/core-app-api'; ## Evaluating Feature Flag State -It is also possible to query a feature flag using the [FeatureFlags Api](https://backstage.io/docs/reference/core-plugin-api.featureflagsapi). +It is also possible to query a feature flag using the [FeatureFlags Api](https://backstage.io/api/stable/interfaces/_backstage_core-plugin-api.index.FeatureFlagsApi.html). ```ts import { useApi, featureFlagsApiRef } from '@backstage/core-plugin-api'; diff --git a/docs/plugins/index.md b/docs/plugins/index.md index f709afa894..388c3b1177 100644 --- a/docs/plugins/index.md +++ b/docs/plugins/index.md @@ -1,9 +1,15 @@ --- id: index -title: Introduction to Plugins -description: Learn about integrating various infrastructure and software development tools into Backstage through plugins. +title: Introduction to Plugins (Legacy) +description: Legacy documentation for integrating various infrastructure and software development tools into Backstage through plugins using the old frontend system. --- +:::caution Legacy Documentation + +This section covers plugin development using the **old frontend system**. For new development, please refer to the [new frontend system](../frontend-system/index.md) and [new backend system](../backend-system/index.md) documentation. The content here is kept for reference and for maintaining existing plugins that have not yet been migrated. + +::: + Backstage orchestrates a cohesive single-page application by seamlessly integrating various plugins. Our vision for the plugin ecosystem champions flexibility, empowering you to incorporate a broad spectrum of infrastructure and software development tools into Backstage as plugins. Adherence to stringent [design guidelines](../dls/design.md) guarantees a consistent and intuitive user experience across the entire plugin landscape. @@ -23,3 +29,9 @@ This approach is equally beneficial if you conceive an idea for a potentially im ## Integration with the Software Catalog Should your plugin complement the Software Catalog rather than exist as a standalone feature (for instance, as an additional tab or a card within an "Overview" tab), you'll find comprehensive guidance on achieving this integration in the [Integrating Plugin into Software Catalog guide](integrating-plugin-into-software-catalog.md). + +## Existing Plugins + +There are a plethora of existing community made plugins which can be found by going to the [Backstage Plugin Directory](https://backstage.io/plugins) + +You can read more about it in this blog post: [The Plugin Directory is now open](https://backstage.io/blog/2020/09/30/plugin-marketplace/) diff --git a/docs/plugins/integrating-plugin-into-software-catalog.md b/docs/plugins/integrating-plugin-into-software-catalog.md index ed2e25c601..1e3ebb2d2b 100644 --- a/docs/plugins/integrating-plugin-into-software-catalog.md +++ b/docs/plugins/integrating-plugin-into-software-catalog.md @@ -4,6 +4,12 @@ title: Integrate into the Software Catalog description: How to integrate a plugin into software catalog --- +:::caution Legacy Documentation + +This page describes integrating plugins into the Software Catalog using the **old frontend system** patterns (`EntitySwitch`, `EntityLayout`, `EntityLayout.Route`). For the new frontend system, entity page integrations are done using `EntityCardBlueprint` and `EntityContentBlueprint` — see [Common Extension Blueprints](../frontend-system/building-plugins/03-common-extension-blueprints.md). + +::: + > This is an advanced use case and currently is an experimental feature. Expect > API to change over time @@ -32,7 +38,7 @@ Creating the plugin... ### Reading entities from within your plugin You can access the currently selected entity using the backstage api -[`useEntity`](../reference/plugin-catalog-react.useentity.md). For example, +[`useEntity`](https://backstage.io/api/stable/functions/_backstage_plugin-catalog-react.index.useEntity.html). For example, ```tsx import { useEntity } from '@backstage/plugin-catalog-react'; @@ -45,7 +51,7 @@ export const MyPluginEntityContent = () => { ``` Internally `useEntity` makes use of -[react `Context`s](https://reactjs.org/docs/context.html). The entity context is +[react `Contexts`](https://18.react.dev/learn/passing-data-deeply-with-context). The entity context is provided by the entity page into which your plugin will be embedded. ### Import your plugin and embed in the entities page @@ -63,7 +69,7 @@ To add your component to the Entity view, you will need to modify the your plugin, you may only care about certain kinds of [entities](https://backstage.io/docs/features/software-catalog/descriptor-format), each of which has its own -[element](https://reactjs.org/docs/rendering-elements.html) for rendering. This +element for rendering. This functionality is handled by the `EntitySwitch` component: ```tsx @@ -94,7 +100,7 @@ const systemPage = ( - + diff --git a/docs/plugins/integrating-search-into-plugins.md b/docs/plugins/integrating-search-into-plugins.md index ec1c4e7a5a..cbd9c89e14 100644 --- a/docs/plugins/integrating-search-into-plugins.md +++ b/docs/plugins/integrating-search-into-plugins.md @@ -4,8 +4,10 @@ title: Integrating Search into a plugin description: How to integrate Search into a Backstage plugin --- -:::info -This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/plugins/integrating-search-into-plugins--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! +:::caution Legacy Documentation + +This section is part of the legacy plugins documentation. The backend search collator patterns described here use the new backend system and are still current. The frontend search experience examples use the old frontend system APIs. + ::: The Backstage Search Platform was designed to give plugin developers the APIs @@ -237,7 +239,7 @@ backend.start(); #### 8. Testing the collator code -To verify your implementation works as expected make sure to add tests for it. For your convenience, there is the [`TestPipeline`](https://backstage.io/docs/reference/plugin-search-backend-node.testpipeline) utility that emulates a pipeline into which you can integrate your custom collator. +To verify your implementation works as expected make sure to add tests for it. For your convenience, there is the [`TestPipeline`](https://backstage.io/api/stable/classes/_backstage_plugin-search-backend-node.index.TestPipeline.html) utility that emulates a pipeline into which you can integrate your custom collator. Look at [DefaultTechDocsCollatorFactory test](https://github.com/backstage/backstage/blob/de294ce5c410c9eb56da6870a1fab795268f60e3/plugins/techdocs-backend/src/search/DefaultTechDocsCollatorFactory.test.ts), for an example. @@ -282,7 +284,7 @@ search experience. of state necessary to perform search queries and display any results. As inputs to the query are updated (e.g. a `term` or `filter` values), the updated query is executed and `results` are refreshed. Check out the - [SearchContextValue](https://backstage.io/docs/reference/plugin-search-react.searchcontextvalue) + [SearchContextValue](https://backstage.io/api/stable/types/_backstage_plugin-search-react.index.SearchContextValue.html) for details. - The aforementioned state can be modified and/or consumed via the `useSearch()` hook, also exported by `@backstage/plugin-search-react`. diff --git a/docs/plugins/internationalization.md b/docs/plugins/internationalization.md index 4eb5e864d1..67a6b962a0 100644 --- a/docs/plugins/internationalization.md +++ b/docs/plugins/internationalization.md @@ -1,12 +1,18 @@ --- id: internationalization -title: Internationalization (Experimental) -description: Documentation on adding internationalization to the plugin +title: Internationalization +description: Documentation on adding internationalization to plugins and apps --- +:::caution Legacy Documentation + +This section is part of the legacy plugins documentation. For the new frontend system version, see [Internationalization](../frontend-system/building-plugins/07-internationalization.md). The i18n APIs (`createTranslationRef`, `useTranslationRef`) work the same way in both the old and new frontend systems. + +::: + ## Overview -The Backstage core function provides internationalization for plugins. The underlying library is [`i18next`](https://www.i18next.com/) with some additional Backstage typescript magic for type safety with keys. +The Backstage core function provides internationalization for plugins and apps. The underlying library is [`i18next`](https://www.i18next.com/) with some additional Backstage typescript magic for type safety with keys. ## For a plugin developer @@ -183,16 +189,56 @@ return ( The return type of the outer `t` function will be a `JSX.Element`, with the underlying value being a React fragment of the different parts of the message. -## For an application developer overwrite plugin messages +## For an application developer -Step 1: Create translation resources +As an app developer you can both override the default English messages of any plugin, and provide translations for additional languages. -You should separate different translations to their own files and import them in the main file: +### Overriding messages + +To customize specific messages without adding new languages, create a translation resource that overrides the default English messages: + +```ts +// packages/app/src/translations/catalog.ts + +import { createTranslationResource } from '@backstage/frontend-plugin-api'; +import { catalogTranslationRef } from '@backstage/plugin-catalog/alpha'; + +export const catalogTranslations = createTranslationResource({ + ref: catalogTranslationRef, + translations: { + en: () => + Promise.resolve({ + default: { + 'indexPage.title': 'Service directory', + 'indexPage.createButtonTitle': 'Register new service', + }, + }), + }, +}); +``` + +Then register it in your app: + +```diff ++ import { catalogTranslations } from './translations/catalog'; + + const app = createApp({ ++ __experimentalTranslations: { ++ resources: [catalogTranslations], ++ }, + }) +``` + +You only need to include the keys you want to override — any missing keys fall back to the plugin's defaults. + +### Adding language translations + +To add support for additional languages, create translation resources with lazy-loaded message files for each language: ```ts // packages/app/src/translations/userSettings.ts -import { createTranslationResource } from '@backstage/core-plugin-api/alpha'; +import { createTranslationResource } from '@backstage/frontend-plugin-api'; import { userSettingsTranslationRef } from '@backstage/plugin-user-settings/alpha'; export const userSettingsTranslations = createTranslationResource({ @@ -203,10 +249,12 @@ export const userSettingsTranslations = createTranslationResource({ }); ``` +The translation messages can be defined using `createTranslationMessages` for type safety: + ```ts // packages/app/src/translations/userSettings-zh.ts -import { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { createTranslationMessages } from '@backstage/frontend-plugin-api'; import { userSettingsTranslationRef } from '@backstage/plugin-user-settings/alpha'; const zh = createTranslationMessages({ @@ -221,7 +269,7 @@ const zh = createTranslationMessages({ export default zh; ``` -It's also possible to export the list of messages directly: +Or as a plain object export: ```ts // packages/app/src/translations/userSettings-zh.ts @@ -239,11 +287,7 @@ export default { }; ``` -You should change `zh` under the translations object to your local language. - -Step 2: Config translations in `packages/app/src/App.tsx` - -In an app you can both override the default messages, as well as register translations for additional languages: +Register it with the available languages declared: ```diff + import { userSettingsTranslations } from './translations/userSettings'; @@ -256,6 +300,116 @@ In an app you can both override the default messages, as well as register transl }) ``` -Step 3: Check everything is working correctly +Go to the Settings page — you should see language switching buttons. Switch languages to verify your translations are loaded correctly. -Go to `Settings` page, you should see change language buttons just under change theme buttons. And then switch language, you should see language had changed +### Using the CLI for full translation workflows + +When translating your app to other languages at scale — especially when working with external translation systems — the Backstage CLI provides `translations export` and `translations import` commands that automate the extraction and wiring of translation messages across all your plugin dependencies. + +#### Exporting default messages + +From your app package directory (e.g. `packages/app`), run: + +```bash +yarn backstage-cli translations export +``` + +This scans all frontend plugin dependencies (including transitive ones) for `TranslationRef` definitions and writes their default English messages as JSON files: + +```text +translations/ + manifest.json + messages/ + catalog.en.json + org.en.json + scaffolder.en.json + ... +``` + +Each `.en.json` file contains the flattened message keys and their default values: + +```json +{ + "indexPage.title": "All your components", + "indexPage.createButtonTitle": "Create new component", + "entityPage.notFound": "Entity not found" +} +``` + +#### Creating translations + +Copy the exported files and translate them for your target languages: + +```bash +cp translations/messages/catalog.en.json translations/messages/catalog.zh.json +``` + +Then edit `catalog.zh.json` with the translated strings. You only need to include the keys you want to translate — missing keys fall back to the English defaults at runtime. + +#### Generating wiring code + +Once you have translated files in place, run: + +```bash +yarn backstage-cli translations import +``` + +This generates a TypeScript module at `src/translations/resources.ts` that wires everything together: + +```ts +// This file is auto-generated by backstage-cli translations import +// Do not edit manually. + +import { createTranslationResource } from '@backstage/frontend-plugin-api'; +import { catalogTranslationRef } from '@backstage/plugin-catalog/alpha'; + +export default [ + createTranslationResource({ + ref: catalogTranslationRef, + translations: { + zh: () => import('../../translations/messages/catalog.zh.json'), + }, + }), +]; +``` + +Import the generated resources in your app: + +```ts +import translationResources from './translations/resources'; + +const app = createApp({ + __experimentalTranslations: { + availableLanguages: ['en', 'zh'], + resources: translationResources, + }, +}); +``` + +#### Custom file patterns + +By default, message files use the pattern `messages/{id}.{lang}.json` (e.g. `messages/catalog.en.json`). You can change this with the `--pattern` option: + +```bash +yarn backstage-cli translations export --pattern '{lang}/{id}.json' +``` + +This produces a directory structure grouped by language instead: + +```text +translations/en/catalog.json +translations/zh/catalog.json +``` + +The pattern is stored in the manifest, so the `import` command automatically uses the same layout. + +#### Integration with external translation systems + +The exported JSON files are standard key-value pairs compatible with most external translation systems. A typical workflow looks like: + +1. Run `translations export` to generate the source English files +2. Upload the `.en.json` files to your translation system +3. Download the translated files back into the translations directory +4. Run `translations import` to regenerate the wiring code + +For full command reference, see the [CLI commands documentation](../tooling/cli/03-commands.md#translations-export). diff --git a/docs/plugins/new-backend-system.md b/docs/plugins/new-backend-system.md index cf983615b5..e7cef270ab 100644 --- a/docs/plugins/new-backend-system.md +++ b/docs/plugins/new-backend-system.md @@ -4,6 +4,12 @@ title: New Backend System description: Details of the new backend system --- +:::caution Legacy Documentation + +This section is part of the legacy plugins documentation. The canonical documentation for the backend system has moved to the [Backend System](../backend-system/index.md) section, which includes more detailed and up-to-date guides for [building plugins and modules](../backend-system/building-plugins-and-modules/01-index.md), [architecture](../backend-system/architecture/01-index.md), and [core services](../backend-system/core-services/01-index.md). + +::: + ## Status The new backend system is released and ready for production use, and many plugins and modules have already been migrated. We recommend all plugins and deployments to migrate to the new system. diff --git a/docs/plugins/observability.md b/docs/plugins/observability.md index 7803d90bd5..eab2ed200c 100644 --- a/docs/plugins/observability.md +++ b/docs/plugins/observability.md @@ -4,6 +4,12 @@ title: Observability description: Adding Observability to Your Plugin --- +:::caution Legacy Documentation + +This section is part of the legacy plugins documentation. For new backend system logging, see the [Logger](../backend-system/core-services/logger.md) and [Root Logger](../backend-system/core-services/root-logger.md) core service documentation. For health checks, see [Root Health](../backend-system/core-services/root-health.md). + +::: + This article briefly describes the observability options that are available to a Backstage integrator. diff --git a/docs/plugins/plugin-development.md b/docs/plugins/plugin-development.md index 5ba1de4a7d..89318e7c5f 100644 --- a/docs/plugins/plugin-development.md +++ b/docs/plugins/plugin-development.md @@ -4,6 +4,12 @@ title: Plugin Development description: Documentation on Plugin Development --- +:::caution Legacy Documentation + +This page covers plugin development patterns for the **old frontend system**, including `createPlugin`, `createRoutableExtension`, and `RouteRef` from `@backstage/core-plugin-api`. For the new frontend system equivalents, see [Building Frontend Plugins](../frontend-system/building-plugins/01-index.md) and [Routes](../frontend-system/architecture/36-routes.md). + +::: + Backstage plugins provide features to a Backstage App. Each plugin is treated as a self-contained web app and can include almost any diff --git a/docs/plugins/plugin-directory-audit.md b/docs/plugins/plugin-directory-audit.md new file mode 100644 index 0000000000..b080463c82 --- /dev/null +++ b/docs/plugins/plugin-directory-audit.md @@ -0,0 +1,29 @@ +--- +id: plugin-directory-audit +title: Plugin Directory Audit +description: Details about the process for auditing plugins in the directory +--- + +:::caution Legacy Documentation + +This section is part of the legacy plugins documentation. The audit process described here is still current. + +::: + +## Audit Process + +We have a simple process in place to audit the plugins in the Plugin Directory: + +1. On a quarterly basis we run the following script: `node ./scripts/plugin-directory-audit.js --audit` +2. This script will flag any plugin as `inactive` that has not been updated on NPM in more than 365 days if it was `active`. +3. It will also flag any plugin that has not been updated on NPM in more than 365 days as `archived` if it was `inactive`. +4. For any plugin flagged as `inactive` or `archived` and has been updated on NPM in less than 365 days they will be flagged as `active` again. +5. These changes will then be submitted as a PR, approved and merged. + +The impact of a plugin being set to `inactive` means they will show in the inactive section at the bottom of the Plugin Directory. A plugin that is set to `archived` will **not** show up in the Plugin Directory at all. + +:::tip + +If your plugin moved to `inactive` or `archived` and you update it, please submit a PR to update the status to `active`! + +::: diff --git a/docs/plugins/proxying.md b/docs/plugins/proxying.md index 5182ee7171..a764ea64d2 100644 --- a/docs/plugins/proxying.md +++ b/docs/plugins/proxying.md @@ -4,6 +4,12 @@ title: Proxying description: Documentation on Proxying --- +:::caution Legacy Documentation + +This section is part of the legacy plugins documentation. The proxy configuration and usage described here applies to both the old and new backend systems. For creating backend plugins and modules, see [Building Backend Plugins and Modules](../backend-system/building-plugins-and-modules/01-index.md). + +::: + This page describes how to configure and use the built-in HTTP proxy functionality in your Backstage backend. ## Overview @@ -141,6 +147,9 @@ registered in this manner. Example: ```ts +import { createBackendModule } from '@backstage/backend-plugin-api'; +import { proxyEndpointsExtensionPoint } from '@backstage/plugin-proxy-node/alpha'; + backend.add( createBackendModule({ pluginId: 'proxy', diff --git a/docs/plugins/publish-private.md b/docs/plugins/publish-private.md deleted file mode 100644 index d04449d465..0000000000 --- a/docs/plugins/publish-private.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -id: publish-private -title: Publish private -description: Documentation on How to Publish private ---- - -## TODO diff --git a/docs/plugins/structure-of-a-plugin.md b/docs/plugins/structure-of-a-plugin.md index 0d18942320..3843359857 100644 --- a/docs/plugins/structure-of-a-plugin.md +++ b/docs/plugins/structure-of-a-plugin.md @@ -4,6 +4,12 @@ title: Structure of a Plugin description: Details about structure of a plugin --- +:::caution Legacy Documentation + +This page describes the structure of a plugin for the **old frontend system**. For the new frontend system, see [Building Frontend Plugins](../frontend-system/building-plugins/01-index.md). The general folder structure is similar, but the plugin wiring in `plugin.ts` differs significantly. + +::: + Nice, you have a new plugin! We'll soon see how we can develop it into doing great things. But first off, let's look at what we get out of the box. @@ -81,8 +87,8 @@ export const ExamplePage = examplePlugin.provide( ``` This is where the plugin is created and where it creates and exports extensions -that can be imported and used the app. See reference docs for -[`createPlugin`](../reference/core-plugin-api.createplugin.md) or introduction to +that can be imported and used in the app. See reference docs for +[`createPlugin`](https://backstage.io/api/stable/functions/_backstage_core-plugin-api.index.createPlugin.html) or introduction to the new [Composability System](./composability.md). ## Components diff --git a/docs/plugins/testing.md b/docs/plugins/testing.md index 7292ac9754..94015700fe 100644 --- a/docs/plugins/testing.md +++ b/docs/plugins/testing.md @@ -4,6 +4,18 @@ title: Testing with Jest description: Documentation on How to do unit testing with Jest --- +:::caution Legacy Documentation + +This section is part of the legacy plugins documentation. The general testing principles described here still apply, but for system-specific testing guides, see [Testing Frontend Plugins](../frontend-system/building-plugins/02-testing.md) and [Testing Backend Plugins and Modules](../backend-system/building-plugins-and-modules/02-testing.md). + +::: + +:::note Note + +You may want to consider migrating to Jest 30, to do this, you can follow this guide: [Migrating to Jest 30](../tutorials/jest30-migration.md) + +::: + Backstage uses [Jest](https://facebook.github.io/jest/) for all our unit testing needs. @@ -185,6 +197,11 @@ loading is broken, not that the loading indicator is broken. ## Examples +For more specific examples of how to test your Backstage **backend plugins** and **modules** or **frontend plugins** you can check out the following guides: + +- [Testing Backend Plugins and Modules](../backend-system/building-plugins-and-modules/02-testing.md) +- [Testing Frontend Plugins](../frontend-system/building-plugins/02-testing.md) + ### Utility Functions A utility function is a function with no side effects. It takes in arguments and diff --git a/docs/publishing.md b/docs/publishing.md index e6c0b6b466..9c2ecb0157 100644 --- a/docs/publishing.md +++ b/docs/publishing.md @@ -47,12 +47,12 @@ Additional steps for the main line release - Check for mentions of "major" & "breaking" and if they are expected in the current release - Verify the version we are shipping is correct - Create Release Notes - - There exists a [release notes template](./.release-notes-template.md) for creating the release notes. It can already be created after the last main line release to keep track of major changes during the month + - There exists a release notes template (`.release-notes-template.md`) for creating the release notes. It can already be created after the last main line release to keep track of major changes during the month - The content is picked by relevancy showcasing the work of the community during the month of the release - Mention newly added packages or features - Mention any security fixes - Create Release Notes PR - - Add the release note file as [`/docs/releases/vx.y.0.md`](./releases) + - Add the release note file as [`/docs/releases/vx.y.0.md`](https://backstage.io/docs/releases/) - Finally copy the content, without the metadata header, into the description of the [`Version Packages` Pull Request](https://github.com/backstage/backstage/pulls?q=is%3Aopen+is%3Apr+in%3Atitle+%22Version+Packages) Once the release has been published edit the newly created release in the [GitHub repository](https://github.com/backstage/backstage/releases) and replace the text content with the release notes. @@ -69,17 +69,23 @@ Once the release has been published edit the newly created release in the [GitHu **This emergency release process is intended only for the Backstage maintainers.** -Given one or more PRs towards master that we want to create a patch release for, run the following script from the repo root: +Given one or more PRs towards master that we want to create a patch release for, add them to the patch release queue by running the following command for each PR: ```bash -./scripts/patch-release-for-pr.js ... +yarn patch-pr ``` -Wait until the script has finished executing, at the end of the output you will find a link of the format `https://github.com/backstage/backstage/pull/new/patch-release-pr-...`. Open this link in your browser to create a PR for the patch release. Finish the sentence "This release fixes an issue where..." and create the PR. +This creates a patch file in the `.patches/` directory (e.g., `.patches/pr-12345.txt`) containing the description of the fix. The [sync_patch-release.yml](https://github.com/backstage/backstage/blob/master/.github/workflows/sync_patch-release.yml) workflow will automatically detect these patch files and create or update a "Patch Release" PR. -Once the PR has been approved and merged, the patch release will be automatically created. The patch release is complete when a notification has been posted to Discord in the `#announcements` channel. Keep an eye on "Deploy Packages" workflow and re-trigger if it fails. It is safe to re-trigger any part of this workflow, including the release step. +The workflow will: -If the above process fails, you can fall back to the manual process documented below. +- Automatically create a patch release PR if one doesn't exist +- Update the existing PR if patch files are added, modified, or removed +- Close and delete the PR branch if all patch files are removed + +Once the "Patch Release" PR has been approved and merged, the patch release will be automatically created. The patch files will be automatically removed from the master branch after the patch release is merged. From here the patch release process is the same as the usual release process, starting with the notification in the `#maintainers` channel on Discord. + +If the above process fails, you can fall back to the manual process documented below, or run the `./scripts/patch-release-for-pr.js ...` script to manually create a patch release PR. ### Old Process diff --git a/docs/references/glossary.md b/docs/references/glossary.md index ad9e2bbee5..b4f42a34a6 100644 --- a/docs/references/glossary.md +++ b/docs/references/glossary.md @@ -120,7 +120,7 @@ An executable or [package](#package) file with all of the necessary information 1. A centralized, self-service interface providing developers with all the necessary resources, tools, documentation, and information to effectively build, integrate, deploy, and manage software products within an organization. -2. Backstage is a specific example of a developer portal, designed as a centralized system with a user interface and database to streamline development and maintenance of an organization's software projects. It features a robust [Software Catalog](#software-catalog) that centralizes and organizes access to the organization's services, websites, mobile features, libraries, and other software components. Backstage also includes [Software Templates](#software-templates-aka-scaffolder) that simplify the creation of new projects and components. +2. Backstage is a specific example of a developer portal, designed as a centralized system with a user interface and database to streamline development and maintenance of an organization's software projects. It features a robust [Software Catalog](#software-catalog) that centralizes and organizes access to the organization's services, websites, mobile features, libraries, and other software components. Backstage also includes [Software Templates](#software-templates) that simplify the creation of new projects and components. Backstage is both a developer portal and a plugin-based framework for creating new custom developer portals. @@ -186,7 +186,7 @@ This standard is a key component of [OpenID Connect](#openid-connect-aka-oidc). Classification of an [entity](#entity) in the Backstage Software Catalog, for example _service_, _database_, or _team_. An element of the [kind|namespace|name triplet](#kind-namespace-name-triplet) that is an important concept for uniqueness. -## Kind|namespace|name triplet +## Kind|namespace|name triplet {#kind-namespace-name-triplet} The primary reference for [Software Catalog](#software-catalog) entities. It is human-readable and should be unique across your Backstage instance. @@ -353,7 +353,7 @@ A specific type of dynamic access control associated with a [resource](#resource ## Scaffolder -Another name for [Software Templates](#software-templates-aka-scaffolder). (The term comes from the use of Software Templates as _scaffolds_ for building new components and projects.) +Another name for [Software Templates](#software-templates). (The term comes from the use of Software Templates as _scaffolds_ for building new components and projects.) ## Scope @@ -379,7 +379,7 @@ Existing search technology that [Backstage Search](#search) can take advantage o The Software Catalog is a core feature of Backstage. See [Backstage Software Catalog](https://backstage.io/docs/next/features/software-catalog/) for an overview, the life of an entity in the catalog, how to configure the catalog, its architecture and high-level design, how to configure and customize it, and its API. The overview describes how the catalog works, how to add components to it, how to find software in it, and more. -## Software Templates (aka Scaffolder) +## Software Templates (aka Scaffolder) {#software-templates} 1. A "skeleton" software project created and managed in the Backstage Software Templates tool. diff --git a/docs/references/index.md b/docs/references/index.md new file mode 100644 index 0000000000..abd5c99c0c --- /dev/null +++ b/docs/references/index.md @@ -0,0 +1,7 @@ +--- +id: 'index' +title: 'Package Index' +description: 'Index of all Backstage Packages' +--- + +Please use [the new API documentation](https://backstage.io/api/stable) for more information. diff --git a/docs/releases/v1.0.0.md b/docs/releases/v1.0.0.md index 6906ef8ae7..c13c57f57e 100644 --- a/docs/releases/v1.0.0.md +++ b/docs/releases/v1.0.0.md @@ -89,4 +89,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/releases/tag/v1.0.0) for this release - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://mailchi.mp/spotify/backstage-community) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.1.0.md b/docs/releases/v1.1.0.md index f484cc5b57..a55b9c8344 100644 --- a/docs/releases/v1.1.0.md +++ b/docs/releases/v1.1.0.md @@ -67,4 +67,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.1.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://mailchi.mp/spotify/backstage-community) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.10.0.md b/docs/releases/v1.10.0.md index b06ab63747..d636bd5de8 100644 --- a/docs/releases/v1.10.0.md +++ b/docs/releases/v1.10.0.md @@ -75,4 +75,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.10.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://mailchi.mp/spotify/backstage-community) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.11.0.md b/docs/releases/v1.11.0.md index ad2118c754..1e08b3234b 100644 --- a/docs/releases/v1.11.0.md +++ b/docs/releases/v1.11.0.md @@ -97,4 +97,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.11.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://mailchi.mp/spotify/backstage-community) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.12.0.md b/docs/releases/v1.12.0.md index 789be8beca..f8564166de 100644 --- a/docs/releases/v1.12.0.md +++ b/docs/releases/v1.12.0.md @@ -59,4 +59,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.12.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://mailchi.mp/spotify/backstage-community) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.13.0.md b/docs/releases/v1.13.0.md index b93920b7b1..e1806b8dfd 100644 --- a/docs/releases/v1.13.0.md +++ b/docs/releases/v1.13.0.md @@ -141,4 +141,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.13.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://mailchi.mp/spotify/backstage-community) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.14.0.md b/docs/releases/v1.14.0.md index 2c565e2519..d2d68f36d5 100644 --- a/docs/releases/v1.14.0.md +++ b/docs/releases/v1.14.0.md @@ -77,4 +77,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.14.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://mailchi.mp/spotify/backstage-community) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.15.0.md b/docs/releases/v1.15.0.md index c7ef6b33f9..0b6170a547 100644 --- a/docs/releases/v1.15.0.md +++ b/docs/releases/v1.15.0.md @@ -193,5 +193,5 @@ start using this new release. [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://mailchi.mp/spotify/backstage-community) if +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.16.0.md b/docs/releases/v1.16.0.md index 37a458cc32..b90274639a 100644 --- a/docs/releases/v1.16.0.md +++ b/docs/releases/v1.16.0.md @@ -84,4 +84,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.16.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.17.0.md b/docs/releases/v1.17.0.md index 36414e6608..6ed7ace5f5 100644 --- a/docs/releases/v1.17.0.md +++ b/docs/releases/v1.17.0.md @@ -69,4 +69,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.17.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.18.0.md b/docs/releases/v1.18.0.md index eb42a5f58a..88f3536dd4 100644 --- a/docs/releases/v1.18.0.md +++ b/docs/releases/v1.18.0.md @@ -130,4 +130,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.18.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.19.0.md b/docs/releases/v1.19.0.md index 49a31f32b6..c744905ec7 100644 --- a/docs/releases/v1.19.0.md +++ b/docs/releases/v1.19.0.md @@ -83,4 +83,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.19.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.2.0.md b/docs/releases/v1.2.0.md index 3751217ae4..f69f6369ca 100644 --- a/docs/releases/v1.2.0.md +++ b/docs/releases/v1.2.0.md @@ -38,7 +38,7 @@ Server-to-server authentication tokens issued from a TokenManager (specifically, ### Kubernetes -Added support for `oidc` as an auth provider for kubernetes authentication and added an optional `oidcTokenProvider` config value. This will allow users to authenticate to kubernetes clusters using ID tokens obtained from the configured auth provider in their Backstage instance. Contributed by @dbravovmw(https://github.com/dbravovmw). #11328(https://github.com/backstage/backstage/pull/11328) +Added support for `oidc` as an auth provider for kubernetes authentication and added an optional `oidcTokenProvider` config value. This will allow users to authenticate to kubernetes clusters using ID tokens obtained from the configured auth provider in their Backstage instance. Contributed by [@dbravovmw](https://github.com/dbravovmw) in [#11328](https://github.com/backstage/backstage/pull/11328) ### Misc @@ -64,4 +64,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.2.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://mailchi.mp/spotify/backstage-community) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.20.0.md b/docs/releases/v1.20.0.md index 0d0075aa41..e4a8b04cab 100644 --- a/docs/releases/v1.20.0.md +++ b/docs/releases/v1.20.0.md @@ -47,4 +47,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.20.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.21.0.md b/docs/releases/v1.21.0.md index 53e77f0021..852eb92de1 100644 --- a/docs/releases/v1.21.0.md +++ b/docs/releases/v1.21.0.md @@ -132,4 +132,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.21.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.22.0.md b/docs/releases/v1.22.0.md index 9f9295fb96..e351d9c86a 100644 --- a/docs/releases/v1.22.0.md +++ b/docs/releases/v1.22.0.md @@ -66,4 +66,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.22.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.23.0.md b/docs/releases/v1.23.0.md index 413db12988..bb8a35f454 100644 --- a/docs/releases/v1.23.0.md +++ b/docs/releases/v1.23.0.md @@ -79,4 +79,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.23.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.24.0.md b/docs/releases/v1.24.0.md index 7bb8b174e1..9b87b5b039 100644 --- a/docs/releases/v1.24.0.md +++ b/docs/releases/v1.24.0.md @@ -87,4 +87,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.24.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.25.0.md b/docs/releases/v1.25.0.md index afb765b447..037c698a17 100644 --- a/docs/releases/v1.25.0.md +++ b/docs/releases/v1.25.0.md @@ -44,4 +44,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.25.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.26.0.md b/docs/releases/v1.26.0.md index 38665ff7e5..a42450b4ce 100644 --- a/docs/releases/v1.26.0.md +++ b/docs/releases/v1.26.0.md @@ -79,4 +79,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.26.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.27.0.md b/docs/releases/v1.27.0.md index 123d9663ad..bf382bbd4d 100644 --- a/docs/releases/v1.27.0.md +++ b/docs/releases/v1.27.0.md @@ -80,4 +80,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.27.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.28.0.md b/docs/releases/v1.28.0.md index bc690fbeef..9509128546 100644 --- a/docs/releases/v1.28.0.md +++ b/docs/releases/v1.28.0.md @@ -197,6 +197,6 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.28.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. Big shoutout to all 64 of you amazing folks who chipped in on this release 🙏: [@acierto](https://github.com/acierto), [@adityak60](https://github.com/adityak60), [@adsk-mukul](https://github.com/adsk-mukul), [@alexef](https://github.com/alexef), [@andrei-ivanovici](https://github.com/andrei-ivanovici), [@anicke](https://github.com/anicke), [@aramissennyeydd](https://github.com/aramissennyeydd), [@awanlin](https://github.com/awanlin), [@benjdlambert](https://github.com/benjdlambert), [@benjidotsh](https://github.com/benjidotsh), [@bethgriggs](https://github.com/bethgriggs), [@brianphillips](https://github.com/brianphillips), [@brunobastosg](https://github.com/brunobastosg), [@camilaibs](https://github.com/camilaibs), [@cjlee01](https://github.com/cjlee01), [@cmoulliard](https://github.com/cmoulliard), [@davidfestal](https://github.com/davidfestal), [@debsmita1](https://github.com/debsmita1), [@drodil](https://github.com/drodil), [@dweber019](https://github.com/dweber019), [@elaine-mattos](https://github.com/elaine-mattos), [@erik-adsk](https://github.com/erik-adsk), [@fabian-m-95](https://github.com/fabian-m-95), [@freben](https://github.com/freben), [@grantila](https://github.com/grantila), [@huggingpixels](https://github.com/huggingpixels), [@ismailmmd](https://github.com/ismailmmd), [@jeevaramanathan](https://github.com/jeevaramanathan), [@johanhammar](https://github.com/johanhammar), [@jslott2sigma](https://github.com/jslott2sigma), [@julien-hery](https://github.com/julien-hery), [@kalleericson](https://github.com/kalleericson), [@kissmikijr](https://github.com/kissmikijr), [@kuangp](https://github.com/kuangp), [@maetis](https://github.com/maetis), [@marcpalm](https://github.com/marcpalm), [@marcuseide](https://github.com/marcuseide), [@mareklibra](https://github.com/mareklibra), [@mario-mui](https://github.com/mario-mui), [@matteosilv](https://github.com/matteosilv), [@mclarke47](https://github.com/mclarke47), [@npiyush97](https://github.com/npiyush97), [@nurbaysymbat](https://github.com/nurbaysymbat), [@parsifal-m](https://github.com/parsifal-m), [@piatkiewicz](https://github.com/piatkiewicz), [@raffitamizian](https://github.com/raffitamizian), [@rbillon59](https://github.com/rbillon59), [@rewixe](https://github.com/rewixe), [@rugvip](https://github.com/rugvip), [@ryan-hanchett](https://github.com/ryan-hanchett), [@sblausten](https://github.com/sblausten), [@snowblitzer](https://github.com/snowblitzer), [@stanislav-c](https://github.com/stanislav-c), [@stephenglass](https://github.com/stephenglass), [@stijnbrouwers](https://github.com/stijnbrouwers), [@suniljose25](https://github.com/suniljose25), [@tcardonne](https://github.com/tcardonne), [@vadave](https://github.com/vadave), [@veenarm](https://github.com/veenarm), [@vinisdl](https://github.com/vinisdl), [@vinzscam](https://github.com/vinzscam), [@waldirmontoya25](https://github.com/waldirmontoya25), [@ydayagi](https://github.com/ydayagi), [@zaperex](https://github.com/zaperex). diff --git a/docs/releases/v1.29.0.md b/docs/releases/v1.29.0.md index 5f7ed1db19..12553c3a1c 100644 --- a/docs/releases/v1.29.0.md +++ b/docs/releases/v1.29.0.md @@ -114,4 +114,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.29.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.3.0.md b/docs/releases/v1.3.0.md index 96aa88a05e..b87851ca83 100644 --- a/docs/releases/v1.3.0.md +++ b/docs/releases/v1.3.0.md @@ -28,7 +28,7 @@ Several new [entity providers](https://backstage.io/docs/features/software-catal - `AzureDevOpsEntityProvider` as replacement for `AzureDevOpsDiscoveryProcessor`. PR [#11604](https://github.com/backstage/backstage/pull/11604) contributed by [@goenning](https://github.com/goenning) - `GitlabDiscoveryEntityProvider` as replacement for `GitLabDiscoveryProcessor`. PR [#11886](https://github.com/backstage/backstage/pull/11886) contributed by [@ivangonzalezacuna](https://github.com/ivangonzalezacuna) -- `BitbucketCloudEntityProvider` as a replacement for `BitbucketDiscoveryProcessor` (for Bitbucket Cloud only). PR [#11345](https://github.com/backstage/backstage/pull/11345) contributed by [@pjungermann](​​https://github.com/pjungermann) +- `BitbucketCloudEntityProvider` as a replacement for `BitbucketDiscoveryProcessor` (for Bitbucket Cloud only). PR [#11345](https://github.com/backstage/backstage/pull/11345) contributed by [@pjungermann](https://github.com/pjungermann) ### New plugin: Vault @@ -62,4 +62,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.3.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap), and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://mailchi.mp/spotify/backstage-community) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.30.0-changelog.md b/docs/releases/v1.30.0-changelog.md index 0af531f483..21c63a23d8 100644 --- a/docs/releases/v1.30.0-changelog.md +++ b/docs/releases/v1.30.0-changelog.md @@ -338,7 +338,7 @@ Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.30.0](https:// - b2d97fd: Fixing loading of additional config files with new `ConfigSources` - fbc7819: Use ES2022 in CLI bundler - 93095ee: Make sure node-fetch is version 2.7.0 or greater -- 6d898d8: Switched the `process` polyfill to use `require.resolve` for greater compatability. +- 6d898d8: Switched the `process` polyfill to use `require.resolve` for greater compatibility. - e53074f: Updated default backend plugin to use `RootConfigService` instead of `Config`. This also removes the dependency on `@backstage/config` as it's no longer used. - ee2b0e5: The experimental module federation build now has the ability to force the use of development versions of `react` and `react-dom` by setting the `FORCE_REACT_DEVELOPMENT` flag. - 239dffc: Remove usage of deprecated functionality from @backstage/config-loader diff --git a/docs/releases/v1.30.0-next.4-changelog.md b/docs/releases/v1.30.0-next.4-changelog.md index 433b5f894c..e3f2840121 100644 --- a/docs/releases/v1.30.0-next.4-changelog.md +++ b/docs/releases/v1.30.0-next.4-changelog.md @@ -212,7 +212,7 @@ Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.30.0-next.4](h ### Patch Changes -- 6d898d8: Switched the `process` polyfill to use `require.resolve` for greater compatability. +- 6d898d8: Switched the `process` polyfill to use `require.resolve` for greater compatibility. - 2ced236: Updated dependency `@module-federation/enhanced` to `0.3.1` - Updated dependencies - @backstage/catalog-model@1.6.0-next.0 diff --git a/docs/releases/v1.30.0.md b/docs/releases/v1.30.0.md index 0d3ad1dd5b..022dc23ba8 100644 --- a/docs/releases/v1.30.0.md +++ b/docs/releases/v1.30.0.md @@ -114,4 +114,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.30.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.31.0.md b/docs/releases/v1.31.0.md index 37b11c2b56..d622d19ce1 100644 --- a/docs/releases/v1.31.0.md +++ b/docs/releases/v1.31.0.md @@ -141,4 +141,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.31.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.32.0.md b/docs/releases/v1.32.0.md index 90011deb38..e811d939f9 100644 --- a/docs/releases/v1.32.0.md +++ b/docs/releases/v1.32.0.md @@ -157,4 +157,4 @@ Below you can find a list of links and references to help you learn about and st - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io /docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.33.0.md b/docs/releases/v1.33.0.md index c8798ea9e5..ce355b5b7d 100644 --- a/docs/releases/v1.33.0.md +++ b/docs/releases/v1.33.0.md @@ -70,4 +70,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.33.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.34.0.md b/docs/releases/v1.34.0.md index cb7b8af1f3..e0191e384f 100644 --- a/docs/releases/v1.34.0.md +++ b/docs/releases/v1.34.0.md @@ -98,4 +98,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.34.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.35.0.md b/docs/releases/v1.35.0.md index 04aba27da2..9ba3936a75 100644 --- a/docs/releases/v1.35.0.md +++ b/docs/releases/v1.35.0.md @@ -70,4 +70,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.35.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.36.0.md b/docs/releases/v1.36.0.md index 53fb25f011..dc05a479a7 100644 --- a/docs/releases/v1.36.0.md +++ b/docs/releases/v1.36.0.md @@ -99,4 +99,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.1.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.37.0.md b/docs/releases/v1.37.0.md index 8363731292..63ff100f36 100644 --- a/docs/releases/v1.37.0.md +++ b/docs/releases/v1.37.0.md @@ -99,7 +99,7 @@ There’s also some more information in the [changeset](https://github.com/backs ### New Frontend System improvements -There’s now [a `DialogApi`](https://backstage.io/docs/reference/frontend-plugin-api.dialogapi/), which greatly helps with placing dialogs in front of your users - in particular from code that lives outside the React tree, such as in callbacks. +There’s now [a `DialogApi`](https://backstage.io/api/stable/interfaces/_backstage_frontend-plugin-api.index.DialogApi.html), which greatly helps with placing dialogs in front of your users - in particular from code that lives outside the React tree, such as in callbacks. At the same time, an `EntityHeaderBlueprint` lets you specifically replace the entity page headers. You can now also group multiple entity page contents into a single tab. A tab that groups more than one entity content is rendered as a dropdown menu. You can use the default list of tab groups or create your own. Groups are sorted in the same order as they are defined in the `app-config.yaml` file. The association between an entity content and a tab group can be optionally done when creating the content extension or via the `app-config.yaml` file. See usage examples in the [#28701](https://github.com/backstage/backstage/pull/28701) and [#29043](https://github.com/backstage/backstage/pull/29043) pull requests. @@ -140,4 +140,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.37.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.38.0.md b/docs/releases/v1.38.0.md index 4d5fe1d75c..14331066d7 100644 --- a/docs/releases/v1.38.0.md +++ b/docs/releases/v1.38.0.md @@ -165,4 +165,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.38.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.39.0.md b/docs/releases/v1.39.0.md index de6903adb4..d6e388f7a8 100644 --- a/docs/releases/v1.39.0.md +++ b/docs/releases/v1.39.0.md @@ -122,4 +122,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.39.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.4.0-changelog.md b/docs/releases/v1.4.0-changelog.md index 484939f796..cd20ee2d7f 100644 --- a/docs/releases/v1.4.0-changelog.md +++ b/docs/releases/v1.4.0-changelog.md @@ -793,7 +793,7 @@ ### Minor Changes - 8798f8d93f: Introduces a new annotation `pagerduty.com/service-id` that can be used instead of the `pagerduty.com/integration-key` annotation. - _Note: If both annotations are specified on a given Entity, then the `pagerduty.com/integration-key` annotation will be prefered_ + _Note: If both annotations are specified on a given Entity, then the `pagerduty.com/integration-key` annotation will be preferred_ **BREAKING** The `PagerDutyClient.fromConfig` static method now expects a `FetchApi` compatible object and has been refactored to accept 2 arguments: config and ClientApiDependencies diff --git a/docs/releases/v1.4.0-next.0-changelog.md b/docs/releases/v1.4.0-next.0-changelog.md index c6b9fd7cf9..7e039c8c1a 100644 --- a/docs/releases/v1.4.0-next.0-changelog.md +++ b/docs/releases/v1.4.0-next.0-changelog.md @@ -45,7 +45,7 @@ ### Minor Changes - 8798f8d93f: Introduces a new annotation `pagerduty.com/service-id` that can be used instead of the `pagerduty.com/integration-key` annotation. - _Note: If both annotations are specified on a given Entity, then the `pagerduty.com/integration-key` annotation will be prefered_ + _Note: If both annotations are specified on a given Entity, then the `pagerduty.com/integration-key` annotation will be preferred_ **BREAKING** The `PagerDutyClient.fromConfig` static method now expects a `FetchApi` compatible object and has been refactored to accept 2 arguments: config and ClientApiDependencies diff --git a/docs/releases/v1.4.0.md b/docs/releases/v1.4.0.md index 9a178ea8ff..ee33a4ddab 100644 --- a/docs/releases/v1.4.0.md +++ b/docs/releases/v1.4.0.md @@ -67,4 +67,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.4.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://mailchi.mp/spotify/backstage-community) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.40.0.md b/docs/releases/v1.40.0.md index befaddfd66..b357ddf207 100644 --- a/docs/releases/v1.40.0.md +++ b/docs/releases/v1.40.0.md @@ -197,4 +197,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.40.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.41.0.md b/docs/releases/v1.41.0.md index 2e16e9ffba..8cb7b12de5 100644 --- a/docs/releases/v1.41.0.md +++ b/docs/releases/v1.41.0.md @@ -99,4 +99,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.41.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.42.0.md b/docs/releases/v1.42.0.md index 4fe52f7a77..14a3fe4181 100644 --- a/docs/releases/v1.42.0.md +++ b/docs/releases/v1.42.0.md @@ -65,4 +65,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.42.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.43.0.md b/docs/releases/v1.43.0.md index 449023d167..09dac1c32e 100644 --- a/docs/releases/v1.43.0.md +++ b/docs/releases/v1.43.0.md @@ -87,4 +87,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.43.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.44.0.md b/docs/releases/v1.44.0.md index c57708a8ec..3dc313fc2d 100644 --- a/docs/releases/v1.44.0.md +++ b/docs/releases/v1.44.0.md @@ -138,4 +138,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.44.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.45.0-changelog.md b/docs/releases/v1.45.0-changelog.md new file mode 100644 index 0000000000..23def9a969 --- /dev/null +++ b/docs/releases/v1.45.0-changelog.md @@ -0,0 +1,2607 @@ +# Release v1.45.0 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.45.0](https://backstage.github.io/upgrade-helper/?to=1.45.0) + +## @backstage/backend-app-api@1.3.0 + +### Minor Changes + +- a17d9df: Updates API for `instanceMetadata` service to return a list of plugins not features. + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + +## @backstage/backend-plugin-api@1.5.0 + +### Minor Changes + +- a17d9df: Promote `instanceMetadata` service to main entrypoint. +- 62fc2de: Explicitly mark `coreServices.rootInstanceMetadata` as a root service. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config@1.3.6 + - @backstage/plugin-permission-node@0.10.6 + +## @backstage/backend-test-utils@1.10.0 + +### Minor Changes + +- d57b13b: Added support for Postgres 18 to the available `TestDatabases`. + + Note that the set of _default_ databases to test against for users of the `TestDatabases` class was also updated to include Postgres 14 and 18, instead of 13 and 17. If you need to override this, you can pass in an explicit `ids` argument, for example `ids: ['POSTGRES_17', 'POSTGRES_13', 'SQLITE_3']`. + +### Patch Changes + +- f3001fd: Tweak some of the mock services to have more precise types +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/backend-app-api@1.3.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-events-node@0.4.17 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config@1.3.6 + +## @backstage/core-plugin-api@1.12.0 + +### Minor Changes + +- 83439b1: All route references are now forwards compatible with the new frontend system, i.e. `@backstage/frontend-plugin-api`. This means they no longer need to be converted with `convertLegacyRouteRef` or `convertLegacyRouteRefs` from `@backstage/core-compat-api`. + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/config@1.3.6 + +## @backstage/eslint-plugin@0.2.0 + +### Minor Changes + +- 926389b: Added `@backstage/no-ui-css-imports-in-non-frontend` rule, which ensures that CSS from `@backstage/ui` is not imported outside of the frontend app. + +## @backstage/frontend-plugin-api@0.13.0 + +### Minor Changes + +- 7d87b4f: Renamed `ExtensionDefinition` to `OverridableExtensionDefinition` and introduced a slimmer `ExtensionDefinition` type that does not include override methods. The overridable type is generally used as an output type, while plain `ExtensionDefinition`s are used for input. This reduces type conflicts across different of `@backstage/frontend-plugin-api`, improving long-term compatibility. + +### Patch Changes + +- 4d03f08: Internal refactor of route reference implementations with minor updates to the `toString` implementations. + +- 7c6a66d: Added support for plugin-relative `attachTo` declarations for extension definitions. This allows for the creation of extension and extension blueprints that attach to other extensions of a particular `kind` in the same plugin, rather than needing to provide the exact extension ID. This is particularly useful when wanting to provide extension blueprints with a built-in hierarchy where the extensions created from one blueprint attach to extensions created from the other blueprint, for example: + + ```ts + // kind: 'tabbed-page' + const parentPage = TabbedPageBlueprint.make({ + params: {....} + }) + // attachTo: { kind: 'tabbed-page', input: 'tabs' } + const child1 = TabContentBlueprint.make({ + name: 'tab1', + params: {....} + }) + ``` + +- 878c251: Updated to `ExtensionInput` to make all type parameters optional. + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. + +- Updated dependencies + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + +## @backstage/repo-tools@0.16.0 + +### Minor Changes + +- 11c61f2: The `package-docs` command will now automatically use a `typedoc.json` file if one exists at the root of your project. + +### Patch Changes + +- 8f56eae: Updated knip-reports to detect dependencies in dev/alpha pattern +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config-loader@1.10.6 + - @backstage/cli-node@0.2.15 + - @backstage/catalog-model@1.7.6 + +## @backstage/ui@0.9.0 + +### Minor Changes + +- 539cf26: **BREAKING**: Migrated Avatar component from Base UI to custom implementation with size changes: + + - Base UI-specific props are no longer supported + - Size values have been updated: + - New `x-small` size added (1.25rem / 20px) + - `small` size unchanged (1.5rem / 24px) + - `medium` size unchanged (2rem / 32px, default) + - `large` size **changed from 3rem to 2.5rem** (40px) + - New `x-large` size added (3rem / 48px) + + Migration: + + ```diff + # Remove Base UI-specific props + - + + + + # Update large size usage to x-large for same visual size + - + + + ``` + + Added `purpose` prop for accessibility control (`'informative'` or `'decoration'`). + +- 5c614ff: **BREAKING**: Migrated Checkbox component from Base UI to React Aria Components. + + API changes required: + + - `checked` → `isSelected` + - `defaultChecked` → `defaultSelected` + - `disabled` → `isDisabled` + - `required` → `isRequired` + - `label` prop removed - use `children` instead + - CSS: `bui-CheckboxLabel` class removed + - Data attribute: `data-checked` → `data-selected` + - Use without label is no longer supported + + Migration examples: + + Before: + + ```tsx + + ``` + + After: + + ```tsx + + Accept terms + + ``` + + Before: + + ```tsx + + ``` + + After: + + ```tsx + Option + ``` + + Before: + + ```tsx + + ``` + + After: + + ```tsx + + Accessible label + + ``` + +- 134151f: Fixing styles on SearchField in Backstage UI after migration to CSS modules. `SearchField` has now its own set of class names. We previously used class names from `TextField` but this approach was creating some confusion so going forward in your theme you'll be able to theme `TextField` and `SearchField` separately. + +- a67670d: **BREAKING**: Removed central `componentDefinitions` object and related type utilities (`ComponentDefinitionName`, `ComponentClassNames`). + + Component definitions are primarily intended for documenting the CSS class API for theming purposes, not for programmatic use in JavaScript/TypeScript. + + **Migration Guide:** + + If you were using component definitions or class names to build custom components, we recommend migrating to either: + + - Use Backstage UI components directly as building blocks, or + - Duplicate the component CSS in your own stylesheets instead of relying on internal class names + +- b78fc45: **BREAKING**: Changed className prop behavior to augment default styles instead of being ignored or overriding them. + + Affected components: + + - Menu, MenuListBox, MenuAutocomplete, MenuAutocompleteListbox, MenuItem, MenuListBoxItem, MenuSection, MenuSeparator + - Switch + - Skeleton + - FieldLabel + - Header, HeaderToolbar + - HeaderPage + - Tabs, TabList, Tab, TabPanel + + If you were passing custom className values to any of these components that relied on the previous behavior, you may need to adjust your styles to account for the default classes now being applied alongside your custom classes. + +- 83c100e: **BREAKING**: Removed `Collapsible` component. Migrate to `Accordion` or use React Aria `Disclosure`. + + ## Migration Path 1: Accordion (Opinionated Styled Component) + + Accordion provides preset styling with a similar component structure. + + ```diff + - import { Collapsible } from '@backstage/ui'; + + import { Accordion, AccordionTrigger, AccordionPanel } from '@backstage/ui'; + + - + - } /> + - Content + - + + + + + + + Content + + + ``` + + CSS classes: `.bui-CollapsibleRoot` → `.bui-Accordion`, `.bui-CollapsibleTrigger` → `.bui-AccordionTrigger` (now on heading element), `.bui-CollapsiblePanel` → `.bui-AccordionPanel` + + ## Migration Path 2: React Aria Disclosure (Full Customization) + + For custom styling without preset styles: + + ```tsx + import { Disclosure, Button, DisclosurePanel } from 'react-aria-components'; + + + + Content + ; + ``` + +- 816af0f: **BREAKING**: The `SelectProps` interface now accepts a generic type parameter for selection mode. + + Added searchable and multiple selection support to Select component. The component now accepts `searchable`, `selectionMode`, and `searchPlaceholder` props to enable filtering and multi-selection modes. + + Migration: If you're using `SelectProps` type directly, update from `SelectProps` to `SelectProps<'single' | 'multiple'>`. Component usage remains backward compatible. + +### Patch Changes + +- d01de00: Fix broken external links in Backstage UI Header component. +- 35a3614: Fixed CSS issues in Select component including popover width constraints, focus outline behavior, and overflow handling. +- 01476f0: Improved visual consistency of PasswordField, SearchField, and MenuAutocomplete components. +- 26c6a78: Fix default text color in Backstage UI +- deaa427: Fixed Text component to prevent `truncate` prop from being spread to the underlying DOM element. +- 1059f95: Improved the Link component structure in Backstage UI. +- 836b0c7: Fixed dialog backdrop appearance in dark mode. +- 6874094: Migrated CellProfile component from Base UI Avatar to Backstage UI Avatar component. +- 719d772: Avatar components in x-small and small sizes now display only one initial instead of two, improving readability at smaller dimensions. +- 6d35a6b: Removed `@base-ui-components/react` dependency as all components now use React Aria Components. +- dac851f: Fix the default font size in Backstage UI. +- 3c0ea67: Fix CSS layer ordering in Backstage UI to make sure component styles are loaded after tokens and base declarations. +- 3b18d80: Fixed RadioGroup radio button ellipse distortion by preventing flex shrink and grow. +- 4eb455c: Fix font smoothing as default in Backstage UI. +- ff9f0c3: Enable tree-shaking of imports other than `*.css`. +- 7839e7b: Added `loading` prop to Button and ButtonIcon components for displaying spinner during async operations. +- a00fb88: Fixed Table Row component to properly support opening links in new tabs via right-click or Cmd+Click when using the `href` prop. +- e16ece5: Set the color-scheme property depending on theme +- 1ef3ca4: Added new VisuallyHidden component for hiding content visually while keeping it accessible to screen readers. +- 00bfb83: Fix default font wight and font family in Backstage UI. + +## @backstage/plugin-catalog@1.32.0 + +### Minor Changes + +- a521911: Add support for customizable icons in `SearchResultListItemBlueprint` and related components + +### Patch Changes + +- b500bcc: Fix React warnings for elements not having stable keys. +- a87c0a5: Fixed responsiveness issues with catalog entity content layout using the new frontend system. +- aff04d3: Fixed missing margins for entity warning content area on catalog entity page in NFS. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-search-react@1.10.0 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-techdocs-react@1.3.5 + - @backstage/catalog-client@1.12.1 + - @backstage/integration-react@1.2.12 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-scaffolder-common@1.7.3 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-catalog-backend@3.2.0 + +### Minor Changes + +- 2d229b2: Enable YAML merge keys in yamlPlaceholderResolver +- 9d3ec06: Make YAML merge (<<:) support configurable in the Backstage Catalog instead of always being enabled +- 8c26af4: Enable YAML merge keys in yamlPlaceholderResolver + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/backend-openapi-utils@0.6.3 + - @backstage/catalog-client@1.12.1 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-permission-node@0.10.6 + +## @backstage/plugin-catalog-backend-module-ldap@0.12.0 + +### Minor Changes + +- 980f240: Moved from `ldapjs` dependency to `ldapts` + + ### Breaking Changes + + **Type Migration** + + Custom transformers must now accept `Entry` from `ldapts` instead of `SearchEntry` + from `ldapjs` The Entry type provides direct property access without need for + `.object()` or `.raw()` methods. + + If you have custom user or group transformers, update the signature from: + + ```typescript + (vendor: LdapVendor, config: UserConfig, entry: SearchEntry) => + Promise; + ``` + + to + + ```typescript + (vendor: LdapVendor, config: UserConfig, entry: Entry) => + Promise; + ``` + + **Search Options** + + Updated LDAP search configuration `typesOnly: false` → `attributeValues: true` + This inverts the boolean logic: `ldapjs` used negative form while `ldapts` uses + positive form. Both achieve the same result: retrieving attribute values rather + than just attribute names. + + Update LDAP search options in configuration from + + ```yaml + options: + typesOnly: false + ``` + + to + + ```yaml + options: + attributeValues: true + ``` + + **API Changes** Removed `LdapClient.searchStreaming()` method. Users should + migrate to `LdapClient.search()` instead + + If you're using `searchStreaming` directly: + + ```typescript + // Before + await client.searchStreaming(dn, options, async entry => { + // process each entry + }); + + // After + const entries = await client.search(dn, options); + for (const entry of entries) { + // process each entry + } + ``` + + > **_NOTE:_**: Both methods have always loaded all entries into memory. The + > `searchStreaming` method was only needed internally to handle `ldapjs`'s + > event-based API. + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-node@1.20.0 + +### Minor Changes + +- 9d3ec06: Make YAML merge (<<:) support configurable in the Backstage Catalog instead of always being enabled +- 8c26af4: Enable YAML merge keys in yamlPlaceholderResolver + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/catalog-model@1.7.6 + - @backstage/catalog-client@1.12.1 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-permission-node@0.10.6 + +## @backstage/plugin-gateway-backend@1.1.0 + +### Minor Changes + +- 229f63e: Added hop count tracking to prevent proxy loops. The gateway now tracks the number of proxy hops using the `backstage-gateway-hops` header and rejects requests that exceed 3 hops with a 508 Loop Detected error. +- a17d9df: Update usage of the `instanceMetadata` service. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + +## @backstage/plugin-notifications-backend@0.6.0 + +### Minor Changes + +- 87e597c: Adds support for default configuration for an entire notification channel. + This setting will also be inherited down to origins and topics while still respecting the users individual choices. + + This will be handy if you want to use a "opt-in" strategy. + +### Patch Changes + +- 15fb764: Show default settings for notifications even before receiving first notification. + + Previously, it was not possible for the users to see or modify their notification settings until they had received at + least one notification from specific origin or topic. + This update ensures that default settings are displayed from the outset, + allowing users to customize their preferences immediately. + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-notifications-node@0.2.21 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-signals-node@0.1.26 + +## @backstage/plugin-notifications-common@0.2.0 + +### Minor Changes + +- 87e597c: Adds support for default configuration for an entire notification channel. + This setting will also be inherited down to origins and topics while still respecting the users individual choices. + + This will be handy if you want to use a "opt-in" strategy. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.6 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.10.0 + +### Minor Changes + +- ff96d7e: fix scaffolder action createDeployToken to allow usage of oauth tokens + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + +## @backstage/plugin-search@1.5.0 + +### Minor Changes + +- a521911: Add support for customizable icons in `SearchResultListItemBlueprint` and related components + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-search-react@1.10.0 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-react@1.10.0 + +### Minor Changes + +- a521911: Add support for customizable icons in `SearchResultListItemBlueprint` and related components + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-techdocs@1.16.0 + +### Minor Changes + +- a521911: Add support for customizable icons in `SearchResultListItemBlueprint` and related components + +### Patch Changes + +- 71c22f3: Removed/moved unused dependencies +- a4d4a70: Fixed an issue where the entire TechDocs page would re-render when navigating between pages within the same entity's documentation. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-search-react@1.10.0 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/integration@1.18.2 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-techdocs-react@1.3.5 + - @backstage/catalog-client@1.12.1 + - @backstage/integration-react@1.2.12 + - @backstage/plugin-auth-react@0.1.21 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/app-defaults@1.7.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/plugin-permission-react@0.4.38 + +## @backstage/backend-defaults@0.13.1 + +### Patch Changes + +- 9bcfa77: Adjusted the log line wording of task worker starting + +- 91ab2eb: Fix a bug in the Gitlab URL reader where `search` did not handle multiple globs + +- fa255f5: Support for Bitbucket Cloud's API token was added as `appPassword` is deprecated (no new creation from September 9, 2025) and will be removed on June 9, 2026. + + API token usage example: + + ```yaml + integrations: + bitbucketCloud: + - username: user@domain.com + token: my-token + ``` + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. + +- b2f6a5a: Fix #31348 issue where BitbucketUrlReader ignored provided token and instead always used integration credentials + +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/backend-app-api@1.3.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config-loader@1.10.6 + - @backstage/config@1.3.6 + - @backstage/cli-node@0.2.15 + - @backstage/integration-aws-node@0.1.19 + - @backstage/plugin-permission-node@0.10.6 + +## @backstage/backend-dynamic-feature-service@0.7.6 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/plugin-catalog-backend@3.2.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-backend-node@1.3.17 + - @backstage/plugin-events-backend@0.5.8 + - @backstage/plugin-events-node@0.4.17 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config-loader@1.10.6 + - @backstage/config@1.3.6 + - @backstage/cli-node@0.2.15 + - @backstage/plugin-scaffolder-node@0.12.1 + - @backstage/backend-openapi-utils@0.6.3 + - @backstage/plugin-app-node@0.1.39 + - @backstage/plugin-permission-node@0.10.6 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/backend-openapi-utils@0.6.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + +## @backstage/catalog-client@1.12.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.6 + +## @backstage/catalog-model@1.7.6 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. + +## @backstage/cli@0.34.5 + +### Patch Changes + +- fc7cbfc: The templates executed with the `yarn new` command now supports templating filenames. +- da19cb5: Fix inconsistent behavior in the `new` command for the `@internal` scope: it now consistently defaults to the `backstage-plugin-` infix whether the `--scope` option is not set or it's set to `internal`. +- fc7cbfc: Added a template for the `yarn new` command to create an catalog entity provider. To add this template to an explicit list in the root `package.json`, use `@backstage/cli/templates/catalog-provider-module`. +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- 279279c: Fixes an issue where using the `backstage-cli new --scope` command with a scope that already includes the `@` symbol (e.g., `@backstage-community`) would result in a double `@@` prefix in the generated package name, causing invalid `package.json` files. +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/eslint-plugin@0.2.0 + - @backstage/config-loader@1.10.6 + - @backstage/config@1.3.6 + - @backstage/cli-node@0.2.15 + - @backstage/catalog-model@1.7.6 + +## @backstage/cli-node@0.2.15 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. + +## @backstage/config@1.3.6 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. + +## @backstage/config-loader@1.10.6 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/config@1.3.6 + +## @backstage/core-app-api@1.19.2 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/core-plugin-api@1.12.0 + - @backstage/config@1.3.6 + +## @backstage/core-compat-api@0.5.4 + +### Patch Changes + +- 4d03f08: Internal refactor of route reference implementations with minor updates to the `toString` implementations. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-plugin-api@1.12.0 + +## @backstage/core-components@0.18.3 + +### Patch Changes + +- 1c7e1ce: - Revert `viewbox` back to old values. + - Added `fullScreen` prop to `EntityCatalogGraphCard` +- 96ad674: Line numbers in LogViewer will not be selectable in UI anymore +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/core-plugin-api@1.12.0 + - @backstage/config@1.3.6 + +## @backstage/create-app@0.7.6 + +### Patch Changes + +- 2c1fe37: Bumped create-app version. +- 20fae88: Bumped create-app version. +- 9f939a6: Added `@backstage/plugin-app-visualizer` to the app in the `--next` template. +- fc7cbfc: Added the new `@backstage/cli/templates/catalog-provider-module` template to the explicit template configuration for the `next-app` template. + +## @backstage/dev-utils@1.1.17 + +### Patch Changes + +- b29a856: Fixed styling of the dev app by adding a lazy import of `@backstage/ui/css/styles.css`. +- Updated dependencies + - @backstage/ui@0.9.0 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/catalog-model@1.7.6 + - @backstage/app-defaults@1.7.2 + - @backstage/integration-react@1.2.12 + +## @backstage/frontend-app-api@0.13.2 + +### Patch Changes + +- 4d03f08: Internal refactor of route reference implementations with minor updates to the `toString` implementations. +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/config@1.3.6 + - @backstage/frontend-defaults@0.3.3 + +## @backstage/frontend-defaults@0.3.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/frontend-app-api@0.13.2 + - @backstage/core-components@0.18.3 + - @backstage/config@1.3.6 + - @backstage/plugin-app@0.3.2 + +## @backstage/frontend-dynamic-feature-loader@0.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/config@1.3.6 + +## @backstage/frontend-test-utils@0.4.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/frontend-app-api@0.13.2 + - @backstage/config@1.3.6 + - @backstage/plugin-app@0.3.2 + - @backstage/test-utils@1.7.13 + +## @backstage/integration@1.18.2 + +### Patch Changes + +- fa255f5: Support for Bitbucket Cloud's API token was added as `appPassword` is deprecated (no new creation from September 9, 2025) and will be removed on June 9, 2026. + + API token usage example: + + ```yaml + integrations: + bitbucketCloud: + - username: user@domain.com + token: my-token + ``` + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. + +- Updated dependencies + - @backstage/config@1.3.6 + +## @backstage/integration-aws-node@0.1.19 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/config@1.3.6 + +## @backstage/integration-react@1.2.12 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/core-plugin-api@1.12.0 + - @backstage/config@1.3.6 + +## @techdocs/cli@1.10.2 + +### Patch Changes + +- c2a2017: Fix for missing styles due to move to BUI. +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/plugin-techdocs-node@1.13.9 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + +## @backstage/test-utils@1.7.13 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/core-app-api@1.19.2 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/config@1.3.6 + +## @backstage/plugin-api-docs@0.13.1 + +### Patch Changes + +- 7c281a5: Add i18n support for Raw tab title and an error message +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-catalog@1.32.0 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-app@0.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/integration-react@1.2.12 + +## @backstage/plugin-app-backend@0.5.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config-loader@1.10.6 + - @backstage/config@1.3.6 + - @backstage/plugin-app-node@0.1.39 + +## @backstage/plugin-app-node@0.1.39 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config-loader@1.10.6 + +## @backstage/plugin-app-visualizer@0.1.25 + +### Patch Changes + +- e81b3f0: Improve tree visualizer to use a horizontal layout and fill the content space. +- 722e2df: Migrated to use `@backstage/ui`. +- Updated dependencies + - @backstage/ui@0.9.0 + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + +## @backstage/plugin-auth@0.1.2 + +### Patch Changes + +- 1609e79: Authentication content screen now uses application title. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-components@0.18.3 + +## @backstage/plugin-auth-backend@0.25.6 + +### Patch Changes + +- a9315d0: Change internal `state` column to `text` to support state of over 255 characters + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. + +- 51ff7d8: Allow configuring dynamic client registration token expiration with config `auth.experimentalDynamicClientRegistration.tokenExpiration`. + + Maximum expiration for the DCR token is 24 hours. Default expiration is 1 hour. + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## @backstage/plugin-auth-backend-module-auth0-provider@0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-backend@0.25.6 + - @backstage/plugin-auth-node@0.6.9 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.14 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.9 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config@1.3.6 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## @backstage/plugin-auth-backend-module-github-provider@0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## @backstage/plugin-auth-backend-module-google-provider@0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## @backstage/plugin-auth-backend-module-guest-provider@0.2.14 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.14 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.4.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-backend@0.25.6 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config@1.3.6 + +## @backstage/plugin-auth-backend-module-okta-provider@0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## @backstage/plugin-auth-backend-module-openshift-provider@0.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config@1.3.6 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-auth-node@0.6.9 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/catalog-client@1.12.1 + +## @backstage/plugin-auth-react@0.1.21 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + +## @backstage/plugin-bitbucket-cloud-common@0.3.4 + +### Patch Changes + +- fa255f5: Support for Bitbucket Cloud's API token was added as `appPassword` is deprecated (no new creation from September 9, 2025) and will be removed on June 9, 2026. + + API token usage example: + + ```yaml + integrations: + bitbucketCloud: + - username: user@domain.com + token: my-token + ``` + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. + +- Updated dependencies + - @backstage/integration@1.18.2 + +## @backstage/plugin-catalog-backend-module-aws@0.4.17 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-kubernetes-common@0.9.8 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-azure@0.3.11 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.8 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/backend-openapi-utils@0.6.3 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.5 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.4 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.5 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.14 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-kubernetes-common@0.9.8 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-catalog-backend-module-gerrit@0.3.8 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-gitea@0.1.6 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-github@0.11.2 + +### Patch Changes + +- 999d1c1: Added configurable `pageSizes` for GitHub GraphQL API queries to prevent `RESOURCE_LIMITS_EXCEEDED` errors with organizations with large number of repositories. Please see the [GitHub Discovery documentation](https://backstage.io/docs/integrations/github/discovery#configuration) for new configuration options. +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.16 + +### Patch Changes + +- 999d1c1: Added configurable `pageSizes` for GitHub GraphQL API queries to prevent `RESOURCE_LIMITS_EXCEEDED` errors with organizations with large number of teams and members. Please see the [GitHub Org Data documentation](https://backstage.io/docs/integrations/github/org#configuration-details) for new configuration options. +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-catalog-backend-module-github@0.11.2 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + +## @backstage/plugin-catalog-backend-module-gitlab@0.7.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.15 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/plugin-catalog-backend-module-gitlab@0.7.5 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.6 + +### Patch Changes + +- 70745c5: Correctly handle entity removal computation when DB count query returns string +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/plugin-catalog-backend@3.2.0 + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-catalog-backend-module-logs@0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.2.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + +## @backstage/plugin-catalog-backend-module-msgraph@0.8.2 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.16 + +### Patch Changes + +- a5bcb2a: fix wrong dereferencing for AsyncApi 3 documents +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.14 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-scaffolder-common@1.7.3 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.6.6 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.11 + +## @backstage/plugin-catalog-common@1.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.3 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-catalog-graph@0.5.3 + +### Patch Changes + +- a2d7ae7: Ensure the catalog graph entity card respects the height prop so the visualization scales down properly on wide screens. +- 1c7e1ce: - Revert `viewbox` back to old values. + - Added `fullScreen` prop to `EntityCatalogGraphCard` +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/catalog-model@1.7.6 + - @backstage/catalog-client@1.12.1 + +## @backstage/plugin-catalog-import@0.13.7 + +### Patch Changes + +- f987bfd: Fixed missing `catalog.entity.create` permission authorization in the default page extension. +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/integration@1.18.2 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/catalog-client@1.12.1 + - @backstage/integration-react@1.2.12 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-react@1.21.3 + +### Patch Changes + +- 36d7582: Added missing i18n +- 2b7924b: Apply default ordering of templates +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- 904d136: Fixed catalog filter "all" not appearing as selected when set as the initially selected filter. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/catalog-model@1.7.6 + - @backstage/frontend-test-utils@0.4.1 + - @backstage/catalog-client@1.12.1 + - @backstage/integration-react@1.2.12 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.23 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-catalog-unprocessed-entities-common@0.0.11 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-config-schema@0.1.74 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + +## @backstage/plugin-devtools@0.1.33 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-devtools-common@0.1.19 + - @backstage/plugin-permission-react@0.4.38 + +## @backstage/plugin-devtools-backend@0.5.11 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-devtools-common@0.1.19 + - @backstage/config-loader@1.10.6 + - @backstage/config@1.3.6 + - @backstage/plugin-permission-node@0.10.6 + +## @backstage/plugin-devtools-common@0.1.19 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-events-backend@0.5.8 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + - @backstage/backend-openapi-utils@0.6.3 + +## @backstage/plugin-events-backend-module-aws-sqs@0.4.17 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + +## @backstage/plugin-events-backend-module-azure@0.2.26 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.26 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + +## @backstage/plugin-events-backend-module-bitbucket-server@0.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + +## @backstage/plugin-events-backend-module-gerrit@0.2.26 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + +## @backstage/plugin-events-backend-module-github@0.4.6 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + +## @backstage/plugin-events-backend-module-gitlab@0.3.7 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + +## @backstage/plugin-events-backend-module-google-pubsub@0.1.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + +## @backstage/plugin-events-backend-module-kafka@0.1.5 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + +## @backstage/plugin-events-backend-test-utils@0.1.50 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.17 + +## @backstage/plugin-events-node@0.4.17 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + +## @backstage/plugin-home@0.8.14 + +### Patch Changes + +- 2ac5d29: Allow customization of `VisitList` by adding optional `enrichVisit`, `transformPathname`, `canSave` functions to `VisitsStorageApi`, along with `VisitDisplayProvider` for colors, labels +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-home-react@0.1.32 + - @backstage/catalog-client@1.12.1 + +## @backstage/plugin-home-react@0.1.32 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + +## @backstage/plugin-kubernetes@0.12.13 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/plugin-kubernetes-common@0.9.8 + - @backstage/plugin-kubernetes-react@0.5.13 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-kubernetes-backend@0.20.4 + +### Patch Changes + +- 71c22f3: Removed/moved unused dependencies +- 1906d37: Updated dependency `@kubernetes/client-node` to `1.4.0`. +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-kubernetes-common@0.9.8 + - @backstage/plugin-kubernetes-node@0.3.6 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/catalog-client@1.12.1 + - @backstage/plugin-permission-node@0.10.6 + +## @backstage/plugin-kubernetes-cluster@0.0.31 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/plugin-kubernetes-common@0.9.8 + - @backstage/plugin-kubernetes-react@0.5.13 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-kubernetes-common@0.9.8 + +### Patch Changes + +- 1906d37: Updated dependency `@kubernetes/client-node` to `1.4.0`. +- Updated dependencies + - @backstage/plugin-permission-common@0.9.3 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-kubernetes-node@0.3.6 + +### Patch Changes + +- 1906d37: Updated dependency `@kubernetes/client-node` to `1.4.0`. +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-kubernetes-common@0.9.8 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-kubernetes-react@0.5.13 + +### Patch Changes + +- 1906d37: Updated dependency `@kubernetes/client-node` to `1.4.0`. +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/core-components@0.18.3 + - @backstage/plugin-kubernetes-common@0.9.8 + - @backstage/core-plugin-api@1.12.0 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-mcp-actions-backend@0.1.5 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/catalog-client@1.12.1 + +## @backstage/plugin-mui-to-bui@0.2.1 + +### Patch Changes + +- 5c614ff: Updated BUI checkbox preview example to align with new component API. +- Updated dependencies + - @backstage/ui@0.9.0 + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/core-plugin-api@1.12.0 + +## @backstage/plugin-notifications@0.5.11 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-signals-react@0.0.17 + +## @backstage/plugin-notifications-backend-module-email@0.3.16 + +### Patch Changes + +- 22a5362: Updated `AWS SES` client to version 2 to support `nodemailer` version 7. +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-notifications-node@0.2.21 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/catalog-client@1.12.1 + +## @backstage/plugin-notifications-backend-module-slack@0.2.1 + +### Patch Changes + +- d959bec: When an error message is logged due to inability to send a message with the Slack SDK, include the Slack Channel ID in the message to aid debugging. +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-notifications-node@0.2.21 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-notifications-node@0.2.21 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/catalog-model@1.7.6 + - @backstage/catalog-client@1.12.1 + - @backstage/plugin-signals-node@0.1.26 + +## @backstage/plugin-org@0.6.46 + +### Patch Changes + +- 6db9e7e: Improved responsiveness of GroupProfileCard component +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-org-react@0.1.44 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/catalog-model@1.7.6 + - @backstage/catalog-client@1.12.1 + +## @backstage/plugin-permission-backend@0.7.6 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config@1.3.6 + - @backstage/plugin-permission-node@0.10.6 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.14 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/plugin-permission-node@0.10.6 + +## @backstage/plugin-permission-common@0.9.3 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/config@1.3.6 + +## @backstage/plugin-permission-node@0.10.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config@1.3.6 + +## @backstage/plugin-permission-react@0.4.38 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/config@1.3.6 + +## @backstage/plugin-proxy-backend@0.6.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-proxy-node@0.1.10 + +## @backstage/plugin-proxy-node@0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + +## @backstage/plugin-scaffolder@1.34.3 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/integration@1.18.2 + - @backstage/plugin-scaffolder-react@1.19.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-techdocs-react@1.3.5 + - @backstage/catalog-client@1.12.1 + - @backstage/integration-react@1.2.12 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-scaffolder-common@1.7.3 + +## @backstage/plugin-scaffolder-backend@3.0.1 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.15 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.16 + - @backstage/plugin-bitbucket-cloud-common@0.3.4 + - @backstage/integration@1.18.2 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.10.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-events-node@0.4.17 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-scaffolder-node@0.12.1 + - @backstage/backend-openapi-utils@0.6.3 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.14 + - @backstage/plugin-permission-node@0.10.6 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.15 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.15 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.15 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.15 + - @backstage/plugin-scaffolder-backend-module-github@0.9.2 + - @backstage/plugin-scaffolder-common@1.7.3 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.15 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.16 + +### Patch Changes + +- fa255f5: Support for Bitbucket Cloud's API token was added as `appPassword` is deprecated (no new creation from September 9, 2025) and will be removed on June 9, 2026. + + API token usage example: + + ```yaml + integrations: + bitbucketCloud: + - username: user@domain.com + token: my-token + ``` + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.15 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.15 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.15 + +### Patch Changes + +- fa255f5: Support for Bitbucket Cloud's API token was added as `appPassword` is deprecated (no new creation from September 9, 2025) and will be removed on June 9, 2026. + + API token usage example: + + ```yaml + integrations: + bitbucketCloud: + - username: user@domain.com + token: my-token + ``` + +- Updated dependencies + - @backstage/plugin-bitbucket-cloud-common@0.3.4 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.15 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.15 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.15 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.15 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.15 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + +## @backstage/plugin-scaffolder-backend-module-github@0.9.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-scaffolder-node@0.12.1 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-notifications-node@0.2.21 + - @backstage/plugin-scaffolder-node@0.12.1 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.15 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.2.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.16 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-scaffolder-node-test-utils@0.3.5 + - @backstage/plugin-scaffolder-node@0.12.1 + +## @backstage/plugin-scaffolder-common@1.7.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-scaffolder-node@0.12.1 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-scaffolder-common@1.7.3 + +## @backstage/plugin-scaffolder-node-test-utils@0.3.5 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-test-utils@1.10.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-scaffolder-node@0.12.1 + +## @backstage/plugin-scaffolder-react@1.19.3 + +### Patch Changes + +- 886a8a1: Fixed a bug in the Scaffolder's template parsing in the `useTemplateSchema` hook by removing the title instead of setting it to `undefined` +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/catalog-model@1.7.6 + - @backstage/catalog-client@1.12.1 + - @backstage/plugin-scaffolder-common@1.7.3 + +## @backstage/plugin-search-backend@2.0.8 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-backend-node@1.3.17 + - @backstage/config@1.3.6 + - @backstage/backend-openapi-utils@0.6.3 + - @backstage/plugin-permission-node@0.10.6 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-catalog@0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-backend-node@1.3.17 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/catalog-client@1.12.1 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-elasticsearch@1.7.8 + +### Patch Changes + +- 71c22f3: Removed/moved unused dependencies +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-search-backend-node@1.3.17 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-explore@0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-search-backend-node@1.3.17 + - @backstage/config@1.3.6 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-pg@0.5.50 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-search-backend-node@1.3.17 + - @backstage/config@1.3.6 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-search-backend-node@1.3.17 + - @backstage/config@1.3.6 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-techdocs@0.4.8 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-backend-node@1.3.17 + - @backstage/plugin-techdocs-node@1.13.9 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/catalog-client@1.12.1 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-node@1.3.17 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/config@1.3.6 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-common@1.2.21 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-signals@0.0.25 + +### Patch Changes + +- 71c22f3: Removed/moved unused dependencies +- f0f006e: Fixes a bug where the `SignalClient` would try to subscribe to the same channel twice after an error, instead of just once. +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-signals-react@0.0.17 + +## @backstage/plugin-signals-backend@0.3.10 + +### Patch Changes + +- 71c22f3: Removed/moved unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + - @backstage/plugin-signals-node@0.1.26 + +## @backstage/plugin-signals-node@0.1.26 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config@1.3.6 + +## @backstage/plugin-signals-react@0.0.17 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.0 + +## @backstage/plugin-techdocs-addons-test-utils@1.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.16.0 + - @backstage/plugin-search-react@1.10.0 + - @backstage/plugin-catalog@1.32.0 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/plugin-techdocs-react@1.3.5 + - @backstage/integration-react@1.2.12 + - @backstage/test-utils@1.7.13 + +## @backstage/plugin-techdocs-backend@2.1.2 + +### Patch Changes + +- 71c22f3: Removed/moved unused dependencies +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-techdocs-node@1.13.9 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/catalog-client@1.12.1 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.30 + +### Patch Changes + +- 6929480: ExpandableCollapse Techdocs Addon was breaking native sidebar collapse on Firefox +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/integration@1.18.2 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-techdocs-react@1.3.5 + - @backstage/integration-react@1.2.12 + +## @backstage/plugin-techdocs-node@1.13.9 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-techdocs-react@1.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-user-settings@0.8.29 + +### Patch Changes + +- 2b6fda3: Revert `storageApiRef` implementation +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-signals-react@0.0.17 + +## @backstage/plugin-user-settings-backend@0.3.8 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/plugin-signals-node@0.1.26 + +## example-app@0.2.115 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-api-docs@0.13.1 + - @backstage/ui@0.9.0 + - @backstage/plugin-org@0.6.46 + - @backstage/cli@0.34.5 + - @backstage/plugin-techdocs@1.16.0 + - @backstage/plugin-signals@0.0.25 + - @backstage/frontend-app-api@0.13.2 + - @backstage/plugin-search-react@1.10.0 + - @backstage/plugin-catalog@1.32.0 + - @backstage/plugin-search@1.5.0 + - @backstage/plugin-home@0.8.14 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.30 + - @backstage/plugin-scaffolder-react@1.19.3 + - @backstage/plugin-catalog-graph@0.5.3 + - @backstage/core-components@0.18.3 + - @backstage/plugin-user-settings@0.8.29 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/plugin-catalog-import@0.13.7 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/plugin-scaffolder@1.34.3 + - @backstage/plugin-catalog-unprocessed-entities@0.2.23 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-mui-to-bui@0.2.1 + - @backstage/plugin-devtools@0.1.33 + - @backstage/plugin-kubernetes@0.12.13 + - @backstage/plugin-notifications@0.5.11 + - @backstage/plugin-techdocs-react@1.3.5 + - @backstage/app-defaults@1.7.2 + - @backstage/integration-react@1.2.12 + - @backstage/plugin-auth-react@0.1.21 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-kubernetes-cluster@0.0.31 + - @backstage/plugin-search-common@1.2.21 + +## example-app-next@0.0.29 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-api-docs@0.13.1 + - @backstage/ui@0.9.0 + - @backstage/plugin-org@0.6.46 + - @backstage/cli@0.34.5 + - @backstage/plugin-techdocs@1.16.0 + - @backstage/plugin-signals@0.0.25 + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/frontend-app-api@0.13.2 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-search-react@1.10.0 + - @backstage/plugin-catalog@1.32.0 + - @backstage/plugin-search@1.5.0 + - @backstage/plugin-home@0.8.14 + - @backstage/plugin-app-visualizer@0.1.25 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.30 + - @backstage/plugin-scaffolder-react@1.19.3 + - @backstage/plugin-catalog-graph@0.5.3 + - @backstage/core-components@0.18.3 + - @backstage/plugin-user-settings@0.8.29 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/plugin-auth@0.1.2 + - @backstage/plugin-catalog-import@0.13.7 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/plugin-scaffolder@1.34.3 + - @backstage/plugin-catalog-unprocessed-entities@0.2.23 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/frontend-defaults@0.3.3 + - @backstage/plugin-app@0.3.2 + - @backstage/plugin-kubernetes@0.12.13 + - @backstage/plugin-notifications@0.5.11 + - @backstage/plugin-techdocs-react@1.3.5 + - @backstage/app-defaults@1.7.2 + - @backstage/integration-react@1.2.12 + - @backstage/plugin-auth-react@0.1.21 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-kubernetes-cluster@0.0.31 + - @backstage/plugin-search-common@1.2.21 + +## app-next-example-plugin@0.0.29 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-components@0.18.3 + +## example-backend@0.0.44 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/plugin-catalog-backend@3.2.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.7.8 + - @backstage/plugin-kubernetes-backend@0.20.4 + - @backstage/plugin-techdocs-backend@2.1.2 + - @backstage/plugin-signals-backend@0.3.10 + - @backstage/plugin-notifications-backend@0.6.0 + - @backstage/plugin-catalog-backend-module-openapi@0.2.16 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-backend@0.7.6 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-backend@2.0.8 + - @backstage/plugin-auth-backend@0.25.6 + - @backstage/plugin-scaffolder-backend@3.0.1 + - @backstage/plugin-search-backend-node@1.3.17 + - @backstage/plugin-events-backend@0.5.8 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.8 + - @backstage/plugin-devtools-backend@0.5.11 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.6 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-mcp-actions-backend@0.1.5 + - @backstage/plugin-app-backend@0.5.8 + - @backstage/plugin-auth-backend-module-github-provider@0.3.9 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.14 + - @backstage/plugin-auth-backend-module-openshift-provider@0.1.2 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.14 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.6 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.14 + - @backstage/plugin-permission-node@0.10.6 + - @backstage/plugin-proxy-backend@0.6.8 + - @backstage/plugin-scaffolder-backend-module-github@0.9.2 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.16 + - @backstage/plugin-search-backend-module-catalog@0.3.10 + - @backstage/plugin-search-backend-module-explore@0.3.9 + - @backstage/plugin-search-backend-module-techdocs@0.4.8 + +## e2e-test@0.2.34 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.7.6 + +## @internal/frontend@0.0.15 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + +## @internal/scaffolder@0.0.15 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/plugin-scaffolder-react@1.19.3 + +## techdocs-cli-embedded-app@0.2.114 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.9.0 + - @backstage/cli@0.34.5 + - @backstage/plugin-techdocs@1.16.0 + - @backstage/plugin-catalog@1.32.0 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-techdocs-react@1.3.5 + - @backstage/app-defaults@1.7.2 + - @backstage/integration-react@1.2.12 + - @backstage/test-utils@1.7.13 + +## @internal/plugin-todo-list@1.0.45 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + +## @internal/plugin-todo-list-backend@1.0.45 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + +## @internal/plugin-todo-list-common@1.0.28 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.3 diff --git a/docs/releases/v1.45.0.md b/docs/releases/v1.45.0.md new file mode 100644 index 0000000000..d2d1ae948b --- /dev/null +++ b/docs/releases/v1.45.0.md @@ -0,0 +1,72 @@ +--- +id: v1.45.0 +title: v1.45.0 +description: Backstage Release v1.45.0 +--- + +These are the release notes for the v1.45.0 release of [Backstage](https://backstage.io/). + +A huge thanks to the whole team of maintainers and contributors as well as the amazing Backstage Community for the hard work in getting this release developed and done. + +## Highlights + +### Backstage UI: Breaking Changes + New Components + +Multiple components migrated from Base UI to React Aria Components including `Avatar`, `Checkbox`, and removal of the `Collapsible` component in favour of `Accordion`. + +Check the [CHANGELOG.md](https://github.com/backstage/backstage/blob/master/packages/ui/CHANGELOG.md) for more migration guides on any breaking changes that come with these latest updates. + +### BREAKING: `ldapjs` -> `ldapts` + +Moved from deprecated `ldapjs` dependency to `ldapts`, with breaking changes to custom transformer types and search options. + +Contributed by [@ganives](https://github.com/ganievs) in [#30594](https://github.com/backstage/backstage/pull/30594) + +Check the [CHANGELOG.md](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-ldap/CHANGELOG.md) for more migration guides on any breaking changes that come with these latest updates. + +### NFS: Plugin-Relative Extension Attachments + +Added support for plugin-relative `attachTo` declarations for extension definitions, allowing extensions to attach to other extensions of a particular kind in the same plugin rather than requiring exact extension IDs. + +### NFS: Forwards Compatibility for Route Refs + +It is now possible to use route references from the old frontend system directly in the new one. That means there's no longer a need to use `convertLegacyRouteRef` or `convertLegacyRouteRefs` to re-use route refs in implementations for the new system. This both simplifies migration, and reduces risk for cross system issues while partially migrated. + +This requires no immediate action on your part. As long as a plugin intends to support the old system, it can still keep defining its route refs using the old system without issues. And calling `convertLegacyRouteRef` or `convertLegacyRouteRefs` does not cause problems. + +### Configurable Dynamic Client Registration Token Expiration + +Allow configuring dynamic client registration token expiration with config `auth.experimentalDynamicClientRegistration.tokenExpiration`. Maximum expiration for the DCR token is 24 hours. Default expiration is 1 hour. Contributed by [@drodil](https://github.com/drodil) in [#31278](https://github.com/backstage/backstage/pull/31278) + +### Support for Bitbucket Cloud API tokens + +Since Bitbucket Cloud is phasing out support for the `appPassword` tokens, you can now instead specify the more modern API tokens using the `token` field of your Bitbucket config. + +### Support for PostgreSQL 18 + +The default setup for `TestDatabases` will start running tests against PG14 and PG18 where available, instead of PG13 and PG17 as previously. If you pass in explicit database IDs to your test database instance, those will still be respected. + +### `coreServices.rootInstanceMetadata` is stable + +The backend service `coreServices.rootInstanceMetadata` is now available as stable. It currently has one method - that lets you list all of the installed backend plugins and their modules on the current instance. + +## Security Fixes + +This release does not contain any security fixes. + +## Upgrade path + +We recommend that you keep your Backstage project up to date with this latest release. For more guidance on how to upgrade, check out the documentation for [keeping Backstage updated](https://backstage.io/docs/getting-started/keeping-backstage-updated). + +## Links and References + +Below you can find a list of links and references to help you learn about and start using this new release. + +- [Backstage official website](https://backstage.io/), [documentation](https://backstage.io/docs/), and [getting started guide](https://backstage.io/docs/getting-started/) +- [GitHub repository](https://github.com/backstage/backstage) +- Backstage's [versioning and support policy](https://backstage.io/docs/overview/versioning-policy) +- [Community Discord](https://discord.gg/backstage-687207715902193673) for discussions and support +- [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.45.0-changelog.md) +- Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) + +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.46.0-changelog.md b/docs/releases/v1.46.0-changelog.md new file mode 100644 index 0000000000..22e6e39dab --- /dev/null +++ b/docs/releases/v1.46.0-changelog.md @@ -0,0 +1,2369 @@ +# Release v1.46.0 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.46.0](https://backstage.github.io/upgrade-helper/?to=1.46.0) + +## @backstage/plugin-techdocs-addons-test-utils@2.0.0 + +### Major Changes + +- 8d6709e: **BREAKING**: `TechDocsAddonTester.renderWithEffects()` no longer returns a screen; this means that you can no longer grab assertions such as `getByText` from its return value. + + Newer versions of `@testing-library` recommends using the `screen` export for assertions - and removing this from the addon tester contract allows us to more freely iterate on which underlying version of the testing library is being used. + + One notable effect of this, however, is that the `@testing-library` `screen` does NOT support assertions on the shadow DOM, which techdocs relies on. You will therefore want to add a dependency on [the `shadow-dom-testing-library` package](https://github.com/konnorrogers/shadow-dom-testing-library/) in your tests, and using its `screen` and its dedicated `*Shadow*` methods. As an example, if you keep doing `getByText` you will not get matches inside the shadow DOM - switch to `getByShadowText` instead. + + ```ts + import { screen } from 'shadow-dom-testing-library'; + + // ... render the addon ... + await TechDocsAddonTester.buildAddonsInTechDocs([]) + .withDom(TEST_CONTENT) + .renderWithEffects(); + + expect(screen.getByShadowText('TEST_CONTENT')).toBeInTheDocument(); + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/plugin-techdocs@1.16.1 + - @backstage/plugin-catalog@1.32.1 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-search-react@1.10.1 + - @backstage/test-utils@1.7.14 + - @backstage/core-plugin-api@1.12.1 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-techdocs-react@1.3.6 + +## @backstage/backend-app-api@1.4.0 + +### Minor Changes + +- 5a2d538: Introduced backend startup result tracking and error handling. The `Backend.start()` method now returns a `BackendStartupResult` with detailed success/failure status and timing information for all plugins and modules. When startup fails, a `BackendStartupError` is thrown that includes the complete startup results, making it easier to diagnose which plugins or modules failed. + + This also improves the default error message when backend startup fails, and of course makes it possible to craft your own custom error reporting based on the startup results. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/backend-defaults@0.14.0 + +### Minor Changes + +- fa43826: Move `better-sqlite3` from dependencies to peer dependencies +- 2bc4e02: **BREAKING** The correct configuration options for Valkey are now being used. + + These changes are **required** to `app-config.yaml`: + + ```diff + backend: + cache: + store: valkey + connection: ... + client: + - namespace: 'my-app' + - keyPrefixSeparator: ':' + + keyPrefix: 'my-app:' + - clearBatchSize: 1000 + - useUnlink: false + ``` + + In comparison to Redis, Valkey requires the full `keyPrefix` including the separator to be specified instead of separate `namespace` and `keyPrefixSeparator` options. Also, Valkey does not support the `clearBatchSize` and `useUnlink` options. + +### Patch Changes + +- 37fba1d: Added support for Bitbucket Cloud OAuth. This introduces an alternative authentication method using a workspace OAuth consumer, alongside App Passwords (deprecated) and API tokens. OAuth does not require a bot or service account and avoids token expiry issues. + + **BREAKING CHANGES** + + - **@backstage/integration** (`src/bitbucketCloud/core.ts`) + + - `getBitbucketCloudRequestOptions` now returns a `Promise` and **must** be awaited. + + - **@backstage/plugin-scaffolder-backend-module-bitbucket-cloud** (`src/actions/helpers.ts`) + - `getBitbucketClient` now returns a `Promise` and **must** be awaited. + - `getAuthorizationHeader` now returns a `Promise` and **must** be awaited. + + **OAuth usage example** + + ```yaml + integrations: + bitbucketCloud: + - clientId: client-id + clientSecret: client-secret + ``` + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 + +- aa79251: build(deps): bump `node-forge` from 1.3.1 to 1.3.2 + +- f96edff: Allow configuration of the `referrerPolicy` + +- fb029b6: Updated luxon types + +- d9759a1: **BREAKING ALPHA**: The old `instanceMetadataService` has been removed from alpha. Please switch over to using the stable `coreServices.rootInstanceMetadata` and related types instead, available from `@backstage/backend-plugin-api`. + +- 847a330: Fix for `jose` types + +- 25b560e: Internal change to support new versions of the `logform` library + +- 2a0c4b0: Adds a new experimental `RootSystemMetadataService` for tracking the collection of Backstage instances that may be deployed at any one time. It currently offers a single API, `getInstalledPlugins` that returns a list of installed plugins based on config you have set up in `discovery.endpoints` as well as the plugins installed on the instance you're calling the API with. It does not handle wildcard values or fallback values. The intention is for this plugin to provide plugin authors with a simple interface to fetch a trustworthy list of all installed plugins. + +- 3016a79: Updated dependency `@types/archiver` to `^7.0.0`. + +- 42db6a6: Don't warn when parsing `storeOptions` for `memory` cache + +- Updated dependencies + - @backstage/cli-node@0.2.16 + - @backstage/integration@1.19.0 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-events-node@0.4.18 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-app-api@1.4.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/config-loader@1.10.7 + - @backstage/backend-dev-utils@0.1.6 + +## @backstage/backend-plugin-api@1.6.0 + +### Minor Changes + +- 2a0c4b0: Adds a new experimental `RootSystemMetadataService` for tracking the collection of Backstage instances that may be deployed at any one time. It currently offers a single API, `getInstalledPlugins` that returns a list of installed plugins based on config you have set up in `discovery.endpoints` as well as the plugins installed on the instance you're calling the API with. It does not handle wildcard values or fallback values. The intention is for this plugin to provide plugin authors with a simple interface to fetch a trustworthy list of all installed plugins. + +### Patch Changes + +- d9759a1: **BREAKING ALPHA**: The old `instanceMetadataService` has been removed from alpha. Please switch over to using the stable `coreServices.rootInstanceMetadata` and related types instead, available from `@backstage/backend-plugin-api`. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/cli-common@0.1.16 + +## @backstage/cli@0.35.0 + +### Minor Changes + +- f6f22a9: Provide `--no-node-snapshot` by default when running the `package start` or `package test`. You can disable this behavior by providing `NODE_OPTIONS='--node-snapshot'`. + +- f8dff94: Switched the default module resolution to `bundler` and the `module` setting to `ES2020`. + + You may need to bump some dependencies as part of this change and fix imports in code. The most common source of this is that type checking will now consider the `exports` field in `package.json` when resolving imports. This in turn can break older versions of packages that had incompatible `exports` fields. Generally these issues will have already been fixed in the upstream packages. + + You might be tempted to use `--skipLibCheck` to hide issues due to this change, but it will weaken the type safety of your project. If you run into a large number of issues and want to keep the old behavior, you can reset the `moduleResolution` and `module` settings your own `tsconfig.json` file to `node` and `ESNext` respectively. But keep in mind that the `node` option will be removed in future versions of TypeScript. + + A future version of Backstage will make these new settings mandatory, as we move to rely on the `exports` field for type resolution in packages, rather than the `typesVersions` field. + +- cd0b8a1: **BREAKING**: `jest` is now a peer dependency. If you run tests using Backstage CLI, you must add Jest and its environment dependencies as `devDependencies` in your project. + + You can choose to install either Jest 29 or Jest 30. The built-in Jest version before this change was Jest 29, however, we recommend that you switch to Jest 30. Upgrading will solve the `Could not parse CSS stylesheet` errors, allow you to use MSW v2 in web packages, and ensure that you remain compatible with future versions of the Backstage CLI. Support for Jest 29 is temporary, with the purpose of allowing you to upgrade at your own pace, but it will eventually be removed. + + - **Jest 29**: Install `jest@^29` and `jest-environment-jsdom@^29`. No migration needed, but you may see `Could not parse CSS stylesheet` warnings/errors when testing components from `@backstage/ui` or other packages using CSS `@layer` declarations. + - **Jest 30**: Install `jest@^30`, `@jest/environment-jsdom-abstract@^30`, and `jsdom@^27`. Fixes the stylesheet parsing warnings/errors, but requires migration steps. + + See the [Jest 30 migration guide](https://backstage.io/docs/tutorials/jest30-migration) for detailed migration instructions. + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 + +- e7db290: Add missing peer/dev dependencies to the frontend plugin template. + + `react-dom` was not declared as a peer dependency, causing module resolution + errors when generating plugins outside a Backstage monorepo. This adds + `react-dom` to `peerDependencies` (for consuming apps) and `devDependencies` + (for local development). `react-router-dom` is also added to `peerDependencies` (for consuming apps) and `devDependencies` + to support routing during plugin development. + + Fixes: + + - Module not found: Can't resolve 'react-dom' + - Module not found: Can't resolve 'react-router-dom' + +- 1226647: Updated dependency `esbuild` to `^0.27.0`. + +- f89a074: Updated dependency `@pmmmwh/react-refresh-webpack-plugin` to `^0.6.0`. + +- 2b81751: Updated dependency `webpack` to `~5.103.0`. + +- fafd9e1: Fixed internal usage of `yargs`. + +- c8c2329: Add proxy configuration from env-vars to create-app tasks + +- 2bae83a: Switched compilation target to ES2022 in order to match the new set of supported Node.js versions, which are 22 and 24. + + The TypeScript compilation target has been set to ES2022, because setting it to a higher target will break projects on older TypeScript versions. If you use a newer TypeScript version in your own project, you can bump `compilerOptions.target` to ES2023 or ES2024 in your own `tsconfig.json` file. + +- 7fbac5c: Updated to use new utilities from `@backstage/cli-common`. + +- 2bae83a: Bumped dev dependencies `@types/node` + +- Updated dependencies + - @backstage/cli-node@0.2.16 + - @backstage/integration@1.19.0 + - @backstage/cli-common@0.1.16 + - @backstage/config-loader@1.10.7 + +## @backstage/integration@1.19.0 + +### Minor Changes + +- 37fba1d: Added support for Bitbucket Cloud OAuth. This introduces an alternative authentication method using a workspace OAuth consumer, alongside App Passwords (deprecated) and API tokens. OAuth does not require a bot or service account and avoids token expiry issues. + + **BREAKING CHANGES** + + - **@backstage/integration** (`src/bitbucketCloud/core.ts`) + + - `getBitbucketCloudRequestOptions` now returns a `Promise` and **must** be awaited. + + - **@backstage/plugin-scaffolder-backend-module-bitbucket-cloud** (`src/actions/helpers.ts`) + - `getBitbucketClient` now returns a `Promise` and **must** be awaited. + - `getAuthorizationHeader` now returns a `Promise` and **must** be awaited. + + **OAuth usage example** + + ```yaml + integrations: + bitbucketCloud: + - clientId: client-id + clientSecret: client-secret + ``` + +### Patch Changes + +- a26a322: Added support for using a GitHub App installation to generate tokens for public repository access when the `publicAccess` option is enabled. When all other authentication methods fail (e.g., the app is not installed in that organization), the provider will now use an available installation to generate a token that can be used to access public repositories as read only. +- fb029b6: Updated luxon types +- e15fdae: Made the github urls case insensitive. + +## @backstage/ui@0.10.0 + +### Minor Changes + +- 16543fa: **Breaking change** The `Cell` component has been refactored to be a generic wrapper component that accepts `children` for custom cell content. The text-specific functionality (previously part of `Cell`) has been moved to a new `CellText` component. + + ### Migration Guide + + If you were using `Cell` with text-specific props (`title`, `description`, `leadingIcon`, `href`), you need to update your code to use `CellText` instead: + + **Before:** + + ```tsx + } + href="/path" + /> + ``` + + **After:** + + ```tsx + } + href="/path" + /> + ``` + + For custom cell content, use the new generic `Cell` component: + + ```tsx + {/* Your custom content */} + ``` + +### Patch Changes + +- 50b7927: Fixed Checkbox indicator showing checkmark color when unchecked. + + Affected components: Checkbox + +- 5bacf55: Fixed `ButtonIcon` incorrectly applying `className` to inner elements instead of only the root element. + + Affected components: ButtonIcon + +- b3ad928: Fixed Table Row component to correctly handle cases where no `href` is provided, preventing unnecessary router provider wrapping and fixing the cursor incorrectly showing as a pointer despite the element not being a link. + + Affected components: Row + +- a20d317: Added row selection support with visual state styling for hover, selected, and pressed states. Fixed checkbox rendering to only show for multi-select toggle mode. + + Affected components: Table, TableHeader, Row, Column + +- fe7c751: Fixed `useTable` hook to prioritize `providedRowCount` over data length for accurate row count in server-side pagination scenarios. + +- c145031: Fixed Table column sorting indicator to show up arrow when no sort is active, correctly indicating that clicking will sort ascending. + + Affected components: Column + +## @backstage/plugin-auth-backend-module-github-provider@0.4.0 + +### Minor Changes + +- b3286d5: Added the `github.com/user-id` annotation to store GitHub's user ID (immutable) in user entities. Also includes addition of the `userIdMatchingUserEntityAnnotation` sign-in resolver that matches users by the new ID. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-catalog-backend@3.3.0 + +### Minor Changes + +- dce1824: Added `ActionsRegistry` actions for `register-entity` and `unregister-entity` + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 8101ec1: Fixed default refresh service to go through the whole ancestry of the entity. +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-openapi-utils@0.6.4 + - @backstage/plugin-events-node@0.4.18 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-github@0.12.0 + +### Minor Changes + +- b3286d5: Added the `github.com/user-id` annotation to store GitHub's user ID (immutable) in user entities. Also includes addition of the `userIdMatchingUserEntityAnnotation` sign-in resolver that matches users by the new ID. + +### Patch Changes + +- ed5a7a3: Introduce new configuration option to exclude suspended users from GitHub Enterprise instances. + + When it’s set to true, suspended users won’t be returned when querying the organization users for GitHub Enterprise instances. + Note that this option should be used only against GitHub Enterprise instances, the property does not exist in the github.com GraphQL schema, setting it will cause a schema validation error and the syncing of users will fail. + +- a413977: Added configurable `pageSizes` option to `GithubOrgEntityProvider` for GitHub GraphQL API queries to prevent `RESOURCE_LIMITS_EXCEEDED` errors with organizations with large number of teams and members. This aligns the configuration options with `GithubMultiOrgEntityProvider`. + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-events-backend-module-kafka@0.2.0 + +### Minor Changes + +- 2c74ea9: Added support for multiple named instances in `kafkaConsumingEventPublisher` configuration. The previous single configuration format is still supported for backward compatibility. +- 2c74ea9: Added `KafkaPublishingEventConsumer` to support sending Backstage events to Kafka topics. + + This addition enables Backstage to publish events to external Kafka systems, complementing the existing ability to receive events from Kafka. This allows for better integration with external systems that rely on Kafka for event streaming. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-kubernetes-backend@0.21.0 + +### Minor Changes + +- 7f9846f: Add possibility to extends Kubernetes REST API. Add fetcher to parameters for custom objects provider + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- fb029b6: Updated luxon types +- e9589d9: Replace `@aws-sdk/signature-v4` with `@smithy/signature-v4`, + as stated in the [package documentation](https://www.npmjs.com/package/@aws-sdk/signature-v4?activeTab=readme) +- 8fa8d87: Add Kubernetes Plugin Secrets Accordion with masked secret datas +- Updated dependencies + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-kubernetes-node@0.4.0 + - @backstage/plugin-kubernetes-common@0.9.9 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-kubernetes-node@0.4.0 + +### Minor Changes + +- 7f9846f: Add possibility to extends Kubernetes REST API. Add fetcher to parameters for custom objects provider + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-kubernetes-common@0.9.9 + +## @backstage/plugin-notifications-backend-module-slack@0.3.0 + +### Minor Changes + +- f95a516: Enables optional routes to Slack channels for broadcast notifications based on origin and/or topics. + +### Patch Changes + +- b80857a: Slack notification handler throttling can now be configured with the `concurrencyLimit` and `throttleInterval` options. +- f8230e4: Updated dependency `@faker-js/faker` to `^10.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-notifications-node@0.2.22 + +## @backstage/plugin-scaffolder@1.35.0 + +### Minor Changes + +- dab3d3f: Added field extension `RepoOwnerPicker` for retrieving GitHub repository owners. + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/plugin-scaffolder-react@1.19.4 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-permission-react@0.4.39 + - @backstage/plugin-scaffolder-common@1.7.4 + - @backstage/plugin-techdocs-react@1.3.6 + +## @backstage/plugin-scaffolder-backend@3.1.0 + +### Minor Changes + +- a4cd405: Add `defaultEnvironment` config to scaffolder to enable more flexible and custom templates. Now it's possible enable access to default parameters and secrets in templates, improving security and reducing complexity. + +### Patch Changes + +- be5972b: Fixed a bug where config was not passed to NunjucksWorkflowRunner, causing defaultEnvironment to be undefined +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 2bae83a: Updated `isolated-vm` to `6.0.1` +- 25b560e: Internal change to support new versions of the `logform` library +- 8f4aded: Fixing OpenAPI definition +- 1226647: Updated dependency `esbuild` to `^0.27.0`. +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-gitlab@0.11.0 + - @backstage/integration@1.19.0 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.5 + - @backstage/backend-defaults@0.14.0 + - @backstage/backend-openapi-utils@0.6.4 + - @backstage/plugin-events-node@0.4.18 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-backend-module-github@0.9.3 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.17 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.16 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.16 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.16 + - @backstage/plugin-scaffolder-common@1.7.4 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.0 + +### Minor Changes + +- 37fba1d: Added support for Bitbucket Cloud OAuth. This introduces an alternative authentication method using a workspace OAuth consumer, alongside App Passwords (deprecated) and API tokens. OAuth does not require a bot or service account and avoids token expiry issues. + + **BREAKING CHANGES** + + - **@backstage/integration** (`src/bitbucketCloud/core.ts`) + + - `getBitbucketCloudRequestOptions` now returns a `Promise` and **must** be awaited. + + - **@backstage/plugin-scaffolder-backend-module-bitbucket-cloud** (`src/actions/helpers.ts`) + - `getBitbucketClient` now returns a `Promise` and **must** be awaited. + - `getAuthorizationHeader` now returns a `Promise` and **must** be awaited. + + **OAuth usage example** + + ```yaml + integrations: + bitbucketCloud: + - clientId: client-id + clientSecret: client-secret + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.5 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.11.0 + +### Minor Changes + +- f2d034b: In the `gitlabRepoPush` action, add 'auto' possibility for `commitAction` input. + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-search-backend-node@1.4.0 + +### Minor Changes + +- 4d3ddb9: Improving method that search tokenizer breaks apart entity names + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/app-defaults@1.7.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/plugin-permission-react@0.4.39 + +## @backstage/backend-dev-utils@0.1.6 + +### Patch Changes + +- 2bae83a: Internal update for Node.js v24 support. + +## @backstage/backend-dynamic-feature-service@0.7.7 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/cli-node@0.2.16 + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-defaults@0.14.0 + - @backstage/backend-openapi-utils@0.6.4 + - @backstage/plugin-app-node@0.1.40 + - @backstage/plugin-catalog-backend@3.3.0 + - @backstage/plugin-events-backend@0.5.9 + - @backstage/plugin-events-node@0.4.18 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/cli-common@0.1.16 + - @backstage/config-loader@1.10.7 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/backend-openapi-utils@0.6.4 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/backend-test-utils@1.10.2 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 8be23a4: Switched `textextensions` dependency for `text-extensions`. +- 5a737e1: Fix PostgreSQL 18 `TestDatabases` by pinning the data directory +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-defaults@0.14.0 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-app-api@1.4.0 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/cli-common@0.1.16 + +### Patch Changes + +- 5cfb2a4: Added new `run`, `runOutput`, and `runCheck` utilities to help run child processes in a safe and portable way. +- c8c2329: Add proxy configuration from env-vars to create-app tasks +- 2bae83a: Bumped dev dependencies `@types/node` + +## @backstage/cli-node@0.2.16 + +### Patch Changes + +- 4e8c726: Updated to use new utilities from `@backstage/cli-common`. +- Updated dependencies + - @backstage/cli-common@0.1.16 + +## @backstage/codemods@0.1.53 + +### Patch Changes + +- 688f070: Updated to use new utilities from `@backstage/cli-common`. +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/cli-common@0.1.16 + +## @backstage/config-loader@1.10.7 + +### Patch Changes + +- 741c47a: Updated dependency `typescript-json-schema` to `^0.67.0`. +- Updated dependencies + - @backstage/cli-common@0.1.16 + +## @backstage/core-app-api@1.19.3 + +### Patch Changes + +- 75683ed: Added replay functionality to `AlertApiForwarder` to buffer and replay recent alerts to new subscribers, preventing missed alerts that were posted before subscription. +- 97cd16f: Internal update of translation imports. +- Updated dependencies + - @backstage/core-plugin-api@1.12.1 + +## @backstage/core-compat-api@0.5.5 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + +## @backstage/core-components@0.18.4 + +### Patch Changes + +- 9a942a4: Fixed bug in the `LogViewer` component where shift + click always opened a new window instead of just changing the selection. + + In addition, improved the `LogViewer` component by a few usability enhancements: + + - Added support for multiple selections using cmd/ctrl + click + - Improved the generated hash that is added to the URL to also support ranges & multiple selections + - Added an hover effect & info tooltip to the "Copy to clipboard" button to indicate its functionality + - Added some color and a separator to the line numbers to improve readability + +- 207c3c8: long words like urls now breaks to new line on warning panels instead of overflowing the container + +- 4c00303: Add `tooltipClasses` prop to `OverflowTooltip` component to allow customisation of the tooltip + +- 5d52dab: Add i18n support for LogViewer search control + +- f6b49ce: added support for wrapLongLines option in CodeSnippet + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + +## @backstage/core-plugin-api@1.12.1 + +### Patch Changes + +- 358c6f7: The `useApp` and `useRouteRef` functions are now forwards compatible with the new frontend system. Along with the previous route reference changes this means that there is no longer a need to use `compatWrapper` from `@backstage/core-compat-api` to make code based on `@backstage/core-plugin-api` compatible with `@backstage/frontend-plugin-api` APIs. +- 97cd16f: Reversed the relationship between the old `@backstage/core-plugin-api` and the new `@backstage/frontend-plugin-api`. Previously, the a lot of API definitions and utilities where defined in the old and re-exported from the old, but this change flips that around so that they now reside in the new package and are re-exported from the old. The external API of both packages remain the same, but this is a step towards being able to add further compatibility with the new frontend system built into the old. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + +## @backstage/create-app@0.7.7 + +### Patch Changes + +- 336db00: Bumped create-app version. +- 2bae83a: Updated engines to support Node 22 or 24 +- c8c2329: Add proxy configuration from env-vars to create-app tasks +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/cli-common@0.1.16 + +## @backstage/dev-utils@1.1.18 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/ui@0.10.0 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/app-defaults@1.7.3 + - @backstage/integration-react@1.2.13 + +## @backstage/frontend-app-api@0.13.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-plugin-api@1.12.1 + - @backstage/frontend-defaults@0.3.4 + +## @backstage/frontend-defaults@0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app@0.3.3 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/frontend-app-api@0.13.3 + +## @backstage/frontend-dynamic-feature-loader@0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + +## @backstage/frontend-plugin-api@0.13.2 + +### Patch Changes + +- 75683ed: Added a new `errorPresentation` prop to `ExtensionBoundary` to control how errors are presented to the user. The default is `'error-display'`, which is the current behavior of showing the error in the `ErrorDisplay` component. The new option is `'error-api'`, posts errors to the `ErrorApi` and does not allow retries. + + The `AppRootElementBlueprint` now wraps its element in an `ErrorBoundary` using the new `'error-api'` presentation mode. + +- 0bc1ce9: Fixed a versioning conflict that could result in a `.withContext` is not a function error. + +- f3f84f1: Made the return type of `.withOverrides` to be simplified. + +- 97cd16f: Reversed the relationship between the old `@backstage/core-plugin-api` and the new `@backstage/frontend-plugin-api`. Previously, the a lot of API definitions and utilities where defined in the old and re-exported from the old, but this change flips that around so that they now reside in the new package and are re-exported from the old. The external API of both packages remain the same, but this is a step towards being able to add further compatibility with the new frontend system built into the old. + +- 9b8bde4: Removed unnecessary dependencies on `@backstage/core-components`, `@backstage/config`, `@material-ui/core`, and `lodash`. + +## @backstage/frontend-test-utils@0.4.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app@0.3.3 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/test-utils@1.7.14 + - @backstage/frontend-app-api@0.13.3 + +## @backstage/integration-react@1.2.13 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/core-plugin-api@1.12.1 + +## @backstage/repo-tools@0.16.1 + +### Patch Changes + +- 688f070: Updated to use new utilities from `@backstage/cli-common`. + +- 85895f9: Updates OpenAPI generator templates to preserve original property names (like 'group-name', 'user-id') from OpenAPI specs when propertyNaming=original is specified. Previously, these were always converted to camelCase regardless of the propertyNaming setting. + + - Updates modelGeneric.mustache templates in both client and server generators + - Updates modelTaggedUnion.mustache templates in both client and server generators + - Uses {{baseName}} when available, falls back to {{name}} for backward compatibility + - Maintains backward compatibility - no changes when propertyNaming=original is not used + +- 2bae83a: Bump `@microsoft/api-documenter` and `@microsoft/api-extractor` to latest versions. + +- d1e38a7: Properly create workspace in OS temporary directory for `generate-patch` command + +- 2bae83a: Bumped dev dependencies `@types/node` + +- Updated dependencies + - @backstage/cli-node@0.2.16 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/cli-common@0.1.16 + - @backstage/config-loader@1.10.7 + +## @techdocs/cli@1.10.3 + +### Patch Changes + +- 43629b1: Updated to use new utilities from `@backstage/cli-common`. +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/backend-defaults@0.14.0 + - @backstage/plugin-techdocs-node@1.13.10 + - @backstage/cli-common@0.1.16 + +## @backstage/test-utils@1.7.14 + +### Patch Changes + +- 97cd16f: Internal update of translation imports. +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/plugin-permission-react@0.4.39 + +## @backstage/theme@0.7.1 + +### Patch Changes + +- fa06f6b: Added a `themeName` prop to `UnifiedThemeProvider`, enabling Backstage UI `data-theme-name` CSS attribute to be set based on active theme. + +## @backstage/plugin-api-docs@0.13.2 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/plugin-catalog@1.32.1 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/plugin-permission-react@0.4.39 + +## @backstage/plugin-app@0.3.3 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- f7bc228: Support to set `defaultLanguage` and `availableLanguages` for the app language API in the new frontend system +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-permission-react@0.4.39 + +## @backstage/plugin-app-backend@0.5.9 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-app-node@0.1.40 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/config-loader@1.10.7 + +## @backstage/plugin-app-node@0.1.40 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/config-loader@1.10.7 + +## @backstage/plugin-app-visualizer@0.1.26 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/ui@0.10.0 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + +## @backstage/plugin-auth@0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/theme@0.7.1 + +## @backstage/plugin-auth-backend@0.25.7 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-auth0-provider@0.2.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-auth-backend@0.25.7 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.15 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.3.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-google-provider@0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-guest-provider@0.2.15 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.15 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.4.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-auth-backend@0.25.7 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-okta-provider@0.2.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-openshift-provider@0.1.3 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-node@0.6.10 + +### Patch Changes + +- 2389358: remove leading dot in auth cookie cleanup call +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- b35f8b2: Fixed chunked cookie replacing edge case in OAuthCookieManager class where some of the old chunks would not get removed if new chunked cookie would have fewer chunks. +- e9dd634: fix flawed cookie removal logic with chunked tokens +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-react@0.1.22 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + +## @backstage/plugin-bitbucket-cloud-common@0.3.5 + +### Patch Changes + +- 37fba1d: Added support for Bitbucket Cloud OAuth. This introduces an alternative authentication method using a workspace OAuth consumer, alongside App Passwords (deprecated) and API tokens. OAuth does not require a bot or service account and avoids token expiry issues. + + **BREAKING CHANGES** + + - **@backstage/integration** (`src/bitbucketCloud/core.ts`) + + - `getBitbucketCloudRequestOptions` now returns a `Promise` and **must** be awaited. + + - **@backstage/plugin-scaffolder-backend-module-bitbucket-cloud** (`src/actions/helpers.ts`) + - `getBitbucketClient` now returns a `Promise` and **must** be awaited. + - `getAuthorizationHeader` now returns a `Promise` and **must** be awaited. + + **OAuth usage example** + + ```yaml + integrations: + bitbucketCloud: + - clientId: client-id + clientSecret: client-secret + ``` + +- Updated dependencies + - @backstage/integration@1.19.0 + +## @backstage/plugin-catalog@1.32.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- 91f5ed8: Fixed `catalogAboutEntityCard` to filter icon links before calling useProps(), preventing side effects from hooks in filtered-out links +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-search-react@1.10.1 + - @backstage/core-plugin-api@1.12.1 + - @backstage/core-compat-api@0.5.5 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-permission-react@0.4.39 + - @backstage/plugin-scaffolder-common@1.7.4 + - @backstage/plugin-techdocs-react@1.3.6 + +## @backstage/plugin-catalog-backend-module-aws@0.4.18 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-defaults@0.14.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-kubernetes-common@0.9.9 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-azure@0.3.12 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.4 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.6 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.5 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.6 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-kubernetes-common@0.9.9 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-gerrit@0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-gitea@0.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.17 + +### Patch Changes + +- ed5a7a3: Introduce new configuration option to exclude suspended users from GitHub Enterprise instances. + + When it’s set to true, suspended users won’t be returned when querying the organization users for GitHub Enterprise instances. + Note that this option should be used only against GitHub Enterprise instances, the property does not exist in the github.com GraphQL schema, setting it will cause a schema validation error and the syncing of users will fail. + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/plugin-catalog-backend-module-github@0.12.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-gitlab@0.7.6 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-defaults@0.14.0 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-backend-module-gitlab@0.7.6 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.7 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- fb029b6: Updated luxon types +- Updated dependencies + - @backstage/backend-defaults@0.14.0 + - @backstage/plugin-catalog-backend@3.3.0 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-ldap@0.12.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-logs@0.1.17 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.3.0 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-catalog-backend-module-msgraph@0.8.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.17 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-scaffolder-common@1.7.4 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.6.7 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.12 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-graph@0.5.4 + +### Patch Changes + +- a67c144: Fixed zoom out description (replace `&` with &) +- f3f84f1: Minor extension type updates after frontend API bump +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + +## @backstage/plugin-catalog-import@0.13.8 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-permission-react@0.4.39 + +## @backstage/plugin-catalog-node@1.20.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-catalog-react@1.21.4 + +### Patch Changes + +- 6d39141: Fixed an issue where `EntityOwnerPicker` failed to filter options when the input text contained uppercase characters. +- b3c0594: Use a versioned context for `useEntityList`, to better work with mixed `@backstage/plugin-catalog-react` versions. +- c51c901: $contains may have string value in an entity filter. Typescript type and Zod parser were not the same. +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/core-compat-api@0.5.5 + - @backstage/frontend-test-utils@0.4.2 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-permission-react@0.4.39 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.24 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. + +- df4d646: Moved types, API and client to the common package, allowing both frontend and + backend plugins to use the `CatalogUnprocessedEntitiesClient`. + + The following types, clients and interfaces have been deprecated and should be + imported from the `@backstage/plugin-catalog-unprocessed-entities-common` instead: + `CatalogUnprocessedEntitiesApi`, `CatalogUnprocessedEntitiesApiResponse`, `UnprocessedEntity`, + `UnprocessedEntityCache`, `UnprocessedEntityError`, `CatalogUnprocessedEntitiesClient`. + + All those types, clients and interfaces are re-exported temporarily in the + `@backstage/plugin-catalog-unprocessed-entities` package until cleaned up. + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.12 + +## @backstage/plugin-catalog-unprocessed-entities-common@0.0.12 + +### Patch Changes + +- df4d646: Moved types, API and client to the common package, allowing both frontend and + backend plugins to use the `CatalogUnprocessedEntitiesClient`. + + The following types, clients and interfaces have been deprecated and should be + imported from the `@backstage/plugin-catalog-unprocessed-entities-common` instead: + `CatalogUnprocessedEntitiesApi`, `CatalogUnprocessedEntitiesApiResponse`, `UnprocessedEntity`, + `UnprocessedEntityCache`, `UnprocessedEntityError`, `CatalogUnprocessedEntitiesClient`. + + All those types, clients and interfaces are re-exported temporarily in the + `@backstage/plugin-catalog-unprocessed-entities` package until cleaned up. + +## @backstage/plugin-config-schema@0.1.75 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + +## @backstage/plugin-devtools@0.1.34 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- 291bf9d: Added scheduled tasks UI feature for the DevTools plugin +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/plugin-devtools-common@0.1.20 + - @backstage/core-compat-api@0.5.5 + - @backstage/plugin-permission-react@0.4.39 + +## @backstage/plugin-devtools-backend@0.5.12 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 291bf9d: Added scheduled tasks UI feature for the DevTools plugin +- Updated dependencies + - @backstage/backend-defaults@0.14.0 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/cli-common@0.1.16 + - @backstage/config-loader@1.10.7 + - @backstage/plugin-devtools-common@0.1.20 + +## @backstage/plugin-devtools-common@0.1.20 + +### Patch Changes + +- 291bf9d: Added scheduled tasks UI feature for the DevTools plugin + +## @backstage/plugin-events-backend@0.5.9 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.4 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-events-backend-module-aws-sqs@0.4.18 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-events-backend-module-azure@0.2.27 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.27 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-events-backend-module-bitbucket-server@0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-events-backend-module-gerrit@0.2.27 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-events-backend-module-github@0.4.7 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-events-backend-module-gitlab@0.3.8 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-events-backend-module-google-pubsub@0.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-events-backend-test-utils@0.1.51 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + +## @backstage/plugin-events-node@0.4.18 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-gateway-backend@1.1.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 7e860dd: Updated dependency `eventsource` to `^4.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-home@0.8.15 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/plugin-home-react@0.1.33 + - @backstage/theme@0.7.1 + +## @backstage/plugin-home-react@0.1.33 + +### Patch Changes + +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + +## @backstage/plugin-kubernetes@0.12.14 + +### Patch Changes + +- f15d5f1: add missing i18n support for kubernetes and kubernetes-react +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/plugin-kubernetes-react@0.5.14 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-kubernetes-common@0.9.9 + - @backstage/core-plugin-api@1.12.1 + - @backstage/plugin-permission-react@0.4.39 + +## @backstage/plugin-kubernetes-cluster@0.0.32 + +### Patch Changes + +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/plugin-kubernetes-react@0.5.14 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-kubernetes-common@0.9.9 + - @backstage/core-plugin-api@1.12.1 + - @backstage/plugin-permission-react@0.4.39 + +## @backstage/plugin-kubernetes-common@0.9.9 + +### Patch Changes + +- 8fa8d87: Add Kubernetes Plugin Secrets Accordion with masked secret datas + +## @backstage/plugin-kubernetes-react@0.5.14 + +### Patch Changes + +- f966a85: Enabled a pod terminal at GKE +- 8fa8d87: Add Kubernetes Plugin Secrets Accordion with masked secret datas +- f15d5f1: add missing i18n support for kubernetes and kubernetes-react +- Updated dependencies + - @backstage/core-components@0.18.4 + - @backstage/plugin-kubernetes-common@0.9.9 + - @backstage/core-plugin-api@1.12.1 + +## @backstage/plugin-mcp-actions-backend@0.1.6 + +### Patch Changes + +- e83e038: Added `@cfworker/json-schema` as a dependency to this package part of the `@modelcontextprotocol/sdk` bump as it's required in the types +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 79ef471: Clarify error handling in readme and update handleError.ts to include all backstage/errors +- Updated dependencies + - @backstage/backend-defaults@0.14.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-mui-to-bui@0.2.2 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.10.0 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + +## @backstage/plugin-notifications@0.5.12 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. + +- 53347cc: Move long notification descriptions behind `Show more/less` button. + + This improves readability of the notifications list by preventing long descriptions from taking up too much space + or rendering very small scrollable areas. + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/plugin-signals-react@0.0.18 + +## @backstage/plugin-notifications-backend@0.6.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-signals-node@0.1.27 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-notifications-node@0.2.22 + +## @backstage/plugin-notifications-backend-module-email@0.3.17 + +### Patch Changes + +- a5d5b3a: SES config for the notification email processor now supports utilizing an ARN for the SES identity when sending an email after the SES SDK V2 update. + + The `sesConfig.fromArn` will set the `fromEmailAddressIdentityArn` option for the SES `SendEmailCommand`. The `sesConfig.sourceArn` field is removed since no equivalent option is available in the send email command options. Setting `sesConfig.sourceArn` will have no effect and log a warning. Example changes: + + ```diff + notifications: + processors: + email: + transportConfig: + transport: "ses" + region: "us-west-2" + sender: "sender@mycompany.com" + replyTo: "no-reply@mycompany.com" + sesConfig: + - sourceArn: "arn:aws:ses:us-west-2:123456789012:identity/example.com" + fromArn: "arn:aws:ses:us-west-2:123456789012:identity/example.com" + ``` + +- b267aea: Updated dependency `@types/nodemailer` to `^7.0.0`. + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-notifications-node@0.2.22 + +## @backstage/plugin-notifications-node@0.2.22 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-signals-node@0.1.27 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-org@0.6.47 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + +## @backstage/plugin-org-react@0.1.45 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + +## @backstage/plugin-permission-backend@0.7.7 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.15 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-permission-node@0.10.7 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-permission-react@0.4.39 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1 + +## @backstage/plugin-proxy-backend@0.6.9 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-proxy-node@0.1.11 + +## @backstage/plugin-proxy-node@0.1.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.17 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16 + +### Patch Changes + +- 5a6aca2: Improve error message when provided target branch is missing +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.16 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.18 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-defaults@0.14.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-backend-module-github@0.9.3 + +### Patch Changes + +- dab3d3f: Added autocompletion resource `owners` for retrieving GitHub repository owners. +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-notifications-node@0.2.22 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.16 + +### Patch Changes + +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + - @backstage/plugin-scaffolder-node-test-utils@0.3.6 + +## @backstage/plugin-scaffolder-common@1.7.4 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + +## @backstage/plugin-scaffolder-node@0.12.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-common@1.7.4 + +## @backstage/plugin-scaffolder-node-test-utils@0.3.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-test-utils@1.10.2 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-react@1.19.4 + +### Patch Changes + +- fb029b6: Updated luxon types +- 5ca461e: Fixed bug where custom `review.name` values were incorrectly formatted by `startCase`, preserving them exactly as written. +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- 9b38f22: Updated dependency `use-immer` to `^0.11.0`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/plugin-permission-react@0.4.39 + - @backstage/plugin-scaffolder-common@1.7.4 + +## @backstage/plugin-search@1.5.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-search-react@1.10.1 + - @backstage/core-plugin-api@1.12.1 + +## @backstage/plugin-search-backend@2.0.9 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/backend-defaults@0.14.0 + - @backstage/backend-openapi-utils@0.6.4 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-search-backend-module-catalog@0.3.11 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-search-backend-module-elasticsearch@1.7.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-search-backend-module-explore@0.3.10 + +### Patch Changes + +- 9b69262: Updated dependency `@backstage-community/plugin-explore-common` to `^0.9.0`. +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-search-backend-module-pg@0.5.51 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.16 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-search-backend-module-techdocs@0.4.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/plugin-techdocs-node@1.13.10 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-search-react@1.10.1 + +### Patch Changes + +- 8947a4e: Skip the very first empty search when going to the landing page +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + +## @backstage/plugin-signals@0.0.26 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/plugin-signals-react@0.0.18 + +## @backstage/plugin-signals-backend@0.3.11 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/plugin-signals-node@0.1.27 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-signals-node@0.1.27 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-signals-react@0.0.18 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1 + +## @backstage/plugin-techdocs@1.16.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- 5c33821: Fixed schema type for additionalAllowedURIProtocols +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- 592361e: The `techdocs` config is now marked as optional. +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-search-react@1.10.1 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-auth-react@0.1.22 + - @backstage/plugin-techdocs-react@1.3.6 + +## @backstage/plugin-techdocs-backend@2.1.3 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 2bae83a: Corrected `ErrorCallback` type to work with Node 22 types +- 592361e: The `techdocs` config is now marked as optional. +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-defaults@0.14.0 + - @backstage/plugin-techdocs-node@1.13.10 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.31 + +### Patch Changes + +- 8d6709e: Updated tests to match test-utils change +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-techdocs-react@1.3.6 + +## @backstage/plugin-techdocs-node@1.13.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 703f8c0: There was an issue in the uploading of large size files to the AWS S3. We have modified the logic by adding retry along with multipart uploading functionality. +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-techdocs-react@1.3.6 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + +## @backstage/plugin-user-settings@0.8.30 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/plugin-signals-react@0.0.18 + +## @backstage/plugin-user-settings-backend@0.3.9 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-defaults@0.14.0 + - @backstage/plugin-signals-node@0.1.27 + - @backstage/backend-plugin-api@1.6.0 + +## example-app@0.2.116 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/cli@0.35.0 + - @backstage/ui@0.10.0 + - @backstage/plugin-catalog-graph@0.5.4 + - @backstage/plugin-api-docs@0.13.2 + - @backstage/plugin-techdocs@1.16.1 + - @backstage/plugin-catalog@1.32.1 + - @backstage/plugin-search@1.5.1 + - @backstage/plugin-org@0.6.47 + - @backstage/plugin-scaffolder-react@1.19.4 + - @backstage/core-components@0.18.4 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.31 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-kubernetes@0.12.14 + - @backstage/plugin-search-react@1.10.1 + - @backstage/core-plugin-api@1.12.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.24 + - @backstage/plugin-catalog-import@0.13.8 + - @backstage/plugin-notifications@0.5.12 + - @backstage/plugin-user-settings@0.8.30 + - @backstage/plugin-scaffolder@1.35.0 + - @backstage/plugin-devtools@0.1.34 + - @backstage/plugin-home@0.8.15 + - @backstage/theme@0.7.1 + - @backstage/plugin-kubernetes-cluster@0.0.32 + - @backstage/app-defaults@1.7.3 + - @backstage/frontend-app-api@0.13.3 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-auth-react@0.1.22 + - @backstage/plugin-mui-to-bui@0.2.2 + - @backstage/plugin-permission-react@0.4.39 + - @backstage/plugin-signals@0.0.26 + - @backstage/plugin-techdocs-react@1.3.6 + +## example-app-next@0.0.30 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/cli@0.35.0 + - @backstage/ui@0.10.0 + - @backstage/plugin-catalog-graph@0.5.4 + - @backstage/plugin-api-docs@0.13.2 + - @backstage/plugin-techdocs@1.16.1 + - @backstage/plugin-catalog@1.32.1 + - @backstage/plugin-search@1.5.1 + - @backstage/plugin-app@0.3.3 + - @backstage/plugin-org@0.6.47 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/plugin-scaffolder-react@1.19.4 + - @backstage/core-components@0.18.4 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.31 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-kubernetes@0.12.14 + - @backstage/plugin-search-react@1.10.1 + - @backstage/core-plugin-api@1.12.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.24 + - @backstage/plugin-app-visualizer@0.1.26 + - @backstage/plugin-catalog-import@0.13.8 + - @backstage/plugin-notifications@0.5.12 + - @backstage/plugin-user-settings@0.8.30 + - @backstage/plugin-scaffolder@1.35.0 + - @backstage/plugin-home@0.8.15 + - @backstage/theme@0.7.1 + - @backstage/plugin-kubernetes-cluster@0.0.32 + - @backstage/app-defaults@1.7.3 + - @backstage/core-compat-api@0.5.5 + - @backstage/frontend-app-api@0.13.3 + - @backstage/frontend-defaults@0.3.4 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-auth@0.1.3 + - @backstage/plugin-auth-react@0.1.22 + - @backstage/plugin-permission-react@0.4.39 + - @backstage/plugin-signals@0.0.26 + - @backstage/plugin-techdocs-react@1.3.6 + +## app-next-example-plugin@0.0.30 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + +## example-backend@0.0.45 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-mcp-actions-backend@0.1.6 + - @backstage/plugin-scaffolder-backend@3.1.0 + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-defaults@0.14.0 + - @backstage/plugin-app-backend@0.5.9 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.15 + - @backstage/plugin-auth-backend-module-openshift-provider@0.1.3 + - @backstage/plugin-auth-backend@0.25.7 + - @backstage/plugin-catalog-backend@3.3.0 + - @backstage/plugin-devtools-backend@0.5.12 + - @backstage/plugin-events-backend@0.5.9 + - @backstage/plugin-kubernetes-backend@0.21.0 + - @backstage/plugin-notifications-backend@0.6.1 + - @backstage/plugin-permission-backend@0.7.7 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/plugin-proxy-backend@0.6.9 + - @backstage/plugin-search-backend@2.0.9 + - @backstage/plugin-signals-backend@0.3.11 + - @backstage/plugin-techdocs-backend@2.1.3 + - @backstage/plugin-auth-backend-module-github-provider@0.4.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-backend-module-github@0.9.3 + - @backstage/plugin-search-backend-module-explore@0.3.10 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.9 + - @backstage/plugin-catalog-backend-module-openapi@0.2.17 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.7 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.7 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.15 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.17 + - @backstage/plugin-search-backend-module-catalog@0.3.11 + - @backstage/plugin-search-backend-module-elasticsearch@1.7.9 + - @backstage/plugin-search-backend-module-techdocs@0.4.9 + +## e2e-test@0.2.35 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.7.7 + - @backstage/cli-common@0.1.16 + +## @internal/frontend@0.0.16 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + +## @internal/scaffolder@0.0.16 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/plugin-scaffolder-react@1.19.4 + +## techdocs-cli-embedded-app@0.2.115 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/cli@0.35.0 + - @backstage/ui@0.10.0 + - @backstage/plugin-techdocs@1.16.1 + - @backstage/plugin-catalog@1.32.1 + - @backstage/core-components@0.18.4 + - @backstage/test-utils@1.7.14 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/app-defaults@1.7.3 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-techdocs-react@1.3.6 + +## yarn-plugin-backstage@0.0.8 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.16 + +## @internal/plugin-todo-list@1.0.46 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + +## @internal/plugin-todo-list-backend@1.0.46 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 diff --git a/docs/releases/v1.46.0-next.0-changelog.md b/docs/releases/v1.46.0-next.0-changelog.md new file mode 100644 index 0000000000..6947caf67a --- /dev/null +++ b/docs/releases/v1.46.0-next.0-changelog.md @@ -0,0 +1,2269 @@ +# Release v1.46.0-next.0 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.46.0-next.0](https://backstage.github.io/upgrade-helper/?to=1.46.0-next.0) + +## @backstage/backend-app-api@1.4.0-next.0 + +### Minor Changes + +- 5a2d538: Introduced backend startup result tracking and error handling. The `Backend.start()` method now returns a `BackendStartupResult` with detailed success/failure status and timing information for all plugins and modules. When startup fails, a `BackendStartupError` is thrown that includes the complete startup results, making it easier to diagnose which plugins or modules failed. + + This also improves the default error message when backend startup fails, and of course makes it possible to craft your own custom error reporting based on the startup results. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/backend-defaults@0.14.0-next.0 + +### Minor Changes + +- fa43826: Move `better-sqlite3` from dependencies to peer dependencies + +### Patch Changes + +- d9759a1: **BREAKING ALPHA**: The old `instanceMetadataService` has been removed from alpha. Please switch over to using the stable `coreServices.rootInstanceMetadata` and related types instead, available from `@backstage/backend-plugin-api`. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-app-api@1.4.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/cli-node@0.2.16-next.0 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/config-loader@1.10.7-next.0 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-scaffolder-backend@3.1.0-next.0 + +### Minor Changes + +- a4cd405: Add `defaultEnvironment` config to scaffolder to enable more flexible and custom templates. Now it's possible enable access to default parameters and secrets in templates, improving security and reducing complexity. + +### Patch Changes + +- 8f4aded: Fixing OpenAPI definition +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/backend-openapi-utils@0.6.4-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.16-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.17-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.16-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.16-next.0 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.16-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.9.3-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.10.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.5-next.0 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-search-backend-node@1.4.0-next.0 + +### Minor Changes + +- 4d3ddb9: Improving method that search tokenizer breaks apart entity names + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/app-defaults@1.7.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + +## @backstage/backend-dynamic-feature-service@0.7.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/cli-common@0.1.16-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/plugin-events-backend@0.5.9-next.0 + - @backstage/plugin-catalog-backend@3.2.1-next.0 + - @backstage/backend-openapi-utils@0.6.4-next.0 + - @backstage/plugin-app-node@0.1.40-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/cli-node@0.2.16-next.0 + - @backstage/config@1.3.6 + - @backstage/config-loader@1.10.7-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/backend-openapi-utils@0.6.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/backend-plugin-api@1.5.1-next.0 + +### Patch Changes + +- d9759a1: **BREAKING ALPHA**: The old `instanceMetadataService` has been removed from alpha. Please switch over to using the stable `coreServices.rootInstanceMetadata` and related types instead, available from `@backstage/backend-plugin-api`. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/cli-common@0.1.16-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/backend-test-utils@1.10.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-app-api@1.4.0-next.0 + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/cli@0.34.6-next.0 + +### Patch Changes + +- c8c2329: Add proxy configuration from env-vars to create-app tasks +- Updated dependencies + - @backstage/integration@1.18.3-next.0 + - @backstage/cli-common@0.1.16-next.0 + - @backstage/cli-node@0.2.16-next.0 + - @backstage/config@1.3.6 + - @backstage/release-manifests@0.0.13 + - @backstage/config-loader@1.10.7-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/eslint-plugin@0.2.0 + - @backstage/types@1.2.2 + +## @backstage/cli-common@0.1.16-next.0 + +### Patch Changes + +- c8c2329: Add proxy configuration from env-vars to create-app tasks + +## @backstage/cli-node@0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.16-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/codemods@0.1.53-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.16-next.0 + +## @backstage/config-loader@1.10.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.16-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/core-app-api@1.19.3-next.0 + +### Patch Changes + +- 97cd16f: Internal update of translation imports. +- Updated dependencies + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/core-compat-api@0.5.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/core-components@0.18.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/version-bridge@1.0.11 + +## @backstage/core-plugin-api@1.12.1-next.0 + +### Patch Changes + +- 358c6f7: The `useApp` and `useRouteRef` functions are now forwards compatible with the new frontend system. Along with the previous route reference changes this means that there is no longer a need to use `compatWrapper` from `@backstage/core-compat-api` to make code based on `@backstage/core-plugin-api` compatible with `@backstage/frontend-plugin-api` APIs. +- 97cd16f: Reversed the relationship between the old `@backstage/core-plugin-api` and the new `@backstage/frontend-plugin-api`. Previously, the a lot of API definitions and utilities where defined in the old and re-exported from the old, but this change flips that around so that they now reside in the new package and are re-exported from the old. The external API of both packages remain the same, but this is a step towards being able to add further compatibility with the new frontend system built into the old. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/create-app@0.7.7-next.0 + +### Patch Changes + +- c8c2329: Add proxy configuration from env-vars to create-app tasks +- Updated dependencies + - @backstage/cli-common@0.1.16-next.0 + +## @backstage/dev-utils@1.1.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.9.1-next.0 + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/app-defaults@1.7.3-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/catalog-model@1.7.6 + +## @backstage/frontend-app-api@0.13.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/frontend-defaults@0.3.4-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/frontend-defaults@0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/plugin-app@0.3.3-next.0 + - @backstage/frontend-app-api@0.13.3-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/frontend-dynamic-feature-loader@0.1.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/config@1.3.6 + +## @backstage/frontend-plugin-api@0.13.2-next.0 + +### Patch Changes + +- 0bc1ce9: Fixed a versioning conflict that could result in a `.withContext` is not a function error. +- 97cd16f: Reversed the relationship between the old `@backstage/core-plugin-api` and the new `@backstage/frontend-plugin-api`. Previously, the a lot of API definitions and utilities where defined in the old and re-exported from the old, but this change flips that around so that they now reside in the new package and are re-exported from the old. The external API of both packages remain the same, but this is a step towards being able to add further compatibility with the new frontend system built into the old. +- Updated dependencies + - @backstage/core-components@0.18.4-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/frontend-test-utils@0.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/test-utils@1.7.14-next.0 + - @backstage/plugin-app@0.3.3-next.0 + - @backstage/frontend-app-api@0.13.3-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/integration@1.18.3-next.0 + +### Patch Changes + +- e15fdae: Made the github urls case insensitive. +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/integration-react@1.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/config@1.3.6 + +## @backstage/repo-tools@0.16.1-next.0 + +### Patch Changes + +- 85895f9: Updates OpenAPI generator templates to preserve original property names (like 'group-name', 'user-id') from OpenAPI specs when propertyNaming=original is specified. Previously, these were always converted to camelCase regardless of the propertyNaming setting. + + - Updates modelGeneric.mustache templates in both client and server generators + - Updates modelTaggedUnion.mustache templates in both client and server generators + - Uses {{baseName}} when available, falls back to {{name}} for backward compatibility + - Maintains backward compatibility - no changes when propertyNaming=original is not used + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/cli-common@0.1.16-next.0 + - @backstage/cli-node@0.2.16-next.0 + - @backstage/config-loader@1.10.7-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + +## @techdocs/cli@1.10.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/cli-common@0.1.16-next.0 + - @backstage/plugin-techdocs-node@1.13.10-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + +## @backstage/test-utils@1.7.14-next.0 + +### Patch Changes + +- 97cd16f: Internal update of translation imports. +- Updated dependencies + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/theme@0.7.1-next.0 + +### Patch Changes + +- fa06f6b: Added a `themeName` prop to `UnifiedThemeProvider`, enabling Backstage UI `data-theme-name` CSS attribute to be set based on active theme. + +## @backstage/ui@0.9.1-next.0 + +### Patch Changes + +- b3ad928: Fixed Table Row component to correctly handle cases where no `href` is provided, preventing unnecessary router provider wrapping and fixing the cursor incorrectly showing as a pointer despite the element not being a link. + + Affected components: Row + +- fe7c751: Fixed `useTable` hook to prioritize `providedRowCount` over data length for accurate row count in server-side pagination scenarios. + +- c145031: Fixed Table column sorting indicator to show up arrow when no sort is active, correctly indicating that clicking will sort ascending. + + Affected components: Column + +## @backstage/plugin-api-docs@0.13.2-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/plugin-catalog@1.32.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-app@0.3.3-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/plugin-app-backend@0.5.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-app-node@0.1.40-next.0 + - @backstage/config@1.3.6 + - @backstage/config-loader@1.10.7-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-app-node@0.1.40-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/config-loader@1.10.7-next.0 + +## @backstage/plugin-app-visualizer@0.1.26-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/ui@0.9.1-next.0 + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/core-components@0.18.4-next.0 + +## @backstage/plugin-auth@0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend@0.25.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + +## @backstage/plugin-auth-backend-module-auth0-provider@0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-auth-backend@0.25.7-next.0 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + +## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-auth-backend-module-github-provider@0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + +## @backstage/plugin-auth-backend-module-google-provider@0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + +## @backstage/plugin-auth-backend-module-guest-provider@0.2.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.4.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-auth-backend@0.25.7-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-auth-backend-module-okta-provider@0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + +## @backstage/plugin-auth-backend-module-openshift-provider@0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-auth-node@0.6.10-next.0 + +### Patch Changes + +- 2389358: remove leading dot in auth cookie cleanup call +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-auth-react@0.1.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/errors@1.2.7 + +## @backstage/plugin-bitbucket-cloud-common@0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.0 + +## @backstage/plugin-catalog@1.32.1-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/core-compat-api@0.5.5-next.0 + - @backstage/plugin-search-react@1.10.1-next.0 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-catalog-backend@3.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/backend-openapi-utils@0.6.4-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-catalog-backend-module-aws@0.4.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-kubernetes-common@0.9.8 + +## @backstage/plugin-catalog-backend-module-azure@0.3.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/backend-openapi-utils@0.6.4-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.5-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-kubernetes-common@0.9.8 + +## @backstage/plugin-catalog-backend-module-gerrit@0.3.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-gitea@0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-github@0.11.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-backend-module-github@0.11.3-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + +## @backstage/plugin-catalog-backend-module-gitlab@0.7.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-backend-module-gitlab@0.7.6-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-backend@3.2.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-catalog-backend-module-ldap@0.12.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-logs@0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-backend@3.2.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + +## @backstage/plugin-catalog-backend-module-msgraph@0.8.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.6.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.11 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-catalog-graph@0.5.4-next.0 + +### Patch Changes + +- a67c144: Fixed zoom out description (replace `&` with &) +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-catalog-import@0.13.8-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-node@1.20.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-catalog-react@1.21.4-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/core-compat-api@0.5.5-next.0 + - @backstage/frontend-test-utils@0.4.2-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.24-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-config-schema@0.1.75-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-devtools@0.1.34-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-devtools-common@0.1.19 + +## @backstage/plugin-devtools-backend@0.5.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/cli-common@0.1.16-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/config@1.3.6 + - @backstage/config-loader@1.10.7-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-devtools-common@0.1.19 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-events-backend@0.5.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/backend-openapi-utils@0.6.4-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-events-backend-module-aws-sqs@0.4.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-events-backend-module-azure@0.2.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + +## @backstage/plugin-events-backend-module-bitbucket-server@0.1.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + +## @backstage/plugin-events-backend-module-gerrit@0.2.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + +## @backstage/plugin-events-backend-module-github@0.4.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-events-backend-module-gitlab@0.3.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + +## @backstage/plugin-events-backend-module-google-pubsub@0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-events-backend-module-kafka@0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-events-backend-test-utils@0.1.51-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.0 + +## @backstage/plugin-events-node@0.4.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-gateway-backend@1.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + +## @backstage/plugin-home@0.8.15-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/plugin-home-react@0.1.33-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-home-react@0.1.33-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/core-components@0.18.4-next.0 + +## @backstage/plugin-kubernetes@0.12.14-next.0 + +### Patch Changes + +- f15d5f1: add missing i18n support for kubernetes and kubernetes-react +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/plugin-kubernetes-react@0.5.14-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-kubernetes-common@0.9.8 + +## @backstage/plugin-kubernetes-backend@0.20.5-next.0 + +### Patch Changes + +- e9589d9: Replace `@aws-sdk/signature-v4` with `@smithy/signature-v4`, + as stated in the [package documentation](https://www.npmjs.com/package/@aws-sdk/signature-v4?activeTab=readme) +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/plugin-kubernetes-node@0.3.7-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-kubernetes-common@0.9.8 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-kubernetes-cluster@0.0.32-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-react@0.5.14-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-kubernetes-common@0.9.8 + +## @backstage/plugin-kubernetes-node@0.3.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + - @backstage/plugin-kubernetes-common@0.9.8 + +## @backstage/plugin-kubernetes-react@0.5.14-next.0 + +### Patch Changes + +- f15d5f1: add missing i18n support for kubernetes and kubernetes-react +- Updated dependencies + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-kubernetes-common@0.9.8 + +## @backstage/plugin-mcp-actions-backend@0.1.6-next.0 + +### Patch Changes + +- 79ef471: Clarify error handling in readme and update handleError.ts to include all backstage/errors +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-mui-to-bui@0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.9.1-next.0 + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + +## @backstage/plugin-notifications@0.5.12-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-signals-react@0.0.18-next.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-notifications-common@0.2.0 + +## @backstage/plugin-notifications-backend@0.6.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-signals-node@0.1.27-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-notifications-node@0.2.22-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-notifications-common@0.2.0 + +## @backstage/plugin-notifications-backend-module-email@0.3.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-notifications-node@0.2.22-next.0 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + - @backstage/plugin-notifications-common@0.2.0 + +## @backstage/plugin-notifications-backend-module-slack@0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-notifications-node@0.2.22-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-notifications-common@0.2.0 + +## @backstage/plugin-notifications-node@0.2.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-signals-node@0.1.27-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-notifications-common@0.2.0 + +## @backstage/plugin-org@0.6.47-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-org-react@0.1.45-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-permission-backend@0.7.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-permission-node@0.10.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-permission-react@0.4.39-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-proxy-backend@0.6.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-proxy-node@0.1.11-next.0 + - @backstage/types@1.2.2 + +## @backstage/plugin-proxy-node@0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + +## @backstage/plugin-scaffolder@1.34.4-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/plugin-scaffolder-react@1.19.4-next.0 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.16-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.5-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-github@0.9.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.10.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-notifications-node@0.2.22-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/plugin-notifications-common@0.2.0 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/plugin-scaffolder-node-test-utils@0.3.6-next.0 + - @backstage/types@1.2.2 + +## @backstage/plugin-scaffolder-common@1.7.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-scaffolder-node@0.12.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-scaffolder-node-test-utils@0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/backend-test-utils@1.10.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/types@1.2.2 + +## @backstage/plugin-scaffolder-react@1.19.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/plugin-search@1.5.1-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/plugin-search-react@1.10.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend@2.0.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0-next.0 + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/backend-openapi-utils@0.6.4-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-catalog@0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-elasticsearch@1.7.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-explore@0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-pg@0.5.51-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-techdocs@0.4.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-techdocs-node@1.13.10-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-react@1.10.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-signals@0.0.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-signals-react@0.0.18-next.0 + - @backstage/types@1.2.2 + +## @backstage/plugin-signals-backend@0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-signals-node@0.1.27-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-signals-node@0.1.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-signals-react@0.0.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/types@1.2.2 + +## @backstage/plugin-techdocs@1.16.1-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/plugin-search-react@1.10.1-next.0 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-auth-react@0.1.22-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-techdocs-addons-test-utils@1.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/test-utils@1.7.14-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/plugin-techdocs@1.16.1-next.0 + - @backstage/plugin-catalog@1.32.1-next.0 + - @backstage/plugin-search-react@1.10.1-next.0 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/integration-react@1.2.13-next.0 + +## @backstage/plugin-techdocs-backend@2.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-techdocs-node@1.13.10-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/core-components@0.18.4-next.0 + +## @backstage/plugin-techdocs-node@1.13.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-techdocs-react@1.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-user-settings@0.8.30-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-signals-react@0.0.18-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-user-settings-common@0.0.1 + +## @backstage/plugin-user-settings-backend@0.3.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-signals-node@0.1.27-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-user-settings-common@0.0.1 + +## example-app@0.2.116-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-graph@0.5.4-next.0 + - @backstage/ui@0.9.1-next.0 + - @backstage/plugin-kubernetes@0.12.14-next.0 + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.24-next.0 + - @backstage/plugin-catalog-import@0.13.8-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/plugin-notifications@0.5.12-next.0 + - @backstage/plugin-user-settings@0.8.30-next.0 + - @backstage/plugin-scaffolder@1.34.4-next.0 + - @backstage/plugin-api-docs@0.13.2-next.0 + - @backstage/plugin-devtools@0.1.34-next.0 + - @backstage/plugin-techdocs@1.16.1-next.0 + - @backstage/plugin-catalog@1.32.1-next.0 + - @backstage/plugin-search@1.5.1-next.0 + - @backstage/plugin-home@0.8.15-next.0 + - @backstage/plugin-org@0.6.47-next.0 + - @backstage/cli@0.34.6-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/plugin-mui-to-bui@0.2.2-next.0 + - @backstage/frontend-app-api@0.13.3-next.0 + - @backstage/plugin-scaffolder-react@1.19.4-next.0 + - @backstage/plugin-search-react@1.10.1-next.0 + - @backstage/plugin-signals@0.0.26-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.31-next.0 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.32-next.0 + - @backstage/app-defaults@1.7.3-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-auth-react@0.1.22-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-search-common@1.2.21 + +## example-app-next@0.0.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-graph@0.5.4-next.0 + - @backstage/ui@0.9.1-next.0 + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/plugin-kubernetes@0.12.14-next.0 + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.24-next.0 + - @backstage/plugin-app-visualizer@0.1.26-next.0 + - @backstage/plugin-catalog-import@0.13.8-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/plugin-notifications@0.5.12-next.0 + - @backstage/plugin-user-settings@0.8.30-next.0 + - @backstage/plugin-scaffolder@1.34.4-next.0 + - @backstage/plugin-api-docs@0.13.2-next.0 + - @backstage/plugin-techdocs@1.16.1-next.0 + - @backstage/plugin-catalog@1.32.1-next.0 + - @backstage/plugin-search@1.5.1-next.0 + - @backstage/plugin-home@0.8.15-next.0 + - @backstage/plugin-app@0.3.3-next.0 + - @backstage/plugin-org@0.6.47-next.0 + - @backstage/cli@0.34.6-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/core-compat-api@0.5.5-next.0 + - @backstage/frontend-app-api@0.13.3-next.0 + - @backstage/frontend-defaults@0.3.4-next.0 + - @backstage/plugin-auth@0.1.3-next.0 + - @backstage/plugin-scaffolder-react@1.19.4-next.0 + - @backstage/plugin-search-react@1.10.1-next.0 + - @backstage/plugin-signals@0.0.26-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.31-next.0 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.32-next.0 + - @backstage/app-defaults@1.7.3-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-auth-react@0.1.22-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-search-common@1.2.21 + +## app-next-example-plugin@0.0.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-components@0.18.4-next.0 + +## example-backend@0.0.45-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-scaffolder-backend@3.1.0-next.0 + - @backstage/plugin-kubernetes-backend@0.20.5-next.0 + - @backstage/plugin-mcp-actions-backend@0.1.6-next.0 + - @backstage/plugin-search-backend@2.0.9-next.0 + - @backstage/plugin-search-backend-module-catalog@0.3.11-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.7.9-next.0 + - @backstage/plugin-search-backend-module-explore@0.3.10-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.4.9-next.0 + - @backstage/plugin-app-backend@0.5.9-next.0 + - @backstage/plugin-auth-backend@0.25.7-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.3.10-next.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.15-next.0 + - @backstage/plugin-auth-backend-module-openshift-provider@0.1.3-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.7-next.0 + - @backstage/plugin-permission-backend@0.7.7-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.15-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/plugin-events-backend@0.5.9-next.0 + - @backstage/plugin-proxy-backend@0.6.9-next.0 + - @backstage/plugin-catalog-backend@3.2.1-next.0 + - @backstage/plugin-devtools-backend@0.5.12-next.0 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.7-next.0 + - @backstage/plugin-notifications-backend@0.6.1-next.0 + - @backstage/plugin-signals-backend@0.3.11-next.0 + - @backstage/plugin-techdocs-backend@2.1.3-next.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.9-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.2.17-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.9.3-next.0 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.17-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-permission-common@0.9.3 + +## e2e-test@0.2.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.16-next.0 + - @backstage/create-app@0.7.7-next.0 + - @backstage/errors@1.2.7 + +## @internal/frontend@0.0.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @internal/scaffolder@0.0.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/plugin-scaffolder-react@1.19.4-next.0 + +## techdocs-cli-embedded-app@0.2.115-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.9.1-next.0 + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/test-utils@1.7.14-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-techdocs@1.16.1-next.0 + - @backstage/plugin-catalog@1.32.1-next.0 + - @backstage/cli@0.34.6-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/app-defaults@1.7.3-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + +## yarn-plugin-backstage@0.0.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.16-next.0 + - @backstage/release-manifests@0.0.13 + - @backstage/errors@1.2.7 + +## @internal/plugin-todo-list@1.0.46-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/core-components@0.18.4-next.0 + +## @internal/plugin-todo-list-backend@1.0.46-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/errors@1.2.7 diff --git a/docs/releases/v1.46.0-next.1-changelog.md b/docs/releases/v1.46.0-next.1-changelog.md new file mode 100644 index 0000000000..42a188ec7f --- /dev/null +++ b/docs/releases/v1.46.0-next.1-changelog.md @@ -0,0 +1,392 @@ +# Release v1.46.0-next.1 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.46.0-next.1](https://backstage.github.io/upgrade-helper/?to=1.46.0-next.1) + +## @backstage/plugin-techdocs-addons-test-utils@2.0.0-next.1 + +### Major Changes + +- 8d6709e: **BREAKING**: `TechDocsAddonTester.renderWithEffects()` no longer returns a screen; this means that you can no longer grab assertions such as `getByText` from its return value. + + Newer versions of `@testing-library` recommends using the `screen` export for assertions - and removing this from the addon tester contract allows us to more freely iterate on which underlying version of the testing library is being used. + + One notable effect of this, however, is that the `@testing-library` `screen` does NOT support assertions on the shadow DOM, which techdocs relies on. You will therefore want to add a dependency on [the `shadow-dom-testing-library` package](https://github.com/konnorrogers/shadow-dom-testing-library/) in your tests, and using its `screen` and its dedicated `*Shadow*` methods. As an example, if you keep doing `getByText` you will not get matches inside the shadow DOM - switch to `getByShadowText` instead. + + ```ts + import { screen } from 'shadow-dom-testing-library'; + + // ... render the addon ... + await TechDocsAddonTester.buildAddonsInTechDocs([]) + .withDom(TEST_CONTENT) + .renderWithEffects(); + + expect(screen.getByShadowText('TEST_CONTENT')).toBeInTheDocument(); + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.4-next.1 + - @backstage/plugin-techdocs@1.16.1-next.1 + +## @backstage/ui@0.10.0-next.1 + +### Minor Changes + +- 16543fa: **Breaking change** The `Cell` component has been refactored to be a generic wrapper component that accepts `children` for custom cell content. The text-specific functionality (previously part of `Cell`) has been moved to a new `CellText` component. + + ### Migration Guide + + If you were using `Cell` with text-specific props (`title`, `description`, `leadingIcon`, `href`), you need to update your code to use `CellText` instead: + + **Before:** + + ```tsx + } + href="/path" + /> + ``` + + **After:** + + ```tsx + } + href="/path" + /> + ``` + + For custom cell content, use the new generic `Cell` component: + + ```tsx + {/* Your custom content */} + ``` + +### Patch Changes + +- 50b7927: Fixed Checkbox indicator showing checkmark color when unchecked. + + Affected components: Checkbox + +- 5bacf55: Fixed `ButtonIcon` incorrectly applying `className` to inner elements instead of only the root element. + + Affected components: ButtonIcon + +- a20d317: Added row selection support with visual state styling for hover, selected, and pressed states. Fixed checkbox rendering to only show for multi-select toggle mode. + + Affected components: Table, TableHeader, Row, Column + +## @backstage/cli@0.34.6-next.1 + +### Patch Changes + +- 7fbac5c: Updated to use new utilities from `@backstage/cli-common`. +- Updated dependencies + - @backstage/cli-node@0.2.16-next.1 + - @backstage/cli-common@0.1.16-next.1 + +## @backstage/cli-common@0.1.16-next.1 + +### Patch Changes + +- 5cfb2a4: Added new `run`, `runOutput`, and `runCheck` utilities to help run child processes in a safe and portable way. + +## @backstage/cli-node@0.2.16-next.1 + +### Patch Changes + +- 4e8c726: Updated to use new utilities from `@backstage/cli-common`. +- Updated dependencies + - @backstage/cli-common@0.1.16-next.1 + +## @backstage/codemods@0.1.53-next.1 + +### Patch Changes + +- 688f070: Updated to use new utilities from `@backstage/cli-common`. +- Updated dependencies + - @backstage/cli-common@0.1.16-next.1 + +## @backstage/core-components@0.18.4-next.1 + +### Patch Changes + +- 9a942a4: Fixed bug in the `LogViewer` component where shift + click always opened a new window instead of just changing the selection. + + In addition, improved the `LogViewer` component by a few usability enhancements: + + - Added support for multiple selections using cmd/ctrl + click + - Improved the generated hash that is added to the URL to also support ranges & multiple selections + - Added an hover effect & info tooltip to the "Copy to clipboard" button to indicate its functionality + - Added some color and a separator to the line numbers to improve readability + +- 207c3c8: long words like urls now breaks to new line on warning panels instead of overflowing the container + +- 5d52dab: Add i18n support for LogViewer search control + +## @backstage/create-app@0.7.7-next.1 + +### Patch Changes + +- Bumped create-app version. +- Updated dependencies + - @backstage/cli-common@0.1.16-next.1 + +## @backstage/dev-utils@1.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.10.0-next.1 + - @backstage/core-components@0.18.4-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.1 + +## @backstage/repo-tools@0.16.1-next.1 + +### Patch Changes + +- 688f070: Updated to use new utilities from `@backstage/cli-common`. +- d1e38a7: Properly create workspace in OS temporary directory for `generate-patch` command +- Updated dependencies + - @backstage/cli-node@0.2.16-next.1 + - @backstage/cli-common@0.1.16-next.1 + +## @techdocs/cli@1.10.3-next.1 + +### Patch Changes + +- 43629b1: Updated to use new utilities from `@backstage/cli-common`. +- Updated dependencies + - @backstage/cli-common@0.1.16-next.1 + +## @backstage/plugin-app-visualizer@0.1.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.10.0-next.1 + - @backstage/core-components@0.18.4-next.1 + +## @backstage/plugin-catalog-backend-module-aws@0.4.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + +## @backstage/plugin-catalog-backend-module-github@0.11.3-next.1 + +### Patch Changes + +- ed5a7a3: Introduce new configuration option to exclude suspended users from GitHub Enterprise instances. + + When it’s set to true, suspended users won’t be returned when querying the organization users for GitHub Enterprise instances. + Note that this option should be used only against GitHub Enterprise instances, the property does not exist in the github.com GraphQL schema, setting it will cause a schema validation error and the syncing of users will fail. + +## @backstage/plugin-catalog-backend-module-github-org@0.3.17-next.1 + +### Patch Changes + +- ed5a7a3: Introduce new configuration option to exclude suspended users from GitHub Enterprise instances. + + When it’s set to true, suspended users won’t be returned when querying the organization users for GitHub Enterprise instances. + Note that this option should be used only against GitHub Enterprise instances, the property does not exist in the github.com GraphQL schema, setting it will cause a schema validation error and the syncing of users will fail. + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-github@0.11.3-next.1 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.6.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.12-next.0 + +## @backstage/plugin-catalog-react@1.21.4-next.1 + +### Patch Changes + +- 6d39141: Fixed an issue where `EntityOwnerPicker` failed to filter options when the input text contained uppercase characters. +- Updated dependencies + - @backstage/core-components@0.18.4-next.1 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.24-next.1 + +### Patch Changes + +- df4d646: Moved types, API and client to the common package, allowing both frontend and + backend plugins to use the `CatalogUnprocessedEntitiesClient`. + + The following types, clients and interfaces have been deprecated and should be + imported from the `@backstage/plugin-catalog-unprocessed-entities-common` instead: + `CatalogUnprocessedEntitiesApi`, `CatalogUnprocessedEntitiesApiResponse`, `UnprocessedEntity`, + `UnprocessedEntityCache`, `UnprocessedEntityError`, `CatalogUnprocessedEntitiesClient`. + + All those types, clients and interfaces are re-exported temporarily in the + `@backstage/plugin-catalog-unprocessed-entities` package until cleaned up. + +- Updated dependencies + - @backstage/core-components@0.18.4-next.1 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.12-next.0 + +## @backstage/plugin-catalog-unprocessed-entities-common@0.0.12-next.0 + +### Patch Changes + +- df4d646: Moved types, API and client to the common package, allowing both frontend and + backend plugins to use the `CatalogUnprocessedEntitiesClient`. + + The following types, clients and interfaces have been deprecated and should be + imported from the `@backstage/plugin-catalog-unprocessed-entities-common` instead: + `CatalogUnprocessedEntitiesApi`, `CatalogUnprocessedEntitiesApiResponse`, `UnprocessedEntity`, + `UnprocessedEntityCache`, `UnprocessedEntityError`, `CatalogUnprocessedEntitiesClient`. + + All those types, clients and interfaces are re-exported temporarily in the + `@backstage/plugin-catalog-unprocessed-entities` package until cleaned up. + +## @backstage/plugin-kubernetes@0.12.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-react@0.5.14-next.1 + - @backstage/core-components@0.18.4-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.1 + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + +## @backstage/plugin-kubernetes-backend@0.20.5-next.1 + +### Patch Changes + +- 8fa8d87: Add Kubernetes Plugin Secrets Accordion with masked secret datas +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + - @backstage/plugin-kubernetes-node@0.3.7-next.1 + +## @backstage/plugin-kubernetes-cluster@0.0.32-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-react@0.5.14-next.1 + - @backstage/core-components@0.18.4-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.1 + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + +## @backstage/plugin-kubernetes-common@0.9.9-next.0 + +### Patch Changes + +- 8fa8d87: Add Kubernetes Plugin Secrets Accordion with masked secret datas + +## @backstage/plugin-kubernetes-node@0.3.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + +## @backstage/plugin-kubernetes-react@0.5.14-next.1 + +### Patch Changes + +- f966a85: Enabled a pod terminal at GKE +- 8fa8d87: Add Kubernetes Plugin Secrets Accordion with masked secret datas +- Updated dependencies + - @backstage/core-components@0.18.4-next.1 + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + +## @backstage/plugin-mui-to-bui@0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.10.0-next.1 + +## @backstage/plugin-scaffolder-react@1.19.4-next.1 + +### Patch Changes + +- 5ca461e: Fixed bug where custom `review.name` values were incorrectly formatted by `startCase`, preserving them exactly as written. +- Updated dependencies + - @backstage/core-components@0.18.4-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.1 + +## @backstage/plugin-techdocs@1.16.1-next.1 + +### Patch Changes + +- 592361e: The `techdocs` config is now marked as optional. +- Updated dependencies + - @backstage/core-components@0.18.4-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.1 + +## @backstage/plugin-techdocs-backend@2.1.3-next.1 + +### Patch Changes + +- 592361e: The `techdocs` config is now marked as optional. + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.31-next.1 + +### Patch Changes + +- 8d6709e: Updated tests to match test-utils change +- Updated dependencies + - @backstage/core-components@0.18.4-next.1 + +## example-app@0.2.116-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.10.0-next.1 + - @backstage/core-components@0.18.4-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.31-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.1 + - @backstage/plugin-scaffolder-react@1.19.4-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.24-next.1 + - @backstage/cli@0.34.6-next.1 + - @backstage/plugin-techdocs@1.16.1-next.1 + - @backstage/plugin-mui-to-bui@0.2.2-next.1 + - @backstage/plugin-kubernetes@0.12.14-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.32-next.1 + +## example-app-next@0.0.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.10.0-next.1 + - @backstage/core-components@0.18.4-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.31-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.1 + - @backstage/plugin-scaffolder-react@1.19.4-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.24-next.1 + - @backstage/cli@0.34.6-next.1 + - @backstage/plugin-techdocs@1.16.1-next.1 + - @backstage/plugin-app-visualizer@0.1.26-next.1 + - @backstage/plugin-kubernetes@0.12.14-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.32-next.1 + +## techdocs-cli-embedded-app@0.2.115-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.10.0-next.1 + - @backstage/core-components@0.18.4-next.1 + - @backstage/cli@0.34.6-next.1 + - @backstage/plugin-techdocs@1.16.1-next.1 diff --git a/docs/releases/v1.46.0-next.2-changelog.md b/docs/releases/v1.46.0-next.2-changelog.md new file mode 100644 index 0000000000..0bb2bf687d --- /dev/null +++ b/docs/releases/v1.46.0-next.2-changelog.md @@ -0,0 +1,1897 @@ +# Release v1.46.0-next.2 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.46.0-next.2](https://backstage.github.io/upgrade-helper/?to=1.46.0-next.2) + +## @backstage/backend-plugin-api@1.6.0-next.1 + +### Minor Changes + +- 2a0c4b0: Adds a new experimental `RootSystemMetadataService` for tracking the collection of Backstage instances that may be deployed at any one time. It currently offers a single API, `getInstalledPlugins` that returns a list of installed plugins based on config you have set up in `discovery.endpoints` as well as the plugins installed on the instance you're calling the API with. It does not handle wildcard values or fallback values. The intention is for this plugin to provide plugin authors with a simple interface to fetch a trustworthy list of all installed plugins. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/cli-common@0.1.16-next.2 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/cli@0.35.0-next.2 + +### Minor Changes + +- f8dff94: Switched the default module resolution to `bundler` and the `module` setting to `ES2020`. + + You may need to bump some dependencies as part of this change and fix imports in code. The most common source of this is that type checking will now consider the `exports` field in `package.json` when resolving imports. This in turn can break older versions of packages that had incompatible `exports` fields. Generally these issues will have already been fixed in the upstream packages. + + You might be tempted to use `--skipLibCheck` to hide issues due to this change, but it will weaken the type safety of your project. If you run into a large number of issues and want to keep the old behavior, you can reset the `moduleResolution` and `module` settings your own `tsconfig.json` file to `node` and `ESNext` respectively. But keep in mind that the `node` option will be removed in future versions of TypeScript. + + A future version of Backstage will make these new settings mandatory, as we move to rely on the `exports` field for type resolution in packages, rather than the `typesVersions` field. + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 1226647: Updated dependency `esbuild` to `^0.27.0`. +- f89a074: Updated dependency `@pmmmwh/react-refresh-webpack-plugin` to `^0.6.0`. +- 2b81751: Updated dependency `webpack` to `~5.103.0`. +- fafd9e1: Fixed internal usage of `yargs`. +- 2bae83a: Switched ECMAScript version to ES2023. +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/config-loader@1.10.7-next.1 + - @backstage/cli-common@0.1.16-next.2 + - @backstage/catalog-model@1.7.6 + - @backstage/cli-node@0.2.16-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/eslint-plugin@0.2.0 + - @backstage/release-manifests@0.0.13 + - @backstage/types@1.2.2 + +## @backstage/plugin-kubernetes-backend@0.21.0-next.2 + +### Minor Changes + +- 7f9846f: Add possibility to extends Kubernetes REST API. Add fetcher to parameters for custom objects provider + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- fb029b6: Updated luxon types +- Updated dependencies + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/plugin-kubernetes-node@0.4.0-next.2 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.19 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-kubernetes-node@0.4.0-next.2 + +### Minor Changes + +- 7f9846f: Add possibility to extends Kubernetes REST API. Add fetcher to parameters for custom objects provider + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.11.0-next.1 + +### Minor Changes + +- f2d034b: In the `gitlabRepoPush` action, add 'auto' possibility for `commitAction` input. + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/backend-app-api@1.4.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/backend-defaults@0.14.0-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- aa79251: build(deps): bump `node-forge` from 1.3.1 to 1.3.2 +- f96edff: Allow configuration of the `referrerPolicy` +- fb029b6: Updated luxon types +- 847a330: Fix for `jose` types +- 25b560e: Internal change to support new versions of the `logform` library +- 2a0c4b0: Adds a new experimental `RootSystemMetadataService` for tracking the collection of Backstage instances that may be deployed at any one time. It currently offers a single API, `getInstalledPlugins` that returns a list of installed plugins based on config you have set up in `discovery.endpoints` as well as the plugins installed on the instance you're calling the API with. It does not handle wildcard values or fallback values. The intention is for this plugin to provide plugin authors with a simple interface to fetch a trustworthy list of all installed plugins. +- 3016a79: Updated dependency `@types/archiver` to `^7.0.0`. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config-loader@1.10.7-next.1 + - @backstage/backend-dev-utils@0.1.6-next.0 + - @backstage/backend-app-api@1.4.0-next.1 + - @backstage/cli-node@0.2.16-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.19 + - @backstage/types@1.2.2 + +## @backstage/backend-dev-utils@0.1.6-next.0 + +### Patch Changes + +- 2bae83a: Internal update for Node.js v24 support. + +## @backstage/backend-dynamic-feature-service@0.7.7-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/backend-openapi-utils@0.6.4-next.1 + - @backstage/plugin-app-node@0.1.40-next.1 + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-catalog-backend@3.2.1-next.1 + - @backstage/plugin-events-backend@0.5.9-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config-loader@1.10.7-next.1 + - @backstage/cli-common@0.1.16-next.2 + - @backstage/cli-node@0.2.16-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + - @backstage/plugin-search-backend-node@1.4.0-next.1 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/backend-openapi-utils@0.6.4-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/backend-test-utils@1.10.2-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 8be23a4: Switched `textextensions` dependency for `text-extensions`. +- 5a737e1: Fix PostgreSQL 18 `TestDatabases` by pinning the data directory +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/backend-app-api@1.4.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/cli-common@0.1.16-next.2 + +### Patch Changes + +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/errors@1.2.7 + +## @backstage/codemods@0.1.53-next.2 + +### Patch Changes + +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/cli-common@0.1.16-next.2 + +## @backstage/config-loader@1.10.7-next.1 + +### Patch Changes + +- 741c47a: Updated dependency `typescript-json-schema` to `^0.67.0`. +- Updated dependencies + - @backstage/cli-common@0.1.16-next.2 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/core-app-api@1.19.3-next.1 + +### Patch Changes + +- 75683ed: Added replay functionality to `AlertApiForwarder` to buffer and replay recent alerts to new subscribers, preventing missed alerts that were posted before subscription. +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/core-components@0.18.4-next.2 + +### Patch Changes + +- 4c00303: Add `tooltipClasses` prop to `OverflowTooltip` component to allow customisation of the tooltip +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/theme@0.7.1-next.0 + - @backstage/version-bridge@1.0.11 + +## @backstage/create-app@0.7.7-next.2 + +### Patch Changes + +- 2bae83a: Updated engines to support Node 22 or 24 +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/cli-common@0.1.16-next.2 + +## @backstage/frontend-plugin-api@0.13.2-next.1 + +### Patch Changes + +- 75683ed: Added a new `errorPresentation` prop to `ExtensionBoundary` to control how errors are presented to the user. The default is `'error-display'`, which is the current behavior of showing the error in the `ErrorDisplay` component. The new option is `'error-api'`, posts errors to the `ErrorApi` and does not allow retries. + + The `AppRootElementBlueprint` now wraps its element in an `ErrorBoundary` using the new `'error-api'` presentation mode. + +- f3f84f1: Made the return type of `.withOverrides` to be simplified. + +- Updated dependencies + - @backstage/core-components@0.18.4-next.2 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/integration@1.18.3-next.1 + +### Patch Changes + +- fb029b6: Updated luxon types +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/repo-tools@0.16.1-next.2 + +### Patch Changes + +- 2bae83a: Bump `@microsoft/api-documenter` and `@microsoft/api-extractor` to latest versions. +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config-loader@1.10.7-next.1 + - @backstage/cli-common@0.1.16-next.2 + - @backstage/catalog-model@1.7.6 + - @backstage/cli-node@0.2.16-next.1 + - @backstage/errors@1.2.7 + +## @techdocs/cli@1.10.3-next.2 + +### Patch Changes + +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/plugin-techdocs-node@1.13.10-next.1 + - @backstage/cli-common@0.1.16-next.2 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + +## @backstage/plugin-api-docs@0.13.2-next.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- Updated dependencies + - @backstage/plugin-catalog@1.32.1-next.1 + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-model@1.7.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-permission-react@0.4.39-next.0 + +## @backstage/plugin-app@0.3.3-next.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- f7bc228: Support to set `defaultLanguage` and `availableLanguages` for the app language API in the new frontend system +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/core-components@0.18.4-next.2 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-permission-react@0.4.39-next.0 + +## @backstage/plugin-app-backend@0.5.9-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-app-node@0.1.40-next.1 + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config-loader@1.10.7-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-app-node@0.1.40-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config-loader@1.10.7-next.1 + +## @backstage/plugin-auth-backend@0.25.7-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-auth-backend-module-auth0-provider@0.2.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.7-next.1 + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.15-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-auth-backend-module-github-provider@0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.3.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-auth-backend-module-google-provider@0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-auth-backend-module-guest-provider@0.2.15-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.4.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.7-next.1 + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-auth-backend-module-okta-provider@0.2.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-auth-backend-module-openshift-provider@0.1.3-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-auth-node@0.6.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- e9dd634: fix flawed cookie removal logic with chunked tokens +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-catalog@1.32.1-next.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- 91f5ed8: Fixed `catalogAboutEntityCard` to filter icon links before calling useProps(), preventing side effects from hooks in filtered-out links +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-compat-api@0.5.5-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-search-react@1.10.1-next.0 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + +## @backstage/plugin-catalog-backend@3.2.1-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.4-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-catalog-backend-module-aws@0.4.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.19 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + +## @backstage/plugin-catalog-backend-module-azure@0.3.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.4-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-bitbucket-cloud-common@0.3.5-next.0 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.15-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + +## @backstage/plugin-catalog-backend-module-gerrit@0.3.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-catalog-backend-module-gitea@0.1.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-catalog-backend-module-github@0.11.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-backend-module-github@0.11.3-next.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab@0.7.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/plugin-catalog-backend-module-gitlab@0.7.6-next.1 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.7-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- fb029b6: Updated luxon types +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/plugin-catalog-backend@3.2.1-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-catalog-backend-module-ldap@0.12.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-catalog-backend-module-logs@0.1.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.2.1-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-catalog-backend-module-msgraph@0.8.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.6.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.12-next.0 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-catalog-graph@0.5.4-next.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/types@1.2.2 + +## @backstage/plugin-catalog-node@1.20.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-catalog-react@1.21.4-next.2 + +### Patch Changes + +- b3c0594: Use a versioned context for `useEntityList`, to better work with mixed `@backstage/plugin-catalog-react` versions. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-compat-api@0.5.5-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/frontend-test-utils@0.4.2-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-permission-react@0.4.39-next.0 + +## @backstage/plugin-devtools-backend@0.5.12-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config-loader@1.10.7-next.1 + - @backstage/cli-common@0.1.16-next.2 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-devtools-common@0.1.19 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-events-backend@0.5.9-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.4-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-events-backend-module-aws-sqs@0.4.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-events-backend-module-azure@0.2.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-events-backend-module-bitbucket-server@0.1.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-events-backend-module-gerrit@0.2.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-events-backend-module-github@0.4.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-events-backend-module-gitlab@0.3.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + +## @backstage/plugin-events-backend-module-google-pubsub@0.1.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-events-backend-module-kafka@0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-events-node@0.4.18-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-gateway-backend@1.1.1-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-home@0.8.15-next.1 + +### Patch Changes + +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- Updated dependencies + - @backstage/core-app-api@1.19.3-next.1 + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/plugin-home-react@0.1.33-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + +## @backstage/plugin-home-react@0.1.33-next.1 + +### Patch Changes + +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/core-components@0.18.4-next.2 + - @backstage/core-plugin-api@1.12.1-next.0 + +## @backstage/plugin-kubernetes-cluster@0.0.32-next.2 + +### Patch Changes + +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-model@1.7.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + - @backstage/plugin-kubernetes-react@0.5.14-next.1 + - @backstage/plugin-permission-react@0.4.39-next.0 + +## @backstage/plugin-mcp-actions-backend@0.1.6-next.1 + +### Patch Changes + +- e83e038: Added `@cfworker/json-schema` as a dependency to this package part of the `@modelcontextprotocol/sdk` bump as it's required in the types +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-notifications-backend@0.6.1-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-signals-node@0.1.27-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/plugin-notifications-node@0.2.22-next.1 + +## @backstage/plugin-notifications-backend-module-email@0.3.17-next.1 + +### Patch Changes + +- b267aea: Updated dependency `@types/nodemailer` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/plugin-notifications-node@0.2.22-next.1 + +## @backstage/plugin-notifications-backend-module-slack@0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/plugin-notifications-node@0.2.22-next.1 + +## @backstage/plugin-notifications-node@0.2.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-signals-node@0.1.27-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-notifications-common@0.2.0 + +## @backstage/plugin-org@0.6.47-next.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-model@1.7.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-permission-backend@0.7.7-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-permission-node@0.10.7-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-proxy-backend@0.6.9-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/types@1.2.2 + - @backstage/plugin-proxy-node@0.1.11-next.1 + +## @backstage/plugin-proxy-node@0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-scaffolder@1.34.4-next.1 + +### Patch Changes + +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-scaffolder-react@1.19.4-next.2 + - @backstage/integration@1.18.3-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + +## @backstage/plugin-scaffolder-backend@3.1.0-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 2bae83a: Updated `isolated-vm` to `6.0.1` +- 25b560e: Internal change to support new versions of the `logform` library +- 1226647: Updated dependency `esbuild` to `^0.27.0`. +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-gitlab@0.11.0-next.1 + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/backend-openapi-utils@0.6.4-next.1 + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-bitbucket-cloud-common@0.3.5-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15-next.1 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.16-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.17-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.16-next.1 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.16-next.1 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.16-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.9.3-next.1 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.16-next.1 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-bitbucket-cloud-common@0.3.5-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16-next.1 + +### Patch Changes + +- 5a6aca2: Improve error message when provided target branch is missing +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/plugin-scaffolder-backend-module-github@0.9.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/plugin-notifications-node@0.2.22-next.1 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.16-next.1 + +### Patch Changes + +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + - @backstage/plugin-scaffolder-node-test-utils@0.3.6-next.1 + +## @backstage/plugin-scaffolder-node@0.12.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + +## @backstage/plugin-scaffolder-node-test-utils@0.3.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-test-utils@1.10.2-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/plugin-scaffolder-react@1.19.4-next.2 + +### Patch Changes + +- fb029b6: Updated luxon types +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- 9b38f22: Updated dependency `use-immer` to `^0.11.0`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + +## @backstage/plugin-search@1.5.1-next.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-search-react@1.10.1-next.0 + +## @backstage/plugin-search-backend@2.0.9-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/backend-openapi-utils@0.6.4-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-backend-node@1.4.0-next.1 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-catalog@0.3.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-backend-node@1.4.0-next.1 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-elasticsearch@1.7.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/plugin-search-backend-node@1.4.0-next.1 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-explore@0.3.10-next.1 + +### Patch Changes + +- 9b69262: Updated dependency `@backstage-community/plugin-explore-common` to `^0.9.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/plugin-search-backend-node@1.4.0-next.1 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-pg@0.5.51-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/plugin-search-backend-node@1.4.0-next.1 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/plugin-search-backend-node@1.4.0-next.1 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-techdocs@0.4.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-node@1.13.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-backend-node@1.4.0-next.1 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-node@1.4.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-signals-backend@0.3.11-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/plugin-signals-node@0.1.27-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-signals-node@0.1.27-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-techdocs@1.16.1-next.2 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/plugin-auth-react@0.1.22-next.0 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-search-react@1.10.1-next.0 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + +## @backstage/plugin-techdocs-backend@2.1.3-next.2 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 2bae83a: Corrected `ErrorCallback` type to work with Node 22 types +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/plugin-techdocs-node@1.13.10-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-techdocs-node@1.13.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 703f8c0: There was an issue in the uploading of large size files to the AWS S3. We have modified the logic by adding retry along with multipart uploading functionality. +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.19 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-user-settings-backend@0.3.9-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-signals-node@0.1.27-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-user-settings-common@0.0.1 + +## example-app@0.2.116-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3-next.1 + - @backstage/cli@0.35.0-next.2 + - @backstage/plugin-catalog-graph@0.5.4-next.1 + - @backstage/plugin-api-docs@0.13.2-next.1 + - @backstage/plugin-techdocs@1.16.1-next.2 + - @backstage/plugin-catalog@1.32.1-next.1 + - @backstage/plugin-search@1.5.1-next.1 + - @backstage/plugin-org@0.6.47-next.1 + - @backstage/plugin-scaffolder-react@1.19.4-next.2 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/plugin-home@0.8.15-next.1 + - @backstage/plugin-scaffolder@1.34.4-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.32-next.2 + - @backstage/app-defaults@1.7.3-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/frontend-app-api@0.13.3-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/ui@0.10.0-next.1 + - @backstage/plugin-auth-react@0.1.22-next.0 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-import@0.13.8-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.24-next.1 + - @backstage/plugin-devtools@0.1.34-next.0 + - @backstage/plugin-kubernetes@0.12.14-next.1 + - @backstage/plugin-mui-to-bui@0.2.2-next.1 + - @backstage/plugin-notifications@0.5.12-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-search-react@1.10.1-next.0 + - @backstage/plugin-signals@0.0.26-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.31-next.1 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/plugin-user-settings@0.8.30-next.0 + +## example-app-next@0.0.30-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3-next.1 + - @backstage/cli@0.35.0-next.2 + - @backstage/plugin-catalog-graph@0.5.4-next.1 + - @backstage/plugin-api-docs@0.13.2-next.1 + - @backstage/plugin-techdocs@1.16.1-next.2 + - @backstage/plugin-catalog@1.32.1-next.1 + - @backstage/plugin-search@1.5.1-next.1 + - @backstage/plugin-app@0.3.3-next.1 + - @backstage/plugin-org@0.6.47-next.1 + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-scaffolder-react@1.19.4-next.2 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/plugin-home@0.8.15-next.1 + - @backstage/plugin-scaffolder@1.34.4-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.32-next.2 + - @backstage/app-defaults@1.7.3-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-compat-api@0.5.5-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/frontend-app-api@0.13.3-next.0 + - @backstage/frontend-defaults@0.3.4-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/ui@0.10.0-next.1 + - @backstage/plugin-app-visualizer@0.1.26-next.1 + - @backstage/plugin-auth@0.1.3-next.0 + - @backstage/plugin-auth-react@0.1.22-next.0 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-import@0.13.8-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.24-next.1 + - @backstage/plugin-kubernetes@0.12.14-next.1 + - @backstage/plugin-notifications@0.5.12-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-search-react@1.10.1-next.0 + - @backstage/plugin-signals@0.0.26-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.31-next.1 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/plugin-user-settings@0.8.30-next.0 + +## example-backend@0.0.45-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-mcp-actions-backend@0.1.6-next.1 + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/plugin-app-backend@0.5.9-next.1 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.15-next.1 + - @backstage/plugin-auth-backend-module-openshift-provider@0.1.3-next.1 + - @backstage/plugin-auth-backend@0.25.7-next.1 + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-catalog-backend@3.2.1-next.1 + - @backstage/plugin-devtools-backend@0.5.12-next.1 + - @backstage/plugin-events-backend@0.5.9-next.1 + - @backstage/plugin-kubernetes-backend@0.21.0-next.2 + - @backstage/plugin-notifications-backend@0.6.1-next.1 + - @backstage/plugin-permission-backend@0.7.7-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/plugin-proxy-backend@0.6.9-next.1 + - @backstage/plugin-scaffolder-backend@3.1.0-next.1 + - @backstage/plugin-search-backend@2.0.9-next.1 + - @backstage/plugin-signals-backend@0.3.11-next.1 + - @backstage/plugin-techdocs-backend@2.1.3-next.2 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/plugin-search-backend-module-explore@0.3.10-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-auth-backend-module-github-provider@0.3.10-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.9-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.2.17-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.7-next.2 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.7-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.15-next.1 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-scaffolder-backend-module-github@0.9.3-next.1 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.17-next.1 + - @backstage/plugin-search-backend-module-catalog@0.3.11-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.7.9-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.4.9-next.1 + - @backstage/plugin-search-backend-node@1.4.0-next.1 + +## techdocs-cli-embedded-app@0.2.115-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3-next.1 + - @backstage/cli@0.35.0-next.2 + - @backstage/plugin-techdocs@1.16.1-next.2 + - @backstage/plugin-catalog@1.32.1-next.1 + - @backstage/core-components@0.18.4-next.2 + - @backstage/app-defaults@1.7.3-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/test-utils@1.7.14-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/ui@0.10.0-next.1 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + +## @internal/plugin-todo-list-backend@1.0.46-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/errors@1.2.7 diff --git a/docs/releases/v1.46.0.md b/docs/releases/v1.46.0.md new file mode 100644 index 0000000000..6e42ff84ed --- /dev/null +++ b/docs/releases/v1.46.0.md @@ -0,0 +1,129 @@ +--- +id: v1.46.0 +title: v1.46.0 +description: Backstage Release v1.46.0 +--- + +These are the release notes for the v1.46.0 release of [Backstage](https://backstage.io/). + +A huge thanks to the whole team of maintainers and contributors as well as the amazing Backstage Community for the hard work in getting this release developed and done. + +## Highlights + +To begin with, this release has a few important framework level updates. + +### **BREAKING:** Updated supported Node.js versions to 22 and 24 + +This release moves up the supported Node.js versions to 22 and 24, according to our [versioning policy](https://backstage.io/docs/overview/versioning-policy#nodejs-releases). As part of this change, we are also setting the compilation targets to ES2023, which lines up with the supported Node.js versions. + +The default TypeScript `target` compiler option is however left at ES2022. This is because older versions of TypeScript will error if they see an unsupported value, even if an override is set in your local `tsconfig.json`. You can still choose to set the TypeScript `target` to either `ES2023` or even `ES2024` in your own `tsconfig.json` if you prefer. + +### **BREAKING:** Switched modules to `ES2020` and resolution mode to `bundler` + +Now that the ecosystem has had some time to mature and adapt, we have finally switched over the default module resolution mode to `bundler` instead of `node`. The `module` setting was also changed to `ES2022`. + +You _may_ need to bump some dependencies as part of this change, or fix imports in code. The most common source of this is that type checking will now consider the `exports` field in `package.json` when resolving imports. This in turn can break older versions of packages that had incompatible `exports` fields. Generally these issues will have already been fixed in the upstream packages, but if you depend on old versions or unmaintained packages, you may still notice some effects of this. + +You might be tempted to use `--skipLibCheck` to hide issues due to this change, but it will weaken the type safety of your project. If you run into a large number of issues and want to keep the old behavior, you can reset the `moduleResolution` and `module` settings of your own `tsconfig.json` file to `node` and `ESNext` respectively. But keep in mind that the `node` option will be removed in future versions of TypeScript. + +A future version of Backstage will make these new settings mandatory, as we move to rely on the `exports` field for type resolution in packages, rather than the `typesVersions` field. + +### **BREAKING:** Moving toward updating `jest` and its DOM + +The `jest` test framework used to be a builtin dependency of the Backstage CLI. In this release, it has been made into a peer dependency to allow for easy upgrades to newer versions as you see fit. + +If you run tests using the Backstage CLI, you must therefore add Jest and its environment dependencies as `devDependencies` in your project root `package.json` when you upgrade. + +You can choose to install either Jest 29 or Jest 30. The built-in Jest version before this change was Jest 29, however, we recommend that you switch to Jest 30. Upgrading will solve the `Could not parse CSS stylesheet` errors, allow you to use MSW v2 in web packages, and ensure that you remain compatible with future versions of the Backstage CLI. Support for Jest 29 is temporary, with the purpose of allowing you to upgrade at your own pace, but it will eventually be removed. + +- **Jest 29**: Install `jest@^29` and `jest-environment-jsdom@^29` in your root `package.json`. No migration needed, but you may see `Could not parse CSS stylesheet` warnings/errors when testing components from `@backstage/ui` or other packages using CSS `@layer` declarations. +- **Jest 30**: Install `jest@^30`, `@types/jest@^30`, `@jest/environment-jsdom-abstract@^30`, and `jsdom@^27` in your root `package.json`. Fixes the stylesheet parsing warnings/errors, but requires migration steps. + +See the [Jest 30 migration guide](https://backstage.io/docs/tutorials/jest30-migration) for detailed migration instructions. + +### **BREAKING:** Now using correct configuration options for Valkey + +If you are using Valkey as your backend cache provider, take note. It used to piggyback on the Redis configuration format, but that was not quite correct. So its config options have been updated to better match its capabilities. + +So if you use Valkey, you need to upgrade your `app-config.yaml`: + +```diff +backend: + cache: + store: valkey + connection: ... + client: +- namespace: 'my-app' +- keyPrefixSeparator: ':' ++ keyPrefix: 'my-app:' +- clearBatchSize: 1000 +- useUnlink: false +``` + +Contributed by [@benjidotsh](https://github.com/benjidotsh) in [#31497](https://github.com/backstage/backstage/pull/31497) + +### **BREAKING:** Techdocs addon test utils removed explicit `screen` + +`TechdocsAddonTester.renderWithEffects()` used to return a `screen` that you could use for things like `getByText`. In order to support moving to newer versions of `@testing-library`, we have removed this, and you should import and use the normal `screen` from `@testing-library/react` instead. + +However: that `screen` can’t see inside the shadow DOM, which TechDocs uses. So if your tests need to look inside shadow DOM, you must install `shadow-dom-testing-library` and use its `screen` which provides special shadow-DOM queries, such as `getByShadowText` instead of `getByText. + +Example: + +```tsx +import { screen } from 'shadow-dom-testing-library'; +// ... render the addon ... +await TechDocsAddonTester.buildAddonsInTechDocs([]) + .withDom(TEST_CONTENT) + .renderWithEffects(); +expect(screen.getByShadowText('TEST_CONTENT')).toBeInTheDocument(); +``` + +### **BREAKING:** Backstage-UI advances! + +Again, Backstage-UI has a bunch of updates! One of them is breaking: If you were using the `Cell` component with text-specific props (`title` etc), you should now instead use the `CellText` component for that. Plain `Cell` is now meant to be used with generic child elements instead. + +### Simplified route compatibility + +If you are partway through a migration to the New Frontend System, you may enjoy the fact that the `useApp` and `useRouteRef` hooks now are forwards compatible with NFS. Along with the previous route reference changes this means that there is no longer a need to use `compatWrapper` from `@backstage/core-compat-api` to make code based on `@backstage/core-plugin-api` compatible with `@backstage/frontend-plugin-api` APIs. + +### Support for Bitbucket Cloud OAuth + +Support for Bitbucket Cloud OAuth has been added - which was much welcome. This introduces an alternative authentication method using a workspace OAuth consumer, alongside App Passwords (deprecated) and API tokens. OAuth does not require a bot or service account and avoids token expiry issues. + +As part of this change, some breaking changes were made to low level auth getter functions, such that they now return a promise instead of a plain value. + +Contributed by [@jksmth](https://github.com/jksmth) in [#31848](https://github.com/backstage/backstage/pull/31848) + +### New Catalog actions + +There are two new MCP-usable actions on the block for registering and unregistering entities: `catalog:register-entity` and `catalog:unregister-entity`. + +Contributed by [@gabemontero](https://github.com/gabemontero) in [#32042](https://github.com/backstage/backstage/pull/32042) + +### New `RepoOwnerPicker` for Scaffolder + +A new field extension for selecting an organization or owner for a particular repository has been shipped! Currently, this only supports GitHub autocomplete, but contributions welcome in making this available for other providers! + +Contributed by [@benjidotsh](https://github.com/benjidotsh) in [#32105](https://github.com/backstage/backstage/pull/32105) + +## Security Fixes + +This release does not contain any security fixes. + +## Upgrade path + +We recommend that you keep your Backstage project up to date with this latest release. For more guidance on how to upgrade, check out the documentation for [keeping Backstage updated](https://backstage.io/docs/getting-started/keeping-backstage-updated). + +## Links and References + +Below you can find a list of links and references to help you learn about and start using this new release. + +- [Backstage official website](https://backstage.io/), [documentation](https://backstage.io/docs/), and [getting started guide](https://backstage.io/docs/getting-started/) +- [GitHub repository](https://github.com/backstage/backstage) +- Backstage's [versioning and support policy](https://backstage.io/docs/overview/versioning-policy) +- [Community Discord](https://discord.gg/backstage-687207715902193673) for discussions and support +- [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.46.0-changelog.md) +- Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) + +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.47.0-changelog.md b/docs/releases/v1.47.0-changelog.md new file mode 100644 index 0000000000..d71078327c --- /dev/null +++ b/docs/releases/v1.47.0-changelog.md @@ -0,0 +1,1467 @@ +# Release v1.47.0 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.47.0](https://backstage.github.io/upgrade-helper/?to=1.47.0) + +## @backstage/backend-defaults@0.15.0 + +### Minor Changes + +- 6fc00e6: Added action filtering support with glob patterns and attribute constraints. + + The `ActionsService` now supports filtering actions based on configuration. This allows controlling which actions are exposed to consumers like the MCP backend. + + Configuration example: + + ```yaml + backend: + actions: + pluginSources: + - catalog + - scaffolder + filter: + include: + - id: 'catalog:*' + attributes: + destructive: false + - id: 'scaffolder:*' + exclude: + - id: '*:delete-*' + - attributes: + readOnly: false + ``` + + Filtering logic: + + - `include`: Rules for actions to include. Each rule can specify an `id` glob pattern and/or `attributes` constraints. An action must match at least one rule to be included. If no include rules are specified, all actions are included by default. + - `exclude`: Rules for actions to exclude. Takes precedence over include rules. + - Each rule combines `id` and `attributes` with AND logic (both must match if specified). + +- 27f9061: **BREAKING**: The constructor for `FetchUrlReader` is now private. If you have to construct an instance of it, please use `FetchUrlReader.fromConfig` instead. + +- 27f9061: **BREAKING**: `coreServices.urlReader` now validates that redirect chains are subject to the allow list in `reading.allow` of your app config. If you were relying on redirects that pointed to URLs that were not allowlisted, you will now have to add those to your config as well. + + Example: + + ```diff + backend: + reading: + allow: + - host: example.com + + - host: storage-api.example.com + ``` + +### Patch Changes + +- 3afeab4: Implementing `readTree` for `GoogleGcsReader` +- c641c14: Wrap some of the action logic with `resolveSafeChildPath` and improve symlink handling when fetching remote and local files +- 7126bf2: Fixed a spelling mistake in root health service shutdown response. +- 872eb91: Upgrade `zod-to-json-schema` to latest version +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/backend-app-api@1.4.1 + - @backstage/integration@1.19.2 + - @backstage/plugin-auth-node@0.6.11 + - @backstage/plugin-permission-node@0.10.8 + +## @backstage/frontend-app-api@0.14.0 + +### Minor Changes + +- 3bd2a1a: BREAKING: The ability for plugins to override APIs has been restricted to only allow overrides of APIs within the same plugin. For example, a plugin can no longer override any of the core APIs provided by the `app` plugin, this must be done with an `app` module instead. + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/frontend-defaults@0.3.5 + +## @backstage/ui@0.11.0 + +### Minor Changes + +- 243e5e7: **BREAKING**: Redesigned Table component with new `useTable` hook API. + + - The `Table` component (React Aria wrapper) is renamed to `TableRoot` + - New high-level `Table` component that handles data display, pagination, sorting, and selection + - The `useTable` hook is completely redesigned with a new API supporting three pagination modes (complete, offset, cursor) + - New types: `ColumnConfig`, `TableProps`, `TableItem`, `UseTableOptions`, `UseTableResult` + + New features include unified pagination modes, debounced query changes, stale data preservation during reloads, and row selection with toggle/replace behaviors. + + **Migration guide:** + + 1. Update imports and use the new `useTable` hook: + + ```diff + -import { Table, useTable } from '@backstage/ui'; + -const { data, paginationProps } = useTable({ data: items, pagination: {...} }); + +import { Table, useTable, type ColumnConfig } from '@backstage/ui'; + +const { tableProps } = useTable({ + + mode: 'complete', + + getData: () => items, + +}); + ``` + + 2. Define columns and render with the new Table API: + + ```diff + -
    Configure, Deploy, & Upgrade.

    @@ -60,7 +73,7 @@ description: Documentation landing page.
  • Search
  • Software Catalog
  • Software Templates (aka Scaffolder)
  • -
  • TechDocs
  • +
  • TechDocs - a docs-like-code solution
  • + - ... + - ... + -
    + - + +const columns: ColumnConfig[] = [ + + { id: 'name', label: 'Name', isRowHeader: true, cell: item => }, + + { id: 'type', label: 'Type', cell: item => }, + +]; + + + + + ``` + + Affected components: Table, TableRoot, TablePagination + +- 95246eb: **Breaking** Updating color tokens to match the new neutral style on different surfaces. + + ## Migration notes + + There's no direct replacement for the old tint tokens but you can use the new neutral set of color tokens on surface 0 or 1 as a replacement. + + - `--bui-bg-tint` can be replaced by `--bui-bg-neutral-on-surface-0` + - `--bui-bg-tint-hover` can be replaced by `--bui-bg-neutral-on-surface-0-hover` + - `--bui-bg-tint-pressed` can be replaced by `--bui-bg-neutral-on-surface-0-pressed` + - `--bui-bg-tint-disabled` can be replaced by `--bui-bg-neutral-on-surface-0-disabled` + +- ea0c6d8: Introduce new `ToggleButton` & `ToggleButtonGroup` components in Backstage UI + +- 4ea1d15: **BREAKING**: Renamed CSS variable `--bui-bg` to `--bui-bg-surface-0` for consistency. + +### Patch Changes + +- 1880402: Fixes app background color on dark mode. + +- d2fdded: Added indeterminate state support to the Checkbox component for handling partial selection scenarios like table header checkboxes. + + Affected components: Checkbox + +- 4fb15d2: Added missing `aria-label` attributes to `SearchField` components in `Select`, `MenuAutocomplete`, and `MenuAutocompleteListbox` to fix accessibility warnings. + + Affected components: Select, MenuAutocomplete, MenuAutocompleteListbox + +- 21c87cc: Fixes disabled state in primary and secondary buttons in Backstage UI. + +- 9c76682: build(deps-dev): bump `storybook` from 10.1.9 to 10.1.10 + +- de80336: Fixed disabled tertiary buttons incorrectly showing hover effects on surfaces. + +- 133d5c6: Added new Popover component for Backstage UI with automatic overflow handling, and full placement support. Also introduced `--bui-shadow` token for consistent elevation styling across overlay components (Popover, Tooltip, Menu). + +- 973c839: Fixed Table sorting indicator not being visible when a column is actively sorted. + + Affected components: Table, Column + +- df40cfc: Fixed Menu component trigger button not toggling correctly. Removed custom click-outside handler that was interfering with React Aria's built-in state management, allowing the menu to properly open and close when clicking the trigger button. + +- b01ab96: Added support for column width configuration in Table component. Columns now accept `width`, `defaultWidth`, `minWidth`, and `maxWidth` props for responsive layout control. + + Affected components: Table, Column + +- b4a4911: Fixed SearchField `startCollapsed` prop not working correctly in Backstage UI. The field now properly starts in a collapsed state, expands when clicked and focused, and collapses back when unfocused with no input. Also fixed CSS logic to work correctly in all layout contexts (flex row, flex column, and regular containers). + + Affected components: SearchField + +- b3253b6: Fixed `Link` component causing hard page refreshes for internal routes. The component now properly uses React Router's navigation instead of full page reloads. + +- fe7fe69: Added support for custom pagination options in `useTable` hook and `Table` component. You can now configure `pageSizeOptions` to customize the page size dropdown, and hook into pagination events via `onPageSizeChange`, `onNextPage`, and `onPreviousPage` callbacks. When `pageSize` doesn't match any option, the first option is used and a warning is logged. + + Affected components: Table, TablePagination + +- cfac8a4: Fixed missing border styles on table selection cells in multi-select mode. + + Affected components: Table + +- 2532d2a: Added `className` and `style` props to the `Table` component. + + Affected components: Table + +## @backstage/plugin-app-react@0.1.0 + +### Minor Changes + +- 9ccf84e: Initial release of this web library for `@backstage/plugin-app`. + +### Patch Changes + +- 9ccf84e: Moved the following blueprints from `@backstage/frontend-plugin-api`: + + - `AppRootWrapperBlueprint` + - `IconBundleBlueprint` + - `NavContentBlueprint` + - `RouterBlueprint` + - `SignInPageBlueprint` + - `SwappableComponentBlueprint` + - `ThemeBlueprint` + - `TranslationBlueprint` + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + +## @backstage/plugin-auth-backend@0.26.0 + +### Minor Changes + +- 7ffc873: Fix `user_created_at` migration causing `SQLiteError` regarding use of non-constants for defaults + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-auth-node@0.6.11 + +## @backstage/plugin-devtools-react@0.1.0 + +### Minor Changes + +- be6cef5: Add support for adding `unprocessed-entities` and other tabs to `devtools` when using the New Frontend system + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + +## @backstage/plugin-events-backend-module-kafka@0.3.0 + +### Minor Changes + +- ef5bbd8: Add support for Kafka offset configuration (`fromBeginning`) and `autoCommit` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + +## @backstage/plugin-home@0.9.0 + +### Minor Changes + +- e091a83: Widget configurations are now only saved to storage when the Save button is explicitly clicked. Added a Cancel button that allows users to discard unsaved changes and revert to the last saved state. + +### Patch Changes + +- bdda543: Updated WidgetOverlay color to use `alpha(theme.palette.background.paper, 0.93)` for better theme alignment instead of hardcoded RGBA +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + - @backstage/plugin-catalog-react@1.21.5 + - @backstage/plugin-home-react@0.1.34 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.3.0 + +### Minor Changes + +- ab606b2: Add ability to configure the API Base URL + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + +## @backstage/plugin-techdocs-node@1.14.0 + +### Minor Changes + +- 63c459c: **BREAKING:** It's now possible to use the credentials from the `integrations.awsS3` config to authenticate with AWS S3. The new priority is: + + 1. `aws.accounts` + 2. `techdocs.publisher.awsS3.credentials` + 3. `integrations.awsS3` + 4. Default credential chain + + In case of multiple `integrations.awsS3` are present, the target integration is determined by the `accessKeyId` in `techdocs.publisher.awsS3.credentials` if provided. Otherwise, the default credential chain is used. + + This means that depending on your setup, this feature may break your existing setup. + In general: + + - if you are configuring `aws.accounts`, no action is required + - if you are configuring `techdocs.publisher.awsS3.credentials`, no action is required + - if you are configuring multiple integrations under `integrations.awsS3`, no action is required + - if you are configuring a single integration under `integrations.awsS3`, make sure that the integration has access to the bucket you are using for TechDocs + +### Patch Changes + +- f0951aa: Updated the `defaultDockerImage` to reflect the latest TechDocs Container version of v1.2.8 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/integration@1.19.2 + +## @backstage/app-defaults@1.7.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5 + +## @backstage/backend-app-api@1.4.1 + +### Patch Changes + +- 04db26b: Clean up process event listeners on backend stop to prevent leaks +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + +## @backstage/backend-dynamic-feature-service@0.7.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0 + - @backstage/backend-plugin-api@1.6.1 + - @backstage/cli-common@0.1.17 + - @backstage/plugin-app-node@0.1.41 + - @backstage/plugin-scaffolder-node@0.12.3 + - @backstage/backend-openapi-utils@0.6.5 + - @backstage/plugin-auth-node@0.6.11 + - @backstage/plugin-permission-common@0.9.4 + - @backstage/plugin-permission-node@0.10.8 + - @backstage/plugin-catalog-backend@3.3.1 + - @backstage/plugin-events-backend@0.5.10 + +## @backstage/backend-openapi-utils@0.6.5 + +### Patch Changes + +- 6678b78: Internal update to use native feature from our request validation library for handling base path determination. +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + +## @backstage/backend-plugin-api@1.6.1 + +### Patch Changes + +- ae4dd5d: Move some of the symlink resolution to `isChildPath` +- Updated dependencies + - @backstage/cli-common@0.1.17 + - @backstage/plugin-auth-node@0.6.11 + - @backstage/plugin-permission-common@0.9.4 + - @backstage/plugin-permission-node@0.10.8 + +## @backstage/backend-test-utils@1.10.3 + +### Patch Changes + +- 872eb91: Upgrade `zod-to-json-schema` to latest version +- Updated dependencies + - @backstage/backend-defaults@0.15.0 + - @backstage/backend-plugin-api@1.6.1 + - @backstage/backend-app-api@1.4.1 + - @backstage/plugin-auth-node@0.6.11 + - @backstage/plugin-permission-common@0.9.4 + +## @backstage/cli@0.35.2 + +### Patch Changes + +- 320c6a9: Bump `@swc/core` to support `ES2023` and `ES2024` +- c0d7bf6: Added `--include` and `--format` options to `backstage-cli info` command for including additional packages via glob patterns and outputting as JSON or Text. +- f6a5d2f: Fixed CSS module class name collisions when running multiple versions of packages simultaneously by using content-based hashing for class name generation. +- 140cbc2: Added `@backstage/backend-test-utils` to backend package templates. +- 4eeba9e: Upgrade `zod-validation-error` to version 4 +- 9ee5996: Bump minimum required `@swc/core` to avoid transpilation bug +- Updated dependencies + - @backstage/cli-common@0.1.17 + - @backstage/integration@1.19.2 + +## @backstage/cli-common@0.1.17 + +### Patch Changes + +- ae4dd5d: Move some of the symlink resolution to `isChildPath` + +## @backstage/core-compat-api@0.5.6 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/plugin-catalog-react@1.21.5 + +## @backstage/core-components@0.18.5 + +### Patch Changes + +- a723b8a: The MarkdownContent component now handles HTML content the same way as GitHub when rendering GitHub-flavored Markdown +- c671db9: Fixed bug in Table component where the toolbar layout would break when both a title and filters were present. +- 55a9dc2: Update colour token again in ItemCardHeader to respect theme definition. + +## @backstage/create-app@0.7.8 + +### Patch Changes + +- fea3e39: Bumped create-app version. +- 9f1ee3e: Bumped create-app version. +- 880310b: Bumped create-app version. +- f1fe6fe: Updated Dockerfile to use Node 24 and Debian Trixie +- Updated dependencies + - @backstage/cli-common@0.1.17 + +## @backstage/dev-utils@1.1.19 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.11.0 + - @backstage/core-components@0.18.5 + - @backstage/app-defaults@1.7.4 + - @backstage/integration-react@1.2.14 + - @backstage/plugin-catalog-react@1.21.5 + +## @backstage/frontend-defaults@0.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + - @backstage/plugin-app@0.3.4 + - @backstage/frontend-app-api@0.14.0 + +## @backstage/frontend-plugin-api@0.13.3 + +### Patch Changes + +- 3bd2a1a: Updated documentation for `createApiRef` to clarify the role of the ID in specifying the owning plugin of an API. + +- 9ccf84e: The following blueprints are being restricted to only be used in app plugin overrides and modules. They are being moved to the `@backstage/plugin-app-react` package and have been deprecated: + + - `AppRootWrapperBlueprint` + - `IconBundleBlueprint` + - `NavContentBlueprint` + - `RouterBlueprint` + - `SignInPageBlueprint` + - `SwappableComponentBlueprint` + - `ThemeBlueprint` + - `TranslationBlueprint` + +- 4554a4e: Added an alpha `PluginWrapperBlueprint` exported from `@backstage/frontend-plugin-api/alpha`, which can install components that will wrap all plugin elements. + +- 872eb91: Upgrade `zod-to-json-schema` to latest version + +## @backstage/frontend-test-utils@0.4.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/plugin-app@0.3.4 + - @backstage/frontend-app-api@0.14.0 + +## @backstage/integration@1.19.2 + +### Patch Changes + +- 3afeab4: Implementing `ScmIntegration` for `GoogleGcs` +- 9083273: Rollback the lowercase replacing in GitHub integration config + +## @backstage/integration-react@1.2.14 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2 + +## @backstage/repo-tools@0.16.2 + +### Patch Changes + +- 498f9dd: Fixed help text for `backstage-repo-tools package schema openapi generate` command. +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/cli-common@0.1.17 + +## @techdocs/cli@1.10.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0 + - @backstage/cli-common@0.1.17 + - @backstage/plugin-techdocs-node@1.14.0 + +## @backstage/plugin-api-docs@0.13.3 + +### Patch Changes + +- 0216090: Updated dependency `@types/swagger-ui-react` to `^5.0.0`. +- abeba2b: Fix types with new bumped dependency +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/plugin-catalog@1.32.2 + - @backstage/core-components@0.18.5 + - @backstage/plugin-catalog-react@1.21.5 + +## @backstage/plugin-app@0.3.4 + +### Patch Changes + +- 4554a4e: Implemented support for the new `PluginWrapperBlueprint` from `@backstage/frontend-plugin-api/alpha`. + +- 9ccf84e: The following blueprints are being restricted to only be used in app plugin overrides and modules. They will now produce a deprecation warning when used outside of the app plugin: + + - `AppRootWrapperBlueprint` + - `IconBundleBlueprint` + - `NavContentBlueprint` + - `RouterBlueprint` + - `SignInPageBlueprint` + - `SwappableComponentBlueprint` + - `ThemeBlueprint` + - `TranslationBlueprint` + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/plugin-app-react@0.1.0 + - @backstage/core-components@0.18.5 + - @backstage/integration-react@1.2.14 + +## @backstage/plugin-app-backend@0.5.10 + +### Patch Changes + +- 9ccf84e: Updated plugin metadata. +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-app-node@0.1.41 + - @backstage/plugin-auth-node@0.6.11 + +## @backstage/plugin-app-node@0.1.41 + +### Patch Changes + +- 9ccf84e: Updated plugin metadata. +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + +## @backstage/plugin-app-visualizer@0.1.27 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.11.0 + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + +## @backstage/plugin-auth@0.1.4 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-auth-backend@0.26.0 + - @backstage/plugin-auth-node@0.6.11 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.4.11 + +### Patch Changes + +- e54fcb2: Added support for custom start URL search parameters (with the new `startUrlSearchParams` config property) +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-auth-backend@0.26.0 + - @backstage/plugin-auth-node@0.6.11 + +## @backstage/plugin-auth-node@0.6.11 + +### Patch Changes + +- 4eeba9e: Upgrade `zod-validation-error` to version 4 +- 872eb91: Upgrade `zod-to-json-schema` to latest version +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + +## @backstage/plugin-auth-react@0.1.23 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5 + +## @backstage/plugin-bitbucket-cloud-common@0.3.6 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2 + +## @backstage/plugin-catalog@1.32.2 + +### Patch Changes + +- 7ca91e8: Header in EntityLayout should always be shown. + Monitoring the loading status caused flickering when the refresh() method of the Async Entity was invoked. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + - @backstage/core-compat-api@0.5.6 + - @backstage/plugin-search-react@1.10.2 + - @backstage/integration-react@1.2.14 + - @backstage/plugin-catalog-react@1.21.5 + - @backstage/plugin-scaffolder-common@1.7.5 + - @backstage/plugin-techdocs-react@1.3.7 + +## @backstage/plugin-catalog-backend@3.3.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/integration@1.19.2 + - @backstage/backend-openapi-utils@0.6.5 + - @backstage/plugin-permission-common@0.9.4 + - @backstage/plugin-permission-node@0.10.8 + +## @backstage/plugin-catalog-backend-module-aws@0.4.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0 + - @backstage/backend-plugin-api@1.6.1 + - @backstage/integration@1.19.2 + +## @backstage/plugin-catalog-backend-module-azure@0.3.13 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/integration@1.19.2 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/backend-openapi-utils@0.6.5 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.7 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/integration@1.19.2 + - @backstage/plugin-bitbucket-cloud-common@0.3.6 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.7 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/integration@1.19.2 + +## @backstage/plugin-catalog-backend-module-gerrit@0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/integration@1.19.2 + +## @backstage/plugin-catalog-backend-module-gitea@0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/integration@1.19.2 + +## @backstage/plugin-catalog-backend-module-github@0.12.1 + +### Patch Changes + +- cb4b907: Improved efficiency of `GithubOrgEntityProvider` membership event handling and edit team. The provider now fetches only the specific user's teams instead of all organization users when processing membership events, and uses `addEntitiesOperation` instead of `replaceEntitiesOperation` to avoid unnecessary entity deletions. +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/integration@1.19.2 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.18 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-catalog-backend-module-github@0.12.1 + +## @backstage/plugin-catalog-backend-module-gitlab@0.7.7 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0 + - @backstage/backend-plugin-api@1.6.1 + - @backstage/integration@1.19.2 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-catalog-backend-module-gitlab@0.7.7 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0 + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-permission-common@0.9.4 + - @backstage/plugin-catalog-backend@3.3.1 + +## @backstage/plugin-catalog-backend-module-logs@0.1.18 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-catalog-backend@3.3.1 + +## @backstage/plugin-catalog-backend-module-msgraph@0.8.4 + +### Patch Changes + +- 115b378: Changed the logger level from 'warning' to 'debug' when we are unable to load the user's photo. +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.18 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/integration@1.19.2 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.18 + +### Patch Changes + +- a307700: Fixed crash when `latest_report_status` is undefined +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-common@1.7.5 + +## @backstage/plugin-catalog-graph@0.5.6 + +### Patch Changes + +- 5c49a00: Update for the `qs` library bump: the old array limit setting has changed to be more strict; you can no longer just give a zero to mean unlimited. So we choose an arbitrary high value, to at least go higher than the default 20. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + - @backstage/plugin-catalog-react@1.21.5 + +## @backstage/plugin-catalog-import@0.13.9 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + - @backstage/integration@1.19.2 + - @backstage/integration-react@1.2.14 + - @backstage/plugin-catalog-react@1.21.5 + +## @backstage/plugin-catalog-react@1.21.5 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + - @backstage/plugin-permission-common@0.9.4 + - @backstage/core-compat-api@0.5.6 + - @backstage/frontend-test-utils@0.4.3 + - @backstage/integration-react@1.2.14 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.25 + +### Patch Changes + +- be6cef5: Add support for adding `unprocessed-entities` and other tabs to `devtools` when using the New Frontend system +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + - @backstage/plugin-devtools-react@0.1.0 + - @backstage/core-compat-api@0.5.6 + +## @backstage/plugin-config-schema@0.1.76 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5 + +## @backstage/plugin-devtools@0.1.35 + +### Patch Changes + +- be6cef5: Add support for adding `unprocessed-entities` and other tabs to `devtools` when using the New Frontend system +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + - @backstage/plugin-devtools-react@0.1.0 + - @backstage/plugin-devtools-common@0.1.21 + - @backstage/core-compat-api@0.5.6 + +## @backstage/plugin-devtools-backend@0.5.13 + +### Patch Changes + +- be6cef5: Add support for adding `unprocessed-entities` and other tabs to `devtools` when using the New Frontend system +- Updated dependencies + - @backstage/backend-defaults@0.15.0 + - @backstage/backend-plugin-api@1.6.1 + - @backstage/cli-common@0.1.17 + - @backstage/plugin-devtools-common@0.1.21 + - @backstage/plugin-permission-common@0.9.4 + - @backstage/plugin-permission-node@0.10.8 + +## @backstage/plugin-devtools-common@0.1.21 + +### Patch Changes + +- be6cef5: Add support for adding `unprocessed-entities` and other tabs to `devtools` when using the New Frontend system +- Updated dependencies + - @backstage/plugin-permission-common@0.9.4 + +## @backstage/plugin-events-backend@0.5.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/backend-openapi-utils@0.6.5 + +## @backstage/plugin-events-backend-module-github@0.4.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/integration@1.19.2 + +## @backstage/plugin-home-react@0.1.34 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + +## @backstage/plugin-kubernetes@0.12.15 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + - @backstage/plugin-catalog-react@1.21.5 + - @backstage/plugin-kubernetes-react@0.5.15 + +## @backstage/plugin-kubernetes-cluster@0.0.33 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5 + - @backstage/plugin-catalog-react@1.21.5 + - @backstage/plugin-kubernetes-react@0.5.15 + +## @backstage/plugin-kubernetes-react@0.5.15 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5 + +## @backstage/plugin-mcp-actions-backend@0.1.7 + +### Patch Changes + +- 4d82a35: build(deps): bump `@modelcontextprotocol/sdk` from 1.24.3 to 1.25.2 +- Updated dependencies + - @backstage/backend-defaults@0.15.0 + - @backstage/backend-plugin-api@1.6.1 + +## @backstage/plugin-mui-to-bui@0.2.3 + +### Patch Changes + +- f157f43: Fix installation command +- e4a1180: Updated tokens from `--bui-bg` to `--bui-bg-surface-0` +- Updated dependencies + - @backstage/ui@0.11.0 + - @backstage/frontend-plugin-api@0.13.3 + +## @backstage/plugin-notifications@0.5.13 + +### Patch Changes + +- 4452d15: Added i18n support. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + +## @backstage/plugin-org@0.6.48 + +### Patch Changes + +- db3cfd7: Adds the Resource kind to the `useGetEntities` fallback so that Resources are included by default when no specific kinds are provided in the group ownership card. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + - @backstage/plugin-catalog-react@1.21.5 + +## @backstage/plugin-org-react@0.1.46 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5 + - @backstage/plugin-catalog-react@1.21.5 + +## @backstage/plugin-permission-common@0.9.4 + +### Patch Changes + +- 872eb91: Upgrade `zod-to-json-schema` to latest version + +## @backstage/plugin-permission-node@0.10.8 + +### Patch Changes + +- 872eb91: Upgrade `zod-to-json-schema` to latest version +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-auth-node@0.6.11 + - @backstage/plugin-permission-common@0.9.4 + +## @backstage/plugin-scaffolder@1.35.1 + +### Patch Changes + +- 9d75495: Fixed bug in RepoUrlPickerComponent component where repository names were not being autocompleted. +- 872eb91: Upgrade `zod-to-json-schema` to latest version +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + - @backstage/integration@1.19.2 + - @backstage/plugin-scaffolder-react@1.19.5 + - @backstage/integration-react@1.2.14 + - @backstage/plugin-catalog-react@1.21.5 + - @backstage/plugin-scaffolder-common@1.7.5 + - @backstage/plugin-techdocs-react@1.3.7 + +## @backstage/plugin-scaffolder-backend@3.1.1 + +### Patch Changes + +- 5012852: Remove unused abort controller in debug:wait action +- c641c14: Wrap some of the action logic with `resolveSafeChildPath` and improve symlink handling when fetching remote and local files +- 27f9061: REwrite] +- 872eb91: Upgrade `zod-to-json-schema` to latest version +- Updated dependencies + - @backstage/backend-defaults@0.15.0 + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + - @backstage/integration@1.19.2 + - @backstage/backend-openapi-utils@0.6.5 + - @backstage/plugin-scaffolder-backend-module-github@0.9.4 + - @backstage/plugin-auth-node@0.6.11 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.17 + - @backstage/plugin-permission-common@0.9.4 + - @backstage/plugin-permission-node@0.10.8 + - @backstage/plugin-bitbucket-cloud-common@0.3.6 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.16 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.18 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.17 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.17 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.17 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.11.1 + - @backstage/plugin-scaffolder-common@1.7.5 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.17 + +### Patch Changes + +- 88abcc6: Improved README with clearer setup and usage guidance. +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + - @backstage/integration@1.19.2 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.18 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + - @backstage/integration@1.19.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.17 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + - @backstage/integration@1.19.2 + - @backstage/plugin-bitbucket-cloud-common@0.3.6 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + - @backstage/integration@1.19.2 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + - @backstage/integration@1.19.2 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0 + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + - @backstage/integration@1.19.2 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + - @backstage/integration@1.19.2 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + - @backstage/integration@1.19.2 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + - @backstage/integration@1.19.2 + +## @backstage/plugin-scaffolder-backend-module-github@0.9.4 + +### Patch Changes + +- bb7088b: Added options to set [workflow access level][access-level] for repositories to `github:repo:create` + + This is useful when creating repositories for GitHub Actions to manage access + to the workflows during creation. + + ```diff + - action: github:repo:create + id: create-repo + input: + repoUrl: github.com?owner=owner&repo=repo + visibility: private + + workflowAccess: organization + ``` + + [access-level]: https://docs.github.com/en/rest/actions/permissions?apiVersion=2022-11-28#set-the-level-of-access-for-workflows-outside-of-the-repository + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + - @backstage/integration@1.19.2 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.11.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + - @backstage/integration@1.19.2 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.18 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + - @backstage/integration@1.19.2 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.18 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + - @backstage/plugin-scaffolder-node-test-utils@0.3.7 + +## @backstage/plugin-scaffolder-common@1.7.5 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2 + - @backstage/plugin-permission-common@0.9.4 + +## @backstage/plugin-scaffolder-node@0.12.3 + +### Patch Changes + +- c641c14: Wrap some of the action logic with `resolveSafeChildPath` and improve symlink handling when fetching remote and local files +- 27f9061: REwrite] +- 872eb91: Upgrade `zod-to-json-schema` to latest version +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/integration@1.19.2 + - @backstage/plugin-permission-common@0.9.4 + - @backstage/plugin-scaffolder-common@1.7.5 + +## @backstage/plugin-scaffolder-node-test-utils@0.3.7 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + - @backstage/backend-test-utils@1.10.3 + +## @backstage/plugin-scaffolder-react@1.19.5 + +### Patch Changes + +- 872eb91: Upgrade `zod-to-json-schema` to latest version +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + - @backstage/plugin-catalog-react@1.21.5 + - @backstage/plugin-scaffolder-common@1.7.5 + +## @backstage/plugin-search@1.5.3 + +### Patch Changes + +- 5c49a00: Update for the `qs` library bump: the old array limit setting has changed to be more strict; you can no longer just give a zero to mean unlimited. So we choose an arbitrary high value, to at least go higher than the default 20. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + - @backstage/plugin-search-react@1.10.2 + - @backstage/plugin-catalog-react@1.21.5 + +## @backstage/plugin-search-backend@2.0.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0 + - @backstage/backend-plugin-api@1.6.1 + - @backstage/backend-openapi-utils@0.6.5 + - @backstage/plugin-permission-common@0.9.4 + - @backstage/plugin-permission-node@0.10.8 + +## @backstage/plugin-search-backend-module-techdocs@0.4.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-techdocs-node@1.14.0 + - @backstage/plugin-permission-common@0.9.4 + +## @backstage/plugin-search-react@1.10.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + +## @backstage/plugin-signals@0.0.27 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + +## @backstage/plugin-techdocs@1.16.2 + +### Patch Changes + +- 0afb8a6: Corrected color of some elements such as Grid cards and Tables. +- 94ff7ab: Code block "Copy to clipboard" button was not positioned correctly for docs built with `mkdocs-material>=9.7` +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + - @backstage/integration@1.19.2 + - @backstage/plugin-search-react@1.10.2 + - @backstage/integration-react@1.2.14 + - @backstage/plugin-auth-react@0.1.23 + - @backstage/plugin-catalog-react@1.21.5 + - @backstage/plugin-techdocs-react@1.3.7 + +## @backstage/plugin-techdocs-addons-test-utils@2.0.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.32.2 + - @backstage/plugin-techdocs@1.16.2 + - @backstage/plugin-search-react@1.10.2 + - @backstage/integration-react@1.2.14 + - @backstage/plugin-catalog-react@1.21.5 + - @backstage/plugin-techdocs-react@1.3.7 + +## @backstage/plugin-techdocs-backend@2.1.4 + +### Patch Changes + +- b6ff2a5: Some AWS `publisher` config options such as `region`, `endpoint`, `accountId` are now marked as `@visibility backend` instead of `secret`. +- Updated dependencies + - @backstage/backend-defaults@0.15.0 + - @backstage/backend-plugin-api@1.6.1 + - @backstage/integration@1.19.2 + - @backstage/plugin-techdocs-node@1.14.0 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.32 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + - @backstage/integration@1.19.2 + - @backstage/integration-react@1.2.14 + - @backstage/plugin-techdocs-react@1.3.7 + +## @backstage/plugin-techdocs-react@1.3.7 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + +## @backstage/plugin-user-settings@0.8.31 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + - @backstage/plugin-catalog-react@1.21.5 + +## @backstage/plugin-user-settings-backend@0.3.10 + +### Patch Changes + +- ad01e54: Resolves an issue where user setting keys containing slashes returned 404 not found. +- Updated dependencies + - @backstage/backend-defaults@0.15.0 + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-auth-node@0.6.11 + +## example-app@0.2.117 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.11.0 + - @backstage/plugin-catalog@1.32.2 + - @backstage/plugin-home@0.9.0 + - @backstage/cli@0.35.2 + - @backstage/core-components@0.18.5 + - @backstage/plugin-org@0.6.48 + - @backstage/plugin-mui-to-bui@0.2.3 + - @backstage/plugin-notifications@0.5.13 + - @backstage/frontend-app-api@0.14.0 + - @backstage/plugin-api-docs@0.13.3 + - @backstage/plugin-techdocs@1.16.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.25 + - @backstage/plugin-devtools@0.1.35 + - @backstage/plugin-scaffolder@1.35.1 + - @backstage/plugin-catalog-graph@0.5.6 + - @backstage/plugin-search@1.5.3 + - @backstage/plugin-scaffolder-react@1.19.5 + - @backstage/plugin-search-react@1.10.2 + - @backstage/app-defaults@1.7.4 + - @backstage/integration-react@1.2.14 + - @backstage/plugin-auth-react@0.1.23 + - @backstage/plugin-catalog-import@0.13.9 + - @backstage/plugin-catalog-react@1.21.5 + - @backstage/plugin-kubernetes@0.12.15 + - @backstage/plugin-kubernetes-cluster@0.0.33 + - @backstage/plugin-signals@0.0.27 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.32 + - @backstage/plugin-techdocs-react@1.3.7 + - @backstage/plugin-user-settings@0.8.31 + +## example-app-next@0.0.31 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.11.0 + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/plugin-catalog@1.32.2 + - @backstage/plugin-home@0.9.0 + - @backstage/cli@0.35.2 + - @backstage/core-components@0.18.5 + - @backstage/plugin-org@0.6.48 + - @backstage/plugin-app@0.3.4 + - @backstage/plugin-notifications@0.5.13 + - @backstage/frontend-app-api@0.14.0 + - @backstage/plugin-api-docs@0.13.3 + - @backstage/plugin-techdocs@1.16.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.25 + - @backstage/plugin-devtools@0.1.35 + - @backstage/plugin-scaffolder@1.35.1 + - @backstage/plugin-catalog-graph@0.5.6 + - @backstage/plugin-search@1.5.3 + - @backstage/plugin-scaffolder-react@1.19.5 + - @backstage/core-compat-api@0.5.6 + - @backstage/frontend-defaults@0.3.5 + - @backstage/plugin-search-react@1.10.2 + - @backstage/app-defaults@1.7.4 + - @backstage/integration-react@1.2.14 + - @backstage/plugin-app-visualizer@0.1.27 + - @backstage/plugin-auth@0.1.4 + - @backstage/plugin-auth-react@0.1.23 + - @backstage/plugin-catalog-import@0.13.9 + - @backstage/plugin-catalog-react@1.21.5 + - @backstage/plugin-kubernetes@0.12.15 + - @backstage/plugin-kubernetes-cluster@0.0.33 + - @backstage/plugin-signals@0.0.27 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.32 + - @backstage/plugin-techdocs-react@1.3.7 + - @backstage/plugin-user-settings@0.8.31 + +## app-next-example-plugin@0.0.31 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + +## example-backend@0.0.46 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0 + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-app-backend@0.5.10 + - @backstage/plugin-techdocs-backend@2.1.4 + - @backstage/plugin-scaffolder-backend@3.1.1 + - @backstage/plugin-mcp-actions-backend@0.1.7 + - @backstage/plugin-auth-backend@0.26.0 + - @backstage/plugin-scaffolder-backend-module-github@0.9.4 + - @backstage/plugin-devtools-backend@0.5.13 + - @backstage/plugin-auth-node@0.6.11 + - @backstage/plugin-permission-common@0.9.4 + - @backstage/plugin-permission-node@0.10.8 + - @backstage/plugin-catalog-backend@3.3.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.10 + - @backstage/plugin-catalog-backend-module-openapi@0.2.18 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.16 + - @backstage/plugin-events-backend@0.5.10 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.18 + - @backstage/plugin-search-backend@2.0.10 + - @backstage/plugin-search-backend-module-techdocs@0.4.10 + +## e2e-test@0.2.36 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.17 + - @backstage/create-app@0.7.8 + +## @internal/scaffolder@0.0.17 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/plugin-scaffolder-react@1.19.5 + +## techdocs-cli-embedded-app@0.2.116 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.11.0 + - @backstage/plugin-catalog@1.32.2 + - @backstage/cli@0.35.2 + - @backstage/core-components@0.18.5 + - @backstage/plugin-techdocs@1.16.2 + - @backstage/app-defaults@1.7.4 + - @backstage/integration-react@1.2.14 + - @backstage/plugin-techdocs-react@1.3.7 + +## @internal/plugin-todo-list@1.0.47 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5 diff --git a/docs/releases/v1.47.0-next.0-changelog.md b/docs/releases/v1.47.0-next.0-changelog.md new file mode 100644 index 0000000000..e1fd3f9ebb --- /dev/null +++ b/docs/releases/v1.47.0-next.0-changelog.md @@ -0,0 +1,578 @@ +# Release v1.47.0-next.0 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.47.0-next.0](https://backstage.github.io/upgrade-helper/?to=1.47.0-next.0) + +## @backstage/ui@0.11.0-next.0 + +### Minor Changes + +- 4ea1d15: **BREAKING**: Renamed CSS variable `--bui-bg` to `--bui-bg-surface-0` for consistency. + +### Patch Changes + +- 1880402: Fixes app background color on dark mode. +- 9c76682: build(deps-dev): bump `storybook` from 10.1.9 to 10.1.10 +- b4a4911: Fixed SearchField `startCollapsed` prop not working correctly in Backstage UI. The field now properly starts in a collapsed state, expands when clicked and focused, and collapses back when unfocused with no input. Also fixed CSS logic to work correctly in all layout contexts (flex row, flex column, and regular containers). + + Affected components: SearchField + +## @backstage/backend-defaults@0.14.1-next.0 + +### Patch Changes + +- 7126bf2: Fixed a spelling mistake in root health service shutdown response. +- Updated dependencies + - @backstage/backend-app-api@1.4.0 + - @backstage/backend-dev-utils@0.1.6 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/cli-node@0.2.16 + - @backstage/config@1.3.6 + - @backstage/config-loader@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/integration@1.19.0 + - @backstage/integration-aws-node@0.1.19 + - @backstage/types@1.2.2 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-events-node@0.4.18 + - @backstage/plugin-permission-node@0.10.7 + +## @backstage/backend-dynamic-feature-service@0.7.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.5-next.0 + - @backstage/backend-defaults@0.14.1-next.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/cli-common@0.1.16 + - @backstage/cli-node@0.2.16 + - @backstage/config@1.3.6 + - @backstage/config-loader@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-app-node@0.1.40 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-catalog-backend@3.3.1-next.0 + - @backstage/plugin-events-backend@0.5.10-next.0 + - @backstage/plugin-events-node@0.4.18 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/plugin-scaffolder-node@0.12.2 + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/backend-openapi-utils@0.6.5-next.0 + +### Patch Changes + +- 6678b78: Internal update to use native feature from our request validation library for handling base path determination. +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/backend-test-utils@1.10.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.1-next.0 + - @backstage/backend-app-api@1.4.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-events-node@0.4.18 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/cli@0.35.2-next.0 + +### Patch Changes + +- 320c6a9: Bump `@swc/core` to support `ES2023` and `ES2024` +- 9ee5996: Bump minimum required `@swc/core` to avoid transpilation bug +- Updated dependencies + - @backstage/catalog-model@1.7.6 + - @backstage/cli-common@0.1.16 + - @backstage/cli-node@0.2.16 + - @backstage/config@1.3.6 + - @backstage/config-loader@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/eslint-plugin@0.2.0 + - @backstage/integration@1.19.0 + - @backstage/release-manifests@0.0.13 + - @backstage/types@1.2.2 + +## @backstage/create-app@0.7.8-next.0 + +### Patch Changes + +- Bumped create-app version. +- Updated dependencies + - @backstage/cli-common@0.1.16 + +## @backstage/dev-utils@1.1.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.11.0-next.0 + - @backstage/app-defaults@1.7.3 + - @backstage/catalog-model@1.7.6 + - @backstage/core-app-api@1.19.3 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/integration-react@1.2.13 + - @backstage/theme@0.7.1 + - @backstage/plugin-catalog-react@1.21.4 + +## @techdocs/cli@1.10.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/cli-common@0.1.16 + - @backstage/config@1.3.6 + - @backstage/plugin-techdocs-node@1.13.10 + +## @backstage/plugin-app-visualizer@0.1.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.11.0-next.0 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/frontend-plugin-api@0.13.2 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.4.11-next.0 + +### Patch Changes + +- e54fcb2: Added support for custom start URL search parameters (with the new `startUrlSearchParams` config property) +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + - @backstage/plugin-auth-backend@0.25.7 + - @backstage/plugin-auth-node@0.6.10 + +## @backstage/plugin-catalog-backend@3.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.5-next.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration@1.19.0 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-events-node@0.4.18 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-permission-node@0.10.7 + +## @backstage/plugin-catalog-backend-module-aws@0.4.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.1-next.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration@1.19.0 + - @backstage/integration-aws-node@0.1.19 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-kubernetes-common@0.9.9 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.5-next.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-gitlab@0.7.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.1-next.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/integration@1.19.0 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-events-node@0.4.18 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-backend-module-gitlab@0.7.7-next.0 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-events-node@0.4.18 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.1-next.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-backend@3.3.1-next.0 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-events-node@0.4.18 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-catalog-backend-module-logs@0.1.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-backend@3.3.1-next.0 + - @backstage/plugin-events-node@0.4.18 + +## @backstage/plugin-devtools-backend@0.5.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.1-next.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/cli-common@0.1.16 + - @backstage/config@1.3.6 + - @backstage/config-loader@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-devtools-common@0.1.20 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-permission-node@0.10.7 + +## @backstage/plugin-events-backend@0.5.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.5-next.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.18 + +## @backstage/plugin-mcp-actions-backend@0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.1-next.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/catalog-client@1.12.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-mui-to-bui@0.2.3-next.0 + +### Patch Changes + +- e4a1180: Updated tokens from `--bui-bg` to `--bui-bg-surface-0` +- Updated dependencies + - @backstage/ui@0.11.0-next.0 + - @backstage/core-plugin-api@1.12.1 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/theme@0.7.1 + +## @backstage/plugin-scaffolder-backend@3.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.5-next.0 + - @backstage/backend-defaults@0.14.1-next.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration@1.19.0 + - @backstage/types@1.2.2 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-bitbucket-cloud-common@0.3.5 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-events-node@0.4.18 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.16 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.17 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.16 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.16 + - @backstage/plugin-scaffolder-backend-module-github@0.9.3 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.11.0 + - @backstage/plugin-scaffolder-common@1.7.4 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.1-next.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration@1.19.0 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.2 + - @backstage/plugin-scaffolder-node-test-utils@0.3.7-next.0 + +## @backstage/plugin-scaffolder-node-test-utils@0.3.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/backend-test-utils@1.10.3-next.0 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-search-backend@2.0.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.5-next.0 + - @backstage/backend-defaults@0.14.1-next.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-techdocs-backend@2.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.1-next.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration@1.19.0 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-techdocs-node@1.13.10 + +## @backstage/plugin-user-settings-backend@0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.1-next.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-signals-node@0.1.27 + - @backstage/plugin-user-settings-common@0.0.1 + +## example-app@0.2.117-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.11.0-next.0 + - @backstage/cli@0.35.2-next.0 + - @backstage/plugin-mui-to-bui@0.2.3-next.0 + - @backstage/app-defaults@1.7.3 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-app-api@1.19.3 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/frontend-app-api@0.13.3 + - @backstage/integration-react@1.2.13 + - @backstage/theme@0.7.1 + - @backstage/plugin-api-docs@0.13.2 + - @backstage/plugin-auth-react@0.1.22 + - @backstage/plugin-catalog@1.32.1 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-graph@0.5.4 + - @backstage/plugin-catalog-import@0.13.8 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-catalog-unprocessed-entities@0.2.24 + - @backstage/plugin-devtools@0.1.34 + - @backstage/plugin-home@0.8.15 + - @backstage/plugin-kubernetes@0.12.14 + - @backstage/plugin-kubernetes-cluster@0.0.32 + - @backstage/plugin-notifications@0.5.12 + - @backstage/plugin-org@0.6.47 + - @backstage/plugin-permission-react@0.4.39 + - @backstage/plugin-scaffolder@1.35.0 + - @backstage/plugin-scaffolder-react@1.19.4 + - @backstage/plugin-search@1.5.1 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-search-react@1.10.1 + - @backstage/plugin-signals@0.0.26 + - @backstage/plugin-techdocs@1.16.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.31 + - @backstage/plugin-techdocs-react@1.3.6 + - @backstage/plugin-user-settings@0.8.30 + +## example-app-next@0.0.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.11.0-next.0 + - @backstage/cli@0.35.2-next.0 + - @backstage/plugin-app-visualizer@0.1.27-next.0 + - @backstage/app-defaults@1.7.3 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-app-api@1.19.3 + - @backstage/core-compat-api@0.5.5 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/frontend-app-api@0.13.3 + - @backstage/frontend-defaults@0.3.4 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/integration-react@1.2.13 + - @backstage/theme@0.7.1 + - @backstage/plugin-api-docs@0.13.2 + - @backstage/plugin-app@0.3.3 + - @backstage/plugin-auth@0.1.3 + - @backstage/plugin-auth-react@0.1.22 + - @backstage/plugin-catalog@1.32.1 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-graph@0.5.4 + - @backstage/plugin-catalog-import@0.13.8 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-catalog-unprocessed-entities@0.2.24 + - @backstage/plugin-home@0.8.15 + - @backstage/plugin-kubernetes@0.12.14 + - @backstage/plugin-kubernetes-cluster@0.0.32 + - @backstage/plugin-notifications@0.5.12 + - @backstage/plugin-org@0.6.47 + - @backstage/plugin-permission-react@0.4.39 + - @backstage/plugin-scaffolder@1.35.0 + - @backstage/plugin-scaffolder-react@1.19.4 + - @backstage/plugin-search@1.5.1 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-search-react@1.10.1 + - @backstage/plugin-signals@0.0.26 + - @backstage/plugin-techdocs@1.16.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.31 + - @backstage/plugin-techdocs-react@1.3.6 + - @backstage/plugin-user-settings@0.8.30 + +## example-backend@0.0.46-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.1-next.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-app-backend@0.5.9 + - @backstage/plugin-auth-backend@0.25.7 + - @backstage/plugin-auth-backend-module-github-provider@0.4.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.15 + - @backstage/plugin-auth-backend-module-openshift-provider@0.1.3 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-catalog-backend@3.3.1-next.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.10-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.2.17 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.7 + - @backstage/plugin-devtools-backend@0.5.13-next.0 + - @backstage/plugin-events-backend@0.5.10-next.0 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.7 + - @backstage/plugin-kubernetes-backend@0.21.0 + - @backstage/plugin-mcp-actions-backend@0.1.7-next.0 + - @backstage/plugin-notifications-backend@0.6.1 + - @backstage/plugin-permission-backend@0.7.7 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.15 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/plugin-proxy-backend@0.6.9 + - @backstage/plugin-scaffolder-backend@3.1.1-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.9.3 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.17 + - @backstage/plugin-search-backend@2.0.10-next.0 + - @backstage/plugin-search-backend-module-catalog@0.3.11 + - @backstage/plugin-search-backend-module-elasticsearch@1.7.9 + - @backstage/plugin-search-backend-module-explore@0.3.10 + - @backstage/plugin-search-backend-module-techdocs@0.4.9 + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/plugin-signals-backend@0.3.11 + - @backstage/plugin-techdocs-backend@2.1.4-next.0 + +## e2e-test@0.2.36-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.7.8-next.0 + - @backstage/cli-common@0.1.16 + - @backstage/errors@1.2.7 + +## techdocs-cli-embedded-app@0.2.116-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.11.0-next.0 + - @backstage/cli@0.35.2-next.0 + - @backstage/app-defaults@1.7.3 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-app-api@1.19.3 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/integration-react@1.2.13 + - @backstage/test-utils@1.7.14 + - @backstage/theme@0.7.1 + - @backstage/plugin-catalog@1.32.1 + - @backstage/plugin-techdocs@1.16.1 + - @backstage/plugin-techdocs-react@1.3.6 diff --git a/docs/releases/v1.47.0-next.1-changelog.md b/docs/releases/v1.47.0-next.1-changelog.md new file mode 100644 index 0000000000..5c93c23480 --- /dev/null +++ b/docs/releases/v1.47.0-next.1-changelog.md @@ -0,0 +1,648 @@ +# Release v1.47.0-next.1 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.47.0-next.1](https://backstage.github.io/upgrade-helper/?to=1.47.0-next.1) + +## @backstage/backend-defaults@0.14.1-next.1 + +### Patch Changes + +- 3afeab4: Implementing `readTree` for `GoogleGcsReader` +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + +## @backstage/backend-dynamic-feature-service@0.7.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.1-next.1 + - @backstage/plugin-catalog-backend@3.3.1-next.1 + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + +## @backstage/cli@0.35.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + +## @backstage/core-compat-api@0.5.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.5-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## @backstage/create-app@0.7.8-next.1 + +### Patch Changes + +- Bumped create-app version. + +## @backstage/dev-utils@1.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.0 + +## @backstage/frontend-app-api@0.13.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-defaults@0.3.5-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## @backstage/frontend-defaults@0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app@0.3.4-next.0 + - @backstage/frontend-app-api@0.13.4-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## @backstage/frontend-test-utils@0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app@0.3.4-next.0 + - @backstage/frontend-app-api@0.13.4-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## @backstage/integration@1.19.2-next.0 + +### Patch Changes + +- 3afeab4: Implementing `ScmIntegration` for `GoogleGcs` +- 9083273: Rollback the lowercase replacing in GitHub integration config + +## @backstage/integration-react@1.2.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + +## @techdocs/cli@1.10.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.1-next.1 + - @backstage/plugin-techdocs-node@1.13.11-next.0 + +## @backstage/plugin-api-docs@0.13.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.32.2-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## @backstage/plugin-app@0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-react@1.2.14-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## @backstage/plugin-bitbucket-cloud-common@0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + +## @backstage/plugin-catalog@1.32.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-scaffolder-common@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.0 + - @backstage/core-compat-api@0.5.6-next.0 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/plugin-search-react@1.10.1 + +## @backstage/plugin-catalog-backend@3.3.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + +## @backstage/plugin-catalog-backend-module-aws@0.4.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.1-next.1 + - @backstage/integration@1.19.2-next.0 + +## @backstage/plugin-catalog-backend-module-azure@0.3.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.6-next.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + +## @backstage/plugin-catalog-backend-module-gerrit@0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + +## @backstage/plugin-catalog-backend-module-gitea@0.1.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + +## @backstage/plugin-catalog-backend-module-github@0.12.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-github@0.12.1-next.0 + +## @backstage/plugin-catalog-backend-module-gitlab@0.7.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.1-next.1 + - @backstage/integration@1.19.2-next.0 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.7.5-next.0 + +## @backstage/plugin-catalog-graph@0.5.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.5-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## @backstage/plugin-catalog-import@0.13.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## @backstage/plugin-catalog-react@1.21.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-react@1.2.14-next.0 + - @backstage/frontend-test-utils@0.4.3-next.0 + - @backstage/core-compat-api@0.5.6-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## @backstage/plugin-devtools@0.1.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.5.6-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## @backstage/plugin-events-backend-module-github@0.4.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + +## @backstage/plugin-home@0.8.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.5-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## @backstage/plugin-kubernetes@0.12.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.5-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## @backstage/plugin-kubernetes-cluster@0.0.33-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.5-next.0 + +## @backstage/plugin-org@0.6.48-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.5-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## @backstage/plugin-org-react@0.1.46-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.5-next.0 + +## @backstage/plugin-scaffolder@1.35.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-scaffolder-common@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.0 + - @backstage/plugin-scaffolder-react@1.19.5-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## @backstage/plugin-scaffolder-backend@3.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.1-next.1 + - @backstage/integration@1.19.2-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.6-next.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.17-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.18-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.1-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.17-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.17-next.0 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.17-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.9.4-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.11.1-next.0 + - @backstage/plugin-scaffolder-common@1.7.5-next.0 + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.16-next.0 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.1-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.17-next.0 + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.6-next.0 + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.1-next.1 + - @backstage/integration@1.19.2-next.0 + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + +## @backstage/plugin-scaffolder-backend-module-github@0.9.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.11.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.2.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + - @backstage/plugin-scaffolder-node-test-utils@0.3.7-next.1 + +## @backstage/plugin-scaffolder-common@1.7.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + +## @backstage/plugin-scaffolder-node@0.12.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + - @backstage/plugin-scaffolder-common@1.7.5-next.0 + +## @backstage/plugin-scaffolder-node-test-utils@0.3.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + +## @backstage/plugin-scaffolder-react@1.19.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## @backstage/plugin-search@1.5.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.5-next.0 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/plugin-search-react@1.10.1 + +## @backstage/plugin-search-backend-module-techdocs@0.4.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-node@1.13.11-next.0 + +## @backstage/plugin-techdocs@1.16.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.0 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/plugin-search-react@1.10.1 + +## @backstage/plugin-techdocs-addons-test-utils@2.0.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-techdocs@1.16.2-next.0 + - @backstage/plugin-catalog@1.32.2-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.0 + - @backstage/plugin-search-react@1.10.1 + +## @backstage/plugin-techdocs-backend@2.1.4-next.1 + +### Patch Changes + +- b6ff2a5: Some AWS `publisher` config options such as `region`, `endpoint`, `accountId` are now marked as `@visibility backend` instead of `secret`. +- Updated dependencies + - @backstage/backend-defaults@0.14.1-next.1 + - @backstage/integration@1.19.2-next.0 + - @backstage/plugin-techdocs-node@1.13.11-next.0 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.32-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + - @backstage/integration-react@1.2.14-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## @backstage/plugin-techdocs-node@1.13.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + +## @backstage/plugin-user-settings@0.8.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.5-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## example-app@0.2.117-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.35.2-next.1 + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-catalog-import@0.13.9-next.0 + - @backstage/plugin-scaffolder@1.35.1-next.0 + - @backstage/plugin-techdocs@1.16.2-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.32-next.0 + - @backstage/plugin-catalog@1.32.2-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.0 + - @backstage/plugin-scaffolder-react@1.19.5-next.0 + - @backstage/frontend-app-api@0.13.4-next.0 + - @backstage/plugin-api-docs@0.13.3-next.0 + - @backstage/plugin-catalog-graph@0.5.5-next.0 + - @backstage/plugin-org@0.6.48-next.0 + - @backstage/plugin-user-settings@0.8.31-next.0 + - @backstage/plugin-home@0.8.16-next.0 + - @backstage/plugin-kubernetes@0.12.15-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.33-next.0 + - @backstage/plugin-search@1.5.2-next.0 + - @backstage/plugin-search-react@1.10.1 + - @backstage/plugin-devtools@0.1.35-next.0 + +## example-app-next@0.0.31-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.35.2-next.1 + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-catalog-import@0.13.9-next.0 + - @backstage/plugin-scaffolder@1.35.1-next.0 + - @backstage/plugin-techdocs@1.16.2-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.32-next.0 + - @backstage/plugin-app@0.3.4-next.0 + - @backstage/plugin-catalog@1.32.2-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.0 + - @backstage/plugin-scaffolder-react@1.19.5-next.0 + - @backstage/frontend-app-api@0.13.4-next.0 + - @backstage/frontend-defaults@0.3.5-next.0 + - @backstage/core-compat-api@0.5.6-next.0 + - @backstage/plugin-api-docs@0.13.3-next.0 + - @backstage/plugin-catalog-graph@0.5.5-next.0 + - @backstage/plugin-org@0.6.48-next.0 + - @backstage/plugin-user-settings@0.8.31-next.0 + - @backstage/plugin-home@0.8.16-next.0 + - @backstage/plugin-kubernetes@0.12.15-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.33-next.0 + - @backstage/plugin-search@1.5.2-next.0 + - @backstage/plugin-app-visualizer@0.1.27-next.0 + - @backstage/plugin-auth@0.1.3 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/plugin-search-react@1.10.1 + +## example-backend@0.0.46-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.1-next.1 + - @backstage/plugin-techdocs-backend@2.1.4-next.1 + - @backstage/plugin-catalog-backend@3.3.1-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.2.18-next.0 + - @backstage/plugin-scaffolder-backend@3.1.1-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.9.4-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.16-next.0 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.18-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.4.10-next.0 + +## @internal/scaffolder@0.0.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.19.5-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## techdocs-cli-embedded-app@0.2.116-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.35.2-next.1 + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-techdocs@1.16.2-next.0 + - @backstage/plugin-catalog@1.32.2-next.0 diff --git a/docs/releases/v1.47.0-next.2-changelog.md b/docs/releases/v1.47.0-next.2-changelog.md new file mode 100644 index 0000000000..443a981de0 --- /dev/null +++ b/docs/releases/v1.47.0-next.2-changelog.md @@ -0,0 +1,385 @@ +# Release v1.47.0-next.2 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.47.0-next.2](https://backstage.github.io/upgrade-helper/?to=1.47.0-next.2) + +## @backstage/app-defaults@1.7.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + +## @backstage/core-components@0.18.5-next.0 + +### Patch Changes + +- a723b8a: The MarkdownContent component now handles HTML content the same way as GitHub when rendering GitHub-flavored Markdown + +## @backstage/create-app@0.7.8-next.2 + +### Patch Changes + +- f1fe6fe: Updated Dockerfile to use Node 24 and Debian Trixie + +## @backstage/dev-utils@1.1.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/app-defaults@1.7.4-next.0 + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.1 + +## @backstage/frontend-defaults@0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/plugin-app@0.3.4-next.1 + +## @backstage/plugin-api-docs@0.13.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/plugin-catalog@1.32.2-next.1 + - @backstage/plugin-catalog-react@1.21.5-next.1 + +## @backstage/plugin-app@0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/integration-react@1.2.14-next.0 + +## @backstage/plugin-app-visualizer@0.1.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + +## @backstage/plugin-auth@0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + +## @backstage/plugin-auth-react@0.1.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + +## @backstage/plugin-catalog@1.32.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.1 + - @backstage/plugin-search-react@1.10.2-next.0 + - @backstage/plugin-techdocs-react@1.3.7-next.0 + +## @backstage/plugin-catalog-graph@0.5.5-next.1 + +### Patch Changes + +- 5c49a00: Update for the `qs` library bump: the old array limit setting has changed to be more strict; you can no longer just give a zero to mean unlimited. So we choose an arbitrary high value, to at least go higher than the default 20. +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.1 + +## @backstage/plugin-catalog-import@0.13.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.1 + +## @backstage/plugin-catalog-react@1.21.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/integration-react@1.2.14-next.0 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + +## @backstage/plugin-config-schema@0.1.76-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + +## @backstage/plugin-devtools@0.1.35-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + +## @backstage/plugin-home@0.8.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.1 + - @backstage/plugin-home-react@0.1.34-next.0 + +## @backstage/plugin-home-react@0.1.34-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + +## @backstage/plugin-kubernetes@0.12.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.1 + - @backstage/plugin-kubernetes-react@0.5.15-next.0 + +## @backstage/plugin-kubernetes-cluster@0.0.33-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.1 + - @backstage/plugin-kubernetes-react@0.5.15-next.0 + +## @backstage/plugin-kubernetes-react@0.5.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + +## @backstage/plugin-notifications@0.5.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + +## @backstage/plugin-org@0.6.48-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.1 + +## @backstage/plugin-org-react@0.1.46-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.1 + +## @backstage/plugin-scaffolder@1.35.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.1 + - @backstage/plugin-scaffolder-react@1.19.5-next.1 + - @backstage/plugin-techdocs-react@1.3.7-next.0 + +## @backstage/plugin-scaffolder-react@1.19.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.1 + +## @backstage/plugin-search@1.5.2-next.1 + +### Patch Changes + +- 5c49a00: Update for the `qs` library bump: the old array limit setting has changed to be more strict; you can no longer just give a zero to mean unlimited. So we choose an arbitrary high value, to at least go higher than the default 20. +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.1 + - @backstage/plugin-search-react@1.10.2-next.0 + +## @backstage/plugin-search-react@1.10.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + +## @backstage/plugin-signals@0.0.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + +## @backstage/plugin-techdocs@1.16.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-auth-react@0.1.23-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.1 + - @backstage/plugin-search-react@1.10.2-next.0 + - @backstage/plugin-techdocs-react@1.3.7-next.0 + +## @backstage/plugin-techdocs-addons-test-utils@2.0.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-catalog@1.32.2-next.1 + - @backstage/plugin-catalog-react@1.21.5-next.1 + - @backstage/plugin-search-react@1.10.2-next.0 + - @backstage/plugin-techdocs@1.16.2-next.1 + - @backstage/plugin-techdocs-react@1.3.7-next.0 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.32-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-techdocs-react@1.3.7-next.0 + +## @backstage/plugin-techdocs-react@1.3.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + +## @backstage/plugin-user-settings@0.8.31-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.1 + +## example-app@0.2.117-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/plugin-catalog-graph@0.5.5-next.1 + - @backstage/plugin-search@1.5.2-next.1 + - @backstage/app-defaults@1.7.4-next.0 + - @backstage/cli@0.35.2-next.1 + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-api-docs@0.13.3-next.1 + - @backstage/plugin-auth-react@0.1.23-next.0 + - @backstage/plugin-catalog@1.32.2-next.1 + - @backstage/plugin-catalog-import@0.13.9-next.1 + - @backstage/plugin-catalog-react@1.21.5-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.25-next.0 + - @backstage/plugin-devtools@0.1.35-next.1 + - @backstage/plugin-home@0.8.16-next.1 + - @backstage/plugin-kubernetes@0.12.15-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.33-next.1 + - @backstage/plugin-notifications@0.5.13-next.0 + - @backstage/plugin-org@0.6.48-next.1 + - @backstage/plugin-scaffolder@1.35.1-next.1 + - @backstage/plugin-scaffolder-react@1.19.5-next.1 + - @backstage/plugin-search-react@1.10.2-next.0 + - @backstage/plugin-signals@0.0.27-next.0 + - @backstage/plugin-techdocs@1.16.2-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.32-next.1 + - @backstage/plugin-techdocs-react@1.3.7-next.0 + - @backstage/plugin-user-settings@0.8.31-next.1 + +## example-app-next@0.0.31-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/plugin-catalog-graph@0.5.5-next.1 + - @backstage/plugin-search@1.5.2-next.1 + - @backstage/app-defaults@1.7.4-next.0 + - @backstage/cli@0.35.2-next.1 + - @backstage/frontend-defaults@0.3.5-next.1 + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-api-docs@0.13.3-next.1 + - @backstage/plugin-app@0.3.4-next.1 + - @backstage/plugin-app-visualizer@0.1.27-next.1 + - @backstage/plugin-auth@0.1.4-next.0 + - @backstage/plugin-auth-react@0.1.23-next.0 + - @backstage/plugin-catalog@1.32.2-next.1 + - @backstage/plugin-catalog-import@0.13.9-next.1 + - @backstage/plugin-catalog-react@1.21.5-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.25-next.0 + - @backstage/plugin-home@0.8.16-next.1 + - @backstage/plugin-kubernetes@0.12.15-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.33-next.1 + - @backstage/plugin-notifications@0.5.13-next.0 + - @backstage/plugin-org@0.6.48-next.1 + - @backstage/plugin-scaffolder@1.35.1-next.1 + - @backstage/plugin-scaffolder-react@1.19.5-next.1 + - @backstage/plugin-search-react@1.10.2-next.0 + - @backstage/plugin-signals@0.0.27-next.0 + - @backstage/plugin-techdocs@1.16.2-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.32-next.1 + - @backstage/plugin-techdocs-react@1.3.7-next.0 + - @backstage/plugin-user-settings@0.8.31-next.1 + +## app-next-example-plugin@0.0.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + +## techdocs-cli-embedded-app@0.2.116-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/app-defaults@1.7.4-next.0 + - @backstage/cli@0.35.2-next.1 + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-catalog@1.32.2-next.1 + - @backstage/plugin-techdocs@1.16.2-next.1 + - @backstage/plugin-techdocs-react@1.3.7-next.0 + +## @internal/plugin-todo-list@1.0.47-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 diff --git a/docs/releases/v1.47.0-next.3-changelog.md b/docs/releases/v1.47.0-next.3-changelog.md new file mode 100644 index 0000000000..053fef2253 --- /dev/null +++ b/docs/releases/v1.47.0-next.3-changelog.md @@ -0,0 +1,365 @@ +# Release v1.47.0-next.3 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.47.0-next.3](https://backstage.github.io/upgrade-helper/?to=1.47.0-next.3) + +## @backstage/backend-defaults@0.15.0-next.2 + +### Minor Changes + +- 6fc00e6: Added action filtering support with glob patterns and attribute constraints. + + The `ActionsService` now supports filtering actions based on configuration. This allows controlling which actions are exposed to consumers like the MCP backend. + + Configuration example: + + ```yaml + backend: + actions: + pluginSources: + - catalog + - scaffolder + filter: + include: + - id: 'catalog:*' + attributes: + destructive: false + - id: 'scaffolder:*' + exclude: + - id: '*:delete-*' + - attributes: + readOnly: false + ``` + + Filtering logic: + + - `include`: Rules for actions to include. Each rule can specify an `id` glob pattern and/or `attributes` constraints. An action must match at least one rule to be included. If no include rules are specified, all actions are included by default. + - `exclude`: Rules for actions to exclude. Takes precedence over include rules. + - Each rule combines `id` and `attributes` with AND logic (both must match if specified). + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@1.4.0 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-permission-node@0.10.7 + +## @backstage/ui@0.11.0-next.1 + +### Minor Changes + +- 243e5e7: **BREAKING**: Redesigned Table component with new `useTable` hook API. + + - The `Table` component (React Aria wrapper) is renamed to `TableRoot` + - New high-level `Table` component that handles data display, pagination, sorting, and selection + - The `useTable` hook is completely redesigned with a new API supporting three pagination modes (complete, offset, cursor) + - New types: `ColumnConfig`, `TableProps`, `TableItem`, `UseTableOptions`, `UseTableResult` + + New features include unified pagination modes, debounced query changes, stale data preservation during reloads, and row selection with toggle/replace behaviors. + + **Migration guide:** + + 1. Update imports and use the new `useTable` hook: + + ```diff + -import { Table, useTable } from '@backstage/ui'; + -const { data, paginationProps } = useTable({ data: items, pagination: {...} }); + +import { Table, useTable, type ColumnConfig } from '@backstage/ui'; + +const { tableProps } = useTable({ + + mode: 'complete', + + getData: () => items, + +}); + ``` + + 2. Define columns and render with the new Table API: + + ```diff + -
    + - ... + - ... + -
    + - + +const columns: ColumnConfig[] = [ + + { id: 'name', label: 'Name', isRowHeader: true, cell: item => }, + + { id: 'type', label: 'Type', cell: item => }, + +]; + + + + + ``` + + Affected components: Table, TableRoot, TablePagination + +- 95246eb: **Breaking** Updating color tokens to match the new neutral style on different surfaces. + + ## Migration notes + + There's no direct replacement for the old tint tokens but you can use the new neutral set of color tokens on surface 0 or 1 as a replacement. + + - `--bui-bg-tint` can be replaced by `--bui-bg-neutral-on-surface-0` + - `--bui-bg-tint-hover` can be replaced by `--bui-bg-neutral-on-surface-0-hover` + - `--bui-bg-tint-pressed` can be replaced by `--bui-bg-neutral-on-surface-0-pressed` + - `--bui-bg-tint-disabled` can be replaced by `--bui-bg-neutral-on-surface-0-disabled` + +- ea0c6d8: Introduce new `ToggleButton` & `ToggleButtonGroup` components in Backstage UI + +### Patch Changes + +- 21c87cc: Fixes disabled state in primary and secondary buttons in Backstage UI. +- b3253b6: Fixed `Link` component causing hard page refreshes for internal routes. The component now properly uses React Router's navigation instead of full page reloads. + +## @backstage/plugin-auth-backend@0.26.0-next.0 + +### Minor Changes + +- 7ffc873: Fix `user_created_at` migration causing `SQLiteError` regarding use of non-constants for defaults + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + +## @backstage/plugin-events-backend-module-kafka@0.3.0-next.0 + +### Minor Changes + +- ef5bbd8: Add support for Kafka offset configuration (`fromBeginning`) and `autoCommit` + +## @backstage/plugin-home@0.9.0-next.2 + +### Minor Changes + +- e091a83: Widget configurations are now only saved to storage when the Save button is explicitly clicked. Added a Cancel button that allows users to discard unsaved changes and revert to the last saved state. + +## @backstage/backend-dynamic-feature-service@0.7.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-catalog-backend@3.3.1-next.1 + - @backstage/plugin-events-backend@0.5.10-next.0 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/plugin-search-backend-node@1.4.0 + +## @backstage/backend-test-utils@1.10.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + - @backstage/backend-app-api@1.4.0 + - @backstage/plugin-auth-node@0.6.10 + +## @backstage/create-app@0.7.8-next.3 + +### Patch Changes + +- Bumped create-app version. + +## @techdocs/cli@1.10.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + +## @backstage/plugin-api-docs@0.13.3-next.2 + +### Patch Changes + +- 0216090: Updated dependency `@types/swagger-ui-react` to `^5.0.0`. +- abeba2b: Fix types with new bumped dependency +- Updated dependencies + - @backstage/plugin-catalog@1.32.2-next.2 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.26.0-next.0 + - @backstage/plugin-auth-node@0.6.10 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.4.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.26.0-next.0 + - @backstage/plugin-auth-node@0.6.10 + +## @backstage/plugin-catalog@1.32.2-next.2 + +### Patch Changes + +- 7ca91e8: Header in EntityLayout should always be shown. + Monitoring the loading status caused flickering when the refresh() method of the Async Entity was invoked. + +## @backstage/plugin-catalog-backend-module-aws@0.4.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + +## @backstage/plugin-catalog-backend-module-github@0.12.1-next.1 + +### Patch Changes + +- cb4b907: Improved efficiency of `GithubOrgEntityProvider` membership event handling and edit team. The provider now fetches only the specific user's teams instead of all organization users when processing membership events, and uses `addEntitiesOperation` instead of `replaceEntitiesOperation` to avoid unnecessary entity deletions. + +## @backstage/plugin-catalog-backend-module-gitlab@0.7.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + - @backstage/plugin-catalog-backend@3.3.1-next.1 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.18-next.0 + +### Patch Changes + +- a307700: Fixed crash when `latest_report_status` is undefined + +## @backstage/plugin-devtools-backend@0.5.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + - @backstage/plugin-permission-node@0.10.7 + +## @backstage/plugin-mcp-actions-backend@0.1.7-next.1 + +### Patch Changes + +- 4d82a35: build(deps): bump `@modelcontextprotocol/sdk` from 1.24.3 to 1.25.2 +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + +## @backstage/plugin-scaffolder@1.35.1-next.2 + +### Patch Changes + +- 9d75495: Fixed bug in RepoUrlPickerComponent component where repository names were not being autocompleted. + +## @backstage/plugin-scaffolder-backend@3.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + - @backstage/plugin-scaffolder-backend-module-github@0.9.4-next.1 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-permission-node@0.10.7 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + +## @backstage/plugin-scaffolder-backend-module-github@0.9.4-next.1 + +### Patch Changes + +- bb7088b: Added options to set [workflow access level][access-level] for repositories to `github:repo:create` + + This is useful when creating repositories for GitHub Actions to manage access + to the workflows during creation. + + ```diff + - action: github:repo:create + id: create-repo + input: + repoUrl: github.com?owner=owner&repo=repo + visibility: private + + workflowAccess: organization + ``` + + [access-level]: https://docs.github.com/en/rest/actions/permissions?apiVersion=2022-11-28#set-the-level-of-access-for-workflows-outside-of-the-repository + +## @backstage/plugin-search-backend@2.0.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/plugin-search-backend-node@1.4.0 + +## @backstage/plugin-techdocs-backend@2.1.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + +## @backstage/plugin-user-settings-backend@0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + - @backstage/plugin-auth-node@0.6.10 + +## example-app@0.2.117-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.11.0-next.1 + - @backstage/plugin-catalog@1.32.2-next.2 + - @backstage/plugin-home@0.9.0-next.2 + - @backstage/plugin-api-docs@0.13.3-next.2 + - @backstage/plugin-scaffolder@1.35.1-next.2 + - @backstage/cli@0.35.2-next.1 + +## example-app-next@0.0.31-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.11.0-next.1 + - @backstage/plugin-catalog@1.32.2-next.2 + - @backstage/plugin-home@0.9.0-next.2 + - @backstage/plugin-api-docs@0.13.3-next.2 + - @backstage/plugin-scaffolder@1.35.1-next.2 + - @backstage/cli@0.35.2-next.1 + +## example-backend@0.0.46-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + - @backstage/plugin-mcp-actions-backend@0.1.7-next.1 + - @backstage/plugin-auth-backend@0.26.0-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.9.4-next.1 + - @backstage/plugin-app-backend@0.5.9 + - @backstage/plugin-auth-backend-module-github-provider@0.4.0 + - @backstage/plugin-auth-backend-module-openshift-provider@0.1.3 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-catalog-backend@3.3.1-next.1 + - @backstage/plugin-devtools-backend@0.5.13-next.1 + - @backstage/plugin-events-backend@0.5.10-next.0 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.7 + - @backstage/plugin-kubernetes-backend@0.21.0 + - @backstage/plugin-notifications-backend@0.6.1 + - @backstage/plugin-permission-backend@0.7.7 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/plugin-proxy-backend@0.6.9 + - @backstage/plugin-scaffolder-backend@3.1.1-next.2 + - @backstage/plugin-search-backend@2.0.10-next.1 + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/plugin-signals-backend@0.3.11 + - @backstage/plugin-techdocs-backend@2.1.4-next.2 diff --git a/docs/releases/v1.47.0.md b/docs/releases/v1.47.0.md new file mode 100644 index 0000000000..20ca86fff7 --- /dev/null +++ b/docs/releases/v1.47.0.md @@ -0,0 +1,128 @@ +--- +id: v1.47.0 +title: v1.47.0 +description: Backstage Release v1.47.0 +--- + +These are the release notes for the v1.47.0 release of [Backstage](https://backstage.io/). + +A huge thanks to the whole team of maintainers and contributors as well as the amazing Backstage Community for the hard work in getting this release developed and done. + +## Highlights + +### BREAKING: Redesigned `Table` and `useTable` in `@backstage/ui` + +The `Table` component and accompanying `useTable` hook have been redesigned. The low-level React Aria wrapper `Table` is now `TableRoot`, while a higher level `Table` component now handles pagination, sorting, selection, and data display. The `useTable` also has a new API, that supports 3 pagination modes: `complete`, `offset`, and `cursor`. + +More information along with a migration guide can be found in the [BUI Changelog](https://ui.backstage.io/changelog). + +### BREAKING: Updated color tokens in `@backstage/ui` + +UI color tokens have been updated to align with the new neutral surface-based design system, and the `-tint` tokens have been removed. + +More information can be found in the [BUI Changelog](https://ui.backstage.io/changelog). + +### BREAKING: Redirect validation in the URL reader + +`coreServices.urlReader` now validates that redirect chains are subject to the allow list in `backend.reading.allow` of your app config. If you were relying on redirects that pointed to URLs that were not allowlisted, you will now have to add those to your config as well. + +To support this, the `FetchUrlReader` class no longer has a public constructor, but instead has a `fromConfig` static factory method. + +### BREAKING: Better AWS S3 auth handling in techdocs + +Techdocs now knows how to properly use `integrations.awsS3` config to authenticate with AWS S3. Since this affects the priority order of which auth setting is being used, it’s technically a breaking change. + +The new priority is: + +1. `aws.accounts` +2. `techdocs.publisher.awsS3.credentials` +3. `integrations.awsS3` +4. Default credential chain + +### Deprecation and upcoming restrictions of blueprints for app customization in `@backstage/frontend-plugin-api` + +There were several blueprints in `@backstage/frontend-plugin-api` that were intended only for use in customization of the central app. For example `SignInPageBlueprint` that replaces the sign-in page, or `NavContentBlueprint` that replaces the sidebar content. The following blueprints have all been deprecated and moved to a new `@backstage/plugin-app-react` package: + +- `IconBundleBlueprint` +- `NavContentBlueprint` +- `RouterBlueprint` +- `SignInPageBlueprint` +- `SwappableComponentBlueprint` +- `ThemeBlueprint` +- `TranslationBlueprint` + +These blueprints are also being restricted from use outside of the `app` plugin. This means that you have to either use an `app` plugin override or a module with `pluginId` set to `app`. These currently trigger deprecation warnings, but will instead be rejected in a future release. + +### Deprecations and upcoming restrictions for API factories in new frontend system + +Similar to the above change, restrictions are being introduced that limit the ability for plugins to override both core Utility APIs and API factories from other plugins. Plugins will be restricted from installing factory overrides for Utility APIs that belong to other plugins. This restriction also applies to the core APIs provided by the app plugin. These overrides now instead need to be made using a plugin override or module. + +Just like the blueprint changes, this release will only trigger deprecation warnings, with the change taking effect in a future release. + +### Backend action filtering support + +`ActionsService` in `@backstage/backend-defaults` now supports action filtering based on configuration to enable control over which actions are exposed to consumers like the MCP backend. + +At the time of writing, this is primarily documented in [the relevant configuration schema](https://github.com/backstage/backstage/blob/master/packages/backend-defaults/config.d.ts). + +### `scaffolder` now supports configuration for `workflowAccess` level for creating Github repositories + +Workflow files can now configure the level of access to Github Actions for workflows outside of a repository. + +This is useful when creating repositories for GitHub Actions to manage access to the workflows for `github:repo:create`. + +```diff + - action: github:repo:create + id: create-repo + input: + repoUrl: github.com?owner=owner&repo=repo + visibility: private ++ workflowAccess: organization +``` + +Github Actions access-level documentation can be found [here](https://docs.github.com/en/rest/actions/permissions?apiVersion=2022-11-28#set-the-level-of-access-for-workflows-outside-of-the-repository). + +Contributed by [@fearphage](https://github.com/fearphage) in [#32237](https://github.com/backstage/backstage/pull/32237) + +### Additional Kafka Events streaming settings + +The Kafka event module now supports `fromBeginning` offsets and `autoCommit`. + +Contributed by [@imod](https://github.com/imod) in [#31410](https://github.com/backstage/backstage/pull/31410) + +### Improvements to the `home` plugin UI + +Widget configuration changes are now only saved when the Save button is explicitly clicked. A new Cancel button has been added to enable users to discard unsaved changes + +Contributed by [@kmikko](https://github.com/kmikko) in [#31198](https://github.com/backstage/backstage/pull/31198) + +### Improved performance for `GithubOrgEntityProvider` + +`GithubOrgEntityProvider` membership event handling and edit team has been improved. The provider now fetches only the specific user's teams instead of all organization users when processing membership events, and uses `addEntitiesOperation` instead of `replaceEntitiesOperation` to avoid unnecessary entity deletions. + +Contributed by [@angeliski](https://github.com/angeliski) in [#32184](https://github.com/backstage/backstage/pull/32184) + +### `Link` component now uses React Router’s navigation system + +Previously the `Link` component would cause hard page refreshes for internal routes. With this update, the component now properly uses React Router’s navigation instead of full page reloads. + +## Security Fixes + +This release contains security fixes for Software Templates and reading external content. + +## Upgrade path + +We recommend that you keep your Backstage project up to date with this latest release. For more guidance on how to upgrade, check out the documentation for [keeping Backstage updated](https://backstage.io/docs/getting-started/keeping-backstage-updated). + +## Links and References + +Below you can find a list of links and references to help you learn about and start using this new release. + +- [Backstage official website](https://backstage.io/), [documentation](https://backstage.io/docs/), and [getting started guide](https://backstage.io/docs/getting-started/) +- [GitHub repository](https://github.com/backstage/backstage) +- Backstage's [versioning and support policy](https://backstage.io/docs/overview/versioning-policy) +- [Community Discord](https://discord.gg/backstage-687207715902193673) for discussions and support +- [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.47.0-changelog.md) +- Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) + +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.48.0-changelog.md b/docs/releases/v1.48.0-changelog.md new file mode 100644 index 0000000000..19a21dd4b5 --- /dev/null +++ b/docs/releases/v1.48.0-changelog.md @@ -0,0 +1,3182 @@ +# Release v1.48.0 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.48.0](https://backstage.github.io/upgrade-helper/?to=1.48.0) + +## @backstage/backend-app-api@1.5.0 + +### Minor Changes + +- f1d29b4: Added support for extension point factories, along with the ability to report module startup failures via the extension point factory context. + +### Patch Changes + +- 6bb2f21: Fixed memory leak by properly cleaning up process event listeners on backend shutdown. +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + +## @backstage/backend-plugin-api@1.7.0 + +### Minor Changes + +- f1d29b4: Added support for extension point factories. This makes it possible to call `registerExtensionPoint` with a single options argument and provide a factory for the extension point rather than a direct implementation. The factory is passed a context with a `reportModuleStartupFailure` method that makes it possible for plugins to report and attribute startup errors to the module that consumed the extension point. +- bb9b471: Plugin IDs that do not match the standard format are deprecated (letters, digits, and dashes only, starting with a letter). Plugin IDs that do no match this format will be rejected in a future release. + + In addition, plugin IDs that don't match the legacy pattern that also allows underscores, with be rejected. + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/cli-common@0.1.18 + - @backstage/plugin-auth-node@0.6.13 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.10 + +## @backstage/backend-test-utils@1.11.0 + +### Minor Changes + +- 42abfb1: Added `createServiceMock`, a public utility for creating `ServiceMock` instances for custom service refs. This allows plugin authors to define mock creators for their own services following the same pattern as the built-in `mockServices` mocks. + +### Patch Changes + +- f1d29b4: Updated `startTestBackend` to support factory-based extension points (v1.1 format) in addition to the existing direct implementation format. +- 7455dae: Use node prefix on native imports +- 68eb322: Added `@types/jest` as an optional peer dependency, since jest types are exposed in the public API surface. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-app-api@1.5.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/backend-defaults@0.15.2 + - @backstage/plugin-auth-node@0.6.13 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-events-node@0.4.19 + +## @backstage/catalog-client@1.13.0 + +### Minor Changes + +- b4e8249: Implemented support for the new `queryLocations` and `streamLocations` that allow paginated/streamed and filtered location queries + +### Patch Changes + +- 9cf6762: Improved the `InMemoryCatalogClient` test utility to support ordering, pagination, full-text search, and field projection for entity query methods. Also fixed `getEntityFacets` to correctly handle multi-valued fields. +- Updated dependencies + - @backstage/filter-predicates@0.1.0 + +## @backstage/filter-predicates@0.1.0 + +### Minor Changes + +- 7feb83b: Introduced package, basically as the extracted predicate types from `@backstage/plugin-catalog-react/alpha` + +## @backstage/frontend-app-api@0.15.0 + +### Minor Changes + +- 55b2ef6: **BREAKING**: Updated the behavior of the new API override logic to reject the override and block app startup instead of just logging a deprecation warning. + +### Patch Changes + +- 7edb810: Implemented support for the `internal` extension input option. + +- 492503a: Updated error reporting and app tree resolution logic to attribute errors to the correct extension and allow app startup to proceed more optimistically: + + - If an attachment fails to provide the required input data, the error is now attributed to the attachment rather than the parent extension. + - Singleton extension inputs will now only forward attachment errors if the input is required. + - Array extension inputs will now filter out failed attachments instead of failing the entire app tree resolution. + +- ef6916e: Added `IconElement` type as a replacement for the deprecated `IconComponent`. The `IconsApi` now has a new `icon()` method that returns `IconElement`, while the existing `getIcon()` method is deprecated. The `IconBundleBlueprint` now accepts both `IconComponent` and `IconElement` values. + +- 122d39c: Completely removed support for the deprecated `app.experimental.packages` configuration. Replace existing usage directly with `app.packages`. + +- 9554c36: **DEPRECATED**: Deprecated support for multiple attachment points. + +- 53b6549: Plugins in the new frontend system now have a `pluginId` field rather than `id` to better align with naming conventions used throughout the frontend and backend systems. The old field is still present but marked as deprecated. All internal code has been updated to prefer `pluginId` while maintaining backward compatibility by falling back to `id` when needed. + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. + +- 09032d7: Internal update to simplify testing utility implementations. + +- 69d880e: Bump to latest zod to ensure it has the latest features + +- Updated dependencies + - @backstage/frontend-defaults@0.4.0 + - @backstage/core-app-api@1.19.5 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/version-bridge@1.0.12 + +## @backstage/frontend-defaults@0.4.0 + +### Minor Changes + +- 55b2ef6: **BREAKING**: The `API_FACTORY_CONFLICT` warning is now treated as an error and will prevent the app from starting. + +### Patch Changes + +- 122d39c: Completely removed support for the deprecated `app.experimental.packages` configuration. Replace existing usage directly with `app.packages`. +- c38b74d: Dependency update for tests. +- 53b6549: Plugins in the new frontend system now have a `pluginId` field rather than `id` to better align with naming conventions used throughout the frontend and backend systems. The old field is still present but marked as deprecated. All internal code has been updated to prefer `pluginId` while maintaining backward compatibility by falling back to `id` when needed. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/frontend-app-api@0.15.0 + - @backstage/core-components@0.18.7 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/plugin-app@0.4.0 + +## @backstage/frontend-plugin-api@0.14.0 + +### Minor Changes + +- ef6916e: Added `IconElement` type as a replacement for the deprecated `IconComponent`. The `IconsApi` now has a new `icon()` method that returns `IconElement`, while the existing `getIcon()` method is deprecated. The `IconBundleBlueprint` now accepts both `IconComponent` and `IconElement` values. + +- bb9b471: Plugin IDs that do not match the standard format are deprecated (letters, digits, and dashes only, starting with a letter). Plugin IDs that do no match this format will be rejected in a future release. + +- ef6916e: Added `SubPageBlueprint` for creating sub-page tabs, `PluginHeaderActionBlueprint` and `PluginHeaderActionsApi` for plugin-scoped header actions, and `PageLayout` as a swappable component. The `PageBlueprint` now supports sub-pages with tabbed navigation, page title, icon, and header actions. Plugins can now specify a `title` and `icon` in `createFrontendPlugin`. + +- c38b74d: **BREAKING**: The following blueprints have been removed and are now only available from `@backstage/plugin-app-react`: + + - `IconBundleBlueprint` + - `NavContentBlueprint` + - `RouterBlueprint` + - `SignInPageBlueprint` + - `SwappableComponentBlueprint` + - `ThemeBlueprint` + - `TranslationBlueprint` + +- 10ebed4: **BREAKING**: Removed type support for multiple attachment points in the `ExtensionDefinitionAttachTo` type. Extensions can no longer specify an array of attachment points in the `attachTo` property. + + The runtime still supports multiple attachment points for backward compatibility with existing compiled code, but new code will receive type errors if attempting to use this pattern. + + Extensions that previously used multiple attachment points should migrate to using a Utility API pattern instead. See the [Sharing Extensions Across Multiple Locations](https://backstage.io/docs/frontend-system/architecture/27-sharing-extensions) guide for the recommended approach. + +### Patch Changes + +- 7edb810: Added a new `internal` option to `createExtensionInput` that marks the input as only allowing attachments from the same plugin. + +- 9554c36: **DEPRECATED**: Multiple attachment points for extensions have been deprecated. The functionality continues to work for backward compatibility, but will log a deprecation warning and be removed in a future release. + + Extensions using array attachment points should migrate to using Utility APIs instead. See the [Sharing Extensions Across Multiple Locations](https://backstage.io/docs/frontend-system/architecture/27-sharing-extensions) guide for the recommended pattern. + +- 53b6549: Plugins in the new frontend system now have a `pluginId` field rather than `id` to better align with naming conventions used throughout the frontend and backend systems. The old field is still present but marked as deprecated. All internal code has been updated to prefer `pluginId` while maintaining backward compatibility by falling back to `id` when needed. + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. + +- 69d880e: Bump to latest zod to ensure it has the latest features + +- Updated dependencies + - @backstage/version-bridge@1.0.12 + +## @backstage/frontend-test-utils@0.5.0 + +### Minor Changes + +- 09a6aad: **BREAKING**: Removed the `TestApiRegistry` class, use `TestApiProvider` directly instead, storing reused APIs in a variable, e.g. `const apis = [...] as const`. +- d2ac2ec: Added `MockAlertApi` and `MockFeatureFlagsApi` implementations to the `mockApis` namespace. The mock implementations include useful testing methods like `clearAlerts()`, `waitForAlert()`, `getState()`, `setState()`, and `clearState()` for better test ergonomics. +- 09a6aad: **BREAKING**: The `mockApis` namespace is no longer a re-export from `@backstage/test-utils`. It's now a standalone namespace with mock implementations of most core APIs. Mock API instances can be passed directly to `TestApiProvider`, `renderInTestApp`, and `renderTestApp` without needing `[apiRef, impl]` tuples. As part of this change, the `.factory()` method on some mocks has been removed, since it's now redundant. + + ```tsx + // Before + import { mockApis } from '@backstage/frontend-test-utils'; + + renderInTestApp(, { + apis: [[identityApiRef, mockApis.identity()]], + }); + + // After - mock APIs can be passed directly + renderInTestApp(, { + apis: [mockApis.identity()], + }); + ``` + + This change also adds `createApiMock`, a public utility for creating mock API factories, intended for plugin authors to create their own `.mock()` variants. + +### Patch Changes + +- 22864b7: Added an `apis` option to `createExtensionTester`, `renderInTestApp`, and `renderTestApp` to override APIs when testing extensions. Use the `mockApis` helpers to create mock implementations: + + ```typescript + import { identityApiRef } from '@backstage/frontend-plugin-api'; + import { mockApis } from '@backstage/frontend-test-utils'; + + // Override APIs in createExtensionTester + const tester = createExtensionTester(myExtension, { + apis: [ + [ + identityApiRef, + mockApis.identity({ userEntityRef: 'user:default/guest' }), + ], + ], + }); + + // Override APIs in renderInTestApp + renderInTestApp(, { + apis: [ + [ + identityApiRef, + mockApis.identity({ userEntityRef: 'user:default/guest' }), + ], + ], + }); + + // Override APIs in renderTestApp + renderTestApp({ + extensions: [myExtension], + apis: [ + [ + identityApiRef, + mockApis.identity({ userEntityRef: 'user:default/guest' }), + ], + ], + }); + ``` + +- 15ed3f9: Added `snapshot()` method to `ExtensionTester`, which returns a tree-shaped representation of the resolved extension hierarchy. Convenient to use with `toMatchInlineSnapshot()`. + +- 013ec22: Added `mountedRoutes` option to `renderTestApp` for binding route refs to paths, matching the existing option in `renderInTestApp`: + + ```typescript + renderTestApp({ + extensions: [...], + mountedRoutes: { + '/my-path': myRouteRef, + }, + }); + ``` + +- d7dd5bd: Fixed Router deprecation warning and switched to using new `RouterBlueprint` from `@backstage/plugin-app-api`. + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. + +- 69d880e: Bump to latest zod to ensure it has the latest features + +- Updated dependencies + - @backstage/frontend-app-api@0.15.0 + - @backstage/core-app-api@1.19.5 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/plugin-app-react@0.2.0 + - @backstage/plugin-app@0.4.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-permission-react@0.4.40 + - @backstage/version-bridge@1.0.12 + - @backstage/test-utils@1.7.15 + - @backstage/plugin-permission-common@0.9.6 + +## @backstage/integration@1.20.0 + +### Minor Changes + +- 6999f6d: The AzureUrl class in the @backstage/integration package is now able to process BOTH git branches and git tags. Initially this class only processed git branches and threw an error when non-branch Azure URLs were passed in. + +### Patch Changes + +- cc6206e: Added support for `{org}.visualstudio.com` domains used by Azure DevOps +- 7455dae: Use node prefix on native imports + +## @backstage/module-federation-common@0.1.0 + +### Minor Changes + +- ce12dec: Added new `@backstage/module-federation-common` package that provides shared types, default configurations, and runtime utilities for module federation. It includes `loadModuleFederationHostShared` for loading shared dependencies in parallel at runtime, `defaultHostSharedDependencies` and `defaultRemoteSharedDependencies` for consistent dependency configuration, and types such as `HostSharedDependencies`, `RemoteSharedDependencies`, and `RuntimeSharedDependenciesGlobal`. + +## @backstage/ui@0.12.0 + +### Minor Changes + +- 46a9adc: **BREAKING**: Alert no longer accepts a `surface` prop + + The Alert component's background is now driven entirely by its `status` prop. The `surface` prop has been removed. + + ```diff + - + + + ``` + + **Affected components:** Alert + +- b63c25b: **BREAKING**: Removed gray scale tokens and renamed background surface tokens to neutral tokens + + The `--bui-gray-1` through `--bui-gray-8` tokens have been removed. The `--bui-bg-surface-*` and `--bui-bg-neutral-on-surface-*` tokens have been replaced by a unified `--bui-bg-neutral-*` scale. + + **Migration:** + + Replace surface tokens directly: + + ```diff + - background: var(--bui-bg-surface-0); + + background: var(--bui-bg-neutral-0); + ``` + + Replace on-surface tokens shifted by +1: + + ```diff + - background: var(--bui-bg-neutral-on-surface-0); + + background: var(--bui-bg-neutral-1); + ``` + + Replace gray tokens 1-4 with neutral equivalents (`--bui-gray-5` through `--bui-gray-8` have no direct replacement): + + ```diff + - background: var(--bui-gray-1); + + background: var(--bui-bg-neutral-1); + ``` + +- 7898df0: **BREAKING**: Replaced `Surface` / `onSurface` system with new provider/consumer background system + + The old `Surface` type (`'0'`–`'3'`, `'auto'`) and its associated props (`surface`, `onSurface`) have been replaced by a provider/consumer `bg` architecture. + + **Types:** + + - `ContainerBg` — `'neutral-1'` \| `'neutral-2'` \| `'neutral-3'` \| `'danger'` \| `'warning'` \| `'success'` + - `ProviderBg` — `ContainerBg | 'neutral-auto'` + + Consumer components (e.g. Button) inherit the parent's `bg` via `data-on-bg`, and CSS handles the visual step-up. See "Neutral level capping" below for details on how levels are bounded. + + **Hooks:** + + - `useBgProvider(bg?)` — for provider components. Returns `{ bg: undefined }` when no `bg` is given (transparent). Supports `'neutral-auto'` to auto-increment from the parent context. + - `useBgConsumer()` — for consumer components. Returns the parent container's `bg` unchanged. + + **Component roles:** + + - **Provider-only** (Box, Flex, Grid): set `data-bg`, wrap children in `BgProvider`. **Transparent by default** — they do _not_ auto-increment; pass `bg="neutral-auto"` explicitly if you want automatic neutral stepping. + - **Consumer-only** (Button, ButtonIcon, ButtonLink): set `data-on-bg`, inherit the parent container's `bg` unchanged. + - **Provider + Consumer** (Card): sets both `data-bg` and `data-on-bg`, wraps children. Card passes `bg="neutral-auto"` to its inner Box, so it auto-increments from the parent context. + + **Neutral level capping:** + + Provider components cap at `neutral-3`. There is no `neutral-4` prop value. The `neutral-4` level exists only in consumer component CSS — for example, a Button sitting on a `neutral-3` surface uses `neutral-4` tokens internally via `data-on-bg`. + + **Migration:** + + Rename the `surface` prop to `bg` on provider components and update values: + + ```diff + - + + + + - + + + + - + + + + - + + + ``` + + Remove `onSurface` from consumer components — they now always inherit from the parent container: + + ```diff + - + + + ``` + + **Affected components:** Button + +- 110fec0: **BREAKING**: Removed link and tint color tokens, added new status foreground tokens, and improved Link component styling + + The following color tokens have been removed: + + - `--bui-fg-link` (and all related tokens: `-hover`, `-pressed`, `-disabled`) + - `--bui-fg-tint` (and all related tokens: `-hover`, `-pressed`, `-disabled`) + - `--bui-bg-tint` (and all related tokens: `-hover`, `-pressed`, `-disabled`) + - `--bui-border-tint` (and all related tokens) + + **New Status Tokens:** + + Added dedicated tokens for status colors that distinguish between usage on status backgrounds vs. standalone usage: + + - `--bui-fg-danger-on-bg` / `--bui-fg-danger` + - `--bui-fg-warning-on-bg` / `--bui-fg-warning` + - `--bui-fg-success-on-bg` / `--bui-fg-success` + - `--bui-fg-info-on-bg` / `--bui-fg-info` + + The `-on-bg` variants are designed for text on colored backgrounds, while the base variants are for standalone status indicators with improved visibility and contrast. + + **Migration:** + + For link colors, migrate to one of the following alternatives: + + ```diff + .custom-link { + - color: var(--bui-fg-link); + + color: var(--bui-fg-info); /* For informational links */ + + /* or */ + + color: var(--bui-fg-primary); /* For standard text links */ + } + ``` + + For tint colors (backgrounds, foregrounds, borders), migrate to appropriate status or neutral colors: + + ```diff + .info-section { + - background: var(--bui-bg-tint); + + background: var(--bui-bg-info); /* For informational sections */ + + /* or */ + + background: var(--bui-bg-neutral-1); /* For neutral emphasis */ + } + ``` + + If you're using status foreground colors on colored backgrounds, update to the new `-on-bg` tokens: + + ```diff + .error-badge { + - color: var(--bui-fg-danger); + + color: var(--bui-fg-danger-on-bg); + background: var(--bui-bg-danger); + } + ``` + + **Affected components:** Link + +### Patch Changes + +- 644e303: Added a new `FullPage` component that fills the remaining viewport height below the `PluginHeader`. + + ```tsx + + + {/* content fills remaining height */} + + ``` + + **Affected components:** FullPage + +- 44877e4: Fixed dark theme `--bui-fg-secondary` and `--bui-fg-disabled` tokens using black-based `oklch(0% ...)` instead of white-based `oklch(100% ...)`, making secondary and disabled text visible on dark backgrounds. + +- 350c948: Fixed Box component to forward HTML attributes to the underlying div element. + + **Affected components:** Box + +- 7455dae: Use node prefix on native imports + +- c8ae765: Fixed nested Accordion icon state issue where the inner accordion's arrow icon would incorrectly show as expanded when only the outer accordion was expanded. The CSS selector now uses a direct parent selector to ensure the icon only responds to its own accordion's expanded state. + + Affected components: Accordion + +- 4d1b7f4: Fixed CSS Module syntax to comply with Next.js 16 Turbopack validation by flattening nested dark theme selectors. + + **Affected components:** Popover, Tooltip + +- 2c219b9: Added `destructive` prop to Button for dangerous actions like delete or remove. Works with all variants (primary, secondary, tertiary). + + **Affected components:** Button + +- 5af9e14: Fixed `useDefinition` hook adding literal "undefined" class name when no className prop was passed. + +- 5c76d13: Allow `ref` as a prop on the `Tag` component + + Affected components: Tag + +- ab25658: Cleaned up `useDefinition` `ownProps` types to remove never-typed ghost properties from autocomplete. + +- 741a98d: Allow data to be passed directly to the `useTable` hook using the property `data` instead of `getData()` for mode `"complete"`. + + This simplifies usage as data changes, rather than having to perform a `useEffect` when data changes, and then reloading the data. It also happens immediately, so stale data won't remain until a rerender (with an internal async state change), so less flickering. + + Affected components: Table + +- a0fe1b2: Fixed changing columns after first render from crashing. It now renders the table with the new column layout as columns change. + + Affected components: Table + +- 508bd1a: Added new `Alert` component with support for status variants (info, success, warning, danger), icons, loading states, and custom actions. + + Updated status color tokens for improved contrast and consistency across light and dark themes: + + - Added new `--bui-bg-info` and `--bui-fg-info` tokens for info status + - Updated `--bui-bg-danger`, `--bui-fg-danger` tokens + - Updated `--bui-bg-warning`, `--bui-fg-warning` tokens + - Updated `--bui-bg-success`, `--bui-fg-success` tokens + + **Affected components**: Alert + +- da30862: Fixed client-side navigation for container components by wrapping the container (not individual items) in RouterProvider. Components now conditionally provide routing context only when children have internal links, removing the Router context requirement when not needed. This also removes the need to wrap these components in MemoryRouter during tests when they are not using the `href` prop. + + Additionally, when multiple tabs match the current URL via prefix matching, the tab with the most specific path (highest segment count) is now selected. For example, with URL `/catalog/users/john`, a tab with path `/catalog/users` is now selected over a tab with path `/catalog`. + + Affected components: Tabs, Tab, TagGroup, Tag, Menu, MenuItem, MenuAutocomplete + +- 092c453: Fixed an infinite render loop in Tabs when navigating to a URL that doesn't match any tab `href`. + +- becf851: export PasswordField component + +- becee36: Migrated Accordion components to use `useDefinition` instead of `useStyles`, and added automatic background adaptation based on parent container context. + +- 5320aa8: Fixed components to not require a Router context when rendering without internal links. + + Affected components: Link, ButtonLink, Row + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. + +- 8c39412: The Table component now wraps the react-aria-components `Table` with a `ResizableTableContainer` only if any column has a width property set. This means that column widths can adapt to the content otherwise (if no width is explicitly set). + + Affected components: Table + +- cb090b4: Bump react-aria-components to v1.14.0 + +- c429101: Fixed React 17 compatibility by using `useId` from `react-aria` instead of the built-in React hook which is only available in React 18+. + +- 74c5a76: Fixed Switch component disabled state styling to show `not-allowed` cursor and disabled text color. + + **Affected components:** Switch + +- 20131c5: Migrated to use the standard `backstage-cli package build` for CSS bundling instead of a custom build script. + +- Updated dependencies + - @backstage/version-bridge@1.0.12 + +## @backstage/plugin-app@0.4.0 + +### Minor Changes + +- ef6916e: Added `SubPageBlueprint` for creating sub-page tabs, `PluginHeaderActionBlueprint` and `PluginHeaderActionsApi` for plugin-scoped header actions, and `PageLayout` as a swappable component. The `PageBlueprint` now supports sub-pages with tabbed navigation, page title, icon, and header actions. Plugins can now specify a `title` and `icon` in `createFrontendPlugin`. +- 7edb810: **BREAKING**: Extensions created with the following blueprints must now be provided via an override or a module for the `app` plugin. Extensions from other plugins will now trigger a warning in the app and be ignored. + + - `IconBundleBlueprint` + - `NavContentBlueprint` + - `RouterBlueprint` + - `SignInPageBlueprint` + - `SwappableComponentBlueprint` + - `ThemeBlueprint` + - `TranslationBlueprint` + +### Patch Changes + +- a2133be: Added new `NavContentNavItem`, `NavContentNavItems`, and `navItems` prop to `NavContentComponentProps` for auto-discovering navigation items from page extensions. The new `navItems` collection supports `take(id)` and `rest()` methods for placing specific items in custom sidebar positions, as well as `withComponent(Component)` which returns a `NavContentNavItemsWithComponent` for rendering items directly as elements. The existing `items` prop is now deprecated in favor of `navItems`. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/ui@0.12.0 + - @backstage/core-components@0.18.7 + - @backstage/theme@0.7.2 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/plugin-app-react@0.2.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/integration-react@1.2.15 + - @backstage/plugin-permission-react@0.4.40 + - @backstage/version-bridge@1.0.12 + +## @backstage/plugin-app-react@0.2.0 + +### Minor Changes + +- a2133be: Added new `NavContentNavItem`, `NavContentNavItems`, and `navItems` prop to `NavContentComponentProps` for auto-discovering navigation items from page extensions. The new `navItems` collection supports `take(id)` and `rest()` methods for placing specific items in custom sidebar positions, as well as `withComponent(Component)` which returns a `NavContentNavItemsWithComponent` for rendering items directly as elements. The existing `items` prop is now deprecated in favor of `navItems`. + +### Patch Changes + +- ef6916e: Added `IconElement` type as a replacement for the deprecated `IconComponent`. The `IconsApi` now has a new `icon()` method that returns `IconElement`, while the existing `getIcon()` method is deprecated. The `IconBundleBlueprint` now accepts both `IconComponent` and `IconElement` values. +- 409af72: Internal refactor to move implementation of blueprints from `@backstage/frontend-plugin-api` to this package. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/core-plugin-api@1.12.3 + +## @backstage/plugin-app-visualizer@0.2.0 + +### Minor Changes + +- ef6916e: Migrated to use `SubPageBlueprint` for tabbed navigation and added a copy-tree-as-JSON plugin header action using `PluginHeaderActionBlueprint`. The plugin now specifies a `title` and `icon`. + +### Patch Changes + +- cb090b4: Bump react-aria-components to v1.14.0 +- c38b74d: Internal updates for blueprint moves to `@backstage/plugin-app-react`. +- 4137a43: Updated CSS token references to use renamed `--bui-border-2` token. +- 4d50e1f: Improved rendering performance of the details page. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/ui@0.12.0 + - @backstage/core-components@0.18.7 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/core-plugin-api@1.12.3 + +## @backstage/plugin-auth-backend@0.27.0 + +### Minor Changes + +- 31de2c9: Added experimental support for Client ID Metadata Documents (CIMD). + + This allows Backstage to act as an OAuth 2.0 authorization server that supports the [IETF Client ID Metadata Document draft](https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/). External OAuth clients can use HTTPS URLs as their `client_id`, and Backstage will fetch metadata from those URLs to validate the client. + + **Configuration example:** + + ```yaml + auth: + experimentalClientIdMetadataDocuments: + enabled: true + # Optional: restrict which `client_id` URLs are allowed (defaults to ['*']) + allowedClientIdPatterns: + - 'https://example.com/*' + - 'https://*.trusted-domain.com/*' + # Optional: restrict which redirect URIs are allowed (defaults to ['*']) + allowedRedirectUriPatterns: + - 'http://localhost:*' + - 'https://*.example.com/*' + ``` + + Clients using CIMD must host a JSON metadata document at their `client_id` URL containing at minimum: + + ```json + { + "client_id": "https://example.com/.well-known/oauth-client/my-app", + "client_name": "My Application", + "redirect_uris": ["http://localhost:8080/callback"], + "token_endpoint_auth_method": "none" + } + ``` + +- d0786b9: Added experimental support for refresh tokens via the `auth.experimentalRefreshToken.enabled` configuration option. When enabled, clients can request the `offline_access` scope to receive refresh tokens that can be used to obtain new access tokens without re-authentication. + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + +## @backstage/plugin-auth-backend-module-auth0-provider@0.3.0 + +### Minor Changes + +- 36804fe: feat: Added organization option to authorization params of the strategy + +### Patch Changes + +- 867c905: Add support for organizational invites in auth0 strategy +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + +## @backstage/plugin-auth-backend-module-github-provider@0.5.0 + +### Minor Changes + +- ff07934: Added the `userIdMatchingUserEntityAnnotation` sign-in resolver that matches users by their GitHub user ID. + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.4.0 + +### Minor Changes + +- ff07934: Added the `{gitlab-integration-host}/user-id` annotation to store GitLab's user ID (immutable) in user entities. Also includes addition of the `userIdMatchingUserEntityAnnotation` sign-in resolver that matches users by the new ID. + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + +## @backstage/plugin-catalog@1.33.0 + +### Minor Changes + +- 491a06c: Add the ability to show icons for the tabs on the entity page (new frontend) +- 05aac34: Migrated `DeleteEntityDialog` and `EntityOrphanWarning` components to Backstage UI. + + The `deleteEntity.description` translation key no longer includes "Click here to delete" text. A new `deleteEntity.actionButtonTitle` key was added for the action button. + +### Patch Changes + +- 220d6c3: Add missing translation entries for catalog UI text. + + This change adds translation keys and updates relevant UI components to use the correct localized labels and text in the catalog plugin. It ensures that catalog screens such as entity layout, tabs, search result items, table labels, and other UI elements correctly reference the i18n system for translation. + + No functional behavior is changed aside from the improved internationalization support. + +- 8d4c48b: Fixed vertical spacing between tags in the catalog table. + +- 7feb83b: Adjusted to use the new `@backstage/filter-predicates` types for predicate expressions. + +- 018ca87: Added `title` and `icon` to the plugin definition for the new frontend system. + +- e8258d0: The default entity content layout still supports rendering summary cards at runtime for backward compatibility, but logs a console warning when they are detected to help identify where migration is needed. + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. + +- 75ac651: Migrated `EntityRelationWarning` and `EntityProcessingErrorsPanel` components from Material UI to Backstage UI. + +- Updated dependencies + - @backstage/ui@0.12.0 + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/core-components@0.18.7 + - @backstage/plugin-search-common@1.2.22 + - @backstage/core-compat-api@0.5.8 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/catalog-client@1.13.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-techdocs-react@1.3.8 + - @backstage/integration-react@1.2.15 + - @backstage/plugin-permission-react@0.4.40 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-search-react@1.10.3 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-scaffolder-common@1.7.6 + +## @backstage/plugin-catalog-backend@3.4.0 + +### Minor Changes + +- f1d29b4: Failures to connect catalog providers are now attributed to the module that provided the failing provider. This means that such failures will be reported as module startup failures rather than a failure to start the catalog plugin, and will therefore respect `onPluginModuleBootFailure` configuration instead. + +- 34cc520: Implemented handling of events from the newly introduced alpha + `catalogScmEventsServiceRef` service, in the builtin entity providers. This + allows entities to get refreshed, and locations updated or removed, as a + response to incoming events. In its first iteration, only the GitHub module + implements such event handling however. + + This is not yet enabled by default, but this fact may change in a future + release. To try it out, ensure that you have the latest catalog GitHub module + installed, and set the following in your app-config: + + ```yaml + catalog: + scmEvents: true + ``` + + Or if you want to pick and choose from the various features: + + ```yaml + catalog: + scmEvents: + # refresh (reprocess) upon events? + refresh: true + # automatically unregister locations based on events? (files deleted, repos archived, etc) + unregister: true + # automatically move locations based on events? (repo transferred, file renamed, etc) + move: true + ``` + +- b4e8249: Implemented the `POST /locations/by-query` endpoint which allows paginated, filtered location queries + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- 7455dae: Use node prefix on native imports +- 5e3ef57: Added `peerModules` metadata declaring recommended modules for cross-plugin integrations. +- 08a5813: Fixed O(n²) performance bottleneck in `buildEntitySearch` `traverse()` by replacing `Array.some()` linear scan with a `Set` for O(1) duplicate path key detection. +- 1e669cc: Migrate audit events reference docs to . +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-openapi-utils@0.6.6 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/catalog-client@1.13.0 + - @backstage/filter-predicates@0.1.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.10 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-events-node@0.4.19 + +## @backstage/plugin-catalog-backend-module-gitlab@0.8.0 + +### Minor Changes + +- 2f51676: allow entity discoverability via gitlab search API +- ff07934: Added the `{gitlab-integration-host}/user-id` annotation to store GitLab's user ID (immutable) in user entities. Also includes addition of the `userIdMatchingUserEntityAnnotation` sign-in resolver that matches users by the new ID. + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- 7455dae: Use node prefix on native imports +- 7e6b5e5: Fixed GitLab search API scope parameter from `'blob'` to `'blobs'`, resolving 400 errors in discovery provider. +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/backend-defaults@0.15.2 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-events-node@0.4.19 + +## @backstage/plugin-catalog-backend-module-msgraph@0.9.0 + +### Minor Changes + +- 8694561: Log group/user count, tenant ID, execution time as separate fields + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-catalog-common@1.1.8 + +## @backstage/plugin-catalog-node@2.0.0 + +### Minor Changes + +- cfd8103: Promoted stable catalog extension points from alpha to main export. The following extension points are now exported from `@backstage/plugin-catalog-node` instead of `@backstage/plugin-catalog-node/alpha`: + + - `catalogLocationsExtensionPoint` and `CatalogLocationsExtensionPoint` + - `catalogProcessingExtensionPoint` and `CatalogProcessingExtensionPoint` + - `catalogAnalysisExtensionPoint` and `CatalogAnalysisExtensionPoint` + + The old alpha exports for these extension points are now deprecated with `@deprecated` markers pointing to the new stable exports. Please update your imports from `@backstage/plugin-catalog-node/alpha` to `@backstage/plugin-catalog-node`. + + Note: The `catalogModelExtensionPoint`, `catalogPermissionExtensionPoint`, and related types remain in alpha. + +- b4e8249: Implemented support for the new `queryLocations` and `streamLocations` that allow paginated/streamed and filtered location queries + +- 34cc520: Introduced the `catalogScmEventsServiceRef`, along with `CatalogScmEventsService` and associated types. These allow communicating a unified set of events, that parts of the catalog can react to. + +### Patch Changes + +- 42abfb1: Updated `catalogServiceMock.mock` to use `createServiceMock` from `@backstage/backend-test-utils`, replacing the internal copy of `simpleMock`. Added `@backstage/backend-test-utils` as an optional peer dependency. +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-test-utils@1.11.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/catalog-client@1.13.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.10 + - @backstage/plugin-catalog-common@1.1.8 + +## @backstage/plugin-catalog-react@2.0.0 + +### Minor Changes + +- 0e9578d: Migrated `UnregisterEntityDialog` from Material UI to Backstage UI components. + +- 491a06c: Add the ability to show icons for the tabs on the entity page (new frontend) + +- b4e8249: Implemented support for the new `queryLocations` and `streamLocations` that allow paginated/streamed and filtered location queries + +- 7feb83b: **BREAKING ALPHA**: All of the predicate types and functions have been moved to the `@backstage/filter-predicates` package. + + When moving into the more general package, they were renamed as follows: + + - `EntityPredicate` -> `FilterPredicate` + - `EntityPredicateExpression` -> `FilterPredicateExpression` + - `EntityPredicatePrimitive` -> `FilterPredicatePrimitive` + - `entityPredicateToFilterFunction` -> `filterPredicateToFilterFunction` + - `EntityPredicateValue` -> `FilterPredicateValue` + +- e8258d0: **BREAKING**: Removed the 'summary' entity card type from `EntityCardType`. Users should migrate to using 'content' or 'info' card types instead. + + TypeScript will now show errors if you try to use `type: 'summary'` when creating entity cards. + +- ac9bead: Added `createTestEntityPage` test utility for testing entity cards and content extensions in the new frontend system. This utility creates a test page extension that provides `EntityProvider` context and accepts entity extensions through input redirects: + + ```typescript + import { renderTestApp } from '@backstage/frontend-test-utils'; + import { createTestEntityPage } from '@backstage/plugin-catalog-react/testUtils'; + + renderTestApp({ + extensions: [createTestEntityPage({ entity: myEntity }), myEntityCard], + }); + ``` + +### Patch Changes + +- f523983: Fixes a bug where the `EntityListProvider` would not correctly hydrate query parameters if more than 20 were provided for the same key. +- 09a6aad: The `catalogApiMock` test utility now returns a `MockWithApiFactory`, allowing it to be passed directly to test utilities like `renderTestApp` and `TestApiProvider` without needing the `[catalogApiRef, catalogApiMock()]` tuple. +- 88dbd5e: fixed bug in `UserListPicker` by getting the `kindParamater` from the `filters` rather than from the `queryParameters` +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/ui@0.12.0 + - @backstage/frontend-test-utils@0.5.0 + - @backstage/core-components@0.18.7 + - @backstage/core-compat-api@0.5.8 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/catalog-client@1.13.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/integration-react@1.2.15 + - @backstage/plugin-permission-react@0.4.40 + - @backstage/version-bridge@1.0.12 + - @backstage/filter-predicates@0.1.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-catalog-common@1.1.8 + +## @backstage/plugin-events-backend-module-google-pubsub@0.2.0 + +### Minor Changes + +- 80905b3: Added an optional `filter` property to PubSub consumers/publishers + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/filter-predicates@0.1.0 + - @backstage/plugin-events-node@0.4.19 + +## @backstage/plugin-search@1.6.0 + +### Minor Changes + +- feef8d9: Added support for configuring the default search type in the search page via the `search.defaultType` option in `app-config.yaml`. This applies to both the legacy and new frontend systems. If not set, the default is empty, which means searching for "all" types. + +### Patch Changes + +- 018ca87: Added `title` and `icon` to the plugin definition for the new frontend system. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/core-components@0.18.7 + - @backstage/plugin-search-common@1.2.22 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-search-react@1.10.3 + +## @backstage/plugin-search-backend-module-elasticsearch@1.8.0 + +### Minor Changes + +- 583bd3a: Added `elasticsearchAuthExtensionPoint` to enable dynamic authentication mechanisms such as bearer tokens with automatic rotation. + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 7021165: Fixed bulk indexing to refresh only the target index instead of all indexes, improving performance in multi-index deployments. +- Updated dependencies + - @backstage/integration-aws-node@0.1.20 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-search-backend-node@1.4.1 + - @backstage/plugin-search-common@1.2.22 + +## @backstage/plugin-techdocs@1.17.0 + +### Minor Changes + +- 27798df: Add two config values to the `page:techdocs/reader` extension that configure default layout, `withoutSearch` and `withoutHeader`. Default are unchanged to `false`. + + E.g. to disable the search and header on the Techdocs Reader Page: + + ```yaml + app: + extensions: + - page:techdocs/reader: + config: + withoutSearch: true + withoutHeader: true + ``` + +### Patch Changes + +- 7feb83b: Adjusted to use the new `@backstage/filter-predicates` types for predicate expressions. +- 491a06c: Add the ability to show icons for the tabs on the entity page (new frontend) +- 018ca87: Added `title` and `icon` to the plugin definition for the new frontend system. +- 9e29545: Improve sidebars (nav/TOC) layout and scrolling +- 22dce2b: TechDocs addons in the new frontend system now use a Utility API pattern instead of multiple attachment points. The `AddonBlueprint` now uses this new approach, and while addons created with older versions still work, they will produce a deprecation warning and will stop working in a future release. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- 0a88779: Added title prop to OffsetPaginatedDocsTable for proper display +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/integration@1.20.0 + - @backstage/core-components@0.18.7 + - @backstage/plugin-search-common@1.2.22 + - @backstage/theme@0.7.2 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/catalog-client@1.13.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-techdocs-react@1.3.8 + - @backstage/integration-react@1.2.15 + - @backstage/plugin-search-react@1.10.3 + - @backstage/plugin-auth-react@0.1.24 + +## @backstage/plugin-user-settings@0.9.0 + +### Minor Changes + +- 104ca74: User-settings will now use DataLoader to batch consecutive calls into one API call to improve performance + +### Patch Changes + +- 018ca87: Added `title` and `icon` to the plugin definition for the new frontend system. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/core-components@0.18.7 + - @backstage/core-app-api@1.19.5 + - @backstage/theme@0.7.2 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-user-settings-common@0.1.0 + - @backstage/plugin-signals-react@0.0.19 + +## @backstage/plugin-user-settings-backend@0.4.0 + +### Minor Changes + +- 104ca74: User-settings will now use DataLoader to batch consecutive calls into one API call to improve performance + +### Patch Changes + +- 8148621: Moved `@backstage/backend-defaults` from `dependencies` to `devDependencies`. +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + - @backstage/plugin-user-settings-common@0.1.0 + - @backstage/plugin-signals-node@0.1.28 + +## @backstage/plugin-user-settings-common@0.1.0 + +### Minor Changes + +- 104ca74: User-settings will now use DataLoader to batch consecutive calls into one API call to improve performance + +## @backstage/app-defaults@1.7.5 + +### Patch Changes + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/core-components@0.18.7 + - @backstage/core-app-api@1.19.5 + - @backstage/theme@0.7.2 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-permission-react@0.4.40 + +## @backstage/backend-defaults@0.15.2 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 44f5d04: Minor internal restructure of the postgres config loading code +- 4fc7bf0: Bump to tar v7 +- 5dd683f: `createRateLimitMiddleware` is now exported from `@backstage/backend-defaults/httpRouter` +- 8dd518a: Support `connection.type: azure` in database client to use Microsoft Entra authentication with Azure database for PostgreSQL +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-app-api@1.5.0 + - @backstage/integration@1.20.0 + - @backstage/integration-aws-node@0.1.20 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/backend-dev-utils@0.1.7 + - @backstage/config-loader@1.10.8 + - @backstage/cli-node@0.2.18 + - @backstage/plugin-auth-node@0.6.13 + - @backstage/plugin-permission-node@0.10.10 + - @backstage/plugin-events-node@0.4.19 + +## @backstage/backend-dev-utils@0.1.7 + +### Patch Changes + +- 7455dae: Use node prefix on native imports + +## @backstage/backend-dynamic-feature-service@0.7.9 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- fdbd404: Updated `@module-federation/enhanced`, `@module-federation/runtime`, and `@module-federation/sdk` dependencies from `^0.9.0` to `^0.21.6`. +- 9b4c414: Updated README for backend-dynamic-feature-service +- Updated dependencies + - @backstage/plugin-catalog-backend@3.4.0 + - @backstage/backend-openapi-utils@0.6.6 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-search-backend-node@1.4.1 + - @backstage/backend-defaults@0.15.2 + - @backstage/plugin-scaffolder-node@0.12.5 + - @backstage/config-loader@1.10.8 + - @backstage/plugin-events-backend@0.5.11 + - @backstage/plugin-search-common@1.2.22 + - @backstage/cli-common@0.1.18 + - @backstage/cli-node@0.2.18 + - @backstage/plugin-auth-node@0.6.13 + - @backstage/plugin-app-node@0.1.42 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.10 + - @backstage/plugin-events-node@0.4.19 + +## @backstage/backend-openapi-utils@0.6.6 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + +## @backstage/cli@0.35.4 + +### Patch Changes + +- cfd8103: Updated catalog provider module template to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of alpha exports. + +- 20131c5: Added support for CSS exports in package builds. When a package declares a CSS file in its `exports` field (e.g., `"./styles.css": "./src/styles.css"`), the CLI will automatically bundle it during `backstage-cli package build`, resolving any `@import` statements. The export path is rewritten from `src/` to `dist/` at publish time. + + Fixed `backstage-cli repo fix` to not add `typesVersions` entries for non-script exports like CSS files. + +- 7455dae: Use node prefix on native imports + +- 6ce4a13: Removed `/alpha` from `scaffolderActionsExtensionPoint` import + +- fdbd404: Removed the `EXPERIMENTAL_MODULE_FEDERATION` environment variable flag, making module federation host support always available during `package start`. The host shared dependencies are now managed through `@backstage/module-federation-common` and injected as a versioned runtime script at build time. + +- fdbd404: Updated `@module-federation/enhanced`, `@module-federation/runtime`, and `@module-federation/sdk` dependencies from `^0.9.0` to `^0.21.6`. + +- 4fc7bf0: Bump to tar v7 + +- 5e3ef57: Added support for the new `peerModules` metadata field in `package.json`. This field allows plugin packages to declare modules that should be installed alongside them for cross-plugin integrations. The field is validated by `backstage-cli repo fix --publish`. + +- 122d39c: Completely removed support for the deprecated `app.experimental.packages` configuration. Replace existing usage directly with `app.packages`. + +- 73351c2: Updated dependency `webpack` to `~5.104.0`. + +- 69d880e: Bump to latest zod to ensure it has the latest features + +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/config-loader@1.10.8 + - @backstage/eslint-plugin@0.2.1 + - @backstage/cli-common@0.1.18 + - @backstage/cli-node@0.2.18 + - @backstage/module-federation-common@0.1.0 + +## @backstage/cli-common@0.1.18 + +### Patch Changes + +- 7455dae: Use node prefix on native imports + +## @backstage/cli-node@0.2.18 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 5e3ef57: Added support for the new `peerModules` metadata field in `package.json`. This field allows plugin packages to declare modules that should be installed alongside them for cross-plugin integrations. The field is validated by `backstage-cli repo fix --publish`. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/cli-common@0.1.18 + +## @backstage/codemods@0.1.54 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/cli-common@0.1.18 + +## @backstage/config-loader@1.10.8 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/cli-common@0.1.18 + +## @backstage/core-app-api@1.19.5 + +### Patch Changes + +- 5a71e7a: Fixed memory leak caused by duplicate `AppThemeSelector` instances and missing cleanup in `AppThemeSelector` and `AppLanguageSelector`. Added `dispose()` method to both selectors for proper resource cleanup. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/core-plugin-api@1.12.3 + - @backstage/version-bridge@1.0.12 + +## @backstage/core-compat-api@0.5.8 + +### Patch Changes + +- c38b74d: Internal updates for blueprint moves to `@backstage/plugin-app-react`. +- ef6916e: Added `IconElement` type as a replacement for the deprecated `IconComponent`. The `IconsApi` now has a new `icon()` method that returns `IconElement`, while the existing `getIcon()` method is deprecated. The `IconBundleBlueprint` now accepts both `IconComponent` and `IconElement` values. +- 53b6549: Plugins in the new frontend system now have a `pluginId` field rather than `id` to better align with naming conventions used throughout the frontend and backend systems. The old field is still present but marked as deprecated. All internal code has been updated to prefer `pluginId` while maintaining backward compatibility by falling back to `id` when needed. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/plugin-app-react@0.2.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/version-bridge@1.0.12 + +## @backstage/core-components@0.18.7 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- cebfea7: Removed link styles from LinkButton to avoid styling inconsistencies related to import order. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/theme@0.7.2 + - @backstage/core-plugin-api@1.12.3 + - @backstage/version-bridge@1.0.12 + +## @backstage/core-plugin-api@1.12.3 + +### Patch Changes + +- 53b6549: Plugins in the new frontend system now have a `pluginId` field rather than `id` to better align with naming conventions used throughout the frontend and backend systems. The old field is still present but marked as deprecated. All internal code has been updated to prefer `pluginId` while maintaining backward compatibility by falling back to `id` when needed. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/version-bridge@1.0.12 + +## @backstage/create-app@0.7.9 + +### Patch Changes + +- 40f2720: Updated to include the missing core plugins in the template used with the `--next` flag. Also updated `react-router*` versions and added Jest 30-related dependencies. Finally, moved the order of `@playwright/test` so it won't trigger a file change during the creation process. +- 1ea737c: Bumped create-app version. +- 7c41134: Bumped create-app version. +- 65ba820: Updated the app template sidebar to use the new `NavContentBlueprint` API for page-based navigation. +- 7455dae: Use node prefix on native imports +- c38b74d: Switched `next-app` template to use blueprint from `@backstage/plugin-app-react`. +- Updated dependencies + - @backstage/cli-common@0.1.18 + +## @backstage/dev-utils@1.1.20 + +### Patch Changes + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/ui@0.12.0 + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/core-components@0.18.7 + - @backstage/core-app-api@1.19.5 + - @backstage/theme@0.7.2 + - @backstage/core-plugin-api@1.12.3 + - @backstage/integration-react@1.2.15 + - @backstage/app-defaults@1.7.5 + +## @backstage/e2e-test-utils@0.1.2 + +### Patch Changes + +- b96c20e: Added optional `channel` option to `generateProjects()` to allow customizing the Playwright browser channel for testing against different browsers variants. When not provided, the function defaults to 'chrome' to maintain backward compatibility. + + Example usage: + + ```ts + import { generateProjects } from '@backstage/e2e-test-utils'; + + export default defineConfig({ + projects: generateProjects({ channel: 'msedge' }), + }); + ``` + +- 7455dae: Use node prefix on native imports + +## @backstage/eslint-plugin@0.2.1 + +### Patch Changes + +- 7455dae: Use node prefix on native imports + +## @backstage/frontend-dynamic-feature-loader@0.1.9 + +### Patch Changes + +- fdbd404: Updated module federation integration to use `@module-federation/enhanced/runtime` `createInstance` API and the new `loadModuleFederationHostShared` from `@backstage/module-federation-common` for loading shared dependencies. Also added support for passing a pre-created `ModuleFederation` instance via the `moduleFederation.instance` option. +- fdbd404: Updated `@module-federation/enhanced`, `@module-federation/runtime`, and `@module-federation/sdk` dependencies from `^0.9.0` to `^0.21.6`. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/module-federation-common@0.1.0 + +## @backstage/integration-aws-node@0.1.20 + +### Patch Changes + +- 7455dae: Use node prefix on native imports + +## @backstage/integration-react@1.2.15 + +### Patch Changes + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/core-plugin-api@1.12.3 + +## @backstage/repo-tools@0.16.4 + +### Patch Changes + +- cd75ed0: Add newline to OpenAPI license template files. +- 7455dae: Use node prefix on native imports +- 4fc7bf0: Bump to tar v7 +- 6523040: Support Prettier v3 for api-reports +- be7ebad: Updated package-docs exclude list to reflect renamed example app packages. +- df59ee6: The `type-deps` command now follows relative imports and re-exports into declaration chunk files, and detects ambient global types such as the `jest` namespace. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/config-loader@1.10.8 + - @backstage/cli-common@0.1.18 + - @backstage/cli-node@0.2.18 + +## @techdocs/cli@1.10.5 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 27798df: Migrate the Techdocs CLI embedded app to the New Frontend System (NFS) +- 508d127: Updated dependency `find-process` to `^2.0.0`. +- Updated dependencies + - @backstage/backend-defaults@0.15.2 + - @backstage/plugin-techdocs-node@1.14.2 + - @backstage/cli-common@0.1.18 + +## @backstage/test-utils@1.7.15 + +### Patch Changes + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- 68eb322: Added `@types/jest` as an optional peer dependency, since jest types are exposed in the public API surface. +- Updated dependencies + - @backstage/core-app-api@1.19.5 + - @backstage/theme@0.7.2 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-permission-react@0.4.40 + - @backstage/plugin-permission-common@0.9.6 + +## @backstage/theme@0.7.2 + +### Patch Changes + +- 1c52dcc: add square shape +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. + +## @backstage/version-bridge@1.0.12 + +### Patch Changes + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. + +## @backstage/plugin-api-docs@0.13.4 + +### Patch Changes + +- ac9bead: Added `@backstage/frontend-test-utils` dev dependency. +- 7455dae: Use node prefix on native imports +- 7feb83b: Adjusted to use the new `@backstage/filter-predicates` types for predicate expressions. +- 491a06c: Add the ability to show icons for the tabs on the entity page (new frontend) +- 4183614: Updated usage of deprecated APIs in the new frontend system. +- 018ca87: Added `title` and `icon` to the plugin definition for the new frontend system. +- 629c3ec: Add `tableOptions` and `title` to Components cards of APIs +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/core-components@0.18.7 + - @backstage/plugin-catalog@1.33.0 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-permission-react@0.4.40 + - @backstage/plugin-catalog-common@1.1.8 + +## @backstage/plugin-api-docs-module-protoc-gen-doc@0.1.11 + +### Patch Changes + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. + +## @backstage/plugin-app-backend@0.5.11 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/config-loader@1.10.8 + - @backstage/plugin-auth-node@0.6.13 + - @backstage/plugin-app-node@0.1.42 + +## @backstage/plugin-app-node@0.1.42 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/config-loader@1.10.8 + +## @backstage/plugin-auth@0.1.5 + +### Patch Changes + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/core-components@0.18.7 + - @backstage/theme@0.7.2 + - @backstage/frontend-plugin-api@0.14.0 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.12 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.13 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/plugin-auth-backend@0.27.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.17 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.12 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + +## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.12 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.12 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.12 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + +## @backstage/plugin-auth-backend-module-google-provider@0.3.12 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + +## @backstage/plugin-auth-backend-module-guest-provider@0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.12 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.12 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.17 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.4.13 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/plugin-auth-backend@0.27.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + +## @backstage/plugin-auth-backend-module-okta-provider@0.2.12 + +### Patch Changes + +- 08aea95: Added a validation check that rejects `audience` configuration values that are not absolute URLs (i.e. missing `https://` or `http://` prefix). +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.12 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + +## @backstage/plugin-auth-backend-module-openshift-provider@0.1.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.11 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + +## @backstage/plugin-auth-node@0.6.13 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/catalog-client@1.13.0 + +## @backstage/plugin-auth-react@0.1.24 + +### Patch Changes + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/core-components@0.18.7 + - @backstage/core-plugin-api@1.12.3 + +## @backstage/plugin-bitbucket-cloud-common@0.3.7 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/integration@1.20.0 + +## @backstage/plugin-catalog-backend-module-aws@0.4.20 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/integration-aws-node@0.1.20 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/backend-defaults@0.15.2 + - @backstage/plugin-kubernetes-common@0.9.10 + - @backstage/plugin-catalog-common@1.1.8 + +## @backstage/plugin-catalog-backend-module-azure@0.3.14 + +### Patch Changes + +- cc6206e: Added support for `{org}.visualstudio.com` domains used by Azure DevOps +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- 6c8a464: Added missing `branch` field to the `azureDevOps` provider config schema. +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-catalog-common@1.1.8 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.11 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-openapi-utils@0.6.6 + - @backstage/backend-plugin-api@1.7.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.8 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.7 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-events-node@0.4.19 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.8 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-events-node@0.4.19 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.16 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-kubernetes-common@0.9.10 + +## @backstage/plugin-catalog-backend-module-gerrit@0.3.11 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-catalog-common@1.1.8 + +## @backstage/plugin-catalog-backend-module-gitea@0.1.9 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-catalog-common@1.1.8 + +## @backstage/plugin-catalog-backend-module-github@0.12.2 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- 7455dae: Use node prefix on native imports +- 34cc520: Implemented translation of webhook events into `catalogScmEventsServiceRef` events. +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-events-node@0.4.19 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.19 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- Updated dependencies + - @backstage/plugin-catalog-backend-module-github@0.12.2 + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-events-node@0.4.19 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.18 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- Updated dependencies + - @backstage/plugin-catalog-backend-module-gitlab@0.8.0 + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-events-node@0.4.19 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.9 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-catalog-backend@3.4.0 + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/backend-defaults@0.15.2 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-events-node@0.4.19 + +## @backstage/plugin-catalog-backend-module-ldap@0.12.2 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-catalog-common@1.1.8 + +## @backstage/plugin-catalog-backend-module-logs@0.1.19 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.4.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-events-node@0.4.19 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.19 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-catalog-common@1.1.8 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.19 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-plugin-api@1.7.0 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.17 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-scaffolder-common@1.7.6 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.6.8 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.13 + +## @backstage/plugin-catalog-common@1.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-permission-common@0.9.6 + +## @backstage/plugin-catalog-graph@0.5.7 + +### Patch Changes + +- ac9bead: Added `@backstage/frontend-test-utils` dev dependency. +- 8dd27c4: Fix large icon rendering in catalog graph nodes +- 7feb83b: Adjusted to use the new `@backstage/filter-predicates` types for predicate expressions. +- 4183614: Updated usage of deprecated APIs in the new frontend system. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/core-components@0.18.7 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/catalog-client@1.13.0 + - @backstage/core-plugin-api@1.12.3 + +## @backstage/plugin-catalog-import@0.13.10 + +### Patch Changes + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/integration@1.20.0 + - @backstage/core-components@0.18.7 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/catalog-client@1.13.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/integration-react@1.2.15 + - @backstage/plugin-permission-react@0.4.40 + - @backstage/plugin-catalog-common@1.1.8 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.26 + +### Patch Changes + +- 018ca87: Added `title` and `icon` to the plugin definition for the new frontend system. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/core-components@0.18.7 + - @backstage/core-compat-api@0.5.8 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/plugin-devtools-react@0.1.1 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.13 + +## @backstage/plugin-catalog-unprocessed-entities-common@0.0.13 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.6 + +## @backstage/plugin-config-schema@0.1.77 + +### Patch Changes + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/core-components@0.18.7 + - @backstage/core-plugin-api@1.12.3 + +## @backstage/plugin-devtools@0.1.36 + +### Patch Changes + +- 018ca87: Added `title` and `icon` to the plugin definition for the new frontend system. +- f2612c2: Fixes an issue where a user lacking permission to schedule tasks can now easily see the issue through a custom icon + tooltip. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/core-components@0.18.7 + - @backstage/core-compat-api@0.5.8 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/plugin-devtools-react@0.1.1 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-permission-react@0.4.40 + - @backstage/plugin-devtools-common@0.1.22 + +## @backstage/plugin-devtools-backend@0.5.14 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 8148621: Moved `@backstage/backend-defaults` from `dependencies` to `devDependencies`. +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/config-loader@1.10.8 + - @backstage/cli-common@0.1.18 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.10 + - @backstage/plugin-devtools-common@0.1.22 + +## @backstage/plugin-devtools-common@0.1.22 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.6 + +## @backstage/plugin-devtools-react@0.1.1 + +### Patch Changes + +- 9fbb270: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/core-plugin-api@1.12.3 + +## @backstage/plugin-events-backend@0.5.11 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.6 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-events-node@0.4.19 + +## @backstage/plugin-events-backend-module-aws-sqs@0.4.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-events-node@0.4.19 + +## @backstage/plugin-events-backend-module-azure@0.2.28 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-events-node@0.4.19 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.28 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-events-node@0.4.19 + +## @backstage/plugin-events-backend-module-bitbucket-server@0.1.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-events-node@0.4.19 + +## @backstage/plugin-events-backend-module-gerrit@0.2.28 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-events-node@0.4.19 + +## @backstage/plugin-events-backend-module-github@0.4.9 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-events-node@0.4.19 + +## @backstage/plugin-events-backend-module-gitlab@0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-events-node@0.4.19 + +## @backstage/plugin-events-backend-module-kafka@0.3.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-events-node@0.4.19 + +## @backstage/plugin-events-backend-test-utils@0.1.52 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.19 + +## @backstage/plugin-events-node@0.4.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + +## @backstage/plugin-gateway-backend@1.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + +## @backstage/plugin-home@0.9.2 + +### Patch Changes + +- 018ca87: Added `title` and `icon` to the plugin definition for the new frontend system. +- 90956a6: Support new frontend system in the homepage plugin +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/core-components@0.18.7 + - @backstage/core-app-api@1.19.5 + - @backstage/core-compat-api@0.5.8 + - @backstage/theme@0.7.2 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/catalog-client@1.13.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-home-react@0.1.35 + +## @backstage/plugin-home-react@0.1.35 + +### Patch Changes + +- 90956a6: Support new frontend system in the homepage plugin +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/core-components@0.18.7 + - @backstage/core-compat-api@0.5.8 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/core-plugin-api@1.12.3 + +## @backstage/plugin-kubernetes@0.12.16 + +### Patch Changes + +- 7feb83b: Adjusted to use the new `@backstage/filter-predicates` types for predicate expressions. +- 491a06c: Add the ability to show icons for the tabs on the entity page (new frontend) +- 4183614: Updated usage of deprecated APIs in the new frontend system. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/core-components@0.18.7 + - @backstage/plugin-kubernetes-react@0.5.16 + - @backstage/plugin-kubernetes-common@0.9.10 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-permission-react@0.4.40 + +## @backstage/plugin-kubernetes-backend@0.21.1 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- ce3639c: Add PersistentVolume and PersistentVolumeClaims Rendering +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/integration-aws-node@0.1.20 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-kubernetes-node@0.4.1 + - @backstage/plugin-kubernetes-common@0.9.10 + - @backstage/catalog-client@1.13.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.10 + +## @backstage/plugin-kubernetes-cluster@0.0.34 + +### Patch Changes + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/core-components@0.18.7 + - @backstage/plugin-kubernetes-react@0.5.16 + - @backstage/plugin-kubernetes-common@0.9.10 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-permission-react@0.4.40 + +## @backstage/plugin-kubernetes-common@0.9.10 + +### Patch Changes + +- ce3639c: Add PersistentVolume and PersistentVolumeClaims Rendering +- Updated dependencies + - @backstage/plugin-permission-common@0.9.6 + +## @backstage/plugin-kubernetes-node@0.4.1 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- ce3639c: Add PersistentVolume and PersistentVolumeClaims Rendering +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-kubernetes-common@0.9.10 + +## @backstage/plugin-kubernetes-react@0.5.16 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- ce3639c: Add PersistentVolume and PersistentVolumeClaims Rendering +- d56542c: Updated dependency `@xterm/addon-attach` to `^0.12.0`. + Updated dependency `@xterm/addon-fit` to `^0.11.0`. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/core-components@0.18.7 + - @backstage/plugin-kubernetes-common@0.9.10 + - @backstage/core-plugin-api@1.12.3 + +## @backstage/plugin-mcp-actions-backend@0.1.9 + +### Patch Changes + +- 31de2c9: Added OAuth Protected Resource Metadata endpoint (`/.well-known/oauth-protected-resource`) per RFC 9728. This allows MCP clients to discover the authorization server for the resource. + + Also enabled OAuth well-known endpoints when CIMD (Client ID Metadata Documents) is configured, not just when DCR is enabled. + +- 8148621: Moved `@backstage/backend-defaults` from `dependencies` to `devDependencies`. + +- 69d880e: Bump to latest zod to ensure it has the latest features + +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/catalog-client@1.13.0 + +## @backstage/plugin-mui-to-bui@0.2.4 + +### Patch Changes + +- 4137a43: Updated CSS token references to use renamed `--bui-bg-app` and `--bui-border-2` tokens. + +- a88c437: Updated MUI to BUI theme converter to align with latest token changes + + **Changes:** + + - Removed generation of deprecated tokens: `--bui-fg-link`, `--bui-fg-link-hover`, `--bui-fg-tint`, `--bui-fg-tint-disabled`, `--bui-bg-tint` and all its variants + - Added generation of new `info` status tokens: `--bui-fg-info`, `--bui-fg-info-on-bg`, `--bui-bg-info`, `--bui-border-info` + - Updated status color mapping to generate both standalone and `-on-bg` variants for danger, warning, success, and info + - Status colors now use `.main` for standalone variants and `.dark` for `-on-bg` variants, providing better visual hierarchy + + The converter now generates tokens that match the updated BUI design system structure, with clear distinction between status colors for standalone use vs. use on colored backgrounds. + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. + +- Updated dependencies + - @backstage/ui@0.12.0 + - @backstage/theme@0.7.2 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/core-plugin-api@1.12.3 + +## @backstage/plugin-notifications@0.5.14 + +### Patch Changes + +- 8005286: Added `renderItem` prop to `NotificationsSidebarItem` component, allowing custom UI rendering while retaining all built-in notification logic (unread count, snackbar, signals, web notifications). +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/core-components@0.18.7 + - @backstage/theme@0.7.2 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/plugin-notifications-common@0.2.1 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-signals-react@0.0.19 + +## @backstage/plugin-notifications-backend@0.6.2 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 5e3ef57: Added `peerModules` metadata declaring recommended modules for cross-plugin integrations. +- e9eb400: Allow configuring included topics for email notifications. +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-notifications-common@0.2.1 + - @backstage/plugin-notifications-node@0.2.23 + - @backstage/plugin-signals-node@0.1.28 + +## @backstage/plugin-notifications-backend-module-email@0.3.18 + +### Patch Changes + +- e9eb400: Allow configuring included topics for email notifications. +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/integration-aws-node@0.1.20 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/catalog-client@1.13.0 + - @backstage/plugin-notifications-common@0.2.1 + - @backstage/plugin-notifications-node@0.2.23 + +## @backstage/plugin-notifications-backend-module-slack@0.3.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-notifications-common@0.2.1 + - @backstage/plugin-notifications-node@0.2.23 + +## @backstage/plugin-notifications-common@0.2.1 + +### Patch Changes + +- e9eb400: Allow configuring included topics for email notifications. + +## @backstage/plugin-notifications-node@0.2.23 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/catalog-client@1.13.0 + - @backstage/plugin-notifications-common@0.2.1 + - @backstage/plugin-signals-node@0.1.28 + +## @backstage/plugin-org@0.6.49 + +### Patch Changes + +- ac9bead: Added `@backstage/frontend-test-utils` dev dependency. +- 7feb83b: Adjusted to use the new `@backstage/filter-predicates` types for predicate expressions. +- 4183614: Updated usage of deprecated APIs in the new frontend system. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- 1dee6de: Add search functionality in MembersListCard +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/core-components@0.18.7 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-catalog-common@1.1.8 + +## @backstage/plugin-org-react@0.1.47 + +### Patch Changes + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/core-components@0.18.7 + - @backstage/catalog-client@1.13.0 + - @backstage/core-plugin-api@1.12.3 + +## @backstage/plugin-permission-backend@0.7.9 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.10 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.10 + +## @backstage/plugin-permission-common@0.9.6 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features + +## @backstage/plugin-permission-node@0.10.10 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + - @backstage/plugin-permission-common@0.9.6 + +## @backstage/plugin-permission-react@0.4.40 + +### Patch Changes + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-permission-common@0.9.6 + +## @backstage/plugin-proxy-backend@0.6.10 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-proxy-node@0.1.12 + +## @backstage/plugin-proxy-node@0.1.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + +## @backstage/plugin-scaffolder@1.35.3 + +### Patch Changes + +- 7455dae: Use node prefix on native imports + +- 4e581a6: Updated the browser tab title on the template wizard page to display the specific template title instead of the generic "Create a new component" text. + +- 7feb83b: Adjusted to use the new `@backstage/filter-predicates` types for predicate expressions. + +- 018ca87: Added `title` and `icon` to the plugin definition for the new frontend system. + +- 2eeca03: Scaffolder form fields in the new frontend system now use a Utility API pattern instead of multiple attachment points. The `FormFieldBlueprint` now uses this new approach, and while form fields created with older versions still work, they will produce a deprecation warning and will stop working in a future release. + + As part of this change, the following alpha exports were removed: + + - `formFieldsApiRef` + - `ScaffolderFormFieldsApi` + +- b9d90a7: Added `@backstage/frontend-test-utils` as a dev dependency for mock API usage in tests. + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. + +- 69d880e: Bump to latest zod to ensure it has the latest features + +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/integration@1.20.0 + - @backstage/core-components@0.18.7 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/catalog-client@1.13.0 + - @backstage/plugin-scaffolder-react@1.19.7 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-techdocs-react@1.3.8 + - @backstage/integration-react@1.2.15 + - @backstage/plugin-permission-react@0.4.40 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-scaffolder-common@1.7.6 + +## @backstage/plugin-scaffolder-backend@3.1.3 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 4fc7bf0: Removed unused dependency +- 0ce78b0: Support `if` conditions inside `each` loops for scaffolder steps +- 5e3ef57: Added `peerModules` metadata declaring recommended modules for cross-plugin integrations. +- 8148621: Moved `@backstage/backend-defaults` from `dependencies` to `devDependencies`. +- 1e669cc: Migrate audit events reference docs to . +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-gitlab@0.11.3 + - @backstage/integration@1.20.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.17 + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.3 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.19 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.18 + - @backstage/plugin-scaffolder-backend-module-github@0.9.6 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.18 + - @backstage/backend-openapi-utils@0.6.6 + - @backstage/plugin-bitbucket-cloud-common@0.3.7 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-scaffolder-node@0.12.5 + - @backstage/plugin-auth-node@0.6.13 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.10 + - @backstage/plugin-events-node@0.4.19 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.18 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.18 + - @backstage/plugin-scaffolder-common@1.7.6 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.18 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-scaffolder-node@0.12.5 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.19 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.3 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-scaffolder-node@0.12.5 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.18 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.3 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 14741e2: Fully enable API token functionality for Bitbucket-Cloud. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.7 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-scaffolder-node@0.12.5 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.18 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-scaffolder-node@0.12.5 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.18 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-scaffolder-node@0.12.5 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.20 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/backend-defaults@0.15.2 + - @backstage/plugin-scaffolder-node@0.12.5 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.18 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-scaffolder-node@0.12.5 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.18 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-scaffolder-node@0.12.5 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.18 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-scaffolder-node@0.12.5 + +## @backstage/plugin-scaffolder-backend-module-github@0.9.6 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 82ca951: cleaned up repo creation to make the unique portions explicit +- 672b972: Updated dependency `libsodium-wrappers` to `^0.8.0`. + Updated dependency `@types/libsodium-wrappers` to `^0.8.0`. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-scaffolder-node@0.12.5 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.11.3 + +### Patch Changes + +- 6b5e7d9: Allow setting optional description on group creation +- 7455dae: Use node prefix on native imports +- f0f9403: Changed `gitlab:group:ensureExists` action to use `Groups.show` API instead of `Groups.search` for checking if a group path exists. This is more efficient as it directly retrieves the group by path rather than searching and filtering results. +- 32c51c0: Added new `gitlab:user:info` scaffolder action that retrieves information about a GitLab user. The action can fetch either the current authenticated user or a specific user by ID. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-scaffolder-node@0.12.5 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-scaffolder-node@0.12.5 + - @backstage/plugin-notifications-common@0.2.1 + - @backstage/plugin-notifications-node@0.2.23 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.18 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-scaffolder-node@0.12.5 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.3.1 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-scaffolder-node@0.12.5 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.19 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-scaffolder-node-test-utils@0.3.8 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-scaffolder-node@0.12.5 + +## @backstage/plugin-scaffolder-common@1.7.6 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/plugin-permission-common@0.9.6 + +## @backstage/plugin-scaffolder-node@0.12.5 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 4fc7bf0: Bump to tar v7 +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-scaffolder-common@1.7.6 + +## @backstage/plugin-scaffolder-node-test-utils@0.3.8 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/backend-test-utils@1.11.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-scaffolder-node@0.12.5 + +## @backstage/plugin-scaffolder-react@1.19.7 + +### Patch Changes + +- 2eeca03: Scaffolder form fields in the new frontend system now use a Utility API pattern instead of multiple attachment points. The `FormFieldBlueprint` now uses this new approach, and while form fields created with older versions still work, they will produce a deprecation warning and will stop working in a future release. + + As part of this change, the following alpha exports were removed: + + - `formFieldsApi` + - `formFieldsApiRef` + - `ScaffolderFormFieldsApi` + +- b9d90a7: Added `@backstage/frontend-test-utils` as a dev dependency for mock API usage in tests. + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. + +- 69d880e: Bump to latest zod to ensure it has the latest features + +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/core-components@0.18.7 + - @backstage/theme@0.7.2 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/catalog-client@1.13.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-permission-react@0.4.40 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-scaffolder-common@1.7.6 + +## @backstage/plugin-search-backend@2.0.12 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 8148621: Moved `@backstage/backend-defaults` from `dependencies` to `devDependencies`. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.6 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-search-backend-node@1.4.1 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.10 + +## @backstage/plugin-search-backend-module-catalog@0.3.12 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-search-backend-node@1.4.1 + - @backstage/plugin-search-common@1.2.22 + - @backstage/catalog-client@1.13.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-catalog-common@1.1.8 + +## @backstage/plugin-search-backend-module-explore@0.3.11 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- df27350: Updated dependency `@backstage-community/plugin-explore-common` to `^0.12.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-search-backend-node@1.4.1 + - @backstage/plugin-search-common@1.2.22 + +## @backstage/plugin-search-backend-module-pg@0.5.52 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 2ee354a: Return `numberOfResults` count with search query responses +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-search-backend-node@1.4.1 + - @backstage/plugin-search-common@1.2.22 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.17 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-search-backend-node@1.4.1 + - @backstage/plugin-search-common@1.2.22 + +## @backstage/plugin-search-backend-module-techdocs@0.4.11 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-search-backend-node@1.4.1 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-techdocs-node@1.14.2 + - @backstage/catalog-client@1.13.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-catalog-common@1.1.8 + +## @backstage/plugin-search-backend-node@1.4.1 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-permission-common@0.9.6 + +## @backstage/plugin-search-common@1.2.22 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-permission-common@0.9.6 + +## @backstage/plugin-search-react@1.10.3 + +### Patch Changes + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/core-components@0.18.7 + - @backstage/plugin-search-common@1.2.22 + - @backstage/theme@0.7.2 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/version-bridge@1.0.12 + +## @backstage/plugin-signals@0.0.28 + +### Patch Changes + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/core-components@0.18.7 + - @backstage/theme@0.7.2 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-signals-react@0.0.19 + +## @backstage/plugin-signals-backend@0.3.12 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-events-node@0.4.19 + - @backstage/plugin-signals-node@0.1.28 + +## @backstage/plugin-signals-node@0.1.28 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + - @backstage/plugin-events-node@0.4.19 + +## @backstage/plugin-signals-react@0.0.19 + +### Patch Changes + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/core-plugin-api@1.12.3 + +## @backstage/plugin-techdocs-addons-test-utils@2.0.2 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/plugin-catalog@1.33.0 + - @backstage/core-app-api@1.19.5 + - @backstage/plugin-techdocs@1.17.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-techdocs-react@1.3.8 + - @backstage/integration-react@1.2.15 + - @backstage/plugin-search-react@1.10.3 + - @backstage/test-utils@1.7.15 + +## @backstage/plugin-techdocs-backend@2.1.5 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 5e3ef57: Added `peerModules` metadata declaring recommended modules for cross-plugin integrations. +- 8148621: Moved `@backstage/backend-defaults` from `dependencies` to `devDependencies`. +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-techdocs-node@1.14.2 + - @backstage/catalog-client@1.13.0 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.33 + +### Patch Changes + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/core-components@0.18.7 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-techdocs-react@1.3.8 + - @backstage/integration-react@1.2.15 + +## @backstage/plugin-techdocs-node@1.14.2 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 3c455d4: Some security fixes +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/integration-aws-node@0.1.20 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-search-common@1.2.22 + +## @backstage/plugin-techdocs-react@1.3.8 + +### Patch Changes + +- 22dce2b: TechDocs addons in the new frontend system now use a Utility API pattern instead of multiple attachment points. The `AddonBlueprint` now uses this new approach, and while addons created with older versions still work, they will produce a deprecation warning and will stop working in a future release. + + As part of this change, the `techDocsAddonDataRef` alpha export was removed. + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. + +- Updated dependencies + - @backstage/core-components@0.18.7 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/version-bridge@1.0.12 + +## example-app@0.0.32 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.12.0 + - @backstage/frontend-defaults@0.4.0 + - @backstage/frontend-app-api@0.15.0 + - @backstage/plugin-app-visualizer@0.2.0 + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/plugin-api-docs@0.13.4 + - @backstage/plugin-catalog-graph@0.5.7 + - @backstage/plugin-org@0.6.49 + - @backstage/cli@0.35.4 + - @backstage/core-components@0.18.7 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-scaffolder@1.35.3 + - @backstage/plugin-notifications@0.5.14 + - @backstage/plugin-catalog@1.33.0 + - @backstage/core-app-api@1.19.5 + - @backstage/plugin-techdocs@1.17.0 + - @backstage/plugin-kubernetes@0.12.16 + - @backstage/core-compat-api@0.5.8 + - @backstage/theme@0.7.2 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/plugin-app-react@0.2.0 + - @backstage/plugin-app@0.4.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.26 + - @backstage/plugin-devtools@0.1.36 + - @backstage/plugin-home@0.9.2 + - @backstage/plugin-search@1.6.0 + - @backstage/plugin-user-settings@0.9.0 + - @backstage/plugin-scaffolder-react@1.19.7 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-home-react@0.1.35 + - @backstage/plugin-techdocs-react@1.3.8 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.33 + - @backstage/integration-react@1.2.15 + - @backstage/plugin-kubernetes-cluster@0.0.34 + - @backstage/plugin-permission-react@0.4.40 + - @backstage/plugin-catalog-import@0.13.10 + - @backstage/app-defaults@1.7.5 + - @backstage/plugin-search-react@1.10.3 + - @backstage/plugin-auth-react@0.1.24 + - @backstage/plugin-signals@0.0.28 + - @backstage/plugin-auth@0.1.5 + - @backstage/plugin-catalog-common@1.1.8 + +## app-example-plugin@0.0.32 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.7 + - @backstage/frontend-plugin-api@0.14.0 + +## example-app-legacy@0.2.118 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.12.0 + - @backstage/frontend-app-api@0.15.0 + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/plugin-api-docs@0.13.4 + - @backstage/plugin-catalog-graph@0.5.7 + - @backstage/plugin-org@0.6.49 + - @backstage/cli@0.35.4 + - @backstage/core-components@0.18.7 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-scaffolder@1.35.3 + - @backstage/plugin-notifications@0.5.14 + - @backstage/plugin-catalog@1.33.0 + - @backstage/plugin-mui-to-bui@0.2.4 + - @backstage/core-app-api@1.19.5 + - @backstage/plugin-techdocs@1.17.0 + - @backstage/plugin-kubernetes@0.12.16 + - @backstage/theme@0.7.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.26 + - @backstage/plugin-devtools@0.1.36 + - @backstage/plugin-home@0.9.2 + - @backstage/plugin-search@1.6.0 + - @backstage/plugin-user-settings@0.9.0 + - @backstage/plugin-scaffolder-react@1.19.7 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-home-react@0.1.35 + - @backstage/plugin-techdocs-react@1.3.8 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.33 + - @backstage/integration-react@1.2.15 + - @backstage/plugin-kubernetes-cluster@0.0.34 + - @backstage/plugin-permission-react@0.4.40 + - @backstage/plugin-catalog-import@0.13.10 + - @backstage/app-defaults@1.7.5 + - @backstage/plugin-search-react@1.10.3 + - @backstage/plugin-auth-react@0.1.24 + - @backstage/plugin-signals@0.0.28 + - @backstage/plugin-catalog-common@1.1.8 + +## example-backend@0.0.47 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.4.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.11 + - @backstage/plugin-catalog-backend-module-openapi@0.2.19 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.17 + - @backstage/plugin-auth-backend@0.27.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.8.0 + - @backstage/plugin-scaffolder-backend-module-github@0.9.6 + - @backstage/plugin-search-backend-module-techdocs@0.4.11 + - @backstage/plugin-search-backend-module-catalog@0.3.12 + - @backstage/plugin-search-backend-module-explore@0.3.11 + - @backstage/plugin-notifications-backend@0.6.2 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-search-backend-node@1.4.1 + - @backstage/plugin-kubernetes-backend@0.21.1 + - @backstage/plugin-permission-backend@0.7.9 + - @backstage/plugin-scaffolder-backend@3.1.3 + - @backstage/backend-defaults@0.15.2 + - @backstage/plugin-devtools-backend@0.5.14 + - @backstage/plugin-techdocs-backend@2.1.5 + - @backstage/plugin-signals-backend@0.3.12 + - @backstage/plugin-events-backend@0.5.11 + - @backstage/plugin-search-backend@2.0.12 + - @backstage/plugin-proxy-backend@0.6.10 + - @backstage/plugin-app-backend@0.5.11 + - @backstage/plugin-auth-node@0.6.13 + - @backstage/plugin-mcp-actions-backend@0.1.9 + - @backstage/plugin-auth-backend-module-github-provider@0.5.0 + - @backstage/plugin-events-backend-module-google-pubsub@0.2.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.10 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.16 + - @backstage/plugin-auth-backend-module-openshift-provider@0.1.4 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.8 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.16 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.19 + +## e2e-test@0.2.37 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.7.9 + - @backstage/cli-common@0.1.18 + +## @internal/frontend@0.0.17 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/version-bridge@1.0.12 + +## @internal/scaffolder@0.0.18 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/plugin-scaffolder-react@1.19.7 + +## techdocs-cli-embedded-app@0.2.117 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.12.0 + - @backstage/frontend-defaults@0.4.0 + - @backstage/cli@0.35.4 + - @backstage/core-components@0.18.7 + - @backstage/plugin-catalog@1.33.0 + - @backstage/core-app-api@1.19.5 + - @backstage/plugin-techdocs@1.17.0 + - @backstage/theme@0.7.2 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/plugin-app-react@0.2.0 + - @backstage/plugin-techdocs-react@1.3.8 + - @backstage/integration-react@1.2.15 + - @backstage/test-utils@1.7.15 + +## yarn-plugin-backstage@0.0.9 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.18 + +## @internal/plugin-todo-list@1.0.48 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.7 + - @backstage/core-plugin-api@1.12.3 + +## @internal/plugin-todo-list-backend@1.0.47 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + +## @internal/plugin-todo-list-common@1.0.29 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.6 diff --git a/docs/releases/v1.48.0-next.0-changelog.md b/docs/releases/v1.48.0-next.0-changelog.md new file mode 100644 index 0000000000..11896befa1 --- /dev/null +++ b/docs/releases/v1.48.0-next.0-changelog.md @@ -0,0 +1,2651 @@ +# Release v1.48.0-next.0 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.48.0-next.0](https://backstage.github.io/upgrade-helper/?to=1.48.0-next.0) + +## @backstage/backend-app-api@1.5.0-next.0 + +### Minor Changes + +- f1d29b4: Added support for extension point factories, along with the ability to report module startup failures via the extension point factory context. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/backend-plugin-api@1.7.0-next.0 + +### Minor Changes + +- f1d29b4: Added support for extension point factories. This makes it possible to call `registerExtensionPoint` with a single options argument and provide a factory for the extension point rather than a direct implementation. The factory is passed a context with a `reportModuleStartupFailure` method that makes it possible for plugins to report and attribute startup errors to the module that consumed the extension point. + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/cli-common@0.1.18-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/plugin-permission-node@0.10.9-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/frontend-plugin-api@0.14.0-next.0 + +### Minor Changes + +- c38b74d: **BREAKING**: The following blueprints have been removed and are now only available from `@backstage/plugin-app-react`: + + - `IconBundleBlueprint` + - `NavContentBlueprint` + - `RouterBlueprint` + - `SignInPageBlueprint` + - `SwappableComponentBlueprint` + - `ThemeBlueprint` + - `TranslationBlueprint` + +### Patch Changes + +- 7edb810: Added a new `internal` option to `createExtensionInput` that marks the input as only allowing attachments from the same plugin. + +- 9554c36: **DEPRECATED**: Multiple attachment points for extensions have been deprecated. The functionality continues to work for backward compatibility, but will log a deprecation warning and be removed in a future release. + + Extensions using array attachment points should migrate to using Utility APIs instead. See the [Sharing Extensions Across Multiple Locations](https://backstage.io/docs/frontend-system/architecture/27-sharing-extensions) guide for the recommended pattern. + +- 53b6549: Plugins in the new frontend system now have a `pluginId` field rather than `id` to better align with naming conventions used throughout the frontend and backend systems. The old field is still present but marked as deprecated. All internal code has been updated to prefer `pluginId` while maintaining backward compatibility by falling back to `id` when needed. + +- 69d880e: Bump to latest zod to ensure it has the latest features + +- Updated dependencies + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/ui@0.12.0-next.0 + +### Minor Changes + +- b1f723b: **BREAKING**: Changed CSS selectors for `ButtonIcon` and `ButtonLink` components. Custom styles targeting `.bui-Button` to style these components must be updated to use `.bui-ButtonIcon` or `.bui-ButtonLink` respectively. + + ```diff + -/* This no longer styles ButtonIcon or ButtonLink */ + -.bui-Button[data-variant="primary"] { ... } + +/* Use component-specific selectors */ + +.bui-ButtonIcon[data-variant="primary"] { ... } + +.bui-ButtonLink[data-variant="primary"] { ... } + ``` + + Affected components: ButtonIcon, ButtonLink + +- caeb9ad: **BREAKING**: The `cell` and `header` properties in `ColumnConfig` now return `ReactElement` instead of `ReactNode`. + + This fixes an issue where React Aria's Collection component would inject an `id` prop into Fragment wrappers, causing "Invalid prop `id` supplied to `React.Fragment`" errors on render. + + Migration: + + ```diff + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + - cell: (item) => item.name, + + cell: (item) => , + - header: () => 'Name', + + header: () => Name, + }, + ]; + ``` + +### Patch Changes + +- 350c948: Fixed Box component to forward HTML attributes to the underlying div element. + + **Affected components:** Box + +- 7455dae: Use node prefix on native imports + +- 508bd1a: Added new `Alert` component with support for status variants (info, success, warning, danger), icons, loading states, and custom actions. + + Updated status color tokens for improved contrast and consistency across light and dark themes: + + - Added new `--bui-bg-info` and `--bui-fg-info` tokens for info status + - Updated `--bui-bg-danger`, `--bui-fg-danger` tokens + - Updated `--bui-bg-warning`, `--bui-fg-warning` tokens + - Updated `--bui-bg-success`, `--bui-fg-success` tokens + + **Affected components**: Alert + +- da30862: Fixed client-side navigation for container components by wrapping the container (not individual items) in RouterProvider. Components now conditionally provide routing context only when children have internal links, removing the Router context requirement when not needed. This also removes the need to wrap these components in MemoryRouter during tests when they are not using the `href` prop. + + Additionally, when multiple tabs match the current URL via prefix matching, the tab with the most specific path (highest segment count) is now selected. For example, with URL `/catalog/users/john`, a tab with path `/catalog/users` is now selected over a tab with path `/catalog`. + + Affected components: Tabs, Tab, TagGroup, Tag, Menu, MenuItem, MenuAutocomplete + +- 092c453: Fixed an infinite render loop in Tabs when navigating to a URL that doesn't match any tab `href`. + +- 5320aa8: Fixed components to not require a Router context when rendering without internal links. + + Affected components: Link, ButtonLink, Row + +- cb090b4: Bump react-aria-components to v1.14.0 + +- c429101: Fixed React 17 compatibility by using `useId` from `react-aria` instead of the built-in React hook which is only available in React 18+. + +- Updated dependencies + - @backstage/version-bridge@1.0.11 + +## @backstage/plugin-app@0.4.0-next.0 + +### Minor Changes + +- 7edb810: **BREAKING**: Extensions created with the following blueprints must now be provided via an override or a module for the `app` plugin. Extensions from other plugins will now trigger a warning in the app and be ignored. + + - `IconBundleBlueprint` + - `NavContentBlueprint` + - `RouterBlueprint` + - `SignInPageBlueprint` + - `SwappableComponentBlueprint` + - `ThemeBlueprint` + - `TranslationBlueprint` + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/core-components@0.18.6-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/plugin-app-react@0.1.1-next.0 + - @backstage/integration-react@1.2.15-next.0 + - @backstage/theme@0.7.1 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-permission-react@0.4.40-next.0 + +## @backstage/plugin-auth-backend-module-auth0-provider@0.3.0-next.0 + +### Minor Changes + +- 36804fe: feat: Added organization option to authorization params of the strategy + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + +## @backstage/plugin-auth-backend-module-github-provider@0.5.0-next.0 + +### Minor Changes + +- ff07934: Added the `userIdMatchingUserEntityAnnotation` sign-in resolver that matches users by their GitHub user ID. + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.4.0-next.0 + +### Minor Changes + +- ff07934: Added the `{gitlab-integration-host}/user-id` annotation to store GitLab's user ID (immutable) in user entities. Also includes addition of the `userIdMatchingUserEntityAnnotation` sign-in resolver that matches users by the new ID. + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + +## @backstage/plugin-catalog-backend@3.4.0-next.0 + +### Minor Changes + +- f1d29b4: Failures to connect catalog providers are now attributed to the module that provided the failing provider. This means that such failures will be reported as module startup failures rather than a failure to start the catalog plugin, and will therefore respect `onPluginModuleBootFailure` configuration instead. + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- 7455dae: Use node prefix on native imports +- 1e669cc: Migrate audit events reference docs to . +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-openapi-utils@0.6.6-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/plugin-permission-node@0.10.9-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.8-next.0 + +## @backstage/plugin-catalog-backend-module-gitlab@0.8.0-next.0 + +### Minor Changes + +- ff07934: Added the `{gitlab-integration-host}/user-id` annotation to store GitLab's user ID (immutable) in user entities. Also includes addition of the `userIdMatchingUserEntityAnnotation` sign-in resolver that matches users by the new ID. + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/backend-defaults@0.15.1-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.8-next.0 + +## @backstage/plugin-catalog-node@1.21.0-next.0 + +### Minor Changes + +- cfd8103: Promoted stable catalog extension points from alpha to main export. The following extension points are now exported from `@backstage/plugin-catalog-node` instead of `@backstage/plugin-catalog-node/alpha`: + + - `catalogLocationsExtensionPoint` and `CatalogLocationsExtensionPoint` + - `catalogProcessingExtensionPoint` and `CatalogProcessingExtensionPoint` + - `catalogAnalysisExtensionPoint` and `CatalogAnalysisExtensionPoint` + + The old alpha exports for these extension points are now deprecated with `@deprecated` markers pointing to the new stable exports. Please update your imports from `@backstage/plugin-catalog-node/alpha` to `@backstage/plugin-catalog-node`. + + Note: The `catalogModelExtensionPoint`, `catalogPermissionExtensionPoint`, and related types remain in alpha. + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/plugin-permission-node@0.10.9-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.8-next.0 + +## @backstage/app-defaults@1.7.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.6-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/core-app-api@1.19.4-next.0 + - @backstage/theme@0.7.1 + - @backstage/plugin-permission-react@0.4.40-next.0 + +## @backstage/backend-defaults@0.15.1-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 44f5d04: Minor internal restructure of the postgres config loading code +- 4fc7bf0: Bump to tar v7 +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/integration-aws-node@0.1.20-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/backend-dev-utils@0.1.7-next.0 + - @backstage/config-loader@1.10.8-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/cli-node@0.2.17-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/backend-app-api@1.5.0-next.0 + - @backstage/plugin-permission-node@0.10.9-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/backend-dev-utils@0.1.7-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports + +## @backstage/backend-dynamic-feature-service@0.7.9-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-catalog-backend@3.4.0-next.0 + - @backstage/backend-openapi-utils@0.6.6-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-search-backend-node@1.4.1-next.0 + - @backstage/backend-defaults@0.15.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/config-loader@1.10.8-next.0 + - @backstage/plugin-events-backend@0.5.11-next.0 + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/cli-common@0.1.18-next.0 + - @backstage/cli-node@0.2.17-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/plugin-app-node@0.1.42-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/plugin-permission-node@0.10.9-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/backend-openapi-utils@0.6.6-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/backend-test-utils@1.10.4-next.0 + +### Patch Changes + +- f1d29b4: Updated `startTestBackend` to support factory-based extension points (v1.1 format) in addition to the existing direct implementation format. +- 7455dae: Use node prefix on native imports +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/backend-defaults@0.15.1-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/backend-app-api@1.5.0-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/cli@0.35.3-next.0 + +### Patch Changes + +- cfd8103: Updated catalog provider module template to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of alpha exports. +- 7455dae: Use node prefix on native imports +- 4fc7bf0: Bump to tar v7 +- 122d39c: Completely removed support for the deprecated `app.experimental.packages` configuration. Replace existing usage directly with `app.packages`. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/config-loader@1.10.8-next.0 + - @backstage/eslint-plugin@0.2.1-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/cli-common@0.1.18-next.0 + - @backstage/cli-node@0.2.17-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/release-manifests@0.0.13 + - @backstage/types@1.2.2 + +## @backstage/cli-common@0.1.18-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/errors@1.2.7 + +## @backstage/cli-node@0.2.17-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/cli-common@0.1.18-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/codemods@0.1.54-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/cli-common@0.1.18-next.0 + +## @backstage/config-loader@1.10.8-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/cli-common@0.1.18-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/core-app-api@1.19.4-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/core-compat-api@0.5.7-next.0 + +### Patch Changes + +- c38b74d: Internal updates for blueprint moves to `@backstage/plugin-app-react`. +- 53b6549: Plugins in the new frontend system now have a `pluginId` field rather than `id` to better align with naming conventions used throughout the frontend and backend systems. The old field is still present but marked as deprecated. All internal code has been updated to prefer `pluginId` while maintaining backward compatibility by falling back to `id` when needed. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/plugin-app-react@0.1.1-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/core-components@0.18.6-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- cebfea7: Removed link styles from LinkButton to avoid styling inconsistencies related to import order. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/theme@0.7.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/core-plugin-api@1.12.2-next.0 + +### Patch Changes + +- 53b6549: Plugins in the new frontend system now have a `pluginId` field rather than `id` to better align with naming conventions used throughout the frontend and backend systems. The old field is still present but marked as deprecated. All internal code has been updated to prefer `pluginId` while maintaining backward compatibility by falling back to `id` when needed. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/create-app@0.7.9-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- c38b74d: Switched `next-app` template to use blueprint from `@backstage/plugin-app-react`. +- Updated dependencies + - @backstage/cli-common@0.1.18-next.0 + +## @backstage/dev-utils@1.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/ui@0.12.0-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/core-app-api@1.19.4-next.0 + - @backstage/app-defaults@1.7.5-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/integration-react@1.2.15-next.0 + - @backstage/theme@0.7.1 + +## @backstage/e2e-test-utils@0.1.2-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports + +## @backstage/eslint-plugin@0.2.1-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports + +## @backstage/frontend-app-api@0.14.1-next.0 + +### Patch Changes + +- 17e0eb3: Updated the behavior of the new API override logic to log a deprecation warning instead of rejecting the override and blocking app startup, as was originally intended. + +- 7edb810: Implemented support for the `internal` extension input option. + +- 492503a: Updated error reporting and app tree resolution logic to attribute errors to the correct extension and allow app startup to proceed more optimistically: + + - If an attachment fails to provide the required input data, the error is now attributed to the attachment rather than the parent extension. + - Singleton extension inputs will now only forward attachment errors if the input is required. + - Array extension inputs will now filter out failed attachments instead of failing the entire app tree resolution. + +- 122d39c: Completely removed support for the deprecated `app.experimental.packages` configuration. Replace existing usage directly with `app.packages`. + +- 9554c36: **DEPRECATED**: Deprecated support for multiple attachment points. + +- 53b6549: Plugins in the new frontend system now have a `pluginId` field rather than `id` to better align with naming conventions used throughout the frontend and backend systems. The old field is still present but marked as deprecated. All internal code has been updated to prefer `pluginId` while maintaining backward compatibility by falling back to `id` when needed. + +- 69d880e: Bump to latest zod to ensure it has the latest features + +- Updated dependencies + - @backstage/frontend-defaults@0.3.6-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/core-app-api@1.19.4-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/frontend-defaults@0.3.6-next.0 + +### Patch Changes + +- 17e0eb3: The `API_FACTORY_CONFLICT` error is now treated as a warning and will not prevent the app from starting. +- 122d39c: Completely removed support for the deprecated `app.experimental.packages` configuration. Replace existing usage directly with `app.packages`. +- c38b74d: Dependency update for tests. +- 53b6549: Plugins in the new frontend system now have a `pluginId` field rather than `id` to better align with naming conventions used throughout the frontend and backend systems. The old field is still present but marked as deprecated. All internal code has been updated to prefer `pluginId` while maintaining backward compatibility by falling back to `id` when needed. +- Updated dependencies + - @backstage/frontend-app-api@0.14.1-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/plugin-app@0.4.0-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/frontend-dynamic-feature-loader@0.1.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/config@1.3.6 + +## @backstage/frontend-test-utils@0.4.5-next.0 + +### Patch Changes + +- d7dd5bd: Fixed Router deprecation warning and switched to using new `RouterBlueprint` from `@backstage/plugin-app-api`. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/frontend-app-api@0.14.1-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/plugin-app@0.4.0-next.0 + - @backstage/plugin-app-react@0.1.1-next.0 + - @backstage/config@1.3.6 + - @backstage/test-utils@1.7.15-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/integration@1.19.3-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/integration-aws-node@0.1.20-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/integration-react@1.2.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.3-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/config@1.3.6 + +## @backstage/repo-tools@0.16.3-next.0 + +### Patch Changes + +- cd75ed0: Add newline to OpenAPI license template files. +- 7455dae: Use node prefix on native imports +- 4fc7bf0: Bump to tar v7 +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/config-loader@1.10.8-next.0 + - @backstage/cli-common@0.1.18-next.0 + - @backstage/cli-node@0.2.17-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + +## @techdocs/cli@1.10.5-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-defaults@0.15.1-next.0 + - @backstage/plugin-techdocs-node@1.14.1-next.0 + - @backstage/cli-common@0.1.18-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + +## @backstage/test-utils@1.7.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/core-app-api@1.19.4-next.0 + - @backstage/config@1.3.6 + - @backstage/theme@0.7.1 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-react@0.4.40-next.0 + +## @backstage/plugin-api-docs@0.13.4-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/plugin-catalog@1.32.3-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.8-next.0 + - @backstage/plugin-permission-react@0.4.40-next.0 + +## @backstage/plugin-app-backend@0.5.11-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/config-loader@1.10.8-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/plugin-app-node@0.1.42-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-app-node@0.1.42-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/config-loader@1.10.8-next.0 + +## @backstage/plugin-app-react@0.1.1-next.0 + +### Patch Changes + +- 409af72: Internal refactor to move implementation of blueprints from `@backstage/frontend-plugin-api` to this package. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + +## @backstage/plugin-app-visualizer@0.1.28-next.0 + +### Patch Changes + +- cb090b4: Bump react-aria-components to v1.14.0 +- c38b74d: Internal updates for blueprint moves to `@backstage/plugin-app-react`. +- 4d50e1f: Improved rendering performance of the details page. +- Updated dependencies + - @backstage/ui@0.12.0-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + +## @backstage/plugin-auth@0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.6-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/errors@1.2.7 + - @backstage/theme@0.7.1 + +## @backstage/plugin-auth-backend@0.26.1-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.11-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.12-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-backend@0.26.1-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.16-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.11-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + +## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.11-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.11-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.11-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-auth-backend-module-google-provider@0.3.11-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + +## @backstage/plugin-auth-backend-module-guest-provider@0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.11-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.11-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.16-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.4.12-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-backend@0.26.1-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-auth-backend-module-okta-provider@0.2.11-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.11-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + +## @backstage/plugin-auth-backend-module-openshift-provider@0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.11-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-auth-node@0.6.12-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-auth-react@0.1.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.6-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/errors@1.2.7 + +## @backstage/plugin-bitbucket-cloud-common@0.3.7-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/integration@1.19.3-next.0 + +## @backstage/plugin-catalog@1.32.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/core-compat-api@0.5.7-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/plugin-techdocs-react@1.3.8-next.0 + - @backstage/plugin-search-react@1.10.3-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.15-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.8-next.0 + - @backstage/plugin-permission-react@0.4.40-next.0 + - @backstage/plugin-scaffolder-common@1.7.6-next.0 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-catalog-backend-module-aws@0.4.20-next.0 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/integration-aws-node@0.1.20-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/backend-defaults@0.15.1-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.8-next.0 + - @backstage/plugin-kubernetes-common@0.9.10-next.0 + +## @backstage/plugin-catalog-backend-module-azure@0.3.14-next.0 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.8-next.0 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.11-next.0 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-openapi-utils@0.6.6-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.8-next.0 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.7-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.8-next.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.8-next.0 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.8-next.0 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.16-next.0 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-kubernetes-common@0.9.10-next.0 + +## @backstage/plugin-catalog-backend-module-gerrit@0.3.11-next.0 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.8-next.0 + +## @backstage/plugin-catalog-backend-module-gitea@0.1.9-next.0 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.8-next.0 + +## @backstage/plugin-catalog-backend-module-github@0.12.2-next.0 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.8-next.0 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.19-next.0 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- Updated dependencies + - @backstage/plugin-catalog-backend-module-github@0.12.2-next.0 + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/config@1.3.6 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.18-next.0 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- Updated dependencies + - @backstage/plugin-catalog-backend-module-gitlab@0.8.0-next.0 + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.9-next.0 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-catalog-backend@3.4.0-next.0 + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/backend-defaults@0.15.1-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-catalog-backend-module-ldap@0.12.2-next.0 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.8-next.0 + +## @backstage/plugin-catalog-backend-module-logs@0.1.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.4.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + +## @backstage/plugin-catalog-backend-module-msgraph@0.8.5-next.0 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.8-next.0 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.19-next.0 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.8-next.0 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.19-next.0 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.17-next.0 + +### Patch Changes + +- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.8-next.0 + - @backstage/plugin-scaffolder-common@1.7.6-next.0 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.6.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.13-next.0 + +## @backstage/plugin-catalog-common@1.1.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-catalog-graph@0.5.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-catalog-import@0.13.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.15-next.0 + - @backstage/plugin-catalog-common@1.1.8-next.0 + - @backstage/plugin-permission-react@0.4.40-next.0 + +## @backstage/plugin-catalog-react@1.21.6-next.0 + +### Patch Changes + +- f523983: Fixes a bug where the `EntityListProvider` would not correctly hydrate query parameters if more than 20 were provided for the same key. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/core-components@0.18.6-next.0 + - @backstage/core-compat-api@0.5.7-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/frontend-test-utils@0.4.5-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.15-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.8-next.0 + - @backstage/plugin-permission-react@0.4.40-next.0 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.6-next.0 + - @backstage/core-compat-api@0.5.7-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.13-next.0 + - @backstage/plugin-devtools-react@0.1.1-next.0 + +## @backstage/plugin-catalog-unprocessed-entities-common@0.0.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-config-schema@0.1.77-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.6-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-devtools@0.1.36-next.0 + +### Patch Changes + +- f2612c2: Fixes an issue where a user lacking permission to schedule tasks can now easily see the issue through a custom icon + tooltip. +- Updated dependencies + - @backstage/core-components@0.18.6-next.0 + - @backstage/core-compat-api@0.5.7-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-devtools-common@0.1.22-next.0 + - @backstage/plugin-devtools-react@0.1.1-next.0 + - @backstage/plugin-permission-react@0.4.40-next.0 + +## @backstage/plugin-devtools-backend@0.5.14-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/backend-defaults@0.15.1-next.0 + - @backstage/config-loader@1.10.8-next.0 + - @backstage/cli-common@0.1.18-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/plugin-permission-node@0.10.9-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-devtools-common@0.1.22-next.0 + +## @backstage/plugin-devtools-common@0.1.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/types@1.2.2 + +## @backstage/plugin-devtools-react@0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + +## @backstage/plugin-events-backend@0.5.11-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.6-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-events-backend-module-aws-sqs@0.4.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-events-backend-module-azure@0.2.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + +## @backstage/plugin-events-backend-module-bitbucket-server@0.1.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + +## @backstage/plugin-events-backend-module-gerrit@0.2.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + +## @backstage/plugin-events-backend-module-github@0.4.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-events-backend-module-gitlab@0.3.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/config@1.3.6 + +## @backstage/plugin-events-backend-module-google-pubsub@0.1.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-events-backend-module-kafka@0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-events-backend-test-utils@0.1.52-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.19-next.0 + +## @backstage/plugin-events-node@0.4.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-gateway-backend@1.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + +## @backstage/plugin-home@0.9.1-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/core-app-api@1.19.4-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/theme@0.7.1 + - @backstage/plugin-home-react@0.1.35-next.0 + +## @backstage/plugin-home-react@0.1.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.6-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + +## @backstage/plugin-kubernetes@0.12.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/plugin-kubernetes-react@0.5.16-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-kubernetes-common@0.9.10-next.0 + - @backstage/plugin-permission-react@0.4.40-next.0 + +## @backstage/plugin-kubernetes-backend@0.21.1-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/integration-aws-node@0.1.20-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-kubernetes-node@0.4.1-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/plugin-permission-node@0.10.9-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-kubernetes-common@0.9.10-next.0 + +## @backstage/plugin-kubernetes-cluster@0.0.34-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/plugin-kubernetes-react@0.5.16-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-kubernetes-common@0.9.10-next.0 + - @backstage/plugin-permission-react@0.4.40-next.0 + +## @backstage/plugin-kubernetes-common@0.9.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-kubernetes-node@0.4.1-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + - @backstage/plugin-kubernetes-common@0.9.10-next.0 + +## @backstage/plugin-kubernetes-react@0.5.16-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/core-components@0.18.6-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-kubernetes-common@0.9.10-next.0 + +## @backstage/plugin-mcp-actions-backend@0.1.8-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/backend-defaults@0.15.1-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-mui-to-bui@0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.12.0-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/theme@0.7.1 + +## @backstage/plugin-notifications@0.5.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.6-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/errors@1.2.7 + - @backstage/theme@0.7.1 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/plugin-signals-react@0.0.19-next.0 + +## @backstage/plugin-notifications-backend@0.6.2-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-notifications-node@0.2.23-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/plugin-signals-node@0.1.28-next.0 + +## @backstage/plugin-notifications-backend-module-email@0.3.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/integration-aws-node@0.1.20-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-notifications-node@0.2.23-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + - @backstage/plugin-notifications-common@0.2.0 + +## @backstage/plugin-notifications-backend-module-slack@0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-notifications-node@0.2.23-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-notifications-common@0.2.0 + +## @backstage/plugin-notifications-node@0.2.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/plugin-signals-node@0.1.28-next.0 + +## @backstage/plugin-org@0.6.49-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.8-next.0 + +## @backstage/plugin-org-react@0.1.47-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-permission-backend@0.7.8-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/plugin-permission-node@0.10.9-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/plugin-permission-node@0.10.9-next.0 + +## @backstage/plugin-permission-common@0.9.5-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-permission-node@0.10.9-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-permission-react@0.4.40-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/config@1.3.6 + +## @backstage/plugin-proxy-backend@0.6.10-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-proxy-node@0.1.12-next.0 + - @backstage/types@1.2.2 + +## @backstage/plugin-proxy-node@0.1.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + +## @backstage/plugin-scaffolder@1.35.2-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports + +- 2eeca03: Scaffolder form fields in the new frontend system now use a Utility API pattern instead of multiple attachment points. The `FormFieldBlueprint` now uses this new approach, and while form fields created with older versions still work, they will produce a deprecation warning and will stop working in a future release. + + As part of this change, the following alpha exports were removed: + + - `formFieldsApiRef` + - `ScaffolderFormFieldsApi` + +- 69d880e: Bump to latest zod to ensure it has the latest features + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/plugin-scaffolder-react@1.19.6-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/plugin-techdocs-react@1.3.8-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.15-next.0 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.8-next.0 + - @backstage/plugin-permission-react@0.4.40-next.0 + - @backstage/plugin-scaffolder-common@1.7.6-next.0 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-scaffolder-backend@3.1.2-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 4fc7bf0: Removed unused dependency +- 1e669cc: Migrate audit events reference docs to . +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.17-next.0 + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.2-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.19-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.18-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.9.5-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.11.2-next.0 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.18-next.0 + - @backstage/backend-openapi-utils@0.6.6-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.7-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/backend-defaults@0.15.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/plugin-permission-node@0.10.9-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.18-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.18-next.0 + - @backstage/plugin-scaffolder-common@1.7.6-next.0 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.19-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.2-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.18-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.2-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 14741e2: Fully enable API token functionality for Bitbucket-Cloud. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/plugin-bitbucket-cloud-common@0.3.7-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.18-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.20-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/backend-defaults@0.15.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.18-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.18-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-github@0.9.5-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 82ca951: cleaned up repo creation to make the unique portions explicit +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.11.2-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/plugin-notifications-node@0.2.23-next.0 + - @backstage/plugin-notifications-common@0.2.0 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.18-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.3.1-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.19-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-scaffolder-node-test-utils@0.3.8-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/types@1.2.2 + +## @backstage/plugin-scaffolder-common@1.7.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.3-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-scaffolder-node@0.12.4-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 4fc7bf0: Bump to tar v7 +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-common@1.7.6-next.0 + +## @backstage/plugin-scaffolder-node-test-utils@0.3.8-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-test-utils@1.10.4-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/types@1.2.2 + +## @backstage/plugin-scaffolder-react@1.19.6-next.0 + +### Patch Changes + +- 2eeca03: Scaffolder form fields in the new frontend system now use a Utility API pattern instead of multiple attachment points. The `FormFieldBlueprint` now uses this new approach, and while form fields created with older versions still work, they will produce a deprecation warning and will stop working in a future release. + + As part of this change, the following alpha exports were removed: + + - `formFieldsApi` + - `formFieldsApiRef` + - `ScaffolderFormFieldsApi` + +- 69d880e: Bump to latest zod to ensure it has the latest features + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/theme@0.7.1 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-permission-react@0.4.40-next.0 + - @backstage/plugin-scaffolder-common@1.7.6-next.0 + +## @backstage/plugin-search@1.5.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/plugin-search-react@1.10.3-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/plugin-search-backend@2.0.11-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.6-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-search-backend-node@1.4.1-next.0 + - @backstage/backend-defaults@0.15.1-next.0 + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/plugin-permission-node@0.10.9-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-search-backend-module-catalog@0.3.12-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-search-backend-node@1.4.1-next.0 + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.8-next.0 + +## @backstage/plugin-search-backend-module-elasticsearch@1.7.10-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/integration-aws-node@0.1.20-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-search-backend-node@1.4.1-next.0 + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/config@1.3.6 + +## @backstage/plugin-search-backend-module-explore@0.3.11-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-search-backend-node@1.4.1-next.0 + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/config@1.3.6 + +## @backstage/plugin-search-backend-module-pg@0.5.52-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-search-backend-node@1.4.1-next.0 + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/config@1.3.6 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.17-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-search-backend-node@1.4.1-next.0 + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/config@1.3.6 + +## @backstage/plugin-search-backend-module-techdocs@0.4.11-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-search-backend-node@1.4.1-next.0 + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/plugin-techdocs-node@1.14.1-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.8-next.0 + +## @backstage/plugin-search-backend-node@1.4.1-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-search-common@1.2.22-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/types@1.2.2 + +## @backstage/plugin-search-react@1.10.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.6-next.0 + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/theme@0.7.1 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/plugin-signals@0.0.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.6-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/theme@0.7.1 + - @backstage/types@1.2.2 + - @backstage/plugin-signals-react@0.0.19-next.0 + +## @backstage/plugin-signals-backend@0.3.12-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + - @backstage/plugin-signals-node@0.1.28-next.0 + +## @backstage/plugin-signals-node@0.1.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-signals-react@0.0.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/types@1.2.2 + +## @backstage/plugin-techdocs@1.16.3-next.0 + +### Patch Changes + +- 9e29545: Improve sidebars (nav/TOC) layout and scrolling +- 22dce2b: TechDocs addons in the new frontend system now use a Utility API pattern instead of multiple attachment points. The `AddonBlueprint` now uses this new approach, and while addons created with older versions still work, they will produce a deprecation warning and will stop working in a future release. +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/plugin-techdocs-react@1.3.8-next.0 + - @backstage/plugin-search-react@1.10.3-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.15-next.0 + - @backstage/theme@0.7.1 + - @backstage/plugin-auth-react@0.1.24-next.0 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-techdocs-addons-test-utils@2.0.2-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/plugin-techdocs@1.16.3-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/plugin-techdocs-react@1.3.8-next.0 + - @backstage/core-app-api@1.19.4-next.0 + - @backstage/plugin-search-react@1.10.3-next.0 + - @backstage/plugin-catalog@1.32.3-next.0 + - @backstage/integration-react@1.2.15-next.0 + - @backstage/test-utils@1.7.15-next.0 + +## @backstage/plugin-techdocs-backend@2.1.5-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/backend-defaults@0.15.1-next.0 + - @backstage/plugin-techdocs-node@1.14.1-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.33-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.6-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/plugin-techdocs-react@1.3.8-next.0 + - @backstage/integration-react@1.2.15-next.0 + +## @backstage/plugin-techdocs-node@1.14.1-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/integration-aws-node@0.1.20-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-techdocs-react@1.3.8-next.0 + +### Patch Changes + +- 22dce2b: TechDocs addons in the new frontend system now use a Utility API pattern instead of multiple attachment points. The `AddonBlueprint` now uses this new approach, and while addons created with older versions still work, they will produce a deprecation warning and will stop working in a future release. + + As part of this change, the `techDocsAddonDataRef` alpha export was removed. + +- Updated dependencies + - @backstage/core-components@0.18.6-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-user-settings@0.8.32-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/core-app-api@1.19.4-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/theme@0.7.1 + - @backstage/types@1.2.2 + - @backstage/plugin-signals-react@0.0.19-next.0 + - @backstage/plugin-user-settings-common@0.0.1 + +## @backstage/plugin-user-settings-backend@0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/backend-defaults@0.15.1-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-signals-node@0.1.28-next.0 + - @backstage/plugin-user-settings-common@0.0.1 + +## example-app@0.2.118-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.14.1-next.0 + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/cli@0.35.3-next.0 + - @backstage/ui@0.12.0-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/plugin-scaffolder@1.35.2-next.0 + - @backstage/plugin-api-docs@0.13.4-next.0 + - @backstage/plugin-scaffolder-react@1.19.6-next.0 + - @backstage/plugin-techdocs@1.16.3-next.0 + - @backstage/plugin-devtools@0.1.36-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/plugin-techdocs-react@1.3.8-next.0 + - @backstage/core-app-api@1.19.4-next.0 + - @backstage/plugin-home@0.9.1-next.0 + - @backstage/plugin-search-react@1.10.3-next.0 + - @backstage/plugin-catalog@1.32.3-next.0 + - @backstage/plugin-catalog-graph@0.5.7-next.0 + - @backstage/plugin-catalog-import@0.13.10-next.0 + - @backstage/plugin-kubernetes@0.12.16-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.34-next.0 + - @backstage/plugin-org@0.6.49-next.0 + - @backstage/plugin-search@1.5.4-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.33-next.0 + - @backstage/plugin-user-settings@0.8.32-next.0 + - @backstage/app-defaults@1.7.5-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/integration-react@1.2.15-next.0 + - @backstage/theme@0.7.1 + - @backstage/plugin-auth-react@0.1.24-next.0 + - @backstage/plugin-catalog-common@1.1.8-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.26-next.0 + - @backstage/plugin-mui-to-bui@0.2.4-next.0 + - @backstage/plugin-notifications@0.5.14-next.0 + - @backstage/plugin-permission-react@0.4.40-next.0 + - @backstage/plugin-signals@0.0.28-next.0 + +## example-app-next@0.0.32-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-defaults@0.3.6-next.0 + - @backstage/frontend-app-api@0.14.1-next.0 + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/plugin-app-visualizer@0.1.28-next.0 + - @backstage/cli@0.35.3-next.0 + - @backstage/ui@0.12.0-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/plugin-scaffolder@1.35.2-next.0 + - @backstage/plugin-api-docs@0.13.4-next.0 + - @backstage/core-compat-api@0.5.7-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/plugin-app@0.4.0-next.0 + - @backstage/plugin-scaffolder-react@1.19.6-next.0 + - @backstage/plugin-techdocs@1.16.3-next.0 + - @backstage/plugin-devtools@0.1.36-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/plugin-techdocs-react@1.3.8-next.0 + - @backstage/plugin-app-react@0.1.1-next.0 + - @backstage/core-app-api@1.19.4-next.0 + - @backstage/plugin-home@0.9.1-next.0 + - @backstage/plugin-auth@0.1.5-next.0 + - @backstage/plugin-search-react@1.10.3-next.0 + - @backstage/plugin-catalog@1.32.3-next.0 + - @backstage/plugin-catalog-graph@0.5.7-next.0 + - @backstage/plugin-catalog-import@0.13.10-next.0 + - @backstage/plugin-kubernetes@0.12.16-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.34-next.0 + - @backstage/plugin-org@0.6.49-next.0 + - @backstage/plugin-search@1.5.4-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.33-next.0 + - @backstage/plugin-user-settings@0.8.32-next.0 + - @backstage/app-defaults@1.7.5-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/integration-react@1.2.15-next.0 + - @backstage/theme@0.7.1 + - @backstage/plugin-auth-react@0.1.24-next.0 + - @backstage/plugin-catalog-common@1.1.8-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.26-next.0 + - @backstage/plugin-notifications@0.5.14-next.0 + - @backstage/plugin-permission-react@0.4.40-next.0 + - @backstage/plugin-signals@0.0.28-next.0 + +## app-next-example-plugin@0.0.32-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.6-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + +## example-backend@0.0.47-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.4.0-next.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.11-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.2.19-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.17-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.7.10-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.9.5-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.4.11-next.0 + - @backstage/plugin-search-backend-module-catalog@0.3.12-next.0 + - @backstage/plugin-search-backend-module-explore@0.3.11-next.0 + - @backstage/plugin-notifications-backend@0.6.2-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-search-backend-node@1.4.1-next.0 + - @backstage/plugin-kubernetes-backend@0.21.1-next.0 + - @backstage/plugin-permission-backend@0.7.8-next.0 + - @backstage/plugin-scaffolder-backend@3.1.2-next.0 + - @backstage/backend-defaults@0.15.1-next.0 + - @backstage/plugin-devtools-backend@0.5.14-next.0 + - @backstage/plugin-techdocs-backend@2.1.5-next.0 + - @backstage/plugin-signals-backend@0.3.12-next.0 + - @backstage/plugin-events-backend@0.5.11-next.0 + - @backstage/plugin-search-backend@2.0.11-next.0 + - @backstage/plugin-proxy-backend@0.6.10-next.0 + - @backstage/plugin-auth-backend@0.26.1-next.0 + - @backstage/plugin-app-backend@0.5.11-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.5.0-next.0 + - @backstage/plugin-mcp-actions-backend@0.1.8-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/plugin-permission-node@0.10.9-next.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.16-next.0 + - @backstage/plugin-auth-backend-module-openshift-provider@0.1.4-next.0 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.8-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.16-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.8-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.19-next.0 + +## e2e-test@0.2.37-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.18-next.0 + - @backstage/create-app@0.7.9-next.0 + - @backstage/errors@1.2.7 + +## @internal/frontend@0.0.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @internal/scaffolder@0.0.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/plugin-scaffolder-react@1.19.6-next.0 + +## techdocs-cli-embedded-app@0.2.117-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.35.3-next.0 + - @backstage/ui@0.12.0-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/plugin-techdocs@1.16.3-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/plugin-techdocs-react@1.3.8-next.0 + - @backstage/core-app-api@1.19.4-next.0 + - @backstage/plugin-catalog@1.32.3-next.0 + - @backstage/app-defaults@1.7.5-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/integration-react@1.2.15-next.0 + - @backstage/test-utils@1.7.15-next.0 + - @backstage/theme@0.7.1 + +## yarn-plugin-backstage@0.0.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.18-next.0 + - @backstage/errors@1.2.7 + - @backstage/release-manifests@0.0.13 + +## @internal/plugin-todo-list@1.0.48-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.6-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + +## @internal/plugin-todo-list-backend@1.0.47-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/errors@1.2.7 + +## @internal/plugin-todo-list-common@1.0.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.5-next.0 diff --git a/docs/releases/v1.48.0-next.1-changelog.md b/docs/releases/v1.48.0-next.1-changelog.md new file mode 100644 index 0000000000..d15b1a220a --- /dev/null +++ b/docs/releases/v1.48.0-next.1-changelog.md @@ -0,0 +1,29587 @@ +# Release v1.48.0-next.1 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.48.0-next.1](https://backstage.github.io/upgrade-helper/?to=1.48.0-next.1) + +## @backstage/backend-test-utils@1.10.5-next.0 + +# @backstage/backend-test-utils + +## 1.10.4-next.0 + +### Patch Changes + +- f1d29b4: Updated `startTestBackend` to support factory-based extension points (v1.1 format) in addition to the existing direct implementation format. +- 7455dae: Use node prefix on native imports +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/backend-defaults@0.15.1-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/backend-app-api@1.5.0-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 1.10.3 + +### Patch Changes + +- 872eb91: Upgrade `zod-to-json-schema` to latest version +- Updated dependencies + - @backstage/backend-defaults@0.15.0 + - @backstage/backend-plugin-api@1.6.1 + - @backstage/backend-app-api@1.4.1 + - @backstage/plugin-auth-node@0.6.11 + - @backstage/plugin-permission-common@0.9.4 + +## 1.10.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + - @backstage/backend-app-api@1.4.0 + - @backstage/plugin-auth-node@0.6.10 + +## 1.10.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.1-next.0 + - @backstage/backend-app-api@1.4.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-events-node@0.4.18 + - @backstage/plugin-permission-common@0.9.3 + +## 1.10.2 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 8be23a4: Switched `textextensions` dependency for `text-extensions`. +- 5a737e1: Fix PostgreSQL 18 `TestDatabases` by pinning the data directory +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-defaults@0.14.0 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-app-api@1.4.0 + - @backstage/backend-plugin-api@1.6.0 + +## 1.10.2-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 8be23a4: Switched `textextensions` dependency for `text-extensions`. +- 5a737e1: Fix PostgreSQL 18 `TestDatabases` by pinning the data directory +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/backend-app-api@1.4.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + +## 1.10.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-app-api@1.4.0-next.0 + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + +## 1.10.0 + +### Minor Changes + +- d57b13b: Added support for Postgres 18 to the available `TestDatabases`. + + Note that the set of _default_ databases to test against for users of the `TestDatabases` class was also updated to include Postgres 14 and 18, instead of 13 and 17. If you need to override this, you can pass in an explicit `ids` argument, for example `ids: ['POSTGRES_17', 'POSTGRES_13', 'SQLITE_3']`. + +### Patch Changes + +- f3001fd: Tweak some of the mock services to have more precise types +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/backend-app-api@1.3.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-events-node@0.4.17 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config@1.3.6 + +## 1.10.0-next.2 + +### Patch Changes + +- f3001fd: Tweak some of the mock services to have more precise types +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.2 + +## 1.10.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.1-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/backend-app-api@1.3.0-next.1 + - @backstage/plugin-permission-common@0.9.3-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + - @backstage/plugin-events-node@0.4.17-next.1 + +## 1.10.0-next.0 + +### Minor Changes + +- d57b13b: Added support for Postgres 18 to the available `TestDatabases`. + + Note that the set of _default_ databases to test against for users of the `TestDatabases` class was also updated to include Postgres 14 and 18, instead of 13 and 17. If you need to override this, you can pass in an explicit `ids` argument, for example `ids: ['POSTGRES_17', 'POSTGRES_13', 'SQLITE_3']`. + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-defaults@0.13.1-next.0 + - @backstage/plugin-events-node@0.4.17-next.0 + - @backstage/plugin-auth-node@0.6.9-next.0 + - @backstage/backend-app-api@1.2.9-next.0 + - @backstage/config@1.3.6-next.0 + - @backstage/backend-plugin-api@1.4.5-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3-next.0 + +## 1.9.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.0 + - @backstage/backend-app-api@1.2.8 + - @backstage/config@1.3.5 + - @backstage/backend-plugin-api@1.4.4 + - @backstage/plugin-auth-node@0.6.8 + - @backstage/plugin-events-node@0.4.16 + - @backstage/plugin-permission-common@0.9.2 + +## 1.9.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-defaults@0.13.0-next.1 + - @backstage/backend-app-api@1.2.8-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + +## 1.9.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.0-next.0 + - @backstage/backend-app-api@1.2.7 + - @backstage/plugin-auth-node@0.6.7 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.15 + - @backstage/plugin-permission-common@0.9.1 + +## 1.9.0 + +### Minor Changes + +- 4e2c237: The `mockServices.rootConfig()` instance now has an `update` method that can be used to test configuration subscriptions and updates. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.12.1 + - @backstage/plugin-auth-node@0.6.7 + - @backstage/plugin-events-node@0.4.15 + - @backstage/types@1.2.2 + - @backstage/backend-app-api@1.2.7 + - @backstage/backend-plugin-api@1.4.3 + +## 1.9.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.7-next.0 + - @backstage/backend-defaults@0.12.1-next.0 + - @backstage/backend-plugin-api@1.4.3-next.0 + - @backstage/backend-app-api@1.2.7-next.0 + - @backstage/plugin-events-node@0.4.15-next.0 + +## 1.9.0-next.0 + +### Minor Changes + +- 4e2c237: The `mockServices.rootConfig()` instance now has an `update` method that can be used to test configuration subscriptions and updates. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@1.2.6 + - @backstage/backend-defaults@0.12.0 + - @backstage/backend-plugin-api@1.4.2 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-auth-node@0.6.6 + - @backstage/plugin-events-node@0.4.14 + - @backstage/plugin-permission-common@0.9.1 + +## 1.8.0 + +### Minor Changes + +- dffaf70: Switched out `mockServices.scheduler` to use a mocked implementation instead of the default scheduler implementation. This implementation runs any scheduled tasks immediately on startup, as long as they don't have an initial delay or a manual trigger. After the initial run, the tasks are never run again unless manually triggered. + +### Patch Changes + +- 279e1f7: Updated the type definition of `mockErrorHandler` to ensure that it is used correctly. + + ```ts + // This is wrong and will now result in a type error + app.use(mockErrorHandler); + + // This is the correct usage + app.use(mockErrorHandler()); + ``` + +- 3a7dad9: Updated `better-sqlite3` to v12 + +- Updated dependencies + - @backstage/backend-defaults@0.12.0 + - @backstage/backend-app-api@1.2.6 + - @backstage/plugin-auth-node@0.6.6 + - @backstage/backend-plugin-api@1.4.2 + - @backstage/plugin-events-node@0.4.14 + +## 1.7.1-next.0 + +### Patch Changes + +- 3a7dad9: Updated `better-sqlite3` to v12 +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/backend-app-api@1.2.6-next.0 + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.1 + +## 1.7.0 + +### Minor Changes + +- ead925a: Add a standard `toString` on credentials objects + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/backend-defaults@0.11.1 + - @backstage/backend-app-api@1.2.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + - @backstage/plugin-events-node@0.4.13 + +## 1.7.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/backend-app-api@1.2.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## 1.7.0-next.0 + +### Minor Changes + +- ead925a: Add a standard `toString` on credentials objects + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/backend-app-api@1.2.4 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-events-node@0.4.12 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0 + +## 1.6.0 + +### Minor Changes + +- 6dfb7be: Added `mockServices.permissions()` that can return actual results. +- c999c25: Added an `actionsRegistryServiceMock` and `actionsServiceMock` to `/alpha` export for the experimental services. + + This allows you to write tests for your actions by doing something similar to the following: + + ```ts + import { actionsRegistryServiceMock } from '@backstage/backend-test-utils/alpha'; + + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockCatalog = catalogServiceMock({ + entities: [ + ... + ], + }); + + createGetCatalogEntityAction({ + catalog: mockCatalog, + actionsRegistry: mockActionsRegistry, + }); + + await expect( + mockActionsRegistry.invoke({ + id: 'test:get-catalog-entity', + input: { name: 'test' }, + }), + ).resolves.toEqual(...) + ``` + +### Patch Changes + +- 12c1fd4: Make the `user` credentials mock behave more like production +- Updated dependencies + - @backstage/backend-defaults@0.11.0 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-app-api@1.2.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12 + - @backstage/plugin-permission-common@0.9.0 + +## 1.6.0-next.2 + +### Patch Changes + +- 12c1fd4: Make the `user` credentials mock behave more like production +- Updated dependencies + - @backstage/backend-defaults@0.11.0-next.2 + - @backstage/backend-app-api@1.2.4-next.2 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 1.6.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-app-api@1.2.4-next.1 + - @backstage/backend-defaults@0.10.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 1.6.0-next.0 + +### Minor Changes + +- c999c25: Added mock implementations for `ActionsService` and `ActionsRegistryService` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/backend-defaults@0.10.1-next.0 + - @backstage/backend-app-api@1.2.4-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + +## 1.5.0 + +### Minor Changes + +- b3832d1: Add a functional `mockServices.events()` +- c6bc67d: Added Valkey support alongside Redis in backend-defaults cache clients, using the new Keyv Valkey package. Also extended backend-test-utils to support Valkey in tests. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0 + - @backstage/backend-app-api@1.2.3 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11 + +## 1.5.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.3 + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-app-api@1.2.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11-next.2 + +## 1.5.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-app-api@1.2.3-next.1 + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11-next.1 + +## 1.5.0-next.1 + +### Minor Changes + +- c6bc67d: Added Valkey support alongside Redis in backend-defaults cache clients, using the new Keyv Valkey package. Also extended backend-test-utils to support Valkey in tests. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.1 + - @backstage/backend-app-api@1.2.3-next.1 + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11-next.1 + +## 1.5.0-next.0 + +### Minor Changes + +- b3832d1: Add a functional `mockServices.events()` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.9.1-next.0 + - @backstage/backend-app-api@1.2.3-next.0 + - @backstage/backend-plugin-api@1.3.1-next.0 + - @backstage/plugin-auth-node@0.6.3-next.0 + - @backstage/plugin-events-node@0.4.11-next.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 1.4.0 + +### Minor Changes + +- cf4eb13: Added `actor` property to `BackstageUserPrincipal` containing the subject of the last service (if any) who performed authentication on behalf of the user. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.9.0 + - @backstage/backend-plugin-api@1.3.0 + - @backstage/backend-app-api@1.2.2 + - @backstage/plugin-auth-node@0.6.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.10 + +## 1.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.9.0-next.2 + - @backstage/backend-app-api@1.2.1 + - @backstage/backend-plugin-api@1.2.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-auth-node@0.6.1 + - @backstage/plugin-events-node@0.4.9 + +## 1.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.9.0-next.1 + - @backstage/backend-app-api@1.2.1 + - @backstage/plugin-auth-node@0.6.1 + - @backstage/backend-plugin-api@1.2.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.9 + +## 1.3.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.9.0-next.0 + - @backstage/backend-app-api@1.2.1 + - @backstage/plugin-auth-node@0.6.1 + - @backstage/backend-plugin-api@1.2.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.9 + +## 1.3.1 + +### Patch Changes + +- c3a91d5: Added support for PostgreSQL version 17 +- 37c6510: Moved `@types/jest` to `devDependencies`. +- Updated dependencies + - @backstage/backend-defaults@0.8.2 + - @backstage/plugin-auth-node@0.6.1 + - @backstage/plugin-events-node@0.4.9 + - @backstage/backend-app-api@1.2.1 + - @backstage/backend-plugin-api@1.2.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 1.3.1-next.2 + +### Patch Changes + +- 37c6510: Moved `@types/jest` to `devDependencies`. +- Updated dependencies + - @backstage/backend-defaults@0.8.2-next.2 + - @backstage/plugin-events-node@0.4.9-next.2 + - @backstage/backend-app-api@1.2.1-next.2 + - @backstage/backend-plugin-api@1.2.1-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-auth-node@0.6.1-next.1 + +## 1.3.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1-next.1 + - @backstage/backend-app-api@1.2.1-next.1 + - @backstage/backend-defaults@0.8.2-next.1 + - @backstage/backend-plugin-api@1.2.1-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.9-next.1 + +## 1.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.8.2-next.0 + - @backstage/backend-app-api@1.2.1-next.0 + - @backstage/plugin-auth-node@0.6.1-next.0 + - @backstage/backend-plugin-api@1.2.1-next.0 + - @backstage/plugin-events-node@0.4.9-next.0 + +## 1.3.0 + +### Minor Changes + +- dd05a97: Added mocks for the new `PermissionsRegistryService`. +- a4aa244: This change introduces mocks for the `auditor` service. + +### Patch Changes + +- f866b86: Internal refactor to use explicit `require` for lazy-loading dependency. +- fb051f2: Sync feature installation compatibility logic with `@backstage/backend-app-api`. +- 72cddf2: Added the new `getPermissionRuleset` method to `mockServices.permissionsRegistry`. +- Updated dependencies + - @backstage/backend-defaults@0.8.0 + - @backstage/backend-plugin-api@1.2.0 + - @backstage/plugin-auth-node@0.6.0 + - @backstage/backend-app-api@1.2.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.8 + +## 1.3.0-next.3 + +### Patch Changes + +- 72cddf2: Added the new `getPermissionRuleset` method to `mockServices.permissionsRegistry`. +- Updated dependencies + - @backstage/backend-defaults@0.8.0-next.3 + - @backstage/backend-plugin-api@1.2.0-next.2 + - @backstage/backend-app-api@1.2.0-next.3 + - @backstage/plugin-auth-node@0.6.0-next.2 + - @backstage/plugin-events-node@0.4.8-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 1.3.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.1 + - @backstage/plugin-auth-node@0.6.0-next.1 + - @backstage/backend-defaults@0.8.0-next.2 + - @backstage/backend-app-api@1.2.0-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.8-next.1 + +## 1.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@1.1.2-next.1 + - @backstage/backend-defaults@0.8.0-next.1 + - @backstage/backend-plugin-api@1.2.0-next.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-auth-node@0.5.7-next.0 + - @backstage/plugin-events-node@0.4.8-next.0 + +## 1.3.0-next.0 + +### Minor Changes + +- dd05a97: Added mocks for the new `PermissionsRegistryService`. +- a4aa244: This change introduces mocks for the `auditor` service. + +### Patch Changes + +- f866b86: Internal refactor to use explicit `require` for lazy-loading dependency. +- fb051f2: Sync feature installation compatibility logic with `@backstage/backend-app-api`. +- Updated dependencies + - @backstage/backend-defaults@0.8.0-next.0 + - @backstage/backend-plugin-api@1.2.0-next.0 + - @backstage/backend-app-api@1.1.2-next.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-auth-node@0.5.7-next.0 + - @backstage/plugin-events-node@0.4.8-next.0 + +## 1.2.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.7.0 + - @backstage/backend-app-api@1.1.1 + - @backstage/types@1.2.1 + - @backstage/plugin-auth-node@0.5.6 + - @backstage/backend-plugin-api@1.1.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-events-node@0.4.7 + +## 1.2.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.2.1-next.0 + - @backstage/backend-app-api@1.1.1-next.1 + - @backstage/backend-defaults@0.7.0-next.1 + - @backstage/backend-plugin-api@1.1.1-next.1 + - @backstage/config@1.3.2-next.0 + - @backstage/errors@1.2.7-next.0 + - @backstage/plugin-auth-node@0.5.6-next.1 + - @backstage/plugin-events-node@0.4.7-next.1 + +## 1.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.7.0-next.0 + - @backstage/backend-app-api@1.1.1-next.0 + - @backstage/plugin-auth-node@0.5.6-next.0 + - @backstage/backend-plugin-api@1.1.1-next.0 + - @backstage/config@1.3.1 + - @backstage/errors@1.2.6 + - @backstage/types@1.2.0 + - @backstage/plugin-events-node@0.4.7-next.0 + +## 1.2.0 + +### Minor Changes + +- de6f280: **BREAKING** Upgraded @keyv/redis and keyv packages to resolve a bug related to incorrect resolution of cache keys. + + This is a breaking change for clients using the `redis` store for cache with `useRedisSets` option set to false since cache keys will be calculated differently (without the sets:namespace: prefix). For clients with default configuration (or useRedisSets set to false) the cache keys will stay the same, but since @keyv/redis library no longer supports redis sets they won't be utilised anymore. + + If you were using `useRedisSets` option in configuration make sure to remove it from `app-config.yaml`: + + ```diff + backend: + cache: + store: redis + connection: redis://user:pass@cache.example.com:6379 + - useRedisSets: false + ``` + +### Patch Changes + +- 0e9c9fa: Mock the new `RootLifecycleService.addBeforeShutdownHook` method. +- Updated dependencies + - @backstage/backend-defaults@0.6.0 + - @backstage/plugin-auth-node@0.5.5 + - @backstage/backend-plugin-api@1.1.0 + - @backstage/backend-app-api@1.1.0 + - @backstage/plugin-events-node@0.4.6 + - @backstage/errors@1.2.6 + - @backstage/config@1.3.1 + - @backstage/types@1.2.0 + +## 1.2.0-next.2 + +### Patch Changes + +- 0e9c9fa: Mock the new `RootLifecycleService.addBeforeShutdownHook` method. +- Updated dependencies + - @backstage/backend-defaults@0.6.0-next.2 + - @backstage/backend-plugin-api@1.1.0-next.2 + - @backstage/backend-app-api@1.1.0-next.2 + - @backstage/errors@1.2.6-next.0 + - @backstage/plugin-auth-node@0.5.5-next.2 + - @backstage/plugin-events-node@0.4.6-next.2 + - @backstage/config@1.3.1-next.0 + - @backstage/types@1.2.0 + +## 1.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.5-next.1 + - @backstage/backend-defaults@0.6.0-next.1 + - @backstage/backend-app-api@1.1.0-next.1 + - @backstage/backend-plugin-api@1.1.0-next.1 + - @backstage/config@1.3.0 + - @backstage/errors@1.2.5 + - @backstage/types@1.2.0 + - @backstage/plugin-events-node@0.4.6-next.1 + +## 1.2.0-next.0 + +### Minor Changes + +- de6f280: **BREAKING** Upgraded @keyv/redis and keyv packages to resolve a bug related to incorrect resolution of cache keys. + + This is a breaking change for clients using the `redis` store for cache with `useRedisSets` option set to false since cache keys will be calculated differently (without the sets:namespace: prefix). For clients with default configuration (or useRedisSets set to false) the cache keys will stay the same, but since @keyv/redis library no longer supports redis sets they won't be utilised anymore. + + If you were using `useRedisSets` option in configuration make sure to remove it from `app-config.yaml`: + + ```diff + backend: + cache: + store: redis + connection: redis://user:pass@cache.example.com:6379 + - useRedisSets: false + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.6.0-next.0 + - @backstage/backend-plugin-api@1.0.3-next.0 + - @backstage/backend-app-api@1.0.3-next.0 + - @backstage/plugin-events-node@0.4.6-next.0 + - @backstage/plugin-auth-node@0.5.5-next.0 + - @backstage/config@1.3.0 + - @backstage/errors@1.2.5 + - @backstage/types@1.2.0 + +## 1.1.0 + +### Minor Changes + +- 5064827: Made it possible to construct `mockServices.database` with a given knex instance + +### Patch Changes + +- 7aae8e3: The `mockServices.discovery.factory()` factory now uses the mocked discovery service as its implementation, avoid the need for configuration. +- eb82994: Removed unused `msw` dependency. +- 4e58bc7: Upgrade to uuid v11 internally +- Updated dependencies + - @backstage/config@1.3.0 + - @backstage/plugin-events-node@0.4.5 + - @backstage/backend-defaults@0.5.3 + - @backstage/types@1.2.0 + - @backstage/plugin-auth-node@0.5.4 + - @backstage/backend-plugin-api@1.0.2 + - @backstage/backend-app-api@1.0.2 + - @backstage/errors@1.2.5 + +## 1.1.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.5-next.3 + - @backstage/backend-defaults@0.5.3-next.3 + - @backstage/backend-app-api@1.0.2-next.2 + - @backstage/backend-plugin-api@1.0.2-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.5.4-next.2 + +## 1.1.0-next.2 + +### Minor Changes + +- 5064827: Made it possible to construct `mockServices.database` with a given knex instance + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.3-next.2 + - @backstage/plugin-events-node@0.4.5-next.2 + - @backstage/plugin-auth-node@0.5.4-next.2 + - @backstage/backend-app-api@1.0.2-next.2 + - @backstage/backend-plugin-api@1.0.2-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.0.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@1.0.2-next.1 + - @backstage/backend-defaults@0.5.3-next.1 + - @backstage/backend-plugin-api@1.0.2-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.5.4-next.1 + - @backstage/plugin-events-node@0.4.4-next.1 + +## 1.0.3-next.0 + +### Patch Changes + +- 7aae8e3: The `mockServices.discovery.factory()` factory now uses the mocked discovery service as its implementation, avoid the need for configuration. +- eb82994: Removed unused `msw` dependency. +- Updated dependencies + - @backstage/plugin-events-node@0.4.3-next.0 + - @backstage/plugin-auth-node@0.5.4-next.0 + - @backstage/backend-defaults@0.5.3-next.0 + - @backstage/backend-app-api@1.0.2-next.0 + - @backstage/backend-plugin-api@1.0.2-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.0.1 + +### Patch Changes + +- fd6e6f4: build(deps): bump `cookie` from 0.6.0 to 0.7.0 +- a19ce00: The `createMockDirectory` cleanup strategy has been changed, no longer requiring it to be called outside individual tests. +- 9cc7dd6: Minor doc string changes +- Updated dependencies + - @backstage/backend-defaults@0.5.1 + - @backstage/backend-app-api@1.0.1 + - @backstage/plugin-auth-node@0.5.3 + - @backstage/plugin-events-node@0.4.1 + - @backstage/backend-plugin-api@1.0.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.0.1-next.2 + +### Patch Changes + +- fd6e6f4: build(deps): bump `cookie` from 0.6.0 to 0.7.0 +- Updated dependencies + - @backstage/backend-app-api@1.0.1-next.1 + - @backstage/backend-defaults@0.5.1-next.2 + - @backstage/plugin-auth-node@0.5.3-next.1 + - @backstage/backend-plugin-api@1.0.1-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.4.1-next.1 + +## 1.0.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.1-next.1 + - @backstage/backend-app-api@1.0.1-next.0 + - @backstage/backend-plugin-api@1.0.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.5.3-next.0 + - @backstage/plugin-events-node@0.4.1-next.0 + +## 1.0.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.1-next.0 + - @backstage/backend-app-api@1.0.1-next.0 + - @backstage/plugin-events-node@0.4.1-next.0 + - @backstage/plugin-auth-node@0.5.3-next.0 + - @backstage/backend-plugin-api@1.0.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.0.0 + +### Major Changes + +- ec1b4be: Release 1.0 of the new backend system! :tada: + + The backend system is finally getting promoted to 1.0.0. This means that the API is now stable and breaking changes should not occur until version 2.0.0, see our [package versioning policy](https://backstage.io/docs/overview/versioning-policy/#package-versioning-policy) for more information what this means. + + This release also marks the end of the old backend system based on `createRouter` exports. Going forward backend plugins packages will start to deprecate and later this year remove exports supporting the old backend system. If you would like to help out with this transition, see or consult the [migration guide](https://backstage.io/docs/backend-system/building-plugins-and-modules/migrating/#remove-support-for-the-old-backend-system). + +### Minor Changes + +- 19ff127: **BREAKING**: Removed service mocks for the identity and token manager services, which have been removed from `@backstage/backend-plugin-api`. +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- 710f621: Added missing service mock for `mockServices.rootConfig.mock`, and fixed the definition of `mockServices.rootHttpRouter.factory` to not have a duplicate callback. +- f421d2a: Make MySQL pool settings a bit more lax +- 0363bf1: There is a new `mockErrorHandler` utility to help in mocking the error middleware in tests. +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-defaults@0.5.0 + - @backstage/backend-app-api@1.0.0 + - @backstage/backend-plugin-api@1.0.0 + - @backstage/plugin-auth-node@0.5.2 + - @backstage/plugin-events-node@0.4.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.0.0-next.2 + +### Major Changes + +- ec1b4be: Release 1.0 of the new backend system! :tada: + + The backend system is finally getting promoted to 1.0.0. This means that the API is now stable and breaking changes should not occur until version 2.0.0, see our [package versioning policy](https://backstage.io/docs/overview/versioning-policy/#package-versioning-policy) for more information what this means. + + This release also marks the end of the old backend system based on `createRouter` exports. Going forward backend plugins packages will start to deprecate and later this year remove exports supporting the old backend system. If you would like to help out with this transition, see or consult the [migration guide](https://backstage.io/docs/backend-system/building-plugins-and-modules/migrating/#remove-support-for-the-old-backend-system). + +### Patch Changes + +- f421d2a: Make MySQL pool settings a bit more lax +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-app-api@1.0.0-next.2 + - @backstage/backend-defaults@0.5.0-next.2 + - @backstage/plugin-auth-node@0.5.2-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.4.0-next.2 + +## 0.6.0-next.1 + +### Patch Changes + +- 710f621: Added missing service mock for `mockServices.rootConfig.mock`, and fixed the definition of `mockServices.rootHttpRouter.factory` to not have a duplicate callback. +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-app-api@0.10.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## 0.6.0-next.0 + +### Minor Changes + +- 19ff127: **BREAKING**: Removed service mocks for the identity and token manager services, which have been removed from `@backstage/backend-plugin-api`. +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- 0363bf1: There is a new `mockErrorHandler` utility to help in mocking the error middleware in tests. +- Updated dependencies + - @backstage/backend-app-api@0.10.0-next.0 + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-defaults@0.5.0-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.5.0 + +### Minor Changes + +- 861f162: **BREAKING**: Removed these deprecated helpers: + + - `setupRequestMockHandlers` is removed; use `registerMswTestHooks` instead. + - `MockDirectoryOptions` is removed; use `CreateMockDirectoryOptions` instead. + + Stopped exporting the deprecated and internal `isDockerDisabledForTests` helper. + + Removed `get` method from `ServiceFactoryTester` which is replaced by `getSubject` + +### Patch Changes + +- 8b13183: Internal updates to support latest version of `BackendFeauture`s from `@backstage/backend-plugin-api`. +- b63d378: Update internal imports +- 7c5f3b0: Update the `ServiceFactoryTester` to be able to test services that enables multi implementation installation. +- 4e79d19: The default services for `startTestBackend` and `ServiceFactoryTester` now includes the Root Health Service. +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/backend-app-api@0.9.0 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.9 + +## 0.4.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-defaults@0.4.2-next.3 + - @backstage/backend-app-api@0.8.1-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.5.0-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + +## 0.4.5-next.2 + +### Patch Changes + +- 8b13183: Internal updates to support latest version of `BackendFeauture`s from `@backstage/backend-plugin-api`. +- 7c5f3b0: Update the `ServiceFactoryTester` to be able to test services that enables multi implementation installation. +- Updated dependencies + - @backstage/backend-defaults@0.4.2-next.2 + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-app-api@0.8.1-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.4.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-app-api@0.8.1-next.1 + - @backstage/backend-defaults@0.4.2-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + - @backstage/plugin-events-node@0.3.9-next.1 + +## 0.4.5-next.0 + +### Patch Changes + +- 4e79d19: The default services for `startTestBackend` and `ServiceFactoryTester` now includes the Root Health Service. +- Updated dependencies + - @backstage/backend-defaults@0.4.2-next.0 + - @backstage/backend-app-api@0.8.1-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-events-node@0.3.9-next.0 + +## 0.4.4 + +### Patch Changes + +- 2f99178: The `ServiceFactoryTest.get` method was deprecated and the `ServiceFactoryTest.getSubject` should be used instead. The `getSubject` method has the same behavior, but has a better method name to indicate that the service instance returned is the subject currently being tested. +- edf5cc3: The function `isDockerDisabledForTests` is deprecated and will no longer be exported in the near future as it should only be used internally. +- b05e1e1: Service factories exported by this package have been updated to use the new service factory format that doesn't use a callback. +- fce7887: Added mock for the Root Health Service in `mockServices`. +- 906c817: Updated `startTestBackend` and `ServiceFactoryTester` to only accept plain service factory or backend feature objects, no longer supporting the callback form. This lines up with the changes to `@backstage/backend-plugin-api` and should not require any code changes. +- 95a3a0b: Rename frontend and backend `setupRequestMockHandlers` methods to `registerMswTestHooks`. +- b9ed1bb: bumped better-sqlite3 from ^9.0.0 to ^11.0.0 +- 98ccf00: Internal refactor of `mockServices.httpAuth.factory` to allow it to still be constructed with options, but without declaring options via `createServiceFactory`. +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-defaults@0.4.0 + - @backstage/backend-app-api@0.8.0 + - @backstage/plugin-events-node@0.3.8 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.4.4-next.1 + +### Patch Changes + +- b9ed1bb: bumped better-sqlite3 from ^9.0.0 to ^11.0.0 +- Updated dependencies + - @backstage/backend-defaults@0.3.4-next.1 + - @backstage/backend-app-api@0.7.10-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + +## 0.4.3-next.0 + +### Patch Changes + +- fce7887: Added mock for the Root Health Service in `mockServices`. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.21-next.0 + - @backstage/backend-defaults@0.3.3-next.0 + - @backstage/backend-app-api@0.7.9-next.0 + - @backstage/plugin-auth-node@0.4.16-next.0 + - @backstage/plugin-events-node@0.3.7-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.4.0 + +### Minor Changes + +- 805cbe7: Added `TestCaches` that functions just like `TestDatabases` + +### Patch Changes + +- 78a0b08: Internal refactor to handle `BackendFeature` contract change. +- 9e63318: Made it possible to give access restrictions to `mockCredentials.service` +- 006b3e8: The type `MockDirectoryOptions` was renamed to `CreateMockDirectoryOptions` so that it's clear these options are exclusive to the mock directory factory. +- 0634fdc: Refactored `TestDatabases` to no longer depend on `backend-common` +- 6a576dc: Fix the logger service mock to prevent returning `undefined` from the `child` method. +- 6c11f6e: Use imports from backend-defaults instead of the deprecated ones from backend-app-api +- Updated dependencies + - @backstage/backend-app-api@0.7.6 + - @backstage/backend-plugin-api@0.6.19 + - @backstage/plugin-auth-node@0.4.14 + - @backstage/backend-defaults@0.3.0 + - @backstage/plugin-events-node@0.3.5 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.4.0-next.3 + +### Patch Changes + +- 006b3e8: The type `MockDirectoryOptions` was renamed to `CreateMockDirectoryOptions` so that it's clear these options are exclusive to the mock directory factory. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.3 + - @backstage/plugin-auth-node@0.4.14-next.3 + - @backstage/plugin-events-node@0.3.5-next.2 + - @backstage/backend-app-api@0.7.6-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.4.0-next.2 + +### Patch Changes + +- 0634fdc: Refactored `TestDatabases` to no longer depend on `backend-common` +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.2 + - @backstage/backend-app-api@0.7.6-next.2 + - @backstage/plugin-auth-node@0.4.14-next.2 + - @backstage/plugin-events-node@0.3.5-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.4.0-next.1 + +### Minor Changes + +- 805cbe7: Added `TestCaches` that functions just like `TestDatabases` + +### Patch Changes + +- 9e63318: Made it possible to give access restrictions to `mockCredentials.service` +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + +## 0.3.9-next.0 + +### Patch Changes + +- 6a576dc: Fix the logger service mock to prevent returning `undefined` from the `child` method. +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.3.8 + +### Patch Changes + +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- 7e5a50d: added `eventsServiceFactory` to `defaultServiceFactories` to resolve issue where different instances of the EventsServices could be used +- Updated dependencies + - @backstage/backend-app-api@0.7.3 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-events-node@0.3.4 + - @backstage/plugin-auth-node@0.4.13 + +## 0.3.8-next.2 + +### Patch Changes + +- 7e5a50d: added `eventsServiceFactory` to `defaultServiceFactories` to resolve issue where different instances of the EventsServices could be used +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-events-node@0.3.4-next.2 + +## 0.3.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.2-next.1 + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## 0.3.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.1-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.3.7 + +### Patch Changes + +- 3256f14: `startTestBackend` will now add placeholder plugins when a modules are provided without their parent plugin. +- 007e7ea: Added mock of the new `listPublicServiceKeys` method for `AuthService`. +- Updated dependencies + - @backstage/backend-common@0.21.7 + - @backstage/backend-app-api@0.7.0 + - @backstage/backend-plugin-api@0.6.17 + - @backstage/plugin-auth-node@0.4.12 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.3.7-next.1 + +### Patch Changes + +- 3256f14: `startTestBackend` will now add placeholder plugins when a modules are provided without their parent plugin. +- 007e7ea: Added mock of the new `listPublicServiceKeys` method for `AuthService`. +- Updated dependencies + - @backstage/backend-common@0.21.7-next.1 + - @backstage/backend-app-api@0.7.0-next.1 + - @backstage/backend-plugin-api@0.6.17-next.1 + - @backstage/plugin-auth-node@0.4.12-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.3.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.6.3-next.0 + - @backstage/backend-common@0.21.7-next.0 + - @backstage/backend-plugin-api@0.6.17-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.12-next.0 + +## 0.3.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.6.2 + - @backstage/plugin-auth-node@0.4.11 + - @backstage/backend-common@0.21.6 + - @backstage/backend-plugin-api@0.6.16 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.6.1 + - @backstage/backend-common@0.21.5 + - @backstage/plugin-auth-node@0.4.10 + - @backstage/backend-plugin-api@0.6.15 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.3.4 + +### Patch Changes + +- 4a3d434: Added support for the new [`auth`](https://backstage.io/docs/backend-system/core-services/auth/) and [`httpAuth`](https://backstage.io/docs/backend-system/core-services/http-auth) services that were created as part of [BEP-0003](https://github.com/backstage/backstage/tree/master/beps/0003-auth-architecture-evolution). These services will be present by default in test apps, and you can access mocked versions of their features under `mockServices.auth` and `mockServices.httpAuth` if you want to inspect or replace their behaviors. + + There is also a new `mockCredentials` that you can use for acquiring mocks of the various types of credentials that are used in the new system. + +- 9802004: Added `mockServices.userInfo`, which now also automatically is made available in test backends. + +- fd61d39: Updated dependency `testcontainers` to `^10.0.0`. + +- ff40ada: Updated dependency `mysql2` to `^3.0.0`. + +- 0fb419b: Updated dependency `uuid` to `^9.0.0`. + Updated dependency `@types/uuid` to `^9.0.0`. + +- Updated dependencies + - @backstage/backend-common@0.21.4 + - @backstage/plugin-auth-node@0.4.9 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/backend-plugin-api@0.6.14 + - @backstage/backend-app-api@0.6.0 + - @backstage/types@1.1.1 + +## 0.3.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.6.0-next.2 + - @backstage/backend-common@0.21.4-next.2 + - @backstage/plugin-auth-node@0.4.9-next.2 + - @backstage/backend-plugin-api@0.6.14-next.2 + - @backstage/config@1.2.0-next.1 + - @backstage/errors@1.2.4-next.0 + - @backstage/types@1.1.1 + +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.2.0-next.1 + - @backstage/backend-app-api@0.6.0-next.1 + - @backstage/backend-common@0.21.4-next.1 + - @backstage/backend-plugin-api@0.6.14-next.1 + - @backstage/plugin-auth-node@0.4.9-next.1 + - @backstage/errors@1.2.4-next.0 + - @backstage/types@1.1.1 + +## 0.3.3-next.0 + +### Patch Changes + +- 4a3d434: Added support for the new [`auth`](https://backstage.io/docs/backend-system/core-services/auth/) and [`httpAuth`](https://backstage.io/docs/backend-system/core-services/http-auth) services that were created as part of [BEP-0003](https://github.com/backstage/backstage/tree/master/beps/0003-auth-architecture-evolution). These services will be present by default in test apps, and you can access mocked versions of their features under `mockServices.auth` and `mockServices.httpAuth` if you want to inspect or replace their behaviors. + + There is also a new `mockCredentials` that you can use for acquiring mocks of the various types of credentials that are used in the new system. + +- 9802004: Added `mockServices.userInfo`, which now also automatically is made available in test backends. + +- fd61d39: Updated dependency `testcontainers` to `^10.0.0`. + +- ff40ada: Updated dependency `mysql2` to `^3.0.0`. + +- 0fb419b: Updated dependency `uuid` to `^9.0.0`. + Updated dependency `@types/uuid` to `^9.0.0`. + +- Updated dependencies + - @backstage/backend-common@0.21.3-next.0 + - @backstage/plugin-auth-node@0.4.8-next.0 + - @backstage/errors@1.2.4-next.0 + - @backstage/backend-plugin-api@0.6.13-next.0 + - @backstage/backend-app-api@0.6.0-next.0 + - @backstage/config@1.1.2-next.0 + - @backstage/types@1.1.1 + +## 0.3.0 + +### Minor Changes + +- e85aa98: drop databases after unit tests if the database instance is not running in docker + +### Patch Changes + +- 6bb6f3e: Updated dependency `fs-extra` to `^11.2.0`. + Updated dependency `@types/fs-extra` to `^11.0.0`. +- Updated dependencies + - @backstage/backend-common@0.21.0 + - @backstage/plugin-auth-node@0.4.4 + - @backstage/backend-app-api@0.5.11 + - @backstage/backend-plugin-api@0.6.10 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.3.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.3 + - @backstage/backend-app-api@0.5.11-next.3 + - @backstage/plugin-auth-node@0.4.4-next.3 + - @backstage/backend-plugin-api@0.6.10-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.3.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.2 + - @backstage/backend-plugin-api@0.6.10-next.2 + - @backstage/backend-app-api@0.5.11-next.2 + - @backstage/plugin-auth-node@0.4.4-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.1 + - @backstage/backend-common@0.21.0-next.1 + - @backstage/backend-app-api@0.5.11-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.4-next.1 + +## 0.3.0-next.0 + +### Minor Changes + +- e85aa98: drop databases after unit tests if the database instance is not running in docker + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-app-api@0.5.11-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/backend-app-api@0.5.10 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-app-api@0.5.10-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## 0.2.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.5.10-next.1 + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-app-api@0.5.10-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## 0.2.9 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. + +- b7de76a: Added support for PostgreSQL versions 15 and 16 + + Also introduced a new `setDefaults(options: { ids?: TestDatabaseId[] })` static method that can be added to the `setupTests.ts` file to define the default database ids you want to use throughout your package. Usage would look like this: `TestDatabases.setDefaults({ ids: ['POSTGRES_12','POSTGRES_16'] })` and would result in PostgreSQL versions 12 and 16 being used for your tests. + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/backend-app-api@0.5.9 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-app-api@0.5.9-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## 0.2.9-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-app-api@0.5.9-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.5.9-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + +## 0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.0 + - @backstage/backend-app-api@0.5.9-next.0 + - @backstage/plugin-auth-node@0.4.2-next.0 + - @backstage/backend-plugin-api@0.6.8-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.8 + +### Patch Changes + +- 013611b42e: `knex` has been bumped to major version 3 and `better-sqlite3` to major version 9, which deprecate node 16 support. +- bb688f7b3b: Ensure recursive deletion of temporary directories in tests +- Updated dependencies + - @backstage/backend-common@0.19.9 + - @backstage/backend-plugin-api@0.6.7 + - @backstage/backend-app-api@0.5.8 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.1 + +## 0.2.8-next.2 + +### Patch Changes + +- [#20570](https://github.com/backstage/backstage/pull/20570) [`013611b42e`](https://github.com/backstage/backstage/commit/013611b42ed457fefa9bb85fddf416cf5e0c1f76) Thanks [@freben](https://github.com/freben)! - `knex` has been bumped to major version 3 and `better-sqlite3` to major version 9, which deprecate node 16 support. + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.7-next.2 + - @backstage/backend-common@0.19.9-next.2 + - @backstage/backend-app-api@0.5.8-next.2 + - @backstage/plugin-auth-node@0.4.1-next.2 + +## 0.2.8-next.1 + +### Patch Changes + +- bb688f7b3b: Ensure recursive deletion of temporary directories in tests +- Updated dependencies + - @backstage/backend-common@0.19.9-next.1 + - @backstage/backend-app-api@0.5.8-next.1 + - @backstage/plugin-auth-node@0.4.1-next.1 + - @backstage/backend-plugin-api@0.6.7-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.5.8-next.0 + - @backstage/backend-common@0.19.9-next.0 + - @backstage/backend-plugin-api@0.6.7-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.1-next.0 + +## 0.2.7 + +### Patch Changes + +- a250ad775f: Added `createMockDirectory()` to help out with file system mocking in tests. +- 5ddc03813e: Remove third type parameter used for `MockInstance`, in order to be compatible with older versions of `@types/jest`. +- 74491c9602: Updated to import `HostDiscovery` from `@backstage/backend-app-api`. +- Updated dependencies + - @backstage/backend-common@0.19.8 + - @backstage/plugin-auth-node@0.4.0 + - @backstage/errors@1.2.3 + - @backstage/backend-app-api@0.5.6 + - @backstage/backend-plugin-api@0.6.6 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + +## 0.2.7-next.2 + +### Patch Changes + +- a250ad775f: Added `createMockDirectory()` to help out with file system mocking in tests. +- 74491c9602: Updated to import `HostDiscovery` from `@backstage/backend-app-api`. +- Updated dependencies + - @backstage/backend-common@0.19.8-next.2 + - @backstage/plugin-auth-node@0.4.0-next.2 + - @backstage/errors@1.2.3-next.0 + - @backstage/backend-app-api@0.5.6-next.2 + - @backstage/backend-plugin-api@0.6.6-next.2 + - @backstage/config@1.1.1-next.0 + - @backstage/types@1.1.1 + +## 0.2.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.7-next.1 + - @backstage/backend-app-api@0.5.5-next.1 + - @backstage/backend-plugin-api@0.6.5-next.1 + - @backstage/plugin-auth-node@0.3.2-next.1 + - @backstage/config@1.1.0 + - @backstage/types@1.1.1 + +## 0.2.6-next.0 + +### Patch Changes + +- 5ddc03813e: Remove third type parameter used for `MockInstance`, in order to be compatible with older versions of `@types/jest`. +- Updated dependencies + - @backstage/plugin-auth-node@0.3.2-next.0 + - @backstage/backend-common@0.19.7-next.0 + - @backstage/config@1.1.0 + - @backstage/backend-app-api@0.5.5-next.0 + - @backstage/backend-plugin-api@0.6.5-next.0 + - @backstage/types@1.1.1 + +## 0.2.3 + +### Patch Changes + +- 58cb5e5cea7b: Introduced a new utility for testing service factories, `ServiceFactoryTester`. +- 202e52c5e361: Add support for installing backend features via module imports, for example `startTestBackend({ features: [import('my-plugin')] })`. +- 9fb3b5373c45: Extended `mockService` to also include mocked variants, for example `mockServices.lifecycle.mock()`. The returned mocked implementation will have a `factory` property which is a service factory for itself. You can also pass a partial implementation of the service to the mock function to use a mock implementation of specific methods. +- eb1594da5812: Serialize test database shutdown, and add logging +- Updated dependencies + - @backstage/backend-app-api@0.5.3 + - @backstage/backend-common@0.19.5 + - @backstage/plugin-auth-node@0.3.0 + - @backstage/config@1.1.0 + - @backstage/types@1.1.1 + - @backstage/backend-plugin-api@0.6.3 + +## 0.2.3-next.3 + +### Patch Changes + +- 202e52c5e361: Add support for installing backend features via module imports, for example `startTestBackend({ features: [import('my-plugin')] })`. +- Updated dependencies + - @backstage/backend-app-api@0.5.3-next.3 + - @backstage/config@1.1.0-next.2 + - @backstage/types@1.1.1-next.0 + - @backstage/backend-plugin-api@0.6.3-next.3 + - @backstage/backend-common@0.19.5-next.3 + - @backstage/plugin-auth-node@0.3.0-next.3 + +## 0.2.3-next.2 + +### Patch Changes + +- 58cb5e5cea7b: Introduced a new utility for testing service factories, `ServiceFactoryTester`. +- Updated dependencies + - @backstage/backend-app-api@0.5.3-next.2 + - @backstage/config@1.1.0-next.1 + - @backstage/backend-common@0.19.5-next.2 + - @backstage/plugin-auth-node@0.3.0-next.2 + - @backstage/backend-plugin-api@0.6.3-next.2 + - @backstage/types@1.1.0 + +## 0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.0-next.0 + - @backstage/backend-app-api@0.5.3-next.1 + - @backstage/backend-common@0.19.5-next.1 + - @backstage/backend-plugin-api@0.6.3-next.1 + - @backstage/plugin-auth-node@0.3.0-next.1 + - @backstage/types@1.1.0 + +## 0.2.2-next.0 + +### Patch Changes + +- 9fb3b5373c45: Extended `mockService` to also include mocked variants, for example `mockServices.lifecycle.mock()`. The returned mocked implementation will have a `factory` property which is a service factory for itself. You can also pass a partial implementation of the service to the mock function to use a mock implementation of specific methods. +- Updated dependencies + - @backstage/plugin-auth-node@0.3.0-next.0 + - @backstage/backend-common@0.19.4-next.0 + - @backstage/backend-app-api@0.5.2-next.0 + - @backstage/backend-plugin-api@0.6.2-next.0 + - @backstage/config@1.0.8 + - @backstage/types@1.1.0 + +## 0.2.0 + +### Minor Changes + +- b9c57a4f857e: **BREAKING**: Renamed `mockServices.config` to `mockServices.rootConfig`. +- a6d7983f349c: **BREAKING**: Removed the `services` option from `createBackend`. Service factories are now `BackendFeature`s and should be installed with `backend.add(...)` instead. The following should be migrated: + + ```ts + const backend = createBackend({ services: [myCustomServiceFactory] }); + ``` + + To instead pass the service factory via `backend.add(...)`: + + ```ts + const backend = createBackend(); + backend.add(customRootLoggerServiceFactory); + ``` + +### Patch Changes + +- ae9304818136: Add needed constants and constructs to support PostgreSQL version 14 as test database +- Updated dependencies + - @backstage/backend-common@0.19.2 + - @backstage/backend-app-api@0.5.0 + - @backstage/backend-plugin-api@0.6.0 + - @backstage/plugin-auth-node@0.2.17 + - @backstage/config@1.0.8 + - @backstage/types@1.1.0 + +## 0.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.5.0-next.2 + - @backstage/backend-plugin-api@0.6.0-next.2 + - @backstage/backend-common@0.19.2-next.2 + - @backstage/plugin-auth-node@0.2.17-next.2 + +## 0.2.0-next.1 + +### Minor Changes + +- b9c57a4f857e: **BREAKING**: Renamed `mockServices.config` to `mockServices.rootConfig`. + +### Patch Changes + +- ae9304818136: Add needed constants and constructs to support PostgreSQL version 14 as test database +- Updated dependencies + - @backstage/backend-common@0.19.2-next.1 + - @backstage/plugin-auth-node@0.2.17-next.1 + - @backstage/backend-app-api@0.5.0-next.1 + - @backstage/backend-plugin-api@0.6.0-next.1 + - @backstage/config@1.0.8 + - @backstage/types@1.1.0 + +## 0.1.40-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.4.6-next.0 + - @backstage/backend-common@0.19.2-next.0 + - @backstage/backend-plugin-api@0.5.5-next.0 + - @backstage/config@1.0.8 + - @backstage/types@1.1.0 + - @backstage/plugin-auth-node@0.2.17-next.0 + +## 0.1.39 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.1 + - @backstage/backend-app-api@0.4.5 + - @backstage/backend-plugin-api@0.5.4 + - @backstage/config@1.0.8 + - @backstage/types@1.1.0 + - @backstage/plugin-auth-node@0.2.16 + +## 0.1.39-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.1-next.0 + - @backstage/backend-app-api@0.4.5-next.0 + - @backstage/backend-plugin-api@0.5.4-next.0 + - @backstage/config@1.0.8 + - @backstage/types@1.1.0 + - @backstage/plugin-auth-node@0.2.16-next.0 + +## 0.1.38 + +### Patch Changes + +- 68a21956ef52: Remove reference to deprecated import +- Updated dependencies + - @backstage/backend-common@0.19.0 + - @backstage/types@1.1.0 + - @backstage/backend-app-api@0.4.4 + - @backstage/backend-plugin-api@0.5.3 + - @backstage/plugin-auth-node@0.2.15 + - @backstage/config@1.0.8 + +## 0.1.38-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.0-next.2 + - @backstage/backend-app-api@0.4.4-next.2 + - @backstage/backend-plugin-api@0.5.3-next.2 + - @backstage/config@1.0.7 + - @backstage/types@1.0.2 + - @backstage/plugin-auth-node@0.2.15-next.2 + +## 0.1.38-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.0-next.1 + - @backstage/backend-app-api@0.4.4-next.1 + - @backstage/backend-plugin-api@0.5.3-next.1 + - @backstage/plugin-auth-node@0.2.15-next.1 + - @backstage/config@1.0.7 + - @backstage/types@1.0.2 + +## 0.1.38-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.4.4-next.0 + - @backstage/backend-common@0.18.6-next.0 + - @backstage/config@1.0.7 + - @backstage/backend-plugin-api@0.5.3-next.0 + - @backstage/types@1.0.2 + - @backstage/plugin-auth-node@0.2.15-next.0 + +## 0.1.37 + +### Patch Changes + +- 63af7f6d53f: Allow specifying custom Docker registry for database tests +- b1eb268bf9d: Added `POSTGRES_11` and `POSTGRES_12` as supported test database IDs. +- Updated dependencies + - @backstage/backend-common@0.18.5 + - @backstage/backend-app-api@0.4.3 + - @backstage/plugin-auth-node@0.2.14 + - @backstage/backend-plugin-api@0.5.2 + - @backstage/config@1.0.7 + - @backstage/types@1.0.2 + +## 0.1.37-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.5-next.1 + - @backstage/backend-app-api@0.4.3-next.1 + - @backstage/plugin-auth-node@0.2.14-next.1 + - @backstage/backend-plugin-api@0.5.2-next.1 + - @backstage/config@1.0.7 + +## 0.1.37-next.0 + +### Patch Changes + +- b1eb268bf9d: Added `POSTGRES_11` and `POSTGRES_12` as supported test database IDs. +- Updated dependencies + - @backstage/backend-common@0.18.5-next.0 + - @backstage/backend-app-api@0.4.3-next.0 + - @backstage/plugin-auth-node@0.2.14-next.0 + - @backstage/backend-plugin-api@0.5.2-next.0 + - @backstage/config@1.0.7 + - @backstage/types@1.0.2 + +## 0.1.36 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.4 + - @backstage/backend-app-api@0.4.2 + - @backstage/plugin-auth-node@0.2.13 + - @backstage/backend-plugin-api@0.5.1 + - @backstage/config@1.0.7 + - @backstage/types@1.0.2 + +## 0.1.36-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.4.2-next.2 + - @backstage/backend-common@0.18.4-next.2 + - @backstage/backend-plugin-api@0.5.1-next.2 + - @backstage/config@1.0.7 + - @backstage/types@1.0.2 + - @backstage/plugin-auth-node@0.2.13-next.2 + +## 0.1.36-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.4.2-next.1 + - @backstage/backend-common@0.18.4-next.1 + - @backstage/backend-plugin-api@0.5.1-next.1 + - @backstage/config@1.0.7 + - @backstage/types@1.0.2 + - @backstage/plugin-auth-node@0.2.13-next.1 + +## 0.1.36-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.4.2-next.0 + - @backstage/backend-common@0.18.4-next.0 + - @backstage/config@1.0.7 + - @backstage/backend-plugin-api@0.5.1-next.0 + - @backstage/types@1.0.2 + - @backstage/plugin-auth-node@0.2.13-next.0 + +## 0.1.35 + +### Patch Changes + +- 928a12a9b3e: Internal refactor of `/alpha` exports. +- 52b0022dab7: Updated dependency `msw` to `^1.0.0`. +- 482dae5de1c: Updated link to docs. +- Updated dependencies + - @backstage/plugin-auth-node@0.2.12 + - @backstage/backend-common@0.18.3 + - @backstage/backend-plugin-api@0.5.0 + - @backstage/backend-app-api@0.4.1 + - @backstage/config@1.0.7 + - @backstage/types@1.0.2 + +## 0.1.35-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.2.12-next.2 + - @backstage/backend-common@0.18.3-next.2 + - @backstage/backend-app-api@0.4.1-next.2 + - @backstage/backend-plugin-api@0.4.1-next.2 + - @backstage/config@1.0.7-next.0 + +## 0.1.35-next.1 + +### Patch Changes + +- 52b0022dab7: Updated dependency `msw` to `^1.0.0`. +- 482dae5de1c: Updated link to docs. +- Updated dependencies + - @backstage/backend-common@0.18.3-next.1 + - @backstage/plugin-auth-node@0.2.12-next.1 + - @backstage/backend-plugin-api@0.4.1-next.1 + - @backstage/backend-app-api@0.4.1-next.1 + - @backstage/config@1.0.7-next.0 + - @backstage/types@1.0.2 + +## 0.1.35-next.0 + +### Patch Changes + +- 928a12a9b3: Internal refactor of `/alpha` exports. +- Updated dependencies + - @backstage/backend-plugin-api@0.4.1-next.0 + - @backstage/backend-app-api@0.4.1-next.0 + - @backstage/backend-common@0.18.3-next.0 + - @backstage/config@1.0.6 + - @backstage/types@1.0.2 + - @backstage/plugin-auth-node@0.2.12-next.0 + +## 0.1.34 + +### Patch Changes + +- baf6e4c96a: Removed unnecessary `@backstage/cli` dependency. +- c51efce2a0: Update docs to always use `yarn add --cwd` for app & backend +- 1835311713: Added explicit return type signature for `mockServices.config()`. +- e716946103: Updated usage of the lifecycle service. +- 7e7557a2be: Updated the `mockServices.rootLogger` options to accept a single level option instead. +- 610d65e143: Updates to match new `BackendFeature` type. +- e412d33025: Use the new `*ServiceFactory` exports from `@backstage/backend-app-api` +- b86efa2d04: Updated usage of `ServiceFactory`. +- 17b573e4be: The new backend system testing utilities have now been marked as stable API. +- f1adb2e36b: Removed the `ref` from all `mockServices`. +- d0901c9ba4: All mock service factories and mock service implementations are now available via the new experimental `mockServices` export. +- 71a5ec0f06: Updated usages of `LogMeta`. +- Updated dependencies + - @backstage/backend-app-api@0.4.0 + - @backstage/backend-plugin-api@0.4.0 + - @backstage/backend-common@0.18.2 + - @backstage/config@1.0.6 + - @backstage/types@1.0.2 + - @backstage/plugin-auth-node@0.2.11 + +## 0.1.34-next.2 + +### Patch Changes + +- baf6e4c96a: Removed unnecessary `@backstage/cli` dependency. +- 1835311713: Added explicit return type signature for `mockServices.config()`. +- e716946103: Updated usage of the lifecycle service. +- 7e7557a2be: Updated the `mockServices.rootLogger` options to accept a single level option instead. +- 610d65e143: Updates to match new `BackendFeature` type. +- e412d33025: Use the new `*ServiceFactory` exports from `@backstage/backend-app-api` +- f1adb2e36b: Removed the `ref` from all `mockServices`. +- 71a5ec0f06: Updated usages of `LogMeta`. +- Updated dependencies + - @backstage/backend-app-api@0.4.0-next.2 + - @backstage/backend-plugin-api@0.4.0-next.2 + - @backstage/backend-common@0.18.2-next.2 + - @backstage/plugin-auth-node@0.2.11-next.2 + - @backstage/config@1.0.6 + - @backstage/types@1.0.2 + +## 0.1.34-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.22.2-next.0 + - @backstage/backend-common@0.18.2-next.1 + - @backstage/backend-plugin-api@0.3.2-next.1 + - @backstage/backend-app-api@0.3.2-next.1 + - @backstage/config@1.0.6 + - @backstage/types@1.0.2 + - @backstage/plugin-auth-node@0.2.11-next.1 + +## 0.1.34-next.0 + +### Patch Changes + +- d0901c9ba4: All mock service factories and mock service implementations are now available via the new experimental `mockServices` export. +- Updated dependencies + - @backstage/backend-app-api@0.3.2-next.0 + - @backstage/backend-common@0.18.2-next.0 + - @backstage/plugin-auth-node@0.2.11-next.0 + - @backstage/cli@0.22.1 + - @backstage/backend-plugin-api@0.3.2-next.0 + +## 0.1.32 + +### Patch Changes + +- 6cfd4d7073: Include implementations for the new `rootLifecycleServiceRef`. +- 015a6dced6: Updated to make sure that service implementations replace default service implementations. +- a3ec2f32ea: The `startTestBackend` setup now includes default implementations for all core services. +- 483e907eaf: Internal updates of `createServiceFactory` from `@backstage/backend-plugin-api`. +- 51b7a7ed07: The backend started by `startTestBackend` now has default implementations of all core services. It now also returns a `TestBackend` instance, which provides access to the underlying `server` that can be used with testing libraries such as `supertest`. +- f23eef3aa2: Updated dependency `better-sqlite3` to `^8.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@0.3.0 + - @backstage/cli@0.22.1 + - @backstage/backend-common@0.18.0 + - @backstage/backend-app-api@0.3.0 + - @backstage/config@1.0.6 + - @backstage/types@1.0.2 + - @backstage/plugin-auth-node@0.2.9 + +## 0.1.32-next.2 + +### Patch Changes + +- 015a6dced6: Updated to make sure that service implementations replace default service implementations. +- a3ec2f32ea: The `startTestBackend` setup now includes default implementations for all core services. +- f23eef3aa2: Updated dependency `better-sqlite3` to `^8.0.0`. +- Updated dependencies + - @backstage/backend-app-api@0.3.0-next.1 + - @backstage/backend-plugin-api@0.3.0-next.1 + - @backstage/backend-common@0.18.0-next.1 + - @backstage/cli@0.22.1-next.2 + - @backstage/config@1.0.6-next.0 + +## 0.1.32-next.1 + +### Patch Changes + +- 6cfd4d7073: Include implementations for the new `rootLifecycleServiceRef`. +- Updated dependencies + - @backstage/backend-plugin-api@0.2.1-next.0 + - @backstage/cli@0.22.1-next.1 + - @backstage/backend-common@0.18.0-next.0 + - @backstage/config@1.0.6-next.0 + - @backstage/backend-app-api@0.2.5-next.0 + +## 0.1.32-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.22.1-next.0 + - @backstage/backend-app-api@0.2.4 + - @backstage/backend-common@0.17.0 + - @backstage/backend-plugin-api@0.2.0 + - @backstage/config@1.0.5 + +## 0.1.31 + +### Patch Changes + +- afa3bf5657: Backends started with `startTestBackend` are now automatically stopped after all tests have run. +- 3280711113: Updated dependency `msw` to `^0.49.0`. +- Updated dependencies + - @backstage/backend-app-api@0.2.4 + - @backstage/cli@0.22.0 + - @backstage/backend-common@0.17.0 + - @backstage/backend-plugin-api@0.2.0 + - @backstage/config@1.0.5 + +## 0.1.31-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.22.0-next.4 + - @backstage/backend-common@0.17.0-next.3 + - @backstage/backend-app-api@0.2.4-next.3 + - @backstage/backend-plugin-api@0.2.0-next.3 + - @backstage/config@1.0.5-next.1 + +## 0.1.31-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.21.2-next.3 + - @backstage/backend-app-api@0.2.4-next.2 + - @backstage/backend-common@0.17.0-next.2 + - @backstage/backend-plugin-api@0.2.0-next.2 + - @backstage/config@1.0.5-next.1 + +## 0.1.31-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.2.4-next.2 + - @backstage/backend-common@0.17.0-next.2 + - @backstage/cli@0.21.2-next.2 + - @backstage/backend-plugin-api@0.2.0-next.2 + - @backstage/config@1.0.5-next.1 + +## 0.1.31-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.17.0-next.1 + - @backstage/cli@0.21.2-next.1 + - @backstage/backend-app-api@0.2.4-next.1 + - @backstage/backend-plugin-api@0.1.5-next.1 + - @backstage/config@1.0.5-next.1 + +## 0.1.31-next.0 + +### Patch Changes + +- 3280711113: Updated dependency `msw` to `^0.49.0`. +- Updated dependencies + - @backstage/cli@0.21.2-next.0 + - @backstage/backend-app-api@0.2.4-next.0 + - @backstage/backend-common@0.16.1-next.0 + - @backstage/backend-plugin-api@0.1.5-next.0 + - @backstage/config@1.0.5-next.0 + +## 0.1.30 + +### Patch Changes + +- e13cd3feaf: Updated dependency `msw` to `^0.48.0`. +- Updated dependencies + - @backstage/backend-common@0.16.0 + - @backstage/cli@0.21.0 + - @backstage/backend-app-api@0.2.3 + - @backstage/backend-plugin-api@0.1.4 + - @backstage/config@1.0.4 + +## 0.1.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.1 + - @backstage/cli@0.21.0-next.1 + - @backstage/backend-app-api@0.2.3-next.1 + - @backstage/backend-plugin-api@0.1.4-next.1 + - @backstage/config@1.0.4-next.0 + +## 0.1.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/cli@0.21.0-next.0 + - @backstage/backend-app-api@0.2.3-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/config@1.0.4-next.0 + +## 0.1.29 + +### Patch Changes + +- 72549952d1: Fixed handling of root scoped services in `startTestBackend`. +- e91e8e9c55: Increased test database max connection pool size to reduce the risk of resource exhaustion. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/cli@0.20.0 + - @backstage/backend-app-api@0.2.2 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/config@1.0.3 + +## 0.1.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/cli@0.20.0-next.2 + - @backstage/backend-app-api@0.2.2-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/config@1.0.3-next.2 + +## 0.1.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.20.0-next.1 + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-plugin-api@0.1.3-next.1 + - @backstage/backend-app-api@0.2.2-next.1 + - @backstage/config@1.0.3-next.1 + +## 0.1.29-next.0 + +### Patch Changes + +- 72549952d1: Fixed handling of root scoped services in `startTestBackend`. +- e91e8e9c55: Increased test database max connection pool size to reduce the risk of resource exhaustion. +- Updated dependencies + - @backstage/backend-app-api@0.2.2-next.0 + - @backstage/backend-plugin-api@0.1.3-next.0 + - @backstage/cli@0.20.0-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + +## 0.1.28 + +### Patch Changes + +- 854ba37357: Updated to support new `ServiceFactory` formats. +- 667d917488: Updated dependency `msw` to `^0.47.0`. +- 87ec2ba4d6: Updated dependency `msw` to `^0.46.0`. +- bf5e9030eb: Updated dependency `msw` to `^0.45.0`. +- de3347ca74: Updated usages of `ServiceFactory`. +- Updated dependencies + - @backstage/cli@0.19.0 + - @backstage/backend-app-api@0.2.1 + - @backstage/backend-plugin-api@0.1.2 + - @backstage/backend-common@0.15.1 + - @backstage/config@1.0.2 + +## 0.1.28-next.3 + +### Patch Changes + +- 854ba37357: Updated to support new `ServiceFactory` formats. +- Updated dependencies + - @backstage/backend-plugin-api@0.1.2-next.2 + - @backstage/config@1.0.2-next.0 + - @backstage/backend-app-api@0.2.1-next.2 + - @backstage/cli@0.19.0-next.3 + - @backstage/backend-common@0.15.1-next.3 + +## 0.1.28-next.2 + +### Patch Changes + +- 667d917488: Updated dependency `msw` to `^0.47.0`. +- 87ec2ba4d6: Updated dependency `msw` to `^0.46.0`. +- Updated dependencies + - @backstage/cli@0.19.0-next.2 + - @backstage/backend-app-api@0.2.1-next.1 + - @backstage/backend-plugin-api@0.1.2-next.1 + - @backstage/backend-common@0.15.1-next.2 + +## 0.1.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.1-next.1 + - @backstage/cli@0.19.0-next.1 + +## 0.1.28-next.0 + +### Patch Changes + +- bf5e9030eb: Updated dependency `msw` to `^0.45.0`. +- de3347ca74: Updated usages of `ServiceFactory`. +- Updated dependencies + - @backstage/backend-common@0.15.1-next.0 + - @backstage/cli@0.18.2-next.0 + - @backstage/backend-plugin-api@0.1.2-next.0 + - @backstage/backend-app-api@0.2.1-next.0 + +## 0.1.27 + +### Patch Changes + +- 0599732ec0: Refactored experimental backend system with new type names. +- 56e1b4b89c: Added alpha test helpers for the new experimental backend system. +- Updated dependencies + - @backstage/cli@0.18.1 + - @backstage/backend-common@0.15.0 + - @backstage/backend-app-api@0.2.0 + - @backstage/backend-plugin-api@0.1.1 + +## 0.1.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.0-next.0 + - @backstage/cli@0.18.1-next.0 + +## 0.1.26 + +### Patch Changes + +- a70869e775: Updated dependency `msw` to `^0.43.0`. +- 8006d0f9bf: Updated dependency `msw` to `^0.44.0`. +- 679b32172e: Updated dependency `knex` to `^2.0.0`. +- Updated dependencies + - @backstage/backend-common@0.14.1 + - @backstage/cli@0.18.0 + +## 0.1.26-next.3 + +### Patch Changes + +- a70869e775: Updated dependency `msw` to `^0.43.0`. +- Updated dependencies + - @backstage/backend-common@0.14.1-next.3 + - @backstage/cli@0.18.0-next.3 + +## 0.1.26-next.2 + +### Patch Changes + +- 679b32172e: Updated dependency `knex` to `^2.0.0`. +- Updated dependencies + - @backstage/backend-common@0.14.1-next.2 + - @backstage/cli@0.18.0-next.2 + +## 0.1.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.18.0-next.1 + - @backstage/backend-common@0.14.1-next.1 + +## 0.1.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.14.1-next.0 + - @backstage/cli@0.17.3-next.0 + +## 0.1.25 + +### Patch Changes + +- 8f7b1835df: Updated dependency `msw` to `^0.41.0`. +- Updated dependencies + - @backstage/cli@0.17.2 + - @backstage/backend-common@0.14.0 + +## 0.1.25-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.17.2-next.2 + - @backstage/backend-common@0.14.0-next.2 + +## 0.1.25-next.1 + +### Patch Changes + +- 8f7b1835df: Updated dependency `msw` to `^0.41.0`. +- Updated dependencies + - @backstage/cli@0.17.2-next.1 + - @backstage/backend-common@0.13.6-next.1 + +## 0.1.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.17.2-next.0 + - @backstage/backend-common@0.13.6-next.0 + +## 0.1.24 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.3 + - @backstage/cli@0.17.1 + - @backstage/config@1.0.1 + +## 0.1.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.3-next.2 + - @backstage/config@1.0.1-next.0 + - @backstage/cli@0.17.1-next.2 + +## 0.1.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.3-next.0 + - @backstage/cli@0.17.1-next.0 + +## 0.1.23 + +### Patch Changes + +- 0654c87cf2: `TestDatabases.create` will no longer set up an `afterAll` test handler if no databases are supported. +- Updated dependencies + - @backstage/cli@0.17.0 + - @backstage/backend-common@0.13.2 + +## 0.1.23-next.1 + +### Patch Changes + +- 0654c87cf2: `TestDatabases.create` will no longer set up an `afterAll` test handler if no databases are supported. +- Updated dependencies + - @backstage/cli@0.17.0-next.1 + - @backstage/backend-common@0.13.2-next.1 + +## 0.1.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.16.1-next.0 + - @backstage/backend-common@0.13.2-next.0 + +## 0.1.22 + +### Patch Changes + +- 89c7e47967: Minor README update +- efc73db10c: Use `better-sqlite3` instead of `@vscode/sqlite3` +- Updated dependencies + - @backstage/cli@0.16.0 + - @backstage/backend-common@0.13.1 + - @backstage/config@1.0.0 + +## 0.1.21 + +### Patch Changes + +- ab7cd7d70e: Do some groundwork for supporting the `better-sqlite3` driver, to maybe eventually replace `@vscode/sqlite3` (#9912) +- 3c2bc73901: Add `setupRequestMockHandlers` which sets up a good `msw` server foundation, copied from `@backstage/test-utils` which is a frontend-only package and should not be used from backends. +- Updated dependencies + - @backstage/backend-common@0.13.0 + - @backstage/cli@0.15.2 + +## 0.1.21-next.0 + +### Patch Changes + +- ab7cd7d70e: Do some groundwork for supporting the `better-sqlite3` driver, to maybe eventually replace `@vscode/sqlite3` (#9912) +- 3c2bc73901: Add `setupRequestMockHandlers` which sets up a good `msw` server foundation, copied from `@backstage/test-utils` which is a frontend-only package and should not be used from backends. +- Updated dependencies + - @backstage/backend-common@0.13.0-next.0 + - @backstage/cli@0.15.2-next.0 + +## 0.1.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.12.0 + - @backstage/cli@0.15.0 + +## 0.1.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.11.0 + - @backstage/cli@0.14.1 + +## 0.1.18 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/cli@0.14.0 + - @backstage/backend-common@0.10.8 + - @backstage/config@0.1.14 + +## 0.1.17 + +### Patch Changes + +- 2441d1cf59: chore(deps): bump `knex` from 0.95.6 to 1.0.2 + + This also replaces `sqlite3` with `@vscode/sqlite3` 5.0.7 + +- Updated dependencies + - @backstage/cli@0.13.2 + - @backstage/backend-common@0.10.7 + +## 0.1.17-next.0 + +### Patch Changes + +- 2441d1cf59: chore(deps): bump `knex` from 0.95.6 to 1.0.2 + + This also replaces `sqlite3` with `@vscode/sqlite3` 5.0.7 + +- Updated dependencies + - @backstage/cli@0.13.2-next.0 + - @backstage/backend-common@0.10.7-next.0 + +## 0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.13.1 + - @backstage/backend-common@0.10.6 + +## 0.1.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.13.1-next.1 + - @backstage/backend-common@0.10.6-next.0 + +## 0.1.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.13.1-next.0 + +## 0.1.15 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.13.0 + - @backstage/backend-common@0.10.5 + +## 0.1.14 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.12.0 + - @backstage/backend-common@0.10.4 + - @backstage/config@0.1.13 + +## 0.1.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.12.0-next.0 + - @backstage/backend-common@0.10.4-next.0 + - @backstage/config@0.1.13-next.0 + +## 0.1.13 + +### Patch Changes + +- b1bc55405e: Bump `testcontainers` dependency to version `8.1.2` +- Updated dependencies + - @backstage/config@0.1.12 + - @backstage/backend-common@0.10.3 + - @backstage/cli@0.11.0 + +## 0.1.12 + +### Patch Changes + +- 130b7aadf2: Lazy-load `testcontainers` module in order to avoid side-effects. +- Updated dependencies + - @backstage/backend-common@0.10.1 + - @backstage/cli@0.10.4 + +## 0.1.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.0 + - @backstage/cli@0.10.3 + +## 0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.9.12 + - @backstage/cli@0.10.0 + +## 0.1.9 + +### Patch Changes + +- e21e3c6102: Bumping minimum requirements for `dockerode` and `testcontainers` +- Updated dependencies + - @backstage/cli@0.9.0 + - @backstage/backend-common@0.9.10 + +## 0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.9.7 + - @backstage/cli@0.8.0 + +## 0.1.7 + +### Patch Changes + +- d1da88a19: Properly export all used types. +- Updated dependencies + - @backstage/config@0.1.9 + - @backstage/backend-common@0.9.2 + - @backstage/cli@0.7.11 + +## 0.1.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.9.0 + - @backstage/config@0.1.8 + +## 0.1.5 + +### Patch Changes + +- 524045758: Make sure that the unique databases names generated by `TestDatabases.create()` + don't collide. +- Updated dependencies + - @backstage/backend-common@0.8.9 + - @backstage/cli@0.7.8 + +## 0.1.4 + +### Patch Changes + +- f7134c368: bump sqlite3 to 5.0.1 +- Updated dependencies + - @backstage/backend-common@0.8.5 + +## 0.1.3 + +### Patch Changes + +- 772dbdb51: Deprecates `SingleConnectionDatabaseManager` and provides an API compatible database + connection manager, `DatabaseManager`, which allows developers to configure database + connections on a per plugin basis. + + The `backend.database` config path allows you to set `prefix` to use an + alternate prefix for automatically generated database names, the default is + `backstage_plugin_`. Use `backend.database.plugin.` to set plugin + specific database connection configuration, e.g. + + ```yaml + backend: + database: + client: 'pg', + prefix: 'custom_prefix_' + connection: + host: 'localhost' + user: 'foo' + password: 'bar' + plugin: + catalog: + connection: + database: 'database_name_overriden' + scaffolder: + client: 'sqlite3' + connection: ':memory:' + ``` + + Migrate existing backstage installations by swapping out the database manager in the + `packages/backend/src/index.ts` file as shown below: + + ```diff + import { + - SingleConnectionDatabaseManager, + + DatabaseManager, + } from '@backstage/backend-common'; + + // ... + + function makeCreateEnv(config: Config) { + // ... + - const databaseManager = SingleConnectionDatabaseManager.fromConfig(config); + + const databaseManager = DatabaseManager.fromConfig(config); + // ... + } + ``` + +- Updated dependencies + - @backstage/backend-common@0.8.3 + - @backstage/cli@0.7.1 + +## 0.1.2 + +### Patch Changes + +- 0711954a9: Skip running docker tests unless in CI +- Updated dependencies [9cd3c533c] +- Updated dependencies [92963779b] +- Updated dependencies [7f7443308] +- Updated dependencies [21e8ebef5] +- Updated dependencies [eda9dbd5f] + - @backstage/cli@0.7.0 + - @backstage/backend-common@0.8.2 + +## @backstage/core-app-api@1.19.5-next.0 + +# @backstage/core-app-api + +## 1.19.4-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## 1.19.3 + +### Patch Changes + +- 75683ed: Added replay functionality to `AlertApiForwarder` to buffer and replay recent alerts to new subscribers, preventing missed alerts that were posted before subscription. +- 97cd16f: Internal update of translation imports. +- Updated dependencies + - @backstage/core-plugin-api@1.12.1 + +## 1.19.3-next.1 + +### Patch Changes + +- 75683ed: Added replay functionality to `AlertApiForwarder` to buffer and replay recent alerts to new subscribers, preventing missed alerts that were posted before subscription. +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## 1.19.3-next.0 + +### Patch Changes + +- 97cd16f: Internal update of translation imports. +- Updated dependencies + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## 1.19.2 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/core-plugin-api@1.12.0 + - @backstage/config@1.3.6 + +## 1.19.2-next.1 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/core-plugin-api@1.11.2-next.1 + +## 1.19.2-next.0 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/core-plugin-api@1.11.2-next.0 + - @backstage/config@1.3.6-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## 1.19.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.5 + - @backstage/core-plugin-api@1.11.1 + +## 1.19.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/core-plugin-api@1.11.1-next.0 + +## 1.19.0 + +### Minor Changes + +- 3fca906: Add `OpenShiftAuth` helper to create default OAuth flow for OpenShift. + +### Patch Changes + +- 5ae6d9d: feat: support no en languages +- Updated dependencies + - @backstage/core-plugin-api@1.11.0 + - @backstage/types@1.2.2 + +## 1.18.0 + +### Minor Changes + +- 5ddc0fe: if session exists and refresh fails, then create a new session if not instant popup + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/core-plugin-api@1.10.9 + +## 1.18.0-next.1 + +### Minor Changes + +- 5ddc0fe: if session exists and refresh fails, then create a new session if not instant popup + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.9-next.0 + +## 1.17.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/core-plugin-api@1.10.9-next.0 + +## 1.17.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.8 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## 1.17.0 + +### Minor Changes + +- 1e0230e: Support custom `AuthConnector` for `OAuth2`. + + A user can pass their own `AuthConnector` implementation in `OAuth2` constructor. + In which case the session manager will use that instead of the `DefaultAuthConnector` to interact with the + authentication provider. + + A custom `AuthConnector` may call the authentication provider from the front-end, store and retrieve tokens + in the session storage, for example, and otherwise send custom requests to the authentication provider and + handle its responses. + + Note, that if the custom `AuthConnector` transforms scopes returned from the authentication provider, + the transformation must be the same as `OAuth2CreateOptions#scopeTransform` passed to `OAuth2` constructor. + See creating `DefaultAuthConnector` in `OAuth2#create(...)` for an example. + +### Patch Changes + +- 73f6cc3: Updated `I18nextTranslationApi` to support interpolation of JSX elements. +- cc119b2: Fixed an issue causing `OAuthRequestDialog` to re-render on mount. +- Updated dependencies + - @backstage/core-plugin-api@1.10.7 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## 1.17.0-next.1 + +### Minor Changes + +- 1e0230e: Support custom `AuthConnector` for `OAuth2`. + + A user can pass their own `AuthConnector` implementation in `OAuth2` constructor. + In which case the session manager will use that instead of the `DefaultAuthConnector` to interact with the + authentication provider. + + A custom `AuthConnector` may call the authentication provider from the front-end, store and retrieve tokens + in the session storage, for example, and otherwise send custom requests to the authentication provider and + handle its responses. + + Note, that if the custom `AuthConnector` transforms scopes returned from the authentication provider, + the transformation must be the same as `OAuth2CreateOptions#scopeTransform` passed to `OAuth2` constructor. + See creating `DefaultAuthConnector` in `OAuth2#create(...)` for an example. + +### Patch Changes + +- cc119b2: Fixed an issue causing `OAuthRequestDialog` to re-render on mount. +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## 1.16.2-next.0 + +### Patch Changes + +- 73f6cc3: Updated `I18nextTranslationApi` to support interpolation of JSX elements. +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## 1.16.1 + +### Patch Changes + +- a47fd39: Removes instances of default React imports, a necessary update for the upcoming React 19 migration. + + + +- Updated dependencies + - @backstage/core-plugin-api@1.10.6 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## 1.16.1-next.0 + +### Patch Changes + +- a47fd39: Removes instances of default React imports, a necessary update for the upcoming React 19 migration. + + + +- Updated dependencies + - @backstage/core-plugin-api@1.10.6-next.0 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## 1.16.0 + +### Minor Changes + +- 9262001: The default auth injection middleware for the `FetchApi` will now also take configuration under `discovery.endpoints` into consideration when deciding whether to include credentials or not. +- 12f8e01: The `discovery.endpoints` configuration no longer requires both `internal` and `external` target when using the object form, instead falling back to the default. + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.5 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## 1.16.0-next.0 + +### Minor Changes + +- 9262001: The default auth injection middleware for the `FetchApi` will now also take configuration under `discovery.endpoints` into consideration when deciding whether to include credentials or not. +- 12f8e01: The `discovery.endpoints` configuration no longer requires both `internal` and `external` target when using the object form, instead falling back to the default. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/core-plugin-api@1.10.4 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## 1.15.5 + +### Patch Changes + +- 58ec9e7: Removed older versions of React packages as a preparatory step for upgrading to React 19. This commit does not introduce any functional changes, but removes dependencies on previous React versions, allowing for a cleaner upgrade path in subsequent commits. +- Updated dependencies + - @backstage/core-plugin-api@1.10.4 + - @backstage/version-bridge@1.0.11 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + +## 1.15.5-next.0 + +### Patch Changes + +- 58ec9e7: Removed older versions of React packages as a preparatory step for upgrading to React 19. This commit does not introduce any functional changes, but removes dependencies on previous React versions, allowing for a cleaner upgrade path in subsequent commits. +- Updated dependencies + - @backstage/core-plugin-api@1.10.4-next.0 + - @backstage/version-bridge@1.0.11-next.0 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + +## 1.15.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.3 + - @backstage/types@1.2.1 + - @backstage/config@1.3.2 + - @backstage/version-bridge@1.0.10 + +## 1.15.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.3-next.0 + - @backstage/types@1.2.1-next.0 + - @backstage/config@1.3.2-next.0 + - @backstage/version-bridge@1.0.10 + +## 1.15.3 + +### Patch Changes + +- e5fa018: The OAuth 2 client implementations will now attempt to refresh the session when the existing session doesn't have the required scopes. The previous behavior was to only try to refresh the session of it was missing, and otherwise directly request a new session. This fixes an issue where some auth providers will not return access tokens with certain scopes unless explicitly requested, leading to an auth popup even if the underlying session already had been granted the requested scopes. +- 2830689: Decrease OAuth2 token refresh grace period +- Updated dependencies + - @backstage/config@1.3.1 + - @backstage/core-plugin-api@1.10.2 + - @backstage/types@1.2.0 + - @backstage/version-bridge@1.0.10 + +## 1.15.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.1-next.0 + - @backstage/core-plugin-api@1.10.2-next.0 + - @backstage/types@1.2.0 + - @backstage/version-bridge@1.0.10 + +## 1.15.3-next.0 + +### Patch Changes + +- e5fa018: The OAuth 2 client implementations will now attempt to refresh the session when the existing session doesn't have the required scopes. The previous behavior was to only try to refresh the session of it was missing, and otherwise directly request a new session. This fixes an issue where some auth providers will not return access tokens with certain scopes unless explicitly requested, leading to an auth popup even if the underlying session already had been granted the requested scopes. +- 2830689: Decrease OAuth2 token refresh grace period +- Updated dependencies + - @backstage/config@1.3.0 + - @backstage/core-plugin-api@1.10.1 + - @backstage/types@1.2.0 + - @backstage/version-bridge@1.0.10 + +## 1.15.2 + +### Patch Changes + +- 44b82da: The default config loader no longer requires `process.env.APP_CONFIG` to be set, allowing config to be read from other sources instead. +- Updated dependencies + - @backstage/config@1.3.0 + - @backstage/types@1.2.0 + - @backstage/core-plugin-api@1.10.1 + - @backstage/version-bridge@1.0.10 + +## 1.15.1 + +### Patch Changes + +- e969dc7: Move `@types/react` to a peer dependency. +- Updated dependencies + - @backstage/core-plugin-api@1.10.0 + - @backstage/version-bridge@1.0.10 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## 1.15.1-next.1 + +### Patch Changes + +- e969dc7: Move `@types/react` to a peer dependency. +- Updated dependencies + - @backstage/core-plugin-api@1.10.0-next.1 + - @backstage/version-bridge@1.0.10-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## 1.15.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.0-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.9 + +## 1.15.0 + +### Minor Changes + +- ddbeace: Added the ability to explicitly disable routes through the `bindRoutes` option by passing `false` as the route target. This also fixes a bug where route bindings in config were incorrectly prioritized above the ones in code in certain situations. + +### Patch Changes + +- ea69e46: The `defaultConfigLoader` now also reads configuration from scripts tags with `type="backstage.io/config"`. The tag is expected to contain a JSON-serialized array of `AppConfig` objects. If any of these script tags are present, the injected runtime configuration in the static assets will no longer be used. +- b537bd7: Allow custom star icons to be provided via the `star` and `unstarred` icon overrides. See how to override existing icons in the [Backstage documentation](https://backstage.io/docs/getting-started/app-custom-theme/#custom-icons). +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-plugin-api@1.9.4 + - @backstage/version-bridge@1.0.9 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## 1.14.3-next.0 + +### Patch Changes + +- ea69e46: The `defaultConfigLoader` now also reads configuration from scripts tags with `type="backstage.io/config"`. The tag is expected to contain a JSON-serialized array of `AppConfig` objects. If any of these script tags are present, the injected runtime configuration in the static assets will no longer be used. +- b537bd7: Allow custom star icons to be provided via the `star` and `unstarred` icon overrides. See how to override existing icons in the [Backstage documentation](https://backstage.io/docs/getting-started/app-custom-theme/#custom-icons). +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/version-bridge@1.0.9-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## 1.14.2 + +### Patch Changes + +- 9a46a81: The request to delete the session cookie when running the app in protected mode is now done with a plain `fetch` rather than `FetchApi`. This fixes a bug where the app would immediately try to sign-in again when removing the cookie during logout. +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## 1.14.1-next.0 + +### Patch Changes + +- 9a46a81: The request to delete the session cookie when running the app in protected mode is now done with a plain `fetch` rather than `FetchApi`. This fixes a bug where the app would immediately try to sign-in again when removing the cookie during logout. +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## 1.14.0 + +### Minor Changes + +- d3c39fc: Allow for the disabling of external routes through config, which was rendered impossible after the introduction of default targets. + + ```yaml + app: + routes: + bindings: + # This has the effect of removing the button for registering new + # catalog entities in the scaffolder template list view + scaffolder.registerComponent: false + ``` + +### Patch Changes + +- db2e2d5: Updated config schema to support app.routes.bindings +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## 1.13.1-next.1 + +### Patch Changes + +- db2e2d5: Updated config schema to support app.routes.bindings +- Updated dependencies + - @backstage/core-plugin-api@1.9.3 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## 1.13.0-next.0 + +### Minor Changes + +- d3c39fc: Allow for the disabling of external routes through config, which was rendered impossible after the introduction of default targets. + + ```yaml + app: + routes: + bindings: + # This has the effect of removing the button for registering new + # catalog entities in the scaffolder template list view + scaffolder.registerComponent: false + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.9.3 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## 1.12.6 + +### Patch Changes + +- 35fbe09: Added support for configuration of route bindings through static configuration, and default targets for external route refs. + + In addition to configuring route bindings through code, it is now also possible to configure route bindings under the `app.routes.bindings` key, for example: + + ```yaml + app: + routes: + bindings: + catalog.createComponent: catalog-import.importPage + ``` + + Each key in the route binding object is of the form `.`, where the route name is key used in the `externalRoutes` object passed to `createPlugin`. The value is of the same form, but with the name taken from the plugin `routes` option instead. + + The equivalent of the above configuration in code is the following: + + ```ts + const app = createApp({ + // ... + bindRoutes({ bind }) { + bind(catalogPlugin.externalRoutes, { + createComponent: catalogImportPlugin.routes.importPage, + }); + }, + }); + ``` + +- Updated dependencies + - @backstage/core-plugin-api@1.9.3 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## 1.12.6-next.0 + +### Patch Changes + +- 35fbe09: Added support for configuration of route bindings through static configuration, and default targets for external route refs. + + In addition to configuring route bindings through code, it is now also possible to configure route bindings under the `app.routes.bindings` key, for example: + + ```yaml + app: + routes: + bindings: + catalog.createComponent: catalog-import.importPage + ``` + + Each key in the route binding object is of the form `.`, where the route name is key used in the `externalRoutes` object passed to `createPlugin`. The value is of the same form, but with the name taken from the plugin `routes` option instead. + + The equivalent of the above configuration in code is the following: + + ```ts + const app = createApp({ + // ... + bindRoutes({ bind }) { + bind(catalogPlugin.externalRoutes, { + createComponent: catalogImportPlugin.routes.importPage, + }); + }, + }); + ``` + +- Updated dependencies + - @backstage/core-plugin-api@1.9.3-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## 1.12.5 + +### Patch Changes + +- 1bed9a3: The Backstage identity session expiration check will no longer fall back to using the provider expiration. This was introduced to smooth out the rollout of Backstage release 1.18, and is no longer needed. + +## 1.12.4 + +### Patch Changes + +- c884b9a: The app is now aware of if it is being served from the `app-backend` with a separate public and protected bundles. When in protected mode the app will now continuously refresh the session cookie, as well as clear the cookie if the user signs out. +- abfbcfc: Updated dependency `@testing-library/react` to `^15.0.0`. +- cb1e3b0: Updated dependency `@testing-library/dom` to `^10.0.0`. +- Updated dependencies + - @backstage/core-plugin-api@1.9.2 + - @backstage/version-bridge@1.0.8 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## 1.12.4-next.0 + +### Patch Changes + +- c884b9a: The app is now aware of if it is being served from the `app-backend` with a separate public and protected bundles. When in protected mode the app will now continuously refresh the session cookie, as well as clear the cookie if the user signs out. +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.12.3 + +### Patch Changes + +- e8f026a: Use ESM exports of react-use library +- Updated dependencies + - @backstage/core-plugin-api@1.9.1 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.12.2 + +### Patch Changes + +- e8f026a: Use ESM exports of react-use library +- Updated dependencies + - @backstage/core-plugin-api@1.9.1 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.12.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.12.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.2.0-next.1 + - @backstage/core-plugin-api@1.9.1-next.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.12.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.2-next.0 + - @backstage/core-plugin-api@1.9.1-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.12.0 + +### Minor Changes + +- f919be9: Added a utility API for VMware Cloud auth; the API ref is available in the + `@backstage/core-plugin-api` and `@backstage/frontend-plugin-api` packages, the + implementation is in `@backstage/core-app-api` and a factory has been added to + `@backstage/app-defaults`. + +### Patch Changes + +- 9aac2b0: Use `--cwd` as the first `yarn` argument +- 8fe56a8: Widen `@types/react` dependency range to include version 18. +- 7da67ce: Change `defaultScopes` for Bitbucket auth from invalid `team` to `account`. +- Updated dependencies + - @backstage/core-plugin-api@1.9.0 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.12.0-next.1 + +### Minor Changes + +- f919be9: Added a utility API for VMware Cloud auth; the API ref is available in the + `@backstage/core-plugin-api` and `@backstage/frontend-plugin-api` packages, the + implementation is in `@backstage/core-app-api` and a factory has been added to + `@backstage/app-defaults`. + +### Patch Changes + +- 9aac2b0: Use `--cwd` as the first `yarn` argument +- 8fe56a8: Widen `@types/react` dependency range to include version 18. +- Updated dependencies + - @backstage/core-plugin-api@1.9.0-next.1 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.11.4-next.0 + +### Patch Changes + +- 7da67ce: Change `defaultScopes` for Bitbucket auth from invalid `team` to `account`. +- Updated dependencies + - @backstage/core-plugin-api@1.8.3-next.0 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.11.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.11.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.11.2 + +### Patch Changes + +- 3e358b0: Added deprecation warning for React Router v6 beta, please make sure you have migrated your apps to use React Router v6 stable as support for the beta version will be removed. See the [migration tutorial](https://backstage.io/docs/tutorials/react-router-stable-migration) for more information. +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.11.2-next.1 + +### Patch Changes + +- 3e358b0dff: Added deprecation warning for React Router v6 beta, please make sure you have migrated your apps to use React Router v6 stable as support for the beta version will be removed. See the [migration tutorial](https://backstage.io/docs/tutorials/react-router-stable-migration) for more information. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.11.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1-next.0 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.11.1 + +### Patch Changes + +- 6c2b872153: Add official support for React 18. +- 89d13e5618: Add current and default scopes when refreshing session +- 9ab0572217: Add component data `core.type` marker for `AppRouter` and `FlatRoutes`. +- Updated dependencies + - @backstage/core-plugin-api@1.8.0 + - @backstage/version-bridge@1.0.7 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + +## 1.11.1-next.0 + +### Patch Changes + +- 6c2b872153: Add official support for React 18. +- 89d13e5618: Add current and default scopes when refreshing session +- 9ab0572217: Add component data `core.type` marker for `AppRouter` and `FlatRoutes`. +- Updated dependencies + - @backstage/core-plugin-api@1.8.0-next.0 + - @backstage/version-bridge@1.0.7-next.0 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + +## 1.11.0 + +### Minor Changes + +- c9d9bfeca2: URL encode some well known unsafe characters in `RouteResolver` (and therefore `useRouteRef`) + +### Patch Changes + +- 29e4d8b76b: Fixed bug in `AppRouter` to determine the correct `signOutTargetUrl` if `app.baseUrl` contains a `basePath` +- acca17e91a: Wrap entire app in ``, enabling support for using translations outside plugins. +- 1a0616fa10: Add missing resource and template app icons +- 9a1fce352e: Updated dependency `@testing-library/jest-dom` to `^6.0.0`. +- f95af4e540: Updated dependency `@testing-library/dom` to `^9.0.0`. +- f1b349cfba: Fixed a bug in `TranslationApi` implementation where in some cases it wouldn't notify subscribers of language changes. +- Updated dependencies + - @backstage/core-plugin-api@1.7.0 + - @backstage/version-bridge@1.0.6 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + +## 1.11.0-next.2 + +### Minor Changes + +- c9d9bfeca2: URL encode some well known unsafe characters in `RouteResolver` (and therefore `useRouteRef`) + +### Patch Changes + +- acca17e91a: Wrap entire app in ``, enabling support for using translations outside plugins. +- Updated dependencies + - @backstage/core-plugin-api@1.7.0-next.1 + - @backstage/config@1.1.1-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.5 + +## 1.10.1-next.1 + +### Patch Changes + +- 1a0616fa10: Add missing resource and template app icons +- Updated dependencies + - @backstage/core-plugin-api@1.7.0-next.0 + - @backstage/config@1.1.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.5 + +## 1.10.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.7.0-next.0 + - @backstage/config@1.1.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.5 + +## 1.10.0 + +### Minor Changes + +- 18619f793c94: Fixed two bugs in how the `OAuth2Session` type represents the underlying data. The `expiresAt` and `backstageIdentity` are now both optional, since that's what they are in practice. This is not considered a breaking change since it was effectively a bug in the modelling of the state that this type represents, and the type was not used in any other external contract. +- 18619f793c94: The `OAuth` class which is used by all OAuth providers will now consider both the session expiration of both the Backstage identity as well as the upstream identity provider, and refresh the session with either of them is about to expire. +- 6e30769cc627: Introduced experimental support for internationalization. + +### Patch Changes + +- 406b786a2a2c: Mark package as being free of side effects, allowing more optimized Webpack builds. +- 9fe827b380e1: Internal refactor +- 8cec7664e146: Removed `@types/node` dependency +- Updated dependencies + - @backstage/config@1.1.0 + - @backstage/core-plugin-api@1.6.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.5 + +## 1.10.0-next.3 + +### Patch Changes + +- 406b786a2a2c: Mark package as being free of side effects, allowing more optimized Webpack builds. +- 9fe827b380e1: Internal refactor +- Updated dependencies + - @backstage/config@1.1.0-next.2 + - @backstage/core-plugin-api@1.6.0-next.3 + - @backstage/types@1.1.1-next.0 + - @backstage/version-bridge@1.0.5-next.0 + +## 1.10.0-next.2 + +### Minor Changes + +- 6e30769cc627: Introduced experimental support for internationalization. + +### Patch Changes + +- 8cec7664e146: Removed `@types/node` dependency +- Updated dependencies + - @backstage/core-plugin-api@1.6.0-next.2 + - @backstage/config@1.1.0-next.1 + - @backstage/types@1.1.0 + - @backstage/version-bridge@1.0.4 + +## 1.10.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.0-next.0 + - @backstage/core-plugin-api@1.6.0-next.1 + - @backstage/types@1.1.0 + - @backstage/version-bridge@1.0.4 + +## 1.10.0-next.0 + +### Minor Changes + +- 18619f793c94: Fixed two bugs in how the `OAuth2Session` type represents the underlying data. The `expiresAt` and `backstageIdentity` are now both optional, since that's what they are in practice. This is not considered a breaking change since it was effectively a bug in the modelling of the state that this type represents, and the type was not used in any other external contract. +- 18619f793c94: The `OAuth` class which is used by all OAuth providers will now consider both the session expiration of both the Backstage identity as well as the upstream identity provider, and refresh the session with either of them is about to expire. + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.6.0-next.0 + - @backstage/config@1.0.8 + - @backstage/types@1.1.0 + - @backstage/version-bridge@1.0.4 + +## 1.9.1 + +### Patch Changes + +- 9ae4e7e63836: Fixed a bug that could cause `navigate` analytics events to be misattributed to the plugin mounted on the root route (e.g. the `home` plugin at `/`) when the route that was navigated to wasn't associated with a routable extension. +- Updated dependencies + - @backstage/core-plugin-api@1.5.3 + - @backstage/config@1.0.8 + - @backstage/types@1.1.0 + - @backstage/version-bridge@1.0.4 + +## 1.9.1-next.0 + +### Patch Changes + +- 9ae4e7e63836: Fixed a bug that could cause `navigate` analytics events to be misattributed to the plugin mounted on the root route (e.g. the `home` plugin at `/`) when the route that was navigated to wasn't associated with a routable extension. +- Updated dependencies + - @backstage/core-plugin-api@1.5.3 + - @backstage/config@1.0.8 + - @backstage/types@1.1.0 + - @backstage/version-bridge@1.0.4 + +## 1.9.0 + +### Minor Changes + +- a77ddf7ccd71: add login in popup options to config popup width and height + +### Patch Changes + +- 8174cf4c0edf: Fixing MUI / Material UI references +- Updated dependencies + - @backstage/core-plugin-api@1.5.3 + - @backstage/config@1.0.8 + - @backstage/types@1.1.0 + - @backstage/version-bridge@1.0.4 + +## 1.8.2-next.1 + +### Patch Changes + +- 8174cf4c0edf: Fixing MUI / Material UI references +- Updated dependencies + - @backstage/core-plugin-api@1.5.3-next.1 + - @backstage/config@1.0.8 + - @backstage/types@1.1.0 + - @backstage/version-bridge@1.0.4 + +## 1.8.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.5.3-next.0 + - @backstage/config@1.0.8 + +## 1.8.1 + +### Patch Changes + +- 12adfbc8fe2d: Fixed a bug that prevented accurate plugin and route data from being applied to `navigate` analytics events when users visited pages constructed with ``, ``, and similar components that are used to gather one or more routable extensions under a given path. +- ac677bc30ae0: Expose discovery.endpoints configuration to use FrontendHostDiscovery +- 74b216ee4e50: Add `PropsWithChildren` to usages of `ComponentType`, in preparation for React 18 where the children are no longer implicit. +- Updated dependencies + - @backstage/core-plugin-api@1.5.2 + - @backstage/types@1.1.0 + - @backstage/config@1.0.8 + - @backstage/version-bridge@1.0.4 + +## 1.8.1-next.0 + +### Patch Changes + +- 74b216ee4e50: Add `PropsWithChildren` to usages of `ComponentType`, in preparation for React 18 where the children are no longer implicit. +- Updated dependencies + - @backstage/core-plugin-api@1.5.2-next.0 + - @backstage/config@1.0.7 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.4 + +## 1.8.0 + +### Minor Changes + +- c89437db899: The analytics' `navigate` event will now include the route parameters as attributes of the navigate event + +### Patch Changes + +- b645d70034a: Fixed a bug in the Azure auth provider which prevented getting access tokens with multiple scopes for one resource +- 42d817e76ab: Added `FrontendHostDiscovery` for config driven discovery implementation +- Updated dependencies + - @backstage/config@1.0.7 + - @backstage/core-plugin-api@1.5.1 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.4 + +## 1.8.0-next.1 + +### Minor Changes + +- c89437db899: The analytics' `navigate` event will now include the route parameters as attributes of the navigate event + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.5.1 + - @backstage/config@1.0.7 + +## 1.7.1-next.0 + +### Patch Changes + +- 42d817e76ab: Added `FrontendHostDiscovery` for config driven discovery implementation +- Updated dependencies + - @backstage/core-plugin-api@1.5.1 + - @backstage/config@1.0.7 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.4 + +## 1.7.0 + +### Minor Changes + +- 7908d72e033: Introduce a new global config parameter, `enableExperimentalRedirectFlow`. When enabled, auth will happen with an in-window redirect flow rather than through a popup window. +- c15e0cedbe1: The `AuthConnector` interface now supports specifying a set of scopes when + refreshing a session. The `DefaultAuthConnector` implementation passes the + `scope` query parameter to the auth-backend plugin appropriately. The + `RefreshingAuthSessionManager` passes any scopes in its `GetSessionRequest` + appropriately. + +### Patch Changes + +- 1e4f5e91b8e: Bump `zod` and `zod-to-json-schema` dependencies. +- e0c6e8b9c3c: Update peer dependencies +- Updated dependencies + - @backstage/core-plugin-api@1.5.1 + - @backstage/version-bridge@1.0.4 + - @backstage/config@1.0.7 + - @backstage/types@1.0.2 + +## 1.7.0-next.3 + +### Minor Changes + +- c15e0cedbe1: The `AuthConnector` interface now supports specifying a set of scopes when + refreshing a session. The `DefaultAuthConnector` implementation passes the + `scope` query parameter to the auth-backend plugin appropriately. The + `RefreshingAuthSessionManager` passes any scopes in its `GetSessionRequest` + appropriately. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.7 + - @backstage/core-plugin-api@1.5.1-next.1 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.4-next.0 + +## 1.7.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.5.1-next.1 + - @backstage/config@1.0.7 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.4-next.0 + +## 1.7.0-next.1 + +### Patch Changes + +- 1e4f5e91b8e: Bump `zod` and `zod-to-json-schema` dependencies. +- e0c6e8b9c3c: Update peer dependencies +- Updated dependencies + - @backstage/core-plugin-api@1.5.1-next.0 + - @backstage/version-bridge@1.0.4-next.0 + - @backstage/config@1.0.7 + - @backstage/types@1.0.2 + +## 1.7.0-next.0 + +### Minor Changes + +- 7908d72e033: Introduce a new global config parameter, `enableExperimentalRedirectFlow`. When enabled, auth will happen with an in-window redirect flow rather than through a popup window. + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.5.0 + - @backstage/config@1.0.7 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + +## 1.6.0 + +### Minor Changes + +- 456eaa8cf83: `OAuth2` now gets ID tokens from a session with the `openid` scope explicitly + requested. + + This should not be considered a breaking change, because spec-compliant OIDC + providers will already be returning ID tokens if and only if the `openid` scope + is granted. + + This change makes the dependence explicit, and removes the burden on + OAuth2-based providers which require an ID token (e.g. this is done by various + default [auth handlers](https://backstage.io/docs/auth/identity-resolver/#authhandler)) to add + `openid` to their default scopes. _That_ could carry another indirect benefit: + by removing `openid` from the default scopes for a provider, grants for + resource-specific access tokens can avoid requesting excess ID token-related + scopes. + +### Patch Changes + +- 52b0022dab7: Updated dependency `msw` to `^1.0.0`. +- Updated dependencies + - @backstage/core-plugin-api@1.5.0 + - @backstage/config@1.0.7 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + +## 1.6.0-next.2 + +### Minor Changes + +- 456eaa8cf83: `OAuth2` now gets ID tokens from a session with the `openid` scope explicitly + requested. + + This should not be considered a breaking change, because spec-compliant OIDC + providers will already be returning ID tokens if and only if the `openid` scope + is granted. + + This change makes the dependence explicit, and removes the burden on + OAuth2-based providers which require an ID token (e.g. this is done by various + default [auth handlers](https://backstage.io/docs/auth/identity-resolver/#authhandler)) to add + `openid` to their default scopes. _That_ could carry another indirect benefit: + by removing `openid` from the default scopes for a provider, grants for + resource-specific access tokens can avoid requesting excess ID token-related + scopes. + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.5.0-next.2 + - @backstage/config@1.0.7-next.0 + +## 1.5.1-next.1 + +### Patch Changes + +- 52b0022dab7: Updated dependency `msw` to `^1.0.0`. +- Updated dependencies + - @backstage/core-plugin-api@1.4.1-next.1 + - @backstage/config@1.0.7-next.0 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + +## 1.5.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.4.1-next.0 + - @backstage/config@1.0.6 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + +## 1.5.0 + +### Minor Changes + +- db10b6ef65: Added a Bitbucket Server Auth Provider and added its API to the app defaults + +### Patch Changes + +- dff4d8ddb1: Fixed an issue where an explicit port the frontend base URL could break the app. +- Updated dependencies + - @backstage/core-plugin-api@1.4.0 + - @backstage/config@1.0.6 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + +## 1.4.1-next.0 + +### Patch Changes + +- dff4d8ddb1: Fixed an issue where an explicit port the frontend base URL could break the app. +- Updated dependencies + - @backstage/config@1.0.6 + - @backstage/core-plugin-api@1.3.0 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + +## 1.4.0 + +### Minor Changes + +- bca8e8b393: Allow defining application level feature flags. See [Feature Flags documentation](https://backstage.io/docs/plugins/feature-flags#in-the-application) for reference. + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.3.0 + - @backstage/config@1.0.6 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + +## 1.4.0-next.1 + +### Minor Changes + +- bca8e8b393: Allow defining application level feature flags. See [Feature Flags documentation](https://backstage.io/docs/plugins/feature-flags#in-the-application) for reference. + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.3.0-next.1 + - @backstage/config@1.0.6-next.0 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + +## 1.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.6-next.0 + - @backstage/core-plugin-api@1.2.1-next.0 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + +## 1.3.0 + +### Minor Changes + +- e0d9c9559a: Added a new `AppRouter` component and `app.createRoot()` method that replaces `app.getRouter()` and `app.getProvider()`, which are now deprecated. The new `AppRouter` component is a drop-in replacement for the old router component, while the new `app.createRoot()` method is used instead of the old provider component. + + An old app setup might look like this: + + ```tsx + const app = createApp(/* ... */); + + const AppProvider = app.getProvider(); + const AppRouter = app.getRouter(); + + const routes = ...; + + const App = () => ( + + + + + {routes} + + + ); + + export default App; + ``` + + With these new APIs, the setup now looks like this: + + ```tsx + import { AppRouter } from '@backstage/core-app-api'; + + const app = createApp(/* ... */); + + const routes = ...; + + export default app.createRoot( + <> + + + + {routes} + + , + ); + ``` + + Note that `app.createRoot()` accepts a React element, rather than a component. + +### Patch Changes + +- d3fea4ae0a: Internal fixes to avoid implicit usage of globals +- b05dcd5530: Move the `zod` dependency to a version that does not collide with other libraries +- b4b5b02315: Tweak feature flag registration so that it happens immediately before the first rendering of the app, rather than just after. +- 6870b43dd1: Fix for the automatic rewriting of base URLs. +- 203271b746: Prevent duplicate feature flag components from rendering in the settings when using components +- 3280711113: Updated dependency `msw` to `^0.49.0`. +- 19356df560: Updated dependency `zen-observable` to `^0.9.0`. +- c3fa90e184: Updated dependency `zen-observable` to `^0.10.0`. +- 8015ff1258: Tweaked wording to use inclusive terminology +- 653d7912ac: Made `WebStorage` notify its subscribers when `localStorage` values change in other tabs/windows +- 63310e3987: Apps will now rewrite the `app.baseUrl` configuration to match the current `location.origin`. The `backend.baseUrl` will also be rewritten in the same way when the `app.baseUrl` and `backend.baseUrl` have matching origins. This will reduce the need for separate frontend builds for different environments. +- Updated dependencies + - @backstage/core-plugin-api@1.2.0 + - @backstage/version-bridge@1.0.3 + - @backstage/types@1.0.2 + - @backstage/config@1.0.5 + +## 1.3.0-next.4 + +### Minor Changes + +- e0d9c9559a: Added a new `AppRouter` component and `app.createRoot()` method that replaces `app.getRouter()` and `app.getProvider()`, which are now deprecated. The new `AppRouter` component is a drop-in replacement for the old router component, while the new `app.createRoot()` method is used instead of the old provider component. + + An old app setup might look like this: + + ```tsx + const app = createApp(/* ... */); + + const AppProvider = app.getProvider(); + const AppRouter = app.getRouter(); + + const routes = ...; + + const App = () => ( + + + + + {routes} + + + ); + + export default App; + ``` + + With these new APIs, the setup now looks like this: + + ```tsx + import { AppRouter } from '@backstage/core-app-api'; + + const app = createApp(/* ... */); + + const routes = ...; + + export default app.createRoot( + <> + + + + {routes} + + , + ); + ``` + + Note that `app.createRoot()` accepts a React element, rather than a component. + +### Patch Changes + +- b05dcd5530: Move the `zod` dependency to a version that does not collide with other libraries +- Updated dependencies + - @backstage/config@1.0.5-next.1 + - @backstage/core-plugin-api@1.2.0-next.2 + - @backstage/types@1.0.2-next.1 + - @backstage/version-bridge@1.0.3-next.0 + +## 1.2.1-next.3 + +### Patch Changes + +- 6870b43dd1: Fix for the automatic rewriting of base URLs. +- 653d7912ac: Made `WebStorage` notify its subscribers when `localStorage` values change in other tabs/windows +- Updated dependencies + - @backstage/config@1.0.5-next.1 + - @backstage/core-plugin-api@1.2.0-next.2 + - @backstage/types@1.0.2-next.1 + - @backstage/version-bridge@1.0.3-next.0 + +## 1.2.1-next.2 + +### Patch Changes + +- b4b5b02315: Tweak feature flag registration so that it happens immediately before the first rendering of the app, rather than just after. +- 203271b746: Prevent duplicate feature flag components from rendering in the settings when using components +- 8015ff1258: Tweaked wording to use inclusive terminology +- 63310e3987: Apps will now rewrite the `app.baseUrl` configuration to match the current `location.origin`. The `backend.baseUrl` will also be rewritten in the same way when the `app.baseUrl` and `backend.baseUrl` have matching origins. This will reduce the need for separate frontend builds for different environments. +- Updated dependencies + - @backstage/core-plugin-api@1.2.0-next.2 + - @backstage/config@1.0.5-next.1 + - @backstage/types@1.0.2-next.1 + - @backstage/version-bridge@1.0.3-next.0 + +## 1.2.1-next.1 + +### Patch Changes + +- d3fea4ae0a: Internal fixes to avoid implicit usage of globals +- c3fa90e184: Updated dependency `zen-observable` to `^0.10.0`. +- Updated dependencies + - @backstage/version-bridge@1.0.3-next.0 + - @backstage/core-plugin-api@1.1.1-next.1 + - @backstage/types@1.0.2-next.1 + - @backstage/config@1.0.5-next.1 + +## 1.2.1-next.0 + +### Patch Changes + +- 3280711113: Updated dependency `msw` to `^0.49.0`. +- 19356df560: Updated dependency `zen-observable` to `^0.9.0`. +- Updated dependencies + - @backstage/core-plugin-api@1.1.1-next.0 + - @backstage/types@1.0.2-next.0 + - @backstage/config@1.0.5-next.0 + - @backstage/version-bridge@1.0.2 + +## 1.2.0 + +### Minor Changes + +- 9b737e5f2e: Updated the React Router wiring to make use of the new `basename` property of the router components in React Router v6 stable. To implement this, a new optional `basename` property has been added to the `Router` app component, which can be forwarded to the concrete router implementation in order to support this new behavior. This is done by default in any app that does not have a `Router` component override. +- 127fcad26d: Deprecated the `homepage` config as the component that used it - `HomepageTimer` - has been removed and replaced by the `HeaderWorldClock` in the home plugin + +### Patch Changes + +- Updated dependencies + - @backstage/version-bridge@1.0.2 + - @backstage/core-plugin-api@1.1.0 + - @backstage/types@1.0.1 + - @backstage/config@1.0.4 + +## 1.2.0-next.0 + +### Minor Changes + +- 9b737e5f2e: Updated the React Router wiring to make use of the new `basename` property of the router components in React Router v6 stable. To implement this, a new optional `basename` property has been added to the `Router` app component, which can be forwarded to the concrete router implementation in order to support this new behavior. This is done by default in any app that does not have a `Router` component override. +- 127fcad26d: Deprecated the `homepage` config as the component that used it - `HomepageTimer` - has been removed and replaced by the `HeaderWorldClock` in the home plugin + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/version-bridge@1.0.1 + +## 1.1.1 + +### Patch Changes + +- 27e6404aba: Fixed a bug where gathered index routes would fail to bind routable extensions. This would typically show up when placing a routable extension in the entity page overview tab. +- Updated dependencies + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## 1.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## 1.1.1-next.1 + +### Patch Changes + +- 27e6404aba: Fixed a bug where gathered index routes would fail to bind routable extensions. This would typically show up when placing a routable extension in the entity page overview tab. +- Updated dependencies + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## 1.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## 1.1.0 + +### Minor Changes + +- a448fea691: Updated the routing system to be compatible with React Router v6 stable. + +### Patch Changes + +- 817f3196f6: Updated React Router dependencies to be peer dependencies. + +- f9ec4e46e3: When using React Router v6 stable, it is now possible for components within the `Route` element tree to have `path` props, although they will be ignored. + +- 7d47def9c4: Removed dependency on `@types/jest`. + +- 744fea158b: Added `getSystemIcons()` function to the `AppContext` available through `useApp` that will pull a list of all the icons that have been registered in the App. + +- 667d917488: Updated dependency `msw` to `^0.47.0`. + +- 87ec2ba4d6: Updated dependency `msw` to `^0.46.0`. + +- bf5e9030eb: Updated dependency `msw` to `^0.45.0`. + +- 8448b53dd6: Clarify that the `WebStorage` observable returns `JsonValue` items. + +- 70299c99d5: Updated `FlatRoutes` to be compatible with React Router v6 stable. + +- e9d40ebf54: If you'd like to send analytics events to multiple implementations, you may now + do so using the `MultipleAnalyticsApi` implementation provided by this package. + + ```tsx + import { MultipleAnalyticsApi } from '@backstage/core-app-api'; + import { + analyticsApiRef, + configApiRef, + storageApiRef, + identityApiRef, + } from '@internal/backstage/core-plugin-api'; + import { CustomAnalyticsApi } from '@internal/analytics'; + import { VendorAnalyticsApi } from '@vendor/analytics'; + + createApiFactory({ + api: analyticsApiRef, + deps: { configApi: configApiRef, identityApi: identityApiRef, storageApi: storageApiRef }, + factory: ({ configApi, identityApi, storageApi }) => + MultipleAnalyticsApi.fromApis([ + VendorAnalyticsApi.fromConfig(configApi, { identityApi }), + CustomAnalyticsApi.fromConfig(configApi, { identityApi, storageApi }), + ]), + }), + ``` + +- Updated dependencies + - @backstage/core-plugin-api@1.0.6 + - @backstage/config@1.0.2 + +## 1.1.0-next.3 + +### Patch Changes + +- 7d47def9c4: Removed dependency on `@types/jest`. +- Updated dependencies + - @backstage/config@1.0.2-next.0 + - @backstage/core-plugin-api@1.0.6-next.3 + +## 1.1.0-next.2 + +### Patch Changes + +- f9ec4e46e3: When using React Router v6 stable, it is now possible for components within the `Route` element tree to have `path` props, although they will be ignored. + +- 667d917488: Updated dependency `msw` to `^0.47.0`. + +- 87ec2ba4d6: Updated dependency `msw` to `^0.46.0`. + +- e9d40ebf54: If you'd like to send analytics events to multiple implementations, you may now + do so using the `MultipleAnalyticsApi` implementation provided by this package. + + ```tsx + import { MultipleAnalyticsApi } from '@backstage/core-app-api'; + import { + analyticsApiRef, + configApiRef, + storageApiRef, + identityApiRef, + } from '@internal/backstage/core-plugin-api'; + import { CustomAnalyticsApi } from '@internal/analytics'; + import { VendorAnalyticsApi } from '@vendor/analytics'; + + createApiFactory({ + api: analyticsApiRef, + deps: { configApi: configApiRef, identityApi: identityApiRef, storageApi: storageApiRef }, + factory: ({ configApi, identityApi, storageApi }) => + MultipleAnalyticsApi.fromApis([ + VendorAnalyticsApi.fromConfig(configApi, { identityApi }), + CustomAnalyticsApi.fromConfig(configApi, { identityApi, storageApi }), + ]), + }), + ``` + +- Updated dependencies + - @backstage/core-plugin-api@1.0.6-next.2 + +## 1.1.0-next.1 + +### Minor Changes + +- a448fea691: Updated the routing system to be compatible with React Router v6 stable. + +### Patch Changes + +- 817f3196f6: Updated React Router dependencies to be peer dependencies. +- 70299c99d5: Updated `FlatRoutes` to be compatible with React Router v6 stable. +- Updated dependencies + - @backstage/core-plugin-api@1.0.6-next.1 + +## 1.0.6-next.0 + +### Patch Changes + +- 744fea158b: Added `getSystemIcons()` function to the `AppContext` available through `useApp` that will pull a list of all the icons that have been registered in the App. +- bf5e9030eb: Updated dependency `msw` to `^0.45.0`. +- Updated dependencies + - @backstage/core-plugin-api@1.0.6-next.0 + +## 1.0.5 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.0.5 + +## 1.0.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.0.5-next.0 + +## 1.0.4 + +### Patch Changes + +- 881fc75a75: Internal tweak removing usage of explicit type parameters for the `BackstagePlugin` type. +- 8fe2357101: The `signOut` method of the `IdentityApi` will now navigate the user back to the base URL of the app as indicated by the `app.baseUrl` config. +- a70869e775: Updated dependency `msw` to `^0.43.0`. +- 8006d0f9bf: Updated dependency `msw` to `^0.44.0`. +- Updated dependencies + - @backstage/core-plugin-api@1.0.4 + +## 1.0.4-next.1 + +### Patch Changes + +- 881fc75a75: Internal tweak removing usage of explicit type parameters for the `BackstagePlugin` type. +- a70869e775: Updated dependency `msw` to `^0.43.0`. +- Updated dependencies + - @backstage/core-plugin-api@1.0.4-next.0 + +## 1.0.4-next.0 + +### Patch Changes + +- 8fe2357101: The `signOut` method of the `IdentityApi` will now navigate the user back to the base URL of the app as indicated by the `app.baseUrl` config. + +## 1.0.3 + +### Patch Changes + +- 8f7b1835df: Updated dependency `msw` to `^0.41.0`. +- 19781483a2: Handle URLs as the first argument to `fetchApi`, when using the `plugin:` protocol +- Updated dependencies + - @backstage/core-plugin-api@1.0.3 + +## 1.0.3-next.0 + +### Patch Changes + +- 8f7b1835df: Updated dependency `msw` to `^0.41.0`. +- Updated dependencies + - @backstage/core-plugin-api@1.0.3-next.0 + +## 1.0.2 + +### Patch Changes + +- 1fae1f57c9: Fix SAML session schema to no longer require the (deprecated) id, to unbreak session data storage. +- Updated dependencies + - @backstage/core-plugin-api@1.0.2 + - @backstage/config@1.0.1 + +## 1.0.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.1-next.0 + - @backstage/core-plugin-api@1.0.2-next.1 + +## 1.0.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.0.2-next.0 + +## 1.0.1 + +### Patch Changes + +- 7c7919777e: build(deps-dev): bump `@testing-library/react-hooks` from 7.0.2 to 8.0.0 +- 24254fd433: build(deps): bump `@testing-library/user-event` from 13.5.0 to 14.0.0 +- 3ff2bfb66e: Refactored the route collection logic to prepare for future changes and avoid duplicate element tree traversal for the analytics context. +- a7bb762dab: fixed empty body issue for POST requests using FetchAPI with 'plugin://' prefix +- 230ad0826f: Bump to using `@types/node` v16 +- c47509e1a0: Implemented changes suggested by Deepsource.io including multiple double non-null assertion operators and unexpected awaits for non-promise values. +- Updated dependencies + - @backstage/core-plugin-api@1.0.1 + - @backstage/version-bridge@1.0.1 + +## 1.0.1-next.1 + +### Patch Changes + +- 24254fd433: build(deps): bump `@testing-library/user-event` from 13.5.0 to 14.0.0 +- 3ff2bfb66e: Refactored the route collection logic to prepare for future changes and avoid duplicate element tree traversal for the analytics context. +- 230ad0826f: Bump to using `@types/node` v16 +- Updated dependencies + - @backstage/core-plugin-api@1.0.1-next.0 + +## 1.0.1-next.0 + +### Patch Changes + +- a7bb762dab: fixed empty body issue for POST requests using FetchAPI with 'plugin://' prefix +- c47509e1a0: Implemented changes suggested by Deepsource.io including multiple double non-null assertion operators and unexpected awaits for non-promise values. + +## 1.0.0 + +### Major Changes + +- b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/core-plugin-api@1.0.0 + - @backstage/version-bridge@1.0.0 + - @backstage/config@1.0.0 + - @backstage/types@1.0.0 + +## 0.6.0 + +### Minor Changes + +- bb2bb36651: **BREAKING**: Removed the deprecated `get` method from `StorageAPI` and its implementations, this method has been replaced by the `snapshot` method. The return value from snapshot no longer includes `newValue` which has been replaced by `value`. For getting notified when a value changes, use \`observe# @backstage/core-app-api. +- f3cce3dcf7: **BREAKING**: Removed export of `GithubSession` and `SamlSession` which are only used internally. +- af5eaa87f4: **BREAKING**: Removed deprecated `auth0AuthApiRef`, `oauth2ApiRef`, `samlAuthApiRef` and `oidcAuthApiRef` as these APIs are too generic to be useful. Instructions for how to migrate can be found at . +- dbf84eee55: **BREAKING**: Removed the deprecated `GithubAuth.normalizeScopes` method. + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@0.8.0 + +## 0.5.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@0.7.0 + +## 0.5.3 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-plugin-api@0.6.1 + - @backstage/config@0.1.14 + - @backstage/types@0.1.2 + - @backstage/version-bridge@0.1.2 + +## 0.5.2 + +### Patch Changes + +- 40775bd263: Switched out the `GithubAuth` implementation to use the common `OAuth2` implementation. This relies on the simultaneous change in `@backstage/plugin-auth-backend` that enabled access token storage in cookies rather than the current solution that's based on `LocalStorage`. + + > **NOTE:** Make sure you upgrade the `auth-backend` deployment before or at the same time as you deploy this change. + +## 0.5.2-next.0 + +### Patch Changes + +- 40775bd263: Switched out the `GithubAuth` implementation to use the common `OAuth2` implementation. This relies on the simultaneous change in `@backstage/plugin-auth-backend` that enabled access token storage in cookies rather than the current solution that's based on `LocalStorage`. + + > **NOTE:** Make sure you upgrade the `auth-backend` deployment before or at the same time as you deploy this change. + +## 0.5.1 + +### Patch Changes + +- f959c22787: Asynchronous methods on the identity API can now reliably be called at any time, including early in the bootstrap process or prior to successful sign-in. + + Previously in such situations, a `Tried to access IdentityApi before app was loaded` error would be thrown. Now, those methods will wait and resolve eventually (as soon as a concrete identity API is provided). + +## 0.5.0 + +### Minor Changes + +- ceebe25391: Removed deprecated `SignInResult` type, which was replaced with the new `onSignInSuccess` callback. + +### Patch Changes + +- fb565073ec: Add an `allowUrl` callback option to `FetchMiddlewares.injectIdentityAuth` +- f050eec2c0: Added validation during the application startup that detects if there are any plugins present that have not had their required external routes bound. Failing the validation will cause a hard crash as it is a programmer error. It lets you detect early on that there are dangling routes, rather than having them cause an error later on. +- Updated dependencies + - @backstage/core-plugin-api@0.6.0 + - @backstage/config@0.1.13 + +## 0.5.0-next.0 + +### Minor Changes + +- ceebe25391: Removed deprecated `SignInResult` type, which was replaced with the new `onSignInSuccess` callback. + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@0.6.0-next.0 + - @backstage/config@0.1.13-next.0 + +## 0.4.0 + +### Minor Changes + +- e2eb92c109: Removed previously deprecated `ApiRegistry` export. + +### Patch Changes + +- 34442cd5cf: Fixed an issue where valid SAML and GitHub sessions would be considered invalid and not be stored. + + Deprecated the `SamlSession` and `GithubSession` types. + +- 784d8078ab: Removed direct and transitive Material UI dependencies. + +- Updated dependencies + - @backstage/config@0.1.12 + - @backstage/core-plugin-api@0.5.0 + +## 0.3.1 + +### Patch Changes + +- 4ce51ab0f1: Internal refactor of the `react-use` imports to use `react-use/lib/*` instead. +- Updated dependencies + - @backstage/core-plugin-api@0.4.1 + - @backstage/core-components@0.8.3 + +## 0.3.0 + +### Minor Changes + +- a195284c7b: Updated `WebStorageApi` to reflect the `StorageApi` changes in `@backstage/core-plugin-api`. +- b3605da81c: - Removed deprecated definition `createApp` from `@backstage/core-app-api` which has been replaced by `@backstage/app-defaults#createApp` + - Removed deprecated type `BackstagePluginWithAnyOutput` + - Removed deprecated constructors for `GithubAuth`, `OAuth2`, and `SamlAuth` as the `create` method should be used instead +- 68f8b10ccd: - Removed deprecation configuration option `theme` from `AppTheme` of the `AppThemeApi` + - Removed reference to `theme` in the `app-defaults` default `AppTheme` + - Removed logic in `AppThemeProvider` that creates `ThemeProvider` from `appTheme.theme` + +### Patch Changes + +- 7927005152: Add `FetchApi` and related `fetchApiRef` which implement fetch, with an added Backstage token header when available. +- 518ddc00bc: Schema-validate local storage cached session info on load +- Updated dependencies + - @backstage/app-defaults@0.1.3 + - @backstage/core-plugin-api@0.4.0 + - @backstage/core-components@0.8.2 + +## 0.2.1 + +### Patch Changes + +- c11ce4f552: Deprecated `Auth0Auth`, pointing to using `OAuth2` directly instead. +- 9d6503e86c: Switched out usage of deprecated `OAuthRequestApi` types from `@backstage/core-plugin-api`. +- Updated dependencies + - @backstage/core-plugin-api@0.3.1 + - @backstage/core-components@0.8.1 + +## 0.2.0 + +### Minor Changes + +- a036b65c2f: **BREAKING CHANGE** + + The app `SignInPage` component has been updated to switch out the `onResult` callback for a new `onSignInSuccess` callback. This is an immediate breaking change without any deprecation period, as it was deemed to be the way of making this change that had the lowest impact. + + The new `onSignInSuccess` callback directly accepts an implementation of an `IdentityApi`, rather than a `SignInResult`. The `SignInPage` from `@backstage/core-component` has been updated to fit this new API, and as long as you pass on `props` directly you should not see any breakage. + + However, if you implement your own custom `SignInPage`, then this will be a breaking change and you need to migrate over to using the new callback. While doing so you can take advantage of the `UserIdentity.fromLegacy` helper from `@backstage/core-components` to make the migration simpler by still using the `SignInResult` type. This helper is also deprecated though and is only provided for immediate migration. Long-term it will be necessary to build the `IdentityApi` using for example `UserIdentity.create` instead. + + The following is an example of how you can migrate existing usage immediately using `UserIdentity.fromLegacy`: + + ```ts + onResult(signInResult); + // becomes + onSignInSuccess(UserIdentity.fromLegacy(signInResult)); + ``` + + The following is an example of how implement the new `onSignInSuccess` callback of the `SignInPage` using `UserIdentity.create`: + + ```ts + const identityResponse = await authApi.getBackstageIdentity(); + // Profile is optional and will be removed, but allows the + // synchronous getProfile method of the IdentityApi to be used. + const profile = await authApi.getProfile(); + onSignInSuccess( + UserIdentity.create({ + identity: identityResponse.identity, + authApi, + profile, + }), + ); + ``` + +### Patch Changes + +- cd450844f6: Moved React dependencies to `peerDependencies` and allow both React v16 and v17 to be used. +- dcd1a0c3f4: Minor improvement to the API reports, by not unpacking arguments directly +- Updated dependencies + - @backstage/core-components@0.8.0 + - @backstage/core-plugin-api@0.3.0 + - @backstage/app-defaults@0.1.2 + - @backstage/version-bridge@0.1.1 + +## 0.1.24 + +### Patch Changes + +- 0e7f256034: Fixed a bug where `useRouteRef` would fail in situations where relative navigation was needed and the app was is mounted on a sub-path. This would typically show up as a failure to navigate to a tab on an entity page. +- Updated dependencies + - @backstage/core-components@0.7.6 + - @backstage/theme@0.2.14 + - @backstage/core-plugin-api@0.2.2 + +## 0.1.23 + +### Patch Changes + +- bab752e2b3: Change default port of backend from 7000 to 7007. + + This is due to the AirPlay Receiver process occupying port 7000 and preventing local Backstage instances on MacOS to start. + + You can change the port back to 7000 or any other value by providing an `app-config.yaml` with the following values: + + backend: + listen: 0.0.0.0:7123 + baseUrl: http://localhost:7123 + + More information can be found here: + +- 000190de69: The `ApiRegistry` from `@backstage/core-app-api` class has been deprecated and will be removed in a future release. To replace it, we have introduced two new helpers that are exported from `@backstage/test-utils`, namely `TestApiProvider` and `TestApiRegistry`. + + These two new helpers are more tailored for writing tests and development setups, as they allow for partial implementations of each of the APIs. + + When migrating existing code it is typically best to prefer usage of `TestApiProvider` when possible, so for example the following code: + + ```tsx + render( + + {...} + + ) + ``` + + Would be migrated to this: + + ```tsx + render( + + {...} + + ) + ``` + + In cases where the `ApiProvider` is used in a more standalone way, for example to reuse a set of APIs across multiple tests, the `TestApiRegistry` can be used instead. Note that the `TestApiRegistry` only has a single static factory method, `.from()`, and it is slightly different from the existing `.from()` method on `ApiRegistry` in that it doesn't require the API pairs to be wrapped in an outer array. + + Usage that looks like this: + + ```ts + const apis = ApiRegistry.with( + identityApiRef, + mockIdentityApi as unknown as IdentityApi, + ).with(configApiRef, new ConfigReader({})); + ``` + + OR like this: + + ```ts + const apis = ApiRegistry.from([ + [identityApiRef, mockIdentityApi as unknown as IdentityApi], + [configApiRef, new ConfigReader({})], + ]); + ``` + + Would be migrated to this: + + ```ts + const apis = TestApiRegistry.from( + [identityApiRef, mockIdentityApi], + [configApiRef, new ConfigReader({})], + ); + ``` + + If your app is still using the `ApiRegistry` to construct the `apis` for `createApp`, we recommend that you move over to use the new method of supplying API factories instead, using `createApiFactory`. + +- Updated dependencies + - @backstage/core-plugin-api@0.2.1 + - @backstage/core-components@0.7.5 + +## 0.1.22 + +### Patch Changes + +- Reverted the `createApp` TypeScript type to match the one before version `0.1.21`, as it was an accidental breaking change. + +## 0.1.21 + +### Patch Changes + +- 0b1de52732: Migrated to using new `ErrorApiError` and `ErrorApiErrorContext` names. + +- ecd1fcb80a: Deprecated the `BackstagePluginWithAnyOutput` type. + +- 32bfbafb0f: Start exporting and marking several types as public to address errors in the API report. + +- 014cbf8cb9: The `createApp` function from `@backstage/core-app-api` has been deprecated, with two new options being provided as a replacement. + + The first and most commonly used one is `createApp` from the new `@backstage/app-defaults` package, which behaves just like the existing `createApp`. In the future this method is likely to be expanded to add more APIs and other pieces into the default setup, for example the Utility APIs from `@backstage/integration-react`. + + The other option that we now provide is to use `createSpecializedApp` from `@backstage/core-app-api`. This is a more low-level API where you need to provide a full set of options, including your own `components`, `icons`, `defaultApis`, and `themes`. The `createSpecializedApp` way of creating an app is particularly useful if you are not using `@backstage/core-components` or Material UI, as it allows you to avoid those dependencies completely. + +- 475edb5bc5: move the BehaviorSubject init into the constructor + +- Updated dependencies + - @backstage/core-components@0.7.4 + - @backstage/core-plugin-api@0.2.0 + - @backstage/app-defaults@0.1.1 + +## 0.1.20 + +### Patch Changes + +- 78c512ce8f: I have added default icons for the catalog, scaffolder, techdocs, and search. +- 8b4284cd5c: Improve API documentation for @backstage/core-plugin-api +- Updated dependencies + - @backstage/core-components@0.7.3 + - @backstage/theme@0.2.13 + - @backstage/core-plugin-api@0.1.13 + +## 0.1.19 + +### Patch Changes + +- 10615525f3: Switch to use the json and observable types from `@backstage/types` +- 41c49884d2: Start using the new `@backstage/types` package. Initially, this means using the `Observable` and `Json*` types from there. The types also remain in their old places but deprecated, and will be removed in a future release. +- 925a967f36: Replace usage of test-utils-core with test-utils +- 6b615e92c8: Api cleanup, adding `@public` where necessary and tweaking some comments +- Updated dependencies + - @backstage/config@0.1.11 + - @backstage/theme@0.2.12 + - @backstage/core-components@0.7.2 + - @backstage/core-plugin-api@0.1.12 + +## 0.1.18 + +### Patch Changes + +- 202f322927: Atlassian auth provider + + - AtlassianAuth added to core-app-api + - Atlassian provider added to plugin-auth-backend + - Updated user-settings with Atlassian connection + +- 36e67d2f24: Internal updates to apply more strict checks to throw errors. + +- Updated dependencies + - @backstage/core-components@0.7.1 + - @backstage/core-plugin-api@0.1.11 + +## 0.1.17 + +### Patch Changes + +- 75bc878221: Internal refactor to avoid importing all of `@material-ui/core`. +- Updated dependencies + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + +## 0.1.16 + +### Patch Changes + +- d9fd798cc8: The Core App API now automatically instruments all route location changes using + the new Analytics API. Each location change triggers a `navigate` event, which + is an analogue of a "pageview" event in traditional web analytics systems. In + addition to the path, these events provide plugin-level metadata via the + analytics context, which can be useful for analyzing plugin usage: + + ```json + { + "action": "navigate", + "subject": "/the-path/navigated/to?with=params#and-hashes", + "context": { + "extension": "App", + "pluginId": "id-of-plugin-that-exported-the-route", + "routeRef": "associated-route-ref-id" + } + } + ``` + + These events can be identified and handled by checking for the action + `navigate` and the extension `App`. + +- 4c3eea7788: Bitbucket Cloud authentication - based on the existing GitHub authentication + changes around BB apis and updated scope. + + - BitbucketAuth added to core-app-api. + - Bitbucket provider added to plugin-auth-backend. + - Cosmetic entry for Bitbucket connection in user-settings Authentication Providers tab. + +- d6ad46eb22: Stop calling connector.removeSession in StaticAuthSessionManager, instead just discarding the + session locally. + +- Updated dependencies + - @backstage/core-components@0.6.1 + - @backstage/core-plugin-api@0.1.10 + +## 0.1.15 + +### Patch Changes + +- 0c4ee1876f: Enables late registration of plugins into the application by updating ApiHolder when additional plugins have been added in. +- Updated dependencies + - @backstage/core-plugin-api@0.1.9 + - @backstage/core-components@0.6.0 + +## 0.1.14 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.5.0 + - @backstage/config@0.1.10 + +## 0.1.13 + +### Patch Changes + +- 671015f132: Switch to using utilities from \`@backstage/version-bridge'. +- bd1981d609: Allow users to specify their own AppThemeProvider +- Updated dependencies + - @backstage/core-components@0.4.2 + - @backstage/core-plugin-api@0.1.8 + +## 0.1.12 + +### Patch Changes + +- 841666a19: Removed deprecated internal functions. +- Updated dependencies + - @backstage/core-components@0.4.1 + - @backstage/config@0.1.9 + - @backstage/core-plugin-api@0.1.7 + +## 0.1.11 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.4.0 + +## 0.1.10 + +### Patch Changes + +- cfcb486aa: Add system icons for the built-in entity types and use them in the entity list of the `catalog-import` plugin. + +- 392b36fa1: Added support for using authenticating via GitHub Apps in addition to GitHub OAuth Apps. It used to be possible to use GitHub Apps, but they did not handle session refresh correctly. + + Note that GitHub Apps handle OAuth scope at the app installation level, meaning that the `scope` parameter for `getAccessToken` has no effect. When calling `getAccessToken` in open source plugins, one should still include the appropriate scope, but also document in the plugin README what scopes are required in the case of GitHub Apps. + + In addition, the `authHandler` and `signInResolver` options have been implemented for the GitHub provider in the auth backend. + +- Updated dependencies + - @backstage/core-components@0.3.3 + - @backstage/config@0.1.8 + +## 0.1.9 + +### Patch Changes + +- 72a31c29a: Add support for additional app origins +- Updated dependencies + - @backstage/config@0.1.7 + - @backstage/core-components@0.3.2 + - @backstage/theme@0.2.10 + +## 0.1.8 + +### Patch Changes + +- 362657623: Add support for serving the app with a base path other than `/`, which is enabled by including the path in `app.baseUrl`. +- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version. +- Updated dependencies + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + +## 0.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.3.0 + - @backstage/config@0.1.6 + - @backstage/core-plugin-api@0.1.5 + +## 0.1.6 + +### Patch Changes + +- 9d40fcb1e: - Bumping `material-ui/core` version to at least `4.12.2` as they made some breaking changes in later versions which broke `Pagination` of the `Table`. + - Switching out `material-table` to `@material-table/core` for support for the later versions of `material-ui/core` + - This causes a minor API change to `@backstage/core-components` as the interface for `Table` re-exports the `prop` from the underlying `Table` components. + - `onChangeRowsPerPage` has been renamed to `onRowsPerPageChange` + - `onChangePage` has been renamed to `onPageChange` + - Migration guide is here: +- Updated dependencies + - @backstage/core-components@0.2.0 + - @backstage/core-plugin-api@0.1.4 + - @backstage/theme@0.2.9 + +## 0.1.5 + +### Patch Changes + +- ea249c6e6: Fix a bug in `FlatRoutes` that prevented outlets from working with the root route, as well as matching root routes too broadly. +- Updated dependencies + - @backstage/core-components@0.1.6 + +## 0.1.4 + +### Patch Changes + +- 62abffee4: Reintroduce export of `defaultConfigLoader`. +- Updated dependencies + - @backstage/core-components@0.1.4 + +## 0.1.3 + +### Patch Changes + +- dc3e7ce68: Introducing new UnhandledErrorForwarder installed by default. For catching unhandled promise rejections, you can override the API to align with general error handling. +- 5f4339b8c: Adding `FeatureFlag` component and treating `FeatureFlags` as first class citizens to composability API +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + +## 0.1.2 + +### Patch Changes + +- 9bca2a252: Fixes a type bug where supplying all app icons to `createApp` was required, rather than just a partial list. + +- 75b8537ce: This change adds automatic error boundaries around extensions. + + This means that all exposed parts of a plugin are wrapped in a general error boundary component, that is plugin aware. The default design for the error box is borrowed from `@backstage/errors`. To override the default "fallback", one must provide a component named `ErrorBoundaryFallback` to `createApp`, like so: + + ```ts + const app = createApp({ + components: { + ErrorBoundaryFallback: props => { + // a custom fallback component + return ( + <> +

    Oops.

    +

    + The plugin {props.plugin.getId()} failed with{' '} + {props.error.message} +

    + + + ); + }, + }, + }); + ``` + + The props here include: + + - `error`. An `Error` object or something that inherits it that represents the error that was thrown from any inner component. + - `resetError`. A callback that will simply attempt to mount the children of the error boundary again. + - `plugin`. A `BackstagePlugin` that can be used to look up info to be presented in the error message. For instance, you may want to keep a map of your internal plugins and team names or slack channels and present these when an error occurs. Typically, you'll do that by getting the plugin ID with `plugin.getId()`. + +- da8cba44f: Deprecate and disable the extension creation methods, which were added to this package by mistake and should only exist within `@backstage/core-plugin-api`. + +- 9bca2a252: Update `createApp` options to allow plugins with unknown output types in order to improve forwards and backwards compatibility. + +- Updated dependencies [e47336ea4] + +- Updated dependencies [75b8537ce] + +- Updated dependencies [da8cba44f] + - @backstage/core-components@0.1.2 + - @backstage/core-plugin-api@0.1.2 + +## 0.1.1 + +### Patch Changes + +- e7c5e4b30: Update installation instructions in README. +- Updated dependencies [031ccd45f] +- Updated dependencies [e7c5e4b30] + - @backstage/core-plugin-api@0.1.1 + - @backstage/core-components@0.1.1 + - @backstage/theme@0.2.8 + +## @backstage/core-plugin-api@1.12.3-next.0 + +# @backstage/core-plugin-api + +## 1.12.2-next.0 + +### Patch Changes + +- 53b6549: Plugins in the new frontend system now have a `pluginId` field rather than `id` to better align with naming conventions used throughout the frontend and backend systems. The old field is still present but marked as deprecated. All internal code has been updated to prefer `pluginId` while maintaining backward compatibility by falling back to `id` when needed. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## 1.12.1 + +### Patch Changes + +- 358c6f7: The `useApp` and `useRouteRef` functions are now forwards compatible with the new frontend system. Along with the previous route reference changes this means that there is no longer a need to use `compatWrapper` from `@backstage/core-compat-api` to make code based on `@backstage/core-plugin-api` compatible with `@backstage/frontend-plugin-api` APIs. +- 97cd16f: Reversed the relationship between the old `@backstage/core-plugin-api` and the new `@backstage/frontend-plugin-api`. Previously, the a lot of API definitions and utilities where defined in the old and re-exported from the old, but this change flips that around so that they now reside in the new package and are re-exported from the old. The external API of both packages remain the same, but this is a step towards being able to add further compatibility with the new frontend system built into the old. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + +## 1.12.1-next.0 + +### Patch Changes + +- 358c6f7: The `useApp` and `useRouteRef` functions are now forwards compatible with the new frontend system. Along with the previous route reference changes this means that there is no longer a need to use `compatWrapper` from `@backstage/core-compat-api` to make code based on `@backstage/core-plugin-api` compatible with `@backstage/frontend-plugin-api` APIs. +- 97cd16f: Reversed the relationship between the old `@backstage/core-plugin-api` and the new `@backstage/frontend-plugin-api`. Previously, the a lot of API definitions and utilities where defined in the old and re-exported from the old, but this change flips that around so that they now reside in the new package and are re-exported from the old. The external API of both packages remain the same, but this is a step towards being able to add further compatibility with the new frontend system built into the old. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## 1.12.0 + +### Minor Changes + +- 83439b1: All route references are now forwards compatible with the new frontend system, i.e. `@backstage/frontend-plugin-api`. This means they no longer need to be converted with `convertLegacyRouteRef` or `convertLegacyRouteRefs` from `@backstage/core-compat-api`. + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/config@1.3.6 + +## 1.11.2-next.1 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns + +## 1.11.2-next.0 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/config@1.3.6-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## 1.11.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.5 + +## 1.11.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + +## 1.11.0 + +### Minor Changes + +- 5114627: Make `openshiftAuthApiRef` available in `@backstage/core-plugin-api`. + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.2.2 + +## 1.10.9 + +### Patch Changes + +- f6ffea6: Add optional message field for auth providers. This is intended to be a user friendly message that displays in the OAuth request dialog. A default message will be displayed if one is not provided. +- Updated dependencies + - @backstage/config@1.3.3 + +## 1.10.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + +## 1.10.8 + +### Patch Changes + +- c83cd8b: Fixed some circular or otherwise unclear imports +- 0169b23: Internal tweak to avoid circular dependencies +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## 1.10.7 + +### Patch Changes + +- 73f6cc3: The `TranslationApi` now supports interpolation of JSX elements by passing them directly as values to the translation function. If any of the provided interpolation values are JSX elements, the translation function will return a JSX element instead of a string. +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## 1.10.7-next.0 + +### Patch Changes + +- 73f6cc3: The `TranslationApi` now supports interpolation of JSX elements by passing them directly as values to the translation function. If any of the provided interpolation values are JSX elements, the translation function will return a JSX element instead of a string. +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## 1.10.6 + +### Patch Changes + +- a47fd39: Removes instances of default React imports, a necessary update for the upcoming React 19 migration. + + + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## 1.10.6-next.0 + +### Patch Changes + +- a47fd39: Removes instances of default React imports, a necessary update for the upcoming React 19 migration. + + + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## 1.10.5 + +### Patch Changes + +- 327d21e: Failure to lazy load an extension will now always result in an error being thrown to be forwarded to error boundaries, rather than being rendered using the `BootErrorPage` app component. +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## 1.10.4 + +### Patch Changes + +- 58ec9e7: Removed older versions of React packages as a preparatory step for upgrading to React 19. This commit does not introduce any functional changes, but removes dependencies on previous React versions, allowing for a cleaner upgrade path in subsequent commits. +- Updated dependencies + - @backstage/version-bridge@1.0.11 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 1.10.4-next.0 + +### Patch Changes + +- 58ec9e7: Removed older versions of React packages as a preparatory step for upgrading to React 19. This commit does not introduce any functional changes, but removes dependencies on previous React versions, allowing for a cleaner upgrade path in subsequent commits. +- Updated dependencies + - @backstage/version-bridge@1.0.11-next.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 1.10.3 + +### Patch Changes + +- b40eb41: Move `Expand` and `ExpandRecursive` to `@backstage/types` +- Updated dependencies + - @backstage/types@1.2.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/version-bridge@1.0.10 + +## 1.10.3-next.0 + +### Patch Changes + +- b40eb41: Move `Expand` and `ExpandRecursive` to `@backstage/types` +- Updated dependencies + - @backstage/types@1.2.1-next.0 + - @backstage/config@1.3.2-next.0 + - @backstage/errors@1.2.7-next.0 + - @backstage/version-bridge@1.0.10 + +## 1.10.2 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.6 + - @backstage/config@1.3.1 + - @backstage/types@1.2.0 + - @backstage/version-bridge@1.0.10 + +## 1.10.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.6-next.0 + - @backstage/config@1.3.1-next.0 + - @backstage/types@1.2.0 + - @backstage/version-bridge@1.0.10 + +## 1.10.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.0 + - @backstage/types@1.2.0 + - @backstage/errors@1.2.5 + - @backstage/version-bridge@1.0.10 + +## 1.10.0 + +### Minor Changes + +- bfd4bec: **BREAKING PRODUCERS**: The `IconComponent` no longer accepts `fontSize="default"`. This has effectively been removed from Material-UI since its last two major versions, and has not worked properly for them in a long time. + + This change should not have an effect on neither users of MUI4 nor MUI5/6, since the updated interface should still let you send the respective `SvgIcon` types into interfaces where relevant (e.g. as app icons). + +### Patch Changes + +- e969dc7: Move `@types/react` to a peer dependency. +- 39001f4: Fixing issue with types for `ParamKeys` leading to type mismatches across versions +- Updated dependencies + - @backstage/version-bridge@1.0.10 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.10.0-next.1 + +### Patch Changes + +- e969dc7: Move `@types/react` to a peer dependency. +- Updated dependencies + - @backstage/version-bridge@1.0.10-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.10.0-next.0 + +### Minor Changes + +- bfd4bec: **BREAKING PRODUCERS**: The `IconComponent` no longer accepts `fontSize="default"`. This has effectively been removed from Material-UI since its last two major versions, and has not worked properly for them in a long time. + + This change should not have an effect on neither users of MUI4 nor MUI5/6, since the updated interface should still let you send the respective `SvgIcon` types into interfaces where relevant (e.g. as app icons). + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.9 + +## 1.9.4 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/version-bridge@1.0.9 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.9.4-next.0 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/version-bridge@1.0.9-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.9.3 + +### Patch Changes + +- 35fbe09: A new `defaultTarget` option has been added to `createExternalRouteRef`. This allows one to specify a default target of the route by name, for example `'catalog.catalogIndex'`, which will be used if the target route is present in the app and there is no explicit route binding. +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## 1.9.3-next.0 + +### Patch Changes + +- 35fbe09: Added a new `defaultTarget` option to `createExternalRouteRef`. I lets you specify a default target of the route by name, for example `'catalog.catalogIndex'`, which will be used if the target route is present in the app and there is no explicit route binding. +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## 1.9.2 + +### Patch Changes + +- abfbcfc: Updated dependency `@testing-library/react` to `^15.0.0`. +- cb1e3b0: Updated dependency `@testing-library/dom` to `^10.0.0`. +- Updated dependencies + - @backstage/version-bridge@1.0.8 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.9.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.9.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.2.0-next.1 + - @backstage/errors@1.2.4-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.9.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.4-next.0 + - @backstage/config@1.1.2-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.9.0 + +### Minor Changes + +- f919be9: Added a utility API for VMware Cloud auth; the API ref is available in the + `@backstage/core-plugin-api` and `@backstage/frontend-plugin-api` packages, the + implementation is in `@backstage/core-app-api` and a factory has been added to + `@backstage/app-defaults`. + +### Patch Changes + +- 8fe56a8: Widen `@types/react` dependency range to include version 18. +- e586f79: Throw a more specific exception `NotImplementedError` when an API implementation cannot be found. +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.9.0-next.1 + +### Minor Changes + +- f919be9: Added a utility API for VMware Cloud auth; the API ref is available in the + `@backstage/core-plugin-api` and `@backstage/frontend-plugin-api` packages, the + implementation is in `@backstage/core-app-api` and a factory has been added to + `@backstage/app-defaults`. + +### Patch Changes + +- 8fe56a8: Widen `@types/react` dependency range to include version 18. +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.8.3-next.0 + +### Patch Changes + +- e586f79: Throw a more specific exception `NotImplementedError` when an API implementation cannot be found. +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.8.2 + +### Patch Changes + +- 6878b1d: Removed unnecessary `i18next` dependency. +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.8.2-next.0 + +### Patch Changes + +- 6878b1d: Removed unnecessary `i18next` dependency. +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.8.1 + +### Patch Changes + +- 03d0b6d: Removed the alpha `convertLegacyRouteRef` utility, which as been moved to `@backstage/core-compat-api` +- 0c93dc3: The `createTranslationRef` function from the `/alpha` subpath can now also accept a nested object structure of default translation messages, which will be flatted using `.` separators. +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.8.1-next.1 + +### Patch Changes + +- 0c93dc37b2: The `createTranslationRef` function from the `/alpha` subpath can now also accept a nested object structure of default translation messages, which will be flatted using `.` separators. +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.8.1-next.0 + +### Patch Changes + +- 03d0b6dcdc: Removed the alpha `convertLegacyRouteRef` utility, which as been moved to `@backstage/core-compat-api` +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.8.0 + +### Minor Changes + +- 1e5b7d993a: `IconComponent` can now have a `fontSize` of `inherit`, which is useful for in-line icons. +- cb6db75bc2: Introduced `AnyRouteRefParams` as a replacement for `AnyParams`, which is now deprecated. + +### Patch Changes + +- 6c2b872153: Add official support for React 18. +- cb6db75bc2: Deprecated several types related to the routing system that are scheduled to be removed, as well as several fields on the route ref types themselves. +- 68fc9dc60e: Added a new `/alpha` export `convertLegacyRouteRef`, which is a temporary utility to allow existing route refs to be used with the new experimental packages. +- Updated dependencies + - @backstage/version-bridge@1.0.7 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + +## 1.8.0-next.0 + +### Minor Changes + +- 1e5b7d993a: `IconComponent` can now have a `fontSize` of `inherit`, which is useful for in-line icons. +- cb6db75bc2: Introduced `AnyRouteRefParams` as a replacement for `AnyParams`, which is now deprecated. + +### Patch Changes + +- 6c2b872153: Add official support for React 18. +- cb6db75bc2: Deprecated several types related to the routing system that are scheduled to be removed, as well as several fields on the route ref types themselves. +- 68fc9dc60e: Added a new `/alpha` export `convertLegacyRouteRef`, which is a temporary utility to allow existing route refs to be used with the new experimental packages. +- Updated dependencies + - @backstage/version-bridge@1.0.7-next.0 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + +## 1.7.0 + +### Minor Changes + +- 322bbcae24: Removed the exprimental plugin configuration API. The `__experimentalReconfigure()` from the plugin options as well as the `__experimentalConfigure()` method on plugin instances have both been removed. + +### Patch Changes + +- 0b55f773a7: Removed some unused dependencies +- 9a1fce352e: Updated dependency `@testing-library/jest-dom` to `^6.0.0`. +- f95af4e540: Updated dependency `@testing-library/dom` to `^9.0.0`. +- Updated dependencies + - @backstage/version-bridge@1.0.6 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + +## 1.7.0-next.1 + +### Patch Changes + +- 0b55f773a7: Removed some unused dependencies +- Updated dependencies + - @backstage/config@1.1.1-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.5 + +## 1.7.0-next.0 + +### Minor Changes + +- 322bbcae24: Removed the exprimental plugin configuration API. The `__experimentalReconfigure()` from the plugin options as well as the `__experimentalConfigure()` method on plugin instances have both been removed. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.5 + +## 1.6.0 + +### Minor Changes + +- 18619f793c94: Added the optional `expiresAt` field that may now be part of a `BackstageIdentityResponse`. +- 6e30769cc627: Introduced experimental support for internationalization. + +### Patch Changes + +- 406b786a2a2c: Mark package as being free of side effects, allowing more optimized Webpack builds. +- 8cec7664e146: Removed `@types/node` dependency +- Updated dependencies + - @backstage/config@1.1.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.5 + +## 1.6.0-next.3 + +### Patch Changes + +- 406b786a2a2c: Mark package as being free of side effects, allowing more optimized Webpack builds. +- Updated dependencies + - @backstage/config@1.1.0-next.2 + - @backstage/types@1.1.1-next.0 + - @backstage/version-bridge@1.0.5-next.0 + +## 1.6.0-next.2 + +### Minor Changes + +- 6e30769cc627: Introduced experimental support for internationalization. + +### Patch Changes + +- 8cec7664e146: Removed `@types/node` dependency +- Updated dependencies + - @backstage/config@1.1.0-next.1 + - @backstage/types@1.1.0 + - @backstage/version-bridge@1.0.4 + +## 1.6.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.0-next.0 + - @backstage/types@1.1.0 + - @backstage/version-bridge@1.0.4 + +## 1.6.0-next.0 + +### Minor Changes + +- 18619f793c94: Added the optional `expiresAt` field that may now be part of a `BackstageIdentityResponse`. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.8 + - @backstage/types@1.1.0 + - @backstage/version-bridge@1.0.4 + +## 1.5.3 + +### Patch Changes + +- 8174cf4c0edf: Fixing MUI / Material UI references +- 13426ebd1235: Change `IconComponent` type to be compatible with Material UI v5 icons. +- Updated dependencies + - @backstage/config@1.0.8 + - @backstage/types@1.1.0 + - @backstage/version-bridge@1.0.4 + +## 1.5.3-next.1 + +### Patch Changes + +- 8174cf4c0edf: Fixing MUI / Material UI references +- Updated dependencies + - @backstage/config@1.0.8 + - @backstage/types@1.1.0 + - @backstage/version-bridge@1.0.4 + +## 1.5.3-next.0 + +### Patch Changes + +- 13426ebd1235: Change `IconComponent` type to be compatible with Material UI v5 icons. +- Updated dependencies + - @backstage/config@1.0.8 + +## 1.5.2 + +### Patch Changes + +- 12adfbc8fe2d: Fixed a bug that prevented accurate plugin and route data from being applied to `navigate` analytics events when users visited pages constructed with ``, ``, and similar components that are used to gather one or more routable extensions under a given path. +- 74b216ee4e50: Add `PropsWithChildren` to usages of `ComponentType`, in preparation for React 18 where the children are no longer implicit. +- Updated dependencies + - @backstage/types@1.1.0 + - @backstage/config@1.0.8 + - @backstage/version-bridge@1.0.4 + +## 1.5.2-next.0 + +### Patch Changes + +- 74b216ee4e50: Add `PropsWithChildren` to usages of `ComponentType`, in preparation for React 18 where the children are no longer implicit. +- Updated dependencies + - @backstage/config@1.0.7 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.4 + +## 1.5.1 + +### Patch Changes + +- 760f521b979: Add component name as data attribute for all components +- 2898b6c8d52: Minor type tweaks for TypeScript 5.0 +- e0c6e8b9c3c: Update peer dependencies +- Updated dependencies + - @backstage/version-bridge@1.0.4 + - @backstage/config@1.0.7 + - @backstage/types@1.0.2 + +## 1.5.1-next.1 + +### Patch Changes + +- 2898b6c8d52: Minor type tweaks for TypeScript 5.0 +- Updated dependencies + - @backstage/config@1.0.7 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.4-next.0 + +## 1.5.1-next.0 + +### Patch Changes + +- e0c6e8b9c3c: Update peer dependencies +- Updated dependencies + - @backstage/version-bridge@1.0.4-next.0 + - @backstage/config@1.0.7 + - @backstage/types@1.0.2 + +## 1.5.0 + +### Minor Changes + +- ab750ddc4f2: The GitLab auth provider can now be used to get OpenID tokens. + +### Patch Changes + +- 928a12a9b3e: Internal refactor of `/alpha` exports. +- 52b0022dab7: Updated dependency `msw` to `^1.0.0`. +- Updated dependencies + - @backstage/config@1.0.7 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + +## 1.5.0-next.2 + +### Minor Changes + +- ab750ddc4f2: The GitLab auth provider can now be used to get OpenID tokens. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.7-next.0 + +## 1.4.1-next.1 + +### Patch Changes + +- 52b0022dab7: Updated dependency `msw` to `^1.0.0`. +- Updated dependencies + - @backstage/config@1.0.7-next.0 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + +## 1.4.1-next.0 + +### Patch Changes + +- 928a12a9b3: Internal refactor of `/alpha` exports. +- Updated dependencies + - @backstage/config@1.0.6 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + +## 1.4.0 + +### Minor Changes + +- db10b6ef65: Added a Bitbucket Server Auth Provider and added its API to the app defaults + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.6 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + +## 1.3.0 + +### Minor Changes + +- bca8e8b393: Allow defining application level feature flags. See [Feature Flags documentation](https://backstage.io/docs/plugins/feature-flags#in-the-application) for reference. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.6 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + +## 1.3.0-next.1 + +### Minor Changes + +- bca8e8b393: Allow defining application level feature flags. See [Feature Flags documentation](https://backstage.io/docs/plugins/feature-flags#in-the-application) for reference. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.6-next.0 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + +## 1.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.6-next.0 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + +## 1.2.0 + +### Minor Changes + +- 9a1864976a: Added a new `display` property to the `AlertMessage` which can accept the values `permanent` or `transient`. + + Here's a rough example of how to trigger an alert using the new `display` property: + + ```ts + import { alertApiRef, useApi } from '@backstage/core-plugin-api'; + + const ExampleTransient = () => { + const alertApi = useApi(alertApiRef); + alertApi.post({ + message: 'Example of Transient Alert', + severity: 'success', + display: 'transient', + }); + }; + ``` + +### Patch Changes + +- d56127c712: useRouteRef - Limit re-resolving to location pathname changes only +- 3280711113: Updated dependency `msw` to `^0.49.0`. +- 19356df560: Updated dependency `zen-observable` to `^0.9.0`. +- c3fa90e184: Updated dependency `zen-observable` to `^0.10.0`. +- Updated dependencies + - @backstage/version-bridge@1.0.3 + - @backstage/types@1.0.2 + - @backstage/config@1.0.5 + +## 1.2.0-next.2 + +### Minor Changes + +- 9a1864976a: Added a new `display` property to the `AlertMessage` which can accept the values `permanent` or `transient`. + + Here's a rough example of how to trigger an alert using the new `display` property: + + ```ts + import { alertApiRef, useApi } from '@backstage/core-plugin-api'; + + const ExampleTransient = () => { + const alertApi = useApi(alertApiRef); + alertApi.post({ + message: 'Example of Transient Alert', + severity: 'success', + display: 'transient', + }); + }; + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.5-next.1 + - @backstage/types@1.0.2-next.1 + - @backstage/version-bridge@1.0.3-next.0 + +## 1.1.1-next.1 + +### Patch Changes + +- c3fa90e184: Updated dependency `zen-observable` to `^0.10.0`. +- Updated dependencies + - @backstage/version-bridge@1.0.3-next.0 + - @backstage/types@1.0.2-next.1 + - @backstage/config@1.0.5-next.1 + +## 1.1.1-next.0 + +### Patch Changes + +- 3280711113: Updated dependency `msw` to `^0.49.0`. +- 19356df560: Updated dependency `zen-observable` to `^0.9.0`. +- Updated dependencies + - @backstage/types@1.0.2-next.0 + - @backstage/config@1.0.5-next.0 + - @backstage/version-bridge@1.0.2 + +## 1.1.0 + +### Minor Changes + +- a228f113d0: The app `Router` component now accepts an optional `basename` property. + +### Patch Changes + +- Updated dependencies + - @backstage/version-bridge@1.0.2 + - @backstage/types@1.0.1 + - @backstage/config@1.0.4 + +## 1.1.0-next.0 + +### Minor Changes + +- a228f113d0: The app `Router` component now accepts an optional `basename` property. + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/version-bridge@1.0.1 + +## 1.0.7 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## 1.0.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## 1.0.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.1 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## 1.0.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.0 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## 1.0.6 + +### Patch Changes + +- 817f3196f6: Updated React Router dependencies to be peer dependencies. +- 7d47def9c4: Removed dependency on `@types/jest`. +- 744fea158b: Added `getSystemIcons()` function to the `AppContext` available through `useApp` that will pull a list of all the icons that have been registered in the App. +- 667d917488: Updated dependency `msw` to `^0.47.0`. +- 87ec2ba4d6: Updated dependency `msw` to `^0.46.0`. +- bf5e9030eb: Updated dependency `msw` to `^0.45.0`. +- ef9ab322de: Minor API signatures cleanup +- Updated dependencies + - @backstage/config@1.0.2 + +## 1.0.6-next.3 + +### Patch Changes + +- 7d47def9c4: Removed dependency on `@types/jest`. +- Updated dependencies + - @backstage/config@1.0.2-next.0 + +## 1.0.6-next.2 + +### Patch Changes + +- 667d917488: Updated dependency `msw` to `^0.47.0`. +- 87ec2ba4d6: Updated dependency `msw` to `^0.46.0`. + +## 1.0.6-next.1 + +### Patch Changes + +- 817f3196f6: Updated React Router dependencies to be peer dependencies. + +## 1.0.6-next.0 + +### Patch Changes + +- 744fea158b: Added `getSystemIcons()` function to the `AppContext` available through `useApp` that will pull a list of all the icons that have been registered in the App. +- bf5e9030eb: Updated dependency `msw` to `^0.45.0`. +- ef9ab322de: Minor API signatures cleanup + +## 1.0.5 + +### Patch Changes + +- 80da5162c7: Introduced a new experimental feature that allows you to declare plugin-wide options for your plugin by defining + `__experimentalConfigure` in your `createPlugin` options. See for more information. + + This is an experimental feature and it will have breaking changes in the future. + +- 87649a06bf: Add a note that the `fetchApi` utility should not be used on sign-in page implementations and similar. + +## 1.0.5-next.0 + +### Patch Changes + +- 80da5162c7: Introduced a new experimental feature that allows you to declare plugin-wide options for your plugin by defining + `__experimentalConfigure` in your `createPlugin` options. See for more information. + + This is an experimental feature and it will have breaking changes in the future. + +## 1.0.4 + +### Patch Changes + +- 881fc75a75: Internal tweak removing usage of explicit type parameters for the `BackstagePlugin` type. +- a70869e775: Updated dependency `msw` to `^0.43.0`. +- 8006d0f9bf: Updated dependency `msw` to `^0.44.0`. +- 2990fff4e5: Enabled the `@backstage/core-plugin-api/alpha` entry point. + +## 1.0.4-next.0 + +### Patch Changes + +- 881fc75a75: Internal tweak removing usage of explicit type parameters for the `BackstagePlugin` type. +- a70869e775: Updated dependency `msw` to `^0.43.0`. +- 2990fff4e5: Enabled the `@backstage/core-plugin-api/alpha` entry point. + +## 1.0.3 + +### Patch Changes + +- 8f7b1835df: Updated dependency `msw` to `^0.41.0`. + +## 1.0.3-next.0 + +### Patch Changes + +- 8f7b1835df: Updated dependency `msw` to `^0.41.0`. + +## 1.0.2 + +### Patch Changes + +- b653a5595c: The authentication APIs are no longer `@alpha`. Since the `@backstage/core-plugin-api` has no `/alpha` entrypoint, the only effect of marking the APIs as `@alpha` was to hide them in documentation. They are still expected to be widely used and there will be a migration path if they are changed in the future. +- Updated dependencies + - @backstage/config@1.0.1 + +## 1.0.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.1-next.0 + +## 1.0.2-next.0 + +### Patch Changes + +- b653a5595c: The authentication APIs are no longer `@alpha`. Since the `@backstage/core-plugin-api` has no `/alpha` entrypoint, the only effect of marking the APIs as `@alpha` was to hide them in documentation. They are still expected to be widely used and there will be a migration path if they are changed in the future. + +## 1.0.1 + +### Patch Changes + +- 7c7919777e: build(deps-dev): bump `@testing-library/react-hooks` from 7.0.2 to 8.0.0 +- 24254fd433: build(deps): bump `@testing-library/user-event` from 13.5.0 to 14.0.0 +- 230ad0826f: Bump to using `@types/node` v16 +- Updated dependencies + - @backstage/version-bridge@1.0.1 + +## 1.0.1-next.0 + +### Patch Changes + +- 24254fd433: build(deps): bump `@testing-library/user-event` from 13.5.0 to 14.0.0 +- 230ad0826f: Bump to using `@types/node` v16 + +## 1.0.0 + +### Major Changes + +- b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/version-bridge@1.0.0 + - @backstage/config@1.0.0 + - @backstage/types@1.0.0 + +## 0.8.0 + +### Minor Changes + +- bb2bb36651: **BREAKING**: Removed the deprecated `get` method from `StorageAPI` and its implementations, this method has been replaced by the `snapshot` method. The return value from snapshot no longer includes `newValue` which has been replaced by `value`. For getting notified when a value changes, use \`observe# @backstage/core-plugin-api. +- af5eaa87f4: **BREAKING**: Removed deprecated `auth0AuthApiRef`, `oauth2ApiRef`, `samlAuthApiRef` and `oidcAuthApiRef` as these APIs are too generic to be useful. Instructions for how to migrate can be found at . +- a480f670c7: **BREAKING**: OAuth provider id is now required when passing a provider to `createAuthRequester`. + +## 0.7.0 + +### Minor Changes + +- 33cd215b11: **BREAKING**: Removed deprecated `AnyAnalyticsContext` type which is replaced by `AnalyticsContextValue` + +## 0.6.1 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 2714145cf5: Removes unused react-use dependency. +- Updated dependencies + - @backstage/config@0.1.14 + - @backstage/types@0.1.2 + - @backstage/version-bridge@0.1.2 + +## 0.6.0 + +### Minor Changes + +- ceebe25391: Removed deprecated `IdentityApi` methods: `getUserId`, `getIdToken`, and `getProfile`. + + Existing usage of `getUserId` can be replaced by `getBackstageIdentity`, more precisely the equivalent of the previous `userId` can be retrieved like this: + + ```ts + import { parseEntityRef } from '@backstage/catalog-model'; + + const identity = await identityApi.getBackstageIdentity(); + const { name: userId } = parseEntityRef(identity.userEntityRef); + ``` + + Note that it is recommended to consume the entire `userEntityRef` rather than parsing out just the name, in order to support namespaces. + + Existing usage of `getIdToken` can be replaced by `getCredentials`, like this: + + ```ts + const { token } = await identityApi.getCredentials(); + ``` + + And existing usage of `getProfile` is replaced by `getProfileInfo`, which returns the same profile object, but is now async. + +- ceebe25391: Removed deprecated `SignInResult` type, which was replaced with the new `onSignInSuccess` callback. + +- d879072b0c: Removed the deprecated `id` field of `BackstageIdentityResponse`. + + Existing usage can be replaced by parsing the `name` of the `identity.userEntityRef` with `parseEntityRef` from `@backstage/catalog-model`, although note that it is recommended to consume the entire `userEntityRef` in order to support namespaces. + +- 94c02b4246: Removed deprecated `BackstageIdentity` type, which was replaced by `BackstageIdentityResponse`. + +- 234a36405b: Removed deprecated `OAuthRequestApi` types: `AuthProvider`, `AuthRequesterOptions`, `AuthRequester`, and `PendingAuthRequest`. + +### Patch Changes + +- Updated dependencies + - @backstage/config@0.1.13 + +## 0.6.0-next.0 + +### Minor Changes + +- ceebe25391: Removed deprecated `IdentityApi` methods: `getUserId`, `getIdToken`, and `getProfile`. + + Existing usage of `getUserId` can be replaced by `getBackstageIdentity`, more precisely the equivalent of the previous `userId` can be retrieved like this: + + ```ts + import { parseEntityRef } from '@backstage/catalog-model'; + + const identity = await identityApi.getBackstageIdentity(); + const { name: userId } = parseEntityRef(identity.userEntityRef); + ``` + + Note that it is recommended to consume the entire `userEntityRef` rather than parsing out just the name, in order to support namespaces. + + Existing usage of `getIdToken` can be replaced by `getCredentials`, like this: + + ```ts + const { token } = await identityApi.getCredentials(); + ``` + + And existing usage of `getProfile` is replaced by `getProfileInfo`, which returns the same profile object, but is now async. + +- ceebe25391: Removed deprecated `SignInResult` type, which was replaced with the new `onSignInSuccess` callback. + +- d879072b0c: Removed the deprecated `id` field of `BackstageIdentityResponse`. + + Existing usage can be replaced by parsing the `name` of the `identity.userEntityRef` with `parseEntityRef` from `@backstage/catalog-model`, although note that it is recommended to consume the entire `userEntityRef` in order to support namespaces. + +- 94c02b4246: Removed deprecated `BackstageIdentity` type, which was replaced by `BackstageIdentityResponse`. + +- 234a36405b: Removed deprecated `OAuthRequestApi` types: `AuthProvider`, `AuthRequesterOptions`, `AuthRequester`, and `PendingAuthRequest`. + +### Patch Changes + +- Updated dependencies + - @backstage/config@0.1.13-next.0 + +## 0.5.0 + +### Minor Changes + +- 784d8078ab: Removed the deprecated `OldIconComponent` type. +- e2eb92c109: Removed previously deprecated exports: `PluginHooks`, `PluginOutput`, and `FeatureFlagOutput`. + + The deprecated `register` method of `PluginConfig` has been removed, as well as the deprecated `output` method of `BackstagePlugin`. + +### Patch Changes + +- 784d8078ab: Removed direct and transitive Material UI dependencies. +- Updated dependencies + - @backstage/config@0.1.12 + +## 0.4.1 + +### Patch Changes + +- c534ef2242: Deprecated `OldIconComponent`. Existing usage should be replaced with `IconComponent`. + +## 0.4.0 + +### Minor Changes + +- a195284c7b: **BREAKING CHANGE** The `StorageApi` has received several updates that fills in gaps for some use-cases and makes it easier to avoid mistakes: + + - The `StorageValueChange` type has been renamed to `StorageValueSnapshot`, the `newValue` property has been renamed to `value`, the stored value type has been narrowed to `JsonValue`, and it has received a new `presence` property that is `'unknown'`, `'absent'`, or `'present'`. + - The `get` method has been deprecated in favor of a new `snapshot` method, which returns a `StorageValueSnapshot`. + - The `observe# @backstage/core-plugin-api method has had its contract changed. It should now emit values when the`presence`of a key changes, this may for example happen when remotely stored values are requested on page load and the presence switches from`'unknown'`to either`'absent'`or`'present'\`. + + The above changes have been made with deprecations in place to maintain much of the backwards compatibility for consumers of the `StorageApi`. The only breaking change is the narrowing of the stored value type, which may in some cases require the addition of an explicit type parameter to the `get` and \`observe# @backstage/core-plugin-api methods. + +- f6722d2458: - Removed deprecated option `description` from `ApiRefConfig` + + - Removed descriptions from all plugin API refs + - Removed deprecated parameters `path`, `icon`, and `title` in `createRouteRef` + - Removed deprecated types `Error` and `ErrorContext` from `ErrorApi` + +- 68f8b10ccd: - Removed deprecation configuration option `theme` from `AppTheme` of the `AppThemeApi` + + - Removed reference to `theme` in the `app-defaults` default `AppTheme` + - Removed logic in `AppThemeProvider` that creates `ThemeProvider` from `appTheme.theme` + +- 6b69b44862: Removed deprecated types `ApiRefType` and `ApiRefsToTypes` + +### Patch Changes + +- 7927005152: Add `FetchApi` and related `fetchApiRef` which implement fetch, with an added Backstage token header when available. + +## 0.3.1 + +### Patch Changes + +- 18d4f500af: Deprecated the `AnyAnalyticsContext` type and mark the `AnalyticsApi` experimental. +- 8a7372cfd5: Deprecated `auth0AuthApiRef`, `oauth2ApiRef`, `oidcAuthApiRef`, `samlAuthApiRef`, and marked the rest of the auth `ApiRef`s as experimental. For more information on how to address the deprecations, see . +- 760791a642: Renamed `AuthProvider` to `AuthProviderInfo` and add a required 'id' property to match the majority of usage. The `AuthProvider` type without the `id` property still exists but is deprecated, and all usage of it without an `id` is deprecated as well. For example, calling `createAuthRequest` without a `provider.id` is deprecated and it will be required in the future. + + The following types have been renamed. The old names are still exported but deprecated, and are scheduled for removal in a future release. + + - Renamed `AuthRequesterOptions` to `OAuthRequesterOptions` + - Renamed `AuthRequester` to `OAuthRequester` + - Renamed `PendingAuthRequest` to `PendingOAuthRequest` + +## 0.3.0 + +### Minor Changes + +- a036b65c2f: The `IdentityApi` has received several updates. The `getUserId`, `getProfile`, and `getIdToken` have all been deprecated. + + The replacement for `getUserId` is the new `getBackstageIdentity` method, which provides both the `userEntityRef` as well as the `ownershipEntityRefs` that are used to resolve ownership. Existing usage of the user ID would typically be using a fixed entity kind and namespace, for example `` `user:default/${identityApi.getUserId()}` ``, this kind of usage should now instead use the `userEntityRef` directly. + + The replacement for `getProfile` is the new async `getProfileInfo`. + + The replacement for `getIdToken` is the new `getCredentials` method, which provides an optional token to the caller like before, but it is now wrapped in an object for forwards compatibility. + + The deprecated `idToken` field of the `BackstageIdentity` type has been removed, leaving only the new `token` field, which should be used instead. The `BackstageIdentity` also received a new `identity` field, which is a decoded version of the information within the token. Furthermore the `BackstageIdentity` has been renamed to `BackstageIdentityResponse`, with the old name being deprecated. + + We expect most of the breaking changes in this update to have low impact since the `IdentityApi` implementation is provided by the app, but it is likely that some tests need to be updated. + + Another breaking change is that the `SignInPage` props have been updated, and the `SignInResult` type is now deprecated. This is unlikely to have any impact on the usage of this package, but it is an important change that you can find more information about in the [`@backstage/core-app-api` CHANGELOG.md](https://github.com/backstage/backstage/blob/master/packages/core-app-api/CHANGELOG.md). + +### Patch Changes + +- cd450844f6: Moved React dependencies to `peerDependencies` and allow both React v16 and v17 to be used. +- dcd1a0c3f4: Minor improvement to the API reports, by not unpacking arguments directly +- Updated dependencies + - @backstage/version-bridge@0.1.1 + +## 0.2.2 + +### Patch Changes + +- b291d0ed7e: Tweaked the logged deprecation warning for `createRouteRef` to hopefully make it more clear. +- bacb94ea8f: Documented the options of each of the extension creation functions. +- Updated dependencies + - @backstage/theme@0.2.14 + +## 0.2.1 + +### Patch Changes + +- 950b36393c: Deprecated `register` option of `createPlugin` and the `outputs` methods of the plugin instance. + + Introduces the `featureFlags` property to define your feature flags instead. + +## 0.2.0 + +### Minor Changes + +- 7e18ed7f29: Removed the unused `UserFlags` type. +- 7df99cdb77: Remove exports of unused types(`RouteOptions` and `RoutePath`). + +### Patch Changes + +- 37ebea2d68: Add deprecation warnings around `title` `icon` and `path` as they are no longer controlled when creating `routeRefs` +- 2dd2a7b2cc: Deprecated the `theme` property on `AppTheme`, replacing it with `Provider`. See for more details. +- b6a4bacdc4: Deprecated the `Error` and `ErrorContext` types, replacing them with identical `ErrorApiError` and `ErrorApiErrorContext` types. + +## 0.1.13 + +### Patch Changes + +- 4a336fd292: Deprecate use of extensions without name. Adds a warning to the developer console to prompt integrators to provide names for extensions. +- 8b4284cd5c: Improve API documentation for @backstage/core-plugin-api +- e059aea7b9: Deprecate unused ApiRef types +- Updated dependencies + - @backstage/theme@0.2.13 + +## 0.1.12 + +### Patch Changes + +- 41c49884d2: Start using the new `@backstage/types` package. Initially, this means using the `Observable` and `Json*` types from there. The types also remain in their old places but deprecated, and will be removed in a future release. +- 925a967f36: Replace usage of test-utils-core with test-utils +- Updated dependencies + - @backstage/config@0.1.11 + - @backstage/theme@0.2.12 + +## 0.1.11 + +### Patch Changes + +- 202f322927: Atlassian auth provider + + - AtlassianAuth added to core-app-api + - Atlassian provider added to plugin-auth-backend + - Updated user-settings with Atlassian connection + +- 36e67d2f24: Internal updates to apply more strict checks to throw errors. + +## 0.1.10 + +### Patch Changes + +- 829bc698f4: Introducing the Analytics API: a lightweight way for plugins to instrument key + events that could help inform a Backstage Integrator how their instance of + Backstage is being used. The API consists of the following: + + - `useAnalytics()`, a hook to be used inside plugin components which retrieves + an Analytics Tracker. + - `tracker.captureEvent()`, a method on the tracker used to instrument key + events. The method expects an action (the event name) and a subject (a unique + identifier of the object the action is being taken on). + - ``, a way to declaratively attach additional information + to any/all events captured in the underlying React tree. There is also a + `withAnalyticsContext()` HOC utility. + - The `tracker.captureEvent()` method also accepts an `attributes` option for + providing additional run-time information about an event, as well as a + `value` option for capturing a numeric/metric value. + + By default, captured events are not sent anywhere. In order to collect and + redirect events to an analytics system, the `analyticsApi` will need to be + implemented and instantiated by an App Integrator. + +- 4c3eea7788: Bitbucket Cloud authentication - based on the existing GitHub authentication + changes around BB apis and updated scope. + + - BitbucketAuth added to core-app-api. + - Bitbucket provider added to plugin-auth-backend. + - Cosmetic entry for Bitbucket connection in user-settings Authentication Providers tab. + +## 0.1.9 + +### Patch Changes + +- 98bd661240: Improve compatibility between different versions by defining the route reference type using a string key rather than a unique symbol. This change only applies to type checking and has no effect on the runtime value, where we still use the symbol. + +## 0.1.8 + +### Patch Changes + +- 671015f132: Switch to using utilities from \`@backstage/version-bridge'. + +## 0.1.7 + +### Patch Changes + +- 3d238b028: Migrated component data attachment method to have better compatibility with component proxies such as `react-hot-loader`. +- Updated dependencies + - @backstage/config@0.1.9 + +## 0.1.6 + +### Patch Changes + +- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version. + +## 0.1.5 + +### Patch Changes + +- c4d8ff963: Switched frontend identity code to use `token` instead of the deprecated `idToken` field +- Updated dependencies + - @backstage/config@0.1.6 + +## 0.1.4 + +### Patch Changes + +- 9d40fcb1e: - Bumping `material-ui/core` version to at least `4.12.2` as they made some breaking changes in later versions which broke `Pagination` of the `Table`. + - Switching out `material-table` to `@material-table/core` for support for the later versions of `material-ui/core` + - This causes a minor API change to `@backstage/core-components` as the interface for `Table` re-exports the `prop` from the underlying `Table` components. + - `onChangeRowsPerPage` has been renamed to `onRowsPerPageChange` + - `onChangePage` has been renamed to `onPageChange` + - Migration guide is here: +- Updated dependencies + - @backstage/theme@0.2.9 + +## 0.1.3 + +### Patch Changes + +- 5f4339b8c: Adding `FeatureFlag` component and treating `FeatureFlags` as first class citizens to composability API + +## 0.1.2 + +### Patch Changes + +- 75b8537ce: This change adds automatic error boundaries around extensions. + + This means that all exposed parts of a plugin are wrapped in a general error boundary component, that is plugin aware. The default design for the error box is borrowed from `@backstage/errors`. To override the default "fallback", one must provide a component named `ErrorBoundaryFallback` to `createApp`, like so: + + ```ts + const app = createApp({ + components: { + ErrorBoundaryFallback: props => { + // a custom fallback component + return ( + <> +

    Oops.

    +

    + The plugin {props.plugin.getId()} failed with{' '} + {props.error.message} +

    + + + ); + }, + }, + }); + ``` + + The props here include: + + - `error`. An `Error` object or something that inherits it that represents the error that was thrown from any inner component. + - `resetError`. A callback that will simply attempt to mount the children of the error boundary again. + - `plugin`. A `BackstagePlugin` that can be used to look up info to be presented in the error message. For instance, you may want to keep a map of your internal plugins and team names or slack channels and present these when an error occurs. Typically, you'll do that by getting the plugin ID with `plugin.getId()`. + +- da8cba44f: Apply fixes to the extension creation API that were mistakenly applied to `@backstage/core-app-api` instead. + +## 0.1.1 + +### Patch Changes + +- 031ccd45f: Made the deprecated `icon` fields compatible with the `IconComponent` type from `@backstage/core` in order to smooth out the migration. +- e7c5e4b30: Update installation instructions in README. +- Updated dependencies [e7c5e4b30] + - @backstage/theme@0.2.8 + +## @backstage/plugin-search-backend@2.0.12-next.0 + +# @backstage/plugin-search-backend + +## 2.0.11-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.6-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-search-backend-node@1.4.1-next.0 + - @backstage/backend-defaults@0.15.1-next.0 + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/plugin-permission-node@0.10.9-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 2.0.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0 + - @backstage/backend-plugin-api@1.6.1 + - @backstage/backend-openapi-utils@0.6.5 + - @backstage/plugin-permission-common@0.9.4 + - @backstage/plugin-permission-node@0.10.8 + +## 2.0.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/plugin-search-backend-node@1.4.0 + +## 2.0.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.5-next.0 + - @backstage/backend-defaults@0.14.1-next.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/plugin-search-common@1.2.21 + +## 2.0.9 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/backend-defaults@0.14.0 + - @backstage/backend-openapi-utils@0.6.4 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + +## 2.0.9-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/backend-openapi-utils@0.6.4-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-backend-node@1.4.0-next.1 + - @backstage/plugin-search-common@1.2.21 + +## 2.0.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0-next.0 + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/backend-openapi-utils@0.6.4-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-common@1.2.21 + +## 2.0.8 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-backend-node@1.3.17 + - @backstage/config@1.3.6 + - @backstage/backend-openapi-utils@0.6.3 + - @backstage/plugin-permission-node@0.10.6 + - @backstage/plugin-search-common@1.2.21 + +## 2.0.8-next.1 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/backend-defaults@0.13.1-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-permission-common@0.9.3-next.1 + - @backstage/backend-openapi-utils@0.6.3-next.1 + - @backstage/plugin-permission-node@0.10.6-next.1 + - @backstage/plugin-search-backend-node@1.3.17-next.1 + +## 2.0.8-next.0 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-defaults@0.13.1-next.0 + - @backstage/plugin-search-backend-node@1.3.17-next.0 + - @backstage/config@1.3.6-next.0 + - @backstage/plugin-permission-node@0.10.6-next.0 + - @backstage/backend-openapi-utils@0.6.3-next.0 + - @backstage/backend-plugin-api@1.4.5-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3-next.0 + - @backstage/plugin-search-common@1.2.21-next.0 + +## 2.0.7 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.0 + - @backstage/config@1.3.5 + - @backstage/backend-openapi-utils@0.6.2 + - @backstage/backend-plugin-api@1.4.4 + - @backstage/plugin-permission-common@0.9.2 + - @backstage/plugin-permission-node@0.10.5 + - @backstage/plugin-search-backend-node@1.3.16 + - @backstage/plugin-search-common@1.2.20 + +## 2.0.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-defaults@0.13.0-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-permission-node@0.10.5-next.0 + - @backstage/plugin-search-backend-node@1.3.16-next.0 + - @backstage/backend-openapi-utils@0.6.2-next.0 + - @backstage/plugin-search-common@1.2.20-next.0 + +## 2.0.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.0-next.0 + - @backstage/plugin-permission-node@0.10.4 + - @backstage/plugin-search-backend-node@1.3.15 + - @backstage/backend-openapi-utils@0.6.1 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-search-common@1.2.19 + +## 2.0.6 + +### Patch Changes + +- 0efcc97: Updated generated schemas +- Updated dependencies + - @backstage/backend-defaults@0.12.1 + - @backstage/types@1.2.2 + - @backstage/backend-openapi-utils@0.6.1 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/plugin-permission-node@0.10.4 + - @backstage/plugin-search-backend-node@1.3.15 + +## 2.0.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.12.1-next.0 + - @backstage/backend-plugin-api@1.4.3-next.0 + - @backstage/plugin-permission-node@0.10.4-next.0 + - @backstage/plugin-search-backend-node@1.3.15-next.0 + - @backstage/backend-openapi-utils@0.6.1-next.0 + +## 2.0.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.0 + - @backstage/backend-defaults@0.12.0 + - @backstage/plugin-permission-node@0.10.3 + - @backstage/plugin-search-backend-node@1.3.14 + - @backstage/backend-plugin-api@1.4.2 + +## 2.0.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/backend-openapi-utils@0.6.0-next.0 + - @backstage/plugin-permission-node@0.10.3-next.0 + - @backstage/plugin-search-backend-node@1.3.14-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-search-common@1.2.19 + +## 2.0.4 + +### Patch Changes + +- 69fb975: Error messages should not contain backend SQL query strings in the API response, this change will ensure that messages are logged and empty response is returned to the user +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-permission-node@0.10.2 + - @backstage/backend-defaults@0.11.1 + - @backstage/backend-openapi-utils@0.5.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-search-backend-node@1.3.13 + - @backstage/plugin-search-common@1.2.19 + +## 2.0.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/plugin-permission-node@0.10.2-next.0 + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-search-backend-node@1.3.13-next.0 + - @backstage/plugin-search-common@1.2.19-next.0 + - @backstage/backend-openapi-utils@0.5.5-next.0 + +## 2.0.4-next.0 + +### Patch Changes + +- 69fb975: Error messages should not contain backend SQL query strings in the API response, this change will ensure that messages are logged and empty response is returned to the user +- Updated dependencies + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/plugin-permission-node@0.10.1 + - @backstage/plugin-search-backend-node@1.3.12 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/backend-openapi-utils@0.5.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-search-common@1.2.18 + +## 2.0.3 + +### Patch Changes + +- c83cd8b: Fixed some circular or otherwise unclear imports +- Updated dependencies + - @backstage/backend-defaults@0.11.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/backend-openapi-utils@0.5.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1 + - @backstage/plugin-search-backend-node@1.3.12 + - @backstage/plugin-search-common@1.2.18 + +## 2.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.0-next.2 + - @backstage/backend-openapi-utils@0.5.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1-next.1 + - @backstage/plugin-search-backend-node@1.3.12-next.1 + - @backstage/plugin-search-common@1.2.18 + +## 2.0.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.1-next.1 + - @backstage/backend-openapi-utils@0.5.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1-next.1 + - @backstage/plugin-search-backend-node@1.3.12-next.1 + - @backstage/plugin-search-common@1.2.18 + +## 2.0.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/backend-defaults@0.10.1-next.0 + - @backstage/plugin-permission-node@0.10.1-next.0 + - @backstage/plugin-search-backend-node@1.3.12-next.0 + - @backstage/backend-openapi-utils@0.5.4-next.0 + +## 2.0.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.0 + - @backstage/backend-openapi-utils@0.5.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-search-backend-node@1.3.11 + - @backstage/plugin-search-common@1.2.18 + +## 2.0.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.3 + - @backstage/backend-openapi-utils@0.5.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-permission-node@0.10.0-next.2 + - @backstage/plugin-search-backend-node@1.3.11-next.2 + - @backstage/plugin-search-common@1.2.18-next.0 + +## 2.0.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/backend-openapi-utils@0.5.3-next.1 + - @backstage/config@1.3.2 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/plugin-search-backend-node@1.3.11-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + +## 2.0.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/plugin-search-backend-node@1.3.11-next.1 + - @backstage/backend-openapi-utils@0.5.3-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-search-common@1.2.18-next.0 + +## 2.0.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.9.1-next.0 + - @backstage/backend-plugin-api@1.3.1-next.0 + - @backstage/plugin-permission-node@0.9.2-next.0 + - @backstage/plugin-search-backend-node@1.3.11-next.0 + - @backstage/backend-openapi-utils@0.5.3-next.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.8.4 + - @backstage/plugin-search-common@1.2.17 + +## 2.0.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.9.0 + - @backstage/backend-plugin-api@1.3.0 + - @backstage/plugin-permission-node@0.9.1 + - @backstage/plugin-search-backend-node@1.3.10 + - @backstage/backend-openapi-utils@0.5.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.8.4 + - @backstage/plugin-search-common@1.2.17 + +## 2.0.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.9.0-next.2 + - @backstage/backend-openapi-utils@0.5.1 + - @backstage/backend-plugin-api@1.2.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.8.4 + - @backstage/plugin-permission-node@0.9.0 + - @backstage/plugin-search-backend-node@1.3.9 + - @backstage/plugin-search-common@1.2.17 + +## 2.0.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.9.0-next.1 + - @backstage/plugin-permission-node@0.9.0 + - @backstage/plugin-search-backend-node@1.3.9 + - @backstage/backend-openapi-utils@0.5.1 + - @backstage/backend-plugin-api@1.2.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.8.4 + - @backstage/plugin-search-common@1.2.17 + +## 2.0.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.9.0-next.0 + - @backstage/plugin-permission-node@0.9.0 + - @backstage/plugin-search-backend-node@1.3.9 + - @backstage/backend-openapi-utils@0.5.1 + - @backstage/backend-plugin-api@1.2.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.8.4 + - @backstage/plugin-search-common@1.2.17 + +## 2.0.0 + +### Major Changes + +- d5c4a9d: **BREAKING** Removed support for the legacy backend system and references to `@backstage/backend-common`, please migrate to the new backend system. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.8.2 + - @backstage/plugin-permission-node@0.9.0 + - @backstage/backend-openapi-utils@0.5.1 + - @backstage/backend-plugin-api@1.2.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.8.4 + - @backstage/plugin-search-backend-node@1.3.9 + - @backstage/plugin-search-common@1.2.17 + +## 1.8.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.8.2-next.2 + - @backstage/backend-openapi-utils@0.5.1-next.1 + - @backstage/backend-plugin-api@1.2.1-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.8.4 + - @backstage/plugin-permission-node@0.8.9-next.1 + - @backstage/plugin-search-backend-node@1.3.9-next.1 + - @backstage/plugin-search-common@1.2.17 + +## 1.8.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.8.2-next.1 + - @backstage/backend-openapi-utils@0.5.1-next.1 + - @backstage/backend-plugin-api@1.2.1-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.8.4 + - @backstage/plugin-permission-node@0.8.9-next.1 + - @backstage/plugin-search-backend-node@1.3.9-next.1 + - @backstage/plugin-search-common@1.2.17 + +## 1.8.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.8.2-next.0 + - @backstage/plugin-permission-node@0.8.9-next.0 + - @backstage/plugin-search-backend-node@1.3.9-next.0 + - @backstage/backend-plugin-api@1.2.1-next.0 + - @backstage/backend-openapi-utils@0.5.1-next.0 + +## 1.8.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-node@0.8.8 + - @backstage/backend-defaults@0.8.0 + - @backstage/backend-plugin-api@1.2.0 + - @backstage/plugin-search-backend-node@1.3.8 + - @backstage/backend-openapi-utils@0.5.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.8.4 + - @backstage/plugin-search-common@1.2.17 + +## 1.8.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-node@0.8.8-next.2 + - @backstage/backend-defaults@0.8.0-next.3 + - @backstage/backend-plugin-api@1.2.0-next.2 + - @backstage/plugin-search-backend-node@1.3.8-next.2 + - @backstage/backend-openapi-utils@0.5.0-next.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.8.4 + - @backstage/plugin-search-common@1.2.17 + +## 1.8.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.1 + - @backstage/plugin-search-backend-node@1.3.8-next.1 + - @backstage/backend-defaults@0.8.0-next.2 + - @backstage/backend-openapi-utils@0.5.0-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.8.4 + - @backstage/plugin-permission-node@0.8.8-next.1 + - @backstage/plugin-search-common@1.2.17 + +## 1.8.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.5.0-next.1 + - @backstage/backend-defaults@0.8.0-next.1 + - @backstage/backend-plugin-api@1.2.0-next.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.8.4 + - @backstage/plugin-permission-node@0.8.8-next.0 + - @backstage/plugin-search-backend-node@1.3.8-next.0 + - @backstage/plugin-search-common@1.2.17 + +## 1.8.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-node@0.8.8-next.0 + - @backstage/backend-defaults@0.8.0-next.0 + - @backstage/backend-plugin-api@1.2.0-next.0 + - @backstage/backend-openapi-utils@0.4.2-next.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.8.4 + - @backstage/plugin-search-backend-node@1.3.8-next.0 + - @backstage/plugin-search-common@1.2.17 + +## 1.8.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.7.0 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-node@0.8.7 + - @backstage/backend-openapi-utils@0.4.1 + - @backstage/backend-plugin-api@1.1.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.8.4 + - @backstage/plugin-search-backend-node@1.3.7 + - @backstage/plugin-search-common@1.2.17 + +## 1.8.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.2.1-next.0 + - @backstage/backend-defaults@0.7.0-next.1 + - @backstage/backend-openapi-utils@0.4.1-next.1 + - @backstage/backend-plugin-api@1.1.1-next.1 + - @backstage/config@1.3.2-next.0 + - @backstage/errors@1.2.7-next.0 + - @backstage/plugin-permission-common@0.8.4-next.0 + - @backstage/plugin-search-common@1.2.17-next.0 + - @backstage/plugin-permission-node@0.8.7-next.1 + - @backstage/plugin-search-backend-node@1.3.7-next.1 + +## 1.8.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.7.0-next.0 + - @backstage/plugin-permission-node@0.8.7-next.0 + - @backstage/backend-openapi-utils@0.4.1-next.0 + - @backstage/backend-plugin-api@1.1.1-next.0 + - @backstage/config@1.3.1 + - @backstage/errors@1.2.6 + - @backstage/types@1.2.0 + - @backstage/plugin-permission-common@0.8.3 + - @backstage/plugin-search-backend-node@1.3.7-next.0 + - @backstage/plugin-search-common@1.2.16 + +## 1.8.0 + +### Minor Changes + +- 384e494: Internal updates to generated code. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.6.0 + - @backstage/backend-plugin-api@1.1.0 + - @backstage/plugin-permission-node@0.8.6 + - @backstage/backend-openapi-utils@0.4.0 + - @backstage/plugin-search-backend-node@1.3.6 + - @backstage/errors@1.2.6 + - @backstage/config@1.3.1 + - @backstage/types@1.2.0 + - @backstage/plugin-permission-common@0.8.3 + - @backstage/plugin-search-common@1.2.16 + +## 1.8.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.6.0-next.2 + - @backstage/backend-plugin-api@1.1.0-next.2 + - @backstage/plugin-permission-node@0.8.6-next.2 + - @backstage/backend-openapi-utils@0.4.0-next.2 + - @backstage/errors@1.2.6-next.0 + - @backstage/plugin-search-backend-node@1.3.6-next.2 + - @backstage/config@1.3.1-next.0 + - @backstage/types@1.2.0 + - @backstage/plugin-permission-common@0.8.3-next.0 + - @backstage/plugin-search-common@1.2.16-next.0 + +## 1.8.0-next.1 + +### Minor Changes + +- 384e494: Internal updates to generated code. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.6.0-next.1 + - @backstage/plugin-search-backend-node@1.3.6-next.1 + - @backstage/backend-plugin-api@1.1.0-next.1 + - @backstage/plugin-permission-node@0.8.6-next.1 + - @backstage/backend-openapi-utils@0.3.1-next.1 + - @backstage/config@1.3.0 + - @backstage/errors@1.2.5 + - @backstage/types@1.2.0 + - @backstage/plugin-permission-common@0.8.2 + - @backstage/plugin-search-common@1.2.15 + +## 1.7.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.6.0-next.0 + - @backstage/backend-plugin-api@1.0.3-next.0 + - @backstage/backend-openapi-utils@0.3.1-next.0 + - @backstage/config@1.3.0 + - @backstage/errors@1.2.5 + - @backstage/types@1.2.0 + - @backstage/plugin-permission-common@0.8.2 + - @backstage/plugin-permission-node@0.8.6-next.0 + - @backstage/plugin-search-backend-node@1.3.6-next.0 + - @backstage/plugin-search-common@1.2.15 + +## 1.7.0 + +### Minor Changes + +- 39fd704: Internal update to use the new generated server types from `backstage-cli package schema openapi generate --server`. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.0 + - @backstage/backend-defaults@0.5.3 + - @backstage/types@1.2.0 + - @backstage/backend-plugin-api@1.0.2 + - @backstage/backend-openapi-utils@0.3.0 + - @backstage/plugin-search-backend-node@1.3.5 + - @backstage/plugin-permission-common@0.8.2 + - @backstage/errors@1.2.5 + - @backstage/plugin-permission-node@0.8.5 + - @backstage/plugin-search-common@1.2.15 + +## 1.7.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.3-next.3 + - @backstage/backend-openapi-utils@0.3.0-next.2 + - @backstage/backend-plugin-api@1.0.2-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.5-next.2 + - @backstage/plugin-search-backend-node@1.3.5-next.3 + - @backstage/plugin-search-common@1.2.14 + +## 1.7.0-next.2 + +### Minor Changes + +- 39fd704: Internal update to use the new generated server types from `backstage-cli package schema openapi generate --server`. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.3-next.2 + - @backstage/backend-openapi-utils@0.3.0-next.2 + - @backstage/backend-plugin-api@1.0.2-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.5-next.2 + - @backstage/plugin-search-backend-node@1.3.5-next.2 + - @backstage/plugin-search-common@1.2.14 + +## 1.6.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.3-next.1 + - @backstage/backend-openapi-utils@0.2.1-next.1 + - @backstage/backend-plugin-api@1.0.2-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.5-next.1 + - @backstage/plugin-search-backend-node@1.3.5-next.1 + - @backstage/plugin-search-common@1.2.14 + +## 1.6.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.3-next.0 + - @backstage/backend-openapi-utils@0.2.1-next.0 + - @backstage/backend-plugin-api@1.0.2-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.5-next.0 + - @backstage/plugin-search-backend-node@1.3.5-next.0 + - @backstage/plugin-search-common@1.2.14 + +## 1.6.0 + +### Minor Changes + +- 3109c24: The export for the new backend system at the `/alpha` export is now also available via the main entry point, which means that you can remove the `/alpha` suffix from the import. + +### Patch Changes + +- 66af016: Fix to schema to allow arbitrary query parameters. +- 094eaa3: Remove references to in-repo backend-common +- Updated dependencies + - @backstage/backend-defaults@0.5.1 + - @backstage/plugin-search-backend-node@1.3.3 + - @backstage/plugin-permission-node@0.8.4 + - @backstage/backend-openapi-utils@0.2.0 + - @backstage/backend-plugin-api@1.0.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-common@1.2.14 + +## 1.5.18-next.2 + +### Patch Changes + +- 66af016: Fix to schema to allow arbitrary query parameters. +- Updated dependencies + - @backstage/backend-defaults@0.5.1-next.2 + - @backstage/backend-openapi-utils@0.2.0-next.1 + - @backstage/backend-plugin-api@1.0.1-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.4-next.1 + - @backstage/plugin-search-backend-node@1.3.3-next.2 + - @backstage/plugin-search-common@1.2.14 + +## 1.5.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.1-next.1 + - @backstage/backend-openapi-utils@0.1.19-next.0 + - @backstage/backend-plugin-api@1.0.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.4-next.0 + - @backstage/plugin-search-backend-node@1.3.3-next.1 + - @backstage/plugin-search-common@1.2.14 + +## 1.5.18-next.0 + +### Patch Changes + +- 094eaa3: Remove references to in-repo backend-common +- Updated dependencies + - @backstage/plugin-search-backend-node@1.3.3-next.0 + - @backstage/backend-defaults@0.5.1-next.0 + - @backstage/plugin-permission-node@0.8.4-next.0 + - @backstage/backend-openapi-utils@0.1.19-next.0 + - @backstage/backend-plugin-api@1.0.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-common@1.2.14 + +## 1.5.17 + +### Patch Changes + +- 5726390: Deprecate create router as the legacy backend system will no longer be supported. +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-defaults@0.5.0 + - @backstage/backend-common@0.25.0 + - @backstage/backend-plugin-api@1.0.0 + - @backstage/plugin-permission-node@0.8.3 + - @backstage/backend-openapi-utils@0.1.18 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-backend-node@1.3.2 + - @backstage/plugin-search-common@1.2.14 + +## 1.5.17-next.2 + +### Patch Changes + +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-common@0.25.0-next.2 + - @backstage/backend-defaults@0.5.0-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/backend-openapi-utils@0.1.18-next.2 + - @backstage/plugin-permission-node@0.8.3-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-backend-node@1.3.2-next.2 + - @backstage/plugin-search-common@1.2.14 + +## 1.5.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-openapi-utils@0.1.18-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-search-common@1.2.14 + +## 1.5.17-next.0 + +### Patch Changes + +- 5726390: Deprecate create router as the legacy backend system will no longer be supported. +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-defaults@0.5.0-next.0 + - @backstage/plugin-permission-node@0.8.3-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/backend-openapi-utils@0.1.18-next.0 + - @backstage/plugin-search-backend-node@1.3.2-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-common@1.2.14 + +## 1.5.15 + +### Patch Changes + +- 3123c16: Fix package metadata +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/plugin-search-common@1.2.14 + - @backstage/backend-openapi-utils@0.1.16 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.5.15-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/backend-defaults@0.4.2-next.3 + - @backstage/backend-openapi-utils@0.1.16-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-permission-node@0.8.1-next.3 + - @backstage/plugin-search-backend-node@1.2.28-next.3 + - @backstage/plugin-search-common@1.2.14-next.1 + +## 1.5.15-next.2 + +### Patch Changes + +- 3123c16: Fix package metadata +- Updated dependencies + - @backstage/backend-defaults@0.4.2-next.2 + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-permission-node@0.8.1-next.2 + - @backstage/plugin-search-backend-node@1.2.28-next.2 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/backend-openapi-utils@0.1.16-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.5.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/plugin-permission-node@0.8.1-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/backend-defaults@0.4.2-next.1 + - @backstage/plugin-search-backend-node@1.2.28-next.1 + - @backstage/plugin-search-common@1.2.14-next.0 + - @backstage/backend-openapi-utils@0.1.16-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.5.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-defaults@0.4.2-next.0 + - @backstage/backend-openapi-utils@0.1.16-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.1-next.0 + - @backstage/plugin-search-backend-node@1.2.28-next.0 + - @backstage/plugin-search-common@1.2.13 + +## 1.5.14 + +### Patch Changes + +- 343f656: The `AuthorizedSearchEngine` will now ignore the deprecated `token` option, and treat it as an unauthorized request. This will not have any effect in practice, since credentials are always provided by the router. +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-defaults@0.4.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.0 + - @backstage/backend-openapi-utils@0.1.15 + - @backstage/plugin-search-backend-node@1.2.27 + - @backstage/plugin-search-common@1.2.13 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.5.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.3.4-next.1 + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-openapi-utils@0.1.15-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/plugin-permission-node@0.7.33-next.1 + - @backstage/plugin-search-backend-node@1.2.27-next.1 + - @backstage/plugin-search-common@1.2.12 + +## 1.5.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.21-next.0 + - @backstage/backend-defaults@0.3.3-next.0 + - @backstage/backend-common@0.23.2-next.0 + - @backstage/backend-openapi-utils@0.1.14-next.0 + - @backstage/plugin-permission-node@0.7.32-next.0 + - @backstage/plugin-search-backend-node@1.2.26-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/plugin-search-common@1.2.12 + +## 1.5.10 + +### Patch Changes + +- 8869b8e: Updated local development setup. +- 78a0b08: Internal refactor to handle `BackendFeature` contract change. +- d44a20a: Added additional plugin metadata to `package.json`. +- 5b6f979: Split backend search plugin startup into "init" and "start" stages to ensure necessary initialization has happened before startup +- 34dc47d: Move @backstage/repo-tools to devDependencies +- Updated dependencies + - @backstage/backend-common@0.23.0 + - @backstage/backend-plugin-api@0.6.19 + - @backstage/backend-defaults@0.3.0 + - @backstage/plugin-search-backend-node@1.2.24 + - @backstage/plugin-permission-node@0.7.30 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/plugin-search-common@1.2.12 + - @backstage/backend-openapi-utils@0.1.12 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.5.10-next.3 + +### Patch Changes + +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.3 + - @backstage/backend-defaults@0.3.0-next.3 + - @backstage/plugin-search-backend-node@1.2.24-next.3 + - @backstage/plugin-permission-common@0.7.14-next.0 + - @backstage/plugin-permission-node@0.7.30-next.3 + - @backstage/plugin-search-common@1.2.12-next.0 + - @backstage/backend-common@0.23.0-next.3 + - @backstage/backend-openapi-utils@0.1.12-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.5.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.2 + - @backstage/backend-common@0.23.0-next.2 + - @backstage/backend-defaults@0.3.0-next.2 + - @backstage/plugin-permission-node@0.7.30-next.2 + - @backstage/backend-openapi-utils@0.1.12-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-search-common@1.2.11 + +## 1.5.10-next.1 + +### Patch Changes + +- 34dc47d: Move @backstage/repo-tools to devDependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-defaults@0.3.0-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + +## 1.5.10-next.0 + +### Patch Changes + +- 8869b8e: Updated local development setup. +- 5b6f979: Split backend search plugin startup into "init" and "start" stages to ensure necessary initialization has happened before startup +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-search-backend-node@1.2.24-next.0 + - @backstage/backend-defaults@0.2.19-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/repo-tools@0.9.1-next.0 + - @backstage/plugin-permission-node@0.7.30-next.0 + - @backstage/backend-openapi-utils@0.1.12-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-search-common@1.2.11 + +## 1.5.8 + +### Patch Changes + +- c6cb568: Add lifecycle monitoring for the search index registry +- Updated dependencies + - @backstage/repo-tools@0.9.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-search-backend-node@1.2.22 + - @backstage/backend-openapi-utils@0.1.11 + - @backstage/plugin-permission-node@0.7.29 + +## 1.5.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/repo-tools@0.9.0-next.2 + +## 1.5.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/repo-tools@0.8.1-next.1 + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-permission-node@0.7.29-next.1 + - @backstage/plugin-search-backend-node@1.2.22-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + - @backstage/backend-openapi-utils@0.1.11-next.1 + +## 1.5.8-next.0 + +### Patch Changes + +- c6cb568: Add lifecycle monitoring for the search index registry +- Updated dependencies + - @backstage/plugin-search-backend-node@1.2.22-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/repo-tools@0.8.1-next.0 + - @backstage/backend-openapi-utils@0.1.11-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-node@0.7.29-next.0 + - @backstage/plugin-search-common@1.2.11 + +## 1.5.7 + +### Patch Changes + +- 2bd291e: Allow reserved characters in requests. +- d5a1fe1: Replaced winston logger with `LoggerService` +- Updated dependencies + - @backstage/backend-common@0.21.7 + - @backstage/repo-tools@0.8.0 + - @backstage/plugin-permission-node@0.7.28 + - @backstage/backend-plugin-api@0.6.17 + - @backstage/plugin-search-backend-node@1.2.21 + - @backstage/backend-openapi-utils@0.1.10 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-search-common@1.2.11 + +## 1.5.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.7-next.1 + - @backstage/backend-plugin-api@0.6.17-next.1 + - @backstage/repo-tools@0.8.0-next.1 + - @backstage/plugin-permission-node@0.7.28-next.1 + - @backstage/plugin-search-backend-node@1.2.21-next.1 + - @backstage/backend-openapi-utils@0.1.10-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-search-common@1.2.11 + +## 1.5.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.7-next.0 + - @backstage/repo-tools@0.8.0-next.0 + - @backstage/backend-openapi-utils@0.1.10-next.0 + - @backstage/backend-plugin-api@0.6.17-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-node@0.7.28-next.0 + - @backstage/plugin-search-backend-node@1.2.21-next.0 + - @backstage/plugin-search-common@1.2.11 + +## 1.5.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.6 + - @backstage/backend-plugin-api@0.6.16 + - @backstage/plugin-permission-node@0.7.27 + - @backstage/plugin-search-backend-node@1.2.20 + - @backstage/backend-openapi-utils@0.1.9 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-search-common@1.2.11 + +## 1.5.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.5 + - @backstage/plugin-permission-node@0.7.26 + - @backstage/plugin-search-backend-node@1.2.19 + - @backstage/backend-openapi-utils@0.1.8 + - @backstage/backend-plugin-api@0.6.15 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-search-common@1.2.11 + +## 1.5.4 + +### Patch Changes + +- 2bd1410: Removed unused dependencies +- 744c0cb: Update the router to use the new `auth` services, it now accepts an optional discovery service option to get credentials for the permission service. +- Updated dependencies + - @backstage/backend-common@0.21.4 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/backend-plugin-api@0.6.14 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-search-common@1.2.11 + - @backstage/backend-openapi-utils@0.1.7 + - @backstage/plugin-search-backend-node@1.2.18 + - @backstage/plugin-permission-node@0.7.25 + - @backstage/types@1.1.1 + +## 1.5.4-next.2 + +### Patch Changes + +- 2bd1410: Removed unused dependencies +- Updated dependencies + - @backstage/backend-common@0.21.4-next.2 + - @backstage/backend-openapi-utils@0.1.7-next.2 + - @backstage/backend-plugin-api@0.6.14-next.2 + - @backstage/config@1.2.0-next.1 + - @backstage/errors@1.2.4-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13-next.1 + - @backstage/plugin-permission-node@0.7.25-next.2 + - @backstage/plugin-search-backend-node@1.2.18-next.2 + - @backstage/plugin-search-common@1.2.11-next.1 + +## 1.5.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.2.0-next.1 + - @backstage/backend-common@0.21.4-next.1 + - @backstage/backend-plugin-api@0.6.14-next.1 + - @backstage/plugin-auth-node@0.4.9-next.1 + - @backstage/plugin-permission-common@0.7.13-next.1 + - @backstage/plugin-permission-node@0.7.25-next.1 + - @backstage/plugin-search-backend-node@1.2.18-next.1 + - @backstage/backend-openapi-utils@0.1.7-next.1 + - @backstage/errors@1.2.4-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-search-common@1.2.11-next.1 + +## 1.5.3-next.0 + +### Patch Changes + +- 744c0cb: Update the router to use the new `auth` services, it now accepts an optional discovery service option to get credentials for the permission service. +- Updated dependencies + - @backstage/backend-common@0.21.3-next.0 + - @backstage/plugin-auth-node@0.4.8-next.0 + - @backstage/errors@1.2.4-next.0 + - @backstage/backend-plugin-api@0.6.13-next.0 + - @backstage/plugin-permission-common@0.7.13-next.0 + - @backstage/plugin-search-common@1.2.11-next.0 + - @backstage/backend-openapi-utils@0.1.6-next.0 + - @backstage/plugin-search-backend-node@1.2.17-next.0 + - @backstage/plugin-permission-node@0.7.24-next.0 + - @backstage/config@1.1.2-next.0 + - @backstage/types@1.1.1 + +## 1.5.0 + +### Minor Changes + +- 126c2f9: Updates the OpenAPI spec to use plugin as `info.title` instead of package name. +- 04907c3: Updates the OpenAPI specification title to plugin ID instead of package name. + +### Patch Changes + +- 3ed0eac: Authorized search engine now obeys query page limit +- Updated dependencies + - @backstage/backend-common@0.21.0 + - @backstage/plugin-auth-node@0.4.4 + - @backstage/backend-plugin-api@0.6.10 + - @backstage/backend-openapi-utils@0.1.3 + - @backstage/plugin-permission-node@0.7.21 + - @backstage/plugin-search-backend-node@1.2.14 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-search-common@1.2.10 + +## 1.5.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.3 + - @backstage/plugin-auth-node@0.4.4-next.3 + - @backstage/plugin-permission-node@0.7.21-next.3 + - @backstage/plugin-search-backend-node@1.2.14-next.3 + - @backstage/backend-openapi-utils@0.1.3-next.3 + - @backstage/backend-plugin-api@0.6.10-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-search-common@1.2.10 + +## 1.5.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.2 + - @backstage/backend-plugin-api@0.6.10-next.2 + - @backstage/plugin-auth-node@0.4.4-next.2 + - @backstage/plugin-permission-node@0.7.21-next.2 + - @backstage/plugin-search-backend-node@1.2.14-next.2 + - @backstage/backend-openapi-utils@0.1.3-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-search-common@1.2.10 + +## 1.5.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.1 + - @backstage/backend-common@0.21.0-next.1 + - @backstage/backend-openapi-utils@0.1.3-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.4-next.1 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-node@0.7.21-next.1 + - @backstage/plugin-search-backend-node@1.2.14-next.1 + - @backstage/plugin-search-common@1.2.10 + +## 1.5.0-next.0 + +### Minor Changes + +- 126c2f9: Updates the OpenAPI spec to use plugin as `info.title` instead of package name. +- 04907c3: Updates the OpenAPI specification title to plugin ID instead of package name. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-openapi-utils@0.1.3-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-permission-node@0.7.21-next.0 + - @backstage/plugin-search-backend-node@1.2.14-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-search-common@1.2.10 + +## 1.4.9 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/backend-openapi-utils@0.1.2 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-node@0.7.20 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/plugin-search-backend-node@1.2.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-search-common@1.2.10 + +## 1.4.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/backend-openapi-utils@0.1.2-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-permission-node@0.7.20-next.2 + - @backstage/plugin-search-backend-node@1.2.13-next.2 + +## 1.4.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/plugin-permission-node@0.7.20-next.1 + - @backstage/plugin-search-backend-node@1.2.13-next.1 + - @backstage/backend-openapi-utils@0.1.2-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-search-common@1.2.9 + +## 1.4.9-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-openapi-utils@0.1.2-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.20-next.0 + - @backstage/plugin-search-backend-node@1.2.13-next.0 + - @backstage/plugin-search-common@1.2.9 + +## 1.4.8 + +### Patch Changes + +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/backend-openapi-utils@0.1.1 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.19 + - @backstage/plugin-search-backend-node@1.2.12 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-search-common@1.2.9 + +## 1.4.8-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-openapi-utils@0.1.1-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.3 + - @backstage/plugin-search-backend-node@1.2.12-next.3 + - @backstage/plugin-search-common@1.2.8 + +## 1.4.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-openapi-utils@0.1.1-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.2 + - @backstage/plugin-search-backend-node@1.2.12-next.2 + - @backstage/plugin-search-common@1.2.8 + +## 1.4.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-openapi-utils@0.1.1-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.1 + - @backstage/plugin-search-backend-node@1.2.12-next.1 + - @backstage/plugin-search-common@1.2.8 + +## 1.4.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.0 + - @backstage/backend-openapi-utils@0.1.1-next.0 + - @backstage/plugin-auth-node@0.4.2-next.0 + - @backstage/plugin-permission-node@0.7.19-next.0 + - @backstage/plugin-search-backend-node@1.2.12-next.0 + - @backstage/backend-plugin-api@0.6.8-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-search-common@1.2.8 + +## 1.4.7 + +### Patch Changes + +- 6694b369a3: Update the OpenAPI spec with more complete error responses and request bodies using Optic. Also, updates the test cases to use the new `supertest` pass through from `@backstage/backend-openapi-utils`. +- Updated dependencies + - @backstage/plugin-search-backend-node@1.2.11 + - @backstage/backend-common@0.19.9 + - @backstage/backend-plugin-api@0.6.7 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/backend-openapi-utils@0.1.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.1 + - @backstage/plugin-permission-node@0.7.18 + - @backstage/plugin-search-common@1.2.8 + +## 1.4.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.7-next.2 + - @backstage/backend-common@0.19.9-next.2 + - @backstage/backend-openapi-utils@0.1.0-next.2 + - @backstage/plugin-auth-node@0.4.1-next.2 + - @backstage/plugin-permission-node@0.7.18-next.2 + - @backstage/plugin-search-backend-node@1.2.11-next.2 + +## 1.4.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.9-next.1 + - @backstage/plugin-auth-node@0.4.1-next.1 + - @backstage/plugin-permission-node@0.7.18-next.1 + - @backstage/plugin-search-backend-node@1.2.11-next.1 + - @backstage/backend-openapi-utils@0.1.0-next.1 + - @backstage/backend-plugin-api@0.6.7-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.9 + - @backstage/plugin-search-common@1.2.7 + +## 1.4.7-next.0 + +### Patch Changes + +- 6694b369a3: Update the OpenAPI spec with more complete error responses and request bodies using Optic. Also, updates the test cases to use the new `supertest` pass through from `@backstage/backend-openapi-utils`. +- Updated dependencies + - @backstage/plugin-search-backend-node@1.2.11-next.0 + - @backstage/backend-openapi-utils@0.1.0-next.0 + - @backstage/backend-common@0.19.9-next.0 + - @backstage/backend-plugin-api@0.6.7-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.1-next.0 + - @backstage/plugin-permission-common@0.7.9 + - @backstage/plugin-permission-node@0.7.18-next.0 + - @backstage/plugin-search-common@1.2.7 + +## 1.4.6 + +### Patch Changes + +- 16be6f9473: Set the default length limit to search query to 100. To override it, define `search.maxTermLength` in the config file. +- Updated dependencies + - @backstage/backend-common@0.19.8 + - @backstage/plugin-auth-node@0.4.0 + - @backstage/errors@1.2.3 + - @backstage/backend-openapi-utils@0.0.5 + - @backstage/backend-plugin-api@0.6.6 + - @backstage/plugin-search-backend-node@1.2.10 + - @backstage/plugin-permission-node@0.7.17 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.9 + - @backstage/plugin-search-common@1.2.7 + +## 1.4.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.8-next.2 + - @backstage/plugin-auth-node@0.4.0-next.2 + - @backstage/errors@1.2.3-next.0 + - @backstage/backend-openapi-utils@0.0.5-next.0 + - @backstage/plugin-permission-node@0.7.17-next.2 + - @backstage/plugin-search-backend-node@1.2.10-next.2 + - @backstage/backend-plugin-api@0.6.6-next.2 + - @backstage/config@1.1.1-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.9-next.0 + - @backstage/plugin-search-common@1.2.7-next.0 + +## 1.4.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.7-next.1 + - @backstage/backend-plugin-api@0.6.5-next.1 + - @backstage/plugin-search-backend-node@1.2.9-next.1 + - @backstage/plugin-auth-node@0.3.2-next.1 + - @backstage/plugin-permission-node@0.7.16-next.1 + - @backstage/config@1.1.0 + - @backstage/backend-openapi-utils@0.0.4 + - @backstage/errors@1.2.2 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.8 + - @backstage/plugin-search-common@1.2.6 + +## 1.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.3.2-next.0 + - @backstage/backend-common@0.19.7-next.0 + - @backstage/config@1.1.0 + - @backstage/backend-openapi-utils@0.0.4 + - @backstage/backend-plugin-api@0.6.5-next.0 + - @backstage/errors@1.2.2 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.8 + - @backstage/plugin-permission-node@0.7.16-next.0 + - @backstage/plugin-search-backend-node@1.2.9-next.0 + - @backstage/plugin-search-common@1.2.6 + +## 1.4.3 + +### Patch Changes + +- 71114ac50e02: The export for the new backend system has been moved to be the `default` export. + + For example, if you are currently importing the plugin using the following pattern: + + ```ts + import { examplePlugin } from '@backstage/plugin-example-backend'; + + backend.add(examplePlugin); + ``` + + It should be migrated to this: + + ```ts + backend.add(import('@backstage/plugin-example-backend')); + ``` + +- Updated dependencies + - @backstage/backend-common@0.19.5 + - @backstage/plugin-auth-node@0.3.0 + - @backstage/config@1.1.0 + - @backstage/errors@1.2.2 + - @backstage/plugin-permission-common@0.7.8 + - @backstage/plugin-search-common@1.2.6 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-node@0.7.14 + - @backstage/backend-plugin-api@0.6.3 + - @backstage/backend-openapi-utils@0.0.4 + - @backstage/plugin-search-backend-node@1.2.7 + +## 1.4.3-next.3 + +### Patch Changes + +- 71114ac50e02: The export for the new backend system has been moved to be the `default` export. + + For example, if you are currently importing the plugin using the following pattern: + + ```ts + import { examplePlugin } from '@backstage/plugin-example-backend'; + + backend.add(examplePlugin); + ``` + + It should be migrated to this: + + ```ts + backend.add(import('@backstage/plugin-example-backend')); + ``` + +- Updated dependencies + - @backstage/config@1.1.0-next.2 + - @backstage/errors@1.2.2-next.0 + - @backstage/plugin-permission-common@0.7.8-next.2 + - @backstage/plugin-search-common@1.2.6-next.2 + - @backstage/types@1.1.1-next.0 + - @backstage/plugin-permission-node@0.7.14-next.3 + - @backstage/backend-plugin-api@0.6.3-next.3 + - @backstage/backend-common@0.19.5-next.3 + - @backstage/backend-openapi-utils@0.0.4-next.0 + - @backstage/plugin-auth-node@0.3.0-next.3 + - @backstage/plugin-search-backend-node@1.2.7-next.3 + +## 1.4.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.0-next.1 + - @backstage/backend-common@0.19.5-next.2 + - @backstage/plugin-auth-node@0.3.0-next.2 + - @backstage/plugin-permission-node@0.7.14-next.2 + - @backstage/backend-plugin-api@0.6.3-next.2 + - @backstage/plugin-permission-common@0.7.8-next.1 + - @backstage/plugin-search-backend-node@1.2.7-next.2 + - @backstage/backend-openapi-utils@0.0.3 + - @backstage/errors@1.2.1 + - @backstage/types@1.1.0 + - @backstage/plugin-search-common@1.2.6-next.1 + +## 1.4.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.0-next.0 + - @backstage/backend-common@0.19.5-next.1 + - @backstage/backend-plugin-api@0.6.3-next.1 + - @backstage/plugin-auth-node@0.3.0-next.1 + - @backstage/plugin-permission-common@0.7.8-next.0 + - @backstage/plugin-permission-node@0.7.14-next.1 + - @backstage/plugin-search-backend-node@1.2.7-next.1 + - @backstage/backend-openapi-utils@0.0.3 + - @backstage/errors@1.2.1 + - @backstage/types@1.1.0 + - @backstage/plugin-search-common@1.2.6-next.0 + +## 1.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.3.0-next.0 + - @backstage/backend-common@0.19.4-next.0 + - @backstage/backend-openapi-utils@0.0.3 + - @backstage/backend-plugin-api@0.6.2-next.0 + - @backstage/config@1.0.8 + - @backstage/errors@1.2.1 + - @backstage/types@1.1.0 + - @backstage/plugin-permission-common@0.7.7 + - @backstage/plugin-permission-node@0.7.13-next.0 + - @backstage/plugin-search-backend-node@1.2.6-next.0 + - @backstage/plugin-search-common@1.2.5 + +## 1.4.0 + +### Minor Changes + +- ebeb77586975: Now performs request validation based on OpenAPI schema through `@backstage/backend-openapi-utils`. Error responses for invalid input, like `"a"` instead of a number, may have changed. + +### Patch Changes + +- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config` +- 12a8c94eda8d: Add package repository and homepage metadata +- 951ab6c9db58: Add missing `configSchema` to package.json +- Updated dependencies + - @backstage/backend-common@0.19.2 + - @backstage/backend-plugin-api@0.6.0 + - @backstage/backend-openapi-utils@0.0.3 + - @backstage/plugin-search-backend-node@1.2.4 + - @backstage/plugin-auth-node@0.2.17 + - @backstage/plugin-permission-node@0.7.11 + - @backstage/config@1.0.8 + - @backstage/errors@1.2.1 + - @backstage/types@1.1.0 + - @backstage/plugin-permission-common@0.7.7 + - @backstage/plugin-search-common@1.2.5 + +## 1.4.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.0-next.2 + - @backstage/backend-common@0.19.2-next.2 + - @backstage/plugin-permission-node@0.7.11-next.2 + - @backstage/plugin-search-backend-node@1.2.4-next.2 + - @backstage/plugin-auth-node@0.2.17-next.2 + +## 1.4.0-next.1 + +### Patch Changes + +- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config` +- 12a8c94eda8d: Add package repository and homepage metadata +- 951ab6c9db58: Add missing `configSchema` to package.json +- Updated dependencies + - @backstage/backend-common@0.19.2-next.1 + - @backstage/backend-openapi-utils@0.0.3-next.1 + - @backstage/plugin-search-backend-node@1.2.4-next.1 + - @backstage/plugin-auth-node@0.2.17-next.1 + - @backstage/backend-plugin-api@0.6.0-next.1 + - @backstage/plugin-permission-node@0.7.11-next.1 + - @backstage/config@1.0.8 + - @backstage/errors@1.2.1 + - @backstage/types@1.1.0 + - @backstage/plugin-permission-common@0.7.7 + - @backstage/plugin-search-common@1.2.5 + +## 1.4.0-next.0 + +### Minor Changes + +- ebeb77586975: Now performs request validation based on OpenAPI schema through `@backstage/backend-openapi-utils`. Error responses for invalid input, like `"a"` instead of a number, may have changed. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.0.3-next.0 + - @backstage/plugin-search-backend-node@1.2.4-next.0 + - @backstage/backend-common@0.19.2-next.0 + - @backstage/backend-plugin-api@0.5.5-next.0 + - @backstage/config@1.0.8 + - @backstage/errors@1.2.1 + - @backstage/types@1.1.0 + - @backstage/plugin-auth-node@0.2.17-next.0 + - @backstage/plugin-permission-common@0.7.7 + - @backstage/plugin-permission-node@0.7.11-next.0 + - @backstage/plugin-search-common@1.2.5 + +## 1.3.3 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.1 + - @backstage/backend-common@0.19.1 + - @backstage/backend-plugin-api@0.5.4 + - @backstage/config@1.0.8 + - @backstage/types@1.1.0 + - @backstage/plugin-auth-node@0.2.16 + - @backstage/plugin-permission-common@0.7.7 + - @backstage/plugin-permission-node@0.7.10 + - @backstage/plugin-search-backend-node@1.2.3 + - @backstage/plugin-search-common@1.2.5 + +## 1.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.1-next.0 + - @backstage/backend-common@0.19.1-next.0 + - @backstage/backend-plugin-api@0.5.4-next.0 + - @backstage/config@1.0.8 + - @backstage/types@1.1.0 + - @backstage/plugin-auth-node@0.2.16-next.0 + - @backstage/plugin-permission-common@0.7.7-next.0 + - @backstage/plugin-permission-node@0.7.10-next.0 + - @backstage/plugin-search-backend-node@1.2.3-next.0 + - @backstage/plugin-search-common@1.2.5-next.0 + +## 1.3.2 + +### Patch Changes + +- ee411e7c2623: Update OpenAPI specs to be in line with linting standards. +- Updated dependencies + - @backstage/backend-common@0.19.0 + - @backstage/types@1.1.0 + - @backstage/errors@1.2.0 + - @backstage/backend-plugin-api@0.5.3 + - @backstage/plugin-auth-node@0.2.15 + - @backstage/plugin-permission-node@0.7.9 + - @backstage/plugin-search-backend-node@1.2.2 + - @backstage/config@1.0.8 + - @backstage/plugin-permission-common@0.7.6 + - @backstage/plugin-search-common@1.2.4 + +## 1.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.0-next.2 + - @backstage/backend-plugin-api@0.5.3-next.2 + - @backstage/config@1.0.7 + - @backstage/errors@1.2.0-next.0 + - @backstage/types@1.0.2 + - @backstage/plugin-auth-node@0.2.15-next.2 + - @backstage/plugin-permission-common@0.7.6-next.0 + - @backstage/plugin-permission-node@0.7.9-next.2 + - @backstage/plugin-search-backend-node@1.2.2-next.2 + - @backstage/plugin-search-common@1.2.4-next.0 + +## 1.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.0-next.1 + - @backstage/errors@1.2.0-next.0 + - @backstage/backend-plugin-api@0.5.3-next.1 + - @backstage/plugin-auth-node@0.2.15-next.1 + - @backstage/plugin-permission-node@0.7.9-next.1 + - @backstage/plugin-search-backend-node@1.2.2-next.1 + - @backstage/plugin-permission-common@0.7.6-next.0 + - @backstage/config@1.0.7 + - @backstage/types@1.0.2 + - @backstage/plugin-search-common@1.2.4-next.0 + +## 1.3.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.6-next.0 + - @backstage/config@1.0.7 + - @backstage/backend-plugin-api@0.5.3-next.0 + - @backstage/errors@1.1.5 + - @backstage/types@1.0.2 + - @backstage/plugin-auth-node@0.2.15-next.0 + - @backstage/plugin-permission-common@0.7.5 + - @backstage/plugin-permission-node@0.7.9-next.0 + - @backstage/plugin-search-backend-node@1.2.2-next.0 + - @backstage/plugin-search-common@1.2.3 + +## 1.3.1 + +### Patch Changes + +- 021cfbb5152: Added an OpenAPI 3.0 spec and enforced schema-first model on the router. +- Updated dependencies + - @backstage/backend-common@0.18.5 + - @backstage/plugin-permission-node@0.7.8 + - @backstage/plugin-auth-node@0.2.14 + - @backstage/plugin-search-backend-node@1.2.1 + - @backstage/backend-plugin-api@0.5.2 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + - @backstage/types@1.0.2 + - @backstage/plugin-permission-common@0.7.5 + - @backstage/plugin-search-common@1.2.3 + +## 1.3.1-next.2 + +### Patch Changes + +- 021cfbb5152: Added an OpenAPI 3.0 spec and enforced schema-first model on the router. +- Updated dependencies + - @backstage/config@1.0.7 + +## 1.3.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.5-next.1 + - @backstage/plugin-auth-node@0.2.14-next.1 + - @backstage/plugin-permission-node@0.7.8-next.1 + - @backstage/plugin-search-backend-node@1.2.1-next.1 + - @backstage/backend-plugin-api@0.5.2-next.1 + - @backstage/config@1.0.7 + +## 1.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.5-next.0 + - @backstage/plugin-permission-node@0.7.8-next.0 + - @backstage/plugin-auth-node@0.2.14-next.0 + - @backstage/plugin-search-backend-node@1.2.1-next.0 + - @backstage/backend-plugin-api@0.5.2-next.0 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + - @backstage/types@1.0.2 + - @backstage/plugin-permission-common@0.7.5 + - @backstage/plugin-search-common@1.2.3 + +## 1.3.0 + +### Minor Changes + +- 1469daa409e: Exports search plugin that can be used with the new backend system. For documentation on how to migrate, check out the [how to migrate to the new backend system guide](https://backstage.io/docs/features/search/how-to-guides/#how-to-migrate-your-backend-installation-to-use-search-together-with-the-new-backend-system). + +### Patch Changes + +- 1e4f5e91b8e: Bump `zod` and `zod-to-json-schema` dependencies. +- Updated dependencies + - @backstage/backend-common@0.18.4 + - @backstage/plugin-permission-node@0.7.7 + - @backstage/plugin-permission-common@0.7.5 + - @backstage/plugin-search-backend-node@1.2.0 + - @backstage/plugin-auth-node@0.2.13 + - @backstage/backend-plugin-api@0.5.1 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + - @backstage/types@1.0.2 + - @backstage/plugin-search-common@1.2.3 + +## 1.3.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.4-next.2 + - @backstage/plugin-permission-node@0.7.7-next.2 + - @backstage/backend-plugin-api@0.5.1-next.2 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + - @backstage/types@1.0.2 + - @backstage/plugin-auth-node@0.2.13-next.2 + - @backstage/plugin-permission-common@0.7.5-next.0 + - @backstage/plugin-search-backend-node@1.2.0-next.2 + - @backstage/plugin-search-common@1.2.3-next.0 + +## 1.3.0-next.1 + +### Minor Changes + +- 1469daa409e: Exports search plugin that can be used with the new backend system. For documentation on how to migrate, check out the [how to migrate to the new backend system guide](https://backstage.io/docs/features/search/how-to-guides/#how-to-migrate-your-backend-installation-to-use-search-together-with-the-new-backend-system). + +### Patch Changes + +- 1e4f5e91b8e: Bump `zod` and `zod-to-json-schema` dependencies. +- Updated dependencies + - @backstage/plugin-permission-node@0.7.7-next.1 + - @backstage/plugin-permission-common@0.7.5-next.0 + - @backstage/plugin-search-backend-node@1.2.0-next.1 + - @backstage/backend-common@0.18.4-next.1 + - @backstage/backend-plugin-api@0.5.1-next.1 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + - @backstage/types@1.0.2 + - @backstage/plugin-auth-node@0.2.13-next.1 + - @backstage/plugin-search-common@1.2.3-next.0 + +## 1.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.4-next.0 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + - @backstage/types@1.0.2 + - @backstage/plugin-auth-node@0.2.13-next.0 + - @backstage/plugin-permission-common@0.7.4 + - @backstage/plugin-permission-node@0.7.7-next.0 + - @backstage/plugin-search-backend-node@1.1.5-next.0 + - @backstage/plugin-search-common@1.2.2 + +## 1.2.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-node@0.7.6 + - @backstage/plugin-auth-node@0.2.12 + - @backstage/backend-common@0.18.3 + - @backstage/errors@1.1.5 + - @backstage/plugin-permission-common@0.7.4 + - @backstage/config@1.0.7 + - @backstage/types@1.0.2 + - @backstage/plugin-search-backend-node@1.1.4 + - @backstage/plugin-search-common@1.2.2 + +## 1.2.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.2.12-next.2 + - @backstage/backend-common@0.18.3-next.2 + - @backstage/plugin-permission-node@0.7.6-next.2 + - @backstage/plugin-search-backend-node@1.1.4-next.2 + - @backstage/config@1.0.7-next.0 + +## 1.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-node@0.7.6-next.1 + - @backstage/errors@1.1.5-next.0 + - @backstage/backend-common@0.18.3-next.1 + - @backstage/plugin-auth-node@0.2.12-next.1 + - @backstage/plugin-permission-common@0.7.4-next.0 + - @backstage/config@1.0.7-next.0 + - @backstage/types@1.0.2 + - @backstage/plugin-search-backend-node@1.1.4-next.1 + - @backstage/plugin-search-common@1.2.2-next.0 + +## 1.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.3-next.0 + - @backstage/config@1.0.6 + - @backstage/errors@1.1.4 + - @backstage/types@1.0.2 + - @backstage/plugin-auth-node@0.2.12-next.0 + - @backstage/plugin-permission-common@0.7.3 + - @backstage/plugin-permission-node@0.7.6-next.0 + - @backstage/plugin-search-backend-node@1.1.4-next.0 + - @backstage/plugin-search-common@1.2.1 + +## 1.2.3 + +### Patch Changes + +- 1b30c9a0f7: Change the router's response to include the error message instead of its object type in case it fails during a search query because the messages have more info. +- Updated dependencies + - @backstage/backend-common@0.18.2 + - @backstage/config@1.0.6 + - @backstage/errors@1.1.4 + - @backstage/types@1.0.2 + - @backstage/plugin-auth-node@0.2.11 + - @backstage/plugin-permission-common@0.7.3 + - @backstage/plugin-permission-node@0.7.5 + - @backstage/plugin-search-backend-node@1.1.3 + - @backstage/plugin-search-common@1.2.1 + +## 1.2.3-next.2 + +### Patch Changes + +- 1b30c9a0f7: Change the router's response to include the error message instead of its object type in case it fails during a search query because the messages have more info. +- Updated dependencies + - @backstage/backend-common@0.18.2-next.2 + - @backstage/plugin-auth-node@0.2.11-next.2 + - @backstage/plugin-permission-node@0.7.5-next.2 + - @backstage/config@1.0.6 + - @backstage/errors@1.1.4 + - @backstage/types@1.0.2 + - @backstage/plugin-permission-common@0.7.3 + - @backstage/plugin-search-backend-node@1.1.3-next.2 + - @backstage/plugin-search-common@1.2.1 + +## 1.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.2-next.1 + - @backstage/config@1.0.6 + - @backstage/errors@1.1.4 + - @backstage/types@1.0.2 + - @backstage/plugin-auth-node@0.2.11-next.1 + - @backstage/plugin-permission-common@0.7.3 + - @backstage/plugin-permission-node@0.7.5-next.1 + - @backstage/plugin-search-backend-node@1.1.3-next.1 + - @backstage/plugin-search-common@1.2.1 + +## 1.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.2-next.0 + - @backstage/plugin-auth-node@0.2.11-next.0 + - @backstage/plugin-permission-node@0.7.5-next.0 + - @backstage/plugin-search-backend-node@1.1.3-next.0 + +## 1.2.1 + +### Patch Changes + +- bfd66b0478: Allow max page limit for search results to be configurable +- Updated dependencies + - @backstage/backend-common@0.18.0 + - @backstage/plugin-search-backend-node@1.1.1 + - @backstage/config@1.0.6 + - @backstage/errors@1.1.4 + - @backstage/types@1.0.2 + - @backstage/plugin-auth-node@0.2.9 + - @backstage/plugin-permission-common@0.7.3 + - @backstage/plugin-permission-node@0.7.3 + - @backstage/plugin-search-common@1.2.1 + +## 1.2.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.0-next.1 + - @backstage/plugin-auth-node@0.2.9-next.1 + - @backstage/plugin-permission-node@0.7.3-next.1 + - @backstage/plugin-search-backend-node@1.1.1-next.2 + - @backstage/config@1.0.6-next.0 + - @backstage/errors@1.1.4 + - @backstage/types@1.0.2 + - @backstage/plugin-permission-common@0.7.3-next.0 + - @backstage/plugin-search-common@1.2.1-next.0 + +## 1.2.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.0-next.0 + - @backstage/config@1.0.6-next.0 + - @backstage/errors@1.1.4 + - @backstage/types@1.0.2 + - @backstage/plugin-auth-node@0.2.9-next.0 + - @backstage/plugin-permission-common@0.7.3-next.0 + - @backstage/plugin-permission-node@0.7.3-next.0 + - @backstage/plugin-search-backend-node@1.1.1-next.1 + - @backstage/plugin-search-common@1.2.1-next.0 + +## 1.2.1-next.0 + +### Patch Changes + +- bfd66b0478: Allow max page limit for search results to be configurable +- Updated dependencies + - @backstage/plugin-search-backend-node@1.1.1-next.0 + - @backstage/backend-common@0.17.0 + - @backstage/config@1.0.5 + - @backstage/errors@1.1.4 + - @backstage/types@1.0.2 + - @backstage/plugin-auth-node@0.2.8 + - @backstage/plugin-permission-common@0.7.2 + - @backstage/plugin-permission-node@0.7.2 + - @backstage/plugin-search-common@1.2.0 + +## 1.2.0 + +### Minor Changes + +- 29ebc43a0b: numberOfResults is now provided alongside the query result + +### Patch Changes + +- b05dcd5530: Move the `zod` dependency to a version that does not collide with other libraries +- Updated dependencies + - @backstage/plugin-search-backend-node@1.1.0 + - @backstage/backend-common@0.17.0 + - @backstage/plugin-permission-common@0.7.2 + - @backstage/plugin-permission-node@0.7.2 + - @backstage/errors@1.1.4 + - @backstage/plugin-auth-node@0.2.8 + - @backstage/types@1.0.2 + - @backstage/plugin-search-common@1.2.0 + - @backstage/config@1.0.5 + +## 1.2.0-next.3 + +### Patch Changes + +- b05dcd5530: Move the `zod` dependency to a version that does not collide with other libraries +- Updated dependencies + - @backstage/plugin-permission-common@0.7.2-next.2 + - @backstage/plugin-permission-node@0.7.2-next.3 + - @backstage/backend-common@0.17.0-next.3 + - @backstage/config@1.0.5-next.1 + - @backstage/errors@1.1.4-next.1 + - @backstage/types@1.0.2-next.1 + - @backstage/plugin-auth-node@0.2.8-next.3 + - @backstage/plugin-search-backend-node@1.1.0-next.3 + - @backstage/plugin-search-common@1.2.0-next.3 + +## 1.2.0-next.2 + +### Minor Changes + +- 29ebc43a0b: numberOfResults is now provided alongside the query result + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.1.0-next.2 + - @backstage/backend-common@0.17.0-next.2 + - @backstage/plugin-search-common@1.2.0-next.2 + - @backstage/plugin-auth-node@0.2.8-next.2 + - @backstage/plugin-permission-node@0.7.2-next.2 + - @backstage/config@1.0.5-next.1 + - @backstage/errors@1.1.4-next.1 + - @backstage/types@1.0.2-next.1 + - @backstage/plugin-permission-common@0.7.2-next.1 + +## 1.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.17.0-next.1 + - @backstage/plugin-search-backend-node@1.0.5-next.1 + - @backstage/types@1.0.2-next.1 + - @backstage/plugin-auth-node@0.2.8-next.1 + - @backstage/plugin-permission-node@0.7.2-next.1 + - @backstage/config@1.0.5-next.1 + - @backstage/errors@1.1.4-next.1 + - @backstage/plugin-permission-common@0.7.2-next.1 + - @backstage/plugin-search-common@1.1.2-next.1 + +## 1.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.0.5-next.0 + - @backstage/backend-common@0.16.1-next.0 + - @backstage/plugin-auth-node@0.2.8-next.0 + - @backstage/plugin-permission-common@0.7.2-next.0 + - @backstage/plugin-permission-node@0.7.2-next.0 + - @backstage/types@1.0.2-next.0 + - @backstage/config@1.0.5-next.0 + - @backstage/errors@1.1.4-next.0 + - @backstage/plugin-search-common@1.1.2-next.0 + +## 1.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0 + - @backstage/plugin-auth-node@0.2.7 + - @backstage/plugin-permission-common@0.7.1 + - @backstage/types@1.0.1 + - @backstage/plugin-permission-node@0.7.1 + - @backstage/plugin-search-backend-node@1.0.4 + - @backstage/config@1.0.4 + - @backstage/errors@1.1.3 + - @backstage/plugin-search-common@1.1.1 + +## 1.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.1 + - @backstage/plugin-auth-node@0.2.7-next.1 + - @backstage/plugin-permission-node@0.7.1-next.1 + - @backstage/plugin-search-backend-node@1.0.4-next.1 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## 1.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/plugin-permission-node@0.7.1-next.0 + - @backstage/plugin-search-backend-node@1.0.4-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## 1.1.0 + +### Minor Changes + +- 16c853a6ed: Be less restrictive with unknown keys on query endpoint +- a799972bb1: The query received by search engines now contains a property called `pageLimit`, it specifies how many results to return per page when sending a query request to the search backend. + + Example: + _Returns up to 30 results per page_ + + GET /query?pageLimit=30 + + The search backend validates the page limit and this value must not exceed 100, but it doesn't set a default value for the page limit parameter, it leaves it up to each search engine to set this, so Lunr, Postgres and Elastic Search set 25 results per page as a default value. + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/plugin-permission-node@0.7.0 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-search-backend-node@1.0.3 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## 1.1.0-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/plugin-search-backend-node@1.0.3-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## 1.1.0-next.1 + +### Minor Changes + +- 16c853a6ed: Be less restrictive with unknown keys on query endpoint +- a799972bb1: The query received by search engines now contains a property called `pageLimit`, it specifies how many results to return per page when sending a query request to the search backend. + + Example: + _Returns up to 30 results per page_ + + GET /query?pageLimit=30 + + The search backend validates the page limit and this value must not exceed 100, but it doesn't set a default value for the page limit parameter, it leaves it up to each search engine to set this, so Lunr, Postgres and Elastic Search set 25 results per page as a default value. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/plugin-search-backend-node@1.0.3-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/types@1.0.0 + - @backstage/plugin-auth-node@0.2.6-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + - @backstage/plugin-permission-node@0.6.6-next.1 + +## 1.0.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/plugin-auth-node@0.2.6-next.0 + - @backstage/plugin-permission-node@0.6.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/types@1.0.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + - @backstage/plugin-search-backend-node@1.0.3-next.0 + - @backstage/plugin-search-common@1.0.2-next.0 + +## 1.0.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.1 + - @backstage/plugin-auth-node@0.2.5 + - @backstage/plugin-permission-node@0.6.5 + - @backstage/plugin-search-backend-node@1.0.2 + - @backstage/config@1.0.2 + - @backstage/errors@1.1.1 + - @backstage/plugin-permission-common@0.6.4 + - @backstage/plugin-search-common@1.0.1 + +## 1.0.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.2-next.0 + - @backstage/errors@1.1.1-next.0 + - @backstage/plugin-permission-common@0.6.4-next.2 + - @backstage/backend-common@0.15.1-next.3 + - @backstage/plugin-auth-node@0.2.5-next.3 + - @backstage/plugin-permission-node@0.6.5-next.3 + - @backstage/plugin-search-backend-node@1.0.2-next.2 + +## 1.0.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.1-next.0 + - @backstage/plugin-search-backend-node@1.0.2-next.0 + - @backstage/plugin-auth-node@0.2.5-next.0 + - @backstage/plugin-permission-common@0.6.4-next.0 + - @backstage/plugin-permission-node@0.6.5-next.0 + - @backstage/plugin-search-common@1.0.1-next.0 + +## 1.0.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.0 + - @backstage/plugin-auth-node@0.2.4 + - @backstage/plugin-permission-node@0.6.4 + - @backstage/plugin-search-backend-node@1.0.1 + +## 1.0.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.0-next.0 + - @backstage/plugin-auth-node@0.2.4-next.0 + - @backstage/plugin-permission-node@0.6.4-next.0 + - @backstage/plugin-search-backend-node@1.0.1-next.0 + +## 1.0.0 + +### Major Changes + +- 7bd7d336b2: This package has been promoted to 1.0. Read more about what it means in [New release: Backstage Search 1.0 blog](https://backstage.io/blog/2022/07/19/releasing-backstage-search-1.0) + +### Patch Changes + +- a21cd43467: If error is `MissingIndexError` we return a 400 response with a more clear error message. +- Updated dependencies + - @backstage/backend-common@0.14.1 + - @backstage/plugin-search-backend-node@1.0.0 + - @backstage/plugin-search-common@1.0.0 + - @backstage/plugin-auth-node@0.2.3 + - @backstage/plugin-permission-common@0.6.3 + - @backstage/plugin-permission-node@0.6.3 + - @backstage/errors@1.1.0 + +## 0.5.4-next.2 + +### Patch Changes + +- a21cd43467: If error is `MissingIndexError` we return a 400 response with a more clear error message. +- Updated dependencies + - @backstage/backend-common@0.14.1-next.3 + - @backstage/plugin-auth-node@0.2.3-next.2 + - @backstage/plugin-permission-common@0.6.3-next.1 + - @backstage/plugin-permission-node@0.6.3-next.2 + - @backstage/plugin-search-backend-node@0.6.3-next.2 + +## 0.5.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.14.1-next.1 + - @backstage/errors@1.1.0-next.0 + - @backstage/plugin-auth-node@0.2.3-next.1 + - @backstage/plugin-permission-common@0.6.3-next.0 + - @backstage/plugin-permission-node@0.6.3-next.1 + - @backstage/plugin-search-backend-node@0.6.3-next.1 + - @backstage/plugin-search-common@0.3.6-next.0 + +## 0.5.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.14.1-next.0 + - @backstage/plugin-auth-node@0.2.3-next.0 + - @backstage/plugin-permission-node@0.6.3-next.0 + - @backstage/plugin-search-backend-node@0.6.3-next.0 + +## 0.5.3 + +### Patch Changes + +- 7d8acfc32e: `RouterOptions` and `createRouter` now marked as public exports +- 915700f64f: The provided search engine now adds a pagination-aware `rank` value to all results. +- Updated dependencies + - @backstage/plugin-search-common@0.3.5 + - @backstage/plugin-permission-node@0.6.2 + - @backstage/backend-common@0.14.0 + - @backstage/plugin-auth-node@0.2.2 + - @backstage/plugin-search-backend-node@0.6.2 + - @backstage/plugin-permission-common@0.6.2 + +## 0.5.3-next.2 + +### Patch Changes + +- 7d8acfc32e: `RouterOptions` and `createRouter` now marked as public exports +- Updated dependencies + - @backstage/plugin-search-common@0.3.5-next.1 + - @backstage/backend-common@0.14.0-next.2 + - @backstage/plugin-search-backend-node@0.6.2-next.2 + - @backstage/plugin-auth-node@0.2.2-next.2 + - @backstage/plugin-permission-node@0.6.2-next.2 + +## 0.5.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.1 + - @backstage/plugin-auth-node@0.2.2-next.1 + - @backstage/plugin-permission-common@0.6.2-next.0 + - @backstage/plugin-permission-node@0.6.2-next.1 + - @backstage/plugin-search-backend-node@0.6.2-next.1 + - @backstage/plugin-search-common@0.3.5-next.0 + +## 0.5.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/plugin-search-backend-node@0.6.2-next.0 + - @backstage/plugin-auth-node@0.2.2-next.0 + - @backstage/plugin-permission-node@0.6.2-next.0 + +## 0.5.2 + +### Patch Changes + +- 8cc75993a6: Fixed issue in `PermissionEvaluator` instance check that would cause unexpected "invalid union" errors. +- Updated dependencies + - @backstage/backend-common@0.13.3 + - @backstage/config@1.0.1 + - @backstage/plugin-search-backend-node@0.6.1 + - @backstage/plugin-auth-node@0.2.1 + - @backstage/plugin-search-common@0.3.4 + - @backstage/plugin-permission-common@0.6.1 + - @backstage/plugin-permission-node@0.6.1 + +## 0.5.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.3-next.2 + - @backstage/config@1.0.1-next.0 + - @backstage/plugin-search-backend-node@0.6.1-next.1 + - @backstage/plugin-search-common@0.3.4-next.0 + - @backstage/plugin-auth-node@0.2.1-next.1 + - @backstage/plugin-permission-common@0.6.1-next.0 + - @backstage/plugin-permission-node@0.6.1-next.1 + +## 0.5.2-next.0 + +### Patch Changes + +- 8cc75993a6: Fixed issue in `PermissionEvaluator` instance check that would cause unexpected "invalid union" errors. +- Updated dependencies + - @backstage/backend-common@0.13.3-next.0 + - @backstage/plugin-auth-node@0.2.1-next.0 + - @backstage/plugin-permission-node@0.6.1-next.0 + - @backstage/plugin-search-backend-node@0.6.1-next.0 + +## 0.5.0 + +### Minor Changes + +- 94ccd772d4: **BREAKING**: The `authorization` property is no longer returned on search results when queried. Note: this will only result in a breaking change if you have custom code in your frontend that relies on the `authorization.resourceRef` property on documents. + +### Patch Changes + +- 30f9884359: Check for non-resource permissions when authorizing result-by-result in AuthorizedSearchEngine. +- 3c8cfaaa80: Use `PermissionEvaluator` instead of `PermissionAuthorizer`, which is now deprecated. +- 62ee65422c: Use new `IndexableResultSet` type as return type of query method in `SearchEngine` implementation. +- c98d271466: Use updated types from `@backstage/plugin-permission-common` +- Updated dependencies + - @backstage/plugin-permission-common@0.6.0 + - @backstage/plugin-permission-node@0.6.0 + - @backstage/plugin-auth-node@0.2.0 + - @backstage/plugin-search-common@0.3.3 + - @backstage/backend-common@0.13.2 + - @backstage/plugin-search-backend-node@0.6.0 + +## 0.5.0-next.2 + +### Patch Changes + +- 3c8cfaaa80: Use `PermissionEvaluator` instead of `PermissionAuthorizer`, which is now deprecated. +- Updated dependencies + - @backstage/plugin-permission-common@0.6.0-next.1 + - @backstage/plugin-permission-node@0.6.0-next.2 + - @backstage/backend-common@0.13.2-next.2 + - @backstage/plugin-search-backend-node@0.6.0-next.1 + +## 0.5.0-next.1 + +### Patch Changes + +- 30f9884359: Check for non-resource permissions when authorizing result-by-result in AuthorizedSearchEngine. +- c98d271466: Use updated types from `@backstage/plugin-permission-common` +- Updated dependencies + - @backstage/plugin-permission-common@0.6.0-next.0 + - @backstage/plugin-permission-node@0.6.0-next.1 + - @backstage/backend-common@0.13.2-next.1 + - @backstage/plugin-search-common@0.3.3-next.1 + +## 0.5.0-next.0 + +### Minor Changes + +- 94ccd772d4: **BREAKING**: The `authorization` property is no longer returned on search results when queried. Note: this will only result in a breaking change if you have custom code in your frontend that relies on the `authorization.resourceRef` property on documents. + +### Patch Changes + +- 62ee65422c: Use new `IndexableResultSet` type as return type of query method in `SearchEngine` implementation. +- Updated dependencies + - @backstage/plugin-auth-node@0.2.0-next.0 + - @backstage/plugin-search-common@0.3.3-next.0 + - @backstage/plugin-search-backend-node@0.5.3-next.0 + - @backstage/backend-common@0.13.2-next.0 + - @backstage/plugin-permission-node@0.5.6-next.0 + +## 0.4.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.1 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/types@1.0.0 + - @backstage/plugin-permission-common@0.5.3 + - @backstage/plugin-search-backend-node@0.5.2 + - @backstage/plugin-auth-node@0.1.6 + - @backstage/plugin-permission-node@0.5.5 + - @backstage/plugin-search-common@0.3.2 + +## 0.4.7 + +### Patch Changes + +- 3e54f6c436: Use `@backstage/plugin-search-common` package instead of `@backstage/search-common`. +- Updated dependencies + - @backstage/backend-common@0.13.0 + - @backstage/plugin-search-common@0.3.1 + - @backstage/plugin-search-backend-node@0.5.1 + - @backstage/plugin-auth-node@0.1.5 + - @backstage/plugin-permission-node@0.5.4 + +## 0.4.7-next.0 + +### Patch Changes + +- 3e54f6c436: Use `@backstage/plugin-search-common` package instead of `@backstage/search-common`. +- Updated dependencies + - @backstage/backend-common@0.13.0-next.0 + - @backstage/plugin-search-common@0.3.1-next.0 + - @backstage/plugin-search-backend-node@0.5.1-next.0 + - @backstage/plugin-auth-node@0.1.5-next.0 + - @backstage/plugin-permission-node@0.5.4-next.0 + +## 0.4.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.12.0 + - @backstage/plugin-permission-common@0.5.2 + - @backstage/plugin-permission-node@0.5.3 + - @backstage/plugin-search-backend-node@0.5.0 + - @backstage/search-common@0.3.0 + - @backstage/plugin-auth-node@0.1.4 + +## 0.4.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.11.0 + - @backstage/plugin-permission-node@0.5.2 + - @backstage/plugin-auth-node@0.1.3 + +## 0.4.4 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/config@0.1.15 + - @backstage/errors@0.2.2 + - @backstage/search-common@0.2.4 + - @backstage/types@0.1.3 + - @backstage/plugin-auth-node@0.1.2 + - @backstage/plugin-permission-common@0.5.1 + - @backstage/plugin-permission-node@0.5.1 + - @backstage/plugin-search-backend-node@0.4.7 + +## 0.4.3 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/errors@0.2.1 + - @backstage/plugin-auth-node@0.1.1 + - @backstage/plugin-permission-common@0.5.0 + - @backstage/config@0.1.14 + - @backstage/search-common@0.2.3 + - @backstage/types@0.1.2 + - @backstage/plugin-permission-node@0.5.0 + - @backstage/plugin-search-backend-node@0.4.6 + +## 0.4.2 + +### Patch Changes + +- b3f3e42036: Use `getBearerTokenFromAuthorizationHeader` from `@backstage/plugin-auth-node` instead of the deprecated `IdentityClient` method. +- Updated dependencies + - @backstage/backend-common@0.10.7 + - @backstage/plugin-auth-node@0.1.0 + - @backstage/plugin-permission-node@0.4.3 + +## 0.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.10.0-next.0 + - @backstage/backend-common@0.10.7-next.0 + - @backstage/plugin-permission-node@0.4.3-next.0 + +## 0.4.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.9.0 + - @backstage/backend-common@0.10.6 + - @backstage/plugin-permission-node@0.4.2 + +## 0.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.9.0-next.1 + - @backstage/backend-common@0.10.6-next.0 + - @backstage/plugin-permission-node@0.4.2-next.1 + +## 0.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.9.0-next.0 + - @backstage/plugin-permission-node@0.4.2-next.0 + +## 0.4.0 + +### Minor Changes + +- bbfbc755aa: **BREAKING** Added three additional required properties to `createRouter` to support filtering search results based on permissions. To make this change to an existing app, add the required parameters to the `createRouter` call in `packages/backend/src/plugins/search.ts`: + + ```diff + export default async function createPlugin({ + logger, + + permissions, + discovery, + config, + tokenManager, + }: PluginEnvironment) { + /* ... */ + + return await createRouter({ + engine: indexBuilder.getSearchEngine(), + + types: indexBuilder.getDocumentTypes(), + + permissions, + + config, + logger, + }); + } + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@0.4.5 + - @backstage/plugin-auth-backend@0.8.0 + - @backstage/search-common@0.2.2 + - @backstage/backend-common@0.10.5 + - @backstage/plugin-permission-node@0.4.1 + +## 0.3.1 + +### Patch Changes + +- cd6854046e: Validate query string in search endpoint +- Updated dependencies + - @backstage/backend-common@0.10.4 + - @backstage/config@0.1.13 + +## 0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.4-next.0 + +## 0.3.0 + +### Minor Changes + +- a41fbfe739: Search result location filtering + + This change introduces a filter for search results based on their location protocol. The intention is to filter out unsafe or + malicious values before they can be consumed by the frontend. By default locations must be http/https URLs (or paths). + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.0 + +## 0.2.8 + +### Patch Changes + +- dcd1a0c3f4: Minor improvement to the API reports, by not unpacking arguments directly +- Updated dependencies + - @backstage/backend-common@0.9.13 + +## 0.2.7 + +### Patch Changes + +- bab752e2b3: Change default port of backend from 7000 to 7007. + + This is due to the AirPlay Receiver process occupying port 7000 and preventing local Backstage instances on MacOS to start. + + You can change the port back to 7000 or any other value by providing an `app-config.yaml` with the following values: + + backend: + listen: 0.0.0.0:7123 + baseUrl: http://localhost:7123 + + More information can be found here: + +- Updated dependencies + - @backstage/backend-common@0.9.11 + +## 0.2.6 + +### Patch Changes + +- a13f21cdc: Implement optional `pageCursor` based paging in search. + + To use paging in your app, add a `` to your + `SearchPage.tsx`. + +- Updated dependencies + - @backstage/search-common@0.2.0 + - @backstage/plugin-search-backend-node@0.4.2 + - @backstage/backend-common@0.9.1 + +## 0.2.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.9.0 + +## 0.2.4 + +### Patch Changes + +- 64baedea5: Improve search query logging message +- Updated dependencies + - @backstage/backend-common@0.8.10 + +## 0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.8.6 + - @backstage/plugin-search-backend-node@0.4.0 + +## 0.2.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.8.5 + - @backstage/plugin-search-backend-node@0.3.0 + +## 0.2.1 + +### Patch Changes + +- 3108ff7bf: Make `yarn dev` respect the `PLUGIN_PORT` environment variable. +- Updated dependencies + - @backstage/plugin-search-backend-node@0.2.1 + - @backstage/backend-common@0.8.3 + +## 0.2.0 + +### Minor Changes + +- 5aff84759: This release represents a move out of a pre-alpha phase of the Backstage Search + plugin, into an alpha phase. With this release, you gain more control over the + layout of your search page on the frontend, as well as the ability to extend + search on the backend to encompass everything Backstage users may want to find. + + If you are updating to version `v0.4.0` of `@backstage/plugin-search` from a + prior release, you will need to make modifications to your app backend. + + First, navigate to your backend package and install the two related search + backend packages: + + ```sh + cd packages/backend + yarn add @backstage/plugin-search-backend @backstage/plugin-search-backend-node + ``` + + Wire up these new packages into your app backend by first creating a new + `search.ts` file at `src/plugins/search.ts` with contents like the following: + + ```typescript + import { useHotCleanup } from '@backstage/backend-common'; + import { createRouter } from '@backstage/plugin-search-backend'; + import { + IndexBuilder, + LunrSearchEngine, + } from '@backstage/plugin-search-backend-node'; + import { PluginEnvironment } from '../types'; + import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; + + export default async function createPlugin({ + logger, + discovery, + }: PluginEnvironment) { + // Initialize a connection to a search engine. + const searchEngine = new LunrSearchEngine({ logger }); + const indexBuilder = new IndexBuilder({ logger, searchEngine }); + + // Collators are responsible for gathering documents known to plugins. This + // particular collator gathers entities from the software catalog. + indexBuilder.addCollator({ + defaultRefreshIntervalSeconds: 600, + collator: new DefaultCatalogCollator({ discovery }), + }); + + // The scheduler controls when documents are gathered from collators and sent + // to the search engine for indexing. + const { scheduler } = await indexBuilder.build(); + + // A 3 second delay gives the backend server a chance to initialize before + // any collators are executed, which may attempt requests against the API. + setTimeout(() => scheduler.start(), 3000); + useHotCleanup(module, () => scheduler.stop()); + + return await createRouter({ + engine: indexBuilder.getSearchEngine(), + logger, + }); + } + ``` + + Then, ensure the search plugin you configured above is initialized by modifying + your backend's `index.ts` file in the following ways: + + ```diff + +import search from './plugins/search'; + // ... + +const searchEnv = useHotMemoize(module, () => createEnv('search')); + // ... + +apiRouter.use('/search', await search(searchEnv)); + // ... + ``` + +### Patch Changes + +- Updated dependencies [92963779b] +- Updated dependencies [db1c8f93b] +- Updated dependencies [5aff84759] +- Updated dependencies [eda9dbd5f] + - @backstage/backend-common@0.8.2 + - @backstage/search-common@0.1.2 + - @backstage/plugin-search-backend-node@0.2.0 + +## 0.1.5 + +### Patch Changes + +- Updated dependencies [22fd8ce2a] +- Updated dependencies [f9fb4a205] + - @backstage/backend-common@0.8.0 + +## 0.1.4 + +### Patch Changes + +- Updated dependencies [e0bfd3d44] +- Updated dependencies [38ca05168] + - @backstage/backend-common@0.7.0 + +## 0.1.3 + +### Patch Changes + +- b9b2b4b76: Lunr Search Engine support +- Updated dependencies [b9b2b4b76] +- Updated dependencies [d367f63b5] +- Updated dependencies [b42531cfe] + - @backstage/plugin-search-backend-node@0.1.3 + - @backstage/backend-common@0.6.3 + +## 0.1.2 + +### Patch Changes + +- 4d248725e: Bump to use the in-repo latest `backend-common`, and the correct version of `express-promise-router` + +## @backstage/backend-plugin-api@1.7.0-next.1 + +### Minor Changes + +- bb9b471: Plugin IDs that do not match the standard format are deprecated (letters, digits, and dashes only, starting with a letter). Plugin IDs that do no match this format will be rejected in a future release. + + In addition, plugin IDs that don't match the legacy pattern that also allows underscores, with be rejected. + +## @backstage/frontend-app-api@0.15.0-next.1 + +### Minor Changes + +- 55b2ef6: **BREAKING**: Updated the behavior of the new API override logic to reject the override and block app startup instead of just logging a deprecation warning. + +### Patch Changes + +- 09032d7: Internal update to simplify testing utility implementations. +- Updated dependencies + - @backstage/frontend-defaults@0.4.0-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.1 + +## @backstage/frontend-defaults@0.4.0-next.1 + +### Minor Changes + +- 55b2ef6: **BREAKING**: The `API_FACTORY_CONFLICT` warning is now treated as an error and will prevent the app from starting. + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.15.0-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/plugin-app@0.4.0-next.1 + - @backstage/core-components@0.18.7-next.1 + +## @backstage/frontend-plugin-api@0.14.0-next.1 + +### Minor Changes + +- bb9b471: Plugin IDs that do not match the standard format are deprecated (letters, digits, and dashes only, starting with a letter). Plugin IDs that do no match this format will be rejected in a future release. +- 10ebed4: **BREAKING**: Removed type support for multiple attachment points in the `ExtensionDefinitionAttachTo` type. Extensions can no longer specify an array of attachment points in the `attachTo` property. + + The runtime still supports multiple attachment points for backward compatibility with existing compiled code, but new code will receive type errors if attempting to use this pattern. + + Extensions that previously used multiple attachment points should migrate to using a Utility API pattern instead. See the [Sharing Extensions Across Multiple Locations](https://backstage.io/docs/frontend-system/architecture/27-sharing-extensions) guide for the recommended approach. + +## @backstage/integration@1.20.0-next.1 + +### Minor Changes + +- 6999f6d: The AzureUrl class in the @backstage/integration package is now able to process BOTH git branches and git tags. Initially this class only processed git branches and threw an error when non-branch Azure URLs were passed in. + +## @backstage/ui@0.12.0-next.1 + +### Minor Changes + +- 058ffd9: **BREAKING**: Removed `large` size variant from Button component as it was never implemented. + + **Migration:** + + ```diff + - + + + ``` + + **Affected components:** Button + +- 110fec0: **BREAKING**: Removed link and tint color tokens, added new status foreground tokens, and improved Link component styling + + The following color tokens have been removed: + + - `--bui-fg-link` (and all related tokens: `-hover`, `-pressed`, `-disabled`) + - `--bui-fg-tint` (and all related tokens: `-hover`, `-pressed`, `-disabled`) + - `--bui-bg-tint` (and all related tokens: `-hover`, `-pressed`, `-disabled`) + - `--bui-border-tint` (and all related tokens) + + **New Status Tokens:** + + Added dedicated tokens for status colors that distinguish between usage on status backgrounds vs. standalone usage: + + - `--bui-fg-danger-on-bg` / `--bui-fg-danger` + - `--bui-fg-warning-on-bg` / `--bui-fg-warning` + - `--bui-fg-success-on-bg` / `--bui-fg-success` + - `--bui-fg-info-on-bg` / `--bui-fg-info` + + The `-on-bg` variants are designed for text on colored backgrounds, while the base variants are for standalone status indicators with improved visibility and contrast. + + **Migration:** + + For link colors, migrate to one of the following alternatives: + + ```diff + .custom-link { + - color: var(--bui-fg-link); + + color: var(--bui-fg-info); /* For informational links */ + + /* or */ + + color: var(--bui-fg-primary); /* For standard text links */ + } + ``` + + For tint colors (backgrounds, foregrounds, borders), migrate to appropriate status or neutral colors: + + ```diff + .info-section { + - background: var(--bui-bg-tint); + + background: var(--bui-bg-info); /* For informational sections */ + + /* or */ + + background: var(--bui-bg-neutral-on-surface-0); /* For neutral emphasis */ + } + ``` + + If you're using status foreground colors on colored backgrounds, update to the new `-on-bg` tokens: + + ```diff + .error-badge { + - color: var(--bui-fg-danger); + + color: var(--bui-fg-danger-on-bg); + background: var(--bui-bg-danger); + } + ``` + + **Affected components:** Link + +### Patch Changes + +- 4d1b7f4: Fixed CSS Module syntax to comply with Next.js 16 Turbopack validation by flattening nested dark theme selectors. + + **Affected components:** Popover, Tooltip + +- 2c219b9: Added `destructive` prop to Button for dangerous actions like delete or remove. Works with all variants (primary, secondary, tertiary). + + **Affected components:** Button + +- 5af9e14: Fixed `useDefinition` hook adding literal "undefined" class name when no className prop was passed. + +- 74c5a76: Fixed Switch component disabled state styling to show `not-allowed` cursor and disabled text color. + + **Affected components:** Switch + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.12-next.0 + +# @backstage/plugin-auth-backend-module-atlassian-provider + +## 0.4.11-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + +## 0.4.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.4.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 0.4.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + +## 0.4.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## 0.4.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## 0.4.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.9-next.0 + - @backstage/backend-plugin-api@1.4.5-next.0 + +## 0.4.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4 + - @backstage/plugin-auth-node@0.6.8 + +## 0.4.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## 0.4.7 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.7 + - @backstage/backend-plugin-api@1.4.3 + +## 0.4.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.7-next.0 + - @backstage/backend-plugin-api@1.4.3-next.0 + +## 0.4.6 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6 + - @backstage/backend-plugin-api@1.4.2 + +## 0.4.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + +## 0.4.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.4.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + +## 0.4.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.4.3 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## 0.4.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + +## 0.4.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + +## 0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.0 + - @backstage/plugin-auth-node@0.6.3-next.0 + +## 0.4.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.0 + - @backstage/plugin-auth-node@0.6.2 + +## 0.4.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1 + - @backstage/backend-plugin-api@1.2.1 + +## 0.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1-next.1 + - @backstage/backend-plugin-api@1.2.1-next.1 + +## 0.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1-next.0 + - @backstage/backend-plugin-api@1.2.1-next.0 + +## 0.4.0 + +### Minor Changes + +- 61f464e: Added `auth.providers..sessionDuration` config for auth providers to allow the lifespan of user sessions to be configured. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0 + - @backstage/plugin-auth-node@0.6.0 + +## 0.4.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.2 + - @backstage/plugin-auth-node@0.6.0-next.2 + +## 0.4.0-next.1 + +### Minor Changes + +- 61f464e: Added `auth.providers..sessionDuration` config for auth providers to allow the lifespan of user sessions to be configured. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.1 + - @backstage/plugin-auth-node@0.6.0-next.1 + +## 0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.0 + - @backstage/plugin-auth-node@0.5.7-next.0 + +## 0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.6 + - @backstage/backend-plugin-api@1.1.1 + +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.1-next.1 + - @backstage/plugin-auth-node@0.5.6-next.1 + +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.6-next.0 + - @backstage/backend-plugin-api@1.1.1-next.0 + +## 0.3.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.5 + - @backstage/backend-plugin-api@1.1.0 + +## 0.3.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.0-next.2 + - @backstage/plugin-auth-node@0.5.5-next.2 + +## 0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.5-next.1 + - @backstage/backend-plugin-api@1.1.0-next.1 + +## 0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.3-next.0 + - @backstage/plugin-auth-node@0.5.5-next.0 + +## 0.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4 + - @backstage/backend-plugin-api@1.0.2 + +## 0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4-next.2 + - @backstage/backend-plugin-api@1.0.2-next.2 + +## 0.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.2-next.1 + - @backstage/plugin-auth-node@0.5.4-next.1 + +## 0.3.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4-next.0 + - @backstage/backend-plugin-api@1.0.2-next.0 + +## 0.3.1 + +### Patch Changes + +- 217458a: Updated configuration schema to include the new `allowedDomains` option for the `emailLocalPartMatchingUserEntityName` sign-in resolver. +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3 + - @backstage/backend-plugin-api@1.0.1 + +## 0.3.1-next.1 + +### Patch Changes + +- 217458a: Updated configuration schema to include the new `allowedDomains` option for the `emailLocalPartMatchingUserEntityName` sign-in resolver. +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3-next.1 + - @backstage/backend-plugin-api@1.0.1-next.1 + +## 0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3-next.0 + - @backstage/backend-plugin-api@1.0.1-next.0 + +## 0.3.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0 + - @backstage/plugin-auth-node@0.5.2 + +## 0.3.0-next.2 + +### Patch Changes + +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + +## 0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + +## 0.3.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + +## 0.2.4 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + +## 0.2.4-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## 0.2.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + +## 0.2.4-next.1 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## 0.2.3 + +### Patch Changes + +- b9832ae: Fix several issues with the Atlassian auth provider (type definition, profile url, profile transformation, scopes) +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-auth-node@0.4.17 + +## 0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## 0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.21-next.0 + - @backstage/plugin-auth-node@0.4.16-next.0 + +## 0.2.0 + +### Minor Changes + +- 8efc6cf: **BREAKING**: The `scope` and `scopes` config options have been removed and replaced by the standard `additionalScopes` config. In addition, the `offline_access`, `read:jira-work`, and `read:jira-user` scopes have been set to required and will always be present. + +### Patch Changes + +- 78a0b08: Internal refactor to handle `BackendFeature` contract change. +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19 + - @backstage/plugin-auth-node@0.4.14 + +## 0.2.0-next.2 + +### Minor Changes + +- 8efc6cf: **BREAKING**: The `scope` and `scopes` config options have been removed and replaced by the standard `additionalScopes` config. In addition, the `offline_access`, `read:jira-work`, and `read:jira-user` scopes have been set to required and will always be present. + +### Patch Changes + +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.3 + - @backstage/plugin-auth-node@0.4.14-next.3 + +## 0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.2 + - @backstage/plugin-auth-node@0.4.14-next.2 + +## 0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + +## 0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + +## 0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## 0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + +## 0.1.9 + +### Patch Changes + +- 269b4c1: Read scopes from config and pass to AtlassianProvider as they are required +- Updated dependencies + - @backstage/backend-plugin-api@0.6.17 + - @backstage/plugin-auth-node@0.4.12 + +## 0.1.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.17-next.1 + - @backstage/plugin-auth-node@0.4.12-next.1 + +## 0.1.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.17-next.0 + - @backstage/plugin-auth-node@0.4.12-next.0 + +## 0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.11 + - @backstage/backend-plugin-api@0.6.16 + +## 0.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.10 + - @backstage/backend-plugin-api@0.6.15 + +## 0.1.6 + +### Patch Changes + +- c511186: Addressed OAuth flow issue in Atlassian auth module +- Updated dependencies + - @backstage/plugin-auth-node@0.4.9 + - @backstage/backend-plugin-api@0.6.14 + +## 0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.9-next.2 + - @backstage/backend-plugin-api@0.6.14-next.2 + +## 0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.14-next.1 + - @backstage/plugin-auth-node@0.4.9-next.1 + +## 0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.8-next.0 + - @backstage/backend-plugin-api@0.6.13-next.0 + +## 0.1.2 + +### Patch Changes + +- 8472188: Added or fixed the `repository` field in `package.json`. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4 + - @backstage/backend-plugin-api@0.6.10 + +## 0.1.2-next.3 + +### Patch Changes + +- 8472188: Added or fixed the `repository` field in `package.json`. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.3 + - @backstage/backend-plugin-api@0.6.10-next.3 + +## 0.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.2 + - @backstage/plugin-auth-node@0.4.4-next.2 + +## 0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.1 + - @backstage/plugin-auth-node@0.4.4-next.1 + +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + +## 0.1.1 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + +## 0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + +## 0.1.1-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## 0.1.0 + +### Minor Changes + +- 2a5891e: New module for `@backstage/plugin-auth-backend` that adds an atlassian auth provider + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + +## 0.1.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## 0.1.0-next.2 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + +## 0.1.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + +## 0.1.0-next.0 + +### Minor Changes + +- 2a5891e816: New module for `@backstage/plugin-auth-backend` that adds an atlassian auth provider + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.0 + - @backstage/plugin-auth-node@0.4.2-next.0 + - @backstage/backend-plugin-api@0.6.8-next.0 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.13-next.0 + +# @backstage/plugin-auth-backend-module-aws-alb-provider + +## 0.4.12-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-backend@0.26.1-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/errors@1.2.7 + +## 0.4.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-auth-backend@0.26.0 + - @backstage/plugin-auth-node@0.6.11 + +## 0.4.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.26.0-next.0 + - @backstage/plugin-auth-node@0.6.10 + +## 0.4.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-auth-backend@0.25.7 + - @backstage/backend-plugin-api@1.6.0 + +## 0.4.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.7-next.1 + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/errors@1.2.7 + +## 0.4.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-auth-backend@0.25.7-next.0 + - @backstage/errors@1.2.7 + +## 0.4.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-backend@0.25.6 + - @backstage/plugin-auth-node@0.6.9 + +## 0.4.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-backend@0.25.6-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## 0.4.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.6-next.0 + - @backstage/plugin-auth-node@0.6.9-next.0 + - @backstage/backend-plugin-api@1.4.5-next.0 + - @backstage/errors@1.2.7 + +## 0.4.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4 + - @backstage/plugin-auth-backend@0.25.5 + - @backstage/plugin-auth-node@0.6.8 + +## 0.4.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-backend@0.25.5-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## 0.4.7 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.4 + - @backstage/plugin-auth-node@0.6.7 + - @backstage/backend-plugin-api@1.4.3 + +## 0.4.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.7-next.0 + - @backstage/backend-plugin-api@1.4.3-next.0 + - @backstage/plugin-auth-backend@0.25.4-next.0 + +## 0.4.6 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.3 + - @backstage/plugin-auth-node@0.6.6 + - @backstage/backend-plugin-api@1.4.2 + +## 0.4.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.3-next.0 + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/errors@1.2.7 + +## 0.4.5 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.2 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-backend@0.25.2-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.4.4 + +### Patch Changes + +- c83cd8b: Fixed some circular or otherwise unclear imports +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-auth-backend@0.25.1 + +## 0.4.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/plugin-auth-backend@0.25.1-next.1 + +## 0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-backend@0.25.1-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.4.3 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.0 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/errors@1.2.7 + +## 0.4.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.0-next.2 + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/errors@1.2.7 + +## 0.4.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.0-next.1 + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + +## 0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.0-next.0 + - @backstage/backend-plugin-api@1.3.1-next.0 + - @backstage/plugin-auth-node@0.6.3-next.0 + - @backstage/errors@1.2.7 + +## 0.4.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.24.5 + - @backstage/backend-plugin-api@1.3.0 + - @backstage/plugin-auth-node@0.6.2 + - @backstage/errors@1.2.7 + +## 0.4.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.24.5-next.2 + - @backstage/backend-plugin-api@1.2.1 + - @backstage/errors@1.2.7 + - @backstage/plugin-auth-node@0.6.1 + +## 0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.24.5-next.1 + - @backstage/plugin-auth-node@0.6.1 + - @backstage/backend-plugin-api@1.2.1 + - @backstage/errors@1.2.7 + +## 0.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.24.5-next.0 + - @backstage/plugin-auth-node@0.6.1 + - @backstage/backend-plugin-api@1.2.1 + - @backstage/errors@1.2.7 + +## 0.4.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1 + - @backstage/plugin-auth-backend@0.24.4 + - @backstage/backend-plugin-api@1.2.1 + - @backstage/errors@1.2.7 + +## 0.4.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.1-next.1 + - @backstage/errors@1.2.7 + - @backstage/plugin-auth-backend@0.24.4-next.2 + - @backstage/plugin-auth-node@0.6.1-next.1 + +## 0.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1-next.1 + - @backstage/plugin-auth-backend@0.24.4-next.1 + - @backstage/backend-plugin-api@1.2.1-next.1 + - @backstage/errors@1.2.7 + +## 0.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.24.4-next.0 + - @backstage/plugin-auth-node@0.6.1-next.0 + - @backstage/backend-plugin-api@1.2.1-next.0 + +## 0.4.0 + +### Minor Changes + +- 61f464e: Added `auth.providers..sessionDuration` config for auth providers to allow the lifespan of user sessions to be configured. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0 + - @backstage/plugin-auth-node@0.6.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-auth-backend@0.24.3 + +## 0.4.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.2 + - @backstage/plugin-auth-backend@0.24.3-next.3 + - @backstage/plugin-auth-node@0.6.0-next.2 + - @backstage/errors@1.2.7 + +## 0.4.0-next.2 + +### Minor Changes + +- 61f464e: Added `auth.providers..sessionDuration` config for auth providers to allow the lifespan of user sessions to be configured. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.1 + - @backstage/plugin-auth-node@0.6.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/plugin-auth-backend@0.24.3-next.2 + +## 0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-auth-backend@0.24.3-next.1 + - @backstage/plugin-auth-node@0.5.7-next.0 + +## 0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-auth-backend@0.24.3-next.0 + - @backstage/plugin-auth-node@0.5.7-next.0 + +## 0.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.6 + - @backstage/plugin-auth-backend@0.24.2 + - @backstage/backend-plugin-api@1.1.1 + - @backstage/errors@1.2.7 + +## 0.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.1-next.1 + - @backstage/errors@1.2.7-next.0 + - @backstage/plugin-auth-backend@0.24.2-next.1 + - @backstage/plugin-auth-node@0.5.6-next.1 + +## 0.3.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.6-next.0 + - @backstage/plugin-auth-backend@0.24.2-next.0 + - @backstage/backend-plugin-api@1.1.1-next.0 + - @backstage/errors@1.2.6 + +## 0.3.1 + +### Patch Changes + +- 5c9cc05: Use native fetch instead of node-fetch +- Updated dependencies + - @backstage/plugin-auth-backend@0.24.1 + - @backstage/plugin-auth-node@0.5.5 + - @backstage/backend-plugin-api@1.1.0 + - @backstage/errors@1.2.6 + +## 0.3.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.0-next.2 + - @backstage/errors@1.2.6-next.0 + - @backstage/plugin-auth-backend@0.24.1-next.2 + - @backstage/plugin-auth-node@0.5.5-next.2 + +## 0.3.1-next.1 + +### Patch Changes + +- 5c9cc05: Use native fetch instead of node-fetch +- Updated dependencies + - @backstage/plugin-auth-backend@0.24.1-next.1 + - @backstage/plugin-auth-node@0.5.5-next.1 + - @backstage/backend-plugin-api@1.1.0-next.1 + - @backstage/errors@1.2.5 + +## 0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.3-next.0 + - @backstage/plugin-auth-node@0.5.5-next.0 + - @backstage/errors@1.2.5 + - @backstage/plugin-auth-backend@0.24.1-next.0 + +## 0.3.0 + +### Minor Changes + +- 75168e3: **BREAKING**: The AWS ALB `fullProfile` will no longer have the its username or email converted to lowercase. This is to ensure unique handling of the users. You may need to update and configure a custom sign-in resolver or profile transform as a result. + +### Patch Changes + +- 5d74716: Remove unused backend-common dependency +- Updated dependencies + - @backstage/plugin-auth-backend@0.24.0 + - @backstage/plugin-auth-node@0.5.4 + - @backstage/backend-plugin-api@1.0.2 + - @backstage/errors@1.2.5 + +## 0.3.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.24.0-next.2 + - @backstage/plugin-auth-node@0.5.4-next.2 + - @backstage/backend-plugin-api@1.0.2-next.2 + - @backstage/errors@1.2.4 + +## 0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.2-next.1 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-backend@0.24.0-next.1 + - @backstage/plugin-auth-node@0.5.4-next.1 + +## 0.3.0-next.0 + +### Minor Changes + +- 75168e3: **BREAKING**: The AWS ALB `fullProfile` will no longer have the its username or email converted to lowercase. This is to ensure unique handling of the users. You may need to update and configure a custom sign-in resolver or profile transform as a result. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.24.0-next.0 + - @backstage/plugin-auth-node@0.5.4-next.0 + - @backstage/backend-plugin-api@1.0.2-next.0 + - @backstage/errors@1.2.4 + +## 0.2.1 + +### Patch Changes + +- 217458a: Updated configuration schema to include the new `allowedDomains` option for the `emailLocalPartMatchingUserEntityName` sign-in resolver. +- 094eaa3: Remove references to in-repo backend-common +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3 + - @backstage/plugin-auth-backend@0.23.1 + - @backstage/backend-plugin-api@1.0.1 + - @backstage/errors@1.2.4 + +## 0.2.1-next.1 + +### Patch Changes + +- 217458a: Updated configuration schema to include the new `allowedDomains` option for the `emailLocalPartMatchingUserEntityName` sign-in resolver. +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3-next.1 + - @backstage/backend-plugin-api@1.0.1-next.1 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-backend@0.23.1-next.1 + +## 0.2.1-next.0 + +### Patch Changes + +- 094eaa3: Remove references to in-repo backend-common +- Updated dependencies + - @backstage/plugin-auth-backend@0.23.1-next.0 + - @backstage/plugin-auth-node@0.5.3-next.0 + - @backstage/backend-plugin-api@1.0.1-next.0 + - @backstage/errors@1.2.4 + +## 0.2.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- ecbc47e: Fix a bug where the signer was checked from the payload instead of the header +- 8d1fb8d: Throw correct error when email is missing from the claims +- Updated dependencies + - @backstage/backend-common@0.25.0 + - @backstage/backend-plugin-api@1.0.0 + - @backstage/plugin-auth-node@0.5.2 + - @backstage/plugin-auth-backend@0.23.0 + - @backstage/errors@1.2.4 + +## 0.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.2 + - @backstage/plugin-auth-node@0.5.2-next.2 + - @backstage/plugin-auth-backend@0.23.0-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/errors@1.2.4 + +## 0.2.0-next.1 + +### Patch Changes + +- 8d1fb8d: Throw correct error when email is missing from the claims +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-backend@0.23.0-next.1 + +## 0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- ecbc47e: Fix a bug where the signer was checked from the payload instead of the header +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-backend@0.23.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/errors@1.2.4 + +## 0.1.15 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema + +- 4ea354f: Added a `signer` configuration option to validate against the token claims. We strongly recommend that you set this value (typically on the format `arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/my-load-balancer/1234567890123456`) to ensure that the auth provider can safely check the authenticity of any incoming tokens. + + Example: + + ```diff + auth: + providers: + awsalb: + # this is the URL of the IdP you configured + issuer: 'https://example.okta.com/oauth2/default' + # this is the ARN of your ALB instance + + signer: 'arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/my-load-balancer/1234567890123456' + # this is the region where your ALB instance resides + region: 'us-west-2' + signIn: + resolvers: + # typically you would pick one of these + - resolver: emailMatchingUserEntityProfileEmail + - resolver: emailLocalPartMatchingUserEntityName + ``` + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-auth-backend@0.22.10 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/errors@1.2.4 + +## 0.1.15-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-backend@0.22.10-next.3 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## 0.1.15-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-auth-backend@0.22.10-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/errors@1.2.4 + +## 0.1.15-next.1 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-auth-backend@0.22.10-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## 0.1.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-backend@0.22.10-next.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## 0.1.14 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/plugin-auth-backend@0.22.9 + - @backstage/errors@1.2.4 + +## 0.1.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-backend@0.22.9-next.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## 0.1.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.21-next.0 + - @backstage/backend-common@0.23.2-next.0 + - @backstage/plugin-auth-backend@0.22.8-next.0 + - @backstage/plugin-auth-node@0.4.16-next.0 + - @backstage/errors@1.2.4 + +## 0.1.11 + +### Patch Changes + +- 78a0b08: Internal refactor to handle `BackendFeature` contract change. +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/backend-common@0.23.0 + - @backstage/backend-plugin-api@0.6.19 + - @backstage/plugin-auth-node@0.4.14 + - @backstage/plugin-auth-backend@0.22.6 + - @backstage/errors@1.2.4 + +## 0.1.11-next.3 + +### Patch Changes + +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.3 + - @backstage/plugin-auth-node@0.4.14-next.3 + - @backstage/plugin-auth-backend@0.22.6-next.3 + - @backstage/backend-common@0.23.0-next.3 + - @backstage/errors@1.2.4 + +## 0.1.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.2 + - @backstage/backend-common@0.23.0-next.2 + - @backstage/plugin-auth-backend@0.22.6-next.2 + - @backstage/plugin-auth-node@0.4.14-next.2 + - @backstage/errors@1.2.4 + +## 0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-backend@0.22.6-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + +## 0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-auth-backend@0.22.6-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/errors@1.2.4 + +## 0.1.10 + +### Patch Changes + +- 4a0577e: fix: Move config declarations to appropriate auth backend modules +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-backend@0.22.5 + - @backstage/plugin-auth-node@0.4.13 + +## 0.1.10-next.2 + +### Patch Changes + +- 4a0577e: fix: Move config declarations to appropriate auth backend modules +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-auth-backend@0.22.5-next.2 + +## 0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-backend@0.22.5-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## 0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.22.5-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/errors@1.2.4 + +## 0.1.9 + +### Patch Changes + +- f286d59: Added support for AWS GovCloud (US) regions + +- 30f5a51: Added `authModuleAwsAlbProvider` as a default export. + + It can now be used like this in your backend: `backend.add(import('@backstage/plugin-auth-backend-module-aws-alb-provider'));` + +- Updated dependencies + - @backstage/backend-common@0.21.7 + - @backstage/plugin-auth-backend@0.22.4 + - @backstage/backend-plugin-api@0.6.17 + - @backstage/plugin-auth-node@0.4.12 + - @backstage/errors@1.2.4 + +## 0.1.9-next.1 + +### Patch Changes + +- 30f5a51: Added `authModuleAwsAlbProvider` as a default export. + + It can now be used like this in your backend: `backend.add(import('@backstage/plugin-auth-backend-module-aws-alb-provider'));` + +- Updated dependencies + - @backstage/backend-common@0.21.7-next.1 + - @backstage/plugin-auth-backend@0.22.4-next.1 + - @backstage/backend-plugin-api@0.6.17-next.1 + - @backstage/plugin-auth-node@0.4.12-next.1 + - @backstage/errors@1.2.4 + +## 0.1.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.7-next.0 + - @backstage/backend-plugin-api@0.6.17-next.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-backend@0.22.4-next.0 + - @backstage/plugin-auth-node@0.4.12-next.0 + +## 0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.22.3 + - @backstage/plugin-auth-node@0.4.11 + - @backstage/backend-common@0.21.6 + - @backstage/backend-plugin-api@0.6.16 + - @backstage/errors@1.2.4 + +## 0.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.5 + - @backstage/plugin-auth-backend@0.22.2 + - @backstage/plugin-auth-node@0.4.10 + - @backstage/backend-plugin-api@0.6.15 + - @backstage/errors@1.2.4 + +## 0.1.6 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.22.1 + +## 0.1.5 + +### Patch Changes + +- 2af5354: Bump dependency `jose` to v5 +- b1b012d: Fix issue with `providerInfo` not being set properly for some proxy providers, by making `providerInfo` an explicit optional return from `authenticate` +- Updated dependencies + - @backstage/backend-common@0.21.4 + - @backstage/plugin-auth-node@0.4.9 + - @backstage/errors@1.2.4 + - @backstage/backend-plugin-api@0.6.14 + - @backstage/plugin-auth-backend@0.22.0 + +## 0.1.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.4-next.2 + - @backstage/plugin-auth-backend@0.22.0-next.2 + - @backstage/plugin-auth-node@0.4.9-next.2 + - @backstage/backend-plugin-api@0.6.14-next.2 + - @backstage/errors@1.2.4-next.0 + +## 0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.4-next.1 + - @backstage/backend-plugin-api@0.6.14-next.1 + - @backstage/plugin-auth-backend@0.22.0-next.1 + - @backstage/plugin-auth-node@0.4.9-next.1 + - @backstage/errors@1.2.4-next.0 + +## 0.1.4-next.0 + +### Patch Changes + +- 2af5354: Bump dependency `jose` to v5 +- b1b012d: Fix issue with `providerInfo` not being set properly for some proxy providers, by making `providerInfo` an explicit optional return from `authenticate` +- Updated dependencies + - @backstage/backend-common@0.21.3-next.0 + - @backstage/plugin-auth-node@0.4.8-next.0 + - @backstage/errors@1.2.4-next.0 + - @backstage/backend-plugin-api@0.6.13-next.0 + - @backstage/plugin-auth-backend@0.22.0-next.0 + +## 0.1.0 + +### Minor Changes + +- 23a98f8: Migrated the AWS ALB auth provider to new `@backstage/plugin-auth-backend-module-aws-alb-provider` module package. + +### Patch Changes + +- d309cad: Refactored to use the `jose` library for JWT handling. +- Updated dependencies + - @backstage/backend-common@0.21.0 + - @backstage/plugin-auth-backend@0.21.0 + - @backstage/plugin-auth-node@0.4.4 + - @backstage/backend-plugin-api@0.6.10 + - @backstage/errors@1.2.3 + +## 0.1.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.3 + - @backstage/plugin-auth-backend@0.21.0-next.3 + - @backstage/plugin-auth-node@0.4.4-next.3 + - @backstage/backend-plugin-api@0.6.10-next.3 + - @backstage/errors@1.2.3 + +## 0.1.0-next.1 + +### Patch Changes + +- d309cad: Refactored to use the `jose` library for JWT handling. +- Updated dependencies + - @backstage/plugin-auth-backend@0.21.0-next.2 + - @backstage/backend-common@0.21.0-next.2 + - @backstage/backend-plugin-api@0.6.10-next.2 + - @backstage/plugin-auth-node@0.4.4-next.2 + - @backstage/errors@1.2.3 + +## 0.1.0-next.0 + +### Minor Changes + +- 23a98f8: Migrated the AWS ALB auth provider to new `@backstage/plugin-auth-backend-module-aws-alb-provider` module package. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.1 + - @backstage/backend-common@0.21.0-next.1 + - @backstage/plugin-auth-backend@0.20.4-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.4-next.1 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.17-next.0 + +# @backstage/plugin-auth-backend-module-azure-easyauth-provider + +## 0.2.16-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + +## 0.2.15 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.2.15-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + +## 0.2.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + +## 0.2.14 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/catalog-model@1.7.6 + +## 0.2.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## 0.2.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.9-next.0 + - @backstage/catalog-model@1.7.6-next.0 + - @backstage/backend-plugin-api@1.4.5-next.0 + - @backstage/errors@1.2.7 + +## 0.2.13 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4 + - @backstage/plugin-auth-node@0.6.8 + +## 0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## 0.2.12 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.7 + - @backstage/backend-plugin-api@1.4.3 + +## 0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.7-next.0 + - @backstage/backend-plugin-api@1.4.3-next.0 + +## 0.2.11 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6 + - @backstage/backend-plugin-api@1.4.2 + +## 0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/errors@1.2.7 + +## 0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/catalog-model@1.7.4 + - @backstage/errors@1.2.7 + +## 0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/errors@1.2.7 + +## 0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.2.8 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/errors@1.2.7 + +## 0.2.8-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + +## 0.2.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + +## 0.2.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.0 + - @backstage/plugin-auth-node@0.6.3-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + +## 0.2.7 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.0 + - @backstage/plugin-auth-node@0.6.2 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + +## 0.2.6 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1 + - @backstage/backend-plugin-api@1.2.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + +## 0.2.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1-next.1 + - @backstage/backend-plugin-api@1.2.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + +## 0.2.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1-next.0 + - @backstage/backend-plugin-api@1.2.1-next.0 + +## 0.2.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0 + - @backstage/plugin-auth-node@0.6.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + +## 0.2.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.2 + - @backstage/plugin-auth-node@0.6.0-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + +## 0.2.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.1 + - @backstage/plugin-auth-node@0.6.0-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + +## 0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-auth-node@0.5.7-next.0 + +## 0.2.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.6 + - @backstage/backend-plugin-api@1.1.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + +## 0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.1-next.1 + - @backstage/catalog-model@1.7.3-next.0 + - @backstage/errors@1.2.7-next.0 + - @backstage/plugin-auth-node@0.5.6-next.1 + +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.6-next.0 + - @backstage/backend-plugin-api@1.1.1-next.0 + - @backstage/catalog-model@1.7.2 + - @backstage/errors@1.2.6 + +## 0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.5 + - @backstage/backend-plugin-api@1.1.0 + - @backstage/errors@1.2.6 + - @backstage/catalog-model@1.7.2 + +## 0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.0-next.2 + - @backstage/errors@1.2.6-next.0 + - @backstage/plugin-auth-node@0.5.5-next.2 + - @backstage/catalog-model@1.7.2-next.0 + +## 0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.5-next.1 + - @backstage/backend-plugin-api@1.1.0-next.1 + - @backstage/catalog-model@1.7.1 + - @backstage/errors@1.2.5 + +## 0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.3-next.0 + - @backstage/plugin-auth-node@0.5.5-next.0 + - @backstage/catalog-model@1.7.1 + - @backstage/errors@1.2.5 + +## 0.2.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4 + - @backstage/backend-plugin-api@1.0.2 + - @backstage/catalog-model@1.7.1 + - @backstage/errors@1.2.5 + +## 0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4-next.2 + - @backstage/backend-plugin-api@1.0.2-next.2 + - @backstage/catalog-model@1.7.0 + - @backstage/errors@1.2.4 + +## 0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.2-next.1 + - @backstage/catalog-model@1.7.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.5.4-next.1 + +## 0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4-next.0 + - @backstage/backend-plugin-api@1.0.2-next.0 + - @backstage/catalog-model@1.7.0 + - @backstage/errors@1.2.4 + +## 0.2.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3 + - @backstage/backend-plugin-api@1.0.1 + - @backstage/catalog-model@1.7.0 + - @backstage/errors@1.2.4 + +## 0.2.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3-next.1 + - @backstage/backend-plugin-api@1.0.1-next.1 + - @backstage/catalog-model@1.7.0 + - @backstage/errors@1.2.4 + +## 0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3-next.0 + - @backstage/backend-plugin-api@1.0.1-next.0 + - @backstage/catalog-model@1.7.0 + - @backstage/errors@1.2.4 + +## 0.2.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0 + - @backstage/plugin-auth-node@0.5.2 + - @backstage/catalog-model@1.7.0 + - @backstage/errors@1.2.4 + +## 0.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + +## 0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + +## 0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + +## 0.1.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + +## 0.1.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## 0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + +## 0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## 0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## 0.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + +## 0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.21-next.0 + - @backstage/plugin-auth-node@0.4.16-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + +## 0.1.2 + +### Patch Changes + +- 78a0b08: Internal refactor to handle `BackendFeature` contract change. +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19 + - @backstage/plugin-auth-node@0.4.14 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + +## 0.1.2-next.2 + +### Patch Changes + +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.3 + - @backstage/plugin-auth-node@0.4.14-next.3 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + +## 0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.2 + - @backstage/plugin-auth-node@0.4.14-next.2 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + +## 0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## 0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/errors@1.2.4 + +## 0.1.0 + +### Minor Changes + +- 06a6725: New auth backend module to add `azure-easyauth` provider. Note that as part of this change the default provider ID has been changed from `easyAuth` to `azureEasyAuth`, which means that if you switch to this new module you need to update your app config as well as the `provider` prop of the `ProxiedSignInPage` in the frontend. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.17 + - @backstage/plugin-auth-node@0.4.12 + - @backstage/catalog-model@1.4.5 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.12-next.0 + +# @backstage/plugin-auth-backend-module-bitbucket-provider + +## 0.3.11-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + +## 0.3.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.3.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + +## 0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## 0.3.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## 0.3.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.9-next.0 + - @backstage/backend-plugin-api@1.4.5-next.0 + +## 0.3.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4 + - @backstage/plugin-auth-node@0.6.8 + +## 0.3.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## 0.3.7 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.7 + - @backstage/backend-plugin-api@1.4.3 + +## 0.3.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.7-next.0 + - @backstage/backend-plugin-api@1.4.3-next.0 + +## 0.3.6 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6 + - @backstage/backend-plugin-api@1.4.2 + +## 0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + +## 0.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.3.3 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## 0.3.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + +## 0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + +## 0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.0 + - @backstage/plugin-auth-node@0.6.3-next.0 + +## 0.3.2 + +### Patch Changes + +- 5d10f99: Enabled persistency of scopes for Bitbucket Cloud. +- Updated dependencies + - @backstage/backend-plugin-api@1.3.0 + - @backstage/plugin-auth-node@0.6.2 + +## 0.3.2-next.0 + +### Patch Changes + +- 5d10f99: Enabled persistency of scopes for Bitbucket Cloud. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1 + - @backstage/backend-plugin-api@1.2.1 + +## 0.3.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1 + - @backstage/backend-plugin-api@1.2.1 + +## 0.3.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1-next.1 + - @backstage/backend-plugin-api@1.2.1-next.1 + +## 0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1-next.0 + - @backstage/backend-plugin-api@1.2.1-next.0 + +## 0.3.0 + +### Minor Changes + +- 61f464e: Added `auth.providers..sessionDuration` config for auth providers to allow the lifespan of user sessions to be configured. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0 + - @backstage/plugin-auth-node@0.6.0 + +## 0.3.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.2 + - @backstage/plugin-auth-node@0.6.0-next.2 + +## 0.3.0-next.1 + +### Minor Changes + +- 61f464e: Added `auth.providers..sessionDuration` config for auth providers to allow the lifespan of user sessions to be configured. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.1 + - @backstage/plugin-auth-node@0.6.0-next.1 + +## 0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.0 + - @backstage/plugin-auth-node@0.5.7-next.0 + +## 0.2.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.6 + - @backstage/backend-plugin-api@1.1.1 + +## 0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.1-next.1 + - @backstage/plugin-auth-node@0.5.6-next.1 + +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.6-next.0 + - @backstage/backend-plugin-api@1.1.1-next.0 + +## 0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.5 + - @backstage/backend-plugin-api@1.1.0 + +## 0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.0-next.2 + - @backstage/plugin-auth-node@0.5.5-next.2 + +## 0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.5-next.1 + - @backstage/backend-plugin-api@1.1.0-next.1 + +## 0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.3-next.0 + - @backstage/plugin-auth-node@0.5.5-next.0 + +## 0.2.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4 + - @backstage/backend-plugin-api@1.0.2 + +## 0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4-next.2 + - @backstage/backend-plugin-api@1.0.2-next.2 + +## 0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.2-next.1 + - @backstage/plugin-auth-node@0.5.4-next.1 + +## 0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4-next.0 + - @backstage/backend-plugin-api@1.0.2-next.0 + +## 0.2.1 + +### Patch Changes + +- 217458a: Updated configuration schema to include the new `allowedDomains` option for the `emailLocalPartMatchingUserEntityName` sign-in resolver. +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3 + - @backstage/backend-plugin-api@1.0.1 + +## 0.2.1-next.1 + +### Patch Changes + +- 217458a: Updated configuration schema to include the new `allowedDomains` option for the `emailLocalPartMatchingUserEntityName` sign-in resolver. +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3-next.1 + - @backstage/backend-plugin-api@1.0.1-next.1 + +## 0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3-next.0 + - @backstage/backend-plugin-api@1.0.1-next.0 + +## 0.2.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0 + - @backstage/plugin-auth-node@0.5.2 + +## 0.2.0-next.2 + +### Patch Changes + +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + +## 0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + +## 0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + +## 0.1.6 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + +## 0.1.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## 0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + +## 0.1.6-next.1 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## 0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## 0.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-auth-node@0.4.17 + +## 0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.21-next.0 + - @backstage/plugin-auth-node@0.4.16-next.0 + +## 0.1.2 + +### Patch Changes + +- 78a0b08: Internal refactor to handle `BackendFeature` contract change. +- 8efc6cf: Added support for the new shared `additionalScopes` configuration. In addition, the `account` scope has been set to required and will always be present. +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19 + - @backstage/plugin-auth-node@0.4.14 + +## 0.1.2-next.2 + +### Patch Changes + +- 8efc6cf: Added support for the new shared `additionalScopes` configuration. In addition, the `account` scope has been set to required and will always be present. +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.3 + - @backstage/plugin-auth-node@0.4.14-next.3 + +## 0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.2 + - @backstage/plugin-auth-node@0.4.14-next.2 + +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + +## 0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## 0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + +## 0.1.0 + +### Minor Changes + +- ba763b6: Migrate the Bitbucket auth provider to the new `@backstage/plugin-auth-backend-module-bitbucket-provider` module package. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.17 + - @backstage/plugin-auth-node@0.4.12 + +## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.12-next.0 + +# @backstage/plugin-auth-backend-module-bitbucket-server-provider + +## 0.2.11-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + +## 0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.2.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + +## 0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## 0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## 0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.9-next.0 + - @backstage/backend-plugin-api@1.4.5-next.0 + +## 0.2.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4 + - @backstage/plugin-auth-node@0.6.8 + +## 0.2.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## 0.2.7 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.7 + - @backstage/backend-plugin-api@1.4.3 + +## 0.2.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.7-next.0 + - @backstage/backend-plugin-api@1.4.3-next.0 + +## 0.2.6 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6 + - @backstage/backend-plugin-api@1.4.2 + +## 0.2.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + +## 0.2.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.2.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + +## 0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.2.3 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## 0.2.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + +## 0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + +## 0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.0 + - @backstage/plugin-auth-node@0.6.3-next.0 + +## 0.2.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.0 + - @backstage/plugin-auth-node@0.6.2 + +## 0.2.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1 + - @backstage/backend-plugin-api@1.2.1 + +## 0.2.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1-next.1 + - @backstage/backend-plugin-api@1.2.1-next.1 + +## 0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1-next.0 + - @backstage/backend-plugin-api@1.2.1-next.0 + +## 0.2.0 + +### Minor Changes + +- 61f464e: Added `auth.providers..sessionDuration` config for auth providers to allow the lifespan of user sessions to be configured. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0 + - @backstage/plugin-auth-node@0.6.0 + +## 0.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.2 + - @backstage/plugin-auth-node@0.6.0-next.2 + +## 0.2.0-next.1 + +### Minor Changes + +- 61f464e: Added `auth.providers..sessionDuration` config for auth providers to allow the lifespan of user sessions to be configured. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.1 + - @backstage/plugin-auth-node@0.6.0-next.1 + +## 0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.0 + - @backstage/plugin-auth-node@0.5.7-next.0 + +## 0.1.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.6 + - @backstage/backend-plugin-api@1.1.1 + +## 0.1.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.1-next.1 + - @backstage/plugin-auth-node@0.5.6-next.1 + +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.6-next.0 + - @backstage/backend-plugin-api@1.1.1-next.0 + +## 0.1.3 + +### Patch Changes + +- 5c9cc05: Use native fetch instead of node-fetch +- Updated dependencies + - @backstage/plugin-auth-node@0.5.5 + - @backstage/backend-plugin-api@1.1.0 + +## 0.1.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.0-next.2 + - @backstage/plugin-auth-node@0.5.5-next.2 + +## 0.1.3-next.1 + +### Patch Changes + +- 5c9cc05: Use native fetch instead of node-fetch +- Updated dependencies + - @backstage/plugin-auth-node@0.5.5-next.1 + - @backstage/backend-plugin-api@1.1.0-next.1 + +## 0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.3-next.0 + - @backstage/plugin-auth-node@0.5.5-next.0 + +## 0.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4 + - @backstage/backend-plugin-api@1.0.2 + +## 0.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4-next.2 + - @backstage/backend-plugin-api@1.0.2-next.2 + +## 0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.2-next.1 + - @backstage/plugin-auth-node@0.5.4-next.1 + +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4-next.0 + - @backstage/backend-plugin-api@1.0.2-next.0 + +## 0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3 + - @backstage/backend-plugin-api@1.0.1 + +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3-next.1 + - @backstage/backend-plugin-api@1.0.1-next.1 + +## 0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3-next.0 + - @backstage/backend-plugin-api@1.0.1-next.0 + +## 0.1.0 + +### Minor Changes + +- 527d973: New module for `@backstage/plugin-auth-backend` that adds a `Bitbucket Server` auth provider. + +### Patch Changes + +- 0a3a13e: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0 + - @backstage/plugin-auth-node@0.5.2 + +## 0.1.0-next.0 + +### Minor Changes + +- 527d973: New module for `@backstage/plugin-auth-backend` that adds a `Bitbucket Server` auth provider. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.12-next.0 + +# @backstage/plugin-auth-backend-module-cloudflare-access-provider + +## 0.4.11-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.4.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.4.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.4.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.4.9 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config@1.3.6 + +## 0.4.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## 0.4.9-next.0 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.9-next.0 + - @backstage/config@1.3.6-next.0 + - @backstage/backend-plugin-api@1.4.5-next.0 + - @backstage/errors@1.2.7 + +## 0.4.8 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.5 + - @backstage/backend-plugin-api@1.4.4 + - @backstage/plugin-auth-node@0.6.8 + +## 0.4.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## 0.4.7 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.7 + - @backstage/backend-plugin-api@1.4.3 + +## 0.4.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.7-next.0 + - @backstage/backend-plugin-api@1.4.3-next.0 + +## 0.4.6 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6 + - @backstage/backend-plugin-api@1.4.2 + +## 0.4.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + +## 0.4.5 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.4.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.4.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.4.3 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.4.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.4.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.0 + - @backstage/plugin-auth-node@0.6.3-next.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.4.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.0 + - @backstage/plugin-auth-node@0.6.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.4.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1 + - @backstage/backend-plugin-api@1.2.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1-next.1 + - @backstage/backend-plugin-api@1.2.1-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1-next.0 + - @backstage/backend-plugin-api@1.2.1-next.0 + +## 0.4.0 + +### Minor Changes + +- 61f464e: Added `auth.providers..sessionDuration` config for auth providers to allow the lifespan of user sessions to be configured. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0 + - @backstage/plugin-auth-node@0.6.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.4.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.2 + - @backstage/plugin-auth-node@0.6.0-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.4.0-next.1 + +### Minor Changes + +- 61f464e: Added `auth.providers..sessionDuration` config for auth providers to allow the lifespan of user sessions to be configured. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.1 + - @backstage/plugin-auth-node@0.6.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-auth-node@0.5.7-next.0 + +## 0.3.4 + +### Patch Changes + +- d4a8246: Use the email from `cfIdentity` instead of `claims` when constructing user profile in order to support Cloudflare Service Tokens. +- Updated dependencies + - @backstage/plugin-auth-node@0.5.6 + - @backstage/backend-plugin-api@1.1.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.1-next.1 + - @backstage/config@1.3.2-next.0 + - @backstage/errors@1.2.7-next.0 + - @backstage/plugin-auth-node@0.5.6-next.1 + +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.6-next.0 + - @backstage/backend-plugin-api@1.1.1-next.0 + - @backstage/config@1.3.1 + - @backstage/errors@1.2.6 + +## 0.3.3 + +### Patch Changes + +- 5c9cc05: Use native fetch instead of node-fetch +- Updated dependencies + - @backstage/plugin-auth-node@0.5.5 + - @backstage/backend-plugin-api@1.1.0 + - @backstage/errors@1.2.6 + - @backstage/config@1.3.1 + +## 0.3.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.0-next.2 + - @backstage/errors@1.2.6-next.0 + - @backstage/plugin-auth-node@0.5.5-next.2 + - @backstage/config@1.3.1-next.0 + +## 0.3.3-next.1 + +### Patch Changes + +- 5c9cc05: Use native fetch instead of node-fetch +- Updated dependencies + - @backstage/plugin-auth-node@0.5.5-next.1 + - @backstage/backend-plugin-api@1.1.0-next.1 + - @backstage/config@1.3.0 + - @backstage/errors@1.2.5 + +## 0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.3-next.0 + - @backstage/plugin-auth-node@0.5.5-next.0 + - @backstage/config@1.3.0 + - @backstage/errors@1.2.5 + +## 0.3.2 + +### Patch Changes + +- d52d7f9: Support ISO and ms string forms of durations in config too +- 4e58bc7: Upgrade to uuid v11 internally +- Updated dependencies + - @backstage/config@1.3.0 + - @backstage/plugin-auth-node@0.5.4 + - @backstage/backend-plugin-api@1.0.2 + - @backstage/errors@1.2.5 + +## 0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4-next.2 + - @backstage/backend-plugin-api@1.0.2-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.2-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.5.4-next.1 + +## 0.3.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4-next.0 + - @backstage/backend-plugin-api@1.0.2-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.3.1 + +### Patch Changes + +- 217458a: Updated configuration schema to include the new `allowedDomains` option for the `emailLocalPartMatchingUserEntityName` sign-in resolver. +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3 + - @backstage/backend-plugin-api@1.0.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.3.1-next.1 + +### Patch Changes + +- 217458a: Updated configuration schema to include the new `allowedDomains` option for the `emailLocalPartMatchingUserEntityName` sign-in resolver. +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3-next.1 + - @backstage/backend-plugin-api@1.0.1-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3-next.0 + - @backstage/backend-plugin-api@1.0.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.3.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0 + - @backstage/plugin-auth-node@0.5.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.3.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.3.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.2.0 + +### Minor Changes + +- 75d026a: Support for Cloudflare Custom Headers and Custom Cookie Auth Name + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.2.0-next.3 + +### Minor Changes + +- 75d026a: Support for Cloudflare Custom Headers and Custom Cookie Auth Name + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## 0.1.6-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.6-next.1 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## 0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## 0.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.21-next.0 + - @backstage/plugin-auth-node@0.4.16-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.2 + +### Patch Changes + +- 78a0b08: Internal refactor to handle `BackendFeature` contract change. +- d44a20a: Added additional plugin metadata to `package.json`. +- 1354d81: Use `node-fetch` instead of native fetch, as per +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19 + - @backstage/plugin-auth-node@0.4.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.2-next.3 + +### Patch Changes + +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.3 + - @backstage/plugin-auth-node@0.4.14-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.2 + - @backstage/plugin-auth-node@0.4.14-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.2-next.1 + +### Patch Changes + +- 1354d81: Use `node-fetch` instead of native fetch, as per +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## 0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.0 + +### Minor Changes + +- c26218d: Created a separate module for the Cloudflare Access auth provider + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.17 + - @backstage/plugin-auth-node@0.4.12 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.12-next.0 + +# @backstage/plugin-auth-backend-module-gcp-iap-provider + +## 0.4.11-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.4.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.4.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.4.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.4.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## 0.4.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## 0.4.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.9-next.0 + - @backstage/backend-plugin-api@1.4.5-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.4.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4 + - @backstage/plugin-auth-node@0.6.8 + +## 0.4.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## 0.4.7 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.7 + - @backstage/types@1.2.2 + - @backstage/backend-plugin-api@1.4.3 + +## 0.4.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.7-next.0 + - @backstage/backend-plugin-api@1.4.3-next.0 + +## 0.4.6 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6 + - @backstage/backend-plugin-api@1.4.2 + +## 0.4.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.4.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.4.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.4.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.4.3 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.4.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.4.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.0 + - @backstage/plugin-auth-node@0.6.3-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.4.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.0 + - @backstage/plugin-auth-node@0.6.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.4.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1 + - @backstage/backend-plugin-api@1.2.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1-next.1 + - @backstage/backend-plugin-api@1.2.1-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1-next.0 + - @backstage/backend-plugin-api@1.2.1-next.0 + +## 0.4.0 + +### Minor Changes + +- 61f464e: Added `auth.providers..sessionDuration` config for auth providers to allow the lifespan of user sessions to be configured. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0 + - @backstage/plugin-auth-node@0.6.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.4.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.2 + - @backstage/plugin-auth-node@0.6.0-next.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.4.0-next.1 + +### Minor Changes + +- 61f464e: Added `auth.providers..sessionDuration` config for auth providers to allow the lifespan of user sessions to be configured. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.1 + - @backstage/plugin-auth-node@0.6.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-auth-node@0.5.7-next.0 + +## 0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.2.1 + - @backstage/plugin-auth-node@0.5.6 + - @backstage/backend-plugin-api@1.1.1 + - @backstage/errors@1.2.7 + +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.2.1-next.0 + - @backstage/backend-plugin-api@1.1.1-next.1 + - @backstage/errors@1.2.7-next.0 + - @backstage/plugin-auth-node@0.5.6-next.1 + +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.6-next.0 + - @backstage/backend-plugin-api@1.1.1-next.0 + - @backstage/errors@1.2.6 + - @backstage/types@1.2.0 + +## 0.3.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.5 + - @backstage/backend-plugin-api@1.1.0 + - @backstage/errors@1.2.6 + - @backstage/types@1.2.0 + +## 0.3.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.0-next.2 + - @backstage/errors@1.2.6-next.0 + - @backstage/plugin-auth-node@0.5.5-next.2 + - @backstage/types@1.2.0 + +## 0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.5-next.1 + - @backstage/backend-plugin-api@1.1.0-next.1 + - @backstage/errors@1.2.5 + - @backstage/types@1.2.0 + +## 0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.3-next.0 + - @backstage/plugin-auth-node@0.5.5-next.0 + - @backstage/errors@1.2.5 + - @backstage/types@1.2.0 + +## 0.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.2.0 + - @backstage/plugin-auth-node@0.5.4 + - @backstage/backend-plugin-api@1.0.2 + - @backstage/errors@1.2.5 + +## 0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4-next.2 + - @backstage/backend-plugin-api@1.0.2-next.2 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.2-next.1 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.5.4-next.1 + +## 0.3.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4-next.0 + - @backstage/backend-plugin-api@1.0.2-next.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.3.1 + +### Patch Changes + +- 217458a: Updated configuration schema to include the new `allowedDomains` option for the `emailLocalPartMatchingUserEntityName` sign-in resolver. +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3 + - @backstage/backend-plugin-api@1.0.1 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.3.1-next.1 + +### Patch Changes + +- 217458a: Updated configuration schema to include the new `allowedDomains` option for the `emailLocalPartMatchingUserEntityName` sign-in resolver. +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3-next.1 + - @backstage/backend-plugin-api@1.0.1-next.1 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3-next.0 + - @backstage/backend-plugin-api@1.0.1-next.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.3.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0 + - @backstage/plugin-auth-node@0.5.2 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.3.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.3.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.2.18 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- 13a9c63: Corrected the documentation for the GCP IAP auth module and updated the configuration to follow proxy configuration conventions by ignoring authEnv +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.2.18-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## 0.2.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.2.18-next.1 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## 0.2.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## 0.2.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.2.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## 0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.21-next.0 + - @backstage/plugin-auth-node@0.4.16-next.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.2.14 + +### Patch Changes + +- 78a0b08: Internal refactor to handle `BackendFeature` contract change. +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19 + - @backstage/plugin-auth-node@0.4.14 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.2.14-next.2 + +### Patch Changes + +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.3 + - @backstage/plugin-auth-node@0.4.14-next.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.2.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.2 + - @backstage/plugin-auth-node@0.4.14-next.2 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.2.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.2.13 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + +## 0.2.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## 0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.2.12 + +### Patch Changes + +- e0ed31c: Add user id annotation sign-in resolver +- Updated dependencies + - @backstage/backend-plugin-api@0.6.17 + - @backstage/plugin-auth-node@0.4.12 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.2.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.17-next.1 + - @backstage/plugin-auth-node@0.4.12-next.1 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.17-next.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.12-next.0 + +## 0.2.11 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.11 + - @backstage/backend-plugin-api@0.6.16 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.10 + - @backstage/backend-plugin-api@0.6.15 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.2.9 + +### Patch Changes + +- 38af71a: Updated dependency `google-auth-library` to `^9.0.0`. +- b1b012d: Fix issue with `providerInfo` not being set properly for some proxy providers, by making `providerInfo` an explicit optional return from `authenticate` +- Updated dependencies + - @backstage/plugin-auth-node@0.4.9 + - @backstage/errors@1.2.4 + - @backstage/backend-plugin-api@0.6.14 + - @backstage/types@1.1.1 + +## 0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.9-next.2 + - @backstage/backend-plugin-api@0.6.14-next.2 + - @backstage/errors@1.2.4-next.0 + - @backstage/types@1.1.1 + +## 0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.14-next.1 + - @backstage/plugin-auth-node@0.4.9-next.1 + - @backstage/errors@1.2.4-next.0 + - @backstage/types@1.1.1 + +## 0.2.8-next.0 + +### Patch Changes + +- 38af71a: Updated dependency `google-auth-library` to `^9.0.0`. +- b1b012d: Fix issue with `providerInfo` not being set properly for some proxy providers, by making `providerInfo` an explicit optional return from `authenticate` +- Updated dependencies + - @backstage/plugin-auth-node@0.4.8-next.0 + - @backstage/errors@1.2.4-next.0 + - @backstage/backend-plugin-api@0.6.13-next.0 + - @backstage/types@1.1.1 + +## 0.2.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4 + - @backstage/backend-plugin-api@0.6.10 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.4-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.3 + - @backstage/backend-plugin-api@0.6.10-next.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.2 + - @backstage/plugin-auth-node@0.4.4-next.2 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.4-next.1 + +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.3 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## 0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.3-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## 0.2.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## 0.2.2-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + +## 0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.2-next.0 + - @backstage/backend-plugin-api@0.6.8-next.0 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.7 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.1 + +## 0.2.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.7-next.2 + - @backstage/plugin-auth-node@0.4.1-next.2 + +## 0.2.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.1-next.1 + - @backstage/backend-plugin-api@0.6.7-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.7-next.0 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.1-next.0 + +## 0.2.0 + +### Minor Changes + +- 6f142d5356: **BREAKING** `gcpIapAuthenticator.initialize()` is no longer `async` + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.0 + - @backstage/errors@1.2.3 + - @backstage/backend-plugin-api@0.6.6 + - @backstage/types@1.1.1 + +## 0.2.0-next.2 + +### Minor Changes + +- 6f142d5356: **BREAKING** `gcpIapAuthenticator.initialize()` is no longer `async` + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.0-next.2 + - @backstage/errors@1.2.3-next.0 + - @backstage/backend-plugin-api@0.6.6-next.2 + - @backstage/types@1.1.1 + +## 0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.5-next.1 + - @backstage/plugin-auth-node@0.3.2-next.1 + - @backstage/errors@1.2.2 + - @backstage/types@1.1.1 + +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.3.2-next.0 + - @backstage/backend-plugin-api@0.6.5-next.0 + - @backstage/errors@1.2.2 + - @backstage/types@1.1.1 + +## 0.1.0 + +### Minor Changes + +- 8513cd7d00e3: New module for `@backstage/plugin-auth-backend` that adds a GCP IAP auth provider. + +### Patch Changes + +- 71114ac50e02: The export for the new backend system has been moved to be the `default` export. + + For example, if you are currently importing the plugin using the following pattern: + + ```ts + import { examplePlugin } from '@backstage/plugin-example-backend'; + + backend.add(examplePlugin); + ``` + + It should be migrated to this: + + ```ts + backend.add(import('@backstage/plugin-example-backend')); + ``` + +- Updated dependencies + - @backstage/plugin-auth-node@0.3.0 + - @backstage/errors@1.2.2 + - @backstage/types@1.1.1 + - @backstage/backend-plugin-api@0.6.3 + +## 0.1.0-next.3 + +### Patch Changes + +- 71114ac50e02: The export for the new backend system has been moved to be the `default` export. + + For example, if you are currently importing the plugin using the following pattern: + + ```ts + import { examplePlugin } from '@backstage/plugin-example-backend'; + + backend.add(examplePlugin); + ``` + + It should be migrated to this: + + ```ts + backend.add(import('@backstage/plugin-example-backend')); + ``` + +- Updated dependencies + - @backstage/errors@1.2.2-next.0 + - @backstage/types@1.1.1-next.0 + - @backstage/backend-plugin-api@0.6.3-next.3 + - @backstage/plugin-auth-node@0.3.0-next.3 + +## 0.1.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.3.0-next.2 + - @backstage/backend-plugin-api@0.6.3-next.2 + - @backstage/errors@1.2.1 + - @backstage/types@1.1.0 + +## 0.1.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.3-next.1 + - @backstage/plugin-auth-node@0.3.0-next.1 + - @backstage/errors@1.2.1 + - @backstage/types@1.1.0 + +## 0.1.0-next.0 + +### Minor Changes + +- 8513cd7d00e3: New module for `@backstage/plugin-auth-backend` that adds a GCP IAP auth provider. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.3.0-next.0 + - @backstage/backend-plugin-api@0.6.2-next.0 + - @backstage/errors@1.2.1 + - @backstage/types@1.1.0 + +## @backstage/plugin-auth-backend-module-google-provider@0.3.12-next.0 + +# @backstage/plugin-auth-backend-module-google-provider + +## 0.3.11-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + +## 0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + +## 0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## 0.3.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## 0.3.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.9-next.0 + - @backstage/backend-plugin-api@1.4.5-next.0 + +## 0.3.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4 + - @backstage/plugin-auth-node@0.6.8 + +## 0.3.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## 0.3.7 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.7 + - @backstage/backend-plugin-api@1.4.3 + +## 0.3.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.7-next.0 + - @backstage/backend-plugin-api@1.4.3-next.0 + +## 0.3.6 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6 + - @backstage/backend-plugin-api@1.4.2 + +## 0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + +## 0.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.3.3 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## 0.3.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + +## 0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + +## 0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.0 + - @backstage/plugin-auth-node@0.6.3-next.0 + +## 0.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.0 + - @backstage/plugin-auth-node@0.6.2 + +## 0.3.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1 + - @backstage/backend-plugin-api@1.2.1 + +## 0.3.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1-next.1 + - @backstage/backend-plugin-api@1.2.1-next.1 + +## 0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1-next.0 + - @backstage/backend-plugin-api@1.2.1-next.0 + +## 0.3.0 + +### Minor Changes + +- 61f464e: Added `auth.providers..sessionDuration` config for auth providers to allow the lifespan of user sessions to be configured. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0 + - @backstage/plugin-auth-node@0.6.0 + +## 0.3.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.2 + - @backstage/plugin-auth-node@0.6.0-next.2 + +## 0.3.0-next.1 + +### Minor Changes + +- 61f464e: Added `auth.providers..sessionDuration` config for auth providers to allow the lifespan of user sessions to be configured. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.1 + - @backstage/plugin-auth-node@0.6.0-next.1 + +## 0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.0 + - @backstage/plugin-auth-node@0.5.7-next.0 + +## 0.2.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.6 + - @backstage/backend-plugin-api@1.1.1 + +## 0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.1-next.1 + - @backstage/plugin-auth-node@0.5.6-next.1 + +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.6-next.0 + - @backstage/backend-plugin-api@1.1.1-next.0 + +## 0.2.3 + +### Patch Changes + +- 79b055a: Pass through `includeGrantedScopes` in order to persist scopes across refresh calls +- Updated dependencies + - @backstage/plugin-auth-node@0.5.5 + - @backstage/backend-plugin-api@1.1.0 + +## 0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.0-next.2 + - @backstage/plugin-auth-node@0.5.5-next.2 + +## 0.2.3-next.1 + +### Patch Changes + +- 79b055a: Pass through `includeGrantedScopes` in order to persist scopes across refresh calls +- Updated dependencies + - @backstage/plugin-auth-node@0.5.5-next.1 + - @backstage/backend-plugin-api@1.1.0-next.1 + +## 0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.3-next.0 + - @backstage/plugin-auth-node@0.5.5-next.0 + +## 0.2.2 + +### Patch Changes + +- b833660: Fix visibility of config for use in front end code +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4 + - @backstage/backend-plugin-api@1.0.2 + +## 0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4-next.2 + - @backstage/backend-plugin-api@1.0.2-next.2 + +## 0.2.2-next.1 + +### Patch Changes + +- b833660: Fix visibility of config for use in front end code +- Updated dependencies + - @backstage/backend-plugin-api@1.0.2-next.1 + - @backstage/plugin-auth-node@0.5.4-next.1 + +## 0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4-next.0 + - @backstage/backend-plugin-api@1.0.2-next.0 + +## 0.2.1 + +### Patch Changes + +- 217458a: Updated configuration schema to include the new `allowedDomains` option for the `emailLocalPartMatchingUserEntityName` sign-in resolver. +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3 + - @backstage/backend-plugin-api@1.0.1 + +## 0.2.1-next.1 + +### Patch Changes + +- 217458a: Updated configuration schema to include the new `allowedDomains` option for the `emailLocalPartMatchingUserEntityName` sign-in resolver. +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3-next.1 + - @backstage/backend-plugin-api@1.0.1-next.1 + +## 0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3-next.0 + - @backstage/backend-plugin-api@1.0.1-next.0 + +## 0.2.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0 + - @backstage/plugin-auth-node@0.5.2 + +## 0.2.0-next.2 + +### Patch Changes + +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + +## 0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + +## 0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + +## 0.1.20 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + +## 0.1.20-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## 0.1.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + +## 0.1.20-next.1 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## 0.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## 0.1.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-auth-node@0.4.17 + +## 0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## 0.1.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.21-next.0 + - @backstage/plugin-auth-node@0.4.16-next.0 + +## 0.1.16 + +### Patch Changes + +- 78a0b08: Internal refactor to handle `BackendFeature` contract change. +- 8efc6cf: Added support for the new shared `additionalScopes` configuration. In addition, the `openid`, `userinfo.email`, and `userinfo.profile` scopes have been set to required and will always be present. +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19 + - @backstage/plugin-auth-node@0.4.14 + +## 0.1.16-next.2 + +### Patch Changes + +- 8efc6cf: Added support for the new shared `additionalScopes` configuration. In addition, the `openid`, `userinfo.email`, and `userinfo.profile` scopes have been set to required and will always be present. +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.3 + - @backstage/plugin-auth-node@0.4.14-next.3 + +## 0.1.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.2 + - @backstage/plugin-auth-node@0.4.14-next.2 + +## 0.1.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + +## 0.1.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + +## 0.1.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## 0.1.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + +## 0.1.14 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.17 + - @backstage/plugin-auth-node@0.4.12 + +## 0.1.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.17-next.1 + - @backstage/plugin-auth-node@0.4.12-next.1 + +## 0.1.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.17-next.0 + - @backstage/plugin-auth-node@0.4.12-next.0 + +## 0.1.13 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.11 + - @backstage/backend-plugin-api@0.6.16 + +## 0.1.12 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.10 + - @backstage/backend-plugin-api@0.6.15 + +## 0.1.11 + +### Patch Changes + +- 38af71a: Updated dependency `google-auth-library` to `^9.0.0`. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.9 + - @backstage/backend-plugin-api@0.6.14 + +## 0.1.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.9-next.2 + - @backstage/backend-plugin-api@0.6.14-next.2 + +## 0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.14-next.1 + - @backstage/plugin-auth-node@0.4.9-next.1 + +## 0.1.10-next.0 + +### Patch Changes + +- 38af71a: Updated dependency `google-auth-library` to `^9.0.0`. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.8-next.0 + - @backstage/backend-plugin-api@0.6.13-next.0 + +## 0.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4 + - @backstage/backend-plugin-api@0.6.10 + +## 0.1.7-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.3 + - @backstage/backend-plugin-api@0.6.10-next.3 + +## 0.1.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.2 + - @backstage/plugin-auth-node@0.4.4-next.2 + +## 0.1.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.1 + - @backstage/plugin-auth-node@0.4.4-next.1 + +## 0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + +## 0.1.6 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + +## 0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## 0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + +## 0.1.6-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## 0.1.5 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + +## 0.1.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## 0.1.5-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + +## 0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + +## 0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.2-next.0 + - @backstage/backend-plugin-api@0.6.8-next.0 + +## 0.1.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.7 + - @backstage/plugin-auth-node@0.4.1 + +## 0.1.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.7-next.2 + - @backstage/plugin-auth-node@0.4.1-next.2 + +## 0.1.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.1-next.1 + - @backstage/backend-plugin-api@0.6.7-next.1 + +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.7-next.0 + - @backstage/plugin-auth-node@0.4.1-next.0 + +## 0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.0 + - @backstage/backend-plugin-api@0.6.6 + +## 0.1.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.0-next.2 + - @backstage/backend-plugin-api@0.6.6-next.2 + +## 0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.5-next.1 + - @backstage/plugin-auth-node@0.3.2-next.1 + +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.3.2-next.0 + - @backstage/backend-plugin-api@0.6.5-next.0 + +## 0.1.0 + +### Minor Changes + +- 8513cd7d00e3: New module for `@backstage/plugin-auth-backend` that adds a Google auth provider. + +### Patch Changes + +- 71114ac50e02: The export for the new backend system has been moved to be the `default` export. + + For example, if you are currently importing the plugin using the following pattern: + + ```ts + import { examplePlugin } from '@backstage/plugin-example-backend'; + + backend.add(examplePlugin); + ``` + + It should be migrated to this: + + ```ts + backend.add(import('@backstage/plugin-example-backend')); + ``` + +- Updated dependencies + - @backstage/plugin-auth-node@0.3.0 + - @backstage/backend-plugin-api@0.6.3 + +## 0.1.0-next.3 + +### Patch Changes + +- 71114ac50e02: The export for the new backend system has been moved to be the `default` export. + + For example, if you are currently importing the plugin using the following pattern: + + ```ts + import { examplePlugin } from '@backstage/plugin-example-backend'; + + backend.add(examplePlugin); + ``` + + It should be migrated to this: + + ```ts + backend.add(import('@backstage/plugin-example-backend')); + ``` + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.3-next.3 + - @backstage/plugin-auth-node@0.3.0-next.3 + +## 0.1.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.3.0-next.2 + - @backstage/backend-plugin-api@0.6.3-next.2 + +## 0.1.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.3-next.1 + - @backstage/plugin-auth-node@0.3.0-next.1 + +## 0.1.0-next.0 + +### Minor Changes + +- 8513cd7d00e3: New module for `@backstage/plugin-auth-backend` that adds a Google auth provider. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.3.0-next.0 + - @backstage/backend-plugin-api@0.6.2-next.0 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.12-next.0 + +# @backstage/plugin-auth-backend-module-microsoft-provider + +## 0.3.11-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + +## 0.3.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.3.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + +## 0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## 0.3.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## 0.3.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.9-next.0 + - @backstage/backend-plugin-api@1.4.5-next.0 + +## 0.3.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4 + - @backstage/plugin-auth-node@0.6.8 + +## 0.3.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## 0.3.7 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.7 + - @backstage/backend-plugin-api@1.4.3 + +## 0.3.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.7-next.0 + - @backstage/backend-plugin-api@1.4.3-next.0 + +## 0.3.6 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6 + - @backstage/backend-plugin-api@1.4.2 + +## 0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + +## 0.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.3.3 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## 0.3.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + +## 0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + +## 0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.0 + - @backstage/plugin-auth-node@0.6.3-next.0 + +## 0.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.0 + - @backstage/plugin-auth-node@0.6.2 + +## 0.3.1 + +### Patch Changes + +- fa15e80: Update `auth.microsoft.signIn.resolvers` config def to include the `userIdMatchingUserEntityAnnotation` resolver. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1 + - @backstage/backend-plugin-api@1.2.1 + +## 0.3.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1-next.1 + - @backstage/backend-plugin-api@1.2.1-next.1 + +## 0.3.1-next.0 + +### Patch Changes + +- fa15e80: Update `auth.microsoft.signIn.resolvers` config def to include the `userIdMatchingUserEntityAnnotation` resolver. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1-next.0 + - @backstage/backend-plugin-api@1.2.1-next.0 + +## 0.3.0 + +### Minor Changes + +- 61f464e: Added `auth.providers..sessionDuration` config for auth providers to allow the lifespan of user sessions to be configured. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0 + - @backstage/plugin-auth-node@0.6.0 + +## 0.3.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.2 + - @backstage/plugin-auth-node@0.6.0-next.2 + +## 0.3.0-next.1 + +### Minor Changes + +- 61f464e: Added `auth.providers..sessionDuration` config for auth providers to allow the lifespan of user sessions to be configured. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.1 + - @backstage/plugin-auth-node@0.6.0-next.1 + +## 0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.0 + - @backstage/plugin-auth-node@0.5.7-next.0 + +## 0.2.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.6 + - @backstage/backend-plugin-api@1.1.1 + +## 0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.1-next.1 + - @backstage/plugin-auth-node@0.5.6-next.1 + +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.6-next.0 + - @backstage/backend-plugin-api@1.1.1-next.0 + +## 0.2.3 + +### Patch Changes + +- 5c9cc05: Use native fetch instead of node-fetch +- Updated dependencies + - @backstage/plugin-auth-node@0.5.5 + - @backstage/backend-plugin-api@1.1.0 + +## 0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.0-next.2 + - @backstage/plugin-auth-node@0.5.5-next.2 + +## 0.2.3-next.1 + +### Patch Changes + +- 5c9cc05: Use native fetch instead of node-fetch +- Updated dependencies + - @backstage/plugin-auth-node@0.5.5-next.1 + - @backstage/backend-plugin-api@1.1.0-next.1 + +## 0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.3-next.0 + - @backstage/plugin-auth-node@0.5.5-next.0 + +## 0.2.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4 + - @backstage/backend-plugin-api@1.0.2 + +## 0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4-next.2 + - @backstage/backend-plugin-api@1.0.2-next.2 + +## 0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.2-next.1 + - @backstage/plugin-auth-node@0.5.4-next.1 + +## 0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4-next.0 + - @backstage/backend-plugin-api@1.0.2-next.0 + +## 0.2.1 + +### Patch Changes + +- 217458a: Updated configuration schema to include the new `allowedDomains` option for the `emailLocalPartMatchingUserEntityName` sign-in resolver. +- daa02d6: Add `skipUserProfile` config flag to Microsoft authenticator +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3 + - @backstage/backend-plugin-api@1.0.1 + +## 0.2.1-next.1 + +### Patch Changes + +- 217458a: Updated configuration schema to include the new `allowedDomains` option for the `emailLocalPartMatchingUserEntityName` sign-in resolver. +- daa02d6: Add `skipUserProfile` config flag to Microsoft authenticator +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3-next.1 + - @backstage/backend-plugin-api@1.0.1-next.1 + +## 0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3-next.0 + - @backstage/backend-plugin-api@1.0.1-next.0 + +## 0.2.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- 3c2d690: Allow users without defined email to be ingested by the `msgraph` catalog plugin and add `userIdMatchingUserEntityAnnotation` sign-in resolver for the Microsoft auth provider to support sign-in for users without defined email. +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0 + - @backstage/plugin-auth-node@0.5.2 + +## 0.2.0-next.2 + +### Patch Changes + +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + +## 0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + +## 0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- 3c2d690: Allow users without defined email to be ingested by the `msgraph` catalog plugin and add `userIdMatchingUserEntityAnnotation` sign-in resolver for the Microsoft auth provider to support sign-in for users without defined email. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + +## 0.1.18 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 39f36a9: Updated the Microsoft authenticator to accurately define required scopes, but to also omit the required and additional scopes when requesting resource scopes. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + +## 0.1.18-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## 0.1.18-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + +## 0.1.18-next.1 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## 0.1.18-next.0 + +### Patch Changes + +- 39f36a9: Updated the Microsoft authenticator to accurately define required scopes, but to also omit the required and additional scopes when requesting resource scopes. +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## 0.1.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-auth-node@0.4.17 + +## 0.1.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## 0.1.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.21-next.0 + - @backstage/plugin-auth-node@0.4.16-next.0 + +## 0.1.14 + +### Patch Changes + +- 78a0b08: Internal refactor to handle `BackendFeature` contract change. +- 8efc6cf: Added support for the new shared `additionalScopes` configuration. +- d44a20a: Added additional plugin metadata to `package.json`. +- c187a9c: Minor internal type updates +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19 + - @backstage/plugin-auth-node@0.4.14 + +## 0.1.14-next.2 + +### Patch Changes + +- 8efc6cf: Added support for the new shared `additionalScopes` configuration. +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.3 + - @backstage/plugin-auth-node@0.4.14-next.3 + +## 0.1.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.2 + - @backstage/plugin-auth-node@0.4.14-next.2 + +## 0.1.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + +## 0.1.13 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + +## 0.1.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## 0.1.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + +## 0.1.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.17 + - @backstage/plugin-auth-node@0.4.12 + +## 0.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.17-next.1 + - @backstage/plugin-auth-node@0.4.12-next.1 + +## 0.1.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.17-next.0 + - @backstage/plugin-auth-node@0.4.12-next.0 + +## 0.1.11 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.11 + - @backstage/backend-plugin-api@0.6.16 + +## 0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.10 + - @backstage/backend-plugin-api@0.6.15 + +## 0.1.9 + +### Patch Changes + +- 2af5354: Bump dependency `jose` to v5 +- Updated dependencies + - @backstage/plugin-auth-node@0.4.9 + - @backstage/backend-plugin-api@0.6.14 + +## 0.1.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.9-next.2 + - @backstage/backend-plugin-api@0.6.14-next.2 + +## 0.1.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.14-next.1 + - @backstage/plugin-auth-node@0.4.9-next.1 + +## 0.1.8-next.0 + +### Patch Changes + +- 2af5354: Bump dependency `jose` to v5 +- Updated dependencies + - @backstage/plugin-auth-node@0.4.8-next.0 + - @backstage/backend-plugin-api@0.6.13-next.0 + +## 0.1.5 + +### Patch Changes + +- 8472188: Added or fixed the `repository` field in `package.json`. +- 1ff2684: Added the possibility to use custom scopes for performing login with Microsoft EntraID. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4 + - @backstage/backend-plugin-api@0.6.10 + +## 0.1.5-next.3 + +### Patch Changes + +- 8472188: Added or fixed the `repository` field in `package.json`. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.3 + - @backstage/backend-plugin-api@0.6.10-next.3 + +## 0.1.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.2 + - @backstage/plugin-auth-node@0.4.4-next.2 + +## 0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.1 + - @backstage/plugin-auth-node@0.4.4-next.1 + +## 0.1.5-next.0 + +### Patch Changes + +- 1ff2684: Added the possibility to use custom scopes for performing login with Microsoft EntraID. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + +## 0.1.4 + +### Patch Changes + +- 928efbc: Deprecated the `authModuleMicrosoftProvider` export. A default export is now available and should be used like this in your backend: `backend.add(import('@backstage/plugin-auth-backend-module-microsoft-provider'));` +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + +## 0.1.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## 0.1.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + +## 0.1.4-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## 0.1.3 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + +## 0.1.3-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## 0.1.3-next.2 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + +## 0.1.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + +## 0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.0 + - @backstage/plugin-auth-node@0.4.2-next.0 + - @backstage/backend-plugin-api@0.6.8-next.0 + +## 0.1.2 + +### Patch Changes + +- a3236ad0ca: Fix link to the repository in `README.md`. + +- 3979524c74: Added support for specifying a domain hint on the Microsoft authentication provider configuration. + +- fde212dd10: Re-add the missing profile photo + as well as access token retrieval for foreign scopes. + + Additionally, we switch from previously 48x48 to 96x96 + which is the size used at the profile card. + +- 5aeb14f035: Correctly mark the client secret in configuration as secret + +- 2817115d09: Removed `prompt=consent` from start method to fix #20641 + +- Updated dependencies + - @backstage/backend-common@0.19.9 + - @backstage/backend-plugin-api@0.6.7 + - @backstage/plugin-auth-node@0.4.1 + +## 0.1.2-next.2 + +### Patch Changes + +- [#20706](https://github.com/backstage/backstage/pull/20706) [`fde212dd10`](https://github.com/backstage/backstage/commit/fde212dd106e507c4a808e5ed8213e29d7338420) Thanks [@pjungermann](https://github.com/pjungermann)! - Re-add the missing profile photo + as well as access token retrieval for foreign scopes. + + Additionally, we switch from previously 48x48 to 96x96 + which is the size used at the profile card. + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.7-next.2 + - @backstage/backend-common@0.19.9-next.2 + - @backstage/plugin-auth-node@0.4.1-next.2 + +## 0.1.2-next.1 + +### Patch Changes + +- 3979524c74: Added support for specifying a domain hint on the Microsoft authentication provider configuration. +- 5aeb14f035: Correctly mark the client secret in configuration as secret +- Updated dependencies + - @backstage/backend-common@0.19.9-next.1 + - @backstage/plugin-auth-node@0.4.1-next.1 + - @backstage/backend-plugin-api@0.6.7-next.1 + +## 0.1.2-next.0 + +### Patch Changes + +- 2817115d09: Removed `prompt=consent` from start method to fix #20641 +- Updated dependencies + - @backstage/backend-common@0.19.9-next.0 + - @backstage/backend-plugin-api@0.6.7-next.0 + - @backstage/plugin-auth-node@0.4.1-next.0 + +## 0.1.0 + +### Minor Changes + +- 2d8f7e82c1: Migrated the Microsoft auth provider to new `@backstage/plugin-auth-backend-module-microsoft-provider` module package. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.8 + - @backstage/plugin-auth-node@0.4.0 + - @backstage/backend-plugin-api@0.6.6 + +## 0.1.0-next.0 + +### Minor Changes + +- 2d8f7e82c1: Migrated the Microsoft auth provider to new `@backstage/plugin-auth-backend-module-microsoft-provider` module package. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.8-next.2 + - @backstage/plugin-auth-node@0.4.0-next.2 + - @backstage/backend-plugin-api@0.6.6-next.2 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.12-next.0 + +# @backstage/plugin-auth-backend-module-oauth2-provider + +## 0.4.11-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + +## 0.4.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.4.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 0.4.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + +## 0.4.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## 0.4.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## 0.4.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.9-next.0 + - @backstage/backend-plugin-api@1.4.5-next.0 + +## 0.4.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4 + - @backstage/plugin-auth-node@0.6.8 + +## 0.4.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## 0.4.7 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.7 + - @backstage/backend-plugin-api@1.4.3 + +## 0.4.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.7-next.0 + - @backstage/backend-plugin-api@1.4.3-next.0 + +## 0.4.6 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6 + - @backstage/backend-plugin-api@1.4.2 + +## 0.4.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + +## 0.4.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.4.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + +## 0.4.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.4.3 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## 0.4.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + +## 0.4.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + +## 0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.0 + - @backstage/plugin-auth-node@0.6.3-next.0 + +## 0.4.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.0 + - @backstage/plugin-auth-node@0.6.2 + +## 0.4.1 + +### Patch Changes + +- ce15e30: Fixed repository url in `README.md` +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1 + - @backstage/backend-plugin-api@1.2.1 + +## 0.4.1-next.2 + +### Patch Changes + +- ce15e30: Fixed repository url in `README.md` +- Updated dependencies + - @backstage/backend-plugin-api@1.2.1-next.1 + - @backstage/plugin-auth-node@0.6.1-next.1 + +## 0.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1-next.1 + - @backstage/backend-plugin-api@1.2.1-next.1 + +## 0.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1-next.0 + - @backstage/backend-plugin-api@1.2.1-next.0 + +## 0.4.0 + +### Minor Changes + +- 61f464e: Added `auth.providers..sessionDuration` config for auth providers to allow the lifespan of user sessions to be configured. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0 + - @backstage/plugin-auth-node@0.6.0 + +## 0.4.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.2 + - @backstage/plugin-auth-node@0.6.0-next.2 + +## 0.4.0-next.1 + +### Minor Changes + +- 61f464e: Added `auth.providers..sessionDuration` config for auth providers to allow the lifespan of user sessions to be configured. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.1 + - @backstage/plugin-auth-node@0.6.0-next.1 + +## 0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.0 + - @backstage/plugin-auth-node@0.5.7-next.0 + +## 0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.6 + - @backstage/backend-plugin-api@1.1.1 + +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.1-next.1 + - @backstage/plugin-auth-node@0.5.6-next.1 + +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.6-next.0 + - @backstage/backend-plugin-api@1.1.1-next.0 + +## 0.3.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.5 + - @backstage/backend-plugin-api@1.1.0 + +## 0.3.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.0-next.2 + - @backstage/plugin-auth-node@0.5.5-next.2 + +## 0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.5-next.1 + - @backstage/backend-plugin-api@1.1.0-next.1 + +## 0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.3-next.0 + - @backstage/plugin-auth-node@0.5.5-next.0 + +## 0.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4 + - @backstage/backend-plugin-api@1.0.2 + +## 0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4-next.2 + - @backstage/backend-plugin-api@1.0.2-next.2 + +## 0.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.2-next.1 + - @backstage/plugin-auth-node@0.5.4-next.1 + +## 0.3.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4-next.0 + - @backstage/backend-plugin-api@1.0.2-next.0 + +## 0.3.1 + +### Patch Changes + +- 217458a: Updated configuration schema to include the new `allowedDomains` option for the `emailLocalPartMatchingUserEntityName` sign-in resolver. +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3 + - @backstage/backend-plugin-api@1.0.1 + +## 0.3.1-next.1 + +### Patch Changes + +- 217458a: Updated configuration schema to include the new `allowedDomains` option for the `emailLocalPartMatchingUserEntityName` sign-in resolver. +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3-next.1 + - @backstage/backend-plugin-api@1.0.1-next.1 + +## 0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3-next.0 + - @backstage/backend-plugin-api@1.0.1-next.0 + +## 0.3.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0 + - @backstage/plugin-auth-node@0.5.2 + +## 0.3.0-next.2 + +### Patch Changes + +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + +## 0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + +## 0.3.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + +## 0.2.4 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + +## 0.2.4-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## 0.2.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + +## 0.2.4-next.1 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## 0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-auth-node@0.4.17 + +## 0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## 0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.21-next.0 + - @backstage/plugin-auth-node@0.4.16-next.0 + +## 0.2.0 + +### Minor Changes + +- 8efc6cf: **BREAKING**: The `scope` config option have been removed and replaced by the standard `additionalScopes` config. + +### Patch Changes + +- 78a0b08: Internal refactor to handle `BackendFeature` contract change. +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19 + - @backstage/plugin-auth-node@0.4.14 + +## 0.2.0-next.2 + +### Minor Changes + +- 8efc6cf: **BREAKING**: The `scope` config option have been removed and replaced by the standard `additionalScopes` config. + +### Patch Changes + +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.3 + - @backstage/plugin-auth-node@0.4.14-next.3 + +## 0.1.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.2 + - @backstage/plugin-auth-node@0.4.14-next.2 + +## 0.1.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + +## 0.1.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + +## 0.1.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## 0.1.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + +## 0.1.14 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.17 + - @backstage/plugin-auth-node@0.4.12 + +## 0.1.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.17-next.1 + - @backstage/plugin-auth-node@0.4.12-next.1 + +## 0.1.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.17-next.0 + - @backstage/plugin-auth-node@0.4.12-next.0 + +## 0.1.13 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.11 + - @backstage/backend-plugin-api@0.6.16 + +## 0.1.12 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.10 + - @backstage/backend-plugin-api@0.6.15 + +## 0.1.11 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.9 + - @backstage/backend-plugin-api@0.6.14 + +## 0.1.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.9-next.2 + - @backstage/backend-plugin-api@0.6.14-next.2 + +## 0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.14-next.1 + - @backstage/plugin-auth-node@0.4.9-next.1 + +## 0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.8-next.0 + - @backstage/backend-plugin-api@0.6.13-next.0 + +## 0.1.7 + +### Patch Changes + +- 8472188: Added or fixed the `repository` field in `package.json`. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4 + - @backstage/backend-plugin-api@0.6.10 + +## 0.1.7-next.3 + +### Patch Changes + +- 8472188: Added or fixed the `repository` field in `package.json`. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.3 + - @backstage/backend-plugin-api@0.6.10-next.3 + +## 0.1.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.2 + - @backstage/plugin-auth-node@0.4.4-next.2 + +## 0.1.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.1 + - @backstage/plugin-auth-node@0.4.4-next.1 + +## 0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + +## 0.1.6 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + +## 0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## 0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + +## 0.1.6-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## 0.1.5 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + +## 0.1.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## 0.1.5-next.2 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + +## 0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + +## 0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.0 + - @backstage/plugin-auth-node@0.4.2-next.0 + - @backstage/backend-plugin-api@0.6.8-next.0 + +## 0.1.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.9 + - @backstage/backend-plugin-api@0.6.7 + - @backstage/plugin-auth-node@0.4.1 + +## 0.1.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.7-next.2 + - @backstage/backend-common@0.19.9-next.2 + - @backstage/plugin-auth-node@0.4.1-next.2 + +## 0.1.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.9-next.1 + - @backstage/plugin-auth-node@0.4.1-next.1 + - @backstage/backend-plugin-api@0.6.7-next.1 + +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.9-next.0 + - @backstage/backend-plugin-api@0.6.7-next.0 + - @backstage/plugin-auth-node@0.4.1-next.0 + +## 0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.8 + - @backstage/plugin-auth-node@0.4.0 + - @backstage/backend-plugin-api@0.6.6 + +## 0.1.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.8-next.2 + - @backstage/plugin-auth-node@0.4.0-next.2 + - @backstage/backend-plugin-api@0.6.6-next.2 + +## 0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.7-next.1 + - @backstage/backend-plugin-api@0.6.5-next.1 + - @backstage/plugin-auth-node@0.3.2-next.1 + +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.3.2-next.0 + - @backstage/backend-common@0.19.7-next.0 + - @backstage/backend-plugin-api@0.6.5-next.0 + +## 0.1.0 + +### Minor Changes + +- 101cf1d13b04: New module for `@backstage/plugin-auth-backend` that adds a `oauth2` auth provider. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.5 + - @backstage/plugin-auth-node@0.3.0 + - @backstage/backend-plugin-api@0.6.3 + +## 0.1.0-next.0 + +### Minor Changes + +- 101cf1d13b04: New module for `@backstage/plugin-auth-backend` that adds a `oauth2` auth provider. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.3-next.3 + - @backstage/backend-common@0.19.5-next.3 + - @backstage/plugin-auth-node@0.3.0-next.3 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.17-next.0 + +# @backstage/plugin-auth-backend-module-oauth2-proxy-provider + +## 0.2.16-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/errors@1.2.7 + +## 0.2.15 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.2.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/errors@1.2.7 + +## 0.2.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/errors@1.2.7 + +## 0.2.14 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## 0.2.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## 0.2.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.9-next.0 + - @backstage/backend-plugin-api@1.4.5-next.0 + - @backstage/errors@1.2.7 + +## 0.2.13 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4 + - @backstage/plugin-auth-node@0.6.8 + +## 0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## 0.2.12 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.7 + - @backstage/backend-plugin-api@1.4.3 + +## 0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.7-next.0 + - @backstage/backend-plugin-api@1.4.3-next.0 + +## 0.2.11 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6 + - @backstage/backend-plugin-api@1.4.2 + +## 0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/errors@1.2.7 + +## 0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/errors@1.2.7 + +## 0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/errors@1.2.7 + +## 0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.2.8 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/errors@1.2.7 + +## 0.2.8-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/errors@1.2.7 + +## 0.2.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + +## 0.2.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.0 + - @backstage/plugin-auth-node@0.6.3-next.0 + - @backstage/errors@1.2.7 + +## 0.2.7 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.0 + - @backstage/plugin-auth-node@0.6.2 + - @backstage/errors@1.2.7 + +## 0.2.6 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1 + - @backstage/backend-plugin-api@1.2.1 + - @backstage/errors@1.2.7 + +## 0.2.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1-next.1 + - @backstage/backend-plugin-api@1.2.1-next.1 + - @backstage/errors@1.2.7 + +## 0.2.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1-next.0 + - @backstage/backend-plugin-api@1.2.1-next.0 + +## 0.2.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0 + - @backstage/plugin-auth-node@0.6.0 + - @backstage/errors@1.2.7 + +## 0.2.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.2 + - @backstage/plugin-auth-node@0.6.0-next.2 + - @backstage/errors@1.2.7 + +## 0.2.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.1 + - @backstage/plugin-auth-node@0.6.0-next.1 + - @backstage/errors@1.2.7 + +## 0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-auth-node@0.5.7-next.0 + +## 0.2.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.6 + - @backstage/backend-plugin-api@1.1.1 + - @backstage/errors@1.2.7 + +## 0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.1-next.1 + - @backstage/errors@1.2.7-next.0 + - @backstage/plugin-auth-node@0.5.6-next.1 + +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.6-next.0 + - @backstage/backend-plugin-api@1.1.1-next.0 + - @backstage/errors@1.2.6 + +## 0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.5 + - @backstage/backend-plugin-api@1.1.0 + - @backstage/errors@1.2.6 + +## 0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.0-next.2 + - @backstage/errors@1.2.6-next.0 + - @backstage/plugin-auth-node@0.5.5-next.2 + +## 0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.5-next.1 + - @backstage/backend-plugin-api@1.1.0-next.1 + - @backstage/errors@1.2.5 + +## 0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.3-next.0 + - @backstage/plugin-auth-node@0.5.5-next.0 + - @backstage/errors@1.2.5 + +## 0.2.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4 + - @backstage/backend-plugin-api@1.0.2 + - @backstage/errors@1.2.5 + +## 0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4-next.2 + - @backstage/backend-plugin-api@1.0.2-next.2 + - @backstage/errors@1.2.4 + +## 0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.2-next.1 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.5.4-next.1 + +## 0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4-next.0 + - @backstage/backend-plugin-api@1.0.2-next.0 + - @backstage/errors@1.2.4 + +## 0.2.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3 + - @backstage/backend-plugin-api@1.0.1 + - @backstage/errors@1.2.4 + +## 0.2.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3-next.1 + - @backstage/backend-plugin-api@1.0.1-next.1 + - @backstage/errors@1.2.4 + +## 0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3-next.0 + - @backstage/backend-plugin-api@1.0.1-next.0 + - @backstage/errors@1.2.4 + +## 0.2.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0 + - @backstage/plugin-auth-node@0.5.2 + - @backstage/errors@1.2.4 + +## 0.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/errors@1.2.4 + +## 0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/errors@1.2.4 + +## 0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/errors@1.2.4 + +## 0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/errors@1.2.4 + +## 0.1.16-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## 0.1.16-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/errors@1.2.4 + +## 0.1.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## 0.1.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## 0.1.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/errors@1.2.4 + +## 0.1.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## 0.1.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.21-next.0 + - @backstage/plugin-auth-node@0.4.16-next.0 + - @backstage/errors@1.2.4 + +## 0.1.12 + +### Patch Changes + +- 78a0b08: Internal refactor to handle `BackendFeature` contract change. +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19 + - @backstage/plugin-auth-node@0.4.14 + - @backstage/errors@1.2.4 + +## 0.1.12-next.2 + +### Patch Changes + +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.3 + - @backstage/plugin-auth-node@0.4.14-next.3 + - @backstage/errors@1.2.4 + +## 0.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.2 + - @backstage/plugin-auth-node@0.4.14-next.2 + - @backstage/errors@1.2.4 + +## 0.1.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/errors@1.2.4 + +## 0.1.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + +## 0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## 0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/errors@1.2.4 + +## 0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.17 + - @backstage/plugin-auth-node@0.4.12 + - @backstage/errors@1.2.4 + +## 0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.17-next.1 + - @backstage/plugin-auth-node@0.4.12-next.1 + - @backstage/errors@1.2.4 + +## 0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.17-next.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.12-next.0 + +## 0.1.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.11 + - @backstage/backend-plugin-api@0.6.16 + - @backstage/errors@1.2.4 + +## 0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.10 + - @backstage/backend-plugin-api@0.6.15 + - @backstage/errors@1.2.4 + +## 0.1.7 + +### Patch Changes + +- 2af5354: Bump dependency `jose` to v5 +- e77d7a9: Internal refactor to avoid deprecated method. +- b1b012d: Fix issue with `providerInfo` not being set properly for some proxy providers, by making `providerInfo` an explicit optional return from `authenticate` +- Updated dependencies + - @backstage/plugin-auth-node@0.4.9 + - @backstage/errors@1.2.4 + - @backstage/backend-plugin-api@0.6.14 + +## 0.1.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.9-next.2 + - @backstage/backend-plugin-api@0.6.14-next.2 + - @backstage/errors@1.2.4-next.0 + +## 0.1.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.14-next.1 + - @backstage/plugin-auth-node@0.4.9-next.1 + - @backstage/errors@1.2.4-next.0 + +## 0.1.6-next.0 + +### Patch Changes + +- 2af5354: Bump dependency `jose` to v5 +- e77d7a9: Internal refactor to avoid deprecated method. +- b1b012d: Fix issue with `providerInfo` not being set properly for some proxy providers, by making `providerInfo` an explicit optional return from `authenticate` +- Updated dependencies + - @backstage/plugin-auth-node@0.4.8-next.0 + - @backstage/errors@1.2.4-next.0 + - @backstage/backend-plugin-api@0.6.13-next.0 + +## 0.1.2 + +### Patch Changes + +- 8472188: Added or fixed the `repository` field in `package.json`. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4 + - @backstage/backend-plugin-api@0.6.10 + - @backstage/errors@1.2.3 + +## 0.1.2-next.3 + +### Patch Changes + +- 8472188: Added or fixed the `repository` field in `package.json`. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.3 + - @backstage/backend-plugin-api@0.6.10-next.3 + - @backstage/errors@1.2.3 + +## 0.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.2 + - @backstage/plugin-auth-node@0.4.4-next.2 + - @backstage/errors@1.2.3 + +## 0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.4-next.1 + +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/errors@1.2.3 + +## 0.1.1 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/errors@1.2.3 + +## 0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/errors@1.2.3 + +## 0.1.1-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## 0.1.0 + +### Minor Changes + +- 271aa12: Release of `oauth2-proxy-provider` plugin + +### Patch Changes + +- a6be465: Exported the provider as default so it gets discovered when using `featureDiscoveryServiceFactory()` +- 510dab4: Change provider id from `oauth2ProxyProvider` to `oauth2Proxy` +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/errors@1.2.3 + +## 0.1.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## 0.1.0-next.1 + +### Patch Changes + +- a6be465: Exported the provider as default so it gets discovered when using `featureDiscoveryServiceFactory()` +- 510dab4: Change provider id from `oauth2ProxyProvider` to `oauth2Proxy` +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/errors@1.2.3 + +## 0.1.0-next.0 + +### Minor Changes + +- 271aa12c7c: Release of `oauth2-proxy-provider` plugin + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.1 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.4.13-next.0 + +# @backstage/plugin-auth-backend-module-oidc-provider + +## 0.4.12-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-backend@0.26.1-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## 0.4.11 + +### Patch Changes + +- e54fcb2: Added support for custom start URL search parameters (with the new `startUrlSearchParams` config property) +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-auth-backend@0.26.0 + - @backstage/plugin-auth-node@0.6.11 + +## 0.4.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.26.0-next.0 + - @backstage/plugin-auth-node@0.6.10 + +## 0.4.11-next.0 + +### Patch Changes + +- e54fcb2: Added support for custom start URL search parameters (with the new `startUrlSearchParams` config property) +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + - @backstage/plugin-auth-backend@0.25.7 + - @backstage/plugin-auth-node@0.6.10 + +## 0.4.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-auth-backend@0.25.7 + - @backstage/backend-plugin-api@1.6.0 + +## 0.4.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.7-next.1 + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## 0.4.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-auth-backend@0.25.7-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## 0.4.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-backend@0.25.6 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config@1.3.6 + +## 0.4.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-backend@0.25.6-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## 0.4.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.6-next.0 + - @backstage/plugin-auth-node@0.6.9-next.0 + - @backstage/config@1.3.6-next.0 + - @backstage/backend-plugin-api@1.4.5-next.0 + - @backstage/types@1.2.2 + +## 0.4.8 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.5 + - @backstage/backend-plugin-api@1.4.4 + - @backstage/plugin-auth-backend@0.25.5 + - @backstage/plugin-auth-node@0.6.8 + +## 0.4.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-backend@0.25.5-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## 0.4.7 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.4 + - @backstage/plugin-auth-node@0.6.7 + - @backstage/types@1.2.2 + - @backstage/backend-plugin-api@1.4.3 + +## 0.4.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.7-next.0 + - @backstage/backend-plugin-api@1.4.3-next.0 + - @backstage/plugin-auth-backend@0.25.4-next.0 + +## 0.4.6 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.3 + - @backstage/plugin-auth-node@0.6.6 + - @backstage/backend-plugin-api@1.4.2 + +## 0.4.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.3-next.0 + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/types@1.2.1 + +## 0.4.5 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-auth-backend@0.25.2 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-backend@0.25.2-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.4.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-auth-backend@0.25.1 + +## 0.4.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-auth-backend@0.25.1-next.1 + +## 0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-backend@0.25.1-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.4.3 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.0 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + +## 0.4.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.0-next.2 + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + +## 0.4.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.0-next.1 + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + +## 0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.0-next.0 + - @backstage/backend-plugin-api@1.3.1-next.0 + - @backstage/plugin-auth-node@0.6.3-next.0 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + +## 0.4.2 + +### Patch Changes + +- 7495edf: Added custom timeout setting for oidc provider + + Here is an example of how to use a custom timeout with the configuration: + + ```yaml + auth: + oidc: + production: + clientId: ${AUTH_GOOGLE_CLIENT_ID} + clientSecret: ${AUTH_GOOGLE_CLIENT_SECRET} + timeout: + seconds: 30 + ``` + +- Updated dependencies + - @backstage/plugin-auth-backend@0.24.5 + - @backstage/backend-plugin-api@1.3.0 + - @backstage/plugin-auth-node@0.6.2 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + +## 0.4.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.24.5-next.2 + - @backstage/backend-plugin-api@1.2.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-auth-node@0.6.1 + +## 0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.24.5-next.1 + - @backstage/plugin-auth-node@0.6.1 + - @backstage/backend-plugin-api@1.2.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + +## 0.4.2-next.0 + +### Patch Changes + +- 7495edf: Added custom timeout setting for oidc provider + + Here is an example of how to use a custom timeout with the configuration: + + ```yaml + auth: + oidc: + production: + clientId: ${AUTH_GOOGLE_CLIENT_ID} + clientSecret: ${AUTH_GOOGLE_CLIENT_SECRET} + timeout: + seconds: 30 + ``` + +- Updated dependencies + - @backstage/plugin-auth-backend@0.24.5-next.0 + - @backstage/plugin-auth-node@0.6.1 + - @backstage/backend-plugin-api@1.2.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + +## 0.4.1 + +### Patch Changes + +- ce15e30: Fixed repository url in `README.md` +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1 + - @backstage/plugin-auth-backend@0.24.4 + - @backstage/backend-plugin-api@1.2.1 + +## 0.4.1-next.2 + +### Patch Changes + +- ce15e30: Fixed repository url in `README.md` +- Updated dependencies + - @backstage/backend-plugin-api@1.2.1-next.1 + - @backstage/plugin-auth-backend@0.24.4-next.2 + - @backstage/plugin-auth-node@0.6.1-next.1 + +## 0.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1-next.1 + - @backstage/plugin-auth-backend@0.24.4-next.1 + - @backstage/backend-plugin-api@1.2.1-next.1 + +## 0.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.24.4-next.0 + - @backstage/plugin-auth-node@0.6.1-next.0 + - @backstage/backend-plugin-api@1.2.1-next.0 + +## 0.4.0 + +### Minor Changes + +- 61f464e: Added `auth.providers..sessionDuration` config for auth providers to allow the lifespan of user sessions to be configured. + +### Patch Changes + +- 0afc4f2: Simplify the `start` method in the `authenticator` to just return the helper promise +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0 + - @backstage/plugin-auth-node@0.6.0 + - @backstage/plugin-auth-backend@0.24.3 + +## 0.4.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.2 + - @backstage/plugin-auth-backend@0.24.3-next.3 + - @backstage/plugin-auth-node@0.6.0-next.2 + +## 0.4.0-next.2 + +### Minor Changes + +- 61f464e: Added `auth.providers..sessionDuration` config for auth providers to allow the lifespan of user sessions to be configured. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.1 + - @backstage/plugin-auth-node@0.6.0-next.1 + - @backstage/plugin-auth-backend@0.24.3-next.2 + +## 0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.0 + - @backstage/plugin-auth-backend@0.24.3-next.1 + - @backstage/plugin-auth-node@0.5.7-next.0 + +## 0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.0 + - @backstage/plugin-auth-backend@0.24.3-next.0 + - @backstage/plugin-auth-node@0.5.7-next.0 + +## 0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.6 + - @backstage/plugin-auth-backend@0.24.2 + - @backstage/backend-plugin-api@1.1.1 + +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.1-next.1 + - @backstage/plugin-auth-backend@0.24.2-next.1 + - @backstage/plugin-auth-node@0.5.6-next.1 + +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.6-next.0 + - @backstage/plugin-auth-backend@0.24.2-next.0 + - @backstage/backend-plugin-api@1.1.1-next.0 + +## 0.3.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.24.1 + - @backstage/plugin-auth-node@0.5.5 + - @backstage/backend-plugin-api@1.1.0 + +## 0.3.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.0-next.2 + - @backstage/plugin-auth-backend@0.24.1-next.2 + - @backstage/plugin-auth-node@0.5.5-next.2 + +## 0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.24.1-next.1 + - @backstage/plugin-auth-node@0.5.5-next.1 + - @backstage/backend-plugin-api@1.1.0-next.1 + +## 0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.3-next.0 + - @backstage/plugin-auth-node@0.5.5-next.0 + - @backstage/plugin-auth-backend@0.24.1-next.0 + +## 0.3.2 + +### Patch Changes + +- 5d74716: Remove unused backend-common dependency +- Updated dependencies + - @backstage/plugin-auth-backend@0.24.0 + - @backstage/plugin-auth-node@0.5.4 + - @backstage/backend-plugin-api@1.0.2 + +## 0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.24.0-next.2 + - @backstage/plugin-auth-node@0.5.4-next.2 + - @backstage/backend-plugin-api@1.0.2-next.2 + +## 0.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.2-next.1 + - @backstage/plugin-auth-backend@0.24.0-next.1 + - @backstage/plugin-auth-node@0.5.4-next.1 + +## 0.3.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.24.0-next.0 + - @backstage/plugin-auth-node@0.5.4-next.0 + - @backstage/backend-plugin-api@1.0.2-next.0 + +## 0.3.1 + +### Patch Changes + +- 217458a: Updated configuration schema to include the new `allowedDomains` option for the `emailLocalPartMatchingUserEntityName` sign-in resolver. +- 094eaa3: Remove references to in-repo backend-common +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3 + - @backstage/plugin-auth-backend@0.23.1 + - @backstage/backend-plugin-api@1.0.1 + +## 0.3.1-next.1 + +### Patch Changes + +- 217458a: Updated configuration schema to include the new `allowedDomains` option for the `emailLocalPartMatchingUserEntityName` sign-in resolver. +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3-next.1 + - @backstage/backend-plugin-api@1.0.1-next.1 + - @backstage/plugin-auth-backend@0.23.1-next.1 + +## 0.3.1-next.0 + +### Patch Changes + +- 094eaa3: Remove references to in-repo backend-common +- Updated dependencies + - @backstage/plugin-auth-backend@0.23.1-next.0 + - @backstage/plugin-auth-node@0.5.3-next.0 + - @backstage/backend-plugin-api@1.0.1-next.0 + +## 0.3.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-common@0.25.0 + - @backstage/backend-plugin-api@1.0.0 + - @backstage/plugin-auth-node@0.5.2 + - @backstage/plugin-auth-backend@0.23.0 + +## 0.3.0-next.2 + +### Patch Changes + +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-common@0.25.0-next.2 + - @backstage/plugin-auth-node@0.5.2-next.2 + - @backstage/plugin-auth-backend@0.23.0-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + +## 0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/plugin-auth-backend@0.23.0-next.1 + +## 0.3.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-backend@0.23.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + +## 0.2.4 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-auth-backend@0.22.10 + - @backstage/plugin-auth-node@0.5.0 + +## 0.2.4-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/plugin-auth-backend@0.22.10-next.3 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## 0.2.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-auth-backend@0.22.10-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + +## 0.2.4-next.1 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-auth-backend@0.22.10-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-auth-backend@0.22.10-next.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## 0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/plugin-auth-backend@0.22.9 + +## 0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-auth-backend@0.22.9-next.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## 0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.21-next.0 + - @backstage/backend-common@0.23.2-next.0 + - @backstage/plugin-auth-backend@0.22.8-next.0 + - @backstage/plugin-auth-node@0.4.16-next.0 + +## 0.2.0 + +### Minor Changes + +- 8efc6cf: **BREAKING**: The `scope` config option have been removed and replaced by the standard `additionalScopes` config. In addition, `openid`, `profile`, and `email` scopes have been set to required and will always be present. + +### Patch Changes + +- 78a0b08: Internal refactor to handle `BackendFeature` contract change. +- d44a20a: Added additional plugin metadata to `package.json`. +- 4f21993: if oidc server do not provide revocation_endpoint,we should not call revoke function +- Updated dependencies + - @backstage/backend-common@0.23.0 + - @backstage/backend-plugin-api@0.6.19 + - @backstage/plugin-auth-node@0.4.14 + - @backstage/plugin-auth-backend@0.22.6 + +## 0.2.0-next.3 + +### Minor Changes + +- 8efc6cf: **BREAKING**: The `scope` config option have been removed and replaced by the standard `additionalScopes` config. In addition, `openid`, `profile`, and `email` scopes have been set to required and will always be present. + +### Patch Changes + +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.3 + - @backstage/plugin-auth-node@0.4.14-next.3 + - @backstage/plugin-auth-backend@0.22.6-next.3 + - @backstage/backend-common@0.23.0-next.3 + +## 0.1.10-next.2 + +### Patch Changes + +- 4f21993: if oidc server do not provide revocation_endpoint,we should not call revoke function +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.2 + - @backstage/backend-common@0.23.0-next.2 + - @backstage/plugin-auth-backend@0.22.6-next.2 + - @backstage/plugin-auth-node@0.4.14-next.2 + +## 0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-backend@0.22.6-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + +## 0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-auth-backend@0.22.6-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + +## 0.1.9 + +### Patch Changes + +- dd53bf3: Add nonce to authorize request to be added in ID token +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-backend@0.22.5 + - @backstage/plugin-auth-node@0.4.13 + +## 0.1.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-backend@0.22.5-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## 0.1.9-next.0 + +### Patch Changes + +- dd53bf3: Add nonce to authorize request to be added in ID token +- Updated dependencies + - @backstage/plugin-auth-backend@0.22.5-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + +## 0.1.8 + +### Patch Changes + +- 28eb473: Support revoke refresh token to oidc logout function +- Updated dependencies + - @backstage/backend-common@0.21.7 + - @backstage/plugin-auth-backend@0.22.4 + - @backstage/backend-plugin-api@0.6.17 + - @backstage/plugin-auth-node@0.4.12 + +## 0.1.8-next.1 + +### Patch Changes + +- 28eb473: Support revoke refresh token to oidc logout function +- Updated dependencies + - @backstage/backend-common@0.21.7-next.1 + - @backstage/plugin-auth-backend@0.22.4-next.1 + - @backstage/backend-plugin-api@0.6.17-next.1 + - @backstage/plugin-auth-node@0.4.12-next.1 + +## 0.1.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.7-next.0 + - @backstage/backend-plugin-api@0.6.17-next.0 + - @backstage/plugin-auth-backend@0.22.4-next.0 + - @backstage/plugin-auth-node@0.4.12-next.0 + +## 0.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.22.3 + - @backstage/plugin-auth-node@0.4.11 + - @backstage/backend-common@0.21.6 + - @backstage/backend-plugin-api@0.6.16 + +## 0.1.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.5 + - @backstage/plugin-auth-backend@0.22.2 + - @backstage/plugin-auth-node@0.4.10 + - @backstage/backend-plugin-api@0.6.15 + +## 0.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.22.1 + +## 0.1.4 + +### Patch Changes + +- 74b1dc9: Increased HTTP request timeout used by OIDC authenticator. +- 2af5354: Bump dependency `jose` to v5 +- Updated dependencies + - @backstage/backend-common@0.21.4 + - @backstage/plugin-auth-node@0.4.9 + - @backstage/backend-plugin-api@0.6.14 + - @backstage/plugin-auth-backend@0.22.0 + +## 0.1.4-next.2 + +### Patch Changes + +- 74b1dc9: Increased HTTP request timeout used by OIDC authenticator. +- Updated dependencies + - @backstage/backend-common@0.21.4-next.2 + - @backstage/plugin-auth-backend@0.22.0-next.2 + - @backstage/plugin-auth-node@0.4.9-next.2 + - @backstage/backend-plugin-api@0.6.14-next.2 + +## 0.1.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.4-next.1 + - @backstage/backend-plugin-api@0.6.14-next.1 + - @backstage/plugin-auth-backend@0.22.0-next.1 + - @backstage/plugin-auth-node@0.4.9-next.1 + +## 0.1.3-next.0 + +### Patch Changes + +- 2af5354: Bump dependency `jose` to v5 +- Updated dependencies + - @backstage/backend-common@0.21.3-next.0 + - @backstage/plugin-auth-node@0.4.8-next.0 + - @backstage/backend-plugin-api@0.6.13-next.0 + - @backstage/plugin-auth-backend@0.22.0-next.0 + +## 0.1.0 + +### Minor Changes + +- 5d2fcba: Created new `@backstage/plugin-auth-backend-module-oidc-provider` module package to house oidc auth provider migration. + +### Patch Changes + +- e471890: Fixed a bug where the OIDC authenticator did not properly persist granted OAuth scopes. +- 8472188: Added or fixed the `repository` field in `package.json`. +- 8afb6f4: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.21.0 + - @backstage/plugin-auth-backend@0.21.0 + - @backstage/plugin-auth-node@0.4.4 + - @backstage/backend-plugin-api@0.6.10 + +## 0.1.0-next.3 + +### Patch Changes + +- e471890: Fixed a bug where the OIDC authenticator did not properly persist granted OAuth scopes. +- 8472188: Added or fixed the `repository` field in `package.json`. +- Updated dependencies + - @backstage/backend-common@0.21.0-next.3 + - @backstage/plugin-auth-backend@0.21.0-next.3 + - @backstage/plugin-auth-node@0.4.4-next.3 + - @backstage/backend-plugin-api@0.6.10-next.3 + +## 0.1.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.21.0-next.2 + - @backstage/backend-common@0.21.0-next.2 + - @backstage/backend-plugin-api@0.6.10-next.2 + - @backstage/plugin-auth-node@0.4.4-next.2 + +## 0.1.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.1 + - @backstage/backend-common@0.21.0-next.1 + - @backstage/plugin-auth-backend@0.20.4-next.1 + - @backstage/plugin-auth-node@0.4.4-next.1 + +## 0.1.0-next.0 + +### Minor Changes + +- 5d2fcba: Created new `@backstage/plugin-auth-backend-module-oidc-provider` module package to house oidc auth provider migration. + +### Patch Changes + +- 8afb6f4: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/plugin-auth-backend@0.20.4-next.0 + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + +## @backstage/plugin-auth-backend-module-okta-provider@0.2.12-next.0 + +# @backstage/plugin-auth-backend-module-okta-provider + +## 0.2.11-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + +## 0.2.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.2.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + +## 0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## 0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## 0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.9-next.0 + - @backstage/backend-plugin-api@1.4.5-next.0 + +## 0.2.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4 + - @backstage/plugin-auth-node@0.6.8 + +## 0.2.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## 0.2.7 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.7 + - @backstage/backend-plugin-api@1.4.3 + +## 0.2.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.7-next.0 + - @backstage/backend-plugin-api@1.4.3-next.0 + +## 0.2.6 + +### Patch Changes + +- 94476d2: Updated dependency `@davidzemon/passport-okta-oauth` to `^0.0.7`. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6 + - @backstage/backend-plugin-api@1.4.2 + +## 0.2.6-next.1 + +### Patch Changes + +- 94476d2: Updated dependency `@davidzemon/passport-okta-oauth` to `^0.0.7`. + +## 0.2.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + +## 0.2.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.2.4 + +### Patch Changes + +- b25977f: Updated dependency `@davidzemon/passport-okta-oauth` to `^0.0.6`. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + +## 0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.2.3 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## 0.2.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + +## 0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + +## 0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.0 + - @backstage/plugin-auth-node@0.6.3-next.0 + +## 0.2.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.0 + - @backstage/plugin-auth-node@0.6.2 + +## 0.2.1 + +### Patch Changes + +- ce15e30: Fixed repository url in `README.md` +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1 + - @backstage/backend-plugin-api@1.2.1 + +## 0.2.1-next.2 + +### Patch Changes + +- ce15e30: Fixed repository url in `README.md` +- Updated dependencies + - @backstage/backend-plugin-api@1.2.1-next.1 + - @backstage/plugin-auth-node@0.6.1-next.1 + +## 0.2.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1-next.1 + - @backstage/backend-plugin-api@1.2.1-next.1 + +## 0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1-next.0 + - @backstage/backend-plugin-api@1.2.1-next.0 + +## 0.2.0 + +### Minor Changes + +- 61f464e: Added `auth.providers..sessionDuration` config for auth providers to allow the lifespan of user sessions to be configured. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0 + - @backstage/plugin-auth-node@0.6.0 + +## 0.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.2 + - @backstage/plugin-auth-node@0.6.0-next.2 + +## 0.2.0-next.1 + +### Minor Changes + +- 61f464e: Added `auth.providers..sessionDuration` config for auth providers to allow the lifespan of user sessions to be configured. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.1 + - @backstage/plugin-auth-node@0.6.0-next.1 + +## 0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.0 + - @backstage/plugin-auth-node@0.5.7-next.0 + +## 0.1.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.6 + - @backstage/backend-plugin-api@1.1.1 + +## 0.1.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.1-next.1 + - @backstage/plugin-auth-node@0.5.6-next.1 + +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.6-next.0 + - @backstage/backend-plugin-api@1.1.1-next.0 + +## 0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.5 + - @backstage/backend-plugin-api@1.1.0 + +## 0.1.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.0-next.2 + - @backstage/plugin-auth-node@0.5.5-next.2 + +## 0.1.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.5-next.1 + - @backstage/backend-plugin-api@1.1.0-next.1 + +## 0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.3-next.0 + - @backstage/plugin-auth-node@0.5.5-next.0 + +## 0.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4 + - @backstage/backend-plugin-api@1.0.2 + +## 0.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4-next.2 + - @backstage/backend-plugin-api@1.0.2-next.2 + +## 0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.2-next.1 + - @backstage/plugin-auth-node@0.5.4-next.1 + +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4-next.0 + - @backstage/backend-plugin-api@1.0.2-next.0 + +## 0.1.1 + +### Patch Changes + +- 217458a: Updated configuration schema to include the new `allowedDomains` option for the `emailLocalPartMatchingUserEntityName` sign-in resolver. +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3 + - @backstage/backend-plugin-api@1.0.1 + +## 0.1.1-next.1 + +### Patch Changes + +- 217458a: Updated configuration schema to include the new `allowedDomains` option for the `emailLocalPartMatchingUserEntityName` sign-in resolver. +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3-next.1 + - @backstage/backend-plugin-api@1.0.1-next.1 + +## 0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3-next.0 + - @backstage/backend-plugin-api@1.0.1-next.0 + +## 0.1.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0 + - @backstage/plugin-auth-node@0.5.2 + +## 0.1.0-next.2 + +### Patch Changes + +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + +## 0.1.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + +## 0.1.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + +## 0.0.16 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + +## 0.0.16-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## 0.0.16-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + +## 0.0.16-next.1 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## 0.0.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## 0.0.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-auth-node@0.4.17 + +## 0.0.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## 0.0.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.21-next.0 + - @backstage/plugin-auth-node@0.4.16-next.0 + +## 0.0.12 + +### Patch Changes + +- 78a0b08: Internal refactor to handle `BackendFeature` contract change. +- 8efc6cf: Added support for the new shared `additionalScopes` configuration, which means it can now also be specified as an array. In addition, the `openid`, `email`, `profile`, and `offline_access` scopes have been set to required and will always be present. +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19 + - @backstage/plugin-auth-node@0.4.14 + +## 0.0.12-next.2 + +### Patch Changes + +- 8efc6cf: Added support for the new shared `additionalScopes` configuration, which means it can now also be specified as an array. In addition, the `openid`, `email`, `profile`, and `offline_access` scopes have been set to required and will always be present. +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.3 + - @backstage/plugin-auth-node@0.4.14-next.3 + +## 0.0.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.2 + - @backstage/plugin-auth-node@0.4.14-next.2 + +## 0.0.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + +## 0.0.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + +## 0.0.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## 0.0.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + +## 0.0.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.17 + - @backstage/plugin-auth-node@0.4.12 + +## 0.0.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.17-next.1 + - @backstage/plugin-auth-node@0.4.12-next.1 + +## 0.0.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.17-next.0 + - @backstage/plugin-auth-node@0.4.12-next.0 + +## 0.0.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.11 + - @backstage/backend-plugin-api@0.6.16 + +## 0.0.8 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.10 + - @backstage/backend-plugin-api@0.6.15 + +## 0.0.7 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.9 + - @backstage/backend-plugin-api@0.6.14 + +## 0.0.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.9-next.2 + - @backstage/backend-plugin-api@0.6.14-next.2 + +## 0.0.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.14-next.1 + - @backstage/plugin-auth-node@0.4.9-next.1 + +## 0.0.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.8-next.0 + - @backstage/backend-plugin-api@0.6.13-next.0 + +## 0.0.3 + +### Patch Changes + +- cd5114c: Added missing `additionalScopes` option to configuration schema. +- 8472188: Added or fixed the `repository` field in `package.json`. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4 + - @backstage/backend-plugin-api@0.6.10 + +## 0.0.3-next.3 + +### Patch Changes + +- 8472188: Added or fixed the `repository` field in `package.json`. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.3 + - @backstage/backend-plugin-api@0.6.10-next.3 + +## 0.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.2 + - @backstage/plugin-auth-node@0.4.4-next.2 + +## 0.0.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.1 + - @backstage/plugin-auth-node@0.4.4-next.1 + +## 0.0.3-next.0 + +### Patch Changes + +- cd5114c: Added missing `additionalScopes` option to configuration schema. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + +## 0.0.2 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + +## 0.0.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## 0.0.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + +## 0.0.2-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## 0.0.1 + +### Patch Changes + +- e1c189b: Adds okta-provider backend module for the auth plugin +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + +## 0.0.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## 0.0.1-next.2 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + +## 0.0.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + +## 0.0.1-next.0 + +### Patch Changes + +- e1c189b524: Adds okta-provider backend module for the auth plugin +- Updated dependencies + - @backstage/backend-common@0.20.0-next.0 + - @backstage/plugin-auth-node@0.4.2-next.0 + - @backstage/backend-plugin-api@0.6.8-next.0 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.12-next.0 + +# @backstage/plugin-auth-backend-module-onelogin-provider + +## 0.3.11-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + +## 0.3.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.3.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + +## 0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## 0.3.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## 0.3.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.9-next.0 + - @backstage/backend-plugin-api@1.4.5-next.0 + +## 0.3.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4 + - @backstage/plugin-auth-node@0.6.8 + +## 0.3.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## 0.3.7 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.7 + - @backstage/backend-plugin-api@1.4.3 + +## 0.3.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.7-next.0 + - @backstage/backend-plugin-api@1.4.3-next.0 + +## 0.3.6 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6 + - @backstage/backend-plugin-api@1.4.2 + +## 0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + +## 0.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.3.3 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## 0.3.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + +## 0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + +## 0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.0 + - @backstage/plugin-auth-node@0.6.3-next.0 + +## 0.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.0 + - @backstage/plugin-auth-node@0.6.2 + +## 0.3.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1 + - @backstage/backend-plugin-api@1.2.1 + +## 0.3.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1-next.1 + - @backstage/backend-plugin-api@1.2.1-next.1 + +## 0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1-next.0 + - @backstage/backend-plugin-api@1.2.1-next.0 + +## 0.3.0 + +### Minor Changes + +- 61f464e: Added `auth.providers..sessionDuration` config for auth providers to allow the lifespan of user sessions to be configured. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0 + - @backstage/plugin-auth-node@0.6.0 + +## 0.3.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.2 + - @backstage/plugin-auth-node@0.6.0-next.2 + +## 0.3.0-next.1 + +### Minor Changes + +- 61f464e: Added `auth.providers..sessionDuration` config for auth providers to allow the lifespan of user sessions to be configured. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.1 + - @backstage/plugin-auth-node@0.6.0-next.1 + +## 0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.0 + - @backstage/plugin-auth-node@0.5.7-next.0 + +## 0.2.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.6 + - @backstage/backend-plugin-api@1.1.1 + +## 0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.1-next.1 + - @backstage/plugin-auth-node@0.5.6-next.1 + +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.6-next.0 + - @backstage/backend-plugin-api@1.1.1-next.0 + +## 0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.5 + - @backstage/backend-plugin-api@1.1.0 + +## 0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.0-next.2 + - @backstage/plugin-auth-node@0.5.5-next.2 + +## 0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.5-next.1 + - @backstage/backend-plugin-api@1.1.0-next.1 + +## 0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.3-next.0 + - @backstage/plugin-auth-node@0.5.5-next.0 + +## 0.2.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4 + - @backstage/backend-plugin-api@1.0.2 + +## 0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4-next.2 + - @backstage/backend-plugin-api@1.0.2-next.2 + +## 0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.2-next.1 + - @backstage/plugin-auth-node@0.5.4-next.1 + +## 0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4-next.0 + - @backstage/backend-plugin-api@1.0.2-next.0 + +## 0.2.1 + +### Patch Changes + +- 217458a: Updated configuration schema to include the new `allowedDomains` option for the `emailLocalPartMatchingUserEntityName` sign-in resolver. +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3 + - @backstage/backend-plugin-api@1.0.1 + +## 0.2.1-next.1 + +### Patch Changes + +- 217458a: Updated configuration schema to include the new `allowedDomains` option for the `emailLocalPartMatchingUserEntityName` sign-in resolver. +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3-next.1 + - @backstage/backend-plugin-api@1.0.1-next.1 + +## 0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3-next.0 + - @backstage/backend-plugin-api@1.0.1-next.0 + +## 0.2.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0 + - @backstage/plugin-auth-node@0.5.2 + +## 0.2.0-next.2 + +### Patch Changes + +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + +## 0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + +## 0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + +## 0.1.4 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + +## 0.1.4-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## 0.1.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + +## 0.1.4-next.1 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## 0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-auth-node@0.4.17 + +## 0.1.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.21-next.0 + - @backstage/plugin-auth-node@0.4.16-next.0 + +## 0.1.0 + +### Minor Changes + +- 566d7cb: Separate out the OneLogin provider into its own module + +### Patch Changes + +- 78a0b08: Internal refactor to handle `BackendFeature` contract change. +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19 + - @backstage/plugin-auth-node@0.4.14 + +## 0.1.0-next.0 + +### Minor Changes + +- 566d7cb: Separate out the OneLogin provider into its own module + +### Patch Changes + +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.3 + - @backstage/plugin-auth-node@0.4.14-next.3 + +## @backstage/plugin-auth-node@0.6.13-next.0 + +# @backstage/plugin-auth-node + +## 0.6.12-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.6.11 + +### Patch Changes + +- 4eeba9e: Upgrade `zod-validation-error` to version 4 +- 872eb91: Upgrade `zod-to-json-schema` to latest version +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + +## 0.6.10 + +### Patch Changes + +- 2389358: remove leading dot in auth cookie cleanup call +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- b35f8b2: Fixed chunked cookie replacing edge case in OAuthCookieManager class where some of the old chunks would not get removed if new chunked cookie would have fewer chunks. +- e9dd634: fix flawed cookie removal logic with chunked tokens +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + +## 0.6.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- e9dd634: fix flawed cookie removal logic with chunked tokens +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.6.10-next.0 + +### Patch Changes + +- 2389358: remove leading dot in auth cookie cleanup call +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.6.9 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/catalog-client@1.12.1 + +## 0.6.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + +## 0.6.9-next.0 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/config@1.3.6-next.0 + - @backstage/catalog-model@1.7.6-next.0 + - @backstage/backend-plugin-api@1.4.5-next.0 + - @backstage/catalog-client@1.12.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.6.8 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.5 + - @backstage/backend-plugin-api@1.4.4 + +## 0.6.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/catalog-client@1.12.0 + +## 0.6.7 + +### Patch Changes + +- 54ddfef: Updating plugin metadata +- 3aff9e1: Changes OAuth cookies from domain-scoped to host-only by avoid setting the domain attribute in the default cookie configurer. +- Updated dependencies + - @backstage/catalog-client@1.12.0 + - @backstage/types@1.2.2 + - @backstage/backend-plugin-api@1.4.3 + +## 0.6.7-next.1 + +### Patch Changes + +- 54ddfef: Updating plugin metadata +- Updated dependencies + - @backstage/catalog-client@1.12.0-next.0 + +## 0.6.7-next.0 + +### Patch Changes + +- 3aff9e1: Changes OAuth cookies from domain-scoped to host-only by avoid setting the domain attribute in the default cookie configurer. +- Updated dependencies + - @backstage/backend-plugin-api@1.4.3-next.0 + +## 0.6.6 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.11.0 + - @backstage/backend-plugin-api@1.4.2 + +## 0.6.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.6.5 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/backend-plugin-api@1.4.1 + +## 0.6.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + +## 0.6.4 + +### Patch Changes + +- 0169b23: Internal tweak to avoid circular dependencies +- Updated dependencies + - @backstage/catalog-client@1.10.1 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.6.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.6.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + +## 0.6.3 + +### Patch Changes + +- 332e934: Added the `identity` property to `BackstageSignInResult`. + + The `prepareBackstageIdentityResponse` function will now also forward the `identity` to the response if present in the provided sign-in result. + +- ab53e6f: Added a new `dangerousEntityRefFallback` option to the `signInWithCatalogUser` method in `AuthResolverContext`. The option will cause the provided entity reference to be used as a fallback in case the user is not found in the catalog. It is up to the caller to provide the fallback entity reference. + + Auth providers that include pre-defined sign-in resolvers are encouraged to define a flag named `dangerouslyAllowSignInWithoutUserInCatalog` in their config, which in turn enables use of the `dangerousEntityRefFallback` option. For example: + + ```ts + export const usernameMatchingUserEntityName = createSignInResolverFactory({ + optionsSchema: z + .object({ + dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(), + }) + .optional(), + create(options = {}) { + return async ( + info: SignInInfo>, + ctx, + ) => { + const { username } = info.result.fullProfile; + if (!username) { + throw new Error('User profile does not contain a username'); + } + + return ctx.signInWithCatalogUser( + { entityRef: { name: username } }, + { + dangerousEntityRefFallback: + options?.dangerouslyAllowSignInWithoutUserInCatalog + ? { entityRef: { name: username } } + : undefined, + }, + ); + }; + }, + }); + ``` + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.6.3-next.2 + +### Patch Changes + +- ab53e6f: Added a new `dangerousEntityRefFallback` option to the `signInWithCatalogUser` method in `AuthResolverContext`. The option will cause the provided entity reference to be used as a fallback in case the user is not found in the catalog. It is up to the caller to provide the fallback entity reference. + + Auth providers that include pre-defined sign-in resolvers are encouraged to define a flag named `dangerouslyAllowSignInWithoutUserInCatalog` in their config, which in turn enables use of the `dangerousEntityRefFallback` option. For example: + + ```ts + export const usernameMatchingUserEntityName = createSignInResolverFactory({ + optionsSchema: z + .object({ + dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(), + }) + .optional(), + create(options = {}) { + return async ( + info: SignInInfo>, + ctx, + ) => { + const { username } = info.result.fullProfile; + if (!username) { + throw new Error('User profile does not contain a username'); + } + + return ctx.signInWithCatalogUser( + { entityRef: { name: username } }, + { + dangerousEntityRefFallback: + options?.dangerouslyAllowSignInWithoutUserInCatalog + ? { entityRef: { name: username } } + : undefined, + }, + ); + }; + }, + }); + ``` + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.6.3-next.1 + +### Patch Changes + +- 332e934: Added the `identity` property to `BackstageSignInResult`. + + The `prepareBackstageIdentityResponse` function will now also forward the `identity` to the response if present in the provided sign-in result. + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.6.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/backend-plugin-api@1.3.1-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.6.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.0 + - @backstage/catalog-client@1.9.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.6.1 + +### Patch Changes + +- b6702ea: Added `AuthResolverContext.resolveOwnershipEntityRefs` as a way of accessing the default ownership resolution logic in sign-in resolvers, replacing `getDefaultOwnershipEntityRefs` from `@backstage/plugin-auth-backend`. +- Updated dependencies + - @backstage/backend-plugin-api@1.2.1 + - @backstage/catalog-client@1.9.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.6.1-next.1 + +### Patch Changes + +- b6702ea: Added `AuthResolverContext.resolveOwnershipEntityRefs` as a way of accessing the default ownership resolution logic in sign-in resolvers, replacing `getDefaultOwnershipEntityRefs` from `@backstage/plugin-auth-backend`. +- Updated dependencies + - @backstage/backend-plugin-api@1.2.1-next.1 + - @backstage/catalog-client@1.9.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.6.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.1-next.0 + +## 0.6.0 + +### Minor Changes + +- 61f464e: Added `auth.providers..sessionDuration` config for auth providers to allow the lifespan of user sessions to be configured. + +### Patch Changes + +- 8699b79: Removed unused dependencies +- cb316fc: Add an `error` handler to the `strategy` to reject the `executeRedirectStrategy` +- ab9a6fb: Added `scopeAlreadyGranted` property to `OAuthAuthenticatorRefreshInput`, signaling to the provider whether the requested scope has already been granted when persisting session scope. +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0 + - @backstage/catalog-client@1.9.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.6.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.2 + - @backstage/catalog-client@1.9.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.6.0-next.1 + +### Minor Changes + +- 61f464e: Added `auth.providers..sessionDuration` config for auth providers to allow the lifespan of user sessions to be configured. + +### Patch Changes + +- 8699b79: Removed unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.1 + - @backstage/catalog-client@1.9.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.5.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.0 + - @backstage/catalog-client@1.9.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.5.6 + +### Patch Changes + +- d9d62ef: Remove some internal usages of the backend-common package +- 8379bf4: Remove usages of `PluginDatabaseManager` and `PluginEndpointDiscovery` and replace with their equivalent service types +- Updated dependencies + - @backstage/types@1.2.1 + - @backstage/backend-plugin-api@1.1.1 + - @backstage/catalog-client@1.9.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.5.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.2.1-next.0 + - @backstage/backend-plugin-api@1.1.1-next.1 + - @backstage/catalog-model@1.7.3-next.0 + - @backstage/config@1.3.2-next.0 + - @backstage/errors@1.2.7-next.0 + - @backstage/catalog-client@1.9.1-next.0 + +## 0.5.6-next.0 + +### Patch Changes + +- d9d62ef: Remove some internal usages of the backend-common package +- 8379bf4: Remove usages of `PluginDatabaseManager` and `PluginEndpointDiscovery` and replace with their equivalent service types +- Updated dependencies + - @backstage/backend-plugin-api@1.1.1-next.0 + - @backstage/catalog-client@1.9.0 + - @backstage/catalog-model@1.7.2 + - @backstage/config@1.3.1 + - @backstage/errors@1.2.6 + - @backstage/types@1.2.0 + +## 0.5.5 + +### Patch Changes + +- c907440: Improved error forwarding for OAuth refresh endpoints +- 1e624ca: Restrict `@types/express` version range from `*` to `^4.17.6`. +- 5c9cc05: Use native fetch instead of node-fetch +- Updated dependencies + - @backstage/backend-plugin-api@1.1.0 + - @backstage/catalog-client@1.9.0 + - @backstage/errors@1.2.6 + - @backstage/catalog-model@1.7.2 + - @backstage/config@1.3.1 + - @backstage/types@1.2.0 + +## 0.5.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.0-next.2 + - @backstage/errors@1.2.6-next.0 + - @backstage/catalog-client@1.9.0-next.2 + - @backstage/catalog-model@1.7.2-next.0 + - @backstage/config@1.3.1-next.0 + - @backstage/types@1.2.0 + +## 0.5.5-next.1 + +### Patch Changes + +- c907440: Improved error forwarding for OAuth refresh endpoints +- 5c9cc05: Use native fetch instead of node-fetch +- Updated dependencies + - @backstage/catalog-client@1.9.0-next.1 + - @backstage/backend-plugin-api@1.1.0-next.1 + - @backstage/catalog-model@1.7.1 + - @backstage/config@1.3.0 + - @backstage/errors@1.2.5 + - @backstage/types@1.2.0 + +## 0.5.5-next.0 + +### Patch Changes + +- 1e624ca: Restrict `@types/express` version range from `*` to `^4.17.6`. +- Updated dependencies + - @backstage/backend-plugin-api@1.0.3-next.0 + - @backstage/catalog-client@1.8.1-next.0 + - @backstage/catalog-model@1.7.1 + - @backstage/config@1.3.0 + - @backstage/errors@1.2.5 + - @backstage/types@1.2.0 + +## 0.5.4 + +### Patch Changes + +- a0a9a4a: Browsers silently drop cookies that exceed 4KB, which can be problematic for refresh tokens and other large cookies.This update ensures that large cookies, like refresh tokens, are not dropped by browsers, maintaining the integrity of the authentication process. The changes include both the implementation of the cookie splitting logic and corresponding tests to validate the new functionality. +- 4e58bc7: Upgrade to uuid v11 internally +- Updated dependencies + - @backstage/catalog-client@1.8.0 + - @backstage/config@1.3.0 + - @backstage/types@1.2.0 + - @backstage/backend-plugin-api@1.0.2 + - @backstage/catalog-model@1.7.1 + - @backstage/errors@1.2.5 + +## 0.5.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.8.0-next.1 + - @backstage/backend-plugin-api@1.0.2-next.2 + - @backstage/catalog-model@1.7.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.5.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.2-next.1 + - @backstage/catalog-client@1.8.0-next.0 + - @backstage/catalog-model@1.7.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.5.4-next.0 + +### Patch Changes + +- a0a9a4a: Browsers silently drop cookies that exceed 4KB, which can be problematic for refresh tokens and other large cookies.This update ensures that large cookies, like refresh tokens, are not dropped by browsers, maintaining the integrity of the authentication process. The changes include both the implementation of the cookie splitting logic and corresponding tests to validate the new functionality. +- Updated dependencies + - @backstage/catalog-client@1.8.0-next.0 + - @backstage/backend-plugin-api@1.0.2-next.0 + - @backstage/catalog-model@1.7.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.5.3 + +### Patch Changes + +- 217458a: Added a new `allowedDomains` option for the common `emailLocalPartMatchingUserEntityName` sign-in resolver. +- 094eaa3: Remove references to in-repo backend-common +- e4ad29a: Fix authentication error handling using redirect flow via `enableExperimentalRedirectFlow` config. If an error is caught during authentication, the user is redirected back to app origin with `error` query parameter containing the error message. +- Updated dependencies + - @backstage/catalog-client@1.7.1 + - @backstage/backend-plugin-api@1.0.1 + - @backstage/catalog-model@1.7.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.5.3-next.1 + +### Patch Changes + +- 217458a: Added a new `allowedDomains` option for the common `emailLocalPartMatchingUserEntityName` sign-in resolver. +- Updated dependencies + - @backstage/catalog-client@1.7.1-next.0 + - @backstage/backend-plugin-api@1.0.1-next.1 + - @backstage/catalog-model@1.7.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.5.3-next.0 + +### Patch Changes + +- 094eaa3: Remove references to in-repo backend-common +- Updated dependencies + - @backstage/backend-plugin-api@1.0.1-next.0 + - @backstage/catalog-client@1.7.0 + - @backstage/catalog-model@1.7.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.5.2 + +### Patch Changes + +- c46eb0f: Extend the "unable to resolve user identity" message +- d908d8c: Accepts an optional options object in the `PassportOAuthAuthenticatorHelper.authenticate` method. +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- 6f409b7: The `emailMatchingUserEntityProfileEmail` sign-in resolver will now also try matching emails with plus addressing removed. +- Updated dependencies + - @backstage/backend-common@0.25.0 + - @backstage/backend-plugin-api@1.0.0 + - @backstage/catalog-model@1.7.0 + - @backstage/catalog-client@1.7.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.5.2-next.2 + +### Patch Changes + +- d908d8c: Accepts an optional options object in the `PassportOAuthAuthenticatorHelper.authenticate` method. +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-common@0.25.0-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/catalog-client@1.7.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.5.2-next.1 + +### Patch Changes + +- c46eb0f: Extend the "unable to resolve user identity" message +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.5.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.5.0 + +### Minor Changes + +- 579afd0: **BREAKING**: Sign-in resolvers configured via `.signIn.resolvers` now take precedence over sign-in resolvers passed to `signInResolver` option of `createOAuthProviderFactory`. This effectively makes sign-in resolvers passed via the `signInResolver` the default one, which you can then override through configuration. + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.5.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.5.0-next.2 + +### Minor Changes + +- 579afd0: **BREAKING**: Sign-in resolvers configured via `.signIn.resolvers` now take precedence over sign-in resolvers passed to `signInResolver` option of `createOAuthProviderFactory`. This effectively makes sign-in resolvers passed via the `signInResolver` the default one, which you can then override through configuration. + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.4.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.4.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.4.17 + +### Patch Changes + +- 55c1a72: Fix issues with Atlassian OAuth provider: retrieve the email and photo that were not in arrays but rather in single props. +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.4.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.4.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.21-next.0 + - @backstage/backend-common@0.23.2-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.4.14 + +### Patch Changes + +- 798ec37: Updated scope management for OAuth providers, where the `createOAuthAuthenticator` now accepts a new collection of `scopes` options: + + - `scopes.persist` - Whether scopes should be persisted, replaces the `shouldPersistScopes` option. + - `scopes.required` - A list of required scopes that will always be requested. + - `scopes.transform` - A function that can be used to transform the scopes before they are requested. + + The `createOAuthProviderFactory` has also received a new `additionalScopes` option, and will also read `additionalScopes` from the auth provider configuration. Both of these can be used to add additional scopes that should always be requested. + + A significant change under the hood that this new scope management brings is that providers that persist scopes will now always merge the already granted scopes with the requested ones. The previous behavior was that the full authorization flow would not include existing scopes, while the refresh flow would only include the existing scopes. + +- d44a20a: Added additional plugin metadata to `package.json`. + +- Updated dependencies + - @backstage/backend-common@0.23.0 + - @backstage/backend-plugin-api@0.6.19 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.4.14-next.3 + +### Patch Changes + +- 798ec37: Updated scope management for OAuth providers, where the `createOAuthAuthenticator` now accepts a new collection of `scopes` options: + + - `scopes.persist` - Whether scopes should be persisted, replaces the `shouldPersistScopes` option. + - `scopes.required` - A list of required scopes that will always be requested. + - `scopes.transform` - A function that can be used to transform the scopes before they are requested. + + The `createOAuthProviderFactory` has also received a new `additionalScopes` option, and will also read `additionalScopes` from the auth provider configuration. Both of these can be used to add additional scopes that should always be requested. + + A significant change under the hood that this new scope management brings is that providers that persist scopes will now always merge the already granted scopes with the requested ones. The previous behavior was that the full authorization flow would not include existing scopes, while the refresh flow would only include the existing scopes. + +- d44a20a: Added additional plugin metadata to `package.json`. + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.3 + - @backstage/backend-common@0.23.0-next.3 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.4.14-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.2 + - @backstage/backend-common@0.23.0-next.2 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.4.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + +## 0.4.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.4.13 + +### Patch Changes + +- ea9262b: Allow overriding default ownership resolving with the new `AuthOwnershipResolutionExtensionPoint` +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/catalog-client@1.6.5 + +## 0.4.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## 0.4.13-next.0 + +### Patch Changes + +- ea9262b: Allow overriding default ownership resolving with the new `AuthOwnershipResolutionExtensionPoint` +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.4.12 + +### Patch Changes + +- d62bc51: Add `tokenTypes` export with constants for various Backstage token types. +- Updated dependencies + - @backstage/backend-common@0.21.7 + - @backstage/backend-plugin-api@0.6.17 + - @backstage/catalog-client@1.6.4 + - @backstage/catalog-model@1.4.5 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.4.12-next.1 + +### Patch Changes + +- d62bc51: Add `tokenTypes` export with constants for various Backstage token types. +- Updated dependencies + - @backstage/backend-common@0.21.7-next.1 + - @backstage/backend-plugin-api@0.6.17-next.1 + - @backstage/catalog-client@1.6.4-next.0 + - @backstage/catalog-model@1.4.5 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.4.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.7-next.0 + - @backstage/backend-plugin-api@0.6.17-next.0 + - @backstage/catalog-client@1.6.3 + - @backstage/catalog-model@1.4.5 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.4.11 + +### Patch Changes + +- 038b2e6: Only consider entities of kind `User` when using `findCatalogUser` with a filter query, unless an explicit `kind` filter is provided. +- Updated dependencies + - @backstage/catalog-client@1.6.3 + - @backstage/backend-common@0.21.6 + - @backstage/backend-plugin-api@0.6.16 + - @backstage/catalog-model@1.4.5 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.4.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.6.2 + - @backstage/backend-common@0.21.5 + - @backstage/backend-plugin-api@0.6.15 + - @backstage/catalog-model@1.4.5 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.4.9 + +### Patch Changes + +- b4fc6e3: Deprecated the `getBearerTokenFromAuthorizationHeader` function, which is being replaced by the new `HttpAuthService`. +- 2af5354: Bump dependency `jose` to v5 +- 0fb419b: Updated dependency `uuid` to `^9.0.0`. + Updated dependency `@types/uuid` to `^9.0.0`. +- b1b012d: Fix issue with `providerInfo` not being set properly for some proxy providers, by making `providerInfo` an explicit optional return from `authenticate` +- Updated dependencies + - @backstage/backend-common@0.21.4 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/backend-plugin-api@0.6.14 + - @backstage/catalog-client@1.6.1 + - @backstage/catalog-model@1.4.5 + - @backstage/types@1.1.1 + +## 0.4.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.6.1-next.1 + - @backstage/backend-common@0.21.4-next.2 + - @backstage/backend-plugin-api@0.6.14-next.2 + - @backstage/catalog-model@1.4.5-next.0 + - @backstage/config@1.2.0-next.1 + - @backstage/errors@1.2.4-next.0 + - @backstage/types@1.1.1 + +## 0.4.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.2.0-next.1 + - @backstage/backend-common@0.21.4-next.1 + - @backstage/backend-plugin-api@0.6.14-next.1 + - @backstage/catalog-client@1.6.1-next.0 + - @backstage/catalog-model@1.4.5-next.0 + - @backstage/errors@1.2.4-next.0 + - @backstage/types@1.1.1 + +## 0.4.8-next.0 + +### Patch Changes + +- b4fc6e3: Deprecated the `getBearerTokenFromAuthorizationHeader` function, which is being replaced by the new `HttpAuthService`. +- 2af5354: Bump dependency `jose` to v5 +- 0fb419b: Updated dependency `uuid` to `^9.0.0`. + Updated dependency `@types/uuid` to `^9.0.0`. +- b1b012d: Fix issue with `providerInfo` not being set properly for some proxy providers, by making `providerInfo` an explicit optional return from `authenticate` +- Updated dependencies + - @backstage/backend-common@0.21.3-next.0 + - @backstage/errors@1.2.4-next.0 + - @backstage/backend-plugin-api@0.6.13-next.0 + - @backstage/catalog-client@1.6.1-next.0 + - @backstage/catalog-model@1.4.5-next.0 + - @backstage/config@1.1.2-next.0 + - @backstage/types@1.1.1 + +## 0.4.4 + +### Patch Changes + +- 71c3b7f: Fix error when Microsoft tokens (or any other using the `defaultProfileTransform`) are requested without the profile scope. +- d4cc552: The helper function `makeProfileInfo` and `PassportHelpers.transformProfile` + were refactored to use the `jose` library. +- Updated dependencies + - @backstage/backend-common@0.21.0 + - @backstage/backend-plugin-api@0.6.10 + - @backstage/catalog-model@1.4.4 + - @backstage/catalog-client@1.6.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.4.4-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.3 + - @backstage/backend-plugin-api@0.6.10-next.3 + - @backstage/catalog-client@1.6.0-next.1 + - @backstage/catalog-model@1.4.4-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.4.4-next.2 + +### Patch Changes + +- d4cc552: The helper function `makeProfileInfo` and `PassportHelpers.transformProfile` + were refactored to use the `jose` library. +- Updated dependencies + - @backstage/backend-common@0.21.0-next.2 + - @backstage/backend-plugin-api@0.6.10-next.2 + - @backstage/config@1.1.1 + - @backstage/catalog-client@1.6.0-next.1 + - @backstage/catalog-model@1.4.4-next.0 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.4.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.4.4-next.0 + - @backstage/catalog-client@1.6.0-next.1 + - @backstage/backend-plugin-api@0.6.10-next.1 + - @backstage/backend-common@0.21.0-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.4.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.4.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + +## 0.4.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.4.2 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/catalog-client@1.5.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.4.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.4.2-next.2 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.0 + - @backstage/backend-plugin-api@0.6.8-next.0 + - @backstage/catalog-client@1.4.6 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.4.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.9 + - @backstage/backend-plugin-api@0.6.7 + - @backstage/catalog-client@1.4.6 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.4.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.7-next.2 + - @backstage/backend-common@0.19.9-next.2 + +## 0.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.9-next.1 + - @backstage/backend-plugin-api@0.6.7-next.1 + - @backstage/catalog-client@1.4.5 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.9-next.0 + - @backstage/backend-plugin-api@0.6.7-next.0 + - @backstage/catalog-client@1.4.5 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.4.0 + +### Minor Changes + +- 6f142d5356: **BREAKING**: The recently introduced `ProxyAuthenticator.initialize()` method is no longer `async` to match the way the OAuth equivalent is implemented. + +### Patch Changes + +- 6c2b0793bf: Fix for persisted scopes not being properly restored on sign-in. +- 8b8b1d23ae: Fixed cookie persisted scope not returned in OAuth refresh handler response. +- ae34255836: Adding optional audience parameter to OAuthState type declaration +- Updated dependencies + - @backstage/backend-common@0.19.8 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/backend-plugin-api@0.6.6 + - @backstage/catalog-client@1.4.5 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + +## 0.4.0-next.2 + +### Minor Changes + +- 6f142d5356: **BREAKING**: The recently introduced `ProxyAuthenticator.initialize()` method is no longer `async` to match the way the OAuth equivalent is implemented. + +### Patch Changes + +- 8b8b1d23ae: Fixed cookie persisted scope not returned in OAuth refresh handler response. +- Updated dependencies + - @backstage/backend-common@0.19.8-next.2 + - @backstage/catalog-model@1.4.3-next.0 + - @backstage/errors@1.2.3-next.0 + - @backstage/backend-plugin-api@0.6.6-next.2 + - @backstage/catalog-client@1.4.5-next.0 + - @backstage/config@1.1.1-next.0 + - @backstage/types@1.1.1 + +## 0.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.7-next.1 + - @backstage/backend-plugin-api@0.6.5-next.1 + - @backstage/config@1.1.0 + - @backstage/catalog-client@1.4.4 + - @backstage/catalog-model@1.4.2 + - @backstage/errors@1.2.2 + - @backstage/types@1.1.1 + +## 0.3.2-next.0 + +### Patch Changes + +- 6c2b0793bf: Fix for persisted scopes not being properly restored on sign-in. +- Updated dependencies + - @backstage/backend-common@0.19.7-next.0 + - @backstage/config@1.1.0 + - @backstage/backend-plugin-api@0.6.5-next.0 + - @backstage/catalog-client@1.4.4 + - @backstage/catalog-model@1.4.2 + - @backstage/errors@1.2.2 + - @backstage/types@1.1.1 + +## 0.3.0 + +### Minor Changes + +- 8513cd7d00e3: Introduced a new system for building auth providers for `@backstage/plugin-auth-backend`, which both increases the amount of code re-use across providers, and also works better with the new backend system. + + Many existing types have been moved from `@backstage/plugin-auth-backend` in order to avoid a direct dependency on the plugin from modules. + + Auth provider integrations are now primarily implemented through a pattern of creating "authenticators", which are in turn specific to each kind of integrations. Initially there are two types: `createOAuthAuthenticator` and `createProxyAuthenticator`. These come paired with functions that let you create the corresponding route handlers, `createOAuthRouteHandlers` and `createProxyAuthRouteHandlers`, as well as provider factories, `createOAuthProviderFactory` and `createProxyAuthProviderFactory`. This new authenticator pattern allows the sign-in logic to be separated from the auth integration logic, allowing it to be completely re-used across all providers of the same kind. + + The new provider factories also implement a new declarative way to configure sign-in resolvers, rather than configuration through code. Sign-in resolvers can now be configured through the `resolvers` configuration key, where the first resolver that provides an identity will be used, for example: + + ```yaml + auth: + providers: + google: + development: + clientId: ... + clientSecret: ... + signIn: + resolvers: + - resolver: emailMatchingUserEntityAnnotation + - resolver: emailLocalPartMatchingUserEntityName + ``` + + These configurable resolvers are created with a new `createSignInResolverFactory` function, which creates a sign-in resolver factory, optionally with an options schema that will be used both when configuring the sign-in resolver through configuration and code. + + The internal helpers from `@backstage/plugin-auth-backend` that were used to implement auth providers using passport strategies have now also been made available as public API, through `PassportHelpers` and `PassportOAuthAuthenticatorHelper`. + +### Patch Changes + +- 18619f793c94: The `BackstageIdentityResponse` interface now has an optional `expiresInSeconds` field that can be used to signal session expiration. The `prepareBackstageIdentityResponse` utility will now also read the expiration from the provided token, and include it in the response. +- Updated dependencies + - @backstage/backend-common@0.19.5 + - @backstage/config@1.1.0 + - @backstage/catalog-client@1.4.4 + - @backstage/catalog-model@1.4.2 + - @backstage/errors@1.2.2 + - @backstage/types@1.1.1 + - @backstage/backend-plugin-api@0.6.3 + +## 0.3.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.4.4-next.2 + - @backstage/catalog-model@1.4.2-next.2 + - @backstage/config@1.1.0-next.2 + - @backstage/errors@1.2.2-next.0 + - @backstage/types@1.1.1-next.0 + - @backstage/backend-plugin-api@0.6.3-next.3 + - @backstage/backend-common@0.19.5-next.3 + +## 0.3.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.0-next.1 + - @backstage/backend-common@0.19.5-next.2 + - @backstage/backend-plugin-api@0.6.3-next.2 + - @backstage/catalog-model@1.4.2-next.1 + - @backstage/catalog-client@1.4.4-next.1 + - @backstage/errors@1.2.1 + - @backstage/types@1.1.0 + +## 0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.0-next.0 + - @backstage/backend-common@0.19.5-next.1 + - @backstage/backend-plugin-api@0.6.3-next.1 + - @backstage/catalog-model@1.4.2-next.0 + - @backstage/catalog-client@1.4.4-next.0 + - @backstage/errors@1.2.1 + - @backstage/types@1.1.0 + +## 0.3.0-next.0 + +### Minor Changes + +- 8513cd7d00e3: Introduced a new system for building auth providers for `@backstage/plugin-auth-backend`, which both increases the amount of code re-use across providers, and also works better with the new backend system. + + Many existing types have been moved from `@backstage/plugin-auth-backend` in order to avoid a direct dependency on the plugin from modules. + + Auth provider integrations are now primarily implemented through a pattern of creating "authenticators", which are in turn specific to each kind of integrations. Initially there are two types: `createOAuthAuthenticator` and `createProxyAuthenticator`. These come paired with functions that let you create the corresponding route handlers, `createOAuthRouteHandlers` and `createProxyAuthRouteHandlers`, as well as provider factories, `createOAuthProviderFactory` and `createProxyAuthProviderFactory`. This new authenticator pattern allows the sign-in logic to be separated from the auth integration logic, allowing it to be completely re-used across all providers of the same kind. + + The new provider factories also implement a new declarative way to configure sign-in resolvers, rather than configuration through code. Sign-in resolvers can now be configured through the `resolvers` configuration key, where the first resolver that provides an identity will be used, for example: + + ```yaml + auth: + providers: + google: + development: + clientId: ... + clientSecret: ... + signIn: + resolvers: + - resolver: emailMatchingUserEntityAnnotation + - resolver: emailLocalPartMatchingUserEntityName + ``` + + These configurable resolvers are created with a new `createSignInResolverFactory` function, which creates a sign-in resolver factory, optionally with an options schema that will be used both when configuring the sign-in resolver through configuration and code. + + The internal helpers from `@backstage/plugin-auth-backend` that were used to implement auth providers using passport strategies have now also been made available as public API, through `PassportHelpers` and `PassportOAuthAuthenticatorHelper`. + +### Patch Changes + +- 18619f793c94: The `BackstageIdentityResponse` interface now has an optional `expiresInSeconds` field that can be used to signal session expiration. The `prepareBackstageIdentityResponse` utility will now also read the expiration from the provided token, and include it in the response. +- Updated dependencies + - @backstage/backend-common@0.19.4-next.0 + - @backstage/backend-plugin-api@0.6.2-next.0 + - @backstage/catalog-client@1.4.3 + - @backstage/catalog-model@1.4.1 + - @backstage/config@1.0.8 + - @backstage/errors@1.2.1 + - @backstage/types@1.1.0 + +## 0.2.17 + +### Patch Changes + +- 12a8c94eda8d: Add package repository and homepage metadata +- Updated dependencies + - @backstage/backend-common@0.19.2 + - @backstage/config@1.0.8 + - @backstage/errors@1.2.1 + +## 0.2.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.2-next.2 + +## 0.2.17-next.1 + +### Patch Changes + +- 12a8c94eda8d: Add package repository and homepage metadata +- Updated dependencies + - @backstage/backend-common@0.19.2-next.1 + - @backstage/config@1.0.8 + - @backstage/errors@1.2.1 + +## 0.2.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.2-next.0 + - @backstage/config@1.0.8 + - @backstage/errors@1.2.1 + +## 0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.1 + - @backstage/backend-common@0.19.1 + - @backstage/config@1.0.8 + +## 0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.1-next.0 + - @backstage/backend-common@0.19.1-next.0 + - @backstage/config@1.0.8 + +## 0.2.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.0 + - @backstage/errors@1.2.0 + - @backstage/config@1.0.8 + +## 0.2.15-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.0-next.2 + - @backstage/config@1.0.7 + - @backstage/errors@1.2.0-next.0 + +## 0.2.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.0-next.1 + - @backstage/errors@1.2.0-next.0 + - @backstage/config@1.0.7 + +## 0.2.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.6-next.0 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + +## 0.2.14 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.5 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + +## 0.2.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.5-next.1 + - @backstage/config@1.0.7 + +## 0.2.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.5-next.0 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + +## 0.2.13 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.4 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + +## 0.2.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.4-next.2 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + +## 0.2.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.4-next.1 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + +## 0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.4-next.0 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + +## 0.2.12 + +### Patch Changes + +- 65454876fb2: Minor API report tweaks +- 52b0022dab7: Updated dependency `msw` to `^1.0.0`. +- Updated dependencies + - @backstage/backend-common@0.18.3 + - @backstage/errors@1.1.5 + - @backstage/config@1.0.7 + +## 0.2.12-next.2 + +### Patch Changes + +- 65454876fb2: Minor API report tweaks +- Updated dependencies + - @backstage/backend-common@0.18.3-next.2 + - @backstage/config@1.0.7-next.0 + +## 0.2.12-next.1 + +### Patch Changes + +- 52b0022dab7: Updated dependency `msw` to `^1.0.0`. +- Updated dependencies + - @backstage/errors@1.1.5-next.0 + - @backstage/backend-common@0.18.3-next.1 + - @backstage/config@1.0.7-next.0 + +## 0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.3-next.0 + - @backstage/config@1.0.6 + - @backstage/errors@1.1.4 + +## 0.2.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.2 + - @backstage/config@1.0.6 + - @backstage/errors@1.1.4 + +## 0.2.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.2-next.2 + - @backstage/config@1.0.6 + - @backstage/errors@1.1.4 + +## 0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.2-next.1 + - @backstage/config@1.0.6 + - @backstage/errors@1.1.4 + +## 0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.2-next.0 + +## 0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.0 + - @backstage/config@1.0.6 + - @backstage/errors@1.1.4 + +## 0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.0-next.1 + - @backstage/config@1.0.6-next.0 + - @backstage/errors@1.1.4 + +## 0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.0-next.0 + - @backstage/config@1.0.6-next.0 + - @backstage/errors@1.1.4 + +## 0.2.8 + +### Patch Changes + +- 3280711113: Updated dependency `msw` to `^0.49.0`. +- Updated dependencies + - @backstage/backend-common@0.17.0 + - @backstage/errors@1.1.4 + - @backstage/config@1.0.5 + +## 0.2.8-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.17.0-next.3 + - @backstage/config@1.0.5-next.1 + - @backstage/errors@1.1.4-next.1 + +## 0.2.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.17.0-next.2 + - @backstage/config@1.0.5-next.1 + - @backstage/errors@1.1.4-next.1 + +## 0.2.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.17.0-next.1 + - @backstage/config@1.0.5-next.1 + - @backstage/errors@1.1.4-next.1 + +## 0.2.8-next.0 + +### Patch Changes + +- 3280711113: Updated dependency `msw` to `^0.49.0`. +- Updated dependencies + - @backstage/backend-common@0.16.1-next.0 + - @backstage/config@1.0.5-next.0 + - @backstage/errors@1.1.4-next.0 + +## 0.2.7 + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- Updated dependencies + - @backstage/backend-common@0.16.0 + - @backstage/config@1.0.4 + - @backstage/errors@1.1.3 + +## 0.2.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.1 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.2.7-next.0 + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.2.6 + +### Patch Changes + +- f3a3fefb96: Ensure `getIdentity` throws an `AuthenticationError` instead of a `NotAllowed` error when authentication fails +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## 0.2.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## 0.2.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + +## 0.2.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + +## 0.2.5 + +### Patch Changes + +- 9212439d15: Minor update to tests +- 2cbd533426: `IdentityClient` is now deprecated. Please migrate to `IdentityApi` and `DefaultIdentityClient` instead. The authenticate function on `DefaultIdentityClient` is also deprecated. Please use `getIdentity` instead. +- 667d917488: Updated dependency `msw` to `^0.47.0`. +- 87ec2ba4d6: Updated dependency `msw` to `^0.46.0`. +- bf5e9030eb: Updated dependency `msw` to `^0.45.0`. +- Updated dependencies + - @backstage/backend-common@0.15.1 + - @backstage/config@1.0.2 + - @backstage/errors@1.1.1 + +## 0.2.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.2-next.0 + - @backstage/errors@1.1.1-next.0 + - @backstage/backend-common@0.15.1-next.3 + +## 0.2.5-next.2 + +### Patch Changes + +- 2cbd533426: `IdentityClient` is now deprecated. Please migrate to `IdentityApi` and `DefaultIdentityClient` instead. The authenticate function on `DefaultIdentityClient` is also deprecated. Please use `getIdentity` instead. +- 667d917488: Updated dependency `msw` to `^0.47.0`. +- 87ec2ba4d6: Updated dependency `msw` to `^0.46.0`. +- Updated dependencies + - @backstage/backend-common@0.15.1-next.2 + +## 0.2.5-next.1 + +### Patch Changes + +- 9212439d15: Minor update to tests +- Updated dependencies + - @backstage/backend-common@0.15.1-next.1 + +## 0.2.5-next.0 + +### Patch Changes + +- bf5e9030eb: Updated dependency `msw` to `^0.45.0`. +- Updated dependencies + - @backstage/backend-common@0.15.1-next.0 + +## 0.2.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.0 + +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.0-next.0 + +## 0.2.3 + +### Patch Changes + +- a70869e775: Updated dependency `msw` to `^0.43.0`. +- 8006d0f9bf: Updated dependency `msw` to `^0.44.0`. +- Updated dependencies + - @backstage/backend-common@0.14.1 + - @backstage/errors@1.1.0 + +## 0.2.3-next.2 + +### Patch Changes + +- a70869e775: Updated dependency `msw` to `^0.43.0`. +- Updated dependencies + - @backstage/backend-common@0.14.1-next.3 + +## 0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.14.1-next.1 + - @backstage/errors@1.1.0-next.0 + +## 0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.14.1-next.0 + +## 0.2.2 + +### Patch Changes + +- 5ca0b86b88: Address corner cases where the key store was not being created at startup +- 8f7b1835df: Updated dependency `msw` to `^0.41.0`. +- 9079a78078: Added configurable algorithms array for IdentityClient +- Updated dependencies + - @backstage/backend-common@0.14.0 + +## 0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.14.0-next.2 + +## 0.2.2-next.1 + +### Patch Changes + +- 8f7b1835df: Updated dependency `msw` to `^0.41.0`. +- Updated dependencies + - @backstage/backend-common@0.13.6-next.1 + +## 0.2.2-next.0 + +### Patch Changes + +- 9079a78078: Added configurable algorithms array for IdentityClient +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + +## 0.2.1 + +### Patch Changes + +- 9ec4e0613e: Update to `jose` 4.6.0 +- Updated dependencies + - @backstage/backend-common@0.13.3 + - @backstage/config@1.0.1 + +## 0.2.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.3-next.2 + - @backstage/config@1.0.1-next.0 + +## 0.2.1-next.0 + +### Patch Changes + +- 9ec4e0613e: Update to `jose` 4.6.0 +- Updated dependencies + - @backstage/backend-common@0.13.3-next.0 + +## 0.2.0 + +### Minor Changes + +- 15d3a3c39a: **BREAKING**: Removed the deprecated `id` and `entity` fields from `BackstageSignInResult`. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.2 + +## 0.2.0-next.0 + +### Minor Changes + +- 15d3a3c39a: **BREAKING**: Removed the deprecated `id` and `entity` fields from `BackstageSignInResult`. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.2-next.0 + +## 0.1.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.1 + - @backstage/catalog-model@1.0.0 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + +## 0.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.0 + - @backstage/catalog-model@0.13.0 + +## 0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.0-next.0 + - @backstage/catalog-model@0.13.0-next.0 + +## 0.1.4 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@0.12.0 + - @backstage/backend-common@0.12.0 + +## 0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.11.0 + - @backstage/catalog-model@0.11.0 + +## 0.1.2 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/catalog-model@0.10.1 + - @backstage/config@0.1.15 + - @backstage/errors@0.2.2 + +## 0.1.1 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/errors@0.2.1 + - @backstage/catalog-model@0.10.0 + - @backstage/config@0.1.14 + +## 0.1.0 + +### Minor Changes + +- 9058bb1b5e: Added this package, to hold shared types and functionality that other backend + packages need to import. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.7 + +## @backstage/plugin-catalog@1.33.0-next.1 + +### Minor Changes + +- 05aac34: Migrated `DeleteEntityDialog` and `EntityOrphanWarning` components to Backstage UI. + + The `deleteEntity.description` translation key no longer includes "Click here to delete" text. A new `deleteEntity.actionButtonTitle` key was added for the action button. + +### Patch Changes + +- 8d4c48b: Fixed vertical spacing between tags in the catalog table. +- e8258d0: The default entity content layout still supports rendering summary cards at runtime for backward compatibility, but logs a console warning when they are detected to help identify where migration is needed. +- Updated dependencies + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/ui@0.12.0-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/core-compat-api@0.5.8-next.1 + - @backstage/plugin-search-react@1.10.3-next.1 + - @backstage/core-components@0.18.7-next.1 + - @backstage/plugin-techdocs-react@1.3.8-next.0 + - @backstage/integration-react@1.2.15-next.1 + - @backstage/plugin-scaffolder-common@1.7.6-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab@0.8.0-next.1 + +### Minor Changes + +- 2f51676: allow entity discoverability via gitlab search API + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/backend-defaults@0.15.2-next.1 + +## @backstage/plugin-catalog-backend-module-msgraph@0.9.0-next.1 + +### Minor Changes + +- 8694561: Log group/user count, tenant ID, execution time as separate fields + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/plugin-catalog-react@1.22.0-next.1 + +### Minor Changes + +- 0e9578d: Migrated `UnregisterEntityDialog` from Material UI to Backstage UI components. +- e8258d0: **BREAKING**: Removed the 'summary' entity card type from `EntityCardType`. Users should migrate to using 'content' or 'info' card types instead. + + TypeScript will now show errors if you try to use `type: 'summary'` when creating entity cards. + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-test-utils@0.4.6-next.1 + - @backstage/ui@0.12.0-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/core-compat-api@0.5.8-next.1 + - @backstage/core-components@0.18.7-next.1 + - @backstage/integration-react@1.2.15-next.1 + +## @backstage/plugin-mcp-actions-backend@0.1.9-next.0 + +# @backstage/plugin-mcp-actions-backend + +## 0.1.8-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/backend-defaults@0.15.1-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.1.7 + +### Patch Changes + +- 4d82a35: build(deps): bump `@modelcontextprotocol/sdk` from 1.24.3 to 1.25.2 +- Updated dependencies + - @backstage/backend-defaults@0.15.0 + - @backstage/backend-plugin-api@1.6.1 + +## 0.1.7-next.1 + +### Patch Changes + +- 4d82a35: build(deps): bump `@modelcontextprotocol/sdk` from 1.24.3 to 1.25.2 +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + +## 0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.1-next.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/catalog-client@1.12.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1 + +## 0.1.6 + +### Patch Changes + +- e83e038: Added `@cfworker/json-schema` as a dependency to this package part of the `@modelcontextprotocol/sdk` bump as it's required in the types +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 79ef471: Clarify error handling in readme and update handleError.ts to include all backstage/errors +- Updated dependencies + - @backstage/backend-defaults@0.14.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## 0.1.6-next.1 + +### Patch Changes + +- e83e038: Added `@cfworker/json-schema` as a dependency to this package part of the `@modelcontextprotocol/sdk` bump as it's required in the types +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## 0.1.6-next.0 + +### Patch Changes + +- 79ef471: Clarify error handling in readme and update handleError.ts to include all backstage/errors +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.1.5 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/catalog-client@1.12.1 + +## 0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-defaults@0.13.1-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + +## 0.1.5-next.0 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-defaults@0.13.1-next.0 + - @backstage/backend-plugin-api@1.4.5-next.0 + - @backstage/catalog-client@1.12.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.19.2-next.0 + +## 0.1.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.0 + - @backstage/backend-plugin-api@1.4.4 + - @backstage/plugin-catalog-node@1.19.1 + +## 0.1.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.0-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/catalog-client@1.12.0 + +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.0-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-client@1.12.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.19.0 + +## 0.1.3 + +### Patch Changes + +- 1d47bf3: Proxy `/.well-known/oauth-authorization-server` to `/.well-known/openid-configuration` on `auth-backend` when `auth.experimentalDynamicClientRegistration.enabled` is enabled. +- 7f2a4a0: Updating docs +- d08b0c9: The MCP backend will now convert known Backstage errors into textual responses with `isError: true`. + The error message can be useful for an LLM to understand and maybe give back to the user. + Previously all errors where thrown out to `@modelcontextprotocol/sdk` which causes a generic 500. +- Updated dependencies + - @backstage/backend-defaults@0.12.1 + - @backstage/plugin-catalog-node@1.19.0 + - @backstage/catalog-client@1.12.0 + - @backstage/types@1.2.2 + - @backstage/backend-plugin-api@1.4.3 + +## 0.1.3-next.1 + +### Patch Changes + +- 1d47bf3: Proxy `/.well-known/oauth-authorization-server` to `/.well-known/openid-configuration` on `auth-backend` when `auth.experimentalDynamicClientRegistration.enabled` is enabled. +- Updated dependencies + - @backstage/backend-defaults@0.12.1-next.1 + - @backstage/catalog-client@1.12.0-next.0 + - @backstage/plugin-catalog-node@1.19.0-next.1 + +## 0.1.3-next.0 + +### Patch Changes + +- d08b0c9: The MCP backend will now convert known Backstage errors into textual responses with `isError: true`. + The error message can be useful for an LLM to understand and maybe give back to the user. + Previously all errors where thrown out to `@modelcontextprotocol/sdk` which causes a generic 500. +- Updated dependencies + - @backstage/backend-defaults@0.12.1-next.0 + - @backstage/backend-plugin-api@1.4.3-next.0 + - @backstage/plugin-catalog-node@1.18.1-next.0 + +## 0.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.12.0 + - @backstage/catalog-client@1.11.0 + - @backstage/plugin-catalog-node@1.18.0 + - @backstage/backend-plugin-api@1.4.2 + +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.1.1 + +### Patch Changes + +- 6bc0799: Fixed the example in the README for generating a static token by adding a subject field +- Updated dependencies + - @backstage/backend-defaults@0.11.1 + - @backstage/catalog-client@1.10.2 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-node@1.17.2 + +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + +## 0.1.1-next.0 + +### Patch Changes + +- 6bc0799: Fixed the example in the README for generating a static token by adding a subject field +- Updated dependencies + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/catalog-client@1.10.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.1.0 + +### Minor Changes + +- 4ed0fb6: Initial implementation of an `mcp-actions` backend + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1 + - @backstage/backend-defaults@0.11.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-permission-backend@0.7.9-next.0 + +# @backstage/plugin-permission-backend + +## 0.7.8-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/plugin-permission-node@0.10.9-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.7.7 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + +## 0.7.7-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.3 + +## 0.7.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.3 + +## 0.7.6 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config@1.3.6 + - @backstage/plugin-permission-node@0.10.6 + +## 0.7.6-next.1 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-permission-common@0.9.3-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + - @backstage/plugin-permission-node@0.10.6-next.1 + +## 0.7.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.9-next.0 + - @backstage/config@1.3.6-next.0 + - @backstage/plugin-permission-node@0.10.6-next.0 + - @backstage/backend-plugin-api@1.4.5-next.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.3-next.0 + +## 0.7.5 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.5 + - @backstage/backend-plugin-api@1.4.4 + - @backstage/plugin-auth-node@0.6.8 + - @backstage/plugin-permission-common@0.9.2 + - @backstage/plugin-permission-node@0.10.5 + +## 0.7.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-permission-node@0.10.5-next.0 + +## 0.7.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.7 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/plugin-permission-node@0.10.4 + +## 0.7.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.7-next.0 + - @backstage/backend-plugin-api@1.4.3-next.0 + - @backstage/plugin-permission-node@0.10.4-next.0 + +## 0.7.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6 + - @backstage/plugin-permission-node@0.10.3 + - @backstage/backend-plugin-api@1.4.2 + +## 0.7.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/plugin-permission-node@0.10.3-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.1 + +## 0.7.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-permission-node@0.10.2 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.7.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/plugin-permission-node@0.10.2-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.7.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1 + +## 0.7.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1-next.1 + +## 0.7.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-permission-node@0.10.1-next.0 + +## 0.7.0 + +### Minor Changes + +- 4da2965: Fixed an issue causing the `PermissionClient` to exhaust the request body size limit too quickly when making many requests. +- cf8fd51: **BREAKING** Removed support for the legacy backend system, please [migrate to the new backend system](https://backstage.io/docs/backend-system/building-backends/migrating) + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.7.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-permission-node@0.10.0-next.2 + +## 0.7.0-next.1 + +### Minor Changes + +- 4da2965: Fixed an issue causing the `PermissionClient` to exhaust the request body size limit too quickly when making many requests. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.7.0-next.0 + +### Minor Changes + +- cf8fd51: **BREAKING** Removed support for the legacy backend system, please [migrate to the new backend system](https://backstage.io/docs/backend-system/building-backends/migrating) + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.0 + - @backstage/plugin-auth-node@0.6.3-next.0 + - @backstage/plugin-permission-node@0.9.2-next.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.8.4 + +## 0.6.0 + +### Minor Changes + +- 78eaa50: Improved validation for the `/authorize` endpoint when a `resourceRef` is provided alongside a basic permission. Additionally, introduced a clearer error message for cases where users attempt to directly evaluate conditional permissions. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.0 + - @backstage/plugin-auth-node@0.6.2 + - @backstage/plugin-permission-node@0.9.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.8.4 + +## 0.5.55 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-node@0.9.0 + - @backstage/plugin-auth-node@0.6.1 + - @backstage/backend-plugin-api@1.2.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.8.4 + +## 0.5.55-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1-next.1 + - @backstage/backend-plugin-api@1.2.1-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.8.4 + - @backstage/plugin-permission-node@0.8.9-next.1 + +## 0.5.55-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-node@0.8.9-next.0 + - @backstage/plugin-auth-node@0.6.1-next.0 + - @backstage/backend-plugin-api@1.2.1-next.0 + +## 0.5.54 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-node@0.8.8 + - @backstage/backend-plugin-api@1.2.0 + - @backstage/plugin-auth-node@0.6.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.8.4 + +## 0.5.54-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-node@0.8.8-next.2 + - @backstage/backend-plugin-api@1.2.0-next.2 + - @backstage/plugin-auth-node@0.6.0-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.8.4 + +## 0.5.54-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.1 + - @backstage/plugin-auth-node@0.6.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.8.4 + - @backstage/plugin-permission-node@0.8.8-next.1 + +## 0.5.54-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-node@0.8.8-next.0 + - @backstage/backend-plugin-api@1.2.0-next.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-auth-node@0.5.7-next.0 + - @backstage/plugin-permission-common@0.8.4 + +## 0.5.53 + +### Patch Changes + +- d9d62ef: Remove some internal usages of the backend-common package +- Updated dependencies + - @backstage/plugin-permission-node@0.8.7 + - @backstage/plugin-auth-node@0.5.6 + - @backstage/backend-plugin-api@1.1.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.8.4 + +## 0.5.53-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.1-next.1 + - @backstage/config@1.3.2-next.0 + - @backstage/errors@1.2.7-next.0 + - @backstage/plugin-auth-node@0.5.6-next.1 + - @backstage/plugin-permission-common@0.8.4-next.0 + - @backstage/plugin-permission-node@0.8.7-next.1 + +## 0.5.53-next.0 + +### Patch Changes + +- d9d62ef: Remove some internal usages of the backend-common package +- Updated dependencies + - @backstage/plugin-permission-node@0.8.7-next.0 + - @backstage/plugin-auth-node@0.5.6-next.0 + - @backstage/backend-plugin-api@1.1.1-next.0 + - @backstage/config@1.3.1 + - @backstage/errors@1.2.6 + - @backstage/plugin-permission-common@0.8.3 + +## 0.5.52 + +### Patch Changes + +- 1e624ca: Restrict `@types/express` version range from `*` to `^4.17.6`. +- 5c9cc05: Use native fetch instead of node-fetch +- Updated dependencies + - @backstage/plugin-auth-node@0.5.5 + - @backstage/backend-plugin-api@1.1.0 + - @backstage/plugin-permission-node@0.8.6 + - @backstage/errors@1.2.6 + - @backstage/config@1.3.1 + - @backstage/plugin-permission-common@0.8.3 + +## 0.5.52-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.0-next.2 + - @backstage/plugin-permission-node@0.8.6-next.2 + - @backstage/errors@1.2.6-next.0 + - @backstage/plugin-auth-node@0.5.5-next.2 + - @backstage/config@1.3.1-next.0 + - @backstage/plugin-permission-common@0.8.3-next.0 + +## 0.5.52-next.1 + +### Patch Changes + +- 5c9cc05: Use native fetch instead of node-fetch +- Updated dependencies + - @backstage/plugin-auth-node@0.5.5-next.1 + - @backstage/backend-plugin-api@1.1.0-next.1 + - @backstage/plugin-permission-node@0.8.6-next.1 + - @backstage/config@1.3.0 + - @backstage/errors@1.2.5 + - @backstage/plugin-permission-common@0.8.2 + +## 0.5.52-next.0 + +### Patch Changes + +- 1e624ca: Restrict `@types/express` version range from `*` to `^4.17.6`. +- Updated dependencies + - @backstage/backend-plugin-api@1.0.3-next.0 + - @backstage/plugin-auth-node@0.5.5-next.0 + - @backstage/config@1.3.0 + - @backstage/errors@1.2.5 + - @backstage/plugin-permission-common@0.8.2 + - @backstage/plugin-permission-node@0.8.6-next.0 + +## 0.5.51 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.0 + - @backstage/plugin-auth-node@0.5.4 + - @backstage/backend-plugin-api@1.0.2 + - @backstage/plugin-permission-common@0.8.2 + - @backstage/errors@1.2.5 + - @backstage/plugin-permission-node@0.8.5 + +## 0.5.51-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4-next.2 + - @backstage/backend-plugin-api@1.0.2-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.5-next.2 + +## 0.5.51-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.2-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.5.4-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.5-next.1 + +## 0.5.51-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4-next.0 + - @backstage/backend-plugin-api@1.0.2-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.5-next.0 + +## 0.5.50 + +### Patch Changes + +- 094eaa3: Remove references to in-repo backend-common +- 3109c24: The export for the new backend system at the `/alpha` export is now also available via the main entry point, which means that you can remove the `/alpha` suffix from the import. +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3 + - @backstage/plugin-permission-node@0.8.4 + - @backstage/backend-plugin-api@1.0.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.8.1 + +## 0.5.50-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3-next.1 + - @backstage/backend-plugin-api@1.0.1-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.4-next.1 + +## 0.5.50-next.0 + +### Patch Changes + +- 094eaa3: Remove references to in-repo backend-common +- Updated dependencies + - @backstage/plugin-permission-node@0.8.4-next.0 + - @backstage/plugin-auth-node@0.5.3-next.0 + - @backstage/backend-plugin-api@1.0.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.8.1 + +## 0.5.49 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- fcb9356: Deprecated `createRouter` and its router options in favour of the new backend system. +- Updated dependencies + - @backstage/backend-common@0.25.0 + - @backstage/backend-plugin-api@1.0.0 + - @backstage/plugin-auth-node@0.5.2 + - @backstage/plugin-permission-node@0.8.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.8.1 + +## 0.5.49-next.2 + +### Patch Changes + +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-common@0.25.0-next.2 + - @backstage/plugin-auth-node@0.5.2-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/plugin-permission-node@0.8.3-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.8.1 + +## 0.5.49-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + +## 0.5.49-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- fcb9356: Deprecated `createRouter` and its router options in favour of the new backend system. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-permission-node@0.8.3-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.8.1 + +## 0.5.47 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.5.47-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.5.0-next.3 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-permission-node@0.8.1-next.3 + +## 0.5.47-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/plugin-permission-node@0.8.1-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.5.47-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/plugin-permission-node@0.8.1-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## 0.5.47-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.1-next.0 + +## 0.5.46 + +### Patch Changes + +- ed10fd2: The `PermissionPolicy` interface has been updated to align with the recent changes to the Backstage auth system. The second argument to the `handle` method is now of the new `PolicyQueryUser` type. This type maintains the old fields from the `BackstageIdentityResponse`, which are now all deprecated. Instead, two new fields have been added, which allows access to the same information: + + - `credentials` - A `BackstageCredentials` object, which is useful for making requests to other services on behalf of the user as part of evaluating the policy. This replaces the deprecated `token` field. See the [Auth Service documentation](https://backstage.io/docs/backend-system/core-services/auth#creating-request-tokens) for information about how to create a token using these credentials. + - `info` - A `BackstageUserInfo` object, which contains the same information as the deprecated `identity`, except for the `type` field that was redundant. + + Most existing policies can be updated by replacing the `BackstageIdentityResponse` type with `PolicyQueryUser`, which is exported from `@backstage/plugin-permission-node`, as well as replacing any occurrences of `user?.identity` with `user?.info`. + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.0 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.5.46-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/plugin-permission-node@0.7.33-next.1 + +## 0.5.45-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.21-next.0 + - @backstage/backend-common@0.23.2-next.0 + - @backstage/plugin-auth-node@0.4.16-next.0 + - @backstage/plugin-permission-node@0.7.32-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.14 + +## 0.5.43 + +### Patch Changes + +- 78a0b08: Internal refactor to handle `BackendFeature` contract change. +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/backend-common@0.23.0 + - @backstage/backend-plugin-api@0.6.19 + - @backstage/plugin-auth-node@0.4.14 + - @backstage/plugin-permission-node@0.7.30 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.5.43-next.3 + +### Patch Changes + +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.3 + - @backstage/plugin-auth-node@0.4.14-next.3 + - @backstage/plugin-permission-common@0.7.14-next.0 + - @backstage/plugin-permission-node@0.7.30-next.3 + - @backstage/backend-common@0.23.0-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.5.43-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.2 + - @backstage/backend-common@0.23.0-next.2 + - @backstage/plugin-permission-node@0.7.30-next.2 + - @backstage/plugin-auth-node@0.4.14-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.13 + +## 0.5.43-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + +## 0.5.43-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/plugin-permission-node@0.7.30-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.13 + +## 0.5.42 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + - @backstage/plugin-permission-node@0.7.29 + +## 0.5.42-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/plugin-permission-node@0.7.29-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## 0.5.42-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-node@0.7.29-next.0 + +## 0.5.41 + +### Patch Changes + +- d5a1fe1: Replaced winston logger with `LoggerService` +- Updated dependencies + - @backstage/backend-common@0.21.7 + - @backstage/plugin-permission-node@0.7.28 + - @backstage/backend-plugin-api@0.6.17 + - @backstage/plugin-auth-node@0.4.12 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.13 + +## 0.5.41-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.7-next.1 + - @backstage/backend-plugin-api@0.6.17-next.1 + - @backstage/plugin-auth-node@0.4.12-next.1 + - @backstage/plugin-permission-node@0.7.28-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.13 + +## 0.5.41-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.7-next.0 + - @backstage/backend-plugin-api@0.6.17-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.12-next.0 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-node@0.7.28-next.0 + +## 0.5.40 + +### Patch Changes + +- f3e2e86: Properly forward causes of errors from upstream backends in the `PermissionIntegrationClient` +- Updated dependencies + - @backstage/plugin-auth-node@0.4.11 + - @backstage/backend-common@0.21.6 + - @backstage/backend-plugin-api@0.6.16 + - @backstage/plugin-permission-node@0.7.27 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.13 + +## 0.5.39 + +### Patch Changes + +- f3e2e86: Properly forward causes of errors from upstream backends in the `PermissionIntegrationClient` +- Updated dependencies + - @backstage/backend-common@0.21.5 + - @backstage/plugin-auth-node@0.4.10 + - @backstage/plugin-permission-node@0.7.26 + - @backstage/backend-plugin-api@0.6.15 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.13 + +## 0.5.38 + +### Patch Changes + +- 9c7fb30: Properly forward causes of errors from upstream backends in the `PermissionIntegrationClient` + +## 0.5.37 + +### Patch Changes + +- 4467036: Allow unauthenticated access to health check endpoint. + +- 9802004: Migrated to use the new auth services introduced in [BEP-0003](https://github.com/backstage/backstage/blob/master/beps/0003-auth-architecture-evolution/README.md). + + The `createRouter` function now accepts `auth`, `httpAuth` and `userInfo` options. Theses are used internally to support the new backend system, and can be ignored. + +- Updated dependencies + - @backstage/backend-common@0.21.4 + - @backstage/plugin-auth-node@0.4.9 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/backend-plugin-api@0.6.14 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-node@0.7.25 + +## 0.5.37-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.4-next.2 + - @backstage/plugin-auth-node@0.4.9-next.2 + - @backstage/backend-plugin-api@0.6.14-next.2 + - @backstage/config@1.2.0-next.1 + - @backstage/errors@1.2.4-next.0 + - @backstage/plugin-permission-common@0.7.13-next.1 + - @backstage/plugin-permission-node@0.7.25-next.2 + +## 0.5.37-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.2.0-next.1 + - @backstage/backend-common@0.21.4-next.1 + - @backstage/backend-plugin-api@0.6.14-next.1 + - @backstage/plugin-auth-node@0.4.9-next.1 + - @backstage/plugin-permission-common@0.7.13-next.1 + - @backstage/plugin-permission-node@0.7.25-next.1 + - @backstage/errors@1.2.4-next.0 + +## 0.5.36-next.0 + +### Patch Changes + +- 9802004: Migrated to use the new auth services introduced in [BEP-0003](https://github.com/backstage/backstage/blob/master/beps/0003-auth-architecture-evolution/README.md). + + The `createRouter` function now accepts `auth`, `httpAuth` and `userInfo` options. Theses are used internally to support the new backend system, and can be ignored. + +- Updated dependencies + - @backstage/backend-common@0.21.3-next.0 + - @backstage/plugin-auth-node@0.4.8-next.0 + - @backstage/errors@1.2.4-next.0 + - @backstage/backend-plugin-api@0.6.13-next.0 + - @backstage/plugin-permission-common@0.7.13-next.0 + - @backstage/plugin-permission-node@0.7.24-next.0 + - @backstage/config@1.1.2-next.0 + +## 0.5.33 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0 + - @backstage/plugin-auth-node@0.4.4 + - @backstage/backend-plugin-api@0.6.10 + - @backstage/plugin-permission-node@0.7.21 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.12 + +## 0.5.33-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.3 + - @backstage/plugin-auth-node@0.4.4-next.3 + - @backstage/plugin-permission-node@0.7.21-next.3 + - @backstage/backend-plugin-api@0.6.10-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.12 + +## 0.5.33-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.2 + - @backstage/backend-plugin-api@0.6.10-next.2 + - @backstage/plugin-auth-node@0.4.4-next.2 + - @backstage/plugin-permission-node@0.7.21-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.12 + +## 0.5.33-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.1 + - @backstage/backend-common@0.21.0-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.4-next.1 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-node@0.7.21-next.1 + +## 0.5.33-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-permission-node@0.7.21-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.12 + +## 0.5.32 + +### Patch Changes + +- b1acd9b: Updated README +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-node@0.7.20 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.5.32-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-permission-node@0.7.20-next.2 + +## 0.5.32-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/plugin-permission-node@0.7.20-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.11 + +## 0.5.32-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.20-next.0 + +## 0.5.31 + +### Patch Changes + +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.19 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.5.31-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.3 + +## 0.5.31-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.2 + +## 0.5.31-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.1 + +## 0.5.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.0 + - @backstage/plugin-auth-node@0.4.2-next.0 + - @backstage/plugin-permission-node@0.7.19-next.0 + - @backstage/backend-plugin-api@0.6.8-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.10 + +## 0.5.30 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.9 + - @backstage/backend-plugin-api@0.6.7 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.1 + - @backstage/plugin-permission-node@0.7.18 + +## 0.5.30-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.7-next.2 + - @backstage/backend-common@0.19.9-next.2 + - @backstage/plugin-auth-node@0.4.1-next.2 + - @backstage/plugin-permission-node@0.7.18-next.2 + +## 0.5.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.9-next.1 + - @backstage/plugin-auth-node@0.4.1-next.1 + - @backstage/plugin-permission-node@0.7.18-next.1 + - @backstage/backend-plugin-api@0.6.7-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.9 + +## 0.5.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.9-next.0 + - @backstage/backend-plugin-api@0.6.7-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.1-next.0 + - @backstage/plugin-permission-common@0.7.9 + - @backstage/plugin-permission-node@0.7.18-next.0 + +## 0.5.29 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.8 + - @backstage/plugin-auth-node@0.4.0 + - @backstage/errors@1.2.3 + - @backstage/backend-plugin-api@0.6.6 + - @backstage/plugin-permission-node@0.7.17 + - @backstage/config@1.1.1 + - @backstage/plugin-permission-common@0.7.9 + +## 0.5.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.8-next.2 + - @backstage/plugin-auth-node@0.4.0-next.2 + - @backstage/errors@1.2.3-next.0 + - @backstage/plugin-permission-node@0.7.17-next.2 + - @backstage/backend-plugin-api@0.6.6-next.2 + - @backstage/config@1.1.1-next.0 + - @backstage/plugin-permission-common@0.7.9-next.0 + +## 0.5.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.7-next.1 + - @backstage/backend-plugin-api@0.6.5-next.1 + - @backstage/plugin-auth-node@0.3.2-next.1 + - @backstage/plugin-permission-node@0.7.16-next.1 + - @backstage/config@1.1.0 + - @backstage/errors@1.2.2 + - @backstage/plugin-permission-common@0.7.8 + +## 0.5.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.3.2-next.0 + - @backstage/backend-common@0.19.7-next.0 + - @backstage/config@1.1.0 + - @backstage/backend-plugin-api@0.6.5-next.0 + - @backstage/errors@1.2.2 + - @backstage/plugin-permission-common@0.7.8 + - @backstage/plugin-permission-node@0.7.16-next.0 + +## 0.5.26 + +### Patch Changes + +- 71114ac50e02: The export for the new backend system has been moved to be the `default` export. + + For example, if you are currently importing the plugin using the following pattern: + + ```ts + import { examplePlugin } from '@backstage/plugin-example-backend'; + + backend.add(examplePlugin); + ``` + + It should be migrated to this: + + ```ts + backend.add(import('@backstage/plugin-example-backend')); + ``` + +- a8a614ba0d07: Minor `package.json` update. + +- 84ad6fccd4d5: Moved `permissionModuleAllowAllPolicy` to `@backstage/plugin-permission-backend-module-allow-all-policy` + +- Updated dependencies + - @backstage/backend-common@0.19.5 + - @backstage/plugin-auth-node@0.3.0 + - @backstage/config@1.1.0 + - @backstage/errors@1.2.2 + - @backstage/plugin-permission-common@0.7.8 + - @backstage/plugin-permission-node@0.7.14 + - @backstage/backend-plugin-api@0.6.3 + +## 0.5.26-next.3 + +### Patch Changes + +- 71114ac50e02: The export for the new backend system has been moved to be the `default` export. + + For example, if you are currently importing the plugin using the following pattern: + + ```ts + import { examplePlugin } from '@backstage/plugin-example-backend'; + + backend.add(examplePlugin); + ``` + + It should be migrated to this: + + ```ts + backend.add(import('@backstage/plugin-example-backend')); + ``` + +- a8a614ba0d07: Minor `package.json` update. + +- Updated dependencies + - @backstage/config@1.1.0-next.2 + - @backstage/errors@1.2.2-next.0 + - @backstage/plugin-permission-common@0.7.8-next.2 + - @backstage/plugin-permission-node@0.7.14-next.3 + - @backstage/backend-plugin-api@0.6.3-next.3 + - @backstage/backend-common@0.19.5-next.3 + - @backstage/plugin-auth-node@0.3.0-next.3 + +## 0.5.26-next.2 + +### Patch Changes + +- 84ad6fccd4d5: Moved `permissionModuleAllowAllPolicy` to `@backstage/plugin-permission-backend-module-allow-all-policy` +- Updated dependencies + - @backstage/config@1.1.0-next.1 + - @backstage/backend-common@0.19.5-next.2 + - @backstage/plugin-auth-node@0.3.0-next.2 + - @backstage/plugin-permission-node@0.7.14-next.2 + - @backstage/backend-plugin-api@0.6.3-next.2 + - @backstage/plugin-permission-common@0.7.8-next.1 + - @backstage/errors@1.2.1 + +## 0.5.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.0-next.0 + - @backstage/backend-common@0.19.5-next.1 + - @backstage/backend-plugin-api@0.6.3-next.1 + - @backstage/plugin-auth-node@0.3.0-next.1 + - @backstage/plugin-permission-common@0.7.8-next.0 + - @backstage/plugin-permission-node@0.7.14-next.1 + - @backstage/errors@1.2.1 + +## 0.5.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.3.0-next.0 + - @backstage/backend-common@0.19.4-next.0 + - @backstage/backend-plugin-api@0.6.2-next.0 + - @backstage/config@1.0.8 + - @backstage/errors@1.2.1 + - @backstage/plugin-permission-common@0.7.7 + - @backstage/plugin-permission-node@0.7.13-next.0 + +## 0.5.23 + +### Patch Changes + +- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config` +- 12a8c94eda8d: Add package repository and homepage metadata +- 0d6ddc62d8da: Refactor backend plugin creation parameter from callback to object. +- Updated dependencies + - @backstage/backend-common@0.19.2 + - @backstage/backend-plugin-api@0.6.0 + - @backstage/plugin-auth-node@0.2.17 + - @backstage/plugin-permission-node@0.7.11 + - @backstage/config@1.0.8 + - @backstage/errors@1.2.1 + - @backstage/plugin-permission-common@0.7.7 + +## 0.5.23-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.0-next.2 + - @backstage/backend-common@0.19.2-next.2 + - @backstage/plugin-permission-node@0.7.11-next.2 + - @backstage/plugin-auth-node@0.2.17-next.2 + +## 0.5.23-next.1 + +### Patch Changes + +- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config` +- 12a8c94eda8d: Add package repository and homepage metadata +- Updated dependencies + - @backstage/backend-common@0.19.2-next.1 + - @backstage/plugin-auth-node@0.2.17-next.1 + - @backstage/backend-plugin-api@0.6.0-next.1 + - @backstage/plugin-permission-node@0.7.11-next.1 + - @backstage/config@1.0.8 + - @backstage/errors@1.2.1 + - @backstage/plugin-permission-common@0.7.7 + +## 0.5.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.2-next.0 + - @backstage/backend-plugin-api@0.5.5-next.0 + - @backstage/config@1.0.8 + - @backstage/errors@1.2.1 + - @backstage/plugin-auth-node@0.2.17-next.0 + - @backstage/plugin-permission-common@0.7.7 + - @backstage/plugin-permission-node@0.7.11-next.0 + +## 0.5.22 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.1 + - @backstage/backend-common@0.19.1 + - @backstage/backend-plugin-api@0.5.4 + - @backstage/config@1.0.8 + - @backstage/plugin-auth-node@0.2.16 + - @backstage/plugin-permission-common@0.7.7 + - @backstage/plugin-permission-node@0.7.10 + +## 0.5.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.1-next.0 + - @backstage/backend-common@0.19.1-next.0 + - @backstage/backend-plugin-api@0.5.4-next.0 + - @backstage/config@1.0.8 + - @backstage/plugin-auth-node@0.2.16-next.0 + - @backstage/plugin-permission-common@0.7.7-next.0 + - @backstage/plugin-permission-node@0.7.10-next.0 + +## 0.5.21 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.0 + - @backstage/errors@1.2.0 + - @backstage/backend-plugin-api@0.5.3 + - @backstage/plugin-auth-node@0.2.15 + - @backstage/plugin-permission-node@0.7.9 + - @backstage/config@1.0.8 + - @backstage/plugin-permission-common@0.7.6 + +## 0.5.21-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.0-next.2 + - @backstage/backend-plugin-api@0.5.3-next.2 + - @backstage/config@1.0.7 + - @backstage/errors@1.2.0-next.0 + - @backstage/plugin-auth-node@0.2.15-next.2 + - @backstage/plugin-permission-common@0.7.6-next.0 + - @backstage/plugin-permission-node@0.7.9-next.2 + +## 0.5.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.0-next.1 + - @backstage/errors@1.2.0-next.0 + - @backstage/backend-plugin-api@0.5.3-next.1 + - @backstage/plugin-auth-node@0.2.15-next.1 + - @backstage/plugin-permission-node@0.7.9-next.1 + - @backstage/plugin-permission-common@0.7.6-next.0 + - @backstage/config@1.0.7 + +## 0.5.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.6-next.0 + - @backstage/config@1.0.7 + - @backstage/backend-plugin-api@0.5.3-next.0 + - @backstage/errors@1.1.5 + - @backstage/plugin-auth-node@0.2.15-next.0 + - @backstage/plugin-permission-common@0.7.5 + - @backstage/plugin-permission-node@0.7.9-next.0 + +## 0.5.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.5 + - @backstage/plugin-permission-node@0.7.8 + - @backstage/plugin-auth-node@0.2.14 + - @backstage/backend-plugin-api@0.5.2 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + - @backstage/plugin-permission-common@0.7.5 + +## 0.5.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.5-next.1 + - @backstage/plugin-auth-node@0.2.14-next.1 + - @backstage/plugin-permission-node@0.7.8-next.1 + - @backstage/backend-plugin-api@0.5.2-next.1 + - @backstage/config@1.0.7 + +## 0.5.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.5-next.0 + - @backstage/plugin-permission-node@0.7.8-next.0 + - @backstage/plugin-auth-node@0.2.14-next.0 + - @backstage/backend-plugin-api@0.5.2-next.0 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + - @backstage/plugin-permission-common@0.7.5 + +## 0.5.19 + +### Patch Changes + +- 84946a580c4: Introduced alpha export of the `permissionPlugin` for use in the new backend system, along with a `permissionModuleAllowAllPolicy` that can be used to allow all requests. +- 1e4f5e91b8e: Bump `zod` and `zod-to-json-schema` dependencies. +- Updated dependencies + - @backstage/backend-common@0.18.4 + - @backstage/plugin-permission-node@0.7.7 + - @backstage/plugin-permission-common@0.7.5 + - @backstage/plugin-auth-node@0.2.13 + - @backstage/backend-plugin-api@0.5.1 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + +## 0.5.19-next.2 + +### Patch Changes + +- 84946a580c4: Introduced alpha export of the `permissionPlugin` for use in the new backend system, along with a `permissionModuleAllowAllPolicy` that can be used to allow all requests. +- Updated dependencies + - @backstage/backend-common@0.18.4-next.2 + - @backstage/plugin-permission-node@0.7.7-next.2 + - @backstage/backend-plugin-api@0.5.1-next.2 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + - @backstage/plugin-auth-node@0.2.13-next.2 + - @backstage/plugin-permission-common@0.7.5-next.0 + +## 0.5.19-next.1 + +### Patch Changes + +- 1e4f5e91b8e: Bump `zod` and `zod-to-json-schema` dependencies. +- Updated dependencies + - @backstage/plugin-permission-node@0.7.7-next.1 + - @backstage/plugin-permission-common@0.7.5-next.0 + - @backstage/backend-common@0.18.4-next.1 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + - @backstage/plugin-auth-node@0.2.13-next.1 + +## 0.5.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.4-next.0 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + - @backstage/plugin-auth-node@0.2.13-next.0 + - @backstage/plugin-permission-common@0.7.4 + - @backstage/plugin-permission-node@0.7.7-next.0 + +## 0.5.18 + +### Patch Changes + +- 52b0022dab7: Updated dependency `msw` to `^1.0.0`. +- Updated dependencies + - @backstage/plugin-permission-node@0.7.6 + - @backstage/plugin-auth-node@0.2.12 + - @backstage/backend-common@0.18.3 + - @backstage/errors@1.1.5 + - @backstage/plugin-permission-common@0.7.4 + - @backstage/config@1.0.7 + +## 0.5.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.2.12-next.2 + - @backstage/backend-common@0.18.3-next.2 + - @backstage/plugin-permission-node@0.7.6-next.2 + - @backstage/config@1.0.7-next.0 + +## 0.5.18-next.1 + +### Patch Changes + +- 52b0022dab7: Updated dependency `msw` to `^1.0.0`. +- Updated dependencies + - @backstage/plugin-permission-node@0.7.6-next.1 + - @backstage/errors@1.1.5-next.0 + - @backstage/backend-common@0.18.3-next.1 + - @backstage/plugin-auth-node@0.2.12-next.1 + - @backstage/plugin-permission-common@0.7.4-next.0 + - @backstage/config@1.0.7-next.0 + +## 0.5.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.3-next.0 + - @backstage/config@1.0.6 + - @backstage/errors@1.1.4 + - @backstage/plugin-auth-node@0.2.12-next.0 + - @backstage/plugin-permission-common@0.7.3 + - @backstage/plugin-permission-node@0.7.6-next.0 + +## 0.5.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.2 + - @backstage/config@1.0.6 + - @backstage/errors@1.1.4 + - @backstage/plugin-auth-node@0.2.11 + - @backstage/plugin-permission-common@0.7.3 + - @backstage/plugin-permission-node@0.7.5 + +## 0.5.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.2-next.2 + - @backstage/plugin-auth-node@0.2.11-next.2 + - @backstage/plugin-permission-node@0.7.5-next.2 + - @backstage/config@1.0.6 + - @backstage/errors@1.1.4 + - @backstage/plugin-permission-common@0.7.3 + +## 0.5.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.2-next.1 + - @backstage/config@1.0.6 + - @backstage/errors@1.1.4 + - @backstage/plugin-auth-node@0.2.11-next.1 + - @backstage/plugin-permission-common@0.7.3 + - @backstage/plugin-permission-node@0.7.5-next.1 + +## 0.5.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.2-next.0 + - @backstage/plugin-auth-node@0.2.11-next.0 + - @backstage/plugin-permission-node@0.7.5-next.0 + +## 0.5.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.0 + - @backstage/config@1.0.6 + - @backstage/errors@1.1.4 + - @backstage/plugin-auth-node@0.2.9 + - @backstage/plugin-permission-common@0.7.3 + - @backstage/plugin-permission-node@0.7.3 + +## 0.5.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.0-next.1 + - @backstage/plugin-auth-node@0.2.9-next.1 + - @backstage/plugin-permission-node@0.7.3-next.1 + - @backstage/config@1.0.6-next.0 + - @backstage/errors@1.1.4 + - @backstage/plugin-permission-common@0.7.3-next.0 + +## 0.5.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.0-next.0 + - @backstage/config@1.0.6-next.0 + - @backstage/errors@1.1.4 + - @backstage/plugin-auth-node@0.2.9-next.0 + - @backstage/plugin-permission-common@0.7.3-next.0 + - @backstage/plugin-permission-node@0.7.3-next.0 + +## 0.5.14 + +### Patch Changes + +- b05dcd5530: Move the `zod` dependency to a version that does not collide with other libraries +- 3280711113: Updated dependency `msw` to `^0.49.0`. +- Updated dependencies + - @backstage/backend-common@0.17.0 + - @backstage/plugin-permission-common@0.7.2 + - @backstage/plugin-permission-node@0.7.2 + - @backstage/errors@1.1.4 + - @backstage/plugin-auth-node@0.2.8 + - @backstage/config@1.0.5 + +## 0.5.14-next.3 + +### Patch Changes + +- b05dcd5530: Move the `zod` dependency to a version that does not collide with other libraries +- Updated dependencies + - @backstage/plugin-permission-common@0.7.2-next.2 + - @backstage/plugin-permission-node@0.7.2-next.3 + - @backstage/backend-common@0.17.0-next.3 + - @backstage/config@1.0.5-next.1 + - @backstage/errors@1.1.4-next.1 + - @backstage/plugin-auth-node@0.2.8-next.3 + +## 0.5.14-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.17.0-next.2 + - @backstage/plugin-auth-node@0.2.8-next.2 + - @backstage/plugin-permission-node@0.7.2-next.2 + - @backstage/config@1.0.5-next.1 + - @backstage/errors@1.1.4-next.1 + - @backstage/plugin-permission-common@0.7.2-next.1 + +## 0.5.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.17.0-next.1 + - @backstage/plugin-auth-node@0.2.8-next.1 + - @backstage/plugin-permission-node@0.7.2-next.1 + - @backstage/config@1.0.5-next.1 + - @backstage/errors@1.1.4-next.1 + - @backstage/plugin-permission-common@0.7.2-next.1 + +## 0.5.14-next.0 + +### Patch Changes + +- 3280711113: Updated dependency `msw` to `^0.49.0`. +- Updated dependencies + - @backstage/backend-common@0.16.1-next.0 + - @backstage/plugin-auth-node@0.2.8-next.0 + - @backstage/plugin-permission-common@0.7.2-next.0 + - @backstage/plugin-permission-node@0.7.2-next.0 + - @backstage/config@1.0.5-next.0 + - @backstage/errors@1.1.4-next.0 + +## 0.5.13 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0 + - @backstage/plugin-auth-node@0.2.7 + - @backstage/plugin-permission-common@0.7.1 + - @backstage/plugin-permission-node@0.7.1 + - @backstage/config@1.0.4 + - @backstage/errors@1.1.3 + +## 0.5.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.1 + - @backstage/plugin-auth-node@0.2.7-next.1 + - @backstage/plugin-permission-node@0.7.1-next.1 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + +## 0.5.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/plugin-permission-node@0.7.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.5.12 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/plugin-permission-node@0.7.0 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## 0.5.12-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## 0.5.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/plugin-auth-node@0.2.6-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + - @backstage/plugin-permission-node@0.6.6-next.1 + +## 0.5.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/plugin-auth-node@0.2.6-next.0 + - @backstage/plugin-permission-node@0.6.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + +## 0.5.11 + +### Patch Changes + +- 667d917488: Updated dependency `msw` to `^0.47.0`. +- 87ec2ba4d6: Updated dependency `msw` to `^0.46.0`. +- bf5e9030eb: Updated dependency `msw` to `^0.45.0`. +- 2cbd533426: Uptake the `IdentityApi` change to use `getIdentity` instead of `authenticate` for retrieving the logged in users identity. +- Updated dependencies + - @backstage/backend-common@0.15.1 + - @backstage/plugin-auth-node@0.2.5 + - @backstage/plugin-permission-node@0.6.5 + - @backstage/config@1.0.2 + - @backstage/errors@1.1.1 + - @backstage/plugin-permission-common@0.6.4 + +## 0.5.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.2-next.0 + - @backstage/errors@1.1.1-next.0 + - @backstage/plugin-permission-common@0.6.4-next.2 + - @backstage/backend-common@0.15.1-next.3 + - @backstage/plugin-auth-node@0.2.5-next.3 + - @backstage/plugin-permission-node@0.6.5-next.3 + +## 0.5.11-next.1 + +### Patch Changes + +- 667d917488: Updated dependency `msw` to `^0.47.0`. +- 87ec2ba4d6: Updated dependency `msw` to `^0.46.0`. +- 2cbd533426: Uptake the `IdentityApi` change to use `getIdentity` instead of `authenticate` for retrieving the logged in users identity. +- Updated dependencies + - @backstage/backend-common@0.15.1-next.2 + - @backstage/plugin-auth-node@0.2.5-next.2 + - @backstage/plugin-permission-common@0.6.4-next.1 + - @backstage/plugin-permission-node@0.6.5-next.2 + +## 0.5.11-next.0 + +### Patch Changes + +- bf5e9030eb: Updated dependency `msw` to `^0.45.0`. +- Updated dependencies + - @backstage/backend-common@0.15.1-next.0 + - @backstage/plugin-auth-node@0.2.5-next.0 + - @backstage/plugin-permission-common@0.6.4-next.0 + - @backstage/plugin-permission-node@0.6.5-next.0 + +## 0.5.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.0 + - @backstage/plugin-auth-node@0.2.4 + - @backstage/plugin-permission-node@0.6.4 + +## 0.5.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.0-next.0 + - @backstage/plugin-auth-node@0.2.4-next.0 + - @backstage/plugin-permission-node@0.6.4-next.0 + +## 0.5.9 + +### Patch Changes + +- a70869e775: Updated dependency `msw` to `^0.43.0`. +- 8006d0f9bf: Updated dependency `msw` to `^0.44.0`. +- Updated dependencies + - @backstage/backend-common@0.14.1 + - @backstage/plugin-auth-node@0.2.3 + - @backstage/plugin-permission-common@0.6.3 + - @backstage/plugin-permission-node@0.6.3 + - @backstage/errors@1.1.0 + +## 0.5.9-next.2 + +### Patch Changes + +- a70869e775: Updated dependency `msw` to `^0.43.0`. +- Updated dependencies + - @backstage/backend-common@0.14.1-next.3 + - @backstage/plugin-auth-node@0.2.3-next.2 + - @backstage/plugin-permission-common@0.6.3-next.1 + - @backstage/plugin-permission-node@0.6.3-next.2 + +## 0.5.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.14.1-next.1 + - @backstage/errors@1.1.0-next.0 + - @backstage/plugin-auth-node@0.2.3-next.1 + - @backstage/plugin-permission-common@0.6.3-next.0 + - @backstage/plugin-permission-node@0.6.3-next.1 + +## 0.5.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.14.1-next.0 + - @backstage/plugin-auth-node@0.2.3-next.0 + - @backstage/plugin-permission-node@0.6.3-next.0 + +## 0.5.8 + +### Patch Changes + +- 8f7b1835df: Updated dependency `msw` to `^0.41.0`. +- Updated dependencies + - @backstage/plugin-permission-node@0.6.2 + - @backstage/backend-common@0.14.0 + - @backstage/plugin-auth-node@0.2.2 + - @backstage/plugin-permission-common@0.6.2 + +## 0.5.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.14.0-next.2 + - @backstage/plugin-auth-node@0.2.2-next.2 + - @backstage/plugin-permission-node@0.6.2-next.2 + +## 0.5.8-next.1 + +### Patch Changes + +- 8f7b1835df: Updated dependency `msw` to `^0.41.0`. +- Updated dependencies + - @backstage/backend-common@0.13.6-next.1 + - @backstage/plugin-auth-node@0.2.2-next.1 + - @backstage/plugin-permission-common@0.6.2-next.0 + - @backstage/plugin-permission-node@0.6.2-next.1 + +## 0.5.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/plugin-auth-node@0.2.2-next.0 + - @backstage/plugin-permission-node@0.6.2-next.0 + +## 0.5.7 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.3 + - @backstage/config@1.0.1 + - @backstage/plugin-auth-node@0.2.1 + - @backstage/plugin-permission-common@0.6.1 + - @backstage/plugin-permission-node@0.6.1 + +## 0.5.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.3-next.2 + - @backstage/config@1.0.1-next.0 + - @backstage/plugin-auth-node@0.2.1-next.1 + - @backstage/plugin-permission-common@0.6.1-next.0 + - @backstage/plugin-permission-node@0.6.1-next.1 + +## 0.5.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.3-next.0 + - @backstage/plugin-auth-node@0.2.1-next.0 + - @backstage/plugin-permission-node@0.6.1-next.0 + +## 0.5.6 + +### Patch Changes + +- c98d271466: Use updated types from `@backstage/plugin-permission-common` +- 95284162d6: - Add more specific check for policies which return conditional decisions for non-resource permissions. + - Refine permission validation in authorize endpoint to differentiate between `BasicPermission` and `ResourcePermission` instances. +- Updated dependencies + - @backstage/plugin-permission-common@0.6.0 + - @backstage/plugin-permission-node@0.6.0 + - @backstage/plugin-auth-node@0.2.0 + - @backstage/backend-common@0.13.2 + +## 0.5.6-next.1 + +### Patch Changes + +- c98d271466: Use updated types from `@backstage/plugin-permission-common` +- 95284162d6: - Add more specific check for policies which return conditional decisions for non-resource permissions. + - Refine permission validation in authorize endpoint to differentiate between `BasicPermission` and `ResourcePermission` instances. +- Updated dependencies + - @backstage/plugin-permission-common@0.6.0-next.0 + - @backstage/plugin-permission-node@0.6.0-next.1 + - @backstage/backend-common@0.13.2-next.1 + +## 0.5.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.2.0-next.0 + - @backstage/backend-common@0.13.2-next.0 + - @backstage/plugin-permission-node@0.5.6-next.0 + +## 0.5.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.1 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/plugin-permission-common@0.5.3 + - @backstage/plugin-auth-node@0.1.6 + - @backstage/plugin-permission-node@0.5.5 + +## 0.5.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.0 + - @backstage/plugin-auth-node@0.1.5 + - @backstage/plugin-permission-node@0.5.4 + +## 0.5.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.0-next.0 + - @backstage/plugin-auth-node@0.1.5-next.0 + - @backstage/plugin-permission-node@0.5.4-next.0 + +## 0.5.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.12.0 + - @backstage/plugin-permission-common@0.5.2 + - @backstage/plugin-permission-node@0.5.3 + - @backstage/plugin-auth-node@0.1.4 + +## 0.5.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.11.0 + - @backstage/plugin-permission-node@0.5.2 + - @backstage/plugin-auth-node@0.1.3 + +## 0.5.1 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/config@0.1.15 + - @backstage/errors@0.2.2 + - @backstage/plugin-auth-node@0.1.2 + - @backstage/plugin-permission-common@0.5.1 + - @backstage/plugin-permission-node@0.5.1 + +## 0.5.0 + +### Minor Changes + +- e2cf0662eb: Add a warning if the permission backend is used without setting `permission.enabled=true`. + + **BREAKING** Permission backend's `createRouter` now requires a `config` option. + + ```diff + // packages/backend/src/plugins/permission.ts + + ... + export default async function createPlugin({ + ... + + config, + }: PluginEnvironment) { + return createRouter({ + ... + + config, + }); + } + ``` + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/errors@0.2.1 + - @backstage/plugin-auth-node@0.1.1 + - @backstage/plugin-permission-common@0.5.0 + - @backstage/config@0.1.14 + - @backstage/plugin-permission-node@0.5.0 + +## 0.4.3 + +### Patch Changes + +- b3f3e42036: Use `getBearerTokenFromAuthorizationHeader` from `@backstage/plugin-auth-node` instead of the deprecated `IdentityClient` method. +- Updated dependencies + - @backstage/backend-common@0.10.7 + - @backstage/plugin-auth-node@0.1.0 + - @backstage/plugin-permission-node@0.4.3 + +## 0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.10.0-next.0 + - @backstage/backend-common@0.10.7-next.0 + - @backstage/plugin-permission-node@0.4.3-next.0 + +## 0.4.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.9.0 + - @backstage/backend-common@0.10.6 + - @backstage/plugin-permission-node@0.4.2 + +## 0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.9.0-next.1 + - @backstage/backend-common@0.10.6-next.0 + - @backstage/plugin-permission-node@0.4.2-next.1 + +## 0.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.9.0-next.0 + - @backstage/plugin-permission-node@0.4.2-next.0 + +## 0.4.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.8.0 + - @backstage/backend-common@0.10.5 + - @backstage/plugin-permission-node@0.4.1 + +## 0.4.0 + +### Minor Changes + +- b768259244: **BREAKING**: Wrap batched requests and responses to /authorize in an envelope object. The latest version of the PermissionClient in @backstage/permission-common uses the new format - as long as the permission-backend is consumed using this client, no other changes are necessary. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.7.0 + - @backstage/plugin-permission-common@0.4.0 + - @backstage/backend-common@0.10.4 + - @backstage/config@0.1.13 + - @backstage/plugin-permission-node@0.4.0 + +## 0.4.0-next.0 + +### Minor Changes + +- b768259244: **BREAKING**: Wrap batched requests and responses to /authorize in an envelope object. The latest version of the PermissionClient in @backstage/permission-common uses the new format - as long as the permission-backend is consumed using this client, no other changes are necessary. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.7.0-next.0 + - @backstage/plugin-permission-common@0.4.0-next.0 + - @backstage/backend-common@0.10.4-next.0 + - @backstage/config@0.1.13-next.0 + - @backstage/plugin-permission-node@0.4.0-next.0 + +## 0.3.0 + +### Minor Changes + +- 419ca637c0: Optimizations to the integration between the permission backend and plugin-backends using createPermissionIntegrationRouter: + + - The permission backend already supported batched requests to authorize, but would make calls to plugin backend to apply conditions serially. Now, after applying the policy for each authorization request, the permission backend makes a single batched /apply-conditions request to each plugin backend referenced in policy decisions. + - The `getResource` method accepted by `createPermissionIntegrationRouter` has been replaced with `getResources`, to allow consumers to make batch requests to upstream data stores. When /apply-conditions is called with a batch of requests, all required resources are requested in a single invocation of `getResources`. + + Plugin owners consuming `createPermissionIntegrationRouter` should replace the `getResource` method in the options with a `getResources` method, accepting an array of resourceRefs, and returning an array of the corresponding resources. + +### Patch Changes + +- Updated dependencies + - @backstage/config@0.1.12 + - @backstage/backend-common@0.10.3 + - @backstage/plugin-permission-node@0.3.0 + - @backstage/plugin-auth-backend@0.6.2 + - @backstage/errors@0.2.0 + - @backstage/plugin-permission-common@0.3.1 + +## 0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.6.0 + - @backstage/backend-common@0.10.1 + - @backstage/plugin-permission-node@0.2.3 + +## 0.2.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.0 + - @backstage/plugin-auth-backend@0.5.2 + - @backstage/plugin-permission-common@0.3.0 + - @backstage/plugin-permission-node@0.2.2 + +## 0.2.1 + +### Patch Changes + +- a036b65c2f: Updated to use the new `BackstageIdentityResponse` type from `@backstage/plugin-auth-backend`. + + The `BackstageIdentityResponse` type is backwards compatible with the `BackstageIdentity`, and provides an additional `identity` field with the claims of the user. + +- Updated dependencies + - @backstage/plugin-auth-backend@0.5.0 + - @backstage/backend-common@0.9.13 + - @backstage/plugin-permission-node@0.2.1 + +## 0.2.0 + +### Minor Changes + +- 450ca92330: Change route used for integration between the authorization framework and other plugin backends to use the /.well-known prefix. + +### Patch Changes + +- e7851efa9e: Rename and adjust permission policy return type to reduce nesting +- Updated dependencies + - @backstage/plugin-auth-backend@0.4.10 + - @backstage/plugin-permission-node@0.2.0 + - @backstage/backend-common@0.9.12 + +## 0.1.0 + +### Minor Changes + +- 7a8312f126: New package containing the backend for authorization and permissions. For more information, see the [authorization PRFC](https://github.com/backstage/backstage/pull/7761). + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.4.9 + - @backstage/plugin-permission-node@0.1.0 + - @backstage/backend-common@0.9.11 + - @backstage/plugin-permission-common@0.2.0 + +## @backstage/plugin-permission-common@0.9.6-next.0 + +# @backstage/plugin-permission-common + +## 0.9.5-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.9.4 + +### Patch Changes + +- 872eb91: Upgrade `zod-to-json-schema` to latest version + +## 0.9.3 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/config@1.3.6 + +## 0.9.3-next.1 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns + +## 0.9.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.6-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.9.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.5 + +## 0.9.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + +## 0.9.1 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/config@1.3.3 + +## 0.9.1-next.0 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/config@1.3.3-next.0 + +## 0.9.0 + +### Minor Changes + +- 4da2965: Fixed an issue causing the `PermissionClient` to exhaust the request body size limit too quickly when making many requests. + +### Patch Changes + +- 37328b1: Fixed an issue causing `PermissionClient` to throw an error when authorizing basic permissions with the `permission.EXPERIMENTAL_enableBatchedRequests` config enabled. +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.9.0-next.0 + +### Minor Changes + +- 4da2965: Fixed an issue causing the `PermissionClient` to exhaust the request body size limit too quickly when making many requests. + +### Patch Changes + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.8.4 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.2.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.8.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.2.1-next.0 + - @backstage/config@1.3.2-next.0 + - @backstage/errors@1.2.7-next.0 + +## 0.8.3 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.6 + - @backstage/config@1.3.1 + - @backstage/types@1.2.0 + +## 0.8.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.6-next.0 + - @backstage/config@1.3.1-next.0 + - @backstage/types@1.2.0 + +## 0.8.2 + +### Patch Changes + +- 4e58bc7: Upgrade to uuid v11 internally +- Updated dependencies + - @backstage/config@1.3.0 + - @backstage/types@1.2.0 + - @backstage/errors@1.2.5 + +## 0.8.1 + +### Patch Changes + +- df784fe: Add the MetadataResponse type from @backstage/plugin-permission-node, since this + type might be used in frontend code. +- 137fa34: Add the MetadataResponseSerializedRule type from @backstage/plugin-permission-node, since this type might be used in frontend code. +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.8.1-next.1 + +### Patch Changes + +- df784fe: Add the MetadataResponse type from @backstage/plugin-permission-node, since this + type might be used in frontend code. +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.8.1-next.0 + +### Patch Changes + +- 137fa34: Add the MetadataResponseSerializedRule type from @backstage/plugin-permission-node, since this type might be used in frontend code. +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.8.0 + +### Minor Changes + +- f4085b8: **BREAKING**: Removed the deprecated and unused `token` option from `EvaluatorRequestOptions`. The `PermissionsClient` now has its own `PermissionClientRequestOptions` type that declares the `token` option instead. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.7.14 + +### Patch Changes + +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.7.14-next.0 + +### Patch Changes + +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.7.13 + +### Patch Changes + +- 0502d82: The `token` option of the `PermissionEvaluator` methods is now deprecated. The options that only apply to backend implementations have been moved to `PermissionsService` from `@backstage/backend-plugin-api` instead. +- 0fb419b: Updated dependency `uuid` to `^9.0.0`. + Updated dependency `@types/uuid` to `^9.0.0`. +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.7.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.2.0-next.1 + - @backstage/errors@1.2.4-next.0 + - @backstage/types@1.1.1 + +## 0.7.13-next.0 + +### Patch Changes + +- 0502d82: The `token` option of the `PermissionEvaluator` methods is now deprecated. The options that only apply to backend implementations have been moved to `PermissionsService` from `@backstage/backend-plugin-api` instead. +- 0fb419b: Updated dependency `uuid` to `^9.0.0`. + Updated dependency `@types/uuid` to `^9.0.0`. +- Updated dependencies + - @backstage/errors@1.2.4-next.0 + - @backstage/config@1.1.2-next.0 + - @backstage/types@1.1.1 + +## 0.7.12 + +### Patch Changes + +- b1acd9b: Updated README +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.7.11 + +### Patch Changes + +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.7.10 + +### Patch Changes + +- 4aa43f62aa: Updated dependency `cross-fetch` to `^4.0.0`. +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.7.9 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + +## 0.7.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.3-next.0 + - @backstage/config@1.1.1-next.0 + - @backstage/types@1.1.1 + +## 0.7.8 + +### Patch Changes + +- 406b786a2a2c: Mark package as being free of side effects, allowing more optimized Webpack builds. +- Updated dependencies + - @backstage/config@1.1.0 + - @backstage/errors@1.2.2 + - @backstage/types@1.1.1 + +## 0.7.8-next.2 + +### Patch Changes + +- 406b786a2a2c: Mark package as being free of side effects, allowing more optimized Webpack builds. +- Updated dependencies + - @backstage/config@1.1.0-next.2 + - @backstage/errors@1.2.2-next.0 + - @backstage/types@1.1.1-next.0 + +## 0.7.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.0-next.1 + - @backstage/errors@1.2.1 + - @backstage/types@1.1.0 + +## 0.7.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.0-next.0 + - @backstage/errors@1.2.1 + - @backstage/types@1.1.0 + +## 0.7.7 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.1 + - @backstage/config@1.0.8 + - @backstage/types@1.1.0 + +## 0.7.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.1-next.0 + - @backstage/config@1.0.8 + - @backstage/types@1.1.0 + +## 0.7.6 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.1.0 + - @backstage/errors@1.2.0 + - @backstage/config@1.0.8 + +## 0.7.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.0-next.0 + - @backstage/config@1.0.7 + - @backstage/types@1.0.2 + +## 0.7.5 + +### Patch Changes + +- 1e4f5e91b8e: Bump `zod` and `zod-to-json-schema` dependencies. +- Updated dependencies + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + - @backstage/types@1.0.2 + +## 0.7.5-next.0 + +### Patch Changes + +- 1e4f5e91b8e: Bump `zod` and `zod-to-json-schema` dependencies. +- Updated dependencies + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + - @backstage/types@1.0.2 + +## 0.7.4 + +### Patch Changes + +- 52b0022dab7: Updated dependency `msw` to `^1.0.0`. +- Updated dependencies + - @backstage/errors@1.1.5 + - @backstage/config@1.0.7 + - @backstage/types@1.0.2 + +## 0.7.4-next.0 + +### Patch Changes + +- 52b0022dab7: Updated dependency `msw` to `^1.0.0`. +- Updated dependencies + - @backstage/errors@1.1.5-next.0 + - @backstage/config@1.0.7-next.0 + - @backstage/types@1.0.2 + +## 0.7.3 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.6 + - @backstage/errors@1.1.4 + - @backstage/types@1.0.2 + +## 0.7.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.6-next.0 + - @backstage/errors@1.1.4 + - @backstage/types@1.0.2 + +## 0.7.2 + +### Patch Changes + +- b05dcd5530: Move the `zod` dependency to a version that does not collide with other libraries +- 3280711113: Updated dependency `msw` to `^0.49.0`. +- Updated dependencies + - @backstage/errors@1.1.4 + - @backstage/types@1.0.2 + - @backstage/config@1.0.5 + +## 0.7.2-next.2 + +### Patch Changes + +- b05dcd5530: Move the `zod` dependency to a version that does not collide with other libraries +- Updated dependencies + - @backstage/config@1.0.5-next.1 + - @backstage/errors@1.1.4-next.1 + - @backstage/types@1.0.2-next.1 + +## 0.7.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.2-next.1 + - @backstage/config@1.0.5-next.1 + - @backstage/errors@1.1.4-next.1 + +## 0.7.2-next.0 + +### Patch Changes + +- 3280711113: Updated dependency `msw` to `^0.49.0`. +- Updated dependencies + - @backstage/types@1.0.2-next.0 + - @backstage/config@1.0.5-next.0 + - @backstage/errors@1.1.4-next.0 + +## 0.7.1 + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- 64848c963c: Properly handle rules that have no parameters in `PermissionClient` +- Updated dependencies + - @backstage/types@1.0.1 + - @backstage/config@1.0.4 + - @backstage/errors@1.1.3 + +## 0.7.1-next.0 + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- 64848c963c: Properly handle rules that have no parameters in `PermissionClient` +- Updated dependencies + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.7.0 + +### Minor Changes + +- 46b4a72cee: **BREAKING**: When defining permission rules, it's now necessary to provide a [ZodSchema](https://github.com/colinhacks/zod) that specifies the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. + + To help with this, we have also made a change to the API of permission rules. Before, the permission rules `toQuery` and `apply` signature expected parameters to be separate arguments, like so... + + ```ts + createPermissionRule({ + apply: (resource, foo, bar) => true, + toQuery: (foo, bar) => {}, + }); + ``` + + The API has now changed to expect the parameters as a single object + + ```ts + createPermissionRule({ + paramSchema: z.object({ + foo: z.string().describe('Foo value to match'), + bar: z.string().describe('Bar value to match'), + }), + apply: (resource, { foo, bar }) => true, + toQuery: ({ foo, bar }) => {}, + }); + ``` + + One final change made is to limit the possible values for a parameter to primitives and arrays of primitives. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## 0.7.0-next.2 + +### Minor Changes + +- 46b4a72cee: **BREAKING**: When defining permission rules, it's now necessary to provide a [ZodSchema](https://github.com/colinhacks/zod) that specifies the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. + + To help with this, we have also made a change to the API of permission rules. Before, the permission rules `toQuery` and `apply` signature expected parameters to be separate arguments, like so... + + ```ts + createPermissionRule({ + apply: (resource, foo, bar) => true, + toQuery: (foo, bar) => {}, + }); + ``` + + The API has now changed to expect the parameters as a single object + + ```ts + createPermissionRule({ + paramSchema: z.object({ + foo: z.string().describe('Foo value to match'), + bar: z.string().describe('Bar value to match'), + }), + apply: (resource, { foo, bar }) => true, + toQuery: ({ foo, bar }) => {}, + }); + ``` + + One final change made is to limit the possible values for a parameter to primitives and arrays of primitives. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## 0.6.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + +## 0.6.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + +## 0.6.4 + +### Patch Changes + +- 7d47def9c4: Removed dependency on `@types/jest`. +- 667d917488: Updated dependency `msw` to `^0.47.0`. +- 87ec2ba4d6: Updated dependency `msw` to `^0.46.0`. +- bf5e9030eb: Updated dependency `msw` to `^0.45.0`. +- Updated dependencies + - @backstage/config@1.0.2 + - @backstage/errors@1.1.1 + +## 0.6.4-next.2 + +### Patch Changes + +- 7d47def9c4: Removed dependency on `@types/jest`. +- Updated dependencies + - @backstage/config@1.0.2-next.0 + - @backstage/errors@1.1.1-next.0 + +## 0.6.4-next.1 + +### Patch Changes + +- 667d917488: Updated dependency `msw` to `^0.47.0`. +- 87ec2ba4d6: Updated dependency `msw` to `^0.46.0`. + +## 0.6.4-next.0 + +### Patch Changes + +- bf5e9030eb: Updated dependency `msw` to `^0.45.0`. + +## 0.6.3 + +### Patch Changes + +- a70869e775: Updated dependency `msw` to `^0.43.0`. +- 8006d0f9bf: Updated dependency `msw` to `^0.44.0`. +- Updated dependencies + - @backstage/errors@1.1.0 + +## 0.6.3-next.1 + +### Patch Changes + +- a70869e775: Updated dependency `msw` to `^0.43.0`. + +## 0.6.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.1.0-next.0 + +## 0.6.2 + +### Patch Changes + +- 8f7b1835df: Updated dependency `msw` to `^0.41.0`. + +## 0.6.2-next.0 + +### Patch Changes + +- 8f7b1835df: Updated dependency `msw` to `^0.41.0`. + +## 0.6.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.1 + +## 0.6.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.1-next.0 + +## 0.6.0 + +### Minor Changes + +- 8012ac46a0: Add `resourceType` property to `PermissionCondition` type to allow matching them with `ResourcePermission` instances. +- c98d271466: Refactor api types into more specific, decoupled names. + + - **BREAKING:** + - Renamed `AuthorizeDecision` to `EvaluatePermissionResponse` + - Renamed `AuthorizeQuery` to `EvaluatePermissionRequest` + - Renamed `AuthorizeRequest` to `EvaluatePermissionRequestBatch` + - Renamed `AuthorizeResponse` to `EvaluatePermissionResponseBatch` + - Renamed `Identified` to `IdentifiedPermissionMessage` + - Add `PermissionMessageBatch` helper type + - Add `ConditionalPolicyDecision`, `DefinitivePolicyDecision`, and `PolicyDecision` types from `@backstage/plugin-permission-node` + +### Patch Changes + +- 90754d4fa9: Removed [strict](https://github.com/colinhacks/zod#strict) validation from `PermissionCriteria` schemas to support backward-compatible changes. + +- 2b07063d77: Added `PermissionEvaluator`, which will replace the existing `PermissionAuthorizer` interface. This new interface provides stronger type safety and validation by splitting `PermissionAuthorizer.authorize()` into two methods: + + - `authorize()`: Used when the caller requires a definitive decision. + - `authorizeConditional()`: Used when the caller can optimize the evaluation of any conditional decisions. For example, a plugin backend may want to use conditions in a database query instead of evaluating each resource in memory. + +- 8012ac46a0: Add `isPermission` helper method. + +- 95284162d6: - Add more specific `Permission` types. + - Add `createPermission` helper to infer the appropriate type for some permission input. + - Add `isResourcePermission` helper to refine Permissions to ResourcePermissions. + +## 0.6.0-next.1 + +### Patch Changes + +- 2b07063d77: Added `PermissionEvaluator`, which will replace the existing `PermissionAuthorizer` interface. This new interface provides stronger type safety and validation by splitting `PermissionAuthorizer.authorize()` into two methods: + + - `authorize()`: Used when the caller requires a definitive decision. + - `authorizeConditional()`: Used when the caller can optimize the evaluation of any conditional decisions. For example, a plugin backend may want to use conditions in a database query instead of evaluating each resource in memory. + +## 0.6.0-next.0 + +### Minor Changes + +- 8012ac46a0: Add `resourceType` property to `PermissionCondition` type to allow matching them with `ResourcePermission` instances. +- c98d271466: Refactor api types into more specific, decoupled names. + + - **BREAKING:** + - Renamed `AuthorizeDecision` to `EvaluatePermissionResponse` + - Renamed `AuthorizeQuery` to `EvaluatePermissionRequest` + - Renamed `AuthorizeRequest` to `EvaluatePermissionRequestBatch` + - Renamed `AuthorizeResponse` to `EvaluatePermissionResponseBatch` + - Renamed `Identified` to `IdentifiedPermissionMessage` + - Add `PermissionMessageBatch` helper type + - Add `ConditionalPolicyDecision`, `DefinitivePolicyDecision`, and `PolicyDecision` types from `@backstage/plugin-permission-node` + +### Patch Changes + +- 8012ac46a0: Add `isPermission` helper method. +- 95284162d6: - Add more specific `Permission` types. + - Add `createPermission` helper to infer the appropriate type for some permission input. + - Add `isResourcePermission` helper to refine Permissions to ResourcePermissions. + +## 0.5.3 + +### Patch Changes + +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + +## 0.5.2 + +### Patch Changes + +- 79b9d8a861: Add api doc comments to `Permission` type properties. + +## 0.5.1 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/config@0.1.15 + - @backstage/errors@0.2.2 + +## 0.5.0 + +### Minor Changes + +- 8c646beb24: **BREAKING** `PermissionCriteria` now requires at least one condition in `anyOf` and `allOf` arrays. This addresses some ambiguous behavior outlined in #9280. + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/errors@0.2.1 + - @backstage/config@0.1.14 + +## 0.4.0 + +### Minor Changes + +- b768259244: **BREAKING**: Authorize API request and response types have been updated. The existing `AuthorizeRequest` and `AuthorizeResponse` types now match the entire request and response objects for the /authorize endpoint, and new types `AuthorizeQuery` and `AuthorizeDecision` have been introduced for individual items in the request and response batches respectively. + + **BREAKING**: PermissionClient has been updated to use the new request and response format in the latest version of @backstage/plugin-permission-backend. + +### Patch Changes + +- Updated dependencies + - @backstage/config@0.1.13 + +## 0.4.0-next.0 + +### Minor Changes + +- b768259244: **BREAKING**: Authorize API request and response types have been updated. The existing `AuthorizeRequest` and `AuthorizeResponse` types now match the entire request and response objects for the /authorize endpoint, and new types `AuthorizeQuery` and `AuthorizeDecision` have been introduced for individual items in the request and response batches respectively. + + **BREAKING**: PermissionClient has been updated to use the new request and response format in the latest version of @backstage/plugin-permission-backend. + +### Patch Changes + +- Updated dependencies + - @backstage/config@0.1.13-next.0 + +## 0.3.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@0.1.12 + - @backstage/errors@0.2.0 + +## 0.3.0 + +### Minor Changes + +- 0e8ec6d974: - Add `PermissionAuthorizer` interface matching `PermissionClient` to allow alternative implementations like the `ServerPermissionClient` in @backstage/plugin-permission-node. + + Breaking Changes: + + - Remove "api" suffixes from constructor parameters in PermissionClient + + ```diff + const { config, discovery } = options; + - const permissionClient = new PermissionClient({ discoveryApi: discovery, configApi: config }); + + const permissionClient = new PermissionClient({ discovery, config }); + ``` + +## 0.2.0 + +### Minor Changes + +- 92439056fb: Accept configApi rather than enabled flag in PermissionClient constructor. + +### Patch Changes + +- Updated dependencies + - @backstage/errors@0.1.5 + +## @backstage/plugin-permission-node@0.10.10-next.0 + +# @backstage/plugin-permission-node + +## 0.10.9-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.10.8 + +### Patch Changes + +- 872eb91: Upgrade `zod-to-json-schema` to latest version +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-auth-node@0.6.11 + - @backstage/plugin-permission-common@0.9.4 + +## 0.10.7 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.10.7-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.3 + +## 0.10.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.3 + +## 0.10.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config@1.3.6 + +## 0.10.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-permission-common@0.9.3-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## 0.10.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.9-next.0 + - @backstage/config@1.3.6-next.0 + - @backstage/backend-plugin-api@1.4.5-next.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.3-next.0 + +## 0.10.5 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.5 + - @backstage/backend-plugin-api@1.4.4 + - @backstage/plugin-auth-node@0.6.8 + - @backstage/plugin-permission-common@0.9.2 + +## 0.10.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + +## 0.10.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.7 + - @backstage/backend-plugin-api@1.4.3 + +## 0.10.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.7-next.0 + - @backstage/backend-plugin-api@1.4.3-next.0 + +## 0.10.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6 + - @backstage/backend-plugin-api@1.4.2 + +## 0.10.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.1 + +## 0.10.2 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.10.2-next.0 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.10.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.0 + +## 0.10.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.0 + +## 0.10.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.10.0 + +### Minor Changes + +- 4da2965: Fixed an issue causing the `PermissionClient` to exhaust the request body size limit too quickly when making many requests. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.10.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.0-next.0 + +## 0.10.0-next.1 + +### Minor Changes + +- 4da2965: Fixed an issue causing the `PermissionClient` to exhaust the request body size limit too quickly when making many requests. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.9.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.0 + - @backstage/plugin-auth-node@0.6.3-next.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.8.4 + +## 0.9.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.0 + - @backstage/plugin-auth-node@0.6.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.8.4 + +## 0.9.0 + +### Minor Changes + +- 22ace13: **BREAKING** The `ServerPermissionClient` can no longer be instantiated with a `tokenManager` and must instead be instantiated with an `auth` service. If you are still on the legacy backend system, use `createLegacyAuthAdapters()` from `@backstage/backend-common` to create a compatible `auth` service. + +### Patch Changes + +- 728e3e1: Improved type inference when passing a `PermissionResourceRef` to `createPermissionRule`. +- 876f2e1: Deprecated `createPermissionIntegrationRouter` and related types, which has been replaced by `PermissionRegistryService`. For more information, including how to migrate existing plugins, see the [service docs](https://backstage.io/docs/backend-system/core-services/permissions-registry). +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1 + - @backstage/backend-plugin-api@1.2.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.8.4 + +## 0.8.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1-next.1 + - @backstage/backend-plugin-api@1.2.1-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.8.4 + +## 0.8.9-next.0 + +### Patch Changes + +- 728e3e1: Improved type inference when passing a `PermissionResourceRef` to `createPermissionRule`. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.1-next.0 + - @backstage/backend-plugin-api@1.2.1-next.0 + +## 0.8.8 + +### Patch Changes + +- 049d5d4: The returned router from `createPermissionIntegrationRouter` is now mutable, allowing for permissions and resources to be added after creation of the router. +- b71f634: Added a new `PermissionRuleset` type that encapsulates a lookup function for permission rules, which can be created by the new `PermissionsRegistryService` via the `getPermissionRuleset` method. The `createConditionTransformer` and `createConditionAuthorizer` functions have been adapted to receive these accessors as arguments, with their older counterparts being deprecated. +- a9621de: Added a new `createPermissionResourceRef` utility that encapsulates the constants and types related to a permission resource types. The `createConditionExports` and `createPermissionRule` functions have also been adapted to accept these references as arguments, deprecating their older counterparts. +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0 + - @backstage/plugin-auth-node@0.6.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.8.4 + +## 0.8.8-next.2 + +### Patch Changes + +- b71f634: Added a new `PermissionRuleset` type that encapsulates a lookup function for permission rules, which can be created by the new `PermissionsRegistryService` via the `getPermissionRuleset` method. The `createConditionTransformer` and `createConditionAuthorizer` functions have been adapted to receive these accessors as arguments, with their older counterparts being deprecated. +- a9621de: Added a new `createPermissionResourceRef` utility that encapsulates the constants and types related to a permission resource types. The `createConditionExports` and `createPermissionRule` functions have also been adapted to accept these references as arguments, deprecating their older counterparts. +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.2 + - @backstage/plugin-auth-node@0.6.0-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.8.4 + +## 0.8.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.1 + - @backstage/plugin-auth-node@0.6.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.8.4 + +## 0.8.8-next.0 + +### Patch Changes + +- 049d5d4: The returned router from `createPermissionIntegrationRouter` is now mutable, allowing for permissions and resources to be added after creation of the router. +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-auth-node@0.5.7-next.0 + - @backstage/plugin-permission-common@0.8.4 + +## 0.8.7 + +### Patch Changes + +- d9d62ef: Remove some internal usages of the backend-common package +- Updated dependencies + - @backstage/plugin-auth-node@0.5.6 + - @backstage/backend-plugin-api@1.1.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.8.4 + +## 0.8.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.1-next.1 + - @backstage/config@1.3.2-next.0 + - @backstage/errors@1.2.7-next.0 + - @backstage/plugin-auth-node@0.5.6-next.1 + - @backstage/plugin-permission-common@0.8.4-next.0 + +## 0.8.7-next.0 + +### Patch Changes + +- d9d62ef: Remove some internal usages of the backend-common package +- Updated dependencies + - @backstage/plugin-auth-node@0.5.6-next.0 + - @backstage/backend-plugin-api@1.1.1-next.0 + - @backstage/config@1.3.1 + - @backstage/errors@1.2.6 + - @backstage/plugin-permission-common@0.8.3 + +## 0.8.6 + +### Patch Changes + +- b149e2a: The `createPermissionIntegrationRouter` function now detects and prevents the exposure of duplicate permissions. +- Updated dependencies + - @backstage/plugin-auth-node@0.5.5 + - @backstage/backend-plugin-api@1.1.0 + - @backstage/errors@1.2.6 + - @backstage/config@1.3.1 + - @backstage/plugin-permission-common@0.8.3 + +## 0.8.6-next.2 + +### Patch Changes + +- b149e2a: The `createPermissionIntegrationRouter` function now detects and prevents the exposure of duplicate permissions. +- Updated dependencies + - @backstage/backend-plugin-api@1.1.0-next.2 + - @backstage/errors@1.2.6-next.0 + - @backstage/plugin-auth-node@0.5.5-next.2 + - @backstage/config@1.3.1-next.0 + - @backstage/plugin-permission-common@0.8.3-next.0 + +## 0.8.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.5-next.1 + - @backstage/backend-plugin-api@1.1.0-next.1 + - @backstage/config@1.3.0 + - @backstage/errors@1.2.5 + - @backstage/plugin-permission-common@0.8.2 + +## 0.8.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.3-next.0 + - @backstage/plugin-auth-node@0.5.5-next.0 + - @backstage/config@1.3.0 + - @backstage/errors@1.2.5 + - @backstage/plugin-permission-common@0.8.2 + +## 0.8.5 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.0 + - @backstage/plugin-auth-node@0.5.4 + - @backstage/backend-plugin-api@1.0.2 + - @backstage/plugin-permission-common@0.8.2 + - @backstage/errors@1.2.5 + +## 0.8.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4-next.2 + - @backstage/backend-plugin-api@1.0.2-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.8.1 + +## 0.8.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.2-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.5.4-next.1 + - @backstage/plugin-permission-common@0.8.1 + +## 0.8.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.4-next.0 + - @backstage/backend-plugin-api@1.0.2-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.8.1 + +## 0.8.4 + +### Patch Changes + +- 094eaa3: Remove references to in-repo backend-common +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3 + - @backstage/backend-plugin-api@1.0.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.8.1 + +## 0.8.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3-next.1 + - @backstage/backend-plugin-api@1.0.1-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.8.1 + +## 0.8.4-next.0 + +### Patch Changes + +- 094eaa3: Remove references to in-repo backend-common +- Updated dependencies + - @backstage/plugin-auth-node@0.5.3-next.0 + - @backstage/backend-plugin-api@1.0.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.8.1 + +## 0.8.3 + +### Patch Changes + +- 19ff127: Internal refactor to remove dependencies on the identity and token manager services, which have been removed. Public APIs no longer require the identity service or token manager to be provided. +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-common@0.25.0 + - @backstage/backend-plugin-api@1.0.0 + - @backstage/plugin-auth-node@0.5.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.8.1 + +## 0.8.3-next.2 + +### Patch Changes + +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-common@0.25.0-next.2 + - @backstage/plugin-auth-node@0.5.2-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.8.1 + +## 0.8.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.8.1 + +## 0.8.3-next.0 + +### Patch Changes + +- 19ff127: Internal refactor to remove dependencies on the identity and token manager services, which have been removed. Public APIs no longer require the identity service or token manager to be provided. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.8.1 + +## 0.8.1 + +### Patch Changes + +- df784fe: The MetadataResponse type has been moved to @backstage/plugin-permission-common + to match the recent move of MetadataResponseSerializedRule, and should be + imported from there going forward. To avoid an immediate breaking change, this + type is still re-exported from this package, but is marked as deprecated and + will be removed in a future release. +- 5cd9878: The MetadataResponseSerializedRule type has been moved to @backstage/plugin-permission-common, and should be imported from there going forward. To avoid an immediate breaking change, this type is still re-exported from this package, but is marked as deprecated and will be removed in a future release. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.8.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.5.0-next.3 + - @backstage/plugin-permission-common@0.8.1-next.1 + +## 0.8.1-next.2 + +### Patch Changes + +- df784fe: The MetadataResponse type has been moved to @backstage/plugin-permission-common + to match the recent move of MetadataResponseSerializedRule, and should be + imported from there going forward. To avoid an immediate breaking change, this + type is still re-exported from this package, but is marked as deprecated and + will be removed in a future release. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.8.1-next.1 + +### Patch Changes + +- 5cd9878: The MetadataResponseSerializedRule type has been moved to @backstage/plugin-permission-common, and should be imported from there going forward. To avoid an immediate breaking change, this type is still re-exported from this package, but is marked as deprecated and will be removed in a future release. +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## 0.8.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-permission-common@0.8.0 + +## 0.8.0 + +### Minor Changes + +- 36f91e8: **BREAKING**: Updated the `ServerPermissionClient` to match the new `PermissionsService` interface, where the deprecated `token` option has been removed and the options are now required. + +### Patch Changes + +- ed10fd2: The `PermissionPolicy` interface has been updated to align with the recent changes to the Backstage auth system. The second argument to the `handle` method is now of the new `PolicyQueryUser` type. This type maintains the old fields from the `BackstageIdentityResponse`, which are now all deprecated. Instead, two new fields have been added, which allows access to the same information: + + - `credentials` - A `BackstageCredentials` object, which is useful for making requests to other services on behalf of the user as part of evaluating the policy. This replaces the deprecated `token` field. See the [Auth Service documentation](https://backstage.io/docs/backend-system/core-services/auth#creating-request-tokens) for information about how to create a token using these credentials. + - `info` - A `BackstageUserInfo` object, which contains the same information as the deprecated `identity`, except for the `type` field that was redundant. + + Most existing policies can be updated by replacing the `BackstageIdentityResponse` type with `PolicyQueryUser`, which is exported from `@backstage/plugin-permission-node`, as well as replacing any occurrences of `user?.identity` with `user?.info`. + +- 28b2cfb: Fix invalid cross-reference in API Reference docs + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.7.33-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-permission-common@0.7.14 + +## 0.7.32-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.21-next.0 + - @backstage/backend-common@0.23.2-next.0 + - @backstage/plugin-auth-node@0.4.16-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.14 + +## 0.7.30 + +### Patch Changes + +- 9e63318: Ensure that service token access restrictions, when present, are taken into account +- d44a20a: Added additional plugin metadata to `package.json`. +- c7b0dd1: Import `tokenManager` definition from `@backstage/backend-plugin-api` +- Updated dependencies + - @backstage/backend-common@0.23.0 + - @backstage/backend-plugin-api@0.6.19 + - @backstage/plugin-auth-node@0.4.14 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.7.30-next.3 + +### Patch Changes + +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.3 + - @backstage/plugin-auth-node@0.4.14-next.3 + - @backstage/plugin-permission-common@0.7.14-next.0 + - @backstage/backend-common@0.23.0-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.7.30-next.2 + +### Patch Changes + +- c7b0dd1: Import `tokenManager` definition from `@backstage/backend-plugin-api` +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.2 + - @backstage/backend-common@0.23.0-next.2 + - @backstage/plugin-auth-node@0.4.14-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.13 + +## 0.7.30-next.1 + +### Patch Changes + +- 9e63318: Ensure that service token access restrictions, when present, are taken into account +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + +## 0.7.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.13 + +## 0.7.29 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + +## 0.7.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## 0.7.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.13 + +## 0.7.28 + +### Patch Changes + +- 29fa05b: Fixed an issue causing `ServerPermissionClient` to generate an invalid token for authorizing permissions against the permission backend. +- Updated dependencies + - @backstage/backend-common@0.21.7 + - @backstage/backend-plugin-api@0.6.17 + - @backstage/plugin-auth-node@0.4.12 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.13 + +## 0.7.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.7-next.1 + - @backstage/backend-plugin-api@0.6.17-next.1 + - @backstage/plugin-auth-node@0.4.12-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.13 + +## 0.7.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.7-next.0 + - @backstage/backend-plugin-api@0.6.17-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.12-next.0 + - @backstage/plugin-permission-common@0.7.13 + +## 0.7.27 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.11 + - @backstage/backend-common@0.21.6 + - @backstage/backend-plugin-api@0.6.16 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.13 + +## 0.7.26 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.5 + - @backstage/plugin-auth-node@0.4.10 + - @backstage/backend-plugin-api@0.6.15 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.13 + +## 0.7.25 + +### Patch Changes + +- 0502d82: The `ServerPermissionClient` has been migrated to implement the `PermissionsService` interface, now accepting the new `BackstageCredentials` object in addition to the `token` option, which is now deprecated. It now also optionally depends on the new `AuthService`. +- Updated dependencies + - @backstage/backend-common@0.21.4 + - @backstage/plugin-auth-node@0.4.9 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/backend-plugin-api@0.6.14 + - @backstage/plugin-permission-common@0.7.13 + +## 0.7.25-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.4-next.2 + - @backstage/plugin-auth-node@0.4.9-next.2 + - @backstage/backend-plugin-api@0.6.14-next.2 + - @backstage/config@1.2.0-next.1 + - @backstage/errors@1.2.4-next.0 + - @backstage/plugin-permission-common@0.7.13-next.1 + +## 0.7.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.2.0-next.1 + - @backstage/backend-common@0.21.4-next.1 + - @backstage/backend-plugin-api@0.6.14-next.1 + - @backstage/plugin-auth-node@0.4.9-next.1 + - @backstage/plugin-permission-common@0.7.13-next.1 + - @backstage/errors@1.2.4-next.0 + +## 0.7.24-next.0 + +### Patch Changes + +- 0502d82: The `ServerPermissionClient` has been migrated to implement the `PermissionsService` interface, now accepting the new `BackstageCredentials` object in addition to the `token` option, which is now deprecated. It now also optionally depends on the new `AuthService`. +- Updated dependencies + - @backstage/backend-common@0.21.3-next.0 + - @backstage/plugin-auth-node@0.4.8-next.0 + - @backstage/errors@1.2.4-next.0 + - @backstage/backend-plugin-api@0.6.13-next.0 + - @backstage/plugin-permission-common@0.7.13-next.0 + - @backstage/config@1.1.2-next.0 + +## 0.7.21 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0 + - @backstage/plugin-auth-node@0.4.4 + - @backstage/backend-plugin-api@0.6.10 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.12 + +## 0.7.21-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.3 + - @backstage/plugin-auth-node@0.4.4-next.3 + - @backstage/backend-plugin-api@0.6.10-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.12 + +## 0.7.21-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.2 + - @backstage/backend-plugin-api@0.6.10-next.2 + - @backstage/plugin-auth-node@0.4.4-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.12 + +## 0.7.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.1 + - @backstage/backend-common@0.21.0-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.4-next.1 + - @backstage/plugin-permission-common@0.7.12 + +## 0.7.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.12 + +## 0.7.20 + +### Patch Changes + +- b1acd9b: Updated README +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.7.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## 0.7.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.11 + +## 0.7.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-permission-common@0.7.11 + +## 0.7.19 + +### Patch Changes + +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.7.19-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-permission-common@0.7.10 + +## 0.7.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.10 + +## 0.7.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-permission-common@0.7.10 + +## 0.7.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.0 + - @backstage/plugin-auth-node@0.4.2-next.0 + - @backstage/backend-plugin-api@0.6.8-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.10 + +## 0.7.18 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.9 + - @backstage/backend-plugin-api@0.6.7 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.1 + +## 0.7.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.7-next.2 + - @backstage/backend-common@0.19.9-next.2 + - @backstage/plugin-auth-node@0.4.1-next.2 + +## 0.7.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.9-next.1 + - @backstage/plugin-auth-node@0.4.1-next.1 + - @backstage/backend-plugin-api@0.6.7-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.9 + +## 0.7.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.9-next.0 + - @backstage/backend-plugin-api@0.6.7-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.1-next.0 + - @backstage/plugin-permission-common@0.7.9 + +## 0.7.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.8 + - @backstage/plugin-auth-node@0.4.0 + - @backstage/errors@1.2.3 + - @backstage/backend-plugin-api@0.6.6 + - @backstage/config@1.1.1 + - @backstage/plugin-permission-common@0.7.9 + +## 0.7.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.8-next.2 + - @backstage/plugin-auth-node@0.4.0-next.2 + - @backstage/errors@1.2.3-next.0 + - @backstage/backend-plugin-api@0.6.6-next.2 + - @backstage/config@1.1.1-next.0 + - @backstage/plugin-permission-common@0.7.9-next.0 + +## 0.7.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.7-next.1 + - @backstage/backend-plugin-api@0.6.5-next.1 + - @backstage/plugin-auth-node@0.3.2-next.1 + - @backstage/config@1.1.0 + - @backstage/errors@1.2.2 + - @backstage/plugin-permission-common@0.7.8 + +## 0.7.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.3.2-next.0 + - @backstage/backend-common@0.19.7-next.0 + - @backstage/config@1.1.0 + - @backstage/backend-plugin-api@0.6.5-next.0 + - @backstage/errors@1.2.2 + - @backstage/plugin-permission-common@0.7.8 + +## 0.7.14 + +### Patch Changes + +- a8a614ba0d07: Minor `package.json` update. +- Updated dependencies + - @backstage/backend-common@0.19.5 + - @backstage/plugin-auth-node@0.3.0 + - @backstage/config@1.1.0 + - @backstage/errors@1.2.2 + - @backstage/plugin-permission-common@0.7.8 + - @backstage/backend-plugin-api@0.6.3 + +## 0.7.14-next.3 + +### Patch Changes + +- a8a614ba0d07: Minor `package.json` update. +- Updated dependencies + - @backstage/config@1.1.0-next.2 + - @backstage/errors@1.2.2-next.0 + - @backstage/plugin-permission-common@0.7.8-next.2 + - @backstage/backend-plugin-api@0.6.3-next.3 + - @backstage/backend-common@0.19.5-next.3 + - @backstage/plugin-auth-node@0.3.0-next.3 + +## 0.7.14-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.0-next.1 + - @backstage/backend-common@0.19.5-next.2 + - @backstage/plugin-auth-node@0.3.0-next.2 + - @backstage/backend-plugin-api@0.6.3-next.2 + - @backstage/plugin-permission-common@0.7.8-next.1 + - @backstage/errors@1.2.1 + +## 0.7.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.0-next.0 + - @backstage/backend-common@0.19.5-next.1 + - @backstage/backend-plugin-api@0.6.3-next.1 + - @backstage/plugin-auth-node@0.3.0-next.1 + - @backstage/plugin-permission-common@0.7.8-next.0 + - @backstage/errors@1.2.1 + +## 0.7.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.3.0-next.0 + - @backstage/backend-common@0.19.4-next.0 + - @backstage/backend-plugin-api@0.6.2-next.0 + - @backstage/config@1.0.8 + - @backstage/errors@1.2.1 + - @backstage/plugin-permission-common@0.7.7 + +## 0.7.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.2 + - @backstage/backend-plugin-api@0.6.0 + - @backstage/plugin-auth-node@0.2.17 + - @backstage/config@1.0.8 + - @backstage/errors@1.2.1 + - @backstage/plugin-permission-common@0.7.7 + +## 0.7.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.0-next.2 + - @backstage/backend-common@0.19.2-next.2 + - @backstage/plugin-auth-node@0.2.17-next.2 + +## 0.7.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.2-next.1 + - @backstage/plugin-auth-node@0.2.17-next.1 + - @backstage/backend-plugin-api@0.6.0-next.1 + - @backstage/config@1.0.8 + - @backstage/errors@1.2.1 + - @backstage/plugin-permission-common@0.7.7 + +## 0.7.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.2-next.0 + - @backstage/backend-plugin-api@0.5.5-next.0 + - @backstage/config@1.0.8 + - @backstage/errors@1.2.1 + - @backstage/plugin-auth-node@0.2.17-next.0 + - @backstage/plugin-permission-common@0.7.7 + +## 0.7.10 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.1 + - @backstage/backend-common@0.19.1 + - @backstage/backend-plugin-api@0.5.4 + - @backstage/config@1.0.8 + - @backstage/plugin-auth-node@0.2.16 + - @backstage/plugin-permission-common@0.7.7 + +## 0.7.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.1-next.0 + - @backstage/backend-common@0.19.1-next.0 + - @backstage/backend-plugin-api@0.5.4-next.0 + - @backstage/config@1.0.8 + - @backstage/plugin-auth-node@0.2.16-next.0 + - @backstage/plugin-permission-common@0.7.7-next.0 + +## 0.7.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.0 + - @backstage/errors@1.2.0 + - @backstage/backend-plugin-api@0.5.3 + - @backstage/plugin-auth-node@0.2.15 + - @backstage/config@1.0.8 + - @backstage/plugin-permission-common@0.7.6 + +## 0.7.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.0-next.2 + - @backstage/backend-plugin-api@0.5.3-next.2 + - @backstage/config@1.0.7 + - @backstage/errors@1.2.0-next.0 + - @backstage/plugin-auth-node@0.2.15-next.2 + - @backstage/plugin-permission-common@0.7.6-next.0 + +## 0.7.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.0-next.1 + - @backstage/errors@1.2.0-next.0 + - @backstage/backend-plugin-api@0.5.3-next.1 + - @backstage/plugin-auth-node@0.2.15-next.1 + - @backstage/plugin-permission-common@0.7.6-next.0 + - @backstage/config@1.0.7 + +## 0.7.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.6-next.0 + - @backstage/config@1.0.7 + - @backstage/backend-plugin-api@0.5.3-next.0 + - @backstage/errors@1.1.5 + - @backstage/plugin-auth-node@0.2.15-next.0 + - @backstage/plugin-permission-common@0.7.5 + +## 0.7.8 + +### Patch Changes + +- a788e715cfc: `createPermissionIntegrationRouter` now accepts rules and permissions for multiple resource types. Example: + + ```typescript + createPermissionIntegrationRouter({ + resources: [ + { + resourceType: 'resourceType-1', + permissions: permissionsResourceType1, + rules: rulesResourceType1, + }, + { + resourceType: 'resourceType-2', + permissions: permissionsResourceType2, + rules: rulesResourceType2, + }, + ], + }); + ``` + +- Updated dependencies + - @backstage/backend-common@0.18.5 + - @backstage/plugin-auth-node@0.2.14 + - @backstage/backend-plugin-api@0.5.2 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + - @backstage/plugin-permission-common@0.7.5 + +## 0.7.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.5-next.1 + - @backstage/plugin-auth-node@0.2.14-next.1 + - @backstage/backend-plugin-api@0.5.2-next.1 + - @backstage/config@1.0.7 + +## 0.7.8-next.0 + +### Patch Changes + +- a788e715cfc: `createPermissionIntegrationRouter` now accepts rules and permissions for multiple resource types. Example: + + ```typescript + createPermissionIntegrationRouter({ + resources: [ + { + resourceType: 'resourceType-1', + permissions: permissionsResourceType1, + rules: rulesResourceType1, + }, + { + resourceType: 'resourceType-2', + permissions: permissionsResourceType2, + rules: rulesResourceType2, + }, + ], + }); + ``` + +- Updated dependencies + - @backstage/backend-common@0.18.5-next.0 + - @backstage/plugin-auth-node@0.2.14-next.0 + - @backstage/backend-plugin-api@0.5.2-next.0 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + - @backstage/plugin-permission-common@0.7.5 + +## 0.7.7 + +### Patch Changes + +- 788f0f5a152: Introduced alpha export of the `policyExtensionPoint` for use in the new backend system. +- 71fd0966d10: Added createConditionAuthorizer utility function, which takes some permission conditions and returns a function that returns a definitive authorization result given a decision and a resource. +- 1e4f5e91b8e: Bump `zod` and `zod-to-json-schema` dependencies. +- Updated dependencies + - @backstage/backend-common@0.18.4 + - @backstage/plugin-permission-common@0.7.5 + - @backstage/plugin-auth-node@0.2.13 + - @backstage/backend-plugin-api@0.5.1 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + +## 0.7.7-next.2 + +### Patch Changes + +- 788f0f5a152: Introduced alpha export of the `policyExtensionPoint` for use in the new backend system. +- Updated dependencies + - @backstage/backend-common@0.18.4-next.2 + - @backstage/backend-plugin-api@0.5.1-next.2 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + - @backstage/plugin-auth-node@0.2.13-next.2 + - @backstage/plugin-permission-common@0.7.5-next.0 + +## 0.7.7-next.1 + +### Patch Changes + +- 71fd0966d10: Added createConditionAuthorizer utility function, which takes some permission conditions and returns a function that returns a definitive authorization result given a decision and a resource. +- 1e4f5e91b8e: Bump `zod` and `zod-to-json-schema` dependencies. +- Updated dependencies + - @backstage/plugin-permission-common@0.7.5-next.0 + - @backstage/backend-common@0.18.4-next.1 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + - @backstage/plugin-auth-node@0.2.13-next.1 + +## 0.7.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.4-next.0 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + - @backstage/plugin-auth-node@0.2.13-next.0 + - @backstage/plugin-permission-common@0.7.4 + +## 0.7.6 + +### Patch Changes + +- 27a103ca07b: Changed the `createPermissionIntegrationRouter` API to allow `getResources`, `resourceType` and `rules` to be optional +- 37e9215c793: Update the service-to-service auth docs URL in error message. + \`\` +- 52b0022dab7: Updated dependency `msw` to `^1.0.0`. +- Updated dependencies + - @backstage/plugin-auth-node@0.2.12 + - @backstage/backend-common@0.18.3 + - @backstage/errors@1.1.5 + - @backstage/plugin-permission-common@0.7.4 + - @backstage/config@1.0.7 + +## 0.7.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.2.12-next.2 + - @backstage/backend-common@0.18.3-next.2 + - @backstage/config@1.0.7-next.0 + +## 0.7.6-next.1 + +### Patch Changes + +- 27a103ca07b: Changed the `createPermissionIntegrationRouter` API to allow `getResources`, `resourceType` and `rules` to be optional +- 37e9215c793: Update the service-to-service auth docs URL in error message. + \`\` +- 52b0022dab7: Updated dependency `msw` to `^1.0.0`. +- Updated dependencies + - @backstage/errors@1.1.5-next.0 + - @backstage/backend-common@0.18.3-next.1 + - @backstage/plugin-auth-node@0.2.12-next.1 + - @backstage/plugin-permission-common@0.7.4-next.0 + - @backstage/config@1.0.7-next.0 + +## 0.7.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.3-next.0 + - @backstage/config@1.0.6 + - @backstage/errors@1.1.4 + - @backstage/plugin-auth-node@0.2.12-next.0 + - @backstage/plugin-permission-common@0.7.3 + +## 0.7.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.2 + - @backstage/config@1.0.6 + - @backstage/errors@1.1.4 + - @backstage/plugin-auth-node@0.2.11 + - @backstage/plugin-permission-common@0.7.3 + +## 0.7.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.2-next.2 + - @backstage/plugin-auth-node@0.2.11-next.2 + - @backstage/config@1.0.6 + - @backstage/errors@1.1.4 + - @backstage/plugin-permission-common@0.7.3 + +## 0.7.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.2-next.1 + - @backstage/config@1.0.6 + - @backstage/errors@1.1.4 + - @backstage/plugin-auth-node@0.2.11-next.1 + - @backstage/plugin-permission-common@0.7.3 + +## 0.7.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.2-next.0 + - @backstage/plugin-auth-node@0.2.11-next.0 + +## 0.7.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.0 + - @backstage/config@1.0.6 + - @backstage/errors@1.1.4 + - @backstage/plugin-auth-node@0.2.9 + - @backstage/plugin-permission-common@0.7.3 + +## 0.7.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.0-next.1 + - @backstage/plugin-auth-node@0.2.9-next.1 + - @backstage/config@1.0.6-next.0 + - @backstage/errors@1.1.4 + - @backstage/plugin-permission-common@0.7.3-next.0 + +## 0.7.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.0-next.0 + - @backstage/config@1.0.6-next.0 + - @backstage/errors@1.1.4 + - @backstage/plugin-auth-node@0.2.9-next.0 + - @backstage/plugin-permission-common@0.7.3-next.0 + +## 0.7.2 + +### Patch Changes + +- b05dcd5530: Move the `zod` dependency to a version that does not collide with other libraries +- 3280711113: Updated dependency `msw` to `^0.49.0`. +- Updated dependencies + - @backstage/backend-common@0.17.0 + - @backstage/plugin-permission-common@0.7.2 + - @backstage/errors@1.1.4 + - @backstage/plugin-auth-node@0.2.8 + - @backstage/config@1.0.5 + +## 0.7.2-next.3 + +### Patch Changes + +- b05dcd5530: Move the `zod` dependency to a version that does not collide with other libraries +- Updated dependencies + - @backstage/plugin-permission-common@0.7.2-next.2 + - @backstage/backend-common@0.17.0-next.3 + - @backstage/config@1.0.5-next.1 + - @backstage/errors@1.1.4-next.1 + - @backstage/plugin-auth-node@0.2.8-next.3 + +## 0.7.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.17.0-next.2 + - @backstage/plugin-auth-node@0.2.8-next.2 + - @backstage/config@1.0.5-next.1 + - @backstage/errors@1.1.4-next.1 + - @backstage/plugin-permission-common@0.7.2-next.1 + +## 0.7.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.17.0-next.1 + - @backstage/plugin-auth-node@0.2.8-next.1 + - @backstage/config@1.0.5-next.1 + - @backstage/errors@1.1.4-next.1 + - @backstage/plugin-permission-common@0.7.2-next.1 + +## 0.7.2-next.0 + +### Patch Changes + +- 3280711113: Updated dependency `msw` to `^0.49.0`. +- Updated dependencies + - @backstage/backend-common@0.16.1-next.0 + - @backstage/plugin-auth-node@0.2.8-next.0 + - @backstage/plugin-permission-common@0.7.2-next.0 + - @backstage/config@1.0.5-next.0 + - @backstage/errors@1.1.4-next.0 + +## 0.7.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0 + - @backstage/plugin-auth-node@0.2.7 + - @backstage/plugin-permission-common@0.7.1 + - @backstage/config@1.0.4 + - @backstage/errors@1.1.3 + +## 0.7.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.1 + - @backstage/plugin-auth-node@0.2.7-next.1 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + +## 0.7.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.7.0 + +### Minor Changes + +- 46b4a72cee: **BREAKING**: When defining permission rules, it's now necessary to provide a [ZodSchema](https://github.com/colinhacks/zod) that specifies the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. + + To help with this, we have also made a change to the API of permission rules. Before, the permission rules `toQuery` and `apply` signature expected parameters to be separate arguments, like so... + + ```ts + createPermissionRule({ + apply: (resource, foo, bar) => true, + toQuery: (foo, bar) => {}, + }); + ``` + + The API has now changed to expect the parameters as a single object + + ```ts + createPermissionRule({ + paramSchema: z.object({ + foo: z.string().describe('Foo value to match'), + bar: z.string().describe('Bar value to match'), + }), + apply: (resource, { foo, bar }) => true, + toQuery: ({ foo, bar }) => {}, + }); + ``` + + One final change made is to limit the possible values for a parameter to primitives and arrays of primitives. + +### Patch Changes + +- 9335ad115e: Exported types for the .metadata endpoint of the permission router +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## 0.7.0-next.2 + +### Minor Changes + +- 46b4a72cee: **BREAKING**: When defining permission rules, it's now necessary to provide a [ZodSchema](https://github.com/colinhacks/zod) that specifies the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. + + To help with this, we have also made a change to the API of permission rules. Before, the permission rules `toQuery` and `apply` signature expected parameters to be separate arguments, like so... + + ```ts + createPermissionRule({ + apply: (resource, foo, bar) => true, + toQuery: (foo, bar) => {}, + }); + ``` + + The API has now changed to expect the parameters as a single object + + ```ts + createPermissionRule({ + paramSchema: z.object({ + foo: z.string().describe('Foo value to match'), + bar: z.string().describe('Bar value to match'), + }), + apply: (resource, { foo, bar }) => true, + toQuery: ({ foo, bar }) => {}, + }); + ``` + + One final change made is to limit the possible values for a parameter to primitives and arrays of primitives. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## 0.6.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/plugin-auth-node@0.2.6-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + +## 0.6.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/plugin-auth-node@0.2.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + +## 0.6.5 + +### Patch Changes + +- 9212439d15: Minor update to tests +- 667d917488: Updated dependency `msw` to `^0.47.0`. +- 87ec2ba4d6: Updated dependency `msw` to `^0.46.0`. +- bf5e9030eb: Updated dependency `msw` to `^0.45.0`. +- Updated dependencies + - @backstage/backend-common@0.15.1 + - @backstage/plugin-auth-node@0.2.5 + - @backstage/config@1.0.2 + - @backstage/errors@1.1.1 + - @backstage/plugin-permission-common@0.6.4 + +## 0.6.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.2-next.0 + - @backstage/errors@1.1.1-next.0 + - @backstage/plugin-permission-common@0.6.4-next.2 + - @backstage/backend-common@0.15.1-next.3 + - @backstage/plugin-auth-node@0.2.5-next.3 + +## 0.6.5-next.2 + +### Patch Changes + +- 667d917488: Updated dependency `msw` to `^0.47.0`. +- 87ec2ba4d6: Updated dependency `msw` to `^0.46.0`. +- Updated dependencies + - @backstage/backend-common@0.15.1-next.2 + - @backstage/plugin-auth-node@0.2.5-next.2 + - @backstage/plugin-permission-common@0.6.4-next.1 + +## 0.6.5-next.1 + +### Patch Changes + +- 9212439d15: Minor update to tests +- Updated dependencies + - @backstage/plugin-auth-node@0.2.5-next.1 + - @backstage/backend-common@0.15.1-next.1 + +## 0.6.5-next.0 + +### Patch Changes + +- bf5e9030eb: Updated dependency `msw` to `^0.45.0`. +- Updated dependencies + - @backstage/backend-common@0.15.1-next.0 + - @backstage/plugin-auth-node@0.2.5-next.0 + - @backstage/plugin-permission-common@0.6.4-next.0 + +## 0.6.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.0 + - @backstage/plugin-auth-node@0.2.4 + +## 0.6.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.0-next.0 + - @backstage/plugin-auth-node@0.2.4-next.0 + +## 0.6.3 + +### Patch Changes + +- a70869e775: Updated dependency `msw` to `^0.43.0`. +- 8006d0f9bf: Updated dependency `msw` to `^0.44.0`. +- Updated dependencies + - @backstage/backend-common@0.14.1 + - @backstage/plugin-auth-node@0.2.3 + - @backstage/plugin-permission-common@0.6.3 + - @backstage/errors@1.1.0 + +## 0.6.3-next.2 + +### Patch Changes + +- a70869e775: Updated dependency `msw` to `^0.43.0`. +- Updated dependencies + - @backstage/backend-common@0.14.1-next.3 + - @backstage/plugin-auth-node@0.2.3-next.2 + - @backstage/plugin-permission-common@0.6.3-next.1 + +## 0.6.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.14.1-next.1 + - @backstage/errors@1.1.0-next.0 + - @backstage/plugin-auth-node@0.2.3-next.1 + - @backstage/plugin-permission-common@0.6.3-next.0 + +## 0.6.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.14.1-next.0 + - @backstage/plugin-auth-node@0.2.3-next.0 + +## 0.6.2 + +### Patch Changes + +- 58426f9c0f: Added a new endpoint for aggregating permission metadata from a plugin backend: `/.well-known/backstage/permissions/metadata` + + By default, the metadata endpoint will return information about the permission rules supported by the plugin. Plugin authors can also provide an optional `permissions` parameter to `createPermissionIntegrationRouter`. If provided, these `Permission` objects will be included in the metadata returned by this endpoint. The `permissions` parameter will eventually be required in a future breaking change. + +- 8f7b1835df: Updated dependency `msw` to `^0.41.0`. + +- Updated dependencies + - @backstage/backend-common@0.14.0 + - @backstage/plugin-auth-node@0.2.2 + - @backstage/plugin-permission-common@0.6.2 + +## 0.6.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.14.0-next.2 + - @backstage/plugin-auth-node@0.2.2-next.2 + +## 0.6.2-next.1 + +### Patch Changes + +- 8f7b1835df: Updated dependency `msw` to `^0.41.0`. +- Updated dependencies + - @backstage/backend-common@0.13.6-next.1 + - @backstage/plugin-auth-node@0.2.2-next.1 + - @backstage/plugin-permission-common@0.6.2-next.0 + +## 0.6.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/plugin-auth-node@0.2.2-next.0 + +## 0.6.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.3 + - @backstage/config@1.0.1 + - @backstage/plugin-auth-node@0.2.1 + - @backstage/plugin-permission-common@0.6.1 + +## 0.6.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.3-next.2 + - @backstage/config@1.0.1-next.0 + - @backstage/plugin-auth-node@0.2.1-next.1 + - @backstage/plugin-permission-common@0.6.1-next.0 + +## 0.6.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.3-next.0 + - @backstage/plugin-auth-node@0.2.1-next.0 + +## 0.6.0 + +### Minor Changes + +- 8012ac46a0: **BREAKING**: Stronger typing in `PermissionPolicy` 🎉. + + Previously, it was entirely the responsibility of the `PermissionPolicy` author to only return `CONDITIONAL` decisions for permissions that are associated with a resource, and to return the correct kind of `PermissionCondition` instances inside the decision. Now, the policy authoring helpers provided in this package now ensure that the decision and permission match. + + **For policy authors**: rename and adjust api of `createConditionExports`. Previously, the function returned a factory for creating conditional decisions named `createPolicyDecision`, which had a couple of drawbacks: + + 1. The function always creates a _conditional_ policy decision, but this was not reflected in the name. + 2. Conditional decisions should only ever be returned from `PermissionPolicy#handle` for resource permissions, but there was nothing in the API that encoded this constraint. + + This change addresses the drawbacks above by making the following changes for policy authors: + + - The `createPolicyDecision` method has been renamed to `createConditionalDecision`. + - Along with conditions, the method now accepts a permission, which must be a `ResourcePermission`. This is expected to be the handled permission in `PermissionPolicy#handle`, whose type must first be narrowed using methods like `isPermission` and `isResourcePermission`: + + ```typescript + class TestPermissionPolicy implements PermissionPolicy { + async handle( + request: PolicyQuery, + _user?: BackstageIdentityResponse, + ): Promise { + if ( + // Narrow type of `request.permission` to `ResourcePermission<'catalog-entity'> + isResourcePermission(request.permission, RESOURCE_TYPE_CATALOG_ENTITY) + ) { + return createCatalogConditionalDecision( + request.permission, + catalogConditions.isEntityOwner( + _user?.identity.ownershipEntityRefs ?? [], + ), + ); + } + + return { + result: AuthorizeResult.ALLOW, + }; + ``` + + **BREAKING**: when creating `PermissionRule`s, provide a `resourceType`. + + ```diff + export const isEntityOwner = createCatalogPermissionRule({ + name: 'IS_ENTITY_OWNER', + description: 'Allow entities owned by the current user', + + resourceType: RESOURCE_TYPE_CATALOG_ENTITY, + apply: (resource: Entity, claims: string[]) => { + if (!resource.relations) { + return false; + } + + return resource.relations + .filter(relation => relation.type === RELATION_OWNED_BY) + .some(relation => claims.includes(relation.targetRef)); + }, + toQuery: (claims: string[]) => ({ + key: 'relations.ownedBy', + values: claims, + }), + }); + ``` + +- c98d271466: **BREAKING:** + + - Rename `PolicyAuthorizeQuery` to `PolicyQuery` + - Remove `PolicyDecision`, `DefinitivePolicyDecision`, and `ConditionalPolicyDecision`. These types are now exported from `@backstage/plugin-permission-common` + +- 322b69e46a: **BREAKING:** `ServerPermissionClient` now implements `PermissionEvaluator`, which moves out the capabilities for evaluating conditional decisions from `authorize()` to `authorizeConditional()` method. + +### Patch Changes + +- 90754d4fa9: Removed [strict](https://github.com/colinhacks/zod#strict) validation from `PermissionCriteria` schemas to support backward-compatible changes. +- 8012ac46a0: Fix signature of permission rule in test suites +- Updated dependencies + - @backstage/plugin-permission-common@0.6.0 + - @backstage/plugin-auth-node@0.2.0 + - @backstage/backend-common@0.13.2 + +## 0.6.0-next.2 + +### Minor Changes + +- 322b69e46a: **BREAKING:** `ServerPermissionClient` now implements `PermissionEvaluator`, which moves out the capabilities for evaluating conditional decisions from `authorize()` to `authorizeConditional()` method. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.6.0-next.1 + - @backstage/backend-common@0.13.2-next.2 + +## 0.6.0-next.1 + +### Minor Changes + +- 8012ac46a0: **BREAKING**: Stronger typing in `PermissionPolicy` 🎉. + + Previously, it was entirely the responsibility of the `PermissionPolicy` author to only return `CONDITIONAL` decisions for permissions that are associated with a resource, and to return the correct kind of `PermissionCondition` instances inside the decision. Now, the policy authoring helpers provided in this package now ensure that the decision and permission match. + + **For policy authors**: rename and adjust api of `createConditionExports`. Previously, the function returned a factory for creating conditional decisions named `createPolicyDecision`, which had a couple of drawbacks: + + 1. The function always creates a _conditional_ policy decision, but this was not reflected in the name. + 2. Conditional decisions should only ever be returned from `PermissionPolicy#handle` for resource permissions, but there was nothing in the API that encoded this constraint. + + This change addresses the drawbacks above by making the following changes for policy authors: + + - The `createPolicyDecision` method has been renamed to `createConditionalDecision`. + - Along with conditions, the method now accepts a permission, which must be a `ResourcePermission`. This is expected to be the handled permission in `PermissionPolicy#handle`, whose type must first be narrowed using methods like `isPermission` and `isResourcePermission`: + + ```typescript + class TestPermissionPolicy implements PermissionPolicy { + async handle( + request: PolicyQuery, + _user?: BackstageIdentityResponse, + ): Promise { + if ( + // Narrow type of `request.permission` to `ResourcePermission<'catalog-entity'> + isResourcePermission(request.permission, RESOURCE_TYPE_CATALOG_ENTITY) + ) { + return createCatalogConditionalDecision( + request.permission, + catalogConditions.isEntityOwner( + _user?.identity.ownershipEntityRefs ?? [], + ), + ); + } + + return { + result: AuthorizeResult.ALLOW, + }; + ``` + + **BREAKING**: when creating `PermissionRule`s, provide a `resourceType`. + + ```diff + export const isEntityOwner = createCatalogPermissionRule({ + name: 'IS_ENTITY_OWNER', + description: 'Allow entities owned by the current user', + + resourceType: RESOURCE_TYPE_CATALOG_ENTITY, + apply: (resource: Entity, claims: string[]) => { + if (!resource.relations) { + return false; + } + + return resource.relations + .filter(relation => relation.type === RELATION_OWNED_BY) + .some(relation => claims.includes(relation.targetRef)); + }, + toQuery: (claims: string[]) => ({ + key: 'relations.ownedBy', + values: claims, + }), + }); + ``` + +- c98d271466: **BREAKING:** + + - Rename `PolicyAuthorizeQuery` to `PolicyQuery` + - Remove `PolicyDecision`, `DefinitivePolicyDecision`, and `ConditionalPolicyDecision`. These types are now exported from `@backstage/plugin-permission-common` + +### Patch Changes + +- 8012ac46a0: Fix signature of permission rule in test suites +- Updated dependencies + - @backstage/plugin-permission-common@0.6.0-next.0 + - @backstage/backend-common@0.13.2-next.1 + +## 0.5.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.2.0-next.0 + - @backstage/backend-common@0.13.2-next.0 + +## 0.5.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.1 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/plugin-permission-common@0.5.3 + - @backstage/plugin-auth-node@0.1.6 + +## 0.5.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.0 + - @backstage/plugin-auth-node@0.1.5 + +## 0.5.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.0-next.0 + - @backstage/plugin-auth-node@0.1.5-next.0 + +## 0.5.3 + +### Patch Changes + +- 580f4e1df8: Export some utility functions for parsing PermissionCriteria + + `isAndCriteria`, `isOrCriteria`, `isNotCriteria` are now exported. + +- Updated dependencies + - @backstage/backend-common@0.12.0 + - @backstage/plugin-permission-common@0.5.2 + - @backstage/plugin-auth-node@0.1.4 + +## 0.5.2 + +### Patch Changes + +- 0816f8237a: Improved error message shown when permissions are enabled without backend-to-backend authentication. +- Updated dependencies + - @backstage/backend-common@0.11.0 + - @backstage/plugin-auth-node@0.1.3 + +## 0.5.1 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/config@0.1.15 + - @backstage/errors@0.2.2 + - @backstage/plugin-auth-node@0.1.2 + - @backstage/plugin-permission-common@0.5.1 + +## 0.5.0 + +### Minor Changes + +- 8c646beb24: **BREAKING** `PermissionCriteria` now requires at least one condition in `anyOf` and `allOf` arrays. This addresses some ambiguous behavior outlined in #9280. + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/errors@0.2.1 + - @backstage/plugin-auth-node@0.1.1 + - @backstage/plugin-permission-common@0.5.0 + - @backstage/config@0.1.14 + +## 0.4.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.7 + - @backstage/plugin-auth-node@0.1.0 + +## 0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.10.0-next.0 + - @backstage/backend-common@0.10.7-next.0 + +## 0.4.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.9.0 + - @backstage/backend-common@0.10.6 + +## 0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.9.0-next.1 + - @backstage/backend-common@0.10.6-next.0 + +## 0.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.9.0-next.0 + +## 0.4.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.8.0 + - @backstage/backend-common@0.10.5 + +## 0.4.0 + +### Minor Changes + +- 0ae4f4cc82: **BREAKING**: `PolicyAuthorizeRequest` type has been renamed to `PolicyAuthorizeQuery`. + **BREAKING**: Update to use renamed request and response types from @backstage/plugin-permission-common. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.7.0 + - @backstage/plugin-permission-common@0.4.0 + - @backstage/backend-common@0.10.4 + - @backstage/config@0.1.13 + +## 0.4.0-next.0 + +### Minor Changes + +- 0ae4f4cc82: **BREAKING**: `PolicyAuthorizeRequest` type has been renamed to `PolicyAuthorizeQuery`. + **BREAKING**: Update to use renamed request and response types from @backstage/plugin-permission-common. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.7.0-next.0 + - @backstage/plugin-permission-common@0.4.0-next.0 + - @backstage/backend-common@0.10.4-next.0 + - @backstage/config@0.1.13-next.0 + +## 0.3.0 + +### Minor Changes + +- 419ca637c0: Optimizations to the integration between the permission backend and plugin-backends using createPermissionIntegrationRouter: + + - The permission backend already supported batched requests to authorize, but would make calls to plugin backend to apply conditions serially. Now, after applying the policy for each authorization request, the permission backend makes a single batched /apply-conditions request to each plugin backend referenced in policy decisions. + - The `getResource` method accepted by `createPermissionIntegrationRouter` has been replaced with `getResources`, to allow consumers to make batch requests to upstream data stores. When /apply-conditions is called with a batch of requests, all required resources are requested in a single invocation of `getResources`. + + Plugin owners consuming `createPermissionIntegrationRouter` should replace the `getResource` method in the options with a `getResources` method, accepting an array of resourceRefs, and returning an array of the corresponding resources. + +### Patch Changes + +- 9db1b86f32: Add helpers for creating PermissionRules with inferred types +- Updated dependencies + - @backstage/config@0.1.12 + - @backstage/backend-common@0.10.3 + - @backstage/plugin-auth-backend@0.6.2 + - @backstage/errors@0.2.0 + - @backstage/plugin-permission-common@0.3.1 + +## 0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.6.0 + - @backstage/backend-common@0.10.1 + +## 0.2.2 + +### Patch Changes + +- 2f8a9b665f: Add `ServerPermissionClient`, which implements `PermissionAuthorizer` from @backstage/plugin-permission-common. This implementation skips authorization entirely when the supplied token is a valid backend-to-backend token, thereby allowing backend-to-backend systems to communicate without authorization. + + The `ServerPermissionClient` should always be used over the standard `PermissionClient` in plugin backends. + +- Updated dependencies + - @backstage/backend-common@0.10.0 + - @backstage/plugin-auth-backend@0.5.2 + - @backstage/plugin-permission-common@0.3.0 + +## 0.2.1 + +### Patch Changes + +- dcd1a0c3f4: Minor improvement to the API reports, by not unpacking arguments directly + +- a036b65c2f: Updated to use the new `BackstageIdentityResponse` type from `@backstage/plugin-auth-backend`. + + The `BackstageIdentityResponse` type is backwards compatible with the `BackstageIdentity`, and provides an additional `identity` field with the claims of the user. + +- Updated dependencies + - @backstage/plugin-auth-backend@0.5.0 + +## 0.2.0 + +### Minor Changes + +- e7851efa9e: Rename and adjust permission policy return type to reduce nesting +- 450ca92330: Change route used for integration between the authorization framework and other plugin backends to use the /.well-known prefix. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.4.10 + +## 0.1.0 + +### Minor Changes + +- 44b46644d9: New package containing common permission and authorization utilities for backend plugins. For more information, see the [authorization PRFC](https://github.com/backstage/backstage/pull/7761). + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.4.9 + - @backstage/plugin-permission-common@0.2.0 + +## @backstage/plugin-search@1.6.0-next.1 + +### Minor Changes + +- feef8d9: Added support for configuring the default search type in the search page via the `search.defaultType` option in `app-config.yaml`. This applies to both the legacy and new frontend systems. If not set, the default is empty, which means searching for "all" types. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/plugin-search-react@1.10.3-next.1 + - @backstage/core-components@0.18.7-next.1 + +## @backstage/plugin-search-backend-module-elasticsearch@1.8.0-next.1 + +### Minor Changes + +- 583bd3a: Added `elasticsearchAuthExtensionPoint` to enable dynamic authentication mechanisms such as bearer tokens with automatic rotation. + +### Patch Changes + +- 7021165: Fixed bulk indexing to refresh only the target index instead of all indexes, improving performance in multi-index deployments. +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/app-defaults@1.7.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.7.2-next.0 + - @backstage/core-components@0.18.7-next.1 + +## @backstage/backend-defaults@0.15.2-next.1 + +### Patch Changes + +- 8dd518a: Support `connection.type: azure` in database client to use Microsoft Entra authentication with Azure database for PostgreSQL +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/cli-node@0.2.18-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/backend-dynamic-feature-service@0.7.9-next.1 + +### Patch Changes + +- 9b4c414: Updated README for backend-dynamic-feature-service +- Updated dependencies + - @backstage/cli-node@0.2.18-next.1 + - @backstage/plugin-catalog-backend@3.4.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/backend-defaults@0.15.2-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## @backstage/cli@0.35.4-next.1 + +### Patch Changes + +- 5e3ef57: Added support for the new `peerModules` metadata field in `package.json`. This field allows plugin packages to declare modules that should be installed alongside them for cross-plugin integrations. The field is validated by `backstage-cli repo fix --publish`. +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/cli-node@0.2.18-next.1 + +## @backstage/cli-node@0.2.18-next.1 + +### Patch Changes + +- 5e3ef57: Added support for the new `peerModules` metadata field in `package.json`. This field allows plugin packages to declare modules that should be installed alongside them for cross-plugin integrations. The field is validated by `backstage-cli repo fix --publish`. + +## @backstage/core-compat-api@0.5.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.1 + +## @backstage/core-components@0.18.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.7.2-next.0 + +## @backstage/create-app@0.7.9-next.1 + +### Patch Changes + +- Bumped create-app version. + +## @backstage/dev-utils@1.1.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/ui@0.12.0-next.1 + - @backstage/theme@0.7.2-next.0 + - @backstage/app-defaults@1.7.5-next.1 + - @backstage/core-components@0.18.7-next.1 + - @backstage/integration-react@1.2.15-next.1 + +## @backstage/frontend-test-utils@0.4.6-next.1 + +### Patch Changes + +- 22864b7: Added an `apis` option to `createExtensionTester`, `renderInTestApp`, and `renderTestApp` to override APIs when testing extensions. Use the `mockApis` helpers to create mock implementations: + + ```typescript + import { identityApiRef } from '@backstage/frontend-plugin-api'; + import { mockApis } from '@backstage/frontend-test-utils'; + + // Override APIs in createExtensionTester + const tester = createExtensionTester(myExtension, { + apis: [ + [ + identityApiRef, + mockApis.identity({ userEntityRef: 'user:default/guest' }), + ], + ], + }); + + // Override APIs in renderInTestApp + renderInTestApp(, { + apis: [ + [ + identityApiRef, + mockApis.identity({ userEntityRef: 'user:default/guest' }), + ], + ], + }); + + // Override APIs in renderTestApp + renderTestApp({ + extensions: [myExtension], + apis: [ + [ + identityApiRef, + mockApis.identity({ userEntityRef: 'user:default/guest' }), + ], + ], + }); + ``` + +- Updated dependencies + - @backstage/frontend-app-api@0.15.0-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/plugin-app@0.4.0-next.1 + - @backstage/test-utils@1.7.15-next.1 + +## @backstage/integration-react@1.2.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + +## @backstage/repo-tools@0.16.4-next.1 + +### Patch Changes + +- 6523040: Support Prettier v3 for api-reports +- Updated dependencies + - @backstage/cli-node@0.2.18-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/test-utils@1.7.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.7.2-next.0 + +## @backstage/theme@0.7.2-next.0 + +### Patch Changes + +- 1c52dcc: add square shape + +## @backstage/plugin-api-docs@0.13.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/plugin-catalog@1.33.0-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/core-components@0.18.7-next.1 + +## @backstage/plugin-app@0.4.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.7.2-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/core-components@0.18.7-next.1 + - @backstage/integration-react@1.2.15-next.1 + +## @backstage/plugin-auth@0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.7.2-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/core-components@0.18.7-next.1 + +## @backstage/plugin-bitbucket-cloud-common@0.3.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + +## @backstage/plugin-catalog-backend@3.4.0-next.1 + +### Patch Changes + +- 5e3ef57: Added `peerModules` metadata declaring recommended modules for cross-plugin integrations. +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/plugin-catalog-backend-module-aws@0.4.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.9.10-next.1 + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/backend-defaults@0.15.2-next.1 + +## @backstage/plugin-catalog-backend-module-azure@0.3.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-bitbucket-cloud-common@0.3.7-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/plugin-catalog-backend-module-gerrit@0.3.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/plugin-catalog-backend-module-gitea@0.1.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/plugin-catalog-backend-module-github@0.12.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/plugin-catalog-graph@0.5.7-next.1 + +### Patch Changes + +- 8dd27c4: Fix large icon rendering in catalog graph nodes +- Updated dependencies + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/core-components@0.18.7-next.1 + +## @backstage/plugin-catalog-import@0.13.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/integration@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/core-components@0.18.7-next.1 + - @backstage/integration-react@1.2.15-next.1 + +## @backstage/plugin-events-backend-module-github@0.4.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/plugin-home@0.9.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/theme@0.7.2-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/core-components@0.18.7-next.1 + +## @backstage/plugin-kubernetes@0.12.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/plugin-kubernetes-common@0.9.10-next.1 + - @backstage/plugin-kubernetes-react@0.5.16-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/core-components@0.18.7-next.1 + +## @backstage/plugin-kubernetes-backend@0.21.1-next.1 + +### Patch Changes + +- ce3639c: Add PersistentVolume and PersistentVolumeClaims Rendering +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.9.10-next.1 + - @backstage/plugin-kubernetes-node@0.4.1-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/plugin-kubernetes-cluster@0.0.34-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/plugin-kubernetes-common@0.9.10-next.1 + - @backstage/plugin-kubernetes-react@0.5.16-next.1 + - @backstage/core-components@0.18.7-next.1 + +## @backstage/plugin-kubernetes-common@0.9.10-next.1 + +### Patch Changes + +- ce3639c: Add PersistentVolume and PersistentVolumeClaims Rendering + +## @backstage/plugin-kubernetes-node@0.4.1-next.1 + +### Patch Changes + +- ce3639c: Add PersistentVolume and PersistentVolumeClaims Rendering +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.9.10-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/plugin-kubernetes-react@0.5.16-next.1 + +### Patch Changes + +- ce3639c: Add PersistentVolume and PersistentVolumeClaims Rendering +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.9.10-next.1 + - @backstage/core-components@0.18.7-next.1 + +## @backstage/plugin-mui-to-bui@0.2.4-next.1 + +### Patch Changes + +- a88c437: Updated MUI to BUI theme converter to align with latest token changes + + **Changes:** + + - Removed generation of deprecated tokens: `--bui-fg-link`, `--bui-fg-link-hover`, `--bui-fg-tint`, `--bui-fg-tint-disabled`, `--bui-bg-tint` and all its variants + - Added generation of new `info` status tokens: `--bui-fg-info`, `--bui-fg-info-on-bg`, `--bui-bg-info`, `--bui-border-info` + - Updated status color mapping to generate both standalone and `-on-bg` variants for danger, warning, success, and info + - Status colors now use `.main` for standalone variants and `.dark` for `-on-bg` variants, providing better visual hierarchy + + The converter now generates tokens that match the updated BUI design system structure, with clear distinction between status colors for standalone use vs. use on colored backgrounds. + +- Updated dependencies + - @backstage/ui@0.12.0-next.1 + - @backstage/theme@0.7.2-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.1 + +## @backstage/plugin-notifications@0.5.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.7.2-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/core-components@0.18.7-next.1 + +## @backstage/plugin-notifications-backend@0.6.2-next.1 + +### Patch Changes + +- 5e3ef57: Added `peerModules` metadata declaring recommended modules for cross-plugin integrations. +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/plugin-org@0.6.49-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/core-components@0.18.7-next.1 + +## @backstage/plugin-org-react@0.1.47-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/core-components@0.18.7-next.1 + +## @backstage/plugin-scaffolder@1.35.3-next.1 + +### Patch Changes + +- 4e581a6: Updated the browser tab title on the template wizard page to display the specific template title instead of the generic "Create a new component" text. +- Updated dependencies + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/integration@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/plugin-scaffolder-react@1.19.7-next.1 + - @backstage/core-components@0.18.7-next.1 + - @backstage/plugin-techdocs-react@1.3.8-next.0 + - @backstage/integration-react@1.2.15-next.1 + - @backstage/plugin-scaffolder-common@1.7.6-next.1 + +## @backstage/plugin-scaffolder-backend@3.1.3-next.1 + +### Patch Changes + +- 5e3ef57: Added `peerModules` metadata declaring recommended modules for cross-plugin integrations. +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.11.3-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/backend-defaults@0.15.2-next.1 + - @backstage/plugin-bitbucket-cloud-common@0.3.7-next.1 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.18-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.19-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.3-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.18-next.1 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.18-next.1 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.18-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.9.6-next.1 + - @backstage/plugin-scaffolder-common@1.7.6-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.3-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.18-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-bitbucket-cloud-common@0.3.7-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/backend-defaults@0.15.2-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## @backstage/plugin-scaffolder-backend-module-github@0.9.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.11.3-next.1 + +### Patch Changes + +- 32c51c0: Added new `gitlab:user:info` scaffolder action that retrieves information about a GitLab user. The action can fetch either the current authenticated user or a specific user by ID. +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## @backstage/plugin-scaffolder-common@1.7.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + +## @backstage/plugin-scaffolder-node@0.12.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-scaffolder-common@1.7.6-next.1 + +## @backstage/plugin-scaffolder-react@1.19.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/theme@0.7.2-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/core-components@0.18.7-next.1 + - @backstage/plugin-scaffolder-common@1.7.6-next.1 + +## @backstage/plugin-search-backend-module-pg@0.5.52-next.1 + +### Patch Changes + +- 2ee354a: Return `numberOfResults` count with search query responses +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/plugin-search-react@1.10.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.7.2-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/core-components@0.18.7-next.1 + +## @backstage/plugin-signals@0.0.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.7.2-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/core-components@0.18.7-next.1 + +## @backstage/plugin-techdocs@1.16.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/theme@0.7.2-next.0 + - @backstage/integration@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/plugin-search-react@1.10.3-next.1 + - @backstage/core-components@0.18.7-next.1 + - @backstage/plugin-techdocs-react@1.3.8-next.0 + - @backstage/integration-react@1.2.15-next.1 + +## @backstage/plugin-techdocs-addons-test-utils@2.0.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/plugin-catalog@1.33.0-next.1 + - @backstage/plugin-search-react@1.10.3-next.1 + - @backstage/plugin-techdocs@1.16.3-next.1 + - @backstage/test-utils@1.7.15-next.1 + - @backstage/plugin-techdocs-react@1.3.8-next.0 + - @backstage/integration-react@1.2.15-next.1 + +## @backstage/plugin-techdocs-backend@2.1.5-next.1 + +### Patch Changes + +- 5e3ef57: Added `peerModules` metadata declaring recommended modules for cross-plugin integrations. +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/plugin-techdocs-node@1.14.2-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/backend-defaults@0.15.2-next.1 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.33-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/core-components@0.18.7-next.1 + - @backstage/plugin-techdocs-react@1.3.8-next.0 + - @backstage/integration-react@1.2.15-next.1 + +## @backstage/plugin-techdocs-node@1.14.2-next.1 + +### Patch Changes + +- 3c455d4: Some security fixes +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/plugin-user-settings@0.8.32-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/theme@0.7.2-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/core-components@0.18.7-next.1 + +## example-app@0.2.118-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.15.0-next.1 + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/plugin-scaffolder@1.35.3-next.1 + - @backstage/plugin-catalog-graph@0.5.7-next.1 + - @backstage/plugin-catalog@1.33.0-next.1 + - @backstage/ui@0.12.0-next.1 + - @backstage/theme@0.7.2-next.0 + - @backstage/cli@0.35.4-next.1 + - @backstage/plugin-mui-to-bui@0.2.4-next.1 + - @backstage/plugin-search@1.6.0-next.1 + - @backstage/plugin-search-react@1.10.3-next.1 + - @backstage/plugin-api-docs@0.13.4-next.1 + - @backstage/plugin-catalog-import@0.13.10-next.1 + - @backstage/plugin-home@0.9.2-next.1 + - @backstage/plugin-kubernetes@0.12.16-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.34-next.1 + - @backstage/plugin-org@0.6.49-next.1 + - @backstage/plugin-scaffolder-react@1.19.7-next.1 + - @backstage/plugin-techdocs@1.16.3-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.33-next.1 + - @backstage/plugin-user-settings@0.8.32-next.1 + - @backstage/app-defaults@1.7.5-next.1 + - @backstage/core-components@0.18.7-next.1 + - @backstage/plugin-notifications@0.5.14-next.1 + - @backstage/plugin-signals@0.0.28-next.1 + - @backstage/plugin-techdocs-react@1.3.8-next.0 + - @backstage/integration-react@1.2.15-next.1 + +## example-app-next@0.0.32-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-defaults@0.4.0-next.1 + - @backstage/frontend-app-api@0.15.0-next.1 + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/plugin-scaffolder@1.35.3-next.1 + - @backstage/plugin-catalog-graph@0.5.7-next.1 + - @backstage/plugin-catalog@1.33.0-next.1 + - @backstage/ui@0.12.0-next.1 + - @backstage/theme@0.7.2-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/cli@0.35.4-next.1 + - @backstage/plugin-search@1.6.0-next.1 + - @backstage/plugin-app@0.4.0-next.1 + - @backstage/plugin-app-visualizer@0.1.28-next.0 + - @backstage/plugin-auth@0.1.5-next.1 + - @backstage/core-compat-api@0.5.8-next.1 + - @backstage/plugin-search-react@1.10.3-next.1 + - @backstage/plugin-api-docs@0.13.4-next.1 + - @backstage/plugin-catalog-import@0.13.10-next.1 + - @backstage/plugin-home@0.9.2-next.1 + - @backstage/plugin-kubernetes@0.12.16-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.34-next.1 + - @backstage/plugin-org@0.6.49-next.1 + - @backstage/plugin-scaffolder-react@1.19.7-next.1 + - @backstage/plugin-techdocs@1.16.3-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.33-next.1 + - @backstage/plugin-user-settings@0.8.32-next.1 + - @backstage/app-defaults@1.7.5-next.1 + - @backstage/core-components@0.18.7-next.1 + - @backstage/plugin-notifications@0.5.14-next.1 + - @backstage/plugin-signals@0.0.28-next.1 + - @backstage/plugin-techdocs-react@1.3.8-next.0 + - @backstage/integration-react@1.2.15-next.1 + +## example-backend@0.0.47-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-module-elasticsearch@1.8.0-next.1 + - @backstage/plugin-kubernetes-backend@0.21.1-next.1 + - @backstage/plugin-scaffolder-backend@3.1.3-next.1 + - @backstage/plugin-notifications-backend@0.6.2-next.1 + - @backstage/plugin-catalog-backend@3.4.0-next.1 + - @backstage/plugin-techdocs-backend@2.1.5-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/backend-defaults@0.15.2-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.2.19-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.9.6-next.1 + +## techdocs-cli-embedded-app@0.2.117-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.33.0-next.1 + - @backstage/ui@0.12.0-next.1 + - @backstage/theme@0.7.2-next.0 + - @backstage/cli@0.35.4-next.1 + - @backstage/plugin-techdocs@1.16.3-next.1 + - @backstage/app-defaults@1.7.5-next.1 + - @backstage/core-components@0.18.7-next.1 + - @backstage/test-utils@1.7.15-next.1 + - @backstage/plugin-techdocs-react@1.3.8-next.0 + - @backstage/integration-react@1.2.15-next.1 diff --git a/docs/releases/v1.48.0-next.2-changelog.md b/docs/releases/v1.48.0-next.2-changelog.md new file mode 100644 index 0000000000..5d041b559c --- /dev/null +++ b/docs/releases/v1.48.0-next.2-changelog.md @@ -0,0 +1,1438 @@ +# Release v1.48.0-next.2 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.48.0-next.2](https://backstage.github.io/upgrade-helper/?to=1.48.0-next.2) + +## @backstage/backend-test-utils@1.11.0-next.1 + +### Minor Changes + +- 42abfb1: Added `createServiceMock`, a public utility for creating `ServiceMock` instances for custom service refs. This allows plugin authors to define mock creators for their own services following the same pattern as the built-in `mockServices` mocks. + +### Patch Changes + +- 68eb322: Added `@types/jest` as an optional peer dependency, since jest types are exposed in the public API surface. +- Updated dependencies + - @backstage/backend-app-api@1.5.0-next.1 + - @backstage/backend-defaults@0.15.2-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-auth-node@0.6.13-next.1 + - @backstage/plugin-events-node@0.4.19-next.0 + +## @backstage/filter-predicates@0.1.0-next.0 + +### Minor Changes + +- 7feb83b: Introduced package, basically as the extracted predicate types from `@backstage/plugin-catalog-react/alpha` + +## @backstage/frontend-test-utils@0.5.0-next.2 + +### Minor Changes + +- 09a6aad: **BREAKING**: Removed the `TestApiRegistry` class, use `TestApiProvider` directly instead, storing reused APIs in a variable, e.g. `const apis = [...] as const`. +- d2ac2ec: Added `MockAlertApi` and `MockFeatureFlagsApi` implementations to the `mockApis` namespace. The mock implementations include useful testing methods like `clearAlerts()`, `waitForAlert()`, `getState()`, `setState()`, and `clearState()` for better test ergonomics. +- 09a6aad: **BREAKING**: The `mockApis` namespace is no longer a re-export from `@backstage/test-utils`. It's now a standalone namespace with mock implementations of most core APIs. Mock API instances can be passed directly to `TestApiProvider`, `renderInTestApp`, and `renderTestApp` without needing `[apiRef, impl]` tuples. As part of this change, the `.factory()` method on some mocks has been removed, since it's now redundant. + + ```tsx + // Before + import { mockApis } from '@backstage/frontend-test-utils'; + + renderInTestApp(, { + apis: [[identityApiRef, mockApis.identity()]], + }); + + // After - mock APIs can be passed directly + renderInTestApp(, { + apis: [mockApis.identity()], + }); + ``` + + This change also adds `createApiMock`, a public utility for creating mock API factories, intended for plugin authors to create their own `.mock()` variants. + +### Patch Changes + +- 15ed3f9: Added `snapshot()` method to `ExtensionTester`, which returns a tree-shaped representation of the resolved extension hierarchy. Convenient to use with `toMatchInlineSnapshot()`. + +- 013ec22: Added `mountedRoutes` option to `renderTestApp` for binding route refs to paths, matching the existing option in `renderInTestApp`: + + ```typescript + renderTestApp({ + extensions: [...], + mountedRoutes: { + '/my-path': myRouteRef, + }, + }); + ``` + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. + +- Updated dependencies + - @backstage/core-app-api@1.19.5-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/frontend-app-api@0.15.0-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-permission-react@0.4.40-next.1 + - @backstage/version-bridge@1.0.12-next.0 + - @backstage/test-utils@1.7.15-next.2 + - @backstage/plugin-app@0.4.0-next.2 + - @backstage/plugin-app-react@0.1.1-next.0 + +## @backstage/ui@0.12.0-next.2 + +### Minor Changes + +- b63c25b: **BREAKING**: Removed gray scale tokens and renamed background surface tokens to neutral tokens + + The `--bui-gray-1` through `--bui-gray-8` tokens have been removed. The `--bui-bg-surface-*` and `--bui-bg-neutral-on-surface-*` tokens have been replaced by a unified `--bui-bg-neutral-*` scale. + + **Migration:** + + Replace surface tokens directly: + + ```diff + - background: var(--bui-bg-surface-0); + + background: var(--bui-bg-app); + ``` + + Replace on-surface tokens shifted by +1: + + ```diff + - background: var(--bui-bg-neutral-on-surface-0); + + background: var(--bui-bg-neutral-1); + ``` + + Replace gray tokens 1-4 with neutral equivalents (`--bui-gray-5` through `--bui-gray-8` have no direct replacement): + + ```diff + - background: var(--bui-gray-1); + + background: var(--bui-bg-neutral-1); + ``` + +### Patch Changes + +- c8ae765: Fixed nested Accordion icon state issue where the inner accordion's arrow icon would incorrectly show as expanded when only the outer accordion was expanded. The CSS selector now uses a direct parent selector to ensure the icon only responds to its own accordion's expanded state. + + Affected components: Accordion + +- 5c76d13: Allow `ref` as a prop on the `Tag` component + + Affected components: Tag + +- 741a98d: Allow data to be passed directly to the `useTable` hook using the property `data` instead of `getData()` for mode `"complete"`. + + This simplifies usage as data changes, rather than having to perform a `useEffect` when data changes, and then reloading the data. It also happens immediately, so stale data won't remain until a rerender (with an internal async state change), so less flickering. + + Affected components: Table + +- a0fe1b2: Fixed changing columns after first render from crashing. It now renders the table with the new column layout as columns change. + + Affected components: Table + +- becf851: export PasswordField component + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. + +- 8c39412: The Table component now wraps the react-aria-components `Table` with a `ResizableTableContainer` only if any column has a width property set. This means that column widths can adapt to the content otherwise (if no width is explicitly set). + + Affected components: Table + +- 20131c5: Migrated to use the standard `backstage-cli package build` for CSS bundling instead of a custom build script. + +- Updated dependencies + - @backstage/version-bridge@1.0.12-next.0 + +## @backstage/plugin-auth-backend@0.27.0-next.1 + +### Minor Changes + +- d0786b9: Added experimental support for refresh tokens via the `auth.experimentalRefreshToken.enabled` configuration option. When enabled, clients can request the `offline_access` scope to receive refresh tokens that can be used to obtain new access tokens without re-authentication. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-auth-node@0.6.13-next.1 + +## @backstage/plugin-catalog@1.33.0-next.2 + +### Minor Changes + +- 491a06c: Add the ability to show icons for the tabs on the entity page (new frontend) + +### Patch Changes + +- 220d6c3: Add missing translation entries for catalog UI text. + + This change adds translation keys and updates relevant UI components to use the correct localized labels and text in the catalog plugin. It ensures that catalog screens such as entity layout, tabs, search result items, table labels, and other UI elements correctly reference the i18n system for translation. + + No functional behavior is changed aside from the improved internationalization support. + +- 7feb83b: Adjusted to use the new `@backstage/filter-predicates` types for predicate expressions. + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. + +- 75ac651: Migrated `EntityRelationWarning` and `EntityProcessingErrorsPanel` components from Material UI to Backstage UI. + +- Updated dependencies + - @backstage/ui@0.12.0-next.2 + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/catalog-client@1.12.2-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/integration-react@1.2.15-next.2 + - @backstage/core-compat-api@0.5.8-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-permission-react@0.4.40-next.1 + - @backstage/version-bridge@1.0.12-next.0 + - @backstage/plugin-techdocs-react@1.3.8-next.1 + - @backstage/plugin-search-react@1.10.3-next.2 + +## @backstage/plugin-catalog-react@2.0.0-next.2 + +### Minor Changes + +- 491a06c: Add the ability to show icons for the tabs on the entity page (new frontend) + +- 7feb83b: **BREAKING ALPHA**: All of the predicate types and functions have been moved to the `@backstage/filter-predicates` package. + + When moving into the more general package, they were renamed as follows: + + - `EntityPredicate` -> `FilterPredicate` + - `EntityPredicateExpression` -> `FilterPredicateExpression` + - `EntityPredicatePrimitive` -> `FilterPredicatePrimitive` + - `entityPredicateToFilterFunction` -> `filterPredicateToFilterFunction` + - `EntityPredicateValue` -> `FilterPredicateValue` + +- ac9bead: Added `createTestEntityPage` test utility for testing entity cards and content extensions in the new frontend system. This utility creates a test page extension that provides `EntityProvider` context and accepts entity extensions through input redirects: + + ```typescript + import { renderTestApp } from '@backstage/frontend-test-utils'; + import { createTestEntityPage } from '@backstage/plugin-catalog-react/testUtils'; + + renderTestApp({ + extensions: [createTestEntityPage({ entity: myEntity }), myEntityCard], + }); + ``` + +### Patch Changes + +- 09a6aad: The `catalogApiMock` test utility now returns a `MockWithApiFactory`, allowing it to be passed directly to test utilities like `renderTestApp` and `TestApiProvider` without needing the `[catalogApiRef, catalogApiMock()]` tuple. +- 88dbd5e: fixed bug in `UserListPicker` by getting the `kindParamater` from the `filters` rather than from the `queryParameters` +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/ui@0.12.0-next.2 + - @backstage/frontend-test-utils@0.5.0-next.2 + - @backstage/catalog-client@1.12.2-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/integration-react@1.2.15-next.2 + - @backstage/core-compat-api@0.5.8-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-permission-react@0.4.40-next.1 + - @backstage/version-bridge@1.0.12-next.0 + - @backstage/filter-predicates@0.1.0-next.0 + +## @backstage/plugin-user-settings@0.9.0-next.2 + +### Minor Changes + +- 104ca74: User-settings will now use DataLoader to batch consecutive calls into one API call to improve performance + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/core-app-api@1.19.5-next.1 + - @backstage/plugin-user-settings-common@0.1.0-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-signals-react@0.0.19-next.1 + - @backstage/theme@0.7.2-next.1 + +## @backstage/plugin-user-settings-backend@0.4.0-next.1 + +### Minor Changes + +- 104ca74: User-settings will now use DataLoader to batch consecutive calls into one API call to improve performance + +### Patch Changes + +- 8148621: Moved `@backstage/backend-defaults` from `dependencies` to `devDependencies`. +- Updated dependencies + - @backstage/plugin-user-settings-common@0.1.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-auth-node@0.6.13-next.1 + +## @backstage/plugin-user-settings-common@0.1.0-next.0 + +### Minor Changes + +- 104ca74: User-settings will now use DataLoader to batch consecutive calls into one API call to improve performance + +## @backstage/app-defaults@1.7.5-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/core-app-api@1.19.5-next.1 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-permission-react@0.4.40-next.1 + - @backstage/theme@0.7.2-next.1 + +## @backstage/backend-app-api@1.5.0-next.1 + +### Patch Changes + +- 6bb2f21: Fixed memory leak by properly cleaning up process event listeners on backend shutdown. +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/catalog-client@1.12.2-next.0 + +### Patch Changes + +- 9cf6762: Improved the `InMemoryCatalogClient` test utility to support ordering, pagination, full-text search, and field projection for entity query methods. Also fixed `getEntityFacets` to correctly handle multi-valued fields. + +## @backstage/cli@0.35.4-next.2 + +### Patch Changes + +- 20131c5: Added support for CSS exports in package builds. When a package declares a CSS file in its `exports` field (e.g., `"./styles.css": "./src/styles.css"`), the CLI will automatically bundle it during `backstage-cli package build`, resolving any `@import` statements. The export path is rewritten from `src/` to `dist/` at publish time. + + Fixed `backstage-cli repo fix` to not add `typesVersions` entries for non-script exports like CSS files. + +- 6ce4a13: Removed `/alpha` from `scaffolderActionsExtensionPoint` import + +- 73351c2: Updated dependency `webpack` to `~5.104.0`. + +- Updated dependencies + - @backstage/integration@1.20.0-next.2 + - @backstage/cli-node@0.2.18-next.1 + - @backstage/config-loader@1.10.8-next.0 + +## @backstage/core-app-api@1.19.5-next.1 + +### Patch Changes + +- 5a71e7a: Fixed memory leak caused by duplicate `AppThemeSelector` instances and missing cleanup in `AppThemeSelector` and `AppLanguageSelector`. Added `dispose()` method to both selectors for proper resource cleanup. +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/version-bridge@1.0.12-next.0 + +## @backstage/core-compat-api@0.5.8-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/version-bridge@1.0.12-next.0 + - @backstage/plugin-app-react@0.1.1-next.0 + +## @backstage/core-components@0.18.7-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/version-bridge@1.0.12-next.0 + - @backstage/theme@0.7.2-next.1 + +## @backstage/core-plugin-api@1.12.3-next.1 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/version-bridge@1.0.12-next.0 + +## @backstage/create-app@0.7.9-next.2 + +### Patch Changes + +- Bumped create-app version. + +## @backstage/dev-utils@1.1.20-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/ui@0.12.0-next.2 + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/core-app-api@1.19.5-next.1 + - @backstage/integration-react@1.2.15-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/app-defaults@1.7.5-next.2 + - @backstage/theme@0.7.2-next.1 + +## @backstage/e2e-test-utils@0.1.2-next.1 + +### Patch Changes + +- b96c20e: Added optional `channel` option to `generateProjects()` to allow customizing the Playwright browser channel for testing against different browsers variants. When not provided, the function defaults to 'chrome' to maintain backward compatibility. + + Example usage: + + ```ts + import { generateProjects } from '@backstage/e2e-test-utils'; + + export default defineConfig({ + projects: generateProjects({ channel: 'msedge' }), + }); + ``` + +## @backstage/frontend-app-api@0.15.0-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/core-app-api@1.19.5-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/frontend-defaults@0.4.0-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/version-bridge@1.0.12-next.0 + +## @backstage/frontend-defaults@0.4.0-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/frontend-app-api@0.15.0-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/plugin-app@0.4.0-next.2 + +## @backstage/frontend-dynamic-feature-loader@0.1.9-next.1 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.2 + +## @backstage/frontend-plugin-api@0.14.0-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/version-bridge@1.0.12-next.0 + +## @backstage/integration@1.20.0-next.2 + +### Patch Changes + +- cc6206e: Added support for `{org}.visualstudio.com` domains used by Azure DevOps + +## @backstage/integration-react@1.2.15-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/integration@1.20.0-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + +## @backstage/repo-tools@0.16.4-next.2 + +### Patch Changes + +- be7ebad: Updated package-docs exclude list to reflect renamed example app packages. +- df59ee6: The `type-deps` command now follows relative imports and re-exports into declaration chunk files, and detects ambient global types such as the `jest` namespace. +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/cli-node@0.2.18-next.1 + - @backstage/config-loader@1.10.8-next.0 + +## @techdocs/cli@1.10.5-next.1 + +### Patch Changes + +- 508d127: Updated dependency `find-process` to `^2.0.0`. +- Updated dependencies + - @backstage/backend-defaults@0.15.2-next.1 + - @backstage/plugin-techdocs-node@1.14.2-next.1 + +## @backstage/test-utils@1.7.15-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- 68eb322: Added `@types/jest` as an optional peer dependency, since jest types are exposed in the public API surface. +- Updated dependencies + - @backstage/core-app-api@1.19.5-next.1 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-permission-react@0.4.40-next.1 + - @backstage/theme@0.7.2-next.1 + +## @backstage/theme@0.7.2-next.1 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. + +## @backstage/version-bridge@1.0.12-next.0 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. + +## @backstage/plugin-api-docs@0.13.4-next.2 + +### Patch Changes + +- ac9bead: Added `@backstage/frontend-test-utils` dev dependency. +- 7feb83b: Adjusted to use the new `@backstage/filter-predicates` types for predicate expressions. +- 491a06c: Add the ability to show icons for the tabs on the entity page (new frontend) +- 4183614: Updated usage of deprecated APIs in the new frontend system. +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/plugin-catalog@1.33.0-next.2 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-permission-react@0.4.40-next.1 + +## @backstage/plugin-api-docs-module-protoc-gen-doc@0.1.11-next.0 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. + +## @backstage/plugin-app@0.4.0-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/integration-react@1.2.15-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-permission-react@0.4.40-next.1 + - @backstage/version-bridge@1.0.12-next.0 + - @backstage/theme@0.7.2-next.1 + - @backstage/plugin-app-react@0.1.1-next.0 + +## @backstage/plugin-app-visualizer@0.1.28-next.1 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/ui@0.12.0-next.2 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + +## @backstage/plugin-auth@0.1.5-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/theme@0.7.2-next.1 + +## @backstage/plugin-auth-backend-module-auth0-provider@0.3.0-next.1 + +### Patch Changes + +- 867c905: Add support for organizational invites in auth0 strategy +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-auth-node@0.6.13-next.1 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.27.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-auth-node@0.6.13-next.1 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.4.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.27.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-auth-node@0.6.13-next.1 + +## @backstage/plugin-auth-node@0.6.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.12.2-next.0 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/plugin-auth-react@0.1.24-next.1 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + +## @backstage/plugin-catalog-backend@3.4.0-next.2 + +### Patch Changes + +- 08a5813: Fixed O(n²) performance bottleneck in `buildEntitySearch` `traverse()` by replacing `Array.some()` linear scan with a `Set` for O(1) duplicate path key detection. +- Updated dependencies + - @backstage/integration@1.20.0-next.2 + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/catalog-client@1.12.2-next.0 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/plugin-permission-node@0.10.10-next.0 + +## @backstage/plugin-catalog-backend-module-aws@0.4.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.2 + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/backend-defaults@0.15.2-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/plugin-catalog-backend-module-azure@0.3.14-next.2 + +### Patch Changes + +- cc6206e: Added support for `{org}.visualstudio.com` domains used by Azure DevOps +- Updated dependencies + - @backstage/integration@1.20.0-next.2 + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.2 + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-events-node@0.4.19-next.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.2 + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-events-node@0.4.19-next.0 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/plugin-catalog-backend-module-gerrit@0.3.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.2 + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/plugin-catalog-backend-module-gitea@0.1.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.2 + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/plugin-catalog-backend-module-github@0.12.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.2 + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-events-node@0.4.19-next.0 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-catalog-backend-module-github@0.12.2-next.2 + - @backstage/plugin-events-node@0.4.19-next.0 + +## @backstage/plugin-catalog-backend-module-gitlab@0.8.0-next.2 + +### Patch Changes + +- 7e6b5e5: Fixed GitLab search API scope parameter from `'blob'` to `'blobs'`, resolving 400 errors in discovery provider. +- Updated dependencies + - @backstage/integration@1.20.0-next.2 + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/backend-defaults@0.15.2-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-events-node@0.4.19-next.0 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/plugin-catalog-backend-module-gitlab@0.8.0-next.2 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-events-node@0.4.19-next.0 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/plugin-catalog-backend@3.4.0-next.2 + - @backstage/backend-defaults@0.15.2-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-events-node@0.4.19-next.0 + +## @backstage/plugin-catalog-backend-module-ldap@0.12.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/plugin-catalog-backend-module-msgraph@0.9.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.2 + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.6.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-auth-node@0.6.13-next.1 + +## @backstage/plugin-catalog-graph@0.5.7-next.2 + +### Patch Changes + +- ac9bead: Added `@backstage/frontend-test-utils` dev dependency. +- 7feb83b: Adjusted to use the new `@backstage/filter-predicates` types for predicate expressions. +- 4183614: Updated usage of deprecated APIs in the new frontend system. +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/catalog-client@1.12.2-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + +## @backstage/plugin-catalog-import@0.13.10-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/integration@1.20.0-next.2 + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/catalog-client@1.12.2-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/integration-react@1.2.15-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-permission-react@0.4.40-next.1 + +## @backstage/plugin-catalog-node@2.0.0-next.1 + +### Patch Changes + +- 42abfb1: Updated `catalogServiceMock.mock` to use `createServiceMock` from `@backstage/backend-test-utils`, replacing the internal copy of `simpleMock`. Added `@backstage/backend-test-utils` as an optional peer dependency. +- Updated dependencies + - @backstage/backend-test-utils@1.11.0-next.1 + - @backstage/catalog-client@1.12.2-next.0 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-permission-node@0.10.10-next.0 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.26-next.1 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/plugin-devtools-react@0.1.1-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-compat-api@0.5.8-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + +## @backstage/plugin-config-schema@0.1.77-next.1 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + +## @backstage/plugin-devtools@0.1.36-next.1 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/plugin-devtools-react@0.1.1-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-compat-api@0.5.8-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-permission-react@0.4.40-next.1 + +## @backstage/plugin-devtools-backend@0.5.14-next.1 + +### Patch Changes + +- 8148621: Moved `@backstage/backend-defaults` from `dependencies` to `devDependencies`. +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/config-loader@1.10.8-next.0 + - @backstage/plugin-permission-node@0.10.10-next.0 + +## @backstage/plugin-devtools-react@0.1.1-next.1 + +### Patch Changes + +- 9fbb270: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + +## @backstage/plugin-home@0.9.2-next.2 + +### Patch Changes + +- 90956a6: Support new frontend system in the homepage plugin +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/core-app-api@1.19.5-next.1 + - @backstage/catalog-client@1.12.2-next.0 + - @backstage/plugin-home-react@0.1.35-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-compat-api@0.5.8-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/theme@0.7.2-next.1 + +## @backstage/plugin-home-react@0.1.35-next.1 + +### Patch Changes + +- 90956a6: Support new frontend system in the homepage plugin +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-compat-api@0.5.8-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + +## @backstage/plugin-kubernetes@0.12.16-next.2 + +### Patch Changes + +- 7feb83b: Adjusted to use the new `@backstage/filter-predicates` types for predicate expressions. +- 491a06c: Add the ability to show icons for the tabs on the entity page (new frontend) +- 4183614: Updated usage of deprecated APIs in the new frontend system. +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/plugin-kubernetes-react@0.5.16-next.2 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-permission-react@0.4.40-next.1 + +## @backstage/plugin-kubernetes-backend@0.21.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/catalog-client@1.12.2-next.0 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-kubernetes-node@0.4.1-next.1 + - @backstage/plugin-permission-node@0.10.10-next.0 + +## @backstage/plugin-kubernetes-cluster@0.0.34-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/plugin-kubernetes-react@0.5.16-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-permission-react@0.4.40-next.1 + +## @backstage/plugin-kubernetes-react@0.5.16-next.2 + +### Patch Changes + +- d56542c: Updated dependency `@xterm/addon-attach` to `^0.12.0`. + Updated dependency `@xterm/addon-fit` to `^0.11.0`. +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + +## @backstage/plugin-mcp-actions-backend@0.1.9-next.1 + +### Patch Changes + +- 8148621: Moved `@backstage/backend-defaults` from `dependencies` to `devDependencies`. +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/catalog-client@1.12.2-next.0 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/plugin-mui-to-bui@0.2.4-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/ui@0.12.0-next.2 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/theme@0.7.2-next.1 + +## @backstage/plugin-notifications@0.5.14-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-signals-react@0.0.19-next.1 + - @backstage/theme@0.7.2-next.1 + +## @backstage/plugin-notifications-backend@0.6.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-notifications-node@0.2.23-next.1 + +## @backstage/plugin-notifications-backend-module-email@0.3.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/catalog-client@1.12.2-next.0 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-notifications-node@0.2.23-next.1 + +## @backstage/plugin-notifications-backend-module-slack@0.3.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-notifications-node@0.2.23-next.1 + +## @backstage/plugin-notifications-node@0.2.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.12.2-next.0 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## @backstage/plugin-org@0.6.49-next.2 + +### Patch Changes + +- ac9bead: Added `@backstage/frontend-test-utils` dev dependency. +- 7feb83b: Adjusted to use the new `@backstage/filter-predicates` types for predicate expressions. +- 4183614: Updated usage of deprecated APIs in the new frontend system. +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + +## @backstage/plugin-org-react@0.1.47-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/catalog-client@1.12.2-next.0 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + +## @backstage/plugin-permission-react@0.4.40-next.1 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/core-plugin-api@1.12.3-next.1 + +## @backstage/plugin-scaffolder@1.35.3-next.2 + +### Patch Changes + +- 7feb83b: Adjusted to use the new `@backstage/filter-predicates` types for predicate expressions. +- b9d90a7: Added `@backstage/frontend-test-utils` as a dev dependency for mock API usage in tests. +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/integration@1.20.0-next.2 + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/catalog-client@1.12.2-next.0 + - @backstage/plugin-scaffolder-react@1.19.7-next.2 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/integration-react@1.2.15-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-permission-react@0.4.40-next.1 + - @backstage/plugin-techdocs-react@1.3.8-next.1 + +## @backstage/plugin-scaffolder-backend@3.1.3-next.2 + +### Patch Changes + +- 8148621: Moved `@backstage/backend-defaults` from `dependencies` to `devDependencies`. +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-gitlab@0.11.3-next.2 + - @backstage/integration@1.20.0-next.2 + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-auth-node@0.6.13-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.17-next.1 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/plugin-permission-node@0.10.10-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.19-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.3-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.18-next.1 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.18-next.1 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.18-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.9.6-next.2 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## @backstage/plugin-scaffolder-backend-module-github@0.9.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.2 + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.11.3-next.2 + +### Patch Changes + +- 6b5e7d9: Allow setting optional description on group creation +- f0f9403: Changed `gitlab:group:ensureExists` action to use `Groups.show` API instead of `Groups.search` for checking if a group path exists. This is more efficient as it directly retrieves the group by path rather than searching and filtering results. +- Updated dependencies + - @backstage/integration@1.20.0-next.2 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## @backstage/plugin-scaffolder-node-test-utils@0.3.8-next.1 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/backend-test-utils@1.11.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## @backstage/plugin-scaffolder-react@1.19.7-next.2 + +### Patch Changes + +- b9d90a7: Added `@backstage/frontend-test-utils` as a dev dependency for mock API usage in tests. +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/catalog-client@1.12.2-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-permission-react@0.4.40-next.1 + - @backstage/version-bridge@1.0.12-next.0 + - @backstage/theme@0.7.2-next.1 + +## @backstage/plugin-search@1.6.0-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/version-bridge@1.0.12-next.0 + - @backstage/plugin-search-react@1.10.3-next.2 + +## @backstage/plugin-search-backend@2.0.12-next.1 + +### Patch Changes + +- 8148621: Moved `@backstage/backend-defaults` from `dependencies` to `devDependencies`. +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-permission-node@0.10.10-next.0 + - @backstage/plugin-search-backend-node@1.4.1-next.0 + +## @backstage/plugin-search-backend-module-catalog@0.3.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/catalog-client@1.12.2-next.0 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-search-backend-node@1.4.1-next.0 + +## @backstage/plugin-search-backend-module-explore@0.3.11-next.1 + +### Patch Changes + +- df27350: Updated dependency `@backstage-community/plugin-explore-common` to `^0.12.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-search-backend-node@1.4.1-next.0 + +## @backstage/plugin-search-backend-module-techdocs@0.4.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/catalog-client@1.12.2-next.0 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-search-backend-node@1.4.1-next.0 + - @backstage/plugin-techdocs-node@1.14.2-next.1 + +## @backstage/plugin-search-react@1.10.3-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/version-bridge@1.0.12-next.0 + - @backstage/theme@0.7.2-next.1 + +## @backstage/plugin-signals@0.0.28-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-signals-react@0.0.19-next.1 + - @backstage/theme@0.7.2-next.1 + +## @backstage/plugin-signals-react@0.0.19-next.1 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/core-plugin-api@1.12.3-next.1 + +## @backstage/plugin-techdocs@1.16.3-next.2 + +### Patch Changes + +- 7feb83b: Adjusted to use the new `@backstage/filter-predicates` types for predicate expressions. +- 491a06c: Add the ability to show icons for the tabs on the entity page (new frontend) +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/integration@1.20.0-next.2 + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/catalog-client@1.12.2-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/integration-react@1.2.15-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-techdocs-react@1.3.8-next.1 + - @backstage/plugin-search-react@1.10.3-next.2 + - @backstage/plugin-auth-react@0.1.24-next.1 + - @backstage/theme@0.7.2-next.1 + +## @backstage/plugin-techdocs-addons-test-utils@2.0.2-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/plugin-catalog@1.33.0-next.2 + - @backstage/core-app-api@1.19.5-next.1 + - @backstage/plugin-techdocs@1.16.3-next.2 + - @backstage/integration-react@1.2.15-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-techdocs-react@1.3.8-next.1 + - @backstage/plugin-search-react@1.10.3-next.2 + - @backstage/test-utils@1.7.15-next.2 + +## @backstage/plugin-techdocs-backend@2.1.5-next.2 + +### Patch Changes + +- 8148621: Moved `@backstage/backend-defaults` from `dependencies` to `devDependencies`. +- Updated dependencies + - @backstage/integration@1.20.0-next.2 + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/catalog-client@1.12.2-next.0 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-techdocs-node@1.14.2-next.1 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.33-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/integration@1.20.0-next.2 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/integration-react@1.2.15-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-techdocs-react@1.3.8-next.1 + +## @backstage/plugin-techdocs-react@1.3.8-next.1 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/version-bridge@1.0.12-next.0 + +## example-app@0.0.32-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.12.0-next.2 + - @backstage/plugin-api-docs@0.13.4-next.2 + - @backstage/plugin-catalog-graph@0.5.7-next.2 + - @backstage/plugin-org@0.6.49-next.2 + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/cli@0.35.4-next.2 + - @backstage/plugin-catalog@1.33.0-next.2 + - @backstage/core-app-api@1.19.5-next.1 + - @backstage/plugin-kubernetes@0.12.16-next.2 + - @backstage/plugin-scaffolder@1.35.3-next.2 + - @backstage/plugin-techdocs@1.16.3-next.2 + - @backstage/plugin-scaffolder-react@1.19.7-next.2 + - @backstage/plugin-user-settings@0.9.0-next.2 + - @backstage/plugin-home-react@0.1.35-next.1 + - @backstage/plugin-home@0.9.2-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.33-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.26-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/frontend-defaults@0.4.0-next.2 + - @backstage/integration-react@1.2.15-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.34-next.2 + - @backstage/frontend-app-api@0.15.0-next.2 + - @backstage/core-compat-api@0.5.8-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-permission-react@0.4.40-next.1 + - @backstage/plugin-app-visualizer@0.1.28-next.1 + - @backstage/plugin-catalog-import@0.13.10-next.2 + - @backstage/plugin-techdocs-react@1.3.8-next.1 + - @backstage/app-defaults@1.7.5-next.2 + - @backstage/plugin-notifications@0.5.14-next.2 + - @backstage/plugin-search-react@1.10.3-next.2 + - @backstage/plugin-auth-react@0.1.24-next.1 + - @backstage/plugin-devtools@0.1.36-next.1 + - @backstage/plugin-signals@0.0.28-next.2 + - @backstage/theme@0.7.2-next.1 + - @backstage/plugin-search@1.6.0-next.2 + - @backstage/plugin-auth@0.1.5-next.2 + - @backstage/plugin-app@0.4.0-next.2 + - @backstage/plugin-app-react@0.1.1-next.0 + +## example-app-legacy@0.2.118-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.12.0-next.2 + - @backstage/plugin-api-docs@0.13.4-next.2 + - @backstage/plugin-catalog-graph@0.5.7-next.2 + - @backstage/plugin-org@0.6.49-next.2 + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/cli@0.35.4-next.2 + - @backstage/plugin-catalog@1.33.0-next.2 + - @backstage/core-app-api@1.19.5-next.1 + - @backstage/plugin-kubernetes@0.12.16-next.2 + - @backstage/plugin-scaffolder@1.35.3-next.2 + - @backstage/plugin-techdocs@1.16.3-next.2 + - @backstage/plugin-scaffolder-react@1.19.7-next.2 + - @backstage/plugin-user-settings@0.9.0-next.2 + - @backstage/plugin-home-react@0.1.35-next.1 + - @backstage/plugin-home@0.9.2-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.33-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.26-next.1 + - @backstage/integration-react@1.2.15-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.34-next.2 + - @backstage/frontend-app-api@0.15.0-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-permission-react@0.4.40-next.1 + - @backstage/plugin-catalog-import@0.13.10-next.2 + - @backstage/plugin-techdocs-react@1.3.8-next.1 + - @backstage/app-defaults@1.7.5-next.2 + - @backstage/plugin-notifications@0.5.14-next.2 + - @backstage/plugin-search-react@1.10.3-next.2 + - @backstage/plugin-auth-react@0.1.24-next.1 + - @backstage/plugin-mui-to-bui@0.2.4-next.2 + - @backstage/plugin-devtools@0.1.36-next.1 + - @backstage/plugin-signals@0.0.28-next.2 + - @backstage/theme@0.7.2-next.1 + - @backstage/plugin-search@1.6.0-next.2 + +## example-backend@0.0.47-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.27.0-next.1 + - @backstage/plugin-devtools-backend@0.5.14-next.1 + - @backstage/plugin-mcp-actions-backend@0.1.9-next.1 + - @backstage/plugin-scaffolder-backend@3.1.3-next.2 + - @backstage/plugin-search-backend@2.0.12-next.1 + - @backstage/plugin-techdocs-backend@2.1.5-next.2 + - @backstage/plugin-search-backend-module-explore@0.3.11-next.1 + - @backstage/plugin-catalog-backend@3.4.0-next.2 + - @backstage/backend-defaults@0.15.2-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-app-backend@0.5.11-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.5.0-next.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.16-next.0 + - @backstage/plugin-auth-backend-module-openshift-provider@0.1.4-next.0 + - @backstage/plugin-auth-node@0.6.13-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.2.19-next.2 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.17-next.1 + - @backstage/plugin-events-backend@0.5.11-next.0 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.8-next.0 + - @backstage/plugin-kubernetes-backend@0.21.1-next.2 + - @backstage/plugin-notifications-backend@0.6.2-next.2 + - @backstage/plugin-permission-backend@0.7.9-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.16-next.0 + - @backstage/plugin-permission-node@0.10.10-next.0 + - @backstage/plugin-proxy-backend@0.6.10-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.9.6-next.2 + - @backstage/plugin-search-backend-module-catalog@0.3.12-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.8.0-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.4.11-next.1 + - @backstage/plugin-search-backend-node@1.4.1-next.0 + - @backstage/plugin-signals-backend@0.3.12-next.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.11-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.8-next.1 + +## @internal/frontend@0.0.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/version-bridge@1.0.12-next.0 diff --git a/docs/releases/v1.48.0.md b/docs/releases/v1.48.0.md new file mode 100644 index 0000000000..53b45c2f5f --- /dev/null +++ b/docs/releases/v1.48.0.md @@ -0,0 +1,130 @@ +--- +id: v1.48.0 +title: v1.48.0 +description: Backstage Release v1.48.0 +--- + +These are the release notes for the v1.48.0 release of [Backstage](https://backstage.io/). + +A huge thanks to the whole team of maintainers and contributors as well as the amazing Backstage Community for the hard work in getting this release developed and done. + +## Highlights + +### **BREAKING ALPHA**: Catalog extension points graduated + +If you are providing custom processors and entity providers into the catalog, you will now note that several (but not quite all!) of those extension points have graduated out of alpha and into the regular stable exports. + +Thus, if you are importing for example `catalogProcessingExtensionPoint` from `@backstage/plugin-catalog-node/alpha`, you now want to remove that `/alpha` suffix. + +### **BREAKING**: API restrictions in New Frontend System + +In the 1.47 release a new behavior was introduced to the New Frontend System that limits the ability for plugins and modules to provide APIs to plugins other than themselves. For example, the `scaffolder` plugin could no longer install a custom `CatalogApi` implementation. This also applies to modules, where you now need to use a module explicitly targeting the `'app'` plugin to for example override the `ErrorApi`. + +In 1.47 this new behavior simply triggered a warning, but in this release the API overrides are instead rejected with an error. + +### Experimental Client ID Metadata Documents + +With the [latest MCP specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization) published in November, it outlined a new authorization method that’s going to take over from Dynamic Client Registration called [Client ID Metadata Documents](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#client-id-metadata-documents). + +This can be enabled in the `auth-backend` plugin by using the `auth.experimentalClientIdMetadataDocuments.enabled` flag in config. + +Be sure to head over to the [changelog](https://github.com/backstage/backstage/blob/master/plugins/auth-backend/CHANGELOG.md) for additional configuration and security considerations. + +### Experimental Refresh Token Support + +Clients can now request the `offline_access` scope to receive refresh tokens when `auth.experimentalRefreshToken.enabled` is set, allowing new access tokens without re-authentication. This can be highly useful when using `experimentalDynamicClientRegistration` so that the tokens don’t expire every 1h by default, which then triggers another auth session approval request. + +This also means that `auth.experimentalDynamicClientRegistration.tokenExpiration` has been removed in favor of using this method as a **BREAKING EXPERIMENTAL** change. + +### New Frontend System: Testing utilities + +The testing utilities for the new frontend system have gotten an overhaul in this release, with all new features documented in the [plugin testing section](https://backstage.io/docs/frontend-system/building-plugins/testing). Some highlights include that `mountedRoutes` and `apis` options are available for all harnesses where applicable. The selection of `mockApis` has been increased to support more of the core APIs and have both Jest and minimal implementation mocks. The `mockApis` now also produces values that can be passed directly to the various testing `apis` options, without the need to wrap `[ref, impl]` tuples. + +### Utilities for creating frontend and backend mocks + +Both `@backstage/frontend-test-utils` and `@backstage/backend-test-utils` now export utilities for creating service and API mocks, `createServiceMock` and `createApiMock`. These utilities create the same kind of mock utilities as the ones provided by `mockServices` and `mockApis`, and are encouraged to be exported via the `/testUtils` sub-path export from packages, similar to how this pattern is used in `@backstage/plugin-catalog-react`. + +### New Frontend System: Plugin titles & icons + +Plugins now have optional titles and icons in the new frontend system. This helps implement app-wide features and navigation patterns that enumerate plugins or in other ways traverse the extension tree in the app. + +As part of this change we are also updating the icon type in the new frontend system. The existing `IconComponent` type is being deprecated, and is being replaced by the element-based `IconElement`. + +### New Frontend System: Page and nav structure + +This release starts incorporating the new navigation system from `@backstage/ui` into the New Frontend System. This comes in the form of an updated `PageBlueprint` that now supports sub-page navigation using a new `SubPageBlueprint`. This also introduces the new plugin-wide `PluginHeader`, although in this release it is limited to only being shown on plugin pages that use the new sub-page navigation. + +This change also updates the API provided by `NavContentBlueprint`. The `items` prop is now deprecated in favor of a new `navItems` prop, which is based on enumerating pages in the app rather than explicit nav items. The `navItems` prop also provides an API that makes it simpler to construct a nav bar that has explicit positioning of a few items, while leaving the rest to be rendered in a group. + +### New Frontend System: Home plugin support + +Added support for the New Frontend system to the Home plugin. + +Contributed by [@kunickiaj](https://github.com/kunickiaj) in [#31225](https://github.com/backstage/backstage/pull/31225). + +### New Frontend System: Entity summary card type removed + +The `summary` type has been removed from `EntityCardBlueprint`. This card type saw little use and feedback mostly pointed to it being confusing. Existing usage is encouraged to move to a `content` type card with a list of smaller cards instead. + +### New Frontend System: Removed multiple attachment points + +The ability for extensions to have multiple attachment points was introduced a couple of releases back in order to support use-cases where extensibility was needed across multiple extensions in a plugin. For example TechDocs addons needed to be available both at the top-level reader page as well as the reader installed as entity content. + +After observing this pattern in the wild, we concluded that it introduced more complexity than what it was worth, and support for multiple extension points has been removed. + +The alternative pattern, which is now used throughout the core feature plugins, is to use a Utility API to collect and share extensions. This pattern is now [documented in the architecture section](https://backstage.io/docs/next/frontend-system/architecture/extensions#sharing-extensions-across-multiple-locations) for the new frontend system. + +### New `@backstage/filter-predicates` Package + +Extracted predicate types from `@backstage/plugin-catalog-react/alpha` into a standalone package for general reuse. + +### New Backstage UI Components + Fixes + +New `Alert` component with support for status variants, icons, loading states, and custom actions. + +New `FullPage` component that fills the remaining viewport height below the `PluginHeader`. + +Added a `destructive` prop to the Button component for dangerous actions like delete or remove, working with all button variants. + +The `useTable` hook now accepts a direct `data` prop for mode `"complete"` instead of requiring `getData()`, reducing flickering when data changes. + +Contributed by [@grantila](https://github.com/grantila) in [#32685](https://github.com/backstage/backstage/pull/32685). + +### Experimental Catalog generic SCM event handling + +There’s a new experimental `catalogScmEventsServiceRef` in town. It’s a communication hub where event providers for example based on webhooks can feed distilled source code provider events into the catalog, where it can react in a generic way to them. This can enable instant refreshes of entities after you press Merge, automatic unregistration of locations when you decommission a repository, and much more. + +See [this issue](https://github.com/backstage/backstage/issues/32833) for more information on how you can contribute to fleshing out this capability! + +### Module Federation enabled by default + +Apps built with `@backstage/cli` now have module federation enabled by default without additional overhead. This is achieved by relying on the runtime APIs rather than introducing it at build-time. See the new [module federation section](https://backstage.io/docs/frontend-system/building-apps/module-federation) for more details and instructions for how to use this in your own app. Note that as part of this change the `EXPERIMENTAL_MODULE_FEDERATION` flag has been removed. + +Contributed by [@davidfestal](https://github.com/davidfestal) in [#30967](https://github.com/backstage/backstage/pull/30967). + +### Azure Database Microsoft Entra Authentication + +Added `connection.type: azure` in the database client config for Microsoft Entra authentication with Azure Database for PostgreSQL. + +Contributed by [@arjan07](https://github.com/arjan07) in [#31855](https://github.com/backstage/backstage/pull/31855). + +## Security Fixes + +This release does not contain any security fixes. + +## Upgrade path + +We recommend that you keep your Backstage project up to date with this latest release. For more guidance on how to upgrade, check out the documentation for [keeping Backstage updated](https://backstage.io/docs/getting-started/keeping-backstage-updated). + +## Links and References + +Below you can find a list of links and references to help you learn about and start using this new release. + +- [Backstage official website](https://backstage.io/), [documentation](https://backstage.io/docs/), and [getting started guide](https://backstage.io/docs/getting-started/) +- [GitHub repository](https://github.com/backstage/backstage) +- Backstage's [versioning and support policy](https://backstage.io/docs/overview/versioning-policy) +- [Community Discord](https://discord.gg/backstage-687207715902193673) for discussions and support +- [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.48.0-changelog.md) +- Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) + +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.49.0-changelog.md b/docs/releases/v1.49.0-changelog.md new file mode 100644 index 0000000000..f3c2bef2e7 --- /dev/null +++ b/docs/releases/v1.49.0-changelog.md @@ -0,0 +1,3330 @@ +# Release v1.49.0 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.49.0](https://backstage.github.io/upgrade-helper/?to=1.49.0) + +## @backstage/integration@2.0.0 + +### Major Changes + +- 527cf88: **BREAKING** Removed deprecated Azure DevOps, Bitbucket, Gerrit and GitHub code: + + - For Azure DevOps, the long deprecated `token` string and `credential` object have been removed from the `config.d.ts`. Use the `credentials` array object instead. + - For Bitbucket, the long deprecated `bitbucket` object has been removed from the `config.d.ts`. Use the `bitbucketCloud` or `bitbucketServer` objects instead. + - For Gerrit, the `parseGerritGitilesUrl` function has been removed, use `parseGitilesUrlRef` instead. The `buildGerritGitilesArchiveUrl` function has also been removed, use `buildGerritGitilesArchiveUrlFromLocation` instead. + - For GitHub, the `getGitHubRequestOptions` function has been removed. + +### Minor Changes + +- d933f62: Add configurable throttling and retry mechanism for GitLab integration. + +### Patch Changes + +- 1513a0b: Fixed a security vulnerability where path traversal sequences in SCM URLs could be used to access unintended API endpoints using server-side integration credentials. +- 993a598: Fixed Azure integration config schema visibility annotations to use per-field `@visibility secret` instead of `@deepVisibility secret` on parent objects, so that non-secret fields like `clientId`, `tenantId`, `organizations`, and `managedIdentityClientId` are no longer incorrectly marked as secret. + +## @backstage/plugin-catalog@2.0.0 + +### Major Changes + +- 5fc35bb: Migrated `EntityAboutCard`, `EntityLinksCard`, `EntityLabelsCard`, `GroupProfileCard`, and `UserProfileCard` from MUI/InfoCard to use the new BUI card layout and BUI components where possible. + + **BREAKING**: Removed `variant` prop from EntityAboutCard, EntityUserProfileCard, EntityGroupProfileCard, EntityLabelsCard, EntityLinksCard. Removed `gridSizes` prop from `AboutField`. + + **Migration:** + + Simply delete the obsolete `variant` and `gridSizes` props, e.g: + + ```diff + - + + + ``` + + ```diff + - + + + ``` + +### Minor Changes + +- 4d58894: Added support for group alias IDs and configurable content ordering on the entity page. Groups can now declare `aliases` so that content targeting an aliased group is included in the group. A new `defaultContentOrder` option (default `title`) controls how content items within each group are sorted, with support for both a page-level default and per-group overrides. + +- c548a0f: Migrated entity table cards (`HasComponentsCard`, `HasResourcesCard`, `HasSubcomponentsCard`, `HasSubdomainsCard`, `HasSystemsCard`, `DependsOnComponentsCard`, `DependsOnResourcesCard`, `DependencyOfComponentsCard`) to use BUI when no legacy props are passed. The old `variant`, `columns`, and `tableOptions` props are deprecated but still supported — passing any of them triggers the legacy MUI-based rendering. The new `columnConfig` prop accepts `EntityColumnConfig[]` for BUI-based rendering. + + `RelatedEntitiesCard` is deprecated — use `EntityRelationCard` from `@backstage/plugin-catalog-react/alpha` instead. + +- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point. + +### Patch Changes + +- 538c985: Updated installation documentation to use feature discovery as the default. +- 07ba746: Fixed entity page tab groups not respecting the ordering from the `groups` configuration. +- aa29b50: New frontend system pages now use the default plugin header together with `HeaderPage` instead of the legacy core page header pattern. +- 3f36ce1: Updated alpha plugin icons to follow the new frontend icon sizing rules when rendered in plugin and navigation surfaces. +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/ui@0.13.0 + - @backstage/core-compat-api@0.5.9 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/plugin-search-react@1.11.0 + - @backstage/catalog-client@1.14.0 + - @backstage/plugin-permission-react@0.4.41 + - @backstage/plugin-scaffolder-common@2.0.0 + - @backstage/catalog-model@1.7.7 + - @backstage/integration-react@1.2.16 + - @backstage/plugin-techdocs-react@1.3.9 + +## @backstage/plugin-scaffolder-common@2.0.0 + +### Major Changes + +- 527cf88: **BREAKING** Removed deprecated `bitbucket` integration from being registered in the `ScaffolderClient`. Use the `bitbucketCloud` or `bitbucketServer` integrations instead. + +### Minor Changes + +- f598909: **BREAKING PRODUCERS**: Made `retry`, `listTasks`, `listTemplatingExtensions`, `dryRun`, and `autocomplete` required methods on the `ScaffolderApi` interface. Implementations of `ScaffolderApi` must now provide these methods. +- e8736ea: Added an optional `secrets` field to `TemplateEntityV1beta3` for configuring secrets validation. The schema for validating secrets is defined under `secrets.schema` as a JSON Schema object. + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/catalog-model@1.7.7 + +## @backstage/backend-app-api@1.6.0 + +### Minor Changes + +- 545557a: Registration errors should be forwarded as BackendStartupResult + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + +## @backstage/backend-defaults@0.16.0 + +### Minor Changes + +- 42960f1: The actions registry invoke endpoint now accepts direct user credentials in addition to service principals, enabling CLI and other direct user clients to invoke actions. +- 0e7d8f9: The scheduler service now uses the metrics service to create metrics, providing plugin-scoped attribution. +- 527cf88: **BREAKING** Removed deprecated `BitbucketUrlReader`. Use the `BitbucketCloudUrlReader` or the `BitbucketServerUrlReader` instead. + +### Patch Changes + +- cc8348e: Added permissions integration to the actions registry. Actions registered with a `visibilityPermission` field are now checked against the permissions framework when listing and invoking. Denied actions are filtered from list results, and invoking a denied action returns a `404 Not Found` as if the action does not exist. Permissions are automatically registered with the `PermissionsRegistryService` so they appear in the permission policy system. +- dee4283: Added `pluginId` field to `ActionsServiceAction` type, populated from the registering plugin's metadata. +- 015668c: Added `cancelTask` method to the `SchedulerService` interface and implementation, allowing cancellation of currently running scheduled tasks. For global tasks, the database lock is released and a periodic liveness check aborts the running task function. For local tasks, the task's abort signal is triggered directly. A new `POST /.backstage/scheduler/v1/tasks/:id/cancel` endpoint is also available. +- 638e6c7: chore(deps): bump `yauzl` from 3.2.0 to 3.2.1 +- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1 +- 62f0a53: Fixed error forwarding in the actions registry so that known errors like `InputError` and `NotFoundError` thrown by actions preserve their original status codes and messages instead of being wrapped in `ForwardedError` and coerced to 500. +- d933f62: Add configurable throttling and retry mechanism for GitLab integration. +- b99158a: Fixed `yarn backstage-cli config:check --strict --config app-config.yaml` config validation error by adding + an optional `default` type discriminator to PostgreSQL connection configuration, + allowing `config:check` to properly validate `default` connection configurations. +- 1ee5b28: Adds an alpha `MetricsService` to provide a unified interface for metrics instrumentation across Backstage plugins. +- 5fcbef2: Updated dependency `express-rate-limit` to `^8.0.0`. +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/cli-node@0.3.0 + - @backstage/integration@2.0.0 + - @backstage/config-loader@1.10.9 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/plugin-permission-node@0.10.11 + - @backstage/plugin-auth-node@0.6.14 + - @backstage/backend-app-api@1.6.0 + - @backstage/plugin-events-node@0.4.20 + +## @backstage/backend-dynamic-feature-service@0.8.0 + +### Minor Changes + +- 0fbcf23: Migrated OpenAPI schemas to 3.1. + +### Patch Changes + +- 4074a22: Fixed `resolvePackagePath` resolution for bundled dynamic plugins. When a plugin bundles its own copy of `@backstage/backend-plugin-api` inside `node_modules`, the `CommonJSModuleLoader` fallback now correctly resolves the plugin's `package.json` by name. Previously the fallback only applied when the resolution originated from the host application; it now also applies when originating from a bundled dependency, which is the case for plugins produced by the `backstage-cli package bundle` command. +- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/backend-defaults@0.16.0 + - @backstage/cli-node@0.3.0 + - @backstage/plugin-catalog-backend@3.5.0 + - @backstage/cli-common@0.2.0 + - @backstage/plugin-scaffolder-node@0.13.0 + - @backstage/plugin-events-backend@0.6.0 + - @backstage/config-loader@1.10.9 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/plugin-permission-node@0.10.11 + - @backstage/plugin-auth-node@0.6.14 + - @backstage/backend-openapi-utils@0.6.7 + - @backstage/plugin-app-node@0.1.43 + - @backstage/plugin-events-node@0.4.20 + - @backstage/plugin-search-backend-node@1.4.2 + +## @backstage/backend-plugin-api@1.8.0 + +### Minor Changes + +- cc8348e: Added optional `visibilityPermission` field to `ActionsRegistryActionOptions`, allowing actions to declare a `BasicPermission` that controls visibility and access. + + ```typescript + import { createPermission } from '@backstage/plugin-permission-common'; + + const myPermission = createPermission({ + name: 'myPlugin.myAction.use', + attributes: {}, + }); + + actionsRegistry.register({ + name: 'my-action', + title: 'My Action', + description: 'An action that requires permission', + visibilityPermission: myPermission, + schema: { + input: z => z.object({ name: z.string() }), + output: z => z.object({ ok: z.boolean() }), + }, + action: async ({ input }) => { + return { output: { ok: true } }; + }, + }); + ``` + + Actions without a `visibilityPermission` field continue to work as before. + +- 015668c: Added `cancelTask` method to the `SchedulerService` interface and implementation, allowing cancellation of currently running scheduled tasks. For global tasks, the database lock is released and a periodic liveness check aborts the running task function. For local tasks, the task's abort signal is triggered directly. A new `POST /.backstage/scheduler/v1/tasks/:id/cancel` endpoint is also available. + +### Patch Changes + +- dee4283: Added `pluginId` field to `ActionsServiceAction` type, populated from the registering plugin's metadata. +- 1ee5b28: Adds an alpha `MetricsService` to provide a unified interface for metrics instrumentation across Backstage plugins. +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/cli-common@0.2.0 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/plugin-permission-node@0.10.11 + - @backstage/plugin-auth-node@0.6.14 + +## @backstage/catalog-client@1.14.0 + +### Minor Changes + +- 972f686: Added support for the `query` field in `getEntitiesByRefs` requests, enabling predicate-based filtering with `$all`, `$any`, `$not`, `$exists`, `$in`, `$contains`, and `$hasPrefix` operators. +- 5d95e8e: Add an `onConflict` option to location creation that can refresh an existing location instead of throwing a conflict error. +- 56c908e: Added support for the `query` field in `getEntityFacets` requests, enabling predicate-based filtering with `$all`, `$any`, `$not`, `$exists`, `$in`, `$contains`, and `$hasPrefix` operators. +- 0fbcf23: Migrated OpenAPI schemas to 3.1. +- 51e23eb: Added predicate-based entity filtering via POST /entities/by-query endpoint. + + Supports `$all`, `$any`, `$not`, `$exists`, `$in`, `$hasPrefix`, and (partially) `$contains` operators for expressive entity queries. Integrated into the existing `queryEntities` flow with full cursor-based pagination, permission enforcement, and `totalItems` support. + + The catalog client's `queryEntities()` method automatically routes to the POST endpoint when a `query` predicate is provided. + +### Patch Changes + +- d2494d6: Minor update to catalog client docs +- Updated dependencies + - @backstage/filter-predicates@0.1.1 + - @backstage/catalog-model@1.7.7 + +## @backstage/cli@0.36.0 + +### Minor Changes + +- d0f4cd2: Added new `auth` command group for authenticating the CLI with Backstage instances using OAuth 2.0 with a pre-registered client metadata document. Commands include `login`, `logout`, `list`, `show`, `print-token`, and `select` for managing multiple authenticated instances. + +- d806b0c: The CLI now automatically discovers CLI modules from the project root's `dependencies` and `devDependencies`. Any installed package with the `cli-module` Backstage role will be loaded automatically without needing to be hardcoded in the CLI itself. + + If no CLI modules are found in the project dependencies, the CLI falls back to the built-in set of modules and prints a deprecation warning. This fallback will be removed in a future release. To prepare for this, add `@backstage/cli-defaults` as a `devDependency` in your root `package.json`: + + ```json + { + "devDependencies": { + "@backstage/cli-defaults": "backstage:^" + } + } + ``` + + If you are not using the Backstage Yarn plugin, run the following instead: + + ```sh + yarn workspace root add --dev @backstage/cli-defaults + ``` + + For fine-grained control you can instead install individual CLI modules: + + ```json + { + "devDependencies": { + "@backstage/cli-module-auth": "backstage:^", + "@backstage/cli-module-build": "backstage:^", + "@backstage/cli-module-config": "backstage:^", + "@backstage/cli-module-github": "backstage:^", + "@backstage/cli-module-info": "backstage:^", + "@backstage/cli-module-lint": "backstage:^", + "@backstage/cli-module-maintenance": "backstage:^", + "@backstage/cli-module-migrate": "backstage:^", + "@backstage/cli-module-new": "backstage:^", + "@backstage/cli-module-test-jest": "backstage:^", + "@backstage/cli-module-translations": "backstage:^" + } + } + ``` + +- 08d9770: **BREAKING**: The CLI templates for frontend plugins have been renamed: + + - `new-frontend-plugin` → `frontend-plugin` + - `new-frontend-plugin-module` → `frontend-plugin-module` + - `frontend-plugin` (legacy) → `legacy-frontend-plugin` + + To smooth out this breaking change, the CLI now auto-detects which frontend system your app uses based on the dependencies in `packages/app/package.json`. When using the default templates (no explicit `templates` configuration): + + - Apps using `@backstage/frontend-defaults` will see the new frontend system templates (`frontend-plugin`, `frontend-plugin-module`) + - Apps using `@backstage/app-defaults` will see the legacy template (displayed as `frontend-plugin`) + + This means existing projects that haven't migrated to the new frontend system will continue to create legacy plugins by default, while new projects will get the new frontend system templates. If you have explicit template configuration in your `package.json`, it will be used as-is without any auto-detection. + +- b36a60d: **BREAKING**: The `migrate package-exports` command has been removed. Use `repo fix` instead. + +### Patch Changes + +- edf2b77: Added a new `cli-module` template for creating CLI module packages. + +- 246877a: Updated dependency `bfj` to `^9.0.2`. + +- 0d2d0f2: Internal refactor of CLI modularization, moving individual commands to be implemented with cleye. + +- a4e5902: Internal refactor of the CLI command registration + +- bba2e49: Internal refactor to use new concurrency utilities from `@backstage/cli-node`. + +- 2fcba39: Internal refactor to move shared utilities into their consuming modules, reducing cross-module dependencies. + +- c85ac86: Internal refactor to split `loadCliConfig` into separate implementations for the build and config CLI modules, removing a cross-module dependency. + +- 94a885a: Added support for the new `cli-module` package role in the build system, ESLint configuration, Jest configuration, and maintenance commands. + +- fd50cb3: Added `translations export` and `translations import` commands for managing translation files. + + The `translations export` command discovers all `TranslationRef` definitions across frontend plugin dependencies and exports their default messages as JSON files. The `translations import` command generates `TranslationResource` wiring code from translated JSON files, ready to be plugged into the app. + + Both commands support a `--pattern` option for controlling the message file layout, for example `--pattern '{lang}/{id}.json'` for language-based directory grouping. + +- 0be3eab: Migrated CLI plugin modules to use `createCliModule` from `@backstage/cli-node`. + +- 61cb976: Migrated internal versioning utilities to use `@backstage/cli-node` instead of a local implementation. + +- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1 + +- ff4a45a: Migrated remaining CLI command handlers from `commander` to `cleye` for argument parsing. Several camelCase CLI flags have been deprecated in favor of their kebab-case equivalents (e.g. `--successCache` → `--success-cache`). The old camelCase forms still work but will now log a deprecation warning. Please update any scripts or CI configurations to use the kebab-case versions. + +- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`. + +- 825c81d: Internal refactor of CLI command modules. + +- ea90ab0: The built-in `yarn new` templates have been moved to `@backstage/cli-module-new`. Existing references to `@backstage/cli/templates/*` in your root `package.json` will continue to work through a backwards compatibility rewrite in the `new` command. + +- ebeb0d4: Updated the new frontend plugin template to use `@backstage/frontend-dev-utils` in its `dev/` entry point instead of wiring `createApp` manually. Generated plugins now get the same dev app helper setup as the built-in examples. + +- 971cc94: The `new` command now prompts for the plugin package name when creating plugin modules, in order to properly populate the `package.json` file. + +- de62a9d: Upgraded `commander` dependency from `^12.0.0` to `^14.0.3` across all CLI packages. + +- 092b41f: Updated dependency `webpack` to `~5.105.0`. + +- 4a75544: Updated dependency `react-refresh` to `^0.18.0`. + +- a9d23c4: Properly support `package.json` `workspaces` field + +- Updated dependencies + - @backstage/cli-node@0.3.0 + - @backstage/cli-common@0.2.0 + - @backstage/cli-defaults@0.1.0 + - @backstage/cli-module-build@0.1.0 + - @backstage/eslint-plugin@0.2.2 + - @backstage/cli-module-test-jest@0.1.0 + +## @backstage/cli-common@0.2.0 + +### Minor Changes + +- 56bd494: Added `targetPaths` and `findOwnPaths` as replacements for `findPaths`, with a cleaner separation between target project paths and package-relative paths. + + To migrate existing `findPaths` usage: + + ```ts + // Before + import { findPaths } from '@backstage/cli-common'; + const paths = findPaths(__dirname); + + // After — for target project paths (cwd-based): + import { targetPaths } from '@backstage/cli-common'; + // paths.targetDir → targetPaths.dir + // paths.targetRoot → targetPaths.rootDir + // paths.resolveTarget('src') → targetPaths.resolve('src') + // paths.resolveTargetRoot('yarn.lock') → targetPaths.resolveRoot('yarn.lock') + + // After — for package-relative paths: + import { findOwnPaths } from '@backstage/cli-common'; + const own = findOwnPaths(__dirname); + // paths.ownDir → own.dir + // paths.ownRoot → own.rootDir + // paths.resolveOwn('config/jest.js') → own.resolve('config/jest.js') + // paths.resolveOwnRoot('tsconfig.json') → own.resolveRoot('tsconfig.json') + ``` + +### Patch Changes + +- e44b6a9: The `findOwnRootDir` utility now searches for the monorepo root by traversing up the directory tree looking for a `package.json` with `workspaces`, instead of assuming a fixed `../..` relative path. If no workspaces root is found during this traversal, `findOwnRootDir` now throws to enforce stricter validation of the repository layout. +- 9361965: Fixed `runCheck` to ignore stdio of the spawned process, preventing unwanted output from leaking to the terminal. + +## @backstage/cli-defaults@0.1.0 + +### Minor Changes + +- 7781ae5: Introduced `@backstage/cli-defaults`, a convenience package that bundles all standard Backstage CLI modules. Install this single package as a `devDependency` to get the full default set of CLI commands without listing each module individually. + +### Patch Changes + +- 42960f1: Added `@backstage/cli-module-actions` to the default set of CLI modules. +- Updated dependencies + - @backstage/cli-module-actions@0.0.1 + - @backstage/cli-module-new@0.1.0 + - @backstage/cli-module-maintenance@0.1.0 + - @backstage/cli-module-build@0.1.0 + - @backstage/cli-module-auth@0.1.0 + - @backstage/cli-module-config@0.1.0 + - @backstage/cli-module-github@0.1.0 + - @backstage/cli-module-info@0.1.0 + - @backstage/cli-module-lint@0.1.0 + - @backstage/cli-module-migrate@0.1.0 + - @backstage/cli-module-test-jest@0.1.0 + - @backstage/cli-module-translations@0.1.0 + +## @backstage/cli-module-auth@0.1.0 + +### Minor Changes + +- 329f394: Initial release of the CLI module packages. Each module provides a set of commands that can be discovered automatically by `@backstage/cli` or executed standalone. + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/cli-node@0.3.0 + +## @backstage/cli-module-build@0.1.0 + +### Minor Changes + +- 62d0849: Added `package bundle` command to create self-contained plugin bundles for dynamic loading, to be used by the `backend-dynamic-feature-service`. Supports backend and frontend plugins, with optional `--pre-packed-dir` for batch bundling from a pre-built workspace. +- 329f394: Initial release of the CLI module packages. Each module provides a set of commands that can be discovered automatically by `@backstage/cli` or executed standalone. + +### Patch Changes + +- Updated dependencies + - @backstage/cli-node@0.3.0 + - @backstage/cli-common@0.2.0 + - @backstage/module-federation-common@0.1.2 + - @backstage/config-loader@1.10.9 + +## @backstage/cli-module-config@0.1.0 + +### Minor Changes + +- 329f394: Initial release of the CLI module packages. Each module provides a set of commands that can be discovered automatically by `@backstage/cli` or executed standalone. + +### Patch Changes + +- Updated dependencies + - @backstage/cli-node@0.3.0 + - @backstage/cli-common@0.2.0 + - @backstage/config-loader@1.10.9 + +## @backstage/cli-module-github@0.1.0 + +### Minor Changes + +- 329f394: Initial release of the CLI module packages. Each module provides a set of commands that can be discovered automatically by `@backstage/cli` or executed standalone. + +### Patch Changes + +- Updated dependencies + - @backstage/cli-node@0.3.0 + - @backstage/cli-common@0.2.0 + +## @backstage/cli-module-info@0.1.0 + +### Minor Changes + +- 329f394: Initial release of the CLI module packages. Each module provides a set of commands that can be discovered automatically by `@backstage/cli` or executed standalone. + +### Patch Changes + +- Updated dependencies + - @backstage/cli-node@0.3.0 + - @backstage/cli-common@0.2.0 + +## @backstage/cli-module-lint@0.1.0 + +### Minor Changes + +- 329f394: Initial release of the CLI module packages. Each module provides a set of commands that can be discovered automatically by `@backstage/cli` or executed standalone. + +### Patch Changes + +- Updated dependencies + - @backstage/cli-node@0.3.0 + - @backstage/cli-common@0.2.0 + +## @backstage/cli-module-maintenance@0.1.0 + +### Minor Changes + +- 329f394: Initial release of the CLI module packages. Each module provides a set of commands that can be discovered automatically by `@backstage/cli` or executed standalone. + +### Patch Changes + +- f189d8f: Added auto-fill of `backstage.pluginPackage` metadata for known plugins during `repo fix`. +- Updated dependencies + - @backstage/cli-node@0.3.0 + - @backstage/cli-common@0.2.0 + +## @backstage/cli-module-migrate@0.1.0 + +### Minor Changes + +- 329f394: Initial release of the CLI module packages. Each module provides a set of commands that can be discovered automatically by `@backstage/cli` or executed standalone. + +### Patch Changes + +- Updated dependencies + - @backstage/cli-node@0.3.0 + - @backstage/cli-common@0.2.0 + +## @backstage/cli-module-new@0.1.0 + +### Minor Changes + +- 329f394: Initial release of the CLI module packages. Each module provides a set of commands that can be discovered automatically by `@backstage/cli` or executed standalone. + +### Patch Changes + +- edf2b77: Added support for the `cli-module` template role for scaffolding new CLI module packages. +- ea90ab0: The built-in `yarn new` templates have been moved to this package from `@backstage/cli`. The default template references have been updated from `@backstage/cli/templates/*` to `@backstage/cli-module-new/templates/*`. Existing references to `@backstage/cli/templates/*` in your root `package.json` will continue to work through a backwards compatibility rewrite. +- ebeb0d4: Updated the new frontend plugin template to use `@backstage/frontend-dev-utils` in its `dev/` entry point instead of wiring `createApp` manually. Generated plugins now get the same dev app helper setup as the built-in examples. +- 971cc94: The `new` command now prompts for the plugin package name when creating plugin modules, in order to properly populate the `package.json` file. +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/cli-node@0.3.0 + - @backstage/cli-common@0.2.0 + +## @backstage/cli-module-test-jest@0.1.0 + +### Minor Changes + +- 329f394: Initial release of the CLI module packages. Each module provides a set of commands that can be discovered automatically by `@backstage/cli` or executed standalone. + +### Patch Changes + +- Updated dependencies + - @backstage/cli-node@0.3.0 + - @backstage/cli-common@0.2.0 + +## @backstage/cli-module-translations@0.1.0 + +### Minor Changes + +- 329f394: Initial release of the CLI module packages. Each module provides a set of commands that can be discovered automatically by `@backstage/cli` or executed standalone. + +### Patch Changes + +- Updated dependencies + - @backstage/cli-node@0.3.0 + - @backstage/cli-common@0.2.0 + +## @backstage/cli-node@0.3.0 + +### Minor Changes + +- 7d055ef: Added `createCliModule` API and related types for building Backstage CLI plugins. + +### Patch Changes + +- 94a885a: Added a new `cli-module` package role for packages that provide CLI plugin extensions. +- 12fa965: Added `CliAuth` class for managing CLI authentication state. This provides a class-based API with a static `create` method that resolves the currently selected (or explicitly named) auth instance, transparently refreshes expired access tokens, and exposes helpers for other CLI modules to authenticate with a Backstage backend. +- 61cb976: Added `toString()` method to `Lockfile` for serializing lockfiles back to string format. +- 06c2015: Added `runConcurrentTasks` and `runWorkerQueueThreads` utilities, moved from the `@backstage/cli` internal code. +- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`. +- 3c811bf: Added `hasBackstageYarnPlugin` and `SuccessCache` exports, moved from `@backstage/cli`. +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- a9d23c4: Properly support `package.json` `workspaces` field +- Updated dependencies + - @backstage/cli-common@0.2.0 + +## @backstage/create-app@0.8.0 + +### Minor Changes + +- a6735c3: **BREAKING**: The new frontend system is now the default template when creating a new Backstage app. The previous `--next` flag has been replaced with a `--legacy` flag that can be used to create an app using the old frontend system instead. + +### Patch Changes + +- d806b0c: The create-app templates now include `@backstage/cli-defaults` as a `devDependency`, enabling the CLI's automatic module discovery for newly created projects. + +- d14b6e0: **BREAKING**: Migrated `MembersListCard`, `OwnershipCard`, and `CatalogGraphCard` to use BUI card primitives via `EntityInfoCard`. + + - `OwnershipCard`: Removed `variant` and `maxScrollHeight` props. Card height and scrolling are now controlled by the parent container — the card fills its container and the body scrolls automatically when content overflows. + - `CatalogGraphCard`: Removed `variant` prop. + - `MembersListCard`: Translation keys `subtitle`, `paginationLabel`, `aggregateMembersToggle.directMembers`, `aggregateMembersToggle.aggregatedMembers`, and `aggregateMembersToggle.ariaLabel` have been removed. The `title` key now includes `{{groupName}}`. New keys added: `cardLabel`, `noSearchResult`, `aggregateMembersToggle.label`. + - `OwnershipCard`: Translation keys `aggregateRelationsToggle.directRelations`, `aggregateRelationsToggle.aggregatedRelations`, and `aggregateRelationsToggle.ariaLabel` have been removed. New key added: `aggregateRelationsToggle.label`. + - Removed `MemberComponentClassKey` export, and `root` and `cardContent` from `MembersListCardClassKey`, `card` from `OwnershipCardClassKey`, and `card` from `CatalogGraphCardClassKey`. + + **Migration:** + + ```diff + - + + + ``` + + ```diff + - + + + ``` + +- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`. + +- ea90ab0: Updated the `next-app` template to reference `@backstage/cli-module-new/templates/*` instead of `@backstage/cli/templates/*` for the built-in `yarn new` templates. + +- de62a9d: Upgraded `commander` dependency from `^12.0.0` to `^14.0.3` across all CLI packages. + +- a9d23c4: Properly support `package.json` `workspaces` field + +- ebd4630: Replace deprecated `workspaces.packages` with `workspaces` in `package.json` + + This change is **not** required, but you can edit your main `package.json`, to fit the more modern & more common pattern: + + ```diff + - "workspaces": { + - "packages": [ + "workspaces": [ + "packages/*", + "plugins/*" + - ] + - }, + ], + ``` + +- Updated dependencies + - @backstage/cli-common@0.2.0 + +## @backstage/frontend-app-api@0.16.0 + +### Minor Changes + +- 92af1ae: **BREAKING**: Removed the `allowUnknownExtensionConfig` option from `createSpecializedApp`. This flag had no effect and was a no-op, so no behavioral changes are expected. + +### Patch Changes + +- d911b72: Frontend apps now respect an explicit `pluginId` on `ApiRef`s when deciding which plugin owns an API factory. +- 0452d02: Add optional `description` field to plugin-level feature flags. +- 5fd78ba: Removed `@backstage/core-plugin-api` leakage from the public API surface. All types such as `ApiHolder` and `ConfigApi` are now imported from `@backstage/frontend-plugin-api`. +- dab6c46: Added the `ExtensionFactoryMiddleware` type as a public export. +- 3f36ce1: Clarified the `IconElement` sizing contract for the new frontend system and aligned legacy system icon rendering with the new icon API. +- 5b160f9: Added `prepareSpecializedApp` for two-phase app wiring so apps can render a bootstrap tree before full app finalization. The bootstrap phase now supports deferred `app/root.elements`, predicate-gated APIs, reusable `sessionState`, and warnings for bootstrap-visible predicates or bootstrap code that accessed APIs that only became available after finalization. Utility APIs that are materialized during bootstrap are also frozen for the lifetime of the app instance, causing deferred overrides of those APIs to be ignored and reported as app errors. +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/core-plugin-api@1.12.4 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/core-app-api@1.19.6 + - @backstage/frontend-defaults@0.5.0 + - @backstage/filter-predicates@0.1.1 + +## @backstage/frontend-defaults@0.5.0 + +### Minor Changes + +- 92af1ae: **BREAKING**: Removed the `allowUnknownExtensionConfig` option from `createApp`. This flag had no effect and was a no-op, so no behavioral changes are expected. +- 33de79d: **BREAKING**: Removed the deprecated `createPublicSignInApp` function. Use `createApp` from `@backstage/frontend-defaults` with `appModulePublicSignIn` from `@backstage/plugin-app/alpha` instead. + +### Patch Changes + +- 5b160f9: Updated `createApp` to use the phased `prepareSpecializedApp` flow, allowing apps to render a bootstrap tree before the full app is finalized. +- Updated dependencies + - @backstage/frontend-app-api@0.16.0 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/plugin-app@0.4.1 + +## @backstage/frontend-dev-utils@0.1.0 + +### Minor Changes + +- c25532a: Added `@backstage/frontend-dev-utils`, a new package that provides a minimal helper for wiring up a development app for frontend plugins using the new frontend system. It exports a `createDevApp` function that handles creating and rendering a development app from a `dev/` entry point. The dev app automatically bypasses the sign-in page and loads the `@backstage/ui` CSS. The options interface accepts `features` together with route bindings through `bindRoutes`. + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.13.0 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/plugin-app@0.4.1 + - @backstage/frontend-defaults@0.5.0 + +## @backstage/frontend-plugin-api@0.15.0 + +### Minor Changes + +- 5fd78ba: Renamed `PluginOptions` to `CreateFrontendPluginOptions` and deprecated the old name. Removed `ResolvedExtensionInputs` from the main entry point; it is still available as an inline type in extension factory signatures. + +- 72991a5: Removed the `ResolvedExtensionInput` and `ExtensionDataRefToValue` helper types from the public API surface to reduce top-level API clutter. These types were internal plumbing that are not needed by plugin authors. If you were relying on `ResolvedExtensionInput`, use the `ResolvedExtensionInputs` type instead, which maps a full set of inputs. If you were using `ExtensionDataRefToValue`, replace it with `ExtensionDataValue` combined with inferred types from your `ExtensionDataRef`. + +- 9508514: **BREAKING**: Promoted `PluginWrapperApi`, `pluginWrapperApiRef`, `PluginWrapperBlueprint`, and the new `PluginWrapperDefinition` type from `@alpha` to `@public`. These are now available from the main package entry point rather than only through `/alpha`. + + The `PluginWrapperApi` type now has a required `getRootWrapper()` method that returns a root wrapper component. The `pluginWrapperApiRef` ID changed from `core.plugin-wrapper.alpha` to `core.plugin-wrapper`. + + The `PluginWrapperBlueprint` now accepts `PluginWrapperDefinition` as the loader return type, which supports an optional `useWrapperValue` hook that allows sharing state between wrapper instances. + +- 6573901: **BREAKING**: Removed the deprecated `AnyExtensionDataRef` type. Use `ExtensionDataRef` without type parameters instead. + +- a9440f0: **BREAKING**: Simplified the `ExtensionAttachTo` type to only support a single attachment target. The array form for attaching to multiple extension points has been removed. Also removed the deprecated `ExtensionAttachToSpec` type alias. + +### Patch Changes + +- e26e3de: The `icon` field on `AuthProviderInfo` now accepts `IconElement` in addition to `IconComponent`, letting you pass `` instead of `MyIcon`. + +- eea95b8: Deprecated `AlertApi` in favor of the new `ToastApi`. + + `AlertApi` is now deprecated and will be removed in a future release. Please migrate to `ToastApi` which provides richer notification features. + + **Why migrate?** + + `ToastApi` offers enhanced capabilities over `AlertApi`: + + - **Title and Description**: Display a prominent title with optional description text + - **Action Links**: Include clickable links within notifications + - **Status Variants**: Support for neutral, info, success, warning, and danger statuses + - **Per-toast Timeout**: Control auto-dismiss timing for each notification individually + - **Programmatic Dismiss**: Close notifications via the `close()` handle returned from `post()` + + **Migration Guide** + + | AlertApi | ToastApi | + | -------------------------------------------- | ------------------------------------------ | + | `message: string` | `title: ReactNode` | + | `severity: 'error'` | `status: 'danger'` | + | `severity: 'success' \| 'info' \| 'warning'` | `status: 'success' \| 'info' \| 'warning'` | + | `display: 'transient'` | `timeout: 5000` (or custom ms) | + | `display: 'permanent'` | omit `timeout` | + | `post()` returns `void` | `post()` returns `{ close(): void }` | + + **Example Migration** + + ```typescript + // Before (AlertApi) + import { alertApiRef, useApi } from '@backstage/core-plugin-api'; + + const alertApi = useApi(alertApiRef); + alertApi.post({ + message: 'Entity saved successfully', + severity: 'success', + display: 'transient', + }); + + // After (ToastApi) + import { toastApiRef, useApi } from '@backstage/frontend-plugin-api'; + + const toastApi = useApi(toastApiRef); + const toast = toastApi.post({ + title: 'Entity saved successfully', + status: 'success', + timeout: 5000, + }); + // Later: toast.close() to dismiss programmatically + ``` + + **Note**: During the migration period, both APIs work simultaneously. The `ToastDisplay` component subscribes to both `AlertApi` and `ToastApi`, so existing code continues to work while you migrate incrementally. + +- 8a3a906: Deprecated `NavItemBlueprint`. Nav items are now automatically inferred from `PageBlueprint` extensions based on their `title` and `icon` params. + +- b15a685: Deprecated `withApis`, use the `withApis` export from `@backstage/core-compat-api` instead. + +- 0452d02: Add optional `description` field to plugin-level feature flags. + +- 1bec049: Fixed inconsistent `JSX.Element` type reference in the `DialogApiDialog.update` method signature. + +- 9c81af9: Made the `pluginId` property optional in the `FrontendFeature` type, allowing plugins published against older versions of the framework to be used without type errors. + +- 2c383b5: Deprecated `AnalyticsImplementationBlueprint` and `AnalyticsImplementationFactory` in favor of the exports from `@backstage/plugin-app-react`. + +- dab6c46: Deprecated the `ExtensionFactoryMiddleware` type, which has been moved to `@backstage/frontend-app-api`. + +- aa29b50: Pages created with `PageBlueprint` now render the plugin header by default in the new frontend system. + +- 3f36ce1: Clarified the `IconElement` sizing contract for the new frontend system and aligned legacy system icon rendering with the new icon API. + +- cc459f7: Added a builder form for `createApiRef` in the new frontend system and deprecated the direct `createApiRef({ ... })` call in favor of `createApiRef().with({ ... })`. The builder form now also preserves literal API ref IDs in the resulting `ApiRef` type. + + The `createApiRef().with({ ... })` form can also use an explicit `pluginId` to declare API ownership without encoding the plugin ID into the API ref ID, while keeping that metadata internal to runtime handling. + +- 5b160f9: Added support for `if` predicates on `createFrontendPlugin` and `createFrontendModule`, applying shared conditions to every extension in the feature. Plugin and extension overrides can now also replace or remove existing `if` predicates. + +- d0206c4: Removed the deprecated `defaultPath` migration helper from `PageBlueprint` params. + +- edb872c: Renamed the `PageTab` type to `PageLayoutTab`. The old `PageTab` name is now a deprecated type alias. + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. + +- 7e743f4: Introduced a new `ToastApi` for displaying rich toast notifications in the new frontend system. + + The new `ToastApi` provides enhanced notification capabilities compared to the existing `AlertApi`: + + - **Title and Description**: Toasts support both a title and an optional description + - **Custom Timeouts**: Each toast can specify its own timeout duration + - **Links**: Toasts can include action links + - **Status Variants**: Support for neutral, info, success, warning, and danger statuses + - **Programmatic Dismiss**: Toasts can be dismissed programmatically using the `close()` handle returned from `post()` + + **Usage:** + + ```typescript + import { toastApiRef, useApi } from '@backstage/frontend-plugin-api'; + + const toastApi = useApi(toastApiRef); + + // Full-featured toast + toastApi.post({ + title: 'Entity saved', + description: 'Your changes have been saved successfully.', + status: 'success', + timeout: 5000, + links: [{ label: 'View entity', href: '/catalog/entity' }], + }); + + // Programmatic dismiss + const { close } = toastApi.post({ title: 'Uploading...', status: 'info' }); + // Later... + close(); + ``` + + The `ToastDisplay` component subscribes to both `ToastApi` and `AlertApi`, providing a migration path where both systems work side by side until `AlertApi` is fully deprecated. + +- fe848e0: Changed `useApiHolder` to return an empty `ApiHolder` instead of throwing when used outside of an API context. + +- Updated dependencies + - @backstage/filter-predicates@0.1.1 + +## @backstage/repo-tools@0.17.0 + +### Minor Changes + +- 0fbcf23: Added support for OpenAPI 3.1 to all `schema openapi` commands. The commands now auto-detect the OpenAPI version from the spec file and use the appropriate generator, supporting both OpenAPI 3.0.x and 3.1.x specifications. + +### Patch Changes + +- 426edbe: Fixed `generate-catalog-info` command failing with "too many arguments" when invoked by lint-staged via the pre-commit hook. +- d5779e5: Updated the CLI report parser to support cleye-style help output, and strip ANSI escape codes from captured output. +- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1 +- 2a51546: Fixed prettier existence checks in OpenAPI commands to use `fs.pathExists` instead of checking the resolved path string, which was always truthy. +- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`. +- de62a9d: Upgraded `commander` dependency from `^12.0.0` to `^14.0.3` across all CLI packages. +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- 18a946c: Updated `@microsoft/api-extractor` to `7.57.3` and added tests for `getTsDocConfig` +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/cli-node@0.3.0 + - @backstage/cli-common@0.2.0 + - @backstage/config-loader@1.10.9 + - @backstage/catalog-model@1.7.7 + +## @backstage/ui@0.13.0 + +### Minor Changes + +- 768f09d: **BREAKING**: Simplified the neutral background prop API for container components. The explicit `neutral-1`, `neutral-2`, `neutral-3`, and `neutral-auto` values have been removed from `ProviderBg`. They are replaced by a single `'neutral'` value that always auto-increments from the parent context, making it impossible to skip or pin to an explicit neutral level. + + **Migration:** + + Replace any explicit `bg="neutral-1"`, `bg="neutral-2"`, `bg="neutral-3"`, or `bg="neutral-auto"` props with `bg="neutral"`. To achieve a specific neutral level in stories or tests, use nested containers — each additional `bg="neutral"` wrapper increments by one level. + + ```tsx + // Before + ... + + // After + + ... + + ``` + + **Affected components:** Box, Flex, Grid, Card, Accordion, Popover, Tooltip, Dialog, Menu + +- b42fcdc: **BREAKING**: Removed `--bui-bg-popover` CSS token. Popover, Tooltip, Menu, and Dialog now use `--bui-bg-app` for their outer shell and `Box bg="neutral-1"` for content areas, providing better theme consistency and eliminating a redundant token. + + **Migration:** + + Replace any usage of `--bui-bg-popover` with `--bui-bg-neutral-1` (for content surfaces) or `--bui-bg-app` (for outer shells): + + ```diff + - background: var(--bui-bg-popover); + + background: var(--bui-bg-neutral-1); + ``` + + **Affected components:** Popover, Tooltip, Menu, Dialog + +- bd3a76e: **BREAKING**: Data attributes rendered by components are now always lowercase. This affects CSS selectors targeting camelCase data attributes. + + **Migration:** + + Update any custom CSS selectors that target camelCase data attributes to use lowercase instead: + + ```diff + - [data-startCollapsed='true'] { ... } + + [data-startcollapsed='true'] { ... } + ``` + + **Affected components:** SearchField + +- 95702ab: **BREAKING**: Removed deprecated types `ComponentDefinition`, `ClassNamesMap`, `DataAttributeValues`, and `DataAttributesMap` from the public API. These were internal styling infrastructure types that have been replaced by the `defineComponent` system. + + **Migration:** + + Remove any direct usage of these types. Component definitions now use `defineComponent()` and their shapes are not part of the public API contract. + + ```diff + - import type { ComponentDefinition, ClassNamesMap } from '@backstage/ui'; + ``` + + If you were reading `definition.dataAttributes`, use `definition.propDefs` instead — props with `dataAttribute: true` in `propDefs` are the equivalent. + +- 42f8c9b: **BREAKING**: Centralized client-side routing in `BUIProvider`. Components like Link, ButtonLink, Tabs, Menu, TagGroup, and Table now require a `BUIProvider` rendered inside a React Router context for client-side navigation to work. + + **Migration:** + + This change requires updating `@backstage/plugin-app` and `@backstage/core-app-api` alongside `@backstage/ui`. If you only upgrade `@backstage/ui`, BUI components will fall back to full-page navigation. + + If you cannot upgrade all packages together, or if you have a custom app shell, add a `BUIProvider` inside your Router: + + ```diff + + import { BUIProvider } from '@backstage/ui'; + + + + + + + + + ``` + + **Affected components:** Link, ButtonLink, Tabs, Menu, TagGroup, Table + +- 17d6398: **BREAKING**: Renamed internal CSS classes to match the `Header` component name. + + **Migration:**: If you are targeting these classes directly in your styles, update the following: + + - `bui-HeaderPage` → `bui-Header` + - `bui-HeaderPageContent` → `bui-HeaderContent` + - `bui-HeaderPageBreadcrumbs` → `bui-HeaderBreadcrumbs` + - `bui-HeaderPageTabsWrapper` → `bui-HeaderTabsWrapper` + - `bui-HeaderPageControls` → `bui-HeaderControls` + + **Affected components:**: Header + +- 9d5f3ba: Removed redundant `selected` and `indeterminate` props from the `Checkbox` component. Use the `isSelected` and `isIndeterminate` props instead, which are the standard React Aria props and already handle both the checkbox behaviour and the corresponding CSS data attributes. + + **Migration:** + Replace any usage of the `selected` and `indeterminate` props on `Checkbox` with the `isSelected` and `isIndeterminate` props. Note that the checked state and related CSS data attributes (such as `data-selected` and `data-indeterminate`) are now driven by React Aria, so any custom logic that previously inspected or set these via the old props should instead rely on the React Aria-managed state and attributes exposed through the new props. + + **Affected components:** Checkbox + +### Patch Changes + +- 04d9d8d: Added `List` and `ListRow` components. These provide a standalone, accessible list of interactive rows built on top of React Aria's `GridList` and `GridListItem` primitives. Rows support icons, descriptions, actions, menus, and single or multiple selection modes. + + **Affected components:** List, ListRow + +- a1f4bee: Made Accordion a `bg` provider so nested components like Button auto-increment their background level. Updated `useDefinition` to resolve `bg` `propDef` defaults for provider components. + +- db92751: Added interactive support to the `Card` component. Pass `onPress` to make the entire card surface pressable, or `href` to make it navigate to a URL. A transparent overlay handles the interaction while nested buttons and links remain independently clickable. + +- 12d8afe: Added analytics capabilities to the component library. Components with navigation behavior (Link, ButtonLink, Tab, MenuItem, Tag, Row) now fire analytics events on click when a `BUIProvider` is present. + + New exports: `BUIProvider`, `useAnalytics`, `getNodeText`, and associated types (`AnalyticsTracker`, `UseAnalyticsFn`, `BUIProviderProps`, `AnalyticsEventAttributes`). + + Components with analytics support now accept a `noTrack` prop to suppress event firing. + + **Affected components:** Link, ButtonLink, Tab, MenuItem, Tag, Row + +- b838cc9: Added a `loading` prop and `data-loading` data attribute to `TableRoot`, allowing consumers to distinguish between stale data and initial loading states. Both `stale` and `loading` set `aria-busy` on the table. + + Affected components: TableRoot + +- 690786f: Improved the `Table` component loading state to show a skeleton UI with visible headers instead of plain "Loading..." text. The table now renders its full structure during loading, with animated skeleton rows in place of data. The loading state includes proper accessibility support with `aria-busy` on the table and screen reader announcements. + + Affected components: Table + +- 58224d3: Fixed neutral-1 hover & pressed state in light mode. + +- 95702ab: Migrated all components from `useStyles` to `useDefinition` hook. Exported `OwnProps` types for each component, enabling better type composition for consumers. + + **Affected components:** Avatar, Checkbox, Container, Dialog, FieldError, FieldLabel, Flex, FullPage, Grid, HeaderPage, Link, Menu, PasswordField, PluginHeader, Popover, RadioGroup, SearchField, Select, Skeleton, Switch, Table, TablePagination, Tabs, TagGroup, Text, TextField, ToggleButton, ToggleButtonGroup, Tooltip, VisuallyHidden + +- 430d5ed: Fixed interactive cards so that CardBody can scroll when the card has a constrained height. Previously, the overlay element blocked scroll events. + + **Affected components:** Card + +- 4c2c350: Removed the `transition` on `Container` padding to prevent an unwanted animation when the viewport is resized. + + Affected components: Container + +- e0b7eb0: Fixed --bui-fg-success token in light mode to be more accessible. + +- ad7c883: Deprecated the `HeaderPage` component name in favor of `Header`. The old `HeaderPage`, `HeaderPageProps`, `HeaderPageOwnProps`, `HeaderPageBreadcrumb`, and `HeaderPageDefinition` exports are still available as deprecated aliases. + +- 0ebde15: Added documentation for the table cell wrapper requirement to TSDoc comments for `Cell`, `CellText`, `CellProfile`, `ColumnConfig`, and `RowRenderFn`. + +- d9d2dd6: Added `SearchAutocomplete` and `SearchAutocompleteItem` components for building accessible search-with-results patterns. Built on React Aria's Autocomplete with keyboard navigation and screen reader support. Designed for async/external search results with a configurable popover width. + + **Affected components:** SearchAutocomplete, SearchAutocompleteItem + +- a6b84e1: Made Checkbox `children` optional and added a dev warning when neither a visible label, `aria-label`, nor `aria-labelledby` is provided. The label wrapper div is no longer rendered when there are no children, removing the unnecessary gap. + + **Affected components:** Checkbox + +- b99f6d5: Fixed `Dialog` content overflowing when no `height` prop is set. The dialog now grows with its content and scrolls when content exceeds the viewport height. + + **Affected components**: Dialog + +- 2f581de: Fixed focus ring styles to use React Aria's `[data-focus-visible]` data attribute instead of the native CSS `:focus-visible` pseudo-class. This ensures keyboard focus rings render reliably when focus is managed programmatically by React Aria (e.g. inside a GridList, Menu, or Select). + + **Affected components:** Accordion, Button, ButtonIcon, ButtonLink, Card, List, Menu, Select, ToggleButtonGroup + +- 17d6398: Fixed incorrect bottom spacing caused by `Container` using `padding-bottom` for its default bottom spacing. Changed to `margin-bottom` and prevented it from applying when `Container` is used as the `Header` root element. + + **Affected components:** Container, Header + +- 2e5c651: Fixed `PluginHeader` to avoid triggering `ResizeObserver loop completed with undelivered notifications` warnings when used in layouts that react to the header height, such as pages that use `FullPage`. + + **Affected components:** PluginHeader + +- d4fa5b4: Fixed tab `matchStrategy` matching to ignore query parameters and hash fragments in tab `href` values. Previously, tabs with query params in their `href` (e.g., `/page?group=foo`) would never show as active since matching compared the full `href` string against `location.pathname` which never includes query params. + + **Affected components:** Tabs, PluginHeader + +- bc42b60: Fixed Table component to use current `--bui-bg-neutral-1` tokens instead of the removed `--bui-bg-tint` tokens, restoring row hover, selected, pressed, and disabled background colors. + +- 9314ff5: Fixed a bug in the `useTable` hook where the loading skeleton was never shown for `complete` mode when using `getData`. The initial data state was an empty array instead of `undefined`, causing the `Table` component to skip the loading state. + +- f42f4cc: Fixed Table column headers overflowing and wrapping when there is not enough space. Headers now truncate with ellipsis instead. + + **Affected components:** Table + +- 1f9682b: Fixed Table row hover, selected, pressed, and disabled background states to use the correct neutral token level based on the container background. + + **Affected components:** Table + +- fbd5c5a: Fixed Table rows showing a pointer cursor when not interactive. Rows now only show `cursor: pointer` when they have an `href`, are selectable, or are pressable. + + **Affected components:** Table + +- 612c217: Fixed `Table` rows with external `href` values to open in a new tab by automatically applying `target="_blank"` and `rel="noopener noreferrer"`. + + **Affected components**: Table + +- 545129a: Updated Table selection checkboxes to use `aria-label` instead of empty fragment children, improving accessibility and removing the unnecessary label gap in the selection cells. + + **Affected components:** Table + +- 36987db: Fixed handling of the `style` prop on `Button`, `ButtonIcon`, and `ButtonLink` so that it is now correctly forwarded to the underlying element instead of being silently dropped. + + **Affected components:** Button, ButtonIcon, ButtonLink + +- 95702ab: Fixed Link variant default from `'body'` to `'body-medium'` to match actual CSS selectors. The previous default did not correspond to a valid variant value. + + **Affected components:** Link + +- 9027b10: Fixed scroll overflow in Menu and Select popover content when constrained by viewport height. + + **Affected components:** Menu, Select + +- 7960d54: Added support for native HTML div attributes on the `Flex`, `Grid`, and `Grid.Item` components. + + **Affected components:** Flex, Grid, Grid.Item + +- 0559408: Added `virtualized` prop to `Table` component for virtualized rendering of large datasets. Accepts `true` for default row height, `{ rowHeight: number }` for fixed height, or `{ estimatedRowHeight: number }` for variable height rows. + +- 8909359: Fixed focus-visible outline styles for Menu and Select components. + + **Affected components:** Menu, Select + +- 12d8afe: Fixed MenuItem `onAction` prop ordering so user-provided `onAction` handlers are chained rather than silently overwritten. + +- aa29b50: Pages created with `PageBlueprint` now render the plugin header by default in the new frontend system. + +- bb66b86: The `Select` trigger now automatically adapts its background colour based on the parent background context. + + **Affected components:** Select + +- 4105a78: Merged the internal `PluginHeaderToolbar` component into `PluginHeader`, removing the separate component and its associated types (`PluginHeaderToolbarOwnProps`, `PluginHeaderToolbarProps`) and definition (`PluginHeaderToolbarDefinition`). This is an internal refactor with no changes to the public API of `PluginHeader`. + + **Affected components:** PluginHeader + +- 9599697: Updated dependency `globals` to `^17.0.0`. + +- 0f462f8: Improved type safety in `useDefinition` by centralizing prop resolution and strengthening the `BgPropsConstraint` to require that `bg` provider components declare `children` as a required prop in their OwnProps type. + +- 8909359: Added proper cursor styles for RadioGroup items. + + **Affected components:** RadioGroup + +- fcaac3b: Fixed `Card` interactive cards not firing the `onPress` handler when clicking the card surface. + + **Affected components**: Card + +- b303857: Fixed `isRequired` prop not being passed to the underlying React Aria field components in TextField, SearchField, and PasswordField. Previously, `isRequired` was consumed locally for the secondary label text but never forwarded, which meant the input elements lacked `aria-required="true"` and React Aria's built-in required validation was not activated. + + **Affected components:** TextField, SearchField, PasswordField + +- 934ac03: `SearchField` and `TextField` now automatically adapt their background color based on the parent bg context, stepping up one neutral level (e.g. neutral-1 → neutral-2) when placed on a neutral background. `TextField` also gains a focus ring using the `--bui-ring` token. + + **Affected components:** SearchField, TextField + +- cd3cb0f: Improved `useBreakpoint` performance by sharing a single set of `matchMedia` listeners across all component instances instead of creating independent listeners per hook call. + +- 36987db: Extended `AlertProps`, `ContainerProps`, `DialogBodyProps`, and `FieldLabelProps` with native div element props to allow passing attributes like `aria-*` and `data-*`. + + **Affected components:** Alert, Container, DialogBody, FieldLabel + +## @backstage/plugin-catalog-backend@3.5.0 + +### Minor Changes + +- a6b2819: Added `query-catalog-entities` action to the catalog backend actions registry. Supports predicate-based filtering with `$all`, `$any`, `$not`, `$exists`, `$in`, `$contains`, and `$hasPrefix` operators. + +- 972f686: Added support for predicate-based filtering on the `/entities/by-refs` endpoint via the `query` field in the request body. Supports `$all`, `$any`, `$not`, `$exists`, `$in`, `$contains`, and `$hasPrefix` operators. + +- 5d95e8e: Add an `onConflict` option to location creation that can refresh an existing location instead of throwing a conflict error. + +- 56c908e: Added support for predicate-based filtering on the `/entity-facets` endpoint via a new `POST` method. Supports `$all`, `$any`, `$not`, `$exists`, `$in`, `$contains`, and `$hasPrefix` operators. + +- 0fbcf23: Migrated OpenAPI schemas to 3.1. + +- bf71677: Added opentelemetry metrics for SCM events: + + - `catalog.events.scm.messages` with attribute `eventType`: Counter for the number of SCM events actually received by the catalog backend. The `eventType` is currently either `location` or `repository`. + +- 51e23eb: Added predicate-based entity filtering via POST /entities/by-query endpoint. + + Supports `$all`, `$any`, `$not`, `$exists`, `$in`, `$hasPrefix`, and (partially) `$contains` operators for expressive entity queries. Integrated into the existing `queryEntities` flow with full cursor-based pagination, permission enforcement, and `totalItems` support. + + The catalog client's `queryEntities()` method automatically routes to the POST endpoint when a `query` predicate is provided. + +### Patch Changes + +- a91bd1b: Improved catalog entity deletion so parent invalidation and deferred relation restitch scheduling are coordinated more safely. + +- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1 + +- 7416e8b: Moved stitch queue concerns out of `refresh_state` and `final_entities` into a dedicated `stitch_queue` table with `entity_ref` as the primary key. The `stitch_ticket` is used for optimistic concurrency control. When a stitch completes successfully and the ticket hasn't changed, the corresponding row is deleted from the queue. The migration handles existing data and is fully reversible. + +- fbf382f: Minor internal optimisation + +- 1ee5b28: Migrates existing catalog metrics to use the alpha MetricsService. This release is a 1:1 migration with no breaking changes. + +- 72747b4: Deprecated two processors as they have been moved to the Community Plugins repo with their own backend modules: + + - `AnnotateScmSlugEntityProcessor`: Use `@backstage-community/plugin-catalog-backend-module-annotate-scm-slug` instead + - `CodeOwnersProcessor`: Use `@backstage-community/plugin-catalog-backend-module-codeowners` instead + +- 3644b72: Make the `search` foreign key catalog migration non-blocking on large tables by using batch deletes and PostgreSQL `NOT VALID`/`VALIDATE` to reduce lock duration + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. + +- 3181973: Changed the `search` table foreign key to point to `final_entities` instead of `refresh_state` + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/catalog-client@1.14.0 + - @backstage/integration@2.0.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/filter-predicates@0.1.1 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/plugin-permission-node@0.10.11 + - @backstage/catalog-model@1.7.7 + - @backstage/backend-openapi-utils@0.6.7 + - @backstage/plugin-events-node@0.4.20 + +## @backstage/plugin-catalog-backend-module-github@0.13.0 + +### Minor Changes + +- b11c2cd: The default user transformer now prefers organization verified domain emails over the user's public GitHub email when populating the user entity profile. It also strips plus-addressed routing tags that GitHub adds to these emails. + + If you want to retain the old behavior, you can do so with a custom user transformer using the `githubOrgEntityProviderTransformsExtensionPoint`: + + ```ts + import { createBackendModule } from '@backstage/backend-plugin-api'; + import { githubOrgEntityProviderTransformsExtensionPoint } from '@backstage/plugin-catalog-backend-module-github-org'; + import { defaultUserTransformer } from '@backstage/plugin-catalog-backend-module-github'; + + export default createBackendModule({ + pluginId: 'catalog', + moduleId: 'github-org-custom-transforms', + register(env) { + env.registerInit({ + deps: { + transforms: githubOrgEntityProviderTransformsExtensionPoint, + }, + async init({ transforms }) { + transforms.setUserTransformer(async (item, ctx) => { + const entity = await defaultUserTransformer(item, ctx); + if (entity && item.email) { + entity.spec.profile!.email = item.email; + } + return entity; + }); + }, + }); + }, + }); + ``` + +### Patch Changes + +- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1 + +- 106d1b2: Added a `defaultUserTransformer.useVerifiedEmails` config option for the `githubOrg` provider. When set to `true`, the default user transformer prefers organization verified domain emails over the user's public GitHub email. Defaults to `false`, which uses only the public GitHub email. + + This option has no effect when a custom user transformer is set via the `githubOrgEntityProviderTransformsExtensionPoint`. + + ```yaml + catalog: + providers: + githubOrg: + production: + githubUrl: https://github.com + orgs: + - my-org + defaultUserTransformer: + useVerifiedEmails: true + ``` + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/integration@2.0.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/catalog-model@1.7.7 + - @backstage/plugin-events-node@0.4.20 + +## @backstage/plugin-catalog-graph@0.6.0 + +### Minor Changes + +- d14b6e0: **BREAKING**: Migrated `MembersListCard`, `OwnershipCard`, and `CatalogGraphCard` to use BUI card primitives via `EntityInfoCard`. + + - `OwnershipCard`: Removed `variant` and `maxScrollHeight` props. Card height and scrolling are now controlled by the parent container — the card fills its container and the body scrolls automatically when content overflows. + - `CatalogGraphCard`: Removed `variant` prop. + - `MembersListCard`: Translation keys `subtitle`, `paginationLabel`, `aggregateMembersToggle.directMembers`, `aggregateMembersToggle.aggregatedMembers`, and `aggregateMembersToggle.ariaLabel` have been removed. The `title` key now includes `{{groupName}}`. New keys added: `cardLabel`, `noSearchResult`, `aggregateMembersToggle.label`. + - `OwnershipCard`: Translation keys `aggregateRelationsToggle.directRelations`, `aggregateRelationsToggle.aggregatedRelations`, and `aggregateRelationsToggle.ariaLabel` have been removed. New key added: `aggregateRelationsToggle.label`. + - Removed `MemberComponentClassKey` export, and `root` and `cardContent` from `MembersListCardClassKey`, `card` from `OwnershipCardClassKey`, and `card` from `CatalogGraphCardClassKey`. + + **Migration:** + + ```diff + - + + + ``` + + ```diff + - + + + ``` + +### Patch Changes + +- 538c985: Updated installation documentation to use feature discovery as the default. +- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point. +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/ui@0.13.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/catalog-client@1.14.0 + - @backstage/catalog-model@1.7.7 + +## @backstage/plugin-catalog-node@2.1.0 + +### Minor Changes + +- bf71677: Added the ability for SCM events subscribers to mark the fact that they have taken actions based on events, which produces output metrics: + + - `catalog.events.scm.actions` with attribute `action`: Counter for the number of actions actually taken by catalog internals or other subscribers, based on SCM events. The `action` is currently either `create`, `delete`, `refresh`, or `move`. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/backend-test-utils@1.11.1 + - @backstage/catalog-client@1.14.0 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/plugin-permission-node@0.10.11 + - @backstage/catalog-model@1.7.7 + +## @backstage/plugin-catalog-react@2.1.0 + +### Minor Changes + +- c548a0f: Added `EntityDataTable`, `EntityRelationCard`, `entityDataTableColumns`, `entityColumnPresets`, and related types as alpha exports. These replace `EntityTable` and `RelatedEntitiesCard` (from `@backstage/plugin-catalog`) respectively, providing a unified BUI-based pattern for entity table cards. +- 4d58894: Added `aliases` and `contentOrder` fields to `EntityContentGroupDefinition`, allowing groups to declare alias IDs and control the sort order of their content items. +- d14b6e0: Exported `useEntityRefLink` hook that returns a function for generating entity page URLs from entity references. +- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point. +- c6080eb: Added `EntityInfoCard` component to `@backstage/plugin-catalog-react` as a BUI-based card wrapper for entity page cards. + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/ui@0.13.0 + - @backstage/core-compat-api@0.5.9 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/catalog-client@1.14.0 + - @backstage/frontend-test-utils@0.5.1 + - @backstage/plugin-permission-react@0.4.41 + - @backstage/filter-predicates@0.1.1 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/catalog-model@1.7.7 + - @backstage/integration-react@1.2.16 + +## @backstage/plugin-devtools-react@0.2.0 + +### Minor Changes + +- f4a1edd: Removed the deprecated `DevToolsContentBlueprint` from `@backstage/plugin-devtools-react`. DevTools pages in the new frontend system now use `SubPageBlueprint` tabs instead, and the catalog unprocessed entities alpha extension now attaches to DevTools as a subpage. + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.4 + - @backstage/frontend-plugin-api@0.15.0 + +## @backstage/plugin-events-backend@0.6.0 + +### Minor Changes + +- 0fbcf23: Migrated OpenAPI schemas to 3.1. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/backend-openapi-utils@0.6.7 + - @backstage/plugin-events-node@0.4.20 + +## @backstage/plugin-notifications-backend-module-slack@0.4.0 + +### Minor Changes + +- cd62d78: **BREAKING**: Only send direct messages to user entity recipients. Notifications sent to non-user entities no longer send Slack direct messages to resolved users. +- 749ba60: Add an extension for custom Slack message layouts + +### Patch Changes + +- e7c6c32: The Slack notification processor now uses the `MetricsService` to create metrics, providing plugin-scoped attribution. `{message}` unit has also been added. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/catalog-model@1.7.7 + - @backstage/plugin-notifications-node@0.2.24 + +## @backstage/plugin-org@0.7.0 + +### Minor Changes + +- d14b6e0: **BREAKING**: Migrated `MembersListCard`, `OwnershipCard`, and `CatalogGraphCard` to use BUI card primitives via `EntityInfoCard`. + + - `OwnershipCard`: Removed `variant` and `maxScrollHeight` props. Card height and scrolling are now controlled by the parent container — the card fills its container and the body scrolls automatically when content overflows. + - `CatalogGraphCard`: Removed `variant` prop. + - `MembersListCard`: Translation keys `subtitle`, `paginationLabel`, `aggregateMembersToggle.directMembers`, `aggregateMembersToggle.aggregatedMembers`, and `aggregateMembersToggle.ariaLabel` have been removed. The `title` key now includes `{{groupName}}`. New keys added: `cardLabel`, `noSearchResult`, `aggregateMembersToggle.label`. + - `OwnershipCard`: Translation keys `aggregateRelationsToggle.directRelations`, `aggregateRelationsToggle.aggregatedRelations`, and `aggregateRelationsToggle.ariaLabel` have been removed. New key added: `aggregateRelationsToggle.label`. + - Removed `MemberComponentClassKey` export, and `root` and `cardContent` from `MembersListCardClassKey`, `card` from `OwnershipCardClassKey`, and `card` from `CatalogGraphCardClassKey`. + + **Migration:** + + ```diff + - + + + ``` + + ```diff + - + + + ``` + +- 5fc35bb: Migrated `EntityAboutCard`, `EntityLinksCard`, `EntityLabelsCard`, `GroupProfileCard`, and `UserProfileCard` from MUI/InfoCard to use the new BUI card layout and BUI components where possible. + + **BREAKING**: Removed `variant` prop from EntityAboutCard, EntityUserProfileCard, EntityGroupProfileCard, EntityLabelsCard, EntityLinksCard. Removed `gridSizes` prop from `AboutField`. + + **Migration:** + + Simply delete the obsolete `variant` and `gridSizes` props, e.g: + + ```diff + - + + + ``` + + ```diff + - + + + ``` + +### Patch Changes + +- 538c985: Updated installation documentation to use feature discovery as the default. +- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point. +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/ui@0.13.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/catalog-model@1.7.7 + +## @backstage/plugin-scaffolder@1.36.0 + +### Minor Changes + +- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point. + +### Patch Changes + +- e27bd4e: Removed check for deprecated `bitbucket` integration from `repoPickerValidation` function used by the `RepoUrlPicker`, it now validates the `bitbucketServer` and `bitbucketCloud` integrations instead. +- 538c985: Updated installation documentation to use feature discovery as the default. +- bd5b842: Added a new `ui:autoSelect` option to the EntityPicker field that controls whether an entity is automatically selected when the field loses focus. When set to `false`, the field will remain empty if the user closes it without explicitly selecting an entity, preventing unintentional selections. Defaults to `true` for backward compatibility. +- 3f36ce1: Updated alpha plugin icons to follow the new frontend icon sizing rules when rendered in plugin and navigation surfaces. +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/catalog-client@1.14.0 + - @backstage/plugin-scaffolder-react@1.20.0 + - @backstage/integration@2.0.0 + - @backstage/plugin-permission-react@0.4.41 + - @backstage/plugin-scaffolder-common@2.0.0 + - @backstage/catalog-model@1.7.7 + - @backstage/integration-react@1.2.16 + - @backstage/plugin-techdocs-react@1.3.9 + +## @backstage/plugin-scaffolder-backend@3.2.0 + +### Minor Changes + +- c9b11eb: Added a new `list-scaffolder-tasks` action that allows querying scaffolder tasks with optional ownership filtering and pagination support +- 1b42218: Adds a new `get-scaffolder-task-logs` action to `@backstage/plugin-scaffolder-backend` that retrieves log events for a given scaffolder task, with optional support for retrieving only new events after a given event ID. +- 0fbcf23: Migrated OpenAPI schemas to 3.1. +- 7695dd2: Added a new `list-scaffolder-actions` action that returns all installed scaffolder actions with their schemas and examples +- e8736ea: Added secrets schema validation for task creation, retry, and dry-run endpoints. When a template defines `spec.secrets.schema`, the API validates provided secrets against the schema and returns a `400` error if validation fails. + +### Patch Changes + +- e27bd4e: Removed `@backstage/plugin-scaffolder-backend-module-bitbucket` from `package.json` as the package itself has been deprecated and the code deleted. +- 4f5ed06: Fixed a security vulnerability where server-configured environment secrets were exposed through the scaffolder dry-run endpoint. +- 30ff981: Fixed a security vulnerability where secrets could bypass log redaction when transformed through Nunjucks filters in scaffolder templates. +- 4e39e63: Removed unused dependencies +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- ccc20cf: create scaffolder MCP action to dry run a provided scaffolder template +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-scaffolder-node@0.13.0 + - @backstage/integration@2.0.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/plugin-scaffolder-common@2.0.0 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/plugin-permission-node@0.10.11 + - @backstage/catalog-model@1.7.7 + - @backstage/backend-openapi-utils@0.6.7 + - @backstage/plugin-events-node@0.4.20 + +## @backstage/plugin-scaffolder-node@0.13.0 + +### Minor Changes + +- e27bd4e: **BREAKING** Removed deprecated `bitbucket` integration from being used in the `parseRepoUrl` function. It will use the `bitbucketCloud` or `bitbucketServer` integrations instead. + +### Patch Changes + +- cd0ecc5: Added `removeFiles` helper function for staging file removals in Git. +- f598909: Added `scaffolderServiceRef` and `ScaffolderService` interface for backend plugins that need to interact with the scaffolder API using `BackstageCredentials` instead of raw tokens. +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/backend-test-utils@1.11.1 + - @backstage/integration@2.0.0 + - @backstage/plugin-scaffolder-common@2.0.0 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/catalog-model@1.7.7 + +## @backstage/plugin-scaffolder-react@1.20.0 + +### Minor Changes + +- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point. +- 470f72d: The `LogViewer` component from `@backstage/core-components` now supports downloading logs if a callback is passed to `onDownloadLogs` + +### Patch Changes + +- 004b5c1: Added back `formFieldsApiRef` and `ScaffolderFormFieldsApi` as alpha exports. +- bd31ddd: Updated dependency `flatted` to `3.3.4`. +- f598909: Added `scaffolderApiMock` test utility, exported from `@backstage/plugin-scaffolder-react/testUtils`. +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/catalog-client@1.14.0 + - @backstage/frontend-test-utils@0.5.1 + - @backstage/plugin-permission-react@0.4.41 + - @backstage/plugin-scaffolder-common@2.0.0 + - @backstage/catalog-model@1.7.7 + +## @backstage/plugin-search@1.7.0 + +### Minor Changes + +- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point. + +### Patch Changes + +- d5eb954: Fixes the search component not registering the first search on navigate to the search page. +- aa29b50: New frontend system pages now use the default plugin header together with `HeaderPage` instead of the legacy core page header pattern. +- 3f36ce1: Updated alpha plugin icons to follow the new frontend icon sizing rules when rendered in plugin and navigation surfaces. +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/ui@0.13.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/plugin-search-react@1.11.0 + +## @backstage/plugin-search-backend@2.1.0 + +### Minor Changes + +- 0fbcf23: Migrated OpenAPI schemas to 3.1. + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/plugin-permission-node@0.10.11 + - @backstage/backend-openapi-utils@0.6.7 + - @backstage/plugin-search-backend-node@1.4.2 + +## @backstage/plugin-search-react@1.11.0 + +### Minor Changes + +- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point. + +### Patch Changes + +- d5eb954: Fixes the search component not registering the first search on navigate to the search page. +- Updated dependencies + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + +## @backstage/app-defaults@1.7.6 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/core-app-api@1.19.6 + - @backstage/plugin-permission-react@0.4.41 + +## @backstage/backend-openapi-utils@0.6.7 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + +## @backstage/backend-test-utils@1.11.1 + +### Patch Changes + +- dee4283: Added `pluginId` field to `ActionsServiceAction` type, populated from the registering plugin's metadata. +- 164711a: Added `cancelTask` to `MockSchedulerService` and mock scheduler service factory. +- 62f0a53: Fixed error forwarding in the actions registry so that known errors like `InputError` and `NotFoundError` thrown by actions preserve their original status codes and messages instead of being wrapped in `ForwardedError` and coerced to 500. +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- 1ee5b28: Adds a new metrics service mock to be leveraged in tests +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/backend-defaults@0.16.0 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/plugin-auth-node@0.6.14 + - @backstage/backend-app-api@1.6.0 + - @backstage/plugin-events-node@0.4.20 + +## @backstage/catalog-model@1.7.7 + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. + +## @backstage/cli-module-actions@0.0.1 + +### Patch Changes + +- 42960f1: Added `actions` CLI module for listing and executing actions from the distributed actions registry. Includes `actions list`, `actions execute`, and `actions sources` commands for managing plugin sources. +- Updated dependencies + - @backstage/cli-node@0.3.0 + +## @backstage/codemods@0.1.55 + +### Patch Changes + +- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`. +- de62a9d: Upgraded `commander` dependency from `^12.0.0` to `^14.0.3` across all CLI packages. +- Updated dependencies + - @backstage/cli-common@0.2.0 + +## @backstage/config-loader@1.10.9 + +### Patch Changes + +- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`. +- Updated dependencies + - @backstage/cli-common@0.2.0 + +## @backstage/core-app-api@1.19.6 + +### Patch Changes + +- 12d8afe: Added `BUIProvider` from `@backstage/ui` to the app shell provider tree, enabling BUI components to fire analytics events through the Backstage analytics system. +- 59752a2: Deprecated `AlertApiForwarder` in favor of the new `ToastApi`. The `AlertApiForwarder` now emits a console warning on first use, guiding developers to migrate to `ToastApi` from `@backstage/frontend-plugin-api`. +- 0452d02: Add optional `description` field to plugin-level feature flags. +- 42f8c9b: Added `BUIProvider` inside the legacy app router to enable client-side routing for all BUI components. +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/ui@0.13.0 + - @backstage/core-plugin-api@1.12.4 + +## @backstage/core-compat-api@0.5.9 + +### Patch Changes + +- b15a685: Added `withApis`, which is a Higher-Order Component for providing APIs as props to a component via `useApiHolder`. +- 8e09233: Added a missing dependency on `@backstage/filter-predicates` to `@backstage/core-compat-api`. This fixes package metadata for consumers that use compatibility helpers relying on filter predicate support. +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/plugin-app-react@0.2.1 + - @backstage/filter-predicates@0.1.1 + +## @backstage/core-components@0.18.8 + +### Patch Changes + +- e26e3de: The login request dialog now handles auth provider icons passed as `IconElement` in addition to `IconComponent`. +- 8e09233: Fixed the shared `Progress` component to provide an accessible name for its loading indicator by default. +- 8b1a847: Fixed Table component layout when both `filters` and `title` props are used together. The filter controls now use a dedicated CSS class (`filterControls`) instead of incorrectly reusing the root container class. +- 3f36ce1: Clarified the `IconElement` sizing contract for the new frontend system and aligned legacy system icon rendering with the new icon API. +- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point. +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- 470f72d: The `LogViewer` component from `@backstage/core-components` now supports downloading logs if a callback is passed to `onDownloadLogs` +- Updated dependencies + - @backstage/core-plugin-api@1.12.4 + +## @backstage/core-plugin-api@1.12.4 + +### Patch Changes + +- d911b72: Updated `createApiRef` to preserve the direct config call without deprecation warnings while staying compatible with the new frontend API ref typing. +- 59752a2: Deprecated `AlertApi`, `AlertMessage`, and `alertApiRef` in favor of the new `ToastApi` from `@backstage/frontend-plugin-api`. +- 0452d02: Add optional `description` field to plugin-level feature flags. +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- fe848e0: Changed `useApiHolder` to return an empty `ApiHolder` instead of throwing when used outside of an API context. +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0 + +## @backstage/dev-utils@1.1.21 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/ui@0.13.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/core-app-api@1.19.6 + - @backstage/catalog-model@1.7.7 + - @backstage/app-defaults@1.7.6 + - @backstage/integration-react@1.2.16 + +## @backstage/eslint-plugin@0.2.2 + +### Patch Changes + +- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1 + +## @backstage/filter-predicates@0.1.1 + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. + +## @backstage/frontend-dynamic-feature-loader@0.1.10 + +### Patch Changes + +- e8ba654: Update the README of the `frontend-dynamic-feature-loader` package to mention the new `backstage-cli package bundle` command. +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/module-federation-common@0.1.2 + +## @backstage/frontend-test-utils@0.5.1 + +### Patch Changes + +- b56f573: Deprecated standalone mock API exports in favor of the `mockApis` namespace. This includes the mock classes (`MockAlertApi`, `MockAnalyticsApi`, `MockConfigApi`, `MockErrorApi`, `MockFetchApi`, `MockFeatureFlagsApi`, `MockPermissionApi`, `MockStorageApi`, `MockTranslationApi`), their option types (`MockErrorApiOptions`, `MockFeatureFlagsApiOptions`), and the `ErrorWithContext` type. `MockFetchApiOptions` is kept as a non-deprecated export. Use the `mockApis` namespace instead, for example `mockApis.alert()` or `mockApis.alert.mock()`. +- 479282f: Fixed type inference of `TestApiPair` when using tuple syntax by wrapping `MockWithApiFactory` in `NoInfer`. +- 8e09233: Added a missing dependency on `@backstage/filter-predicates` to `@backstage/frontend-test-utils`. This fixes package metadata for consumers using the frontend test app helpers with predicate-based behavior. +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- 909c742: Switched `MockTranslationApi` and related test utility imports from `@backstage/core-plugin-api/alpha` to the stable `@backstage/frontend-plugin-api` export. The `TranslationApi` type in the API report is now sourced from a single package. This has no effect on runtime behavior. +- Updated dependencies + - @backstage/frontend-app-api@0.16.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/core-app-api@1.19.6 + - @backstage/plugin-app@0.4.1 + - @backstage/plugin-app-react@0.2.1 + - @backstage/plugin-permission-react@0.4.41 + - @backstage/filter-predicates@0.1.1 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/test-utils@1.7.16 + +## @backstage/integration-react@1.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.4 + - @backstage/integration@2.0.0 + +## @backstage/module-federation-common@0.1.2 + +### Patch Changes + +- 0cb5646: Fixed the `@mui/material/styles` shared dependency key by removing a trailing slash that caused module resolution failures with MUI package exports. + +## @techdocs/cli@1.10.6 + +### Patch Changes + +- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`. +- de62a9d: Upgraded `commander` dependency from `^12.0.0` to `^14.0.3` across all CLI packages. +- Updated dependencies + - @backstage/backend-defaults@0.16.0 + - @backstage/cli-common@0.2.0 + - @backstage/plugin-techdocs-node@1.14.4 + - @backstage/catalog-model@1.7.7 + +## @backstage/test-utils@1.7.16 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-app-api@1.19.6 + - @backstage/plugin-permission-react@0.4.41 + - @backstage/plugin-permission-common@0.9.7 + +## @backstage/plugin-api-docs@0.13.5 + +### Patch Changes + +- 9c9d425: Fixed invisible text in parameter input fields when using dark mode in OpenAPI definition pages +- 538c985: Updated installation documentation to use feature discovery as the default. +- 30e08df: Added default entity content groups for the API docs entity content tabs. The API definition tab defaults to the `documentation` group and the APIs tab defaults to the `development` group. +- c548a0f: Migrated entity table cards (`ConsumedApisCard`, `ProvidedApisCard`, `HasApisCard`, `ConsumingComponentsCard`, `ProvidingComponentsCard`) to use BUI when no legacy props are passed. The old `variant`, `columns`, and `tableOptions` props are deprecated but still supported — passing any of them triggers the legacy MUI-based rendering. The new `columnConfig` prop accepts `EntityColumnConfig[]` for BUI-based rendering. +- aa29b50: New frontend system pages now use the default plugin header together with `HeaderPage` instead of the legacy core page header pattern. +- 3f36ce1: Updated alpha plugin icons to follow the new frontend icon sizing rules when rendered in plugin and navigation surfaces. +- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point. +- ca277ef: Updated dependency `graphiql` to `3.9.0` to address security vulnerability in `markdown-it` package. + Updated dependency `@graphiql/react` to `0.29.0` to match the version used by `graphiql`. + Moved dependency `graphql-config` to `devDependencies` as it is needed only for types. +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/ui@0.13.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/plugin-catalog@2.0.0 + - @backstage/plugin-permission-react@0.4.41 + - @backstage/catalog-model@1.7.7 + +## @backstage/plugin-app@0.4.1 + +### Patch Changes + +- 5f3f5d2: `NavContentBlueprint` nav item collections now keep previously collected `rest()` results in sync when additional items are taken later in the same render, making it easier to place items across multiple sidebar sections. + +- aa29b50: Pages created with `PageBlueprint` now render the plugin header by default in the new frontend system. + +- c0ab376: The app nav now falls back to `plugin.icon` for navigation items that don't have an explicit icon set. + +- 12d8afe: Added `BUIProvider` from `@backstage/ui` to the app root, enabling BUI components to fire analytics events through the Backstage analytics system. + +- 5fec07d: Updated the default app root to better support phased app preparation by allowing the app layout to be absent during bootstrap, routing bootstrap failures through the app root boundary, and avoiding installation of a guest identity in protected apps that do not provide a sign-in page. + +- 9508514: Updated the default `PluginWrapperApi` implementation to support the new `useWrapperValue` hook and root wrapper. The root wrapper is now rendered in the app root to manage shared hook state across plugin wrapper instances. + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. + +- 42f8c9b: Moved `BUIProvider` inside the app router to enable automatic client-side routing for all BUI components. + +- 909c742: Switched translation API imports (`translationApiRef`, `appLanguageApiRef`) from the alpha `@backstage/core-plugin-api/alpha` path to the stable `@backstage/frontend-plugin-api` export. This has no effect on runtime behavior. + +- 7e743f4: Introduced a new `ToastApi` for displaying rich toast notifications in the new frontend system. + + The new `ToastApi` provides enhanced notification capabilities compared to the existing `AlertApi`: + + - **Title and Description**: Toasts support both a title and an optional description + - **Custom Timeouts**: Each toast can specify its own timeout duration + - **Links**: Toasts can include action links + - **Status Variants**: Support for neutral, info, success, warning, and danger statuses + - **Programmatic Dismiss**: Toasts can be dismissed programmatically using the `close()` handle returned from `post()` + + **Usage:** + + ```typescript + import { toastApiRef, useApi } from '@backstage/frontend-plugin-api'; + + const toastApi = useApi(toastApiRef); + + // Full-featured toast + toastApi.post({ + title: 'Entity saved', + description: 'Your changes have been saved successfully.', + status: 'success', + timeout: 5000, + links: [{ label: 'View entity', href: '/catalog/entity' }], + }); + + // Programmatic dismiss + const { close } = toastApi.post({ title: 'Uploading...', status: 'info' }); + // Later... + close(); + ``` + + The `ToastDisplay` component subscribes to both `ToastApi` and `AlertApi`, providing a migration path where both systems work side by side until `AlertApi` is fully deprecated. + +- Updated dependencies + - @backstage/ui@0.13.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/plugin-app-react@0.2.1 + - @backstage/plugin-permission-react@0.4.41 + - @backstage/filter-predicates@0.1.1 + - @backstage/integration-react@1.2.16 + +## @backstage/plugin-app-backend@0.5.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/config-loader@1.10.9 + - @backstage/plugin-auth-node@0.6.14 + - @backstage/plugin-app-node@0.1.43 + +## @backstage/plugin-app-node@0.1.43 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/config-loader@1.10.9 + +## @backstage/plugin-app-react@0.2.1 + +### Patch Changes + +- 5f3f5d2: `NavContentBlueprint` nav item collections now keep previously collected `rest()` results in sync when additional items are taken later in the same render, making it easier to place items across multiple sidebar sections. +- 2c383b5: Added `AnalyticsImplementationBlueprint` and `AnalyticsImplementationFactory`, migrated from `@backstage/frontend-plugin-api`. +- Updated dependencies + - @backstage/core-plugin-api@1.12.4 + - @backstage/frontend-plugin-api@0.15.0 + +## @backstage/plugin-app-visualizer@0.2.1 + +### Patch Changes + +- c25532a: Switched dev entry point to use `createDevApp` from `@backstage/frontend-dev-utils`. +- Updated dependencies + - @backstage/ui@0.13.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + +## @backstage/plugin-auth@0.1.6 + +### Patch Changes + +- 06294aa: Migrated the ConsentPage UI from Material-UI and `@backstage/core-components` to `@backstage/ui`. +- aa29b50: New frontend system pages now use the default plugin header together with `HeaderPage` instead of the legacy core page header pattern. +- Updated dependencies + - @backstage/ui@0.13.0 + - @backstage/frontend-plugin-api@0.15.0 + +## @backstage/plugin-auth-backend@0.27.2 + +### Patch Changes + +- 1ccad86: Added `who-am-i` action to the auth backend actions registry. Returns the catalog entity and user info for the currently authenticated user. +- d0f4cd2: Added optional client metadata document endpoint at `/.well-known/oauth-client/cli.json` relative to the auth backend base URL for CLI authentication. Enabled when `auth.experimentalClientIdMetadataDocuments.enabled` is set to `true`. +- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1 +- e9b6e97: Fixed a security vulnerability where the CIMD metadata fetch could follow HTTP redirects to internal hosts, bypassing SSRF protections. +- 0f9d673: Improved redirect URI validation in the experimental OIDC provider to match against normalized URLs rather than raw strings. +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- 634eded: Fixed a foreign key constraint violation when issuing refresh tokens for CIMD clients, and + prevented a failed refresh token issuance from failing the entire token exchange. + Fixed AWS ALB auth provider incorrectly returning HTTP 500 instead of 401 for JWT validation failures, + which caused retry loops and memory pressure under load. +- 619be54: Update migrations to be reversible +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/catalog-model@1.7.7 + - @backstage/plugin-auth-node@0.6.14 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.13 + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-auth-node@0.6.14 + +## @backstage/plugin-auth-backend-module-auth0-provider@0.3.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-auth-node@0.6.14 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.14 + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- 634eded: Fixed a foreign key constraint violation when issuing refresh tokens for CIMD clients, and + prevented a failed refresh token issuance from failing the entire token exchange. + Fixed AWS ALB auth provider incorrectly returning HTTP 500 instead of 401 for JWT validation failures, + which caused retry loops and memory pressure under load. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-auth-backend@0.27.2 + - @backstage/plugin-auth-node@0.6.14 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.18 + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/catalog-model@1.7.7 + - @backstage/plugin-auth-node@0.6.14 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.13 + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-auth-node@0.6.14 + +## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.13 + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-auth-node@0.6.14 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.13 + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-auth-node@0.6.14 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.13 + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-auth-node@0.6.14 + +## @backstage/plugin-auth-backend-module-github-provider@0.5.1 + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-auth-node@0.6.14 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.4.1 + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-auth-node@0.6.14 + +## @backstage/plugin-auth-backend-module-google-provider@0.3.13 + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-auth-node@0.6.14 + +## @backstage/plugin-auth-backend-module-guest-provider@0.2.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/catalog-model@1.7.7 + - @backstage/plugin-auth-node@0.6.14 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.13 + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-auth-node@0.6.14 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.13 + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-auth-node@0.6.14 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.18 + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-auth-node@0.6.14 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.4.14 + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-auth-backend@0.27.2 + - @backstage/plugin-auth-node@0.6.14 + +## @backstage/plugin-auth-backend-module-okta-provider@0.2.13 + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-auth-node@0.6.14 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.13 + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-auth-node@0.6.14 + +## @backstage/plugin-auth-backend-module-openshift-provider@0.1.5 + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/catalog-model@1.7.7 + - @backstage/plugin-auth-node@0.6.14 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-auth-node@0.6.14 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/catalog-model@1.7.7 + - @backstage/plugin-auth-node@0.6.14 + +## @backstage/plugin-auth-node@0.6.14 + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/catalog-client@1.14.0 + - @backstage/catalog-model@1.7.7 + +## @backstage/plugin-auth-react@0.1.25 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + +## @backstage/plugin-bitbucket-cloud-common@0.3.8 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0 + +## @backstage/plugin-catalog-backend-module-aws@0.4.21 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/backend-defaults@0.16.0 + - @backstage/integration@2.0.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/catalog-model@1.7.7 + +## @backstage/plugin-catalog-backend-module-azure@0.3.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/integration@2.0.0 + - @backstage/plugin-catalog-node@2.1.0 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/catalog-model@1.7.7 + - @backstage/backend-openapi-utils@0.6.7 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/integration@2.0.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/catalog-model@1.7.7 + - @backstage/plugin-bitbucket-cloud-common@0.3.8 + - @backstage/plugin-events-node@0.4.20 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/integration@2.0.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/catalog-model@1.7.7 + - @backstage/plugin-events-node@0.4.20 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/catalog-model@1.7.7 + +## @backstage/plugin-catalog-backend-module-gerrit@0.3.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/integration@2.0.0 + - @backstage/plugin-catalog-node@2.1.0 + +## @backstage/plugin-catalog-backend-module-gitea@0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/integration@2.0.0 + - @backstage/plugin-catalog-node@2.1.0 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.20 + +### Patch Changes + +- 106d1b2: Added a `defaultUserTransformer.useVerifiedEmails` config option for the `githubOrg` provider. When set to `true`, the default user transformer prefers organization verified domain emails over the user's public GitHub email. Defaults to `false`, which uses only the public GitHub email. + + This option has no effect when a custom user transformer is set via the `githubOrgEntityProviderTransformsExtensionPoint`. + + ```yaml + catalog: + providers: + githubOrg: + production: + githubUrl: https://github.com + orgs: + - my-org + defaultUserTransformer: + useVerifiedEmails: true + ``` + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-catalog-backend-module-github@0.13.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/plugin-events-node@0.4.20 + +## @backstage/plugin-catalog-backend-module-gitlab@0.8.1 + +### Patch Changes + +- d933f62: Add configurable throttling and retry mechanism for GitLab integration. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/backend-defaults@0.16.0 + - @backstage/integration@2.0.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/catalog-model@1.7.7 + - @backstage/plugin-events-node@0.4.20 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-catalog-backend-module-gitlab@0.8.1 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/plugin-events-node@0.4.20 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.10 + +### Patch Changes + +- 5f1e7b8: Migrated metrics from direct `@opentelemetry/api` usage to the alpha `MetricsService`, providing plugin-scoped metric attribution. The `@opentelemetry/api` dependency has been removed. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/backend-defaults@0.16.0 + - @backstage/plugin-catalog-backend@3.5.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/catalog-model@1.7.7 + - @backstage/plugin-events-node@0.4.20 + +## @backstage/plugin-catalog-backend-module-ldap@0.12.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/catalog-model@1.7.7 + +## @backstage/plugin-catalog-backend-module-logs@0.1.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-catalog-backend@3.5.0 + - @backstage/plugin-events-node@0.4.20 + +## @backstage/plugin-catalog-backend-module-msgraph@0.9.1 + +### Patch Changes + +- 97eaecf: Fixed scheduler task remaining stuck in running state after pod termination by propagating AbortSignal into MicrosoftGraphOrgEntityProvider.read() +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/catalog-model@1.7.7 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/integration@2.0.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/catalog-model@1.7.7 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/catalog-model@1.7.7 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.18 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/plugin-scaffolder-common@2.0.0 + - @backstage/catalog-model@1.7.7 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.6.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/catalog-model@1.7.7 + - @backstage/plugin-auth-node@0.6.14 + +## @backstage/plugin-catalog-import@0.13.11 + +### Patch Changes + +- 538c985: Updated installation documentation to use feature discovery as the default. +- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point. +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/catalog-client@1.14.0 + - @backstage/integration@2.0.0 + - @backstage/plugin-permission-react@0.4.41 + - @backstage/catalog-model@1.7.7 + - @backstage/integration-react@1.2.16 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.27 + +### Patch Changes + +- 538c985: Updated installation documentation to use feature discovery as the default. +- aa29b50: New frontend system pages now use the default plugin header together with `HeaderPage` instead of the legacy core page header pattern. +- 3f36ce1: Updated alpha plugin icons to follow the new frontend icon sizing rules when rendered in plugin and navigation surfaces. +- f4a1edd: Removed the deprecated `DevToolsContentBlueprint` from `@backstage/plugin-devtools-react`. DevTools pages in the new frontend system now use `SubPageBlueprint` tabs instead, and the catalog unprocessed entities alpha extension now attaches to DevTools as a subpage. +- Updated dependencies + - @backstage/ui@0.13.0 + - @backstage/core-compat-api@0.5.9 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + +## @backstage/plugin-config-schema@0.1.78 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + +## @backstage/plugin-devtools@0.1.37 + +### Patch Changes + +- afabb37: Fixed URL encoding of task IDs for the trigger feature (tasks that contained a "/" in their ID were not triggered) +- f80195e: Added `cancelScheduledTask` to the DevTools API and a cancel button to the scheduled tasks UI. +- 538c985: Updated installation documentation to use feature discovery as the default. +- aa29b50: New frontend system pages now use the default plugin header together with `HeaderPage` instead of the legacy core page header pattern. +- 3f36ce1: Updated alpha plugin icons to follow the new frontend icon sizing rules when rendered in plugin and navigation surfaces. +- f4a1edd: Removed the deprecated `DevToolsContentBlueprint` from `@backstage/plugin-devtools-react`. DevTools pages in the new frontend system now use `SubPageBlueprint` tabs instead, and the catalog unprocessed entities alpha extension now attaches to DevTools as a subpage. +- Updated dependencies + - @backstage/ui@0.13.0 + - @backstage/core-compat-api@0.5.9 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/plugin-devtools-common@0.1.23 + - @backstage/plugin-permission-react@0.4.41 + +## @backstage/plugin-devtools-backend@0.5.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/cli-common@0.2.0 + - @backstage/plugin-devtools-common@0.1.23 + - @backstage/config-loader@1.10.9 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/plugin-permission-node@0.10.11 + +## @backstage/plugin-devtools-common@0.1.23 + +### Patch Changes + +- f80195e: Added `cancelScheduledTask` to the DevTools API and a cancel button to the scheduled tasks UI. +- Updated dependencies + - @backstage/plugin-permission-common@0.9.7 + +## @backstage/plugin-events-backend-module-aws-sqs@0.4.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-events-node@0.4.20 + +## @backstage/plugin-events-backend-module-azure@0.2.29 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-events-node@0.4.20 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.29 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-events-node@0.4.20 + +## @backstage/plugin-events-backend-module-bitbucket-server@0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-events-node@0.4.20 + +## @backstage/plugin-events-backend-module-gerrit@0.2.29 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-events-node@0.4.20 + +## @backstage/plugin-events-backend-module-github@0.4.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/integration@2.0.0 + - @backstage/plugin-events-node@0.4.20 + +## @backstage/plugin-events-backend-module-gitlab@0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-events-node@0.4.20 + +## @backstage/plugin-events-backend-module-google-pubsub@0.2.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/filter-predicates@0.1.1 + - @backstage/plugin-events-node@0.4.20 + +## @backstage/plugin-events-backend-module-kafka@0.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-events-node@0.4.20 + +## @backstage/plugin-events-backend-test-utils@0.1.53 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.20 + +## @backstage/plugin-events-node@0.4.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + +## @backstage/plugin-gateway-backend@1.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + +## @backstage/plugin-home@0.9.3 + +### Patch Changes + +- 538c985: Updated installation documentation to use feature discovery as the default. +- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point. +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/core-compat-api@0.5.9 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/catalog-client@1.14.0 + - @backstage/core-app-api@1.19.6 + - @backstage/plugin-home-react@0.1.36 + - @backstage/catalog-model@1.7.7 + +## @backstage/plugin-home-react@0.1.36 + +### Patch Changes + +- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point. +- Updated dependencies + - @backstage/core-compat-api@0.5.9 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + +## @backstage/plugin-kubernetes@0.12.17 + +### Patch Changes + +- 538c985: Updated installation documentation to use feature discovery as the default. +- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point. +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/plugin-permission-react@0.4.41 + - @backstage/plugin-kubernetes-react@0.5.17 + - @backstage/catalog-model@1.7.7 + +## @backstage/plugin-kubernetes-backend@0.21.2 + +### Patch Changes + +- 6b6b5de: Added `endpointType` config option to the GKE cluster locator, allowing use of DNS-based control plane endpoints instead of public IP endpoints. Set `endpointType: 'dns'` to use GKE DNS endpoints (e.g. `gke-..gke.goog`) which provide proper TLS certificates and IAM-based access control. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/catalog-client@1.14.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/plugin-permission-node@0.10.11 + - @backstage/catalog-model@1.7.7 + - @backstage/plugin-kubernetes-node@0.4.2 + +## @backstage/plugin-kubernetes-cluster@0.0.35 + +### Patch Changes + +- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point. +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/plugin-permission-react@0.4.41 + - @backstage/plugin-kubernetes-react@0.5.17 + - @backstage/catalog-model@1.7.7 + +## @backstage/plugin-kubernetes-node@0.4.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/catalog-model@1.7.7 + +## @backstage/plugin-kubernetes-react@0.5.17 + +### Patch Changes + +- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point. +- Updated dependencies + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/catalog-model@1.7.7 + +## @backstage/plugin-mcp-actions-backend@0.1.10 + +### Patch Changes + +- 62f0a53: Fixed error forwarding in the actions registry so that known errors like `InputError` and `NotFoundError` thrown by actions preserve their original status codes and messages instead of being wrapped in `ForwardedError` and coerced to 500. + +- dee4283: Added `mcpActions.name` and `mcpActions.description` config options to customize the MCP server identity. Namespaced tool names now use dot separator to align with the MCP spec convention. + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. + +- c74b697: Added support for splitting MCP actions into multiple servers via `mcpActions.servers` configuration. Each server gets its own endpoint at `/api/mcp-actions/v1/{key}` with actions scoped using include/exclude filter rules. Tool names are now namespaced with the plugin ID by default, configurable via `mcpActions.namespacedToolNames`. When `mcpActions.servers` is not configured, the plugin continues to serve a single server at `/api/mcp-actions/v1`. + +- dc81af1: Adds two new metrics to track MCP server operations and sessions. + + - `mcp.server.operation.duration`: The duration taken to process an individual MCP operation + - `mcp.server.session.duration`: The duration of the MCP session from the perspective of the server + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/catalog-client@1.14.0 + - @backstage/plugin-catalog-node@2.1.0 + +## @backstage/plugin-mui-to-bui@0.2.5 + +### Patch Changes + +- 538c985: Updated installation documentation to use feature discovery as the default. +- ad7c883: Updated the MUI to BUI theme converter page to use the renamed `Header` component from `@backstage/ui`. +- Updated dependencies + - @backstage/ui@0.13.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/frontend-plugin-api@0.15.0 + +## @backstage/plugin-notifications@0.5.15 + +### Patch Changes + +- aa29b50: New frontend system pages now use the default plugin header together with `HeaderPage` instead of the legacy core page header pattern. +- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point. +- Updated dependencies + - @backstage/ui@0.13.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/plugin-signals-react@0.0.20 + +## @backstage/plugin-notifications-backend@0.6.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/catalog-model@1.7.7 + - @backstage/plugin-notifications-node@0.2.24 + - @backstage/plugin-signals-node@0.1.29 + +## @backstage/plugin-notifications-backend-module-email@0.3.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/catalog-client@1.14.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/catalog-model@1.7.7 + - @backstage/plugin-notifications-node@0.2.24 + +## @backstage/plugin-notifications-node@0.2.24 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/catalog-client@1.14.0 + - @backstage/catalog-model@1.7.7 + - @backstage/plugin-signals-node@0.1.29 + +## @backstage/plugin-org-react@0.1.48 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/catalog-client@1.14.0 + - @backstage/catalog-model@1.7.7 + +## @backstage/plugin-permission-backend@0.7.10 + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/plugin-permission-node@0.10.11 + - @backstage/plugin-auth-node@0.6.14 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/plugin-permission-node@0.10.11 + - @backstage/plugin-auth-node@0.6.14 + +## @backstage/plugin-permission-common@0.9.7 + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. + +## @backstage/plugin-permission-node@0.10.11 + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/plugin-auth-node@0.6.14 + +## @backstage/plugin-permission-react@0.4.41 + +### Patch Changes + +- 5fec07d: Permission checks made in the same tick are now batched into a single call to the permission backend. +- Updated dependencies + - @backstage/core-plugin-api@1.12.4 + - @backstage/plugin-permission-common@0.9.7 + +## @backstage/plugin-proxy-backend@0.6.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-proxy-node@0.1.13 + +## @backstage/plugin-proxy-node@0.1.13 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-scaffolder-node@0.13.0 + - @backstage/integration@2.0.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.4 + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-scaffolder-node@0.13.0 + - @backstage/integration@2.0.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.8 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-scaffolder-node@0.13.0 + - @backstage/integration@2.0.0 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-scaffolder-node@0.13.0 + - @backstage/integration@2.0.0 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.21 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/backend-defaults@0.16.0 + - @backstage/plugin-scaffolder-node@0.13.0 + - @backstage/integration@2.0.0 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-scaffolder-node@0.13.0 + - @backstage/integration@2.0.0 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-scaffolder-node@0.13.0 + - @backstage/integration@2.0.0 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-scaffolder-node@0.13.0 + - @backstage/integration@2.0.0 + +## @backstage/plugin-scaffolder-backend-module-github@0.9.7 + +### Patch Changes + +- a761a48: Added optional `returnWorkflowRunDetails` input to `github:actions:dispatch` action. When true, exposes `workflowRunId`, `workflowRunUrl`, and `workflowRunHtmlUrl` as outputs using the GitHub API `return_run_details` parameter. +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- b2591f6: Fixed environment `waitTime` description incorrectly asking for milliseconds instead of minutes. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-scaffolder-node@0.13.0 + - @backstage/integration@2.0.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/catalog-model@1.7.7 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.11.4 + +### Patch Changes + +- 5730c8e: Added `maskedAndHidden` option to `gitlab:projectVariable:create` and `publish:gitlab` action to support creating GitLab project variables that are both masked and hidden. Updated gitbeaker to version 43.8.0 for proper type support. + +- 0c1726a: Added new `gitlab:group:access` scaffolder action to add or remove users and groups as members of GitLab groups. The action supports specifying members via `userIds` and/or `groupIds` array parameters, configurable access levels (Guest, Reporter, Developer, Maintainer, Owner), and defaults to the 'add' action when not specified. + +- 4b8fcf0: Added two optional inputs to the `publish:gitlab` action: + + - `settings.name`: set a custom human-readable project title that differs from the repository slug. + - `ownerUsername`: add a specific GitLab user as project owner (access level 50) of the newly created repository. Requires a privileged token in the integration configuration. + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-scaffolder-node@0.13.0 + - @backstage/integration@2.0.0 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-scaffolder-node@0.13.0 + - @backstage/plugin-notifications-node@0.2.24 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-scaffolder-node@0.13.0 + - @backstage/integration@2.0.0 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-scaffolder-node@0.13.0 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-scaffolder-node@0.13.0 + - @backstage/plugin-scaffolder-node-test-utils@0.3.9 + +## @backstage/plugin-scaffolder-node-test-utils@0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/backend-test-utils@1.11.1 + - @backstage/plugin-scaffolder-node@0.13.0 + +## @backstage/plugin-search-backend-module-catalog@0.3.13 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/catalog-client@1.14.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/catalog-model@1.7.7 + - @backstage/plugin-search-backend-node@1.4.2 + +## @backstage/plugin-search-backend-module-elasticsearch@1.8.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-search-backend-node@1.4.2 + +## @backstage/plugin-search-backend-module-explore@0.3.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-search-backend-node@1.4.2 + +## @backstage/plugin-search-backend-module-pg@0.5.53 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-search-backend-node@1.4.2 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.18 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-search-backend-node@1.4.2 + +## @backstage/plugin-search-backend-module-techdocs@0.4.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/catalog-client@1.14.0 + - @backstage/plugin-techdocs-node@1.14.4 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/catalog-model@1.7.7 + - @backstage/plugin-search-backend-node@1.4.2 + +## @backstage/plugin-search-backend-node@1.4.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-permission-common@0.9.7 + +## @backstage/plugin-signals@0.0.29 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/plugin-signals-react@0.0.20 + +## @backstage/plugin-signals-backend@0.3.13 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-events-node@0.4.20 + - @backstage/plugin-signals-node@0.1.29 + +## @backstage/plugin-signals-node@0.1.29 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-auth-node@0.6.14 + - @backstage/plugin-events-node@0.4.20 + +## @backstage/plugin-signals-react@0.0.20 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.4 + +## @backstage/plugin-techdocs@1.17.1 + +### Patch Changes + +- 9795d30: chore(deps): bump `dompurify` from 3.3.1 to 3.3.2 +- 30e08df: Added `documentation` as the default entity content group for the TechDocs entity content tab. +- 3f36ce1: Updated alpha plugin icons to follow the new frontend icon sizing rules when rendered in plugin and navigation surfaces. +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/plugin-search-react@1.11.0 + - @backstage/catalog-client@1.14.0 + - @backstage/integration@2.0.0 + - @backstage/catalog-model@1.7.7 + - @backstage/integration-react@1.2.16 + - @backstage/plugin-auth-react@0.1.25 + - @backstage/plugin-techdocs-react@1.3.9 + +## @backstage/plugin-techdocs-addons-test-utils@2.0.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/plugin-search-react@1.11.0 + - @backstage/core-app-api@1.19.6 + - @backstage/plugin-techdocs@1.17.1 + - @backstage/plugin-catalog@2.0.0 + - @backstage/integration-react@1.2.16 + - @backstage/test-utils@1.7.16 + - @backstage/plugin-techdocs-react@1.3.9 + +## @backstage/plugin-techdocs-backend@2.1.6 + +### Patch Changes + +- cb7c6b1: Added `techdocs.generator.mkdocs.dangerouslyAllowAdditionalKeys` configuration option to explicitly bypass MkDocs configuration key restrictions. This enables support for additional MkDocs configuration keys beyond the default safe allow list, such as the `hooks` key which some MkDocs plugins require. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/catalog-client@1.14.0 + - @backstage/plugin-techdocs-node@1.14.4 + - @backstage/integration@2.0.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/catalog-model@1.7.7 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.34 + +### Patch Changes + +- 895563a: Avoid enabling the TechDocs LightBox addon for images wrapped in links, so image links keep working. +- Updated dependencies + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/integration@2.0.0 + - @backstage/integration-react@1.2.16 + - @backstage/plugin-techdocs-react@1.3.9 + +## @backstage/plugin-techdocs-node@1.14.4 + +### Patch Changes + +- cb7c6b1: Added `techdocs.generator.mkdocs.dangerouslyAllowAdditionalKeys` configuration option to explicitly bypass MkDocs configuration key restrictions. This enables support for additional MkDocs configuration keys beyond the default safe allow list, such as the `hooks` key which some MkDocs plugins require. +- e96f6d9: Removed `INHERIT` from the `ALLOWED_MKDOCS_KEYS` set to address a security concern with MkDocs configuration inheritance. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/integration@2.0.0 + - @backstage/catalog-model@1.7.7 + +## @backstage/plugin-techdocs-react@1.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/catalog-model@1.7.7 + +## @backstage/plugin-user-settings@0.9.1 + +### Patch Changes + +- e26e3de: The `ProviderSettingsItem` `icon` prop now accepts `IconElement` in addition to `IconComponent`. +- 538c985: Updated installation documentation to use feature discovery as the default. +- aa29b50: New frontend system pages now use the default plugin header together with `HeaderPage` instead of the legacy core page header pattern. +- 3f36ce1: Updated alpha plugin icons to follow the new frontend icon sizing rules when rendered in plugin and navigation surfaces. +- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point. +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/ui@0.13.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/core-app-api@1.19.6 + - @backstage/catalog-model@1.7.7 + - @backstage/plugin-signals-react@0.0.20 + +## @backstage/plugin-user-settings-backend@0.4.1 + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-auth-node@0.6.14 + - @backstage/plugin-signals-node@0.1.29 + +## example-app@0.0.33 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.36.0 + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/ui@0.13.0 + - @backstage/core-compat-api@0.5.9 + - @backstage/frontend-app-api@0.16.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/plugin-app-visualizer@0.2.1 + - @backstage/plugin-auth@0.1.6 + - @backstage/core-components@0.18.8 + - @backstage/plugin-user-settings@0.9.1 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/plugin-search-react@1.11.0 + - @backstage/plugin-search@1.7.0 + - @backstage/plugin-api-docs@0.13.5 + - @backstage/plugin-devtools@0.1.37 + - @backstage/core-app-api@1.19.6 + - @backstage/plugin-scaffolder@1.36.0 + - @backstage/plugin-techdocs@1.17.1 + - @backstage/plugin-catalog@2.0.0 + - @backstage/plugin-catalog-graph@0.6.0 + - @backstage/plugin-catalog-import@0.13.11 + - @backstage/plugin-catalog-unprocessed-entities@0.2.27 + - @backstage/plugin-home@0.9.3 + - @backstage/plugin-kubernetes@0.12.17 + - @backstage/plugin-org@0.7.0 + - @backstage/plugin-scaffolder-react@1.20.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.34 + - @backstage/plugin-app@0.4.1 + - @backstage/plugin-app-react@0.2.1 + - @backstage/plugin-notifications@0.5.15 + - @backstage/plugin-permission-react@0.4.41 + - @backstage/frontend-defaults@0.5.0 + - @backstage/plugin-home-react@0.1.36 + - @backstage/plugin-kubernetes-cluster@0.0.35 + - @backstage/catalog-model@1.7.7 + - @backstage/app-defaults@1.7.6 + - @backstage/integration-react@1.2.16 + - @backstage/plugin-auth-react@0.1.25 + - @backstage/plugin-signals@0.0.29 + - @backstage/plugin-techdocs-react@1.3.9 + +## app-example-plugin@0.0.33 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + +## example-app-legacy@0.2.119 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.36.0 + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/ui@0.13.0 + - @backstage/frontend-app-api@0.16.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/plugin-user-settings@0.9.1 + - @backstage/plugin-search-react@1.11.0 + - @backstage/plugin-search@1.7.0 + - @backstage/plugin-api-docs@0.13.5 + - @backstage/plugin-devtools@0.1.37 + - @backstage/core-app-api@1.19.6 + - @backstage/plugin-scaffolder@1.36.0 + - @backstage/plugin-techdocs@1.17.1 + - @backstage/plugin-catalog@2.0.0 + - @backstage/plugin-catalog-graph@0.6.0 + - @backstage/plugin-catalog-import@0.13.11 + - @backstage/plugin-catalog-unprocessed-entities@0.2.27 + - @backstage/plugin-home@0.9.3 + - @backstage/plugin-kubernetes@0.12.17 + - @backstage/plugin-mui-to-bui@0.2.5 + - @backstage/plugin-org@0.7.0 + - @backstage/plugin-scaffolder-react@1.20.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.34 + - @backstage/plugin-notifications@0.5.15 + - @backstage/plugin-permission-react@0.4.41 + - @backstage/plugin-home-react@0.1.36 + - @backstage/plugin-kubernetes-cluster@0.0.35 + - @backstage/catalog-model@1.7.7 + - @backstage/app-defaults@1.7.6 + - @backstage/integration-react@1.2.16 + - @backstage/plugin-auth-react@0.1.25 + - @backstage/plugin-signals@0.0.29 + - @backstage/plugin-techdocs-react@1.3.9 + +## example-backend@0.0.48 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/backend-defaults@0.16.0 + - @backstage/plugin-catalog-backend@3.5.0 + - @backstage/plugin-auth-backend@0.27.2 + - @backstage/plugin-techdocs-backend@2.1.6 + - @backstage/plugin-scaffolder-backend-module-github@0.9.7 + - @backstage/plugin-scaffolder-backend@3.2.0 + - @backstage/plugin-mcp-actions-backend@0.1.10 + - @backstage/plugin-kubernetes-backend@0.21.2 + - @backstage/plugin-events-backend@0.6.0 + - @backstage/plugin-search-backend@2.1.0 + - @backstage/plugin-auth-backend-module-openshift-provider@0.1.5 + - @backstage/plugin-auth-backend-module-github-provider@0.5.1 + - @backstage/plugin-permission-backend@0.7.10 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/plugin-permission-node@0.10.11 + - @backstage/catalog-model@1.7.7 + - @backstage/plugin-auth-node@0.6.14 + - @backstage/plugin-app-backend@0.5.12 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.17 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.12 + - @backstage/plugin-catalog-backend-module-openapi@0.2.20 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.18 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.9 + - @backstage/plugin-devtools-backend@0.5.15 + - @backstage/plugin-events-backend-module-google-pubsub@0.2.1 + - @backstage/plugin-notifications-backend@0.6.3 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.17 + - @backstage/plugin-proxy-backend@0.6.11 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.20 + - @backstage/plugin-search-backend-module-catalog@0.3.13 + - @backstage/plugin-search-backend-module-elasticsearch@1.8.1 + - @backstage/plugin-search-backend-module-explore@0.3.12 + - @backstage/plugin-search-backend-module-techdocs@0.4.12 + - @backstage/plugin-search-backend-node@1.4.2 + - @backstage/plugin-signals-backend@0.3.13 + +## @internal/cli@0.0.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-node@0.3.0 + +## e2e-test@0.2.38 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.2.0 + - @backstage/create-app@0.8.0 + +## @internal/frontend@0.0.18 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/filter-predicates@0.1.1 + +## @internal/scaffolder@0.0.19 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/plugin-scaffolder-react@1.20.0 + +## techdocs-cli-embedded-app@0.2.118 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.36.0 + - @backstage/ui@0.13.0 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/core-app-api@1.19.6 + - @backstage/plugin-techdocs@1.17.1 + - @backstage/plugin-catalog@2.0.0 + - @backstage/plugin-app-react@0.2.1 + - @backstage/frontend-defaults@0.5.0 + - @backstage/catalog-model@1.7.7 + - @backstage/integration-react@1.2.16 + - @backstage/test-utils@1.7.16 + - @backstage/plugin-techdocs-react@1.3.9 + +## yarn-plugin-backstage@0.0.10 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.2.0 + +## @internal/plugin-todo-list@1.0.49 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + +## @internal/plugin-todo-list-backend@1.0.48 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 diff --git a/docs/releases/v1.49.0-next.0-changelog.md b/docs/releases/v1.49.0-next.0-changelog.md new file mode 100644 index 0000000000..fcfc092d5e --- /dev/null +++ b/docs/releases/v1.49.0-next.0-changelog.md @@ -0,0 +1,2362 @@ +# Release v1.49.0-next.0 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.49.0-next.0](https://backstage.github.io/upgrade-helper/?to=1.49.0-next.0) + +## @backstage/cli-common@0.2.0-next.0 + +### Minor Changes + +- 56bd494: Added `targetPaths` and `findOwnPaths` as replacements for `findPaths`, with a cleaner separation between target project paths and package-relative paths. + + To migrate existing `findPaths` usage: + + ```ts + // Before + import { findPaths } from '@backstage/cli-common'; + const paths = findPaths(__dirname); + + // After — for target project paths (cwd-based): + import { targetPaths } from '@backstage/cli-common'; + // paths.targetDir → targetPaths.dir + // paths.targetRoot → targetPaths.rootDir + // paths.resolveTarget('src') → targetPaths.resolve('src') + // paths.resolveTargetRoot('yarn.lock') → targetPaths.resolveRoot('yarn.lock') + + // After — for package-relative paths: + import { findOwnPaths } from '@backstage/cli-common'; + const own = findOwnPaths(__dirname); + // paths.ownDir → own.dir + // paths.ownRoot → own.rootDir + // paths.resolveOwn('config/jest.js') → own.resolve('config/jest.js') + // paths.resolveOwnRoot('tsconfig.json') → own.resolveRoot('tsconfig.json') + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.7 + +## @backstage/integration@1.21.0-next.0 + +### Minor Changes + +- d933f62: Add configurable throttling and retry mechanism for GitLab integration. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-catalog-backend@3.5.0-next.0 + +### Minor Changes + +- bf71677: Added opentelemetry metrics for SCM events: + + - `catalog.events.scm.messages` with attribute `eventType`: Counter for the number of SCM events actually received by the catalog backend. The `eventType` is currently either `location` or `repository`. + +### Patch Changes + +- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1 +- fbf382f: Minor internal optimisation +- 1ee5b28: Migrates existing catalog metrics to use the alpha MetricsService. This release is a 1:1 migration with no breaking changes. +- 3181973: Changed the `search` table foreign key to point to `final_entities` instead of `refresh_state` +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-client@1.13.1-next.0 + - @backstage/backend-openapi-utils@0.6.7-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/filter-predicates@0.1.0 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-events-node@0.4.20-next.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.11-next.0 + +## @backstage/plugin-catalog-node@2.1.0-next.0 + +### Minor Changes + +- bf71677: Added the ability for SCM events subscribers to mark the fact that they have taken actions based on events, which produces output metrics: + + - `catalog.events.scm.actions` with attribute `action`: Counter for the number of actions actually taken by catalog internals or other subscribers, based on SCM events. The `action` is currently either `create`, `delete`, `refresh`, or `move`. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-client@1.13.1-next.0 + - @backstage/backend-test-utils@1.11.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.11-next.0 + +## @backstage/plugin-notifications-backend-module-slack@0.4.0-next.0 + +### Minor Changes + +- 749ba60: Add an extension for custom Slack message layouts + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-notifications-common@0.2.1 + - @backstage/plugin-notifications-node@0.2.24-next.0 + +## @backstage/app-defaults@1.7.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/theme@0.7.2 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## @backstage/backend-app-api@1.5.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/backend-defaults@0.15.3-next.0 + +### Patch Changes + +- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1 +- d933f62: Add configurable throttling and retry mechanism for GitLab integration. +- b99158a: Fixed `yarn backstage-cli config:check --strict --config app-config.yaml` config validation error by adding + an optional `default` type discriminator to PostgreSQL connection configuration, + allowing `config:check` to properly validate `default` connection configurations. +- 1ee5b28: Adds an alpha `MetricsService` to provide a unified interface for metrics instrumentation across Backstage plugins. +- Updated dependencies + - @backstage/cli-node@0.2.19-next.0 + - @backstage/integration@1.21.0-next.0 + - @backstage/config-loader@1.10.9-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/backend-app-api@1.5.1-next.0 + - @backstage/backend-dev-utils@0.1.7 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.20 + - @backstage/types@1.2.2 + - @backstage/plugin-auth-node@0.6.14-next.0 + - @backstage/plugin-events-node@0.4.20-next.0 + - @backstage/plugin-permission-node@0.10.11-next.0 + +## @backstage/backend-dynamic-feature-service@0.7.10-next.0 + +### Patch Changes + +- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`. +- Updated dependencies + - @backstage/cli-common@0.2.0-next.0 + - @backstage/cli-node@0.2.19-next.0 + - @backstage/backend-defaults@0.15.3-next.0 + - @backstage/plugin-catalog-backend@3.5.0-next.0 + - @backstage/config-loader@1.10.9-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/backend-openapi-utils@0.6.7-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-app-node@0.1.43-next.0 + - @backstage/plugin-auth-node@0.6.14-next.0 + - @backstage/plugin-events-backend@0.5.12-next.0 + - @backstage/plugin-events-node@0.4.20-next.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.11-next.0 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + - @backstage/plugin-search-backend-node@1.4.2-next.0 + - @backstage/plugin-search-common@1.2.22 + +## @backstage/backend-openapi-utils@0.6.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/backend-plugin-api@1.7.1-next.0 + +### Patch Changes + +- 1ee5b28: Adds an alpha `MetricsService` to provide a unified interface for metrics instrumentation across Backstage plugins. +- Updated dependencies + - @backstage/cli-common@0.2.0-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-auth-node@0.6.14-next.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.11-next.0 + +## @backstage/backend-test-utils@1.11.1-next.0 + +### Patch Changes + +- 1ee5b28: Adds a new metrics service mock to be leveraged in tests +- Updated dependencies + - @backstage/backend-defaults@0.15.3-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/backend-app-api@1.5.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-auth-node@0.6.14-next.0 + - @backstage/plugin-events-node@0.4.20-next.0 + - @backstage/plugin-permission-common@0.9.6 + +## @backstage/catalog-client@1.13.1-next.0 + +### Patch Changes + +- d2494d6: Minor update to catalog client docs +- Updated dependencies + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/filter-predicates@0.1.0 + +## @backstage/cli@0.35.5-next.0 + +### Patch Changes + +- 246877a: Updated dependency `bfj` to `^9.0.2`. + +- bba2e49: Internal refactor to use new concurrency utilities from `@backstage/cli-node`. + +- fd50cb3: Added `translations export` and `translations import` commands for managing translation files. + + The `translations export` command discovers all `TranslationRef` definitions across frontend plugin dependencies and exports their default messages as JSON files. The `translations import` command generates `TranslationResource` wiring code from translated JSON files, ready to be plugged into the app. + + Both commands support a `--pattern` option for controlling the message file layout, for example `--pattern '{lang}/{id}.json'` for language-based directory grouping. + +- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1 + +- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`. + +- de62a9d: Upgraded `commander` dependency from `^12.0.0` to `^14.0.3` across all CLI packages. + +- 092b41f: Updated dependency `webpack` to `~5.105.0`. + +- Updated dependencies + - @backstage/cli-common@0.2.0-next.0 + - @backstage/cli-node@0.2.19-next.0 + - @backstage/eslint-plugin@0.2.2-next.0 + - @backstage/integration@1.21.0-next.0 + - @backstage/config-loader@1.10.9-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/module-federation-common@0.1.0 + - @backstage/release-manifests@0.0.13 + - @backstage/types@1.2.2 + +## @backstage/cli-node@0.2.19-next.0 + +### Patch Changes + +- 06c2015: Added `runConcurrentTasks` and `runWorkerQueueThreads` utilities, moved from the `@backstage/cli` internal code. +- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`. +- Updated dependencies + - @backstage/cli-common@0.2.0-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/codemods@0.1.55-next.0 + +### Patch Changes + +- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`. +- de62a9d: Upgraded `commander` dependency from `^12.0.0` to `^14.0.3` across all CLI packages. +- Updated dependencies + - @backstage/cli-common@0.2.0-next.0 + +## @backstage/config-loader@1.10.9-next.0 + +### Patch Changes + +- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`. +- Updated dependencies + - @backstage/cli-common@0.2.0-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/core-app-api@1.19.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + +## @backstage/core-compat-api@0.5.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-app-react@0.2.1-next.0 + +## @backstage/core-components@0.18.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/theme@0.7.2 + - @backstage/version-bridge@1.0.12 + +## @backstage/core-plugin-api@1.12.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + +## @backstage/create-app@0.7.10-next.0 + +### Patch Changes + +- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`. +- de62a9d: Upgraded `commander` dependency from `^12.0.0` to `^14.0.3` across all CLI packages. +- Updated dependencies + - @backstage/cli-common@0.2.0-next.0 + +## @backstage/dev-utils@1.1.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.12.1-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/app-defaults@1.7.6-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/integration-react@1.2.16-next.0 + - @backstage/theme@0.7.2 + +## @backstage/eslint-plugin@0.2.2-next.0 + +### Patch Changes + +- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1 + +## @backstage/frontend-app-api@0.15.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/config@1.3.6 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/frontend-defaults@0.4.1-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + +## @backstage/frontend-defaults@0.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/plugin-app@0.4.1-next.0 + - @backstage/config@1.3.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/errors@1.2.7 + - @backstage/frontend-app-api@0.15.1-next.0 + +## @backstage/frontend-dynamic-feature-loader@0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/config@1.3.6 + - @backstage/module-federation-common@0.1.0 + +## @backstage/frontend-plugin-api@0.14.2-next.0 + +### Patch Changes + +- 9c81af9: Made the `pluginId` property optional in the `FrontendFeature` type, allowing plugins published against older versions of the framework to be used without type errors. +- Updated dependencies + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + +## @backstage/frontend-test-utils@0.5.1-next.0 + +### Patch Changes + +- 909c742: Switched `MockTranslationApi` and related test utility imports from `@backstage/core-plugin-api/alpha` to the stable `@backstage/frontend-plugin-api` export. The `TranslationApi` type in the API report is now sourced from a single package. This has no effect on runtime behavior. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/plugin-app@0.4.1-next.0 + - @backstage/config@1.3.6 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/frontend-app-api@0.15.1-next.0 + - @backstage/test-utils@1.7.16-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-app-react@0.2.1-next.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## @backstage/integration-react@1.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/config@1.3.6 + - @backstage/core-plugin-api@1.12.4-next.0 + +## @backstage/repo-tools@0.16.6-next.0 + +### Patch Changes + +- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1 +- 2a51546: Fixed prettier existence checks in OpenAPI commands to use `fs.pathExists` instead of checking the resolved path string, which was always truthy. +- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`. +- de62a9d: Upgraded `commander` dependency from `^12.0.0` to `^14.0.3` across all CLI packages. +- 18a946c: Updated `@microsoft/api-extractor` to `7.57.3` and added tests for `getTsDocConfig` +- Updated dependencies + - @backstage/cli-common@0.2.0-next.0 + - @backstage/cli-node@0.2.19-next.0 + - @backstage/config-loader@1.10.9-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + +## @techdocs/cli@1.10.6-next.0 + +### Patch Changes + +- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`. +- de62a9d: Upgraded `commander` dependency from `^12.0.0` to `^14.0.3` across all CLI packages. +- Updated dependencies + - @backstage/cli-common@0.2.0-next.0 + - @backstage/backend-defaults@0.15.3-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-techdocs-node@1.14.3-next.0 + +## @backstage/test-utils@1.7.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/theme@0.7.2 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## @backstage/ui@0.12.1-next.0 + +### Patch Changes + +- a1f4bee: Made Accordion a `bg` provider so nested components like Button auto-increment their background level. Updated `useDefinition` to resolve `bg` `propDef` defaults for provider components. + +- 8909359: Fixed focus-visible outline styles for Menu and Select components. + + **Affected components:** Menu, Select + +- 0f462f8: Improved type safety in `useDefinition` by centralizing prop resolution and strengthening the `BgPropsConstraint` to require that `bg` provider components declare `children` as a required prop in their OwnProps type. + +- 8909359: Added proper cursor styles for RadioGroup items. + + **Affected components:** RadioGroup + +- Updated dependencies + - @backstage/version-bridge@1.0.12 + +## @backstage/plugin-api-docs@0.13.5-next.0 + +### Patch Changes + +- 9c9d425: Fixed invisible text in parameter input fields when using dark mode in OpenAPI definition pages +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/plugin-catalog@1.33.1-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## @backstage/plugin-app@0.4.1-next.0 + +### Patch Changes + +- 909c742: Switched translation API imports (`translationApiRef`, `appLanguageApiRef`) from the alpha `@backstage/core-plugin-api/alpha` path to the stable `@backstage/frontend-plugin-api` export. This has no effect on runtime behavior. +- Updated dependencies + - @backstage/ui@0.12.1-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/integration-react@1.2.16-next.0 + - @backstage/theme@0.7.2 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-app-react@0.2.1-next.0 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## @backstage/plugin-app-backend@0.5.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.9-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-app-node@0.1.43-next.0 + - @backstage/plugin-auth-node@0.6.14-next.0 + +## @backstage/plugin-app-node@0.1.43-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.9-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + +## @backstage/plugin-app-react@0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + +## @backstage/plugin-app-visualizer@0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.12.1-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + +## @backstage/plugin-auth@0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/errors@1.2.7 + - @backstage/theme@0.7.2 + +## @backstage/plugin-auth-backend@0.27.1-next.0 + +### Patch Changes + +- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1 +- 619be54: Update migrations to be reversible +- Updated dependencies + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-auth-node@0.6.14-next.0 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/plugin-auth-node@0.6.14-next.0 + +## @backstage/plugin-auth-backend-module-auth0-provider@0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-auth-node@0.6.14-next.0 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.27.1-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-auth-node@0.6.14-next.0 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-auth-node@0.6.14-next.0 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/plugin-auth-node@0.6.14-next.0 + +## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/plugin-auth-node@0.6.14-next.0 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-auth-node@0.6.14-next.0 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-auth-node@0.6.14-next.0 + +## @backstage/plugin-auth-backend-module-github-provider@0.5.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/plugin-auth-node@0.6.14-next.0 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/plugin-auth-node@0.6.14-next.0 + +## @backstage/plugin-auth-backend-module-google-provider@0.3.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/plugin-auth-node@0.6.14-next.0 + +## @backstage/plugin-auth-backend-module-guest-provider@0.2.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-auth-node@0.6.14-next.0 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/plugin-auth-node@0.6.14-next.0 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/plugin-auth-node@0.6.14-next.0 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-auth-node@0.6.14-next.0 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.4.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.27.1-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + - @backstage/plugin-auth-node@0.6.14-next.0 + +## @backstage/plugin-auth-backend-module-okta-provider@0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/plugin-auth-node@0.6.14-next.0 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/plugin-auth-node@0.6.14-next.0 + +## @backstage/plugin-auth-backend-module-openshift-provider@0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + - @backstage/plugin-auth-node@0.6.14-next.0 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + - @backstage/plugin-auth-node@0.6.14-next.0 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-auth-node@0.6.14-next.0 + +## @backstage/plugin-auth-node@0.6.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-client@1.13.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-auth-react@0.1.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + +## @backstage/plugin-bitbucket-cloud-common@0.3.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + +## @backstage/plugin-catalog@1.33.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.12.1-next.0 + - @backstage/plugin-search-react@1.10.5-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/catalog-client@1.13.1-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/core-compat-api@0.5.9-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.16-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-react@0.4.41-next.0 + - @backstage/plugin-scaffolder-common@1.7.7-next.0 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-react@1.3.9-next.0 + +## @backstage/plugin-catalog-backend-module-aws@0.4.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.3-next.0 + - @backstage/integration@1.21.0-next.0 + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.20 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-kubernetes-common@0.9.10 + +## @backstage/plugin-catalog-backend-module-azure@0.3.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.8 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/backend-openapi-utils@0.6.7-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-bitbucket-cloud-common@0.3.8-next.0 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-events-node@0.4.20-next.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-events-node@0.4.20-next.0 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-kubernetes-common@0.9.10 + +## @backstage/plugin-catalog-backend-module-gerrit@0.3.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.8 + +## @backstage/plugin-catalog-backend-module-gitea@0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.8 + +## @backstage/plugin-catalog-backend-module-github@0.12.3-next.0 + +### Patch Changes + +- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1 +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-events-node@0.4.20-next.0 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-github@0.12.3-next.0 + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-events-node@0.4.20-next.0 + +## @backstage/plugin-catalog-backend-module-gitlab@0.8.1-next.0 + +### Patch Changes + +- d933f62: Add configurable throttling and retry mechanism for GitLab integration. +- Updated dependencies + - @backstage/backend-defaults@0.15.3-next.0 + - @backstage/integration@1.21.0-next.0 + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-events-node@0.4.20-next.0 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-gitlab@0.8.1-next.0 + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/plugin-events-node@0.4.20-next.0 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.3-next.0 + - @backstage/plugin-catalog-backend@3.5.0-next.0 + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.20-next.0 + - @backstage/plugin-permission-common@0.9.6 + +## @backstage/plugin-catalog-backend-module-ldap@0.12.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.8 + +## @backstage/plugin-catalog-backend-module-logs@0.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.5.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/plugin-events-node@0.4.20-next.0 + +## @backstage/plugin-catalog-backend-module-msgraph@0.9.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.8 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.8 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-scaffolder-common@1.7.7-next.0 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.6.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-auth-node@0.6.14-next.0 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.13 + - @backstage/plugin-permission-common@0.9.6 + +## @backstage/plugin-catalog-graph@0.5.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/catalog-client@1.13.1-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/types@1.2.2 + +## @backstage/plugin-catalog-import@0.13.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/integration@1.21.0-next.0 + - @backstage/catalog-client@1.13.1-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.16-next.0 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## @backstage/plugin-catalog-react@2.0.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.12.1-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/frontend-test-utils@0.5.1-next.0 + - @backstage/catalog-client@1.13.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/core-compat-api@0.5.9-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/filter-predicates@0.1.0 + - @backstage/integration-react@1.2.16-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/core-compat-api@0.5.9-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.13 + - @backstage/plugin-devtools-react@0.1.2-next.0 + +## @backstage/plugin-config-schema@0.1.78-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-devtools@0.1.37-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/core-compat-api@0.5.9-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-devtools-common@0.1.22 + - @backstage/plugin-devtools-react@0.1.2-next.0 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## @backstage/plugin-devtools-backend@0.5.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.2.0-next.0 + - @backstage/config-loader@1.10.9-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-devtools-common@0.1.22 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.11-next.0 + +## @backstage/plugin-devtools-react@0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + +## @backstage/plugin-events-backend@0.5.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/backend-openapi-utils@0.6.7-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.20-next.0 + +## @backstage/plugin-events-backend-module-aws-sqs@0.4.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.20-next.0 + +## @backstage/plugin-events-backend-module-azure@0.2.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/plugin-events-node@0.4.20-next.0 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/plugin-events-node@0.4.20-next.0 + +## @backstage/plugin-events-backend-module-bitbucket-server@0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/plugin-events-node@0.4.20-next.0 + +## @backstage/plugin-events-backend-module-gerrit@0.2.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/plugin-events-node@0.4.20-next.0 + +## @backstage/plugin-events-backend-module-github@0.4.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.20-next.0 + +## @backstage/plugin-events-backend-module-gitlab@0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-events-node@0.4.20-next.0 + +## @backstage/plugin-events-backend-module-google-pubsub@0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/filter-predicates@0.1.0 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.20-next.0 + +## @backstage/plugin-events-backend-module-kafka@0.3.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.20-next.0 + +## @backstage/plugin-events-backend-test-utils@0.1.53-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.20-next.0 + +## @backstage/plugin-events-node@0.4.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-gateway-backend@1.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + +## @backstage/plugin-home@0.9.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/catalog-client@1.13.1-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-compat-api@0.5.9-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/theme@0.7.2 + - @backstage/plugin-home-react@0.1.36-next.0 + +## @backstage/plugin-home-react@0.1.36-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/core-compat-api@0.5.9-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + +## @backstage/plugin-kubernetes@0.12.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/plugin-kubernetes-common@0.9.10 + - @backstage/plugin-kubernetes-react@0.5.17-next.0 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## @backstage/plugin-kubernetes-backend@0.21.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-client@1.13.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.20 + - @backstage/types@1.2.2 + - @backstage/plugin-kubernetes-common@0.9.10 + - @backstage/plugin-kubernetes-node@0.4.2-next.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.11-next.0 + +## @backstage/plugin-kubernetes-cluster@0.0.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/plugin-kubernetes-common@0.9.10 + - @backstage/plugin-kubernetes-react@0.5.17-next.0 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## @backstage/plugin-kubernetes-node@0.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + - @backstage/plugin-kubernetes-common@0.9.10 + +## @backstage/plugin-kubernetes-react@0.5.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-kubernetes-common@0.9.10 + +## @backstage/plugin-mcp-actions-backend@0.1.10-next.0 + +### Patch Changes + +- dc81af1: Adds two new metrics to track MCP server operations and sessions. + + - `mcp.server.operation.duration`: The duration taken to process an individual MCP operation + - `mcp.server.session.duration`: The duration of the MCP session from the perspective of the server + +- Updated dependencies + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-client@1.13.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-mui-to-bui@0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.12.1-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/theme@0.7.2 + +## @backstage/plugin-notifications@0.5.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/theme@0.7.2 + - @backstage/plugin-notifications-common@0.2.1 + - @backstage/plugin-signals-react@0.0.20-next.0 + +## @backstage/plugin-notifications-backend@0.6.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-notifications-common@0.2.1 + - @backstage/plugin-notifications-node@0.2.24-next.0 + - @backstage/plugin-signals-node@0.1.29-next.0 + +## @backstage/plugin-notifications-backend-module-email@0.3.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-client@1.13.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.20 + - @backstage/types@1.2.2 + - @backstage/plugin-notifications-common@0.2.1 + - @backstage/plugin-notifications-node@0.2.24-next.0 + +## @backstage/plugin-notifications-node@0.2.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-client@1.13.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-notifications-common@0.2.1 + - @backstage/plugin-signals-node@0.1.29-next.0 + +## @backstage/plugin-org@0.6.50-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/plugin-catalog-common@1.1.8 + +## @backstage/plugin-org-react@0.1.48-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.13.1-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + +## @backstage/plugin-permission-backend@0.7.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-auth-node@0.6.14-next.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.11-next.0 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/plugin-auth-node@0.6.14-next.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.11-next.0 + +## @backstage/plugin-permission-node@0.10.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-auth-node@0.6.14-next.0 + - @backstage/plugin-permission-common@0.9.6 + +## @backstage/plugin-permission-react@0.4.41-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/plugin-permission-common@0.9.6 + +## @backstage/plugin-proxy-backend@0.6.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/types@1.2.2 + - @backstage/plugin-proxy-node@0.1.13-next.0 + +## @backstage/plugin-proxy-node@0.1.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + +## @backstage/plugin-scaffolder@1.35.5-next.0 + +### Patch Changes + +- bd5b842: Added a new `ui:autoSelect` option to the EntityPicker field that controls whether an entity is automatically selected when the field loses focus. When set to `false`, the field will remain empty if the user closes it without explicitly selecting an entity, preventing unintentional selections. Defaults to `true` for backward compatibility. +- ee87720: Added back the `formFieldsApiRef` and `ScaffolderFormFieldsApi` alpha exports that were unintentionally removed. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/integration@1.21.0-next.0 + - @backstage/catalog-client@1.13.1-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.16-next.0 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-react@0.4.41-next.0 + - @backstage/plugin-scaffolder-common@1.7.7-next.0 + - @backstage/plugin-scaffolder-react@1.19.8-next.0 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-react@1.3.9-next.0 + +## @backstage/plugin-scaffolder-backend@3.1.4-next.0 + +### Patch Changes + +- 4e39e63: Removed unused dependencies +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/backend-openapi-utils@0.6.7-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.20-next.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.11-next.0 + - @backstage/plugin-scaffolder-common@1.7.7-next.0 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.4-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.19-next.0 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-bitbucket-cloud-common@0.3.8-next.0 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.3-next.0 + - @backstage/integration@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + +## @backstage/plugin-scaffolder-backend-module-github@0.9.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.11.4-next.0 + +### Patch Changes + +- 5730c8e: Added `maskedAndHidden` option to `gitlab:projectVariable:create` and `publish:gitlab` action to support creating GitLab project variables that are both masked and hidden. Updated gitbeaker to version 43.8.0 for proper type support. +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/plugin-notifications-common@0.2.1 + - @backstage/plugin-notifications-node@0.2.24-next.0 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.3.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + - @backstage/plugin-scaffolder-node-test-utils@0.3.9-next.0 + +## @backstage/plugin-scaffolder-common@1.7.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.6 + +## @backstage/plugin-scaffolder-node@0.12.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-scaffolder-common@1.7.7-next.0 + +## @backstage/plugin-scaffolder-node-test-utils@0.3.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/backend-test-utils@1.11.1-next.0 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + +## @backstage/plugin-scaffolder-react@1.19.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/catalog-client@1.13.1-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/theme@0.7.2 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-permission-react@0.4.41-next.0 + - @backstage/plugin-scaffolder-common@1.7.7-next.0 + +## @backstage/plugin-search@1.6.2-next.0 + +### Patch Changes + +- d5eb954: Fixes the search component not registering the first search on navigate to the search page. +- Updated dependencies + - @backstage/plugin-search-react@1.10.5-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-search-common@1.2.22 + +## @backstage/plugin-search-backend@2.0.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/backend-openapi-utils@0.6.7-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.11-next.0 + - @backstage/plugin-search-backend-node@1.4.2-next.0 + - @backstage/plugin-search-common@1.2.22 + +## @backstage/plugin-search-backend-module-catalog@0.3.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-client@1.13.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-search-backend-node@1.4.2-next.0 + - @backstage/plugin-search-common@1.2.22 + +## @backstage/plugin-search-backend-module-elasticsearch@1.8.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.20 + - @backstage/plugin-search-backend-node@1.4.2-next.0 + - @backstage/plugin-search-common@1.2.22 + +## @backstage/plugin-search-backend-module-explore@0.3.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-search-backend-node@1.4.2-next.0 + - @backstage/plugin-search-common@1.2.22 + +## @backstage/plugin-search-backend-module-pg@0.5.53-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-search-backend-node@1.4.2-next.0 + - @backstage/plugin-search-common@1.2.22 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-search-backend-node@1.4.2-next.0 + - @backstage/plugin-search-common@1.2.22 + +## @backstage/plugin-search-backend-module-techdocs@0.4.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-client@1.13.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-search-backend-node@1.4.2-next.0 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-techdocs-node@1.14.3-next.0 + +## @backstage/plugin-search-backend-node@1.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-search-common@1.2.22 + +## @backstage/plugin-search-react@1.10.5-next.0 + +### Patch Changes + +- d5eb954: Fixes the search component not registering the first search on navigate to the search page. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/theme@0.7.2 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-search-common@1.2.22 + +## @backstage/plugin-signals@0.0.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/theme@0.7.2 + - @backstage/types@1.2.2 + - @backstage/plugin-signals-react@0.0.20-next.0 + +## @backstage/plugin-signals-backend@0.3.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.20-next.0 + - @backstage/plugin-signals-node@0.1.29-next.0 + +## @backstage/plugin-signals-node@0.1.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + - @backstage/plugin-auth-node@0.6.14-next.0 + - @backstage/plugin-events-node@0.4.20-next.0 + +## @backstage/plugin-signals-react@0.0.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/types@1.2.2 + +## @backstage/plugin-techdocs@1.17.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-react@1.10.5-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/integration@1.21.0-next.0 + - @backstage/catalog-client@1.13.1-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.16-next.0 + - @backstage/theme@0.7.2 + - @backstage/plugin-auth-react@0.1.25-next.0 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-react@1.3.9-next.0 + +## @backstage/plugin-techdocs-addons-test-utils@2.0.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-react@1.10.5-next.0 + - @backstage/plugin-catalog@1.33.1-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/plugin-techdocs@1.17.1-next.0 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/integration-react@1.2.16-next.0 + - @backstage/test-utils@1.7.16-next.0 + - @backstage/plugin-techdocs-react@1.3.9-next.0 + +## @backstage/plugin-techdocs-backend@2.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-client@1.13.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-techdocs-node@1.14.3-next.0 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.34-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/integration@1.21.0-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/integration-react@1.2.16-next.0 + - @backstage/plugin-techdocs-react@1.3.9-next.0 + +## @backstage/plugin-techdocs-node@1.14.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.20 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-techdocs-react@1.3.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-user-settings@0.9.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/theme@0.7.2 + - @backstage/types@1.2.2 + - @backstage/plugin-signals-react@0.0.20-next.0 + - @backstage/plugin-user-settings-common@0.1.0 + +## @backstage/plugin-user-settings-backend@0.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-auth-node@0.6.14-next.0 + - @backstage/plugin-signals-node@0.1.29-next.0 + - @backstage/plugin-user-settings-common@0.1.0 + +## example-app@0.0.33-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.12.1-next.0 + - @backstage/plugin-search-react@1.10.5-next.0 + - @backstage/plugin-search@1.6.2-next.0 + - @backstage/plugin-api-docs@0.13.5-next.0 + - @backstage/cli@0.35.5-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/plugin-scaffolder@1.35.5-next.0 + - @backstage/plugin-app@0.4.1-next.0 + - @backstage/plugin-app-visualizer@0.2.1-next.0 + - @backstage/plugin-catalog@1.33.1-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/plugin-techdocs@1.17.1-next.0 + - @backstage/app-defaults@1.7.6-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-compat-api@0.5.9-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/frontend-app-api@0.15.1-next.0 + - @backstage/frontend-defaults@0.4.1-next.0 + - @backstage/integration-react@1.2.16-next.0 + - @backstage/theme@0.7.2 + - @backstage/plugin-app-react@0.2.1-next.0 + - @backstage/plugin-auth@0.1.6-next.0 + - @backstage/plugin-auth-react@0.1.25-next.0 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-catalog-graph@0.5.8-next.0 + - @backstage/plugin-catalog-import@0.13.11-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.27-next.0 + - @backstage/plugin-devtools@0.1.37-next.0 + - @backstage/plugin-home@0.9.3-next.0 + - @backstage/plugin-home-react@0.1.36-next.0 + - @backstage/plugin-kubernetes@0.12.17-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.35-next.0 + - @backstage/plugin-notifications@0.5.15-next.0 + - @backstage/plugin-org@0.6.50-next.0 + - @backstage/plugin-permission-react@0.4.41-next.0 + - @backstage/plugin-scaffolder-react@1.19.8-next.0 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-signals@0.0.29-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.34-next.0 + - @backstage/plugin-techdocs-react@1.3.9-next.0 + - @backstage/plugin-user-settings@0.9.1-next.0 + +## app-example-plugin@0.0.33-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/core-components@0.18.8-next.0 + +## example-app-legacy@0.2.119-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.12.1-next.0 + - @backstage/plugin-search-react@1.10.5-next.0 + - @backstage/plugin-search@1.6.2-next.0 + - @backstage/plugin-api-docs@0.13.5-next.0 + - @backstage/cli@0.35.5-next.0 + - @backstage/plugin-scaffolder@1.35.5-next.0 + - @backstage/plugin-catalog@1.33.1-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/plugin-mui-to-bui@0.2.5-next.0 + - @backstage/plugin-techdocs@1.17.1-next.0 + - @backstage/app-defaults@1.7.6-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/frontend-app-api@0.15.1-next.0 + - @backstage/integration-react@1.2.16-next.0 + - @backstage/theme@0.7.2 + - @backstage/plugin-auth-react@0.1.25-next.0 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-catalog-graph@0.5.8-next.0 + - @backstage/plugin-catalog-import@0.13.11-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.27-next.0 + - @backstage/plugin-devtools@0.1.37-next.0 + - @backstage/plugin-home@0.9.3-next.0 + - @backstage/plugin-home-react@0.1.36-next.0 + - @backstage/plugin-kubernetes@0.12.17-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.35-next.0 + - @backstage/plugin-notifications@0.5.15-next.0 + - @backstage/plugin-org@0.6.50-next.0 + - @backstage/plugin-permission-react@0.4.41-next.0 + - @backstage/plugin-scaffolder-react@1.19.8-next.0 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-signals@0.0.29-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.34-next.0 + - @backstage/plugin-techdocs-react@1.3.9-next.0 + - @backstage/plugin-user-settings@0.9.1-next.0 + +## example-backend@0.0.48-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.3-next.0 + - @backstage/plugin-auth-backend@0.27.1-next.0 + - @backstage/plugin-catalog-backend@3.5.0-next.0 + - @backstage/plugin-scaffolder-backend@3.1.4-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/plugin-mcp-actions-backend@0.1.10-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-app-backend@0.5.12-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.5.1-next.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.17-next.0 + - @backstage/plugin-auth-backend-module-openshift-provider@0.1.5-next.0 + - @backstage/plugin-auth-node@0.6.14-next.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.12-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.2.20-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.18-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.9-next.0 + - @backstage/plugin-devtools-backend@0.5.15-next.0 + - @backstage/plugin-events-backend@0.5.12-next.0 + - @backstage/plugin-events-backend-module-google-pubsub@0.2.1-next.0 + - @backstage/plugin-kubernetes-backend@0.21.2-next.0 + - @backstage/plugin-notifications-backend@0.6.3-next.0 + - @backstage/plugin-permission-backend@0.7.10-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.17-next.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.11-next.0 + - @backstage/plugin-proxy-backend@0.6.11-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.9.7-next.0 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.20-next.0 + - @backstage/plugin-search-backend@2.0.13-next.0 + - @backstage/plugin-search-backend-module-catalog@0.3.13-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.8.1-next.0 + - @backstage/plugin-search-backend-module-explore@0.3.12-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.4.12-next.0 + - @backstage/plugin-search-backend-node@1.4.2-next.0 + - @backstage/plugin-signals-backend@0.3.13-next.0 + - @backstage/plugin-techdocs-backend@2.1.6-next.0 + +## e2e-test@0.2.38-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.2.0-next.0 + - @backstage/create-app@0.7.10-next.0 + - @backstage/errors@1.2.7 + +## @internal/frontend@0.0.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + +## @internal/scaffolder@0.0.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/plugin-scaffolder-react@1.19.8-next.0 + +## techdocs-cli-embedded-app@0.2.118-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.12.1-next.0 + - @backstage/cli@0.35.5-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/plugin-catalog@1.33.1-next.0 + - @backstage/plugin-techdocs@1.17.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/frontend-defaults@0.4.1-next.0 + - @backstage/integration-react@1.2.16-next.0 + - @backstage/test-utils@1.7.16-next.0 + - @backstage/theme@0.7.2 + - @backstage/plugin-app-react@0.2.1-next.0 + - @backstage/plugin-techdocs-react@1.3.9-next.0 + +## yarn-plugin-backstage@0.0.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.2.0-next.0 + - @backstage/errors@1.2.7 + - @backstage/release-manifests@0.0.13 + +## @internal/plugin-todo-list@1.0.49-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + +## @internal/plugin-todo-list-backend@1.0.48-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/errors@1.2.7 diff --git a/docs/releases/v1.49.0-next.1-changelog.md b/docs/releases/v1.49.0-next.1-changelog.md new file mode 100644 index 0000000000..7110b3d0d5 --- /dev/null +++ b/docs/releases/v1.49.0-next.1-changelog.md @@ -0,0 +1,1567 @@ +# Release v1.49.0-next.1 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.49.0-next.1](https://backstage.github.io/upgrade-helper/?to=1.49.0-next.1) + +## @backstage/integration@2.0.0-next.1 + +### Major Changes + +- 527cf88: **BREAKING** Removed deprecated Azure DevOps, Bitbucket, Gerrit and GitHub code: + + - For Azure DevOps, the long deprecated `token` string and `credential` object have been removed from the `config.d.ts`. Use the `credentials` array object instead. + - For Bitbucket, the long deprecated `bitbucket` object has been removed from the `config.d.ts`. Use the `bitbucketCloud` or `bitbucketServer` objects instead. + - For Gerrit, the `parseGerritGitilesUrl` function has been removed, use `parseGitilesUrlRef` instead. The `buildGerritGitilesArchiveUrl` function has also been removed, use `buildGerritGitilesArchiveUrlFromLocation` instead. + - For GitHub, the `getGitHubRequestOptions` function has been removed. + +### Patch Changes + +- 993a598: Fixed Azure integration config schema visibility annotations to use per-field `@visibility secret` instead of `@deepVisibility secret` on parent objects, so that non-secret fields like `clientId`, `tenantId`, `organizations`, and `managedIdentityClientId` are no longer incorrectly marked as secret. +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-common@2.0.0-next.1 + +### Major Changes + +- 527cf88: **BREAKING** Removed deprecated `bitbucket` integration from being registered in the `ScaffolderClient`. Use the `bitbucketCloud` or `bitbucketServer` integrations instead. + +### Minor Changes + +- f598909: **BREAKING PRODUCERS**: Made `retry`, `listTasks`, `listTemplatingExtensions`, `dryRun`, and `autocomplete` required methods on the `ScaffolderApi` interface. Implementations of `ScaffolderApi` must now provide these methods. + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.6 + +## @backstage/backend-defaults@0.16.0-next.1 + +### Minor Changes + +- 0e7d8f9: The scheduler service now uses the metrics service to create metrics, providing plugin-scoped attribution. +- 527cf88: **BREAKING** Removed deprecated `BitbucketUrlReader`. Use the `BitbucketCloudUrlReader` or the `BitbucketServerUrlReader` instead. + +### Patch Changes + +- 62f0a53: Fixed error forwarding in the actions registry so that known errors like `InputError` and `NotFoundError` thrown by actions preserve their original status codes and messages instead of being wrapped in `ForwardedError` and coerced to 500. +- Updated dependencies + - @backstage/cli-node@0.2.19-next.1 + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.1 + - @backstage/backend-app-api@1.5.1-next.0 + - @backstage/backend-dev-utils@0.1.7 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/config-loader@1.10.9-next.0 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.20 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.20-next.0 + - @backstage/plugin-permission-node@0.10.11-next.0 + +## @backstage/backend-dynamic-feature-service@0.8.0-next.1 + +### Minor Changes + +- 0fbcf23: Migrated OpenAPI schemas to 3.1. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.5.0-next.1 + - @backstage/cli-common@0.2.0-next.1 + - @backstage/cli-node@0.2.19-next.1 + - @backstage/backend-defaults@0.16.0-next.1 + - @backstage/plugin-events-backend@0.6.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.1 + - @backstage/backend-openapi-utils@0.6.7-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/config-loader@1.10.9-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-app-node@0.1.43-next.0 + - @backstage/plugin-events-node@0.4.20-next.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.11-next.0 + - @backstage/plugin-search-backend-node@1.4.2-next.0 + - @backstage/plugin-search-common@1.2.22 + +## @backstage/catalog-client@1.14.0-next.1 + +### Minor Changes + +- 972f686: Added support for the `query` field in `getEntitiesByRefs` requests, enabling predicate-based filtering with `$all`, `$any`, `$not`, `$exists`, `$in`, `$contains`, and `$hasPrefix` operators. +- 56c908e: Added support for the `query` field in `getEntityFacets` requests, enabling predicate-based filtering with `$all`, `$any`, `$not`, `$exists`, `$in`, `$contains`, and `$hasPrefix` operators. +- 0fbcf23: Migrated OpenAPI schemas to 3.1. +- 51e23eb: Added predicate-based entity filtering via POST /entities/by-query endpoint. + + Supports `$all`, `$any`, `$not`, `$exists`, `$in`, `$hasPrefix`, and (partially) `$contains` operators for expressive entity queries. Integrated into the existing `queryEntities` flow with full cursor-based pagination, permission enforcement, and `totalItems` support. + + The catalog client's `queryEntities()` method automatically routes to the POST endpoint when a `query` predicate is provided. + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/filter-predicates@0.1.0 + +## @backstage/cli@0.36.0-next.1 + +### Minor Changes + +- b36a60d: **BREAKING**: The `migrate package-exports` command has been removed. Use `repo fix` instead. + +### Patch Changes + +- 0d2d0f2: Internal refactor of CLI modularization, moving individual commands to be implemented with cleye. +- 2fcba39: Internal refactor to move shared utilities into their consuming modules, reducing cross-module dependencies. +- c85ac86: Internal refactor to split `loadCliConfig` into separate implementations for the build and config CLI modules, removing a cross-module dependency. +- 61cb976: Migrated internal versioning utilities to use `@backstage/cli-node` instead of a local implementation. +- 825c81d: Internal refactor of CLI command modules. +- a9d23c4: Properly support `package.json` `workspaces` field +- Updated dependencies + - @backstage/cli-common@0.2.0-next.1 + - @backstage/cli-node@0.2.19-next.1 + - @backstage/module-federation-common@0.1.2-next.0 + - @backstage/integration@2.0.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/config-loader@1.10.9-next.0 + - @backstage/errors@1.2.7 + - @backstage/eslint-plugin@0.2.2-next.0 + - @backstage/release-manifests@0.0.13 + - @backstage/types@1.2.2 + +## @backstage/repo-tools@0.17.0-next.1 + +### Minor Changes + +- 0fbcf23: Added support for OpenAPI 3.1 to all `schema openapi` commands. The commands now auto-detect the OpenAPI version from the spec file and use the appropriate generator, supporting both OpenAPI 3.0.x and 3.1.x specifications. + +### Patch Changes + +- 426edbe: Fixed `generate-catalog-info` command failing with "too many arguments" when invoked by lint-staged via the pre-commit hook. +- d5779e5: Updated the CLI report parser to support cleye-style help output, and strip ANSI escape codes from captured output. +- Updated dependencies + - @backstage/cli-common@0.2.0-next.1 + - @backstage/cli-node@0.2.19-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config-loader@1.10.9-next.0 + - @backstage/errors@1.2.7 + +## @backstage/ui@0.13.0-next.1 + +### Minor Changes + +- 768f09d: **BREAKING**: Simplified the neutral background prop API for container components. The explicit `neutral-1`, `neutral-2`, `neutral-3`, and `neutral-auto` values have been removed from `ProviderBg`. They are replaced by a single `'neutral'` value that always auto-increments from the parent context, making it impossible to skip or pin to an explicit neutral level. + + **Migration:** + + Replace any explicit `bg="neutral-1"`, `bg="neutral-2"`, `bg="neutral-3"`, or `bg="neutral-auto"` props with `bg="neutral"`. To achieve a specific neutral level in stories or tests, use nested containers — each additional `bg="neutral"` wrapper increments by one level. + + ```tsx + // Before + ... + + // After + + ... + + ``` + + **Affected components:** Box, Flex, Grid, Card, Accordion, Popover, Tooltip, Dialog, Menu + +- b42fcdc: **BREAKING**: Removed `--bui-bg-popover` CSS token. Popover, Tooltip, Menu, and Dialog now use `--bui-bg-app` for their outer shell and `Box bg="neutral-1"` for content areas, providing better theme consistency and eliminating a redundant token. + + **Migration:** + + Replace any usage of `--bui-bg-popover` with `--bui-bg-neutral-1` (for content surfaces) or `--bui-bg-app` (for outer shells): + + ```diff + - background: var(--bui-bg-popover); + + background: var(--bui-bg-neutral-1); + ``` + + **Affected components:** Popover, Tooltip, Menu, Dialog + +- bd3a76e: **BREAKING**: Data attributes rendered by components are now always lowercase. This affects CSS selectors targeting camelCase data attributes. + + **Migration:** + + Update any custom CSS selectors that target camelCase data attributes to use lowercase instead: + + ```diff + - [data-startCollapsed='true'] { ... } + + [data-startcollapsed='true'] { ... } + ``` + + **Affected components:** SearchField + +- 95702ab: **BREAKING**: Removed deprecated types `ComponentDefinition`, `ClassNamesMap`, `DataAttributeValues`, and `DataAttributesMap` from the public API. These were internal styling infrastructure types that have been replaced by the `defineComponent` system. + + **Migration:** + + Remove any direct usage of these types. Component definitions now use `defineComponent()` and their shapes are not part of the public API contract. + + ```diff + - import type { ComponentDefinition, ClassNamesMap } from '@backstage/ui'; + ``` + + If you were reading `definition.dataAttributes`, use `definition.propDefs` instead — props with `dataAttribute: true` in `propDefs` are the equivalent. + +### Patch Changes + +- 58224d3: Fixed neutral-1 hover & pressed state in light mode. + +- 95702ab: Migrated all components from `useStyles` to `useDefinition` hook. Exported `OwnProps` types for each component, enabling better type composition for consumers. + + **Affected components:** Avatar, Checkbox, Container, Dialog, FieldError, FieldLabel, Flex, FullPage, Grid, HeaderPage, Link, Menu, PasswordField, PluginHeader, Popover, RadioGroup, SearchField, Select, Skeleton, Switch, Table, TablePagination, Tabs, TagGroup, Text, TextField, ToggleButton, ToggleButtonGroup, Tooltip, VisuallyHidden + +- 4c2c350: Removed the `transition` on `Container` padding to prevent an unwanted animation when the viewport is resized. + + Affected components: Container + +- d4fa5b4: Fixed tab `matchStrategy` matching to ignore query parameters and hash fragments in tab `href` values. Previously, tabs with query params in their `href` (e.g., `/page?group=foo`) would never show as active since matching compared the full `href` string against `location.pathname` which never includes query params. + + **Affected components:** Tabs, PluginHeader + +- 36987db: Fixed handling of the `style` prop on `Button`, `ButtonIcon`, and `ButtonLink` so that it is now correctly forwarded to the underlying element instead of being silently dropped. + + **Affected components:** Button, ButtonIcon, ButtonLink + +- 95702ab: Fixed Link variant default from `'body'` to `'body-medium'` to match actual CSS selectors. The previous default did not correspond to a valid variant value. + + **Affected components:** Link + +- 9027b10: Fixed scroll overflow in Menu and Select popover content when constrained by viewport height. + + **Affected components:** Menu, Select + +- 4105a78: Merged the internal `PluginHeaderToolbar` component into `PluginHeader`, removing the separate component and its associated types (`PluginHeaderToolbarOwnProps`, `PluginHeaderToolbarProps`) and definition (`PluginHeaderToolbarDefinition`). This is an internal refactor with no changes to the public API of `PluginHeader`. + + **Affected components:** PluginHeader + +- b303857: Fixed `isRequired` prop not being passed to the underlying React Aria field components in TextField, SearchField, and PasswordField. Previously, `isRequired` was consumed locally for the secondary label text but never forwarded, which meant the input elements lacked `aria-required="true"` and React Aria's built-in required validation was not activated. + + **Affected components:** TextField, SearchField, PasswordField + +- cd3cb0f: Improved `useBreakpoint` performance by sharing a single set of `matchMedia` listeners across all component instances instead of creating independent listeners per hook call. + +- 36987db: Extended `AlertProps`, `ContainerProps`, `DialogBodyProps`, and `FieldLabelProps` with native div element props to allow passing attributes like `aria-*` and `data-*`. + + **Affected components:** Alert, Container, DialogBody, FieldLabel + +- Updated dependencies + - @backstage/version-bridge@1.0.12 + +## @backstage/plugin-catalog@1.34.0-next.1 + +### Minor Changes + +- 4d58894: Added support for group alias IDs and configurable content ordering on the entity page. Groups can now declare `aliases` so that content targeting an aliased group is included in the group. A new `defaultContentOrder` option (default `title`) controls how content items within each group are sorted, with support for both a page-level default and per-group overrides. + +### Patch Changes + +- 07ba746: Fixed entity page tab groups not respecting the ordering from the `groups` configuration. +- Updated dependencies + - @backstage/ui@0.13.0-next.1 + - @backstage/catalog-client@1.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/plugin-scaffolder-common@2.0.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-compat-api@0.5.9-next.1 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/integration-react@1.2.16-next.1 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-react@0.4.41-next.0 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-search-react@1.10.5-next.0 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-react@1.3.9-next.0 + +## @backstage/plugin-catalog-backend@3.5.0-next.1 + +### Minor Changes + +- a6b2819: Added `query-catalog-entities` action to the catalog backend actions registry. Supports predicate-based filtering with `$all`, `$any`, `$not`, `$exists`, `$in`, `$contains`, and `$hasPrefix` operators. +- 972f686: Added support for predicate-based filtering on the `/entities/by-refs` endpoint via the `query` field in the request body. Supports `$all`, `$any`, `$not`, `$exists`, `$in`, `$contains`, and `$hasPrefix` operators. +- 56c908e: Added support for predicate-based filtering on the `/entity-facets` endpoint via a new `POST` method. Supports `$all`, `$any`, `$not`, `$exists`, `$in`, `$contains`, and `$hasPrefix` operators. +- 0fbcf23: Migrated OpenAPI schemas to 3.1. +- 51e23eb: Added predicate-based entity filtering via POST /entities/by-query endpoint. + + Supports `$all`, `$any`, `$not`, `$exists`, `$in`, `$hasPrefix`, and (partially) `$contains` operators for expressive entity queries. Integrated into the existing `queryEntities` flow with full cursor-based pagination, permission enforcement, and `totalItems` support. + + The catalog client's `queryEntities()` method automatically routes to the POST endpoint when a `query` predicate is provided. + +### Patch Changes + +- 72747b4: Deprecated two processors as they have been moved to the Community Plugins repo with their own backend modules: + + - `AnnotateScmSlugEntityProcessor`: Use `@backstage-community/plugin-catalog-backend-module-annotate-scm-slug` instead + - `CodeOwnersProcessor`: Use `@backstage-community/plugin-catalog-backend-module-codeowners` instead + +- Updated dependencies + - @backstage/catalog-client@1.14.0-next.1 + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.1 + - @backstage/backend-openapi-utils@0.6.7-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/filter-predicates@0.1.0 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-events-node@0.4.20-next.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.11-next.0 + +## @backstage/plugin-catalog-backend-module-github@0.13.0-next.1 + +### Minor Changes + +- b11c2cd: The default user transformer now prefers organization verified domain emails over the user's public GitHub email when populating the user entity profile. It also strips plus-addressed routing tags that GitHub adds to these emails. + + If you want to retain the old behavior, you can do so with a custom user transformer using the `githubOrgEntityProviderTransformsExtensionPoint`: + + ```ts + import { createBackendModule } from '@backstage/backend-plugin-api'; + import { githubOrgEntityProviderTransformsExtensionPoint } from '@backstage/plugin-catalog-backend-module-github-org'; + import { defaultUserTransformer } from '@backstage/plugin-catalog-backend-module-github'; + + export default createBackendModule({ + pluginId: 'catalog', + moduleId: 'github-org-custom-transforms', + register(env) { + env.registerInit({ + deps: { + transforms: githubOrgEntityProviderTransformsExtensionPoint, + }, + async init({ transforms }) { + transforms.setUserTransformer(async (item, ctx) => { + const entity = await defaultUserTransformer(item, ctx); + if (entity && item.email) { + entity.spec.profile!.email = item.email; + } + return entity; + }); + }, + }); + }, + }); + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-events-node@0.4.20-next.0 + +## @backstage/plugin-catalog-react@2.1.0-next.1 + +### Minor Changes + +- 4d58894: Added `aliases` and `contentOrder` fields to `EntityContentGroupDefinition`, allowing groups to declare alias IDs and control the sort order of their content items. + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.13.0-next.1 + - @backstage/catalog-client@1.14.0-next.1 + - @backstage/frontend-test-utils@0.5.1-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-compat-api@0.5.9-next.1 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/filter-predicates@0.1.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/integration-react@1.2.16-next.1 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## @backstage/plugin-events-backend@0.6.0-next.1 + +### Minor Changes + +- 0fbcf23: Migrated OpenAPI schemas to 3.1. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.7-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.20-next.0 + +## @backstage/plugin-scaffolder-backend@3.2.0-next.1 + +### Minor Changes + +- c9b11eb: Added a new `list-scaffolder-tasks` action that allows querying scaffolder tasks with optional ownership filtering and pagination support +- 0fbcf23: Migrated OpenAPI schemas to 3.1. +- 7695dd2: Added a new `list-scaffolder-actions` action that returns all installed scaffolder actions with their schemas and examples + +### Patch Changes + +- e27bd4e: Removed `@backstage/plugin-scaffolder-backend-module-bitbucket` from `package.json` as the package itself has been deprecated and the code deleted. +- ccc20cf: create scaffolder MCP action to dry run a provided scaffolder template +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-scaffolder-common@2.0.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.1 + - @backstage/backend-openapi-utils@0.6.7-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.20-next.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.11-next.0 + +## @backstage/plugin-scaffolder-node@0.13.0-next.1 + +### Minor Changes + +- e27bd4e: **BREAKING** Removed deprecated `bitbucket` integration from being used in the `parseRepoUrl` function. It will use the `bitbucketCloud` or `bitbucketServer` integrations instead. + +### Patch Changes + +- f598909: Added `scaffolderServiceRef` and `ScaffolderService` interface for backend plugins that need to interact with the scaffolder API using `BackstageCredentials` instead of raw tokens. +- Updated dependencies + - @backstage/backend-test-utils@1.11.1-next.1 + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-scaffolder-common@2.0.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.6 + +## @backstage/plugin-search-backend@2.1.0-next.1 + +### Minor Changes + +- 0fbcf23: Migrated OpenAPI schemas to 3.1. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.7-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.11-next.0 + - @backstage/plugin-search-backend-node@1.4.2-next.0 + - @backstage/plugin-search-common@1.2.22 + +## @backstage/backend-test-utils@1.11.1-next.1 + +### Patch Changes + +- 62f0a53: Fixed error forwarding in the actions registry so that known errors like `InputError` and `NotFoundError` thrown by actions preserve their original status codes and messages instead of being wrapped in `ForwardedError` and coerced to 500. +- Updated dependencies + - @backstage/backend-defaults@0.16.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.1 + - @backstage/backend-app-api@1.5.1-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.20-next.0 + - @backstage/plugin-permission-common@0.9.6 + +## @backstage/cli-common@0.2.0-next.1 + +### Patch Changes + +- e44b6a9: The `findOwnRootDir` utility now searches for the monorepo root by traversing up the directory tree looking for a `package.json` with `workspaces`, instead of assuming a fixed `../..` relative path. If no workspaces root is found during this traversal, `findOwnRootDir` now throws to enforce stricter validation of the repository layout. +- Updated dependencies + - @backstage/errors@1.2.7 + +## @backstage/cli-node@0.2.19-next.1 + +### Patch Changes + +- 61cb976: Added `toString()` method to `Lockfile` for serializing lockfiles back to string format. +- 3c811bf: Added `hasBackstageYarnPlugin` and `SuccessCache` exports, moved from `@backstage/cli`. +- a9d23c4: Properly support `package.json` `workspaces` field +- Updated dependencies + - @backstage/cli-common@0.2.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/core-compat-api@0.5.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-app-react@0.2.1-next.0 + +## @backstage/create-app@0.7.10-next.1 + +### Patch Changes + +- a9d23c4: Properly support `package.json` `workspaces` field + +- ebd4630: Replace deprecated `workspaces.packages` with `workspaces` in `package.json` + + This change is **not** required, but you can edit your main `package.json`, to fit the more modern & more common pattern: + + ```diff + - "workspaces": { + - "packages": [ + "workspaces": [ + "packages/*", + "plugins/*" + - ] + - }, + ], + ``` + +- Updated dependencies + - @backstage/cli-common@0.2.0-next.1 + +## @backstage/dev-utils@1.1.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.13.0-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/app-defaults@1.7.6-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/integration-react@1.2.16-next.1 + - @backstage/theme@0.7.2 + +## @backstage/frontend-dynamic-feature-loader@0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/module-federation-common@0.1.2-next.0 + - @backstage/config@1.3.6 + - @backstage/frontend-plugin-api@0.14.2-next.0 + +## @backstage/frontend-test-utils@0.5.1-next.1 + +### Patch Changes + +- 479282f: Fixed type inference of `TestApiPair` when using tuple syntax by wrapping `MockWithApiFactory` in `NoInfer`. +- Updated dependencies + - @backstage/plugin-app@0.4.1-next.1 + - @backstage/config@1.3.6 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/frontend-app-api@0.15.1-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/test-utils@1.7.16-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-app-react@0.2.1-next.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## @backstage/integration-react@1.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/config@1.3.6 + - @backstage/core-plugin-api@1.12.4-next.0 + +## @backstage/module-federation-common@0.1.2-next.0 + +### Patch Changes + +- 0cb5646: Fixed the `@mui/material/styles` shared dependency key by removing a trailing slash that caused module resolution failures with MUI package exports. +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @techdocs/cli@1.10.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.2.0-next.1 + - @backstage/plugin-techdocs-node@1.14.3-next.1 + - @backstage/backend-defaults@0.16.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + +## @backstage/plugin-api-docs@0.13.5-next.1 + +### Patch Changes + +- 30e08df: Added default entity content groups for the API docs entity content tabs. The API definition tab defaults to the `documentation` group and the APIs tab defaults to the `development` group. +- Updated dependencies + - @backstage/plugin-catalog@1.34.0-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## @backstage/plugin-app@0.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.13.0-next.1 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/integration-react@1.2.16-next.1 + - @backstage/theme@0.7.2 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-app-react@0.2.1-next.0 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## @backstage/plugin-app-visualizer@0.2.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.13.0-next.1 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + +## @backstage/plugin-auth-backend@0.27.1-next.1 + +### Patch Changes + +- 1ccad86: Added `who-am-i` action to the auth backend actions registry. Returns the catalog entity and user info for the currently authenticated user. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.14-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-auth-node@0.6.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.14.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-bitbucket-cloud-common@0.3.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + +## @backstage/plugin-catalog-backend-module-aws@0.4.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.16.0-next.1 + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.20 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-kubernetes-common@0.9.10 + +## @backstage/plugin-catalog-backend-module-azure@0.3.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.8 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-bitbucket-cloud-common@0.3.8-next.1 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-events-node@0.4.20-next.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-events-node@0.4.20-next.0 + +## @backstage/plugin-catalog-backend-module-gerrit@0.3.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.8 + +## @backstage/plugin-catalog-backend-module-gitea@0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.8 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-github@0.13.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-events-node@0.4.20-next.0 + +## @backstage/plugin-catalog-backend-module-gitlab@0.8.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.16.0-next.1 + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-events-node@0.4.20-next.0 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.5.0-next.1 + - @backstage/backend-defaults@0.16.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.20-next.0 + - @backstage/plugin-permission-common@0.9.6 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.8 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-common@2.0.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.8 + +## @backstage/plugin-catalog-graph@0.5.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/types@1.2.2 + +## @backstage/plugin-catalog-import@0.13.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/integration@2.0.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/integration-react@1.2.16-next.1 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## @backstage/plugin-catalog-node@2.1.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.14.0-next.1 + - @backstage/backend-test-utils@1.11.1-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.11-next.0 + +## @backstage/plugin-devtools@0.1.37-next.1 + +### Patch Changes + +- afabb37: Fixed URL encoding of task IDs for the trigger feature (tasks that contained a "/" in their ID were not triggered) +- Updated dependencies + - @backstage/core-compat-api@0.5.9-next.1 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/plugin-devtools-common@0.1.22 + - @backstage/plugin-devtools-react@0.1.2-next.0 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## @backstage/plugin-events-backend-module-github@0.4.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.20-next.0 + +## @backstage/plugin-home@0.9.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-compat-api@0.5.9-next.1 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/theme@0.7.2 + - @backstage/plugin-home-react@0.1.36-next.0 + +## @backstage/plugin-kubernetes@0.12.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/plugin-kubernetes-common@0.9.10 + - @backstage/plugin-kubernetes-react@0.5.17-next.0 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## @backstage/plugin-kubernetes-backend@0.21.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.14.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.20 + - @backstage/types@1.2.2 + - @backstage/plugin-kubernetes-common@0.9.10 + - @backstage/plugin-kubernetes-node@0.4.2-next.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.11-next.0 + +## @backstage/plugin-kubernetes-cluster@0.0.35-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/plugin-kubernetes-common@0.9.10 + - @backstage/plugin-kubernetes-react@0.5.17-next.0 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## @backstage/plugin-mcp-actions-backend@0.1.10-next.1 + +### Patch Changes + +- 62f0a53: Fixed error forwarding in the actions registry so that known errors like `InputError` and `NotFoundError` thrown by actions preserve their original status codes and messages instead of being wrapped in `ForwardedError` and coerced to 500. +- Updated dependencies + - @backstage/catalog-client@1.14.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-mui-to-bui@0.2.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.13.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/theme@0.7.2 + +## @backstage/plugin-notifications-backend-module-email@0.3.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.14.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.1 + - @backstage/plugin-notifications-node@0.2.24-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.20 + - @backstage/types@1.2.2 + - @backstage/plugin-notifications-common@0.2.1 + +## @backstage/plugin-notifications-node@0.2.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.14.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-notifications-common@0.2.1 + - @backstage/plugin-signals-node@0.1.29-next.0 + +## @backstage/plugin-org@0.6.50-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/plugin-catalog-common@1.1.8 + +## @backstage/plugin-org-react@0.1.48-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + +## @backstage/plugin-scaffolder@1.35.5-next.1 + +### Patch Changes + +- e27bd4e: Removed check for deprecated `bitbucket` integration from `repoPickerValidation` function used by the `RepoUrlPicker`, it now validates the `bitbucketServer` and `bitbucketCloud` integrations instead. +- Updated dependencies + - @backstage/catalog-client@1.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/plugin-scaffolder-react@1.19.8-next.1 + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-scaffolder-common@2.0.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/integration-react@1.2.16-next.1 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-react@0.4.41-next.0 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-react@1.3.9-next.0 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-bitbucket-cloud-common@0.3.8-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.16.0-next.1 + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-github@0.9.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.11.4-next.1 + +### Patch Changes + +- 0c1726a: Added new `gitlab:group:access` scaffolder action to add or remove users and groups as members of GitLab groups. The action supports specifying members via `userIds` and/or `groupIds` array parameters, configurable access levels (Guest, Reporter, Developer, Maintainer, Owner), and defaults to the 'add' action when not specified. +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/plugin-notifications-node@0.2.24-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/plugin-notifications-common@0.2.1 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node-test-utils@0.3.9-next.1 + +## @backstage/plugin-scaffolder-node-test-utils@0.3.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-test-utils@1.11.1-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/types@1.2.2 + +## @backstage/plugin-scaffolder-react@1.19.8-next.1 + +### Patch Changes + +- 004b5c1: Added back `formFieldsApiRef` and `ScaffolderFormFieldsApi` as alpha exports. +- f598909: Added `scaffolderApiMock` test utility, exported from `@backstage/plugin-scaffolder-react/testUtils`. +- Updated dependencies + - @backstage/catalog-client@1.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/frontend-test-utils@0.5.1-next.1 + - @backstage/plugin-scaffolder-common@2.0.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/theme@0.7.2 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## @backstage/plugin-search@1.6.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-search-react@1.10.5-next.0 + +## @backstage/plugin-search-backend-module-catalog@0.3.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.14.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-search-backend-node@1.4.2-next.0 + - @backstage/plugin-search-common@1.2.22 + +## @backstage/plugin-search-backend-module-techdocs@0.4.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.14.0-next.1 + - @backstage/plugin-techdocs-node@1.14.3-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-search-backend-node@1.4.2-next.0 + - @backstage/plugin-search-common@1.2.22 + +## @backstage/plugin-techdocs@1.17.1-next.1 + +### Patch Changes + +- 30e08df: Added `documentation` as the default entity content group for the TechDocs entity content tab. +- Updated dependencies + - @backstage/catalog-client@1.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/integration@2.0.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/integration-react@1.2.16-next.1 + - @backstage/theme@0.7.2 + - @backstage/plugin-auth-react@0.1.25-next.0 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-search-react@1.10.5-next.0 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-react@1.3.9-next.0 + +## @backstage/plugin-techdocs-addons-test-utils@2.0.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.17.1-next.1 + - @backstage/plugin-catalog@1.34.0-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/integration-react@1.2.16-next.1 + - @backstage/test-utils@1.7.16-next.0 + - @backstage/plugin-search-react@1.10.5-next.0 + - @backstage/plugin-techdocs-react@1.3.9-next.0 + +## @backstage/plugin-techdocs-backend@2.1.6-next.1 + +### Patch Changes + +- cb7c6b1: Added `techdocs.generator.mkdocs.dangerouslyAllowAdditionalKeys` configuration option to explicitly bypass MkDocs configuration key restrictions. This enables support for additional MkDocs configuration keys beyond the default safe allow list, such as the `hooks` key which some MkDocs plugins require. +- Updated dependencies + - @backstage/catalog-client@1.14.0-next.1 + - @backstage/plugin-techdocs-node@1.14.3-next.1 + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.34-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/integration-react@1.2.16-next.1 + - @backstage/plugin-techdocs-react@1.3.9-next.0 + +## @backstage/plugin-techdocs-node@1.14.3-next.1 + +### Patch Changes + +- cb7c6b1: Added `techdocs.generator.mkdocs.dangerouslyAllowAdditionalKeys` configuration option to explicitly bypass MkDocs configuration key restrictions. This enables support for additional MkDocs configuration keys beyond the default safe allow list, such as the `hooks` key which some MkDocs plugins require. +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.20 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-user-settings@0.9.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/theme@0.7.2 + - @backstage/types@1.2.2 + - @backstage/plugin-signals-react@0.0.20-next.0 + - @backstage/plugin-user-settings-common@0.1.0 + +## example-app@0.0.33-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.13.0-next.1 + - @backstage/cli@0.36.0-next.1 + - @backstage/plugin-devtools@0.1.37-next.1 + - @backstage/plugin-scaffolder@1.35.5-next.1 + - @backstage/plugin-api-docs@0.13.5-next.1 + - @backstage/plugin-techdocs@1.17.1-next.1 + - @backstage/plugin-catalog@1.34.0-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/plugin-scaffolder-react@1.19.8-next.1 + - @backstage/plugin-app@0.4.1-next.1 + - @backstage/plugin-app-visualizer@0.2.1-next.1 + - @backstage/plugin-catalog-graph@0.5.8-next.1 + - @backstage/plugin-catalog-import@0.13.11-next.1 + - @backstage/plugin-home@0.9.3-next.1 + - @backstage/plugin-org@0.6.50-next.1 + - @backstage/app-defaults@1.7.6-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-compat-api@0.5.9-next.1 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/frontend-app-api@0.15.1-next.0 + - @backstage/frontend-defaults@0.4.1-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/integration-react@1.2.16-next.1 + - @backstage/theme@0.7.2 + - @backstage/plugin-app-react@0.2.1-next.0 + - @backstage/plugin-auth@0.1.6-next.0 + - @backstage/plugin-auth-react@0.1.25-next.0 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-catalog-unprocessed-entities@0.2.27-next.0 + - @backstage/plugin-home-react@0.1.36-next.0 + - @backstage/plugin-kubernetes@0.12.17-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.35-next.1 + - @backstage/plugin-notifications@0.5.15-next.0 + - @backstage/plugin-permission-react@0.4.41-next.0 + - @backstage/plugin-search@1.6.2-next.1 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-search-react@1.10.5-next.0 + - @backstage/plugin-signals@0.0.29-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.34-next.1 + - @backstage/plugin-techdocs-react@1.3.9-next.0 + - @backstage/plugin-user-settings@0.9.1-next.1 + +## example-app-legacy@0.2.119-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.13.0-next.1 + - @backstage/cli@0.36.0-next.1 + - @backstage/plugin-devtools@0.1.37-next.1 + - @backstage/plugin-scaffolder@1.35.5-next.1 + - @backstage/plugin-api-docs@0.13.5-next.1 + - @backstage/plugin-techdocs@1.17.1-next.1 + - @backstage/plugin-catalog@1.34.0-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/plugin-scaffolder-react@1.19.8-next.1 + - @backstage/plugin-mui-to-bui@0.2.5-next.1 + - @backstage/plugin-catalog-graph@0.5.8-next.1 + - @backstage/plugin-catalog-import@0.13.11-next.1 + - @backstage/plugin-home@0.9.3-next.1 + - @backstage/plugin-org@0.6.50-next.1 + - @backstage/app-defaults@1.7.6-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/frontend-app-api@0.15.1-next.0 + - @backstage/integration-react@1.2.16-next.1 + - @backstage/theme@0.7.2 + - @backstage/plugin-auth-react@0.1.25-next.0 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-catalog-unprocessed-entities@0.2.27-next.0 + - @backstage/plugin-home-react@0.1.36-next.0 + - @backstage/plugin-kubernetes@0.12.17-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.35-next.1 + - @backstage/plugin-notifications@0.5.15-next.0 + - @backstage/plugin-permission-react@0.4.41-next.0 + - @backstage/plugin-search@1.6.2-next.1 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-search-react@1.10.5-next.0 + - @backstage/plugin-signals@0.0.29-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.34-next.1 + - @backstage/plugin-techdocs-react@1.3.9-next.0 + - @backstage/plugin-user-settings@0.9.1-next.1 + +## example-backend@0.0.48-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.5.0-next.1 + - @backstage/plugin-auth-backend@0.27.1-next.1 + - @backstage/plugin-techdocs-backend@2.1.6-next.1 + - @backstage/backend-defaults@0.16.0-next.1 + - @backstage/plugin-scaffolder-backend@3.2.0-next.1 + - @backstage/plugin-mcp-actions-backend@0.1.10-next.1 + - @backstage/plugin-events-backend@0.6.0-next.1 + - @backstage/plugin-search-backend@2.1.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.1 + - @backstage/plugin-kubernetes-backend@0.21.2-next.1 + - @backstage/plugin-search-backend-module-catalog@0.3.13-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.4.12-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-app-backend@0.5.12-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.5.1-next.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.17-next.0 + - @backstage/plugin-auth-backend-module-openshift-provider@0.1.5-next.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.12-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.2.20-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.18-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.9-next.0 + - @backstage/plugin-devtools-backend@0.5.15-next.0 + - @backstage/plugin-events-backend-module-google-pubsub@0.2.1-next.0 + - @backstage/plugin-notifications-backend@0.6.3-next.0 + - @backstage/plugin-permission-backend@0.7.10-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.17-next.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.11-next.0 + - @backstage/plugin-proxy-backend@0.6.11-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.9.7-next.1 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.20-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.8.1-next.0 + - @backstage/plugin-search-backend-module-explore@0.3.12-next.0 + - @backstage/plugin-search-backend-node@1.4.2-next.0 + - @backstage/plugin-signals-backend@0.3.13-next.0 + +## techdocs-cli-embedded-app@0.2.118-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.13.0-next.1 + - @backstage/cli@0.36.0-next.1 + - @backstage/plugin-techdocs@1.17.1-next.1 + - @backstage/plugin-catalog@1.34.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/frontend-defaults@0.4.1-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/integration-react@1.2.16-next.1 + - @backstage/test-utils@1.7.16-next.0 + - @backstage/theme@0.7.2 + - @backstage/plugin-app-react@0.2.1-next.0 + - @backstage/plugin-techdocs-react@1.3.9-next.0 diff --git a/docs/releases/v1.49.0-next.2-changelog.md b/docs/releases/v1.49.0-next.2-changelog.md new file mode 100644 index 0000000000..b6442fa4b0 --- /dev/null +++ b/docs/releases/v1.49.0-next.2-changelog.md @@ -0,0 +1,1826 @@ +# Release v1.49.0-next.2 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.49.0-next.2](https://backstage.github.io/upgrade-helper/?to=1.49.0-next.2) + +## @backstage/plugin-catalog@2.0.0-next.2 + +### Major Changes + +- 5fc35bb: Migrated `EntityAboutCard`, `EntityLinksCard`, `EntityLabelsCard`, `GroupProfileCard`, and `UserProfileCard` from MUI/InfoCard to use the new BUI card layout and BUI components where possible. + + **BREAKING**: Removed `variant` prop from EntityAboutCard, EntityUserProfileCard, EntityGroupProfileCard, EntityLabelsCard, EntityLinksCard. Removed `gridSizes` prop from `AboutField`. + + **Migration:** + + Simply delete the obsolete `variant` and `gridSizes` props, e.g: + + ```diff + - + + + ``` + + ```diff + - + + + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.5.9-next.2 + - @backstage/ui@0.13.0-next.2 + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/catalog-client@1.14.0-next.2 + - @backstage/plugin-catalog-react@2.1.0-next.2 + - @backstage/core-components@0.18.8-next.1 + - @backstage/plugin-scaffolder-common@2.0.0-next.2 + - @backstage/plugin-search-react@1.10.5-next.1 + - @backstage/plugin-techdocs-react@1.3.9-next.1 + +## @backstage/backend-app-api@1.6.0-next.1 + +### Minor Changes + +- 545557a: Registration errors should be forwarded as BackendStartupResult + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + +## @backstage/backend-plugin-api@1.8.0-next.1 + +### Minor Changes + +- 015668c: Added `cancelTask` method to the `SchedulerService` interface and implementation, allowing cancellation of currently running scheduled tasks. For global tasks, the database lock is released and a periodic liveness check aborts the running task function. For local tasks, the task's abort signal is triggered directly. A new `POST /.backstage/scheduler/v1/tasks/:id/cancel` endpoint is also available. + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.2.0-next.2 + - @backstage/plugin-auth-node@0.6.14-next.2 + - @backstage/plugin-permission-node@0.10.11-next.1 + +## @backstage/catalog-client@1.14.0-next.2 + +### Minor Changes + +- 5d95e8e: Add an `onConflict` option to location creation that can refresh an existing location instead of throwing a conflict error. + +## @backstage/cli@0.36.0-next.2 + +### Minor Changes + +- d0f4cd2: Added new `auth` command group for authenticating the CLI with Backstage instances using OAuth 2.0 with a pre-registered client metadata document. Commands include `login`, `logout`, `list`, `show`, `print-token`, and `select` for managing multiple authenticated instances. + +### Patch Changes + +- a4e5902: Internal refactor of the CLI command registration +- ff4a45a: Migrated remaining CLI command handlers from `commander` to `cleye` for argument parsing. Several camelCase CLI flags have been deprecated in favor of their kebab-case equivalents (e.g. `--successCache` → `--success-cache`). The old camelCase forms still work but will now log a deprecation warning. Please update any scripts or CI configurations to use the kebab-case versions. +- 4a75544: Updated dependency `react-refresh` to `^0.18.0`. +- Updated dependencies + - @backstage/cli-common@0.2.0-next.2 + - @backstage/integration@2.0.0-next.2 + +## @backstage/frontend-app-api@0.16.0-next.1 + +### Minor Changes + +- 92af1ae: **BREAKING**: Removed the `allowUnknownExtensionConfig` option from `createSpecializedApp`. This flag had no effect and was a no-op, so no behavioral changes are expected. + +### Patch Changes + +- 0452d02: Add optional `description` field to plugin-level feature flags. +- dab6c46: Added the `ExtensionFactoryMiddleware` type as a public export. +- Updated dependencies + - @backstage/core-app-api@1.19.6-next.1 + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/frontend-defaults@0.5.0-next.1 + +## @backstage/frontend-defaults@0.5.0-next.1 + +### Minor Changes + +- 92af1ae: **BREAKING**: Removed the `allowUnknownExtensionConfig` option from `createApp`. This flag had no effect and was a no-op, so no behavioral changes are expected. +- 33de79d: **BREAKING**: Removed the deprecated `createPublicSignInApp` function. Use `createApp` from `@backstage/frontend-defaults` with `appModulePublicSignIn` from `@backstage/plugin-app/alpha` instead. + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/frontend-app-api@0.16.0-next.1 + - @backstage/core-components@0.18.8-next.1 + - @backstage/plugin-app@0.4.1-next.2 + +## @backstage/frontend-plugin-api@0.15.0-next.1 + +### Minor Changes + +- 6573901: **BREAKING**: Removed the deprecated `AnyExtensionDataRef` type. Use `ExtensionDataRef` without type parameters instead. +- a9440f0: **BREAKING**: Simplified the `ExtensionAttachTo` type to only support a single attachment target. The array form for attaching to multiple extension points has been removed. Also removed the deprecated `ExtensionAttachToSpec` type alias. + +### Patch Changes + +- 8a3a906: Deprecated `NavItemBlueprint`. Nav items are now automatically inferred from `PageBlueprint` extensions based on their `title` and `icon` params. +- b15a685: Deprecated `withApis`, use the `withApis` export from `@backstage/core-compat-api` instead. +- 0452d02: Add optional `description` field to plugin-level feature flags. +- 1bec049: Fixed inconsistent `JSX.Element` type reference in the `DialogApiDialog.update` method signature. +- 2c383b5: Deprecated `AnalyticsImplementationBlueprint` and `AnalyticsImplementationFactory` in favor of the exports from `@backstage/plugin-app-react`. +- dab6c46: Deprecated the `ExtensionFactoryMiddleware` type, which has been moved to `@backstage/frontend-app-api`. +- d0206c4: Removed the deprecated `defaultPath` migration helper from `PageBlueprint` params. +- edb872c: Renamed the `PageTab` type to `PageLayoutTab`. The old `PageTab` name is now a deprecated type alias. +- fe848e0: Changed `useApiHolder` to return an empty `ApiHolder` instead of throwing when used outside of an API context. + +## @backstage/plugin-catalog-backend@3.5.0-next.2 + +### Minor Changes + +- 5d95e8e: Add an `onConflict` option to location creation that can refresh an existing location instead of throwing a conflict error. + +### Patch Changes + +- 7416e8b: Moved stitch queue concerns out of `refresh_state` and `final_entities` into a dedicated `stitch_queue` table with `entity_ref` as the primary key. The `stitch_ticket` is used for optimistic concurrency control. When a stitch completes successfully and the ticket hasn't changed, the corresponding row is deleted from the queue. The migration handles existing data and is fully reversible. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/catalog-client@1.14.0-next.2 + - @backstage/integration@2.0.0-next.2 + - @backstage/backend-openapi-utils@0.6.7-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.2 + - @backstage/plugin-events-node@0.4.20-next.1 + - @backstage/plugin-permission-node@0.10.11-next.1 + +## @backstage/plugin-catalog-graph@0.6.0-next.2 + +### Minor Changes + +- d14b6e0: **BREAKING**: Migrated `MembersListCard`, `OwnershipCard`, and `CatalogGraphCard` to use BUI card primitives via `EntityInfoCard`. + + - `OwnershipCard`: Removed `variant` and `maxScrollHeight` props. Card height and scrolling are now controlled by the parent container — the card fills its container and the body scrolls automatically when content overflows. + - `CatalogGraphCard`: Removed `variant` prop. + - `MembersListCard`: Translation keys `subtitle`, `paginationLabel`, `aggregateMembersToggle.directMembers`, `aggregateMembersToggle.aggregatedMembers`, and `aggregateMembersToggle.ariaLabel` have been removed. The `title` key now includes `{{groupName}}`. New keys added: `cardLabel`, `noSearchResult`, `aggregateMembersToggle.label`. + - `OwnershipCard`: Translation keys `aggregateRelationsToggle.directRelations`, `aggregateRelationsToggle.aggregatedRelations`, and `aggregateRelationsToggle.ariaLabel` have been removed. New key added: `aggregateRelationsToggle.label`. + - Removed `MemberComponentClassKey` export, and `root` and `cardContent` from `MembersListCardClassKey`, `card` from `OwnershipCardClassKey`, and `card` from `CatalogGraphCardClassKey`. + + **Migration:** + + ```diff + - + + + ``` + + ```diff + - + + + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.13.0-next.2 + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/catalog-client@1.14.0-next.2 + - @backstage/plugin-catalog-react@2.1.0-next.2 + - @backstage/core-components@0.18.8-next.1 + +## @backstage/plugin-catalog-react@2.1.0-next.2 + +### Minor Changes + +- d14b6e0: Exported `useEntityRefLink` hook that returns a function for generating entity page URLs from entity references. +- c6080eb: Added `EntityInfoCard` component to `@backstage/plugin-catalog-react` as a BUI-based card wrapper for entity page cards. + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.5.9-next.2 + - @backstage/ui@0.13.0-next.2 + - @backstage/frontend-test-utils@0.5.1-next.2 + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/catalog-client@1.14.0-next.2 + - @backstage/core-components@0.18.8-next.1 + +## @backstage/plugin-notifications-backend-module-slack@0.4.0-next.1 + +### Minor Changes + +- cd62d78: **BREAKING**: Only send direct messages to user entity recipients. Notifications sent to non-user entities no longer send Slack direct messages to resolved users. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.2 + - @backstage/plugin-notifications-node@0.2.24-next.2 + +## @backstage/plugin-org@0.7.0-next.2 + +### Minor Changes + +- d14b6e0: **BREAKING**: Migrated `MembersListCard`, `OwnershipCard`, and `CatalogGraphCard` to use BUI card primitives via `EntityInfoCard`. + + - `OwnershipCard`: Removed `variant` and `maxScrollHeight` props. Card height and scrolling are now controlled by the parent container — the card fills its container and the body scrolls automatically when content overflows. + - `CatalogGraphCard`: Removed `variant` prop. + - `MembersListCard`: Translation keys `subtitle`, `paginationLabel`, `aggregateMembersToggle.directMembers`, `aggregateMembersToggle.aggregatedMembers`, and `aggregateMembersToggle.ariaLabel` have been removed. The `title` key now includes `{{groupName}}`. New keys added: `cardLabel`, `noSearchResult`, `aggregateMembersToggle.label`. + - `OwnershipCard`: Translation keys `aggregateRelationsToggle.directRelations`, `aggregateRelationsToggle.aggregatedRelations`, and `aggregateRelationsToggle.ariaLabel` have been removed. New key added: `aggregateRelationsToggle.label`. + - Removed `MemberComponentClassKey` export, and `root` and `cardContent` from `MembersListCardClassKey`, `card` from `OwnershipCardClassKey`, and `card` from `CatalogGraphCardClassKey`. + + **Migration:** + + ```diff + - + + + ``` + + ```diff + - + + + ``` + +- 5fc35bb: Migrated `EntityAboutCard`, `EntityLinksCard`, `EntityLabelsCard`, `GroupProfileCard`, and `UserProfileCard` from MUI/InfoCard to use the new BUI card layout and BUI components where possible. + + **BREAKING**: Removed `variant` prop from EntityAboutCard, EntityUserProfileCard, EntityGroupProfileCard, EntityLabelsCard, EntityLinksCard. Removed `gridSizes` prop from `AboutField`. + + **Migration:** + + Simply delete the obsolete `variant` and `gridSizes` props, e.g: + + ```diff + - + + + ``` + + ```diff + - + + + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.13.0-next.2 + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.2 + - @backstage/core-components@0.18.8-next.1 + +## @backstage/plugin-scaffolder-backend@3.2.0-next.2 + +### Minor Changes + +- e8736ea: Added secrets schema validation for task creation, retry, and dry-run endpoints. When a template defines `spec.secrets.schema`, the API validates provided secrets against the schema and returns a `400` error if validation fails. + +### Patch Changes + +- 30ff981: Fixed a security vulnerability where secrets could bypass log redaction when transformed through Nunjucks filters in scaffolder templates. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-scaffolder-common@2.0.0-next.2 + - @backstage/backend-openapi-utils@0.6.7-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.2 + - @backstage/plugin-events-node@0.4.20-next.1 + - @backstage/plugin-permission-node@0.10.11-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + +## @backstage/plugin-scaffolder-common@2.0.0-next.2 + +### Minor Changes + +- e8736ea: Added an optional `secrets` field to `TemplateEntityV1beta3` for configuring secrets validation. The schema for validating secrets is defined under `secrets.schema` as a JSON Schema object. + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.2 + +## @backstage/plugin-scaffolder-react@1.20.0-next.2 + +### Minor Changes + +- 470f72d: The `LogViewer` component from `@backstage/core-components` now supports downloading logs if a callback is passed to `onDownloadLogs` + +### Patch Changes + +- bd31ddd: Updated dependency `flatted` to `3.3.4`. +- Updated dependencies + - @backstage/frontend-test-utils@0.5.1-next.2 + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/catalog-client@1.14.0-next.2 + - @backstage/plugin-catalog-react@2.1.0-next.2 + - @backstage/core-components@0.18.8-next.1 + - @backstage/plugin-scaffolder-common@2.0.0-next.2 + +## @backstage/backend-defaults@0.16.0-next.2 + +### Patch Changes + +- 015668c: Added `cancelTask` method to the `SchedulerService` interface and implementation, allowing cancellation of currently running scheduled tasks. For global tasks, the database lock is released and a periodic liveness check aborts the running task function. For local tasks, the task's abort signal is triggered directly. A new `POST /.backstage/scheduler/v1/tasks/:id/cancel` endpoint is also available. +- 5fcbef2: Updated dependency `express-rate-limit` to `^8.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/backend-app-api@1.6.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + - @backstage/plugin-events-node@0.4.20-next.1 + - @backstage/plugin-permission-node@0.10.11-next.1 + +## @backstage/backend-dynamic-feature-service@0.8.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/backend-defaults@0.16.0-next.2 + - @backstage/cli-common@0.2.0-next.2 + - @backstage/plugin-catalog-backend@3.5.0-next.2 + - @backstage/backend-openapi-utils@0.6.7-next.1 + - @backstage/plugin-app-node@0.1.43-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + - @backstage/plugin-events-backend@0.6.0-next.2 + - @backstage/plugin-events-node@0.4.20-next.1 + - @backstage/plugin-permission-node@0.10.11-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + - @backstage/plugin-search-backend-node@1.4.2-next.1 + +## @backstage/backend-openapi-utils@0.6.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + +## @backstage/backend-test-utils@1.11.1-next.2 + +### Patch Changes + +- 164711a: Added `cancelTask` to `MockSchedulerService` and mock scheduler service factory. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/backend-defaults@0.16.0-next.2 + - @backstage/backend-app-api@1.6.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + - @backstage/plugin-events-node@0.4.20-next.1 + +## @backstage/cli-common@0.2.0-next.2 + +### Patch Changes + +- 9361965: Fixed `runCheck` to ignore stdio of the spawned process, preventing unwanted output from leaking to the terminal. + +## @backstage/core-app-api@1.19.6-next.1 + +### Patch Changes + +- 12d8afe: Added `BUIProvider` from `@backstage/ui` to the app shell provider tree, enabling BUI components to fire analytics events through the Backstage analytics system. +- 0452d02: Add optional `description` field to plugin-level feature flags. +- Updated dependencies + - @backstage/ui@0.13.0-next.2 + - @backstage/core-plugin-api@1.12.4-next.1 + +## @backstage/core-compat-api@0.5.9-next.2 + +### Patch Changes + +- b15a685: Added `withApis`, which is a Higher-Order Component for providing APIs as props to a component via `useApiHolder`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.2 + - @backstage/plugin-app-react@0.2.1-next.1 + +## @backstage/core-components@0.18.8-next.1 + +### Patch Changes + +- 8b1a847: Fixed Table component layout when both `filters` and `title` props are used together. The filter controls now use a dedicated CSS class (`filterControls`) instead of incorrectly reusing the root container class. +- 470f72d: The `LogViewer` component from `@backstage/core-components` now supports downloading logs if a callback is passed to `onDownloadLogs` +- Updated dependencies + - @backstage/core-plugin-api@1.12.4-next.1 + +## @backstage/core-plugin-api@1.12.4-next.1 + +### Patch Changes + +- 0452d02: Add optional `description` field to plugin-level feature flags. +- fe848e0: Changed `useApiHolder` to return an empty `ApiHolder` instead of throwing when used outside of an API context. +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + +## @backstage/create-app@0.7.10-next.2 + +### Patch Changes + +- d14b6e0: **BREAKING**: Migrated `MembersListCard`, `OwnershipCard`, and `CatalogGraphCard` to use BUI card primitives via `EntityInfoCard`. + + - `OwnershipCard`: Removed `variant` and `maxScrollHeight` props. Card height and scrolling are now controlled by the parent container — the card fills its container and the body scrolls automatically when content overflows. + - `CatalogGraphCard`: Removed `variant` prop. + - `MembersListCard`: Translation keys `subtitle`, `paginationLabel`, `aggregateMembersToggle.directMembers`, `aggregateMembersToggle.aggregatedMembers`, and `aggregateMembersToggle.ariaLabel` have been removed. The `title` key now includes `{{groupName}}`. New keys added: `cardLabel`, `noSearchResult`, `aggregateMembersToggle.label`. + - `OwnershipCard`: Translation keys `aggregateRelationsToggle.directRelations`, `aggregateRelationsToggle.aggregatedRelations`, and `aggregateRelationsToggle.ariaLabel` have been removed. New key added: `aggregateRelationsToggle.label`. + - Removed `MemberComponentClassKey` export, and `root` and `cardContent` from `MembersListCardClassKey`, `card` from `OwnershipCardClassKey`, and `card` from `CatalogGraphCardClassKey`. + + **Migration:** + + ```diff + - + + + ``` + + ```diff + - + + + ``` + +- Updated dependencies + - @backstage/cli-common@0.2.0-next.2 + +## @backstage/frontend-dynamic-feature-loader@0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + +## @backstage/frontend-test-utils@0.5.1-next.2 + +### Patch Changes + +- b56f573: Deprecated standalone mock API exports in favor of the `mockApis` namespace. This includes the mock classes (`MockAlertApi`, `MockAnalyticsApi`, `MockConfigApi`, `MockErrorApi`, `MockFetchApi`, `MockFeatureFlagsApi`, `MockPermissionApi`, `MockStorageApi`, `MockTranslationApi`), their option types (`MockErrorApiOptions`, `MockFeatureFlagsApiOptions`), and the `ErrorWithContext` type. `MockFetchApiOptions` is kept as a non-deprecated export. Use the `mockApis` namespace instead, for example `mockApis.alert()` or `mockApis.alert.mock()`. +- Updated dependencies + - @backstage/core-app-api@1.19.6-next.1 + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/frontend-app-api@0.16.0-next.1 + - @backstage/plugin-app-react@0.2.1-next.1 + - @backstage/plugin-app@0.4.1-next.2 + +## @backstage/integration@2.0.0-next.2 + +### Patch Changes + +- 1513a0b: Fixed a security vulnerability where path traversal sequences in SCM URLs could be used to access unintended API endpoints using server-side integration credentials. + +## @backstage/repo-tools@0.17.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/cli-common@0.2.0-next.2 + +## @backstage/ui@0.13.0-next.2 + +### Patch Changes + +- db92751: Added interactive support to the `Card` component. Pass `onPress` to make the entire card surface pressable, or `href` to make it navigate to a URL. A transparent overlay handles the interaction while nested buttons and links remain independently clickable. + +- 12d8afe: Added analytics capabilities to the component library. Components with navigation behavior (Link, ButtonLink, Tab, MenuItem, Tag, Row) now fire analytics events on click when a `BUIProvider` is present. + + New exports: `BUIProvider`, `useAnalytics`, `getNodeText`, and associated types (`AnalyticsTracker`, `UseAnalyticsFn`, `BUIProviderProps`, `AnalyticsEventAttributes`). + + Components with analytics support now accept a `noTrack` prop to suppress event firing. + + **Affected components:** Link, ButtonLink, Tab, MenuItem, Tag, Row + +- 430d5ed: Fixed interactive cards so that CardBody can scroll when the card has a constrained height. Previously, the overlay element blocked scroll events. + + **Affected components:** Card + +- ad7c883: Deprecated the `HeaderPage` component name in favor of `Header`. The old `HeaderPage`, `HeaderPageProps`, `HeaderPageOwnProps`, `HeaderPageBreadcrumb`, and `HeaderPageDefinition` exports are still available as deprecated aliases. + +- f42f4cc: Fixed Table column headers overflowing and wrapping when there is not enough space. Headers now truncate with ellipsis instead. + + **Affected components:** Table + +- fbd5c5a: Fixed Table rows showing a pointer cursor when not interactive. Rows now only show `cursor: pointer` when they have an `href`, are selectable, or are pressable. + + **Affected components:** Table + +- 7960d54: Added support for native HTML div attributes on the `Flex`, `Grid`, and `Grid.Item` components. + + **Affected components:** Flex, Grid, Grid.Item + +- 12d8afe: Fixed MenuItem `onAction` prop ordering so user-provided `onAction` handlers are chained rather than silently overwritten. + +- bb66b86: The `Select` trigger now automatically adapts its background colour based on the parent background context. + + **Affected components:** Select + +- 934ac03: `SearchField` and `TextField` now automatically adapt their background color based on the parent bg context, stepping up one neutral level (e.g. neutral-1 → neutral-2) when placed on a neutral background. `TextField` also gains a focus ring using the `--bui-ring` token. + + **Affected components:** SearchField, TextField + +## @backstage/plugin-api-docs@0.13.5-next.2 + +### Patch Changes + +- ca277ef: Updated dependency `graphiql` to `3.9.0` to address security vulnerability in `markdown-it` package. + Updated dependency `@graphiql/react` to `0.29.0` to match the version used by `graphiql`. + Moved dependency `graphql-config` to `devDependencies` as it is needed only for types. +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.2 + - @backstage/core-components@0.18.8-next.1 + - @backstage/plugin-catalog@2.0.0-next.2 + +## @backstage/plugin-app@0.4.1-next.2 + +### Patch Changes + +- 12d8afe: Added `BUIProvider` from `@backstage/ui` to the app root, enabling BUI components to fire analytics events through the Backstage analytics system. +- Updated dependencies + - @backstage/ui@0.13.0-next.2 + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/core-components@0.18.8-next.1 + - @backstage/plugin-app-react@0.2.1-next.1 + +## @backstage/plugin-app-backend@0.5.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-app-node@0.1.43-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + +## @backstage/plugin-app-node@0.1.43-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + +## @backstage/plugin-app-react@0.2.1-next.1 + +### Patch Changes + +- 2c383b5: Added `AnalyticsImplementationBlueprint` and `AnalyticsImplementationFactory`, migrated from `@backstage/frontend-plugin-api`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + +## @backstage/plugin-app-visualizer@0.2.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.13.0-next.2 + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/core-components@0.18.8-next.1 + +## @backstage/plugin-auth@0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-components@0.18.8-next.1 + +## @backstage/plugin-auth-backend@0.27.1-next.2 + +### Patch Changes + +- d0f4cd2: Added optional client metadata document endpoint at `/.well-known/oauth-client/cli.json` relative to the auth backend base URL for CLI authentication. Enabled when `auth.experimentalClientIdMetadataDocuments.enabled` is set to `true`. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + - @backstage/plugin-catalog-node@2.1.0-next.2 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + +## @backstage/plugin-auth-backend-module-auth0-provider@0.3.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-backend@0.27.1-next.2 + - @backstage/plugin-auth-node@0.6.14-next.2 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + +## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + +## @backstage/plugin-auth-backend-module-github-provider@0.5.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + +## @backstage/plugin-auth-backend-module-google-provider@0.3.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + +## @backstage/plugin-auth-backend-module-guest-provider@0.2.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.4.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-backend@0.27.1-next.2 + - @backstage/plugin-auth-node@0.6.14-next.2 + +## @backstage/plugin-auth-backend-module-okta-provider@0.2.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + +## @backstage/plugin-auth-backend-module-openshift-provider@0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + +## @backstage/plugin-auth-node@0.6.14-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/catalog-client@1.14.0-next.2 + +## @backstage/plugin-catalog-backend-module-aws@0.4.21-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/backend-defaults@0.16.0-next.2 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-catalog-node@2.1.0-next.2 + +## @backstage/plugin-catalog-backend-module-azure@0.3.15-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-catalog-node@2.1.0-next.2 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/backend-openapi-utils@0.6.7-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.2 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-catalog-node@2.1.0-next.2 + - @backstage/plugin-events-node@0.4.20-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-catalog-node@2.1.0-next.2 + - @backstage/plugin-events-node@0.4.20-next.1 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.2 + +## @backstage/plugin-catalog-backend-module-gerrit@0.3.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-catalog-node@2.1.0-next.2 + +## @backstage/plugin-catalog-backend-module-gitea@0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-catalog-node@2.1.0-next.2 + +## @backstage/plugin-catalog-backend-module-github@0.13.0-next.2 + +### Patch Changes + +- 106d1b2: Added a `defaultUserTransformer.useVerifiedEmails` config option for the `githubOrg` provider. When set to `true`, the default user transformer prefers organization verified domain emails over the user's public GitHub email. Defaults to `false`, which uses only the public GitHub email. + + This option has no effect when a custom user transformer is set via the `githubOrgEntityProviderTransformsExtensionPoint`. + + ```yaml + catalog: + providers: + githubOrg: + production: + githubUrl: https://github.com + orgs: + - my-org + defaultUserTransformer: + useVerifiedEmails: true + ``` + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-catalog-node@2.1.0-next.2 + - @backstage/plugin-events-node@0.4.20-next.1 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.20-next.2 + +### Patch Changes + +- 106d1b2: Added a `defaultUserTransformer.useVerifiedEmails` config option for the `githubOrg` provider. When set to `true`, the default user transformer prefers organization verified domain emails over the user's public GitHub email. Defaults to `false`, which uses only the public GitHub email. + + This option has no effect when a custom user transformer is set via the `githubOrgEntityProviderTransformsExtensionPoint`. + + ```yaml + catalog: + providers: + githubOrg: + production: + githubUrl: https://github.com + orgs: + - my-org + defaultUserTransformer: + useVerifiedEmails: true + ``` + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-catalog-backend-module-github@0.13.0-next.2 + - @backstage/plugin-catalog-node@2.1.0-next.2 + - @backstage/plugin-events-node@0.4.20-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab@0.8.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/backend-defaults@0.16.0-next.2 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-catalog-node@2.1.0-next.2 + - @backstage/plugin-events-node@0.4.20-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-catalog-backend-module-gitlab@0.8.1-next.2 + - @backstage/plugin-catalog-node@2.1.0-next.2 + - @backstage/plugin-events-node@0.4.20-next.1 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/backend-defaults@0.16.0-next.2 + - @backstage/plugin-catalog-backend@3.5.0-next.2 + - @backstage/plugin-catalog-node@2.1.0-next.2 + - @backstage/plugin-events-node@0.4.20-next.1 + +## @backstage/plugin-catalog-backend-module-ldap@0.12.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.2 + +## @backstage/plugin-catalog-backend-module-logs@0.1.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-catalog-backend@3.5.0-next.2 + - @backstage/plugin-events-node@0.4.20-next.1 + +## @backstage/plugin-catalog-backend-module-msgraph@0.9.1-next.1 + +### Patch Changes + +- 97eaecf: Fixed scheduler task remaining stuck in running state after pod termination by propagating AbortSignal into MicrosoftGraphOrgEntityProvider.read() +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.2 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-catalog-node@2.1.0-next.2 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.2 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-scaffolder-common@2.0.0-next.2 + - @backstage/plugin-catalog-node@2.1.0-next.2 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.6.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + - @backstage/plugin-catalog-node@2.1.0-next.2 + +## @backstage/plugin-catalog-import@0.13.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/catalog-client@1.14.0-next.2 + - @backstage/plugin-catalog-react@2.1.0-next.2 + - @backstage/integration@2.0.0-next.2 + - @backstage/core-components@0.18.8-next.1 + +## @backstage/plugin-catalog-node@2.1.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-test-utils@1.11.1-next.2 + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/catalog-client@1.14.0-next.2 + - @backstage/plugin-permission-node@0.10.11-next.1 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.5.9-next.2 + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/core-components@0.18.8-next.1 + - @backstage/plugin-devtools-react@0.1.2-next.1 + +## @backstage/plugin-devtools@0.1.37-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.5.9-next.2 + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/core-components@0.18.8-next.1 + - @backstage/plugin-devtools-react@0.1.2-next.1 + +## @backstage/plugin-devtools-backend@0.5.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/cli-common@0.2.0-next.2 + - @backstage/plugin-permission-node@0.10.11-next.1 + +## @backstage/plugin-devtools-react@0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + +## @backstage/plugin-events-backend@0.6.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/backend-openapi-utils@0.6.7-next.1 + - @backstage/plugin-events-node@0.4.20-next.1 + +## @backstage/plugin-events-backend-module-aws-sqs@0.4.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-events-node@0.4.20-next.1 + +## @backstage/plugin-events-backend-module-azure@0.2.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-events-node@0.4.20-next.1 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-events-node@0.4.20-next.1 + +## @backstage/plugin-events-backend-module-bitbucket-server@0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-events-node@0.4.20-next.1 + +## @backstage/plugin-events-backend-module-gerrit@0.2.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-events-node@0.4.20-next.1 + +## @backstage/plugin-events-backend-module-github@0.4.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-events-node@0.4.20-next.1 + +## @backstage/plugin-events-backend-module-gitlab@0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-events-node@0.4.20-next.1 + +## @backstage/plugin-events-backend-module-google-pubsub@0.2.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-events-node@0.4.20-next.1 + +## @backstage/plugin-events-backend-module-kafka@0.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-events-node@0.4.20-next.1 + +## @backstage/plugin-events-node@0.4.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + +## @backstage/plugin-gateway-backend@1.1.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + +## @backstage/plugin-home@0.9.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.5.9-next.2 + - @backstage/core-app-api@1.19.6-next.1 + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/catalog-client@1.14.0-next.2 + - @backstage/plugin-catalog-react@2.1.0-next.2 + - @backstage/core-components@0.18.8-next.1 + - @backstage/plugin-home-react@0.1.36-next.1 + +## @backstage/plugin-home-react@0.1.36-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.5.9-next.2 + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/core-components@0.18.8-next.1 + +## @backstage/plugin-kubernetes@0.12.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.2 + - @backstage/core-components@0.18.8-next.1 + +## @backstage/plugin-kubernetes-backend@0.21.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/catalog-client@1.14.0-next.2 + - @backstage/plugin-catalog-node@2.1.0-next.2 + - @backstage/plugin-kubernetes-node@0.4.2-next.1 + - @backstage/plugin-permission-node@0.10.11-next.1 + +## @backstage/plugin-kubernetes-node@0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + +## @backstage/plugin-mcp-actions-backend@0.1.10-next.2 + +### Patch Changes + +- c74b697: Added support for splitting MCP actions into multiple servers via `mcpActions.servers` configuration. Each server gets its own endpoint at `/api/mcp-actions/v1/{key}` with actions scoped using include/exclude filter rules. Tool names are now namespaced with the plugin ID by default, configurable via `mcpActions.namespacedToolNames`. When `mcpActions.servers` is not configured, the plugin continues to serve a single server at `/api/mcp-actions/v1`. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/catalog-client@1.14.0-next.2 + - @backstage/plugin-catalog-node@2.1.0-next.2 + +## @backstage/plugin-mui-to-bui@0.2.5-next.2 + +### Patch Changes + +- ad7c883: Updated the MUI to BUI theme converter page to use the renamed `Header` component from `@backstage/ui`. +- Updated dependencies + - @backstage/ui@0.13.0-next.2 + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + +## @backstage/plugin-notifications@0.5.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/core-components@0.18.8-next.1 + +## @backstage/plugin-notifications-backend@0.6.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.2 + - @backstage/plugin-notifications-node@0.2.24-next.2 + - @backstage/plugin-signals-node@0.1.29-next.1 + +## @backstage/plugin-notifications-backend-module-email@0.3.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/catalog-client@1.14.0-next.2 + - @backstage/plugin-catalog-node@2.1.0-next.2 + - @backstage/plugin-notifications-node@0.2.24-next.2 + +## @backstage/plugin-notifications-node@0.2.24-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/catalog-client@1.14.0-next.2 + - @backstage/plugin-signals-node@0.1.29-next.1 + +## @backstage/plugin-permission-backend@0.7.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + - @backstage/plugin-permission-node@0.10.11-next.1 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + - @backstage/plugin-permission-node@0.10.11-next.1 + +## @backstage/plugin-permission-node@0.10.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + +## @backstage/plugin-proxy-backend@0.6.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-proxy-node@0.1.13-next.1 + +## @backstage/plugin-proxy-node@0.1.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + +## @backstage/plugin-scaffolder@1.35.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/catalog-client@1.14.0-next.2 + - @backstage/plugin-catalog-react@2.1.0-next.2 + - @backstage/integration@2.0.0-next.2 + - @backstage/core-components@0.18.8-next.1 + - @backstage/plugin-scaffolder-react@1.20.0-next.2 + - @backstage/plugin-scaffolder-common@2.0.0-next.2 + - @backstage/plugin-techdocs-react@1.3.9-next.1 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.21-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/backend-defaults@0.16.0-next.2 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + +## @backstage/plugin-scaffolder-backend-module-github@0.9.7-next.2 + +### Patch Changes + +- b2591f6: Fixed environment `waitTime` description incorrectly asking for milliseconds instead of minutes. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-catalog-node@2.1.0-next.2 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.11.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-notifications-node@0.2.24-next.2 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + - @backstage/plugin-scaffolder-node-test-utils@0.3.9-next.2 + +## @backstage/plugin-scaffolder-node@0.13.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-test-utils@1.11.1-next.2 + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-scaffolder-common@2.0.0-next.2 + +## @backstage/plugin-scaffolder-node-test-utils@0.3.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-test-utils@1.11.1-next.2 + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + +## @backstage/plugin-search@1.6.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.2 + - @backstage/core-components@0.18.8-next.1 + - @backstage/plugin-search-react@1.10.5-next.1 + +## @backstage/plugin-search-backend@2.1.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/backend-openapi-utils@0.6.7-next.1 + - @backstage/plugin-permission-node@0.10.11-next.1 + - @backstage/plugin-search-backend-node@1.4.2-next.1 + +## @backstage/plugin-search-backend-module-catalog@0.3.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/catalog-client@1.14.0-next.2 + - @backstage/plugin-catalog-node@2.1.0-next.2 + - @backstage/plugin-search-backend-node@1.4.2-next.1 + +## @backstage/plugin-search-backend-module-elasticsearch@1.8.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-search-backend-node@1.4.2-next.1 + +## @backstage/plugin-search-backend-module-explore@0.3.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-search-backend-node@1.4.2-next.1 + +## @backstage/plugin-search-backend-module-pg@0.5.53-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-search-backend-node@1.4.2-next.1 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-search-backend-node@1.4.2-next.1 + +## @backstage/plugin-search-backend-module-techdocs@0.4.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-techdocs-node@1.14.4-next.2 + - @backstage/catalog-client@1.14.0-next.2 + - @backstage/plugin-catalog-node@2.1.0-next.2 + - @backstage/plugin-search-backend-node@1.4.2-next.1 + +## @backstage/plugin-search-backend-node@1.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + +## @backstage/plugin-search-react@1.10.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/core-components@0.18.8-next.1 + +## @backstage/plugin-signals@0.0.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/core-components@0.18.8-next.1 + +## @backstage/plugin-signals-backend@0.3.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-events-node@0.4.20-next.1 + - @backstage/plugin-signals-node@0.1.29-next.1 + +## @backstage/plugin-signals-node@0.1.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + - @backstage/plugin-events-node@0.4.20-next.1 + +## @backstage/plugin-techdocs@1.17.1-next.2 + +### Patch Changes + +- 9795d30: chore(deps): bump `dompurify` from 3.3.1 to 3.3.2 +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/catalog-client@1.14.0-next.2 + - @backstage/plugin-catalog-react@2.1.0-next.2 + - @backstage/integration@2.0.0-next.2 + - @backstage/core-components@0.18.8-next.1 + - @backstage/plugin-search-react@1.10.5-next.1 + - @backstage/plugin-techdocs-react@1.3.9-next.1 + +## @backstage/plugin-techdocs-addons-test-utils@2.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.6-next.1 + - @backstage/plugin-techdocs@1.17.1-next.2 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.2 + - @backstage/plugin-catalog@2.0.0-next.2 + - @backstage/plugin-search-react@1.10.5-next.1 + - @backstage/plugin-techdocs-react@1.3.9-next.1 + +## @backstage/plugin-techdocs-backend@2.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-techdocs-node@1.14.4-next.2 + - @backstage/catalog-client@1.14.0-next.2 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-catalog-node@2.1.0-next.2 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.34-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/core-components@0.18.8-next.1 + - @backstage/plugin-techdocs-react@1.3.9-next.1 + +## @backstage/plugin-techdocs-node@1.14.4-next.2 + +### Patch Changes + +- e96f6d9: Removed `INHERIT` from the `ALLOWED_MKDOCS_KEYS` set to address a security concern with MkDocs configuration inheritance. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + +## @backstage/plugin-techdocs-react@1.3.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/core-components@0.18.8-next.1 + +## @backstage/plugin-user-settings@0.9.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.6-next.1 + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.2 + - @backstage/core-components@0.18.8-next.1 + +## @backstage/plugin-user-settings-backend@0.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + - @backstage/plugin-signals-node@0.1.29-next.1 + +## example-app@0.0.33-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.5.9-next.2 + - @backstage/ui@0.13.0-next.2 + - @backstage/cli@0.36.0-next.2 + - @backstage/core-app-api@1.19.6-next.1 + - @backstage/plugin-techdocs@1.17.1-next.2 + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/frontend-app-api@0.16.0-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.2 + - @backstage/core-components@0.18.8-next.1 + - @backstage/plugin-org@0.7.0-next.2 + - @backstage/plugin-catalog-graph@0.6.0-next.2 + - @backstage/plugin-app-react@0.2.1-next.1 + - @backstage/plugin-app@0.4.1-next.2 + - @backstage/frontend-defaults@0.5.0-next.1 + - @backstage/plugin-scaffolder-react@1.20.0-next.2 + - @backstage/plugin-api-docs@0.13.5-next.2 + - @backstage/plugin-catalog@2.0.0-next.2 + - @backstage/plugin-app-visualizer@0.2.1-next.2 + - @backstage/plugin-auth@0.1.6-next.1 + - @backstage/plugin-catalog-import@0.13.11-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.27-next.1 + - @backstage/plugin-devtools@0.1.37-next.2 + - @backstage/plugin-home@0.9.3-next.2 + - @backstage/plugin-home-react@0.1.36-next.1 + - @backstage/plugin-kubernetes@0.12.17-next.2 + - @backstage/plugin-notifications@0.5.15-next.1 + - @backstage/plugin-scaffolder@1.35.5-next.2 + - @backstage/plugin-search@1.6.2-next.2 + - @backstage/plugin-search-react@1.10.5-next.1 + - @backstage/plugin-signals@0.0.29-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.34-next.2 + - @backstage/plugin-techdocs-react@1.3.9-next.1 + - @backstage/plugin-user-settings@0.9.1-next.2 + +## app-example-plugin@0.0.33-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-components@0.18.8-next.1 + +## example-app-legacy@0.2.119-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.13.0-next.2 + - @backstage/cli@0.36.0-next.2 + - @backstage/core-app-api@1.19.6-next.1 + - @backstage/plugin-techdocs@1.17.1-next.2 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/frontend-app-api@0.16.0-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.2 + - @backstage/core-components@0.18.8-next.1 + - @backstage/plugin-org@0.7.0-next.2 + - @backstage/plugin-catalog-graph@0.6.0-next.2 + - @backstage/plugin-mui-to-bui@0.2.5-next.2 + - @backstage/plugin-scaffolder-react@1.20.0-next.2 + - @backstage/plugin-api-docs@0.13.5-next.2 + - @backstage/plugin-catalog@2.0.0-next.2 + - @backstage/plugin-catalog-import@0.13.11-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.27-next.1 + - @backstage/plugin-devtools@0.1.37-next.2 + - @backstage/plugin-home@0.9.3-next.2 + - @backstage/plugin-home-react@0.1.36-next.1 + - @backstage/plugin-kubernetes@0.12.17-next.2 + - @backstage/plugin-notifications@0.5.15-next.1 + - @backstage/plugin-scaffolder@1.35.5-next.2 + - @backstage/plugin-search@1.6.2-next.2 + - @backstage/plugin-search-react@1.10.5-next.1 + - @backstage/plugin-signals@0.0.29-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.34-next.2 + - @backstage/plugin-techdocs-react@1.3.9-next.1 + - @backstage/plugin-user-settings@0.9.1-next.2 + +## example-backend@0.0.48-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/backend-defaults@0.16.0-next.2 + - @backstage/plugin-auth-backend@0.27.1-next.2 + - @backstage/plugin-catalog-backend@3.5.0-next.2 + - @backstage/plugin-scaffolder-backend@3.2.0-next.2 + - @backstage/plugin-mcp-actions-backend@0.1.10-next.2 + - @backstage/plugin-scaffolder-backend-module-github@0.9.7-next.2 + - @backstage/plugin-app-backend@0.5.12-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.5.1-next.1 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.17-next.1 + - @backstage/plugin-auth-backend-module-openshift-provider@0.1.5-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.12-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.2.20-next.2 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.18-next.2 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.9-next.1 + - @backstage/plugin-devtools-backend@0.5.15-next.1 + - @backstage/plugin-events-backend@0.6.0-next.2 + - @backstage/plugin-events-backend-module-google-pubsub@0.2.1-next.1 + - @backstage/plugin-kubernetes-backend@0.21.2-next.2 + - @backstage/plugin-notifications-backend@0.6.3-next.1 + - @backstage/plugin-permission-backend@0.7.10-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.17-next.1 + - @backstage/plugin-permission-node@0.10.11-next.1 + - @backstage/plugin-proxy-backend@0.6.11-next.1 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.20-next.2 + - @backstage/plugin-search-backend@2.1.0-next.2 + - @backstage/plugin-search-backend-module-catalog@0.3.13-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.8.1-next.1 + - @backstage/plugin-search-backend-module-explore@0.3.12-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.4.12-next.2 + - @backstage/plugin-search-backend-node@1.4.2-next.1 + - @backstage/plugin-signals-backend@0.3.13-next.1 + - @backstage/plugin-techdocs-backend@2.1.6-next.2 + +## @internal/frontend@0.0.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + +## @internal/scaffolder@0.0.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/plugin-scaffolder-react@1.20.0-next.2 + +## techdocs-cli-embedded-app@0.2.118-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.13.0-next.2 + - @backstage/cli@0.36.0-next.2 + - @backstage/core-app-api@1.19.6-next.1 + - @backstage/plugin-techdocs@1.17.1-next.2 + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-components@0.18.8-next.1 + - @backstage/plugin-app-react@0.2.1-next.1 + - @backstage/frontend-defaults@0.5.0-next.1 + - @backstage/plugin-catalog@2.0.0-next.2 + - @backstage/plugin-techdocs-react@1.3.9-next.1 + +## @internal/plugin-todo-list-backend@1.0.48-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 diff --git a/docs/releases/v1.49.0.md b/docs/releases/v1.49.0.md new file mode 100644 index 0000000000..83c2e69f3d --- /dev/null +++ b/docs/releases/v1.49.0.md @@ -0,0 +1,233 @@ +--- +id: v1.49.0 +title: v1.49.0 +description: Backstage Release v1.49.0 +--- + +These are the release notes for the v1.49.0 release of [Backstage](https://backstage.io/). + +A huge thanks to the whole team of maintainers and contributors as well as the amazing Backstage Community for the hard work in getting this release developed and done. + +## Highlights + +### New Frontend System: 1.0 Release Candidate + +This release marks the release candidate for version 1.0 of the New Frontend System. Newly created Backstage apps now use the New Frontend System by default, the `--next` flag for `create-app` has been replaced with a `--legacy` flag for apps that still want to use the old system. The `yarn new` templates have been updated accordingly, with auto-detection that presents the correct plugin templates based on which frontend system your app uses. + +On the framework side, this release includes significant stabilization of the API surface. Several deprecated types and options have been removed, `PluginWrapperApi` has graduated from alpha to stable, and a new builder pattern for `createApiRef` has been introduced. A full list of API changes can be found in the [`@backstage/frontend-plugin-api` changelog](https://github.com/backstage/backstage/blob/master/packages/frontend-plugin-api/CHANGELOG.md). + +Plugin pages across the supported plugins have been migrated to the Backstage UI `Header` component, aligning the plugin header and page-level navigation patterns across the New Frontend System. A new `@backstage/frontend-dev-utils` package with a `createDevApp` helper simplifies the setup of plugin development apps. + +Documentation has been overhauled across the board to cover and prioritize the New Frontend System, aligning installation guidance across plugin READMEs, closing gaps with the existing "Plugins" section that is now marked as legacy, and many other changes. + +### **BREAKING**: Backstage UI updates + +There are several new additions in Backstage UI, for example new `SearchAutocomplete` and `SearchAutocompleteItem` components for building accessible search-with-results patterns. New `List` and `ListRow` components built on React Aria's GridList for interactive lists with icons, descriptions, and selection. A `virtualized` prop added to the `Table` for rendering large datasets. And much more. + +There are also several breaking changes to note: + +- **Checkbox**: Removed the redundant `selected` and `indeterminate` props. Use `isSelected` and `isIndeterminate` instead, which are the standard React Aria props. +- **Header CSS classes**: Renamed internal CSS classes to match the `Header` component name (e.g. `bui-HeaderPage` → `bui-Header`). +- **BUIProvider routing**: Centralized client-side routing in `BUIProvider`. Components like Link, ButtonLink, Tabs, Menu, and Table now require a `BUIProvider` inside a React Router context for client-side navigation to work. +- **Data attributes**: Data attributes rendered by components are now always lowercase, affecting CSS selectors targeting camelCase attributes. +- **Removed deprecated types**: `ComponentDefinition`, `ClassNamesMap`, `DataAttributeValues`, and `DataAttributesMap` have been removed from the public API. +- **Popover token**: Removed `--bui-bg-popover` CSS token. Use `--bui-bg-neutral-1` or `--bui-bg-app` instead. +- **Neutral background**: Simplified the neutral background prop API. Explicit `neutral-1`, `neutral-2`, `neutral-3`, and `neutral-auto` values have been replaced by a single `neutral` value that auto-increments from the parent context. + +Check the [BUI Changelog](https://ui.backstage.io/changelog) for more details. + +### **BREAKING**: Entity cards migrated to Backstage UI + +The `@backstage/plugin-catalog` package has received a major version bump as `EntityAboutCard`, `EntityLinksCard`, `EntityLabelsCard`, `GroupProfileCard`, and `UserProfileCard` have been migrated from MUI to BUI. The `variant` prop has been removed from these cards, and the `gridSizes` prop has been removed from `AboutField`. + +Additionally, entity table cards like `HasComponentsCard`, `HasResourcesCard`, `HasSubcomponentsCard`, and others now render with BUI when no legacy props are passed. The old `variant`, `columns`, and `tableOptions` props are deprecated but still supported — passing them triggers the legacy MUI rendering. `RelatedEntitiesCard` is deprecated in favor of the new `EntityRelationCard` from `@backstage/plugin-catalog-react/alpha`. + +`MembersListCard`, `OwnershipCard`, and `CatalogGraphCard` from `@backstage/plugin-org` and `@backstage/plugin-catalog-graph` have also been migrated to BUI card primitives via `EntityInfoCard`. The `variant` prop has been removed from these cards. + +```diff +- ++ +``` + +### **BREAKING**: Deprecated Bitbucket and legacy integrations removed + +Several long-deprecated integrations have been cleaned up across the project. + +In `@backstage/integration`, the deprecated `bitbucket` config object, Azure DevOps `token`/`credential` fields, `parseGerritGitilesUrl`, `buildGerritGitilesArchiveUrl`, and `getGitHubRequestOptions` have all been removed. Use `bitbucketCloud`/`bitbucketServer`, the `credentials` array, `parseGitilesUrlRef`/`buildGerritGitilesArchiveUrlFromLocation`, respectively. + +In `@backstage/backend-defaults`, the deprecated `BitbucketUrlReader` has been removed. Use `BitbucketCloudUrlReader` or `BitbucketServerUrlReader` instead. + +In `@backstage/plugin-scaffolder-node`, the deprecated `bitbucket` integration has been removed from `parseRepoUrl`. It now uses `bitbucketCloud` or `bitbucketServer`. + +In `@backstage/plugin-scaffolder-common`, the deprecated `bitbucket` integration has been removed from `ScaffolderClient`. + +### **BREAKING PRODUCERS**: `ScaffolderApi` methods now required + +The `retry`, `listTasks`, `listTemplatingExtensions`, `dryRun`, and `autocomplete` methods are now required on the `ScaffolderApi` interface. Implementations must provide all of these methods. + +### **BREAKING**: Slack notifications direct messages + +The `@backstage/plugin-notifications-backend-module-slack` module now only sends direct messages to user entity recipients. Notifications sent to non-user entities no longer trigger Slack direct messages. + +Contributed by [@kaidubauskas-dd](https://github.com/kaidubauskas-dd) in [#32619](https://github.com/backstage/backstage/pull/32619). + +### AI and MCP improvements + +This release significantly expands the set of built-in actions available for AI agents and MCP consumers. New actions include `who-am-i` for identifying the authenticated user, `query-catalog-entities` for predicate-based catalog queries, `list-scaffolder-actions` for discovering available scaffolder actions with their schemas, `list-scaffolder-tasks` for querying scaffolder tasks with ownership filtering, and `get-scaffolder-task-logs` for retrieving log events from running tasks. + +The MCP actions backend now supports splitting actions into multiple servers via `mcpActions.servers` configuration, with include/exclude filter rules per server. Added `mcpActions.name` and `mcpActions.description` config options for customizing server identity, and tool names now use dot separators to align with the MCP spec convention. Error forwarding has also been improved so that known errors preserve their original status codes. + +A new `actions` CLI module provides `actions list`, `actions execute`, and `actions sources` commands for interacting with the actions registry directly from the terminal. + +See the [AI documentation](https://backstage.io/docs/ai/generated-index) for more information on using Backstage with AI agents. + +### CLI Module System + +The Backstage CLI has been refactored into an extensible module system. CLI commands are now provided by individual `cli-module` packages that are discovered automatically from your project's dependencies. This release includes the initial set of CLI modules: + +- `@backstage/cli-module-auth` +- `@backstage/cli-module-actions` +- `@backstage/cli-module-build` +- `@backstage/cli-module-config` +- `@backstage/cli-module-github` +- `@backstage/cli-module-info` +- `@backstage/cli-module-lint` +- `@backstage/cli-module-maintenance` +- `@backstage/cli-module-migrate` +- `@backstage/cli-module-new` +- `@backstage/cli-module-test-jest` +- `@backstage/cli-module-translations` + +If no CLI modules are found in project dependencies, the CLI falls back to the built-in set with a deprecation warning. To prepare for the removal of this fallback, add `@backstage/cli-defaults` as a `devDependency`: + +```json +{ + "devDependencies": { + "@backstage/cli-defaults": "backstage:^" + } +} +``` + +A new `createCliModule` API in `@backstage/cli-node` enables third-party CLI modules, and a `cli-module` template is available via `yarn new`. + +### CLI Auth Commands + +A new `auth` command group has been added for authenticating the CLI with Backstage instances using OAuth 2.0. Commands include `login`, `logout`, `list`, `show`, `print-token`, and `select` for managing multiple authenticated instances. An API for interacting with the selected and specific instance and the authentication sessions towards them has been added to `@backstage/cli-node` grouped under the `CliAuth` export. + +### `package bundle` Command + +A new `package bundle` command has been added to `@backstage/cli-module-build` for creating self-contained plugin bundles for dynamic loading, to be used by the `backend-dynamic-feature-service`. + +Contributed by [@davidfestal](https://github.com/davidfestal) in [#33125](https://github.com/backstage/backstage/pull/33125). + +### Predicate-based catalog entity filtering + +A powerful new predicate-based filtering system has been added to the catalog. The catalog client's `queryEntities()`, `getEntitiesByRefs()`, and `getEntityFacets()` methods now support a `query` field with operators like `$all`, `$any`, `$not`, `$exists`, `$in`, `$hasPrefix`, and `$contains` for expressive entity queries. + +These are exposed through new POST endpoints with full pagination and permission enforcement support. A new `query-catalog-entities` action has been added for predicate-based catalog queries via the actions system. + +### Actions: Permissions and user invocation + +Backend actions now support permission-based visibility. An optional `visibilityPermission` field has been added to `ActionsRegistryActionOptions`, allowing actions to declare a permission that controls access. + +The actions registry invoke endpoint now also accepts direct user credentials in addition to service principals, enabling CLI and other direct user clients to invoke actions. + +### New `ToastApi` for rich notifications + +A new `ToastApi` has been introduced in the New Frontend System for displaying rich toast notifications. Toasts support titles, descriptions, custom timeouts, action links, and status variants (neutral, info, success, warning, danger). They can also be dismissed programmatically. + +```typescript +const toastApi = useApi(toastApiRef); + +toastApi.post({ + title: 'Entity saved', + description: 'Your changes have been saved successfully.', + status: 'success', + timeout: 5000, + links: [{ label: 'View entity', href: '/catalog/entity' }], +}); +``` + +The `ToastDisplay` component subscribes to both `ToastApi` and the existing `AlertApi`, providing a migration path. The `AlertApi`, `AlertMessage`, `alertApiRef`, and `AlertApiForwarder` have been deprecated in favor of `ToastApi`. + +### Template secrets schema validation + +Templates can now define a `secrets.schema` field in `TemplateEntityV1beta3` for validating secrets provided during task creation. The scaffolder backend validates secrets against the schema and returns a `400` error if validation fails. + +### Scheduler `cancelTask` + +A new `cancelTask` method has been added to the `SchedulerService` interface and implementation, allowing cancellation of currently running scheduled tasks. A corresponding cancel button has been added to the DevTools scheduled tasks UI. + +### Entity page group aliases and content ordering + +Entity page groups can now declare `aliases` so that content targeting an aliased group is included in the group. A new `defaultContentOrder` option controls how content items within each group are sorted, with support for both page-level and per-group overrides. + +### `LogViewer` download support + +The `LogViewer` component now supports downloading logs via an `onDownloadLogs` callback. + +Contributed by [@0xts](https://github.com/0xts) in [#31989](https://github.com/backstage/backstage/pull/31989). + +### Translation refs promoted to stable + +Translation refs across 18 packages have been promoted from alpha-only exports to the stable entry points. If you are importing translation refs from `/alpha` entry points, for example `import { catalogTranslationRef } from '@backstage/plugin-catalog/alpha'`, you can now import them directly from the main package entry point instead. The alpha re-exports are still available but deprecated. + +### Permission batching + +Permission checks made in the same tick are now batched into a single call to the permission backend, improving performance for pages with many permission-gated elements. + +### GitHub org verified email preference + +The default user transformer for `githubOrg` now prefers organization verified domain emails over the user's public GitHub email. A new `defaultUserTransformer.useVerifiedEmails` config option controls this behavior for the config-based provider. + +### GitLab integration improvements + +Added configurable throttling and retry mechanism for GitLab integration. + +Contributed by [@JohannesWill](https://github.com/JohannesWill) in [#30804](https://github.com/backstage/backstage/pull/30804). + +Added a new `gitlab:group:access` scaffolder action for managing GitLab group memberships. + +Contributed by [@Jellyfrog](https://github.com/Jellyfrog) in [#32528](https://github.com/backstage/backstage/pull/32528). + +Added `maskedAndHidden` option to `gitlab:projectVariable:create` and `publish:gitlab`. + +Contributed by [@hyfc](https://github.com/hyfc) in [#32818](https://github.com/backstage/backstage/pull/32818). + +### Custom Slack message layouts + +Added an extension for custom Slack message layouts in the notifications backend Slack module. + +Contributed by [@kaidubauskas-dd](https://github.com/kaidubauskas-dd) in [#32733](https://github.com/backstage/backstage/pull/32733). + +### OpenAPI 3.1 support + +OpenAPI schemas across several packages have been migrated to version 3.1. The `schema openapi` commands in `@backstage/repo-tools` now auto-detect the OpenAPI version and support both 3.0.x and 3.1.x specifications. + +### Deprecated Catalog Processors & New Community Plugin Backend Modules + +Two Catalog processors have been deprecated as they have been moved to the Community Plugins repo with their own backend modules: + +- `AnnotateScmSlugEntityProcessor`: Use `@backstage-community/plugin-catalog-backend-module-annotate-scm-slug` instead +- `CodeOwnersProcessor`: Use `@backstage-community/plugin-catalog-backend-module-codeowners` instead + +## Security Fixes + +This release does not contain any security fixes. + +## Upgrade path + +We recommend that you keep your Backstage project up to date with this latest release. For more guidance on how to upgrade, check out the documentation for [keeping Backstage updated](https://backstage.io/docs/getting-started/keeping-backstage-updated). + +## Links and References + +Below you can find a list of links and references to help you learn about and start using this new release. + +- [Backstage official website](https://backstage.io/), [documentation](https://backstage.io/docs/), and [getting started guide](https://backstage.io/docs/getting-started/) +- [GitHub repository](https://github.com/backstage/backstage) +- Backstage's [versioning and support policy](https://backstage.io/docs/overview/versioning-policy) +- [Community Discord](https://discord.gg/backstage-687207715902193673) for discussions and support +- [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.49.0-changelog.md) +- Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) + +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.5.0.md b/docs/releases/v1.5.0.md index 325bee378d..58a9b42b75 100644 --- a/docs/releases/v1.5.0.md +++ b/docs/releases/v1.5.0.md @@ -57,4 +57,4 @@ Below you can find a list of links and references to help you learn about and st - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.5.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) -Sign up for our [newsletter](https://mailchi.mp/spotify/backstage-community) if you want to be informed about what is happening in the world of Backstage. +Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.50.0-changelog.md b/docs/releases/v1.50.0-changelog.md new file mode 100644 index 0000000000..481abc7034 --- /dev/null +++ b/docs/releases/v1.50.0-changelog.md @@ -0,0 +1,2837 @@ +# Release v1.50.0 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.50.0](https://backstage.github.io/upgrade-helper/?to=1.50.0) + +## @backstage/backend-defaults@0.17.0 + +### Minor Changes + +- c69e03c: Added support for AWS RDS IAM authentication for PostgreSQL connections. Set `connection.type: rds` along with `host`, `user`, and `region` to use short-lived IAM tokens instead of a static password. Requires the `@aws-sdk/rds-signer` package and an IAM role with `rds-db:connect` permission. + +### Patch Changes + +- 4559806: Added support for typed `examples` on actions registered via the actions registry. Action authors can now provide examples with compile-time-checked `input` and `output` values that match their schema definitions. +- 5cd814f: Refactored auditor severity log level mappings to use `zod/v4` with schema-driven defaults and type inference. +- 482ceed: Migrated from `assertError` to `toError` for error handling. +- 6e2aaab: Fixed `AwsS3UrlReader` failing to read files from S3 buckets configured with custom endpoint hosts. When an integration was configured with a specific endpoint like `https://bucket-1.s3.eu-central-1.amazonaws.com`, the URL parser incorrectly fell through to the non-AWS code path, always defaulting the region to `us-east-1` instead of extracting it from the hostname. +- 308c672: `HostDiscovery` now logs a warning when `backend.baseUrl` is set to a localhost address while `NODE_ENV` is `production`, and when `backend.baseUrl` is not a valid URL. +- 85c5a46: DefaultActionsRegistryService: add json middleware to /.backstage/actions/ routes only +- 547258f: Refactored the database creation retry loop to avoid an unnecessary delay after the final failed attempt. +- 79453c0: Updated dependency `wait-for-expect` to `^4.0.0`. +- f14df56: Added experimental support for using `embedded-postgres` as the database for local development. Set `backend.database.client` to `embedded-postgres` in your app config to enable this. The `embedded-postgres` package must be installed as an explicit dependency in your project. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/errors@1.3.0 + - @backstage/plugin-auth-node@0.7.0 + - @backstage/backend-app-api@1.6.1 + - @backstage/cli-node@0.3.1 + - @backstage/config-loader@1.10.10 + - @backstage/integration@2.0.1 + - @backstage/plugin-permission-node@0.10.12 + - @backstage/config@1.3.7 + - @backstage/integration-aws-node@0.1.21 + - @backstage/plugin-events-node@0.4.21 + - @backstage/plugin-permission-common@0.9.8 + +## @backstage/backend-plugin-api@1.9.0 + +### Minor Changes + +- 4559806: Added support for typed `examples` on actions registered via the actions registry. Action authors can now provide examples with compile-time-checked `input` and `output` values that match their schema definitions. + +### Patch Changes + +- 213ebe7: Aligned `.T` behavior between `ExtensionPoint` and `ServiceRef` to consistently return `null` instead of throwing. +- 68c557b: Added stricter type checks in `isDatabaseConflictError`. +- Updated dependencies + - @backstage/errors@1.3.0 + - @backstage/plugin-auth-node@0.7.0 + - @backstage/cli-common@0.2.1 + - @backstage/plugin-permission-node@0.10.12 + - @backstage/config@1.3.7 + - @backstage/plugin-permission-common@0.9.8 + +## @backstage/catalog-client@1.15.0 + +### Minor Changes + +- c384fff: **BREAKING PRODUCERS**: Added required `entityRef` field to the `Location` type, exposing the stable entity reference for each registered location. Any code that produces `Location` objects must now include this field. Added `updateLocation` method to `CatalogApi` for updating the type and target of an existing location. + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0 + - @backstage/catalog-model@1.8.0 + - @backstage/filter-predicates@0.1.2 + +## @backstage/catalog-model@1.8.0 + +### Minor Changes + +- e5fcfcb: Added a new catalog model layer system that allows plugins to declare and extend catalog entity kinds, annotations, labels, tags, and relations using JSON Schema. The new `createCatalogModelLayer` API provides a builder for composing model definitions, and a `compileCatalogModel` function validates and merges them into a unified model. Built-in entity kinds now include model layer definitions. + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0 + +## @backstage/cli-module-actions@0.1.0 + +### Minor Changes + +- c705d44: Added improved CLI output formatting and UX for the actions module. The `list` command now groups actions by plugin source with colored headers and action titles. The `execute --help` command renders full action details including markdown descriptions. Complex schema types like objects, arrays, and union types are now accepted as JSON flags. Error messages from the server are now surfaced directly. The `sources add` and `sources remove` commands accept multiple plugin IDs at once. + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0 + - @backstage/cli-node@0.3.1 + +## @backstage/core-app-api@1.20.0 + +### Minor Changes + +- 400aa23: Added `FetchMiddlewares.clarifyFailures()` which replaces the uninformative "TypeError: Failed to fetch" with a message that includes the request method and URL. + +### Patch Changes + +- 9244b70: The default auth implementation now checks for a `logoutUrl` in the logout response body. If the auth provider returns one (e.g. Auth0 federated logout), the browser is redirected to that URL to clear the provider's session cookies. This is backward compatible — providers that return an empty response are unaffected. +- Updated dependencies + - @backstage/ui@0.14.0 + - @backstage/config@1.3.7 + - @backstage/core-plugin-api@1.12.5 + +## @backstage/errors@1.3.0 + +### Minor Changes + +- b2319ff: A new `toError` utility function is now available for converting unknown values to `ErrorLike` objects. If the value is already error-like it is returned as-is, strings are used directly as the error message, and all other values are wrapped as `unknown error ''`. Non-error causes passed to `CustomErrorBase` are now converted and stored using `toError` rather than discarded. + +### Patch Changes + +- 608c1e5: Simplified `assertError` to delegate to `isError` instead of duplicating the same checks. + +## @backstage/frontend-plugin-api@0.16.0 + +### Minor Changes + +- fa55078: **BREAKING**: Removed the deprecated `createSchemaFromZod` helper. Use the new `configSchema` option instead. See the [1.50 migration documentation](https://backstage.io/docs/frontend-system/architecture/migrations#150) for more information. +- 49397c1: Simplified the type signature of `createRouteRef` by replacing the dual `TParams`/`TParamKeys` type parameters with a single `TParamKey` parameter. This is a breaking change for callers that explicitly provided type arguments, but most usage that relies on inference is unaffected. + +### Patch Changes + +- 4c09967: Fixed the duplicate extension error message in `createFrontendModule` to correctly say "Module" instead of "Plugin". +- e4804ab: Added `open` method to `DialogApi` that renders dialogs without any built-in dialog chrome, giving the caller full control over the dialog presentation. This avoids focus trap conflicts that occur when mixing components from different design libraries. The existing `show` and `showModal` methods are now deprecated in favor of `open`. +- ddc5247: Fixed `FlattenedMessages` type to avoid excessive type instantiation depth in TypeScript 6 when using `createTranslationRef` with the `translations` option. +- a2a6c3b: Added a new `configSchema` option for `createExtension` and `createExtensionBlueprint` that accepts direct schema values from any [Standard Schema](https://github.com/standard-schema/standard-schema) compatible library with JSON Schema support, such as zod v4 (`zod@^4.0.0`). The old `config.schema` option is now deprecated. Note that Zod v3 is not supported by the new `configSchema` option, including the `zod/v4` subpath export which does not include JSON Schema conversion support. You must upgrade to the `zod` v4 package. See the [1.50 migration documentation](https://backstage.io/docs/frontend-system/architecture/migrations#150) for more information. +- d66a3ec: Added `titleLink` prop to `PageLayoutProps` so the plugin header title can link back to the plugin root. +- e220589: Removed the unnecessary need to use `defineParams` callback from `PluginHeaderActionBlueprint`. It still works, but is no longer required. +- Updated dependencies + - @backstage/errors@1.3.0 + - @backstage/filter-predicates@0.1.2 + +## @backstage/ui@0.14.0 + +### Minor Changes + +- 8659f33: **BREAKING**: The `Header` component's `tabs` prop now uses `HeaderNavTabItem[]` instead of `HeaderTab[]`. Tabs render as a `
    - ); -}; -``` - -# The Fetch - -We're ready to flush out our fetch component - -1. Add our api hook inside ExampleFetchComponent - -```tsx -const auth = useApi(githubAuthApiRef); -``` - -2. The access token we need to make our GitHub request and the request itself is - obtained in an asynchronous manner. -3. Add the `useAsync` block inside the ExampleFetchComponent - -```tsx -const { value, loading, error } = useAsync(async (): Promise => { - const token = await auth.getAccessToken(); - - const gqlEndpoint = graphql.defaults({ - // Uncomment baseUrl if using enterprise - // baseUrl: 'https://github.MY-BIZ.com/api', - headers: { - authorization: `token ${token}`, - }, - }); - const { viewer } = await gqlEndpoint(query); - return viewer; -}, []); -``` - -4. The resolved data is conveniently destructured with `value` containing our - Viewer type. `loading` as a boolean, self explanatory. And `error` which is - present only if necessary. So let's use those as the first 3 of 4 multi - return statements. -5. Add the _if return_ blocks below our async block - -```tsx -if (loading) return ; -if (error) return {error.message}; -if (value && value.repositories) return ; -``` - -6. The third line here utilizes our custom table accepting our Viewer type. -7. Finally, we add our _else return_ block to catch any other scenarios. - -```tsx -return ( -
    -); -``` - -8. After saving that, and given we don't have any errors, you should see a table - with basic information on your repositories. -9. Here is the entire file for reference - [ExampleFetchComponent.tsx](https://github.com/backstage/backstage/tree/master/contrib/docs/tutorials/quickstart-app-plugin/ExampleFetchComponent.md) -10. We finished! You should see your own GitHub repository's information - displayed in a basic table. If you run into issues, you can compare the repo - that backs this document, - [simple-backstage-app-plugin](https://github.com/johnson-jesse/simple-backstage-app-plugin) - -# Where to go from here - -> Break apart ExampleFetchComponent into smaller logical parts contained in -> their own files. Rename your components to something other than ExampleXxx. -> -> You might be really proud of a plugin you develop. Follow this next tutorial -> for an in-depth look at publishing and including that for the entire Backstage -> community. [TODO](#). diff --git a/docs/tutorials/react-router-stable-migration.md b/docs/tutorials/react-router-stable-migration.md index f4dcc33559..60e7da8c53 100644 --- a/docs/tutorials/react-router-stable-migration.md +++ b/docs/tutorials/react-router-stable-migration.md @@ -151,31 +151,6 @@ Somewhat related to the `Routes` change, it is no longer possible to render a such a `Route` element would cause the contents of its `element` prop to be rendered instead, but it will now throw an error. -### `PermissionedRoute` - -Because of the above change, the `PermissionedRoute` component no longer works in all situations with React Router v6 stable. It has been deprecated in favor of the new `RequirePermission` component, which can be placed anywhere in order to perform a permissions check. - -It's crucial that you update to `RequirePermission` at the same time as you update to React Router v6 stable as the `PermissionedRoute` component will no longer function. - -```tsx -{/* highlight-remove-start */} -} -{/* highlight-remove-end */} -{/* highlight-add-start */} - - - - } -{/* highlight-add-end */} -/> -``` - ### `` component When migrating over to React Router v6 stable, you might also see browser console warnings for the `Navigate` component. This will need to be wrapped up in a `Route` component with the `Navigate` component in the `element` prop. diff --git a/docs/tutorials/using-backstage-proxy-within-plugin.md b/docs/tutorials/using-backstage-proxy-within-plugin.md index 58e973d891..fef4b5b8e0 100644 --- a/docs/tutorials/using-backstage-proxy-within-plugin.md +++ b/docs/tutorials/using-backstage-proxy-within-plugin.md @@ -25,7 +25,7 @@ If your plugin requires access to an API, backstage offers - [Option 2: Defining the API client interface](#defining-the-api-client-interface) - [Creating the API client](#creating-the-api-client) - [Bundling your ApiRef with your plugin](#bundling-your-apiref-with-your-plugin) - - [Using the API in your components](#using-your-plugin-in-your-components) + - [Using the API in your components](#using-the-api-in-your-components) ## Setting up the backstage proxy @@ -46,7 +46,7 @@ proxy: You can find more details about the proxy config options in the [proxying section](../plugins/proxying.md). -# Calling an API using the backstage proxy +## Calling an API using the backstage proxy If you followed the previous steps, you should now be able to access your API by calling `${backend-url}/api/proxy/`. The reason why @@ -94,15 +94,15 @@ export const AwesomeUsersTable = () => { This section describes the steps to wrap your API client in a [Utility API](../api/utility-apis.md), which are: -- use [`createApiRef`](../reference/core-plugin-api.createapiref.md) to create a - new [`ApiRef`](../reference/core-plugin-api.apiref.md) -- register an [`ApiFactory`](../reference/core-plugin-api.apifactory.md) with +- use [`createApiRef`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.index.createApiRef.html) to create a + new [`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.ApiRef.html) +- register an [`ApiFactory`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.ApiFactory.html) with your plugin using - [`createApiFactory`](../reference/core-plugin-api.createapifactory.md). This + [`createApiFactory`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.index.createApiFactory.html). This will wrap your API implementation, associate your `ApiRef` with your implementation and tell backstage how to instantiate it - finally, you can use your API in your components by calling - [`useApi`](../reference/core-plugin-api.useapi.md) + [`useApi`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.index.useApi.html) ### Defining the API client interface @@ -187,8 +187,8 @@ export class MyAwesomeApiClient implements MyAwesomeApi { ``` > 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) +> [DiscoveryApi](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.DiscoveryApi.html) or the +> [FetchApi](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.index.FetchApi.html) ### Bundling your ApiRef with your plugin @@ -233,7 +233,7 @@ export const myCustomPlugin = createPlugin({ ### Using the API in your components Now you should be able to access your API using the backstage hook -[`useApi`](../reference/core-plugin-api.useapi.md) from within your plugin code. +[`useApi`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.index.useApi.html) from within your plugin code. ```ts title="plugins/my-awesome-plugin/src/components/AwesomeUsersTable.tsx" import { useApi } from '@backstage/core-plugin-api'; diff --git a/lighthouserc.js b/lighthouserc.js index c7c1275cdb..fc00cd2b9d 100644 --- a/lighthouserc.js +++ b/lighthouserc.js @@ -25,19 +25,21 @@ module.exports = { 'http://localhost:3000/docs', 'http://localhost:3000/docs/default/component/backstage', /** Software Templates */ - 'http://localhost:3000/create', - 'http://localhost:3000/create/tasks', - 'http://localhost:3000/create/actions', - 'http://localhost:3000/create/edit', - 'http://localhost:3000/create/templating-extensions', - 'http://localhost:3000/create/templates/default/react-ssr-template', + // TODO(Rugvip): Figure out why these don't work after the BUI header switch + // 'http://localhost:3000/create', + // 'http://localhost:3000/create/tasks', + // 'http://localhost:3000/create/actions', + // 'http://localhost:3000/create/edit', + // 'http://localhost:3000/create/templating-extensions', + // 'http://localhost:3000/create/templates/default/react-ssr-template', /** Search */ 'http://localhost:3000/search', /** Miscellaneous */ - 'http://localhost:3000/settings', + // TODO(Rugvip): Figure out why these don't work after the BUI header switch + // 'http://localhost:3000/settings/general', /** plugin-devtools */ - 'http://localhost:3000/devtools', - 'http://localhost:3000/devtools/config', + // 'http://localhost:3000/devtools/info', + // 'http://localhost:3000/devtools/config', /** plugin-explore */ 'http://localhost:3000/explore', 'http://localhost:3000/explore/groups', @@ -54,7 +56,7 @@ module.exports = { preset: 'desktop', }, startServerCommand: 'yarn start', - startServerReadyPattern: 'webpack compiled successfully', + startServerReadyPattern: 'compiled.*successfully', startServerReadyTimeout: 600000, numberOfRuns: 1, puppeteerScript: './.lighthouseci/scripts/guest-auth.js', diff --git a/microsite/README.md b/microsite/README.md index 62a28fb6a3..42b39acfa4 100644 --- a/microsite/README.md +++ b/microsite/README.md @@ -13,6 +13,7 @@ This website was created with [Docusaurus](https://docusaurus.io/). - [Editing Content](#editing-content) - [Adding Content](#adding-content) - [Full Documentation](#full-documentation) +- [How to patch the stable docs](#patching-the-docs) # Getting Started @@ -240,3 +241,7 @@ Full documentation can be found on the [Docusaurus website](https://docusaurus.i A feedback widget is provided by the `docusaurus-pushfeedback` plugin, which connects to the https://pushfeedback.com service. Styling of the button is configured in `microsite/src/theme/customTheme.scss`, while the plugin itself is configured in `microsite/docusaurus.config.js`. Feedback submissions are connected to an account owned by @Rugvip and are available on request, reach out to @Rugvip on [Discord](https://discord.gg/backstage-687207715902193673). + +# Patching the docs + +To patch the documentation for the stable version, you can submit a PR to the correct `patch/v` branch. For example, if `1.48.0` is the stable version, you would submit a fix to the branch `patch/v1.48.0`. Once your fix is in, you can trigger the `deploy_microsite` GH action (or wait until the next run on master) and you should see the fix on the stable version at https://backstage.io/docs. diff --git a/microsite/blog/2020-03-18-what-is-backstage.mdx b/microsite/blog/2020-03-18-what-is-backstage.mdx index caac489ca1..2fd04768d6 100644 --- a/microsite/blog/2020-03-18-what-is-backstage.mdx +++ b/microsite/blog/2020-03-18-what-is-backstage.mdx @@ -36,7 +36,7 @@ Our internal installation of Backstage has over 100 different integrations — w 3. Centralised technical documentation 4. Review performance of your team’s mobile features -These are just a few examples. Expect us to continue providing examples of how Backstage is used inside Spotify while we build out more end-2-end use-cases in the open. +These are just a few examples. Expect us to continue providing examples of how Backstage is used inside Spotify while we build out more end-to-end use-cases in the open. ### 1. Creating a new microservice diff --git a/microsite/blog/2020-04-30-how-to-quickly-set-up-backstage.mdx b/microsite/blog/2020-04-30-how-to-quickly-set-up-backstage.mdx index d01bd496d1..735191a3e2 100644 --- a/microsite/blog/2020-04-30-how-to-quickly-set-up-backstage.mdx +++ b/microsite/blog/2020-04-30-how-to-quickly-set-up-backstage.mdx @@ -50,7 +50,7 @@ yarn start And you are good to go! 👍 -Read the full documentation on how to [create an app](/docs/getting-started/create-an-app) on GitHub. +Read the full documentation on how to [create an app](/docs/getting-started/) on GitHub. ## What do I get? (Let's get technical...) diff --git a/microsite/blog/2020-05-22-phase-2-service-catalog.mdx b/microsite/blog/2020-05-22-phase-2-service-catalog.mdx index 9e681b9da3..550485ba60 100644 --- a/microsite/blog/2020-05-22-phase-2-service-catalog.mdx +++ b/microsite/blog/2020-05-22-phase-2-service-catalog.mdx @@ -39,7 +39,7 @@ On top of that, we have found that the service catalog is a great way to organis ![img](assets/20-05-20/tabs.png) -More concretely, having this structure in place will allow plugins such as [CircleCI](https://github.com/backstage/backstage/tree/master/plugins/circleci) to show only the builds for the specific service you are viewing, or a [Spinnaker](https://github.com/backstage/backstage/issues/631) plugin to show running deployments, or an Open API plugin to [show documentation](https://github.com/backstage/backstage/issues/627) for endpoints exposed by the service, or the [Lighthouse](https://github.com/backstage/community-plugins/tree/main/workspaces/lighthouse/plugins/lighthouse) plugin to show audit reports for your website. You get the point. +More concretely, having this structure in place will allow plugins such as [CircleCI](https://github.com/CircleCI-Public/backstage-plugin/tree/main/plugins/circleci) to show only the builds for the specific service you are viewing, or a [Spinnaker](https://github.com/backstage/backstage/issues/631) plugin to show running deployments, or an Open API plugin to [show documentation](https://github.com/backstage/backstage/issues/627) for endpoints exposed by the service, or the [Lighthouse](https://github.com/backstage/community-plugins/tree/main/workspaces/lighthouse/plugins/lighthouse) plugin to show audit reports for your website. You get the point. ## Timeline diff --git a/microsite/blog/2020-06-22-backstage-service-catalog-alpha.mdx b/microsite/blog/2020-06-22-backstage-service-catalog-alpha.mdx index 98f4aa15e1..ee17adfc12 100644 --- a/microsite/blog/2020-06-22-backstage-service-catalog-alpha.mdx +++ b/microsite/blog/2020-06-22-backstage-service-catalog-alpha.mdx @@ -51,4 +51,4 @@ As with most alpha releases, you should expect things to change quite a lot unti If you have feedback or questions, please open a [GitHub issue](https://github.com/backstage/backstage/issues), ping us on [Discord chat](https://discord.gg/backstage-687207715902193673) or send us an email at [backstage-interest@spotify.com](mailto:backstage-interest@spotify.com) 🙏 -To get regular product updates and news about the Backstage community, sign up for the [Backstage newsletter](https://info.backstage.spotify.com/newsletter_subscribe). +To get regular product updates and news about the Backstage community, sign up for the [Backstage newsletter](https://spoti.fi/backstagenewsletter). diff --git a/microsite/blog/2020-08-05-announcing-backstage-software-templates.mdx b/microsite/blog/2020-08-05-announcing-backstage-software-templates.mdx index af606dc8da..5f3313ed5b 100644 --- a/microsite/blog/2020-08-05-announcing-backstage-software-templates.mdx +++ b/microsite/blog/2020-08-05-announcing-backstage-software-templates.mdx @@ -75,4 +75,4 @@ We have learned that one of the keys to getting these standards adopted is to ke If you have feedback or questions, please open a [GitHub issue](https://github.com/backstage/backstage/issues), ping us on [Discord chat](https://discord.gg/backstage-687207715902193673) or send us an email at [backstage-interest@spotify.com](mailto:backstage-interest@spotify.com) 🙏 -To get regular product updates and news about the Backstage community, sign up for the [Backstage newsletter](https://info.backstage.spotify.com/newsletter_subscribe). +To get regular product updates and news about the Backstage community, sign up for the [Backstage newsletter](https://spoti.fi/backstagenewsletter). diff --git a/microsite/blog/2020-09-23-backstage-cncf-sandbox.mdx b/microsite/blog/2020-09-23-backstage-cncf-sandbox.mdx index b11b0ff18b..78be28b55d 100644 --- a/microsite/blog/2020-09-23-backstage-cncf-sandbox.mdx +++ b/microsite/blog/2020-09-23-backstage-cncf-sandbox.mdx @@ -18,4 +18,4 @@ The Backstage community is healthy and growing quickly. Over [130 people](https: We’re excited to embark on this journey with the CNCF community. There’s so much great tech being built here, and it’s about time we share it to build even greater products, together. Entering into the CNCF Sandbox is just the first step. We are committed to working with the community to bring Backstage through the Incubation step, and finally all the way to becoming a Graduated, top-level project. -Thanks to everyone for your support so far. We hope you [join us](https://info.backstage.spotify.com/newsletter_subscribe) in this next chapter of Backstage's journey. If you have questions or feedback, feel free to [email](mailto:backstage-interest@spotify.com) me directly. +Thanks to everyone for your support so far. We hope you [join us](https://spoti.fi/backstagenewsletter) in this next chapter of Backstage's journey. If you have questions or feedback, feel free to [email](mailto:backstage-interest@spotify.com) me directly. diff --git a/microsite/blog/2021-06-22-spotify-backstage-is-growing.mdx b/microsite/blog/2021-06-22-spotify-backstage-is-growing.mdx index 8c6ee78671..092c4b80f6 100644 --- a/microsite/blog/2021-06-22-spotify-backstage-is-growing.mdx +++ b/microsite/blog/2021-06-22-spotify-backstage-is-growing.mdx @@ -49,7 +49,7 @@ We’ve launched a new website at: [backstage.spotify.com](https://backstage.spo You’ll find a high-level introduction to the platform, tips and tricks tested by Spotify to accelerate developer effectiveness, and access to a group of partners that have scaled Backstage for numerous adopters. You can also use the site to book product overviews, demos, and technical deep dives with members of the Spotify team. -We will continue to post important product announcements, technical documentation, feature demos, and community news here on Backstage.io. ([Subscribe to the newsletter](https://info.backstage.spotify.com/newsletter_subscribe) to stay up to date.) And both contributors and adopting companies can continue to find around-the-clock/around-the-world technical support on [GitHub](https://github.com/backstage/backstage) and [Discord](https://discord.gg/backstage-687207715902193673). +We will continue to post important product announcements, technical documentation, feature demos, and community news here on Backstage.io. ([Subscribe to the newsletter](https://spoti.fi/backstagenewsletter) to stay up to date.) And both contributors and adopting companies can continue to find around-the-clock/around-the-world technical support on [GitHub](https://github.com/backstage/backstage) and [Discord](https://discord.gg/backstage-687207715902193673). ## Separate community sessions for adopters and contributors diff --git a/microsite/blog/2021-09-30-50-public-adopters.mdx b/microsite/blog/2021-09-30-50-public-adopters.mdx index e8f6c58160..40905850ec 100644 --- a/microsite/blog/2021-09-30-50-public-adopters.mdx +++ b/microsite/blog/2021-09-30-50-public-adopters.mdx @@ -41,5 +41,5 @@ If you are a Backstage enthusiast, please [join me][news] and the entire Backsta [da]: https://medium.com/dazn-tech/developer-experience-dx-at-dazn-e6de9a0208d2 [ex]: https://backstage.spotify.com/blog/community-session/firehydrant-expedia-loblaw/ [plugins]: https://backstage.io/plugins -[news]: https://info.backstage.spotify.com/newsletter_subscribe +[news]: https://spoti.fi/backstagenewsletter [gh]: https://github.com/backstage/backstage/blob/master/ADOPTERS.md diff --git a/microsite/blog/2023-01-31-incremental-entity-provider.mdx b/microsite/blog/2023-01-31-incremental-entity-provider.mdx index 8138b8a39a..2121c3f950 100644 --- a/microsite/blog/2023-01-31-incremental-entity-provider.mdx +++ b/microsite/blog/2023-01-31-incremental-entity-provider.mdx @@ -16,7 +16,7 @@ Data is transformed into entities via what is known as the ingestion and process ## Entity Providers -Backstage offers what are known as [entity providers](https://backstage.io/docs/features/software-catalog/life-of-an-entity) as a means for ingesting the raw data into the pipeline and transforming them into Backstage entities. For example, Backstage comes with a [GitHub Entity Provider](https://backstage.io/docs/reference/plugin-catalog-backend-module-github) that finds all catalog-info.yaml files in GitHub repositories. The processing loop transforms them into Backstage entities and subsequently persists them to the software catalog. +Backstage offers what are known as [entity providers](https://backstage.io/docs/features/software-catalog/life-of-an-entity) as a means for ingesting the raw data into the pipeline and transforming them into Backstage entities. For example, Backstage comes with a [GitHub Entity Provider](https://backstage.io/api/stable/modules/_backstage_plugin-catalog-backend-module-github.html) that finds all catalog-info.yaml files in GitHub repositories. The processing loop transforms them into Backstage entities and subsequently persists them to the software catalog. Entity providers are a relatively new abstraction and the recommended way to ingest data into the catalog. The Backstage catalog engine starts each registered entity provider, which connects to its data source (e.g., the GitHub Entity Provider connects to GitHub). The entity provider will query the external data source and convert the data into the entity format. Finally, the entity provider issues what is known as a mutation to the catalog engine. A mutation is a signal from the entity provider to the catalog engine that entities are available to be processed and stored. diff --git a/microsite/blog/2023-04-26-kubecon-eu-2023.mdx b/microsite/blog/2023-04-26-kubecon-eu-2023.mdx index 8f369c2d14..983e546bd2 100644 --- a/microsite/blog/2023-04-26-kubecon-eu-2023.mdx +++ b/microsite/blog/2023-04-26-kubecon-eu-2023.mdx @@ -1,7 +1,7 @@ --- title: 'ICYMI: KubeCon EU 2023' author: Bailey Brooks, Spotify -authorURL: http://github.com/bailey +authorURL: https://github.com/bailey authorImageURL: https://pbs.twimg.com/profile_images/1477424303192694785/qCfN6XWW_400x400.jpg --- diff --git a/microsite/blog/2023-06-21-switching-out-sandbox.mdx b/microsite/blog/2023-06-21-switching-out-sandbox.mdx index f86cb126a3..920a1f54d8 100644 --- a/microsite/blog/2023-06-21-switching-out-sandbox.mdx +++ b/microsite/blog/2023-06-21-switching-out-sandbox.mdx @@ -71,4 +71,4 @@ This change comes with the v1.15.0 release of Backstage that was released yester For more guidance on how to upgrade, check out the documentation for [keeping Backstage updated](https://backstage.io/docs/getting-started/keeping-backstage-updated). -If you have any further questions you can either reach out to us in the [Community Discord](https://discord.gg/backstage-687207715902193673), or in the [office hours](https://info.backstage.spotify.com/office-hours). +If you have any further questions you can either reach out to us in the [Community Discord](https://discord.gg/backstage-687207715902193673), or in the [office hours](https://spoti.fi/backstageofficehours). diff --git a/microsite/blog/2023-09-29-chicago-traiding-company-adopter-spotlight.mdx b/microsite/blog/2023-09-29-chicago-traiding-company-adopter-spotlight.mdx index bd479a6a86..d7297ad145 100644 --- a/microsite/blog/2023-09-29-chicago-traiding-company-adopter-spotlight.mdx +++ b/microsite/blog/2023-09-29-chicago-traiding-company-adopter-spotlight.mdx @@ -21,19 +21,19 @@ After exploring several service routes from managed to in-house/owned, CTC opted The CTC DevOps team created a small special interest group to champion the developer portal build and quickly delved into the big issues impacting their developer experience. From there, the team began creating tasks and templates within Backstage. -To start, Scott worked with end-user teams outside DevOps early in the process of documenting migration to the new DevOps Kubernetes platform services. When Scott wrote the onboarding documentation, he made it a point to pair with a developer on another team that would be using it. This approach provided him with a quality sounding board, great instantaneous feedback, and a sort of "beta developer" to test V1 documentation clarity., A few weeks later, another colleague on the same end-user team followed the documentation and she didn't reach out to Scott at all. +To start, Scott worked with end-user teams outside DevOps early in the process of documenting migration to the new DevOps Kubernetes platform services. When Scott wrote the onboarding documentation, he made it a point to pair with a developer on another team that would be using it. This approach provided him with a quality sounding board, great instantaneous feedback, and a sort of "beta developer" to test V1 documentation clarity. A few weeks later, another colleague on the same end-user team followed the documentation and she didn't reach out to Scott at all. After this early win, the team was ready for broad distribution. Partnering with the senior leadership team, Scott's team began onboarding teams to the new DevOps Kubernetes platform using the templates his team created in Backstage. At CTC, their Backstage instance is set up to automatically scan for deployments in Kubernetes; so if you're in Kubernetes, you're onboarded to Backstage. -"Backstage made onboarding [to the new DevOps platform] not scary,"" Scott said. "Because now — all of a sudden — you have this recipe for how to do it, you don't have to jump through these hoops, the templates are there and ready for you. You choose what you want based on these templates we have available, and you're off and running on your own."" +"Backstage made onboarding [to the new DevOps platform] not scary," Scott said. "Because now — all of a sudden — you have this recipe for how to do it, you don't have to jump through these hoops, the templates are there and ready for you. You choose what you want based on these templates we have available, and you're off and running on your own." Scott's team monitored progress against their onboarding goals partially by how often the DevOps team was getting pinged for support and found that it has made support smoother. They can easily refer people to templates and repo standards instead of making them create something ad hoc. -"It's really been cool seeing how that progressed. I even have a few teams that didn't talk to DevOps at all. They used a [template] and they have a service ready. We were actually pretty excited about that, because that means now we have a scalable tool able to onboard others onto our platform."" said Scott. +"It's really been cool seeing how that progressed. I even have a few teams that didn't talk to DevOps at all. They used a [template] and they have a service ready. We were actually pretty excited about that, because that means now we have a scalable tool able to onboard others onto our platform," said Scott. -Another exciting win for CTC was template additions generated outside the initial toolkit, which has eased the burden substantially for the DevOps team. They were able to create new templates to deploy against best practices such as a template for creating a brand-new Terraform module and Git repo or — Scott's favorite — ​​a template that creates a Java repository along with Jenkins jobs for CI and the Flux CD config for deployment. +Another exciting win for CTC was template additions generated outside the initial toolkit, which has eased the burden substantially for the DevOps team. They were able to create new templates to deploy against best practices such as a template for creating a brand-new Terraform module and Git repo or — Scott's favorite — a template that creates a Java repository along with Jenkins jobs for CI and the Flux CD config for deployment. -"The template builds and auto-deploys a Docker image, so if you make a change to your mainline branch, then it will automatically build a new Docker image and deploy it to a Kubernetes cluster."" Scott said. "So within basically 10 minutes of you filling out a form, you actually have something deployed out to a Kubernetes cluster."" +"The template builds and auto-deploys a Docker image, so if you make a change to your mainline branch, then it will automatically build a new Docker image and deploy it to a Kubernetes cluster," Scott said. "So within basically 10 minutes of you filling out a form, you actually have something deployed out to a Kubernetes cluster." This process eased flows for both end-user devs and DevOps teams overall. @@ -49,12 +49,12 @@ For CTC, the journey with Backstage has just begun. They're now looking to drive When asked about advice he has for other Backstage adopters, Scott talked about the DevOps team's comms strategy and ensuring end-user developers had pathways to provide feedback. In addition to the focus on templates for the new services, having both a dedicated Slack channel and open-door communication with the DevOps team helped reduce onboarding friction to the DevOps Kubernetes platform. -Outside of that, he believes the approach to documentation is paramount. "Write your documentation from the perspective of, 'Hey, you have this task assigned to you to write a template, here are the exact steps you have to follow.'"" Scott said. "Think of it as a recipe. Removing your familiarity bias will help make this tool more useful for less familiar teams."" +Outside of that, he believes the approach to documentation is paramount. "Write your documentation from the perspective of, 'Hey, you have this task assigned to you to write a template, here are the exact steps you have to follow'," Scott said. "Think of it as a recipe. Removing your familiarity bias will help make this tool more useful for less familiar teams." Finally, don't be afraid to step outside the core use cases when it comes to the Backstage framework and finding solutions to meet your needs. -"Backstage gives you a lot of really easy to use features right out of the box. And one of the great things about open source is you can look at the code and see exactly what the behavior is and work within that behavior. But we've also developed our own processes for custom tasks because — at the end of the day — our platform has some very custom aspects to it."" Scott said. +"Backstage gives you a lot of really easy to use features right out of the box. And one of the great things about open source is you can look at the code and see exactly what the behavior is and work within that behavior. But we've also developed our own processes for custom tasks because — at the end of the day — our platform has some very custom aspects to it," Scott said. Interested in more stories from Backstage adopters? Check out these recent posts from [Stash](https://backstage.io/blog/2023/07/08/stash-adopter-post) and [Expedia Group](https://backstage.io/blog/2023/08/17/expedia-proof-of-value-metrics-2). -Want to learn more about Backstage? Join our weekly [Office Hours](https://info.backstage.spotify.com/office-hours) and bring your burning questions. +Want to learn more about Backstage? Join our weekly [Office Hours](https://spoti.fi/backstageofficehours) and bring your burning questions. diff --git a/microsite/blog/2024-06-27-backstage-engineer-journey.mdx b/microsite/blog/2024-06-27-backstage-engineer-journey.mdx index 9180a4b6d7..d0aea4ca50 100644 --- a/microsite/blog/2024-06-27-backstage-engineer-journey.mdx +++ b/microsite/blog/2024-06-27-backstage-engineer-journey.mdx @@ -61,7 +61,7 @@ My goal has changed from being a contributor to being the driver, i.e., from doi At Spotify, we expect engineers to use technologies approved by our [Tech Radar](https://backstage.io/blog/2020/05/14/tech-radar-plugin/#what-is-tech-radar) and follow the best quality standards. -To do so, we use a plugin called [Scaffolder](https://backstage.io/docs/reference/plugin-scaffolder) (aka Software Templates), which allows engineers to create templates for setting up new projects. Templates automate actions such as creating repositories, allocating resources, registering in the catalog, configuring deployments, and connecting to other internal systems. +To do so, we use a plugin called [Scaffolder](https://backstage.io/api/stable/modules/_backstage_plugin-scaffolder.html) (aka Software Templates), which allows engineers to create templates for setting up new projects. Templates automate actions such as creating repositories, allocating resources, registering in the catalog, configuring deployments, and connecting to other internal systems. > **Reflection:**
    _“Do you need to create a ticket to get these things done before you even start coding? And is there a lot of time spent looking for boilerplate code to start a new project and connecting it to other internal systems?"_ diff --git a/microsite/blog/2025-08-08-migrating-legacy-services-to-a-modern-developer-portal.mdx b/microsite/blog/2025-08-08-migrating-legacy-services-to-a-modern-developer-portal.mdx index 268fd4933b..788d7b4d37 100644 --- a/microsite/blog/2025-08-08-migrating-legacy-services-to-a-modern-developer-portal.mdx +++ b/microsite/blog/2025-08-08-migrating-legacy-services-to-a-modern-developer-portal.mdx @@ -1,6 +1,6 @@ --- title: 'Migrating legacy services to a modern developer portal: A technical guide to Backstage integration' -author: Damola Obaleke +author: Damola Obaleke, The New York Times authorURL: https://github.com/damolaobaleke authorImageURL: https://avatars.githubusercontent.com/u/45045727?v=4 --- diff --git a/microsite/blog/2025-11-25-backstagecon-kubecon-25-atlanta.mdx b/microsite/blog/2025-11-25-backstagecon-kubecon-25-atlanta.mdx new file mode 100644 index 0000000000..62f8c36dae --- /dev/null +++ b/microsite/blog/2025-11-25-backstagecon-kubecon-25-atlanta.mdx @@ -0,0 +1,93 @@ +--- +title: 'Backstage in Atlanta: Highlights from BackstageCon and KubeCon + CloudNativeCon North America 2025' +author: Kate Klymkovska, Spotify & André Wanlin, Spotify +--- + +![Backstage in Atlanta: Highlights from BackstageCon and KubeCon + CloudNativeCon North America 2025](assets/2025-11-25/backstagecon-kubecon-na-2025-header.png) + +Cancelled flights and wonky Wi-Fi couldn't hold us back from having an amazing time in the Big Peach! It was great to head south and commune with the community in person at [BackstageCon](https://colocatedeventsna2025.sched.com/overview/type/BackstageCon) and [KubeCon + CloudNativeCon North America 2025](https://kccncna2025.sched.com) in Atlanta. We gave talks, shared insights, chatted without having to type into little text boxes, and tackled issues elbow-to-elbow during [ContribFest](https://backstage.io/blog/2025/10/15/backstage-contribfest-kubecon-guide). The week was capped off with the [Backstage maintainers talk](https://www.youtube.com/watch?v=evmmr-uxNsc&list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU) where we celebrated a year of milestones, the release of the adoption-ready New Frontend System, and work towards an AI-Native Backstage. Catch up with recordings of [all the Backstage talks](https://www.youtube.com/playlist?list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU) and check out highlights from our week in the Big A below. + +{/* truncate */} + +## Scenes from BackstageCon (and beyond) + +![Scenes from BackstageCon and KubeCon + CloudNativeCon NA 2025](assets/2025-11-25/kcon25-atl-slideshow.gif) +_📸 [CNCF](https://www.flickr.com/photos/143247548@N03/albums/72177720330018728/)_ + +Brrr…it was a cold start with “Hot 'Lanta” bringing a serious autumn chill on Monday morning. But all the spicy DevEx insights would soon heat things up in the halls of the Georgia World Conference Center. Throughout the day, we heard discussions around how organizations adopt Backstage and scale it for productivity, as well as practical lessons on governance, deployment models, change management, and standardizing best practices around Backstage. + +And despite an unfortunate amount of flight disruptions waylaying some of the speakers, the organizers were able to work around all the last-minute issues for a day full of presentations, demos, lightning talks, and keynotes. Check out our community YouTube channel to watch recordings of [all the Backstage talks](https://www.youtube.com/playlist?list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU) from Atlanta. Not sure where to start? + +### A few personal picks + +Here are six BackstageCon talks that we attended and thought were worth sharing. They feature Backstage adopters across a range of industries — from finance and healthcare, to networking, automotive experiences, and beyond — all sharing use cases that relate to almost any engineering org: + +- 🏦 [Supercharging Backstage Scaffolder for Workflows](https://www.youtube.com/watch?v=0m94ERF0k4Q&list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU): Jonathan Chan and Francis Hackenberger, engineers from the Royal Bank of Canada's cloud team, talk about how they scale up the power of Backstage Software Templates at one of Canada's largest banks. + +- 🩺 [From Friction to Flow: How Backstage Transformed Our Security Experience](https://www.youtube.com/watch?v=ZCM9i1zD364&list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU): In this lightning talk, Christopher Diaz, principal engineer at RVO Health, squeezes three years' worth of Backstage learnings into a 10-minute lightning talk — including how Backstage has unlocked an inner source culture within their org, with teams building custom plugins to extend their instance (“That's what makes it so much fun to build on Backstage: everything you need to solve a problem is literally just a pull request away” 🙌). + +- 📶 [How Ericsson Scaled Developer Experience with Backstage](https://www.youtube.com/watch?v=1mDEkiN4NTs&list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU): Ericsson principal engineer Damien O'Toole and engineering manager Kieran Egan share what they learned on the way to scaling internal Backstage adoption to over 1,260 users and over 4,073 weekly visits. + +- 🚗 [Cruising the AI Highway: Standardized MCP Deployment and Governance with Backstage](https://www.youtube.com/watch?v=pFSSpDHB2rU&list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU): Engineers from Toyota Connected — “recovering system administrator” Rob Heckel and platform engineer Tyler Rothenberg — share how they deploy MCP servers across their org in an easy, secure, and sustainable way, covering everything from the basics of MCP to deploying MCP servers using cloud native tools and integrating them with Backstage. + +- 📊 [Lego Bricks for Developers: Turning Insights into Golden Paths at Neo4j](https://www.youtube.com/watch?v=IdLXeK8motg&list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU): Developer advocate Jeremy Adams (stepping in for a waylayed Chris Heisz) joins engineer Luke Beamish to talk about how they use Golden Paths at Neo4j, a leader in graph technology whose tech is used for cancer research, GenAI, and NASA's mission to Mars. + +- 🔔 [Driving Adoption by Generating High Value Notifications in Backstage](https://www.youtube.com/watch?v=N4gCLbhxTHA&list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU): How do notifications work in Backstage and what makes them such a powerful way to drive Backstage adoption within your org? Watch this talk by Spotify engineering manager Avantika Iyer and customer success engineer/Backstage community maintainer André Wanlin. [Hey, that's me! Seriously, tho, notifications are the jam. — AW] + +### Backstage at KubeCon + +The Backstage-related talks continued throughout the week during KubeCon + CloudNativeCon, including: + +- 🚀 [The Journey of Deploying Backstage in a Large Organization](https://www.youtube.com/watch?v=l7OQdrUkoVI&list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU), as told by engineers Mathieu Girard and Teddy Poingt from Beneva, Canada's largest mutual insurance company. + +- 📚 [Build Your Internal Developer Platform with the Experts](https://www.youtube.com/watch?v=eqxKB6Gq7oc&list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU) in this hands-on workshop led by CNCF ambassadors and project maintainers. + +- 🎵 [Harmonizing Strategy and Engineering: Lessons Learnt in Building a Platform Plugin for Diverse Users](https://www.youtube.com/watch?v=2t_Pdiu3B1E&list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU) from Spotify senior product manager Sri Chandrasekaran and senior engineer Kate Klymkovska. [Hey, that's me! What André said, except product vision instead of notifications. — KK] + +### Watch 'em all! + +📺 There's much more to explore and dive into. Head over to the Backstage Community YouTube channel to [watch all the Backstage talks](https://www.youtube.com/playlist?list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU) from BackstageCon and KubeCon. + +## Our third Backstage ContribFest + +![Backstage ContribFest at KubeCon + CloudNativeCon NA 2025!](assets/2025-11-25/kcon25-atl-contribfest.jpg) + +We talked about Backstage a ton, but we also invited attendees to help us build it. In Atlanta, we co-hosted our third [Backstage Contribfest](https://backstage.io/blog/2025/10/15/backstage-contribfest-kubecon-guide) with participants of all experience and skill levels diving into the Backstage repos to tackle issues together — and with plenty of Backstage maintainers and contributors close at hand to help out. + +A few ContribFest-fueled pull requests have already popped up in the repos: + +- fix(ui): render html in gfm mode in MarkdownContent component ([#31770](https://github.com/backstage/backstage/pull/31770)) +- catalog-react: OwnerEntityPicker case insensitive ([#31756](https://github.com/backstage/backstage/pull/31756)) +- fix: making the github urls case insensitive ([#31754](https://github.com/backstage/backstage/pull/31754)) +- fix: success announcements should be transient ([#6041](https://github.com/backstage/community-plugins/pull/6041) — ✅ merged!) + +Many thanks to all the participants and to our fellow hosts — Kurt King from Procore and Aramis Sennyey from DoorDash!🙏 + +🛠️ If you haven't yet joined our worldwide community of contributors, [there's no time like the present](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md)! + +## Maintainers talk: Stable foundations, MCP innovations + +![Backstage Maintainers Track at KubeCon + CloudNativeCon NA 2025!](assets/2025-11-25/kcon25-atl-maintainers.gif) + +As always, the main event for the community is the Maintainers Track session. Backstage core maintainers Ben Lambert and Patrik Oldsberg closed out the week celebrating a year of milestones — including contributions big and small from the community — while looking ahead to what's next for the framework. + +In their talk, [Backstage Celebrations: Stable Foundations and MCP Innovations](https://www.youtube.com/watch?v=evmmr-uxNsc&list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU), they shared: + +- **Project Updates:** Adopters, plugins, and stars — and all the other ways the Backstage community continues to grow, including in the community-plugins repo. Plus, improvements to issue handling and more. + +- **Project Maturity:** With the new backend ([released as a stable 1.0 last year](https://backstage.io/docs/releases/v1.31.0/)) and new frontend ([adoption-ready as of this year](https://backstage.io/docs/releases/v1.42.0)) now both in place, the Backstage framework has modern foundations built for the future. With more emphasis on declarative integration (and less TypeScript code), Backstage is more accessible and easier to maintain — and plugins are simpler than ever to build and integrate. Make sure to watch the demo to see how quickly you can install plugins into a fresh Backstage instance. + +- **New Stuff ✨:** MCP, MCP, MCP, and MCP: What happens when you combine your AI tools with your engineering source of truth? Learn about Backstage + MCP, the latest improvements to the Backstage Actions Registry, authorization via new dynamic client registration and CLI authentication, and a live demo. Plus, all the latest on the framework's new design system, [Backstage UI](https://ui.backstage.io) (BUI). + +- **Roadmap:** Looking ahead to what's next for the New Frontend System, the Backstage UI rollout, the plugin ecosystem, MCP, CLI, and the release process. + +📺 Watch the entire [Backstage maintainers talk](https://www.youtube.com/watch?v=evmmr-uxNsc&list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU) to dig into the past, present, and future of the framework, including live demos showcasing the newest features and capabilities. + +## See you in 🌷Amsterdam! + +![Cloud native puppies improving developer experience at KubeCon + CloudNativeCon NA 2025!](assets/2025-11-25/kcon25-atl-puppies.gif) +_Cloud native puppies improving developer experience in Atlanta. 📸 [CNCF](https://www.flickr.com/photos/143247548@N03/albums/72177720330018728/)_ + +And that's a wrap for Atlanta! We say goodbye to the home of 🎤 trap music and get ready to say hello to a [stroopwafel](https://en.wikipedia.org/wiki/Stroopwafel) fika 🧇. + +See you next year at [BackstageCon](https://events.linuxfoundation.org/kubecon-cloudnativecon-europe/co-located-events/backstagecon/) and [KubeCon + CloudNativeCon Europe 2026](https://events.linuxfoundation.org/kubecon-cloudnativecon-europe/) in Amsterdam! diff --git a/microsite/blog/2025-12-30-backstage-wrapped-2025.mdx b/microsite/blog/2025-12-30-backstage-wrapped-2025.mdx new file mode 100644 index 0000000000..d4b66c6a70 --- /dev/null +++ b/microsite/blog/2025-12-30-backstage-wrapped-2025.mdx @@ -0,0 +1,115 @@ +--- +title: 'Backstage Wrapped 2025: Celebrating a mature framework with modern foundations' +author: Vincenzo Scamporlino, Spotify +authorURL: https://github.com/vinzscam +authorImageURL: https://avatars.githubusercontent.com/u/8433119?v=8 +--- + +![Let's blast into the New Year with a quick look back at all the big hits — backend, frontend, AI, UI, and more! See how today's Backstage is ready to build the future of developer experience.](assets/2025-12-30/wrapped2025.png) + +This year was another big one for Backstage and our worldwide community of adopters, contributors, and partners. Backstage turned five, the New Frontend System became adoption-ready, and your favorite platform for developer experience gained AI superpowers with the introduction of the Actions Registry and MCP server support. Let's blast into the New Year with a quick look back, along with a peek around the corner at what's coming next. + +{/* truncate */} + +--- + +## Busy beavers here, there, and everywhere + +As in years past, we've visualized how our vibrant global community lights up the Backstage open source repo with enthusiasm, creativity, and commits. This year, we're visualizing contributions to the Community Plugins repo, too. Can you spot your name in the videos below? + + + +_All 7.7k commits to `backstage/backstage` in 2025_ + + + +_All 3.3k commits to `backstage/community-plugins` in 2025_ + +Couldn't spot your name in the videos? Here's one more chance — every contributor who helped shape Backstage in 2025, all in one picture. + +![Backstage 2025 contributors](assets/2025-12-30/contributors-wrapped-2025.png) + +## Mature for a five-year-old + +![Backstage community stats: 3.4k+ adopters, 1.8k contributors, 15k Discord members, 7k project forks, 68k total contributions, 250+ open source plugins, 31k stars](assets/2025-12-30/wrapped2025-stats.png) +_The growing Backstage ecosystem, by the numbers_ + +Earlier this year, the Backstage project [celebrated its 5th birthday][bday] on the main stage at [KubeCon + CloudNativeCon in London][kcon-eu]. + +While the framework still isn't old enough to shave, it's matured into the industry standard for internal developer portals (IDPs). According to [this DX survey][dx] released in March: + +> Backstage is dominating the IDP market, holding an impressive 89% market share compared to SaaS competitors and achieving a remarkable 67% overall market penetration. These percentages are even higher for enterprise companies. + +As you can see above in the [latest project stats][video], the Backstage ecosystem continues to expand, growing in adopters and contributors, as well as plugins and third-party service providers. + +Want to dive into more Backstage stats? Check out the project's health scores and more on the relaunched [LFX Insights][lfx] site. + +[bday]: https://www.youtube.com/watch?v=JqG1wey7-Ao&t=563s&list=PL8iP9yIjU0Q3K8LV_a9CcFmhvvNTAUzL1 +[kcon-eu]: https://backstage.io/blog/2025/04/29/backstagecon-kubecon-25-london#big-birthday-energy-on-the-big-stage +[dx]: https://newsletter.getdx.com/p/backstage-and-the-developer-portal-market +[video]: https://www.youtube.com/watch?v=Wih0a1v1Et0&list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU +[lfx]: https://insights.linuxfoundation.org/project/backstage + +## Building the future on modern foundations + +Just because Backstage has cemented its place as the standard for IDPs doesn't mean it's sitting still. From backend to frontend, from AI to UI, the technical foundations of the platform have been modernized, updated, and revamped — this is why today's Backstage is ready to build the future of developer experience. + +Top 2025 highlights: + +- Goodbye to the old backend system +- New Frontend System is adoption-ready +- Hello to the Actions Registry + MCP server support +- Introducing a new design system with Backstage UI + +With the [New Backend System][backend] released as a stable 1.0 last year and the [New Frontend System][frontend] becoming [adoption-ready][1-42] this year, Backstage is easier than ever to use, maintain, and build on — making it both more accessible and more capable for adopters, old and new. + +This year also saw the debut of the [Actions Registry][actions] and MCP server support — so now Backstage can empower both your human developers and their AI helpers. + +And [Backstage UI][ui] is coming! The alpha release of the upcoming design system made huge strides in 2025, preparing the way for a new level of usability throughout the Backstage experience. + +Catch up on a whole year's worth of improvements — and see where Backstage is heading next — in the recordings below. + +[backend]: https://backstage.io/docs/backend-system/ +[frontend]: https://backstage.io/docs/frontend-system/ +[1-42]: https://backstage.io/docs/releases/v1.42.0 +[actions]: https://backstage.io/docs/backend-system/core-services/actions-registry +[ui]: https://ui.backstage.io/ + + + +_Watch the Backstage maintainers' talk at [KubeCon in London][lon], where they demoed the New Frontend System and introduced the Actions Registry._ + + +_Watch the maintainers' talk at [KubeCon in Atlanta][atl], where they shared +work towards an AI-Native Backstage, progress on Backstage UI, and more._ + +[lon]: https://backstage.io/blog/2025/04/29/backstagecon-kubecon-25-london +[atl]: https://backstage.io/blog/2025/11/25/backstagecon-kubecon-25-atlanta + +## See you in 2026! + +Happy New Year, Backstage Community! See you in the repos! diff --git a/microsite/blog/2026-02-25-get-a-jump-on-contribfest.mdx b/microsite/blog/2026-02-25-get-a-jump-on-contribfest.mdx new file mode 100644 index 0000000000..67941fa436 --- /dev/null +++ b/microsite/blog/2026-02-25-get-a-jump-on-contribfest.mdx @@ -0,0 +1,48 @@ +--- +title: 'Get a jump on ContribFest with the new web app' +author: Elaine de Mattos Silva Bezerra, DB Systel GmbH, Heikki Hellgren, OP Financial Group & André Wanlin, Spotify +--- + +![Get a jump on ContribFest with the new web app](assets/2026-02-25/backstage-contribfest-kubecon-web-app-header.png) + +Become a Contrib Champ and join us at ContribFest, where commits become legendary! + +We are once again hosting ContribFest at KubeCon + CloudNativeCon. This time around, it's taking place in Amsterdam on March 26, 2026, at 13:45 CET — make sure to [add it to your schedule](https://kccnceu2026.sched.com/event/2EF7v/contribfest-supercharge-your-open-source-impact-backstage-contribfest-live-andre-wanlin-emma-indal-spotify-heikki-hellgren-op-financial-group-elaine-bezerra-db-systel-gmbh?iframe=no). Learn more about what to expect below and get started now by exploring the new [ContribFest web app](https://contribfest.backstage.io/). + +{/* truncate */} + +## Introducing the ContribFest web app + +We're excited to announce the new ContribFest web app: [https://contribfest.backstage.io/](https://contribfest.backstage.io/). The app simplifies local setup and helps you quickly find good issues to work on from the curated list pre-selected by your ContribFest co-hosts. + +You'll see that the app is broken down into five sections: + +- [Welcome](https://contribfest.backstage.io/): This is where you'll find links to all the things, including the session's slide deck, assignment sheet, the Backstage and Community Plugins repositories, and their respective contribution guides. +- [Getting Started](https://contribfest.backstage.io/getting-started/): Whether you are new to Backstage or an old hat, use this handy checklist to help you get your local environment set up for contributing, including all the commands. (Make sure you check all the boxes, you never know what might happen! 😉) +- [Curated Issues](https://contribfest.backstage.io/issues/): This is what you come to the session for: finding an issue that speaks to you and contributing towards it. This section has a list of issues that we've curated — and filters, so you can slice and dice the list to find the perfect issue to work on. +- [Contrib Champs](https://contribfest.backstage.io/contrib-champs/): We've hosted three other ContribFests in the past — this is where you'll find merged PRs from those sessions, a place to celebrate contributions. Make sure to tag your PRs with “ContribFest”, and maybe your name will show up here one day, too! 🏆 +- [Hall of Hosts](https://contribfest.backstage.io/hall-of-hosts/): ContribFest would not take place without the various community members who have stepped up to help co-host the sessions. This is where you'll see an honor roll of past co-hosts. 🙏 + +## About those Contrib Champs + +The goals of the Backstage ContribFest sessions are many — foster community, work with experts, etc. — but it's pretty obvious that contributions are the most important. It's in the name after all. Here are a few past contributions that we wanted to share to give you an idea of what that looks like: + +- [#27694](https://github.com/backstage/backstage/pull/27694) by [hyb175](https://github.com/hyb175) — Add Pagination to Tech Docs Table: for those with lots of entities with TechDocs, this is a massive performance improvement. +- [#29470](https://github.com/backstage/backstage/pull/29470) by [ioboi](https://github.com/ioboi) — Openshift Auth provider: this allows those using OpenShift to use it to sign into their Backstage instance. +- [#31770](https://github.com/backstage/backstage/pull/31770) by [theZMC](https://github.com/theZMC) — Render HTML in GitHub-flavored Markdown: with this change in place, HTML will now render correctly in the MarkdownContent component when you are using the GitHub-flavored Markdown mode. + +Check out the [Contrib Champs page](https://contribfest.backstage.io/contrib-champs/) to see the full list! + +## Using Dev Containers + +Along with the new ContribFest web app, we are also looking to use Dev Containers this time around to help streamline the session for those who'd like to use that option to get started. On the [Getting Started page](https://contribfest.backstage.io/getting-started/), pick the Dev Containers radio button and then follow the checklist. To give you a quick preview, you'll need to have the following installed: + +- Git, you'll need this to be able to pull down the code +- Docker Desktop (or Docker Engine on Linux) +- VS Code with the Dev Containers extension or IntelliJ IDEA Ultimate + +Check out our [Dev Containers tutorial](https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/devcontainer.md) for a deeper dive into the subject. + +## Amsterdam, here we come! + +On behalf of the Backstage ContribFest co-host team, thank you for following along. We're looking forward to meeting you in Amsterdam and working together on your contributions. Please be sure to introduce yourself! diff --git a/microsite/blog/assets/2024-06-27/backstage-engineer-journey.svg b/microsite/blog/assets/2024-06-27/backstage-engineer-journey.svg index 1e302206a5..bd5a73c14d 100644 --- a/microsite/blog/assets/2024-06-27/backstage-engineer-journey.svg +++ b/microsite/blog/assets/2024-06-27/backstage-engineer-journey.svg @@ -1,362 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/microsite/blog/assets/2025-10-15/backstage-contribfest-kubecon-guide-header.png b/microsite/blog/assets/2025-10-15/backstage-contribfest-kubecon-guide-header.png index 23ed0309c1..cf09601b23 100644 Binary files a/microsite/blog/assets/2025-10-15/backstage-contribfest-kubecon-guide-header.png and b/microsite/blog/assets/2025-10-15/backstage-contribfest-kubecon-guide-header.png differ diff --git a/microsite/blog/assets/2025-11-25/backstagecon-kubecon-na-2025-header.png b/microsite/blog/assets/2025-11-25/backstagecon-kubecon-na-2025-header.png new file mode 100644 index 0000000000..ff2010776f Binary files /dev/null and b/microsite/blog/assets/2025-11-25/backstagecon-kubecon-na-2025-header.png differ diff --git a/microsite/blog/assets/2025-11-25/kcon25-atl-contribfest.jpg b/microsite/blog/assets/2025-11-25/kcon25-atl-contribfest.jpg new file mode 100644 index 0000000000..ae3414f104 Binary files /dev/null and b/microsite/blog/assets/2025-11-25/kcon25-atl-contribfest.jpg differ diff --git a/microsite/blog/assets/2025-11-25/kcon25-atl-maintainers.gif b/microsite/blog/assets/2025-11-25/kcon25-atl-maintainers.gif new file mode 100644 index 0000000000..ad71bc1123 Binary files /dev/null and b/microsite/blog/assets/2025-11-25/kcon25-atl-maintainers.gif differ diff --git a/microsite/blog/assets/2025-11-25/kcon25-atl-puppies.gif b/microsite/blog/assets/2025-11-25/kcon25-atl-puppies.gif new file mode 100644 index 0000000000..462afd87cb Binary files /dev/null and b/microsite/blog/assets/2025-11-25/kcon25-atl-puppies.gif differ diff --git a/microsite/blog/assets/2025-11-25/kcon25-atl-slideshow.gif b/microsite/blog/assets/2025-11-25/kcon25-atl-slideshow.gif new file mode 100644 index 0000000000..a7e5767441 Binary files /dev/null and b/microsite/blog/assets/2025-11-25/kcon25-atl-slideshow.gif differ diff --git a/microsite/blog/assets/2025-12-30/contributors-wrapped-2025.png b/microsite/blog/assets/2025-12-30/contributors-wrapped-2025.png new file mode 100644 index 0000000000..7aa16f106e Binary files /dev/null and b/microsite/blog/assets/2025-12-30/contributors-wrapped-2025.png differ diff --git a/microsite/blog/assets/2025-12-30/wrapped2025-stats.png b/microsite/blog/assets/2025-12-30/wrapped2025-stats.png new file mode 100644 index 0000000000..59d31f142c Binary files /dev/null and b/microsite/blog/assets/2025-12-30/wrapped2025-stats.png differ diff --git a/microsite/blog/assets/2025-12-30/wrapped2025.png b/microsite/blog/assets/2025-12-30/wrapped2025.png new file mode 100644 index 0000000000..b99768dfda Binary files /dev/null and b/microsite/blog/assets/2025-12-30/wrapped2025.png differ diff --git a/microsite/blog/assets/2026-02-25/backstage-contribfest-kubecon-web-app-header.png b/microsite/blog/assets/2026-02-25/backstage-contribfest-kubecon-web-app-header.png new file mode 100644 index 0000000000..21e726aeb7 Binary files /dev/null and b/microsite/blog/assets/2026-02-25/backstage-contribfest-kubecon-web-app-header.png differ diff --git a/microsite/data/plugins/3scale.yaml b/microsite/data/plugins/3scale.yaml index 1b27e1ee84..87ab503b8a 100644 --- a/microsite/data/plugins/3scale.yaml +++ b/microsite/data/plugins/3scale.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/backstage/community-plugins/blob/main/workspac iconUrl: /img/3scale.svg npmPackageName: '@backstage-community/plugin-3scale-backend' addedDate: '2023-05-15' +status: active diff --git a/microsite/data/plugins/adr.yaml b/microsite/data/plugins/adr.yaml index 003ae22e7b..35b93fcbdc 100644 --- a/microsite/data/plugins/adr.yaml +++ b/microsite/data/plugins/adr.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/backstage/community-plugins/tree/main/workspac iconUrl: /img/adr-logo.png npmPackageName: '@backstage-community/plugin-adr' addedDate: '2022-04-13' +status: active diff --git a/microsite/data/plugins/ai-assistant.yaml b/microsite/data/plugins/ai-assistant.yaml index 6ac4b6cd9e..55b4b56e68 100644 --- a/microsite/data/plugins/ai-assistant.yaml +++ b/microsite/data/plugins/ai-assistant.yaml @@ -8,3 +8,4 @@ documentation: https://roadie.io/backstage/plugins/ai-assistant-rag-ai/ iconUrl: https://roadie.io/images/logos/roadie-racks-ai.png npmPackageName: '@roadiehq/rag-ai' addedDate: '2024-03-12' +status: active diff --git a/microsite/data/plugins/airbrake.yaml b/microsite/data/plugins/airbrake.yaml index 8facf13185..73ce4d9874 100644 --- a/microsite/data/plugins/airbrake.yaml +++ b/microsite/data/plugins/airbrake.yaml @@ -6,5 +6,6 @@ category: Monitoring description: Access Airbrake error monitoring and other integrations from within Backstage documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/airbrake/plugins/airbrake iconUrl: https://wp-assets.airbrake.io/wp-content/uploads/2020/10/05222904/Square-white-A-on-Orange.png -npmPackageName: '@backstage/plugin-airbrake' +npmPackageName: '@backstage-community/plugin-airbrake' addedDate: '2022-01-10' +status: active diff --git a/microsite/data/plugins/allure.yaml b/microsite/data/plugins/allure.yaml index cbcd0ec435..6302ffb3c7 100644 --- a/microsite/data/plugins/allure.yaml +++ b/microsite/data/plugins/allure.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/backstage/community-plugins/tree/main/workspac iconUrl: https://avatars.githubusercontent.com/u/5879127 npmPackageName: '@backstage-community/plugin-allure' addedDate: '2021-09-02' +status: active diff --git a/microsite/data/plugins/amplication.yaml b/microsite/data/plugins/amplication.yaml index a1b5f09c0e..15fb08e83f 100644 --- a/microsite/data/plugins/amplication.yaml +++ b/microsite/data/plugins/amplication.yaml @@ -8,3 +8,4 @@ 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' +status: active diff --git a/microsite/data/plugins/analytics-module-ga.yaml b/microsite/data/plugins/analytics-module-ga.yaml index 3dcb27bcd5..58806e127e 100644 --- a/microsite/data/plugins/analytics-module-ga.yaml +++ b/microsite/data/plugins/analytics-module-ga.yaml @@ -6,5 +6,6 @@ category: Monitoring description: Track usage of your Backstage instance using Google Analytics 4. documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/analytics/plugins/analytics-module-ga4#readme iconUrl: /img/ga-icon.png -npmPackageName: '@backstage/plugin-analytics-module-ga4' +npmPackageName: '@backstage-community/plugin-analytics-module-ga4' addedDate: '2021-10-07' +status: active diff --git a/microsite/data/plugins/analytics-module-generic.yaml b/microsite/data/plugins/analytics-module-generic.yaml index 260319aca6..b34f45425e 100644 --- a/microsite/data/plugins/analytics-module-generic.yaml +++ b/microsite/data/plugins/analytics-module-generic.yaml @@ -5,5 +5,7 @@ authorUrl: https://josie.lol category: Monitoring description: Track usage of your Backstage instance using generic POST endpoints. documentation: https://github.com/pfeifferj/backstage-plugin-analytics-generic/blob/main/README.md +iconUrl: '/img/logo-gradient-on-dark.svg' npmPackageName: '@pfeifferj/backstage-plugin-analytics-generic' addedDate: '2024-08-23' +status: active diff --git a/microsite/data/plugins/analytics-module-matomo.yaml b/microsite/data/plugins/analytics-module-matomo.yaml index 88ce08c868..b5dca4e662 100644 --- a/microsite/data/plugins/analytics-module-matomo.yaml +++ b/microsite/data/plugins/analytics-module-matomo.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/backstage/community-plugins/blob/main/workspac iconUrl: /img/matomo.png npmPackageName: '@backstage-community/plugin-analytics-module-matomo' addedDate: '2023-10-17' +status: active diff --git a/microsite/data/plugins/analytics-module-qm.yaml b/microsite/data/plugins/analytics-module-qm.yaml index df2ae3e935..00c07c7589 100644 --- a/microsite/data/plugins/analytics-module-qm.yaml +++ b/microsite/data/plugins/analytics-module-qm.yaml @@ -8,3 +8,5 @@ documentation: https://github.com/quantummetric/analytics-module-qm/blob/main/RE iconUrl: /img/qm-icon.png npmPackageName: '@quantum-metric/plugin-analytics-module-qm' addedDate: '2024-03-15' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/announcements.yaml b/microsite/data/plugins/announcements.yaml index f24965f10a..d7442ff740 100644 --- a/microsite/data/plugins/announcements.yaml +++ b/microsite/data/plugins/announcements.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/backstage/community-plugins/tree/main/workspac iconUrl: /img/plugin-announcements-logo.png npmPackageName: '@backstage-community/plugin-announcements' addedDate: '2022-11-09' +status: active diff --git a/microsite/data/plugins/api-docs.yaml b/microsite/data/plugins/api-docs.yaml index 645e32c54b..c60703a20b 100644 --- a/microsite/data/plugins/api-docs.yaml +++ b/microsite/data/plugins/api-docs.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/backstage/backstage/blob/master/plugins/api-do iconUrl: https://raw.githubusercontent.com/vscode-icons/vscode-icons/master/icons/file_type_swagger.svg npmPackageName: '@backstage/plugin-api-docs' addedDate: '2020-11-19' +status: active diff --git a/microsite/data/plugins/api-linter.yaml b/microsite/data/plugins/api-linter.yaml index a68da228eb..25d80c7928 100644 --- a/microsite/data/plugins/api-linter.yaml +++ b/microsite/data/plugins/api-linter.yaml @@ -7,3 +7,5 @@ description: API Linter is a quality assurance tool that checks the compliance o documentation: https://github.com/zalando/backstage-plugin-api-linter iconUrl: https://raw.githubusercontent.com/zalando/zally/main/logo.png npmPackageName: backstage-plugin-api-linter +addedDate: '2022-07-22' +status: active diff --git a/microsite/data/plugins/api-sdk-generator.yaml b/microsite/data/plugins/api-sdk-generator.yaml index 5553089807..19bc165fa8 100644 --- a/microsite/data/plugins/api-sdk-generator.yaml +++ b/microsite/data/plugins/api-sdk-generator.yaml @@ -6,5 +6,7 @@ category: Development description: Generate SDKs for your REST API to accelerate integration documentation: https://github.com/konfig-dev/backstage-plugin-konfig/tree/main/plugins/backstage-plugin-konfig iconUrl: https://raw.githubusercontent.com/konfig-dev/backstage-plugin-konfig/main/plugins/backstage-plugin-konfig/docs/logo.png -npmPackageName: 'backstage-plugin-konfig' +npmPackageName: backstage-plugin-konfig addedDate: '2023-08-07' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/api-spectral-linter.yaml b/microsite/data/plugins/api-spectral-linter.yaml index 5f1682c71b..ae94220793 100644 --- a/microsite/data/plugins/api-spectral-linter.yaml +++ b/microsite/data/plugins/api-spectral-linter.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/dweber019/backstage-plugins/tree/main/plugins/ iconUrl: https://raw.githubusercontent.com/dweber019/backstage-plugins/main/plugins/api-docs-spectral-linter/docs/pluginIcon.png npmPackageName: '@dweber019/backstage-plugin-api-docs-spectral-linter' addedDate: '2023-03-27' +status: active diff --git a/microsite/data/plugins/api-wsdl.yaml b/microsite/data/plugins/api-wsdl.yaml index 219227c509..ab5c99bc84 100644 --- a/microsite/data/plugins/api-wsdl.yaml +++ b/microsite/data/plugins/api-wsdl.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/dweber019/backstage-plugins/tree/main/plugins/ iconUrl: https://raw.githubusercontent.com/dweber019/backstage-plugins/main/plugins/api-docs-module-wsdl/docs/logo.png npmPackageName: '@dweber019/backstage-plugin-api-docs-module-wsdl' addedDate: '2023-12-22' +status: active diff --git a/microsite/data/plugins/apicurio-registry.yaml b/microsite/data/plugins/apicurio-registry.yaml index 3750fbaa69..ae89d7fd56 100644 --- a/microsite/data/plugins/apicurio-registry.yaml +++ b/microsite/data/plugins/apicurio-registry.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/simonnepomuk/backstage-plugin-apicurio-registr iconUrl: https://avatars.githubusercontent.com/u/28107283?s=200&v=4 npmPackageName: '@simonnepomuk/backstage-plugin-apicurio-registry' addedDate: '2025-08-31' +status: active diff --git a/microsite/data/plugins/apiiro.yaml b/microsite/data/plugins/apiiro.yaml new file mode 100644 index 0000000000..dffaaceabf --- /dev/null +++ b/microsite/data/plugins/apiiro.yaml @@ -0,0 +1,11 @@ +--- +title: Apiiro +author: Apiiro +authorUrl: https://apiiro.com/ +category: Security +description: View your Apiiro Repository Risk Profile in Backstage +documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/apiiro/plugins/apiiro/README.md +iconUrl: https://avatars.githubusercontent.com/u/48519090 +npmPackageName: '@backstage-community/plugin-apiiro' +addedDate: '2026-01-27' +status: active diff --git a/microsite/data/plugins/apollo-explorer.yaml b/microsite/data/plugins/apollo-explorer.yaml index 2b0f834f92..60eb09ce2a 100644 --- a/microsite/data/plugins/apollo-explorer.yaml +++ b/microsite/data/plugins/apollo-explorer.yaml @@ -6,5 +6,6 @@ category: Debugging description: Integrates Apollo Explorer graphs as a tool to browse GraphQL API endpoints inside Backstage. documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/apollo-explorer/plugins/apollo-explorer/README.md iconUrl: /img/apollo-explorer.png -npmPackageName: '@backstage/plugin-apollo-explorer' +npmPackageName: '@backstage-community/plugin-apollo-explorer' addedDate: '2022-07-20' +status: active diff --git a/microsite/data/plugins/argo-cd.yaml b/microsite/data/plugins/argo-cd.yaml index daac6dabd1..bd7802403c 100644 --- a/microsite/data/plugins/argo-cd.yaml +++ b/microsite/data/plugins/argo-cd.yaml @@ -7,7 +7,5 @@ description: View Argo CD status for your projects in Backstage. documentation: https://roadie.io/backstage/plugins/argo-cd/?utm_source=backstage.io&utm_medium=marketplace&utm_campaign=argo-cd iconUrl: https://roadie.io/images/logos/argo.png npmPackageName: '@roadiehq/backstage-plugin-argo-cd' -tags: - - cd - - ci addedDate: '2021-04-20' +status: active diff --git a/microsite/data/plugins/artifactory.yaml b/microsite/data/plugins/artifactory.yaml index 119e09eb8e..5758590075 100644 --- a/microsite/data/plugins/artifactory.yaml +++ b/microsite/data/plugins/artifactory.yaml @@ -10,3 +10,5 @@ documentation: https://github.com/StageCentral/backstage-artifactory-plugin/tree iconUrl: https://raw.githubusercontent.com/StageCentral/backstage-artifactory-plugin/main/logo/logo.png npmPackageName: '@stagecentral/plugin-artifactory' addedDate: '2023-08-23' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/aws-amazon-ecr.yaml b/microsite/data/plugins/aws-amazon-ecr.yaml new file mode 100644 index 0000000000..1749b84f04 --- /dev/null +++ b/microsite/data/plugins/aws-amazon-ecr.yaml @@ -0,0 +1,11 @@ +--- +title: Amazon Elastic Container Registry +author: Amazon Web Services +authorUrl: https://aws.amazon.com/ +category: Infrastructure +description: View Amazon ECR repositories and container image scan findings for your components in Backstage. +documentation: https://github.com/awslabs/backstage-plugins-for-aws/tree/main/plugins/ecr#readme +iconUrl: https://raw.githubusercontent.com/awslabs/backstage-plugins-for-aws/main/docs/images/logos/ecr.png +npmPackageName: '@aws/amazon-ecr-plugin-for-backstage' +addedDate: '2026-03-26' +status: active diff --git a/microsite/data/plugins/aws-amazon-ecs.yaml b/microsite/data/plugins/aws-amazon-ecs.yaml index a94779babd..55a4791f3c 100644 --- a/microsite/data/plugins/aws-amazon-ecs.yaml +++ b/microsite/data/plugins/aws-amazon-ecs.yaml @@ -6,5 +6,6 @@ category: Infrastructure description: View resources related to Amazon Elastic Container Service for your components in Backstage. documentation: https://github.com/awslabs/backstage-plugins-for-aws/tree/main/plugins/ecs#readme iconUrl: https://raw.githubusercontent.com/awslabs/backstage-plugins-for-aws/main/docs/images/logos/amazon-ecs-logo.png -npmPackageName: 'https://www.npmjs.com/package/@aws/amazon-ecs-plugin-for-backstage' +npmPackageName: '@aws/amazon-ecs-plugin-for-backstage' addedDate: '2024-04-22' +status: active diff --git a/microsite/data/plugins/aws-app-development.yaml b/microsite/data/plugins/aws-app-development.yaml deleted file mode 100644 index 5df0598c5e..0000000000 --- a/microsite/data/plugins/aws-app-development.yaml +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: Harmonix on AWS -author: Amazon Web Services -authorUrl: https://aws.amazon.com/ -category: Infrastructure -description: Harmonix allows customers to build and manage AWS Apps & Environments within Backstage -documentation: https://harmonixonaws.io -iconUrl: https://github.com/awslabs/harmonix/blob/main/docs/images/harmonix-white.png?raw=true -npmPackageName: '@aws/plugin-aws-apps-for-backstage' -addedDate: '2023-05-10' diff --git a/microsite/data/plugins/aws-cloudformation.yaml b/microsite/data/plugins/aws-cloudformation.yaml index 6ee36e82b0..f82c4d64c4 100644 --- a/microsite/data/plugins/aws-cloudformation.yaml +++ b/microsite/data/plugins/aws-cloudformation.yaml @@ -6,5 +6,7 @@ category: Infrastructure description: Load Backstage entities from AWS CloudFormation stacks documentation: https://github.com/purple-technology/backstage-aws-cloudformation-plugin#readme iconUrl: https://raw.githubusercontent.com/purple-technology/backstage-aws-cloudformation-plugin/master/docs/cloudformation-logo.png -npmPackageName: 'backstage-aws-cloudformation-plugin' +npmPackageName: backstage-aws-cloudformation-plugin addedDate: '2021-08-30' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/aws-codebuild.yaml b/microsite/data/plugins/aws-codebuild.yaml index 2c12f92dce..687362299d 100644 --- a/microsite/data/plugins/aws-codebuild.yaml +++ b/microsite/data/plugins/aws-codebuild.yaml @@ -6,5 +6,6 @@ category: CI/CD description: View resources related to AWS CodeBuild for your components in Backstage. documentation: https://github.com/awslabs/backstage-plugins-for-aws/tree/main/plugins/codebuild#readme iconUrl: https://raw.githubusercontent.com/awslabs/backstage-plugins-for-aws/main/docs/images/logos/aws-codebuild-logo.png -npmPackageName: 'https://www.npmjs.com/package/@aws/aws-codebuild-plugin-for-backstage' +npmPackageName: '@aws/aws-codebuild-plugin-for-backstage' addedDate: '2024-04-22' +status: active diff --git a/microsite/data/plugins/aws-codepipeline.yaml b/microsite/data/plugins/aws-codepipeline.yaml index 16986b6718..8475b97082 100644 --- a/microsite/data/plugins/aws-codepipeline.yaml +++ b/microsite/data/plugins/aws-codepipeline.yaml @@ -6,5 +6,6 @@ category: CI/CD description: View resources related to AWS CodePipeline for your components in Backstage. documentation: https://github.com/awslabs/backstage-plugins-for-aws/tree/main/plugins/codepipeline#readme iconUrl: https://raw.githubusercontent.com/awslabs/backstage-plugins-for-aws/main/docs/images/logos/aws-codepipeline-logo.png -npmPackageName: 'https://www.npmjs.com/package/@aws/aws-codepipeline-plugin-for-backstage' +npmPackageName: '@aws/aws-codepipeline-plugin-for-backstage' addedDate: '2024-04-22' +status: active diff --git a/microsite/data/plugins/aws-config.yaml b/microsite/data/plugins/aws-config.yaml new file mode 100644 index 0000000000..a57a601c4b --- /dev/null +++ b/microsite/data/plugins/aws-config.yaml @@ -0,0 +1,11 @@ +--- +title: AWS Config +author: Amazon Web Services +authorUrl: https://aws.amazon.com/ +category: Infrastructure +description: Ingest AWS resources from AWS Config into the Backstage catalog using incremental ingestion. +documentation: https://github.com/awslabs/backstage-plugins-for-aws/tree/main/plugins/core/catalog-config#readme +iconUrl: https://raw.githubusercontent.com/awslabs/backstage-plugins-for-aws/main/docs/images/logos/config.png +npmPackageName: '@aws/aws-config-catalog-module-for-backstage' +addedDate: '2026-03-26' +status: active diff --git a/microsite/data/plugins/aws-genai.yaml b/microsite/data/plugins/aws-genai.yaml new file mode 100644 index 0000000000..f881ccc769 --- /dev/null +++ b/microsite/data/plugins/aws-genai.yaml @@ -0,0 +1,11 @@ +--- +title: Generative AI +author: Amazon Web Services +authorUrl: https://aws.amazon.com/ +category: Machine Learning +description: Build generative AI assistants in Backstage that leverage the plugin ecosystem with tool use and LLM integration. +documentation: https://github.com/awslabs/backstage-plugins-for-aws/tree/main/plugins/genai#readme +iconUrl: https://raw.githubusercontent.com/awslabs/backstage-plugins-for-aws/main/docs/images/logos/bedrock.png +npmPackageName: '@aws/genai-plugin-for-backstage' +addedDate: '2026-03-26' +status: active diff --git a/microsite/data/plugins/aws-lambda.yaml b/microsite/data/plugins/aws-lambda.yaml index 80cebd06a8..33d4e795b3 100644 --- a/microsite/data/plugins/aws-lambda.yaml +++ b/microsite/data/plugins/aws-lambda.yaml @@ -8,3 +8,4 @@ documentation: https://roadie.io/backstage/plugins/aws-lambda/?utm_source=backst iconUrl: https://roadie.io/images/logos/lambda.png npmPackageName: '@roadiehq/backstage-plugin-aws-lambda' addedDate: '2021-04-20' +status: active diff --git a/microsite/data/plugins/aws-securityhub.yaml b/microsite/data/plugins/aws-securityhub.yaml new file mode 100644 index 0000000000..b2cec6e6ba --- /dev/null +++ b/microsite/data/plugins/aws-securityhub.yaml @@ -0,0 +1,11 @@ +--- +title: AWS Security Hub +author: Amazon Web Services +authorUrl: https://aws.amazon.com/ +category: Security +description: View and manage AWS Security Hub findings for your resources with severity filtering and AI remediation. +documentation: https://github.com/awslabs/backstage-plugins-for-aws/tree/main/plugins/securityhub#readme +iconUrl: https://raw.githubusercontent.com/awslabs/backstage-plugins-for-aws/main/docs/images/logos/securityhub.png +npmPackageName: '@aws/aws-securityhub-plugin-for-backstage' +addedDate: '2026-03-26' +status: active diff --git a/microsite/data/plugins/azure-pipelines.yaml b/microsite/data/plugins/azure-pipelines.yaml index e0e3534fdb..1e39ce2132 100644 --- a/microsite/data/plugins/azure-pipelines.yaml +++ b/microsite/data/plugins/azure-pipelines.yaml @@ -6,5 +6,6 @@ category: CI/CD description: Easily view your Azure Pipelines within the Software Catalog documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/azure-devops/plugins/azure-devops/README.md iconUrl: /img/azure-pipelines.svg -npmPackageName: '@backstage/plugin-azure-devops' +npmPackageName: '@backstage-community/plugin-azure-devops' addedDate: '2021-12-22' +status: active diff --git a/microsite/data/plugins/azure-resources.yaml b/microsite/data/plugins/azure-resources.yaml index a6cb389b0a..3a76117066 100644 --- a/microsite/data/plugins/azure-resources.yaml +++ b/microsite/data/plugins/azure-resources.yaml @@ -5,5 +5,8 @@ authorUrl: https://vipps.no category: Infrastructure description: A plugin showing Azure resource groups and security recommendations in relation to an entity in the catalog. documentation: https://github.com/vippsas/backstage-azure-resource-frontend +iconUrl: /img/logo-gradient-on-dark.svg npmPackageName: '@vippsno/plugin-azure-resources' addedDate: '2022-09-05' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/azure-sites.yaml b/microsite/data/plugins/azure-sites.yaml index 03b61b9614..d0103128d8 100644 --- a/microsite/data/plugins/azure-sites.yaml +++ b/microsite/data/plugins/azure-sites.yaml @@ -5,5 +5,7 @@ authorUrl: https://friss.com category: Infrastructure description: Azure Sites (Apps & Functions) support for a given entity. View the current status of the site, quickly jump to site's Overview page, or Log Stream page. documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/azure-sites/plugins/azure-sites +iconUrl: '/img/logo-gradient-on-dark.svg' npmPackageName: '@backstage-community/plugin-azure-sites' addedDate: '2022-10-18' +status: active diff --git a/microsite/data/plugins/azure-spring-apps.yml b/microsite/data/plugins/azure-spring-apps.yaml similarity index 82% rename from microsite/data/plugins/azure-spring-apps.yml rename to microsite/data/plugins/azure-spring-apps.yaml index e0b77a42c8..c010e71206 100644 --- a/microsite/data/plugins/azure-spring-apps.yml +++ b/microsite/data/plugins/azure-spring-apps.yaml @@ -1,5 +1,5 @@ --- -title: 'Azure Spring Apps' +title: Azure Spring Apps author: Enfuse authorUrl: https://enfuse.io/ category: Discovery @@ -8,3 +8,5 @@ documentation: https://github.com/enfuse/asae-backstage-plugin/blob/main/README. iconUrl: /img/enfuse.png npmPackageName: '@enfuse/plugin-azure-spring-apps' addedDate: '2022-11-21' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/azure-storage.yaml b/microsite/data/plugins/azure-storage.yaml index 6e723c0eeb..f7964ad0c9 100644 --- a/microsite/data/plugins/azure-storage.yaml +++ b/microsite/data/plugins/azure-storage.yaml @@ -7,8 +7,5 @@ description: Explore Azure Storage Blobs in Backstage. documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/azure-storage-explorer/plugins/azure-storage iconUrl: /img/azure-storage-folder.png npmPackageName: '@backstage-community/plugin-azure-storage-explorer' -tags: - - Azure - - Azure Storage - - Infrastructure addedDate: '2023-12-01' +status: active diff --git a/microsite/data/plugins/backchat.yaml b/microsite/data/plugins/backchat.yaml index dd09938307..01b37369fe 100644 --- a/microsite/data/plugins/backchat.yaml +++ b/microsite/data/plugins/backchat.yaml @@ -8,3 +8,5 @@ documentation: https://github.com/benwilcock/backstage-plugin-backchat iconUrl: /img/backchat-logo.png npmPackageName: '@benbravo73/backstage-plugin-backchat' addedDate: '2024-01-12' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/backstage-kubernetes.yaml b/microsite/data/plugins/backstage-kubernetes.yaml index f9514880b0..62ba4e0368 100644 --- a/microsite/data/plugins/backstage-kubernetes.yaml +++ b/microsite/data/plugins/backstage-kubernetes.yaml @@ -9,3 +9,4 @@ iconUrl: /img/backstage-k8s.svg npmPackageName: '@backstage/plugin-kubernetes' order: 4 addedDate: '2021-03-29' +status: active diff --git a/microsite/data/plugins/backstage-search-platform.yaml b/microsite/data/plugins/backstage-search-platform.yaml index fa069d8875..af11209211 100644 --- a/microsite/data/plugins/backstage-search-platform.yaml +++ b/microsite/data/plugins/backstage-search-platform.yaml @@ -9,3 +9,4 @@ iconUrl: /img/backstage-search-platform.svg npmPackageName: '@backstage/plugin-search' order: 5 addedDate: '2022-09-07' +status: active diff --git a/microsite/data/plugins/backstage-software-catalog.yaml b/microsite/data/plugins/backstage-software-catalog.yaml index 3d47acabc7..3433ac4e8e 100644 --- a/microsite/data/plugins/backstage-software-catalog.yaml +++ b/microsite/data/plugins/backstage-software-catalog.yaml @@ -9,3 +9,4 @@ iconUrl: /img/backstage-software-catalog.svg npmPackageName: '@backstage/plugin-catalog' order: 1 addedDate: '2021-06-17' +status: active diff --git a/microsite/data/plugins/backstage-software-templates.yaml b/microsite/data/plugins/backstage-software-templates.yaml index 916f754a2a..6e29deedd6 100644 --- a/microsite/data/plugins/backstage-software-templates.yaml +++ b/microsite/data/plugins/backstage-software-templates.yaml @@ -9,3 +9,4 @@ iconUrl: /img/backstage-software-templates.svg npmPackageName: '@backstage/plugin-scaffolder' order: 2 addedDate: '2021-03-29' +status: active diff --git a/microsite/data/plugins/backstage-techdocs.yaml b/microsite/data/plugins/backstage-techdocs.yaml index ea462a6c94..adbb5608c9 100644 --- a/microsite/data/plugins/backstage-techdocs.yaml +++ b/microsite/data/plugins/backstage-techdocs.yaml @@ -9,3 +9,4 @@ iconUrl: /img/backstage-techdocs.svg npmPackageName: '@backstage/plugin-techdocs' order: 3 addedDate: '2021-03-29' +status: active diff --git a/microsite/data/plugins/badges.yaml b/microsite/data/plugins/badges.yaml index 789aaf155d..80d3b633f7 100644 --- a/microsite/data/plugins/badges.yaml +++ b/microsite/data/plugins/badges.yaml @@ -6,5 +6,6 @@ category: Discovery description: The badges plugin offers a set of badges that can be used outside of Backstage, showing information related to data from the catalog. documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/badges/plugins/badges/README.md iconUrl: /img/badges.svg -npmPackageName: '@backstage/plugin-badges' +npmPackageName: '@backstage-community/plugin-badges' addedDate: '2021-09-29' +status: active diff --git a/microsite/data/plugins/balajisiva-backstage-plugin-template-builder.yaml b/microsite/data/plugins/balajisiva-backstage-plugin-template-builder.yaml new file mode 100644 index 0000000000..6b8227c629 --- /dev/null +++ b/microsite/data/plugins/balajisiva-backstage-plugin-template-builder.yaml @@ -0,0 +1,11 @@ +--- +title: Template Builder +author: Balaji Sivasubramanian +authorUrl: https://github.com/balajisiva +category: Tooling +description: Visual editor for creating and managing Backstage scaffolder templates with live YAML preview, flow visualization, and GitHub integration +documentation: https://github.com/balajisiva/backstage-template-builder#readme +iconUrl: https://raw.githubusercontent.com/balajisiva/backstage-template-builder/main/plugins/backstage-template-builder/icon.svg +npmPackageName: '@balajisiva/backstage-plugin-template-builder' +addedDate: '2026-02-16' +status: active diff --git a/microsite/data/plugins/bazaar.yaml b/microsite/data/plugins/bazaar.yaml index 1130822e77..ba64c622f4 100644 --- a/microsite/data/plugins/bazaar.yaml +++ b/microsite/data/plugins/bazaar.yaml @@ -5,5 +5,6 @@ category: Discovery description: A marketplace where engineers can propose projects suitable for inner sourcing documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/bazaar/plugins/bazaar/README.md iconUrl: /img/bazaar.svg -npmPackageName: '@backstage/plugin-bazaar' +npmPackageName: '@backstage-community/plugin-bazaar' addedDate: '2022-01-11' +status: active diff --git a/microsite/data/plugins/betterscan.yaml b/microsite/data/plugins/betterscan.yaml index 852ae0081c..edc3caeedd 100644 --- a/microsite/data/plugins/betterscan.yaml +++ b/microsite/data/plugins/betterscan.yaml @@ -8,3 +8,5 @@ documentation: https://www.npmjs.com/package/@marcinguy/backstage-plugin-betters iconUrl: https://uploads-ssl.webflow.com/6339e3b81867539b5fe2498d/633a1643dcb06d3029867161_g4.svg npmPackageName: '@marcinguy/backstage-plugin-betterscan' addedDate: '2022-12-08' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/bitrise.yaml b/microsite/data/plugins/bitrise.yaml index 5cbfe5aabf..57fc360580 100644 --- a/microsite/data/plugins/bitrise.yaml +++ b/microsite/data/plugins/bitrise.yaml @@ -6,5 +6,6 @@ category: CI/CD description: View Bitrise builds and download the build artifacts within Backstage. documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/bitrise/plugins/bitrise/README.md iconUrl: https://avatars.githubusercontent.com/u/7174390?s=400&v=4 -npmPackageName: '@backstage/plugin-bitrise' +npmPackageName: '@backstage-community/plugin-bitrise' addedDate: '2021-03-01' +status: active diff --git a/microsite/data/plugins/blackduck.yaml b/microsite/data/plugins/blackduck.yaml index 7a7858fff2..db81068d85 100644 --- a/microsite/data/plugins/blackduck.yaml +++ b/microsite/data/plugins/blackduck.yaml @@ -7,6 +7,5 @@ description: BlackDuck scanned vulnerabilities in Backstage. documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/blackduck/plugins/blackduck iconUrl: https://avatars.githubusercontent.com/u/431461?s=200&v=4 npmPackageName: '@backstage-community/plugin-blackduck' -tags: - - security addedDate: '2022-12-03' +status: active diff --git a/microsite/data/plugins/blockchain-radar.yaml b/microsite/data/plugins/blockchain-radar.yaml index 0aacd19ab6..2af951d3e6 100644 --- a/microsite/data/plugins/blockchain-radar.yaml +++ b/microsite/data/plugins/blockchain-radar.yaml @@ -7,9 +7,5 @@ description: Discover and manage contracts, multisigs and their access control p documentation: https://github.com/aurora-is-near/backstage-plugin-blockchainradar iconUrl: https://raw.githubusercontent.com/aurora-is-near/backstage-plugin-blockchainradar/main/docs/imgs/signs/sign-green.svg npmPackageName: '@aurora-is-near/backstage-plugin-blockchainradar-backend' -tags: - - security - - monitoring - - blockchain - - alerting addedDate: '2023-08-31' +status: active diff --git a/microsite/data/plugins/bugsnag.yaml b/microsite/data/plugins/bugsnag.yaml index dc593ce5e8..59ccb8f6dc 100644 --- a/microsite/data/plugins/bugsnag.yaml +++ b/microsite/data/plugins/bugsnag.yaml @@ -8,3 +8,4 @@ documentation: https://roadie.io/backstage/plugins/bugsnag/?utm_source=backstage iconUrl: https://roadie.io/images/logos/bugsnag.png npmPackageName: '@roadiehq/backstage-plugin-bugsnag' addedDate: '2021-09-24' +status: active diff --git a/microsite/data/plugins/buildkite.yaml b/microsite/data/plugins/buildkite.yaml index 48754f6ae6..726d886de4 100644 --- a/microsite/data/plugins/buildkite.yaml +++ b/microsite/data/plugins/buildkite.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/buildkite/backstage-plugin iconUrl: /img/buildkite.svg npmPackageName: '@buildkite/backstage-plugin-buildkite' addedDate: '2025-04-12' +status: active diff --git a/microsite/data/plugins/bulletin-board.yaml b/microsite/data/plugins/bulletin-board.yaml index 67791f6f84..1c56932e2c 100644 --- a/microsite/data/plugins/bulletin-board.yaml +++ b/microsite/data/plugins/bulletin-board.yaml @@ -6,5 +6,7 @@ category: Discovery description: Share interesting ideas, news and links with your teammates within Backstage. documentation: https://github.com/v-ngu/backstage-plugin-bulletin-board iconUrl: /img/bulletin-board.png -npmPackageName: 'backstage-plugin-bulletin-board' +npmPackageName: backstage-plugin-bulletin-board addedDate: '2023-04-01' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/c2l2c-dora-metrics.yaml b/microsite/data/plugins/c2l2c-dora-metrics.yaml new file mode 100644 index 0000000000..81455c7c3d --- /dev/null +++ b/microsite/data/plugins/c2l2c-dora-metrics.yaml @@ -0,0 +1,11 @@ +--- +title: DORA Metrics +author: C2L2C +authorUrl: https://github.com/C2L2C +category: Monitoring +description: Per-entity DORA metrics for catalog services. Computes Deployment Frequency, Lead Time, CFR & MTTR from GitHub PRs — multi-environment, trend history, no extra backend. +documentation: https://github.com/C2L2C/backstage-plugin-dora-metrics#readme +iconUrl: https://raw.githubusercontent.com/tailwindlabs/heroicons/master/src/24/outline/chart-bar.svg +npmPackageName: '@c2l2c/backstage-plugin-dora-metrics' +addedDate: '2026-03-18' +status: active diff --git a/microsite/data/plugins/catalog-backend-module-gcp.yaml b/microsite/data/plugins/catalog-backend-module-gcp.yaml index b5df858862..eda1729812 100644 --- a/microsite/data/plugins/catalog-backend-module-gcp.yaml +++ b/microsite/data/plugins/catalog-backend-module-gcp.yaml @@ -7,10 +7,5 @@ description: Import your Infrastructure from GCP into Backstage as Resource Enti documentation: https://github.com/backtostage/backstage-plugins/blob/main/plugins/catalog-backend-module-gcp/README.md iconUrl: https://avatars1.githubusercontent.com/u/2810941?s=280&v=4 npmPackageName: '@backtostage/plugin-catalog-backend-module-gcp' -tags: - - cloud - - project - - resources - - gcp - - cloudsql addedDate: '2024-08-01' +status: active diff --git a/microsite/data/plugins/catalog-backend-module-puppetdb.yaml b/microsite/data/plugins/catalog-backend-module-puppetdb.yaml index e7a15ecd4a..91a286ca29 100644 --- a/microsite/data/plugins/catalog-backend-module-puppetdb.yaml +++ b/microsite/data/plugins/catalog-backend-module-puppetdb.yaml @@ -7,7 +7,5 @@ description: Import nodes from PuppetDB into Backstage as Resource Entities documentation: https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-puppetdb/README.md iconUrl: /img/puppet.png npmPackageName: '@backstage/plugin-catalog-backend-module-puppetdb' -tags: - - puppet - - puppetdb addedDate: '2023-02-06' +status: active diff --git a/microsite/data/plugins/catalog-graph.yaml b/microsite/data/plugins/catalog-graph.yaml index 46ee7b2f17..b41bb1d920 100644 --- a/microsite/data/plugins/catalog-graph.yaml +++ b/microsite/data/plugins/catalog-graph.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/backstage/backstage/blob/master/plugins/catalo iconUrl: /img/catalog-graph.svg npmPackageName: '@backstage/plugin-catalog-graph' addedDate: '2021-09-15' +status: active diff --git a/microsite/data/plugins/catalog-webhook.yaml b/microsite/data/plugins/catalog-webhook.yaml index 5c72cdd605..1fc2a87979 100644 --- a/microsite/data/plugins/catalog-webhook.yaml +++ b/microsite/data/plugins/catalog-webhook.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/ForgedApps/backstage-catalog-webhook-plugin/bl iconUrl: /img/forgedapps.jpg npmPackageName: '@forgedapps/backstage-catalog-webhook-plugin' addedDate: '2024-10-28' +status: active diff --git a/microsite/data/plugins/chatgpt-playground.yaml b/microsite/data/plugins/chatgpt-playground.yaml index 79146adc2c..9c39c5f0d9 100644 --- a/microsite/data/plugins/chatgpt-playground.yaml +++ b/microsite/data/plugins/chatgpt-playground.yaml @@ -1,5 +1,5 @@ --- -title: 'ChatGPT Playground' +title: ChatGPT Playground author: Enfuse authorUrl: https://enfuse.io/ category: Development @@ -8,3 +8,5 @@ documentation: https://github.com/enfuse/backstage-chatgpt-plugin/blob/main/READ iconUrl: /img/enfuse.png npmPackageName: '@enfuse/chatgpt-plugin-frontend' addedDate: '2023-05-23' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/cicd-statistics.yaml b/microsite/data/plugins/cicd-statistics.yaml index ea745b1129..1e274e7805 100644 --- a/microsite/data/plugins/cicd-statistics.yaml +++ b/microsite/data/plugins/cicd-statistics.yaml @@ -7,7 +7,5 @@ description: Visualize CI/CD pipeline statistics such as build time or success a documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/cicd-statistics/plugins/cicd-statistics iconUrl: /img/cicd-statistics.svg npmPackageName: '@backstage-community/plugin-cicd-statistics' -tags: - - ci - - cd addedDate: '2022-01-13' +status: active diff --git a/microsite/data/plugins/circleci.yaml b/microsite/data/plugins/circleci.yaml index 5980f8cf45..ec11b44b2a 100644 --- a/microsite/data/plugins/circleci.yaml +++ b/microsite/data/plugins/circleci.yaml @@ -7,7 +7,5 @@ description: Automate your development process with CI hosted in the cloud or on documentation: https://github.com/CircleCI-Public/backstage-plugin/tree/main/plugins/circleci iconUrl: /img/circleci.png npmPackageName: '@circleci/backstage-plugin' -tags: - - ci - - cd addedDate: '2021-04-28' +status: active diff --git a/microsite/data/plugins/cloud-build.yaml b/microsite/data/plugins/cloud-build.yaml index b63b29a559..80d9899096 100644 --- a/microsite/data/plugins/cloud-build.yaml +++ b/microsite/data/plugins/cloud-build.yaml @@ -7,8 +7,5 @@ description: Build, test, and deploy on Google's serverless CI/CD platform. documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/cloudbuild/plugins/cloudbuild iconUrl: https://avatars2.githubusercontent.com/u/38220399?s=400&v=4 npmPackageName: '@backstage-community/plugin-cloudbuild' -tags: - - ci - - cd - - test addedDate: '2021-01-20' +status: active diff --git a/microsite/data/plugins/cloud-carbon-footprint.yaml b/microsite/data/plugins/cloud-carbon-footprint.yaml index 2a213644ad..596dbb08a2 100644 --- a/microsite/data/plugins/cloud-carbon-footprint.yaml +++ b/microsite/data/plugins/cloud-carbon-footprint.yaml @@ -7,10 +7,6 @@ description: View your cloud carbon footprint by estimating energy use (kilowatt documentation: https://github.com/cloud-carbon-footprint/ccf-backstage-plugin/blob/trunk/README.md iconUrl: https://www.cloudcarbonfootprint.org/img/logo.png npmPackageName: '@cloud-carbon-footprint/backstage-plugin-frontend' -tags: - - sustainability - - cloud - - climate - - carbon-emissions - - carbon-footprint addedDate: '2022-05-03' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/cloudify.yaml b/microsite/data/plugins/cloudify.yaml index 93b7a26423..3909a0f398 100644 --- a/microsite/data/plugins/cloudify.yaml +++ b/microsite/data/plugins/cloudify.yaml @@ -6,5 +6,7 @@ category: Orchestration description: Cloudify provides a remote execution and environment management backend for Kubernetes, Terraform, Ansible, etc. documentation: https://github.com/cloudify-cosmo/backstage-cloudify-plugin#readme iconUrl: https://avatars.githubusercontent.com/u/6260555?s=200&v=4 -npmPackageName: 'plugin-cloudify' +npmPackageName: plugin-cloudify addedDate: '2022-05-31' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/cloudsmith.yaml b/microsite/data/plugins/cloudsmith.yaml index 4a07318a67..b057c71b97 100644 --- a/microsite/data/plugins/cloudsmith.yaml +++ b/microsite/data/plugins/cloudsmith.yaml @@ -7,10 +7,5 @@ description: Show Cloudsmith Repository stats, Quota, Audit logs and Security sc documentation: https://github.com/RoadieHQ/roadie-backstage-plugins/tree/main/plugins/frontend/backstage-plugin-cloudsmith iconUrl: https://cloudsmith.com/img/cloudsmith-mini-dark.svg npmPackageName: '@roadiehq/backstage-plugin-cloudsmith' -tags: - - dashboards - - monitoring - - audit logs - - security scanning - - usage addedDate: '2022-11-18' +status: active diff --git a/microsite/data/plugins/codacy-repo-adder.yaml b/microsite/data/plugins/codacy-repo-adder.yaml index a479a45ecf..d0c2380cbf 100644 --- a/microsite/data/plugins/codacy-repo-adder.yaml +++ b/microsite/data/plugins/codacy-repo-adder.yaml @@ -8,3 +8,5 @@ documentation: https://github.com/codacy/backstage-plugin/blob/main/README.md iconUrl: /img/codacy-icon.svg npmPackageName: '@codacy/backstage-plugin' addedDate: '2024-04-19' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/coder.yaml b/microsite/data/plugins/coder.yaml index 71d32b5ce4..2345fc8bcb 100644 --- a/microsite/data/plugins/coder.yaml +++ b/microsite/data/plugins/coder.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/coder/backstage-plugins/blob/main/plugins/back iconUrl: /img/coder.png npmPackageName: '@coder/backstage-plugin-coder' addedDate: '2024-03-15' +status: active diff --git a/microsite/data/plugins/codescene.yaml b/microsite/data/plugins/codescene.yaml index b69124aecc..6fb47465a6 100644 --- a/microsite/data/plugins/codescene.yaml +++ b/microsite/data/plugins/codescene.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/backstage/community-plugins/blob/main/workspac iconUrl: /img/codescene_logo.svg npmPackageName: '@backstage-community/plugin-codescene' addedDate: '2022-04-12' +status: active diff --git a/microsite/data/plugins/conviso-platform.yaml b/microsite/data/plugins/conviso-platform.yaml new file mode 100644 index 0000000000..2380e39ff2 --- /dev/null +++ b/microsite/data/plugins/conviso-platform.yaml @@ -0,0 +1,11 @@ +--- +title: Conviso Platform +author: Conviso Application Security +authorUrl: https://convisoappsec.com +category: Security +description: Import your Backstage catalog entities as security assets in Conviso Platform. +documentation: https://github.com/convisoappsec/backstage-plugin-conviso/blob/main/README.md +iconUrl: https://raw.githubusercontent.com/convisoappsec/backstage-plugin-conviso/main/assets/convisoappsec_logo.png +npmPackageName: '@conviso/backstage-plugin-conviso' +addedDate: '2025-11-14' +status: active diff --git a/microsite/data/plugins/cortex.yaml b/microsite/data/plugins/cortex.yaml index 77b01ddceb..de3fe52f61 100644 --- a/microsite/data/plugins/cortex.yaml +++ b/microsite/data/plugins/cortex.yaml @@ -7,8 +7,5 @@ description: Grade the quality of your Backstage services using Scorecards. Auto documentation: https://github.com/cortexapps/backstage-plugin iconUrl: /img/cortex.png npmPackageName: '@cortexapps/backstage-plugin' -tags: - - web - - monitoring - - sre addedDate: '2021-06-03' +status: active diff --git a/microsite/data/plugins/cost-insights.yaml b/microsite/data/plugins/cost-insights.yaml index 625b3f1c2c..0b8339170b 100644 --- a/microsite/data/plugins/cost-insights.yaml +++ b/microsite/data/plugins/cost-insights.yaml @@ -7,6 +7,5 @@ description: Visualize, understand and optimize your team's cloud costs. documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/cost-insights/plugins/cost-insights iconUrl: /img/cost-insights.png npmPackageName: '@backstage-community/plugin-cost-insights' -tags: - - web addedDate: '2021-04-28' +status: active diff --git a/microsite/data/plugins/crossplane.yaml b/microsite/data/plugins/crossplane.yaml index 0fe66ae43c..28e3101cb3 100644 --- a/microsite/data/plugins/crossplane.yaml +++ b/microsite/data/plugins/crossplane.yaml @@ -7,3 +7,4 @@ documentation: https://github.com/TeraSky-OSS/backstage-plugins/tree/main/plugin iconUrl: https://github.com/cncf/artwork/blob/main/projects/crossplane/icon/color/crossplane-icon-color.png?raw=true npmPackageName: '@terasky/backstage-plugin-crossplane-resources-frontend' addedDate: '2024-12-30' +status: active diff --git a/microsite/data/plugins/cyclops-modules.yaml b/microsite/data/plugins/cyclops-modules.yaml index f2c083295e..d4e28e957f 100644 --- a/microsite/data/plugins/cyclops-modules.yaml +++ b/microsite/data/plugins/cyclops-modules.yaml @@ -7,8 +7,6 @@ description: Manage Cyclops Modules through Backstage catalog documentation: https://github.com/cyclops-ui/backstage-plugins#readme iconUrl: /img/cyclops.svg npmPackageName: '@cyclopsui/backstage-plugin-cyclops-modules' -tags: - - Kubernetes - - Productivity - - Delivery addedDate: '2024-01-02' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/daily-weather-update.yaml b/microsite/data/plugins/daily-weather-update.yaml index 4118a4d5d6..3009e4dff1 100644 --- a/microsite/data/plugins/daily-weather-update.yaml +++ b/microsite/data/plugins/daily-weather-update.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/Infosys/daily-weather-plugin/tree/main/plugins iconUrl: https://github.com/Infosys/daily-weather-plugin/blob/main/plugins/weather/src/docs/plugin-logo.png?raw=true npmPackageName: '@infosys_ltd/daily-weather-plugin' addedDate: '2025-04-16' +status: active diff --git a/microsite/data/plugins/datacontract.yaml b/microsite/data/plugins/datacontract.yaml index a267730496..faf36bc182 100644 --- a/microsite/data/plugins/datacontract.yaml +++ b/microsite/data/plugins/datacontract.yaml @@ -8,3 +8,4 @@ documentation: https://www.npmjs.com/package/@remunda/backstage-plugin-datacontr iconUrl: https://www.remunda.cz/logos/datacontract.png npmPackageName: '@remunda/backstage-plugin-datacontract' addedDate: '2025-08-19' +status: active diff --git a/microsite/data/plugins/datadog-entity-sync.yaml b/microsite/data/plugins/datadog-entity-sync.yaml index 2f0cd06cd5..23eeda66cf 100644 --- a/microsite/data/plugins/datadog-entity-sync.yaml +++ b/microsite/data/plugins/datadog-entity-sync.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/DataDog/datadog-backstage-plugins/tree/main/pl iconUrl: https://imgix.datadoghq.com/img/about/presskit/logo-v/dd_vertical_purple.png?auto=format&fit=max&w=847&dpr=2 npmPackageName: '@datadog/backstage-plugin-datadog-entity-sync-backend' addedDate: '2025-09-19' +status: active diff --git a/microsite/data/plugins/datadog.yaml b/microsite/data/plugins/datadog.yaml index 0a720698f9..0816a0510d 100644 --- a/microsite/data/plugins/datadog.yaml +++ b/microsite/data/plugins/datadog.yaml @@ -8,3 +8,4 @@ documentation: https://roadie.io/backstage/plugins/datadog/?utm_source=backstage iconUrl: https://roadie.io/images/logos/datadog-white-background.png npmPackageName: '@roadiehq/backstage-plugin-datadog' addedDate: '2021-04-20' +status: active diff --git a/microsite/data/plugins/daytona.yaml b/microsite/data/plugins/daytona.yaml index cf3baaaa92..8349165694 100644 --- a/microsite/data/plugins/daytona.yaml +++ b/microsite/data/plugins/daytona.yaml @@ -7,8 +7,5 @@ description: Open Source Development Environment Manager documentation: https://github.com/daytonaio/backstage-plugins/tree/main/plugins/daytona iconUrl: /img/daytona.svg npmPackageName: '@daytonaio/backstage-plugin-daytona' -tags: - - Development - - Tools - - Productivity addedDate: '2024-11-02' +status: active diff --git a/microsite/data/plugins/dbt.yaml b/microsite/data/plugins/dbt.yaml index e287da7752..5f8788038b 100644 --- a/microsite/data/plugins/dbt.yaml +++ b/microsite/data/plugins/dbt.yaml @@ -5,8 +5,8 @@ authorUrl: https://github.com/IIBenII category: Discovery description: View dbt models and tests documentation. documentation: https://github.com/IIBenII/backstage-plugin-dbt +iconUrl: /img/logo-gradient-on-dark.svg npmPackageName: '@iiben_orgii/backstage-plugin-dbt' -tags: - - dbt - - doc addedDate: '2023-04-27' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/dependencytrack.yaml b/microsite/data/plugins/dependencytrack.yaml index abf1014be6..6dda1d1969 100644 --- a/microsite/data/plugins/dependencytrack.yaml +++ b/microsite/data/plugins/dependencytrack.yaml @@ -7,3 +7,6 @@ description: Plugin for extracting metrics and findings from OWASP's Dependencyt documentation: https://github.com/TRIMM/plugin-dependencytrack/blob/main/README.md iconUrl: https://avatars.githubusercontent.com/u/40258585?s=200&v=4 npmPackageName: '@trimm/plugin-dependencytrack' +addedDate: '2022-09-06' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/dev-friends-day.yaml b/microsite/data/plugins/dev-friends-day.yaml index 9cbcd5b42f..0b458824e2 100644 --- a/microsite/data/plugins/dev-friends-day.yaml +++ b/microsite/data/plugins/dev-friends-day.yaml @@ -8,3 +8,5 @@ documentation: https://github.com/piatkiewicz/backstage-plugins/tree/main/dev-fr iconUrl: /img/dev-friends-days.jpeg npmPackageName: '@piatkiewicz/backstage-dev-friends-days' addedDate: '2024-02-28' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/dev-quotes.yaml b/microsite/data/plugins/dev-quotes.yaml index ad2ac91e4c..e9d1d37834 100644 --- a/microsite/data/plugins/dev-quotes.yaml +++ b/microsite/data/plugins/dev-quotes.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/Parsifal-M/backstage-dev-quotes?tab=readme-ov- iconUrl: /img/dqicon.png npmPackageName: '@parsifal-m/plugin-dev-quotes-homepage' addedDate: '2023-12-13' +status: active diff --git a/microsite/data/plugins/devcontainers.yaml b/microsite/data/plugins/devcontainers.yaml index 427c44b4c6..69dd14b76d 100644 --- a/microsite/data/plugins/devcontainers.yaml +++ b/microsite/data/plugins/devcontainers.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/coder/backstage-plugins/blob/main/plugins/back iconUrl: /img/devcontainers.png npmPackageName: '@coder/backstage-plugin-devcontainers-react' addedDate: '2024-03-15' +status: active diff --git a/microsite/data/plugins/devpod.yaml b/microsite/data/plugins/devpod.yaml index 44f3d54eb5..cb854a440f 100644 --- a/microsite/data/plugins/devpod.yaml +++ b/microsite/data/plugins/devpod.yaml @@ -7,3 +7,4 @@ documentation: https://github.com/TeraSky-OSS/backstage-plugins/tree/main/plugin iconUrl: https://raw.githubusercontent.com/loft-sh/devpod/refs/heads/main/docs/static/media/devpod-logo.png npmPackageName: '@terasky/backstage-plugin-devpod' addedDate: '2024-12-30' +status: active diff --git a/microsite/data/plugins/devtools.yaml b/microsite/data/plugins/devtools.yaml index b531195c04..9c6f332f91 100644 --- a/microsite/data/plugins/devtools.yaml +++ b/microsite/data/plugins/devtools.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/backstage/backstage/blob/master/plugins/devtoo iconUrl: /img/devtools.svg npmPackageName: '@backstage/plugin-devtools' addedDate: '2023-06-17' +status: active diff --git a/microsite/data/plugins/digital.ai-deploy.yaml b/microsite/data/plugins/digital.ai-deploy.yaml index a36dd08ffb..585ab4d20c 100644 --- a/microsite/data/plugins/digital.ai-deploy.yaml +++ b/microsite/data/plugins/digital.ai-deploy.yaml @@ -6,8 +6,6 @@ category: CI/CD description: The plugin offers integration with Digital.ai Deploy and backstage components and services. It provide access to deployments and reports. documentation: https://docs.digital.ai/deploy/docs/concept/xl-deploy-backstage-overview iconUrl: /img/digital.ai-deploy.svg -npmPackageName: '@digital.ai/plugin-dai-deploy' -tags: - - ci - - cd +npmPackageName: '@digital-ai/plugin-dai-deploy' addedDate: '2024-05-06' +status: active diff --git a/microsite/data/plugins/digital.ai-release.yaml b/microsite/data/plugins/digital.ai-release.yaml index 4407b26ac7..af15ce100f 100644 --- a/microsite/data/plugins/digital.ai-release.yaml +++ b/microsite/data/plugins/digital.ai-release.yaml @@ -7,12 +7,5 @@ description: The plugin offers integration with Digital.ai Release and backstage documentation: https://docs.digital.ai/release/docs/concept/release-backstage-overview iconUrl: /img/digital.ai-release.svg npmPackageName: '@digital-ai/plugin-dai-release' -tags: - - ci - - cd - - release - - orchestration - - devops - - pipeline - - automation addedDate: '2024-05-28' +status: active diff --git a/microsite/data/plugins/docker-tags.yaml b/microsite/data/plugins/docker-tags.yaml index c6528cae24..eb8abddfcb 100644 --- a/microsite/data/plugins/docker-tags.yaml +++ b/microsite/data/plugins/docker-tags.yaml @@ -1,10 +1,12 @@ --- title: Docker tags author: Workm8 -authorUrl: 'https://github.com/work-m8' +authorUrl: https://github.com/work-m8 category: Services -description: 'Creates a list of docker tags based on hub.docker.com' -documentation: 'https://github.com/Work-m8/backstage-docker-plugin/blob/main/README.md' -iconUrl: 'https://raw.githubusercontent.com/Work-m8/.github/main/profile/wm_logo.png' +description: Creates a list of docker tags based on hub.docker.com +documentation: https://github.com/Work-m8/backstage-docker-plugin/blob/main/README.md +iconUrl: https://raw.githubusercontent.com/Work-m8/.github/main/profile/wm_logo.png npmPackageName: '@workm8/backstage-docker-tags' -addedDate: '2023-24-10' +addedDate: '2023-10-24' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/dora-metrics.yaml b/microsite/data/plugins/dora-metrics.yaml index 1831c7488f..d612c2bfe9 100644 --- a/microsite/data/plugins/dora-metrics.yaml +++ b/microsite/data/plugins/dora-metrics.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/liatrio/backstage-dora-plugin/tree/main iconUrl: /img/liatrio-icon.png npmPackageName: '@liatrio/backstage-dora-plugin' addedDate: '2024-10-14' +status: active diff --git a/microsite/data/plugins/dora-scorecard.yaml b/microsite/data/plugins/dora-scorecard.yaml new file mode 100644 index 0000000000..4eb3499da1 --- /dev/null +++ b/microsite/data/plugins/dora-scorecard.yaml @@ -0,0 +1,10 @@ +--- +title: DORA Metrics Scorecard +author: zc149 +authorUrl: https://github.com/zc149 +category: Monitoring +description: Track and visualize DORA (DevOps Research and Assessment) metrics with automated data collection from GitHub. Displays Deployment Frequency, Lead Time, Change Failure Rate, and MTTR with performance tier classification. +documentation: https://github.com/zc149/backstage-plugin-dora-scorecard +npmPackageName: '@jikwan/backstage-plugin-dora-scorecard' +addedDate: '2025-12-29' +status: active diff --git a/microsite/data/plugins/dx.yaml b/microsite/data/plugins/dx.yaml index 6fbcb93f76..11d52a897d 100644 --- a/microsite/data/plugins/dx.yaml +++ b/microsite/data/plugins/dx.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/get-dx/backstage-plugin iconUrl: /img/dx-icon.png npmPackageName: '@get-dx/backstage-plugin' addedDate: '2024-03-05' +status: active diff --git a/microsite/data/plugins/dynatrace-managed.yaml b/microsite/data/plugins/dynatrace-managed.yaml index 94793e254d..66d1711b53 100644 --- a/microsite/data/plugins/dynatrace-managed.yaml +++ b/microsite/data/plugins/dynatrace-managed.yaml @@ -7,10 +7,5 @@ description: View problem and synthetic information for services in your softwar documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/dynatrace/plugins/dynatrace iconUrl: /img/dynatrace.svg npmPackageName: '@backstage-community/plugin-dynatrace' -tags: - - monitoring - - observability - - alerting - - problem - - synthetic addedDate: '2022-06-23' +status: active diff --git a/microsite/data/plugins/dynatrace.yaml b/microsite/data/plugins/dynatrace.yaml index 15331bb08a..bc3fcf05f9 100644 --- a/microsite/data/plugins/dynatrace.yaml +++ b/microsite/data/plugins/dynatrace.yaml @@ -7,9 +7,5 @@ description: Provide customizable context-rich observability and security insigh documentation: https://github.com/Dynatrace/backstage-plugin iconUrl: /img/dynatrace.svg npmPackageName: '@dynatrace/backstage-plugin-dql' -tags: - - monitoring - - observability - - security - - problem addedDate: '2024-03-22' +status: active diff --git a/microsite/data/plugins/end-of-life.yaml b/microsite/data/plugins/end-of-life.yaml index ddf4b26341..2f90f6cc01 100644 --- a/microsite/data/plugins/end-of-life.yaml +++ b/microsite/data/plugins/end-of-life.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/dweber019/backstage-plugins/tree/main/plugins/ iconUrl: https://raw.githubusercontent.com/dweber019/backstage-plugins/main/plugins/endoflife/docs/pluginIcon.png npmPackageName: '@dweber019/backstage-plugin-endoflife' addedDate: '2024-01-18' +status: active diff --git a/microsite/data/plugins/entity-feedback.yaml b/microsite/data/plugins/entity-feedback.yaml index a03537b4f7..248444c28f 100644 --- a/microsite/data/plugins/entity-feedback.yaml +++ b/microsite/data/plugins/entity-feedback.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/backstage/community-plugins/tree/main/workspac iconUrl: /img/entity-feedback-logo.png npmPackageName: '@backstage-community/plugin-entity-feedback' addedDate: '2023-01-20' +status: active diff --git a/microsite/data/plugins/entity-validation.yaml b/microsite/data/plugins/entity-validation.yaml index 2ccb6deced..e5759c2f36 100644 --- a/microsite/data/plugins/entity-validation.yaml +++ b/microsite/data/plugins/entity-validation.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/backstage/community-plugins/tree/main/workspac npmPackageName: '@backstage-community/plugin-entity-validation' iconUrl: /img/entity-validation.svg addedDate: '2023-09-13' +status: active diff --git a/microsite/data/plugins/env0.yaml b/microsite/data/plugins/env0.yaml index 431873d347..74841cef67 100644 --- a/microsite/data/plugins/env0.yaml +++ b/microsite/data/plugins/env0.yaml @@ -8,3 +8,5 @@ documentation: https://github.com/env0/env0-backstage-plugin iconUrl: https://avatars.githubusercontent.com/u/46656519?s=200&v=4 npmPackageName: '@env0/backstage-plugin-env0' addedDate: '2025-01-15' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/facets-cloud.yaml b/microsite/data/plugins/facets-cloud.yaml index 0f239a77ee..2f0b7d3319 100644 --- a/microsite/data/plugins/facets-cloud.yaml +++ b/microsite/data/plugins/facets-cloud.yaml @@ -10,3 +10,4 @@ documentation: https://github.com/Facets-cloud/facets-backstage-plugin iconUrl: /img/facets-cloud-logo.png npmPackageName: '@facets-cloud/backstage-plugin' addedDate: '2025-02-25' +status: active diff --git a/microsite/data/plugins/feedback.yaml b/microsite/data/plugins/feedback.yaml index fce9588653..e265952c31 100644 --- a/microsite/data/plugins/feedback.yaml +++ b/microsite/data/plugins/feedback.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/backstage/community-plugins/blob/main/workspac iconUrl: /img/plugin-feedback-logo.svg npmPackageName: '@backstage-community/plugin-entity-feedback' addedDate: '2024-04-09' +status: active diff --git a/microsite/data/plugins/festive-fun.yaml b/microsite/data/plugins/festive-fun.yaml index 33995eca27..8baacac506 100644 --- a/microsite/data/plugins/festive-fun.yaml +++ b/microsite/data/plugins/festive-fun.yaml @@ -6,5 +6,7 @@ category: Humor description: Festive, seasonal, animations to spice up your instance of Backstage! documentation: https://github.com/benjmac/backstage-plugin-festive-fun/ iconUrl: https://raw.githubusercontent.com/benjmac/backstage-plugin-festive-fun/main/docs/festive-fun-logo.png -npmPackageName: 'backstage-plugin-festive-fun' +npmPackageName: backstage-plugin-festive-fun addedDate: '2023-11-20' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/firebase-functions.yaml b/microsite/data/plugins/firebase-functions.yaml index 176cfc1360..662f045245 100644 --- a/microsite/data/plugins/firebase-functions.yaml +++ b/microsite/data/plugins/firebase-functions.yaml @@ -8,3 +8,5 @@ documentation: https://roadie.io/backstage/plugins/firebase-functions/?utm_sourc iconUrl: https://roadie.io/images/logos/firebase.png npmPackageName: '@roadiehq/backstage-plugin-firebase-functions' addedDate: '2021-04-20' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/firefly.yaml b/microsite/data/plugins/firefly.yaml index 8a24920499..58d8b7a38b 100644 --- a/microsite/data/plugins/firefly.yaml +++ b/microsite/data/plugins/firefly.yaml @@ -8,3 +8,4 @@ documentation: https://docs.firefly.ai/firefly-docs/integrations/backstage iconUrl: https://avatars.githubusercontent.com/u/100200663?s=400&v=4 npmPackageName: '@fireflyai/backstage-plugin-firefly' addedDate: '2025-04-07' +status: active diff --git a/microsite/data/plugins/firehydrant.yaml b/microsite/data/plugins/firehydrant.yaml index 7aa00675b4..624d603da7 100644 --- a/microsite/data/plugins/firehydrant.yaml +++ b/microsite/data/plugins/firehydrant.yaml @@ -6,5 +6,6 @@ category: Monitoring description: View service incidents information from FireHydrant, such as active incidents and incident metrics, directly within Backstage. documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/firehydrant/plugins/firehydrant/README.md iconUrl: https://github.com/backstage/community-plugins/blob/main/workspaces/firehydrant/plugins/firehydrant/doc/firehydrant_logo.png -npmPackageName: '@backstage/plugin-firehydrant' +npmPackageName: '@backstage-community/plugin-firehydrant' addedDate: '2021-08-18' +status: active diff --git a/microsite/data/plugins/flagsmith.yaml b/microsite/data/plugins/flagsmith.yaml new file mode 100644 index 0000000000..42f9a8523a --- /dev/null +++ b/microsite/data/plugins/flagsmith.yaml @@ -0,0 +1,11 @@ +--- +title: Flagsmith +author: Flagsmith +authorUrl: https://www.flagsmith.com +category: Feature Flags +description: View Flagsmith feature flags and usage statistics inside Backstage. +documentation: https://docs.flagsmith.com/third-party-integrations/backstage +iconUrl: '/img/flagsmith.svg' +npmPackageName: '@flagsmith/backstage-plugin' +addedDate: '2026-02-26' +status: active diff --git a/microsite/data/plugins/flux.yaml b/microsite/data/plugins/flux.yaml index 91156f7ed3..253d5b4a64 100644 --- a/microsite/data/plugins/flux.yaml +++ b/microsite/data/plugins/flux.yaml @@ -8,3 +8,5 @@ documentation: https://github.com/weaveworks/weaveworks-backstage/tree/main/plug iconUrl: https://raw.githubusercontent.com/fluxcd/website/main/assets/icons/logo.svg npmPackageName: '@weaveworksoss/backstage-plugin-flux' addedDate: '2023-08-23' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/fossa.yaml b/microsite/data/plugins/fossa.yaml index a1a66eb7d6..64ad72a026 100644 --- a/microsite/data/plugins/fossa.yaml +++ b/microsite/data/plugins/fossa.yaml @@ -6,5 +6,6 @@ category: Quality description: View FOSSA license compliance of your components in Backstage. documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/fossa/plugins/fossa/README.md iconUrl: https://avatars0.githubusercontent.com/u/9543448?s=400&v=4 -npmPackageName: '@backstage/plugin-fossa' +npmPackageName: '@backstage-community/plugin-fossa' addedDate: '2020-12-10' +status: active diff --git a/microsite/data/plugins/gcp-projects.yaml b/microsite/data/plugins/gcp-projects.yaml index 7902724c96..64cf5e243c 100644 --- a/microsite/data/plugins/gcp-projects.yaml +++ b/microsite/data/plugins/gcp-projects.yaml @@ -7,8 +7,5 @@ description: Create, list and manage your Google Cloud Projects. documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/gcp-projects/plugins/gcp-projects iconUrl: https://avatars1.githubusercontent.com/u/2810941?s=280&v=4 npmPackageName: '@backstage-community/plugin-gcp-projects' -tags: - - cloud - - project - - resources addedDate: '2021-01-20' +status: active diff --git a/microsite/data/plugins/git-release-manager.yaml b/microsite/data/plugins/git-release-manager.yaml index 244c88b981..4eab25c757 100644 --- a/microsite/data/plugins/git-release-manager.yaml +++ b/microsite/data/plugins/git-release-manager.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/backstage/community-plugins/tree/main/workspac iconUrl: /img/git-release-manager-logo.svg npmPackageName: '@backstage-community/plugin-git-release-manager' addedDate: '2021-10-04' +status: active diff --git a/microsite/data/plugins/github-actions.yaml b/microsite/data/plugins/github-actions.yaml index f496e7c15d..55503f4fbb 100644 --- a/microsite/data/plugins/github-actions.yaml +++ b/microsite/data/plugins/github-actions.yaml @@ -4,11 +4,8 @@ author: Spotify authorUrl: https://github.com/spotify category: CI/CD description: GitHub Actions makes it easy to automate all your software workflows, now with world-class CI/CD. Build, test, and deploy your code right from GitHub. -documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/github-actions/plugins/github-actions +documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/github/plugins/github-actions iconUrl: https://avatars2.githubusercontent.com/u/44036562?s=400&v=4 npmPackageName: '@backstage-community/plugin-github-actions' -tags: - - ci - - cd - - github addedDate: '2021-01-20' +status: active diff --git a/microsite/data/plugins/github-codespaces.yaml b/microsite/data/plugins/github-codespaces.yaml index 90f90d778c..46ae467463 100644 --- a/microsite/data/plugins/github-codespaces.yaml +++ b/microsite/data/plugins/github-codespaces.yaml @@ -7,9 +7,6 @@ description: Integrates GitHub Codespaces for a Backstage component with the Aut documentation: https://github.com/adityasinghal26/backstage-plugins/tree/main/plugins/github-codespaces iconUrl: https://avatars.githubusercontent.com/u/9919?s=200&v=4 npmPackageName: '@adityasinghal26/plugin-github-codespaces' -tags: - - github - - codespaces - - development - - devcontainers addedDate: '2023-12-30' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/github-copilot.yaml b/microsite/data/plugins/github-copilot.yaml index 5228d66ec5..2dd725e7be 100644 --- a/microsite/data/plugins/github-copilot.yaml +++ b/microsite/data/plugins/github-copilot.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/backstage/community-plugins/tree/main/workspac iconUrl: https://platformnewsletter.blob.core.windows.net/$web/copilot.png npmPackageName: '@backstage-community/plugin-copilot' addedDate: '2024-10-21' +status: active diff --git a/microsite/data/plugins/github-insights.yaml b/microsite/data/plugins/github-insights.yaml index 87a636f006..deb9fe4ff5 100644 --- a/microsite/data/plugins/github-insights.yaml +++ b/microsite/data/plugins/github-insights.yaml @@ -8,3 +8,4 @@ documentation: https://roadie.io/backstage/plugins/github-insights/?utm_source=b iconUrl: https://roadie.io/images/logos/insights.png npmPackageName: '@roadiehq/backstage-plugin-github-insights' addedDate: '2021-04-20' +status: active diff --git a/microsite/data/plugins/github-pull-requests-board.yaml b/microsite/data/plugins/github-pull-requests-board.yaml index 51c9f1a2e4..efbe4e1fda 100644 --- a/microsite/data/plugins/github-pull-requests-board.yaml +++ b/microsite/data/plugins/github-pull-requests-board.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/backstage/community-plugins/tree/main/workspac iconUrl: /img/github-pull-requests-board-logo.svg npmPackageName: '@backstage-community/plugin-github-pull-requests-board' addedDate: '2022-05-10' +status: active diff --git a/microsite/data/plugins/github-pull-requests.yaml b/microsite/data/plugins/github-pull-requests.yaml index 57276a634b..122e4a5189 100644 --- a/microsite/data/plugins/github-pull-requests.yaml +++ b/microsite/data/plugins/github-pull-requests.yaml @@ -8,3 +8,4 @@ documentation: https://roadie.io/backstage/plugins/github-pull-requests/?utm_sou iconUrl: https://roadie.io/images/logos/github.png npmPackageName: '@roadiehq/backstage-plugin-github-pull-requests' addedDate: '2021-04-20' +status: active diff --git a/microsite/data/plugins/github-workflows.yaml b/microsite/data/plugins/github-workflows.yaml index f80e6f3f29..613b84ff0b 100644 --- a/microsite/data/plugins/github-workflows.yaml +++ b/microsite/data/plugins/github-workflows.yaml @@ -7,9 +7,5 @@ description: The Github Workflows plugin provides an alternative for manually tr documentation: https://platform.vee.codes/plugin/github-workflows/ iconUrl: https://veecode-platform.github.io/support/imgs/logo_2.svg npmPackageName: '@veecode-platform/backstage-plugin-github-workflows' -tags: - - ci - - cd - - github - - workflows addedDate: '2023-09-12' +status: active diff --git a/microsite/data/plugins/gitlab-pipelines.yaml b/microsite/data/plugins/gitlab-pipelines.yaml index 4b77d67e07..e8d236d54a 100644 --- a/microsite/data/plugins/gitlab-pipelines.yaml +++ b/microsite/data/plugins/gitlab-pipelines.yaml @@ -7,10 +7,5 @@ description: The Gitlab pipelines plugin integrates GitlabCi with its backstage documentation: https://platform.vee.codes/plugin/gitlab-pipelines/ iconUrl: https://veecode-platform.github.io/support/imgs/logo_1.svg npmPackageName: '@veecode-platform/backstage-plugin-gitlab-pipelines' -tags: - - ci - - cd - - gitlabCi - - pipelines - - jobs addedDate: '2023-09-27' +status: active diff --git a/microsite/data/plugins/gitlab.yaml b/microsite/data/plugins/gitlab.yaml index ff815956b5..5afd2009b5 100644 --- a/microsite/data/plugins/gitlab.yaml +++ b/microsite/data/plugins/gitlab.yaml @@ -7,8 +7,5 @@ description: View GitLab pipelines, merge requests, languages and contributors. documentation: https://github.com/immobiliare/backstage-plugin-gitlab iconUrl: https://about.gitlab.com/images/press/logo/png/gitlab-icon-rgb.png npmPackageName: '@immobiliarelabs/backstage-plugin-gitlab' -tags: - - ci - - cd - - gitlab addedDate: '2021-08-17' +status: active diff --git a/microsite/data/plugins/gitops-cluster.yaml b/microsite/data/plugins/gitops-cluster.yaml index 395ab8ec75..66eca0b73e 100644 --- a/microsite/data/plugins/gitops-cluster.yaml +++ b/microsite/data/plugins/gitops-cluster.yaml @@ -5,10 +5,7 @@ authorUrl: https://www.weave.works/ category: Kubernetes description: Create GitOps-managed Kubernetes clusters. Currently, it supports provisioning EKS clusters on GitHub via GitHub Actions. documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/gitops-profiles/plugins/gitops-profiles +iconUrl: '/img/logo-gradient-on-dark.svg' npmPackageName: '@backstage-community/plugin-gitops-profiles' -tags: - - kubernetes - - gitops - - github - - eks addedDate: '2020-11-03' +status: active diff --git a/microsite/data/plugins/glean.yaml b/microsite/data/plugins/glean.yaml index 057ec2f4fd..b33248854e 100644 --- a/microsite/data/plugins/glean.yaml +++ b/microsite/data/plugins/glean.yaml @@ -8,3 +8,5 @@ documentation: https://github.com/beejiujitsu/wealthsimple-backstage-plugins/tre iconUrl: /img/glean-logo-circular-white.png npmPackageName: '@beejiujitsu/backstage-plugin-glean-backend' addedDate: '2024-12-30' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/gocd.yaml b/microsite/data/plugins/gocd.yaml index 2924ef9b41..c5b483df5f 100644 --- a/microsite/data/plugins/gocd.yaml +++ b/microsite/data/plugins/gocd.yaml @@ -7,7 +7,5 @@ description: GoCD is an open-source tool which is used in software development t documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/gocd/plugins/gocd iconUrl: https://www.gocd.org/assets/images/go_logo-5b5ca9e1.svg npmPackageName: '@backstage-community/plugin-gocd' -tags: - - ci - - cd addedDate: '2022-01-15' +status: active diff --git a/microsite/data/plugins/grafana.yaml b/microsite/data/plugins/grafana.yaml index 0637717466..f3b9261d46 100644 --- a/microsite/data/plugins/grafana.yaml +++ b/microsite/data/plugins/grafana.yaml @@ -7,8 +7,5 @@ description: Associate alerts and dashboards to components. documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/grafana/plugins/grafana/docs/index.md iconUrl: https://avatars.githubusercontent.com/u/7195757?s=200&v=4 npmPackageName: '@backstage-community/plugin-grafana' -tags: - - dashboards - - monitoring - - alerting addedDate: '2021-10-11' +status: active diff --git a/microsite/data/plugins/graphiql.yaml b/microsite/data/plugins/graphiql.yaml index 4b5aee5161..8d0a37cb65 100644 --- a/microsite/data/plugins/graphiql.yaml +++ b/microsite/data/plugins/graphiql.yaml @@ -5,9 +5,7 @@ authorUrl: https://github.com/spotify category: Debugging description: Integrates GraphiQL as a tool to browse GraphQL API endpoints inside Backstage. documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/graphiql/plugins/graphiql -iconUrl: https://upload.wikimedia.org/wikipedia/commons/thumb/1/17/GraphQL_Logo.svg/1024px-GraphQL_Logo.svg.png +iconUrl: https://upload.wikimedia.org/wikipedia/commons/1/17/GraphQL_Logo.svg npmPackageName: '@backstage-community/plugin-graphiql' -tags: - - graphql - - graphiql addedDate: '2020-11-03' +status: active diff --git a/microsite/data/plugins/graphql-catalog.yaml b/microsite/data/plugins/graphql-catalog.yaml index 9255f475e7..4c848269eb 100644 --- a/microsite/data/plugins/graphql-catalog.yaml +++ b/microsite/data/plugins/graphql-catalog.yaml @@ -7,8 +7,6 @@ description: Adds the GraphQL Endpoint to Backstage Catalog as a plugin. documentation: https://github.com/thefrontside/playhouse/blob/main/plugins/graphql-backend/README.md iconUrl: https://raw.githubusercontent.com/thefrontside/frontside.com/production/legacy/src/img/frontside-logo.png npmPackageName: '@frontside/backstage-plugin-graphql-backend' -tags: - - graphql - - catalog - - graphql-catalog addedDate: '2024-05-01' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/graphql-voyager.yaml b/microsite/data/plugins/graphql-voyager.yaml index e9c5a95e45..0a5379a960 100644 --- a/microsite/data/plugins/graphql-voyager.yaml +++ b/microsite/data/plugins/graphql-voyager.yaml @@ -7,7 +7,5 @@ description: Integrates the GraphQL Voyager tool inside Backstage as a plugin. documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/graphql-voyager/plugins/graphql-voyager iconUrl: https://res.cloudinary.com/apideck/image/upload/v1612724234/icons/graphql-voyager.png npmPackageName: '@backstage-community/plugin-graphql-voyager' -tags: - - graphql - - graphql-voyager addedDate: '2023-01-27' +status: active diff --git a/microsite/data/plugins/gravatar.yaml b/microsite/data/plugins/gravatar.yaml index d31c16a155..c5903e157d 100644 --- a/microsite/data/plugins/gravatar.yaml +++ b/microsite/data/plugins/gravatar.yaml @@ -7,8 +7,5 @@ description: The processor generates a Gravatar URL for each user based on their documentation: https://github.com/RoadieHQ/roadie-backstage-plugins/blob/main/plugins/backend/catalog-backend-module-gravatar/README.md iconUrl: https://gravatar.com/avatar/gravatar npmPackageName: '@roadiehq/catalog-backend-module-gravatar' -tags: - - gravatar - - catalog - - processor addedDate: '2024-08-26' +status: active diff --git a/microsite/data/plugins/grpc-playground.yaml b/microsite/data/plugins/grpc-playground.yaml index b0f12d4aa2..a65d57c54c 100644 --- a/microsite/data/plugins/grpc-playground.yaml +++ b/microsite/data/plugins/grpc-playground.yaml @@ -6,5 +6,7 @@ category: Discovery description: Easily view and test your gRPC API with a GUI Client, inspired from BloomRPC application. documentation: https://github.com/zalopay-oss/backstage-grpc-playground iconUrl: https://raw.githubusercontent.com/zalopay-oss/backstage-grpc-playground/main/images/gprc-logo.png -npmPackageName: 'backstage-grpc-playground' +npmPackageName: backstage-grpc-playground addedDate: '2022-06-08' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/harbor.yaml b/microsite/data/plugins/harbor.yaml index 5a142b46fa..f874f7e83a 100644 --- a/microsite/data/plugins/harbor.yaml +++ b/microsite/data/plugins/harbor.yaml @@ -7,8 +7,6 @@ description: This plugin will show you information about Docker images within th documentation: https://github.com/BESTSELLER/backstage-plugin-harbor/blob/master/README.md iconUrl: https://raw.githubusercontent.com/cncf/artwork/master/projects/harbor/icon/color/harbor-icon-color.svg npmPackageName: '@bestsellerit/backstage-plugin-harbor' -tags: - - goharbor - - harbor - - docker addedDate: '2022-06-23' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/harness-ccm.yaml b/microsite/data/plugins/harness-ccm.yaml index e696fdca4b..eb978c6088 100644 --- a/microsite/data/plugins/harness-ccm.yaml +++ b/microsite/data/plugins/harness-ccm.yaml @@ -6,6 +6,5 @@ description: This plugin helps you to manage and optimize cloud costs using Harn documentation: https://github.com/harness/backstage-plugins/tree/main/plugins/harness-ccm iconUrl: https://static.harness.io/ng-static/images/favicon.png npmPackageName: '@harnessio/backstage-plugin-harness-ccm' -tags: - - finops addedDate: '2024-09-27' +status: active diff --git a/microsite/data/plugins/harness-chaos.yaml b/microsite/data/plugins/harness-chaos.yaml index e089b3d86b..cfb67d3d20 100644 --- a/microsite/data/plugins/harness-chaos.yaml +++ b/microsite/data/plugins/harness-chaos.yaml @@ -6,6 +6,5 @@ description: This plugin lets you view the status of Harness Chaos Engineering R documentation: https://github.com/harness/backstage-plugins/tree/main/plugins/harness-chaos iconUrl: https://static.harness.io/ng-static/images/favicon.png npmPackageName: '@harnessio/backstage-plugin-harness-chaos' -tags: - - resiliency addedDate: '2024-06-25' +status: active diff --git a/microsite/data/plugins/harness-ci-cd.yaml b/microsite/data/plugins/harness-ci-cd.yaml index 97d2340dfa..96740e2927 100644 --- a/microsite/data/plugins/harness-ci-cd.yaml +++ b/microsite/data/plugins/harness-ci-cd.yaml @@ -7,7 +7,5 @@ description: This plugin lets you view Harness pipeline execution details inside documentation: https://github.com/harness/backstage-plugins/tree/main/plugins/harness-ci-cd iconUrl: https://static.harness.io/ng-static/images/favicon.png npmPackageName: '@harnessio/backstage-plugin-ci-cd' -tags: - - ci - - cd addedDate: '2022-12-08' +status: active diff --git a/microsite/data/plugins/harness-feature-flags.yaml b/microsite/data/plugins/harness-feature-flags.yaml index ac085d901a..048407b6d7 100644 --- a/microsite/data/plugins/harness-feature-flags.yaml +++ b/microsite/data/plugins/harness-feature-flags.yaml @@ -7,6 +7,5 @@ description: This plugin lets you view Harness Feature Flags created in your pro documentation: https://github.com/harness/backstage-plugins/tree/main/plugins/harness-feature-flags iconUrl: https://static.harness.io/ng-static/images/favicon.png npmPackageName: '@harnessio/backstage-plugin-feature-flags' -tags: - - feature-flags addedDate: '2023-03-01' +status: active diff --git a/microsite/data/plugins/harness-iacm.yaml b/microsite/data/plugins/harness-iacm.yaml index b9aa3b8ab4..7a6bf0a6d6 100644 --- a/microsite/data/plugins/harness-iacm.yaml +++ b/microsite/data/plugins/harness-iacm.yaml @@ -7,7 +7,5 @@ description: This plugin provides an overview of all the resources provisioned t documentation: https://github.com/harness/backstage-plugins/tree/main/plugins/harness-iacm#harness-iacm-plugin iconUrl: https://static.harness.io/ng-static/images/favicon.png npmPackageName: '@harnessio/backstage-plugin-harness-iacm' -tags: - - infrastructure-as-code - - resource-management addedDate: '2024-07-03' +status: active diff --git a/microsite/data/plugins/harness-srm.yaml b/microsite/data/plugins/harness-srm.yaml index d55c60ea04..d687832232 100644 --- a/microsite/data/plugins/harness-srm.yaml +++ b/microsite/data/plugins/harness-srm.yaml @@ -7,8 +7,5 @@ description: This plugin tracks the defined SLOs and Error Budgets for your Moni documentation: https://github.com/harness/backstage-plugins/tree/main/plugins/harness-srm iconUrl: https://static.harness.io/ng-static/images/favicon.png npmPackageName: '@harnessio/backstage-plugin-harness-srm' -tags: - - service-reliability-management - - slo - - sli addedDate: '2023-11-17' +status: active diff --git a/microsite/data/plugins/hashicorp-terraform.yaml b/microsite/data/plugins/hashicorp-terraform.yaml index c254667015..3b996be7b3 100644 --- a/microsite/data/plugins/hashicorp-terraform.yaml +++ b/microsite/data/plugins/hashicorp-terraform.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/globallogicuki/globallogic-backstage-plugins/t iconUrl: /img/terraform-logo.svg npmPackageName: '@globallogicuki/backstage-plugin-terraform' addedDate: '2024-07-17' +status: active diff --git a/microsite/data/plugins/hcp-consul.yaml b/microsite/data/plugins/hcp-consul.yaml index 6dabad582e..5501e0d191 100644 --- a/microsite/data/plugins/hcp-consul.yaml +++ b/microsite/data/plugins/hcp-consul.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/hashicorp-forge/backstage-plugin-hcp-consul/tr iconUrl: /img/hcp-consul.svg npmPackageName: '@hashicorp/plugin-hcp-consul' addedDate: '2023-11-24' +status: active diff --git a/microsite/data/plugins/hetzner-cloud.yaml b/microsite/data/plugins/hetzner-cloud.yaml index 6f1fbe3f25..36e9172e9f 100644 --- a/microsite/data/plugins/hetzner-cloud.yaml +++ b/microsite/data/plugins/hetzner-cloud.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/gluobe/hetzner-backstage-plugin/blob/main/READ iconUrl: https://avatars.githubusercontent.com/u/18439789?s=200&v=4 npmPackageName: '@gluo-nv/backstage-plugin-hetzner' addedDate: '2025-06-03' +status: active diff --git a/microsite/data/plugins/holiday-tracker.yaml b/microsite/data/plugins/holiday-tracker.yaml index 3232088582..20cbea8057 100644 --- a/microsite/data/plugins/holiday-tracker.yaml +++ b/microsite/data/plugins/holiday-tracker.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/Infosys/holiday-tracker-plugin/blob/main/plugi iconUrl: https://github.com/Infosys/holiday-tracker-plugin/blob/main/plugins/holiday-tracker/src/docs/holiday-tracker-plugin.logo.png?raw=true npmPackageName: '@infosys_ltd/holiday-tracker-plugin' addedDate: '2025-07-23' +status: active diff --git a/microsite/data/plugins/home.yaml b/microsite/data/plugins/home.yaml index 0ca2de56f2..b2b45b9a11 100644 --- a/microsite/data/plugins/home.yaml +++ b/microsite/data/plugins/home.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/backstage/backstage/blob/master/plugins/home/R iconUrl: /img/home.png npmPackageName: '@backstage/plugin-home' addedDate: '2021-08-31' +status: active diff --git a/microsite/data/plugins/hoop.yaml b/microsite/data/plugins/hoop.yaml index 9e41502807..062b1daa74 100644 --- a/microsite/data/plugins/hoop.yaml +++ b/microsite/data/plugins/hoop.yaml @@ -7,8 +7,6 @@ description: hoop.dev is a cloud-native ssh implementation that lets you edit co documentation: https://github.com/hoophq/backstage-plugin iconUrl: https://avatars.githubusercontent.com/u/113131551?s=200&v=4 npmPackageName: '@hoophq/backstage-plugin' -tags: - - cloud-native ssh - - monitoring - - redact addedDate: '2022-12-20' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/humanitec.yaml b/microsite/data/plugins/humanitec.yaml index a94ca431e6..fc1ddc7d4f 100644 --- a/microsite/data/plugins/humanitec.yaml +++ b/microsite/data/plugins/humanitec.yaml @@ -10,3 +10,4 @@ documentation: https://github.com/humanitec/humanitec-backstage-plugins iconUrl: /img/humanitec-logo.png npmPackageName: '@humanitec/backstage-plugin' addedDate: '2022-06-22' +status: active diff --git a/microsite/data/plugins/hypo-stage.yaml b/microsite/data/plugins/hypo-stage.yaml new file mode 100644 index 0000000000..2fc4f16afe --- /dev/null +++ b/microsite/data/plugins/hypo-stage.yaml @@ -0,0 +1,11 @@ +--- +title: HypoStage +author: ArchHypo +authorUrl: https://github.com/ArchHypo +category: Discovery +description: Document architectural assumptions, track uncertainty, and validate decisions with hypothesis management, quality attributes, and technical planning. +documentation: https://github.com/ArchHypo/hypo-stage/blob/v1.0.0/README.md +iconUrl: 'https://raw.githubusercontent.com/ArchHypo/hypo-stage/main/docs/backstage-directory/icon/hypo-stage.png' +npmPackageName: '@archhypo/plugin-hypo-stage' +addedDate: '2026-04-14' +status: active diff --git a/microsite/data/plugins/ibm-apic-backend.yaml b/microsite/data/plugins/ibm-apic-backend.yaml index dcb41a1cef..1903a0dc1e 100644 --- a/microsite/data/plugins/ibm-apic-backend.yaml +++ b/microsite/data/plugins/ibm-apic-backend.yaml @@ -7,6 +7,6 @@ description: Bring IBM APIC to Backstage. documentation: https://github.com/croz-ltd/apic-backend-plugin?utm_source=backstage.io&utm_medium=marketplace&utm_campaign=backstage-ibm-apic-backend iconUrl: https://croz.net/app/uploads/2024/05/apple-touch-icon.png npmPackageName: '@croz/plugin-ibm-apic-backend' -tags: - - openapic addedDate: '2024-03-01' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/ilert.yaml b/microsite/data/plugins/ilert.yaml index c410084e8c..1ebf2b9d9f 100644 --- a/microsite/data/plugins/ilert.yaml +++ b/microsite/data/plugins/ilert.yaml @@ -7,10 +7,5 @@ description: iLert is a platform for alerting, on-call management and uptime mon documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/ilert/plugins/ilert iconUrl: https://avatars.githubusercontent.com/u/13230510?s=200&v=4 npmPackageName: '@backstage-community/plugin-ilert' -tags: - - monitoring - - errors - - alerting - - uptime - - on-call addedDate: '2021-04-20' +status: active diff --git a/microsite/data/plugins/incident-io.yaml b/microsite/data/plugins/incident-io.yaml new file mode 100644 index 0000000000..2deea1e686 --- /dev/null +++ b/microsite/data/plugins/incident-io.yaml @@ -0,0 +1,10 @@ +title: incident.io +author: incident.io +authorUrl: https://github.com/incident-io +category: Monitoring +description: Display ongoing and historic incidents against Backstage components, and to provide quick links to open new incidents for that service inside of incident.io. +documentation: https://github.com/incident-io/backstage-plugins/blob/master/incident/README.md +iconUrl: /img/incident-io-logo.png +npmPackageName: '@incident-io/backstage' +addedDate: '2026-04-06' +status: active diff --git a/microsite/data/plugins/infisical.yaml b/microsite/data/plugins/infisical.yaml index 41134c0b42..7aa08273fd 100644 --- a/microsite/data/plugins/infisical.yaml +++ b/microsite/data/plugins/infisical.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/Infisical/infisical-backstage-plugin iconUrl: https://avatars.githubusercontent.com/u/107880645?s=200&v=4 npmPackageName: '@infisical/backstage-plugin-infisical' addedDate: '2025-04-11' +status: active diff --git a/microsite/data/plugins/infracost.yaml b/microsite/data/plugins/infracost.yaml index b6ead655d5..33f870d843 100644 --- a/microsite/data/plugins/infracost.yaml +++ b/microsite/data/plugins/infracost.yaml @@ -7,9 +7,5 @@ description: The Infracost plug-in works in conjunction with terraform and provi documentation: https://platform.vee.codes/plugin/Infracost iconUrl: https://veecode-platform.github.io/support/imgs/logo_5.svg npmPackageName: '@veecode-platform/backstage-plugin-infracost' -tags: - - finops - - infracost - - estimate - - terraform addedDate: '2024-07-25' +status: active diff --git a/microsite/data/plugins/infrawallet.yaml b/microsite/data/plugins/infrawallet.yaml index bb6c3de7cf..77e2eefaf8 100644 --- a/microsite/data/plugins/infrawallet.yaml +++ b/microsite/data/plugins/infrawallet.yaml @@ -7,8 +7,5 @@ description: Control your cloud costs just in the way how you control your bank documentation: https://github.com/electrolux-oss/infrawallet/blob/main/README.md iconUrl: /img/infrawallet-logo.png npmPackageName: '@electrolux-oss/plugin-infrawallet' -tags: - - finops - - cost - - infrastructure addedDate: '2024-06-03' +status: active diff --git a/microsite/data/plugins/jaegertracing.yaml b/microsite/data/plugins/jaegertracing.yaml index dc93193ddb..ba2b40daee 100644 --- a/microsite/data/plugins/jaegertracing.yaml +++ b/microsite/data/plugins/jaegertracing.yaml @@ -7,8 +7,5 @@ description: View Jaeger traces and trace details in Backstage components. documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/jaeger/plugins/jaeger/README.md iconUrl: https://avatars.githubusercontent.com/u/28545596?s=200&v=4 npmPackageName: '@backstage-community/plugin-jaeger' -tags: - - monitoring - - tracing - - observability addedDate: '2024-12-21' +status: active diff --git a/microsite/data/plugins/jenkins-actions.yaml b/microsite/data/plugins/jenkins-actions.yaml index 4fccdcf0b6..55b22a14e8 100644 --- a/microsite/data/plugins/jenkins-actions.yaml +++ b/microsite/data/plugins/jenkins-actions.yaml @@ -8,5 +8,4 @@ documentation: https://github.com/backstage/community-plugins/tree/main/workspac iconUrl: /img/jenkins.png npmPackageName: '@backstage-community/plugin-scaffolder-backend-module-jenkins' addedDate: '2024-12-06' -tags: - - Scaffolder +status: active diff --git a/microsite/data/plugins/jenkins.yaml b/microsite/data/plugins/jenkins.yaml index e57a7e9e7b..b723362db4 100644 --- a/microsite/data/plugins/jenkins.yaml +++ b/microsite/data/plugins/jenkins.yaml @@ -7,7 +7,5 @@ description: Jenkins offers a simple way to set up a continuous integration and documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/jenkins/plugins/jenkins iconUrl: https://img.icons8.com/color/1600/jenkins.png npmPackageName: '@backstage-community/plugin-jenkins' -tags: - - ci - - cd addedDate: '2021-01-20' +status: active diff --git a/microsite/data/plugins/jfrog-artifactory-libs.yaml b/microsite/data/plugins/jfrog-artifactory-libs.yaml index 22583cbfbd..a97d975c59 100644 --- a/microsite/data/plugins/jfrog-artifactory-libs.yaml +++ b/microsite/data/plugins/jfrog-artifactory-libs.yaml @@ -3,11 +3,10 @@ title: JFrog Artifactory Libs plugin author: Vity authorUrl: https://github.com/Vity01 category: Discovery -description: - frontend plugin provides a simple way to display generated artifact (library) details like - - group, artifact, repository, what is the latest version, and it simply - allows to copy library definition for the package managers. +description: frontend plugin provides a simple way to display generated artifact (library) details like - group, artifact, repository, what is the latest version, and it simply allows to copy library definition for the package managers. documentation: https://github.com/Vity01/backstage-jfrog-artifactory-libs iconUrl: https://raw.githubusercontent.com/Vity01/backstage-jfrog-artifactory-libs/main/doc/artifactory.svg -npmPackageName: 'backstage-plugin-jfrog-artifactory-libs' +npmPackageName: backstage-plugin-jfrog-artifactory-libs addedDate: '2023-08-14' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/jfrog-artifactory.yaml b/microsite/data/plugins/jfrog-artifactory.yaml index b4f1ef567e..b4e921f9c5 100644 --- a/microsite/data/plugins/jfrog-artifactory.yaml +++ b/microsite/data/plugins/jfrog-artifactory.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/backstage/community-plugins/blob/main/workspac iconUrl: /img/jfrog-artifactory.svg npmPackageName: '@backstage-community/plugin-jfrog-artifactory' addedDate: '2023-05-15' +status: active diff --git a/microsite/data/plugins/jira-dashboard.yaml b/microsite/data/plugins/jira-dashboard.yaml index 8cb9351c8d..e111248f55 100644 --- a/microsite/data/plugins/jira-dashboard.yaml +++ b/microsite/data/plugins/jira-dashboard.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/AxisCommunications/backstage-plugins/blob/main iconUrl: https://raw.githubusercontent.com/AxisCommunications/backstage-plugins/main/plugins/jira-dashboard/media/jira-logo.png npmPackageName: '@axis-backstage/plugin-jira-dashboard' addedDate: '2023-12-07' +status: active diff --git a/microsite/data/plugins/jira.yaml b/microsite/data/plugins/jira.yaml index 6c803bcd68..0ba6c96fac 100644 --- a/microsite/data/plugins/jira.yaml +++ b/microsite/data/plugins/jira.yaml @@ -8,3 +8,4 @@ documentation: https://roadie.io/backstage/plugins/jira/?utm_source=backstage.io iconUrl: https://roadie.io/images/logos/jira.png npmPackageName: '@roadiehq/backstage-plugin-jira' addedDate: '2021-04-20' +status: active diff --git a/microsite/data/plugins/k8sgpt.yaml b/microsite/data/plugins/k8sgpt.yaml index f509009a4f..c2d6c6a3d8 100644 --- a/microsite/data/plugins/k8sgpt.yaml +++ b/microsite/data/plugins/k8sgpt.yaml @@ -8,3 +8,5 @@ documentation: https://github.com/suxess-it/backstage-plugin-k8sgpt/blob/main/RE iconUrl: https://raw.githubusercontent.com/k8sgpt-ai/k8sgpt/1a81227d6148be59b7b9ae4e9ae5e2d9a5b7a9ae/images/banner-white.png npmPackageName: '@suxess-it/backstage-plugin-k8sgpt' addedDate: '2023-06-05' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/kafka.yaml b/microsite/data/plugins/kafka.yaml index 7529add2ee..e7d7ce443a 100644 --- a/microsite/data/plugins/kafka.yaml +++ b/microsite/data/plugins/kafka.yaml @@ -7,6 +7,5 @@ description: Observability for Apache Kafka clusters and async API of components documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/kafka/plugins/kafka iconUrl: https://kafka.apache.org/images/apache-kafka.png npmPackageName: '@backstage-community/plugin-kafka' -tags: - - monitoring addedDate: '2021-01-21' +status: active diff --git a/microsite/data/plugins/keycloak.yaml b/microsite/data/plugins/keycloak.yaml index 4136f50f95..d7fad6a0ae 100644 --- a/microsite/data/plugins/keycloak.yaml +++ b/microsite/data/plugins/keycloak.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/backstage/community-plugins/blob/main/workspac iconUrl: /img/keycloak.svg npmPackageName: '@backstage-community/plugin-catalog-backend-module-keycloak' addedDate: '2023-05-15' +status: active diff --git a/microsite/data/plugins/kiali.yaml b/microsite/data/plugins/kiali.yaml index 86299c5045..5c27a3b776 100644 --- a/microsite/data/plugins/kiali.yaml +++ b/microsite/data/plugins/kiali.yaml @@ -8,3 +8,5 @@ documentation: https://janus-idp.io/plugins/kiali iconUrl: /img/kiali.svg npmPackageName: '@janus-idp/backstage-plugin-kiali' addedDate: '2023-07-25' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/knative-event-mesh.yaml b/microsite/data/plugins/knative-event-mesh.yaml index 6ecb219b32..2a9e4cb17c 100644 --- a/microsite/data/plugins/knative-event-mesh.yaml +++ b/microsite/data/plugins/knative-event-mesh.yaml @@ -8,3 +8,4 @@ documentation: https://knative.dev/docs/install/installing-backstage-plugins/ npmPackageName: '@knative-extensions/plugin-knative-event-mesh-backend' iconUrl: https://raw.githubusercontent.com/knative/community/refs/heads/main/icons/logo.svg addedDate: '2024-12-16' +status: active diff --git a/microsite/data/plugins/kong-service-manager.yaml b/microsite/data/plugins/kong-service-manager.yaml index 2b5f444479..9d5688b12b 100644 --- a/microsite/data/plugins/kong-service-manager.yaml +++ b/microsite/data/plugins/kong-service-manager.yaml @@ -7,10 +7,5 @@ description: The Kong Service Manager plugin provides information about the kong documentation: https://platform.vee.codes/plugin/kong-service-manager/ iconUrl: https://veecode-platform.github.io/support/imgs/logo_3.svg npmPackageName: '@veecode-platform/plugin-kong-service-manager' -tags: - - kong - - api-manager - - services - - routes - - plugins addedDate: '2024-05-27' +status: active diff --git a/microsite/data/plugins/kpt-config-as-data.yaml b/microsite/data/plugins/kpt-config-as-data.yaml index af157a70c3..c97996e2b0 100644 --- a/microsite/data/plugins/kpt-config-as-data.yaml +++ b/microsite/data/plugins/kpt-config-as-data.yaml @@ -8,3 +8,5 @@ documentation: https://github.com/GoogleContainerTools/kpt-backstage-plugins/tre iconUrl: https://github.com/GoogleContainerTools/kpt/blob/main/logo/KptLogoSmall.png?raw=true npmPackageName: '@kpt/backstage-plugin-cad' addedDate: '2022-05-13' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/kubecost.yaml b/microsite/data/plugins/kubecost.yaml index f1f4ff1218..4e6b388c18 100644 --- a/microsite/data/plugins/kubecost.yaml +++ b/microsite/data/plugins/kubecost.yaml @@ -8,3 +8,5 @@ documentation: https://github.com/suxess-it/backstage-plugin-kubecost/blob/main/ iconUrl: https://avatars.githubusercontent.com/u/45108136?s=200&v=4 npmPackageName: '@suxess-it/backstage-plugin-kubecost' addedDate: '2023-06-29' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/kubelog.yaml b/microsite/data/plugins/kubelog.yaml index fcadba2978..2ab4c8c89d 100644 --- a/microsite/data/plugins/kubelog.yaml +++ b/microsite/data/plugins/kubelog.yaml @@ -7,4 +7,5 @@ description: Kubelog provides your Backstage users the ability to view logs of y documentation: https://github.com/jfvilas/kubelog iconUrl: https://raw.githubusercontent.com/jfvilas/kubelog/master/src/assets/kubelog-logo.png npmPackageName: '@jfvilas/plugin-kubelog' -addedDate: 2024-08-25 +addedDate: '2024-08-25' +status: active diff --git a/microsite/data/plugins/kubernetes-gpt-analyzer.yaml b/microsite/data/plugins/kubernetes-gpt-analyzer.yaml index e5de961869..5c111bec70 100644 --- a/microsite/data/plugins/kubernetes-gpt-analyzer.yaml +++ b/microsite/data/plugins/kubernetes-gpt-analyzer.yaml @@ -7,10 +7,5 @@ description: The Kubernetes GPT Analyzer plug-in uses artificial intelligence wi documentation: https://platform.vee.codes/plugin/kubernetes-gpt-analyzer/ iconUrl: https://veecode-platform.github.io/support/imgs/logo_4.svg npmPackageName: '@veecode-platform/backstage-plugin-kubernetes-gpt-analyzer' -tags: - - monitor - - ai - - kubernetes - - k8soperator - - gpt addedDate: '2024-07-31' +status: active diff --git a/microsite/data/plugins/kubernetes-ingestor.yaml b/microsite/data/plugins/kubernetes-ingestor.yaml index 006333e990..6c3ae5b24f 100644 --- a/microsite/data/plugins/kubernetes-ingestor.yaml +++ b/microsite/data/plugins/kubernetes-ingestor.yaml @@ -7,3 +7,4 @@ documentation: https://github.com/TeraSky-OSS/backstage-plugins/tree/main/plugin iconUrl: https://avatars.githubusercontent.com/u/13629408 npmPackageName: '@terasky/backstage-plugin-kubernetes-ingestor' addedDate: '2024-12-30' +status: active diff --git a/microsite/data/plugins/kubernetes-provider.yaml b/microsite/data/plugins/kubernetes-provider.yaml index e0971d55be..75f1f0780a 100644 --- a/microsite/data/plugins/kubernetes-provider.yaml +++ b/microsite/data/plugins/kubernetes-provider.yaml @@ -6,5 +6,7 @@ category: Kubernetes description: Import Kubernetes resources into Backstage Components documentation: https://github.com/AntoineDao/backstage-provider-kubernetes#readme iconUrl: https://avatars.githubusercontent.com/u/13629408 -npmPackageName: https://www.npmjs.com/package/@antoinedao/backstage-provider-kubernetes +npmPackageName: '@antoinedao/backstage-provider-kubernetes' addedDate: '2023-04-10' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/kubevela.yaml b/microsite/data/plugins/kubevela.yaml index 524a046025..ab5acc1cb1 100644 --- a/microsite/data/plugins/kubevela.yaml +++ b/microsite/data/plugins/kubevela.yaml @@ -7,9 +7,6 @@ description: KubeVela is a modern software delivery platform that makes deployin documentation: https://github.com/kubevela-contrib/backstage-plugin-kubevela iconUrl: https://kubevela.io/img/logo.svg npmPackageName: '@oamdev/plugin-kubevela-backend' -tags: - - cd - - delivery - - multi-cluster - - deployment as code addedDate: '2023-02-12' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/kwirthlog.yaml b/microsite/data/plugins/kwirthlog.yaml index 49863c2d4e..ac8eafe036 100644 --- a/microsite/data/plugins/kwirthlog.yaml +++ b/microsite/data/plugins/kwirthlog.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/jfvilas/plugin-kwirth-log iconUrl: https://raw.githubusercontent.com/jfvilas/plugin-kwirth-log/master/src/assets/kwirthlog-logo.png npmPackageName: '@jfvilas/plugin-kwirth-log' addedDate: '2025-09-18' +status: active diff --git a/microsite/data/plugins/kwirthmetrics.yaml b/microsite/data/plugins/kwirthmetrics.yaml index 9a8c069eda..48bf963ac2 100644 --- a/microsite/data/plugins/kwirthmetrics.yaml +++ b/microsite/data/plugins/kwirthmetrics.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/jfvilas/plugin-kwirth-metrics iconUrl: https://raw.githubusercontent.com/jfvilas/plugin-kwirth-metrics/master/src/assets/kwirthmetrics-logo.png npmPackageName: '@jfvilas/plugin-kwirth-metrics' addedDate: '2025-09-14' +status: active diff --git a/microsite/data/plugins/kyverno.yaml b/microsite/data/plugins/kyverno.yaml index 63554d02fe..2137262fcb 100644 --- a/microsite/data/plugins/kyverno.yaml +++ b/microsite/data/plugins/kyverno.yaml @@ -7,3 +7,4 @@ documentation: https://github.com/TeraSky-OSS/backstage-plugins/tree/main/plugin iconUrl: https://github.com/cncf/artwork/blob/main/projects/kyverno/icon/color/kyverno-icon-color.png?raw=true npmPackageName: '@terasky/backstage-plugin-kyverno-policy-reports' addedDate: '2025-01-05' +status: active diff --git a/microsite/data/plugins/launchdarkly.yaml b/microsite/data/plugins/launchdarkly.yaml index 30e4cab685..c97550147e 100644 --- a/microsite/data/plugins/launchdarkly.yaml +++ b/microsite/data/plugins/launchdarkly.yaml @@ -5,5 +5,7 @@ authorUrl: https://roadie.io category: CI/CD description: View LaunchDarkly feature flags for your entities in Backstage. documentation: https://github.com/RoadieHQ/roadie-backstage-plugins/blob/launchdarkly/plugins/frontend/backstage-plugin-launchdarkly/README.md +iconUrl: '/img/logo-gradient-on-dark.svg' npmPackageName: '@roadiehq/backstage-plugin-launchdarkly' addedDate: '2024-07-27' +status: active diff --git a/microsite/data/plugins/ldap-auth.yml b/microsite/data/plugins/ldap-auth.yaml similarity index 85% rename from microsite/data/plugins/ldap-auth.yml rename to microsite/data/plugins/ldap-auth.yaml index 7446f28f6f..3b56deceb5 100644 --- a/microsite/data/plugins/ldap-auth.yml +++ b/microsite/data/plugins/ldap-auth.yaml @@ -7,3 +7,6 @@ description: Authenticate users to an external LDAP server documentation: https://github.com/immobiliare/backstage-plugin-ldap-auth/blob/main/README.md iconUrl: https://avatars.githubusercontent.com/u/10090828 npmPackageName: '@immobiliarelabs/backstage-plugin-ldap-auth' +addedDate: '2022-09-05' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/library-check.yaml b/microsite/data/plugins/library-check.yaml index 68489d676f..2150fb0d0b 100644 --- a/microsite/data/plugins/library-check.yaml +++ b/microsite/data/plugins/library-check.yaml @@ -8,3 +8,5 @@ documentation: https://github.com/anakzr/backstage-plugin-library-check iconUrl: /img/library-check-logo.png npmPackageName: '@anakz/backstage-plugin-library-check' addedDate: '2024-03-11' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/lighthouse.yaml b/microsite/data/plugins/lighthouse.yaml index a29e48406a..f9d70e325a 100644 --- a/microsite/data/plugins/lighthouse.yaml +++ b/microsite/data/plugins/lighthouse.yaml @@ -7,9 +7,5 @@ description: Google's Lighthouse tool is a great resource for benchmarking and i documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/lighthouse/plugins/lighthouse iconUrl: https://seeklogo.com/images/G/google-lighthouse-logo-1C7FA08580-seeklogo.com.png npmPackageName: '@backstage-community/plugin-lighthouse' -tags: - - web - - seo - - accessibility - - performance addedDate: '2021-01-20' +status: active diff --git a/microsite/data/plugins/linguist.yaml b/microsite/data/plugins/linguist.yaml index fb8e88b747..19cf007f1f 100644 --- a/microsite/data/plugins/linguist.yaml +++ b/microsite/data/plugins/linguist.yaml @@ -6,5 +6,6 @@ category: Metadata description: View the programming language break down for your entities within the Software Catalog documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/linguist/plugins/linguist/README.md iconUrl: /img/linguist.svg -npmPackageName: '@backstage/plugin-linguist' +npmPackageName: '@backstage-community/plugin-linguist' addedDate: '2023-06-17' +status: active diff --git a/microsite/data/plugins/litmus.yaml b/microsite/data/plugins/litmus.yaml index d27bc4c7d7..045c2ce381 100644 --- a/microsite/data/plugins/litmus.yaml +++ b/microsite/data/plugins/litmus.yaml @@ -6,5 +6,7 @@ category: Chaos Engineering description: This plugin lets you view the status of Litmus resources and launch Chaos Experiments directly inside Backstage. documentation: https://github.com/litmuschaos/backstage-plugin/blob/master/README.md iconUrl: https://raw.githubusercontent.com/cncf/artwork/master/projects/litmus/icon/color/litmus-icon-color.svg -npmPackageName: 'backstage-plugin-litmus' +npmPackageName: backstage-plugin-litmus addedDate: '2023-10-06' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/mend.yaml b/microsite/data/plugins/mend.yaml index 8c18c6b71f..3ae16d50c9 100644 --- a/microsite/data/plugins/mend.yaml +++ b/microsite/data/plugins/mend.yaml @@ -7,6 +7,5 @@ description: Proactive application risk management for developer and security te documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/mend/plugins/mend iconUrl: https://avatars.githubusercontent.com/u/105765982?s=200&v=4 npmPackageName: '@backstage-community/plugin-mend' -tags: - - security addedDate: '2024-10-15' +status: active diff --git a/microsite/data/plugins/mia-platform.yaml b/microsite/data/plugins/mia-platform.yaml index d4a7b692cd..7e7fa23f78 100644 --- a/microsite/data/plugins/mia-platform.yaml +++ b/microsite/data/plugins/mia-platform.yaml @@ -7,7 +7,5 @@ description: Elevate your workflow with our plugin that seamlessly integrates yo documentation: https://github.com/mia-platform/backstage-plugin/blob/main/README.md iconUrl: /img/mia-platform.png npmPackageName: '@mia-platform/backstage-plugin-frontend' -tags: - - platform - - discovery addedDate: '2024-04-12' +status: active diff --git a/microsite/data/plugins/microcks.yaml b/microsite/data/plugins/microcks.yaml index 0ac7a0c00a..3bab01f339 100644 --- a/microsite/data/plugins/microcks.yaml +++ b/microsite/data/plugins/microcks.yaml @@ -7,6 +7,5 @@ description: Import API definitions (OpenAPI, AsyncAPI, gRPC) from Microcks inst documentation: https://github.com/microcks/microcks-backstage-provider iconUrl: https://raw.githubusercontent.com/microcks/.github/refs/heads/main/assets/microcks-logo-blue.png npmPackageName: '@microcks/microcks-backstage-provider' -tags: - - mock addedDate: '2023-04-06' +status: active diff --git a/microsite/data/plugins/microsoft-calendar.yaml b/microsite/data/plugins/microsoft-calendar.yaml index b09493a865..8803c503ec 100644 --- a/microsite/data/plugins/microsoft-calendar.yaml +++ b/microsite/data/plugins/microsoft-calendar.yaml @@ -6,9 +6,7 @@ description: | The plugin integrates with your Microsoft Calendar. It displays all your calendars and events in it. documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/microsoft-calendar/plugins/microsoft-calendar +iconUrl: '/img/logo-gradient-on-dark.svg' npmPackageName: '@backstage-community/plugin-microsoft-calendar' -tags: - - Microsoft - - calendar - - Management addedDate: '2023-03-15' +status: active diff --git a/microsite/data/plugins/microsoft-forms.yaml b/microsite/data/plugins/microsoft-forms.yaml index 7b36fe2ad6..ea1d11899e 100644 --- a/microsite/data/plugins/microsoft-forms.yaml +++ b/microsite/data/plugins/microsoft-forms.yaml @@ -6,7 +6,5 @@ description: Embed a form into the entity page with ease! documentation: https://github.com/zcmander/backstage-plugin-msforms iconUrl: /img/microsoft-forms.png npmPackageName: '@zcmander/backstage-plugin-msforms' -tags: - - Microsoft - - forms addedDate: '2023-04-11' +status: active diff --git a/microsite/data/plugins/n8n.yaml b/microsite/data/plugins/n8n.yaml new file mode 100644 index 0000000000..7898c1efe4 --- /dev/null +++ b/microsite/data/plugins/n8n.yaml @@ -0,0 +1,11 @@ +--- +title: n8n +author: André Ahlert Junior +authorUrl: https://github.com/andreahlert +category: Other +description: n8n workflow automation — view workflows and execution history on Backstage entity pages. +documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/n8n/plugins/n8n +iconUrl: /img/logo-gradient-on-dark.svg +npmPackageName: '@backstage-community/plugin-n8n' +addedDate: '2026-02-15' +status: active diff --git a/microsite/data/plugins/new-relic.yaml b/microsite/data/plugins/new-relic.yaml index 57b2db00ec..98f7214c45 100644 --- a/microsite/data/plugins/new-relic.yaml +++ b/microsite/data/plugins/new-relic.yaml @@ -7,9 +7,5 @@ description: Observability platform built to help engineers create and monitor t documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/newrelic/plugins/newrelic iconUrl: https://www.mulesoft.com/sites/default/files/2018-10/New_relic.png npmPackageName: '@backstage-community/plugin-newrelic' -tags: - - performance - - monitoring - - errors - - alerting addedDate: '2020-11-03' +status: active diff --git a/microsite/data/plugins/newrelic-dashboard.yaml b/microsite/data/plugins/newrelic-dashboard.yaml index e53285cca7..4c95c87ec7 100644 --- a/microsite/data/plugins/newrelic-dashboard.yaml +++ b/microsite/data/plugins/newrelic-dashboard.yaml @@ -7,10 +7,5 @@ description: Easily view your New Relic Dashboards in Backstage, via real-time s documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/newrelic/plugins/newrelic-dashboard/README.md iconUrl: https://newrelic.com/themes/custom/erno/assets/mediakit/new_relic_logo_vertical_white.svg npmPackageName: '@backstage-community/plugin-newrelic-dashboard' -tags: - - performance - - monitoring - - errors - - alerting - - dashboards addedDate: '2021-12-23' +status: active diff --git a/microsite/data/plugins/nexus-repository-manager.yaml b/microsite/data/plugins/nexus-repository-manager.yaml index 77b62c841d..5b7dca269d 100644 --- a/microsite/data/plugins/nexus-repository-manager.yaml +++ b/microsite/data/plugins/nexus-repository-manager.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/backstage/community-plugins/blob/main/workspac iconUrl: /img/nexus-repository-manager.svg npmPackageName: '@backstage-community/plugin-nexus-repository-manager' addedDate: '2023-10-25' +status: active diff --git a/microsite/data/plugins/nobl9.yaml b/microsite/data/plugins/nobl9.yaml index ee447fd626..b099b1dd79 100644 --- a/microsite/data/plugins/nobl9.yaml +++ b/microsite/data/plugins/nobl9.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/nobl9/nobl9-backstage-plugin/blob/main/README. iconUrl: /img/nobl9.svg npmPackageName: '@nobl9/nobl9-backstage-plugin' addedDate: '2023-12-15' +status: active diff --git a/microsite/data/plugins/npm.yaml b/microsite/data/plugins/npm.yaml index 67f3de976f..68e569e4ed 100644 --- a/microsite/data/plugins/npm.yaml +++ b/microsite/data/plugins/npm.yaml @@ -5,5 +5,7 @@ authorUrl: https://github.com/christoph-jerolimov category: Artifacts description: A plugin that shows meta info and latest versions from a npm registry. documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/npm/plugins/npm/README.md +iconUrl: '/img/logo-gradient-on-dark.svg' npmPackageName: '@backstage-community/plugin-npm' addedDate: '2024-11-29' +status: active diff --git a/microsite/data/plugins/ocm.yaml b/microsite/data/plugins/ocm.yaml index 1756029b27..38cc897339 100644 --- a/microsite/data/plugins/ocm.yaml +++ b/microsite/data/plugins/ocm.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/backstage/community-plugins/blob/main/workspac iconUrl: /img/ocm.svg npmPackageName: '@backstage-community/plugin-ocm' addedDate: '2023-05-15' +status: active diff --git a/microsite/data/plugins/octopus-deploy.yaml b/microsite/data/plugins/octopus-deploy.yaml index cfc5e2c9e5..90c3a009f3 100644 --- a/microsite/data/plugins/octopus-deploy.yaml +++ b/microsite/data/plugins/octopus-deploy.yaml @@ -6,5 +6,6 @@ category: CI/CD description: Easily view your Octopus Deploy releases within the Software Catalog documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/octopus-deploy/plugins/octopus-deploy/README.md iconUrl: /img/octopus-deploy.svg -npmPackageName: '@backstage/plugin-octopus-deploy' +npmPackageName: '@backstage-community/plugin-octopus-deploy' addedDate: '2023-02-24' +status: active diff --git a/microsite/data/plugins/okta-entity-providers.yaml b/microsite/data/plugins/okta-entity-providers.yaml index 4544c9eae7..55e88c1b12 100644 --- a/microsite/data/plugins/okta-entity-providers.yaml +++ b/microsite/data/plugins/okta-entity-providers.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/RoadieHQ/roadie-backstage-plugins/tree/main/pl iconUrl: https://roadie.io/images/logos/okta.png npmPackageName: '@roadiehq/catalog-backend-module-okta' addedDate: '2022-07-26' +status: active diff --git a/microsite/data/plugins/opa-permissions-wrapper.yaml b/microsite/data/plugins/opa-permissions-wrapper.yaml index 64af859dbc..f33c4d68c9 100644 --- a/microsite/data/plugins/opa-permissions-wrapper.yaml +++ b/microsite/data/plugins/opa-permissions-wrapper.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/Parsifal-M/backstage-opa-plugins/blob/main/plu iconUrl: /img/opapermlogo.png npmPackageName: '@parsifal-m/plugin-permission-backend-module-opa-wrapper' addedDate: '2024-02-26' +status: active diff --git a/microsite/data/plugins/open-dora.yaml b/microsite/data/plugins/open-dora.yaml index 16b4c63969..c3bc4880e3 100644 --- a/microsite/data/plugins/open-dora.yaml +++ b/microsite/data/plugins/open-dora.yaml @@ -8,3 +8,5 @@ documentation: https://github.com/DevoteamNL/opendora/tree/main/backstage-plugin iconUrl: /img/open-dora-icon.png npmPackageName: '@devoteam-nl/open-dora-backstage-plugin' addedDate: '2023-11-29' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/opencost.yaml b/microsite/data/plugins/opencost.yaml index 95973e3b0c..b8373106f4 100644 --- a/microsite/data/plugins/opencost.yaml +++ b/microsite/data/plugins/opencost.yaml @@ -6,5 +6,6 @@ category: Monitoring description: OpenCost provides cloud cost monitoring for your cloud native environments. documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/opencost/plugins/opencost/README.md iconUrl: /img/opencost.png -npmPackageName: '@backstage/plugin-opencost' +npmPackageName: '@backstage-community/plugin-opencost' addedDate: '2023-10-26' +status: active diff --git a/microsite/data/plugins/opsgenie.yaml b/microsite/data/plugins/opsgenie.yaml index 2a7818f7da..daedc8a967 100644 --- a/microsite/data/plugins/opsgenie.yaml +++ b/microsite/data/plugins/opsgenie.yaml @@ -7,8 +7,6 @@ description: Opsgenie offers a simple way to associate alerts to components and documentation: https://github.com/K-Phoen/backstage-plugin-opsgenie/ iconUrl: https://avatars.githubusercontent.com/u/1818843?s=200&v=4 npmPackageName: '@k-phoen/backstage-plugin-opsgenie' -tags: - - monitoring - - errors - - alerting addedDate: '2022-03-13' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/opslevel-maturity.yaml b/microsite/data/plugins/opslevel-maturity.yaml index 724f46ae17..5ed3378eb3 100644 --- a/microsite/data/plugins/opslevel-maturity.yaml +++ b/microsite/data/plugins/opslevel-maturity.yaml @@ -7,8 +7,6 @@ description: Integrate with OpsLevel to track performance against your engineeri documentation: https://github.com/OpsLevel/backstage-plugin iconUrl: https://avatars.githubusercontent.com/u/44910550?s=200&v=4 npmPackageName: backstage-plugin-opslevel-maturity -tags: - - service maturity - - service quality - - maturity score addedDate: '2022-12-07' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/ozone.yaml b/microsite/data/plugins/ozone.yaml index 9093c21ff0..8307ff6f24 100644 --- a/microsite/data/plugins/ozone.yaml +++ b/microsite/data/plugins/ozone.yaml @@ -9,3 +9,5 @@ documentation: https://docs.ozone.one/ozone-end-user-guide/~/changes/7YIrhOgqbpK iconUrl: /img/ozone-logo.png npmPackageName: '@ozonecloud/plugin-ozone' addedDate: '2023-06-14' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/pager-duty.yaml b/microsite/data/plugins/pager-duty.yaml index 7d603a37c1..4d0c42bf20 100644 --- a/microsite/data/plugins/pager-duty.yaml +++ b/microsite/data/plugins/pager-duty.yaml @@ -7,8 +7,5 @@ description: Bring the power of PagerDuty to Backstage, reduce cognitive load, i documentation: https://pagerduty.github.io/backstage-plugin-docs/index.html iconUrl: https://avatars2.githubusercontent.com/u/766800?s=200&v=4 npmPackageName: '@pagerduty/backstage-plugin' -tags: - - monitoring - - errors - - alerting addedDate: '2020-12-22' +status: active diff --git a/microsite/data/plugins/parseable-dataset.yaml b/microsite/data/plugins/parseable-dataset.yaml index 4624d3429f..11ab96c50c 100644 --- a/microsite/data/plugins/parseable-dataset.yaml +++ b/microsite/data/plugins/parseable-dataset.yaml @@ -6,6 +6,5 @@ description: View and explore telemetry datasets from Parseable directly inside documentation: https://github.com/parseablehq/backstage-plugin/blob/main/parseable-backstage-plugin/plugins/parseable-logstream/README.md iconUrl: https://raw.githubusercontent.com/parseablehq/.github/main/images/logo.svg npmPackageName: '@parseable/backstage-plugin-logstream' -tags: - - monitoring addedDate: '2025-07-14' +status: active diff --git a/microsite/data/plugins/periskop.yaml b/microsite/data/plugins/periskop.yaml index 51e9c37103..7865f83a01 100644 --- a/microsite/data/plugins/periskop.yaml +++ b/microsite/data/plugins/periskop.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/backstage/community-plugins/tree/main/workspac iconUrl: https://raw.githubusercontent.com/periskop-dev/periskop/master/docs/assets/periskop-logo.png npmPackageName: '@backstage-community/plugin-periskop' addedDate: '2022-02-25' +status: active diff --git a/microsite/data/plugins/playlist.yaml b/microsite/data/plugins/playlist.yaml index 2871fb904e..b978ce42b7 100644 --- a/microsite/data/plugins/playlist.yaml +++ b/microsite/data/plugins/playlist.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/backstage/community-plugins/tree/main/workspac iconUrl: /img/playlist-logo.png npmPackageName: '@backstage-community/plugin-playlist' addedDate: '2022-07-02' +status: active diff --git a/microsite/data/plugins/policy-reporter.yaml b/microsite/data/plugins/policy-reporter.yaml index cf8fddac10..b6f1da1833 100644 --- a/microsite/data/plugins/policy-reporter.yaml +++ b/microsite/data/plugins/policy-reporter.yaml @@ -7,3 +7,4 @@ documentation: https://github.com/kyverno/backstage-policy-reporter-plugin iconUrl: https://github.com/cncf/artwork/blob/main/projects/kyverno/icon/color/kyverno-icon-color.png?raw=true npmPackageName: '@kyverno/backstage-plugin-policy-reporter' addedDate: '2025-01-31' +status: active diff --git a/microsite/data/plugins/prometheus.yaml b/microsite/data/plugins/prometheus.yaml index f7a079d44c..45203c6f25 100644 --- a/microsite/data/plugins/prometheus.yaml +++ b/microsite/data/plugins/prometheus.yaml @@ -7,8 +7,5 @@ description: Prometheus plugin provides visualization of Prometheus metrics and documentation: https://roadie.io/backstage/plugins/prometheus/?utm_source=backstage.io&utm_medium=marketplace&utm_campaign=prometheus iconUrl: https://avatars.githubusercontent.com/u/3380462?s=200&v=4 npmPackageName: '@roadiehq/backstage-plugin-prometheus' -tags: - - monitoring - - graphs - - alerting addedDate: '2021-10-06' +status: active diff --git a/microsite/data/plugins/pulumi.yaml b/microsite/data/plugins/pulumi.yaml index 0177b7df0d..547a46c085 100644 --- a/microsite/data/plugins/pulumi.yaml +++ b/microsite/data/plugins/pulumi.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/pulumi/pulumi-backstage-plugin iconUrl: https://www.pulumi.com/logos/brand/avatar-on-white.png npmPackageName: '@pulumi/backstage-plugin-pulumi' addedDate: '2023-09-27' +status: active diff --git a/microsite/data/plugins/puppetdb.yaml b/microsite/data/plugins/puppetdb.yaml index 650d8318d3..1db8e5eef3 100644 --- a/microsite/data/plugins/puppetdb.yaml +++ b/microsite/data/plugins/puppetdb.yaml @@ -6,8 +6,6 @@ category: Configuration Management description: Visualize resource information and Puppet facts from PuppetDB. documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/puppetdb/plugins/puppetdb/README.md iconUrl: /img/puppet.png -npmPackageName: '@backstage/plugin-puppetdb' -tags: - - puppet - - puppetdb +npmPackageName: '@backstage-community/plugin-puppetdb' addedDate: '2023-04-06' +status: active diff --git a/microsite/data/plugins/qeta.yaml b/microsite/data/plugins/qeta.yaml index 0b9dc21cfd..ba3798f5c4 100644 --- a/microsite/data/plugins/qeta.yaml +++ b/microsite/data/plugins/qeta.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/drodil/backstage-plugin-qeta iconUrl: /img/qeta-logo.png npmPackageName: '@drodil/backstage-plugin-qeta' addedDate: '2022-12-21' +status: active diff --git a/microsite/data/plugins/quay.yaml b/microsite/data/plugins/quay.yaml index 9bcbf82de8..abe21e3a77 100644 --- a/microsite/data/plugins/quay.yaml +++ b/microsite/data/plugins/quay.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/backstage/community-plugins/blob/main/workspac iconUrl: /img/quay.svg npmPackageName: '@backstage-community/plugin-quay' addedDate: '2023-05-15' +status: active diff --git a/microsite/data/plugins/rafay.yaml b/microsite/data/plugins/rafay.yaml index 7aaf0d7681..5b092af229 100644 --- a/microsite/data/plugins/rafay.yaml +++ b/microsite/data/plugins/rafay.yaml @@ -8,3 +8,5 @@ documentation: https://docs.rafay.co/backstage/setup/ iconUrl: https://avatars.githubusercontent.com/u/33210764?s=200&v=4 npmPackageName: '@rafaysystems/backstage-plugin-rafay' addedDate: '2023-08-24' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/readme.yaml b/microsite/data/plugins/readme.yaml index c914efe355..a316a0c063 100644 --- a/microsite/data/plugins/readme.yaml +++ b/microsite/data/plugins/readme.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/AxisCommunications/backstage-plugins/blob/main iconUrl: https://raw.githubusercontent.com/AxisCommunications/backstage-plugins/main/plugins/readme/media/readme.png npmPackageName: '@axis-backstage/plugin-readme' addedDate: '2023-12-07' +status: active diff --git a/microsite/data/plugins/relations.yaml b/microsite/data/plugins/relations.yaml index 61ce052453..1601f8c8f5 100644 --- a/microsite/data/plugins/relations.yaml +++ b/microsite/data/plugins/relations.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/dweber019/backstage-plugins/tree/main/plugins/ iconUrl: https://raw.githubusercontent.com/dweber019/backstage-plugins/main/plugins/relations-backend/docs/pluginIcon.png npmPackageName: '@dweber019/backstage-plugin-relations' addedDate: '2024-05-15' +status: active diff --git a/microsite/data/plugins/renovate-hoster.yaml b/microsite/data/plugins/renovate-hoster.yaml index 4cf4f65956..1aea5776a8 100644 --- a/microsite/data/plugins/renovate-hoster.yaml +++ b/microsite/data/plugins/renovate-hoster.yaml @@ -5,5 +5,7 @@ authorUrl: https://github.com/secustor category: Quality description: This plugin enables you to host Renovate CLI yourself inside of Backstage and extract data from it. documentation: https://github.com/secustor/backstage-plugins/tree/main/plugins/renovate -npmPackageName: '@backstage/backstage-plugin-renovate' +iconUrl: '/img/logo-gradient-on-dark.svg' +npmPackageName: '@secustor/backstage-plugin-renovate' addedDate: '2025-03-31' +status: active diff --git a/microsite/data/plugins/revision.yaml b/microsite/data/plugins/revision.yaml index aeb0273602..0f8c994c60 100644 --- a/microsite/data/plugins/revision.yaml +++ b/microsite/data/plugins/revision.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/revision-org/backstage-plugins/tree/main/plugi iconUrl: https://raw.githubusercontent.com/revision-org/revision-assets/b4895ae036e99341576caf1c9bbf92ec89fb65dc/logo-black-full.svg npmPackageName: '@revisionapp/backstage-revision-plugin' addedDate: '2024-01-26' +status: active diff --git a/microsite/data/plugins/rollbar.yaml b/microsite/data/plugins/rollbar.yaml index 3777705b38..5a93122bc5 100644 --- a/microsite/data/plugins/rollbar.yaml +++ b/microsite/data/plugins/rollbar.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/backstage/community-plugins/tree/main/workspac iconUrl: https://cdn.rollbar.com/wp-content/themes/rollbar/assets/img/logo-white-rollbar.svg npmPackageName: '@backstage-community/plugin-rollbar' addedDate: '2020-11-03' +status: active diff --git a/microsite/data/plugins/rootly.yaml b/microsite/data/plugins/rootly.yaml index fa65e2d6ef..38d65fbdab 100644 --- a/microsite/data/plugins/rootly.yaml +++ b/microsite/data/plugins/rootly.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/rootlyhq/backstage-plugin/blob/master/README.m iconUrl: https://raw.githubusercontent.com/rootlyhq/backstage-plugin/master/docs/logo.png npmPackageName: '@rootly/backstage-plugin' addedDate: '2022-06-16' +status: active diff --git a/microsite/data/plugins/rulehub-policy-catalog.yaml b/microsite/data/plugins/rulehub-policy-catalog.yaml new file mode 100644 index 0000000000..3474ebe3db --- /dev/null +++ b/microsite/data/plugins/rulehub-policy-catalog.yaml @@ -0,0 +1,10 @@ +title: RuleHub Policy Catalog +author: Rulehub +authorUrl: https://github.com/rulehub +category: Security +description: Backstage frontend plugin that displays a policy/compliance catalog from a static JSON index. +documentation: https://github.com/rulehub/rulehub-backstage-plugin#readme +iconUrl: https://github.com/rulehub.png +npmPackageName: '@rulehub/rulehub-backstage-plugin' +addedDate: '2026-03-06' +status: active diff --git a/microsite/data/plugins/s3-viewer.yaml b/microsite/data/plugins/s3-viewer.yaml index 57d5b22ba1..1431fff309 100644 --- a/microsite/data/plugins/s3-viewer.yaml +++ b/microsite/data/plugins/s3-viewer.yaml @@ -7,6 +7,5 @@ description: Visualization of S3 buckets and their contents in file explorer sty documentation: https://github.com/spreadshirt/backstage-plugin-s3/ iconUrl: /img/s3-bucket.svg npmPackageName: '@spreadshirt/backstage-plugin-s3-viewer' -tags: - - s3 addedDate: '2023-01-18' +status: active diff --git a/microsite/data/plugins/scaffolder-backend-datolabs-gcp.yaml b/microsite/data/plugins/scaffolder-backend-datolabs-gcp.yaml index 528506c02d..8bdc5443ba 100644 --- a/microsite/data/plugins/scaffolder-backend-datolabs-gcp.yaml +++ b/microsite/data/plugins/scaffolder-backend-datolabs-gcp.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/datolabs-io/backstage-plugins/blob/main/worksp iconUrl: https://avatars1.githubusercontent.com/u/2810941?s=280&v=4 npmPackageName: '@datolabs/plugin-scaffolder-backend-module-gcp' addedDate: '2024-12-20' +status: active diff --git a/microsite/data/plugins/scaffolder-backend-dotnet.yaml b/microsite/data/plugins/scaffolder-backend-dotnet.yaml index 03fee737cb..eeb9076d36 100644 --- a/microsite/data/plugins/scaffolder-backend-dotnet.yaml +++ b/microsite/data/plugins/scaffolder-backend-dotnet.yaml @@ -8,3 +8,5 @@ documentation: https://github.com/alefcarlos/plusultra-dotnet-backstage-plugins/ iconUrl: /img/scaffolder-backend-dotnet-icon.png npmPackageName: '@plusultra/plugin-scaffolder-dotnet-backend' addedDate: '2022-01-24' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/scaffolder-backend-git.yaml b/microsite/data/plugins/scaffolder-backend-git.yaml index 4378b36a1e..8c71774bc5 100644 --- a/microsite/data/plugins/scaffolder-backend-git.yaml +++ b/microsite/data/plugins/scaffolder-backend-git.yaml @@ -8,3 +8,5 @@ documentation: https://github.com/arhill05/backstage-plugin-scaffolder-git-actio iconUrl: https://git-scm.com/images/logos/downloads/Git-Logo-2Color.png npmPackageName: '@mdude2314/backstage-plugin-scaffolder-git-actions' addedDate: '2022-05-13' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/scaffolder-backend-module-rails.yaml b/microsite/data/plugins/scaffolder-backend-module-rails.yaml index f740b95738..b41f9f52d9 100644 --- a/microsite/data/plugins/scaffolder-backend-module-rails.yaml +++ b/microsite/data/plugins/scaffolder-backend-module-rails.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/backstage/backstage/blob/master/plugins/scaffo iconUrl: /img/rails-icon.png npmPackageName: '@backstage/plugin-scaffolder-backend-module-rails' addedDate: '2021-06-24' +status: active diff --git a/microsite/data/plugins/scaffolder-backend-module-webex.yaml b/microsite/data/plugins/scaffolder-backend-module-webex.yaml index f2113b2a8d..c13d3eb61c 100644 --- a/microsite/data/plugins/scaffolder-backend-module-webex.yaml +++ b/microsite/data/plugins/scaffolder-backend-module-webex.yaml @@ -3,9 +3,10 @@ title: Cisco Webex Scaffolder Actions author: Robert Lindley authorUrl: https://github.com/Coderrob category: Scaffolder -description: >- - A Backstage.io plugin that enables sending Webex messages via - Incoming Webhooks within scaffolder templates. +description: A Backstage.io plugin that enables sending Webex messages via Incoming Webhooks within scaffolder templates. documentation: https://github.com/Coderrob/backstage-plugin-scaffolder-backend-module-webex +iconUrl: /img/logo-gradient-on-dark.svg npmPackageName: '@coderrob/backstage-plugin-scaffolder-backend-module-webex' addedDate: '2024-09-04' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/scaffolder-backend-ms-teams.yaml b/microsite/data/plugins/scaffolder-backend-ms-teams.yaml index 6d16b1300b..dd5f745bce 100644 --- a/microsite/data/plugins/scaffolder-backend-ms-teams.yaml +++ b/microsite/data/plugins/scaffolder-backend-ms-teams.yaml @@ -5,5 +5,7 @@ authorUrl: https://github.com/grvpandey11 category: Scaffolder description: Interact with Microsoft Teams from Scaffolder templates documentation: https://github.com/grvpandey11/backstage-plugin-scaffolder-backend-module-ms-teams/tree/main#readme +iconUrl: '/img/logo-gradient-on-dark.svg' npmPackageName: '@grvpandey11/backstage-plugin-scaffolder-backend-module-ms-teams' addedDate: '2023-08-11' +status: active diff --git a/microsite/data/plugins/scaffolder-backend-odo.yaml b/microsite/data/plugins/scaffolder-backend-odo.yaml index 954d51ecb2..7cf234f920 100644 --- a/microsite/data/plugins/scaffolder-backend-odo.yaml +++ b/microsite/data/plugins/scaffolder-backend-odo.yaml @@ -8,3 +8,5 @@ documentation: https://github.com/redhat-developer/backstage-odo-devfile-plugin/ iconUrl: https://odo.dev/img/logo.png npmPackageName: '@redhat-developer/plugin-scaffolder-odo-actions' addedDate: '2023-12-08' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/scaffolder-backend-roadie-aws.yaml b/microsite/data/plugins/scaffolder-backend-roadie-aws.yaml index 65808b9af2..2ffb66c518 100644 --- a/microsite/data/plugins/scaffolder-backend-roadie-aws.yaml +++ b/microsite/data/plugins/scaffolder-backend-roadie-aws.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/RoadieHQ/roadie-backstage-plugins/blob/main/pl iconUrl: https://upload.wikimedia.org/wikipedia/commons/9/93/Amazon_Web_Services_Logo.svg npmPackageName: '@roadiehq/scaffolder-backend-module-aws' addedDate: '2022-03-07' +status: active diff --git a/microsite/data/plugins/scaffolder-backend-roadie-http-request.yaml b/microsite/data/plugins/scaffolder-backend-roadie-http-request.yaml index e377f4d1f2..9835944617 100644 --- a/microsite/data/plugins/scaffolder-backend-roadie-http-request.yaml +++ b/microsite/data/plugins/scaffolder-backend-roadie-http-request.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/RoadieHQ/roadie-backstage-plugins/blob/main/pl iconUrl: /img/scaffolder-http-request-logo.svg npmPackageName: '@roadiehq/scaffolder-backend-module-http-request' addedDate: '2022-03-03' +status: active diff --git a/microsite/data/plugins/scaffolder-backend-roadie-utils.yaml b/microsite/data/plugins/scaffolder-backend-roadie-utils.yaml index dace41994f..aaa6cff4af 100644 --- a/microsite/data/plugins/scaffolder-backend-roadie-utils.yaml +++ b/microsite/data/plugins/scaffolder-backend-roadie-utils.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/RoadieHQ/roadie-backstage-plugins/blob/main/pl iconUrl: /img/scaffolder-utils-logo.png npmPackageName: '@roadiehq/scaffolder-backend-module-utils' addedDate: '2022-03-03' +status: active diff --git a/microsite/data/plugins/scaffolder-backend-slack.yaml b/microsite/data/plugins/scaffolder-backend-slack.yaml index a726751eaf..4f8bf0f15c 100644 --- a/microsite/data/plugins/scaffolder-backend-slack.yaml +++ b/microsite/data/plugins/scaffolder-backend-slack.yaml @@ -8,3 +8,5 @@ documentation: https://github.com/arhill05/backstage-plugin-scaffolder-backend-m iconUrl: /img/Slack-mark-RGB.png npmPackageName: '@mdude2314/backstage-plugin-scaffolder-backend-module-slack' addedDate: '2023-08-04' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/scaffolder-frontend-devfile-field.yaml b/microsite/data/plugins/scaffolder-frontend-devfile-field.yaml index 3215c63795..73a0d1d472 100644 --- a/microsite/data/plugins/scaffolder-frontend-devfile-field.yaml +++ b/microsite/data/plugins/scaffolder-frontend-devfile-field.yaml @@ -8,3 +8,5 @@ documentation: https://github.com/redhat-developer/backstage-odo-devfile-plugin/ iconUrl: https://landscape.cncf.io/logos/devfile.svg npmPackageName: '@redhat-developer/plugin-scaffolder-frontend-module-devfile-field' addedDate: '2023-12-08' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/scaleops.yaml b/microsite/data/plugins/scaleops.yaml index f11733b35f..cd027e6b53 100644 --- a/microsite/data/plugins/scaleops.yaml +++ b/microsite/data/plugins/scaleops.yaml @@ -7,3 +7,4 @@ documentation: https://github.com/TeraSky-OSS/backstage-plugins/tree/main/plugin iconUrl: https://avatars.githubusercontent.com/u/100293501?s=200&v=4 npmPackageName: '@terasky/backstage-plugin-scaleops-frontend' addedDate: '2024-12-30' +status: active diff --git a/microsite/data/plugins/scalr.yaml b/microsite/data/plugins/scalr.yaml index 63b2403d39..13cbd09083 100644 --- a/microsite/data/plugins/scalr.yaml +++ b/microsite/data/plugins/scalr.yaml @@ -8,3 +8,4 @@ documentation: 'https://github.com/Scalr/scalr-backstage-plugin/blob/main/README iconUrl: 'https://cdn.prod.website-files.com/63e6a81a3b7fbc072c4af4a7/63eab2d9d2e4cb583a3a7613_Scalr%20Full-Color%20Logo%20Dark.svg' npmPackageName: '@scalr-io/backstage-plugin-scalr' addedDate: '2025-04-15' +status: active diff --git a/microsite/data/plugins/score-card.yaml b/microsite/data/plugins/score-card.yaml index 48f619af2a..68b2908ff8 100644 --- a/microsite/data/plugins/score-card.yaml +++ b/microsite/data/plugins/score-card.yaml @@ -8,3 +8,5 @@ documentation: https://github.com/Oriflame/backstage-plugins/tree/main/plugins/s iconUrl: /img/score-card-plugin-logo.png npmPackageName: '@oriflame/backstage-plugin-score-card' addedDate: '2022-10-06' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/sds_workspace.yaml b/microsite/data/plugins/sds_workspace.yaml new file mode 100644 index 0000000000..cbe3c6f832 --- /dev/null +++ b/microsite/data/plugins/sds_workspace.yaml @@ -0,0 +1,11 @@ +--- +title: Citrix Secure Developer Spaces (SDS) +author: Citrix +authorUrl: https://github.com/strong-network +category: Development +description: Connect Backstage with Citrix Secure Developer Spaces (SDS) to view and create developer workspaces. +documentation: https://www.npmjs.com/package/@citrixcloud/backstage-sds-workspaces +iconUrl: /img/citrix_sds.png +npmPackageName: '@citrixcloud/backstage-sds-workspaces' +addedDate: '2025-10-07' +status: active diff --git a/microsite/data/plugins/search-backend-module-azure-devops-wiki.yaml b/microsite/data/plugins/search-backend-module-azure-devops-wiki.yaml index cc8849ee08..b727aeaed7 100644 --- a/microsite/data/plugins/search-backend-module-azure-devops-wiki.yaml +++ b/microsite/data/plugins/search-backend-module-azure-devops-wiki.yaml @@ -8,3 +8,5 @@ documentation: https://github.com/arhill05/backstage-plugin-search-backend-modul iconUrl: /img/ado-wiki-search-icon.png npmPackageName: '@mdude2314/backstage-plugin-search-backend-module-azure-devops-wiki' addedDate: '2023-06-13' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/search-backend-module-cognitive-search.yaml b/microsite/data/plugins/search-backend-module-cognitive-search.yaml index 128e5d9167..9fbbd61ae5 100644 --- a/microsite/data/plugins/search-backend-module-cognitive-search.yaml +++ b/microsite/data/plugins/search-backend-module-cognitive-search.yaml @@ -5,5 +5,7 @@ authorUrl: https://www.ap-com.co.jp/en/ category: Search description: Search extension plugin to create backstage search indexes into Azure Cognitive Search. documentation: https://github.com/ap-communications/platt-backstage-plugin/tree/main/plugins/search-backend-module-cognitive-search +iconUrl: '/img/logo-gradient-on-dark.svg' npmPackageName: '@platt/plugin-search-backend-module-cognitive-search' addedDate: '2023-09-13' +status: active diff --git a/microsite/data/plugins/security-insights.yaml b/microsite/data/plugins/security-insights.yaml index fd4a001e14..b0a14ce8c4 100644 --- a/microsite/data/plugins/security-insights.yaml +++ b/microsite/data/plugins/security-insights.yaml @@ -8,3 +8,4 @@ documentation: https://roadie.io/backstage/plugins/security-insights/?utm_source iconUrl: https://roadie.io/images/logos/github.png npmPackageName: '@roadiehq/backstage-plugin-security-insights' addedDate: '2021-04-20' +status: active diff --git a/microsite/data/plugins/sendgrid.yaml b/microsite/data/plugins/sendgrid.yaml index 11706dd9ed..89919d8a44 100644 --- a/microsite/data/plugins/sendgrid.yaml +++ b/microsite/data/plugins/sendgrid.yaml @@ -5,5 +5,7 @@ authorUrl: https://github.com/CodeVerse-GP category: Scaffolder description: A Backstage scaffolder action to send emails using SendGrid. documentation: https://github.com/CodeVerse-GP/scaffolder-backend-module-sendgrid#readme +iconUrl: '/img/logo-gradient-on-dark.svg' npmPackageName: '@codeverse-gp/scaffolder-backend-module-sendgrid' addedDate: '2025-04-18' +status: active diff --git a/microsite/data/plugins/sentry.yaml b/microsite/data/plugins/sentry.yaml index 06fb9a5d95..6fa36914eb 100644 --- a/microsite/data/plugins/sentry.yaml +++ b/microsite/data/plugins/sentry.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/backstage/community-plugins/tree/main/workspac iconUrl: https://sentry-brand.storage.googleapis.com/sentry-glyph-white.png npmPackageName: '@backstage-community/plugin-sentry' addedDate: '2020-11-03' +status: active diff --git a/microsite/data/plugins/shortcuts.yaml b/microsite/data/plugins/shortcuts.yaml index b420e28e2f..8be85b69be 100644 --- a/microsite/data/plugins/shortcuts.yaml +++ b/microsite/data/plugins/shortcuts.yaml @@ -6,5 +6,6 @@ category: Utility description: The shortcuts plugin allows a user to have easy access to pages within a Backstage app by storing them as "shortcuts" in the Sidebar. documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/shortcuts/plugins/shortcuts/README.md iconUrl: /img/shortcuts.svg -npmPackageName: '@backstage/plugin-shortcuts' +npmPackageName: '@backstage-community/plugin-shortcuts' addedDate: '2021-10-06' +status: active diff --git a/microsite/data/plugins/should-i-deploy.yaml b/microsite/data/plugins/should-i-deploy.yaml index 38e77c310f..4c2d8a5823 100644 --- a/microsite/data/plugins/should-i-deploy.yaml +++ b/microsite/data/plugins/should-i-deploy.yaml @@ -6,5 +6,7 @@ category: Humor description: Can I deploy this plugin if it's Friday? Oh, hell no!!! documentation: https://github.com/NandoRFS/backstage-plugin-should-i-deploy/ iconUrl: /img/should-i-deploy-logo.png -npmPackageName: 'backstage-plugin-should-i-deploy' +npmPackageName: backstage-plugin-should-i-deploy addedDate: '2023-11-05' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/signadot.yaml b/microsite/data/plugins/signadot.yaml index 9b6b162901..d981b8390e 100644 --- a/microsite/data/plugins/signadot.yaml +++ b/microsite/data/plugins/signadot.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/signadot/backstage-plugin iconUrl: https://avatars.githubusercontent.com/u/57196635 npmPackageName: '@signadot-lib/backstage-plugin' addedDate: '2025-07-09' +status: active diff --git a/microsite/data/plugins/simple-icons.yaml b/microsite/data/plugins/simple-icons.yaml index 45722ab421..460e0a9a33 100644 --- a/microsite/data/plugins/simple-icons.yaml +++ b/microsite/data/plugins/simple-icons.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/dweber019/backstage-plugins/tree/main/plugins/ iconUrl: https://raw.githubusercontent.com/dweber019/backstage-plugins/main/plugins/simple-icons/docs/pluginIcon.png npmPackageName: '@dweber019/backstage-plugin-simple-icons' addedDate: '2024-07-30' +status: active diff --git a/microsite/data/plugins/snyk-security.yaml b/microsite/data/plugins/snyk-security.yaml index 8c22d44510..aa94e196f8 100644 --- a/microsite/data/plugins/snyk-security.yaml +++ b/microsite/data/plugins/snyk-security.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/snyk-tech-services/backstage-plugin-snyk/blob/ iconUrl: https://github.com/snyk-tech-services/backstage-plugin-snyk/assets/109112986/95eb1b06-b3e8-4910-9233-11926e02fa18 npmPackageName: 'backstage-plugin-snyk' addedDate: '2021-01-22' +status: active diff --git a/microsite/data/plugins/solo-io-gloo-platform-portal.yaml b/microsite/data/plugins/solo-io-gloo-platform-portal.yaml index 2f17b84f17..b3101402ec 100644 --- a/microsite/data/plugins/solo-io-gloo-platform-portal.yaml +++ b/microsite/data/plugins/solo-io-gloo-platform-portal.yaml @@ -8,3 +8,5 @@ documentation: https://github.com/solo-io/backstage-plugins-overview#readme iconUrl: /img/solo-io-glooy-circle.png.webp npmPackageName: '@solo.io/dev-portal-backstage-plugin' addedDate: '2023-05-19' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/sonarqube.yaml b/microsite/data/plugins/sonarqube.yaml index 72c662e2fa..447876a559 100644 --- a/microsite/data/plugins/sonarqube.yaml +++ b/microsite/data/plugins/sonarqube.yaml @@ -6,5 +6,6 @@ category: Quality description: Components to display code quality metrics from SonarCloud and SonarQube. documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/sonarqube/plugins/sonarqube/README.md iconUrl: /img/sonarqube-icon.svg -npmPackageName: '@backstage/plugin-sonarqube' +npmPackageName: '@backstage-community/plugin-sonarqube' addedDate: '2020-11-03' +status: active diff --git a/microsite/data/plugins/spacelift-io.yaml b/microsite/data/plugins/spacelift-io.yaml index fe9b48139f..0f39893c49 100644 --- a/microsite/data/plugins/spacelift-io.yaml +++ b/microsite/data/plugins/spacelift-io.yaml @@ -8,3 +8,4 @@ documentation: https://docs.spacelift.io/integrations/external-integrations/back iconUrl: https://avatars.githubusercontent.com/u/53318513?s=200&v=4 npmPackageName: '@spacelift-io/backstage-integration-frontend' addedDate: '2025-06-10' +status: active diff --git a/microsite/data/plugins/splunk-on-call.yaml b/microsite/data/plugins/splunk-on-call.yaml index 0eb0ad14da..e60653828d 100644 --- a/microsite/data/plugins/splunk-on-call.yaml +++ b/microsite/data/plugins/splunk-on-call.yaml @@ -7,9 +7,5 @@ description: Splunk On-Call offers a simple way to identify incidents and escala documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/splunk/plugins/splunk-on-call iconUrl: /img/splunk-black-white-bg.png npmPackageName: '@backstage-community/plugin-splunk-on-call' -tags: - - monitoring - - errors - - alerting - - splunk addedDate: '2021-11-17' +status: active diff --git a/microsite/data/plugins/spring-batch-dashboard.yaml b/microsite/data/plugins/spring-batch-dashboard.yaml new file mode 100644 index 0000000000..ff5885ffc7 --- /dev/null +++ b/microsite/data/plugins/spring-batch-dashboard.yaml @@ -0,0 +1,11 @@ +--- +title: Spring Batch Dashboard +author: zc149 +authorUrl: https://github.com/zc149 +category: Monitoring +description: Monitor and manage Spring Batch jobs from Backstage. +documentation: https://github.com/zc149/backstage-plugin-spring-batch-dashboard +iconUrl: https://raw.githubusercontent.com/zc149/backstage-plugin-spring-batch-dashboard/main/.github/images/icon.png +npmPackageName: '@jikwan/backstage-plugin-spring-batch-dashboard' +addedDate: '2026-03-11' +status: active diff --git a/microsite/data/plugins/stack-overflow.yaml b/microsite/data/plugins/stack-overflow.yaml index b058ce90fe..e48be6e3b9 100644 --- a/microsite/data/plugins/stack-overflow.yaml +++ b/microsite/data/plugins/stack-overflow.yaml @@ -6,5 +6,6 @@ category: Discovery description: Provides Stack Overflow specific functionality that can be used in different ways (e.g. for homepage and search) to compose your Backstage App. documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/stack-overflow/plugins/stack-overflow/README.md iconUrl: /img/stack-overflow-logo.svg -npmPackageName: '@backstage/plugin-stack-overflow' +npmPackageName: '@backstage-community/plugin-stack-overflow' addedDate: '2022-06-14' +status: active diff --git a/microsite/data/plugins/stackoverflow-teams.yaml b/microsite/data/plugins/stackoverflow-teams.yaml index 262a6e9c69..99ebfd1fa6 100644 --- a/microsite/data/plugins/stackoverflow-teams.yaml +++ b/microsite/data/plugins/stackoverflow-teams.yaml @@ -8,3 +8,4 @@ documentation: https://stackoverflowteams.help/en/articles/9692515-backstage-io- iconUrl: /img/stack-overflow-logo.svg npmPackageName: '@stackoverflow/backstage-plugin-stack-overflow-teams' addedDate: '2025-06-10' +status: active diff --git a/microsite/data/plugins/stackstorm.yaml b/microsite/data/plugins/stackstorm.yaml index d264f530f9..9eb81f7bc1 100644 --- a/microsite/data/plugins/stackstorm.yaml +++ b/microsite/data/plugins/stackstorm.yaml @@ -7,8 +7,5 @@ description: Manage StackStorm workflow executions from within Backstage. documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/stackstorm/plugins/stackstorm iconUrl: /img/stackstorm.png npmPackageName: '@backstage-community/plugin-stackstorm' -tags: - - stackstorm - - st2 - - automation - - workflow +addedDate: '2023-02-02' +status: active diff --git a/microsite/data/plugins/statusneo-github-plugin.yaml b/microsite/data/plugins/statusneo-github-plugin.yaml index 2497a99c1b..25eb8aea24 100644 --- a/microsite/data/plugins/statusneo-github-plugin.yaml +++ b/microsite/data/plugins/statusneo-github-plugin.yaml @@ -1,3 +1,4 @@ +--- title: Statusneo GitHub Plugin author: Statusneo authorUrl: https://statusneo.com/ @@ -6,8 +7,6 @@ description: Provides status cards GitHub pull requests and GitHub actions. documentation: https://github.com/StatusNeo/backstage-plugin-github iconUrl: /img/github-statusneo.png npmPackageName: '@statusneo/backstage-plugin-github' -tags: - - GitHub - - Monitoring - - Productivity addedDate: '2023-02-15' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/statuspage.yaml b/microsite/data/plugins/statuspage.yaml index d5402c8ea3..7bc9cc1641 100644 --- a/microsite/data/plugins/statuspage.yaml +++ b/microsite/data/plugins/statuspage.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/AxisCommunications/backstage-plugins/blob/main iconUrl: https://raw.githubusercontent.com/AxisCommunications/backstage-plugins/main/plugins/statuspage/media/logo.png npmPackageName: '@axis-backstage/plugin-statuspage' addedDate: '2024-02-06' +status: active diff --git a/microsite/data/plugins/synergy.yaml b/microsite/data/plugins/synergy.yaml index 6c5e722322..c47dd28653 100644 --- a/microsite/data/plugins/synergy.yaml +++ b/microsite/data/plugins/synergy.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/jiteshy/backstage-plugin-synergy/blob/main/REA iconUrl: /img/synergy-logo.png npmPackageName: '@jiteshy/backstage-plugin-synergy' addedDate: '2024-12-03' +status: active diff --git a/microsite/data/plugins/sysdig.yaml b/microsite/data/plugins/sysdig.yaml index 220f4468b2..ab1bfb92bb 100644 --- a/microsite/data/plugins/sysdig.yaml +++ b/microsite/data/plugins/sysdig.yaml @@ -8,3 +8,5 @@ documentation: https://github.com/sysdiglabs/backstage-plugin-sysdig/blob/main/R iconUrl: https://sysdig.com/wp-content/uploads/sysdig-logo-new-white.svg npmPackageName: '@sysdig/backstage-plugin-sysdig' addedDate: '2024-03-06' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/targetboard.yaml b/microsite/data/plugins/targetboard.yaml index 2cac367eec..0077fbbbbe 100644 --- a/microsite/data/plugins/targetboard.yaml +++ b/microsite/data/plugins/targetboard.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/targetboard/backstage-plugin/blob/main/README. iconUrl: https://app.targetboard.ai/assets/TargetBoard-Backstage-logo.svg npmPackageName: '@targetboard/backstage-plugin' addedDate: '2025-11-02' +status: active diff --git a/microsite/data/plugins/tech-insights.yaml b/microsite/data/plugins/tech-insights.yaml index 3bd40d576a..971f72e096 100644 --- a/microsite/data/plugins/tech-insights.yaml +++ b/microsite/data/plugins/tech-insights.yaml @@ -7,9 +7,5 @@ description: Visualize, understand and optimize your team's tech health. documentation: https://roadie.io/backstage/plugins/tech-insights/ iconUrl: https://roadie.io/images/logos/tech-insights.png npmPackageName: '@backstage-community/plugin-tech-insights' -tags: - - tech-insights - - reporting - - tech health - - migrations addedDate: '2022-03-31' +status: active diff --git a/microsite/data/plugins/tech-radar.yaml b/microsite/data/plugins/tech-radar.yaml index 5db68eef27..35c7432931 100644 --- a/microsite/data/plugins/tech-radar.yaml +++ b/microsite/data/plugins/tech-radar.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/backstage/community-plugins/tree/main/workspac iconUrl: /img/tech-radar.svg npmPackageName: '@backstage-community/plugin-tech-radar' addedDate: '2020-11-03' +status: active diff --git a/microsite/data/plugins/tekton-pipelines.yaml b/microsite/data/plugins/tekton-pipelines.yaml index fe19ab2af2..d29e651ee1 100644 --- a/microsite/data/plugins/tekton-pipelines.yaml +++ b/microsite/data/plugins/tekton-pipelines.yaml @@ -8,3 +8,5 @@ documentation: https://github.com/jquad-group/backstage-jquad#readme iconUrl: https://raw.githubusercontent.com/jquad-group/backstage-jquad/main/img/tekton-horizontal-color.png npmPackageName: '@jquad-group/plugin-tekton-pipelines' addedDate: '2022-08-08' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/tekton.yaml b/microsite/data/plugins/tekton.yaml index 71aa433a83..346d472a47 100644 --- a/microsite/data/plugins/tekton.yaml +++ b/microsite/data/plugins/tekton.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/backstage/community-plugins/blob/main/workspac iconUrl: /img/tekton.svg npmPackageName: '@backstage-community/plugin-tekton' addedDate: '2023-05-15' +status: active diff --git a/microsite/data/plugins/template-designer.yaml b/microsite/data/plugins/template-designer.yaml new file mode 100644 index 0000000000..9146da018a --- /dev/null +++ b/microsite/data/plugins/template-designer.yaml @@ -0,0 +1,11 @@ +--- +title: Template Designer +author: tduniec +authorUrl: https://github.com/tduniec +category: Productivity +description: Template Designer turns blank Backstage YAML into a storyboard-like canvas, guiding anyone through drag-and-drop scaffolder authoring before ever touching code. Rally non-experts, broadcast best practices, and accelerate template launches directly inside Backstage. +documentation: https://github.com/tduniec/template-designer-plugin/blob/main/README.md +iconUrl: https://raw.githubusercontent.com/tduniec/template-designer-plugin/main/img/logo/templateDesignerLogo.png +npmPackageName: '@tduniec/plugin-template-designer' +addedDate: '2025-11-19' +status: active diff --git a/microsite/data/plugins/time-saver.yaml b/microsite/data/plugins/time-saver.yaml index 02f088ffa1..cac5f3f285 100644 --- a/microsite/data/plugins/time-saver.yaml +++ b/microsite/data/plugins/time-saver.yaml @@ -7,4 +7,6 @@ description: Visualise your time saved using Backstage Scaffolder templates documentation: https://github.com/tduniec/backstage-timesaver-plugin/blob/main/README.md iconUrl: https://raw.githubusercontent.com/tduniec/backstage-timesaver-plugin/main/img/tsLogo.png npmPackageName: '@tduniec/backstage-plugin-time-saver' -addedDate: 2024-01-30 +addedDate: '2024-01-30' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/tips.yaml b/microsite/data/plugins/tips.yaml index 03327f3e41..7650c9d868 100644 --- a/microsite/data/plugins/tips.yaml +++ b/microsite/data/plugins/tips.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/dweber019/backstage-plugins/tree/main/plugins/ iconUrl: https://raw.githubusercontent.com/dweber019/backstage-plugins/main/plugins/tips/docs/pluginIcon.png npmPackageName: '@dweber019/backstage-plugin-tips' addedDate: '2023-10-08' +status: active diff --git a/microsite/data/plugins/todo.yaml b/microsite/data/plugins/todo.yaml index ab353e8150..dc6366b6de 100644 --- a/microsite/data/plugins/todo.yaml +++ b/microsite/data/plugins/todo.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/backstage/community-plugins/tree/main/workspac iconUrl: https://backstage.io/img/todo-logo.png npmPackageName: '@backstage-community/plugin-todo' addedDate: '2021-03-16' +status: active diff --git a/microsite/data/plugins/toolbox.yaml b/microsite/data/plugins/toolbox.yaml index b488b24369..b115b56759 100644 --- a/microsite/data/plugins/toolbox.yaml +++ b/microsite/data/plugins/toolbox.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/drodil/backstage-plugin-toolbox iconUrl: /img/toolbox-logo.png npmPackageName: '@drodil/backstage-plugin-toolbox' addedDate: '2023-01-09' +status: active diff --git a/microsite/data/plugins/topology.yaml b/microsite/data/plugins/topology.yaml index f1b74ef45b..724ee9c0b9 100644 --- a/microsite/data/plugins/topology.yaml +++ b/microsite/data/plugins/topology.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/backstage/community-plugins/blob/main/workspac iconUrl: /img/topology.svg npmPackageName: '@backstage-community/plugin-topology' addedDate: '2023-05-15' +status: active diff --git a/microsite/data/plugins/torque.yaml b/microsite/data/plugins/torque.yaml index 1480c3bad9..705ab72dc4 100644 --- a/microsite/data/plugins/torque.yaml +++ b/microsite/data/plugins/torque.yaml @@ -1,12 +1,14 @@ --- title: Torque author: Quali -authorUrl: 'https://www.qtorque.io/' +authorUrl: https://www.qtorque.io/ category: Orchestration description: | Deploy the environments you need to keep building, testing and delivering incredible code. Plugin includes an Entity ComponentCard, Backend API route and scaffolder actions. documentation: https://github.com/QualiTorque/torque-backstage-plugin/tree/main/packages/torque#readme iconUrl: https://user-images.githubusercontent.com/8643801/214640977-751bc338-6b77-40a0-a897-cba41b6f006a.png -npmPackageName: '@qtorque/backstage-plugin-torque' +npmPackageName: '@qtorque/backstage-torque-plugin' addedDate: '2023-01-25' +status: inactive +staleSince: '2026-02-17' diff --git a/microsite/data/plugins/travis-ci.yaml b/microsite/data/plugins/travis-ci.yaml index a5cec13d22..7adf3565e8 100644 --- a/microsite/data/plugins/travis-ci.yaml +++ b/microsite/data/plugins/travis-ci.yaml @@ -8,3 +8,4 @@ documentation: https://roadie.io/backstage/plugins/travis-ci/?utm_source=backsta iconUrl: https://roadie.io/images/logos/travis.png npmPackageName: '@roadiehq/backstage-plugin-travis-ci' addedDate: '2021-04-20' +status: active diff --git a/microsite/data/plugins/umami.yaml b/microsite/data/plugins/umami.yaml index 5f1fc208d4..0a3559d710 100644 --- a/microsite/data/plugins/umami.yaml +++ b/microsite/data/plugins/umami.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/AxisCommunications/backstage-plugins/blob/main iconUrl: /img/umami-logo.svg npmPackageName: '@axis-backstage/plugin-analytics-module-umami' addedDate: '2024-02-05' +status: active diff --git a/microsite/data/plugins/usage-statistics.yaml b/microsite/data/plugins/usage-statistics.yaml index d32fb54ac4..44d6ddb5f4 100644 --- a/microsite/data/plugins/usage-statistics.yaml +++ b/microsite/data/plugins/usage-statistics.yaml @@ -5,5 +5,7 @@ authorUrl: https://github.com/CodeVerse-GP category: Monitoring description: Shows usage statistics for scaffolder templates documentation: https://github.com/CodeVerse-GP/usage-statistics/blob/main/README.md +iconUrl: '/img/logo-gradient-on-dark.svg' npmPackageName: '@codeverse-gp/plugin-usage-statistics' addedDate: '2025-11-05' +status: active diff --git a/microsite/data/plugins/vault.yaml b/microsite/data/plugins/vault.yaml index 1ee14c4bd3..30a5f5f227 100644 --- a/microsite/data/plugins/vault.yaml +++ b/microsite/data/plugins/vault.yaml @@ -7,6 +7,5 @@ description: Visualize a list of the secrets stored in your HashiCorp Vault inst documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/vault/plugins/vault iconUrl: /img/vault.png npmPackageName: '@backstage-community/plugin-vault' -tags: - - vault addedDate: '2022-06-03' +status: active diff --git a/microsite/data/plugins/wheel-of-names.yaml b/microsite/data/plugins/wheel-of-names.yaml index 0377742ab0..6376d41c59 100644 --- a/microsite/data/plugins/wheel-of-names.yaml +++ b/microsite/data/plugins/wheel-of-names.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/backstage/community-plugins/blob/main/workspac iconUrl: /img/wheel-of-names.svg npmPackageName: '@backstage-community/plugin-wheel-of-names' addedDate: '2025-05-21' +status: active diff --git a/microsite/data/plugins/wiz.yaml b/microsite/data/plugins/wiz.yaml index 1496231dfa..672e72d3f7 100644 --- a/microsite/data/plugins/wiz.yaml +++ b/microsite/data/plugins/wiz.yaml @@ -8,3 +8,4 @@ documentation: https://roadie.io/backstage/plugins/wiz/ iconUrl: https://roadie.io/images/wiz-logo.png npmPackageName: '@roadiehq/backstage-plugin-wiz' addedDate: '2024-10-14' +status: active diff --git a/microsite/data/plugins/xcmetrics.yaml b/microsite/data/plugins/xcmetrics.yaml index f02b195eb5..dde1b88ea4 100644 --- a/microsite/data/plugins/xcmetrics.yaml +++ b/microsite/data/plugins/xcmetrics.yaml @@ -6,5 +6,6 @@ category: Monitoring description: Discover valuable insights hiding inside Xcode’s build logs. documentation: https://xcmetrics.io/ iconUrl: /img/xcmetrics-icon.png -npmPackageName: '@backstage/plugin-xcmetrics' +npmPackageName: '@backstage-community/plugin-xcmetrics' addedDate: '2021-08-06' +status: active diff --git a/microsite/data/plugins/xkcd.yaml b/microsite/data/plugins/xkcd.yaml index 370718a08f..e01e277bd4 100644 --- a/microsite/data/plugins/xkcd.yaml +++ b/microsite/data/plugins/xkcd.yaml @@ -8,3 +8,4 @@ documentation: https://github.com/Vity01/backstage-xkcd/ iconUrl: https://raw.githubusercontent.com/Vity01/backstage-xkcd/main/docs/xkcd.svg npmPackageName: 'backstage-plugin-xkcd' addedDate: '2023-05-05' +status: active diff --git a/microsite/docusaurus.config.ts b/microsite/docusaurus.config.ts index 850e6058af..3a47b62627 100644 --- a/microsite/docusaurus.config.ts +++ b/microsite/docusaurus.config.ts @@ -19,6 +19,8 @@ /** @type{import('prism-react-renderer').PrismTheme} **/ // @ts-ignore import { themes } from 'prism-react-renderer'; +import { cpSync, existsSync } from 'node:fs'; +import { resolve as resolvePath } from 'node:path'; import type * as Preset from '@docusaurus/preset-classic'; import { Config } from '@docusaurus/types'; import RedirectPlugin from '@docusaurus/plugin-client-redirects'; @@ -28,7 +30,9 @@ import type * as OpenApiPlugin from 'docusaurus-plugin-openapi-docs'; const backstageTheme = themes.vsDark; backstageTheme.plain.backgroundColor = '#232323'; -const useVersionedDocs = require('fs').existsSync('versions.json'); +const useVersionedDocs = existsSync('versions.json'); +const wellKnownDocsPath = resolvePath(__dirname, '../docs/.well-known'); +const wellKnownPublicPath = '/.well-known'; // This patches the redirect plugin to ignore the error when it tries to override existing fields. // This lets us add redirects that only apply to the next docs, while the stable docs still contain the source path. @@ -63,11 +67,63 @@ const defaultOpenApiOptions = { }, } satisfies OpenApiPlugin.Options; +const seoDescription = + 'Backstage is an open source developer portal framework that centralizes your software catalog, unifies infrastructure tools, and helps teams ship high-quality code faster.'; + +const seoKeywords = [ + 'Backstage', + 'developer portal', + 'internal developer platform', + 'platform engineering', + 'software catalog', + 'software templates', + 'TechDocs', + 'developer experience', + 'IDP', + 'open source', +]; + const config: Config = { title: 'Backstage Software Catalog and Developer Platform', tagline: 'An open source framework for building developer portals', url: 'https://backstage.io', baseUrl: '/', + headTags: [ + { + tagName: 'link', + attributes: { + rel: 'preconnect', + href: 'https://fonts.googleapis.com', + }, + }, + { + tagName: 'link', + attributes: { + rel: 'preconnect', + href: 'https://fonts.gstatic.com', + crossOrigin: 'anonymous', + }, + }, + { + tagName: 'script', + attributes: { + type: 'application/ld+json', + }, + innerHTML: JSON.stringify({ + '@context': 'https://schema.org', + '@type': 'WebSite', + name: 'Backstage', + url: 'https://backstage.io', + description: seoDescription, + image: 'https://backstage.io/img/sharing-opengraph.png', + publisher: { + '@type': 'Organization', + name: 'Spotify', + url: 'https://spotify.github.io/', + }, + }), + }, + ], organizationName: 'Spotify', projectName: 'backstage', scripts: [ @@ -86,7 +142,23 @@ const config: Config = { repoUrl: 'https://github.com/backstage/backstage', }, onBrokenLinks: 'log', - onBrokenMarkdownLinks: 'log', + future: { + v4: { + removeLegacyPostBuildHeadAttribute: true, + }, + experimental_faster: { + swcJsLoader: true, + swcJsMinimizer: true, + lightningCssMinimizer: true, + rspackBundler: true, + mdxCrossCompilerCache: true, + rspackPersistentCache: true, + // TODO: React has an issue with server rendering here. + // ssgWorkerThreads: true, + // TODO: This prints extra warnings in the console, add back when we have a fix. + // swcHtmlMinimizer: true, + }, + }, presets: [ [ '@docusaurus/preset-classic', @@ -152,26 +224,24 @@ const config: Config = { return removeHtmlComments(fileContent); }, format: 'detect', - }, - webpack: { - jsLoader: isServer => ({ - loader: require.resolve('swc-loader'), - options: { - jsc: { - parser: { - syntax: 'typescript', - tsx: true, - }, - target: 'es2017', - }, - module: { - type: isServer ? 'commonjs' : 'es6', - }, - }, - }), + hooks: { + onBrokenMarkdownLinks: 'log', + }, }, plugins: [ 'docusaurus-plugin-sass', + function disableExpensiveBundlerOptimizationPlugin() { + return { + name: 'disable-expensive-bundler-optimizations', + configureWebpack(_config) { + return { + optimization: { + concatenateModules: false, + }, + }; + }, + }; + }, () => ({ name: 'yaml-loader', configureWebpack() { @@ -187,6 +257,40 @@ const config: Config = { }; }, }), + () => ({ + name: 'publish-well-known-docs', + getPathsToWatch() { + return [wellKnownDocsPath]; + }, + configureWebpack() { + if (!existsSync(wellKnownDocsPath)) { + return undefined; + } + + return { + devServer: { + static: [ + { + publicPath: wellKnownPublicPath, + directory: wellKnownDocsPath, + staticOptions: { + dotfiles: 'allow', + }, + }, + ], + }, + }; + }, + async postBuild({ outDir }: { outDir: string }) { + if (!existsSync(wellKnownDocsPath)) { + return; + } + + cpSync(wellKnownDocsPath, resolvePath(outDir, '.well-known'), { + recursive: true, + }); + }, + }), ctx => PatchedRedirectPlugin(ctx, { id: '@docusaurus/plugin-client-redirects', @@ -223,7 +327,7 @@ const config: Config = { }, { from: '/docs/features/software-templates/testing-scaffolder-alpha', - to: '/docs/features/software-templates/migrating-to-rjsf-v5', + to: '/docs/features/software-templates/', }, { from: '/docs/auth/glossary', @@ -273,6 +377,10 @@ const config: Config = { from: '/docs/getting-started/app-custom-theme', to: '/docs/conf/user-interface', }, + { + from: '/docs/plugins/existing-plugins', + to: '/docs/plugins/', + }, ], }), [ @@ -312,6 +420,28 @@ const config: Config = { ], themes: ['docusaurus-theme-openapi-docs'], themeConfig: { + metadata: [ + { + name: 'description', + content: seoDescription, + }, + { + name: 'keywords', + content: seoKeywords.join(', '), + }, + { + property: 'og:site_name', + content: 'Backstage', + }, + { + property: 'og:type', + content: 'website', + }, + { + name: 'twitter:card', + content: 'summary_large_image', + }, + ], languageTabs: [ { highlight: 'javascript', @@ -362,16 +492,6 @@ const config: Config = { srcDark: 'img/logo.svg', }, items: [ - { - href: 'https://github.com/backstage/backstage', - label: 'GitHub', - position: 'left', - }, - { - href: 'https://discord.gg/backstage-687207715902193673', - label: 'Discord', - position: 'left', - }, { to: 'docs/overview/what-is-backstage', label: 'Docs', @@ -383,15 +503,32 @@ const config: Config = { position: 'left', }, { - to: '/blog', - label: 'Blog', + type: 'dropdown', + label: 'Reference', position: 'left', + items: [ + { + label: `Stable (${releases[0]})`, + href: 'https://backstage.io/api/stable', + target: '_self', + }, + { + label: 'Next', + href: 'https://backstage.io/api/next', + target: '_self', + }, + ], }, { to: `docs/releases/${releases[0]}`, label: 'Releases', position: 'left', }, + { + to: '/blog', + label: 'Blog', + position: 'left', + }, { to: '/demos', label: 'Demos', @@ -402,6 +539,20 @@ const config: Config = { label: 'Community', position: 'left', }, + { + href: 'https://github.com/backstage/backstage', + position: 'right', + className: 'header-github-link', + 'aria-label': 'GitHub repository', + title: 'GitHub repository', + }, + { + href: 'https://discord.gg/backstage-687207715902193673', + position: 'right', + className: 'header-discord-link', + 'aria-label': 'Discord community', + title: 'Discord community', + }, ...(useVersionedDocs ? [ { @@ -413,6 +564,13 @@ const config: Config = { ], }, image: 'img/sharing-opengraph.png', + metadata: [ + { + name: 'description', + content: + 'Backstage is an open source developer portal framework that centralizes your software catalog, unifies infrastructure tools, and helps teams ship high-quality code faster.', + }, + ], footer: { links: [ { @@ -467,7 +625,7 @@ const config: Config = { }, { label: 'Subscribe to our newsletter', - to: 'https://info.backstage.spotify.com/newsletter_subscribe', + to: 'https://spoti.fi/backstagenewsletter', }, { label: 'CNCF Incubation', diff --git a/microsite/package.json b/microsite/package.json index a042a23286..51e1284668 100644 --- a/microsite/package.json +++ b/microsite/package.json @@ -4,7 +4,7 @@ "private": true, "license": "Apache-2.0", "scripts": { - "build": "node scripts/pre-build.js && docusaurus build", + "build": "docusaurus build", "deploy": "docusaurus deploy", "docusaurus": "docusaurus", "generate-openapi-docs": "yarn docusaurus clean-api-docs all && yarn docusaurus gen-api-docs all", @@ -12,7 +12,7 @@ "prettier:fix": "prettier --write .", "publish-gh-pages": "docusaurus-publish", "rename-version": "docusaurus-rename-version", - "start": "node scripts/pre-build.js && docusaurus start", + "start": "docusaurus start", "swizzle": "docusaurus swizzle", "verify:sidebars": "node ./scripts/verify-sidebars", "version": "docusaurus-version", @@ -20,15 +20,18 @@ }, "prettier": "@backstage/cli/config/prettier", "resolutions": { + "@swc/core": "1.15.18", + "@swc/html": "1.15.18", "node-polyfill-webpack-plugin": "^3.0.0" }, "dependencies": { - "@docusaurus/core": "^3.1.1", - "@docusaurus/plugin-client-redirects": "^3.1.1", - "@docusaurus/preset-classic": "^3.1.1", - "@docusaurus/types": "^3.1.1", + "@docusaurus/core": "^3.9.0", + "@docusaurus/faster": "^3.9.0", + "@docusaurus/plugin-client-redirects": "^3.9.0", + "@docusaurus/preset-classic": "^3.9.0", + "@docusaurus/types": "^3.9.0", "@mdx-js/react": "^3.0.0", - "@swc/core": "^1.3.46", + "@swc/core": "^1.15.6", "clsx": "^2.0.0", "docusaurus-plugin-openapi-docs": "^4.3.0", "docusaurus-plugin-sass": "^0.2.3", @@ -44,11 +47,10 @@ "devDependencies": { "@docusaurus/module-type-aliases": "^3.1.1", "@docusaurus/tsconfig": "^3.1.1", - "@types/luxon": "^3.0.0", "@types/webpack-env": "^1.18.0", - "js-yaml": "^4.1.0", + "js-yaml": "^4.1.1", "prettier": "^2.6.2", - "typescript": "~5.2.0", - "yaml-loader": "^0.8.0" + "typescript": "~5.7.0", + "yaml-loader": "^0.9.0" } } diff --git a/microsite/releases.js b/microsite/releases.js index 4136b0edad..89fe79522c 100644 --- a/microsite/releases.js +++ b/microsite/releases.js @@ -1,5 +1,5 @@ -const fs = require('fs'); -const path = require('path'); +const fs = require('node:fs'); +const path = require('node:path'); const docsDir = path.resolve(__dirname, '../docs'); diff --git a/microsite/scripts/pre-build.js b/microsite/scripts/pre-build.js deleted file mode 100644 index 3b5bd4fb0b..0000000000 --- a/microsite/scripts/pre-build.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const { existsSync, writeFileSync, mkdirSync } = require('fs'); -const path = require('path'); - -const PLACEHOLDER = `--- -id: "index" -title: "Package Index" -description: "Index of all Backstage Packages" ---- -Run \`yarn build:api-docs\` to generate the API docs. -`; - -async function main() { - const referencesDir = '../docs/reference'; - if (!existsSync(referencesDir)) { - mkdirSync(referencesDir); - writeFileSync(path.join(referencesDir, 'index.md'), PLACEHOLDER); - } -} - -main().catch(error => { - console.error(error.stack); - process.exit(1); -}); diff --git a/microsite/sidebars.ts b/microsite/sidebars.ts index 9a4f03620f..df39e911ff 100644 --- a/microsite/sidebars.ts +++ b/microsite/sidebars.ts @@ -18,664 +18,782 @@ const scaffolderSidebar = tryToLoadCustomSidebar( '../docs/features/software-templates/api/sidebar.ts', ); +function sidebarElementWithIndex( + element: { + description?: string; + differentiator?: string; + label: string; + docId?: string; + }, + children: Array, +) { + const { label, description, differentiator = '' } = element; + return { + type: 'category', + label, + description, + link: { + type: 'generated-index', + title: label, + slug: `/${differentiator}${label + .toLowerCase() + .replace(/[^a-z0-9]/g, '-')}/generated-index`, + }, + items: children, + }; +} + export default { - docs: { - Overview: [ + docs: [ + sidebarElementWithIndex({ label: 'Overview' }, [ 'overview/what-is-backstage', 'overview/technical-overview', 'overview/architecture-overview', 'overview/roadmap', 'overview/threat-model', 'overview/versioning-policy', - ], - 'Getting Started': [ + ]), + sidebarElementWithIndex({ label: 'Getting Started' }, [ 'getting-started/index', - { - type: 'category', - label: 'Configuring Backstage', - items: [ - 'getting-started/config/database', - 'getting-started/config/authentication', - 'getting-started/configure-app-with-plugins', - 'getting-started/homepage', - ], - }, - { - type: 'category', - label: 'Deploying Backstage', - items: [ - 'deployment/index', - 'deployment/scaling', - 'deployment/docker', - 'deployment/k8s', - ], - }, - { - type: 'category', - label: 'Using Backstage', - items: [ - 'getting-started/logging-in', + sidebarElementWithIndex({ label: 'Configuring Backstage' }, [ + 'getting-started/config/database', + 'getting-started/config/authentication', + 'getting-started/configure-app-with-plugins', + 'getting-started/homepage', + ]), + sidebarElementWithIndex({ label: 'Deploying Backstage' }, [ + 'deployment/index', + 'deployment/scaling', + 'deployment/docker', + 'deployment/k8s', + ]), + sidebarElementWithIndex({ label: 'Using Backstage' }, [ + 'getting-started/logging-in', + 'getting-started/viewing-catalog', + 'getting-started/view-what-you-own', + 'getting-started/viewing-entity-relationships', + 'getting-started/filter-catalog', + sidebarElementWithIndex({ label: 'Managing Components' }, [ 'getting-started/register-a-component', 'getting-started/create-a-component', - ], - }, + 'getting-started/update-a-component', + 'getting-started/unregister-delete-component', + ]), + ]), 'overview/support', 'getting-started/keeping-backstage-updated', - ], + ]), ...(process.env.GOLDEN_PATH - ? { - 'Golden Paths': [ + ? [ + sidebarElementWithIndex({ label: 'Golden Paths' }, [ + sidebarElementWithIndex({ label: '000 - Adoption' }, [ + 'golden-path/adoption/getting-started', + 'golden-path/adoption/leadership-buy-in', + 'golden-path/adoption/setting-up-a-poc', + 'golden-path/adoption/first-stakeholder-feedback', + 'golden-path/adoption/customizing-your-instance', + 'golden-path/adoption/preparing-for-ga', + ]), + sidebarElementWithIndex({ label: '001 - Create an App' }, [ + 'golden-path/create-app/index', + 'golden-path/create-app/npx-create-app', + 'golden-path/create-app/local-development', + 'golden-path/create-app/installing-plugins', + 'golden-path/create-app/logging-in', + 'golden-path/create-app/custom-theme', + 'golden-path/create-app/keeping-backstage-updated', + ]), + sidebarElementWithIndex({ label: '002 - Plugins' }, [ + 'golden-path/plugins/index', + 'golden-path/plugins/why-build-plugins', + 'golden-path/plugins/sustainable-plugin-development', + sidebarElementWithIndex({ label: 'Backend Plugins' }, [ + 'golden-path/plugins/backend/001-first-steps', + 'golden-path/plugins/backend/002-poking-around', + ]), + ]), + ]), + ] + : []), + sidebarElementWithIndex( + { + label: 'Core Features', + description: 'Features powering the core of Backstage.', + }, + [ + sidebarElementWithIndex( + { + label: 'AI', + description: + 'Features in Backstage you can leverage with your AI tools.', + }, + ['ai/skills', 'ai/mcp-actions', 'ai/well-known-actions'], + ), + sidebarElementWithIndex( + { + label: 'Auth and Identity', + description: 'Authentication and identity management features.', + }, + [ + 'auth/index', + sidebarElementWithIndex( + { + label: 'Included providers', + description: + 'Pre-configured authentication providers included with Backstage.', + }, + [ + 'auth/auth0/provider', + 'auth/atlassian/provider', + 'auth/aws-alb/provider', + 'auth/microsoft/provider', + 'auth/microsoft/easy-auth', + 'auth/bitbucket/provider', + 'auth/bitbucketServer/provider', + 'auth/cloudflare/provider', + 'auth/github/provider', + 'auth/gitlab/provider', + 'auth/google/provider', + 'auth/google/gcp-iap-auth', + 'auth/guest/provider', + 'auth/okta/provider', + 'auth/oauth2-proxy/provider', + 'auth/onelogin/provider', + 'auth/vmware-cloud/provider', + ], + ), + 'auth/identity-resolver', + 'auth/oauth', + 'auth/oidc', + 'auth/add-auth-provider', + 'auth/service-to-service-auth', + 'auth/autologout', + 'auth/troubleshooting', + ], + ), + sidebarElementWithIndex( + { + label: 'Kubernetes', + description: 'Use Kubernetes right from Backstage.', + }, + [ + 'features/kubernetes/overview', + 'features/kubernetes/installation', + 'features/kubernetes/configuration', + 'features/kubernetes/authentication', + 'features/kubernetes/authentication-strategies', + 'features/kubernetes/troubleshooting', + 'features/kubernetes/proxy', + ], + ), + sidebarElementWithIndex( + { + label: 'Notifications', + description: 'Know when important events happen in Backstage.', + }, + [ + 'notifications/index', + 'notifications/processors', + 'notifications/usage', + ], + ), + sidebarElementWithIndex( + { + label: 'Permissions', + description: 'Access control for your Backstage instance.', + }, + [ + 'permissions/overview', + 'permissions/concepts', + 'permissions/getting-started', + 'permissions/writing-a-policy', + 'permissions/frontend-integration', + 'permissions/custom-rules', + sidebarElementWithIndex( + { + label: 'Tutorial: using Permissions in your plugin', + description: + 'Step-by-step guide for plugin authors on using Permissions.', + }, + [ + 'permissions/plugin-authors/01-setup', + 'permissions/plugin-authors/02-adding-a-basic-permission-check', + 'permissions/plugin-authors/03-adding-a-resource-permission-check', + 'permissions/plugin-authors/04-authorizing-access-to-paginated-data', + 'permissions/plugin-authors/05-frontend-authorization', + ], + ), + ], + ), + sidebarElementWithIndex( + { label: 'Search', description: 'Using Search within Backstage.' }, + [ + 'features/search/search-overview', + 'features/search/getting-started', + 'features/search/concepts', { type: 'category', - label: '001 - create-app', - items: [ - 'golden-path/create-app/index', - 'golden-path/create-app/npx-create-app', - 'golden-path/create-app/local-development', - 'golden-path/create-app/installing-plugins', - 'golden-path/create-app/logging-in', - 'golden-path/create-app/custom-theme', - 'golden-path/create-app/keeping-backstage-updated', - ], + label: 'API', + link: + searchSidebar.length > 0 + ? { + type: 'generated-index', + title: 'Search API', + slug: '/category/search-api', + } + : { + type: 'doc', + id: 'openapi/generated-docs/404', + }, + items: searchSidebar, }, + 'features/search/architecture', + 'features/search/search-engines', + 'features/search/collators', + 'features/search/how-to-guides', + ], + ), + sidebarElementWithIndex( + { + label: 'Software Catalog', + description: 'Manage and explore your software components.', + }, + [ + 'features/software-catalog/software-catalog-overview', + 'features/software-catalog/life-of-an-entity', + 'features/software-catalog/configuration', + 'features/software-catalog/system-model', + 'features/software-catalog/descriptor-format', + 'features/software-catalog/references', + 'features/software-catalog/well-known-annotations', + 'features/software-catalog/well-known-relations', + 'features/software-catalog/well-known-statuses', + 'features/software-catalog/extending-the-model', + 'features/software-catalog/external-integrations', + 'features/software-catalog/catalog-customization', + 'features/software-catalog/entity-presentation', + 'features/software-catalog/audit-events', { type: 'category', - label: '002 - Plugins', - items: [ - 'golden-path/plugins/index', - 'golden-path/plugins/why-build-plugins', - 'golden-path/plugins/sustainable-plugin-development', - { - type: 'category', - label: 'Backend Plugins', - items: [ - 'golden-path/plugins/backend/001-first-steps', - 'golden-path/plugins/backend/002-poking-around', - ], - }, - ], + label: 'API', + link: + catalogSidebar.length > 0 + ? { + type: 'generated-index', + title: 'Catalog API', + slug: '/category/catalog-api', + } + : { + type: 'doc', + id: 'openapi/generated-docs/404', + }, + items: catalogSidebar, + }, + 'features/software-catalog/creating-the-catalog-graph', + 'features/software-catalog/faq', + ], + ), + sidebarElementWithIndex( + { + label: 'Software Templates', + description: 'Create and manage software templates.', + }, + [ + 'features/software-templates/software-templates-index', + 'features/software-templates/configuration', + 'features/software-templates/adding-templates', + 'features/software-templates/writing-templates', + 'features/software-templates/input-examples', + 'features/software-templates/ui-options-examples', + 'features/software-templates/builtin-actions', + 'features/software-templates/writing-custom-actions', + 'features/software-templates/writing-tests-for-actions', + 'features/software-templates/writing-custom-field-extensions', + 'features/software-templates/writing-custom-step-layouts', + 'features/software-templates/authorizing-scaffolder-template-details', + 'features/software-templates/dry-run-testing', + 'features/software-templates/experimental', + 'features/software-templates/templating-extensions', + 'features/software-templates/audit-events', + { + type: 'category', + label: 'API', + link: + scaffolderSidebar.length > 0 + ? { + type: 'generated-index', + title: 'Scaffolder API', + slug: '/category/scaffolder-api', + } + : { + type: 'doc', + id: 'openapi/generated-docs/404', + }, + items: scaffolderSidebar, }, ], - } - : {}), - 'Core Features': [ - { - type: 'category', - label: 'Auth and Identity', - items: [ - 'auth/index', + ), + sidebarElementWithIndex( { - type: 'category', - label: 'Included providers', - items: [ - 'auth/auth0/provider', - 'auth/atlassian/provider', - 'auth/aws-alb/provider', - 'auth/microsoft/provider', - 'auth/microsoft/easy-auth', - 'auth/bitbucket/provider', - 'auth/bitbucketServer/provider', - 'auth/cloudflare/provider', - 'auth/github/provider', - 'auth/gitlab/provider', - 'auth/google/provider', - 'auth/google/gcp-iap-auth', - 'auth/guest/provider', - 'auth/okta/provider', - 'auth/oauth2-proxy/provider', - 'auth/onelogin/provider', - 'auth/vmware-cloud/provider', - ], + label: 'TechDocs', + description: 'Documentation as code for your software.', }, - 'auth/identity-resolver', - 'auth/oauth', - 'auth/oidc', - 'auth/add-auth-provider', - 'auth/service-to-service-auth', - 'auth/autologout', - 'auth/troubleshooting', - ], - }, + [ + 'features/techdocs/techdocs-overview', + 'features/techdocs/getting-started', + 'features/techdocs/concepts', + 'features/techdocs/addons', + 'features/techdocs/architecture', + 'features/techdocs/extensions', + 'features/techdocs/creating-and-publishing', + 'features/techdocs/configuration', + 'features/techdocs/using-cloud-storage', + 'features/techdocs/configuring-ci-cd', + 'features/techdocs/cli', + 'features/techdocs/how-to-guides', + 'features/techdocs/troubleshooting', + 'features/techdocs/faqs', + ], + ), + ], + ), + sidebarElementWithIndex( { - type: 'category', - label: 'Kubernetes', - items: [ - 'features/kubernetes/overview', - 'features/kubernetes/installation', - 'features/kubernetes/configuration', - 'features/kubernetes/authentication', - 'features/kubernetes/authentication-strategies', - 'features/kubernetes/troubleshooting', - 'features/kubernetes/proxy', - ], + label: 'Integrations', + description: 'Connect and integrate with external services.', }, - { - type: 'category', - label: 'Notifications', - items: [ - 'notifications/index', - 'notifications/processors', - 'notifications/usage', - ], - }, - { - type: 'category', - label: 'Permissions', - items: [ - 'permissions/overview', - 'permissions/concepts', - 'permissions/getting-started', - 'permissions/writing-a-policy', - 'permissions/frontend-integration', - 'permissions/custom-rules', - { - type: 'category', - label: 'Tutorial: using Permissions in your plugin', - items: [ - 'permissions/plugin-authors/01-setup', - 'permissions/plugin-authors/02-adding-a-basic-permission-check', - 'permissions/plugin-authors/03-adding-a-resource-permission-check', - 'permissions/plugin-authors/04-authorizing-access-to-paginated-data', - 'permissions/plugin-authors/05-frontend-authorization', - ], - }, - ], - }, - { - type: 'category', - label: 'Search', - items: [ - 'features/search/search-overview', - 'features/search/getting-started', - 'features/search/concepts', - { - type: 'category', - label: 'API', - link: - searchSidebar.length > 0 - ? { - type: 'generated-index', - title: 'Search API', - slug: '/category/search-api', - } - : { - type: 'doc', - id: 'openapi/generated-docs/404', - }, - items: searchSidebar, - }, - 'features/search/architecture', - 'features/search/search-engines', - 'features/search/collators', - 'features/search/how-to-guides', - ], - }, - { - type: 'category', - label: 'Software Catalog', - items: [ - 'features/software-catalog/software-catalog-overview', - 'features/software-catalog/life-of-an-entity', - 'features/software-catalog/configuration', - 'features/software-catalog/system-model', - 'features/software-catalog/descriptor-format', - 'features/software-catalog/references', - 'features/software-catalog/well-known-annotations', - 'features/software-catalog/well-known-relations', - 'features/software-catalog/well-known-statuses', - 'features/software-catalog/extending-the-model', - 'features/software-catalog/external-integrations', - 'features/software-catalog/catalog-customization', - { - type: 'category', - label: 'API', - link: - catalogSidebar.length > 0 - ? { - type: 'generated-index', - title: 'Catalog API', - slug: '/category/catalog-api', - } - : { - type: 'doc', - id: 'openapi/generated-docs/404', - }, - items: catalogSidebar, - }, - 'features/software-catalog/creating-the-catalog-graph', - 'features/software-catalog/faq', - ], - }, - { - type: 'category', - label: 'Software Templates', - items: [ - 'features/software-templates/software-templates-index', - 'features/software-templates/configuration', - 'features/software-templates/adding-templates', - 'features/software-templates/writing-templates', - 'features/software-templates/input-examples', - 'features/software-templates/builtin-actions', - 'features/software-templates/writing-custom-actions', - 'features/software-templates/writing-tests-for-actions', - 'features/software-templates/writing-custom-field-extensions', - 'features/software-templates/writing-custom-step-layouts', - 'features/software-templates/authorizing-scaffolder-template-details', - 'features/software-templates/migrating-to-rjsf-v5', - 'features/software-templates/migrating-from-v1beta2-to-v1beta3', - 'features/software-templates/dry-run-testing', - 'features/software-templates/experimental', - 'features/software-templates/templating-extensions', - { - type: 'category', - label: 'API', - link: - scaffolderSidebar.length > 0 - ? { - type: 'generated-index', - title: 'Scaffolder API', - slug: '/category/scaffolder-api', - } - : { - type: 'doc', - id: 'openapi/generated-docs/404', - }, - items: scaffolderSidebar, - }, - ], - }, - { - type: 'category', - label: 'TechDocs', - items: [ - 'features/techdocs/techdocs-overview', - 'features/techdocs/getting-started', - 'features/techdocs/concepts', - 'features/techdocs/addons', - 'features/techdocs/architecture', - 'features/techdocs/extensions', - 'features/techdocs/creating-and-publishing', - 'features/techdocs/configuration', - 'features/techdocs/using-cloud-storage', - 'features/techdocs/configuring-ci-cd', - 'features/techdocs/cli', - 'features/techdocs/how-to-guides', - 'features/techdocs/troubleshooting', - 'features/techdocs/faqs', - ], - }, - ], - Integrations: [ - 'integrations/index', - { - type: 'category', - label: 'AWS S3', - items: [ + [ + 'integrations/index', + sidebarElementWithIndex({ label: 'AWS S3' }, [ 'integrations/aws-s3/locations', 'integrations/aws-s3/discovery', - ], - }, - { - type: 'category', - label: 'Azure Blob Storage', - items: [ + ]), + sidebarElementWithIndex({ label: 'Azure Blob Storage' }, [ 'integrations/azure-blobStorage/locations', 'integrations/azure-blobStorage/discovery', - ], - }, - { - type: 'category', - label: 'Azure', - items: [ + ]), + sidebarElementWithIndex({ label: 'Azure' }, [ 'integrations/azure/locations', 'integrations/azure/discovery', 'integrations/azure/org', - ], - }, - { - type: 'category', - label: 'Bitbucket Cloud', - items: [ + ]), + sidebarElementWithIndex({ label: 'Bitbucket Cloud' }, [ 'integrations/bitbucketCloud/locations', 'integrations/bitbucketCloud/discovery', - ], - }, - { - type: 'category', - label: 'Bitbucket Server', - items: [ + ]), + sidebarElementWithIndex({ label: 'Bitbucket Server' }, [ 'integrations/bitbucketServer/locations', 'integrations/bitbucketServer/discovery', - ], - }, - { - type: 'category', - label: 'Datadog', - items: ['integrations/datadog-rum/installation'], - }, - { - type: 'category', - label: 'Gerrit', - items: [ + ]), + sidebarElementWithIndex({ label: 'Datadog' }, [ + 'integrations/datadog-rum/installation', + ]), + sidebarElementWithIndex({ label: 'Gerrit' }, [ 'integrations/gerrit/locations', 'integrations/gerrit/discovery', - ], - }, - { - type: 'category', - label: 'GitHub', - items: [ + ]), + sidebarElementWithIndex({ label: 'Github' }, [ 'integrations/github/locations', 'integrations/github/discovery', 'integrations/github/org', 'integrations/github/github-apps', - ], - }, - { - type: 'category', - label: 'GitLab', - items: [ + ]), + sidebarElementWithIndex({ label: 'GitLab' }, [ 'integrations/gitlab/locations', 'integrations/gitlab/discovery', 'integrations/gitlab/org', - ], - }, + ]), + sidebarElementWithIndex({ label: 'Gitea' }, [ + 'integrations/gitea/locations', + 'integrations/gitea/discovery', + ]), + sidebarElementWithIndex({ label: 'Harness' }, [ + 'integrations/harness/locations', + ]), + sidebarElementWithIndex({ label: 'Google GCS' }, [ + 'integrations/google-cloud-storage/locations', + ]), + sidebarElementWithIndex({ label: 'LDAP' }, ['integrations/ldap/org']), + sidebarElementWithIndex({ label: 'Okta' }, ['integrations/okta/org']), + ], + ), + sidebarElementWithIndex( { - type: 'category', - label: 'Gitea', - items: ['integrations/gitea/locations', 'integrations/gitea/discovery'], + label: 'Configuration', + description: 'Manage static configuration files and settings.', }, + ['conf/index', 'conf/reading', 'conf/writing', 'conf/defining'], + ), + sidebarElementWithIndex( { - type: 'category', - label: 'Harness', - items: ['integrations/harness/locations'], + label: 'Framework', + description: 'Core framework concepts and architecture.', }, - { - type: 'category', - label: 'Google GCS', - items: ['integrations/google-cloud-storage/locations'], - }, - { - type: 'category', - label: 'LDAP', - items: ['integrations/ldap/org'], - }, - ], - Plugins: [ - 'plugins/index', - 'plugins/existing-plugins', - 'plugins/create-a-plugin', - 'plugins/plugin-development', - 'plugins/structure-of-a-plugin', - 'plugins/integrating-plugin-into-software-catalog', - 'plugins/integrating-search-into-plugins', - 'plugins/composability', - 'plugins/internationalization', - 'plugins/analytics', - 'plugins/feature-flags', - { - type: 'category', - label: 'OpenAPI', - items: [ - 'openapi/01-getting-started', - 'openapi/generate-client', - 'openapi/test-case-validation', - ], - }, - { - type: 'category', - label: 'Backends and APIs', - items: [ - 'plugins/proxying', - 'plugins/backend-plugin', - 'plugins/call-existing-api', - ], - }, - { - type: 'category', - label: 'Testing', - items: ['plugins/testing'], - }, - { - type: 'category', - label: 'Publishing', - items: [ - 'plugins/publish-private', - 'plugins/add-to-directory', - 'plugins/observability', - ], - }, - ], - Configuration: [ - 'conf/index', - 'conf/reading', - 'conf/writing', - 'conf/defining', - ], - Framework: [ - { - type: 'category', - label: 'Backend System', - items: [ - 'backend-system/index', + [ + sidebarElementWithIndex( { - type: 'category', - label: 'Architecture', - items: [ - 'backend-system/architecture/index', - 'backend-system/architecture/services', - 'backend-system/architecture/plugins', - 'backend-system/architecture/extension-points', - 'backend-system/architecture/modules', - 'backend-system/architecture/feature-loaders', - 'backend-system/architecture/naming-patterns', - ], + label: 'Backend System', + description: 'Backend system components and architecture.', }, + [ + 'backend-system/index', + sidebarElementWithIndex( + { + label: 'Architecture', + description: 'Architecture of the backend system.', + differentiator: 'backend-system/', + }, + [ + 'backend-system/architecture/index', + 'backend-system/architecture/services', + 'backend-system/architecture/plugins', + 'backend-system/architecture/extension-points', + 'backend-system/architecture/modules', + 'backend-system/architecture/feature-loaders', + 'backend-system/architecture/naming-patterns', + ], + ), + sidebarElementWithIndex( + { + label: 'Building Backends', + description: 'Guides on building backend systems.', + }, + [ + 'backend-system/building-backends/index', + 'backend-system/building-backends/migrating', + ], + ), + sidebarElementWithIndex( + { + label: 'Building Plugins & Modules', + description: 'Guides on building plugins and modules.', + }, + [ + 'backend-system/building-plugins-and-modules/index', + 'backend-system/building-plugins-and-modules/testing', + 'backend-system/building-plugins-and-modules/migrating', + ], + ), + sidebarElementWithIndex( + { + label: 'Core Services', + description: 'Core services of the backend system.', + }, + [ + 'backend-system/core-services/index', + 'backend-system/core-services/auditor', + 'backend-system/core-services/auth', + 'backend-system/core-services/cache', + 'backend-system/core-services/database', + 'backend-system/core-services/discovery', + 'backend-system/core-services/http-auth', + 'backend-system/core-services/http-router', + 'backend-system/core-services/identity', + 'backend-system/core-services/lifecycle', + 'backend-system/core-services/logger', + 'backend-system/core-services/metrics', + 'backend-system/core-services/permissions', + 'backend-system/core-services/permissions-registry', + 'backend-system/core-services/plugin-metadata', + 'backend-system/core-services/root-config', + 'backend-system/core-services/root-health', + 'backend-system/core-services/root-http-router', + 'backend-system/core-services/root-instance-metadata', + 'backend-system/core-services/root-lifecycle', + 'backend-system/core-services/root-logger', + 'backend-system/core-services/scheduler', + 'backend-system/core-services/token-manager', + 'backend-system/core-services/url-reader', + 'backend-system/core-services/user-info', + 'backend-system/core-services/actions-registry', + 'backend-system/core-services/actions', + ], + ), + ], + ), + sidebarElementWithIndex( { - type: 'category', - label: 'Building Backends', - items: [ - 'backend-system/building-backends/index', - 'backend-system/building-backends/migrating', - ], + label: 'Frontend System', + description: 'Frontend system components and architecture.', }, + [ + 'frontend-system/index', + sidebarElementWithIndex( + { + label: 'Architecture', + description: 'Architecture of the frontend system.', + differentiator: 'frontend-system/', + }, + [ + 'frontend-system/architecture/index', + 'frontend-system/architecture/app', + 'frontend-system/architecture/plugins', + 'frontend-system/architecture/extensions', + 'frontend-system/architecture/extension-blueprints', + 'frontend-system/architecture/extension-overrides', + 'frontend-system/architecture/sharing-extensions', + 'frontend-system/architecture/references', + 'frontend-system/architecture/utility-apis', + 'frontend-system/architecture/routes', + 'frontend-system/architecture/naming-patterns', + 'frontend-system/architecture/migrations', + ], + ), + sidebarElementWithIndex( + { + label: 'Building Plugins', + description: 'Guides on building frontend plugins.', + }, + [ + 'frontend-system/building-plugins/index', + 'frontend-system/building-plugins/testing', + 'frontend-system/building-plugins/common-extension-blueprints', + 'frontend-system/building-plugins/built-in-data-refs', + 'frontend-system/building-plugins/migrating', + 'frontend-system/building-plugins/internationalization', + 'frontend-system/building-plugins/analytics', + 'frontend-system/building-plugins/feature-flags', + ], + ), + sidebarElementWithIndex( + { + label: 'Building Apps', + description: 'Guides on building frontend applications.', + }, + [ + 'frontend-system/building-apps/index', + 'frontend-system/building-apps/installing-plugins', + 'frontend-system/building-apps/configuring-extensions', + 'frontend-system/building-apps/built-in-extensions', + 'frontend-system/building-apps/plugin-conversion', + 'frontend-system/building-apps/module-federation', + 'frontend-system/building-apps/migrating', + ], + ), + sidebarElementWithIndex( + { + label: 'Utility APIs', + description: 'Utility APIs for the frontend system.', + }, + [ + 'frontend-system/utility-apis/index', + 'frontend-system/utility-apis/creating', + 'frontend-system/utility-apis/consuming', + 'frontend-system/utility-apis/configuring', + 'frontend-system/utility-apis/testing', + ], + ), + ], + ), + sidebarElementWithIndex( { - type: 'category', - label: 'Building Plugins & Modules', - items: [ - 'backend-system/building-plugins-and-modules/index', - 'backend-system/building-plugins-and-modules/testing', - 'backend-system/building-plugins-and-modules/migrating', - ], + label: 'Backstage CLI', + description: 'Command-line interface for Backstage.', }, + [ + 'tooling/cli/overview', + 'tooling/cli/build-system', + 'tooling/cli/commands', + 'tooling/cli/templates', + sidebarElementWithIndex( + { + label: 'Local Development', + description: + 'Guides for local development using Backstage CLI.', + }, + [ + 'tooling/local-dev/linking-local-packages', + 'tooling/local-dev/debugging', + 'tooling/local-dev/profiling', + ], + ), + 'tooling/package-metadata', + ], + ), + sidebarElementWithIndex( { - type: 'category', - label: 'Core Services', - items: [ - 'backend-system/core-services/index', - 'backend-system/core-services/auditor', - 'backend-system/core-services/auth', - 'backend-system/core-services/cache', - 'backend-system/core-services/database', - 'backend-system/core-services/discovery', - 'backend-system/core-services/http-auth', - 'backend-system/core-services/http-router', - 'backend-system/core-services/identity', - 'backend-system/core-services/lifecycle', - 'backend-system/core-services/logger', - 'backend-system/core-services/permissions', - 'backend-system/core-services/permissions-registry', - 'backend-system/core-services/plugin-metadata', - 'backend-system/core-services/root-config', - 'backend-system/core-services/root-health', - 'backend-system/core-services/root-http-router', - 'backend-system/core-services/root-lifecycle', - 'backend-system/core-services/root-logger', - 'backend-system/core-services/scheduler', - 'backend-system/core-services/token-manager', - 'backend-system/core-services/url-reader', - 'backend-system/core-services/user-info', - 'backend-system/core-services/actions-registry', - 'backend-system/core-services/actions', - ], + label: 'User Interface', + description: 'User interface customization and configuration.', }, - ], - }, + [ + 'conf/user-interface/index', + 'conf/user-interface/logo', + 'conf/user-interface/icons', + 'conf/user-interface/sidebar', + ], + ), + sidebarElementWithIndex( + { + label: 'OpenAPI', + description: + 'Work with OpenAPI specifications and generate clients.', + }, + [ + 'openapi/01-getting-started', + 'openapi/generate-client', + 'openapi/test-case-validation', + ], + ), + ], + ), + sidebarElementWithIndex( { - type: 'category', - label: 'New Frontend System', - items: [ - 'frontend-system/index', + label: 'Tutorials', + description: + 'Step-by-step tutorials for various Backstage features and use cases.', + }, + [ + sidebarElementWithIndex( { - type: 'category', - label: 'Architecture', - items: [ - 'frontend-system/architecture/index', - 'frontend-system/architecture/app', - 'frontend-system/architecture/plugins', - 'frontend-system/architecture/extensions', - 'frontend-system/architecture/extension-blueprints', - 'frontend-system/architecture/extension-overrides', - 'frontend-system/architecture/references', - 'frontend-system/architecture/utility-apis', - 'frontend-system/architecture/routes', - 'frontend-system/architecture/naming-patterns', - 'frontend-system/architecture/migrations', - ], + label: 'Non-technical', + description: 'Non-technical tutorials and guides.', }, + ['overview/adopting'], + ), + sidebarElementWithIndex( { - type: 'category', - label: 'Building Plugins', - items: [ - 'frontend-system/building-plugins/index', - 'frontend-system/building-plugins/testing', - 'frontend-system/building-plugins/common-extension-blueprints', - 'frontend-system/building-plugins/built-in-data-refs', - 'frontend-system/building-plugins/migrating', - ], + label: 'Technical', + description: 'Technical tutorials and guides.', }, + [ + 'tutorials/configuring-plugin-databases', + 'tutorials/manual-knex-rollback', + 'tutorials/switching-sqlite-postgres', + 'tutorials/using-backstage-proxy-within-plugin', + 'tutorials/corporate-proxy', + 'tutorials/enable-public-entry', + 'tutorials/setup-opentelemetry', + 'tutorials/integrating-event-driven-updates-with-entity-providers', + 'accessibility/index', + ], + ), + sidebarElementWithIndex( { - type: 'category', - label: 'Building Apps', - items: [ - 'frontend-system/building-apps/index', - 'frontend-system/building-apps/configuring-extensions', - 'frontend-system/building-apps/built-in-extensions', - 'frontend-system/building-apps/plugin-conversion', - 'frontend-system/building-apps/migrating', - ], + label: 'Migrations', + description: + 'Guides for migrating between different versions and systems.', }, + [ + 'tutorials/jest30-migration', + 'tutorials/react-router-stable-migration', + 'tutorials/react18-migration', + 'tutorials/package-role-migration', + 'tutorials/migrating-away-from-core', + 'tutorials/yarn-migration', + 'tutorials/migrate-to-mui5', + 'tutorials/auth-service-migration', + 'tutorials/jsx-transform-migration', + ], + ), + ], + ), + sidebarElementWithIndex( + { + label: 'Plugins (Legacy)', + description: + 'Legacy plugin development documentation for the old frontend system. For new development, see the Frontend System and Backend System sections under Framework.', + }, + [ + 'plugins/index', + 'plugins/create-a-plugin', + 'plugins/plugin-development', + 'plugins/structure-of-a-plugin', + 'plugins/integrating-plugin-into-software-catalog', + 'plugins/integrating-search-into-plugins', + 'plugins/composability', + 'plugins/internationalization', + 'plugins/analytics', + 'plugins/feature-flags', + sidebarElementWithIndex( { - type: 'category', - label: 'Utility APIs', - items: [ - 'frontend-system/utility-apis/index', - 'frontend-system/utility-apis/creating', - 'frontend-system/utility-apis/consuming', - 'frontend-system/utility-apis/configuring', - ], + label: 'Backends and APIs', + description: 'Build and manage backend services and APIs.', }, - ], - }, + [ + 'plugins/proxying', + 'plugins/backend-plugin', + 'plugins/call-existing-api', + ], + ), + sidebarElementWithIndex( + { label: 'Testing', description: 'Testing plugins and modules.' }, + ['plugins/testing'], + ), + sidebarElementWithIndex( + { label: 'Publishing', description: 'Publishing your plugins.' }, + ['plugins/add-to-directory', 'plugins/plugin-directory-audit'], + ), + 'plugins/observability', + 'plugins/new-backend-system', + ], + ), + sidebarElementWithIndex( + { label: 'FAQ', description: 'Frequently asked questions and answers.' }, + ['faq/index', 'faq/product', 'faq/technical'], + ), + sidebarElementWithIndex( { - 'Backstage CLI': [ - 'tooling/cli/overview', - 'tooling/cli/build-system', - 'tooling/cli/commands', - 'tooling/cli/templates', + label: 'Contribute', + description: 'Information on contributing to Backstage.', + }, + [ + 'contribute/index', + 'contribute/getting-involved', + 'contribute/project-structure', + 'contribute/doc-style-guide', + ], + ), + sidebarElementWithIndex( + { + label: 'References', + description: 'Reference materials and documentation.', + }, + [ + sidebarElementWithIndex( { - type: 'category', - label: 'Local Development', - items: [ - 'tooling/local-dev/linking-local-packages', - 'tooling/local-dev/debugging', - 'tooling/local-dev/profiling', - ], + label: 'Designing for Backstage', + description: + 'Guidelines and resources for designing Backstage components.', }, - 'tooling/package-metadata', - ], - }, - { - type: 'category', - label: 'User Interface', - items: [ - 'conf/user-interface/index', - 'conf/user-interface/logo', - 'conf/user-interface/icons', - 'conf/user-interface/sidebar', - ], - }, - ], - Tutorials: [ - { 'Non-technical': ['overview/adopting'] }, - { - Technical: [ - 'tutorials/quickstart-app-plugin', - 'tutorials/configuring-plugin-databases', - 'tutorials/manual-knex-rollback', - 'tutorials/switching-sqlite-postgres', - 'tutorials/using-backstage-proxy-within-plugin', - 'tutorials/enable-public-entry', - 'tutorials/setup-opentelemetry', - 'tutorials/integrating-event-driven-updates-with-entity-providers', - 'accessibility/index', - ], - }, - { - Migrations: [ - 'tutorials/react-router-stable-migration', - 'tutorials/react18-migration', - 'tutorials/package-role-migration', - 'tutorials/migrating-away-from-core', - 'tutorials/yarn-migration', - 'tutorials/migrate-to-mui5', - 'tutorials/auth-service-migration', - 'tutorials/jsx-transform-migration', - ], - }, - ], - FAQ: ['faq/index', 'faq/product', 'faq/technical'], - Contribute: [ - 'contribute/index', - 'contribute/getting-involved', - 'contribute/project-structure', - ], - References: [ - { - 'Designing for Backstage': [ - 'dls/design', - 'dls/component-design-guidelines', - 'dls/contributing-to-storybook', - 'dls/figma', - ], - }, - { - type: 'category', - label: 'Architecture Decision Records (ADRs)', - items: [ - 'architecture-decisions/adrs-overview', - 'architecture-decisions/adrs-adr001', - 'architecture-decisions/adrs-adr002', - 'architecture-decisions/adrs-adr003', - 'architecture-decisions/adrs-adr004', - 'architecture-decisions/adrs-adr005', - 'architecture-decisions/adrs-adr006', - 'architecture-decisions/adrs-adr007', - 'architecture-decisions/adrs-adr008', - 'architecture-decisions/adrs-adr009', - 'architecture-decisions/adrs-adr010', - 'architecture-decisions/adrs-adr011', - 'architecture-decisions/adrs-adr012', - 'architecture-decisions/adrs-adr013', - 'architecture-decisions/adrs-adr014', - ], - }, - 'api/deprecations', - 'references/glossary', - 'api/utility-apis', - 'reference/index', - ], - }, + [ + 'dls/design', + 'dls/component-design-guidelines', + 'dls/contributing-to-storybook', + 'dls/figma', + ], + ), + sidebarElementWithIndex( + { + label: 'Architecture Decision Records (ADRs)', + description: + 'Documentation of architectural decisions made in Backstage.', + }, + [ + 'architecture-decisions/adrs-overview', + 'architecture-decisions/adrs-adr001', + 'architecture-decisions/adrs-adr002', + 'architecture-decisions/adrs-adr003', + 'architecture-decisions/adrs-adr004', + 'architecture-decisions/adrs-adr005', + 'architecture-decisions/adrs-adr006', + 'architecture-decisions/adrs-adr007', + 'architecture-decisions/adrs-adr008', + 'architecture-decisions/adrs-adr009', + 'architecture-decisions/adrs-adr010', + 'architecture-decisions/adrs-adr011', + 'architecture-decisions/adrs-adr012', + 'architecture-decisions/adrs-adr013', + 'architecture-decisions/adrs-adr014', + 'architecture-decisions/adrs-adr015', + ], + ), + 'api/deprecations', + 'references/glossary', + 'api/utility-apis', + 'references/index', + ], + ), + ], releases: { 'Release Notes': releases.map(release => `releases/${release}`), }, diff --git a/microsite/src/pages/community/community.module.scss b/microsite/src/pages/community/community.module.scss index 14ff66db37..ac3a954a2b 100644 --- a/microsite/src/pages/community/community.module.scss +++ b/microsite/src/pages/community/community.module.scss @@ -14,6 +14,12 @@ } } +.partnerLogo { + display: flex; + align-items: center; + justify-content: center; +} + .listContainer { height: 100%; align-items: stretch !important; diff --git a/microsite/src/pages/community/index.tsx b/microsite/src/pages/community/index.tsx index 9770c91e8e..2e11556609 100644 --- a/microsite/src/pages/community/index.tsx +++ b/microsite/src/pages/community/index.tsx @@ -36,7 +36,7 @@ const Community = () => { { content: 'Subscribe to the', label: 'Community Newsletter', - link: 'https://info.backstage.spotify.com/newsletter_subscribe', + link: 'https://spoti.fi/backstagenewsletter', }, ]; @@ -52,7 +52,7 @@ const Community = () => { title: 'Newsletter', content: "The official monthly Backstage newsletter. Don't miss the latest news from your favorite project!", - link: 'https://info.backstage.spotify.com/newsletter_subscribe', + link: 'https://spoti.fi/backstagenewsletter', label: 'Subscribe', }, ]; @@ -81,51 +81,53 @@ const Community = () => { }, ]; - const partners: { name: string; url: string; logo: string }[] = [ + const partners: { + name: string; + url: string; + logo: string; + height: number; + }[] = [ { name: 'Frontside Software', url: 'https://frontside.com/backstage/', logo: 'img/partner-logo-frontside', - }, - { - name: 'RedHat', - url: 'https://developers.redhat.com/rhdh', - logo: 'img/partner-logo-redhat', - }, - { - name: 'Roadie', - url: 'https://roadie.io/', - logo: 'img/partner-logo-roadie', - }, - { - name: 'solo.io', - url: 'https://www.solo.io/spotlight/', - logo: 'img/partner-logo-solo', - }, - { - name: 'ThoughtWorks', - url: 'https://www.thoughtworks.com/about-us/partnerships/technology/backstage-by-spotify', - logo: 'img/partner-logo-thoughtworks', - }, - { - name: 'VMWare', - url: 'https://tanzu.vmware.com/developer-portal', - logo: 'img/partner-logo-tanzubybroadcom', - }, - { - name: 'StatusNeo', - url: 'https://statusneo.com/backstage', - logo: 'img/partner-logo-statusneo', - }, - { - name: 'Alauda', - url: 'https://www.alauda.io/community/169249', - logo: 'img/partner-logo-alauda', + height: 62, }, { name: 'Liatrio', url: 'https://www.liatrio.com/service-offerings/backstage', logo: 'img/partner-logo-liatrio', + height: 36, + }, + { + name: 'RedHat', + url: 'https://developers.redhat.com/rhdh', + logo: 'img/partner-logo-redhat', + height: 38, + }, + { + name: 'Roadie', + url: 'https://roadie.io/', + logo: 'img/partner-logo-roadie', + height: 62, + }, + { + name: 'Spotify', + url: 'https://backstage.spotify.com', + logo: 'img/partner-logo-spotify', + height: 28, + }, + { + name: 'StatusNeo', + url: 'https://statusneo.com/backstage', + logo: 'img/partner-logo-statusneo', + height: 30, + }, + { + name: 'ThoughtWorks', + url: 'https://www.thoughtworks.com/about-us/partnerships/technology/backstage-by-spotify', + logo: 'img/partner-logo-thoughtworks', + height: 55, }, ]; //#endregion @@ -232,11 +234,12 @@ const Community = () => { Commercial Partners} > - {partners.map(({ name, url, logo }, index) => ( -
    + {partners.map(({ name, url, logo, height }, index) => ( +
    {
    🗞️ Want to stay up to date with Backstage? Sign up for our{' '} Newsletter @@ -84,7 +84,7 @@ const HomePage = () => { label: 'GITHUB', }, { - link: 'https://info.backstage.spotify.com/office-hours', + link: 'https://spoti.fi/backstageofficehours', label: 'OFFICE HOURS', }, ]} diff --git a/microsite/src/pages/plugins/_pluginCard.tsx b/microsite/src/pages/plugins/_pluginCard.tsx index ba7864f9aa..a464dd2fc6 100644 --- a/microsite/src/pages/plugins/_pluginCard.tsx +++ b/microsite/src/pages/plugins/_pluginCard.tsx @@ -14,6 +14,7 @@ export interface IPluginData { addedDate: string; isNew: boolean; order?: number; + status: 'active' | 'inactive' | 'archived'; } const defaultIconUrl = '/img/logo-gradient-on-dark.svg'; diff --git a/microsite/src/pages/plugins/index.tsx b/microsite/src/pages/plugins/index.tsx index 1a87f9c0af..9acf3fb341 100644 --- a/microsite/src/pages/plugins/index.tsx +++ b/microsite/src/pages/plugins/index.tsx @@ -117,12 +117,24 @@ const Plugins = () => { const corePlugins = useMemo(() => { return plugins.corePlugins + .filter(pluginData => pluginData.status !== 'archived') .filter(pluginData => matchesCategory(pluginData, selectedCategories)) .filter(pluginData => matchesSearch(pluginData, searchTerm)); }, [selectedCategories, searchTerm]); const otherPlugins = useMemo(() => { return plugins.otherPlugins + .filter( + pluginData => + pluginData.status !== 'inactive' && pluginData.status !== 'archived', + ) + .filter(pluginData => matchesCategory(pluginData, selectedCategories)) + .filter(pluginData => matchesSearch(pluginData, searchTerm)); + }, [selectedCategories, searchTerm]); + + const inactivePlugins = useMemo(() => { + return plugins.otherPlugins + .filter(pluginData => pluginData.status === 'inactive') .filter(pluginData => matchesCategory(pluginData, selectedCategories)) .filter(pluginData => matchesSearch(pluginData, searchTerm)); }, [selectedCategories, searchTerm]); @@ -162,15 +174,17 @@ const Plugins = () => { />
    - {corePlugins.length === 0 && otherPlugins.length === 0 && ( -
    -

    No plugins found

    -

    - We couldn't find any plugins matching your criteria. Please try - adjusting your search or filter settings. -

    -
    - )} + {corePlugins.length === 0 && + otherPlugins.length === 0 && + inactivePlugins.length === 0 && ( +
    +

    No plugins found

    +

    + We couldn't find any plugins matching your criteria. Please try + adjusting your search or filter settings. +

    +
    + )} {showCoreFeatures && corePlugins.length > 0 && (
    @@ -185,7 +199,7 @@ const Plugins = () => { {showOtherPlugins && otherPlugins.length > 0 && (
    -

    All Plugins ({otherPlugins.length})

    +

    Active Plugins ({otherPlugins.length})

    Friendly reminder: While we love the variety and contributions of our open source plugins, they haven't been fully vetted by the @@ -199,6 +213,36 @@ const Plugins = () => {

    )} + + {showOtherPlugins && + otherPlugins.length === 0 && + inactivePlugins.length > 0 && ( +
    +

    Active Plugins (0)

    +

    We couldn't find any active plugins matching your criteria.

    +
    + )} + + {inactivePlugins.length > 0 && ( +
    +

    Inactive Plugins ({inactivePlugins.length})

    +

    + These plugins are no longer actively maintained as their NPM + package has not seen an update in more than 365 days. They are + kept here for reference but may not work with current versions of + Backstage. Details on the audit process can be found in the{' '} + + Plugin Directory Audit + {' '} + documentation. +

    +
    + {inactivePlugins.map(pluginData => ( + + ))} +
    +
    + )}
    ); diff --git a/microsite/src/theme/customTheme.scss b/microsite/src/theme/customTheme.scss index 823327b28f..db903ebdd7 100644 --- a/microsite/src/theme/customTheme.scss +++ b/microsite/src/theme/customTheme.scss @@ -200,3 +200,37 @@ feedback-button { html.plugin-docs feedback-button { display: initial; } + +/* Navbar icon links */ +.header-github-link:hover, +.header-discord-link:hover { + opacity: 0.6; +} + +.header-github-link::before, +.header-discord-link::before { + content: ''; + width: 24px; + height: 24px; + display: flex; +} + +.header-github-link::before { + background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%23fff' d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E") + no-repeat; +} + +html[data-theme='light'] .header-github-link::before { + background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%23000' d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E") + no-repeat; +} + +.header-discord-link::before { + background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%23fff' d='M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z'/%3E%3C/svg%3E") + no-repeat; +} + +html[data-theme='light'] .header-discord-link::before { + background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%235865F2' d='M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z'/%3E%3C/svg%3E") + no-repeat; +} diff --git a/microsite/static/img/3scale.svg b/microsite/static/img/3scale.svg index 46551958aa..00649d34c7 100644 --- a/microsite/static/img/3scale.svg +++ b/microsite/static/img/3scale.svg @@ -1,22 +1 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/microsite/static/img/backstage-search-platform.svg b/microsite/static/img/backstage-search-platform.svg index 9ddc3fbb2e..abe4fa2a96 100644 --- a/microsite/static/img/backstage-search-platform.svg +++ b/microsite/static/img/backstage-search-platform.svg @@ -1,17 +1 @@ - - - - - - - - - - - - + \ No newline at end of file diff --git a/microsite/static/img/badges.svg b/microsite/static/img/badges.svg index b0272986e6..2f60eb01b6 100644 --- a/microsite/static/img/badges.svg +++ b/microsite/static/img/badges.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/microsite/static/img/bazaar.svg b/microsite/static/img/bazaar.svg index 1d180a1e03..7f142902ea 100644 --- a/microsite/static/img/bazaar.svg +++ b/microsite/static/img/bazaar.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/microsite/static/img/buildkite.svg b/microsite/static/img/buildkite.svg index cc6bb28961..3781985e8b 100644 --- a/microsite/static/img/buildkite.svg +++ b/microsite/static/img/buildkite.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/microsite/static/img/catalog-graph.svg b/microsite/static/img/catalog-graph.svg index 6cd13b0e66..67f91dc3ba 100644 --- a/microsite/static/img/catalog-graph.svg +++ b/microsite/static/img/catalog-graph.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/microsite/static/img/cicd-statistics.svg b/microsite/static/img/cicd-statistics.svg index 302f5e8f98..64c71b0854 100644 --- a/microsite/static/img/cicd-statistics.svg +++ b/microsite/static/img/cicd-statistics.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/microsite/static/img/citrix_sds.png b/microsite/static/img/citrix_sds.png new file mode 100644 index 0000000000..fac58411a1 Binary files /dev/null and b/microsite/static/img/citrix_sds.png differ diff --git a/microsite/static/img/codacy-icon.svg b/microsite/static/img/codacy-icon.svg index ef65004330..19626911e1 100644 --- a/microsite/static/img/codacy-icon.svg +++ b/microsite/static/img/codacy-icon.svg @@ -1 +1 @@ -codacy-white \ No newline at end of file +codacy-white \ No newline at end of file diff --git a/microsite/static/img/codescene_logo.svg b/microsite/static/img/codescene_logo.svg index 9e433b2c7b..9ecaf382d2 100644 --- a/microsite/static/img/codescene_logo.svg +++ b/microsite/static/img/codescene_logo.svg @@ -1,50 +1 @@ - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/microsite/static/img/cyclops.svg b/microsite/static/img/cyclops.svg index fbc297443c..584564c9f7 100644 --- a/microsite/static/img/cyclops.svg +++ b/microsite/static/img/cyclops.svg @@ -1,51 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/microsite/static/img/daytona.svg b/microsite/static/img/daytona.svg index 1920b2f878..84d4c05ec1 100644 --- a/microsite/static/img/daytona.svg +++ b/microsite/static/img/daytona.svg @@ -1,62 +1 @@ - - - - - - - - - - + \ No newline at end of file diff --git a/microsite/static/img/devtools.svg b/microsite/static/img/devtools.svg index 6bd5dc17c5..f978b9b3c0 100644 --- a/microsite/static/img/devtools.svg +++ b/microsite/static/img/devtools.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/microsite/static/img/digital.ai-deploy.svg b/microsite/static/img/digital.ai-deploy.svg index d0becb98f1..780b0e417c 100644 --- a/microsite/static/img/digital.ai-deploy.svg +++ b/microsite/static/img/digital.ai-deploy.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/microsite/static/img/digital.ai-release.svg b/microsite/static/img/digital.ai-release.svg index ba510e2a79..9f33e123a5 100644 --- a/microsite/static/img/digital.ai-release.svg +++ b/microsite/static/img/digital.ai-release.svg @@ -1,14 +1 @@ - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/microsite/static/img/dynatrace.svg b/microsite/static/img/dynatrace.svg index 863f93c123..1f10121bee 100644 --- a/microsite/static/img/dynatrace.svg +++ b/microsite/static/img/dynatrace.svg @@ -1,18 +1 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/microsite/static/img/entity-validation.svg b/microsite/static/img/entity-validation.svg index c841c8be1a..8a1ae0dc20 100644 --- a/microsite/static/img/entity-validation.svg +++ b/microsite/static/img/entity-validation.svg @@ -1,3 +1 @@ - - - \ No newline at end of file + \ No newline at end of file diff --git a/microsite/static/img/flagsmith.svg b/microsite/static/img/flagsmith.svg new file mode 100644 index 0000000000..d8039477ca --- /dev/null +++ b/microsite/static/img/flagsmith.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/microsite/static/img/github-pull-requests-board-logo.svg b/microsite/static/img/github-pull-requests-board-logo.svg index 0114b50d08..16e55afc33 100644 --- a/microsite/static/img/github-pull-requests-board-logo.svg +++ b/microsite/static/img/github-pull-requests-board-logo.svg @@ -1,30 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/microsite/static/img/hcp-consul.svg b/microsite/static/img/hcp-consul.svg index d84972585c..4716f37348 100644 --- a/microsite/static/img/hcp-consul.svg +++ b/microsite/static/img/hcp-consul.svg @@ -1,17 +1 @@ - -Consul Logo - - - - - - - - - - - - - - - +Consul Logo \ No newline at end of file diff --git a/microsite/static/img/incident-io-logo.png b/microsite/static/img/incident-io-logo.png new file mode 100644 index 0000000000..ac12718808 Binary files /dev/null and b/microsite/static/img/incident-io-logo.png differ diff --git a/microsite/static/img/jfrog-artifactory.svg b/microsite/static/img/jfrog-artifactory.svg index ded672b4bc..b43374db61 100644 --- a/microsite/static/img/jfrog-artifactory.svg +++ b/microsite/static/img/jfrog-artifactory.svg @@ -1,22 +1 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/microsite/static/img/keycloak.svg b/microsite/static/img/keycloak.svg index 871bd5b0f8..16e9c5568f 100644 --- a/microsite/static/img/keycloak.svg +++ b/microsite/static/img/keycloak.svg @@ -1,22 +1 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/microsite/static/img/kiali.svg b/microsite/static/img/kiali.svg index bfc8510040..db7db43a3e 100644 --- a/microsite/static/img/kiali.svg +++ b/microsite/static/img/kiali.svg @@ -1,22 +1 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/microsite/static/img/linguist.svg b/microsite/static/img/linguist.svg index 3cddc1affc..89a788a275 100644 --- a/microsite/static/img/linguist.svg +++ b/microsite/static/img/linguist.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/microsite/static/img/nexus-repository-manager.svg b/microsite/static/img/nexus-repository-manager.svg index 6f145811cc..144fbb00d4 100644 --- a/microsite/static/img/nexus-repository-manager.svg +++ b/microsite/static/img/nexus-repository-manager.svg @@ -1,22 +1 @@ - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/microsite/static/img/nobl9.svg b/microsite/static/img/nobl9.svg index a4065df602..486fe446e3 100644 --- a/microsite/static/img/nobl9.svg +++ b/microsite/static/img/nobl9.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/microsite/static/img/ocm.svg b/microsite/static/img/ocm.svg index 6ee5c74f9e..f53f1f91cf 100644 --- a/microsite/static/img/ocm.svg +++ b/microsite/static/img/ocm.svg @@ -1,22 +1 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/microsite/static/img/octopus-deploy.svg b/microsite/static/img/octopus-deploy.svg index 45d8447277..fe077ea54e 100644 --- a/microsite/static/img/octopus-deploy.svg +++ b/microsite/static/img/octopus-deploy.svg @@ -1,13 +1 @@ - - - - - - + \ No newline at end of file diff --git a/microsite/static/img/partner-logo-frontside-blk.png b/microsite/static/img/partner-logo-frontside-blk.png index 16b14ac588..87f0fec87c 100644 Binary files a/microsite/static/img/partner-logo-frontside-blk.png and b/microsite/static/img/partner-logo-frontside-blk.png differ diff --git a/microsite/static/img/partner-logo-frontside.png b/microsite/static/img/partner-logo-frontside.png index c8cad6bce1..ecce0b5d69 100644 Binary files a/microsite/static/img/partner-logo-frontside.png and b/microsite/static/img/partner-logo-frontside.png differ diff --git a/microsite/static/img/partner-logo-roadie-blk.png b/microsite/static/img/partner-logo-roadie-blk.png index a837d3f6f5..2c5ad25be5 100644 Binary files a/microsite/static/img/partner-logo-roadie-blk.png and b/microsite/static/img/partner-logo-roadie-blk.png differ diff --git a/microsite/static/img/partner-logo-roadie.png b/microsite/static/img/partner-logo-roadie.png index 41b169f55b..23182e602a 100644 Binary files a/microsite/static/img/partner-logo-roadie.png and b/microsite/static/img/partner-logo-roadie.png differ diff --git a/microsite/static/img/partner-logo-spotify-blk.png b/microsite/static/img/partner-logo-spotify-blk.png new file mode 100644 index 0000000000..cfe9107765 Binary files /dev/null and b/microsite/static/img/partner-logo-spotify-blk.png differ diff --git a/microsite/static/img/partner-logo-spotify.png b/microsite/static/img/partner-logo-spotify.png new file mode 100644 index 0000000000..826ce233a9 Binary files /dev/null and b/microsite/static/img/partner-logo-spotify.png differ diff --git a/microsite/static/img/partner-logo-statusneo-blk.png b/microsite/static/img/partner-logo-statusneo-blk.png index 78fb9bd10e..77eeb704bb 100644 Binary files a/microsite/static/img/partner-logo-statusneo-blk.png and b/microsite/static/img/partner-logo-statusneo-blk.png differ diff --git a/microsite/static/img/partner-logo-statusneo.png b/microsite/static/img/partner-logo-statusneo.png index 61a676a220..1502de6e2b 100644 Binary files a/microsite/static/img/partner-logo-statusneo.png and b/microsite/static/img/partner-logo-statusneo.png differ diff --git a/microsite/static/img/partner-logo-thoughtworks-blk.png b/microsite/static/img/partner-logo-thoughtworks-blk.png index a9a4894ccf..87cb928c27 100644 Binary files a/microsite/static/img/partner-logo-thoughtworks-blk.png and b/microsite/static/img/partner-logo-thoughtworks-blk.png differ diff --git a/microsite/static/img/partner-logo-thoughtworks.png b/microsite/static/img/partner-logo-thoughtworks.png index 53c3bc2a9e..a481f81143 100644 Binary files a/microsite/static/img/partner-logo-thoughtworks.png and b/microsite/static/img/partner-logo-thoughtworks.png differ diff --git a/microsite/static/img/plugin-feedback-logo.svg b/microsite/static/img/plugin-feedback-logo.svg index bd8014f9c7..da5dc0b46d 100644 --- a/microsite/static/img/plugin-feedback-logo.svg +++ b/microsite/static/img/plugin-feedback-logo.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/microsite/static/img/quay.svg b/microsite/static/img/quay.svg index cf3c5e435e..144fbb00d4 100644 --- a/microsite/static/img/quay.svg +++ b/microsite/static/img/quay.svg @@ -1,22 +1 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/microsite/static/img/s3-bucket.svg b/microsite/static/img/s3-bucket.svg index a92ac68982..7a68bc29cf 100644 --- a/microsite/static/img/s3-bucket.svg +++ b/microsite/static/img/s3-bucket.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/microsite/static/img/shortcuts.svg b/microsite/static/img/shortcuts.svg index c53a45c0ab..f30b3545c2 100644 --- a/microsite/static/img/shortcuts.svg +++ b/microsite/static/img/shortcuts.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/microsite/static/img/tech-radar.svg b/microsite/static/img/tech-radar.svg index 350ff4f9cc..1253ab0102 100644 --- a/microsite/static/img/tech-radar.svg +++ b/microsite/static/img/tech-radar.svg @@ -1,14 +1 @@ - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/microsite/static/img/tekton.svg b/microsite/static/img/tekton.svg index a5fdd252df..c0cfa30b74 100644 --- a/microsite/static/img/tekton.svg +++ b/microsite/static/img/tekton.svg @@ -1,32 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/microsite/static/img/terraform-logo.svg b/microsite/static/img/terraform-logo.svg index 9ec37bdab4..a23001feb0 100644 --- a/microsite/static/img/terraform-logo.svg +++ b/microsite/static/img/terraform-logo.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/microsite/static/img/topology.svg b/microsite/static/img/topology.svg index 21d84a888a..a848f79203 100644 --- a/microsite/static/img/topology.svg +++ b/microsite/static/img/topology.svg @@ -1,22 +1 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/microsite/static/img/wheel-of-names.svg b/microsite/static/img/wheel-of-names.svg index d6f19a06fb..b10efb2798 100644 --- a/microsite/static/img/wheel-of-names.svg +++ b/microsite/static/img/wheel-of-names.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/microsite/static/robots.txt b/microsite/static/robots.txt new file mode 100644 index 0000000000..b67550f16d --- /dev/null +++ b/microsite/static/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://backstage.io/sitemap.xml diff --git a/microsite/tsconfig.json b/microsite/tsconfig.json index 9a5106747e..c51398e2ed 100644 --- a/microsite/tsconfig.json +++ b/microsite/tsconfig.json @@ -9,5 +9,6 @@ "@docusaurus/theme-classic", "@types/webpack-env" ] - } + }, + "exclude": ["node_modules", "build", ".docusaurus", "static", "blog"] } diff --git a/microsite/yarn.lock b/microsite/yarn.lock index 20c3f39fbd..3f17fdd373 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -5,126 +5,94 @@ __metadata: version: 8 cacheKey: 10 -"@algolia/autocomplete-core@npm:1.9.3": - version: 1.9.3 - resolution: "@algolia/autocomplete-core@npm:1.9.3" +"@algolia/abtesting@npm:1.13.0": + version: 1.13.0 + resolution: "@algolia/abtesting@npm:1.13.0" dependencies: - "@algolia/autocomplete-plugin-algolia-insights": "npm:1.9.3" - "@algolia/autocomplete-shared": "npm:1.9.3" - checksum: 10/a0d195ecde8027f99d40f45a16ecc6db74302063576627f1660fc206d4a9a26fdfcbb4e21a9a6b7812f4f9d378eaa9a4d5899d8ccc9a8fc75cbbad3bb73fd13c + "@algolia/client-common": "npm:5.47.0" + "@algolia/requester-browser-xhr": "npm:5.47.0" + "@algolia/requester-fetch": "npm:5.47.0" + "@algolia/requester-node-http": "npm:5.47.0" + checksum: 10/e2523f868d47b7d2a19849fe3d68b96bc30d8d92cd8f48b42656c2a512c6ebc20fd87edef14a0ea1bf4fd4faaef3df0e8ed8092c128f1bfac3ad3d7ff1fff621 languageName: node linkType: hard -"@algolia/autocomplete-plugin-algolia-insights@npm:1.9.3": - version: 1.9.3 - resolution: "@algolia/autocomplete-plugin-algolia-insights@npm:1.9.3" +"@algolia/client-abtesting@npm:5.47.0": + version: 5.47.0 + resolution: "@algolia/client-abtesting@npm:5.47.0" dependencies: - "@algolia/autocomplete-shared": "npm:1.9.3" - peerDependencies: - search-insights: ">= 1 < 3" - checksum: 10/de0ddbf4813ac7403dbd1a91cdda950cfecff9cfd23bb5e5823dd2e2666b75c73241daf00c9d002446b625f402381fa23d72f16bb3f848a763f0b3c9851cad43 + "@algolia/client-common": "npm:5.47.0" + "@algolia/requester-browser-xhr": "npm:5.47.0" + "@algolia/requester-fetch": "npm:5.47.0" + "@algolia/requester-node-http": "npm:5.47.0" + checksum: 10/c4217e4fb076e5d99106b3d1f0ec54a22c7a78e060fde55e70296896bce30ece4093fcef87c8a32fd15e4e133bff1e7a9d66811fc878964d91038209082db63c languageName: node linkType: hard -"@algolia/autocomplete-preset-algolia@npm:1.9.3": - version: 1.9.3 - resolution: "@algolia/autocomplete-preset-algolia@npm:1.9.3" +"@algolia/client-analytics@npm:5.47.0": + version: 5.47.0 + resolution: "@algolia/client-analytics@npm:5.47.0" dependencies: - "@algolia/autocomplete-shared": "npm:1.9.3" - peerDependencies: - "@algolia/client-search": ">= 4.9.1 < 6" - algoliasearch: ">= 4.9.1 < 6" - checksum: 10/a0df95f377a9db4fe33207e59534cbd80893b1f3eb5fb631dc4b481f0afeaf47135da916500550b52a63d073d1d89df439407efd232201ead7ad65dcbcdce208 + "@algolia/client-common": "npm:5.47.0" + "@algolia/requester-browser-xhr": "npm:5.47.0" + "@algolia/requester-fetch": "npm:5.47.0" + "@algolia/requester-node-http": "npm:5.47.0" + checksum: 10/7fe3fcb31c67688c57b9abd16f0e5498eac96879ba1199aa00fef67dba8921b5ff9c0c36725f5700f89cf1bf75ff93564c5eb1b346c5e01f71c0817442de74fd languageName: node linkType: hard -"@algolia/autocomplete-shared@npm:1.9.3": - version: 1.9.3 - resolution: "@algolia/autocomplete-shared@npm:1.9.3" - peerDependencies: - "@algolia/client-search": ">= 4.9.1 < 6" - algoliasearch: ">= 4.9.1 < 6" - checksum: 10/2332d12268b7f9e8cd54954dace9d78791c44c44477b3dc37e15868f2244ae6cbf6f9650c1399a984646d37cf647f35284ecddbfcd98355925fae44ff4b11a4e +"@algolia/client-common@npm:5.47.0": + version: 5.47.0 + resolution: "@algolia/client-common@npm:5.47.0" + checksum: 10/b19a3b209d2a7d13d16f1380cb343b96513d7ee2bc5fcc85d8386e9f5dcccdab158b59e2fa0a66cf2dc428b2c9fbda4322facfce97e3d170f7369de8e667f728 languageName: node linkType: hard -"@algolia/cache-browser-local-storage@npm:4.20.0": - version: 4.20.0 - resolution: "@algolia/cache-browser-local-storage@npm:4.20.0" +"@algolia/client-insights@npm:5.47.0": + version: 5.47.0 + resolution: "@algolia/client-insights@npm:5.47.0" dependencies: - "@algolia/cache-common": "npm:4.20.0" - checksum: 10/b9ca7e190ab77ddf4d30d22223345f69fc89899aa6887ee716e4ffcef14c8c9d28b782cb7cc96a0f04eed95a989878a6feca5b9aa6add0cd1846222c3308bb65 + "@algolia/client-common": "npm:5.47.0" + "@algolia/requester-browser-xhr": "npm:5.47.0" + "@algolia/requester-fetch": "npm:5.47.0" + "@algolia/requester-node-http": "npm:5.47.0" + checksum: 10/cf78729b78d26400cd53282afd53ffba272e7327861468aee65c854ba2796389853d3e2e7a5d2660fc1cf0dab169b0c40eecdf5c4c0997f6c2b3117098c0027e languageName: node linkType: hard -"@algolia/cache-common@npm:4.20.0": - version: 4.20.0 - resolution: "@algolia/cache-common@npm:4.20.0" - checksum: 10/a46377de8a309feea109aae1283fc9157c73766a4c51e3085870a1fc49f6e33698814379f3bbdf475713fa0663dace86fc90f0466e64469b1b885a0538abace4 - languageName: node - linkType: hard - -"@algolia/cache-in-memory@npm:4.20.0": - version: 4.20.0 - resolution: "@algolia/cache-in-memory@npm:4.20.0" +"@algolia/client-personalization@npm:5.47.0": + version: 5.47.0 + resolution: "@algolia/client-personalization@npm:5.47.0" dependencies: - "@algolia/cache-common": "npm:4.20.0" - checksum: 10/3d67dcfae431605c8b9b1502f14865722f13b97b2822e1e3ed53bbf7bf66a120a825ccf5ed03476ebdf4aa15482dad5bfc6c2c93d81f07f862c373c689f49317 + "@algolia/client-common": "npm:5.47.0" + "@algolia/requester-browser-xhr": "npm:5.47.0" + "@algolia/requester-fetch": "npm:5.47.0" + "@algolia/requester-node-http": "npm:5.47.0" + checksum: 10/86d6732899256608d2fa26dc827e6a3f6d9f8aa08e2441be6bbb759cad23eabbf8bd6e474285bf864a122e9d4754e123456527a960a1e3368a37d5b8711f71d1 languageName: node linkType: hard -"@algolia/client-account@npm:4.20.0": - version: 4.20.0 - resolution: "@algolia/client-account@npm:4.20.0" +"@algolia/client-query-suggestions@npm:5.47.0": + version: 5.47.0 + resolution: "@algolia/client-query-suggestions@npm:5.47.0" dependencies: - "@algolia/client-common": "npm:4.20.0" - "@algolia/client-search": "npm:4.20.0" - "@algolia/transporter": "npm:4.20.0" - checksum: 10/b59e9c7a324bbfba4abdab3f41d333522eb1abce7dab74e69d297acd9ee2a3c60e82e5e9db42e6a46b5ea26a35728533e6e4ff846c631b588ceb73d14dcbc5fb + "@algolia/client-common": "npm:5.47.0" + "@algolia/requester-browser-xhr": "npm:5.47.0" + "@algolia/requester-fetch": "npm:5.47.0" + "@algolia/requester-node-http": "npm:5.47.0" + checksum: 10/b067110f7f3c655c1fb0cb97b66829ae21e9bc379f56174b159d83f0e44d53732be46cfc201dc12a6d95413a9e3a06431fcae8fa7cc6fb029e49113c975a40e5 languageName: node linkType: hard -"@algolia/client-analytics@npm:4.20.0": - version: 4.20.0 - resolution: "@algolia/client-analytics@npm:4.20.0" +"@algolia/client-search@npm:5.47.0": + version: 5.47.0 + resolution: "@algolia/client-search@npm:5.47.0" dependencies: - "@algolia/client-common": "npm:4.20.0" - "@algolia/client-search": "npm:4.20.0" - "@algolia/requester-common": "npm:4.20.0" - "@algolia/transporter": "npm:4.20.0" - checksum: 10/f46d47fdd12fc2e458599936f1fd54e346cfb161df35256291744eb39999294644d79befbbfa980b05f6adc98f7023c8a6de84f21a5c6d7eeadf846ada1ee155 - languageName: node - linkType: hard - -"@algolia/client-common@npm:4.20.0": - version: 4.20.0 - resolution: "@algolia/client-common@npm:4.20.0" - dependencies: - "@algolia/requester-common": "npm:4.20.0" - "@algolia/transporter": "npm:4.20.0" - checksum: 10/7418ba5a002fde8844da0f92d6779f2307520eb249737f20afe4679e4ab91eb2a5bf0606131dea3119f76e5c10b79a14ce62a460b1ed9b260ce730718e64b1bb - languageName: node - linkType: hard - -"@algolia/client-personalization@npm:4.20.0": - version: 4.20.0 - resolution: "@algolia/client-personalization@npm:4.20.0" - dependencies: - "@algolia/client-common": "npm:4.20.0" - "@algolia/requester-common": "npm:4.20.0" - "@algolia/transporter": "npm:4.20.0" - checksum: 10/987715df85fbefa9bc2eb96c09080a30e93c3fdd3d52b42a268c93f0d49dc2184bb06d7d1922029e1285b6ed66858ab2c7edff865a0d4bf40f3b44ca26785727 - languageName: node - linkType: hard - -"@algolia/client-search@npm:4.20.0": - version: 4.20.0 - resolution: "@algolia/client-search@npm:4.20.0" - dependencies: - "@algolia/client-common": "npm:4.20.0" - "@algolia/requester-common": "npm:4.20.0" - "@algolia/transporter": "npm:4.20.0" - checksum: 10/e82b56404be4447df491554c988db748463795f4c399cc5375a38531ab201d01bf6c937bd26a76dcef94855ff27fe4c779668732bc001691b34b3a9047852551 + "@algolia/client-common": "npm:5.47.0" + "@algolia/requester-browser-xhr": "npm:5.47.0" + "@algolia/requester-fetch": "npm:5.47.0" + "@algolia/requester-node-http": "npm:5.47.0" + checksum: 10/4a090c1dd0cd04c4aa21d860de2ae2120e6ae533a97aaa96b579a0a832d6dcfd1de9c5a49d71d0067cba43d0c5f538f70a20e11ea4e519c6041566225acde82b languageName: node linkType: hard @@ -135,65 +103,66 @@ __metadata: languageName: node linkType: hard -"@algolia/logger-common@npm:4.20.0": - version: 4.20.0 - resolution: "@algolia/logger-common@npm:4.20.0" - checksum: 10/06ed28f76b630c8e7597534b15138ab6f71c10dfc6e13f1fb1b76965b39c88fd1d9cb3fe6bb9d046de6533ebcbe5ad92e751bc36fabe98ceda39d1d5f47bb637 - languageName: node - linkType: hard - -"@algolia/logger-console@npm:4.20.0": - version: 4.20.0 - resolution: "@algolia/logger-console@npm:4.20.0" +"@algolia/ingestion@npm:1.47.0": + version: 1.47.0 + resolution: "@algolia/ingestion@npm:1.47.0" dependencies: - "@algolia/logger-common": "npm:4.20.0" - checksum: 10/721dffe37563e2998d4c361f09a05736b4baa141bfb7da25d50f890ba8257ac99845dd94b43d0d6db38e2fdab96508a726e184a00e5b1e83ef18a16da6fc716c + "@algolia/client-common": "npm:5.47.0" + "@algolia/requester-browser-xhr": "npm:5.47.0" + "@algolia/requester-fetch": "npm:5.47.0" + "@algolia/requester-node-http": "npm:5.47.0" + checksum: 10/ca2fe63092d7a208dc9ad7ddef0a091746ea7cca3992b072a06c1a1cb3d7ae5b648e475bb899d5f577b1c643056ccd69a98dc4042e39d8d9431df3dfd6659117 languageName: node linkType: hard -"@algolia/requester-browser-xhr@npm:4.20.0": - version: 4.20.0 - resolution: "@algolia/requester-browser-xhr@npm:4.20.0" +"@algolia/monitoring@npm:1.47.0": + version: 1.47.0 + resolution: "@algolia/monitoring@npm:1.47.0" dependencies: - "@algolia/requester-common": "npm:4.20.0" - checksum: 10/669790c7dfd491318976b9d61d98d9785880d7385ba33669f3f8b9c66ea88320bcded82d34f58b5df74b2cb8beb62ef48a28d39117f7997be84348c9fa7f6132 + "@algolia/client-common": "npm:5.47.0" + "@algolia/requester-browser-xhr": "npm:5.47.0" + "@algolia/requester-fetch": "npm:5.47.0" + "@algolia/requester-node-http": "npm:5.47.0" + checksum: 10/a60e8db8b7eff490f9e747554a94359c2e2f3a1b58045be299355f60adf93e2183aacbb83aa832be3bbcf01e0af4f2ea07e21b7c1c90645b92f659968c639ac5 languageName: node linkType: hard -"@algolia/requester-common@npm:4.20.0": - version: 4.20.0 - resolution: "@algolia/requester-common@npm:4.20.0" - checksum: 10/8d7aa1d8fc6f0e18ce759845af8150028e376bc85242a9e8db8e6ba3e71a7aaab8e7adfbf7db60b827c33861c59519bd67a7f6f44916b46827766ccb2907cfca - languageName: node - linkType: hard - -"@algolia/requester-node-http@npm:4.20.0": - version: 4.20.0 - resolution: "@algolia/requester-node-http@npm:4.20.0" +"@algolia/recommend@npm:5.47.0": + version: 5.47.0 + resolution: "@algolia/recommend@npm:5.47.0" dependencies: - "@algolia/requester-common": "npm:4.20.0" - checksum: 10/7857114b59c67e0d22e8a7ff3f755d11534a1602a4fc80802d3b35802777880a4980420914ea4a6e3e21198f5bacb95906289ce1bb9372458bf6a60a723bee59 + "@algolia/client-common": "npm:5.47.0" + "@algolia/requester-browser-xhr": "npm:5.47.0" + "@algolia/requester-fetch": "npm:5.47.0" + "@algolia/requester-node-http": "npm:5.47.0" + checksum: 10/ab57264e408a438ab5ad73168b631172afd65a635ca4d39c2c047932545fb494e1fcd7a23c1b8da11f22ed1e184800d5d5736112e36bd0b601bab2bf9e97983b languageName: node linkType: hard -"@algolia/transporter@npm:4.20.0": - version: 4.20.0 - resolution: "@algolia/transporter@npm:4.20.0" +"@algolia/requester-browser-xhr@npm:5.47.0": + version: 5.47.0 + resolution: "@algolia/requester-browser-xhr@npm:5.47.0" dependencies: - "@algolia/cache-common": "npm:4.20.0" - "@algolia/logger-common": "npm:4.20.0" - "@algolia/requester-common": "npm:4.20.0" - checksum: 10/d02db1b3fe18f4ab08e6ea90407c9dd8e09344ec9271f0eefbfea5ddc863ce8cc7e08e4854bbe67281ea5422bc5cc00c215041f6315f71f22d10f12270b8247c + "@algolia/client-common": "npm:5.47.0" + checksum: 10/6afed1f690c01969c9ef25a7368a2ea201eee155c268bf6082d0d71c0f77803779b8632a38281f3d4794c5140104e607fce8054f8143c9258b1fffc1409b31f0 languageName: node linkType: hard -"@ampproject/remapping@npm:^2.2.0": - version: 2.2.1 - resolution: "@ampproject/remapping@npm:2.2.1" +"@algolia/requester-fetch@npm:5.47.0": + version: 5.47.0 + resolution: "@algolia/requester-fetch@npm:5.47.0" dependencies: - "@jridgewell/gen-mapping": "npm:^0.3.0" - "@jridgewell/trace-mapping": "npm:^0.3.9" - checksum: 10/e15fecbf3b54c988c8b4fdea8ef514ab482537e8a080b2978cc4b47ccca7140577ca7b65ad3322dcce65bc73ee6e5b90cbfe0bbd8c766dad04d5c62ec9634c42 + "@algolia/client-common": "npm:5.47.0" + checksum: 10/6dc1b107af2259d99e1a416f3bb8524c66fa5f75ab7870fb26dccc022bfaf6e23ab56e5bb029783278773598e2136605195512435a0fd16583fb611ed72a742e + languageName: node + linkType: hard + +"@algolia/requester-node-http@npm:5.47.0": + version: 5.47.0 + resolution: "@algolia/requester-node-http@npm:5.47.0" + dependencies: + "@algolia/client-common": "npm:5.47.0" + checksum: 10/36f48c3328da48818e88497fd463afaa4bb492883a030824d1c947eec244e8bf9f60b9801fa8340dab8028a4da57db0eb9d8de1c839381f18076e803bd4ad8af languageName: node linkType: hard @@ -208,367 +177,328 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.16.0, @babel/code-frame@npm:^7.24.7, @babel/code-frame@npm:^7.8.3": - version: 7.24.7 - resolution: "@babel/code-frame@npm:7.24.7" +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/code-frame@npm:7.28.6" dependencies: - "@babel/highlight": "npm:^7.24.7" - picocolors: "npm:^1.0.0" - checksum: 10/4812e94885ba7e3213d49583a155fdffb05292330f0a9b2c41b49288da70cf3c746a3fda0bf1074041a6d741c33f8d7be24be5e96f41ef77395eeddc5c9ff624 + "@babel/helper-validator-identifier": "npm:^7.28.5" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.1.1" + checksum: 10/93e7ed9e039e3cb661bdb97c26feebafacc6ec13d745881dae5c7e2708f579475daebe7a3b5d23b183bb940b30744f52f4a5bcb65b4df03b79d82fcb38495784 languageName: node linkType: hard -"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.25.2, @babel/compat-data@npm:^7.25.4": - version: 7.25.4 - resolution: "@babel/compat-data@npm:7.25.4" - checksum: 10/d37a8936cc355a9ca3050102e03d179bdae26bd2e5c99a977637376c192b23637a039795f153c849437a086727628c9860e2c6af92d7151396e2362c09176337 +"@babel/compat-data@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/compat-data@npm:7.28.6" + checksum: 10/dc17dfb55711a15f006e34c4610c49b7335fc11b23e192f9e5f625e8ea0f48805e61a57b6b4f5550879332782c93af0b5d6952825fffbb8d4e604b14d698249f languageName: node linkType: hard -"@babel/core@npm:^7.21.3, @babel/core@npm:^7.23.3": - version: 7.25.2 - resolution: "@babel/core@npm:7.25.2" +"@babel/core@npm:^7.21.3, @babel/core@npm:^7.25.9": + version: 7.28.6 + resolution: "@babel/core@npm:7.28.6" dependencies: - "@ampproject/remapping": "npm:^2.2.0" - "@babel/code-frame": "npm:^7.24.7" - "@babel/generator": "npm:^7.25.0" - "@babel/helper-compilation-targets": "npm:^7.25.2" - "@babel/helper-module-transforms": "npm:^7.25.2" - "@babel/helpers": "npm:^7.25.0" - "@babel/parser": "npm:^7.25.0" - "@babel/template": "npm:^7.25.0" - "@babel/traverse": "npm:^7.25.2" - "@babel/types": "npm:^7.25.2" + "@babel/code-frame": "npm:^7.28.6" + "@babel/generator": "npm:^7.28.6" + "@babel/helper-compilation-targets": "npm:^7.28.6" + "@babel/helper-module-transforms": "npm:^7.28.6" + "@babel/helpers": "npm:^7.28.6" + "@babel/parser": "npm:^7.28.6" + "@babel/template": "npm:^7.28.6" + "@babel/traverse": "npm:^7.28.6" + "@babel/types": "npm:^7.28.6" + "@jridgewell/remapping": "npm:^2.3.5" convert-source-map: "npm:^2.0.0" debug: "npm:^4.1.0" gensync: "npm:^1.0.0-beta.2" json5: "npm:^2.2.3" semver: "npm:^6.3.1" - checksum: 10/0d6ec10ff430df66f654c089d6f7ef1d9bed0c318ac257ad5f0dfa0caa45666011828ae75f998bcdb279763e892b091b2925d0bc483299e61649d2c7a2245e33 + checksum: 10/1a150a69c547daf13c457be1fdaf1a0935d02b94605e777e049537ec2f279b4bb442ffbe1c2d8ff62c688878b1d5530a5784daf72ece950d1917fb78717f51d2 languageName: node linkType: hard -"@babel/generator@npm:^7.23.3, @babel/generator@npm:^7.25.0, @babel/generator@npm:^7.25.4": - version: 7.25.5 - resolution: "@babel/generator@npm:7.25.5" +"@babel/generator@npm:^7.25.9, @babel/generator@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/generator@npm:7.28.6" dependencies: - "@babel/types": "npm:^7.25.4" - "@jridgewell/gen-mapping": "npm:^0.3.5" - "@jridgewell/trace-mapping": "npm:^0.3.25" - jsesc: "npm:^2.5.1" - checksum: 10/e6d046afe739cfa706c40c127b7436731acb2a3146d408a7d89dbf16448491b35bc09b7d285cc19c2c1f8980d74b5a99df200d67c859bb5260986614685b0770 + "@babel/parser": "npm:^7.28.6" + "@babel/types": "npm:^7.28.6" + "@jridgewell/gen-mapping": "npm:^0.3.12" + "@jridgewell/trace-mapping": "npm:^0.3.28" + jsesc: "npm:^3.0.2" + checksum: 10/ef2af927e8e0985d02ec4321a242da761a934e927539147c59fdd544034dc7f0e9846f6bf86209aca7a28aee2243ed0fad668adccd48f96d7d6866215173f9af languageName: node linkType: hard -"@babel/helper-annotate-as-pure@npm:^7.22.5, @babel/helper-annotate-as-pure@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-annotate-as-pure@npm:7.24.7" +"@babel/helper-annotate-as-pure@npm:^7.27.1, @babel/helper-annotate-as-pure@npm:^7.27.3": + version: 7.27.3 + resolution: "@babel/helper-annotate-as-pure@npm:7.27.3" dependencies: - "@babel/types": "npm:^7.24.7" - checksum: 10/a9017bfc1c4e9f2225b967fbf818004703de7cf29686468b54002ffe8d6b56e0808afa20d636819fcf3a34b89ba72f52c11bdf1d69f303928ee10d92752cad95 + "@babel/types": "npm:^7.27.3" + checksum: 10/63863a5c936ef82b546ca289c9d1b18fabfc24da5c4ee382830b124e2e79b68d626207febc8d4bffc720f50b2ee65691d7d12cc0308679dee2cd6bdc926b7190 languageName: node linkType: hard -"@babel/helper-builder-binary-assignment-operator-visitor@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-builder-binary-assignment-operator-visitor@npm:7.24.7" +"@babel/helper-compilation-targets@npm:^7.27.1, @babel/helper-compilation-targets@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helper-compilation-targets@npm:7.28.6" dependencies: - "@babel/traverse": "npm:^7.24.7" - "@babel/types": "npm:^7.24.7" - checksum: 10/3ddff45d1e086c9c6dcef53ef46521a0c11ddb09fe3ab42dca5af6bb1b1703895a9f4f8056f49fdf53c2dbf6e5cf1ddb4baf17d7e3766c63f051ab8d60a919ee - languageName: node - linkType: hard - -"@babel/helper-compilation-targets@npm:^7.22.6, @babel/helper-compilation-targets@npm:^7.24.7, @babel/helper-compilation-targets@npm:^7.24.8, @babel/helper-compilation-targets@npm:^7.25.2": - version: 7.25.2 - resolution: "@babel/helper-compilation-targets@npm:7.25.2" - dependencies: - "@babel/compat-data": "npm:^7.25.2" - "@babel/helper-validator-option": "npm:^7.24.8" - browserslist: "npm:^4.23.1" + "@babel/compat-data": "npm:^7.28.6" + "@babel/helper-validator-option": "npm:^7.27.1" + browserslist: "npm:^4.24.0" lru-cache: "npm:^5.1.1" semver: "npm:^6.3.1" - checksum: 10/eccb2d75923d2d4d596f9ff64716e8664047c4192f1b44c7d5c07701d4a3498ac2587a72ddae1046e65a501bc630eb7df4557958b08ec2dcf5b4a264a052f111 + checksum: 10/f512a5aeee4dfc6ea8807f521d085fdca8d66a7d068a6dd5e5b37da10a6081d648c0bbf66791a081e4e8e6556758da44831b331540965dfbf4f5275f3d0a8788 languageName: node linkType: hard -"@babel/helper-create-class-features-plugin@npm:^7.24.7, @babel/helper-create-class-features-plugin@npm:^7.25.0, @babel/helper-create-class-features-plugin@npm:^7.25.4": - version: 7.25.4 - resolution: "@babel/helper-create-class-features-plugin@npm:7.25.4" +"@babel/helper-create-class-features-plugin@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helper-create-class-features-plugin@npm:7.28.6" dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.24.7" - "@babel/helper-member-expression-to-functions": "npm:^7.24.8" - "@babel/helper-optimise-call-expression": "npm:^7.24.7" - "@babel/helper-replace-supers": "npm:^7.25.0" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7" - "@babel/traverse": "npm:^7.25.4" + "@babel/helper-annotate-as-pure": "npm:^7.27.3" + "@babel/helper-member-expression-to-functions": "npm:^7.28.5" + "@babel/helper-optimise-call-expression": "npm:^7.27.1" + "@babel/helper-replace-supers": "npm:^7.28.6" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" + "@babel/traverse": "npm:^7.28.6" semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/47218da9fd964af30d41f0635d9e33eed7518e03aa8f10c3eb8a563bb2c14f52be3e3199db5912ae0e26058c23bb511c811e565c55ecec09427b04b867ed13c2 + checksum: 10/11f55607fcf66827ade745c0616aa3c6086aa655c0fab665dd3c4961829752e4c94c942262db30c4831ef9bce37ad444722e85ef1b7136587e28c6b1ef8ad43c languageName: node linkType: hard -"@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.24.7, @babel/helper-create-regexp-features-plugin@npm:^7.25.0, @babel/helper-create-regexp-features-plugin@npm:^7.25.2": - version: 7.25.2 - resolution: "@babel/helper-create-regexp-features-plugin@npm:7.25.2" +"@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.27.1, @babel/helper-create-regexp-features-plugin@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/helper-create-regexp-features-plugin@npm:7.28.5" dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.24.7" - regexpu-core: "npm:^5.3.1" + "@babel/helper-annotate-as-pure": "npm:^7.27.3" + regexpu-core: "npm:^6.3.1" semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/33dd627eef9e4229aba66789efd8fb7342fc2667b821d4b7947c7294f6d472cf025ff2db9b358a1e03de98376de44e839f0611a456a57127fd6e4b4dbfc96c51 + checksum: 10/d8791350fe0479af0909aa5efb6dfd3bacda743c7c3f8fa1b0bb18fe014c206505834102ee24382df1cfe5a83b4e4083220e97f420a48b2cec15bb1ad6c7c9d3 languageName: node linkType: hard -"@babel/helper-define-polyfill-provider@npm:^0.4.2": - version: 0.4.2 - resolution: "@babel/helper-define-polyfill-provider@npm:0.4.2" +"@babel/helper-define-polyfill-provider@npm:^0.6.5, @babel/helper-define-polyfill-provider@npm:^0.6.6": + version: 0.6.6 + resolution: "@babel/helper-define-polyfill-provider@npm:0.6.6" dependencies: - "@babel/helper-compilation-targets": "npm:^7.22.6" - "@babel/helper-plugin-utils": "npm:^7.22.5" - debug: "npm:^4.1.1" + "@babel/helper-compilation-targets": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.28.6" + debug: "npm:^4.4.3" lodash.debounce: "npm:^4.0.8" - resolve: "npm:^1.14.2" + resolve: "npm:^1.22.11" peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10/6383a34af4048957e46366fa7e6228b61e140955a707f8af7b69c26b2b780880db164d08b6de9420f6ec5a0ee01eb23aa5d78a4b141f2b65b3670e71906471bf + checksum: 10/1c725c47bafb10ae4527aff6741b44ca49b18bf7005ae4583b15f992783e7c1d7687eab1a5583a373b5494160d46e91e29145280bd850e97d36b8b01bc5fef99 languageName: node linkType: hard -"@babel/helper-define-polyfill-provider@npm:^0.6.2": - version: 0.6.2 - resolution: "@babel/helper-define-polyfill-provider@npm:0.6.2" - dependencies: - "@babel/helper-compilation-targets": "npm:^7.22.6" - "@babel/helper-plugin-utils": "npm:^7.22.5" - debug: "npm:^4.1.1" - lodash.debounce: "npm:^4.0.8" - resolve: "npm:^1.14.2" - peerDependencies: - "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10/bb32ec12024d3f16e70641bc125d2534a97edbfdabbc9f69001ec9c4ce46f877c7a224c566aa6c8c510c3b0def2e43dc4433bf6a40896ba5ce0cef4ea5ccbcff +"@babel/helper-globals@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/helper-globals@npm:7.28.0" + checksum: 10/91445f7edfde9b65dcac47f4f858f68dc1661bf73332060ab67ad7cc7b313421099a2bfc4bda30c3db3842cfa1e86fffbb0d7b2c5205a177d91b22c8d7d9cb47 languageName: node linkType: hard -"@babel/helper-member-expression-to-functions@npm:^7.24.8": - version: 7.24.8 - resolution: "@babel/helper-member-expression-to-functions@npm:7.24.8" +"@babel/helper-member-expression-to-functions@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/helper-member-expression-to-functions@npm:7.28.5" dependencies: - "@babel/traverse": "npm:^7.24.8" - "@babel/types": "npm:^7.24.8" - checksum: 10/ac878761cfd0a46c081cda0da75cc186f922cf16e8ecdd0c4fb6dca4330d9fe4871b41a9976224cf9669c9e7fe0421b5c27349f2e99c125fa0be871b327fa770 + "@babel/traverse": "npm:^7.28.5" + "@babel/types": "npm:^7.28.5" + checksum: 10/05e0857cf7913f03d88ca62952d3888693c21a4f4d7cfc141c630983f71fc0a64393e05cecceb7701dfe98298f7cc38fcb735d892e3c8c6f56f112c85ee1b154 languageName: node linkType: hard -"@babel/helper-module-imports@npm:^7.22.15, @babel/helper-module-imports@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-module-imports@npm:7.24.7" +"@babel/helper-module-imports@npm:^7.27.1, @babel/helper-module-imports@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helper-module-imports@npm:7.28.6" dependencies: - "@babel/traverse": "npm:^7.24.7" - "@babel/types": "npm:^7.24.7" - checksum: 10/df8bfb2bb18413aa151ecd63b7d5deb0eec102f924f9de6bc08022ced7ed8ca7fed914562d2f6fa5b59b74a5d6e255dc35612b2bc3b8abf361e13f61b3704770 + "@babel/traverse": "npm:^7.28.6" + "@babel/types": "npm:^7.28.6" + checksum: 10/64b1380d74425566a3c288074d7ce4dea56d775d2d3325a3d4a6df1dca702916c1d268133b6f385de9ba5b822b3c6e2af5d3b11ac88e5453d5698d77264f0ec0 languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.24.7, @babel/helper-module-transforms@npm:^7.24.8, @babel/helper-module-transforms@npm:^7.25.0, @babel/helper-module-transforms@npm:^7.25.2": - version: 7.25.2 - resolution: "@babel/helper-module-transforms@npm:7.25.2" +"@babel/helper-module-transforms@npm:^7.27.1, @babel/helper-module-transforms@npm:^7.28.3, @babel/helper-module-transforms@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helper-module-transforms@npm:7.28.6" dependencies: - "@babel/helper-module-imports": "npm:^7.24.7" - "@babel/helper-simple-access": "npm:^7.24.7" - "@babel/helper-validator-identifier": "npm:^7.24.7" - "@babel/traverse": "npm:^7.25.2" + "@babel/helper-module-imports": "npm:^7.28.6" + "@babel/helper-validator-identifier": "npm:^7.28.5" + "@babel/traverse": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/a3bcf7815f3e9d8b205e0af4a8d92603d685868e45d119b621357e274996bf916216bb95ab5c6a60fde3775b91941555bf129d608e3d025b04f8aac84589f300 + checksum: 10/2e421c7db743249819ee51e83054952709dc2e197c7d5d415b4bdddc718580195704bfcdf38544b3f674efc2eccd4d29a65d38678fc827ed3934a7690984cd8b languageName: node linkType: hard -"@babel/helper-optimise-call-expression@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-optimise-call-expression@npm:7.24.7" +"@babel/helper-optimise-call-expression@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-optimise-call-expression@npm:7.27.1" dependencies: - "@babel/types": "npm:^7.24.7" - checksum: 10/da7a7f2d1bb1be4cffd5fa820bd605bc075c7dd014e0458f608bb6f34f450fe9412c8cea93e788227ab396e0e02c162d7b1db3fbcb755a6360e354c485d61df0 + "@babel/types": "npm:^7.27.1" + checksum: 10/0fb7ee824a384529d6b74f8a58279f9b56bfe3cce332168067dddeab2552d8eeb56dc8eaf86c04a3a09166a316cb92dfc79c4c623cd034ad4c563952c98b464f languageName: node linkType: hard -"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.24.7, @babel/helper-plugin-utils@npm:^7.24.8, @babel/helper-plugin-utils@npm:^7.8.0, @babel/helper-plugin-utils@npm:^7.8.3": - version: 7.24.8 - resolution: "@babel/helper-plugin-utils@npm:7.24.8" - checksum: 10/adbc9fc1142800a35a5eb0793296924ee8057fe35c61657774208670468a9fbfbb216f2d0bc46c680c5fefa785e5ff917cc1674b10bd75cdf9a6aa3444780630 +"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.24.8, @babel/helper-plugin-utils@npm:^7.27.1, @babel/helper-plugin-utils@npm:^7.28.6, @babel/helper-plugin-utils@npm:^7.8.0": + version: 7.28.6 + resolution: "@babel/helper-plugin-utils@npm:7.28.6" + checksum: 10/21c853bbc13dbdddf03309c9a0477270124ad48989e1ad6524b83e83a77524b333f92edd2caae645c5a7ecf264ec6d04a9ebe15aeb54c7f33c037b71ec521e4a languageName: node linkType: hard -"@babel/helper-remap-async-to-generator@npm:^7.24.7, @babel/helper-remap-async-to-generator@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/helper-remap-async-to-generator@npm:7.25.0" +"@babel/helper-remap-async-to-generator@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-remap-async-to-generator@npm:7.27.1" dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.24.7" - "@babel/helper-wrap-function": "npm:^7.25.0" - "@babel/traverse": "npm:^7.25.0" + "@babel/helper-annotate-as-pure": "npm:^7.27.1" + "@babel/helper-wrap-function": "npm:^7.27.1" + "@babel/traverse": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/6b1ab73a067008c92e2fe5b7a9f39aab32e7f5a8c5eaf0a864436c21791f708ad8619d4a509febdfe934aeb373af4baa7c7d9f41181b385e09f39eaf11ca108e + checksum: 10/0747397ba013f87dbf575454a76c18210d61c7c9af0f697546b4bcac670b54ddc156330234407b397f0c948738c304c228e0223039bc45eab4fbf46966a5e8cc languageName: node linkType: hard -"@babel/helper-replace-supers@npm:^7.24.7, @babel/helper-replace-supers@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/helper-replace-supers@npm:7.25.0" +"@babel/helper-replace-supers@npm:^7.27.1, @babel/helper-replace-supers@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helper-replace-supers@npm:7.28.6" dependencies: - "@babel/helper-member-expression-to-functions": "npm:^7.24.8" - "@babel/helper-optimise-call-expression": "npm:^7.24.7" - "@babel/traverse": "npm:^7.25.0" + "@babel/helper-member-expression-to-functions": "npm:^7.28.5" + "@babel/helper-optimise-call-expression": "npm:^7.27.1" + "@babel/traverse": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/97c6c17780cb9692132f7243f5a21fb6420104cb8ff8752dc03cfc9a1912a243994c0290c77ff096637ab6f2a7363b63811cfc68c2bad44e6b39460ac2f6a63f + checksum: 10/ad2724713a4d983208f509e9607e8f950855f11bd97518a700057eb8bec69d687a8f90dc2da0c3c47281d2e3b79cf1d14ecf1fe3e1ee0a8e90b61aee6759c9a7 languageName: node linkType: hard -"@babel/helper-simple-access@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-simple-access@npm:7.24.7" +"@babel/helper-skip-transparent-expression-wrappers@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.27.1" dependencies: - "@babel/traverse": "npm:^7.24.7" - "@babel/types": "npm:^7.24.7" - checksum: 10/5083e190186028e48fc358a192e4b93ab320bd016103caffcfda81302a13300ccce46c9cd255ae520c25d2a6a9b47671f93e5fe5678954a2329dc0a685465c49 + "@babel/traverse": "npm:^7.27.1" + "@babel/types": "npm:^7.27.1" + checksum: 10/4f380c5d0e0769fa6942a468b0c2d7c8f0c438f941aaa88f785f8752c103631d0904c7b4e76207a3b0e6588b2dec376595370d92ca8f8f1b422c14a69aa146d4 languageName: node linkType: hard -"@babel/helper-skip-transparent-expression-wrappers@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.24.7" +"@babel/helper-string-parser@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-string-parser@npm:7.27.1" + checksum: 10/0ae29cc2005084abdae2966afdb86ed14d41c9c37db02c3693d5022fba9f5d59b011d039380b8e537c34daf117c549f52b452398f576e908fb9db3c7abbb3a00 + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/helper-validator-identifier@npm:7.28.5" + checksum: 10/8e5d9b0133702cfacc7f368bf792f0f8ac0483794877c6dca5fcb73810ee138e27527701826fb58a40a004f3a5ec0a2f3c3dd5e326d262530b119918f3132ba7 + languageName: node + linkType: hard + +"@babel/helper-validator-option@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-validator-option@npm:7.27.1" + checksum: 10/db73e6a308092531c629ee5de7f0d04390835b21a263be2644276cb27da2384b64676cab9f22cd8d8dbd854c92b1d7d56fc8517cf0070c35d1c14a8c828b0903 + languageName: node + linkType: hard + +"@babel/helper-wrap-function@npm:^7.27.1": + version: 7.28.6 + resolution: "@babel/helper-wrap-function@npm:7.28.6" dependencies: - "@babel/traverse": "npm:^7.24.7" - "@babel/types": "npm:^7.24.7" - checksum: 10/784a6fdd251a9a7e42ccd04aca087ecdab83eddc60fda76a2950e00eb239cc937d3c914266f0cc476298b52ac3f44ffd04c358e808bd17552a7e008d75494a77 + "@babel/template": "npm:^7.28.6" + "@babel/traverse": "npm:^7.28.6" + "@babel/types": "npm:^7.28.6" + checksum: 10/d8a895a75399904746f4127db33593a20021fc55d1a5b5dfeb060b87cc13a8dceea91e70a4951bcd376ba9bd8232b0c04bff9a86c1dab83d691e01852c3b5bcd languageName: node linkType: hard -"@babel/helper-string-parser@npm:^7.24.8": - version: 7.24.8 - resolution: "@babel/helper-string-parser@npm:7.24.8" - checksum: 10/6d1bf8f27dd725ce02bdc6dffca3c95fb9ab8a06adc2edbd9c1c9d68500274230d1a609025833ed81981eff560045b6b38f7b4c6fb1ab19fc90e5004e3932535 - languageName: node - linkType: hard - -"@babel/helper-validator-identifier@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-validator-identifier@npm:7.24.7" - checksum: 10/86875063f57361471b531dbc2ea10bbf5406e12b06d249b03827d361db4cad2388c6f00936bcd9dc86479f7e2c69ea21412c2228d4b3672588b754b70a449d4b - languageName: node - linkType: hard - -"@babel/helper-validator-option@npm:^7.22.15, @babel/helper-validator-option@npm:^7.24.7, @babel/helper-validator-option@npm:^7.24.8": - version: 7.24.8 - resolution: "@babel/helper-validator-option@npm:7.24.8" - checksum: 10/a52442dfa74be6719c0608fee3225bd0493c4057459f3014681ea1a4643cd38b68ff477fe867c4b356da7330d085f247f0724d300582fa4ab9a02efaf34d107c - languageName: node - linkType: hard - -"@babel/helper-wrap-function@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/helper-wrap-function@npm:7.25.0" +"@babel/helpers@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helpers@npm:7.28.6" dependencies: - "@babel/template": "npm:^7.25.0" - "@babel/traverse": "npm:^7.25.0" - "@babel/types": "npm:^7.25.0" - checksum: 10/08724128b9c540c02a59f02f9c1c9940fe5363d85d0f30ec826a4f926afdb26fa4ec33ca2b88b4aa745fe3dbe1f44be2969b8a03af259af7945d8cd3262168d3 + "@babel/template": "npm:^7.28.6" + "@babel/types": "npm:^7.28.6" + checksum: 10/213485cdfffc4deb81fc1bf2cefed61bc825049322590ef69690e223faa300a2a4d1e7d806c723bb1f1f538226b9b1b6c356ca94eb47fa7c6d9e9f251ee425e6 languageName: node linkType: hard -"@babel/helpers@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/helpers@npm:7.25.0" +"@babel/parser@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/parser@npm:7.28.6" dependencies: - "@babel/template": "npm:^7.25.0" - "@babel/types": "npm:^7.25.0" - checksum: 10/4fcb8167eba9853e30b8b235b81b923ef7b707396b0e23d7a4fa3e811729506755576cb9ec736e8b92cf19e5a1ec61e83d182904d8e6a0953803c6bebc2e1592 - languageName: node - linkType: hard - -"@babel/highlight@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/highlight@npm:7.24.7" - dependencies: - "@babel/helper-validator-identifier": "npm:^7.24.7" - chalk: "npm:^2.4.2" - js-tokens: "npm:^4.0.0" - picocolors: "npm:^1.0.0" - checksum: 10/69b73f38cdd4f881b09b939a711e76646da34f4834f4ce141d7a49a6bb1926eab1c594148970a8aa9360398dff800f63aade4e81fafdd7c8d8a8489ea93bfec1 - languageName: node - linkType: hard - -"@babel/parser@npm:^7.25.0, @babel/parser@npm:^7.25.4": - version: 7.25.4 - resolution: "@babel/parser@npm:7.25.4" - dependencies: - "@babel/types": "npm:^7.25.4" + "@babel/types": "npm:^7.28.6" bin: parser: ./bin/babel-parser.js - checksum: 10/343b8a76c43549e370fe96f4f6d564382a6cdff60e9c3b8a594c51e4cefd58ec9945e82e8c4dfbf15ac865a04e4b29806531440760748e28568e6aec21bc9cb5 + checksum: 10/483a6fb5f9876ec9cbbb98816f2c94f39ae4d1158d35f87e1c4bf19a1f56027c96a1a3962ff0c8c46e8322a6d9e1c80d26b7f9668410df13d5b5769d9447b010 languageName: node linkType: hard -"@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.25.3": - version: 7.25.3 - resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.25.3" +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.28.5" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/traverse": "npm:^7.25.3" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/traverse": "npm:^7.28.5" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/9743feb0152f2ac686aaee6dfd41e8ea211989a459d4c2b10b531442f6865057cd1a502515634c25462b155bc58f0710267afed72396780e9b72be25370dd577 + checksum: 10/750de98b34e6d09b545ded6e635b43cbab02fe319622964175259b98f41b16052e5931c4fbd45bad8cd0a37ebdd381233edecec9ee395b8ec51f47f47d1dbcd4 languageName: node linkType: hard -"@babel/plugin-bugfix-safari-class-field-initializer-scope@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/plugin-bugfix-safari-class-field-initializer-scope@npm:7.25.0" +"@babel/plugin-bugfix-safari-class-field-initializer-scope@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-bugfix-safari-class-field-initializer-scope@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/5e504bba884a4500e71224d344efb1e70ebbeabd621e07a58f2d3c0d14a71a49c97b4989259a288cdbbfacebfea224397acf1217d26c77aebf9aa35bdd988249 + checksum: 10/eb7f4146dc01f1198ce559a90b077e58b951a07521ec414e3c7d4593bf6c4ab5c2af22242a7e9fec085e20299e0ba6ea97f44a45e84ab148141bf9eb959ad25e languageName: node linkType: hard -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.25.0" +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/f574beb1d4f723bb9b913ce379259a55b50a308364585ccb83e00d933465c26c04cbbc85a06e6d4c829279eb1021b3236133d486b3ff11cfd90ad815c8b478d2 + checksum: 10/621cfddfcc99a81e74f8b6f9101fd260b27500cb1a568e3ceae9cc8afe9aee45ac3bca3900a2b66c612b1a2366d29ef67d4df5a1c975be727eaad6906f98c2c6 languageName: node linkType: hard -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:7.24.7" +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7" - "@babel/plugin-transform-optional-chaining": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" + "@babel/plugin-transform-optional-chaining": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.13.0 - checksum: 10/887f1b8bd0ef61206ece47919fda78a32eef35da31c0d95ab8d7adc8b4722534dc5177c86c8d6d81bcf4343f3c08c6adab2b46cfd2bea8e33c6c04e51306f9cc + checksum: 10/f07aa80272bd7a46b7ba11a4644da6c9b6a5a64e848dfaffdad6f02663adefd512e1aaebe664c4dd95f7ed4f80c872c7f8db8d8e34b47aae0930b412a28711a0 languageName: node linkType: hard -"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:7.25.0" +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:7.28.6" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/traverse": "npm:^7.25.0" + "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/traverse": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/de04a9342e9a0db1673683112c83cdc52173f489f45aeed864ceba72dfba8c8588e565171e64cb2a408a09269e5fb35c6ab4ef50e3e649c4f8c0c787feb5c048 + checksum: 10/9377897aa7cba3a0b78a7c6015799ff71504b2b203329357e42ab3185d44aab07344ba33f5dd53f14d5340c1dc5a2587346343e0859538947bbab0484e72b914 languageName: node linkType: hard @@ -581,39 +511,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-async-generators@npm:^7.8.4": - version: 7.8.4 - resolution: "@babel/plugin-syntax-async-generators@npm:7.8.4" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/7ed1c1d9b9e5b64ef028ea5e755c0be2d4e5e4e3d6cf7df757b9a8c4cfa4193d268176d0f1f7fbecdda6fe722885c7fda681f480f3741d8a2d26854736f05367 - languageName: node - linkType: hard - -"@babel/plugin-syntax-class-properties@npm:^7.12.13": - version: 7.12.13 - resolution: "@babel/plugin-syntax-class-properties@npm:7.12.13" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.12.13" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/24f34b196d6342f28d4bad303612d7ff566ab0a013ce89e775d98d6f832969462e7235f3e7eaf17678a533d4be0ba45d3ae34ab4e5a9dcbda5d98d49e5efa2fc - languageName: node - linkType: hard - -"@babel/plugin-syntax-class-static-block@npm:^7.14.5": - version: 7.14.5 - resolution: "@babel/plugin-syntax-class-static-block@npm:7.14.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.14.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/3e80814b5b6d4fe17826093918680a351c2d34398a914ce6e55d8083d72a9bdde4fbaf6a2dcea0e23a03de26dc2917ae3efd603d27099e2b98380345703bf948 - languageName: node - linkType: hard - "@babel/plugin-syntax-dynamic-import@npm:^7.8.3": version: 7.8.3 resolution: "@babel/plugin-syntax-dynamic-import@npm:7.8.3" @@ -625,168 +522,47 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-export-namespace-from@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-export-namespace-from@npm:7.8.3" +"@babel/plugin-syntax-import-assertions@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-syntax-import-assertions@npm:7.28.6" dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.3" + "@babel/helper-plugin-utils": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/85740478be5b0de185228e7814451d74ab8ce0a26fcca7613955262a26e99e8e15e9da58f60c754b84515d4c679b590dbd3f2148f0f58025f4ae706f1c5a5d4a + checksum: 10/25017235e1e2c4ed892aa327a3fa10f4209cc618c6dd7806fc40c07d8d7d24a39743d3d5568b8d1c8f416cffe03c174e78874ded513c9338b07a7ab1dcbab050 languageName: node linkType: hard -"@babel/plugin-syntax-import-assertions@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-syntax-import-assertions@npm:7.24.7" +"@babel/plugin-syntax-import-attributes@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-syntax-import-attributes@npm:7.28.6" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/bd065cd73ae3dbe69e6f9167aa605da3df77d69bbad2ede95e4aa9e7af7744d5bc1838b928c77338ca62df7691a7adf6e608279be50c18e4b3c70cf77e3013d7 + checksum: 10/6c8c6a5988dbb9799d6027360d1a5ba64faabf551f2ef11ba4eade0c62253b5c85d44ddc8eb643c74b9acb2bcaa664a950bd5de9a5d4aef291c4f2a48223bb4b languageName: node linkType: hard -"@babel/plugin-syntax-import-attributes@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-syntax-import-attributes@npm:7.24.7" +"@babel/plugin-syntax-jsx@npm:^7.27.1, @babel/plugin-syntax-jsx@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-syntax-jsx@npm:7.28.6" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/22fc50bd85a491bb8d22065f330a41f60d66f2f2d7a1deb73e80c8a4b5d7a42a092a03f8da18800650eca0fc14585167cc4e5c9fab351f0d390d1592347162ae + checksum: 10/572e38f5c1bb4b8124300e7e3dd13e82ae84a21f90d3f0786c98cd05e63c78ca1f32d1cfe462dfbaf5e7d5102fa7cd8fd741dfe4f3afc2e01a3b2877dcc8c866 languageName: node linkType: hard -"@babel/plugin-syntax-import-meta@npm:^7.10.4": - version: 7.10.4 - resolution: "@babel/plugin-syntax-import-meta@npm:7.10.4" +"@babel/plugin-syntax-typescript@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-syntax-typescript@npm:7.28.6" dependencies: - "@babel/helper-plugin-utils": "npm:^7.10.4" + "@babel/helper-plugin-utils": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/166ac1125d10b9c0c430e4156249a13858c0366d38844883d75d27389621ebe651115cb2ceb6dc011534d5055719fa1727b59f39e1ab3ca97820eef3dcab5b9b - languageName: node - linkType: hard - -"@babel/plugin-syntax-json-strings@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-json-strings@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/bf5aea1f3188c9a507e16efe030efb996853ca3cadd6512c51db7233cc58f3ac89ff8c6bdfb01d30843b161cfe7d321e1bf28da82f7ab8d7e6bc5464666f354a - languageName: node - linkType: hard - -"@babel/plugin-syntax-jsx@npm:^7.22.5, @babel/plugin-syntax-jsx@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-syntax-jsx@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/a93516ae5b34868ab892a95315027d4e5e38e8bd1cfca6158f2974b0901cbb32bbe64ea10ad5b25f919ddc40c6d8113c4823372909c9c9922170c12b0b1acecb - languageName: node - linkType: hard - -"@babel/plugin-syntax-logical-assignment-operators@npm:^7.10.4": - version: 7.10.4 - resolution: "@babel/plugin-syntax-logical-assignment-operators@npm:7.10.4" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.10.4" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/aff33577037e34e515911255cdbb1fd39efee33658aa00b8a5fd3a4b903585112d037cce1cc9e4632f0487dc554486106b79ccd5ea63a2e00df4363f6d4ff886 - languageName: node - linkType: hard - -"@babel/plugin-syntax-nullish-coalescing-operator@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-nullish-coalescing-operator@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/87aca4918916020d1fedba54c0e232de408df2644a425d153be368313fdde40d96088feed6c4e5ab72aac89be5d07fef2ddf329a15109c5eb65df006bf2580d1 - languageName: node - linkType: hard - -"@babel/plugin-syntax-numeric-separator@npm:^7.10.4": - version: 7.10.4 - resolution: "@babel/plugin-syntax-numeric-separator@npm:7.10.4" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.10.4" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/01ec5547bd0497f76cc903ff4d6b02abc8c05f301c88d2622b6d834e33a5651aa7c7a3d80d8d57656a4588f7276eba357f6b7e006482f5b564b7a6488de493a1 - languageName: node - linkType: hard - -"@babel/plugin-syntax-object-rest-spread@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-object-rest-spread@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/fddcf581a57f77e80eb6b981b10658421bc321ba5f0a5b754118c6a92a5448f12a0c336f77b8abf734841e102e5126d69110a306eadb03ca3e1547cab31f5cbf - languageName: node - linkType: hard - -"@babel/plugin-syntax-optional-catch-binding@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-optional-catch-binding@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/910d90e72bc90ea1ce698e89c1027fed8845212d5ab588e35ef91f13b93143845f94e2539d831dc8d8ededc14ec02f04f7bd6a8179edd43a326c784e7ed7f0b9 - languageName: node - linkType: hard - -"@babel/plugin-syntax-optional-chaining@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-optional-chaining@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/eef94d53a1453361553c1f98b68d17782861a04a392840341bc91780838dd4e695209c783631cf0de14c635758beafb6a3a65399846ffa4386bff90639347f30 - languageName: node - linkType: hard - -"@babel/plugin-syntax-private-property-in-object@npm:^7.14.5": - version: 7.14.5 - resolution: "@babel/plugin-syntax-private-property-in-object@npm:7.14.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.14.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/b317174783e6e96029b743ccff2a67d63d38756876e7e5d0ba53a322e38d9ca452c13354a57de1ad476b4c066dbae699e0ca157441da611117a47af88985ecda - languageName: node - linkType: hard - -"@babel/plugin-syntax-top-level-await@npm:^7.14.5": - version: 7.14.5 - resolution: "@babel/plugin-syntax-top-level-await@npm:7.14.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.14.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/bbd1a56b095be7820029b209677b194db9b1d26691fe999856462e66b25b281f031f3dfd91b1619e9dcf95bebe336211833b854d0fb8780d618e35667c2d0d7e - languageName: node - linkType: hard - -"@babel/plugin-syntax-typescript@npm:^7.24.7": - version: 7.25.4 - resolution: "@babel/plugin-syntax-typescript@npm:7.25.4" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/0771b45a35fd536cd3b3a48e5eda0f53e2d4f4a0ca07377cc247efa39eaf6002ed1c478106aad2650e54aefaebcb4f34f3284c4ae9252695dbd944bf66addfb0 + checksum: 10/5c55f9c63bd36cf3d7e8db892294c8f85000f9c1526c3a1cc310d47d1e174f5c6f6605e5cc902c4636d885faba7a9f3d5e5edc6b35e4f3b1fd4c2d58d0304fa5 languageName: node linkType: hard @@ -802,466 +578,467 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-arrow-functions@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-arrow-functions@npm:7.24.7" +"@babel/plugin-transform-arrow-functions@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-arrow-functions@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/6720173645826046878015c579c2ca9d93cdba79a2832f0180f5cf147d9817c85bf9c8338b16d6bdaa71f87809b7a194a6902e6c82ec00b6354aca6b40abe5e6 + checksum: 10/62c2cc0ae2093336b1aa1376741c5ed245c0987d9e4b4c5313da4a38155509a7098b5acce582b6781cc0699381420010da2e3086353344abe0a6a0ec38961eb7 languageName: node linkType: hard -"@babel/plugin-transform-async-generator-functions@npm:^7.25.4": - version: 7.25.4 - resolution: "@babel/plugin-transform-async-generator-functions@npm:7.25.4" +"@babel/plugin-transform-async-generator-functions@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-async-generator-functions@npm:7.28.6" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/helper-remap-async-to-generator": "npm:^7.25.0" - "@babel/plugin-syntax-async-generators": "npm:^7.8.4" - "@babel/traverse": "npm:^7.25.4" + "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-remap-async-to-generator": "npm:^7.27.1" + "@babel/traverse": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/0004d910bbec3ef916acf5c7cf8b11671e65d2dd425a82f1101838b9b6243bfdf9578335584d9dedd20acc162796b687930e127c6042484e05b758af695e6cb8 + checksum: 10/b3c9e62a90808e8ad0e1608a7fd7169a5bfba3b54f0d8590495e7b0d95b25e882f45062f490e4ab6823bb9758da3619d645c9a536ae77e62cb9abe38400a8c08 languageName: node linkType: hard -"@babel/plugin-transform-async-to-generator@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-async-to-generator@npm:7.24.7" +"@babel/plugin-transform-async-to-generator@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-async-to-generator@npm:7.28.6" dependencies: - "@babel/helper-module-imports": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/helper-remap-async-to-generator": "npm:^7.24.7" + "@babel/helper-module-imports": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-remap-async-to-generator": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/b2041d9d50b09afef983c4f1dece63fdfc5a8e4646e42591db398bc4322958434d60b3cb0f5d0f9f9dbdad8577e8a1a33ba9859aacc3004bf6d25d094d20193f + checksum: 10/bca5774263ec01dd2bf71c74bbaf7baa183bf03576636b7826c3346be70c8c8cb15cff549112f2983c36885131a0afde6c443591278c281f733ee17f455aa9b1 languageName: node linkType: hard -"@babel/plugin-transform-block-scoped-functions@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-block-scoped-functions@npm:7.24.7" +"@babel/plugin-transform-block-scoped-functions@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-block-scoped-functions@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/33e2fb9f24c11889b2bacbe9c3625f738edafc2136c8206598e0422664267ec5ca9422cb4563cc42039ccfc333fb42ce5f8513382e56c5b02f934005d0d6e8ff + checksum: 10/7fb4988ca80cf1fc8345310d5edfe38e86b3a72a302675cdd09404d5064fe1d1fe1283ebe658ad2b71445ecef857bfb29a748064306b5f6c628e0084759c2201 languageName: node linkType: hard -"@babel/plugin-transform-block-scoping@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/plugin-transform-block-scoping@npm:7.25.0" +"@babel/plugin-transform-block-scoping@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-block-scoping@npm:7.28.6" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" + "@babel/helper-plugin-utils": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/981e565a8ff1e1f8d539b5ff067328517233142b131329d11e6c60405204e2a4a993828c367f7dc729a9608aabebdada869616563816e5f8f1385e91ac0fa4d6 + checksum: 10/7ab8a0856024a5360ba16c3569b739385e939bc5a15ad7d811bec8459361a9aa5ee7c5f154a4e2ce79f5d66779c19464e7532600c31a1b6f681db4eb7e1c7bde languageName: node linkType: hard -"@babel/plugin-transform-class-properties@npm:^7.25.4": - version: 7.25.4 - resolution: "@babel/plugin-transform-class-properties@npm:7.25.4" +"@babel/plugin-transform-class-properties@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-class-properties@npm:7.28.6" dependencies: - "@babel/helper-create-class-features-plugin": "npm:^7.25.4" - "@babel/helper-plugin-utils": "npm:^7.24.8" + "@babel/helper-create-class-features-plugin": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/203a21384303d66fb5d841b77cba8b8994623ff4d26d208e3d05b36858c4919626a8d74871fa4b9195310c2e7883bf180359c4f5a76481ea55190c224d9746f4 + checksum: 10/200f30d44b36a768fa3a8cf690db9e333996af2ad14d9fa1b4c91a427ed9302907873b219b4ce87517ca1014a810eb2e929a6a66be68473f72b546fc64d04fbc languageName: node linkType: hard -"@babel/plugin-transform-class-static-block@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-class-static-block@npm:7.24.7" +"@babel/plugin-transform-class-static-block@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-class-static-block@npm:7.28.6" dependencies: - "@babel/helper-create-class-features-plugin": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/plugin-syntax-class-static-block": "npm:^7.14.5" + "@babel/helper-create-class-features-plugin": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.12.0 - checksum: 10/00b4d35788bcfefb56b6a1d3506ca23f11dd55d4bb5a34eb70397c06283dc7f596cd9d40995c4a6cb897b45ad220de211f854e7a030a05e26a307c8f56b6ba4b + checksum: 10/bea7836846deefd02d9976ad1b30b5ade0d6329ecd92866db789dcf6aacfaf900b7a77031e25680f8de5ad636a771a5bdca8961361e6218d45d538ec5d9b71cc languageName: node linkType: hard -"@babel/plugin-transform-classes@npm:^7.25.4": - version: 7.25.4 - resolution: "@babel/plugin-transform-classes@npm:7.25.4" +"@babel/plugin-transform-classes@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-classes@npm:7.28.6" dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.24.7" - "@babel/helper-compilation-targets": "npm:^7.25.2" - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/helper-replace-supers": "npm:^7.25.0" - "@babel/traverse": "npm:^7.25.4" - globals: "npm:^11.1.0" + "@babel/helper-annotate-as-pure": "npm:^7.27.3" + "@babel/helper-compilation-targets": "npm:^7.28.6" + "@babel/helper-globals": "npm:^7.28.0" + "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-replace-supers": "npm:^7.28.6" + "@babel/traverse": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/17db5889803529bec366c6f0602687fdd605c2fec8cb6fe918261cb55cd89e9d8c9aa2aa6f3fd64d36492ce02d7d0752b09a284b0f833c1185f7dad9b9506310 + checksum: 10/9c3278a314d1c4bcda792bb22aced20e30c735557daf9bcc56397c0f3eb54761b21c770219e4581036a10dabda3e597321ed093bc245d5f4d561e19ceff66a6d languageName: node linkType: hard -"@babel/plugin-transform-computed-properties@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-computed-properties@npm:7.24.7" +"@babel/plugin-transform-computed-properties@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-computed-properties@npm:7.28.6" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/template": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/template": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/fecf3c770b2dd8e70be6da12d4dd0273de9d8ef4d0f46be98d56fddb3a451932cdc9bb81de3057c9acb903e05ece657886cc31886d5762afa7b0a256db0f791e + checksum: 10/4a5e270f7e1f1e9787cf7cf133d48e3c1e38eb935d29a90331a1324d7c720f589b7b626b2e6485cd5521a7a13f2dbdc89a3e46ecbe7213d5bbb631175267c4aa languageName: node linkType: hard -"@babel/plugin-transform-destructuring@npm:^7.24.8": - version: 7.24.8 - resolution: "@babel/plugin-transform-destructuring@npm:7.24.8" +"@babel/plugin-transform-destructuring@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/plugin-transform-destructuring@npm:7.28.5" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/traverse": "npm:^7.28.5" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/e3bba0bb050592615fbf062ea07ae94f99e9cf22add006eaa66ed672d67ff7051b578a5ea68a7d79f9184fb3c27c65333d86b0b8ea04f9810bcccbeea2ffbe76 + checksum: 10/9cc67d3377bc5d8063599f2eb4588f5f9a8ab3abc9b64a40c24501fb3c1f91f4d5cf281ea9f208fd6b2ef8d9d8b018dacf1bed9493334577c966cd32370a7036 languageName: node linkType: hard -"@babel/plugin-transform-dotall-regex@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-dotall-regex@npm:7.24.7" +"@babel/plugin-transform-dotall-regex@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-dotall-regex@npm:7.28.6" dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-create-regexp-features-plugin": "npm:^7.28.5" + "@babel/helper-plugin-utils": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/51b75638748f6e5adab95b711d3365b8d7757f881c178946618a43b15063ec1160b07f4aa3b116bf3f1e097a88226a01db4cae2c5c4aad4c71fe5568828a03f5 + checksum: 10/866ffbbdee77fa955063b37c75593db8dbbe46b1ebb64cc788ea437e3a9aa41cb7b9afcee617c678a32b6705baa0892ec8e5d4b8af3bbb0ab1b254514ccdbd37 languageName: node linkType: hard -"@babel/plugin-transform-duplicate-keys@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-duplicate-keys@npm:7.24.7" +"@babel/plugin-transform-duplicate-keys@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-duplicate-keys@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/4284d8fe058c838f80d594bace1380ce02995fa9a271decbece59c40815bc2f7e715807dcbe4d5da8b444716e6d05cc6d79771f500fb044cd0dd00ce4324b619 + checksum: 10/987b718d2fab7626f61b72325c8121ead42341d6f46ad3a9b5e5f67f3ec558c903f1b8336277ffc43caac504ce00dd23a5456b5d1da23913333e1da77751f08d languageName: node linkType: hard -"@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:7.25.0" +"@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:7.28.6" dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.25.0" - "@babel/helper-plugin-utils": "npm:^7.24.8" + "@babel/helper-create-regexp-features-plugin": "npm:^7.28.5" + "@babel/helper-plugin-utils": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/869c08def8eb80e3619c77e7af962dd82323a8447697298f461624077593c7b7082fc2238989880a0c0ba94bc6442300fd23e33255ac225760bc8bb755268941 + checksum: 10/3f2e2b85199adfdc3297983412c2ecdacc0004bc5ac3263d29909219b8c5afa2ca49e3b6efc11ce67034d5780eef27882a94873444cf27d841d7fa7f01d7dcff languageName: node linkType: hard -"@babel/plugin-transform-dynamic-import@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-dynamic-import@npm:7.24.7" +"@babel/plugin-transform-dynamic-import@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-dynamic-import@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/plugin-syntax-dynamic-import": "npm:^7.8.3" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/e949c02aa57098d916eb6edcbef0f3f7d62640f37e1a061b0692523964e081f8182f2c4292173b4dbea4edb8d146e65d6a20ce4b6b5f8c33be34bd846ae114ea + checksum: 10/7a9fbc8d17148b7f11a1d1ca3990d2c2cd44bd08a45dcaf14f20a017721235b9044b20e6168b6940282bb1b48fb78e6afbdfb9dd9d82fde614e15baa7d579932 languageName: node linkType: hard -"@babel/plugin-transform-exponentiation-operator@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.24.7" +"@babel/plugin-transform-explicit-resource-management@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-explicit-resource-management@npm:7.28.6" dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/plugin-transform-destructuring": "npm:^7.28.5" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/014b211f73a524ee98441541ddc4f6b067eefcf94d509e99074a45ea8c3f3ad0e36cab6f5f96666ac05b747a21fa6fda949aa25153656bb2821545a4b302e0d4 + checksum: 10/36d638a253dbdaee5548b4ddd21c04ee4e39914b207437bb64cf79bb41c2caadb4321768d3dba308c1016702649bc44efe751e2052de393004563c7376210d86 languageName: node linkType: hard -"@babel/plugin-transform-export-namespace-from@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-export-namespace-from@npm:7.24.7" +"@babel/plugin-transform-exponentiation-operator@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.28.6" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/plugin-syntax-export-namespace-from": "npm:^7.8.3" + "@babel/helper-plugin-utils": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/d59d21945d2fd1ead914bb21f909f75b70ebe0e7627c2b1326ce500babca4c8e4a2513af6899d92e06e87186c61ee5087209345f5102fb4ff5a0e47e7b159a2c + checksum: 10/b232152499370435c7cd4bf3321f58e189150e35ca3722ea16533d33434b97294df1342f5499671ec48e62b71c34cdea0ca8cf317ad12594a10f6fc670315e62 languageName: node linkType: hard -"@babel/plugin-transform-for-of@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-for-of@npm:7.24.7" +"@babel/plugin-transform-export-namespace-from@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-export-namespace-from@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/ea471ad1345f1153f7f72f1f084e74f48dc349272ca1b2d8710b841b015c9861d673e12c3c98d42ab3c640cb6ab88bb9a8da1f4ca9c57a8f71f00815fa23ecef + checksum: 10/85082923eca317094f08f4953d8ea2a6558b3117826c0b740676983902b7236df1f4213ad844cb38c2dae104753dbe8f1cc51f01567835d476d32f5f544a4385 languageName: node linkType: hard -"@babel/plugin-transform-function-name@npm:^7.25.1": - version: 7.25.1 - resolution: "@babel/plugin-transform-function-name@npm:7.25.1" +"@babel/plugin-transform-for-of@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-for-of@npm:7.27.1" dependencies: - "@babel/helper-compilation-targets": "npm:^7.24.8" - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/traverse": "npm:^7.25.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/1b4cd214c8523f7fa024fcda540ffe5503eda0e0be08b7c21405c96a870b5fe8bb1bda9e23a43a31467bf3dfc3a08edca250cf7f55f09dc40759a1ca6c6d6a4a + checksum: 10/705c591d17ef263c309bba8c38e20655e8e74ff7fd21883a9cdaf5bf1df42d724383ad3d88ac01f42926e15b1e1e66f2f7f8c4e87de955afffa290d52314b019 languageName: node linkType: hard -"@babel/plugin-transform-json-strings@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-json-strings@npm:7.24.7" +"@babel/plugin-transform-function-name@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-function-name@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/plugin-syntax-json-strings": "npm:^7.8.3" + "@babel/helper-compilation-targets": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/traverse": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/5549dc97fc2d429a089d14ccfd51d8b3ba23c39b79edfe6d754e804fb1d50e6a4c070e73550be514a919c4db1553d8e6f7406178d68756b5959afe025a602cb2 + checksum: 10/26a2a183c3c52a96495967420a64afc5a09f743a230272a131668abf23001e393afa6371e6f8e6c60f4182bea210ed31d1caf866452d91009c1daac345a52f23 languageName: node linkType: hard -"@babel/plugin-transform-literals@npm:^7.25.2": - version: 7.25.2 - resolution: "@babel/plugin-transform-literals@npm:7.25.2" +"@babel/plugin-transform-json-strings@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-json-strings@npm:7.28.6" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" + "@babel/helper-plugin-utils": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/d9728625a6d55305610dd37057fe1a3473df4f3789fef693c900516caf8958dfb341394ecf69ce9b60c82c422ad2954491a7e4d4533432fd5df812827443d6e9 + checksum: 10/69d82a1a0a72ed6e6f7969e09cf330516599d79b2b4e680e9dd3c57616a8c6af049b5103456e370ab56642815e80e46ed88bb81e9e059304a85c5fe0bf137c29 languageName: node linkType: hard -"@babel/plugin-transform-logical-assignment-operators@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.24.7" +"@babel/plugin-transform-literals@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-literals@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/plugin-syntax-logical-assignment-operators": "npm:^7.10.4" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/e39581cf1f9a43330b8340177c618fdb3232deb03faab1937819ef39327660a1fe94fd0ec2f66d1f5b5f98acba68871a77a9931588011c13dded3d7094ecc9de + checksum: 10/0a76d12ab19f32dd139964aea7da48cecdb7de0b75e207e576f0f700121fe92367d788f328bf4fb44b8261a0f605c97b44e62ae61cddbb67b14e94c88b411f95 languageName: node linkType: hard -"@babel/plugin-transform-member-expression-literals@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-member-expression-literals@npm:7.24.7" +"@babel/plugin-transform-logical-assignment-operators@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.28.6" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/837b60ea42fc69a430c8f7fb124247ba009ff6d93187a521fe9f83556fe124715bd46533b1684a3e139f272849a14d1d4faf3397bde13714f99ce0938526ea6f + checksum: 10/36095d5d1cfc680e95298b5389a16016da800ae3379b130dabf557e94652c47b06610407e9fa44aaa03e9b0a5aa7b4b93348123985d44a45e369bf5f3497d149 languageName: node linkType: hard -"@babel/plugin-transform-modules-amd@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-modules-amd@npm:7.24.7" +"@babel/plugin-transform-member-expression-literals@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-member-expression-literals@npm:7.27.1" dependencies: - "@babel/helper-module-transforms": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/66465ffba49af7a7b7a62995eb58f591ecd23ab42b0c67f8a70020177b3789d2a379bd6cbb68cbd09a69fd75c38a91f5a09ea70f5c8347bf4c6ea81caa0f6c6b + checksum: 10/804121430a6dcd431e6ffe99c6d1fbbc44b43478113b79c677629e7f877b4f78a06b69c6bfb2747fd84ee91879fe2eb32e4620b53124603086cf5b727593ebe8 languageName: node linkType: hard -"@babel/plugin-transform-modules-commonjs@npm:^7.24.7, @babel/plugin-transform-modules-commonjs@npm:^7.24.8": - version: 7.24.8 - resolution: "@babel/plugin-transform-modules-commonjs@npm:7.24.8" +"@babel/plugin-transform-modules-amd@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-modules-amd@npm:7.27.1" dependencies: - "@babel/helper-module-transforms": "npm:^7.24.8" - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/helper-simple-access": "npm:^7.24.7" + "@babel/helper-module-transforms": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/18e5d229767c7b5b6ff0cbf1a8d2d555965b90201839d0ac2dc043b56857624ea344e59f733f028142a8c1d54923b82e2a0185694ef36f988d797bfbaf59819c + checksum: 10/5ca9257981f2bbddd9dccf9126f1368de1cb335e7a5ff5cca9282266825af5b18b5f06c144320dcf5d2a200d2b53b6d22d9b801a55dc0509ab5a5838af7e61b7 languageName: node linkType: hard -"@babel/plugin-transform-modules-systemjs@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/plugin-transform-modules-systemjs@npm:7.25.0" +"@babel/plugin-transform-modules-commonjs@npm:^7.27.1, @babel/plugin-transform-modules-commonjs@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-modules-commonjs@npm:7.28.6" dependencies: - "@babel/helper-module-transforms": "npm:^7.25.0" - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/helper-validator-identifier": "npm:^7.24.7" - "@babel/traverse": "npm:^7.25.0" + "@babel/helper-module-transforms": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/2c38efdbaf6faf730cdcb0c5e42d2d15bb114eecf184db078319de496b5e3ce68d499e531265a0e13e29f0dcaa001f240773db5c4c078eac7f4456d6c8bddd88 + checksum: 10/ec6ea2958e778a7e0220f4a75cb5816cecddc6bd98efa10499fff7baabaa29a594d50d787a4ebf8a8ba66fefcf76ca2ded602be0b4554ae3317e53b3b3375b37 languageName: node linkType: hard -"@babel/plugin-transform-modules-umd@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-modules-umd@npm:7.24.7" +"@babel/plugin-transform-modules-systemjs@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/plugin-transform-modules-systemjs@npm:7.28.5" dependencies: - "@babel/helper-module-transforms": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-module-transforms": "npm:^7.28.3" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.28.5" + "@babel/traverse": "npm:^7.28.5" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/cef9c8917b3c35c3b6cb424dc2e6f74016122f1d25c196e2c7e51eb080d95e96c5d34966c0d5b9d4e17b8e60d455a97ed271317ed104e0e70bff159830a59678 + checksum: 10/1b91b4848845eaf6e21663d97a2a6c896553b127deaf3c2e9a2a4f041249277d13ebf71fd42d0ecbc4385e9f76093eff592fe0da0dcf1401b3f38c1615d8c539 languageName: node linkType: hard -"@babel/plugin-transform-named-capturing-groups-regex@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.24.7" +"@babel/plugin-transform-modules-umd@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-modules-umd@npm:7.27.1" dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-module-transforms": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/7388932863b4ee01f177eb6c2e2df9e2312005e43ada99897624d5565db4b9cef1e30aa7ad2c79bbe5373f284cfcddea98d8fe212714a24c6aba223272163058 + languageName: node + linkType: hard + +"@babel/plugin-transform-named-capturing-groups-regex@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.27.1" + dependencies: + "@babel/helper-create-regexp-features-plugin": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/b0ecb1afd22946b21fb8f34e826cfbfea4b5337f7592a5ff8af7937eddec4440149c59d2d134b4f21b2ed91b57611f39b19827729e19d99b7c11eaf614435f83 + checksum: 10/a711c92d9753df26cefc1792481e5cbff4fe4f32b383d76b25e36fa865d8023b1b9aa6338cf18f5c0e864c71a7fbe8115e840872ccd61a914d9953849c68de7d languageName: node linkType: hard -"@babel/plugin-transform-new-target@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-new-target@npm:7.24.7" +"@babel/plugin-transform-new-target@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-new-target@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/91b6a7439b7622f80dc755ddfb9ab083355bedc0b2af18e7c7a948faed14467599609331c8d59cfab4273640e3fc36e4cd02ad5b6dcb4a428f5a8baefc507acc + checksum: 10/620d78ee476ae70960989e477dc86031ffa3d554b1b1999e6ec95261629f7a13e5a7b98579c63a009f9fdf14def027db57de1f0ae1f06fb6eaed8908ff65cf68 languageName: node linkType: hard -"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.24.7" +"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.28.6" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" + "@babel/helper-plugin-utils": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/113cd24b6ce4d0a8e54ad9324428244942ce752a3fd38f8b615c3a786641ec18a00a01b662fe4cbebf369358f5904a975bbde0a977b839f2438b16f0d7d1dd36 + checksum: 10/88106952ca4f4fea8f97222a25f9595c6859d458d76905845dfa54f54e7d345e3dc338932e8c84a9c57a6c88b2f6d9ebff47130ce508a49c2b6e6a9f03858750 languageName: node linkType: hard -"@babel/plugin-transform-numeric-separator@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-numeric-separator@npm:7.24.7" +"@babel/plugin-transform-numeric-separator@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-numeric-separator@npm:7.28.6" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/plugin-syntax-numeric-separator": "npm:^7.10.4" + "@babel/helper-plugin-utils": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/dc5bb0534889d207b1da125635471c42da61a4a4e9e68855f24b1cd04ccdcf8325b2c29112e719913c2097242e7e62d660e0fea2a46f3a9a983c9d02a0ec7a04 + checksum: 10/4b5ca60e481e22f0842761a3badca17376a230b5a7e5482338604eb95836c2d0c9c9bde53bdc5c2de1c6a12ae6c12de7464d098bf74b0943f85905ca358f0b68 languageName: node linkType: hard -"@babel/plugin-transform-object-rest-spread@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-object-rest-spread@npm:7.24.7" +"@babel/plugin-transform-object-rest-spread@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-object-rest-spread@npm:7.28.6" dependencies: - "@babel/helper-compilation-targets": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/plugin-syntax-object-rest-spread": "npm:^7.8.3" - "@babel/plugin-transform-parameters": "npm:^7.24.7" + "@babel/helper-compilation-targets": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/plugin-transform-destructuring": "npm:^7.28.5" + "@babel/plugin-transform-parameters": "npm:^7.27.7" + "@babel/traverse": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/d586995dc3396bbf8fb75b84f0a3548d923e4c3500bb414641a7fe30762a4ffd82987887fece6381f600d8de2da1e3310fc9a725271724d35f9020fcd5d4b2a3 + checksum: 10/9c8c51a515a5ec98a33a715e82d49f873e58b04b53fa1e826f3c2009f7133cd396d6730553a53d265e096dbfbea17dd100ae38815d0b506c094cb316a7a5519e languageName: node linkType: hard -"@babel/plugin-transform-object-super@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-object-super@npm:7.24.7" +"@babel/plugin-transform-object-super@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-object-super@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/helper-replace-supers": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-replace-supers": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/382739a017972d7126416b958ea81b4b950b6275414908a54bfef6aeed9b9fcc6c8d247db3a1134b09a3b355a60039670ce41ee41c626f8acec70f49c3c8d2a6 + checksum: 10/46b819cb9a6cd3cfefe42d07875fee414f18d5e66040366ae856116db560ad4e16f3899a0a7fddd6773e0d1458444f94b208b67c0e3b6977a27ea17a5c13dbf6 languageName: node linkType: hard -"@babel/plugin-transform-optional-catch-binding@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-optional-catch-binding@npm:7.24.7" +"@babel/plugin-transform-optional-catch-binding@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-optional-catch-binding@npm:7.28.6" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/plugin-syntax-optional-catch-binding": "npm:^7.8.3" + "@babel/helper-plugin-utils": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/605ae3764354e83f73c1e6430bac29e308806abcce8d1369cf69e4921771ff3592e8f60ba60c15990070d79b8d8740f0841069d64b466b3ce8a8c43e9743da7e + checksum: 10/ee24a17defec056eb9ef01824d7e4a1f65d531af6b4b79acfd0bcb95ce0b47926e80c61897f36f8c01ce733b069c9acdb1c9ce5ec07a729d0dbf9e8d859fe992 languageName: node linkType: hard -"@babel/plugin-transform-optional-chaining@npm:^7.24.7, @babel/plugin-transform-optional-chaining@npm:^7.24.8": - version: 7.24.8 - resolution: "@babel/plugin-transform-optional-chaining@npm:7.24.8" +"@babel/plugin-transform-optional-chaining@npm:^7.27.1, @babel/plugin-transform-optional-chaining@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-optional-chaining@npm:7.28.6" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7" - "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" + "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/1f873fb9d86c280b64dfe5ebc59244b459b717ed72a7682da2386db3d9e11fc9d831cfc2e11d37262b4325a7a0e3ccbccfb8cd0b944caf199d3c9e03fff7b0af + checksum: 10/c7cf29f99384a9a98748f04489a122c0106e0316aa64a2e61ef8af74c1057b587b96d9a08eb4e33d2ac17d1aaff1f0a86fae658d429fa7bcce4ef977e0ad684b languageName: node linkType: hard -"@babel/plugin-transform-parameters@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-parameters@npm:7.24.7" +"@babel/plugin-transform-parameters@npm:^7.27.7": + version: 7.27.7 + resolution: "@babel/plugin-transform-parameters@npm:7.27.7" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/41ff6bda926fabfb2e5d90b70621f279330691bed92009297340a8e776cfe9c3f2dda6afbc31dd3cbdccdfa9a5c57f2046e3ccc84f963c3797356df003d1703a + checksum: 10/ba0aa8c977a03bf83030668f64c1d721e4e82d8cce89cdde75a2755862b79dbe9e7f58ca955e68c721fd494d6ee3826e46efad3fbf0855fcc92cb269477b4777 languageName: node linkType: hard -"@babel/plugin-transform-private-methods@npm:^7.25.4": - version: 7.25.4 - resolution: "@babel/plugin-transform-private-methods@npm:7.25.4" +"@babel/plugin-transform-private-methods@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-private-methods@npm:7.28.6" dependencies: - "@babel/helper-create-class-features-plugin": "npm:^7.25.4" - "@babel/helper-plugin-utils": "npm:^7.24.8" + "@babel/helper-create-class-features-plugin": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/d5c29ba121d6ce40e8055a632c32e69006c513607145a29701f93b416a8c53a60e53565df417218e2d8b7f1ba73adb837601e8e9d0a3215da50e4c9507f9f1fa + checksum: 10/b80179b28f6a165674d0b0d6c6349b13a01dd282b18f56933423c0a33c23fc0626c8f011f859fc20737d021fe966eb8474a5233e4596401482e9ee7fb00e2aa2 languageName: node linkType: hard -"@babel/plugin-transform-private-property-in-object@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-private-property-in-object@npm:7.24.7" +"@babel/plugin-transform-private-property-in-object@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-private-property-in-object@npm:7.28.6" dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.24.7" - "@babel/helper-create-class-features-plugin": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/plugin-syntax-private-property-in-object": "npm:^7.14.5" + "@babel/helper-annotate-as-pure": "npm:^7.27.3" + "@babel/helper-create-class-features-plugin": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/a23ee18340818e292abfcb98b1086a188c81d640b1045e6809e9a3e8add78f9cb26607774de4ed653cbecd4277965dc4f4f1affc3504682209bb2a65fd4251f8 + checksum: 10/d02008c62fd32ff747b850b8581ab5076b717320e1cb01c7fc66ebf5169095bd922e18cfb269992f85bc7fbd2cc61e5b5af25e2b54aad67411474b789ea94d5f languageName: node linkType: hard -"@babel/plugin-transform-property-literals@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-property-literals@npm:7.24.7" +"@babel/plugin-transform-property-literals@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-property-literals@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/71708890fe007d45ad7a130150a2ba1fea0205f575b925ca2e1bb65018730636a68e65c634a474e5b658378d72871c337c953560009c081a645e088769bf168a + checksum: 10/7caec27d5ed8870895c9faf4f71def72745d69da0d8e77903146a4e135fd7bed5778f5f9cebb36c5fba86338e6194dd67a08c033fc84b4299b7eceab6d9630cb languageName: node linkType: hard @@ -1276,302 +1053,300 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-react-display-name@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-react-display-name@npm:7.22.5" +"@babel/plugin-transform-react-display-name@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/plugin-transform-react-display-name@npm:7.28.0" dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/a12bfd1e4e93055efca3ace3c34722571bda59d9740dca364d225d9c6e3ca874f134694d21715c42cc63d79efd46db9665bd4a022998767f9245f1e29d5d204d + checksum: 10/d623644a078086f410b1952429d82c10e2833ebffb97800b25f55ab7f3ffafde34e57a4a71958da73f4abfcef39b598e2ca172f2b43531f98b3f12e0de17c219 languageName: node linkType: hard -"@babel/plugin-transform-react-jsx-development@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-react-jsx-development@npm:7.22.5" +"@babel/plugin-transform-react-jsx-development@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-react-jsx-development@npm:7.27.1" dependencies: - "@babel/plugin-transform-react-jsx": "npm:^7.22.5" + "@babel/plugin-transform-react-jsx": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/36bc3ff0b96bb0ef4723070a50cfdf2e72cfd903a59eba448f9fe92fea47574d6f22efd99364413719e1f3fb3c51b6c9b2990b87af088f8486a84b2a5f9e4560 + checksum: 10/b88865d5b8c018992f2332da939faa15c4d4a864c9435a5937beaff3fe43781432cc42e0a5d5631098e0bd4066fc33f5fa72203b388b074c3545fe7aaa21e474 languageName: node linkType: hard -"@babel/plugin-transform-react-jsx@npm:^7.22.15, @babel/plugin-transform-react-jsx@npm:^7.22.5": - version: 7.22.15 - resolution: "@babel/plugin-transform-react-jsx@npm:7.22.15" +"@babel/plugin-transform-react-jsx@npm:^7.27.1": + version: 7.28.6 + resolution: "@babel/plugin-transform-react-jsx@npm:7.28.6" dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.22.5" - "@babel/helper-module-imports": "npm:^7.22.15" - "@babel/helper-plugin-utils": "npm:^7.22.5" - "@babel/plugin-syntax-jsx": "npm:^7.22.5" - "@babel/types": "npm:^7.22.15" + "@babel/helper-annotate-as-pure": "npm:^7.27.3" + "@babel/helper-module-imports": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/plugin-syntax-jsx": "npm:^7.28.6" + "@babel/types": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/a436bfbffe723d162e5816d510dca7349a1fc572c501d73f1e17bbca7eb899d7a6a14d8fc2ae5993dd79fdd77bcc68d295e59a3549bed03b8579c767f6e3c9dc + checksum: 10/c6eade7309f0710b6aac9e747f8c3305633801c035a35efc5e2436742cc466e457ed5848d3dd5dade36e34332cfc50ac92d69a33f7803d66ae2d72f13a76c3bc languageName: node linkType: hard -"@babel/plugin-transform-react-pure-annotations@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-react-pure-annotations@npm:7.22.5" +"@babel/plugin-transform-react-pure-annotations@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-react-pure-annotations@npm:7.27.1" dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.22.5" - "@babel/helper-plugin-utils": "npm:^7.22.5" + "@babel/helper-annotate-as-pure": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/092021c4f404e267002099ec20b3f12dd730cb90b0d83c5feed3dc00dbe43b9c42c795a18e7c6c7d7bddea20c7dd56221b146aec81b37f2e7eb5137331c61120 + checksum: 10/a6f591c5e85a1ab0685d4a25afe591fe8d11dc0b73c677cf9560ff8d540d036a1cce9efcb729fc9092def4d854dc304ffdc063a89a9247900b69c516bf971a4c languageName: node linkType: hard -"@babel/plugin-transform-regenerator@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-regenerator@npm:7.24.7" +"@babel/plugin-transform-regenerator@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-regenerator@npm:7.28.6" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - regenerator-transform: "npm:^0.15.2" + "@babel/helper-plugin-utils": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/70fa2bb36d3e2ce69a25c7227da8ad92307ab7b50cb6dfcc4dc5ce8f1cc79b0fcf997292a1cb3b4ae7cb136f515d1b2c3fb78c927bdba8d719794430403eb0c6 + checksum: 10/09028ed8ed7f5e3879cbfdcf92a8a730c13b15ce042ef86b29b31cca5a250da641f777dfaf81ab1706fb8cf9252c177f02e882fc7465d3a10b9f385c0bb2ea16 languageName: node linkType: hard -"@babel/plugin-transform-reserved-words@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-reserved-words@npm:7.24.7" +"@babel/plugin-transform-regexp-modifiers@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-regexp-modifiers@npm:7.28.6" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/64a2669671bb97c3dee3830a82c3e932fe6e02d56a4053c6ee4453d317b5f436d3d44907fbb0f4fbd8a56ebee34f6aee250e49743b7243d14d00c069215f3113 - languageName: node - linkType: hard - -"@babel/plugin-transform-runtime@npm:^7.22.9": - version: 7.22.15 - resolution: "@babel/plugin-transform-runtime@npm:7.22.15" - dependencies: - "@babel/helper-module-imports": "npm:^7.22.15" - "@babel/helper-plugin-utils": "npm:^7.22.5" - babel-plugin-polyfill-corejs2: "npm:^0.4.5" - babel-plugin-polyfill-corejs3: "npm:^0.8.3" - babel-plugin-polyfill-regenerator: "npm:^0.5.2" - semver: "npm:^6.3.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/7f4a302a0d61236febf47f7a85d242b5ebb0d08d0ceb501a42e4e0341faadf1d0359d87c9c93c1081d9e1215b37cdcbdd626db6523aab1e60fb385f3bba2523a - languageName: node - linkType: hard - -"@babel/plugin-transform-shorthand-properties@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-shorthand-properties@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/c68c2be965007e0cb6667daa209bc0af877cab4b327ef2e21b2114c38554243c3f7fdcc5b03679b20f72a26d966aa646af771f3165c882067e85a3887647f028 - languageName: node - linkType: hard - -"@babel/plugin-transform-spread@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-spread@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/76e2c8544129d727d5a698e2a67d74e438bc35df843adb5f769316ec432c5e1bbb4128123a95b2fe8ef0aec7b26d87efe81d64326291c77ad757ff184d38448a - languageName: node - linkType: hard - -"@babel/plugin-transform-sticky-regex@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-sticky-regex@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/3b9a99ae043ef363c81bfb097fa7a553fcf7c7d9fddc13dd2b47b3b2e45cf2741a9ca78cfe55f463983b043b365f0f8452f2d5eaadbdea20e6d6de50c16bed25 - languageName: node - linkType: hard - -"@babel/plugin-transform-template-literals@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-template-literals@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/ecf05a8511176d5570cb0d481577a407a4e8a9a430f86522d809e0ac2c823913e854ef9e2a1c83c0bd7c12489d82e1b48fabb52e697e80d6a6962125197593ca - languageName: node - linkType: hard - -"@babel/plugin-transform-typeof-symbol@npm:^7.24.8": - version: 7.24.8 - resolution: "@babel/plugin-transform-typeof-symbol@npm:7.24.8" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/5f113fed94b694ec4a40a27b8628ce736cfa172b69fcffa2833c9a41895032127f3daeea552e94fdb4a3ce4e8cd51de67a670ab87a1f447a0cf55c9cb2d7ed11 - languageName: node - linkType: hard - -"@babel/plugin-transform-typescript@npm:^7.24.7": - version: 7.25.2 - resolution: "@babel/plugin-transform-typescript@npm:7.25.2" - dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.24.7" - "@babel/helper-create-class-features-plugin": "npm:^7.25.0" - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7" - "@babel/plugin-syntax-typescript": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/50e017ffd131c08661daa22b6c759999bb7a6cdfbf683291ee4bcbea4ae839440b553d2f8896bcf049aca1d267b39f3b09e8336059e919e83149b5ad859671f6 - languageName: node - linkType: hard - -"@babel/plugin-transform-unicode-escapes@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-unicode-escapes@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/6b8bca3495acedc89e880942de7b83c263fb5b4c9599594dcf3923e2128ae25f1f4725a295fe101027f75d8ef081ef28319296adf274b5022e57039e42836103 - languageName: node - linkType: hard - -"@babel/plugin-transform-unicode-property-regex@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-unicode-property-regex@npm:7.24.7" - dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/c0c284bbbdead7e17e059d72e1b288f86b0baacc410398ef6c6c703fe4326b069e68515ccb84359601315cd8e888f9226731d00624b7c6959b1c0853f072b61f - languageName: node - linkType: hard - -"@babel/plugin-transform-unicode-regex@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-unicode-regex@npm:7.24.7" - dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/b545310d0d592d75566b9cd158f4b8951e34d07d839656789d179b39b3fd92b32bd387cdfaf33a93e636609f3bfb9bb03d41f3e43be598116c9c6c80cc3418c4 - languageName: node - linkType: hard - -"@babel/plugin-transform-unicode-sets-regex@npm:^7.25.4": - version: 7.25.4 - resolution: "@babel/plugin-transform-unicode-sets-regex@npm:7.25.4" - dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.25.2" - "@babel/helper-plugin-utils": "npm:^7.24.8" + "@babel/helper-create-regexp-features-plugin": "npm:^7.28.5" + "@babel/helper-plugin-utils": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/d5d07d17932656fa4d62fd67ecaa1a5e4c2e92365a924f1a2a8cf8108762f137a30cd55eb3a7d0504258f27a19ad0decca6b62a5c37a5aada709cbb46c4a871f + checksum: 10/5aacc570034c085afa0165137bb9a04cd4299b86eb9092933a96dcc1132c8f591d9d534419988f5f762b2f70d43a3c719a6b8fa05fdd3b2b1820d01cf85500da languageName: node linkType: hard -"@babel/preset-env@npm:^7.20.2, @babel/preset-env@npm:^7.22.9": - version: 7.25.4 - resolution: "@babel/preset-env@npm:7.25.4" +"@babel/plugin-transform-reserved-words@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-reserved-words@npm:7.27.1" dependencies: - "@babel/compat-data": "npm:^7.25.4" - "@babel/helper-compilation-targets": "npm:^7.25.2" - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/helper-validator-option": "npm:^7.24.8" - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "npm:^7.25.3" - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "npm:^7.25.0" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "npm:^7.25.0" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "npm:^7.24.7" - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "npm:^7.25.0" - "@babel/plugin-proposal-private-property-in-object": "npm:7.21.0-placeholder-for-preset-env.2" - "@babel/plugin-syntax-async-generators": "npm:^7.8.4" - "@babel/plugin-syntax-class-properties": "npm:^7.12.13" - "@babel/plugin-syntax-class-static-block": "npm:^7.14.5" - "@babel/plugin-syntax-dynamic-import": "npm:^7.8.3" - "@babel/plugin-syntax-export-namespace-from": "npm:^7.8.3" - "@babel/plugin-syntax-import-assertions": "npm:^7.24.7" - "@babel/plugin-syntax-import-attributes": "npm:^7.24.7" - "@babel/plugin-syntax-import-meta": "npm:^7.10.4" - "@babel/plugin-syntax-json-strings": "npm:^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators": "npm:^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" - "@babel/plugin-syntax-numeric-separator": "npm:^7.10.4" - "@babel/plugin-syntax-object-rest-spread": "npm:^7.8.3" - "@babel/plugin-syntax-optional-catch-binding": "npm:^7.8.3" - "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" - "@babel/plugin-syntax-private-property-in-object": "npm:^7.14.5" - "@babel/plugin-syntax-top-level-await": "npm:^7.14.5" - "@babel/plugin-syntax-unicode-sets-regex": "npm:^7.18.6" - "@babel/plugin-transform-arrow-functions": "npm:^7.24.7" - "@babel/plugin-transform-async-generator-functions": "npm:^7.25.4" - "@babel/plugin-transform-async-to-generator": "npm:^7.24.7" - "@babel/plugin-transform-block-scoped-functions": "npm:^7.24.7" - "@babel/plugin-transform-block-scoping": "npm:^7.25.0" - "@babel/plugin-transform-class-properties": "npm:^7.25.4" - "@babel/plugin-transform-class-static-block": "npm:^7.24.7" - "@babel/plugin-transform-classes": "npm:^7.25.4" - "@babel/plugin-transform-computed-properties": "npm:^7.24.7" - "@babel/plugin-transform-destructuring": "npm:^7.24.8" - "@babel/plugin-transform-dotall-regex": "npm:^7.24.7" - "@babel/plugin-transform-duplicate-keys": "npm:^7.24.7" - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "npm:^7.25.0" - "@babel/plugin-transform-dynamic-import": "npm:^7.24.7" - "@babel/plugin-transform-exponentiation-operator": "npm:^7.24.7" - "@babel/plugin-transform-export-namespace-from": "npm:^7.24.7" - "@babel/plugin-transform-for-of": "npm:^7.24.7" - "@babel/plugin-transform-function-name": "npm:^7.25.1" - "@babel/plugin-transform-json-strings": "npm:^7.24.7" - "@babel/plugin-transform-literals": "npm:^7.25.2" - "@babel/plugin-transform-logical-assignment-operators": "npm:^7.24.7" - "@babel/plugin-transform-member-expression-literals": "npm:^7.24.7" - "@babel/plugin-transform-modules-amd": "npm:^7.24.7" - "@babel/plugin-transform-modules-commonjs": "npm:^7.24.8" - "@babel/plugin-transform-modules-systemjs": "npm:^7.25.0" - "@babel/plugin-transform-modules-umd": "npm:^7.24.7" - "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.24.7" - "@babel/plugin-transform-new-target": "npm:^7.24.7" - "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.24.7" - "@babel/plugin-transform-numeric-separator": "npm:^7.24.7" - "@babel/plugin-transform-object-rest-spread": "npm:^7.24.7" - "@babel/plugin-transform-object-super": "npm:^7.24.7" - "@babel/plugin-transform-optional-catch-binding": "npm:^7.24.7" - "@babel/plugin-transform-optional-chaining": "npm:^7.24.8" - "@babel/plugin-transform-parameters": "npm:^7.24.7" - "@babel/plugin-transform-private-methods": "npm:^7.25.4" - "@babel/plugin-transform-private-property-in-object": "npm:^7.24.7" - "@babel/plugin-transform-property-literals": "npm:^7.24.7" - "@babel/plugin-transform-regenerator": "npm:^7.24.7" - "@babel/plugin-transform-reserved-words": "npm:^7.24.7" - "@babel/plugin-transform-shorthand-properties": "npm:^7.24.7" - "@babel/plugin-transform-spread": "npm:^7.24.7" - "@babel/plugin-transform-sticky-regex": "npm:^7.24.7" - "@babel/plugin-transform-template-literals": "npm:^7.24.7" - "@babel/plugin-transform-typeof-symbol": "npm:^7.24.8" - "@babel/plugin-transform-unicode-escapes": "npm:^7.24.7" - "@babel/plugin-transform-unicode-property-regex": "npm:^7.24.7" - "@babel/plugin-transform-unicode-regex": "npm:^7.24.7" - "@babel/plugin-transform-unicode-sets-regex": "npm:^7.25.4" - "@babel/preset-modules": "npm:0.1.6-no-external-plugins" - babel-plugin-polyfill-corejs2: "npm:^0.4.10" - babel-plugin-polyfill-corejs3: "npm:^0.10.6" - babel-plugin-polyfill-regenerator: "npm:^0.6.1" - core-js-compat: "npm:^3.37.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/dea0b66742d2863b369c06c053e11e15ba785892ea19cccf7aef3c1bdaa38b6ab082e19984c5ea7810d275d9445c5400fcc385ad71ce707ed9256fadb102af3b + languageName: node + linkType: hard + +"@babel/plugin-transform-runtime@npm:^7.25.9": + version: 7.28.5 + resolution: "@babel/plugin-transform-runtime@npm:7.28.5" + dependencies: + "@babel/helper-module-imports": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" + babel-plugin-polyfill-corejs2: "npm:^0.4.14" + babel-plugin-polyfill-corejs3: "npm:^0.13.0" + babel-plugin-polyfill-regenerator: "npm:^0.6.5" semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/45ca65bdc7fa11ca51167804052460eda32bf2e6620c7ba998e2d95bc867595913532ee7d748e97e808eabcc66aabe796bd75c59014d996ec8183fa5a7245862 + checksum: 10/0d16c90d40dd34f1a981e742ad656ceef619b92d3662ec9ac8d7c8ba79f22bb425c3f9e097333659a4938f03868a53077b1a3aadb7f37504157a0c7af64ec2be + languageName: node + linkType: hard + +"@babel/plugin-transform-shorthand-properties@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-shorthand-properties@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/fbba6e2aef0b69681acb68202aa249c0598e470cc0853d7ff5bd0171fd6a7ec31d77cfabcce9df6360fc8349eded7e4a65218c32551bd3fc0caaa1ac899ac6d4 + languageName: node + linkType: hard + +"@babel/plugin-transform-spread@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-spread@npm:7.28.6" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/1fa02ac60ae5e49d46fa2966aaf3f7578cf37255534c2ecf379d65855088a1623c3eea28b9ee6a0b1413b0199b51f9019d0da3fe9da89986bc47e07242415f60 + languageName: node + linkType: hard + +"@babel/plugin-transform-sticky-regex@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-sticky-regex@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/e1414a502efba92c7974681767e365a8cda6c5e9e5f33472a9eaa0ce2e75cea0a9bef881ff8dda37c7810ad902f98d3c00ead92a3ac3b73a79d011df85b5a189 + languageName: node + linkType: hard + +"@babel/plugin-transform-template-literals@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-template-literals@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/93aad782503b691faef7c0893372d5243df3219b07f1f22cfc32c104af6a2e7acd6102c128439eab15336d048f1b214ca134b87b0630d8cd568bf447f78b25ce + languageName: node + linkType: hard + +"@babel/plugin-transform-typeof-symbol@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-typeof-symbol@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/812d736402a6f9313b86b8adf36740394400be7a09c48e51ee45ab4a383a3f46fc618d656dd12e44934665e42ae71cf143e25b95491b699ef7c737950dbdb862 + languageName: node + linkType: hard + +"@babel/plugin-transform-typescript@npm:^7.28.5": + version: 7.28.6 + resolution: "@babel/plugin-transform-typescript@npm:7.28.6" + dependencies: + "@babel/helper-annotate-as-pure": "npm:^7.27.3" + "@babel/helper-create-class-features-plugin": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" + "@babel/plugin-syntax-typescript": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/a0bccc531fa8710a45b0b593140273741e0e4a0721b1ef6ef9dfefae0bbe61528440d65aab7936929551fd76793272257d74f60cf66891352f793294930a4b67 + languageName: node + linkType: hard + +"@babel/plugin-transform-unicode-escapes@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-unicode-escapes@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/87b9e49dee4ab6e78f4cdcdbdd837d7784f02868a96bfc206c8dbb17dd85db161b5a0ecbe95b19a42e8aea0ce57e80249e1facbf9221d7f4114d52c3b9136c9e + languageName: node + linkType: hard + +"@babel/plugin-transform-unicode-property-regex@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-unicode-property-regex@npm:7.28.6" + dependencies: + "@babel/helper-create-regexp-features-plugin": "npm:^7.28.5" + "@babel/helper-plugin-utils": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/d14e8c51aa73f592575c1543400fd67d96df6410d75c9dc10dd640fd7eecb37366a2f2368bbdd7529842532eda4af181c921bda95146c6d373c64ea59c6e9991 + languageName: node + linkType: hard + +"@babel/plugin-transform-unicode-regex@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-unicode-regex@npm:7.27.1" + dependencies: + "@babel/helper-create-regexp-features-plugin": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/a34d89a2b75fb78e66d97c3dc90d4877f7e31f43316b52176f95a5dee20e9bb56ecf158eafc42a001676ddf7b393d9e67650bad6b32f5405780f25fb83cd68e3 + languageName: node + linkType: hard + +"@babel/plugin-transform-unicode-sets-regex@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-transform-unicode-sets-regex@npm:7.28.6" + dependencies: + "@babel/helper-create-regexp-features-plugin": "npm:^7.28.5" + "@babel/helper-plugin-utils": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10/423971fe2eef9d18782b1c30f5f42613ee510e5b9c08760c5538a0997b36c34495acce261e0e37a27831f81330359230bd1f33c2e1822de70241002b45b7d68e + languageName: node + linkType: hard + +"@babel/preset-env@npm:^7.20.2, @babel/preset-env@npm:^7.25.9": + version: 7.28.6 + resolution: "@babel/preset-env@npm:7.28.6" + dependencies: + "@babel/compat-data": "npm:^7.28.6" + "@babel/helper-compilation-targets": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-validator-option": "npm:^7.27.1" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "npm:^7.28.5" + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "npm:^7.27.1" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "npm:^7.27.1" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "npm:^7.27.1" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "npm:^7.28.6" + "@babel/plugin-proposal-private-property-in-object": "npm:7.21.0-placeholder-for-preset-env.2" + "@babel/plugin-syntax-import-assertions": "npm:^7.28.6" + "@babel/plugin-syntax-import-attributes": "npm:^7.28.6" + "@babel/plugin-syntax-unicode-sets-regex": "npm:^7.18.6" + "@babel/plugin-transform-arrow-functions": "npm:^7.27.1" + "@babel/plugin-transform-async-generator-functions": "npm:^7.28.6" + "@babel/plugin-transform-async-to-generator": "npm:^7.28.6" + "@babel/plugin-transform-block-scoped-functions": "npm:^7.27.1" + "@babel/plugin-transform-block-scoping": "npm:^7.28.6" + "@babel/plugin-transform-class-properties": "npm:^7.28.6" + "@babel/plugin-transform-class-static-block": "npm:^7.28.6" + "@babel/plugin-transform-classes": "npm:^7.28.6" + "@babel/plugin-transform-computed-properties": "npm:^7.28.6" + "@babel/plugin-transform-destructuring": "npm:^7.28.5" + "@babel/plugin-transform-dotall-regex": "npm:^7.28.6" + "@babel/plugin-transform-duplicate-keys": "npm:^7.27.1" + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "npm:^7.28.6" + "@babel/plugin-transform-dynamic-import": "npm:^7.27.1" + "@babel/plugin-transform-explicit-resource-management": "npm:^7.28.6" + "@babel/plugin-transform-exponentiation-operator": "npm:^7.28.6" + "@babel/plugin-transform-export-namespace-from": "npm:^7.27.1" + "@babel/plugin-transform-for-of": "npm:^7.27.1" + "@babel/plugin-transform-function-name": "npm:^7.27.1" + "@babel/plugin-transform-json-strings": "npm:^7.28.6" + "@babel/plugin-transform-literals": "npm:^7.27.1" + "@babel/plugin-transform-logical-assignment-operators": "npm:^7.28.6" + "@babel/plugin-transform-member-expression-literals": "npm:^7.27.1" + "@babel/plugin-transform-modules-amd": "npm:^7.27.1" + "@babel/plugin-transform-modules-commonjs": "npm:^7.28.6" + "@babel/plugin-transform-modules-systemjs": "npm:^7.28.5" + "@babel/plugin-transform-modules-umd": "npm:^7.27.1" + "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.27.1" + "@babel/plugin-transform-new-target": "npm:^7.27.1" + "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.28.6" + "@babel/plugin-transform-numeric-separator": "npm:^7.28.6" + "@babel/plugin-transform-object-rest-spread": "npm:^7.28.6" + "@babel/plugin-transform-object-super": "npm:^7.27.1" + "@babel/plugin-transform-optional-catch-binding": "npm:^7.28.6" + "@babel/plugin-transform-optional-chaining": "npm:^7.28.6" + "@babel/plugin-transform-parameters": "npm:^7.27.7" + "@babel/plugin-transform-private-methods": "npm:^7.28.6" + "@babel/plugin-transform-private-property-in-object": "npm:^7.28.6" + "@babel/plugin-transform-property-literals": "npm:^7.27.1" + "@babel/plugin-transform-regenerator": "npm:^7.28.6" + "@babel/plugin-transform-regexp-modifiers": "npm:^7.28.6" + "@babel/plugin-transform-reserved-words": "npm:^7.27.1" + "@babel/plugin-transform-shorthand-properties": "npm:^7.27.1" + "@babel/plugin-transform-spread": "npm:^7.28.6" + "@babel/plugin-transform-sticky-regex": "npm:^7.27.1" + "@babel/plugin-transform-template-literals": "npm:^7.27.1" + "@babel/plugin-transform-typeof-symbol": "npm:^7.27.1" + "@babel/plugin-transform-unicode-escapes": "npm:^7.27.1" + "@babel/plugin-transform-unicode-property-regex": "npm:^7.28.6" + "@babel/plugin-transform-unicode-regex": "npm:^7.27.1" + "@babel/plugin-transform-unicode-sets-regex": "npm:^7.28.6" + "@babel/preset-modules": "npm:0.1.6-no-external-plugins" + babel-plugin-polyfill-corejs2: "npm:^0.4.14" + babel-plugin-polyfill-corejs3: "npm:^0.13.0" + babel-plugin-polyfill-regenerator: "npm:^0.6.5" + core-js-compat: "npm:^3.43.0" + semver: "npm:^6.3.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/ee1b96dec8518436013c4a758003734842e9fed2a3af2013bee7a462289bae4e7bbce99733701164c28a93554be5a9a4c50818fa32335274d19e8b7d3dd53316 languageName: node linkType: hard @@ -1588,97 +1363,86 @@ __metadata: languageName: node linkType: hard -"@babel/preset-react@npm:^7.18.6, @babel/preset-react@npm:^7.22.5": - version: 7.22.15 - resolution: "@babel/preset-react@npm:7.22.15" +"@babel/preset-react@npm:^7.18.6, @babel/preset-react@npm:^7.25.9": + version: 7.28.5 + resolution: "@babel/preset-react@npm:7.28.5" dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - "@babel/helper-validator-option": "npm:^7.22.15" - "@babel/plugin-transform-react-display-name": "npm:^7.22.5" - "@babel/plugin-transform-react-jsx": "npm:^7.22.15" - "@babel/plugin-transform-react-jsx-development": "npm:^7.22.5" - "@babel/plugin-transform-react-pure-annotations": "npm:^7.22.5" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-validator-option": "npm:^7.27.1" + "@babel/plugin-transform-react-display-name": "npm:^7.28.0" + "@babel/plugin-transform-react-jsx": "npm:^7.27.1" + "@babel/plugin-transform-react-jsx-development": "npm:^7.27.1" + "@babel/plugin-transform-react-pure-annotations": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/f9296e45346c3b6ab8296952edde5f1774cc9fdbdbefbc76047278fc3e889d3e15740f038ce017aca562d89f32fcbb6c11783d464fc6ae3066433178fa58513c + checksum: 10/c00d43b27790caddee7c4971b11b4bf479a761175433e2f168b3d7e1ac6ee36d4d929a76acc7f302e9bff3a5b26d02d37f0ad7ae6359e076e5baa862b00843b2 languageName: node linkType: hard -"@babel/preset-typescript@npm:^7.21.0, @babel/preset-typescript@npm:^7.22.5": - version: 7.24.7 - resolution: "@babel/preset-typescript@npm:7.24.7" +"@babel/preset-typescript@npm:^7.21.0, @babel/preset-typescript@npm:^7.25.9": + version: 7.28.5 + resolution: "@babel/preset-typescript@npm:7.28.5" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/helper-validator-option": "npm:^7.24.7" - "@babel/plugin-syntax-jsx": "npm:^7.24.7" - "@babel/plugin-transform-modules-commonjs": "npm:^7.24.7" - "@babel/plugin-transform-typescript": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-validator-option": "npm:^7.27.1" + "@babel/plugin-syntax-jsx": "npm:^7.27.1" + "@babel/plugin-transform-modules-commonjs": "npm:^7.27.1" + "@babel/plugin-transform-typescript": "npm:^7.28.5" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/995e9783f8e474581e7533d6b10ec1fbea69528cc939ad8582b5937e13548e5215d25a8e2c845e7b351fdaa13139896b5e42ab3bde83918ea4e41773f10861ac + checksum: 10/72c03e01c34906041b1813542761a283c52da1751e7ddf63191bc5fb2a0354eca30a00537c5a92951688bec3975bdc0e50ef4516b5e94cfd6d4cf947f2125bdc languageName: node linkType: hard -"@babel/regjsgen@npm:^0.8.0": - version: 0.8.0 - resolution: "@babel/regjsgen@npm:0.8.0" - checksum: 10/c57fb730b17332b7572574b74364a77d70faa302a281a62819476fa3b09822974fd75af77aea603ad77378395be64e81f89f0e800bf86cbbf21652d49ce12ee8 - languageName: node - linkType: hard - -"@babel/runtime-corejs3@npm:^7.22.6": - version: 7.26.10 - resolution: "@babel/runtime-corejs3@npm:7.26.10" +"@babel/runtime-corejs3@npm:^7.25.9": + version: 7.28.6 + resolution: "@babel/runtime-corejs3@npm:7.28.6" dependencies: - core-js-pure: "npm:^3.30.2" - regenerator-runtime: "npm:^0.14.0" - checksum: 10/6da4663c8b1c46410fb698188754ef69d7600c90b47e997a5a002fe069af9bbe5075f7f8c88df3559222536383b47406613e3158d5dcc08b013fc4bcc287324f + core-js-pure: "npm:^3.43.0" + checksum: 10/7110da259094d1299762b619dd92bc3cc4f6ae124662e87c9856c24fe425628a423fe133c577168cd54049d05159952000bf58705efb147231059f4802a4c7bb languageName: node 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.10 - resolution: "@babel/runtime@npm:7.26.10" - dependencies: - regenerator-runtime: "npm:^0.14.0" - checksum: 10/9d7ff8e96abe3791047c1138789c742411e3ef19c4d7ca18ce916f83cec92c06ec5dc64401759f6dd1e377cf8a01bbd2c62e033eb7550f435cf6579768d0d4a5 +"@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.25.9": + version: 7.28.6 + resolution: "@babel/runtime@npm:7.28.6" + checksum: 10/fbcd439cb74d4a681958eb064c509829e3f46d8a4bfaaf441baa81bb6733d1e680bccc676c813883d7741bcaada1d0d04b15aa320ef280b5734e2192b50decf9 languageName: node linkType: hard -"@babel/template@npm:^7.24.7, @babel/template@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/template@npm:7.25.0" +"@babel/template@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/template@npm:7.28.6" dependencies: - "@babel/code-frame": "npm:^7.24.7" - "@babel/parser": "npm:^7.25.0" - "@babel/types": "npm:^7.25.0" - checksum: 10/07ebecf6db8b28244b7397628e09c99e7a317b959b926d90455c7253c88df3677a5a32d1501d9749fe292a263ff51a4b6b5385bcabd5dadd3a48036f4d4949e0 + "@babel/code-frame": "npm:^7.28.6" + "@babel/parser": "npm:^7.28.6" + "@babel/types": "npm:^7.28.6" + checksum: 10/0ad6e32bf1e7e31bf6b52c20d15391f541ddd645cbd488a77fe537a15b280ee91acd3a777062c52e03eedbc2e1f41548791f6a3697c02476ec5daf49faa38533 languageName: node linkType: hard -"@babel/traverse@npm:^7.22.8, @babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.24.8, @babel/traverse@npm:^7.25.0, @babel/traverse@npm:^7.25.1, @babel/traverse@npm:^7.25.2, @babel/traverse@npm:^7.25.3, @babel/traverse@npm:^7.25.4": - version: 7.25.4 - resolution: "@babel/traverse@npm:7.25.4" +"@babel/traverse@npm:^7.25.9, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.5, @babel/traverse@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/traverse@npm:7.28.6" dependencies: - "@babel/code-frame": "npm:^7.24.7" - "@babel/generator": "npm:^7.25.4" - "@babel/parser": "npm:^7.25.4" - "@babel/template": "npm:^7.25.0" - "@babel/types": "npm:^7.25.4" + "@babel/code-frame": "npm:^7.28.6" + "@babel/generator": "npm:^7.28.6" + "@babel/helper-globals": "npm:^7.28.0" + "@babel/parser": "npm:^7.28.6" + "@babel/template": "npm:^7.28.6" + "@babel/types": "npm:^7.28.6" debug: "npm:^4.3.1" - globals: "npm:^11.1.0" - checksum: 10/a85c16047ab8e454e2e758c75c31994cec328bd6d8b4b22e915fa7393a03b3ab96d1218f43dc7ef77c957cc488dc38100bdf504d08a80a131e89b2e49cfa2be5 + checksum: 10/dd71efe9412433169b805d5c346a6473e539ce30f605752a0d40a0733feba37259bd72bb4ad2ab591e2eaff1ee56633de160c1e98efdc8f373cf33a4a8660275 languageName: node linkType: hard -"@babel/types@npm:^7.21.3, @babel/types@npm:^7.22.15, @babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.25.0, @babel/types@npm:^7.25.2, @babel/types@npm:^7.25.4, @babel/types@npm:^7.4.4": - version: 7.25.4 - resolution: "@babel/types@npm:7.25.4" +"@babel/types@npm:^7.21.3, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.5, @babel/types@npm:^7.28.6, @babel/types@npm:^7.4.4": + version: 7.28.6 + resolution: "@babel/types@npm:7.28.6" dependencies: - "@babel/helper-string-parser": "npm:^7.24.8" - "@babel/helper-validator-identifier": "npm:^7.24.7" - to-fast-properties: "npm:^2.0.0" - checksum: 10/d4a1194612d0a2a6ce9a0be325578b43d74e5f5278c67409468ba0a924341f0ad349ef0245ee8a36da3766efe5cc59cd6bb52547674150f97d8dc4c8cfa5d6b8 + "@babel/helper-string-parser": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.28.5" + checksum: 10/f9c6e52b451065aae5654686ecfc7de2d27dd0fbbc204ee2bd912a71daa359521a32f378981b1cf333ace6c8f86928814452cb9f388a7da59ad468038deb6b5f languageName: node linkType: hard @@ -1689,6 +1453,600 @@ __metadata: languageName: node linkType: hard +"@csstools/cascade-layer-name-parser@npm:^2.0.5": + version: 2.0.5 + resolution: "@csstools/cascade-layer-name-parser@npm:2.0.5" + peerDependencies: + "@csstools/css-parser-algorithms": ^3.0.5 + "@csstools/css-tokenizer": ^3.0.4 + checksum: 10/fb26ae1db6f7a71ee0c3fdaea89f5325f88d7a0b2505fcf4b75e94f2c816ef1edb2961eecbc397df06f67d696ccc6bc99588ea9ee07dd7632bf10febf6b67ed9 + languageName: node + linkType: hard + +"@csstools/color-helpers@npm:^5.1.0": + version: 5.1.0 + resolution: "@csstools/color-helpers@npm:5.1.0" + checksum: 10/0138b3d5ccbe77aeccf6721fd008a53523c70e932f0c82dca24a1277ca780447e1d8357da47512ebf96358476f8764de57002f3e491920d67e69202f5a74c383 + languageName: node + linkType: hard + +"@csstools/css-calc@npm:^2.1.4": + version: 2.1.4 + resolution: "@csstools/css-calc@npm:2.1.4" + peerDependencies: + "@csstools/css-parser-algorithms": ^3.0.5 + "@csstools/css-tokenizer": ^3.0.4 + checksum: 10/06975b650c0f44c60eeb7afdb3fd236f2dd607b2c622e0bc908d3f54de39eb84e0692833320d03dac04bd6c1ab0154aa3fa0dd442bd9e5f917cf14d8e2ba8d74 + languageName: node + linkType: hard + +"@csstools/css-color-parser@npm:^3.1.0": + version: 3.1.0 + resolution: "@csstools/css-color-parser@npm:3.1.0" + dependencies: + "@csstools/color-helpers": "npm:^5.1.0" + "@csstools/css-calc": "npm:^2.1.4" + peerDependencies: + "@csstools/css-parser-algorithms": ^3.0.5 + "@csstools/css-tokenizer": ^3.0.4 + checksum: 10/4741095fdc4501e8e7ada4ed14fbf9dbbe6fea9b989818790ebca15657c29c62defbebacf18592cde2aa638a1d098bbe86d742d2c84ba932fbc00fac51cb8805 + languageName: node + linkType: hard + +"@csstools/css-parser-algorithms@npm:^3.0.5": + version: 3.0.5 + resolution: "@csstools/css-parser-algorithms@npm:3.0.5" + peerDependencies: + "@csstools/css-tokenizer": ^3.0.4 + checksum: 10/e93083b5cb36a3c1e7a47ce10cf62961d05bd1e4c608bb3ee50186ff740157ab0ec16a3956f7b86251efd10703034d849693201eea858ae904848c68d2d46ada + languageName: node + linkType: hard + +"@csstools/css-tokenizer@npm:^3.0.4": + version: 3.0.4 + resolution: "@csstools/css-tokenizer@npm:3.0.4" + checksum: 10/eb6c84c086312f6bb8758dfe2c85addd7475b0927333c5e39a4d59fb210b9810f8c346972046f95e60a721329cffe98895abe451e51de753ad1ca7a8c24ec65f + languageName: node + linkType: hard + +"@csstools/media-query-list-parser@npm:^4.0.3": + version: 4.0.3 + resolution: "@csstools/media-query-list-parser@npm:4.0.3" + peerDependencies: + "@csstools/css-parser-algorithms": ^3.0.5 + "@csstools/css-tokenizer": ^3.0.4 + checksum: 10/ac4e34c21a1c7fc8b788274f316c29ff2f07e7a08996d27b9beb06454666591be9946362c1b7c4d342558c278c8e7d0e35655043e47a9ffd94c3fdb292fbe020 + languageName: node + linkType: hard + +"@csstools/postcss-alpha-function@npm:^1.0.1": + version: 1.0.1 + resolution: "@csstools/postcss-alpha-function@npm:1.0.1" + dependencies: + "@csstools/css-color-parser": "npm:^3.1.0" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/40dfd418eb36fe87500769e2ee31717fc549eced3152966a4a5b4121e657a9846d14ef9bffee4faa3298a362d85af2684c3a4fea31bbca785205e7bfecbb94dc + languageName: node + linkType: hard + +"@csstools/postcss-cascade-layers@npm:^5.0.2": + version: 5.0.2 + resolution: "@csstools/postcss-cascade-layers@npm:5.0.2" + dependencies: + "@csstools/selector-specificity": "npm:^5.0.0" + postcss-selector-parser: "npm:^7.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/9b73c28338f75eebd1032d6375e76547f90683806971f1dd3a47e6305901c89642094e1a80815fcfbb10b0afb61174f9ab3207db860a5841ca92ae993dc87cbe + languageName: node + linkType: hard + +"@csstools/postcss-color-function-display-p3-linear@npm:^1.0.1": + version: 1.0.1 + resolution: "@csstools/postcss-color-function-display-p3-linear@npm:1.0.1" + dependencies: + "@csstools/css-color-parser": "npm:^3.1.0" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/10b1b098d66314d287cca728c601c6905017769a31dd27488da49922937476a22eb280232e4b1df352b4f76158994dc18607cfc7b565d83346746795cb3f7844 + languageName: node + linkType: hard + +"@csstools/postcss-color-function@npm:^4.0.12": + version: 4.0.12 + resolution: "@csstools/postcss-color-function@npm:4.0.12" + dependencies: + "@csstools/css-color-parser": "npm:^3.1.0" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/b13563a097966f9f670544e7f76abe8d170a59d09c5e7bd26533daf5b6bffcc74a82e694d5d970326299b5fa70c52972d9aeabe5dbd2fd90a3322668d4aa3e74 + languageName: node + linkType: hard + +"@csstools/postcss-color-mix-function@npm:^3.0.12": + version: 3.0.12 + resolution: "@csstools/postcss-color-mix-function@npm:3.0.12" + dependencies: + "@csstools/css-color-parser": "npm:^3.1.0" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/f4ac11b913860e919fc325e817ba1dd7fa2740d6a86eb2abe92013ac8173fa4efb697f6ccffa3178526fa9ed6274ce654bf278adc86effa62dd1f5adf16e2f7c + languageName: node + linkType: hard + +"@csstools/postcss-color-mix-variadic-function-arguments@npm:^1.0.2": + version: 1.0.2 + resolution: "@csstools/postcss-color-mix-variadic-function-arguments@npm:1.0.2" + dependencies: + "@csstools/css-color-parser": "npm:^3.1.0" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/a38642b7020589ffc684f0f4c76a2e59a8d6dc75f55036a06c9e8a109c55245234c9fb50eae6f2b97b0046591767af922d0a089a8a0c742372cf4935411f5e5c + languageName: node + linkType: hard + +"@csstools/postcss-content-alt-text@npm:^2.0.8": + version: 2.0.8 + resolution: "@csstools/postcss-content-alt-text@npm:2.0.8" + dependencies: + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/a69e1daf2fddd4cfb46806a7e5888b9138d498e173b15040d27d963a3d66aaaed9097a780291229e5dafaf8292443b4adcb329d4f1a4fb7d3f04ef2edd798c12 + languageName: node + linkType: hard + +"@csstools/postcss-contrast-color-function@npm:^2.0.12": + version: 2.0.12 + resolution: "@csstools/postcss-contrast-color-function@npm:2.0.12" + dependencies: + "@csstools/css-color-parser": "npm:^3.1.0" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/ac8fed35786d6e4c077d34b023a72278e29a5cef90ee834df273ce0197fcee9848b3d40046bfff37959f42c7cfb4f14ffac1b58a86d87a80c1759a9300db7c49 + languageName: node + linkType: hard + +"@csstools/postcss-exponential-functions@npm:^2.0.9": + version: 2.0.9 + resolution: "@csstools/postcss-exponential-functions@npm:2.0.9" + dependencies: + "@csstools/css-calc": "npm:^2.1.4" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + peerDependencies: + postcss: ^8.4 + checksum: 10/80d5847d747fc67c32ee3ba49f9c9290654fb086c58b2f13256b14124b7349dac68ba8e107f631248cef2448ca57ef18adbbbc816dd63a54ba91826345373f39 + languageName: node + linkType: hard + +"@csstools/postcss-font-format-keywords@npm:^4.0.0": + version: 4.0.0 + resolution: "@csstools/postcss-font-format-keywords@npm:4.0.0" + dependencies: + "@csstools/utilities": "npm:^2.0.0" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/63091d4748cfc5a51e3c288cd620f058a4e776ba15da6180edaee94aaad9c4e92076f575d064dabc00b28966b33dd1e59f84a6ca6a66aed59556ef92a0dfed45 + languageName: node + linkType: hard + +"@csstools/postcss-gamut-mapping@npm:^2.0.11": + version: 2.0.11 + resolution: "@csstools/postcss-gamut-mapping@npm:2.0.11" + dependencies: + "@csstools/css-color-parser": "npm:^3.1.0" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + peerDependencies: + postcss: ^8.4 + checksum: 10/be4cb5a14eef78acbd9dfca7cdad0ab4e8e4a11c9e8bbb27e427bfd276fd5d3aa37bc1bf36deb040d404398989a3123bd70fc51be970c4d944cf6a18d231c1b8 + languageName: node + linkType: hard + +"@csstools/postcss-gradients-interpolation-method@npm:^5.0.12": + version: 5.0.12 + resolution: "@csstools/postcss-gradients-interpolation-method@npm:5.0.12" + dependencies: + "@csstools/css-color-parser": "npm:^3.1.0" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/902505cccb5a3b91d0cb8c22594130a9da3b8ec8be135b406ef7ab799e3564a8c571a08820dbe83de556d011ef9b0fe298d7cfcb741e98862ac66b287c938bf2 + languageName: node + linkType: hard + +"@csstools/postcss-hwb-function@npm:^4.0.12": + version: 4.0.12 + resolution: "@csstools/postcss-hwb-function@npm:4.0.12" + dependencies: + "@csstools/css-color-parser": "npm:^3.1.0" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/8e37a45cffa9458466fa9a05a0926ea1579e6b21501c59bb464282481f41a2694f45343e85d37da744a36a99a4ceb3e263aeca46ea5fcfb8a12a5558cc11efaa + languageName: node + linkType: hard + +"@csstools/postcss-ic-unit@npm:^4.0.4": + version: 4.0.4 + resolution: "@csstools/postcss-ic-unit@npm:4.0.4" + dependencies: + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/3bbdbba983686b9e12a5bbf36bb2ba823a6426efb9369ca415e342c37136e041929fcafacb6fa113a06a117c22785098707c91dbf306446e66618c7881553324 + languageName: node + linkType: hard + +"@csstools/postcss-initial@npm:^2.0.1": + version: 2.0.1 + resolution: "@csstools/postcss-initial@npm:2.0.1" + peerDependencies: + postcss: ^8.4 + checksum: 10/d0e7205d1db23f7957472738f039c9029f2cc80a7ed03a47d459a543f687327e3a92e75ad871dd0ca0522999e00cd834c4b225e3fbee72edffbb051ea6cec014 + languageName: node + linkType: hard + +"@csstools/postcss-is-pseudo-class@npm:^5.0.3": + version: 5.0.3 + resolution: "@csstools/postcss-is-pseudo-class@npm:5.0.3" + dependencies: + "@csstools/selector-specificity": "npm:^5.0.0" + postcss-selector-parser: "npm:^7.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/99abf2595c3b92ba993f26704c622837ec7546832037f75778d74a2c3d2e5009a027e52178d7f5c967d9c0dcda44244db9a8131c51e42fcbf4a0c22f21b3b1b6 + languageName: node + linkType: hard + +"@csstools/postcss-light-dark-function@npm:^2.0.11": + version: 2.0.11 + resolution: "@csstools/postcss-light-dark-function@npm:2.0.11" + dependencies: + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/52fa6464e31d4815557ef9bcff0a94a89549bcf1ccb4ffcc51478a5fa01815311fb2b52b96e3f671c64da8493fb50d3fc235cbfcec797f685dcccb4133dc09c4 + languageName: node + linkType: hard + +"@csstools/postcss-logical-float-and-clear@npm:^3.0.0": + version: 3.0.0 + resolution: "@csstools/postcss-logical-float-and-clear@npm:3.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/793d9a89c28d4809a83b6111d321f60947a59f119d61046e5c4023ce2caedbb221298e69b6df38995e51b763545807db7b03da47e47461622f32928fec92b65f + languageName: node + linkType: hard + +"@csstools/postcss-logical-overflow@npm:^2.0.0": + version: 2.0.0 + resolution: "@csstools/postcss-logical-overflow@npm:2.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/bf73ea1d7754f59773af5a7b434e9eaa2ce05c8fe7aa26a726dce8f2a42abb0f5686fbf9672d25912250226174c35f2c5737ca072d21f8b68420500b7449fe58 + languageName: node + linkType: hard + +"@csstools/postcss-logical-overscroll-behavior@npm:^2.0.0": + version: 2.0.0 + resolution: "@csstools/postcss-logical-overscroll-behavior@npm:2.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/bf043fdad02b9578fc2dcddb409b014a15dee65a9813ceb583237dff1caf807e18101f68bde2b0d8b685139d823114ab8deed6da3027878d11a945755824d3b1 + languageName: node + linkType: hard + +"@csstools/postcss-logical-resize@npm:^3.0.0": + version: 3.0.0 + resolution: "@csstools/postcss-logical-resize@npm:3.0.0" + dependencies: + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/3be1133a9ac27e0a0d73b19d573adc00ad78a697522eaf6c9de90260882ba8ff0904c7ab3e68379ee7724e28661c4b497cb665e258214bc8355f4a0d91021c46 + languageName: node + linkType: hard + +"@csstools/postcss-logical-viewport-units@npm:^3.0.4": + version: 3.0.4 + resolution: "@csstools/postcss-logical-viewport-units@npm:3.0.4" + dependencies: + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/utilities": "npm:^2.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/ddb8d9b473c55cce1c1261652d657d33d9306d80112eac578d53b05dd48a5607ea2064fcf6bc298ccc1e63143e11517d35230bad6063dae14d445530c45a81ec + languageName: node + linkType: hard + +"@csstools/postcss-media-minmax@npm:^2.0.9": + version: 2.0.9 + resolution: "@csstools/postcss-media-minmax@npm:2.0.9" + dependencies: + "@csstools/css-calc": "npm:^2.1.4" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/media-query-list-parser": "npm:^4.0.3" + peerDependencies: + postcss: ^8.4 + checksum: 10/ddd35129dc482a2cffe44cc75c48844cee56370f551e7e3abcfa0a158c3a2a48d8a2196e82e223fdf794a066688d423558e211f69010cfbc6044c989464d3899 + languageName: node + linkType: hard + +"@csstools/postcss-media-queries-aspect-ratio-number-values@npm:^3.0.5": + version: 3.0.5 + resolution: "@csstools/postcss-media-queries-aspect-ratio-number-values@npm:3.0.5" + dependencies: + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/media-query-list-parser": "npm:^4.0.3" + peerDependencies: + postcss: ^8.4 + checksum: 10/b0124a071c7880327b23ebcd77e2c74594a852bf9193f2f552630d9e8b0996789884c05cf4ebff4dbf5c3bfb5e6cb70e9e52a740f150034bfae87208898d3d9d + languageName: node + linkType: hard + +"@csstools/postcss-nested-calc@npm:^4.0.0": + version: 4.0.0 + resolution: "@csstools/postcss-nested-calc@npm:4.0.0" + dependencies: + "@csstools/utilities": "npm:^2.0.0" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/f334861687d7e3a4b9c26940e767a06f07e0095cab405a5b086fca407d6f743c57b552d4504ba7d5b1700a97da3507a41bf3bc2d126a26028b79f96ea38b6af5 + languageName: node + linkType: hard + +"@csstools/postcss-normalize-display-values@npm:^4.0.1": + version: 4.0.1 + resolution: "@csstools/postcss-normalize-display-values@npm:4.0.1" + dependencies: + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/46138f696ddadc0777dc66e97ff3a5f5a4dfa4f25ac396590b22df66dcc46d335c19af4fb4468e35472e1379ff180c858839c3ad51e7763ba3f9d898b00fb8a1 + languageName: node + linkType: hard + +"@csstools/postcss-oklab-function@npm:^4.0.12": + version: 4.0.12 + resolution: "@csstools/postcss-oklab-function@npm:4.0.12" + dependencies: + "@csstools/css-color-parser": "npm:^3.1.0" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/d5a57c23939bdb71ab9cf0ecf35b217ee958206e4b31f7955ff006a74284de51fb79bc1df50974171d2975bd0fa5d34a31687c49d1c52c36d4b83ee09b7544fc + languageName: node + linkType: hard + +"@csstools/postcss-position-area-property@npm:^1.0.0": + version: 1.0.0 + resolution: "@csstools/postcss-position-area-property@npm:1.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/50f1274b8f88d89d90494f7511c2d34736ccc6f48ce650efe85772fb1a355c98bc41b749ba6c7129de24a26536c77166a850a912b650c9c6781665ed9e85321e + languageName: node + linkType: hard + +"@csstools/postcss-progressive-custom-properties@npm:^4.2.1": + version: 4.2.1 + resolution: "@csstools/postcss-progressive-custom-properties@npm:4.2.1" + dependencies: + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/aefbdcd7ceaa25c004c454245148ed03cdeecf420887062c04eb0ff1a0ea0394ac174da2968250db34278236ecae5b25d8b3fb0c6b9b9e594b67f13e99ba56fd + languageName: node + linkType: hard + +"@csstools/postcss-property-rule-prelude-list@npm:^1.0.0": + version: 1.0.0 + resolution: "@csstools/postcss-property-rule-prelude-list@npm:1.0.0" + dependencies: + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + peerDependencies: + postcss: ^8.4 + checksum: 10/f915cef138a8a96d256a47c6c317456d3d31d516777bc3d556ad8276a2d919405cc24781c91e4c629f2bf009e79be84f38cf62ac73fe94edd7bf61d4b2c7cf93 + languageName: node + linkType: hard + +"@csstools/postcss-random-function@npm:^2.0.1": + version: 2.0.1 + resolution: "@csstools/postcss-random-function@npm:2.0.1" + dependencies: + "@csstools/css-calc": "npm:^2.1.4" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + peerDependencies: + postcss: ^8.4 + checksum: 10/d421a790b11675edf493f3e48259636beca164c494ed2883042118b35674d26f04e1a46f9e89203a179e20acc2a1f5912078ec81b330a2c1a1abef7e7387e587 + languageName: node + linkType: hard + +"@csstools/postcss-relative-color-syntax@npm:^3.0.12": + version: 3.0.12 + resolution: "@csstools/postcss-relative-color-syntax@npm:3.0.12" + dependencies: + "@csstools/css-color-parser": "npm:^3.1.0" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/7c6b5671268c1e30e8f113305c362d567010a0164e2b573a4d878289d5e79ab390d95975375a4c1ab577a1075d244bf242a411c4ca7ecc395546664d59becc0b + languageName: node + linkType: hard + +"@csstools/postcss-scope-pseudo-class@npm:^4.0.1": + version: 4.0.1 + resolution: "@csstools/postcss-scope-pseudo-class@npm:4.0.1" + dependencies: + postcss-selector-parser: "npm:^7.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/043667ad54b3a26e619d6c16129c1f4d8f8c7cd1c52443475aa7782dbc411390c23bd2fe41ea9c6a3f280594abbcdd9d4117a3d7c27cd2a77e31e6fd11e29fc0 + languageName: node + linkType: hard + +"@csstools/postcss-sign-functions@npm:^1.1.4": + version: 1.1.4 + resolution: "@csstools/postcss-sign-functions@npm:1.1.4" + dependencies: + "@csstools/css-calc": "npm:^2.1.4" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + peerDependencies: + postcss: ^8.4 + checksum: 10/0afcb008142a0a41df51267d79cf950f4f314394dca7c041e3a0be87df56517ac5400861630a979b5bef49f01c296025106622110384039e3c8f82802d6adcde + languageName: node + linkType: hard + +"@csstools/postcss-stepped-value-functions@npm:^4.0.9": + version: 4.0.9 + resolution: "@csstools/postcss-stepped-value-functions@npm:4.0.9" + dependencies: + "@csstools/css-calc": "npm:^2.1.4" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + peerDependencies: + postcss: ^8.4 + checksum: 10/6465a883be42d4cc4a4e83be2626a1351de4bfe84a63641c53e7c39d3c0e109152489ca2d8235625cdf6726341c676b9fbbca18fe80bb5eae8d488a0e42fc5e4 + languageName: node + linkType: hard + +"@csstools/postcss-syntax-descriptor-syntax-production@npm:^1.0.1": + version: 1.0.1 + resolution: "@csstools/postcss-syntax-descriptor-syntax-production@npm:1.0.1" + dependencies: + "@csstools/css-tokenizer": "npm:^3.0.4" + peerDependencies: + postcss: ^8.4 + checksum: 10/d0216cf3cd0b86203c5927cb211500543dec498d6d91b393caaa1df82af7dd7570a477a9a829ab15341ef812e341a7b34193f204a18c10e571b6da8df14c2127 + languageName: node + linkType: hard + +"@csstools/postcss-system-ui-font-family@npm:^1.0.0": + version: 1.0.0 + resolution: "@csstools/postcss-system-ui-font-family@npm:1.0.0" + dependencies: + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + peerDependencies: + postcss: ^8.4 + checksum: 10/6e2eed873ce887e3e3cec8d36d48fb71ef68b9995275ba008b3d5538ce63704eb4c9d4b1bd8e4a9e6d605116d7658a64557abbca7858069c7e81ea386433b8f9 + languageName: node + linkType: hard + +"@csstools/postcss-text-decoration-shorthand@npm:^4.0.3": + version: 4.0.3 + resolution: "@csstools/postcss-text-decoration-shorthand@npm:4.0.3" + dependencies: + "@csstools/color-helpers": "npm:^5.1.0" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/afc350e389bae7fdceecb3876b9be00bdbd56e5f43054f9f5de2d42b3c55a163e5ba737212030479389c9c1fca5d066f5b051da1fdf72e13191a035d2cc6f4e0 + languageName: node + linkType: hard + +"@csstools/postcss-trigonometric-functions@npm:^4.0.9": + version: 4.0.9 + resolution: "@csstools/postcss-trigonometric-functions@npm:4.0.9" + dependencies: + "@csstools/css-calc": "npm:^2.1.4" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + peerDependencies: + postcss: ^8.4 + checksum: 10/c746cd986df061a87de4f2d0129aa2d2e98a2948e5005fe6fe419a9e9ec7a0f7382461847cbd3f67f8f66169bdf23a1d7f53ca6b9922ddd235ec45f2867a8825 + languageName: node + linkType: hard + +"@csstools/postcss-unset-value@npm:^4.0.0": + version: 4.0.0 + resolution: "@csstools/postcss-unset-value@npm:4.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/af65b1c59fe93fa15ad6e5a6edbfd6fe89a3c6e19118a4729592f623c1f55b14518f2de3e8ef2bb6838b1540ebffc9df1a4f1e097dea44abf0faeefeb93d1f58 + languageName: node + linkType: hard + +"@csstools/selector-resolve-nested@npm:^3.1.0": + version: 3.1.0 + resolution: "@csstools/selector-resolve-nested@npm:3.1.0" + peerDependencies: + postcss-selector-parser: ^7.0.0 + checksum: 10/eaad6a6c99345cae2849a2c73daf53381fabd75851eefd830ee743e4d454d4e2930aa99c8b9e651fed92b9a8361f352c6c754abf82c576bba4953f1e59c927e9 + languageName: node + linkType: hard + +"@csstools/selector-specificity@npm:^5.0.0": + version: 5.0.0 + resolution: "@csstools/selector-specificity@npm:5.0.0" + peerDependencies: + postcss-selector-parser: ^7.0.0 + checksum: 10/8df1a01a1fa52b66c7ba0286e1c77d1faff45009876f09ddcac542a1c4bca9f34ee92a10acf056b8e7b7ac93679c1635496c6cdfd7d88dbaff2b6afd1eb823ec + languageName: node + linkType: hard + +"@csstools/utilities@npm:^2.0.0": + version: 2.0.0 + resolution: "@csstools/utilities@npm:2.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/c9c8d82063ec5156d56b056c9124fed95714f05d7c1a64043174b0559aa099989f17a826579f22045384defe152e32d6355b7a9660cfed96819f43fccf277941 + languageName: node + linkType: hard + "@discoveryjs/json-ext@npm:0.5.7": version: 0.5.7 resolution: "@discoveryjs/json-ext@npm:0.5.7" @@ -1696,25 +2054,41 @@ __metadata: languageName: node linkType: hard -"@docsearch/css@npm:3.5.2": - version: 3.5.2 - resolution: "@docsearch/css@npm:3.5.2" - checksum: 10/a4d3cdeb75d2811eae6edc45f69a8e8490d3a639c74d235c6c952ea7e7f75591f541924f854a69bbf5fba9ab956ac3b3a1114a6badd3a2d461faca3166cfc457 +"@docsearch/core@npm:4.5.3": + version: 4.5.3 + resolution: "@docsearch/core@npm:4.5.3" + peerDependencies: + "@types/react": ">= 16.8.0 < 20.0.0" + react: ">= 16.8.0 < 20.0.0" + react-dom: ">= 16.8.0 < 20.0.0" + peerDependenciesMeta: + "@types/react": + optional: true + react: + optional: true + react-dom: + optional: true + checksum: 10/0254ff87d6ac62b94d6e266a37ec8702655175b6faccad6d20af8e37f98e47770ea4a556c929221e683676df337b032c09abfed0fda44b8d9cdcf073c33f2917 languageName: node linkType: hard -"@docsearch/react@npm:^3.5.2": - version: 3.5.2 - resolution: "@docsearch/react@npm:3.5.2" +"@docsearch/css@npm:4.5.3": + version: 4.5.3 + resolution: "@docsearch/css@npm:4.5.3" + checksum: 10/2e706d9f6f619018a5b19e50cd0227f336361cc747e4659bc533336d5c1bea5e135435aa4e312171c4cadc394b60c54ec9d144dd198a6aa366a8d1dec112505c + languageName: node + linkType: hard + +"@docsearch/react@npm:^3.9.0 || ^4.1.0": + version: 4.5.3 + resolution: "@docsearch/react@npm:4.5.3" dependencies: - "@algolia/autocomplete-core": "npm:1.9.3" - "@algolia/autocomplete-preset-algolia": "npm:1.9.3" - "@docsearch/css": "npm:3.5.2" - algoliasearch: "npm:^4.19.1" + "@docsearch/core": "npm:4.5.3" + "@docsearch/css": "npm:4.5.3" peerDependencies: - "@types/react": ">= 16.8.0 < 19.0.0" - react: ">= 16.8.0 < 19.0.0" - react-dom: ">= 16.8.0 < 19.0.0" + "@types/react": ">= 16.8.0 < 20.0.0" + react: ">= 16.8.0 < 20.0.0" + react-dom: ">= 16.8.0 < 20.0.0" search-insights: ">= 1 < 3" peerDependenciesMeta: "@types/react": @@ -1725,128 +2099,181 @@ __metadata: optional: true search-insights: optional: true - checksum: 10/3191dbb443625ea993480e7f4ff8189ff3ca4e8ab0c69248d61363dd32853dc7c338af9ae0e8014cc30f61ec6b94a21d7cd66f3d9128ff9def1971b6c21a6f36 + checksum: 10/855af70e9af5f09420b23c55cb93e973ba0ef39c2454df94385ff46cf9659a08795e7c8361a1583f9bf4c98c985290462984348685def6df375e3d1027ec3b6a languageName: node linkType: hard -"@docusaurus/core@npm:3.5.2, @docusaurus/core@npm:^3.1.1": - version: 3.5.2 - resolution: "@docusaurus/core@npm:3.5.2" +"@docusaurus/babel@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/babel@npm:3.9.2" dependencies: - "@babel/core": "npm:^7.23.3" - "@babel/generator": "npm:^7.23.3" + "@babel/core": "npm:^7.25.9" + "@babel/generator": "npm:^7.25.9" "@babel/plugin-syntax-dynamic-import": "npm:^7.8.3" - "@babel/plugin-transform-runtime": "npm:^7.22.9" - "@babel/preset-env": "npm:^7.22.9" - "@babel/preset-react": "npm:^7.22.5" - "@babel/preset-typescript": "npm:^7.22.5" - "@babel/runtime": "npm:^7.22.6" - "@babel/runtime-corejs3": "npm:^7.22.6" - "@babel/traverse": "npm:^7.22.8" - "@docusaurus/cssnano-preset": "npm:3.5.2" - "@docusaurus/logger": "npm:3.5.2" - "@docusaurus/mdx-loader": "npm:3.5.2" - "@docusaurus/utils": "npm:3.5.2" - "@docusaurus/utils-common": "npm:3.5.2" - "@docusaurus/utils-validation": "npm:3.5.2" - autoprefixer: "npm:^10.4.14" - babel-loader: "npm:^9.1.3" + "@babel/plugin-transform-runtime": "npm:^7.25.9" + "@babel/preset-env": "npm:^7.25.9" + "@babel/preset-react": "npm:^7.25.9" + "@babel/preset-typescript": "npm:^7.25.9" + "@babel/runtime": "npm:^7.25.9" + "@babel/runtime-corejs3": "npm:^7.25.9" + "@babel/traverse": "npm:^7.25.9" + "@docusaurus/logger": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" babel-plugin-dynamic-import-node: "npm:^2.3.3" + fs-extra: "npm:^11.1.1" + tslib: "npm:^2.6.0" + checksum: 10/ff1806ee5899e61c37930046faf4e485368ebb5412a42b65666c0a9dd15f330acf3136cbccb8d8ff98f9d1403b407b698fbffab4b23c08815607152c1dda1c2a + languageName: node + linkType: hard + +"@docusaurus/bundler@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/bundler@npm:3.9.2" + dependencies: + "@babel/core": "npm:^7.25.9" + "@docusaurus/babel": "npm:3.9.2" + "@docusaurus/cssnano-preset": "npm:3.9.2" + "@docusaurus/logger": "npm:3.9.2" + "@docusaurus/types": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" + babel-loader: "npm:^9.2.1" + clean-css: "npm:^5.3.3" + copy-webpack-plugin: "npm:^11.0.0" + css-loader: "npm:^6.11.0" + css-minimizer-webpack-plugin: "npm:^5.0.1" + cssnano: "npm:^6.1.2" + file-loader: "npm:^6.2.0" + html-minifier-terser: "npm:^7.2.0" + mini-css-extract-plugin: "npm:^2.9.2" + null-loader: "npm:^4.0.1" + postcss: "npm:^8.5.4" + postcss-loader: "npm:^7.3.4" + postcss-preset-env: "npm:^10.2.1" + terser-webpack-plugin: "npm:^5.3.9" + tslib: "npm:^2.6.0" + url-loader: "npm:^4.1.1" + webpack: "npm:^5.95.0" + webpackbar: "npm:^6.0.1" + peerDependencies: + "@docusaurus/faster": "*" + peerDependenciesMeta: + "@docusaurus/faster": + optional: true + checksum: 10/767c3554305b7e7ab6d5b4bf36ab00bad3d257a9936f7b3698fe9d8275e10a16098ad84d4492a469ddde4dcf098e75df8cca82443fbeb634719fdf6d7fe2c4be + languageName: node + linkType: hard + +"@docusaurus/core@npm:3.9.2, @docusaurus/core@npm:^3.9.0": + version: 3.9.2 + resolution: "@docusaurus/core@npm:3.9.2" + dependencies: + "@docusaurus/babel": "npm:3.9.2" + "@docusaurus/bundler": "npm:3.9.2" + "@docusaurus/logger": "npm:3.9.2" + "@docusaurus/mdx-loader": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" + "@docusaurus/utils-common": "npm:3.9.2" + "@docusaurus/utils-validation": "npm:3.9.2" boxen: "npm:^6.2.1" chalk: "npm:^4.1.2" chokidar: "npm:^3.5.3" - clean-css: "npm:^5.3.2" cli-table3: "npm:^0.6.3" combine-promises: "npm:^1.1.0" commander: "npm:^5.1.0" - copy-webpack-plugin: "npm:^11.0.0" core-js: "npm:^3.31.1" - css-loader: "npm:^6.8.1" - css-minimizer-webpack-plugin: "npm:^5.0.1" - cssnano: "npm:^6.1.2" - del: "npm:^6.1.1" detect-port: "npm:^1.5.1" escape-html: "npm:^1.0.3" eta: "npm:^2.2.0" eval: "npm:^0.1.8" - file-loader: "npm:^6.2.0" + execa: "npm:5.1.1" fs-extra: "npm:^11.1.1" - html-minifier-terser: "npm:^7.2.0" html-tags: "npm:^3.3.1" - html-webpack-plugin: "npm:^5.5.3" + html-webpack-plugin: "npm:^5.6.0" leven: "npm:^3.1.0" lodash: "npm:^4.17.21" - mini-css-extract-plugin: "npm:^2.7.6" + open: "npm:^8.4.0" p-map: "npm:^4.0.0" - postcss: "npm:^8.4.26" - postcss-loader: "npm:^7.3.3" prompts: "npm:^2.4.2" - react-dev-utils: "npm:^12.0.1" - react-helmet-async: "npm:^1.3.0" + react-helmet-async: "npm:@slorber/react-helmet-async@1.3.0" react-loadable: "npm:@docusaurus/react-loadable@6.0.0" react-loadable-ssr-addon-v5-slorber: "npm:^1.0.1" react-router: "npm:^5.3.4" react-router-config: "npm:^5.1.1" react-router-dom: "npm:^5.3.4" - rtl-detect: "npm:^1.0.4" semver: "npm:^7.5.4" - serve-handler: "npm:^6.1.5" - shelljs: "npm:^0.8.5" - terser-webpack-plugin: "npm:^5.3.9" + serve-handler: "npm:^6.1.6" + tinypool: "npm:^1.0.2" tslib: "npm:^2.6.0" update-notifier: "npm:^6.0.2" - url-loader: "npm:^4.1.1" - webpack: "npm:^5.88.1" - webpack-bundle-analyzer: "npm:^4.9.0" - webpack-dev-server: "npm:^4.15.1" - webpack-merge: "npm:^5.9.0" - webpackbar: "npm:^5.0.2" + webpack: "npm:^5.95.0" + webpack-bundle-analyzer: "npm:^4.10.2" + webpack-dev-server: "npm:^5.2.2" + webpack-merge: "npm:^6.0.1" peerDependencies: "@mdx-js/react": ^3.0.0 - react: ^18.0.0 - react-dom: ^18.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 bin: docusaurus: bin/docusaurus.mjs - checksum: 10/4515fe7502ad9912c2d25b7b1ec56196d4f96fc5b4702f6f8551ab2b3dbd0e6286f789e3663ceb8e95b31af0816817b0b35a7fb230a0c950b7e2802f30966442 + checksum: 10/3d6e1219648fdf8f32c3a53c06c79df512715edb054a524f1874d81f7a95f9bd599ca803ae9decbec3917ec648aa10606643f2b9c36b12ef349b4cbe3a4abf23 languageName: node linkType: hard -"@docusaurus/cssnano-preset@npm:3.5.2": - version: 3.5.2 - resolution: "@docusaurus/cssnano-preset@npm:3.5.2" +"@docusaurus/cssnano-preset@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/cssnano-preset@npm:3.9.2" dependencies: cssnano-preset-advanced: "npm:^6.1.2" - postcss: "npm:^8.4.38" + postcss: "npm:^8.5.4" postcss-sort-media-queries: "npm:^5.2.0" tslib: "npm:^2.6.0" - checksum: 10/4bb1fae3741e14cbbdb64c1b0707435970838bf219831234a70cf382e6811ffac1cadf733d5e1fe7c278e7b2a9e533bfa802a5212b22ec46edd703208cf49f92 + checksum: 10/21b9b787042ab00db945969c45fdd6fb489cd0811ad256e482b44db3d6846b4f9c54a5b360e76f7160736e2866602d690bbe29d3cb9b08577d7087b38720566f languageName: node linkType: hard -"@docusaurus/logger@npm:3.5.2": - version: 3.5.2 - resolution: "@docusaurus/logger@npm:3.5.2" +"@docusaurus/faster@npm:^3.9.0": + version: 3.9.2 + resolution: "@docusaurus/faster@npm:3.9.2" + dependencies: + "@docusaurus/types": "npm:3.9.2" + "@rspack/core": "npm:^1.5.0" + "@swc/core": "npm:^1.7.39" + "@swc/html": "npm:^1.13.5" + browserslist: "npm:^4.24.2" + lightningcss: "npm:^1.27.0" + swc-loader: "npm:^0.2.6" + tslib: "npm:^2.6.0" + webpack: "npm:^5.95.0" + peerDependencies: + "@docusaurus/types": "*" + checksum: 10/116c4df683a59199c2522407015ca066917accf2ab148fb93c36274c6db669feb23198a6dc15201205670e10fd7ce33a143c14827188f37084ad8ca0aac42824 + languageName: node + linkType: hard + +"@docusaurus/logger@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/logger@npm:3.9.2" dependencies: chalk: "npm:^4.1.2" tslib: "npm:^2.6.0" - checksum: 10/9cc1ac17503fdd739ceba6340289bf39740e1aad87e0a7e5da97fcd2f6186b5f4da05bbbbf660a43af04f0ec8bc2aaac086d6d31c79ab64c71acb3da36213b9e + checksum: 10/4d7b1bccc90fa108d7c8ffd1cd071ce2e179e0af9989f4c76acfe021789b186a15e4e56db55360df15de3e41f1d69c4e851a73cdf594fa02bb6fb219eaf48e57 languageName: node linkType: hard -"@docusaurus/mdx-loader@npm:3.5.2": - version: 3.5.2 - resolution: "@docusaurus/mdx-loader@npm:3.5.2" +"@docusaurus/mdx-loader@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/mdx-loader@npm:3.9.2" dependencies: - "@docusaurus/logger": "npm:3.5.2" - "@docusaurus/utils": "npm:3.5.2" - "@docusaurus/utils-validation": "npm:3.5.2" + "@docusaurus/logger": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" + "@docusaurus/utils-validation": "npm:3.9.2" "@mdx-js/mdx": "npm:^3.0.0" "@slorber/remark-comment": "npm:^1.0.0" escape-html: "npm:^1.0.3" estree-util-value-to-estree: "npm:^3.0.1" file-loader: "npm:^6.2.0" fs-extra: "npm:^11.1.1" - image-size: "npm:^1.0.2" + image-size: "npm:^2.0.2" mdast-util-mdx: "npm:^3.0.0" mdast-util-to-string: "npm:^4.0.0" rehype-raw: "npm:^7.0.0" @@ -1862,67 +2289,67 @@ __metadata: vfile: "npm:^6.0.1" webpack: "npm:^5.88.1" peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10/9d9f771c1af1d285e182c2966159d4d0904fae563d7ce0e4e3d5fbf2a98555721252a32d1acd4f971f0a5fca90929906fd1ad9c005c7ec9306801c11b3c64ffc + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 10/00777730141774c9f465ee9b35a61c414e694f43995377c0f1a6a3fa07cca177eea45614be72a8ed40b094cdcc5fd2ba3762b68a4ad410071d2ea018a502d76e languageName: node linkType: hard -"@docusaurus/module-type-aliases@npm:3.5.2, @docusaurus/module-type-aliases@npm:^3.1.1": - version: 3.5.2 - resolution: "@docusaurus/module-type-aliases@npm:3.5.2" +"@docusaurus/module-type-aliases@npm:3.9.2, @docusaurus/module-type-aliases@npm:^3.1.1": + version: 3.9.2 + resolution: "@docusaurus/module-type-aliases@npm:3.9.2" dependencies: - "@docusaurus/types": "npm:3.5.2" + "@docusaurus/types": "npm:3.9.2" "@types/history": "npm:^4.7.11" "@types/react": "npm:*" "@types/react-router-config": "npm:*" "@types/react-router-dom": "npm:*" - react-helmet-async: "npm:*" + react-helmet-async: "npm:@slorber/react-helmet-async@1.3.0" react-loadable: "npm:@docusaurus/react-loadable@6.0.0" peerDependencies: react: "*" react-dom: "*" - checksum: 10/48d7b8a1f4fb946d541cff312a953149692bd6590e0ee2e6877b6903f7cb1871c6e79dfcaad465bd24201388d58a85a7a49c53d95f6f51d51bd1ae49d37020f2 + checksum: 10/6b47ea9f31871e7200ca8c76016496a524fa0b0458ab9bdd582afbd79a9c1756c1ad8414c1cf8ff942b94b2505d1a50492963d8d59294cafc5f7b894e82f3d5f languageName: node linkType: hard -"@docusaurus/plugin-client-redirects@npm:^3.1.1": - version: 3.5.2 - resolution: "@docusaurus/plugin-client-redirects@npm:3.5.2" +"@docusaurus/plugin-client-redirects@npm:^3.9.0": + version: 3.9.2 + resolution: "@docusaurus/plugin-client-redirects@npm:3.9.2" dependencies: - "@docusaurus/core": "npm:3.5.2" - "@docusaurus/logger": "npm:3.5.2" - "@docusaurus/utils": "npm:3.5.2" - "@docusaurus/utils-common": "npm:3.5.2" - "@docusaurus/utils-validation": "npm:3.5.2" + "@docusaurus/core": "npm:3.9.2" + "@docusaurus/logger": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" + "@docusaurus/utils-common": "npm:3.9.2" + "@docusaurus/utils-validation": "npm:3.9.2" eta: "npm:^2.2.0" fs-extra: "npm:^11.1.1" lodash: "npm:^4.17.21" tslib: "npm:^2.6.0" peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10/f42d00b7be74b140dc71aa72a33570bd7742d9e2b094d465500f54657a7058a205f741cbf5c5520871e6bc8a2842a6d376a6b37a1929141b564d8cceb5beaa9b + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 10/2f45f0a247bf02c54b4fdbd7de73be6523ad467869573861fe083586e46b94844eb01103e5e1e3c1f44d93b2e9b9e64c8fac9168910fe9d6aa377c2fcd79701a languageName: node linkType: hard -"@docusaurus/plugin-content-blog@npm:3.5.2": - version: 3.5.2 - resolution: "@docusaurus/plugin-content-blog@npm:3.5.2" +"@docusaurus/plugin-content-blog@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/plugin-content-blog@npm:3.9.2" dependencies: - "@docusaurus/core": "npm:3.5.2" - "@docusaurus/logger": "npm:3.5.2" - "@docusaurus/mdx-loader": "npm:3.5.2" - "@docusaurus/theme-common": "npm:3.5.2" - "@docusaurus/types": "npm:3.5.2" - "@docusaurus/utils": "npm:3.5.2" - "@docusaurus/utils-common": "npm:3.5.2" - "@docusaurus/utils-validation": "npm:3.5.2" + "@docusaurus/core": "npm:3.9.2" + "@docusaurus/logger": "npm:3.9.2" + "@docusaurus/mdx-loader": "npm:3.9.2" + "@docusaurus/theme-common": "npm:3.9.2" + "@docusaurus/types": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" + "@docusaurus/utils-common": "npm:3.9.2" + "@docusaurus/utils-validation": "npm:3.9.2" cheerio: "npm:1.0.0-rc.12" feed: "npm:^4.2.2" fs-extra: "npm:^11.1.1" lodash: "npm:^4.17.21" - reading-time: "npm:^1.5.0" + schema-dts: "npm:^1.1.2" srcset: "npm:^4.0.0" tslib: "npm:^2.6.0" unist-util-visit: "npm:^5.0.0" @@ -1930,189 +2357,224 @@ __metadata: webpack: "npm:^5.88.1" peerDependencies: "@docusaurus/plugin-content-docs": "*" - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10/42429c6d9182e6fb6699eefb016597be11718859bda9a107aaa226c67bda08a3f567952be72b04f71ded7e18bd3513380aa42466b960495c8219db3f1b0b3c83 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 10/86680042f4b36acf22a7f83cd432eebca31d2770027595e48151b21e081bc50bf18f4e090549a69dd2d4da49cec83953bfb40c71c1a3a91d36ddd0f84351a659 languageName: node linkType: hard -"@docusaurus/plugin-content-docs@npm:3.5.2": - version: 3.5.2 - resolution: "@docusaurus/plugin-content-docs@npm:3.5.2" +"@docusaurus/plugin-content-docs@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/plugin-content-docs@npm:3.9.2" dependencies: - "@docusaurus/core": "npm:3.5.2" - "@docusaurus/logger": "npm:3.5.2" - "@docusaurus/mdx-loader": "npm:3.5.2" - "@docusaurus/module-type-aliases": "npm:3.5.2" - "@docusaurus/theme-common": "npm:3.5.2" - "@docusaurus/types": "npm:3.5.2" - "@docusaurus/utils": "npm:3.5.2" - "@docusaurus/utils-common": "npm:3.5.2" - "@docusaurus/utils-validation": "npm:3.5.2" + "@docusaurus/core": "npm:3.9.2" + "@docusaurus/logger": "npm:3.9.2" + "@docusaurus/mdx-loader": "npm:3.9.2" + "@docusaurus/module-type-aliases": "npm:3.9.2" + "@docusaurus/theme-common": "npm:3.9.2" + "@docusaurus/types": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" + "@docusaurus/utils-common": "npm:3.9.2" + "@docusaurus/utils-validation": "npm:3.9.2" "@types/react-router-config": "npm:^5.0.7" combine-promises: "npm:^1.1.0" fs-extra: "npm:^11.1.1" js-yaml: "npm:^4.1.0" lodash: "npm:^4.17.21" + schema-dts: "npm:^1.1.2" tslib: "npm:^2.6.0" utility-types: "npm:^3.10.0" webpack: "npm:^5.88.1" peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10/100250c4e988de1f31cf5bc1fd9f6b6929ccfbb748113352bf5ac4b90ed2f096cb4366d95451a367d26d79735879711f0ec1eae352a7829c0f5e40443c873d6f + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 10/5a3fb39a1add1f5db8aabd03b56f65e988a776d5983def89b6b4f17ab0c78dd4de255aab080a082aa8e7e7915da1705277148789fada56d9c6d9ad8f468e784e languageName: node linkType: hard -"@docusaurus/plugin-content-pages@npm:3.5.2": - version: 3.5.2 - resolution: "@docusaurus/plugin-content-pages@npm:3.5.2" +"@docusaurus/plugin-content-pages@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/plugin-content-pages@npm:3.9.2" dependencies: - "@docusaurus/core": "npm:3.5.2" - "@docusaurus/mdx-loader": "npm:3.5.2" - "@docusaurus/types": "npm:3.5.2" - "@docusaurus/utils": "npm:3.5.2" - "@docusaurus/utils-validation": "npm:3.5.2" + "@docusaurus/core": "npm:3.9.2" + "@docusaurus/mdx-loader": "npm:3.9.2" + "@docusaurus/types": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" + "@docusaurus/utils-validation": "npm:3.9.2" fs-extra: "npm:^11.1.1" tslib: "npm:^2.6.0" webpack: "npm:^5.88.1" peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10/f89fdd30d4c4e6e750c6814f28eb21f6886d4e5f6c56bbfb14fc518262b19406222da48ab7884dce0dd599045d6325ae89a02a16a807df60b8d52aa27cdb4e8b + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 10/341b5ac4d4e55fcb8f72cad3977f548e19061d4bb19392f5dea194cdb7f7d260640ed656a793145809fc8c0ca5fb15cac6e9b6c73545a83dfcd0463cad3f60a2 languageName: node linkType: hard -"@docusaurus/plugin-debug@npm:3.5.2": - version: 3.5.2 - resolution: "@docusaurus/plugin-debug@npm:3.5.2" +"@docusaurus/plugin-css-cascade-layers@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/plugin-css-cascade-layers@npm:3.9.2" dependencies: - "@docusaurus/core": "npm:3.5.2" - "@docusaurus/types": "npm:3.5.2" - "@docusaurus/utils": "npm:3.5.2" + "@docusaurus/core": "npm:3.9.2" + "@docusaurus/types": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" + "@docusaurus/utils-validation": "npm:3.9.2" + tslib: "npm:^2.6.0" + checksum: 10/039debb4e3764b9ecb6aafa2b7ca1d59f2cf52bb4f4e71390394d554149b0bc9a7aa355f2ea72a92c2e2fbdd5b7ed7843cd8100d1b8d0f62187588253ff8d6d5 + languageName: node + linkType: hard + +"@docusaurus/plugin-debug@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/plugin-debug@npm:3.9.2" + dependencies: + "@docusaurus/core": "npm:3.9.2" + "@docusaurus/types": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" fs-extra: "npm:^11.1.1" - react-json-view-lite: "npm:^1.2.0" + react-json-view-lite: "npm:^2.3.0" tslib: "npm:^2.6.0" peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10/a839e6c3a595ea202fdd7fbce638ab8df26ba73a8c7ead8c04d1bbb509ebe34e9633e7fe9eb54a7a733e93a03d74a60df4d9f6597b9621ff464280d4dd71db34 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 10/8974d17143f3b379c977441011f6c9daa8d38eb484c33726b9e9a63012553e66847a1a18e7e86ed52bc5321d2833ee8e934a1619afcdd669b255089ffd23a506 languageName: node linkType: hard -"@docusaurus/plugin-google-analytics@npm:3.5.2": - version: 3.5.2 - resolution: "@docusaurus/plugin-google-analytics@npm:3.5.2" +"@docusaurus/plugin-google-analytics@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/plugin-google-analytics@npm:3.9.2" dependencies: - "@docusaurus/core": "npm:3.5.2" - "@docusaurus/types": "npm:3.5.2" - "@docusaurus/utils-validation": "npm:3.5.2" + "@docusaurus/core": "npm:3.9.2" + "@docusaurus/types": "npm:3.9.2" + "@docusaurus/utils-validation": "npm:3.9.2" tslib: "npm:^2.6.0" peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10/0b8c4d21333d40c2509d6ef807caaf69f085010c5deac514ab34f53b5486fd76766c90213dc98976a6c4d66fdfa14bf6b05594e51e8a53ec60c2a3fa08fd9a83 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 10/026ffe26b4e47adf509f3de79a9a4b095e0374e4db0ce73041f9d28933df1e7697069bce7a0be1744eeca2b2d5ed057f023f4f2e98aff838b33cc2c2d1eed882 languageName: node linkType: hard -"@docusaurus/plugin-google-gtag@npm:3.5.2": - version: 3.5.2 - resolution: "@docusaurus/plugin-google-gtag@npm:3.5.2" +"@docusaurus/plugin-google-gtag@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/plugin-google-gtag@npm:3.9.2" dependencies: - "@docusaurus/core": "npm:3.5.2" - "@docusaurus/types": "npm:3.5.2" - "@docusaurus/utils-validation": "npm:3.5.2" + "@docusaurus/core": "npm:3.9.2" + "@docusaurus/types": "npm:3.9.2" + "@docusaurus/utils-validation": "npm:3.9.2" "@types/gtag.js": "npm:^0.0.12" tslib: "npm:^2.6.0" peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10/5d53c2483c8c7e3a8e842bd091a774d4041f0e165d216b3c02f031a224a77258c9456e8b2acd0500b4a0eff474a83c1b82803628db9d4b132514409936c68ac4 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 10/ee596c5a1d1a71d947c390274326b46a9aeb56c3d0c0c0b672629e890e783f6f6947505dc66ee0abe4d70fe61f743ff9df0a4b4cdc4fe3994eefbb1a4386769b languageName: node linkType: hard -"@docusaurus/plugin-google-tag-manager@npm:3.5.2": - version: 3.5.2 - resolution: "@docusaurus/plugin-google-tag-manager@npm:3.5.2" +"@docusaurus/plugin-google-tag-manager@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/plugin-google-tag-manager@npm:3.9.2" dependencies: - "@docusaurus/core": "npm:3.5.2" - "@docusaurus/types": "npm:3.5.2" - "@docusaurus/utils-validation": "npm:3.5.2" + "@docusaurus/core": "npm:3.9.2" + "@docusaurus/types": "npm:3.9.2" + "@docusaurus/utils-validation": "npm:3.9.2" tslib: "npm:^2.6.0" peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10/9a6fc2ca54ea677c6edfd78f4f392d7d9ae86afd085fcda96d5ac41efa441352c25a2519595d9d15fb9b838e2ae39837f0daf02e2406c5cd56199ae237bd7b7a + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 10/0c4ee07b5ee2cdf252b53aa52b54f42ae591441df5e289e2a1614bcabe1852f973ec65a1086c7682f7e5a5e65a50c0597ed9c393505c2902f1a52569798ef6c0 languageName: node linkType: hard -"@docusaurus/plugin-sitemap@npm:3.5.2": - version: 3.5.2 - resolution: "@docusaurus/plugin-sitemap@npm:3.5.2" +"@docusaurus/plugin-sitemap@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/plugin-sitemap@npm:3.9.2" dependencies: - "@docusaurus/core": "npm:3.5.2" - "@docusaurus/logger": "npm:3.5.2" - "@docusaurus/types": "npm:3.5.2" - "@docusaurus/utils": "npm:3.5.2" - "@docusaurus/utils-common": "npm:3.5.2" - "@docusaurus/utils-validation": "npm:3.5.2" + "@docusaurus/core": "npm:3.9.2" + "@docusaurus/logger": "npm:3.9.2" + "@docusaurus/types": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" + "@docusaurus/utils-common": "npm:3.9.2" + "@docusaurus/utils-validation": "npm:3.9.2" fs-extra: "npm:^11.1.1" sitemap: "npm:^7.1.1" tslib: "npm:^2.6.0" peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10/74d17206fdf7fb1fe826e0876b71f0e404e115e98a71fe34a1b0ce607e84b285dfd0f5995bab53eeff2e32ee3f03ae5c27d9337bd8cab9108f55bcbb2f5d9333 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 10/adba37c28fadf7901b6d12253093b92c90856a00f98f9830b039fdcb92c2472cdb43a0f939a9d3012db0e003571fd77e6cb58972c375b70b7bae748be2a6f263 languageName: node linkType: hard -"@docusaurus/preset-classic@npm:^3.1.1": - version: 3.5.2 - resolution: "@docusaurus/preset-classic@npm:3.5.2" +"@docusaurus/plugin-svgr@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/plugin-svgr@npm:3.9.2" dependencies: - "@docusaurus/core": "npm:3.5.2" - "@docusaurus/plugin-content-blog": "npm:3.5.2" - "@docusaurus/plugin-content-docs": "npm:3.5.2" - "@docusaurus/plugin-content-pages": "npm:3.5.2" - "@docusaurus/plugin-debug": "npm:3.5.2" - "@docusaurus/plugin-google-analytics": "npm:3.5.2" - "@docusaurus/plugin-google-gtag": "npm:3.5.2" - "@docusaurus/plugin-google-tag-manager": "npm:3.5.2" - "@docusaurus/plugin-sitemap": "npm:3.5.2" - "@docusaurus/theme-classic": "npm:3.5.2" - "@docusaurus/theme-common": "npm:3.5.2" - "@docusaurus/theme-search-algolia": "npm:3.5.2" - "@docusaurus/types": "npm:3.5.2" + "@docusaurus/core": "npm:3.9.2" + "@docusaurus/types": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" + "@docusaurus/utils-validation": "npm:3.9.2" + "@svgr/core": "npm:8.1.0" + "@svgr/webpack": "npm:^8.1.0" + tslib: "npm:^2.6.0" + webpack: "npm:^5.88.1" peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10/ec578e62b3b13b1874b14235a448a913c2d2358ea9b9d9c60bb250be468ab62387c88ec44e1ee82ad5b3d7243306e31919888a80eae62e5e8eab0ae12194bf69 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 10/5e5b7789a34c1d013d248e41ea8704dc22174aaff4fdd77336886021f5ae38e435bb8109b97f122ba2ecbcba87a8d0cd0705987e2fc014162d567ea38cc1ee04 languageName: node linkType: hard -"@docusaurus/theme-classic@npm:3.5.2": - version: 3.5.2 - resolution: "@docusaurus/theme-classic@npm:3.5.2" +"@docusaurus/preset-classic@npm:^3.9.0": + version: 3.9.2 + resolution: "@docusaurus/preset-classic@npm:3.9.2" dependencies: - "@docusaurus/core": "npm:3.5.2" - "@docusaurus/mdx-loader": "npm:3.5.2" - "@docusaurus/module-type-aliases": "npm:3.5.2" - "@docusaurus/plugin-content-blog": "npm:3.5.2" - "@docusaurus/plugin-content-docs": "npm:3.5.2" - "@docusaurus/plugin-content-pages": "npm:3.5.2" - "@docusaurus/theme-common": "npm:3.5.2" - "@docusaurus/theme-translations": "npm:3.5.2" - "@docusaurus/types": "npm:3.5.2" - "@docusaurus/utils": "npm:3.5.2" - "@docusaurus/utils-common": "npm:3.5.2" - "@docusaurus/utils-validation": "npm:3.5.2" + "@docusaurus/core": "npm:3.9.2" + "@docusaurus/plugin-content-blog": "npm:3.9.2" + "@docusaurus/plugin-content-docs": "npm:3.9.2" + "@docusaurus/plugin-content-pages": "npm:3.9.2" + "@docusaurus/plugin-css-cascade-layers": "npm:3.9.2" + "@docusaurus/plugin-debug": "npm:3.9.2" + "@docusaurus/plugin-google-analytics": "npm:3.9.2" + "@docusaurus/plugin-google-gtag": "npm:3.9.2" + "@docusaurus/plugin-google-tag-manager": "npm:3.9.2" + "@docusaurus/plugin-sitemap": "npm:3.9.2" + "@docusaurus/plugin-svgr": "npm:3.9.2" + "@docusaurus/theme-classic": "npm:3.9.2" + "@docusaurus/theme-common": "npm:3.9.2" + "@docusaurus/theme-search-algolia": "npm:3.9.2" + "@docusaurus/types": "npm:3.9.2" + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 10/ae8328253111429e9ca219273d5ae8c39f26a98c2422b25a8d233e3a0f2737e453dd1dbcc29e4c9a601b936d0217b3e91dce218d12f9f33793ae3120ed629c1c + languageName: node + linkType: hard + +"@docusaurus/theme-classic@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/theme-classic@npm:3.9.2" + dependencies: + "@docusaurus/core": "npm:3.9.2" + "@docusaurus/logger": "npm:3.9.2" + "@docusaurus/mdx-loader": "npm:3.9.2" + "@docusaurus/module-type-aliases": "npm:3.9.2" + "@docusaurus/plugin-content-blog": "npm:3.9.2" + "@docusaurus/plugin-content-docs": "npm:3.9.2" + "@docusaurus/plugin-content-pages": "npm:3.9.2" + "@docusaurus/theme-common": "npm:3.9.2" + "@docusaurus/theme-translations": "npm:3.9.2" + "@docusaurus/types": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" + "@docusaurus/utils-common": "npm:3.9.2" + "@docusaurus/utils-validation": "npm:3.9.2" "@mdx-js/react": "npm:^3.0.0" clsx: "npm:^2.0.0" - copy-text-to-clipboard: "npm:^3.2.0" - infima: "npm:0.2.0-alpha.44" + infima: "npm:0.2.0-alpha.45" lodash: "npm:^4.17.21" nprogress: "npm:^0.2.0" - postcss: "npm:^8.4.26" + postcss: "npm:^8.5.4" prism-react-renderer: "npm:^2.3.0" prismjs: "npm:^1.29.0" react-router-dom: "npm:^5.3.4" @@ -2120,20 +2582,20 @@ __metadata: tslib: "npm:^2.6.0" utility-types: "npm:^3.10.0" peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10/74a4bf64ba6699ebb3adea29d7c57996b032a6d77e4b650dcf68b8af7d6152a3712354dd964eab674a189d582ffbde50037cec8c0cf6369da267c37e5856eb2b + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 10/aeb889cf6d0d4b8836d4684d775b70098f675f3d53cf24cca773784d7ed9ffa33c6240ea87bcd33b9abb6a66ba6a4c3c1d16c004da1e6ad4167af5600f3131d6 languageName: node linkType: hard -"@docusaurus/theme-common@npm:3.5.2": - version: 3.5.2 - resolution: "@docusaurus/theme-common@npm:3.5.2" +"@docusaurus/theme-common@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/theme-common@npm:3.9.2" dependencies: - "@docusaurus/mdx-loader": "npm:3.5.2" - "@docusaurus/module-type-aliases": "npm:3.5.2" - "@docusaurus/utils": "npm:3.5.2" - "@docusaurus/utils-common": "npm:3.5.2" + "@docusaurus/mdx-loader": "npm:3.9.2" + "@docusaurus/module-type-aliases": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" + "@docusaurus/utils-common": "npm:3.9.2" "@types/history": "npm:^4.7.11" "@types/react": "npm:*" "@types/react-router-config": "npm:*" @@ -2144,26 +2606,26 @@ __metadata: utility-types: "npm:^3.10.0" peerDependencies: "@docusaurus/plugin-content-docs": "*" - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10/1cb8f97516f152f3b4a747926e9c1a22dc1be5ffc8cfe1a3b0ac5a3bdaaa749b61fb34dbf4c0cb03e319b2cc9ea5c12b39dedcd869721cd2ad8d19be82e015f9 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 10/697a23f4bcad3ec49af6386e939f5d10c04c7160daaee11e78c021b385a18d6d040f6c7a86f8f8d0e076fc2d2abd4eea71d51849bebc8c409af5d73a7b2c6738 languageName: node linkType: hard -"@docusaurus/theme-search-algolia@npm:3.5.2": - version: 3.5.2 - resolution: "@docusaurus/theme-search-algolia@npm:3.5.2" +"@docusaurus/theme-search-algolia@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/theme-search-algolia@npm:3.9.2" dependencies: - "@docsearch/react": "npm:^3.5.2" - "@docusaurus/core": "npm:3.5.2" - "@docusaurus/logger": "npm:3.5.2" - "@docusaurus/plugin-content-docs": "npm:3.5.2" - "@docusaurus/theme-common": "npm:3.5.2" - "@docusaurus/theme-translations": "npm:3.5.2" - "@docusaurus/utils": "npm:3.5.2" - "@docusaurus/utils-validation": "npm:3.5.2" - algoliasearch: "npm:^4.18.0" - algoliasearch-helper: "npm:^3.13.3" + "@docsearch/react": "npm:^3.9.0 || ^4.1.0" + "@docusaurus/core": "npm:3.9.2" + "@docusaurus/logger": "npm:3.9.2" + "@docusaurus/plugin-content-docs": "npm:3.9.2" + "@docusaurus/theme-common": "npm:3.9.2" + "@docusaurus/theme-translations": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" + "@docusaurus/utils-validation": "npm:3.9.2" + algoliasearch: "npm:^5.37.0" + algoliasearch-helper: "npm:^3.26.0" clsx: "npm:^2.0.0" eta: "npm:^2.2.0" fs-extra: "npm:^11.1.1" @@ -2171,87 +2633,85 @@ __metadata: tslib: "npm:^2.6.0" utility-types: "npm:^3.10.0" peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10/24e9378dec5ec91d1f2a6e539182d2424bbf14d60e92db180870716e3c7327931d4aab912a8251e18323395bd5b9a10f97f9af87e88a962ef691903fb9f3d7be + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 10/e2623c11c69a40eaec8cbf76e4b19444c0929ee47a899882dc0b5e314153e64aa1ed64b7a0ea0b182bdf418e616191a147e5ff5ff9679efec761e0757d7a0825 languageName: node linkType: hard -"@docusaurus/theme-translations@npm:3.5.2": - version: 3.5.2 - resolution: "@docusaurus/theme-translations@npm:3.5.2" +"@docusaurus/theme-translations@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/theme-translations@npm:3.9.2" dependencies: fs-extra: "npm:^11.1.1" tslib: "npm:^2.6.0" - checksum: 10/f89d7d87b4fb2fd43e286c557d9fa830d06a369516cd6c26d43afd7ab4d92c57be47c2ca8bf5779c150193511bab439237e6d2901e637a8c094955de34f9a044 + checksum: 10/a98b5a7b1d7f123b5609caadea4000b4ee956426396d4a62ce675bd1e0fe7fd65be3bba2ef97b2ec3f84bafd65fd53d1d55d3a35c585ddc7288ebca199c5a865 languageName: node linkType: hard "@docusaurus/tsconfig@npm:^3.1.1": - version: 3.5.2 - resolution: "@docusaurus/tsconfig@npm:3.5.2" - checksum: 10/808a17eaf422ae9a948c6558dd1e92d4700b067ead3a63a84049c6845bf94f84e311cd0e4d517047fe9ea057efe393bb22c2d5c92d727d06c9f895e971f2c3ea + version: 3.9.2 + resolution: "@docusaurus/tsconfig@npm:3.9.2" + checksum: 10/8e08e352ab8285d4601822dbd424700a7c875b30dd2eefb087f540e62e5a9f05026368625ce3e89948380f0b232ecd7a52e03ce069fab4de1e6a39dedf324d1b languageName: node linkType: hard -"@docusaurus/types@npm:3.5.2, @docusaurus/types@npm:^3.1.1": - version: 3.5.2 - resolution: "@docusaurus/types@npm:3.5.2" +"@docusaurus/types@npm:3.9.2, @docusaurus/types@npm:^3.9.0": + version: 3.9.2 + resolution: "@docusaurus/types@npm:3.9.2" dependencies: "@mdx-js/mdx": "npm:^3.0.0" "@types/history": "npm:^4.7.11" + "@types/mdast": "npm:^4.0.2" "@types/react": "npm:*" commander: "npm:^5.1.0" joi: "npm:^17.9.2" - react-helmet-async: "npm:^1.3.0" + react-helmet-async: "npm:@slorber/react-helmet-async@1.3.0" utility-types: "npm:^3.10.0" - webpack: "npm:^5.88.1" + webpack: "npm:^5.95.0" webpack-merge: "npm:^5.9.0" peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10/d13193916812312ae06d0e193c9f5d778948a6f1635d03b381b06a10d12f6479394e617fc5ef5b028fd7a155090d366b6ccd15b5552895645be2fede880faf0b + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 10/bc91d05a206de008bfb301873877dd531b361d64a40d1301106a6952901bb3662d349e0e0c9af9e40ff1b6c7764741cc86e15e4d116c1b7d7f065dde0e8e38e1 languageName: node linkType: hard -"@docusaurus/utils-common@npm:3.5.2": - version: 3.5.2 - resolution: "@docusaurus/utils-common@npm:3.5.2" +"@docusaurus/utils-common@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/utils-common@npm:3.9.2" dependencies: + "@docusaurus/types": "npm:3.9.2" tslib: "npm:^2.6.0" - peerDependencies: - "@docusaurus/types": "*" - peerDependenciesMeta: - "@docusaurus/types": - optional: true - checksum: 10/9d550c89663d4271456ae0832c82a1691207ccc95e21df3a05a4bd6bbd2624bb9e3ab7327d939c04b2023378987bcf99321b2c37be1af214852832f65d6db14a + checksum: 10/77e01da05ebbf202343b3131ffaba7132c764ccc0ab48dd4421535c75a59e2d4a1dcaf8471cde257b5394528ada52f79da6398f6f2eb9fde79904be736f19275 languageName: node linkType: hard -"@docusaurus/utils-validation@npm:3.5.2": - version: 3.5.2 - resolution: "@docusaurus/utils-validation@npm:3.5.2" +"@docusaurus/utils-validation@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/utils-validation@npm:3.9.2" dependencies: - "@docusaurus/logger": "npm:3.5.2" - "@docusaurus/utils": "npm:3.5.2" - "@docusaurus/utils-common": "npm:3.5.2" + "@docusaurus/logger": "npm:3.9.2" + "@docusaurus/utils": "npm:3.9.2" + "@docusaurus/utils-common": "npm:3.9.2" fs-extra: "npm:^11.2.0" joi: "npm:^17.9.2" js-yaml: "npm:^4.1.0" lodash: "npm:^4.17.21" tslib: "npm:^2.6.0" - checksum: 10/636a5c0d3543c6bd64a844e4ff365afbba270ab7722a6b22c64384939ce952d88df72000e7d47e80adf312810fbb501c20c7afdbfa755b19ecbda31c26073dd0 + checksum: 10/d8a853d7e79efd986be4dd3eb575ac287b58aa2363086742856671bace480f3ee35920634057d661fba2bda5992c7d45ac5dc021d77e5882e356d735e428e60f languageName: node linkType: hard -"@docusaurus/utils@npm:3.5.2": - version: 3.5.2 - resolution: "@docusaurus/utils@npm:3.5.2" +"@docusaurus/utils@npm:3.9.2": + version: 3.9.2 + resolution: "@docusaurus/utils@npm:3.9.2" dependencies: - "@docusaurus/logger": "npm:3.5.2" - "@docusaurus/utils-common": "npm:3.5.2" - "@svgr/webpack": "npm:^8.1.0" + "@docusaurus/logger": "npm:3.9.2" + "@docusaurus/types": "npm:3.9.2" + "@docusaurus/utils-common": "npm:3.9.2" escape-string-regexp: "npm:^4.0.0" + execa: "npm:5.1.1" file-loader: "npm:^6.2.0" fs-extra: "npm:^11.1.1" github-slugger: "npm:^1.5.0" @@ -2261,19 +2721,42 @@ __metadata: js-yaml: "npm:^4.1.0" lodash: "npm:^4.17.21" micromatch: "npm:^4.0.5" + p-queue: "npm:^6.6.2" prompts: "npm:^2.4.2" resolve-pathname: "npm:^3.0.0" - shelljs: "npm:^0.8.5" tslib: "npm:^2.6.0" url-loader: "npm:^4.1.1" utility-types: "npm:^3.10.0" webpack: "npm:^5.88.1" - peerDependencies: - "@docusaurus/types": "*" - peerDependenciesMeta: - "@docusaurus/types": - optional: true - checksum: 10/93355851b49de217d21f9a31d5e4083bd2b1b761af438fed5eb49c3890f01aba4bcb1801f1cd69e440ef88aa29b81783479c22302ad08a061fb1b559659e34e6 + checksum: 10/bb4f24acd4fb2d998186d4bd315c5bb481e6afa3a1dfaa549bb52e2b0d4cb94bc9f635bd897690838cc812a0cff3a30cf3ab713b2d74221902b58c2214ce5268 + languageName: node + linkType: hard + +"@emnapi/core@npm:^1.5.0": + version: 1.8.1 + resolution: "@emnapi/core@npm:1.8.1" + dependencies: + "@emnapi/wasi-threads": "npm:1.1.0" + tslib: "npm:^2.4.0" + checksum: 10/904ea60c91fc7d8aeb4a8f2c433b8cfb47c50618f2b6f37429fc5093c857c6381c60628a5cfbc3a7b0d75b0a288f21d4ed2d4533e82f92c043801ef255fd6a5c + languageName: node + linkType: hard + +"@emnapi/runtime@npm:^1.5.0": + version: 1.8.1 + resolution: "@emnapi/runtime@npm:1.8.1" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10/26725e202d4baefdc4a6ba770f703dfc80825a27c27a08c22bac1e1ce6f8f75c47b4fe9424d9b63239463c33ef20b650f08d710da18dfa1164a95e5acb865dba + languageName: node + linkType: hard + +"@emnapi/wasi-threads@npm:1.1.0": + version: 1.1.0 + resolution: "@emnapi/wasi-threads@npm:1.1.0" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10/0d557e75262d2f4c95cb2a456ba0785ef61f919ce488c1d76e5e3acfd26e00c753ef928cd80068363e0c166ba8cc0141305daf0f81aad5afcd421f38f11e0f4e languageName: node linkType: hard @@ -2362,14 +2845,23 @@ __metadata: languageName: node linkType: hard -"@jridgewell/gen-mapping@npm:^0.3.0, @jridgewell/gen-mapping@npm:^0.3.2, @jridgewell/gen-mapping@npm:^0.3.5": - version: 0.3.8 - resolution: "@jridgewell/gen-mapping@npm:0.3.8" +"@jridgewell/gen-mapping@npm:^0.3.0, @jridgewell/gen-mapping@npm:^0.3.12, @jridgewell/gen-mapping@npm:^0.3.2, @jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.13 + resolution: "@jridgewell/gen-mapping@npm:0.3.13" dependencies: - "@jridgewell/set-array": "npm:^1.2.1" - "@jridgewell/sourcemap-codec": "npm:^1.4.10" + "@jridgewell/sourcemap-codec": "npm:^1.5.0" "@jridgewell/trace-mapping": "npm:^0.3.24" - checksum: 10/9d3a56ab3612ab9b85d38b2a93b87f3324f11c5130859957f6500e4ac8ce35f299d5ccc3ecd1ae87597601ecf83cee29e9afd04c18777c24011073992ff946df + checksum: 10/902f8261dcf450b4af7b93f9656918e02eec80a2169e155000cb2059f90113dd98f3ccf6efc6072cee1dd84cac48cade51da236972d942babc40e4c23da4d62a + languageName: node + linkType: hard + +"@jridgewell/remapping@npm:^2.3.5": + version: 2.3.5 + resolution: "@jridgewell/remapping@npm:2.3.5" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10/c2bb01856e65b506d439455f28aceacf130d6c023d1d4e3b48705e88def3571753e1a887daa04b078b562316c92d26ce36408a60534bceca3f830aec88a339ad languageName: node linkType: hard @@ -2380,13 +2872,6 @@ __metadata: languageName: node linkType: hard -"@jridgewell/set-array@npm:^1.2.1": - version: 1.2.1 - resolution: "@jridgewell/set-array@npm:1.2.1" - checksum: 10/832e513a85a588f8ed4f27d1279420d8547743cc37fcad5a5a76fc74bb895b013dfe614d0eed9cb860048e6546b798f8f2652020b4b2ba0561b05caa8c654b10 - languageName: node - linkType: hard - "@jridgewell/source-map@npm:^0.3.3": version: 0.3.5 resolution: "@jridgewell/source-map@npm:0.3.5" @@ -2397,20 +2882,20 @@ __metadata: languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14": - version: 1.5.0 - resolution: "@jridgewell/sourcemap-codec@npm:1.5.0" - checksum: 10/4ed6123217569a1484419ac53f6ea0d9f3b57e5b57ab30d7c267bdb27792a27eb0e4b08e84a2680aa55cc2f2b411ffd6ec3db01c44fdc6dc43aca4b55f8374fd +"@jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0": + version: 1.5.5 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" + checksum: 10/5d9d207b462c11e322d71911e55e21a4e2772f71ffe8d6f1221b8eb5ae6774458c1d242f897fb0814e8714ca9a6b498abfa74dfe4f434493342902b1a48b33a5 languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.20, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.9": - version: 0.3.25 - resolution: "@jridgewell/trace-mapping@npm:0.3.25" +"@jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.28, @jridgewell/trace-mapping@npm:^0.3.9": + version: 0.3.31 + resolution: "@jridgewell/trace-mapping@npm:0.3.31" dependencies: "@jridgewell/resolve-uri": "npm:^3.1.0" "@jridgewell/sourcemap-codec": "npm:^1.4.14" - checksum: 10/dced32160a44b49d531b80a4a2159dceab6b3ddf0c8e95a0deae4b0e894b172defa63d5ac52a19c2068e1fe7d31ea4ba931fbeec103233ecb4208953967120fc + checksum: 10/da0283270e691bdb5543806077548532791608e52386cfbbf3b9e8fb00457859d1bd01d512851161c886eb3a2f3ce6fd9bcf25db8edf3bddedd275bd4a88d606 languageName: node linkType: hard @@ -2421,6 +2906,246 @@ __metadata: languageName: node linkType: hard +"@jsonjoy.com/base64@npm:17.65.0": + version: 17.65.0 + resolution: "@jsonjoy.com/base64@npm:17.65.0" + peerDependencies: + tslib: 2 + checksum: 10/966e890665a8bb9618d8491e6432767c9e256f835b616a589bd192a24a8259696b30dea60e35aa2700a24872540fd704fefb661c7b5a8e78f886c348a29ea061 + languageName: node + linkType: hard + +"@jsonjoy.com/base64@npm:^1.1.2": + version: 1.1.2 + resolution: "@jsonjoy.com/base64@npm:1.1.2" + peerDependencies: + tslib: 2 + checksum: 10/d76bb58eff841c090d9bf69a073611ffa73c40a664ccbcea689f65961f57d7b24051269d06b437e4f6204285d6ba92f50f587c5e95c5f9e4f10b36a2ed4cd0c8 + languageName: node + linkType: hard + +"@jsonjoy.com/buffers@npm:17.65.0, @jsonjoy.com/buffers@npm:^17.65.0": + version: 17.65.0 + resolution: "@jsonjoy.com/buffers@npm:17.65.0" + peerDependencies: + tslib: 2 + checksum: 10/d5e9072da89bc1181af1436ceaaec0ad5e5749d69c3301ffd5cd56b2fc15a812e15726ddad86218b6312feaa25791f4162ed861eed73b1ec4ac389b8b576b2ef + languageName: node + linkType: hard + +"@jsonjoy.com/buffers@npm:^1.0.0, @jsonjoy.com/buffers@npm:^1.2.0": + version: 1.2.1 + resolution: "@jsonjoy.com/buffers@npm:1.2.1" + peerDependencies: + tslib: 2 + checksum: 10/8ef4784d05c0fb4d0f27a1f78f5b0ae1f3b537d237f978d10be0b88f59a534ae44db2a4bde28eee0eb461ede31dc194aab5927ac001ed2b764629fa43ae9b60b + languageName: node + linkType: hard + +"@jsonjoy.com/codegen@npm:17.65.0": + version: 17.65.0 + resolution: "@jsonjoy.com/codegen@npm:17.65.0" + peerDependencies: + tslib: 2 + checksum: 10/ea7093b9015f95fc8189a0429927b8658a43b2be528a78935730ed1e270d42e00160d94d0b51c94f0a202bbdd2a4f3396a8d2d08d22325b21feaedc00a407123 + languageName: node + linkType: hard + +"@jsonjoy.com/codegen@npm:^1.0.0": + version: 1.0.0 + resolution: "@jsonjoy.com/codegen@npm:1.0.0" + peerDependencies: + tslib: 2 + checksum: 10/a0afb03d2af4fbc1377c547e507f5db99a25f515d8c4b6b2cef1ff28145ac59fff12b6e1f41f9734cb62ea5619e7f9be1acd0908305d6f4176898ee534ee9a64 + languageName: node + linkType: hard + +"@jsonjoy.com/fs-core@npm:4.56.10": + version: 4.56.10 + resolution: "@jsonjoy.com/fs-core@npm:4.56.10" + dependencies: + "@jsonjoy.com/fs-node-builtins": "npm:4.56.10" + "@jsonjoy.com/fs-node-utils": "npm:4.56.10" + thingies: "npm:^2.5.0" + peerDependencies: + tslib: 2 + checksum: 10/4ff94bf45f11a87f7a2efa46af4bcdc7d3a3d89c44d834529bd32be9e4563910bd28ecb0e232bfcdd9365b7696a8ee4c5bc4928b8abd03ac14167209eff033a8 + languageName: node + linkType: hard + +"@jsonjoy.com/fs-fsa@npm:4.56.10": + version: 4.56.10 + resolution: "@jsonjoy.com/fs-fsa@npm:4.56.10" + dependencies: + "@jsonjoy.com/fs-core": "npm:4.56.10" + "@jsonjoy.com/fs-node-builtins": "npm:4.56.10" + "@jsonjoy.com/fs-node-utils": "npm:4.56.10" + thingies: "npm:^2.5.0" + peerDependencies: + tslib: 2 + checksum: 10/1b5213fe0c47f8ef675477bd4d66a76a5650f5891fce32599303d5a498614e248dec06cead4bcb58a0721f1c31dd2e2400b2a0941d4dc04db1861359d015aac6 + languageName: node + linkType: hard + +"@jsonjoy.com/fs-node-builtins@npm:4.56.10": + version: 4.56.10 + resolution: "@jsonjoy.com/fs-node-builtins@npm:4.56.10" + peerDependencies: + tslib: 2 + checksum: 10/49ebcc00bb794960b591808ce242c17604556a6b8792413876e75344e0506dd73d4ea4ea3cd566447e399cfacd8a41a2a0fbd361cdcb00a43bbb32a37d2c60d9 + languageName: node + linkType: hard + +"@jsonjoy.com/fs-node-to-fsa@npm:4.56.10": + version: 4.56.10 + resolution: "@jsonjoy.com/fs-node-to-fsa@npm:4.56.10" + dependencies: + "@jsonjoy.com/fs-fsa": "npm:4.56.10" + "@jsonjoy.com/fs-node-builtins": "npm:4.56.10" + "@jsonjoy.com/fs-node-utils": "npm:4.56.10" + peerDependencies: + tslib: 2 + checksum: 10/171ad50c365829684deda3cb27de69a482dbb9916b2ea944d70f2f61a337a2994d8e5b0c17df08700fbf11f71e1a513d0332d8d9379bc3ed9e707ef0ec9967bf + languageName: node + linkType: hard + +"@jsonjoy.com/fs-node-utils@npm:4.56.10": + version: 4.56.10 + resolution: "@jsonjoy.com/fs-node-utils@npm:4.56.10" + dependencies: + "@jsonjoy.com/fs-node-builtins": "npm:4.56.10" + peerDependencies: + tslib: 2 + checksum: 10/6a64c96e7b3e31580e747d675ddb380de05635970cd597c185aa254a987873410fd03cffdf52ad18512a4c9692f148a45292fbc736f1a8c0fcf03be532dd6f99 + languageName: node + linkType: hard + +"@jsonjoy.com/fs-node@npm:4.56.10": + version: 4.56.10 + resolution: "@jsonjoy.com/fs-node@npm:4.56.10" + dependencies: + "@jsonjoy.com/fs-core": "npm:4.56.10" + "@jsonjoy.com/fs-node-builtins": "npm:4.56.10" + "@jsonjoy.com/fs-node-utils": "npm:4.56.10" + "@jsonjoy.com/fs-print": "npm:4.56.10" + "@jsonjoy.com/fs-snapshot": "npm:4.56.10" + glob-to-regex.js: "npm:^1.0.0" + thingies: "npm:^2.5.0" + peerDependencies: + tslib: 2 + checksum: 10/5cb12740f9294d3e3ffe2d5fa19d620df7c1fddcefeb41940df62ff1c5fb7ca7c2d077cb4e285f35dd7abe1075a0998ce92c264c9d7551f5d8b0edf030c5332a + languageName: node + linkType: hard + +"@jsonjoy.com/fs-print@npm:4.56.10": + version: 4.56.10 + resolution: "@jsonjoy.com/fs-print@npm:4.56.10" + dependencies: + "@jsonjoy.com/fs-node-utils": "npm:4.56.10" + tree-dump: "npm:^1.1.0" + peerDependencies: + tslib: 2 + checksum: 10/48cb69d7d2c4567d6938e59d3bcd2ddaca4d2154dcb3922064a2968727009cf239b54def9fe9e5818a9042bfd749cc11c961efbec58dd686a2ece59d127bb333 + languageName: node + linkType: hard + +"@jsonjoy.com/fs-snapshot@npm:4.56.10": + version: 4.56.10 + resolution: "@jsonjoy.com/fs-snapshot@npm:4.56.10" + dependencies: + "@jsonjoy.com/buffers": "npm:^17.65.0" + "@jsonjoy.com/fs-node-utils": "npm:4.56.10" + "@jsonjoy.com/json-pack": "npm:^17.65.0" + "@jsonjoy.com/util": "npm:^17.65.0" + peerDependencies: + tslib: 2 + checksum: 10/151f38050afe22d724d3a543b5363f43f3e0e7a6904fc1c1ba890569e34c80332dbd5f30d829fad02e76ccd0a141419a3ba331b89edb86ebc3a6d1896c1b7d04 + languageName: node + linkType: hard + +"@jsonjoy.com/json-pack@npm:^1.11.0": + version: 1.21.0 + resolution: "@jsonjoy.com/json-pack@npm:1.21.0" + dependencies: + "@jsonjoy.com/base64": "npm:^1.1.2" + "@jsonjoy.com/buffers": "npm:^1.2.0" + "@jsonjoy.com/codegen": "npm:^1.0.0" + "@jsonjoy.com/json-pointer": "npm:^1.0.2" + "@jsonjoy.com/util": "npm:^1.9.0" + hyperdyperid: "npm:^1.2.0" + thingies: "npm:^2.5.0" + tree-dump: "npm:^1.1.0" + peerDependencies: + tslib: 2 + checksum: 10/138b7eb8c96e6e435b0218c8f2eb5554e4eb49198a8718673a65e81da53b4617553ffa7124b51d6ea00fdfb868d6ff8b5ad6365e8336380ca7025f04d0412ee7 + languageName: node + linkType: hard + +"@jsonjoy.com/json-pack@npm:^17.65.0": + version: 17.65.0 + resolution: "@jsonjoy.com/json-pack@npm:17.65.0" + dependencies: + "@jsonjoy.com/base64": "npm:17.65.0" + "@jsonjoy.com/buffers": "npm:17.65.0" + "@jsonjoy.com/codegen": "npm:17.65.0" + "@jsonjoy.com/json-pointer": "npm:17.65.0" + "@jsonjoy.com/util": "npm:17.65.0" + hyperdyperid: "npm:^1.2.0" + thingies: "npm:^2.5.0" + tree-dump: "npm:^1.1.0" + peerDependencies: + tslib: 2 + checksum: 10/a80ab55e08eb99a4bed1f235f9b2c9623b2c33f7ff7238a9afe6907dd140c403af551621c48ef9e4003ff00ac78612a4186c7b4edfb79fe7e43a59ba5f8028ce + languageName: node + linkType: hard + +"@jsonjoy.com/json-pointer@npm:17.65.0": + version: 17.65.0 + resolution: "@jsonjoy.com/json-pointer@npm:17.65.0" + dependencies: + "@jsonjoy.com/util": "npm:17.65.0" + peerDependencies: + tslib: 2 + checksum: 10/864e1f6e026653e4b825155b8d8b4f84cd96d40122330e01d8539abfd34cbe9e339288bec20ab2715c2ded0d6f8853d15dcdff27a4338e6e24cee13c9ce02cd1 + languageName: node + linkType: hard + +"@jsonjoy.com/json-pointer@npm:^1.0.2": + version: 1.0.2 + resolution: "@jsonjoy.com/json-pointer@npm:1.0.2" + dependencies: + "@jsonjoy.com/codegen": "npm:^1.0.0" + "@jsonjoy.com/util": "npm:^1.9.0" + peerDependencies: + tslib: 2 + checksum: 10/f22baeb3abc8ace2d8902d06ec297343431d4486dcf399aaaffd26ace7e62e194fe0efb4b7880e45b3b7939224ee838d3213448ef654fc8a61c91a76fe994d94 + languageName: node + linkType: hard + +"@jsonjoy.com/util@npm:17.65.0, @jsonjoy.com/util@npm:^17.65.0": + version: 17.65.0 + resolution: "@jsonjoy.com/util@npm:17.65.0" + dependencies: + "@jsonjoy.com/buffers": "npm:17.65.0" + "@jsonjoy.com/codegen": "npm:17.65.0" + peerDependencies: + tslib: 2 + checksum: 10/487715ccd3969a9ce053827feb5cacdf5959b4d3990202d51e4ba009095cc80c271cab8c55df5859ed2f6a36700609a9a1a5c8a2f010a665d90d6132472b7a25 + languageName: node + linkType: hard + +"@jsonjoy.com/util@npm:^1.9.0": + version: 1.9.0 + resolution: "@jsonjoy.com/util@npm:1.9.0" + dependencies: + "@jsonjoy.com/buffers": "npm:^1.0.0" + "@jsonjoy.com/codegen": "npm:^1.0.0" + peerDependencies: + tslib: 2 + checksum: 10/1a6e5301d725a7161b93ff707eb1a954bf4552a2fa96eee9a960d3ae3ed5f993d18b56dcff29e98036341a5968c5d1b2dfe21f76695390e7f0d89b81f24c85e0 + languageName: node + linkType: hard + "@leichtgewicht/ip-codec@npm:^2.0.1": version: 2.0.4 resolution: "@leichtgewicht/ip-codec@npm:2.0.4" @@ -2471,6 +3196,79 @@ __metadata: languageName: node linkType: hard +"@module-federation/error-codes@npm:0.22.0": + version: 0.22.0 + resolution: "@module-federation/error-codes@npm:0.22.0" + checksum: 10/4edb269e9f3039899f879788c84d2bfecff94ca8e87ffcd80dbf8589d8543ec32558b3fa05c8549a8abd3ac33e856ff2aacf458dea5c0d7bea608bf12bb13359 + languageName: node + linkType: hard + +"@module-federation/runtime-core@npm:0.22.0": + version: 0.22.0 + resolution: "@module-federation/runtime-core@npm:0.22.0" + dependencies: + "@module-federation/error-codes": "npm:0.22.0" + "@module-federation/sdk": "npm:0.22.0" + checksum: 10/d21969198322b6f79e0513b702d0af5097613d47819724c849b6c677c163cd10fb8c89e3ff62b798bec498ee4d8e95dec71861071bc4ed74bd86a7e43193bc05 + languageName: node + linkType: hard + +"@module-federation/runtime-tools@npm:0.22.0": + version: 0.22.0 + resolution: "@module-federation/runtime-tools@npm:0.22.0" + dependencies: + "@module-federation/runtime": "npm:0.22.0" + "@module-federation/webpack-bundler-runtime": "npm:0.22.0" + checksum: 10/0e7693c1ec02fc5bef770b478c8757cad9cfefb2310d1943151d0ad079b72472d9b2c8a087299e9124dfcd6b649c83290c7fdfa333865baab4ba193f39e7b6bd + languageName: node + linkType: hard + +"@module-federation/runtime@npm:0.22.0": + version: 0.22.0 + resolution: "@module-federation/runtime@npm:0.22.0" + dependencies: + "@module-federation/error-codes": "npm:0.22.0" + "@module-federation/runtime-core": "npm:0.22.0" + "@module-federation/sdk": "npm:0.22.0" + checksum: 10/eca608be999d7d2e83abc1169643c2f795a5ed950f9e2bdf7000400a30b3e1e0ca4bdaa5daa09f55e44868383d444707e40236cec1aaa7b40432b0cce800b7f3 + languageName: node + linkType: hard + +"@module-federation/sdk@npm:0.22.0": + version: 0.22.0 + resolution: "@module-federation/sdk@npm:0.22.0" + checksum: 10/d7085d883730a33145052520787a7e59cf9c54b51b2946bebc7c63a6bb668bcc6cbdc27fa0b7354a62f5a7ee4e8829a66b84e644607498f2e37cfd5eb4ded0da + languageName: node + linkType: hard + +"@module-federation/webpack-bundler-runtime@npm:0.22.0": + version: 0.22.0 + resolution: "@module-federation/webpack-bundler-runtime@npm:0.22.0" + dependencies: + "@module-federation/runtime": "npm:0.22.0" + "@module-federation/sdk": "npm:0.22.0" + checksum: 10/afd24406817dfc6474ebcf5be714ccf26690eb3f6f5172bda711c8f23dba149fe47293f7aa2d0733dfed0334c98d4d3d9e7c2da2be78750cae5a72d72f32ce93 + languageName: node + linkType: hard + +"@napi-rs/wasm-runtime@npm:1.0.7": + version: 1.0.7 + resolution: "@napi-rs/wasm-runtime@npm:1.0.7" + dependencies: + "@emnapi/core": "npm:^1.5.0" + "@emnapi/runtime": "npm:^1.5.0" + "@tybys/wasm-util": "npm:^0.10.1" + checksum: 10/6bc32d32d486d07b83220a9b7b2b715e39acacbacef0011ebca05c00b41d80a0535123da10fea7a7d6d7e206712bb50dc50ac3cf88b770754d44378570fb5c05 + languageName: node + linkType: hard + +"@noble/hashes@npm:1.4.0": + version: 1.4.0 + resolution: "@noble/hashes@npm:1.4.0" + checksum: 10/e156e65794c473794c52fa9d06baf1eb20903d0d96719530f523cc4450f6c721a957c544796e6efd0197b2296e7cd70efeb312f861465e17940a3e3c7e0febc6 + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -2652,6 +3450,151 @@ __metadata: languageName: node linkType: hard +"@peculiar/asn1-cms@npm:^2.6.0": + version: 2.6.0 + resolution: "@peculiar/asn1-cms@npm:2.6.0" + dependencies: + "@peculiar/asn1-schema": "npm:^2.6.0" + "@peculiar/asn1-x509": "npm:^2.6.0" + "@peculiar/asn1-x509-attr": "npm:^2.6.0" + asn1js: "npm:^3.0.6" + tslib: "npm:^2.8.1" + checksum: 10/cc3f2c60d87ecd400fe5409dc0016578c8c80511ae1295747913c5704adeb571136f1b779362996879acdb81efb34735f3fae6f8513fae4542dd004ae4615b13 + languageName: node + linkType: hard + +"@peculiar/asn1-csr@npm:^2.6.0": + version: 2.6.0 + resolution: "@peculiar/asn1-csr@npm:2.6.0" + dependencies: + "@peculiar/asn1-schema": "npm:^2.6.0" + "@peculiar/asn1-x509": "npm:^2.6.0" + asn1js: "npm:^3.0.6" + tslib: "npm:^2.8.1" + checksum: 10/68653246ae56119722ca737bddd8a3edc1dd0e2f4bcc58d611b62512667073b9ccd61a0051ca8f0a67cf6d07245ecbdbf526e6f389c81ef81e845c46a2bc5bbb + languageName: node + linkType: hard + +"@peculiar/asn1-ecc@npm:^2.6.0": + version: 2.6.0 + resolution: "@peculiar/asn1-ecc@npm:2.6.0" + dependencies: + "@peculiar/asn1-schema": "npm:^2.6.0" + "@peculiar/asn1-x509": "npm:^2.6.0" + asn1js: "npm:^3.0.6" + tslib: "npm:^2.8.1" + checksum: 10/f31146a78c634440d49e0b1959c8ba59657e0fd172c2f9aff421627aee954cf3dcaa9c9b957390960d107cc460f277b9266c95cf32e434ba6b5475f87fad6436 + languageName: node + linkType: hard + +"@peculiar/asn1-pfx@npm:^2.6.0": + version: 2.6.0 + resolution: "@peculiar/asn1-pfx@npm:2.6.0" + dependencies: + "@peculiar/asn1-cms": "npm:^2.6.0" + "@peculiar/asn1-pkcs8": "npm:^2.6.0" + "@peculiar/asn1-rsa": "npm:^2.6.0" + "@peculiar/asn1-schema": "npm:^2.6.0" + asn1js: "npm:^3.0.6" + tslib: "npm:^2.8.1" + checksum: 10/623ffda2b04822140afd0a6e1846089a55f986686d72dfb1c6eaad67791a0aaa519e13a8528f72de1a930739318ea7a87eb11d7de0c3587f950cb45338ac302b + languageName: node + linkType: hard + +"@peculiar/asn1-pkcs8@npm:^2.6.0": + version: 2.6.0 + resolution: "@peculiar/asn1-pkcs8@npm:2.6.0" + dependencies: + "@peculiar/asn1-schema": "npm:^2.6.0" + "@peculiar/asn1-x509": "npm:^2.6.0" + asn1js: "npm:^3.0.6" + tslib: "npm:^2.8.1" + checksum: 10/f76334284f5c9833eff807948363f7835ac311cb59a82dd92757960c8f8b15c8ea52bcee20b4ff21dd0adb4c7db51411cb87a612c2e28c66b4426366a302f6d8 + languageName: node + linkType: hard + +"@peculiar/asn1-pkcs9@npm:^2.6.0": + version: 2.6.0 + resolution: "@peculiar/asn1-pkcs9@npm:2.6.0" + dependencies: + "@peculiar/asn1-cms": "npm:^2.6.0" + "@peculiar/asn1-pfx": "npm:^2.6.0" + "@peculiar/asn1-pkcs8": "npm:^2.6.0" + "@peculiar/asn1-schema": "npm:^2.6.0" + "@peculiar/asn1-x509": "npm:^2.6.0" + "@peculiar/asn1-x509-attr": "npm:^2.6.0" + asn1js: "npm:^3.0.6" + tslib: "npm:^2.8.1" + checksum: 10/ab2cfd497c1585fa89c2a257c61edbb6b5a4182fc3cec0c255782d6480932732bb3b9f47c3dc1706e65f7b683dab615d9ae9c235071bee366645a95b4cc70b06 + languageName: node + linkType: hard + +"@peculiar/asn1-rsa@npm:^2.6.0": + version: 2.6.0 + resolution: "@peculiar/asn1-rsa@npm:2.6.0" + dependencies: + "@peculiar/asn1-schema": "npm:^2.6.0" + "@peculiar/asn1-x509": "npm:^2.6.0" + asn1js: "npm:^3.0.6" + tslib: "npm:^2.8.1" + checksum: 10/9084becd2d06d3b0fc8baafcebb5bdd085cc3be5221ce7f7017ae575b0abc59cd1eaeb880b475aae5a28c97eda3551ab620ba0a0a5eb72b12b8db103182d8cf4 + languageName: node + linkType: hard + +"@peculiar/asn1-schema@npm:^2.6.0": + version: 2.6.0 + resolution: "@peculiar/asn1-schema@npm:2.6.0" + dependencies: + asn1js: "npm:^3.0.6" + pvtsutils: "npm:^1.3.6" + tslib: "npm:^2.8.1" + checksum: 10/af9b1094d0e020f0fd828777488578322d62a41f597ead7d80939dafcfe35b672fcb0ec7460ef66b2a155f9614d4340a98896d417a830aff1685cb4c21d5bbe4 + languageName: node + linkType: hard + +"@peculiar/asn1-x509-attr@npm:^2.6.0": + version: 2.6.0 + resolution: "@peculiar/asn1-x509-attr@npm:2.6.0" + dependencies: + "@peculiar/asn1-schema": "npm:^2.6.0" + "@peculiar/asn1-x509": "npm:^2.6.0" + asn1js: "npm:^3.0.6" + tslib: "npm:^2.8.1" + checksum: 10/9ecd7a2e57e2199cc937691fc6e8fdce0d25039e2d16b2261ba5b7550250b903fd9d9f8bf3f5290eb23332fdb289abb79656c97477bf711af6897235312e0535 + languageName: node + linkType: hard + +"@peculiar/asn1-x509@npm:^2.6.0": + version: 2.6.0 + resolution: "@peculiar/asn1-x509@npm:2.6.0" + dependencies: + "@peculiar/asn1-schema": "npm:^2.6.0" + asn1js: "npm:^3.0.6" + pvtsutils: "npm:^1.3.6" + tslib: "npm:^2.8.1" + checksum: 10/61e5ef1bb83fa4687b0c307b9e10d982bd4b2c09af7d66d1d4c486a190dab9ebc15ae382880229d3678743a6fa32081d21d6c2d18a7bbe9d634fc09c46c244b6 + languageName: node + linkType: hard + +"@peculiar/x509@npm:^1.14.2": + version: 1.14.3 + resolution: "@peculiar/x509@npm:1.14.3" + dependencies: + "@peculiar/asn1-cms": "npm:^2.6.0" + "@peculiar/asn1-csr": "npm:^2.6.0" + "@peculiar/asn1-ecc": "npm:^2.6.0" + "@peculiar/asn1-pkcs9": "npm:^2.6.0" + "@peculiar/asn1-rsa": "npm:^2.6.0" + "@peculiar/asn1-schema": "npm:^2.6.0" + "@peculiar/asn1-x509": "npm:^2.6.0" + pvtsutils: "npm:^1.3.6" + reflect-metadata: "npm:^0.2.2" + tslib: "npm:^2.8.1" + tsyringe: "npm:^4.10.0" + checksum: 10/d37c56fa5f2c644141948d85010e14f0e4963089e3b0b81edd0bfe85bdfea0eb3f38ab6ff20d322db2bd6977117824cc498a77b2d35af111983b4d58b5e2ccd1 + languageName: node + linkType: hard + "@pkgjs/parseargs@npm:^0.11.0": version: 0.11.0 resolution: "@pkgjs/parseargs@npm:0.11.0" @@ -2705,48 +3648,183 @@ __metadata: languageName: node linkType: hard -"@redocly/config@npm:^0.20.1": - version: 0.20.3 - resolution: "@redocly/config@npm:0.20.3" - checksum: 10/dd9501c96d6aa7d6cd7d9879d9bbab6c62fced0adbc8ca2ee1cde37a65384e8b09972aad2dd3a3bef1ee48e6a6287b854deb9858b3fd27c8c36fc7aec039a496 +"@redocly/config@npm:^0.22.0": + version: 0.22.2 + resolution: "@redocly/config@npm:0.22.2" + checksum: 10/f4c33b9654a65c5407630bfebe83b46ea18fab3f5d40a0c69d4476ed1bf45cdc5edbad94cdef994e99fba14e27d1adc5ef1205422855604d1852d442518bb447 languageName: node linkType: hard -"@redocly/openapi-core@npm:^1.10.5": - version: 1.27.2 - resolution: "@redocly/openapi-core@npm:1.27.2" +"@redocly/openapi-core@npm:^1.34.3": + version: 1.34.6 + resolution: "@redocly/openapi-core@npm:1.34.6" dependencies: "@redocly/ajv": "npm:^8.11.2" - "@redocly/config": "npm:^0.20.1" + "@redocly/config": "npm:^0.22.0" colorette: "npm:^1.2.0" - https-proxy-agent: "npm:^7.0.4" + https-proxy-agent: "npm:^7.0.5" js-levenshtein: "npm:^1.1.6" js-yaml: "npm:^4.1.0" minimatch: "npm:^5.0.1" - node-fetch: "npm:^2.6.1" pluralize: "npm:^8.0.0" yaml-ast-parser: "npm:0.0.43" - checksum: 10/f23ee5db3ea31fa8eeb4e13644f4a70a1ce875a4a939a2a9f4c79020055a3d0ac32593e9716b8e9b1ab2478d14efa63a755400a798dc955421acd472ffdabbc8 + checksum: 10/d643b9d98c1de604796867bba0cd9ecd7f24773e121fa7f9381d2bde72bb176895a52b9b02856ede874d13c96e1fe8eade592fc63e70b58f271fa57c1e47d195 languageName: node linkType: hard -"@reduxjs/toolkit@npm:^1.7.1": - version: 1.9.7 - resolution: "@reduxjs/toolkit@npm:1.9.7" +"@reduxjs/toolkit@npm:^2.8.2": + version: 2.11.2 + resolution: "@reduxjs/toolkit@npm:2.11.2" dependencies: - immer: "npm:^9.0.21" - redux: "npm:^4.2.1" - redux-thunk: "npm:^2.4.2" - reselect: "npm:^4.1.8" + "@standard-schema/spec": "npm:^1.0.0" + "@standard-schema/utils": "npm:^0.3.0" + immer: "npm:^11.0.0" + redux: "npm:^5.0.1" + redux-thunk: "npm:^3.1.0" + reselect: "npm:^5.1.0" peerDependencies: - react: ^16.9.0 || ^17.0.0 || ^18 - react-redux: ^7.2.1 || ^8.0.2 + react: ^16.9.0 || ^17.0.0 || ^18 || ^19 + react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 peerDependenciesMeta: react: optional: true react-redux: optional: true - checksum: 10/11c718270bb378e5b26e172eb84cc549d6f263748b6f330b07ee9c366c6474b013fd410e5b2f65a5742e73b7873a3ac14e06cae4bb01480ba03b423c4fd92583 + checksum: 10/2e1098c7fcd10b770344d0b39c58a3b9cb3aec7da0979c02f1ac1ef8f56619ac787c623e480bef0cf6f2c05f1c13fd4d44045c794e2c2d3460f9bf0d2875995c + languageName: node + linkType: hard + +"@rspack/binding-darwin-arm64@npm:1.7.3": + version: 1.7.3 + resolution: "@rspack/binding-darwin-arm64@npm:1.7.3" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rspack/binding-darwin-x64@npm:1.7.3": + version: 1.7.3 + resolution: "@rspack/binding-darwin-x64@npm:1.7.3" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rspack/binding-linux-arm64-gnu@npm:1.7.3": + version: 1.7.3 + resolution: "@rspack/binding-linux-arm64-gnu@npm:1.7.3" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rspack/binding-linux-arm64-musl@npm:1.7.3": + version: 1.7.3 + resolution: "@rspack/binding-linux-arm64-musl@npm:1.7.3" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rspack/binding-linux-x64-gnu@npm:1.7.3": + version: 1.7.3 + resolution: "@rspack/binding-linux-x64-gnu@npm:1.7.3" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rspack/binding-linux-x64-musl@npm:1.7.3": + version: 1.7.3 + resolution: "@rspack/binding-linux-x64-musl@npm:1.7.3" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rspack/binding-wasm32-wasi@npm:1.7.3": + version: 1.7.3 + resolution: "@rspack/binding-wasm32-wasi@npm:1.7.3" + dependencies: + "@napi-rs/wasm-runtime": "npm:1.0.7" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@rspack/binding-win32-arm64-msvc@npm:1.7.3": + version: 1.7.3 + resolution: "@rspack/binding-win32-arm64-msvc@npm:1.7.3" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rspack/binding-win32-ia32-msvc@npm:1.7.3": + version: 1.7.3 + resolution: "@rspack/binding-win32-ia32-msvc@npm:1.7.3" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@rspack/binding-win32-x64-msvc@npm:1.7.3": + version: 1.7.3 + resolution: "@rspack/binding-win32-x64-msvc@npm:1.7.3" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@rspack/binding@npm:1.7.3": + version: 1.7.3 + resolution: "@rspack/binding@npm:1.7.3" + dependencies: + "@rspack/binding-darwin-arm64": "npm:1.7.3" + "@rspack/binding-darwin-x64": "npm:1.7.3" + "@rspack/binding-linux-arm64-gnu": "npm:1.7.3" + "@rspack/binding-linux-arm64-musl": "npm:1.7.3" + "@rspack/binding-linux-x64-gnu": "npm:1.7.3" + "@rspack/binding-linux-x64-musl": "npm:1.7.3" + "@rspack/binding-wasm32-wasi": "npm:1.7.3" + "@rspack/binding-win32-arm64-msvc": "npm:1.7.3" + "@rspack/binding-win32-ia32-msvc": "npm:1.7.3" + "@rspack/binding-win32-x64-msvc": "npm:1.7.3" + dependenciesMeta: + "@rspack/binding-darwin-arm64": + optional: true + "@rspack/binding-darwin-x64": + optional: true + "@rspack/binding-linux-arm64-gnu": + optional: true + "@rspack/binding-linux-arm64-musl": + optional: true + "@rspack/binding-linux-x64-gnu": + optional: true + "@rspack/binding-linux-x64-musl": + optional: true + "@rspack/binding-wasm32-wasi": + optional: true + "@rspack/binding-win32-arm64-msvc": + optional: true + "@rspack/binding-win32-ia32-msvc": + optional: true + "@rspack/binding-win32-x64-msvc": + optional: true + checksum: 10/295c59c22370f71e0f8543fca9819d7d13741e02ef340f1fdda84789d800e324a91d7e54c5119ec0e2a64d4074efff7d2a7f989522bef2d8cb12fb7abffbb5d5 + languageName: node + linkType: hard + +"@rspack/core@npm:^1.5.0": + version: 1.7.3 + resolution: "@rspack/core@npm:1.7.3" + dependencies: + "@module-federation/runtime-tools": "npm:0.22.0" + "@rspack/binding": "npm:1.7.3" + "@rspack/lite-tapable": "npm:1.1.0" + peerDependencies: + "@swc/helpers": ">=0.5.1" + peerDependenciesMeta: + "@swc/helpers": + optional: true + checksum: 10/cd6a4d8dea6eb2fe8fd97b1266ef3ab0bdefd02970f70f0154b20c7ba3b4235cb92e712cf7f9f42bf64786663422ee46e85917b15fe06b563da22bc39ca5f35e + languageName: node + linkType: hard + +"@rspack/lite-tapable@npm:1.1.0": + version: 1.1.0 + resolution: "@rspack/lite-tapable@npm:1.1.0" + checksum: 10/41ff73fe5e1b8dccaad746c9c1bd36dd67649e1ad35776f311b5ba94333a397704e11158579e25a6a7e677c51abe35e66987b1b000faef48d4e4ad2470fea150 languageName: node linkType: hard @@ -2805,6 +3883,20 @@ __metadata: languageName: node linkType: hard +"@standard-schema/spec@npm:^1.0.0": + version: 1.1.0 + resolution: "@standard-schema/spec@npm:1.1.0" + checksum: 10/a209615c9e8b2ea535d7db0a5f6aa0f962fd4ab73ee86a46c100fb78116964af1f55a27c1794d4801e534a196794223daa25ff5135021e03c7828aa3d95e1763 + languageName: node + linkType: hard + +"@standard-schema/utils@npm:^0.3.0": + version: 0.3.0 + resolution: "@standard-schema/utils@npm:0.3.0" + checksum: 10/7084f875d322792f2e0a5904009434c8374b9345b09ba89828b68fd56fa3c2b366d35bf340d9e8c72736ef01793c2f70d350c372ed79845dc3566c58d34b4b51 + languageName: node + linkType: hard + "@svgr/babel-plugin-add-jsx-attribute@npm:8.0.0": version: 8.0.0 resolution: "@svgr/babel-plugin-add-jsx-attribute@npm:8.0.0" @@ -2961,92 +4053,92 @@ __metadata: languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-darwin-arm64@npm:1.11.24" +"@swc/core-darwin-arm64@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-darwin-arm64@npm:1.15.18" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-darwin-x64@npm:1.11.24" +"@swc/core-darwin-x64@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-darwin-x64@npm:1.15.18" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.11.24" +"@swc/core-linux-arm-gnueabihf@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.15.18" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-linux-arm64-gnu@npm:1.11.24" +"@swc/core-linux-arm64-gnu@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-linux-arm64-gnu@npm:1.15.18" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-linux-arm64-musl@npm:1.11.24" +"@swc/core-linux-arm64-musl@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-linux-arm64-musl@npm:1.15.18" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-linux-x64-gnu@npm:1.11.24" +"@swc/core-linux-x64-gnu@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-linux-x64-gnu@npm:1.15.18" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-linux-x64-musl@npm:1.11.24" +"@swc/core-linux-x64-musl@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-linux-x64-musl@npm:1.15.18" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-win32-arm64-msvc@npm:1.11.24" +"@swc/core-win32-arm64-msvc@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-win32-arm64-msvc@npm:1.15.18" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-win32-ia32-msvc@npm:1.11.24" +"@swc/core-win32-ia32-msvc@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-win32-ia32-msvc@npm:1.15.18" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-win32-x64-msvc@npm:1.11.24" +"@swc/core-win32-x64-msvc@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-win32-x64-msvc@npm:1.15.18" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@swc/core@npm:^1.3.46": - version: 1.11.24 - resolution: "@swc/core@npm:1.11.24" +"@swc/core@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core@npm:1.15.18" dependencies: - "@swc/core-darwin-arm64": "npm:1.11.24" - "@swc/core-darwin-x64": "npm:1.11.24" - "@swc/core-linux-arm-gnueabihf": "npm:1.11.24" - "@swc/core-linux-arm64-gnu": "npm:1.11.24" - "@swc/core-linux-arm64-musl": "npm:1.11.24" - "@swc/core-linux-x64-gnu": "npm:1.11.24" - "@swc/core-linux-x64-musl": "npm:1.11.24" - "@swc/core-win32-arm64-msvc": "npm:1.11.24" - "@swc/core-win32-ia32-msvc": "npm:1.11.24" - "@swc/core-win32-x64-msvc": "npm:1.11.24" + "@swc/core-darwin-arm64": "npm:1.15.18" + "@swc/core-darwin-x64": "npm:1.15.18" + "@swc/core-linux-arm-gnueabihf": "npm:1.15.18" + "@swc/core-linux-arm64-gnu": "npm:1.15.18" + "@swc/core-linux-arm64-musl": "npm:1.15.18" + "@swc/core-linux-x64-gnu": "npm:1.15.18" + "@swc/core-linux-x64-musl": "npm:1.15.18" + "@swc/core-win32-arm64-msvc": "npm:1.15.18" + "@swc/core-win32-ia32-msvc": "npm:1.15.18" + "@swc/core-win32-x64-msvc": "npm:1.15.18" "@swc/counter": "npm:^0.1.3" - "@swc/types": "npm:^0.1.21" + "@swc/types": "npm:^0.1.25" peerDependencies: "@swc/helpers": ">=0.5.17" dependenciesMeta: @@ -3073,7 +4165,7 @@ __metadata: peerDependenciesMeta: "@swc/helpers": optional: true - checksum: 10/0b3e883f8a5652a7ab221a777386ccc8a65fc5b53d533bad15b703b22984eb3b449efd907b1872263f1a9990a9a50612f3c6deb619894a43f03cd974ec9bd1b7 + checksum: 10/ef198b9feb6eee034e3a912c37988ece2885fec35419e8245d467adbc1fc47a5c3e61869d1bdbe6fff76cbd9186ef278120cbb9746d5f7446576f4a7f15c2dcd languageName: node linkType: hard @@ -3084,12 +4176,122 @@ __metadata: languageName: node linkType: hard -"@swc/types@npm:^0.1.21": - version: 0.1.21 - resolution: "@swc/types@npm:0.1.21" +"@swc/html-darwin-arm64@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/html-darwin-arm64@npm:1.15.18" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@swc/html-darwin-x64@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/html-darwin-x64@npm:1.15.18" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@swc/html-linux-arm-gnueabihf@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/html-linux-arm-gnueabihf@npm:1.15.18" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@swc/html-linux-arm64-gnu@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/html-linux-arm64-gnu@npm:1.15.18" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@swc/html-linux-arm64-musl@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/html-linux-arm64-musl@npm:1.15.18" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@swc/html-linux-x64-gnu@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/html-linux-x64-gnu@npm:1.15.18" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@swc/html-linux-x64-musl@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/html-linux-x64-musl@npm:1.15.18" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@swc/html-win32-arm64-msvc@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/html-win32-arm64-msvc@npm:1.15.18" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@swc/html-win32-ia32-msvc@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/html-win32-ia32-msvc@npm:1.15.18" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@swc/html-win32-x64-msvc@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/html-win32-x64-msvc@npm:1.15.18" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@swc/html@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/html@npm:1.15.18" dependencies: "@swc/counter": "npm:^0.1.3" - checksum: 10/6554bf5c78519f49099a2ba448d170191a14b1c7a35df848f10ee4d6c03ecd681e5213884905187de1d1d221589ec8b5cb77f477d099dc1627c3ec9d7f2fcdb0 + "@swc/html-darwin-arm64": "npm:1.15.18" + "@swc/html-darwin-x64": "npm:1.15.18" + "@swc/html-linux-arm-gnueabihf": "npm:1.15.18" + "@swc/html-linux-arm64-gnu": "npm:1.15.18" + "@swc/html-linux-arm64-musl": "npm:1.15.18" + "@swc/html-linux-x64-gnu": "npm:1.15.18" + "@swc/html-linux-x64-musl": "npm:1.15.18" + "@swc/html-win32-arm64-msvc": "npm:1.15.18" + "@swc/html-win32-ia32-msvc": "npm:1.15.18" + "@swc/html-win32-x64-msvc": "npm:1.15.18" + dependenciesMeta: + "@swc/html-darwin-arm64": + optional: true + "@swc/html-darwin-x64": + optional: true + "@swc/html-linux-arm-gnueabihf": + optional: true + "@swc/html-linux-arm64-gnu": + optional: true + "@swc/html-linux-arm64-musl": + optional: true + "@swc/html-linux-x64-gnu": + optional: true + "@swc/html-linux-x64-musl": + optional: true + "@swc/html-win32-arm64-msvc": + optional: true + "@swc/html-win32-ia32-msvc": + optional: true + "@swc/html-win32-x64-msvc": + optional: true + checksum: 10/5fdef26fa88693c2913d2ee147332ab366bc6d41726ea2b57db31d3992fbf47aa5b881537f32a077535a64f896a7f7af8f0092343d74256fd8c290431e1a8407 + languageName: node + linkType: hard + +"@swc/types@npm:^0.1.25": + version: 0.1.25 + resolution: "@swc/types@npm:0.1.25" + dependencies: + "@swc/counter": "npm:^0.1.3" + checksum: 10/f6741450224892d12df43e5ca7f3cc0287df644dcd672626eb0cc2a3a8e3e875f4b29eb11336f37c7240cf6e010ba59eb3a79f4fb8bee5cbd168dfc1326ff369 languageName: node linkType: hard @@ -3109,10 +4311,12 @@ __metadata: languageName: node linkType: hard -"@trysound/sax@npm:0.2.0": - version: 0.2.0 - resolution: "@trysound/sax@npm:0.2.0" - checksum: 10/7379713eca480ac0d9b6c7b063e06b00a7eac57092354556c81027066eb65b61ea141a69d0cc2e15d32e05b2834d4c9c2184793a5e36bbf5daf05ee5676af18c +"@tybys/wasm-util@npm:^0.10.1": + version: 0.10.1 + resolution: "@tybys/wasm-util@npm:0.10.1" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10/7fe0d239397aebb002ac4855d30c197c06a05ea8df8511350a3a5b1abeefe26167c60eda8a5508337571161e4c4b53d7c1342296123f9607af8705369de9fa7f languageName: node linkType: hard @@ -3135,22 +4339,22 @@ __metadata: languageName: node linkType: hard -"@types/bonjour@npm:^3.5.9": - version: 3.5.10 - resolution: "@types/bonjour@npm:3.5.10" +"@types/bonjour@npm:^3.5.13": + version: 3.5.13 + resolution: "@types/bonjour@npm:3.5.13" dependencies: "@types/node": "npm:*" - checksum: 10/bfcadb042a41b124c4e3de4925e3be6d35b78f93f27c4535d5ff86980dc0f8bc407ed99b9b54528952dc62834d5a779392f7a12c2947dd19330eb05a6bcae15a + checksum: 10/e827570e097bd7d625a673c9c208af2d1a22fa3885c0a1646533cf24394c839c3e5f60ac1bc60c0ddcc69c0615078c9fb2c01b42596c7c582d895d974f2409ee languageName: node linkType: hard -"@types/connect-history-api-fallback@npm:^1.3.5": - version: 1.3.5 - resolution: "@types/connect-history-api-fallback@npm:1.3.5" +"@types/connect-history-api-fallback@npm:^1.5.4": + version: 1.5.4 + resolution: "@types/connect-history-api-fallback@npm:1.5.4" dependencies: "@types/express-serve-static-core": "npm:*" "@types/node": "npm:*" - checksum: 10/464d06e5ab00f113fa89978633d5eb00d225aeb4ebbadc07f6f3bc337aa7cbfcd74957b2a539d6d47f2e128e956a17819973ec7ae62ade2e16e367a6c38b8d3a + checksum: 10/e1dee43b8570ffac02d2d47a2b4ba80d3ca0dd1840632dafb221da199e59dbe3778d3d7303c9e23c6b401f37c076935a5bc2aeae1c4e5feaefe1c371fe2073fd languageName: node linkType: hard @@ -3201,33 +4405,34 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:*, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.6": - version: 1.0.6 - resolution: "@types/estree@npm:1.0.6" - checksum: 10/9d35d475095199c23e05b431bcdd1f6fec7380612aed068b14b2a08aa70494de8a9026765a5a91b1073f636fb0368f6d8973f518a31391d519e20c59388ed88d +"@types/estree@npm:*, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.8": + version: 1.0.8 + resolution: "@types/estree@npm:1.0.8" + checksum: 10/25a4c16a6752538ffde2826c2cc0c6491d90e69cd6187bef4a006dd2c3c45469f049e643d7e516c515f21484dc3d48fd5c870be158a5beb72f5baf3dc43e4099 languageName: node linkType: hard -"@types/express-serve-static-core@npm:*, @types/express-serve-static-core@npm:^4.17.18": - version: 4.17.31 - resolution: "@types/express-serve-static-core@npm:4.17.31" +"@types/express-serve-static-core@npm:*, @types/express-serve-static-core@npm:^4.17.21, @types/express-serve-static-core@npm:^4.17.33": + version: 4.19.8 + resolution: "@types/express-serve-static-core@npm:4.19.8" dependencies: "@types/node": "npm:*" "@types/qs": "npm:*" "@types/range-parser": "npm:*" - checksum: 10/020210f32d12fa4848d686e3acb98bc35dcec3ade16ae857e99005ac808aa07e1e1a909fdf4f1f2033159afb3eec3622811ac4cb82fa41553c714d9fa35fb77a + "@types/send": "npm:*" + checksum: 10/eb1b832343c0991395c9b10e124dc805921ea7c08efe01222d83912123b8c054119d009e9e55c91af6bdbeeec153c0d35411c9c6d80781bc8c0a43e8b1a84387 languageName: node linkType: hard -"@types/express@npm:*, @types/express@npm:^4.17.13": - version: 4.17.14 - resolution: "@types/express@npm:4.17.14" +"@types/express@npm:*, @types/express@npm:^4.17.25": + version: 4.17.25 + resolution: "@types/express@npm:4.17.25" dependencies: "@types/body-parser": "npm:*" - "@types/express-serve-static-core": "npm:^4.17.18" + "@types/express-serve-static-core": "npm:^4.17.33" "@types/qs": "npm:*" - "@types/serve-static": "npm:*" - checksum: 10/293e53a7572ef93f70c830a0b1d620b5e16670509bb91e800393a67e7f41f170478809d862deed0edf573fc1afc3a6f7fc99ed176a28c0d8dd3f07c433f88672 + "@types/serve-static": "npm:^1" + checksum: 10/c309fdb79fb8569b5d8d8f11268d0160b271f8b38f0a82c20a0733e526baf033eb7a921cd51d54fe4333c616de9e31caf7d4f3ef73baaf212d61f23f460b0369 languageName: node linkType: hard @@ -3238,15 +4443,6 @@ __metadata: languageName: node linkType: hard -"@types/hast@npm:^2.0.0": - version: 2.3.10 - resolution: "@types/hast@npm:2.3.10" - dependencies: - "@types/unist": "npm:^2" - checksum: 10/41531b7fbf590b02452996fc63272479c20a07269e370bd6514982cbcd1819b4b84d3ea620f2410d1b9541a23d08ce2eeb0a592145d05e00e249c3d56700d460 - languageName: node - linkType: hard - "@types/hast@npm:^3.0.0": version: 3.0.4 resolution: "@types/hast@npm:3.0.4" @@ -3263,16 +4459,6 @@ __metadata: languageName: node linkType: hard -"@types/hoist-non-react-statics@npm:^3.3.0": - version: 3.3.6 - resolution: "@types/hoist-non-react-statics@npm:3.3.6" - dependencies: - "@types/react": "npm:*" - hoist-non-react-statics: "npm:^3.3.0" - checksum: 10/f03e43bd081876c49584ffa0eb690d69991f258203efca44dcc30efdda49a50653ff06402917d1edc9cb7e2adebbe9e2d1d0e739bc99c1b5372103b1cc534e47 - languageName: node - linkType: hard - "@types/html-minifier-terser@npm:^6.0.0": version: 6.1.0 resolution: "@types/html-minifier-terser@npm:6.1.0" @@ -3287,6 +4473,13 @@ __metadata: languageName: node linkType: hard +"@types/http-errors@npm:*": + version: 2.0.5 + resolution: "@types/http-errors@npm:2.0.5" + checksum: 10/a88da669366bc483e8f3b3eb3d34ada5f8d13eeeef851b1204d77e2ba6fc42aba4566d877cca5c095204a3f4349b87fe397e3e21288837bdd945dd514120755b + languageName: node + linkType: hard + "@types/http-proxy@npm:^1.17.8": version: 1.17.9 resolution: "@types/http-proxy@npm:1.17.9" @@ -3321,29 +4514,13 @@ __metadata: languageName: node linkType: hard -"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.15, @types/json-schema@npm:^7.0.4, @types/json-schema@npm:^7.0.5, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": +"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.15, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": version: 7.0.15 resolution: "@types/json-schema@npm:7.0.15" checksum: 10/1a3c3e06236e4c4aab89499c428d585527ce50c24fe8259e8b3926d3df4cfbbbcf306cfc73ddfb66cbafc973116efd15967020b0f738f63e09e64c7d260519e7 languageName: node linkType: hard -"@types/luxon@npm:^3.0.0": - version: 3.4.2 - resolution: "@types/luxon@npm:3.4.2" - checksum: 10/fd89566e3026559f2bc4ddcc1e70a2c16161905ed50be9473ec0cfbbbe919165041408c4f6e06c4bcf095445535052e2c099087c76b1b38e368127e618fc968d - languageName: node - linkType: hard - -"@types/mdast@npm:^3.0.0": - version: 3.0.15 - resolution: "@types/mdast@npm:3.0.15" - dependencies: - "@types/unist": "npm:^2" - checksum: 10/050a5c1383928b2688dd145382a22535e2af87dc3fd592c843abb7851bcc99893a1ee0f63be19fc4e89779387ec26a57486cfb425b016c0b2a98a17fc4a1e8b3 - languageName: node - linkType: hard - "@types/mdast@npm:^4.0.0, @types/mdast@npm:^4.0.2": version: 4.0.3 resolution: "@types/mdast@npm:4.0.3" @@ -3360,10 +4537,10 @@ __metadata: languageName: node linkType: hard -"@types/mime@npm:*": - version: 3.0.1 - resolution: "@types/mime@npm:3.0.1" - checksum: 10/4040fac73fd0cea2460e29b348c1a6173da747f3a87da0dbce80dd7a9355a3d0e51d6d9a401654f3e5550620e3718b5a899b2ec1debf18424e298a2c605346e7 +"@types/mime@npm:^1": + version: 1.3.5 + resolution: "@types/mime@npm:1.3.5" + checksum: 10/e29a5f9c4776f5229d84e525b7cd7dd960b51c30a0fb9a028c0821790b82fca9f672dab56561e2acd9e8eed51d431bde52eafdfef30f643586c4162f1aecfc78 languageName: node linkType: hard @@ -3388,20 +4565,6 @@ __metadata: languageName: node linkType: hard -"@types/parse-json@npm:^4.0.0": - version: 4.0.0 - resolution: "@types/parse-json@npm:4.0.0" - checksum: 10/4df9de98150d2978afc2161482a3a8e6617883effba3223324f079de97ba7eabd7d84b90ced11c3f82b0c08d4a8383f678c9f73e9c41258f769b3fa234a2bb4f - languageName: node - linkType: hard - -"@types/parse5@npm:^6.0.0": - version: 6.0.3 - resolution: "@types/parse5@npm:6.0.3" - checksum: 10/834d40c9b1a8a99a9574b0b3f6629cf48adcff2eda01a35d701f1de5dcf46ce24223684647890aba9f985d6c801b233f878168683de0ae425940403c383fba8f - languageName: node - linkType: hard - "@types/prismjs@npm:^1.26.0": version: 1.26.1 resolution: "@types/prismjs@npm:1.26.1" @@ -3409,7 +4572,7 @@ __metadata: languageName: node linkType: hard -"@types/prop-types@npm:*, @types/prop-types@npm:^15.0.0": +"@types/prop-types@npm:*": version: 15.7.14 resolution: "@types/prop-types@npm:15.7.14" checksum: 10/d0c5407b9ccc3dd5fae0ccf9b1007e7622ba5e6f1c18399b4f24dff33619d469da4b9fa918a374f19dc0d9fe6a013362aab0b844b606cfc10676efba3f5f736d @@ -3430,18 +4593,6 @@ __metadata: languageName: node linkType: hard -"@types/react-redux@npm:^7.1.20": - version: 7.1.34 - resolution: "@types/react-redux@npm:7.1.34" - dependencies: - "@types/hoist-non-react-statics": "npm:^3.3.0" - "@types/react": "npm:*" - hoist-non-react-statics: "npm:^3.3.0" - redux: "npm:^4.0.0" - checksum: 10/febcd1db0c83c5002c6bee0fdda9e70da0653454ffbb72d6c37cbf2f5c005e06fb5271cff344d7164c385c944526565282de9a95ff379e040476b71d27fc2512 - languageName: node - linkType: hard - "@types/react-router-config@npm:*, @types/react-router-config@npm:^5.0.7": version: 5.0.8 resolution: "@types/react-router-config@npm:5.0.8" @@ -3485,10 +4636,10 @@ __metadata: languageName: node linkType: hard -"@types/retry@npm:0.12.0": - version: 0.12.0 - resolution: "@types/retry@npm:0.12.0" - checksum: 10/bbd0b88f4b3eba7b7acfc55ed09c65ef6f2e1bcb4ec9b4dca82c66566934351534317d294a770a7cc6c0468d5573c5350abab6e37c65f8ef254443e1b028e44d +"@types/retry@npm:0.12.2": + version: 0.12.2 + resolution: "@types/retry@npm:0.12.2" + checksum: 10/e5675035717b39ce4f42f339657cae9637cf0c0051cf54314a6a2c44d38d91f6544be9ddc0280587789b6afd056be5d99dbe3e9f4df68c286c36321579b1bf4a languageName: node linkType: hard @@ -3508,31 +4659,51 @@ __metadata: languageName: node linkType: hard -"@types/serve-index@npm:^1.9.1": - version: 1.9.1 - resolution: "@types/serve-index@npm:1.9.1" +"@types/send@npm:*": + version: 1.2.1 + resolution: "@types/send@npm:1.2.1" + dependencies: + "@types/node": "npm:*" + checksum: 10/81ef5790037ba1d2d458392e4241501f0f8b4838cc8797e169e179e099410e12069ec68e8dbd39211cb097c4a9b1ff1682dbcea897ab4ce21dad93438b862d27 + languageName: node + linkType: hard + +"@types/send@npm:<1": + version: 0.17.6 + resolution: "@types/send@npm:0.17.6" + dependencies: + "@types/mime": "npm:^1" + "@types/node": "npm:*" + checksum: 10/4948ab32ab84a81a0073f8243dd48ee766bc80608d5391060360afd1249f83c08a7476f142669ac0b0b8831c89d909a88bcb392d1b39ee48b276a91b50f3d8d1 + languageName: node + linkType: hard + +"@types/serve-index@npm:^1.9.4": + version: 1.9.4 + resolution: "@types/serve-index@npm:1.9.4" dependencies: "@types/express": "npm:*" - checksum: 10/026f3995fb500f6df7c3fe5009e53bad6d739e20b84089f58ebfafb2f404bbbb6162bbe33f72d2f2af32d5b8d3799c8e179793f90d9ed5871fb8591190bb6056 + checksum: 10/72727c88d54da5b13275ebfb75dcdc4aa12417bbe9da1939e017c4c5f0c906fae843aa4e0fbfe360e7ee9df2f3d388c21abfc488f77ce58693fb57809f8ded92 languageName: node linkType: hard -"@types/serve-static@npm:*, @types/serve-static@npm:^1.13.10": - version: 1.15.0 - resolution: "@types/serve-static@npm:1.15.0" +"@types/serve-static@npm:^1, @types/serve-static@npm:^1.15.5": + version: 1.15.10 + resolution: "@types/serve-static@npm:1.15.10" dependencies: - "@types/mime": "npm:*" + "@types/http-errors": "npm:*" "@types/node": "npm:*" - checksum: 10/5f2f36a8f5970e350c19a2bfac385f0a225e16b5cc9a2441e8b01df32f2f5cb0f2950da53f67579f77c52236b3580909c3fe83d50d64fb56b216ea0a67c1d12b + "@types/send": "npm:<1" + checksum: 10/d9be72487540b9598e7d77260d533f241eb2e5db5181bb885ef2d6bc4592dad1c9e8c0e27f465d59478b2faf90edd2d535e834f20fbd9dd3c0928d43dc486404 languageName: node linkType: hard -"@types/sockjs@npm:^0.3.33": - version: 0.3.33 - resolution: "@types/sockjs@npm:0.3.33" +"@types/sockjs@npm:^0.3.36": + version: 0.3.36 + resolution: "@types/sockjs@npm:0.3.36" dependencies: "@types/node": "npm:*" - checksum: 10/b9bbb2b5c5ead2fb884bb019f61a014e37410bddd295de28184e1b2e71ee6b04120c5ba7b9954617f0bdf962c13d06249ce65004490889c747c80d3f628ea842 + checksum: 10/b4b5381122465d80ea8b158537c00bc82317222d3fb31fd7229ff25b31fa89134abfbab969118da55622236bf3d8fee75759f3959908b5688991f492008f29bc languageName: node linkType: hard @@ -3543,13 +4714,20 @@ __metadata: languageName: node linkType: hard -"@types/unist@npm:^2, @types/unist@npm:^2.0.0": +"@types/unist@npm:^2.0.0": version: 2.0.11 resolution: "@types/unist@npm:2.0.11" checksum: 10/6d436e832bc35c6dde9f056ac515ebf2b3384a1d7f63679d12358766f9b313368077402e9c1126a14d827f10370a5485e628bf61aa91117cf4fc882423191a4e languageName: node linkType: hard +"@types/use-sync-external-store@npm:^0.0.6": + version: 0.0.6 + resolution: "@types/use-sync-external-store@npm:0.0.6" + checksum: 10/a95ce330668501ad9b1c5b7f2b14872ad201e552a0e567787b8f1588b22c7040c7c3d80f142cbb9f92d13c4ea41c46af57a20f2af4edf27f224d352abcfe4049 + languageName: node + linkType: hard + "@types/webpack-env@npm:^1.18.0": version: 1.18.8 resolution: "@types/webpack-env@npm:1.18.8" @@ -3557,12 +4735,12 @@ __metadata: languageName: node linkType: hard -"@types/ws@npm:^8.5.5": - version: 8.5.6 - resolution: "@types/ws@npm:8.5.6" +"@types/ws@npm:^8.5.10": + version: 8.18.1 + resolution: "@types/ws@npm:8.18.1" dependencies: "@types/node": "npm:*" - checksum: 10/1c3ce7fe65569dc85314981622bdbccd8bab671cff74367f9bfba8390ddd0f241f29cdcf67b14163a2c832a0a08bfc0e8f60912b9c143648e25a5a05e016b892 + checksum: 10/1ce05e3174dcacf28dae0e9b854ef1c9a12da44c7ed73617ab6897c5cbe4fccbb155a20be5508ae9a7dde2f83bd80f5cf3baa386b934fc4b40889ec963e94f3a languageName: node linkType: hard @@ -3761,16 +4939,7 @@ __metadata: languageName: node linkType: hard -"abort-controller@npm:^3.0.0": - version: 3.0.0 - resolution: "abort-controller@npm:3.0.0" - dependencies: - event-target-shim: "npm:^5.0.0" - checksum: 10/ed84af329f1828327798229578b4fe03a4dd2596ba304083ebd2252666bdc1d7647d66d0b18704477e1f8aa315f055944aa6e859afebd341f12d0a53c37b4b40 - languageName: node - linkType: hard - -"accepts@npm:~1.3.4, accepts@npm:~1.3.5, accepts@npm:~1.3.8": +"accepts@npm:~1.3.4, accepts@npm:~1.3.8": version: 1.3.8 resolution: "accepts@npm:1.3.8" dependencies: @@ -3780,6 +4949,15 @@ __metadata: languageName: node linkType: hard +"acorn-import-phases@npm:^1.0.3": + version: 1.0.4 + resolution: "acorn-import-phases@npm:1.0.4" + peerDependencies: + acorn: ^8.14.0 + checksum: 10/471050ac7d9b61909c837b426de9eeef2958997f6277ad7dea88d5894fd9b3245d8ed4a225c2ca44f814dbb20688009db7a80e525e8196fc9e98c5285b66161d + languageName: node + linkType: hard + "acorn-jsx@npm:^5.0.0": version: 5.3.2 resolution: "acorn-jsx@npm:5.3.2" @@ -3796,16 +4974,16 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.0.0, acorn@npm:^8.0.4, acorn@npm:^8.14.0, acorn@npm:^8.8.2": - version: 8.14.0 - resolution: "acorn@npm:8.14.0" +"acorn@npm:^8.0.0, acorn@npm:^8.0.4, acorn@npm:^8.15.0": + version: 8.15.0 + resolution: "acorn@npm:8.15.0" bin: acorn: bin/acorn - checksum: 10/6df29c35556782ca9e632db461a7f97947772c6c1d5438a81f0c873a3da3a792487e83e404d1c6c25f70513e91aa18745f6eafb1fcc3a43ecd1920b21dd173d2 + checksum: 10/77f2de5051a631cf1729c090e5759148459cdb76b5f5c70f890503d629cf5052357b0ce783c0f976dd8a93c5150f59f6d18df1def3f502396a20f81282482fa4 languageName: node linkType: hard -"address@npm:^1.0.1, address@npm:^1.1.2": +"address@npm:^1.0.1": version: 1.2.1 resolution: "address@npm:1.2.1" checksum: 10/270cde3ed9836fd14b51c425bdc0f445933f7a880e3871412addfff4f871df378a4a0945b4496dd270a0dfa6734bb5fa5647547afe2ce84cbd3d435e2ca5f325 @@ -3875,7 +5053,7 @@ __metadata: languageName: node linkType: hard -"ajv-keywords@npm:^3.4.1, ajv-keywords@npm:^3.5.2": +"ajv-keywords@npm:^3.5.2": version: 3.5.2 resolution: "ajv-keywords@npm:3.5.2" peerDependencies: @@ -3895,31 +5073,19 @@ __metadata: languageName: node linkType: hard -"ajv@npm:8.11.0": - version: 8.11.0 - resolution: "ajv@npm:8.11.0" - dependencies: - fast-deep-equal: "npm:^3.1.1" - json-schema-traverse: "npm:^1.0.0" - require-from-string: "npm:^2.0.2" - uri-js: "npm:^4.2.2" - checksum: 10/aa0dfd6cebdedde8e77747e84e7b7c55921930974b8547f54b4156164ff70445819398face32dafda4bd4c61bbc7513d308d4c2bf769f8ea6cb9c8449f9faf54 - languageName: node - linkType: hard - -"ajv@npm:^6.12.2, ajv@npm:^6.12.5": - version: 6.12.6 - resolution: "ajv@npm:6.12.6" +"ajv@npm:^6.12.5": + version: 6.14.0 + resolution: "ajv@npm:6.14.0" dependencies: fast-deep-equal: "npm:^3.1.1" fast-json-stable-stringify: "npm:^2.0.0" json-schema-traverse: "npm:^0.4.1" uri-js: "npm:^4.2.2" - checksum: 10/48d6ad21138d12eb4d16d878d630079a2bda25a04e745c07846a4ad768319533031e28872a9b3c5790fa1ec41aabdf2abed30a56e5a03ebc2cf92184b8ee306c + checksum: 10/c71f14dd2b6f2535d043f74019c8169f7aeb1106bafbb741af96f34fdbf932255c919ddd46344043d03b62ea0ccb319f83667ec5eedf612393f29054fe5ce4a5 languageName: node linkType: hard -"ajv@npm:^8.0.0, ajv@npm:^8.9.0": +"ajv@npm:^8.0.0, ajv@npm:^8.11.0, ajv@npm:^8.9.0": version: 8.17.1 resolution: "ajv@npm:8.17.1" dependencies: @@ -3931,36 +5097,36 @@ __metadata: languageName: node linkType: hard -"algoliasearch-helper@npm:^3.13.3": - version: 3.14.2 - resolution: "algoliasearch-helper@npm:3.14.2" +"algoliasearch-helper@npm:^3.26.0": + version: 3.27.0 + resolution: "algoliasearch-helper@npm:3.27.0" dependencies: "@algolia/events": "npm:^4.0.1" peerDependencies: algoliasearch: ">= 3.1 < 6" - checksum: 10/f0b61bd5fa6ecae250c7ee4e4e8804721dad68b2e5781dfe36fdeec5f6e967470b3116f63ab67312b1499ccd40ab1b8c0d05d36dc9ca72aa2d9cd57c0874adb3 + checksum: 10/46ee2f2a2349898c29d54e4228534789103967ce2681aa0f6e05a968d6ca6d7524855a0b09909c7d313c9ad24c455b9243117015d32a79a900d0b6320229bee6 languageName: node linkType: hard -"algoliasearch@npm:^4.18.0, algoliasearch@npm:^4.19.1": - version: 4.20.0 - resolution: "algoliasearch@npm:4.20.0" +"algoliasearch@npm:^5.37.0": + version: 5.47.0 + resolution: "algoliasearch@npm:5.47.0" dependencies: - "@algolia/cache-browser-local-storage": "npm:4.20.0" - "@algolia/cache-common": "npm:4.20.0" - "@algolia/cache-in-memory": "npm:4.20.0" - "@algolia/client-account": "npm:4.20.0" - "@algolia/client-analytics": "npm:4.20.0" - "@algolia/client-common": "npm:4.20.0" - "@algolia/client-personalization": "npm:4.20.0" - "@algolia/client-search": "npm:4.20.0" - "@algolia/logger-common": "npm:4.20.0" - "@algolia/logger-console": "npm:4.20.0" - "@algolia/requester-browser-xhr": "npm:4.20.0" - "@algolia/requester-common": "npm:4.20.0" - "@algolia/requester-node-http": "npm:4.20.0" - "@algolia/transporter": "npm:4.20.0" - checksum: 10/15129c8d1cb9710e958e3b687835361ed6b79ed45b7b4f4283b2339c3044e101b19821a9e2a83ef38c240eb663f74b0179434ed11dee02076693bc8df2b2c1ff + "@algolia/abtesting": "npm:1.13.0" + "@algolia/client-abtesting": "npm:5.47.0" + "@algolia/client-analytics": "npm:5.47.0" + "@algolia/client-common": "npm:5.47.0" + "@algolia/client-insights": "npm:5.47.0" + "@algolia/client-personalization": "npm:5.47.0" + "@algolia/client-query-suggestions": "npm:5.47.0" + "@algolia/client-search": "npm:5.47.0" + "@algolia/ingestion": "npm:1.47.0" + "@algolia/monitoring": "npm:1.47.0" + "@algolia/recommend": "npm:5.47.0" + "@algolia/requester-browser-xhr": "npm:5.47.0" + "@algolia/requester-fetch": "npm:5.47.0" + "@algolia/requester-node-http": "npm:5.47.0" + checksum: 10/afd7b3aec5c4f28d88d19ef409c163f16954a4f1c3fff545f78593a5864ff3d6b3bd16ffed16e074b2162d58f77a13270fed00fdb86d68d26f291ae67f6ade58 languageName: node linkType: hard @@ -3982,6 +5148,15 @@ __metadata: languageName: node linkType: hard +"ansi-escapes@npm:^4.3.2": + version: 4.3.2 + resolution: "ansi-escapes@npm:4.3.2" + dependencies: + type-fest: "npm:^0.21.3" + checksum: 10/8661034456193ffeda0c15c8c564a9636b0c04094b7f78bd01517929c17c504090a60f7a75f949f5af91289c264d3e1001d91492c1bd58efc8e100500ce04de2 + languageName: node + linkType: hard + "ansi-html-community@npm:^0.0.8": version: 0.0.8 resolution: "ansi-html-community@npm:0.0.8" @@ -4005,15 +5180,6 @@ __metadata: languageName: node linkType: hard -"ansi-styles@npm:^3.2.1": - version: 3.2.1 - resolution: "ansi-styles@npm:3.2.1" - dependencies: - color-convert: "npm:^1.9.0" - checksum: 10/d85ade01c10e5dd77b6c89f34ed7531da5830d2cb5882c645f330079975b716438cd7ebb81d0d6e6b4f9c577f19ae41ab55f07f19786b02f9dfd9e0377395665 - languageName: node - linkType: hard - "ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": version: 4.3.0 resolution: "ansi-styles@npm:4.3.0" @@ -4094,13 +5260,6 @@ __metadata: languageName: node linkType: hard -"array-flatten@npm:^2.1.2": - version: 2.1.2 - resolution: "array-flatten@npm:2.1.2" - checksum: 10/e8988aac1fbfcdaae343d08c9a06a6fddd2c6141721eeeea45c3cf523bf4431d29a46602929455ed548c7a3e0769928cdc630405427297e7081bd118fdec9262 - languageName: node - linkType: hard - "array-union@npm:^2.1.0": version: 2.1.0 resolution: "array-union@npm:2.1.0" @@ -4108,27 +5267,14 @@ __metadata: languageName: node linkType: hard -"asn1.js@npm:^4.10.1": - version: 4.10.1 - resolution: "asn1.js@npm:4.10.1" +"asn1js@npm:^3.0.6": + version: 3.0.7 + resolution: "asn1js@npm:3.0.7" dependencies: - bn.js: "npm:^4.0.0" - inherits: "npm:^2.0.1" - minimalistic-assert: "npm:^1.0.0" - checksum: 10/5a02104b9ba167917c786a3fdac9840a057d29e6b609250e6af924d0529ead1a32417da13eec809cadea8f991eb67782196f3df427c5b4f30eaf22044fc64fda - languageName: node - linkType: hard - -"assert@npm:^2.1.0": - version: 2.1.0 - resolution: "assert@npm:2.1.0" - dependencies: - call-bind: "npm:^1.0.2" - is-nan: "npm:^1.3.2" - object-is: "npm:^1.1.5" - object.assign: "npm:^4.1.4" - util: "npm:^0.12.5" - checksum: 10/6b9d813c8eef1c0ac13feac5553972e4bd180ae16000d4eb5c0ded2489188737c75a5aacefc97a985008b37502f62fe1bad34da1a7481a54bbfabec3964c8aa7 + pvtsutils: "npm:^1.3.6" + pvutils: "npm:^1.1.3" + tslib: "npm:^2.8.1" + checksum: 10/1ae92cc6825ff002aed5b2a800e89db1fccd0775b42278431332fe3ee6839711e80e1ca504c72a35a03d94d417c3b315fb03bc6a6f4518c309b1dcb5385a1a93 languageName: node linkType: hard @@ -4148,57 +5294,40 @@ __metadata: languageName: node linkType: hard -"async@npm:3.2.4": - version: 3.2.4 - resolution: "async@npm:3.2.4" - checksum: 10/bebb5dc2258c45b83fa1d3be179ae0eb468e1646a62d443c8d60a45e84041b28fccebe1e2d1f234bfc3dcad44e73dcdbf4ba63d98327c9f6556e3dbd47c2ae8b +"async@npm:3.2.6": + version: 3.2.6 + resolution: "async@npm:3.2.6" + checksum: 10/cb6e0561a3c01c4b56a799cc8bab6ea5fef45f069ab32500b6e19508db270ef2dffa55e5aed5865c5526e9907b1f8be61b27530823b411ffafb5e1538c86c368 languageName: node linkType: hard -"at-least-node@npm:^1.0.0": - version: 1.0.0 - resolution: "at-least-node@npm:1.0.0" - checksum: 10/463e2f8e43384f1afb54bc68485c436d7622acec08b6fad269b421cb1d29cebb5af751426793d0961ed243146fe4dc983402f6d5a51b720b277818dbf6f2e49e - languageName: node - linkType: hard - -"autoprefixer@npm:^10.4.14, autoprefixer@npm:^10.4.19": - version: 10.4.20 - resolution: "autoprefixer@npm:10.4.20" +"autoprefixer@npm:^10.4.19, autoprefixer@npm:^10.4.23": + version: 10.4.23 + resolution: "autoprefixer@npm:10.4.23" dependencies: - browserslist: "npm:^4.23.3" - caniuse-lite: "npm:^1.0.30001646" - fraction.js: "npm:^4.3.7" - normalize-range: "npm:^0.1.2" - picocolors: "npm:^1.0.1" + browserslist: "npm:^4.28.1" + caniuse-lite: "npm:^1.0.30001760" + fraction.js: "npm:^5.3.4" + picocolors: "npm:^1.1.1" postcss-value-parser: "npm:^4.2.0" peerDependencies: postcss: ^8.1.0 bin: autoprefixer: bin/autoprefixer - checksum: 10/d3c4b562fc4af2393623a0207cc336f5b9f94c4264ae1c316376904c279702ce2b12dc3f27205f491195d1e29bb52ffc269970ceb0f271f035fadee128a273f7 + checksum: 10/153033db6f137712383ddf3f1f267b1d5900695d135f7794cbfaaec832fdbf4e34efd85418dec6f78c418062afdf3f0a07313e32a83764c119bfb5b2f01648b6 languageName: node linkType: hard -"available-typed-arrays@npm:^1.0.7": - version: 1.0.7 - resolution: "available-typed-arrays@npm:1.0.7" - dependencies: - possible-typed-array-names: "npm:^1.0.0" - checksum: 10/6c9da3a66caddd83c875010a1ca8ef11eac02ba15fb592dc9418b2b5e7b77b645fa7729380a92d9835c2f05f2ca1b6251f39b993e0feb3f1517c74fa1af02cab - languageName: node - linkType: hard - -"babel-loader@npm:^9.1.3": - version: 9.1.3 - resolution: "babel-loader@npm:9.1.3" +"babel-loader@npm:^9.2.1": + version: 9.2.1 + resolution: "babel-loader@npm:9.2.1" dependencies: find-cache-dir: "npm:^4.0.0" schema-utils: "npm:^4.0.0" peerDependencies: "@babel/core": ^7.12.0 webpack: ">=5" - checksum: 10/7086e678273b5d1261141dca84ed784caab9f7921c8c24d7278c8ee3088235a9a9fd85caac9f0fa687336cb3c27248ca22dbf431469769b1b995d55aec606992 + checksum: 10/f1f24ae3c22d488630629240b0eba9c935545f82ff843c214e8f8df66e266492b7a3d4cb34ef9c9721fb174ca222e900799951c3fd82199473bc6bac52ec03a3 languageName: node linkType: hard @@ -4211,62 +5340,39 @@ __metadata: languageName: node linkType: hard -"babel-plugin-polyfill-corejs2@npm:^0.4.10, babel-plugin-polyfill-corejs2@npm:^0.4.5": - version: 0.4.11 - resolution: "babel-plugin-polyfill-corejs2@npm:0.4.11" +"babel-plugin-polyfill-corejs2@npm:^0.4.14": + version: 0.4.15 + resolution: "babel-plugin-polyfill-corejs2@npm:0.4.15" dependencies: - "@babel/compat-data": "npm:^7.22.6" - "@babel/helper-define-polyfill-provider": "npm:^0.6.2" + "@babel/compat-data": "npm:^7.28.6" + "@babel/helper-define-polyfill-provider": "npm:^0.6.6" semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10/9c79908bed61b9f52190f254e22d3dca6ce25769738642579ba8d23832f3f9414567a90d8367a31831fa45d9b9607ac43d8d07ed31167d8ca8cda22871f4c7a1 + checksum: 10/e5f8a4e716400b2b5c51f7b3c0eec58da92f1d8cc1c6fe2e32555c98bc594be1de7fa1da373f8e42ab098c33867c4cc2931ce648c92aab7a4f4685417707c438 languageName: node linkType: hard -"babel-plugin-polyfill-corejs3@npm:^0.10.6": - version: 0.10.6 - resolution: "babel-plugin-polyfill-corejs3@npm:0.10.6" +"babel-plugin-polyfill-corejs3@npm:^0.13.0": + version: 0.13.0 + resolution: "babel-plugin-polyfill-corejs3@npm:0.13.0" dependencies: - "@babel/helper-define-polyfill-provider": "npm:^0.6.2" - core-js-compat: "npm:^3.38.0" + "@babel/helper-define-polyfill-provider": "npm:^0.6.5" + core-js-compat: "npm:^3.43.0" peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10/360ac9054a57a18c540059dc627ad5d84d15f79790cb3d84d19a02eec7188c67d08a07db789c3822d6f5df22d918e296d1f27c4055fec2e287d328f09ea8a78a + checksum: 10/aa36f9a09521404dd0569a4cbd5f88aa4b9abff59508749abde5d09d66c746012fb94ed1e6e2c8be3710939a2a4c6293ee3be889125d7611c93e5897d9e5babd languageName: node linkType: hard -"babel-plugin-polyfill-corejs3@npm:^0.8.3": - version: 0.8.4 - resolution: "babel-plugin-polyfill-corejs3@npm:0.8.4" +"babel-plugin-polyfill-regenerator@npm:^0.6.5": + version: 0.6.6 + resolution: "babel-plugin-polyfill-regenerator@npm:0.6.6" dependencies: - "@babel/helper-define-polyfill-provider": "npm:^0.4.2" - core-js-compat: "npm:^3.32.2" + "@babel/helper-define-polyfill-provider": "npm:^0.6.6" peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10/d1c642f0a9f5935c31e9adb4f3777d826b8c84bdd043bd8074ec7cd6f108aa4290f261e9e9b846273a677795b68a53f8779a3218000b6fe5ba62d05f81ee4da2 - languageName: node - linkType: hard - -"babel-plugin-polyfill-regenerator@npm:^0.5.2": - version: 0.5.2 - resolution: "babel-plugin-polyfill-regenerator@npm:0.5.2" - dependencies: - "@babel/helper-define-polyfill-provider": "npm:^0.4.2" - peerDependencies: - "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10/d962200f604016a9a09bc9b4aaf60a3db7af876bb65bcefaeac04d44ac9d9ec4037cf24ce117760cc141d7046b6394c7eb0320ba9665cb4a2ee64df2be187c93 - languageName: node - linkType: hard - -"babel-plugin-polyfill-regenerator@npm:^0.6.1": - version: 0.6.2 - resolution: "babel-plugin-polyfill-regenerator@npm:0.6.2" - dependencies: - "@babel/helper-define-polyfill-provider": "npm:^0.6.2" - peerDependencies: - "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10/150233571072b6b3dfe946242da39cba8587b7f908d1c006f7545fc88b0e3c3018d445739beb61e7a75835f0c2751dbe884a94ff9b245ec42369d9267e0e1b3f + checksum: 10/8de7ea32856e75784601cacf8f4e3cbf04ce1fd05d56614b08b7bbe0674d1e59e37ccaa1c7ed16e3b181a63abe5bd43a1ab0e28b8c95618a9ebf0be5e24d6b25 languageName: node linkType: hard @@ -4274,22 +5380,22 @@ __metadata: version: 0.0.0-use.local resolution: "backstage-microsite@workspace:." dependencies: - "@docusaurus/core": "npm:^3.1.1" + "@docusaurus/core": "npm:^3.9.0" + "@docusaurus/faster": "npm:^3.9.0" "@docusaurus/module-type-aliases": "npm:^3.1.1" - "@docusaurus/plugin-client-redirects": "npm:^3.1.1" - "@docusaurus/preset-classic": "npm:^3.1.1" + "@docusaurus/plugin-client-redirects": "npm:^3.9.0" + "@docusaurus/preset-classic": "npm:^3.9.0" "@docusaurus/tsconfig": "npm:^3.1.1" - "@docusaurus/types": "npm:^3.1.1" + "@docusaurus/types": "npm:^3.9.0" "@mdx-js/react": "npm:^3.0.0" - "@swc/core": "npm:^1.3.46" - "@types/luxon": "npm:^3.0.0" + "@swc/core": "npm:^1.15.6" "@types/webpack-env": "npm:^1.18.0" clsx: "npm:^2.0.0" docusaurus-plugin-openapi-docs: "npm:^4.3.0" docusaurus-plugin-sass: "npm:^0.2.3" docusaurus-pushfeedback: "npm:^1.0.0" docusaurus-theme-openapi-docs: "npm:^4.3.0" - js-yaml: "npm:^4.1.0" + js-yaml: "npm:^4.1.1" luxon: "npm:^3.0.0" prettier: "npm:^2.6.2" prism-react-renderer: "npm:^2.1.0" @@ -4297,8 +5403,8 @@ __metadata: react-dom: "npm:^18.0.2" sass: "npm:^1.57.1" swc-loader: "npm:^0.2.3" - typescript: "npm:~5.2.0" - yaml-loader: "npm:^0.8.0" + typescript: "npm:~5.7.0" + yaml-loader: "npm:^0.9.0" languageName: unknown linkType: soft @@ -4323,6 +5429,15 @@ __metadata: languageName: node linkType: hard +"baseline-browser-mapping@npm:^2.9.0": + version: 2.9.18 + resolution: "baseline-browser-mapping@npm:2.9.18" + bin: + baseline-browser-mapping: dist/cli.js + checksum: 10/7607ae6cad62e013787f13f52856899ebc81d4fc47c311ed513619ade9c83a317b8c0e17092e6de96a0d26f2c6bbf10e1c37db84d419cfb2e4a5485fa5af0d93 + languageName: node + linkType: hard + "batch@npm:0.6.1": version: 0.6.1 resolution: "batch@npm:0.6.1" @@ -4344,49 +5459,33 @@ __metadata: languageName: node linkType: hard -"bn.js@npm:^4.0.0, bn.js@npm:^4.1.0, bn.js@npm:^4.11.9": - version: 4.12.1 - resolution: "bn.js@npm:4.12.1" - checksum: 10/07f22df8880b423c4890648e95791319898b96712b6ebc5d6b1082b34074f09dedb8601e717d67f905ce29bb1a5313f9a2b1a2015a679e42c9eed94392c0d379 - languageName: node - linkType: hard - -"bn.js@npm:^5.2.1": - version: 5.2.1 - resolution: "bn.js@npm:5.2.1" - checksum: 10/7a7e8764d7a6e9708b8b9841b2b3d6019cc154d2fc23716d0efecfe1e16921b7533c6f7361fb05471eab47986c4aa310c270f88e3507172104632ac8df2cfd84 - languageName: node - linkType: hard - -"body-parser@npm:1.20.3": - version: 1.20.3 - resolution: "body-parser@npm:1.20.3" +"body-parser@npm:~1.20.3": + version: 1.20.4 + resolution: "body-parser@npm:1.20.4" dependencies: - bytes: "npm:3.1.2" + bytes: "npm:~3.1.2" content-type: "npm:~1.0.5" debug: "npm:2.6.9" depd: "npm:2.0.0" - destroy: "npm:1.2.0" - http-errors: "npm:2.0.0" - iconv-lite: "npm:0.4.24" - on-finished: "npm:2.4.1" - qs: "npm:6.13.0" - raw-body: "npm:2.5.2" + destroy: "npm:~1.2.0" + http-errors: "npm:~2.0.1" + iconv-lite: "npm:~0.4.24" + on-finished: "npm:~2.4.1" + qs: "npm:~6.14.0" + raw-body: "npm:~2.5.3" type-is: "npm:~1.6.18" - unpipe: "npm:1.0.0" - checksum: 10/8723e3d7a672eb50854327453bed85ac48d045f4958e81e7d470c56bf111f835b97e5b73ae9f6393d0011cc9e252771f46fd281bbabc57d33d3986edf1e6aeca + unpipe: "npm:~1.0.0" + checksum: 10/ff67e28d3f426707be8697a75fdf8d564dc50c341b41f054264d8ab6e2924e519c7ce8acc9d0de05328fdc41e1d9f3f200aec9c1cfb1867d6b676a410d97c689 languageName: node linkType: hard -"bonjour-service@npm:^1.0.11": - version: 1.0.14 - resolution: "bonjour-service@npm:1.0.14" +"bonjour-service@npm:^1.2.1": + version: 1.3.0 + resolution: "bonjour-service@npm:1.3.0" dependencies: - array-flatten: "npm:^2.1.2" - dns-equal: "npm:^1.0.0" fast-deep-equal: "npm:^3.1.3" multicast-dns: "npm:^7.2.5" - checksum: 10/cd6626a7cdbedc4d230537a2d9f6892aef6bdcf04b303554e87ff8c6362a00e072365a5d91b1980206afdf10c8e2ad8006f908e3368005ea15cc13aa18f00775 + checksum: 10/63d516d88f15fa4b89e247e6ff7d81c21a3ef5ed035b0b043c2b38e0c839f54f4ce58fbf9b7668027bf538ac86de366939dbb55cca63930f74eeea1e278c9585 languageName: node linkType: hard @@ -4457,99 +5556,18 @@ __metadata: languageName: node linkType: hard -"brorand@npm:^1.0.1, brorand@npm:^1.1.0": - version: 1.1.0 - resolution: "brorand@npm:1.1.0" - checksum: 10/8a05c9f3c4b46572dec6ef71012b1946db6cae8c7bb60ccd4b7dd5a84655db49fe043ecc6272e7ef1f69dc53d6730b9e2a3a03a8310509a3d797a618cbee52be - languageName: node - linkType: hard - -"browserify-aes@npm:^1.0.4, browserify-aes@npm:^1.2.0": - version: 1.2.0 - resolution: "browserify-aes@npm:1.2.0" +"browserslist@npm:^4.0.0, browserslist@npm:^4.23.0, browserslist@npm:^4.24.0, browserslist@npm:^4.24.2, browserslist@npm:^4.28.1": + version: 4.28.1 + resolution: "browserslist@npm:4.28.1" dependencies: - buffer-xor: "npm:^1.0.3" - cipher-base: "npm:^1.0.0" - create-hash: "npm:^1.1.0" - evp_bytestokey: "npm:^1.0.3" - inherits: "npm:^2.0.1" - safe-buffer: "npm:^5.0.1" - checksum: 10/2813058f74e083a00450b11ea9d5d1f072de7bf0133f5d122d4ff7b849bece56d52b9c51ad0db0fad21c0bc4e8272fd5196114bbe7b94a9b7feb0f9fbb33a3bf - languageName: node - linkType: hard - -"browserify-cipher@npm:^1.0.1": - version: 1.0.1 - resolution: "browserify-cipher@npm:1.0.1" - dependencies: - browserify-aes: "npm:^1.0.4" - browserify-des: "npm:^1.0.0" - evp_bytestokey: "npm:^1.0.0" - checksum: 10/2d8500acf1ee535e6bebe808f7a20e4c3a9e2ed1a6885fff1facbfd201ac013ef030422bec65ca9ece8ffe82b03ca580421463f9c45af6c8415fd629f4118c13 - languageName: node - linkType: hard - -"browserify-des@npm:^1.0.0": - version: 1.0.2 - resolution: "browserify-des@npm:1.0.2" - dependencies: - cipher-base: "npm:^1.0.1" - des.js: "npm:^1.0.0" - inherits: "npm:^2.0.1" - safe-buffer: "npm:^5.1.2" - checksum: 10/2fd9018e598b1b25e002abaf656d46d8e0f2ee2666ff18852d37e5c3d0e47701d6824256b060fac395420d56a0c49c2b0d40a194e6fbd837bfdd893e7eb5ade4 - languageName: node - linkType: hard - -"browserify-rsa@npm:^4.0.0, browserify-rsa@npm:^4.1.0": - version: 4.1.1 - resolution: "browserify-rsa@npm:4.1.1" - dependencies: - bn.js: "npm:^5.2.1" - randombytes: "npm:^2.1.0" - safe-buffer: "npm:^5.2.1" - checksum: 10/62ae0da60e49e8d5dd3b0922119b6edee94ebfa3a184211c804024b3a75f9dab31a1d124cc0545ed050e273f0325c2fd7aba6a51e44ba6f726fceae3210ddade - languageName: node - linkType: hard - -"browserify-sign@npm:^4.2.3": - version: 4.2.3 - resolution: "browserify-sign@npm:4.2.3" - dependencies: - bn.js: "npm:^5.2.1" - browserify-rsa: "npm:^4.1.0" - create-hash: "npm:^1.2.0" - create-hmac: "npm:^1.1.7" - elliptic: "npm:^6.5.5" - hash-base: "npm:~3.0" - inherits: "npm:^2.0.4" - parse-asn1: "npm:^5.1.7" - readable-stream: "npm:^2.3.8" - safe-buffer: "npm:^5.2.1" - checksum: 10/403a8061d229ae31266670345b4a7c00051266761d2c9bbeb68b1a9bcb05f68143b16110cf23a171a5d6716396a1f41296282b3e73eeec0a1871c77f0ff4ee6b - languageName: node - linkType: hard - -"browserify-zlib@npm:^0.2.0": - version: 0.2.0 - resolution: "browserify-zlib@npm:0.2.0" - dependencies: - pako: "npm:~1.0.5" - checksum: 10/852e72effdc00bf8acc6d167d835179eda9e5bd13721ae5d0a2d132dc542f33e73bead2959eb43a2f181a9c495bc2ae2bdb4ec37c4e37ff61a0277741cbaaa7a - languageName: node - linkType: hard - -"browserslist@npm:^4.0.0, browserslist@npm:^4.18.1, browserslist@npm:^4.23.0, browserslist@npm:^4.23.1, browserslist@npm:^4.23.3, browserslist@npm:^4.24.0": - version: 4.24.4 - resolution: "browserslist@npm:4.24.4" - dependencies: - caniuse-lite: "npm:^1.0.30001688" - electron-to-chromium: "npm:^1.5.73" - node-releases: "npm:^2.0.19" - update-browserslist-db: "npm:^1.1.1" + baseline-browser-mapping: "npm:^2.9.0" + caniuse-lite: "npm:^1.0.30001759" + electron-to-chromium: "npm:^1.5.263" + node-releases: "npm:^2.0.27" + update-browserslist-db: "npm:^1.2.0" bin: browserslist: cli.js - checksum: 10/11fda105e803d891311a21a1f962d83599319165faf471c2d70e045dff82a12128f5b50b1fcba665a2352ad66147aaa248a9d2355a80aadc3f53375eb3de2e48 + checksum: 10/64f2a97de4bce8473c0e5ae0af8d76d1ead07a5b05fc6bc87b848678bb9c3a91ae787b27aa98cdd33fc00779607e6c156000bed58fefb9cf8e4c5a183b994cdb languageName: node linkType: hard @@ -4560,13 +5578,6 @@ __metadata: languageName: node linkType: hard -"buffer-xor@npm:^1.0.3": - version: 1.0.3 - resolution: "buffer-xor@npm:1.0.3" - checksum: 10/4a63d48b5117c7eda896d81cd3582d9707329b07c97a14b0ece2edc6e64220ea7ea17c94b295e8c2cb7b9f8291e2b079f9096be8ac14be238420a43e06ec66e2 - languageName: node - linkType: hard - "buffer@npm:^6.0.3": version: 6.0.3 resolution: "buffer@npm:6.0.3" @@ -4577,10 +5588,12 @@ __metadata: languageName: node linkType: hard -"builtin-status-codes@npm:^3.0.0": - version: 3.0.0 - resolution: "builtin-status-codes@npm:3.0.0" - checksum: 10/1119429cf4b0d57bf76b248ad6f529167d343156ebbcc4d4e4ad600484f6bc63002595cbb61b67ad03ce55cd1d3c4711c03bbf198bf24653b8392420482f3773 +"bundle-name@npm:^4.1.0": + version: 4.1.0 + resolution: "bundle-name@npm:4.1.0" + dependencies: + run-applescript: "npm:^7.0.0" + checksum: 10/1d966c8d2dbf4d9d394e53b724ac756c2414c45c01340b37743621f59cc565a435024b394ddcb62b9b335d1c9a31f4640eb648c3fec7f97ee74dc0694c9beb6c languageName: node linkType: hard @@ -4591,13 +5604,20 @@ __metadata: languageName: node linkType: hard -"bytes@npm:3.1.2": +"bytes@npm:3.1.2, bytes@npm:~3.1.2": version: 3.1.2 resolution: "bytes@npm:3.1.2" checksum: 10/a10abf2ba70c784471d6b4f58778c0beeb2b5d405148e66affa91f23a9f13d07603d0a0354667310ae1d6dc141474ffd44e2a074be0f6e2254edb8fc21445388 languageName: node linkType: hard +"bytestreamjs@npm:^2.0.1": + version: 2.0.1 + resolution: "bytestreamjs@npm:2.0.1" + checksum: 10/523b1024e3f887cdc0b3db7c4fc14b8563aaeb75e6642a41991b3208277fd0ae9cd66003c73473fe706c42797bf0c3f1f498fb9880b431d75b332e5709d56a0c + languageName: node + linkType: hard + "cacache@npm:^16.1.0": version: 16.1.3 resolution: "cacache@npm:16.1.3" @@ -4656,7 +5676,7 @@ __metadata: languageName: node linkType: hard -"call-bind@npm:^1.0.0, call-bind@npm:^1.0.2, call-bind@npm:^1.0.7, call-bind@npm:^1.0.8": +"call-bind@npm:^1.0.8": version: 1.0.8 resolution: "call-bind@npm:1.0.8" dependencies: @@ -4728,10 +5748,10 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001646, caniuse-lite@npm:^1.0.30001688": - version: 1.0.30001695 - resolution: "caniuse-lite@npm:1.0.30001695" - checksum: 10/8107c5e89b86c7a2fd506b93c658ff945c98c6518260c3b28af9f02bd83bf83939696241f0b413545c5b9895c86bcae64c9370388576440e74e9b848f04170d3 +"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001759, caniuse-lite@npm:^1.0.30001760": + version: 1.0.30001766 + resolution: "caniuse-lite@npm:1.0.30001766" + checksum: 10/0edeb69bdb741c98deda609b75e35e5c31e944537e9873ea89a4cf9e9baa3e158675b224951a6f3a212d1f0c328a8494ace323c551ca1fa58ce4915562c4e4d3 languageName: node linkType: hard @@ -4742,18 +5762,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^2.4.2": - version: 2.4.2 - resolution: "chalk@npm:2.4.2" - dependencies: - ansi-styles: "npm:^3.2.1" - escape-string-regexp: "npm:^1.0.5" - supports-color: "npm:^5.3.0" - checksum: 10/3d1d103433166f6bfe82ac75724951b33769675252d8417317363ef9d54699b7c3b2d46671b772b893a8e50c3ece70c4b933c73c01e81bc60ea4df9b55afa303 - languageName: node - linkType: hard - -"chalk@npm:^4.0.0, chalk@npm:^4.1.0, chalk@npm:^4.1.2": +"chalk@npm:^4.0.0, chalk@npm:^4.1.2": version: 4.1.2 resolution: "chalk@npm:4.1.2" dependencies: @@ -4841,9 +5850,9 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:^3.4.2, chokidar@npm:^3.5.3": - version: 3.5.3 - resolution: "chokidar@npm:3.5.3" +"chokidar@npm:^3.5.3, chokidar@npm:^3.6.0": + version: 3.6.0 + resolution: "chokidar@npm:3.6.0" dependencies: anymatch: "npm:~3.1.2" braces: "npm:~3.0.2" @@ -4856,7 +5865,7 @@ __metadata: dependenciesMeta: fsevents: optional: true - checksum: 10/863e3ff78ee7a4a24513d2a416856e84c8e4f5e60efbe03e8ab791af1a183f569b62fc6f6b8044e2804966cb81277ddbbc1dc374fba3265bd609ea8efd62f5b3 + checksum: 10/c327fb07704443f8d15f7b4a7ce93b2f0bc0e6cea07ec28a7570aa22cd51fcf0379df589403976ea956c369f25aa82d84561947e227cd925902e1751371658df languageName: node linkType: hard @@ -4890,22 +5899,12 @@ __metadata: languageName: node linkType: hard -"cipher-base@npm:^1.0.0, cipher-base@npm:^1.0.1, cipher-base@npm:^1.0.3": - version: 1.0.6 - resolution: "cipher-base@npm:1.0.6" - dependencies: - inherits: "npm:^2.0.4" - safe-buffer: "npm:^5.2.1" - checksum: 10/faf232deff2351448ea23d265eb8723e035ebbb454baca45fb60c1bd71056ede8b153bef1b221e067f13e6b9288ebb83bb6ae2d5dd4cec285411f9fc22ec1f5b - languageName: node - linkType: hard - -"clean-css@npm:^5.2.2, clean-css@npm:^5.3.2, clean-css@npm:~5.3.2": - version: 5.3.2 - resolution: "clean-css@npm:5.3.2" +"clean-css@npm:^5.2.2, clean-css@npm:^5.3.3, clean-css@npm:~5.3.2": + version: 5.3.3 + resolution: "clean-css@npm:5.3.3" dependencies: source-map: "npm:~0.6.0" - checksum: 10/efd9efbf400f38a12f99324bad5359bdd153211b048721e4d4ddb629a88865dff3012dca547a14bdd783d78ccf064746e39fd91835546a08e2d811866aff0857 + checksum: 10/2db1ae37b384c8ff0a06a12bfa80f56cc02b4abcaaf340db98c0ae88a61dd67c856653fd8135ace6eb0ec13aeab3089c425d2e4238d2a2ad6b6917e6ccc74729 languageName: node linkType: hard @@ -4958,14 +5957,7 @@ __metadata: languageName: node linkType: hard -"clsx@npm:^1.1.1": - version: 1.2.1 - resolution: "clsx@npm:1.2.1" - checksum: 10/5ded6f61f15f1fa0350e691ccec43a28b12fb8e64c8e94715f2a937bc3722d4c3ed41d6e945c971fc4dcc2a7213a43323beaf2e1c28654af63ba70c9968a8643 - languageName: node - linkType: hard - -"clsx@npm:^2.0.0": +"clsx@npm:^2.0.0, clsx@npm:^2.1.1": version: 2.1.1 resolution: "clsx@npm:2.1.1" checksum: 10/cdfb57fa6c7649bbff98d9028c2f0de2f91c86f551179541cf784b1cfdc1562dcb951955f46d54d930a3879931a980e32a46b598acaea274728dbe068deca919 @@ -4979,15 +5971,6 @@ __metadata: languageName: node linkType: hard -"color-convert@npm:^1.9.0": - version: 1.9.3 - resolution: "color-convert@npm:1.9.3" - dependencies: - color-name: "npm:1.1.3" - checksum: 10/ffa319025045f2973919d155f25e7c00d08836b6b33ea2d205418c59bd63a665d713c52d9737a9e0fe467fb194b40fbef1d849bae80d674568ee220a31ef3d10 - languageName: node - linkType: hard - "color-convert@npm:^2.0.1": version: 2.0.1 resolution: "color-convert@npm:2.0.1" @@ -4997,13 +5980,6 @@ __metadata: languageName: node linkType: hard -"color-name@npm:1.1.3": - version: 1.1.3 - resolution: "color-name@npm:1.1.3" - checksum: 10/09c5d3e33d2105850153b14466501f2bfb30324a2f76568a408763a3b7433b0e50e5b4ab1947868e65cb101bb7cb75029553f2c333b6d4b8138a73fcc133d69d - languageName: node - linkType: hard - "color-name@npm:~1.1.4": version: 1.1.4 resolution: "color-name@npm:1.1.4" @@ -5104,7 +6080,7 @@ __metadata: languageName: node linkType: hard -"compressible@npm:~2.0.16": +"compressible@npm:~2.0.18": version: 2.0.18 resolution: "compressible@npm:2.0.18" dependencies: @@ -5113,18 +6089,18 @@ __metadata: languageName: node linkType: hard -"compression@npm:^1.7.4": - version: 1.7.4 - resolution: "compression@npm:1.7.4" +"compression@npm:^1.8.1": + version: 1.8.1 + resolution: "compression@npm:1.8.1" dependencies: - accepts: "npm:~1.3.5" - bytes: "npm:3.0.0" - compressible: "npm:~2.0.16" + bytes: "npm:3.1.2" + compressible: "npm:~2.0.18" debug: "npm:2.6.9" - on-headers: "npm:~1.0.2" - safe-buffer: "npm:5.1.2" + negotiator: "npm:~0.6.4" + on-headers: "npm:~1.1.0" + safe-buffer: "npm:5.2.1" vary: "npm:~1.1.2" - checksum: 10/469cd097908fe1d3ff146596d4c24216ad25eabb565c5456660bdcb3a14c82ebc45c23ce56e19fc642746cf407093b55ab9aa1ac30b06883b27c6c736e6383c2 + checksum: 10/e7552bfbd780f2003c6fe8decb44561f5cc6bc82f0c61e81122caff5ec656f37824084f52155b1e8ef31d7656cecbec9a2499b7a68e92e20780ffb39b479abb7 languageName: node linkType: hard @@ -5188,17 +6164,10 @@ __metadata: languageName: node linkType: hard -"consola@npm:^2.15.3": - version: 2.15.3 - resolution: "consola@npm:2.15.3" - checksum: 10/ba5b3c6960b2eafb9d2ff2325444dd1d4eb53115df46eba823a4e7bfe6afbba0eb34747c0de82c7cd8a939db59b0cb5a8b8a54a94bb2e44feeddc26cefde3622 - languageName: node - linkType: hard - -"console-browserify@npm:^1.2.0": - version: 1.2.0 - resolution: "console-browserify@npm:1.2.0" - checksum: 10/4f16c471fa84909af6ae00527ce8d19dd9ed587eab85923c145cadfbc35414139f87e7bdd61746138e22cd9df45c2a1ca060370998c2c39f801d4a778105bac5 +"consola@npm:^3.2.3": + version: 3.4.2 + resolution: "consola@npm:3.4.2" + checksum: 10/32192c9f50d7cac27c5d7c4ecd3ff3679aea863e6bf5bd6a9cc2b05d1cd78addf5dae71df08c54330c142be8e7fbd46f051030129b57c6aacdd771efe409c4b2 languageName: node linkType: hard @@ -5209,13 +6178,6 @@ __metadata: languageName: node linkType: hard -"constants-browserify@npm:^1.0.0": - version: 1.0.0 - resolution: "constants-browserify@npm:1.0.0" - checksum: 10/49ef0babd907616dddde6905b80fe44ad5948e1eaaf6cf65d5f23a8c60c029ff63a1198c364665be1d6b2cb183d7e12921f33049cc126734ade84a3cfdbc83f6 - languageName: node - linkType: hard - "content-disposition@npm:0.5.2": version: 0.5.2 resolution: "content-disposition@npm:0.5.2" @@ -5223,7 +6185,7 @@ __metadata: languageName: node linkType: hard -"content-disposition@npm:0.5.4": +"content-disposition@npm:~0.5.4": version: 0.5.4 resolution: "content-disposition@npm:0.5.4" dependencies: @@ -5246,24 +6208,24 @@ __metadata: languageName: node linkType: hard -"cookie-signature@npm:1.0.6": - version: 1.0.6 - resolution: "cookie-signature@npm:1.0.6" - checksum: 10/f4e1b0a98a27a0e6e66fd7ea4e4e9d8e038f624058371bf4499cfcd8f3980be9a121486995202ba3fca74fbed93a407d6d54d43a43f96fd28d0bd7a06761591a +"cookie-signature@npm:~1.0.6": + version: 1.0.7 + resolution: "cookie-signature@npm:1.0.7" + checksum: 10/1a62808cd30d15fb43b70e19829b64d04b0802d8ef00275b57d152de4ae6a3208ca05c197b6668d104c4d9de389e53ccc2d3bc6bcaaffd9602461417d8c40710 languageName: node linkType: hard -"cookie@npm:0.6.0": - version: 0.6.0 - resolution: "cookie@npm:0.6.0" - checksum: 10/c1f8f2ea7d443b9331680598b0ae4e6af18a618c37606d1bbdc75bec8361cce09fe93e727059a673f2ba24467131a9fb5a4eec76bb1b149c1b3e1ccb268dc583 +"cookie@npm:~0.7.1": + version: 0.7.2 + resolution: "cookie@npm:0.7.2" + checksum: 10/24b286c556420d4ba4e9bc09120c9d3db7d28ace2bd0f8ccee82422ce42322f73c8312441271e5eefafbead725980e5996cc02766dbb89a90ac7f5636ede608f languageName: node linkType: hard -"copy-text-to-clipboard@npm:^3.1.0, copy-text-to-clipboard@npm:^3.2.0": - version: 3.2.0 - resolution: "copy-text-to-clipboard@npm:3.2.0" - checksum: 10/df7115c197a166d51f59e4e20ab2a68a855ae8746d25ff149b5465c694d9a405c7e6684b73a9f87ba8d653070164e229c15dfdb9fd77c30be1ff0da569661060 +"copy-text-to-clipboard@npm:^3.2.0": + version: 3.2.2 + resolution: "copy-text-to-clipboard@npm:3.2.2" + checksum: 10/ae512de746632eb0cc3fe78ba16022b46134f835aa411d372798ee76c93baae8faa6b2cf5052ebd132ec6a2016124dc19786b231e5c0d333999c5d448e7a2e42 languageName: node linkType: hard @@ -5283,19 +6245,19 @@ __metadata: languageName: node linkType: hard -"core-js-compat@npm:^3.32.2, core-js-compat@npm:^3.37.1, core-js-compat@npm:^3.38.0": - version: 3.38.1 - resolution: "core-js-compat@npm:3.38.1" +"core-js-compat@npm:^3.43.0": + version: 3.48.0 + resolution: "core-js-compat@npm:3.48.0" dependencies: - browserslist: "npm:^4.23.3" - checksum: 10/4e2f219354fd268895f79486461a12df96f24ed307321482fe2a43529c5a64e7c16bcba654980ba217d603444f5141d43a79058aeac77511085f065c5da72207 + browserslist: "npm:^4.28.1" + checksum: 10/83c326dcfef5e174fd3f8f33c892c66e06d567ce27f323a1197a6c280c0178fe18d3e9c5fb95b00c18b98d6c53fba5c646def5fedaa77310a4297d16dfbe2029 languageName: node linkType: hard -"core-js-pure@npm:^3.30.2": - version: 3.33.0 - resolution: "core-js-pure@npm:3.33.0" - checksum: 10/4916014881a4e19475a503c91788ea0414d11687ea5c0de6666d5de756819db444ef88b29e12c7ad35d7320fc952251183ec60cf3f206e7955cccdfe4c260054 +"core-js-pure@npm:^3.43.0": + version: 3.48.0 + resolution: "core-js-pure@npm:3.48.0" + checksum: 10/7c624d5551252ad166b9a7df4daca354540b71bb2ce9c8df2a9ef7acb6335a7a56557bcbe2bd78e20e3a4eeeee2922ff37a22a67e978b293a2b4e5b9a7a04d9b languageName: node linkType: hard @@ -5313,20 +6275,7 @@ __metadata: languageName: node linkType: hard -"cosmiconfig@npm:^6.0.0": - version: 6.0.0 - resolution: "cosmiconfig@npm:6.0.0" - dependencies: - "@types/parse-json": "npm:^4.0.0" - import-fresh: "npm:^3.1.0" - parse-json: "npm:^5.0.0" - path-type: "npm:^4.0.0" - yaml: "npm:^1.7.2" - checksum: 10/b184d2bfbced9ba6840fd097dbf3455c68b7258249bb9b1277913823d516d8dfdade8c5ccbf79db0ca8ebd4cc9b9be521ccc06a18396bd242d50023c208f1594 - languageName: node - linkType: hard - -"cosmiconfig@npm:^8.1.3, cosmiconfig@npm:^8.2.0": +"cosmiconfig@npm:^8.1.3, cosmiconfig@npm:^8.3.5": version: 8.3.6 resolution: "cosmiconfig@npm:8.3.6" dependencies: @@ -5343,55 +6292,6 @@ __metadata: languageName: node linkType: hard -"create-ecdh@npm:^4.0.4": - version: 4.0.4 - resolution: "create-ecdh@npm:4.0.4" - dependencies: - bn.js: "npm:^4.1.0" - elliptic: "npm:^6.5.3" - checksum: 10/0dd7fca9711d09e152375b79acf1e3f306d1a25ba87b8ff14c2fd8e68b83aafe0a7dd6c4e540c9ffbdd227a5fa1ad9b81eca1f233c38bb47770597ba247e614b - languageName: node - linkType: hard - -"create-hash@npm:^1.1.0, create-hash@npm:^1.2.0": - version: 1.2.0 - resolution: "create-hash@npm:1.2.0" - dependencies: - cipher-base: "npm:^1.0.1" - inherits: "npm:^2.0.1" - md5.js: "npm:^1.3.4" - ripemd160: "npm:^2.0.1" - sha.js: "npm:^2.4.0" - checksum: 10/3cfef32043b47a8999602af9bcd74966db6971dd3eb828d1a479f3a44d7f58e38c1caf34aa21a01941cc8d9e1a841738a732f200f00ea155f8a8835133d2e7bc - languageName: node - linkType: hard - -"create-hash@npm:~1.1.3": - version: 1.1.3 - resolution: "create-hash@npm:1.1.3" - dependencies: - cipher-base: "npm:^1.0.1" - inherits: "npm:^2.0.1" - ripemd160: "npm:^2.0.0" - sha.js: "npm:^2.4.0" - checksum: 10/b9f675719321dd3a3c3540bb46afcbdaf7182366ce93da9265318290e928be881e5edeff8c48a5ee9263c342e5e3f705fad5eb48f2e2cddc5fed1eb54077e076 - languageName: node - linkType: hard - -"create-hmac@npm:^1.1.7": - version: 1.1.7 - resolution: "create-hmac@npm:1.1.7" - dependencies: - cipher-base: "npm:^1.0.3" - create-hash: "npm:^1.1.0" - inherits: "npm:^2.0.1" - ripemd160: "npm:^2.0.0" - safe-buffer: "npm:^5.0.1" - sha.js: "npm:^2.4.8" - checksum: 10/2b26769f87e99ef72150bf99d1439d69272b2e510e23a2b8daf4e93e2412f4842504237d726044fa797cb20ee0ec8bee78d414b11f2d7ca93299185c93df0dae - languageName: node - linkType: hard - "cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.3": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" @@ -5403,27 +6303,7 @@ __metadata: languageName: node linkType: hard -"crypto-browserify@npm:^3.12.0": - version: 3.12.1 - resolution: "crypto-browserify@npm:3.12.1" - dependencies: - browserify-cipher: "npm:^1.0.1" - browserify-sign: "npm:^4.2.3" - create-ecdh: "npm:^4.0.4" - create-hash: "npm:^1.2.0" - create-hmac: "npm:^1.1.7" - diffie-hellman: "npm:^5.0.3" - hash-base: "npm:~3.0.4" - inherits: "npm:^2.0.4" - pbkdf2: "npm:^3.1.2" - public-encrypt: "npm:^4.0.3" - randombytes: "npm:^2.1.0" - randomfill: "npm:^1.0.4" - checksum: 10/13da0b5f61b3e8e68fcbebf0394f2b2b4d35a0d0ba6ab762720c13391d3697ea42735260a26328a6a3d872be7d4cb5abe98a7a8f88bc93da7ba59b993331b409 - languageName: node - linkType: hard - -"crypto-js@npm:^4.1.1": +"crypto-js@npm:^4.2.0": version: 4.2.0 resolution: "crypto-js@npm:4.2.0" checksum: 10/c7bcc56a6e01c3c397e95aa4a74e4241321f04677f9a618a8f48a63b5781617248afb9adb0629824792e7ec20ca0d4241a49b6b2938ae6f973ec4efc5c53c924 @@ -5439,6 +6319,17 @@ __metadata: languageName: node linkType: hard +"css-blank-pseudo@npm:^7.0.1": + version: 7.0.1 + resolution: "css-blank-pseudo@npm:7.0.1" + dependencies: + postcss-selector-parser: "npm:^7.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/bbe45955d0cb5a803f63f44f68b565cd1df41e737aca391262f9e9c8f2b86600fad18fbf9c5f48ba0cf10891647662831bc29019c02bcfc697c65ba649d18a1b + languageName: node + linkType: hard + "css-declaration-sorter@npm:^7.2.0": version: 7.2.0 resolution: "css-declaration-sorter@npm:7.2.0" @@ -5448,21 +6339,40 @@ __metadata: languageName: node linkType: hard -"css-loader@npm:^6.8.1": - version: 6.8.1 - resolution: "css-loader@npm:6.8.1" +"css-has-pseudo@npm:^7.0.3": + version: 7.0.3 + resolution: "css-has-pseudo@npm:7.0.3" + dependencies: + "@csstools/selector-specificity": "npm:^5.0.0" + postcss-selector-parser: "npm:^7.0.0" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/ffda6490aacb2903803f7d6c7b3ee41e654a3e9084c5eb0cf8f7602cf49ebce1b7891962016f622c36c67f4f685ed57628e7a0efbdba8cc55c5bf76ed58267d8 + languageName: node + linkType: hard + +"css-loader@npm:^6.11.0": + version: 6.11.0 + resolution: "css-loader@npm:6.11.0" dependencies: icss-utils: "npm:^5.1.0" - postcss: "npm:^8.4.21" - postcss-modules-extract-imports: "npm:^3.0.0" - postcss-modules-local-by-default: "npm:^4.0.3" - postcss-modules-scope: "npm:^3.0.0" + postcss: "npm:^8.4.33" + postcss-modules-extract-imports: "npm:^3.1.0" + postcss-modules-local-by-default: "npm:^4.0.5" + postcss-modules-scope: "npm:^3.2.0" postcss-modules-values: "npm:^4.0.0" postcss-value-parser: "npm:^4.2.0" - semver: "npm:^7.3.8" + semver: "npm:^7.5.4" peerDependencies: + "@rspack/core": 0.x || 1.x webpack: ^5.0.0 - checksum: 10/f20bb2a181c64d2f49586ab3922cae884519cfc8ae9ba8513065032255ed7bbdb4de75362f99d641d39d36d3732b7932884cd0e6fc71c8b0fb8b99a654f9cd08 + peerDependenciesMeta: + "@rspack/core": + optional: true + webpack: + optional: true + checksum: 10/9e3665509f6786d46683de5c5f5c4bdd4aa62396b4017b41dbbb41ea5ada4012c80ee1e3302b79b504bc24da7fa69e3552d99006cecc953e0d9eef4a3053b929 languageName: node linkType: hard @@ -5495,6 +6405,15 @@ __metadata: languageName: node linkType: hard +"css-prefers-color-scheme@npm:^10.0.0": + version: 10.0.0 + resolution: "css-prefers-color-scheme@npm:10.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/b09055fdb8250c5f83b396bb310f7df48955cac6ff5dedb52f271af089a568b0c7b442461a24c533ffbe3f406ab39a043713264c32b9c75a625c8aaa48551714 + languageName: node + linkType: hard + "css-select@npm:^4.1.3": version: 4.3.0 resolution: "css-select@npm:4.3.0" @@ -5548,6 +6467,13 @@ __metadata: languageName: node linkType: hard +"cssdb@npm:^8.6.0": + version: 8.7.1 + resolution: "cssdb@npm:8.7.1" + checksum: 10/70b38620d6ac4b3fff0c1e6edff4ee5ed37f22f9d426dbf0fe5051ad14e39b31e4606068eb12cf46233ea305554a589574f95cce89f23bb79f651e344002bde6 + languageName: node + linkType: hard + "cssesc@npm:^3.0.0": version: 3.0.0 resolution: "cssesc@npm:3.0.0" @@ -5651,7 +6577,14 @@ __metadata: languageName: node linkType: hard -"debug@npm:2.6.9, debug@npm:^2.6.0": +"debounce@npm:^1.2.1": + version: 1.2.1 + resolution: "debounce@npm:1.2.1" + checksum: 10/0b95b2a9d80ed69117d890f8dab8c0f2d6066f8d20edd1d810ae51f8f366a6d4c8b1d56e97dcb9304e93d57de4d5db440d34a03def7dad50403fc3f22bf16808 + languageName: node + linkType: hard + +"debug@npm:2.6.9": version: 2.6.9 resolution: "debug@npm:2.6.9" dependencies: @@ -5660,15 +6593,15 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.3": - version: 4.3.4 - resolution: "debug@npm:4.3.4" +"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.3.1, debug@npm:^4.3.3, debug@npm:^4.4.3": + version: 4.4.3 + resolution: "debug@npm:4.4.3" dependencies: - ms: "npm:2.1.2" + ms: "npm:^2.1.3" peerDependenciesMeta: supports-color: optional: true - checksum: 10/0073c3bcbd9cb7d71dd5f6b55be8701af42df3e56e911186dfa46fac3a5b9eb7ce7f377dd1d3be6db8977221f8eb333d945216f645cf56f6b688cd484837d255 + checksum: 10/9ada3434ea2993800bd9a1e320bd4aa7af69659fb51cca685d390949434bc0a8873c21ed7c9b852af6f2455a55c6d050aa3937d52b3c69f796dab666f762acad languageName: node linkType: hard @@ -5697,19 +6630,27 @@ __metadata: languageName: node linkType: hard -"deepmerge@npm:^4.2.2, deepmerge@npm:^4.3.1": +"deepmerge@npm:^4.3.1": version: 4.3.1 resolution: "deepmerge@npm:4.3.1" checksum: 10/058d9e1b0ff1a154468bf3837aea436abcfea1ba1d165ddaaf48ca93765fdd01a30d33c36173da8fbbed951dd0a267602bc782fe288b0fc4b7e1e7091afc4529 languageName: node linkType: hard -"default-gateway@npm:^6.0.3": - version: 6.0.3 - resolution: "default-gateway@npm:6.0.3" +"default-browser-id@npm:^5.0.0": + version: 5.0.1 + resolution: "default-browser-id@npm:5.0.1" + checksum: 10/52c637637bcd76bfe974462a2f1dd75cb04784c2852935575760f82e1fd338e5e80d3c45a9b01fdbb1e450553a830bb163b004d2eca223c5573989f82232a072 + languageName: node + linkType: hard + +"default-browser@npm:^5.2.1": + version: 5.4.0 + resolution: "default-browser@npm:5.4.0" dependencies: - execa: "npm:^5.0.0" - checksum: 10/126f8273ecac8ee9ff91ea778e8784f6cd732d77c3157e8c5bdd6ed03651b5291f71446d05bc02d04073b1e67583604db5394ea3cf992ede0088c70ea15b7378 + bundle-name: "npm:^4.1.0" + default-browser-id: "npm:^5.0.0" + checksum: 10/cac0222ca5c9a3387d25337228689652ab33679a6566995c7194a75af7e554e91ec9ac92a70bfaa8e8089eae9f466ae99267bb38601282aade89b200f50a765c languageName: node linkType: hard @@ -5738,7 +6679,14 @@ __metadata: languageName: node linkType: hard -"define-properties@npm:^1.1.3, define-properties@npm:^1.2.1": +"define-lazy-prop@npm:^3.0.0": + version: 3.0.0 + resolution: "define-lazy-prop@npm:3.0.0" + checksum: 10/f28421cf9ee86eecaf5f3b8fe875f13d7009c2625e97645bfff7a2a49aca678270b86c39f9c32939e5ca7ab96b551377ed4139558c795e076774287ad3af1aa4 + languageName: node + linkType: hard + +"define-properties@npm:^1.2.1": version: 1.2.1 resolution: "define-properties@npm:1.2.1" dependencies: @@ -5749,22 +6697,6 @@ __metadata: languageName: node linkType: hard -"del@npm:^6.1.1": - version: 6.1.1 - resolution: "del@npm:6.1.1" - dependencies: - globby: "npm:^11.0.1" - graceful-fs: "npm:^4.2.4" - is-glob: "npm:^4.0.1" - is-path-cwd: "npm:^2.2.0" - is-path-inside: "npm:^3.0.2" - p-map: "npm:^4.0.0" - rimraf: "npm:^3.0.2" - slash: "npm:^3.0.0" - checksum: 10/563288b73b8b19a7261c47fd21a330eeab6e2acd7c6208c49790dfd369127120dd7836cdf0c1eca216b77c94782a81507eac6b4734252d3bef2795cb366996b6 - languageName: node - linkType: hard - "delegates@npm:^1.0.0": version: 1.0.0 resolution: "delegates@npm:1.0.0" @@ -5772,7 +6704,7 @@ __metadata: languageName: node linkType: hard -"depd@npm:2.0.0": +"depd@npm:2.0.0, depd@npm:~2.0.0": version: 2.0.0 resolution: "depd@npm:2.0.0" checksum: 10/c0c8ff36079ce5ada64f46cc9d6fd47ebcf38241105b6e0c98f412e8ad91f084bcf906ff644cc3a4bd876ca27a62accb8b0fff72ea6ed1a414b89d8506f4a5ca @@ -5793,17 +6725,7 @@ __metadata: languageName: node linkType: hard -"des.js@npm:^1.0.0": - version: 1.1.0 - resolution: "des.js@npm:1.1.0" - dependencies: - inherits: "npm:^2.0.1" - minimalistic-assert: "npm:^1.0.0" - checksum: 10/d35fc82b5a0b2127b12699212e90b54ddd8134e0cf8d27a8c30507ed3572aa574ab71800cbb473769128a52dcf21acc3271c5c359508a5aa772e990df3b1a698 - languageName: node - linkType: hard - -"destroy@npm:1.2.0": +"destroy@npm:1.2.0, destroy@npm:~1.2.0": version: 1.2.0 resolution: "destroy@npm:1.2.0" checksum: 10/0acb300b7478a08b92d810ab229d5afe0d2f4399272045ab22affa0d99dbaf12637659411530a6fcd597a9bdac718fc94373a61a95b4651bbc7b83684a565e38 @@ -5819,6 +6741,13 @@ __metadata: languageName: node linkType: hard +"detect-libc@npm:^2.0.3": + version: 2.1.2 + resolution: "detect-libc@npm:2.1.2" + checksum: 10/b736c8d97d5d46164c0d1bed53eb4e6a3b1d8530d460211e2d52f1c552875e706c58a5376854e4e54f8b828c9cada58c855288c968522eb93ac7696d65970766 + languageName: node + linkType: hard + "detect-node@npm:^2.0.4": version: 2.1.0 resolution: "detect-node@npm:2.1.0" @@ -5835,19 +6764,6 @@ __metadata: languageName: node linkType: hard -"detect-port-alt@npm:^1.1.6": - version: 1.1.6 - resolution: "detect-port-alt@npm:1.1.6" - dependencies: - address: "npm:^1.0.1" - debug: "npm:^2.6.0" - bin: - detect: ./bin/detect-port - detect-port: ./bin/detect-port - checksum: 10/35c9f9c69d12d2ca43d093f4f02d7763b47673910749bd12e6fedeb0ab5c546d27ab8e6425a9cbc65edd408490241390a8e680e8ec7e13940e84754ad81d632e - languageName: node - linkType: hard - "detect-port@npm:^1.5.1": version: 1.5.1 resolution: "detect-port@npm:1.5.1" @@ -5870,24 +6786,6 @@ __metadata: languageName: node linkType: hard -"diff@npm:^5.0.0": - version: 5.2.0 - resolution: "diff@npm:5.2.0" - checksum: 10/01b7b440f83a997350a988e9d2f558366c0f90f15be19f4aa7f1bb3109a4e153dfc3b9fbf78e14ea725717017407eeaa2271e3896374a0181e8f52445740846d - languageName: node - linkType: hard - -"diffie-hellman@npm:^5.0.3": - version: 5.0.3 - resolution: "diffie-hellman@npm:5.0.3" - dependencies: - bn.js: "npm:^4.1.0" - miller-rabin: "npm:^4.0.0" - randombytes: "npm:^2.0.0" - checksum: 10/2ff28231f93b27a4903461432d2de831df02e3568ea7633d5d7b6167eb73077f823b2bca26de6ba4f5c7ecd10a3df5aa94d376d136ab6209948c03cc4e4ac1fe - languageName: node - linkType: hard - "dir-glob@npm:^3.0.1": version: 3.0.1 resolution: "dir-glob@npm:3.0.1" @@ -5897,13 +6795,6 @@ __metadata: languageName: node linkType: hard -"dns-equal@npm:^1.0.0": - version: 1.0.0 - resolution: "dns-equal@npm:1.0.0" - checksum: 10/c4f55af6f13536de39ebcfa15f504a5678d4fc2cf37b76fd41e73aa46dbd1fa596c9468c0c929aeb248ec443cb217fde949942c513312acf93c76cf783276617 - languageName: node - linkType: hard - "dns-packet@npm:^5.2.2": version: 5.4.0 resolution: "dns-packet@npm:5.4.0" @@ -5914,30 +6805,30 @@ __metadata: linkType: hard "docusaurus-plugin-openapi-docs@npm:^4.3.0": - version: 4.3.7 - resolution: "docusaurus-plugin-openapi-docs@npm:4.3.7" + version: 4.7.1 + resolution: "docusaurus-plugin-openapi-docs@npm:4.7.1" dependencies: "@apidevtools/json-schema-ref-parser": "npm:^11.5.4" - "@redocly/openapi-core": "npm:^1.10.5" + "@redocly/openapi-core": "npm:^1.34.3" allof-merge: "npm:^0.6.6" chalk: "npm:^4.1.2" - clsx: "npm:^1.1.1" - fs-extra: "npm:^9.0.1" + clsx: "npm:^2.1.1" + fs-extra: "npm:^11.3.0" json-pointer: "npm:^0.6.2" json5: "npm:^2.2.3" - lodash: "npm:^4.17.20" + lodash: "npm:^4.17.21" mustache: "npm:^4.2.0" - openapi-to-postmanv2: "npm:^4.21.0" - postman-collection: "npm:^4.4.0" - slugify: "npm:^1.6.5" + openapi-to-postmanv2: "npm:^5.0.0" + postman-collection: "npm:^5.0.2" + slugify: "npm:^1.6.6" swagger2openapi: "npm:^7.0.8" - xml-formatter: "npm:^2.6.1" + xml-formatter: "npm:^3.6.6" peerDependencies: "@docusaurus/plugin-content-docs": ^3.5.0 "@docusaurus/utils": ^3.5.0 "@docusaurus/utils-validation": ^3.5.0 react: ^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0 - checksum: 10/ad011358b13fd5e574e33ad3e1b2bb002226e2dcab72e6a8c98c4ad5436a81e28815afe6f14252a48bb52f0c088b03dfd93a5955a8a6883fbda794bebf7d1792 + checksum: 10/641edbbcac889edacd109f40815d46bd4b45b3941981f57c2922e916f2657d95beae5f0d68a628b4c24b0e926289e93e27e52e27480e01087262d29734f5b1c8 languageName: node linkType: hard @@ -5963,41 +6854,43 @@ __metadata: linkType: hard "docusaurus-theme-openapi-docs@npm:^4.3.0": - version: 4.3.2 - resolution: "docusaurus-theme-openapi-docs@npm:4.3.2" + version: 4.7.1 + resolution: "docusaurus-theme-openapi-docs@npm:4.7.1" dependencies: "@hookform/error-message": "npm:^2.0.1" - "@reduxjs/toolkit": "npm:^1.7.1" + "@reduxjs/toolkit": "npm:^2.8.2" allof-merge: "npm:^0.6.6" - clsx: "npm:^1.1.1" - copy-text-to-clipboard: "npm:^3.1.0" - crypto-js: "npm:^4.1.1" + buffer: "npm:^6.0.3" + clsx: "npm:^2.1.1" + copy-text-to-clipboard: "npm:^3.2.0" + crypto-js: "npm:^4.2.0" file-saver: "npm:^2.0.5" - lodash: "npm:^4.17.20" - node-polyfill-webpack-plugin: "npm:^3.0.0" - postman-code-generators: "npm:^1.10.1" - postman-collection: "npm:^4.4.0" - prism-react-renderer: "npm:^2.3.0" - react-hook-form: "npm:^7.43.8" - react-live: "npm:^4.0.0" + lodash: "npm:^4.17.21" + pako: "npm:^2.1.0" + postman-code-generators: "npm:^2.0.0" + postman-collection: "npm:^5.0.2" + prism-react-renderer: "npm:^2.4.1" + process: "npm:^0.11.10" + react-hook-form: "npm:^7.59.0" + react-live: "npm:^4.1.8" react-magic-dropzone: "npm:^1.0.1" - react-markdown: "npm:^8.0.1" - react-modal: "npm:^3.15.1" - react-redux: "npm:^7.2.0" - rehype-raw: "npm:^6.1.1" - remark-gfm: "npm:3.0.1" - sass: "npm:^1.80.4" - sass-loader: "npm:^16.0.2" + react-markdown: "npm:^10.1.0" + react-modal: "npm:^3.16.3" + react-redux: "npm:^9.2.0" + rehype-raw: "npm:^7.0.0" + remark-gfm: "npm:4.0.1" + sass: "npm:^1.89.2" + sass-loader: "npm:^16.0.5" unist-util-visit: "npm:^5.0.0" - webpack: "npm:^5.61.0" - xml-formatter: "npm:^2.6.1" + url: "npm:^0.11.4" + xml-formatter: "npm:^3.6.6" peerDependencies: "@docusaurus/theme-common": ^3.5.0 docusaurus-plugin-openapi-docs: ^4.0.0 docusaurus-plugin-sass: ^0.2.3 react: ^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0 - checksum: 10/94e7203c2773605a3f883c744ccdb756e96e763dd79823e20e40433a53f45e4b23a586902462295e75e49b7e7f08b864111fa3b77e66bdd963c7e8bbf5f8f424 + checksum: 10/2b8ad287d156c26f04fd617895da54ed7ffdc368bbb0ef95bdde30870c6e9306a0cbcafeb8002ef6fc5a3a6e623c7de92488fded450c47efa1986aca1f752c16 languageName: node linkType: hard @@ -6032,13 +6925,6 @@ __metadata: languageName: node linkType: hard -"domain-browser@npm:^4.22.0": - version: 4.23.0 - resolution: "domain-browser@npm:4.23.0" - checksum: 10/56d5a969ed330a16aa6f03f26e7ba3b98e07c7ce4a77d08f987e9e424f1deca009070ed9bd24011d9b863499dcba95de4d679bba77aef346ee23230e570ab9cf - languageName: node - linkType: hard - "domelementtype@npm:^2.0.1, domelementtype@npm:^2.2.0, domelementtype@npm:^2.3.0": version: 2.3.0 resolution: "domelementtype@npm:2.3.0" @@ -6137,25 +7023,10 @@ __metadata: languageName: node linkType: hard -"electron-to-chromium@npm:^1.5.73": - version: 1.5.88 - resolution: "electron-to-chromium@npm:1.5.88" - checksum: 10/cb52f817ad7b918580c2d70f3e7c21bea7e6f260d3b3a221265fce03cd6b653af91464bfebee8a55f0f6bcbc11b34853e7555bafb3ecf65ff035d65e63e6c745 - languageName: node - linkType: hard - -"elliptic@npm:^6.5.3, elliptic@npm:^6.5.5": - version: 6.6.1 - resolution: "elliptic@npm:6.6.1" - dependencies: - bn.js: "npm:^4.11.9" - brorand: "npm:^1.1.0" - hash.js: "npm:^1.0.0" - hmac-drbg: "npm:^1.0.1" - inherits: "npm:^2.0.4" - minimalistic-assert: "npm:^1.0.1" - minimalistic-crypto-utils: "npm:^1.0.1" - checksum: 10/dc678c9febd89a219c4008ba3a9abb82237be853d9fd171cd602c8fb5ec39927e65c6b5e7a1b2a4ea82ee8e0ded72275e7932bb2da04a5790c2638b818e4e1c5 +"electron-to-chromium@npm:^1.5.263": + version: 1.5.279 + resolution: "electron-to-chromium@npm:1.5.279" + checksum: 10/debb143e6d5482a0f4cff7535bdb5725d58625f65bfa3704a5d1074052d934a72b20f253e7db124acad9cd40f404f14a53eb671db11cc10d69833960483be70d languageName: node linkType: hard @@ -6217,13 +7088,13 @@ __metadata: languageName: node linkType: hard -"enhanced-resolve@npm:^5.17.1": - version: 5.17.1 - resolution: "enhanced-resolve@npm:5.17.1" +"enhanced-resolve@npm:^5.17.4": + version: 5.18.4 + resolution: "enhanced-resolve@npm:5.18.4" dependencies: graceful-fs: "npm:^4.2.4" tapable: "npm:^2.2.0" - checksum: 10/e8e03cb7a4bf3c0250a89afbd29e5ec20e90ba5fcd026066232a0754864d7d0a393fa6fc0e5379314a6529165a1834b36731147080714459d98924520410d8f5 + checksum: 10/dcd477cb694d9cc84109a03269c13d3da0851d50099fd3fa7c56b2867dd720d59c7f1431bd47c9cad2825ad52588bd71d3a68cf1e5ee0bc57551d8a3fab4e6f2 languageName: node linkType: hard @@ -6278,10 +7149,10 @@ __metadata: languageName: node linkType: hard -"es-module-lexer@npm:^1.2.1": - version: 1.2.1 - resolution: "es-module-lexer@npm:1.2.1" - checksum: 10/4bb92673b94b46e8d2e23ff275696842c83cdabd19eaa84bab60ce37ee036051dedb158746f6d88a58b9d430f881a717c23434e2c8f05d1ba2c69d68e4f05ab4 +"es-module-lexer@npm:^2.0.0": + version: 2.0.0 + resolution: "es-module-lexer@npm:2.0.0" + checksum: 10/b075855289b5f40ee496f3d7525c5c501d029c3da15c22298a0030d625bf36d1da0768b26278f7f4bada2a602459b505888e20b77c414fba5da5619b0e84dbd1 languageName: node linkType: hard @@ -6484,39 +7355,21 @@ __metadata: languageName: node linkType: hard -"event-target-shim@npm:^5.0.0": - version: 5.0.1 - resolution: "event-target-shim@npm:5.0.1" - checksum: 10/49ff46c3a7facbad3decb31f597063e761785d7fdb3920d4989d7b08c97a61c2f51183e2f3a03130c9088df88d4b489b1b79ab632219901f184f85158508f4c8 - languageName: node - linkType: hard - -"eventemitter3@npm:^4.0.0": +"eventemitter3@npm:^4.0.0, eventemitter3@npm:^4.0.4": version: 4.0.7 resolution: "eventemitter3@npm:4.0.7" checksum: 10/8030029382404942c01d0037079f1b1bc8fed524b5849c237b80549b01e2fc49709e1d0c557fa65ca4498fc9e24cff1475ef7b855121fcc15f9d61f93e282346 languageName: node linkType: hard -"events@npm:^3.2.0, events@npm:^3.3.0": +"events@npm:^3.2.0": version: 3.3.0 resolution: "events@npm:3.3.0" checksum: 10/a3d47e285e28d324d7180f1e493961a2bbb4cad6412090e4dec114f4db1f5b560c7696ee8e758f55e23913ede856e3689cd3aa9ae13c56b5d8314cd3b3ddd1be languageName: node linkType: hard -"evp_bytestokey@npm:^1.0.0, evp_bytestokey@npm:^1.0.3": - version: 1.0.3 - resolution: "evp_bytestokey@npm:1.0.3" - dependencies: - md5.js: "npm:^1.3.4" - node-gyp: "npm:latest" - safe-buffer: "npm:^5.1.1" - checksum: 10/ad4e1577f1a6b721c7800dcc7c733fe01f6c310732bb5bf2240245c2a5b45a38518b91d8be2c610611623160b9d1c0e91f1ce96d639f8b53e8894625cf20fa45 - languageName: node - linkType: hard - -"execa@npm:^5.0.0, execa@npm:^5.1.1": +"execa@npm:5.1.1, execa@npm:^5.1.1": version: 5.1.1 resolution: "execa@npm:5.1.1" dependencies: @@ -6540,42 +7393,42 @@ __metadata: languageName: node linkType: hard -"express@npm:^4.17.3": - version: 4.21.0 - resolution: "express@npm:4.21.0" +"express@npm:^4.22.1": + version: 4.22.1 + resolution: "express@npm:4.22.1" dependencies: accepts: "npm:~1.3.8" array-flatten: "npm:1.1.1" - body-parser: "npm:1.20.3" - content-disposition: "npm:0.5.4" + body-parser: "npm:~1.20.3" + content-disposition: "npm:~0.5.4" content-type: "npm:~1.0.4" - cookie: "npm:0.6.0" - cookie-signature: "npm:1.0.6" + cookie: "npm:~0.7.1" + cookie-signature: "npm:~1.0.6" debug: "npm:2.6.9" depd: "npm:2.0.0" encodeurl: "npm:~2.0.0" escape-html: "npm:~1.0.3" etag: "npm:~1.8.1" - finalhandler: "npm:1.3.1" - fresh: "npm:0.5.2" - http-errors: "npm:2.0.0" + finalhandler: "npm:~1.3.1" + fresh: "npm:~0.5.2" + http-errors: "npm:~2.0.0" merge-descriptors: "npm:1.0.3" methods: "npm:~1.1.2" - on-finished: "npm:2.4.1" + on-finished: "npm:~2.4.1" parseurl: "npm:~1.3.3" - path-to-regexp: "npm:0.1.10" + path-to-regexp: "npm:~0.1.12" proxy-addr: "npm:~2.0.7" - qs: "npm:6.13.0" + qs: "npm:~6.14.0" range-parser: "npm:~1.2.1" safe-buffer: "npm:5.2.1" - send: "npm:0.19.0" - serve-static: "npm:1.16.2" + send: "npm:~0.19.0" + serve-static: "npm:~1.16.2" setprototypeof: "npm:1.2.0" - statuses: "npm:2.0.1" + statuses: "npm:~2.0.1" type-is: "npm:~1.6.18" utils-merge: "npm:1.0.1" vary: "npm:~1.1.2" - checksum: 10/3b1ee5bc5b1bd996f688702519cebc9b63a24e506965f6e1773268238cfa2c24ffdb38cc3fcb4fde66f77de1c0bebd9ee058dad06bb9c6f084b525f3c09164d3 + checksum: 10/f33c1bd0c7d36e2a1f18de9cdc176469d32f68e20258d2941b8d296ab9a4fd9011872c246391bf87714f009fac5114c832ec5ac65cbee39421f1258801eb8470 languageName: node linkType: hard @@ -6636,15 +7489,6 @@ __metadata: languageName: node linkType: hard -"fast-url-parser@npm:1.1.3": - version: 1.1.3 - resolution: "fast-url-parser@npm:1.1.3" - dependencies: - punycode: "npm:^1.3.2" - checksum: 10/6d33f46ce9776f7f3017576926207a950ca39bc5eb78fc794404f2288fe494720f9a119084b75569bd9eb09d2b46678bfaf39c191fb2c808ef3c833dc8982752 - languageName: node - linkType: hard - "fastq@npm:^1.6.0": version: 1.14.0 resolution: "fastq@npm:1.14.0" @@ -6681,6 +7525,15 @@ __metadata: languageName: node linkType: hard +"figures@npm:^3.2.0": + version: 3.2.0 + resolution: "figures@npm:3.2.0" + dependencies: + escape-string-regexp: "npm:^1.0.5" + checksum: 10/a3bf94e001be51d3770500789157f067218d4bc681a65e1f69d482de15120bcac822dceb1a7b3803f32e4e3a61a46df44f7f2c8ba95d6375e7491502e0dd3d97 + languageName: node + linkType: hard + "file-loader@npm:^6.2.0": version: 6.2.0 resolution: "file-loader@npm:6.2.0" @@ -6707,13 +7560,6 @@ __metadata: languageName: node linkType: hard -"filesize@npm:^8.0.6": - version: 8.0.7 - resolution: "filesize@npm:8.0.7" - checksum: 10/e35f1799c314cef49a585af82fe2d15b362f743a74c95f06e3dd99cf0334ca45516ed144f6a58649ca0e2e5e63844c0ef476d9374d5d43736d26f7c13aa49dad - languageName: node - linkType: hard - "fill-range@npm:^7.1.1": version: 7.1.1 resolution: "fill-range@npm:7.1.1" @@ -6723,18 +7569,18 @@ __metadata: languageName: node linkType: hard -"finalhandler@npm:1.3.1": - version: 1.3.1 - resolution: "finalhandler@npm:1.3.1" +"finalhandler@npm:~1.3.1": + version: 1.3.2 + resolution: "finalhandler@npm:1.3.2" dependencies: debug: "npm:2.6.9" encodeurl: "npm:~2.0.0" escape-html: "npm:~1.0.3" - on-finished: "npm:2.4.1" + on-finished: "npm:~2.4.1" parseurl: "npm:~1.3.3" - statuses: "npm:2.0.1" + statuses: "npm:~2.0.2" unpipe: "npm:~1.0.0" - checksum: 10/4babe72969b7373b5842bc9f75c3a641a4d0f8eb53af6b89fa714d4460ce03fb92b28de751d12ba415e96e7e02870c436d67412120555e2b382640535697305b + checksum: 10/6cb4f9f80eaeb5a0fac4fdbd27a65d39271f040a0034df16556d896bfd855fd42f09da886781b3102117ea8fceba97b903c1f8b08df1fb5740576d5e0f481eed languageName: node linkType: hard @@ -6748,25 +7594,6 @@ __metadata: languageName: node linkType: hard -"find-up@npm:^3.0.0": - version: 3.0.0 - resolution: "find-up@npm:3.0.0" - dependencies: - locate-path: "npm:^3.0.0" - checksum: 10/38eba3fe7a66e4bc7f0f5a1366dc25508b7cfc349f852640e3678d26ad9a6d7e2c43eff0a472287de4a9753ef58f066a0ea892a256fa3636ad51b3fe1e17fae9 - languageName: node - linkType: hard - -"find-up@npm:^5.0.0": - version: 5.0.0 - resolution: "find-up@npm:5.0.0" - dependencies: - locate-path: "npm:^6.0.0" - path-exists: "npm:^4.0.0" - checksum: 10/07955e357348f34660bde7920783204ff5a26ac2cafcaa28bace494027158a97b9f56faaf2d89a6106211a8174db650dd9f503f9c0d526b1202d5554a00b9095 - languageName: node - linkType: hard - "find-up@npm:^6.3.0": version: 6.3.0 resolution: "find-up@npm:6.3.0" @@ -6777,6 +7604,15 @@ __metadata: languageName: node linkType: hard +"flat@npm:^5.0.2": + version: 5.0.2 + resolution: "flat@npm:5.0.2" + bin: + flat: cli.js + checksum: 10/72479e651c15eab53e25ce04c31bab18cfaac0556505cac19221dbbe85bbb9686bc76e4d397e89e5bf516ce667dcf818f8b07e585568edba55abc2bf1f698fb5 + languageName: node + linkType: hard + "follow-redirects@npm:^1.0.0": version: 1.15.6 resolution: "follow-redirects@npm:1.15.6" @@ -6787,15 +7623,6 @@ __metadata: languageName: node linkType: hard -"for-each@npm:^0.3.3": - version: 0.3.4 - resolution: "for-each@npm:0.3.4" - dependencies: - is-callable: "npm:^1.2.7" - checksum: 10/c3bc4ebe8bd51655919dd9132c7ad0703c267bd0d737093e8424f46feea2eeaa73ecc54237346435258548d07aaeac643deb47de9b872c359e0c37cf0507a7f1 - languageName: node - linkType: hard - "foreach@npm:^2.0.4": version: 2.0.6 resolution: "foreach@npm:2.0.6" @@ -6813,37 +7640,6 @@ __metadata: languageName: node linkType: hard -"fork-ts-checker-webpack-plugin@npm:^6.5.0": - version: 6.5.2 - resolution: "fork-ts-checker-webpack-plugin@npm:6.5.2" - dependencies: - "@babel/code-frame": "npm:^7.8.3" - "@types/json-schema": "npm:^7.0.5" - chalk: "npm:^4.1.0" - chokidar: "npm:^3.4.2" - cosmiconfig: "npm:^6.0.0" - deepmerge: "npm:^4.2.2" - fs-extra: "npm:^9.0.0" - glob: "npm:^7.1.6" - memfs: "npm:^3.1.2" - minimatch: "npm:^3.0.4" - schema-utils: "npm:2.7.0" - semver: "npm:^7.3.2" - tapable: "npm:^1.0.0" - peerDependencies: - eslint: ">= 6" - typescript: ">= 2.7" - vue-template-compiler: "*" - webpack: ">= 4" - peerDependenciesMeta: - eslint: - optional: true - vue-template-compiler: - optional: true - checksum: 10/4a7037d654c07eb4e881d0626fdfdfac22fe90531e1e203846be89d68e863d3f9fcfc004b9037669455bf461081c83091eddf6485a7b131e7e6706c8939eeb67 - languageName: node - linkType: hard - "form-data-encoder@npm:^2.1.2": version: 2.1.4 resolution: "form-data-encoder@npm:2.1.4" @@ -6865,40 +7661,28 @@ __metadata: languageName: node linkType: hard -"fraction.js@npm:^4.3.7": - version: 4.3.7 - resolution: "fraction.js@npm:4.3.7" - checksum: 10/bb5ebcdeeffcdc37b68ead3bdfc244e68de188e0c64e9702197333c72963b95cc798883ad16adc21588088b942bca5b6a6ff4aeb1362d19f6f3b629035dc15f5 +"fraction.js@npm:^5.3.4": + version: 5.3.4 + resolution: "fraction.js@npm:5.3.4" + checksum: 10/ef2c4bc81b2484065f8f7e4c2498f3fdfe6d233b8e7c7f75e3683ed10698536129b2c2dbd6c3f788ca4a020ec07116dd909a91036a364c98dc802b5003bfc613 languageName: node linkType: hard -"fresh@npm:0.5.2": +"fresh@npm:0.5.2, fresh@npm:~0.5.2": version: 0.5.2 resolution: "fresh@npm:0.5.2" checksum: 10/64c88e489b5d08e2f29664eb3c79c705ff9a8eb15d3e597198ef76546d4ade295897a44abb0abd2700e7ef784b2e3cbf1161e4fbf16f59129193fd1030d16da1 languageName: node linkType: hard -"fs-extra@npm:^11.1.1, fs-extra@npm:^11.2.0": - version: 11.2.0 - resolution: "fs-extra@npm:11.2.0" +"fs-extra@npm:^11.1.1, fs-extra@npm:^11.2.0, fs-extra@npm:^11.3.0": + version: 11.3.3 + resolution: "fs-extra@npm:11.3.3" dependencies: graceful-fs: "npm:^4.2.0" jsonfile: "npm:^6.0.1" universalify: "npm:^2.0.0" - checksum: 10/0579bf6726a4cd054d4aa308f10b483f52478bb16284f32cf60b4ce0542063d551fca1a08a2af365e35db21a3fa5a06cf2a6ed614004b4368982bc754cb816b3 - languageName: node - linkType: hard - -"fs-extra@npm:^9.0.0, fs-extra@npm:^9.0.1": - version: 9.1.0 - resolution: "fs-extra@npm:9.1.0" - dependencies: - at-least-node: "npm:^1.0.0" - graceful-fs: "npm:^4.2.0" - jsonfile: "npm:^6.0.1" - universalify: "npm:^2.0.0" - checksum: 10/08600da1b49552ed23dfac598c8fc909c66776dd130fea54fbcad22e330f7fcc13488bb995f6bc9ce5651aa35b65702faf616fe76370ee56f1aade55da982dca + checksum: 10/daeaefafbebe8fa6efd2fb96fc926f2c952be5877811f00a6794f0d64e0128e3d0d93368cd328f8f063b45deacf385c40e3d931aa46014245431cd2f4f89c67a languageName: node linkType: hard @@ -6911,13 +7695,6 @@ __metadata: languageName: node linkType: hard -"fs-monkey@npm:^1.0.3": - version: 1.0.3 - resolution: "fs-monkey@npm:1.0.3" - checksum: 10/af1abe305863956f5471fe41a4026da7607e866ee5f6c9a9ad6666b51eed102cbba08043eec708e15a1c78ced56bc33c72ee1ddf79720704791c77ed8f274a47 - languageName: node - linkType: hard - "fs.realpath@npm:^1.0.0": version: 1.0.0 resolution: "fs.realpath@npm:1.0.0" @@ -6944,7 +7721,7 @@ __metadata: languageName: node linkType: hard -"function-bind@npm:^1.1.1, function-bind@npm:^1.1.2": +"function-bind@npm:^1.1.2": version: 1.1.2 resolution: "function-bind@npm:1.1.2" checksum: 10/185e20d20f10c8d661d59aac0f3b63b31132d492e1b11fcc2a93cb2c47257ebaee7407c38513efd2b35cafdf972d9beb2ea4593c1e0f3bf8f2744836928d7454 @@ -7048,6 +7825,15 @@ __metadata: languageName: node linkType: hard +"glob-to-regex.js@npm:^1.0.0, glob-to-regex.js@npm:^1.0.1": + version: 1.2.0 + resolution: "glob-to-regex.js@npm:1.2.0" + peerDependencies: + tslib: 2 + checksum: 10/13034e642db479d75448bdd9f37de7451bef2879c394bfe3f8df6588e0479893e94059eaee77cdf50dce675607fb2395c132dcca0c9a559a6192e89b2ad0f134 + languageName: node + linkType: hard + "glob-to-regexp@npm:^0.4.1": version: 0.4.1 resolution: "glob-to-regexp@npm:0.4.1" @@ -7071,7 +7857,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^7.0.0, glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6": +"glob@npm:^7.0.0, glob@npm:^7.1.3, glob@npm:^7.1.4": version: 7.2.3 resolution: "glob@npm:7.2.3" dependencies: @@ -7107,34 +7893,7 @@ __metadata: languageName: node linkType: hard -"global-modules@npm:^2.0.0": - version: 2.0.0 - resolution: "global-modules@npm:2.0.0" - dependencies: - global-prefix: "npm:^3.0.0" - checksum: 10/4aee73adf533fe82ead2ad15c8bfb6ea4fb29e16d2d067521ab39d3b45b8f834d71c47a807e4f8f696e79497c3946d4ccdcd708da6f3a4522d65b087b8852f64 - languageName: node - linkType: hard - -"global-prefix@npm:^3.0.0": - version: 3.0.0 - resolution: "global-prefix@npm:3.0.0" - dependencies: - ini: "npm:^1.3.5" - kind-of: "npm:^6.0.2" - which: "npm:^1.3.1" - checksum: 10/a405b9f83c7d88a49dc1c1e458d6585e258356810d3d0f41094265152a06a0f393b14d911f45616e35a4ce3894176a73be2984883575e778f55e90bf812d7337 - languageName: node - linkType: hard - -"globals@npm:^11.1.0": - version: 11.12.0 - resolution: "globals@npm:11.12.0" - checksum: 10/9f054fa38ff8de8fa356502eb9d2dae0c928217b8b5c8de1f09f5c9b6c8a96d8b9bd3afc49acbcd384a98a81fea713c859e1b09e214c60509517bb8fc2bc13c2 - languageName: node - linkType: hard - -"globby@npm:^11.0.1, globby@npm:^11.0.4, globby@npm:^11.1.0": +"globby@npm:^11.1.0": version: 11.1.0 resolution: "globby@npm:11.1.0" dependencies: @@ -7238,13 +7997,6 @@ __metadata: languageName: node linkType: hard -"has-flag@npm:^3.0.0": - version: 3.0.0 - resolution: "has-flag@npm:3.0.0" - checksum: 10/4a15638b454bf086c8148979aae044dd6e39d63904cd452d970374fa6a87623423da485dfb814e7be882e05c096a7ccf1ebd48e7e7501d0208d8384ff4dea73b - languageName: node - linkType: hard - "has-flag@npm:^4.0.0": version: 4.0.0 resolution: "has-flag@npm:4.0.0" @@ -7261,22 +8013,13 @@ __metadata: languageName: node linkType: hard -"has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0": +"has-symbols@npm:^1.1.0": version: 1.1.0 resolution: "has-symbols@npm:1.1.0" checksum: 10/959385c98696ebbca51e7534e0dc723ada325efa3475350951363cce216d27373e0259b63edb599f72eb94d6cde8577b4b2375f080b303947e560f85692834fa languageName: node linkType: hard -"has-tostringtag@npm:^1.0.2": - version: 1.0.2 - resolution: "has-tostringtag@npm:1.0.2" - dependencies: - has-symbols: "npm:^1.0.3" - checksum: 10/c74c5f5ceee3c8a5b8bc37719840dc3749f5b0306d818974141dda2471a1a2ca6c8e46b9d6ac222c5345df7a901c9b6f350b1e6d62763fec877e26609a401bfe - languageName: node - linkType: hard - "has-unicode@npm:^2.0.1": version: 2.0.1 resolution: "has-unicode@npm:2.0.1" @@ -7291,55 +8034,6 @@ __metadata: languageName: node linkType: hard -"has@npm:^1.0.3": - version: 1.0.3 - resolution: "has@npm:1.0.3" - dependencies: - function-bind: "npm:^1.1.1" - checksum: 10/a449f3185b1d165026e8d25f6a8c3390bd25c201ff4b8c1aaf948fc6a5fcfd6507310b8c00c13a3325795ea9791fcc3d79d61eafa313b5750438fc19183df57b - languageName: node - linkType: hard - -"hash-base@npm:^2.0.0": - version: 2.0.2 - resolution: "hash-base@npm:2.0.2" - dependencies: - inherits: "npm:^2.0.1" - checksum: 10/e39f3f2bb91679ed350bd2eb81035acb1e1e6e9bb86d9f1197fcfdc3cf39a2c56bf82a1870f000fae651477883b4c107fd6ac0c640a18ab06298b87c39939396 - languageName: node - linkType: hard - -"hash-base@npm:^3.0.0": - version: 3.1.0 - resolution: "hash-base@npm:3.1.0" - dependencies: - inherits: "npm:^2.0.4" - readable-stream: "npm:^3.6.0" - safe-buffer: "npm:^5.2.0" - checksum: 10/26b7e97ac3de13cb23fc3145e7e3450b0530274a9562144fc2bf5c1e2983afd0e09ed7cc3b20974ba66039fad316db463da80eb452e7373e780cbee9a0d2f2dc - languageName: node - linkType: hard - -"hash-base@npm:~3.0, hash-base@npm:~3.0.4": - version: 3.0.5 - resolution: "hash-base@npm:3.0.5" - dependencies: - inherits: "npm:^2.0.4" - safe-buffer: "npm:^5.2.1" - checksum: 10/6a82675a5de2ea9347501bbe655a2334950c7ec972fd9810ae9529e06aeab8f7e8ef68fc2112e5e6f0745561a7e05326efca42ad59bb5fd116537f5f8b0a216d - languageName: node - linkType: hard - -"hash.js@npm:^1.0.0, hash.js@npm:^1.0.3": - version: 1.1.7 - resolution: "hash.js@npm:1.1.7" - dependencies: - inherits: "npm:^2.0.3" - minimalistic-assert: "npm:^1.0.1" - checksum: 10/0c89ee4006606a40f92df5cc3c263342e7fea68110f3e9ef032bd2083650430505db01b6b7926953489517d4027535e4fdc7f970412893d3031c361d3ec8f4b3 - languageName: node - linkType: hard - "hasown@npm:^2.0.2": version: 2.0.2 resolution: "hasown@npm:2.0.2" @@ -7349,21 +8043,6 @@ __metadata: languageName: node linkType: hard -"hast-util-from-parse5@npm:^7.0.0": - version: 7.1.2 - resolution: "hast-util-from-parse5@npm:7.1.2" - dependencies: - "@types/hast": "npm:^2.0.0" - "@types/unist": "npm:^2.0.0" - hastscript: "npm:^7.0.0" - property-information: "npm:^6.0.0" - vfile: "npm:^5.0.0" - vfile-location: "npm:^4.0.0" - web-namespaces: "npm:^2.0.0" - checksum: 10/7a90a16430a1482ed1be5c2f8b182e8b12aee8834781245b101700b5a04cea8b569cf40ef08214e1eb333249432e861b17e6fe46d0447b5281827c8798e86f1a - languageName: node - linkType: hard - "hast-util-from-parse5@npm:^8.0.0": version: 8.0.1 resolution: "hast-util-from-parse5@npm:8.0.1" @@ -7380,15 +8059,6 @@ __metadata: languageName: node linkType: hard -"hast-util-parse-selector@npm:^3.0.0": - version: 3.1.1 - resolution: "hast-util-parse-selector@npm:3.1.1" - dependencies: - "@types/hast": "npm:^2.0.0" - checksum: 10/511d373465f60dd65e924f88bf0954085f4fb6e3a2b062a4b5ac43b93cbfd36a8dce6234b5d1e3e63499d936375687e83fc5da55628b22bd6b581b5ee167d1c4 - languageName: node - linkType: hard - "hast-util-parse-selector@npm:^4.0.0": version: 4.0.0 resolution: "hast-util-parse-selector@npm:4.0.0" @@ -7398,25 +8068,6 @@ __metadata: languageName: node linkType: hard -"hast-util-raw@npm:^7.2.0": - version: 7.2.3 - resolution: "hast-util-raw@npm:7.2.3" - dependencies: - "@types/hast": "npm:^2.0.0" - "@types/parse5": "npm:^6.0.0" - hast-util-from-parse5: "npm:^7.0.0" - hast-util-to-parse5: "npm:^7.0.0" - html-void-elements: "npm:^2.0.0" - parse5: "npm:^6.0.0" - unist-util-position: "npm:^4.0.0" - unist-util-visit: "npm:^4.0.0" - vfile: "npm:^5.0.0" - web-namespaces: "npm:^2.0.0" - zwitch: "npm:^2.0.0" - checksum: 10/fe39d4b9e68de7131ec61e9abe887cc0579dc7491f738735150c6021565fc998e37c9d096e97fc35c769e986e04b721d4724835ee82fcc22076d778acf6c4832 - languageName: node - linkType: hard - "hast-util-raw@npm:^9.0.0": version: 9.0.2 resolution: "hast-util-raw@npm:9.0.2" @@ -7485,20 +8136,6 @@ __metadata: languageName: node linkType: hard -"hast-util-to-parse5@npm:^7.0.0": - version: 7.1.0 - resolution: "hast-util-to-parse5@npm:7.1.0" - dependencies: - "@types/hast": "npm:^2.0.0" - comma-separated-tokens: "npm:^2.0.0" - property-information: "npm:^6.0.0" - space-separated-tokens: "npm:^2.0.0" - web-namespaces: "npm:^2.0.0" - zwitch: "npm:^2.0.0" - checksum: 10/695539881431f9713ca4a0be7d06bf3e57ae4d9f930eccba371534c50cff11855d345f8ec30099d04482637ad82e3c70d480269bfa4c109f37993536e8ea690d - languageName: node - linkType: hard - "hast-util-to-parse5@npm:^8.0.0": version: 8.0.0 resolution: "hast-util-to-parse5@npm:8.0.0" @@ -7514,13 +8151,6 @@ __metadata: languageName: node linkType: hard -"hast-util-whitespace@npm:^2.0.0": - version: 2.0.1 - resolution: "hast-util-whitespace@npm:2.0.1" - checksum: 10/ad5a61f4e81330413d4182247e158d77408a076994fbe7257574ea6489728bb4138c83e00482051c941973d4ed3049729afb35600debfc6d1d945c40453685f7 - languageName: node - linkType: hard - "hast-util-whitespace@npm:^3.0.0": version: 3.0.0 resolution: "hast-util-whitespace@npm:3.0.0" @@ -7530,19 +8160,6 @@ __metadata: languageName: node linkType: hard -"hastscript@npm:^7.0.0": - version: 7.2.0 - resolution: "hastscript@npm:7.2.0" - dependencies: - "@types/hast": "npm:^2.0.0" - comma-separated-tokens: "npm:^2.0.0" - hast-util-parse-selector: "npm:^3.0.0" - property-information: "npm:^6.0.0" - space-separated-tokens: "npm:^2.0.0" - checksum: 10/98740e0b69b4765a23d0174fb93eb1c1bdcae6a9f1c9e1b07de6aca2d578427a42e1d45ee98eda26463ac58ff73a8ce45af19c4eb8b5f6f768a9c8543964d28f - languageName: node - linkType: hard - "hastscript@npm:^8.0.0": version: 8.0.0 resolution: "hastscript@npm:8.0.0" @@ -7579,18 +8196,7 @@ __metadata: languageName: node linkType: hard -"hmac-drbg@npm:^1.0.1": - version: 1.0.1 - resolution: "hmac-drbg@npm:1.0.1" - dependencies: - hash.js: "npm:^1.0.3" - minimalistic-assert: "npm:^1.0.0" - minimalistic-crypto-utils: "npm:^1.0.1" - checksum: 10/0298a1445b8029a69b713d918ecaa84a1d9f614f5857e0c6e1ca517abfa1357216987b2ee08cc6cc73ba82a6c6ddf2ff11b9717a653530ef03be599d4699b836 - languageName: node - linkType: hard - -"hoist-non-react-statics@npm:^3.1.0, hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.2": +"hoist-non-react-statics@npm:^3.1.0": version: 3.3.2 resolution: "hoist-non-react-statics@npm:3.3.2" dependencies: @@ -7611,10 +8217,10 @@ __metadata: languageName: node linkType: hard -"html-entities@npm:^2.3.2": - version: 2.3.3 - resolution: "html-entities@npm:2.3.3" - checksum: 10/24f6b77ce234e263f3d44530de2356e67c313c8ba7e5f6e02c16dcea3a950711d8820afb320746d57b8dae61fde7aaaa7f60017b706fa4bce8624ba3c29ad316 +"html-escaper@npm:^2.0.2": + version: 2.0.2 + resolution: "html-escaper@npm:2.0.2" + checksum: 10/034d74029dcca544a34fb6135e98d427acd73019796ffc17383eaa3ec2fe1c0471dcbbc8f8ed39e46e86d43ccd753a160631615e4048285e313569609b66d5b7 languageName: node linkType: hard @@ -7659,10 +8265,10 @@ __metadata: languageName: node linkType: hard -"html-void-elements@npm:^2.0.0": - version: 2.0.1 - resolution: "html-void-elements@npm:2.0.1" - checksum: 10/06d41f13b9d5d6e0f39861c4bec9a9196fa4906d56cd5cf6cf54ad2e52a85bf960cca2bf9600026bde16c8331db171bedba5e5a35e2e43630c8f1d497b2fb658 +"html-url-attributes@npm:^3.0.0": + version: 3.0.1 + resolution: "html-url-attributes@npm:3.0.1" + checksum: 10/494074c2f730c5c0e517aa1b10111fb36732534a2d2b70427582c4a615472b47da472cf3a17562cc653826d378d20960f2783e0400f4f7cf0c3c2d91c6188d13 languageName: node linkType: hard @@ -7673,9 +8279,9 @@ __metadata: languageName: node linkType: hard -"html-webpack-plugin@npm:^5.5.3": - version: 5.5.3 - resolution: "html-webpack-plugin@npm:5.5.3" +"html-webpack-plugin@npm:^5.6.0": + version: 5.6.6 + resolution: "html-webpack-plugin@npm:5.6.6" dependencies: "@types/html-minifier-terser": "npm:^6.0.0" html-minifier-terser: "npm:^6.0.2" @@ -7683,8 +8289,14 @@ __metadata: pretty-error: "npm:^4.0.0" tapable: "npm:^2.0.0" peerDependencies: + "@rspack/core": 0.x || 1.x webpack: ^5.20.0 - checksum: 10/01d302a434e3db9f0e2db370f06300fb613de0fb8bdcafd4693e44c2528b8608621e5e7ca5d8302446db3f20c5f8875f1f675926d469b13ebab139954d241055 + peerDependenciesMeta: + "@rspack/core": + optional: true + webpack: + optional: true + checksum: 10/819ebee079466029a771236fdadcbcfe0aaf110eac1b74c0983a0318a7f99f3c69adcf1d617e218769a8c8e96ea17de2df30759bd950ec391d0c5676d480894e languageName: node linkType: hard @@ -7751,6 +8363,19 @@ __metadata: languageName: node linkType: hard +"http-errors@npm:~2.0.0, http-errors@npm:~2.0.1": + version: 2.0.1 + resolution: "http-errors@npm:2.0.1" + dependencies: + depd: "npm:~2.0.0" + inherits: "npm:~2.0.4" + setprototypeof: "npm:~1.2.0" + statuses: "npm:~2.0.2" + toidentifier: "npm:~1.0.1" + checksum: 10/9fe31bc0edf36566c87048aed1d3d0cbe03552564adc3541626a0613f542d753fbcb13bdfcec0a3a530dbe1714bb566c89d46244616b66bddd26ac413b06a207 + languageName: node + linkType: hard + "http-parser-js@npm:>=0.5.1": version: 0.5.8 resolution: "http-parser-js@npm:0.5.8" @@ -7769,7 +8394,7 @@ __metadata: languageName: node linkType: hard -"http-proxy-middleware@npm:^2.0.3": +"http-proxy-middleware@npm:^2.0.9": version: 2.0.9 resolution: "http-proxy-middleware@npm:2.0.9" dependencies: @@ -7822,13 +8447,6 @@ __metadata: languageName: node linkType: hard -"https-browserify@npm:^1.0.0": - version: 1.0.0 - resolution: "https-browserify@npm:1.0.0" - checksum: 10/2d707c457319e1320adf0e7556174c190865fb345b6a183f033cee440f73221dbe7fa3f0adcffb1e6b0664726256bd44771a82e50fe6c66976c10b237100536a - languageName: node - linkType: hard - "https-proxy-agent@npm:^5.0.0": version: 5.0.1 resolution: "https-proxy-agent@npm:5.0.1" @@ -7839,7 +8457,7 @@ __metadata: languageName: node linkType: hard -"https-proxy-agent@npm:^7.0.4": +"https-proxy-agent@npm:^7.0.5": version: 7.0.6 resolution: "https-proxy-agent@npm:7.0.6" dependencies: @@ -7865,12 +8483,10 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:0.4.24": - version: 0.4.24 - resolution: "iconv-lite@npm:0.4.24" - dependencies: - safer-buffer: "npm:>= 2.1.2 < 3" - checksum: 10/6d3a2dac6e5d1fb126d25645c25c3a1209f70cceecc68b8ef51ae0da3cdc078c151fade7524a30b12a3094926336831fca09c666ef55b37e2c69638b5d6bd2e3 +"hyperdyperid@npm:^1.2.0": + version: 1.2.0 + resolution: "hyperdyperid@npm:1.2.0" + checksum: 10/64abb5568ff17aa08ac0175ae55e46e22831c5552be98acdd1692081db0209f36fff58b31432017b4e1772c178962676a2cc3c54e4d5d7f020d7710cec7ad7a6 languageName: node linkType: hard @@ -7883,6 +8499,15 @@ __metadata: languageName: node linkType: hard +"iconv-lite@npm:~0.4.24": + version: 0.4.24 + resolution: "iconv-lite@npm:0.4.24" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3" + checksum: 10/6d3a2dac6e5d1fb126d25645c25c3a1209f70cceecc68b8ef51ae0da3cdc078c151fade7524a30b12a3094926336831fca09c666ef55b37e2c69638b5d6bd2e3 + languageName: node + linkType: hard + "icss-utils@npm:^5.0.0, icss-utils@npm:^5.1.0": version: 5.1.0 resolution: "icss-utils@npm:5.1.0" @@ -7906,32 +8531,30 @@ __metadata: languageName: node linkType: hard -"image-size@npm:^1.0.2": - version: 1.0.2 - resolution: "image-size@npm:1.0.2" - dependencies: - queue: "npm:6.0.2" +"image-size@npm:^2.0.2": + version: 2.0.2 + resolution: "image-size@npm:2.0.2" bin: image-size: bin/image-size.js - checksum: 10/693dfb2f8bfda2aacd087ef7130fd997fd0aceca838291bae400646db1826b80108185d0062ea3c0365b12c3ab5145bb923fdc777fd94c4991840d47fe44ade3 + checksum: 10/d15203279fe7ada01252d8c56ba97516385d6d5ac2cbf3d734580fc88db4f5272b9b3f7f378ad63abc7d06b5500c43b90d9f84626e2bda1cab403c16eb469592 languageName: node linkType: hard -"immer@npm:^9.0.21, immer@npm:^9.0.7": - version: 9.0.21 - resolution: "immer@npm:9.0.21" - checksum: 10/8455d6b4dc8abfe40f06eeec9bcc944d147c81279424c0f927a4d4905ae34e5af19ab6da60bcc700c14f51c452867d7089b3b9236f5a9a2248e39b4a09ee89de +"immer@npm:^11.0.0": + version: 11.1.3 + resolution: "immer@npm:11.1.3" + checksum: 10/d0c591381d8022fcad868d36f087635d5e5cddfd7f43c069bc0d2fee946ddc9969cd5f886e76a4ef0a36f2ce81b5e0228177e2cb4d85b3215aca40e6cb53ad2a languageName: node linkType: hard -"immutable@npm:^5.0.2": - version: 5.0.2 - resolution: "immutable@npm:5.0.2" - checksum: 10/89b1117c610024b7a9214eade8b9f1ed38b00c82235f119515cfa5eaf26270eccbc803296d4c3c12f53e50802f042f84d811998910b866363913720da768472e +"immutable@npm:^5.1.5": + version: 5.1.5 + resolution: "immutable@npm:5.1.5" + checksum: 10/7aec2740239772ec8e92e793c991bd809203a97694f4ff3a18e50e28f9a6b02393ad033d87b458037bdf8c0ea37d4446d640e825f6171df3405cf6cf300ce028 languageName: node linkType: hard -"import-fresh@npm:^3.1.0, import-fresh@npm:^3.3.0": +"import-fresh@npm:^3.3.0": version: 3.3.0 resolution: "import-fresh@npm:3.3.0" dependencies: @@ -7969,10 +8592,10 @@ __metadata: languageName: node linkType: hard -"infima@npm:0.2.0-alpha.44": - version: 0.2.0-alpha.44 - resolution: "infima@npm:0.2.0-alpha.44" - checksum: 10/a4d724ca23a67229ce61b6f73a4a394ff93a15bd9f141b2941e6dfc032f112ee49362c10ece388c189e53895cd5a8e264671184e097cc48aab90cd7d0fe41646 +"infima@npm:0.2.0-alpha.45": + version: 0.2.0-alpha.45 + resolution: "infima@npm:0.2.0-alpha.45" + checksum: 10/5e620f52d4787a0d4f96fd428411138ec09042d2a7e9adc7fc38612a9c57e49dd485ccc4f35bbbcd07f66e63bb2f6fbb6dde35a8351e9a978a7e4e1ebb7f0af0 languageName: node linkType: hard @@ -7986,7 +8609,7 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3, inherits@npm:~2.0.4": +"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:~2.0.3, inherits@npm:~2.0.4": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 10/cd45e923bee15186c07fa4c89db0aace24824c482fb887b528304694b2aa6ff8a898da8657046a5dcf3e46cd6db6c61629551f9215f208d7c3f157cf9b290521 @@ -8007,7 +8630,7 @@ __metadata: languageName: node linkType: hard -"ini@npm:^1.3.4, ini@npm:^1.3.5, ini@npm:~1.3.0": +"ini@npm:^1.3.4, ini@npm:~1.3.0": version: 1.3.8 resolution: "ini@npm:1.3.8" checksum: 10/314ae176e8d4deb3def56106da8002b462221c174ddb7ce0c49ee72c8cd1f9044f7b10cc555a7d8850982c3b9ca96fc212122749f5234bc2b6fb05fb942ed566 @@ -8058,10 +8681,10 @@ __metadata: languageName: node linkType: hard -"ipaddr.js@npm:^2.0.1": - version: 2.0.1 - resolution: "ipaddr.js@npm:2.0.1" - checksum: 10/b809f60af0473f1452480b05a2cec8270284290d18d2778df522d08e0b6d0db21b84f5bf4949190f3c728794d3eef36bfaeff14a1e1acf6045553f4532b119de +"ipaddr.js@npm:^2.1.0": + version: 2.3.0 + resolution: "ipaddr.js@npm:2.3.0" + checksum: 10/be3d01bc2e20fc2dc5349b489ea40883954b816ce3e57aa48ad943d4e7c4ace501f28a7a15bde4b96b6b97d0fbb28d599ff2f87399f3cda7bd728889402eed3b languageName: node linkType: hard @@ -8082,16 +8705,6 @@ __metadata: languageName: node linkType: hard -"is-arguments@npm:^1.0.4": - version: 1.2.0 - resolution: "is-arguments@npm:1.2.0" - dependencies: - call-bound: "npm:^1.0.2" - has-tostringtag: "npm:^1.0.2" - checksum: 10/471a8ef631b8ee8829c43a8ab05c081700c0e25180c73d19f3bf819c1a8448c426a9e8e601f278973eca68966384b16ceb78b8c63af795b099cd199ea5afc457 - languageName: node - linkType: hard - "is-arrayish@npm:^0.2.1": version: 0.2.1 resolution: "is-arrayish@npm:0.2.1" @@ -8108,20 +8721,6 @@ __metadata: languageName: node linkType: hard -"is-buffer@npm:^2.0.0": - version: 2.0.5 - resolution: "is-buffer@npm:2.0.5" - checksum: 10/3261a8b858edcc6c9566ba1694bf829e126faa88911d1c0a747ea658c5d81b14b6955e3a702d59dabadd58fdd440c01f321aa71d6547105fd21d03f94d0597e7 - languageName: node - linkType: hard - -"is-callable@npm:^1.2.7": - version: 1.2.7 - resolution: "is-callable@npm:1.2.7" - checksum: 10/48a9297fb92c99e9df48706241a189da362bff3003354aea4048bd5f7b2eb0d823cd16d0a383cece3d76166ba16d85d9659165ac6fcce1ac12e6c649d66dbdb9 - languageName: node - linkType: hard - "is-ci@npm:^3.0.1": version: 3.0.1 resolution: "is-ci@npm:3.0.1" @@ -8133,12 +8732,12 @@ __metadata: languageName: node linkType: hard -"is-core-module@npm:^2.9.0": - version: 2.11.0 - resolution: "is-core-module@npm:2.11.0" +"is-core-module@npm:^2.16.1": + version: 2.16.1 + resolution: "is-core-module@npm:2.16.1" dependencies: - has: "npm:^1.0.3" - checksum: 10/9b09ce78f1f281e20c596023e8464d51dfc93b5933bf23f00c002eafbebdaa766726be42bacfb4459c4cfe14569f0987db11fe6bc30d6e57985c9071a289966e + hasown: "npm:^2.0.2" + checksum: 10/452b2c2fb7f889cbbf7e54609ef92cf6c24637c568acc7e63d166812a0fb365ae8a504c333a29add8bdb1686704068caa7f4e4b639b650dde4f00a038b8941fb languageName: node linkType: hard @@ -8158,6 +8757,15 @@ __metadata: languageName: node linkType: hard +"is-docker@npm:^3.0.0": + version: 3.0.0 + resolution: "is-docker@npm:3.0.0" + bin: + is-docker: cli.js + checksum: 10/b698118f04feb7eaf3338922bd79cba064ea54a1c3db6ec8c0c8d8ee7613e7e5854d802d3ef646812a8a3ace81182a085dfa0a71cc68b06f3fa794b9783b3c90 + languageName: node + linkType: hard + "is-extendable@npm:^0.1.0": version: 0.1.1 resolution: "is-extendable@npm:0.1.1" @@ -8179,18 +8787,6 @@ __metadata: languageName: node linkType: hard -"is-generator-function@npm:^1.0.7": - version: 1.1.0 - resolution: "is-generator-function@npm:1.1.0" - dependencies: - call-bound: "npm:^1.0.3" - get-proto: "npm:^1.0.0" - has-tostringtag: "npm:^1.0.2" - safe-regex-test: "npm:^1.1.0" - checksum: 10/5906ff51a856a5fbc6b90a90fce32040b0a6870da905f98818f1350f9acadfc9884f7c3dec833fce04b83dd883937b86a190b6593ede82e8b1af8b6c4ecf7cbd - languageName: node - linkType: hard - "is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1": version: 4.0.3 resolution: "is-glob@npm:4.0.3" @@ -8207,6 +8803,17 @@ __metadata: languageName: node linkType: hard +"is-inside-container@npm:^1.0.0": + version: 1.0.0 + resolution: "is-inside-container@npm:1.0.0" + dependencies: + is-docker: "npm:^3.0.0" + bin: + is-inside-container: cli.js + checksum: 10/c50b75a2ab66ab3e8b92b3bc534e1ea72ca25766832c0623ac22d134116a98bcf012197d1caabe1d1c4bd5f84363d4aa5c36bb4b585fbcaf57be172cd10a1a03 + languageName: node + linkType: hard + "is-installed-globally@npm:^0.4.0": version: 0.4.0 resolution: "is-installed-globally@npm:0.4.0" @@ -8224,13 +8831,10 @@ __metadata: languageName: node linkType: hard -"is-nan@npm:^1.3.2": - version: 1.3.2 - resolution: "is-nan@npm:1.3.2" - dependencies: - call-bind: "npm:^1.0.0" - define-properties: "npm:^1.1.3" - checksum: 10/1f784d3472c09bc2e47acba7ffd4f6c93b0394479aa613311dc1d70f1bfa72eb0846c81350967722c959ba65811bae222204d6c65856fdce68f31986140c7b0e +"is-network-error@npm:^1.0.0": + version: 1.3.0 + resolution: "is-network-error@npm:1.3.0" + checksum: 10/56dc0b8ed9c0bb72202058f172ad0c3121cf68772e8cbba343d3775f6e2ec7877d423cbcea45f4cedcd345de8693de1b52dfe0c6fc15d652c4aa98c2abf0185a languageName: node linkType: hard @@ -8262,13 +8866,6 @@ __metadata: languageName: node linkType: hard -"is-path-cwd@npm:^2.2.0": - version: 2.2.0 - resolution: "is-path-cwd@npm:2.2.0" - checksum: 10/46a840921bb8cc0dc7b5b423a14220e7db338072a4495743a8230533ce78812dc152548c86f4b828411fe98c5451959f07cf841c6a19f611e46600bd699e8048 - languageName: node - linkType: hard - "is-path-inside@npm:^3.0.2": version: 3.0.3 resolution: "is-path-inside@npm:3.0.3" @@ -8299,13 +8896,6 @@ __metadata: languageName: node linkType: hard -"is-plain-object@npm:^5.0.0": - version: 5.0.0 - resolution: "is-plain-object@npm:5.0.0" - checksum: 10/e32d27061eef62c0847d303125440a38660517e586f2f3db7c9d179ae5b6674ab0f469d519b2e25c147a1a3bc87156d0d5f4d8821e0ce4a9ee7fe1fcf11ce45c - languageName: node - linkType: hard - "is-reference@npm:^3.0.0": version: 3.0.1 resolution: "is-reference@npm:3.0.1" @@ -8315,18 +8905,6 @@ __metadata: languageName: node linkType: hard -"is-regex@npm:^1.2.1": - version: 1.2.1 - resolution: "is-regex@npm:1.2.1" - dependencies: - call-bound: "npm:^1.0.2" - gopd: "npm:^1.2.0" - has-tostringtag: "npm:^1.0.2" - hasown: "npm:^2.0.2" - checksum: 10/c42b7efc5868a5c9a4d8e6d3e9816e8815c611b09535c00fead18a1138455c5cb5e1887f0023a467ad3f9c419d62ba4dc3d9ba8bafe55053914d6d6454a945d2 - languageName: node - linkType: hard - "is-regexp@npm:^1.0.0": version: 1.0.0 resolution: "is-regexp@npm:1.0.0" @@ -8334,13 +8912,6 @@ __metadata: languageName: node linkType: hard -"is-root@npm:^2.1.0": - version: 2.1.0 - resolution: "is-root@npm:2.1.0" - checksum: 10/37eea0822a2a9123feb58a9d101558ba276771a6d830f87005683349a9acff15958a9ca590a44e778c6b335660b83e85c744789080d734f6081a935a4880aee2 - languageName: node - linkType: hard - "is-stream@npm:^2.0.0": version: 2.0.1 resolution: "is-stream@npm:2.0.1" @@ -8348,15 +8919,6 @@ __metadata: languageName: node linkType: hard -"is-typed-array@npm:^1.1.14, is-typed-array@npm:^1.1.3": - version: 1.1.15 - resolution: "is-typed-array@npm:1.1.15" - dependencies: - which-typed-array: "npm:^1.1.16" - checksum: 10/e8cf60b9ea85667097a6ad68c209c9722cfe8c8edf04d6218366469e51944c5cc25bae45ffb845c23f811d262e4314d3b0168748eb16711aa34d12724cdf0735 - languageName: node - linkType: hard - "is-typedarray@npm:^1.0.0": version: 1.0.0 resolution: "is-typedarray@npm:1.0.0" @@ -8373,6 +8935,15 @@ __metadata: languageName: node linkType: hard +"is-wsl@npm:^3.1.0": + version: 3.1.0 + resolution: "is-wsl@npm:3.1.0" + dependencies: + is-inside-container: "npm:^1.0.0" + checksum: 10/f9734c81f2f9cf9877c5db8356bfe1ff61680f1f4c1011e91278a9c0564b395ae796addb4bf33956871041476ec82c3e5260ed57b22ac91794d4ae70a1d2f0a9 + languageName: node + linkType: hard + "is-yarn-global@npm:^0.4.0": version: 0.4.1 resolution: "is-yarn-global@npm:0.4.1" @@ -8387,13 +8958,6 @@ __metadata: languageName: node linkType: hard -"isarray@npm:^2.0.5": - version: 2.0.5 - resolution: "isarray@npm:2.0.5" - checksum: 10/1d8bc7911e13bb9f105b1b3e0b396c787a9e63046af0b8fe0ab1414488ab06b2b099b87a2d8a9e31d21c9a6fad773c7fc8b257c4880f2d957274479d28ca3414 - languageName: node - linkType: hard - "isarray@npm:~1.0.0": version: 1.0.0 resolution: "isarray@npm:1.0.0" @@ -8472,7 +9036,7 @@ __metadata: languageName: node linkType: hard -"jiti@npm:^1.18.2, jiti@npm:^1.20.0": +"jiti@npm:^1.20.0": version: 1.21.0 resolution: "jiti@npm:1.21.0" bin: @@ -8508,7 +9072,7 @@ __metadata: languageName: node linkType: hard -"js-yaml@npm:4.1.0, js-yaml@npm:^4.1.0": +"js-yaml@npm:4.1.0": version: 4.1.0 resolution: "js-yaml@npm:4.1.0" dependencies: @@ -8520,32 +9084,34 @@ __metadata: linkType: hard "js-yaml@npm:^3.13.1": - version: 3.14.1 - resolution: "js-yaml@npm:3.14.1" + version: 3.14.2 + resolution: "js-yaml@npm:3.14.2" dependencies: argparse: "npm:^1.0.7" esprima: "npm:^4.0.0" bin: js-yaml: bin/js-yaml.js - checksum: 10/9e22d80b4d0105b9899135365f746d47466ed53ef4223c529b3c0f7a39907743fdbd3c4379f94f1106f02755b5e90b2faaf84801a891135544e1ea475d1a1379 + checksum: 10/172e0b6007b0bf0fc8d2469c94424f7dd765c64a047d2b790831fecef2204a4054eabf4d911eb73ab8c9a3256ab8ba1ee8d655b789bf24bf059c772acc2075a1 languageName: node linkType: hard -"jsesc@npm:^2.5.1": - version: 2.5.2 - resolution: "jsesc@npm:2.5.2" +"js-yaml@npm:^4.1.0, js-yaml@npm:^4.1.1": + version: 4.1.1 + resolution: "js-yaml@npm:4.1.1" + dependencies: + argparse: "npm:^2.0.1" bin: - jsesc: bin/jsesc - checksum: 10/d2096abdcdec56969764b40ffc91d4a23408aa2f351b4d1c13f736f25476643238c43fdbaf38a191c26b1b78fd856d965f5d4d0dde7b89459cd94025190cdf13 + js-yaml: bin/js-yaml.js + checksum: 10/a52d0519f0f4ef5b4adc1cde466cb54c50d56e2b4a983b9d5c9c0f2f99462047007a6274d7e95617a21d3c91fde3ee6115536ed70991cd645ba8521058b78f77 languageName: node linkType: hard -"jsesc@npm:~0.5.0": - version: 0.5.0 - resolution: "jsesc@npm:0.5.0" +"jsesc@npm:^3.0.2, jsesc@npm:~3.1.0": + version: 3.1.0 + resolution: "jsesc@npm:3.1.0" bin: jsesc: bin/jsesc - checksum: 10/fab949f585c71e169c5cbe00f049f20de74f067081bbd64a55443bad1c71e1b5a5b448f2359bf2fe06f5ed7c07e2e4a9101843b01c823c30b6afc11f5bfaf724 + checksum: 10/20bd37a142eca5d1794f354db8f1c9aeb54d85e1f5c247b371de05d23a9751ecd7bd3a9c4fc5298ea6fa09a100dafb4190fa5c98c6610b75952c3487f3ce7967 languageName: node linkType: hard @@ -8658,13 +9224,6 @@ __metadata: languageName: node linkType: hard -"kleur@npm:^4.0.3": - version: 4.1.5 - resolution: "kleur@npm:4.1.5" - checksum: 10/44d84cc4eedd4311099402ef6d4acd9b2d16e08e499d6ef3bb92389bd4692d7ef09e35248c26e27f98acac532122acb12a1bfee645994ae3af4f0a37996da7df - languageName: node - linkType: hard - "klona@npm:^2.0.4": version: 2.0.6 resolution: "klona@npm:2.0.6" @@ -8681,13 +9240,13 @@ __metadata: languageName: node linkType: hard -"launch-editor@npm:^2.6.0": - version: 2.6.0 - resolution: "launch-editor@npm:2.6.0" +"launch-editor@npm:^2.6.1": + version: 2.12.0 + resolution: "launch-editor@npm:2.12.0" dependencies: - picocolors: "npm:^1.0.0" - shell-quote: "npm:^1.7.3" - checksum: 10/48e4230643e8fdb5c14c11314706d58d9f3fbafe2606be3d6e37da1918ad8bfe39dd87875c726a1b59b9f4da99d87ec3e36d4c528464f0b820f9e91e5cb1c02d + picocolors: "npm:^1.1.1" + shell-quote: "npm:^1.8.3" + checksum: 10/43d2b66c674d129f9a96bbae602808a0afa7e6bb6f38de5518479e33b1a542e9772b262304505c2aa363b0185424580b4011a9198082d306e2b419c6f12da5e2 languageName: node linkType: hard @@ -8698,6 +9257,126 @@ __metadata: languageName: node linkType: hard +"lightningcss-android-arm64@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-android-arm64@npm:1.31.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-darwin-arm64@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-darwin-arm64@npm:1.31.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-darwin-x64@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-darwin-x64@npm:1.31.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-freebsd-x64@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-freebsd-x64@npm:1.31.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-linux-arm-gnueabihf@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-linux-arm-gnueabihf@npm:1.31.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"lightningcss-linux-arm64-gnu@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-linux-arm64-gnu@npm:1.31.1" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-arm64-musl@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-linux-arm64-musl@npm:1.31.1" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-linux-x64-gnu@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-linux-x64-gnu@npm:1.31.1" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-x64-musl@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-linux-x64-musl@npm:1.31.1" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-win32-arm64-msvc@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-win32-arm64-msvc@npm:1.31.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-win32-x64-msvc@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-win32-x64-msvc@npm:1.31.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"lightningcss@npm:^1.27.0": + version: 1.31.1 + resolution: "lightningcss@npm:1.31.1" + dependencies: + detect-libc: "npm:^2.0.3" + lightningcss-android-arm64: "npm:1.31.1" + lightningcss-darwin-arm64: "npm:1.31.1" + lightningcss-darwin-x64: "npm:1.31.1" + lightningcss-freebsd-x64: "npm:1.31.1" + lightningcss-linux-arm-gnueabihf: "npm:1.31.1" + lightningcss-linux-arm64-gnu: "npm:1.31.1" + lightningcss-linux-arm64-musl: "npm:1.31.1" + lightningcss-linux-x64-gnu: "npm:1.31.1" + lightningcss-linux-x64-musl: "npm:1.31.1" + lightningcss-win32-arm64-msvc: "npm:1.31.1" + lightningcss-win32-x64-msvc: "npm:1.31.1" + dependenciesMeta: + lightningcss-android-arm64: + optional: true + lightningcss-darwin-arm64: + optional: true + lightningcss-darwin-x64: + optional: true + lightningcss-freebsd-x64: + optional: true + lightningcss-linux-arm-gnueabihf: + optional: true + lightningcss-linux-arm64-gnu: + optional: true + lightningcss-linux-arm64-musl: + optional: true + lightningcss-linux-x64-gnu: + optional: true + lightningcss-linux-x64-musl: + optional: true + lightningcss-win32-arm64-msvc: + optional: true + lightningcss-win32-x64-msvc: + optional: true + checksum: 10/3c2b2c2f648b12d9cba623d2e558f74fcce35911077e3d33f97ed521e0ad7a84e2c814628f6e16f64095c4483f6b180dee7b2e441b3ff5f44d142a510785a0c6 + languageName: node + linkType: hard + "lilconfig@npm:^3.1.1": version: 3.1.2 resolution: "lilconfig@npm:3.1.2" @@ -8719,10 +9398,10 @@ __metadata: languageName: node linkType: hard -"loader-runner@npm:^4.2.0": - version: 4.3.0 - resolution: "loader-runner@npm:4.3.0" - checksum: 10/555ae002869c1e8942a0efd29a99b50a0ce6c3296efea95caf48f00d7f6f7f659203ed6613688b6181aa81dc76de3e65ece43094c6dffef3127fe1a84d973cd3 +"loader-runner@npm:^4.3.1": + version: 4.3.1 + resolution: "loader-runner@npm:4.3.1" + checksum: 10/d77127497c3f91fdba351e3e91156034e6e590e9f050b40df6c38ac16c54b5c903f7e2e141e09fefd046ee96b26fb50773c695ebc0aa205a4918683b124b04ba languageName: node linkType: hard @@ -8737,32 +9416,6 @@ __metadata: languageName: node linkType: hard -"loader-utils@npm:^3.2.0": - version: 3.2.1 - resolution: "loader-utils@npm:3.2.1" - checksum: 10/177f5bb9b4c651263714fcd1b50682c1367b06893462529f510287775f9e461ca27a41bf364c8dffa9cd74ed9e8b1fdb30c03a526f6bcf12573bdc1a1644d086 - languageName: node - linkType: hard - -"locate-path@npm:^3.0.0": - version: 3.0.0 - resolution: "locate-path@npm:3.0.0" - dependencies: - p-locate: "npm:^3.0.0" - path-exists: "npm:^3.0.0" - checksum: 10/53db3996672f21f8b0bf2a2c645ae2c13ffdae1eeecfcd399a583bce8516c0b88dcb4222ca6efbbbeb6949df7e46860895be2c02e8d3219abd373ace3bfb4e11 - languageName: node - linkType: hard - -"locate-path@npm:^6.0.0": - version: 6.0.0 - resolution: "locate-path@npm:6.0.0" - dependencies: - p-locate: "npm:^5.0.0" - checksum: 10/72eb661788a0368c099a184c59d2fee760b3831c9c1c33955e8a19ae4a21b4116e53fa736dc086cdeb9fce9f7cc508f2f92d2d3aae516f133e16a2bb59a39f5a - languageName: node - linkType: hard - "locate-path@npm:^7.1.0": version: 7.2.0 resolution: "locate-path@npm:7.2.0" @@ -8779,27 +9432,6 @@ __metadata: languageName: node linkType: hard -"lodash.escape@npm:^4.0.1": - version: 4.0.1 - resolution: "lodash.escape@npm:4.0.1" - checksum: 10/ba1effab9aea7e20ee69b26cbfeb41c73da2eb4d2ab1c261aaf53dd0902ce1afc2f0b34fb24bc69c1d2dd201c332e1d1eb696092fc844a2c5c8e7ccd1ca32014 - languageName: node - linkType: hard - -"lodash.flatten@npm:^4.4.0": - version: 4.4.0 - resolution: "lodash.flatten@npm:4.4.0" - checksum: 10/a2b192f220b0b6c78a6c0175e96bad888b9e0f2a887a8e8c1d0c29d03231fbf110bbb9be0d9de5f936537d143eeb9d5b4f44c4a44f5592c195bf2fae6a6b1e3a - languageName: node - linkType: hard - -"lodash.invokemap@npm:^4.6.0": - version: 4.6.0 - resolution: "lodash.invokemap@npm:4.6.0" - checksum: 10/70e629f78dc0e7aabfabf0ef575cc0b3d3b207699a5c91788bf5363d8f53764b80afd5c1985a98043ecc23095a145b271101626ed62dbb785ffcb22237b731c9 - languageName: node - linkType: hard - "lodash.memoize@npm:^4.1.2": version: 4.1.2 resolution: "lodash.memoize@npm:4.1.2" @@ -8807,13 +9439,6 @@ __metadata: languageName: node linkType: hard -"lodash.pullall@npm:^4.2.0": - version: 4.2.0 - resolution: "lodash.pullall@npm:4.2.0" - checksum: 10/ec2aa1a1eea37226ef7b69041779221ef6fdc6472589dd4d89cec2a0f3d067301a0274abc6a0522797f9958497c79d8bc754825f23ea21e0e053ef8bdfe742ad - languageName: node - linkType: hard - "lodash.uniq@npm:^4.5.0": version: 4.5.0 resolution: "lodash.uniq@npm:4.5.0" @@ -8821,13 +9446,6 @@ __metadata: languageName: node linkType: hard -"lodash.uniqby@npm:^4.7.0": - version: 4.7.0 - resolution: "lodash.uniqby@npm:4.7.0" - checksum: 10/256616bd1bd6be84d8a5eceb61338a0ab8d8b34314ba7bfd5f0de35227d0e2c1e659c61ff4ac31eba6a664085cc7e397bc34c3534fba208102db660a4f98f211 - languageName: node - linkType: hard - "lodash@npm:4.17.21, lodash@npm:^4.17.15, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.17.4": version: 4.17.21 resolution: "lodash@npm:4.17.21" @@ -8893,9 +9511,9 @@ __metadata: linkType: hard "luxon@npm:^3.0.0": - version: 3.5.0 - resolution: "luxon@npm:3.5.0" - checksum: 10/48f86e6c1c96815139f8559456a3354a276ba79bcef0ae0d4f2172f7652f3ba2be2237b0e103b8ea0b79b47715354ac9fac04eb1db3485dcc72d5110491dd47f + version: 3.7.2 + resolution: "luxon@npm:3.7.2" + checksum: 10/b24cd205ed306ce7415991687897dcc4027921ae413c9116590bc33a95f93b86ce52cf74ba72b4f5c5ab1c10090517f54ac8edfb127c049e0bf55b90dc2260be languageName: node linkType: hard @@ -8930,6 +9548,15 @@ __metadata: languageName: node linkType: hard +"markdown-table@npm:^2.0.0": + version: 2.0.0 + resolution: "markdown-table@npm:2.0.0" + dependencies: + repeat-string: "npm:^1.0.0" + checksum: 10/8018cd1a1733ffda916a0548438e50f3d21b6c6b71fb23696b33c0b5922a8cc46035eb4b204a59c6054f063076f934461ae094599656a63f87c1c3a80bd3c229 + languageName: node + linkType: hard + "markdown-table@npm:^3.0.0": version: 3.0.3 resolution: "markdown-table@npm:3.0.3" @@ -8944,28 +9571,6 @@ __metadata: languageName: node linkType: hard -"md5.js@npm:^1.3.4": - version: 1.3.5 - resolution: "md5.js@npm:1.3.5" - dependencies: - hash-base: "npm:^3.0.0" - inherits: "npm:^2.0.1" - safe-buffer: "npm:^5.1.2" - checksum: 10/098494d885684bcc4f92294b18ba61b7bd353c23147fbc4688c75b45cb8590f5a95fd4584d742415dcc52487f7a1ef6ea611cfa1543b0dc4492fe026357f3f0c - languageName: node - linkType: hard - -"mdast-util-definitions@npm:^5.0.0": - version: 5.1.2 - resolution: "mdast-util-definitions@npm:5.1.2" - dependencies: - "@types/mdast": "npm:^3.0.0" - "@types/unist": "npm:^2.0.0" - unist-util-visit: "npm:^4.0.0" - checksum: 10/4491b7c551ce1bdeb6c8fb1968cd461acb01ca1584f12c240755541a92d7f02bc5b9c9d6303d50deaed6d959ba58fe9a352a3e676e0f1d954e003de1277f57e4 - languageName: node - linkType: hard - "mdast-util-directive@npm:^3.0.0": version: 3.0.0 resolution: "mdast-util-directive@npm:3.0.0" @@ -8982,18 +9587,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-find-and-replace@npm:^2.0.0": - version: 2.2.2 - resolution: "mdast-util-find-and-replace@npm:2.2.2" - dependencies: - "@types/mdast": "npm:^3.0.0" - escape-string-regexp: "npm:^5.0.0" - unist-util-is: "npm:^5.0.0" - unist-util-visit-parents: "npm:^5.0.0" - checksum: 10/59e11e853b74d8f6083950327df39e27287b383930ff836298a5100aeda5568282bb45046c27886d2156ea101580bb0689b890c29623cefa5adc74e95d9ca9ff - languageName: node - linkType: hard - "mdast-util-find-and-replace@npm:^3.0.0, mdast-util-find-and-replace@npm:^3.0.1": version: 3.0.1 resolution: "mdast-util-find-and-replace@npm:3.0.1" @@ -9006,26 +9599,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-from-markdown@npm:^1.0.0": - version: 1.3.1 - resolution: "mdast-util-from-markdown@npm:1.3.1" - dependencies: - "@types/mdast": "npm:^3.0.0" - "@types/unist": "npm:^2.0.0" - decode-named-character-reference: "npm:^1.0.0" - mdast-util-to-string: "npm:^3.1.0" - micromark: "npm:^3.0.0" - micromark-util-decode-numeric-character-reference: "npm:^1.0.0" - micromark-util-decode-string: "npm:^1.0.0" - micromark-util-normalize-identifier: "npm:^1.0.0" - micromark-util-symbol: "npm:^1.0.0" - micromark-util-types: "npm:^1.0.0" - unist-util-stringify-position: "npm:^3.0.0" - uvu: "npm:^0.5.0" - checksum: 10/1d334a54ddd6481ec4acf64c2c537b6463bc5113ba5a408f65c228dcc302d46837352814f11307af0f8b51dd7e4a0b887ce692e4d30ff31ff9d578b8ca82810b - languageName: node - linkType: hard - "mdast-util-from-markdown@npm:^2.0.0": version: 2.0.0 resolution: "mdast-util-from-markdown@npm:2.0.0" @@ -9060,18 +9633,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-gfm-autolink-literal@npm:^1.0.0": - version: 1.0.3 - resolution: "mdast-util-gfm-autolink-literal@npm:1.0.3" - dependencies: - "@types/mdast": "npm:^3.0.0" - ccount: "npm:^2.0.0" - mdast-util-find-and-replace: "npm:^2.0.0" - micromark-util-character: "npm:^1.0.0" - checksum: 10/272d075cdc7937bec0179af4052bd9032a6fbb05608b387b1b075b0491c73ce012f3ff1c718cdb5fb0ed1032c1fa7570d955b59c0ab3c3c72609928754774529 - languageName: node - linkType: hard - "mdast-util-gfm-autolink-literal@npm:^2.0.0": version: 2.0.0 resolution: "mdast-util-gfm-autolink-literal@npm:2.0.0" @@ -9085,17 +9646,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-gfm-footnote@npm:^1.0.0": - version: 1.0.2 - resolution: "mdast-util-gfm-footnote@npm:1.0.2" - dependencies: - "@types/mdast": "npm:^3.0.0" - mdast-util-to-markdown: "npm:^1.3.0" - micromark-util-normalize-identifier: "npm:^1.0.0" - checksum: 10/825f207afc98fd1daa0acc8adcb5754d1f0d577ccb1749245289bee7c892557668d8ee3a5ab618f42e710646cf018dcda84f3c0c608ae11718e9014e5bf4f9dc - languageName: node - linkType: hard - "mdast-util-gfm-footnote@npm:^2.0.0": version: 2.0.0 resolution: "mdast-util-gfm-footnote@npm:2.0.0" @@ -9109,16 +9659,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-gfm-strikethrough@npm:^1.0.0": - version: 1.0.3 - resolution: "mdast-util-gfm-strikethrough@npm:1.0.3" - dependencies: - "@types/mdast": "npm:^3.0.0" - mdast-util-to-markdown: "npm:^1.3.0" - checksum: 10/a9c2dc3ef46be7952d13b7063a16171bba8aa266bffe6b1e7267df02a60b4fa3734115cca311e9127db8cfcbbcd68fdd92aa26152bcd0c14372c79b254e4df2f - languageName: node - linkType: hard - "mdast-util-gfm-strikethrough@npm:^2.0.0": version: 2.0.0 resolution: "mdast-util-gfm-strikethrough@npm:2.0.0" @@ -9130,18 +9670,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-gfm-table@npm:^1.0.0": - version: 1.0.7 - resolution: "mdast-util-gfm-table@npm:1.0.7" - dependencies: - "@types/mdast": "npm:^3.0.0" - markdown-table: "npm:^3.0.0" - mdast-util-from-markdown: "npm:^1.0.0" - mdast-util-to-markdown: "npm:^1.3.0" - checksum: 10/167f7f7a9dc17ce852f4f9bd155d7be179588e2ccf4ce3c4f23b12c1c9db5de904cdacc6f41b2d635cb84eb09a7ff5a33497585f2664a7f1e6bd6f7ab7e1197a - languageName: node - linkType: hard - "mdast-util-gfm-table@npm:^2.0.0": version: 2.0.0 resolution: "mdast-util-gfm-table@npm:2.0.0" @@ -9155,16 +9683,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-gfm-task-list-item@npm:^1.0.0": - version: 1.0.2 - resolution: "mdast-util-gfm-task-list-item@npm:1.0.2" - dependencies: - "@types/mdast": "npm:^3.0.0" - mdast-util-to-markdown: "npm:^1.3.0" - checksum: 10/958417a7d7690728b44d65127ab9189c7feaa17aea924dd56a888c781ab3abaa4eb0c209f05c4dbf203da3d0c4df8fdace4c9471b644268bfc7fc792a018a171 - languageName: node - linkType: hard - "mdast-util-gfm-task-list-item@npm:^2.0.0": version: 2.0.0 resolution: "mdast-util-gfm-task-list-item@npm:2.0.0" @@ -9177,21 +9695,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-gfm@npm:^2.0.0": - version: 2.0.2 - resolution: "mdast-util-gfm@npm:2.0.2" - dependencies: - mdast-util-from-markdown: "npm:^1.0.0" - mdast-util-gfm-autolink-literal: "npm:^1.0.0" - mdast-util-gfm-footnote: "npm:^1.0.0" - mdast-util-gfm-strikethrough: "npm:^1.0.0" - mdast-util-gfm-table: "npm:^1.0.0" - mdast-util-gfm-task-list-item: "npm:^1.0.0" - mdast-util-to-markdown: "npm:^1.0.0" - checksum: 10/70e6cd32af94181d409f171f984f83fc18b3efe316844c62f31816f5c1612a92517b8ed766340f23e0a6d6cb0f27a8b07d288bab6619cbdbb0c5341006bcdc4d - languageName: node - linkType: hard - "mdast-util-gfm@npm:^3.0.0": version: 3.0.0 resolution: "mdast-util-gfm@npm:3.0.0" @@ -9269,16 +9772,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-phrasing@npm:^3.0.0": - version: 3.0.1 - resolution: "mdast-util-phrasing@npm:3.0.1" - dependencies: - "@types/mdast": "npm:^3.0.0" - unist-util-is: "npm:^5.0.0" - checksum: 10/c5b616d9b1eb76a6b351d195d94318494722525a12a89d9c8a3b091af7db3dd1fc55d294f9d29266d8159a8267b0df4a7a133bda8a3909d5331c383e1e1ff328 - languageName: node - linkType: hard - "mdast-util-phrasing@npm:^4.0.0": version: 4.0.0 resolution: "mdast-util-phrasing@npm:4.0.0" @@ -9289,22 +9782,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-to-hast@npm:^12.1.0": - version: 12.3.0 - resolution: "mdast-util-to-hast@npm:12.3.0" - dependencies: - "@types/hast": "npm:^2.0.0" - "@types/mdast": "npm:^3.0.0" - mdast-util-definitions: "npm:^5.0.0" - micromark-util-sanitize-uri: "npm:^1.1.0" - trim-lines: "npm:^3.0.0" - unist-util-generated: "npm:^2.0.0" - unist-util-position: "npm:^4.0.0" - unist-util-visit: "npm:^4.0.0" - checksum: 10/82b72bf46863f0f5683dbf1c5917186ee2da2e06af1a5f5aaeca51b880f4cb2b3ae0463ebb4fa1a776f5d3c73f5fc6cd542920060cf5040f3d4431607ee73cce - languageName: node - linkType: hard - "mdast-util-to-hast@npm:^13.0.0": version: 13.1.0 resolution: "mdast-util-to-hast@npm:13.1.0" @@ -9322,22 +9799,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-to-markdown@npm:^1.0.0, mdast-util-to-markdown@npm:^1.3.0": - version: 1.5.0 - resolution: "mdast-util-to-markdown@npm:1.5.0" - dependencies: - "@types/mdast": "npm:^3.0.0" - "@types/unist": "npm:^2.0.0" - longest-streak: "npm:^3.0.0" - mdast-util-phrasing: "npm:^3.0.0" - mdast-util-to-string: "npm:^3.0.0" - micromark-util-decode-string: "npm:^1.0.0" - unist-util-visit: "npm:^4.0.0" - zwitch: "npm:^2.0.0" - checksum: 10/713f674588a01969a2ce524a69985bd57e507377eea2c4ba69800fb305414468b30144ae9b837fbdde8c609877673140e4f56f6cabe9e0e2bc1487291e3c5144 - languageName: node - linkType: hard - "mdast-util-to-markdown@npm:^2.0.0": version: 2.1.0 resolution: "mdast-util-to-markdown@npm:2.1.0" @@ -9354,15 +9815,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-to-string@npm:^3.0.0, mdast-util-to-string@npm:^3.1.0": - version: 3.2.0 - resolution: "mdast-util-to-string@npm:3.2.0" - dependencies: - "@types/mdast": "npm:^3.0.0" - checksum: 10/fafe201c12a0d412a875fe8540bf70b4360f3775fb7f0d19403ba7b59e50f74f730e3b405c72ad940bc8a3ec1ba311f76dfca61c4ce585dce1ccda2168ec244f - languageName: node - linkType: hard - "mdast-util-to-string@npm:^4.0.0": version: 4.0.0 resolution: "mdast-util-to-string@npm:4.0.0" @@ -9393,12 +9845,27 @@ __metadata: languageName: node linkType: hard -"memfs@npm:^3.1.2, memfs@npm:^3.4.3": - version: 3.4.12 - resolution: "memfs@npm:3.4.12" +"memfs@npm:^4.43.1": + version: 4.56.10 + resolution: "memfs@npm:4.56.10" dependencies: - fs-monkey: "npm:^1.0.3" - checksum: 10/3c03b1c10915c902da6a4c68de63d936c56a69069eec739fe1c952b14b168217adff1b34e438dd8268123ab2af7f6dafa75e89718144b9a5e2d1f6497b934e40 + "@jsonjoy.com/fs-core": "npm:4.56.10" + "@jsonjoy.com/fs-fsa": "npm:4.56.10" + "@jsonjoy.com/fs-node": "npm:4.56.10" + "@jsonjoy.com/fs-node-builtins": "npm:4.56.10" + "@jsonjoy.com/fs-node-to-fsa": "npm:4.56.10" + "@jsonjoy.com/fs-node-utils": "npm:4.56.10" + "@jsonjoy.com/fs-print": "npm:4.56.10" + "@jsonjoy.com/fs-snapshot": "npm:4.56.10" + "@jsonjoy.com/json-pack": "npm:^1.11.0" + "@jsonjoy.com/util": "npm:^1.9.0" + glob-to-regex.js: "npm:^1.0.1" + thingies: "npm:^2.5.0" + tree-dump: "npm:^1.0.3" + tslib: "npm:^2.0.0" + peerDependencies: + tslib: 2 + checksum: 10/9358ab01174207aa3de7c47d7810dd32d0b6c0cdcbef455f3a51628c454e29feb4f1bfc15f25bfc59354ba6645fb3a10fcbb0dd1fbfc42e0595fae33fbc73bd1 languageName: node linkType: hard @@ -9430,30 +9897,6 @@ __metadata: languageName: node linkType: hard -"micromark-core-commonmark@npm:^1.0.0, micromark-core-commonmark@npm:^1.0.1": - version: 1.1.0 - resolution: "micromark-core-commonmark@npm:1.1.0" - dependencies: - decode-named-character-reference: "npm:^1.0.0" - micromark-factory-destination: "npm:^1.0.0" - micromark-factory-label: "npm:^1.0.0" - micromark-factory-space: "npm:^1.0.0" - micromark-factory-title: "npm:^1.0.0" - micromark-factory-whitespace: "npm:^1.0.0" - micromark-util-character: "npm:^1.0.0" - micromark-util-chunked: "npm:^1.0.0" - micromark-util-classify-character: "npm:^1.0.0" - micromark-util-html-tag-name: "npm:^1.0.0" - micromark-util-normalize-identifier: "npm:^1.0.0" - micromark-util-resolve-all: "npm:^1.0.0" - micromark-util-subtokenize: "npm:^1.0.0" - micromark-util-symbol: "npm:^1.0.0" - micromark-util-types: "npm:^1.0.1" - uvu: "npm:^0.5.0" - checksum: 10/a73694d223ac8baad8ff00597a3c39d61f5b32bfd56fe4bcf295d75b2a4e8e67fb2edbfc7cc287b362b9d7f6d24fce08b6a7e8b5b155d79bcc1e4d9b2756ffb2 - languageName: node - linkType: hard - "micromark-core-commonmark@npm:^2.0.0": version: 2.0.0 resolution: "micromark-core-commonmark@npm:2.0.0" @@ -9505,18 +9948,6 @@ __metadata: languageName: node linkType: hard -"micromark-extension-gfm-autolink-literal@npm:^1.0.0": - version: 1.0.5 - resolution: "micromark-extension-gfm-autolink-literal@npm:1.0.5" - dependencies: - micromark-util-character: "npm:^1.0.0" - micromark-util-sanitize-uri: "npm:^1.0.0" - micromark-util-symbol: "npm:^1.0.0" - micromark-util-types: "npm:^1.0.0" - checksum: 10/1e0ccc758baef3cd0478ba84ff86fa1ec2b389042421c7cade9485b775456c1a9c3bd797393002b2c6f6abd9bdf829cb114874557bbcb8e43d16d06a464811c0 - languageName: node - linkType: hard - "micromark-extension-gfm-autolink-literal@npm:^2.0.0": version: 2.0.0 resolution: "micromark-extension-gfm-autolink-literal@npm:2.0.0" @@ -9529,22 +9960,6 @@ __metadata: languageName: node linkType: hard -"micromark-extension-gfm-footnote@npm:^1.0.0": - version: 1.1.2 - resolution: "micromark-extension-gfm-footnote@npm:1.1.2" - dependencies: - micromark-core-commonmark: "npm:^1.0.0" - micromark-factory-space: "npm:^1.0.0" - micromark-util-character: "npm:^1.0.0" - micromark-util-normalize-identifier: "npm:^1.0.0" - micromark-util-sanitize-uri: "npm:^1.0.0" - micromark-util-symbol: "npm:^1.0.0" - micromark-util-types: "npm:^1.0.0" - uvu: "npm:^0.5.0" - checksum: 10/8777073fb76d2fd01f6b2405106af6c349c1e25660c4d37cadcc61c187d71c8444870f73cefaaa67f12884d5e45c78ee3c5583561a0b330bd91c6d997113584a - languageName: node - linkType: hard - "micromark-extension-gfm-footnote@npm:^2.0.0": version: 2.0.0 resolution: "micromark-extension-gfm-footnote@npm:2.0.0" @@ -9561,20 +9976,6 @@ __metadata: languageName: node linkType: hard -"micromark-extension-gfm-strikethrough@npm:^1.0.0": - version: 1.0.7 - resolution: "micromark-extension-gfm-strikethrough@npm:1.0.7" - dependencies: - micromark-util-chunked: "npm:^1.0.0" - micromark-util-classify-character: "npm:^1.0.0" - micromark-util-resolve-all: "npm:^1.0.0" - micromark-util-symbol: "npm:^1.0.0" - micromark-util-types: "npm:^1.0.0" - uvu: "npm:^0.5.0" - checksum: 10/8411ef1aa5dc83f662e8b45b085f70ddff29deb3c4259269e8a1ff656397abb755d8ea841a14be23e8585a31d3c0a5de1bd2c05f3453b66670e499d4a0004f5e - languageName: node - linkType: hard - "micromark-extension-gfm-strikethrough@npm:^2.0.0": version: 2.0.0 resolution: "micromark-extension-gfm-strikethrough@npm:2.0.0" @@ -9589,19 +9990,6 @@ __metadata: languageName: node linkType: hard -"micromark-extension-gfm-table@npm:^1.0.0": - version: 1.0.7 - resolution: "micromark-extension-gfm-table@npm:1.0.7" - dependencies: - micromark-factory-space: "npm:^1.0.0" - micromark-util-character: "npm:^1.0.0" - micromark-util-symbol: "npm:^1.0.0" - micromark-util-types: "npm:^1.0.0" - uvu: "npm:^0.5.0" - checksum: 10/f05d86a099c941a2a309d60bf4839d16a00a93cb880cda4ab8faeb831647763fff6e03197ec15b80e1f195002afcca6afe2b95c3622b049b82d7ff8ef1c1c776 - languageName: node - linkType: hard - "micromark-extension-gfm-table@npm:^2.0.0": version: 2.0.0 resolution: "micromark-extension-gfm-table@npm:2.0.0" @@ -9615,15 +10003,6 @@ __metadata: languageName: node linkType: hard -"micromark-extension-gfm-tagfilter@npm:^1.0.0": - version: 1.0.2 - resolution: "micromark-extension-gfm-tagfilter@npm:1.0.2" - dependencies: - micromark-util-types: "npm:^1.0.0" - checksum: 10/55c7d9019d6a39efaaed2c2e40b0aaa137d2c4f9c94cac82e93f509a806c3a775e4c815b5d8e986617450b68861a19776e4b886307e83db452b393f15a837b39 - languageName: node - linkType: hard - "micromark-extension-gfm-tagfilter@npm:^2.0.0": version: 2.0.0 resolution: "micromark-extension-gfm-tagfilter@npm:2.0.0" @@ -9633,19 +10012,6 @@ __metadata: languageName: node linkType: hard -"micromark-extension-gfm-task-list-item@npm:^1.0.0": - version: 1.0.5 - resolution: "micromark-extension-gfm-task-list-item@npm:1.0.5" - dependencies: - micromark-factory-space: "npm:^1.0.0" - micromark-util-character: "npm:^1.0.0" - micromark-util-symbol: "npm:^1.0.0" - micromark-util-types: "npm:^1.0.0" - uvu: "npm:^0.5.0" - checksum: 10/46bb1baa10bfb785a2e3e2f975e5509260b9995d5c3aeddf77051957d218ce1af4ea737bcb6a56a930e62d42b05307b20632a400eff25cdb290789ff3170cad5 - languageName: node - linkType: hard - "micromark-extension-gfm-task-list-item@npm:^2.0.0": version: 2.0.1 resolution: "micromark-extension-gfm-task-list-item@npm:2.0.1" @@ -9659,22 +10025,6 @@ __metadata: languageName: node linkType: hard -"micromark-extension-gfm@npm:^2.0.0": - version: 2.0.3 - resolution: "micromark-extension-gfm@npm:2.0.3" - dependencies: - micromark-extension-gfm-autolink-literal: "npm:^1.0.0" - micromark-extension-gfm-footnote: "npm:^1.0.0" - micromark-extension-gfm-strikethrough: "npm:^1.0.0" - micromark-extension-gfm-table: "npm:^1.0.0" - micromark-extension-gfm-tagfilter: "npm:^1.0.0" - micromark-extension-gfm-task-list-item: "npm:^1.0.0" - micromark-util-combine-extensions: "npm:^1.0.0" - micromark-util-types: "npm:^1.0.0" - checksum: 10/3ffd06ced4314abd0f0c72ec227f034f38dd47facbb62439ef3216d42f32433f3901d14675cf806e8d73689802a11849958b330bb5b55dd4fd5cdc64ebaf345c - languageName: node - linkType: hard - "micromark-extension-gfm@npm:^3.0.0": version: 3.0.0 resolution: "micromark-extension-gfm@npm:3.0.0" @@ -9767,17 +10117,6 @@ __metadata: languageName: node linkType: hard -"micromark-factory-destination@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-factory-destination@npm:1.1.0" - dependencies: - micromark-util-character: "npm:^1.0.0" - micromark-util-symbol: "npm:^1.0.0" - micromark-util-types: "npm:^1.0.0" - checksum: 10/9e2b5fb5fedbf622b687e20d51eb3d56ae90c0e7ecc19b37bd5285ec392c1e56f6e21aa7cfcb3c01eda88df88fe528f3acb91a5f57d7f4cba310bc3cd7f824fa - languageName: node - linkType: hard - "micromark-factory-destination@npm:^2.0.0": version: 2.0.0 resolution: "micromark-factory-destination@npm:2.0.0" @@ -9789,18 +10128,6 @@ __metadata: languageName: node linkType: hard -"micromark-factory-label@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-factory-label@npm:1.1.0" - dependencies: - micromark-util-character: "npm:^1.0.0" - micromark-util-symbol: "npm:^1.0.0" - micromark-util-types: "npm:^1.0.0" - uvu: "npm:^0.5.0" - checksum: 10/fcda48f1287d9b148c562c627418a2ab759cdeae9c8e017910a0cba94bb759a96611e1fc6df33182e97d28fbf191475237298983bb89ef07d5b02464b1ad28d5 - languageName: node - linkType: hard - "micromark-factory-label@npm:^2.0.0": version: 2.0.0 resolution: "micromark-factory-label@npm:2.0.0" @@ -9849,18 +10176,6 @@ __metadata: languageName: node linkType: hard -"micromark-factory-title@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-factory-title@npm:1.1.0" - dependencies: - micromark-factory-space: "npm:^1.0.0" - micromark-util-character: "npm:^1.0.0" - micromark-util-symbol: "npm:^1.0.0" - micromark-util-types: "npm:^1.0.0" - checksum: 10/4432d3dbc828c81f483c5901b0c6591a85d65a9e33f7d96ba7c3ae821617a0b3237ff5faf53a9152d00aaf9afb3a9f185b205590f40ed754f1d9232e0e9157b1 - languageName: node - linkType: hard - "micromark-factory-title@npm:^2.0.0": version: 2.0.0 resolution: "micromark-factory-title@npm:2.0.0" @@ -9873,18 +10188,6 @@ __metadata: languageName: node linkType: hard -"micromark-factory-whitespace@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-factory-whitespace@npm:1.1.0" - dependencies: - micromark-factory-space: "npm:^1.0.0" - micromark-util-character: "npm:^1.0.0" - micromark-util-symbol: "npm:^1.0.0" - micromark-util-types: "npm:^1.0.0" - checksum: 10/ef0fa682c7d593d85a514ee329809dee27d10bc2a2b65217d8ef81173e33b8e83c549049764b1ad851adfe0a204dec5450d9d20a4ca8598f6c94533a73f73fcd - languageName: node - linkType: hard - "micromark-factory-whitespace@npm:^2.0.0": version: 2.0.0 resolution: "micromark-factory-whitespace@npm:2.0.0" @@ -9917,15 +10220,6 @@ __metadata: languageName: node linkType: hard -"micromark-util-chunked@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-util-chunked@npm:1.1.0" - dependencies: - micromark-util-symbol: "npm:^1.0.0" - checksum: 10/c435bde9110cb595e3c61b7f54c2dc28ee03e6a57fa0fc1e67e498ad8bac61ee5a7457a2b6a73022ddc585676ede4b912d28dcf57eb3bd6951e54015e14dc20b - languageName: node - linkType: hard - "micromark-util-chunked@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-chunked@npm:2.0.0" @@ -9935,17 +10229,6 @@ __metadata: languageName: node linkType: hard -"micromark-util-classify-character@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-util-classify-character@npm:1.1.0" - dependencies: - micromark-util-character: "npm:^1.0.0" - micromark-util-symbol: "npm:^1.0.0" - micromark-util-types: "npm:^1.0.0" - checksum: 10/8499cb0bb1f7fb946f5896285fcca65cd742f66cd3e79ba7744792bd413ec46834f932a286de650349914d02e822946df3b55d03e6a8e1d245d1ddbd5102e5b0 - languageName: node - linkType: hard - "micromark-util-classify-character@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-classify-character@npm:2.0.0" @@ -9957,16 +10240,6 @@ __metadata: languageName: node linkType: hard -"micromark-util-combine-extensions@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-util-combine-extensions@npm:1.1.0" - dependencies: - micromark-util-chunked: "npm:^1.0.0" - micromark-util-types: "npm:^1.0.0" - checksum: 10/ee78464f5d4b61ccb437850cd2d7da4d690b260bca4ca7a79c4bb70291b84f83988159e373b167181b6716cb197e309bc6e6c96a68cc3ba9d50c13652774aba9 - languageName: node - linkType: hard - "micromark-util-combine-extensions@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-combine-extensions@npm:2.0.0" @@ -9977,15 +10250,6 @@ __metadata: languageName: node linkType: hard -"micromark-util-decode-numeric-character-reference@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-util-decode-numeric-character-reference@npm:1.1.0" - dependencies: - micromark-util-symbol: "npm:^1.0.0" - checksum: 10/4733fe75146e37611243f055fc6847137b66f0cde74d080e33bd26d0408c1d6f44cabc984063eee5968b133cb46855e729d555b9ff8d744652262b7b51feec73 - languageName: node - linkType: hard - "micromark-util-decode-numeric-character-reference@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-decode-numeric-character-reference@npm:2.0.0" @@ -9995,18 +10259,6 @@ __metadata: languageName: node linkType: hard -"micromark-util-decode-string@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-util-decode-string@npm:1.1.0" - dependencies: - decode-named-character-reference: "npm:^1.0.0" - micromark-util-character: "npm:^1.0.0" - micromark-util-decode-numeric-character-reference: "npm:^1.0.0" - micromark-util-symbol: "npm:^1.0.0" - checksum: 10/f1625155db452f15aa472918499689ba086b9c49d1322a08b22bfbcabe918c61b230a3002c8bc3ea9b1f52ca7a9bb1c3dd43ccb548c7f5f8b16c24a1ae77a813 - languageName: node - linkType: hard - "micromark-util-decode-string@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-decode-string@npm:2.0.0" @@ -10019,13 +10271,6 @@ __metadata: languageName: node linkType: hard -"micromark-util-encode@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-util-encode@npm:1.1.0" - checksum: 10/4ef29d02b12336918cea6782fa87c8c578c67463925221d4e42183a706bde07f4b8b5f9a5e1c7ce8c73bb5a98b261acd3238fecd152e6dd1cdfa2d1ae11b60a0 - languageName: node - linkType: hard - "micromark-util-encode@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-encode@npm:2.0.0" @@ -10049,13 +10294,6 @@ __metadata: languageName: node linkType: hard -"micromark-util-html-tag-name@npm:^1.0.0": - version: 1.2.0 - resolution: "micromark-util-html-tag-name@npm:1.2.0" - checksum: 10/ccf0fa99b5c58676dc5192c74665a3bfd1b536fafaf94723bd7f31f96979d589992df6fcf2862eba290ef18e6a8efb30ec8e1e910d9f3fc74f208871e9f84750 - languageName: node - linkType: hard - "micromark-util-html-tag-name@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-html-tag-name@npm:2.0.0" @@ -10063,15 +10301,6 @@ __metadata: languageName: node linkType: hard -"micromark-util-normalize-identifier@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-util-normalize-identifier@npm:1.1.0" - dependencies: - micromark-util-symbol: "npm:^1.0.0" - checksum: 10/8655bea41ffa4333e03fc22462cb42d631bbef9c3c07b625fd852b7eb442a110f9d2e5902a42e65188d85498279569502bf92f3434a1180fc06f7c37edfbaee2 - languageName: node - linkType: hard - "micromark-util-normalize-identifier@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-normalize-identifier@npm:2.0.0" @@ -10081,15 +10310,6 @@ __metadata: languageName: node linkType: hard -"micromark-util-resolve-all@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-util-resolve-all@npm:1.1.0" - dependencies: - micromark-util-types: "npm:^1.0.0" - checksum: 10/1ce6c0237cd3ca061e76fae6602cf95014e764a91be1b9f10d36cb0f21ca88f9a07de8d49ab8101efd0b140a4fbfda6a1efb72027ab3f4d5b54c9543271dc52c - languageName: node - linkType: hard - "micromark-util-resolve-all@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-resolve-all@npm:2.0.0" @@ -10099,17 +10319,6 @@ __metadata: languageName: node linkType: hard -"micromark-util-sanitize-uri@npm:^1.0.0, micromark-util-sanitize-uri@npm:^1.1.0": - version: 1.2.0 - resolution: "micromark-util-sanitize-uri@npm:1.2.0" - dependencies: - micromark-util-character: "npm:^1.0.0" - micromark-util-encode: "npm:^1.0.0" - micromark-util-symbol: "npm:^1.0.0" - checksum: 10/0d024100d95ffb88bf75f3360e305b545c1eb745430959b8633f7aa93f37ec401fc7094c90c97298409a9e30d94d53b895bae224e1bb966bea114976cfa0fd48 - languageName: node - linkType: hard - "micromark-util-sanitize-uri@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-sanitize-uri@npm:2.0.0" @@ -10121,18 +10330,6 @@ __metadata: languageName: node linkType: hard -"micromark-util-subtokenize@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-util-subtokenize@npm:1.1.0" - dependencies: - micromark-util-chunked: "npm:^1.0.0" - micromark-util-symbol: "npm:^1.0.0" - micromark-util-types: "npm:^1.0.0" - uvu: "npm:^0.5.0" - checksum: 10/075a1db6ea586d65827d3eead33dbfc520c4e43659c93fcd8fd82f44a7b75cfe61dcde967a3dfcc2ffd999347440ba5aa6698e65a04f3fc627e13e9f12a1a910 - languageName: node - linkType: hard - "micromark-util-subtokenize@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-subtokenize@npm:2.0.0" @@ -10159,7 +10356,7 @@ __metadata: languageName: node linkType: hard -"micromark-util-types@npm:^1.0.0, micromark-util-types@npm:^1.0.1": +"micromark-util-types@npm:^1.0.0": version: 1.1.0 resolution: "micromark-util-types@npm:1.1.0" checksum: 10/287ac5de4a3802bb6f6c3842197c294997a488db1c0486e03c7a8e674d9eb7720c17dda1bcb814814b8343b338c4826fcbc0555f3e75463712a60dcdb53a028e @@ -10173,31 +10370,6 @@ __metadata: languageName: node linkType: hard -"micromark@npm:^3.0.0": - version: 3.2.0 - resolution: "micromark@npm:3.2.0" - dependencies: - "@types/debug": "npm:^4.0.0" - debug: "npm:^4.0.0" - decode-named-character-reference: "npm:^1.0.0" - micromark-core-commonmark: "npm:^1.0.1" - micromark-factory-space: "npm:^1.0.0" - micromark-util-character: "npm:^1.0.0" - micromark-util-chunked: "npm:^1.0.0" - micromark-util-combine-extensions: "npm:^1.0.0" - micromark-util-decode-numeric-character-reference: "npm:^1.0.0" - micromark-util-encode: "npm:^1.0.0" - micromark-util-normalize-identifier: "npm:^1.0.0" - micromark-util-resolve-all: "npm:^1.0.0" - micromark-util-sanitize-uri: "npm:^1.0.0" - micromark-util-subtokenize: "npm:^1.0.0" - micromark-util-symbol: "npm:^1.0.0" - micromark-util-types: "npm:^1.0.1" - uvu: "npm:^0.5.0" - checksum: 10/560a4a501efc3859d622461aaa9345fb95b99a2f34d3d3f2a775ab04de1dd857cb0f642083a6b28ab01bd817f5f0741a1be9857fd702f45e04a3752927a66719 - languageName: node - linkType: hard - "micromark@npm:^4.0.0": version: 4.0.0 resolution: "micromark@npm:4.0.0" @@ -10233,25 +10405,20 @@ __metadata: languageName: node linkType: hard -"miller-rabin@npm:^4.0.0": - version: 4.0.1 - resolution: "miller-rabin@npm:4.0.1" - dependencies: - bn.js: "npm:^4.0.0" - brorand: "npm:^1.0.1" - bin: - miller-rabin: bin/miller-rabin - checksum: 10/2a38ba9d1e878d94ee8a8ab3505b40e8d44fb9700a7716570fe4c8ca7e20d49b69aea579106580618c877cc6ff969eff71705042fafb47573736bf89404417bc - languageName: node - linkType: hard - -"mime-db@npm:1.52.0, mime-db@npm:>= 1.43.0 < 2": +"mime-db@npm:1.52.0": version: 1.52.0 resolution: "mime-db@npm:1.52.0" checksum: 10/54bb60bf39e6f8689f6622784e668a3d7f8bed6b0d886f5c3c446cb3284be28b30bf707ed05d0fe44a036f8469976b2629bbea182684977b084de9da274694d7 languageName: node linkType: hard +"mime-db@npm:>= 1.43.0 < 2, mime-db@npm:^1.54.0": + version: 1.54.0 + resolution: "mime-db@npm:1.54.0" + checksum: 10/9e7834be3d66ae7f10eaa69215732c6d389692b194f876198dca79b2b90cbf96688d9d5d05ef7987b20f749b769b11c01766564264ea5f919c88b32a29011311 + languageName: node + linkType: hard + "mime-db@npm:~1.33.0": version: 1.33.0 resolution: "mime-db@npm:1.33.0" @@ -10259,12 +10426,12 @@ __metadata: languageName: node linkType: hard -"mime-format@npm:2.0.1": - version: 2.0.1 - resolution: "mime-format@npm:2.0.1" +"mime-format@npm:2.0.2": + version: 2.0.2 + resolution: "mime-format@npm:2.0.2" dependencies: charset: "npm:^1.0.0" - checksum: 10/0a7731d2a268f5076fdb401e60e908fdf2d842b39a67a11ba8bac682d6d968c2290a257226b86318afbaafb6317b33d1f4c4a934b585bf8f9c9543b0b291cf3e + checksum: 10/8dfcb4310f0fad63b85455314972ad6660f40fa527056e45e633c5956189db44c9c73568d03cb2d68fb12e3fbe88f92ad92ab695e22121c7a37a299b3d4d38cc languageName: node linkType: hard @@ -10277,7 +10444,7 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:2.1.35, mime-types@npm:^2.1.27, mime-types@npm:^2.1.31, mime-types@npm:~2.1.17, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": +"mime-types@npm:^2.1.27, mime-types@npm:~2.1.17, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": version: 2.1.35 resolution: "mime-types@npm:2.1.35" dependencies: @@ -10286,6 +10453,15 @@ __metadata: languageName: node linkType: hard +"mime-types@npm:^3.0.1": + version: 3.0.2 + resolution: "mime-types@npm:3.0.2" + dependencies: + mime-db: "npm:^1.54.0" + checksum: 10/9db0ad31f5eff10ee8f848130779b7f2d056ddfdb6bda696cb69be68d486d33a3457b4f3f9bdeb60d0736edb471bd5a7c0a384375c011c51c889fd0d5c3b893e + languageName: node + linkType: hard + "mime@npm:1.6.0": version: 1.6.0 resolution: "mime@npm:1.6.0" @@ -10295,6 +10471,15 @@ __metadata: languageName: node linkType: hard +"mime@npm:3.0.0": + version: 3.0.0 + resolution: "mime@npm:3.0.0" + bin: + mime: cli.js + checksum: 10/b2d31580deb58be89adaa1877cbbf152b7604b980fd7ef8f08b9e96bfedf7d605d9c23a8ba62aa12c8580b910cd7c1d27b7331d0f40f7a14e17d5a0bbec3b49f + languageName: node + linkType: hard + "mimic-fn@npm:^2.1.0": version: 2.1.0 resolution: "mimic-fn@npm:2.1.0" @@ -10316,32 +10501,26 @@ __metadata: languageName: node linkType: hard -"mini-css-extract-plugin@npm:^2.7.6": - version: 2.7.6 - resolution: "mini-css-extract-plugin@npm:2.7.6" +"mini-css-extract-plugin@npm:^2.9.2": + version: 2.10.0 + resolution: "mini-css-extract-plugin@npm:2.10.0" dependencies: schema-utils: "npm:^4.0.0" + tapable: "npm:^2.2.1" peerDependencies: webpack: ^5.0.0 - checksum: 10/1f718bfdcb7c2bf5e4336f694e5576432149d63f9dacaf94eae38ad046534050471a712a2d1bedf95e1722a2d3b56c3361d7352849e802e4875e716885e952c3 + checksum: 10/bae5350ab82171c6c9a22a4397df14aa69280f5ff0e1ff4d2429ea841bc096927b1e27ba7b75a9c3dd77bd44bab449d6197bd748381f1326cbc8befcb10d1a9e languageName: node linkType: hard -"minimalistic-assert@npm:^1.0.0, minimalistic-assert@npm:^1.0.1": +"minimalistic-assert@npm:^1.0.0": version: 1.0.1 resolution: "minimalistic-assert@npm:1.0.1" checksum: 10/cc7974a9268fbf130fb055aff76700d7e2d8be5f761fb5c60318d0ed010d839ab3661a533ad29a5d37653133385204c503bfac995aaa4236f4e847461ea32ba7 languageName: node linkType: hard -"minimalistic-crypto-utils@npm:^1.0.1": - version: 1.0.1 - resolution: "minimalistic-crypto-utils@npm:1.0.1" - checksum: 10/6e8a0422b30039406efd4c440829ea8f988845db02a3299f372fceba56ffa94994a9c0f2fd70c17f9969eedfbd72f34b5070ead9656a34d3f71c0bd72583a0ed - languageName: node - linkType: hard - -"minimatch@npm:3.1.2, minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1": +"minimatch@npm:3.1.2, minimatch@npm:^3.1.1": version: 3.1.2 resolution: "minimatch@npm:3.1.2" dependencies: @@ -10468,13 +10647,6 @@ __metadata: languageName: node linkType: hard -"mri@npm:^1.1.0": - version: 1.2.0 - resolution: "mri@npm:1.2.0" - checksum: 10/6775a1d2228bb9d191ead4efc220bd6be64f943ad3afd4dcb3b3ac8fc7b87034443f666e38805df38e8d047b29f910c3cc7810da0109af83e42c82c73bd3f6bc - languageName: node - linkType: hard - "mrmime@npm:^1.0.0": version: 1.0.1 resolution: "mrmime@npm:1.0.1" @@ -10489,14 +10661,7 @@ __metadata: languageName: node linkType: hard -"ms@npm:2.1.2": - version: 2.1.2 - resolution: "ms@npm:2.1.2" - checksum: 10/673cdb2c3133eb050c745908d8ce632ed2c02d85640e2edb3ace856a2266a813b30c613569bf3354fdf4ea7d1a1494add3bfa95e2713baa27d0c2c71fc44f58f - languageName: node - linkType: hard - -"ms@npm:2.1.3, ms@npm:^2.0.0": +"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.3": version: 2.1.3 resolution: "ms@npm:2.1.3" checksum: 10/aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d @@ -10535,22 +10700,29 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.3.7": - version: 3.3.8 - resolution: "nanoid@npm:3.3.8" +"nanoid@npm:^3.3.11": + version: 3.3.11 + resolution: "nanoid@npm:3.3.11" bin: nanoid: bin/nanoid.cjs - checksum: 10/2d1766606cf0d6f47b6f0fdab91761bb81609b2e3d367027aff45e6ee7006f660fb7e7781f4a34799fe6734f1268eeed2e37a5fdee809ade0c2d4eb11b0f9c40 + checksum: 10/73b5afe5975a307aaa3c95dfe3334c52cdf9ae71518176895229b8d65ab0d1c0417dd081426134eb7571c055720428ea5d57c645138161e7d10df80815527c48 languageName: node linkType: hard -"negotiator@npm:0.6.3, negotiator@npm:^0.6.3": +"negotiator@npm:0.6.3": version: 0.6.3 resolution: "negotiator@npm:0.6.3" checksum: 10/2723fb822a17ad55c93a588a4bc44d53b22855bf4be5499916ca0cab1e7165409d0b288ba2577d7b029f10ce18cf2ed8e703e5af31c984e1e2304277ef979837 languageName: node linkType: hard +"negotiator@npm:^0.6.3, negotiator@npm:~0.6.4": + version: 0.6.4 + resolution: "negotiator@npm:0.6.4" + checksum: 10/d98c04a136583afd055746168f1067d58ce4bfe6e4c73ca1d339567f81ea1f7e665b5bd1e81f4771c67b6c2ea89b21cb2adaea2b16058c7dc31317778f931dab + languageName: node + linkType: hard + "neo-async@npm:^2.6.2": version: 2.6.2 resolution: "neo-async@npm:2.6.2" @@ -10619,13 +10791,6 @@ __metadata: languageName: node linkType: hard -"node-forge@npm:^1": - version: 1.3.1 - resolution: "node-forge@npm:1.3.1" - checksum: 10/05bab6868633bf9ad4c3b1dd50ec501c22ffd69f556cdf169a00998ca1d03e8107a6032ba013852f202035372021b845603aeccd7dfcb58cdb7430013b3daa8d - languageName: node - linkType: hard - "node-gyp@npm:latest": version: 9.3.0 resolution: "node-gyp@npm:9.3.0" @@ -10646,40 +10811,6 @@ __metadata: languageName: node linkType: hard -"node-polyfill-webpack-plugin@npm:^3.0.0": - version: 3.0.0 - resolution: "node-polyfill-webpack-plugin@npm:3.0.0" - dependencies: - assert: "npm:^2.1.0" - browserify-zlib: "npm:^0.2.0" - buffer: "npm:^6.0.3" - console-browserify: "npm:^1.2.0" - constants-browserify: "npm:^1.0.0" - crypto-browserify: "npm:^3.12.0" - domain-browser: "npm:^4.22.0" - events: "npm:^3.3.0" - https-browserify: "npm:^1.0.0" - os-browserify: "npm:^0.3.0" - path-browserify: "npm:^1.0.1" - process: "npm:^0.11.10" - punycode: "npm:^2.3.0" - querystring-es3: "npm:^0.2.1" - readable-stream: "npm:^4.4.2" - stream-browserify: "npm:^3.0.0" - stream-http: "npm:^3.2.0" - string_decoder: "npm:^1.3.0" - timers-browserify: "npm:^2.0.12" - tty-browserify: "npm:^0.0.1" - type-fest: "npm:^4.4.0" - url: "npm:^0.11.3" - util: "npm:^0.12.5" - vm-browserify: "npm:^1.1.2" - peerDependencies: - webpack: ">=5" - checksum: 10/cd35f2df7bfeb616d4554faf8628fa0425dcbffaa3b6f5520341117add1636b63011afb17f3f95ec53128f84f68e526e829a45d9e9e868ec5a51137db1a63113 - languageName: node - linkType: hard - "node-readfiles@npm:^0.2.0": version: 0.2.0 resolution: "node-readfiles@npm:0.2.0" @@ -10689,10 +10820,10 @@ __metadata: languageName: node linkType: hard -"node-releases@npm:^2.0.19": - version: 2.0.19 - resolution: "node-releases@npm:2.0.19" - checksum: 10/c2b33b4f0c40445aee56141f13ca692fa6805db88510e5bbb3baadb2da13e1293b738e638e15e4a8eb668bb9e97debb08e7a35409b477b5cc18f171d35a83045 +"node-releases@npm:^2.0.27": + version: 2.0.27 + resolution: "node-releases@npm:2.0.27" + checksum: 10/f6c78ddb392ae500719644afcbe68a9ea533242c02312eb6a34e8478506eb7482a3fb709c70235b01c32fe65625b68dfa9665113f816d87f163bc3819b62b106 languageName: node linkType: hard @@ -10714,13 +10845,6 @@ __metadata: languageName: node linkType: hard -"normalize-range@npm:^0.1.2": - version: 0.1.2 - resolution: "normalize-range@npm:0.1.2" - checksum: 10/9b2f14f093593f367a7a0834267c24f3cb3e887a2d9809c77d8a7e5fd08738bcd15af46f0ab01cc3a3d660386f015816b5c922cea8bf2ee79777f40874063184 - languageName: node - linkType: hard - "normalize-url@npm:^8.0.0": version: 8.0.0 resolution: "normalize-url@npm:8.0.0" @@ -10765,6 +10889,18 @@ __metadata: languageName: node linkType: hard +"null-loader@npm:^4.0.1": + version: 4.0.1 + resolution: "null-loader@npm:4.0.1" + dependencies: + loader-utils: "npm:^2.0.0" + schema-utils: "npm:^3.0.0" + peerDependencies: + webpack: ^4.0.0 || ^5.0.0 + checksum: 10/eeb4c4dd2f8f41e46f5665e4500359109e95ec1028a178a60e0161984906572da7dd87644bcc3cb29f0125d77e2b2508fb4f3813cfb1c6604a15865beb4b987b + languageName: node + linkType: hard + "oas-kit-common@npm:^1.0.8": version: 1.0.8 resolution: "oas-kit-common@npm:1.0.8" @@ -10860,16 +10996,6 @@ __metadata: languageName: node linkType: hard -"object-is@npm:^1.1.5": - version: 1.1.6 - resolution: "object-is@npm:1.1.6" - dependencies: - call-bind: "npm:^1.0.7" - define-properties: "npm:^1.2.1" - checksum: 10/4f6f544773a595da21c69a7531e0e1d6250670f4e09c55f47eb02c516035cfcb1b46ceb744edfd3ecb362309dbccb6d7f88e43bf42e4d4595ac10a329061053a - languageName: node - linkType: hard - "object-keys@npm:^1.1.1": version: 1.1.1 resolution: "object-keys@npm:1.1.1" @@ -10877,7 +11003,7 @@ __metadata: languageName: node linkType: hard -"object.assign@npm:^4.1.0, object.assign@npm:^4.1.4": +"object.assign@npm:^4.1.0": version: 4.1.7 resolution: "object.assign@npm:4.1.7" dependencies: @@ -10898,7 +11024,7 @@ __metadata: languageName: node linkType: hard -"on-finished@npm:2.4.1": +"on-finished@npm:2.4.1, on-finished@npm:^2.4.1, on-finished@npm:~2.4.1": version: 2.4.1 resolution: "on-finished@npm:2.4.1" dependencies: @@ -10907,10 +11033,10 @@ __metadata: languageName: node linkType: hard -"on-headers@npm:~1.0.2": - version: 1.0.2 - resolution: "on-headers@npm:1.0.2" - checksum: 10/870766c16345855e2012e9422ba1ab110c7e44ad5891a67790f84610bd70a72b67fdd71baf497295f1d1bf38dd4c92248f825d48729c53c0eae5262fb69fa171 +"on-headers@npm:~1.1.0": + version: 1.1.0 + resolution: "on-headers@npm:1.1.0" + checksum: 10/98aa64629f986fb8cc4517dd8bede73c980e31208cba97f4442c330959f60ced3dc6214b83420491f5111fc7c4f4343abe2ea62c85f505cf041d67850f238776 languageName: node linkType: hard @@ -10932,7 +11058,19 @@ __metadata: languageName: node linkType: hard -"open@npm:^8.0.9, open@npm:^8.4.0": +"open@npm:^10.0.3": + version: 10.2.0 + resolution: "open@npm:10.2.0" + dependencies: + default-browser: "npm:^5.2.1" + define-lazy-prop: "npm:^3.0.0" + is-inside-container: "npm:^1.0.0" + wsl-utils: "npm:^0.1.0" + checksum: 10/e6ad9474734eac3549dcc7d85e952394856ccaee48107c453bd6a725b82e3b8ed5f427658935df27efa76b411aeef62888edea8a9e347e8e7c82632ec966b30e + languageName: node + linkType: hard + +"open@npm:^8.4.0": version: 8.4.0 resolution: "open@npm:8.4.0" dependencies: @@ -10943,14 +11081,14 @@ __metadata: languageName: node linkType: hard -"openapi-to-postmanv2@npm:^4.21.0": - version: 4.25.0 - resolution: "openapi-to-postmanv2@npm:4.25.0" +"openapi-to-postmanv2@npm:^5.0.0": + version: 5.8.0 + resolution: "openapi-to-postmanv2@npm:5.8.0" dependencies: - ajv: "npm:8.11.0" + ajv: "npm:^8.11.0" ajv-draft-04: "npm:1.0.0" ajv-formats: "npm:2.1.1" - async: "npm:3.2.4" + async: "npm:3.2.6" commander: "npm:2.20.3" graphlib: "npm:2.1.8" js-yaml: "npm:4.1.0" @@ -10961,12 +11099,12 @@ __metadata: oas-resolver-browser: "npm:2.5.6" object-hash: "npm:3.0.0" path-browserify: "npm:1.0.1" - postman-collection: "npm:^4.4.0" + postman-collection: "npm:^5.0.0" swagger2openapi: "npm:7.0.8" yaml: "npm:1.10.2" bin: openapi2postmanv2: bin/openapi2postmanv2.js - checksum: 10/1f4dceab7cc5c3e7dd866c1fe2e6870279792a8a194cc5323cfd1d277457cf4a87354aa9b9c24b8c6ac0a29fe95014b93a824fbc167ff544493507d4714b87e8 + checksum: 10/da324b99a223a9621ae4471c151571d9835c40b706c8c42caaedc70fc79a73c69dc023ba27e373613f585407b855c68ee8d5e3ed530000ab03f39c36b1d644ef languageName: node linkType: hard @@ -10979,13 +11117,6 @@ __metadata: languageName: node linkType: hard -"os-browserify@npm:^0.3.0": - version: 0.3.0 - resolution: "os-browserify@npm:0.3.0" - checksum: 10/16e37ba3c0e6a4c63443c7b55799ce4066d59104143cb637ecb9fce586d5da319cdca786ba1c867abbe3890d2cbf37953f2d51eea85e20dd6c4570d6c54bfebf - languageName: node - linkType: hard - "p-cancelable@npm:^3.0.0": version: 3.0.0 resolution: "p-cancelable@npm:3.0.0" @@ -10993,21 +11124,10 @@ __metadata: languageName: node linkType: hard -"p-limit@npm:^2.0.0": - version: 2.3.0 - resolution: "p-limit@npm:2.3.0" - dependencies: - p-try: "npm:^2.0.0" - checksum: 10/84ff17f1a38126c3314e91ecfe56aecbf36430940e2873dadaa773ffe072dc23b7af8e46d4b6485d302a11673fe94c6b67ca2cfbb60c989848b02100d0594ac1 - languageName: node - linkType: hard - -"p-limit@npm:^3.0.2": - version: 3.1.0 - resolution: "p-limit@npm:3.1.0" - dependencies: - yocto-queue: "npm:^0.1.0" - checksum: 10/7c3690c4dbf62ef625671e20b7bdf1cbc9534e83352a2780f165b0d3ceba21907e77ad63401708145ca4e25bfc51636588d89a8c0aeb715e6c37d1c066430360 +"p-finally@npm:^1.0.0": + version: 1.0.0 + resolution: "p-finally@npm:1.0.0" + checksum: 10/93a654c53dc805dd5b5891bab16eb0ea46db8f66c4bfd99336ae929323b1af2b70a8b0654f8f1eae924b2b73d037031366d645f1fd18b3d30cbd15950cc4b1d4 languageName: node linkType: hard @@ -11020,24 +11140,6 @@ __metadata: languageName: node linkType: hard -"p-locate@npm:^3.0.0": - version: 3.0.0 - resolution: "p-locate@npm:3.0.0" - dependencies: - p-limit: "npm:^2.0.0" - checksum: 10/83991734a9854a05fe9dbb29f707ea8a0599391f52daac32b86f08e21415e857ffa60f0e120bfe7ce0cc4faf9274a50239c7895fc0d0579d08411e513b83a4ae - languageName: node - linkType: hard - -"p-locate@npm:^5.0.0": - version: 5.0.0 - resolution: "p-locate@npm:5.0.0" - dependencies: - p-limit: "npm:^3.0.2" - checksum: 10/1623088f36cf1cbca58e9b61c4e62bf0c60a07af5ae1ca99a720837356b5b6c5ba3eb1b2127e47a06865fee59dd0453cad7cc844cda9d5a62ac1a5a51b7c86d3 - languageName: node - linkType: hard - "p-locate@npm:^6.0.0": version: 6.0.0 resolution: "p-locate@npm:6.0.0" @@ -11056,20 +11158,33 @@ __metadata: languageName: node linkType: hard -"p-retry@npm:^4.5.0": - version: 4.6.2 - resolution: "p-retry@npm:4.6.2" +"p-queue@npm:^6.6.2": + version: 6.6.2 + resolution: "p-queue@npm:6.6.2" dependencies: - "@types/retry": "npm:0.12.0" - retry: "npm:^0.13.1" - checksum: 10/45c270bfddaffb4a895cea16cb760dcc72bdecb6cb45fef1971fa6ea2e91ddeafddefe01e444ac73e33b1b3d5d29fb0dd18a7effb294262437221ddc03ce0f2e + eventemitter3: "npm:^4.0.4" + p-timeout: "npm:^3.2.0" + checksum: 10/60fe227ffce59fbc5b1b081305b61a2f283ff145005853702b7d4d3f99a0176bd21bb126c99a962e51fe1e01cb8aa10f0488b7bbe73b5dc2e84b5cc650b8ffd2 languageName: node linkType: hard -"p-try@npm:^2.0.0": - version: 2.2.0 - resolution: "p-try@npm:2.2.0" - checksum: 10/f8a8e9a7693659383f06aec604ad5ead237c7a261c18048a6e1b5b85a5f8a067e469aa24f5bc009b991ea3b058a87f5065ef4176793a200d4917349881216cae +"p-retry@npm:^6.2.0": + version: 6.2.1 + resolution: "p-retry@npm:6.2.1" + dependencies: + "@types/retry": "npm:0.12.2" + is-network-error: "npm:^1.0.0" + retry: "npm:^0.13.1" + checksum: 10/7104ef13703b155d70883b0d3654ecc03148407d2711a4516739cf93139e8bec383451e14925e25e3c1ae04dbace3ed53c26dc3853c1e9b9867fcbdde25f4cdc + languageName: node + linkType: hard + +"p-timeout@npm:^3.2.0": + version: 3.2.0 + resolution: "p-timeout@npm:3.2.0" + dependencies: + p-finally: "npm:^1.0.0" + checksum: 10/3dd0eaa048780a6f23e5855df3dd45c7beacff1f820476c1d0d1bcd6648e3298752ba2c877aa1c92f6453c7dd23faaf13d9f5149fc14c0598a142e2c5e8d649c languageName: node linkType: hard @@ -11092,10 +11207,10 @@ __metadata: languageName: node linkType: hard -"pako@npm:~1.0.5": - version: 1.0.11 - resolution: "pako@npm:1.0.11" - checksum: 10/1ad07210e894472685564c4d39a08717e84c2a68a70d3c1d9e657d32394ef1670e22972a433cbfe48976cb98b154ba06855dcd3fcfba77f60f1777634bec48c0 +"pako@npm:^2.1.0": + version: 2.1.0 + resolution: "pako@npm:2.1.0" + checksum: 10/38a04991d0ec4f4b92794a68b8c92bf7340692c5d980255c92148da96eb3e550df7a86a7128b5ac0c65ecddfe5ef3bbe9c6dab13e1bc315086e759b18f7c1401 languageName: node linkType: hard @@ -11118,20 +11233,6 @@ __metadata: languageName: node linkType: hard -"parse-asn1@npm:^5.0.0, parse-asn1@npm:^5.1.7": - version: 5.1.7 - resolution: "parse-asn1@npm:5.1.7" - dependencies: - asn1.js: "npm:^4.10.1" - browserify-aes: "npm:^1.2.0" - evp_bytestokey: "npm:^1.0.3" - hash-base: "npm:~3.0" - pbkdf2: "npm:^3.1.2" - safe-buffer: "npm:^5.2.1" - checksum: 10/f82c079f4d9a4d33159c7682f9c516680f4d659fde8060697a6b3c1be4795976e826d53a1e5751a81ddc800e9c6d6fa4629b59f6d1f3241ac8447a00c89a67d3 - languageName: node - linkType: hard - "parse-entities@npm:^4.0.0": version: 4.0.1 resolution: "parse-entities@npm:4.0.1" @@ -11148,7 +11249,7 @@ __metadata: languageName: node linkType: hard -"parse-json@npm:^5.0.0, parse-json@npm:^5.2.0": +"parse-json@npm:^5.2.0": version: 5.2.0 resolution: "parse-json@npm:5.2.0" dependencies: @@ -11177,13 +11278,6 @@ __metadata: languageName: node linkType: hard -"parse5@npm:^6.0.0": - version: 6.0.1 - resolution: "parse5@npm:6.0.1" - checksum: 10/dfb110581f62bd1425725a7c784ae022a24669bd0efc24b58c71fc731c4d868193e2ebd85b74cde2dbb965e4dcf07059b1e651adbec1b3b5267531bd132fdb75 - languageName: node - linkType: hard - "parse5@npm:^7.0.0": version: 7.1.2 resolution: "parse5@npm:7.1.2" @@ -11217,20 +11311,6 @@ __metadata: languageName: node linkType: hard -"path-exists@npm:^3.0.0": - version: 3.0.0 - resolution: "path-exists@npm:3.0.0" - checksum: 10/96e92643aa34b4b28d0de1cd2eba52a1c5313a90c6542d03f62750d82480e20bfa62bc865d5cfc6165f5fcd5aeb0851043c40a39be5989646f223300021bae0a - languageName: node - linkType: hard - -"path-exists@npm:^4.0.0": - version: 4.0.0 - resolution: "path-exists@npm:4.0.0" - checksum: 10/505807199dfb7c50737b057dd8d351b82c033029ab94cb10a657609e00c1bc53b951cfdbccab8de04c5584d5eff31128ce6afd3db79281874a5ef2adbba55ed1 - languageName: node - linkType: hard - "path-exists@npm:^5.0.0": version: 5.0.0 resolution: "path-exists@npm:5.0.0" @@ -11276,17 +11356,10 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:0.1.10": - version: 0.1.10 - resolution: "path-to-regexp@npm:0.1.10" - checksum: 10/894e31f1b20e592732a87db61fff5b95c892a3fe430f9ab18455ebe69ee88ef86f8eb49912e261f9926fc53da9f93b46521523e33aefd9cb0a7b0d85d7096006 - languageName: node - linkType: hard - -"path-to-regexp@npm:2.2.1": - version: 2.2.1 - resolution: "path-to-regexp@npm:2.2.1" - checksum: 10/1a7125f8c1b5904d556a29722333219df4aa779039e903efe2fbfe0cc3ae9246672846fc8ad285664020b70e434347e0bc9af691fd7d61df8eaa7b018dcd56fb +"path-to-regexp@npm:3.3.0": + version: 3.3.0 + resolution: "path-to-regexp@npm:3.3.0" + checksum: 10/8d256383af8db66233ee9027cfcbf8f5a68155efbb4f55e784279d3ab206dcaee554ddb72ff0dae97dd2882af9f7fa802634bb7cffa2e796927977e31b829259 languageName: node linkType: hard @@ -11299,6 +11372,13 @@ __metadata: languageName: node linkType: hard +"path-to-regexp@npm:~0.1.12": + version: 0.1.12 + resolution: "path-to-regexp@npm:0.1.12" + checksum: 10/2e30f6a0144679c1f95c98e166b96e6acd1e72be9417830fefc8de7ac1992147eb9a4c7acaa59119fb1b3c34eec393b2129ef27e24b2054a3906fc4fb0d1398e + languageName: node + linkType: hard + "path-type@npm:^4.0.0": version: 4.0.0 resolution: "path-type@npm:4.0.0" @@ -11316,20 +11396,6 @@ __metadata: languageName: node linkType: hard -"pbkdf2@npm:^3.1.2": - version: 3.1.3 - resolution: "pbkdf2@npm:3.1.3" - dependencies: - create-hash: "npm:~1.1.3" - create-hmac: "npm:^1.1.7" - ripemd160: "npm:=2.0.1" - safe-buffer: "npm:^5.2.1" - sha.js: "npm:^2.4.11" - to-buffer: "npm:^1.2.0" - checksum: 10/980cf2977aa84ec3166fde195a28464ab494131c0a5778fc8f20b8894410747e502159c19ef2b41842c728bc52ba49ffee6847e3ee61ac0d482689f85d8a1b30 - languageName: node - linkType: hard - "periscopic@npm:^3.0.0": version: 3.1.0 resolution: "periscopic@npm:3.1.0" @@ -11341,7 +11407,7 @@ __metadata: languageName: node linkType: hard -"picocolors@npm:^1.0.0, picocolors@npm:^1.0.1, picocolors@npm:^1.1.1": +"picocolors@npm:^1.0.0, picocolors@npm:^1.1.1": version: 1.1.1 resolution: "picocolors@npm:1.1.1" checksum: 10/e1cf46bf84886c79055fdfa9dcb3e4711ad259949e3565154b004b260cd356c5d54b31a1437ce9782624bf766272fe6b0154f5f0c744fb7af5d454d2b60db045 @@ -11349,9 +11415,9 @@ __metadata: linkType: hard "picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.2.3, picomatch@npm:^2.3.1": - version: 2.3.1 - resolution: "picomatch@npm:2.3.1" - checksum: 10/60c2595003b05e4535394d1da94850f5372c9427ca4413b71210f437f7b2ca091dbd611c45e8b37d10036fa8eade25c1b8951654f9d3973bfa66a2ff4d3b08bc + version: 2.3.2 + resolution: "picomatch@npm:2.3.2" + checksum: 10/b788ef8148a2415b9dec12f0bb350ae6a5830f8f1950e472abc2f5225494debf7d1b75eb031df0ceaea9e8ec3e7bad599e8dbf3c60d61b42be429ba41bff4426 languageName: node linkType: hard @@ -11371,12 +11437,17 @@ __metadata: languageName: node linkType: hard -"pkg-up@npm:^3.1.0": - version: 3.1.0 - resolution: "pkg-up@npm:3.1.0" +"pkijs@npm:^3.3.3": + version: 3.3.3 + resolution: "pkijs@npm:3.3.3" dependencies: - find-up: "npm:^3.0.0" - checksum: 10/5bac346b7c7c903613c057ae3ab722f320716199d753f4a7d053d38f2b5955460f3e6ab73b4762c62fd3e947f58e04f1343e92089e7bb6091c90877406fcd8c8 + "@noble/hashes": "npm:1.4.0" + asn1js: "npm:^3.0.6" + bytestreamjs: "npm:^2.0.1" + pvtsutils: "npm:^1.3.6" + pvutils: "npm:^1.1.3" + tslib: "npm:^2.8.1" + checksum: 10/51ef42d8332fcb9e81f5cca9355b1a2aeacdbd77483cacc2a1c589036887a5a1dc6e7a68abd7cca2f0d605f68e038cc877e1fae4c38b3d358fa84a327dccaadd languageName: node linkType: hard @@ -11387,10 +11458,14 @@ __metadata: languageName: node linkType: hard -"possible-typed-array-names@npm:^1.0.0": - version: 1.0.0 - resolution: "possible-typed-array-names@npm:1.0.0" - checksum: 10/8ed3e96dfeea1c5880c1f4c9cb707e5fb26e8be22f14f82ef92df20fd2004e635c62ba47fbe8f2bb63bfd80dac1474be2fb39798da8c2feba2815435d1f749af +"postcss-attribute-case-insensitive@npm:^7.0.1": + version: 7.0.1 + resolution: "postcss-attribute-case-insensitive@npm:7.0.1" + dependencies: + postcss-selector-parser: "npm:^7.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/18829dfc6dd2f6b1ca82afa8555f07ec8ac5687fe95612e353aa601b842bdec05ca78fc96016dba2b7d32607b31e085e5087fda00e1e0dfdc6c2a1b07b1b15c2 languageName: node linkType: hard @@ -11406,6 +11481,56 @@ __metadata: languageName: node linkType: hard +"postcss-clamp@npm:^4.1.0": + version: 4.1.0 + resolution: "postcss-clamp@npm:4.1.0" + dependencies: + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4.6 + checksum: 10/fb38286d3e607a8b11ef28c89272bd572a077f5a496e2838c3996697bbc4cfb8f7a5be4b4a8987e6b0223db48c9ce5683c9d840f7afe54210ab0f77127628415 + languageName: node + linkType: hard + +"postcss-color-functional-notation@npm:^7.0.12": + version: 7.0.12 + resolution: "postcss-color-functional-notation@npm:7.0.12" + dependencies: + "@csstools/css-color-parser": "npm:^3.1.0" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/c3f59571330defde7c95256dbc2756ffd298072a6b167eb6751efb2f97ac267c081d7313556e3a4615cff8a46202f3c149bb2a456326805ca0cba5173faf5aad + languageName: node + linkType: hard + +"postcss-color-hex-alpha@npm:^10.0.0": + version: 10.0.0 + resolution: "postcss-color-hex-alpha@npm:10.0.0" + dependencies: + "@csstools/utilities": "npm:^2.0.0" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/2dbbd66d76522c7d281c292589360f21806b6dd31a582484e7e4a848e5244d645d5c5e1b6c6219dd5fb7333808cd94a27dd0d2e1db093d043668ed7b42db59ad + languageName: node + linkType: hard + +"postcss-color-rebeccapurple@npm:^10.0.0": + version: 10.0.0 + resolution: "postcss-color-rebeccapurple@npm:10.0.0" + dependencies: + "@csstools/utilities": "npm:^2.0.0" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/8ca0ee2b6b45ff62abdfc9b6757d8832d398c2e47dd705759485b685f544eaed81ec00f050a1bad67ffb5e6243332085a09807d47526ce3b43456b027119e0ae + languageName: node + linkType: hard + "postcss-colormin@npm:^6.1.0": version: 6.1.0 resolution: "postcss-colormin@npm:6.1.0" @@ -11432,6 +11557,60 @@ __metadata: languageName: node linkType: hard +"postcss-custom-media@npm:^11.0.6": + version: 11.0.6 + resolution: "postcss-custom-media@npm:11.0.6" + dependencies: + "@csstools/cascade-layer-name-parser": "npm:^2.0.5" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/media-query-list-parser": "npm:^4.0.3" + peerDependencies: + postcss: ^8.4 + checksum: 10/0fa7ec309065590ce9921bb455947b0a2b8354a1e2f04dae1b3b01e1e4832fd0bb01c983d2bdfc886ceb7ba5d90ffaffc7082afa696aac350f55de0f960c3786 + languageName: node + linkType: hard + +"postcss-custom-properties@npm:^14.0.6": + version: 14.0.6 + resolution: "postcss-custom-properties@npm:14.0.6" + dependencies: + "@csstools/cascade-layer-name-parser": "npm:^2.0.5" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/utilities": "npm:^2.0.0" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/fe57781d58990f8061aac2a07c6aedb66c5715b3350af11f4eb26121ff27a735610228a7e18b243a0cfeb24bd10cb5a2359e3056d693ba69c3a09bbef8a8c461 + languageName: node + linkType: hard + +"postcss-custom-selectors@npm:^8.0.5": + version: 8.0.5 + resolution: "postcss-custom-selectors@npm:8.0.5" + dependencies: + "@csstools/cascade-layer-name-parser": "npm:^2.0.5" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + postcss-selector-parser: "npm:^7.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/191cfe62ad3eaf3d8bff75ed461baebbb3b9a52de9c1c75bded61da4ed2302d7c53c457e9febfa7cffc9a1fb7f6ed98cab8c4b2a071a1097e487e0117018e6cf + languageName: node + linkType: hard + +"postcss-dir-pseudo-class@npm:^9.0.1": + version: 9.0.1 + resolution: "postcss-dir-pseudo-class@npm:9.0.1" + dependencies: + postcss-selector-parser: "npm:^7.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/7f6212fe7f2a83e95d85df14208df3edb75b6b8f89ad865fdfbd1abf5765b6649ff46bb7ff56f7788ff8cfe60546ff305cc2fd2f9b1f9e1647a4386507714070 + languageName: node + linkType: hard + "postcss-discard-comments@npm:^6.0.2": version: 6.0.2 resolution: "postcss-discard-comments@npm:6.0.2" @@ -11479,17 +11658,108 @@ __metadata: languageName: node linkType: hard -"postcss-loader@npm:^7.3.3": - version: 7.3.3 - resolution: "postcss-loader@npm:7.3.3" +"postcss-double-position-gradients@npm:^6.0.4": + version: 6.0.4 + resolution: "postcss-double-position-gradients@npm:6.0.4" dependencies: - cosmiconfig: "npm:^8.2.0" - jiti: "npm:^1.18.2" - semver: "npm:^7.3.8" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/2840202fd819b48f713c63e1bca5e3f6b88ac5374cf2e4ebf6fabad6d5493685f9a3cde6f890ca73f978a7f5cde5ba4b6ec02659715a0b035e9be1063b74fb1f + languageName: node + linkType: hard + +"postcss-focus-visible@npm:^10.0.1": + version: 10.0.1 + resolution: "postcss-focus-visible@npm:10.0.1" + dependencies: + postcss-selector-parser: "npm:^7.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/47c038ccf139bad6a4c12cf59c5ac78acbac96ae0517ae08d5db676680d585ae7943e22328bd0d31876d6bacc24e4b717b5f809d26218d76989f7b9a44369793 + languageName: node + linkType: hard + +"postcss-focus-within@npm:^9.0.1": + version: 9.0.1 + resolution: "postcss-focus-within@npm:9.0.1" + dependencies: + postcss-selector-parser: "npm:^7.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/cfaef831e370b25787953450271096fc4dbf61de70291e38c2a3e355cb183432bcb6f4cbd8ebef34617d20cd711982a2918b8d5688ed179f43d1d2cc4cb58c7e + languageName: node + linkType: hard + +"postcss-font-variant@npm:^5.0.0": + version: 5.0.0 + resolution: "postcss-font-variant@npm:5.0.0" + peerDependencies: + postcss: ^8.1.0 + checksum: 10/738328282cf71750f6efc72d72017f938a6e76c9c49602aae4cc4337beac6d13e72a4ade608567293cb87cad2af502e6aaef652fdcc500e09b4aba38c3e32fc6 + languageName: node + linkType: hard + +"postcss-gap-properties@npm:^6.0.0": + version: 6.0.0 + resolution: "postcss-gap-properties@npm:6.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/8fa8a208fe254ddfcb0442072a6232576efa1fc3deea917be6d3a0c25dfcb855cc6806572e42a098aa0276a5ad3917f19b269409f5ce1f22d233c0072d72f823 + languageName: node + linkType: hard + +"postcss-image-set-function@npm:^7.0.0": + version: 7.0.0 + resolution: "postcss-image-set-function@npm:7.0.0" + dependencies: + "@csstools/utilities": "npm:^2.0.0" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/44c1e7d8586b36e9a55cc63dde4cc9f2b2632861d51bc8aebc1aca9045de2052b243bed3d0f9838334f3584d19bf04a4d308ed3fea671af30dd404e319fae252 + languageName: node + linkType: hard + +"postcss-lab-function@npm:^7.0.12": + version: 7.0.12 + resolution: "postcss-lab-function@npm:7.0.12" + dependencies: + "@csstools/css-color-parser": "npm:^3.1.0" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/utilities": "npm:^2.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/f767b41552cff819587a316693863393b81dd6f03f4e9f484adf166834c3032541ce128688faa4c2cf3f2f1d20adb0f3b9d76b5acc3251671f646880fcdd9900 + languageName: node + linkType: hard + +"postcss-loader@npm:^7.3.4": + version: 7.3.4 + resolution: "postcss-loader@npm:7.3.4" + dependencies: + cosmiconfig: "npm:^8.3.5" + jiti: "npm:^1.20.0" + semver: "npm:^7.5.4" peerDependencies: postcss: ^7.0.0 || ^8.0.1 webpack: ^5.0.0 - checksum: 10/743a4286db68169d271bef31e6e9351874bcf2dfa408b82c648c2d5bfba9c862cbfe3004494d927469654d6ac8b82fe647f2b80a186c1dbd44d81632eec1e838 + checksum: 10/234b01149a966a6190290c6d265b8e3df10f43262dd679451c1e7370bae74e27b746b02e660d204b901e3cf1ad28759c2679a93c64a3eb499169d8dec39df1c1 + languageName: node + linkType: hard + +"postcss-logical@npm:^8.1.0": + version: 8.1.0 + resolution: "postcss-logical@npm:8.1.0" + dependencies: + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/495ce49a1fb831eb30d848909a54430b0170ec7681dcd84da9f1cb531cad167b9fa358dc242b70c2cae5c92b1a3fbbf43e625a54c3e615ea9dcce8d15cee5926 languageName: node linkType: hard @@ -11579,36 +11849,36 @@ __metadata: languageName: node linkType: hard -"postcss-modules-extract-imports@npm:^3.0.0": - version: 3.0.0 - resolution: "postcss-modules-extract-imports@npm:3.0.0" +"postcss-modules-extract-imports@npm:^3.1.0": + version: 3.1.0 + resolution: "postcss-modules-extract-imports@npm:3.1.0" peerDependencies: postcss: ^8.1.0 - checksum: 10/8d68bb735cef4d43f9cdc1053581e6c1c864860b77fcfb670372b39c5feeee018dc5ddb2be4b07fef9bcd601edded4262418bbaeaf1bd4af744446300cebe358 + checksum: 10/00bfd3aff045fc13ded8e3bbfd8dfc73eff9a9708db1b2a132266aef6544c8d2aee7a5d7e021885f6f9bbd5565a9a9ab52990316e21ad9468a2534f87df8e849 languageName: node linkType: hard -"postcss-modules-local-by-default@npm:^4.0.3": - version: 4.0.3 - resolution: "postcss-modules-local-by-default@npm:4.0.3" +"postcss-modules-local-by-default@npm:^4.0.5": + version: 4.2.0 + resolution: "postcss-modules-local-by-default@npm:4.2.0" dependencies: icss-utils: "npm:^5.0.0" - postcss-selector-parser: "npm:^6.0.2" + postcss-selector-parser: "npm:^7.0.0" postcss-value-parser: "npm:^4.1.0" peerDependencies: postcss: ^8.1.0 - checksum: 10/4f671d77cb6a025c8be09540fea00ce2d3dbf3375a3a15b48f927325c7418d7c3c87a83bacbf81c5de6ef8bd1660d5f6f2542b98de5877355a23b739379f8c79 + checksum: 10/552329aa39fbf229b8ac5a04f8aed0b1553e7a3c10b165ee700d1deb020c071875b3df7ab5e3591f6af33d461df66d330ec9c1256229e45fc618a47c60f41536 languageName: node linkType: hard -"postcss-modules-scope@npm:^3.0.0": - version: 3.0.0 - resolution: "postcss-modules-scope@npm:3.0.0" +"postcss-modules-scope@npm:^3.2.0": + version: 3.2.1 + resolution: "postcss-modules-scope@npm:3.2.1" dependencies: - postcss-selector-parser: "npm:^6.0.4" + postcss-selector-parser: "npm:^7.0.0" peerDependencies: postcss: ^8.1.0 - checksum: 10/cc36b8111c6160a1c21ca0e82de9daf0147be95f3b5403aedd83bcaee44ee425cb62b77f677fc53d0c8d51f7981018c1c8f0a4ad3d6f0138b09326ac48c2b297 + checksum: 10/51c747fa15cedf1b2856da472985ea7a7bb510a63daf30f95f250f34fce9e28ef69b802e6cc03f9c01f69043d171bc33279109a9235847c2d3a75c44eac67334 languageName: node linkType: hard @@ -11623,6 +11893,19 @@ __metadata: languageName: node linkType: hard +"postcss-nesting@npm:^13.0.2": + version: 13.0.2 + resolution: "postcss-nesting@npm:13.0.2" + dependencies: + "@csstools/selector-resolve-nested": "npm:^3.1.0" + "@csstools/selector-specificity": "npm:^5.0.0" + postcss-selector-parser: "npm:^7.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/ac82d7d2d2621d76ca42e065b90728c91206480ef01f874c21d032c5964b723818ab13194b09c7871edee8e4220241160f72d29ece65acbfe8827937a5b6ace0 + languageName: node + linkType: hard + "postcss-normalize-charset@npm:^6.0.2": version: 6.0.2 resolution: "postcss-normalize-charset@npm:6.0.2" @@ -11721,6 +12004,15 @@ __metadata: languageName: node linkType: hard +"postcss-opacity-percentage@npm:^3.0.0": + version: 3.0.0 + resolution: "postcss-opacity-percentage@npm:3.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/dc813113f05f91f1c87ab3c125911f9e5989d1f3fc7cc5586a165901a63c0d02077d134df844391ea5624088680c6b3cee75bc33b8efdcaf340a91046e47e4e1 + languageName: node + linkType: hard + "postcss-ordered-values@npm:^6.0.2": version: 6.0.2 resolution: "postcss-ordered-values@npm:6.0.2" @@ -11733,6 +12025,129 @@ __metadata: languageName: node linkType: hard +"postcss-overflow-shorthand@npm:^6.0.0": + version: 6.0.0 + resolution: "postcss-overflow-shorthand@npm:6.0.0" + dependencies: + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/80f07e0beb97b7ac5dac590802591fc93392b0d7a9678e17998b4d34ee0cca637665232c7ea88b3a4342192bc9a2a4f5c757ad86b837a5fd59d083d37cc7da16 + languageName: node + linkType: hard + +"postcss-page-break@npm:^3.0.4": + version: 3.0.4 + resolution: "postcss-page-break@npm:3.0.4" + peerDependencies: + postcss: ^8 + checksum: 10/a7d08c945fc691f62c77ac701e64722218b14ec5c8fc1972b8af9c21553492d40808cf95e61b9697b1dacaf7e6180636876d7fee314f079e6c9e39ac1b1edc6f + languageName: node + linkType: hard + +"postcss-place@npm:^10.0.0": + version: 10.0.0 + resolution: "postcss-place@npm:10.0.0" + dependencies: + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/738cd0dc2412cf573bcfb2f7dce8e1cd21887f61c8808f55114f08fb8fbf03715e957fdd8859241eecebe400a5202771f513610b04e0f17c7742f6a5ea3bafb3 + languageName: node + linkType: hard + +"postcss-preset-env@npm:^10.2.1": + version: 10.6.1 + resolution: "postcss-preset-env@npm:10.6.1" + dependencies: + "@csstools/postcss-alpha-function": "npm:^1.0.1" + "@csstools/postcss-cascade-layers": "npm:^5.0.2" + "@csstools/postcss-color-function": "npm:^4.0.12" + "@csstools/postcss-color-function-display-p3-linear": "npm:^1.0.1" + "@csstools/postcss-color-mix-function": "npm:^3.0.12" + "@csstools/postcss-color-mix-variadic-function-arguments": "npm:^1.0.2" + "@csstools/postcss-content-alt-text": "npm:^2.0.8" + "@csstools/postcss-contrast-color-function": "npm:^2.0.12" + "@csstools/postcss-exponential-functions": "npm:^2.0.9" + "@csstools/postcss-font-format-keywords": "npm:^4.0.0" + "@csstools/postcss-gamut-mapping": "npm:^2.0.11" + "@csstools/postcss-gradients-interpolation-method": "npm:^5.0.12" + "@csstools/postcss-hwb-function": "npm:^4.0.12" + "@csstools/postcss-ic-unit": "npm:^4.0.4" + "@csstools/postcss-initial": "npm:^2.0.1" + "@csstools/postcss-is-pseudo-class": "npm:^5.0.3" + "@csstools/postcss-light-dark-function": "npm:^2.0.11" + "@csstools/postcss-logical-float-and-clear": "npm:^3.0.0" + "@csstools/postcss-logical-overflow": "npm:^2.0.0" + "@csstools/postcss-logical-overscroll-behavior": "npm:^2.0.0" + "@csstools/postcss-logical-resize": "npm:^3.0.0" + "@csstools/postcss-logical-viewport-units": "npm:^3.0.4" + "@csstools/postcss-media-minmax": "npm:^2.0.9" + "@csstools/postcss-media-queries-aspect-ratio-number-values": "npm:^3.0.5" + "@csstools/postcss-nested-calc": "npm:^4.0.0" + "@csstools/postcss-normalize-display-values": "npm:^4.0.1" + "@csstools/postcss-oklab-function": "npm:^4.0.12" + "@csstools/postcss-position-area-property": "npm:^1.0.0" + "@csstools/postcss-progressive-custom-properties": "npm:^4.2.1" + "@csstools/postcss-property-rule-prelude-list": "npm:^1.0.0" + "@csstools/postcss-random-function": "npm:^2.0.1" + "@csstools/postcss-relative-color-syntax": "npm:^3.0.12" + "@csstools/postcss-scope-pseudo-class": "npm:^4.0.1" + "@csstools/postcss-sign-functions": "npm:^1.1.4" + "@csstools/postcss-stepped-value-functions": "npm:^4.0.9" + "@csstools/postcss-syntax-descriptor-syntax-production": "npm:^1.0.1" + "@csstools/postcss-system-ui-font-family": "npm:^1.0.0" + "@csstools/postcss-text-decoration-shorthand": "npm:^4.0.3" + "@csstools/postcss-trigonometric-functions": "npm:^4.0.9" + "@csstools/postcss-unset-value": "npm:^4.0.0" + autoprefixer: "npm:^10.4.23" + browserslist: "npm:^4.28.1" + css-blank-pseudo: "npm:^7.0.1" + css-has-pseudo: "npm:^7.0.3" + css-prefers-color-scheme: "npm:^10.0.0" + cssdb: "npm:^8.6.0" + postcss-attribute-case-insensitive: "npm:^7.0.1" + postcss-clamp: "npm:^4.1.0" + postcss-color-functional-notation: "npm:^7.0.12" + postcss-color-hex-alpha: "npm:^10.0.0" + postcss-color-rebeccapurple: "npm:^10.0.0" + postcss-custom-media: "npm:^11.0.6" + postcss-custom-properties: "npm:^14.0.6" + postcss-custom-selectors: "npm:^8.0.5" + postcss-dir-pseudo-class: "npm:^9.0.1" + postcss-double-position-gradients: "npm:^6.0.4" + postcss-focus-visible: "npm:^10.0.1" + postcss-focus-within: "npm:^9.0.1" + postcss-font-variant: "npm:^5.0.0" + postcss-gap-properties: "npm:^6.0.0" + postcss-image-set-function: "npm:^7.0.0" + postcss-lab-function: "npm:^7.0.12" + postcss-logical: "npm:^8.1.0" + postcss-nesting: "npm:^13.0.2" + postcss-opacity-percentage: "npm:^3.0.0" + postcss-overflow-shorthand: "npm:^6.0.0" + postcss-page-break: "npm:^3.0.4" + postcss-place: "npm:^10.0.0" + postcss-pseudo-class-any-link: "npm:^10.0.1" + postcss-replace-overflow-wrap: "npm:^4.0.0" + postcss-selector-not: "npm:^8.0.1" + peerDependencies: + postcss: ^8.4 + checksum: 10/e18aa351e18262a9f086a40ce15de4763f8e9e208cb8b92f05ce15ebeaca740b568409c5309d3289a7f880a81ec51b166644a4fa5a82bb4180a5fb24213a646b + languageName: node + linkType: hard + +"postcss-pseudo-class-any-link@npm:^10.0.1": + version: 10.0.1 + resolution: "postcss-pseudo-class-any-link@npm:10.0.1" + dependencies: + postcss-selector-parser: "npm:^7.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/376525d1a6fa223d908deb884b93d5cb76f4fa7431c090a8ada63e5ee9657bec7bf8e23eff1c36264c051c5a653928e38392165a862b7c5bf5e39e9364383fce + languageName: node + linkType: hard + "postcss-reduce-idents@npm:^6.0.3": version: 6.0.3 resolution: "postcss-reduce-idents@npm:6.0.3" @@ -11767,7 +12182,27 @@ __metadata: languageName: node linkType: hard -"postcss-selector-parser@npm:^6.0.11, postcss-selector-parser@npm:^6.0.16, postcss-selector-parser@npm:^6.0.2, postcss-selector-parser@npm:^6.0.4": +"postcss-replace-overflow-wrap@npm:^4.0.0": + version: 4.0.0 + resolution: "postcss-replace-overflow-wrap@npm:4.0.0" + peerDependencies: + postcss: ^8.0.3 + checksum: 10/0629ec17deae65e27dc3059ecec1c6bc833ee65291093b476fce151ab0af45c9e1a56ce250eb9ec4bbc306c19ab318cc982fdbcca8651d347d7dfaa3c9fc9201 + languageName: node + linkType: hard + +"postcss-selector-not@npm:^8.0.1": + version: 8.0.1 + resolution: "postcss-selector-not@npm:8.0.1" + dependencies: + postcss-selector-parser: "npm:^7.0.0" + peerDependencies: + postcss: ^8.4 + checksum: 10/28c1f7863ac85016ecd695304ee1eb21b1128eacba333d6d4540fd93691c58ff6329ac323b6a640f2da918e95c7b58e8f534c8b6e2ed016f6e31cdfdc743edbc + languageName: node + linkType: hard + +"postcss-selector-parser@npm:^6.0.11, postcss-selector-parser@npm:^6.0.16": version: 6.1.2 resolution: "postcss-selector-parser@npm:6.1.2" dependencies: @@ -11777,6 +12212,16 @@ __metadata: languageName: node linkType: hard +"postcss-selector-parser@npm:^7.0.0": + version: 7.1.1 + resolution: "postcss-selector-parser@npm:7.1.1" + dependencies: + cssesc: "npm:^3.0.0" + util-deprecate: "npm:^1.0.2" + checksum: 10/bb3c6455b20af26a556e3021e21101d8470252644e673c1612f7348ff8dd41b11321329f0694cf299b5b94863f823480b72d3e2f4bd3a89dc43e2d8c0dbad341 + languageName: node + linkType: hard + "postcss-sort-media-queries@npm:^5.2.0": version: 5.2.0 resolution: "postcss-sort-media-queries@npm:5.2.0" @@ -11827,34 +12272,34 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.4.21, postcss@npm:^8.4.24, postcss@npm:^8.4.26, postcss@npm:^8.4.38": - version: 8.4.41 - resolution: "postcss@npm:8.4.41" +"postcss@npm:^8.4.21, postcss@npm:^8.4.24, postcss@npm:^8.4.33, postcss@npm:^8.5.4": + version: 8.5.6 + resolution: "postcss@npm:8.5.6" dependencies: - nanoid: "npm:^3.3.7" - picocolors: "npm:^1.0.1" - source-map-js: "npm:^1.2.0" - checksum: 10/6e6176c2407eff60493ca60a706c6b7def20a722c3adda94ea1ece38345eb99964191336fd62b62652279cec6938e79e0b1e1d477142c8d3516e7a725a74ee37 + nanoid: "npm:^3.3.11" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10/9e4fbe97574091e9736d0e82a591e29aa100a0bf60276a926308f8c57249698935f35c5d2f4e80de778d0cbb8dcffab4f383d85fd50c5649aca421c3df729b86 languageName: node linkType: hard -"postman-code-generators@npm:^1.10.1": - version: 1.14.1 - resolution: "postman-code-generators@npm:1.14.1" +"postman-code-generators@npm:^2.0.0": + version: 2.1.0 + resolution: "postman-code-generators@npm:2.1.0" dependencies: async: "npm:3.2.2" detect-package-manager: "npm:3.0.2" lodash: "npm:4.17.21" path: "npm:0.12.7" - postman-collection: "npm:^4.4.0" + postman-collection: "npm:^5.0.0" shelljs: "npm:0.8.5" - checksum: 10/48d6a45a41ddaf9615a11b035daddabb53685c902d7059e39eea7fa7452b2c08c7020832812f0f9dfbbb5196b460a7647b06840649ba8035d80a7ddd477ff188 + checksum: 10/44333af42f96b635930dfa2ee1846f535d1165de51b7c22c773ac21d3655b3af0e7e7973ba9f5c32f4f9c3bdd1c487a40c9ad4284012c437daa01d1b4b31b7bc languageName: node linkType: hard -"postman-collection@npm:^4.4.0": - version: 4.5.0 - resolution: "postman-collection@npm:4.5.0" +"postman-collection@npm:^5.0.0, postman-collection@npm:^5.0.2": + version: 5.2.0 + resolution: "postman-collection@npm:5.2.0" dependencies: "@faker-js/faker": "npm:5.5.3" file-type: "npm:3.9.0" @@ -11862,21 +12307,21 @@ __metadata: iconv-lite: "npm:0.6.3" liquid-json: "npm:0.3.1" lodash: "npm:4.17.21" - mime-format: "npm:2.0.1" - mime-types: "npm:2.1.35" - postman-url-encoder: "npm:3.0.5" - semver: "npm:7.6.3" + mime: "npm:3.0.0" + mime-format: "npm:2.0.2" + postman-url-encoder: "npm:3.0.8" + semver: "npm:7.7.1" uuid: "npm:8.3.2" - checksum: 10/5d1d0a2a7e5450bb62130f511abb4aca02f1a46d9379cc921be39161368a65fb4ed6d40abadfc6510ce8d61d8080f16a815120e80f2bb29fa4b6b44d12f3f6b8 + checksum: 10/7b4feb1c09cd225ad6440488027b9c95329ee7ad3289f96e2d7454c779fc7d19e98157773889ecf72c1a41a695ab7b63ac8a83936a71a01ad09773fc4196ba92 languageName: node linkType: hard -"postman-url-encoder@npm:3.0.5": - version: 3.0.5 - resolution: "postman-url-encoder@npm:3.0.5" +"postman-url-encoder@npm:3.0.8": + version: 3.0.8 + resolution: "postman-url-encoder@npm:3.0.8" dependencies: - punycode: "npm:^2.1.1" - checksum: 10/e4bb3698c9e19cccc33d249b0f45e93645cab043b7a56590f3a25f5c2daf3caa99fdae7801cc12d60e1ec6158ddd75a31c16834a3b4a03d5784f1768f8cfd026 + punycode: "npm:^2.3.1" + checksum: 10/9873c95137ef3f69081cfa466ca8ee8793385770a82889e15524592607f05782bcbf385a69afe31d5730de2886e8f0071728443baca0c5bbf32608dc8dda79b3 languageName: node linkType: hard @@ -11906,7 +12351,7 @@ __metadata: languageName: node linkType: hard -"prism-react-renderer@npm:^2.1.0, prism-react-renderer@npm:^2.3.0, prism-react-renderer@npm:^2.4.0": +"prism-react-renderer@npm:^2.1.0, prism-react-renderer@npm:^2.3.0, prism-react-renderer@npm:^2.4.0, prism-react-renderer@npm:^2.4.1": version: 2.4.1 resolution: "prism-react-renderer@npm:2.4.1" dependencies: @@ -11966,7 +12411,7 @@ __metadata: languageName: node linkType: hard -"prop-types@npm:^15.0.0, prop-types@npm:^15.6.2, prop-types@npm:^15.7.2": +"prop-types@npm:^15.6.2, prop-types@npm:^15.7.2": version: 15.8.1 resolution: "prop-types@npm:15.8.1" dependencies: @@ -12001,28 +12446,14 @@ __metadata: languageName: node linkType: hard -"public-encrypt@npm:^4.0.3": - version: 4.0.3 - resolution: "public-encrypt@npm:4.0.3" - dependencies: - bn.js: "npm:^4.1.0" - browserify-rsa: "npm:^4.0.0" - create-hash: "npm:^1.1.0" - parse-asn1: "npm:^5.0.0" - randombytes: "npm:^2.0.1" - safe-buffer: "npm:^5.1.2" - checksum: 10/059d64da8ba9ea0733377d23b57b6cbe5be663c8eb187b9c051eec85f799ff95c4e194eb3a69db07cc1f73a2a63519e67716ae9b8630e13e7149840d0abe044d - languageName: node - linkType: hard - -"punycode@npm:^1.3.2, punycode@npm:^1.4.1": +"punycode@npm:^1.4.1": version: 1.4.1 resolution: "punycode@npm:1.4.1" checksum: 10/af2700dde1a116791ff8301348ff344c47d6c224e875057237d1b5112035655fb07a6175cfdb8bf0e3a8cdfd2dc82b3a622e0aefd605566c0e949a6d0d1256a4 languageName: node linkType: hard -"punycode@npm:^2.1.0, punycode@npm:^2.1.1, punycode@npm:^2.3.0": +"punycode@npm:^2.1.0, punycode@npm:^2.3.1": version: 2.3.1 resolution: "punycode@npm:2.3.1" checksum: 10/febdc4362bead22f9e2608ff0171713230b57aff9dddc1c273aa2a651fbd366f94b7d6a71d78342a7c0819906750351ca7f2edd26ea41b626d87d6a13d1bd059 @@ -12038,28 +12469,28 @@ __metadata: languageName: node linkType: hard -"qs@npm:6.13.0": - version: 6.13.0 - resolution: "qs@npm:6.13.0" +"pvtsutils@npm:^1.3.6": + version: 1.3.6 + resolution: "pvtsutils@npm:1.3.6" dependencies: - side-channel: "npm:^1.0.6" - checksum: 10/f548b376e685553d12e461409f0d6e5c59ec7c7d76f308e2a888fd9db3e0c5e89902bedd0754db3a9038eda5f27da2331a6f019c8517dc5e0a16b3c9a6e9cef8 + tslib: "npm:^2.8.1" + checksum: 10/d45b12f8526e13ecf15fe09b30cde65501f3300fd2a07c11b28a966d434d1f767c8a61597ecba2e19c7eb19ca0c740341a6babc67a4f741e08b1ef1095c71663 languageName: node linkType: hard -"qs@npm:^6.12.3": - version: 6.14.0 - resolution: "qs@npm:6.14.0" +"pvutils@npm:^1.1.3": + version: 1.1.5 + resolution: "pvutils@npm:1.1.5" + checksum: 10/9a5a71603c72bf9ea3a4501e8251e3f7a56026ed059bf63a18bd9a30cac6c35cc8250b39eb6291c1cb204cdeb6660663ab9bb2c74e85a512919bb2d614e340ea + languageName: node + linkType: hard + +"qs@npm:^6.12.3, qs@npm:~6.14.0": + version: 6.14.2 + resolution: "qs@npm:6.14.2" dependencies: side-channel: "npm:^1.1.0" - checksum: 10/a60e49bbd51c935a8a4759e7505677b122e23bf392d6535b8fc31c1e447acba2c901235ecb192764013cd2781723dc1f61978b5fdd93cc31d7043d31cdc01974 - languageName: node - linkType: hard - -"querystring-es3@npm:^0.2.1": - version: 0.2.1 - resolution: "querystring-es3@npm:0.2.1" - checksum: 10/c99fccfe1a9c4c25ea6194fa7a559fdb83d2628f118f898af6f0ac02c4ffcd7e0576997bb80e7dfa892d193988b60e23d4968122426351819f87051862af991c + checksum: 10/682933a85bb4b7bd0d66e13c0a40d9e612b5e4bcc2cb9238f711a9368cd22d91654097a74fff93551e58146db282c56ac094957dfdc60ce64ea72c3c9d7779ac languageName: node linkType: hard @@ -12070,15 +12501,6 @@ __metadata: languageName: node linkType: hard -"queue@npm:6.0.2": - version: 6.0.2 - resolution: "queue@npm:6.0.2" - dependencies: - inherits: "npm:~2.0.3" - checksum: 10/3437954ef1442c86ff01a0fbe3dc6222838823b1ca97f37eff651bc20b868c0c2904424ef2c0d44cba46055f54b578f92866e573125dc9a5e8823d751e4d1585 - languageName: node - linkType: hard - "quick-lru@npm:^5.1.1": version: 5.1.1 resolution: "quick-lru@npm:5.1.1" @@ -12086,7 +12508,7 @@ __metadata: languageName: node linkType: hard -"randombytes@npm:^2.0.0, randombytes@npm:^2.0.1, randombytes@npm:^2.0.5, randombytes@npm:^2.1.0": +"randombytes@npm:^2.1.0": version: 2.1.0 resolution: "randombytes@npm:2.1.0" dependencies: @@ -12095,16 +12517,6 @@ __metadata: languageName: node linkType: hard -"randomfill@npm:^1.0.4": - version: 1.0.4 - resolution: "randomfill@npm:1.0.4" - dependencies: - randombytes: "npm:^2.0.5" - safe-buffer: "npm:^5.1.0" - checksum: 10/33734bb578a868d29ee1b8555e21a36711db084065d94e019a6d03caa67debef8d6a1bfd06a2b597e32901ddc761ab483a85393f0d9a75838f1912461d4dbfc7 - languageName: node - linkType: hard - "range-parser@npm:1.2.0": version: 1.2.0 resolution: "range-parser@npm:1.2.0" @@ -12119,15 +12531,15 @@ __metadata: languageName: node linkType: hard -"raw-body@npm:2.5.2": - version: 2.5.2 - resolution: "raw-body@npm:2.5.2" +"raw-body@npm:~2.5.3": + version: 2.5.3 + resolution: "raw-body@npm:2.5.3" dependencies: - bytes: "npm:3.1.2" - http-errors: "npm:2.0.0" - iconv-lite: "npm:0.4.24" - unpipe: "npm:1.0.0" - checksum: 10/863b5171e140546a4d99f349b720abac4410338e23df5e409cfcc3752538c9caf947ce382c89129ba976f71894bd38b5806c774edac35ebf168d02aa1ac11a95 + bytes: "npm:~3.1.2" + http-errors: "npm:~2.0.1" + iconv-lite: "npm:~0.4.24" + unpipe: "npm:~1.0.0" + checksum: 10/f35759fe5a6548e7c529121ead1de4dd163f899749a5896c42e278479df2d9d7f98b5bb17312737c03617765e5a1433e586f717616e5cfbebc13b4738b820601 languageName: node linkType: hard @@ -12145,38 +12557,6 @@ __metadata: languageName: node linkType: hard -"react-dev-utils@npm:^12.0.1": - version: 12.0.1 - resolution: "react-dev-utils@npm:12.0.1" - dependencies: - "@babel/code-frame": "npm:^7.16.0" - address: "npm:^1.1.2" - browserslist: "npm:^4.18.1" - chalk: "npm:^4.1.2" - cross-spawn: "npm:^7.0.3" - detect-port-alt: "npm:^1.1.6" - escape-string-regexp: "npm:^4.0.0" - filesize: "npm:^8.0.6" - find-up: "npm:^5.0.0" - fork-ts-checker-webpack-plugin: "npm:^6.5.0" - global-modules: "npm:^2.0.0" - globby: "npm:^11.0.4" - gzip-size: "npm:^6.0.0" - immer: "npm:^9.0.7" - is-root: "npm:^2.1.0" - loader-utils: "npm:^3.2.0" - open: "npm:^8.4.0" - pkg-up: "npm:^3.1.0" - prompts: "npm:^2.4.2" - react-error-overlay: "npm:^6.0.11" - recursive-readdir: "npm:^2.2.2" - shell-quote: "npm:^1.7.3" - strip-ansi: "npm:^6.0.1" - text-table: "npm:^0.2.0" - checksum: 10/4f6e04a3c4c6bc041bb85586646cff5e611049dd91f505e73cec47e284a854f28a25a4f50ff24b46e7df051b2a82c387870c8e08da232edbbbb36c01d4e94a2b - languageName: node - linkType: hard - "react-dom@npm:^18.0.2": version: 18.3.1 resolution: "react-dom@npm:18.3.1" @@ -12189,13 +12569,6 @@ __metadata: languageName: node linkType: hard -"react-error-overlay@npm:^6.0.11": - version: 6.0.11 - resolution: "react-error-overlay@npm:6.0.11" - checksum: 10/b4ac746fc4fb50da733768aadbc638d34dd56d4e46ed4b2f2d1ac54dced0c5fa5fe47ebbbf90810ada44056ed0713bba5b9b930b69f4e45466e7f59fc806c44e - languageName: node - linkType: hard - "react-fast-compare@npm:^3.2.0": version: 3.2.0 resolution: "react-fast-compare@npm:3.2.0" @@ -12203,9 +12576,9 @@ __metadata: languageName: node linkType: hard -"react-helmet-async@npm:*, react-helmet-async@npm:^1.3.0": +"react-helmet-async@npm:@slorber/react-helmet-async@1.3.0": version: 1.3.0 - resolution: "react-helmet-async@npm:1.3.0" + resolution: "@slorber/react-helmet-async@npm:1.3.0" dependencies: "@babel/runtime": "npm:^7.12.5" invariant: "npm:^2.2.4" @@ -12213,18 +12586,18 @@ __metadata: react-fast-compare: "npm:^3.2.0" shallowequal: "npm:^1.1.0" peerDependencies: - react: ^16.6.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.6.0 || ^17.0.0 || ^18.0.0 - checksum: 10/73d6383dd5d5794cad3837cf6b71d7e23afa6f3ba745e50a9d0d6bf42ff0ab175e4292f250ffe757f4bd782e64c37c4583fb884340cd63891deb33e144628661 + react: ^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10/76854c3a9220e1adc7b4aece55926146583d43b6bf08905d8cb6a7e3ee0ac60f7a03b285c2bb6c4aa3110e8d048e5dee4e3bdcea9c4b9b5c00db67ba002b95ce languageName: node linkType: hard -"react-hook-form@npm:^7.43.8": - version: 7.54.2 - resolution: "react-hook-form@npm:7.54.2" +"react-hook-form@npm:^7.59.0": + version: 7.71.1 + resolution: "react-hook-form@npm:7.71.1" peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 - checksum: 10/b156d15b6246c76d0275e5722d9056014693e014d0e3dec06e44bf2672ee549aaba4366de5144d18c4cab29e631f3b2b84269d4fd5727ca17aad9b970fde6960 + checksum: 10/54b56aa52a6ac17fb5aa68ce072b08f50b75c2705bf878069f817c346e464c1ebfbf67be372d94cf8e620fa07ca8e64ffca71bb69071dd0290a1ef8f43eefb9d languageName: node linkType: hard @@ -12235,26 +12608,12 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^17.0.2": - version: 17.0.2 - resolution: "react-is@npm:17.0.2" - checksum: 10/73b36281e58eeb27c9cc6031301b6ae19ecdc9f18ae2d518bdb39b0ac564e65c5779405d623f1df9abf378a13858b79442480244bd579968afc1faf9a2ce5e05 - languageName: node - linkType: hard - -"react-is@npm:^18.0.0": - version: 18.3.1 - resolution: "react-is@npm:18.3.1" - checksum: 10/d5f60c87d285af24b1e1e7eaeb123ec256c3c8bdea7061ab3932e3e14685708221bf234ec50b21e10dd07f008f1b966a2730a0ce4ff67905b3872ff2042aec22 - languageName: node - linkType: hard - -"react-json-view-lite@npm:^1.2.0": - version: 1.2.1 - resolution: "react-json-view-lite@npm:1.2.1" +"react-json-view-lite@npm:^2.3.0": + version: 2.5.0 + resolution: "react-json-view-lite@npm:2.5.0" peerDependencies: - react: ^16.13.1 || ^17.0.0 || ^18.0.0 - checksum: 10/2d78abee0ec5f3912d2778816cdbb7c8cafff2d021ae5cfc45baa8c0fcd0a54e969ee4a7c56ba3b4b551e789bc4a4a0b1e63652030d502488ed123790c2a7f0d + react: ^18.0.0 || ^19.0.0 + checksum: 10/196a989d3ecb6d662baeb51260f2cf1e1391e109db405343019c4fa30da1c1e6fc9c0b9aa464444b16cb5ef6867b49db547073aa73abb36b2d7d976e4dcc0dc4 languageName: node linkType: hard @@ -12265,7 +12624,7 @@ __metadata: languageName: node linkType: hard -"react-live@npm:^4.0.0": +"react-live@npm:^4.1.8": version: 4.1.8 resolution: "react-live@npm:4.1.8" dependencies: @@ -12309,33 +12668,29 @@ __metadata: languageName: node linkType: hard -"react-markdown@npm:^8.0.1": - version: 8.0.7 - resolution: "react-markdown@npm:8.0.7" +"react-markdown@npm:^10.1.0": + version: 10.1.0 + resolution: "react-markdown@npm:10.1.0" dependencies: - "@types/hast": "npm:^2.0.0" - "@types/prop-types": "npm:^15.0.0" - "@types/unist": "npm:^2.0.0" - comma-separated-tokens: "npm:^2.0.0" - hast-util-whitespace: "npm:^2.0.0" - prop-types: "npm:^15.0.0" - property-information: "npm:^6.0.0" - react-is: "npm:^18.0.0" - remark-parse: "npm:^10.0.0" - remark-rehype: "npm:^10.0.0" - space-separated-tokens: "npm:^2.0.0" - style-to-object: "npm:^0.4.0" - unified: "npm:^10.0.0" - unist-util-visit: "npm:^4.0.0" - vfile: "npm:^5.0.0" + "@types/hast": "npm:^3.0.0" + "@types/mdast": "npm:^4.0.0" + devlop: "npm:^1.0.0" + hast-util-to-jsx-runtime: "npm:^2.0.0" + html-url-attributes: "npm:^3.0.0" + mdast-util-to-hast: "npm:^13.0.0" + remark-parse: "npm:^11.0.0" + remark-rehype: "npm:^11.0.0" + unified: "npm:^11.0.0" + unist-util-visit: "npm:^5.0.0" + vfile: "npm:^6.0.0" peerDependencies: - "@types/react": ">=16" - react: ">=16" - checksum: 10/5702a2ef0b8a8cb0a085bb5101810d7446e818f7b76291238eff73cce5aaea65b95ffa28f9b4127d1fc785b6cfe0790bba261b11c5a69655ff901399d8ea6896 + "@types/react": ">=18" + react: ">=18" + checksum: 10/886c037d18266e94750abbc00c6096435bccd4da5f2d1727984bcbdf83e9f2a5cc9a6b0eecfe6c30456dc26546fee41c7f6aecbf176e6d7453963df4abefd7df languageName: node linkType: hard -"react-modal@npm:^3.15.1": +"react-modal@npm:^3.16.3": version: 3.16.3 resolution: "react-modal@npm:3.16.3" dependencies: @@ -12350,24 +12705,22 @@ __metadata: languageName: node linkType: hard -"react-redux@npm:^7.2.0": - version: 7.2.9 - resolution: "react-redux@npm:7.2.9" +"react-redux@npm:^9.2.0": + version: 9.2.0 + resolution: "react-redux@npm:9.2.0" dependencies: - "@babel/runtime": "npm:^7.15.4" - "@types/react-redux": "npm:^7.1.20" - hoist-non-react-statics: "npm:^3.3.2" - loose-envify: "npm:^1.4.0" - prop-types: "npm:^15.7.2" - react-is: "npm:^17.0.2" + "@types/use-sync-external-store": "npm:^0.0.6" + use-sync-external-store: "npm:^1.4.0" peerDependencies: - react: ^16.8.3 || ^17 || ^18 + "@types/react": ^18.2.25 || ^19 + react: ^18.0 || ^19 + redux: ^5.0.0 peerDependenciesMeta: - react-dom: + "@types/react": optional: true - react-native: + redux: optional: true - checksum: 10/1c3018bd2601e6d18339281867910b583dcbb3d8856403086e08c00abf0dfe467a94c0d1356bafa8cdf107bf1e2c9899a28486e4778e85c8bc4dfed2076b116f + checksum: 10/b3d2f89f469169475ab0a9f8914d54a336ac9bc6a31af6e8dcfe9901e6fe2cfd8c1a3f6ce7a2f7f3e0928a93fbab833b668804155715598b7f2ad89927d3ff50 languageName: node linkType: hard @@ -12428,7 +12781,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^2.0.1, readable-stream@npm:^2.3.8": +"readable-stream@npm:^2.0.1": version: 2.3.8 resolution: "readable-stream@npm:2.3.8" dependencies: @@ -12443,7 +12796,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^3.0.6, readable-stream@npm:^3.5.0, readable-stream@npm:^3.6.0": +"readable-stream@npm:^3.0.6, readable-stream@npm:^3.6.0": version: 3.6.2 resolution: "readable-stream@npm:3.6.2" dependencies: @@ -12454,19 +12807,6 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^4.4.2": - version: 4.7.0 - resolution: "readable-stream@npm:4.7.0" - dependencies: - abort-controller: "npm:^3.0.0" - buffer: "npm:^6.0.3" - events: "npm:^3.3.0" - process: "npm:^0.11.10" - string_decoder: "npm:^1.3.0" - checksum: 10/bdf096c8ff59452ce5d08f13da9597f9fcfe400b4facfaa88e74ec057e5ad1fdfa140ffe28e5ed806cf4d2055f0b812806e962bca91dce31bc4cef08e53be3a4 - languageName: node - linkType: hard - "readdirp@npm:^4.0.1": version: 4.0.1 resolution: "readdirp@npm:4.0.1" @@ -12483,13 +12823,6 @@ __metadata: languageName: node linkType: hard -"reading-time@npm:^1.5.0": - version: 1.5.0 - resolution: "reading-time@npm:1.5.0" - checksum: 10/d52921d2563693f34e71ecc6ec97bd48ec960c44dff04384e56c47ee68cfa36749acbcaeec4d0cd1d18113e53ae67825bb067ea63ba1f86107e289573e5f584f - languageName: node - linkType: hard - "rechoir@npm:^0.6.2": version: 0.6.2 resolution: "rechoir@npm:0.6.2" @@ -12499,30 +12832,26 @@ __metadata: languageName: node linkType: hard -"recursive-readdir@npm:^2.2.2": - version: 2.2.3 - resolution: "recursive-readdir@npm:2.2.3" - dependencies: - minimatch: "npm:^3.0.5" - checksum: 10/19298852b0b87810aed5f2c81a73bfaaeb9ade7c9bf363f350fc1443f2cc3df66ecade5e102dfbb153fcd9df20342c301848e11e149e5f78759c1d55aa2c9c39 - languageName: node - linkType: hard - -"redux-thunk@npm:^2.4.2": - version: 2.4.2 - resolution: "redux-thunk@npm:2.4.2" +"redux-thunk@npm:^3.1.0": + version: 3.1.0 + resolution: "redux-thunk@npm:3.1.0" peerDependencies: - redux: ^4 - checksum: 10/9bcb1193835128ecebf1e1a1b1a37bc15e8dfbdf6b6ee1b5566dd4c8e4ca05a81175f0c6dda34ab47f87053cd13b74d9f881d59446691d7b192831852b5d7a72 + redux: ^5.0.0 + checksum: 10/38c563db5f0bbec90d2e65cc27f3c870c1b6102e0c071258734fac41cb0e51d31d894125815c2f4133b20aff231f51f028ad99bccc05a7e3249f1a5d5a959ed3 languageName: node linkType: hard -"redux@npm:^4.0.0, redux@npm:^4.2.1": - version: 4.2.1 - resolution: "redux@npm:4.2.1" - dependencies: - "@babel/runtime": "npm:^7.9.2" - checksum: 10/371e4833b671193303a7dea7803c8fdc8e0d566740c78f580e0a3b77b4161da25037626900a2205a5d616117fa6ad09a4232e5a110bd437186b5c6355a041750 +"redux@npm:^5.0.1": + version: 5.0.1 + resolution: "redux@npm:5.0.1" + checksum: 10/a373f9ed65693ead58bea5ef61c1d6bef39da9f2706db3be6f84815f3a1283230ecd1184efb1b3daa7f807d8211b0181564ca8f336fc6ee0b1e2fa0ba06737c2 + languageName: node + linkType: hard + +"reflect-metadata@npm:^0.2.2": + version: 0.2.2 + resolution: "reflect-metadata@npm:0.2.2" + checksum: 10/1c93f9ac790fea1c852fde80c91b2760420069f4862f28e6fae0c00c6937a56508716b0ed2419ab02869dd488d123c4ab92d062ae84e8739ea7417fae10c4745 languageName: node linkType: hard @@ -12533,12 +12862,12 @@ __metadata: languageName: node linkType: hard -"regenerate-unicode-properties@npm:^10.1.0": - version: 10.1.0 - resolution: "regenerate-unicode-properties@npm:10.1.0" +"regenerate-unicode-properties@npm:^10.2.2": + version: 10.2.2 + resolution: "regenerate-unicode-properties@npm:10.2.2" dependencies: regenerate: "npm:^1.4.2" - checksum: 10/25b268659898955ad105267b4efba20e361e27b233670694b683728a2800314bec3053918d3bf71b0604376fd76fe9bc9c6f80379cfb6d1e209a58de44101aac + checksum: 10/5041ee31185c4700de9dd76783fab9def51c412751190d523d621db5b8e35a6c2d91f1642c12247e7d94f84b8ae388d044baac1e88fc2ba0ac215ca8dc7bed38 languageName: node linkType: hard @@ -12549,33 +12878,17 @@ __metadata: languageName: node linkType: hard -"regenerator-runtime@npm:^0.14.0": - version: 0.14.0 - resolution: "regenerator-runtime@npm:0.14.0" - checksum: 10/6c19495baefcf5fbb18a281b56a97f0197b5f219f42e571e80877f095320afac0bdb31dab8f8186858e6126950068c3f17a1226437881e3e70446ea66751897c - languageName: node - linkType: hard - -"regenerator-transform@npm:^0.15.2": - version: 0.15.2 - resolution: "regenerator-transform@npm:0.15.2" +"regexpu-core@npm:^6.3.1": + version: 6.4.0 + resolution: "regexpu-core@npm:6.4.0" dependencies: - "@babel/runtime": "npm:^7.8.4" - checksum: 10/c4fdcb46d11bbe32605b4b9ed76b21b8d3f241a45153e9dc6f5542fed4c7744fed459f42701f650d5d5956786bf7de57547329d1c05a9df2ed9e367b9d903302 - languageName: node - linkType: hard - -"regexpu-core@npm:^5.3.1": - version: 5.3.2 - resolution: "regexpu-core@npm:5.3.2" - dependencies: - "@babel/regjsgen": "npm:^0.8.0" regenerate: "npm:^1.4.2" - regenerate-unicode-properties: "npm:^10.1.0" - regjsparser: "npm:^0.9.1" + regenerate-unicode-properties: "npm:^10.2.2" + regjsgen: "npm:^0.8.0" + regjsparser: "npm:^0.13.0" unicode-match-property-ecmascript: "npm:^2.0.0" - unicode-match-property-value-ecmascript: "npm:^2.1.0" - checksum: 10/ed0d7c66d84c633fbe8db4939d084c780190eca11f6920807dfb8ebac59e2676952cd8f2008d9c86ae8cf0463ea5fd12c5cff09ef2ce7d51ee6b420a5eb4d177 + unicode-match-property-value-ecmascript: "npm:^2.2.1" + checksum: 10/bf5f85a502a17f127a1f922270e2ecc1f0dd071ff76a3ec9afcd6b1c2bf7eae1486d1e3b1a6d621aee8960c8b15139e6b5058a84a68e518e1a92b52e9322faf9 languageName: node linkType: hard @@ -12597,25 +12910,21 @@ __metadata: languageName: node linkType: hard -"regjsparser@npm:^0.9.1": - version: 0.9.1 - resolution: "regjsparser@npm:0.9.1" - dependencies: - jsesc: "npm:~0.5.0" - bin: - regjsparser: bin/parser - checksum: 10/be7757ef76e1db10bf6996001d1021048b5fb12f5cb470a99b8cf7f3ff943f0f0e2291c0dcdbb418b458ddc4ac10e48680a822b69ef487a0284c8b6b77beddc3 +"regjsgen@npm:^0.8.0": + version: 0.8.0 + resolution: "regjsgen@npm:0.8.0" + checksum: 10/b930f03347e4123c917d7b40436b4f87f625b8dd3e705b447ddd44804e4616c3addb7453f0902d6e914ab0446c30e816e445089bb641a4714237fe8141a0ef9d languageName: node linkType: hard -"rehype-raw@npm:^6.1.1": - version: 6.1.1 - resolution: "rehype-raw@npm:6.1.1" +"regjsparser@npm:^0.13.0": + version: 0.13.0 + resolution: "regjsparser@npm:0.13.0" dependencies: - "@types/hast": "npm:^2.0.0" - hast-util-raw: "npm:^7.2.0" - unified: "npm:^10.0.0" - checksum: 10/3599d22c45264bea52c93eec2136f50f119282c0bd4e9604aeb2421fe20db84f9c4536caebf64f29158d8c2403b6fd3b3da634211393fdda9cdd500149d00ae4 + jsesc: "npm:~3.1.0" + bin: + regjsparser: bin/parser + checksum: 10/eeaabd3454f59394cbb3bfeb15fd789e638040f37d0bee9071a9b0b85524ddc52b5f7aaaaa4847304c36fa37429e53d109c4dbf6b878cb5ffa4f4198c1042fb7 languageName: node linkType: hard @@ -12674,21 +12983,9 @@ __metadata: languageName: node linkType: hard -"remark-gfm@npm:3.0.1": - version: 3.0.1 - resolution: "remark-gfm@npm:3.0.1" - dependencies: - "@types/mdast": "npm:^3.0.0" - mdast-util-gfm: "npm:^2.0.0" - micromark-extension-gfm: "npm:^2.0.0" - unified: "npm:^10.0.0" - checksum: 10/8ec301f5fb1f52c548b5a6d7ca6a3422d55db73cd703f147c979d16dca003f065181f55404d6f3f49d33f1faca3fe56ae731ed7fe0acc00cd945a8e605f155f2 - languageName: node - linkType: hard - -"remark-gfm@npm:^4.0.0": - version: 4.0.0 - resolution: "remark-gfm@npm:4.0.0" +"remark-gfm@npm:4.0.1, remark-gfm@npm:^4.0.0": + version: 4.0.1 + resolution: "remark-gfm@npm:4.0.1" dependencies: "@types/mdast": "npm:^4.0.0" mdast-util-gfm: "npm:^3.0.0" @@ -12696,7 +12993,7 @@ __metadata: remark-parse: "npm:^11.0.0" remark-stringify: "npm:^11.0.0" unified: "npm:^11.0.0" - checksum: 10/9f7b17aae0e9dc79ba9c989c2a679baff7161e1831a87307cfa2e0e9b0c492bd8c1900cdf7305855b898a2a9fab9aa8e586d71ce49cbc1ea90f68b714c249c0d + checksum: 10/86899862cf4ae1466664d3f88c6113e30b5e84e35480aef4093890aed2297ab9872506ff1f614c63963bba7d075c326d0027a1591c11bb493f6776dad21b95f6 languageName: node linkType: hard @@ -12710,17 +13007,6 @@ __metadata: languageName: node linkType: hard -"remark-parse@npm:^10.0.0": - version: 10.0.2 - resolution: "remark-parse@npm:10.0.2" - dependencies: - "@types/mdast": "npm:^3.0.0" - mdast-util-from-markdown: "npm:^1.0.0" - unified: "npm:^10.0.0" - checksum: 10/184f48956734a58a7e157d83233e532ea289697f5ecebd1fb082cce79e6d9f5b1d3da72462356b2b3b5843643cee890280ffe3d21c9d4ad2d7d5e20bb5de7f14 - languageName: node - linkType: hard - "remark-parse@npm:^11.0.0": version: 11.0.0 resolution: "remark-parse@npm:11.0.0" @@ -12733,18 +13019,6 @@ __metadata: languageName: node linkType: hard -"remark-rehype@npm:^10.0.0": - version: 10.1.0 - resolution: "remark-rehype@npm:10.1.0" - dependencies: - "@types/hast": "npm:^2.0.0" - "@types/mdast": "npm:^3.0.0" - mdast-util-to-hast: "npm:^12.1.0" - unified: "npm:^10.0.0" - checksum: 10/cf765b639d16872404b50d5945df0ba825d14f1150397dde804e7d9e2e856a7b7343c4dc3796c85e7c18ca84f3c989bd40e476bd194fc00a5a870e8a64ec30d9 - languageName: node - linkType: hard - "remark-rehype@npm:^11.0.0": version: 11.1.0 resolution: "remark-rehype@npm:11.1.0" @@ -12782,6 +13056,13 @@ __metadata: languageName: node linkType: hard +"repeat-string@npm:^1.0.0": + version: 1.6.1 + resolution: "repeat-string@npm:1.6.1" + checksum: 10/1b809fc6db97decdc68f5b12c4d1a671c8e3f65ec4a40c238bc5200e44e85bcc52a54f78268ab9c29fcf5fe4f1343e805420056d1f30fa9a9ee4c2d93e3cc6c0 + languageName: node + linkType: hard + "require-directory@npm:^2.1.1": version: 2.1.1 resolution: "require-directory@npm:2.1.1" @@ -12810,10 +13091,10 @@ __metadata: languageName: node linkType: hard -"reselect@npm:^4.1.8": - version: 4.1.8 - resolution: "reselect@npm:4.1.8" - checksum: 10/199984d9872f71cd207f4aa6e6fd2bd48d95154f7aa9b3aee3398335f39f5491059e732f28c12e9031d5d434adab2c458dc8af5afb6564d0ad37e1644445e09c +"reselect@npm:^5.1.0": + version: 5.1.1 + resolution: "reselect@npm:5.1.1" + checksum: 10/1fdae11a39ed9c8d85a24df19517c8372ee24fefea9cce3fae9eaad8e9cefbba5a3d4940c6fe31296b6addf76e035588c55798f7e6e147e1b7c0855f119e7fa5 languageName: node linkType: hard @@ -12838,29 +13119,29 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.1.6, resolve@npm:^1.14.2": - version: 1.22.1 - resolution: "resolve@npm:1.22.1" +"resolve@npm:^1.1.6, resolve@npm:^1.22.11": + version: 1.22.11 + resolution: "resolve@npm:1.22.11" dependencies: - is-core-module: "npm:^2.9.0" + is-core-module: "npm:^2.16.1" path-parse: "npm:^1.0.7" supports-preserve-symlinks-flag: "npm:^1.0.0" bin: resolve: bin/resolve - checksum: 10/4adcfac33f0baf6fc46d6c3a11acfad5c9345eab8bb7280d65672dc40a9694ddab6d18be2feebccf6cfc581bedd7ebfa792f6bc86db1903a41d328c23161bd23 + checksum: 10/e1b2e738884a08de03f97ee71494335eba8c2b0feb1de9ae065e82c48997f349f77a2b10e8817e147cf610bfabc4b1cb7891ee8eaf5bf80d4ad514a34c4fab0a languageName: node linkType: hard -"resolve@patch:resolve@npm%3A^1.1.6#optional!builtin, resolve@patch:resolve@npm%3A^1.14.2#optional!builtin": - version: 1.22.1 - resolution: "resolve@patch:resolve@npm%3A1.22.1#optional!builtin::version=1.22.1&hash=c3c19d" +"resolve@patch:resolve@npm%3A^1.1.6#optional!builtin, resolve@patch:resolve@npm%3A^1.22.11#optional!builtin": + version: 1.22.11 + resolution: "resolve@patch:resolve@npm%3A1.22.11#optional!builtin::version=1.22.11&hash=c3c19d" dependencies: - is-core-module: "npm:^2.9.0" + is-core-module: "npm:^2.16.1" path-parse: "npm:^1.0.7" supports-preserve-symlinks-flag: "npm:^1.0.0" bin: resolve: bin/resolve - checksum: 10/551dd500765cce767c583747f5f21ceb51d437f539b01aee96d6ec39eb2c68a8ff5d646b083d690fe428a81329856bc1bbdb094379b8df4b3f10e7e1f6aa3839 + checksum: 10/fd342cad25e52cd6f4f3d1716e189717f2522bfd6641109fe7aa372f32b5714a296ed7c238ddbe7ebb0c1ddfe0b7f71c9984171024c97cf1b2073e3e40ff71a8 languageName: node linkType: hard @@ -12905,33 +13186,6 @@ __metadata: languageName: node linkType: hard -"ripemd160@npm:=2.0.1": - version: 2.0.1 - resolution: "ripemd160@npm:2.0.1" - dependencies: - hash-base: "npm:^2.0.0" - inherits: "npm:^2.0.1" - checksum: 10/f1a20b72b3ef897a981544c72a1fe15c2bd580f6f40e3062f7839af8e81232f746aa860964686e4b81e90929ad086f14823a9864e4e4bed3367e597fe14a0968 - languageName: node - linkType: hard - -"ripemd160@npm:^2.0.0, ripemd160@npm:^2.0.1": - version: 2.0.2 - resolution: "ripemd160@npm:2.0.2" - dependencies: - hash-base: "npm:^3.0.0" - inherits: "npm:^2.0.1" - checksum: 10/006accc40578ee2beae382757c4ce2908a826b27e2b079efdcd2959ee544ddf210b7b5d7d5e80467807604244e7388427330f5c6d4cd61e6edaddc5773ccc393 - languageName: node - linkType: hard - -"rtl-detect@npm:^1.0.4": - version: 1.0.4 - resolution: "rtl-detect@npm:1.0.4" - checksum: 10/92088ff8c66e283b8a730fe9542e0117e43e39b20f1294ba986a715deadb734c5b67db179943ad4f2acd0e23448038e9d49235e40fc3b999fd8ea3f75cdda69c - languageName: node - linkType: hard - "rtlcss@npm:^4.1.0": version: 4.1.1 resolution: "rtlcss@npm:4.1.1" @@ -12946,6 +13200,13 @@ __metadata: languageName: node linkType: hard +"run-applescript@npm:^7.0.0": + version: 7.1.0 + resolution: "run-applescript@npm:7.1.0" + checksum: 10/8659fb5f2717b2b37a68cbfe5f678254cf24b5a82a6df3372b180c80c7c137dcd757a4166c3887e459f59a090ca414e8ea7ca97cf3ee5123db54b3b4006d7b7a + languageName: node + linkType: hard + "run-parallel@npm:^1.1.9": version: 1.2.0 resolution: "run-parallel@npm:1.2.0" @@ -12955,37 +13216,17 @@ __metadata: languageName: node linkType: hard -"sade@npm:^1.7.3": - version: 1.8.1 - resolution: "sade@npm:1.8.1" - dependencies: - mri: "npm:^1.1.0" - checksum: 10/1c67ba03c94083e0ae307ff5564ecb86c2104c0f558042fdaa40ea0054f91a63a9783f14069870f2f784336adabb70f90f22a84dc457b5a25e859aaadefe0910 - languageName: node - linkType: hard - -"safe-buffer@npm:5.1.2, safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1": - version: 5.1.2 - resolution: "safe-buffer@npm:5.1.2" - checksum: 10/7eb5b48f2ed9a594a4795677d5a150faa7eb54483b2318b568dc0c4fc94092a6cce5be02c7288a0500a156282f5276d5688bce7259299568d1053b2150ef374a - languageName: node - linkType: hard - -"safe-buffer@npm:5.2.1, safe-buffer@npm:>=5.1.0, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.0, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0": +"safe-buffer@npm:5.2.1, safe-buffer@npm:>=5.1.0, safe-buffer@npm:^5.1.0, safe-buffer@npm:~5.2.0": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" checksum: 10/32872cd0ff68a3ddade7a7617b8f4c2ae8764d8b7d884c651b74457967a9e0e886267d3ecc781220629c44a865167b61c375d2da6c720c840ecd73f45d5d9451 languageName: node linkType: hard -"safe-regex-test@npm:^1.1.0": - version: 1.1.0 - resolution: "safe-regex-test@npm:1.1.0" - dependencies: - call-bound: "npm:^1.0.2" - es-errors: "npm:^1.3.0" - is-regex: "npm:^1.2.1" - checksum: 10/ebdb61f305bf4756a5b023ad86067df5a11b26898573afe9e52a548a63c3bd594825d9b0e2dde2eb3c94e57e0e04ac9929d4107c394f7b8e56a4613bed46c69a +"safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1": + version: 5.1.2 + resolution: "safe-buffer@npm:5.1.2" + checksum: 10/7eb5b48f2ed9a594a4795677d5a150faa7eb54483b2318b568dc0c4fc94092a6cce5be02c7288a0500a156282f5276d5688bce7259299568d1053b2150ef374a languageName: node linkType: hard @@ -13021,9 +13262,9 @@ __metadata: languageName: node linkType: hard -"sass-loader@npm:^16.0.2": - version: 16.0.4 - resolution: "sass-loader@npm:16.0.4" +"sass-loader@npm:^16.0.5": + version: 16.0.6 + resolution: "sass-loader@npm:16.0.6" dependencies: neo-async: "npm:^2.6.2" peerDependencies: @@ -13043,31 +13284,31 @@ __metadata: optional: true webpack: optional: true - checksum: 10/16e3552e04301864d59d99f1c90952e0c97cfa793a38a551b6dc19e49a9c0779ec8b3dd8ab39bc79cc4401972f13e8d5baaf577c96ea39911db5866a0b4bdcdd + checksum: 10/ff4d11c48a4286dbee0de0aa9c9225e96a4390039f8fccc3333e21b7d9ac27c4ed9572212ec733a31723ec740808f6d446aea20f1d05fd47e6ce0f23d4942fb8 languageName: node linkType: hard -"sass@npm:^1.57.1, sass@npm:^1.80.4": - version: 1.83.4 - resolution: "sass@npm:1.83.4" +"sass@npm:^1.57.1, sass@npm:^1.89.2": + version: 1.98.0 + resolution: "sass@npm:1.98.0" dependencies: "@parcel/watcher": "npm:^2.4.1" chokidar: "npm:^4.0.0" - immutable: "npm:^5.0.2" + immutable: "npm:^5.1.5" source-map-js: "npm:>=0.6.2 <2.0.0" dependenciesMeta: "@parcel/watcher": optional: true bin: sass: sass.js - checksum: 10/9a7d1c6be1a9e711a1c561d189b9816aa7715f6d0ec0b2ec181f64163788d0caaf4741924eeadce558720b58b1de0e9b21b9dae6a0d14489c4d2a142d3f3b12e + checksum: 10/37d134d07639dc8fc8557495c3b98801bb736f0d1fc5fbfb2e0dba3c21200f447cf3e6166cd285603c922171366696c1d2e766af2c4cf82d882a3dc4d4e39f00 languageName: node linkType: hard -"sax@npm:^1.2.4": - version: 1.2.4 - resolution: "sax@npm:1.2.4" - checksum: 10/09b79ff6dc09689a24323352117c94593c69db348997b2af0edbd82fa08aba47d778055bf9616b57285bb73d25d790900c044bf631a8f10c8252412e3f3fe5dd +"sax@npm:^1.2.4, sax@npm:^1.5.0": + version: 1.5.0 + resolution: "sax@npm:1.5.0" + checksum: 10/9012ff37dda7a7ac5da45db2143b04036103e8bef8d586c3023afd5df6caf0ebd7f38017eee344ad2e2247eded7d38e9c42cf291d8dd91781352900ac0fd2d9f languageName: node linkType: hard @@ -13080,18 +13321,14 @@ __metadata: languageName: node linkType: hard -"schema-utils@npm:2.7.0": - version: 2.7.0 - resolution: "schema-utils@npm:2.7.0" - dependencies: - "@types/json-schema": "npm:^7.0.4" - ajv: "npm:^6.12.2" - ajv-keywords: "npm:^3.4.1" - checksum: 10/e5afb6ecf8e9c63ce5f964cd8f2a2e7bdc8c3a63f6bc7dd5cfdc475aa90c1b9ade1555a749519c1673a0bfa203a12e04499e7d6d956163f8e7a77aaa3f12935c +"schema-dts@npm:^1.1.2": + version: 1.1.5 + resolution: "schema-dts@npm:1.1.5" + checksum: 10/74f8376449241f008349cbd938e30e2174f0c974bb5155c852bdbbb4873a0f151a12601cf2fe115ae0811a0f7ccaefd3525e21c2a8f0fab7ada9c0309230db0a languageName: node linkType: hard -"schema-utils@npm:^3.0.0, schema-utils@npm:^3.1.1, schema-utils@npm:^3.2.0": +"schema-utils@npm:^3.0.0": version: 3.3.0 resolution: "schema-utils@npm:3.3.0" dependencies: @@ -13102,15 +13339,15 @@ __metadata: languageName: node linkType: hard -"schema-utils@npm:^4.0.0, schema-utils@npm:^4.0.1": - version: 4.2.0 - resolution: "schema-utils@npm:4.2.0" +"schema-utils@npm:^4.0.0, schema-utils@npm:^4.0.1, schema-utils@npm:^4.2.0, schema-utils@npm:^4.3.0, schema-utils@npm:^4.3.3": + version: 4.3.3 + resolution: "schema-utils@npm:4.3.3" dependencies: "@types/json-schema": "npm:^7.0.9" ajv: "npm:^8.9.0" ajv-formats: "npm:^2.1.1" ajv-keywords: "npm:^5.1.0" - checksum: 10/808784735eeb153ab7f3f787f840aa3bc63f423d2a5a7e96c9e70a0e53d0bc62d7b37ea396fc598ce19196e4fb86a72f897154b7c6ce2358bbc426166f205e14 + checksum: 10/dba77a46ad7ff0c906f7f09a1a61109e6cb56388f15a68070b93c47a691f516c6a3eb454f81a8cceb0a0e55b87f8b05770a02bfb1f4e0a3143b5887488b2f900 languageName: node linkType: hard @@ -13131,12 +13368,13 @@ __metadata: languageName: node linkType: hard -"selfsigned@npm:^2.1.1": - version: 2.1.1 - resolution: "selfsigned@npm:2.1.1" +"selfsigned@npm:^5.5.0": + version: 5.5.0 + resolution: "selfsigned@npm:5.5.0" dependencies: - node-forge: "npm:^1" - checksum: 10/6005206e0d005448274aceceaded5195b944f67a42b72d212a6169d2e5f4bdc87c15a3fe45732c544db8c7175702091aaf95403ad6632585294a6ec8cca63638 + "@peculiar/x509": "npm:^1.14.2" + pkijs: "npm:^3.3.3" + checksum: 10/fe9be2647507c3ee21dcaf5cab20e1ae4b8b84eac83d2fe4d82f9a3b6c70636f9aaeeba0089e3343dcb13fbb31ef70c2e72c41f2e2dcf38368040b49830c670e languageName: node linkType: hard @@ -13149,12 +13387,12 @@ __metadata: languageName: node linkType: hard -"semver@npm:7.6.3, semver@npm:^7.3.2, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.4": - version: 7.6.3 - resolution: "semver@npm:7.6.3" +"semver@npm:7.7.1, semver@npm:^7.3.2, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.5.4": + version: 7.7.1 + resolution: "semver@npm:7.7.1" bin: semver: bin/semver.js - checksum: 10/36b1fbe1a2b6f873559cd57b238f1094a053dbfd997ceeb8757d79d1d2089c56d1321b9f1069ce263dc64cfa922fa1d2ad566b39426fe1ac6c723c1487589e10 + checksum: 10/4cfa1eb91ef3751e20fc52e47a935a0118d56d6f15a837ab814da0c150778ba2ca4f1a4d9068b33070ea4273629e615066664c2cfcd7c272caf7a8a0f6518b2c languageName: node linkType: hard @@ -13188,7 +13426,28 @@ __metadata: languageName: node linkType: hard -"serialize-javascript@npm:^6.0.0, serialize-javascript@npm:^6.0.1": +"send@npm:~0.19.0": + version: 0.19.1 + resolution: "send@npm:0.19.1" + dependencies: + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + encodeurl: "npm:~2.0.0" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" + mime: "npm:1.6.0" + ms: "npm:2.1.3" + on-finished: "npm:2.4.1" + range-parser: "npm:~1.2.1" + statuses: "npm:2.0.1" + checksum: 10/360bf50a839c7bbc181f67c3a0f3424a7ad8016dfebcd9eb90891f4b762b4377da14414c32250d67b53872e884171c27469110626f6c22765caa7c38c207ee1d + languageName: node + linkType: hard + +"serialize-javascript@npm:^6.0.0, serialize-javascript@npm:^6.0.1, serialize-javascript@npm:^6.0.2": version: 6.0.2 resolution: "serialize-javascript@npm:6.0.2" dependencies: @@ -13197,19 +13456,18 @@ __metadata: languageName: node linkType: hard -"serve-handler@npm:^6.1.5": - version: 6.1.5 - resolution: "serve-handler@npm:6.1.5" +"serve-handler@npm:^6.1.6": + version: 6.1.6 + resolution: "serve-handler@npm:6.1.6" dependencies: bytes: "npm:3.0.0" content-disposition: "npm:0.5.2" - fast-url-parser: "npm:1.1.3" mime-types: "npm:2.1.18" minimatch: "npm:3.1.2" path-is-inside: "npm:1.0.2" - path-to-regexp: "npm:2.2.1" + path-to-regexp: "npm:3.3.0" range-parser: "npm:1.2.0" - checksum: 10/cab6f381d380ae77ae6da017b5c7b1c25d8f0bed00cf509a18bc768c1830a0043ce53668390ad8a84366e47b353b3f1f7c9d10c7167886179f2e89cb95243a90 + checksum: 10/7e7d93eb7e69fcd9f9c5afc2ef2b46cb0072b4af13cbabef9bca725afb350ddae6857d8c8be2c256f7ce1f7677c20347801399c11caa5805c0090339f894e8f2 languageName: node linkType: hard @@ -13228,7 +13486,7 @@ __metadata: languageName: node linkType: hard -"serve-static@npm:1.16.2": +"serve-static@npm:~1.16.2": version: 1.16.2 resolution: "serve-static@npm:1.16.2" dependencies: @@ -13261,13 +13519,6 @@ __metadata: languageName: node linkType: hard -"setimmediate@npm:^1.0.4": - version: 1.0.5 - resolution: "setimmediate@npm:1.0.5" - checksum: 10/76e3f5d7f4b581b6100ff819761f04a984fa3f3990e72a6554b57188ded53efce2d3d6c0932c10f810b7c59414f85e2ab3c11521877d1dea1ce0b56dc906f485 - languageName: node - linkType: hard - "setprototypeof@npm:1.1.0": version: 1.1.0 resolution: "setprototypeof@npm:1.1.0" @@ -13275,26 +13526,13 @@ __metadata: languageName: node linkType: hard -"setprototypeof@npm:1.2.0": +"setprototypeof@npm:1.2.0, setprototypeof@npm:~1.2.0": version: 1.2.0 resolution: "setprototypeof@npm:1.2.0" checksum: 10/fde1630422502fbbc19e6844346778f99d449986b2f9cdcceb8326730d2f3d9964dbcb03c02aaadaefffecd0f2c063315ebea8b3ad895914bf1afc1747fc172e languageName: node linkType: hard -"sha.js@npm:^2.4.0, sha.js@npm:^2.4.11, sha.js@npm:^2.4.8": - version: 2.4.12 - resolution: "sha.js@npm:2.4.12" - dependencies: - inherits: "npm:^2.0.4" - safe-buffer: "npm:^5.2.1" - to-buffer: "npm:^1.2.0" - bin: - sha.js: bin.js - checksum: 10/39c0993592c2ab34eb2daae2199a2a1d502713765aecb611fd97c0c4ab7cd53e902d628e1962aaf384bafd28f55951fef46dcc78799069ce41d74b03aa13b5a7 - languageName: node - linkType: hard - "shallow-clone@npm:^3.0.0": version: 3.0.1 resolution: "shallow-clone@npm:3.0.1" @@ -13327,14 +13565,14 @@ __metadata: languageName: node linkType: hard -"shell-quote@npm:^1.7.3": - version: 1.7.4 - resolution: "shell-quote@npm:1.7.4" - checksum: 10/e5059820d0ffc7e9298f9ee4be528cd5820c45a87d318dac5ad4b62066069fe4e62520801af639d8723b0a36af621ef6330adf8ce532cfaeb0c874c691117f1b +"shell-quote@npm:^1.8.3": + version: 1.8.3 + resolution: "shell-quote@npm:1.8.3" + checksum: 10/5473e354637c2bd698911224129c9a8961697486cff1fb221f234d71c153fc377674029b0223d1d3c953a68d451d79366abfe53d1a0b46ee1f28eb9ade928f4c languageName: node linkType: hard -"shelljs@npm:0.8.5, shelljs@npm:^0.8.5": +"shelljs@npm:0.8.5": version: 0.8.5 resolution: "shelljs@npm:0.8.5" dependencies: @@ -13438,7 +13676,7 @@ __metadata: languageName: node linkType: hard -"side-channel@npm:^1.0.6, side-channel@npm:^1.1.0": +"side-channel@npm:^1.1.0": version: 1.1.0 resolution: "side-channel@npm:1.1.0" dependencies: @@ -13520,7 +13758,7 @@ __metadata: languageName: node linkType: hard -"slugify@npm:^1.6.5": +"slugify@npm:^1.6.6": version: 1.6.6 resolution: "slugify@npm:1.6.6" checksum: 10/d0737cdedc834c50f74227bc1a1cf4f449f3575893f031b0e8c59f501c73526c866a23e47261b262c7acdaaaaf30d6f9e8aaae22772b3f56e858ac84c35efa7b @@ -13583,10 +13821,10 @@ __metadata: languageName: node linkType: hard -"source-map-js@npm:>=0.6.2 <2.0.0, source-map-js@npm:^1.0.1, source-map-js@npm:^1.2.0": - version: 1.2.0 - resolution: "source-map-js@npm:1.2.0" - checksum: 10/74f331cfd2d121c50790c8dd6d3c9de6be21926de80583b23b37029b0f37aefc3e019fa91f9a10a5e120c08135297e1ecf312d561459c45908cb1e0e365f49e5 +"source-map-js@npm:>=0.6.2 <2.0.0, source-map-js@npm:^1.0.1, source-map-js@npm:^1.2.1": + version: 1.2.1 + resolution: "source-map-js@npm:1.2.1" + checksum: 10/ff9d8c8bf096d534a5b7707e0382ef827b4dd360a577d3f34d2b9f48e12c9d230b5747974ee7c607f0df65113732711bb701fe9ece3c7edbd43cb2294d707df3 languageName: node linkType: hard @@ -13685,32 +13923,17 @@ __metadata: languageName: node linkType: hard -"std-env@npm:^3.0.1": - version: 3.3.1 - resolution: "std-env@npm:3.3.1" - checksum: 10/8106b4ed17791a0ab7797313516f59e5cc90c0d63b72f1f77219c2d085272874cf534b0580775560c106ce99dbb7a3639c4eed4194127206bae05f2ae091b069 +"statuses@npm:~2.0.1, statuses@npm:~2.0.2": + version: 2.0.2 + resolution: "statuses@npm:2.0.2" + checksum: 10/6927feb50c2a75b2a4caab2c565491f7a93ad3d8dbad7b1398d52359e9243a20e2ebe35e33726dee945125ef7a515e9097d8a1b910ba2bbd818265a2f6c39879 languageName: node linkType: hard -"stream-browserify@npm:^3.0.0": - version: 3.0.0 - resolution: "stream-browserify@npm:3.0.0" - dependencies: - inherits: "npm:~2.0.4" - readable-stream: "npm:^3.5.0" - checksum: 10/05a3cd0a0ce2d568dbdeb69914557c26a1b0a9d871839666b692eae42b96189756a3ed685affc90dab64ff588a8524c8aec6d85072c07905a1f0d941ea68f956 - languageName: node - linkType: hard - -"stream-http@npm:^3.2.0": - version: 3.2.0 - resolution: "stream-http@npm:3.2.0" - dependencies: - builtin-status-codes: "npm:^3.0.0" - inherits: "npm:^2.0.4" - readable-stream: "npm:^3.6.0" - xtend: "npm:^4.0.2" - checksum: 10/4f85738cbc6de70ecf0a04bc38b6092b4d91dd5317d3d93c88a84c48e63b82a8724ab5fd591df9f587b5139fe439d1748e4e3db3cb09c2b1e23649cb9d89859e +"std-env@npm:^3.7.0": + version: 3.10.0 + resolution: "std-env@npm:3.10.0" + checksum: 10/19c9cda4f370b1ffae2b8b08c72167d8c3e5cfa972aaf5c6873f85d0ed2faa729407f5abb194dc33380708c00315002febb6f1e1b484736bfcf9361ad366013a languageName: node linkType: hard @@ -13736,7 +13959,7 @@ __metadata: languageName: node linkType: hard -"string_decoder@npm:^1.1.1, string_decoder@npm:^1.3.0": +"string_decoder@npm:^1.1.1": version: 1.3.0 resolution: "string_decoder@npm:1.3.0" dependencies: @@ -13869,15 +14092,6 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^5.3.0": - version: 5.5.0 - resolution: "supports-color@npm:5.5.0" - dependencies: - has-flag: "npm:^3.0.0" - checksum: 10/5f505c6fa3c6e05873b43af096ddeb22159831597649881aeb8572d6fe3b81e798cc10840d0c9735e0026b250368851b7f77b65e84f4e4daa820a4f69947f55b - languageName: node - linkType: hard - "supports-color@npm:^7.1.0": version: 7.2.0 resolution: "supports-color@npm:7.2.0" @@ -13911,19 +14125,19 @@ __metadata: linkType: hard "svgo@npm:^3.0.2, svgo@npm:^3.2.0": - version: 3.3.2 - resolution: "svgo@npm:3.3.2" + version: 3.3.3 + resolution: "svgo@npm:3.3.3" dependencies: - "@trysound/sax": "npm:0.2.0" commander: "npm:^7.2.0" css-select: "npm:^5.1.0" css-tree: "npm:^2.3.1" css-what: "npm:^6.1.0" csso: "npm:^5.0.5" picocolors: "npm:^1.0.0" + sax: "npm:^1.5.0" bin: svgo: ./bin/svgo - checksum: 10/82fdea9b938884d808506104228e4d3af0050d643d5b46ff7abc903ff47a91bbf6561373394868aaf07a28f006c4057b8fbf14bbd666298abdd7cc590d4f7700 + checksum: 10/f3c1b4d05d1704483e53515d5995af5f06a2718df85e3a8320f57bb256b8dc926b84c87a1a9b98e9d3ca1224314cc0676a803bdd03163508292f2d45c7077096 languageName: node linkType: hard @@ -13950,29 +14164,22 @@ __metadata: languageName: node linkType: hard -"swc-loader@npm:^0.2.3": - version: 0.2.6 - resolution: "swc-loader@npm:0.2.6" +"swc-loader@npm:^0.2.3, swc-loader@npm:^0.2.6": + version: 0.2.7 + resolution: "swc-loader@npm:0.2.7" dependencies: "@swc/counter": "npm:^0.1.3" peerDependencies: "@swc/core": ^1.2.147 webpack: ">=2" - checksum: 10/fe90948c02a51bb8ffcff1ce3590e01dc12860b0bb7c9e22052b14fa846ed437781ae265614a5e14344bea22001108780f00a6e350e28c0b3499bc4cd11335fb + checksum: 10/15cbc3769209f7e2f927e49c19a5ef30fe03663bd34688289c003d966c7dfe782eec39de77c454aa7465ce84138d8bc31257b736b32ff3be10191f1de88cbbf5 languageName: node linkType: hard -"tapable@npm:^1.0.0": - version: 1.1.3 - resolution: "tapable@npm:1.1.3" - checksum: 10/1cec71f00f9a6cb1d88961b5d4f2dead4e185508b18b1bf1e688c8135039a391dd3e12b0887232b682ef28f1ef6f0c5e9a48794f6f5ef68f35d05de7e7a0a578 - languageName: node - linkType: hard - -"tapable@npm:^2.0.0, tapable@npm:^2.1.1, tapable@npm:^2.2.0": - version: 2.2.1 - resolution: "tapable@npm:2.2.1" - checksum: 10/1769336dd21481ae6347611ca5fca47add0962fd8e80466515032125eca0084a4f0ede11e65341b9c0018ef4e1cf1ad820adbb0fba7cc99865c6005734000b0a +"tapable@npm:^2.0.0, tapable@npm:^2.2.0, tapable@npm:^2.2.1, tapable@npm:^2.3.0": + version: 2.3.0 + resolution: "tapable@npm:2.3.0" + checksum: 10/496a841039960533bb6e44816a01fffc2a1eb428bb2051ecab9e87adf07f19e1f937566cbbbb09dceff31163c0ffd81baafcad84db900b601f0155dd0b37e9f2 languageName: node linkType: hard @@ -13990,15 +14197,15 @@ __metadata: languageName: node linkType: hard -"terser-webpack-plugin@npm:^5.3.10, terser-webpack-plugin@npm:^5.3.9": - version: 5.3.10 - resolution: "terser-webpack-plugin@npm:5.3.10" +"terser-webpack-plugin@npm:^5.3.16, terser-webpack-plugin@npm:^5.3.9": + version: 5.3.16 + resolution: "terser-webpack-plugin@npm:5.3.16" dependencies: - "@jridgewell/trace-mapping": "npm:^0.3.20" + "@jridgewell/trace-mapping": "npm:^0.3.25" jest-worker: "npm:^27.4.5" - schema-utils: "npm:^3.1.1" - serialize-javascript: "npm:^6.0.1" - terser: "npm:^5.26.0" + schema-utils: "npm:^4.3.0" + serialize-javascript: "npm:^6.0.2" + terser: "npm:^5.31.1" peerDependencies: webpack: ^5.1.0 peerDependenciesMeta: @@ -14008,28 +14215,21 @@ __metadata: optional: true uglify-js: optional: true - checksum: 10/fb1c2436ae1b4e983be043fa0a3d355c047b16b68f102437d08c736d7960c001e7420e2f722b9d99ce0dc70ca26a68cc63c0b82bc45f5b48671142b352a9d938 + checksum: 10/09dfbff602acfa114cdd174254b69a04adbc47856021ab351e37982202fd1ec85e0b62ffd5864c98beb8e96aef2f43da490b3448b4541db539c2cff6607394a6 languageName: node linkType: hard -"terser@npm:^5.10.0, terser@npm:^5.15.1, terser@npm:^5.26.0": - version: 5.34.1 - resolution: "terser@npm:5.34.1" +"terser@npm:^5.10.0, terser@npm:^5.15.1, terser@npm:^5.31.1": + version: 5.46.0 + resolution: "terser@npm:5.46.0" dependencies: "@jridgewell/source-map": "npm:^0.3.3" - acorn: "npm:^8.8.2" + acorn: "npm:^8.15.0" commander: "npm:^2.20.0" source-map-support: "npm:~0.5.20" bin: terser: bin/terser - checksum: 10/4389f39b5b841e2a7795ee733b54bf8fc44f8784a78c213dae32c7e6adc66c3bb258ebdcbacb8e7f1fa08fceb20bfc4ce4f7666d42bbfc29ab71126e89614c34 - languageName: node - linkType: hard - -"text-table@npm:^0.2.0": - version: 0.2.0 - resolution: "text-table@npm:0.2.0" - checksum: 10/4383b5baaeffa9bb4cda2ac33a4aa2e6d1f8aaf811848bf73513a9b88fd76372dc461f6fd6d2e9cb5100f48b473be32c6f95bd983509b7d92bb4d92c10747452 + checksum: 10/331e4f5a165d91d16ac6a95b510d4f5ef24679e4bc9e1b4e4182e89b7245f614d24ce0def583e2ca3ca45f82ba810991e0c5b66dd4353a6e0b7082786af6bd35 languageName: node linkType: hard @@ -14051,6 +14251,15 @@ __metadata: languageName: node linkType: hard +"thingies@npm:^2.5.0": + version: 2.5.0 + resolution: "thingies@npm:2.5.0" + peerDependencies: + tslib: ^2 + checksum: 10/b8b028a474aab798ff12ad5a5648306059976d3e23870327ae4ef640012953314b1d7f208dd5a8e9ebaeee6dd1cdb05503bb829699ee8f36514c37f771f2f035 + languageName: node + linkType: hard + "thunky@npm:^1.0.2": version: 1.1.0 resolution: "thunky@npm:1.1.0" @@ -14058,15 +14267,6 @@ __metadata: languageName: node linkType: hard -"timers-browserify@npm:^2.0.12": - version: 2.0.12 - resolution: "timers-browserify@npm:2.0.12" - dependencies: - setimmediate: "npm:^1.0.4" - checksum: 10/ec37ae299066bef6c464dcac29c7adafba1999e7227a9bdc4e105a459bee0f0b27234a46bfd7ab4041da79619e06a58433472867a913d01c26f8a203f87cee70 - languageName: node - linkType: hard - "tiny-invariant@npm:^1.0.2": version: 1.3.1 resolution: "tiny-invariant@npm:1.3.1" @@ -14081,21 +14281,10 @@ __metadata: languageName: node linkType: hard -"to-buffer@npm:^1.2.0": - version: 1.2.1 - resolution: "to-buffer@npm:1.2.1" - dependencies: - isarray: "npm:^2.0.5" - safe-buffer: "npm:^5.2.1" - typed-array-buffer: "npm:^1.0.3" - checksum: 10/f8d03f070b8567d9c949f1b59c8d47c83ed2e59b50b5449258f931df9a1fcb751aa8bb8756a9345adc529b6b1822521157c48e1a7d01779a47185060d7bf96d4 - languageName: node - linkType: hard - -"to-fast-properties@npm:^2.0.0": - version: 2.0.0 - resolution: "to-fast-properties@npm:2.0.0" - checksum: 10/be2de62fe58ead94e3e592680052683b1ec986c72d589e7b21e5697f8744cdbf48c266fa72f6c15932894c10187b5f54573a3bcf7da0bfd964d5caf23d436168 +"tinypool@npm:^1.0.2": + version: 1.1.1 + resolution: "tinypool@npm:1.1.1" + checksum: 10/0d54139e9dbc6ef33349768fa78890a4d708d16a7ab68e4e4ef3bb740609ddf0f9fd13292c2f413fbba756166c97051a657181c8f7ae92ade690604f183cc01d languageName: node linkType: hard @@ -14108,7 +14297,7 @@ __metadata: languageName: node linkType: hard -"toidentifier@npm:1.0.1": +"toidentifier@npm:1.0.1, toidentifier@npm:~1.0.1": version: 1.0.1 resolution: "toidentifier@npm:1.0.1" checksum: 10/952c29e2a85d7123239b5cfdd889a0dde47ab0497f0913d70588f19c53f7e0b5327c95f4651e413c74b785147f9637b17410ac8c846d5d4a20a5a33eb6dc3a45 @@ -14129,6 +14318,15 @@ __metadata: languageName: node linkType: hard +"tree-dump@npm:^1.0.3, tree-dump@npm:^1.1.0": + version: 1.1.0 + resolution: "tree-dump@npm:1.1.0" + peerDependencies: + tslib: 2 + checksum: 10/2c20118d2671996aa6f1ba1310cef1404fb525bde5d989ab542013f62b23a3633c0f0b32cbd516ee6205051ec21912b2470dabca006d19c9eba0740b567e2b60 + languageName: node + linkType: hard + "trim-lines@npm:^3.0.0": version: 3.0.1 resolution: "trim-lines@npm:3.0.1" @@ -14150,17 +14348,33 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.3, tslib@npm:^2.6.0": - version: 2.6.2 - resolution: "tslib@npm:2.6.2" - checksum: 10/bd26c22d36736513980091a1e356378e8b662ded04204453d353a7f34a4c21ed0afc59b5f90719d4ba756e581a162ecbf93118dc9c6be5acf70aa309188166ca +"tslib@npm:^1.9.3": + version: 1.14.1 + resolution: "tslib@npm:1.14.1" + checksum: 10/7dbf34e6f55c6492637adb81b555af5e3b4f9cc6b998fb440dac82d3b42bdc91560a35a5fb75e20e24a076c651438234da6743d139e4feabf0783f3cdfe1dddb languageName: node linkType: hard -"tty-browserify@npm:^0.0.1": - version: 0.0.1 - resolution: "tty-browserify@npm:0.0.1" - checksum: 10/93b745d43fa5a7d2b948fa23be8d313576d1d884b48acd957c07710bac1c0d8ac34c0556ad4c57c73d36e11741763ef66b3fb4fb97b06b7e4d525315a3cd45f5 +"tslib@npm:^2.0.0, tslib@npm:^2.0.3, tslib@npm:^2.4.0, tslib@npm:^2.6.0, tslib@npm:^2.8.1": + version: 2.8.1 + resolution: "tslib@npm:2.8.1" + checksum: 10/3e2e043d5c2316461cb54e5c7fe02c30ef6dccb3384717ca22ae5c6b5bc95232a6241df19c622d9c73b809bea33b187f6dbc73030963e29950c2141bc32a79f7 + languageName: node + linkType: hard + +"tsyringe@npm:^4.10.0": + version: 4.10.0 + resolution: "tsyringe@npm:4.10.0" + dependencies: + tslib: "npm:^1.9.3" + checksum: 10/b42660dc112cee2db02b3d69f2ef6a6a9d185afd96b18d8f88e47c1e62be94b69a9f5a58fcfdb2a3fbb7c6c175b8162ea00f7db6499bf333ce945e570e31615c + languageName: node + linkType: hard + +"type-fest@npm:^0.21.3": + version: 0.21.3 + resolution: "type-fest@npm:0.21.3" + checksum: 10/f4254070d9c3d83a6e573bcb95173008d73474ceadbbf620dd32d273940ca18734dff39c2b2480282df9afe5d1675ebed5499a00d791758748ea81f61a38961f languageName: node linkType: hard @@ -14178,13 +14392,6 @@ __metadata: languageName: node linkType: hard -"type-fest@npm:^4.4.0": - version: 4.33.0 - resolution: "type-fest@npm:4.33.0" - checksum: 10/0d179e66fa765bd0a25a785b12dc797f90f2f92bdb8c9c8a789f3fd8e5a4492444e7ef83551b3b8463aeab24fd6195761e26b03174722de636b4b75aa5726fb7 - languageName: node - linkType: hard - "type-is@npm:~1.6.18": version: 1.6.18 resolution: "type-is@npm:1.6.18" @@ -14195,17 +14402,6 @@ __metadata: languageName: node linkType: hard -"typed-array-buffer@npm:^1.0.3": - version: 1.0.3 - resolution: "typed-array-buffer@npm:1.0.3" - dependencies: - call-bound: "npm:^1.0.3" - es-errors: "npm:^1.3.0" - is-typed-array: "npm:^1.1.14" - checksum: 10/3fb91f0735fb413b2bbaaca9fabe7b8fc14a3fa5a5a7546bab8a57e755be0e3788d893195ad9c2b842620592de0e68d4c077d4c2c41f04ec25b8b5bb82fa9a80 - languageName: node - linkType: hard - "typedarray-to-buffer@npm:^3.1.5": version: 3.1.5 resolution: "typedarray-to-buffer@npm:3.1.5" @@ -14215,23 +14411,23 @@ __metadata: languageName: node linkType: hard -"typescript@npm:~5.2.0": - version: 5.2.2 - resolution: "typescript@npm:5.2.2" +"typescript@npm:~5.7.0": + version: 5.7.3 + resolution: "typescript@npm:5.7.3" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/d65e50eb849bd21ff8677e5b9447f9c6e74777e346afd67754934264dcbf4bd59e7d2473f6062d9a015d66bd573311166357e3eb07fea0b52859cf9bb2b58555 + checksum: 10/6a7e556de91db3d34dc51cd2600e8e91f4c312acd8e52792f243c7818dfadb27bae677175fad6947f9c81efb6c57eb6b2d0c736f196a6ee2f1f7d57b74fc92fa languageName: node linkType: hard -"typescript@patch:typescript@npm%3A~5.2.0#optional!builtin": - version: 5.2.2 - resolution: "typescript@patch:typescript@npm%3A5.2.2#optional!builtin::version=5.2.2&hash=f3b441" +"typescript@patch:typescript@npm%3A~5.7.0#optional!builtin": + version: 5.7.3 + resolution: "typescript@patch:typescript@npm%3A5.7.3#optional!builtin::version=5.7.3&hash=5786d5" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/f79cc2ba802c94c2b78dbb00d767a10adb67368ae764709737dc277273ec148aa4558033a03ce901406b35fddf4eac46dabc94a1e1d12d2587e2b9cfe5707b4a + checksum: 10/dc58d777eb4c01973f7fbf1fd808aad49a0efdf545528dab9b07d94fdcb65b8751742804c3057e9619a4627f2d9cc85547fdd49d9f4326992ad0181b49e61d81 languageName: node linkType: hard @@ -14259,10 +14455,10 @@ __metadata: languageName: node linkType: hard -"unicode-match-property-value-ecmascript@npm:^2.1.0": - version: 2.1.0 - resolution: "unicode-match-property-value-ecmascript@npm:2.1.0" - checksum: 10/06661bc8aba2a60c7733a7044f3e13085808939ad17924ffd4f5222a650f88009eb7c09481dc9c15cfc593d4ad99bd1cde8d54042733b335672591a81c52601c +"unicode-match-property-value-ecmascript@npm:^2.2.1": + version: 2.2.1 + resolution: "unicode-match-property-value-ecmascript@npm:2.2.1" + checksum: 10/a42bebebab4c82ea6d8363e487b1fb862f82d1b54af1b67eb3fef43672939b685780f092c4f235266b90225863afa1258d57e7be3578d8986a08d8fc309aabe1 languageName: node linkType: hard @@ -14273,21 +14469,6 @@ __metadata: languageName: node linkType: hard -"unified@npm:^10.0.0": - version: 10.1.2 - resolution: "unified@npm:10.1.2" - dependencies: - "@types/unist": "npm:^2.0.0" - bail: "npm:^2.0.0" - extend: "npm:^3.0.0" - is-buffer: "npm:^2.0.0" - is-plain-obj: "npm:^4.0.0" - trough: "npm:^2.0.0" - vfile: "npm:^5.0.0" - checksum: 10/6cffebcefc3290be26d25a58ba714cda943142782baf320fddf374ca3a319bdaabb006f96df4be17b8b367f5e6f6e113b1027c52ef66154846a7a110550f6688 - languageName: node - linkType: hard - "unified@npm:^11.0.0, unified@npm:^11.0.3, unified@npm:^11.0.4": version: 11.0.4 resolution: "unified@npm:11.0.4" @@ -14330,22 +14511,6 @@ __metadata: languageName: node linkType: hard -"unist-util-generated@npm:^2.0.0": - version: 2.0.1 - resolution: "unist-util-generated@npm:2.0.1" - checksum: 10/0528642918683f1518ab7a50cf8c900df10d8717b58bd2fb05aab29393b1c4050fd2740792f18d477b52f942bfb0e6e00023e985c0a7bd63859d3d836b56e4ce - languageName: node - linkType: hard - -"unist-util-is@npm:^5.0.0": - version: 5.2.1 - resolution: "unist-util-is@npm:5.2.1" - dependencies: - "@types/unist": "npm:^2.0.0" - checksum: 10/c10f6c07aad4f4830ffa8ea82b42a2c8d5cd36c7555e27889e5fee953040af321e4e6f4e52c4edb606604de75d7230a5f4bc7b71b8ac3e874a26ab595c2057e4 - languageName: node - linkType: hard - "unist-util-is@npm:^6.0.0": version: 6.0.0 resolution: "unist-util-is@npm:6.0.0" @@ -14364,15 +14529,6 @@ __metadata: languageName: node linkType: hard -"unist-util-position@npm:^4.0.0": - version: 4.0.4 - resolution: "unist-util-position@npm:4.0.4" - dependencies: - "@types/unist": "npm:^2.0.0" - checksum: 10/aedbc5d112cdab85b752a7dacd8f04233655f00e08948a42f6e49682467c6fc0c531c91acc71188da5ac8acfea9e67d72bc054127d1c4b76b31792cfb5132423 - languageName: node - linkType: hard - "unist-util-position@npm:^5.0.0": version: 5.0.0 resolution: "unist-util-position@npm:5.0.0" @@ -14392,15 +14548,6 @@ __metadata: languageName: node linkType: hard -"unist-util-stringify-position@npm:^3.0.0": - version: 3.0.3 - resolution: "unist-util-stringify-position@npm:3.0.3" - dependencies: - "@types/unist": "npm:^2.0.0" - checksum: 10/07913e4fd77fe57d95f8b2f771354f97a29082229c1ad14ceedce6bbc77b2d784ca8296563335471cdca97915e548204bd6f098ea5b808b822b4b54087662cfb - languageName: node - linkType: hard - "unist-util-stringify-position@npm:^4.0.0": version: 4.0.0 resolution: "unist-util-stringify-position@npm:4.0.0" @@ -14410,16 +14557,6 @@ __metadata: languageName: node linkType: hard -"unist-util-visit-parents@npm:^5.0.0, unist-util-visit-parents@npm:^5.1.1": - version: 5.1.3 - resolution: "unist-util-visit-parents@npm:5.1.3" - dependencies: - "@types/unist": "npm:^2.0.0" - unist-util-is: "npm:^5.0.0" - checksum: 10/5381fc57a129d478d983b988d86b72a1266d6f91fc608562b00bfa76596128d6e4d1c2b26ced64d96e55eb5d27d620081b4ee9703979bab63e1210789e781372 - languageName: node - linkType: hard - "unist-util-visit-parents@npm:^6.0.0": version: 6.0.1 resolution: "unist-util-visit-parents@npm:6.0.1" @@ -14430,17 +14567,6 @@ __metadata: languageName: node linkType: hard -"unist-util-visit@npm:^4.0.0": - version: 4.1.2 - resolution: "unist-util-visit@npm:4.1.2" - dependencies: - "@types/unist": "npm:^2.0.0" - unist-util-is: "npm:^5.0.0" - unist-util-visit-parents: "npm:^5.1.1" - checksum: 10/e3b20c6b1f5ae1b7b40bbf9be49103a342d98fad98bdf958110c20d72e5923bd3f12966b6702459bc61ab832facb5af418a79af87cefa7a8a41b892369678b13 - languageName: node - linkType: hard - "unist-util-visit@npm:^5.0.0": version: 5.0.0 resolution: "unist-util-visit@npm:5.0.0" @@ -14459,16 +14585,16 @@ __metadata: languageName: node linkType: hard -"unpipe@npm:1.0.0, unpipe@npm:~1.0.0": +"unpipe@npm:~1.0.0": version: 1.0.0 resolution: "unpipe@npm:1.0.0" checksum: 10/4fa18d8d8d977c55cb09715385c203197105e10a6d220087ec819f50cb68870f02942244f1017565484237f1f8c5d3cd413631b1ae104d3096f24fdfde1b4aa2 languageName: node linkType: hard -"update-browserslist-db@npm:^1.1.1": - version: 1.1.2 - resolution: "update-browserslist-db@npm:1.1.2" +"update-browserslist-db@npm:^1.2.0": + version: 1.2.3 + resolution: "update-browserslist-db@npm:1.2.3" dependencies: escalade: "npm:^3.2.0" picocolors: "npm:^1.1.1" @@ -14476,7 +14602,7 @@ __metadata: browserslist: ">= 4.21.0" bin: update-browserslist-db: cli.js - checksum: 10/e7bf8221dfb21eba4a770cd803df94625bb04f65a706aa94c567de9600fe4eb6133fda016ec471dad43b9e7959c1bffb6580b5e20a87808d2e8a13e3892699a9 + checksum: 10/059f774300efb4b084a49293143c511f3ae946d40397b5c30914e900cd5691a12b8e61b41dd54ed73d3b56c8204165a0333107dd784ccf8f8c81790bcc423175 languageName: node linkType: hard @@ -14535,7 +14661,7 @@ __metadata: languageName: node linkType: hard -"url@npm:^0.11.3": +"url@npm:^0.11.4": version: 0.11.4 resolution: "url@npm:0.11.4" dependencies: @@ -14554,6 +14680,15 @@ __metadata: languageName: node linkType: hard +"use-sync-external-store@npm:^1.4.0": + version: 1.6.0 + resolution: "use-sync-external-store@npm:1.6.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10/b40ad2847ba220695bff2d4ba4f4d60391c0fb4fb012faa7a4c18eb38b69181936f5edc55a522c4d20a788d1a879b73c3810952c9d0fd128d01cb3f22042c09e + languageName: node + linkType: hard + "util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2, util-deprecate@npm:~1.0.1": version: 1.0.2 resolution: "util-deprecate@npm:1.0.2" @@ -14570,19 +14705,6 @@ __metadata: languageName: node linkType: hard -"util@npm:^0.12.5": - version: 0.12.5 - resolution: "util@npm:0.12.5" - dependencies: - inherits: "npm:^2.0.3" - is-arguments: "npm:^1.0.4" - is-generator-function: "npm:^1.0.7" - is-typed-array: "npm:^1.1.3" - which-typed-array: "npm:^1.1.2" - checksum: 10/61a10de7753353dd4d744c917f74cdd7d21b8b46379c1e48e1c4fd8e83f8190e6bd9978fc4e5102ab6a10ebda6019d1b36572fa4a325e175ec8b789a121f6147 - languageName: node - linkType: hard - "utila@npm:~0.4": version: 0.4.0 resolution: "utila@npm:0.4.0" @@ -14613,20 +14735,6 @@ __metadata: languageName: node linkType: hard -"uvu@npm:^0.5.0": - version: 0.5.6 - resolution: "uvu@npm:0.5.6" - dependencies: - dequal: "npm:^2.0.0" - diff: "npm:^5.0.0" - kleur: "npm:^4.0.3" - sade: "npm:^1.7.3" - bin: - uvu: bin.js - checksum: 10/66ba25afc6732249877f9f4f8b6146f3aaa97538c51cf498f55825d602c33dbb903e02c7e1547cbca6bdfbb609e07eb7ea758b5156002ac2dd5072f00606f8d9 - languageName: node - linkType: hard - "validate.io-array@npm:^1.0.3": version: 1.0.6 resolution: "validate.io-array@npm:1.0.6" @@ -14681,16 +14789,6 @@ __metadata: languageName: node linkType: hard -"vfile-location@npm:^4.0.0": - version: 4.1.0 - resolution: "vfile-location@npm:4.1.0" - dependencies: - "@types/unist": "npm:^2.0.0" - vfile: "npm:^5.0.0" - checksum: 10/c894e8e5224170d1f85288f4a1d1ebcee0780823ea2b49d881648ab360ebf01b37ecb09b1c4439a75f9a51f31a9f9742cd045e987763e367c352a1ef7c50d446 - languageName: node - linkType: hard - "vfile-location@npm:^5.0.0": version: 5.0.2 resolution: "vfile-location@npm:5.0.2" @@ -14701,16 +14799,6 @@ __metadata: languageName: node linkType: hard -"vfile-message@npm:^3.0.0": - version: 3.1.4 - resolution: "vfile-message@npm:3.1.4" - dependencies: - "@types/unist": "npm:^2.0.0" - unist-util-stringify-position: "npm:^3.0.0" - checksum: 10/423ca87f4427a403e4688d7ec663a2e6add694eefac47c945746463377428c7553bc613058841f1da83e18b68af886d3dd11cb96d582b5cc3c98e11efb7e55e9 - languageName: node - linkType: hard - "vfile-message@npm:^4.0.0": version: 4.0.2 resolution: "vfile-message@npm:4.0.2" @@ -14721,18 +14809,6 @@ __metadata: languageName: node linkType: hard -"vfile@npm:^5.0.0": - version: 5.3.7 - resolution: "vfile@npm:5.3.7" - dependencies: - "@types/unist": "npm:^2.0.0" - is-buffer: "npm:^2.0.0" - unist-util-stringify-position: "npm:^3.0.0" - vfile-message: "npm:^3.0.0" - checksum: 10/d8f59b419d4c83b3ed24f500cf02393149b728f8803f88519c18fe0733f62544fa9ab0d8425a8bc7835181d848b9ce29c014168dc45af72f416074bbe475f643 - languageName: node - linkType: hard - "vfile@npm:^6.0.0, vfile@npm:^6.0.1": version: 6.0.1 resolution: "vfile@npm:6.0.1" @@ -14744,13 +14820,6 @@ __metadata: languageName: node linkType: hard -"vm-browserify@npm:^1.1.2": - version: 1.1.2 - resolution: "vm-browserify@npm:1.1.2" - checksum: 10/ad5b17c9f7a9d9f1ed0e24c897782ab7a587c1fd40f370152482e1af154c7cf0b0bacc45c5ae76a44289881e083ae4ae127808fdff864aa9b562192aae8b5c3b - languageName: node - linkType: hard - "warning@npm:^4.0.3": version: 4.0.3 resolution: "warning@npm:4.0.3" @@ -14760,13 +14829,13 @@ __metadata: languageName: node linkType: hard -"watchpack@npm:^2.4.1": - version: 2.4.2 - resolution: "watchpack@npm:2.4.2" +"watchpack@npm:^2.4.4": + version: 2.5.1 + resolution: "watchpack@npm:2.5.1" dependencies: glob-to-regexp: "npm:^0.4.1" graceful-fs: "npm:^4.1.2" - checksum: 10/6bd4c051d9af189a6c781c3158dcb3069f432a0c144159eeb0a44117412105c61b2b683a5c9eebc4324625e0e9b76536387d0ba354594fa6cbbdf1ef60bee4c3 + checksum: 10/9c9cdd4a9f9ae146b10d15387f383f52589e4cc27b324da6be8e7e3e755255b062a69dd7f00eef2ce67b2c01e546aae353456e74f8c1350bba00462cc6375549 languageName: node linkType: hard @@ -14793,84 +14862,81 @@ __metadata: languageName: node linkType: hard -"webpack-bundle-analyzer@npm:^4.9.0": - version: 4.9.1 - resolution: "webpack-bundle-analyzer@npm:4.9.1" +"webpack-bundle-analyzer@npm:^4.10.2": + version: 4.10.2 + resolution: "webpack-bundle-analyzer@npm:4.10.2" dependencies: "@discoveryjs/json-ext": "npm:0.5.7" acorn: "npm:^8.0.4" acorn-walk: "npm:^8.0.0" commander: "npm:^7.2.0" + debounce: "npm:^1.2.1" escape-string-regexp: "npm:^4.0.0" gzip-size: "npm:^6.0.0" - is-plain-object: "npm:^5.0.0" - lodash.debounce: "npm:^4.0.8" - lodash.escape: "npm:^4.0.1" - lodash.flatten: "npm:^4.4.0" - lodash.invokemap: "npm:^4.6.0" - lodash.pullall: "npm:^4.2.0" - lodash.uniqby: "npm:^4.7.0" + html-escaper: "npm:^2.0.2" opener: "npm:^1.5.2" picocolors: "npm:^1.0.0" sirv: "npm:^2.0.3" ws: "npm:^7.3.1" bin: webpack-bundle-analyzer: lib/bin/analyzer.js - checksum: 10/1126f7ad46d926316f467523c6e512e063b9d82e3252a74b4f997f69f32005735e51a0f58345db6921a37c876256effcdb3b4cc1b2053cd91d1fe583eda18fea + checksum: 10/cb7ff9d01dc04ef23634f439ab9fe739e022cce5595cb340e01d106ed474605ce4ef50b11b47e444507d341b16650dcb3610e88944020ca6c1c38e88072d43ba languageName: node linkType: hard -"webpack-dev-middleware@npm:^5.3.1": - version: 5.3.4 - resolution: "webpack-dev-middleware@npm:5.3.4" +"webpack-dev-middleware@npm:^7.4.2": + version: 7.4.5 + resolution: "webpack-dev-middleware@npm:7.4.5" dependencies: colorette: "npm:^2.0.10" - memfs: "npm:^3.4.3" - mime-types: "npm:^2.1.31" + memfs: "npm:^4.43.1" + mime-types: "npm:^3.0.1" + on-finished: "npm:^2.4.1" range-parser: "npm:^1.2.1" schema-utils: "npm:^4.0.0" peerDependencies: - webpack: ^4.0.0 || ^5.0.0 - checksum: 10/3004374130f31c2910da39b80e24296009653bb11caa0b8449d962b67e003d7e73d01fbcfda9be1f1f04179f66a9c39f4caf7963df54303b430e39ba5a94f7c2 + webpack: ^5.0.0 + peerDependenciesMeta: + webpack: + optional: true + checksum: 10/50e9b162d740b81f14c0926beb5fa01fc6d2ae16740bab709320dd5ea1a52ebcc48b66f3db5a7262fc4dc31a7e18590db766cef5da90e77a39e3a26d3b5b1001 languageName: node linkType: hard -"webpack-dev-server@npm:^4.15.1": - version: 4.15.1 - resolution: "webpack-dev-server@npm:4.15.1" +"webpack-dev-server@npm:^5.2.2": + version: 5.2.3 + resolution: "webpack-dev-server@npm:5.2.3" dependencies: - "@types/bonjour": "npm:^3.5.9" - "@types/connect-history-api-fallback": "npm:^1.3.5" - "@types/express": "npm:^4.17.13" - "@types/serve-index": "npm:^1.9.1" - "@types/serve-static": "npm:^1.13.10" - "@types/sockjs": "npm:^0.3.33" - "@types/ws": "npm:^8.5.5" + "@types/bonjour": "npm:^3.5.13" + "@types/connect-history-api-fallback": "npm:^1.5.4" + "@types/express": "npm:^4.17.25" + "@types/express-serve-static-core": "npm:^4.17.21" + "@types/serve-index": "npm:^1.9.4" + "@types/serve-static": "npm:^1.15.5" + "@types/sockjs": "npm:^0.3.36" + "@types/ws": "npm:^8.5.10" ansi-html-community: "npm:^0.0.8" - bonjour-service: "npm:^1.0.11" - chokidar: "npm:^3.5.3" + bonjour-service: "npm:^1.2.1" + chokidar: "npm:^3.6.0" colorette: "npm:^2.0.10" - compression: "npm:^1.7.4" + compression: "npm:^1.8.1" connect-history-api-fallback: "npm:^2.0.0" - default-gateway: "npm:^6.0.3" - express: "npm:^4.17.3" + express: "npm:^4.22.1" graceful-fs: "npm:^4.2.6" - html-entities: "npm:^2.3.2" - http-proxy-middleware: "npm:^2.0.3" - ipaddr.js: "npm:^2.0.1" - launch-editor: "npm:^2.6.0" - open: "npm:^8.0.9" - p-retry: "npm:^4.5.0" - rimraf: "npm:^3.0.2" - schema-utils: "npm:^4.0.0" - selfsigned: "npm:^2.1.1" + http-proxy-middleware: "npm:^2.0.9" + ipaddr.js: "npm:^2.1.0" + launch-editor: "npm:^2.6.1" + open: "npm:^10.0.3" + p-retry: "npm:^6.2.0" + schema-utils: "npm:^4.2.0" + selfsigned: "npm:^5.5.0" serve-index: "npm:^1.9.1" sockjs: "npm:^0.3.24" spdy: "npm:^4.0.2" - webpack-dev-middleware: "npm:^5.3.1" - ws: "npm:^8.13.0" + webpack-dev-middleware: "npm:^7.4.2" + ws: "npm:^8.18.0" peerDependencies: - webpack: ^4.37.0 || ^5.0.0 + webpack: ^5.0.0 peerDependenciesMeta: webpack: optional: true @@ -14878,7 +14944,7 @@ __metadata: optional: true bin: webpack-dev-server: bin/webpack-dev-server.js - checksum: 10/fd6dfb6c71eb94696b21930ea4c2f25e95ba85fac1bbc15aa5d03af0a90712eba057901fa9131ed3e901665c95b2379208279aca61e9c48e7cda276c3caa95dd + checksum: 10/6a3d55c5d84d10b5e23a0638e0031ef85b262947fdacc86d96b7392538ad5894ac14aebef1e2b877f8d27302c71247215b7aa6713e01b1c37d31342415b1a150 languageName: node linkType: hard @@ -14892,60 +14958,77 @@ __metadata: languageName: node linkType: hard -"webpack-sources@npm:^3.2.3": - version: 3.2.3 - resolution: "webpack-sources@npm:3.2.3" - checksum: 10/a661f41795d678b7526ae8a88cd1b3d8ce71a7d19b6503da8149b2e667fc7a12f9b899041c1665d39e38245ed3a59ab68de648ea31040c3829aa695a5a45211d +"webpack-merge@npm:^6.0.1": + version: 6.0.1 + resolution: "webpack-merge@npm:6.0.1" + dependencies: + clone-deep: "npm:^4.0.1" + flat: "npm:^5.0.2" + wildcard: "npm:^2.0.1" + checksum: 10/39ab911c26237922295d9b3d0617c8ea0c438c35a3b21b05506616a10423f5ece1962bccbedec932c5db61af57999b6d055d56d1f1755c63e2701bd4a55c3887 languageName: node linkType: hard -"webpack@npm:^5.61.0, webpack@npm:^5.88.1": - version: 5.97.1 - resolution: "webpack@npm:5.97.1" +"webpack-sources@npm:^3.3.3": + version: 3.3.3 + resolution: "webpack-sources@npm:3.3.3" + checksum: 10/ec5d72607e8068467370abccbfff855c596c098baedbe9d198a557ccf198e8546a322836a6f74241492576adba06100286592993a62b63196832cdb53c8bae91 + languageName: node + linkType: hard + +"webpack@npm:^5.88.1, webpack@npm:^5.95.0": + version: 5.104.1 + resolution: "webpack@npm:5.104.1" dependencies: "@types/eslint-scope": "npm:^3.7.7" - "@types/estree": "npm:^1.0.6" + "@types/estree": "npm:^1.0.8" + "@types/json-schema": "npm:^7.0.15" "@webassemblyjs/ast": "npm:^1.14.1" "@webassemblyjs/wasm-edit": "npm:^1.14.1" "@webassemblyjs/wasm-parser": "npm:^1.14.1" - acorn: "npm:^8.14.0" - browserslist: "npm:^4.24.0" + acorn: "npm:^8.15.0" + acorn-import-phases: "npm:^1.0.3" + browserslist: "npm:^4.28.1" chrome-trace-event: "npm:^1.0.2" - enhanced-resolve: "npm:^5.17.1" - es-module-lexer: "npm:^1.2.1" + enhanced-resolve: "npm:^5.17.4" + es-module-lexer: "npm:^2.0.0" eslint-scope: "npm:5.1.1" events: "npm:^3.2.0" glob-to-regexp: "npm:^0.4.1" graceful-fs: "npm:^4.2.11" json-parse-even-better-errors: "npm:^2.3.1" - loader-runner: "npm:^4.2.0" + loader-runner: "npm:^4.3.1" mime-types: "npm:^2.1.27" neo-async: "npm:^2.6.2" - schema-utils: "npm:^3.2.0" - tapable: "npm:^2.1.1" - terser-webpack-plugin: "npm:^5.3.10" - watchpack: "npm:^2.4.1" - webpack-sources: "npm:^3.2.3" + schema-utils: "npm:^4.3.3" + tapable: "npm:^2.3.0" + terser-webpack-plugin: "npm:^5.3.16" + watchpack: "npm:^2.4.4" + webpack-sources: "npm:^3.3.3" peerDependenciesMeta: webpack-cli: optional: true bin: webpack: bin/webpack.js - checksum: 10/665bd3b8c84b20f0b1f250159865e4d3e9b76c682030313d49124d5f8e96357ccdcc799dd9fe0ebf010fdb33dbc59d9863d79676a308e868e360ac98f7c09987 + checksum: 10/c22b58fdf41d993971516154cb621d13b7b61dd744b7e2c6be972e9afcc9942694a8aaa322c07fba46448c69d663023ff53d0aeeb00eb0b125d83ace5ca8fa3c languageName: node linkType: hard -"webpackbar@npm:^5.0.2": - version: 5.0.2 - resolution: "webpackbar@npm:5.0.2" +"webpackbar@npm:^6.0.1": + version: 6.0.1 + resolution: "webpackbar@npm:6.0.1" dependencies: - chalk: "npm:^4.1.0" - consola: "npm:^2.15.3" + ansi-escapes: "npm:^4.3.2" + chalk: "npm:^4.1.2" + consola: "npm:^3.2.3" + figures: "npm:^3.2.0" + markdown-table: "npm:^2.0.0" pretty-time: "npm:^1.1.0" - std-env: "npm:^3.0.1" + std-env: "npm:^3.7.0" + wrap-ansi: "npm:^7.0.0" peerDependencies: webpack: 3 || 4 || 5 - checksum: 10/059d5bed5c52a40636e29271285de4a8f9ac2ebef8941b896fc3fb858df2bf6f7c2fdedab80d6637626b91e03686c553ff644af47deb5c44fedf32edf558396f + checksum: 10/9da47f8dcbc9173b19e41e3e1049fa451b0c02095ffa003e8c09c56aa2cc544334d1c6fff0797162a807b29090db9cf9a269cd5ec453196142543f9275cbbf70 languageName: node linkType: hard @@ -14977,31 +15060,6 @@ __metadata: languageName: node linkType: hard -"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.2": - version: 1.1.18 - resolution: "which-typed-array@npm:1.1.18" - dependencies: - available-typed-arrays: "npm:^1.0.7" - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.3" - for-each: "npm:^0.3.3" - gopd: "npm:^1.2.0" - has-tostringtag: "npm:^1.0.2" - checksum: 10/11eed801b2bd08cdbaecb17aff381e0fb03526532f61acc06e6c7b9370e08062c33763a51f27825f13fdf34aabd0df6104007f4e8f96e6eaef7db0ce17a26d6e - languageName: node - linkType: hard - -"which@npm:^1.3.1": - version: 1.3.1 - resolution: "which@npm:1.3.1" - dependencies: - isexe: "npm:^2.0.0" - bin: - which: ./bin/which - checksum: 10/549dcf1752f3ee7fbb64f5af2eead4b9a2f482108b7de3e85c781d6c26d8cf6a52d37cfbe0642a155fa6470483fe892661a859c03157f24c669cf115f3bbab5e - languageName: node - linkType: hard - "which@npm:^2.0.1, which@npm:^2.0.2": version: 2.0.2 resolution: "which@npm:2.0.2" @@ -15031,10 +15089,10 @@ __metadata: languageName: node linkType: hard -"wildcard@npm:^2.0.0": - version: 2.0.0 - resolution: "wildcard@npm:2.0.0" - checksum: 10/56d4f8be540918ab3a676f0e57c9cac1d13009dc9974dbdc751a073bf71ec080376697eded083e8a8f86fcb3479135bfa9d4489e25e6c748666d3a53ee096d24 +"wildcard@npm:^2.0.0, wildcard@npm:^2.0.1": + version: 2.0.1 + resolution: "wildcard@npm:2.0.1" + checksum: 10/e0c60a12a219e4b12065d1199802d81c27b841ed6ad6d9d28240980c73ceec6f856771d575af367cbec2982d9ae7838759168b551776577f155044f5a5ba843c languageName: node linkType: hard @@ -15094,9 +15152,9 @@ __metadata: languageName: node linkType: hard -"ws@npm:^8.13.0": - version: 8.13.0 - resolution: "ws@npm:8.13.0" +"ws@npm:^8.18.0": + version: 8.19.0 + resolution: "ws@npm:8.19.0" peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ">=5.0.2" @@ -15105,7 +15163,16 @@ __metadata: optional: true utf-8-validate: optional: true - checksum: 10/1769532b6fdab9ff659f0b17810e7501831d34ecca23fd179ee64091dd93a51f42c59f6c7bb4c7a384b6c229aca8076fb312aa35626257c18081511ef62a161d + checksum: 10/26e4901e93abaf73af9f26a93707c95b4845e91a7a347ec8c569e6e9be7f9df066f6c2b817b2d685544e208207898a750b78461e6e8d810c11a370771450c31b + languageName: node + linkType: hard + +"wsl-utils@npm:^0.1.0": + version: 0.1.0 + resolution: "wsl-utils@npm:0.1.0" + dependencies: + is-wsl: "npm:^3.1.0" + checksum: 10/de4c92187e04c3c27b4478f410a02e81c351dc85efa3447bf1666f34fc80baacd890a6698ec91995631714086992036013286aea3d77e6974020d40a08e00aec languageName: node linkType: hard @@ -15116,12 +15183,12 @@ __metadata: languageName: node linkType: hard -"xml-formatter@npm:^2.6.1": - version: 2.6.1 - resolution: "xml-formatter@npm:2.6.1" +"xml-formatter@npm:^3.6.6": + version: 3.6.7 + resolution: "xml-formatter@npm:3.6.7" dependencies: - xml-parser-xo: "npm:^3.2.0" - checksum: 10/e18a4f195185d1a36b4918b23f74401d689b214ff3c5d2772e9ae6ec22b0866d0756d2d1b0b123cf2dbed9b5fbad18f01ca6092010b7f70fa037619ad3126333 + xml-parser-xo: "npm:^4.1.5" + checksum: 10/0bd9955ab43b60e0bbf55d092761f27dbe4a9871cc60554cc07f67d31a2ae50423d207b2d8f408aad7c5fb7651cc54cba325056b9ac7abcb5df246fc9826a8e3 languageName: node linkType: hard @@ -15136,17 +15203,10 @@ __metadata: languageName: node linkType: hard -"xml-parser-xo@npm:^3.2.0": - version: 3.2.0 - resolution: "xml-parser-xo@npm:3.2.0" - checksum: 10/666b373f3274e7265a23c82624dd4e89e6253776676057ced25c478c12808f5775525a5d67fa6abe33d40984082e0da35f7eb372cd26f9071287cd604ffb83c8 - languageName: node - linkType: hard - -"xtend@npm:^4.0.2": - version: 4.0.2 - resolution: "xtend@npm:4.0.2" - checksum: 10/ac5dfa738b21f6e7f0dd6e65e1b3155036d68104e67e5d5d1bde74892e327d7e5636a076f625599dc394330a731861e87343ff184b0047fef1360a7ec0a5a36a +"xml-parser-xo@npm:^4.1.5": + version: 4.1.5 + resolution: "xml-parser-xo@npm:4.1.5" + checksum: 10/0a6d24a2deb757e6595314f500cadd7d1fb2b06b92798a5bf4bdff05fc3b50815c20ec5bd101293602ca5a65f95453ec4a7320e364e060bf33ad8f0520e027a9 languageName: node linkType: hard @@ -15178,18 +15238,17 @@ __metadata: languageName: node linkType: hard -"yaml-loader@npm:^0.8.0": - version: 0.8.1 - resolution: "yaml-loader@npm:0.8.1" +"yaml-loader@npm:^0.9.0": + version: 0.9.0 + resolution: "yaml-loader@npm:0.9.0" dependencies: javascript-stringify: "npm:^2.0.1" - loader-utils: "npm:^2.0.0" yaml: "npm:^2.0.0" - checksum: 10/2969eb7d6083a95a8d95369906bbc73d124125be5f61556506d230a57b7acc872abe836bf631f89aa97d9e8198bee8d100fa68d5a6524be84cd8eb5062cf76d3 + checksum: 10/e9b4dd3363d86f5ed753ef70cacecdc9d5aae2078975b91e71b4c7b88c469140b0d7f6cfc22ba2390bf440a74a16df9ce74223393c089eba994a4750ed8a7b49 languageName: node linkType: hard -"yaml@npm:1.10.2, yaml@npm:^1.10.0, yaml@npm:^1.7.2": +"yaml@npm:1.10.2, yaml@npm:^1.10.0": version: 1.10.2 resolution: "yaml@npm:1.10.2" checksum: 10/e088b37b4d4885b70b50c9fa1b7e54bd2e27f5c87205f9deaffd1fb293ab263d9c964feadb9817a7b129a5bf30a06582cb08750f810568ecc14f3cdbabb79cb3 @@ -15225,13 +15284,6 @@ __metadata: languageName: node linkType: hard -"yocto-queue@npm:^0.1.0": - version: 0.1.0 - resolution: "yocto-queue@npm:0.1.0" - checksum: 10/f77b3d8d00310def622123df93d4ee654fc6a0096182af8bd60679ddcdfb3474c56c6c7190817c84a2785648cdee9d721c0154eb45698c62176c322fb46fc700 - languageName: node - linkType: hard - "yocto-queue@npm:^1.0.0": version: 1.0.0 resolution: "yocto-queue@npm:1.0.0" diff --git a/mkdocs.yml b/mkdocs.yml index 45fdd2c9d6..5f02cfeaa2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -13,6 +13,7 @@ plugins: nav: - Overview: - What is Backstage?: 'overview/what-is-backstage.md' + - Technical overview: 'overview/technical-overview.md' - Architecture overview: 'overview/architecture-overview.md' - Project Roadmap: 'overview/roadmap.md' - Vision: 'overview/vision.md' @@ -37,8 +38,15 @@ nav: - Kubernetes: 'deployment/k8s.md' - Using Backstage: - Logging in: 'getting-started/logging-in.md' - - Register a component: 'getting-started/register-a-component.md' - - Create a component: 'getting-started/create-a-component.md' + - Viewing the Catalog: 'getting-started/viewing-catalog.md' + - Viewing what you own: 'getting-started/view-what-you-own.md' + - Viewing entity relationships: 'getting-started/viewing-entity-relationships.md' + - Filtering the Catalog: 'getting-started/filter-catalog.md' + - Managing Components: + - Register a component: 'getting-started/register-a-component.md' + - Create a component: 'getting-started/create-a-component.md' + - Update a component: 'getting-started/update-a-component.md' + - Unregister/delete a component: 'getting-started/unregister-delete-component.md' - Keeping Backstage Updated: 'getting-started/keeping-backstage-updated.md' - Core Features: - Software Catalog: @@ -54,6 +62,7 @@ nav: - Extending the model: 'features/software-catalog/extending-the-model.md' - External integrations: 'features/software-catalog/external-integrations.md' - Catalog Customization: 'features/software-catalog/catalog-customization.md' + - Entity Presentation: 'features/software-catalog/entity-presentation.md' - API: 'features/software-catalog/api.md' - FAQ: 'features/software-catalog/faq.md' - Kubernetes: @@ -72,7 +81,6 @@ nav: - Writing Custom Actions: 'features/software-templates/writing-custom-actions.md' - Writing Custom Step Layouts: 'features/software-templates/writing-custom-step-layouts.md' - Templating Extensions: 'features/software-templates/templating-extensions.md' - - Migrating from v1beta2 to v1beta3 templates: 'features/software-templates/migrating-from-v1beta2-to-v1beta3.md' - Dry Run Testing: 'features/software-templates/dry-run-testing.md' - Backstage Search: - Overview: 'features/search/README.md' @@ -133,9 +141,8 @@ nav: - Locations: 'integrations/google-cloud-storage/locations.md' - LDAP: - Org Data: 'integrations/ldap/org.md' - - Plugins: + - Plugins (Legacy): - Intro to plugins: 'plugins/index.md' - - Existing plugins: 'plugins/existing-plugins.md' - Create a Backstage Plugin: 'plugins/create-a-plugin.md' - Plugin Development: 'plugins/plugin-development.md' - Structure of a plugin: 'plugins/structure-of-a-plugin.md' @@ -143,6 +150,7 @@ nav: - Composability System: 'plugins/composability.md' - Plugin Analytics: 'plugins/analytics.md' - Feature Flags: 'plugins/feature-flags.md' + - Internationalization (i18n): 'plugins/internationalization.md' - OpenAPI: - Schema-first plugins with OpenAPI (Experimental): 'openapi/01-getting-started.md' - Generate a client from your OpenAPI spec: 'openapi/generate-client.md' @@ -154,9 +162,9 @@ nav: - Testing: - Testing with Jest: 'plugins/testing.md' - Publishing: - - Publish private: 'plugins/publish-private.md' - Add to Directory: 'plugins/add-to-directory.md' - Observability: 'plugins/observability.md' + - New Backend System: 'plugins/new-backend-system.md' - Configuration: - Static Configuration in Backstage: 'conf/index.md' - Reading Backstage Configuration: 'conf/reading.md' @@ -214,7 +222,6 @@ nav: - React Router 6.0 Migration: 'tutorials/react-router-stable-migration.md' - Package Role Migration: 'tutorials/package-role-migration.md' - Migrating away from @backstage/core: 'tutorials/migrating-away-from-core.md' - - Adding Custom Plugin to Existing Monorepo App: 'tutorials/quickstart-app-plugin.md' - Manual Rollback using Knex: 'tutorials/manual-knex-rollback.md' - Switching Backstage from SQLite to PostgreSQL: 'tutorials/switching-sqlite-postgres.md' - Using the Backstage Proxy from Within a Plugin: 'tutorials/using-backstage-proxy-within-plugin.md' diff --git a/package.json b/package.json index 31429c425a..7e4b416e68 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "1.45.0-next.3", + "version": "1.50.0", "backstage": { "cli": { "new": { @@ -16,15 +16,11 @@ "type": "git", "url": "https://github.com/backstage/backstage" }, - "workspaces": { - "packages": [ - "packages/*", - "plugins/*" - ] - }, + "workspaces": [ + "packages/*", + "plugins/*" + ], "scripts": { - "build-storybook": "storybook build --output-dir dist-storybook", - "build-storybook:chromatic": "STORYBOOK_STORY_SET=chromatic storybook build --stats-json --output-dir dist-storybook", "build:all": "backstage-cli repo build --all", "build:api-docs": "LANG=en_EN yarn build:api-reports --docs --exclude 'plugins/@(api-docs|api-docs-module-protoc-gen-doc|app-visualizer|catalog-graph|catalog-import|catalog-unprocessed-entities|config-schema|example-todo-list|example-todo-list-backend)'", "build:api-reports": "yarn build:api-reports:only --tsc", @@ -32,10 +28,11 @@ "build:backend": "yarn workspace example-backend build", "build:knip-reports": "backstage-repo-tools knip-reports", "build:plugins-report": "node ./scripts/build-plugins-report", + "build-storybook": "storybook build --output-dir dist-storybook", + "build-storybook:chromatic": "STORYBOOK_STORY_SET=chromatic storybook build --stats-json --output-dir dist-storybook", "clean": "backstage-cli repo clean", "create-plugin": "echo \"use 'yarn new' instead\"", "dev": "echo \"use 'yarn start' instead\"", - "dev:next": "echo \"use 'yarn start:next' instead\"", "docker-build": "yarn tsc && yarn workspace example-backend build && yarn workspace example-backend build-image", "fix": "backstage-cli repo fix --publish", "postinstall": "husky || true", @@ -47,6 +44,7 @@ "lint:type-deps": "backstage-repo-tools type-deps", "mui-to-bui": "node scripts/mui-to-bui/backstage-migration-analytics.js", "new": "backstage-cli new", + "patch-pr": "node .patches/create-pr-patch.js", "prepare": "husky", "prettier:check": "prettier --check .", "prettier:fix": "prettier --write .", @@ -54,10 +52,10 @@ "snyk:test": "npx snyk test --yarn-workspaces --strict-out-of-sync=false", "snyk:test:package": "yarn snyk:test --include", "start": "backstage-cli repo start", - "start-backend": "echo \"Use 'yarn start example-backend' instead\"", "start:docker": "docker compose -f docker-compose.deps.yml up --wait && BACKSTAGE_ENV=docker yarn start", + "start:legacy": "yarn start example-app-legacy example-backend", "start:microsite": "cd microsite/ && yarn start", - "start:next": "yarn start example-app-next example-backend", + "start-backend": "echo \"Use 'yarn start example-backend' instead\"", "storybook": "storybook dev -p 6006", "sync-issue-templates": "node ./.github/ISSUE_TEMPLATE/sync.js", "techdocs-cli": "node scripts/techdocs-cli.js", @@ -116,7 +114,6 @@ "csstype@npm:^3.0.2": "3.0.9", "csstype@npm:^3.1.2": "3.0.9", "csstype@npm:^3.1.3": "3.0.9", - "jest-haste-map@^29.7.0": "patch:jest-haste-map@npm%3A29.7.0#./.yarn/patches/jest-haste-map-npm-29.7.0-e3be419eff.patch", "recast@npm:0.23.9>ast-types": "patch:ast-types@npm%3A0.16.1#./.yarn/patches/ast-types-npm-0.16.1-43c4ac4b0d.patch" }, "dependencies": { @@ -128,53 +125,64 @@ }, "devDependencies": { "@backstage/cli": "workspace:*", + "@backstage/cli-defaults": "workspace:*", "@backstage/codemods": "workspace:*", "@backstage/create-app": "workspace:*", "@backstage/e2e-test-utils": "workspace:*", "@backstage/eslint-plugin": "workspace:*", "@backstage/repo-tools": "workspace:*", "@changesets/cli": "^2.14.0", + "@jest/environment-jsdom-abstract": "^30", "@octokit/rest": "^19.0.3", "@playwright/test": "^1.32.3", "@spotify/eslint-plugin": "^15.0.0", - "@storybook/addon-a11y": "^9.1.7", - "@storybook/addon-docs": "^9.1.7", - "@storybook/addon-links": "^9.1.7", - "@storybook/addon-themes": "^9.1.7", - "@storybook/react-vite": "^9.1.7", + "@storybook/addon-a11y": "^10.3.3", + "@storybook/addon-docs": "^10.3.3", + "@storybook/addon-links": "^10.3.3", + "@storybook/addon-mcp": "^0.4.2", + "@storybook/addon-themes": "^10.3.3", + "@storybook/addon-vitest": "^10.3.3", + "@storybook/react-vite": "^10.3.3", "@techdocs/cli": "workspace:*", "@types/cacheable-request": "^8.3.6", + "@types/jest": "^30", + "@types/jsdom": "^27", "@types/memjs": "^1.3.3", - "@types/node": "^20.16.0", + "@types/node": "^22.13.14", "@types/webpack": "^5.28.0", "array-to-table": "^1.0.1", "command-exists": "^1.2.9", - "cross-env": "^7.0.0", + "cross-env": "^10.0.0", "e2e-test": "workspace:*", "eslint": "^8.6.0", + "eslint-plugin-node-import": "^1.0.5", "eslint-plugin-notice": "^1.0.0", "eslint-plugin-react": "^7.28.0", - "eslint-plugin-testing-library": "^6.0.0", + "eslint-plugin-testing-library": "^7.0.0", "fs-extra": "^11.2.0", "husky": "^9.0.0", - "lint-staged": "^15.0.0", + "jest": "^30", + "js-yaml": "^4.1.1", + "jsdom": "^27", + "lint-staged": "^16.0.0", "madge": "^8.0.0", "minimist": "^1.2.5", - "node-gyp": "^10.0.0", + "node-gyp": "^12.0.0", "prettier": "^2.2.1", "semver": "^7.5.3", "shx": "^0.4.0", "sloc": "^0.3.1", - "sort-package-json": "^2.8.0", - "storybook": "^9.1.7", + "sort-package-json": "^3.0.0", + "storybook": "^10.3.3", "ts-morph": "^24.0.0", "typedoc": "^0.28.0", "typescript": "~5.7.0", - "vite": "^7.1.5" + "vite": "^7.1.5", + "zod": "^3.25.76 || ^4.0.0" }, "packageManager": "yarn@4.8.1", "engines": { - "node": "20 || 22" + "node": "22 || 24" }, "madge": { "fileExtensions": [ diff --git a/packages/app-defaults/CHANGELOG.md b/packages/app-defaults/CHANGELOG.md index fbbbdf6569..9f01db7736 100644 --- a/packages/app-defaults/CHANGELOG.md +++ b/packages/app-defaults/CHANGELOG.md @@ -1,5 +1,159 @@ # @backstage/app-defaults +## 1.7.7 + +### Patch Changes + +- 400aa23: Added `FetchMiddlewares.clarifyFailures()` to the default fetch API middleware stack. +- Updated dependencies + - @backstage/theme@0.7.3 + - @backstage/core-app-api@1.20.0 + - @backstage/core-components@0.18.9 + - @backstage/plugin-permission-react@0.5.0 + - @backstage/core-plugin-api@1.12.5 + +## 1.7.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.7.3-next.0 + - @backstage/core-app-api@1.20.0-next.2 + - @backstage/core-components@0.18.9-next.1 + - @backstage/core-plugin-api@1.12.5-next.2 + - @backstage/plugin-permission-react@0.4.42-next.1 + +## 1.7.7-next.1 + +### Patch Changes + +- 400aa23: Added `FetchMiddlewares.clarifyFailures()` to the default fetch API middleware stack. +- Updated dependencies + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + +## 1.7.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-app-api@1.19.7-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + - @backstage/theme@0.7.2 + - @backstage/plugin-permission-react@0.4.42-next.0 + +## 1.7.6 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/core-app-api@1.19.6 + - @backstage/plugin-permission-react@0.4.41 + +## 1.7.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/theme@0.7.2 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## 1.7.5 + +### Patch Changes + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/core-components@0.18.7 + - @backstage/core-app-api@1.19.5 + - @backstage/theme@0.7.2 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-permission-react@0.4.40 + +## 1.7.5-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/core-app-api@1.19.5-next.1 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-permission-react@0.4.40-next.1 + - @backstage/theme@0.7.2-next.1 + +## 1.7.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.7.2-next.0 + - @backstage/core-components@0.18.7-next.1 + +## 1.7.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.6-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/core-app-api@1.19.4-next.0 + - @backstage/theme@0.7.1 + - @backstage/plugin-permission-react@0.4.40-next.0 + +## 1.7.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5 + +## 1.7.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + +## 1.7.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/plugin-permission-react@0.4.39 + +## 1.7.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + +## 1.7.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/plugin-permission-react@0.4.38 + ## 1.7.2-next.0 ### Patch Changes diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json index 8cd48112b2..912082932b 100644 --- a/packages/app-defaults/package.json +++ b/packages/app-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/app-defaults", - "version": "1.7.2-next.0", + "version": "1.7.7", "description": "Provides the default wiring of a Backstage App", "backstage": { "role": "web-library" @@ -52,13 +52,13 @@ "@types/react": "^18.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/packages/app-defaults/src/defaults/apis.ts b/packages/app-defaults/src/defaults/apis.ts index 97989dff91..b591154246 100644 --- a/packages/app-defaults/src/defaults/apis.ts +++ b/packages/app-defaults/src/defaults/apis.ts @@ -113,6 +113,7 @@ export const apis = [ identityApi, config: configApi, }), + FetchMiddlewares.clarifyFailures(), ], }); }, diff --git a/packages/app-next-example-plugin/.eslintrc.js b/packages/app-example-plugin/.eslintrc.js similarity index 100% rename from packages/app-next-example-plugin/.eslintrc.js rename to packages/app-example-plugin/.eslintrc.js diff --git a/packages/app-next-example-plugin/CHANGELOG.md b/packages/app-example-plugin/CHANGELOG.md similarity index 87% rename from packages/app-next-example-plugin/CHANGELOG.md rename to packages/app-example-plugin/CHANGELOG.md index 74ba940174..f87af48446 100644 --- a/packages/app-next-example-plugin/CHANGELOG.md +++ b/packages/app-example-plugin/CHANGELOG.md @@ -1,4 +1,107 @@ -# app-next-example-plugin +# app-example-plugin + +## 0.0.34 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/core-components@0.18.9 + +## 0.0.34-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + +## 0.0.34-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.9-next.0 + - @backstage/frontend-plugin-api@0.15.2-next.0 + +## 0.0.33 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + +## 0.0.33-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-components@0.18.8-next.1 + +## 0.0.33-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/core-components@0.18.8-next.0 + +## 0.0.32 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.7 + - @backstage/frontend-plugin-api@0.14.0 + +## 0.0.32-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.6-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + +## 0.0.31 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + +## 0.0.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + +## 0.0.30 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + +## 0.0.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-components@0.18.4-next.0 + +## 0.0.29 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-components@0.18.3 ## 0.0.29-next.0 diff --git a/packages/app-next-example-plugin/catalog-info.yaml b/packages/app-example-plugin/catalog-info.yaml similarity index 76% rename from packages/app-next-example-plugin/catalog-info.yaml rename to packages/app-example-plugin/catalog-info.yaml index 53869a83a9..8fef6a1706 100644 --- a/packages/app-next-example-plugin/catalog-info.yaml +++ b/packages/app-example-plugin/catalog-info.yaml @@ -1,8 +1,8 @@ apiVersion: backstage.io/v1alpha1 kind: Component metadata: - name: app-next-example-plugin - title: app-next-example-plugin + name: app-example-plugin + title: app-example-plugin description: Backstage internal example plugin spec: lifecycle: experimental diff --git a/packages/app-example-plugin/knip-report.md b/packages/app-example-plugin/knip-report.md new file mode 100644 index 0000000000..52915c86db --- /dev/null +++ b/packages/app-example-plugin/knip-report.md @@ -0,0 +1,9 @@ +# Knip report + +## Unused devDependencies (2) + +| Name | Location | Severity | +| :---------- | :---------------- | :------- | +| cross-fetch | package.json:51:6 | error | +| msw | package.json:52:6 | error | + diff --git a/packages/app-next-example-plugin/package.json b/packages/app-example-plugin/package.json similarity index 87% rename from packages/app-next-example-plugin/package.json rename to packages/app-example-plugin/package.json index 5415d793b3..ece5d38f57 100644 --- a/packages/app-next-example-plugin/package.json +++ b/packages/app-example-plugin/package.json @@ -1,12 +1,12 @@ { - "name": "app-next-example-plugin", - "version": "0.0.29-next.0", + "name": "app-example-plugin", + "version": "0.0.34", "description": "Backstage internal example plugin", "backstage": { "role": "frontend-plugin", "pluginId": "example", "pluginPackages": [ - "app-next-example-plugin" + "app-example-plugin" ] }, "publishConfig": { @@ -22,7 +22,7 @@ "repository": { "type": "git", "url": "https://github.com/backstage/backstage", - "directory": "packages/app-next-example-plugin" + "directory": "packages/app-example-plugin" }, "license": "Apache-2.0", "sideEffects": false, @@ -52,13 +52,13 @@ "msw": "^1.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/packages/app-example-plugin/report.api.md b/packages/app-example-plugin/report.api.md new file mode 100644 index 0000000000..0879f318aa --- /dev/null +++ b/packages/app-example-plugin/report.api.md @@ -0,0 +1,106 @@ +## API Report File for "app-example-plugin" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionInput } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; +import { JSX as JSX_2 } from 'react'; +import { JSX as JSX_3 } from 'react/jsx-runtime'; +import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; +import { RouteRef } from '@backstage/frontend-plugin-api'; + +// @public (undocumented) +const examplePlugin: OverridableFrontendPlugin< + {}, + {}, + { + 'page:example': OverridableExtensionDefinition<{ + kind: 'page'; + name: undefined; + config: { + path: string | undefined; + title: string | undefined; + }; + configInput: { + path?: string | undefined; + title?: string | undefined; + }; + output: + | ExtensionDataRef + | ExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + > + | ExtensionDataRef + | ExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >; + inputs: { + pages: ExtensionInput< + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >, + { + singleton: false; + optional: false; + internal: false; + } + >; + }; + params: { + path: string; + title?: string; + icon?: IconElement; + loader?: () => Promise; + routeRef?: RouteRef; + noHeader?: boolean; + }; + }>; + } +>; +export default examplePlugin; + +// @public (undocumented) +export const ExampleSidebarItem: () => JSX_3.Element; + +// (No @packageDocumentation comment for this package) +``` diff --git a/packages/app-next-example-plugin/src/Component.tsx b/packages/app-example-plugin/src/Component.tsx similarity index 100% rename from packages/app-next-example-plugin/src/Component.tsx rename to packages/app-example-plugin/src/Component.tsx diff --git a/packages/app-next-example-plugin/src/ExampleSidebarItem.tsx b/packages/app-example-plugin/src/ExampleSidebarItem.tsx similarity index 100% rename from packages/app-next-example-plugin/src/ExampleSidebarItem.tsx rename to packages/app-example-plugin/src/ExampleSidebarItem.tsx diff --git a/packages/app-next-example-plugin/src/index.ts b/packages/app-example-plugin/src/index.ts similarity index 100% rename from packages/app-next-example-plugin/src/index.ts rename to packages/app-example-plugin/src/index.ts diff --git a/packages/app-next-example-plugin/src/plugin.tsx b/packages/app-example-plugin/src/plugin.tsx similarity index 100% rename from packages/app-next-example-plugin/src/plugin.tsx rename to packages/app-example-plugin/src/plugin.tsx diff --git a/packages/app-legacy/.eslintrc.js b/packages/app-legacy/.eslintrc.js new file mode 100644 index 0000000000..e487f765b2 --- /dev/null +++ b/packages/app-legacy/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, { + rules: { + '@backstage/no-top-level-material-ui-4-imports': 'error', + }, +}); diff --git a/packages/app-legacy/CHANGELOG.md b/packages/app-legacy/CHANGELOG.md new file mode 100644 index 0000000000..6d9a5c7b04 --- /dev/null +++ b/packages/app-legacy/CHANGELOG.md @@ -0,0 +1,14053 @@ +# example-app-legacy + +## 0.2.120 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0 + - @backstage/app-defaults@1.7.7 + - @backstage/theme@0.7.3 + - @backstage/cli@0.36.1 + - @backstage/plugin-catalog@2.0.2 + - @backstage/plugin-catalog-graph@0.6.1 + - @backstage/plugin-catalog-import@0.13.12 + - @backstage/catalog-model@1.8.0 + - @backstage/plugin-catalog-react@2.1.2 + - @backstage/core-app-api@1.20.0 + - @backstage/plugin-org@0.7.1 + - @backstage/core-components@0.18.9 + - @backstage/plugin-catalog-unprocessed-entities@0.2.29 + - @backstage/plugin-scaffolder-react@1.20.1 + - @backstage/plugin-scaffolder@1.36.2 + - @backstage/frontend-app-api@0.16.2 + - @backstage/plugin-techdocs@1.17.3 + - @backstage/plugin-kubernetes@0.12.18 + - @backstage/plugin-notifications@0.5.16 + - @backstage/plugin-search@1.7.1 + - @backstage/plugin-api-docs@0.14.0 + - @backstage/plugin-permission-react@0.5.0 + - @backstage/plugin-signals@0.0.30 + - @backstage/plugin-devtools@0.1.38 + - @backstage/plugin-kubernetes-cluster@0.0.36 + - @backstage/config@1.3.7 + - @backstage/core-plugin-api@1.12.5 + - @backstage/integration-react@1.2.17 + - @backstage/plugin-auth-react@0.1.26 + - @backstage/plugin-catalog-common@1.1.9 + - @backstage/plugin-home@0.9.4 + - @backstage/plugin-home-react@0.1.37 + - @backstage/plugin-mui-to-bui@0.2.6 + - @backstage/plugin-search-common@1.2.23 + - @backstage/plugin-search-react@1.11.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.35 + - @backstage/plugin-techdocs-react@1.3.10 + - @backstage/plugin-user-settings@0.9.2 + +## 0.2.120-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.2 + - @backstage/theme@0.7.3-next.0 + - @backstage/plugin-catalog@2.0.2-next.2 + - @backstage/core-app-api@1.20.0-next.2 + - @backstage/cli@0.36.1-next.2 + - @backstage/core-components@0.18.9-next.1 + - @backstage/plugin-catalog-import@0.13.12-next.2 + - @backstage/plugin-catalog-react@2.1.2-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.29-next.2 + - @backstage/plugin-notifications@0.5.16-next.2 + - @backstage/plugin-catalog-graph@0.6.1-next.2 + - @backstage/plugin-scaffolder@1.36.2-next.2 + - @backstage/plugin-techdocs@1.17.3-next.2 + - @backstage/catalog-model@1.7.8-next.0 + - @backstage/config@1.3.7-next.0 + - @backstage/core-plugin-api@1.12.5-next.2 + - @backstage/frontend-app-api@0.16.2-next.2 + - @backstage/plugin-auth-react@0.1.26-next.1 + - @backstage/plugin-devtools@0.1.38-next.2 + - @backstage/plugin-search@1.7.1-next.2 + - @backstage/plugin-user-settings@0.9.2-next.2 + - @backstage/app-defaults@1.7.7-next.2 + - @backstage/plugin-home@0.9.4-next.2 + - @backstage/plugin-mui-to-bui@0.2.6-next.2 + - @backstage/plugin-scaffolder-react@1.20.1-next.2 + - @backstage/plugin-search-react@1.11.1-next.2 + - @backstage/plugin-signals@0.0.30-next.2 + - @backstage/plugin-techdocs-react@1.3.10-next.2 + - @backstage/integration-react@1.2.17-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.35-next.2 + - @backstage/plugin-org@0.7.1-next.2 + - @backstage/plugin-api-docs@0.13.6-next.2 + - @backstage/plugin-catalog-common@1.1.9-next.0 + - @backstage/plugin-kubernetes@0.12.18-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.36-next.1 + - @backstage/plugin-permission-react@0.4.42-next.1 + - @backstage/plugin-search-common@1.2.23-next.0 + +## 0.2.120-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/app-defaults@1.7.7-next.1 + - @backstage/cli@0.36.1-next.1 + - @backstage/ui@0.14.0-next.1 + - @backstage/plugin-catalog-graph@0.6.1-next.1 + - @backstage/plugin-catalog-import@0.13.12-next.1 + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/plugin-org@0.7.1-next.1 + - @backstage/frontend-app-api@0.16.2-next.1 + - @backstage/plugin-kubernetes@0.12.18-next.1 + - @backstage/plugin-notifications@0.5.16-next.1 + - @backstage/plugin-scaffolder@1.36.2-next.1 + - @backstage/plugin-search@1.7.1-next.1 + - @backstage/plugin-signals@0.0.30-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-api-docs@0.13.6-next.1 + - @backstage/plugin-catalog@2.0.2-next.1 + - @backstage/plugin-home@0.9.4-next.1 + - @backstage/plugin-scaffolder-react@1.20.1-next.1 + - @backstage/plugin-search-react@1.11.1-next.1 + - @backstage/plugin-techdocs@1.17.3-next.1 + - @backstage/plugin-user-settings@0.9.2-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.29-next.1 + - @backstage/plugin-devtools@0.1.38-next.1 + - @backstage/plugin-home-react@0.1.37-next.1 + - @backstage/plugin-mui-to-bui@0.2.6-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.35-next.1 + - @backstage/plugin-techdocs-react@1.3.10-next.1 + +## 0.2.120-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@2.0.2-next.0 + - @backstage/ui@0.14.0-next.0 + - @backstage/plugin-scaffolder-react@1.20.1-next.0 + - @backstage/plugin-catalog-react@2.1.1-next.0 + - @backstage/plugin-techdocs@1.17.3-next.0 + - @backstage/core-components@0.18.9-next.0 + - @backstage/plugin-signals@0.0.30-next.0 + - @backstage/plugin-scaffolder@1.36.2-next.0 + - @backstage/frontend-app-api@0.16.2-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.29-next.0 + - @backstage/plugin-api-docs@0.13.6-next.0 + - @backstage/plugin-catalog-graph@0.6.1-next.0 + - @backstage/plugin-catalog-import@0.13.12-next.0 + - @backstage/plugin-home@0.9.4-next.0 + - @backstage/plugin-org@0.7.1-next.0 + - @backstage/plugin-user-settings@0.9.2-next.0 + - @backstage/core-app-api@1.19.7-next.0 + - @backstage/plugin-devtools@0.1.38-next.0 + - @backstage/plugin-mui-to-bui@0.2.6-next.0 + - @backstage/plugin-notifications@0.5.16-next.0 + - @backstage/plugin-search@1.7.1-next.0 + - @backstage/cli@0.36.1-next.0 + - @backstage/plugin-kubernetes@0.12.18-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.36-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.35-next.0 + - @backstage/app-defaults@1.7.7-next.0 + - @backstage/integration-react@1.2.17-next.0 + - @backstage/plugin-auth-react@0.1.26-next.0 + - @backstage/plugin-home-react@0.1.37-next.0 + - @backstage/plugin-search-react@1.11.1-next.0 + - @backstage/plugin-techdocs-react@1.3.10-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + - @backstage/catalog-model@1.7.7 + - @backstage/config@1.3.6 + - @backstage/theme@0.7.2 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-react@0.4.42-next.0 + - @backstage/plugin-search-common@1.2.22 + +## 0.2.119 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.36.0 + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/ui@0.13.0 + - @backstage/frontend-app-api@0.16.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/plugin-user-settings@0.9.1 + - @backstage/plugin-search-react@1.11.0 + - @backstage/plugin-search@1.7.0 + - @backstage/plugin-api-docs@0.13.5 + - @backstage/plugin-devtools@0.1.37 + - @backstage/core-app-api@1.19.6 + - @backstage/plugin-scaffolder@1.36.0 + - @backstage/plugin-techdocs@1.17.1 + - @backstage/plugin-catalog@2.0.0 + - @backstage/plugin-catalog-graph@0.6.0 + - @backstage/plugin-catalog-import@0.13.11 + - @backstage/plugin-catalog-unprocessed-entities@0.2.27 + - @backstage/plugin-home@0.9.3 + - @backstage/plugin-kubernetes@0.12.17 + - @backstage/plugin-mui-to-bui@0.2.5 + - @backstage/plugin-org@0.7.0 + - @backstage/plugin-scaffolder-react@1.20.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.34 + - @backstage/plugin-notifications@0.5.15 + - @backstage/plugin-permission-react@0.4.41 + - @backstage/plugin-home-react@0.1.36 + - @backstage/plugin-kubernetes-cluster@0.0.35 + - @backstage/catalog-model@1.7.7 + - @backstage/app-defaults@1.7.6 + - @backstage/integration-react@1.2.16 + - @backstage/plugin-auth-react@0.1.25 + - @backstage/plugin-signals@0.0.29 + - @backstage/plugin-techdocs-react@1.3.9 + +## 0.2.119-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.13.0-next.2 + - @backstage/cli@0.36.0-next.2 + - @backstage/core-app-api@1.19.6-next.1 + - @backstage/plugin-techdocs@1.17.1-next.2 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/frontend-app-api@0.16.0-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.2 + - @backstage/core-components@0.18.8-next.1 + - @backstage/plugin-org@0.7.0-next.2 + - @backstage/plugin-catalog-graph@0.6.0-next.2 + - @backstage/plugin-mui-to-bui@0.2.5-next.2 + - @backstage/plugin-scaffolder-react@1.20.0-next.2 + - @backstage/plugin-api-docs@0.13.5-next.2 + - @backstage/plugin-catalog@2.0.0-next.2 + - @backstage/plugin-catalog-import@0.13.11-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.27-next.1 + - @backstage/plugin-devtools@0.1.37-next.2 + - @backstage/plugin-home@0.9.3-next.2 + - @backstage/plugin-home-react@0.1.36-next.1 + - @backstage/plugin-kubernetes@0.12.17-next.2 + - @backstage/plugin-notifications@0.5.15-next.1 + - @backstage/plugin-scaffolder@1.35.5-next.2 + - @backstage/plugin-search@1.6.2-next.2 + - @backstage/plugin-search-react@1.10.5-next.1 + - @backstage/plugin-signals@0.0.29-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.34-next.2 + - @backstage/plugin-techdocs-react@1.3.9-next.1 + - @backstage/plugin-user-settings@0.9.1-next.2 + +## 0.2.119-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.13.0-next.1 + - @backstage/cli@0.36.0-next.1 + - @backstage/plugin-devtools@0.1.37-next.1 + - @backstage/plugin-scaffolder@1.35.5-next.1 + - @backstage/plugin-api-docs@0.13.5-next.1 + - @backstage/plugin-techdocs@1.17.1-next.1 + - @backstage/plugin-catalog@1.34.0-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/plugin-scaffolder-react@1.19.8-next.1 + - @backstage/plugin-mui-to-bui@0.2.5-next.1 + - @backstage/plugin-catalog-graph@0.5.8-next.1 + - @backstage/plugin-catalog-import@0.13.11-next.1 + - @backstage/plugin-home@0.9.3-next.1 + - @backstage/plugin-org@0.6.50-next.1 + - @backstage/app-defaults@1.7.6-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/frontend-app-api@0.15.1-next.0 + - @backstage/integration-react@1.2.16-next.1 + - @backstage/theme@0.7.2 + - @backstage/plugin-auth-react@0.1.25-next.0 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-catalog-unprocessed-entities@0.2.27-next.0 + - @backstage/plugin-home-react@0.1.36-next.0 + - @backstage/plugin-kubernetes@0.12.17-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.35-next.1 + - @backstage/plugin-notifications@0.5.15-next.0 + - @backstage/plugin-permission-react@0.4.41-next.0 + - @backstage/plugin-search@1.6.2-next.1 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-search-react@1.10.5-next.0 + - @backstage/plugin-signals@0.0.29-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.34-next.1 + - @backstage/plugin-techdocs-react@1.3.9-next.0 + - @backstage/plugin-user-settings@0.9.1-next.1 + +## 0.2.119-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.12.1-next.0 + - @backstage/plugin-search-react@1.10.5-next.0 + - @backstage/plugin-search@1.6.2-next.0 + - @backstage/plugin-api-docs@0.13.5-next.0 + - @backstage/cli@0.35.5-next.0 + - @backstage/plugin-scaffolder@1.35.5-next.0 + - @backstage/plugin-catalog@1.33.1-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/plugin-mui-to-bui@0.2.5-next.0 + - @backstage/plugin-techdocs@1.17.1-next.0 + - @backstage/app-defaults@1.7.6-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/frontend-app-api@0.15.1-next.0 + - @backstage/integration-react@1.2.16-next.0 + - @backstage/theme@0.7.2 + - @backstage/plugin-auth-react@0.1.25-next.0 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-catalog-graph@0.5.8-next.0 + - @backstage/plugin-catalog-import@0.13.11-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.27-next.0 + - @backstage/plugin-devtools@0.1.37-next.0 + - @backstage/plugin-home@0.9.3-next.0 + - @backstage/plugin-home-react@0.1.36-next.0 + - @backstage/plugin-kubernetes@0.12.17-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.35-next.0 + - @backstage/plugin-notifications@0.5.15-next.0 + - @backstage/plugin-org@0.6.50-next.0 + - @backstage/plugin-permission-react@0.4.41-next.0 + - @backstage/plugin-scaffolder-react@1.19.8-next.0 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-signals@0.0.29-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.34-next.0 + - @backstage/plugin-techdocs-react@1.3.9-next.0 + - @backstage/plugin-user-settings@0.9.1-next.0 + +## 0.2.118 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.12.0 + - @backstage/frontend-app-api@0.15.0 + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/plugin-api-docs@0.13.4 + - @backstage/plugin-catalog-graph@0.5.7 + - @backstage/plugin-org@0.6.49 + - @backstage/cli@0.35.4 + - @backstage/core-components@0.18.7 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-scaffolder@1.35.3 + - @backstage/plugin-notifications@0.5.14 + - @backstage/plugin-catalog@1.33.0 + - @backstage/plugin-mui-to-bui@0.2.4 + - @backstage/core-app-api@1.19.5 + - @backstage/plugin-techdocs@1.17.0 + - @backstage/plugin-kubernetes@0.12.16 + - @backstage/theme@0.7.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.26 + - @backstage/plugin-devtools@0.1.36 + - @backstage/plugin-home@0.9.2 + - @backstage/plugin-search@1.6.0 + - @backstage/plugin-user-settings@0.9.0 + - @backstage/plugin-scaffolder-react@1.19.7 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-home-react@0.1.35 + - @backstage/plugin-techdocs-react@1.3.8 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.33 + - @backstage/integration-react@1.2.15 + - @backstage/plugin-kubernetes-cluster@0.0.34 + - @backstage/plugin-permission-react@0.4.40 + - @backstage/plugin-catalog-import@0.13.10 + - @backstage/app-defaults@1.7.5 + - @backstage/plugin-search-react@1.10.3 + - @backstage/plugin-auth-react@0.1.24 + - @backstage/plugin-signals@0.0.28 + - @backstage/plugin-catalog-common@1.1.8 + +## 0.2.118-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.12.0-next.2 + - @backstage/plugin-api-docs@0.13.4-next.2 + - @backstage/plugin-catalog-graph@0.5.7-next.2 + - @backstage/plugin-org@0.6.49-next.2 + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/cli@0.35.4-next.2 + - @backstage/plugin-catalog@1.33.0-next.2 + - @backstage/core-app-api@1.19.5-next.1 + - @backstage/plugin-kubernetes@0.12.16-next.2 + - @backstage/plugin-scaffolder@1.35.3-next.2 + - @backstage/plugin-techdocs@1.16.3-next.2 + - @backstage/plugin-scaffolder-react@1.19.7-next.2 + - @backstage/plugin-user-settings@0.9.0-next.2 + - @backstage/plugin-home-react@0.1.35-next.1 + - @backstage/plugin-home@0.9.2-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.33-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.26-next.1 + - @backstage/integration-react@1.2.15-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.34-next.2 + - @backstage/frontend-app-api@0.15.0-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-permission-react@0.4.40-next.1 + - @backstage/plugin-catalog-import@0.13.10-next.2 + - @backstage/plugin-techdocs-react@1.3.8-next.1 + - @backstage/app-defaults@1.7.5-next.2 + - @backstage/plugin-notifications@0.5.14-next.2 + - @backstage/plugin-search-react@1.10.3-next.2 + - @backstage/plugin-auth-react@0.1.24-next.1 + - @backstage/plugin-mui-to-bui@0.2.4-next.2 + - @backstage/plugin-devtools@0.1.36-next.1 + - @backstage/plugin-signals@0.0.28-next.2 + - @backstage/theme@0.7.2-next.1 + - @backstage/plugin-search@1.6.0-next.2 + +## 0.2.118-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.15.0-next.1 + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/plugin-scaffolder@1.35.3-next.1 + - @backstage/plugin-catalog-graph@0.5.7-next.1 + - @backstage/plugin-catalog@1.33.0-next.1 + - @backstage/ui@0.12.0-next.1 + - @backstage/theme@0.7.2-next.0 + - @backstage/cli@0.35.4-next.1 + - @backstage/plugin-mui-to-bui@0.2.4-next.1 + - @backstage/plugin-search@1.6.0-next.1 + - @backstage/plugin-search-react@1.10.3-next.1 + - @backstage/plugin-api-docs@0.13.4-next.1 + - @backstage/plugin-catalog-import@0.13.10-next.1 + - @backstage/plugin-home@0.9.2-next.1 + - @backstage/plugin-kubernetes@0.12.16-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.34-next.1 + - @backstage/plugin-org@0.6.49-next.1 + - @backstage/plugin-scaffolder-react@1.19.7-next.1 + - @backstage/plugin-techdocs@1.16.3-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.33-next.1 + - @backstage/plugin-user-settings@0.8.32-next.1 + - @backstage/app-defaults@1.7.5-next.1 + - @backstage/core-components@0.18.7-next.1 + - @backstage/plugin-notifications@0.5.14-next.1 + - @backstage/plugin-signals@0.0.28-next.1 + - @backstage/plugin-techdocs-react@1.3.8-next.0 + - @backstage/integration-react@1.2.15-next.1 + +## 0.2.118-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.14.1-next.0 + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/cli@0.35.3-next.0 + - @backstage/ui@0.12.0-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/plugin-scaffolder@1.35.2-next.0 + - @backstage/plugin-api-docs@0.13.4-next.0 + - @backstage/plugin-scaffolder-react@1.19.6-next.0 + - @backstage/plugin-techdocs@1.16.3-next.0 + - @backstage/plugin-devtools@0.1.36-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/plugin-techdocs-react@1.3.8-next.0 + - @backstage/core-app-api@1.19.4-next.0 + - @backstage/plugin-home@0.9.1-next.0 + - @backstage/plugin-search-react@1.10.3-next.0 + - @backstage/plugin-catalog@1.32.3-next.0 + - @backstage/plugin-catalog-graph@0.5.7-next.0 + - @backstage/plugin-catalog-import@0.13.10-next.0 + - @backstage/plugin-kubernetes@0.12.16-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.34-next.0 + - @backstage/plugin-org@0.6.49-next.0 + - @backstage/plugin-search@1.5.4-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.33-next.0 + - @backstage/plugin-user-settings@0.8.32-next.0 + - @backstage/app-defaults@1.7.5-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/integration-react@1.2.15-next.0 + - @backstage/theme@0.7.1 + - @backstage/plugin-auth-react@0.1.24-next.0 + - @backstage/plugin-catalog-common@1.1.8-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.26-next.0 + - @backstage/plugin-mui-to-bui@0.2.4-next.0 + - @backstage/plugin-notifications@0.5.14-next.0 + - @backstage/plugin-permission-react@0.4.40-next.0 + - @backstage/plugin-signals@0.0.28-next.0 + +## 0.2.117 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.11.0 + - @backstage/plugin-catalog@1.32.2 + - @backstage/plugin-home@0.9.0 + - @backstage/cli@0.35.2 + - @backstage/core-components@0.18.5 + - @backstage/plugin-org@0.6.48 + - @backstage/plugin-mui-to-bui@0.2.3 + - @backstage/plugin-notifications@0.5.13 + - @backstage/frontend-app-api@0.14.0 + - @backstage/plugin-api-docs@0.13.3 + - @backstage/plugin-techdocs@1.16.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.25 + - @backstage/plugin-devtools@0.1.35 + - @backstage/plugin-scaffolder@1.35.1 + - @backstage/plugin-catalog-graph@0.5.6 + - @backstage/plugin-search@1.5.3 + - @backstage/plugin-scaffolder-react@1.19.5 + - @backstage/plugin-search-react@1.10.2 + - @backstage/app-defaults@1.7.4 + - @backstage/integration-react@1.2.14 + - @backstage/plugin-auth-react@0.1.23 + - @backstage/plugin-catalog-import@0.13.9 + - @backstage/plugin-catalog-react@1.21.5 + - @backstage/plugin-kubernetes@0.12.15 + - @backstage/plugin-kubernetes-cluster@0.0.33 + - @backstage/plugin-signals@0.0.27 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.32 + - @backstage/plugin-techdocs-react@1.3.7 + - @backstage/plugin-user-settings@0.8.31 + +## 0.2.117-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.11.0-next.1 + - @backstage/plugin-catalog@1.32.2-next.2 + - @backstage/plugin-home@0.9.0-next.2 + - @backstage/plugin-api-docs@0.13.3-next.2 + - @backstage/plugin-scaffolder@1.35.1-next.2 + - @backstage/cli@0.35.2-next.1 + +## 0.2.117-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/plugin-catalog-graph@0.5.5-next.1 + - @backstage/plugin-search@1.5.2-next.1 + - @backstage/app-defaults@1.7.4-next.0 + - @backstage/cli@0.35.2-next.1 + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-api-docs@0.13.3-next.1 + - @backstage/plugin-auth-react@0.1.23-next.0 + - @backstage/plugin-catalog@1.32.2-next.1 + - @backstage/plugin-catalog-import@0.13.9-next.1 + - @backstage/plugin-catalog-react@1.21.5-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.25-next.0 + - @backstage/plugin-devtools@0.1.35-next.1 + - @backstage/plugin-home@0.8.16-next.1 + - @backstage/plugin-kubernetes@0.12.15-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.33-next.1 + - @backstage/plugin-notifications@0.5.13-next.0 + - @backstage/plugin-org@0.6.48-next.1 + - @backstage/plugin-scaffolder@1.35.1-next.1 + - @backstage/plugin-scaffolder-react@1.19.5-next.1 + - @backstage/plugin-search-react@1.10.2-next.0 + - @backstage/plugin-signals@0.0.27-next.0 + - @backstage/plugin-techdocs@1.16.2-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.32-next.1 + - @backstage/plugin-techdocs-react@1.3.7-next.0 + - @backstage/plugin-user-settings@0.8.31-next.1 + +## 0.2.117-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.35.2-next.1 + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-catalog-import@0.13.9-next.0 + - @backstage/plugin-scaffolder@1.35.1-next.0 + - @backstage/plugin-techdocs@1.16.2-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.32-next.0 + - @backstage/plugin-catalog@1.32.2-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.0 + - @backstage/plugin-scaffolder-react@1.19.5-next.0 + - @backstage/frontend-app-api@0.13.4-next.0 + - @backstage/plugin-api-docs@0.13.3-next.0 + - @backstage/plugin-catalog-graph@0.5.5-next.0 + - @backstage/plugin-org@0.6.48-next.0 + - @backstage/plugin-user-settings@0.8.31-next.0 + - @backstage/plugin-home@0.8.16-next.0 + - @backstage/plugin-kubernetes@0.12.15-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.33-next.0 + - @backstage/plugin-search@1.5.2-next.0 + - @backstage/plugin-search-react@1.10.1 + - @backstage/plugin-devtools@0.1.35-next.0 + +## 0.2.117-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.11.0-next.0 + - @backstage/cli@0.35.2-next.0 + - @backstage/plugin-mui-to-bui@0.2.3-next.0 + - @backstage/app-defaults@1.7.3 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-app-api@1.19.3 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/frontend-app-api@0.13.3 + - @backstage/integration-react@1.2.13 + - @backstage/theme@0.7.1 + - @backstage/plugin-api-docs@0.13.2 + - @backstage/plugin-auth-react@0.1.22 + - @backstage/plugin-catalog@1.32.1 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-graph@0.5.4 + - @backstage/plugin-catalog-import@0.13.8 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-catalog-unprocessed-entities@0.2.24 + - @backstage/plugin-devtools@0.1.34 + - @backstage/plugin-home@0.8.15 + - @backstage/plugin-kubernetes@0.12.14 + - @backstage/plugin-kubernetes-cluster@0.0.32 + - @backstage/plugin-notifications@0.5.12 + - @backstage/plugin-org@0.6.47 + - @backstage/plugin-permission-react@0.4.39 + - @backstage/plugin-scaffolder@1.35.0 + - @backstage/plugin-scaffolder-react@1.19.4 + - @backstage/plugin-search@1.5.1 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-search-react@1.10.1 + - @backstage/plugin-signals@0.0.26 + - @backstage/plugin-techdocs@1.16.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.31 + - @backstage/plugin-techdocs-react@1.3.6 + - @backstage/plugin-user-settings@0.8.30 + +## 0.2.116 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/cli@0.35.0 + - @backstage/ui@0.10.0 + - @backstage/plugin-catalog-graph@0.5.4 + - @backstage/plugin-api-docs@0.13.2 + - @backstage/plugin-techdocs@1.16.1 + - @backstage/plugin-catalog@1.32.1 + - @backstage/plugin-search@1.5.1 + - @backstage/plugin-org@0.6.47 + - @backstage/plugin-scaffolder-react@1.19.4 + - @backstage/core-components@0.18.4 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.31 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-kubernetes@0.12.14 + - @backstage/plugin-search-react@1.10.1 + - @backstage/core-plugin-api@1.12.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.24 + - @backstage/plugin-catalog-import@0.13.8 + - @backstage/plugin-notifications@0.5.12 + - @backstage/plugin-user-settings@0.8.30 + - @backstage/plugin-scaffolder@1.35.0 + - @backstage/plugin-devtools@0.1.34 + - @backstage/plugin-home@0.8.15 + - @backstage/theme@0.7.1 + - @backstage/plugin-kubernetes-cluster@0.0.32 + - @backstage/app-defaults@1.7.3 + - @backstage/frontend-app-api@0.13.3 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-auth-react@0.1.22 + - @backstage/plugin-mui-to-bui@0.2.2 + - @backstage/plugin-permission-react@0.4.39 + - @backstage/plugin-signals@0.0.26 + - @backstage/plugin-techdocs-react@1.3.6 + +## 0.2.116-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3-next.1 + - @backstage/cli@0.35.0-next.2 + - @backstage/plugin-catalog-graph@0.5.4-next.1 + - @backstage/plugin-api-docs@0.13.2-next.1 + - @backstage/plugin-techdocs@1.16.1-next.2 + - @backstage/plugin-catalog@1.32.1-next.1 + - @backstage/plugin-search@1.5.1-next.1 + - @backstage/plugin-org@0.6.47-next.1 + - @backstage/plugin-scaffolder-react@1.19.4-next.2 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/plugin-home@0.8.15-next.1 + - @backstage/plugin-scaffolder@1.34.4-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.32-next.2 + - @backstage/app-defaults@1.7.3-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/frontend-app-api@0.13.3-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/ui@0.10.0-next.1 + - @backstage/plugin-auth-react@0.1.22-next.0 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-import@0.13.8-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.24-next.1 + - @backstage/plugin-devtools@0.1.34-next.0 + - @backstage/plugin-kubernetes@0.12.14-next.1 + - @backstage/plugin-mui-to-bui@0.2.2-next.1 + - @backstage/plugin-notifications@0.5.12-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-search-react@1.10.1-next.0 + - @backstage/plugin-signals@0.0.26-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.31-next.1 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/plugin-user-settings@0.8.30-next.0 + +## 0.2.116-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.10.0-next.1 + - @backstage/core-components@0.18.4-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.31-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.1 + - @backstage/plugin-scaffolder-react@1.19.4-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.24-next.1 + - @backstage/cli@0.34.6-next.1 + - @backstage/plugin-techdocs@1.16.1-next.1 + - @backstage/plugin-mui-to-bui@0.2.2-next.1 + - @backstage/plugin-kubernetes@0.12.14-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.32-next.1 + +## 0.2.116-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-graph@0.5.4-next.0 + - @backstage/ui@0.9.1-next.0 + - @backstage/plugin-kubernetes@0.12.14-next.0 + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.24-next.0 + - @backstage/plugin-catalog-import@0.13.8-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/plugin-notifications@0.5.12-next.0 + - @backstage/plugin-user-settings@0.8.30-next.0 + - @backstage/plugin-scaffolder@1.34.4-next.0 + - @backstage/plugin-api-docs@0.13.2-next.0 + - @backstage/plugin-devtools@0.1.34-next.0 + - @backstage/plugin-techdocs@1.16.1-next.0 + - @backstage/plugin-catalog@1.32.1-next.0 + - @backstage/plugin-search@1.5.1-next.0 + - @backstage/plugin-home@0.8.15-next.0 + - @backstage/plugin-org@0.6.47-next.0 + - @backstage/cli@0.34.6-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/plugin-mui-to-bui@0.2.2-next.0 + - @backstage/frontend-app-api@0.13.3-next.0 + - @backstage/plugin-scaffolder-react@1.19.4-next.0 + - @backstage/plugin-search-react@1.10.1-next.0 + - @backstage/plugin-signals@0.0.26-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.31-next.0 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.32-next.0 + - @backstage/app-defaults@1.7.3-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-auth-react@0.1.22-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-search-common@1.2.21 + +## 0.2.115 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-api-docs@0.13.1 + - @backstage/ui@0.9.0 + - @backstage/plugin-org@0.6.46 + - @backstage/cli@0.34.5 + - @backstage/plugin-techdocs@1.16.0 + - @backstage/plugin-signals@0.0.25 + - @backstage/frontend-app-api@0.13.2 + - @backstage/plugin-search-react@1.10.0 + - @backstage/plugin-catalog@1.32.0 + - @backstage/plugin-search@1.5.0 + - @backstage/plugin-home@0.8.14 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.30 + - @backstage/plugin-scaffolder-react@1.19.3 + - @backstage/plugin-catalog-graph@0.5.3 + - @backstage/core-components@0.18.3 + - @backstage/plugin-user-settings@0.8.29 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/plugin-catalog-import@0.13.7 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/plugin-scaffolder@1.34.3 + - @backstage/plugin-catalog-unprocessed-entities@0.2.23 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-mui-to-bui@0.2.1 + - @backstage/plugin-devtools@0.1.33 + - @backstage/plugin-kubernetes@0.12.13 + - @backstage/plugin-notifications@0.5.11 + - @backstage/plugin-techdocs-react@1.3.5 + - @backstage/app-defaults@1.7.2 + - @backstage/integration-react@1.2.12 + - @backstage/plugin-auth-react@0.1.21 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-kubernetes-cluster@0.0.31 + - @backstage/plugin-search-common@1.2.21 + +## 0.2.115-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.9.0-next.2 + - @backstage/plugin-techdocs@1.16.0-next.1 + - @backstage/plugin-signals@0.0.25-next.1 + - @backstage/plugin-search-react@1.10.0-next.1 + - @backstage/plugin-catalog@1.32.0-next.1 + - @backstage/plugin-search@1.5.0-next.1 + - @backstage/cli@0.34.5-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.30-next.1 + - @backstage/plugin-catalog-react@1.21.3-next.1 + - @backstage/core-components@0.18.3-next.1 + - @backstage/core-plugin-api@1.11.2-next.1 + - @backstage/core-app-api@1.19.2-next.1 + - @backstage/plugin-catalog-graph@0.5.3-next.1 + - @backstage/plugin-scaffolder@1.34.3-next.0 + - @backstage/plugin-api-docs@0.13.1-next.1 + - @backstage/plugin-catalog-import@0.13.7-next.0 + - @backstage/plugin-org@0.6.46-next.0 + - @backstage/plugin-scaffolder-react@1.19.3-next.0 + - @backstage/plugin-user-settings@0.8.29-next.0 + +## 0.2.115-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.9.0-next.1 + - @backstage/plugin-mui-to-bui@0.2.1-next.1 + +## 0.2.115-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.34.5-next.0 + - @backstage/ui@0.8.2-next.0 + - @backstage/plugin-scaffolder-react@1.19.3-next.0 + - @backstage/plugin-techdocs@1.15.2-next.0 + - @backstage/plugin-user-settings@0.8.29-next.0 + - @backstage/plugin-permission-react@0.4.38-next.0 + - @backstage/plugin-search-react@1.9.6-next.0 + - @backstage/plugin-signals@0.0.25-next.0 + - @backstage/plugin-catalog-react@1.21.3-next.0 + - @backstage/plugin-scaffolder@1.34.3-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.23-next.0 + - @backstage/core-app-api@1.19.2-next.0 + - @backstage/core-plugin-api@1.11.2-next.0 + - @backstage/frontend-app-api@0.13.2-next.0 + - @backstage/config@1.3.6-next.0 + - @backstage/core-components@0.18.3-next.0 + - @backstage/catalog-model@1.7.6-next.0 + - @backstage/plugin-catalog-import@0.13.7-next.0 + - @backstage/app-defaults@1.7.2-next.0 + - @backstage/integration-react@1.2.12-next.0 + - @backstage/theme@0.7.0 + - @backstage/plugin-api-docs@0.13.1-next.0 + - @backstage/plugin-auth-react@0.1.21-next.0 + - @backstage/plugin-catalog@1.31.5-next.0 + - @backstage/plugin-catalog-common@1.1.7-next.0 + - @backstage/plugin-catalog-graph@0.5.3-next.0 + - @backstage/plugin-devtools@0.1.33-next.0 + - @backstage/plugin-home@0.8.14-next.0 + - @backstage/plugin-kubernetes@0.12.13-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.31-next.0 + - @backstage/plugin-mui-to-bui@0.2.1-next.0 + - @backstage/plugin-notifications@0.5.11-next.0 + - @backstage/plugin-org@0.6.46-next.0 + - @backstage/plugin-search@1.4.32-next.0 + - @backstage/plugin-search-common@1.2.21-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.30-next.0 + - @backstage/plugin-techdocs-react@1.3.5-next.0 + +## 0.2.114 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.8.0 + - @backstage/plugin-mui-to-bui@0.2.0 + - @backstage/plugin-catalog-graph@0.5.2 + - @backstage/plugin-scaffolder-react@1.19.2 + - @backstage/plugin-catalog-react@1.21.2 + - @backstage/core-components@0.18.2 + - @backstage/plugin-signals@0.0.24 + - @backstage/cli@0.34.4 + - @backstage/plugin-home@0.8.13 + - @backstage/plugin-org@0.6.45 + - @backstage/plugin-scaffolder@1.34.2 + - @backstage/plugin-search-react@1.9.5 + - @backstage/plugin-search@1.4.31 + - @backstage/plugin-notifications@0.5.10 + - @backstage/plugin-api-docs@0.13.0 + - @backstage/config@1.3.5 + - @backstage/plugin-user-settings@0.8.27 + - @backstage/theme@0.7.0 + - @backstage/plugin-kubernetes-cluster@0.0.30 + - @backstage/plugin-kubernetes@0.12.12 + - @backstage/frontend-app-api@0.13.1 + - @backstage/plugin-catalog@1.31.4 + - @backstage/app-defaults@1.7.1 + - @backstage/plugin-techdocs@1.15.1 + - @backstage/plugin-techdocs-react@1.3.4 + - @backstage/core-app-api@1.19.1 + - @backstage/core-plugin-api@1.11.1 + - @backstage/integration-react@1.2.11 + - @backstage/plugin-auth-react@0.1.20 + - @backstage/plugin-catalog-common@1.1.6 + - @backstage/plugin-catalog-import@0.13.6 + - @backstage/plugin-catalog-unprocessed-entities@0.2.22 + - @backstage/plugin-devtools@0.1.32 + - @backstage/plugin-permission-react@0.4.37 + - @backstage/plugin-search-common@1.2.20 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.29 + +## 0.2.114-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-mui-to-bui@0.2.0-next.0 + - @backstage/plugin-scaffolder-react@1.19.2-next.2 + - @backstage/plugin-org@0.6.45-next.2 + - @backstage/cli@0.34.4-next.2 + - @backstage/ui@0.7.2-next.1 + - @backstage/core-components@0.18.2-next.2 + - @backstage/theme@0.6.9-next.0 + - @backstage/plugin-scaffolder@1.34.2-next.2 + - @backstage/app-defaults@1.7.1-next.2 + - @backstage/plugin-home@0.8.13-next.2 + - @backstage/plugin-notifications@0.5.10-next.2 + - @backstage/plugin-search-react@1.9.5-next.2 + - @backstage/plugin-signals@0.0.24-next.2 + - @backstage/plugin-techdocs@1.15.1-next.2 + - @backstage/plugin-techdocs-react@1.3.4-next.1 + - @backstage/plugin-user-settings@0.8.27-next.2 + +## 0.2.114-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/cli@0.34.4-next.1 + - @backstage/core-app-api@1.19.1-next.0 + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/frontend-app-api@0.13.1-next.1 + - @backstage/integration-react@1.2.11-next.1 + - @backstage/plugin-catalog-import@0.13.6-next.1 + - @backstage/plugin-home@0.8.13-next.1 + - @backstage/plugin-permission-react@0.4.37-next.0 + - @backstage/plugin-techdocs@1.15.1-next.1 + - @backstage/plugin-techdocs-react@1.3.4-next.1 + - @backstage/app-defaults@1.7.1-next.1 + - @backstage/plugin-api-docs@0.13.0-next.1 + - @backstage/plugin-catalog@1.31.4-next.1 + - @backstage/plugin-catalog-graph@0.5.2-next.1 + - @backstage/plugin-catalog-react@1.21.2-next.1 + - @backstage/plugin-notifications@0.5.10-next.1 + - @backstage/plugin-org@0.6.45-next.1 + - @backstage/plugin-scaffolder@1.34.2-next.1 + - @backstage/plugin-scaffolder-react@1.19.2-next.1 + - @backstage/plugin-search@1.4.31-next.1 + - @backstage/plugin-search-react@1.9.5-next.1 + - @backstage/plugin-signals@0.0.24-next.1 + - @backstage/plugin-user-settings@0.8.27-next.1 + - @backstage/plugin-auth-react@0.1.20-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.22-next.1 + - @backstage/plugin-devtools@0.1.32-next.1 + - @backstage/plugin-kubernetes@0.12.12-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.30-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.29-next.1 + - @backstage/plugin-catalog-common@1.1.6-next.0 + - @backstage/plugin-search-common@1.2.20-next.0 + +## 0.2.114-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.7.2-next.0 + - @backstage/plugin-catalog-graph@0.5.2-next.0 + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/core-components@0.18.2-next.0 + - @backstage/cli@0.34.4-next.0 + - @backstage/plugin-scaffolder@1.34.2-next.0 + - @backstage/plugin-api-docs@0.13.0-next.0 + - @backstage/plugin-catalog@1.31.4-next.0 + - @backstage/plugin-catalog-import@0.13.6-next.0 + - @backstage/plugin-home@0.8.13-next.0 + - @backstage/plugin-kubernetes@0.12.12-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.30-next.0 + - @backstage/plugin-org@0.6.45-next.0 + - @backstage/plugin-scaffolder-react@1.19.2-next.0 + - @backstage/plugin-search@1.4.31-next.0 + - @backstage/plugin-techdocs@1.15.1-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.29-next.0 + - @backstage/plugin-user-settings@0.8.27-next.0 + - @backstage/app-defaults@1.7.1-next.0 + - @backstage/integration-react@1.2.11-next.0 + - @backstage/plugin-auth-react@0.1.20-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.22-next.0 + - @backstage/plugin-devtools@0.1.32-next.0 + - @backstage/plugin-notifications@0.5.10-next.0 + - @backstage/plugin-search-react@1.9.5-next.0 + - @backstage/plugin-signals@0.0.24-next.0 + - @backstage/plugin-techdocs-react@1.3.4-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.19.0 + - @backstage/core-plugin-api@1.11.0 + - @backstage/frontend-app-api@0.13.1-next.0 + - @backstage/theme@0.6.8 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-search-common@1.2.19 + +## 0.2.113 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.34.2 + - @backstage/ui@0.7.1 + - @backstage/plugin-techdocs@1.15.0 + - @backstage/plugin-catalog-react@1.21.0 + - @backstage/plugin-notifications@0.5.9 + - @backstage/app-defaults@1.7.0 + - @backstage/plugin-catalog-graph@0.5.0 + - @backstage/plugin-org@0.6.44 + - @backstage/plugin-techdocs-react@1.3.3 + - @backstage/plugin-auth-react@0.1.19 + - @backstage/plugin-home@0.8.12 + - @backstage/core-plugin-api@1.11.0 + - @backstage/plugin-scaffolder@1.34.1 + - @backstage/plugin-user-settings@0.8.26 + - @backstage/plugin-catalog@1.31.3 + - @backstage/core-components@0.18.0 + - @backstage/frontend-app-api@0.13.0 + - @backstage/plugin-scaffolder-react@1.19.1 + - @backstage/core-app-api@1.19.0 + - @backstage/plugin-api-docs@0.12.11 + - @backstage/plugin-catalog-import@0.13.5 + - @backstage/plugin-catalog-unprocessed-entities@0.2.21 + - @backstage/plugin-devtools@0.1.31 + - @backstage/plugin-kubernetes@0.12.11 + - @backstage/plugin-search@1.4.30 + - @backstage/plugin-search-react@1.9.4 + - @backstage/plugin-signals@0.0.23 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.28 + - @backstage/integration-react@1.2.10 + - @backstage/plugin-kubernetes-cluster@0.0.29 + +## 0.2.113-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.7.1-next.0 + - @backstage/plugin-auth-react@0.1.19-next.1 + - @backstage/plugin-home@0.8.12-next.2 + - @backstage/plugin-scaffolder@1.34.1-next.2 + - @backstage/plugin-catalog-react@1.21.0-next.2 + - @backstage/core-components@0.17.6-next.1 + - @backstage/plugin-user-settings@0.8.26-next.2 + - @backstage/plugin-scaffolder-react@1.19.1-next.2 + - @backstage/plugin-catalog@1.31.3-next.2 + - @backstage/cli@0.34.2-next.2 + - @backstage/plugin-catalog-graph@0.4.23-next.2 + - @backstage/plugin-catalog-import@0.13.5-next.2 + - @backstage/plugin-org@0.6.44-next.2 + - @backstage/plugin-techdocs@1.14.2-next.2 + - @backstage/plugin-api-docs@0.12.11-next.2 + - @backstage/plugin-kubernetes@0.12.11-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.29-next.2 + - @backstage/plugin-search@1.4.30-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.28-next.0 + +## 0.2.113-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.20.2-next.1 + - @backstage/cli@0.34.2-next.1 + - @backstage/core-components@0.17.6-next.0 + - @backstage/frontend-app-api@0.12.1-next.0 + - @backstage/plugin-api-docs@0.12.11-next.1 + - @backstage/plugin-catalog@1.31.3-next.1 + - @backstage/plugin-catalog-graph@0.4.23-next.1 + - @backstage/plugin-catalog-import@0.13.5-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.21-next.1 + - @backstage/plugin-devtools@0.1.31-next.1 + - @backstage/plugin-home@0.8.12-next.1 + - @backstage/plugin-kubernetes@0.12.11-next.1 + - @backstage/plugin-notifications@0.5.9-next.1 + - @backstage/plugin-org@0.6.44-next.1 + - @backstage/plugin-scaffolder@1.34.1-next.1 + - @backstage/plugin-scaffolder-react@1.19.1-next.1 + - @backstage/plugin-search@1.4.30-next.1 + - @backstage/plugin-search-react@1.9.4-next.0 + - @backstage/plugin-signals@0.0.23-next.1 + - @backstage/plugin-techdocs@1.14.2-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.28-next.0 + - @backstage/plugin-techdocs-react@1.3.3-next.0 + - @backstage/plugin-user-settings@0.8.26-next.1 + - @backstage/integration-react@1.2.10-next.0 + - @backstage/app-defaults@1.6.6-next.0 + - @backstage/plugin-auth-react@0.1.19-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.29-next.1 + +## 0.2.113-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.34.1-next.0 + - @backstage/plugin-org@0.6.44-next.0 + - @backstage/plugin-techdocs@1.14.2-next.0 + - @backstage/plugin-catalog@1.31.3-next.0 + - @backstage/plugin-catalog-react@1.20.2-next.0 + - @backstage/app-defaults@1.6.5 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.18.0 + - @backstage/core-components@0.17.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/frontend-app-api@0.12.0 + - @backstage/integration-react@1.2.9 + - @backstage/theme@0.6.8 + - @backstage/ui@0.7.0 + - @backstage/plugin-api-docs@0.12.11-next.0 + - @backstage/plugin-auth-react@0.1.18 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-graph@0.4.23-next.0 + - @backstage/plugin-catalog-import@0.13.5-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.21-next.0 + - @backstage/plugin-devtools@0.1.31-next.0 + - @backstage/plugin-home@0.8.12-next.0 + - @backstage/plugin-kubernetes@0.12.11-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.29-next.0 + - @backstage/plugin-notifications@0.5.9-next.0 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-scaffolder@1.34.1-next.0 + - @backstage/plugin-scaffolder-react@1.19.1-next.0 + - @backstage/plugin-search@1.4.30-next.0 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-search-react@1.9.3 + - @backstage/plugin-signals@0.0.23-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.27 + - @backstage/plugin-techdocs-react@1.3.2 + - @backstage/plugin-user-settings@0.8.26-next.0 + +## 0.2.112 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.31.2 + - @backstage/plugin-org@0.6.42 + - @backstage/core-components@0.17.5 + - @backstage/plugin-scaffolder-react@1.19.0 + - @backstage/plugin-scaffolder@1.34.0 + - @backstage/frontend-app-api@0.12.0 + - @backstage/ui@0.7.0 + - @backstage/cli@0.34.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.20 + - @backstage/plugin-catalog-import@0.13.4 + - @backstage/plugin-catalog-graph@0.4.22 + - @backstage/plugin-notifications@0.5.8 + - @backstage/plugin-user-settings@0.8.25 + - @backstage/plugin-search-react@1.9.3 + - @backstage/plugin-kubernetes@0.12.10 + - @backstage/plugin-api-docs@0.12.10 + - @backstage/plugin-devtools@0.1.30 + - @backstage/plugin-techdocs@1.14.0 + - @backstage/plugin-search@1.4.29 + - @backstage/plugin-home@0.8.11 + - @backstage/plugin-signals@0.0.22 + - @backstage/plugin-catalog-react@1.20.0 + - @backstage/theme@0.6.8 + - @backstage/app-defaults@1.6.5 + - @backstage/plugin-auth-react@0.1.18 + - @backstage/plugin-kubernetes-cluster@0.0.28 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.27 + - @backstage/plugin-techdocs-react@1.3.2 + +## 0.2.112-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.12.0-next.2 + - @backstage/cli@0.34.0-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.20-next.2 + - @backstage/plugin-catalog-import@0.13.4-next.2 + - @backstage/plugin-catalog-graph@0.4.22-next.2 + - @backstage/plugin-notifications@0.5.8-next.2 + - @backstage/plugin-user-settings@0.8.25-next.2 + - @backstage/plugin-search-react@1.9.3-next.1 + - @backstage/plugin-kubernetes@0.12.10-next.2 + - @backstage/plugin-scaffolder@1.34.0-next.2 + - @backstage/plugin-api-docs@0.12.10-next.2 + - @backstage/plugin-devtools@0.1.30-next.2 + - @backstage/plugin-techdocs@1.14.0-next.2 + - @backstage/plugin-catalog@1.31.2-next.2 + - @backstage/plugin-search@1.4.29-next.2 + - @backstage/plugin-home@0.8.11-next.2 + - @backstage/ui@0.7.0-next.2 + - @backstage/plugin-catalog-react@1.20.0-next.2 + - @backstage/plugin-signals@0.0.22-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/app-defaults@1.6.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.18.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration-react@1.2.9 + - @backstage/theme@0.6.8-next.0 + - @backstage/plugin-auth-react@0.1.18-next.0 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-kubernetes-cluster@0.0.28-next.1 + - @backstage/plugin-org@0.6.42-next.2 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-scaffolder-react@1.19.0-next.1 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.27-next.0 + - @backstage/plugin-techdocs-react@1.3.2-next.0 + +## 0.2.112-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.31.2-next.1 + - @backstage/plugin-scaffolder-react@1.19.0-next.1 + - @backstage/plugin-scaffolder@1.34.0-next.1 + - @backstage/ui@0.7.0-next.1 + - @backstage/plugin-home@0.8.11-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.20-next.1 + - @backstage/frontend-app-api@0.11.5-next.1 + - @backstage/plugin-catalog-import@0.13.4-next.1 + - @backstage/plugin-notifications@0.5.8-next.1 + - @backstage/plugin-kubernetes@0.12.10-next.1 + - @backstage/plugin-api-docs@0.12.10-next.1 + - @backstage/plugin-devtools@0.1.30-next.1 + - @backstage/plugin-techdocs@1.14.0-next.1 + - @backstage/plugin-signals@0.0.22-next.1 + - @backstage/plugin-search@1.4.29-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/theme@0.6.8-next.0 + - @backstage/cli@0.33.2-next.0 + - @backstage/plugin-catalog-graph@0.4.22-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.28-next.1 + - @backstage/plugin-org@0.6.42-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.27-next.0 + - @backstage/plugin-user-settings@0.8.25-next.1 + - @backstage/plugin-search-react@1.9.3-next.0 + - @backstage/plugin-techdocs-react@1.3.2-next.0 + - @backstage/app-defaults@1.6.5-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.18.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration-react@1.2.9 + - @backstage/plugin-auth-react@0.1.18-next.0 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-search-common@1.2.19 + +## 0.2.112-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-org@0.6.42-next.0 + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/ui@0.7.0-next.0 + - @backstage/plugin-catalog@1.31.2-next.0 + - @backstage/plugin-api-docs@0.12.10-next.0 + - @backstage/plugin-catalog-graph@0.4.22-next.0 + - @backstage/plugin-catalog-import@0.13.4-next.0 + - @backstage/plugin-home@0.8.11-next.0 + - @backstage/plugin-kubernetes@0.12.10-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.28-next.0 + - @backstage/plugin-scaffolder@1.33.1-next.0 + - @backstage/plugin-scaffolder-react@1.18.1-next.0 + - @backstage/plugin-search@1.4.29-next.0 + - @backstage/plugin-techdocs@1.13.3-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.26 + - @backstage/plugin-user-settings@0.8.25-next.0 + - @backstage/frontend-app-api@0.11.5-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.20-next.0 + - @backstage/plugin-devtools@0.1.30-next.0 + - @backstage/plugin-notifications@0.5.8-next.0 + - @backstage/plugin-signals@0.0.22-next.0 + - @backstage/cli@0.33.1 + - @backstage/integration-react@1.2.9 + - @backstage/plugin-search-react@1.9.2 + +## 0.2.111 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-home@0.8.10 + - @backstage/plugin-techdocs@1.13.2 + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/plugin-catalog-graph@0.4.21 + - @backstage/config@1.3.3 + - @backstage/frontend-app-api@0.11.4 + - @backstage/catalog-model@1.7.5 + - @backstage/plugin-scaffolder@1.33.0 + - @backstage/ui@0.6.0 + - @backstage/cli@0.33.1 + - @backstage/plugin-scaffolder-react@1.18.0 + - @backstage/plugin-catalog@1.31.1 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/plugin-org@0.6.41 + - @backstage/theme@0.6.7 + - @backstage/plugin-catalog-import@0.13.3 + - @backstage/core-app-api@1.18.0 + - @backstage/plugin-user-settings@0.8.24 + - @backstage/integration-react@1.2.9 + - @backstage/plugin-signals@0.0.21 + - @backstage/app-defaults@1.6.4 + - @backstage/plugin-api-docs@0.12.9 + - @backstage/plugin-auth-react@0.1.17 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-unprocessed-entities@0.2.19 + - @backstage/plugin-devtools@0.1.29 + - @backstage/plugin-kubernetes@0.12.9 + - @backstage/plugin-kubernetes-cluster@0.0.27 + - @backstage/plugin-notifications@0.5.7 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-search@1.4.28 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-search-react@1.9.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.26 + - @backstage/plugin-techdocs-react@1.3.1 + +## 0.2.111-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-home@0.8.10-next.2 + - @backstage/canon@0.6.0-next.1 + - @backstage/cli@0.33.1-next.2 + - @backstage/plugin-scaffolder-react@1.18.0-next.2 + - @backstage/theme@0.6.7-next.1 + - @backstage/core-app-api@1.18.0-next.1 + - @backstage/core-components@0.17.4-next.2 + - @backstage/plugin-user-settings@0.8.24-next.2 + - @backstage/plugin-scaffolder@1.33.0-next.2 + - @backstage/plugin-signals@0.0.21-next.2 + - @backstage/app-defaults@1.6.4-next.2 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/frontend-app-api@0.11.4-next.2 + - @backstage/plugin-api-docs@0.12.9-next.1 + - @backstage/plugin-catalog@1.31.1-next.2 + - @backstage/plugin-catalog-graph@0.4.21-next.1 + - @backstage/plugin-catalog-import@0.13.3-next.1 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/plugin-notifications@0.5.7-next.1 + - @backstage/plugin-org@0.6.41-next.1 + - @backstage/plugin-search@1.4.28-next.1 + - @backstage/plugin-search-react@1.9.2-next.1 + - @backstage/plugin-techdocs@1.13.2-next.1 + +## 0.2.111-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/frontend-app-api@0.11.4-next.1 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/cli@0.33.1-next.1 + - @backstage/core-app-api@1.17.2-next.0 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/integration-react@1.2.9-next.1 + - @backstage/plugin-catalog-import@0.13.3-next.1 + - @backstage/plugin-home@0.8.10-next.1 + - @backstage/plugin-permission-react@0.4.36-next.0 + - @backstage/plugin-techdocs@1.13.2-next.1 + - @backstage/plugin-techdocs-react@1.3.1-next.1 + - @backstage/plugin-catalog@1.31.1-next.1 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-org@0.6.41-next.1 + - @backstage/plugin-scaffolder@1.32.1-next.1 + - @backstage/plugin-scaffolder-react@1.17.1-next.1 + - @backstage/plugin-search-common@1.2.19-next.0 + - @backstage/plugin-api-docs@0.12.9-next.1 + - @backstage/plugin-catalog-graph@0.4.21-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.19-next.1 + - @backstage/plugin-kubernetes@0.12.9-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.27-next.1 + - @backstage/plugin-user-settings@0.8.24-next.1 + - @backstage/app-defaults@1.6.4-next.1 + - @backstage/plugin-notifications@0.5.7-next.1 + - @backstage/plugin-search@1.4.28-next.1 + - @backstage/plugin-search-react@1.9.2-next.1 + - @backstage/plugin-signals@0.0.21-next.1 + - @backstage/plugin-auth-react@0.1.17-next.1 + - @backstage/plugin-devtools@0.1.29-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.26-next.1 + +## 0.2.111-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.13.2-next.0 + - @backstage/plugin-catalog-graph@0.4.21-next.0 + - @backstage/canon@0.6.0-next.0 + - @backstage/plugin-scaffolder@1.32.1-next.0 + - @backstage/plugin-catalog-import@0.13.2-next.0 + - @backstage/integration-react@1.2.9-next.0 + - @backstage/theme@0.6.7-next.0 + - @backstage/cli@0.33.1-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.26-next.0 + - @backstage/plugin-catalog@1.31.1-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/app-defaults@1.6.4-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/plugin-home@0.8.10-next.0 + - @backstage/plugin-notifications@0.5.7-next.0 + - @backstage/plugin-scaffolder-react@1.17.1-next.0 + - @backstage/plugin-search-react@1.9.2-next.0 + - @backstage/plugin-signals@0.0.21-next.0 + - @backstage/plugin-techdocs-react@1.3.1-next.0 + - @backstage/plugin-user-settings@0.8.24-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.1 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-app-api@0.11.4-next.0 + - @backstage/plugin-api-docs@0.12.9-next.0 + - @backstage/plugin-auth-react@0.1.17-next.0 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-catalog-unprocessed-entities@0.2.19-next.0 + - @backstage/plugin-devtools@0.1.29-next.0 + - @backstage/plugin-kubernetes@0.12.9-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.27-next.0 + - @backstage/plugin-org@0.6.41-next.0 + - @backstage/plugin-permission-react@0.4.35 + - @backstage/plugin-search@1.4.28-next.0 + - @backstage/plugin-search-common@1.2.18 + +## 0.2.110 + +### Patch Changes + +- Updated dependencies + - @backstage/canon@0.5.0 + - @backstage/core-components@0.17.3 + - @backstage/cli@0.33.0 + - @backstage/plugin-scaffolder-react@1.17.0 + - @backstage/plugin-scaffolder@1.32.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.18 + - @backstage/plugin-catalog-import@0.13.1 + - @backstage/plugin-catalog-graph@0.4.20 + - @backstage/plugin-notifications@0.5.6 + - @backstage/plugin-user-settings@0.8.23 + - @backstage/plugin-kubernetes@0.12.8 + - @backstage/plugin-api-docs@0.12.8 + - @backstage/plugin-devtools@0.1.28 + - @backstage/plugin-techdocs@1.13.0 + - @backstage/plugin-catalog@1.31.0 + - @backstage/plugin-signals@0.0.20 + - @backstage/plugin-search@1.4.27 + - @backstage/plugin-home@0.8.9 + - @backstage/plugin-org@0.6.40 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.25 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-app-api@0.11.3 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/plugin-techdocs-react@1.3.0 + - @backstage/plugin-search-react@1.9.1 + - @backstage/app-defaults@1.6.3 + - @backstage/integration-react@1.2.8 + - @backstage/plugin-auth-react@0.1.16 + - @backstage/plugin-kubernetes-cluster@0.0.26 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.1 + - @backstage/theme@0.6.6 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-react@0.4.35 + - @backstage/plugin-search-common@1.2.18 + +## 0.2.110-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.33.0-next.2 + - @backstage/app-defaults@1.6.3-next.0 + - @backstage/canon@0.5.0-next.2 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.0 + - @backstage/core-components@0.17.3-next.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-app-api@0.11.3-next.1 + - @backstage/integration-react@1.2.7 + - @backstage/theme@0.6.6 + - @backstage/plugin-api-docs@0.12.8-next.2 + - @backstage/plugin-auth-react@0.1.16-next.0 + - @backstage/plugin-catalog@1.31.0-next.2 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-catalog-graph@0.4.20-next.2 + - @backstage/plugin-catalog-import@0.13.1-next.2 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.18-next.2 + - @backstage/plugin-devtools@0.1.28-next.2 + - @backstage/plugin-home@0.8.9-next.2 + - @backstage/plugin-kubernetes@0.12.8-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.26-next.2 + - @backstage/plugin-notifications@0.5.6-next.2 + - @backstage/plugin-org@0.6.40-next.2 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/plugin-scaffolder@1.32.0-next.2 + - @backstage/plugin-scaffolder-react@1.16.1-next.2 + - @backstage/plugin-search@1.4.27-next.2 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-search-react@1.9.1-next.1 + - @backstage/plugin-signals@0.0.20-next.1 + - @backstage/plugin-techdocs@1.13.0-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.25-next.2 + - @backstage/plugin-techdocs-react@1.3.0-next.1 + - @backstage/plugin-user-settings@0.8.23-next.2 + +## 0.2.110-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/cli@0.33.0-next.1 + - @backstage/canon@0.5.0-next.2 + - @backstage/plugin-notifications@0.5.6-next.2 + - @backstage/plugin-scaffolder@1.32.0-next.2 + - @backstage/plugin-catalog@1.31.0-next.2 + - @backstage/plugin-techdocs@1.13.0-next.2 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/plugin-catalog-import@0.13.1-next.2 + - @backstage/plugin-home@0.8.9-next.2 + - @backstage/plugin-search-react@1.9.1-next.1 + - @backstage/app-defaults@1.6.3-next.0 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-api-docs@0.12.8-next.2 + - @backstage/plugin-auth-react@0.1.16-next.0 + - @backstage/plugin-catalog-graph@0.4.20-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.18-next.2 + - @backstage/plugin-devtools@0.1.28-next.2 + - @backstage/plugin-kubernetes@0.12.8-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.26-next.2 + - @backstage/plugin-org@0.6.40-next.2 + - @backstage/plugin-scaffolder-react@1.16.1-next.2 + - @backstage/plugin-search@1.4.27-next.2 + - @backstage/plugin-signals@0.0.20-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.25-next.2 + - @backstage/plugin-techdocs-react@1.3.0-next.1 + - @backstage/plugin-user-settings@0.8.23-next.2 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-app-api@0.11.3-next.1 + - @backstage/theme@0.6.6 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/plugin-search-common@1.2.18 + +## 0.2.110-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/canon@0.5.0-next.1 + - @backstage/cli@0.32.2-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.25-next.1 + - @backstage/plugin-techdocs@1.13.0-next.1 + - @backstage/plugin-org@0.6.40-next.1 + - @backstage/plugin-user-settings@0.8.23-next.1 + - @backstage/plugin-notifications@0.5.6-next.1 + - @backstage/plugin-catalog@1.31.0-next.1 + - @backstage/plugin-catalog-graph@0.4.20-next.1 + - @backstage/plugin-catalog-import@0.13.1-next.1 + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/plugin-home@0.8.9-next.1 + - @backstage/plugin-scaffolder@1.31.1-next.1 + - @backstage/plugin-scaffolder-react@1.16.1-next.1 + - @backstage/app-defaults@1.6.2 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.0 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-app-api@0.11.3-next.0 + - @backstage/integration-react@1.2.7 + - @backstage/theme@0.6.6 + - @backstage/plugin-api-docs@0.12.8-next.1 + - @backstage/plugin-auth-react@0.1.15 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-catalog-unprocessed-entities@0.2.18-next.1 + - @backstage/plugin-devtools@0.1.28-next.1 + - @backstage/plugin-kubernetes@0.12.8-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.26-next.1 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/plugin-search@1.4.27-next.1 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-search-react@1.9.1-next.0 + - @backstage/plugin-signals@0.0.20-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + +## 0.2.110-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.31.1-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.18-next.0 + - @backstage/plugin-catalog-import@0.13.1-next.0 + - @backstage/plugin-catalog-graph@0.4.20-next.0 + - @backstage/plugin-notifications@0.5.6-next.0 + - @backstage/plugin-user-settings@0.8.23-next.0 + - @backstage/plugin-kubernetes@0.12.8-next.0 + - @backstage/plugin-api-docs@0.12.8-next.0 + - @backstage/plugin-devtools@0.1.28-next.0 + - @backstage/plugin-techdocs@1.13.0-next.0 + - @backstage/plugin-catalog@1.31.0-next.0 + - @backstage/plugin-signals@0.0.20-next.0 + - @backstage/plugin-search@1.4.27-next.0 + - @backstage/plugin-home@0.8.9-next.0 + - @backstage/plugin-org@0.6.40-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + - @backstage/frontend-app-api@0.11.3-next.0 + - @backstage/canon@0.5.0-next.0 + - @backstage/plugin-scaffolder-react@1.16.1-next.0 + - @backstage/cli@0.32.1 + - @backstage/plugin-catalog-react@1.18.1-next.0 + - @backstage/plugin-search-react@1.9.1-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.25-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.26-next.0 + - @backstage/integration-react@1.2.7 + +## 0.2.109 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.6 + - @backstage/core-app-api@1.17.0 + - @backstage/canon@0.4.0 + - @backstage/cli@0.32.1 + - @backstage/plugin-user-settings@0.8.22 + - @backstage/core-components@0.17.2 + - @backstage/plugin-home@0.8.8 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-scaffolder@1.31.0 + - @backstage/frontend-app-api@0.11.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.17 + - @backstage/plugin-catalog-import@0.13.0 + - @backstage/plugin-catalog-graph@0.4.19 + - @backstage/plugin-notifications@0.5.5 + - @backstage/plugin-kubernetes@0.12.7 + - @backstage/plugin-api-docs@0.12.7 + - @backstage/plugin-devtools@0.1.27 + - @backstage/plugin-techdocs@1.12.6 + - @backstage/plugin-catalog@1.30.0 + - @backstage/plugin-signals@0.0.19 + - @backstage/plugin-search@1.4.26 + - @backstage/plugin-org@0.6.39 + - @backstage/plugin-search-react@1.9.0 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/plugin-scaffolder-react@1.16.0 + - @backstage/plugin-kubernetes-cluster@0.0.25 + - @backstage/plugin-techdocs-react@1.2.17 + - @backstage/plugin-auth-react@0.1.15 + - @backstage/core-plugin-api@1.10.7 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.24 + - @backstage/app-defaults@1.6.2 + - @backstage/config@1.3.2 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-search-common@1.2.18 + +## 0.2.109-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.17.0-next.1 + - @backstage/canon@0.4.0-next.3 + - @backstage/cli@0.32.1-next.3 + - @backstage/plugin-home@0.8.8-next.3 + - @backstage/plugin-catalog@1.30.0-next.3 + - @backstage/frontend-app-api@0.11.2-next.3 + - @backstage/plugin-search-react@1.9.0-next.2 + - @backstage/plugin-scaffolder@1.31.0-next.3 + - @backstage/plugin-search@1.4.26-next.3 + - @backstage/app-defaults@1.6.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-api-docs@0.12.7-next.3 + - @backstage/plugin-catalog-graph@0.4.19-next.3 + - @backstage/plugin-catalog-import@0.13.0-next.3 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/plugin-notifications@0.5.5-next.3 + - @backstage/plugin-org@0.6.39-next.3 + - @backstage/plugin-scaffolder-react@1.16.0-next.3 + - @backstage/plugin-signals@0.0.19-next.1 + - @backstage/plugin-techdocs@1.12.6-next.3 + - @backstage/plugin-user-settings@0.8.22-next.3 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/integration-react@1.2.7-next.3 + - @backstage/theme@0.6.6-next.0 + - @backstage/plugin-auth-react@0.1.15-next.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.17-next.3 + - @backstage/plugin-devtools@0.1.27-next.3 + - @backstage/plugin-kubernetes@0.12.7-next.3 + - @backstage/plugin-kubernetes-cluster@0.0.25-next.3 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/plugin-search-common@1.2.18-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.24-next.3 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + +## 0.2.109-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/canon@0.4.0-next.2 + - @backstage/plugin-catalog-import@0.13.0-next.2 + - @backstage/plugin-techdocs@1.12.6-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog@1.29.1-next.2 + - @backstage/app-defaults@1.6.2-next.1 + - @backstage/cli@0.32.1-next.2 + - @backstage/config@1.3.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-app-api@0.11.2-next.2 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-api-docs@0.12.7-next.2 + - @backstage/plugin-auth-react@0.1.15-next.1 + - @backstage/plugin-catalog-graph@0.4.19-next.2 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-home@0.8.8-next.2 + - @backstage/plugin-kubernetes@0.12.7-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.25-next.2 + - @backstage/plugin-notifications@0.5.5-next.2 + - @backstage/plugin-org@0.6.39-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/plugin-scaffolder@1.31.0-next.2 + - @backstage/plugin-scaffolder-react@1.16.0-next.2 + - @backstage/plugin-search@1.4.26-next.2 + - @backstage/plugin-search-react@1.9.0-next.1 + - @backstage/plugin-signals@0.0.19-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.24-next.2 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + - @backstage/plugin-user-settings@0.8.22-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.17-next.2 + - @backstage/plugin-devtools@0.1.27-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/theme@0.6.6-next.0 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + +## 0.2.109-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.6-next.0 + - @backstage/core-components@0.17.2-next.0 + - @backstage/cli@0.32.1-next.1 + - @backstage/canon@0.4.0-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.17-next.1 + - @backstage/plugin-catalog-import@0.12.14-next.1 + - @backstage/plugin-catalog-graph@0.4.19-next.1 + - @backstage/plugin-notifications@0.5.5-next.1 + - @backstage/plugin-user-settings@0.8.22-next.1 + - @backstage/plugin-kubernetes@0.12.7-next.1 + - @backstage/plugin-scaffolder@1.31.0-next.1 + - @backstage/plugin-api-docs@0.12.7-next.1 + - @backstage/plugin-devtools@0.1.27-next.1 + - @backstage/plugin-techdocs@1.12.6-next.1 + - @backstage/plugin-catalog@1.29.1-next.1 + - @backstage/plugin-signals@0.0.19-next.0 + - @backstage/plugin-search@1.4.26-next.1 + - @backstage/plugin-home@0.8.8-next.1 + - @backstage/plugin-org@0.6.39-next.1 + - @backstage/plugin-search-react@1.9.0-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.25-next.1 + - @backstage/frontend-app-api@0.11.2-next.1 + - @backstage/plugin-scaffolder-react@1.16.0-next.1 + - @backstage/plugin-techdocs-react@1.2.17-next.0 + - @backstage/plugin-auth-react@0.1.15-next.0 + - @backstage/app-defaults@1.6.2-next.0 + - @backstage/integration-react@1.2.7-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.24-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.16.1 + - @backstage/core-plugin-api@1.10.6 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-permission-react@0.4.34-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + +## 0.2.109-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/canon@0.3.2-next.0 + - @backstage/plugin-user-settings@0.8.22-next.0 + - @backstage/plugin-scaffolder@1.31.0-next.0 + - @backstage/cli@0.32.1-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.0 + - @backstage/plugin-scaffolder-react@1.16.0-next.0 + - @backstage/integration-react@1.2.7-next.0 + - @backstage/plugin-catalog-import@0.12.14-next.0 + - @backstage/plugin-techdocs@1.12.6-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.24-next.0 + - @backstage/app-defaults@1.6.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.16.1 + - @backstage/core-components@0.17.1 + - @backstage/core-plugin-api@1.10.6 + - @backstage/frontend-app-api@0.11.2-next.0 + - @backstage/theme@0.6.5 + - @backstage/plugin-api-docs@0.12.7-next.0 + - @backstage/plugin-auth-react@0.1.14 + - @backstage/plugin-catalog@1.29.1-next.0 + - @backstage/plugin-catalog-common@1.1.3 + - @backstage/plugin-catalog-graph@0.4.19-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.17-next.0 + - @backstage/plugin-devtools@0.1.27-next.0 + - @backstage/plugin-home@0.8.8-next.0 + - @backstage/plugin-kubernetes@0.12.7-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.25-next.0 + - @backstage/plugin-notifications@0.5.5-next.0 + - @backstage/plugin-org@0.6.39-next.0 + - @backstage/plugin-permission-react@0.4.33 + - @backstage/plugin-search@1.4.26-next.0 + - @backstage/plugin-search-common@1.2.17 + - @backstage/plugin-search-react@1.8.8 + - @backstage/plugin-signals@0.0.18 + - @backstage/plugin-techdocs-react@1.2.16 + +## 0.2.108 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.29.0 + - @backstage/plugin-api-docs@0.12.6 + - @backstage/plugin-devtools@0.1.26 + - @backstage/plugin-search@1.4.25 + - @backstage/plugin-techdocs-react@1.2.16 + - @backstage/cli@0.32.0 + - @backstage/canon@0.3.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.16 + - @backstage/plugin-kubernetes@0.12.6 + - @backstage/plugin-catalog-react@1.17.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.23 + - @backstage/integration-react@1.2.6 + - @backstage/plugin-kubernetes-cluster@0.0.24 + - @backstage/frontend-app-api@0.11.1 + - @backstage/core-components@0.17.1 + - @backstage/core-plugin-api@1.10.6 + - @backstage/plugin-permission-react@0.4.33 + - @backstage/plugin-scaffolder-react@1.15.0 + - @backstage/plugin-catalog-import@0.12.13 + - @backstage/app-defaults@1.6.1 + - @backstage/core-app-api@1.16.1 + - @backstage/plugin-catalog-graph@0.4.18 + - @backstage/plugin-notifications@0.5.4 + - @backstage/plugin-user-settings@0.8.21 + - @backstage/plugin-search-react@1.8.8 + - @backstage/plugin-auth-react@0.1.14 + - @backstage/plugin-scaffolder@1.30.0 + - @backstage/plugin-techdocs@1.12.5 + - @backstage/theme@0.6.5 + - @backstage/plugin-home@0.8.7 + - @backstage/plugin-org@0.6.38 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.3 + - @backstage/plugin-search-common@1.2.17 + - @backstage/plugin-signals@0.0.18 + +## 0.2.108-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-devtools@0.1.26-next.2 + - @backstage/plugin-catalog@1.29.0-next.2 + - @backstage/plugin-search@1.4.25-next.2 + - @backstage/cli@0.32.0-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.23-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.16-next.2 + - @backstage/integration-react@1.2.6-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.24-next.2 + - @backstage/frontend-app-api@0.11.1-next.1 + - @backstage/core-components@0.17.1-next.1 + - @backstage/core-plugin-api@1.10.6-next.0 + - @backstage/plugin-permission-react@0.4.33-next.0 + - @backstage/plugin-scaffolder-react@1.15.0-next.2 + - @backstage/plugin-catalog-import@0.12.13-next.2 + - @backstage/plugin-techdocs-react@1.2.16-next.1 + - @backstage/app-defaults@1.6.1-next.1 + - @backstage/core-app-api@1.16.1-next.0 + - @backstage/plugin-catalog-graph@0.4.18-next.2 + - @backstage/plugin-catalog-react@1.17.0-next.2 + - @backstage/plugin-notifications@0.5.4-next.2 + - @backstage/plugin-user-settings@0.8.21-next.2 + - @backstage/plugin-search-react@1.8.8-next.1 + - @backstage/plugin-auth-react@0.1.14-next.1 + - @backstage/plugin-kubernetes@0.12.6-next.2 + - @backstage/plugin-scaffolder@1.30.0-next.2 + - @backstage/plugin-api-docs@0.12.6-next.2 + - @backstage/plugin-techdocs@1.12.5-next.2 + - @backstage/canon@0.3.0-next.2 + - @backstage/theme@0.6.5-next.0 + - @backstage/plugin-home@0.8.7-next.2 + - @backstage/plugin-org@0.6.38-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.3 + - @backstage/plugin-search-common@1.2.17 + - @backstage/plugin-signals@0.0.18-next.1 + +## 0.2.108-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-react@1.2.16-next.0 + - @backstage/plugin-kubernetes@0.12.6-next.1 + - @backstage/canon@0.2.1-next.1 + - @backstage/core-components@0.17.1-next.0 + - @backstage/plugin-catalog-import@0.12.12-next.1 + - @backstage/integration-react@1.2.6-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.23-next.0 + - @backstage/plugin-techdocs@1.12.5-next.1 + - @backstage/app-defaults@1.6.1-next.0 + - @backstage/cli@0.32.0-next.1 + - @backstage/plugin-api-docs@0.12.6-next.1 + - @backstage/plugin-auth-react@0.1.14-next.0 + - @backstage/plugin-catalog@1.29.0-next.1 + - @backstage/plugin-catalog-graph@0.4.18-next.1 + - @backstage/plugin-catalog-react@1.16.1-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.16-next.1 + - @backstage/plugin-devtools@0.1.26-next.1 + - @backstage/plugin-home@0.8.7-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.24-next.1 + - @backstage/plugin-notifications@0.5.4-next.1 + - @backstage/plugin-org@0.6.38-next.1 + - @backstage/plugin-scaffolder@1.30.0-next.1 + - @backstage/plugin-scaffolder-react@1.15.0-next.1 + - @backstage/plugin-search@1.4.25-next.1 + - @backstage/plugin-search-react@1.8.8-next.0 + - @backstage/plugin-signals@0.0.18-next.0 + - @backstage/plugin-user-settings@0.8.21-next.1 + - @backstage/frontend-app-api@0.11.1-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.16.0 + - @backstage/core-plugin-api@1.10.5 + - @backstage/theme@0.6.4 + - @backstage/plugin-catalog-common@1.1.3 + - @backstage/plugin-permission-react@0.4.32 + - @backstage/plugin-search-common@1.2.17 + +## 0.2.108-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.29.0-next.0 + - @backstage/cli@0.32.0-next.0 + - @backstage/canon@0.2.1-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.16-next.0 + - @backstage/plugin-scaffolder-react@1.15.0-next.0 + - @backstage/plugin-catalog-react@1.16.1-next.0 + - @backstage/plugin-scaffolder@1.30.0-next.0 + - @backstage/plugin-api-docs@0.12.6-next.0 + - @backstage/plugin-catalog-graph@0.4.18-next.0 + - @backstage/plugin-catalog-import@0.12.12-next.0 + - @backstage/plugin-org@0.6.38-next.0 + - @backstage/plugin-techdocs@1.12.5-next.0 + - @backstage/plugin-user-settings@0.8.21-next.0 + - @backstage/app-defaults@1.6.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.16.0 + - @backstage/core-components@0.17.0 + - @backstage/core-plugin-api@1.10.5 + - @backstage/frontend-app-api@0.11.0 + - @backstage/integration-react@1.2.5 + - @backstage/theme@0.6.4 + - @backstage/plugin-auth-react@0.1.13 + - @backstage/plugin-catalog-common@1.1.3 + - @backstage/plugin-devtools@0.1.26-next.0 + - @backstage/plugin-home@0.8.7-next.0 + - @backstage/plugin-kubernetes@0.12.6-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.24-next.0 + - @backstage/plugin-notifications@0.5.4-next.0 + - @backstage/plugin-permission-react@0.4.32 + - @backstage/plugin-search@1.4.25-next.0 + - @backstage/plugin-search-common@1.2.17 + - @backstage/plugin-search-react@1.8.7 + - @backstage/plugin-signals@0.0.17 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.22 + - @backstage/plugin-techdocs-react@1.2.15 + +## 0.2.107 + +### Patch Changes + +- Updated dependencies + - @backstage/canon@0.2.0 + - @backstage/core-components@0.17.0 + - @backstage/plugin-catalog@1.28.0 + - @backstage/plugin-techdocs@1.12.4 + - @backstage/core-plugin-api@1.10.5 + - @backstage/cli@0.31.0 + - @backstage/frontend-app-api@0.11.0 + - @backstage/plugin-search-react@1.8.7 + - @backstage/plugin-home@0.8.6 + - @backstage/plugin-kubernetes@0.12.5 + - @backstage/core-app-api@1.16.0 + - @backstage/plugin-catalog-react@1.16.0 + - @backstage/plugin-scaffolder@1.29.0 + - @backstage/plugin-scaffolder-react@1.14.6 + - @backstage/plugin-signals@0.0.17 + - @backstage/plugin-search@1.4.24 + - @backstage/app-defaults@1.6.0 + - @backstage/plugin-api-docs@0.12.5 + - @backstage/plugin-techdocs-react@1.2.15 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.22 + - @backstage/integration-react@1.2.5 + - @backstage/plugin-auth-react@0.1.13 + - @backstage/plugin-catalog-graph@0.4.17 + - @backstage/plugin-catalog-import@0.12.11 + - @backstage/plugin-catalog-unprocessed-entities@0.2.15 + - @backstage/plugin-devtools@0.1.25 + - @backstage/plugin-kubernetes-cluster@0.0.23 + - @backstage/plugin-notifications@0.5.3 + - @backstage/plugin-org@0.6.37 + - @backstage/plugin-user-settings@0.8.20 + - @backstage/plugin-permission-react@0.4.32 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/theme@0.6.4 + - @backstage/plugin-catalog-common@1.1.3 + - @backstage/plugin-search-common@1.2.17 + +## 0.2.107-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.28.0-next.2 + - @backstage/frontend-app-api@0.11.0-next.2 + - @backstage/cli@0.31.0-next.1 + - @backstage/core-app-api@1.16.0-next.0 + - @backstage/plugin-catalog-react@1.16.0-next.2 + - @backstage/plugin-signals@0.0.17-next.2 + - @backstage/core-components@0.16.5-next.1 + - @backstage/plugin-scaffolder-react@1.14.6-next.2 + - @backstage/app-defaults@1.6.0-next.1 + - @backstage/plugin-scaffolder@1.29.0-next.2 + - @backstage/plugin-api-docs@0.12.5-next.2 + - @backstage/plugin-catalog-graph@0.4.17-next.2 + - @backstage/plugin-catalog-import@0.12.11-next.2 + - @backstage/plugin-org@0.6.37-next.2 + - @backstage/plugin-techdocs@1.12.4-next.2 + - @backstage/plugin-user-settings@0.8.20-next.2 + - @backstage/plugin-search-react@1.8.7-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.15-next.2 + - @backstage/plugin-devtools@0.1.25-next.2 + - @backstage/plugin-home@0.8.6-next.2 + - @backstage/plugin-kubernetes@0.12.5-next.2 + - @backstage/plugin-notifications@0.5.3-next.2 + - @backstage/plugin-search@1.4.24-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.22-next.2 + - @backstage/plugin-techdocs-react@1.2.15-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/core-plugin-api@1.10.4 + - @backstage/integration-react@1.2.5-next.0 + - @backstage/theme@0.6.4 + - @backstage/plugin-auth-react@0.1.13-next.1 + - @backstage/plugin-catalog-common@1.1.3 + - @backstage/plugin-kubernetes-cluster@0.0.23-next.2 + - @backstage/plugin-permission-react@0.4.31 + - @backstage/plugin-search-common@1.2.17 + +## 0.2.107-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.12.4-next.1 + - @backstage/cli@0.30.1-next.0 + - @backstage/core-components@0.16.5-next.0 + - @backstage/plugin-scaffolder-react@1.14.6-next.1 + - @backstage/plugin-catalog-react@1.16.0-next.1 + - @backstage/plugin-catalog@1.28.0-next.1 + - @backstage/app-defaults@1.5.18-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.15.5 + - @backstage/core-plugin-api@1.10.4 + - @backstage/frontend-app-api@0.10.6-next.1 + - @backstage/integration-react@1.2.4 + - @backstage/theme@0.6.4 + - @backstage/plugin-api-docs@0.12.5-next.1 + - @backstage/plugin-auth-react@0.1.13-next.0 + - @backstage/plugin-catalog-common@1.1.3 + - @backstage/plugin-catalog-graph@0.4.17-next.1 + - @backstage/plugin-catalog-import@0.12.11-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.15-next.1 + - @backstage/plugin-devtools@0.1.25-next.1 + - @backstage/plugin-home@0.8.6-next.1 + - @backstage/plugin-kubernetes@0.12.5-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.23-next.1 + - @backstage/plugin-notifications@0.5.3-next.1 + - @backstage/plugin-org@0.6.37-next.1 + - @backstage/plugin-permission-react@0.4.31 + - @backstage/plugin-scaffolder@1.29.0-next.1 + - @backstage/plugin-search@1.4.24-next.1 + - @backstage/plugin-search-common@1.2.17 + - @backstage/plugin-search-react@1.8.7-next.1 + - @backstage/plugin-signals@0.0.17-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.22-next.1 + - @backstage/plugin-techdocs-react@1.2.15-next.1 + - @backstage/plugin-user-settings@0.8.20-next.1 + +## 0.2.107-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-react@1.8.7-next.0 + - @backstage/plugin-kubernetes@0.12.5-next.0 + - @backstage/plugin-scaffolder@1.29.0-next.0 + - @backstage/plugin-catalog-react@1.16.0-next.0 + - @backstage/plugin-scaffolder-react@1.14.6-next.0 + - @backstage/plugin-catalog@1.28.0-next.0 + - @backstage/plugin-techdocs@1.12.4-next.0 + - @backstage/plugin-techdocs-react@1.2.15-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.22-next.0 + - @backstage/plugin-search@1.4.24-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.23-next.0 + - @backstage/plugin-api-docs@0.12.5-next.0 + - @backstage/plugin-catalog-graph@0.4.17-next.0 + - @backstage/plugin-catalog-import@0.12.11-next.0 + - @backstage/plugin-home@0.8.6-next.0 + - @backstage/plugin-org@0.6.37-next.0 + - @backstage/plugin-user-settings@0.8.20-next.0 + - @backstage/frontend-app-api@0.10.6-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.15-next.0 + - @backstage/plugin-devtools@0.1.25-next.0 + - @backstage/plugin-notifications@0.5.3-next.0 + - @backstage/plugin-signals@0.0.17-next.0 + - @backstage/cli@0.30.0 + - @backstage/integration-react@1.2.4 + +## 0.2.106 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.30.0 + - @backstage/plugin-scaffolder-react@1.14.5 + - @backstage/plugin-notifications@0.5.2 + - @backstage/plugin-search-react@1.8.6 + - @backstage/plugin-search@1.4.23 + - @backstage/plugin-kubernetes-cluster@0.0.22 + - @backstage/core-components@0.16.4 + - @backstage/plugin-scaffolder@1.28.0 + - @backstage/plugin-techdocs@1.12.3 + - @backstage/plugin-home@0.8.5 + - @backstage/plugin-catalog@1.27.0 + - @backstage/plugin-org@0.6.36 + - @backstage/plugin-kubernetes@0.12.4 + - @backstage/plugin-signals@0.0.16 + - @backstage/plugin-catalog-graph@0.4.16 + - @backstage/plugin-catalog-react@1.15.2 + - @backstage/frontend-app-api@0.10.5 + - @backstage/plugin-catalog-unprocessed-entities@0.2.14 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.21 + - @backstage/integration-react@1.2.4 + - @backstage/core-plugin-api@1.10.4 + - @backstage/plugin-permission-react@0.4.31 + - @backstage/plugin-catalog-import@0.12.10 + - @backstage/plugin-techdocs-react@1.2.14 + - @backstage/app-defaults@1.5.17 + - @backstage/core-app-api@1.15.5 + - @backstage/plugin-user-settings@0.8.19 + - @backstage/plugin-auth-react@0.1.12 + - @backstage/plugin-api-docs@0.12.4 + - @backstage/plugin-devtools@0.1.24 + - @backstage/theme@0.6.4 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.3 + - @backstage/plugin-search-common@1.2.17 + +## 0.2.106-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.27.0-next.3 + - @backstage/cli@0.30.0-next.3 + - @backstage/plugin-scaffolder@1.28.0-next.3 + - @backstage/frontend-app-api@0.10.5-next.3 + - @backstage/plugin-api-docs@0.12.4-next.3 + - @backstage/plugin-catalog-graph@0.4.16-next.3 + - @backstage/plugin-catalog-import@0.12.10-next.3 + - @backstage/plugin-org@0.6.36-next.3 + - @backstage/plugin-scaffolder-react@1.14.5-next.3 + - @backstage/plugin-techdocs@1.12.3-next.3 + - @backstage/plugin-user-settings@0.8.19-next.3 + - @backstage/app-defaults@1.5.17-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.15.5-next.0 + - @backstage/core-components@0.16.4-next.1 + - @backstage/core-plugin-api@1.10.4-next.0 + - @backstage/integration-react@1.2.4-next.0 + - @backstage/theme@0.6.4-next.0 + - @backstage/plugin-auth-react@0.1.12-next.1 + - @backstage/plugin-catalog-common@1.1.3 + - @backstage/plugin-catalog-react@1.15.2-next.3 + - @backstage/plugin-catalog-unprocessed-entities@0.2.14-next.1 + - @backstage/plugin-devtools@0.1.24-next.3 + - @backstage/plugin-home@0.8.5-next.3 + - @backstage/plugin-kubernetes@0.12.4-next.3 + - @backstage/plugin-kubernetes-cluster@0.0.22-next.3 + - @backstage/plugin-notifications@0.5.2-next.2 + - @backstage/plugin-permission-react@0.4.31-next.0 + - @backstage/plugin-search@1.4.23-next.3 + - @backstage/plugin-search-common@1.2.17 + - @backstage/plugin-search-react@1.8.6-next.3 + - @backstage/plugin-signals@0.0.16-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.21-next.1 + - @backstage/plugin-techdocs-react@1.2.14-next.1 + +## 0.2.106-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.14.5-next.2 + - @backstage/cli@0.30.0-next.2 + - @backstage/plugin-scaffolder@1.28.0-next.2 + - @backstage/plugin-techdocs@1.12.3-next.2 + - @backstage/plugin-home@0.8.5-next.2 + - @backstage/core-components@0.16.4-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.22-next.2 + - @backstage/plugin-kubernetes@0.12.4-next.2 + - @backstage/plugin-catalog-graph@0.4.16-next.2 + - @backstage/plugin-catalog-react@1.15.2-next.2 + - @backstage/plugin-catalog@1.27.0-next.2 + - @backstage/app-defaults@1.5.17-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.15.5-next.0 + - @backstage/core-plugin-api@1.10.4-next.0 + - @backstage/frontend-app-api@0.10.5-next.2 + - @backstage/integration-react@1.2.4-next.0 + - @backstage/theme@0.6.4-next.0 + - @backstage/plugin-api-docs@0.12.4-next.2 + - @backstage/plugin-auth-react@0.1.12-next.1 + - @backstage/plugin-catalog-common@1.1.3 + - @backstage/plugin-catalog-import@0.12.10-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.14-next.1 + - @backstage/plugin-devtools@0.1.24-next.2 + - @backstage/plugin-notifications@0.5.2-next.2 + - @backstage/plugin-org@0.6.36-next.2 + - @backstage/plugin-permission-react@0.4.31-next.0 + - @backstage/plugin-search@1.4.23-next.2 + - @backstage/plugin-search-common@1.2.17 + - @backstage/plugin-search-react@1.8.6-next.2 + - @backstage/plugin-signals@0.0.16-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.21-next.1 + - @backstage/plugin-techdocs-react@1.2.14-next.1 + - @backstage/plugin-user-settings@0.8.19-next.2 + +## 0.2.106-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-cluster@0.0.22-next.1 + - @backstage/core-components@0.16.4-next.0 + - @backstage/plugin-techdocs@1.12.2-next.1 + - @backstage/plugin-scaffolder@1.28.0-next.1 + - @backstage/cli@0.30.0-next.1 + - @backstage/plugin-home@0.8.5-next.1 + - @backstage/plugin-org@0.6.36-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.21-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.14-next.0 + - @backstage/integration-react@1.2.4-next.0 + - @backstage/frontend-app-api@0.10.5-next.1 + - @backstage/core-plugin-api@1.10.4-next.0 + - @backstage/plugin-permission-react@0.4.31-next.0 + - @backstage/plugin-scaffolder-react@1.14.4-next.1 + - @backstage/plugin-catalog-import@0.12.10-next.1 + - @backstage/plugin-techdocs-react@1.2.14-next.0 + - @backstage/app-defaults@1.5.17-next.0 + - @backstage/core-app-api@1.15.5-next.0 + - @backstage/plugin-catalog-graph@0.4.16-next.1 + - @backstage/plugin-catalog-react@1.15.2-next.1 + - @backstage/plugin-notifications@0.5.2-next.1 + - @backstage/plugin-user-settings@0.8.19-next.1 + - @backstage/plugin-search-react@1.8.6-next.1 + - @backstage/plugin-auth-react@0.1.12-next.0 + - @backstage/plugin-kubernetes@0.12.4-next.1 + - @backstage/plugin-api-docs@0.12.4-next.1 + - @backstage/plugin-devtools@0.1.24-next.1 + - @backstage/plugin-catalog@1.26.2-next.1 + - @backstage/plugin-signals@0.0.16-next.0 + - @backstage/theme@0.6.4-next.0 + - @backstage/plugin-search@1.4.23-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.3 + - @backstage/plugin-search-common@1.2.17 + +## 0.2.106-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.30.0-next.0 + - @backstage/plugin-notifications@0.5.2-next.0 + - @backstage/plugin-search-react@1.8.6-next.0 + - @backstage/plugin-search@1.4.23-next.0 + - @backstage/plugin-techdocs@1.12.2-next.0 + - @backstage/plugin-scaffolder@1.27.5-next.0 + - @backstage/plugin-scaffolder-react@1.14.4-next.0 + - @backstage/frontend-app-api@0.10.5-next.0 + - @backstage/app-defaults@1.5.16 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.15.4 + - @backstage/core-components@0.16.3 + - @backstage/core-plugin-api@1.10.3 + - @backstage/integration-react@1.2.3 + - @backstage/theme@0.6.3 + - @backstage/plugin-api-docs@0.12.4-next.0 + - @backstage/plugin-auth-react@0.1.11 + - @backstage/plugin-catalog@1.26.2-next.0 + - @backstage/plugin-catalog-common@1.1.3 + - @backstage/plugin-catalog-graph@0.4.16-next.0 + - @backstage/plugin-catalog-import@0.12.10-next.0 + - @backstage/plugin-catalog-react@1.15.2-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.13 + - @backstage/plugin-devtools@0.1.24-next.0 + - @backstage/plugin-home@0.8.5-next.0 + - @backstage/plugin-kubernetes@0.12.4-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.22-next.0 + - @backstage/plugin-org@0.6.36-next.0 + - @backstage/plugin-permission-react@0.4.30 + - @backstage/plugin-search-common@1.2.17 + - @backstage/plugin-signals@0.0.15 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.20 + - @backstage/plugin-techdocs-react@1.2.13 + - @backstage/plugin-user-settings@0.8.19-next.0 + +## 0.2.105 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-home@0.8.4 + - @backstage/cli@0.29.5 + - @backstage/plugin-catalog-react@1.15.1 + - @backstage/plugin-techdocs@1.12.1 + - @backstage/plugin-scaffolder-react@1.14.3 + - @backstage/core-plugin-api@1.10.3 + - @backstage/core-components@0.16.3 + - @backstage/plugin-catalog@1.26.1 + - @backstage/plugin-scaffolder@1.27.4 + - @backstage/plugin-api-docs@0.12.3 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.20 + - @backstage/app-defaults@1.5.16 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.15.4 + - @backstage/frontend-app-api@0.10.4 + - @backstage/integration-react@1.2.3 + - @backstage/theme@0.6.3 + - @backstage/plugin-auth-react@0.1.11 + - @backstage/plugin-catalog-common@1.1.3 + - @backstage/plugin-catalog-graph@0.4.15 + - @backstage/plugin-catalog-import@0.12.9 + - @backstage/plugin-catalog-unprocessed-entities@0.2.13 + - @backstage/plugin-devtools@0.1.23 + - @backstage/plugin-kubernetes@0.12.3 + - @backstage/plugin-kubernetes-cluster@0.0.21 + - @backstage/plugin-notifications@0.5.1 + - @backstage/plugin-org@0.6.35 + - @backstage/plugin-permission-react@0.4.30 + - @backstage/plugin-search@1.4.22 + - @backstage/plugin-search-common@1.2.17 + - @backstage/plugin-search-react@1.8.5 + - @backstage/plugin-signals@0.0.15 + - @backstage/plugin-techdocs-react@1.2.13 + - @backstage/plugin-user-settings@0.8.18 + +## 0.2.105-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.12.1-next.1 + - @backstage/plugin-scaffolder-react@1.14.3-next.2 + - @backstage/core-plugin-api@1.10.3-next.0 + - @backstage/plugin-catalog@1.26.1-next.1 + - @backstage/plugin-api-docs@0.12.3-next.1 + - @backstage/plugin-scaffolder@1.27.4-next.2 + - @backstage/frontend-app-api@0.10.4-next.0 + - @backstage/plugin-catalog-graph@0.4.15-next.1 + - @backstage/plugin-catalog-import@0.12.9-next.1 + - @backstage/plugin-catalog-react@1.15.1-next.1 + - @backstage/plugin-devtools@0.1.23-next.0 + - @backstage/plugin-home@0.8.4-next.2 + - @backstage/plugin-kubernetes@0.12.3-next.1 + - @backstage/plugin-org@0.6.35-next.1 + - @backstage/plugin-search@1.4.22-next.1 + - @backstage/plugin-search-react@1.8.5-next.0 + - @backstage/plugin-user-settings@0.8.18-next.1 + - @backstage/app-defaults@1.5.16-next.0 + - @backstage/cli@0.29.5-next.1 + - @backstage/core-app-api@1.15.4-next.0 + - @backstage/core-components@0.16.3-next.0 + - @backstage/integration-react@1.2.3-next.0 + - @backstage/plugin-auth-react@0.1.11-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.13-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.21-next.1 + - @backstage/plugin-notifications@0.5.1-next.0 + - @backstage/plugin-permission-react@0.4.30-next.0 + - @backstage/plugin-signals@0.0.15-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.20-next.1 + - @backstage/plugin-techdocs-react@1.2.13-next.0 + - @backstage/catalog-model@1.7.3-next.0 + - @backstage/config@1.3.2-next.0 + - @backstage/plugin-search-common@1.2.17-next.0 + - @backstage/plugin-catalog-common@1.1.3-next.0 + - @backstage/theme@0.6.3 + +## 0.2.105-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.15.1-next.0 + - @backstage/plugin-home@0.8.4-next.1 + - @backstage/plugin-scaffolder-react@1.14.3-next.1 + - @backstage/plugin-scaffolder@1.27.4-next.1 + - @backstage/plugin-api-docs@0.12.3-next.0 + - @backstage/plugin-catalog@1.26.1-next.0 + - @backstage/plugin-catalog-graph@0.4.15-next.0 + - @backstage/plugin-catalog-import@0.12.9-next.0 + - @backstage/plugin-kubernetes@0.12.3-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.21-next.0 + - @backstage/plugin-org@0.6.35-next.0 + - @backstage/plugin-search@1.4.22-next.0 + - @backstage/plugin-techdocs@1.12.1-next.0 + - @backstage/plugin-user-settings@0.8.18-next.0 + - @backstage/cli@0.29.5-next.0 + - @backstage/integration-react@1.2.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.12 + - @backstage/plugin-devtools@0.1.22 + - @backstage/plugin-notifications@0.5.0 + - @backstage/plugin-signals@0.0.14 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.20-next.0 + +## 0.2.105-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-home@0.8.4-next.0 + - @backstage/cli@0.29.5-next.0 + - @backstage/plugin-scaffolder-react@1.14.3-next.0 + - @backstage/plugin-scaffolder@1.27.4-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.20-next.0 + - @backstage/app-defaults@1.5.15 + - @backstage/catalog-model@1.7.2 + - @backstage/config@1.3.1 + - @backstage/core-app-api@1.15.3 + - @backstage/core-components@0.16.2 + - @backstage/core-plugin-api@1.10.2 + - @backstage/frontend-app-api@0.10.3 + - @backstage/integration-react@1.2.2 + - @backstage/theme@0.6.3 + - @backstage/plugin-api-docs@0.12.2 + - @backstage/plugin-auth-react@0.1.10 + - @backstage/plugin-catalog@1.26.0 + - @backstage/plugin-catalog-common@1.1.2 + - @backstage/plugin-catalog-graph@0.4.14 + - @backstage/plugin-catalog-import@0.12.8 + - @backstage/plugin-catalog-react@1.15.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.12 + - @backstage/plugin-devtools@0.1.22 + - @backstage/plugin-kubernetes@0.12.2 + - @backstage/plugin-kubernetes-cluster@0.0.20 + - @backstage/plugin-notifications@0.5.0 + - @backstage/plugin-org@0.6.34 + - @backstage/plugin-permission-react@0.4.29 + - @backstage/plugin-search@1.4.21 + - @backstage/plugin-search-common@1.2.16 + - @backstage/plugin-search-react@1.8.4 + - @backstage/plugin-signals@0.0.14 + - @backstage/plugin-techdocs@1.12.0 + - @backstage/plugin-techdocs-react@1.2.12 + - @backstage/plugin-user-settings@0.8.17 + +## 0.2.104 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.15.0 + - @backstage/cli@0.29.4 + - @backstage/plugin-home@0.8.3 + - @backstage/plugin-techdocs@1.12.0 + - @backstage/core-app-api@1.15.3 + - @backstage/plugin-catalog@1.26.0 + - @backstage/plugin-scaffolder-react@1.14.2 + - @backstage/plugin-scaffolder@1.27.2 + - @backstage/plugin-search-react@1.8.4 + - @backstage/plugin-search@1.4.21 + - @backstage/plugin-notifications@0.5.0 + - @backstage/theme@0.6.3 + - @backstage/plugin-api-docs@0.12.2 + - @backstage/core-components@0.16.2 + - @backstage/plugin-catalog-graph@0.4.14 + - @backstage/plugin-catalog-import@0.12.8 + - @backstage/plugin-kubernetes@0.12.2 + - @backstage/plugin-kubernetes-cluster@0.0.20 + - @backstage/plugin-org@0.6.34 + - @backstage/plugin-user-settings@0.8.17 + - @backstage/app-defaults@1.5.15 + - @backstage/catalog-model@1.7.2 + - @backstage/config@1.3.1 + - @backstage/core-plugin-api@1.10.2 + - @backstage/frontend-app-api@0.10.3 + - @backstage/integration-react@1.2.2 + - @backstage/plugin-auth-react@0.1.10 + - @backstage/plugin-catalog-common@1.1.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.12 + - @backstage/plugin-devtools@0.1.22 + - @backstage/plugin-permission-react@0.4.29 + - @backstage/plugin-search-common@1.2.16 + - @backstage/plugin-signals@0.0.14 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.19 + - @backstage/plugin-techdocs-react@1.2.12 + +## 0.2.104-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.26.0-next.2 + - @backstage/plugin-notifications@0.5.0-next.2 + - @backstage/cli@0.29.3-next.2 + - @backstage/plugin-catalog-react@1.14.3-next.2 + - @backstage/plugin-scaffolder-react@1.14.2-next.2 + - @backstage/plugin-scaffolder@1.27.2-next.2 + - @backstage/plugin-api-docs@0.12.2-next.2 + - @backstage/plugin-catalog-graph@0.4.14-next.2 + - @backstage/plugin-catalog-import@0.12.8-next.2 + - @backstage/plugin-org@0.6.34-next.2 + - @backstage/plugin-user-settings@0.8.17-next.2 + - @backstage/app-defaults@1.5.15-next.2 + - @backstage/catalog-model@1.7.2-next.0 + - @backstage/config@1.3.1-next.0 + - @backstage/core-app-api@1.15.3-next.1 + - @backstage/core-components@0.16.2-next.2 + - @backstage/core-plugin-api@1.10.2-next.0 + - @backstage/frontend-app-api@0.10.3-next.2 + - @backstage/integration-react@1.2.2-next.1 + - @backstage/theme@0.6.3-next.0 + - @backstage/plugin-auth-react@0.1.10-next.2 + - @backstage/plugin-catalog-common@1.1.2-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.12-next.2 + - @backstage/plugin-devtools@0.1.22-next.2 + - @backstage/plugin-home@0.8.3-next.2 + - @backstage/plugin-kubernetes@0.12.2-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.20-next.2 + - @backstage/plugin-permission-react@0.4.29-next.0 + - @backstage/plugin-search@1.4.21-next.2 + - @backstage/plugin-search-common@1.2.16-next.0 + - @backstage/plugin-search-react@1.8.4-next.2 + - @backstage/plugin-signals@0.0.14-next.2 + - @backstage/plugin-techdocs@1.11.3-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.19-next.2 + - @backstage/plugin-techdocs-react@1.2.12-next.2 + +## 0.2.104-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-home@0.8.3-next.1 + - @backstage/plugin-catalog-react@1.14.3-next.1 + - @backstage/plugin-scaffolder-react@1.14.2-next.1 + - @backstage/cli@0.29.3-next.1 + - @backstage/core-components@0.16.2-next.1 + - @backstage/plugin-api-docs@0.12.2-next.1 + - @backstage/plugin-catalog@1.25.2-next.1 + - @backstage/plugin-catalog-graph@0.4.14-next.1 + - @backstage/plugin-catalog-import@0.12.8-next.1 + - @backstage/plugin-kubernetes@0.12.2-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.20-next.1 + - @backstage/plugin-org@0.6.34-next.1 + - @backstage/plugin-scaffolder@1.27.2-next.1 + - @backstage/plugin-search@1.4.21-next.1 + - @backstage/plugin-techdocs@1.11.3-next.1 + - @backstage/plugin-user-settings@0.8.17-next.1 + - @backstage/app-defaults@1.5.15-next.1 + - @backstage/catalog-model@1.7.1 + - @backstage/config@1.3.0 + - @backstage/core-app-api@1.15.3-next.0 + - @backstage/core-plugin-api@1.10.1 + - @backstage/frontend-app-api@0.10.3-next.1 + - @backstage/integration-react@1.2.2-next.0 + - @backstage/theme@0.6.3-next.0 + - @backstage/plugin-auth-react@0.1.10-next.1 + - @backstage/plugin-catalog-common@1.1.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.12-next.1 + - @backstage/plugin-devtools@0.1.22-next.1 + - @backstage/plugin-notifications@0.4.2-next.1 + - @backstage/plugin-permission-react@0.4.28 + - @backstage/plugin-search-common@1.2.15 + - @backstage/plugin-search-react@1.8.4-next.1 + - @backstage/plugin-signals@0.0.14-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.19-next.1 + - @backstage/plugin-techdocs-react@1.2.12-next.1 + +## 0.2.104-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.29.3-next.0 + - @backstage/core-app-api@1.15.3-next.0 + - @backstage/plugin-scaffolder-react@1.14.2-next.0 + - @backstage/plugin-scaffolder@1.27.2-next.0 + - @backstage/plugin-search-react@1.8.4-next.0 + - @backstage/plugin-search@1.4.21-next.0 + - @backstage/plugin-catalog-react@1.14.3-next.0 + - @backstage/plugin-techdocs@1.11.3-next.0 + - @backstage/plugin-home@0.8.3-next.0 + - @backstage/theme@0.6.3-next.0 + - @backstage/plugin-api-docs@0.12.2-next.0 + - @backstage/app-defaults@1.5.15-next.0 + - @backstage/catalog-model@1.7.1 + - @backstage/config@1.3.0 + - @backstage/core-components@0.16.2-next.0 + - @backstage/core-plugin-api@1.10.1 + - @backstage/frontend-app-api@0.10.3-next.0 + - @backstage/integration-react@1.2.2-next.0 + - @backstage/plugin-auth-react@0.1.10-next.0 + - @backstage/plugin-catalog@1.25.2-next.0 + - @backstage/plugin-catalog-common@1.1.1 + - @backstage/plugin-catalog-graph@0.4.14-next.0 + - @backstage/plugin-catalog-import@0.12.8-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.12-next.0 + - @backstage/plugin-devtools@0.1.22-next.0 + - @backstage/plugin-kubernetes@0.12.2-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.20-next.0 + - @backstage/plugin-notifications@0.4.2-next.0 + - @backstage/plugin-org@0.6.34-next.0 + - @backstage/plugin-permission-react@0.4.28 + - @backstage/plugin-search-common@1.2.15 + - @backstage/plugin-signals@0.0.14-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.19-next.0 + - @backstage/plugin-techdocs-react@1.2.12-next.0 + - @backstage/plugin-user-settings@0.8.17-next.0 + +## 0.2.103 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.29.0 + - @backstage/plugin-catalog@1.25.0 + - @backstage/plugin-api-docs@0.12.0 + - @backstage/config@1.3.0 + - @backstage/theme@0.6.1 + - @backstage/plugin-notifications@0.4.0 + - @backstage/plugin-techdocs@1.11.1 + - @backstage/plugin-search@1.4.19 + - @backstage/plugin-org@0.6.32 + - @backstage/plugin-scaffolder-react@1.14.0 + - @backstage/plugin-catalog-react@1.14.1 + - @backstage/plugin-scaffolder@1.27.0 + - @backstage/plugin-catalog-import@0.12.6 + - @backstage/core-components@0.16.0 + - @backstage/plugin-kubernetes@0.12.0 + - @backstage/plugin-home@0.8.1 + - @backstage/plugin-techdocs-react@1.2.10 + - @backstage/core-app-api@1.15.2 + - @backstage/plugin-signals@0.0.12 + - @backstage/plugin-kubernetes-cluster@0.0.18 + - @backstage/app-defaults@1.5.13 + - @backstage/catalog-model@1.7.1 + - @backstage/core-plugin-api@1.10.1 + - @backstage/frontend-app-api@0.10.1 + - @backstage/integration-react@1.2.1 + - @backstage/plugin-auth-react@0.1.8 + - @backstage/plugin-catalog-common@1.1.1 + - @backstage/plugin-catalog-graph@0.4.12 + - @backstage/plugin-catalog-unprocessed-entities@0.2.10 + - @backstage/plugin-devtools@0.1.20 + - @backstage/plugin-permission-react@0.4.28 + - @backstage/plugin-search-common@1.2.15 + - @backstage/plugin-search-react@1.8.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.17 + - @backstage/plugin-user-settings@0.8.15 + +## 0.2.103-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.25.0-next.3 + - @backstage/cli@0.29.0-next.3 + - @backstage/plugin-search@1.4.19-next.3 + - @backstage/plugin-techdocs@1.11.1-next.3 + - @backstage/plugin-scaffolder-react@1.14.0-next.3 + - @backstage/plugin-scaffolder@1.27.0-next.3 + - @backstage/core-components@0.16.0-next.2 + - @backstage/plugin-catalog-react@1.14.1-next.3 + - @backstage/plugin-api-docs@0.11.12-next.3 + - @backstage/plugin-catalog-graph@0.4.12-next.3 + - @backstage/plugin-catalog-import@0.12.6-next.3 + - @backstage/plugin-org@0.6.32-next.3 + - @backstage/plugin-user-settings@0.8.15-next.3 + - @backstage/app-defaults@1.5.13-next.2 + - @backstage/catalog-model@1.7.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.15.1 + - @backstage/core-plugin-api@1.10.0 + - @backstage/frontend-app-api@0.10.1-next.2 + - @backstage/integration-react@1.2.0 + - @backstage/theme@0.6.1-next.0 + - @backstage/plugin-auth-react@0.1.8-next.2 + - @backstage/plugin-catalog-common@1.1.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.10-next.2 + - @backstage/plugin-devtools@0.1.20-next.2 + - @backstage/plugin-home@0.8.1-next.3 + - @backstage/plugin-kubernetes@0.12.0-next.3 + - @backstage/plugin-kubernetes-cluster@0.0.18-next.3 + - @backstage/plugin-notifications@0.3.3-next.2 + - @backstage/plugin-permission-react@0.4.27 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-search-react@1.8.2-next.2 + - @backstage/plugin-signals@0.0.12-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.17-next.2 + - @backstage/plugin-techdocs-react@1.2.10-next.2 + +## 0.2.103-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.29.0-next.2 + - @backstage/plugin-catalog-import@0.12.6-next.2 + - @backstage/plugin-scaffolder@1.26.3-next.2 + - @backstage/plugin-catalog@1.24.1-next.2 + - @backstage/plugin-catalog-graph@0.4.12-next.2 + - @backstage/plugin-catalog-react@1.14.1-next.2 + - @backstage/plugin-home@0.8.1-next.2 + - @backstage/plugin-org@0.6.32-next.2 + - @backstage/plugin-scaffolder-react@1.13.3-next.2 + - @backstage/app-defaults@1.5.13-next.1 + - @backstage/catalog-model@1.7.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.15.1 + - @backstage/core-components@0.16.0-next.1 + - @backstage/core-plugin-api@1.10.0 + - @backstage/frontend-app-api@0.10.1-next.1 + - @backstage/integration-react@1.2.0 + - @backstage/theme@0.6.1-next.0 + - @backstage/plugin-api-docs@0.11.12-next.2 + - @backstage/plugin-auth-react@0.1.8-next.1 + - @backstage/plugin-catalog-common@1.1.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.10-next.1 + - @backstage/plugin-devtools@0.1.20-next.1 + - @backstage/plugin-kubernetes@0.12.0-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.18-next.2 + - @backstage/plugin-notifications@0.3.3-next.1 + - @backstage/plugin-permission-react@0.4.27 + - @backstage/plugin-search@1.4.19-next.2 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-search-react@1.8.2-next.1 + - @backstage/plugin-signals@0.0.12-next.1 + - @backstage/plugin-techdocs@1.11.1-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.17-next.1 + - @backstage/plugin-techdocs-react@1.2.10-next.1 + - @backstage/plugin-user-settings@0.8.15-next.2 + +## 0.2.103-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.29.0-next.1 + - @backstage/theme@0.6.1-next.0 + - @backstage/plugin-techdocs@1.11.1-next.1 + - @backstage/plugin-kubernetes@0.12.0-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.18-next.1 + - @backstage/app-defaults@1.5.13-next.1 + - @backstage/catalog-model@1.7.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.15.1 + - @backstage/core-components@0.16.0-next.1 + - @backstage/core-plugin-api@1.10.0 + - @backstage/frontend-app-api@0.10.1-next.1 + - @backstage/integration-react@1.2.0 + - @backstage/plugin-api-docs@0.11.12-next.1 + - @backstage/plugin-auth-react@0.1.8-next.1 + - @backstage/plugin-catalog@1.24.1-next.1 + - @backstage/plugin-catalog-common@1.1.0 + - @backstage/plugin-catalog-graph@0.4.12-next.1 + - @backstage/plugin-catalog-import@0.12.6-next.1 + - @backstage/plugin-catalog-react@1.14.1-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.10-next.1 + - @backstage/plugin-devtools@0.1.20-next.1 + - @backstage/plugin-home@0.8.1-next.1 + - @backstage/plugin-notifications@0.3.3-next.1 + - @backstage/plugin-org@0.6.32-next.1 + - @backstage/plugin-permission-react@0.4.27 + - @backstage/plugin-scaffolder@1.26.3-next.1 + - @backstage/plugin-scaffolder-react@1.13.3-next.1 + - @backstage/plugin-search@1.4.19-next.1 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-search-react@1.8.2-next.1 + - @backstage/plugin-signals@0.0.12-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.17-next.1 + - @backstage/plugin-techdocs-react@1.2.10-next.1 + - @backstage/plugin-user-settings@0.8.15-next.1 + +## 0.2.103-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.29.0-next.0 + - @backstage/plugin-api-docs@0.11.12-next.0 + - @backstage/plugin-scaffolder-react@1.13.2-next.0 + - @backstage/plugin-techdocs@1.11.1-next.0 + - @backstage/core-components@0.16.0-next.0 + - @backstage/plugin-home@0.8.1-next.0 + - @backstage/plugin-scaffolder@1.26.2-next.0 + - @backstage/plugin-techdocs-react@1.2.10-next.0 + - @backstage/plugin-notifications@0.3.3-next.0 + - @backstage/app-defaults@1.5.13-next.0 + - @backstage/catalog-model@1.7.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.15.1 + - @backstage/core-plugin-api@1.10.0 + - @backstage/frontend-app-api@0.10.1-next.0 + - @backstage/integration-react@1.2.0 + - @backstage/theme@0.6.0 + - @backstage/plugin-auth-react@0.1.8-next.0 + - @backstage/plugin-catalog@1.24.1-next.0 + - @backstage/plugin-catalog-common@1.1.0 + - @backstage/plugin-catalog-graph@0.4.12-next.0 + - @backstage/plugin-catalog-import@0.12.6-next.0 + - @backstage/plugin-catalog-react@1.14.1-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.10-next.0 + - @backstage/plugin-devtools@0.1.20-next.0 + - @backstage/plugin-kubernetes@0.11.17-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.18-next.0 + - @backstage/plugin-org@0.6.32-next.0 + - @backstage/plugin-permission-react@0.4.27 + - @backstage/plugin-search@1.4.19-next.0 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-search-react@1.8.2-next.0 + - @backstage/plugin-signals@0.0.12-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.17-next.0 + - @backstage/plugin-user-settings@0.8.15-next.0 + +## 0.2.102 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.28.0 + - @backstage/core-components@0.15.1 + - @backstage/plugin-scaffolder@1.26.0 + - @backstage/plugin-scaffolder-react@1.13.0 + - @backstage/plugin-catalog@1.24.0 + - @backstage/plugin-signals@0.0.11 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.16 + - @backstage/plugin-catalog-unprocessed-entities@0.2.9 + - @backstage/integration-react@1.2.0 + - @backstage/plugin-kubernetes-cluster@0.0.17 + - @backstage/frontend-app-api@0.10.0 + - @backstage/core-plugin-api@1.10.0 + - @backstage/plugin-permission-react@0.4.27 + - @backstage/plugin-catalog-import@0.12.5 + - @backstage/plugin-techdocs-react@1.2.9 + - @backstage/app-defaults@1.5.12 + - @backstage/core-app-api@1.15.1 + - @backstage/plugin-catalog-graph@0.4.11 + - @backstage/plugin-catalog-react@1.14.0 + - @backstage/plugin-notifications@0.3.2 + - @backstage/plugin-user-settings@0.8.14 + - @backstage/plugin-search-react@1.8.1 + - @backstage/plugin-auth-react@0.1.7 + - @backstage/plugin-kubernetes@0.11.16 + - @backstage/plugin-api-docs@0.11.11 + - @backstage/plugin-devtools@0.1.19 + - @backstage/plugin-techdocs@1.11.0 + - @backstage/theme@0.6.0 + - @backstage/plugin-search@1.4.18 + - @backstage/plugin-home@0.8.0 + - @backstage/plugin-org@0.6.31 + - @backstage/catalog-model@1.7.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.1.0 + - @backstage/plugin-search-common@1.2.14 + +## 0.2.102-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.28.0-next.2 + - @backstage/plugin-scaffolder@1.26.0-next.2 + - @backstage/plugin-catalog@1.24.0-next.2 + - @backstage/plugin-catalog-react@1.14.0-next.2 + - @backstage/plugin-home@0.8.0-next.2 + - @backstage/plugin-techdocs@1.11.0-next.2 + - @backstage/plugin-catalog-import@0.12.5-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.16-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.17-next.2 + - @backstage/plugin-kubernetes@0.11.16-next.2 + - @backstage/plugin-search@1.4.18-next.2 + - @backstage/plugin-user-settings@0.8.14-next.2 + - @backstage/theme@0.6.0-next.1 + - @backstage/plugin-catalog-graph@0.4.11-next.2 + - @backstage/app-defaults@1.5.12-next.2 + - @backstage/catalog-model@1.7.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.15.1-next.1 + - @backstage/core-components@0.15.1-next.2 + - @backstage/core-plugin-api@1.10.0-next.1 + - @backstage/frontend-app-api@0.10.0-next.2 + - @backstage/integration-react@1.2.0-next.2 + - @backstage/plugin-api-docs@0.11.11-next.2 + - @backstage/plugin-auth-react@0.1.7-next.2 + - @backstage/plugin-catalog-common@1.1.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.9-next.2 + - @backstage/plugin-devtools@0.1.19-next.2 + - @backstage/plugin-notifications@0.3.2-next.2 + - @backstage/plugin-org@0.6.31-next.2 + - @backstage/plugin-permission-react@0.4.27-next.1 + - @backstage/plugin-scaffolder-react@1.13.0-next.2 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-search-react@1.8.1-next.2 + - @backstage/plugin-signals@0.0.11-next.2 + - @backstage/plugin-techdocs-react@1.2.9-next.2 + +## 0.2.102-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.15.1-next.1 + - @backstage/cli@0.28.0-next.1 + - @backstage/plugin-scaffolder@1.26.0-next.1 + - @backstage/plugin-signals@0.0.11-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.16-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.9-next.1 + - @backstage/integration-react@1.2.0-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.17-next.1 + - @backstage/frontend-app-api@0.10.0-next.1 + - @backstage/core-plugin-api@1.10.0-next.1 + - @backstage/plugin-permission-react@0.4.27-next.1 + - @backstage/plugin-scaffolder-react@1.13.0-next.1 + - @backstage/plugin-catalog-import@0.12.5-next.1 + - @backstage/plugin-techdocs-react@1.2.9-next.1 + - @backstage/app-defaults@1.5.12-next.1 + - @backstage/core-app-api@1.15.1-next.1 + - @backstage/plugin-catalog-graph@0.4.11-next.1 + - @backstage/plugin-catalog-react@1.14.0-next.1 + - @backstage/plugin-notifications@0.3.2-next.1 + - @backstage/plugin-user-settings@0.8.14-next.1 + - @backstage/plugin-search-react@1.8.1-next.1 + - @backstage/plugin-auth-react@0.1.7-next.1 + - @backstage/plugin-kubernetes@0.11.16-next.1 + - @backstage/plugin-api-docs@0.11.11-next.1 + - @backstage/plugin-devtools@0.1.19-next.1 + - @backstage/plugin-techdocs@1.10.11-next.1 + - @backstage/plugin-catalog@1.24.0-next.1 + - @backstage/theme@0.5.8-next.0 + - @backstage/plugin-search@1.4.18-next.1 + - @backstage/plugin-home@0.7.12-next.1 + - @backstage/plugin-org@0.6.31-next.1 + - @backstage/catalog-model@1.7.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.1.0 + - @backstage/plugin-search-common@1.2.14 + +## 0.2.102-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.26.0-next.0 + - @backstage/plugin-catalog@1.24.0-next.0 + - @backstage/cli@0.28.0-next.0 + - @backstage/plugin-scaffolder-react@1.13.0-next.0 + - @backstage/plugin-api-docs@0.11.10-next.0 + - @backstage/frontend-app-api@0.10.0-next.0 + - @backstage/plugin-techdocs@1.10.10-next.0 + - @backstage/app-defaults@1.5.12-next.0 + - @backstage/core-components@0.15.1-next.0 + - @backstage/core-plugin-api@1.10.0-next.0 + - @backstage/plugin-catalog-graph@0.4.10-next.0 + - @backstage/plugin-catalog-import@0.12.4-next.0 + - @backstage/plugin-catalog-react@1.13.1-next.0 + - @backstage/plugin-devtools@0.1.19-next.0 + - @backstage/plugin-home@0.7.11-next.0 + - @backstage/plugin-kubernetes@0.11.15-next.0 + - @backstage/plugin-org@0.6.30-next.0 + - @backstage/plugin-search@1.4.17-next.0 + - @backstage/plugin-search-react@1.8.1-next.0 + - @backstage/plugin-user-settings@0.8.13-next.0 + - @backstage/catalog-model@1.7.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.15.1-next.0 + - @backstage/integration-react@1.1.32-next.0 + - @backstage/theme@0.5.7 + - @backstage/plugin-auth-react@0.1.7-next.0 + - @backstage/plugin-catalog-common@1.1.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.9-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.16-next.0 + - @backstage/plugin-notifications@0.3.2-next.0 + - @backstage/plugin-permission-react@0.4.27-next.0 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-signals@0.0.11-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.15-next.0 + - @backstage/plugin-techdocs-react@1.2.9-next.0 + +## 0.2.101 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.15.0 + - @backstage/plugin-catalog-react@1.13.0 + - @backstage/plugin-techdocs@1.10.9 + - @backstage/plugin-catalog@1.23.0 + - @backstage/plugin-home@0.7.10 + - @backstage/cli@0.27.1 + - @backstage/plugin-techdocs-react@1.2.8 + - @backstage/core-app-api@1.15.0 + - @backstage/frontend-app-api@0.9.0 + - @backstage/plugin-catalog-import@0.12.3 + - @backstage/plugin-catalog-graph@0.4.9 + - @backstage/plugin-user-settings@0.8.12 + - @backstage/plugin-search-react@1.8.0 + - @backstage/plugin-kubernetes@0.11.14 + - @backstage/plugin-scaffolder@1.25.0 + - @backstage/plugin-api-docs@0.11.9 + - @backstage/plugin-devtools@0.1.18 + - @backstage/plugin-search@1.4.16 + - @backstage/plugin-org@0.6.29 + - @backstage/integration-react@1.1.31 + - @backstage/plugin-notifications@0.3.1 + - @backstage/catalog-model@1.7.0 + - @backstage/plugin-scaffolder-react@1.12.0 + - @backstage/app-defaults@1.5.11 + - @backstage/plugin-catalog-common@1.1.0 + - @backstage/core-plugin-api@1.9.4 + - @backstage/theme@0.5.7 + - @backstage/plugin-auth-react@0.1.6 + - @backstage/plugin-catalog-unprocessed-entities@0.2.8 + - @backstage/plugin-kubernetes-cluster@0.0.15 + - @backstage/plugin-permission-react@0.4.26 + - @backstage/plugin-signals@0.0.10 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.14 + - @backstage/config@1.2.0 + - @backstage/plugin-search-common@1.2.14 + +## 0.2.101-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/plugin-catalog-react@1.13.0-next.2 + - @backstage/plugin-techdocs@1.10.9-next.2 + - @backstage/plugin-catalog@1.23.0-next.2 + - @backstage/plugin-home@0.7.10-next.2 + - @backstage/cli@0.27.1-next.2 + - @backstage/core-app-api@1.14.3-next.0 + - @backstage/integration-react@1.1.31-next.0 + - @backstage/plugin-scaffolder@1.25.0-next.2 + - @backstage/plugin-notifications@0.3.1-next.2 + - @backstage/plugin-scaffolder-react@1.12.0-next.2 + - @backstage/app-defaults@1.5.11-next.1 + - @backstage/plugin-catalog-import@0.12.3-next.2 + - @backstage/plugin-search-react@1.8.0-next.2 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-app-api@0.9.0-next.2 + - @backstage/theme@0.5.7-next.0 + - @backstage/plugin-api-docs@0.11.9-next.2 + - @backstage/plugin-auth-react@0.1.6-next.1 + - @backstage/plugin-catalog-graph@0.4.9-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.8-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.15-next.2 + - @backstage/plugin-kubernetes@0.11.14-next.2 + - @backstage/plugin-org@0.6.29-next.2 + - @backstage/plugin-permission-react@0.4.26-next.0 + - @backstage/plugin-search@1.4.16-next.2 + - @backstage/plugin-signals@0.0.10-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.14-next.2 + - @backstage/plugin-techdocs-react@1.2.8-next.2 + - @backstage/plugin-user-settings@0.8.12-next.2 + - @backstage/plugin-devtools@0.1.18-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-search-common@1.2.14 + +## 0.2.101-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.27.1-next.1 + - @backstage/frontend-app-api@0.9.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-graph@0.4.9-next.1 + - @backstage/plugin-scaffolder-react@1.12.0-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.8-next.0 + - @backstage/plugin-signals@0.0.10-next.1 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/app-defaults@1.5.11-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + - @backstage/plugin-api-docs@0.11.9-next.1 + - @backstage/plugin-auth-react@0.1.6-next.0 + - @backstage/plugin-catalog@1.22.1-next.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-import@0.12.3-next.1 + - @backstage/plugin-devtools@0.1.18-next.1 + - @backstage/plugin-home@0.7.10-next.1 + - @backstage/plugin-kubernetes@0.11.14-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.15-next.1 + - @backstage/plugin-notifications@0.3.1-next.1 + - @backstage/plugin-org@0.6.29-next.1 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-scaffolder@1.25.0-next.1 + - @backstage/plugin-search@1.4.16-next.1 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-search-react@1.8.0-next.1 + - @backstage/plugin-techdocs@1.10.9-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.14-next.1 + - @backstage/plugin-techdocs-react@1.2.8-next.1 + - @backstage/plugin-user-settings@0.8.12-next.1 + +## 0.2.101-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-react@1.2.8-next.0 + - @backstage/frontend-app-api@0.9.0-next.0 + - @backstage/plugin-catalog-import@0.12.3-next.0 + - @backstage/plugin-catalog-graph@0.4.9-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/plugin-user-settings@0.8.12-next.0 + - @backstage/plugin-search-react@1.8.0-next.0 + - @backstage/plugin-kubernetes@0.11.14-next.0 + - @backstage/plugin-scaffolder@1.25.0-next.0 + - @backstage/plugin-api-docs@0.11.9-next.0 + - @backstage/plugin-devtools@0.1.18-next.0 + - @backstage/plugin-techdocs@1.10.9-next.0 + - @backstage/plugin-catalog@1.22.1-next.0 + - @backstage/plugin-search@1.4.16-next.0 + - @backstage/plugin-home@0.7.10-next.0 + - @backstage/plugin-org@0.6.29-next.0 + - @backstage/plugin-scaffolder-react@1.12.0-next.0 + - @backstage/cli@0.27.1-next.0 + - @backstage/plugin-signals@0.0.10-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.14-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.15-next.0 + - @backstage/app-defaults@1.5.10 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.5 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7 + - @backstage/plugin-notifications@0.3.1-next.0 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-search-common@1.2.14 + +## 0.2.100 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.22.0 + - @backstage/plugin-scaffolder@1.24.0 + - @backstage/plugin-scaffolder-react@1.11.0 + - @backstage/cli@0.27.0 + - @backstage/plugin-notifications@0.3.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/plugin-search-react@1.7.14 + - @backstage/plugin-home@0.7.9 + - @backstage/plugin-techdocs@1.10.8 + - @backstage/core-components@0.14.10 + - @backstage/plugin-api-docs@0.11.8 + - @backstage/frontend-app-api@0.8.0 + - @backstage/plugin-catalog-graph@0.4.8 + - @backstage/plugin-catalog-import@0.12.2 + - @backstage/plugin-devtools@0.1.17 + - @backstage/plugin-org@0.6.28 + - @backstage/plugin-search@1.4.15 + - @backstage/plugin-user-settings@0.8.11 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-kubernetes@0.11.13 + - @backstage/core-app-api@1.14.2 + - @backstage/plugin-auth-react@0.1.5 + - @backstage/catalog-model@1.6.0 + - @backstage/app-defaults@1.5.10 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7 + - @backstage/plugin-kubernetes-cluster@0.0.14 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-signals@0.0.9 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.13 + - @backstage/plugin-techdocs-react@1.2.7 + +## 0.2.100-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.24.0-next.3 + - @backstage/plugin-notifications@0.3.0-next.1 + - @backstage/cli@0.27.0-next.4 + - @backstage/plugin-techdocs@1.10.8-next.3 + - @backstage/plugin-api-docs@0.11.8-next.3 + - @backstage/plugin-catalog@1.22.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/frontend-app-api@0.7.5-next.3 + - @backstage/plugin-catalog-graph@0.4.8-next.4 + - @backstage/plugin-catalog-import@0.12.2-next.3 + - @backstage/plugin-catalog-react@1.12.3-next.3 + - @backstage/plugin-devtools@0.1.17-next.3 + - @backstage/plugin-home@0.7.9-next.3 + - @backstage/plugin-kubernetes@0.11.13-next.3 + - @backstage/plugin-org@0.6.28-next.3 + - @backstage/plugin-search@1.4.15-next.3 + - @backstage/plugin-search-react@1.7.14-next.3 + - @backstage/plugin-user-settings@0.8.11-next.3 + - @backstage/app-defaults@1.5.10-next.2 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.5-next.0 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.14-next.3 + - @backstage/plugin-permission-react@0.4.25-next.1 + - @backstage/plugin-scaffolder-react@1.11.0-next.3 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/plugin-signals@0.0.9-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.13-next.1 + - @backstage/plugin-techdocs-react@1.2.7-next.1 + +## 0.2.100-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.11.0-next.2 + - @backstage/plugin-catalog@1.22.0-next.2 + - @backstage/cli@0.27.0-next.3 + - @backstage/frontend-app-api@0.7.5-next.2 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/plugin-search-react@1.7.14-next.2 + - @backstage/plugin-search@1.4.15-next.2 + - @backstage/plugin-api-docs@0.11.8-next.2 + - @backstage/plugin-techdocs@1.10.8-next.2 + - @backstage/plugin-scaffolder@1.24.0-next.2 + - @backstage/plugin-catalog-react@1.12.3-next.2 + - @backstage/plugin-catalog-graph@0.4.8-next.3 + - @backstage/plugin-catalog-import@0.12.2-next.2 + - @backstage/plugin-org@0.6.28-next.2 + - @backstage/plugin-user-settings@0.8.11-next.2 + - @backstage/plugin-devtools@0.1.17-next.2 + - @backstage/plugin-home@0.7.9-next.2 + - @backstage/plugin-kubernetes@0.11.13-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @backstage/plugin-permission-react@0.4.25-next.1 + - @backstage/app-defaults@1.5.10-next.2 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.5-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.14-next.2 + - @backstage/plugin-notifications@0.2.4-next.0 + - @backstage/plugin-signals@0.0.9-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.13-next.0 + - @backstage/plugin-techdocs-react@1.2.7-next.0 + +## 0.2.100-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.27.0-next.2 + - @backstage/plugin-catalog-graph@0.4.8-next.2 + - @backstage/app-defaults@1.5.10-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/frontend-app-api@0.7.5-next.1 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-api-docs@0.11.8-next.1 + - @backstage/plugin-auth-react@0.1.5-next.0 + - @backstage/plugin-catalog@1.21.2-next.1 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-catalog-import@0.12.2-next.1 + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.0 + - @backstage/plugin-devtools@0.1.17-next.1 + - @backstage/plugin-home@0.7.9-next.1 + - @backstage/plugin-kubernetes@0.11.13-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.14-next.1 + - @backstage/plugin-notifications@0.2.4-next.0 + - @backstage/plugin-org@0.6.28-next.1 + - @backstage/plugin-permission-react@0.4.25-next.0 + - @backstage/plugin-scaffolder@1.23.1-next.1 + - @backstage/plugin-scaffolder-react@1.11.0-next.1 + - @backstage/plugin-search@1.4.15-next.1 + - @backstage/plugin-search-common@1.2.14-next.0 + - @backstage/plugin-search-react@1.7.14-next.1 + - @backstage/plugin-signals@0.0.9-next.0 + - @backstage/plugin-techdocs@1.10.8-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.13-next.0 + - @backstage/plugin-techdocs-react@1.2.7-next.0 + - @backstage/plugin-user-settings@0.8.11-next.1 + +## 0.2.100-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.10.8-next.1 + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/frontend-app-api@0.7.5-next.1 + - @backstage/plugin-kubernetes@0.11.13-next.1 + - @backstage/cli@0.27.0-next.1 + - @backstage/plugin-scaffolder@1.23.1-next.1 + - @backstage/plugin-user-settings@0.8.11-next.1 + - @backstage/plugin-search-react@1.7.14-next.1 + - @backstage/plugin-catalog@1.21.2-next.1 + - @backstage/plugin-search@1.4.15-next.1 + - @backstage/plugin-api-docs@0.11.8-next.1 + - @backstage/plugin-catalog-graph@0.4.8-next.1 + - @backstage/plugin-catalog-import@0.12.2-next.1 + - @backstage/plugin-home@0.7.9-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.14-next.1 + - @backstage/plugin-org@0.6.28-next.1 + - @backstage/plugin-scaffolder-react@1.11.0-next.1 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-permission-react@0.4.25-next.0 + - @backstage/plugin-search-common@1.2.14-next.0 + - @backstage/plugin-devtools@0.1.17-next.1 + - @backstage/app-defaults@1.5.10-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.5-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.0 + - @backstage/plugin-notifications@0.2.4-next.0 + - @backstage/plugin-signals@0.0.9-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.13-next.0 + - @backstage/plugin-techdocs-react@1.2.7-next.0 + +## 0.2.100-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.27.0-next.0 + - @backstage/plugin-catalog-react@1.12.3-next.0 + - @backstage/plugin-search-react@1.7.14-next.0 + - @backstage/plugin-home@0.7.8-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/plugin-scaffolder-react@1.11.0-next.0 + - @backstage/frontend-app-api@0.7.4-next.0 + - @backstage/plugin-catalog-graph@0.4.8-next.0 + - @backstage/plugin-scaffolder@1.23.1-next.0 + - @backstage/core-app-api@1.14.1-next.0 + - @backstage/plugin-auth-react@0.1.5-next.0 + - @backstage/app-defaults@1.5.9-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-api-docs@0.11.8-next.0 + - @backstage/plugin-catalog@1.21.2-next.0 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-catalog-import@0.12.2-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.0 + - @backstage/plugin-devtools@0.1.17-next.0 + - @backstage/plugin-kubernetes@0.11.13-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.14-next.0 + - @backstage/plugin-notifications@0.2.4-next.0 + - @backstage/plugin-org@0.6.28-next.0 + - @backstage/plugin-permission-react@0.4.24 + - @backstage/plugin-search@1.4.15-next.0 + - @backstage/plugin-search-common@1.2.13 + - @backstage/plugin-signals@0.0.9-next.0 + - @backstage/plugin-techdocs@1.10.8-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.13-next.0 + - @backstage/plugin-techdocs-react@1.2.7-next.0 + - @backstage/plugin-user-settings@0.8.10-next.0 + +## 0.2.99 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.23.0 + - @backstage/plugin-techdocs@1.10.7 + - @backstage/plugin-techdocs-react@1.2.6 + - @backstage/core-components@0.14.9 + - @backstage/cli@0.26.11 + - @backstage/core-app-api@1.14.0 + - @backstage/frontend-app-api@0.7.3 + - @backstage/plugin-org@0.6.27 + - @backstage/plugin-catalog-react@1.12.2 + - @backstage/plugin-catalog@1.21.1 + - @backstage/plugin-notifications@0.2.3 + - @backstage/plugin-home@0.7.7 + - @backstage/plugin-scaffolder-react@1.10.0 + - @backstage/plugin-permission-react@0.4.24 + - @backstage/plugin-search@1.4.14 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.12 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-search-common@1.2.13 + - @backstage/app-defaults@1.5.8 + - @backstage/integration-react@1.1.29 + - @backstage/plugin-api-docs@0.11.7 + - @backstage/plugin-auth-react@0.1.4 + - @backstage/plugin-catalog-graph@0.4.7 + - @backstage/plugin-catalog-import@0.12.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.6 + - @backstage/plugin-devtools@0.1.16 + - @backstage/plugin-kubernetes@0.11.12 + - @backstage/plugin-kubernetes-cluster@0.0.13 + - @backstage/plugin-search-react@1.7.13 + - @backstage/plugin-signals@0.0.8 + - @backstage/plugin-user-settings@0.8.9 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + +## 0.2.99-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.10.7-next.2 + - @backstage/core-components@0.14.9-next.1 + - @backstage/plugin-search@1.4.14-next.2 + - @backstage/app-defaults@1.5.8-next.2 + - @backstage/cli@0.26.11-next.1 + - @backstage/frontend-app-api@0.7.3-next.2 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/plugin-api-docs@0.11.7-next.2 + - @backstage/plugin-auth-react@0.1.4-next.1 + - @backstage/plugin-catalog@1.21.1-next.2 + - @backstage/plugin-catalog-graph@0.4.7-next.2 + - @backstage/plugin-catalog-import@0.12.1-next.2 + - @backstage/plugin-catalog-react@1.12.2-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.6-next.1 + - @backstage/plugin-devtools@0.1.16-next.1 + - @backstage/plugin-home@0.7.7-next.2 + - @backstage/plugin-kubernetes@0.11.12-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.13-next.2 + - @backstage/plugin-notifications@0.2.3-next.2 + - @backstage/plugin-org@0.6.27-next.2 + - @backstage/plugin-scaffolder@1.22.1-next.2 + - @backstage/plugin-scaffolder-react@1.10.0-next.2 + - @backstage/plugin-search-react@1.7.13-next.1 + - @backstage/plugin-signals@0.0.8-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.12-next.1 + - @backstage/plugin-techdocs-react@1.2.6-next.1 + - @backstage/plugin-user-settings@0.8.9-next.2 + +## 0.2.99-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-org@0.6.27-next.1 + - @backstage/core-app-api@1.13.1-next.1 + - @backstage/plugin-catalog-react@1.12.2-next.1 + - @backstage/plugin-catalog@1.21.1-next.1 + - @backstage/plugin-techdocs@1.10.7-next.1 + - @backstage/plugin-notifications@0.2.3-next.1 + - @backstage/plugin-home@0.7.7-next.1 + - @backstage/plugin-scaffolder-react@1.10.0-next.1 + - @backstage/plugin-scaffolder@1.22.1-next.1 + - @backstage/cli@0.26.11-next.1 + - @backstage/app-defaults@1.5.8-next.1 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/frontend-app-api@0.7.3-next.1 + - @backstage/plugin-api-docs@0.11.7-next.1 + - @backstage/plugin-catalog-graph@0.4.7-next.1 + - @backstage/plugin-catalog-import@0.12.1-next.1 + - @backstage/plugin-search@1.4.13-next.1 + - @backstage/plugin-search-react@1.7.13-next.0 + - @backstage/plugin-signals@0.0.8-next.0 + - @backstage/plugin-user-settings@0.8.9-next.1 + - @backstage/plugin-kubernetes@0.11.12-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.13-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.4-next.0 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-catalog-unprocessed-entities@0.2.6-next.0 + - @backstage/plugin-devtools@0.1.16-next.0 + - @backstage/plugin-permission-react@0.4.23 + - @backstage/plugin-search-common@1.2.12 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.12-next.0 + - @backstage/plugin-techdocs-react@1.2.6-next.0 + +## 0.2.99-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.22.0-next.0 + - @backstage/plugin-techdocs-react@1.2.6-next.0 + - @backstage/plugin-techdocs@1.10.7-next.0 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-app-api@1.13.0-next.0 + - @backstage/frontend-app-api@0.7.2-next.0 + - @backstage/cli@0.26.10-next.0 + - @backstage/plugin-org@0.6.27-next.0 + - @backstage/plugin-catalog-react@1.12.2-next.0 + - @backstage/plugin-catalog@1.21.1-next.0 + - @backstage/plugin-scaffolder-react@1.10.0-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.12-next.0 + - @backstage/app-defaults@1.5.7-next.0 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/plugin-api-docs@0.11.7-next.0 + - @backstage/plugin-auth-react@0.1.4-next.0 + - @backstage/plugin-catalog-graph@0.4.7-next.0 + - @backstage/plugin-catalog-import@0.12.1-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.6-next.0 + - @backstage/plugin-devtools@0.1.16-next.0 + - @backstage/plugin-home@0.7.6-next.0 + - @backstage/plugin-kubernetes@0.11.12-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.13-next.0 + - @backstage/plugin-notifications@0.2.3-next.0 + - @backstage/plugin-search@1.4.13-next.0 + - @backstage/plugin-search-react@1.7.13-next.0 + - @backstage/plugin-signals@0.0.8-next.0 + - @backstage/plugin-user-settings@0.8.8-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-permission-react@0.4.23 + - @backstage/plugin-search-common@1.2.12 + +## 0.2.98 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8 + - @backstage/plugin-techdocs@1.10.6 + - @backstage/plugin-api-docs@0.11.6 + - @backstage/cli@0.26.7 + - @backstage/plugin-scaffolder@1.21.0 + - @backstage/plugin-catalog-import@0.12.0 + - @backstage/plugin-kubernetes@0.11.11 + - @backstage/plugin-search@1.4.12 + - @backstage/plugin-catalog-graph@0.4.6 + - @backstage/plugin-notifications@0.2.2 + - @backstage/plugin-org@0.6.26 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-scaffolder-react@1.9.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.11 + - @backstage/plugin-catalog-unprocessed-entities@0.2.5 + - @backstage/plugin-kubernetes-cluster@0.0.12 + - @backstage/plugin-permission-react@0.4.23 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-techdocs-react@1.2.5 + - @backstage/plugin-catalog-react@1.12.1 + - @backstage/plugin-search-common@1.2.12 + - @backstage/plugin-user-settings@0.8.7 + - @backstage/plugin-search-react@1.7.12 + - @backstage/plugin-auth-react@0.1.3 + - @backstage/plugin-devtools@0.1.15 + - @backstage/plugin-catalog@1.21.0 + - @backstage/plugin-signals@0.0.7 + - @backstage/plugin-home@0.7.5 + - @backstage/core-app-api@1.12.6 + - @backstage/integration-react@1.1.28 + - @backstage/app-defaults@1.5.6 + - @backstage/frontend-app-api@0.7.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## 0.2.98-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-graph@0.4.6-next.2 + - @backstage/core-components@0.14.8-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.11-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.5-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.12-next.3 + - @backstage/plugin-permission-react@0.4.23-next.1 + - @backstage/plugin-scaffolder-react@1.8.7-next.3 + - @backstage/plugin-catalog-common@1.0.24-next.0 + - @backstage/plugin-catalog-import@0.12.0-next.3 + - @backstage/plugin-techdocs-react@1.2.5-next.2 + - @backstage/plugin-catalog-react@1.12.1-next.2 + - @backstage/plugin-notifications@0.2.2-next.3 + - @backstage/plugin-search-common@1.2.12-next.0 + - @backstage/plugin-user-settings@0.8.7-next.2 + - @backstage/plugin-search-react@1.7.12-next.2 + - @backstage/plugin-auth-react@0.1.3-next.2 + - @backstage/plugin-kubernetes@0.11.11-next.3 + - @backstage/plugin-scaffolder@1.21.0-next.3 + - @backstage/plugin-api-docs@0.11.6-next.2 + - @backstage/plugin-devtools@0.1.15-next.2 + - @backstage/plugin-techdocs@1.10.6-next.2 + - @backstage/plugin-catalog@1.21.0-next.3 + - @backstage/plugin-signals@0.0.7-next.2 + - @backstage/plugin-search@1.4.12-next.3 + - @backstage/plugin-home@0.7.5-next.2 + - @backstage/plugin-org@0.6.26-next.2 + - @backstage/integration-react@1.1.28-next.1 + - @backstage/cli@0.26.7-next.3 + - @backstage/app-defaults@1.5.6-next.2 + - @backstage/frontend-app-api@0.7.1-next.2 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.12.6-next.0 + - @backstage/core-plugin-api@1.9.3-next.0 + - @backstage/theme@0.5.6-next.0 + +## 0.2.98-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.1 + - @backstage/plugin-api-docs@0.11.6-next.1 + - @backstage/plugin-scaffolder@1.21.0-next.2 + - @backstage/plugin-techdocs@1.10.6-next.1 + - @backstage/plugin-org@0.6.26-next.1 + - @backstage/core-plugin-api@1.9.3-next.0 + - @backstage/plugin-catalog-graph@0.4.6-next.1 + - @backstage/plugin-catalog@1.21.0-next.2 + - @backstage/core-app-api@1.12.6-next.0 + - @backstage/cli@0.26.7-next.2 + - @backstage/app-defaults@1.5.6-next.1 + - @backstage/frontend-app-api@0.7.1-next.1 + - @backstage/integration-react@1.1.28-next.0 + - @backstage/plugin-auth-react@0.1.3-next.1 + - @backstage/plugin-catalog-import@0.12.0-next.2 + - @backstage/plugin-catalog-react@1.12.1-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.5-next.1 + - @backstage/plugin-devtools@0.1.15-next.1 + - @backstage/plugin-home@0.7.5-next.1 + - @backstage/plugin-kubernetes@0.11.11-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.12-next.2 + - @backstage/plugin-notifications@0.2.2-next.2 + - @backstage/plugin-scaffolder-react@1.8.7-next.2 + - @backstage/plugin-search@1.4.12-next.2 + - @backstage/plugin-search-react@1.7.12-next.1 + - @backstage/plugin-signals@0.0.7-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.11-next.1 + - @backstage/plugin-techdocs-react@1.2.5-next.1 + - @backstage/plugin-user-settings@0.8.7-next.1 + - @backstage/plugin-permission-react@0.4.23-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/theme@0.5.6-next.0 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-search-common@1.2.11 + +## 0.2.98-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.26.7-next.1 + - @backstage/plugin-catalog-import@0.12.0-next.1 + - @backstage/plugin-kubernetes@0.11.11-next.1 + - @backstage/plugin-search@1.4.12-next.1 + - @backstage/plugin-notifications@0.2.2-next.1 + - @backstage/plugin-scaffolder-react@1.8.7-next.1 + - @backstage/plugin-scaffolder@1.20.2-next.1 + - @backstage/plugin-catalog@1.20.1-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.12-next.1 + - @backstage/plugin-catalog-react@1.12.1-next.0 + +## 0.2.98-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.10.6-next.0 + - @backstage/plugin-notifications@0.2.2-next.0 + - @backstage/plugin-scaffolder-react@1.8.6-next.0 + - @backstage/theme@0.5.6-next.0 + - @backstage/plugin-api-docs@0.11.6-next.0 + - @backstage/cli@0.26.6-next.0 + - @backstage/plugin-scaffolder@1.20.1-next.0 + - @backstage/app-defaults@1.5.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/frontend-app-api@0.7.1-next.0 + - @backstage/plugin-home@0.7.5-next.0 + - @backstage/plugin-search-react@1.7.12-next.0 + - @backstage/plugin-signals@0.0.7-next.0 + - @backstage/plugin-techdocs-react@1.2.5-next.0 + - @backstage/plugin-user-settings@0.8.7-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.12.5 + - @backstage/core-plugin-api@1.9.2 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-auth-react@0.1.3-next.0 + - @backstage/plugin-catalog@1.20.1-next.0 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-catalog-graph@0.4.6-next.0 + - @backstage/plugin-catalog-import@0.11.1-next.0 + - @backstage/plugin-catalog-react@1.12.1-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.5-next.0 + - @backstage/plugin-devtools@0.1.15-next.0 + - @backstage/plugin-kubernetes@0.11.11-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.12-next.0 + - @backstage/plugin-org@0.6.26-next.0 + - @backstage/plugin-permission-react@0.4.22 + - @backstage/plugin-search@1.4.12-next.0 + - @backstage/plugin-search-common@1.2.11 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.11-next.0 + +## 0.2.97 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.8.5 + - @backstage/plugin-scaffolder@1.20.0 + - @backstage/plugin-user-settings@0.8.6 + - @backstage/plugin-catalog@1.20.0 + - @backstage/plugin-notifications@0.2.1 + - @backstage/plugin-api-docs@0.11.5 + - @backstage/plugin-home@0.7.4 + - @backstage/cli@0.26.5 + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-techdocs@1.10.5 + - @backstage/plugin-catalog-import@0.11.0 + - @backstage/frontend-app-api@0.7.0 + - @backstage/plugin-auth-react@0.1.2 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/theme@0.5.4 + - @backstage/core-app-api@1.12.5 + - @backstage/plugin-catalog-graph@0.4.5 + - @backstage/plugin-search@1.4.11 + - @backstage/plugin-org@0.6.25 + - @backstage/app-defaults@1.5.5 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-catalog-unprocessed-entities@0.2.4 + - @backstage/plugin-devtools@0.1.14 + - @backstage/plugin-kubernetes@0.11.10 + - @backstage/plugin-kubernetes-cluster@0.0.11 + - @backstage/plugin-search-react@1.7.11 + - @backstage/plugin-signals@0.0.6 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.10 + - @backstage/plugin-techdocs-react@1.2.4 + +## 0.2.97-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.26.5-next.1 + - @backstage/frontend-app-api@0.7.0-next.2 + - @backstage/plugin-techdocs@1.10.5-next.2 + - @backstage/plugin-notifications@0.2.1-next.2 + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/plugin-catalog@1.20.0-next.2 + - @backstage/plugin-api-docs@0.11.5-next.2 + - @backstage/core-components@0.14.7-next.2 + - @backstage/plugin-scaffolder@1.19.4-next.2 + - @backstage/plugin-search@1.4.11-next.2 + - @backstage/plugin-org@0.6.25-next.2 + - @backstage/plugin-search-react@1.7.11-next.1 + - @backstage/plugin-catalog-graph@0.4.5-next.2 + - @backstage/plugin-catalog-import@0.10.11-next.2 + - @backstage/plugin-home@0.7.4-next.2 + - @backstage/plugin-kubernetes@0.11.10-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.11-next.2 + - @backstage/plugin-scaffolder-react@1.8.5-next.2 + - @backstage/plugin-user-settings@0.8.6-next.2 + - @backstage/integration-react@1.1.27-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.10-next.2 + +## 0.2.97-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.8.5-next.1 + - @backstage/plugin-scaffolder@1.19.4-next.1 + - @backstage/plugin-notifications@0.2.1-next.1 + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog@1.19.1-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + - @backstage/app-defaults@1.5.5-next.1 + - @backstage/cli@0.26.5-next.0 + - @backstage/frontend-app-api@0.6.5-next.1 + - @backstage/integration-react@1.1.26 + - @backstage/plugin-api-docs@0.11.5-next.1 + - @backstage/plugin-auth-react@0.1.2-next.1 + - @backstage/plugin-catalog-graph@0.4.5-next.1 + - @backstage/plugin-catalog-import@0.10.11-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.4-next.1 + - @backstage/plugin-devtools@0.1.14-next.1 + - @backstage/plugin-home@0.7.4-next.1 + - @backstage/plugin-kubernetes@0.11.10-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.11-next.1 + - @backstage/plugin-org@0.6.25-next.1 + - @backstage/plugin-search@1.4.11-next.1 + - @backstage/plugin-search-react@1.7.11-next.1 + - @backstage/plugin-signals@0.0.6-next.1 + - @backstage/plugin-techdocs@1.10.5-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.10-next.1 + - @backstage/plugin-techdocs-react@1.2.4-next.1 + - @backstage/plugin-user-settings@0.8.6-next.1 + +## 0.2.97-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-notifications@0.2.1-next.0 + - @backstage/plugin-api-docs@0.11.5-next.0 + - @backstage/plugin-catalog@1.19.1-next.0 + - @backstage/plugin-home@0.7.4-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-techdocs@1.10.5-next.0 + - @backstage/cli@0.26.5-next.0 + - @backstage/plugin-auth-react@0.1.1-next.0 + - @backstage/theme@0.5.4-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-scaffolder-react@1.8.5-next.0 + - @backstage/plugin-scaffolder@1.19.4-next.0 + - @backstage/plugin-catalog-graph@0.4.5-next.0 + - @backstage/plugin-catalog-import@0.10.11-next.0 + - @backstage/plugin-devtools@0.1.14-next.0 + - @backstage/plugin-org@0.6.25-next.0 + - @backstage/plugin-search@1.4.11-next.0 + - @backstage/plugin-user-settings@0.8.6-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.4-next.0 + - @backstage/plugin-kubernetes@0.11.10-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.11-next.0 + - @backstage/plugin-techdocs-react@1.2.4-next.0 + - @backstage/app-defaults@1.5.5-next.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.12.4 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-app-api@0.6.5-next.0 + - @backstage/integration-react@1.1.26 + - @backstage/plugin-permission-react@0.4.22 + - @backstage/plugin-search-common@1.2.11 + - @backstage/plugin-search-react@1.7.11-next.0 + - @backstage/plugin-signals@0.0.6-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.10-next.0 + +## 0.2.96 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-github-pull-requests-board@0.2.0 + - @backstage/plugin-azure-devops@0.4.3 + - @backstage/plugin-catalog@1.19.0 + - @backstage/plugin-techdocs-react@1.2.3 + - @backstage/plugin-notifications@0.2.0 + - @backstage/plugin-github-actions@0.6.15 + - @backstage/cli@0.26.3 + - @backstage/plugin-search-react@1.7.10 + - @backstage/plugin-auth-react@0.1.0 + - @backstage/plugin-catalog-react@1.11.3 + - @backstage/plugin-gocd@0.1.40 + - @backstage/frontend-app-api@0.6.4 + - @backstage/plugin-scaffolder@1.19.3 + - @backstage/plugin-graphiql@0.3.7 + - @backstage/core-components@0.14.4 + - @backstage/plugin-cloudbuild@0.5.1 + - @backstage/core-app-api@1.12.4 + - @backstage/app-defaults@1.5.4 + - @backstage/core-plugin-api@1.9.2 + - @backstage/theme@0.5.3 + - @backstage/plugin-adr@0.6.17 + - @backstage/plugin-airbrake@0.3.34 + - @backstage/plugin-apache-airflow@0.2.24 + - @backstage/plugin-api-docs@0.11.4 + - @backstage/plugin-azure-sites@0.1.23 + - @backstage/plugin-catalog-graph@0.4.4 + - @backstage/plugin-catalog-import@0.10.10 + - @backstage/plugin-catalog-unprocessed-entities@0.2.3 + - @backstage/plugin-code-coverage@0.2.27 + - @backstage/plugin-cost-insights@0.12.23 + - @backstage/plugin-dynatrace@10.0.3 + - @backstage/plugin-entity-feedback@0.2.17 + - @backstage/plugin-explore@0.4.20 + - @backstage/plugin-gcalendar@0.3.27 + - @backstage/plugin-gcp-projects@0.3.50 + - @backstage/plugin-home@0.7.3 + - @backstage/plugin-jenkins@0.9.9 + - @backstage/plugin-kafka@0.3.34 + - @backstage/plugin-kubernetes-cluster@0.0.10 + - @backstage/plugin-kubernetes@0.11.9 + - @backstage/plugin-lighthouse@0.4.19 + - @backstage/plugin-microsoft-calendar@0.1.16 + - @backstage/plugin-newrelic@0.3.49 + - @backstage/plugin-nomad@0.1.15 + - @backstage/plugin-octopus-deploy@0.2.16 + - @backstage/plugin-org@0.6.24 + - @backstage/plugin-pagerduty@0.7.6 + - @backstage/plugin-permission-react@0.4.22 + - @backstage/plugin-playlist@0.2.8 + - @backstage/plugin-puppetdb@0.1.17 + - @backstage/plugin-rollbar@0.4.34 + - @backstage/plugin-scaffolder-react@1.8.4 + - @backstage/plugin-search@1.4.10 + - @backstage/plugin-sentry@0.5.19 + - @backstage/plugin-shortcuts@0.3.23 + - @backstage/plugin-signals@0.0.5 + - @backstage/plugin-stack-overflow@0.1.29 + - @backstage/plugin-stackstorm@0.1.15 + - @backstage/plugin-tech-insights@0.3.26 + - @backstage/plugin-tech-radar@0.7.3 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.9 + - @backstage/plugin-techdocs@1.10.4 + - @backstage/plugin-todo@0.2.38 + - @backstage/plugin-user-settings@0.8.5 + - @backstage/integration-react@1.1.26 + - @backstage/plugin-badges@0.2.58 + - @backstage/catalog-model@1.4.5 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.22 + - @backstage/plugin-devtools@0.1.13 + - @backstage/plugin-linguist@0.1.19 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-newrelic-dashboard@0.3.9 + - @backstage/plugin-search-common@1.2.11 + +## 0.2.96-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-azure-devops@0.4.3-next.1 + - @backstage/plugin-catalog@1.19.0-next.1 + - @backstage/cli@0.26.3-next.1 + - @backstage/plugin-auth-react@0.1.0-next.1 + - @backstage/plugin-scaffolder@1.19.3-next.1 + - @backstage/core-app-api@1.12.4-next.0 + - @backstage/frontend-app-api@0.6.4-next.1 + - @backstage/plugin-scaffolder-react@1.8.4-next.1 + - @backstage/plugin-cost-insights@0.12.23-next.1 + - @backstage/plugin-signals@0.0.5-next.1 + - @backstage/plugin-notifications@0.2.0-next.1 + - @backstage/plugin-api-docs@0.11.4-next.1 + - @backstage/plugin-catalog-graph@0.4.4-next.1 + - @backstage/plugin-explore@0.4.20-next.1 + - @backstage/plugin-org@0.6.24-next.1 + - @backstage/plugin-user-settings@0.8.5-next.1 + - @backstage/plugin-kubernetes@0.11.9-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.10-next.1 + - @backstage/app-defaults@1.5.4-next.1 + - @backstage/catalog-model@1.4.5 + - @backstage/config@1.2.0 + - @backstage/core-components@0.14.4-next.0 + - @backstage/core-plugin-api@1.9.1 + - @backstage/integration-react@1.1.26-next.0 + - @backstage/theme@0.5.2 + - @backstage/plugin-adr@0.6.17-next.1 + - @backstage/plugin-airbrake@0.3.34-next.1 + - @backstage/plugin-apache-airflow@0.2.24-next.0 + - @backstage/plugin-azure-sites@0.1.23-next.1 + - @backstage/plugin-badges@0.2.58-next.1 + - @backstage/plugin-catalog-common@1.0.22 + - @backstage/plugin-catalog-import@0.10.10-next.1 + - @backstage/plugin-catalog-react@1.11.3-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.3-next.0 + - @backstage/plugin-cloudbuild@0.5.1-next.1 + - @backstage/plugin-code-coverage@0.2.27-next.1 + - @backstage/plugin-devtools@0.1.13-next.1 + - @backstage/plugin-dynatrace@10.0.3-next.1 + - @backstage/plugin-entity-feedback@0.2.17-next.1 + - @backstage/plugin-gcalendar@0.3.27-next.0 + - @backstage/plugin-gcp-projects@0.3.50-next.0 + - @backstage/plugin-github-actions@0.6.15-next.1 + - @backstage/plugin-github-pull-requests-board@0.1.28-next.1 + - @backstage/plugin-gocd@0.1.40-next.1 + - @backstage/plugin-graphiql@0.3.7-next.1 + - @backstage/plugin-home@0.7.3-next.1 + - @backstage/plugin-jenkins@0.9.9-next.1 + - @backstage/plugin-kafka@0.3.34-next.1 + - @backstage/plugin-lighthouse@0.4.19-next.1 + - @backstage/plugin-linguist@0.1.19-next.1 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-microsoft-calendar@0.1.16-next.0 + - @backstage/plugin-newrelic@0.3.49-next.0 + - @backstage/plugin-newrelic-dashboard@0.3.9-next.1 + - @backstage/plugin-nomad@0.1.15-next.1 + - @backstage/plugin-octopus-deploy@0.2.16-next.1 + - @backstage/plugin-pagerduty@0.7.6-next.1 + - @backstage/plugin-permission-react@0.4.21 + - @backstage/plugin-playlist@0.2.8-next.1 + - @backstage/plugin-puppetdb@0.1.17-next.1 + - @backstage/plugin-rollbar@0.4.34-next.1 + - @backstage/plugin-search@1.4.10-next.1 + - @backstage/plugin-search-common@1.2.11 + - @backstage/plugin-search-react@1.7.10-next.1 + - @backstage/plugin-sentry@0.5.19-next.1 + - @backstage/plugin-shortcuts@0.3.23-next.0 + - @backstage/plugin-stack-overflow@0.1.29-next.1 + - @backstage/plugin-stackstorm@0.1.15-next.0 + - @backstage/plugin-tech-insights@0.3.26-next.1 + - @backstage/plugin-tech-radar@0.7.3-next.1 + - @backstage/plugin-techdocs@1.10.4-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.9-next.0 + - @backstage/plugin-techdocs-react@1.2.3-next.0 + - @backstage/plugin-todo@0.2.38-next.1 + +## 0.2.96-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-gocd@0.1.40-next.0 + - @backstage/plugin-scaffolder@1.19.3-next.0 + - @backstage/cli@0.26.3-next.0 + - @backstage/plugin-github-pull-requests-board@0.1.28-next.0 + - @backstage/core-components@0.14.4-next.0 + - @backstage/plugin-catalog@1.19.0-next.0 + - @backstage/app-defaults@1.5.4-next.0 + - @backstage/catalog-model@1.4.5 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.12.3 + - @backstage/core-plugin-api@1.9.1 + - @backstage/frontend-app-api@0.6.4-next.0 + - @backstage/integration-react@1.1.26-next.0 + - @backstage/theme@0.5.2 + - @backstage/plugin-adr@0.6.17-next.0 + - @backstage/plugin-airbrake@0.3.34-next.0 + - @backstage/plugin-apache-airflow@0.2.24-next.0 + - @backstage/plugin-api-docs@0.11.4-next.0 + - @backstage/plugin-azure-devops@0.4.3-next.0 + - @backstage/plugin-azure-sites@0.1.23-next.0 + - @backstage/plugin-badges@0.2.58-next.0 + - @backstage/plugin-catalog-common@1.0.22 + - @backstage/plugin-catalog-graph@0.4.4-next.0 + - @backstage/plugin-catalog-import@0.10.10-next.0 + - @backstage/plugin-catalog-react@1.11.3-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.3-next.0 + - @backstage/plugin-cloudbuild@0.5.1-next.0 + - @backstage/plugin-code-coverage@0.2.27-next.0 + - @backstage/plugin-cost-insights@0.12.23-next.0 + - @backstage/plugin-devtools@0.1.13-next.0 + - @backstage/plugin-dynatrace@10.0.3-next.0 + - @backstage/plugin-entity-feedback@0.2.17-next.0 + - @backstage/plugin-explore@0.4.20-next.0 + - @backstage/plugin-gcalendar@0.3.27-next.0 + - @backstage/plugin-gcp-projects@0.3.50-next.0 + - @backstage/plugin-github-actions@0.6.15-next.0 + - @backstage/plugin-graphiql@0.3.7-next.0 + - @backstage/plugin-home@0.7.3-next.0 + - @backstage/plugin-jenkins@0.9.9-next.0 + - @backstage/plugin-kafka@0.3.34-next.0 + - @backstage/plugin-kubernetes@0.11.9-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.10-next.0 + - @backstage/plugin-lighthouse@0.4.19-next.0 + - @backstage/plugin-linguist@0.1.19-next.0 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-microsoft-calendar@0.1.16-next.0 + - @backstage/plugin-newrelic@0.3.49-next.0 + - @backstage/plugin-newrelic-dashboard@0.3.9-next.0 + - @backstage/plugin-nomad@0.1.15-next.0 + - @backstage/plugin-notifications@0.1.3-next.0 + - @backstage/plugin-octopus-deploy@0.2.16-next.0 + - @backstage/plugin-org@0.6.24-next.0 + - @backstage/plugin-pagerduty@0.7.6-next.0 + - @backstage/plugin-permission-react@0.4.21 + - @backstage/plugin-playlist@0.2.8-next.0 + - @backstage/plugin-puppetdb@0.1.17-next.0 + - @backstage/plugin-rollbar@0.4.34-next.0 + - @backstage/plugin-scaffolder-react@1.8.4-next.0 + - @backstage/plugin-search@1.4.10-next.0 + - @backstage/plugin-search-common@1.2.11 + - @backstage/plugin-search-react@1.7.10-next.0 + - @backstage/plugin-sentry@0.5.19-next.0 + - @backstage/plugin-shortcuts@0.3.23-next.0 + - @backstage/plugin-signals@0.0.5-next.0 + - @backstage/plugin-stack-overflow@0.1.29-next.0 + - @backstage/plugin-stackstorm@0.1.15-next.0 + - @backstage/plugin-tech-insights@0.3.26-next.0 + - @backstage/plugin-tech-radar@0.7.3-next.0 + - @backstage/plugin-techdocs@1.10.4-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.9-next.0 + - @backstage/plugin-techdocs-react@1.2.3-next.0 + - @backstage/plugin-todo@0.2.38-next.0 + - @backstage/plugin-user-settings@0.8.5-next.0 + +## 0.2.95 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-nomad@0.1.14 + - @backstage/plugin-newrelic@0.3.48 + - @backstage/plugin-tech-radar@0.7.2 + - @backstage/plugin-entity-feedback@0.2.16 + - @backstage/plugin-pagerduty@0.7.5 + - @backstage/plugin-microsoft-calendar@0.1.15 + - @backstage/plugin-gcp-projects@0.3.49 + - @backstage/plugin-newrelic-dashboard@0.3.8 + - @backstage/plugin-catalog-unprocessed-entities@0.2.2 + - @backstage/plugin-kubernetes-cluster@0.0.9 + - @backstage/core-components@0.14.3 + - @backstage/plugin-scaffolder-react@1.8.3 + - @backstage/plugin-apache-airflow@0.2.23 + - @backstage/plugin-catalog-import@0.10.9 + - @backstage/plugin-github-actions@0.6.14 + - @backstage/plugin-octopus-deploy@0.2.15 + - @backstage/plugin-stack-overflow@0.1.28 + - @backstage/plugin-techdocs-react@1.2.2 + - @backstage/core-app-api@1.12.3 + - @backstage/plugin-catalog-graph@0.4.3 + - @backstage/plugin-catalog-react@1.11.2 + - @backstage/plugin-code-coverage@0.2.26 + - @backstage/plugin-cost-insights@0.12.22 + - @backstage/plugin-notifications@0.1.2 + - @backstage/plugin-tech-insights@0.3.25 + - @backstage/plugin-user-settings@0.8.4 + - @backstage/plugin-azure-devops@0.4.2 + - @backstage/plugin-search-react@1.7.9 + - @backstage/plugin-azure-sites@0.1.22 + - @backstage/plugin-cloudbuild@0.5.0 + - @backstage/plugin-lighthouse@0.4.18 + - @backstage/plugin-scaffolder@1.19.2 + - @backstage/plugin-stackstorm@0.1.14 + - @backstage/plugin-dynatrace@10.0.2 + - @backstage/plugin-gcalendar@0.3.26 + - @backstage/plugin-shortcuts@0.3.22 + - @backstage/plugin-airbrake@0.3.33 + - @backstage/plugin-devtools@0.1.12 + - @backstage/plugin-graphiql@0.3.6 + - @backstage/plugin-linguist@0.1.18 + - @backstage/plugin-playlist@0.2.7 + - @backstage/plugin-puppetdb@0.1.16 + - @backstage/plugin-techdocs@1.10.3 + - @backstage/plugin-catalog@1.18.2 + - @backstage/plugin-explore@0.4.19 + - @backstage/plugin-jenkins@0.9.8 + - @backstage/plugin-rollbar@0.4.33 + - @backstage/plugin-badges@0.2.57 + - @backstage/plugin-search@1.4.9 + - @backstage/plugin-sentry@0.5.18 + - @backstage/plugin-kafka@0.3.33 + - @backstage/plugin-gocd@0.1.39 + - @backstage/plugin-home@0.7.2 + - @backstage/plugin-adr@0.6.16 + - @backstage/plugin-org@0.6.23 + - @backstage/cli@0.26.2 + - @backstage/app-defaults@1.5.3 + - @backstage/frontend-app-api@0.6.3 + - @backstage/integration-react@1.1.25 + - @backstage/plugin-api-docs@0.11.3 + - @backstage/plugin-github-pull-requests-board@0.1.27 + - @backstage/plugin-kubernetes@0.11.8 + - @backstage/plugin-signals@0.0.4 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.8 + - @backstage/plugin-todo@0.2.37 + - @backstage/core-plugin-api@1.9.1 + - @backstage/catalog-model@1.4.5 + - @backstage/config@1.2.0 + - @backstage/theme@0.5.2 + - @backstage/plugin-catalog-common@1.0.22 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-permission-react@0.4.21 + - @backstage/plugin-search-common@1.2.11 + +## 0.2.94 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-nomad@0.1.13 + - @backstage/plugin-newrelic@0.3.47 + - @backstage/plugin-tech-radar@0.7.1 + - @backstage/plugin-entity-feedback@0.2.15 + - @backstage/plugin-pagerduty@0.7.4 + - @backstage/plugin-microsoft-calendar@0.1.14 + - @backstage/plugin-gcp-projects@0.3.48 + - @backstage/plugin-newrelic-dashboard@0.3.7 + - @backstage/plugin-catalog-unprocessed-entities@0.2.1 + - @backstage/plugin-kubernetes-cluster@0.0.8 + - @backstage/core-components@0.14.2 + - @backstage/plugin-scaffolder-react@1.8.2 + - @backstage/plugin-apache-airflow@0.2.22 + - @backstage/plugin-catalog-import@0.10.8 + - @backstage/plugin-github-actions@0.6.13 + - @backstage/plugin-octopus-deploy@0.2.14 + - @backstage/plugin-stack-overflow@0.1.27 + - @backstage/plugin-techdocs-react@1.2.1 + - @backstage/core-app-api@1.12.2 + - @backstage/plugin-catalog-graph@0.4.2 + - @backstage/plugin-catalog-react@1.11.1 + - @backstage/plugin-code-coverage@0.2.25 + - @backstage/plugin-cost-insights@0.12.21 + - @backstage/plugin-notifications@0.1.1 + - @backstage/plugin-tech-insights@0.3.24 + - @backstage/plugin-user-settings@0.8.3 + - @backstage/plugin-azure-devops@0.4.1 + - @backstage/plugin-search-react@1.7.8 + - @backstage/plugin-azure-sites@0.1.21 + - @backstage/plugin-cloudbuild@0.4.2 + - @backstage/plugin-lighthouse@0.4.17 + - @backstage/plugin-scaffolder@1.19.1 + - @backstage/plugin-stackstorm@0.1.13 + - @backstage/plugin-dynatrace@10.0.1 + - @backstage/plugin-gcalendar@0.3.25 + - @backstage/plugin-shortcuts@0.3.21 + - @backstage/plugin-airbrake@0.3.32 + - @backstage/plugin-devtools@0.1.11 + - @backstage/plugin-graphiql@0.3.5 + - @backstage/plugin-linguist@0.1.17 + - @backstage/plugin-playlist@0.2.6 + - @backstage/plugin-puppetdb@0.1.15 + - @backstage/plugin-techdocs@1.10.2 + - @backstage/plugin-catalog@1.18.1 + - @backstage/plugin-explore@0.4.18 + - @backstage/plugin-jenkins@0.9.7 + - @backstage/plugin-rollbar@0.4.32 + - @backstage/plugin-badges@0.2.56 + - @backstage/plugin-search@1.4.8 + - @backstage/plugin-sentry@0.5.17 + - @backstage/plugin-kafka@0.3.32 + - @backstage/plugin-gocd@0.1.38 + - @backstage/plugin-home@0.7.1 + - @backstage/plugin-adr@0.6.15 + - @backstage/plugin-org@0.6.22 + - @backstage/cli@0.26.1 + - @backstage/app-defaults@1.5.2 + - @backstage/frontend-app-api@0.6.2 + - @backstage/integration-react@1.1.25 + - @backstage/plugin-api-docs@0.11.2 + - @backstage/plugin-github-pull-requests-board@0.1.26 + - @backstage/plugin-kubernetes@0.11.7 + - @backstage/plugin-signals@0.0.3 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.7 + - @backstage/plugin-todo@0.2.36 + - @backstage/core-plugin-api@1.9.1 + - @backstage/catalog-model@1.4.5 + - @backstage/config@1.2.0 + - @backstage/theme@0.5.2 + - @backstage/plugin-catalog-common@1.0.22 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-permission-react@0.4.21 + - @backstage/plugin-search-common@1.2.11 + +## 0.2.93 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-apache-airflow@0.2.21 + - @backstage/plugin-catalog-import@0.10.7 + - @backstage/cli@0.26.0 + - @backstage/plugin-shortcuts@0.3.20 + - @backstage/plugin-github-pull-requests-board@0.1.25 + - @backstage/plugin-techdocs@1.10.1 + - @backstage/config@1.2.0 + - @backstage/plugin-adr@0.6.14 + - @backstage/plugin-api-docs@0.11.1 + - @backstage/core-components@0.14.1 + - @backstage/plugin-notifications@0.1.0 + - @backstage/plugin-stack-overflow@0.1.26 + - @backstage/plugin-rollbar@0.4.31 + - @backstage/plugin-search@1.4.7 + - @backstage/theme@0.5.2 + - @backstage/plugin-explore@0.4.17 + - @backstage/integration-react@1.1.25 + - @backstage/plugin-catalog@1.18.0 + - @backstage/plugin-github-actions@0.6.12 + - @backstage/plugin-dynatrace@10.0.0 + - @backstage/plugin-scaffolder@1.19.0 + - @backstage/plugin-techdocs-react@1.2.0 + - @backstage/plugin-gcalendar@0.3.24 + - @backstage/plugin-airbrake@0.3.31 + - @backstage/plugin-catalog-react@1.11.0 + - @backstage/plugin-azure-devops@0.4.0 + - @backstage/plugin-user-settings@0.8.2 + - @backstage/plugin-org@0.6.21 + - @backstage/plugin-linguist@0.1.16 + - @backstage/plugin-badges@0.2.55 + - @backstage/plugin-cloudbuild@0.4.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.0 + - @backstage/plugin-tech-radar@0.7.0 + - @backstage/frontend-app-api@0.6.1 + - @backstage/plugin-search-common@1.2.11 + - @backstage/plugin-devtools@0.1.10 + - @backstage/plugin-cost-insights@0.12.20 + - @backstage/plugin-entity-feedback@0.2.14 + - @backstage/plugin-kubernetes@0.11.6 + - @backstage/plugin-stackstorm@0.1.12 + - @backstage/plugin-scaffolder-react@1.8.1 + - @backstage/plugin-azure-sites@0.1.20 + - @backstage/plugin-playlist@0.2.5 + - @backstage/plugin-home@0.7.0 + - @backstage/plugin-signals@0.0.2 + - @backstage/plugin-lighthouse@0.4.16 + - @backstage/plugin-sentry@0.5.16 + - @backstage/plugin-tech-insights@0.3.23 + - @backstage/plugin-octopus-deploy@0.2.13 + - @backstage/plugin-catalog-graph@0.4.1 + - @backstage/plugin-kafka@0.3.31 + - @backstage/plugin-jenkins@0.9.6 + - @backstage/plugin-code-coverage@0.2.24 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.6 + - @backstage/app-defaults@1.5.1 + - @backstage/catalog-model@1.4.5 + - @backstage/core-app-api@1.12.1 + - @backstage/core-plugin-api@1.9.1 + - @backstage/plugin-catalog-common@1.0.22 + - @backstage/plugin-gcp-projects@0.3.47 + - @backstage/plugin-gocd@0.1.37 + - @backstage/plugin-graphiql@0.3.4 + - @backstage/plugin-kubernetes-cluster@0.0.7 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-microsoft-calendar@0.1.13 + - @backstage/plugin-newrelic@0.3.46 + - @backstage/plugin-newrelic-dashboard@0.3.6 + - @backstage/plugin-nomad@0.1.12 + - @backstage/plugin-pagerduty@0.7.3 + - @backstage/plugin-permission-react@0.4.21 + - @backstage/plugin-puppetdb@0.1.14 + - @backstage/plugin-search-react@1.7.7 + - @backstage/plugin-todo@0.2.35 + +## 0.2.93-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.10.1-next.2 + - @backstage/plugin-search@1.4.7-next.2 + - @backstage/plugin-scaffolder@1.19.0-next.2 + - @backstage/plugin-techdocs-react@1.2.0-next.2 + - @backstage/plugin-notifications@0.1.0-next.2 + - @backstage/core-components@0.14.1-next.2 + - @backstage/plugin-tech-radar@0.7.0-next.2 + - @backstage/plugin-catalog-react@1.11.0-next.2 + - @backstage/plugin-azure-devops@0.4.0-next.2 + - @backstage/plugin-devtools@0.1.10-next.2 + - @backstage/plugin-azure-sites@0.1.20-next.2 + - @backstage/plugin-catalog@1.18.0-next.2 + - @backstage/plugin-linguist@0.1.16-next.2 + - @backstage/plugin-catalog-import@0.10.7-next.2 + - @backstage/plugin-home@0.7.0-next.2 + - @backstage/plugin-github-actions@0.6.12-next.2 + - @backstage/cli@0.25.3-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.6-next.2 + - @backstage/integration-react@1.1.25-next.2 + - @backstage/plugin-github-pull-requests-board@0.1.25-next.2 + - @backstage/app-defaults@1.5.1-next.2 + - @backstage/frontend-app-api@0.6.1-next.2 + - @backstage/plugin-adr@0.6.14-next.2 + - @backstage/plugin-airbrake@0.3.31-next.2 + - @backstage/plugin-apache-airflow@0.2.21-next.2 + - @backstage/plugin-api-docs@0.11.1-next.2 + - @backstage/plugin-badges@0.2.55-next.2 + - @backstage/plugin-catalog-graph@0.4.1-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.1.9-next.2 + - @backstage/plugin-cloudbuild@0.4.1-next.2 + - @backstage/plugin-code-coverage@0.2.24-next.2 + - @backstage/plugin-cost-insights@0.12.20-next.2 + - @backstage/plugin-dynatrace@10.0.0-next.2 + - @backstage/plugin-entity-feedback@0.2.14-next.2 + - @backstage/plugin-explore@0.4.17-next.2 + - @backstage/plugin-gcalendar@0.3.24-next.2 + - @backstage/plugin-gcp-projects@0.3.47-next.2 + - @backstage/plugin-gocd@0.1.37-next.2 + - @backstage/plugin-graphiql@0.3.4-next.2 + - @backstage/plugin-jenkins@0.9.6-next.2 + - @backstage/plugin-kafka@0.3.31-next.2 + - @backstage/plugin-kubernetes@0.11.6-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.7-next.2 + - @backstage/plugin-lighthouse@0.4.16-next.2 + - @backstage/plugin-microsoft-calendar@0.1.13-next.2 + - @backstage/plugin-newrelic@0.3.46-next.2 + - @backstage/plugin-newrelic-dashboard@0.3.6-next.2 + - @backstage/plugin-nomad@0.1.12-next.2 + - @backstage/plugin-octopus-deploy@0.2.13-next.2 + - @backstage/plugin-org@0.6.21-next.2 + - @backstage/plugin-pagerduty@0.7.3-next.2 + - @backstage/plugin-playlist@0.2.5-next.2 + - @backstage/plugin-puppetdb@0.1.14-next.2 + - @backstage/plugin-rollbar@0.4.31-next.2 + - @backstage/plugin-scaffolder-react@1.8.1-next.2 + - @backstage/plugin-search-react@1.7.7-next.2 + - @backstage/plugin-sentry@0.5.16-next.2 + - @backstage/plugin-shortcuts@0.3.20-next.2 + - @backstage/plugin-signals@0.0.2-next.2 + - @backstage/plugin-stack-overflow@0.1.26-next.2 + - @backstage/plugin-stackstorm@0.1.12-next.2 + - @backstage/plugin-tech-insights@0.3.23-next.2 + - @backstage/plugin-todo@0.2.35-next.2 + - @backstage/plugin-user-settings@0.8.2-next.2 + - @backstage/catalog-model@1.4.5-next.0 + - @backstage/config@1.2.0-next.1 + - @backstage/core-app-api@1.12.1-next.1 + - @backstage/core-plugin-api@1.9.1-next.1 + - @backstage/theme@0.5.2-next.0 + - @backstage/plugin-catalog-common@1.0.22-next.1 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-permission-react@0.4.21-next.1 + - @backstage/plugin-search-common@1.2.11-next.1 + +## 0.2.93-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.2.0-next.1 + - @backstage/core-components@0.14.1-next.1 + - @backstage/plugin-github-actions@0.6.12-next.1 + - @backstage/cli@0.25.3-next.1 + - @backstage/plugin-catalog-react@1.10.1-next.1 + - @backstage/plugin-entity-feedback@0.2.14-next.1 + - @backstage/plugin-api-docs@0.11.1-next.1 + - @backstage/plugin-notifications@0.1.0-next.1 + - @backstage/plugin-catalog@1.17.1-next.1 + - @backstage/plugin-azure-devops@0.4.0-next.1 + - @backstage/plugin-org@0.6.21-next.1 + - @backstage/core-app-api@1.12.1-next.1 + - @backstage/core-plugin-api@1.9.1-next.1 + - @backstage/frontend-app-api@0.6.1-next.1 + - @backstage/integration-react@1.1.25-next.1 + - @backstage/plugin-catalog-import@0.10.7-next.1 + - @backstage/plugin-cost-insights@0.12.20-next.1 + - @backstage/plugin-home@0.6.3-next.1 + - @backstage/plugin-permission-react@0.4.21-next.1 + - @backstage/plugin-stack-overflow@0.1.26-next.1 + - @backstage/plugin-techdocs@1.10.1-next.1 + - @backstage/plugin-techdocs-react@1.1.17-next.1 + - @backstage/app-defaults@1.5.1-next.1 + - @backstage/plugin-adr@0.6.14-next.1 + - @backstage/plugin-airbrake@0.3.31-next.1 + - @backstage/plugin-apache-airflow@0.2.21-next.1 + - @backstage/plugin-azure-sites@0.1.20-next.1 + - @backstage/plugin-badges@0.2.55-next.1 + - @backstage/plugin-catalog-graph@0.4.1-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.1.9-next.1 + - @backstage/plugin-cloudbuild@0.4.1-next.1 + - @backstage/plugin-code-coverage@0.2.24-next.1 + - @backstage/plugin-devtools@0.1.10-next.1 + - @backstage/plugin-dynatrace@9.0.1-next.1 + - @backstage/plugin-explore@0.4.17-next.1 + - @backstage/plugin-gcalendar@0.3.24-next.1 + - @backstage/plugin-gcp-projects@0.3.47-next.1 + - @backstage/plugin-github-pull-requests-board@0.1.25-next.1 + - @backstage/plugin-gocd@0.1.37-next.1 + - @backstage/plugin-graphiql@0.3.4-next.1 + - @backstage/plugin-jenkins@0.9.6-next.1 + - @backstage/plugin-kafka@0.3.31-next.1 + - @backstage/plugin-kubernetes@0.11.6-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.7-next.1 + - @backstage/plugin-lighthouse@0.4.16-next.1 + - @backstage/plugin-linguist@0.1.16-next.1 + - @backstage/plugin-microsoft-calendar@0.1.13-next.1 + - @backstage/plugin-newrelic@0.3.46-next.1 + - @backstage/plugin-newrelic-dashboard@0.3.6-next.1 + - @backstage/plugin-nomad@0.1.12-next.1 + - @backstage/plugin-octopus-deploy@0.2.13-next.1 + - @backstage/plugin-pagerduty@0.7.3-next.1 + - @backstage/plugin-playlist@0.2.5-next.1 + - @backstage/plugin-puppetdb@0.1.14-next.1 + - @backstage/plugin-rollbar@0.4.31-next.1 + - @backstage/plugin-scaffolder@1.18.1-next.1 + - @backstage/plugin-scaffolder-react@1.8.1-next.1 + - @backstage/plugin-search@1.4.7-next.1 + - @backstage/plugin-search-react@1.7.7-next.1 + - @backstage/plugin-sentry@0.5.16-next.1 + - @backstage/plugin-shortcuts@0.3.20-next.1 + - @backstage/plugin-signals@0.0.2-next.1 + - @backstage/plugin-stackstorm@0.1.12-next.1 + - @backstage/plugin-tech-insights@0.3.23-next.1 + - @backstage/plugin-tech-radar@0.6.14-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.6-next.1 + - @backstage/plugin-todo@0.2.35-next.1 + - @backstage/plugin-user-settings@0.8.2-next.1 + - @backstage/catalog-model@1.4.5-next.0 + - @backstage/theme@0.5.2-next.0 + - @backstage/plugin-catalog-common@1.0.22-next.1 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.11-next.1 + +## 0.2.93-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-github-pull-requests-board@0.1.25-next.0 + - @backstage/plugin-adr@0.6.14-next.0 + - @backstage/plugin-stack-overflow@0.1.26-next.0 + - @backstage/theme@0.5.2-next.0 + - @backstage/core-components@0.14.1-next.0 + - @backstage/plugin-notifications@0.1.0-next.0 + - @backstage/integration-react@1.1.25-next.0 + - @backstage/cli@0.25.3-next.0 + - @backstage/plugin-catalog-react@1.10.1-next.0 + - @backstage/plugin-azure-devops@0.4.0-next.0 + - @backstage/plugin-linguist@0.1.16-next.0 + - @backstage/plugin-catalog@1.17.1-next.0 + - @backstage/plugin-org@0.6.21-next.0 + - @backstage/plugin-search-common@1.2.11-next.0 + - @backstage/plugin-search@1.4.7-next.0 + - @backstage/plugin-devtools@0.1.10-next.0 + - @backstage/plugin-tech-radar@0.6.14-next.0 + - @backstage/plugin-scaffolder-react@1.8.1-next.0 + - @backstage/plugin-api-docs@0.11.1-next.0 + - @backstage/plugin-cost-insights@0.12.20-next.0 + - @backstage/plugin-home@0.6.3-next.0 + - @backstage/plugin-scaffolder@1.18.1-next.0 + - @backstage/plugin-shortcuts@0.3.20-next.0 + - @backstage/plugin-signals@0.0.2-next.0 + - @backstage/plugin-catalog-import@0.10.7-next.0 + - @backstage/catalog-model@1.4.5-next.0 + - @backstage/config@1.1.2-next.0 + - @backstage/core-plugin-api@1.9.1-next.0 + - @backstage/frontend-app-api@0.6.1-next.0 + - @backstage/plugin-badges@0.2.55-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.1.9-next.0 + - @backstage/plugin-code-coverage@0.2.24-next.0 + - @backstage/plugin-entity-feedback@0.2.14-next.0 + - @backstage/plugin-explore@0.4.17-next.0 + - @backstage/plugin-gcalendar@0.3.24-next.0 + - @backstage/plugin-gocd@0.1.37-next.0 + - @backstage/plugin-jenkins@0.9.6-next.0 + - @backstage/plugin-microsoft-calendar@0.1.13-next.0 + - @backstage/plugin-newrelic-dashboard@0.3.6-next.0 + - @backstage/plugin-pagerduty@0.7.3-next.0 + - @backstage/plugin-playlist@0.2.5-next.0 + - @backstage/plugin-puppetdb@0.1.14-next.0 + - @backstage/plugin-stackstorm@0.1.12-next.0 + - @backstage/plugin-tech-insights@0.3.23-next.0 + - @backstage/plugin-techdocs@1.10.1-next.0 + - @backstage/plugin-todo@0.2.35-next.0 + - @backstage/plugin-user-settings@0.8.2-next.0 + - @backstage/app-defaults@1.5.1-next.0 + - @backstage/plugin-azure-sites@0.1.20-next.0 + - @backstage/plugin-search-react@1.7.7-next.0 + - @backstage/plugin-techdocs-react@1.1.17-next.0 + - @backstage/plugin-airbrake@0.3.31-next.0 + - @backstage/plugin-apache-airflow@0.2.21-next.0 + - @backstage/plugin-catalog-graph@0.4.1-next.0 + - @backstage/plugin-cloudbuild@0.4.1-next.0 + - @backstage/plugin-dynatrace@9.0.1-next.0 + - @backstage/plugin-gcp-projects@0.3.47-next.0 + - @backstage/plugin-github-actions@0.6.12-next.0 + - @backstage/plugin-graphiql@0.3.4-next.0 + - @backstage/plugin-kafka@0.3.31-next.0 + - @backstage/plugin-kubernetes@0.11.6-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.7-next.0 + - @backstage/plugin-lighthouse@0.4.16-next.0 + - @backstage/plugin-newrelic@0.3.46-next.0 + - @backstage/plugin-nomad@0.1.12-next.0 + - @backstage/plugin-octopus-deploy@0.2.13-next.0 + - @backstage/plugin-rollbar@0.4.31-next.0 + - @backstage/plugin-sentry@0.5.16-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.6-next.0 + - @backstage/core-app-api@1.12.1-next.0 + - @backstage/plugin-catalog-common@1.0.22-next.0 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-permission-react@0.4.21-next.0 + +## 0.2.92 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-azure-devops@0.3.12 + - @backstage/frontend-app-api@0.6.0 + - @backstage/plugin-scaffolder-react@1.8.0 + - @backstage/plugin-catalog-react@1.10.0 + - @backstage/plugin-catalog-import@0.10.6 + - @backstage/plugin-catalog-graph@0.4.0 + - @backstage/plugin-catalog@1.17.0 + - @backstage/plugin-azure-sites@0.1.19 + - @backstage/plugin-api-docs@0.11.0 + - @backstage/plugin-adr@0.6.13 + - @backstage/app-defaults@1.5.0 + - @backstage/plugin-gcp-projects@0.3.46 + - @backstage/plugin-airbrake@0.3.30 + - @backstage/plugin-scaffolder@1.18.0 + - @backstage/plugin-devtools@0.1.9 + - @backstage/cli@0.25.2 + - @backstage/core-components@0.14.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.5 + - @backstage/plugin-signals@0.0.1 + - @backstage/plugin-kubernetes@0.11.5 + - @backstage/plugin-notifications@0.0.1 + - @backstage/plugin-catalog-unprocessed-entities@0.1.8 + - @backstage/plugin-microsoft-calendar@0.1.12 + - @backstage/plugin-github-actions@0.6.11 + - @backstage/plugin-octopus-deploy@0.2.12 + - @backstage/plugin-techdocs-react@1.1.16 + - @backstage/core-app-api@1.12.0 + - @backstage/plugin-code-coverage@0.2.23 + - @backstage/plugin-cost-insights@0.12.19 + - @backstage/plugin-tech-insights@0.3.22 + - @backstage/plugin-lighthouse@0.4.15 + - @backstage/plugin-tech-radar@0.6.13 + - @backstage/plugin-dynatrace@9.0.0 + - @backstage/plugin-shortcuts@0.3.19 + - @backstage/plugin-graphiql@0.3.3 + - @backstage/plugin-linguist@0.1.15 + - @backstage/plugin-newrelic@0.3.45 + - @backstage/plugin-jenkins@0.9.5 + - @backstage/plugin-rollbar@0.4.30 + - @backstage/plugin-badges@0.2.54 + - @backstage/plugin-sentry@0.5.15 + - @backstage/plugin-nomad@0.1.11 + - @backstage/plugin-home@0.6.2 + - @backstage/catalog-model@1.4.4 + - @backstage/plugin-explore@0.4.16 + - @backstage/plugin-user-settings@0.8.1 + - @backstage/plugin-techdocs@1.10.0 + - @backstage/plugin-search@1.4.6 + - @backstage/theme@0.5.1 + - @backstage/core-plugin-api@1.9.0 + - @backstage/plugin-playlist@0.2.4 + - @backstage/plugin-apache-airflow@0.2.20 + - @backstage/plugin-cloudbuild@0.4.0 + - @backstage/plugin-entity-feedback@0.2.13 + - @backstage/plugin-gcalendar@0.3.23 + - @backstage/plugin-kafka@0.3.30 + - @backstage/plugin-kubernetes-cluster@0.0.6 + - @backstage/plugin-newrelic-dashboard@0.3.5 + - @backstage/plugin-org@0.6.20 + - @backstage/plugin-pagerduty@0.7.2 + - @backstage/plugin-permission-react@0.4.20 + - @backstage/plugin-puppetdb@0.1.13 + - @backstage/plugin-search-react@1.7.6 + - @backstage/plugin-stack-overflow@0.1.25 + - @backstage/plugin-stackstorm@0.1.11 + - @backstage/plugin-gocd@0.1.36 + - @backstage/plugin-todo@0.2.34 + - @backstage/config@1.1.1 + - @backstage/integration-react@1.1.24 + - @backstage/plugin-catalog-common@1.0.21 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.10 + +## 0.2.92-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-api-docs@0.11.0-next.3 + - @backstage/cli@0.25.2-next.3 + - @backstage/plugin-kubernetes@0.11.5-next.3 + - @backstage/theme@0.5.1-next.1 + - @backstage/plugin-playlist@0.2.4-next.3 + - @backstage/plugin-catalog@1.17.0-next.3 + - @backstage/frontend-app-api@0.6.0-next.3 + - @backstage/plugin-notifications@0.0.1-next.1 + - @backstage/plugin-signals@0.0.1-next.3 + - @backstage/core-components@0.14.0-next.2 + - @backstage/plugin-catalog-react@1.10.0-next.3 + - @backstage/plugin-gcp-projects@0.3.46-next.2 + - @backstage/plugin-scaffolder-react@1.8.0-next.3 + - @backstage/plugin-scaffolder@1.18.0-next.3 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.5-next.3 + - @backstage/plugin-home@0.6.2-next.3 + - @backstage/plugin-gcalendar@0.3.23-next.2 + - @backstage/plugin-microsoft-calendar@0.1.12-next.2 + - @backstage/plugin-techdocs@1.10.0-next.3 + - @backstage/plugin-catalog-import@0.10.6-next.3 + - @backstage/plugin-github-actions@0.6.11-next.3 + - @backstage/plugin-dynatrace@9.0.0-next.3 + - @backstage/plugin-kubernetes-cluster@0.0.6-next.3 + - @backstage/app-defaults@1.5.0-next.3 + - @backstage/catalog-model@1.4.4-next.0 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.12.0-next.1 + - @backstage/core-plugin-api@1.9.0-next.1 + - @backstage/integration-react@1.1.24-next.2 + - @backstage/plugin-adr@0.6.13-next.3 + - @backstage/plugin-airbrake@0.3.30-next.3 + - @backstage/plugin-apache-airflow@0.2.20-next.2 + - @backstage/plugin-azure-devops@0.3.12-next.3 + - @backstage/plugin-azure-sites@0.1.19-next.3 + - @backstage/plugin-badges@0.2.54-next.3 + - @backstage/plugin-catalog-common@1.0.21-next.0 + - @backstage/plugin-catalog-graph@0.4.0-next.3 + - @backstage/plugin-catalog-unprocessed-entities@0.1.8-next.2 + - @backstage/plugin-cloudbuild@0.4.0-next.3 + - @backstage/plugin-code-coverage@0.2.23-next.3 + - @backstage/plugin-cost-insights@0.12.19-next.3 + - @backstage/plugin-devtools@0.1.9-next.3 + - @backstage/plugin-entity-feedback@0.2.13-next.3 + - @backstage/plugin-explore@0.4.16-next.3 + - @backstage/plugin-gocd@0.1.36-next.3 + - @backstage/plugin-graphiql@0.3.3-next.3 + - @backstage/plugin-jenkins@0.9.5-next.3 + - @backstage/plugin-kafka@0.3.30-next.3 + - @backstage/plugin-lighthouse@0.4.15-next.3 + - @backstage/plugin-linguist@0.1.15-next.3 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-newrelic@0.3.45-next.2 + - @backstage/plugin-newrelic-dashboard@0.3.5-next.3 + - @backstage/plugin-nomad@0.1.11-next.3 + - @backstage/plugin-octopus-deploy@0.2.12-next.3 + - @backstage/plugin-org@0.6.20-next.3 + - @backstage/plugin-pagerduty@0.7.2-next.3 + - @backstage/plugin-permission-react@0.4.20-next.1 + - @backstage/plugin-puppetdb@0.1.13-next.3 + - @backstage/plugin-rollbar@0.4.30-next.3 + - @backstage/plugin-search@1.4.6-next.3 + - @backstage/plugin-search-common@1.2.10 + - @backstage/plugin-search-react@1.7.6-next.3 + - @backstage/plugin-sentry@0.5.15-next.3 + - @backstage/plugin-shortcuts@0.3.19-next.2 + - @backstage/plugin-stack-overflow@0.1.25-next.3 + - @backstage/plugin-stackstorm@0.1.11-next.2 + - @backstage/plugin-tech-insights@0.3.22-next.3 + - @backstage/plugin-tech-radar@0.6.13-next.3 + - @backstage/plugin-techdocs-react@1.1.16-next.2 + - @backstage/plugin-todo@0.2.34-next.3 + - @backstage/plugin-user-settings@0.8.1-next.3 + +## 0.2.92-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-azure-devops@0.3.12-next.2 + - @backstage/plugin-azure-sites@0.1.19-next.2 + - @backstage/core-components@0.14.0-next.1 + - @backstage/plugin-catalog-graph@0.4.0-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.5-next.2 + - @backstage/plugin-signals@0.0.1-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.1.8-next.1 + - @backstage/plugin-microsoft-calendar@0.1.12-next.1 + - @backstage/plugin-catalog-import@0.10.6-next.2 + - @backstage/plugin-github-actions@0.6.11-next.2 + - @backstage/plugin-octopus-deploy@0.2.12-next.2 + - @backstage/plugin-techdocs-react@1.1.16-next.1 + - @backstage/app-defaults@1.5.0-next.2 + - @backstage/core-app-api@1.12.0-next.1 + - @backstage/plugin-code-coverage@0.2.23-next.2 + - @backstage/plugin-cost-insights@0.12.19-next.2 + - @backstage/plugin-tech-insights@0.3.22-next.2 + - @backstage/plugin-lighthouse@0.4.15-next.2 + - @backstage/plugin-scaffolder@1.18.0-next.2 + - @backstage/plugin-tech-radar@0.6.13-next.2 + - @backstage/plugin-dynatrace@9.0.0-next.2 + - @backstage/plugin-shortcuts@0.3.19-next.1 + - @backstage/plugin-airbrake@0.3.30-next.2 + - @backstage/plugin-api-docs@0.10.4-next.2 + - @backstage/plugin-devtools@0.1.9-next.2 + - @backstage/plugin-graphiql@0.3.3-next.2 + - @backstage/plugin-linguist@0.1.15-next.2 + - @backstage/plugin-newrelic@0.3.45-next.1 + - @backstage/plugin-catalog@1.17.0-next.2 + - @backstage/plugin-jenkins@0.9.5-next.2 + - @backstage/plugin-rollbar@0.4.30-next.2 + - @backstage/plugin-badges@0.2.54-next.2 + - @backstage/plugin-sentry@0.5.15-next.2 + - @backstage/plugin-nomad@0.1.11-next.2 + - @backstage/plugin-home@0.6.2-next.2 + - @backstage/core-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-react@1.10.0-next.2 + - @backstage/frontend-app-api@0.6.0-next.2 + - @backstage/plugin-adr@0.6.13-next.2 + - @backstage/plugin-apache-airflow@0.2.20-next.1 + - @backstage/plugin-cloudbuild@0.4.0-next.2 + - @backstage/plugin-entity-feedback@0.2.13-next.2 + - @backstage/plugin-explore@0.4.16-next.2 + - @backstage/plugin-gcalendar@0.3.23-next.1 + - @backstage/plugin-kafka@0.3.30-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.6-next.2 + - @backstage/plugin-kubernetes@0.11.5-next.2 + - @backstage/plugin-newrelic-dashboard@0.3.5-next.2 + - @backstage/plugin-org@0.6.20-next.2 + - @backstage/plugin-pagerduty@0.7.2-next.2 + - @backstage/plugin-permission-react@0.4.20-next.1 + - @backstage/plugin-playlist@0.2.4-next.2 + - @backstage/plugin-puppetdb@0.1.13-next.2 + - @backstage/plugin-scaffolder-react@1.8.0-next.2 + - @backstage/plugin-search-react@1.7.6-next.2 + - @backstage/plugin-search@1.4.6-next.2 + - @backstage/plugin-stack-overflow@0.1.25-next.2 + - @backstage/plugin-stackstorm@0.1.11-next.1 + - @backstage/plugin-techdocs@1.10.0-next.2 + - @backstage/plugin-user-settings@0.8.1-next.2 + - @backstage/theme@0.5.1-next.0 + - @backstage/cli@0.25.2-next.2 + - @backstage/plugin-notifications@0.0.1-next.0 + - @backstage/integration-react@1.1.24-next.1 + - @backstage/plugin-gcp-projects@0.3.46-next.1 + - @backstage/plugin-gocd@0.1.36-next.2 + - @backstage/plugin-todo@0.2.34-next.2 + - @backstage/config@1.1.1 + - @backstage/catalog-model@1.4.4-next.0 + - @backstage/plugin-catalog-common@1.0.21-next.0 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.10 + +## 0.2.92-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.6.0-next.1 + - @backstage/plugin-scaffolder-react@1.8.0-next.1 + - @backstage/plugin-adr@0.6.13-next.1 + - @backstage/plugin-catalog-graph@0.3.4-next.1 + - @backstage/core-components@0.14.0-next.0 + - @backstage/plugin-scaffolder@1.18.0-next.1 + - @backstage/cli@0.25.2-next.1 + - @backstage/catalog-model@1.4.4-next.0 + - @backstage/plugin-explore@0.4.16-next.1 + - @backstage/plugin-catalog@1.17.0-next.1 + - @backstage/core-plugin-api@1.8.3-next.0 + - @backstage/app-defaults@1.4.8-next.1 + - @backstage/core-app-api@1.11.4-next.0 + - @backstage/plugin-techdocs@1.10.0-next.1 + - @backstage/plugin-azure-sites@0.1.19-next.1 + - @backstage/plugin-home@0.6.2-next.1 + - @backstage/plugin-catalog-import@0.10.6-next.1 + - @backstage/plugin-catalog-react@1.9.4-next.1 + - @backstage/plugin-devtools@0.1.9-next.1 + - @backstage/plugin-graphiql@0.3.3-next.1 + - @backstage/plugin-linguist@0.1.15-next.1 + - @backstage/plugin-search@1.4.6-next.1 + - @backstage/plugin-search-react@1.7.6-next.1 + - @backstage/plugin-stack-overflow@0.1.25-next.1 + - @backstage/plugin-tech-radar@0.6.13-next.1 + - @backstage/plugin-user-settings@0.8.1-next.1 + - @backstage/integration-react@1.1.24-next.0 + - @backstage/plugin-airbrake@0.3.30-next.1 + - @backstage/plugin-apache-airflow@0.2.20-next.0 + - @backstage/plugin-api-docs@0.10.4-next.1 + - @backstage/plugin-azure-devops@0.3.12-next.1 + - @backstage/plugin-badges@0.2.54-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.1.8-next.0 + - @backstage/plugin-cloudbuild@0.4.0-next.1 + - @backstage/plugin-code-coverage@0.2.23-next.1 + - @backstage/plugin-cost-insights@0.12.19-next.1 + - @backstage/plugin-dynatrace@8.0.4-next.1 + - @backstage/plugin-entity-feedback@0.2.13-next.1 + - @backstage/plugin-gcalendar@0.3.23-next.0 + - @backstage/plugin-gcp-projects@0.3.46-next.0 + - @backstage/plugin-github-actions@0.6.11-next.1 + - @backstage/plugin-gocd@0.1.36-next.1 + - @backstage/plugin-jenkins@0.9.5-next.1 + - @backstage/plugin-kafka@0.3.30-next.1 + - @backstage/plugin-kubernetes@0.11.5-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.6-next.1 + - @backstage/plugin-lighthouse@0.4.15-next.1 + - @backstage/plugin-microsoft-calendar@0.1.12-next.0 + - @backstage/plugin-newrelic@0.3.45-next.0 + - @backstage/plugin-newrelic-dashboard@0.3.5-next.1 + - @backstage/plugin-nomad@0.1.11-next.1 + - @backstage/plugin-octopus-deploy@0.2.12-next.1 + - @backstage/plugin-org@0.6.20-next.1 + - @backstage/plugin-pagerduty@0.7.2-next.1 + - @backstage/plugin-playlist@0.2.4-next.1 + - @backstage/plugin-puppetdb@0.1.13-next.1 + - @backstage/plugin-rollbar@0.4.30-next.1 + - @backstage/plugin-sentry@0.5.15-next.1 + - @backstage/plugin-shortcuts@0.3.19-next.0 + - @backstage/plugin-signals@0.0.1-next.1 + - @backstage/plugin-stackstorm@0.1.11-next.0 + - @backstage/plugin-tech-insights@0.3.22-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.5-next.1 + - @backstage/plugin-techdocs-react@1.1.16-next.0 + - @backstage/plugin-todo@0.2.34-next.1 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0 + - @backstage/plugin-catalog-common@1.0.21-next.0 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-permission-react@0.4.20-next.0 + - @backstage/plugin-search-common@1.2.10 + +## 0.2.92-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-import@0.10.6-next.0 + - @backstage/plugin-catalog-graph@0.3.4-next.0 + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/plugin-catalog@1.17.0-next.0 + - @backstage/plugin-adr@0.6.13-next.0 + - @backstage/app-defaults@1.4.8-next.0 + - @backstage/frontend-app-api@0.6.0-next.0 + - @backstage/plugin-scaffolder-react@1.8.0-next.0 + - @backstage/plugin-scaffolder@1.18.0-next.0 + - @backstage/plugin-devtools@0.1.9-next.0 + - @backstage/plugin-user-settings@0.8.1-next.0 + - @backstage/plugin-tech-radar@0.6.13-next.0 + - @backstage/plugin-graphiql@0.3.3-next.0 + - @backstage/plugin-techdocs@1.9.4-next.0 + - @backstage/plugin-search@1.4.6-next.0 + - @backstage/plugin-api-docs@0.10.4-next.0 + - @backstage/cli@0.25.2-next.0 + - @backstage/plugin-stack-overflow@0.1.25-next.0 + - @backstage/plugin-signals@0.0.1-next.0 + - @backstage/plugin-home@0.6.2-next.0 + - @backstage/plugin-cloudbuild@0.4.0-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.5-next.0 + - @backstage/plugin-azure-devops@0.3.12-next.0 + - @backstage/plugin-linguist@0.1.15-next.0 + - @backstage/plugin-airbrake@0.3.30-next.0 + - @backstage/plugin-azure-sites@0.1.19-next.0 + - @backstage/plugin-badges@0.2.54-next.0 + - @backstage/plugin-code-coverage@0.2.23-next.0 + - @backstage/plugin-cost-insights@0.12.19-next.0 + - @backstage/plugin-dynatrace@8.0.4-next.0 + - @backstage/plugin-entity-feedback@0.2.13-next.0 + - @backstage/plugin-explore@0.4.16-next.0 + - @backstage/plugin-github-actions@0.6.11-next.0 + - @backstage/plugin-gocd@0.1.36-next.0 + - @backstage/plugin-jenkins@0.9.5-next.0 + - @backstage/plugin-kafka@0.3.30-next.0 + - @backstage/plugin-kubernetes@0.11.5-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.6-next.0 + - @backstage/plugin-lighthouse@0.4.15-next.0 + - @backstage/plugin-newrelic-dashboard@0.3.5-next.0 + - @backstage/plugin-nomad@0.1.11-next.0 + - @backstage/plugin-octopus-deploy@0.2.12-next.0 + - @backstage/plugin-org@0.6.20-next.0 + - @backstage/plugin-pagerduty@0.7.2-next.0 + - @backstage/plugin-playlist@0.2.4-next.0 + - @backstage/plugin-puppetdb@0.1.13-next.0 + - @backstage/plugin-rollbar@0.4.30-next.0 + - @backstage/plugin-sentry@0.5.15-next.0 + - @backstage/plugin-tech-insights@0.3.22-next.0 + - @backstage/plugin-todo@0.2.34-next.0 + - @backstage/core-components@0.13.10 + - @backstage/plugin-search-react@1.7.6-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/integration-react@1.1.23 + - @backstage/theme@0.5.0 + - @backstage/plugin-apache-airflow@0.2.19 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-catalog-unprocessed-entities@0.1.7 + - @backstage/plugin-gcalendar@0.3.22 + - @backstage/plugin-gcp-projects@0.3.45 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-microsoft-calendar@0.1.11 + - @backstage/plugin-newrelic@0.3.44 + - @backstage/plugin-permission-react@0.4.19 + - @backstage/plugin-search-common@1.2.10 + - @backstage/plugin-shortcuts@0.3.18 + - @backstage/plugin-stackstorm@0.1.10 + - @backstage/plugin-techdocs-react@1.1.15 + +## 0.2.91 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-api-docs@0.10.3 + - @backstage/plugin-scaffolder-react@1.7.1 + - @backstage/core-components@0.13.10 + - @backstage/plugin-user-settings@0.8.0 + - @backstage/plugin-azure-sites@0.1.18 + - @backstage/cli@0.25.1 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-octopus-deploy@0.2.11 + - @backstage/frontend-app-api@0.5.0 + - @backstage/plugin-kubernetes@0.11.4 + - @backstage/plugin-home@0.6.1 + - @backstage/plugin-scaffolder@1.17.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.4 + - @backstage/plugin-catalog-unprocessed-entities@0.1.7 + - @backstage/plugin-kubernetes-cluster@0.0.5 + - @backstage/plugin-microsoft-calendar@0.1.11 + - @backstage/plugin-newrelic-dashboard@0.3.4 + - @backstage/plugin-permission-react@0.4.19 + - @backstage/plugin-entity-feedback@0.2.12 + - @backstage/plugin-apache-airflow@0.2.19 + - @backstage/plugin-github-actions@0.6.10 + - @backstage/plugin-stack-overflow@0.1.24 + - @backstage/plugin-techdocs-react@1.1.15 + - @backstage/plugin-catalog-graph@0.3.3 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/plugin-code-coverage@0.2.22 + - @backstage/plugin-cost-insights@0.12.18 + - @backstage/plugin-tech-insights@0.3.21 + - @backstage/plugin-azure-devops@0.3.11 + - @backstage/plugin-gcp-projects@0.3.45 + - @backstage/plugin-cloudbuild@0.3.29 + - @backstage/plugin-lighthouse@0.4.14 + - @backstage/plugin-stackstorm@0.1.10 + - @backstage/plugin-tech-radar@0.6.12 + - @backstage/plugin-dynatrace@8.0.3 + - @backstage/plugin-gcalendar@0.3.22 + - @backstage/plugin-pagerduty@0.7.1 + - @backstage/plugin-shortcuts@0.3.18 + - @backstage/plugin-airbrake@0.3.29 + - @backstage/plugin-devtools@0.1.8 + - @backstage/plugin-graphiql@0.3.2 + - @backstage/plugin-linguist@0.1.14 + - @backstage/plugin-newrelic@0.3.44 + - @backstage/plugin-playlist@0.2.3 + - @backstage/plugin-puppetdb@0.1.12 + - @backstage/plugin-techdocs@1.9.3 + - @backstage/plugin-catalog@1.16.1 + - @backstage/plugin-explore@0.4.15 + - @backstage/plugin-jenkins@0.9.4 + - @backstage/plugin-rollbar@0.4.29 + - @backstage/plugin-badges@0.2.53 + - @backstage/plugin-search@1.4.5 + - @backstage/plugin-sentry@0.5.14 + - @backstage/plugin-kafka@0.3.29 + - @backstage/plugin-nomad@0.1.10 + - @backstage/plugin-gocd@0.1.35 + - @backstage/plugin-todo@0.2.33 + - @backstage/plugin-adr@0.6.12 + - @backstage/plugin-org@0.6.19 + - @backstage/plugin-catalog-import@0.10.5 + - @backstage/plugin-search-react@1.7.5 + - @backstage/app-defaults@1.4.7 + - @backstage/integration-react@1.1.23 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.3 + - @backstage/theme@0.5.0 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.10 + +## 0.2.91-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-user-settings@0.8.0-next.2 + - @backstage/plugin-octopus-deploy@0.2.11-next.2 + - @backstage/frontend-app-api@0.4.1-next.2 + - @backstage/plugin-home@0.6.1-next.2 + - @backstage/plugin-scaffolder-react@1.7.1-next.2 + - @backstage/plugin-scaffolder@1.17.1-next.2 + - @backstage/plugin-linguist@0.1.14-next.2 + - @backstage/plugin-catalog@1.16.1-next.2 + - @backstage/plugin-catalog-import@0.10.5-next.2 + - @backstage/plugin-graphiql@0.3.2-next.2 + - @backstage/plugin-search@1.4.5-next.2 + - @backstage/plugin-tech-radar@0.6.12-next.2 + - @backstage/plugin-techdocs@1.9.3-next.2 + - @backstage/plugin-adr@0.6.12-next.2 + - @backstage/plugin-catalog-react@1.9.3-next.2 + - @backstage/plugin-explore@0.4.15-next.2 + - @backstage/plugin-search-react@1.7.5-next.2 + - @backstage/plugin-stack-overflow@0.1.24-next.2 + - @backstage/cli@0.25.1-next.1 + - @backstage/plugin-pagerduty@0.7.1-next.2 + - @backstage/plugin-api-docs@0.10.3-next.2 + - @backstage/plugin-catalog-graph@0.3.3-next.2 + - @backstage/plugin-org@0.6.19-next.2 + - @backstage/plugin-airbrake@0.3.29-next.2 + - @backstage/plugin-azure-devops@0.3.11-next.2 + - @backstage/plugin-azure-sites@0.1.18-next.2 + - @backstage/plugin-badges@0.2.53-next.2 + - @backstage/plugin-cloudbuild@0.3.29-next.2 + - @backstage/plugin-code-coverage@0.2.22-next.2 + - @backstage/plugin-cost-insights@0.12.18-next.2 + - @backstage/plugin-dynatrace@8.0.3-next.2 + - @backstage/plugin-entity-feedback@0.2.12-next.2 + - @backstage/plugin-github-actions@0.6.10-next.2 + - @backstage/plugin-gocd@0.1.35-next.2 + - @backstage/plugin-jenkins@0.9.4-next.2 + - @backstage/plugin-kafka@0.3.29-next.2 + - @backstage/plugin-kubernetes@0.11.4-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.5-next.2 + - @backstage/plugin-lighthouse@0.4.14-next.2 + - @backstage/plugin-newrelic-dashboard@0.3.4-next.2 + - @backstage/plugin-nomad@0.1.10-next.2 + - @backstage/plugin-playlist@0.2.3-next.2 + - @backstage/plugin-puppetdb@0.1.12-next.2 + - @backstage/plugin-rollbar@0.4.29-next.2 + - @backstage/plugin-sentry@0.5.14-next.2 + - @backstage/plugin-tech-insights@0.3.21-next.2 + - @backstage/plugin-todo@0.2.33-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.4-next.1 + - @backstage/integration-react@1.1.23-next.0 + - @backstage/plugin-apache-airflow@0.2.19-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.1.7-next.1 + - @backstage/plugin-devtools@0.1.8-next.1 + - @backstage/plugin-gcalendar@0.3.22-next.1 + - @backstage/plugin-gcp-projects@0.3.45-next.1 + - @backstage/plugin-microsoft-calendar@0.1.11-next.1 + - @backstage/plugin-newrelic@0.3.44-next.1 + - @backstage/plugin-shortcuts@0.3.18-next.1 + - @backstage/plugin-stackstorm@0.1.10-next.1 + +## 0.2.91-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-azure-sites@0.1.18-next.1 + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/plugin-user-settings@0.8.0-next.1 + - @backstage/plugin-kubernetes@0.11.4-next.1 + - @backstage/core-components@0.13.10-next.1 + - @backstage/app-defaults@1.4.7-next.1 + - @backstage/cli@0.25.1-next.1 + - @backstage/core-app-api@1.11.3-next.0 + - @backstage/frontend-app-api@0.4.1-next.1 + - @backstage/integration-react@1.1.23-next.0 + - @backstage/plugin-adr@0.6.12-next.1 + - @backstage/plugin-airbrake@0.3.29-next.1 + - @backstage/plugin-apache-airflow@0.2.19-next.1 + - @backstage/plugin-api-docs@0.10.3-next.1 + - @backstage/plugin-azure-devops@0.3.11-next.1 + - @backstage/plugin-badges@0.2.53-next.1 + - @backstage/plugin-catalog@1.16.1-next.1 + - @backstage/plugin-catalog-graph@0.3.3-next.1 + - @backstage/plugin-catalog-import@0.10.5-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.1.7-next.1 + - @backstage/plugin-cloudbuild@0.3.29-next.1 + - @backstage/plugin-code-coverage@0.2.22-next.1 + - @backstage/plugin-cost-insights@0.12.18-next.1 + - @backstage/plugin-devtools@0.1.8-next.1 + - @backstage/plugin-dynatrace@8.0.3-next.1 + - @backstage/plugin-entity-feedback@0.2.12-next.1 + - @backstage/plugin-explore@0.4.15-next.1 + - @backstage/plugin-gcalendar@0.3.22-next.1 + - @backstage/plugin-gcp-projects@0.3.45-next.1 + - @backstage/plugin-github-actions@0.6.10-next.1 + - @backstage/plugin-gocd@0.1.35-next.1 + - @backstage/plugin-graphiql@0.3.2-next.1 + - @backstage/plugin-home@0.6.1-next.1 + - @backstage/plugin-jenkins@0.9.4-next.1 + - @backstage/plugin-kafka@0.3.29-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.5-next.1 + - @backstage/plugin-lighthouse@0.4.14-next.1 + - @backstage/plugin-linguist@0.1.14-next.1 + - @backstage/plugin-microsoft-calendar@0.1.11-next.1 + - @backstage/plugin-newrelic@0.3.44-next.1 + - @backstage/plugin-newrelic-dashboard@0.3.4-next.1 + - @backstage/plugin-nomad@0.1.10-next.1 + - @backstage/plugin-octopus-deploy@0.2.11-next.1 + - @backstage/plugin-org@0.6.19-next.1 + - @backstage/plugin-pagerduty@0.7.1-next.1 + - @backstage/plugin-permission-react@0.4.19-next.1 + - @backstage/plugin-playlist@0.2.3-next.1 + - @backstage/plugin-puppetdb@0.1.12-next.1 + - @backstage/plugin-rollbar@0.4.29-next.1 + - @backstage/plugin-scaffolder@1.17.1-next.1 + - @backstage/plugin-scaffolder-react@1.7.1-next.1 + - @backstage/plugin-search@1.4.5-next.1 + - @backstage/plugin-search-react@1.7.5-next.1 + - @backstage/plugin-sentry@0.5.14-next.1 + - @backstage/plugin-shortcuts@0.3.18-next.1 + - @backstage/plugin-stack-overflow@0.1.24-next.1 + - @backstage/plugin-stackstorm@0.1.10-next.1 + - @backstage/plugin-tech-insights@0.3.21-next.1 + - @backstage/plugin-tech-radar@0.6.12-next.1 + - @backstage/plugin-techdocs@1.9.3-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.4-next.1 + - @backstage/plugin-techdocs-react@1.1.15-next.1 + - @backstage/plugin-todo@0.2.33-next.1 + - @backstage/config@1.1.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.9 + +## 0.2.91-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.7.1-next.0 + - @backstage/core-components@0.13.10-next.0 + - @backstage/cli@0.25.1-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.4-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.1.7-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.5-next.0 + - @backstage/plugin-microsoft-calendar@0.1.11-next.0 + - @backstage/plugin-newrelic-dashboard@0.3.4-next.0 + - @backstage/plugin-permission-react@0.4.19-next.0 + - @backstage/plugin-entity-feedback@0.2.12-next.0 + - @backstage/plugin-apache-airflow@0.2.19-next.0 + - @backstage/plugin-github-actions@0.6.10-next.0 + - @backstage/plugin-octopus-deploy@0.2.11-next.0 + - @backstage/plugin-stack-overflow@0.1.24-next.0 + - @backstage/plugin-techdocs-react@1.1.15-next.0 + - @backstage/plugin-catalog-graph@0.3.3-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/plugin-code-coverage@0.2.22-next.0 + - @backstage/plugin-cost-insights@0.12.18-next.0 + - @backstage/plugin-tech-insights@0.3.21-next.0 + - @backstage/plugin-user-settings@0.7.15-next.0 + - @backstage/plugin-azure-devops@0.3.11-next.0 + - @backstage/plugin-gcp-projects@0.3.45-next.0 + - @backstage/plugin-azure-sites@0.1.18-next.0 + - @backstage/plugin-cloudbuild@0.3.29-next.0 + - @backstage/plugin-kubernetes@0.11.4-next.0 + - @backstage/plugin-lighthouse@0.4.14-next.0 + - @backstage/plugin-scaffolder@1.17.1-next.0 + - @backstage/plugin-stackstorm@0.1.10-next.0 + - @backstage/plugin-tech-radar@0.6.12-next.0 + - @backstage/plugin-dynatrace@8.0.3-next.0 + - @backstage/plugin-gcalendar@0.3.22-next.0 + - @backstage/plugin-pagerduty@0.7.1-next.0 + - @backstage/plugin-shortcuts@0.3.18-next.0 + - @backstage/plugin-airbrake@0.3.29-next.0 + - @backstage/plugin-api-docs@0.10.3-next.0 + - @backstage/plugin-devtools@0.1.8-next.0 + - @backstage/plugin-graphiql@0.3.2-next.0 + - @backstage/plugin-linguist@0.1.14-next.0 + - @backstage/plugin-newrelic@0.3.44-next.0 + - @backstage/plugin-playlist@0.2.3-next.0 + - @backstage/plugin-puppetdb@0.1.12-next.0 + - @backstage/plugin-techdocs@1.9.3-next.0 + - @backstage/plugin-catalog@1.16.1-next.0 + - @backstage/plugin-explore@0.4.15-next.0 + - @backstage/plugin-jenkins@0.9.4-next.0 + - @backstage/plugin-rollbar@0.4.29-next.0 + - @backstage/plugin-badges@0.2.53-next.0 + - @backstage/plugin-search@1.4.5-next.0 + - @backstage/plugin-sentry@0.5.14-next.0 + - @backstage/plugin-kafka@0.3.29-next.0 + - @backstage/plugin-nomad@0.1.10-next.0 + - @backstage/plugin-gocd@0.1.35-next.0 + - @backstage/plugin-home@0.6.1-next.0 + - @backstage/plugin-todo@0.2.33-next.0 + - @backstage/plugin-adr@0.6.12-next.0 + - @backstage/plugin-org@0.6.19-next.0 + - @backstage/app-defaults@1.4.7-next.0 + - @backstage/frontend-app-api@0.4.1-next.0 + - @backstage/integration-react@1.1.22 + - @backstage/plugin-catalog-import@0.10.5-next.0 + - @backstage/plugin-search-react@1.7.5-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2 + - @backstage/core-plugin-api@1.8.1 + - @backstage/theme@0.5.0 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.9 + +## 0.2.90 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/frontend-app-api@0.4.0 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/plugin-api-docs@0.10.2 + - @backstage/core-components@0.13.9 + - @backstage/cli@0.25.0 + - @backstage/theme@0.5.0 + - @backstage/plugin-scaffolder@1.17.0 + - @backstage/plugin-home@0.6.0 + - @backstage/plugin-catalog@1.16.0 + - @backstage/plugin-scaffolder-react@1.7.0 + - @backstage/plugin-kubernetes@0.11.3 + - @backstage/plugin-org@0.6.18 + - @backstage/plugin-github-actions@0.6.9 + - @backstage/plugin-azure-devops@0.3.10 + - @backstage/core-app-api@1.11.2 + - @backstage/plugin-lighthouse@0.4.13 + - @backstage/plugin-explore@0.4.14 + - @backstage/plugin-catalog-import@0.10.4 + - @backstage/plugin-user-settings@0.7.14 + - @backstage/plugin-tech-radar@0.6.11 + - @backstage/plugin-graphiql@0.3.1 + - @backstage/plugin-techdocs@1.9.2 + - @backstage/plugin-search@1.4.4 + - @backstage/plugin-search-react@1.7.4 + - @backstage/plugin-stack-overflow@0.1.23 + - @backstage/plugin-adr@0.6.11 + - @backstage/plugin-gcp-projects@0.3.44 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.3 + - @backstage/plugin-pagerduty@0.7.0 + - @backstage/app-defaults@1.4.6 + - @backstage/integration-react@1.1.22 + - @backstage/plugin-airbrake@0.3.28 + - @backstage/plugin-apache-airflow@0.2.18 + - @backstage/plugin-azure-sites@0.1.17 + - @backstage/plugin-badges@0.2.52 + - @backstage/plugin-catalog-graph@0.3.2 + - @backstage/plugin-catalog-unprocessed-entities@0.1.6 + - @backstage/plugin-cloudbuild@0.3.28 + - @backstage/plugin-code-coverage@0.2.21 + - @backstage/plugin-cost-insights@0.12.17 + - @backstage/plugin-devtools@0.1.7 + - @backstage/plugin-dynatrace@8.0.2 + - @backstage/plugin-entity-feedback@0.2.11 + - @backstage/plugin-gcalendar@0.3.21 + - @backstage/plugin-gocd@0.1.34 + - @backstage/plugin-jenkins@0.9.3 + - @backstage/plugin-kafka@0.3.28 + - @backstage/plugin-kubernetes-cluster@0.0.4 + - @backstage/plugin-linguist@0.1.13 + - @backstage/plugin-microsoft-calendar@0.1.10 + - @backstage/plugin-newrelic@0.3.43 + - @backstage/plugin-newrelic-dashboard@0.3.3 + - @backstage/plugin-nomad@0.1.9 + - @backstage/plugin-octopus-deploy@0.2.10 + - @backstage/plugin-permission-react@0.4.18 + - @backstage/plugin-playlist@0.2.2 + - @backstage/plugin-puppetdb@0.1.11 + - @backstage/plugin-rollbar@0.4.28 + - @backstage/plugin-sentry@0.5.13 + - @backstage/plugin-shortcuts@0.3.17 + - @backstage/plugin-stackstorm@0.1.9 + - @backstage/plugin-tech-insights@0.3.20 + - @backstage/plugin-techdocs-react@1.1.14 + - @backstage/plugin-todo@0.2.32 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.9 + +## 0.2.90-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-home@0.6.0-next.3 + - @backstage/plugin-org@0.6.18-next.3 + - @backstage/plugin-azure-devops@0.3.10-next.3 + - @backstage/cli@0.25.0-next.3 + - @backstage/plugin-api-docs@0.10.2-next.4 + - @backstage/plugin-scaffolder-react@1.6.2-next.3 + - @backstage/plugin-scaffolder@1.16.2-next.3 + - @backstage/core-components@0.13.9-next.3 + - @backstage/app-defaults@1.4.6-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/frontend-app-api@0.4.0-next.3 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-adr@0.6.11-next.3 + - @backstage/plugin-airbrake@0.3.28-next.3 + - @backstage/plugin-apache-airflow@0.2.18-next.3 + - @backstage/plugin-azure-sites@0.1.17-next.3 + - @backstage/plugin-badges@0.2.52-next.3 + - @backstage/plugin-catalog@1.16.0-next.4 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-graph@0.3.2-next.3 + - @backstage/plugin-catalog-import@0.10.4-next.4 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-catalog-unprocessed-entities@0.1.6-next.3 + - @backstage/plugin-cloudbuild@0.3.28-next.3 + - @backstage/plugin-code-coverage@0.2.21-next.3 + - @backstage/plugin-cost-insights@0.12.17-next.3 + - @backstage/plugin-devtools@0.1.7-next.3 + - @backstage/plugin-dynatrace@8.0.2-next.3 + - @backstage/plugin-entity-feedback@0.2.11-next.3 + - @backstage/plugin-explore@0.4.14-next.3 + - @backstage/plugin-gcalendar@0.3.21-next.3 + - @backstage/plugin-gcp-projects@0.3.44-next.3 + - @backstage/plugin-github-actions@0.6.9-next.3 + - @backstage/plugin-gocd@0.1.34-next.3 + - @backstage/plugin-graphiql@0.3.1-next.4 + - @backstage/plugin-jenkins@0.9.3-next.3 + - @backstage/plugin-kafka@0.3.28-next.3 + - @backstage/plugin-kubernetes@0.11.3-next.3 + - @backstage/plugin-kubernetes-cluster@0.0.4-next.3 + - @backstage/plugin-lighthouse@0.4.13-next.3 + - @backstage/plugin-linguist@0.1.13-next.3 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-microsoft-calendar@0.1.10-next.3 + - @backstage/plugin-newrelic@0.3.43-next.3 + - @backstage/plugin-newrelic-dashboard@0.3.3-next.3 + - @backstage/plugin-nomad@0.1.9-next.3 + - @backstage/plugin-octopus-deploy@0.2.10-next.3 + - @backstage/plugin-pagerduty@0.7.0-next.3 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/plugin-playlist@0.2.2-next.3 + - @backstage/plugin-puppetdb@0.1.11-next.3 + - @backstage/plugin-rollbar@0.4.28-next.3 + - @backstage/plugin-search@1.4.4-next.4 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-search-react@1.7.4-next.3 + - @backstage/plugin-sentry@0.5.13-next.3 + - @backstage/plugin-shortcuts@0.3.17-next.3 + - @backstage/plugin-stack-overflow@0.1.23-next.3 + - @backstage/plugin-stackstorm@0.1.9-next.3 + - @backstage/plugin-tech-insights@0.3.20-next.3 + - @backstage/plugin-tech-radar@0.6.11-next.4 + - @backstage/plugin-techdocs@1.9.2-next.4 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.3-next.3 + - @backstage/plugin-techdocs-react@1.1.14-next.3 + - @backstage/plugin-todo@0.2.32-next.3 + - @backstage/plugin-user-settings@0.7.14-next.4 + +## 0.2.90-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.4.0-next.2 + - @backstage/cli@0.25.0-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-kubernetes@0.11.3-next.2 + - @backstage/plugin-lighthouse@0.4.13-next.2 + - @backstage/plugin-catalog-import@0.10.4-next.3 + - @backstage/plugin-user-settings@0.7.14-next.3 + - @backstage/plugin-tech-radar@0.6.11-next.3 + - @backstage/plugin-graphiql@0.3.1-next.3 + - @backstage/plugin-techdocs@1.9.2-next.3 + - @backstage/plugin-catalog@1.16.0-next.3 + - @backstage/plugin-search@1.4.4-next.3 + - @backstage/plugin-home@0.6.0-next.2 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/plugin-stack-overflow@0.1.23-next.2 + - @backstage/plugin-search-react@1.7.4-next.2 + - @backstage/plugin-explore@0.4.14-next.2 + - @backstage/plugin-adr@0.6.11-next.2 + - @backstage/plugin-scaffolder-react@1.6.2-next.2 + - @backstage/app-defaults@1.4.6-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/plugin-airbrake@0.3.28-next.2 + - @backstage/plugin-apache-airflow@0.2.18-next.2 + - @backstage/plugin-api-docs@0.10.2-next.3 + - @backstage/plugin-azure-devops@0.3.10-next.2 + - @backstage/plugin-azure-sites@0.1.17-next.2 + - @backstage/plugin-badges@0.2.52-next.2 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-graph@0.3.2-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.1.6-next.2 + - @backstage/plugin-cloudbuild@0.3.28-next.2 + - @backstage/plugin-code-coverage@0.2.21-next.2 + - @backstage/plugin-cost-insights@0.12.17-next.2 + - @backstage/plugin-devtools@0.1.7-next.2 + - @backstage/plugin-dynatrace@8.0.2-next.2 + - @backstage/plugin-entity-feedback@0.2.11-next.2 + - @backstage/plugin-gcalendar@0.3.21-next.2 + - @backstage/plugin-gcp-projects@0.3.44-next.2 + - @backstage/plugin-github-actions@0.6.9-next.2 + - @backstage/plugin-gocd@0.1.34-next.2 + - @backstage/plugin-jenkins@0.9.3-next.2 + - @backstage/plugin-kafka@0.3.28-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.4-next.2 + - @backstage/plugin-linguist@0.1.13-next.2 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-microsoft-calendar@0.1.10-next.2 + - @backstage/plugin-newrelic@0.3.43-next.2 + - @backstage/plugin-newrelic-dashboard@0.3.3-next.2 + - @backstage/plugin-nomad@0.1.9-next.2 + - @backstage/plugin-octopus-deploy@0.2.10-next.2 + - @backstage/plugin-org@0.6.18-next.2 + - @backstage/plugin-pagerduty@0.7.0-next.2 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/plugin-playlist@0.2.2-next.2 + - @backstage/plugin-puppetdb@0.1.11-next.2 + - @backstage/plugin-rollbar@0.4.28-next.2 + - @backstage/plugin-scaffolder@1.16.2-next.2 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-sentry@0.5.13-next.2 + - @backstage/plugin-shortcuts@0.3.17-next.2 + - @backstage/plugin-stackstorm@0.1.9-next.2 + - @backstage/plugin-tech-insights@0.3.20-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.3-next.2 + - @backstage/plugin-techdocs-react@1.1.14-next.2 + - @backstage/plugin-todo@0.2.32-next.2 + +## 0.2.90-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.4.0-next.1 + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/cli@0.25.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/plugin-catalog@1.16.0-next.2 + - @backstage/plugin-scaffolder-react@1.6.2-next.1 + - @backstage/plugin-home@0.6.0-next.1 + - @backstage/plugin-github-actions@0.6.9-next.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/plugin-search-react@1.7.4-next.1 + - @backstage/plugin-azure-devops@0.3.10-next.1 + - @backstage/plugin-scaffolder@1.16.2-next.1 + - @backstage/plugin-api-docs@0.10.2-next.2 + - @backstage/plugin-gcp-projects@0.3.44-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.3-next.1 + - @backstage/plugin-user-settings@0.7.14-next.2 + - @backstage/plugin-adr@0.6.11-next.1 + - @backstage/plugin-pagerduty@0.7.0-next.1 + - @backstage/plugin-catalog-import@0.10.4-next.2 + - @backstage/plugin-explore@0.4.14-next.1 + - @backstage/plugin-graphiql@0.3.1-next.2 + - @backstage/plugin-search@1.4.4-next.2 + - @backstage/plugin-stack-overflow@0.1.23-next.1 + - @backstage/plugin-tech-radar@0.6.11-next.2 + - @backstage/plugin-techdocs@1.9.2-next.2 + - @backstage/app-defaults@1.4.6-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/plugin-airbrake@0.3.28-next.1 + - @backstage/plugin-apache-airflow@0.2.18-next.1 + - @backstage/plugin-azure-sites@0.1.17-next.1 + - @backstage/plugin-badges@0.2.52-next.1 + - @backstage/plugin-catalog-graph@0.3.2-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.1.6-next.1 + - @backstage/plugin-cloudbuild@0.3.28-next.1 + - @backstage/plugin-code-coverage@0.2.21-next.1 + - @backstage/plugin-cost-insights@0.12.17-next.1 + - @backstage/plugin-devtools@0.1.7-next.1 + - @backstage/plugin-dynatrace@8.0.2-next.1 + - @backstage/plugin-entity-feedback@0.2.11-next.1 + - @backstage/plugin-gcalendar@0.3.21-next.1 + - @backstage/plugin-gocd@0.1.34-next.1 + - @backstage/plugin-jenkins@0.9.3-next.1 + - @backstage/plugin-kafka@0.3.28-next.1 + - @backstage/plugin-kubernetes@0.11.3-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.4-next.1 + - @backstage/plugin-lighthouse@0.4.13-next.1 + - @backstage/plugin-linguist@0.1.13-next.1 + - @backstage/plugin-microsoft-calendar@0.1.10-next.1 + - @backstage/plugin-newrelic@0.3.43-next.1 + - @backstage/plugin-newrelic-dashboard@0.3.3-next.1 + - @backstage/plugin-nomad@0.1.9-next.1 + - @backstage/plugin-octopus-deploy@0.2.10-next.1 + - @backstage/plugin-org@0.6.18-next.1 + - @backstage/plugin-playlist@0.2.2-next.1 + - @backstage/plugin-puppetdb@0.1.11-next.1 + - @backstage/plugin-rollbar@0.4.28-next.1 + - @backstage/plugin-sentry@0.5.13-next.1 + - @backstage/plugin-shortcuts@0.3.17-next.1 + - @backstage/plugin-stackstorm@0.1.9-next.1 + - @backstage/plugin-tech-insights@0.3.20-next.1 + - @backstage/plugin-techdocs-react@1.1.14-next.1 + - @backstage/plugin-todo@0.2.32-next.1 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0-next.0 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.8 + +## 0.2.90-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.16.0-next.1 + - @backstage/plugin-catalog-import@0.10.4-next.1 + - @backstage/plugin-graphiql@0.3.1-next.1 + - @backstage/plugin-search@1.4.4-next.1 + - @backstage/plugin-tech-radar@0.6.11-next.1 + - @backstage/plugin-techdocs@1.9.2-next.1 + - @backstage/plugin-user-settings@0.7.14-next.1 + - @backstage/plugin-api-docs@0.10.2-next.1 + - @backstage/plugin-catalog-graph@0.3.2-next.0 + - @backstage/plugin-explore@0.4.14-next.0 + - @backstage/plugin-org@0.6.18-next.0 + - @backstage/plugin-scaffolder@1.16.2-next.0 + - @backstage/plugin-scaffolder-react@1.6.2-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.3-next.0 + +## 0.2.90-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1-next.0 + - @backstage/plugin-catalog-react@1.9.2-next.0 + - @backstage/plugin-api-docs@0.10.2-next.0 + - @backstage/core-components@0.13.9-next.0 + - @backstage/plugin-scaffolder@1.16.2-next.0 + - @backstage/plugin-catalog@1.16.0-next.0 + - @backstage/frontend-app-api@0.3.1-next.0 + - @backstage/plugin-org@0.6.18-next.0 + - @backstage/plugin-scaffolder-react@1.6.2-next.0 + - @backstage/plugin-catalog-import@0.10.4-next.0 + - @backstage/plugin-user-settings@0.7.14-next.0 + - @backstage/plugin-tech-radar@0.6.11-next.0 + - @backstage/plugin-graphiql@0.3.1-next.0 + - @backstage/plugin-techdocs@1.9.2-next.0 + - @backstage/plugin-search@1.4.4-next.0 + - @backstage/plugin-search-react@1.7.4-next.0 + - @backstage/cli@0.24.1-next.0 + - @backstage/plugin-home@0.5.12-next.0 + - @backstage/plugin-kubernetes@0.11.3-next.0 + - @backstage/theme@0.5.0-next.0 + - @backstage/app-defaults@1.4.6-next.0 + - @backstage/core-app-api@1.11.2-next.0 + - @backstage/integration-react@1.1.22-next.0 + - @backstage/plugin-adr@0.6.11-next.0 + - @backstage/plugin-airbrake@0.3.28-next.0 + - @backstage/plugin-apache-airflow@0.2.18-next.0 + - @backstage/plugin-azure-devops@0.3.10-next.0 + - @backstage/plugin-azure-sites@0.1.17-next.0 + - @backstage/plugin-badges@0.2.52-next.0 + - @backstage/plugin-catalog-graph@0.3.2-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.1.6-next.0 + - @backstage/plugin-circleci@0.3.28-next.0 + - @backstage/plugin-cloudbuild@0.3.28-next.0 + - @backstage/plugin-code-coverage@0.2.21-next.0 + - @backstage/plugin-cost-insights@0.12.17-next.0 + - @backstage/plugin-devtools@0.1.7-next.0 + - @backstage/plugin-dynatrace@8.0.2-next.0 + - @backstage/plugin-entity-feedback@0.2.11-next.0 + - @backstage/plugin-explore@0.4.14-next.0 + - @backstage/plugin-gcalendar@0.3.21-next.0 + - @backstage/plugin-gcp-projects@0.3.44-next.0 + - @backstage/plugin-github-actions@0.6.9-next.0 + - @backstage/plugin-gocd@0.1.34-next.0 + - @backstage/plugin-jenkins@0.9.3-next.0 + - @backstage/plugin-kafka@0.3.28-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.4-next.0 + - @backstage/plugin-lighthouse@0.4.13-next.0 + - @backstage/plugin-linguist@0.1.13-next.0 + - @backstage/plugin-microsoft-calendar@0.1.10-next.0 + - @backstage/plugin-newrelic@0.3.43-next.0 + - @backstage/plugin-newrelic-dashboard@0.3.3-next.0 + - @backstage/plugin-nomad@0.1.9-next.0 + - @backstage/plugin-octopus-deploy@0.2.10-next.0 + - @backstage/plugin-pagerduty@0.6.9-next.0 + - @backstage/plugin-permission-react@0.4.18-next.0 + - @backstage/plugin-playlist@0.2.2-next.0 + - @backstage/plugin-puppetdb@0.1.11-next.0 + - @backstage/plugin-rollbar@0.4.28-next.0 + - @backstage/plugin-sentry@0.5.13-next.0 + - @backstage/plugin-shortcuts@0.3.17-next.0 + - @backstage/plugin-stack-overflow@0.1.23-next.0 + - @backstage/plugin-stackstorm@0.1.9-next.0 + - @backstage/plugin-tech-insights@0.3.20-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.3-next.0 + - @backstage/plugin-techdocs-react@1.1.14-next.0 + - @backstage/plugin-todo@0.2.32-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.8 + +## 0.2.89 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-api-docs@0.10.0 + - @backstage/plugin-catalog-import@0.10.2 + - @backstage/cli@0.24.0 + - @backstage/plugin-catalog-react@1.9.0 + - @backstage/plugin-shortcuts@0.3.16 + - @backstage/plugin-home@0.5.10 + - @backstage/core-components@0.13.8 + - @backstage/plugin-catalog@1.15.0 + - @backstage/plugin-graphiql@0.3.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.2 + - @backstage/plugin-techdocs@1.9.0 + - @backstage/plugin-cloudbuild@0.3.26 + - @backstage/plugin-scaffolder@1.16.0 + - @backstage/frontend-app-api@0.3.0 + - @backstage/plugin-user-settings@0.7.12 + - @backstage/plugin-tech-radar@0.6.10 + - @backstage/plugin-search@1.4.2 + - @backstage/plugin-catalog-unprocessed-entities@0.1.5 + - @backstage/integration-react@1.1.21 + - @backstage/plugin-kubernetes-cluster@0.0.2 + - @backstage/plugin-microsoft-calendar@0.1.9 + - @backstage/plugin-newrelic-dashboard@0.3.1 + - @backstage/core-plugin-api@1.8.0 + - @backstage/plugin-permission-react@0.4.17 + - @backstage/plugin-scaffolder-react@1.6.0 + - @backstage/plugin-entity-feedback@0.2.9 + - @backstage/plugin-apache-airflow@0.2.17 + - @backstage/plugin-github-actions@0.6.7 + - @backstage/plugin-octopus-deploy@0.2.8 + - @backstage/plugin-stack-overflow@0.1.22 + - @backstage/plugin-techdocs-react@1.1.13 + - @backstage/app-defaults@1.4.5 + - @backstage/core-app-api@1.11.1 + - @backstage/plugin-catalog-graph@0.3.0 + - @backstage/plugin-code-coverage@0.2.19 + - @backstage/plugin-cost-insights@0.12.15 + - @backstage/plugin-tech-insights@0.3.18 + - @backstage/plugin-azure-devops@0.3.8 + - @backstage/plugin-gcp-projects@0.3.43 + - @backstage/plugin-search-react@1.7.2 + - @backstage/plugin-azure-sites@0.1.15 + - @backstage/plugin-kubernetes@0.11.1 + - @backstage/plugin-lighthouse@0.4.11 + - @backstage/plugin-stackstorm@0.1.8 + - @backstage/plugin-dynatrace@8.0.0 + - @backstage/plugin-gcalendar@0.3.20 + - @backstage/plugin-pagerduty@0.6.7 + - @backstage/plugin-airbrake@0.3.26 + - @backstage/plugin-circleci@0.3.26 + - @backstage/plugin-devtools@0.1.6 + - @backstage/plugin-linguist@0.1.11 + - @backstage/plugin-newrelic@0.3.42 + - @backstage/plugin-playlist@0.2.0 + - @backstage/plugin-puppetdb@0.1.9 + - @backstage/plugin-explore@0.4.12 + - @backstage/plugin-jenkins@0.9.1 + - @backstage/plugin-rollbar@0.4.26 + - @backstage/theme@0.4.4 + - @backstage/plugin-badges@0.2.50 + - @backstage/plugin-sentry@0.5.11 + - @backstage/plugin-kafka@0.3.26 + - @backstage/plugin-nomad@0.1.7 + - @backstage/plugin-gocd@0.1.32 + - @backstage/plugin-todo@0.2.30 + - @backstage/plugin-adr@0.6.9 + - @backstage/plugin-org@0.6.16 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.8 + +## 0.2.89-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-api-docs@0.10.0-next.2 + - @backstage/plugin-shortcuts@0.3.16-next.2 + - @backstage/core-components@0.13.8-next.2 + - @backstage/plugin-catalog@1.15.0-next.2 + - @backstage/plugin-scaffolder@1.16.0-next.2 + - @backstage/frontend-app-api@0.3.0-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.1.5-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.2-next.2 + - @backstage/plugin-newrelic-dashboard@0.3.1-next.2 + - @backstage/plugin-github-actions@0.6.7-next.2 + - @backstage/plugin-code-coverage@0.2.19-next.2 + - @backstage/plugin-azure-sites@0.1.15-next.2 + - @backstage/plugin-cloudbuild@0.3.26-next.2 + - @backstage/plugin-kubernetes@0.11.1-next.2 + - @backstage/plugin-lighthouse@0.4.11-next.2 + - @backstage/plugin-dynatrace@8.0.0-next.2 + - @backstage/plugin-airbrake@0.3.26-next.2 + - @backstage/plugin-circleci@0.3.26-next.2 + - @backstage/plugin-puppetdb@0.1.9-next.2 + - @backstage/plugin-techdocs@1.9.0-next.2 + - @backstage/plugin-jenkins@0.9.1-next.2 + - @backstage/plugin-rollbar@0.4.26-next.2 + - @backstage/plugin-sentry@0.5.11-next.2 + - @backstage/plugin-kafka@0.3.26-next.2 + - @backstage/plugin-nomad@0.1.7-next.2 + - @backstage/plugin-gocd@0.1.32-next.2 + - @backstage/plugin-adr@0.6.9-next.2 + - @backstage/plugin-catalog-react@1.9.0-next.2 + - @backstage/app-defaults@1.4.5-next.2 + - @backstage/cli@0.24.0-next.1 + - @backstage/integration-react@1.1.21-next.1 + - @backstage/plugin-apache-airflow@0.2.17-next.2 + - @backstage/plugin-azure-devops@0.3.8-next.2 + - @backstage/plugin-badges@0.2.50-next.2 + - @backstage/plugin-catalog-graph@0.2.38-next.2 + - @backstage/plugin-catalog-import@0.10.2-next.2 + - @backstage/plugin-cost-insights@0.12.15-next.2 + - @backstage/plugin-devtools@0.1.6-next.2 + - @backstage/plugin-entity-feedback@0.2.9-next.2 + - @backstage/plugin-explore@0.4.12-next.2 + - @backstage/plugin-gcalendar@0.3.20-next.2 + - @backstage/plugin-gcp-projects@0.3.43-next.2 + - @backstage/plugin-graphiql@0.3.0-next.2 + - @backstage/plugin-home@0.5.10-next.2 + - @backstage/plugin-linguist@0.1.11-next.2 + - @backstage/plugin-microsoft-calendar@0.1.9-next.2 + - @backstage/plugin-newrelic@0.3.42-next.2 + - @backstage/plugin-octopus-deploy@0.2.8-next.2 + - @backstage/plugin-org@0.6.16-next.2 + - @backstage/plugin-pagerduty@0.6.7-next.2 + - @backstage/plugin-playlist@0.1.18-next.2 + - @backstage/plugin-scaffolder-react@1.6.0-next.2 + - @backstage/plugin-search@1.4.2-next.2 + - @backstage/plugin-search-react@1.7.2-next.2 + - @backstage/plugin-stack-overflow@0.1.22-next.2 + - @backstage/plugin-stackstorm@0.1.8-next.2 + - @backstage/plugin-tech-insights@0.3.18-next.2 + - @backstage/plugin-tech-radar@0.6.10-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.2-next.2 + - @backstage/plugin-techdocs-react@1.1.13-next.2 + - @backstage/plugin-todo@0.2.30-next.2 + - @backstage/plugin-user-settings@0.7.12-next.2 + +## 0.2.89-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-home@0.5.10-next.1 + - @backstage/plugin-cost-insights@0.12.15-next.1 + - @backstage/plugin-scaffolder@1.16.0-next.1 + - @backstage/frontend-app-api@0.3.0-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.2-next.1 + - @backstage/plugin-microsoft-calendar@0.1.9-next.1 + - @backstage/plugin-scaffolder-react@1.6.0-next.1 + - @backstage/plugin-catalog-graph@0.2.38-next.1 + - @backstage/plugin-tech-insights@0.3.18-next.1 + - @backstage/plugin-gcalendar@0.3.20-next.1 + - @backstage/plugin-api-docs@0.9.13-next.1 + - @backstage/plugin-playlist@0.1.18-next.1 + - @backstage/plugin-catalog@1.15.0-next.1 + - @backstage/plugin-explore@0.4.12-next.1 + - @backstage/plugin-search@1.4.2-next.1 + - @backstage/plugin-org@0.6.16-next.1 + - @backstage/plugin-catalog-react@1.9.0-next.1 + - @backstage/plugin-graphiql@0.3.0-next.1 + - @backstage/plugin-search-react@1.7.2-next.1 + - @backstage/cli@0.24.0-next.1 + - @backstage/integration-react@1.1.21-next.1 + - @backstage/plugin-catalog-import@0.10.2-next.1 + - @backstage/plugin-github-actions@0.6.7-next.1 + - @backstage/plugin-techdocs@1.8.1-next.1 + - @backstage/plugin-kubernetes@0.11.1-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.2-next.1 + - @backstage/plugin-newrelic@0.3.42-next.1 + - @backstage/plugin-adr@0.6.9-next.1 + - @backstage/plugin-stack-overflow@0.1.22-next.1 + - @backstage/plugin-tech-radar@0.6.10-next.1 + - @backstage/plugin-user-settings@0.7.12-next.1 + - @backstage/core-components@0.13.8-next.1 + - @backstage/plugin-airbrake@0.3.26-next.1 + - @backstage/plugin-azure-devops@0.3.8-next.1 + - @backstage/plugin-azure-sites@0.1.15-next.1 + - @backstage/plugin-badges@0.2.50-next.1 + - @backstage/plugin-circleci@0.3.26-next.1 + - @backstage/plugin-cloudbuild@0.3.26-next.1 + - @backstage/plugin-code-coverage@0.2.19-next.1 + - @backstage/plugin-dynatrace@8.0.0-next.1 + - @backstage/plugin-entity-feedback@0.2.9-next.1 + - @backstage/plugin-gocd@0.1.32-next.1 + - @backstage/plugin-jenkins@0.9.1-next.1 + - @backstage/plugin-kafka@0.3.26-next.1 + - @backstage/plugin-lighthouse@0.4.11-next.1 + - @backstage/plugin-linguist@0.1.11-next.1 + - @backstage/plugin-newrelic-dashboard@0.3.1-next.1 + - @backstage/plugin-nomad@0.1.7-next.1 + - @backstage/plugin-octopus-deploy@0.2.8-next.1 + - @backstage/plugin-pagerduty@0.6.7-next.1 + - @backstage/plugin-puppetdb@0.1.9-next.1 + - @backstage/plugin-rollbar@0.4.26-next.1 + - @backstage/plugin-sentry@0.5.11-next.1 + - @backstage/plugin-todo@0.2.30-next.1 + - @backstage/app-defaults@1.4.5-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.1-next.0 + - @backstage/core-plugin-api@1.8.0-next.0 + - @backstage/theme@0.4.4-next.0 + - @backstage/plugin-apache-airflow@0.2.17-next.1 + - @backstage/plugin-catalog-common@1.0.17 + - @backstage/plugin-catalog-unprocessed-entities@0.1.5-next.1 + - @backstage/plugin-devtools@0.1.6-next.1 + - @backstage/plugin-gcp-projects@0.3.43-next.1 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-permission-react@0.4.17-next.0 + - @backstage/plugin-search-common@1.2.7 + - @backstage/plugin-shortcuts@0.3.16-next.1 + - @backstage/plugin-stackstorm@0.1.8-next.1 + - @backstage/plugin-techdocs-react@1.1.13-next.1 + +## 0.2.89-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-import@0.10.2-next.0 + - @backstage/cli@0.24.0-next.0 + - @backstage/core-components@0.13.7-next.0 + - @backstage/plugin-home@0.5.10-next.0 + - @backstage/plugin-catalog@1.15.0-next.0 + - @backstage/plugin-graphiql@0.3.0-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.2-next.0 + - @backstage/plugin-techdocs@1.8.1-next.0 + - @backstage/frontend-app-api@0.3.0-next.0 + - @backstage/plugin-catalog-react@1.9.0-next.0 + - @backstage/plugin-user-settings@0.7.12-next.0 + - @backstage/plugin-tech-radar@0.6.10-next.0 + - @backstage/plugin-search@1.4.2-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.1.5-next.0 + - @backstage/integration-react@1.1.21-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.2-next.0 + - @backstage/plugin-microsoft-calendar@0.1.9-next.0 + - @backstage/plugin-newrelic-dashboard@0.3.1-next.0 + - @backstage/core-plugin-api@1.8.0-next.0 + - @backstage/plugin-permission-react@0.4.17-next.0 + - @backstage/plugin-scaffolder-react@1.6.0-next.0 + - @backstage/plugin-entity-feedback@0.2.9-next.0 + - @backstage/plugin-apache-airflow@0.2.17-next.0 + - @backstage/plugin-github-actions@0.6.7-next.0 + - @backstage/plugin-octopus-deploy@0.2.8-next.0 + - @backstage/plugin-stack-overflow@0.1.22-next.0 + - @backstage/plugin-techdocs-react@1.1.13-next.0 + - @backstage/app-defaults@1.4.5-next.0 + - @backstage/core-app-api@1.11.1-next.0 + - @backstage/plugin-catalog-graph@0.2.38-next.0 + - @backstage/plugin-code-coverage@0.2.19-next.0 + - @backstage/plugin-cost-insights@0.12.15-next.0 + - @backstage/plugin-tech-insights@0.3.18-next.0 + - @backstage/plugin-azure-devops@0.3.8-next.0 + - @backstage/plugin-gcp-projects@0.3.43-next.0 + - @backstage/plugin-search-react@1.7.2-next.0 + - @backstage/plugin-azure-sites@0.1.15-next.0 + - @backstage/plugin-cloudbuild@0.3.26-next.0 + - @backstage/plugin-kubernetes@0.11.1-next.0 + - @backstage/plugin-lighthouse@0.4.11-next.0 + - @backstage/plugin-scaffolder@1.16.0-next.0 + - @backstage/plugin-stackstorm@0.1.8-next.0 + - @backstage/plugin-dynatrace@8.0.0-next.0 + - @backstage/plugin-gcalendar@0.3.20-next.0 + - @backstage/plugin-pagerduty@0.6.7-next.0 + - @backstage/plugin-shortcuts@0.3.16-next.0 + - @backstage/plugin-airbrake@0.3.26-next.0 + - @backstage/plugin-api-docs@0.9.13-next.0 + - @backstage/plugin-circleci@0.3.26-next.0 + - @backstage/plugin-devtools@0.1.6-next.0 + - @backstage/plugin-linguist@0.1.11-next.0 + - @backstage/plugin-newrelic@0.3.42-next.0 + - @backstage/plugin-playlist@0.1.18-next.0 + - @backstage/plugin-puppetdb@0.1.9-next.0 + - @backstage/plugin-explore@0.4.12-next.0 + - @backstage/plugin-jenkins@0.9.1-next.0 + - @backstage/plugin-rollbar@0.4.26-next.0 + - @backstage/theme@0.4.4-next.0 + - @backstage/plugin-badges@0.2.50-next.0 + - @backstage/plugin-sentry@0.5.11-next.0 + - @backstage/plugin-kafka@0.3.26-next.0 + - @backstage/plugin-nomad@0.1.7-next.0 + - @backstage/plugin-gocd@0.1.32-next.0 + - @backstage/plugin-todo@0.2.30-next.0 + - @backstage/plugin-adr@0.6.9-next.0 + - @backstage/plugin-org@0.6.16-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.17 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.7 + +## 0.2.88 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.2.0 + - @backstage/plugin-pagerduty@0.6.6 + - @backstage/plugin-org@0.6.15 + - @backstage/plugin-code-coverage@0.2.18 + - @backstage/plugin-catalog-react@1.8.5 + - @backstage/plugin-catalog@1.14.0 + - @backstage/cli@0.23.0 + - @backstage/core-app-api@1.11.0 + - @backstage/plugin-techdocs@1.8.0 + - @backstage/core-plugin-api@1.7.0 + - @backstage/plugin-home@0.5.9 + - @backstage/plugin-newrelic@0.3.41 + - @backstage/plugin-user-settings@0.7.11 + - @backstage/plugin-scaffolder@1.15.1 + - @backstage/core-components@0.13.6 + - @backstage/plugin-entity-feedback@0.2.8 + - @backstage/integration-react@1.1.20 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-tech-radar@0.6.9 + - @backstage/plugin-adr@0.6.8 + - @backstage/app-defaults@1.4.4 + - @backstage/plugin-graphiql@0.2.55 + - @backstage/plugin-search@1.4.1 + - @backstage/plugin-jenkins@0.9.0 + - @backstage/plugin-search-react@1.7.1 + - @backstage/plugin-api-docs@0.9.12 + - @backstage/plugin-kubernetes@0.11.0 + - @backstage/plugin-airbrake@0.3.25 + - @backstage/plugin-apache-airflow@0.2.16 + - @backstage/plugin-azure-devops@0.3.7 + - @backstage/plugin-azure-sites@0.1.14 + - @backstage/plugin-badges@0.2.49 + - @backstage/plugin-catalog-graph@0.2.37 + - @backstage/plugin-catalog-import@0.10.1 + - @backstage/plugin-catalog-unprocessed-entities@0.1.4 + - @backstage/plugin-circleci@0.3.25 + - @backstage/plugin-cloudbuild@0.3.25 + - @backstage/plugin-cost-insights@0.12.14 + - @backstage/plugin-devtools@0.1.5 + - @backstage/plugin-dynatrace@7.0.5 + - @backstage/plugin-explore@0.4.11 + - @backstage/plugin-gcalendar@0.3.19 + - @backstage/plugin-gcp-projects@0.3.42 + - @backstage/plugin-github-actions@0.6.6 + - @backstage/plugin-gocd@0.1.31 + - @backstage/plugin-kafka@0.3.25 + - @backstage/plugin-kubernetes-cluster@0.0.1 + - @backstage/plugin-lighthouse@0.4.10 + - @backstage/plugin-linguist@0.1.10 + - @backstage/plugin-microsoft-calendar@0.1.8 + - @backstage/plugin-newrelic-dashboard@0.3.0 + - @backstage/plugin-nomad@0.1.6 + - @backstage/plugin-octopus-deploy@0.2.7 + - @backstage/plugin-permission-react@0.4.16 + - @backstage/plugin-playlist@0.1.17 + - @backstage/plugin-puppetdb@0.1.8 + - @backstage/plugin-rollbar@0.4.25 + - @backstage/plugin-scaffolder-react@1.5.6 + - @backstage/plugin-sentry@0.5.10 + - @backstage/plugin-shortcuts@0.3.15 + - @backstage/plugin-stack-overflow@0.1.21 + - @backstage/plugin-stackstorm@0.1.7 + - @backstage/plugin-tech-insights@0.3.17 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.1 + - @backstage/plugin-techdocs-react@1.1.12 + - @backstage/plugin-todo@0.2.28 + - @backstage/theme@0.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.17 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.7 + +## 0.2.88-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.23.0-next.2 + - @backstage/frontend-app-api@0.2.0-next.2 + - @backstage/core-app-api@1.11.0-next.2 + - @backstage/plugin-techdocs@1.7.1-next.2 + - @backstage/plugin-newrelic@0.3.41-next.2 + - @backstage/plugin-scaffolder@1.15.1-next.2 + - @backstage/integration-react@1.1.20-next.2 + - @backstage/core-components@0.13.6-next.2 + - @backstage/core-plugin-api@1.7.0-next.1 + - @backstage/catalog-model@1.4.3-next.0 + - @backstage/plugin-catalog-react@1.8.5-next.2 + - @backstage/plugin-tech-radar@0.6.9-next.2 + - @backstage/plugin-catalog@1.14.0-next.2 + - @backstage/plugin-adr@0.6.8-next.2 + - @backstage/plugin-graphiql@0.2.55-next.2 + - @backstage/plugin-search-react@1.7.1-next.2 + - @backstage/plugin-newrelic-dashboard@0.3.0-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.1-next.0 + - @backstage/plugin-kubernetes@0.11.0-next.2 + - @backstage/theme@0.4.3-next.0 + - @backstage/plugin-jenkins@0.8.7-next.2 + - @backstage/plugin-tech-insights@0.3.17-next.2 + - @backstage/plugin-playlist@0.1.17-next.2 + - @backstage/app-defaults@1.4.4-next.2 + - @backstage/config@1.1.1-next.0 + - @backstage/plugin-airbrake@0.3.25-next.2 + - @backstage/plugin-apache-airflow@0.2.16-next.2 + - @backstage/plugin-api-docs@0.9.12-next.2 + - @backstage/plugin-azure-devops@0.3.7-next.2 + - @backstage/plugin-azure-sites@0.1.14-next.2 + - @backstage/plugin-badges@0.2.49-next.2 + - @backstage/plugin-catalog-common@1.0.17-next.0 + - @backstage/plugin-catalog-graph@0.2.37-next.2 + - @backstage/plugin-catalog-import@0.10.1-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.1.4-next.2 + - @backstage/plugin-circleci@0.3.25-next.2 + - @backstage/plugin-cloudbuild@0.3.25-next.2 + - @backstage/plugin-code-coverage@0.2.18-next.2 + - @backstage/plugin-cost-insights@0.12.14-next.2 + - @backstage/plugin-devtools@0.1.5-next.2 + - @backstage/plugin-dynatrace@7.0.5-next.2 + - @backstage/plugin-entity-feedback@0.2.8-next.2 + - @backstage/plugin-explore@0.4.11-next.2 + - @backstage/plugin-gcalendar@0.3.19-next.2 + - @backstage/plugin-gcp-projects@0.3.42-next.2 + - @backstage/plugin-github-actions@0.6.6-next.2 + - @backstage/plugin-gocd@0.1.31-next.2 + - @backstage/plugin-home@0.5.9-next.2 + - @backstage/plugin-kafka@0.3.25-next.2 + - @backstage/plugin-lighthouse@0.4.10-next.2 + - @backstage/plugin-linguist@0.1.10-next.2 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-microsoft-calendar@0.1.8-next.2 + - @backstage/plugin-nomad@0.1.6-next.2 + - @backstage/plugin-octopus-deploy@0.2.7-next.2 + - @backstage/plugin-org@0.6.15-next.2 + - @backstage/plugin-pagerduty@0.6.6-next.2 + - @backstage/plugin-permission-react@0.4.16-next.1 + - @backstage/plugin-puppetdb@0.1.8-next.2 + - @backstage/plugin-rollbar@0.4.25-next.2 + - @backstage/plugin-scaffolder-react@1.5.6-next.2 + - @backstage/plugin-search@1.4.1-next.2 + - @backstage/plugin-search-common@1.2.7-next.0 + - @backstage/plugin-sentry@0.5.10-next.2 + - @backstage/plugin-shortcuts@0.3.15-next.2 + - @backstage/plugin-stack-overflow@0.1.21-next.2 + - @backstage/plugin-stackstorm@0.1.7-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.1-next.2 + - @backstage/plugin-techdocs-react@1.1.12-next.2 + - @backstage/plugin-todo@0.2.28-next.2 + - @backstage/plugin-user-settings@0.7.11-next.2 + +## 0.2.88-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-org@0.6.15-next.1 + - @backstage/plugin-code-coverage@0.2.18-next.1 + - @backstage/plugin-catalog@1.14.0-next.1 + - @backstage/plugin-techdocs@1.7.1-next.1 + - @backstage/plugin-home@0.5.9-next.1 + - @backstage/core-components@0.13.6-next.1 + - @backstage/core-app-api@1.10.1-next.1 + - @backstage/app-defaults@1.4.4-next.1 + - @backstage/frontend-app-api@0.2.0-next.1 + - @backstage/plugin-search@1.4.1-next.1 + - @backstage/cli@0.23.0-next.1 + - @backstage/plugin-cost-insights@0.12.14-next.1 + - @backstage/plugin-kubernetes@0.11.0-next.1 + - @backstage/plugin-adr@0.6.8-next.1 + - @backstage/plugin-explore@0.4.11-next.1 + - @backstage/plugin-search-react@1.7.1-next.1 + - @backstage/plugin-api-docs@0.9.12-next.1 + - @backstage/plugin-catalog-graph@0.2.37-next.1 + - @backstage/plugin-scaffolder@1.15.1-next.1 + - @backstage/plugin-scaffolder-react@1.5.6-next.1 + - @backstage/plugin-user-settings@0.7.11-next.1 + - @backstage/plugin-graphiql@0.2.55-next.1 + - @backstage/plugin-tech-radar@0.6.9-next.1 + - @backstage/integration-react@1.1.20-next.1 + - @backstage/plugin-airbrake@0.3.25-next.1 + - @backstage/plugin-apache-airflow@0.2.16-next.1 + - @backstage/plugin-azure-devops@0.3.7-next.1 + - @backstage/plugin-azure-sites@0.1.14-next.1 + - @backstage/plugin-badges@0.2.49-next.1 + - @backstage/plugin-catalog-import@0.10.1-next.1 + - @backstage/plugin-catalog-react@1.8.5-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.1.4-next.1 + - @backstage/plugin-circleci@0.3.25-next.1 + - @backstage/plugin-cloudbuild@0.3.25-next.1 + - @backstage/plugin-devtools@0.1.5-next.1 + - @backstage/plugin-dynatrace@7.0.5-next.1 + - @backstage/plugin-entity-feedback@0.2.8-next.1 + - @backstage/plugin-gcalendar@0.3.19-next.1 + - @backstage/plugin-gcp-projects@0.3.42-next.1 + - @backstage/plugin-github-actions@0.6.6-next.1 + - @backstage/plugin-gocd@0.1.31-next.1 + - @backstage/plugin-jenkins@0.8.7-next.1 + - @backstage/plugin-kafka@0.3.25-next.1 + - @backstage/plugin-lighthouse@0.4.10-next.1 + - @backstage/plugin-linguist@0.1.10-next.1 + - @backstage/plugin-microsoft-calendar@0.1.8-next.1 + - @backstage/plugin-newrelic@0.3.41-next.1 + - @backstage/plugin-newrelic-dashboard@0.2.18-next.1 + - @backstage/plugin-nomad@0.1.6-next.1 + - @backstage/plugin-octopus-deploy@0.2.7-next.1 + - @backstage/plugin-pagerduty@0.6.6-next.1 + - @backstage/plugin-playlist@0.1.17-next.1 + - @backstage/plugin-puppetdb@0.1.8-next.1 + - @backstage/plugin-rollbar@0.4.25-next.1 + - @backstage/plugin-sentry@0.5.10-next.1 + - @backstage/plugin-shortcuts@0.3.15-next.1 + - @backstage/plugin-stack-overflow@0.1.21-next.1 + - @backstage/plugin-stackstorm@0.1.7-next.1 + - @backstage/plugin-tech-insights@0.3.17-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.1-next.1 + - @backstage/plugin-techdocs-react@1.1.12-next.1 + - @backstage/plugin-todo@0.2.28-next.1 + - @backstage/core-plugin-api@1.7.0-next.0 + - @backstage/config@1.1.0 + - @backstage/plugin-permission-react@0.4.16-next.0 + - @backstage/catalog-model@1.4.2 + - @backstage/theme@0.4.2 + - @backstage/plugin-catalog-common@1.0.16 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.6 + +## 0.2.88-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.2.0-next.0 + - @backstage/plugin-pagerduty@0.6.6-next.0 + - @backstage/plugin-catalog-react@1.8.5-next.0 + - @backstage/cli@0.23.0-next.0 + - @backstage/core-plugin-api@1.7.0-next.0 + - @backstage/plugin-catalog@1.14.0-next.0 + - @backstage/plugin-scaffolder@1.15.1-next.0 + - @backstage/plugin-tech-radar@0.6.9-next.0 + - @backstage/plugin-user-settings@0.7.11-next.0 + - @backstage/plugin-api-docs@0.9.12-next.0 + - @backstage/plugin-code-coverage@0.2.18-next.0 + - @backstage/plugin-cost-insights@0.12.14-next.0 + - @backstage/core-components@0.13.6-next.0 + - @backstage/integration-react@1.1.20-next.0 + - @backstage/plugin-catalog-import@0.10.1-next.0 + - @backstage/plugin-github-actions@0.6.6-next.0 + - @backstage/plugin-techdocs@1.7.1-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.1-next.0 + - @backstage/plugin-adr@0.6.8-next.0 + - @backstage/plugin-airbrake@0.3.25-next.0 + - @backstage/plugin-azure-devops@0.3.7-next.0 + - @backstage/plugin-azure-sites@0.1.14-next.0 + - @backstage/plugin-badges@0.2.49-next.0 + - @backstage/plugin-catalog-graph@0.2.37-next.0 + - @backstage/plugin-circleci@0.3.25-next.0 + - @backstage/plugin-cloudbuild@0.3.25-next.0 + - @backstage/plugin-dynatrace@7.0.5-next.0 + - @backstage/plugin-entity-feedback@0.2.8-next.0 + - @backstage/plugin-explore@0.4.11-next.0 + - @backstage/plugin-gocd@0.1.31-next.0 + - @backstage/plugin-home@0.5.9-next.0 + - @backstage/plugin-jenkins@0.8.7-next.0 + - @backstage/plugin-kafka@0.3.25-next.0 + - @backstage/plugin-kubernetes@0.10.4-next.0 + - @backstage/plugin-lighthouse@0.4.10-next.0 + - @backstage/plugin-linguist@0.1.10-next.0 + - @backstage/plugin-newrelic-dashboard@0.2.18-next.0 + - @backstage/plugin-nomad@0.1.6-next.0 + - @backstage/plugin-octopus-deploy@0.2.7-next.0 + - @backstage/plugin-org@0.6.15-next.0 + - @backstage/plugin-playlist@0.1.17-next.0 + - @backstage/plugin-puppetdb@0.1.8-next.0 + - @backstage/plugin-rollbar@0.4.25-next.0 + - @backstage/plugin-scaffolder-react@1.5.6-next.0 + - @backstage/plugin-search@1.4.1-next.0 + - @backstage/plugin-sentry@0.5.10-next.0 + - @backstage/plugin-tech-insights@0.3.17-next.0 + - @backstage/plugin-todo@0.2.28-next.0 + - @backstage/app-defaults@1.4.4-next.0 + - @backstage/config@1.1.0 + - @backstage/core-app-api@1.10.1-next.0 + - @backstage/plugin-apache-airflow@0.2.16-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.1.4-next.0 + - @backstage/plugin-devtools@0.1.5-next.0 + - @backstage/plugin-gcalendar@0.3.19-next.0 + - @backstage/plugin-gcp-projects@0.3.42-next.0 + - @backstage/plugin-graphiql@0.2.55-next.0 + - @backstage/plugin-microsoft-calendar@0.1.8-next.0 + - @backstage/plugin-newrelic@0.3.41-next.0 + - @backstage/plugin-permission-react@0.4.16-next.0 + - @backstage/plugin-search-react@1.7.1-next.0 + - @backstage/plugin-shortcuts@0.3.15-next.0 + - @backstage/plugin-stack-overflow@0.1.21-next.0 + - @backstage/plugin-stackstorm@0.1.7-next.0 + - @backstage/plugin-techdocs-react@1.1.12-next.0 + - @backstage/catalog-model@1.4.2 + - @backstage/theme@0.4.2 + - @backstage/plugin-catalog-common@1.0.16 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.6 + +## 0.2.87 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes@0.10.3 + - @backstage/plugin-playlist@0.1.16 + - @backstage/plugin-home@0.5.8 + - @backstage/cli@0.22.13 + - @backstage/plugin-catalog-unprocessed-entities@0.1.3 + - @backstage/integration-react@1.1.19 + - @backstage/plugin-microsoft-calendar@0.1.7 + - @backstage/plugin-newrelic-dashboard@0.2.17 + - @backstage/plugin-apache-airflow@0.2.15 + - @backstage/plugin-github-actions@0.6.5 + - @backstage/plugin-octopus-deploy@0.2.6 + - @backstage/plugin-azure-devops@0.3.6 + - @backstage/plugin-gcp-projects@0.3.41 + - @backstage/plugin-azure-sites@0.1.13 + - @backstage/plugin-cloudbuild@0.3.24 + - @backstage/plugin-lighthouse@0.4.9 + - @backstage/plugin-tech-radar@0.6.8 + - @backstage/plugin-dynatrace@7.0.4 + - @backstage/plugin-gcalendar@0.3.18 + - @backstage/plugin-shortcuts@0.3.14 + - @backstage/plugin-airbrake@0.3.24 + - @backstage/plugin-circleci@0.3.24 + - @backstage/plugin-graphiql@0.2.54 + - @backstage/plugin-linguist@0.1.9 + - @backstage/plugin-newrelic@0.3.40 + - @backstage/plugin-puppetdb@0.1.7 + - @backstage/plugin-jenkins@0.8.6 + - @backstage/plugin-rollbar@0.4.24 + - @backstage/plugin-badges@0.2.48 + - @backstage/plugin-sentry@0.5.9 + - @backstage/plugin-kafka@0.3.24 + - @backstage/plugin-nomad@0.1.5 + - @backstage/plugin-todo@0.2.26 + - @backstage/plugin-org@0.6.14 + - @backstage/frontend-app-api@0.1.0 + - @backstage/plugin-techdocs@1.7.0 + - @backstage/plugin-catalog-react@1.8.4 + - @backstage/core-components@0.13.5 + - @backstage/plugin-entity-feedback@0.2.7 + - @backstage/plugin-pagerduty@0.6.5 + - @backstage/config@1.1.0 + - @backstage/app-defaults@1.4.3 + - @backstage/catalog-model@1.4.2 + - @backstage/core-app-api@1.10.0 + - @backstage/core-plugin-api@1.6.0 + - @backstage/plugin-adr@0.6.7 + - @backstage/plugin-api-docs@0.9.11 + - @backstage/plugin-catalog-common@1.0.16 + - @backstage/plugin-catalog-graph@0.2.36 + - @backstage/plugin-catalog-import@0.10.0 + - @backstage/plugin-code-coverage@0.2.17 + - @backstage/plugin-cost-insights@0.12.13 + - @backstage/plugin-devtools@0.1.4 + - @backstage/plugin-explore@0.4.10 + - @backstage/plugin-gocd@0.1.30 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-permission-react@0.4.15 + - @backstage/plugin-scaffolder@1.15.0 + - @backstage/plugin-scaffolder-react@1.5.5 + - @backstage/plugin-search@1.4.0 + - @backstage/plugin-search-common@1.2.6 + - @backstage/plugin-search-react@1.7.0 + - @backstage/plugin-stack-overflow@0.1.20 + - @backstage/plugin-stackstorm@0.1.6 + - @backstage/plugin-tech-insights@0.3.16 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.0 + - @backstage/plugin-techdocs-react@1.1.10 + - @backstage/plugin-user-settings@0.7.9 + - @backstage/theme@0.4.2 + - @internal/plugin-catalog-customized@0.0.14 + +## 0.2.87-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-home@0.5.8-next.3 + - @backstage/plugin-graphiql@0.2.54-next.3 + - @backstage/cli@0.22.13-next.3 + - @backstage/plugin-kubernetes@0.10.3-next.3 + - @backstage/app-defaults@1.4.3-next.3 + - @backstage/catalog-model@1.4.2-next.2 + - @backstage/config@1.1.0-next.2 + - @backstage/core-app-api@1.10.0-next.3 + - @backstage/core-components@0.13.5-next.3 + - @backstage/core-plugin-api@1.6.0-next.3 + - @backstage/integration-react@1.1.19-next.3 + - @backstage/plugin-adr@0.6.7-next.3 + - @backstage/plugin-airbrake@0.3.24-next.3 + - @backstage/plugin-apache-airflow@0.2.15-next.3 + - @backstage/plugin-api-docs@0.9.11-next.3 + - @backstage/plugin-azure-devops@0.3.6-next.3 + - @backstage/plugin-azure-sites@0.1.13-next.3 + - @backstage/plugin-badges@0.2.48-next.3 + - @backstage/plugin-catalog-common@1.0.16-next.2 + - @backstage/plugin-catalog-graph@0.2.36-next.3 + - @backstage/plugin-catalog-import@0.9.14-next.3 + - @backstage/plugin-catalog-react@1.8.4-next.3 + - @backstage/plugin-catalog-unprocessed-entities@0.1.3-next.3 + - @backstage/plugin-circleci@0.3.24-next.3 + - @backstage/plugin-cloudbuild@0.3.24-next.3 + - @backstage/plugin-code-coverage@0.2.17-next.3 + - @backstage/plugin-cost-insights@0.12.13-next.3 + - @backstage/plugin-devtools@0.1.4-next.3 + - @backstage/plugin-dynatrace@7.0.4-next.3 + - @backstage/plugin-entity-feedback@0.2.7-next.3 + - @backstage/plugin-explore@0.4.10-next.3 + - @backstage/plugin-gcalendar@0.3.18-next.3 + - @backstage/plugin-gcp-projects@0.3.41-next.3 + - @backstage/plugin-github-actions@0.6.5-next.3 + - @backstage/plugin-gocd@0.1.30-next.3 + - @backstage/plugin-jenkins@0.8.6-next.3 + - @backstage/plugin-kafka@0.3.24-next.3 + - @backstage/plugin-lighthouse@0.4.9-next.3 + - @backstage/plugin-linguist@0.1.9-next.3 + - @backstage/plugin-linguist-common@0.1.2-next.0 + - @backstage/plugin-microsoft-calendar@0.1.7-next.3 + - @backstage/plugin-newrelic@0.3.40-next.3 + - @backstage/plugin-newrelic-dashboard@0.2.17-next.3 + - @backstage/plugin-nomad@0.1.5-next.3 + - @backstage/plugin-octopus-deploy@0.2.6-next.3 + - @backstage/plugin-org@0.6.14-next.3 + - @backstage/plugin-pagerduty@0.6.5-next.3 + - @backstage/plugin-permission-react@0.4.15-next.3 + - @backstage/plugin-playlist@0.1.16-next.3 + - @backstage/plugin-puppetdb@0.1.7-next.3 + - @backstage/plugin-rollbar@0.4.24-next.3 + - @backstage/plugin-scaffolder@1.15.0-next.3 + - @backstage/plugin-scaffolder-react@1.5.5-next.3 + - @backstage/plugin-search@1.4.0-next.3 + - @backstage/plugin-search-common@1.2.6-next.2 + - @backstage/plugin-search-react@1.7.0-next.3 + - @backstage/plugin-sentry@0.5.9-next.3 + - @backstage/plugin-shortcuts@0.3.14-next.3 + - @backstage/plugin-stack-overflow@0.1.20-next.3 + - @backstage/plugin-stackstorm@0.1.6-next.3 + - @backstage/plugin-tech-insights@0.3.16-next.3 + - @backstage/plugin-tech-radar@0.6.8-next.3 + - @backstage/plugin-techdocs@1.7.0-next.3 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.0-next.3 + - @backstage/plugin-techdocs-react@1.1.10-next.3 + - @backstage/plugin-todo@0.2.26-next.3 + - @backstage/plugin-user-settings@0.7.9-next.3 + - @backstage/theme@0.4.2-next.0 + - @internal/plugin-catalog-customized@0.0.14-next.3 + +## 0.2.87-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-playlist@0.1.16-next.2 + - @backstage/plugin-tech-radar@0.6.8-next.2 + - @backstage/plugin-user-settings@0.7.9-next.2 + - @backstage/plugin-org@0.6.14-next.2 + - @backstage/plugin-techdocs@1.7.0-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.0-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.1.3-next.2 + - @backstage/integration-react@1.1.19-next.2 + - @backstage/plugin-microsoft-calendar@0.1.7-next.2 + - @backstage/core-components@0.13.5-next.2 + - @backstage/core-plugin-api@1.6.0-next.2 + - @backstage/plugin-entity-feedback@0.2.7-next.2 + - @backstage/plugin-apache-airflow@0.2.15-next.2 + - @backstage/plugin-github-actions@0.6.5-next.2 + - @backstage/plugin-octopus-deploy@0.2.6-next.2 + - @backstage/plugin-stack-overflow@0.1.20-next.2 + - @backstage/app-defaults@1.4.3-next.2 + - @backstage/core-app-api@1.10.0-next.2 + - @backstage/plugin-code-coverage@0.2.17-next.2 + - @backstage/plugin-cost-insights@0.12.13-next.2 + - @backstage/plugin-tech-insights@0.3.16-next.2 + - @backstage/plugin-azure-devops@0.3.6-next.2 + - @backstage/plugin-gcp-projects@0.3.41-next.2 + - @backstage/plugin-azure-sites@0.1.13-next.2 + - @backstage/plugin-cloudbuild@0.3.24-next.2 + - @backstage/plugin-kubernetes@0.10.3-next.2 + - @backstage/plugin-lighthouse@0.4.9-next.2 + - @backstage/plugin-scaffolder@1.14.5-next.2 + - @backstage/plugin-stackstorm@0.1.6-next.2 + - @backstage/plugin-dynatrace@7.0.4-next.2 + - @backstage/plugin-gcalendar@0.3.18-next.2 + - @backstage/plugin-pagerduty@0.6.5-next.2 + - @backstage/plugin-shortcuts@0.3.14-next.2 + - @backstage/plugin-airbrake@0.3.24-next.2 + - @backstage/plugin-api-docs@0.9.11-next.2 + - @backstage/plugin-circleci@0.3.24-next.2 + - @backstage/plugin-devtools@0.1.4-next.2 + - @backstage/plugin-graphiql@0.2.54-next.2 + - @backstage/plugin-linguist@0.1.9-next.2 + - @backstage/plugin-newrelic@0.3.40-next.2 + - @backstage/plugin-puppetdb@0.1.7-next.2 + - @backstage/config@1.1.0-next.1 + - @backstage/plugin-explore@0.4.10-next.2 + - @backstage/plugin-jenkins@0.8.6-next.2 + - @backstage/plugin-rollbar@0.4.24-next.2 + - @backstage/plugin-badges@0.2.48-next.2 + - @backstage/plugin-search@1.4.0-next.2 + - @backstage/plugin-sentry@0.5.9-next.2 + - @backstage/plugin-kafka@0.3.24-next.2 + - @backstage/plugin-nomad@0.1.5-next.2 + - @backstage/cli@0.22.13-next.2 + - @backstage/plugin-gocd@0.1.30-next.2 + - @backstage/plugin-home@0.5.8-next.2 + - @backstage/plugin-todo@0.2.26-next.2 + - @backstage/plugin-adr@0.6.7-next.2 + - @backstage/plugin-scaffolder-react@1.5.5-next.2 + - @internal/plugin-catalog-customized@0.0.14-next.2 + - @backstage/plugin-catalog-graph@0.2.36-next.2 + - @backstage/plugin-catalog-import@0.9.14-next.2 + - @backstage/plugin-catalog-react@1.8.4-next.2 + - @backstage/plugin-newrelic-dashboard@0.2.17-next.2 + - @backstage/plugin-search-react@1.7.0-next.2 + - @backstage/plugin-techdocs-react@1.1.10-next.2 + - @backstage/plugin-permission-react@0.4.15-next.2 + - @backstage/catalog-model@1.4.2-next.1 + - @backstage/theme@0.4.1 + - @backstage/plugin-catalog-common@1.0.16-next.1 + - @backstage/plugin-linguist-common@0.1.1 + - @backstage/plugin-search-common@1.2.6-next.1 + +## 0.2.87-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-playlist@0.1.16-next.1 + - @backstage/plugin-tech-radar@0.6.8-next.1 + - @backstage/plugin-catalog-react@1.8.4-next.1 + - @backstage/core-components@0.13.5-next.1 + - @backstage/config@1.1.0-next.0 + - @backstage/plugin-org@0.6.14-next.1 + - @backstage/plugin-home@0.5.8-next.1 + - @backstage/plugin-devtools@0.1.4-next.1 + - @backstage/plugin-user-settings@0.7.9-next.1 + - @backstage/plugin-search-react@1.7.0-next.1 + - @backstage/plugin-search@1.4.0-next.1 + - @backstage/plugin-kubernetes@0.10.3-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.0-next.1 + - @backstage/plugin-adr@0.6.7-next.1 + - @backstage/plugin-airbrake@0.3.24-next.1 + - @backstage/plugin-api-docs@0.9.11-next.1 + - @backstage/plugin-azure-devops@0.3.6-next.1 + - @backstage/plugin-azure-sites@0.1.13-next.1 + - @backstage/plugin-badges@0.2.48-next.1 + - @internal/plugin-catalog-customized@0.0.14-next.1 + - @backstage/plugin-catalog-graph@0.2.36-next.1 + - @backstage/plugin-catalog-import@0.9.14-next.1 + - @backstage/plugin-circleci@0.3.24-next.1 + - @backstage/plugin-cloudbuild@0.3.24-next.1 + - @backstage/plugin-code-coverage@0.2.17-next.1 + - @backstage/plugin-cost-insights@0.12.13-next.1 + - @backstage/plugin-dynatrace@7.0.4-next.1 + - @backstage/plugin-entity-feedback@0.2.7-next.1 + - @backstage/plugin-explore@0.4.10-next.1 + - @backstage/plugin-github-actions@0.6.5-next.1 + - @backstage/plugin-gocd@0.1.30-next.1 + - @backstage/plugin-jenkins@0.8.6-next.1 + - @backstage/plugin-kafka@0.3.24-next.1 + - @backstage/plugin-lighthouse@0.4.9-next.1 + - @backstage/plugin-linguist@0.1.9-next.1 + - @backstage/plugin-newrelic-dashboard@0.2.17-next.1 + - @backstage/plugin-nomad@0.1.5-next.1 + - @backstage/plugin-octopus-deploy@0.2.6-next.1 + - @backstage/plugin-pagerduty@0.6.5-next.1 + - @backstage/plugin-puppetdb@0.1.7-next.1 + - @backstage/plugin-rollbar@0.4.24-next.1 + - @backstage/plugin-scaffolder@1.14.5-next.1 + - @backstage/plugin-scaffolder-react@1.5.5-next.1 + - @backstage/plugin-sentry@0.5.9-next.1 + - @backstage/plugin-tech-insights@0.3.16-next.1 + - @backstage/plugin-techdocs@1.6.9-next.1 + - @backstage/plugin-todo@0.2.26-next.1 + - @backstage/app-defaults@1.4.3-next.1 + - @backstage/cli@0.22.13-next.1 + - @backstage/integration-react@1.1.19-next.1 + - @backstage/plugin-apache-airflow@0.2.15-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.1.3-next.1 + - @backstage/plugin-gcalendar@0.3.18-next.1 + - @backstage/plugin-gcp-projects@0.3.41-next.1 + - @backstage/plugin-graphiql@0.2.54-next.1 + - @backstage/plugin-microsoft-calendar@0.1.7-next.1 + - @backstage/plugin-newrelic@0.3.40-next.1 + - @backstage/plugin-shortcuts@0.3.14-next.1 + - @backstage/plugin-stack-overflow@0.1.20-next.1 + - @backstage/plugin-stackstorm@0.1.6-next.1 + - @backstage/plugin-techdocs-react@1.1.10-next.1 + - @backstage/catalog-model@1.4.2-next.0 + - @backstage/core-app-api@1.10.0-next.1 + - @backstage/core-plugin-api@1.6.0-next.1 + - @backstage/plugin-permission-react@0.4.15-next.1 + - @backstage/plugin-catalog-common@1.0.16-next.0 + - @backstage/theme@0.4.1 + - @backstage/plugin-linguist-common@0.1.1 + - @backstage/plugin-search-common@1.2.6-next.0 + +## 0.2.87-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-unprocessed-entities@0.1.3-next.0 + - @backstage/integration-react@1.1.18-next.0 + - @backstage/plugin-microsoft-calendar@0.1.7-next.0 + - @backstage/plugin-newrelic-dashboard@0.2.16-next.0 + - @backstage/plugin-apache-airflow@0.2.15-next.0 + - @backstage/plugin-github-actions@0.6.4-next.0 + - @backstage/plugin-octopus-deploy@0.2.5-next.0 + - @backstage/plugin-azure-devops@0.3.5-next.0 + - @backstage/plugin-gcp-projects@0.3.41-next.0 + - @backstage/plugin-azure-sites@0.1.12-next.0 + - @backstage/plugin-cloudbuild@0.3.23-next.0 + - @backstage/plugin-lighthouse@0.4.8-next.0 + - @backstage/plugin-tech-radar@0.6.8-next.0 + - @backstage/plugin-dynatrace@7.0.3-next.0 + - @backstage/plugin-gcalendar@0.3.18-next.0 + - @backstage/plugin-shortcuts@0.3.14-next.0 + - @backstage/plugin-airbrake@0.3.23-next.0 + - @backstage/plugin-circleci@0.3.23-next.0 + - @backstage/plugin-graphiql@0.2.54-next.0 + - @backstage/plugin-linguist@0.1.8-next.0 + - @backstage/plugin-newrelic@0.3.40-next.0 + - @backstage/plugin-playlist@0.1.15-next.0 + - @backstage/plugin-puppetdb@0.1.6-next.0 + - @backstage/plugin-jenkins@0.8.5-next.0 + - @backstage/plugin-rollbar@0.4.23-next.0 + - @backstage/plugin-badges@0.2.47-next.0 + - @backstage/plugin-sentry@0.5.8-next.0 + - @backstage/plugin-kafka@0.3.23-next.0 + - @backstage/plugin-nomad@0.1.4-next.0 + - @backstage/plugin-todo@0.2.25-next.0 + - @backstage/plugin-org@0.6.13-next.0 + - @backstage/cli@0.22.12-next.0 + - @backstage/plugin-techdocs@1.6.8-next.0 + - @backstage/plugin-entity-feedback@0.2.6-next.0 + - @backstage/core-app-api@1.10.0-next.0 + - @backstage/plugin-code-coverage@0.2.16-next.0 + - @backstage/core-plugin-api@1.6.0-next.0 + - @backstage/plugin-cost-insights@0.12.12-next.0 + - @backstage/core-components@0.13.5-next.0 + - @backstage/plugin-adr@0.6.6-next.0 + - @backstage/plugin-catalog-import@0.9.13-next.0 + - @backstage/plugin-scaffolder@1.14.4-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.18-next.0 + - @backstage/plugin-home@0.5.7-next.0 + - @backstage/plugin-pagerduty@0.6.4-next.0 + - @backstage/plugin-stack-overflow@0.1.20-next.0 + - @backstage/plugin-api-docs@0.9.10-next.0 + - @internal/plugin-catalog-customized@0.0.14-next.0 + - @backstage/plugin-catalog-graph@0.2.35-next.0 + - @backstage/plugin-explore@0.4.9-next.0 + - @backstage/plugin-scaffolder-react@1.5.4-next.0 + - @backstage/app-defaults@1.4.3-next.0 + - @backstage/catalog-model@1.4.1 + - @backstage/config@1.0.8 + - @backstage/theme@0.4.1 + - @backstage/plugin-catalog-common@1.0.15 + - @backstage/plugin-catalog-react@1.8.3-next.0 + - @backstage/plugin-devtools@0.1.4-next.0 + - @backstage/plugin-gocd@0.1.29-next.0 + - @backstage/plugin-kubernetes@0.10.2-next.0 + - @backstage/plugin-linguist-common@0.1.1 + - @backstage/plugin-permission-react@0.4.15-next.0 + - @backstage/plugin-search@1.3.6-next.0 + - @backstage/plugin-search-common@1.2.5 + - @backstage/plugin-search-react@1.6.5-next.0 + - @backstage/plugin-stackstorm@0.1.6-next.0 + - @backstage/plugin-tech-insights@0.3.15-next.0 + - @backstage/plugin-techdocs-react@1.1.10-next.0 + - @backstage/plugin-user-settings@0.7.8-next.0 + +## 0.2.86 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.9.1 + - @backstage/plugin-catalog-graph@0.2.33 + - @backstage/plugin-playlist@0.1.13 + - @backstage/plugin-pagerduty@0.6.2 + - @backstage/plugin-scaffolder@1.14.2 + - @backstage/plugin-explore@0.4.7 + - @backstage/plugin-org@0.6.11 + - @backstage/integration-react@1.1.16 + - @backstage/plugin-microsoft-calendar@0.1.6 + - @backstage/plugin-newrelic-dashboard@0.2.14 + - @backstage/plugin-entity-feedback@0.2.4 + - @backstage/plugin-linguist-common@0.1.1 + - @backstage/plugin-apache-airflow@0.2.14 + - @backstage/plugin-octopus-deploy@0.2.3 + - @backstage/plugin-stack-overflow@0.1.19 + - @backstage/plugin-code-coverage@0.2.14 + - @backstage/plugin-tech-insights@0.3.13 + - @backstage/plugin-dynatrace@7.0.1 + - @backstage/plugin-gcalendar@0.3.17 + - @backstage/plugin-shortcuts@0.3.13 + - @backstage/plugin-airbrake@0.3.21 + - @backstage/plugin-kafka@0.3.21 + - @backstage/plugin-nomad@0.1.2 + - @backstage/plugin-adr@0.6.4 + - @backstage/plugin-newrelic@0.3.39 + - @backstage/cli@0.22.10 + - @backstage/plugin-kubernetes@0.10.0 + - @backstage/core-components@0.13.4 + - @backstage/plugin-home@0.5.5 + - @backstage/plugin-catalog-react@1.8.1 + - @backstage/plugin-scaffolder-react@1.5.2 + - @backstage/plugin-devtools@0.1.3 + - @backstage/app-defaults@1.4.2 + - @backstage/core-plugin-api@1.5.3 + - @backstage/plugin-api-docs@0.9.7 + - @backstage/plugin-azure-devops@0.3.3 + - @backstage/plugin-azure-sites@0.1.10 + - @backstage/plugin-badges@0.2.45 + - @backstage/plugin-catalog-import@0.9.11 + - @backstage/plugin-catalog-unprocessed-entities@0.1.2 + - @backstage/plugin-circleci@0.3.21 + - @backstage/plugin-cloudbuild@0.3.21 + - @backstage/plugin-cost-insights@0.12.10 + - @backstage/plugin-gcp-projects@0.3.40 + - @backstage/plugin-github-actions@0.6.2 + - @backstage/plugin-gocd@0.1.27 + - @backstage/plugin-graphiql@0.2.53 + - @backstage/plugin-jenkins@0.8.3 + - @backstage/plugin-lighthouse@0.4.6 + - @backstage/plugin-linguist@0.1.6 + - @backstage/plugin-puppetdb@0.1.4 + - @backstage/plugin-rollbar@0.4.21 + - @backstage/plugin-search@1.3.4 + - @backstage/plugin-search-react@1.6.4 + - @backstage/plugin-sentry@0.5.6 + - @backstage/plugin-stackstorm@0.1.5 + - @backstage/plugin-tech-radar@0.6.7 + - @backstage/plugin-techdocs@1.6.6 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.16 + - @backstage/plugin-todo@0.2.23 + - @backstage/plugin-user-settings@0.7.6 + - @backstage/catalog-model@1.4.1 + - @backstage/config@1.0.8 + - @backstage/theme@0.4.1 + - @backstage/plugin-catalog-common@1.0.15 + - @backstage/plugin-permission-react@0.4.14 + - @backstage/plugin-search-common@1.2.5 + - @backstage/plugin-techdocs-react@1.1.9 + - @internal/plugin-catalog-customized@0.0.13 + +## 0.2.86-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-graph@0.2.33-next.2 + - @backstage/plugin-linguist-common@0.1.1-next.1 + - @backstage/plugin-catalog-react@1.8.1-next.1 + - @backstage/plugin-linguist@0.1.6-next.2 + - @backstage/plugin-adr@0.6.4-next.2 + - @backstage/plugin-airbrake@0.3.21-next.2 + - @backstage/plugin-api-docs@0.9.7-next.2 + - @backstage/plugin-azure-devops@0.3.3-next.1 + - @backstage/plugin-azure-sites@0.1.10-next.1 + - @backstage/plugin-badges@0.2.45-next.1 + - @internal/plugin-catalog-customized@0.0.13-next.2 + - @backstage/plugin-catalog-import@0.9.11-next.2 + - @backstage/plugin-circleci@0.3.21-next.1 + - @backstage/plugin-cloudbuild@0.3.21-next.1 + - @backstage/plugin-code-coverage@0.2.14-next.2 + - @backstage/plugin-cost-insights@0.12.10-next.1 + - @backstage/plugin-dynatrace@7.0.1-next.2 + - @backstage/plugin-entity-feedback@0.2.4-next.2 + - @backstage/plugin-explore@0.4.7-next.1 + - @backstage/plugin-github-actions@0.6.2-next.2 + - @backstage/plugin-gocd@0.1.27-next.1 + - @backstage/plugin-home@0.5.5-next.1 + - @backstage/plugin-jenkins@0.8.3-next.2 + - @backstage/plugin-kafka@0.3.21-next.2 + - @backstage/plugin-kubernetes@0.9.4-next.1 + - @backstage/plugin-lighthouse@0.4.6-next.1 + - @backstage/plugin-newrelic-dashboard@0.2.14-next.2 + - @backstage/plugin-nomad@0.1.2-next.2 + - @backstage/plugin-octopus-deploy@0.2.3-next.2 + - @backstage/plugin-org@0.6.11-next.2 + - @backstage/plugin-pagerduty@0.6.2-next.1 + - @backstage/plugin-playlist@0.1.13-next.2 + - @backstage/plugin-puppetdb@0.1.4-next.1 + - @backstage/plugin-rollbar@0.4.21-next.1 + - @backstage/plugin-scaffolder@1.14.2-next.2 + - @backstage/plugin-scaffolder-react@1.5.2-next.1 + - @backstage/plugin-search@1.3.4-next.1 + - @backstage/plugin-sentry@0.5.6-next.1 + - @backstage/plugin-tech-insights@0.3.13-next.2 + - @backstage/plugin-techdocs@1.6.6-next.2 + - @backstage/plugin-todo@0.2.23-next.1 + - @backstage/plugin-user-settings@0.7.6-next.1 + - @backstage/cli@0.22.10-next.1 + - @backstage/integration-react@1.1.16-next.1 + - @backstage/plugin-apache-airflow@0.2.14-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.1.2-next.0 + - @backstage/plugin-devtools@0.1.3-next.1 + - @backstage/plugin-gcalendar@0.3.17-next.1 + - @backstage/plugin-gcp-projects@0.3.40-next.0 + - @backstage/plugin-graphiql@0.2.53-next.0 + - @backstage/plugin-microsoft-calendar@0.1.6-next.1 + - @backstage/plugin-newrelic@0.3.39-next.0 + - @backstage/plugin-shortcuts@0.3.13-next.1 + - @backstage/plugin-stack-overflow@0.1.19-next.1 + - @backstage/plugin-stackstorm@0.1.5-next.0 + - @backstage/plugin-tech-radar@0.6.7-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.16-next.1 + +## 0.2.86-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-playlist@0.1.13-next.1 + - @backstage/plugin-scaffolder@1.14.2-next.1 + - @backstage/integration-react@1.1.16-next.1 + - @backstage/plugin-microsoft-calendar@0.1.6-next.1 + - @backstage/plugin-newrelic-dashboard@0.2.14-next.1 + - @backstage/plugin-entity-feedback@0.2.4-next.1 + - @backstage/plugin-linguist-common@0.1.1-next.0 + - @backstage/plugin-apache-airflow@0.2.14-next.1 + - @backstage/plugin-octopus-deploy@0.2.3-next.1 + - @backstage/plugin-stack-overflow@0.1.19-next.1 + - @backstage/plugin-catalog-graph@0.2.33-next.1 + - @backstage/plugin-code-coverage@0.2.14-next.1 + - @backstage/plugin-tech-insights@0.3.13-next.1 + - @backstage/plugin-dynatrace@7.0.1-next.1 + - @backstage/plugin-gcalendar@0.3.17-next.1 + - @backstage/plugin-shortcuts@0.3.13-next.1 + - @backstage/plugin-airbrake@0.3.21-next.1 + - @backstage/plugin-kafka@0.3.21-next.1 + - @backstage/plugin-nomad@0.1.2-next.1 + - @backstage/plugin-adr@0.6.4-next.1 + - @backstage/plugin-org@0.6.11-next.1 + - @backstage/plugin-devtools@0.1.3-next.1 + - @backstage/cli@0.22.10-next.1 + - @backstage/plugin-catalog-import@0.9.11-next.1 + - @backstage/plugin-github-actions@0.6.2-next.1 + - @backstage/plugin-techdocs@1.6.6-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.16-next.1 + - @backstage/plugin-linguist@0.1.6-next.1 + - @backstage/plugin-jenkins@0.8.3-next.1 + - @backstage/app-defaults@1.4.2-next.0 + - @backstage/catalog-model@1.4.1 + - @backstage/config@1.0.8 + - @backstage/core-app-api@1.9.1-next.0 + - @backstage/core-components@0.13.4-next.0 + - @backstage/core-plugin-api@1.5.3 + - @backstage/theme@0.4.1 + - @backstage/plugin-api-docs@0.9.7-next.1 + - @backstage/plugin-azure-devops@0.3.3-next.0 + - @backstage/plugin-azure-sites@0.1.10-next.0 + - @backstage/plugin-badges@0.2.45-next.0 + - @backstage/plugin-catalog-common@1.0.15 + - @backstage/plugin-catalog-react@1.8.1-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.1.2-next.0 + - @backstage/plugin-circleci@0.3.21-next.0 + - @backstage/plugin-cloudbuild@0.3.21-next.0 + - @backstage/plugin-cost-insights@0.12.10-next.0 + - @backstage/plugin-explore@0.4.7-next.0 + - @backstage/plugin-gcp-projects@0.3.40-next.0 + - @backstage/plugin-gocd@0.1.27-next.0 + - @backstage/plugin-graphiql@0.2.53-next.0 + - @backstage/plugin-home@0.5.5-next.0 + - @backstage/plugin-kubernetes@0.9.4-next.0 + - @backstage/plugin-lighthouse@0.4.6-next.0 + - @backstage/plugin-newrelic@0.3.39-next.0 + - @backstage/plugin-pagerduty@0.6.2-next.0 + - @backstage/plugin-permission-react@0.4.14 + - @backstage/plugin-puppetdb@0.1.4-next.0 + - @backstage/plugin-rollbar@0.4.21-next.0 + - @backstage/plugin-scaffolder-react@1.5.2-next.0 + - @backstage/plugin-search@1.3.4-next.0 + - @backstage/plugin-search-common@1.2.5 + - @backstage/plugin-search-react@1.6.4-next.0 + - @backstage/plugin-sentry@0.5.6-next.0 + - @backstage/plugin-stackstorm@0.1.5-next.0 + - @backstage/plugin-tech-radar@0.6.7-next.0 + - @backstage/plugin-techdocs-react@1.1.9-next.0 + - @backstage/plugin-todo@0.2.23-next.0 + - @backstage/plugin-user-settings@0.7.6-next.0 + - @internal/plugin-catalog-customized@0.0.13-next.1 + +## 0.2.86-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.9.1-next.0 + - @backstage/plugin-catalog-graph@0.2.33-next.0 + - @backstage/cli@0.22.10-next.0 + - @backstage/core-components@0.13.4-next.0 + - @backstage/plugin-home@0.5.5-next.0 + - @backstage/plugin-scaffolder-react@1.5.2-next.0 + - @backstage/app-defaults@1.4.2-next.0 + - @backstage/core-plugin-api@1.5.3 + - @backstage/plugin-adr@0.6.4-next.0 + - @backstage/plugin-apache-airflow@0.2.14-next.0 + - @backstage/plugin-api-docs@0.9.7-next.0 + - @backstage/plugin-azure-devops@0.3.3-next.0 + - @backstage/plugin-azure-sites@0.1.10-next.0 + - @backstage/plugin-badges@0.2.45-next.0 + - @backstage/plugin-catalog-import@0.9.11-next.0 + - @backstage/plugin-catalog-react@1.8.1-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.1.2-next.0 + - @backstage/plugin-circleci@0.3.21-next.0 + - @backstage/plugin-cloudbuild@0.3.21-next.0 + - @backstage/plugin-code-coverage@0.2.14-next.0 + - @backstage/plugin-cost-insights@0.12.10-next.0 + - @backstage/plugin-devtools@0.1.3-next.0 + - @backstage/plugin-dynatrace@7.0.1-next.0 + - @backstage/plugin-entity-feedback@0.2.4-next.0 + - @backstage/plugin-explore@0.4.7-next.0 + - @backstage/plugin-gcalendar@0.3.17-next.0 + - @backstage/plugin-gcp-projects@0.3.40-next.0 + - @backstage/plugin-github-actions@0.6.2-next.0 + - @backstage/plugin-gocd@0.1.27-next.0 + - @backstage/plugin-graphiql@0.2.53-next.0 + - @backstage/plugin-jenkins@0.8.3-next.0 + - @backstage/plugin-kafka@0.3.21-next.0 + - @backstage/plugin-kubernetes@0.9.4-next.0 + - @backstage/plugin-lighthouse@0.4.6-next.0 + - @backstage/plugin-linguist@0.1.6-next.0 + - @backstage/plugin-microsoft-calendar@0.1.6-next.0 + - @backstage/plugin-newrelic@0.3.39-next.0 + - @backstage/plugin-nomad@0.1.2-next.0 + - @backstage/plugin-octopus-deploy@0.2.3-next.0 + - @backstage/plugin-org@0.6.11-next.0 + - @backstage/plugin-pagerduty@0.6.2-next.0 + - @backstage/plugin-playlist@0.1.13-next.0 + - @backstage/plugin-puppetdb@0.1.4-next.0 + - @backstage/plugin-rollbar@0.4.21-next.0 + - @backstage/plugin-scaffolder@1.14.2-next.0 + - @backstage/plugin-search@1.3.4-next.0 + - @backstage/plugin-search-react@1.6.4-next.0 + - @backstage/plugin-sentry@0.5.6-next.0 + - @backstage/plugin-shortcuts@0.3.13-next.0 + - @backstage/plugin-stack-overflow@0.1.19-next.0 + - @backstage/plugin-stackstorm@0.1.5-next.0 + - @backstage/plugin-tech-insights@0.3.13-next.0 + - @backstage/plugin-tech-radar@0.6.7-next.0 + - @backstage/plugin-techdocs@1.6.6-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.16-next.0 + - @backstage/plugin-todo@0.2.23-next.0 + - @backstage/plugin-user-settings@0.7.6-next.0 + - @backstage/catalog-model@1.4.1 + - @backstage/config@1.0.8 + - @backstage/integration-react@1.1.16-next.0 + - @backstage/theme@0.4.1 + - @backstage/plugin-airbrake@0.3.21-next.0 + - @backstage/plugin-catalog-common@1.0.15 + - @backstage/plugin-linguist-common@0.1.0 + - @backstage/plugin-newrelic-dashboard@0.2.14-next.0 + - @backstage/plugin-permission-react@0.4.14 + - @backstage/plugin-search-common@1.2.5 + - @backstage/plugin-techdocs-react@1.1.9-next.0 + - @internal/plugin-catalog-customized@0.0.13-next.0 + +## 0.2.85 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search@1.3.3 + - @backstage/plugin-devtools@0.1.2 + - @backstage/cli@0.22.9 + - @backstage/theme@0.4.1 + - @backstage/plugin-catalog-unprocessed-entities@0.1.1 + - @backstage/plugin-stackstorm@0.1.4 + - @backstage/plugin-scaffolder-react@1.5.1 + - @backstage/plugin-airbrake@0.3.20 + - @backstage/plugin-lighthouse@0.4.5 + - @backstage/plugin-catalog-import@0.9.10 + - @backstage/plugin-dynatrace@7.0.0 + - @backstage/plugin-catalog-react@1.8.0 + - @backstage/plugin-home@0.5.4 + - @backstage/core-components@0.13.3 + - @backstage/plugin-playlist@0.1.12 + - @backstage/plugin-nomad@0.1.1 + - @backstage/plugin-kubernetes@0.9.3 + - @backstage/core-app-api@1.9.0 + - @backstage/plugin-cost-insights@0.12.9 + - @backstage/plugin-user-settings@0.7.5 + - @backstage/core-plugin-api@1.5.3 + - @backstage/plugin-adr@0.6.3 + - @backstage/plugin-puppetdb@0.1.3 + - @backstage/app-defaults@1.4.1 + - @backstage/catalog-model@1.4.1 + - @backstage/config@1.0.8 + - @backstage/integration-react@1.1.15 + - @backstage/plugin-apache-airflow@0.2.13 + - @backstage/plugin-api-docs@0.9.6 + - @backstage/plugin-azure-devops@0.3.2 + - @backstage/plugin-azure-sites@0.1.9 + - @backstage/plugin-badges@0.2.44 + - @backstage/plugin-catalog-common@1.0.15 + - @backstage/plugin-catalog-graph@0.2.32 + - @backstage/plugin-circleci@0.3.20 + - @backstage/plugin-cloudbuild@0.3.20 + - @backstage/plugin-code-coverage@0.2.13 + - @backstage/plugin-entity-feedback@0.2.3 + - @backstage/plugin-explore@0.4.6 + - @backstage/plugin-gcalendar@0.3.16 + - @backstage/plugin-gcp-projects@0.3.39 + - @backstage/plugin-github-actions@0.6.1 + - @backstage/plugin-gocd@0.1.26 + - @backstage/plugin-graphiql@0.2.52 + - @backstage/plugin-jenkins@0.8.2 + - @backstage/plugin-kafka@0.3.20 + - @backstage/plugin-linguist@0.1.5 + - @backstage/plugin-linguist-common@0.1.0 + - @backstage/plugin-microsoft-calendar@0.1.5 + - @backstage/plugin-newrelic@0.3.38 + - @backstage/plugin-newrelic-dashboard@0.2.13 + - @backstage/plugin-octopus-deploy@0.2.2 + - @backstage/plugin-org@0.6.10 + - @backstage/plugin-pagerduty@0.6.1 + - @backstage/plugin-permission-react@0.4.14 + - @backstage/plugin-rollbar@0.4.20 + - @backstage/plugin-scaffolder@1.14.1 + - @backstage/plugin-search-common@1.2.5 + - @backstage/plugin-search-react@1.6.3 + - @backstage/plugin-sentry@0.5.5 + - @backstage/plugin-shortcuts@0.3.12 + - @backstage/plugin-stack-overflow@0.1.18 + - @backstage/plugin-tech-insights@0.3.12 + - @backstage/plugin-tech-radar@0.6.6 + - @backstage/plugin-techdocs@1.6.5 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.15 + - @backstage/plugin-techdocs-react@1.1.8 + - @backstage/plugin-todo@0.2.22 + - @internal/plugin-catalog-customized@0.0.12 + +## 0.2.85-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search@1.3.3-next.2 + - @backstage/plugin-airbrake@0.3.20-next.2 + - @backstage/cli@0.22.9-next.1 + - @backstage/plugin-catalog-react@1.8.0-next.2 + - @backstage/plugin-home@0.5.4-next.2 + - @backstage/plugin-cost-insights@0.12.9-next.2 + - @backstage/plugin-user-settings@0.7.5-next.2 + - @backstage/theme@0.4.1-next.1 + - @backstage/core-plugin-api@1.5.3-next.1 + - @backstage/core-components@0.13.3-next.2 + - @backstage/core-app-api@1.8.2-next.1 + - @backstage/plugin-kubernetes@0.9.3-next.2 + - @backstage/plugin-adr@0.6.3-next.2 + - @backstage/app-defaults@1.4.1-next.2 + - @backstage/catalog-model@1.4.1-next.0 + - @backstage/config@1.0.8 + - @backstage/integration-react@1.1.15-next.2 + - @backstage/plugin-apache-airflow@0.2.13-next.2 + - @backstage/plugin-api-docs@0.9.6-next.2 + - @backstage/plugin-azure-devops@0.3.2-next.2 + - @backstage/plugin-azure-sites@0.1.9-next.2 + - @backstage/plugin-badges@0.2.44-next.2 + - @backstage/plugin-catalog-common@1.0.15-next.0 + - @backstage/plugin-catalog-graph@0.2.32-next.2 + - @backstage/plugin-catalog-import@0.9.10-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.1.1-next.2 + - @backstage/plugin-circleci@0.3.20-next.2 + - @backstage/plugin-cloudbuild@0.3.20-next.2 + - @backstage/plugin-code-coverage@0.2.13-next.2 + - @backstage/plugin-devtools@0.1.2-next.2 + - @backstage/plugin-dynatrace@7.0.0-next.2 + - @backstage/plugin-entity-feedback@0.2.3-next.2 + - @backstage/plugin-explore@0.4.6-next.2 + - @backstage/plugin-gcalendar@0.3.16-next.2 + - @backstage/plugin-gcp-projects@0.3.39-next.2 + - @backstage/plugin-github-actions@0.6.1-next.2 + - @backstage/plugin-gocd@0.1.26-next.2 + - @backstage/plugin-graphiql@0.2.52-next.2 + - @backstage/plugin-jenkins@0.8.2-next.2 + - @backstage/plugin-kafka@0.3.20-next.2 + - @backstage/plugin-lighthouse@0.4.5-next.2 + - @backstage/plugin-linguist@0.1.5-next.2 + - @backstage/plugin-linguist-common@0.1.0 + - @backstage/plugin-microsoft-calendar@0.1.5-next.2 + - @backstage/plugin-newrelic@0.3.38-next.2 + - @backstage/plugin-newrelic-dashboard@0.2.13-next.2 + - @backstage/plugin-nomad@0.1.1-next.2 + - @backstage/plugin-octopus-deploy@0.2.2-next.2 + - @backstage/plugin-org@0.6.10-next.2 + - @backstage/plugin-pagerduty@0.6.1-next.2 + - @backstage/plugin-permission-react@0.4.14-next.2 + - @backstage/plugin-playlist@0.1.12-next.2 + - @backstage/plugin-puppetdb@0.1.3-next.2 + - @backstage/plugin-rollbar@0.4.20-next.2 + - @backstage/plugin-scaffolder@1.14.1-next.2 + - @backstage/plugin-scaffolder-react@1.5.1-next.2 + - @backstage/plugin-search-common@1.2.5-next.0 + - @backstage/plugin-search-react@1.6.3-next.2 + - @backstage/plugin-sentry@0.5.5-next.2 + - @backstage/plugin-shortcuts@0.3.12-next.2 + - @backstage/plugin-stack-overflow@0.1.18-next.2 + - @backstage/plugin-stackstorm@0.1.4-next.2 + - @backstage/plugin-tech-insights@0.3.12-next.2 + - @backstage/plugin-tech-radar@0.6.6-next.2 + - @backstage/plugin-techdocs@1.6.5-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.15-next.2 + - @backstage/plugin-techdocs-react@1.1.8-next.2 + - @backstage/plugin-todo@0.2.22-next.2 + - @internal/plugin-catalog-customized@0.0.12-next.2 + +## 0.2.85-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.4.1-next.0 + - @backstage/plugin-scaffolder-react@1.5.1-next.1 + - @backstage/plugin-lighthouse@0.4.5-next.1 + - @backstage/plugin-catalog-import@0.9.10-next.1 + - @backstage/core-components@0.13.3-next.1 + - @backstage/plugin-kubernetes@0.9.3-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.1.1-next.1 + - @backstage/plugin-home@0.5.4-next.1 + - @backstage/core-plugin-api@1.5.3-next.0 + - @backstage/app-defaults@1.4.1-next.1 + - @backstage/cli@0.22.9-next.0 + - @backstage/integration-react@1.1.15-next.1 + - @backstage/plugin-adr@0.6.3-next.1 + - @backstage/plugin-airbrake@0.3.20-next.1 + - @backstage/plugin-api-docs@0.9.6-next.1 + - @backstage/plugin-azure-devops@0.3.2-next.1 + - @backstage/plugin-azure-sites@0.1.9-next.1 + - @backstage/plugin-badges@0.2.44-next.1 + - @backstage/plugin-catalog-graph@0.2.32-next.1 + - @backstage/plugin-catalog-react@1.7.1-next.1 + - @backstage/plugin-circleci@0.3.20-next.1 + - @backstage/plugin-cloudbuild@0.3.20-next.1 + - @backstage/plugin-code-coverage@0.2.13-next.1 + - @backstage/plugin-cost-insights@0.12.9-next.1 + - @backstage/plugin-devtools@0.1.2-next.1 + - @backstage/plugin-dynatrace@6.0.1-next.1 + - @backstage/plugin-entity-feedback@0.2.3-next.1 + - @backstage/plugin-explore@0.4.6-next.1 + - @backstage/plugin-gcalendar@0.3.16-next.1 + - @backstage/plugin-gcp-projects@0.3.39-next.1 + - @backstage/plugin-github-actions@0.6.1-next.1 + - @backstage/plugin-gocd@0.1.26-next.1 + - @backstage/plugin-graphiql@0.2.52-next.1 + - @backstage/plugin-jenkins@0.8.2-next.1 + - @backstage/plugin-kafka@0.3.20-next.1 + - @backstage/plugin-linguist@0.1.5-next.1 + - @backstage/plugin-microsoft-calendar@0.1.5-next.1 + - @backstage/plugin-newrelic@0.3.38-next.1 + - @backstage/plugin-nomad@0.1.1-next.1 + - @backstage/plugin-octopus-deploy@0.2.2-next.1 + - @backstage/plugin-org@0.6.10-next.1 + - @backstage/plugin-pagerduty@0.6.1-next.1 + - @backstage/plugin-playlist@0.1.12-next.1 + - @backstage/plugin-puppetdb@0.1.3-next.1 + - @backstage/plugin-rollbar@0.4.20-next.1 + - @backstage/plugin-scaffolder@1.14.1-next.1 + - @backstage/plugin-search@1.3.3-next.1 + - @backstage/plugin-search-react@1.6.3-next.1 + - @backstage/plugin-sentry@0.5.5-next.1 + - @backstage/plugin-shortcuts@0.3.12-next.1 + - @backstage/plugin-stack-overflow@0.1.18-next.1 + - @backstage/plugin-stackstorm@0.1.4-next.1 + - @backstage/plugin-tech-insights@0.3.12-next.1 + - @backstage/plugin-tech-radar@0.6.6-next.1 + - @backstage/plugin-techdocs@1.6.5-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.15-next.1 + - @backstage/plugin-techdocs-react@1.1.8-next.1 + - @backstage/plugin-todo@0.2.22-next.1 + - @backstage/plugin-user-settings@0.7.5-next.1 + - @backstage/plugin-apache-airflow@0.2.13-next.1 + - @backstage/plugin-newrelic-dashboard@0.2.13-next.1 + - @internal/plugin-catalog-customized@0.0.12-next.1 + - @backstage/core-app-api@1.8.2-next.0 + - @backstage/plugin-permission-react@0.4.14-next.1 + - @backstage/config@1.0.8 + +## 0.2.85-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-devtools@0.1.2-next.0 + - @backstage/cli@0.22.9-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.1.1-next.0 + - @backstage/plugin-stackstorm@0.1.4-next.0 + - @backstage/plugin-dynatrace@6.0.1-next.0 + - @backstage/plugin-home@0.5.4-next.0 + - @backstage/core-components@0.13.3-next.0 + - @backstage/plugin-playlist@0.1.12-next.0 + - @backstage/plugin-nomad@0.1.1-next.0 + - @backstage/app-defaults@1.4.1-next.0 + - @backstage/catalog-model@1.4.1-next.0 + - @backstage/config@1.0.8 + - @backstage/core-app-api@1.8.1 + - @backstage/core-plugin-api@1.5.2 + - @backstage/integration-react@1.1.15-next.0 + - @backstage/theme@0.4.0 + - @backstage/plugin-adr@0.6.3-next.0 + - @backstage/plugin-airbrake@0.3.20-next.0 + - @backstage/plugin-apache-airflow@0.2.13-next.0 + - @backstage/plugin-api-docs@0.9.6-next.0 + - @backstage/plugin-azure-devops@0.3.2-next.0 + - @backstage/plugin-azure-sites@0.1.9-next.0 + - @backstage/plugin-badges@0.2.44-next.0 + - @backstage/plugin-catalog-common@1.0.15-next.0 + - @backstage/plugin-catalog-graph@0.2.32-next.0 + - @backstage/plugin-catalog-import@0.9.10-next.0 + - @backstage/plugin-catalog-react@1.7.1-next.0 + - @backstage/plugin-circleci@0.3.20-next.0 + - @backstage/plugin-cloudbuild@0.3.20-next.0 + - @backstage/plugin-code-coverage@0.2.13-next.0 + - @backstage/plugin-cost-insights@0.12.9-next.0 + - @backstage/plugin-entity-feedback@0.2.3-next.0 + - @backstage/plugin-explore@0.4.6-next.0 + - @backstage/plugin-gcalendar@0.3.16-next.0 + - @backstage/plugin-gcp-projects@0.3.39-next.0 + - @backstage/plugin-github-actions@0.6.1-next.0 + - @backstage/plugin-gocd@0.1.26-next.0 + - @backstage/plugin-graphiql@0.2.52-next.0 + - @backstage/plugin-jenkins@0.8.2-next.0 + - @backstage/plugin-kafka@0.3.20-next.0 + - @backstage/plugin-kubernetes@0.9.3-next.0 + - @backstage/plugin-lighthouse@0.4.5-next.0 + - @backstage/plugin-linguist@0.1.5-next.0 + - @backstage/plugin-linguist-common@0.1.0 + - @backstage/plugin-microsoft-calendar@0.1.5-next.0 + - @backstage/plugin-newrelic@0.3.38-next.0 + - @backstage/plugin-newrelic-dashboard@0.2.13-next.0 + - @backstage/plugin-octopus-deploy@0.2.2-next.0 + - @backstage/plugin-org@0.6.10-next.0 + - @backstage/plugin-pagerduty@0.6.1-next.0 + - @backstage/plugin-permission-react@0.4.14-next.0 + - @backstage/plugin-puppetdb@0.1.3-next.0 + - @backstage/plugin-rollbar@0.4.20-next.0 + - @backstage/plugin-scaffolder@1.14.1-next.0 + - @backstage/plugin-scaffolder-react@1.5.1-next.0 + - @backstage/plugin-search@1.3.3-next.0 + - @backstage/plugin-search-common@1.2.5-next.0 + - @backstage/plugin-search-react@1.6.3-next.0 + - @backstage/plugin-sentry@0.5.5-next.0 + - @backstage/plugin-shortcuts@0.3.12-next.0 + - @backstage/plugin-stack-overflow@0.1.18-next.0 + - @backstage/plugin-tech-insights@0.3.12-next.0 + - @backstage/plugin-tech-radar@0.6.6-next.0 + - @backstage/plugin-techdocs@1.6.5-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.15-next.0 + - @backstage/plugin-techdocs-react@1.1.8-next.0 + - @backstage/plugin-todo@0.2.22-next.0 + - @backstage/plugin-user-settings@0.7.5-next.0 + - @internal/plugin-catalog-customized@0.0.12-next.0 + +## 0.2.84 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-dynatrace@6.0.0 + - @backstage/core-app-api@1.8.1 + - @backstage/core-plugin-api@1.5.2 + - @backstage/plugin-kubernetes@0.9.2 + - @backstage/plugin-user-settings@0.7.4 + - @backstage/plugin-catalog-import@0.9.9 + - @backstage/plugin-scaffolder-react@1.5.0 + - @backstage/plugin-search-react@1.6.2 + - @backstage/plugin-octopus-deploy@0.2.1 + - @backstage/plugin-home@0.5.3 + - @backstage/cli@0.22.8 + - @backstage/core-components@0.13.2 + - @backstage/plugin-scaffolder@1.14.0 + - @backstage/plugin-nomad@0.1.0 + - @backstage/plugin-pagerduty@0.6.0 + - @backstage/plugin-catalog-unprocessed-entities@0.1.0 + - @backstage/plugin-search@1.3.2 + - @backstage/theme@0.4.0 + - @backstage/plugin-catalog-react@1.7.0 + - @backstage/plugin-entity-feedback@0.2.2 + - @backstage/catalog-model@1.4.0 + - @backstage/plugin-adr@0.6.2 + - @backstage/plugin-org@0.6.9 + - @backstage/plugin-techdocs@1.6.4 + - @backstage/plugin-tech-insights@0.3.11 + - @backstage/plugin-devtools@0.1.1 + - @backstage/plugin-api-docs@0.9.5 + - @backstage/plugin-github-actions@0.6.0 + - @backstage/plugin-shortcuts@0.3.11 + - @backstage/plugin-playlist@0.1.11 + - @backstage/plugin-jenkins@0.8.1 + - @backstage/plugin-explore@0.4.5 + - @backstage/plugin-stack-overflow@0.1.17 + - @backstage/app-defaults@1.4.0 + - @backstage/plugin-techdocs-react@1.1.7 + - @backstage/plugin-apache-airflow@0.2.12 + - @backstage/plugin-azure-devops@0.3.1 + - @backstage/plugin-azure-sites@0.1.8 + - @backstage/plugin-badges@0.2.43 + - @backstage/plugin-catalog-graph@0.2.31 + - @backstage/plugin-circleci@0.3.19 + - @backstage/plugin-cloudbuild@0.3.19 + - @backstage/plugin-code-coverage@0.2.12 + - @backstage/plugin-cost-insights@0.12.8 + - @backstage/plugin-gcalendar@0.3.15 + - @backstage/plugin-gcp-projects@0.3.38 + - @backstage/plugin-gocd@0.1.25 + - @backstage/plugin-graphiql@0.2.51 + - @backstage/plugin-kafka@0.3.19 + - @backstage/plugin-lighthouse@0.4.4 + - @backstage/plugin-linguist@0.1.4 + - @backstage/plugin-microsoft-calendar@0.1.4 + - @backstage/plugin-newrelic@0.3.37 + - @backstage/plugin-puppetdb@0.1.2 + - @backstage/plugin-rollbar@0.4.19 + - @backstage/plugin-sentry@0.5.4 + - @backstage/plugin-stackstorm@0.1.3 + - @backstage/plugin-tech-radar@0.6.5 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.14 + - @backstage/plugin-todo@0.2.21 + - @backstage/integration-react@1.1.14 + - @backstage/plugin-airbrake@0.3.19 + - @backstage/plugin-newrelic-dashboard@0.2.12 + - @backstage/plugin-permission-react@0.4.13 + - @internal/plugin-catalog-customized@0.0.11 + - @backstage/config@1.0.8 + - @backstage/plugin-catalog-common@1.0.14 + - @backstage/plugin-linguist-common@0.1.0 + - @backstage/plugin-search-common@1.2.4 + +## 0.2.84-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-react@1.6.2-next.3 + - @backstage/plugin-catalog-import@0.9.9-next.3 + - @backstage/cli@0.22.8-next.2 + - @backstage/core-components@0.13.2-next.3 + - @backstage/plugin-scaffolder@1.14.0-next.3 + - @backstage/catalog-model@1.4.0-next.1 + - @backstage/plugin-devtools@0.1.1-next.3 + - @backstage/plugin-api-docs@0.9.5-next.3 + - @backstage/plugin-techdocs@1.6.4-next.3 + - @backstage/plugin-scaffolder-react@1.5.0-next.3 + - @backstage/plugin-explore@0.4.5-next.3 + - @backstage/plugin-adr@0.6.2-next.3 + - @backstage/plugin-playlist@0.1.11-next.3 + - @backstage/plugin-search@1.3.2-next.3 + - @backstage/plugin-stack-overflow@0.1.17-next.3 + - @backstage/app-defaults@1.4.0-next.3 + - @backstage/config@1.0.7 + - @backstage/core-app-api@1.8.1-next.0 + - @backstage/core-plugin-api@1.5.2-next.0 + - @backstage/integration-react@1.1.14-next.3 + - @backstage/theme@0.4.0-next.1 + - @backstage/plugin-airbrake@0.3.19-next.3 + - @backstage/plugin-apache-airflow@0.2.12-next.3 + - @backstage/plugin-azure-devops@0.3.1-next.3 + - @backstage/plugin-azure-sites@0.1.8-next.3 + - @backstage/plugin-badges@0.2.43-next.3 + - @backstage/plugin-catalog-common@1.0.14-next.1 + - @backstage/plugin-catalog-graph@0.2.31-next.3 + - @backstage/plugin-catalog-react@1.7.0-next.3 + - @backstage/plugin-catalog-unprocessed-entities@0.1.0-next.2 + - @backstage/plugin-circleci@0.3.19-next.3 + - @backstage/plugin-cloudbuild@0.3.19-next.3 + - @backstage/plugin-code-coverage@0.2.12-next.3 + - @backstage/plugin-cost-insights@0.12.8-next.3 + - @backstage/plugin-dynatrace@6.0.0-next.3 + - @backstage/plugin-entity-feedback@0.2.2-next.3 + - @backstage/plugin-gcalendar@0.3.15-next.3 + - @backstage/plugin-gcp-projects@0.3.38-next.3 + - @backstage/plugin-github-actions@0.6.0-next.3 + - @backstage/plugin-gocd@0.1.25-next.3 + - @backstage/plugin-graphiql@0.2.51-next.3 + - @backstage/plugin-home@0.5.3-next.3 + - @backstage/plugin-jenkins@0.8.1-next.3 + - @backstage/plugin-kafka@0.3.19-next.3 + - @backstage/plugin-kubernetes@0.9.2-next.3 + - @backstage/plugin-lighthouse@0.4.4-next.3 + - @backstage/plugin-linguist@0.1.4-next.3 + - @backstage/plugin-linguist-common@0.1.0 + - @backstage/plugin-microsoft-calendar@0.1.4-next.3 + - @backstage/plugin-newrelic@0.3.37-next.3 + - @backstage/plugin-newrelic-dashboard@0.2.12-next.3 + - @backstage/plugin-octopus-deploy@0.2.1-next.3 + - @backstage/plugin-org@0.6.9-next.3 + - @backstage/plugin-pagerduty@0.5.12-next.3 + - @backstage/plugin-permission-react@0.4.13-next.0 + - @backstage/plugin-puppetdb@0.1.2-next.3 + - @backstage/plugin-rollbar@0.4.19-next.3 + - @backstage/plugin-search-common@1.2.4-next.0 + - @backstage/plugin-sentry@0.5.4-next.3 + - @backstage/plugin-shortcuts@0.3.11-next.3 + - @backstage/plugin-stackstorm@0.1.3-next.3 + - @backstage/plugin-tech-insights@0.3.11-next.3 + - @backstage/plugin-tech-radar@0.6.5-next.3 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.14-next.3 + - @backstage/plugin-techdocs-react@1.1.7-next.3 + - @backstage/plugin-todo@0.2.21-next.3 + - @backstage/plugin-user-settings@0.7.4-next.3 + - @internal/plugin-catalog-customized@0.0.11-next.3 + +## 0.2.84-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.4.0-next.1 + - @backstage/plugin-catalog-react@1.7.0-next.2 + - @backstage/core-components@0.13.2-next.2 + - @backstage/plugin-tech-insights@0.3.11-next.2 + - @backstage/plugin-scaffolder-react@1.5.0-next.2 + - @backstage/plugin-scaffolder@1.13.2-next.2 + - @backstage/plugin-home@0.5.3-next.2 + - @backstage/plugin-kubernetes@0.9.2-next.2 + - @backstage/app-defaults@1.4.0-next.2 + - @backstage/cli@0.22.8-next.1 + - @backstage/integration-react@1.1.14-next.2 + - @backstage/plugin-adr@0.6.1-next.2 + - @backstage/plugin-airbrake@0.3.19-next.2 + - @backstage/plugin-api-docs@0.9.4-next.2 + - @backstage/plugin-azure-devops@0.3.1-next.2 + - @backstage/plugin-azure-sites@0.1.8-next.2 + - @backstage/plugin-badges@0.2.43-next.2 + - @backstage/plugin-catalog-graph@0.2.31-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.1.0-next.1 + - @backstage/plugin-circleci@0.3.19-next.2 + - @backstage/plugin-cloudbuild@0.3.19-next.2 + - @backstage/plugin-code-coverage@0.2.12-next.2 + - @backstage/plugin-cost-insights@0.12.8-next.2 + - @backstage/plugin-devtools@0.1.1-next.2 + - @backstage/plugin-dynatrace@6.0.0-next.2 + - @backstage/plugin-entity-feedback@0.2.2-next.2 + - @backstage/plugin-explore@0.4.4-next.2 + - @backstage/plugin-gcalendar@0.3.15-next.2 + - @backstage/plugin-gcp-projects@0.3.38-next.2 + - @backstage/plugin-github-actions@0.6.0-next.2 + - @backstage/plugin-gocd@0.1.25-next.2 + - @backstage/plugin-graphiql@0.2.51-next.2 + - @backstage/plugin-jenkins@0.8.1-next.2 + - @backstage/plugin-kafka@0.3.19-next.2 + - @backstage/plugin-lighthouse@0.4.4-next.2 + - @backstage/plugin-linguist@0.1.4-next.2 + - @backstage/plugin-microsoft-calendar@0.1.4-next.2 + - @backstage/plugin-newrelic@0.3.37-next.2 + - @backstage/plugin-octopus-deploy@0.2.1-next.2 + - @backstage/plugin-org@0.6.9-next.2 + - @backstage/plugin-pagerduty@0.5.12-next.2 + - @backstage/plugin-playlist@0.1.10-next.2 + - @backstage/plugin-puppetdb@0.1.2-next.2 + - @backstage/plugin-rollbar@0.4.19-next.2 + - @backstage/plugin-search@1.3.1-next.2 + - @backstage/plugin-search-react@1.6.1-next.2 + - @backstage/plugin-sentry@0.5.4-next.2 + - @backstage/plugin-shortcuts@0.3.11-next.2 + - @backstage/plugin-stack-overflow@0.1.16-next.2 + - @backstage/plugin-stackstorm@0.1.3-next.2 + - @backstage/plugin-tech-radar@0.6.5-next.2 + - @backstage/plugin-techdocs@1.6.3-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.14-next.2 + - @backstage/plugin-techdocs-react@1.1.7-next.2 + - @backstage/plugin-todo@0.2.21-next.2 + - @backstage/plugin-user-settings@0.7.4-next.2 + - @internal/plugin-catalog-customized@0.0.11-next.2 + - @backstage/plugin-catalog-import@0.9.9-next.2 + - @backstage/plugin-newrelic-dashboard@0.2.12-next.2 + - @backstage/plugin-apache-airflow@0.2.12-next.2 + - @backstage/config@1.0.7 + - @backstage/core-app-api@1.8.1-next.0 + - @backstage/core-plugin-api@1.5.2-next.0 + - @backstage/plugin-permission-react@0.4.13-next.0 + +## 0.2.84-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-home@0.5.3-next.1 + - @backstage/plugin-user-settings@0.7.4-next.1 + - @backstage/plugin-pagerduty@0.5.12-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.1.0-next.0 + - @backstage/plugin-scaffolder-react@1.5.0-next.1 + - @backstage/plugin-adr@0.6.1-next.1 + - @backstage/plugin-search-react@1.6.1-next.1 + - @backstage/plugin-search@1.3.1-next.1 + - @backstage/plugin-techdocs@1.6.3-next.1 + - @backstage/core-components@0.13.2-next.1 + - @backstage/plugin-api-docs@0.9.4-next.1 + - @backstage/plugin-catalog-react@1.7.0-next.1 + - @backstage/plugin-devtools@0.1.1-next.1 + - @backstage/catalog-model@1.4.0-next.0 + - @backstage/cli@0.22.8-next.1 + - @backstage/core-app-api@1.8.1-next.0 + - @backstage/core-plugin-api@1.5.2-next.0 + - @backstage/plugin-scaffolder@1.13.2-next.1 + - @internal/plugin-catalog-customized@0.0.11-next.1 + - @backstage/plugin-catalog-graph@0.2.31-next.1 + - @backstage/plugin-explore@0.4.4-next.1 + - @backstage/plugin-org@0.6.9-next.1 + - @backstage/integration-react@1.1.14-next.1 + - @backstage/plugin-catalog-import@0.9.9-next.1 + - @backstage/plugin-github-actions@0.6.0-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.14-next.1 + - @backstage/plugin-azure-devops@0.3.1-next.1 + - @backstage/plugin-badges@0.2.43-next.1 + - @backstage/plugin-code-coverage@0.2.12-next.1 + - @backstage/plugin-entity-feedback@0.2.2-next.1 + - @backstage/plugin-gcalendar@0.3.15-next.1 + - @backstage/plugin-gocd@0.1.25-next.1 + - @backstage/plugin-jenkins@0.8.1-next.1 + - @backstage/plugin-kubernetes@0.9.2-next.1 + - @backstage/plugin-linguist@0.1.4-next.1 + - @backstage/plugin-microsoft-calendar@0.1.4-next.1 + - @backstage/plugin-newrelic-dashboard@0.2.12-next.1 + - @backstage/plugin-playlist@0.1.10-next.1 + - @backstage/plugin-puppetdb@0.1.2-next.1 + - @backstage/plugin-stackstorm@0.1.3-next.1 + - @backstage/plugin-tech-insights@0.3.11-next.1 + - @backstage/plugin-todo@0.2.21-next.1 + - @backstage/plugin-stack-overflow@0.1.16-next.1 + - @backstage/app-defaults@1.4.0-next.1 + - @backstage/plugin-airbrake@0.3.19-next.1 + - @backstage/plugin-apache-airflow@0.2.12-next.1 + - @backstage/plugin-azure-sites@0.1.8-next.1 + - @backstage/plugin-circleci@0.3.19-next.1 + - @backstage/plugin-cloudbuild@0.3.19-next.1 + - @backstage/plugin-cost-insights@0.12.8-next.1 + - @backstage/plugin-dynatrace@6.0.0-next.1 + - @backstage/plugin-gcp-projects@0.3.38-next.1 + - @backstage/plugin-graphiql@0.2.51-next.1 + - @backstage/plugin-kafka@0.3.19-next.1 + - @backstage/plugin-lighthouse@0.4.4-next.1 + - @backstage/plugin-newrelic@0.3.37-next.1 + - @backstage/plugin-octopus-deploy@0.2.1-next.1 + - @backstage/plugin-rollbar@0.4.19-next.1 + - @backstage/plugin-sentry@0.5.4-next.1 + - @backstage/plugin-shortcuts@0.3.11-next.1 + - @backstage/plugin-tech-radar@0.6.5-next.1 + - @backstage/plugin-techdocs-react@1.1.7-next.1 + - @backstage/plugin-catalog-common@1.0.14-next.0 + - @backstage/config@1.0.7 + - @backstage/theme@0.4.0-next.0 + - @backstage/plugin-linguist-common@0.1.0 + - @backstage/plugin-permission-react@0.4.13-next.0 + - @backstage/plugin-search-common@1.2.4-next.0 + +## 0.2.84-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes@0.9.2-next.0 + - @backstage/plugin-user-settings@0.7.4-next.0 + - @backstage/plugin-catalog-import@0.9.9-next.0 + - @backstage/plugin-scaffolder-react@1.4.1-next.0 + - @backstage/plugin-octopus-deploy@0.2.1-next.0 + - @backstage/plugin-scaffolder@1.13.2-next.0 + - @backstage/plugin-catalog-react@1.7.0-next.0 + - @backstage/plugin-techdocs@1.6.3-next.0 + - @backstage/plugin-github-actions@0.6.0-next.0 + - @backstage/plugin-stack-overflow@0.1.16-next.0 + - @backstage/plugin-home@0.5.3-next.0 + - @backstage/app-defaults@1.4.0-next.0 + - @backstage/theme@0.4.0-next.0 + - @backstage/cli@0.22.8-next.0 + - @backstage/plugin-techdocs-react@1.1.7-next.0 + - @backstage/plugin-dynatrace@6.0.0-next.0 + - @backstage/plugin-pagerduty@0.5.12-next.0 + - @backstage/plugin-catalog-graph@0.2.31-next.0 + - @backstage/plugin-org@0.6.9-next.0 + - @backstage/plugin-airbrake@0.3.19-next.0 + - @backstage/plugin-api-docs@0.9.4-next.0 + - @backstage/plugin-azure-devops@0.3.1-next.0 + - @backstage/plugin-azure-sites@0.1.8-next.0 + - @backstage/plugin-badges@0.2.43-next.0 + - @internal/plugin-catalog-customized@0.0.11-next.0 + - @backstage/plugin-circleci@0.3.19-next.0 + - @backstage/plugin-cloudbuild@0.3.19-next.0 + - @backstage/plugin-code-coverage@0.2.12-next.0 + - @backstage/plugin-cost-insights@0.12.8-next.0 + - @backstage/plugin-entity-feedback@0.2.2-next.0 + - @backstage/plugin-explore@0.4.4-next.0 + - @backstage/plugin-gocd@0.1.25-next.0 + - @backstage/plugin-jenkins@0.8.1-next.0 + - @backstage/plugin-kafka@0.3.19-next.0 + - @backstage/plugin-lighthouse@0.4.4-next.0 + - @backstage/plugin-linguist@0.1.4-next.0 + - @backstage/plugin-newrelic-dashboard@0.2.12-next.0 + - @backstage/plugin-playlist@0.1.10-next.0 + - @backstage/plugin-puppetdb@0.1.2-next.0 + - @backstage/plugin-rollbar@0.4.19-next.0 + - @backstage/plugin-search@1.3.1-next.0 + - @backstage/plugin-sentry@0.5.4-next.0 + - @backstage/plugin-tech-insights@0.3.11-next.0 + - @backstage/plugin-todo@0.2.21-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.14-next.0 + - @backstage/config@1.0.7 + - @backstage/core-app-api@1.8.0 + - @backstage/core-components@0.13.2-next.0 + - @backstage/core-plugin-api@1.5.1 + - @backstage/integration-react@1.1.14-next.0 + - @backstage/plugin-apache-airflow@0.2.12-next.0 + - @backstage/plugin-devtools@0.1.1-next.0 + - @backstage/plugin-gcalendar@0.3.15-next.0 + - @backstage/plugin-gcp-projects@0.3.38-next.0 + - @backstage/plugin-graphiql@0.2.51-next.0 + - @backstage/plugin-microsoft-calendar@0.1.4-next.0 + - @backstage/plugin-newrelic@0.3.37-next.0 + - @backstage/plugin-permission-react@0.4.12 + - @backstage/plugin-search-react@1.6.1-next.0 + - @backstage/plugin-shortcuts@0.3.11-next.0 + - @backstage/plugin-stackstorm@0.1.3-next.0 + - @backstage/plugin-tech-radar@0.6.5-next.0 + - @backstage/catalog-model@1.3.0 + - @backstage/plugin-catalog-common@1.0.13 + - @backstage/plugin-linguist-common@0.1.0 + - @backstage/plugin-search-common@1.2.3 + +## 0.2.83 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-tech-insights@0.3.10 + - @backstage/plugin-scaffolder@1.13.1 + - @backstage/plugin-devtools@0.1.0 + - @backstage/plugin-kubernetes@0.9.0 + - @backstage/plugin-search@1.3.0 + - @backstage/plugin-home@0.5.2 + - @backstage/plugin-explore@0.4.3 + - @backstage/theme@0.3.0 + - @backstage/plugin-techdocs@1.6.2 + - @backstage/app-defaults@1.3.1 + - @backstage/plugin-badges@0.2.42 + - @backstage/plugin-tech-radar@0.6.4 + - @backstage/cli@0.22.7 + - @backstage/plugin-stack-overflow@0.1.15 + - @backstage/plugin-gcalendar@0.3.14 + - @backstage/plugin-catalog-react@1.6.0 + - @backstage/core-app-api@1.8.0 + - @backstage/plugin-scaffolder-react@1.4.0 + - @backstage/plugin-catalog-graph@0.2.30 + - @backstage/plugin-circleci@0.3.18 + - @backstage/plugin-search-react@1.6.0 + - @backstage/plugin-org@0.6.8 + - @backstage/core-components@0.13.1 + - @backstage/plugin-azure-devops@0.3.0 + - @backstage/plugin-jenkins@0.8.0 + - @backstage/plugin-octopus-deploy@0.2.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.13 + - @backstage/plugin-user-settings@0.7.3 + - @backstage/plugin-shortcuts@0.3.10 + - @backstage/integration-react@1.1.13 + - @backstage/plugin-airbrake@0.3.18 + - @backstage/plugin-api-docs@0.9.3 + - @backstage/plugin-azure-sites@0.1.7 + - @backstage/plugin-cloudbuild@0.3.18 + - @backstage/plugin-code-coverage@0.2.11 + - @backstage/plugin-cost-insights@0.12.7 + - @backstage/plugin-dynatrace@5.0.0 + - @backstage/plugin-entity-feedback@0.2.1 + - @backstage/plugin-gcp-projects@0.3.37 + - @backstage/plugin-github-actions@0.5.18 + - @backstage/plugin-gocd@0.1.24 + - @backstage/plugin-graphiql@0.2.50 + - @backstage/plugin-kafka@0.3.18 + - @backstage/plugin-lighthouse@0.4.3 + - @backstage/plugin-linguist@0.1.3 + - @backstage/plugin-microsoft-calendar@0.1.3 + - @backstage/plugin-newrelic@0.3.36 + - @backstage/plugin-pagerduty@0.5.11 + - @backstage/plugin-playlist@0.1.9 + - @backstage/plugin-puppetdb@0.1.1 + - @backstage/plugin-rollbar@0.4.18 + - @backstage/plugin-sentry@0.5.3 + - @backstage/plugin-stackstorm@0.1.2 + - @backstage/plugin-techdocs-react@1.1.6 + - @backstage/plugin-todo@0.2.20 + - @backstage/catalog-model@1.3.0 + - @backstage/config@1.0.7 + - @backstage/core-plugin-api@1.5.1 + - @backstage/plugin-apache-airflow@0.2.11 + - @backstage/plugin-catalog-common@1.0.13 + - @backstage/plugin-catalog-import@0.9.8 + - @backstage/plugin-linguist-common@0.1.0 + - @backstage/plugin-newrelic-dashboard@0.2.11 + - @backstage/plugin-permission-react@0.4.12 + - @backstage/plugin-search-common@1.2.3 + - @internal/plugin-catalog-customized@0.0.10 + +## 0.2.83-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-devtools@0.1.0-next.0 + - @backstage/plugin-kubernetes@0.9.0-next.2 + - @backstage/plugin-home@0.5.2-next.2 + - @backstage/theme@0.3.0-next.0 + - @backstage/plugin-badges@0.2.42-next.2 + - @backstage/plugin-tech-radar@0.6.4-next.2 + - @backstage/plugin-scaffolder-react@1.4.0-next.2 + - @backstage/plugin-explore@0.4.3-next.2 + - @backstage/plugin-org@0.6.8-next.2 + - @backstage/core-components@0.13.1-next.1 + - @backstage/plugin-search-react@1.6.0-next.2 + - @backstage/plugin-azure-devops@0.3.0-next.2 + - @backstage/plugin-stack-overflow@0.1.15-next.2 + - @backstage/app-defaults@1.3.1-next.2 + - @backstage/cli@0.22.7-next.0 + - @backstage/integration-react@1.1.13-next.2 + - @backstage/plugin-airbrake@0.3.18-next.2 + - @backstage/plugin-api-docs@0.9.3-next.2 + - @backstage/plugin-azure-sites@0.1.7-next.2 + - @backstage/plugin-catalog-graph@0.2.30-next.2 + - @backstage/plugin-catalog-react@1.6.0-next.2 + - @backstage/plugin-circleci@0.3.18-next.2 + - @backstage/plugin-cloudbuild@0.3.18-next.2 + - @backstage/plugin-code-coverage@0.2.11-next.2 + - @backstage/plugin-cost-insights@0.12.7-next.2 + - @backstage/plugin-dynatrace@5.0.0-next.2 + - @backstage/plugin-entity-feedback@0.2.1-next.2 + - @backstage/plugin-gcalendar@0.3.14-next.1 + - @backstage/plugin-gcp-projects@0.3.37-next.1 + - @backstage/plugin-github-actions@0.5.18-next.2 + - @backstage/plugin-gocd@0.1.24-next.2 + - @backstage/plugin-graphiql@0.2.50-next.1 + - @backstage/plugin-jenkins@0.7.17-next.2 + - @backstage/plugin-kafka@0.3.18-next.2 + - @backstage/plugin-lighthouse@0.4.3-next.2 + - @backstage/plugin-linguist@0.1.3-next.2 + - @backstage/plugin-microsoft-calendar@0.1.3-next.1 + - @backstage/plugin-newrelic@0.3.36-next.1 + - @backstage/plugin-octopus-deploy@0.2.0-next.2 + - @backstage/plugin-pagerduty@0.5.11-next.2 + - @backstage/plugin-playlist@0.1.9-next.2 + - @backstage/plugin-puppetdb@0.1.1-next.2 + - @backstage/plugin-rollbar@0.4.18-next.2 + - @backstage/plugin-scaffolder@1.13.1-next.2 + - @backstage/plugin-search@1.3.0-next.2 + - @backstage/plugin-sentry@0.5.3-next.2 + - @backstage/plugin-shortcuts@0.3.10-next.2 + - @backstage/plugin-stackstorm@0.1.2-next.1 + - @backstage/plugin-tech-insights@0.3.10-next.2 + - @backstage/plugin-techdocs@1.6.2-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.13-next.2 + - @backstage/plugin-techdocs-react@1.1.6-next.1 + - @backstage/plugin-todo@0.2.20-next.2 + - @backstage/plugin-user-settings@0.7.3-next.2 + - @backstage/plugin-apache-airflow@0.2.11-next.1 + - @backstage/plugin-catalog-import@0.9.8-next.2 + - @backstage/plugin-newrelic-dashboard@0.2.11-next.2 + - @backstage/config@1.0.7 + - @backstage/core-app-api@1.8.0-next.1 + - @backstage/core-plugin-api@1.5.1 + - @backstage/plugin-permission-react@0.4.12 + - @internal/plugin-catalog-customized@0.0.10-next.2 + +## 0.2.83-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-tech-insights@0.3.10-next.1 + - @backstage/plugin-scaffolder@1.13.1-next.1 + - @backstage/plugin-search@1.3.0-next.1 + - @backstage/plugin-explore@0.4.3-next.1 + - @backstage/app-defaults@1.3.1-next.1 + - @backstage/plugin-kubernetes@0.9.0-next.1 + - @backstage/core-app-api@1.8.0-next.1 + - @backstage/plugin-catalog-graph@0.2.30-next.1 + - @backstage/plugin-home@0.5.2-next.1 + - @backstage/plugin-octopus-deploy@0.2.0-next.1 + - @backstage/plugin-circleci@0.3.18-next.1 + - @backstage/core-components@0.13.1-next.0 + - @backstage/cli@0.22.7-next.0 + - @backstage/plugin-airbrake@0.3.18-next.1 + - @backstage/core-plugin-api@1.5.1 + - @backstage/plugin-apache-airflow@0.2.11-next.0 + - @backstage/plugin-api-docs@0.9.3-next.1 + - @backstage/plugin-azure-devops@0.2.9-next.1 + - @backstage/plugin-azure-sites@0.1.7-next.1 + - @backstage/plugin-badges@0.2.42-next.1 + - @backstage/plugin-catalog-import@0.9.8-next.1 + - @backstage/plugin-catalog-react@1.6.0-next.1 + - @backstage/plugin-cloudbuild@0.3.18-next.1 + - @backstage/plugin-code-coverage@0.2.11-next.1 + - @backstage/plugin-cost-insights@0.12.7-next.1 + - @backstage/plugin-dynatrace@5.0.0-next.1 + - @backstage/plugin-entity-feedback@0.2.1-next.1 + - @backstage/plugin-gcalendar@0.3.14-next.0 + - @backstage/plugin-gcp-projects@0.3.37-next.0 + - @backstage/plugin-github-actions@0.5.18-next.1 + - @backstage/plugin-gocd@0.1.24-next.1 + - @backstage/plugin-graphiql@0.2.50-next.0 + - @backstage/plugin-jenkins@0.7.17-next.1 + - @backstage/plugin-kafka@0.3.18-next.1 + - @backstage/plugin-lighthouse@0.4.3-next.1 + - @backstage/plugin-linguist@0.1.3-next.1 + - @backstage/plugin-microsoft-calendar@0.1.3-next.0 + - @backstage/plugin-newrelic@0.3.36-next.0 + - @backstage/plugin-org@0.6.8-next.1 + - @backstage/plugin-pagerduty@0.5.11-next.1 + - @backstage/plugin-playlist@0.1.9-next.1 + - @backstage/plugin-puppetdb@0.1.1-next.1 + - @backstage/plugin-rollbar@0.4.18-next.1 + - @backstage/plugin-scaffolder-react@1.3.1-next.1 + - @backstage/plugin-search-react@1.6.0-next.1 + - @backstage/plugin-sentry@0.5.3-next.1 + - @backstage/plugin-shortcuts@0.3.10-next.1 + - @backstage/plugin-stack-overflow@0.1.15-next.1 + - @backstage/plugin-stackstorm@0.1.2-next.0 + - @backstage/plugin-tech-radar@0.6.4-next.1 + - @backstage/plugin-techdocs@1.6.2-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.13-next.1 + - @backstage/plugin-todo@0.2.20-next.1 + - @backstage/plugin-user-settings@0.7.3-next.1 + - @backstage/integration-react@1.1.13-next.1 + - @backstage/plugin-newrelic-dashboard@0.2.11-next.1 + - @backstage/plugin-techdocs-react@1.1.6-next.0 + - @backstage/config@1.0.7 + - @backstage/plugin-permission-react@0.4.12 + - @internal/plugin-catalog-customized@0.0.10-next.1 + +## 0.2.83-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.6.2-next.0 + - @backstage/plugin-stack-overflow@0.1.14-next.0 + - @backstage/plugin-catalog-react@1.6.0-next.0 + - @backstage/plugin-circleci@0.3.18-next.0 + - @backstage/plugin-search-react@1.6.0-next.0 + - @backstage/plugin-search@1.3.0-next.0 + - @backstage/plugin-home@0.5.1-next.0 + - @backstage/core-app-api@1.7.1-next.0 + - @backstage/plugin-scaffolder-react@1.3.1-next.0 + - @backstage/plugin-scaffolder@1.13.1-next.0 + - @backstage/plugin-shortcuts@0.3.10-next.0 + - @backstage/plugin-tech-radar@0.6.4-next.0 + - @backstage/cli@0.22.7-next.0 + - @backstage/plugin-airbrake@0.3.18-next.0 + - @backstage/plugin-api-docs@0.9.3-next.0 + - @backstage/plugin-azure-devops@0.2.9-next.0 + - @backstage/plugin-azure-sites@0.1.7-next.0 + - @backstage/plugin-badges@0.2.42-next.0 + - @internal/plugin-catalog-customized@0.0.10-next.0 + - @backstage/plugin-catalog-graph@0.2.30-next.0 + - @backstage/plugin-catalog-import@0.9.8-next.0 + - @backstage/plugin-cloudbuild@0.3.18-next.0 + - @backstage/plugin-code-coverage@0.2.11-next.0 + - @backstage/plugin-cost-insights@0.12.7-next.0 + - @backstage/plugin-dynatrace@5.0.0-next.0 + - @backstage/plugin-entity-feedback@0.2.1-next.0 + - @backstage/plugin-explore@0.4.3-next.0 + - @backstage/plugin-github-actions@0.5.18-next.0 + - @backstage/plugin-gocd@0.1.24-next.0 + - @backstage/plugin-jenkins@0.7.17-next.0 + - @backstage/plugin-kafka@0.3.18-next.0 + - @backstage/plugin-kubernetes@0.8.1-next.0 + - @backstage/plugin-lighthouse@0.4.3-next.0 + - @backstage/plugin-linguist@0.1.3-next.0 + - @backstage/plugin-newrelic-dashboard@0.2.11-next.0 + - @backstage/plugin-org@0.6.8-next.0 + - @backstage/plugin-pagerduty@0.5.11-next.0 + - @backstage/plugin-playlist@0.1.9-next.0 + - @backstage/plugin-puppetdb@0.1.1-next.0 + - @backstage/plugin-rollbar@0.4.18-next.0 + - @backstage/plugin-sentry@0.5.3-next.0 + - @backstage/plugin-tech-insights@0.3.10-next.0 + - @backstage/plugin-todo@0.2.20-next.0 + - @backstage/plugin-user-settings@0.7.3-next.0 + - @backstage/integration-react@1.1.13-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.13-next.0 + - @backstage/app-defaults@1.3.1-next.0 + - @backstage/core-components@0.13.0 + - @backstage/core-plugin-api@1.5.1 + - @backstage/plugin-apache-airflow@0.2.10 + - @backstage/plugin-gcalendar@0.3.13 + - @backstage/plugin-gcp-projects@0.3.36 + - @backstage/plugin-graphiql@0.2.49 + - @backstage/plugin-microsoft-calendar@0.1.2 + - @backstage/plugin-newrelic@0.3.35 + - @backstage/plugin-stackstorm@0.1.1 + - @backstage/catalog-model@1.3.0 + - @backstage/config@1.0.7 + - @backstage/theme@0.2.19 + - @backstage/plugin-catalog-common@1.0.13 + - @backstage/plugin-linguist-common@0.1.0 + - @backstage/plugin-permission-react@0.4.12 + - @backstage/plugin-search-common@1.2.3 + - @backstage/plugin-techdocs-react@1.1.5 + +## 0.2.82 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.0 + - @backstage/plugin-kubernetes@0.8.0 + - @backstage/cli@0.22.6 + - @backstage/plugin-circleci@0.3.17 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.12 + - @backstage/plugin-scaffolder-react@1.3.0 + - @backstage/plugin-scaffolder@1.13.0 + - @backstage/plugin-api-docs@0.9.2 + - @backstage/plugin-org@0.6.7 + - @backstage/plugin-entity-feedback@0.2.0 + - @backstage/plugin-catalog-react@1.5.0 + - @backstage/plugin-techdocs@1.6.1 + - @backstage/plugin-puppetdb@0.1.0 + - @backstage/plugin-explore@0.4.2 + - @backstage/plugin-tech-insights@0.3.9 + - @backstage/app-defaults@1.3.0 + - @backstage/core-app-api@1.7.0 + - @backstage/plugin-search-react@1.5.2 + - @backstage/plugin-code-coverage@0.2.10 + - @backstage/plugin-cost-insights@0.12.6 + - @backstage/plugin-playlist@0.1.8 + - @backstage/plugin-home@0.5.0 + - @backstage/plugin-dynatrace@4.0.0 + - @backstage/plugin-airbrake@0.3.17 + - @backstage/plugin-techdocs-react@1.1.5 + - @backstage/integration-react@1.1.12 + - @backstage/plugin-stackstorm@0.1.1 + - @backstage/plugin-linguist@0.1.2 + - @backstage/plugin-newrelic-dashboard@0.2.10 + - @backstage/plugin-apache-airflow@0.2.10 + - @backstage/plugin-catalog-import@0.9.7 + - @backstage/plugin-github-actions@0.5.17 + - @backstage/plugin-catalog-graph@0.2.29 + - @backstage/plugin-user-settings@0.7.2 + - @backstage/plugin-azure-devops@0.2.8 + - @backstage/plugin-gcp-projects@0.3.36 + - @backstage/plugin-azure-sites@0.1.6 + - @backstage/plugin-cloudbuild@0.3.17 + - @backstage/plugin-lighthouse@0.4.2 + - @backstage/plugin-tech-radar@0.6.3 + - @backstage/plugin-gcalendar@0.3.13 + - @backstage/plugin-pagerduty@0.5.10 + - @backstage/plugin-shortcuts@0.3.9 + - @backstage/plugin-graphiql@0.2.49 + - @backstage/plugin-newrelic@0.3.35 + - @backstage/plugin-jenkins@0.7.16 + - @backstage/plugin-rollbar@0.4.17 + - @backstage/plugin-badges@0.2.41 + - @backstage/plugin-search@1.2.0 + - @backstage/plugin-sentry@0.5.2 + - @backstage/plugin-kafka@0.3.17 + - @backstage/plugin-gocd@0.1.23 + - @backstage/plugin-todo@0.2.19 + - @backstage/theme@0.2.19 + - @backstage/core-plugin-api@1.5.1 + - @backstage/catalog-model@1.3.0 + - @backstage/plugin-microsoft-calendar@0.1.2 + - @backstage/plugin-permission-react@0.4.12 + - @backstage/plugin-stack-overflow@0.1.13 + - @backstage/config@1.0.7 + - @backstage/plugin-catalog-common@1.0.13 + - @backstage/plugin-linguist-common@0.1.0 + - @backstage/plugin-search-common@1.2.3 + - @internal/plugin-catalog-customized@0.0.9 + +## 0.2.82-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes@0.8.0-next.3 + - @backstage/plugin-scaffolder@1.13.0-next.3 + - @backstage/cli@0.22.6-next.3 + - @backstage/plugin-explore@0.4.2-next.3 + - @backstage/plugin-catalog-react@1.5.0-next.3 + - @backstage/plugin-scaffolder-react@1.3.0-next.3 + - @backstage/core-app-api@1.7.0-next.3 + - @backstage/catalog-model@1.3.0-next.0 + - @backstage/plugin-techdocs@1.6.1-next.3 + - @backstage/core-components@0.13.0-next.3 + - @backstage/app-defaults@1.3.0-next.3 + - @backstage/config@1.0.7 + - @backstage/core-plugin-api@1.5.1-next.1 + - @backstage/integration-react@1.1.12-next.3 + - @backstage/theme@0.2.19-next.0 + - @backstage/plugin-airbrake@0.3.17-next.3 + - @backstage/plugin-apache-airflow@0.2.10-next.3 + - @backstage/plugin-api-docs@0.9.2-next.3 + - @backstage/plugin-azure-devops@0.2.8-next.3 + - @backstage/plugin-azure-sites@0.1.6-next.3 + - @backstage/plugin-badges@0.2.41-next.3 + - @backstage/plugin-catalog-common@1.0.13-next.1 + - @backstage/plugin-catalog-graph@0.2.29-next.3 + - @backstage/plugin-catalog-import@0.9.7-next.3 + - @backstage/plugin-circleci@0.3.17-next.3 + - @backstage/plugin-cloudbuild@0.3.17-next.3 + - @backstage/plugin-code-coverage@0.2.10-next.3 + - @backstage/plugin-cost-insights@0.12.6-next.3 + - @backstage/plugin-dynatrace@4.0.0-next.3 + - @backstage/plugin-entity-feedback@0.2.0-next.3 + - @backstage/plugin-gcalendar@0.3.13-next.3 + - @backstage/plugin-gcp-projects@0.3.36-next.3 + - @backstage/plugin-github-actions@0.5.17-next.3 + - @backstage/plugin-gocd@0.1.23-next.3 + - @backstage/plugin-graphiql@0.2.49-next.3 + - @backstage/plugin-home@0.4.33-next.3 + - @backstage/plugin-jenkins@0.7.16-next.3 + - @backstage/plugin-kafka@0.3.17-next.3 + - @backstage/plugin-lighthouse@0.4.2-next.3 + - @backstage/plugin-linguist@0.1.2-next.3 + - @backstage/plugin-linguist-common@0.1.0 + - @backstage/plugin-microsoft-calendar@0.1.2-next.3 + - @backstage/plugin-newrelic@0.3.35-next.3 + - @backstage/plugin-newrelic-dashboard@0.2.10-next.3 + - @backstage/plugin-org@0.6.7-next.3 + - @backstage/plugin-pagerduty@0.5.10-next.3 + - @backstage/plugin-permission-react@0.4.12-next.1 + - @backstage/plugin-playlist@0.1.8-next.3 + - @backstage/plugin-rollbar@0.4.17-next.3 + - @backstage/plugin-search@1.2.0-next.3 + - @backstage/plugin-search-common@1.2.3-next.0 + - @backstage/plugin-search-react@1.5.2-next.3 + - @backstage/plugin-sentry@0.5.2-next.3 + - @backstage/plugin-shortcuts@0.3.9-next.3 + - @backstage/plugin-stack-overflow@0.1.13-next.3 + - @backstage/plugin-stackstorm@0.1.1-next.3 + - @backstage/plugin-tech-insights@0.3.9-next.3 + - @backstage/plugin-tech-radar@0.6.3-next.3 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.12-next.3 + - @backstage/plugin-techdocs-react@1.1.5-next.3 + - @backstage/plugin-todo@0.2.19-next.3 + - @backstage/plugin-user-settings@0.7.2-next.3 + - @internal/plugin-catalog-customized@0.0.9-next.3 + +## 0.2.82-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-circleci@0.3.17-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.12-next.2 + - @backstage/cli@0.22.6-next.2 + - @backstage/core-components@0.12.6-next.2 + - @backstage/plugin-catalog-react@1.4.1-next.2 + - @backstage/plugin-scaffolder-react@1.3.0-next.2 + - @backstage/plugin-scaffolder@1.13.0-next.2 + - @backstage/plugin-code-coverage@0.2.10-next.2 + - @backstage/plugin-cost-insights@0.12.6-next.2 + - @backstage/plugin-playlist@0.1.8-next.2 + - @backstage/plugin-search@1.2.0-next.2 + - @backstage/core-plugin-api@1.5.1-next.1 + - @backstage/plugin-techdocs@1.6.1-next.2 + - @backstage/app-defaults@1.3.0-next.2 + - @backstage/catalog-model@1.2.1 + - @backstage/config@1.0.7 + - @backstage/core-app-api@1.7.0-next.2 + - @backstage/integration-react@1.1.12-next.2 + - @backstage/theme@0.2.19-next.0 + - @backstage/plugin-airbrake@0.3.17-next.2 + - @backstage/plugin-apache-airflow@0.2.10-next.2 + - @backstage/plugin-api-docs@0.9.2-next.2 + - @backstage/plugin-azure-devops@0.2.8-next.2 + - @backstage/plugin-azure-sites@0.1.6-next.2 + - @backstage/plugin-badges@0.2.41-next.2 + - @backstage/plugin-catalog-common@1.0.13-next.0 + - @backstage/plugin-catalog-graph@0.2.29-next.2 + - @backstage/plugin-catalog-import@0.9.7-next.2 + - @backstage/plugin-cloudbuild@0.3.17-next.2 + - @backstage/plugin-dynatrace@3.0.1-next.2 + - @backstage/plugin-entity-feedback@0.2.0-next.2 + - @backstage/plugin-explore@0.4.2-next.2 + - @backstage/plugin-gcalendar@0.3.13-next.2 + - @backstage/plugin-gcp-projects@0.3.36-next.2 + - @backstage/plugin-github-actions@0.5.17-next.2 + - @backstage/plugin-gocd@0.1.23-next.2 + - @backstage/plugin-graphiql@0.2.49-next.2 + - @backstage/plugin-home@0.4.33-next.2 + - @backstage/plugin-jenkins@0.7.16-next.2 + - @backstage/plugin-kafka@0.3.17-next.2 + - @backstage/plugin-kubernetes@0.7.10-next.2 + - @backstage/plugin-lighthouse@0.4.2-next.2 + - @backstage/plugin-linguist@0.1.2-next.2 + - @backstage/plugin-linguist-common@0.1.0 + - @backstage/plugin-microsoft-calendar@0.1.2-next.2 + - @backstage/plugin-newrelic@0.3.35-next.2 + - @backstage/plugin-newrelic-dashboard@0.2.10-next.2 + - @backstage/plugin-org@0.6.7-next.2 + - @backstage/plugin-pagerduty@0.5.10-next.2 + - @backstage/plugin-permission-react@0.4.12-next.1 + - @backstage/plugin-rollbar@0.4.17-next.2 + - @backstage/plugin-search-common@1.2.3-next.0 + - @backstage/plugin-search-react@1.5.2-next.2 + - @backstage/plugin-sentry@0.5.2-next.2 + - @backstage/plugin-shortcuts@0.3.9-next.2 + - @backstage/plugin-stack-overflow@0.1.13-next.2 + - @backstage/plugin-stackstorm@0.1.1-next.2 + - @backstage/plugin-tech-insights@0.3.9-next.2 + - @backstage/plugin-tech-radar@0.6.3-next.2 + - @backstage/plugin-techdocs-react@1.1.5-next.2 + - @backstage/plugin-todo@0.2.19-next.2 + - @backstage/plugin-user-settings@0.7.2-next.2 + - @internal/plugin-catalog-customized@0.0.9-next.2 + +## 0.2.82-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.6-next.1 + - @backstage/cli@0.22.6-next.1 + - @backstage/plugin-org@0.6.7-next.1 + - @backstage/plugin-techdocs@1.6.1-next.1 + - @backstage/plugin-scaffolder-react@1.3.0-next.1 + - @backstage/core-app-api@1.7.0-next.1 + - @backstage/plugin-scaffolder@1.13.0-next.1 + - @backstage/plugin-dynatrace@3.0.1-next.1 + - @backstage/plugin-airbrake@0.3.17-next.1 + - @backstage/plugin-circleci@0.3.17-next.1 + - @backstage/integration-react@1.1.12-next.1 + - @backstage/plugin-stackstorm@0.1.1-next.1 + - @backstage/plugin-linguist@0.1.2-next.1 + - @backstage/plugin-playlist@0.1.8-next.1 + - @backstage/plugin-graphiql@0.2.49-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.12-next.1 + - @backstage/plugin-microsoft-calendar@0.1.2-next.1 + - @backstage/plugin-newrelic-dashboard@0.2.10-next.1 + - @backstage/core-plugin-api@1.5.1-next.0 + - @backstage/plugin-permission-react@0.4.12-next.0 + - @backstage/plugin-entity-feedback@0.2.0-next.1 + - @backstage/plugin-apache-airflow@0.2.10-next.1 + - @backstage/plugin-catalog-import@0.9.7-next.1 + - @backstage/plugin-github-actions@0.5.17-next.1 + - @backstage/plugin-stack-overflow@0.1.13-next.1 + - @backstage/plugin-techdocs-react@1.1.5-next.1 + - @backstage/plugin-catalog-graph@0.2.29-next.1 + - @backstage/plugin-catalog-react@1.4.1-next.1 + - @backstage/plugin-code-coverage@0.2.10-next.1 + - @backstage/plugin-cost-insights@0.12.6-next.1 + - @backstage/plugin-tech-insights@0.3.9-next.1 + - @backstage/plugin-user-settings@0.7.2-next.1 + - @backstage/plugin-azure-devops@0.2.8-next.1 + - @backstage/plugin-gcp-projects@0.3.36-next.1 + - @backstage/plugin-search-react@1.5.2-next.1 + - @backstage/plugin-azure-sites@0.1.6-next.1 + - @backstage/plugin-cloudbuild@0.3.17-next.1 + - @backstage/plugin-kubernetes@0.7.10-next.1 + - @backstage/plugin-lighthouse@0.4.2-next.1 + - @backstage/plugin-tech-radar@0.6.3-next.1 + - @backstage/plugin-gcalendar@0.3.13-next.1 + - @backstage/plugin-pagerduty@0.5.10-next.1 + - @backstage/plugin-shortcuts@0.3.9-next.1 + - @backstage/plugin-api-docs@0.9.2-next.1 + - @backstage/plugin-newrelic@0.3.35-next.1 + - @backstage/plugin-explore@0.4.2-next.1 + - @backstage/plugin-jenkins@0.7.16-next.1 + - @backstage/plugin-rollbar@0.4.17-next.1 + - @backstage/theme@0.2.19-next.0 + - @backstage/plugin-badges@0.2.41-next.1 + - @backstage/plugin-search@1.1.2-next.1 + - @backstage/plugin-sentry@0.5.2-next.1 + - @backstage/plugin-kafka@0.3.17-next.1 + - @backstage/plugin-gocd@0.1.23-next.1 + - @backstage/plugin-home@0.4.33-next.1 + - @backstage/plugin-todo@0.2.19-next.1 + - @backstage/app-defaults@1.3.0-next.1 + - @backstage/catalog-model@1.2.1 + - @backstage/config@1.0.7 + - @backstage/plugin-catalog-common@1.0.13-next.0 + - @backstage/plugin-linguist-common@0.1.0 + - @backstage/plugin-search-common@1.2.3-next.0 + - @internal/plugin-catalog-customized@0.0.9-next.1 + +## 0.2.82-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-api-docs@0.9.2-next.0 + - @backstage/plugin-scaffolder@1.13.0-next.0 + - @backstage/plugin-scaffolder-react@1.3.0-next.0 + - @backstage/plugin-entity-feedback@0.2.0-next.0 + - @backstage/plugin-tech-insights@0.3.9-next.0 + - @backstage/core-components@0.12.6-next.0 + - @backstage/app-defaults@1.3.0-next.0 + - @backstage/core-app-api@1.7.0-next.0 + - @backstage/plugin-search-react@1.5.2-next.0 + - @backstage/plugin-techdocs@1.6.1-next.0 + - @backstage/cli@0.22.6-next.0 + - @backstage/plugin-techdocs-react@1.1.5-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.12-next.0 + - @backstage/plugin-newrelic-dashboard@0.2.10-next.0 + - @backstage/plugin-apache-airflow@0.2.10-next.0 + - @backstage/plugin-catalog-import@0.9.7-next.0 + - @backstage/plugin-github-actions@0.5.17-next.0 + - @backstage/plugin-catalog-graph@0.2.29-next.0 + - @backstage/plugin-catalog-react@1.4.1-next.0 + - @backstage/plugin-code-coverage@0.2.10-next.0 + - @backstage/plugin-cost-insights@0.12.6-next.0 + - @backstage/plugin-user-settings@0.7.2-next.0 + - @backstage/plugin-azure-devops@0.2.8-next.0 + - @backstage/plugin-gcp-projects@0.3.36-next.0 + - @backstage/plugin-azure-sites@0.1.6-next.0 + - @backstage/plugin-cloudbuild@0.3.17-next.0 + - @backstage/plugin-kubernetes@0.7.10-next.0 + - @backstage/plugin-lighthouse@0.4.2-next.0 + - @backstage/plugin-tech-radar@0.6.3-next.0 + - @backstage/plugin-dynatrace@3.0.1-next.0 + - @backstage/plugin-gcalendar@0.3.13-next.0 + - @backstage/plugin-pagerduty@0.5.10-next.0 + - @backstage/plugin-shortcuts@0.3.9-next.0 + - @backstage/plugin-airbrake@0.3.17-next.0 + - @backstage/plugin-circleci@0.3.17-next.0 + - @backstage/plugin-graphiql@0.2.49-next.0 + - @backstage/plugin-newrelic@0.3.35-next.0 + - @backstage/plugin-explore@0.4.2-next.0 + - @backstage/plugin-jenkins@0.7.16-next.0 + - @backstage/plugin-rollbar@0.4.17-next.0 + - @backstage/plugin-badges@0.2.41-next.0 + - @backstage/plugin-search@1.1.2-next.0 + - @backstage/plugin-sentry@0.5.2-next.0 + - @backstage/plugin-kafka@0.3.17-next.0 + - @backstage/plugin-gocd@0.1.23-next.0 + - @backstage/plugin-home@0.4.33-next.0 + - @backstage/plugin-todo@0.2.19-next.0 + - @backstage/plugin-org@0.6.7-next.0 + - @internal/plugin-catalog-customized@0.0.9-next.0 + - @backstage/integration-react@1.1.12-next.0 + - @backstage/plugin-linguist@0.1.2-next.0 + - @backstage/plugin-microsoft-calendar@0.1.2-next.0 + - @backstage/plugin-playlist@0.1.8-next.0 + - @backstage/plugin-stack-overflow@0.1.13-next.0 + - @backstage/plugin-stackstorm@0.1.1-next.0 + - @backstage/core-plugin-api@1.5.0 + - @backstage/config@1.0.7 + - @backstage/plugin-permission-react@0.4.11 + - @backstage/catalog-model@1.2.1 + - @backstage/theme@0.2.18 + - @backstage/plugin-catalog-common@1.0.12 + - @backstage/plugin-linguist-common@0.1.0 + - @backstage/plugin-search-common@1.2.2 + +## 0.2.81 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.5 + - @backstage/plugin-scaffolder-react@1.2.0 + - @backstage/plugin-techdocs-react@1.1.4 + - @backstage/plugin-catalog-react@1.4.0 + - @backstage/plugin-tech-insights@0.3.8 + - @backstage/plugin-search-react@1.5.1 + - @backstage/plugin-scaffolder@1.12.0 + - @backstage/plugin-techdocs@1.6.0 + - @backstage/plugin-explore@0.4.1 + - @backstage/plugin-search@1.1.1 + - @backstage/cli@0.22.4 + - @backstage/plugin-kubernetes@0.7.9 + - @backstage/plugin-tech-radar@0.6.2 + - @backstage/plugin-microsoft-calendar@0.1.1 + - @backstage/plugin-newrelic-dashboard@0.2.9 + - @backstage/plugin-code-coverage@0.2.9 + - @backstage/plugin-azure-sites@0.1.5 + - @backstage/plugin-gcalendar@0.3.12 + - @backstage/plugin-shortcuts@0.3.8 + - @backstage/plugin-org@0.6.6 + - @backstage/plugin-api-docs@0.9.1 + - @backstage/plugin-stackstorm@0.1.0 + - @backstage/plugin-circleci@0.3.16 + - @backstage/plugin-playlist@0.1.7 + - @backstage/core-plugin-api@1.5.0 + - @backstage/catalog-model@1.2.1 + - @backstage/plugin-catalog-common@1.0.12 + - @backstage/plugin-cost-insights@0.12.5 + - @backstage/plugin-jenkins@0.7.15 + - @backstage/core-app-api@1.6.0 + - @backstage/integration-react@1.1.11 + - @backstage/plugin-airbrake@0.3.16 + - @backstage/plugin-apache-airflow@0.2.9 + - @backstage/plugin-azure-devops@0.2.7 + - @backstage/plugin-badges@0.2.40 + - @backstage/plugin-catalog-import@0.9.6 + - @backstage/plugin-cloudbuild@0.3.16 + - @backstage/plugin-dynatrace@3.0.0 + - @backstage/plugin-entity-feedback@0.1.1 + - @backstage/plugin-gcp-projects@0.3.35 + - @backstage/plugin-github-actions@0.5.16 + - @backstage/plugin-gocd@0.1.22 + - @backstage/plugin-graphiql@0.2.48 + - @backstage/plugin-home@0.4.32 + - @backstage/plugin-kafka@0.3.16 + - @backstage/plugin-lighthouse@0.4.1 + - @backstage/plugin-linguist@0.1.1 + - @backstage/plugin-newrelic@0.3.34 + - @backstage/plugin-pagerduty@0.5.9 + - @backstage/plugin-rollbar@0.4.16 + - @backstage/plugin-sentry@0.5.1 + - @backstage/plugin-stack-overflow@0.1.12 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.11 + - @backstage/plugin-todo@0.2.18 + - @backstage/plugin-user-settings@0.7.1 + - @backstage/config@1.0.7 + - @backstage/theme@0.2.18 + - @internal/plugin-catalog-customized@0.0.8 + - @backstage/plugin-catalog-graph@0.2.28 + - @backstage/app-defaults@1.2.1 + - @backstage/plugin-linguist-common@0.1.0 + - @backstage/plugin-permission-react@0.4.11 + - @backstage/plugin-search-common@1.2.2 + +## 0.2.81-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.5-next.2 + - @backstage/plugin-scaffolder-react@1.2.0-next.2 + - @backstage/plugin-techdocs-react@1.1.4-next.2 + - @backstage/plugin-catalog-react@1.4.0-next.2 + - @backstage/plugin-tech-insights@0.3.8-next.2 + - @backstage/plugin-search-react@1.5.1-next.2 + - @backstage/plugin-scaffolder@1.12.0-next.2 + - @backstage/plugin-techdocs@1.6.0-next.2 + - @backstage/plugin-explore@0.4.1-next.2 + - @backstage/plugin-search@1.1.1-next.2 + - @backstage/plugin-kubernetes@0.7.9-next.2 + - @backstage/plugin-api-docs@0.9.1-next.2 + - @backstage/core-app-api@1.6.0-next.2 + - @backstage/plugin-org@0.6.6-next.2 + - @backstage/core-plugin-api@1.5.0-next.2 + - @backstage/app-defaults@1.2.1-next.2 + - @backstage/cli@0.22.4-next.1 + - @backstage/integration-react@1.1.11-next.2 + - @backstage/plugin-airbrake@0.3.16-next.2 + - @backstage/plugin-apache-airflow@0.2.9-next.2 + - @backstage/plugin-azure-devops@0.2.7-next.2 + - @backstage/plugin-azure-sites@0.1.5-next.2 + - @backstage/plugin-badges@0.2.40-next.2 + - @backstage/plugin-catalog-graph@0.2.28-next.2 + - @backstage/plugin-catalog-import@0.9.6-next.2 + - @backstage/plugin-circleci@0.3.16-next.2 + - @backstage/plugin-cloudbuild@0.3.16-next.2 + - @backstage/plugin-code-coverage@0.2.9-next.2 + - @backstage/plugin-cost-insights@0.12.5-next.2 + - @backstage/plugin-dynatrace@3.0.0-next.2 + - @backstage/plugin-entity-feedback@0.1.1-next.2 + - @backstage/plugin-gcalendar@0.3.12-next.2 + - @backstage/plugin-gcp-projects@0.3.35-next.2 + - @backstage/plugin-github-actions@0.5.16-next.2 + - @backstage/plugin-gocd@0.1.22-next.2 + - @backstage/plugin-graphiql@0.2.48-next.2 + - @backstage/plugin-home@0.4.32-next.2 + - @backstage/plugin-jenkins@0.7.15-next.2 + - @backstage/plugin-kafka@0.3.16-next.2 + - @backstage/plugin-lighthouse@0.4.1-next.2 + - @backstage/plugin-linguist@0.1.1-next.2 + - @backstage/plugin-microsoft-calendar@0.1.1-next.2 + - @backstage/plugin-newrelic@0.3.34-next.2 + - @backstage/plugin-newrelic-dashboard@0.2.9-next.2 + - @backstage/plugin-pagerduty@0.5.9-next.2 + - @backstage/plugin-playlist@0.1.7-next.2 + - @backstage/plugin-rollbar@0.4.16-next.2 + - @backstage/plugin-sentry@0.5.1-next.2 + - @backstage/plugin-shortcuts@0.3.8-next.2 + - @backstage/plugin-stack-overflow@0.1.12-next.2 + - @backstage/plugin-stackstorm@0.1.0-next.2 + - @backstage/plugin-tech-radar@0.6.2-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.11-next.2 + - @backstage/plugin-todo@0.2.18-next.2 + - @backstage/plugin-user-settings@0.7.1-next.2 + - @internal/plugin-catalog-customized@0.0.8-next.2 + - @backstage/plugin-permission-react@0.4.11-next.2 + - @backstage/config@1.0.7-next.0 + +## 0.2.81-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.22.4-next.1 + - @backstage/plugin-scaffolder@1.12.0-next.1 + - @backstage/plugin-scaffolder-react@1.2.0-next.1 + - @backstage/plugin-techdocs@1.6.0-next.1 + - @backstage/plugin-tech-radar@0.6.2-next.1 + - @backstage/plugin-microsoft-calendar@0.1.1-next.1 + - @backstage/plugin-newrelic-dashboard@0.2.9-next.1 + - @backstage/core-components@0.12.5-next.1 + - @backstage/plugin-code-coverage@0.2.9-next.1 + - @backstage/plugin-azure-sites@0.1.5-next.1 + - @backstage/plugin-gcalendar@0.3.12-next.1 + - @backstage/plugin-shortcuts@0.3.8-next.1 + - @backstage/plugin-explore@0.4.1-next.1 + - @backstage/plugin-org@0.6.6-next.1 + - @backstage/plugin-kubernetes@0.7.9-next.1 + - @backstage/plugin-circleci@0.3.16-next.1 + - @backstage/plugin-playlist@0.1.7-next.1 + - @backstage/plugin-techdocs-react@1.1.4-next.1 + - @backstage/core-app-api@1.5.1-next.1 + - @backstage/core-plugin-api@1.4.1-next.1 + - @backstage/integration-react@1.1.11-next.1 + - @backstage/plugin-airbrake@0.3.16-next.1 + - @backstage/plugin-apache-airflow@0.2.9-next.1 + - @backstage/plugin-api-docs@0.9.1-next.1 + - @backstage/plugin-azure-devops@0.2.7-next.1 + - @backstage/plugin-badges@0.2.40-next.1 + - @backstage/plugin-catalog-import@0.9.6-next.1 + - @backstage/plugin-cloudbuild@0.3.16-next.1 + - @backstage/plugin-cost-insights@0.12.5-next.1 + - @backstage/plugin-dynatrace@3.0.0-next.1 + - @backstage/plugin-entity-feedback@0.1.1-next.1 + - @backstage/plugin-gcp-projects@0.3.35-next.1 + - @backstage/plugin-github-actions@0.5.16-next.1 + - @backstage/plugin-gocd@0.1.22-next.1 + - @backstage/plugin-graphiql@0.2.48-next.1 + - @backstage/plugin-home@0.4.32-next.1 + - @backstage/plugin-jenkins@0.7.15-next.1 + - @backstage/plugin-kafka@0.3.16-next.1 + - @backstage/plugin-lighthouse@0.4.1-next.1 + - @backstage/plugin-linguist@0.1.1-next.1 + - @backstage/plugin-newrelic@0.3.34-next.1 + - @backstage/plugin-pagerduty@0.5.9-next.1 + - @backstage/plugin-rollbar@0.4.16-next.1 + - @backstage/plugin-search@1.1.1-next.1 + - @backstage/plugin-sentry@0.5.1-next.1 + - @backstage/plugin-stack-overflow@0.1.12-next.1 + - @backstage/plugin-stackstorm@0.1.0-next.1 + - @backstage/plugin-tech-insights@0.3.8-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.11-next.1 + - @backstage/plugin-todo@0.2.18-next.1 + - @backstage/plugin-user-settings@0.7.1-next.1 + - @backstage/config@1.0.7-next.0 + - @backstage/theme@0.2.18-next.0 + - @backstage/plugin-catalog-react@1.4.0-next.1 + - @backstage/app-defaults@1.2.1-next.1 + - @backstage/catalog-model@1.2.1-next.1 + - @backstage/plugin-catalog-common@1.0.12-next.1 + - @backstage/plugin-catalog-graph@0.2.28-next.1 + - @backstage/plugin-linguist-common@0.1.0 + - @backstage/plugin-permission-react@0.4.11-next.1 + - @backstage/plugin-search-common@1.2.2-next.0 + - @backstage/plugin-search-react@1.5.1-next.1 + - @internal/plugin-catalog-customized@0.0.8-next.1 + +## 0.2.81-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.1.1-next.0 + - @backstage/cli@0.22.4-next.0 + - @backstage/plugin-catalog-react@1.4.0-next.0 + - @backstage/plugin-techdocs@1.6.0-next.0 + - @backstage/plugin-api-docs@0.9.1-next.0 + - @backstage/plugin-org@0.6.6-next.0 + - @backstage/plugin-stackstorm@0.1.0-next.0 + - @backstage/core-plugin-api@1.4.1-next.0 + - @backstage/catalog-model@1.2.1-next.0 + - @backstage/plugin-catalog-common@1.0.12-next.0 + - @backstage/plugin-techdocs-react@1.1.4-next.0 + - @backstage/plugin-cost-insights@0.12.5-next.0 + - @backstage/plugin-scaffolder@1.11.1-next.0 + - @backstage/plugin-jenkins@0.7.15-next.0 + - @backstage/plugin-playlist@0.1.7-next.0 + - @backstage/plugin-explore@0.4.1-next.0 + - @backstage/plugin-catalog-graph@0.2.28-next.0 + - @backstage/plugin-catalog-import@0.9.6-next.0 + - @backstage/app-defaults@1.2.1-next.0 + - @backstage/config@1.0.6 + - @backstage/core-app-api@1.5.1-next.0 + - @backstage/core-components@0.12.5-next.0 + - @backstage/integration-react@1.1.11-next.0 + - @backstage/theme@0.2.17 + - @backstage/plugin-airbrake@0.3.16-next.0 + - @backstage/plugin-apache-airflow@0.2.9-next.0 + - @backstage/plugin-azure-devops@0.2.7-next.0 + - @backstage/plugin-azure-sites@0.1.5-next.0 + - @backstage/plugin-badges@0.2.40-next.0 + - @backstage/plugin-circleci@0.3.16-next.0 + - @backstage/plugin-cloudbuild@0.3.16-next.0 + - @backstage/plugin-code-coverage@0.2.9-next.0 + - @backstage/plugin-dynatrace@3.0.0-next.0 + - @backstage/plugin-entity-feedback@0.1.1-next.0 + - @backstage/plugin-gcalendar@0.3.12-next.0 + - @backstage/plugin-gcp-projects@0.3.35-next.0 + - @backstage/plugin-github-actions@0.5.16-next.0 + - @backstage/plugin-gocd@0.1.22-next.0 + - @backstage/plugin-graphiql@0.2.48-next.0 + - @backstage/plugin-home@0.4.32-next.0 + - @backstage/plugin-kafka@0.3.16-next.0 + - @backstage/plugin-kubernetes@0.7.9-next.0 + - @backstage/plugin-lighthouse@0.4.1-next.0 + - @backstage/plugin-linguist@0.1.1-next.0 + - @backstage/plugin-linguist-common@0.1.0 + - @backstage/plugin-microsoft-calendar@0.1.1-next.0 + - @backstage/plugin-newrelic@0.3.34-next.0 + - @backstage/plugin-newrelic-dashboard@0.2.9-next.0 + - @backstage/plugin-pagerduty@0.5.9-next.0 + - @backstage/plugin-permission-react@0.4.11-next.0 + - @backstage/plugin-rollbar@0.4.16-next.0 + - @backstage/plugin-search@1.1.1-next.0 + - @backstage/plugin-search-common@1.2.1 + - @backstage/plugin-search-react@1.5.1-next.0 + - @backstage/plugin-sentry@0.5.1-next.0 + - @backstage/plugin-shortcuts@0.3.8-next.0 + - @backstage/plugin-stack-overflow@0.1.12-next.0 + - @backstage/plugin-tech-insights@0.3.8-next.0 + - @backstage/plugin-tech-radar@0.6.2-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.11-next.0 + - @backstage/plugin-todo@0.2.18-next.0 + - @backstage/plugin-user-settings@0.7.1-next.0 + - @internal/plugin-catalog-customized@0.0.8-next.0 + +## 0.2.80 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.1.0 + - @backstage/plugin-scaffolder@1.11.0 + - @backstage/plugin-azure-devops@0.2.6 + - @backstage/cli@0.22.2 + - @backstage/plugin-techdocs@1.5.0 + - @backstage/core-components@0.12.4 + - @backstage/plugin-microsoft-calendar@0.1.0 + - @backstage/plugin-catalog-graph@0.2.27 + - @backstage/plugin-code-coverage@0.2.8 + - @backstage/plugin-azure-sites@0.1.4 + - @backstage/plugin-lighthouse@0.4.0 + - @backstage/plugin-shortcuts@0.3.7 + - @backstage/plugin-home@0.4.31 + - @backstage/plugin-tech-insights@0.3.7 + - @backstage/plugin-search@1.1.0 + - @backstage/plugin-api-docs@0.9.0 + - @backstage/plugin-entity-feedback@0.1.0 + - @backstage/plugin-tech-radar@0.6.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.10 + - @backstage/plugin-dynatrace@2.0.0 + - @backstage/plugin-playlist@0.1.6 + - @backstage/catalog-model@1.2.0 + - @backstage/theme@0.2.17 + - @backstage/core-app-api@1.5.0 + - @backstage/plugin-kubernetes@0.7.8 + - @backstage/core-plugin-api@1.4.0 + - @backstage/app-defaults@1.2.0 + - @backstage/plugin-user-settings@0.7.0 + - @backstage/plugin-catalog-react@1.3.0 + - @backstage/plugin-pagerduty@0.5.8 + - @backstage/plugin-explore@0.4.0 + - @backstage/plugin-linguist@0.1.0 + - @backstage/plugin-linguist-common@0.1.0 + - @backstage/plugin-search-react@1.5.0 + - @backstage/plugin-apache-airflow@0.2.8 + - @backstage/plugin-circleci@0.3.15 + - @backstage/plugin-sentry@0.5.0 + - @backstage/plugin-org@0.6.5 + - @backstage/plugin-cost-insights@0.12.4 + - @backstage/plugin-airbrake@0.3.15 + - @internal/plugin-catalog-customized@0.0.7 + - @backstage/config@1.0.6 + - @backstage/integration-react@1.1.10 + - @backstage/plugin-badges@0.2.39 + - @backstage/plugin-catalog-common@1.0.11 + - @backstage/plugin-catalog-import@0.9.5 + - @backstage/plugin-cloudbuild@0.3.15 + - @backstage/plugin-gcalendar@0.3.11 + - @backstage/plugin-gcp-projects@0.3.34 + - @backstage/plugin-github-actions@0.5.15 + - @backstage/plugin-gocd@0.1.21 + - @backstage/plugin-graphiql@0.2.47 + - @backstage/plugin-jenkins@0.7.14 + - @backstage/plugin-kafka@0.3.15 + - @backstage/plugin-newrelic@0.3.33 + - @backstage/plugin-newrelic-dashboard@0.2.8 + - @backstage/plugin-permission-react@0.4.10 + - @backstage/plugin-rollbar@0.4.15 + - @backstage/plugin-search-common@1.2.1 + - @backstage/plugin-stack-overflow@0.1.11 + - @backstage/plugin-techdocs-react@1.1.3 + - @backstage/plugin-todo@0.2.17 + +## 0.2.80-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.1.0-next.2 + - @backstage/plugin-scaffolder@1.11.0-next.2 + - @backstage/cli@0.22.2-next.1 + - @backstage/plugin-api-docs@0.9.0-next.2 + - @backstage/plugin-tech-radar@0.6.1-next.1 + - @backstage/catalog-model@1.2.0-next.1 + - @backstage/plugin-techdocs@1.5.0-next.2 + - @backstage/plugin-explore@0.4.0-next.2 + - @backstage/plugin-search-react@1.5.0-next.1 + - @backstage/plugin-sentry@0.5.0-next.2 + - @backstage/plugin-circleci@0.3.15-next.2 + - @backstage/core-components@0.12.4-next.1 + - @backstage/plugin-azure-sites@0.1.4-next.2 + - @backstage/plugin-search@1.1.0-next.2 + - @backstage/plugin-lighthouse@0.4.0-next.2 + - @backstage/app-defaults@1.1.1-next.1 + - @backstage/config@1.0.6 + - @backstage/core-app-api@1.4.1-next.0 + - @backstage/core-plugin-api@1.3.0 + - @backstage/integration-react@1.1.10-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-airbrake@0.3.15-next.2 + - @backstage/plugin-apache-airflow@0.2.8-next.1 + - @backstage/plugin-azure-devops@0.2.6-next.2 + - @backstage/plugin-badges@0.2.39-next.2 + - @backstage/plugin-catalog-common@1.0.11-next.1 + - @backstage/plugin-catalog-graph@0.2.27-next.2 + - @backstage/plugin-catalog-import@0.9.5-next.2 + - @backstage/plugin-catalog-react@1.3.0-next.2 + - @backstage/plugin-cloudbuild@0.3.15-next.2 + - @backstage/plugin-code-coverage@0.2.8-next.2 + - @backstage/plugin-cost-insights@0.12.4-next.2 + - @backstage/plugin-dynatrace@2.0.0-next.2 + - @backstage/plugin-gcalendar@0.3.11-next.1 + - @backstage/plugin-gcp-projects@0.3.34-next.1 + - @backstage/plugin-github-actions@0.5.15-next.2 + - @backstage/plugin-gocd@0.1.21-next.2 + - @backstage/plugin-graphiql@0.2.47-next.1 + - @backstage/plugin-home@0.4.31-next.2 + - @backstage/plugin-jenkins@0.7.14-next.2 + - @backstage/plugin-kafka@0.3.15-next.2 + - @backstage/plugin-kubernetes@0.7.8-next.2 + - @backstage/plugin-newrelic@0.3.33-next.1 + - @backstage/plugin-newrelic-dashboard@0.2.8-next.2 + - @backstage/plugin-org@0.6.5-next.2 + - @backstage/plugin-pagerduty@0.5.8-next.2 + - @backstage/plugin-permission-react@0.4.9 + - @backstage/plugin-playlist@0.1.6-next.2 + - @backstage/plugin-rollbar@0.4.15-next.2 + - @backstage/plugin-search-common@1.2.1 + - @backstage/plugin-shortcuts@0.3.7-next.1 + - @backstage/plugin-stack-overflow@0.1.11-next.2 + - @backstage/plugin-tech-insights@0.3.7-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.10-next.2 + - @backstage/plugin-techdocs-react@1.1.3-next.2 + - @backstage/plugin-todo@0.2.17-next.2 + - @backstage/plugin-user-settings@0.6.3-next.2 + - @internal/plugin-catalog-customized@0.0.7-next.2 + +## 0.2.80-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.1.0-next.1 + - @backstage/plugin-scaffolder@1.11.0-next.1 + - @backstage/cli@0.22.2-next.0 + - @backstage/plugin-techdocs@1.5.0-next.1 + - @backstage/plugin-tech-insights@0.3.7-next.1 + - @backstage/plugin-search@1.1.0-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.10-next.1 + - @backstage/plugin-dynatrace@2.0.0-next.1 + - @backstage/plugin-playlist@0.1.6-next.1 + - @backstage/plugin-home@0.4.31-next.1 + - @backstage/core-app-api@1.4.1-next.0 + - @backstage/plugin-kubernetes@0.7.8-next.1 + - @backstage/core-components@0.12.4-next.0 + - @backstage/plugin-tech-radar@0.6.1-next.0 + - @backstage/plugin-explore@0.4.0-next.1 + - @backstage/plugin-apache-airflow@0.2.8-next.0 + - @backstage/plugin-circleci@0.3.15-next.1 + - @backstage/plugin-sentry@0.4.8-next.1 + - @backstage/plugin-search-react@1.5.0-next.0 + - @backstage/plugin-airbrake@0.3.15-next.1 + - @backstage/plugin-catalog-react@1.3.0-next.1 + - @backstage/app-defaults@1.1.1-next.0 + - @backstage/catalog-model@1.1.6-next.0 + - @backstage/config@1.0.6 + - @backstage/core-plugin-api@1.3.0 + - @backstage/integration-react@1.1.10-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-api-docs@0.8.15-next.1 + - @backstage/plugin-azure-devops@0.2.6-next.1 + - @backstage/plugin-azure-sites@0.1.4-next.1 + - @backstage/plugin-badges@0.2.39-next.1 + - @backstage/plugin-catalog-common@1.0.11-next.0 + - @backstage/plugin-catalog-graph@0.2.27-next.1 + - @backstage/plugin-catalog-import@0.9.5-next.1 + - @backstage/plugin-cloudbuild@0.3.15-next.1 + - @backstage/plugin-code-coverage@0.2.8-next.1 + - @backstage/plugin-cost-insights@0.12.4-next.1 + - @backstage/plugin-gcalendar@0.3.11-next.0 + - @backstage/plugin-gcp-projects@0.3.34-next.0 + - @backstage/plugin-github-actions@0.5.15-next.1 + - @backstage/plugin-gocd@0.1.21-next.1 + - @backstage/plugin-graphiql@0.2.47-next.0 + - @backstage/plugin-jenkins@0.7.14-next.1 + - @backstage/plugin-kafka@0.3.15-next.1 + - @backstage/plugin-lighthouse@0.3.15-next.1 + - @backstage/plugin-newrelic@0.3.33-next.0 + - @backstage/plugin-newrelic-dashboard@0.2.8-next.1 + - @backstage/plugin-org@0.6.5-next.1 + - @backstage/plugin-pagerduty@0.5.8-next.1 + - @backstage/plugin-permission-react@0.4.9 + - @backstage/plugin-rollbar@0.4.15-next.1 + - @backstage/plugin-search-common@1.2.1 + - @backstage/plugin-shortcuts@0.3.7-next.0 + - @backstage/plugin-stack-overflow@0.1.11-next.1 + - @backstage/plugin-techdocs-react@1.1.3-next.1 + - @backstage/plugin-todo@0.2.17-next.1 + - @backstage/plugin-user-settings@0.6.3-next.1 + - @internal/plugin-catalog-customized@0.0.7-next.1 + +## 0.2.80-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-azure-devops@0.2.6-next.0 + - @backstage/plugin-scaffolder-react@1.1.0-next.0 + - @backstage/plugin-scaffolder@1.11.0-next.0 + - @backstage/plugin-api-docs@0.8.15-next.0 + - @backstage/plugin-techdocs@1.4.4-next.0 + - @backstage/plugin-catalog-react@1.3.0-next.0 + - @backstage/plugin-user-settings@0.6.3-next.0 + - @backstage/catalog-model@1.1.6-next.0 + - @backstage/plugin-kubernetes@0.7.8-next.0 + - @backstage/plugin-airbrake@0.3.15-next.0 + - @backstage/plugin-azure-sites@0.1.4-next.0 + - @backstage/plugin-badges@0.2.39-next.0 + - @internal/plugin-catalog-customized@0.0.7-next.0 + - @backstage/plugin-catalog-graph@0.2.27-next.0 + - @backstage/plugin-catalog-import@0.9.5-next.0 + - @backstage/plugin-circleci@0.3.15-next.0 + - @backstage/plugin-cloudbuild@0.3.15-next.0 + - @backstage/plugin-code-coverage@0.2.8-next.0 + - @backstage/plugin-cost-insights@0.12.4-next.0 + - @backstage/plugin-dynatrace@2.0.0-next.0 + - @backstage/plugin-explore@0.3.46-next.0 + - @backstage/plugin-github-actions@0.5.15-next.0 + - @backstage/plugin-gocd@0.1.21-next.0 + - @backstage/plugin-home@0.4.31-next.0 + - @backstage/plugin-jenkins@0.7.14-next.0 + - @backstage/plugin-kafka@0.3.15-next.0 + - @backstage/plugin-lighthouse@0.3.15-next.0 + - @backstage/plugin-newrelic-dashboard@0.2.8-next.0 + - @backstage/plugin-org@0.6.5-next.0 + - @backstage/plugin-pagerduty@0.5.8-next.0 + - @backstage/plugin-playlist@0.1.6-next.0 + - @backstage/plugin-rollbar@0.4.15-next.0 + - @backstage/plugin-search@1.0.8-next.0 + - @backstage/plugin-sentry@0.4.8-next.0 + - @backstage/plugin-tech-insights@0.3.7-next.0 + - @backstage/plugin-todo@0.2.17-next.0 + - @backstage/plugin-catalog-common@1.0.11-next.0 + - @backstage/plugin-techdocs-react@1.1.3-next.0 + - @backstage/cli@0.22.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.10-next.0 + - @backstage/integration-react@1.1.9 + - @backstage/plugin-apache-airflow@0.2.7 + - @backstage/plugin-gcalendar@0.3.10 + - @backstage/plugin-gcp-projects@0.3.33 + - @backstage/plugin-graphiql@0.2.46 + - @backstage/plugin-newrelic@0.3.32 + - @backstage/plugin-shortcuts@0.3.6 + - @backstage/plugin-stack-overflow@0.1.11-next.0 + - @backstage/plugin-tech-radar@0.6.0 + +## 0.2.79 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.22.1 + - @backstage/plugin-search@1.0.7 + - @backstage/plugin-scaffolder@1.10.0 + - @backstage/plugin-catalog-graph@0.2.26 + - @backstage/catalog-model@1.1.5 + - @backstage/plugin-catalog-react@1.2.4 + - @backstage/core-components@0.12.3 + - @backstage/plugin-scaffolder-react@1.0.0 + - @backstage/plugin-user-settings@0.6.2 + - @backstage/plugin-explore@0.3.45 + - @backstage/plugin-techdocs@1.4.3 + - @backstage/plugin-github-actions@0.5.14 + - @backstage/plugin-home@0.4.30 + - @backstage/plugin-jenkins@0.7.13 + - @backstage/plugin-kubernetes@0.7.7 + - @backstage/plugin-stack-overflow@0.1.10 + - @backstage/plugin-tech-radar@0.6.0 + - @backstage/plugin-search-react@1.4.0 + - @backstage/app-defaults@1.1.0 + - @backstage/core-plugin-api@1.3.0 + - @backstage/core-app-api@1.4.0 + - @backstage/plugin-lighthouse@0.3.14 + - @backstage/plugin-cost-insights@0.12.3 + - @backstage/plugin-cloudbuild@0.3.14 + - @backstage/plugin-api-docs@0.8.14 + - @backstage/plugin-azure-devops@0.2.5 + - @backstage/plugin-azure-sites@0.1.3 + - @backstage/plugin-gcp-projects@0.3.33 + - @backstage/plugin-techdocs-react@1.1.2 + - @backstage/plugin-permission-react@0.4.9 + - @backstage/plugin-playlist@0.1.5 + - @backstage/config@1.0.6 + - @backstage/integration-react@1.1.9 + - @backstage/theme@0.2.16 + - @backstage/plugin-airbrake@0.3.14 + - @backstage/plugin-apache-airflow@0.2.7 + - @backstage/plugin-badges@0.2.38 + - @backstage/plugin-catalog-common@1.0.10 + - @backstage/plugin-catalog-import@0.9.4 + - @backstage/plugin-circleci@0.3.14 + - @backstage/plugin-code-coverage@0.2.7 + - @backstage/plugin-dynatrace@1.0.4 + - @backstage/plugin-gcalendar@0.3.10 + - @backstage/plugin-gocd@0.1.20 + - @backstage/plugin-graphiql@0.2.46 + - @backstage/plugin-kafka@0.3.14 + - @backstage/plugin-newrelic@0.3.32 + - @backstage/plugin-newrelic-dashboard@0.2.7 + - @backstage/plugin-org@0.6.4 + - @backstage/plugin-pagerduty@0.5.7 + - @backstage/plugin-rollbar@0.4.14 + - @backstage/plugin-search-common@1.2.1 + - @backstage/plugin-sentry@0.4.7 + - @backstage/plugin-shortcuts@0.3.6 + - @backstage/plugin-tech-insights@0.3.6 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.9 + - @backstage/plugin-todo@0.2.16 + - @internal/plugin-catalog-customized@0.0.6 + +## 0.2.79-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.10.0-next.2 + - @backstage/cli@0.22.1-next.2 + - @backstage/plugin-scaffolder-react@1.0.0-next.0 + - @backstage/plugin-user-settings@0.6.2-next.2 + - @backstage/plugin-search-react@1.4.0-next.2 + - @backstage/plugin-tech-radar@0.6.0-next.2 + - @backstage/app-defaults@1.1.0-next.2 + - @backstage/core-plugin-api@1.3.0-next.1 + - @backstage/core-app-api@1.4.0-next.1 + - @backstage/plugin-lighthouse@0.3.14-next.2 + - @backstage/plugin-catalog-react@1.2.4-next.2 + - @backstage/plugin-permission-react@0.4.9-next.1 + - @backstage/plugin-playlist@0.1.5-next.2 + - @backstage/plugin-techdocs-react@1.1.2-next.2 + - @backstage/catalog-model@1.1.5-next.1 + - @backstage/config@1.0.6-next.0 + - @backstage/core-components@0.12.3-next.2 + - @backstage/integration-react@1.1.9-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-airbrake@0.3.14-next.2 + - @backstage/plugin-apache-airflow@0.2.7-next.2 + - @backstage/plugin-api-docs@0.8.14-next.2 + - @backstage/plugin-azure-devops@0.2.5-next.2 + - @backstage/plugin-azure-sites@0.1.3-next.2 + - @backstage/plugin-badges@0.2.38-next.2 + - @backstage/plugin-catalog-common@1.0.10-next.1 + - @backstage/plugin-catalog-graph@0.2.26-next.2 + - @backstage/plugin-catalog-import@0.9.4-next.2 + - @backstage/plugin-circleci@0.3.14-next.2 + - @backstage/plugin-cloudbuild@0.3.14-next.2 + - @backstage/plugin-code-coverage@0.2.7-next.2 + - @backstage/plugin-cost-insights@0.12.3-next.2 + - @backstage/plugin-dynatrace@1.0.4-next.2 + - @backstage/plugin-explore@0.3.45-next.2 + - @backstage/plugin-gcalendar@0.3.10-next.2 + - @backstage/plugin-gcp-projects@0.3.33-next.2 + - @backstage/plugin-github-actions@0.5.14-next.2 + - @backstage/plugin-gocd@0.1.20-next.2 + - @backstage/plugin-graphiql@0.2.46-next.2 + - @backstage/plugin-home@0.4.30-next.2 + - @backstage/plugin-jenkins@0.7.13-next.2 + - @backstage/plugin-kafka@0.3.14-next.2 + - @backstage/plugin-kubernetes@0.7.7-next.2 + - @backstage/plugin-newrelic@0.3.32-next.2 + - @backstage/plugin-newrelic-dashboard@0.2.7-next.2 + - @backstage/plugin-org@0.6.4-next.2 + - @backstage/plugin-pagerduty@0.5.7-next.2 + - @backstage/plugin-rollbar@0.4.14-next.2 + - @backstage/plugin-search@1.0.7-next.2 + - @backstage/plugin-search-common@1.2.1-next.0 + - @backstage/plugin-sentry@0.4.7-next.2 + - @backstage/plugin-shortcuts@0.3.6-next.2 + - @backstage/plugin-stack-overflow@0.1.10-next.2 + - @backstage/plugin-tech-insights@0.3.6-next.2 + - @backstage/plugin-techdocs@1.4.3-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.9-next.2 + - @backstage/plugin-todo@0.2.16-next.2 + - @internal/plugin-catalog-customized@0.0.6-next.2 + +## 0.2.79-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.22.1-next.1 + - @backstage/plugin-catalog-graph@0.2.26-next.1 + - @backstage/plugin-explore@0.3.45-next.1 + - @backstage/plugin-techdocs@1.4.3-next.1 + - @backstage/plugin-cost-insights@0.12.3-next.1 + - @backstage/config@1.0.6-next.0 + - @backstage/app-defaults@1.0.11-next.1 + - @backstage/catalog-model@1.1.5-next.1 + - @backstage/core-app-api@1.3.1-next.0 + - @backstage/core-components@0.12.3-next.1 + - @backstage/core-plugin-api@1.2.1-next.0 + - @backstage/integration-react@1.1.9-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-airbrake@0.3.14-next.1 + - @backstage/plugin-apache-airflow@0.2.7-next.1 + - @backstage/plugin-api-docs@0.8.14-next.1 + - @backstage/plugin-azure-devops@0.2.5-next.1 + - @backstage/plugin-azure-sites@0.1.3-next.1 + - @backstage/plugin-badges@0.2.38-next.1 + - @backstage/plugin-catalog-common@1.0.10-next.1 + - @backstage/plugin-catalog-import@0.9.4-next.1 + - @backstage/plugin-catalog-react@1.2.4-next.1 + - @backstage/plugin-circleci@0.3.14-next.1 + - @backstage/plugin-cloudbuild@0.3.14-next.1 + - @backstage/plugin-code-coverage@0.2.7-next.1 + - @backstage/plugin-dynatrace@1.0.4-next.1 + - @backstage/plugin-gcalendar@0.3.10-next.1 + - @backstage/plugin-gcp-projects@0.3.33-next.1 + - @backstage/plugin-github-actions@0.5.14-next.1 + - @backstage/plugin-gocd@0.1.20-next.1 + - @backstage/plugin-graphiql@0.2.46-next.1 + - @backstage/plugin-home@0.4.30-next.1 + - @backstage/plugin-jenkins@0.7.13-next.1 + - @backstage/plugin-kafka@0.3.14-next.1 + - @backstage/plugin-kubernetes@0.7.7-next.1 + - @backstage/plugin-lighthouse@0.3.14-next.1 + - @backstage/plugin-newrelic@0.3.32-next.1 + - @backstage/plugin-newrelic-dashboard@0.2.7-next.1 + - @backstage/plugin-org@0.6.4-next.1 + - @backstage/plugin-pagerduty@0.5.7-next.1 + - @backstage/plugin-permission-react@0.4.9-next.0 + - @backstage/plugin-playlist@0.1.5-next.1 + - @backstage/plugin-rollbar@0.4.14-next.1 + - @backstage/plugin-scaffolder@1.10.0-next.1 + - @backstage/plugin-search@1.0.7-next.1 + - @backstage/plugin-search-common@1.2.1-next.0 + - @backstage/plugin-search-react@1.3.2-next.1 + - @backstage/plugin-sentry@0.4.7-next.1 + - @backstage/plugin-shortcuts@0.3.6-next.1 + - @backstage/plugin-stack-overflow@0.1.10-next.1 + - @backstage/plugin-tech-insights@0.3.6-next.1 + - @backstage/plugin-tech-radar@0.5.21-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.9-next.1 + - @backstage/plugin-techdocs-react@1.1.2-next.1 + - @backstage/plugin-todo@0.2.16-next.1 + - @backstage/plugin-user-settings@0.6.2-next.1 + - @internal/plugin-catalog-customized@0.0.6-next.1 + +## 0.2.79-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.22.1-next.0 + - @backstage/plugin-scaffolder@1.10.0-next.0 + - @backstage/catalog-model@1.1.5-next.0 + - @backstage/plugin-catalog-react@1.2.4-next.0 + - @backstage/core-components@0.12.3-next.0 + - @backstage/plugin-techdocs-react@1.1.2-next.0 + - @backstage/plugin-techdocs@1.4.3-next.0 + - @backstage/plugin-explore@0.3.45-next.0 + - @backstage/app-defaults@1.0.11-next.0 + - @backstage/config@1.0.5 + - @backstage/core-app-api@1.3.0 + - @backstage/core-plugin-api@1.2.0 + - @backstage/integration-react@1.1.9-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-airbrake@0.3.14-next.0 + - @backstage/plugin-apache-airflow@0.2.7-next.0 + - @backstage/plugin-api-docs@0.8.14-next.0 + - @backstage/plugin-azure-devops@0.2.5-next.0 + - @backstage/plugin-azure-sites@0.1.3-next.0 + - @backstage/plugin-badges@0.2.38-next.0 + - @backstage/plugin-catalog-common@1.0.10-next.0 + - @backstage/plugin-catalog-graph@0.2.26-next.0 + - @backstage/plugin-catalog-import@0.9.4-next.0 + - @backstage/plugin-circleci@0.3.14-next.0 + - @backstage/plugin-cloudbuild@0.3.14-next.0 + - @backstage/plugin-code-coverage@0.2.7-next.0 + - @backstage/plugin-cost-insights@0.12.3-next.0 + - @backstage/plugin-dynatrace@1.0.4-next.0 + - @backstage/plugin-gcalendar@0.3.10-next.0 + - @backstage/plugin-gcp-projects@0.3.33-next.0 + - @backstage/plugin-github-actions@0.5.14-next.0 + - @backstage/plugin-gocd@0.1.20-next.0 + - @backstage/plugin-graphiql@0.2.46-next.0 + - @backstage/plugin-home@0.4.30-next.0 + - @backstage/plugin-jenkins@0.7.13-next.0 + - @backstage/plugin-kafka@0.3.14-next.0 + - @backstage/plugin-kubernetes@0.7.7-next.0 + - @backstage/plugin-lighthouse@0.3.14-next.0 + - @backstage/plugin-newrelic@0.3.32-next.0 + - @backstage/plugin-newrelic-dashboard@0.2.7-next.0 + - @backstage/plugin-org@0.6.4-next.0 + - @backstage/plugin-pagerduty@0.5.7-next.0 + - @backstage/plugin-permission-react@0.4.8 + - @backstage/plugin-playlist@0.1.5-next.0 + - @backstage/plugin-rollbar@0.4.14-next.0 + - @backstage/plugin-search@1.0.7-next.0 + - @backstage/plugin-search-common@1.2.0 + - @backstage/plugin-search-react@1.3.2-next.0 + - @backstage/plugin-sentry@0.4.7-next.0 + - @backstage/plugin-shortcuts@0.3.6-next.0 + - @backstage/plugin-stack-overflow@0.1.10-next.0 + - @backstage/plugin-tech-insights@0.3.6-next.0 + - @backstage/plugin-tech-radar@0.5.21-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.9-next.0 + - @backstage/plugin-todo@0.2.16-next.0 + - @backstage/plugin-user-settings@0.6.2-next.0 + - @internal/plugin-catalog-customized@0.0.6-next.0 + +## 0.2.79 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.2 + - @backstage/app-defaults@1.0.10 + - @backstage/cli@0.22.0 + - @backstage/integration-react@1.1.8 + - @backstage/plugin-airbrake@0.3.13 + - @backstage/plugin-apache-airflow@0.2.6 + - @backstage/plugin-api-docs@0.8.13 + - @backstage/plugin-azure-devops@0.2.4 + - @backstage/plugin-azure-sites@0.1.2 + - @backstage/plugin-badges@0.2.37 + - @backstage/plugin-catalog-graph@0.2.25 + - @backstage/plugin-catalog-import@0.9.3 + - @backstage/plugin-catalog-react@1.2.3 + - @backstage/plugin-circleci@0.3.13 + - @backstage/plugin-cloudbuild@0.3.13 + - @backstage/plugin-code-coverage@0.2.6 + - @backstage/plugin-cost-insights@0.12.2 + - @backstage/plugin-dynatrace@1.0.3 + - @backstage/plugin-explore@0.3.44 + - @backstage/plugin-gcalendar@0.3.9 + - @backstage/plugin-gcp-projects@0.3.32 + - @backstage/plugin-github-actions@0.5.13 + - @backstage/plugin-gocd@0.1.19 + - @backstage/plugin-graphiql@0.2.45 + - @backstage/plugin-home@0.4.29 + - @backstage/plugin-jenkins@0.7.12 + - @backstage/plugin-kafka@0.3.13 + - @backstage/plugin-kubernetes@0.7.6 + - @backstage/plugin-lighthouse@0.3.13 + - @backstage/plugin-newrelic@0.3.31 + - @backstage/plugin-newrelic-dashboard@0.2.6 + - @backstage/plugin-org@0.6.3 + - @backstage/plugin-pagerduty@0.5.6 + - @backstage/plugin-playlist@0.1.4 + - @backstage/plugin-rollbar@0.4.13 + - @backstage/plugin-scaffolder@1.9.1 + - @backstage/plugin-search@1.0.6 + - @backstage/plugin-search-react@1.3.1 + - @backstage/plugin-sentry@0.4.6 + - @backstage/plugin-shortcuts@0.3.5 + - @backstage/plugin-stack-overflow@0.1.9 + - @backstage/plugin-tech-insights@0.3.5 + - @backstage/plugin-tech-radar@0.5.20 + - @backstage/plugin-techdocs@1.4.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.8 + - @backstage/plugin-techdocs-react@1.1.1 + - @backstage/plugin-todo@0.2.15 + - @backstage/plugin-user-settings@0.6.1 + - @internal/plugin-catalog-customized@0.0.6 + +## 0.2.78 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-graph@0.2.24 + - @backstage/plugin-pagerduty@0.5.5 + - @backstage/plugin-techdocs-react@1.1.0 + - @backstage/plugin-scaffolder@1.9.0 + - @backstage/plugin-cost-insights@0.12.1 + - @backstage/plugin-kubernetes@0.7.5 + - @backstage/core-plugin-api@1.2.0 + - @backstage/plugin-search-react@1.3.0 + - @backstage/core-components@0.12.1 + - @backstage/plugin-explore@0.3.43 + - @backstage/cli@0.22.0 + - @backstage/core-app-api@1.3.0 + - @backstage/plugin-cloudbuild@0.3.12 + - @backstage/plugin-gcp-projects@0.3.31 + - @backstage/plugin-github-actions@0.5.12 + - @backstage/plugin-graphiql@0.2.44 + - @backstage/plugin-techdocs@1.4.1 + - @backstage/plugin-org@0.6.2 + - @backstage/plugin-user-settings@0.6.0 + - @backstage/app-defaults@1.0.9 + - @backstage/plugin-airbrake@0.3.12 + - @backstage/plugin-api-docs@0.8.12 + - @backstage/plugin-azure-devops@0.2.3 + - @backstage/plugin-badges@0.2.36 + - @backstage/plugin-catalog-import@0.9.2 + - @backstage/plugin-catalog-react@1.2.2 + - @backstage/plugin-circleci@0.3.12 + - @backstage/plugin-code-coverage@0.2.5 + - @backstage/plugin-home@0.4.28 + - @backstage/plugin-jenkins@0.7.11 + - @backstage/plugin-kafka@0.3.12 + - @backstage/plugin-permission-react@0.4.8 + - @backstage/plugin-playlist@0.1.3 + - @backstage/plugin-rollbar@0.4.12 + - @backstage/plugin-search@1.0.5 + - @backstage/plugin-sentry@0.4.5 + - @backstage/plugin-shortcuts@0.3.4 + - @backstage/plugin-todo@0.2.14 + - @backstage/plugin-lighthouse@0.3.12 + - @backstage/plugin-stack-overflow@0.1.8 + - @backstage/integration-react@1.1.7 + - @backstage/plugin-apache-airflow@0.2.5 + - @backstage/plugin-azure-sites@0.1.1 + - @backstage/plugin-dynatrace@1.0.2 + - @backstage/plugin-gcalendar@0.3.8 + - @backstage/plugin-gocd@0.1.18 + - @backstage/plugin-newrelic@0.3.30 + - @backstage/plugin-tech-insights@0.3.4 + - @backstage/plugin-tech-radar@0.5.19 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.7 + - @backstage/plugin-search-common@1.2.0 + - @internal/plugin-catalog-customized@0.0.5 + - @backstage/plugin-newrelic-dashboard@0.2.5 + - @backstage/catalog-model@1.1.4 + - @backstage/config@1.0.5 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.9 + +## 0.2.78-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-cost-insights@0.12.1-next.4 + - @backstage/plugin-kubernetes@0.7.5-next.4 + - @backstage/cli@0.22.0-next.4 + - @backstage/core-app-api@1.3.0-next.4 + - @backstage/core-components@0.12.1-next.4 + - @backstage/plugin-scaffolder@1.9.0-next.4 + - @backstage/app-defaults@1.0.9-next.4 + - @backstage/plugin-airbrake@0.3.12-next.4 + - @backstage/plugin-api-docs@0.8.12-next.4 + - @backstage/plugin-azure-devops@0.2.3-next.4 + - @backstage/plugin-badges@0.2.36-next.4 + - @backstage/plugin-catalog-graph@0.2.24-next.4 + - @backstage/plugin-catalog-import@0.9.2-next.4 + - @backstage/plugin-catalog-react@1.2.2-next.4 + - @backstage/plugin-circleci@0.3.12-next.4 + - @backstage/plugin-cloudbuild@0.3.12-next.4 + - @backstage/plugin-code-coverage@0.2.5-next.4 + - @backstage/plugin-explore@0.3.43-next.4 + - @backstage/plugin-github-actions@0.5.12-next.4 + - @backstage/plugin-home@0.4.28-next.4 + - @backstage/plugin-jenkins@0.7.11-next.4 + - @backstage/plugin-kafka@0.3.12-next.4 + - @backstage/plugin-org@0.6.2-next.4 + - @backstage/plugin-permission-react@0.4.8-next.3 + - @backstage/plugin-playlist@0.1.3-next.4 + - @backstage/plugin-rollbar@0.4.12-next.4 + - @backstage/plugin-search@1.0.5-next.4 + - @backstage/plugin-search-react@1.3.0-next.4 + - @backstage/plugin-sentry@0.4.5-next.4 + - @backstage/plugin-shortcuts@0.3.4-next.4 + - @backstage/plugin-techdocs@1.4.1-next.4 + - @backstage/plugin-todo@0.2.14-next.4 + - @backstage/plugin-user-settings@0.6.0-next.4 + - @backstage/plugin-stack-overflow@0.1.8-next.4 + - @backstage/catalog-model@1.1.4-next.1 + - @backstage/config@1.0.5-next.1 + - @backstage/core-plugin-api@1.2.0-next.2 + - @backstage/integration-react@1.1.7-next.4 + - @backstage/theme@0.2.16 + - @backstage/plugin-apache-airflow@0.2.5-next.4 + - @backstage/plugin-azure-sites@0.1.1-next.4 + - @backstage/plugin-catalog-common@1.0.9-next.3 + - @backstage/plugin-dynatrace@1.0.2-next.4 + - @backstage/plugin-gcalendar@0.3.8-next.4 + - @backstage/plugin-gcp-projects@0.3.31-next.4 + - @backstage/plugin-gocd@0.1.18-next.4 + - @backstage/plugin-graphiql@0.2.44-next.4 + - @backstage/plugin-lighthouse@0.3.12-next.4 + - @backstage/plugin-newrelic@0.3.30-next.4 + - @backstage/plugin-newrelic-dashboard@0.2.5-next.4 + - @backstage/plugin-pagerduty@0.5.5-next.4 + - @backstage/plugin-search-common@1.2.0-next.3 + - @backstage/plugin-tech-insights@0.3.4-next.4 + - @backstage/plugin-tech-radar@0.5.19-next.4 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.7-next.4 + - @backstage/plugin-techdocs-react@1.0.7-next.4 + - @internal/plugin-catalog-customized@0.0.5-next.4 + +## 0.2.78-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.21.2-next.3 + - @backstage/core-app-api@1.2.1-next.3 + - @backstage/plugin-airbrake@0.3.12-next.3 + - @backstage/core-components@0.12.1-next.3 + - @backstage/app-defaults@1.0.9-next.3 + - @backstage/catalog-model@1.1.4-next.1 + - @backstage/config@1.0.5-next.1 + - @backstage/core-plugin-api@1.2.0-next.2 + - @backstage/integration-react@1.1.7-next.3 + - @backstage/theme@0.2.16 + - @backstage/plugin-apache-airflow@0.2.5-next.3 + - @backstage/plugin-api-docs@0.8.12-next.3 + - @backstage/plugin-azure-devops@0.2.3-next.3 + - @backstage/plugin-azure-sites@0.1.1-next.3 + - @backstage/plugin-badges@0.2.36-next.3 + - @backstage/plugin-catalog-common@1.0.9-next.2 + - @backstage/plugin-catalog-graph@0.2.24-next.3 + - @backstage/plugin-catalog-import@0.9.2-next.3 + - @backstage/plugin-catalog-react@1.2.2-next.3 + - @backstage/plugin-circleci@0.3.12-next.3 + - @backstage/plugin-cloudbuild@0.3.12-next.3 + - @backstage/plugin-code-coverage@0.2.5-next.3 + - @backstage/plugin-cost-insights@0.12.1-next.3 + - @backstage/plugin-dynatrace@1.0.2-next.3 + - @backstage/plugin-explore@0.3.43-next.3 + - @backstage/plugin-gcalendar@0.3.8-next.3 + - @backstage/plugin-gcp-projects@0.3.31-next.3 + - @backstage/plugin-github-actions@0.5.12-next.3 + - @backstage/plugin-gocd@0.1.18-next.3 + - @backstage/plugin-graphiql@0.2.44-next.3 + - @backstage/plugin-home@0.4.28-next.3 + - @backstage/plugin-jenkins@0.7.11-next.3 + - @backstage/plugin-kafka@0.3.12-next.3 + - @backstage/plugin-kubernetes@0.7.5-next.3 + - @backstage/plugin-lighthouse@0.3.12-next.3 + - @backstage/plugin-newrelic@0.3.30-next.3 + - @backstage/plugin-newrelic-dashboard@0.2.5-next.3 + - @backstage/plugin-org@0.6.2-next.3 + - @backstage/plugin-pagerduty@0.5.5-next.3 + - @backstage/plugin-permission-react@0.4.8-next.2 + - @backstage/plugin-playlist@0.1.3-next.3 + - @backstage/plugin-rollbar@0.4.12-next.3 + - @backstage/plugin-scaffolder@1.9.0-next.3 + - @backstage/plugin-search@1.0.5-next.3 + - @backstage/plugin-search-common@1.2.0-next.2 + - @backstage/plugin-search-react@1.3.0-next.3 + - @backstage/plugin-sentry@0.4.5-next.3 + - @backstage/plugin-shortcuts@0.3.4-next.3 + - @backstage/plugin-stack-overflow@0.1.8-next.3 + - @backstage/plugin-tech-insights@0.3.4-next.3 + - @backstage/plugin-tech-radar@0.5.19-next.3 + - @backstage/plugin-techdocs@1.4.1-next.3 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.7-next.3 + - @backstage/plugin-techdocs-react@1.0.7-next.3 + - @backstage/plugin-todo@0.2.14-next.3 + - @backstage/plugin-user-settings@0.6.0-next.3 + - @internal/plugin-catalog-customized@0.0.5-next.3 + +## 0.2.78-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.2.0-next.2 + - @backstage/plugin-search-react@1.3.0-next.2 + - @backstage/plugin-explore@0.3.43-next.2 + - @backstage/cli@0.21.2-next.2 + - @backstage/core-app-api@1.2.1-next.2 + - @backstage/core-components@0.12.1-next.2 + - @backstage/plugin-catalog-react@1.2.2-next.2 + - @backstage/plugin-cost-insights@0.12.1-next.2 + - @backstage/plugin-gcp-projects@0.3.31-next.2 + - @backstage/plugin-scaffolder@1.9.0-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.7-next.2 + - @backstage/plugin-catalog-graph@0.2.24-next.2 + - @backstage/plugin-search-common@1.2.0-next.2 + - @backstage/plugin-api-docs@0.8.12-next.2 + - @internal/plugin-catalog-customized@0.0.5-next.2 + - @backstage/app-defaults@1.0.9-next.2 + - @backstage/integration-react@1.1.7-next.2 + - @backstage/plugin-airbrake@0.3.12-next.2 + - @backstage/plugin-apache-airflow@0.2.5-next.2 + - @backstage/plugin-azure-devops@0.2.3-next.2 + - @backstage/plugin-azure-sites@0.1.1-next.2 + - @backstage/plugin-badges@0.2.36-next.2 + - @backstage/plugin-catalog-import@0.9.2-next.2 + - @backstage/plugin-circleci@0.3.12-next.2 + - @backstage/plugin-cloudbuild@0.3.12-next.2 + - @backstage/plugin-code-coverage@0.2.5-next.2 + - @backstage/plugin-dynatrace@1.0.2-next.2 + - @backstage/plugin-gcalendar@0.3.8-next.2 + - @backstage/plugin-github-actions@0.5.12-next.2 + - @backstage/plugin-gocd@0.1.18-next.2 + - @backstage/plugin-graphiql@0.2.44-next.2 + - @backstage/plugin-home@0.4.28-next.2 + - @backstage/plugin-jenkins@0.7.11-next.2 + - @backstage/plugin-kafka@0.3.12-next.2 + - @backstage/plugin-kubernetes@0.7.5-next.2 + - @backstage/plugin-lighthouse@0.3.12-next.2 + - @backstage/plugin-newrelic@0.3.30-next.2 + - @backstage/plugin-newrelic-dashboard@0.2.5-next.2 + - @backstage/plugin-org@0.6.2-next.2 + - @backstage/plugin-pagerduty@0.5.5-next.2 + - @backstage/plugin-permission-react@0.4.8-next.2 + - @backstage/plugin-playlist@0.1.3-next.2 + - @backstage/plugin-rollbar@0.4.12-next.2 + - @backstage/plugin-search@1.0.5-next.2 + - @backstage/plugin-sentry@0.4.5-next.2 + - @backstage/plugin-shortcuts@0.3.4-next.2 + - @backstage/plugin-stack-overflow@0.1.8-next.2 + - @backstage/plugin-tech-insights@0.3.4-next.2 + - @backstage/plugin-tech-radar@0.5.19-next.2 + - @backstage/plugin-techdocs@1.4.1-next.2 + - @backstage/plugin-techdocs-react@1.0.7-next.2 + - @backstage/plugin-todo@0.2.14-next.2 + - @backstage/plugin-user-settings@0.6.0-next.2 + - @backstage/catalog-model@1.1.4-next.1 + - @backstage/config@1.0.5-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.9-next.2 + +## 0.2.78-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.2.1-next.1 + - @backstage/core-components@0.12.1-next.1 + - @backstage/plugin-cloudbuild@0.3.12-next.1 + - @backstage/plugin-gcp-projects@0.3.31-next.1 + - @backstage/plugin-github-actions@0.5.12-next.1 + - @backstage/plugin-graphiql@0.2.44-next.1 + - @backstage/plugin-techdocs@1.4.1-next.1 + - @backstage/plugin-cost-insights@0.12.1-next.1 + - @backstage/plugin-search-react@1.2.2-next.1 + - @backstage/plugin-stack-overflow@0.1.8-next.1 + - @backstage/cli@0.21.2-next.1 + - @backstage/plugin-org@0.6.2-next.1 + - @backstage/core-plugin-api@1.1.1-next.1 + - @backstage/plugin-catalog-react@1.2.2-next.1 + - @backstage/plugin-scaffolder@1.9.0-next.1 + - @backstage/plugin-shortcuts@0.3.4-next.1 + - @backstage/plugin-user-settings@0.6.0-next.1 + - @backstage/plugin-explore@0.3.43-next.1 + - @backstage/app-defaults@1.0.9-next.1 + - @backstage/plugin-apache-airflow@0.2.5-next.1 + - @backstage/plugin-api-docs@0.8.12-next.1 + - @backstage/plugin-azure-devops@0.2.3-next.1 + - @backstage/plugin-azure-sites@0.1.1-next.1 + - @backstage/plugin-badges@0.2.36-next.1 + - @backstage/plugin-catalog-graph@0.2.24-next.1 + - @backstage/plugin-catalog-import@0.9.2-next.1 + - @backstage/plugin-circleci@0.3.12-next.1 + - @backstage/plugin-code-coverage@0.2.5-next.1 + - @backstage/plugin-dynatrace@1.0.2-next.1 + - @backstage/plugin-gcalendar@0.3.8-next.1 + - @backstage/plugin-gocd@0.1.18-next.1 + - @backstage/plugin-home@0.4.28-next.1 + - @backstage/plugin-jenkins@0.7.11-next.1 + - @backstage/plugin-kafka@0.3.12-next.1 + - @backstage/plugin-kubernetes@0.7.5-next.1 + - @backstage/plugin-lighthouse@0.3.12-next.1 + - @backstage/plugin-newrelic@0.3.30-next.1 + - @backstage/plugin-pagerduty@0.5.5-next.1 + - @backstage/plugin-playlist@0.1.3-next.1 + - @backstage/plugin-rollbar@0.4.12-next.1 + - @backstage/plugin-search@1.0.5-next.1 + - @backstage/plugin-sentry@0.4.5-next.1 + - @backstage/plugin-tech-insights@0.3.4-next.1 + - @backstage/plugin-tech-radar@0.5.19-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.7-next.1 + - @backstage/plugin-todo@0.2.14-next.1 + - @backstage/integration-react@1.1.7-next.1 + - @backstage/plugin-airbrake@0.3.12-next.1 + - @backstage/plugin-newrelic-dashboard@0.2.5-next.1 + - @backstage/plugin-techdocs-react@1.0.7-next.1 + - @internal/plugin-catalog-customized@0.0.5-next.1 + - @backstage/config@1.0.5-next.1 + - @backstage/plugin-permission-react@0.4.8-next.1 + - @backstage/catalog-model@1.1.4-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.9-next.1 + - @backstage/plugin-search-common@1.1.2-next.1 + +## 0.2.78-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-graph@0.2.24-next.0 + - @backstage/plugin-pagerduty@0.5.5-next.0 + - @backstage/plugin-techdocs-react@1.0.7-next.0 + - @backstage/plugin-scaffolder@1.9.0-next.0 + - @backstage/cli@0.21.2-next.0 + - @backstage/plugin-cost-insights@0.12.1-next.0 + - @backstage/core-components@0.12.1-next.0 + - @backstage/plugin-user-settings@0.6.0-next.0 + - @backstage/plugin-explore@0.3.43-next.0 + - @backstage/core-app-api@1.2.1-next.0 + - @backstage/core-plugin-api@1.1.1-next.0 + - @backstage/integration-react@1.1.7-next.0 + - @backstage/plugin-airbrake@0.3.12-next.0 + - @backstage/plugin-apache-airflow@0.2.5-next.0 + - @backstage/plugin-api-docs@0.8.12-next.0 + - @backstage/plugin-azure-devops@0.2.3-next.0 + - @backstage/plugin-azure-sites@0.1.1-next.0 + - @backstage/plugin-badges@0.2.36-next.0 + - @backstage/plugin-catalog-import@0.9.2-next.0 + - @backstage/plugin-circleci@0.3.12-next.0 + - @backstage/plugin-cloudbuild@0.3.12-next.0 + - @backstage/plugin-code-coverage@0.2.5-next.0 + - @backstage/plugin-dynatrace@1.0.2-next.0 + - @backstage/plugin-gcalendar@0.3.8-next.0 + - @backstage/plugin-gcp-projects@0.3.31-next.0 + - @backstage/plugin-github-actions@0.5.12-next.0 + - @backstage/plugin-gocd@0.1.18-next.0 + - @backstage/plugin-graphiql@0.2.44-next.0 + - @backstage/plugin-home@0.4.28-next.0 + - @backstage/plugin-jenkins@0.7.11-next.0 + - @backstage/plugin-kafka@0.3.12-next.0 + - @backstage/plugin-kubernetes@0.7.5-next.0 + - @backstage/plugin-lighthouse@0.3.12-next.0 + - @backstage/plugin-newrelic@0.3.30-next.0 + - @backstage/plugin-org@0.6.1-next.0 + - @backstage/plugin-playlist@0.1.3-next.0 + - @backstage/plugin-rollbar@0.4.12-next.0 + - @backstage/plugin-search@1.0.5-next.0 + - @backstage/plugin-sentry@0.4.5-next.0 + - @backstage/plugin-shortcuts@0.3.4-next.0 + - @backstage/plugin-stack-overflow@0.1.8-next.0 + - @backstage/plugin-tech-insights@0.3.4-next.0 + - @backstage/plugin-tech-radar@0.5.19-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.7-next.0 + - @backstage/plugin-techdocs@1.4.1-next.0 + - @backstage/plugin-todo@0.2.14-next.0 + - @backstage/plugin-catalog-react@1.2.2-next.0 + - @backstage/app-defaults@1.0.9-next.0 + - @backstage/catalog-model@1.1.4-next.0 + - @backstage/config@1.0.5-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.9-next.0 + - @backstage/plugin-newrelic-dashboard@0.2.5-next.0 + - @backstage/plugin-permission-react@0.4.8-next.0 + - @backstage/plugin-search-common@1.1.2-next.0 + - @backstage/plugin-search-react@1.2.2-next.0 + - @internal/plugin-catalog-customized@0.0.5-next.0 + +## 0.2.77 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.8.0 + - @backstage/plugin-catalog-react@1.2.1 + - @backstage/plugin-techdocs@1.4.0 + - @backstage/core-components@0.12.0 + - @backstage/plugin-cost-insights@0.12.0 + - @backstage/plugin-apache-airflow@0.2.4 + - @backstage/core-app-api@1.2.0 + - @backstage/plugin-explore@0.3.42 + - @backstage/plugin-tech-insights@0.3.3 + - @backstage/plugin-github-actions@0.5.11 + - @backstage/cli@0.21.0 + - @backstage/core-plugin-api@1.1.0 + - @backstage/plugin-dynatrace@1.0.1 + - @backstage/plugin-catalog-import@0.9.1 + - @backstage/plugin-catalog-graph@0.2.23 + - @backstage/plugin-tech-radar@0.5.18 + - @backstage/plugin-playlist@0.1.2 + - @backstage/plugin-stack-overflow@0.1.7 + - @backstage/catalog-model@1.1.3 + - @backstage/plugin-gcalendar@0.3.7 + - @backstage/plugin-code-coverage@0.2.4 + - @backstage/plugin-org@0.6.0 + - @backstage/plugin-api-docs@0.8.11 + - @backstage/plugin-kubernetes@0.7.4 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.6 + - @backstage/plugin-circleci@0.3.11 + - @backstage/plugin-azure-sites@0.1.0 + - @backstage/plugin-techdocs-react@1.0.6 + - @backstage/plugin-cloudbuild@0.3.11 + - @backstage/plugin-jenkins@0.7.10 + - @backstage/plugin-kafka@0.3.11 + - @backstage/plugin-pagerduty@0.5.4 + - @backstage/plugin-rollbar@0.4.11 + - @backstage/plugin-airbrake@0.3.11 + - @backstage/plugin-azure-devops@0.2.2 + - @backstage/plugin-badges@0.2.35 + - @internal/plugin-catalog-customized@0.0.4 + - @backstage/plugin-gocd@0.1.17 + - @backstage/plugin-home@0.4.27 + - @backstage/plugin-lighthouse@0.3.11 + - @backstage/plugin-newrelic-dashboard@0.2.4 + - @backstage/plugin-search@1.0.4 + - @backstage/plugin-sentry@0.4.4 + - @backstage/plugin-todo@0.2.13 + - @backstage/app-defaults@1.0.8 + - @backstage/integration-react@1.1.6 + - @backstage/plugin-gcp-projects@0.3.30 + - @backstage/plugin-graphiql@0.2.43 + - @backstage/plugin-newrelic@0.3.29 + - @backstage/plugin-search-react@1.2.1 + - @backstage/plugin-shortcuts@0.3.3 + - @backstage/plugin-user-settings@0.5.1 + - @backstage/config@1.0.4 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.8 + - @backstage/plugin-permission-react@0.4.7 + - @backstage/plugin-search-common@1.1.1 + +## 0.2.77-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.8.0-next.1 + - @backstage/plugin-cost-insights@0.12.0-next.1 + - @backstage/plugin-techdocs@1.4.0-next.2 + - @backstage/plugin-explore@0.3.42-next.1 + - @backstage/plugin-github-actions@0.5.11-next.1 + - @backstage/cli@0.21.0-next.1 + - @backstage/plugin-catalog-graph@0.2.23-next.1 + - @backstage/core-components@0.12.0-next.1 + - @backstage/plugin-code-coverage@0.2.4-next.1 + - @backstage/plugin-org@0.6.0-next.1 + - @backstage/plugin-kubernetes@0.7.4-next.1 + - @backstage/app-defaults@1.0.8-next.1 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/core-app-api@1.2.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration-react@1.1.6-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-airbrake@0.3.11-next.1 + - @backstage/plugin-apache-airflow@0.2.4-next.1 + - @backstage/plugin-api-docs@0.8.11-next.1 + - @backstage/plugin-azure-devops@0.2.2-next.1 + - @backstage/plugin-azure-sites@0.1.0-next.1 + - @backstage/plugin-badges@0.2.35-next.1 + - @backstage/plugin-catalog-common@1.0.8-next.0 + - @backstage/plugin-catalog-import@0.9.1-next.1 + - @backstage/plugin-catalog-react@1.2.1-next.1 + - @backstage/plugin-circleci@0.3.11-next.1 + - @backstage/plugin-cloudbuild@0.3.11-next.1 + - @backstage/plugin-dynatrace@1.0.1-next.1 + - @backstage/plugin-gcalendar@0.3.7-next.1 + - @backstage/plugin-gcp-projects@0.3.30-next.1 + - @backstage/plugin-gocd@0.1.17-next.1 + - @backstage/plugin-graphiql@0.2.43-next.1 + - @backstage/plugin-home@0.4.27-next.1 + - @backstage/plugin-jenkins@0.7.10-next.1 + - @backstage/plugin-kafka@0.3.11-next.1 + - @backstage/plugin-lighthouse@0.3.11-next.1 + - @backstage/plugin-newrelic@0.3.29-next.1 + - @backstage/plugin-newrelic-dashboard@0.2.4-next.1 + - @backstage/plugin-pagerduty@0.5.4-next.1 + - @backstage/plugin-permission-react@0.4.7-next.0 + - @backstage/plugin-playlist@0.1.2-next.2 + - @backstage/plugin-rollbar@0.4.11-next.1 + - @backstage/plugin-search@1.0.4-next.1 + - @backstage/plugin-search-common@1.1.1-next.0 + - @backstage/plugin-search-react@1.2.1-next.1 + - @backstage/plugin-sentry@0.4.4-next.1 + - @backstage/plugin-shortcuts@0.3.3-next.1 + - @backstage/plugin-stack-overflow@0.1.7-next.1 + - @backstage/plugin-tech-insights@0.3.3-next.1 + - @backstage/plugin-tech-radar@0.5.18-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.6-next.1 + - @backstage/plugin-techdocs-react@1.0.6-next.1 + - @backstage/plugin-todo@0.2.13-next.1 + - @backstage/plugin-user-settings@0.5.1-next.1 + - @internal/plugin-catalog-customized@0.0.4-next.1 + +## 0.2.77-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.4.0-next.1 + - @backstage/plugin-playlist@0.1.2-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.6-next.0 + +## 0.2.77-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.8.0-next.0 + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-app-api@1.2.0-next.0 + - @backstage/plugin-tech-insights@0.3.2-next.0 + - @backstage/plugin-techdocs@1.4.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/plugin-cost-insights@0.12.0-next.0 + - @backstage/plugin-dynatrace@1.0.1-next.0 + - @backstage/cli@0.21.0-next.0 + - @backstage/plugin-catalog-import@0.9.1-next.0 + - @backstage/plugin-tech-radar@0.5.18-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-gcalendar@0.3.7-next.0 + - @backstage/plugin-code-coverage@0.2.4-next.0 + - @backstage/plugin-github-actions@0.5.11-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.6-next.0 + - @backstage/plugin-circleci@0.3.11-next.0 + - @backstage/plugin-azure-sites@0.1.0-next.0 + - @backstage/plugin-cloudbuild@0.3.11-next.0 + - @backstage/plugin-jenkins@0.7.10-next.0 + - @backstage/plugin-kafka@0.3.11-next.0 + - @backstage/plugin-pagerduty@0.5.4-next.0 + - @backstage/plugin-rollbar@0.4.11-next.0 + - @backstage/plugin-airbrake@0.3.11-next.0 + - @backstage/plugin-api-docs@0.8.11-next.0 + - @backstage/plugin-azure-devops@0.2.2-next.0 + - @backstage/plugin-badges@0.2.35-next.0 + - @internal/plugin-catalog-customized@0.0.4-next.0 + - @backstage/plugin-catalog-graph@0.2.23-next.0 + - @backstage/plugin-explore@0.3.42-next.0 + - @backstage/plugin-gocd@0.1.17-next.0 + - @backstage/plugin-home@0.4.27-next.0 + - @backstage/plugin-kubernetes@0.7.4-next.0 + - @backstage/plugin-lighthouse@0.3.11-next.0 + - @backstage/plugin-newrelic-dashboard@0.2.4-next.0 + - @backstage/plugin-org@0.5.11-next.0 + - @backstage/plugin-playlist@0.1.2-next.0 + - @backstage/plugin-search@1.0.4-next.0 + - @backstage/plugin-sentry@0.4.4-next.0 + - @backstage/plugin-todo@0.2.13-next.0 + - @backstage/app-defaults@1.0.8-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-apache-airflow@0.2.4-next.0 + - @backstage/plugin-gcp-projects@0.3.30-next.0 + - @backstage/plugin-graphiql@0.2.43-next.0 + - @backstage/plugin-newrelic@0.3.29-next.0 + - @backstage/plugin-search-react@1.2.1-next.0 + - @backstage/plugin-shortcuts@0.3.3-next.0 + - @backstage/plugin-stack-overflow@0.1.7-next.0 + - @backstage/plugin-techdocs-react@1.0.6-next.0 + - @backstage/plugin-user-settings@0.5.1-next.0 + - @backstage/plugin-permission-react@0.4.7-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.8-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## 0.2.76 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-user-settings@0.5.0 + - @backstage/plugin-scaffolder@1.7.0 + - @backstage/plugin-org@0.5.10 + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-catalog-graph@0.2.22 + - @backstage/core-app-api@1.1.1 + - @backstage/cli@0.20.0 + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/plugin-catalog-import@0.9.0 + - @backstage/plugin-tech-insights@0.3.1 + - @backstage/plugin-kubernetes@0.7.3 + - @backstage/plugin-gcalendar@0.3.6 + - @backstage/plugin-api-docs@0.8.10 + - @backstage/plugin-search-react@1.2.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-cost-insights@0.11.32 + - @backstage/plugin-airbrake@0.3.10 + - @backstage/plugin-azure-devops@0.2.1 + - @backstage/plugin-badges@0.2.34 + - @backstage/plugin-circleci@0.3.10 + - @backstage/plugin-cloudbuild@0.3.10 + - @backstage/plugin-code-coverage@0.2.3 + - @backstage/plugin-dynatrace@1.0.0 + - @backstage/plugin-explore@0.3.41 + - @backstage/plugin-github-actions@0.5.10 + - @backstage/plugin-gocd@0.1.16 + - @backstage/plugin-home@0.4.26 + - @backstage/plugin-jenkins@0.7.9 + - @backstage/plugin-kafka@0.3.10 + - @backstage/plugin-lighthouse@0.3.10 + - @backstage/plugin-newrelic-dashboard@0.2.3 + - @backstage/plugin-pagerduty@0.5.3 + - @backstage/plugin-playlist@0.1.1 + - @backstage/plugin-rollbar@0.4.10 + - @backstage/plugin-search@1.0.3 + - @backstage/plugin-sentry@0.4.3 + - @backstage/plugin-techdocs@1.3.3 + - @backstage/plugin-techdocs-react@1.0.5 + - @backstage/plugin-todo@0.2.12 + - @internal/plugin-catalog-customized@0.0.3 + - @backstage/app-defaults@1.0.7 + - @backstage/integration-react@1.1.5 + - @backstage/plugin-apache-airflow@0.2.3 + - @backstage/plugin-gcp-projects@0.3.29 + - @backstage/plugin-graphiql@0.2.42 + - @backstage/plugin-newrelic@0.3.28 + - @backstage/plugin-shortcuts@0.3.2 + - @backstage/plugin-stack-overflow@0.1.6 + - @backstage/plugin-tech-radar@0.5.17 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + - @backstage/plugin-permission-react@0.4.6 + +## 0.2.76-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.7.0-next.2 + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-catalog-import@0.9.0-next.2 + - @backstage/cli@0.20.0-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-playlist@0.1.1-next.2 + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/app-defaults@1.0.7-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-airbrake@0.3.10-next.2 + - @backstage/plugin-apache-airflow@0.2.3-next.2 + - @backstage/plugin-api-docs@0.8.10-next.2 + - @backstage/plugin-azure-devops@0.2.1-next.2 + - @backstage/plugin-badges@0.2.34-next.2 + - @backstage/plugin-catalog-graph@0.2.22-next.2 + - @backstage/plugin-circleci@0.3.10-next.2 + - @backstage/plugin-cloudbuild@0.3.10-next.2 + - @backstage/plugin-code-coverage@0.2.3-next.2 + - @backstage/plugin-cost-insights@0.11.32-next.2 + - @backstage/plugin-dynatrace@1.0.0-next.2 + - @backstage/plugin-explore@0.3.41-next.2 + - @backstage/plugin-gcalendar@0.3.6-next.2 + - @backstage/plugin-gcp-projects@0.3.29-next.2 + - @backstage/plugin-github-actions@0.5.10-next.2 + - @backstage/plugin-gocd@0.1.16-next.2 + - @backstage/plugin-graphiql@0.2.42-next.2 + - @backstage/plugin-home@0.4.26-next.2 + - @backstage/plugin-jenkins@0.7.9-next.2 + - @backstage/plugin-kafka@0.3.10-next.2 + - @backstage/plugin-kubernetes@0.7.3-next.2 + - @backstage/plugin-lighthouse@0.3.10-next.2 + - @backstage/plugin-newrelic@0.3.28-next.2 + - @backstage/plugin-newrelic-dashboard@0.2.3-next.2 + - @backstage/plugin-org@0.5.10-next.2 + - @backstage/plugin-pagerduty@0.5.3-next.2 + - @backstage/plugin-rollbar@0.4.10-next.2 + - @backstage/plugin-search@1.0.3-next.2 + - @backstage/plugin-search-react@1.2.0-next.2 + - @backstage/plugin-sentry@0.4.3-next.2 + - @backstage/plugin-shortcuts@0.3.2-next.2 + - @backstage/plugin-stack-overflow@0.1.6-next.2 + - @backstage/plugin-tech-insights@0.3.1-next.2 + - @backstage/plugin-tech-radar@0.5.17-next.2 + - @backstage/plugin-techdocs@1.3.3-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.5-next.2 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + - @backstage/plugin-todo@0.2.12-next.2 + - @backstage/plugin-user-settings@0.5.0-next.2 + - @internal/plugin-catalog-customized@0.0.3-next.2 + +## 0.2.76-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-user-settings@0.5.0-next.1 + - @backstage/plugin-scaffolder@1.7.0-next.1 + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-app-api@1.1.1-next.1 + - @backstage/cli@0.20.0-next.1 + - @backstage/plugin-tech-insights@0.3.1-next.1 + - @backstage/plugin-gcalendar@0.3.6-next.1 + - @backstage/plugin-api-docs@0.8.10-next.1 + - @backstage/plugin-search-react@1.2.0-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/plugin-airbrake@0.3.10-next.1 + - @backstage/plugin-azure-devops@0.2.1-next.1 + - @backstage/plugin-badges@0.2.34-next.1 + - @internal/plugin-catalog-customized@0.0.3-next.1 + - @backstage/plugin-catalog-graph@0.2.22-next.1 + - @backstage/plugin-catalog-import@0.8.13-next.1 + - @backstage/plugin-circleci@0.3.10-next.1 + - @backstage/plugin-cloudbuild@0.3.10-next.1 + - @backstage/plugin-code-coverage@0.2.3-next.1 + - @backstage/plugin-dynatrace@1.0.0-next.1 + - @backstage/plugin-explore@0.3.41-next.1 + - @backstage/plugin-github-actions@0.5.10-next.1 + - @backstage/plugin-gocd@0.1.16-next.1 + - @backstage/plugin-home@0.4.26-next.1 + - @backstage/plugin-jenkins@0.7.9-next.1 + - @backstage/plugin-kafka@0.3.10-next.1 + - @backstage/plugin-kubernetes@0.7.3-next.1 + - @backstage/plugin-lighthouse@0.3.10-next.1 + - @backstage/plugin-newrelic-dashboard@0.2.3-next.1 + - @backstage/plugin-org@0.5.10-next.1 + - @backstage/plugin-pagerduty@0.5.3-next.1 + - @backstage/plugin-playlist@0.1.1-next.1 + - @backstage/plugin-rollbar@0.4.10-next.1 + - @backstage/plugin-search@1.0.3-next.1 + - @backstage/plugin-sentry@0.4.3-next.1 + - @backstage/plugin-techdocs@1.3.3-next.1 + - @backstage/plugin-todo@0.2.12-next.1 + - @backstage/app-defaults@1.0.7-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/plugin-apache-airflow@0.2.3-next.1 + - @backstage/plugin-cost-insights@0.11.32-next.1 + - @backstage/plugin-gcp-projects@0.3.29-next.1 + - @backstage/plugin-graphiql@0.2.42-next.1 + - @backstage/plugin-newrelic@0.3.28-next.1 + - @backstage/plugin-shortcuts@0.3.2-next.1 + - @backstage/plugin-stack-overflow@0.1.6-next.1 + - @backstage/plugin-tech-radar@0.5.17-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.5-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/integration-react@1.1.5-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.7-next.1 + - @backstage/plugin-permission-react@0.4.6-next.1 + - @backstage/plugin-techdocs-react@1.0.5-next.1 + +## 0.2.76-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-org@0.5.10-next.0 + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/cli@0.20.0-next.0 + - @backstage/plugin-kubernetes@0.7.3-next.0 + - @backstage/plugin-scaffolder@1.7.0-next.0 + - @backstage/plugin-api-docs@0.8.10-next.0 + - @backstage/plugin-user-settings@0.5.0-next.0 + - @backstage/plugin-cost-insights@0.11.32-next.0 + - @backstage/plugin-airbrake@0.3.10-next.0 + - @backstage/plugin-azure-devops@0.2.1-next.0 + - @backstage/plugin-badges@0.2.34-next.0 + - @backstage/plugin-catalog-graph@0.2.22-next.0 + - @backstage/plugin-catalog-import@0.8.13-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/plugin-circleci@0.3.10-next.0 + - @backstage/plugin-cloudbuild@0.3.10-next.0 + - @backstage/plugin-code-coverage@0.2.3-next.0 + - @backstage/plugin-dynatrace@0.2.1-next.0 + - @backstage/plugin-explore@0.3.41-next.0 + - @backstage/plugin-github-actions@0.5.10-next.0 + - @backstage/plugin-gocd@0.1.16-next.0 + - @backstage/plugin-home@0.4.26-next.0 + - @backstage/plugin-jenkins@0.7.9-next.0 + - @backstage/plugin-kafka@0.3.10-next.0 + - @backstage/plugin-lighthouse@0.3.10-next.0 + - @backstage/plugin-newrelic-dashboard@0.2.3-next.0 + - @backstage/plugin-pagerduty@0.5.3-next.0 + - @backstage/plugin-playlist@0.1.1-next.0 + - @backstage/plugin-rollbar@0.4.10-next.0 + - @backstage/plugin-search@1.0.3-next.0 + - @backstage/plugin-sentry@0.4.3-next.0 + - @backstage/plugin-tech-insights@0.3.1-next.0 + - @backstage/plugin-techdocs@1.3.3-next.0 + - @backstage/plugin-techdocs-react@1.0.5-next.0 + - @backstage/plugin-todo@0.2.12-next.0 + - @backstage/app-defaults@1.0.7-next.0 + - @backstage/integration-react@1.1.5-next.0 + - @backstage/plugin-apache-airflow@0.2.3-next.0 + - @backstage/plugin-gcalendar@0.3.6-next.0 + - @backstage/plugin-gcp-projects@0.3.29-next.0 + - @backstage/plugin-graphiql@0.2.42-next.0 + - @backstage/plugin-newrelic@0.3.28-next.0 + - @backstage/plugin-search-react@1.1.1-next.0 + - @backstage/plugin-shortcuts@0.3.2-next.0 + - @backstage/plugin-stack-overflow@0.1.6-next.0 + - @backstage/plugin-tech-radar@0.5.17-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.5-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-app-api@1.1.1-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.7-next.0 + - @backstage/plugin-permission-react@0.4.6-next.0 + - @backstage/plugin-search-common@1.0.2-next.0 + - @internal/plugin-catalog-customized@0.0.3-next.0 + +## 0.2.75 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.19.0 + - @backstage/app-defaults@1.0.6 + - @backstage/core-app-api@1.1.0 + - @backstage/core-components@0.11.1 + - @backstage/core-plugin-api@1.0.6 + - @backstage/plugin-airbrake@0.3.9 + - @backstage/plugin-api-docs@0.8.9 + - @backstage/plugin-azure-devops@0.2.0 + - @backstage/plugin-badges@0.2.33 + - @backstage/plugin-catalog-graph@0.2.21 + - @backstage/plugin-catalog-import@0.8.12 + - @backstage/plugin-catalog-react@1.1.4 + - @backstage/plugin-circleci@0.3.9 + - @backstage/plugin-cloudbuild@0.3.9 + - @backstage/plugin-code-coverage@0.2.2 + - @backstage/plugin-cost-insights@0.11.31 + - @backstage/plugin-explore@0.3.40 + - @backstage/plugin-gcp-projects@0.3.28 + - @backstage/plugin-github-actions@0.5.9 + - @backstage/plugin-graphiql@0.2.41 + - @backstage/plugin-home@0.4.25 + - @backstage/plugin-jenkins@0.7.8 + - @backstage/plugin-kafka@0.3.9 + - @backstage/plugin-kubernetes@0.7.2 + - @backstage/plugin-lighthouse@0.3.9 + - @backstage/plugin-org@0.5.9 + - @backstage/plugin-pagerduty@0.5.2 + - @backstage/plugin-permission-react@0.4.5 + - @backstage/plugin-rollbar@0.4.9 + - @backstage/plugin-scaffolder@1.6.0 + - @backstage/plugin-search-react@1.1.0 + - @backstage/plugin-search@1.0.2 + - @backstage/plugin-sentry@0.4.2 + - @backstage/plugin-shortcuts@0.3.1 + - @backstage/plugin-tech-insights@0.3.0 + - @backstage/plugin-techdocs-react@1.0.4 + - @backstage/plugin-techdocs@1.3.2 + - @backstage/plugin-todo@0.2.11 + - @backstage/plugin-user-settings@0.4.8 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.4 + - @backstage/plugin-newrelic-dashboard@0.2.2 + - @backstage/plugin-dynatrace@0.2.0 + - @backstage/catalog-model@1.1.1 + - @backstage/config@1.0.2 + - @backstage/integration-react@1.1.4 + - @backstage/plugin-apache-airflow@0.2.2 + - @backstage/plugin-gcalendar@0.3.5 + - @backstage/plugin-gocd@0.1.15 + - @backstage/plugin-newrelic@0.3.27 + - @backstage/plugin-stack-overflow@0.1.5 + - @backstage/plugin-tech-radar@0.5.16 + - @backstage/plugin-playlist@0.1.0 + - @backstage/plugin-catalog-common@1.0.6 + - @internal/plugin-catalog-customized@0.0.2 + - @backstage/plugin-search-common@1.0.1 + +## 0.2.75-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.4-next.2 + - @backstage/app-defaults@1.0.6-next.2 + - @backstage/catalog-model@1.1.1-next.0 + - @backstage/config@1.0.2-next.0 + - @backstage/core-app-api@1.1.0-next.3 + - @backstage/core-components@0.11.1-next.3 + - @backstage/core-plugin-api@1.0.6-next.3 + - @backstage/integration-react@1.1.4-next.2 + - @backstage/plugin-airbrake@0.3.9-next.3 + - @backstage/plugin-apache-airflow@0.2.2-next.3 + - @backstage/plugin-api-docs@0.8.9-next.3 + - @backstage/plugin-azure-devops@0.2.0-next.3 + - @backstage/plugin-badges@0.2.33-next.3 + - @backstage/plugin-catalog-graph@0.2.21-next.2 + - @backstage/plugin-catalog-import@0.8.12-next.3 + - @backstage/plugin-circleci@0.3.9-next.3 + - @backstage/plugin-cloudbuild@0.3.9-next.3 + - @backstage/plugin-code-coverage@0.2.2-next.3 + - @backstage/plugin-cost-insights@0.11.31-next.3 + - @backstage/plugin-dynatrace@0.2.0-next.3 + - @backstage/plugin-explore@0.3.40-next.3 + - @backstage/plugin-gcalendar@0.3.5-next.3 + - @backstage/plugin-gcp-projects@0.3.28-next.3 + - @backstage/plugin-github-actions@0.5.9-next.3 + - @backstage/plugin-gocd@0.1.15-next.2 + - @backstage/plugin-graphiql@0.2.41-next.3 + - @backstage/plugin-home@0.4.25-next.3 + - @backstage/plugin-jenkins@0.7.8-next.3 + - @backstage/plugin-kafka@0.3.9-next.3 + - @backstage/plugin-kubernetes@0.7.2-next.3 + - @backstage/plugin-lighthouse@0.3.9-next.3 + - @backstage/plugin-newrelic@0.3.27-next.3 + - @backstage/plugin-org@0.5.9-next.3 + - @backstage/plugin-pagerduty@0.5.2-next.3 + - @backstage/plugin-permission-react@0.4.5-next.2 + - @backstage/plugin-rollbar@0.4.9-next.3 + - @backstage/plugin-scaffolder@1.6.0-next.3 + - @backstage/plugin-search@1.0.2-next.3 + - @backstage/plugin-sentry@0.4.2-next.3 + - @backstage/plugin-shortcuts@0.3.1-next.3 + - @backstage/plugin-stack-overflow@0.1.5-next.3 + - @backstage/plugin-tech-insights@0.3.0-next.3 + - @backstage/plugin-tech-radar@0.5.16-next.3 + - @backstage/plugin-techdocs@1.3.2-next.3 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.4-next.2 + - @backstage/plugin-todo@0.2.11-next.3 + - @backstage/plugin-user-settings@0.4.8-next.3 + - @backstage/cli@0.19.0-next.3 + - @backstage/plugin-newrelic-dashboard@0.2.2-next.2 + - @backstage/plugin-techdocs-react@1.0.4-next.2 + +## 0.2.75-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.19.0-next.2 + - @backstage/core-app-api@1.1.0-next.2 + - @backstage/plugin-catalog-import@0.8.12-next.2 + - @backstage/plugin-scaffolder@1.6.0-next.2 + - @backstage/plugin-techdocs@1.3.2-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.4-next.1 + - @backstage/plugin-apache-airflow@0.2.2-next.2 + - @backstage/core-components@0.11.1-next.2 + - @backstage/core-plugin-api@1.0.6-next.2 + - @backstage/integration-react@1.1.4-next.1 + - @backstage/plugin-airbrake@0.3.9-next.2 + - @backstage/plugin-api-docs@0.8.9-next.2 + - @backstage/plugin-azure-devops@0.2.0-next.2 + - @backstage/plugin-badges@0.2.33-next.2 + - @backstage/plugin-circleci@0.3.9-next.2 + - @backstage/plugin-cloudbuild@0.3.9-next.2 + - @backstage/plugin-code-coverage@0.2.2-next.2 + - @backstage/plugin-cost-insights@0.11.31-next.2 + - @backstage/plugin-dynatrace@0.2.0-next.2 + - @backstage/plugin-explore@0.3.40-next.2 + - @backstage/plugin-gcalendar@0.3.5-next.2 + - @backstage/plugin-gcp-projects@0.3.28-next.2 + - @backstage/plugin-github-actions@0.5.9-next.2 + - @backstage/plugin-gocd@0.1.15-next.1 + - @backstage/plugin-graphiql@0.2.41-next.2 + - @backstage/plugin-home@0.4.25-next.2 + - @backstage/plugin-jenkins@0.7.8-next.2 + - @backstage/plugin-kafka@0.3.9-next.2 + - @backstage/plugin-kubernetes@0.7.2-next.2 + - @backstage/plugin-lighthouse@0.3.9-next.2 + - @backstage/plugin-newrelic@0.3.27-next.2 + - @backstage/plugin-org@0.5.9-next.2 + - @backstage/plugin-pagerduty@0.5.2-next.2 + - @backstage/plugin-rollbar@0.4.9-next.2 + - @backstage/plugin-search@1.0.2-next.2 + - @backstage/plugin-sentry@0.4.2-next.2 + - @backstage/plugin-shortcuts@0.3.1-next.2 + - @backstage/plugin-stack-overflow@0.1.5-next.2 + - @backstage/plugin-tech-insights@0.3.0-next.2 + - @backstage/plugin-tech-radar@0.5.16-next.2 + - @backstage/plugin-todo@0.2.11-next.2 + - @backstage/plugin-user-settings@0.4.8-next.2 + - @backstage/plugin-search-react@1.1.0-next.2 + +## 0.2.75-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/app-defaults@1.0.6-next.1 + - @backstage/core-app-api@1.1.0-next.1 + - @backstage/core-components@0.11.1-next.1 + - @backstage/core-plugin-api@1.0.6-next.1 + - @backstage/plugin-airbrake@0.3.9-next.1 + - @backstage/plugin-api-docs@0.8.9-next.1 + - @backstage/plugin-azure-devops@0.2.0-next.1 + - @backstage/plugin-badges@0.2.33-next.1 + - @backstage/plugin-catalog-graph@0.2.21-next.1 + - @backstage/plugin-catalog-import@0.8.12-next.1 + - @backstage/plugin-catalog-react@1.1.4-next.1 + - @backstage/plugin-circleci@0.3.9-next.1 + - @backstage/plugin-cloudbuild@0.3.9-next.1 + - @backstage/plugin-code-coverage@0.2.2-next.1 + - @backstage/plugin-cost-insights@0.11.31-next.1 + - @backstage/plugin-explore@0.3.40-next.1 + - @backstage/plugin-gcp-projects@0.3.28-next.1 + - @backstage/plugin-github-actions@0.5.9-next.1 + - @backstage/plugin-graphiql@0.2.41-next.1 + - @backstage/plugin-home@0.4.25-next.1 + - @backstage/plugin-jenkins@0.7.8-next.1 + - @backstage/plugin-kafka@0.3.9-next.1 + - @backstage/plugin-kubernetes@0.7.2-next.1 + - @backstage/plugin-lighthouse@0.3.9-next.1 + - @backstage/plugin-org@0.5.9-next.1 + - @backstage/plugin-pagerduty@0.5.2-next.1 + - @backstage/plugin-permission-react@0.4.5-next.1 + - @backstage/plugin-rollbar@0.4.9-next.1 + - @backstage/plugin-scaffolder@1.6.0-next.1 + - @backstage/plugin-search-react@1.0.2-next.1 + - @backstage/plugin-search@1.0.2-next.1 + - @backstage/plugin-sentry@0.4.2-next.1 + - @backstage/plugin-shortcuts@0.3.1-next.1 + - @backstage/plugin-tech-insights@0.3.0-next.1 + - @backstage/plugin-techdocs-react@1.0.4-next.1 + - @backstage/plugin-techdocs@1.3.2-next.1 + - @backstage/plugin-todo@0.2.11-next.1 + - @backstage/plugin-user-settings@0.4.8-next.1 + - @backstage/cli@0.19.0-next.1 + - @backstage/plugin-dynatrace@0.2.0-next.1 + - @backstage/plugin-apache-airflow@0.2.2-next.1 + - @backstage/plugin-gcalendar@0.3.5-next.1 + - @backstage/plugin-newrelic@0.3.27-next.1 + - @backstage/plugin-newrelic-dashboard@0.2.2-next.1 + - @backstage/plugin-tech-radar@0.5.16-next.1 + - @backstage/plugin-stack-overflow@0.1.5-next.1 + +## 0.2.75-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kafka@0.3.9-next.0 + - @backstage/plugin-lighthouse@0.3.9-next.0 + - @backstage/plugin-scaffolder@1.6.0-next.0 + - @backstage/plugin-azure-devops@0.2.0-next.0 + - @backstage/plugin-cloudbuild@0.3.9-next.0 + - @backstage/plugin-explore@0.3.40-next.0 + - @backstage/plugin-github-actions@0.5.9-next.0 + - @backstage/plugin-jenkins@0.7.8-next.0 + - @backstage/plugin-newrelic-dashboard@0.2.2-next.0 + - @backstage/plugin-pagerduty@0.5.2-next.0 + - @backstage/plugin-sentry@0.4.2-next.0 + - @backstage/plugin-tech-insights@0.2.5-next.0 + - @backstage/plugin-techdocs@1.3.2-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.4-next.0 + - @backstage/plugin-user-settings@0.4.8-next.0 + - @backstage/plugin-dynatrace@0.2.0-next.0 + - @backstage/plugin-cost-insights@0.11.31-next.0 + - @backstage/core-app-api@1.0.6-next.0 + - @backstage/core-plugin-api@1.0.6-next.0 + - @backstage/core-components@0.11.1-next.0 + - @backstage/plugin-org@0.5.9-next.0 + - @backstage/cli@0.18.2-next.0 + - @backstage/integration-react@1.1.4-next.0 + - @backstage/plugin-airbrake@0.3.9-next.0 + - @backstage/plugin-apache-airflow@0.2.2-next.0 + - @backstage/plugin-api-docs@0.8.9-next.0 + - @backstage/plugin-badges@0.2.33-next.0 + - @backstage/plugin-catalog-import@0.8.12-next.0 + - @backstage/plugin-circleci@0.3.9-next.0 + - @backstage/plugin-code-coverage@0.2.2-next.0 + - @backstage/plugin-gcalendar@0.3.5-next.0 + - @backstage/plugin-gcp-projects@0.3.28-next.0 + - @backstage/plugin-gocd@0.1.15-next.0 + - @backstage/plugin-graphiql@0.2.41-next.0 + - @backstage/plugin-home@0.4.25-next.0 + - @backstage/plugin-kubernetes@0.7.2-next.0 + - @backstage/plugin-newrelic@0.3.27-next.0 + - @backstage/plugin-rollbar@0.4.9-next.0 + - @backstage/plugin-search@1.0.2-next.0 + - @backstage/plugin-shortcuts@0.3.1-next.0 + - @backstage/plugin-stack-overflow@0.1.5-next.0 + - @backstage/plugin-tech-radar@0.5.16-next.0 + - @backstage/plugin-todo@0.2.11-next.0 + - @backstage/plugin-catalog-react@1.1.4-next.0 + - @backstage/app-defaults@1.0.6-next.0 + - @backstage/plugin-catalog-graph@0.2.21-next.0 + - @backstage/plugin-search-react@1.0.2-next.0 + - @backstage/plugin-permission-react@0.4.5-next.0 + - @backstage/plugin-techdocs-react@1.0.4-next.0 + - @backstage/plugin-catalog-common@1.0.6-next.0 + - @backstage/plugin-search-common@1.0.1-next.0 + - @internal/plugin-catalog-customized@0.0.2-next.0 + +## 0.2.74 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes@0.7.1 + - @backstage/cli@0.18.1 + - @backstage/plugin-techdocs@1.3.1 + - @backstage/plugin-home@0.4.24 + - @backstage/core-components@0.11.0 + - @backstage/plugin-scaffolder@1.5.0 + - @backstage/core-plugin-api@1.0.5 + - @backstage/plugin-catalog-react@1.1.3 + - @backstage/plugin-cost-insights@0.11.30 + - @backstage/plugin-graphiql@0.2.40 + - @backstage/plugin-catalog-common@1.0.5 + - @backstage/plugin-kafka@0.3.8 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.3 + - @backstage/plugin-gocd@0.1.14 + - @backstage/plugin-sentry@0.4.1 + - @backstage/plugin-api-docs@0.8.8 + - @backstage/plugin-techdocs-react@1.0.3 + - @backstage/plugin-shortcuts@0.3.0 + - @backstage/plugin-tech-radar@0.5.15 + - @backstage/app-defaults@1.0.5 + - @backstage/core-app-api@1.0.5 + - @backstage/integration-react@1.1.3 + - @backstage/plugin-airbrake@0.3.8 + - @backstage/plugin-apache-airflow@0.2.1 + - @backstage/plugin-azure-devops@0.1.24 + - @backstage/plugin-badges@0.2.32 + - @internal/plugin-catalog-customized@0.0.1 + - @backstage/plugin-catalog-graph@0.2.20 + - @backstage/plugin-catalog-import@0.8.11 + - @backstage/plugin-circleci@0.3.8 + - @backstage/plugin-cloudbuild@0.3.8 + - @backstage/plugin-code-coverage@0.2.1 + - @backstage/plugin-dynatrace@0.1.2 + - @backstage/plugin-explore@0.3.39 + - @backstage/plugin-gcalendar@0.3.4 + - @backstage/plugin-gcp-projects@0.3.27 + - @backstage/plugin-github-actions@0.5.8 + - @backstage/plugin-jenkins@0.7.7 + - @backstage/plugin-lighthouse@0.3.8 + - @backstage/plugin-newrelic@0.3.26 + - @backstage/plugin-newrelic-dashboard@0.2.1 + - @backstage/plugin-org@0.5.8 + - @backstage/plugin-pagerduty@0.5.1 + - @backstage/plugin-permission-react@0.4.4 + - @backstage/plugin-rollbar@0.4.8 + - @backstage/plugin-search@1.0.1 + - @backstage/plugin-search-react@1.0.1 + - @backstage/plugin-stack-overflow@0.1.4 + - @backstage/plugin-tech-insights@0.2.4 + - @backstage/plugin-todo@0.2.10 + - @backstage/plugin-user-settings@0.4.7 + +## 0.2.74-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes@0.7.1-next.2 + - @backstage/plugin-catalog-react@1.1.3-next.2 + - @backstage/plugin-cost-insights@0.11.30-next.1 + - @backstage/cli@0.18.1-next.1 + - @backstage/plugin-techdocs@1.3.1-next.2 + - @backstage/core-components@0.11.0-next.2 + - @backstage/app-defaults@1.0.5-next.1 + - @backstage/integration-react@1.1.3-next.1 + - @backstage/plugin-airbrake@0.3.8-next.1 + - @backstage/plugin-apache-airflow@0.2.1-next.1 + - @backstage/plugin-api-docs@0.8.8-next.2 + - @backstage/plugin-azure-devops@0.1.24-next.1 + - @backstage/plugin-badges@0.2.32-next.1 + - @backstage/plugin-catalog-graph@0.2.20-next.1 + - @backstage/plugin-catalog-import@0.8.11-next.1 + - @backstage/plugin-circleci@0.3.8-next.1 + - @backstage/plugin-cloudbuild@0.3.8-next.1 + - @backstage/plugin-code-coverage@0.2.1-next.1 + - @backstage/plugin-dynatrace@0.1.2-next.1 + - @backstage/plugin-explore@0.3.39-next.1 + - @backstage/plugin-gcalendar@0.3.4-next.1 + - @backstage/plugin-gcp-projects@0.3.27-next.1 + - @backstage/plugin-github-actions@0.5.8-next.1 + - @backstage/plugin-gocd@0.1.14-next.1 + - @backstage/plugin-graphiql@0.2.40-next.1 + - @backstage/plugin-home@0.4.24-next.2 + - @backstage/plugin-jenkins@0.7.7-next.2 + - @backstage/plugin-kafka@0.3.8-next.1 + - @backstage/plugin-lighthouse@0.3.8-next.1 + - @backstage/plugin-newrelic@0.3.26-next.1 + - @backstage/plugin-newrelic-dashboard@0.2.1-next.1 + - @backstage/plugin-org@0.5.8-next.1 + - @backstage/plugin-pagerduty@0.5.1-next.1 + - @backstage/plugin-rollbar@0.4.8-next.1 + - @backstage/plugin-scaffolder@1.5.0-next.2 + - @backstage/plugin-search@1.0.1-next.1 + - @backstage/plugin-search-react@1.0.1-next.1 + - @backstage/plugin-sentry@0.4.1-next.1 + - @backstage/plugin-shortcuts@0.3.0-next.1 + - @backstage/plugin-stack-overflow@0.1.4-next.1 + - @backstage/plugin-tech-insights@0.2.4-next.1 + - @backstage/plugin-tech-radar@0.5.15-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.3-next.2 + - @backstage/plugin-techdocs-react@1.0.3-next.2 + - @backstage/plugin-todo@0.2.10-next.1 + - @backstage/plugin-user-settings@0.4.7-next.1 + +## 0.2.74-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes@0.7.1-next.1 + - @backstage/plugin-home@0.4.24-next.1 + - @backstage/core-components@0.10.1-next.1 + - @backstage/plugin-scaffolder@1.5.0-next.1 + - @backstage/plugin-techdocs@1.3.1-next.1 + - @backstage/plugin-catalog-common@1.0.5-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.3-next.1 + - @backstage/plugin-api-docs@0.8.8-next.1 + - @backstage/plugin-techdocs-react@1.0.3-next.1 + - @backstage/plugin-catalog-react@1.1.3-next.1 + - @backstage/plugin-jenkins@0.7.7-next.1 + +## 0.2.74-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.0.5-next.0 + - @backstage/plugin-techdocs@1.3.1-next.0 + - @backstage/cli@0.18.1-next.0 + - @backstage/plugin-kafka@0.3.8-next.0 + - @backstage/plugin-gocd@0.1.14-next.0 + - @backstage/plugin-sentry@0.4.1-next.0 + - @backstage/plugin-shortcuts@0.3.0-next.0 + - @backstage/integration-react@1.1.3-next.0 + - @backstage/plugin-catalog-import@0.8.11-next.0 + - @backstage/plugin-catalog-react@1.1.3-next.0 + - @backstage/plugin-github-actions@0.5.8-next.0 + - @backstage/plugin-scaffolder@1.4.1-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.3-next.0 + - @backstage/app-defaults@1.0.5-next.0 + - @backstage/core-app-api@1.0.5-next.0 + - @backstage/core-components@0.10.1-next.0 + - @backstage/plugin-airbrake@0.3.8-next.0 + - @backstage/plugin-apache-airflow@0.2.1-next.0 + - @backstage/plugin-api-docs@0.8.8-next.0 + - @backstage/plugin-azure-devops@0.1.24-next.0 + - @backstage/plugin-badges@0.2.32-next.0 + - @backstage/plugin-catalog-graph@0.2.20-next.0 + - @backstage/plugin-circleci@0.3.8-next.0 + - @backstage/plugin-cloudbuild@0.3.8-next.0 + - @backstage/plugin-code-coverage@0.2.1-next.0 + - @backstage/plugin-cost-insights@0.11.30-next.0 + - @backstage/plugin-dynatrace@0.1.2-next.0 + - @backstage/plugin-explore@0.3.39-next.0 + - @backstage/plugin-gcalendar@0.3.4-next.0 + - @backstage/plugin-gcp-projects@0.3.27-next.0 + - @backstage/plugin-graphiql@0.2.40-next.0 + - @backstage/plugin-home@0.4.24-next.0 + - @backstage/plugin-jenkins@0.7.7-next.0 + - @backstage/plugin-kubernetes@0.7.1-next.0 + - @backstage/plugin-lighthouse@0.3.8-next.0 + - @backstage/plugin-newrelic@0.3.26-next.0 + - @backstage/plugin-newrelic-dashboard@0.2.1-next.0 + - @backstage/plugin-org@0.5.8-next.0 + - @backstage/plugin-pagerduty@0.5.1-next.0 + - @backstage/plugin-permission-react@0.4.4-next.0 + - @backstage/plugin-rollbar@0.4.8-next.0 + - @backstage/plugin-search@1.0.1-next.0 + - @backstage/plugin-search-react@1.0.1-next.0 + - @backstage/plugin-stack-overflow@0.1.4-next.0 + - @backstage/plugin-tech-insights@0.2.4-next.0 + - @backstage/plugin-tech-radar@0.5.15-next.0 + - @backstage/plugin-techdocs-react@1.0.3-next.0 + - @backstage/plugin-todo@0.2.10-next.0 + - @backstage/plugin-user-settings@0.4.7-next.0 + - @internal/plugin-catalog-customized@0.0.1-next.0 + +## 0.2.73 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-cost-insights@0.11.29 + - @backstage/plugin-code-coverage@0.2.0 + - @backstage/plugin-scaffolder@1.4.0 + - @backstage/core-components@0.10.0 + - @backstage/catalog-model@1.1.0 + - @backstage/plugin-techdocs-react@1.0.2 + - @backstage/plugin-search@1.0.0 + - @backstage/plugin-search-react@1.0.0 + - @backstage/plugin-search-common@1.0.0 + - @backstage/plugin-kubernetes@0.7.0 + - @backstage/plugin-tech-radar@0.5.14 + - @backstage/plugin-catalog@1.4.0 + - @backstage/plugin-newrelic-dashboard@0.2.0 + - @backstage/cli@0.18.0 + - @backstage/plugin-apache-airflow@0.2.0 + - @backstage/app-defaults@1.0.4 + - @backstage/core-app-api@1.0.4 + - @backstage/core-plugin-api@1.0.4 + - @backstage/plugin-jenkins@0.7.6 + - @backstage/plugin-stack-overflow@0.1.3 + - @backstage/plugin-catalog-import@0.8.10 + - @backstage/plugin-github-actions@0.5.7 + - @backstage/plugin-sentry@0.4.0 + - @backstage/integration-react@1.1.2 + - @backstage/plugin-airbrake@0.3.7 + - @backstage/plugin-api-docs@0.8.7 + - @backstage/plugin-azure-devops@0.1.23 + - @backstage/plugin-badges@0.2.31 + - @backstage/plugin-circleci@0.3.7 + - @backstage/plugin-cloudbuild@0.3.7 + - @backstage/plugin-dynatrace@0.1.1 + - @backstage/plugin-explore@0.3.38 + - @backstage/plugin-gcalendar@0.3.3 + - @backstage/plugin-gcp-projects@0.3.26 + - @backstage/plugin-gocd@0.1.13 + - @backstage/plugin-graphiql@0.2.39 + - @backstage/plugin-home@0.4.23 + - @backstage/plugin-kafka@0.3.7 + - @backstage/plugin-lighthouse@0.3.7 + - @backstage/plugin-newrelic@0.3.25 + - @backstage/plugin-org@0.5.7 + - @backstage/plugin-pagerduty@0.5.0 + - @backstage/plugin-rollbar@0.4.7 + - @backstage/plugin-shortcuts@0.2.8 + - @backstage/plugin-tech-insights@0.2.3 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.2 + - @backstage/plugin-techdocs@1.3.0 + - @backstage/plugin-todo@0.2.9 + - @backstage/plugin-user-settings@0.4.6 + - @backstage/plugin-catalog-react@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.4 + - @backstage/plugin-catalog-graph@0.2.19 + - @backstage/plugin-permission-react@0.4.3 + +## 0.2.73-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-code-coverage@0.2.0-next.3 + - @backstage/plugin-scaffolder@1.4.0-next.3 + - @backstage/plugin-kubernetes@0.7.0-next.3 + - @backstage/plugin-tech-radar@0.5.14-next.3 + - @backstage/plugin-newrelic-dashboard@0.2.0-next.3 + - @backstage/app-defaults@1.0.4-next.3 + - @backstage/core-app-api@1.0.4-next.1 + - @backstage/core-plugin-api@1.0.4-next.0 + - @backstage/core-components@0.10.0-next.3 + - @backstage/plugin-sentry@0.4.0-next.3 + - @backstage/cli@0.18.0-next.3 + - @backstage/integration-react@1.1.2-next.3 + - @backstage/plugin-airbrake@0.3.7-next.3 + - @backstage/plugin-apache-airflow@0.2.0-next.3 + - @backstage/plugin-api-docs@0.8.7-next.3 + - @backstage/plugin-azure-devops@0.1.23-next.3 + - @backstage/plugin-badges@0.2.31-next.3 + - @backstage/plugin-catalog-import@0.8.10-next.3 + - @backstage/plugin-circleci@0.3.7-next.3 + - @backstage/plugin-cloudbuild@0.3.7-next.3 + - @backstage/plugin-cost-insights@0.11.29-next.3 + - @backstage/plugin-dynatrace@0.1.1-next.3 + - @backstage/plugin-explore@0.3.38-next.3 + - @backstage/plugin-gcalendar@0.3.3-next.3 + - @backstage/plugin-gcp-projects@0.3.26-next.3 + - @backstage/plugin-github-actions@0.5.7-next.3 + - @backstage/plugin-gocd@0.1.13-next.3 + - @backstage/plugin-graphiql@0.2.39-next.3 + - @backstage/plugin-home@0.4.23-next.3 + - @backstage/plugin-jenkins@0.7.6-next.3 + - @backstage/plugin-kafka@0.3.7-next.3 + - @backstage/plugin-lighthouse@0.3.7-next.3 + - @backstage/plugin-newrelic@0.3.25-next.3 + - @backstage/plugin-org@0.5.7-next.3 + - @backstage/plugin-pagerduty@0.5.0-next.3 + - @backstage/plugin-rollbar@0.4.7-next.3 + - @backstage/plugin-search@0.9.1-next.3 + - @backstage/plugin-shortcuts@0.2.8-next.3 + - @backstage/plugin-stack-overflow@0.1.3-next.3 + - @backstage/plugin-tech-insights@0.2.3-next.3 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.2-next.3 + - @backstage/plugin-techdocs@1.2.1-next.3 + - @backstage/plugin-todo@0.2.9-next.3 + - @backstage/plugin-user-settings@0.4.6-next.3 + - @backstage/catalog-model@1.1.0-next.3 + - @backstage/plugin-catalog-react@1.1.2-next.3 + - @backstage/plugin-catalog@1.4.0-next.3 + - @backstage/plugin-catalog-graph@0.2.19-next.3 + - @backstage/plugin-permission-react@0.4.3-next.1 + - @backstage/plugin-search-react@0.2.2-next.3 + - @backstage/plugin-techdocs-react@1.0.2-next.2 + +## 0.2.73-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-cost-insights@0.11.29-next.2 + - @backstage/core-components@0.10.0-next.2 + - @backstage/plugin-scaffolder@1.4.0-next.2 + - @backstage/plugin-jenkins@0.7.6-next.2 + - @backstage/catalog-model@1.1.0-next.2 + - @backstage/plugin-search-react@0.2.2-next.2 + - @backstage/theme@0.2.16-next.1 + - @backstage/plugin-catalog-import@0.8.10-next.2 + - @backstage/plugin-techdocs@1.2.1-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.2-next.2 + - @backstage/plugin-api-docs@0.8.7-next.2 + - @backstage/plugin-catalog@1.4.0-next.2 + - @backstage/cli@0.18.0-next.2 + - @backstage/plugin-catalog-react@1.1.2-next.2 + - @backstage/app-defaults@1.0.4-next.2 + - @backstage/integration-react@1.1.2-next.2 + - @backstage/plugin-airbrake@0.3.7-next.2 + - @backstage/plugin-apache-airflow@0.2.0-next.2 + - @backstage/plugin-azure-devops@0.1.23-next.2 + - @backstage/plugin-badges@0.2.31-next.2 + - @backstage/plugin-catalog-graph@0.2.19-next.2 + - @backstage/plugin-circleci@0.3.7-next.2 + - @backstage/plugin-cloudbuild@0.3.7-next.2 + - @backstage/plugin-code-coverage@0.1.34-next.2 + - @backstage/plugin-dynatrace@0.1.1-next.2 + - @backstage/plugin-explore@0.3.38-next.2 + - @backstage/plugin-gcalendar@0.3.3-next.2 + - @backstage/plugin-gcp-projects@0.3.26-next.2 + - @backstage/plugin-github-actions@0.5.7-next.2 + - @backstage/plugin-gocd@0.1.13-next.2 + - @backstage/plugin-graphiql@0.2.39-next.2 + - @backstage/plugin-home@0.4.23-next.2 + - @backstage/plugin-kafka@0.3.7-next.2 + - @backstage/plugin-kubernetes@0.6.7-next.2 + - @backstage/plugin-lighthouse@0.3.7-next.2 + - @backstage/plugin-newrelic@0.3.25-next.2 + - @backstage/plugin-newrelic-dashboard@0.1.15-next.2 + - @backstage/plugin-org@0.5.7-next.2 + - @backstage/plugin-pagerduty@0.5.0-next.2 + - @backstage/plugin-rollbar@0.4.7-next.2 + - @backstage/plugin-search@0.9.1-next.2 + - @backstage/plugin-sentry@0.3.45-next.2 + - @backstage/plugin-shortcuts@0.2.8-next.2 + - @backstage/plugin-stack-overflow@0.1.3-next.2 + - @backstage/plugin-tech-insights@0.2.3-next.2 + - @backstage/plugin-tech-radar@0.5.14-next.2 + - @backstage/plugin-techdocs-react@1.0.2-next.1 + - @backstage/plugin-todo@0.2.9-next.2 + - @backstage/plugin-user-settings@0.4.6-next.2 + +## 0.2.73-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.6-next.1 + - @backstage/cli@0.18.0-next.1 + - @backstage/plugin-apache-airflow@0.2.0-next.1 + - @backstage/catalog-model@1.1.0-next.1 + - @backstage/plugin-search@0.9.1-next.1 + - @backstage/plugin-org@0.5.7-next.1 + - @backstage/plugin-catalog@1.3.1-next.1 + - @backstage/plugin-home@0.4.23-next.1 + - @backstage/plugin-stack-overflow@0.1.3-next.1 + - @backstage/theme@0.2.16-next.0 + - @backstage/plugin-techdocs@1.2.1-next.1 + - @backstage/app-defaults@1.0.4-next.1 + - @backstage/integration-react@1.1.2-next.1 + - @backstage/plugin-airbrake@0.3.7-next.1 + - @backstage/plugin-api-docs@0.8.7-next.1 + - @backstage/plugin-azure-devops@0.1.23-next.1 + - @backstage/plugin-badges@0.2.31-next.1 + - @backstage/plugin-catalog-common@1.0.4-next.0 + - @backstage/plugin-catalog-graph@0.2.19-next.1 + - @backstage/plugin-catalog-import@0.8.10-next.1 + - @backstage/plugin-catalog-react@1.1.2-next.1 + - @backstage/plugin-circleci@0.3.7-next.1 + - @backstage/plugin-cloudbuild@0.3.7-next.1 + - @backstage/plugin-code-coverage@0.1.34-next.1 + - @backstage/plugin-cost-insights@0.11.29-next.1 + - @backstage/plugin-dynatrace@0.1.1-next.1 + - @backstage/plugin-explore@0.3.38-next.1 + - @backstage/plugin-gcalendar@0.3.3-next.1 + - @backstage/plugin-gcp-projects@0.3.26-next.1 + - @backstage/plugin-github-actions@0.5.7-next.1 + - @backstage/plugin-gocd@0.1.13-next.1 + - @backstage/plugin-graphiql@0.2.39-next.1 + - @backstage/plugin-jenkins@0.7.6-next.1 + - @backstage/plugin-kafka@0.3.7-next.1 + - @backstage/plugin-kubernetes@0.6.7-next.1 + - @backstage/plugin-lighthouse@0.3.7-next.1 + - @backstage/plugin-newrelic@0.3.25-next.1 + - @backstage/plugin-newrelic-dashboard@0.1.15-next.1 + - @backstage/plugin-pagerduty@0.5.0-next.1 + - @backstage/plugin-permission-react@0.4.3-next.0 + - @backstage/plugin-rollbar@0.4.7-next.1 + - @backstage/plugin-scaffolder@1.4.0-next.1 + - @backstage/plugin-search-common@0.3.6-next.0 + - @backstage/plugin-sentry@0.3.45-next.1 + - @backstage/plugin-shortcuts@0.2.8-next.1 + - @backstage/plugin-tech-insights@0.2.3-next.1 + - @backstage/plugin-tech-radar@0.5.14-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.2-next.1 + - @backstage/plugin-todo@0.2.9-next.1 + - @backstage/plugin-user-settings@0.4.6-next.1 + - @backstage/plugin-search-react@0.2.2-next.1 + +## 0.2.73-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.4.0-next.0 + - @backstage/catalog-model@1.1.0-next.0 + - @backstage/core-components@0.9.6-next.0 + - @backstage/plugin-techdocs-react@1.0.2-next.0 + - @backstage/plugin-catalog@1.3.1-next.0 + - @backstage/plugin-stack-overflow@0.1.3-next.0 + - @backstage/core-app-api@1.0.4-next.0 + - @backstage/cli@0.17.3-next.0 + - @backstage/plugin-azure-devops@0.1.23-next.0 + - @backstage/plugin-techdocs@1.2.1-next.0 + - @backstage/plugin-catalog-import@0.8.10-next.0 + - @backstage/plugin-pagerduty@0.5.0-next.0 + - @backstage/plugin-github-actions@0.5.7-next.0 + - @backstage/plugin-airbrake@0.3.7-next.0 + - @backstage/plugin-api-docs@0.8.7-next.0 + - @backstage/plugin-badges@0.2.31-next.0 + - @backstage/plugin-catalog-graph@0.2.19-next.0 + - @backstage/plugin-catalog-react@1.1.2-next.0 + - @backstage/plugin-circleci@0.3.7-next.0 + - @backstage/plugin-cloudbuild@0.3.7-next.0 + - @backstage/plugin-code-coverage@0.1.34-next.0 + - @backstage/plugin-cost-insights@0.11.29-next.0 + - @backstage/plugin-dynatrace@0.1.1-next.0 + - @backstage/plugin-explore@0.3.38-next.0 + - @backstage/plugin-gocd@0.1.13-next.0 + - @backstage/plugin-home@0.4.23-next.0 + - @backstage/plugin-jenkins@0.7.6-next.0 + - @backstage/plugin-kafka@0.3.7-next.0 + - @backstage/plugin-kubernetes@0.6.7-next.0 + - @backstage/plugin-lighthouse@0.3.7-next.0 + - @backstage/plugin-newrelic-dashboard@0.1.15-next.0 + - @backstage/plugin-org@0.5.7-next.0 + - @backstage/plugin-rollbar@0.4.7-next.0 + - @backstage/plugin-search@0.9.1-next.0 + - @backstage/plugin-sentry@0.3.45-next.0 + - @backstage/plugin-tech-insights@0.2.3-next.0 + - @backstage/plugin-todo@0.2.9-next.0 + - @backstage/app-defaults@1.0.4-next.0 + - @backstage/integration-react@1.1.2-next.0 + - @backstage/plugin-apache-airflow@0.1.15-next.0 + - @backstage/plugin-gcalendar@0.3.3-next.0 + - @backstage/plugin-gcp-projects@0.3.26-next.0 + - @backstage/plugin-graphiql@0.2.39-next.0 + - @backstage/plugin-newrelic@0.3.25-next.0 + - @backstage/plugin-search-react@0.2.2-next.0 + - @backstage/plugin-shortcuts@0.2.8-next.0 + - @backstage/plugin-tech-radar@0.5.14-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.2-next.0 + - @backstage/plugin-user-settings@0.4.6-next.0 + +## 0.2.72 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes@0.6.6 + - @backstage/plugin-cost-insights@0.11.28 + - @backstage/plugin-scaffolder@1.3.0 + - @backstage/plugin-catalog@1.3.0 + - @backstage/plugin-techdocs@1.2.0 + - @backstage/plugin-catalog-react@1.1.1 + - @backstage/cli@0.17.2 + - @backstage/plugin-user-settings@0.4.5 + - @backstage/plugin-pagerduty@0.4.0 + - @backstage/plugin-search-common@0.3.5 + - @backstage/plugin-search@0.9.0 + - @backstage/plugin-search-react@0.2.1 + - @backstage/plugin-sentry@0.3.44 + - @backstage/plugin-tech-insights@0.2.2 + - @backstage/plugin-catalog-common@1.0.3 + - @backstage/core-components@0.9.5 + - @backstage/plugin-dynatrace@0.1.0 + - @backstage/plugin-tech-radar@0.5.13 + - @backstage/core-app-api@1.0.3 + - @backstage/core-plugin-api@1.0.3 + - @backstage/integration-react@1.1.1 + - @backstage/plugin-airbrake@0.3.6 + - @backstage/plugin-apache-airflow@0.1.14 + - @backstage/plugin-api-docs@0.8.6 + - @backstage/plugin-azure-devops@0.1.22 + - @backstage/plugin-badges@0.2.30 + - @backstage/plugin-catalog-import@0.8.9 + - @backstage/plugin-circleci@0.3.6 + - @backstage/plugin-cloudbuild@0.3.6 + - @backstage/plugin-code-coverage@0.1.33 + - @backstage/plugin-explore@0.3.37 + - @backstage/plugin-gcalendar@0.3.2 + - @backstage/plugin-gcp-projects@0.3.25 + - @backstage/plugin-github-actions@0.5.6 + - @backstage/plugin-gocd@0.1.12 + - @backstage/plugin-graphiql@0.2.38 + - @backstage/plugin-home@0.4.22 + - @backstage/plugin-jenkins@0.7.5 + - @backstage/plugin-kafka@0.3.6 + - @backstage/plugin-lighthouse@0.3.6 + - @backstage/plugin-newrelic@0.3.24 + - @backstage/plugin-org@0.5.6 + - @backstage/plugin-rollbar@0.4.6 + - @backstage/plugin-shortcuts@0.2.7 + - @backstage/plugin-stack-overflow@0.1.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.1 + - @backstage/plugin-todo@0.2.8 + - @backstage/catalog-model@1.0.3 + - @backstage/plugin-techdocs-react@1.0.1 + - @backstage/app-defaults@1.0.3 + - @backstage/plugin-catalog-graph@0.2.18 + - @backstage/plugin-newrelic-dashboard@0.1.14 + - @backstage/plugin-permission-react@0.4.2 + +## 0.2.72-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.3.0-next.2 + - @backstage/cli@0.17.2-next.2 + - @backstage/plugin-pagerduty@0.4.0-next.2 + - @backstage/plugin-search-common@0.3.5-next.1 + - @backstage/plugin-cost-insights@0.11.28-next.2 + - @backstage/plugin-catalog-common@1.0.3-next.1 + - @backstage/plugin-kubernetes@0.6.6-next.2 + - @backstage/core-components@0.9.5-next.2 + - @backstage/plugin-search@0.8.2-next.2 + - @backstage/plugin-org@0.5.6-next.2 + +## 0.2.72-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-sentry@0.3.44-next.1 + - @backstage/plugin-kubernetes@0.6.6-next.1 + - @backstage/plugin-tech-insights@0.2.2-next.1 + - @backstage/plugin-cost-insights@0.11.28-next.1 + - @backstage/core-components@0.9.5-next.1 + - @backstage/cli@0.17.2-next.1 + - @backstage/core-app-api@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.3-next.0 + - @backstage/integration-react@1.1.1-next.1 + - @backstage/plugin-airbrake@0.3.6-next.1 + - @backstage/plugin-apache-airflow@0.1.14-next.1 + - @backstage/plugin-api-docs@0.8.6-next.1 + - @backstage/plugin-azure-devops@0.1.22-next.1 + - @backstage/plugin-badges@0.2.30-next.1 + - @backstage/plugin-catalog-import@0.8.9-next.1 + - @backstage/plugin-circleci@0.3.6-next.1 + - @backstage/plugin-cloudbuild@0.3.6-next.1 + - @backstage/plugin-code-coverage@0.1.33-next.1 + - @backstage/plugin-explore@0.3.37-next.1 + - @backstage/plugin-gcalendar@0.3.2-next.1 + - @backstage/plugin-gcp-projects@0.3.25-next.1 + - @backstage/plugin-github-actions@0.5.6-next.1 + - @backstage/plugin-gocd@0.1.12-next.1 + - @backstage/plugin-graphiql@0.2.38-next.1 + - @backstage/plugin-home@0.4.22-next.1 + - @backstage/plugin-jenkins@0.7.5-next.1 + - @backstage/plugin-kafka@0.3.6-next.1 + - @backstage/plugin-lighthouse@0.3.6-next.1 + - @backstage/plugin-newrelic@0.3.24-next.1 + - @backstage/plugin-org@0.5.6-next.1 + - @backstage/plugin-pagerduty@0.3.33-next.1 + - @backstage/plugin-rollbar@0.4.6-next.1 + - @backstage/plugin-scaffolder@1.3.0-next.1 + - @backstage/plugin-search@0.8.2-next.1 + - @backstage/plugin-shortcuts@0.2.7-next.1 + - @backstage/plugin-stack-overflow@0.1.2-next.1 + - @backstage/plugin-tech-radar@0.5.13-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.1-next.1 + - @backstage/plugin-techdocs@1.1.2-next.1 + - @backstage/plugin-todo@0.2.8-next.1 + - @backstage/plugin-user-settings@0.4.5-next.1 + - @backstage/catalog-model@1.0.3-next.0 + - @backstage/plugin-catalog@1.2.1-next.1 + - @backstage/plugin-catalog-graph@0.2.18-next.1 + - @backstage/plugin-catalog-react@1.1.1-next.1 + - @backstage/app-defaults@1.0.3-next.1 + - @backstage/plugin-search-react@0.2.1-next.0 + - @backstage/plugin-newrelic-dashboard@0.1.14-next.1 + - @backstage/plugin-permission-react@0.4.2-next.0 + - @backstage/plugin-techdocs-react@1.0.1-next.1 + - @backstage/plugin-catalog-common@1.0.3-next.0 + - @backstage/plugin-search-common@0.3.5-next.0 + +## 0.2.72-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes@0.6.6-next.0 + - @backstage/plugin-cost-insights@0.11.28-next.0 + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/plugin-catalog@1.2.1-next.0 + - @backstage/cli@0.17.2-next.0 + - @backstage/plugin-pagerduty@0.3.33-next.0 + - @backstage/core-components@0.9.5-next.0 + - @backstage/plugin-gcp-projects@0.3.25-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.1-next.0 + - @backstage/plugin-scaffolder@1.3.0-next.0 + - @backstage/plugin-techdocs@1.1.2-next.0 + - @backstage/plugin-techdocs-react@1.0.1-next.0 + - @backstage/plugin-airbrake@0.3.6-next.0 + - @backstage/plugin-api-docs@0.8.6-next.0 + - @backstage/plugin-azure-devops@0.1.22-next.0 + - @backstage/plugin-badges@0.2.30-next.0 + - @backstage/plugin-catalog-graph@0.2.18-next.0 + - @backstage/plugin-catalog-import@0.8.9-next.0 + - @backstage/plugin-circleci@0.3.6-next.0 + - @backstage/plugin-cloudbuild@0.3.6-next.0 + - @backstage/plugin-code-coverage@0.1.33-next.0 + - @backstage/plugin-explore@0.3.37-next.0 + - @backstage/plugin-github-actions@0.5.6-next.0 + - @backstage/plugin-gocd@0.1.12-next.0 + - @backstage/plugin-home@0.4.22-next.0 + - @backstage/plugin-jenkins@0.7.5-next.0 + - @backstage/plugin-kafka@0.3.6-next.0 + - @backstage/plugin-lighthouse@0.3.6-next.0 + - @backstage/plugin-newrelic-dashboard@0.1.14-next.0 + - @backstage/plugin-org@0.5.6-next.0 + - @backstage/plugin-rollbar@0.4.6-next.0 + - @backstage/plugin-search@0.8.2-next.0 + - @backstage/plugin-sentry@0.3.44-next.0 + - @backstage/plugin-tech-insights@0.2.2-next.0 + - @backstage/plugin-todo@0.2.8-next.0 + - @backstage/app-defaults@1.0.3-next.0 + - @backstage/integration-react@1.1.1-next.0 + - @backstage/plugin-apache-airflow@0.1.14-next.0 + - @backstage/plugin-gcalendar@0.3.2-next.0 + - @backstage/plugin-graphiql@0.2.38-next.0 + - @backstage/plugin-newrelic@0.3.24-next.0 + - @backstage/plugin-shortcuts@0.2.7-next.0 + - @backstage/plugin-stack-overflow@0.1.2-next.0 + - @backstage/plugin-tech-radar@0.5.13-next.0 + - @backstage/plugin-user-settings@0.4.5-next.0 + +## 0.2.71 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.4 + - @backstage/plugin-kubernetes@0.6.5 + - @backstage/plugin-scaffolder@1.2.0 + - @backstage/plugin-api-docs@0.8.5 + - @backstage/plugin-tech-insights@0.2.1 + - @backstage/plugin-tech-radar@0.5.12 + - @backstage/plugin-org@0.5.5 + - @backstage/plugin-techdocs@1.1.1 + - @backstage/cli@0.17.1 + - @backstage/core-plugin-api@1.0.2 + - @backstage/plugin-user-settings@0.4.4 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.0 + - @backstage/plugin-home@0.4.21 + - @backstage/plugin-catalog@1.2.0 + - @backstage/plugin-catalog-react@1.1.0 + - @backstage/integration-react@1.1.0 + - @backstage/plugin-techdocs-react@1.0.0 + - @backstage/plugin-newrelic-dashboard@0.1.13 + - @backstage/plugin-graphiql@0.2.37 + - @backstage/config@1.0.1 + - @backstage/plugin-search@0.8.1 + - @backstage/plugin-search-react@0.2.0 + - @backstage/plugin-catalog-graph@0.2.17 + - @backstage/core-app-api@1.0.2 + - @backstage/plugin-gcalendar@0.3.1 + - @backstage/plugin-search-common@0.3.4 + - @backstage/plugin-azure-devops@0.1.21 + - @backstage/app-defaults@1.0.2 + - @backstage/catalog-model@1.0.2 + - @backstage/plugin-airbrake@0.3.5 + - @backstage/plugin-apache-airflow@0.1.13 + - @backstage/plugin-badges@0.2.29 + - @backstage/plugin-catalog-common@1.0.2 + - @backstage/plugin-catalog-import@0.8.8 + - @backstage/plugin-circleci@0.3.5 + - @backstage/plugin-cloudbuild@0.3.5 + - @backstage/plugin-code-coverage@0.1.32 + - @backstage/plugin-cost-insights@0.11.27 + - @backstage/plugin-explore@0.3.36 + - @backstage/plugin-gcp-projects@0.3.24 + - @backstage/plugin-github-actions@0.5.5 + - @backstage/plugin-gocd@0.1.11 + - @backstage/plugin-jenkins@0.7.4 + - @backstage/plugin-kafka@0.3.5 + - @backstage/plugin-lighthouse@0.3.5 + - @backstage/plugin-newrelic@0.3.23 + - @backstage/plugin-pagerduty@0.3.32 + - @backstage/plugin-permission-react@0.4.1 + - @backstage/plugin-rollbar@0.4.5 + - @backstage/plugin-sentry@0.3.43 + - @backstage/plugin-shortcuts@0.2.6 + - @backstage/plugin-stack-overflow@0.1.1 + - @backstage/plugin-todo@0.2.7 + +## 0.2.71-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.4-next.1 + - @backstage/plugin-kubernetes@0.6.5-next.2 + - @backstage/plugin-org@0.5.5-next.2 + - @backstage/plugin-techdocs@1.1.1-next.2 + - @backstage/plugin-techdocs-react@0.1.1-next.2 + - @backstage/plugin-scaffolder@1.2.0-next.2 + - @backstage/plugin-graphiql@0.2.37-next.1 + - @backstage/config@1.0.1-next.0 + - @backstage/plugin-catalog@1.2.0-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@0.1.0-next.2 + - @backstage/cli@0.17.1-next.2 + - @backstage/plugin-search@0.8.1-next.2 + - @backstage/plugin-search-react@0.2.0-next.2 + - @backstage/plugin-search-common@0.3.4-next.0 + - @backstage/plugin-azure-devops@0.1.21-next.2 + - @backstage/plugin-catalog-react@1.1.0-next.2 + - @backstage/catalog-model@1.0.2-next.0 + - @backstage/core-app-api@1.0.2-next.1 + - @backstage/core-plugin-api@1.0.2-next.1 + - @backstage/integration-react@1.1.0-next.2 + - @backstage/plugin-catalog-import@0.8.8-next.2 + - @backstage/plugin-code-coverage@0.1.32-next.2 + - @backstage/plugin-cost-insights@0.11.27-next.1 + - @backstage/plugin-home@0.4.21-next.2 + - @backstage/plugin-lighthouse@0.3.5-next.2 + - @backstage/plugin-permission-react@0.4.1-next.1 + - @backstage/plugin-stack-overflow@0.1.1-next.2 + - @backstage/plugin-airbrake@0.3.5-next.2 + - @backstage/plugin-api-docs@0.8.5-next.2 + - @backstage/plugin-badges@0.2.29-next.2 + - @backstage/plugin-catalog-graph@0.2.17-next.2 + - @backstage/plugin-circleci@0.3.5-next.2 + - @backstage/plugin-cloudbuild@0.3.5-next.2 + - @backstage/plugin-explore@0.3.36-next.2 + - @backstage/plugin-github-actions@0.5.5-next.2 + - @backstage/plugin-gocd@0.1.11-next.2 + - @backstage/plugin-jenkins@0.7.4-next.2 + - @backstage/plugin-kafka@0.3.5-next.2 + - @backstage/plugin-newrelic-dashboard@0.1.13-next.2 + - @backstage/plugin-pagerduty@0.3.32-next.2 + - @backstage/plugin-rollbar@0.4.5-next.2 + - @backstage/plugin-sentry@0.3.43-next.2 + - @backstage/plugin-tech-insights@0.2.1-next.2 + - @backstage/plugin-todo@0.2.7-next.2 + - @backstage/plugin-catalog-common@1.0.2-next.0 + +## 0.2.71-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.4-next.0 + - @backstage/plugin-api-docs@0.8.5-next.1 + - @backstage/plugin-tech-insights@0.2.1-next.1 + - @backstage/plugin-tech-radar@0.5.12-next.0 + - @backstage/cli@0.17.1-next.1 + - @backstage/core-plugin-api@1.0.2-next.0 + - @backstage/plugin-user-settings@0.4.4-next.0 + - @backstage/plugin-scaffolder@1.2.0-next.1 + - @backstage/plugin-catalog@1.2.0-next.1 + - @backstage/plugin-catalog-react@1.1.0-next.1 + - @backstage/plugin-org@0.5.5-next.1 + - @backstage/plugin-graphiql@0.2.37-next.0 + - @backstage/plugin-search-react@0.2.0-next.1 + - @backstage/plugin-techdocs-react@0.1.1-next.1 + - @backstage/plugin-techdocs@1.1.1-next.1 + - @backstage/app-defaults@1.0.2-next.0 + - @backstage/integration-react@1.1.0-next.1 + - @backstage/plugin-airbrake@0.3.5-next.1 + - @backstage/plugin-apache-airflow@0.1.13-next.0 + - @backstage/plugin-azure-devops@0.1.21-next.1 + - @backstage/plugin-badges@0.2.29-next.1 + - @backstage/plugin-catalog-graph@0.2.17-next.1 + - @backstage/plugin-catalog-import@0.8.8-next.1 + - @backstage/plugin-circleci@0.3.5-next.1 + - @backstage/plugin-cloudbuild@0.3.5-next.1 + - @backstage/plugin-code-coverage@0.1.32-next.1 + - @backstage/plugin-cost-insights@0.11.27-next.0 + - @backstage/plugin-explore@0.3.36-next.1 + - @backstage/plugin-gcalendar@0.3.1-next.1 + - @backstage/plugin-gcp-projects@0.3.24-next.0 + - @backstage/plugin-github-actions@0.5.5-next.1 + - @backstage/plugin-gocd@0.1.11-next.1 + - @backstage/plugin-home@0.4.21-next.1 + - @backstage/plugin-jenkins@0.7.4-next.1 + - @backstage/plugin-kafka@0.3.5-next.1 + - @backstage/plugin-kubernetes@0.6.5-next.1 + - @backstage/plugin-lighthouse@0.3.5-next.1 + - @backstage/plugin-newrelic@0.3.23-next.0 + - @backstage/plugin-newrelic-dashboard@0.1.13-next.1 + - @backstage/plugin-pagerduty@0.3.32-next.1 + - @backstage/plugin-rollbar@0.4.5-next.1 + - @backstage/plugin-search@0.8.1-next.1 + - @backstage/plugin-sentry@0.3.43-next.1 + - @backstage/plugin-shortcuts@0.2.6-next.0 + - @backstage/plugin-stack-overflow@0.1.1-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@0.1.0-next.1 + - @backstage/plugin-todo@0.2.7-next.1 + - @backstage/core-app-api@1.0.2-next.0 + - @backstage/plugin-permission-react@0.4.1-next.0 + +## 0.2.71-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.2.0-next.0 + - @backstage/cli@0.17.1-next.0 + - @backstage/plugin-home@0.4.21-next.0 + - @backstage/plugin-api-docs@0.8.5-next.0 + - @backstage/plugin-catalog@1.2.0-next.0 + - @backstage/plugin-catalog-react@1.1.0-next.0 + - @backstage/integration-react@1.1.0-next.0 + - @backstage/plugin-newrelic-dashboard@0.1.13-next.0 + - @backstage/plugin-search@0.8.1-next.0 + - @backstage/plugin-search-react@0.1.1-next.0 + - @backstage/plugin-techdocs@1.1.1-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@0.1.0-next.0 + - @backstage/plugin-techdocs-react@0.1.1-next.0 + - @backstage/plugin-gcalendar@0.3.1-next.0 + - @backstage/plugin-org@0.5.5-next.0 + - @backstage/plugin-catalog-import@0.8.8-next.0 + - @backstage/plugin-github-actions@0.5.5-next.0 + - @backstage/plugin-airbrake@0.3.5-next.0 + - @backstage/plugin-azure-devops@0.1.21-next.0 + - @backstage/plugin-badges@0.2.29-next.0 + - @backstage/plugin-catalog-graph@0.2.17-next.0 + - @backstage/plugin-circleci@0.3.5-next.0 + - @backstage/plugin-cloudbuild@0.3.5-next.0 + - @backstage/plugin-code-coverage@0.1.32-next.0 + - @backstage/plugin-explore@0.3.36-next.0 + - @backstage/plugin-gocd@0.1.11-next.0 + - @backstage/plugin-jenkins@0.7.4-next.0 + - @backstage/plugin-kafka@0.3.5-next.0 + - @backstage/plugin-kubernetes@0.6.5-next.0 + - @backstage/plugin-lighthouse@0.3.5-next.0 + - @backstage/plugin-pagerduty@0.3.32-next.0 + - @backstage/plugin-rollbar@0.4.5-next.0 + - @backstage/plugin-sentry@0.3.43-next.0 + - @backstage/plugin-stack-overflow@0.1.1-next.0 + - @backstage/plugin-tech-insights@0.2.1-next.0 + - @backstage/plugin-todo@0.2.7-next.0 + +## 0.2.70 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-react@0.4.0 + - @backstage/plugin-home@0.4.20 + - @backstage/plugin-techdocs@1.1.0 + - @backstage/plugin-catalog-react@1.0.1 + - @backstage/cli@0.17.0 + - @backstage/plugin-scaffolder@1.1.0 + - @backstage/catalog-model@1.0.1 + - @backstage/plugin-kubernetes@0.6.4 + - @backstage/core-app-api@1.0.1 + - @backstage/core-components@0.9.3 + - @backstage/core-plugin-api@1.0.1 + - @backstage/plugin-catalog-graph@0.2.16 + - @backstage/plugin-catalog-import@0.8.7 + - @backstage/plugin-kafka@0.3.4 + - @backstage/plugin-lighthouse@0.3.4 + - @backstage/plugin-rollbar@0.4.4 + - @backstage/plugin-search-react@0.1.0 + - @backstage/plugin-search@0.8.0 + - @backstage/integration-react@1.0.1 + - @backstage/plugin-airbrake@0.3.4 + - @backstage/plugin-apache-airflow@0.1.12 + - @backstage/plugin-api-docs@0.8.4 + - @backstage/plugin-azure-devops@0.1.20 + - @backstage/plugin-badges@0.2.28 + - @backstage/plugin-catalog@1.1.0 + - @backstage/plugin-circleci@0.3.4 + - @backstage/plugin-cloudbuild@0.3.4 + - @backstage/plugin-code-coverage@0.1.31 + - @backstage/plugin-cost-insights@0.11.26 + - @backstage/plugin-explore@0.3.35 + - @backstage/plugin-gcalendar@0.3.0 + - @backstage/plugin-gcp-projects@0.3.23 + - @backstage/plugin-github-actions@0.5.4 + - @backstage/plugin-gocd@0.1.10 + - @backstage/plugin-graphiql@0.2.36 + - @backstage/plugin-jenkins@0.7.3 + - @backstage/plugin-newrelic@0.3.22 + - @backstage/plugin-org@0.5.4 + - @backstage/plugin-pagerduty@0.3.31 + - @backstage/plugin-sentry@0.3.42 + - @backstage/plugin-shortcuts@0.2.5 + - @backstage/plugin-stack-overflow@0.1.0 + - @backstage/plugin-tech-insights@0.2.0 + - @backstage/plugin-tech-radar@0.5.11 + - @backstage/plugin-todo@0.2.6 + - @backstage/plugin-user-settings@0.4.3 + - @backstage/plugin-search-common@0.3.3 + - @backstage/plugin-catalog-common@1.0.1 + - @backstage/app-defaults@1.0.1 + - @backstage/plugin-newrelic-dashboard@0.1.12 + +## 0.2.70-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-home@0.4.20-next.3 + - @backstage/plugin-techdocs@1.1.0-next.3 + - @backstage/cli@0.17.0-next.3 + - @backstage/plugin-scaffolder@1.1.0-next.3 + - @backstage/plugin-kubernetes@0.6.4-next.2 + - @backstage/core-app-api@1.0.1-next.1 + - @backstage/core-components@0.9.3-next.2 + - @backstage/core-plugin-api@1.0.1-next.0 + - @backstage/integration-react@1.0.1-next.2 + - @backstage/plugin-airbrake@0.3.4-next.1 + - @backstage/plugin-apache-airflow@0.1.12-next.1 + - @backstage/plugin-api-docs@0.8.4-next.3 + - @backstage/plugin-azure-devops@0.1.20-next.1 + - @backstage/plugin-badges@0.2.28-next.1 + - @backstage/plugin-catalog-graph@0.2.16-next.3 + - @backstage/plugin-catalog-import@0.8.7-next.2 + - @backstage/plugin-catalog-react@1.0.1-next.3 + - @backstage/plugin-catalog@1.1.0-next.3 + - @backstage/plugin-circleci@0.3.4-next.1 + - @backstage/plugin-cloudbuild@0.3.4-next.1 + - @backstage/plugin-code-coverage@0.1.31-next.1 + - @backstage/plugin-cost-insights@0.11.26-next.1 + - @backstage/plugin-explore@0.3.35-next.1 + - @backstage/plugin-gcalendar@0.3.0-next.2 + - @backstage/plugin-gcp-projects@0.3.23-next.1 + - @backstage/plugin-github-actions@0.5.4-next.2 + - @backstage/plugin-gocd@0.1.10-next.2 + - @backstage/plugin-graphiql@0.2.36-next.2 + - @backstage/plugin-jenkins@0.7.3-next.1 + - @backstage/plugin-kafka@0.3.4-next.1 + - @backstage/plugin-lighthouse@0.3.4-next.1 + - @backstage/plugin-newrelic@0.3.22-next.1 + - @backstage/plugin-org@0.5.4-next.3 + - @backstage/plugin-pagerduty@0.3.31-next.1 + - @backstage/plugin-rollbar@0.4.4-next.1 + - @backstage/plugin-search@0.7.5-next.1 + - @backstage/plugin-sentry@0.3.42-next.2 + - @backstage/plugin-shortcuts@0.2.5-next.1 + - @backstage/plugin-stack-overflow@0.1.0-next.1 + - @backstage/plugin-tech-insights@0.2.0-next.1 + - @backstage/plugin-tech-radar@0.5.11-next.2 + - @backstage/plugin-todo@0.2.6-next.1 + - @backstage/plugin-user-settings@0.4.3-next.1 + - @backstage/app-defaults@1.0.1-next.2 + - @backstage/plugin-search-react@0.1.0-next.0 + - @backstage/plugin-permission-react@0.4.0-next.1 + - @backstage/plugin-newrelic-dashboard@0.1.12-next.1 + +## 0.2.70-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-react@0.4.0-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.1 + - @backstage/cli@0.17.0-next.1 + - @backstage/plugin-home@0.4.20-next.1 + - @backstage/plugin-kubernetes@0.6.4-next.1 + - @backstage/plugin-catalog-common@1.0.1-next.1 + - @backstage/plugin-org@0.5.4-next.1 + - @backstage/plugin-catalog-graph@0.2.16-next.1 + - @backstage/plugin-tech-radar@0.5.11-next.1 + - @backstage/plugin-catalog@1.1.0-next.1 + - @backstage/plugin-gcalendar@0.3.0-next.1 + - @backstage/plugin-techdocs@1.0.1-next.1 + - @backstage/plugin-scaffolder@1.0.1-next.1 + - @backstage/integration-react@1.0.1-next.1 + - @backstage/plugin-catalog-import@0.8.7-next.1 + - @backstage/plugin-github-actions@0.5.4-next.1 + - @backstage/app-defaults@1.0.1-next.1 + - @backstage/plugin-search-common@0.3.3-next.1 + - @backstage/plugin-api-docs@0.8.4-next.1 + +## 0.2.70-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog@1.0.1-next.0 + - @backstage/plugin-user-settings@0.4.3-next.0 + - @backstage/cli@0.16.1-next.0 + - @backstage/plugin-search-common@0.3.3-next.0 + - @backstage/core-app-api@1.0.1-next.0 + - @backstage/plugin-search@0.7.5-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/plugin-scaffolder@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + - @backstage/plugin-techdocs@1.0.1-next.0 + - @backstage/plugin-tech-insights@0.1.14-next.0 + - @backstage/plugin-kubernetes@0.6.4-next.0 + - @backstage/plugin-airbrake@0.3.4-next.0 + - @backstage/plugin-api-docs@0.8.4-next.0 + - @backstage/plugin-azure-devops@0.1.20-next.0 + - @backstage/plugin-badges@0.2.28-next.0 + - @backstage/plugin-catalog-graph@0.2.16-next.0 + - @backstage/plugin-catalog-import@0.8.7-next.0 + - @backstage/plugin-circleci@0.3.4-next.0 + - @backstage/plugin-cloudbuild@0.3.4-next.0 + - @backstage/plugin-code-coverage@0.1.31-next.0 + - @backstage/plugin-cost-insights@0.11.26-next.0 + - @backstage/plugin-explore@0.3.35-next.0 + - @backstage/plugin-github-actions@0.5.4-next.0 + - @backstage/plugin-gocd@0.1.10-next.0 + - @backstage/plugin-home@0.4.20-next.0 + - @backstage/plugin-jenkins@0.7.3-next.0 + - @backstage/plugin-kafka@0.3.4-next.0 + - @backstage/plugin-lighthouse@0.3.4-next.0 + - @backstage/plugin-newrelic-dashboard@0.1.12-next.0 + - @backstage/plugin-org@0.5.4-next.0 + - @backstage/plugin-pagerduty@0.3.31-next.0 + - @backstage/plugin-rollbar@0.4.4-next.0 + - @backstage/plugin-sentry@0.3.42-next.0 + - @backstage/plugin-todo@0.2.6-next.0 + - @backstage/app-defaults@1.0.1-next.0 + - @backstage/integration-react@1.0.1-next.0 + - @backstage/plugin-apache-airflow@0.1.12-next.0 + - @backstage/plugin-catalog-common@1.0.1-next.0 + - @backstage/plugin-gcalendar@0.2.1-next.0 + - @backstage/plugin-gcp-projects@0.3.23-next.0 + - @backstage/plugin-graphiql@0.2.36-next.0 + - @backstage/plugin-newrelic@0.3.22-next.0 + - @backstage/plugin-shortcuts@0.2.5-next.0 + - @backstage/plugin-tech-radar@0.5.11-next.0 + +## 0.2.69 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.16.0 + - @backstage/plugin-gcalendar@0.2.0 + - @backstage/plugin-scaffolder@1.0.0 + - @backstage/plugin-airbrake@0.3.3 + - @backstage/plugin-kafka@0.3.3 + - @backstage/plugin-todo@0.2.5 + - @backstage/app-defaults@1.0.0 + - @backstage/core-app-api@1.0.0 + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/integration-react@1.0.0 + - @backstage/plugin-apache-airflow@0.1.11 + - @backstage/plugin-api-docs@0.8.3 + - @backstage/plugin-azure-devops@0.1.19 + - @backstage/plugin-badges@0.2.27 + - @backstage/plugin-catalog-graph@0.2.15 + - @backstage/plugin-catalog-import@0.8.6 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/plugin-catalog@1.0.0 + - @backstage/plugin-circleci@0.3.3 + - @backstage/plugin-cloudbuild@0.3.3 + - @backstage/plugin-code-coverage@0.1.30 + - @backstage/plugin-cost-insights@0.11.25 + - @backstage/plugin-explore@0.3.34 + - @backstage/plugin-gcp-projects@0.3.22 + - @backstage/plugin-github-actions@0.5.3 + - @backstage/plugin-gocd@0.1.9 + - @backstage/plugin-graphiql@0.2.35 + - @backstage/plugin-home@0.4.19 + - @backstage/plugin-jenkins@0.7.2 + - @backstage/plugin-kubernetes@0.6.3 + - @backstage/plugin-lighthouse@0.3.3 + - @backstage/plugin-newrelic@0.3.21 + - @backstage/plugin-org@0.5.3 + - @backstage/plugin-pagerduty@0.3.30 + - @backstage/plugin-permission-react@0.3.4 + - @backstage/plugin-rollbar@0.4.3 + - @backstage/plugin-search@0.7.4 + - @backstage/plugin-sentry@0.3.41 + - @backstage/plugin-shortcuts@0.2.4 + - @backstage/plugin-tech-insights@0.1.13 + - @backstage/plugin-tech-radar@0.5.10 + - @backstage/plugin-techdocs@1.0.0 + - @backstage/plugin-user-settings@0.4.2 + - @backstage/catalog-model@1.0.0 + - @backstage/plugin-catalog-common@1.0.0 + - @backstage/plugin-newrelic-dashboard@0.1.11 + - @backstage/plugin-search-common@0.3.2 + +## 0.2.68 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.15.2 + - @backstage/plugin-catalog@0.10.0 + - @backstage/plugin-catalog-react@0.9.0 + - @backstage/core-components@0.9.1 + - @backstage/plugin-gcp-projects@0.3.21 + - @backstage/plugin-scaffolder@0.15.0 + - @backstage/catalog-model@0.13.0 + - @backstage/plugin-airbrake@0.3.2 + - @backstage/plugin-rollbar@0.4.2 + - @backstage/plugin-catalog-graph@0.2.14 + - @backstage/plugin-catalog-import@0.8.5 + - @backstage/plugin-explore@0.3.33 + - @backstage/plugin-catalog-common@0.2.2 + - @backstage/plugin-search-common@0.3.1 + - @backstage/plugin-search@0.7.3 + - @backstage/plugin-tech-radar@0.5.9 + - @backstage/plugin-org@0.5.2 + - @backstage/plugin-techdocs@0.15.1 + - @backstage/app-defaults@0.2.1 + - @backstage/integration-react@0.1.25 + - @backstage/plugin-apache-airflow@0.1.10 + - @backstage/plugin-api-docs@0.8.2 + - @backstage/plugin-azure-devops@0.1.18 + - @backstage/plugin-badges@0.2.26 + - @backstage/plugin-circleci@0.3.2 + - @backstage/plugin-cloudbuild@0.3.2 + - @backstage/plugin-code-coverage@0.1.29 + - @backstage/plugin-cost-insights@0.11.24 + - @backstage/plugin-github-actions@0.5.2 + - @backstage/plugin-gocd@0.1.8 + - @backstage/plugin-graphiql@0.2.34 + - @backstage/plugin-home@0.4.18 + - @backstage/plugin-jenkins@0.7.1 + - @backstage/plugin-kafka@0.3.2 + - @backstage/plugin-kubernetes@0.6.2 + - @backstage/plugin-lighthouse@0.3.2 + - @backstage/plugin-newrelic@0.3.20 + - @backstage/plugin-newrelic-dashboard@0.1.10 + - @backstage/plugin-pagerduty@0.3.29 + - @backstage/plugin-sentry@0.3.40 + - @backstage/plugin-shortcuts@0.2.3 + - @backstage/plugin-tech-insights@0.1.12 + - @backstage/plugin-todo@0.2.4 + - @backstage/plugin-user-settings@0.4.1 + +## 0.2.68-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.15.2-next.0 + - @backstage/plugin-catalog@0.10.0-next.0 + - @backstage/plugin-catalog-react@0.9.0-next.0 + - @backstage/core-components@0.9.1-next.0 + - @backstage/plugin-gcp-projects@0.3.21-next.0 + - @backstage/plugin-scaffolder@0.15.0-next.0 + - @backstage/catalog-model@0.13.0-next.0 + - @backstage/plugin-rollbar@0.4.2-next.0 + - @backstage/plugin-catalog-graph@0.2.14-next.0 + - @backstage/plugin-catalog-import@0.8.5-next.0 + - @backstage/plugin-explore@0.3.33-next.0 + - @backstage/plugin-catalog-common@0.2.2-next.0 + - @backstage/plugin-search-common@0.3.1-next.0 + - @backstage/plugin-search@0.7.3-next.0 + - @backstage/plugin-tech-radar@0.5.9-next.0 + - @backstage/plugin-org@0.5.2-next.0 + - @backstage/plugin-techdocs@0.15.1-next.0 + - @backstage/app-defaults@0.2.1-next.0 + - @backstage/integration-react@0.1.25-next.0 + - @backstage/plugin-airbrake@0.3.2-next.0 + - @backstage/plugin-apache-airflow@0.1.10-next.0 + - @backstage/plugin-api-docs@0.8.2-next.0 + - @backstage/plugin-azure-devops@0.1.18-next.0 + - @backstage/plugin-badges@0.2.26-next.0 + - @backstage/plugin-circleci@0.3.2-next.0 + - @backstage/plugin-cloudbuild@0.3.2-next.0 + - @backstage/plugin-code-coverage@0.1.29-next.0 + - @backstage/plugin-cost-insights@0.11.24-next.0 + - @backstage/plugin-github-actions@0.5.2-next.0 + - @backstage/plugin-gocd@0.1.8-next.0 + - @backstage/plugin-graphiql@0.2.34-next.0 + - @backstage/plugin-home@0.4.18-next.0 + - @backstage/plugin-jenkins@0.7.1-next.0 + - @backstage/plugin-kafka@0.3.2-next.0 + - @backstage/plugin-kubernetes@0.6.2-next.0 + - @backstage/plugin-lighthouse@0.3.2-next.0 + - @backstage/plugin-newrelic@0.3.20-next.0 + - @backstage/plugin-newrelic-dashboard@0.1.10-next.0 + - @backstage/plugin-pagerduty@0.3.29-next.0 + - @backstage/plugin-sentry@0.3.40-next.0 + - @backstage/plugin-shortcuts@0.2.3-next.0 + - @backstage/plugin-tech-insights@0.1.12-next.0 + - @backstage/plugin-todo@0.2.4-next.0 + - @backstage/plugin-user-settings@0.4.1-next.0 + +## 0.2.67 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@0.12.0 + - @backstage/core-components@0.9.0 + - @backstage/plugin-search@0.7.2 + - @backstage/plugin-techdocs@0.15.0 + - @backstage/plugin-api-docs@0.8.1 + - @backstage/plugin-catalog@0.9.1 + - @backstage/plugin-catalog-graph@0.2.13 + - @backstage/plugin-catalog-import@0.8.4 + - @backstage/plugin-catalog-react@0.8.0 + - @backstage/plugin-explore@0.3.32 + - @backstage/plugin-rollbar@0.4.1 + - @backstage/plugin-catalog-common@0.2.0 + - @backstage/plugin-org@0.5.1 + - @backstage/plugin-scaffolder@0.14.0 + - @backstage/core-app-api@0.6.0 + - @backstage/core-plugin-api@0.8.0 + - @backstage/cli@0.15.0 + - @backstage/app-defaults@0.2.0 + - @backstage/plugin-user-settings@0.4.0 + - @backstage/plugin-airbrake@0.3.1 + - @backstage/search-common@0.3.0 + - @backstage/plugin-jenkins@0.7.0 + - @backstage/plugin-code-coverage@0.1.28 + - @backstage/plugin-tech-insights@0.1.11 + - @backstage/plugin-azure-devops@0.1.17 + - @backstage/plugin-badges@0.2.25 + - @backstage/plugin-circleci@0.3.1 + - @backstage/plugin-cloudbuild@0.3.1 + - @backstage/plugin-cost-insights@0.11.23 + - @backstage/plugin-github-actions@0.5.1 + - @backstage/plugin-gocd@0.1.7 + - @backstage/plugin-home@0.4.17 + - @backstage/plugin-kafka@0.3.1 + - @backstage/plugin-kubernetes@0.6.1 + - @backstage/plugin-lighthouse@0.3.1 + - @backstage/plugin-newrelic-dashboard@0.1.9 + - @backstage/plugin-pagerduty@0.3.28 + - @backstage/plugin-sentry@0.3.39 + - @backstage/plugin-todo@0.2.3 + - @backstage/integration-react@0.1.24 + - @backstage/plugin-apache-airflow@0.1.9 + - @backstage/plugin-gcp-projects@0.3.20 + - @backstage/plugin-graphiql@0.2.33 + - @backstage/plugin-newrelic@0.3.19 + - @backstage/plugin-shortcuts@0.2.2 + - @backstage/plugin-tech-radar@0.5.8 + - @backstage/plugin-permission-react@0.3.3 + +## 0.2.66 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@0.9.0 + - @backstage/cli@0.14.1 + - @backstage/core-components@0.8.10 + - @backstage/plugin-api-docs@0.8.0 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/plugin-catalog-graph@0.2.12 + - @backstage/plugin-jenkins@0.6.0 + - @backstage/plugin-scaffolder@0.13.0 + - @backstage/plugin-techdocs@0.14.0 + - @backstage/plugin-airbrake@0.3.0 + - @backstage/catalog-model@0.11.0 + - @backstage/plugin-catalog-import@0.8.3 + - @backstage/plugin-explore@0.3.31 + - @backstage/core-plugin-api@0.7.0 + - @backstage/plugin-circleci@0.3.0 + - @backstage/plugin-cloudbuild@0.3.0 + - @backstage/plugin-github-actions@0.5.0 + - @backstage/plugin-kafka@0.3.0 + - @backstage/plugin-kubernetes@0.6.0 + - @backstage/plugin-lighthouse@0.3.0 + - @backstage/plugin-org@0.5.0 + - @backstage/plugin-rollbar@0.4.0 + - @backstage/plugin-gocd@0.1.6 + - @backstage/plugin-azure-devops@0.1.16 + - @backstage/plugin-badges@0.2.24 + - @backstage/plugin-code-coverage@0.1.27 + - @backstage/plugin-home@0.4.16 + - @backstage/plugin-newrelic-dashboard@0.1.8 + - @backstage/plugin-pagerduty@0.3.27 + - @backstage/plugin-search@0.7.1 + - @backstage/plugin-sentry@0.3.38 + - @backstage/plugin-tech-insights@0.1.10 + - @backstage/plugin-todo@0.2.2 + - @backstage/plugin-cost-insights@0.11.22 + - @backstage/app-defaults@0.1.9 + - @backstage/core-app-api@0.5.4 + - @backstage/integration-react@0.1.23 + - @backstage/plugin-apache-airflow@0.1.8 + - @backstage/plugin-gcp-projects@0.3.19 + - @backstage/plugin-graphiql@0.2.32 + - @backstage/plugin-newrelic@0.3.18 + - @backstage/plugin-permission-react@0.3.2 + - @backstage/plugin-shortcuts@0.2.1 + - @backstage/plugin-tech-radar@0.5.7 + - @backstage/plugin-user-settings@0.3.21 + +## 0.2.65 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.14.0 + - @backstage/core-app-api@0.5.3 + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/integration-react@0.1.22 + - @backstage/plugin-airbrake@0.2.0 + - @backstage/plugin-apache-airflow@0.1.7 + - @backstage/plugin-api-docs@0.7.3 + - @backstage/plugin-azure-devops@0.1.15 + - @backstage/plugin-badges@0.2.23 + - @backstage/plugin-catalog@0.8.0 + - @backstage/plugin-catalog-import@0.8.2 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/plugin-circleci@0.2.38 + - @backstage/plugin-cloudbuild@0.2.36 + - @backstage/plugin-code-coverage@0.1.26 + - @backstage/plugin-cost-insights@0.11.21 + - @backstage/plugin-explore@0.3.30 + - @backstage/plugin-gcp-projects@0.3.18 + - @backstage/plugin-github-actions@0.4.36 + - @backstage/plugin-gocd@0.1.5 + - @backstage/plugin-graphiql@0.2.31 + - @backstage/plugin-home@0.4.15 + - @backstage/plugin-jenkins@0.5.21 + - @backstage/plugin-kafka@0.2.29 + - @backstage/plugin-kubernetes@0.5.8 + - @backstage/plugin-lighthouse@0.2.38 + - @backstage/plugin-newrelic@0.3.17 + - @backstage/plugin-newrelic-dashboard@0.1.7 + - @backstage/plugin-org@0.4.3 + - @backstage/plugin-pagerduty@0.3.26 + - @backstage/plugin-permission-react@0.3.1 + - @backstage/plugin-rollbar@0.3.27 + - @backstage/plugin-scaffolder@0.12.3 + - @backstage/plugin-search@0.7.0 + - @backstage/plugin-sentry@0.3.37 + - @backstage/plugin-shortcuts@0.2.0 + - @backstage/plugin-tech-insights@0.1.9 + - @backstage/plugin-tech-radar@0.5.6 + - @backstage/plugin-techdocs@0.13.4 + - @backstage/plugin-todo@0.2.1 + - @backstage/plugin-user-settings@0.3.20 + - @backstage/app-defaults@0.1.8 + - @backstage/catalog-model@0.10.0 + - @backstage/search-common@0.2.3 + - @backstage/theme@0.2.15 + - @backstage/plugin-catalog-common@0.1.3 + - @backstage/plugin-catalog-graph@0.2.11 + +## 0.2.64 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.13.2 + - @backstage/plugin-todo@0.2.0 + - @backstage/plugin-newrelic-dashboard@0.1.6 + - @backstage/core-components@0.8.8 + - @backstage/plugin-scaffolder@0.12.2 + - @backstage/plugin-search@0.6.2 + - @backstage/plugin-catalog-react@0.6.14 + - @backstage/plugin-catalog@0.7.12 + - @backstage/plugin-catalog-graph@0.2.10 + - @backstage/plugin-catalog-import@0.8.1 + - @backstage/plugin-home@0.4.14 + - @backstage/app-defaults@0.1.7 + - @backstage/integration-react@0.1.21 + - @backstage/plugin-airbrake@0.1.3 + - @backstage/plugin-apache-airflow@0.1.6 + - @backstage/plugin-api-docs@0.7.2 + - @backstage/plugin-azure-devops@0.1.14 + - @backstage/plugin-badges@0.2.22 + - @backstage/plugin-circleci@0.2.37 + - @backstage/plugin-cloudbuild@0.2.35 + - @backstage/plugin-code-coverage@0.1.25 + - @backstage/plugin-cost-insights@0.11.20 + - @backstage/plugin-explore@0.3.29 + - @backstage/plugin-gcp-projects@0.3.17 + - @backstage/plugin-github-actions@0.4.35 + - @backstage/plugin-gocd@0.1.4 + - @backstage/plugin-graphiql@0.2.30 + - @backstage/plugin-jenkins@0.5.20 + - @backstage/plugin-kafka@0.2.28 + - @backstage/plugin-kubernetes@0.5.7 + - @backstage/plugin-lighthouse@0.2.37 + - @backstage/plugin-newrelic@0.3.16 + - @backstage/plugin-pagerduty@0.3.25 + - @backstage/plugin-rollbar@0.3.26 + - @backstage/plugin-sentry@0.3.36 + - @backstage/plugin-shortcuts@0.1.22 + - @backstage/plugin-tech-insights@0.1.8 + - @backstage/plugin-tech-radar@0.5.5 + - @backstage/plugin-techdocs@0.13.3 + - @backstage/plugin-user-settings@0.3.19 + +## 0.2.64-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.13.2-next.0 + - @backstage/plugin-todo@0.2.0-next.0 + - @backstage/plugin-newrelic-dashboard@0.1.6-next.0 + - @backstage/core-components@0.8.8-next.0 + - @backstage/plugin-scaffolder@0.12.2-next.0 + - @backstage/plugin-search@0.6.2-next.0 + - @backstage/plugin-catalog-react@0.6.14-next.0 + - @backstage/plugin-catalog-graph@0.2.10-next.0 + - @backstage/plugin-catalog-import@0.8.1-next.0 + - @backstage/plugin-home@0.4.14-next.0 + - @backstage/app-defaults@0.1.7-next.0 + - @backstage/integration-react@0.1.21-next.0 + - @backstage/plugin-airbrake@0.1.3-next.0 + - @backstage/plugin-apache-airflow@0.1.6-next.0 + - @backstage/plugin-api-docs@0.7.2-next.0 + - @backstage/plugin-azure-devops@0.1.14-next.0 + - @backstage/plugin-badges@0.2.22-next.0 + - @backstage/plugin-catalog@0.7.12-next.0 + - @backstage/plugin-circleci@0.2.37-next.0 + - @backstage/plugin-cloudbuild@0.2.35-next.0 + - @backstage/plugin-code-coverage@0.1.25-next.0 + - @backstage/plugin-cost-insights@0.11.20-next.0 + - @backstage/plugin-explore@0.3.29-next.0 + - @backstage/plugin-gcp-projects@0.3.17-next.0 + - @backstage/plugin-github-actions@0.4.35-next.0 + - @backstage/plugin-gocd@0.1.4-next.0 + - @backstage/plugin-graphiql@0.2.30-next.0 + - @backstage/plugin-jenkins@0.5.20-next.0 + - @backstage/plugin-kafka@0.2.28-next.0 + - @backstage/plugin-kubernetes@0.5.7-next.0 + - @backstage/plugin-lighthouse@0.2.37-next.0 + - @backstage/plugin-newrelic@0.3.16-next.0 + - @backstage/plugin-org@0.4.2-next.0 + - @backstage/plugin-pagerduty@0.3.25-next.0 + - @backstage/plugin-rollbar@0.3.26-next.0 + - @backstage/plugin-sentry@0.3.36-next.0 + - @backstage/plugin-shortcuts@0.1.22-next.0 + - @backstage/plugin-tech-insights@0.1.8-next.0 + - @backstage/plugin-tech-radar@0.5.5-next.0 + - @backstage/plugin-techdocs@0.13.3-next.0 + - @backstage/plugin-user-settings@0.3.19-next.0 + +## 0.2.63 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/plugin-catalog-react@0.6.13 + - @backstage/plugin-code-coverage@0.1.24 + - @backstage/plugin-catalog-common@0.1.2 + - @backstage/cli@0.13.1 + - @backstage/plugin-scaffolder@0.12.1 + - @backstage/integration-react@0.1.20 + - @backstage/plugin-org@0.4.1 + - @backstage/plugin-pagerduty@0.3.24 + - @backstage/plugin-catalog-import@0.8.0 + - @backstage/core-app-api@0.5.2 + - @backstage/plugin-techdocs@0.13.2 + - @backstage/app-defaults@0.1.6 + - @backstage/plugin-airbrake@0.1.2 + - @backstage/plugin-apache-airflow@0.1.5 + - @backstage/plugin-api-docs@0.7.1 + - @backstage/plugin-azure-devops@0.1.13 + - @backstage/plugin-badges@0.2.21 + - @backstage/plugin-catalog@0.7.11 + - @backstage/plugin-catalog-graph@0.2.9 + - @backstage/plugin-circleci@0.2.36 + - @backstage/plugin-cloudbuild@0.2.34 + - @backstage/plugin-cost-insights@0.11.19 + - @backstage/plugin-explore@0.3.28 + - @backstage/plugin-gcp-projects@0.3.16 + - @backstage/plugin-github-actions@0.4.34 + - @backstage/plugin-gocd@0.1.3 + - @backstage/plugin-graphiql@0.2.29 + - @backstage/plugin-home@0.4.13 + - @backstage/plugin-jenkins@0.5.19 + - @backstage/plugin-kafka@0.2.27 + - @backstage/plugin-kubernetes@0.5.6 + - @backstage/plugin-lighthouse@0.2.36 + - @backstage/plugin-newrelic@0.3.15 + - @backstage/plugin-newrelic-dashboard@0.1.5 + - @backstage/plugin-rollbar@0.3.25 + - @backstage/plugin-search@0.6.1 + - @backstage/plugin-sentry@0.3.35 + - @backstage/plugin-shortcuts@0.1.21 + - @backstage/plugin-tech-insights@0.1.7 + - @backstage/plugin-tech-radar@0.5.4 + - @backstage/plugin-todo@0.1.21 + - @backstage/plugin-user-settings@0.3.18 + +## 0.2.63-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.1 + - @backstage/plugin-catalog-react@0.6.13-next.1 + - @backstage/plugin-code-coverage@0.1.24-next.1 + - @backstage/plugin-catalog-common@0.1.2-next.0 + - @backstage/cli@0.13.1-next.1 + - @backstage/plugin-scaffolder@0.12.1-next.1 + - @backstage/core-app-api@0.5.2-next.0 + - @backstage/plugin-techdocs@0.13.2-next.1 + - @backstage/plugin-catalog@0.7.11-next.1 + - @backstage/app-defaults@0.1.6-next.1 + +## 0.2.63-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + - @backstage/plugin-org@0.4.1-next.0 + - @backstage/plugin-pagerduty@0.3.24-next.0 + - @backstage/plugin-catalog-import@0.8.0-next.0 + - @backstage/plugin-techdocs@0.13.2-next.0 + - @backstage/plugin-scaffolder@0.12.1-next.0 + - @backstage/cli@0.13.1-next.0 + - @backstage/app-defaults@0.1.6-next.0 + - @backstage/integration-react@0.1.20-next.0 + - @backstage/plugin-airbrake@0.1.2-next.0 + - @backstage/plugin-apache-airflow@0.1.5-next.0 + - @backstage/plugin-api-docs@0.7.1-next.0 + - @backstage/plugin-azure-devops@0.1.13-next.0 + - @backstage/plugin-badges@0.2.21-next.0 + - @backstage/plugin-catalog@0.7.11-next.0 + - @backstage/plugin-catalog-graph@0.2.9-next.0 + - @backstage/plugin-catalog-react@0.6.13-next.0 + - @backstage/plugin-circleci@0.2.36-next.0 + - @backstage/plugin-cloudbuild@0.2.34-next.0 + - @backstage/plugin-code-coverage@0.1.24-next.0 + - @backstage/plugin-cost-insights@0.11.19-next.0 + - @backstage/plugin-explore@0.3.28-next.0 + - @backstage/plugin-gcp-projects@0.3.16-next.0 + - @backstage/plugin-github-actions@0.4.34-next.0 + - @backstage/plugin-gocd@0.1.3-next.0 + - @backstage/plugin-graphiql@0.2.29-next.0 + - @backstage/plugin-home@0.4.13-next.0 + - @backstage/plugin-jenkins@0.5.19-next.0 + - @backstage/plugin-kafka@0.2.27-next.0 + - @backstage/plugin-kubernetes@0.5.6-next.0 + - @backstage/plugin-lighthouse@0.2.36-next.0 + - @backstage/plugin-newrelic@0.3.15-next.0 + - @backstage/plugin-newrelic-dashboard@0.1.5-next.0 + - @backstage/plugin-rollbar@0.3.25-next.0 + - @backstage/plugin-search@0.6.1-next.0 + - @backstage/plugin-sentry@0.3.35-next.0 + - @backstage/plugin-shortcuts@0.1.21-next.0 + - @backstage/plugin-tech-insights@0.1.7-next.0 + - @backstage/plugin-tech-radar@0.5.4-next.0 + - @backstage/plugin-todo@0.1.21-next.0 + - @backstage/plugin-user-settings@0.3.18-next.0 + +## 0.2.62 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.6 + - @backstage/plugin-catalog-graph@0.2.8 + - @backstage/cli@0.13.0 + - @backstage/plugin-techdocs@0.13.1 + - @backstage/plugin-search@0.6.0 + - @backstage/plugin-explore@0.3.27 + - @backstage/search-common@0.2.2 + - @backstage/plugin-azure-devops@0.1.12 + - @backstage/plugin-catalog@0.7.10 + - @backstage/plugin-github-actions@0.4.33 + - @backstage/plugin-gcp-projects@0.3.15 + - @backstage/core-app-api@0.5.1 + - @backstage/plugin-home@0.4.12 + +## 0.2.61 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-tech-radar@0.5.3 + - @backstage/core-components@0.8.5 + - @backstage/plugin-api-docs@0.7.0 + - @backstage/plugin-kubernetes@0.5.5 + - @backstage/cli@0.12.0 + - @backstage/plugin-search@0.5.6 + - @backstage/core-plugin-api@0.6.0 + - @backstage/plugin-org@0.4.0 + - @backstage/plugin-catalog@0.7.9 + - @backstage/plugin-cost-insights@0.11.18 + - @backstage/plugin-shortcuts@0.1.20 + - @backstage/plugin-user-settings@0.3.17 + - @backstage/core-app-api@0.5.0 + - @backstage/plugin-catalog-react@0.6.12 + - @backstage/plugin-scaffolder@0.12.0 + - @backstage/plugin-azure-devops@0.1.11 + - @backstage/plugin-badges@0.2.20 + - @backstage/plugin-catalog-import@0.7.10 + - @backstage/plugin-kafka@0.2.26 + - @backstage/plugin-pagerduty@0.3.23 + - @backstage/plugin-rollbar@0.3.24 + - @backstage/plugin-sentry@0.3.34 + - @backstage/plugin-tech-insights@0.1.6 + - @backstage/plugin-techdocs@0.13.0 + - @backstage/plugin-todo@0.1.20 + - @backstage/plugin-newrelic-dashboard@0.1.4 + - @backstage/catalog-model@0.9.10 + - @backstage/app-defaults@0.1.5 + - @backstage/integration-react@0.1.19 + - @backstage/plugin-airbrake@0.1.1 + - @backstage/plugin-apache-airflow@0.1.4 + - @backstage/plugin-catalog-graph@0.2.7 + - @backstage/plugin-circleci@0.2.35 + - @backstage/plugin-cloudbuild@0.2.33 + - @backstage/plugin-code-coverage@0.1.23 + - @backstage/plugin-explore@0.3.26 + - @backstage/plugin-gcp-projects@0.3.14 + - @backstage/plugin-github-actions@0.4.32 + - @backstage/plugin-gocd@0.1.2 + - @backstage/plugin-graphiql@0.2.28 + - @backstage/plugin-home@0.4.11 + - @backstage/plugin-jenkins@0.5.18 + - @backstage/plugin-lighthouse@0.2.35 + - @backstage/plugin-newrelic@0.3.14 + +## 0.2.61-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-tech-radar@0.5.3-next.0 + - @backstage/core-components@0.8.5-next.0 + - @backstage/plugin-api-docs@0.6.23-next.0 + - @backstage/cli@0.12.0-next.0 + - @backstage/core-plugin-api@0.6.0-next.0 + - @backstage/plugin-org@0.3.35-next.0 + - @backstage/plugin-catalog@0.7.9-next.0 + - @backstage/plugin-cost-insights@0.11.18-next.0 + - @backstage/plugin-shortcuts@0.1.20-next.0 + - @backstage/plugin-user-settings@0.3.17-next.0 + - @backstage/core-app-api@0.5.0-next.0 + - @backstage/plugin-catalog-react@0.6.12-next.0 + - @backstage/plugin-azure-devops@0.1.11-next.0 + - @backstage/plugin-badges@0.2.20-next.0 + - @backstage/plugin-catalog-import@0.7.10-next.0 + - @backstage/plugin-kafka@0.2.26-next.0 + - @backstage/plugin-kubernetes@0.5.5-next.0 + - @backstage/plugin-pagerduty@0.3.23-next.0 + - @backstage/plugin-rollbar@0.3.24-next.0 + - @backstage/plugin-scaffolder@0.11.19-next.0 + - @backstage/plugin-search@0.5.6-next.0 + - @backstage/plugin-sentry@0.3.34-next.0 + - @backstage/plugin-tech-insights@0.1.6-next.0 + - @backstage/plugin-techdocs@0.12.15-next.0 + - @backstage/plugin-todo@0.1.19-next.0 + - @backstage/catalog-model@0.9.10-next.0 + - @backstage/app-defaults@0.1.5-next.0 + - @backstage/integration-react@0.1.19-next.0 + - @backstage/plugin-airbrake@0.1.1-next.0 + - @backstage/plugin-apache-airflow@0.1.4-next.0 + - @backstage/plugin-catalog-graph@0.2.7-next.0 + - @backstage/plugin-circleci@0.2.35-next.0 + - @backstage/plugin-cloudbuild@0.2.33-next.0 + - @backstage/plugin-code-coverage@0.1.23-next.0 + - @backstage/plugin-explore@0.3.26-next.0 + - @backstage/plugin-gcp-projects@0.3.14-next.0 + - @backstage/plugin-github-actions@0.4.32-next.0 + - @backstage/plugin-gocd@0.1.2-next.0 + - @backstage/plugin-graphiql@0.2.28-next.0 + - @backstage/plugin-home@0.4.11-next.0 + - @backstage/plugin-jenkins@0.5.18-next.0 + - @backstage/plugin-lighthouse@0.2.35-next.0 + - @backstage/plugin-newrelic@0.3.14-next.0 + - @backstage/plugin-newrelic-dashboard@0.1.4-next.0 + +## 0.2.60 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.4 + - @backstage/cli@0.11.0 + - @backstage/plugin-tech-insights@0.1.5 + - @backstage/plugin-gocd@0.1.1 + - @backstage/core-plugin-api@0.5.0 + - @backstage/plugin-org@0.3.34 + - @backstage/plugin-home@0.4.10 + - @backstage/plugin-azure-devops@0.1.10 + - @backstage/plugin-apache-airflow@0.1.3 + - @backstage/plugin-catalog-import@0.7.9 + - @backstage/plugin-catalog-react@0.6.11 + - @backstage/plugin-code-coverage@0.1.22 + - @backstage/plugin-cost-insights@0.11.17 + - @backstage/plugin-jenkins@0.5.17 + - @backstage/plugin-scaffolder@0.11.18 + - @backstage/plugin-techdocs@0.12.14 + - @backstage/plugin-kubernetes@0.5.4 + - @backstage/plugin-pagerduty@0.3.22 + - @backstage/plugin-api-docs@0.6.22 + - @backstage/core-app-api@0.4.0 + - @backstage/plugin-airbrake@0.1.0 + - @backstage/app-defaults@0.1.4 + - @backstage/catalog-model@0.9.9 + - @backstage/integration-react@0.1.18 + - @backstage/plugin-badges@0.2.19 + - @backstage/plugin-catalog@0.7.8 + - @backstage/plugin-catalog-graph@0.2.6 + - @backstage/plugin-circleci@0.2.34 + - @backstage/plugin-cloudbuild@0.2.32 + - @backstage/plugin-explore@0.3.25 + - @backstage/plugin-gcp-projects@0.3.13 + - @backstage/plugin-github-actions@0.4.31 + - @backstage/plugin-graphiql@0.2.27 + - @backstage/plugin-kafka@0.2.25 + - @backstage/plugin-lighthouse@0.2.34 + - @backstage/plugin-newrelic@0.3.13 + - @backstage/plugin-newrelic-dashboard@0.1.3 + - @backstage/plugin-rollbar@0.3.23 + - @backstage/plugin-search@0.5.5 + - @backstage/plugin-sentry@0.3.33 + - @backstage/plugin-shortcuts@0.1.19 + - @backstage/plugin-tech-radar@0.5.2 + - @backstage/plugin-todo@0.1.18 + - @backstage/plugin-user-settings@0.3.16 + +## 0.2.59 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.10.5 + - @backstage/plugin-search@0.5.4 + - @backstage/plugin-techdocs@0.12.13 + - @backstage/core-plugin-api@0.4.1 + - @backstage/plugin-catalog-react@0.6.10 + - @backstage/plugin-kubernetes@0.5.3 + - @backstage/core-app-api@0.3.1 + - @backstage/core-components@0.8.3 + - @backstage/plugin-apache-airflow@0.1.2 + - @backstage/plugin-azure-devops@0.1.9 + - @backstage/plugin-badges@0.2.18 + - @backstage/plugin-catalog@0.7.7 + - @backstage/plugin-catalog-graph@0.2.5 + - @backstage/plugin-catalog-import@0.7.8 + - @backstage/plugin-circleci@0.2.33 + - @backstage/plugin-cloudbuild@0.2.31 + - @backstage/plugin-code-coverage@0.1.21 + - @backstage/plugin-cost-insights@0.11.16 + - @backstage/plugin-explore@0.3.24 + - @backstage/plugin-gcp-projects@0.3.12 + - @backstage/plugin-github-actions@0.4.30 + - @backstage/plugin-graphiql@0.2.26 + - @backstage/plugin-home@0.4.9 + - @backstage/plugin-jenkins@0.5.16 + - @backstage/plugin-kafka@0.2.24 + - @backstage/plugin-lighthouse@0.2.33 + - @backstage/plugin-newrelic@0.3.12 + - @backstage/plugin-newrelic-dashboard@0.1.2 + - @backstage/plugin-org@0.3.33 + - @backstage/plugin-pagerduty@0.3.21 + - @backstage/plugin-rollbar@0.3.22 + - @backstage/plugin-scaffolder@0.11.17 + - @backstage/plugin-sentry@0.3.32 + - @backstage/plugin-shortcuts@0.1.18 + - @backstage/plugin-tech-insights@0.1.4 + - @backstage/plugin-tech-radar@0.5.1 + - @backstage/plugin-user-settings@0.3.15 + - @backstage/plugin-api-docs@0.6.21 + +## 0.2.58 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search@0.5.2 + - @backstage/app-defaults@0.1.3 + - @backstage/core-app-api@0.3.0 + - @backstage/core-plugin-api@0.4.0 + - @backstage/plugin-user-settings@0.3.14 + - @backstage/plugin-scaffolder@0.11.15 + - @backstage/plugin-catalog-react@0.6.8 + - @backstage/plugin-kubernetes@0.5.2 + - @backstage/core-components@0.8.2 + - @backstage/cli@0.10.3 + - @backstage/plugin-tech-radar@0.5.0 + - @backstage/plugin-tech-insights@0.1.2 + - @backstage/plugin-org@0.3.32 + - @backstage/plugin-catalog@0.7.5 + - @backstage/plugin-newrelic-dashboard@0.1.1 + - @backstage/plugin-home@0.4.8 + - @backstage/plugin-circleci@0.2.32 + - @backstage/plugin-cloudbuild@0.2.30 + - @backstage/plugin-github-actions@0.4.28 + - @backstage/plugin-jenkins@0.5.15 + - @backstage/plugin-lighthouse@0.2.32 + - @backstage/plugin-sentry@0.3.31 + - @backstage/plugin-apache-airflow@0.1.1 + - @backstage/plugin-api-docs@0.6.19 + - @backstage/plugin-azure-devops@0.1.8 + - @backstage/plugin-badges@0.2.17 + - @backstage/plugin-catalog-graph@0.2.4 + - @backstage/plugin-catalog-import@0.7.6 + - @backstage/plugin-code-coverage@0.1.20 + - @backstage/plugin-cost-insights@0.11.14 + - @backstage/plugin-explore@0.3.23 + - @backstage/plugin-gcp-projects@0.3.11 + - @backstage/plugin-graphiql@0.2.25 + - @backstage/plugin-kafka@0.2.23 + - @backstage/plugin-newrelic@0.3.11 + - @backstage/plugin-pagerduty@0.3.20 + - @backstage/plugin-rollbar@0.3.21 + - @backstage/plugin-shortcuts@0.1.16 + - @backstage/plugin-techdocs@0.12.11 + - @backstage/plugin-todo@0.1.17 + - @backstage/integration-react@0.1.16 + +## 0.2.57 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-github-actions@0.4.27 + - @backstage/core-app-api@0.2.1 + - @backstage/plugin-kubernetes@0.5.1 + - @backstage/core-plugin-api@0.3.1 + - @backstage/core-components@0.8.1 + - @backstage/plugin-org@0.3.31 + - @backstage/plugin-azure-devops@0.1.7 + - @backstage/cli@0.10.2 + - @backstage/catalog-model@0.9.8 + - @backstage/plugin-techdocs@0.12.10 + - @backstage/plugin-catalog-react@0.6.7 + - @backstage/plugin-apache-airflow@0.1.0 + +## 0.2.56 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.0 + - @backstage/core-plugin-api@0.3.0 + - @backstage/plugin-cloudbuild@0.2.29 + - @backstage/plugin-catalog@0.7.4 + - @backstage/core-app-api@0.2.0 + - @backstage/plugin-scaffolder@0.11.14 + - @backstage/plugin-kubernetes@0.5.0 + - @backstage/app-defaults@0.1.2 + - @backstage/integration-react@0.1.15 + - @backstage/plugin-api-docs@0.6.18 + - @backstage/plugin-azure-devops@0.1.6 + - @backstage/plugin-badges@0.2.16 + - @backstage/plugin-catalog-graph@0.2.3 + - @backstage/plugin-catalog-import@0.7.5 + - @backstage/plugin-catalog-react@0.6.5 + - @backstage/plugin-circleci@0.2.31 + - @backstage/plugin-code-coverage@0.1.19 + - @backstage/plugin-cost-insights@0.11.13 + - @backstage/plugin-explore@0.3.22 + - @backstage/plugin-gcp-projects@0.3.10 + - @backstage/plugin-github-actions@0.4.26 + - @backstage/plugin-graphiql@0.2.24 + - @backstage/plugin-home@0.4.7 + - @backstage/plugin-jenkins@0.5.14 + - @backstage/plugin-kafka@0.2.22 + - @backstage/plugin-lighthouse@0.2.31 + - @backstage/plugin-newrelic@0.3.10 + - @backstage/plugin-org@0.3.30 + - @backstage/plugin-pagerduty@0.3.19 + - @backstage/plugin-rollbar@0.3.20 + - @backstage/plugin-search@0.5.1 + - @backstage/plugin-sentry@0.3.30 + - @backstage/plugin-shortcuts@0.1.15 + - @backstage/plugin-tech-insights@0.1.1 + - @backstage/plugin-tech-radar@0.4.13 + - @backstage/plugin-techdocs@0.12.9 + - @backstage/plugin-todo@0.1.16 + - @backstage/plugin-user-settings@0.3.13 + - @backstage/cli@0.10.1 + +## 0.2.55 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-azure-devops@0.1.5 + - @backstage/core-components@0.7.6 + - @backstage/theme@0.2.14 + - @backstage/plugin-user-settings@0.3.12 + - @backstage/plugin-api-docs@0.6.16 + - @backstage/cli@0.10.0 + - @backstage/plugin-circleci@0.2.30 + - @backstage/core-plugin-api@0.2.2 + - @backstage/plugin-search@0.5.0 + - @backstage/plugin-tech-insights@0.1.0 + - @backstage/core-app-api@0.1.24 + - @backstage/plugin-kubernetes@0.4.22 + - @backstage/plugin-scaffolder@0.11.13 + - @backstage/plugin-techdocs@0.12.8 + +## 0.2.53 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@0.7.3 + - @backstage/plugin-cost-insights@0.11.11 + - @backstage/cli@0.9.0 + - @backstage/plugin-gcp-projects@0.3.9 + - @backstage/catalog-model@0.9.7 + - @backstage/plugin-scaffolder@0.11.11 + - @backstage/plugin-api-docs@0.6.14 + - @backstage/plugin-azure-devops@0.1.4 + - @backstage/plugin-badges@0.2.14 + - @backstage/plugin-catalog-graph@0.2.2 + - @backstage/plugin-catalog-import@0.7.4 + - @backstage/plugin-catalog-react@0.6.4 + - @backstage/plugin-circleci@0.2.29 + - @backstage/plugin-cloudbuild@0.2.28 + - @backstage/plugin-code-coverage@0.1.18 + - @backstage/plugin-explore@0.3.21 + - @backstage/plugin-github-actions@0.4.24 + - @backstage/plugin-home@0.4.6 + - @backstage/plugin-jenkins@0.5.12 + - @backstage/plugin-kafka@0.2.21 + - @backstage/plugin-kubernetes@0.4.20 + - @backstage/plugin-lighthouse@0.2.30 + - @backstage/plugin-newrelic@0.3.9 + - @backstage/plugin-pagerduty@0.3.18 + - @backstage/plugin-rollbar@0.3.19 + - @backstage/plugin-search@0.4.18 + - @backstage/plugin-sentry@0.3.29 + - @backstage/plugin-tech-radar@0.4.12 + - @backstage/plugin-techdocs@0.12.6 + - @backstage/plugin-todo@0.1.15 + - @backstage/plugin-user-settings@0.3.11 + - @backstage/core-components@0.7.4 + - @backstage/core-plugin-api@0.2.0 + - @backstage/core-app-api@0.1.21 + - @backstage/plugin-shortcuts@0.1.14 + - @backstage/app-defaults@0.1.1 + - @backstage/integration-react@0.1.14 + - @backstage/plugin-graphiql@0.2.21 + - @backstage/plugin-org@0.3.28 + +## 0.2.51 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.7.1 + - @backstage/plugin-techdocs@0.12.3 + - @backstage/plugin-sentry@0.3.26 + - @backstage/plugin-user-settings@0.3.10 + - @backstage/plugin-catalog-graph@0.2.1 + - @backstage/plugin-jenkins@0.5.11 + - @backstage/core-app-api@0.1.18 + - @backstage/core-plugin-api@0.1.11 + - @backstage/cli@0.8.0 + - @backstage/plugin-catalog@0.7.2 + - @backstage/plugin-catalog-import@0.7.3 + - @backstage/plugin-catalog-react@0.6.1 + - @backstage/catalog-model@0.9.5 + +## 0.2.50 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@0.6.0 + - @backstage/plugin-catalog@0.7.1 + - @backstage/plugin-api-docs@0.6.12 + - @backstage/plugin-scaffolder@0.11.8 + - @backstage/core-app-api@0.1.17 + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + - @backstage/plugin-graphiql@0.2.20 + - @backstage/cli@0.7.16 + - @backstage/plugin-home@0.4.4 + - @backstage/plugin-catalog-graph@0.2.0 + - @backstage/plugin-search@0.4.15 + - @backstage/plugin-techdocs@0.12.2 + - @backstage/plugin-kubernetes@0.4.17 + - @backstage/plugin-azure-devops@0.1.1 + - @backstage/plugin-badges@0.2.13 + - @backstage/plugin-catalog-import@0.7.2 + - @backstage/plugin-circleci@0.2.27 + - @backstage/plugin-cloudbuild@0.2.27 + - @backstage/plugin-code-coverage@0.1.15 + - @backstage/plugin-explore@0.3.20 + - @backstage/plugin-github-actions@0.4.22 + - @backstage/plugin-jenkins@0.5.10 + - @backstage/plugin-kafka@0.2.19 + - @backstage/plugin-lighthouse@0.2.29 + - @backstage/plugin-org@0.3.27 + - @backstage/plugin-pagerduty@0.3.17 + - @backstage/plugin-rollbar@0.3.18 + - @backstage/plugin-sentry@0.3.25 + - @backstage/plugin-todo@0.1.14 + - @backstage/integration-react@0.1.12 + - @backstage/plugin-cost-insights@0.11.10 + - @backstage/plugin-gcp-projects@0.3.8 + - @backstage/plugin-newrelic@0.3.8 + - @backstage/plugin-shortcuts@0.1.12 + - @backstage/plugin-tech-radar@0.4.11 + - @backstage/plugin-user-settings@0.3.9 + +## 0.2.49 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.7.15 + - @backstage/core-components@0.6.1 + - @backstage/core-plugin-api@0.1.10 + - @backstage/core-app-api@0.1.16 + - @backstage/plugin-org@0.3.26 + - @backstage/plugin-catalog@0.7.0 + - @backstage/plugin-catalog-react@0.5.2 + - @backstage/catalog-model@0.9.4 + - @backstage/plugin-cost-insights@0.11.9 + - @backstage/plugin-user-settings@0.3.8 + - @backstage/plugin-kubernetes@0.4.16 + - @backstage/plugin-catalog-import@0.7.1 + - @backstage/plugin-badges@0.2.12 + - @backstage/plugin-home@0.4.3 + - @backstage/plugin-search@0.4.14 + - @backstage/plugin-shortcuts@0.1.11 + - @backstage/plugin-api-docs@0.6.11 + - @backstage/plugin-catalog-graph@0.1.3 + - @backstage/plugin-circleci@0.2.26 + - @backstage/plugin-cloudbuild@0.2.26 + - @backstage/plugin-code-coverage@0.1.14 + - @backstage/plugin-explore@0.3.19 + - @backstage/plugin-gcp-projects@0.3.7 + - @backstage/plugin-github-actions@0.4.21 + - @backstage/plugin-graphiql@0.2.19 + - @backstage/plugin-jenkins@0.5.9 + - @backstage/plugin-kafka@0.2.18 + - @backstage/plugin-lighthouse@0.2.28 + - @backstage/plugin-newrelic@0.3.7 + - @backstage/plugin-pagerduty@0.3.16 + - @backstage/plugin-rollbar@0.3.17 + - @backstage/plugin-scaffolder@0.11.7 + - @backstage/plugin-sentry@0.3.24 + - @backstage/plugin-tech-radar@0.4.10 + - @backstage/plugin-techdocs@0.12.1 + - @backstage/plugin-todo@0.1.13 + +## 0.2.48 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.7.14 + - @backstage/plugin-techdocs@0.12.0 + - @backstage/plugin-user-settings@0.3.7 + - @backstage/core-app-api@0.1.15 + - @backstage/plugin-catalog-import@0.7.0 + - @backstage/plugin-badges@0.2.11 + - @backstage/plugin-cost-insights@0.11.8 + - @backstage/plugin-tech-radar@0.4.9 + - @backstage/core-plugin-api@0.1.9 + - @backstage/plugin-kubernetes@0.4.15 + - @backstage/core-components@0.6.0 + - @backstage/integration-react@0.1.11 + - @backstage/plugin-catalog@0.6.17 + - @backstage/plugin-api-docs@0.6.10 + - @backstage/plugin-catalog-graph@0.1.2 + - @backstage/plugin-catalog-react@0.5.1 + - @backstage/plugin-circleci@0.2.25 + - @backstage/plugin-cloudbuild@0.2.25 + - @backstage/plugin-code-coverage@0.1.13 + - @backstage/plugin-explore@0.3.18 + - @backstage/plugin-gcp-projects@0.3.6 + - @backstage/plugin-github-actions@0.4.20 + - @backstage/plugin-graphiql@0.2.18 + - @backstage/plugin-home@0.4.2 + - @backstage/plugin-jenkins@0.5.8 + - @backstage/plugin-kafka@0.2.17 + - @backstage/plugin-lighthouse@0.2.27 + - @backstage/plugin-newrelic@0.3.6 + - @backstage/plugin-org@0.3.25 + - @backstage/plugin-pagerduty@0.3.15 + - @backstage/plugin-rollbar@0.3.16 + - @backstage/plugin-scaffolder@0.11.6 + - @backstage/plugin-search@0.4.13 + - @backstage/plugin-sentry@0.3.23 + - @backstage/plugin-shortcuts@0.1.10 + - @backstage/plugin-todo@0.1.12 + +## 0.2.47 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-explore@0.3.17 + - @backstage/core-components@0.5.0 + - @backstage/plugin-catalog-import@0.6.0 + - @backstage/plugin-catalog-graph@0.1.1 + - @backstage/cli@0.7.13 + - @backstage/plugin-catalog@0.6.16 + - @backstage/plugin-user-settings@0.3.6 + - @backstage/plugin-circleci@0.2.24 + - @backstage/plugin-catalog-react@0.5.0 + - @backstage/plugin-api-docs@0.6.9 + - @backstage/catalog-model@0.9.3 + - @backstage/plugin-rollbar@0.3.15 + - @backstage/plugin-techdocs@0.11.3 + - @backstage/plugin-kubernetes@0.4.14 + - @backstage/core-app-api@0.1.14 + - @backstage/integration-react@0.1.10 + - @backstage/plugin-badges@0.2.10 + - @backstage/plugin-cloudbuild@0.2.24 + - @backstage/plugin-code-coverage@0.1.12 + - @backstage/plugin-cost-insights@0.11.7 + - @backstage/plugin-gcp-projects@0.3.5 + - @backstage/plugin-github-actions@0.4.19 + - @backstage/plugin-graphiql@0.2.17 + - @backstage/plugin-home@0.4.1 + - @backstage/plugin-jenkins@0.5.7 + - @backstage/plugin-kafka@0.2.16 + - @backstage/plugin-lighthouse@0.2.26 + - @backstage/plugin-newrelic@0.3.5 + - @backstage/plugin-org@0.3.24 + - @backstage/plugin-pagerduty@0.3.14 + - @backstage/plugin-scaffolder@0.11.5 + - @backstage/plugin-search@0.4.12 + - @backstage/plugin-sentry@0.3.22 + - @backstage/plugin-shortcuts@0.1.9 + - @backstage/plugin-tech-radar@0.4.8 + - @backstage/plugin-todo@0.1.11 + +## 0.2.46 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.4.2 + - @backstage/cli@0.7.12 + - @backstage/plugin-techdocs@0.11.2 + - @backstage/plugin-user-settings@0.3.5 + - @backstage/plugin-newrelic@0.3.4 + - @backstage/plugin-home@0.4.0 + - @backstage/integration-react@0.1.9 + - @backstage/plugin-api-docs@0.6.8 + - @backstage/plugin-badges@0.2.9 + - @backstage/plugin-catalog@0.6.15 + - @backstage/plugin-catalog-import@0.5.21 + - @backstage/plugin-catalog-react@0.4.6 + - @backstage/plugin-circleci@0.2.23 + - @backstage/plugin-cloudbuild@0.2.23 + - @backstage/plugin-code-coverage@0.1.11 + - @backstage/plugin-cost-insights@0.11.6 + - @backstage/plugin-explore@0.3.16 + - @backstage/plugin-gcp-projects@0.3.4 + - @backstage/plugin-github-actions@0.4.18 + - @backstage/plugin-graphiql@0.2.16 + - @backstage/plugin-jenkins@0.5.6 + - @backstage/plugin-kafka@0.2.15 + - @backstage/plugin-kubernetes@0.4.13 + - @backstage/plugin-lighthouse@0.2.25 + - @backstage/plugin-org@0.3.23 + - @backstage/plugin-pagerduty@0.3.13 + - @backstage/plugin-rollbar@0.3.14 + - @backstage/plugin-scaffolder@0.11.4 + - @backstage/plugin-search@0.4.11 + - @backstage/plugin-sentry@0.3.21 + - @backstage/plugin-shortcuts@0.1.8 + - @backstage/plugin-tech-radar@0.4.7 + - @backstage/plugin-todo@0.1.10 + - @backstage/core-app-api@0.1.13 + - @backstage/core-plugin-api@0.1.8 + +## 0.2.45 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-todo@0.1.9 + - @backstage/core-components@0.4.1 + - @backstage/catalog-model@0.9.2 + - @backstage/plugin-home@0.3.0 + - @backstage/plugin-scaffolder@0.11.3 + - @backstage/plugin-org@0.3.22 + - @backstage/core-app-api@0.1.12 + - @backstage/plugin-search@0.4.10 + - @backstage/core-plugin-api@0.1.7 + - @backstage/cli@0.7.11 + +## 0.2.44 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@0.11.2 + - @backstage/cli@0.7.10 + - @backstage/plugin-catalog-react@0.4.5 + - @backstage/plugin-techdocs@0.11.1 + - @backstage/core-components@0.4.0 + - @backstage/plugin-catalog-import@0.5.20 + - @backstage/plugin-shortcuts@0.1.7 + - @backstage/plugin-org@0.3.21 + - @backstage/plugin-catalog@0.6.14 + - @backstage/search-common@0.2.0 + - @backstage/plugin-search@0.4.9 + - @backstage/catalog-model@0.9.1 + - @backstage/core-app-api@0.1.11 + - @backstage/integration-react@0.1.8 + - @backstage/plugin-api-docs@0.6.7 + - @backstage/plugin-badges@0.2.8 + - @backstage/plugin-circleci@0.2.22 + - @backstage/plugin-cloudbuild@0.2.22 + - @backstage/plugin-code-coverage@0.1.10 + - @backstage/plugin-cost-insights@0.11.5 + - @backstage/plugin-explore@0.3.15 + - @backstage/plugin-gcp-projects@0.3.3 + - @backstage/plugin-github-actions@0.4.17 + - @backstage/plugin-graphiql@0.2.15 + - @backstage/plugin-home@0.2.1 + - @backstage/plugin-jenkins@0.5.5 + - @backstage/plugin-kafka@0.2.14 + - @backstage/plugin-kubernetes@0.4.12 + - @backstage/plugin-lighthouse@0.2.24 + - @backstage/plugin-newrelic@0.3.3 + - @backstage/plugin-pagerduty@0.3.12 + - @backstage/plugin-rollbar@0.3.13 + - @backstage/plugin-sentry@0.3.20 + - @backstage/plugin-tech-radar@0.4.6 + - @backstage/plugin-todo@0.1.8 + - @backstage/plugin-user-settings@0.3.4 + +## 0.2.43 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-explore@0.3.14 + - @backstage/core-app-api@0.1.10 + - @backstage/plugin-catalog-import@0.5.19 + - @backstage/plugin-catalog@0.6.13 + - @backstage/plugin-org@0.3.20 + - @backstage/plugin-catalog-react@0.4.4 + - @backstage/core-components@0.3.3 + - @backstage/plugin-techdocs@0.11.0 + - @backstage/plugin-search@0.4.8 + +## 0.2.42 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@0.11.1 + - @backstage/plugin-home@0.2.0 + - @backstage/cli@0.7.9 + - @backstage/core-app-api@0.1.9 + - @backstage/plugin-catalog-import@0.5.18 + - @backstage/plugin-catalog@0.6.12 + - @backstage/plugin-catalog-react@0.4.3 + - @backstage/plugin-sentry@0.3.19 + - @backstage/plugin-jenkins@0.5.4 + - @backstage/core-components@0.3.2 + - @backstage/theme@0.2.10 + +## 0.2.41 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-cost-insights@0.11.4 + - @backstage/plugin-github-actions@0.4.16 + - @backstage/plugin-jenkins@0.5.3 + - @backstage/plugin-kubernetes@0.4.11 + - @backstage/plugin-pagerduty@0.3.11 + - @backstage/plugin-scaffolder@0.11.0 + - @backstage/plugin-sentry@0.3.18 + - @backstage/plugin-techdocs@0.10.4 + - @backstage/core-app-api@0.1.8 + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + - @backstage/plugin-api-docs@0.6.6 + - @backstage/plugin-catalog@0.6.11 + - @backstage/plugin-catalog-import@0.5.17 + - @backstage/plugin-catalog-react@0.4.2 + - @backstage/plugin-explore@0.3.13 + - @backstage/plugin-lighthouse@0.2.23 + - @backstage/plugin-rollbar@0.3.12 + - @backstage/plugin-search@0.4.7 + - @backstage/plugin-tech-radar@0.4.5 + - @backstage/cli@0.7.8 + - @backstage/integration-react@0.1.7 + +## 0.2.40 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.7.7 + - @backstage/plugin-catalog@0.6.10 + - @backstage/core-components@0.3.0 + - @backstage/core-plugin-api@0.1.5 + - @backstage/plugin-api-docs@0.6.5 + - @backstage/plugin-scaffolder@0.10.3 + - @backstage/plugin-search@0.4.6 + - @backstage/plugin-cost-insights@0.11.3 + - @backstage/plugin-kubernetes@0.4.10 + - @backstage/plugin-lighthouse@0.2.22 + - @backstage/plugin-pagerduty@0.3.10 + - @backstage/plugin-techdocs@0.10.3 + - @backstage/search-common@0.1.3 + - @backstage/core-app-api@0.1.7 + - @backstage/integration-react@0.1.6 + - @backstage/plugin-badges@0.2.7 + - @backstage/plugin-catalog-import@0.5.16 + - @backstage/plugin-catalog-react@0.4.1 + - @backstage/plugin-circleci@0.2.21 + - @backstage/plugin-cloudbuild@0.2.21 + - @backstage/plugin-code-coverage@0.1.9 + - @backstage/plugin-explore@0.3.12 + - @backstage/plugin-gcp-projects@0.3.2 + - @backstage/plugin-github-actions@0.4.15 + - @backstage/plugin-graphiql@0.2.14 + - @backstage/plugin-jenkins@0.5.2 + - @backstage/plugin-kafka@0.2.13 + - @backstage/plugin-newrelic@0.3.2 + - @backstage/plugin-org@0.3.19 + - @backstage/plugin-rollbar@0.3.11 + - @backstage/plugin-sentry@0.3.17 + - @backstage/plugin-shortcuts@0.1.6 + - @backstage/plugin-tech-radar@0.4.4 + - @backstage/plugin-todo@0.1.7 + - @backstage/plugin-user-settings@0.3.3 + +## 0.2.39 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@0.10.2 + - @backstage/core-components@0.2.0 + - @backstage/plugin-catalog@0.6.9 + - @backstage/plugin-catalog-react@0.4.0 + - @backstage/cli@0.7.6 + - @backstage/core-app-api@0.1.6 + - @backstage/core-plugin-api@0.1.4 + - @backstage/integration-react@0.1.5 + - @backstage/theme@0.2.9 + - @backstage/plugin-api-docs@0.6.4 + - @backstage/plugin-badges@0.2.6 + - @backstage/plugin-catalog-import@0.5.15 + - @backstage/plugin-circleci@0.2.20 + - @backstage/plugin-cloudbuild@0.2.20 + - @backstage/plugin-code-coverage@0.1.8 + - @backstage/plugin-cost-insights@0.11.2 + - @backstage/plugin-explore@0.3.11 + - @backstage/plugin-gcp-projects@0.3.1 + - @backstage/plugin-github-actions@0.4.14 + - @backstage/plugin-graphiql@0.2.13 + - @backstage/plugin-jenkins@0.5.1 + - @backstage/plugin-kafka@0.2.12 + - @backstage/plugin-kubernetes@0.4.9 + - @backstage/plugin-lighthouse@0.2.21 + - @backstage/plugin-newrelic@0.3.1 + - @backstage/plugin-org@0.3.18 + - @backstage/plugin-pagerduty@0.3.9 + - @backstage/plugin-rollbar@0.3.10 + - @backstage/plugin-search@0.4.5 + - @backstage/plugin-sentry@0.3.16 + - @backstage/plugin-shortcuts@0.1.5 + - @backstage/plugin-tech-radar@0.4.3 + - @backstage/plugin-techdocs@0.10.2 + - @backstage/plugin-todo@0.1.6 + - @backstage/plugin-user-settings@0.3.2 + +## 0.2.37 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@0.6.7 + - @backstage/plugin-catalog-react@0.3.0 + - @backstage/plugin-scaffolder@0.10.1 + - @backstage/plugin-techdocs@0.10.0 + - @backstage/plugin-jenkins@0.5.0 + - @backstage/plugin-user-settings@0.3.0 + - @backstage/plugin-search@0.4.3 + - @backstage/cli@0.7.4 + - @backstage/plugin-api-docs@0.6.2 + - @backstage/plugin-badges@0.2.5 + - @backstage/plugin-catalog-import@0.5.13 + - @backstage/plugin-circleci@0.2.19 + - @backstage/plugin-cloudbuild@0.2.19 + - @backstage/plugin-code-coverage@0.1.7 + - @backstage/plugin-explore@0.3.10 + - @backstage/plugin-github-actions@0.4.13 + - @backstage/plugin-kafka@0.2.11 + - @backstage/plugin-kubernetes@0.4.8 + - @backstage/plugin-lighthouse@0.2.20 + - @backstage/plugin-org@0.3.17 + - @backstage/plugin-pagerduty@0.3.8 + - @backstage/plugin-rollbar@0.3.9 + - @backstage/plugin-sentry@0.3.15 + - @backstage/plugin-todo@0.1.5 + +## 0.2.36 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.1.5 + - @backstage/plugin-pagerduty@0.3.7 + - @backstage/plugin-catalog-import@0.5.12 + - @backstage/plugin-explore@0.3.9 + - @backstage/catalog-model@0.9.0 + - @backstage/plugin-scaffolder@0.10.0 + - @backstage/plugin-catalog@0.6.6 + - @backstage/plugin-org@0.3.16 + - @backstage/plugin-techdocs@0.9.9 + - @backstage/plugin-api-docs@0.6.1 + - @backstage/plugin-badges@0.2.4 + - @backstage/plugin-catalog-react@0.2.6 + - @backstage/plugin-circleci@0.2.18 + - @backstage/plugin-cloudbuild@0.2.18 + - @backstage/plugin-code-coverage@0.1.6 + - @backstage/plugin-github-actions@0.4.12 + - @backstage/plugin-jenkins@0.4.7 + - @backstage/plugin-kafka@0.2.10 + - @backstage/plugin-kubernetes@0.4.7 + - @backstage/plugin-lighthouse@0.2.19 + - @backstage/plugin-rollbar@0.3.8 + - @backstage/plugin-search@0.4.2 + - @backstage/plugin-sentry@0.3.14 + - @backstage/plugin-todo@0.1.4 + +## 0.2.34 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@0.6.4 + - @backstage/plugin-search@0.4.1 + - @backstage/plugin-explore@0.3.7 + - @backstage/core-app-api@0.1.3 + - @backstage/core-plugin-api@0.1.3 + - @backstage/plugin-scaffolder@0.9.9 + - @backstage/cli@0.7.2 + - @backstage/plugin-api-docs@0.6.0 + - @backstage/plugin-cost-insights@0.11.0 + - @backstage/plugin-gcp-projects@0.3.0 + - @backstage/plugin-newrelic@0.3.0 + - @backstage/plugin-techdocs@0.9.7 + - @backstage/catalog-model@0.8.4 + - @backstage/integration-react@0.1.4 + - @backstage/plugin-badges@0.2.3 + - @backstage/plugin-catalog-import@0.5.11 + - @backstage/plugin-catalog-react@0.2.4 + - @backstage/plugin-circleci@0.2.17 + - @backstage/plugin-cloudbuild@0.2.17 + - @backstage/plugin-code-coverage@0.1.5 + - @backstage/plugin-github-actions@0.4.10 + - @backstage/plugin-graphiql@0.2.12 + - @backstage/plugin-jenkins@0.4.6 + - @backstage/plugin-kafka@0.2.9 + - @backstage/plugin-kubernetes@0.4.6 + - @backstage/plugin-lighthouse@0.2.18 + - @backstage/plugin-org@0.3.15 + - @backstage/plugin-pagerduty@0.3.6 + - @backstage/plugin-rollbar@0.3.7 + - @backstage/plugin-sentry@0.3.13 + - @backstage/plugin-shortcuts@0.1.4 + - @backstage/plugin-tech-radar@0.4.1 + - @backstage/plugin-todo@0.1.3 + - @backstage/plugin-user-settings@0.2.12 + +## 0.2.33 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@0.2.3 + - @backstage/plugin-catalog@0.6.3 + - @backstage/cli@0.7.1 + - @backstage/plugin-api-docs@0.5.0 + - @backstage/plugin-jenkins@0.4.5 + - @backstage/plugin-techdocs@0.9.6 + - @backstage/plugin-circleci@0.2.16 + - @backstage/plugin-catalog-import@0.5.10 + - @backstage/plugin-sentry@0.3.12 + - @backstage/plugin-user-settings@0.2.11 + - @backstage/catalog-model@0.8.3 + - @backstage/core@0.7.13 + +## 0.2.32 + +### Patch Changes + +- Updated dependencies [9cd3c533c] +- Updated dependencies [db1c8f93b] +- Updated dependencies [9d906c7a1] +- Updated dependencies [9bdd2cca8] +- Updated dependencies [27a9b503a] +- Updated dependencies [f4e3ac5ce] +- Updated dependencies [9b4010965] +- Updated dependencies [7f7443308] +- Updated dependencies [7028ee1ca] +- Updated dependencies [70bc30c5b] +- Updated dependencies [db1c8f93b] +- Updated dependencies [5aff84759] +- Updated dependencies [21e8ebef5] +- Updated dependencies [4fbb00707] +- Updated dependencies [d5ad47bbb] + - @backstage/cli@0.7.0 + - @backstage/plugin-catalog@0.6.2 + - @backstage/plugin-cost-insights@0.10.2 + - @backstage/plugin-github-actions@0.4.9 + - @backstage/catalog-model@0.8.2 + - @backstage/plugin-scaffolder@0.9.8 + - @backstage/integration-react@0.1.3 + - @backstage/plugin-catalog-react@0.2.2 + - @backstage/plugin-catalog-import@0.5.9 + - @backstage/plugin-search@0.4.0 + +## 0.2.31 + +### Patch Changes + +- Updated dependencies [497f4ce18] +- Updated dependencies [ee4eb5b40] +- Updated dependencies [84160313e] +- Updated dependencies [3772de8ba] +- Updated dependencies [7e7c71417] +- Updated dependencies [f430b6c6f] +- Updated dependencies [2a942cc9e] +- Updated dependencies [e7c5e4b30] +- Updated dependencies [ebe802bc4] +- Updated dependencies [1cf1d351f] +- Updated dependencies [90a505a77] +- Updated dependencies [76f99a1a0] +- Updated dependencies [deaba2e13] +- Updated dependencies [1157fa307] +- Updated dependencies [8e919a6f8] +- Updated dependencies [2305ab8fc] +- Updated dependencies [054bcd029] +- Updated dependencies [aad98c544] +- Updated dependencies [f46a9e82d] + - @backstage/plugin-scaffolder@0.9.7 + - @backstage/cli@0.6.14 + - @backstage/plugin-catalog@0.6.1 + - @backstage/theme@0.2.8 + - @backstage/catalog-model@0.8.1 + - @backstage/core@0.7.12 + - @backstage/plugin-tech-radar@0.4.0 + - @backstage/plugin-catalog-react@0.2.1 + - @backstage/plugin-techdocs@0.9.5 + +## 0.2.30 + +### Patch Changes + +- Updated dependencies [41c3ec421] +- Updated dependencies [add62a455] +- Updated dependencies [cc592248b] +- Updated dependencies [17c497b81] +- Updated dependencies [1cd0cacd9] +- Updated dependencies [4ea9df9d3] +- Updated dependencies [7a7da5146] +- Updated dependencies [5baf2ff0f] +- Updated dependencies [bf805b467] +- Updated dependencies [203ce6f6f] +- Updated dependencies [7ab5bfe68] +- Updated dependencies [704875e26] +- Updated dependencies [3a181cff1] + - @backstage/plugin-cost-insights@0.10.1 + - @backstage/catalog-model@0.8.0 + - @backstage/core@0.7.11 + - @backstage/plugin-catalog@0.6.0 + - @backstage/plugin-catalog-react@0.2.0 + - @backstage/cli@0.6.13 + - @backstage/plugin-techdocs@0.9.4 + - @backstage/plugin-jenkins@0.4.4 + - @backstage/plugin-api-docs@0.4.15 + - @backstage/plugin-badges@0.2.2 + - @backstage/plugin-catalog-import@0.5.8 + - @backstage/plugin-circleci@0.2.15 + - @backstage/plugin-cloudbuild@0.2.16 + - @backstage/plugin-code-coverage@0.1.4 + - @backstage/plugin-explore@0.3.6 + - @backstage/plugin-github-actions@0.4.8 + - @backstage/plugin-kafka@0.2.8 + - @backstage/plugin-kubernetes@0.4.5 + - @backstage/plugin-lighthouse@0.2.17 + - @backstage/plugin-org@0.3.14 + - @backstage/plugin-pagerduty@0.3.5 + - @backstage/plugin-rollbar@0.3.6 + - @backstage/plugin-scaffolder@0.9.6 + - @backstage/plugin-search@0.3.7 + - @backstage/plugin-sentry@0.3.11 + - @backstage/plugin-todo@0.1.2 + +## 0.2.29 + +### Patch Changes + +- Updated dependencies [7cbfcae48] +- Updated dependencies [2bfec55a6] +- Updated dependencies [f7f7783a3] +- Updated dependencies [65e6c4541] +- Updated dependencies [68fdbf014] +- Updated dependencies [5da6a561d] +- Updated dependencies [ca6e0ab69] +- Updated dependencies [5914a76d5] +- Updated dependencies [81d7b9c6f] +- Updated dependencies [a62cfe068] +- Updated dependencies [35e091604] +- Updated dependencies [a53f3d603] +- Updated dependencies [b203699e9] + - @backstage/plugin-cost-insights@0.10.0 + - @backstage/cli@0.6.12 + - @backstage/catalog-model@0.7.10 + - @backstage/plugin-scaffolder@0.9.5 + - @backstage/core@0.7.10 + - @backstage/plugin-api-docs@0.4.14 + - @backstage/plugin-cloudbuild@0.2.15 + - @backstage/plugin-github-actions@0.4.7 + - @backstage/plugin-techdocs@0.9.3 + - @backstage/plugin-catalog-import@0.5.7 + - @backstage/plugin-catalog@0.5.8 + +## 0.2.28 + +### Patch Changes + +- Updated dependencies [062bbf90f] +- Updated dependencies [2cd70e164] +- Updated dependencies [0b033d07b] +- Updated dependencies [3be844496] +- Updated dependencies [5542de095] +- Updated dependencies [10c008a3a] +- Updated dependencies [81ef1d57b] +- Updated dependencies [ea21d46f0] +- Updated dependencies [e3fc89df6] +- Updated dependencies [f59a945b7] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/integration-react@0.1.2 + - @backstage/plugin-api-docs@0.4.13 + - @backstage/plugin-badges@0.2.1 + - @backstage/plugin-catalog@0.5.7 + - @backstage/plugin-catalog-import@0.5.6 + - @backstage/plugin-catalog-react@0.1.6 + - @backstage/plugin-circleci@0.2.14 + - @backstage/plugin-cloudbuild@0.2.14 + - @backstage/plugin-code-coverage@0.1.3 + - @backstage/plugin-cost-insights@0.9.1 + - @backstage/plugin-explore@0.3.5 + - @backstage/plugin-gcp-projects@0.2.6 + - @backstage/plugin-github-actions@0.4.6 + - @backstage/plugin-graphiql@0.2.11 + - @backstage/plugin-jenkins@0.4.3 + - @backstage/plugin-kafka@0.2.7 + - @backstage/plugin-kubernetes@0.4.4 + - @backstage/plugin-lighthouse@0.2.16 + - @backstage/plugin-newrelic@0.2.7 + - @backstage/plugin-org@0.3.13 + - @backstage/plugin-pagerduty@0.3.4 + - @backstage/plugin-rollbar@0.3.5 + - @backstage/plugin-scaffolder@0.9.4 + - @backstage/plugin-search@0.3.6 + - @backstage/plugin-sentry@0.3.10 + - @backstage/plugin-shortcuts@0.1.2 + - @backstage/plugin-tech-radar@0.3.11 + - @backstage/plugin-techdocs@0.9.2 + - @backstage/plugin-todo@0.1.1 + - @backstage/plugin-user-settings@0.2.10 + - @backstage/cli@0.6.11 + - @backstage/catalog-model@0.7.9 + +## 0.2.27 + +### Patch Changes + +- Updated dependencies [6f1b82b14] +- Updated dependencies [38ca05168] +- Updated dependencies [f65adcde7] +- Updated dependencies [81c54d1f2] +- Updated dependencies [80888659b] +- Updated dependencies [7b8272fb7] +- Updated dependencies [8aedbb4af] +- Updated dependencies [fc79a6dd3] +- Updated dependencies [f53fba29f] +- Updated dependencies [b2e2ec753] +- Updated dependencies [9314a8592] +- Updated dependencies [2e05277e0] +- Updated dependencies [4075c6367] +- Updated dependencies [d8b81fd28] + - @backstage/plugin-cost-insights@0.9.0 + - @backstage/plugin-catalog-import@0.5.5 + - @backstage/plugin-github-actions@0.4.5 + - @backstage/cli@0.6.10 + - @backstage/core@0.7.8 + - @backstage/plugin-catalog-react@0.1.5 + - @backstage/theme@0.2.7 + - @backstage/plugin-kubernetes@0.4.3 + - @backstage/plugin-tech-radar@0.3.10 + - @backstage/plugin-scaffolder@0.9.3 + - @backstage/plugin-techdocs@0.9.1 + - @backstage/catalog-model@0.7.8 + +## 0.2.26 + +### Patch Changes + +- Updated dependencies [1ce80ff02] +- Updated dependencies [b98de52ae] +- Updated dependencies [4c42ecca2] +- Updated dependencies [c614ede9a] +- Updated dependencies [9afcac5af] +- Updated dependencies [07a7806c3] +- Updated dependencies [f6efa71ee] +- Updated dependencies [19a4dd710] +- Updated dependencies [dcd54c7cd] +- Updated dependencies [da546ce00] +- Updated dependencies [e0c9ed759] +- Updated dependencies [6fbd7beca] +- Updated dependencies [15cbe6815] +- Updated dependencies [39bdaa004] +- Updated dependencies [cb8c848a3] +- Updated dependencies [21fddf452] +- Updated dependencies [17915e29b] +- Updated dependencies [6eaecbd81] +- Updated dependencies [23769512a] +- Updated dependencies [1a142ae8a] + - @backstage/plugin-api-docs@0.4.12 + - @backstage/plugin-cost-insights@0.8.5 + - @backstage/plugin-github-actions@0.4.4 + - @backstage/plugin-catalog-import@0.5.4 + - @backstage/plugin-circleci@0.2.13 + - @backstage/plugin-explore@0.3.4 + - @backstage/plugin-graphiql@0.2.10 + - @backstage/plugin-jenkins@0.4.2 + - @backstage/plugin-lighthouse@0.2.15 + - @backstage/plugin-pagerduty@0.3.3 + - @backstage/plugin-rollbar@0.3.4 + - @backstage/plugin-sentry@0.3.9 + - @backstage/core@0.7.7 + - @backstage/plugin-scaffolder@0.9.2 + - @backstage/plugin-catalog@0.5.6 + - @backstage/plugin-search@0.3.5 + - @backstage/plugin-techdocs@0.9.0 + +## 0.2.25 + +### Patch Changes + +- Updated dependencies [94da20976] +- Updated dependencies [d8cc7e67a] +- Updated dependencies [4e5c94249] +- Updated dependencies [99fbef232] +- Updated dependencies [cb0206b2b] +- Updated dependencies [1373f4f12] +- Updated dependencies [29a7e4be8] +- Updated dependencies [ab07d77f6] +- Updated dependencies [96728a2af] +- Updated dependencies [931b21a12] +- Updated dependencies [937ed39ce] +- Updated dependencies [87c4f59de] +- Updated dependencies [55b2fc0c0] +- Updated dependencies [9a9e7a42f] +- Updated dependencies [50ce875a0] +- Updated dependencies [ac6025f63] +- Updated dependencies [e292e393f] +- Updated dependencies [479b29124] + - @backstage/core@0.7.6 + - @backstage/cli@0.6.9 + - @backstage/plugin-scaffolder@0.9.1 + - @backstage/plugin-catalog-import@0.5.3 + - @backstage/plugin-catalog@0.5.5 + - @backstage/theme@0.2.6 + - @backstage/plugin-code-coverage@0.1.2 + - @backstage/plugin-techdocs@0.8.0 + +## 0.2.24 + +### Patch Changes + +- Updated dependencies [a360f9478] +- Updated dependencies [bb5055aee] +- Updated dependencies [d0d1c2f7b] +- Updated dependencies [5d0740563] +- Updated dependencies [12390778e] +- Updated dependencies [fef852ecd] +- Updated dependencies [18f7345a6] +- Updated dependencies [5cafcf452] +- Updated dependencies [86a95ba67] +- Updated dependencies [e27cb6c45] +- Updated dependencies [184b02bef] +- Updated dependencies [60ce64aa2] + - @backstage/plugin-scaffolder@0.9.0 + - @backstage/catalog-model@0.7.7 + - @backstage/core@0.7.5 + - @backstage/plugin-catalog@0.5.4 + - @backstage/plugin-api-docs@0.4.11 + - @backstage/plugin-techdocs@0.7.2 + - @backstage/plugin-tech-radar@0.3.9 + - @backstage/cli@0.6.8 + +## 0.2.23 + +### Patch Changes + +- Updated dependencies [d0b4ebf22] +- Updated dependencies [1279a3325] +- Updated dependencies [4a4681b1b] +- Updated dependencies [97b60de98] +- Updated dependencies [3f96a9d5a] +- Updated dependencies [b051e770c] +- Updated dependencies [f9c75f7a9] +- Updated dependencies [98dd5da71] +- Updated dependencies [97d53f686] +- Updated dependencies [64d2ce700] + - @backstage/plugin-badges@0.2.0 + - @backstage/core@0.7.4 + - @backstage/catalog-model@0.7.6 + - @backstage/plugin-scaffolder@0.8.2 + - @backstage/plugin-catalog-import@0.5.2 + - @backstage/plugin-catalog@0.5.3 + - @backstage/plugin-org@0.3.12 + - @backstage/plugin-github-deployments@0.1.2 + - @backstage/cli@0.6.7 + +## 0.2.21 + +### Patch Changes + +- Updated dependencies [a2a3c7803] +- Updated dependencies [687f066e1] +- Updated dependencies [d10ea17c9] +- Updated dependencies [01ccef4c7] +- Updated dependencies [4d248725e] +- Updated dependencies [4d248725e] +- Updated dependencies [aaeb7ecf3] +- Updated dependencies [ea9d977e7] +- Updated dependencies [fcc3ada24] +- Updated dependencies [687f066e1] +- Updated dependencies [2aab54319] +- Updated dependencies [4618774ff] +- Updated dependencies [3139f83af] +- Updated dependencies [598f5bcfb] +- Updated dependencies [4d248725e] +- Updated dependencies [df59930b3] + - @backstage/plugin-kubernetes@0.4.2 + - @backstage/plugin-badges@0.1.2 + - @backstage/plugin-cost-insights@0.8.4 + - @backstage/plugin-catalog-react@0.1.3 + - @backstage/plugin-github-actions@0.4.2 + - @backstage/plugin-jenkins@0.4.1 + - @backstage/plugin-catalog@0.5.1 + - @backstage/plugin-techdocs@0.7.0 + - @backstage/core@0.7.3 + - @backstage/theme@0.2.5 + - @backstage/cli@0.6.6 + +## 0.2.20 + +### Patch Changes + +- Updated dependencies [633a31fec] +- Updated dependencies [34e6bb409] +- Updated dependencies [b56815b40] +- Updated dependencies [09eb54e01] +- Updated dependencies [147b4c5b1] +- Updated dependencies [83bfc98a3] +- Updated dependencies [7d8c4c97c] +- Updated dependencies [e7baa0d2e] +- Updated dependencies [1df417bd3] +- Updated dependencies [84972540b] +- Updated dependencies [3385b374b] +- Updated dependencies [a0dacc184] +- Updated dependencies [8686eb38c] +- Updated dependencies [9ca0e4009] +- Updated dependencies [34ff49b0f] +- Updated dependencies [8686eb38c] +- Updated dependencies [c8b54c370] +- Updated dependencies [1f98a6ff8] +- Updated dependencies [e8b2ed9cc] + - @backstage/plugin-catalog@0.5.0 + - @backstage/plugin-tech-radar@0.3.8 + - @backstage/plugin-graphiql@0.2.9 + - @backstage/plugin-user-settings@0.2.8 + - @backstage/plugin-techdocs@0.6.2 + - @backstage/plugin-catalog-import@0.5.0 + - @backstage/plugin-kubernetes@0.4.1 + - @backstage/cli@0.6.5 + - @backstage/plugin-scaffolder@0.8.0 + - @backstage/core@0.7.2 + - @backstage/plugin-api-docs@0.4.9 + - @backstage/plugin-catalog-react@0.1.2 + - @backstage/plugin-circleci@0.2.12 + - @backstage/plugin-cloudbuild@0.2.13 + - @backstage/plugin-explore@0.3.2 + - @backstage/plugin-github-actions@0.4.1 + - @backstage/plugin-jenkins@0.4.0 + - @backstage/plugin-lighthouse@0.2.14 + - @backstage/plugin-org@0.3.10 + - @backstage/plugin-search@0.3.4 + +## 0.2.19 + +### Patch Changes + +- Updated dependencies [13fb84244] +- Updated dependencies [4f3d0dce0] +- Updated dependencies [8f72318fe] +- Updated dependencies [0b42fff22] +- Updated dependencies [0b42fff22] +- Updated dependencies [ff4d666ab] +- Updated dependencies [aa095e469] +- Updated dependencies [f98f212e4] +- Updated dependencies [9f7dc10fb] +- Updated dependencies [eabe89d38] +- Updated dependencies [9581ff0b4] +- Updated dependencies [93c62c755] +- Updated dependencies [2089de76b] +- Updated dependencies [c9b5c1eca] +- Updated dependencies [dc1fc92c8] +- Updated dependencies [2089de76b] +- Updated dependencies [868e4cdf2] +- Updated dependencies [ca4a904f6] +- Updated dependencies [763926bc1] +- Updated dependencies [5ab5864f6] +- Updated dependencies [4202807bb] +- Updated dependencies [e2c1b3fb6] + - @backstage/plugin-github-actions@0.4.0 + - @backstage/plugin-catalog@0.4.2 + - @backstage/plugin-kafka@0.2.6 + - @backstage/catalog-model@0.7.4 + - @backstage/core@0.7.1 + - @backstage/plugin-techdocs@0.6.1 + - @backstage/plugin-scaffolder@0.7.1 + - @backstage/plugin-kubernetes@0.4.0 + - @backstage/theme@0.2.4 + - @backstage/plugin-explore@0.3.1 + - @backstage/cli@0.6.4 + +## 0.2.18 + +### Patch Changes + +- Updated dependencies [ba21797ca] +- Updated dependencies [b2a5320a4] +- Updated dependencies [12d8f27a6] +- Updated dependencies [507513fed] +- Updated dependencies [32a003973] +- Updated dependencies [40c0fdbaa] +- Updated dependencies [12d8f27a6] +- Updated dependencies [cfc83cac1] +- Updated dependencies [bc327dc42] +- Updated dependencies [2386de1d3] +- Updated dependencies [42c8ebb79] +- Updated dependencies [10362e9eb] +- Updated dependencies [e37d2de99] +- Updated dependencies [813c6a4f2] +- Updated dependencies [11c6208fe] +- Updated dependencies [8106c9528] +- Updated dependencies [05183f202] +- Updated dependencies [40c0fdbaa] +- Updated dependencies [f71589800] +- Updated dependencies [2a271d89e] +- Updated dependencies [bece09057] +- Updated dependencies [d4f0a1406] +- Updated dependencies [169f48deb] +- Updated dependencies [8a1566719] +- Updated dependencies [9d455f69a] +- Updated dependencies [4c049a1a1] +- Updated dependencies [d21058c34] +- Updated dependencies [b33e553b2] +- Updated dependencies [04667f571] +- Updated dependencies [b93538acc] +- Updated dependencies [0452ba238] +- Updated dependencies [8871e7523] +- Updated dependencies [02816ecd7] +- Updated dependencies [dbea11072] + - @backstage/plugin-kubernetes@0.3.12 + - @backstage/plugin-circleci@0.2.11 + - @backstage/plugin-cloudbuild@0.2.12 + - @backstage/plugin-github-actions@0.3.5 + - @backstage/plugin-scaffolder@0.7.0 + - @backstage/cli@0.6.3 + - @backstage/plugin-api-docs@0.4.8 + - @backstage/plugin-catalog@0.4.1 + - @backstage/catalog-model@0.7.3 + - @backstage/plugin-explore@0.3.0 + - @backstage/plugin-techdocs@0.6.0 + - @backstage/plugin-catalog-import@0.4.3 + - @backstage/core@0.7.0 + - @backstage/plugin-catalog-react@0.1.1 + - @backstage/plugin-pagerduty@0.3.2 + - @backstage/plugin-jenkins@0.3.12 + - @backstage/plugin-lighthouse@0.2.13 + - @backstage/plugin-cost-insights@0.8.3 + - @backstage/plugin-gcp-projects@0.2.5 + - @backstage/plugin-gitops-profiles@0.2.6 + - @backstage/plugin-graphiql@0.2.8 + - @backstage/plugin-kafka@0.2.5 + - @backstage/plugin-newrelic@0.2.6 + - @backstage/plugin-org@0.3.9 + - @backstage/plugin-register-component@0.2.12 + - @backstage/plugin-rollbar@0.3.3 + - @backstage/plugin-search@0.3.3 + - @backstage/plugin-sentry@0.3.8 + - @backstage/plugin-tech-radar@0.3.7 + - @backstage/plugin-user-settings@0.2.7 + +## 0.2.17 + +### Patch Changes + +- Updated dependencies [3a58084b6] +- Updated dependencies [a5f42cf66] +- Updated dependencies [38205492a] +- Updated dependencies [e488f0502] +- Updated dependencies [e799e74d4] +- Updated dependencies [e3bc5aad7] +- Updated dependencies [a5f42cf66] +- Updated dependencies [a8953a9c9] +- Updated dependencies [f37992797] +- Updated dependencies [347137ccf] +- Updated dependencies [d0760ecdf] +- Updated dependencies [1407b34c6] +- Updated dependencies [88f1f1b60] +- Updated dependencies [d6593abe6] +- Updated dependencies [bad21a085] +- Updated dependencies [e8e35fb5f] +- Updated dependencies [9615e68fb] +- Updated dependencies [e780e119c] +- Updated dependencies [437bac549] +- Updated dependencies [9f2b3a26e] +- Updated dependencies [49f9b7346] +- Updated dependencies [5c2e2863f] +- Updated dependencies [968b588f7] +- Updated dependencies [3a58084b6] +- Updated dependencies [2499f6cde] +- Updated dependencies [5469a9761] +- Updated dependencies [60d1bc3e7] +- Updated dependencies [2c1f2a7c2] +- Updated dependencies [6266ddd11] + - @backstage/core@0.6.3 + - @backstage/plugin-scaffolder@0.6.0 + - @backstage/plugin-cost-insights@0.8.2 + - @backstage/plugin-org@0.3.8 + - @backstage/plugin-catalog@0.4.0 + - @backstage/plugin-catalog-import@0.4.2 + - @backstage/plugin-techdocs@0.5.8 + - @backstage/plugin-explore@0.2.7 + - @backstage/plugin-catalog-react@0.1.0 + - @backstage/plugin-api-docs@0.4.7 + - @backstage/catalog-model@0.7.2 + - @backstage/cli@0.6.2 + - @backstage/plugin-tech-radar@0.3.6 + - @backstage/plugin-circleci@0.2.10 + - @backstage/plugin-cloudbuild@0.2.11 + - @backstage/plugin-github-actions@0.3.4 + - @backstage/plugin-jenkins@0.3.11 + - @backstage/plugin-kafka@0.2.4 + - @backstage/plugin-kubernetes@0.3.11 + - @backstage/plugin-lighthouse@0.2.12 + - @backstage/plugin-pagerduty@0.3.1 + - @backstage/plugin-register-component@0.2.11 + - @backstage/plugin-rollbar@0.3.2 + - @backstage/plugin-search@0.3.2 + - @backstage/plugin-sentry@0.3.7 + +## 0.2.16 + +### Patch Changes + +- Updated dependencies [6c4a76c59] +- Updated dependencies [32a950409] +- Updated dependencies [f10950bd2] +- Updated dependencies [914c89b13] +- Updated dependencies [fd3f2a8c0] +- Updated dependencies [257a753ff] +- Updated dependencies [d872f662d] +- Updated dependencies [9337f509d] +- Updated dependencies [d34d26125] +- Updated dependencies [0af242b6d] +- Updated dependencies [f4c2bcf54] +- Updated dependencies [e8692df4a] +- Updated dependencies [53b69236d] +- Updated dependencies [549a859ac] +- Updated dependencies [10a0124e0] +- Updated dependencies [07e226872] +- Updated dependencies [f62e7abe5] +- Updated dependencies [96f378d10] +- Updated dependencies [532bc0ec0] +- Updated dependencies [688b73110] + - @backstage/plugin-scaffolder@0.5.1 + - @backstage/plugin-catalog@0.3.2 + - @backstage/core@0.6.2 + - @backstage/cli@0.6.1 + - @backstage/plugin-user-settings@0.2.6 + - @backstage/plugin-catalog-react@0.0.4 + - @backstage/plugin-api-docs@0.4.6 + - @backstage/plugin-catalog-import@0.4.1 + - @backstage/plugin-github-actions@0.3.3 + - @backstage/plugin-jenkins@0.3.10 + - @backstage/plugin-lighthouse@0.2.11 + - @backstage/plugin-org@0.3.7 + - @backstage/plugin-sentry@0.3.6 + - @backstage/plugin-pagerduty@0.3.0 + - @backstage/plugin-circleci@0.2.9 + - @backstage/plugin-cloudbuild@0.2.10 + - @backstage/plugin-explore@0.2.6 + - @backstage/plugin-kafka@0.2.3 + - @backstage/plugin-kubernetes@0.3.10 + - @backstage/plugin-register-component@0.2.10 + - @backstage/plugin-rollbar@0.3.1 + - @backstage/plugin-search@0.3.1 + - @backstage/plugin-techdocs@0.5.7 + +## 0.2.15 + +### Patch Changes + +- 07bafa248: Add configurable `scope` for oauth2 auth provider. + + Some OAuth2 providers require certain scopes to facilitate a user sign-in using the Authorization Code flow. + This change adds the optional `scope` key to auth.providers.oauth2. An example is: + + ```yaml + auth: + providers: + oauth2: + development: + clientId: + $env: DEV_OAUTH2_CLIENT_ID + clientSecret: + $env: DEV_OAUTH2_CLIENT_SECRET + authorizationUrl: + $env: DEV_OAUTH2_AUTH_URL + tokenUrl: + $env: DEV_OAUTH2_TOKEN_URL + scope: saml-login-selector openid profile email + ``` + + This tells the OAuth 2.0 AS to perform a SAML login and return OIDC information include the `profile` + and `email` claims as part of the ID Token. + +- Updated dependencies [753bb4c40] +- Updated dependencies [6ed2b47d6] +- Updated dependencies [b33fa4cf4] +- Updated dependencies [d36660721] +- Updated dependencies [6b26c9f41] +- Updated dependencies [b3f0c3811] +- Updated dependencies [302795d10] +- Updated dependencies [9ec66c345] +- Updated dependencies [53d3e2d62] +- Updated dependencies [ca559171b] +- Updated dependencies [53348f0af] +- Updated dependencies [f5e564cd6] +- Updated dependencies [68dd79d83] +- Updated dependencies [29a138636] +- Updated dependencies [14aef4b94] +- Updated dependencies [41af18227] +- Updated dependencies [1df75733e] +- Updated dependencies [02d6803e8] +- Updated dependencies [b288a291e] +- Updated dependencies [025c0c7bf] +- Updated dependencies [e5da858d7] +- Updated dependencies [9230d07e7] +- Updated dependencies [f5f45744e] +- Updated dependencies [0fe8ff5be] +- Updated dependencies [c5ab91ce3] +- Updated dependencies [64b9efac2] +- Updated dependencies [19d354c78] +- Updated dependencies [7716d1d70] +- Updated dependencies [8f3443427] +- Updated dependencies [b51ee6ece] +- Updated dependencies [accdfeb30] +- Updated dependencies [804502a5c] + - @backstage/plugin-catalog-import@0.4.0 + - @backstage/plugin-catalog@0.3.1 + - @backstage/plugin-kubernetes@0.3.9 + - @backstage/plugin-rollbar@0.3.0 + - @backstage/plugin-scaffolder@0.5.0 + - @backstage/plugin-cost-insights@0.8.1 + - @backstage/plugin-circleci@0.2.8 + - @backstage/plugin-search@0.3.0 + - @backstage/plugin-cloudbuild@0.2.9 + - @backstage/plugin-register-component@0.2.9 + - @backstage/plugin-sentry@0.3.5 + - @backstage/plugin-jenkins@0.3.9 + - @backstage/plugin-api-docs@0.4.5 + - @backstage/plugin-lighthouse@0.2.10 + - @backstage/plugin-techdocs@0.5.6 + - @backstage/plugin-pagerduty@0.2.8 + - @backstage/plugin-org@0.3.6 + - @backstage/plugin-github-actions@0.3.2 + - @backstage/plugin-explore@0.2.5 + - @backstage/plugin-newrelic@0.2.5 + - @backstage/plugin-catalog-react@0.0.3 + - @backstage/plugin-kafka@0.2.2 + - @backstage/core@0.6.1 + - @backstage/plugin-gitops-profiles@0.2.5 + - @backstage/plugin-tech-radar@0.3.5 + +## 0.2.14 + +### Patch Changes + +- 9d6ef14bc: Migrated to new composability API, exporting the plugin instance as `rollbarPlugin`, the entity page content as `EntityRollbarContent`, and entity conditional as `isRollbarAvailable`. Updated the `EntityPage` for the `example-app` to include a composite `ErrorsSwitcher` component that works with both `Sentry` & `Rollbar`. Also removed the unused and undocumented `RollbarHome` related components. +- Updated dependencies [ceef4dd89] +- Updated dependencies [720149854] +- Updated dependencies [19172f5a9] +- Updated dependencies [4c6a6dddd] +- Updated dependencies [398e1f83e] +- Updated dependencies [87b189d00] +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [7fc89bae2] +- Updated dependencies [c810082ae] +- Updated dependencies [b712841d6] +- Updated dependencies [a5628df40] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [bc5082a00] +- Updated dependencies [6e612ce25] +- Updated dependencies [e44925723] +- Updated dependencies [b37501a3d] +- Updated dependencies [9d6ef14bc] +- Updated dependencies [025e122c3] +- Updated dependencies [e9aab60c7] +- Updated dependencies [21e624ba9] +- Updated dependencies [0269f4fd9] +- Updated dependencies [19fe61c27] +- Updated dependencies [da9f53c60] +- Updated dependencies [a08c4b0b0] +- Updated dependencies [bc5082a00] +- Updated dependencies [bc5082a00] +- Updated dependencies [b37501a3d] +- Updated dependencies [90c8f20b9] +- Updated dependencies [32c95605f] +- Updated dependencies [7881f2117] +- Updated dependencies [8dfdec613] +- Updated dependencies [54c7d02f7] +- Updated dependencies [de98c32ed] +- Updated dependencies [806929fe2] +- Updated dependencies [019fe39a0] +- Updated dependencies [019fe39a0] +- Updated dependencies [11cb5ef94] + - @backstage/plugin-catalog-import@0.3.7 + - @backstage/plugin-scaffolder@0.4.2 + - @backstage/plugin-cost-insights@0.8.0 + - @backstage/cli@0.6.0 + - @backstage/plugin-graphiql@0.2.7 + - @backstage/core@0.6.0 + - @backstage/plugin-api-docs@0.4.4 + - @backstage/plugin-catalog@0.3.0 + - @backstage/plugin-catalog-react@0.0.2 + - @backstage/plugin-org@0.3.5 + - @backstage/theme@0.2.3 + - @backstage/plugin-lighthouse@0.2.9 + - @backstage/plugin-techdocs@0.5.5 + - @backstage/plugin-user-settings@0.2.5 + - @backstage/catalog-model@0.7.1 + - @backstage/plugin-rollbar@0.2.9 + - @backstage/plugin-gcp-projects@0.2.4 + - @backstage/plugin-tech-radar@0.3.4 + - @backstage/plugin-welcome@0.2.5 + - @backstage/plugin-explore@0.2.4 + - @backstage/plugin-circleci@0.2.7 + - @backstage/plugin-cloudbuild@0.2.8 + - @backstage/plugin-github-actions@0.3.1 + - @backstage/plugin-jenkins@0.3.8 + - @backstage/plugin-kafka@0.2.1 + - @backstage/plugin-register-component@0.2.8 + - @backstage/plugin-search@0.2.7 + - @backstage/plugin-sentry@0.3.4 + - @backstage/plugin-gitops-profiles@0.2.4 + - @backstage/plugin-kubernetes@0.3.8 + - @backstage/plugin-newrelic@0.2.4 + - @backstage/plugin-pagerduty@0.2.7 + +## 0.2.13 + +### Patch Changes + +- Updated dependencies [681111228] +- Updated dependencies [12a56cdfe] +- Updated dependencies [8b7ef9f8b] +- Updated dependencies [fac91bcc5] +- Updated dependencies [9dd057662] +- Updated dependencies [234e7d985] +- Updated dependencies [ef7957be4] +- Updated dependencies [0b1182346] +- Updated dependencies [a6e3b9596] + - @backstage/plugin-kubernetes@0.3.7 + - @backstage/cli@0.5.0 + - @backstage/plugin-cost-insights@0.6.0 + - @backstage/plugin-catalog@0.2.14 + - @backstage/plugin-catalog-import@0.3.6 + - @backstage/plugin-scaffolder@0.4.1 + - @backstage/plugin-kafka@0.2.0 + +## 0.2.12 + +### Patch Changes + +- Updated dependencies [def2307f3] +- Updated dependencies [46bba09ea] +- Updated dependencies [efd6ef753] +- Updated dependencies [593632f07] +- Updated dependencies [8c2437c15] +- Updated dependencies [2b514d532] +- Updated dependencies [33846acfc] +- Updated dependencies [b604a9d41] +- Updated dependencies [d014185db] +- Updated dependencies [a187b8ad0] +- Updated dependencies [8855f61f6] +- Updated dependencies [ed6baab66] +- Updated dependencies [f04db53d7] +- Updated dependencies [a5e27d5c1] +- Updated dependencies [debf359b5] +- Updated dependencies [a93f42213] + - @backstage/catalog-model@0.7.0 + - @backstage/plugin-github-actions@0.3.0 + - @backstage/core@0.5.0 + - @backstage/plugin-catalog@0.2.12 + - @backstage/plugin-cost-insights@0.5.7 + - @backstage/plugin-catalog-import@0.3.5 + - @backstage/cli@0.4.7 + - @backstage/plugin-kubernetes@0.3.6 + - @backstage/plugin-api-docs@0.4.3 + - @backstage/plugin-scaffolder@0.4.0 + - @backstage/plugin-techdocs@0.5.4 + - @backstage/plugin-lighthouse@0.2.8 + - @backstage/plugin-circleci@0.2.6 + - @backstage/plugin-cloudbuild@0.2.7 + - @backstage/plugin-jenkins@0.3.6 + - @backstage/plugin-kafka@0.1.1 + - @backstage/plugin-org@0.3.4 + - @backstage/plugin-pagerduty@0.2.6 + - @backstage/plugin-register-component@0.2.7 + - @backstage/plugin-rollbar@0.2.8 + - @backstage/plugin-search@0.2.6 + - @backstage/plugin-sentry@0.3.3 + - @backstage/plugin-explore@0.2.3 + - @backstage/plugin-gcp-projects@0.2.3 + - @backstage/plugin-gitops-profiles@0.2.3 + - @backstage/plugin-graphiql@0.2.6 + - @backstage/plugin-newrelic@0.2.3 + - @backstage/plugin-tech-radar@0.3.3 + - @backstage/plugin-user-settings@0.2.4 + - @backstage/plugin-welcome@0.2.4 + +## 0.2.9 + +### Patch Changes + +- Updated dependencies [ab0892358] +- Updated dependencies [37a7d26c4] +- Updated dependencies [8e083f41f] +- Updated dependencies [88da267cc] +- Updated dependencies [9c09a364f] +- Updated dependencies [01707438b] +- Updated dependencies [edb7d0775] +- Updated dependencies [818d45e94] +- Updated dependencies [0588be01f] +- Updated dependencies [b8abdda57] +- Updated dependencies [b7a124883] +- Updated dependencies [bc909178d] +- Updated dependencies [947d3c269] + - @backstage/plugin-cost-insights@0.5.5 + - @backstage/plugin-tech-radar@0.3.2 + - @backstage/cli@0.4.5 + - @backstage/plugin-scaffolder@0.3.6 + - @backstage/plugin-sentry@0.3.2 + - @backstage/plugin-catalog@0.2.10 + - @backstage/plugin-search@0.2.5 + - @backstage/plugin-catalog-import@0.3.3 + - @backstage/plugin-pagerduty@0.2.5 + +## 0.2.8 + +### Patch Changes + +- Updated dependencies [19554f6d6] +- Updated dependencies [1dc445e89] +- Updated dependencies [342270e4d] +- Updated dependencies [7d72f9b09] + - @backstage/cli@0.4.3 + - @backstage/plugin-github-actions@0.2.6 + - @backstage/plugin-scaffolder@0.3.5 + - @backstage/core@0.4.2 + - @backstage/test-utils@0.1.6 + - @backstage/plugin-graphiql@0.2.3 + - @backstage/plugin-catalog@0.2.8 + - @backstage/plugin-pagerduty@0.2.4 + - @backstage/plugin-techdocs@0.5.1 + +## 0.2.7 + +### Patch Changes + +- Updated dependencies [c0fac6163] +- Updated dependencies [48c305e69] +- Updated dependencies [c911061b7] +- Updated dependencies [ab805860a] +- Updated dependencies [dae4f3983] +- Updated dependencies [8ef71ed32] +- Updated dependencies [0e6298f7e] +- Updated dependencies [f3e75508d] +- Updated dependencies [3b50f833d] +- Updated dependencies [c36a01b4c] +- Updated dependencies [c5297baeb] +- Updated dependencies [ac3560b42] +- Updated dependencies [962d1ad66] + - @backstage/plugin-org@0.3.2 + - @backstage/plugin-cost-insights@0.5.2 + - @backstage/catalog-model@0.6.0 + - @backstage/plugin-techdocs@0.5.0 + - @backstage/core@0.4.1 + - @backstage/plugin-catalog-import@0.3.2 + - @backstage/plugin-pagerduty@0.2.3 + - @backstage/cli@0.4.2 + - @backstage/plugin-sentry@0.3.1 + - @backstage/plugin-api-docs@0.4.1 + - @backstage/plugin-catalog@0.2.7 + - @backstage/plugin-circleci@0.2.5 + - @backstage/plugin-cloudbuild@0.2.5 + - @backstage/plugin-github-actions@0.2.5 + - @backstage/plugin-jenkins@0.3.4 + - @backstage/plugin-kubernetes@0.3.3 + - @backstage/plugin-lighthouse@0.2.6 + - @backstage/plugin-register-component@0.2.5 + - @backstage/plugin-rollbar@0.2.7 + - @backstage/plugin-scaffolder@0.3.4 + - @backstage/plugin-search@0.2.4 + +## 0.2.6 + +### Patch Changes + +- Updated dependencies [b2a07d2dc] +- Updated dependencies [075d3dc5a] +- Updated dependencies [e3071a0d4] +- Updated dependencies [d6e8099ed] +- Updated dependencies [88ef11b45] +- Updated dependencies [06dbe707b] +- Updated dependencies [2527628e1] +- Updated dependencies [011708102] +- Updated dependencies [04efbbdd2] +- Updated dependencies [90458fed6] +- Updated dependencies [6011b7d3e] +- Updated dependencies [79418ddb6] +- Updated dependencies [d2938af4c] +- Updated dependencies [61897fb2c] +- Updated dependencies [e1f4e24ef] +- Updated dependencies [6a0d7a9fb] +- Updated dependencies [0f8877168] +- Updated dependencies [1c69d4716] +- Updated dependencies [246799c7f] +- Updated dependencies [83b6e0c1f] +- Updated dependencies [87a33d2fe] +- Updated dependencies [1665ae8bb] +- Updated dependencies [04f26f88d] +- Updated dependencies [ff243ce96] +- Updated dependencies [2b71db211] + - @backstage/plugin-jenkins@0.3.3 + - @backstage/plugin-sentry@0.3.0 + - @backstage/plugin-cost-insights@0.5.0 + - @backstage/cli@0.4.1 + - @backstage/core@0.4.0 + - @backstage/plugin-circleci@0.2.4 + - @backstage/plugin-catalog@0.2.6 + - @backstage/plugin-pagerduty@0.2.2 + - @backstage/plugin-catalog-import@0.3.1 + - @backstage/test-utils@0.1.5 + - @backstage/plugin-search@0.2.3 + - @backstage/plugin-api-docs@0.4.0 + - @backstage/catalog-model@0.5.0 + - @backstage/plugin-techdocs@0.4.0 + - @backstage/theme@0.2.2 + - @backstage/plugin-org@0.3.1 + - @backstage/plugin-cloudbuild@0.2.4 + - @backstage/plugin-explore@0.2.2 + - @backstage/plugin-gcp-projects@0.2.2 + - @backstage/plugin-github-actions@0.2.4 + - @backstage/plugin-gitops-profiles@0.2.2 + - @backstage/plugin-graphiql@0.2.2 + - @backstage/plugin-kubernetes@0.3.2 + - @backstage/plugin-lighthouse@0.2.5 + - @backstage/plugin-newrelic@0.2.2 + - @backstage/plugin-register-component@0.2.4 + - @backstage/plugin-rollbar@0.2.6 + - @backstage/plugin-scaffolder@0.3.3 + - @backstage/plugin-tech-radar@0.3.1 + - @backstage/plugin-user-settings@0.2.3 + - @backstage/plugin-welcome@0.2.3 + +## 0.2.5 + +### Patch Changes + +- Updated dependencies [7eb8bfe4a] +- Updated dependencies [fe7257ff0] +- Updated dependencies [a2cfa311a] +- Updated dependencies [69f38457f] +- Updated dependencies [bec334b33] +- Updated dependencies [303c5ea17] +- Updated dependencies [b4488ddb0] +- Updated dependencies [4a655c89d] +- Updated dependencies [08835a61d] +- Updated dependencies [a9fd599f7] +- Updated dependencies [8a16e8af8] +- Updated dependencies [bcc211a08] +- Updated dependencies [00670a96e] +- Updated dependencies [da2ad65cb] +- Updated dependencies [ebf37bbae] + - @backstage/plugin-api-docs@0.3.1 + - @backstage/plugin-cost-insights@0.4.2 + - @backstage/plugin-sentry@0.2.4 + - @backstage/plugin-welcome@0.2.2 + - @backstage/cli@0.4.0 + - @backstage/catalog-model@0.4.0 + - @backstage/plugin-catalog-import@0.3.0 + - @backstage/plugin-scaffolder@0.3.2 + - @backstage/plugin-kubernetes@0.3.1 + - @backstage/plugin-techdocs@0.3.1 + - @backstage/plugin-catalog@0.2.5 + - @backstage/test-utils@0.1.4 + - @backstage/plugin-circleci@0.2.3 + - @backstage/plugin-cloudbuild@0.2.3 + - @backstage/plugin-github-actions@0.2.3 + - @backstage/plugin-jenkins@0.3.2 + - @backstage/plugin-lighthouse@0.2.4 + - @backstage/plugin-register-component@0.2.3 + - @backstage/plugin-rollbar@0.2.5 + - @backstage/plugin-search@0.2.2 + +## 0.2.4 + +### Patch Changes + +- Updated dependencies [294295453] +- Updated dependencies [f3bb55ee3] +- Updated dependencies [4b53294a6] +- Updated dependencies [6f70ed7a9] +- Updated dependencies [ab94c9542] +- Updated dependencies [3a201c5d5] +- Updated dependencies [f538e2c56] +- Updated dependencies [2daf18e80] +- Updated dependencies [069cda35f] +- Updated dependencies [8697dea5b] +- Updated dependencies [b623cc275] + - @backstage/cli@0.3.2 + - @backstage/plugin-api-docs@0.3.0 + - @backstage/plugin-techdocs@0.3.0 + - @backstage/plugin-catalog@0.2.4 + - @backstage/catalog-model@0.3.1 + - @backstage/plugin-rollbar@0.2.4 + +## 0.2.3 + +### Patch Changes + +- 475fc0aaa: Using the search field in the sidebar now navigates to the search result page. +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [29a0ccab2] +- Updated dependencies [8e6728e25] +- Updated dependencies [c93a14b49] +- Updated dependencies [ef2831dde] +- Updated dependencies [2a71f4bab] +- Updated dependencies [1185919f3] +- Updated dependencies [a8de7f554] +- Updated dependencies [faf311c26] +- Updated dependencies [31d8b6979] +- Updated dependencies [991345969] +- Updated dependencies [475fc0aaa] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-kubernetes@0.3.0 + - @backstage/cli@0.3.1 + - @backstage/plugin-cost-insights@0.4.1 + - @backstage/plugin-scaffolder@0.3.1 + - @backstage/plugin-register-component@0.2.2 + - @backstage/plugin-circleci@0.2.2 + - @backstage/plugin-search@0.2.1 + - @backstage/plugin-api-docs@0.2.2 + - @backstage/plugin-catalog@0.2.3 + - @backstage/plugin-cloudbuild@0.2.2 + - @backstage/plugin-github-actions@0.2.2 + - @backstage/plugin-jenkins@0.3.1 + - @backstage/plugin-lighthouse@0.2.3 + - @backstage/plugin-rollbar@0.2.3 + - @backstage/plugin-sentry@0.2.3 + - @backstage/plugin-techdocs@0.2.3 + +## 0.2.2 + +### Patch Changes + +- 3efd03c0e: Removed obsolete CircleCI proxy config from example-app +- Updated dependencies [1722cb53c] +- Updated dependencies [1722cb53c] +- Updated dependencies [17a9f48f6] +- Updated dependencies [4040d4fcb] +- Updated dependencies [f360395d0] +- Updated dependencies [259d848ee] +- Updated dependencies [8b7737d0b] +- Updated dependencies [902340451] + - @backstage/cli@0.3.0 + - @backstage/core@0.3.1 + - @backstage/plugin-cost-insights@0.4.0 + - @backstage/plugin-lighthouse@0.2.2 + - @backstage/plugin-rollbar@0.2.2 + - @backstage/plugin-sentry@0.2.2 + - @backstage/plugin-techdocs@0.2.2 + - @backstage/plugin-user-settings@0.2.2 + - @backstage/plugin-catalog@0.2.2 + - @backstage/test-utils@0.1.3 + +## 0.2.1 + +### Patch Changes + +- Updated dependencies [7b37d65fd] +- Updated dependencies [4aca74e08] +- Updated dependencies [a41730c6e] +- Updated dependencies [9a294574c] +- Updated dependencies [0703edee0] +- Updated dependencies [e8f69ba93] +- Updated dependencies [0c0798f08] +- Updated dependencies [5a2705de2] +- Updated dependencies [0c0798f08] +- Updated dependencies [84b654d5d] +- Updated dependencies [0c0798f08] +- Updated dependencies [199237d2f] +- Updated dependencies [6627b626f] +- Updated dependencies [3f05616bf] +- Updated dependencies [803527bd3] +- Updated dependencies [4577e377b] +- Updated dependencies [2d0bd1be7] +- Updated dependencies [59166e5ec] +- Updated dependencies [a906f20e7] + - @backstage/core@0.3.0 + - @backstage/plugin-jenkins@0.3.0 + - @backstage/plugin-cost-insights@0.3.0 + - @backstage/plugin-user-settings@0.2.1 + - @backstage/plugin-api-docs@0.2.1 + - @backstage/plugin-tech-radar@0.3.0 + - @backstage/theme@0.2.1 + - @backstage/plugin-catalog@0.2.1 + - @backstage/plugin-scaffolder@0.3.0 + - @backstage/plugin-circleci@0.2.1 + - @backstage/plugin-cloudbuild@0.2.1 + - @backstage/plugin-explore@0.2.1 + - @backstage/plugin-gcp-projects@0.2.1 + - @backstage/plugin-github-actions@0.2.1 + - @backstage/plugin-gitops-profiles@0.2.1 + - @backstage/plugin-graphiql@0.2.1 + - @backstage/plugin-kubernetes@0.2.1 + - @backstage/plugin-lighthouse@0.2.1 + - @backstage/plugin-newrelic@0.2.1 + - @backstage/plugin-register-component@0.2.1 + - @backstage/plugin-rollbar@0.2.1 + - @backstage/plugin-sentry@0.2.1 + - @backstage/plugin-techdocs@0.2.1 + - @backstage/plugin-welcome@0.2.1 + +## 0.2.0 + +### Minor Changes + +- 6d97d2d6f: The InfoCard variant `'height100'` is deprecated. Use variant `'gridItem'` instead. + + When the InfoCard is displayed as a grid item within a grid, you may want items to have the same height for all items. + Set to the `'gridItem'` variant to display the InfoCard with full height suitable for Grid: + `...` + + Changed the InfoCards in '@backstage/plugin-github-actions', '@backstage/plugin-jenkins', '@backstage/plugin-lighthouse' + to pass an optional variant to the corresponding card of the plugin. + + As a result the overview content of the EntityPage shows cards with full height suitable for Grid. + +### Patch Changes + +- 65d722455: Add Pull Request tab to components view. +- 26e69ab1a: Remove cost insights example client from demo app and export from plugin + Create cost insights dev plugin using example client + Make PluginConfig and dependent types public +- e7f5471fd: cleaning up because external plugins have already implemented new api for creating +- Updated dependencies [28edd7d29] +- Updated dependencies [819a70229] +- Updated dependencies [2846ef95c] +- Updated dependencies [3a4236570] +- Updated dependencies [ae5983387] +- Updated dependencies [0d4459c08] +- Updated dependencies [d67c529ab] +- Updated dependencies [482b6313d] +- Updated dependencies [e0be86b6f] +- Updated dependencies [f70a52868] +- Updated dependencies [12b5fe940] +- Updated dependencies [8351ad79b] +- Updated dependencies [30dd11122] +- Updated dependencies [1297dcb3a] +- Updated dependencies [368fd8243] +- Updated dependencies [fb74f1db6] +- Updated dependencies [3472c8be7] +- Updated dependencies [cab473771] +- Updated dependencies [1d0aec70f] +- Updated dependencies [1c60f716e] +- Updated dependencies [a73979d45] +- Updated dependencies [144c66d50] +- Updated dependencies [a768a07fb] +- Updated dependencies [a3840bed2] +- Updated dependencies [339668995] +- Updated dependencies [b79017fd3] +- Updated dependencies [6d97d2d6f] +- Updated dependencies [5adfc005e] +- Updated dependencies [f0aa01bcc] +- Updated dependencies [8d1360aa9] +- Updated dependencies [72f6cda35] +- Updated dependencies [0ee9e9f66] +- Updated dependencies [5c70f3d35] +- Updated dependencies [bb48b9833] +- Updated dependencies [fd8384d7e] +- Updated dependencies [8c2b76e45] +- Updated dependencies [0aecfded0] +- Updated dependencies [93a3fa3ae] +- Updated dependencies [782f3b354] +- Updated dependencies [c5ef12926] +- Updated dependencies [8b9c8196f] +- Updated dependencies [2713f28f4] +- Updated dependencies [6a84cb072] +- Updated dependencies [406015b0d] +- Updated dependencies [82759d3e4] +- Updated dependencies [60d40892c] +- Updated dependencies [cba4e4d97] +- Updated dependencies [ac8d5d5c7] +- Updated dependencies [2ebcfac8d] +- Updated dependencies [4fc1d440e] +- Updated dependencies [fa56f4615] +- Updated dependencies [8afce088a] +- Updated dependencies [4512b9967] +- Updated dependencies [ebca83d48] +- Updated dependencies [aca79334f] +- Updated dependencies [c0d5242a0] +- Updated dependencies [b3d57961c] +- Updated dependencies [9a3b3dbf1] +- Updated dependencies [e7d4ac7ce] +- Updated dependencies [0b956f21b] +- Updated dependencies [1c8c43756] +- Updated dependencies [0e67c6b40] +- Updated dependencies [26e69ab1a] +- Updated dependencies [97c2cb19b] +- Updated dependencies [02c60b5f8] +- Updated dependencies [3beb5c9fc] +- Updated dependencies [754e31db5] +- Updated dependencies [57b54c8ed] +- Updated dependencies [1611c6dbc] +- Updated dependencies [7bbeb049f] + - @backstage/cli@0.2.0 + - @backstage/plugin-api-docs@0.2.0 + - @backstage/plugin-catalog@0.2.0 + - @backstage/plugin-circleci@0.2.0 + - @backstage/plugin-explore@0.2.0 + - @backstage/plugin-gcp-projects@0.2.0 + - @backstage/plugin-github-actions@0.2.0 + - @backstage/plugin-gitops-profiles@0.2.0 + - @backstage/plugin-graphiql@0.2.0 + - @backstage/plugin-jenkins@0.2.0 + - @backstage/plugin-kubernetes@0.2.0 + - @backstage/plugin-lighthouse@0.2.0 + - @backstage/plugin-newrelic@0.2.0 + - @backstage/plugin-register-component@0.2.0 + - @backstage/plugin-rollbar@0.2.0 + - @backstage/plugin-scaffolder@0.2.0 + - @backstage/plugin-sentry@0.2.0 + - @backstage/plugin-tech-radar@0.2.0 + - @backstage/plugin-techdocs@0.2.0 + - @backstage/plugin-welcome@0.2.0 + - @backstage/core@0.2.0 + - @backstage/plugin-cloudbuild@0.2.0 + - @backstage/catalog-model@0.2.0 + - @backstage/theme@0.2.0 + - @backstage/plugin-cost-insights@0.2.0 + - @backstage/plugin-user-settings@0.2.0 + - @backstage/test-utils@0.1.2 diff --git a/packages/app-legacy/README.md b/packages/app-legacy/README.md new file mode 100644 index 0000000000..1035c192df --- /dev/null +++ b/packages/app-legacy/README.md @@ -0,0 +1,5 @@ +# example-app-legacy + +This package is an example of a Backstage application using the old (legacy) frontend system. + +**NOTE:** This is the legacy frontend system. For new projects, use `packages/app` which uses the new frontend system. diff --git a/packages/app-next/catalog-info.yaml b/packages/app-legacy/catalog-info.yaml similarity index 73% rename from packages/app-next/catalog-info.yaml rename to packages/app-legacy/catalog-info.yaml index 14e51ecd04..bf729b380c 100644 --- a/packages/app-next/catalog-info.yaml +++ b/packages/app-legacy/catalog-info.yaml @@ -1,8 +1,8 @@ apiVersion: backstage.io/v1alpha1 kind: Component metadata: - name: example-app-next - title: example-app-next + name: example-app-legacy + title: example-app-legacy spec: lifecycle: experimental type: backstage-frontend diff --git a/packages/app-legacy/knip-report.md b/packages/app-legacy/knip-report.md new file mode 100644 index 0000000000..a04cab24f5 --- /dev/null +++ b/packages/app-legacy/knip-report.md @@ -0,0 +1,30 @@ +# Knip report + +## Unused dependencies (8) + +| Name | Location | Severity | +| :------------------------------ | :---------------- | :------- | +| @backstage/plugin-search-common | package.json:67:6 | error | +| @backstage/plugin-auth-react | package.json:48:6 | error | +| @backstage/plugin-home-react | package.json:57:6 | error | +| @backstage/frontend-app-api | package.json:45:6 | error | +| @material-ui/lab | package.json:78:6 | error | +| zen-observable | package.json:86:6 | error | +| @octokit/rest | package.json:79:6 | error | +| history | package.json:80:6 | error | + +## Unused devDependencies (4) + +| Name | Location | Severity | +| :-------------------------- | :---------------- | :------- | +| @testing-library/user-event | package.json:94:6 | error | +| @types/zen-observable | package.json:98:6 | error | +| @playwright/test | package.json:90:6 | error | +| @types/jquery | package.json:95:6 | error | + +## Unlisted dependencies (1) + +| Name | Location | Severity | +| :---------- | :------------------------------------------------------------- | :------- | +| @rjsf/utils | src/components/scaffolder/customScaffolderExtensions.tsx:16:39 | error | + diff --git a/packages/app-next/package.json b/packages/app-legacy/package.json similarity index 83% rename from packages/app-next/package.json rename to packages/app-legacy/package.json index 5dae3c9750..58bbd2d42d 100644 --- a/packages/app-next/package.json +++ b/packages/app-legacy/package.json @@ -1,14 +1,15 @@ { - "name": "example-app-next", - "version": "0.0.29-next.2", + "name": "example-app-legacy", + "version": "0.2.120", "backstage": { "role": "frontend" }, "private": true, + "homepage": "https://backstage.io", "repository": { "type": "git", "url": "https://github.com/backstage/backstage", - "directory": "packages/app-next" + "directory": "packages/app-legacy" }, "license": "Apache-2.0", "files": [ @@ -18,7 +19,7 @@ "build": "backstage-cli package build", "clean": "backstage-cli package clean", "lint": "backstage-cli package lint", - "start": "backstage-cli package start --config ../../app-config.yaml --config app-config.yaml", + "start": "cross-env EXPERIMENTAL_LAZY_COMPILATION=1 backstage-cli package start", "test": "backstage-cli package test" }, "browserslist": { @@ -39,17 +40,11 @@ "@backstage/cli": "workspace:^", "@backstage/config": "workspace:^", "@backstage/core-app-api": "workspace:^", - "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-app-api": "workspace:^", - "@backstage/frontend-defaults": "workspace:^", - "@backstage/frontend-plugin-api": "workspace:^", "@backstage/integration-react": "workspace:^", "@backstage/plugin-api-docs": "workspace:^", - "@backstage/plugin-app": "workspace:^", - "@backstage/plugin-app-visualizer": "workspace:^", - "@backstage/plugin-auth": "workspace:^", "@backstage/plugin-auth-react": "workspace:^", "@backstage/plugin-catalog": "workspace:^", "@backstage/plugin-catalog-common": "workspace:^", @@ -57,9 +52,12 @@ "@backstage/plugin-catalog-import": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", "@backstage/plugin-catalog-unprocessed-entities": "workspace:^", + "@backstage/plugin-devtools": "workspace:^", "@backstage/plugin-home": "workspace:^", + "@backstage/plugin-home-react": "workspace:^", "@backstage/plugin-kubernetes": "workspace:^", "@backstage/plugin-kubernetes-cluster": "workspace:^", + "@backstage/plugin-mui-to-bui": "workspace:^", "@backstage/plugin-notifications": "workspace:^", "@backstage/plugin-org": "workspace:^", "@backstage/plugin-permission-react": "workspace:^", @@ -79,25 +77,29 @@ "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", "@octokit/rest": "^19.0.3", + "@rjsf/utils": "5.24.13", "history": "^5.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router": "^6.3.0", - "react-router-dom": "^6.3.0", + "react-router": "^6.30.2", + "react-router-dom": "^6.30.2", "react-use": "^17.2.4", "zen-observable": "^0.10.0" }, "devDependencies": { "@backstage/test-utils": "workspace:^", + "@playwright/test": "^1.32.3", "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.0.0", - "@types/jquery": "^3.3.34", + "@types/jquery": "^4.0.0", "@types/react": "*", "@types/react-dom": "*", "@types/zen-observable": "^0.8.0", - "cross-env": "^7.0.0" + "axios": "^1.15.0", + "cross-env": "^10.0.0", + "msw": "^1.0.0" }, "bundled": true } diff --git a/packages/app-next/public/android-chrome-192x192.png b/packages/app-legacy/public/android-chrome-192x192.png similarity index 100% rename from packages/app-next/public/android-chrome-192x192.png rename to packages/app-legacy/public/android-chrome-192x192.png diff --git a/packages/app-next/public/android-chrome-512x512.png b/packages/app-legacy/public/android-chrome-512x512.png similarity index 100% rename from packages/app-next/public/android-chrome-512x512.png rename to packages/app-legacy/public/android-chrome-512x512.png diff --git a/packages/app-next/public/apple-touch-icon.png b/packages/app-legacy/public/apple-touch-icon.png similarity index 100% rename from packages/app-next/public/apple-touch-icon.png rename to packages/app-legacy/public/apple-touch-icon.png diff --git a/packages/app-next/public/favicon-16x16.png b/packages/app-legacy/public/favicon-16x16.png similarity index 100% rename from packages/app-next/public/favicon-16x16.png rename to packages/app-legacy/public/favicon-16x16.png diff --git a/packages/app-next/public/favicon-32x32.png b/packages/app-legacy/public/favicon-32x32.png similarity index 100% rename from packages/app-next/public/favicon-32x32.png rename to packages/app-legacy/public/favicon-32x32.png diff --git a/packages/app-next/public/favicon.ico b/packages/app-legacy/public/favicon.ico similarity index 100% rename from packages/app-next/public/favicon.ico rename to packages/app-legacy/public/favicon.ico diff --git a/packages/app-next/public/favicon.svg b/packages/app-legacy/public/favicon.svg similarity index 100% rename from packages/app-next/public/favicon.svg rename to packages/app-legacy/public/favicon.svg diff --git a/packages/app-next/public/index.html b/packages/app-legacy/public/index.html similarity index 97% rename from packages/app-next/public/index.html rename to packages/app-legacy/public/index.html index 74387edea3..9b67673b2e 100644 --- a/packages/app-next/public/index.html +++ b/packages/app-legacy/public/index.html @@ -42,7 +42,7 @@ href="<%= publicPath %>/safari-pinned-tab.svg" color="#5bbad5" /> - <%= config.getString('app.title') %> + <%= config.getOptionalString('app.title') ?? 'Backstage' %> <% if (config.has('app.datadogRum')) { %>

    Safe Content

    ' + dialect="gfm" + />, + ); + + expect(screen.getByText('Safe Content')).toBeInTheDocument(); + expect(container.querySelector('script')).toBeNull(); + expect(container.querySelector('style')).toBeNull(); + }); }); diff --git a/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx b/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx index 5ad23cce09..4098a6e52d 100644 --- a/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx +++ b/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx @@ -20,6 +20,10 @@ import gfm from 'remark-gfm'; import { Children, createElement } from 'react'; import { CodeSnippet } from '../CodeSnippet'; import { HeadingProps } from 'react-markdown/lib/ast-to-react'; +import rehypeRaw from 'rehype-raw'; +import rehypeSanitize, { defaultSchema } from 'rehype-sanitize'; +import type { PluggableList } from 'react-markdown/lib/react-markdown'; +import { Link } from '../Link'; export type MarkdownContentClassKey = 'markdown'; @@ -106,8 +110,46 @@ const components: Options['components'] = { h4: headingRenderer, h5: headingRenderer, h6: headingRenderer, + + a: ({ href, children, title, target, rel }) => + href ? ( + + {children} + + ) : ( + <>{children} + ), }; +const gfmRehypePlugins: PluggableList = [ + [ + rehypeRaw, + { + tagFiter: true, + }, + ], + [ + rehypeSanitize, + { + ...defaultSchema, + attributes: { + ...defaultSchema.attributes, + code: [ + ...(defaultSchema.attributes?.code ?? []), + // for syntax highlighting classes in code blocks + // breaks the codesnippet component override above if omitted + ['className'], + ], + }, + }, + ], +]; + /** * Renders markdown with the default dialect {@link https://github.github.com/gfm/ | gfm - GitHub flavored Markdown} to backstage theme styled HTML. * @@ -127,6 +169,7 @@ export function MarkdownContent(props: Props) { return ( { try { await request.trigger(); } catch (e) { - setError(isError(e) ? e.message : 'An unspecified error occurred'); + setError(toError(e).message); } finally { setBusy(false); } }; - const IconComponent = request.provider.icon; + const providerIcon = request.provider.icon; const message = request.provider.message ?? t('oauthRequestDialog.message', { @@ -76,11 +77,14 @@ const LoginRequestListItem = ({ request, busy, setBusy }: RowProps) => { provider: request.provider.title, }); + const iconElement = + providerIcon === null || isValidElement(providerIcon) + ? providerIcon + : createElement(providerIcon as IconComponent, { fontSize: 'large' }); + return ( - - - + {iconElement ?? <>} { secondary={ <> {message && ( - + {message} )} - {error && {error}} + {error && ( + + {error} + + )} } /> diff --git a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.stories.tsx b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.stories.tsx index 3b5df0e358..f48002d018 100644 --- a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.stories.tsx +++ b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.stories.tsx @@ -19,6 +19,7 @@ import { OverflowTooltip } from './OverflowTooltip'; export default { title: 'Data Display/OverflowTooltip', component: OverflowTooltip, + tags: ['!manifest'], }; const text = diff --git a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx index 68499e2dc1..7fc45d767d 100644 --- a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx +++ b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx @@ -23,6 +23,7 @@ type Props = { title?: TooltipProps['title']; line?: number | undefined; placement?: TooltipProps['placement']; + tooltipClasses?: TooltipProps['classes']; }; export type OverflowTooltipClassKey = 'container'; @@ -51,6 +52,7 @@ export function OverflowTooltip(props: Props) { {props.text} diff --git a/packages/core-components/src/components/Progress/Progress.stories.tsx b/packages/core-components/src/components/Progress/Progress.stories.tsx index 7c7932101e..7120f699bf 100644 --- a/packages/core-components/src/components/Progress/Progress.stories.tsx +++ b/packages/core-components/src/components/Progress/Progress.stories.tsx @@ -19,6 +19,7 @@ import { Progress } from './Progress'; export default { title: 'Feedback/Progress', component: Progress, + tags: ['!manifest'], }; export const progress = () => ; diff --git a/packages/core-components/src/components/Progress/Progress.test.tsx b/packages/core-components/src/components/Progress/Progress.test.tsx index 918c9ba518..f72454754e 100644 --- a/packages/core-components/src/components/Progress/Progress.test.tsx +++ b/packages/core-components/src/components/Progress/Progress.test.tsx @@ -15,6 +15,7 @@ */ import { renderInTestApp } from '@backstage/test-utils'; +import { screen } from '@testing-library/react'; import { Progress } from './Progress'; @@ -23,4 +24,11 @@ describe('', () => { const { queryByTestId } = await renderInTestApp(); expect(queryByTestId('progress')).toBeInTheDocument(); }); + + it('provides an accessible name for the progress bar', async () => { + await renderInTestApp(); + expect( + await screen.findByRole('progressbar', { name: 'Loading' }), + ).toBeInTheDocument(); + }); }); diff --git a/packages/core-components/src/components/Progress/Progress.tsx b/packages/core-components/src/components/Progress/Progress.tsx index b7176a5825..f1dea0ed5c 100644 --- a/packages/core-components/src/components/Progress/Progress.tsx +++ b/packages/core-components/src/components/Progress/Progress.tsx @@ -22,6 +22,7 @@ import { useTheme } from '@material-ui/core/styles'; import { PropsWithChildren, useEffect, useState } from 'react'; export function Progress(props: PropsWithChildren) { + const { 'aria-label': ariaLabel, ...progressProps } = props; const theme = useTheme(); const [isVisible, setIsVisible] = useState(false); @@ -34,7 +35,11 @@ export function Progress(props: PropsWithChildren) { }, [theme.transitions.duration.short]); return isVisible ? ( - + ) : ( ); diff --git a/packages/core-components/src/components/ProgressBars/Gauge.stories.tsx b/packages/core-components/src/components/ProgressBars/Gauge.stories.tsx index 4dcb788289..9578a35864 100644 --- a/packages/core-components/src/components/ProgressBars/Gauge.stories.tsx +++ b/packages/core-components/src/components/ProgressBars/Gauge.stories.tsx @@ -21,6 +21,7 @@ const containerStyle = { width: 300 }; export default { title: 'Data Display/Gauge', component: Gauge, + tags: ['!manifest'], }; export const Default = () => ( diff --git a/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx b/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx index 0377c37ef0..6b673a65d5 100644 --- a/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx +++ b/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx @@ -34,6 +34,7 @@ const Wrapper = ({ children }: PropsWithChildren<{}>) => ( export default { title: 'Data Display/Progress Card', component: GaugeCard, + tags: ['!manifest'], }; export const Default = () => ( diff --git a/packages/core-components/src/components/ProgressBars/LinearGauge.stories.tsx b/packages/core-components/src/components/ProgressBars/LinearGauge.stories.tsx index 295856bd51..eebfb7356c 100644 --- a/packages/core-components/src/components/ProgressBars/LinearGauge.stories.tsx +++ b/packages/core-components/src/components/ProgressBars/LinearGauge.stories.tsx @@ -21,6 +21,7 @@ const containerStyle = { width: 300 }; export default { title: 'Data Display/LinearGauge', component: LinearGauge, + tags: ['!manifest'], }; export const Default = () => ( diff --git a/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.stories.tsx b/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.stories.tsx index 7eab08f55f..050ae2ce29 100644 --- a/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.stories.tsx +++ b/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.stories.tsx @@ -20,6 +20,7 @@ import { ErrorPanelProps } from '../ErrorPanel'; export default { title: 'Data Display/ResponseErrorPanel', component: ResponseErrorPanel, + tags: ['!manifest'], }; export const Default = (args: ErrorPanelProps) => ( diff --git a/packages/core-components/src/components/Select/Select.stories.tsx b/packages/core-components/src/components/Select/Select.stories.tsx index f3eaea1021..d6a11244a4 100644 --- a/packages/core-components/src/components/Select/Select.stories.tsx +++ b/packages/core-components/src/components/Select/Select.stories.tsx @@ -19,6 +19,7 @@ import { SelectComponent as Select, SelectProps } from './Select'; export default { title: 'Inputs/Select', component: Select, + tags: ['!manifest'], }; const SELECT_ITEMS = [ diff --git a/packages/core-components/src/components/SimpleStepper/SimpleStepper.stories.tsx b/packages/core-components/src/components/SimpleStepper/SimpleStepper.stories.tsx index a50183b179..d99e1b0a39 100644 --- a/packages/core-components/src/components/SimpleStepper/SimpleStepper.stories.tsx +++ b/packages/core-components/src/components/SimpleStepper/SimpleStepper.stories.tsx @@ -22,6 +22,7 @@ import { SimpleStepperStep } from './SimpleStepperStep'; export default { title: 'Navigation/SimpleStepper', component: SimpleStepper, + tags: ['!manifest'], }; const defaultArgs = { diff --git a/packages/core-components/src/components/Status/Status.stories.tsx b/packages/core-components/src/components/Status/Status.stories.tsx index 1237a02432..1404ae4373 100644 --- a/packages/core-components/src/components/Status/Status.stories.tsx +++ b/packages/core-components/src/components/Status/Status.stories.tsx @@ -28,6 +28,7 @@ import { InfoCard } from '../../layout/InfoCard'; export default { title: 'Data Display/Status', component: StatusOK, + tags: ['!manifest'], }; const data = [ diff --git a/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.stories.tsx b/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.stories.tsx index 8a30300685..2ee11fd23d 100644 --- a/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.stories.tsx +++ b/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.stories.tsx @@ -41,6 +41,7 @@ const metadata = { export default { title: 'Data Display/Structured Metadata Table', component: StructuredMetadataTable, + tags: ['!manifest'], }; const Wrapper = ({ children }: PropsWithChildren<{}>) => ( diff --git a/packages/core-components/src/components/TabbedLayout/TabbedLayout.stories.tsx b/packages/core-components/src/components/TabbedLayout/TabbedLayout.stories.tsx index a4060d9d43..b0a6754fd2 100644 --- a/packages/core-components/src/components/TabbedLayout/TabbedLayout.stories.tsx +++ b/packages/core-components/src/components/TabbedLayout/TabbedLayout.stories.tsx @@ -20,6 +20,7 @@ import { TabbedLayout } from './TabbedLayout'; export default { title: 'Navigation/TabbedLayout', component: TabbedLayout, + tags: ['!manifest'], }; const Wrapper = ({ children }: PropsWithChildren<{}>) => ( diff --git a/packages/core-components/src/components/TabbedLayout/TabbedLayout.test.tsx b/packages/core-components/src/components/TabbedLayout/TabbedLayout.test.tsx index 1be58d2dcf..3e4ab1df7c 100644 --- a/packages/core-components/src/components/TabbedLayout/TabbedLayout.test.tsx +++ b/packages/core-components/src/components/TabbedLayout/TabbedLayout.test.tsx @@ -47,18 +47,20 @@ describe('TabbedLayout', () => { }); expect(error).toEqual([ + expect.stringContaining( + 'Error: Child of TabbedLayout must be an TabbedLayout.Route', + ), expect.objectContaining({ - detail: new Error( - 'Child of TabbedLayout must be an TabbedLayout.Route', - ), + type: 'unhandled-exception', }), + expect.stringContaining( + 'Error: Child of TabbedLayout must be an TabbedLayout.Route', + ), expect.objectContaining({ - detail: new Error( - 'Child of TabbedLayout must be an TabbedLayout.Route', - ), + type: 'unhandled-exception', }), - expect.stringMatching( - /The above error occurred in the component/, + expect.stringContaining( + 'The above error occurred in the component', ), ]); }); diff --git a/packages/core-components/src/components/Table/Table.stories.tsx b/packages/core-components/src/components/Table/Table.stories.tsx index ac8303cde7..c17726d5fd 100644 --- a/packages/core-components/src/components/Table/Table.stories.tsx +++ b/packages/core-components/src/components/Table/Table.stories.tsx @@ -23,6 +23,7 @@ import { Table, TableColumn, TableFilter } from './Table'; export default { title: 'Data Display/Table', component: Table, + tags: ['!manifest'], }; const useStyles = makeStyles(theme => ({ diff --git a/packages/core-components/src/components/Table/Table.test.tsx b/packages/core-components/src/components/Table/Table.test.tsx index dfae66d1c2..075dbcc0cd 100644 --- a/packages/core-components/src/components/Table/Table.test.tsx +++ b/packages/core-components/src/components/Table/Table.test.tsx @@ -68,7 +68,7 @@ describe('
    ', () => {
    , ); expect(rendered.getByText('second value, first row')).toHaveStyle({ - color: 'blue', + color: 'rgb(0, 0, 255)', // blue }); }); @@ -85,7 +85,7 @@ describe('
    ', () => {
    , ); expect(rendered.getByText('second value, first row')).toHaveStyle({ - color: 'blue', + color: 'rgb(0, 0, 255)', // blue 'font-weight': 700, }); }); @@ -115,10 +115,10 @@ describe('
    ', () => {
    , ); expect(rendered.getByText('second value, first row')).toHaveStyle({ - color: 'green', + color: 'rgb(0, 128, 0)', // green }); expect(rendered.getByText('second value, second row')).toHaveStyle({ - color: 'red', + color: 'rgb(255, 0, 0)', // red }); }); @@ -135,11 +135,11 @@ describe('
    ', () => {
    , ); expect(rendered.getByText('second value, first row')).toHaveStyle({ - color: 'green', + color: 'rgb(0, 128, 0)', // green 'font-weight': 700, }); expect(rendered.getByText('second value, second row')).toHaveStyle({ - color: 'red', + color: 'rgb(255, 0, 0)', // red 'font-weight': 700, }); }); @@ -165,11 +165,11 @@ describe('
    ', () => { expect(rendered.getByText(column1.title).closest('th')).not.toHaveStyle( { - backgroundColor: 'pink', + backgroundColor: 'rgb(255, 192, 203)', // pink }, ); expect(rendered.getByText(column2.title).closest('th')).toHaveStyle({ - backgroundColor: 'pink', + backgroundColor: 'rgb(255, 192, 203)', // pink }); }); @@ -209,6 +209,23 @@ describe('
    ', () => { expect(rendered.getByText('subtitle')).toBeInTheDocument(); }); + it('renders with both title and filters without layout issues', async () => { + const rendered = await renderInTestApp( +
    , + ); + expect(rendered.getByText('My Table')).toBeInTheDocument(); + expect(rendered.getByText('Filters (0)')).toBeInTheDocument(); + }); + it('renders custom empty component if empty', async () => { const rendered = await renderInTestApp(
    ({ @@ -186,6 +186,11 @@ const useFilterStyles = makeStyles( display: 'flex', alignItems: 'center', justifyContent: 'space-between', + flexWrap: 'wrap', + }, + filterControls: { + display: 'flex', + alignItems: 'center', }, title: { fontWeight: theme.typography.fontWeightBold, @@ -315,7 +320,7 @@ export function TableToolbar(toolbarProps: { if (hasFilters) { return ( - + diff --git a/packages/core-components/src/components/TrendLine/TrendLine.stories.tsx b/packages/core-components/src/components/TrendLine/TrendLine.stories.tsx index 2f1e9bc4f1..710c0448f4 100644 --- a/packages/core-components/src/components/TrendLine/TrendLine.stories.tsx +++ b/packages/core-components/src/components/TrendLine/TrendLine.stories.tsx @@ -21,6 +21,7 @@ import { InfoCard } from '../../layout/InfoCard'; export default { title: 'Data Display/TrendLine', component: TrendLine, + tags: ['!manifest'], }; const containerStyle = { width: 700 }; diff --git a/packages/core-components/src/components/WarningPanel/WarningPanel.stories.tsx b/packages/core-components/src/components/WarningPanel/WarningPanel.stories.tsx index 7e45f3c398..9fa950dc0c 100644 --- a/packages/core-components/src/components/WarningPanel/WarningPanel.stories.tsx +++ b/packages/core-components/src/components/WarningPanel/WarningPanel.stories.tsx @@ -22,6 +22,7 @@ import { Link } from '../Link'; export default { title: 'Feedback/Warning Panel', component: WarningPanel, + tags: ['!manifest'], }; export const Default = () => ( diff --git a/packages/core-components/src/components/WarningPanel/WarningPanel.tsx b/packages/core-components/src/components/WarningPanel/WarningPanel.tsx index 7d9c4d472d..2e05784fd6 100644 --- a/packages/core-components/src/components/WarningPanel/WarningPanel.tsx +++ b/packages/core-components/src/components/WarningPanel/WarningPanel.tsx @@ -96,6 +96,7 @@ const useStyles = makeStyles( fontWeight: theme.typography.fontWeightBold, }, markdownContent: { + wordBreak: 'break-word', '& p': { display: 'inline', }, diff --git a/packages/core-components/src/index.ts b/packages/core-components/src/index.ts index 3c5e708360..2006858599 100644 --- a/packages/core-components/src/index.ts +++ b/packages/core-components/src/index.ts @@ -25,3 +25,4 @@ export * from './hooks'; export * from './icons'; export * from './layout'; export * from './overridableComponents'; +export { coreComponentsTranslationRef } from './translation'; diff --git a/packages/core-components/src/layout/BottomLink/BottomLink.stories.tsx b/packages/core-components/src/layout/BottomLink/BottomLink.stories.tsx index 93607c35f5..345030b80c 100644 --- a/packages/core-components/src/layout/BottomLink/BottomLink.stories.tsx +++ b/packages/core-components/src/layout/BottomLink/BottomLink.stories.tsx @@ -19,6 +19,7 @@ import { BottomLink } from '../BottomLink'; export default { title: 'Layout/BottomLink', component: BottomLink, + tags: ['!manifest'], }; export const Default = (args: { link: string; title: string }) => ( diff --git a/packages/core-components/src/layout/Breadcrumbs/Breadcrumbs.stories.tsx b/packages/core-components/src/layout/Breadcrumbs/Breadcrumbs.stories.tsx index f962e4673e..cec62304ee 100644 --- a/packages/core-components/src/layout/Breadcrumbs/Breadcrumbs.stories.tsx +++ b/packages/core-components/src/layout/Breadcrumbs/Breadcrumbs.stories.tsx @@ -31,6 +31,7 @@ import { Breadcrumbs } from './Breadcrumbs'; export default { title: 'Layout/Breadcrumbs', component: Breadcrumbs, + tags: ['!manifest'], }; export const InHeader = () => ( diff --git a/packages/core-components/src/layout/Content/Content.stories.tsx b/packages/core-components/src/layout/Content/Content.stories.tsx index 8879c3c51d..eb6947b3f7 100644 --- a/packages/core-components/src/layout/Content/Content.stories.tsx +++ b/packages/core-components/src/layout/Content/Content.stories.tsx @@ -18,6 +18,7 @@ import { Content } from './Content'; export default { title: 'Layout/Content', component: Content, + tags: ['!manifest'], }; type Props = { diff --git a/packages/core-components/src/layout/ContentHeader/ContentHeader.stories.tsx b/packages/core-components/src/layout/ContentHeader/ContentHeader.stories.tsx index 6b4ced0edd..989fba81ce 100644 --- a/packages/core-components/src/layout/ContentHeader/ContentHeader.stories.tsx +++ b/packages/core-components/src/layout/ContentHeader/ContentHeader.stories.tsx @@ -20,6 +20,7 @@ import { ContentHeader } from '../ContentHeader'; export default { title: 'Layout/ContentHeader', component: ContentHeader, + tags: ['!manifest'], }; type ContentHeaderProps = { diff --git a/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.test.tsx b/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.test.tsx index 4011fd2125..65e8a55801 100644 --- a/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.test.tsx +++ b/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.test.tsx @@ -67,18 +67,20 @@ describe('', () => { }); expect(error).toEqual([ + expect.stringContaining('Error: Bomb'), expect.objectContaining({ - detail: new Error('Bomb'), + type: 'unhandled-exception', }), + expect.stringContaining('Error: Bomb'), expect.objectContaining({ - detail: new Error('Bomb'), + type: 'unhandled-exception', }), - expect.stringMatching( - /^The above error occurred in the component:/, + expect.stringContaining( + 'The above error occurred in the component:', ), - expect.stringMatching(/^ErrorBoundary/), - expect.stringMatching(/Warning: findDOMNode/), // React warning, unfortunate but currently true + expect.stringContaining('ErrorBoundary'), + expect.stringContaining('Warning: findDOMNode'), // React warning, unfortunate but currently true ]); - expect(error.length).toEqual(5); + expect(error.length).toEqual(7); }); }); diff --git a/packages/core-components/src/layout/Header/Header.stories.tsx b/packages/core-components/src/layout/Header/Header.stories.tsx index 3b9c67fce5..a87c89544a 100644 --- a/packages/core-components/src/layout/Header/Header.stories.tsx +++ b/packages/core-components/src/layout/Header/Header.stories.tsx @@ -36,6 +36,7 @@ export default { control: { type: 'select' }, }, }, + tags: ['!manifest'], }; const labels = ( diff --git a/packages/core-components/src/layout/HeaderActionMenu/HeaderActionMenu.stories.tsx b/packages/core-components/src/layout/HeaderActionMenu/HeaderActionMenu.stories.tsx index 0fc4139dfb..b087c08d06 100644 --- a/packages/core-components/src/layout/HeaderActionMenu/HeaderActionMenu.stories.tsx +++ b/packages/core-components/src/layout/HeaderActionMenu/HeaderActionMenu.stories.tsx @@ -18,6 +18,7 @@ import { HeaderActionMenu, HeaderActionMenuProps } from './HeaderActionMenu'; export default { title: 'Layout/HeaderActionMenu', component: HeaderActionMenu, + tags: ['!manifest'], }; export const Default = (args: HeaderActionMenuProps) => ( diff --git a/packages/core-components/src/layout/HeaderLabel/HeaderLabel.stories.tsx b/packages/core-components/src/layout/HeaderLabel/HeaderLabel.stories.tsx index dd93b0768c..60028eb89c 100644 --- a/packages/core-components/src/layout/HeaderLabel/HeaderLabel.stories.tsx +++ b/packages/core-components/src/layout/HeaderLabel/HeaderLabel.stories.tsx @@ -18,6 +18,7 @@ import { HeaderLabel } from './HeaderLabel'; export default { title: 'Layout/HeaderLabel', component: HeaderLabel, + tags: ['!manifest'], }; type HeaderLabelProps = { diff --git a/packages/core-components/src/layout/HeaderTabs/HeaderTabs.stories.tsx b/packages/core-components/src/layout/HeaderTabs/HeaderTabs.stories.tsx index 08ea2a0648..b551bb1d04 100644 --- a/packages/core-components/src/layout/HeaderTabs/HeaderTabs.stories.tsx +++ b/packages/core-components/src/layout/HeaderTabs/HeaderTabs.stories.tsx @@ -18,6 +18,7 @@ import { HeaderTabs } from './HeaderTabs'; export default { title: 'Layout/HeaderTabs', component: HeaderTabs, + tags: ['!manifest'], }; export const SingleTab = (args: any) => ; diff --git a/packages/core-components/src/layout/InfoCard/InfoCard.stories.tsx b/packages/core-components/src/layout/InfoCard/InfoCard.stories.tsx index 72bc79b674..10400412c6 100644 --- a/packages/core-components/src/layout/InfoCard/InfoCard.stories.tsx +++ b/packages/core-components/src/layout/InfoCard/InfoCard.stories.tsx @@ -23,6 +23,7 @@ import { InfoCard, Props } from './InfoCard'; export default { title: 'Layout/Information Card', component: InfoCard, + tags: ['!manifest'], }; const text = ( diff --git a/packages/core-components/src/layout/ItemCard/ItemCard.stories.tsx b/packages/core-components/src/layout/ItemCard/ItemCard.stories.tsx index def451d418..4ab03c305f 100644 --- a/packages/core-components/src/layout/ItemCard/ItemCard.stories.tsx +++ b/packages/core-components/src/layout/ItemCard/ItemCard.stories.tsx @@ -27,6 +27,7 @@ import { ItemCardHeader } from './ItemCardHeader'; export default { title: 'Layout/Item Cards', + tags: ['!manifest'], }; const text = diff --git a/packages/core-components/src/layout/ItemCard/ItemCardHeader.tsx b/packages/core-components/src/layout/ItemCard/ItemCardHeader.tsx index 115c9e89e1..cb1010b2c7 100644 --- a/packages/core-components/src/layout/ItemCard/ItemCardHeader.tsx +++ b/packages/core-components/src/layout/ItemCard/ItemCardHeader.tsx @@ -30,7 +30,7 @@ export type ItemCardHeaderClassKey = 'root'; const styles = (theme: Theme) => createStyles({ root: { - color: theme.palette.text.primary, + color: theme.getPageTheme({ themeId: 'card' }).fontColor, padding: theme.spacing(2, 2, 3), backgroundImage: theme.getPageTheme({ themeId: 'card' }).backgroundImage, backgroundPosition: 0, diff --git a/packages/core-components/src/layout/Page/Page.stories.tsx b/packages/core-components/src/layout/Page/Page.stories.tsx index aa0aedbe5d..2ec3afebb7 100644 --- a/packages/core-components/src/layout/Page/Page.stories.tsx +++ b/packages/core-components/src/layout/Page/Page.stories.tsx @@ -40,6 +40,7 @@ import { Page } from '../Page'; export default { title: 'Plugins/Examples', component: Page, + tags: ['!manifest'], }; interface TableData { diff --git a/packages/core-components/src/layout/ProxiedSignInPage/types.test.ts b/packages/core-components/src/layout/ProxiedSignInPage/types.test.ts index 178714ecf9..abc9cabac6 100644 --- a/packages/core-components/src/layout/ProxiedSignInPage/types.test.ts +++ b/packages/core-components/src/layout/ProxiedSignInPage/types.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TypeOf } from 'zod'; +import type { TypeOf } from 'zod/v3'; import { ProxiedSession, proxiedSessionSchema } from './types'; describe('types', () => { diff --git a/packages/core-components/src/layout/ProxiedSignInPage/types.ts b/packages/core-components/src/layout/ProxiedSignInPage/types.ts index dd4230cd8b..bf237aabc7 100644 --- a/packages/core-components/src/layout/ProxiedSignInPage/types.ts +++ b/packages/core-components/src/layout/ProxiedSignInPage/types.ts @@ -18,7 +18,7 @@ import { BackstageIdentityResponse, ProfileInfo, } from '@backstage/core-plugin-api'; -import { z } from 'zod'; +import { z } from 'zod/v3'; export const proxiedSessionSchema = z.object({ providerInfo: z.object({}).catchall(z.unknown()).optional(), diff --git a/packages/core-components/src/layout/Sidebar/Items.test.tsx b/packages/core-components/src/layout/Sidebar/Items.test.tsx index 3121228ccd..35a0793409 100644 --- a/packages/core-components/src/layout/Sidebar/Items.test.tsx +++ b/packages/core-components/src/layout/Sidebar/Items.test.tsx @@ -96,7 +96,7 @@ describe('Items', () => { it('should render a button with custom style', async () => { expect( await screen.findByRole('button', { name: /create/i }), - ).toHaveStyle(`background-color: transparent`); + ).toHaveStyle(`background-color: rgba(0, 0, 0, 0)`); // transparent }); it('should send button clicks to analytics', async () => { diff --git a/packages/core-components/src/layout/Sidebar/Items.tsx b/packages/core-components/src/layout/Sidebar/Items.tsx index efe1f97dfb..984488cdbb 100644 --- a/packages/core-components/src/layout/Sidebar/Items.tsx +++ b/packages/core-components/src/layout/Sidebar/Items.tsx @@ -116,6 +116,17 @@ const makeSidebarStyles = (sidebarConfig: SidebarConfig) => font: 'inherit', textTransform: 'none', }, + itemIcon: { + display: 'inline-flex', + fontSize: theme.typography.fontSize, + lineHeight: 0, + '& svg': { + width: '1.5em', + height: '1.5em', + fontSize: 'inherit', + flexShrink: 0, + }, + }, closed: { width: sidebarConfig.drawerWidthClosed, justifyContent: 'center', @@ -401,7 +412,9 @@ const SidebarItemBase = forwardRef< const displayItemIcon = ( - + + + {!isOpen && hasSubmenu ? : <>} ); diff --git a/packages/core-components/src/layout/Sidebar/Sidebar.stories.tsx b/packages/core-components/src/layout/Sidebar/Sidebar.stories.tsx index f02fad5095..d5720d00a7 100644 --- a/packages/core-components/src/layout/Sidebar/Sidebar.stories.tsx +++ b/packages/core-components/src/layout/Sidebar/Sidebar.stories.tsx @@ -48,6 +48,7 @@ export default { (Story: ComponentType>) => wrapInTestApp(, { mountedRoutes: { '/': routeRef } }), ], + tags: ['!manifest'], }; const handleSearch = (input: string) => { diff --git a/packages/core-components/src/layout/SignInPage/types.ts b/packages/core-components/src/layout/SignInPage/types.ts index a7e1ca62c2..d56a0c54da 100644 --- a/packages/core-components/src/layout/SignInPage/types.ts +++ b/packages/core-components/src/layout/SignInPage/types.ts @@ -35,15 +35,6 @@ export type SignInProviderConfig = { /** @public */ export type IdentityProviders = ('guest' | 'custom' | SignInProviderConfig)[]; -/** - * Invoked when the sign-in process has failed. - */ -export type onSignInFailure = () => void; -/** - * Invoked when the sign-in process has started. - */ -export type onSignInStarted = () => void; - export type ProviderComponent = ComponentType< SignInPageProps & { config: SignInProviderConfig; diff --git a/packages/core-components/src/layout/TabbedCard/TabbedCard.stories.tsx b/packages/core-components/src/layout/TabbedCard/TabbedCard.stories.tsx index 7c83dad083..c9019107ec 100644 --- a/packages/core-components/src/layout/TabbedCard/TabbedCard.stories.tsx +++ b/packages/core-components/src/layout/TabbedCard/TabbedCard.stories.tsx @@ -36,6 +36,7 @@ export default { ), ], + tags: ['!manifest'], }; export const Default = () => { diff --git a/packages/core-components/src/translation.ts b/packages/core-components/src/translation.ts index aa644fa909..d6012273e9 100644 --- a/packages/core-components/src/translation.ts +++ b/packages/core-components/src/translation.ts @@ -16,7 +16,7 @@ import { createTranslationRef } from '@backstage/core-plugin-api/alpha'; -/** @alpha */ +/** @public */ export const coreComponentsTranslationRef = createTranslationRef({ id: 'core-components', messages: { @@ -127,5 +127,13 @@ export const coreComponentsTranslationRef = createTranslationRef({ title: 'You do not appear to be signed in. Please try reloading the browser page.', }, + logViewer: { + downloadBtn: { + tooltip: 'Download logs', + }, + searchField: { + placeholder: 'Search', + }, + }, }, }); diff --git a/packages/core-plugin-api/CHANGELOG.md b/packages/core-plugin-api/CHANGELOG.md index 29dedfe6a7..995781d3f8 100644 --- a/packages/core-plugin-api/CHANGELOG.md +++ b/packages/core-plugin-api/CHANGELOG.md @@ -1,5 +1,141 @@ # @backstage/core-plugin-api +## 1.12.5 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0 + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/config@1.3.7 + +## 1.12.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/config@1.3.7-next.0 + - @backstage/frontend-plugin-api@0.16.0-next.2 + +## 1.12.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + +## 1.12.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + +## 1.12.4 + +### Patch Changes + +- d911b72: Updated `createApiRef` to preserve the direct config call without deprecation warnings while staying compatible with the new frontend API ref typing. +- 59752a2: Deprecated `AlertApi`, `AlertMessage`, and `alertApiRef` in favor of the new `ToastApi` from `@backstage/frontend-plugin-api`. +- 0452d02: Add optional `description` field to plugin-level feature flags. +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- fe848e0: Changed `useApiHolder` to return an empty `ApiHolder` instead of throwing when used outside of an API context. +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0 + +## 1.12.4-next.1 + +### Patch Changes + +- 0452d02: Add optional `description` field to plugin-level feature flags. +- fe848e0: Changed `useApiHolder` to return an empty `ApiHolder` instead of throwing when used outside of an API context. +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + +## 1.12.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + +## 1.12.3 + +### Patch Changes + +- 53b6549: Plugins in the new frontend system now have a `pluginId` field rather than `id` to better align with naming conventions used throughout the frontend and backend systems. The old field is still present but marked as deprecated. All internal code has been updated to prefer `pluginId` while maintaining backward compatibility by falling back to `id` when needed. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/version-bridge@1.0.12 + +## 1.12.3-next.1 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/version-bridge@1.0.12-next.0 + +## 1.12.2-next.0 + +### Patch Changes + +- 53b6549: Plugins in the new frontend system now have a `pluginId` field rather than `id` to better align with naming conventions used throughout the frontend and backend systems. The old field is still present but marked as deprecated. All internal code has been updated to prefer `pluginId` while maintaining backward compatibility by falling back to `id` when needed. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## 1.12.1 + +### Patch Changes + +- 358c6f7: The `useApp` and `useRouteRef` functions are now forwards compatible with the new frontend system. Along with the previous route reference changes this means that there is no longer a need to use `compatWrapper` from `@backstage/core-compat-api` to make code based on `@backstage/core-plugin-api` compatible with `@backstage/frontend-plugin-api` APIs. +- 97cd16f: Reversed the relationship between the old `@backstage/core-plugin-api` and the new `@backstage/frontend-plugin-api`. Previously, the a lot of API definitions and utilities where defined in the old and re-exported from the old, but this change flips that around so that they now reside in the new package and are re-exported from the old. The external API of both packages remain the same, but this is a step towards being able to add further compatibility with the new frontend system built into the old. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + +## 1.12.1-next.0 + +### Patch Changes + +- 358c6f7: The `useApp` and `useRouteRef` functions are now forwards compatible with the new frontend system. Along with the previous route reference changes this means that there is no longer a need to use `compatWrapper` from `@backstage/core-compat-api` to make code based on `@backstage/core-plugin-api` compatible with `@backstage/frontend-plugin-api` APIs. +- 97cd16f: Reversed the relationship between the old `@backstage/core-plugin-api` and the new `@backstage/frontend-plugin-api`. Previously, the a lot of API definitions and utilities where defined in the old and re-exported from the old, but this change flips that around so that they now reside in the new package and are re-exported from the old. The external API of both packages remain the same, but this is a step towards being able to add further compatibility with the new frontend system built into the old. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## 1.12.0 + +### Minor Changes + +- 83439b1: All route references are now forwards compatible with the new frontend system, i.e. `@backstage/frontend-plugin-api`. This means they no longer need to be converted with `convertLegacyRouteRef` or `convertLegacyRouteRefs` from `@backstage/core-compat-api`. + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/config@1.3.6 + ## 1.11.2-next.1 ### Patch Changes diff --git a/packages/core-plugin-api/knip-report.md b/packages/core-plugin-api/knip-report.md index 3aaa33dfa4..600ed914a6 100644 --- a/packages/core-plugin-api/knip-report.md +++ b/packages/core-plugin-api/knip-report.md @@ -1,8 +1,16 @@ # Knip report +## Unused dependencies (3) + +| Name | Location | Severity | +| :---------------- | :---------------- | :------- | +| @backstage/config | package.json:52:6 | error | +| @backstage/types | package.json:55:6 | error | +| zod | package.json:58:6 | error | + ## Unused devDependencies (1) -| Name | Location | Severity | -| :-------------------------- | :----------- | :------- | -| @testing-library/user-event | packages/core-plugin-api/package.json | error | +| Name | Location | Severity | +| :-------------------------- | :---------------- | :------- | +| @testing-library/user-event | package.json:67:6 | error | diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json index 430d081b49..706e89f9c3 100644 --- a/packages/core-plugin-api/package.json +++ b/packages/core-plugin-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/core-plugin-api", - "version": "1.11.2-next.1", + "version": "1.12.5", "description": "Core API used by Backstage plugins", "backstage": { "role": "web-library" @@ -51,9 +51,11 @@ "dependencies": { "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", + "@backstage/frontend-plugin-api": "workspace:^", "@backstage/types": "workspace:^", "@backstage/version-bridge": "workspace:^", - "history": "^5.0.0" + "history": "^5.0.0", + "zod": "^3.25.76 || ^4.0.0" }, "devDependencies": { "@backstage/cli": "workspace:^", @@ -66,13 +68,13 @@ "@types/react": "^18.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/packages/core-plugin-api/report-alpha.api.md b/packages/core-plugin-api/report-alpha.api.md index 6243c76cff..b0aea73868 100644 --- a/packages/core-plugin-api/report-alpha.api.md +++ b/packages/core-plugin-api/report-alpha.api.md @@ -3,253 +3,54 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { ApiRef } from '@backstage/core-plugin-api'; -import { Expand } from '@backstage/types'; -import { ExpandRecursive } from '@backstage/types'; -import { JSX as JSX_2 } from 'react'; -import { Observable } from '@backstage/types'; -import { TranslationMessages as TranslationMessages_2 } from '@backstage/core-plugin-api/alpha'; -import { TranslationRef as TranslationRef_2 } from '@backstage/core-plugin-api/alpha'; +import { AppLanguageApi } from '@backstage/frontend-plugin-api'; +import { appLanguageApiRef } from '@backstage/frontend-plugin-api'; +import { createTranslationMessages } from '@backstage/frontend-plugin-api'; +import { createTranslationRef } from '@backstage/frontend-plugin-api'; +import { createTranslationResource } from '@backstage/frontend-plugin-api'; +import { TranslationApi } from '@backstage/frontend-plugin-api'; +import { translationApiRef } from '@backstage/frontend-plugin-api'; +import { TranslationFunction } from '@backstage/frontend-plugin-api'; +import { TranslationMessages } from '@backstage/frontend-plugin-api'; +import { TranslationMessagesOptions } from '@backstage/frontend-plugin-api'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; +import { TranslationRefOptions } from '@backstage/frontend-plugin-api'; +import { TranslationResource } from '@backstage/frontend-plugin-api'; +import { TranslationResourceOptions } from '@backstage/frontend-plugin-api'; +import { TranslationSnapshot } from '@backstage/frontend-plugin-api'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; -// @alpha (undocumented) -export type AppLanguageApi = { - getAvailableLanguages(): { - languages: string[]; - }; - setLanguage(language?: string): void; - getLanguage(): { - language: string; - }; - language$(): Observable<{ - language: string; - }>; -}; +export { AppLanguageApi }; -// @alpha (undocumented) -export const appLanguageApiRef: ApiRef; +export { appLanguageApiRef }; -// @alpha -export function createTranslationMessages< - TId extends string, - TMessages extends { - [key in string]: string; - }, - TFull extends boolean, ->( - options: TranslationMessagesOptions, -): TranslationMessages; +export { createTranslationMessages }; -// @alpha (undocumented) -export function createTranslationRef< - TId extends string, - const TNestedMessages extends AnyNestedMessages, - TTranslations extends { - [language in string]: () => Promise<{ - default: { - [key in keyof FlattenedMessages]: string | null; - }; - }>; - }, ->( - config: TranslationRefOptions, -): TranslationRef>; +export { createTranslationRef }; -// @alpha (undocumented) -export function createTranslationResource< - TId extends string, - TMessages extends { - [key in string]: string; - }, - TTranslations extends { - [language in string]: () => Promise<{ - default: - | TranslationMessages_2 - | { - [key in keyof TMessages]: string | null; - }; - }>; - }, ->( - options: TranslationResourceOptions, -): TranslationResource; +export { createTranslationResource }; -// @alpha (undocumented) -export type TranslationApi = { - getTranslation< - TMessages extends { - [key in string]: string; - }, - >( - translationRef: TranslationRef, - ): TranslationSnapshot; - translation$< - TMessages extends { - [key in string]: string; - }, - >( - translationRef: TranslationRef, - ): Observable>; -}; +export { TranslationApi }; -// @alpha (undocumented) -export const translationApiRef: ApiRef; +export { translationApiRef }; -// @alpha (undocumented) -export type TranslationFunction< - TMessages extends { - [key in string]: string; - }, -> = CollapsedMessages extends infer IMessages extends { - [key in string]: string; -} - ? { - ( - key: TKey, - ...[args]: TranslationFunctionOptions< - NestedMessageKeys, - PluralKeys, - IMessages, - string - > - ): IMessages[TKey]; - ( - key: TKey, - ...[args]: TranslationFunctionOptions< - NestedMessageKeys, - PluralKeys, - IMessages, - string | JSX_2.Element - > - ): JSX_2.Element; - } - : never; +export { TranslationFunction }; -// @alpha -export interface TranslationMessages< - TId extends string = string, - TMessages extends { - [key in string]: string; - } = { - [key in string]: string; - }, - TFull extends boolean = boolean, -> { - // (undocumented) - $$type: '@backstage/TranslationMessages'; - full: TFull; - id: TId; - messages: TMessages; -} +export { TranslationMessages }; -// @alpha -export interface TranslationMessagesOptions< - TId extends string, - TMessages extends { - [key in string]: string; - }, - TFull extends boolean, -> { - // (undocumented) - full?: TFull; - // (undocumented) - messages: false extends TFull - ? { - [key in keyof TMessages]?: string | null; - } - : { - [key in keyof TMessages]: string | null; - }; - // (undocumented) - ref: TranslationRef_2; -} +export { TranslationMessagesOptions }; -// @alpha (undocumented) -export interface TranslationRef< - TId extends string = string, - TMessages extends { - [key in string]: string; - } = { - [key in string]: string; - }, -> { - // (undocumented) - $$type: '@backstage/TranslationRef'; - // (undocumented) - id: TId; - // (undocumented) - T: TMessages; -} +export { TranslationRef }; -// @alpha (undocumented) -export interface TranslationRefOptions< - TId extends string, - TNestedMessages extends AnyNestedMessages, - TTranslations extends { - [language in string]: () => Promise<{ - default: { - [key in keyof FlattenedMessages]: string | null; - }; - }>; - }, -> { - // (undocumented) - id: TId; - // (undocumented) - messages: TNestedMessages; - // (undocumented) - translations?: TTranslations; -} +export { TranslationRefOptions }; -// @alpha (undocumented) -export interface TranslationResource { - // (undocumented) - $$type: '@backstage/TranslationResource'; - // (undocumented) - id: TId; -} +export { TranslationResource }; -// @alpha (undocumented) -export interface TranslationResourceOptions< - TId extends string, - TMessages extends { - [key in string]: string; - }, - TTranslations extends { - [language in string]: () => Promise<{ - default: - | TranslationMessages_2 - | { - [key in keyof TMessages]: string | null; - }; - }>; - }, -> { - // (undocumented) - ref: TranslationRef_2; - // (undocumented) - translations: TTranslations; -} +export { TranslationResourceOptions }; -// @alpha (undocumented) -export type TranslationSnapshot< - TMessages extends { - [key in string]: string; - }, -> = - | { - ready: false; - } - | { - ready: true; - t: TranslationFunction; - }; +export { TranslationSnapshot }; -// @alpha (undocumented) -export const useTranslationRef: ( - translationRef: TranslationRef, -) => { - t: TranslationFunction; -}; +export { useTranslationRef }; // (No @packageDocumentation comment for this package) ``` diff --git a/packages/core-plugin-api/report.api.md b/packages/core-plugin-api/report.api.md index e1b1a243df..7165cfc697 100644 --- a/packages/core-plugin-api/report.api.md +++ b/packages/core-plugin-api/report.api.md @@ -3,33 +3,86 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AlertApi } from '@backstage/frontend-plugin-api'; +import { alertApiRef } from '@backstage/frontend-plugin-api'; +import { AlertMessage } from '@backstage/frontend-plugin-api'; +import { AnyApiFactory } from '@backstage/frontend-plugin-api'; +import { AnyApiRef } from '@backstage/frontend-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; +import { ApiHolder } from '@backstage/frontend-plugin-api'; +import { ApiRef } from '@backstage/frontend-plugin-api'; +import { ApiRefConfig } from '@backstage/frontend-plugin-api'; +import { AppTheme } from '@backstage/frontend-plugin-api'; +import { AppThemeApi } from '@backstage/frontend-plugin-api'; +import { appThemeApiRef } from '@backstage/frontend-plugin-api'; +import { atlassianAuthApiRef } from '@backstage/frontend-plugin-api'; +import { AuthProviderInfo } from '@backstage/frontend-plugin-api'; +import { AuthRequestOptions } from '@backstage/frontend-plugin-api'; +import { BackstageIdentityApi } from '@backstage/frontend-plugin-api'; +import { BackstageIdentityResponse } from '@backstage/frontend-plugin-api'; import { BackstagePlugin as BackstagePlugin_2 } from '@backstage/core-plugin-api'; +import { BackstageUserIdentity } from '@backstage/frontend-plugin-api'; +import { bitbucketAuthApiRef } from '@backstage/frontend-plugin-api'; +import { bitbucketServerAuthApiRef } from '@backstage/frontend-plugin-api'; import { ComponentType } from 'react'; -import { Config } from '@backstage/config'; +import { ConfigApi } from '@backstage/frontend-plugin-api'; +import { configApiRef } from '@backstage/frontend-plugin-api'; +import { createApiFactory } from '@backstage/frontend-plugin-api'; +import { DiscoveryApi } from '@backstage/frontend-plugin-api'; +import { discoveryApiRef } from '@backstage/frontend-plugin-api'; +import { ErrorApi } from '@backstage/frontend-plugin-api'; +import { ErrorApiError } from '@backstage/frontend-plugin-api'; +import { ErrorApiErrorContext } from '@backstage/frontend-plugin-api'; +import { errorApiRef } from '@backstage/frontend-plugin-api'; +import { FeatureFlag } from '@backstage/frontend-plugin-api'; +import { FeatureFlagsApi } from '@backstage/frontend-plugin-api'; +import { featureFlagsApiRef } from '@backstage/frontend-plugin-api'; +import { FeatureFlagsSaveOptions } from '@backstage/frontend-plugin-api'; +import { FeatureFlagState } from '@backstage/frontend-plugin-api'; +import { FetchApi } from '@backstage/frontend-plugin-api'; +import { fetchApiRef } from '@backstage/frontend-plugin-api'; +import { githubAuthApiRef } from '@backstage/frontend-plugin-api'; +import { gitlabAuthApiRef } from '@backstage/frontend-plugin-api'; +import { googleAuthApiRef } from '@backstage/frontend-plugin-api'; +import { IconComponent } from '@backstage/frontend-plugin-api'; import { IconComponent as IconComponent_2 } from '@backstage/core-plugin-api'; +import { IdentityApi } from '@backstage/frontend-plugin-api'; import { IdentityApi as IdentityApi_2 } from '@backstage/core-plugin-api'; -import { JsonValue } from '@backstage/types'; +import { identityApiRef } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react/jsx-runtime'; -import { Observable } from '@backstage/types'; +import { microsoftAuthApiRef } from '@backstage/frontend-plugin-api'; +import { OAuthApi } from '@backstage/frontend-plugin-api'; +import { OAuthRequestApi } from '@backstage/frontend-plugin-api'; +import { oauthRequestApiRef } from '@backstage/frontend-plugin-api'; +import { OAuthRequester } from '@backstage/frontend-plugin-api'; +import { OAuthRequesterOptions } from '@backstage/frontend-plugin-api'; +import { OAuthScope } from '@backstage/frontend-plugin-api'; +import { oktaAuthApiRef } from '@backstage/frontend-plugin-api'; +import { oneloginAuthApiRef } from '@backstage/frontend-plugin-api'; +import { OpenIdConnectApi } from '@backstage/frontend-plugin-api'; +import { openshiftAuthApiRef } from '@backstage/frontend-plugin-api'; +import { PendingOAuthRequest } from '@backstage/frontend-plugin-api'; +import { ProfileInfo } from '@backstage/frontend-plugin-api'; +import { ProfileInfoApi } from '@backstage/frontend-plugin-api'; import { PropsWithChildren } from 'react'; import { ReactElement } from 'react'; import { ReactNode } from 'react'; +import { SessionApi } from '@backstage/frontend-plugin-api'; +import { SessionState } from '@backstage/frontend-plugin-api'; +import { StorageApi } from '@backstage/frontend-plugin-api'; +import { storageApiRef } from '@backstage/frontend-plugin-api'; +import { StorageValueSnapshot } from '@backstage/frontend-plugin-api'; +import { TypesToApiRefs } from '@backstage/frontend-plugin-api'; +import { useApi } from '@backstage/frontend-plugin-api'; +import { useApiHolder } from '@backstage/frontend-plugin-api'; +import { vmwareCloudAuthApiRef } from '@backstage/frontend-plugin-api'; +import { withApis } from '@backstage/frontend-plugin-api'; -// @public -export type AlertApi = { - post(alert: AlertMessage): void; - alert$(): Observable; -}; +export { AlertApi }; -// @public -export const alertApiRef: ApiRef; +export { alertApiRef }; -// @public -export type AlertMessage = { - message: string; - severity?: 'success' | 'info' | 'warning' | 'error'; - display?: 'permanent' | 'transient'; -}; +export { AlertMessage }; // @public export type AnalyticsApi = { @@ -76,17 +129,9 @@ export type AnalyticsTracker = { ) => void; }; -// @public -export type AnyApiFactory = ApiFactory< - unknown, - unknown, - { - [key in string]: unknown; - } ->; +export { AnyApiFactory }; -// @public -export type AnyApiRef = ApiRef; +export { AnyApiRef }; // @public export type AnyExternalRoutes = { @@ -108,34 +153,13 @@ export type AnyRoutes = { [name: string]: RouteRef | SubRouteRef; }; -// @public -export type ApiFactory< - Api, - Impl extends Api, - Deps extends { - [name in string]: unknown; - }, -> = { - api: ApiRef; - deps: TypesToApiRefs; - factory(deps: Deps): Impl; -}; +export { ApiFactory }; -// @public -export type ApiHolder = { - get(api: ApiRef): T | undefined; -}; +export { ApiHolder }; -// @public -export type ApiRef = { - id: string; - T: T; -}; +export { ApiRef }; -// @public -export type ApiRefConfig = { - id: string; -}; +export { ApiRefConfig }; // @public export type AppComponents = { @@ -160,30 +184,13 @@ export type AppContext = { getComponents(): AppComponents; }; -// @public -export type AppTheme = { - id: string; - title: string; - variant: 'light' | 'dark'; - icon?: React.ReactElement; - Provider(props: { children: ReactNode }): JSX.Element | null; -}; +export { AppTheme }; -// @public -export type AppThemeApi = { - getInstalledThemes(): AppTheme[]; - activeThemeId$(): Observable; - getActiveThemeId(): string | undefined; - setActiveThemeId(themeId?: string): void; -}; +export { AppThemeApi }; -// @public -export const appThemeApiRef: ApiRef; +export { appThemeApiRef }; -// @public -export const atlassianAuthApiRef: ApiRef< - OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi ->; +export { atlassianAuthApiRef }; // @public export function attachComponentData

    ( @@ -192,33 +199,13 @@ export function attachComponentData

    ( data: unknown, ): void; -// @public -export type AuthProviderInfo = { - id: string; - title: string; - icon: IconComponent; - message?: string; -}; +export { AuthProviderInfo }; -// @public -export type AuthRequestOptions = { - optional?: boolean; - instantPopup?: boolean; -}; +export { AuthRequestOptions }; -// @public -export type BackstageIdentityApi = { - getBackstageIdentity( - options?: AuthRequestOptions, - ): Promise; -}; +export { BackstageIdentityApi }; -// @public -export type BackstageIdentityResponse = { - token: string; - expiresAt?: Date; - identity: BackstageUserIdentity; -}; +export { BackstageIdentityResponse }; // @public export type BackstagePlugin< @@ -234,22 +221,11 @@ export type BackstagePlugin< externalRoutes: ExternalRoutes; }; -// @public -export type BackstageUserIdentity = { - type: 'user'; - userEntityRef: string; - ownershipEntityRefs: string[]; -}; +export { BackstageUserIdentity }; -// @public -export const bitbucketAuthApiRef: ApiRef< - OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi ->; +export { bitbucketAuthApiRef }; -// @public -export const bitbucketServerAuthApiRef: ApiRef< - OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi ->; +export { bitbucketServerAuthApiRef }; // @public export type BootErrorPageProps = PropsWithChildren<{ @@ -273,26 +249,11 @@ export type ComponentLoader = sync: T; }; -// @public -export type ConfigApi = Config; +export { ConfigApi }; -// @public -export const configApiRef: ApiRef; +export { configApiRef }; -// @public -export function createApiFactory< - Api, - Impl extends Api, - Deps extends { - [name in string]: unknown; - }, ->(factory: ApiFactory): ApiFactory; - -// @public -export function createApiFactory( - api: ApiRef, - instance: Impl, -): ApiFactory; +export { createApiFactory }; // @public export function createApiRef(config: ApiRefConfig): ApiRef; @@ -363,13 +324,9 @@ export function createSubRouteRef< parent: RouteRef; }): MakeSubRouteRef, ParentParams>; -// @public -export type DiscoveryApi = { - getBaseUrl(pluginId: string): Promise; -}; +export { DiscoveryApi }; -// @public -export const discoveryApiRef: ApiRef; +export { discoveryApiRef }; // @public export interface ElementCollection { @@ -385,29 +342,13 @@ export interface ElementCollection { }): ElementCollection; } -// @public -export type ErrorApi = { - post(error: ErrorApiError, context?: ErrorApiErrorContext): void; - error$(): Observable<{ - error: ErrorApiError; - context?: ErrorApiErrorContext; - }>; -}; +export { ErrorApi }; -// @public -export type ErrorApiError = { - name: string; - message: string; - stack?: string; -}; +export { ErrorApiError }; -// @public -export type ErrorApiErrorContext = { - hidden?: boolean; -}; +export { ErrorApiErrorContext }; -// @public -export const errorApiRef: ApiRef; +export { errorApiRef }; // @public export type ErrorBoundaryFallbackProps = PropsWithChildren<{ @@ -429,62 +370,28 @@ export type ExternalRouteRef< $$routeRefType: 'external'; params: ParamKeys; optional?: Optional; + readonly $$type: '@backstage/ExternalRouteRef'; + readonly T: Params; }; -// @public -export type FeatureFlag = { - name: string; - pluginId: string; - description?: string; -}; +export { FeatureFlag }; -// @public -export interface FeatureFlagsApi { - getRegisteredFlags(): FeatureFlag[]; - isActive(name: string): boolean; - registerFlag(flag: FeatureFlag): void; - save(options: FeatureFlagsSaveOptions): void; -} +export { FeatureFlagsApi }; -// @public -export const featureFlagsApiRef: ApiRef; +export { featureFlagsApiRef }; // @public export type FeatureFlagsHooks = { register(name: string): void; }; -// @public -export type FeatureFlagsSaveOptions = { - states: Record; - merge?: boolean; -}; +export { FeatureFlagsSaveOptions }; -// @public -export const FeatureFlagState: { - readonly None: 0; - readonly Active: 1; -}; +export { FeatureFlagState }; -// @public (undocumented) -export type FeatureFlagState = - (typeof FeatureFlagState)[keyof typeof FeatureFlagState]; +export { FetchApi }; -// @public (undocumented) -export namespace FeatureFlagState { - // (undocumented) - export type Active = typeof FeatureFlagState.Active; - // (undocumented) - export type None = typeof FeatureFlagState.None; -} - -// @public -export type FetchApi = { - fetch: typeof fetch; -}; - -// @public -export const fetchApiRef: ApiRef; +export { fetchApiRef }; // @public export function getComponentData( @@ -492,46 +399,17 @@ export function getComponentData( type: string, ): T | undefined; -// @public -export const githubAuthApiRef: ApiRef< - OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi ->; +export { githubAuthApiRef }; -// @public -export const gitlabAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi ->; +export { gitlabAuthApiRef }; -// @public -export const googleAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi ->; +export { googleAuthApiRef }; -// @public -export type IconComponent = ComponentType<{ - fontSize?: 'medium' | 'large' | 'small' | 'inherit'; -}>; +export { IconComponent }; -// @public -export type IdentityApi = { - getProfileInfo(): Promise; - getBackstageIdentity(): Promise; - getCredentials(): Promise<{ - token?: string; - }>; - signOut(): Promise; -}; +export { IdentityApi }; -// @public -export const identityApiRef: ApiRef; +export { identityApiRef }; // @public @deprecated export type MakeSubRouteRef< @@ -551,75 +429,27 @@ export type MergeParams< P2 extends AnyParams, > = (P1[keyof P1] extends never ? {} : P1) & (P2 extends undefined ? {} : P2); -// @public -export const microsoftAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi ->; +export { microsoftAuthApiRef }; -// @public -export type OAuthApi = { - getAccessToken( - scope?: OAuthScope, - options?: AuthRequestOptions, - ): Promise; -}; +export { OAuthApi }; -// @public -export type OAuthRequestApi = { - createAuthRequester( - options: OAuthRequesterOptions, - ): OAuthRequester; - authRequest$(): Observable; -}; +export { OAuthRequestApi }; -// @public -export const oauthRequestApiRef: ApiRef; +export { oauthRequestApiRef }; -// @public -export type OAuthRequester = ( - scopes: Set, -) => Promise; +export { OAuthRequester }; -// @public -export type OAuthRequesterOptions = { - provider: AuthProviderInfo; - onAuthRequest(scopes: Set): Promise; -}; +export { OAuthRequesterOptions }; -// @public -export type OAuthScope = string | string[]; +export { OAuthScope }; -// @public -export const oktaAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi ->; +export { oktaAuthApiRef }; -// @public -export const oneloginAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi ->; +export { oneloginAuthApiRef }; -// @public -export type OpenIdConnectApi = { - getIdToken(options?: AuthRequestOptions): Promise; -}; +export { OpenIdConnectApi }; -// @public -export const openshiftAuthApiRef: ApiRef< - OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi ->; +export { openshiftAuthApiRef }; // @public @deprecated export type OptionalParams< @@ -653,12 +483,7 @@ export type PathParams = { [name in ParamNames]: string; }; -// @public -export type PendingOAuthRequest = { - provider: AuthProviderInfo; - reject(): void; - trigger(): Promise; -}; +export { PendingOAuthRequest }; // @public export type PluginConfig< @@ -675,86 +500,40 @@ export type PluginConfig< // @public export type PluginFeatureFlagConfig = { name: string; + description?: string; }; -// @public -export type ProfileInfo = { - email?: string; - displayName?: string; - picture?: string; -}; +export { ProfileInfo }; -// @public -export type ProfileInfoApi = { - getProfile(options?: AuthRequestOptions): Promise; -}; +export { ProfileInfoApi }; // @public export type RouteFunc = ( - ...[params]: Params extends undefined ? readonly [] : readonly [Params] + ...input: Params extends undefined ? readonly [] : readonly [Params] ) => string; // @public export type RouteRef = { $$routeRefType: 'absolute'; params: ParamKeys; + readonly $$type: '@backstage/RouteRef'; + readonly T: Params; }; -// @public -export type SessionApi = { - signIn(): Promise; - signOut(): Promise; - sessionState$(): Observable; -}; +export { SessionApi }; -// @public -export const SessionState: { - readonly SignedIn: 'SignedIn'; - readonly SignedOut: 'SignedOut'; -}; - -// @public (undocumented) -export type SessionState = (typeof SessionState)[keyof typeof SessionState]; - -// @public (undocumented) -export namespace SessionState { - // (undocumented) - export type SignedIn = typeof SessionState.SignedIn; - // (undocumented) - export type SignedOut = typeof SessionState.SignedOut; -} +export { SessionState }; // @public export type SignInPageProps = PropsWithChildren<{ onSignInSuccess(identityApi: IdentityApi_2): void; }>; -// @public -export interface StorageApi { - forBucket(name: string): StorageApi; - observe$( - key: string, - ): Observable>; - remove(key: string): Promise; - set(key: string, data: T): Promise; - snapshot(key: string): StorageValueSnapshot; -} +export { StorageApi }; -// @public -export const storageApiRef: ApiRef; +export { storageApiRef }; -// @public -export type StorageValueSnapshot = - | { - key: string; - presence: 'unknown' | 'absent'; - value?: undefined; - } - | { - key: string; - presence: 'present'; - value: TValue; - }; +export { StorageValueSnapshot }; // @public export type SubRouteRef = { @@ -762,21 +541,18 @@ export type SubRouteRef = { parent: RouteRef; path: string; params: ParamKeys; + readonly $$type: '@backstage/SubRouteRef'; + readonly T: Params; }; -// @public -export type TypesToApiRefs = { - [key in keyof T]: ApiRef; -}; +export { TypesToApiRefs }; // @public export function useAnalytics(): AnalyticsTracker; -// @public -export function useApi(apiRef: ApiRef): T; +export { useApi }; -// @public -export function useApiHolder(): ApiHolder; +export { useApiHolder }; // @public export const useApp: () => AppContext; @@ -803,22 +579,7 @@ export function useRouteRefParams( _routeRef: RouteRef | SubRouteRef, ): Params; -// @public -export const vmwareCloudAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi ->; +export { vmwareCloudAuthApiRef }; -// @public -export function withApis( - apis: TypesToApiRefs, -): ( - WrappedComponent: ComponentType, -) => { - (props: PropsWithChildren>): JSX_2.Element; - displayName: string; -}; +export { withApis }; ``` diff --git a/packages/core-plugin-api/src/alpha.ts b/packages/core-plugin-api/src/alpha.ts index ebcbcf34c0..f823b909b1 100644 --- a/packages/core-plugin-api/src/alpha.ts +++ b/packages/core-plugin-api/src/alpha.ts @@ -14,5 +14,26 @@ * limitations under the License. */ -export * from './translation'; -export * from './apis/alpha'; +// Translation exports +export { + type TranslationMessages, + type TranslationMessagesOptions, + createTranslationMessages, + type TranslationResource, + type TranslationResourceOptions, + createTranslationResource, + type TranslationRef, + type TranslationRefOptions, + createTranslationRef, + useTranslationRef, +} from '@backstage/frontend-plugin-api'; + +// API definition exports +export { + appLanguageApiRef, + type AppLanguageApi, + translationApiRef, + type TranslationApi, + type TranslationFunction, + type TranslationSnapshot, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/analytics/useAnalytics.test.tsx b/packages/core-plugin-api/src/analytics/useAnalytics.test.tsx index 037e585430..45483ecb83 100644 --- a/packages/core-plugin-api/src/analytics/useAnalytics.test.tsx +++ b/packages/core-plugin-api/src/analytics/useAnalytics.test.tsx @@ -16,9 +16,12 @@ import { renderHook } from '@testing-library/react'; import { useAnalytics } from './useAnalytics'; -import { useApi } from '../apis'; +import { useApi } from '@backstage/frontend-plugin-api'; -jest.mock('../apis'); +jest.mock('@backstage/frontend-plugin-api', () => ({ + ...jest.requireActual('@backstage/frontend-plugin-api'), + useApi: jest.fn(), +})); const mocked = (f: Function) => f as jest.Mock; diff --git a/packages/core-plugin-api/src/analytics/useAnalytics.tsx b/packages/core-plugin-api/src/analytics/useAnalytics.tsx index 50cec8f790..6297f81aa2 100644 --- a/packages/core-plugin-api/src/analytics/useAnalytics.tsx +++ b/packages/core-plugin-api/src/analytics/useAnalytics.tsx @@ -15,12 +15,8 @@ */ import { useAnalyticsContext } from './AnalyticsContext'; -import { - analyticsApiRef, - AnalyticsTracker, - AnalyticsApi, - useApi, -} from '../apis'; +import { analyticsApiRef, AnalyticsTracker, AnalyticsApi } from '../apis'; +import { useApi } from '@backstage/frontend-plugin-api'; import { useRef } from 'react'; import { Tracker } from './Tracker'; diff --git a/packages/core-plugin-api/src/apis/definitions/AlertApi.ts b/packages/core-plugin-api/src/apis/definitions/AlertApi.ts index afdcb6a617..7394e5d0e8 100644 --- a/packages/core-plugin-api/src/apis/definitions/AlertApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/AlertApi.ts @@ -14,43 +14,25 @@ * limitations under the License. */ -import { createApiRef, ApiRef } from '../system'; -import { Observable } from '@backstage/types'; - /** - * Message handled by the {@link AlertApi}. + * @deprecated AlertApi is deprecated. Use ToastApi from `@backstage/frontend-plugin-api` instead. * - * @public - */ -export type AlertMessage = { - message: string; - // Severity will default to success since that is what material ui defaults the value to. - severity?: 'success' | 'info' | 'warning' | 'error'; - display?: 'permanent' | 'transient'; -}; - -/** - * The alert API is used to report alerts to the app, and display them to the user. + * ToastApi provides richer notification features including title/description, + * action links, custom icons, per-toast timeout control, and programmatic dismiss. * - * @public - */ -export type AlertApi = { - /** - * Post an alert for handling by the application. - */ - post(alert: AlertMessage): void; - - /** - * Observe alerts posted by other parts of the application. - */ - alert$(): Observable; -}; - -/** - * The {@link ApiRef} of {@link AlertApi}. + * @example + * ```typescript + * // Before (AlertApi) + * import { alertApiRef } from '@backstage/core-plugin-api'; + * alertApi.post({ message: 'Saved!', severity: 'success', display: 'transient' }); * - * @public + * // After (ToastApi) + * import { toastApiRef } from '@backstage/frontend-plugin-api'; + * toastApi.post({ title: 'Saved!', status: 'success', timeout: 5000 }); + * ``` */ -export const alertApiRef: ApiRef = createApiRef({ - id: 'core.alert', -}); +export { + type AlertApi, + type AlertMessage, + alertApiRef, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/definitions/AnalyticsApi.ts b/packages/core-plugin-api/src/apis/definitions/AnalyticsApi.ts index ef190d6c21..c6f4eadd05 100644 --- a/packages/core-plugin-api/src/apis/definitions/AnalyticsApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/AnalyticsApi.ts @@ -122,7 +122,7 @@ export type AnalyticsApi = { }; /** - * The {@link ApiRef} of {@link AnalyticsApi}. + * The `ApiRef` of {@link AnalyticsApi}. * * @public */ diff --git a/packages/core-plugin-api/src/apis/definitions/AppThemeApi.ts b/packages/core-plugin-api/src/apis/definitions/AppThemeApi.ts index e771fad597..2ad399d301 100644 --- a/packages/core-plugin-api/src/apis/definitions/AppThemeApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/AppThemeApi.ts @@ -14,74 +14,8 @@ * limitations under the License. */ -import { ReactNode } from 'react'; -import { ApiRef, createApiRef } from '../system'; -import { Observable } from '@backstage/types'; - -/** - * Describes a theme provided by the app. - * - * @public - */ -export type AppTheme = { - /** - * ID used to remember theme selections. - */ - id: string; - - /** - * Title of the theme - */ - title: string; - - /** - * Theme variant - */ - variant: 'light' | 'dark'; - - /** - * An Icon for the theme mode setting. - */ - icon?: React.ReactElement; - - Provider(props: { children: ReactNode }): JSX.Element | null; -}; - -/** - * The AppThemeApi gives access to the current app theme, and allows switching - * to other options that have been registered as a part of the App. - * - * @public - */ -export type AppThemeApi = { - /** - * Get a list of available themes. - */ - getInstalledThemes(): AppTheme[]; - - /** - * Observe the currently selected theme. A value of undefined means no specific theme has been selected. - */ - activeThemeId$(): Observable; - - /** - * Get the current theme ID. Returns undefined if no specific theme is selected. - */ - getActiveThemeId(): string | undefined; - - /** - * Set a specific theme to use in the app, overriding the default theme selection. - * - * Clear the selection by passing in undefined. - */ - setActiveThemeId(themeId?: string): void; -}; - -/** - * The {@link ApiRef} of {@link AppThemeApi}. - * - * @public - */ -export const appThemeApiRef: ApiRef = createApiRef({ - id: 'core.apptheme', -}); +export { + type AppTheme, + type AppThemeApi, + appThemeApiRef, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/definitions/ConfigApi.ts b/packages/core-plugin-api/src/apis/definitions/ConfigApi.ts index d3bada9e46..e5e8b4559c 100644 --- a/packages/core-plugin-api/src/apis/definitions/ConfigApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/ConfigApi.ts @@ -13,22 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ApiRef, createApiRef } from '../system'; -import { Config } from '@backstage/config'; -/** - * The Config API is used to provide a mechanism to access the - * runtime configuration of the system. - * - * @public - */ -export type ConfigApi = Config; - -/** - * The {@link ApiRef} of {@link ConfigApi}. - * - * @public - */ -export const configApiRef: ApiRef = createApiRef({ - id: 'core.config', -}); +export { type ConfigApi, configApiRef } from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/definitions/DiscoveryApi.ts b/packages/core-plugin-api/src/apis/definitions/DiscoveryApi.ts index d23fe3db6c..20c8639340 100644 --- a/packages/core-plugin-api/src/apis/definitions/DiscoveryApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/DiscoveryApi.ts @@ -13,43 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ApiRef, createApiRef } from '../system'; -/** - * The discovery API is used to provide a mechanism for plugins to - * discover the endpoint to use to talk to their backend counterpart. - * - * @remarks - * - * The purpose of the discovery API is to allow for many different deployment - * setups and routing methods through a central configuration, instead - * of letting each individual plugin manage that configuration. - * - * Implementations of the discovery API can be a simple as a URL pattern - * using the pluginId, but could also have overrides for individual plugins, - * or query a separate discovery service. - * - * @public - */ -export type DiscoveryApi = { - /** - * Returns the HTTP base backend URL for a given plugin, without a trailing slash. - * - * This method must always be called just before making a request, as opposed to - * fetching the URL when constructing an API client. That is to ensure that more - * flexible routing patterns can be supported. - * - * For example, asking for the URL for `auth` may return something - * like `https://backstage.example.com/api/auth` - */ - getBaseUrl(pluginId: string): Promise; -}; - -/** - * The {@link ApiRef} of {@link DiscoveryApi}. - * - * @public - */ -export const discoveryApiRef: ApiRef = createApiRef({ - id: 'core.discovery', -}); +export { + type DiscoveryApi, + discoveryApiRef, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/definitions/ErrorApi.ts b/packages/core-plugin-api/src/apis/definitions/ErrorApi.ts index 9c73d94cac..8f3f9632eb 100644 --- a/packages/core-plugin-api/src/apis/definitions/ErrorApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/ErrorApi.ts @@ -14,78 +14,9 @@ * limitations under the License. */ -import { ApiRef, createApiRef } from '../system'; -import { Observable } from '@backstage/types'; - -/** - * Mirrors the JavaScript Error class, for the purpose of - * providing documentation and optional fields. - * - * @public - */ -export type ErrorApiError = { - name: string; - message: string; - stack?: string; -}; - -/** - * Provides additional information about an error that was posted to the application. - * - * @public - */ -export type ErrorApiErrorContext = { - /** - * If set to true, this error should not be displayed to the user. - * - * Hidden errors are typically not displayed in the UI, but the ErrorApi - * implementation may still report them to error tracking services - * or other utilities that care about all errors. - * - * @defaultValue false - */ - hidden?: boolean; -}; - -/** - * The error API is used to report errors to the app, and display them to the user. - * - * @remarks - * - * Plugins can use this API as a method of displaying errors to the user, but also - * to report errors for collection by error reporting services. - * - * If an error can be displayed inline, e.g. as feedback in a form, that should be - * preferred over relying on this API to display the error. The main use of this API - * for displaying errors should be for asynchronous errors, such as a failing background process. - * - * Even if an error is displayed inline, it should still be reported through this API - * if it would be useful to collect or log it for debugging purposes, but with - * the hidden flag set. For example, an error arising from form field validation - * should probably not be reported, while a failed REST call would be useful to report. - * - * @public - */ -export type ErrorApi = { - /** - * Post an error for handling by the application. - */ - post(error: ErrorApiError, context?: ErrorApiErrorContext): void; - - /** - * Observe errors posted by other parts of the application. - */ - error$(): Observable<{ - error: ErrorApiError; - context?: ErrorApiErrorContext; - }>; -}; - -/** - * The {@link ApiRef} of {@link ErrorApi}. - * - * @public - */ -export const errorApiRef: ApiRef = createApiRef({ - id: 'core.error', -}); +export { + type ErrorApiError, + type ErrorApiErrorContext, + type ErrorApi, + errorApiRef, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/definitions/FeatureFlagsApi.ts b/packages/core-plugin-api/src/apis/definitions/FeatureFlagsApi.ts index d4429975cc..9b192e784d 100644 --- a/packages/core-plugin-api/src/apis/definitions/FeatureFlagsApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/FeatureFlagsApi.ts @@ -13,114 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* We want to maintain the same information as an enum, so we disable the redeclaration warning */ -/* eslint-disable @typescript-eslint/no-redeclare */ -import { ApiRef, createApiRef } from '../system'; - -/** - * Feature flag descriptor. - * - * @public - */ -export type FeatureFlag = { - name: string; - pluginId: string; - description?: string; -}; - -/** - * Enum representing the state of a feature flag (inactive/active). - * - * @public - */ -export const FeatureFlagState = { - /** - * Feature flag inactive (disabled). - */ - None: 0, - /** - * Feature flag active (enabled). - */ - Active: 1, -} as const; - -/** - * @public - */ -export type FeatureFlagState = - (typeof FeatureFlagState)[keyof typeof FeatureFlagState]; - -/** - * @public - */ -export namespace FeatureFlagState { - export type None = typeof FeatureFlagState.None; - export type Active = typeof FeatureFlagState.Active; -} - -/** - * Options to use when saving feature flags. - * - * @public - */ -export type FeatureFlagsSaveOptions = { - /** - * The new feature flag states to save. - */ - states: Record; - - /** - * Whether the saves states should be merged into the existing ones, or replace them. - * - * Defaults to false. - */ - merge?: boolean; -}; - -/** - * The feature flags API is used to toggle functionality to users across plugins and Backstage. - * - * @remarks - * - * Plugins can use this API to register feature flags that they have available - * for users to enable/disable, and this API will centralize the current user's - * state of which feature flags they would like to enable. - * - * This is ideal for Backstage plugins, as well as your own App, to trial incomplete - * or unstable upcoming features. Although there will be a common interface for users - * to enable and disable feature flags, this API acts as another way to enable/disable. - * - * @public - */ -export interface FeatureFlagsApi { - /** - * Registers a new feature flag. Once a feature flag has been registered it - * can be toggled by users, and read back to enable or disable features. - */ - registerFlag(flag: FeatureFlag): void; - - /** - * Get a list of all registered flags. - */ - getRegisteredFlags(): FeatureFlag[]; - - /** - * Whether the feature flag with the given name is currently activated for the user. - */ - isActive(name: string): boolean; - - /** - * Save the user's choice of feature flag states. - */ - save(options: FeatureFlagsSaveOptions): void; -} - -/** - * The {@link ApiRef} of {@link FeatureFlagsApi}. - * - * @public - */ -export const featureFlagsApiRef: ApiRef = createApiRef({ - id: 'core.featureflags', -}); +export { + type FeatureFlag, + type FeatureFlagsApi, + type FeatureFlagsSaveOptions, + FeatureFlagState, + featureFlagsApiRef, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/definitions/FetchApi.ts b/packages/core-plugin-api/src/apis/definitions/FetchApi.ts index aba0e53bb7..7fdf949e58 100644 --- a/packages/core-plugin-api/src/apis/definitions/FetchApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/FetchApi.ts @@ -14,38 +14,4 @@ * limitations under the License. */ -import { ApiRef, createApiRef } from '../system'; - -/** - * A wrapper for the fetch API, that has additional behaviors such as the - * ability to automatically inject auth information where necessary. - * - * @public - */ -export type FetchApi = { - /** - * The `fetch` implementation. - */ - fetch: typeof fetch; -}; - -/** - * The {@link ApiRef} of {@link FetchApi}. - * - * @remarks - * - * This is a wrapper for the fetch API, that has additional behaviors such as - * the ability to automatically inject auth information where necessary. - * - * Note that the default behavior of this API (unless overridden by your org), - * is to require that the user is already signed in so that it has auth - * information to inject. Therefore, using the default implementation of this - * utility API e.g. on the `SignInPage` or similar, would cause issues. In - * special circumstances like those, you can use the regular system `fetch` - * instead. - * - * @public - */ -export const fetchApiRef: ApiRef = createApiRef({ - id: 'core.fetch', -}); +export { type FetchApi, fetchApiRef } from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/definitions/IdentityApi.ts b/packages/core-plugin-api/src/apis/definitions/IdentityApi.ts index 1b127a1971..0648f96059 100644 --- a/packages/core-plugin-api/src/apis/definitions/IdentityApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/IdentityApi.ts @@ -13,44 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ApiRef, createApiRef } from '../system'; -import { BackstageUserIdentity, ProfileInfo } from './auth'; -/** - * The Identity API used to identify and get information about the signed in user. - * - * @public - */ -export type IdentityApi = { - /** - * The profile of the signed in user. - */ - getProfileInfo(): Promise; - - /** - * User identity information within Backstage. - */ - getBackstageIdentity(): Promise; - - /** - * Provides credentials in the form of a token which proves the identity of the signed in user. - * - * The token will be undefined if the signed in user does not have a verified - * identity, such as a demo user or mocked user for e2e tests. - */ - getCredentials(): Promise<{ token?: string }>; - - /** - * Sign out the current user - */ - signOut(): Promise; -}; - -/** - * The {@link ApiRef} of {@link IdentityApi}. - * - * @public - */ -export const identityApiRef: ApiRef = createApiRef({ - id: 'core.identity', -}); +export { + type IdentityApi, + identityApiRef, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/definitions/OAuthRequestApi.ts b/packages/core-plugin-api/src/apis/definitions/OAuthRequestApi.ts index 75bf3a3864..c217b77fb5 100644 --- a/packages/core-plugin-api/src/apis/definitions/OAuthRequestApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/OAuthRequestApi.ts @@ -14,118 +14,10 @@ * limitations under the License. */ -import { Observable } from '@backstage/types'; -import { ApiRef, createApiRef } from '../system'; -import { AuthProviderInfo } from './auth'; - -/** - * Describes how to handle auth requests. Both how to show them to the user, and what to do when - * the user accesses the auth request. - * - * @public - */ -export type OAuthRequesterOptions = { - /** - * Information about the auth provider, which will be forwarded to auth requests. - */ - provider: AuthProviderInfo; - - /** - * Implementation of the auth flow, which will be called synchronously when - * trigger() is called on an auth requests. - */ - onAuthRequest(scopes: Set): Promise; -}; - -/** - * Function used to trigger new auth requests for a set of scopes. - * - * @remarks - * - * The returned promise will resolve to the same value returned by the onAuthRequest in the - * {@link OAuthRequesterOptions}. Or rejected, if the request is rejected. - * - * This function can be called multiple times before the promise resolves. All calls - * will be merged into one request, and the scopes forwarded to the onAuthRequest will be the - * union of all requested scopes. - * - * @public - */ -export type OAuthRequester = ( - scopes: Set, -) => Promise; - -/** - * An pending auth request for a single auth provider. The request will remain in this pending - * state until either reject() or trigger() is called. - * - * @remarks - * - * Any new requests for the same provider are merged into the existing pending request, meaning - * there will only ever be a single pending request for a given provider. - * - * @public - */ -export type PendingOAuthRequest = { - /** - * Information about the auth provider, as given in the AuthRequesterOptions - */ - provider: AuthProviderInfo; - - /** - * Rejects the request, causing all pending AuthRequester calls to fail with "RejectedError". - */ - reject(): void; - - /** - * Trigger the auth request to continue the auth flow, by for example showing a popup. - * - * Synchronously calls onAuthRequest with all scope currently in the request. - */ - trigger(): Promise; -}; - -/** - * Provides helpers for implemented OAuth login flows within Backstage. - * - * @public - */ -export type OAuthRequestApi = { - /** - * A utility for showing login popups or similar things, and merging together multiple requests for - * different scopes into one request that includes all scopes. - * - * The passed in options provide information about the login provider, and how to handle auth requests. - * - * The returned AuthRequester function is used to request login with new scopes. These requests - * are merged together and forwarded to the auth handler, as soon as a consumer of auth requests - * triggers an auth flow. - * - * See AuthRequesterOptions, AuthRequester, and handleAuthRequests for more info. - */ - createAuthRequester( - options: OAuthRequesterOptions, - ): OAuthRequester; - - /** - * Observers pending auth requests. The returned observable will emit all - * current active auth request, at most one for each created auth requester. - * - * Each request has its own info about the login provider, forwarded from the auth requester options. - * - * Depending on user interaction, the request should either be rejected, or used to trigger the auth handler. - * If the request is rejected, all pending AuthRequester calls will fail with a "RejectedError". - * If a auth is triggered, and the auth handler resolves successfully, then all currently pending - * AuthRequester calls will resolve to the value returned by the onAuthRequest call. - */ - authRequest$(): Observable; -}; - -/** - * The {@link ApiRef} of {@link OAuthRequestApi}. - * - * @public - */ -export const oauthRequestApiRef: ApiRef = createApiRef({ - id: 'core.oauthrequest', -}); +export { + type OAuthRequesterOptions, + type OAuthRequester, + type PendingOAuthRequest, + type OAuthRequestApi, + oauthRequestApiRef, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/definitions/StorageApi.ts b/packages/core-plugin-api/src/apis/definitions/StorageApi.ts index 1506e5f143..4f3af7b611 100644 --- a/packages/core-plugin-api/src/apis/definitions/StorageApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/StorageApi.ts @@ -14,97 +14,8 @@ * limitations under the License. */ -import { ApiRef, createApiRef } from '../system'; -import { JsonValue, Observable } from '@backstage/types'; - -/** - * A snapshot in time of the current known value of a storage key. - * - * @public - */ -export type StorageValueSnapshot = - | { - key: string; - presence: 'unknown' | 'absent'; - value?: undefined; - } - | { - key: string; - presence: 'present'; - value: TValue; - }; - -/** - * Provides a key-value persistence API. - * - * @public - */ -export interface StorageApi { - /** - * Create a bucket to store data in. - * - * @param name - Namespace for the storage to be stored under, - * will inherit previous namespaces too - */ - forBucket(name: string): StorageApi; - - /** - * Remove persistent data. - * - * @param key - Unique key associated with the data. - */ - remove(key: string): Promise; - - /** - * Save persistent data, and emit messages to anyone that is using - * {@link StorageApi.observe$} for this key. - * - * @param key - Unique key associated with the data. - * @param data - The data to be stored under the key. - */ - set(key: string, data: T): Promise; - - /** - * Observe the value over time for a particular key in the current bucket. - * - * @remarks - * - * The observable will only emit values when the value changes in the underlying - * storage, although multiple values with the same shape may be emitted in a row. - * - * If a {@link StorageApi.snapshot} of a key is retrieved and the presence is - * `'unknown'`, then you are guaranteed to receive a snapshot with a known - * presence, as long as you observe the key within the same tick. - * - * Since the emitted values are shared across all subscribers, it is important - * not to mutate the returned values. The values may be frozen as a precaution. - * - * @param key - Unique key associated with the data - */ - observe$( - key: string, - ): Observable>; - - /** - * Returns an immediate snapshot value for the given key, if possible. - * - * @remarks - * - * Combine with {@link StorageApi.observe$} to get notified of value changes. - * - * Note that this method is synchronous, and some underlying storages may be - * unable to retrieve a value using this method - the result may or may not - * consistently have a presence of 'unknown'. Use {@link StorageApi.observe$} - * to be sure to receive an actual value eventually. - */ - snapshot(key: string): StorageValueSnapshot; -} - -/** - * The {@link ApiRef} of {@link StorageApi}. - * - * @public - */ -export const storageApiRef: ApiRef = createApiRef({ - id: 'core.storage', -}); +export { + type StorageValueSnapshot, + type StorageApi, + storageApiRef, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/definitions/auth.ts b/packages/core-plugin-api/src/apis/definitions/auth.ts index 9a0ddcd958..a60c84c635 100644 --- a/packages/core-plugin-api/src/apis/definitions/auth.ts +++ b/packages/core-plugin-api/src/apis/definitions/auth.ts @@ -16,493 +16,28 @@ /* We want to maintain the same information as an enum, so we disable the redeclaration warning */ /* eslint-disable @typescript-eslint/no-redeclare */ -import { ApiRef, createApiRef } from '../system'; -import { IconComponent } from '../../icons/types'; -import { Observable } from '@backstage/types'; - -/** - * This file contains declarations for common interfaces of auth-related APIs. - * The declarations should be used to signal which type of authentication and - * authorization methods each separate auth provider supports. - * - * For example, a Google OAuth provider that supports OAuth 2 and OpenID Connect, - * would be declared as follows: - * - * const googleAuthApiRef = createApiRef({ ... }) - */ - -/** - * Information about the auth provider. - * - * @remarks - * - * This information is used both to connect the correct auth provider in the backend, as - * well as displaying the provider to the user. - * - * @public - */ -export type AuthProviderInfo = { - /** - * The ID of the auth provider. This should match with ID of the provider in the `@backstage/auth-backend`. - */ - id: string; - - /** - * Title for the auth provider, for example "GitHub" - */ - title: string; - - /** - * Icon for the auth provider. - */ - icon: IconComponent; - - /** - * Optional user friendly messaage to display for the auth provider. - */ - message?: string; -}; - -/** - * An array of scopes, or a scope string formatted according to the - * auth provider, which is typically a space separated list. - * - * @remarks - * - * See the documentation for each auth provider for the list of scopes - * supported by each provider. - * - * @public - */ -export type OAuthScope = string | string[]; - -/** - * Configuration of an authentication request. - * - * @public - */ -export type AuthRequestOptions = { - /** - * If this is set to true, the user will not be prompted to log in, - * and an empty response will be returned if there is no existing session. - * - * This can be used to perform a check whether the user is logged in, or if you don't - * want to force a user to be logged in, but provide functionality if they already are. - * - * @defaultValue false - */ - optional?: boolean; - - /** - * If this is set to true, the request will bypass the regular oauth login modal - * and open the login popup directly. - * - * The method must be called synchronously from a user action for this to work in all browsers. - * - * @defaultValue false - */ - instantPopup?: boolean; -}; - -/** - * This API provides access to OAuth 2 credentials. It lets you request access tokens, - * which can be used to act on behalf of the user when talking to APIs. - * - * @public - */ -export type OAuthApi = { - /** - * Requests an OAuth 2 Access Token, optionally with a set of scopes. The access token allows - * you to make requests on behalf of the user, and the copes may grant you broader access, depending - * on the auth provider. - * - * Each auth provider has separate handling of scope, so you need to look at the documentation - * for each one to know what scope you need to request. - * - * This method is cheap and should be called each time an access token is used. Do not for example - * store the access token in React component state, as that could cause the token to expire. Instead - * fetch a new access token for each request. - * - * Be sure to include all required scopes when requesting an access token. When testing your implementation - * it is best to log out the Backstage session and then visit your plugin page directly, as - * you might already have some required scopes in your existing session. Not requesting the correct - * scopes can lead to 403 or other authorization errors, which can be tricky to debug. - * - * If the user has not yet granted access to the provider and the set of requested scopes, the user - * will be prompted to log in. The returned promise will not resolve until the user has - * successfully logged in. The returned promise can be rejected, but only if the user rejects the login request. - */ - getAccessToken( - scope?: OAuthScope, - options?: AuthRequestOptions, - ): Promise; -}; - -/** - * This API provides access to OpenID Connect credentials. It lets you request ID tokens, - * which can be passed to backend services to prove the user's identity. - * - * @public - */ -export type OpenIdConnectApi = { - /** - * Requests an OpenID Connect ID Token. - * - * This method is cheap and should be called each time an ID token is used. Do not for example - * store the id token in React component state, as that could cause the token to expire. Instead - * fetch a new id token for each request. - * - * If the user has not yet logged in to Google inside Backstage, the user will be prompted - * to log in. The returned promise will not resolve until the user has successfully logged in. - * The returned promise can be rejected, but only if the user rejects the login request. - */ - getIdToken(options?: AuthRequestOptions): Promise; -}; - -/** - * This API provides access to profile information of the user from an auth provider. - * - * @public - */ -export type ProfileInfoApi = { - /** - * Get profile information for the user as supplied by this auth provider. - * - * If the optional flag is not set, a session is guaranteed to be returned, while if - * the optional flag is set, the session may be undefined. See {@link AuthRequestOptions} for more details. - */ - getProfile(options?: AuthRequestOptions): Promise; -}; - -/** - * This API provides access to the user's identity within Backstage. - * - * @remarks - * - * An auth provider that implements this interface can be used to sign-in to backstage. It is - * not intended to be used directly from a plugin, but instead serves as a connection between - * this authentication method and the app's {@link IdentityApi} - * - * @public - */ -export type BackstageIdentityApi = { - /** - * Get the user's identity within Backstage. This should normally not be called directly, - * use the {@link IdentityApi} instead. - * - * If the optional flag is not set, a session is guaranteed to be returned, while if - * the optional flag is set, the session may be undefined. See {@link AuthRequestOptions} for more details. - */ - getBackstageIdentity( - options?: AuthRequestOptions, - ): Promise; -}; - -/** - * User identity information within Backstage. - * - * @public - */ -export type BackstageUserIdentity = { - /** - * The type of identity that this structure represents. In the frontend app - * this will currently always be 'user'. - */ - type: 'user'; - - /** - * The entityRef of the user in the catalog. - * For example User:default/sandra - */ - userEntityRef: string; - - /** - * The user and group entities that the user claims ownership through - */ - ownershipEntityRefs: string[]; -}; - -/** - * Token and Identity response, with the users claims in the Identity. - * - * @public - */ -export type BackstageIdentityResponse = { - /** - * The token used to authenticate the user within Backstage. - */ - token: string; - - /** - * The time at which the token expires. If not set, it can be assumed that the token does not expire. - */ - expiresAt?: Date; - - /** - * Identity information derived from the token. - */ - identity: BackstageUserIdentity; -}; - -/** - * Profile information of the user. - * - * @public - */ -export type ProfileInfo = { - /** - * Email ID. - */ - email?: string; - - /** - * Display name that can be presented to the user. - */ - displayName?: string; - - /** - * URL to an avatar image of the user. - */ - picture?: string; -}; - -/** - * Session state values passed to subscribers of the SessionApi. - * - * @public - */ -export const SessionState = { - /** - * User signed in. - */ - SignedIn: 'SignedIn', - /** - * User not signed in. - */ - SignedOut: 'SignedOut', -} as const; - -/** - * @public - */ -export type SessionState = (typeof SessionState)[keyof typeof SessionState]; - -/** - * @public - */ -export namespace SessionState { - export type SignedIn = typeof SessionState.SignedIn; - export type SignedOut = typeof SessionState.SignedOut; -} - -/** - * The SessionApi provides basic controls for any auth provider that is tied to a persistent session. - * - * @public - */ -export type SessionApi = { - /** - * Sign in with a minimum set of permissions. - */ - signIn(): Promise; - - /** - * Sign out from the current session. This will reload the page. - */ - signOut(): Promise; - - /** - * Observe the current state of the auth session. Emits the current state on subscription. - */ - sessionState$(): Observable; -}; - -/** - * Provides authentication towards Google APIs and identities. - * - * @public - * @remarks - * - * See {@link https://developers.google.com/identity/protocols/googlescopes} for a full list of supported scopes. - * - * Note that the ID token payload is only guaranteed to contain the user's numerical Google ID, - * email and expiration information. Do not rely on any other fields, as they might not be present. - */ -export const googleAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi -> = createApiRef({ - id: 'core.auth.google', -}); - -/** - * Provides authentication towards GitHub APIs. - * - * @public - * @remarks - * - * See {@link https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/} - * for a full list of supported scopes. - */ -export const githubAuthApiRef: ApiRef< - OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi -> = createApiRef({ - id: 'core.auth.github', -}); - -/** - * Provides authentication towards Okta APIs. - * - * @public - * @remarks - * - * See {@link https://developer.okta.com/docs/guides/implement-oauth-for-okta/scopes/} - * for a full list of supported scopes. - */ -export const oktaAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi -> = createApiRef({ - id: 'core.auth.okta', -}); - -/** - * Provides authentication towards GitLab APIs. - * - * @public - * @remarks - * - * See {@link https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#limiting-scopes-of-a-personal-access-token} - * for a full list of supported scopes. - */ -export const gitlabAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi -> = createApiRef({ - id: 'core.auth.gitlab', -}); - -/** - * Provides authentication towards Microsoft APIs and identities. - * - * @public - * @remarks - * - * For more info and a full list of supported scopes, see: - * - {@link https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent} - * - {@link https://docs.microsoft.com/en-us/graph/permissions-reference} - */ -export const microsoftAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi -> = createApiRef({ - id: 'core.auth.microsoft', -}); - -/** - * Provides authentication towards OneLogin APIs. - * - * @public - */ -export const oneloginAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi -> = createApiRef({ - id: 'core.auth.onelogin', -}); - -/** - * Provides authentication towards Bitbucket APIs. - * - * @public - * @remarks - * - * See {@link https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/} - * for a full list of supported scopes. - */ -export const bitbucketAuthApiRef: ApiRef< - OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi -> = createApiRef({ - id: 'core.auth.bitbucket', -}); - -/** - * Provides authentication towards Bitbucket Server APIs. - * - * @public - * @remarks - * - * See {@link https://confluence.atlassian.com/bitbucketserver/bitbucket-oauth-2-0-provider-api-1108483661.html#BitbucketOAuth2.0providerAPI-scopes} - * for a full list of supported scopes. - */ -export const bitbucketServerAuthApiRef: ApiRef< - OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi -> = createApiRef({ - id: 'core.auth.bitbucket-server', -}); - -/** - * Provides authentication towards Atlassian APIs. - * - * @public - * @remarks - * - * See {@link https://developer.atlassian.com/cloud/jira/platform/scopes-for-connect-and-oauth-2-3LO-apps/} - * for a full list of supported scopes. - */ -export const atlassianAuthApiRef: ApiRef< - OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi -> = createApiRef({ - id: 'core.auth.atlassian', -}); - -/** - * Provides authentication towards VMware Cloud APIs and identities. - * - * @public - * @remarks - * - * For more info about VMware Cloud identity and access management: - * - {@link https://docs.vmware.com/en/VMware-Cloud-services/services/Using-VMware-Cloud-Services/GUID-53D39337-D93A-4B84-BD18-DDF43C21479A.html} - */ -export const vmwareCloudAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi -> = createApiRef({ - id: 'core.auth.vmware-cloud', -}); - -/** - * Provides authentication towards OpenShift APIs and identities. - * - * @public - * @remarks - * - * See {@link https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html/authentication_and_authorization/configuring-oauth-clients} - * on how to configure the OAuth clients and - * {@link https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html-single/authentication_and_authorization/index#tokens-scoping-about_configuring-internal-oauth} - * for available scopes. - */ -export const openshiftAuthApiRef: ApiRef< - OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi -> = createApiRef({ - id: 'core.auth.openshift', -}); +export { + type AuthProviderInfo, + type OAuthScope, + type AuthRequestOptions, + type OAuthApi, + type OpenIdConnectApi, + type ProfileInfoApi, + type BackstageIdentityApi, + type BackstageUserIdentity, + type BackstageIdentityResponse, + type ProfileInfo, + SessionState, + type SessionApi, + googleAuthApiRef, + githubAuthApiRef, + oktaAuthApiRef, + gitlabAuthApiRef, + microsoftAuthApiRef, + oneloginAuthApiRef, + bitbucketAuthApiRef, + bitbucketServerAuthApiRef, + atlassianAuthApiRef, + vmwareCloudAuthApiRef, + openshiftAuthApiRef, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/system/ApiRef.test.ts b/packages/core-plugin-api/src/apis/system/ApiRef.test.ts index dab872236f..5bfb83ce3e 100644 --- a/packages/core-plugin-api/src/apis/system/ApiRef.test.ts +++ b/packages/core-plugin-api/src/apis/system/ApiRef.test.ts @@ -19,9 +19,15 @@ import { createApiRef } from './ApiRef'; describe('ApiRef', () => { it('should be created', () => { const ref = createApiRef({ id: 'abc' }); + expect(ref.$$type).toBe('@backstage/ApiRef'); expect(ref.id).toBe('abc'); expect(String(ref)).toBe('apiRef{abc}'); - expect(() => ref.T).toThrow('tried to read ApiRef.T of apiRef{abc}'); + expect(ref.T).toBeNull(); + }); + + it('should not accept pluginId in the core createApiRef config', () => { + // @ts-expect-error pluginId is not supported in core-plugin-api + createApiRef({ id: 'abc', pluginId: 'test' }); }); it('should reject invalid ids', () => { diff --git a/packages/core-plugin-api/src/apis/system/ApiRef.ts b/packages/core-plugin-api/src/apis/system/ApiRef.ts index adedff9f73..8c3986c583 100644 --- a/packages/core-plugin-api/src/apis/system/ApiRef.ts +++ b/packages/core-plugin-api/src/apis/system/ApiRef.ts @@ -14,51 +14,23 @@ * limitations under the License. */ -import type { ApiRef } from './types'; +import { + createApiRef as createFrontendApiRef, + type ApiRef, + type ApiRefConfig, +} from '@backstage/frontend-plugin-api'; -/** - * API reference configuration - holds an ID of the referenced API. - * - * @public - */ -export type ApiRefConfig = { - id: string; -}; - -class ApiRefImpl implements ApiRef { - constructor(private readonly config: ApiRefConfig) { - const valid = config.id - .split('.') - .flatMap(part => part.split('-')) - .every(part => part.match(/^[a-z][a-z0-9]*$/)); - if (!valid) { - throw new Error( - `API id must only contain period separated lowercase alphanum tokens with dashes, got '${config.id}'`, - ); - } - } - - get id(): string { - return this.config.id; - } - - // Utility for getting type of an api, using `typeof apiRef.T` - get T(): T { - throw new Error(`tried to read ApiRef.T of ${this}`); - } - - toString() { - return `apiRef{${this.config.id}}`; - } -} +const createFrontendApiRefCompat = createFrontendApiRef as ( + config: ApiRefConfig, +) => ApiRef; /** * Creates a reference to an API. * - * @param config - The descriptor of the API to reference. - * @returns An API reference. * @public */ export function createApiRef(config: ApiRefConfig): ApiRef { - return new ApiRefImpl(config); + return createFrontendApiRefCompat(config); } + +export type { ApiRefConfig }; diff --git a/packages/core-plugin-api/src/apis/system/helpers.ts b/packages/core-plugin-api/src/apis/system/helpers.ts index eaffed3003..07acaa6a53 100644 --- a/packages/core-plugin-api/src/apis/system/helpers.ts +++ b/packages/core-plugin-api/src/apis/system/helpers.ts @@ -14,61 +14,4 @@ * limitations under the License. */ -import { ApiRef, ApiFactory, TypesToApiRefs } from './types'; - -/** - * Used to infer types for a standalone {@link ApiFactory} that isn't immediately passed - * to another function. - * - * @remarks - * - * This function doesn't actually do anything, it's only used to infer types. - * - * @public - */ -export function createApiFactory< - Api, - Impl extends Api, - Deps extends { [name in string]: unknown }, ->(factory: ApiFactory): ApiFactory; -/** - * Used to infer types for a standalone {@link ApiFactory} that isn't immediately passed - * to another function. - * - * @param api - Ref of the API that will be produced by the factory. - * @param instance - Implementation of the API to use. - * @public - */ -export function createApiFactory( - api: ApiRef, - instance: Impl, -): ApiFactory; -/** - * Used to infer types for a standalone {@link ApiFactory} that isn't immediately passed - * to another function. - * - * @remarks - * - * Creates factory from {@link ApiRef} or returns the factory itself if provided. - * - * @param factory - Existing factory or {@link ApiRef}. - * @param instance - The instance to be returned by the factory. - * @public - */ -export function createApiFactory< - Api, - Impl extends Api, - Deps extends { [name in string]: unknown }, ->( - factory: ApiFactory | ApiRef, - instance?: Impl, -): ApiFactory { - if ('id' in factory) { - return { - api: factory, - deps: {} as TypesToApiRefs, - factory: () => instance!, - }; - } - return factory; -} +export { createApiFactory } from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/system/types.ts b/packages/core-plugin-api/src/apis/system/types.ts index 96614c320c..3b4ba449d3 100644 --- a/packages/core-plugin-api/src/apis/system/types.ts +++ b/packages/core-plugin-api/src/apis/system/types.ts @@ -14,61 +14,11 @@ * limitations under the License. */ -/** - * API reference. - * - * @public - */ -export type ApiRef = { - id: string; - T: T; -}; - -/** - * Catch-all {@link ApiRef} type. - * - * @public - */ -export type AnyApiRef = ApiRef; - -/** - * Wraps a type with API properties into a type holding their respective {@link ApiRef}s. - * - * @public - */ -export type TypesToApiRefs = { [key in keyof T]: ApiRef }; - -/** - * Provides lookup of APIs through their {@link ApiRef}s. - * - * @public - */ -export type ApiHolder = { - get(api: ApiRef): T | undefined; -}; - -/** - * Describes type returning API implementations. - * - * @public - */ -export type ApiFactory< - Api, - Impl extends Api, - Deps extends { [name in string]: unknown }, -> = { - api: ApiRef; - deps: TypesToApiRefs; - factory(deps: Deps): Impl; -}; - -/** - * Catch-all {@link ApiFactory} type. - * - * @public - */ -export type AnyApiFactory = ApiFactory< - unknown, - unknown, - { [key in string]: unknown } ->; +export type { + ApiRef, + AnyApiRef, + TypesToApiRefs, + ApiHolder, + ApiFactory, + AnyApiFactory, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/system/useApi.tsx b/packages/core-plugin-api/src/apis/system/useApi.tsx index 30e39033f7..a2caec8aea 100644 --- a/packages/core-plugin-api/src/apis/system/useApi.tsx +++ b/packages/core-plugin-api/src/apis/system/useApi.tsx @@ -14,81 +14,4 @@ * limitations under the License. */ -import { ComponentType, PropsWithChildren } from 'react'; -import { ApiRef, ApiHolder, TypesToApiRefs } from './types'; -import { useVersionedContext } from '@backstage/version-bridge'; -import { NotImplementedError } from '@backstage/errors'; - -/** - * React hook for retrieving {@link ApiHolder}, an API catalog. - * - * @public - */ -export function useApiHolder(): ApiHolder { - const versionedHolder = useVersionedContext<{ 1: ApiHolder }>('api-context'); - if (!versionedHolder) { - throw new NotImplementedError('API context is not available'); - } - - const apiHolder = versionedHolder.atVersion(1); - if (!apiHolder) { - throw new NotImplementedError('ApiContext v1 not available'); - } - return apiHolder; -} - -/** - * React hook for retrieving APIs. - * - * @param apiRef - Reference of the API to use. - * @public - */ -export function useApi(apiRef: ApiRef): T { - const apiHolder = useApiHolder(); - - const api = apiHolder.get(apiRef); - if (!api) { - throw new NotImplementedError(`No implementation available for ${apiRef}`); - } - return api; -} - -/** - * Wrapper for giving component an API context. - * - * @param apis - APIs for the context. - * @public - */ -export function withApis(apis: TypesToApiRefs) { - return function withApisWrapper( - WrappedComponent: ComponentType, - ) { - const Hoc = (props: PropsWithChildren>) => { - const apiHolder = useApiHolder(); - - const impls = {} as T; - - for (const key in apis) { - if (apis.hasOwnProperty(key)) { - const ref = apis[key]; - - const api = apiHolder.get(ref); - if (!api) { - throw new NotImplementedError( - `No implementation available for ${ref}`, - ); - } - impls[key] = api; - } - } - - return ; - }; - const displayName = - WrappedComponent.displayName || WrappedComponent.name || 'Component'; - - Hoc.displayName = `withApis(${displayName})`; - - return Hoc; - }; -} +export { useApiHolder, useApi, withApis } from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/app/useApp.test.tsx b/packages/core-plugin-api/src/app/useApp.test.tsx index 34c956138e..7888df0464 100644 --- a/packages/core-plugin-api/src/app/useApp.test.tsx +++ b/packages/core-plugin-api/src/app/useApp.test.tsx @@ -15,20 +15,132 @@ */ import { renderHook } from '@testing-library/react'; +import { PropsWithChildren } from 'react'; import { createVersionedContextForTesting } from '@backstage/version-bridge'; +import { + appTreeApiRef, + iconsApiRef, + AppTreeApi, + IconsApi, + AppTree, + AppNode, +} from '@backstage/frontend-plugin-api'; import { useApp } from './useApp'; +import { TestApiProvider, withLogCollector } from '@backstage/test-utils'; -describe('v1 consumer', () => { - const context = createVersionedContextForTesting('app-context'); +describe('useApp', () => { + describe('old system', () => { + const context = createVersionedContextForTesting('app-context'); - afterEach(() => { - context.reset(); + afterEach(() => { + context.reset(); + }); + + it('should provide an app context', () => { + const wrapper = ({ children }: PropsWithChildren<{}>) => ( + {children} + ); + context.set({ 1: 'context-value' }); + + const renderedHook = renderHook(() => useApp(), { wrapper }); + expect(renderedHook.result.current).toBe('context-value'); + }); }); - it('should provide an app context', () => { - context.set({ 1: 'context-value' }); + describe('new system', () => { + const mockIcon = () => null; + const mockIconsApi: IconsApi = { + icon: jest.fn((key: string) => + key === 'test-icon' ? mockIcon() : undefined, + ), + getIcon: jest.fn((key: string) => + key === 'test-icon' ? mockIcon : undefined, + ), + listIconKeys: jest.fn(() => ['test-icon']), + }; - const renderedHook = renderHook(() => useApp()); - expect(renderedHook.result.current).toBe('context-value'); + const mockPlugin = { + id: 'test-plugin', + }; + + const mockAppNode: AppNode = { + spec: { + id: 'test-node', + attachTo: { id: 'root', input: 'children' }, + extension: {} as any, + disabled: false, + plugin: mockPlugin as any, + }, + edges: { + attachments: new Map(), + }, + }; + + const mockAppTree: AppTree = { + root: mockAppNode, + nodes: new Map([['test-node', mockAppNode]]), + orphans: [], + }; + + const mockAppTreeApi: AppTreeApi = { + getTree: jest.fn(() => ({ tree: mockAppTree })), + getNodesByRoutePath: jest.fn(() => ({ nodes: [] })), + }; + + it('should provide an app context from new app system', () => { + const wrapper = ({ children }: PropsWithChildren<{}>) => ( + + {children} + + ); + + const renderedHook = renderHook(() => useApp(), { wrapper }); + + const appContext = renderedHook.result.current; + expect(appContext).toBeDefined(); + expect(appContext.getPlugins()).toHaveLength(1); + expect(appContext.getPlugins()[0].getId()).toBe('test-plugin'); + expect(appContext.getSystemIcon('test-icon')).toBe(mockIcon); + expect(appContext.getSystemIcons()).toEqual({ 'test-icon': mockIcon }); + expect(appContext.getComponents().Progress).toBeDefined(); + expect(appContext.getComponents().NotFoundErrorPage).toBeDefined(); + }); + + it('should error on missing appTreeApi or iconsApi', () => { + withLogCollector(['error'], () => { + expect(() => + renderHook(() => useApp(), { + wrapper: ({ children }: PropsWithChildren<{}>) => ( + {children} + ), + }), + ).toThrow('App context is not available'); + + expect(() => + renderHook(() => useApp(), { + wrapper: ({ children }: PropsWithChildren<{}>) => ( + + {children} + + ), + }), + ).toThrow('App context is not available'); + + expect(() => + renderHook(() => useApp(), { + wrapper: ({ children }: PropsWithChildren<{}>) => ( + + {children} + + ), + }), + ).toThrow('App context is not available'); + }); + }); }); }); diff --git a/packages/core-plugin-api/src/app/useApp.tsx b/packages/core-plugin-api/src/app/useApp.tsx index 05723ee387..5d789689cd 100644 --- a/packages/core-plugin-api/src/app/useApp.tsx +++ b/packages/core-plugin-api/src/app/useApp.tsx @@ -14,18 +14,159 @@ * limitations under the License. */ +import { useMemo } from 'react'; import { useVersionedContext } from '@backstage/version-bridge'; +import { + appTreeApiRef, + iconsApiRef, + useApiHolder, + ErrorDisplay, + NotFoundErrorPage, + Progress, + createFrontendPlugin, + FrontendPlugin, + ApiHolder, +} from '@backstage/frontend-plugin-api'; +import { + AppComponents, + IconComponent, + BackstagePlugin, +} from '@backstage/core-plugin-api'; +import { getOrCreateGlobalSingleton } from '@backstage/version-bridge'; import { AppContext as AppContextV1 } from './types'; +const legacyPluginStore = getOrCreateGlobalSingleton( + 'legacy-plugin-compatibility-store', + () => new WeakMap(), +); + +function toLegacyPlugin(plugin: FrontendPlugin): BackstagePlugin { + let legacy = legacyPluginStore.get(plugin); + if (legacy) { + return legacy; + } + + const errorMsg = 'Not implemented in legacy plugin compatibility layer'; + const notImplemented = () => { + throw new Error(errorMsg); + }; + + legacy = { + getId(): string { + return plugin.pluginId ?? plugin.id; + }, + get routes() { + return {}; + }, + get externalRoutes() { + return {}; + }, + getApis: notImplemented, + getFeatureFlags: notImplemented, + provide: notImplemented, + }; + + legacyPluginStore.set(plugin, legacy); + return legacy; +} + +function toNewPlugin(plugin: BackstagePlugin): FrontendPlugin { + return createFrontendPlugin({ + pluginId: plugin.getId(), + }); +} + +function useOptionalApiHolder(): ApiHolder | undefined { + try { + return useApiHolder(); + } catch { + return undefined; + } +} + /** * React hook providing {@link AppContext}. * * @public */ export const useApp = (): AppContextV1 => { + const apiHolder = useOptionalApiHolder(); + const appTreeApi = apiHolder?.get(appTreeApiRef); + const iconsApi = apiHolder?.get(iconsApiRef); const versionedContext = useVersionedContext<{ 1: AppContextV1 }>( 'app-context', ); + + const newAppContext = useMemo(() => { + if (!appTreeApi) { + return undefined; + } + + if (!iconsApi) { + return undefined; + } + + const { tree } = appTreeApi.getTree(); + + let gatheredPlugins: BackstagePlugin[] | undefined = undefined; + + const ErrorBoundaryFallbackWrapper: AppComponents['ErrorBoundaryFallback'] = + ({ plugin, ...rest }) => ( + + ); + + return { + getPlugins(): BackstagePlugin[] { + if (gatheredPlugins) { + return gatheredPlugins; + } + + const pluginSet = new Set(); + for (const node of tree.nodes.values()) { + const plugin = node.spec.plugin; + if (plugin) { + pluginSet.add(toLegacyPlugin(plugin)); + } + } + gatheredPlugins = Array.from(pluginSet); + + return gatheredPlugins; + }, + + getSystemIcon(key: string): IconComponent | undefined { + return iconsApi.getIcon(key); + }, + + getSystemIcons(): Record { + return Object.fromEntries( + iconsApi.listIconKeys().map(key => [key, iconsApi.getIcon(key)!]), + ); + }, + + getComponents(): AppComponents { + return { + NotFoundErrorPage: NotFoundErrorPage, + BootErrorPage() { + throw new Error( + 'The BootErrorPage app component should not be accessed by plugins', + ); + }, + Progress: Progress, + Router() { + throw new Error( + 'The Router app component should not be accessed by plugins', + ); + }, + ErrorBoundaryFallback: ErrorBoundaryFallbackWrapper, + }; + }, + }; + }, [appTreeApi, iconsApi]); + + if (newAppContext) { + return newAppContext; + } + if (!versionedContext) { throw new Error('App context is not available'); } diff --git a/packages/core-plugin-api/src/extensions/extensions.test.tsx b/packages/core-plugin-api/src/extensions/extensions.test.tsx index 75bb4ae443..59fe0d118d 100644 --- a/packages/core-plugin-api/src/extensions/extensions.test.tsx +++ b/packages/core-plugin-api/src/extensions/extensions.test.tsx @@ -26,7 +26,6 @@ import { createReactExtension, createRoutableExtension, } from './extensions'; -import { ForwardedError } from '@backstage/errors'; jest.mock('../app'); @@ -117,7 +116,13 @@ describe('extensions', () => { render(); }); screen.getByText('Error in my-plugin'); - expect(errors[0]).toMatchObject({ detail: new Error('Test error') }); + expect(errors).toEqual([ + expect.stringContaining('Error: Test error'), + expect.objectContaining({ type: 'unhandled-exception' }), + expect.stringContaining('Error: Test error'), + expect.objectContaining({ type: 'unhandled-exception' }), + expect.stringContaining('The above error occurred in'), + ]); }); it('should handle failed lazy loads', async () => { @@ -154,12 +159,17 @@ describe('extensions', () => { screen.getByText( 'Error in my-plugin: Error: Failed lazy loading of the BrokenComponent extension, try to reload the page; caused by Error: Test error', ); - expect(errors[0]).toMatchObject({ - detail: new ForwardedError( - 'Failed lazy loading of the BrokenComponent extension, try to reload the page', - new Error('Test error'), + expect(errors).toEqual([ + expect.stringContaining( + 'Error: Failed lazy loading of the BrokenComponent extension, try to reload the page', ), - }); + expect.objectContaining({ type: 'unhandled-exception' }), + expect.stringContaining( + 'Error: Failed lazy loading of the BrokenComponent extension, try to reload the page', + ), + expect.objectContaining({ type: 'unhandled-exception' }), + expect.stringContaining('The above error occurred in'), + ]); }); it('should wrap extended component with analytics context', async () => { diff --git a/packages/core-plugin-api/src/icons/types.ts b/packages/core-plugin-api/src/icons/types.ts index 2b00e1456f..2e64828584 100644 --- a/packages/core-plugin-api/src/icons/types.ts +++ b/packages/core-plugin-api/src/icons/types.ts @@ -14,24 +14,4 @@ * limitations under the License. */ -import { ComponentType } from 'react'; - -/** - * IconComponent is the common icon type used throughout Backstage when - * working with and rendering generic icons, including the app system icons. - * - * @remarks - * - * The type is based on SvgIcon from Material UI, but we do not want the plugin-api - * package to have a dependency on Material UI, nor do we want the props to be as broad - * as the SvgIconProps interface. - * - * If you have the need to forward additional props from SvgIconProps, you can - * open an issue or submit a PR to the main Backstage repo. When doing so please - * also describe your use-case and reasoning of the addition. - * - * @public - */ -export type IconComponent = ComponentType<{ - fontSize?: 'medium' | 'large' | 'small' | 'inherit'; -}>; +export type { IconComponent } from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/plugin/types.ts b/packages/core-plugin-api/src/plugin/types.ts index 3ba7706e16..057f493530 100644 --- a/packages/core-plugin-api/src/plugin/types.ts +++ b/packages/core-plugin-api/src/plugin/types.ts @@ -73,6 +73,8 @@ export type BackstagePlugin< export type PluginFeatureFlagConfig = { /** Feature flag name */ name: string; + /** Feature flag description */ + description?: string; }; /** diff --git a/packages/core-plugin-api/src/routing/ExternalRouteRef.test.ts b/packages/core-plugin-api/src/routing/ExternalRouteRef.test.ts index 05e7a23d5d..8c3a5f0802 100644 --- a/packages/core-plugin-api/src/routing/ExternalRouteRef.test.ts +++ b/packages/core-plugin-api/src/routing/ExternalRouteRef.test.ts @@ -16,6 +16,7 @@ import { AnyParams, ExternalRouteRef } from './types'; import { createExternalRouteRef } from './ExternalRouteRef'; +import { RouteResolutionApi, RouteFunc } from '@backstage/frontend-plugin-api'; describe('ExternalRouteRef', () => { it('should be created', () => { @@ -24,7 +25,9 @@ describe('ExternalRouteRef', () => { }); expect(routeRef.params).toEqual([]); expect(routeRef.optional).toBe(false); - expect(String(routeRef)).toBe('routeRef{type=external,id=my-route-ref}'); + expect(String(routeRef)).toMatch( + /^routeRef\{type=external,id=my-route-ref\}$/, + ); }); it('should be created as optional', () => { @@ -109,4 +112,48 @@ describe('ExternalRouteRef', () => { // To avoid complains about missing expectations and unused vars expect([_1, _2, _3, _4, _5, _6].join('')).toEqual(expect.any(String)); }); + + describe('with new frontend system', () => { + const routeResolutionApi = { resolve: jest.fn() } as RouteResolutionApi; + + function expectType(): ( + v: U, + ) => [T, U] extends [U, T] ? { ok(): void } : { invalid: U } { + return () => ({ ok() {} } as any); + } + + it('should resolve routes correctly', () => { + expectType | undefined>()( + routeResolutionApi.resolve(createExternalRouteRef({ id: '1' })), + ).ok(); + expectType | undefined>()( + routeResolutionApi.resolve( + createExternalRouteRef({ id: '1', optional: true }), + ), + ).ok(); + expectType | undefined>()( + routeResolutionApi.resolve( + createExternalRouteRef({ id: '1', optional: false }), + ), + ).ok(); + + expectType | undefined>()( + routeResolutionApi.resolve( + createExternalRouteRef({ id: '1', params: ['x'] }), + ), + ).ok(); + expectType | undefined>()( + routeResolutionApi.resolve( + createExternalRouteRef({ id: '1', params: ['x'], optional: true }), + ), + ).ok(); + expectType | undefined>()( + routeResolutionApi.resolve( + createExternalRouteRef({ id: '1', params: ['x'], optional: false }), + ), + ).ok(); + + expect(1).toBe(1); + }); + }); }); diff --git a/packages/core-plugin-api/src/routing/ExternalRouteRef.ts b/packages/core-plugin-api/src/routing/ExternalRouteRef.ts index 16e2f9bc72..5d33aa7a67 100644 --- a/packages/core-plugin-api/src/routing/ExternalRouteRef.ts +++ b/packages/core-plugin-api/src/routing/ExternalRouteRef.ts @@ -52,12 +52,45 @@ export class ExternalRouteRefImpl< } toString() { + if (this.#nfsId) { + return `externalRouteRef{id=${this.#nfsId},legacyId=${this.id}}`; + } return `routeRef{type=external,id=${this.id}}`; } getDefaultTarget() { return this.defaultTarget; } + + // NFS implementation below + readonly $$type = '@backstage/ExternalRouteRef'; + readonly version = 'v1'; + readonly T = undefined as any; + + #nfsId: string | undefined = undefined; + + getParams(): string[] { + return this.params as string[]; + } + getDescription(): string { + if (this.#nfsId) { + return this.#nfsId; + } + return this.id; + } + setId(newId: string) { + if (!newId) { + throw new Error(`ExternalRouteRef id must be a non-empty string`); + } + if (this.#nfsId && this.#nfsId !== newId) { + throw new Error( + `ExternalRouteRef was referenced twice as both '${ + this.#nfsId + }' and '${newId}'`, + ); + } + this.#nfsId = newId; + } } /** @@ -106,5 +139,5 @@ export function createExternalRouteRef< (options.params ?? []) as ParamKeys>, Boolean(options.optional) as Optional, options?.defaultTarget, - ); + ) as ExternalRouteRef, Optional>; } diff --git a/packages/core-plugin-api/src/routing/RouteRef.test.ts b/packages/core-plugin-api/src/routing/RouteRef.test.ts index 489900d892..29ab228b9f 100644 --- a/packages/core-plugin-api/src/routing/RouteRef.test.ts +++ b/packages/core-plugin-api/src/routing/RouteRef.test.ts @@ -16,6 +16,7 @@ import { AnyParams, RouteRef, ParamKeys } from './types'; import { createRouteRef } from './RouteRef'; +import { RouteResolutionApi, RouteFunc } from '@backstage/frontend-plugin-api'; describe('RouteRef', () => { it('should be created', () => { @@ -23,7 +24,9 @@ describe('RouteRef', () => { id: 'my-route-ref', }); expect(routeRef.params).toEqual([]); - expect(String(routeRef)).toBe('routeRef{type=absolute,id=my-route-ref}'); + expect(String(routeRef)).toMatch( + /^routeRef\{type=absolute,id=my-route-ref\}$/, + ); }); it('should be created with params', () => { @@ -95,4 +98,37 @@ describe('RouteRef', () => { expect(true).toBeDefined(); }); + + describe('with new frontend system', () => { + const routeResolutionApi = { resolve: jest.fn() } as RouteResolutionApi; + + function expectType(): ( + v: U, + ) => [T, U] extends [U, T] ? { ok(): void } : { invalid: U } { + return () => ({ ok() {} } as any); + } + + it('should resolve routes correctly', () => { + expectType | undefined>()( + routeResolutionApi.resolve(createRouteRef({ id: '1' })), + ).ok(); + expectType | undefined>()( + routeResolutionApi.resolve(createRouteRef({ id: '1' })), + ).ok(); + expectType | undefined>()( + routeResolutionApi.resolve(createRouteRef({ id: '1', params: [] })), + ).ok(); + + expectType | undefined>()( + routeResolutionApi.resolve(createRouteRef({ id: '1', params: ['x'] })), + ).ok(); + expectType | undefined>()( + routeResolutionApi.resolve( + createRouteRef({ id: '1', params: ['x', 'y'] }), + ), + ).ok(); + + expect(1).toBe(1); + }); + }); }); diff --git a/packages/core-plugin-api/src/routing/RouteRef.ts b/packages/core-plugin-api/src/routing/RouteRef.ts index d78f3ddb8d..c8465dafde 100644 --- a/packages/core-plugin-api/src/routing/RouteRef.ts +++ b/packages/core-plugin-api/src/routing/RouteRef.ts @@ -45,8 +45,40 @@ export class RouteRefImpl } toString() { + if (this.#nfsId) { + return `routeRef{id=${this.#nfsId},legacyId=${this.id}}`; + } return `routeRef{type=absolute,id=${this.id}}`; } + + // NFS implementation below + readonly $$type = '@backstage/RouteRef'; + readonly version = 'v1'; + readonly T = undefined as any; + readonly alias = undefined; + + #nfsId: string | undefined = undefined; + + getParams() { + return this.params; + } + getDescription() { + if (this.#nfsId) { + return this.#nfsId; + } + return this.id; + } + setId(newId: string) { + if (!newId) { + throw new Error(`RouteRef id must be a non-empty string`); + } + if (this.#nfsId && this.#nfsId !== newId) { + throw new Error( + `RouteRef was referenced twice as both '${this.#nfsId}' and '${newId}'`, + ); + } + this.#nfsId = newId; + } } /** @@ -72,5 +104,5 @@ export function createRouteRef< return new RouteRefImpl( config.id, (config.params ?? []) as ParamKeys>, - ); + ) as RouteRef>; } diff --git a/packages/core-plugin-api/src/routing/SubRouteRef.test.ts b/packages/core-plugin-api/src/routing/SubRouteRef.test.ts index 1db429e178..ccec4c97d6 100644 --- a/packages/core-plugin-api/src/routing/SubRouteRef.test.ts +++ b/packages/core-plugin-api/src/routing/SubRouteRef.test.ts @@ -17,6 +17,7 @@ import { AnyParams, SubRouteRef } from './types'; import { createSubRouteRef } from './SubRouteRef'; import { createRouteRef } from './RouteRef'; +import { RouteResolutionApi, RouteFunc } from '@backstage/frontend-plugin-api'; const parent = createRouteRef({ id: 'parent' }); const parentX = createRouteRef({ id: 'parent-x', params: ['x'] }); @@ -31,7 +32,7 @@ describe('SubRouteRef', () => { expect(routeRef.path).toBe('/foo'); expect(routeRef.parent).toBe(parent); expect(routeRef.params).toEqual([]); - expect(String(routeRef)).toBe('routeRef{type=sub,id=my-route-ref}'); + expect(String(routeRef)).toMatch(/^routeRef\{type=sub,id=my-route-ref\}$/); }); it('should be created with params', () => { @@ -125,4 +126,51 @@ describe('SubRouteRef', () => { // To avoid complains about missing expectations and unused vars expect([_1, _2, _3, _4].join('')).toEqual(expect.any(String)); }); + + describe('with new frontend system', () => { + const routeResolutionApi = { resolve: jest.fn() } as RouteResolutionApi; + + function expectType(): ( + v: U, + ) => [T, U] extends [U, T] ? { ok(): void } : { invalid: U } { + return () => ({ ok() {} } as any); + } + + it('should resolve routes correctly', () => { + expectType | undefined>()( + routeResolutionApi.resolve( + createSubRouteRef({ id: '1', parent, path: '/foo' }), + ), + ).ok(); + expectType | undefined>()( + routeResolutionApi.resolve( + createSubRouteRef({ id: '1', parent: parentX, path: '/foo' }), + ), + ).ok(); + + expectType | undefined>()( + routeResolutionApi.resolve( + createSubRouteRef({ id: '1', parent, path: '/:y' }), + ), + ).ok(); + expectType | undefined>()( + routeResolutionApi.resolve( + createSubRouteRef({ id: '1', parent: parentX, path: '/:y' }), + ), + ).ok(); + + expectType | undefined>()( + routeResolutionApi.resolve( + createSubRouteRef({ id: '1', parent, path: '/:y/:z' }), + ), + ).ok(); + expectType | undefined>()( + routeResolutionApi.resolve( + createSubRouteRef({ id: '1', parent: parentX, path: '/:y/:z' }), + ), + ).ok(); + + expect(1).toBe(1); + }); + }); }); diff --git a/packages/core-plugin-api/src/routing/SubRouteRef.ts b/packages/core-plugin-api/src/routing/SubRouteRef.ts index ed9ded41d4..8db4c1947b 100644 --- a/packages/core-plugin-api/src/routing/SubRouteRef.ts +++ b/packages/core-plugin-api/src/routing/SubRouteRef.ts @@ -56,6 +56,21 @@ export class SubRouteRefImpl toString() { return `routeRef{type=sub,id=${this.id}}`; } + + // NFS implementation below + readonly $$type = '@backstage/SubRouteRef'; + readonly version = 'v1'; + readonly T = undefined as any; + + getParams(): string[] { + return this.params as string[]; + } + getParent(): RouteRef { + return this.parent; + } + getDescription(): string { + return `at ${this.path} with parent ${this.parent}`; + } } /** @@ -155,7 +170,7 @@ export function createSubRouteRef< path, parent, params as ParamKeys>, - ) as SubRouteRef>>; + ); // But skip type checking of the return value itself, because the conditional // type checking of the parent parameter overlap is tricky to express. diff --git a/packages/core-plugin-api/src/routing/types.ts b/packages/core-plugin-api/src/routing/types.ts index 4598574afb..b0a4d83918 100644 --- a/packages/core-plugin-api/src/routing/types.ts +++ b/packages/core-plugin-api/src/routing/types.ts @@ -98,6 +98,11 @@ export type RouteRef = { /** @deprecated access to this property will be removed in the future */ params: ParamKeys; + + /** Compatibility field for new frontend system */ + readonly $$type: '@backstage/RouteRef'; + /** Compatibility field for new frontend system */ + readonly T: Params; }; /** @@ -120,6 +125,11 @@ export type SubRouteRef = { /** @deprecated access to this property will be removed in the future */ params: ParamKeys; + + /** Compatibility field for new frontend system */ + readonly $$type: '@backstage/SubRouteRef'; + /** Compatibility field for new frontend system */ + readonly T: Params; }; /** @@ -142,6 +152,11 @@ export type ExternalRouteRef< params: ParamKeys; optional?: Optional; + + /** Compatibility field for new frontend system */ + readonly $$type: '@backstage/ExternalRouteRef'; + /** Compatibility field for new frontend system */ + readonly T: Params; }; /** diff --git a/packages/core-plugin-api/src/routing/useRouteRef.test.tsx b/packages/core-plugin-api/src/routing/useRouteRef.test.tsx index b96fc5fc23..325690b6eb 100644 --- a/packages/core-plugin-api/src/routing/useRouteRef.test.tsx +++ b/packages/core-plugin-api/src/routing/useRouteRef.test.tsx @@ -18,140 +18,203 @@ import { renderHook } from '@testing-library/react'; import { PropsWithChildren } from 'react'; import { MemoryRouter, Router } from 'react-router-dom'; import { createVersionedContextForTesting } from '@backstage/version-bridge'; +import { + routeResolutionApiRef, + RouteResolutionApi, + RouteFunc, +} from '@backstage/frontend-plugin-api'; import { useRouteRef } from './useRouteRef'; import { createRouteRef } from './RouteRef'; +import { createExternalRouteRef } from './ExternalRouteRef'; import { createBrowserHistory } from 'history'; +import { TestApiProvider } from '@backstage/test-utils'; -describe('v1 consumer', () => { - const context = createVersionedContextForTesting('routing-context'); +describe('useRouteRef', () => { + describe('old app system', () => { + const context = createVersionedContextForTesting('routing-context'); - afterEach(() => { - context.reset(); - }); - - it('should resolve routes', () => { - const resolve = jest.fn(() => () => '/hello'); - context.set({ 1: { resolve } }); - - const routeRef = createRouteRef({ id: 'ref1' }); - - const renderedHook = renderHook(() => useRouteRef(routeRef), { - wrapper: ({ children }: PropsWithChildren<{}>) => ( - - ), + afterEach(() => { + context.reset(); }); - const routeFunc = renderedHook.result.current; - expect(routeFunc()).toBe('/hello'); - expect(resolve).toHaveBeenCalledWith( - routeRef, - expect.objectContaining({ - pathname: '/my-page', - }), - ); - }); + it('should resolve routes', () => { + const resolve = jest.fn(() => () => '/hello'); + context.set({ 1: { resolve } }); - it('re-resolves the routeFunc when the search parameters change', () => { - const resolve = jest.fn(() => () => '/hello'); - context.set({ 1: { resolve } }); + const routeRef = createRouteRef({ id: 'ref1' }); - const routeRef = createRouteRef({ id: 'ref1' }); - const history = createBrowserHistory(); - history.push('/my-page'); + const renderedHook = renderHook(() => useRouteRef(routeRef), { + wrapper: ({ children }: PropsWithChildren<{}>) => ( + + ), + }); - const { rerender } = renderHook(() => useRouteRef(routeRef), { - wrapper: ({ children }: PropsWithChildren<{}>) => ( - - ), + const routeFunc = renderedHook.result.current; + expect(routeFunc()).toBe('/hello'); + expect(resolve).toHaveBeenCalledWith( + routeRef, + expect.objectContaining({ + pathname: '/my-page', + }), + ); }); - expect(resolve).toHaveBeenCalledTimes(1); + it('re-resolves the routeFunc when the search parameters change', () => { + const resolve = jest.fn(() => () => '/hello'); + context.set({ 1: { resolve } }); - history.push('/my-new-page'); - rerender(); + const routeRef = createRouteRef({ id: 'ref1' }); + const history = createBrowserHistory(); + history.push('/my-page'); - expect(resolve).toHaveBeenCalledTimes(2); - }); + const { rerender } = renderHook(() => useRouteRef(routeRef), { + wrapper: ({ children }: PropsWithChildren<{}>) => ( + + ), + }); - it('does not re-resolve the routeFunc the location pathname does not change', () => { - const resolve = jest.fn(() => () => '/hello'); - context.set({ 1: { resolve } }); + expect(resolve).toHaveBeenCalledTimes(1); - const routeRef = createRouteRef({ id: 'ref1' }); - const history = createBrowserHistory(); - history.push('/my-page'); + history.push('/my-new-page'); + rerender(); - const { rerender } = renderHook(() => useRouteRef(routeRef), { - wrapper: ({ children }: PropsWithChildren<{}>) => ( - - ), + expect(resolve).toHaveBeenCalledTimes(2); }); - expect(resolve).toHaveBeenCalledTimes(1); + it('does not re-resolve the routeFunc the location pathname does not change', () => { + const resolve = jest.fn(() => () => '/hello'); + context.set({ 1: { resolve } }); - history.push('/my-page'); - rerender(); + const routeRef = createRouteRef({ id: 'ref1' }); + const history = createBrowserHistory(); + history.push('/my-page'); - expect(resolve).toHaveBeenCalledTimes(1); - }); + const { rerender } = renderHook(() => useRouteRef(routeRef), { + wrapper: ({ children }: PropsWithChildren<{}>) => ( + + ), + }); - it('does not re-resolve the routeFunc when the search parameter changes', () => { - const resolve = jest.fn(() => () => '/hello'); - context.set({ 1: { resolve } }); + expect(resolve).toHaveBeenCalledTimes(1); - const routeRef = createRouteRef({ id: 'ref1' }); - const history = createBrowserHistory(); - history.push('/my-page'); + history.push('/my-page'); + rerender(); - const { rerender } = renderHook(() => useRouteRef(routeRef), { - wrapper: ({ children }: PropsWithChildren<{}>) => ( - - ), + expect(resolve).toHaveBeenCalledTimes(1); }); - expect(resolve).toHaveBeenCalledTimes(1); + it('does not re-resolve the routeFunc when the search parameter changes', () => { + const resolve = jest.fn(() => () => '/hello'); + context.set({ 1: { resolve } }); - history.push('/my-page?foo=bar'); - rerender(); + const routeRef = createRouteRef({ id: 'ref1' }); + const history = createBrowserHistory(); + history.push('/my-page'); - expect(resolve).toHaveBeenCalledTimes(1); - }); + const { rerender } = renderHook(() => useRouteRef(routeRef), { + wrapper: ({ children }: PropsWithChildren<{}>) => ( + + ), + }); - it('does not re-resolve the routeFunc when the hash parameter changes', () => { - const resolve = jest.fn(() => () => '/hello'); - context.set({ 1: { resolve } }); + expect(resolve).toHaveBeenCalledTimes(1); - const routeRef = createRouteRef({ id: 'ref1' }); - const history = createBrowserHistory(); - history.push('/my-page'); + history.push('/my-page?foo=bar'); + rerender(); - const { rerender } = renderHook(() => useRouteRef(routeRef), { - wrapper: ({ children }: PropsWithChildren<{}>) => ( - - ), + expect(resolve).toHaveBeenCalledTimes(1); }); - expect(resolve).toHaveBeenCalledTimes(1); + it('does not re-resolve the routeFunc when the hash parameter changes', () => { + const resolve = jest.fn(() => () => '/hello'); + context.set({ 1: { resolve } }); - history.push('/my-page#foo'); - rerender(); + const routeRef = createRouteRef({ id: 'ref1' }); + const history = createBrowserHistory(); + history.push('/my-page'); - expect(resolve).toHaveBeenCalledTimes(1); + const { rerender } = renderHook(() => useRouteRef(routeRef), { + wrapper: ({ children }: PropsWithChildren<{}>) => ( + + ), + }); + + expect(resolve).toHaveBeenCalledTimes(1); + + history.push('/my-page#foo'); + rerender(); + + expect(resolve).toHaveBeenCalledTimes(1); + }); + }); + + describe('new app system', () => { + it('should resolve routes using routeResolutionApi', () => { + const routeRef = createRouteRef({ id: 'ref1' }); + const mockRouteFunc: RouteFunc = jest.fn(() => '/new-route'); + + const mockRouteResolutionApi: RouteResolutionApi = { + resolve: jest.fn(() => mockRouteFunc), + }; + + const wrapper = ({ children }: PropsWithChildren<{}>) => ( + + {children} + + ); + + const renderedHook = renderHook(() => useRouteRef(routeRef), { wrapper }); + + const routeFunc = renderedHook.result.current; + expect(routeFunc).toBe(mockRouteFunc); + expect(mockRouteResolutionApi.resolve).toHaveBeenCalledWith( + expect.anything(), + { sourcePath: '/my-page' }, + ); + expect(routeFunc()).toBe('/new-route'); + }); + + it('should handle optional external route refs', () => { + const externalRouteRef = createExternalRouteRef({ + id: 'external-ref', + optional: true, + }); + + const mockRouteResolutionApi: RouteResolutionApi = { + resolve: jest.fn(() => undefined), + }; + + const wrapper = ({ children }: PropsWithChildren<{}>) => ( + + {children} + + ); + + const renderedHook = renderHook(() => useRouteRef(externalRouteRef), { + wrapper, + }); + + expect(renderedHook.result.current).toBeUndefined(); + }); }); }); diff --git a/packages/core-plugin-api/src/routing/useRouteRef.tsx b/packages/core-plugin-api/src/routing/useRouteRef.tsx index cf292b110f..48908ada5d 100644 --- a/packages/core-plugin-api/src/routing/useRouteRef.tsx +++ b/packages/core-plugin-api/src/routing/useRouteRef.tsx @@ -17,6 +17,11 @@ import { useMemo } from 'react'; import { matchRoutes, useLocation } from 'react-router-dom'; import { useVersionedContext } from '@backstage/version-bridge'; +import { + RouteResolutionApi, + routeResolutionApiRef, + useApi, +} from '@backstage/frontend-plugin-api'; import { AnyParams, ExternalRouteRef, @@ -38,6 +43,14 @@ export interface RouteResolver { ): RouteFunc | undefined; } +function useRouteResolutionApi(): RouteResolutionApi | undefined { + try { + return useApi(routeResolutionApiRef); + } catch { + return undefined; + } +} + /** * React hook for constructing URLs to routes. * @@ -86,30 +99,52 @@ export function useRouteRef( | ExternalRouteRef, ): RouteFunc | undefined { const { pathname } = useLocation(); + const routeResolutionApi = useRouteResolutionApi(); const versionedContext = useVersionedContext<{ 1: RouteResolver }>( 'routing-context', ); - if (!versionedContext) { - throw new Error('Routing context is not available'); - } - const resolver = versionedContext.atVersion(1); - const routeFunc = useMemo( + const resolver = versionedContext?.atVersion(1); + + const newRouteFunc = useMemo(() => { + if (!routeResolutionApi) { + return null; + } + + try { + return routeResolutionApi?.resolve(routeRef, { + sourcePath: pathname, + }); + } catch { + return null; + } + }, [routeResolutionApi, routeRef, pathname]); + + const legacyRouteFunc = useMemo( () => resolver && resolver.resolve(routeRef, { pathname }), [resolver, routeRef, pathname], ); - if (!versionedContext) { - throw new Error('useRouteRef used outside of routing context'); + if (newRouteFunc !== null) { + const isOptional = 'optional' in routeRef && routeRef.optional; + if (!newRouteFunc && !isOptional) { + throw new Error(`No path for ${routeRef}`); + } + return newRouteFunc; } + + if (!versionedContext) { + throw new Error('Routing context is not available'); + } + if (!resolver) { throw new Error('RoutingContext v1 not available'); } const isOptional = 'optional' in routeRef && routeRef.optional; - if (!routeFunc && !isOptional) { + if (!legacyRouteFunc && !isOptional) { throw new Error(`No path for ${routeRef}`); } - return routeFunc; + return legacyRouteFunc; } diff --git a/packages/core-plugin-api/src/translation/index.ts b/packages/core-plugin-api/src/translation/index.ts deleted file mode 100644 index 121ec62c72..0000000000 --- a/packages/core-plugin-api/src/translation/index.ts +++ /dev/null @@ -1,32 +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. - */ - -export { - type TranslationMessages, - type TranslationMessagesOptions, - createTranslationMessages, -} from './TranslationMessages'; -export { - type TranslationResource, - type TranslationResourceOptions, - createTranslationResource, -} from './TranslationResource'; -export { - type TranslationRef, - type TranslationRefOptions, - createTranslationRef, -} from './TranslationRef'; -export { useTranslationRef } from './useTranslationRef'; diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index f86586b990..cecdf63805 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,267 @@ # @backstage/create-app +## 0.8.2 + +### Patch Changes + +- a2cb332: Bumped create-app version. +- 6c10d88: Bumped create-app version. +- e8ffac1: Various fixes for the `create-app` template: reorganizing sidebar items, removing redundant config and code, and adding a documentation example. +- 72a493a: Added the `mcp-actions-backend` and `plugin-auth` plugins +- 4cf9f3a: Removed the unnecessary `@backstage/cli-module-new` dependency from the `next-app` template, since it is already included through `@backstage/cli-defaults`. +- 2541837: Renamed the built-in template directories from `next-app` to `default-app` and `default-app` to `legacy-app`. +- Updated dependencies + - @backstage/cli-common@0.2.1 + +## 0.8.2-next.2 + +### Patch Changes + +- Bumped create-app version. +- Updated dependencies + - @backstage/cli-common@0.2.1-next.1 + +## 0.8.2-next.1 + +### Patch Changes + +- Bumped create-app version. + +## 0.8.2-next.0 + +### Patch Changes + +- e8ffac1: Various fixes for the `create-app` template: reorganizing sidebar items, removing redundant config and code, and adding a documentation example. +- 4cf9f3a: Removed the unnecessary `@backstage/cli-module-new` dependency from the `next-app` template, since it is already included through `@backstage/cli-defaults`. +- Updated dependencies + - @backstage/cli-common@0.2.1-next.0 + +## 0.8.0 + +### Minor Changes + +- a6735c3: **BREAKING**: The new frontend system is now the default template when creating a new Backstage app. The previous `--next` flag has been replaced with a `--legacy` flag that can be used to create an app using the old frontend system instead. + +### Patch Changes + +- d806b0c: The create-app templates now include `@backstage/cli-defaults` as a `devDependency`, enabling the CLI's automatic module discovery for newly created projects. +- d14b6e0: **BREAKING**: Migrated `MembersListCard`, `OwnershipCard`, and `CatalogGraphCard` to use BUI card primitives via `EntityInfoCard`. + + - `OwnershipCard`: Removed `variant` and `maxScrollHeight` props. Card height and scrolling are now controlled by the parent container — the card fills its container and the body scrolls automatically when content overflows. + - `CatalogGraphCard`: Removed `variant` prop. + - `MembersListCard`: Translation keys `subtitle`, `paginationLabel`, `aggregateMembersToggle.directMembers`, `aggregateMembersToggle.aggregatedMembers`, and `aggregateMembersToggle.ariaLabel` have been removed. The `title` key now includes `{{groupName}}`. New keys added: `cardLabel`, `noSearchResult`, `aggregateMembersToggle.label`. + - `OwnershipCard`: Translation keys `aggregateRelationsToggle.directRelations`, `aggregateRelationsToggle.aggregatedRelations`, and `aggregateRelationsToggle.ariaLabel` have been removed. New key added: `aggregateRelationsToggle.label`. + - Removed `MemberComponentClassKey` export, and `root` and `cardContent` from `MembersListCardClassKey`, `card` from `OwnershipCardClassKey`, and `card` from `CatalogGraphCardClassKey`. + + **Migration:** + + ```diff + - + + + ``` + + ```diff + - + + + ``` + +- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`. +- ea90ab0: Updated the `next-app` template to reference `@backstage/cli-module-new/templates/*` instead of `@backstage/cli/templates/*` for the built-in `yarn new` templates. +- de62a9d: Upgraded `commander` dependency from `^12.0.0` to `^14.0.3` across all CLI packages. +- a9d23c4: Properly support `package.json` `workspaces` field +- ebd4630: Replace deprecated `workspaces.packages` with `workspaces` in `package.json` + + This change is **not** required, but you can edit your main `package.json`, to fit the more modern & more common pattern: + + ```diff + - "workspaces": { + - "packages": [ + "workspaces": [ + "packages/*", + "plugins/*" + - ] + - }, + ], + ``` + +- Updated dependencies + - @backstage/cli-common@0.2.0 + +## 0.7.10-next.2 + +### Patch Changes + +- d14b6e0: **BREAKING**: Migrated `MembersListCard`, `OwnershipCard`, and `CatalogGraphCard` to use BUI card primitives via `EntityInfoCard`. + + - `OwnershipCard`: Removed `variant` and `maxScrollHeight` props. Card height and scrolling are now controlled by the parent container — the card fills its container and the body scrolls automatically when content overflows. + - `CatalogGraphCard`: Removed `variant` prop. + - `MembersListCard`: Translation keys `subtitle`, `paginationLabel`, `aggregateMembersToggle.directMembers`, `aggregateMembersToggle.aggregatedMembers`, and `aggregateMembersToggle.ariaLabel` have been removed. The `title` key now includes `{{groupName}}`. New keys added: `cardLabel`, `noSearchResult`, `aggregateMembersToggle.label`. + - `OwnershipCard`: Translation keys `aggregateRelationsToggle.directRelations`, `aggregateRelationsToggle.aggregatedRelations`, and `aggregateRelationsToggle.ariaLabel` have been removed. New key added: `aggregateRelationsToggle.label`. + - Removed `MemberComponentClassKey` export, and `root` and `cardContent` from `MembersListCardClassKey`, `card` from `OwnershipCardClassKey`, and `card` from `CatalogGraphCardClassKey`. + + **Migration:** + + ```diff + - + + + ``` + + ```diff + - + + + ``` + +- Updated dependencies + - @backstage/cli-common@0.2.0-next.2 + +## 0.7.10-next.1 + +### Patch Changes + +- a9d23c4: Properly support `package.json` `workspaces` field +- ebd4630: Replace deprecated `workspaces.packages` with `workspaces` in `package.json` + + This change is **not** required, but you can edit your main `package.json`, to fit the more modern & more common pattern: + + ```diff + - "workspaces": { + - "packages": [ + "workspaces": [ + "packages/*", + "plugins/*" + - ] + - }, + ], + ``` + +- Updated dependencies + - @backstage/cli-common@0.2.0-next.1 + +## 0.7.10-next.0 + +### Patch Changes + +- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`. +- de62a9d: Upgraded `commander` dependency from `^12.0.0` to `^14.0.3` across all CLI packages. +- Updated dependencies + - @backstage/cli-common@0.2.0-next.0 + +## 0.7.9 + +### Patch Changes + +- 40f2720: Updated to include the missing core plugins in the template used with the `--next` flag. Also updated `react-router*` versions and added Jest 30-related dependencies. Finally, moved the order of `@playwright/test` so it won't trigger a file change during the creation process. +- 1ea737c: Bumped create-app version. +- 7c41134: Bumped create-app version. +- 65ba820: Updated the app template sidebar to use the new `NavContentBlueprint` API for page-based navigation. +- 7455dae: Use node prefix on native imports +- c38b74d: Switched `next-app` template to use blueprint from `@backstage/plugin-app-react`. +- Updated dependencies + - @backstage/cli-common@0.1.18 + +## 0.7.9-next.2 + +### Patch Changes + +- Bumped create-app version. + +## 0.7.9-next.1 + +### Patch Changes + +- Bumped create-app version. + +## 0.7.9-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- c38b74d: Switched `next-app` template to use blueprint from `@backstage/plugin-app-react`. +- Updated dependencies + - @backstage/cli-common@0.1.18-next.0 + +## 0.7.8 + +### Patch Changes + +- fea3e39: Bumped create-app version. +- 9f1ee3e: Bumped create-app version. +- 880310b: Bumped create-app version. +- f1fe6fe: Updated Dockerfile to use Node 24 and Debian Trixie +- Updated dependencies + - @backstage/cli-common@0.1.17 + +## 0.7.8-next.3 + +### Patch Changes + +- Bumped create-app version. + +## 0.7.8-next.2 + +### Patch Changes + +- f1fe6fe: Updated Dockerfile to use Node 24 and Debian Trixie + +## 0.7.8-next.1 + +### Patch Changes + +- Bumped create-app version. + +## 0.7.8-next.0 + +### Patch Changes + +- Bumped create-app version. +- Updated dependencies + - @backstage/cli-common@0.1.16 + +## 0.7.7 + +### Patch Changes + +- 336db00: Bumped create-app version. +- 2bae83a: Updated engines to support Node 22 or 24 +- c8c2329: Add proxy configuration from env-vars to create-app tasks +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/cli-common@0.1.16 + +## 0.7.7-next.2 + +### Patch Changes + +- 2bae83a: Updated engines to support Node 22 or 24 +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/cli-common@0.1.16-next.2 + +## 0.7.7-next.1 + +### Patch Changes + +- Bumped create-app version. +- Updated dependencies + - @backstage/cli-common@0.1.16-next.1 + +## 0.7.7-next.0 + +### Patch Changes + +- c8c2329: Add proxy configuration from env-vars to create-app tasks +- Updated dependencies + - @backstage/cli-common@0.1.16-next.0 + +## 0.7.6 + +### Patch Changes + +- 2c1fe37: Bumped create-app version. +- 20fae88: Bumped create-app version. +- 9f939a6: Added `@backstage/plugin-app-visualizer` to the app in the `--next` template. +- fc7cbfc: Added the new `@backstage/cli/templates/catalog-provider-module` template to the explicit template configuration for the `next-app` template. + ## 0.7.6-next.3 ### Patch Changes @@ -6775,7 +7037,7 @@ The `.fromConfig` of the `DefaultCatalogCollator` also now takes a `tokenManager To apply this change to an existing plugin, it is important that all plugins that you are using have already been migrated. In this case the most crucial piece is that no entity page cards of contents may require the `entity` prop, and they must instead consume the entity from context using `useEntity`. - Since this change is large with a lot of repeated changes, we'll describe a couple of common cases rather than the entire change. If your entity pages are unchanged from the `create-app` template, you can also just bring in the latest version directly from the [template itself](https://github.com/backstage/backstage/blob/master/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx). + Since this change is large with a lot of repeated changes, we'll describe a couple of common cases rather than the entire change. If your entity pages are unchanged from the `create-app` template, you can also just bring in the latest version directly from the [template itself](https://github.com/backstage/backstage/blob/master/packages/create-app/templates/legacy-app/packages/app/src/components/catalog/EntityPage.tsx). The first step of the change is to change the `packages/app/src/components/catalog/EntityPage.tsx` export to `entityPage` rather than `EntityPage`. This will require an update to `App.tsx`, which is the only change we need to do outside of `EntityPage.tsx`: diff --git a/packages/create-app/bin/backstage-create-app b/packages/create-app/bin/backstage-create-app index bacee3e835..fe8fc68188 100755 --- a/packages/create-app/bin/backstage-create-app +++ b/packages/create-app/bin/backstage-create-app @@ -16,10 +16,12 @@ */ /* eslint-disable no-restricted-syntax */ -const path = require('path'); +const path = require('node:path'); // Figure out whether we're running inside the backstage repo or as an installed dependency -const isLocal = require('fs').existsSync(path.resolve(__dirname, '../src')); +const isLocal = require('node:fs').existsSync( + path.resolve(__dirname, '../src'), +); if (!isLocal || process.env.BACKSTAGE_E2E_CLI_TEST) { require('..'); diff --git a/packages/create-app/cli-report.md b/packages/create-app/cli-report.md index 88e19d05c6..f9286a628c 100644 --- a/packages/create-app/cli-report.md +++ b/packages/create-app/cli-report.md @@ -8,7 +8,7 @@ Usage: backstage-create-app [options] Options: - --next + --legacy --path [directory] --skip-install --template-path [directory] diff --git a/packages/create-app/knip-report.md b/packages/create-app/knip-report.md index dc4b092da6..3ceefe49d7 100644 --- a/packages/create-app/knip-report.md +++ b/packages/create-app/knip-report.md @@ -2,7 +2,7 @@ ## Unused devDependencies (1) -| Name | Location | Severity | -| :-------------------- | :----------- | :------- | -| @types/command-exists | packages/create-app/package.json | error | +| Name | Location | Severity | +| :-------------------- | :---------------- | :------- | +| @types/command-exists | package.json:58:6 | error | diff --git a/packages/create-app/package.json b/packages/create-app/package.json index d0b5850d58..1ebe2543ac 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/create-app", - "version": "0.7.6-next.3", + "version": "0.8.2", "description": "A CLI that helps you create your own Backstage app", "backstage": { "role": "cli" @@ -45,7 +45,7 @@ "dependencies": { "@backstage/cli-common": "workspace:^", "chalk": "^4.0.0", - "commander": "^12.0.0", + "commander": "^14.0.3", "fs-extra": "^11.2.0", "handlebars": "^4.7.3", "inquirer": "^8.2.0", @@ -58,7 +58,7 @@ "@types/command-exists": "^1.2.0", "@types/fs-extra": "^11.0.0", "@types/inquirer": "^8.1.3", - "@types/node": "^20.16.0", + "@types/node": "^22.13.14", "@types/recursive-readdir": "^2.2.0", "msw": "^2.0.0", "nodemon": "^3.0.1" diff --git a/packages/create-app/scripts/add-lock-seed.js b/packages/create-app/scripts/add-lock-seed.js index faa01fca9b..cddf59d8d1 100644 --- a/packages/create-app/scripts/add-lock-seed.js +++ b/packages/create-app/scripts/add-lock-seed.js @@ -16,7 +16,7 @@ */ const fs = require('fs-extra'); -const path = require('path'); +const path = require('node:path'); const YARN_REGISTRY = 'https://registry.yarnpkg.com'; const NPM_REGISTRY = 'https://registry.npmjs.org'; diff --git a/packages/create-app/scripts/postpack.js b/packages/create-app/scripts/postpack.js index f13db11e76..ffed449a22 100644 --- a/packages/create-app/scripts/postpack.js +++ b/packages/create-app/scripts/postpack.js @@ -18,7 +18,7 @@ /* eslint-disable no-restricted-syntax */ const fs = require('fs-extra'); -const path = require('path'); +const path = require('node:path'); async function main() { const pkgPath = path.resolve(__dirname, '../package.json'); diff --git a/packages/create-app/scripts/prepack.js b/packages/create-app/scripts/prepack.js index 2caf865280..c87fac8450 100644 --- a/packages/create-app/scripts/prepack.js +++ b/packages/create-app/scripts/prepack.js @@ -18,7 +18,7 @@ /* eslint-disable no-restricted-syntax */ const fs = require('fs-extra'); -const path = require('path'); +const path = require('node:path'); async function main() { const pkgPath = path.resolve(__dirname, '../package.json'); diff --git a/packages/create-app/seed-yarn.lock b/packages/create-app/seed-yarn.lock index 9d4d5449d1..b6ba82ab17 100644 --- a/packages/create-app/seed-yarn.lock +++ b/packages/create-app/seed-yarn.lock @@ -22,3 +22,18 @@ version "1.11.31" resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.11.31.tgz#e5de9ed005551ce9a16aa69e79935fc33065475c" integrity sha512-mAby9aUnKRjMEA7v8cVZS9Ah4duoRBnX7X6r5qrhTxErx+68MoY1TPrVwj/66/SWN3Bl+jijqAqoB8Qx0QE34A== + +fast-xml-builder@*: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fast-xml-builder/-/fast-xml-builder-1.0.0.tgz#a485d7e8381f1db983cf006f849d1066e2935241" + integrity sha512-fpZuDogrAgnyt9oDDz+5DBz0zgPdPZz6D4IR7iESxRXElrlGTRkHJ9eEt+SACRJwT0FNFrt71DFQIUFBJfX/uQ== + +"knex@npm:3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/knex/-/knex-3.1.0.tgz#b6ddd5b5ad26a6315234a5b09ec38dc4a370bd8c" + integrity sha512-GLoII6hR0c4ti243gMs5/1Rb3B+AjwMOfjYm97pu0FOQa7JH56hgBxYf5WK2525ceSbBY1cjeZ9yk99GPMB6Kw== + +"knex@npm:^3.0.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/knex/-/knex-3.1.0.tgz#b6ddd5b5ad26a6315234a5b09ec38dc4a370bd8c" + integrity sha512-GLoII6hR0c4ti243gMs5/1Rb3B+AjwMOfjYm97pu0FOQa7JH56hgBxYf5WK2525ceSbBY1cjeZ9yk99GPMB6Kw== diff --git a/packages/create-app/src/createApp.test.ts b/packages/create-app/src/createApp.test.ts index b689410cac..06a0e7df05 100644 --- a/packages/create-app/src/createApp.test.ts +++ b/packages/create-app/src/createApp.test.ts @@ -15,16 +15,21 @@ */ import inquirer from 'inquirer'; -import path from 'path'; +import path from 'node:path'; import { Command } from 'commander'; import * as tasks from './lib/tasks'; import createApp from './createApp'; -import { findPaths } from '@backstage/cli-common'; -import { tmpdir } from 'os'; +import { findOwnPaths, targetPaths } from '@backstage/cli-common'; +import { tmpdir } from 'node:os'; import { createMockDirectory } from '@backstage/backend-test-utils'; +import { overrideTargetPaths } from '@backstage/cli-common/testUtils'; jest.mock('./lib/tasks'); +const MOCK_TARGET_DIR = '/mock/target-dir'; +const MOCK_TARGET_ROOT = '/mock/target-root'; +overrideTargetPaths({ dir: MOCK_TARGET_DIR, rootDir: MOCK_TARGET_ROOT }); + // By mocking this the filesystem mocks won't mess with reading all of the package.jsons jest.mock('./lib/versions', () => ({ packageVersions: { root: '1.0.0' }, @@ -64,12 +69,7 @@ describe('command entrypoint', () => { expect(tryInitGitRepositoryMock).toHaveBeenCalled(); expect(templatingMock).toHaveBeenCalled(); expect(templatingMock.mock.lastCall?.[0]).toEqual( - findPaths(__dirname).resolveTarget( - 'packages', - 'create-app', - 'templates', - 'default-app', - ), + findOwnPaths(__dirname).resolve('templates/default-app'), ); expect(templatingMock.mock.lastCall?.[1]).toContain( path.join(tmpdir(), 'MyApp'), @@ -85,30 +85,20 @@ describe('command entrypoint', () => { expect(tryInitGitRepositoryMock).toHaveBeenCalled(); expect(templatingMock).toHaveBeenCalled(); expect(templatingMock.mock.lastCall?.[0]).toEqual( - findPaths(__dirname).resolveTarget( - 'packages', - 'create-app', - 'templates', - 'default-app', - ), + findOwnPaths(__dirname).resolve('templates/default-app'), ); expect(templatingMock.mock.lastCall?.[1]).toEqual('myDirectory'); expect(buildAppMock).toHaveBeenCalled(); }); - it('should call expected tasks when `--next` is supplied', async () => { - const cmd = { next: true } as unknown as Command; + it('should call expected tasks when `--legacy` is supplied', async () => { + const cmd = { legacy: true } as unknown as Command; await createApp(cmd); expect(checkAppExistsMock).toHaveBeenCalled(); expect(tryInitGitRepositoryMock).toHaveBeenCalled(); expect(templatingMock).toHaveBeenCalled(); expect(templatingMock.mock.lastCall?.[0]).toEqual( - findPaths(__dirname).resolveTarget( - 'packages', - 'create-app', - 'templates', - 'next-app', - ), + findOwnPaths(__dirname).resolve('templates/legacy-app'), ); expect(templatingMock.mock.lastCall?.[1]).toContain( path.join(tmpdir(), 'MyApp'), @@ -127,7 +117,7 @@ describe('command entrypoint', () => { expect(tryInitGitRepositoryMock).toHaveBeenCalled(); expect(templatingMock).toHaveBeenCalled(); expect(templatingMock.mock.lastCall?.[0]).toEqual( - findPaths(__dirname).resolveTarget('templateDirectory'), + targetPaths.resolve('templateDirectory'), ); expect(templatingMock.mock.lastCall?.[1]).toEqual('myDirectory'); expect(buildAppMock).toHaveBeenCalled(); diff --git a/packages/create-app/src/createApp.ts b/packages/create-app/src/createApp.ts index b96c93b8f2..21a7cf272c 100644 --- a/packages/create-app/src/createApp.ts +++ b/packages/create-app/src/createApp.ts @@ -17,9 +17,9 @@ import chalk from 'chalk'; import { OptionValues } from 'commander'; import inquirer, { Answers } from 'inquirer'; -import { resolve as resolvePath } from 'path'; -import { findPaths } from '@backstage/cli-common'; -import os from 'os'; +import { resolve as resolvePath } from 'node:path'; +import { targetPaths, findOwnPaths } from '@backstage/cli-common'; +import os from 'node:os'; import fs from 'fs-extra'; import { Task, @@ -36,8 +36,6 @@ import { const DEFAULT_BRANCH = 'master'; export default async (opts: OptionValues): Promise => { - /* eslint-disable-next-line no-restricted-syntax */ - const paths = findPaths(__dirname); const answers: Answers = await inquirer.prompt([ { type: 'input', @@ -65,21 +63,23 @@ export default async (opts: OptionValues): Promise => { }, ]); - // Pick the built-in template based on the --next flag - const builtInTemplate = opts.next - ? paths.resolveOwn('templates/next-app') - : paths.resolveOwn('templates/default-app'); + // Pick the built-in template based on the --legacy flag + /* eslint-disable-next-line no-restricted-syntax */ + const ownPaths = findOwnPaths(__dirname); + const builtInTemplate = opts.legacy + ? ownPaths.resolve('templates/legacy-app') + : ownPaths.resolve('templates/default-app'); // Use `--template-path` argument as template when specified. Otherwise, use the default template. const templateDir = opts.templatePath - ? paths.resolveTarget(opts.templatePath) + ? targetPaths.resolve(opts.templatePath) : builtInTemplate; // Use `--path` argument as application directory when specified, otherwise // create a directory using `answers.name` const appDir = opts.path - ? resolvePath(paths.targetDir, opts.path) - : resolvePath(paths.targetDir, answers.name); + ? resolvePath(targetPaths.dir, opts.path) + : resolvePath(targetPaths.dir, answers.name); Task.log(); Task.log('Creating the app...'); @@ -102,7 +102,7 @@ export default async (opts: OptionValues): Promise => { // Template to temporary location, and then move files Task.section('Checking if the directory is available'); - await checkAppExistsTask(paths.targetDir, answers.name); + await checkAppExistsTask(targetPaths.dir, answers.name); Task.section('Creating a temporary app directory'); const tempDir = await fs.mkdtemp(resolvePath(os.tmpdir(), answers.name)); diff --git a/packages/create-app/src/index.ts b/packages/create-app/src/index.ts index cfa448aa1a..ff3ef29b18 100644 --- a/packages/create-app/src/index.ts +++ b/packages/create-app/src/index.ts @@ -31,7 +31,7 @@ const main = (argv: string[]) => { .name('backstage-create-app') .version(version) .description('Creates a new app in a new directory or specified path') - .option('--next', 'Use the next generation of the app template') + .option('--legacy', 'Use the legacy version of the app template') .option( '--path [directory]', 'Location to store the app defaulting to a new folder with the app name', diff --git a/packages/create-app/src/lib/errors.ts b/packages/create-app/src/lib/errors.ts index 2f67b94ae1..8e5d2b1423 100644 --- a/packages/create-app/src/lib/errors.ts +++ b/packages/create-app/src/lib/errors.ts @@ -14,27 +14,9 @@ * limitations under the License. */ +import { ExitCodeError } from '@backstage/cli-common'; import chalk from 'chalk'; -export class CustomError extends Error { - get name(): string { - return this.constructor.name; - } -} - -export class ExitCodeError extends CustomError { - readonly code: number; - - constructor(code: number, command?: string) { - if (command) { - super(`Command '${command}' exited with code ${code}`); - } else { - super(`Child exited with code ${code}`); - } - this.code = code; - } -} - export function exitWithError(error: Error): never { if (error instanceof ExitCodeError) { process.stderr.write(`\n${chalk.red(error.message)}\n\n`); diff --git a/packages/create-app/src/lib/tasks.test.ts b/packages/create-app/src/lib/tasks.test.ts index 86a7008b4d..c16a818187 100644 --- a/packages/create-app/src/lib/tasks.test.ts +++ b/packages/create-app/src/lib/tasks.test.ts @@ -15,9 +15,9 @@ */ import fs from 'fs-extra'; -import child_process from 'child_process'; -import { resolve as resolvePath } from 'path'; -import os from 'os'; +import child_process from 'node:child_process'; +import { resolve as resolvePath } from 'node:path'; +import os from 'node:os'; import { Task, buildAppTask, @@ -49,63 +49,72 @@ jest.mock('child_process'); jest.mock('./versions', () => ({ packageVersions: { root: '1.2.3', - '@backstage/cli': '1.0.0', + '@backstage/app-defaults': '1.0.0', '@backstage/backend-defaults': '1.0.0', '@backstage/backend-tasks': '1.0.0', - '@backstage/catalog-model': '1.0.0', '@backstage/catalog-client': '1.0.0', + '@backstage/catalog-model': '1.0.0', + '@backstage/cli': '1.0.0', + '@backstage/cli-defaults': '1.0.0', '@backstage/config': '1.0.0', - '@backstage/plugin-app-backend': '1.0.0', - '@backstage/plugin-auth-backend': '1.0.0', - '@backstage/plugin-auth-node': '1.0.0', - '@backstage/plugin-auth-backend-module-github-provider': '1.0.0', - '@backstage/plugin-auth-backend-module-guest-provider': '1.0.0', - '@backstage/plugin-catalog-backend': '1.0.0', - '@backstage/plugin-catalog-backend-module-logs': '1.0.0', - '@backstage/plugin-catalog-backend-module-scaffolder-entity-model': '1.0.0', - '@backstage/plugin-permission-common': '1.0.0', - '@backstage/plugin-permission-node': '1.0.0', - '@backstage/plugin-permission-backend': '1.0.0', - '@backstage/plugin-permission-backend-module-allow-all-policy': '1.0.0', - '@backstage/plugin-proxy-backend': '1.0.0', - '@backstage/plugin-scaffolder-backend': '1.0.0', - '@backstage/plugin-search-backend': '1.0.0', - '@backstage/plugin-search-backend-module-catalog': '1.0.0', - '@backstage/plugin-search-backend-module-pg': '1.0.0', - '@backstage/plugin-search-backend-module-techdocs': '1.0.0', - '@backstage/plugin-search-backend-node': '1.0.0', - '@backstage/plugin-techdocs-backend': '1.0.0', - '@backstage/app-defaults': '1.0.0', '@backstage/core-app-api': '1.0.0', + '@backstage/core-compat-api': '1.0.0', '@backstage/core-components': '1.0.0', '@backstage/core-plugin-api': '1.0.0', '@backstage/e2e-test-utils': '1.0.0', + '@backstage/frontend-defaults': '1.0.0', + '@backstage/frontend-plugin-api': '1.0.0', + '@backstage/frontend-test-utils': '1.0.0', '@backstage/integration-react': '1.0.0', '@backstage/plugin-api-docs': '1.0.0', + '@backstage/plugin-app-backend': '1.0.0', + '@backstage/plugin-app-react': '1.0.0', + '@backstage/plugin-app-visualizer': '1.0.0', + '@backstage/plugin-auth': '1.0.0', + '@backstage/plugin-auth-backend': '1.0.0', + '@backstage/plugin-auth-backend-module-github-provider': '1.0.0', + '@backstage/plugin-auth-backend-module-guest-provider': '1.0.0', + '@backstage/plugin-auth-node': '1.0.0', '@backstage/plugin-catalog': '1.0.0', + '@backstage/plugin-catalog-backend': '1.0.0', + '@backstage/plugin-catalog-backend-module-logs': '1.0.0', + '@backstage/plugin-catalog-backend-module-scaffolder-entity-model': '1.0.0', '@backstage/plugin-catalog-common': '1.0.0', '@backstage/plugin-catalog-graph': '1.0.0', '@backstage/plugin-catalog-import': '1.0.0', '@backstage/plugin-catalog-react': '1.0.0', '@backstage/plugin-kubernetes': '1.0.0', '@backstage/plugin-kubernetes-backend': '1.0.0', + '@backstage/plugin-mcp-actions-backend': '1.0.0', '@backstage/plugin-notifications': '1.0.0', '@backstage/plugin-notifications-backend': '1.0.0', '@backstage/plugin-org': '1.0.0', + '@backstage/plugin-permission-backend': '1.0.0', + '@backstage/plugin-permission-backend-module-allow-all-policy': '1.0.0', + '@backstage/plugin-permission-common': '1.0.0', + '@backstage/plugin-permission-node': '1.0.0', + '@backstage/plugin-permission-react': '1.0.0', + '@backstage/plugin-proxy-backend': '1.0.0', '@backstage/plugin-scaffolder': '1.0.0', + '@backstage/plugin-scaffolder-backend': '1.0.0', '@backstage/plugin-scaffolder-backend-module-github': '1.0.0', '@backstage/plugin-scaffolder-backend-module-notifications': '1.0.0', - '@backstage/plugin-permission-react': '1.0.0', '@backstage/plugin-search': '1.0.0', + '@backstage/plugin-search-backend': '1.0.0', + '@backstage/plugin-search-backend-module-catalog': '1.0.0', + '@backstage/plugin-search-backend-module-pg': '1.0.0', + '@backstage/plugin-search-backend-module-techdocs': '1.0.0', + '@backstage/plugin-search-backend-node': '1.0.0', '@backstage/plugin-search-react': '1.0.0', '@backstage/plugin-signals': '1.0.0', '@backstage/plugin-signals-backend': '1.0.0', '@backstage/plugin-techdocs': '1.0.0', - '@backstage/plugin-techdocs-react': '1.0.0', + '@backstage/plugin-techdocs-backend': '1.0.0', '@backstage/plugin-techdocs-module-addons-contrib': '1.0.0', + '@backstage/plugin-techdocs-react': '1.0.0', '@backstage/plugin-user-settings': '1.0.0', - '@backstage/theme': '1.0.0', '@backstage/test-utils': '1.0.0', + '@backstage/theme': '1.0.0', '@backstage/ui': '1.0.0', }, })); diff --git a/packages/create-app/src/lib/tasks.ts b/packages/create-app/src/lib/tasks.ts index 230590e99c..7819711699 100644 --- a/packages/create-app/src/lib/tasks.ts +++ b/packages/create-app/src/lib/tasks.ts @@ -13,6 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { bootstrapEnvProxyAgents } from '@backstage/cli-common'; + +bootstrapEnvProxyAgents(); import chalk from 'chalk'; import fs from 'fs-extra'; @@ -24,11 +27,11 @@ import { dirname, resolve as resolvePath, relative as relativePath, -} from 'path'; -import { exec as execCb } from 'child_process'; +} from 'node:path'; +import { exec as execCb } from 'node:child_process'; import { packageVersions } from './versions'; -import { promisify } from 'util'; -import os from 'os'; +import { promisify } from 'node:util'; +import os from 'node:os'; const TASK_NAME_MAX_LENGTH = 14; const TEN_MINUTES_MS = 1000 * 60 * 10; diff --git a/packages/create-app/src/lib/versions.ts b/packages/create-app/src/lib/versions.ts index 51fa8dff84..be60a95526 100644 --- a/packages/create-app/src/lib/versions.ts +++ b/packages/create-app/src/lib/versions.ts @@ -38,6 +38,7 @@ import { version as backendDefaults } from '../../../backend-defaults/package.js import { version as catalogClient } from '../../../catalog-client/package.json'; import { version as catalogModel } from '../../../catalog-model/package.json'; import { version as cli } from '../../../cli/package.json'; +import { version as cliDefaults } from '../../../cli-defaults/package.json'; import { version as config } from '../../../config/package.json'; import { version as coreAppApi } from '../../../core-app-api/package.json'; import { version as coreCompatApi } from '../../../core-compat-api/package.json'; @@ -55,6 +56,8 @@ import { version as ui } from '../../../ui/package.json'; import { version as pluginApiDocs } from '../../../../plugins/api-docs/package.json'; import { version as pluginAppVisualizer } from '../../../../plugins/app-visualizer/package.json'; import { version as pluginAppBackend } from '../../../../plugins/app-backend/package.json'; +import { version as pluginAppReact } from '../../../../plugins/app-react/package.json'; +import { version as pluginAuth } from '../../../../plugins/auth/package.json'; import { version as pluginAuthBackend } from '../../../../plugins/auth-backend/package.json'; import { version as pluginAuthBackendModuleGithubProvider } from '../../../../plugins/auth-backend-module-github-provider/package.json'; import { version as pluginAuthBackendModuleGuestProvider } from '../../../../plugins/auth-backend-module-guest-provider/package.json'; @@ -69,6 +72,7 @@ import { version as pluginCatalogGraph } from '../../../../plugins/catalog-graph import { version as pluginCatalogImport } from '../../../../plugins/catalog-import/package.json'; import { version as pluginKubernetes } from '../../../../plugins/kubernetes/package.json'; import { version as pluginKubernetesBackend } from '../../../../plugins/kubernetes-backend/package.json'; +import { version as pluginMcpActionsBackend } from '../../../../plugins/mcp-actions-backend/package.json'; import { version as pluginNotifications } from '../../../../plugins/notifications/package.json'; import { version as pluginNotificationsBackend } from '../../../../plugins/notifications-backend/package.json'; import { version as pluginOrg } from '../../../../plugins/org/package.json'; @@ -106,6 +110,7 @@ export const packageVersions = { '@backstage/catalog-client': catalogClient, '@backstage/catalog-model': catalogModel, '@backstage/cli': cli, + '@backstage/cli-defaults': cliDefaults, '@backstage/config': config, '@backstage/core-app-api': coreAppApi, '@backstage/core-compat-api': coreCompatApi, @@ -118,7 +123,9 @@ export const packageVersions = { '@backstage/repo-tools': repoTools, '@backstage/plugin-api-docs': pluginApiDocs, '@backstage/plugin-app-backend': pluginAppBackend, + '@backstage/plugin-app-react': pluginAppReact, '@backstage/plugin-app-visualizer': pluginAppVisualizer, + '@backstage/plugin-auth': pluginAuth, '@backstage/plugin-auth-backend': pluginAuthBackend, '@backstage/plugin-auth-backend-module-github-provider': pluginAuthBackendModuleGithubProvider, @@ -137,6 +144,7 @@ export const packageVersions = { '@backstage/plugin-catalog-import': pluginCatalogImport, '@backstage/plugin-kubernetes': pluginKubernetes, '@backstage/plugin-kubernetes-backend': pluginKubernetesBackend, + '@backstage/plugin-mcp-actions-backend': pluginMcpActionsBackend, '@backstage/plugin-notifications': pluginNotifications, '@backstage/plugin-notifications-backend': pluginNotificationsBackend, '@backstage/plugin-org': pluginOrg, diff --git a/packages/create-app/templates/default-app/app-config.yaml.hbs b/packages/create-app/templates/default-app/app-config.yaml.hbs index ca52ec530c..58a48a3e95 100644 --- a/packages/create-app/templates/default-app/app-config.yaml.hbs +++ b/packages/create-app/templates/default-app/app-config.yaml.hbs @@ -2,6 +2,21 @@ app: title: Scaffolded Backstage App baseUrl: http://localhost:3000 + # Enable all packages by default, this will discover packages from packages/app/package.json + packages: all + + extensions: + # Disable the nav items that we're manually rendering in packages/app/src/modules/nav/Sidebar.tsx + - nav-item:search: false + - nav-item:user-settings: false + - nav-item:catalog: false + - nav-item:scaffolder: false + + # Configure the catalog index page to be the root page, this is normally mounted on /catalog + - page:catalog: + config: + path: / + organization: name: My Company @@ -30,6 +45,12 @@ backend: database: client: better-sqlite3 connection: ':memory:' + # see https://backstage.io/docs/ai/mcp-actions#actions-configuration for more details + actions: + pluginSources: + - auth + - catalog + - scaffolder # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir integrations: @@ -67,6 +88,18 @@ auth: providers: # See https://backstage.io/docs/auth/guest/provider guest: {} + # see https://backstage.io/docs/ai/mcp-actions#client-id-metadata-documents + # to learn more about client id metadata documents + experimentalClientIdMetadataDocuments: + enabled: false + # Optional: restrict which `client_id` URLs are allowed (defaults to ['*']) + # allowedClientIdPatterns: + # - 'https://example.com/*' + # - 'https://*.trusted-domain.com/*' + # Optional: restrict which redirect URIs are allowed (defaults to ['*']) + # allowedRedirectUriPatterns: + # - 'http://localhost:*' + # - 'https://*.example.com/*' scaffolder: # see https://backstage.io/docs/features/software-templates/configuration for software template options @@ -111,3 +144,8 @@ kubernetes: permission: # setting this to `false` will disable permissions enabled: true + +# see https://backstage.io/docs/ai/mcp-actions for more details +mcpActions: + name: 'My Company Backstage' # defaults to "backstage" + description: 'Tools for managing your software catalog, creating new services from templates, and exploring your developer portal' # optional diff --git a/packages/create-app/templates/default-app/package.json.hbs b/packages/create-app/templates/default-app/package.json.hbs index 8399c6505a..6e2d899860 100644 --- a/packages/create-app/templates/default-app/package.json.hbs +++ b/packages/create-app/templates/default-app/package.json.hbs @@ -3,7 +3,7 @@ "version": "1.0.0", "private": true, "engines": { - "node": "20 || 22" + "node": "22 || 24" }, "scripts": { "start": "backstage-cli repo start", @@ -22,16 +22,28 @@ "prettier:check": "prettier --check .", "new": "backstage-cli new" }, - "workspaces": { - "packages": [ - "packages/*", - "plugins/*" - ] + "backstage": { + "cli": { + "new": { + "globals": { + "license": "UNLICENSED" + } + } + } }, + "workspaces": [ + "packages/*", + "plugins/*" + ], "devDependencies": { "@backstage/cli": "^{{version '@backstage/cli'}}", + "@backstage/cli-defaults": "^{{version '@backstage/cli-defaults'}}", "@backstage/e2e-test-utils": "^{{version '@backstage/e2e-test-utils'}}", + "@jest/environment-jsdom-abstract": "^30.0.0", "@playwright/test": "^1.32.3", + "@types/jest": "^30.0.0", + "jest": "^30.2.0", + "jsdom": "^27.1.0", "node-gyp": "^10.0.0", "prettier": "^2.3.2", "typescript": "~5.8.0" diff --git a/packages/create-app/templates/default-app/packages/app/e2e-tests/app.test.ts b/packages/create-app/templates/default-app/packages/app/e2e-tests/app.test.ts index 839ff883de..d8dbc1faa8 100644 --- a/packages/create-app/templates/default-app/packages/app/e2e-tests/app.test.ts +++ b/packages/create-app/templates/default-app/packages/app/e2e-tests/app.test.ts @@ -23,5 +23,11 @@ test('App should render the welcome page', async ({ page }) => { await expect(enterButton).toBeVisible(); await enterButton.click(); - await expect(page.getByText('My Company Catalog')).toBeVisible(); + const nav = page.getByRole('navigation'); + await expect( + nav.getByRole('link', { name: 'Catalog', exact: true }), + ).toBeVisible(); + await expect( + page.getByRole('link', { name: 'APIs', exact: true }), + ).toBeVisible(); }); diff --git a/packages/create-app/templates/default-app/packages/app/package.json.hbs b/packages/create-app/templates/default-app/packages/app/package.json.hbs index 5152113098..54672f6afa 100644 --- a/packages/create-app/templates/default-app/packages/app/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/app/package.json.hbs @@ -14,42 +14,38 @@ "lint": "backstage-cli package lint" }, "dependencies": { - "@backstage/app-defaults": "^{{version '@backstage/app-defaults'}}", - "@backstage/catalog-model": "^{{version '@backstage/catalog-model'}}", - "@backstage/cli": "^{{version '@backstage/cli'}}", - "@backstage/core-app-api": "^{{version '@backstage/core-app-api'}}", - "@backstage/core-components": "^{{version '@backstage/core-components'}}", - "@backstage/core-plugin-api": "^{{version '@backstage/core-plugin-api'}}", - "@backstage/integration-react": "^{{version '@backstage/integration-react'}}", - "@backstage/plugin-api-docs": "^{{version '@backstage/plugin-api-docs'}}", - "@backstage/plugin-catalog": "^{{version '@backstage/plugin-catalog'}}", - "@backstage/plugin-catalog-common": "^{{version '@backstage/plugin-catalog-common'}}", - "@backstage/plugin-catalog-graph": "^{{version '@backstage/plugin-catalog-graph'}}", - "@backstage/plugin-catalog-import": "^{{version '@backstage/plugin-catalog-import'}}", - "@backstage/plugin-catalog-react": "^{{version '@backstage/plugin-catalog-react'}}", - "@backstage/plugin-kubernetes": "^{{version '@backstage/plugin-kubernetes'}}", + "@backstage/cli": "^{{ version '@backstage/cli'}}", + "@backstage/core-components": "^{{ version '@backstage/core-components'}}", + "@backstage/core-plugin-api": "^{{ version '@backstage/core-plugin-api'}}", + "@backstage/frontend-defaults": "^{{ version '@backstage/frontend-defaults'}}", + "@backstage/frontend-plugin-api": "^{{ version '@backstage/frontend-plugin-api'}}", + "@backstage/integration-react": "^{{ version '@backstage/integration-react'}}", + "@backstage/plugin-api-docs": "^{{ version '@backstage/plugin-api-docs'}}", + "@backstage/plugin-app-react": "^{{ version '@backstage/plugin-app-react'}}", + "@backstage/plugin-app-visualizer": "^{{ version '@backstage/plugin-app-visualizer'}}", + "@backstage/plugin-auth": "^{{ version '@backstage/plugin-auth'}}", + "@backstage/plugin-catalog": "^{{ version '@backstage/plugin-catalog'}}", + "@backstage/plugin-catalog-graph": "^{{ version '@backstage/plugin-catalog-graph'}}", + "@backstage/plugin-catalog-import": "^{{ version '@backstage/plugin-catalog-import'}}", + "@backstage/plugin-kubernetes": "^{{ version '@backstage/plugin-kubernetes'}}", "@backstage/plugin-notifications": "^{{ version '@backstage/plugin-notifications'}}", - "@backstage/plugin-org": "^{{version '@backstage/plugin-org'}}", - "@backstage/plugin-permission-react": "^{{version '@backstage/plugin-permission-react'}}", - "@backstage/plugin-scaffolder": "^{{version '@backstage/plugin-scaffolder'}}", - "@backstage/plugin-search": "^{{version '@backstage/plugin-search'}}", - "@backstage/plugin-search-react": "^{{version '@backstage/plugin-search-react'}}", + "@backstage/plugin-org": "^{{ version '@backstage/plugin-org'}}", + "@backstage/plugin-scaffolder": "^{{ version '@backstage/plugin-scaffolder'}}", + "@backstage/plugin-search": "^{{ version '@backstage/plugin-search'}}", "@backstage/plugin-signals": "^{{ version '@backstage/plugin-signals'}}", - "@backstage/plugin-techdocs": "^{{version '@backstage/plugin-techdocs'}}", - "@backstage/plugin-techdocs-module-addons-contrib": "^{{version '@backstage/plugin-techdocs-module-addons-contrib'}}", - "@backstage/plugin-techdocs-react": "^{{version '@backstage/plugin-techdocs-react'}}", - "@backstage/plugin-user-settings": "^{{version '@backstage/plugin-user-settings'}}", - "@backstage/theme": "^{{version '@backstage/theme'}}", - "@backstage/ui": "^{{version '@backstage/ui'}}", + "@backstage/plugin-techdocs": "^{{ version '@backstage/plugin-techdocs'}}", + "@backstage/plugin-techdocs-module-addons-contrib": "^{{ version '@backstage/plugin-techdocs-module-addons-contrib'}}", + "@backstage/plugin-user-settings": "^{{ version '@backstage/plugin-user-settings'}}", + "@backstage/ui": "^{{ version '@backstage/ui'}}", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router": "^6.3.0", - "react-router-dom": "^6.3.0" + "react-router": "^6.30.2", + "react-router-dom": "^6.30.2" }, "devDependencies": { - "@backstage/test-utils": "^{{version '@backstage/test-utils'}}", + "@backstage/frontend-test-utils": "^{{ version '@backstage/frontend-test-utils'}}", "@playwright/test": "^1.32.3", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", diff --git a/packages/create-app/templates/default-app/packages/app/src/App.test.tsx b/packages/create-app/templates/default-app/packages/app/src/App.test.tsx index b6ca21d422..98e7a6438d 100644 --- a/packages/create-app/templates/default-app/packages/app/src/App.test.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/App.test.tsx @@ -19,7 +19,7 @@ describe('App', () => { ] as any, }; - const rendered = render(); + const rendered = render(App.createRoot()); await waitFor(() => { expect(rendered.baseElement).toBeInTheDocument(); diff --git a/packages/create-app/templates/default-app/packages/app/src/App.tsx b/packages/create-app/templates/default-app/packages/app/src/App.tsx index bb9c98eff8..c0206fec07 100644 --- a/packages/create-app/templates/default-app/packages/app/src/App.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/App.tsx @@ -1,112 +1,7 @@ -import { Navigate, Route } from 'react-router-dom'; -import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; -import { - CatalogEntityPage, - CatalogIndexPage, - catalogPlugin, -} from '@backstage/plugin-catalog'; -import { - CatalogImportPage, - catalogImportPlugin, -} from '@backstage/plugin-catalog-import'; -import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { orgPlugin } from '@backstage/plugin-org'; -import { SearchPage } from '@backstage/plugin-search'; -import { - TechDocsIndexPage, - techdocsPlugin, - TechDocsReaderPage, -} from '@backstage/plugin-techdocs'; -import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; -import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; -import { UserSettingsPage } from '@backstage/plugin-user-settings'; -import { apis } from './apis'; -import { entityPage } from './components/catalog/EntityPage'; -import { searchPage } from './components/search/SearchPage'; -import { Root } from './components/Root'; +import { createApp } from '@backstage/frontend-defaults'; +import catalogPlugin from '@backstage/plugin-catalog/alpha'; +import { navModule } from './modules/nav'; -import { - AlertDisplay, - OAuthRequestDialog, - SignInPage, -} from '@backstage/core-components'; -import { createApp } from '@backstage/app-defaults'; -import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; -import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; -import { RequirePermission } from '@backstage/plugin-permission-react'; -import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; -import { NotificationsPage } from '@backstage/plugin-notifications'; -import { SignalsDisplay } from '@backstage/plugin-signals'; - -const app = createApp({ - apis, - bindRoutes({ bind }) { - bind(catalogPlugin.externalRoutes, { - createComponent: scaffolderPlugin.routes.root, - viewTechDoc: techdocsPlugin.routes.docRoot, - createFromTemplate: scaffolderPlugin.routes.selectedTemplate, - }); - bind(apiDocsPlugin.externalRoutes, { - registerApi: catalogImportPlugin.routes.importPage, - }); - bind(scaffolderPlugin.externalRoutes, { - registerComponent: catalogImportPlugin.routes.importPage, - viewTechDoc: techdocsPlugin.routes.docRoot, - }); - bind(orgPlugin.externalRoutes, { - catalogIndex: catalogPlugin.routes.catalogIndex, - }); - }, - components: { - SignInPage: props => , - }, +export default createApp({ + features: [catalogPlugin, navModule], }); - -const routes = ( - - } /> - } /> - } - > - {entityPage} - - } /> - } - > - - - - - } /> - } /> - - - - } - /> - }> - {searchPage} - - } /> - } /> - } /> - -); - -export default app.createRoot( - <> - - - - - {routes} - - , -); diff --git a/packages/create-app/templates/default-app/packages/app/src/index.tsx b/packages/create-app/templates/default-app/packages/app/src/index.tsx index 46f31902f4..ac9e52bdc1 100644 --- a/packages/create-app/templates/default-app/packages/app/src/index.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/index.tsx @@ -3,4 +3,4 @@ import ReactDOM from 'react-dom/client'; import App from './App'; import '@backstage/ui/css/styles.css'; -ReactDOM.createRoot(document.getElementById('root')!).render(); +ReactDOM.createRoot(document.getElementById('root')!).render(App.createRoot()); diff --git a/packages/create-app/templates/next-app/packages/app/src/modules/nav/LogoFull.tsx b/packages/create-app/templates/default-app/packages/app/src/modules/nav/LogoFull.tsx similarity index 100% rename from packages/create-app/templates/next-app/packages/app/src/modules/nav/LogoFull.tsx rename to packages/create-app/templates/default-app/packages/app/src/modules/nav/LogoFull.tsx diff --git a/packages/create-app/templates/next-app/packages/app/src/modules/nav/LogoIcon.tsx b/packages/create-app/templates/default-app/packages/app/src/modules/nav/LogoIcon.tsx similarity index 100% rename from packages/create-app/templates/next-app/packages/app/src/modules/nav/LogoIcon.tsx rename to packages/create-app/templates/default-app/packages/app/src/modules/nav/LogoIcon.tsx diff --git a/packages/create-app/templates/next-app/packages/app/src/modules/nav/Sidebar.tsx b/packages/create-app/templates/default-app/packages/app/src/modules/nav/Sidebar.tsx similarity index 50% rename from packages/create-app/templates/next-app/packages/app/src/modules/nav/Sidebar.tsx rename to packages/create-app/templates/default-app/packages/app/src/modules/nav/Sidebar.tsx index ddfdde64d0..e0bb425d56 100644 --- a/packages/create-app/templates/next-app/packages/app/src/modules/nav/Sidebar.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/modules/nav/Sidebar.tsx @@ -1,25 +1,30 @@ import { + Sidebar, SidebarDivider, SidebarGroup, SidebarItem, SidebarScrollWrapper, SidebarSpace, } from '@backstage/core-components'; -import { compatWrapper } from '@backstage/core-compat-api'; -import { Sidebar } from '@backstage/core-components'; -import { NavContentBlueprint } from '@backstage/frontend-plugin-api'; +import { NavContentBlueprint } from '@backstage/plugin-app-react'; import { SidebarLogo } from './SidebarLogo'; -import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; -import HomeIcon from '@material-ui/icons/Home'; import MenuIcon from '@material-ui/icons/Menu'; import SearchIcon from '@material-ui/icons/Search'; import { SidebarSearchModal } from '@backstage/plugin-search'; -import { UserSettingsSignInAvatar, Settings as SidebarSettings } from '@backstage/plugin-user-settings'; +import { UserSettingsSignInAvatar } from '@backstage/plugin-user-settings'; +import { NotificationsSidebarItem } from '@backstage/plugin-notifications'; export const SidebarContent = NavContentBlueprint.make({ params: { - component: ({ items }) => - compatWrapper( + component: ({ navItems }) => { + const nav = navItems.withComponent(item => ( + item.icon} to={item.href} text={item.title} /> + )); + + // Skipped items + nav.take('page:search'); // Using search modal instead + + return ( } to="/search"> @@ -27,32 +32,27 @@ export const SidebarContent = NavContentBlueprint.make({ }> - {/* Global nav, not org-specific */} - - - {/* End global nav */} + {nav.take('page:catalog')} + {nav.take('page:scaffolder')} - {/* Items in this group will be scrollable if they run out of space */} - {items.map((item, index) => ( - - ))} + {nav.rest({ sortBy: 'title' })} + + } to="/settings" > - + {nav.take('page:app-visualizer')} + {nav.take('page:user-settings')} - , - ), + + ); + }, }, }); diff --git a/packages/create-app/templates/next-app/packages/app/src/modules/nav/SidebarLogo.tsx b/packages/create-app/templates/default-app/packages/app/src/modules/nav/SidebarLogo.tsx similarity index 100% rename from packages/create-app/templates/next-app/packages/app/src/modules/nav/SidebarLogo.tsx rename to packages/create-app/templates/default-app/packages/app/src/modules/nav/SidebarLogo.tsx diff --git a/packages/create-app/templates/next-app/packages/app/src/modules/nav/index.ts b/packages/create-app/templates/default-app/packages/app/src/modules/nav/index.ts similarity index 100% rename from packages/create-app/templates/next-app/packages/app/src/modules/nav/index.ts rename to packages/create-app/templates/default-app/packages/app/src/modules/nav/index.ts diff --git a/packages/create-app/templates/default-app/packages/backend/Dockerfile b/packages/create-app/templates/default-app/packages/backend/Dockerfile index ee80245d8b..a70705fba1 100644 --- a/packages/create-app/templates/default-app/packages/backend/Dockerfile +++ b/packages/create-app/templates/default-app/packages/backend/Dockerfile @@ -12,7 +12,7 @@ # Alternatively, there is also a multi-stage Dockerfile documented here: # https://backstage.io/docs/deployment/docker#multi-stage-build -FROM node:22-bookworm-slim +FROM node:24-trixie-slim # Set Python interpreter for `node-gyp` to use ENV PYTHON=/usr/bin/python3 diff --git a/packages/create-app/templates/default-app/packages/backend/package.json.hbs b/packages/create-app/templates/default-app/packages/backend/package.json.hbs index 518683ffd7..e906dff67d 100644 --- a/packages/create-app/templates/default-app/packages/backend/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/backend/package.json.hbs @@ -27,6 +27,7 @@ "@backstage/plugin-catalog-backend-module-logs": "^{{version '@backstage/plugin-catalog-backend-module-logs'}}", "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^{{version '@backstage/plugin-catalog-backend-module-scaffolder-entity-model'}}", "@backstage/plugin-kubernetes-backend": "^{{version '@backstage/plugin-kubernetes-backend'}}", + "@backstage/plugin-mcp-actions-backend": "^{{version '@backstage/plugin-mcp-actions-backend'}}", "@backstage/plugin-notifications-backend": "^{{version '@backstage/plugin-notifications-backend'}}", "@backstage/plugin-permission-backend": "^{{version '@backstage/plugin-permission-backend'}}", "@backstage/plugin-permission-backend-module-allow-all-policy": "^{{version '@backstage/plugin-permission-backend-module-allow-all-policy'}}", diff --git a/packages/create-app/templates/default-app/packages/backend/src/index.ts b/packages/create-app/templates/default-app/packages/backend/src/index.ts index cc2db7c3d4..7e9be64a7d 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/index.ts +++ b/packages/create-app/templates/default-app/packages/backend/src/index.ts @@ -63,4 +63,7 @@ backend.add(import('@backstage/plugin-kubernetes-backend')); backend.add(import('@backstage/plugin-notifications-backend')); backend.add(import('@backstage/plugin-signals-backend')); +// mcp actions plugin +backend.add(import('@backstage/plugin-mcp-actions-backend')); + backend.start(); diff --git a/packages/create-app/templates/default-app/playwright.config.ts b/packages/create-app/templates/default-app/playwright.config.ts index 0dc740b466..967bf1b847 100644 --- a/packages/create-app/templates/default-app/playwright.config.ts +++ b/packages/create-app/templates/default-app/playwright.config.ts @@ -24,7 +24,7 @@ export default defineConfig({ timeout: 60_000, expect: { - timeout: 5_000, + timeout: 30_000, }, // Run your local dev server before starting the tests @@ -33,9 +33,9 @@ export default defineConfig({ : [ { command: 'yarn start app', - port: 3000, + url: 'http://localhost:3000', reuseExistingServer: true, - timeout: 60_000, + timeout: 120_000, }, { command: 'yarn start backend', diff --git a/packages/create-app/templates/default-app/yarn.lock b/packages/create-app/templates/default-app/yarn.lock index 5ad7fe233d..f70eb08d6d 100644 --- a/packages/create-app/templates/default-app/yarn.lock +++ b/packages/create-app/templates/default-app/yarn.lock @@ -1 +1,12 @@ -# intentionally left empty +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10 + +"root-workspace-0b6124@workspace:.": + version: 0.0.0-use.local + resolution: "root-workspace-0b6124@workspace:." + languageName: unknown + linkType: soft diff --git a/packages/create-app/templates/next-app/.dockerignore b/packages/create-app/templates/legacy-app/.dockerignore similarity index 100% rename from packages/create-app/templates/next-app/.dockerignore rename to packages/create-app/templates/legacy-app/.dockerignore diff --git a/packages/create-app/templates/next-app/.eslintignore b/packages/create-app/templates/legacy-app/.eslintignore similarity index 100% rename from packages/create-app/templates/next-app/.eslintignore rename to packages/create-app/templates/legacy-app/.eslintignore diff --git a/packages/create-app/templates/next-app/.eslintrc.js.hbs b/packages/create-app/templates/legacy-app/.eslintrc.js.hbs similarity index 100% rename from packages/create-app/templates/next-app/.eslintrc.js.hbs rename to packages/create-app/templates/legacy-app/.eslintrc.js.hbs diff --git a/packages/create-app/templates/next-app/.gitignore.hbs b/packages/create-app/templates/legacy-app/.gitignore.hbs similarity index 100% rename from packages/create-app/templates/next-app/.gitignore.hbs rename to packages/create-app/templates/legacy-app/.gitignore.hbs diff --git a/packages/create-app/templates/next-app/.prettierignore b/packages/create-app/templates/legacy-app/.prettierignore similarity index 100% rename from packages/create-app/templates/next-app/.prettierignore rename to packages/create-app/templates/legacy-app/.prettierignore diff --git a/packages/create-app/templates/next-app/.yarn/releases/yarn-4.4.1.cjs b/packages/create-app/templates/legacy-app/.yarn/releases/yarn-4.4.1.cjs similarity index 100% rename from packages/create-app/templates/next-app/.yarn/releases/yarn-4.4.1.cjs rename to packages/create-app/templates/legacy-app/.yarn/releases/yarn-4.4.1.cjs diff --git a/packages/create-app/templates/next-app/.yarnrc.yml.hbs b/packages/create-app/templates/legacy-app/.yarnrc.yml.hbs similarity index 100% rename from packages/create-app/templates/next-app/.yarnrc.yml.hbs rename to packages/create-app/templates/legacy-app/.yarnrc.yml.hbs diff --git a/packages/create-app/templates/next-app/README.md b/packages/create-app/templates/legacy-app/README.md similarity index 100% rename from packages/create-app/templates/next-app/README.md rename to packages/create-app/templates/legacy-app/README.md diff --git a/packages/create-app/templates/default-app/app-config.local.yaml b/packages/create-app/templates/legacy-app/app-config.local.yaml similarity index 100% rename from packages/create-app/templates/default-app/app-config.local.yaml rename to packages/create-app/templates/legacy-app/app-config.local.yaml diff --git a/packages/create-app/templates/next-app/app-config.production.yaml b/packages/create-app/templates/legacy-app/app-config.production.yaml similarity index 100% rename from packages/create-app/templates/next-app/app-config.production.yaml rename to packages/create-app/templates/legacy-app/app-config.production.yaml diff --git a/packages/create-app/templates/next-app/app-config.yaml.hbs b/packages/create-app/templates/legacy-app/app-config.yaml.hbs similarity index 76% rename from packages/create-app/templates/next-app/app-config.yaml.hbs rename to packages/create-app/templates/legacy-app/app-config.yaml.hbs index 67917fd193..756c7e1568 100644 --- a/packages/create-app/templates/next-app/app-config.yaml.hbs +++ b/packages/create-app/templates/legacy-app/app-config.yaml.hbs @@ -2,22 +2,6 @@ app: title: Scaffolded Backstage App baseUrl: http://localhost:3000 - # Enable all packages by default, this will discover packages from packages/app/package.json - packages: all - - extensions: - # Disable the nav items that we're manually rendering in packages/app/src/modules/nav/Sidebar.tsx - - nav-item:search: false - - nav-item:user-settings: false - - nav-item:catalog: false - - nav-item:scaffolder: false - - # Configure the catalog index page to be the root page, this is normally mounted on /catalog - - page:catalog: - config: - path: / - - organization: name: My Company @@ -46,6 +30,12 @@ backend: database: client: better-sqlite3 connection: ':memory:' + # see https://backstage.io/docs/ai/mcp-actions#actions-configuration for more details + actions: + pluginSources: + - auth + - catalog + - scaffolder # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir integrations: @@ -83,6 +73,25 @@ auth: providers: # See https://backstage.io/docs/auth/guest/provider guest: {} + # see https://backstage.io/docs/ai/mcp-actions#dynamic-client-registration + # to learn more about dynamic client registration + experimentalDynamicClientRegistration: + enabled: false + # Optional: limit valid callback URLs for added security + # allowedRedirectUriPatterns: + # - cursor://* + # see https://backstage.io/docs/ai/mcp-actions#client-id-metadata-documents + # to learn more about client id metadata documents + experimentalClientIdMetadataDocuments: + enabled: true + # Optional: restrict which `client_id` URLs are allowed (defaults to ['*']) + # allowedClientIdPatterns: + # - 'https://example.com/*' + # - 'https://*.trusted-domain.com/*' + # Optional: restrict which redirect URIs are allowed (defaults to ['*']) + # allowedRedirectUriPatterns: + # - 'http://localhost:*' + # - 'https://*.example.com/*' scaffolder: # see https://backstage.io/docs/features/software-templates/configuration for software template options @@ -127,3 +136,8 @@ kubernetes: permission: # setting this to `false` will disable permissions enabled: true + +# see https://backstage.io/docs/ai/mcp-actions for more details +mcpActions: + name: 'My Company Backstage' # defaults to "backstage" + description: 'Tools for managing your software catalog, creating new services from templates, and exploring your developer portal' # optional diff --git a/packages/create-app/templates/next-app/backstage.json.hbs b/packages/create-app/templates/legacy-app/backstage.json.hbs similarity index 100% rename from packages/create-app/templates/next-app/backstage.json.hbs rename to packages/create-app/templates/legacy-app/backstage.json.hbs diff --git a/packages/create-app/templates/next-app/catalog-info.yaml.hbs b/packages/create-app/templates/legacy-app/catalog-info.yaml.hbs similarity index 100% rename from packages/create-app/templates/next-app/catalog-info.yaml.hbs rename to packages/create-app/templates/legacy-app/catalog-info.yaml.hbs diff --git a/packages/create-app/templates/next-app/examples/entities.yaml b/packages/create-app/templates/legacy-app/examples/entities.yaml similarity index 93% rename from packages/create-app/templates/next-app/examples/entities.yaml rename to packages/create-app/templates/legacy-app/examples/entities.yaml index 447e8b1f34..cc57de8b7a 100644 --- a/packages/create-app/templates/next-app/examples/entities.yaml +++ b/packages/create-app/templates/legacy-app/examples/entities.yaml @@ -12,6 +12,8 @@ apiVersion: backstage.io/v1alpha1 kind: Component metadata: name: example-website + annotations: + backstage.io/techdocs-ref: dir:./example-docs spec: type: website lifecycle: experimental diff --git a/packages/create-app/templates/next-app/examples/org.yaml b/packages/create-app/templates/legacy-app/examples/org.yaml similarity index 100% rename from packages/create-app/templates/next-app/examples/org.yaml rename to packages/create-app/templates/legacy-app/examples/org.yaml diff --git a/packages/create-app/templates/next-app/examples/template/content/catalog-info.yaml b/packages/create-app/templates/legacy-app/examples/template/content/catalog-info.yaml similarity index 100% rename from packages/create-app/templates/next-app/examples/template/content/catalog-info.yaml rename to packages/create-app/templates/legacy-app/examples/template/content/catalog-info.yaml diff --git a/packages/create-app/templates/next-app/examples/template/content/index.js b/packages/create-app/templates/legacy-app/examples/template/content/index.js similarity index 100% rename from packages/create-app/templates/next-app/examples/template/content/index.js rename to packages/create-app/templates/legacy-app/examples/template/content/index.js diff --git a/packages/create-app/templates/next-app/examples/template/content/package.json b/packages/create-app/templates/legacy-app/examples/template/content/package.json similarity index 100% rename from packages/create-app/templates/next-app/examples/template/content/package.json rename to packages/create-app/templates/legacy-app/examples/template/content/package.json diff --git a/packages/create-app/templates/next-app/examples/template/template.yaml b/packages/create-app/templates/legacy-app/examples/template/template.yaml similarity index 99% rename from packages/create-app/templates/next-app/examples/template/template.yaml rename to packages/create-app/templates/legacy-app/examples/template/template.yaml index 2a20bd45ed..efb33be866 100644 --- a/packages/create-app/templates/next-app/examples/template/template.yaml +++ b/packages/create-app/templates/legacy-app/examples/template/template.yaml @@ -65,7 +65,7 @@ spec: input: repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} catalogInfoPath: '/catalog-info.yaml' - + # Let's notify the user that the template has completed using the Notification action - id: notify name: Notify diff --git a/packages/create-app/templates/next-app/package.json.hbs b/packages/create-app/templates/legacy-app/package.json.hbs similarity index 60% rename from packages/create-app/templates/next-app/package.json.hbs rename to packages/create-app/templates/legacy-app/package.json.hbs index 33e76aba3b..836ecccac5 100644 --- a/packages/create-app/templates/next-app/package.json.hbs +++ b/packages/create-app/templates/legacy-app/package.json.hbs @@ -3,7 +3,7 @@ "version": "1.0.0", "private": true, "engines": { - "node": "20 || 22" + "node": "22 || 24" }, "scripts": { "start": "backstage-cli repo start", @@ -29,31 +29,34 @@ "license": "UNLICENSED" }, "templates": [ - "@backstage/cli/templates/new-frontend-plugin", - "@backstage/cli/templates/new-frontend-plugin-module", - "@backstage/cli/templates/backend-plugin", - "@backstage/cli/templates/backend-plugin-module", - "@backstage/cli/templates/plugin-web-library", - "@backstage/cli/templates/plugin-node-library", - "@backstage/cli/templates/plugin-common-library", - "@backstage/cli/templates/web-library", - "@backstage/cli/templates/node-library", - "@backstage/cli/templates/catalog-provider-module", - "@backstage/cli/templates/scaffolder-backend-module" + "@backstage/cli-module-new/templates/legacy-frontend-plugin", + "@backstage/cli-module-new/templates/backend-plugin", + "@backstage/cli-module-new/templates/backend-plugin-module", + "@backstage/cli-module-new/templates/plugin-web-library", + "@backstage/cli-module-new/templates/plugin-node-library", + "@backstage/cli-module-new/templates/plugin-common-library", + "@backstage/cli-module-new/templates/web-library", + "@backstage/cli-module-new/templates/node-library", + "@backstage/cli-module-new/templates/cli-module", + "@backstage/cli-module-new/templates/catalog-provider-module", + "@backstage/cli-module-new/templates/scaffolder-backend-module" ] } } }, - "workspaces": { - "packages": [ - "packages/*", - "plugins/*" - ] - }, + "workspaces": [ + "packages/*", + "plugins/*" + ], "devDependencies": { "@backstage/cli": "^{{version '@backstage/cli'}}", + "@backstage/cli-defaults": "^{{version '@backstage/cli-defaults'}}", "@backstage/e2e-test-utils": "^{{version '@backstage/e2e-test-utils'}}", + "@jest/environment-jsdom-abstract": "^30.0.0", "@playwright/test": "^1.32.3", + "@types/jest": "^30.0.0", + "jest": "^30.2.0", + "jsdom": "^27.1.0", "node-gyp": "^10.0.0", "prettier": "^2.3.2", "typescript": "~5.8.0" diff --git a/packages/create-app/templates/next-app/packages/README.md b/packages/create-app/templates/legacy-app/packages/README.md similarity index 100% rename from packages/create-app/templates/next-app/packages/README.md rename to packages/create-app/templates/legacy-app/packages/README.md diff --git a/packages/create-app/templates/next-app/packages/app/.eslintignore b/packages/create-app/templates/legacy-app/packages/app/.eslintignore similarity index 100% rename from packages/create-app/templates/next-app/packages/app/.eslintignore rename to packages/create-app/templates/legacy-app/packages/app/.eslintignore diff --git a/packages/create-app/templates/legacy-app/packages/app/.eslintrc.js.hbs b/packages/create-app/templates/legacy-app/packages/app/.eslintrc.js.hbs new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/packages/create-app/templates/legacy-app/packages/app/.eslintrc.js.hbs @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/packages/create-app/templates/next-app/packages/app/e2e-tests/app.test.ts b/packages/create-app/templates/legacy-app/packages/app/e2e-tests/app.test.ts similarity index 100% rename from packages/create-app/templates/next-app/packages/app/e2e-tests/app.test.ts rename to packages/create-app/templates/legacy-app/packages/app/e2e-tests/app.test.ts diff --git a/packages/create-app/templates/legacy-app/packages/app/package.json.hbs b/packages/create-app/templates/legacy-app/packages/app/package.json.hbs new file mode 100644 index 0000000000..392a00200b --- /dev/null +++ b/packages/create-app/templates/legacy-app/packages/app/package.json.hbs @@ -0,0 +1,76 @@ +{ + "name": "app", + "version": "0.0.0", + "private": true, + "bundled": true, + "backstage": { + "role": "frontend" + }, + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "clean": "backstage-cli package clean", + "test": "backstage-cli package test", + "lint": "backstage-cli package lint" + }, + "dependencies": { + "@backstage/app-defaults": "^{{version '@backstage/app-defaults'}}", + "@backstage/catalog-model": "^{{version '@backstage/catalog-model'}}", + "@backstage/cli": "^{{version '@backstage/cli'}}", + "@backstage/core-app-api": "^{{version '@backstage/core-app-api'}}", + "@backstage/core-components": "^{{version '@backstage/core-components'}}", + "@backstage/core-plugin-api": "^{{version '@backstage/core-plugin-api'}}", + "@backstage/integration-react": "^{{version '@backstage/integration-react'}}", + "@backstage/plugin-api-docs": "^{{version '@backstage/plugin-api-docs'}}", + "@backstage/plugin-catalog": "^{{version '@backstage/plugin-catalog'}}", + "@backstage/plugin-catalog-common": "^{{version '@backstage/plugin-catalog-common'}}", + "@backstage/plugin-catalog-graph": "^{{version '@backstage/plugin-catalog-graph'}}", + "@backstage/plugin-catalog-import": "^{{version '@backstage/plugin-catalog-import'}}", + "@backstage/plugin-catalog-react": "^{{version '@backstage/plugin-catalog-react'}}", + "@backstage/plugin-kubernetes": "^{{version '@backstage/plugin-kubernetes'}}", + "@backstage/plugin-notifications": "^{{ version '@backstage/plugin-notifications'}}", + "@backstage/plugin-org": "^{{version '@backstage/plugin-org'}}", + "@backstage/plugin-permission-react": "^{{version '@backstage/plugin-permission-react'}}", + "@backstage/plugin-scaffolder": "^{{version '@backstage/plugin-scaffolder'}}", + "@backstage/plugin-search": "^{{version '@backstage/plugin-search'}}", + "@backstage/plugin-search-react": "^{{version '@backstage/plugin-search-react'}}", + "@backstage/plugin-signals": "^{{ version '@backstage/plugin-signals'}}", + "@backstage/plugin-techdocs": "^{{version '@backstage/plugin-techdocs'}}", + "@backstage/plugin-techdocs-module-addons-contrib": "^{{version '@backstage/plugin-techdocs-module-addons-contrib'}}", + "@backstage/plugin-techdocs-react": "^{{version '@backstage/plugin-techdocs-react'}}", + "@backstage/plugin-user-settings": "^{{version '@backstage/plugin-user-settings'}}", + "@backstage/theme": "^{{version '@backstage/theme'}}", + "@backstage/ui": "^{{version '@backstage/ui'}}", + "@material-ui/core": "^4.12.2", + "@material-ui/icons": "^4.9.1", + "react": "^18.0.2", + "react-dom": "^18.0.2", + "react-router": "^6.30.2", + "react-router-dom": "^6.30.2" + }, + "devDependencies": { + "@backstage/test-utils": "^{{version '@backstage/test-utils'}}", + "@playwright/test": "^1.32.3", + "@testing-library/dom": "^9.0.0", + "@testing-library/jest-dom": "^6.0.0", + "@testing-library/react": "^14.0.0", + "@testing-library/user-event": "^14.0.0", + "@types/react-dom": "*", + "cross-env": "^7.0.0" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "files": [ + "dist" + ] +} diff --git a/packages/create-app/templates/next-app/packages/app/public/android-chrome-192x192.png b/packages/create-app/templates/legacy-app/packages/app/public/android-chrome-192x192.png similarity index 100% rename from packages/create-app/templates/next-app/packages/app/public/android-chrome-192x192.png rename to packages/create-app/templates/legacy-app/packages/app/public/android-chrome-192x192.png diff --git a/packages/create-app/templates/next-app/packages/app/public/apple-touch-icon.png b/packages/create-app/templates/legacy-app/packages/app/public/apple-touch-icon.png similarity index 100% rename from packages/create-app/templates/next-app/packages/app/public/apple-touch-icon.png rename to packages/create-app/templates/legacy-app/packages/app/public/apple-touch-icon.png diff --git a/packages/create-app/templates/next-app/packages/app/public/favicon-16x16.png b/packages/create-app/templates/legacy-app/packages/app/public/favicon-16x16.png similarity index 100% rename from packages/create-app/templates/next-app/packages/app/public/favicon-16x16.png rename to packages/create-app/templates/legacy-app/packages/app/public/favicon-16x16.png diff --git a/packages/create-app/templates/next-app/packages/app/public/favicon-32x32.png b/packages/create-app/templates/legacy-app/packages/app/public/favicon-32x32.png similarity index 100% rename from packages/create-app/templates/next-app/packages/app/public/favicon-32x32.png rename to packages/create-app/templates/legacy-app/packages/app/public/favicon-32x32.png diff --git a/packages/create-app/templates/next-app/packages/app/public/favicon.ico b/packages/create-app/templates/legacy-app/packages/app/public/favicon.ico similarity index 100% rename from packages/create-app/templates/next-app/packages/app/public/favicon.ico rename to packages/create-app/templates/legacy-app/packages/app/public/favicon.ico diff --git a/packages/create-app/templates/next-app/packages/app/public/index.html b/packages/create-app/templates/legacy-app/packages/app/public/index.html similarity index 100% rename from packages/create-app/templates/next-app/packages/app/public/index.html rename to packages/create-app/templates/legacy-app/packages/app/public/index.html diff --git a/packages/create-app/templates/next-app/packages/app/public/manifest.json b/packages/create-app/templates/legacy-app/packages/app/public/manifest.json similarity index 100% rename from packages/create-app/templates/next-app/packages/app/public/manifest.json rename to packages/create-app/templates/legacy-app/packages/app/public/manifest.json diff --git a/packages/create-app/templates/next-app/packages/app/public/robots.txt b/packages/create-app/templates/legacy-app/packages/app/public/robots.txt similarity index 100% rename from packages/create-app/templates/next-app/packages/app/public/robots.txt rename to packages/create-app/templates/legacy-app/packages/app/public/robots.txt diff --git a/packages/create-app/templates/next-app/packages/app/public/safari-pinned-tab.svg b/packages/create-app/templates/legacy-app/packages/app/public/safari-pinned-tab.svg similarity index 100% rename from packages/create-app/templates/next-app/packages/app/public/safari-pinned-tab.svg rename to packages/create-app/templates/legacy-app/packages/app/public/safari-pinned-tab.svg diff --git a/packages/create-app/templates/next-app/packages/app/src/App.test.tsx b/packages/create-app/templates/legacy-app/packages/app/src/App.test.tsx similarity index 92% rename from packages/create-app/templates/next-app/packages/app/src/App.test.tsx rename to packages/create-app/templates/legacy-app/packages/app/src/App.test.tsx index 98e7a6438d..b6ca21d422 100644 --- a/packages/create-app/templates/next-app/packages/app/src/App.test.tsx +++ b/packages/create-app/templates/legacy-app/packages/app/src/App.test.tsx @@ -19,7 +19,7 @@ describe('App', () => { ] as any, }; - const rendered = render(App.createRoot()); + const rendered = render(); await waitFor(() => { expect(rendered.baseElement).toBeInTheDocument(); diff --git a/packages/create-app/templates/legacy-app/packages/app/src/App.tsx b/packages/create-app/templates/legacy-app/packages/app/src/App.tsx new file mode 100644 index 0000000000..bb9c98eff8 --- /dev/null +++ b/packages/create-app/templates/legacy-app/packages/app/src/App.tsx @@ -0,0 +1,112 @@ +import { Navigate, Route } from 'react-router-dom'; +import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +import { + CatalogEntityPage, + CatalogIndexPage, + catalogPlugin, +} from '@backstage/plugin-catalog'; +import { + CatalogImportPage, + catalogImportPlugin, +} from '@backstage/plugin-catalog-import'; +import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; +import { orgPlugin } from '@backstage/plugin-org'; +import { SearchPage } from '@backstage/plugin-search'; +import { + TechDocsIndexPage, + techdocsPlugin, + TechDocsReaderPage, +} from '@backstage/plugin-techdocs'; +import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; +import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; +import { UserSettingsPage } from '@backstage/plugin-user-settings'; +import { apis } from './apis'; +import { entityPage } from './components/catalog/EntityPage'; +import { searchPage } from './components/search/SearchPage'; +import { Root } from './components/Root'; + +import { + AlertDisplay, + OAuthRequestDialog, + SignInPage, +} from '@backstage/core-components'; +import { createApp } from '@backstage/app-defaults'; +import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; +import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; +import { RequirePermission } from '@backstage/plugin-permission-react'; +import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; +import { NotificationsPage } from '@backstage/plugin-notifications'; +import { SignalsDisplay } from '@backstage/plugin-signals'; + +const app = createApp({ + apis, + bindRoutes({ bind }) { + bind(catalogPlugin.externalRoutes, { + createComponent: scaffolderPlugin.routes.root, + viewTechDoc: techdocsPlugin.routes.docRoot, + createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { + registerApi: catalogImportPlugin.routes.importPage, + }); + bind(scaffolderPlugin.externalRoutes, { + registerComponent: catalogImportPlugin.routes.importPage, + viewTechDoc: techdocsPlugin.routes.docRoot, + }); + bind(orgPlugin.externalRoutes, { + catalogIndex: catalogPlugin.routes.catalogIndex, + }); + }, + components: { + SignInPage: props => , + }, +}); + +const routes = ( + + } /> + } /> + } + > + {entityPage} + + } /> + } + > + + + + + } /> + } /> + + + + } + /> + }> + {searchPage} + + } /> + } /> + } /> + +); + +export default app.createRoot( + <> + + + + + {routes} + + , +); diff --git a/packages/create-app/templates/default-app/packages/app/src/apis.ts b/packages/create-app/templates/legacy-app/packages/app/src/apis.ts similarity index 100% rename from packages/create-app/templates/default-app/packages/app/src/apis.ts rename to packages/create-app/templates/legacy-app/packages/app/src/apis.ts diff --git a/packages/create-app/templates/default-app/packages/app/src/components/Root/LogoFull.tsx b/packages/create-app/templates/legacy-app/packages/app/src/components/Root/LogoFull.tsx similarity index 100% rename from packages/create-app/templates/default-app/packages/app/src/components/Root/LogoFull.tsx rename to packages/create-app/templates/legacy-app/packages/app/src/components/Root/LogoFull.tsx diff --git a/packages/create-app/templates/default-app/packages/app/src/components/Root/LogoIcon.tsx b/packages/create-app/templates/legacy-app/packages/app/src/components/Root/LogoIcon.tsx similarity index 100% rename from packages/create-app/templates/default-app/packages/app/src/components/Root/LogoIcon.tsx rename to packages/create-app/templates/legacy-app/packages/app/src/components/Root/LogoIcon.tsx diff --git a/packages/create-app/templates/default-app/packages/app/src/components/Root/Root.tsx b/packages/create-app/templates/legacy-app/packages/app/src/components/Root/Root.tsx similarity index 100% rename from packages/create-app/templates/default-app/packages/app/src/components/Root/Root.tsx rename to packages/create-app/templates/legacy-app/packages/app/src/components/Root/Root.tsx diff --git a/packages/create-app/templates/default-app/packages/app/src/components/Root/index.ts b/packages/create-app/templates/legacy-app/packages/app/src/components/Root/index.ts similarity index 100% rename from packages/create-app/templates/default-app/packages/app/src/components/Root/index.ts rename to packages/create-app/templates/legacy-app/packages/app/src/components/Root/index.ts diff --git a/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx b/packages/create-app/templates/legacy-app/packages/app/src/components/catalog/EntityPage.tsx similarity index 89% rename from packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx rename to packages/create-app/templates/legacy-app/packages/app/src/components/catalog/EntityPage.tsx index f75d984366..d229b536cb 100644 --- a/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/create-app/templates/legacy-app/packages/app/src/components/catalog/EntityPage.tsx @@ -128,17 +128,17 @@ const overviewContent = ( {entityWarningContent} - + - + - + ); @@ -175,10 +175,10 @@ const serviceEntityPage = ( - + - + @@ -210,10 +210,10 @@ const websiteEntityPage = ( - + - + @@ -266,7 +266,7 @@ const apiPage = ( - + @@ -298,10 +298,10 @@ const userPage = ( {entityWarningContent} - + - + @@ -314,10 +314,10 @@ const groupPage = ( {entityWarningContent} - + - + @@ -336,28 +336,27 @@ const systemPage = ( {entityWarningContent} - + - + - + - + - + {entityWarningContent} - + - + - + diff --git a/packages/create-app/templates/default-app/packages/app/src/components/search/SearchPage.tsx b/packages/create-app/templates/legacy-app/packages/app/src/components/search/SearchPage.tsx similarity index 100% rename from packages/create-app/templates/default-app/packages/app/src/components/search/SearchPage.tsx rename to packages/create-app/templates/legacy-app/packages/app/src/components/search/SearchPage.tsx diff --git a/packages/create-app/templates/next-app/packages/app/src/index.tsx b/packages/create-app/templates/legacy-app/packages/app/src/index.tsx similarity index 64% rename from packages/create-app/templates/next-app/packages/app/src/index.tsx rename to packages/create-app/templates/legacy-app/packages/app/src/index.tsx index ac9e52bdc1..46f31902f4 100644 --- a/packages/create-app/templates/next-app/packages/app/src/index.tsx +++ b/packages/create-app/templates/legacy-app/packages/app/src/index.tsx @@ -3,4 +3,4 @@ import ReactDOM from 'react-dom/client'; import App from './App'; import '@backstage/ui/css/styles.css'; -ReactDOM.createRoot(document.getElementById('root')!).render(App.createRoot()); +ReactDOM.createRoot(document.getElementById('root')!).render(); diff --git a/packages/create-app/templates/next-app/packages/app/src/setupTests.ts b/packages/create-app/templates/legacy-app/packages/app/src/setupTests.ts similarity index 100% rename from packages/create-app/templates/next-app/packages/app/src/setupTests.ts rename to packages/create-app/templates/legacy-app/packages/app/src/setupTests.ts diff --git a/packages/create-app/templates/legacy-app/packages/backend/.eslintrc.js.hbs b/packages/create-app/templates/legacy-app/packages/backend/.eslintrc.js.hbs new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/packages/create-app/templates/legacy-app/packages/backend/.eslintrc.js.hbs @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/packages/create-app/templates/next-app/packages/backend/Dockerfile b/packages/create-app/templates/legacy-app/packages/backend/Dockerfile similarity index 99% rename from packages/create-app/templates/next-app/packages/backend/Dockerfile rename to packages/create-app/templates/legacy-app/packages/backend/Dockerfile index ee80245d8b..a70705fba1 100644 --- a/packages/create-app/templates/next-app/packages/backend/Dockerfile +++ b/packages/create-app/templates/legacy-app/packages/backend/Dockerfile @@ -12,7 +12,7 @@ # Alternatively, there is also a multi-stage Dockerfile documented here: # https://backstage.io/docs/deployment/docker#multi-stage-build -FROM node:22-bookworm-slim +FROM node:24-trixie-slim # Set Python interpreter for `node-gyp` to use ENV PYTHON=/usr/bin/python3 diff --git a/packages/create-app/templates/next-app/packages/backend/README.md b/packages/create-app/templates/legacy-app/packages/backend/README.md similarity index 100% rename from packages/create-app/templates/next-app/packages/backend/README.md rename to packages/create-app/templates/legacy-app/packages/backend/README.md diff --git a/packages/create-app/templates/next-app/packages/backend/package.json.hbs b/packages/create-app/templates/legacy-app/packages/backend/package.json.hbs similarity index 97% rename from packages/create-app/templates/next-app/packages/backend/package.json.hbs rename to packages/create-app/templates/legacy-app/packages/backend/package.json.hbs index 518683ffd7..e906dff67d 100644 --- a/packages/create-app/templates/next-app/packages/backend/package.json.hbs +++ b/packages/create-app/templates/legacy-app/packages/backend/package.json.hbs @@ -27,6 +27,7 @@ "@backstage/plugin-catalog-backend-module-logs": "^{{version '@backstage/plugin-catalog-backend-module-logs'}}", "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^{{version '@backstage/plugin-catalog-backend-module-scaffolder-entity-model'}}", "@backstage/plugin-kubernetes-backend": "^{{version '@backstage/plugin-kubernetes-backend'}}", + "@backstage/plugin-mcp-actions-backend": "^{{version '@backstage/plugin-mcp-actions-backend'}}", "@backstage/plugin-notifications-backend": "^{{version '@backstage/plugin-notifications-backend'}}", "@backstage/plugin-permission-backend": "^{{version '@backstage/plugin-permission-backend'}}", "@backstage/plugin-permission-backend-module-allow-all-policy": "^{{version '@backstage/plugin-permission-backend-module-allow-all-policy'}}", diff --git a/packages/create-app/templates/next-app/packages/backend/src/index.ts b/packages/create-app/templates/legacy-app/packages/backend/src/index.ts similarity index 96% rename from packages/create-app/templates/next-app/packages/backend/src/index.ts rename to packages/create-app/templates/legacy-app/packages/backend/src/index.ts index cc2db7c3d4..7e9be64a7d 100644 --- a/packages/create-app/templates/next-app/packages/backend/src/index.ts +++ b/packages/create-app/templates/legacy-app/packages/backend/src/index.ts @@ -63,4 +63,7 @@ backend.add(import('@backstage/plugin-kubernetes-backend')); backend.add(import('@backstage/plugin-notifications-backend')); backend.add(import('@backstage/plugin-signals-backend')); +// mcp actions plugin +backend.add(import('@backstage/plugin-mcp-actions-backend')); + backend.start(); diff --git a/packages/create-app/templates/next-app/playwright.config.ts b/packages/create-app/templates/legacy-app/playwright.config.ts similarity index 100% rename from packages/create-app/templates/next-app/playwright.config.ts rename to packages/create-app/templates/legacy-app/playwright.config.ts diff --git a/packages/create-app/templates/next-app/plugins/README.md b/packages/create-app/templates/legacy-app/plugins/README.md similarity index 100% rename from packages/create-app/templates/next-app/plugins/README.md rename to packages/create-app/templates/legacy-app/plugins/README.md diff --git a/packages/create-app/templates/next-app/tsconfig.json b/packages/create-app/templates/legacy-app/tsconfig.json similarity index 100% rename from packages/create-app/templates/next-app/tsconfig.json rename to packages/create-app/templates/legacy-app/tsconfig.json diff --git a/packages/create-app/templates/next-app/yarn.lock b/packages/create-app/templates/legacy-app/yarn.lock similarity index 100% rename from packages/create-app/templates/next-app/yarn.lock rename to packages/create-app/templates/legacy-app/yarn.lock diff --git a/packages/create-app/templates/next-app/examples/example-docs/docs/index.md b/packages/create-app/templates/next-app/examples/example-docs/docs/index.md new file mode 100644 index 0000000000..ecd4dbd529 --- /dev/null +++ b/packages/create-app/templates/next-app/examples/example-docs/docs/index.md @@ -0,0 +1,5 @@ +# Example TechDocs page + +This is an example TechDocs page! + +The source of this documentation for the `example-website` component is found in the `examples/example-docs` directory, which is referenced from `examples/entities.yaml`. Typically the `docs` directory would live alongside the `catalog-info.yaml` file of a component instead, but in this case it is separated out to avoid cluttering the examples directory. diff --git a/packages/create-app/templates/next-app/examples/example-docs/mkdocs.yml b/packages/create-app/templates/next-app/examples/example-docs/mkdocs.yml new file mode 100644 index 0000000000..b482f45a27 --- /dev/null +++ b/packages/create-app/templates/next-app/examples/example-docs/mkdocs.yml @@ -0,0 +1,4 @@ +site_name: example-website +docs_dir: docs +plugins: + - techdocs-core diff --git a/packages/create-app/templates/next-app/packages/app/package.json.hbs b/packages/create-app/templates/next-app/packages/app/package.json.hbs deleted file mode 100644 index 726983fd69..0000000000 --- a/packages/create-app/templates/next-app/packages/app/package.json.hbs +++ /dev/null @@ -1,65 +0,0 @@ -{ - "name": "app", - "version": "0.0.0", - "private": true, - "bundled": true, - "backstage": { - "role": "frontend" - }, - "scripts": { - "start": "backstage-cli package start", - "build": "backstage-cli package build", - "clean": "backstage-cli package clean", - "test": "backstage-cli package test", - "lint": "backstage-cli package lint" - }, - "dependencies": { - "@backstage/cli": "^{{ version '@backstage/cli'}}", - "@backstage/core-compat-api": "^{{ version '@backstage/core-compat-api'}}", - "@backstage/core-components": "^{{ version '@backstage/core-components'}}", - "@backstage/core-plugin-api": "^{{ version '@backstage/core-plugin-api'}}", - "@backstage/frontend-defaults": "^{{ version '@backstage/frontend-defaults'}}", - "@backstage/frontend-plugin-api": "^{{ version '@backstage/frontend-plugin-api'}}", - "@backstage/integration-react": "^{{ version '@backstage/integration-react'}}", - "@backstage/plugin-app-visualizer": "^{{ version '@backstage/plugin-app-visualizer'}}", - "@backstage/plugin-catalog": "^{{ version '@backstage/plugin-catalog'}}", - "@backstage/plugin-notifications": "^{{ version '@backstage/plugin-notifications'}}", - "@backstage/plugin-org": "^{{ version '@backstage/plugin-org'}}", - "@backstage/plugin-scaffolder": "^{{ version '@backstage/plugin-scaffolder'}}", - "@backstage/plugin-search": "^{{ version '@backstage/plugin-search'}}", - "@backstage/plugin-signals": "^{{ version '@backstage/plugin-signals'}}", - "@backstage/plugin-user-settings": "^{{ version '@backstage/plugin-user-settings'}}", - "@backstage/ui": "^{{ version '@backstage/ui'}}", - "@material-ui/core": "^4.12.2", - "@material-ui/icons": "^4.9.1", - "react": "^18.0.2", - "react-dom": "^18.0.2", - "react-router": "^6.3.0", - "react-router-dom": "^6.3.0" - }, - "devDependencies": { - "@backstage/frontend-test-utils": "^{{ version '@backstage/frontend-test-utils'}}", - "@playwright/test": "^1.32.3", - "@testing-library/dom": "^9.0.0", - "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "@types/react-dom": "*", - "cross-env": "^7.0.0" - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, - "files": [ - "dist" - ] -} diff --git a/packages/create-app/templates/next-app/packages/app/src/App.tsx b/packages/create-app/templates/next-app/packages/app/src/App.tsx deleted file mode 100644 index c0206fec07..0000000000 --- a/packages/create-app/templates/next-app/packages/app/src/App.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { createApp } from '@backstage/frontend-defaults'; -import catalogPlugin from '@backstage/plugin-catalog/alpha'; -import { navModule } from './modules/nav'; - -export default createApp({ - features: [catalogPlugin, navModule], -}); diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index 056bc4d9f9..f7ffe9fe82 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,260 @@ # @backstage/dev-utils +## 1.1.22 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0 + - @backstage/app-defaults@1.7.7 + - @backstage/theme@0.7.3 + - @backstage/catalog-model@1.8.0 + - @backstage/plugin-catalog-react@2.1.2 + - @backstage/core-app-api@1.20.0 + - @backstage/core-components@0.18.9 + - @backstage/core-plugin-api@1.12.5 + - @backstage/integration-react@1.2.17 + +## 1.1.22-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.2 + - @backstage/theme@0.7.3-next.0 + - @backstage/core-app-api@1.20.0-next.2 + - @backstage/core-components@0.18.9-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.2 + - @backstage/catalog-model@1.7.8-next.0 + - @backstage/core-plugin-api@1.12.5-next.2 + - @backstage/app-defaults@1.7.7-next.2 + - @backstage/integration-react@1.2.17-next.1 + +## 1.1.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/app-defaults@1.7.7-next.1 + - @backstage/ui@0.14.0-next.1 + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + +## 1.1.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.0 + - @backstage/plugin-catalog-react@2.1.1-next.0 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-app-api@1.19.7-next.0 + - @backstage/app-defaults@1.7.7-next.0 + - @backstage/integration-react@1.2.17-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + - @backstage/catalog-model@1.7.7 + - @backstage/theme@0.7.2 + +## 1.1.21 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/ui@0.13.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/core-app-api@1.19.6 + - @backstage/catalog-model@1.7.7 + - @backstage/app-defaults@1.7.6 + - @backstage/integration-react@1.2.16 + +## 1.1.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.13.0-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/app-defaults@1.7.6-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/integration-react@1.2.16-next.1 + - @backstage/theme@0.7.2 + +## 1.1.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.12.1-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/app-defaults@1.7.6-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/integration-react@1.2.16-next.0 + - @backstage/theme@0.7.2 + +## 1.1.20 + +### Patch Changes + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/ui@0.12.0 + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/core-components@0.18.7 + - @backstage/core-app-api@1.19.5 + - @backstage/theme@0.7.2 + - @backstage/core-plugin-api@1.12.3 + - @backstage/integration-react@1.2.15 + - @backstage/app-defaults@1.7.5 + +## 1.1.20-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/ui@0.12.0-next.2 + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/core-app-api@1.19.5-next.1 + - @backstage/integration-react@1.2.15-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/app-defaults@1.7.5-next.2 + - @backstage/theme@0.7.2-next.1 + +## 1.1.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/ui@0.12.0-next.1 + - @backstage/theme@0.7.2-next.0 + - @backstage/app-defaults@1.7.5-next.1 + - @backstage/core-components@0.18.7-next.1 + - @backstage/integration-react@1.2.15-next.1 + +## 1.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/ui@0.12.0-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/core-app-api@1.19.4-next.0 + - @backstage/app-defaults@1.7.5-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/integration-react@1.2.15-next.0 + - @backstage/theme@0.7.1 + +## 1.1.19 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.11.0 + - @backstage/core-components@0.18.5 + - @backstage/app-defaults@1.7.4 + - @backstage/integration-react@1.2.14 + - @backstage/plugin-catalog-react@1.21.5 + +## 1.1.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/app-defaults@1.7.4-next.0 + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.1 + +## 1.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.0 + +## 1.1.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.11.0-next.0 + - @backstage/app-defaults@1.7.3 + - @backstage/catalog-model@1.7.6 + - @backstage/core-app-api@1.19.3 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/integration-react@1.2.13 + - @backstage/theme@0.7.1 + - @backstage/plugin-catalog-react@1.21.4 + +## 1.1.18 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/ui@0.10.0 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/app-defaults@1.7.3 + - @backstage/integration-react@1.2.13 + +## 1.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.10.0-next.1 + - @backstage/core-components@0.18.4-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.1 + +## 1.1.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.9.1-next.0 + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/app-defaults@1.7.3-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/catalog-model@1.7.6 + +## 1.1.17 + +### Patch Changes + +- b29a856: Fixed styling of the dev app by adding a lazy import of `@backstage/ui/css/styles.css`. +- Updated dependencies + - @backstage/ui@0.9.0 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/catalog-model@1.7.6 + - @backstage/app-defaults@1.7.2 + - @backstage/integration-react@1.2.12 + ## 1.1.17-next.1 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index c7147efda9..96280f4346 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/dev-utils", - "version": "1.1.17-next.1", + "version": "1.1.22", "description": "Utilities for developing Backstage plugins.", "backstage": { "role": "web-library" @@ -59,14 +59,14 @@ "@types/react": "^18.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0", + "react-router-dom": "^6.30.2", "zen-observable": "^0.10.0" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/packages/e2e-test-utils/CHANGELOG.md b/packages/e2e-test-utils/CHANGELOG.md index d897a9a63a..17c3d443ea 100644 --- a/packages/e2e-test-utils/CHANGELOG.md +++ b/packages/e2e-test-utils/CHANGELOG.md @@ -1,5 +1,45 @@ # @backstage/e2e-test-utils +## 0.1.2 + +### Patch Changes + +- b96c20e: Added optional `channel` option to `generateProjects()` to allow customizing the Playwright browser channel for testing against different browsers variants. When not provided, the function defaults to 'chrome' to maintain backward compatibility. + + Example usage: + + ```ts + import { generateProjects } from '@backstage/e2e-test-utils'; + + export default defineConfig({ + projects: generateProjects({ channel: 'msedge' }), + }); + ``` + +- 7455dae: Use node prefix on native imports + +## 0.1.2-next.1 + +### Patch Changes + +- b96c20e: Added optional `channel` option to `generateProjects()` to allow customizing the Playwright browser channel for testing against different browsers variants. When not provided, the function defaults to 'chrome' to maintain backward compatibility. + + Example usage: + + ```ts + import { generateProjects } from '@backstage/e2e-test-utils'; + + export default defineConfig({ + projects: generateProjects({ channel: 'msedge' }), + }); + ``` + +## 0.1.2-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports + ## 0.1.1 ### Patch Changes diff --git a/packages/e2e-test-utils/knip-report.md b/packages/e2e-test-utils/knip-report.md index b3b407dc65..5798802e35 100644 --- a/packages/e2e-test-utils/knip-report.md +++ b/packages/e2e-test-utils/knip-report.md @@ -4,5 +4,5 @@ | Name | Location | Severity | | :--------------- | :----------- | :------- | -| @playwright/test | packages/e2e-test-utils/package.json | error | +| @playwright/test | package.json | error | diff --git a/packages/e2e-test-utils/package.json b/packages/e2e-test-utils/package.json index b3044facf6..edf88711db 100644 --- a/packages/e2e-test-utils/package.json +++ b/packages/e2e-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/e2e-test-utils", - "version": "0.1.1", + "version": "0.1.2", "description": "Shared end-to-end test utilities Backstage", "backstage": { "role": "node-library" diff --git a/packages/e2e-test-utils/report-playwright.api.md b/packages/e2e-test-utils/report-playwright.api.md index 7153f2e578..f286c84f4d 100644 --- a/packages/e2e-test-utils/report-playwright.api.md +++ b/packages/e2e-test-utils/report-playwright.api.md @@ -9,7 +9,9 @@ import { PlaywrightTestConfig } from '@playwright/test'; export function failOnBrowserErrors(): void; // @public -export function generateProjects(): PlaywrightTestConfig['projects']; +export function generateProjects(options?: { + channel?: string; +}): PlaywrightTestConfig['projects']; // (No @packageDocumentation comment for this package) ``` diff --git a/packages/e2e-test-utils/src/playwright/generateProjects.ts b/packages/e2e-test-utils/src/playwright/generateProjects.ts index b6f3d159c5..33a30e2813 100644 --- a/packages/e2e-test-utils/src/playwright/generateProjects.ts +++ b/packages/e2e-test-utils/src/playwright/generateProjects.ts @@ -15,7 +15,7 @@ */ import fs from 'fs-extra'; -import { resolve as resolvePath } from 'path'; +import { resolve as resolvePath } from 'node:path'; import { PlaywrightTestConfig } from '@playwright/test'; import { getPackagesSync } from '@manypkg/get-packages'; import type { BackstagePackage } from '@backstage/cli-node'; @@ -24,8 +24,14 @@ import type { BackstagePackage } from '@backstage/cli-node'; * Generates a list of playwright projects by scanning the monorepo for packages with an `e2e-tests/` folder. * * @public + * + * @param options - Optional configuration for the generated Playwright projects. + * When provided, `options.channel` controls the browser channel used for tests. + * Valid values are listed in the [Playwright documentation](https://playwright.dev/docs/api/class-testoptions#test-options-channel). */ -export function generateProjects(): PlaywrightTestConfig['projects'] { +export function generateProjects(options?: { + channel?: string; +}): PlaywrightTestConfig['projects'] { // TODO(Rugvip): Switch this over to use @backstage/cli-node once released, and support SINCE=origin/main const { root, packages } = getPackagesSync(process.cwd()); const e2eTestPackages = [...(root ? [root] : []), ...packages].filter(pkg => { @@ -36,7 +42,7 @@ export function generateProjects(): PlaywrightTestConfig['projects'] { name: pkg.packageJson.name, testDir: resolvePath(pkg.dir, 'e2e-tests'), use: { - channel: 'chrome', + channel: options?.channel ?? 'chrome', }, })); } diff --git a/packages/e2e-test/CHANGELOG.md b/packages/e2e-test/CHANGELOG.md index a10481c28d..11bf4e058d 100644 --- a/packages/e2e-test/CHANGELOG.md +++ b/packages/e2e-test/CHANGELOG.md @@ -1,5 +1,107 @@ # e2e-test +## 0.2.39 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0 + - @backstage/create-app@0.8.2 + - @backstage/cli-common@0.2.1 + +## 0.2.39-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/create-app@0.8.2-next.2 + - @backstage/cli-common@0.2.1-next.1 + +## 0.2.39-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.2.1-next.0 + - @backstage/create-app@0.8.2-next.0 + - @backstage/errors@1.2.7 + +## 0.2.38 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.2.0 + - @backstage/create-app@0.8.0 + +## 0.2.38-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.2.0-next.0 + - @backstage/create-app@0.7.10-next.0 + - @backstage/errors@1.2.7 + +## 0.2.37 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.7.9 + - @backstage/cli-common@0.1.18 + +## 0.2.37-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.18-next.0 + - @backstage/create-app@0.7.9-next.0 + - @backstage/errors@1.2.7 + +## 0.2.36 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.17 + - @backstage/create-app@0.7.8 + +## 0.2.36-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.7.8-next.0 + - @backstage/cli-common@0.1.16 + - @backstage/errors@1.2.7 + +## 0.2.35 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.7.7 + - @backstage/cli-common@0.1.16 + +## 0.2.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.16-next.0 + - @backstage/create-app@0.7.7-next.0 + - @backstage/errors@1.2.7 + +## 0.2.34 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.7.6 + ## 0.2.34-next.0 ### Patch Changes diff --git a/packages/e2e-test/knip-report.md b/packages/e2e-test/knip-report.md index 516a9d0872..742d40ac0e 100644 --- a/packages/e2e-test/knip-report.md +++ b/packages/e2e-test/knip-report.md @@ -2,7 +2,7 @@ ## Unused dependencies (1) -| Name | Location | Severity | -| :-------------------- | :----------- | :------- | -| @backstage/create-app | packages/e2e-test/package.json | error | +| Name | Location | Severity | +| :---------------- | :---------------- | :------- | +| @backstage/errors | package.json:37:6 | error | diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json index 116c219359..4fa0cd00b8 100644 --- a/packages/e2e-test/package.json +++ b/packages/e2e-test/package.json @@ -1,6 +1,6 @@ { "name": "e2e-test", - "version": "0.2.34-next.0", + "version": "0.2.39", "description": "E2E test for verifying Backstage packages", "backstage": { "role": "cli" @@ -36,7 +36,7 @@ "@backstage/create-app": "workspace:^", "@backstage/errors": "workspace:^", "chalk": "^4.0.0", - "commander": "^12.0.0", + "commander": "^14.0.3", "cross-fetch": "^4.0.0", "fs-extra": "^11.2.0", "handlebars": "^4.7.3", @@ -47,7 +47,7 @@ "devDependencies": { "@backstage/cli": "workspace:^", "@types/fs-extra": "^11.0.0", - "@types/node": "^20.16.0", + "@types/node": "^22.13.14", "nodemon": "^3.0.1" } } diff --git a/packages/e2e-test/src/commands/index.ts b/packages/e2e-test/src/commands/index.ts index fd0ba59c30..cbcb9f3907 100644 --- a/packages/e2e-test/src/commands/index.ts +++ b/packages/e2e-test/src/commands/index.ts @@ -15,12 +15,12 @@ */ import { Command } from 'commander'; -import { run } from './run'; +import { runCommand } from './runCommand'; export function registerCommands(program: Command) { program .command('run') .option('--keep', 'Do not remove the temporary dir after tests complete') .description('Run e2e tests') - .action(run); + .action(runCommand); } diff --git a/packages/e2e-test/src/commands/run.ts b/packages/e2e-test/src/commands/runCommand.ts similarity index 85% rename from packages/e2e-test/src/commands/run.ts rename to packages/e2e-test/src/commands/runCommand.ts index 6df58349df..eeb8798776 100644 --- a/packages/e2e-test/src/commands/run.ts +++ b/packages/e2e-test/src/commands/runCommand.ts @@ -14,39 +14,33 @@ * limitations under the License. */ -import os from 'os'; +import os from 'node:os'; import fs from 'fs-extra'; import fetch from 'cross-fetch'; import handlebars from 'handlebars'; import killTree from 'tree-kill'; -import { resolve as resolvePath, join as joinPath } from 'path'; -import path from 'path'; +import { resolve as resolvePath, join as joinPath } from 'node:path'; +import path from 'node:path'; -import { - spawnPiped, - runPlain, - waitFor, - waitForExit, - print, -} from '../lib/helpers'; +import { waitFor, print } from '../lib/helpers'; import mysql from 'mysql2/promise'; import pgtools from 'pgtools'; -import { findPaths } from '@backstage/cli-common'; import { OptionValues } from 'commander'; +import { findOwnPaths, runOutput, run } from '@backstage/cli-common'; -// eslint-disable-next-line no-restricted-syntax -const paths = findPaths(__dirname); +/* eslint-disable-next-line no-restricted-syntax */ +const ownPaths = findOwnPaths(__dirname); const templatePackagePaths = [ - 'packages/cli/templates/frontend-plugin/package.json.hbs', + 'packages/cli-module-new/templates/frontend-plugin/package.json.hbs', 'packages/create-app/templates/default-app/package.json.hbs', 'packages/create-app/templates/default-app/packages/app/package.json.hbs', 'packages/create-app/templates/default-app/packages/backend/package.json.hbs', ]; -export async function run(opts: OptionValues) { +export async function runCommand(opts: OptionValues) { const rootDir = await fs.mkdtemp(resolvePath(os.tmpdir(), 'backstage-e2e-')); print(`CLI E2E test root: ${rootDir}\n`); @@ -67,21 +61,7 @@ export async function run(opts: OptionValues) { await createPlugin({ appDir, pluginId, select: 'backend-plugin' }); print(`Running 'yarn test:e2e' in newly created app with new plugin`); - await runPlain(['yarn', 'test:e2e'], { - cwd: appDir, - env: { ...process.env, CI: undefined }, - }); - - await switchToReact17(appDir); - - print(`Running 'yarn install' to install React 17`); - await runPlain(['yarn', 'install'], { cwd: appDir }); - - print(`Running 'yarn tsc' with React 17`); - await runPlain(['yarn', 'tsc'], { cwd: appDir }); - - print(`Running 'yarn test:e2e' with React 17`); - await runPlain(['yarn', 'test:e2e'], { + await runOutput(['yarn', 'test:e2e'], { cwd: appDir, env: { ...process.env, CI: undefined }, }); @@ -144,7 +124,7 @@ async function buildDistWorkspace(workspaceName: string, rootDir: string) { } for (const pkgJsonPath of templatePackagePaths) { - const jsonPath = paths.resolveOwnRoot(pkgJsonPath); + const jsonPath = ownPaths.resolveRoot(pkgJsonPath); const pkgTemplate = await fs.readFile(jsonPath, 'utf8'); const pkg = JSON.parse( handlebars.compile(pkgTemplate)( @@ -190,7 +170,7 @@ async function buildDistWorkspace(workspaceName: string, rootDir: string) { appendDeps(require('@backstage/create-app/package.json')); print(`Preparing workspace`); - await runPlain([ + await runOutput([ 'yarn', 'backstage-cli', 'build-workspace', @@ -202,14 +182,14 @@ async function buildDistWorkspace(workspaceName: string, rootDir: string) { print('Pinning yarn version in workspace'); await pinYarnVersion(workspaceDir); - const yarnPatchesPath = paths.resolveOwnRoot('.yarn/patches'); + const yarnPatchesPath = ownPaths.resolveRoot('.yarn/patches'); if (await fs.pathExists(yarnPatchesPath)) { print('Copying yarn patches'); await fs.copy(yarnPatchesPath, resolvePath(workspaceDir, '.yarn/patches')); } print('Installing workspace dependencies'); - await runPlain(['yarn', 'workspaces', 'focus', '--all', '--production'], { + await runOutput(['yarn', 'workspaces', 'focus', '--all', '--production'], { cwd: workspaceDir, }); @@ -220,7 +200,7 @@ async function buildDistWorkspace(workspaceName: string, rootDir: string) { * Pin the yarn version in a directory to the one we're using in the Backstage repo */ async function pinYarnVersion(dir: string) { - const yarnRc = await fs.readFile(paths.resolveOwnRoot('.yarnrc.yml'), 'utf8'); + const yarnRc = await fs.readFile(ownPaths.resolveRoot('.yarnrc.yml'), 'utf8'); const yarnRcLines = yarnRc.split(/\r?\n/); const yarnPathLine = yarnRcLines.find(line => line.startsWith('yarnPath:')); if (!yarnPathLine) { @@ -231,8 +211,8 @@ async function pinYarnVersion(dir: string) { throw new Error(`Invalid 'yarnPath' in ${yarnRc}`); } const [, localYarnPath] = match; - const yarnPath = paths.resolveOwnRoot(localYarnPath); - const yarnPluginPath = paths.resolveOwnRoot( + const yarnPath = ownPaths.resolveRoot(localYarnPath); + const yarnPluginPath = ownPaths.resolveRoot( localYarnPath, '../../plugins/@yarnpkg/plugin-workspace-tools.cjs', ); @@ -257,7 +237,7 @@ async function createApp( workspaceDir: string, rootDir: string, ) { - const child = spawnPiped( + const child = run( [ 'node', resolvePath(workspaceDir, 'packages/create-app/bin/backstage-create-app'), @@ -265,6 +245,7 @@ async function createApp( ], { cwd: rootDir, + stdio: ['pipe', 'pipe', 'pipe'], }, ); @@ -278,7 +259,7 @@ async function createApp( child.stdin?.write(`${appName}\n`); print('Waiting for app create script to be done'); - await waitForExit(child); + await child.waitForExit(); const appDir = resolvePath(rootDir, appName); @@ -318,7 +299,7 @@ async function createApp( 'test:all', ]) { print(`Running 'yarn ${cmd}' in newly created app`); - await runPlain(['yarn', cmd], { cwd: appDir }); + await runOutput(['yarn', cmd], { cwd: appDir }); } return appDir; @@ -333,7 +314,7 @@ async function createApp( */ async function overrideYarnLockSeed(appDir: string) { const content = await fs.readFile( - paths.resolveOwnRoot('packages/create-app/seed-yarn.lock'), + ownPaths.resolveRoot('packages/create-app/seed-yarn.lock'), 'utf8', ); const trimmedContent = content @@ -363,7 +344,9 @@ async function overrideModuleResolutions(appDir: string, workspaceDir: string) { pkgJson.dependencies[name] = `file:${pkgPath}`; pkgJson.resolutions[name] = `file:${pkgPath}`; - delete pkgJson.devDependencies[name]; + if (pkgJson.devDependencies) { + delete pkgJson.devDependencies[name]; + } } } fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 }); @@ -378,21 +361,26 @@ async function createPlugin(options: { select: string; }) { const { appDir, pluginId, select } = options; - const child = spawnPiped( + const child = run( ['yarn', 'new', '--select', select, '--option', `pluginId=${pluginId}`], { cwd: appDir, + stdio: ['pipe', 'pipe', 'pipe'], }, ); try { let stdout = ''; + let stderr = ''; child.stdout?.on('data', (data: Buffer) => { stdout = stdout + data.toString('utf8'); }); + child.stderr?.on('data', (data: Buffer) => { + stderr = stderr + data.toString('utf8'); + }); print('Waiting for plugin create script to be done'); - await waitForExit(child); + await child.waitForExit(); const pluginDir = resolvePath( appDir, @@ -401,48 +389,17 @@ async function createPlugin(options: { ); print(`Running 'yarn tsc' in root for newly created plugin`); - await runPlain(['yarn', 'tsc'], { cwd: appDir }); + await runOutput(['yarn', 'tsc'], { cwd: appDir }); for (const cmd of [['lint'], ['test', '--no-watch']]) { print(`Running 'yarn ${cmd.join(' ')}' in newly created plugin`); - await runPlain(['yarn', ...cmd], { cwd: pluginDir }); + await runOutput(['yarn', ...cmd], { cwd: pluginDir }); } } finally { child.kill(); } } -/** - * Switch the entire project to use React 17 - */ -async function switchToReact17(appDir: string) { - const rootPkg = await fs.readJson(resolvePath(appDir, 'package.json')); - rootPkg.resolutions = { - ...(rootPkg.resolutions || {}), - react: '^17.0.0', - 'react-dom': '^17.0.0', - '@types/react': '^17.0.0', - '@types/react-dom': '^17.0.0', - 'swagger-ui-react/react': '17.0.2', - 'swagger-ui-react/react-dom': '17.0.2', - 'swagger-ui-react/react-redux': '^8', - }; - await fs.writeJson(resolvePath(appDir, 'package.json'), rootPkg, { - spaces: 2, - }); - - await fs.writeFile( - resolvePath(appDir, 'packages/app/src/index.tsx'), - `import '@backstage/cli/asset-types'; -import ReactDOM from 'react-dom'; -import App from './App'; - -ReactDOM.render(, document.getElementById('root')); -`, - 'utf8', - ); -} - /** Drops PG databases */ async function dropDB(database: string, client: string) { try { @@ -494,13 +451,15 @@ async function dropClientDatabases(client: string) { * Start serving the newly created backend and make sure that all db migrations works correctly */ async function testBackendStart(appDir: string, ...args: string[]) { - const child = spawnPiped(['yarn', 'workspace', 'backend', 'start', ...args], { + const child = run(['yarn', 'workspace', 'backend', 'start', ...args], { cwd: appDir, // Windows does not like piping stdin here, the child process will hang when requiring the 'process' module stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env, GITHUB_TOKEN: 'abc', + // Ensure that the backend fails on unhandledRejection errors. + NODE_ENV: 'test', // TODO: Default auth policy is disabled for e2e tests - replace this with external service auth APP_CONFIG_backend_auth_dangerouslyDisableDefaultAuthPolicy: 'true', }, @@ -586,7 +545,7 @@ async function testBackendStart(appDir: string, ...args: string[]) { } try { - await waitForExit(child); + await child.waitForExit(); } catch (error) { if (!successful) { throw new Error(`Backend failed to startup: ${stderr}`); diff --git a/packages/e2e-test/src/index.ts b/packages/e2e-test/src/index.ts index f3c63ae175..df299628b2 100644 --- a/packages/e2e-test/src/index.ts +++ b/packages/e2e-test/src/index.ts @@ -18,7 +18,6 @@ import { program } from 'commander'; import chalk from 'chalk'; import { registerCommands } from './commands'; import { version } from '../package.json'; -import { exitWithError } from './lib/helpers'; async function main(argv: string[]) { program.name('e2e-test').version(version); @@ -36,4 +35,12 @@ async function main(argv: string[]) { program.parse(argv); } -main(process.argv).catch(exitWithError); +main(process.argv).catch(err => { + process.stdout.write(`${err.name}: ${err.stack || err.message}\n`); + + if (typeof err.code === 'number') { + process.exit(err.code); + } else { + process.exit(1); + } +}); diff --git a/packages/e2e-test/src/lib/helpers.ts b/packages/e2e-test/src/lib/helpers.ts index fc5aefcff9..d310812794 100644 --- a/packages/e2e-test/src/lib/helpers.ts +++ b/packages/e2e-test/src/lib/helpers.ts @@ -14,77 +14,6 @@ * limitations under the License. */ -import { assertError } from '@backstage/errors'; -import { - spawn, - execFile as execFileCb, - SpawnOptions, - ChildProcess, -} from 'child_process'; -import { promisify } from 'util'; - -const execFile = promisify(execFileCb); - -export function spawnPiped(cmd: string[], options?: SpawnOptions) { - function pipeWithPrefix(stream: NodeJS.WriteStream, prefix = '') { - return (data: Buffer) => { - const prefixedMsg = data - .toString('utf8') - .trimEnd() - .replace(/^/gm, prefix); - stream.write(`${prefixedMsg}\n`, 'utf8'); - }; - } - - const child = spawn(cmd[0], cmd.slice(1), { - stdio: 'pipe', - shell: true, - ...options, - }); - child.on('error', exitWithError); - - const logPrefix = cmd.map(s => s.replace(/.+\//, '')).join(' '); - child.stdout?.on( - 'data', - pipeWithPrefix(process.stdout, `[${logPrefix}].out: `), - ); - child.stderr?.on( - 'data', - pipeWithPrefix(process.stderr, `[${logPrefix}].err: `), - ); - - return child; -} - -export async function runPlain(cmd: string[], options?: SpawnOptions) { - try { - const { stdout } = await execFile(cmd[0], cmd.slice(1), { - ...options, - shell: true, - }); - return stdout.trim(); - } catch (error) { - assertError(error); - if (error.stdout) { - process.stdout.write(error.stdout as Buffer); - } - if (error.stderr) { - process.stderr.write(error.stderr as Buffer); - } - throw error; - } -} - -export function exitWithError(err: Error & { code?: unknown }) { - process.stdout.write(`${err.name}: ${err.stack || err.message}\n`); - - if (typeof err.code === 'number') { - process.exit(err.code); - } else { - process.exit(1); - } -} - /** * Waits for fn() to be true * Checks every 100ms @@ -108,22 +37,6 @@ export function waitFor(fn: () => boolean, maxSeconds: number = 120) { }); } -export async function waitForExit(child: ChildProcess) { - if (child.exitCode !== null) { - throw new Error(`Child already exited with code ${child.exitCode}`); - } - await new Promise((resolve, reject) => - child.once('exit', code => { - if (code) { - reject(new Error(`Child exited with code ${code}`)); - } else { - print('Child finished'); - resolve(); - } - }), - ); -} - export function print(msg: string) { return process.stdout.write(`${msg}\n`); } diff --git a/packages/errors/CHANGELOG.md b/packages/errors/CHANGELOG.md index fa187b9229..fdccdfa84e 100644 --- a/packages/errors/CHANGELOG.md +++ b/packages/errors/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/errors +## 1.3.0 + +### Minor Changes + +- b2319ff: A new `toError` utility function is now available for converting unknown values to `ErrorLike` objects. If the value is already error-like it is returned as-is, strings are used directly as the error message, and all other values are wrapped as `unknown error ''`. Non-error causes passed to `CustomErrorBase` are now converted and stored using `toError` rather than discarded. + +### Patch Changes + +- 608c1e5: Simplified `assertError` to delegate to `isError` instead of duplicating the same checks. + +## 1.3.0-next.0 + +### Minor Changes + +- b2319ff: A new `toError` utility function is now available for converting unknown values to `ErrorLike` objects. If the value is already error-like it is returned as-is, strings are used directly as the error message, and all other values are wrapped as `unknown error ''`. Non-error causes passed to `CustomErrorBase` are now converted and stored using `toError` rather than discarded. + ## 1.2.7 ### Patch Changes diff --git a/packages/errors/package.json b/packages/errors/package.json index d90e791d4c..a1836e0854 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/errors", - "version": "1.2.7", + "version": "1.3.0", "description": "Common utilities for error handling within Backstage", "backstage": { "role": "common-library" diff --git a/packages/errors/report.api.md b/packages/errors/report.api.md index 894aac8413..5026f8d872 100644 --- a/packages/errors/report.api.md +++ b/packages/errors/report.api.md @@ -151,8 +151,14 @@ export function serializeError( ): SerializedError; // @public -export class ServiceUnavailableError extends CustomErrorBase {} +export class ServiceUnavailableError extends CustomErrorBase { + // (undocumented) + name: 'ServiceUnavailableError'; +} // @public export function stringifyError(error: unknown): string; + +// @public +export function toError(value: unknown): ErrorLike; ``` diff --git a/packages/errors/src/errors/CustomErrorBase.ts b/packages/errors/src/errors/CustomErrorBase.ts index ea9c91f173..d2bf6411a9 100644 --- a/packages/errors/src/errors/CustomErrorBase.ts +++ b/packages/errors/src/errors/CustomErrorBase.ts @@ -14,8 +14,7 @@ * limitations under the License. */ -import { stringifyError } from '../serialization/error'; -import { isError } from './assertion'; +import { toError } from './assertion'; /** * A base class that custom Error classes can inherit from. @@ -42,13 +41,19 @@ export class CustomErrorBase extends Error { readonly cause?: Error | undefined; constructor(message?: string, cause?: Error | unknown) { + const causeError = cause !== undefined ? toError(cause) : undefined; + let fullMessage = message; - if (cause !== undefined) { - const causeStr = stringifyError(cause); + if (causeError !== undefined) { + const causeStr = String(causeError); + const causeMsg = + causeStr !== '[object Object]' + ? causeStr + : `${causeError.name}: ${causeError.message}`; if (fullMessage) { - fullMessage += `; caused by ${causeStr}`; + fullMessage += `; caused by ${causeMsg}`; } else { - fullMessage = `caused by ${causeStr}`; + fullMessage = `caused by ${causeMsg}`; } } @@ -63,6 +68,6 @@ export class CustomErrorBase extends Error { } } - this.cause = isError(cause) ? cause : undefined; + this.cause = causeError; } } diff --git a/packages/errors/src/errors/assertion.test.ts b/packages/errors/src/errors/assertion.test.ts index 28367c2a68..783a625518 100644 --- a/packages/errors/src/errors/assertion.test.ts +++ b/packages/errors/src/errors/assertion.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { assertError, isError } from './assertion'; +import { assertError, isError, toError } from './assertion'; import { NotFoundError } from './common'; import { CustomErrorBase } from './CustomErrorBase'; @@ -77,3 +77,93 @@ describe('isError', () => { }, ); }); + +describe('toError', () => { + it.each(areErrors)( + 'should pass through error-like values as-is %#', + error => { + expect(toError(error)).toBe(error); + }, + ); + + it('should preserve the original error instance', () => { + const original = new NotFoundError('not found'); + expect(toError(original)).toBe(original); + }); + + it('should use strings directly as the error message', () => { + const result = toError('something went wrong'); + expect(result).toBeInstanceOf(Error); + expect(result.message).toBe('something went wrong'); + }); + + it('should handle empty strings', () => { + const result = toError(''); + expect(result).toBeInstanceOf(Error); + expect(result.message).toBe(''); + }); + + it('should wrap undefined', () => { + const result = toError(undefined); + expect(result).toBeInstanceOf(Error); + expect(result.message).toBe("unknown error 'undefined'"); + }); + + it('should wrap null', () => { + const result = toError(null); + expect(result).toBeInstanceOf(Error); + expect(result.message).toBe("unknown error 'null'"); + }); + + it('should wrap numbers', () => { + expect(toError(0).message).toBe("unknown error '0'"); + expect(toError(42).message).toBe("unknown error '42'"); + }); + + it('should wrap booleans', () => { + expect(toError(false).message).toBe("unknown error 'false'"); + expect(toError(true).message).toBe("unknown error 'true'"); + }); + + it('should wrap plain objects', () => { + expect(toError({ name: 'e' }).message).toBe( + "unknown error '[object Object]'", + ); + expect(toError({}).message).toBe("unknown error '[object Object]'"); + }); + + it('should wrap arrays', () => { + expect(toError([]).message).toBe("unknown error ''"); + expect(toError([1, 2]).message).toBe("unknown error '1,2'"); + }); + + it('should handle objects with a custom toString', () => { + const obj = { toString: () => 'custom string' }; + expect(toError(obj).message).toBe("unknown error 'custom string'"); + }); + + it('should handle symbols', () => { + const result = toError(Symbol('test')); + expect(result).toBeInstanceOf(Error); + expect(result.message).toBe("unknown error of type 'symbol'"); + }); + + it('should handle BigInt', () => { + expect(toError(BigInt(42)).message).toBe("unknown error '42'"); + }); + + it('should not throw for objects with a throwing toString', () => { + const obj = Object.create(null); + const result = toError(obj); + expect(result).toBeInstanceOf(Error); + expect(result.message).toBe("unknown error of type 'object'"); + }); + + it('should not throw for circular objects', () => { + const obj: { self?: unknown } = {}; + obj.self = obj; + const result = toError(obj); + expect(result).toBeInstanceOf(Error); + expect(result.message).toBe("unknown error '[object Object]'"); + }); +}); diff --git a/packages/errors/src/errors/assertion.ts b/packages/errors/src/errors/assertion.ts index 7ba0ae6984..ada83e4c86 100644 --- a/packages/errors/src/errors/assertion.ts +++ b/packages/errors/src/errors/assertion.ts @@ -58,16 +58,33 @@ export function isError(value: unknown): value is ErrorLike { * @param value - an unknown value */ export function assertError(value: unknown): asserts value is ErrorLike { - if (typeof value !== 'object' || value === null || Array.isArray(value)) { - throw new Error(`Encountered invalid error, not an object, got '${value}'`); - } - const maybe = value as Partial; - if (typeof maybe.name !== 'string' || maybe.name === '') { - throw new Error(`Encountered error object without a name, got '${value}'`); - } - if (typeof maybe.message !== 'string') { - throw new Error( - `Encountered error object without a message, got '${value}'`, - ); + if (!isError(value)) { + throw new Error(`Encountered invalid error, got '${value}'`); + } +} + +/** + * Converts an unknown value to an {@link ErrorLike} object. + * + * If the value is already an {@link ErrorLike} object, it is returned as-is. + * If the value is a string, a new `Error` is created with that string as the message. + * For all other values, a new `Error` is created with a message of the form + * `unknown error ''`. + * + * @public + * @param value - an unknown value + * @returns an {@link ErrorLike} object + */ +export function toError(value: unknown): ErrorLike { + if (isError(value)) { + return value; + } + if (typeof value === 'string') { + return new Error(value) as ErrorLike; + } + try { + return new Error(`unknown error '${value}'`) as ErrorLike; + } catch { + return new Error(`unknown error of type '${typeof value}'`) as ErrorLike; } } diff --git a/packages/errors/src/errors/common.test.ts b/packages/errors/src/errors/common.test.ts index dfa9743616..4c496614ab 100644 --- a/packages/errors/src/errors/common.test.ts +++ b/packages/errors/src/errors/common.test.ts @@ -23,7 +23,7 @@ describe('common', () => { const error = new E('abcdef'); expect(error.name).toBe(name); expect(error.message).toBe('abcdef'); - expect(error.stack).toContain(__filename); + expect(error.stack).toContain('common.test.ts'); expect(error.toString()).toContain(name); expect(error.toString()).toContain('abcdef'); } diff --git a/packages/errors/src/errors/common.ts b/packages/errors/src/errors/common.ts index 861c3a7c3a..a1a9cfbf2d 100644 --- a/packages/errors/src/errors/common.ts +++ b/packages/errors/src/errors/common.ts @@ -100,7 +100,9 @@ export class NotImplementedError extends CustomErrorBase { * * @public */ -export class ServiceUnavailableError extends CustomErrorBase {} +export class ServiceUnavailableError extends CustomErrorBase { + name = 'ServiceUnavailableError' as const; +} /** * An error that forwards an underlying cause with additional context in the message. diff --git a/packages/errors/src/errors/index.ts b/packages/errors/src/errors/index.ts index 0a59891f87..0c74c4ab4f 100644 --- a/packages/errors/src/errors/index.ts +++ b/packages/errors/src/errors/index.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -export { assertError, isError } from './assertion'; +export { assertError, isError, toError } from './assertion'; export type { ErrorLike } from './assertion'; export { AuthenticationError, diff --git a/packages/errors/src/serialization/error.test.ts b/packages/errors/src/serialization/error.test.ts index 6418170f98..2c462610da 100644 --- a/packages/errors/src/serialization/error.test.ts +++ b/packages/errors/src/serialization/error.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { NotModifiedError } from '../errors'; +import { NotModifiedError, ServiceUnavailableError } from '../errors'; import { deserializeError, serializeError, stringifyError } from './error'; class CustomError extends Error { @@ -77,6 +77,16 @@ describe('serialization', () => { expect(withoutStack2.cause.stack).not.toBeDefined(); }); + it('round-trips a ServiceUnavailableError', () => { + const before = new ServiceUnavailableError('service down'); + const after = deserializeError( + serializeError(before, { includeStack: true }), + ); + expect(after.name).toEqual('ServiceUnavailableError'); + expect(after.message).toEqual('service down'); + expect(after.stack).toEqual(before.stack); + }); + it('stringifies all supported forms', () => { expect(stringifyError({})).toEqual("unknown error '[object Object]'"); expect( diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 65acedd942..e7160fafeb 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -1,5 +1,47 @@ # @backstage/eslint-plugin +## 0.2.3 + +### Patch Changes + +- df43b0e: Fixed `no-mixed-plugin-imports` rule to return `null` from non-fixable suggestion handlers and added an explicit `SuggestionReportDescriptor[]` type annotation, matching the stricter type checking in TypeScript 6.0. + +## 0.2.3-next.0 + +### Patch Changes + +- df43b0e: Fixed `no-mixed-plugin-imports` rule to return `null` from non-fixable suggestion handlers and added an explicit `SuggestionReportDescriptor[]` type annotation, matching the stricter type checking in TypeScript 6.0. + +## 0.2.2 + +### Patch Changes + +- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1 + +## 0.2.2-next.0 + +### Patch Changes + +- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1 + +## 0.2.1 + +### Patch Changes + +- 7455dae: Use node prefix on native imports + +## 0.2.1-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports + +## 0.2.0 + +### Minor Changes + +- 926389b: Added `@backstage/no-ui-css-imports-in-non-frontend` rule, which ensures that CSS from `@backstage/ui` is not imported outside of the frontend app. + ## 0.2.0-next.0 ### Minor Changes diff --git a/packages/eslint-plugin/lib/getPackages.js b/packages/eslint-plugin/lib/getPackages.js index 68739d358b..d4ed1b6372 100644 --- a/packages/eslint-plugin/lib/getPackages.js +++ b/packages/eslint-plugin/lib/getPackages.js @@ -16,7 +16,7 @@ // @ts-check -const path = require('path'); +const path = require('node:path'); const manypkg = require('@manypkg/get-packages'); /** diff --git a/packages/eslint-plugin/lib/visitImports.js b/packages/eslint-plugin/lib/visitImports.js index 376c37575f..85c5ac0568 100644 --- a/packages/eslint-plugin/lib/visitImports.js +++ b/packages/eslint-plugin/lib/visitImports.js @@ -16,7 +16,7 @@ // @ts-check -const { builtinModules } = require('module'); +const { builtinModules } = require('node:module'); const getPackages = require('./getPackages'); /** diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index f7dc9eebcc..cd10f347d7 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/eslint-plugin", - "version": "0.2.0-next.0", + "version": "0.2.3", "description": "Backstage ESLint plugin", "publishConfig": { "access": "public" @@ -19,7 +19,7 @@ }, "dependencies": { "@manypkg/get-packages": "^1.1.3", - "minimatch": "^9.0.0" + "minimatch": "^10.2.1" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/packages/eslint-plugin/rules/no-mixed-plugin-imports.js b/packages/eslint-plugin/rules/no-mixed-plugin-imports.js index 9f00d38014..ecc9605d79 100644 --- a/packages/eslint-plugin/rules/no-mixed-plugin-imports.js +++ b/packages/eslint-plugin/rules/no-mixed-plugin-imports.js @@ -173,6 +173,7 @@ module.exports = { !ignoreTargetPackages.includes(targetName), ) ) { + /** @type {import('eslint').Rule.SuggestionReportDescriptor[]} */ const suggest = []; if ( @@ -187,9 +188,9 @@ module.exports = { sourcePackage: sourceName, sourceRole: sourceRole, }, - /** @param {import('eslint').Rule.RuleFixer} _fixer */ - fix(_fixer) { + fix() { // Not a fixable case, just give a suggestion to change plugin id + return null; }, }); suggest.push({ @@ -197,7 +198,6 @@ module.exports = { data: { targetPackage: targetName, }, - /** @param {import('eslint').Rule.RuleFixer} fixer */ fix(fixer) { const source = context.sourceCode; const nodeSource = source.getText(imp.node); @@ -210,7 +210,6 @@ module.exports = { data: { targetPackage: targetName, }, - /** @param {import('eslint').Rule.RuleFixer} fixer */ fix(fixer) { const source = context.sourceCode; const nodeSource = source.getText(imp.node); @@ -228,7 +227,6 @@ module.exports = { data: { targetPackage: targetName, }, - /** @param {import('eslint').Rule.RuleFixer} fixer */ fix(fixer) { const source = context.sourceCode; const nodeSource = source.getText(imp.node); @@ -241,7 +239,6 @@ module.exports = { data: { targetPackage: targetName, }, - /** @param {import('eslint').Rule.RuleFixer} fixer */ fix(fixer) { const source = context.sourceCode; const nodeSource = source.getText(imp.node); @@ -252,9 +249,9 @@ module.exports = { } else { suggest.push({ messageId: 'removeImport', - /** @param {import('eslint').Rule.RuleFixer} _fixer */ - fix(_fixer) { - // Not a fixable case, just give a suggestion to remove the import + fix() { + // Not a fixable case, just give a suggestion to change plugin id + return null; }, }); } diff --git a/packages/eslint-plugin/rules/no-relative-monorepo-imports.js b/packages/eslint-plugin/rules/no-relative-monorepo-imports.js index df30d35f67..107e21049f 100644 --- a/packages/eslint-plugin/rules/no-relative-monorepo-imports.js +++ b/packages/eslint-plugin/rules/no-relative-monorepo-imports.js @@ -16,7 +16,7 @@ // @ts-check -const path = require('path'); +const path = require('node:path'); const visitImports = require('../lib/visitImports'); const getPackageMap = require('../lib/getPackages'); diff --git a/packages/eslint-plugin/rules/no-undeclared-imports.js b/packages/eslint-plugin/rules/no-undeclared-imports.js index 17331d2fce..6ca02d49f7 100644 --- a/packages/eslint-plugin/rules/no-undeclared-imports.js +++ b/packages/eslint-plugin/rules/no-undeclared-imports.js @@ -16,11 +16,11 @@ // @ts-check -const path = require('path'); +const path = require('node:path'); const getPackageMap = require('../lib/getPackages'); const visitImports = require('../lib/visitImports'); const minimatch = require('minimatch'); -const { execFileSync } = require('child_process'); +const { execFileSync } = require('node:child_process'); const depFields = /** @type {const} */ ({ dep: 'dependencies', diff --git a/packages/eslint-plugin/src/no-forbidden-package-imports.test.ts b/packages/eslint-plugin/src/no-forbidden-package-imports.test.ts index c46f82f4fc..c7407cd388 100644 --- a/packages/eslint-plugin/src/no-forbidden-package-imports.test.ts +++ b/packages/eslint-plugin/src/no-forbidden-package-imports.test.ts @@ -15,7 +15,7 @@ */ import { RuleTester } from 'eslint'; -import path from 'path'; +import path from 'node:path'; import rule from '../rules/no-forbidden-package-imports'; const RULE = 'no-forbidden-package-imports'; diff --git a/packages/eslint-plugin/src/no-mixed-plugin-imports.test.ts b/packages/eslint-plugin/src/no-mixed-plugin-imports.test.ts index 45764ca71a..1c97ad2767 100644 --- a/packages/eslint-plugin/src/no-mixed-plugin-imports.test.ts +++ b/packages/eslint-plugin/src/no-mixed-plugin-imports.test.ts @@ -15,7 +15,7 @@ */ import { RuleTester } from 'eslint'; -import path from 'path'; +import path from 'node:path'; import rule from '../rules/no-mixed-plugin-imports'; const RULE = 'no-mixed-plugin-imports'; diff --git a/packages/eslint-plugin/src/no-relative-monorepo-imports.test.ts b/packages/eslint-plugin/src/no-relative-monorepo-imports.test.ts index 31e1674b76..6ffa66ccab 100644 --- a/packages/eslint-plugin/src/no-relative-monorepo-imports.test.ts +++ b/packages/eslint-plugin/src/no-relative-monorepo-imports.test.ts @@ -15,7 +15,7 @@ */ import { RuleTester } from 'eslint'; -import path from 'path'; +import path from 'node:path'; import rule from '../rules/no-relative-monorepo-imports'; const RULE = 'no-relative-monorepo-imports'; diff --git a/packages/eslint-plugin/src/no-ui-css-imports-in-non-frontend.test.ts b/packages/eslint-plugin/src/no-ui-css-imports-in-non-frontend.test.ts index 373659ae6e..9e8ded54bc 100644 --- a/packages/eslint-plugin/src/no-ui-css-imports-in-non-frontend.test.ts +++ b/packages/eslint-plugin/src/no-ui-css-imports-in-non-frontend.test.ts @@ -15,7 +15,7 @@ */ import { RuleTester } from 'eslint'; -import path from 'path'; +import path from 'node:path'; import rule from '../rules/no-ui-css-imports-in-non-frontend'; const RULE = 'no-ui-css-imports-in-non-frontend'; diff --git a/packages/eslint-plugin/src/no-undeclared-imports.test.ts b/packages/eslint-plugin/src/no-undeclared-imports.test.ts index 7127d2f344..0021dd0636 100644 --- a/packages/eslint-plugin/src/no-undeclared-imports.test.ts +++ b/packages/eslint-plugin/src/no-undeclared-imports.test.ts @@ -15,7 +15,7 @@ */ import { RuleTester } from 'eslint'; -import { join as joinPath } from 'path'; +import { join as joinPath } from 'node:path'; import rule from '../rules/no-undeclared-imports'; jest.mock('child_process', () => ({ diff --git a/packages/filter-predicates/.eslintrc.js b/packages/filter-predicates/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/packages/filter-predicates/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/packages/filter-predicates/CHANGELOG.md b/packages/filter-predicates/CHANGELOG.md new file mode 100644 index 0000000000..646732a764 --- /dev/null +++ b/packages/filter-predicates/CHANGELOG.md @@ -0,0 +1,36 @@ +# @backstage/filter-predicates + +## 0.1.2 + +### Patch Changes + +- 8923d6d: Added `createZodV4FilterPredicateSchema` as a zod v4 counterpart to `createZodV3FilterPredicateSchema`. +- Updated dependencies + - @backstage/errors@1.3.0 + - @backstage/config@1.3.7 + +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/config@1.3.7-next.0 + +## 0.1.1 + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. + +## 0.1.0 + +### Minor Changes + +- 7feb83b: Introduced package, basically as the extracted predicate types from `@backstage/plugin-catalog-react/alpha` + +## 0.1.0-next.0 + +### Minor Changes + +- 7feb83b: Introduced package, basically as the extracted predicate types from `@backstage/plugin-catalog-react/alpha` diff --git a/packages/filter-predicates/README.md b/packages/filter-predicates/README.md new file mode 100644 index 0000000000..e8f70fe5aa --- /dev/null +++ b/packages/filter-predicates/README.md @@ -0,0 +1,18 @@ +# @backstage/filter-predicates + +Contains types and implementations related to the concept of +[filter predicate expressions](https://backstage.io/docs/features/software-catalog/catalog-customization#entity-predicate-queries). + +These allow you to uniformly express filters, including logical operators and +advanced matchers, for filtering through structured data. + +Example: + +```json +{ + "filter": { + "kind": "Component", + "spec.type": { "$in": ["service", "website"] } + } +} +``` diff --git a/packages/filter-predicates/catalog-info.yaml b/packages/filter-predicates/catalog-info.yaml new file mode 100644 index 0000000000..63be7ba015 --- /dev/null +++ b/packages/filter-predicates/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-filter-predicates + title: '@backstage/filter-predicates' + description: A library for expressing filter predicates and evaluating them against values +spec: + lifecycle: experimental + type: backstage-common-library + owner: framework-maintainers diff --git a/packages/filter-predicates/knip-report.md b/packages/filter-predicates/knip-report.md new file mode 100644 index 0000000000..2661c35327 --- /dev/null +++ b/packages/filter-predicates/knip-report.md @@ -0,0 +1,2 @@ +# Knip report + diff --git a/packages/filter-predicates/package.json b/packages/filter-predicates/package.json new file mode 100644 index 0000000000..1c9fae6665 --- /dev/null +++ b/packages/filter-predicates/package.json @@ -0,0 +1,48 @@ +{ + "name": "@backstage/filter-predicates", + "version": "0.1.2", + "description": "A library for expressing filter predicates and evaluating them against values", + "backstage": { + "role": "common-library" + }, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "module": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "keywords": [ + "backstage" + ], + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "packages/filter-predicates" + }, + "license": "Apache-2.0", + "sideEffects": false, + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "backstage-cli package build", + "clean": "backstage-cli package clean", + "lint": "backstage-cli package lint", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "test": "backstage-cli package test" + }, + "dependencies": { + "@backstage/config": "workspace:^", + "@backstage/errors": "workspace:^", + "@backstage/types": "workspace:^", + "zod": "^3.25.76 || ^4.0.0", + "zod-validation-error": "^4.0.2" + }, + "devDependencies": { + "@backstage/cli": "workspace:^" + } +} diff --git a/packages/filter-predicates/report.api.md b/packages/filter-predicates/report.api.md new file mode 100644 index 0000000000..0aea83072f --- /dev/null +++ b/packages/filter-predicates/report.api.md @@ -0,0 +1,114 @@ +## API Report File for "@backstage/filter-predicates" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { Config } from '@backstage/config'; +import { JsonValue } from '@backstage/types'; +import { z } from 'zod/v4'; +import * as zodV3 from 'zod/v3'; + +// @public +export function createZodV3FilterPredicateSchema( + z: typeof zodV3.z, +): zodV3.ZodType; + +// @public +export function createZodV4FilterPredicateSchema(): z.ZodType< + FilterPredicate, + FilterPredicate +>; + +// @public +export function evaluateFilterPredicate( + predicate: FilterPredicate, + value: unknown, +): boolean; + +// @public +export type FilterPredicate = + | FilterPredicateExpression + | FilterPredicatePrimitive + | { + $all: FilterPredicate[]; + } + | { + $any: FilterPredicate[]; + } + | { + $not: FilterPredicate; + } + | UnknownFilterPredicateOperator; + +// @public +export type FilterPredicateExpression = { + [KPath in string]: FilterPredicateValue; +} & { + [KPath in `$${string}`]: never; +}; + +// @public +export type FilterPredicatePrimitive = string | number | boolean; + +// @public +export function filterPredicateToFilterFunction( + predicate: FilterPredicate, +): (value: T) => boolean; + +// @public +export type FilterPredicateValue = + | FilterPredicatePrimitive + | { + $exists: boolean; + } + | { + $in: FilterPredicatePrimitive[]; + } + | { + $contains: FilterPredicate; + } + | { + $hasPrefix: string; + } + | UnknownFilterPredicateValueMatcher; + +// @public +export function getJsonValueAtPath( + value: JsonValue | undefined, + path: string, +): JsonValue | undefined; + +// @public +export function parseFilterPredicate(value: unknown): FilterPredicate; + +// @public +export function readFilterPredicateFromConfig( + config: Config, + options?: ReadFilterPredicateFromConfigOptions, +): FilterPredicate; + +// @public +export interface ReadFilterPredicateFromConfigOptions { + key?: string; +} + +// @public +export function readOptionalFilterPredicateFromConfig( + config: Config, + options?: ReadFilterPredicateFromConfigOptions, +): FilterPredicate | undefined; + +// @public +export type UnknownFilterPredicateOperator = { + [KOperator in `$${string}`]: JsonValue; +} & { + [KOperator in '$all' | '$any' | '$not']: never; +}; + +// @public +export type UnknownFilterPredicateValueMatcher = { + [KMatcher in `$${string}`]: JsonValue; +} & { + [KMatcher in '$exists' | '$in' | '$contains' | '$hasPrefix']: never; +}; +``` diff --git a/plugins/scaffolder-backend-module-bitbucket/src/index.ts b/packages/filter-predicates/src/index.ts similarity index 74% rename from plugins/scaffolder-backend-module-bitbucket/src/index.ts rename to packages/filter-predicates/src/index.ts index 7f68a3f395..414b4127a5 100644 --- a/plugins/scaffolder-backend-module-bitbucket/src/index.ts +++ b/packages/filter-predicates/src/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2025 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,10 +15,9 @@ */ /** - * A module for the scaffolder backend that lets you interact with bitbucket + * Contains types and implementations related to the concept of filter predicate expressions. * * @packageDocumentation */ -export * from './deprecated'; -export { bitbucketModule as default } from './module'; +export * from './predicates'; diff --git a/packages/filter-predicates/src/predicates/config.test.ts b/packages/filter-predicates/src/predicates/config.test.ts new file mode 100644 index 0000000000..494e707f40 --- /dev/null +++ b/packages/filter-predicates/src/predicates/config.test.ts @@ -0,0 +1,112 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ConfigReader } from '@backstage/config'; +import { + readFilterPredicateFromConfig, + readOptionalFilterPredicateFromConfig, +} from './config'; + +describe('readFilterPredicateFromConfig', () => { + it('should read a filter predicate from config', () => { + const config = new ConfigReader({ + predicate: { kind: 'component', 'spec.type': 'service' }, + }); + + const result = readFilterPredicateFromConfig(config, { key: 'predicate' }); + + expect(result).toEqual({ kind: 'component', 'spec.type': 'service' }); + }); + + it('should read a filter predicate from the root config', () => { + const config = new ConfigReader({ + kind: 'component', + 'spec.type': 'service', + }); + + const result = readFilterPredicateFromConfig(config); + + expect(result).toEqual({ kind: 'component', 'spec.type': 'service' }); + }); + + it('should throw when filter predicate is missing', () => { + const config = new ConfigReader({}); + + expect(() => + readFilterPredicateFromConfig(config, { key: 'predicate' }), + ).toThrow(/predicate/); + }); + + it('should throw when filter predicate is invalid', () => { + const config = new ConfigReader({ + predicate: { kind: { $invalid: 'foo' } }, + }); + + expect(() => + readFilterPredicateFromConfig(config, { key: 'predicate' }), + ).toThrow(/Could not read filter predicate from config at 'predicate':/); + }); +}); + +describe('readOptionalFilterPredicateFromConfig', () => { + it('should read a filter predicate from config', () => { + const config = new ConfigReader({ + predicate: { kind: 'component' }, + }); + + const result = readOptionalFilterPredicateFromConfig(config, { + key: 'predicate', + }); + + expect(result).toEqual({ kind: 'component' }); + }); + + it('should return undefined when filter predicate is missing', () => { + const config = new ConfigReader({}); + + const result = readOptionalFilterPredicateFromConfig(config, { + key: 'predicate', + }); + + expect(result).toBeUndefined(); + }); + + it('should throw when filter predicate is invalid', () => { + const config = new ConfigReader({ + predicate: { kind: { $invalid: 'foo' } }, + }); + + expect(() => + readOptionalFilterPredicateFromConfig(config, { key: 'predicate' }), + ).toThrow(/Could not read filter predicate from config at 'predicate':/); + }); + + it('should read complex filter predicates', () => { + const config = new ConfigReader({ + filter: { + $any: [{ kind: 'component', 'spec.type': 'service' }, { kind: 'api' }], + }, + }); + + const result = readOptionalFilterPredicateFromConfig(config, { + key: 'filter', + }); + + expect(result).toEqual({ + $any: [{ kind: 'component', 'spec.type': 'service' }, { kind: 'api' }], + }); + }); +}); diff --git a/packages/filter-predicates/src/predicates/config.ts b/packages/filter-predicates/src/predicates/config.ts new file mode 100644 index 0000000000..12f0620523 --- /dev/null +++ b/packages/filter-predicates/src/predicates/config.ts @@ -0,0 +1,75 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Config } from '@backstage/config'; +import { InputError, stringifyError } from '@backstage/errors'; +import { parseFilterPredicate } from './schema'; +import { FilterPredicate } from './types'; + +/** + * Options for {@link readFilterPredicateFromConfig} and {@link readOptionalFilterPredicateFromConfig}. + * + * @public + */ +export interface ReadFilterPredicateFromConfigOptions { + /** + * The key to read from the config. If not provided, the entire config is used. + */ + key?: string; +} + +/** + * Read a filter predicate expression from a config object. + * + * @public + */ +export function readFilterPredicateFromConfig( + config: Config, + options?: ReadFilterPredicateFromConfigOptions, +): FilterPredicate { + const key = options?.key; + const value = key ? config.get(key) : config.get(); + + try { + return parseFilterPredicate(value); + } catch (error) { + const where = key ? ` at '${key}'` : ''; + throw new InputError( + `Could not read filter predicate from config${where}: ${stringifyError( + error, + )}`, + ); + } +} + +/** + * Read an optional filter predicate expression from a config object. + * + * @public + */ +export function readOptionalFilterPredicateFromConfig( + config: Config, + options?: ReadFilterPredicateFromConfigOptions, +): FilterPredicate | undefined { + const key = options?.key; + const value = key ? config.getOptional(key) : config.getOptional(); + + if (value === undefined) { + return undefined; + } + + return readFilterPredicateFromConfig(config, options); +} diff --git a/plugins/catalog-react/src/alpha/predicates/entityPredicateToFilterFunction.test.ts b/packages/filter-predicates/src/predicates/evaluate.test.ts similarity index 79% rename from plugins/catalog-react/src/alpha/predicates/entityPredicateToFilterFunction.test.ts rename to packages/filter-predicates/src/predicates/evaluate.test.ts index 152230de7c..6eb54c3c92 100644 --- a/plugins/catalog-react/src/alpha/predicates/entityPredicateToFilterFunction.test.ts +++ b/packages/filter-predicates/src/predicates/evaluate.test.ts @@ -14,10 +14,13 @@ * limitations under the License. */ -import { entityPredicateToFilterFunction } from './entityPredicateToFilterFunction'; -import { EntityPredicate } from './types'; +import { + evaluateFilterPredicate, + filterPredicateToFilterFunction, +} from './evaluate'; +import { FilterPredicate } from './types'; -describe('entityPredicateToFilterFunction', () => { +describe('evaluate', () => { const entities = [ { apiVersion: 'backstage.io/v1alpha1', @@ -129,7 +132,7 @@ describe('entityPredicateToFilterFunction', () => { }, ]; - it.each([ + describe.each([ ['s', { kind: 'component', 'spec.type': 'service' }], ['s', { 'metadata.tags': { $contains: 'java' } }], [ @@ -186,7 +189,7 @@ describe('entityPredicateToFilterFunction', () => { metadata: { $contains: { name: 'a' } }, }, ], - ['', { $unknown: 'ignored' } as unknown as EntityPredicate], + ['', { $unknown: 'ignored' } as unknown as FilterPredicate], [ 's,w', { kind: 'component', 'spec.type': { $in: ['service', 'website'] } }, @@ -234,12 +237,33 @@ describe('entityPredicateToFilterFunction', () => { 'metadata.annotations.github.com/repo': { $exists: true }, }, ], + ['a', { 'spec.type': { $hasPrefix: 'g' } }], ])('filter entry %#', (expected, filter) => { - const filtered = entities.filter(entity => - entityPredicateToFilterFunction(filter)(entity), - ); - expect(filtered.map(e => e.metadata.name).sort()).toEqual( - expected.split(',').filter(Boolean).sort(), - ); + it('filterPredicateToFilterFunction', () => { + const filtered = entities.filter(entity => + filterPredicateToFilterFunction(filter)(entity), + ); + expect(filtered.map(e => e.metadata.name).sort()).toEqual( + expected.split(',').filter(Boolean).sort(), + ); + }); + + it('evaluateFilterPredicate', () => { + const filtered = entities.filter(entity => + evaluateFilterPredicate(filter, entity), + ); + expect(filtered.map(e => e.metadata.name).sort()).toEqual( + expected.split(',').filter(Boolean).sort(), + ); + }); + }); + + it('handles unknown filter predicate operators and matchers', () => { + const operator = { $unknown: 'foo' } as unknown as FilterPredicate; + const value = { kind: { $unknown: 'foo' } } as unknown as FilterPredicate; + expect(evaluateFilterPredicate(operator, entities[0])).toBe(false); + expect(evaluateFilterPredicate(value, entities[0])).toBe(false); + expect(filterPredicateToFilterFunction(operator)(entities[0])).toBe(false); + expect(filterPredicateToFilterFunction(value)(entities[0])).toBe(false); }); }); diff --git a/plugins/catalog-react/src/alpha/predicates/entityPredicateToFilterFunction.ts b/packages/filter-predicates/src/predicates/evaluate.ts similarity index 53% rename from plugins/catalog-react/src/alpha/predicates/entityPredicateToFilterFunction.ts rename to packages/filter-predicates/src/predicates/evaluate.ts index f223911992..2006265004 100644 --- a/plugins/catalog-react/src/alpha/predicates/entityPredicateToFilterFunction.ts +++ b/packages/filter-predicates/src/predicates/evaluate.ts @@ -15,51 +15,48 @@ */ import { JsonValue } from '@backstage/types'; -import { EntityPredicate, EntityPredicateValue } from './types'; -import { valueAtPath } from './valueAtPath'; +import { FilterPredicate, FilterPredicateValue } from './types'; +import { getJsonValueAtPath } from './getJsonValueAtPath'; /** - * Convert an entity predicate to a filter function that can be used to filter entities. - * @alpha - */ -export function entityPredicateToFilterFunction( - entityPredicate: EntityPredicate, -): (value: T) => boolean { - return value => evaluateEntityPredicate(entityPredicate, value); -} - -/** - * Evaluate a entity predicate against a value, typically an entity. + * Evaluate a filter predicate against a value. * - * @internal + * @public */ -function evaluateEntityPredicate( - filter: EntityPredicate, - value: JsonValue, +export function evaluateFilterPredicate( + predicate: FilterPredicate, + value: unknown, ): boolean { - if (typeof filter !== 'object' || filter === null || Array.isArray(filter)) { - return valuesAreEqual(value, filter); + if ( + typeof predicate !== 'object' || + predicate === null || + Array.isArray(predicate) + ) { + return valuesAreEqual(value, predicate); } - if ('$all' in filter) { - return filter.$all.every(f => evaluateEntityPredicate(f, value)); + if ('$all' in predicate) { + return predicate.$all.every(f => evaluateFilterPredicate(f, value)); } - if ('$any' in filter) { - return filter.$any.some(f => evaluateEntityPredicate(f, value)); + if ('$any' in predicate) { + return predicate.$any.some(f => evaluateFilterPredicate(f, value)); } - if ('$not' in filter) { - return !evaluateEntityPredicate(filter.$not, value); + if ('$not' in predicate) { + return !evaluateFilterPredicate(predicate.$not, value); } - for (const filterKey in filter) { - if (!Object.hasOwn(filter, filterKey)) { + for (const filterKey in predicate) { + if (!Object.hasOwn(predicate, filterKey)) { continue; } if (filterKey.startsWith('$')) { return false; } if ( - !evaluatePredicateValue(filter[filterKey], valueAtPath(value, filterKey)) + !evaluateFilterPredicateValue( + predicate[filterKey], + getJsonValueAtPath(value as JsonValue, filterKey), + ) ) { return false; } @@ -69,13 +66,24 @@ function evaluateEntityPredicate( } /** - * Evaluate a single value against a predicate value. + * Convert a filter predicate to a filter function. + * + * @public + */ +export function filterPredicateToFilterFunction( + predicate: FilterPredicate, +): (value: T) => boolean { + return value => evaluateFilterPredicate(predicate, value); +} + +/** + * Evaluate a single value against a filter predicate value. * * @internal */ -function evaluatePredicateValue( - filter: EntityPredicateValue, - value: JsonValue | undefined, +function evaluateFilterPredicateValue( + filter: FilterPredicateValue, + value: unknown, ): boolean { if (typeof filter !== 'object' || filter === null || Array.isArray(filter)) { return valuesAreEqual(value, filter); @@ -85,7 +93,7 @@ function evaluatePredicateValue( if (!Array.isArray(value)) { return false; } - return value.some(v => evaluateEntityPredicate(filter.$contains, v)); + return value.some(v => evaluateFilterPredicate(filter.$contains, v)); } if ('$in' in filter) { return filter.$in.some(search => valuesAreEqual(value, search)); @@ -96,14 +104,19 @@ function evaluatePredicateValue( } return value === undefined; } + if ('$hasPrefix' in filter) { + if (typeof value !== 'string') { + return false; + } + return value + .toLocaleUpperCase('en-US') + .startsWith(filter.$hasPrefix.toLocaleUpperCase('en-US')); + } return false; } -function valuesAreEqual( - a: JsonValue | undefined, - b: JsonValue | undefined, -): boolean { +function valuesAreEqual(a: unknown, b: unknown): boolean { if (a === null || b === null) { return false; } diff --git a/plugins/catalog-react/src/alpha/predicates/valueAtPath.test.ts b/packages/filter-predicates/src/predicates/getJsonValueAtPath.test.ts similarity index 92% rename from plugins/catalog-react/src/alpha/predicates/valueAtPath.test.ts rename to packages/filter-predicates/src/predicates/getJsonValueAtPath.test.ts index 7ea563e4a1..40421232f4 100644 --- a/plugins/catalog-react/src/alpha/predicates/valueAtPath.test.ts +++ b/packages/filter-predicates/src/predicates/getJsonValueAtPath.test.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import { valueAtPath } from './valueAtPath'; +import { getJsonValueAtPath } from './getJsonValueAtPath'; -describe('valueAtPath', () => { +describe('getJsonValueAtPath', () => { const subject = { name: 'Test', fields: { @@ -58,6 +58,6 @@ describe('valueAtPath', () => { ['mixed.annotations.example.com/description', 'A test subject'], ['mixed.annotations.long.domain.example.com/custom', 'long'], ])(`should find value at path %s`, (path, expected) => { - expect(valueAtPath(subject, path)).toEqual(expected); + expect(getJsonValueAtPath(subject, path)).toEqual(expected); }); }); diff --git a/plugins/catalog-react/src/alpha/predicates/valueAtPath.ts b/packages/filter-predicates/src/predicates/getJsonValueAtPath.ts similarity index 95% rename from plugins/catalog-react/src/alpha/predicates/valueAtPath.ts rename to packages/filter-predicates/src/predicates/getJsonValueAtPath.ts index acb46995d8..2dc982d896 100644 --- a/plugins/catalog-react/src/alpha/predicates/valueAtPath.ts +++ b/packages/filter-predicates/src/predicates/getJsonValueAtPath.ts @@ -27,9 +27,9 @@ import { JsonValue } from '@backstage/types'; * * This lookup does not traverse into arrays, returning `undefined` instead. * - * @internal + * @public */ -export function valueAtPath( +export function getJsonValueAtPath( value: JsonValue | undefined, path: string, ): JsonValue | undefined { @@ -55,7 +55,7 @@ export function valueAtPath( } } if (path.startsWith(`${valueKey}.`)) { - const found = valueAtPath( + const found = getJsonValueAtPath( value[valueKey], path.slice(valueKey.length + 1), ); diff --git a/packages/filter-predicates/src/predicates/index.ts b/packages/filter-predicates/src/predicates/index.ts new file mode 100644 index 0000000000..57a38f340a --- /dev/null +++ b/packages/filter-predicates/src/predicates/index.ts @@ -0,0 +1,39 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { + readFilterPredicateFromConfig, + readOptionalFilterPredicateFromConfig, +} from './config'; +export type { ReadFilterPredicateFromConfigOptions } from './config'; +export { + evaluateFilterPredicate, + filterPredicateToFilterFunction, +} from './evaluate'; +export { getJsonValueAtPath } from './getJsonValueAtPath'; +export { + createZodV3FilterPredicateSchema, + createZodV4FilterPredicateSchema, + parseFilterPredicate, +} from './schema'; +export type { + FilterPredicate, + FilterPredicateExpression, + FilterPredicatePrimitive, + FilterPredicateValue, + UnknownFilterPredicateOperator, + UnknownFilterPredicateValueMatcher, +} from './types'; diff --git a/packages/filter-predicates/src/predicates/schema.test.ts b/packages/filter-predicates/src/predicates/schema.test.ts new file mode 100644 index 0000000000..ff38dabfce --- /dev/null +++ b/packages/filter-predicates/src/predicates/schema.test.ts @@ -0,0 +1,221 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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/v3'; +import { + createZodV3FilterPredicateSchema, + parseFilterPredicate, +} from './schema'; +import { FilterPredicate } from './types'; + +describe('createZodV3FilterPredicateSchema', () => { + const schema = createZodV3FilterPredicateSchema(z); + + describe('valid predicates', () => { + const predicates: FilterPredicate[] = [ + 'string', + '', + 1, + { kind: 'component', 'spec.type': 'service' }, + { 'metadata.tags': { $in: ['java'] } }, + { 'metadata.tags': { $contains: 'java' } }, + { + $all: [ + { 'metadata.tags': { $contains: 'java' } }, + { 'metadata.tags': { $contains: 'spring' } }, + ], + }, + { 'metadata.tags': { $in: ['go'] } }, + { 'metadata.tags.0': 'java' }, + { $not: { 'metadata.tags': { $in: ['java'] } } }, + { + $any: [ + { kind: 'component', 'spec.type': 'service' }, + { kind: 'group' }, + ], + }, + { + relations: { + $contains: { type: 'ownedBy', targetRef: 'group:default/g' }, + }, + }, + { + metadata: { $contains: { name: 'a' } }, + }, + { kind: 'component', 'spec.type': { $in: ['service', 'website'] } }, + { + $any: [ + { + $all: [ + { + kind: 'component', + 'spec.type': { $in: ['service', 'website'] }, + }, + ], + }, + { $all: [{ kind: 'api', 'spec.type': 'grpc' }] }, + ], + }, + { kind: 'component', 'spec.type': { $in: ['service'] } }, + { 'spec.owner': { $exists: true } }, + { 'spec.owner': { $exists: false } }, + { 'spec.type': 'service' }, + { $not: { 'spec.type': 'service' } }, + { + kind: 'component', + 'metadata.annotations.github.com/repo': { $exists: true }, + }, + { $all: [{ x: { $exists: true } }] }, + { $any: [{ x: { $exists: true } }] }, + { $not: { x: { $exists: true } } }, + { $not: { $all: [{ x: { $exists: true } }] } }, + ]; + + it.each(predicates)('should accept valid predicate %j', predicate => { + expect(schema.parse(predicate)).toEqual(predicate); + }); + }); + + describe('invalid predicates', () => { + const predicates: Array< + Exclude + > = [ + [], + ['foo', 'bar'], + { kind: { 1: 'foo' } }, + { kind: { foo: 'bar' } }, + { kind: { $unknown: 'foo' } }, + { kind: { $in: 'foo' } }, + { kind: { $in: [{ x: 'foo' }] } }, + { kind: { $in: [{ x: 'foo' }] } }, + { 'spec.type': null }, + { $all: [{ x: { $unknown: true } }] }, + { $any: [{ x: { $unknown: true } }] }, + { $not: { x: { $unknown: true } } }, + { $not: { $all: [{ x: { $unknown: true } }] } }, + { $unknown: 'foo' }, + { 'metadata.tags': ['foo', 'bar'] }, + ]; + + it.each(predicates)('should reject invalid predicate %j', predicate => { + const result = schema.safeParse(predicate); + expect(result.success).toBe(false); + }); + }); +}); + +describe('parseFilterPredicate', () => { + describe('valid predicates', () => { + const predicates: FilterPredicate[] = [ + 'string', + '', + 1, + { kind: 'component', 'spec.type': 'service' }, + { 'metadata.tags': { $in: ['java'] } }, + { 'metadata.tags': { $contains: 'java' } }, + { + $all: [ + { 'metadata.tags': { $contains: 'java' } }, + { 'metadata.tags': { $contains: 'spring' } }, + ], + }, + { 'metadata.tags': { $in: ['go'] } }, + { 'metadata.tags.0': 'java' }, + { $not: { 'metadata.tags': { $in: ['java'] } } }, + { + $any: [ + { kind: 'component', 'spec.type': 'service' }, + { kind: 'group' }, + ], + }, + { + relations: { + $contains: { type: 'ownedBy', targetRef: 'group:default/g' }, + }, + }, + { + metadata: { $contains: { name: 'a' } }, + }, + { kind: 'component', 'spec.type': { $in: ['service', 'website'] } }, + { + $any: [ + { + $all: [ + { + kind: 'component', + 'spec.type': { $in: ['service', 'website'] }, + }, + ], + }, + { $all: [{ kind: 'api', 'spec.type': 'grpc' }] }, + ], + }, + { kind: 'component', 'spec.type': { $in: ['service'] } }, + { 'spec.owner': { $exists: true } }, + { 'spec.owner': { $exists: false } }, + { 'spec.type': 'service' }, + { $not: { 'spec.type': 'service' } }, + { + kind: 'component', + 'metadata.annotations.github.com/repo': { $exists: true }, + }, + { $all: [{ x: { $exists: true } }] }, + { $any: [{ x: { $exists: true } }] }, + { $not: { x: { $exists: true } } }, + { $not: { $all: [{ x: { $exists: true } }] } }, + { kind: { $hasPrefix: 'Com' } }, + ]; + + it.each(predicates)( + 'should return the predicate for valid input %j', + predicate => { + expect(parseFilterPredicate(predicate)).toEqual(predicate); + }, + ); + }); + + describe('invalid predicates', () => { + const predicates: Array< + Exclude + > = [ + [], + ['foo', 'bar'], + { kind: { 1: 'foo' } }, + { kind: { foo: 'bar' } }, + { kind: { $unknown: 'foo' } }, + { kind: { $in: 'foo' } }, + { kind: { $in: [{ x: 'foo' }] } }, + { kind: { $in: [{ x: 'foo' }] } }, + { 'spec.type': null }, + { $all: [{ x: { $unknown: true } }] }, + { $any: [{ x: { $unknown: true } }] }, + { $not: { x: { $unknown: true } } }, + { $not: { $all: [{ x: { $unknown: true } }] } }, + { $unknown: 'foo' }, + { kind: { $hasPrefix: 1 } }, + { 'metadata.tags': ['foo', 'bar'] }, + ]; + + it.each(predicates)( + 'should throw InputError for invalid predicate %j', + predicate => { + expect(() => parseFilterPredicate(predicate)).toThrow( + /Invalid filter predicate/, + ); + }, + ); + }); +}); diff --git a/packages/filter-predicates/src/predicates/schema.ts b/packages/filter-predicates/src/predicates/schema.ts new file mode 100644 index 0000000000..8526748472 --- /dev/null +++ b/packages/filter-predicates/src/predicates/schema.ts @@ -0,0 +1,141 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { InputError } from '@backstage/errors'; +import { fromZodError } from 'zod-validation-error/v3'; +import * as zodV3 from 'zod/v3'; +import { z as zodV4 } from 'zod/v4'; +import { + FilterPredicate, + FilterPredicateExpression, + FilterPredicatePrimitive, + FilterPredicateValue, +} from './types'; + +/** + * Create a Zod schema for validating filter predicates. + * + * @public + */ +export function createZodV3FilterPredicateSchema( + z: typeof zodV3.z, +): zodV3.ZodType { + const primitiveSchema = z.union([ + z.string(), + z.number(), + z.boolean(), + ]) as zodV3.ZodType; + + // eslint-disable-next-line prefer-const + let valuePredicateSchema: zodV3.ZodType; + + const expressionSchema = z.lazy(() => + z.union([ + z.record(z.string().regex(/^(?!\$).*$/), valuePredicateSchema), + z.record(z.string().regex(/^\$/), z.never()), + ]), + ) as zodV3.ZodType; + + const predicateSchema = z.lazy(() => + z.union([ + expressionSchema, + primitiveSchema, + z.object({ $all: z.array(predicateSchema) }), + z.object({ $any: z.array(predicateSchema) }), + z.object({ $not: predicateSchema }), + ]), + ) as zodV3.ZodType; + + valuePredicateSchema = z.union([ + primitiveSchema, + z.object({ $exists: z.boolean() }), + z.object({ $in: z.array(primitiveSchema) }), + z.object({ $contains: predicateSchema }), + z.object({ $hasPrefix: z.string() }), + ]) as zodV3.ZodType; + + return predicateSchema; +} + +/** + * Creates a zod v4 schema for validating filter predicates. + * + * @public + */ +export function createZodV4FilterPredicateSchema(): zodV4.ZodType< + FilterPredicate, + FilterPredicate +> { + const z = zodV4; + + const primitiveSchema = z.union([ + z.string(), + z.number(), + z.boolean(), + ]) as zodV4.ZodType; + + // eslint-disable-next-line prefer-const + let valuePredicateSchema: zodV4.ZodType< + FilterPredicateValue, + FilterPredicateValue + >; + + const expressionSchema = z.lazy(() => + z.union([ + z.record(z.string().regex(/^(?!\$).*$/), valuePredicateSchema), + z.record(z.string().regex(/^\$/), z.never()), + ]), + ) as zodV4.ZodType; + + const predicateSchema = z.lazy(() => + z.union([ + expressionSchema, + primitiveSchema, + z.object({ $all: z.array(predicateSchema) }), + z.object({ $any: z.array(predicateSchema) }), + z.object({ $not: predicateSchema }), + ]), + ) as zodV4.ZodType; + + valuePredicateSchema = z.union([ + primitiveSchema, + z.object({ $exists: z.boolean() }), + z.object({ $in: z.array(primitiveSchema) }), + z.object({ $contains: predicateSchema }), + z.object({ $hasPrefix: z.string() }), + ]) as zodV4.ZodType; + + return predicateSchema; +} + +/** + * Parses a value to check that it's a valid filter predicate. + * + * @public + * @param value - The value to parse. + * @returns A valid filter predicate. + * @throws An error if the value is not a valid filter predicate. + */ +export function parseFilterPredicate(value: unknown): FilterPredicate { + const schema = createZodV3FilterPredicateSchema(zodV3.z); + const result = schema.safeParse(value); + if (!result.success) { + throw new InputError( + `Invalid filter predicate: ${fromZodError(result.error)}`, + ); + } + return result.data; +} diff --git a/packages/filter-predicates/src/predicates/types.ts b/packages/filter-predicates/src/predicates/types.ts new file mode 100644 index 0000000000..8c5b298719 --- /dev/null +++ b/packages/filter-predicates/src/predicates/types.ts @@ -0,0 +1,200 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { JsonValue } from '@backstage/types'; + +/** + * Represents future additions to the set of filter predicate operators. + * + * @remarks + * + * If you write code that explicitly inspects filter predicate expressions, you + * should be ready for the appearance of such operators and deliberately + * gracefully fail to match them. + * + * @public + */ +export type UnknownFilterPredicateOperator = { + [KOperator in `$${string}`]: JsonValue; +} & { + [KOperator in '$all' | '$any' | '$not']: never; +}; + +/** + * A filter predicate that can be evaluated against a value. + * + * @remarks + * + * A predicate is always an object at the root. The most basic use case is to + * declare keys that are dot-separated paths into a structured data value, and + * values that all need to match the corresponding property value in the data. + * + * The equality test is case-insensitive and numbers are converted to strings, + * i.e. `"Component"` and `"component"` are considered equal, and so are `7` and + * `"7"`. + * + * Example that matches catalog entity components that are of type `service`: + * + * ```json + * { + * "kind": "Component", + * "spec.type": "service" + * } + * ``` + * + * The special keys `$all`, `$any`, and `$not` are logical operators that can be + * used to combine multiple predicates or negate their result. These must be + * used standalone, i.e. they cannot be combined with other matchers. + * + * Example: + * + * ```json + * { + * "$all": [ + * { + * "kind": "Component" + * }, + * { + * "$any": [{ "spec.type": "service" }, { "spec.type": "website" }] + * } + * ] + * } + * ``` + * + * Objects with the special keys `$exists`, `$in`, and `$contains` are value + * operators that can be used to perform more advanced matching against property + * values than just equality. + * + * Example: + * + * ```json + * { + * "filter": { + * "kind": "Component", + * "spec.type": { "$in": ["service", "website"] }, + * "metadata.annotations.github.com/project-slug": { $exists: true } + * } + * } + * ``` + * + * @public + */ +export type FilterPredicate = + | FilterPredicateExpression + | FilterPredicatePrimitive + | { + /** + * Asserts that all of the given predicates must be true. + */ + $all: FilterPredicate[]; + } + | { + /** + * Asserts that at least one of the given predicates must be true. + */ + $any: FilterPredicate[]; + } + | { + /** + * Asserts that the given predicate must not be true. + */ + $not: FilterPredicate; + } + | UnknownFilterPredicateOperator; + +/** + * A filter predicate expression that matches against one or more object + * properties. + * + * @remarks + * + * Each key of a record is a dot-separated path into the entity structure, e.g. + * `metadata.name`. + * + * The values are filter predicates that are evaluated against the value of the + * property at the given path. + * + * For values that are given as primitives, the equality test is + * case-insensitive and numbers are converted to strings, i.e. `"Component"` and + * `"component"` are considered equal, and so are `7` and `"7"`. + * + * @public + */ +export type FilterPredicateExpression = { + [KPath in string]: FilterPredicateValue; +} & { + [KPath in `$${string}`]: never; +}; + +/** + * Represents future additions to the set of filter predicate value matchers. + * + * @remarks + * + * If you write code that explicitly inspects filter predicate expressions, you + * should be ready for the appearance of such matchers and deliberately + * gracefully fail to match them. + * + * @public + */ +export type UnknownFilterPredicateValueMatcher = { + [KMatcher in `$${string}`]: JsonValue; +} & { + [KMatcher in '$exists' | '$in' | '$contains' | '$hasPrefix']: never; +}; + +/** + * A filter predicate value that can be used to match against a property value. + * + * @public + */ +export type FilterPredicateValue = + | FilterPredicatePrimitive + | { + /** + * Asserts that the property exists and has any value (`true`) - or not + * (`false`). + */ + $exists: boolean; + } + | { + /** + * Asserts that the property value is any one of the given possible + * values. + */ + $in: FilterPredicatePrimitive[]; + } + | { + /** + * Asserts that the property value is an array, and that at least one of + * its elements matches the given predicate. + */ + $contains: FilterPredicate; + } + | { + /** + * Asserts that the property value is string, and that it starts with the given string. + */ + $hasPrefix: string; + } + | UnknownFilterPredicateValueMatcher; + +/** + * A primitive value that can be used in filter predicates. + * + * @public + */ +export type FilterPredicatePrimitive = string | number | boolean; diff --git a/packages/filter-predicates/src/setupTests.ts b/packages/filter-predicates/src/setupTests.ts new file mode 100644 index 0000000000..e512e4ba83 --- /dev/null +++ b/packages/filter-predicates/src/setupTests.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export {}; diff --git a/packages/frontend-app-api/CHANGELOG.md b/packages/frontend-app-api/CHANGELOG.md index 38ec33c33a..6b648552d4 100644 --- a/packages/frontend-app-api/CHANGELOG.md +++ b/packages/frontend-app-api/CHANGELOG.md @@ -1,5 +1,249 @@ # @backstage/frontend-app-api +## 0.16.2 + +### Patch Changes + +- 400aa23: Wrapped extension permission authorization in a try/catch to surface errors as `ForwardedError` with a clear message. +- 364d4fe: Added `apis` to `BootstrapSpecializedApp` and `FinalizedSpecializedApp` types. +- Updated dependencies + - @backstage/errors@1.3.0 + - @backstage/core-app-api@1.20.0 + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/filter-predicates@0.1.2 + - @backstage/frontend-defaults@0.5.1 + - @backstage/config@1.3.7 + - @backstage/core-plugin-api@1.12.5 + +## 0.16.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/core-app-api@1.20.0-next.2 + - @backstage/config@1.3.7-next.0 + - @backstage/core-plugin-api@1.12.5-next.2 + - @backstage/filter-predicates@0.1.2-next.0 + - @backstage/frontend-defaults@0.5.1-next.2 + - @backstage/frontend-plugin-api@0.16.0-next.2 + +## 0.16.2-next.1 + +### Patch Changes + +- 400aa23: Wrapped extension permission authorization in a try/catch to surface errors as `ForwardedError` with a clear message. +- Updated dependencies + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/frontend-defaults@0.5.1-next.1 + +## 0.16.2-next.0 + +### Patch Changes + +- 364d4fe: Added `apis` to `BootstrapSpecializedApp` and `FinalizedSpecializedApp` types. +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.2-next.0 + - @backstage/core-app-api@1.19.7-next.0 + - @backstage/frontend-defaults@0.5.1-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/filter-predicates@0.1.1 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + +## 0.16.0 + +### Minor Changes + +- 92af1ae: **BREAKING**: Removed the `allowUnknownExtensionConfig` option from `createSpecializedApp`. This flag had no effect and was a no-op, so no behavioral changes are expected. + +### Patch Changes + +- d911b72: Frontend apps now respect an explicit `pluginId` on `ApiRef`s when deciding which plugin owns an API factory. +- 0452d02: Add optional `description` field to plugin-level feature flags. +- 5fd78ba: Removed `@backstage/core-plugin-api` leakage from the public API surface. All types such as `ApiHolder` and `ConfigApi` are now imported from `@backstage/frontend-plugin-api`. +- dab6c46: Added the `ExtensionFactoryMiddleware` type as a public export. +- 3f36ce1: Clarified the `IconElement` sizing contract for the new frontend system and aligned legacy system icon rendering with the new icon API. +- 5b160f9: Added `prepareSpecializedApp` for two-phase app wiring so apps can render a bootstrap tree before full app finalization. The bootstrap phase now supports deferred `app/root.elements`, predicate-gated APIs, reusable `sessionState`, and warnings for bootstrap-visible predicates or bootstrap code that accessed APIs that only became available after finalization. Utility APIs that are materialized during bootstrap are also frozen for the lifetime of the app instance, causing deferred overrides of those APIs to be ignored and reported as app errors. +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/core-plugin-api@1.12.4 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/core-app-api@1.19.6 + - @backstage/frontend-defaults@0.5.0 + - @backstage/filter-predicates@0.1.1 + +## 0.16.0-next.1 + +### Minor Changes + +- 92af1ae: **BREAKING**: Removed the `allowUnknownExtensionConfig` option from `createSpecializedApp`. This flag had no effect and was a no-op, so no behavioral changes are expected. + +### Patch Changes + +- 0452d02: Add optional `description` field to plugin-level feature flags. +- dab6c46: Added the `ExtensionFactoryMiddleware` type as a public export. +- Updated dependencies + - @backstage/core-app-api@1.19.6-next.1 + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/frontend-defaults@0.5.0-next.1 + +## 0.15.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/config@1.3.6 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/frontend-defaults@0.4.1-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + +## 0.15.0 + +### Minor Changes + +- 55b2ef6: **BREAKING**: Updated the behavior of the new API override logic to reject the override and block app startup instead of just logging a deprecation warning. + +### Patch Changes + +- 7edb810: Implemented support for the `internal` extension input option. +- 492503a: Updated error reporting and app tree resolution logic to attribute errors to the correct extension and allow app startup to proceed more optimistically: + + - If an attachment fails to provide the required input data, the error is now attributed to the attachment rather than the parent extension. + - Singleton extension inputs will now only forward attachment errors if the input is required. + - Array extension inputs will now filter out failed attachments instead of failing the entire app tree resolution. + +- ef6916e: Added `IconElement` type as a replacement for the deprecated `IconComponent`. The `IconsApi` now has a new `icon()` method that returns `IconElement`, while the existing `getIcon()` method is deprecated. The `IconBundleBlueprint` now accepts both `IconComponent` and `IconElement` values. +- 122d39c: Completely removed support for the deprecated `app.experimental.packages` configuration. Replace existing usage directly with `app.packages`. +- 9554c36: **DEPRECATED**: Deprecated support for multiple attachment points. +- 53b6549: Plugins in the new frontend system now have a `pluginId` field rather than `id` to better align with naming conventions used throughout the frontend and backend systems. The old field is still present but marked as deprecated. All internal code has been updated to prefer `pluginId` while maintaining backward compatibility by falling back to `id` when needed. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- 09032d7: Internal update to simplify testing utility implementations. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/frontend-defaults@0.4.0 + - @backstage/core-app-api@1.19.5 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/version-bridge@1.0.12 + +## 0.15.0-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/core-app-api@1.19.5-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/frontend-defaults@0.4.0-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/version-bridge@1.0.12-next.0 + +## 0.15.0-next.1 + +### Minor Changes + +- 55b2ef6: **BREAKING**: Updated the behavior of the new API override logic to reject the override and block app startup instead of just logging a deprecation warning. + +### Patch Changes + +- 09032d7: Internal update to simplify testing utility implementations. +- Updated dependencies + - @backstage/frontend-defaults@0.4.0-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.1 + +## 0.14.1-next.0 + +### Patch Changes + +- 17e0eb3: Updated the behavior of the new API override logic to log a deprecation warning instead of rejecting the override and blocking app startup, as was originally intended. +- 7edb810: Implemented support for the `internal` extension input option. +- 492503a: Updated error reporting and app tree resolution logic to attribute errors to the correct extension and allow app startup to proceed more optimistically: + + - If an attachment fails to provide the required input data, the error is now attributed to the attachment rather than the parent extension. + - Singleton extension inputs will now only forward attachment errors if the input is required. + - Array extension inputs will now filter out failed attachments instead of failing the entire app tree resolution. + +- 122d39c: Completely removed support for the deprecated `app.experimental.packages` configuration. Replace existing usage directly with `app.packages`. +- 9554c36: **DEPRECATED**: Deprecated support for multiple attachment points. +- 53b6549: Plugins in the new frontend system now have a `pluginId` field rather than `id` to better align with naming conventions used throughout the frontend and backend systems. The old field is still present but marked as deprecated. All internal code has been updated to prefer `pluginId` while maintaining backward compatibility by falling back to `id` when needed. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/frontend-defaults@0.3.6-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/core-app-api@1.19.4-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## 0.14.0 + +### Minor Changes + +- 3bd2a1a: BREAKING: The ability for plugins to override APIs has been restricted to only allow overrides of APIs within the same plugin. For example, a plugin can no longer override any of the core APIs provided by the `app` plugin, this must be done with an `app` module instead. + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/frontend-defaults@0.3.5 + +## 0.13.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-defaults@0.3.5-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## 0.13.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-plugin-api@1.12.1 + - @backstage/frontend-defaults@0.3.4 + +## 0.13.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/frontend-defaults@0.3.4-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## 0.13.2 + +### Patch Changes + +- 4d03f08: Internal refactor of route reference implementations with minor updates to the `toString` implementations. +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/config@1.3.6 + - @backstage/frontend-defaults@0.3.3 + ## 0.13.2-next.0 ### Patch Changes diff --git a/packages/frontend-app-api/config.d.ts b/packages/frontend-app-api/config.d.ts index 7032cd378d..2a9197750e 100644 --- a/packages/frontend-app-api/config.d.ts +++ b/packages/frontend-app-api/config.d.ts @@ -16,15 +16,6 @@ export interface Config { app?: { - experimental?: { - /** - * @visibility frontend - * @deepVisibility frontend - * @deprecated This is no longer experimental; use `app.packages` instead. - */ - packages?: 'all' | { include?: string[]; exclude?: string[] }; - }; - /** * Controls what packages are loaded by the new frontend system. * diff --git a/packages/frontend-app-api/knip-report.md b/packages/frontend-app-api/knip-report.md index 77a3cfea97..7b0d8c1c7c 100644 --- a/packages/frontend-app-api/knip-report.md +++ b/packages/frontend-app-api/knip-report.md @@ -1,17 +1,16 @@ # Knip report -## Unused dependencies (4) +## Unused dependencies (3) -| Name | Location | Severity | -| :--------------------------- | :----------- | :------- | -| @backstage/frontend-defaults | packages/frontend-app-api/package.json | error | -| @backstage/version-bridge | packages/frontend-app-api/package.json | error | -| @backstage/errors | packages/frontend-app-api/package.json | error | -| zod | packages/frontend-app-api/package.json | error | +| Name | Location | Severity | +| :--------------------------- | :---------------- | :------- | +| @backstage/frontend-defaults | package.json:40:6 | error | +| @backstage/version-bridge | package.json:43:6 | error | +| zod | package.json:45:6 | error | ## Unused devDependencies (1) -| Name | Location | Severity | -| :----------------------------- | :----------- | :------- | -| @backstage/frontend-test-utils | packages/frontend-app-api/package.json | error | +| Name | Location | Severity | +| :----------------------------- | :---------------- | :------- | +| @backstage/frontend-test-utils | package.json:49:6 | error | diff --git a/packages/frontend-app-api/package.json b/packages/frontend-app-api/package.json index 9bdf329f0f..224af139cf 100644 --- a/packages/frontend-app-api/package.json +++ b/packages/frontend-app-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-app-api", - "version": "0.13.2-next.0", + "version": "0.16.2", "backstage": { "role": "web-library" }, @@ -36,30 +36,32 @@ "@backstage/core-app-api": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", + "@backstage/filter-predicates": "workspace:^", "@backstage/frontend-defaults": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", "@backstage/types": "workspace:^", "@backstage/version-bridge": "workspace:^", "lodash": "^4.17.21", - "zod": "^3.22.4" + "zod": "^3.25.76 || ^4.0.0" }, "devDependencies": { "@backstage/cli": "workspace:^", "@backstage/frontend-test-utils": "workspace:^", "@backstage/plugin-app": "workspace:^", + "@backstage/plugin-permission-common": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^16.0.0", "@types/react": "^18.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/packages/frontend-app-api/report.api.md b/packages/frontend-app-api/report.api.md index bd0709813b..b6b502e2da 100644 --- a/packages/frontend-app-api/report.api.md +++ b/packages/frontend-app-api/report.api.md @@ -3,11 +3,14 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { ApiHolder } from '@backstage/core-plugin-api'; +import { ApiHolder } from '@backstage/frontend-plugin-api'; import { AppNode } from '@backstage/frontend-plugin-api'; import { AppTree } from '@backstage/frontend-plugin-api'; -import { ConfigApi } from '@backstage/core-plugin-api'; -import { ExtensionFactoryMiddleware } from '@backstage/frontend-plugin-api'; +import { ConfigApi } from '@backstage/frontend-plugin-api'; +import { ExtensionDataContainer } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionDataValue } from '@backstage/frontend-plugin-api'; +import { ExtensionFactoryMiddleware as ExtensionFactoryMiddleware_2 } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; import { FrontendFeature } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; @@ -59,6 +62,14 @@ export type AppErrorTypes = { inputName: string; }; }; + EXTENSION_INPUT_INTERNAL_IGNORED: { + context: { + node: AppNode; + inputName: string; + extensionId: string; + plugin: FrontendPlugin; + }; + }; EXTENSION_ATTACHMENT_CONFLICT: { context: { node: AppNode; @@ -109,6 +120,34 @@ export type AppErrorTypes = { node: AppNode; }; }; + API_FACTORY_CONFLICT: { + context: { + node: AppNode; + apiRefId: string; + pluginId: string; + existingPluginId: string; + }; + }; + EXTENSION_BOOTSTRAP_PREDICATE_IGNORED: { + context: { + node: AppNode; + }; + }; + EXTENSION_BOOTSTRAP_API_UNAVAILABLE: { + context: { + node: AppNode; + apiRefId: string; + }; + }; + EXTENSION_BOOTSTRAP_API_OVERRIDE_IGNORED: { + context: { + node: AppNode; + apiRefId: string; + bootstrapNode: AppNode; + pluginId: string; + bootstrapPluginId: string; + }; + }; ROUTE_DUPLICATE: { context: { routeId: string; @@ -126,6 +165,13 @@ export type AppErrorTypes = { }; }; +// @public +export type BootstrapSpecializedApp = { + apis: ApiHolder; + element: JSX.Element; + tree: AppTree; +}; + // @public export type CreateAppRouteBinder = < TExternalRoutes extends { @@ -139,28 +185,46 @@ export type CreateAppRouteBinder = < >, ) => void; -// @public -export function createSpecializedApp(options?: CreateSpecializedAppOptions): { - apis: ApiHolder; - tree: AppTree; - errors?: AppError[]; -}; +// @public @deprecated +export function createSpecializedApp( + options?: CreateSpecializedAppOptions, +): FinalizedSpecializedApp; -// @public +// @public @deprecated export type CreateSpecializedAppOptions = { features?: FrontendFeature[]; config?: ConfigApi; bindRoutes?(context: { bind: CreateAppRouteBinder }): void; advanced?: { apis?: ApiHolder; - allowUnknownExtensionConfig?: boolean; extensionFactoryMiddleware?: - | ExtensionFactoryMiddleware - | ExtensionFactoryMiddleware[]; + | ExtensionFactoryMiddleware_2 + | ExtensionFactoryMiddleware_2[]; pluginInfoResolver?: FrontendPluginInfoResolver; }; }; +// @public (undocumented) +export type ExtensionFactoryMiddleware = ( + originalFactory: (contextOverrides?: { + config?: JsonObject; + }) => ExtensionDataContainer, + context: { + node: AppNode; + apis: ApiHolder; + config?: JsonObject; + }, +) => Iterable>; + +// @public +export type FinalizedSpecializedApp = { + apis: ApiHolder; + element: JSX.Element; + sessionState: SpecializedAppSessionState; + tree: AppTree; + errors?: AppError[]; +}; + // @public export type FrontendPluginInfoResolver = (ctx: { packageJson(): Promise; @@ -174,4 +238,35 @@ export type FrontendPluginInfoResolver = (ctx: { }) => Promise<{ info: FrontendPluginInfo; }>; + +// @public +export type PreparedSpecializedApp = { + getBootstrapApp(): BootstrapSpecializedApp; + onFinalized(callback: (app: FinalizedSpecializedApp) => void): () => void; + finalize(): FinalizedSpecializedApp; +}; + +// @public +export function prepareSpecializedApp( + options?: PrepareSpecializedAppOptions, +): PreparedSpecializedApp; + +// @public +export type PrepareSpecializedAppOptions = { + features?: FrontendFeature[]; + config?: ConfigApi; + bindRoutes?(context: { bind: CreateAppRouteBinder }): void; + advanced?: { + sessionState?: SpecializedAppSessionState; + extensionFactoryMiddleware?: + | ExtensionFactoryMiddleware_2 + | ExtensionFactoryMiddleware_2[]; + pluginInfoResolver?: FrontendPluginInfoResolver; + }; +}; + +// @public +export type SpecializedAppSessionState = { + $$type: '@backstage/SpecializedAppSessionState'; +}; ``` diff --git a/packages/frontend-app-api/src/apis/implementations/IconsApi/DefaultIconsApi.test.ts b/packages/frontend-app-api/src/apis/implementations/IconsApi/DefaultIconsApi.test.ts new file mode 100644 index 0000000000..558c160ecb --- /dev/null +++ b/packages/frontend-app-api/src/apis/implementations/IconsApi/DefaultIconsApi.test.ts @@ -0,0 +1,172 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createElement, memo, forwardRef } from 'react'; +import { DefaultIconsApi } from './DefaultIconsApi'; + +describe('DefaultIconsApi', () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('should return undefined for unknown keys', () => { + const api = new DefaultIconsApi({}); + expect(api.icon('missing')).toBeUndefined(); + expect(api.getIcon('missing')).toBeUndefined(); + }); + + it('should list all registered icon keys', () => { + jest.spyOn(console, 'warn').mockImplementation(() => {}); + const api = new DefaultIconsApi({ + a: createElement('span'), + b: () => createElement('span'), + c: null, + }); + expect(api.listIconKeys()).toEqual(['a', 'b', 'c']); + }); + + it('should return IconElement values directly via icon()', () => { + const element = createElement('span', null, 'test-icon'); + const api = new DefaultIconsApi({ myIcon: element }); + + expect(api.icon('myIcon')).toBe(element); + }); + + it('should return null IconElement values via icon()', () => { + const api = new DefaultIconsApi({ empty: null }); + expect(api.icon('empty')).toBeNull(); + }); + + it('should convert IconComponent values to elements for icon()', () => { + jest.spyOn(console, 'warn').mockImplementation(() => {}); + const MyIcon = () => createElement('span', null, 'rendered'); + const api = new DefaultIconsApi({ myIcon: MyIcon }); + + const result = api.icon('myIcon'); + expect(result).toBeTruthy(); + // @ts-expect-error accessing internal React element structure + expect(result.type).toBe(MyIcon); + // @ts-expect-error accessing internal React element structure + expect(result.props.fontSize).toBe('inherit'); + }); + + it('should wrap IconElement values in a component for getIcon()', () => { + const element = createElement('span', null, 'test-icon'); + const api = new DefaultIconsApi({ myIcon: element }); + + const icon = api.getIcon('myIcon'); + expect(icon).toBeDefined(); + expect(typeof icon).toBe('function'); + // @ts-expect-error testing runtime behavior + const result = icon({}); + expect(result.type).toBe('span'); + expect(result.props.style).toEqual({ fontSize: '1.5rem' }); + expect(result.props.children).toBe(element.props.children); + expect(api.getIcon('myIcon')).toBe(icon); + }); + + it('should honor fontSize for getIcon()', () => { + const element = createElement('svg'); + const api = new DefaultIconsApi({ myIcon: element }); + const icon = api.getIcon('myIcon'); + + // @ts-expect-error testing runtime behavior + const result = icon({ fontSize: 'small' }); + expect(result.props.style.fontSize).toBe('1.25rem'); + }); + + it('should forward runtime props to the original icon element', () => { + const element = createElement('svg', { + className: 'existing', + style: { color: 'red' }, + }); + const api = new DefaultIconsApi({ myIcon: element }); + const icon = api.getIcon('myIcon'); + + // @ts-expect-error testing runtime behavior + const result = icon({ className: 'extra', style: { width: '2em' } }); + + expect(result.type).toBe('svg'); + expect(result.props.className).toBe('existing extra'); + expect(result.props.style).toEqual({ + color: 'red', + fontSize: '1.5rem', + width: '2em', + }); + }); + + it('should wrap null IconElement in a component for getIcon()', () => { + const api = new DefaultIconsApi({ empty: null }); + + const icon = api.getIcon('empty'); + expect(icon).toBeDefined(); + expect(typeof icon).toBe('function'); + // @ts-expect-error testing runtime behavior + expect(icon({})).toBeNull(); + }); + + it('should log a single warning listing all IconComponent keys', () => { + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + + void new DefaultIconsApi({ + a: () => createElement('span'), + elem: createElement('span'), + b: () => createElement('span'), + empty: null, + }); + + expect(warnSpy).toHaveBeenCalledTimes(1); + expect(warnSpy).toHaveBeenCalledWith(expect.stringMatching(/a, b$/)); + }); + + it('should not warn when only IconElement values are provided', () => { + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + + void new DefaultIconsApi({ + element: createElement('span'), + empty: null, + }); + + expect(warnSpy).not.toHaveBeenCalled(); + }); + + it('should treat React.memo components as IconComponent', () => { + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + const MemoIcon = memo(() => createElement('svg')); + const api = new DefaultIconsApi({ myIcon: MemoIcon }); + + const el = api.icon('myIcon'); + expect(el).toBeTruthy(); + // @ts-expect-error accessing internal React element structure + expect(el.type).toBe(MemoIcon); + + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('myIcon')); + }); + + it('should treat React.forwardRef components as IconComponent', () => { + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + const RefIcon = forwardRef(() => createElement('svg')); + // @ts-expect-error forwardRef is not strictly IconComponent but should be handled + const api = new DefaultIconsApi({ myIcon: RefIcon }); + + const el = api.icon('myIcon'); + expect(el).toBeTruthy(); + // @ts-expect-error accessing internal React element structure + expect(el.type).toBe(RefIcon); + + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('myIcon')); + }); +}); diff --git a/packages/frontend-app-api/src/apis/implementations/IconsApi/DefaultIconsApi.ts b/packages/frontend-app-api/src/apis/implementations/IconsApi/DefaultIconsApi.ts index 53a7fa6421..52ac07551a 100644 --- a/packages/frontend-app-api/src/apis/implementations/IconsApi/DefaultIconsApi.ts +++ b/packages/frontend-app-api/src/apis/implementations/IconsApi/DefaultIconsApi.ts @@ -14,7 +14,27 @@ * limitations under the License. */ -import { IconComponent, IconsApi } from '@backstage/frontend-plugin-api'; +import { + IconComponent, + IconElement, + IconsApi, +} from '@backstage/frontend-plugin-api'; +import { cloneElement, createElement, isValidElement } from 'react'; + +const legacyFontSizeMap = { + inherit: 'inherit', + small: '1.25rem', + medium: '1.5rem', + large: '2.1875rem', +} as const; + +function mergeClassNames(...classNames: Array) { + const merged = classNames.filter(Boolean).join(' '); + if (merged) { + return merged; + } + return undefined; +} /** * Implementation for the {@link IconsApi} @@ -22,14 +42,80 @@ import { IconComponent, IconsApi } from '@backstage/frontend-plugin-api'; * @internal */ export class DefaultIconsApi implements IconsApi { - #icons: Map; + #icons: Map; + #components = new Map(); - constructor(icons: { [key in string]: IconComponent }) { - this.#icons = new Map(Object.entries(icons)); + constructor(icons: { [key in string]: IconComponent | IconElement }) { + const deprecatedKeys: string[] = []; + + this.#icons = new Map( + Object.entries(icons).map(([key, value]) => { + if (value === null || isValidElement(value)) { + return [key, value]; + } + deprecatedKeys.push(key); + return [ + key, + createElement(value as IconComponent, { fontSize: 'inherit' }), + ]; + }), + ); + + if (deprecatedKeys.length > 0) { + const keys = deprecatedKeys.join(', '); + // eslint-disable-next-line no-console + console.warn( + `The following icons were registered as IconComponent, which is deprecated. Use IconElement instead by passing rather than MyIcon: ${keys}`, + ); + } + } + + icon(key: string): IconElement | undefined { + return this.#icons.get(key); } getIcon(key: string): IconComponent | undefined { - return this.#icons.get(key); + let component = this.#components.get(key); + if (component) { + return component; + } + const el = this.#icons.get(key); + if (el === undefined) { + return undefined; + } + component = props => { + if (el === null) { + return null; + } + + const { + fontSize = 'medium', + className, + style, + ...rest + } = props as { + fontSize?: keyof typeof legacyFontSizeMap; + className?: string; + style?: Record; + } & Record; + + const elementProps = el.props as { + className?: string; + style?: Record; + }; + + return cloneElement(el, { + ...rest, + className: mergeClassNames(elementProps.className, className), + style: { + ...elementProps.style, + fontSize: legacyFontSizeMap[fontSize], + ...style, + }, + }); + }; + this.#components.set(key, component); + return component; } listIconKeys(): string[] { diff --git a/packages/frontend-app-api/src/routing/RouteAliasResolver.ts b/packages/frontend-app-api/src/routing/RouteAliasResolver.ts index 64e5eb7ce8..badf27f964 100644 --- a/packages/frontend-app-api/src/routing/RouteAliasResolver.ts +++ b/packages/frontend-app-api/src/routing/RouteAliasResolver.ts @@ -16,8 +16,7 @@ import { RouteRef } from '@backstage/frontend-plugin-api'; import { RouteRefsById } from './collectRouteIds'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { toInternalRouteRef } from '../../../frontend-plugin-api/src/routing/RouteRef'; +import { OpaqueRouteRef } from '@internal/frontend'; /** * @internal @@ -41,7 +40,7 @@ export function createRouteAliasResolver( let currentRef = routeRef; for (let i = 0; i < 100; i++) { - const alias = toInternalRouteRef(currentRef).alias; + const alias = OpaqueRouteRef.toInternal(currentRef).alias; if (alias) { if (pluginId) { const [aliasPluginId] = alias.split('.'); diff --git a/packages/frontend-app-api/src/routing/RouteResolver.test.ts b/packages/frontend-app-api/src/routing/RouteResolver.test.ts index 1b23ac7131..7b9105f2ae 100644 --- a/packages/frontend-app-api/src/routing/RouteResolver.test.ts +++ b/packages/frontend-app-api/src/routing/RouteResolver.test.ts @@ -22,7 +22,12 @@ import { RouteRef, SubRouteRef, } from '@backstage/frontend-plugin-api'; -import { BackstagePlugin } from '@backstage/core-plugin-api'; +import { + BackstagePlugin, + createRouteRef as createLegacyRouteRef, + createSubRouteRef as createLegacySubRouteRef, + createExternalRouteRef as createLegacyExternalRouteRef, +} from '@backstage/core-plugin-api'; import { RouteResolver } from './RouteResolver'; import { MATCH_ALL_ROUTE } from './extractRouteInfoFromAppNode'; import { @@ -416,4 +421,196 @@ describe('RouteResolver', () => { '/my-parent/a%2F%23%26%3Fb', ); }); + + describe('with legacy route refs', () => { + const legacyRef1 = createLegacyRouteRef({ id: 'ref1' }); + const legacyRef2 = createLegacyRouteRef({ id: 'ref2', params: ['x'] }); + const legacyRef3 = createLegacyRouteRef({ id: 'ref3', params: ['y'] }); + const legacySubRef1 = createLegacySubRouteRef({ + id: 'sub1', + parent: legacyRef1, + path: '/foo', + }); + const legacySubRef2 = createLegacySubRouteRef({ + id: 'sub2', + parent: legacyRef1, + path: '/foo/:a', + }); + const legacySubRef3 = createLegacySubRouteRef({ + id: 'sub3', + parent: legacyRef2, + path: '/bar', + }); + const legacySubRef4 = createLegacySubRouteRef({ + id: 'sub4', + parent: legacyRef2, + path: '/bar/:a', + }); + const legacyExternalRef1 = createLegacyExternalRouteRef({ + id: 'external1', + }); + const legacyExternalRef2 = createLegacyExternalRouteRef({ + id: 'external2', + params: ['x'], + }); + + it('should not resolve anything with an empty resolver', () => { + const r = new RouteResolver( + new Map(), + new Map(), + [], + new Map(), + '', + emptyResolver, + new Map(), + ); + + expect(r.resolve(legacyRef1, src('/'))?.()).toBe(undefined); + expect(r.resolve(legacyRef2, src('/'))?.({ x: '1x' })).toBe(undefined); + expect(r.resolve(legacySubRef1, src('/'))?.()).toBe(undefined); + expect(r.resolve(legacySubRef2, src('/'))?.({ a: '2a' })).toBe(undefined); + expect(r.resolve(legacySubRef3, src('/'))?.({ x: '3x' })).toBe(undefined); + expect(r.resolve(legacySubRef4, src('/'))?.({ x: '4x', a: '4a' })).toBe( + undefined, + ); + expect(r.resolve(legacyExternalRef1, src('/'))?.()).toBe(undefined); + expect(r.resolve(legacyExternalRef2, src('/'))?.({ x: '5x' })).toBe( + undefined, + ); + }); + + it('should resolve an absolute route', () => { + const r = new RouteResolver( + new Map([[legacyRef1, 'my-route']]), + new Map(), + [{ routeRefs: new Set([legacyRef1]), path: 'my-route', ...rest }], + new Map(), + '', + emptyResolver, + new Map(), + ); + + expect(r.resolve(legacyRef1, src('/'))?.()).toBe('/my-route'); + expect(r.resolve(legacyRef2, src('/'))?.({ x: '1x' })).toBe(undefined); + expect(r.resolve(legacySubRef1, src('/'))?.()).toBe('/my-route/foo'); + expect(r.resolve(legacySubRef2, src('/'))?.({ a: '2a' })).toBe( + '/my-route/foo/2a', + ); + expect(r.resolve(legacySubRef3, src('/'))?.({ x: '3x' })).toBe(undefined); + expect(r.resolve(legacySubRef4, src('/'))?.({ x: '4x', a: '4a' })).toBe( + undefined, + ); + expect(r.resolve(legacyExternalRef1, src('/'))?.()).toBe(undefined); + expect(r.resolve(legacyExternalRef2, src('/'))?.({ x: '5x' })).toBe( + undefined, + ); + }); + + it('should resolve an absolute route with a param and with a parent', () => { + const r = new RouteResolver( + new Map([ + [legacyRef1, 'my-route'], + [legacyRef2, 'my-parent/:x'], + ]), + new Map([[legacyRef2, legacyRef1]]), + [ + { + routeRefs: new Set([legacyRef2]), + path: 'my-parent/:x', + ...rest, + children: [ + MATCH_ALL_ROUTE, + { routeRefs: new Set([legacyRef1]), path: 'my-route', ...rest }, + ], + }, + ], + new Map([ + [legacyExternalRef1, legacyRef1], + [legacyExternalRef2, legacySubRef3], + ]), + '', + emptyResolver, + new Map(), + ); + + expect(r.resolve(legacyRef1, src('/'))?.()).toBe('/my-route'); + expect(r.resolve(legacyRef2, src('/'))?.({ x: '1x' })).toBe( + '/my-route/my-parent/1x', + ); + expect(r.resolve(legacySubRef1, src('/'))?.()).toBe('/my-route/foo'); + expect(r.resolve(legacySubRef2, src('/'))?.({ a: '2a' })).toBe( + '/my-route/foo/2a', + ); + expect(r.resolve(legacySubRef3, src('/'))?.({ x: '3x' })).toBe( + '/my-route/my-parent/3x/bar', + ); + expect(r.resolve(legacySubRef4, src('/'))?.({ x: '4x', a: '4a' })).toBe( + '/my-route/my-parent/4x/bar/4a', + ); + expect(r.resolve(legacyExternalRef1, src('/'))?.()).toBe('/my-route'); + expect(r.resolve(legacyExternalRef2, src('/'))?.({ x: '6x' })).toBe( + '/my-route/my-parent/6x/bar', + ); + }); + + it('should resolve the most specific match', () => { + const r = new RouteResolver( + new Map([ + [legacyRef1, 'deep'], + [legacyRef2, 'root/:x'], + [legacyRef3, 'sub/:y'], + ]), + new Map([ + [legacyRef3, legacyRef2], + [legacyRef1, legacyRef3], + ]), + [ + { + routeRefs: new Set([legacyRef2]), + path: 'root/:x', + ...rest, + children: [ + MATCH_ALL_ROUTE, + { + routeRefs: new Set([legacyRef3]), + path: 'sub/:y', + ...rest, + children: [ + MATCH_ALL_ROUTE, + { + routeRefs: new Set([legacyRef1]), + path: 'deep', + ...rest, + }, + ], + }, + ], + }, + ], + new Map(), + '', + emptyResolver, + new Map(), + ); + + expect(r.resolve(legacyRef2, src('/'))?.({ x: 'x' })).toBe('/root/x'); + expect(r.resolve(legacyRef3, src('/root/x'))?.({ y: 'y' })).toBe( + '/root/x/sub/y', + ); + + expect(() => r.resolve(legacyRef1, src('/'))?.()).toThrow( + /^Cannot route.*with parent.*as it has parameters$/, + ); + expect(() => r.resolve(legacyRef1, src('/root/x'))?.()).toThrow( + /^Cannot route.*with parent.*as it has parameters$/, + ); + expect(r.resolve(legacyRef1, src('/root/x/sub/y'))?.()).toBe( + '/root/x/sub/y/deep', + ); + // Without the MATCH_ALL_ROUTE, we wouldn't properly match the route here + expect( + r.resolve(legacyRef1, src('/root/x/sub/y/any/nested/path/here'))?.(), + ).toBe('/root/x/sub/y/deep'); + }); + }); }); diff --git a/packages/frontend-app-api/src/routing/RouteResolver.ts b/packages/frontend-app-api/src/routing/RouteResolver.ts index 4c0ad0e3fa..a19105b387 100644 --- a/packages/frontend-app-api/src/routing/RouteResolver.ts +++ b/packages/frontend-app-api/src/routing/RouteResolver.ts @@ -25,28 +25,13 @@ import { } from '@backstage/frontend-plugin-api'; import mapValues from 'lodash/mapValues'; import { AnyRouteRef, BackstageRouteObject } from './types'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { isRouteRef } from '../../../frontend-plugin-api/src/routing/RouteRef'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports import { - isSubRouteRef, - toInternalSubRouteRef, -} from '../../../frontend-plugin-api/src/routing/SubRouteRef'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { - isExternalRouteRef, - toInternalExternalRouteRef, -} from '../../../frontend-plugin-api/src/routing/ExternalRouteRef'; + OpaqueRouteRef, + OpaqueExternalRouteRef, + OpaqueSubRouteRef, +} from '@internal/frontend'; import { RouteAliasResolver } from './RouteAliasResolver'; - -// Joins a list of paths together, avoiding trailing and duplicate slashes -export function joinPaths(...paths: string[]): string { - const normalized = paths.join('/').replace(/\/\/+/g, '/'); - if (normalized !== '/' && normalized.endsWith('/')) { - return normalized.slice(0, -1); - } - return normalized; -} +import { joinPaths } from './joinPaths'; /** * Resolves the absolute route ref that our target route ref is pointing pointing to, as well @@ -65,10 +50,10 @@ function resolveTargetRef( let ref: AnyRouteRef = targetRouteRef; let path = ''; - if (isExternalRouteRef(ref)) { + if (OpaqueExternalRouteRef.isType(ref)) { let resolvedRoute = routeBindings.get(ref); if (!resolvedRoute) { - const internal = toInternalExternalRouteRef(ref); + const internal = OpaqueExternalRouteRef.toInternal(ref); const defaultTarget = internal.getDefaultTarget(); if (defaultTarget) { resolvedRoute = routeRefsById.get(defaultTarget); @@ -80,13 +65,13 @@ function resolveTargetRef( ref = resolvedRoute; } - if (isSubRouteRef(ref)) { - const internal = toInternalSubRouteRef(ref); + if (OpaqueSubRouteRef.isType(ref)) { + const internal = OpaqueSubRouteRef.toInternal(ref); path = ref.path; ref = internal.getParent(); } - if (!isRouteRef(ref)) { + if (!OpaqueRouteRef.isType(ref)) { throw new Error( `Unexpectedly resolved ${targetRouteRef} to a non-route ref ${ref}`, ); diff --git a/packages/frontend-app-api/src/routing/collectRouteIds.test.ts b/packages/frontend-app-api/src/routing/collectRouteIds.test.ts index b1bab3fbbe..9575603bf4 100644 --- a/packages/frontend-app-api/src/routing/collectRouteIds.test.ts +++ b/packages/frontend-app-api/src/routing/collectRouteIds.test.ts @@ -39,10 +39,10 @@ describe('collectRouteIds', () => { const extRef = createExternalRouteRef(); expect(String(ref)).toMatch( - /^RouteRef\{created at '.*collectRouteIds\.test\.ts.*'\}$/, + /^routeRef\{id=undefined,at='.*collectRouteIds\.test\.ts.*'\}$/, ); expect(String(extRef)).toMatch( - /^ExternalRouteRef\{created at '.*collectRouteIds\.test\.ts.*'\}$/, + /^externalRouteRef\{id=undefined,at='.*collectRouteIds\.test\.ts.*'\}$/, ); const collected = collectRouteIds( @@ -62,8 +62,12 @@ describe('collectRouteIds', () => { 'test.extRef': extRef, }); - expect(String(ref)).toBe('RouteRef{test.ref}'); - expect(String(extRef)).toBe('ExternalRouteRef{test.extRef}'); + expect(String(ref)).toMatch( + /^routeRef\{id=test.ref,at='.*collectRouteIds\.test\.ts.*'\}$/, + ); + expect(String(extRef)).toMatch( + /^externalRouteRef\{id=test.extRef,at='.*collectRouteIds\.test\.ts.*'\}$/, + ); }); it('should report duplicate route IDs', () => { diff --git a/packages/frontend-app-api/src/routing/collectRouteIds.ts b/packages/frontend-app-api/src/routing/collectRouteIds.ts index 79afb07928..c99c28ac1a 100644 --- a/packages/frontend-app-api/src/routing/collectRouteIds.ts +++ b/packages/frontend-app-api/src/routing/collectRouteIds.ts @@ -20,16 +20,12 @@ import { ExternalRouteRef, FrontendFeature, } from '@backstage/frontend-plugin-api'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports import { - isRouteRef, - toInternalRouteRef, -} from '../../../frontend-plugin-api/src/routing/RouteRef'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { toInternalExternalRouteRef } from '../../../frontend-plugin-api/src/routing/ExternalRouteRef'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { toInternalSubRouteRef } from '../../../frontend-plugin-api/src/routing/SubRouteRef'; -import { OpaqueFrontendPlugin } from '@internal/frontend'; + OpaqueRouteRef, + OpaqueSubRouteRef, + OpaqueExternalRouteRef, + OpaqueFrontendPlugin, +} from '@internal/frontend'; import { ErrorCollector } from '../wiring/createErrorCollector'; /** @internal */ @@ -62,12 +58,12 @@ export function collectRouteIds( continue; } - if (isRouteRef(ref)) { - const internalRef = toInternalRouteRef(ref); + if (OpaqueRouteRef.isType(ref)) { + const internalRef = OpaqueRouteRef.toInternal(ref); internalRef.setId(refId); routesById.set(refId, ref); } else { - const internalRef = toInternalSubRouteRef(ref); + const internalRef = OpaqueSubRouteRef.toInternal(ref); routesById.set(refId, internalRef); } } @@ -82,7 +78,7 @@ export function collectRouteIds( continue; } - const internalRef = toInternalExternalRouteRef(ref); + const internalRef = OpaqueExternalRouteRef.toInternal(ref); internalRef.setId(refId); externalRoutesById.set(refId, ref); } diff --git a/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts b/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts index efcc9ef18c..8375cf6d3a 100644 --- a/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts +++ b/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts @@ -636,7 +636,7 @@ describe('discovery', () => { }, ), ).toThrow( - /Refused to resolve alias 'other.root' for RouteRef{created at 'at .*extractRouteInfoFromAppNode\.test\.ts:\d+:\d+'} as it points to a different plugin, the expected plugin is 'test' but the alias points to 'other'/, + /Refused to resolve alias 'other.root' for routeRef{id=undefined,at='.*extractRouteInfoFromAppNode\.test\.ts:\d+:\d+'} as it points to a different plugin, the expected plugin is 'test' but the alias points to 'other'/, ); }); @@ -662,7 +662,7 @@ describe('discovery', () => { }, ), ).toThrow( - /Alias loop detected for RouteRef{created at 'at .*extractRouteInfoFromAppNode\.test\.ts:\d+:\d+'}/, + /Alias loop detected for routeRef{id=undefined,at='.*extractRouteInfoFromAppNode\.test\.ts:\d+:\d+'}/, ); }); }); diff --git a/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.ts b/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.ts index 4d88fb5fb1..062a54f709 100644 --- a/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.ts +++ b/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.ts @@ -21,6 +21,7 @@ import { createExactRouteAliasResolver, RouteAliasResolver, } from './RouteAliasResolver'; +import { joinPaths } from './joinPaths'; /** @internal */ export type RouteInfo = { @@ -41,15 +42,6 @@ export const MATCH_ALL_ROUTE: BackstageRouteObject = { routeRefs: new Set(), }; -// Joins a list of paths together, avoiding trailing and duplicate slashes -export function joinPaths(...paths: string[]): string { - const normalized = paths.join('/').replace(/\/\/+/g, '/'); - if (normalized !== '/' && normalized.endsWith('/')) { - return normalized.slice(0, -1); - } - return normalized; -} - export function extractRouteInfoFromAppNode( node: AppNode, routeAliasResolver: RouteAliasResolver, diff --git a/packages/frontend-app-api/src/routing/joinPaths.ts b/packages/frontend-app-api/src/routing/joinPaths.ts new file mode 100644 index 0000000000..89b84bb51c --- /dev/null +++ b/packages/frontend-app-api/src/routing/joinPaths.ts @@ -0,0 +1,24 @@ +/* + * 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. + */ + +// Joins a list of paths together, avoiding trailing and duplicate slashes +export function joinPaths(...paths: string[]): string { + const normalized = paths.join('/').replace(/\/\/+/g, '/'); + if (normalized !== '/' && normalized.endsWith('/')) { + return normalized.slice(0, -1); + } + return normalized; +} diff --git a/packages/frontend-app-api/src/routing/resolveRouteBindings.ts b/packages/frontend-app-api/src/routing/resolveRouteBindings.ts index f9836d7107..44fe4ecbf5 100644 --- a/packages/frontend-app-api/src/routing/resolveRouteBindings.ts +++ b/packages/frontend-app-api/src/routing/resolveRouteBindings.ts @@ -23,8 +23,7 @@ import { RouteRefsById } from './collectRouteIds'; import { ErrorCollector } from '../wiring/createErrorCollector'; import { Config } from '@backstage/config'; import { JsonObject } from '@backstage/types'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { toInternalExternalRouteRef } from '../../../frontend-plugin-api/src/routing/ExternalRouteRef'; +import { OpaqueExternalRouteRef } from '@internal/frontend'; /** * Extracts a union of the keys in a map whose value extends the given type @@ -166,7 +165,7 @@ export function resolveRouteBindings( for (const externalRef of routesById.externalRoutes.values()) { if (!result.has(externalRef) && !disabledExternalRefs.has(externalRef)) { const defaultRefId = - toInternalExternalRouteRef(externalRef).getDefaultTarget(); + OpaqueExternalRouteRef.toInternal(externalRef).getDefaultTarget(); if (defaultRefId) { const defaultRef = routesById.routes.get(defaultRefId); if (defaultRef) { diff --git a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts index a07fdf352d..9a60e55033 100644 --- a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts +++ b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts @@ -17,18 +17,17 @@ import { AppNode, Extension, + ExtensionDataContainer, ExtensionDataRef, ExtensionDefinition, - ExtensionFactoryMiddleware, - ExtensionInput, PortableSchema, - ResolvedExtensionInput, createExtension, createExtensionBlueprint, createExtensionDataRef, createExtensionInput, createFrontendPlugin, } from '@backstage/frontend-plugin-api'; +import { ExtensionFactoryMiddleware } from '../wiring/types'; import { createAppNodeInstance, instantiateAppNodeTree, @@ -41,7 +40,7 @@ import { resolveExtensionDefinition, } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { createSchemaFromZod } from '../../../frontend-plugin-api/src/schema/createSchemaFromZod'; +import { createDeprecatedConfigSchema } from '../../../frontend-plugin-api/src/schema/createPortableSchema'; import { TestApiRegistry, withLogCollector } from '@backstage/test-utils'; import { createErrorCollector } from '../wiring/createErrorCollector'; @@ -142,6 +141,36 @@ function createV1Extension(opts: { return ext; } +function mirrorInputs(ctx: { + inputs: { + [name in string]: + | undefined + | ({ node: AppNode } & ExtensionDataContainer) + | Array<{ node: AppNode } & ExtensionDataContainer>; + }; +}) { + return [ + inputMirrorDataRef( + Object.fromEntries( + Object.entries(ctx.inputs).map(([k, v]) => [ + k, + Array.isArray(v) + ? v.map(vi => ({ + node: vi.node, + test: vi.get(testDataRef), + other: vi.get(otherDataRef), + })) + : { + node: v?.node, + test: v?.get(testDataRef), + other: v?.get(otherDataRef), + }, + ]), + ), + ), + ]; +} + describe('instantiateAppNodeTree', () => { describe('v1', () => { const simpleExtension = createV1Extension({ @@ -152,12 +181,10 @@ describe('instantiateAppNodeTree', () => { test: testDataRef, other: otherDataRef.optional(), }, - configSchema: createSchemaFromZod(z => - z.object({ - output: z.string().default('test'), - other: z.number().optional(), - }), - ), + configSchema: createDeprecatedConfigSchema({ + output: z => z.string().default('test'), + other: z => z.number().optional(), + }), factory({ config }) { return { test: config.output, other: config.other }; }, @@ -237,6 +264,60 @@ describe('instantiateAppNodeTree', () => { expect(childNode?.instance).toBeDefined(); }); + it('should ignore non-matching plugin attachments for internal inputs', () => { + const otherPlugin = createFrontendPlugin({ pluginId: 'other' }); + const tree = resolveAppTree( + 'root-node', + [ + makeSpec( + resolveExtensionDefinition( + createExtension({ + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + test: createExtensionInput([testDataRef], { + singleton: true, + internal: true, + }), + }, + output: [inputMirrorDataRef], + factory: mirrorInputs, + }), + { namespace: 'root-node' }, + ), + ), + makeSpec(simpleExtension, { + id: 'child-node-app', + attachTo: { id: 'root-node', input: 'test' }, + }), + makeSpec(simpleExtension, { + id: 'child-node-other', + attachTo: { id: 'root-node', input: 'test' }, + plugin: otherPlugin, + }), + ], + collector, + ); + + instantiateAppNodeTree(tree.root, testApis, collector); + + expect(tree.root.instance?.getData(inputMirrorDataRef)).toMatchObject({ + test: { node: { spec: { id: 'child-node-app' } }, test: 'test' }, + }); + expect(collector.collectErrors()).toEqual([ + { + code: 'EXTENSION_INPUT_INTERNAL_IGNORED', + message: + "extension 'child-node-other' from plugin 'other' attached to input 'test' on 'root-node' was ignored, the input is marked as internal and attached extensions must therefore be provided via an override or a module for the 'app' plugin, not the 'other' plugin", + context: { + node: tree.root, + inputName: 'test', + extensionId: 'child-node-other', + plugin: otherPlugin, + }, + }, + ]); + }); + it('should not instantiate disabled attachments', () => { const tree = resolveAppTree( 'root-node', @@ -783,36 +864,6 @@ describe('instantiateAppNodeTree', () => { { namespace: 'app' }, ); - function mirrorInputs(ctx: { - inputs: { - [name in string]: - | undefined - | ResolvedExtensionInput - | Array>; - }; - }) { - return [ - inputMirrorDataRef( - Object.fromEntries( - Object.entries(ctx.inputs).map(([k, v]) => [ - k, - Array.isArray(v) - ? v.map(vi => ({ - node: vi.node, - test: vi.get(testDataRef), - other: vi.get(otherDataRef), - })) - : { - node: v?.node, - test: v?.get(testDataRef), - other: v?.get(otherDataRef), - }, - ]), - ), - ), - ]; - } - it('should instantiate a single node', () => { const tree = resolveAppTree( 'root-node', @@ -1574,42 +1625,251 @@ describe('instantiateAppNodeTree', () => { ]); }); - it('should refuse to create an instance with multiple inputs that did not provide required data', () => { - const node = makeNode( - resolveExtensionDefinition( - createExtension({ - name: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - singleton: createExtensionInput([otherDataRef], { - singleton: true, - }), - }, - output: [], - factory: () => [], - }), - { namespace: 'app' }, - ), + describe('with attachment failures', () => { + const inputCountRef = createExtensionDataRef().with({ + id: 'input-count', + }); + + const attachmentWithoutRequiredData = makeInstanceWithId( + simpleExtension, + undefined, ); - expect( - createAppNodeInstance({ - apis: testApis, - attachments: new Map([ - ['singleton', [makeInstanceWithId(simpleExtension, undefined)]], - ]), - node, - collector, - }), - ).toBeUndefined(); - expect(collector.collectErrors()).toEqual([ - { - code: 'EXTENSION_INPUT_DATA_MISSING', - message: - "extension 'app/test' could not be attached because its output data ('test') does not match what the input 'singleton' requires ('other')", - context: { node, inputName: 'singleton' }, - }, - ]); + + it('should proceed if input is optional', () => { + const node = makeNode( + resolveExtensionDefinition( + createExtension({ + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + singleton: createExtensionInput([otherDataRef], { + singleton: true, + optional: true, + }), + }, + output: [inputCountRef], + factory: ({ inputs }) => [ + inputCountRef(inputs.singleton ? 1 : 0), + ], + }), + { namespace: 'app' }, + ), + ); + expect( + createAppNodeInstance({ + apis: testApis, + attachments: new Map([ + ['singleton', [attachmentWithoutRequiredData]], + ]), + node, + collector, + })?.getData(inputCountRef), + ).toBe(0); + + expect(collector.collectErrors()).toEqual([ + { + code: 'EXTENSION_INPUT_DATA_MISSING', + message: + "extension 'app/test' could not be attached because its output data ('test') does not match what the input 'singleton' requires ('other')", + context: { + node: attachmentWithoutRequiredData, + inputName: 'singleton', + }, + }, + ]); + }); + + it('should fail if input is required', () => { + const node = makeNode( + resolveExtensionDefinition( + createExtension({ + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + singleton: createExtensionInput([otherDataRef], { + singleton: true, + }), + }, + output: [inputCountRef], + factory: ({ inputs }) => [ + inputCountRef(inputs.singleton ? 1 : 0), + ], + }), + { namespace: 'app' }, + ), + ); + + expect( + createAppNodeInstance({ + apis: testApis, + attachments: new Map([ + ['singleton', [attachmentWithoutRequiredData]], + ]), + node, + collector, + })?.getData(inputCountRef), + ).toBeUndefined(); + + expect(collector.collectErrors()).toEqual([ + { + code: 'EXTENSION_ATTACHMENT_MISSING', + message: + "input 'singleton' is required but it failed to be instantiated", + context: { inputName: 'singleton', node }, + }, + { + code: 'EXTENSION_INPUT_DATA_MISSING', + message: + "extension 'app/test' could not be attached because its output data ('test') does not match what the input 'singleton' requires ('other')", + context: { + node: attachmentWithoutRequiredData, + inputName: 'singleton', + }, + }, + ]); + }); + + it('should filter out failed attachments for non-singleton inputs', () => { + const node = makeNode( + resolveExtensionDefinition( + createExtension({ + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + children: createExtensionInput([otherDataRef]), + }, + output: [inputCountRef], + factory: ({ inputs }) => [ + inputCountRef(inputs.children.length), + ], + }), + { namespace: 'app' }, + ), + ); + expect( + createAppNodeInstance({ + apis: testApis, + attachments: new Map([ + [ + 'children', + [ + attachmentWithoutRequiredData, + makeInstanceWithId(simpleExtension, { + other: 42, + }), + ], + ], + ]), + node, + collector, + })?.getData(inputCountRef), + ).toBe(1); + + expect(collector.collectErrors()).toEqual([ + { + code: 'EXTENSION_INPUT_DATA_MISSING', + message: + "extension 'app/test' could not be attached because its output data ('test') does not match what the input 'children' requires ('other')", + context: { + node: attachmentWithoutRequiredData, + inputName: 'children', + }, + }, + ]); + }); }); }); }); + + describe('if predicate', () => { + function makeNodeWithEnabled( + enabled: AppNodeSpec['if'], + disabled = false, + ): AppNode { + const ext = resolveExtensionDefinition( + createExtension({ + attachTo: { id: 'ignored', input: 'ignored' }, + output: [testDataRef], + factory: () => [testDataRef('value')], + }), + { namespace: 'test-ext' }, + ); + return { + spec: { + id: ext.id, + attachTo: ext.attachTo, + disabled, + if: enabled, + extension: ext as Extension, + plugin: createFrontendPlugin({ pluginId: 'app' }), + }, + edges: { attachments: new Map() }, + }; + } + + it('should skip a node when the predicate is not satisfied', () => { + const node = makeNodeWithEnabled({ + featureFlags: { $contains: 'the-flag' }, + }); + const tree = resolveAppTree('test-ext', [node.spec], collector); + instantiateAppNodeTree(tree.root, testApis, collector, undefined, { + featureFlags: [], + }); + expect(tree.root.instance).toBeUndefined(); + }); + + it('should instantiate a node when the predicate is satisfied', () => { + const node = makeNodeWithEnabled({ + featureFlags: { $contains: 'the-flag' }, + }); + const tree = resolveAppTree('test-ext', [node.spec], collector); + instantiateAppNodeTree(tree.root, testApis, collector, undefined, { + featureFlags: ['the-flag'], + }); + expect(tree.root.instance).toBeDefined(); + expect(tree.root.instance?.getData(testDataRef)).toBe('value'); + }); + + it('should support $all operator across multiple flags', () => { + const node = makeNodeWithEnabled({ + $all: [ + { featureFlags: { $contains: 'flag-a' } }, + { featureFlags: { $contains: 'flag-b' } }, + ], + }); + const tree = resolveAppTree('test-ext', [node.spec], collector); + + // Only one flag active — should not instantiate + instantiateAppNodeTree(tree.root, testApis, collector, undefined, { + featureFlags: ['flag-a'], + }); + expect(tree.root.instance).toBeUndefined(); + + // Both flags active — should instantiate + const tree2 = resolveAppTree('test-ext', [node.spec], collector); + instantiateAppNodeTree(tree2.root, testApis, collector, undefined, { + featureFlags: ['flag-a', 'flag-b'], + }); + expect(tree2.root.instance).toBeDefined(); + }); + + it('should instantiate nodes without an enabled field regardless of predicateContext', () => { + const node = makeNodeWithEnabled(undefined); + const tree = resolveAppTree('test-ext', [node.spec], collector); + instantiateAppNodeTree(tree.root, testApis, collector, undefined, { + featureFlags: [], + }); + expect(tree.root.instance).toBeDefined(); + }); + + it('should instantiate nodes with enabled predicate when predicateContext is not provided', () => { + const node = makeNodeWithEnabled({ + featureFlags: { $contains: 'the-flag' }, + }); + const tree = resolveAppTree('test-ext', [node.spec], collector); + // No predicateContext passed — predicate evaluation is skipped + instantiateAppNodeTree(tree.root, testApis, collector); + expect(tree.root.instance).toBeDefined(); + }); + }); }); diff --git a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts index 04082f8520..8cef89f497 100644 --- a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts +++ b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts @@ -18,16 +18,18 @@ import { ApiHolder, ExtensionDataContainer, ExtensionDataRef, - ExtensionFactoryMiddleware, ExtensionInput, - ResolvedExtensionInputs, } from '@backstage/frontend-plugin-api'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { ResolvedExtensionInputs } from '../../../frontend-plugin-api/src/wiring/createExtension'; +import { ExtensionFactoryMiddleware } from '../wiring/types'; import mapValues from 'lodash/mapValues'; import { AppNode, AppNodeInstance } from '@backstage/frontend-plugin-api'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { toInternalExtension } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition'; import { createExtensionDataContainer } from '@internal/frontend'; import { ErrorCollector } from '../wiring/createErrorCollector'; +import { evaluateFilterPredicate } from '@backstage/filter-predicates'; const INSTANTIATION_FAILED = new Error('Instantiation failed'); @@ -38,21 +40,22 @@ const INSTANTIATION_FAILED = new Error('Instantiation failed'); function mapWithFailures( iterable: Iterable, callback: (item: T) => U, + options?: { ignoreFailures?: boolean }, ): U[] { let failed = false; - const results = Array.from(iterable).map(item => { + const results = []; + for (const item of iterable) { try { - return callback(item); + results.push(callback(item)); } catch (error) { if (error === INSTANTIATION_FAILED) { failed = true; } else { throw error; } - return null as any; } - }); - if (failed) { + } + if (failed && !options?.ignoreFailures) { throw INSTANTIATION_FAILED; } return results; @@ -62,6 +65,19 @@ type Mutable = { -readonly [P in keyof T]: T[P]; }; +type InstantiateAppNodeSubtreeOptions = { + rootNode: AppNode; + apis: ApiHolder; + collector: ErrorCollector; + extensionFactoryMiddleware?: ExtensionFactoryMiddleware; + stopAtAttachment?(ctx: { node: AppNode; input: string }): boolean; + skipChild?(ctx: { node: AppNode; input: string; child: AppNode }): boolean; + onMissingApi?(ctx: { node: AppNode; apiRefId: string }): void; + predicateContext?: Record; + reuseExistingInstances?: boolean; + writeNodeInstances?: boolean; +}; + function resolveV1InputDataMap( dataMap: { [name in string]: ExtensionDataRef; @@ -119,7 +135,7 @@ function resolveInputDataContainer( .map(r => `'${r.id}'`) .join(', '); - collector.report({ + collector.child({ node: attachment }).report({ code: 'EXTENSION_INPUT_DATA_MISSING', message: `extension '${attachment.spec.id}' could not be attached because its output data (${provided}) does not match what the input '${inputName}' requires (${expected})`, }); @@ -246,10 +262,32 @@ function resolveV2Inputs( inputMap: { [inputName in string]: ExtensionInput }, attachments: ReadonlyMap, parentCollector: ErrorCollector<{ node: AppNode }>, + node: AppNode, ): ResolvedExtensionInputs<{ [inputName in string]: ExtensionInput }> { return mapValues(inputMap, (input, inputName) => { - const attachedNodes = attachments.get(inputName) ?? []; + const allAttachedNodes = attachments.get(inputName) ?? []; const collector = parentCollector.child({ inputName }); + const inputPluginId = node.spec.plugin.pluginId; + + const attachedNodes = input.config.internal + ? allAttachedNodes.filter(attachment => { + const attachmentPluginId = attachment.spec.plugin.pluginId; + if (attachmentPluginId !== inputPluginId) { + collector.report({ + code: 'EXTENSION_INPUT_INTERNAL_IGNORED', + message: + `extension '${attachment.spec.id}' from plugin '${attachmentPluginId}' attached to input '${inputName}' on '${node.spec.id}' was ignored, ` + + `the input is marked as internal and attached extensions must therefore be provided via an override or a module for the '${inputPluginId}' plugin, not the '${attachmentPluginId}' plugin`, + context: { + extensionId: attachment.spec.id, + plugin: attachment.spec.plugin, + }, + }); + return false; + } + return true; + }) + : allAttachedNodes; if (input.config.singleton) { if (attachedNodes.length > 1) { @@ -271,21 +309,37 @@ function resolveV2Inputs( } return undefined; } - return resolveInputDataContainer( - input.extensionData, - attachedNodes[0], - inputName, - collector, - ); + try { + return resolveInputDataContainer( + input.extensionData, + attachedNodes[0], + inputName, + collector, + ); + } catch (error) { + if (error === INSTANTIATION_FAILED) { + if (input.config.optional) { + return undefined; + } + collector.report({ + code: 'EXTENSION_ATTACHMENT_MISSING', + message: `input '${inputName}' is required but it failed to be instantiated`, + }); + } + throw error; + } } - return mapWithFailures(attachedNodes, attachment => - resolveInputDataContainer( - input.extensionData, - attachment, - inputName, - collector, - ), + return mapWithFailures( + attachedNodes, + attachment => + resolveInputDataContainer( + input.extensionData, + attachment, + inputName, + collector, + ), + { ignoreFailures: true }, ); }) as ResolvedExtensionInputs<{ [inputName in string]: ExtensionInput }>; } @@ -297,12 +351,28 @@ export function createAppNodeInstance(options: { apis: ApiHolder; attachments: ReadonlyMap; collector: ErrorCollector; + onMissingApi?(ctx: { node: AppNode; apiRefId: string }): void; }): AppNodeInstance | undefined { const { node, apis, attachments } = options; const collector = options.collector.child({ node }); const { id, extension, config } = node.spec; const extensionData = new Map(); const extensionDataRefs = new Set>(); + const scopedApis: ApiHolder = + options.onMissingApi === undefined + ? apis + : { + get(apiRef) { + const api = apis.get(apiRef); + if (api === undefined) { + options.onMissingApi?.({ + node, + apiRefId: apiRef.id, + }); + } + return api; + }, + }; let parsedConfig: { [x: string]: any }; try { @@ -327,7 +397,7 @@ export function createAppNodeInstance(options: { if (internalExtension.version === 'v1') { const namedOutputs = internalExtension.factory({ node, - apis, + apis: scopedApis, config: parsedConfig, inputs: resolveV1Inputs(internalExtension.inputs, attachments), }); @@ -348,12 +418,13 @@ export function createAppNodeInstance(options: { } else if (internalExtension.version === 'v2') { const context = { node, - apis, + apis: scopedApis, config: parsedConfig, inputs: resolveV2Inputs( internalExtension.inputs, attachments, collector, + node, ), }; const outputDataValues = options.extensionFactoryMiddleware @@ -459,6 +530,87 @@ export function createAppNodeInstance(options: { }; } +/** + * Starting at the provided node, instantiate a subtree without necessarily + * mutating the original app tree. + * + * @internal + */ +export function instantiateAppNodeSubtree( + options: InstantiateAppNodeSubtreeOptions, +): AppNode | undefined { + const instantiatedNodes = new WeakMap(); + + function createInstance(node: AppNode): AppNode | undefined { + if (instantiatedNodes.has(node)) { + return instantiatedNodes.get(node) ?? undefined; + } + if (options.reuseExistingInstances !== false && node.instance) { + instantiatedNodes.set(node, node); + return node; + } + if (node.spec.disabled) { + instantiatedNodes.set(node, null); + return undefined; + } + if ( + options.predicateContext !== undefined && + node.spec.if !== undefined && + !evaluateFilterPredicate(node.spec.if, options.predicateContext) + ) { + instantiatedNodes.set(node, null); + return undefined; + } + + const instantiatedAttachments = new Map(); + + for (const [input, children] of node.edges.attachments) { + if (options.stopAtAttachment?.({ node, input })) { + continue; + } + const instantiatedChildren = children.flatMap(child => { + if (options.skipChild?.({ node, input, child })) { + return []; + } + const childNode = createInstance(child); + return childNode ? [childNode] : []; + }); + if (instantiatedChildren.length > 0) { + instantiatedAttachments.set(input, instantiatedChildren); + } + } + + const instance = createAppNodeInstance({ + extensionFactoryMiddleware: options.extensionFactoryMiddleware, + node, + apis: options.apis, + attachments: instantiatedAttachments, + collector: options.collector, + onMissingApi: options.onMissingApi, + }); + if (!instance) { + instantiatedNodes.set(node, null); + return undefined; + } + + if (options.writeNodeInstances === false) { + const detachedNode: AppNode = { + spec: node.spec, + edges: node.edges, + instance, + }; + instantiatedNodes.set(node, detachedNode); + return detachedNode; + } + + (node as Mutable).instance = instance; + instantiatedNodes.set(node, node); + return node; + } + + return createInstance(options.rootNode); +} + /** * Starting at the provided node, instantiate all reachable nodes in the tree that have not been disabled. * @internal @@ -468,40 +620,45 @@ export function instantiateAppNodeTree( apis: ApiHolder, collector: ErrorCollector, extensionFactoryMiddleware?: ExtensionFactoryMiddleware, -): boolean { - function createInstance(node: AppNode): AppNodeInstance | undefined { - if (node.instance) { - return node.instance; - } - if (node.spec.disabled) { - return undefined; - } - - const instantiatedAttachments = new Map(); - - for (const [input, children] of node.edges.attachments) { - const instantiatedChildren = children.flatMap(child => { - const childInstance = createInstance(child); - if (!childInstance) { - return []; - } - return [child]; - }); - if (instantiatedChildren.length > 0) { - instantiatedAttachments.set(input, instantiatedChildren); + optionsOrPredicateContext?: + | { + stopAtAttachment?(ctx: { node: AppNode; input: string }): boolean; + skipChild?(ctx: { + node: AppNode; + input: string; + child: AppNode; + }): boolean; + onMissingApi?(ctx: { node: AppNode; apiRefId: string }): void; + predicateContext?: Record; } - } + | Record, +): boolean { + const options: { + stopAtAttachment?(ctx: { node: AppNode; input: string }): boolean; + skipChild?(ctx: { node: AppNode; input: string; child: AppNode }): boolean; + onMissingApi?(ctx: { node: AppNode; apiRefId: string }): void; + predicateContext?: Record; + } = + optionsOrPredicateContext && + ('stopAtAttachment' in optionsOrPredicateContext || + 'skipChild' in optionsOrPredicateContext || + 'onMissingApi' in optionsOrPredicateContext || + 'predicateContext' in optionsOrPredicateContext) + ? optionsOrPredicateContext + : { + predicateContext: optionsOrPredicateContext, + }; - (node as Mutable).instance = createAppNodeInstance({ - extensionFactoryMiddleware, - node, + return ( + instantiateAppNodeSubtree({ + rootNode, apis, - attachments: instantiatedAttachments, collector, - }); - - return node.instance; - } - - return createInstance(rootNode) !== undefined; + extensionFactoryMiddleware, + stopAtAttachment: options.stopAtAttachment, + skipChild: options.skipChild, + onMissingApi: options.onMissingApi, + predicateContext: options.predicateContext, + }) !== undefined + ); } diff --git a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts index 3ccdf5258f..ec56394b39 100644 --- a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts +++ b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts @@ -15,6 +15,8 @@ */ import { + createExtension, + createExtensionDataRef, createFrontendModule, createFrontendPlugin, Extension, @@ -506,4 +508,204 @@ describe('resolveAppNodeSpecs', () => { }, ]); }); + + it('should carry if predicate through to AppNodeSpec', () => { + const dataRef = createExtensionDataRef().with({ id: 'test.data' }); + const ifPredicate = { featureFlags: { $contains: 'my-flag' } }; + const plugin = createFrontendPlugin({ + pluginId: 'test-plugin', + extensions: [ + createExtension({ + attachTo: { id: 'app', input: 'root' }, + if: ifPredicate, + output: [dataRef], + factory: () => [dataRef('value')], + }), + ], + }); + const specs = resolveAppNodeSpecs({ + features: [plugin], + builtinExtensions: [], + parameters: [], + collector, + }); + expect(specs).toHaveLength(1); + expect(specs[0].if).toEqual(ifPredicate); + }); + + it('should apply plugin if predicates to all plugin extensions', () => { + const dataRef = createExtensionDataRef().with({ id: 'test.data' }); + const pluginIf = { featureFlags: { $contains: 'plugin-flag' } }; + const plugin = createFrontendPlugin({ + pluginId: 'test-plugin', + if: pluginIf, + extensions: [ + createExtension({ + name: 'one', + attachTo: { id: 'app', input: 'root' }, + output: [dataRef], + factory: () => [dataRef('one')], + }), + createExtension({ + name: 'two', + attachTo: { id: 'app', input: 'root' }, + output: [dataRef], + factory: () => [dataRef('two')], + }), + ], + }); + + const specs = resolveAppNodeSpecs({ + features: [plugin], + builtinExtensions: [], + parameters: [], + collector, + }); + + expect(specs).toHaveLength(2); + expect(specs[0].if).toEqual(pluginIf); + expect(specs[1].if).toEqual(pluginIf); + }); + + it('should allow plugin overrides to replace or remove plugin if predicates', () => { + const dataRef = createExtensionDataRef().with({ id: 'test.data' }); + const pluginIf = { featureFlags: { $contains: 'plugin-flag' } }; + const overrideIf = { permissions: { $contains: 'override.permission' } }; + const plugin = createFrontendPlugin({ + pluginId: 'test-plugin', + if: pluginIf, + extensions: [ + createExtension({ + name: 'one', + attachTo: { id: 'app', input: 'root' }, + output: [dataRef], + factory: () => [dataRef('one')], + }), + ], + }); + + const overriddenSpecs = resolveAppNodeSpecs({ + features: [plugin.withOverrides({ if: overrideIf })], + builtinExtensions: [], + parameters: [], + collector, + }); + const clearedSpecs = resolveAppNodeSpecs({ + features: [plugin.withOverrides({ if: undefined })], + builtinExtensions: [], + parameters: [], + collector, + }); + + expect(overriddenSpecs).toHaveLength(1); + expect(overriddenSpecs[0].if).toEqual(overrideIf); + expect(clearedSpecs).toHaveLength(1); + expect(clearedSpecs[0].if).toBeUndefined(); + }); + + it('should merge plugin and module if predicates with extension predicates', () => { + const dataRef = createExtensionDataRef().with({ id: 'test.data' }); + const pluginIf = { featureFlags: { $contains: 'plugin-flag' } }; + const moduleIf = { permissions: { $contains: 'module.permission' } }; + const extensionIf = { featureFlags: { $contains: 'extension-flag' } }; + const moduleExtensionIf = { featureFlags: { $contains: 'module-flag' } }; + const plugin = createFrontendPlugin({ + pluginId: 'test-plugin', + if: pluginIf, + extensions: [ + createExtension({ + name: 'plugin-extension', + attachTo: { id: 'app', input: 'root' }, + if: extensionIf, + output: [dataRef], + factory: () => [dataRef('plugin')], + }), + createExtension({ + name: 'module-extension', + attachTo: { id: 'app', input: 'root' }, + output: [dataRef], + factory: () => [dataRef('base')], + }), + ], + }); + const module = createFrontendModule({ + pluginId: 'test-plugin', + if: moduleIf, + extensions: [ + plugin.getExtension('test-plugin/module-extension').override({ + if: moduleExtensionIf, + factory: () => [dataRef('module')], + }), + ], + }); + + const specs = resolveAppNodeSpecs({ + features: [plugin, module], + builtinExtensions: [], + parameters: [], + collector, + }); + + expect(specs).toHaveLength(2); + expect(specs[0].id).toBe('test-plugin/plugin-extension'); + expect(specs[0].if).toEqual({ $all: [pluginIf, extensionIf] }); + expect(specs[1].id).toBe('test-plugin/module-extension'); + expect(specs[1].if).toEqual({ $all: [moduleIf, moduleExtensionIf] }); + }); + + it('should allow module extension overrides to replace or remove extension if predicates', () => { + const dataRef = createExtensionDataRef().with({ id: 'test.data' }); + const extensionIf = { featureFlags: { $contains: 'extension-flag' } }; + const overrideIf = { permissions: { $contains: 'override.permission' } }; + const plugin = createFrontendPlugin({ + pluginId: 'test-plugin', + extensions: [ + createExtension({ + name: 'extension', + attachTo: { id: 'app', input: 'root' }, + if: extensionIf, + output: [dataRef], + factory: () => [dataRef('base')], + }), + ], + }); + + const overriddenSpecs = resolveAppNodeSpecs({ + features: [ + plugin, + createFrontendModule({ + pluginId: 'test-plugin', + extensions: [ + plugin.getExtension('test-plugin/extension').override({ + if: overrideIf, + }), + ], + }), + ], + builtinExtensions: [], + parameters: [], + collector, + }); + const clearedSpecs = resolveAppNodeSpecs({ + features: [ + plugin, + createFrontendModule({ + pluginId: 'test-plugin', + extensions: [ + plugin.getExtension('test-plugin/extension').override({ + if: undefined, + }), + ], + }), + ], + builtinExtensions: [], + parameters: [], + collector, + }); + + expect(overriddenSpecs).toHaveLength(1); + expect(overriddenSpecs[0].if).toEqual(overrideIf); + expect(clearedSpecs).toHaveLength(1); + expect(clearedSpecs[0].if).toBeUndefined(); + }); }); diff --git a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts index d3b4ad6723..0e56fa4a98 100644 --- a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts +++ b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts @@ -20,6 +20,7 @@ import { FrontendFeature, FrontendPlugin, } from '@backstage/frontend-plugin-api'; +import { FilterPredicate } from '@backstage/filter-predicates'; import { ExtensionParameters } from './readAppExtensionsConfig'; import { AppNodeSpec } from '@backstage/frontend-plugin-api'; import { OpaqueFrontendPlugin } from '@internal/frontend'; @@ -32,6 +33,37 @@ import { import { toInternalExtension } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition'; import { ErrorCollector } from '../wiring/createErrorCollector'; +function normalizePlugin(plugin: FrontendPlugin): FrontendPlugin { + // Ensure pluginId is always set for plugins in the app + if (!plugin.pluginId && 'id' in plugin && typeof plugin.id === 'string') { + (plugin as any).pluginId = plugin.id; + } + return plugin; +} + +function combinePredicates( + left: FilterPredicate | undefined, + right: FilterPredicate | undefined, +) { + if (!left) { + return right; + } + if (!right) { + return left; + } + + return { $all: [left, right] }; +} + +function getExtensionPredicate(options: { + internalExtension: ReturnType; +}) { + if (options.internalExtension.version === 'v2') { + return options.internalExtension.if; + } + return undefined; +} + /** @internal */ export function resolveAppNodeSpecs(options: { features?: FrontendFeature[]; @@ -48,7 +80,9 @@ export function resolveAppNodeSpecs(options: { collector, } = options; - const plugins = features.filter(OpaqueFrontendPlugin.isType); + const plugins = features + .filter(OpaqueFrontendPlugin.isType) + .map(normalizePlugin); const modules = features.filter(isInternalFrontendModule); const filterForbidden = ( @@ -57,7 +91,7 @@ export function resolveAppNodeSpecs(options: { if (forbidden.has(extension.id)) { collector.report({ code: 'EXTENSION_IGNORED', - message: `It is forbidden to override the '${extension.id}' extension, attempted by the '${extension.plugin.id}' plugin`, + message: `It is forbidden to override the '${extension.id}' extension, attempted by the '${extension.plugin.pluginId}' plugin`, context: { plugin: extension.plugin, extensionId: extension.id, @@ -69,29 +103,53 @@ export function resolveAppNodeSpecs(options: { }; const pluginExtensions = plugins.flatMap(plugin => { - return OpaqueFrontendPlugin.toInternal(plugin) - .extensions.map(extension => ({ - ...extension, - plugin, - })) + const internalPlugin = OpaqueFrontendPlugin.toInternal(plugin); + return internalPlugin.extensions + .map(extension => { + const internalExtension = toInternalExtension(extension); + return { + ...internalExtension, + plugin, + if: combinePredicates( + internalPlugin.if, + internalExtension.version === 'v2' + ? internalExtension.if + : undefined, + ), + }; + }) .filter(filterForbidden); }); - const moduleExtensions = modules.flatMap(mod => - toInternalFrontendModule(mod) - .extensions.flatMap(extension => { + const moduleExtensions = modules.flatMap(mod => { + const internalModule = toInternalFrontendModule(mod); + return internalModule.extensions + .flatMap(extension => { + const internalExtension = toInternalExtension(extension); + // Modules for plugins that are not installed are ignored - const plugin = plugins.find(p => p.id === mod.pluginId); + const plugin = plugins.find(p => p.pluginId === mod.pluginId); if (!plugin) { return []; } - return [{ ...extension, plugin }]; + return [ + { + ...internalExtension, + plugin, + if: combinePredicates( + internalModule.if, + internalExtension.version === 'v2' + ? internalExtension.if + : undefined, + ), + }, + ]; }) - .filter(filterForbidden), - ); + .filter(filterForbidden); + }); const appPlugin = - plugins.find(plugin => plugin.id === 'app') ?? + plugins.find(plugin => plugin.pluginId === 'app') ?? createFrontendPlugin({ pluginId: 'app', }); @@ -106,6 +164,7 @@ export function resolveAppNodeSpecs(options: { source: plugin, attachTo: internalExtension.attachTo, disabled: internalExtension.disabled, + if: getExtensionPredicate({ internalExtension }), config: undefined as unknown, }, }; @@ -119,6 +178,7 @@ export function resolveAppNodeSpecs(options: { plugin: appPlugin, attachTo: internalExtension.attachTo, disabled: internalExtension.disabled, + if: getExtensionPredicate({ internalExtension }), config: undefined as unknown, }, }; @@ -138,6 +198,9 @@ export function resolveAppNodeSpecs(options: { configuredExtensions[index].extension = internalExtension; configuredExtensions[index].params.attachTo = internalExtension.attachTo; configuredExtensions[index].params.disabled = internalExtension.disabled; + configuredExtensions[index].params.if = getExtensionPredicate({ + internalExtension, + }); } else { // Add the extension as a new one when not overriding an existing one configuredExtensions.push({ @@ -147,6 +210,7 @@ export function resolveAppNodeSpecs(options: { source: extension.plugin, attachTo: internalExtension.attachTo, disabled: internalExtension.disabled, + if: getExtensionPredicate({ internalExtension }), config: undefined, }, }); @@ -159,7 +223,7 @@ export function resolveAppNodeSpecs(options: { if (seenExtensionIds.has(extension.id)) { collector.report({ code: 'EXTENSION_IGNORED', - message: `The '${extension.id}' extension from the '${params.plugin.id}' plugin is a duplicate and will be ignored`, + message: `The '${extension.id}' extension from the '${params.plugin.pluginId}' plugin is a duplicate and will be ignored`, context: { plugin: params.plugin, extensionId: extension.id, @@ -225,6 +289,7 @@ export function resolveAppNodeSpecs(options: { attachTo: param.params.attachTo, extension: param.extension, disabled: param.params.disabled, + if: param.params.if, plugin: param.params.plugin, source: param.params.source, config: param.params.config, diff --git a/packages/frontend-app-api/src/tree/resolveAppTree.test.ts b/packages/frontend-app-api/src/tree/resolveAppTree.test.ts index bffc156509..a8e63f82e0 100644 --- a/packages/frontend-app-api/src/tree/resolveAppTree.test.ts +++ b/packages/frontend-app-api/src/tree/resolveAppTree.test.ts @@ -149,7 +149,7 @@ describe('buildAppTree', () => { attachTo: [ { id: 'a', input: 'x' }, { id: 'b', input: 'x' }, - ], + ] as any, }, { ...baseSpec, @@ -157,7 +157,7 @@ describe('buildAppTree', () => { attachTo: [ { id: 'b', input: 'x' }, { id: 'c', input: 'x' }, - ], + ] as any, }, ], collector, diff --git a/packages/frontend-app-api/src/tree/resolveAppTree.ts b/packages/frontend-app-api/src/tree/resolveAppTree.ts index f2f31f8950..c2054c4bac 100644 --- a/packages/frontend-app-api/src/tree/resolveAppTree.ts +++ b/packages/frontend-app-api/src/tree/resolveAppTree.ts @@ -165,6 +165,12 @@ export function resolveAppTree( if (spec.id === rootNodeId) { rootNode = node; } else if (Array.isArray(spec.attachTo)) { + // eslint-disable-next-line no-console + console.warn( + `Extension '${spec.id}' is using multiple attachment points which is deprecated and will be removed in a future release. ` + + `Use a Utility API instead to share functionality across multiple locations. ` + + `See https://backstage.io/docs/frontend-system/architecture/27-sharing-extensions for migration guidance.`, + ); let foundFirstParent = false; for (const origAttachTo of spec.attachTo) { let attachTo = origAttachTo; diff --git a/packages/frontend-app-api/src/wiring/FrontendApiRegistry.test.ts b/packages/frontend-app-api/src/wiring/FrontendApiRegistry.test.ts new file mode 100644 index 0000000000..65e6010182 --- /dev/null +++ b/packages/frontend-app-api/src/wiring/FrontendApiRegistry.test.ts @@ -0,0 +1,72 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + type AnyApiFactory, + createApiRef, +} from '@backstage/frontend-plugin-api'; +import { + FrontendApiRegistry, + FrontendApiResolver, +} from './FrontendApiRegistry'; + +describe('FrontendApiResolver', () => { + it('should cache falsy API values', () => { + const falseApiRef = createApiRef({ id: 'test.false' }); + const falseFactoryFn = jest.fn(() => false); + const registry = new FrontendApiRegistry(); + + registry.register({ + api: falseApiRef, + deps: {}, + factory: falseFactoryFn, + } as AnyApiFactory); + + const resolver = new FrontendApiResolver({ primaryRegistry: registry }); + + expect(resolver.get(falseApiRef)).toBe(false); + expect(resolver.get(falseApiRef)).toBe(false); + expect(falseFactoryFn).toHaveBeenCalledTimes(1); + }); + + it('should resolve falsy dependencies', () => { + const falseApiRef = createApiRef({ id: 'test.false' }); + const dependentApiRef = createApiRef({ id: 'test.dependent' }); + const falseFactoryFn = jest.fn(() => false); + const dependentFactoryFn = jest.fn((deps: { falseDependency: boolean }) => + deps.falseDependency === false ? 'resolved' : 'unexpected', + ); + const registry = new FrontendApiRegistry(); + + registry.register({ + api: falseApiRef, + deps: {}, + factory: falseFactoryFn, + } as AnyApiFactory); + registry.register({ + api: dependentApiRef, + deps: { falseDependency: falseApiRef }, + factory: dependentFactoryFn, + } as AnyApiFactory); + + const resolver = new FrontendApiResolver({ primaryRegistry: registry }); + + expect(resolver.get(dependentApiRef)).toBe('resolved'); + expect(dependentFactoryFn).toHaveBeenCalledWith({ + falseDependency: false, + }); + }); +}); diff --git a/packages/frontend-app-api/src/wiring/FrontendApiRegistry.ts b/packages/frontend-app-api/src/wiring/FrontendApiRegistry.ts new file mode 100644 index 0000000000..00d565170a --- /dev/null +++ b/packages/frontend-app-api/src/wiring/FrontendApiRegistry.ts @@ -0,0 +1,139 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + AnyApiFactory, + AnyApiRef, + ApiFactory, + ApiHolder, + ApiRef, +} from '@backstage/frontend-plugin-api'; + +export class FrontendApiRegistry { + private readonly factories = new Map(); + + register(factory: AnyApiFactory) { + if (this.factories.has(factory.api.id)) { + return false; + } + + this.factories.set(factory.api.id, factory); + return true; + } + + registerAll(factories: AnyApiFactory[]) { + for (const factory of factories) { + this.register(factory); + } + } + + set(factory: AnyApiFactory) { + this.factories.set(factory.api.id, factory); + } + + setAll(factories: Iterable) { + for (const factory of factories) { + this.set(factory); + } + } + + get( + api: ApiRef, + ): ApiFactory | undefined { + const factory = this.factories.get(api.id); + if (!factory) { + return undefined; + } + + return factory as ApiFactory; + } + + getAllApis() { + const refs = new Set(); + for (const factory of this.factories.values()) { + refs.add(factory.api); + } + return refs; + } +} + +export class FrontendApiResolver implements ApiHolder { + private readonly apis = new Map(); + private readonly primaryRegistry?: FrontendApiRegistry; + private readonly secondaryRegistry?: FrontendApiRegistry; + private readonly fallbackApis?: ApiHolder; + + constructor(options: { + primaryRegistry?: FrontendApiRegistry; + secondaryRegistry?: FrontendApiRegistry; + fallbackApis?: ApiHolder; + }) { + this.primaryRegistry = options.primaryRegistry; + this.secondaryRegistry = options.secondaryRegistry; + this.fallbackApis = options.fallbackApis; + } + + get(ref: ApiRef): T | undefined { + return this.load(ref); + } + + isMaterialized(apiRefId: string) { + return this.apis.has(apiRefId); + } + + invalidate(apiRefIds?: Iterable) { + if (apiRefIds === undefined) { + this.apis.clear(); + return; + } + + for (const apiRefId of apiRefIds) { + this.apis.delete(apiRefId); + } + } + + private load(ref: ApiRef, loading: AnyApiRef[] = []): T | undefined { + const existing = this.apis.get(ref.id); + if (this.apis.has(ref.id)) { + return existing as T; + } + + const factory = + this.primaryRegistry?.get(ref) ?? this.secondaryRegistry?.get(ref); + if (!factory) { + return this.fallbackApis?.get(ref); + } + + if (loading.includes(factory.api)) { + throw new Error(`Circular dependency of api factory for ${factory.api}`); + } + + const deps = {} as { [name: string]: unknown }; + for (const [key, depRef] of Object.entries(factory.deps)) { + const dep = this.load(depRef, [...loading, factory.api]); + if (dep === undefined) { + throw new Error( + `No API factory available for dependency ${depRef} of dependent ${factory.api}`, + ); + } + deps[key] = dep; + } + + const api = factory.factory(deps); + this.apis.set(ref.id, api); + return api as T; + } +} diff --git a/packages/frontend-app-api/src/wiring/apiFactories.ts b/packages/frontend-app-api/src/wiring/apiFactories.ts new file mode 100644 index 0000000000..6ccc75b9c1 --- /dev/null +++ b/packages/frontend-app-api/src/wiring/apiFactories.ts @@ -0,0 +1,304 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + ApiBlueprint, + AnyApiFactory, + ApiHolder, + AppNode, + FrontendFeature, + featureFlagsApiRef, +} from '@backstage/frontend-plugin-api'; +import { OpaqueFrontendPlugin } from '@internal/frontend'; +import { instantiateAppNodeSubtree } from '../tree/instantiateAppNodeTree'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { + isInternalFrontendModule, + toInternalFrontendModule, +} from '../../../frontend-plugin-api/src/wiring/createFrontendModule'; +import { ErrorCollector } from './createErrorCollector'; +import { + FrontendApiRegistry, + FrontendApiResolver, +} from './FrontendApiRegistry'; +import { type ExtensionPredicateContext } from './predicates'; + +export type ApiFactoryEntry = { + node: AppNode; + pluginId: string; + factory: AnyApiFactory; +}; + +/** + * Registers feature flag declarations on an already prepared API holder. + * + * This is primarily used when bootstrap reuses APIs from a provided session + * state rather than building a fresh registry from bootstrap-visible factories. + */ +export function registerFeatureFlagDeclarationsInHolder( + apis: ApiHolder, + features: FrontendFeature[], +) { + const featureFlagApi = apis.get(featureFlagsApiRef); + if (featureFlagApi) { + registerFeatureFlagDeclarations(featureFlagApi, features); + } +} + +/** + * Decorates the feature flags API factory so plugin and module declarations are + * registered whenever that API is instantiated. + */ +export function wrapFeatureFlagApiFactory( + factory: AnyApiFactory, + features: FrontendFeature[], +) { + if (factory.api.id !== featureFlagsApiRef.id) { + return factory; + } + + return { + ...factory, + factory(deps) { + const featureFlagApi = factory.factory( + deps, + ) as typeof featureFlagsApiRef.T; + registerFeatureFlagDeclarations(featureFlagApi, features); + return featureFlagApi; + }, + } as AnyApiFactory; +} + +/** + * Reconciles deferred API factories into the finalized API registry. + * + * It preserves bootstrap-frozen APIs, allows safe deferred additions, and + * reports cases where bootstrap-visible extensions relied on APIs that only + * became available during finalization. + */ +export function syncFinalApiFactories(options: { + deferredApiNodes: Iterable; + appApiRegistry: FrontendApiRegistry; + apiResolver: FrontendApiResolver; + collector: ErrorCollector; + features: FrontendFeature[]; + bootstrapApiFactoryEntries: ReadonlyMap; + bootstrapMissingApiAccesses: Map; + predicateContext: ExtensionPredicateContext; +}) { + const finalApiEntries = collectApiFactoryEntries({ + apiNodes: options.deferredApiNodes, + collector: options.collector, + predicateContext: options.predicateContext, + entries: new Map(options.bootstrapApiFactoryEntries), + }); + // Only newly introduced or still-safe overrides are registered here. Any + // bootstrap-materialized API remains frozen for the lifetime of the app. + const changedEntries = Array.from(finalApiEntries.values()).filter(entry => { + const bootstrapEntry = options.bootstrapApiFactoryEntries.get( + entry.factory.api.id, + ); + if (!bootstrapEntry) { + return true; + } + if (bootstrapEntry.factory === entry.factory) { + return false; + } + if (options.apiResolver.isMaterialized(entry.factory.api.id)) { + options.collector.report({ + code: 'EXTENSION_BOOTSTRAP_API_OVERRIDE_IGNORED', + message: + `Extension '${entry.node.spec.id}' tried to override API ` + + `'${entry.factory.api.id}' after it had already been materialized during bootstrap. ` + + 'The bootstrap implementation was kept and the deferred override was ignored.', + context: { + node: entry.node, + apiRefId: entry.factory.api.id, + bootstrapNode: bootstrapEntry.node, + pluginId: entry.pluginId, + bootstrapPluginId: bootstrapEntry.pluginId, + }, + }); + return false; + } + return true; + }); + const changedFactories = changedEntries.map(entry => + wrapFeatureFlagApiFactory(entry.factory, options.features), + ); + options.appApiRegistry.setAll(changedFactories); + options.apiResolver.invalidate( + changedFactories.map(factory => factory.api.id), + ); + for (const bootstrapAccess of options.bootstrapMissingApiAccesses.values()) { + if ( + options.bootstrapApiFactoryEntries.has(bootstrapAccess.apiRefId) || + !finalApiEntries.has(bootstrapAccess.apiRefId) + ) { + continue; + } + + options.collector.report({ + code: 'EXTENSION_BOOTSTRAP_API_UNAVAILABLE', + message: + `Extension '${bootstrapAccess.node.spec.id}' tried to access API ` + + `'${bootstrapAccess.apiRefId}' during bootstrap before it was available. ` + + 'That API became available during finalization, so bootstrap-visible extensions must not depend on deferred APIs.', + context: { + node: bootstrapAccess.node, + apiRefId: bootstrapAccess.apiRefId, + }, + }); + } +} + +const EMPTY_API_HOLDER: ApiHolder = { + get() { + return undefined; + }, +}; + +function registerFeatureFlagDeclarations( + featureFlagApi: typeof featureFlagsApiRef.T, + features: FrontendFeature[], +) { + for (const feature of features) { + if (OpaqueFrontendPlugin.isType(feature)) { + OpaqueFrontendPlugin.toInternal(feature).featureFlags.forEach(flag => + featureFlagApi.registerFlag({ + name: flag.name, + description: flag.description, + pluginId: feature.id, + }), + ); + } + if (isInternalFrontendModule(feature)) { + toInternalFrontendModule(feature).featureFlags.forEach(flag => + featureFlagApi.registerFlag({ + name: flag.name, + description: flag.description, + pluginId: feature.pluginId, + }), + ); + } + } +} + +/** + * Instantiates API extension subtrees in isolation and extracts the factories + * they provide without mutating the live app tree. + * + * The collected entries are later used both for bootstrap registration and for + * the finalization-time reconciliation of deferred API roots. + */ +export function collectApiFactoryEntries(options: { + apiNodes: Iterable; + collector: ErrorCollector; + predicateContext?: ExtensionPredicateContext; + entries?: Map; +}): Map { + const factoriesById = options.entries ?? new Map(); + for (const apiNode of options.apiNodes) { + // API extensions are instantiated in isolation so we can inspect the + // produced factories without mutating the live app tree. + const detachedApiNode = instantiateAppNodeSubtree({ + rootNode: apiNode, + apis: EMPTY_API_HOLDER, + collector: options.collector, + predicateContext: options.predicateContext, + writeNodeInstances: false, + reuseExistingInstances: false, + }); + if (!detachedApiNode) { + continue; + } + const apiFactory = detachedApiNode.instance?.getData( + ApiBlueprint.dataRefs.factory, + ); + if (apiFactory) { + const apiRefId = apiFactory.api.id; + const ownerId = getApiOwnerId(apiRefId); + const pluginId = apiNode.spec.plugin.pluginId ?? 'app'; + const existingFactory = factoriesById.get(apiRefId); + + // This allows modules to override factories provided by the plugin, but + // it rejects API overrides from other plugins. In the event of a + // conflict, the owning plugin is attempted to be inferred from the API + // reference ID. + if (existingFactory && existingFactory.pluginId !== pluginId) { + const shouldReplace = + ownerId === pluginId && existingFactory.pluginId !== ownerId; + const acceptedPluginId = shouldReplace + ? pluginId + : existingFactory.pluginId; + const rejectedPluginId = shouldReplace + ? existingFactory.pluginId + : pluginId; + + options.collector.report({ + code: 'API_FACTORY_CONFLICT', + message: `API '${apiRefId}' is already provided by plugin '${acceptedPluginId}', cannot also be provided by '${rejectedPluginId}'.`, + context: { + node: apiNode, + apiRefId, + pluginId: rejectedPluginId, + existingPluginId: acceptedPluginId, + }, + }); + if (shouldReplace) { + factoriesById.set(apiRefId, { + pluginId, + node: apiNode, + factory: apiFactory, + }); + } + continue; + } + + factoriesById.set(apiRefId, { + pluginId, + node: apiNode, + factory: apiFactory, + }); + } else { + options.collector.report({ + code: 'API_EXTENSION_INVALID', + message: `API extension '${apiNode.spec.id}' did not output an API factory`, + context: { + node: apiNode, + }, + }); + } + } + + return factoriesById; +} + +// TODO(Rugvip): It would be good if this was more explicit, but I think that +// might need to wait for some future update for API factories. +function getApiOwnerId(apiRefId: string): string { + const [prefix, ...rest] = apiRefId.split('.'); + if (!prefix) { + return apiRefId; + } + if (prefix === 'core') { + return 'app'; + } + if (prefix === 'plugin' && rest[0]) { + return rest[0]; + } + return prefix; +} diff --git a/packages/frontend-app-api/src/wiring/createErrorCollector.ts b/packages/frontend-app-api/src/wiring/createErrorCollector.ts index c20e92d0f0..7077e0e9c4 100644 --- a/packages/frontend-app-api/src/wiring/createErrorCollector.ts +++ b/packages/frontend-app-api/src/wiring/createErrorCollector.ts @@ -38,6 +38,14 @@ export type AppErrorTypes = { EXTENSION_INPUT_DATA_MISSING: { context: { node: AppNode; inputName: string }; }; + EXTENSION_INPUT_INTERNAL_IGNORED: { + context: { + node: AppNode; + inputName: string; + extensionId: string; + plugin: FrontendPlugin; + }; + }; EXTENSION_ATTACHMENT_CONFLICT: { context: { node: AppNode; inputName: string }; }; @@ -66,6 +74,29 @@ export type AppErrorTypes = { API_EXTENSION_INVALID: { context: { node: AppNode }; }; + API_FACTORY_CONFLICT: { + context: { + node: AppNode; + apiRefId: string; + pluginId: string; + existingPluginId: string; + }; + }; + EXTENSION_BOOTSTRAP_PREDICATE_IGNORED: { + context: { node: AppNode }; + }; + EXTENSION_BOOTSTRAP_API_UNAVAILABLE: { + context: { node: AppNode; apiRefId: string }; + }; + EXTENSION_BOOTSTRAP_API_OVERRIDE_IGNORED: { + context: { + node: AppNode; + apiRefId: string; + bootstrapNode: AppNode; + pluginId: string; + bootstrapPluginId: string; + }; + }; // routing ROUTE_DUPLICATE: { context: { routeId: string }; diff --git a/packages/frontend-app-api/src/wiring/createPluginInfoAttacher.ts b/packages/frontend-app-api/src/wiring/createPluginInfoAttacher.ts index dc2b54f534..9a88590d7c 100644 --- a/packages/frontend-app-api/src/wiring/createPluginInfoAttacher.ts +++ b/packages/frontend-app-api/src/wiring/createPluginInfoAttacher.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { ConfigApi } from '@backstage/core-plugin-api'; +import { ConfigApi } from '@backstage/frontend-plugin-api'; import { FrontendFeature, FrontendPluginInfo, @@ -77,7 +77,10 @@ export function createPluginInfoAttacher( }), }); - const infoWithOverrides = applyInfoOverrides(plugin.id, resolvedInfo); + const infoWithOverrides = applyInfoOverrides( + plugin.pluginId, + resolvedInfo, + ); return normalizePluginInfo(infoWithOverrides); }), }; diff --git a/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx b/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx index 5622af0448..3e841a1283 100644 --- a/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx +++ b/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx @@ -20,75 +20,94 @@ import { coreExtensionData, createExtension, createFrontendPlugin, + createFrontendModule, ApiBlueprint, + createApiRef, createRouteRef, createExternalRouteRef, createExtensionInput, useRouteRef, + useApi, analyticsApiRef, createExtensionDataRef, } from '@backstage/frontend-plugin-api'; -import { screen, render } from '@testing-library/react'; +import { act, render, screen } from '@testing-library/react'; import { createSpecializedApp } from './createSpecializedApp'; +import { + FinalizedSpecializedApp, + prepareSpecializedApp, + PreparedSpecializedApp, +} from './prepareSpecializedApp'; import { mockApis, TestApiRegistry } from '@backstage/test-utils'; -import { configApiRef, featureFlagsApiRef } from '@backstage/core-plugin-api'; +import { + configApiRef, + featureFlagsApiRef, + IdentityApi, + identityApiRef, +} from '@backstage/core-plugin-api'; import { MemoryRouter } from 'react-router-dom'; import { ApiProvider, ConfigReader } from '@backstage/core-app-api'; -import { Fragment } from 'react'; +import { ComponentType, Fragment, useEffect, useState } from 'react'; +import appPluginOriginal from '@backstage/plugin-app'; + +const signInPageComponentDataRef = createExtensionDataRef< + ComponentType<{ onSignInSuccess(identity: IdentityApi): void }> +>().with({ id: 'core.sign-in-page.component' }); + +function makeAppPlugin(label: string = 'Test') { + return createFrontendPlugin({ + pluginId: 'app', + extensions: [ + createExtension({ + attachTo: { id: 'root', input: 'app' }, + output: [coreExtensionData.reactElement], + factory: () => [coreExtensionData.reactElement(

    {label}
    )], + }), + ], + }); +} + +function renderPreparedBootstrap(preparedApp: PreparedSpecializedApp) { + const bootstrapApp = preparedApp.getBootstrapApp(); + const bootstrapElement = bootstrapApp.element; + if (!bootstrapElement) { + throw new Error('Expected bootstrap tree to expose a root element'); + } + + render(bootstrapElement); +} + +async function waitForFinalizedApp(preparedApp: PreparedSpecializedApp) { + return new Promise(resolve => { + preparedApp.onFinalized(resolve); + }); +} describe('createSpecializedApp', () => { + const appPlugin = appPluginOriginal.withOverrides({ + extensions: [ + appPluginOriginal.getExtension('app/layout').override({ + factory: () => [coreExtensionData.reactElement(
    App Layout
    )], + }), + ], + }); + it('should render the root app', () => { const app = createSpecializedApp({ - features: [ - createFrontendPlugin({ - pluginId: 'test', - extensions: [ - createExtension({ - attachTo: { id: 'root', input: 'app' }, - output: [coreExtensionData.reactElement], - factory: () => [coreExtensionData.reactElement(
    Test
    )], - }), - ], - }), - ], + features: [makeAppPlugin()], }); - render(app.tree.root.instance!.getData(coreExtensionData.reactElement)); + render(app.element); expect(screen.getByText('Test')).toBeInTheDocument(); }); it('should deduplicate features keeping the last received one', () => { const app = createSpecializedApp({ - features: [ - createFrontendPlugin({ - pluginId: 'test', - extensions: [ - createExtension({ - attachTo: { id: 'root', input: 'app' }, - output: [coreExtensionData.reactElement], - factory: () => [ - coreExtensionData.reactElement(
    Test 1
    ), - ], - }), - ], - }), - createFrontendPlugin({ - pluginId: 'test', - extensions: [ - createExtension({ - attachTo: { id: 'root', input: 'app' }, - output: [coreExtensionData.reactElement], - factory: () => [ - coreExtensionData.reactElement(
    Test 2
    ), - ], - }), - ], - }), - ], + features: [makeAppPlugin('Test 1'), makeAppPlugin('Test 2')], }); - render(app.tree.root.instance!.getData(coreExtensionData.reactElement)); + render(app.element); expect(screen.getByText('Test 2')).toBeInTheDocument(); }); @@ -114,18 +133,51 @@ describe('createSpecializedApp', () => { ], }); - render(app.tree.root.instance!.getData(coreExtensionData.reactElement)); + render(app.element); expect(screen.getByText('Test foo')).toBeInTheDocument(); }); + it('should warn and ignore bootstrap-visible if predicates', () => { + const app = createSpecializedApp({ + features: [ + createFrontendPlugin({ + pluginId: 'test', + extensions: [ + createExtension({ + name: 'conditional-root', + attachTo: { id: 'root', input: 'app' }, + if: { featureFlags: { $contains: 'test-flag' } }, + output: [coreExtensionData.reactElement], + factory: () => [ + coreExtensionData.reactElement(
    Ignored Predicate
    ), + ], + }), + ], + }), + ], + }); + + render(app.element); + + expect(screen.getByText('Ignored Predicate')).toBeInTheDocument(); + expect(app.errors).toEqual([ + expect.objectContaining({ + code: 'EXTENSION_BOOTSTRAP_PREDICATE_IGNORED', + }), + ]); + }); + it('should support APIs and feature flags', async () => { const flags = new Array<{ name: string; pluginId: string }>(); const app = createSpecializedApp({ features: [ createFrontendPlugin({ pluginId: 'test', - featureFlags: [{ name: 'a' }, { name: 'b' }], + featureFlags: [ + { name: 'a' }, + { name: 'b', description: 'Feature B description' }, + ], extensions: [ createExtension({ attachTo: { id: 'root', input: 'app' }, @@ -164,84 +216,15 @@ describe('createSpecializedApp', () => { ], }); - render(app.tree.root.instance!.getData(coreExtensionData.reactElement)); + render(app.element); expect(screen.getByText('flags:test=a,test=b')).toBeInTheDocument(); - expect(app.apis).toMatchInlineSnapshot(` - ApiResolver { - "apis": Map { - "core.featureflags" => { - "getRegisteredFlags": [Function], - "registerFlag": [Function], - }, - }, - "factories": ApiFactoryRegistry { - "factories": Map { - "core.featureflags" => { - "factory": { - "api": ApiRefImpl { - "config": { - "id": "core.featureflags", - }, - }, - "deps": {}, - "factory": [Function], - }, - "priority": 10, - }, - "core.app-tree" => { - "factory": { - "api": ApiRefImpl { - "config": { - "id": "core.app-tree", - }, - }, - "deps": {}, - "factory": [Function], - }, - "priority": 100, - }, - "core.config" => { - "factory": { - "api": ApiRefImpl { - "config": { - "id": "core.config", - }, - }, - "deps": {}, - "factory": [Function], - }, - "priority": 100, - }, - "core.route-resolution" => { - "factory": { - "api": ApiRefImpl { - "config": { - "id": "core.route-resolution", - }, - }, - "deps": {}, - "factory": [Function], - }, - "priority": 100, - }, - "core.identity" => { - "factory": { - "api": ApiRefImpl { - "config": { - "id": "core.identity", - }, - }, - "deps": {}, - "factory": [Function], - }, - "priority": 100, - }, - }, - }, - } - `); + expect(flags).toEqual([ + { name: 'a', pluginId: 'test' }, + { name: 'b', pluginId: 'test', description: 'Feature B description' }, + ]); + expect(app.sessionState).toBeDefined(); }); it('should initialize the APIs in the correct order to allow for overrides', () => { @@ -249,8 +232,9 @@ describe('createSpecializedApp', () => { const app = createSpecializedApp({ features: [ - createFrontendPlugin({ - pluginId: 'first', + makeAppPlugin(), + createFrontendModule({ + pluginId: 'app', extensions: [ ApiBlueprint.make({ params: defineParams => @@ -264,9 +248,8 @@ describe('createSpecializedApp', () => { }), ], }), - createFrontendPlugin({ - pluginId: 'test', - featureFlags: [{ name: 'a' }, { name: 'b' }], + createFrontendModule({ + pluginId: 'app', extensions: [ createExtension({ attachTo: { id: 'root', input: 'app' }, @@ -306,19 +289,145 @@ describe('createSpecializedApp', () => { ], }); - render(app.tree.root.instance!.getData(coreExtensionData.reactElement)); + render(app.element); expect(mockAnalyticsApi).toHaveBeenCalled(); }); - it('should use provided apis', async () => { + it('should select the API factory from the owning plugin on conflict', () => { + const testApiRef = createApiRef<{ value: string }>({ id: 'test.api' }); + const appRootPlugin = createFrontendPlugin({ + pluginId: 'app', + extensions: [ + createExtension({ + attachTo: { id: 'root', input: 'app' }, + output: [coreExtensionData.reactElement], + factory: ({ apis }) => [ + coreExtensionData.reactElement( +
    Selected API: {apis.get(testApiRef)!.value}
    , + ), + ], + }), + ], + }); + + const app = createSpecializedApp({ + features: [ + appRootPlugin, + createFrontendPlugin({ + pluginId: 'other-before', + extensions: [ + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: testApiRef, + deps: {}, + factory: () => ({ value: 'other' }), + }), + }), + ], + }), + createFrontendPlugin({ + pluginId: 'test', + extensions: [ + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: testApiRef, + deps: {}, + factory: () => ({ value: 'owner' }), + }), + }), + ], + }), + createFrontendPlugin({ + pluginId: 'other-after', + extensions: [ + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: testApiRef, + deps: {}, + factory: () => ({ value: 'other' }), + }), + }), + ], + }), + ], + }); + + expect(app.errors).toEqual([ + expect.objectContaining({ + code: 'API_FACTORY_CONFLICT', + message: expect.stringContaining("API 'test.api'"), + }), + expect.objectContaining({ + code: 'API_FACTORY_CONFLICT', + message: expect.stringContaining("API 'test.api'"), + }), + ]); + + render(app.element); + expect(screen.getByText('Selected API: owner')).toBeInTheDocument(); + }); + + it('should allow API overrides within the same plugin', () => { + const testApiRef = createApiRef<{ value: string }>({ id: 'test.api' }); + const appRootPlugin = createFrontendPlugin({ + pluginId: 'app', + extensions: [ + createExtension({ + attachTo: { id: 'root', input: 'app' }, + output: [coreExtensionData.reactElement], + factory: ({ apis }) => [ + coreExtensionData.reactElement( +
    Selected API: {apis.get(testApiRef)!.value}
    , + ), + ], + }), + ], + }); + + const app = createSpecializedApp({ + features: [ + appRootPlugin, + createFrontendPlugin({ + pluginId: 'test', + extensions: [ + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: testApiRef, + deps: {}, + factory: () => ({ value: 'plugin' }), + }), + }), + ], + }), + createFrontendModule({ + pluginId: 'test', + extensions: [ + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: testApiRef, + deps: {}, + factory: () => ({ value: 'module' }), + }), + }), + ], + }), + ], + }); + + expect(app.errors).toBeUndefined(); + render(app.element); + expect(screen.getByText('Selected API: module')).toBeInTheDocument(); + }); + + it('should reuse provided apis', async () => { + const testApiRef = createApiRef<{ value: string }>({ id: 'test.api' }); const app = createSpecializedApp({ - advanced: { - apis: TestApiRegistry.from([ - configApiRef, - new ConfigReader({ anything: 'config' }), - ]), - }, features: [ createFrontendPlugin({ pluginId: 'test', @@ -329,8 +438,9 @@ describe('createSpecializedApp', () => { factory: ({ apis }) => [ coreExtensionData.reactElement(
    - providedApis: - {apis.get(configApiRef)!.getString('anything')} + reusedApis: + {apis.get(configApiRef)!.getString('anything')}: + {apis.get(testApiRef)!.value}
    , ), ], @@ -338,28 +448,17 @@ describe('createSpecializedApp', () => { ], }), ], + advanced: { + apis: TestApiRegistry.from( + [configApiRef, new ConfigReader({ anything: 'config' })], + [testApiRef, { value: 'from-apis' }], + ), + }, }); render(app.tree.root.instance!.getData(coreExtensionData.reactElement)); - expect(screen.getByText('providedApis:config')).toBeInTheDocument(); - - expect(app.apis).toMatchInlineSnapshot(` - TestApiRegistry { - "apis": Map { - "core.config" => ConfigReader { - "context": "mock-config", - "data": { - "anything": "config", - }, - "fallback": undefined, - "filteredKeys": undefined, - "notifiedFilteredKeys": Set {}, - "prefix": "", - }, - }, - } - `); + expect(screen.getByText('reusedApis:config:from-apis')).toBeInTheDocument(); }); it('should make the app structure available through the AppTreeApi', async () => { @@ -569,12 +668,13 @@ describe('createSpecializedApp', () => { output: [coreExtensionData.reactElement], factory: () => [coreExtensionData.reactElement(
    )], }), + // Test backward compatibility - runtime still supports multiple attachment points createExtension({ name: 'cloned', attachTo: [ { id: 'test/a', input: 'children' }, { id: 'test/b', input: 'children' }, - ], + ] as any, output: [coreExtensionData.reactElement], factory: () => [coreExtensionData.reactElement(
    )], }), @@ -794,4 +894,897 @@ describe('createSpecializedApp', () => { }); }); }); + + describe('prepareSpecializedApp', () => { + it('should accept session state through advanced options', () => { + const originalApp = createSpecializedApp({ + features: [makeAppPlugin('Original')], + }); + const preparedApp = prepareSpecializedApp({ + features: [makeAppPlugin('Prepared')], + advanced: { + sessionState: originalApp.sessionState, + }, + }); + + const app = preparedApp.finalize(); + + render(app.tree.root.instance!.getData(coreExtensionData.reactElement)); + + expect(screen.getByText('Prepared')).toBeInTheDocument(); + }); + + it('should reject finalize after selecting onFinalized', () => { + const preparedApp = prepareSpecializedApp({ + features: [makeAppPlugin()], + }); + + const unsubscribe = preparedApp.onFinalized(() => {}); + + expect(() => preparedApp.finalize()).toThrow( + 'prepareSpecializedApp only supports using either onFinalized() or finalize(), not both', + ); + + unsubscribe(); + }); + + it('should reject onFinalized after selecting finalize', () => { + const preparedApp = prepareSpecializedApp({ + features: [makeAppPlugin()], + }); + + preparedApp.finalize(); + + expect(() => preparedApp.onFinalized(() => {})).toThrow( + 'prepareSpecializedApp only supports using either onFinalized() or finalize(), not both', + ); + }); + + it('should synchronously finalize feature flag predicates without sign-in', async () => { + const featureFlagsApi = { + isActive: jest.fn((name: string) => name === 'test-flag'), + registerFlag: jest.fn(), + getRegisteredFlags: () => [], + save: jest.fn(), + } as unknown as typeof featureFlagsApiRef.T; + const noSignInAppPlugin = appPluginOriginal.withOverrides({ + extensions: [ + appPluginOriginal + .getExtension('sign-in-page:app') + .override({ disabled: true }), + ], + }); + const preparedApp = prepareSpecializedApp({ + features: [ + noSignInAppPlugin, + createFrontendPlugin({ + pluginId: 'test', + featureFlags: [{ name: 'test-flag' }], + extensions: [ + createExtension({ + name: 'bootstrap-element', + attachTo: { id: 'app/root', input: 'elements' }, + output: [coreExtensionData.reactElement], + factory: () => [ + coreExtensionData.reactElement(
    Bootstrap Element
    ), + ], + }), + createExtension({ + name: 'deferred-element', + attachTo: { id: 'app/root', input: 'elements' }, + if: { featureFlags: { $contains: 'test-flag' } }, + output: [coreExtensionData.reactElement], + factory: () => [ + coreExtensionData.reactElement(
    Deferred Element
    ), + ], + }), + ], + }), + createFrontendModule({ + pluginId: 'app', + extensions: [ + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: featureFlagsApiRef, + deps: {}, + factory: () => featureFlagsApi, + }), + }), + ], + }), + ], + }); + + renderPreparedBootstrap(preparedApp); + + expect(screen.getByText('Bootstrap Element')).toBeInTheDocument(); + expect(screen.queryByText('Deferred Element')).not.toBeInTheDocument(); + + const finalizedApp = preparedApp.finalize(); + render( + finalizedApp.tree.root.instance!.getData( + coreExtensionData.reactElement, + ), + ); + + expect(featureFlagsApi.isActive).toHaveBeenCalledWith('test-flag'); + await expect( + screen.findByText('Deferred Element'), + ).resolves.toBeInTheDocument(); + }); + + it('should defer conditional api roots without resetting visible api instances', () => { + const featureFlagsApi = { + isActive: jest.fn((name: string) => name === 'test-flag'), + registerFlag: jest.fn(), + getRegisteredFlags: () => [], + save: jest.fn(), + } as unknown as typeof featureFlagsApiRef.T; + const visibleApiExtension = ApiBlueprint.make({ + name: 'visible-api', + params: defineParams => + defineParams({ + api: createApiRef<{ value: string }>({ id: 'test.visible-api' }), + deps: {}, + factory: () => ({ value: 'visible' }), + }), + }); + const deferredApiExtension = ApiBlueprint.make({ + name: 'deferred-api', + params: defineParams => + defineParams({ + api: createApiRef<{ value: string }>({ id: 'test.deferred-api' }), + deps: {}, + factory: () => ({ value: 'deferred' }), + }), + }).override({ + if: { featureFlags: { $contains: 'test-flag' } }, + }); + const preparedApp = prepareSpecializedApp({ + features: [ + makeAppPlugin(), + createFrontendPlugin({ + pluginId: 'test', + featureFlags: [{ name: 'test-flag' }], + extensions: [ + visibleApiExtension, + deferredApiExtension, + ApiBlueprint.make({ + name: 'feature-flags', + params: defineParams => + defineParams({ + api: featureFlagsApiRef, + deps: {}, + factory: () => featureFlagsApi, + }), + }), + ], + }), + ], + }); + + const bootstrapTree = preparedApp.getBootstrapApp().tree; + const apiNodes = bootstrapTree.root.edges.attachments.get('apis') ?? []; + const visibleApiNode = apiNodes.find( + node => node.spec.id === 'api:test/visible-api', + ); + const deferredApiNode = apiNodes.find( + node => node.spec.id === 'api:test/deferred-api', + ); + + expect(visibleApiNode?.instance).toBeDefined(); + expect(deferredApiNode?.instance).toBeUndefined(); + + const visibleApiInstance = visibleApiNode?.instance; + preparedApp.finalize(); + + expect(visibleApiNode?.instance).toBe(visibleApiInstance); + expect(deferredApiNode?.instance).toBeDefined(); + }); + + it('should ignore deferred overrides of materialized bootstrap APIs', () => { + const apiRef = createApiRef<{ value: string }>({ + id: 'test.bootstrap-frozen-api', + }); + let bootstrapApiValue: string | undefined; + let finalApiValue: string | undefined; + const featureFlagsApi = { + isActive: jest.fn((name: string) => name === 'test-flag'), + registerFlag: jest.fn(), + getRegisteredFlags: () => [], + save: jest.fn(), + } as unknown as typeof featureFlagsApiRef.T; + const noSignInAppPlugin = appPluginOriginal.withOverrides({ + extensions: [ + appPluginOriginal + .getExtension('sign-in-page:app') + .override({ disabled: true }), + ], + }); + const preparedApp = prepareSpecializedApp({ + features: [ + noSignInAppPlugin, + createFrontendPlugin({ + pluginId: 'test', + featureFlags: [{ name: 'test-flag' }], + extensions: [ + ApiBlueprint.make({ + name: 'bootstrap-api', + params: defineParams => + defineParams({ + api: apiRef, + deps: {}, + factory: () => ({ value: 'bootstrap' }), + }), + }), + ApiBlueprint.make({ + name: 'deferred-api', + params: defineParams => + defineParams({ + api: apiRef, + deps: {}, + factory: () => ({ value: 'final' }), + }), + }).override({ + if: { featureFlags: { $contains: 'test-flag' } }, + }), + createExtension({ + name: 'bootstrap-reader', + attachTo: { id: 'app/root', input: 'elements' }, + output: [coreExtensionData.reactElement], + factory: ({ apis }) => { + bootstrapApiValue = apis.get(apiRef)?.value; + return [ + coreExtensionData.reactElement(
    Bootstrap Reader
    ), + ]; + }, + }), + createExtension({ + name: 'final-reader', + attachTo: { id: 'app/root', input: 'elements' }, + if: { featureFlags: { $contains: 'test-flag' } }, + output: [coreExtensionData.reactElement], + factory: ({ apis }) => { + finalApiValue = apis.get(apiRef)?.value; + return [ + coreExtensionData.reactElement(
    Final Reader
    ), + ]; + }, + }), + ], + }), + createFrontendModule({ + pluginId: 'app', + extensions: [ + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: featureFlagsApiRef, + deps: {}, + factory: () => featureFlagsApi, + }), + }), + ], + }), + ], + }); + + renderPreparedBootstrap(preparedApp); + expect(bootstrapApiValue).toBe('bootstrap'); + + const finalizedApp = preparedApp.finalize(); + + expect(featureFlagsApi.isActive).toHaveBeenCalledWith('test-flag'); + expect(finalApiValue).toBe('bootstrap'); + expect(finalizedApp.errors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + code: 'EXTENSION_BOOTSTRAP_API_OVERRIDE_IGNORED', + context: expect.objectContaining({ + apiRefId: apiRef.id, + }), + }), + ]), + ); + }); + + it('should allow deferred overrides of bootstrap APIs that were not materialized', () => { + const apiRef = createApiRef<{ value: string }>({ + id: 'test.bootstrap-overridable-api', + }); + let finalApiValue: string | undefined; + const featureFlagsApi = { + isActive: jest.fn((name: string) => name === 'test-flag'), + registerFlag: jest.fn(), + getRegisteredFlags: () => [], + save: jest.fn(), + } as unknown as typeof featureFlagsApiRef.T; + const noSignInAppPlugin = appPluginOriginal.withOverrides({ + extensions: [ + appPluginOriginal + .getExtension('sign-in-page:app') + .override({ disabled: true }), + ], + }); + const preparedApp = prepareSpecializedApp({ + features: [ + noSignInAppPlugin, + createFrontendPlugin({ + pluginId: 'test', + featureFlags: [{ name: 'test-flag' }], + extensions: [ + ApiBlueprint.make({ + name: 'bootstrap-api', + params: defineParams => + defineParams({ + api: apiRef, + deps: {}, + factory: () => ({ value: 'bootstrap' }), + }), + }), + ApiBlueprint.make({ + name: 'deferred-api', + params: defineParams => + defineParams({ + api: apiRef, + deps: {}, + factory: () => ({ value: 'final' }), + }), + }).override({ + if: { featureFlags: { $contains: 'test-flag' } }, + }), + createExtension({ + name: 'bootstrap-element', + attachTo: { id: 'app/root', input: 'elements' }, + output: [coreExtensionData.reactElement], + factory: () => [ + coreExtensionData.reactElement(
    Bootstrap Element
    ), + ], + }), + createExtension({ + name: 'final-reader', + attachTo: { id: 'app/root', input: 'elements' }, + if: { featureFlags: { $contains: 'test-flag' } }, + output: [coreExtensionData.reactElement], + factory: ({ apis }) => { + finalApiValue = apis.get(apiRef)?.value; + return [ + coreExtensionData.reactElement(
    Final Reader
    ), + ]; + }, + }), + ], + }), + createFrontendModule({ + pluginId: 'app', + extensions: [ + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: featureFlagsApiRef, + deps: {}, + factory: () => featureFlagsApi, + }), + }), + ], + }), + ], + }); + + renderPreparedBootstrap(preparedApp); + expect(screen.getByText('Bootstrap Element')).toBeInTheDocument(); + + const finalizedApp = preparedApp.finalize(); + + expect(featureFlagsApi.isActive).toHaveBeenCalledWith('test-flag'); + expect(finalApiValue).toBe('final'); + expect(finalizedApp.errors ?? []).not.toEqual( + expect.arrayContaining([ + expect.objectContaining({ + code: 'EXTENSION_BOOTSTRAP_API_OVERRIDE_IGNORED', + context: expect.objectContaining({ + apiRefId: apiRef.id, + }), + }), + ]), + ); + }); + + it('should defer app root children until finalize', async () => { + const identityApi = { + getProfileInfo: async () => ({ displayName: 'Test User' }), + getBackstageIdentity: async () => ({ + type: 'user' as const, + userEntityRef: 'user:default/test-user', + ownershipEntityRefs: ['user:default/test-user'], + }), + getCredentials: async () => ({ token: 'token' }), + signOut: async () => {}, + }; + const appLayoutFactory = jest.fn(() => [ + coreExtensionData.reactElement(
    App Layout
    ), + ]); + const phasedAppPlugin = appPluginOriginal.withOverrides({ + extensions: [ + appPluginOriginal.getExtension('app/layout').override({ + factory: appLayoutFactory, + }), + ], + }); + let onSignInSuccess: ((identity: IdentityApi) => void) | undefined; + + const preparedApp = prepareSpecializedApp({ + features: [ + phasedAppPlugin, + createFrontendModule({ + pluginId: 'app', + extensions: [ + phasedAppPlugin.getExtension('sign-in-page:app').override({ + factory: () => { + function SignInPage(props: { + onSignInSuccess(identity: IdentityApi): void; + }) { + onSignInSuccess = props.onSignInSuccess; + return
    Custom Sign In
    ; + } + + return [signInPageComponentDataRef(SignInPage)]; + }, + }), + ], + }), + ], + }); + + renderPreparedBootstrap(preparedApp); + await expect( + screen.findByText('Custom Sign In'), + ).resolves.toBeInTheDocument(); + + const finalizedAppPromise = waitForFinalizedApp(preparedApp); + if (!onSignInSuccess) { + throw new Error('Expected sign-in success callback to be captured'); + } + const triggerSignInSuccess = onSignInSuccess; + act(() => { + triggerSignInSuccess(identityApi); + }); + + const finalizedApp = await finalizedAppPromise; + expect(appLayoutFactory).toHaveBeenCalledTimes(1); + render( + finalizedApp.tree.root.instance!.getData( + coreExtensionData.reactElement, + ), + ); + + expect(screen.getByText('App Layout')).toBeInTheDocument(); + expect(appLayoutFactory).toHaveBeenCalledTimes(1); + }); + + it('should expose a sign-in page element and finalize with the captured identity', async () => { + const identityApi = { + getProfileInfo: async () => ({ displayName: 'Test User' }), + getBackstageIdentity: async () => ({ + type: 'user' as const, + userEntityRef: 'user:default/test-user', + ownershipEntityRefs: ['user:default/test-user'], + }), + getCredentials: async () => ({ token: 'token' }), + signOut: async () => {}, + }; + const identityAppPlugin = appPluginOriginal.withOverrides({ + extensions: [ + appPluginOriginal.getExtension('app/layout').override({ + factory: () => { + function IdentityReader() { + const [identity, setIdentity] = useState(); + const appIdentityApi = useApi(identityApiRef); + + useEffect(() => { + void appIdentityApi.getBackstageIdentity().then(result => { + setIdentity(result.userEntityRef); + }); + }, [appIdentityApi]); + + return
    Identity: {identity}
    ; + } + + return [coreExtensionData.reactElement()]; + }, + }), + ], + }); + let onSignInSuccess: ((identity: IdentityApi) => void) | undefined; + + const preparedApp = prepareSpecializedApp({ + features: [ + identityAppPlugin, + createFrontendModule({ + pluginId: 'app', + extensions: [ + identityAppPlugin.getExtension('sign-in-page:app').override({ + factory: () => { + function SignInPage(props: { + onSignInSuccess(identity: IdentityApi): void; + }) { + onSignInSuccess = props.onSignInSuccess; + return
    Custom Sign In
    ; + } + + return [signInPageComponentDataRef(SignInPage)]; + }, + }), + ], + }), + ], + }); + + renderPreparedBootstrap(preparedApp); + await expect( + screen.findByText('Custom Sign In'), + ).resolves.toBeInTheDocument(); + + const finalizedAppPromise = waitForFinalizedApp(preparedApp); + if (!onSignInSuccess) { + throw new Error('Expected sign-in success callback to be captured'); + } + const triggerSignInSuccess = onSignInSuccess; + act(() => { + triggerSignInSuccess(identityApi); + }); + + const finalizedApp = await finalizedAppPromise; + render( + finalizedApp.tree.root.instance!.getData( + coreExtensionData.reactElement, + ), + ); + await expect( + screen.findByText('Identity: user:default/test-user'), + ).resolves.toBeInTheDocument(); + }); + + it('should reuse predicate context gathered during sign-in completion', async () => { + const identityApi = { + getProfileInfo: async () => ({ displayName: 'Test User' }), + getBackstageIdentity: async () => ({ + type: 'user' as const, + userEntityRef: 'user:default/test-user', + ownershipEntityRefs: ['user:default/test-user'], + }), + getCredentials: async () => ({ token: 'token' }), + signOut: async () => {}, + }; + const featureFlagsApi = { + isActive: jest.fn((name: string) => name === 'test-flag'), + registerFlag: jest.fn(), + getRegisteredFlags: () => [], + save: jest.fn(), + } as unknown as typeof featureFlagsApiRef.T; + const gatedAppPlugin = appPluginOriginal.withOverrides({ + extensions: [ + appPluginOriginal.getExtension('app/layout').override({ + if: { featureFlags: { $contains: 'test-flag' } }, + factory: () => [ + coreExtensionData.reactElement(
    Flagged Layout
    ), + ], + }), + ], + }); + let onSignInSuccess: ((identity: IdentityApi) => void) | undefined; + + const preparedApp = prepareSpecializedApp({ + features: [ + gatedAppPlugin, + createFrontendModule({ + pluginId: 'app', + extensions: [ + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: featureFlagsApiRef, + deps: {}, + factory: () => featureFlagsApi, + }), + }), + gatedAppPlugin.getExtension('sign-in-page:app').override({ + factory: () => { + function SignInPage(props: { + onSignInSuccess(identity: IdentityApi): void; + }) { + onSignInSuccess = props.onSignInSuccess; + return
    Custom Sign In
    ; + } + + return [signInPageComponentDataRef(SignInPage)]; + }, + }), + ], + }), + ], + }); + + renderPreparedBootstrap(preparedApp); + await expect( + screen.findByText('Custom Sign In'), + ).resolves.toBeInTheDocument(); + + const finalizedAppPromise = waitForFinalizedApp(preparedApp); + if (!onSignInSuccess) { + throw new Error('Expected sign-in success callback to be captured'); + } + const triggerSignInSuccess = onSignInSuccess; + act(() => { + triggerSignInSuccess(identityApi); + }); + + const finalizedApp = await finalizedAppPromise; + expect(featureFlagsApi.isActive).toHaveBeenCalledWith('test-flag'); + expect(featureFlagsApi.isActive).toHaveBeenCalledTimes(1); + render( + finalizedApp.tree.root.instance!.getData( + coreExtensionData.reactElement, + ), + ); + + expect(screen.getByText('Flagged Layout')).toBeInTheDocument(); + }); + + it('should defer conditional app root elements until finalize', async () => { + const identityApi = { + getProfileInfo: async () => ({ displayName: 'Test User' }), + getBackstageIdentity: async () => ({ + type: 'user' as const, + userEntityRef: 'user:default/test-user', + ownershipEntityRefs: ['user:default/test-user'], + }), + getCredentials: async () => ({ token: 'token' }), + signOut: async () => {}, + }; + const featureFlagsApi = { + isActive: jest.fn((name: string) => name === 'test-flag'), + registerFlag: jest.fn(), + getRegisteredFlags: () => [], + save: jest.fn(), + } as unknown as typeof featureFlagsApiRef.T; + + const preparedApp = prepareSpecializedApp({ + features: [ + appPluginOriginal, + createFrontendPlugin({ + pluginId: 'test', + featureFlags: [{ name: 'test-flag' }], + extensions: [ + createExtension({ + name: 'bootstrap-element', + attachTo: { id: 'app/root', input: 'elements' }, + output: [coreExtensionData.reactElement], + factory: () => [ + coreExtensionData.reactElement(
    Bootstrap Element
    ), + ], + }), + createExtension({ + name: 'deferred-element', + attachTo: { id: 'app/root', input: 'elements' }, + if: { featureFlags: { $contains: 'test-flag' } }, + output: [coreExtensionData.reactElement], + factory: () => [ + coreExtensionData.reactElement(
    Deferred Element
    ), + ], + }), + ], + }), + createFrontendModule({ + pluginId: 'app', + extensions: [ + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: featureFlagsApiRef, + deps: {}, + factory: () => featureFlagsApi, + }), + }), + appPluginOriginal.getExtension('sign-in-page:app').override({ + factory: () => { + function SignInPage(props: { + onSignInSuccess(identity: IdentityApi): void; + }) { + useEffect(() => { + props.onSignInSuccess(identityApi); + }, [props]); + return
    Custom Sign In
    ; + } + + return [signInPageComponentDataRef(SignInPage)]; + }, + }), + ], + }), + ], + }); + + renderPreparedBootstrap(preparedApp); + await expect( + screen.findByText('Bootstrap Element'), + ).resolves.toBeInTheDocument(); + expect(screen.queryByText('Deferred Element')).not.toBeInTheDocument(); + + const finalizedApp = await waitForFinalizedApp(preparedApp); + render( + finalizedApp.tree.root.instance!.getData( + coreExtensionData.reactElement, + ), + ); + + await expect( + screen.findByText('Deferred Element'), + ).resolves.toBeInTheDocument(); + }); + + it('should warn when bootstrap extensions access APIs that appear during finalization', async () => { + const identityApi = { + getProfileInfo: async () => ({ displayName: 'Test User' }), + getBackstageIdentity: async () => ({ + type: 'user' as const, + userEntityRef: 'user:default/test-user', + ownershipEntityRefs: ['user:default/test-user'], + }), + getCredentials: async () => ({ token: 'token' }), + signOut: async () => {}, + }; + const delayedApiRef = createApiRef<{ value: string }>({ + id: 'test.delayed-api', + }); + const featureFlagsApi = { + isActive: jest.fn((name: string) => name === 'test-flag'), + registerFlag: jest.fn(), + getRegisteredFlags: () => [], + save: jest.fn(), + } as unknown as typeof featureFlagsApiRef.T; + + const preparedApp = prepareSpecializedApp({ + features: [ + appPluginOriginal, + createFrontendPlugin({ + pluginId: 'test', + featureFlags: [{ name: 'test-flag' }], + extensions: [ + createExtension({ + name: 'bootstrap-api-reader', + attachTo: { id: 'app/root', input: 'elements' }, + output: [coreExtensionData.reactElement], + factory: ({ apis }) => { + const delayedApi = apis.get(delayedApiRef); + return [ + coreExtensionData.reactElement( +
    + Bootstrap API:{' '} + {delayedApi ? delayedApi.value : 'missing'} +
    , + ), + ]; + }, + }), + ApiBlueprint.make({ + name: 'delayed-api', + params: defineParams => + defineParams({ + api: delayedApiRef, + deps: {}, + factory: () => ({ value: 'ready' }), + }), + }).override({ + if: { featureFlags: { $contains: 'test-flag' } }, + }), + ], + }), + createFrontendModule({ + pluginId: 'app', + extensions: [ + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: featureFlagsApiRef, + deps: {}, + factory: () => featureFlagsApi, + }), + }), + appPluginOriginal.getExtension('sign-in-page:app').override({ + factory: () => { + function SignInPage(props: { + onSignInSuccess(identity: IdentityApi): void; + }) { + useEffect(() => { + props.onSignInSuccess(identityApi); + }, [props]); + return
    Custom Sign In
    ; + } + + return [signInPageComponentDataRef(SignInPage)]; + }, + }), + ], + }), + ], + }); + + renderPreparedBootstrap(preparedApp); + await expect( + screen.findByText('Bootstrap API: missing'), + ).resolves.toBeInTheDocument(); + + const finalizedApp = await waitForFinalizedApp(preparedApp); + + expect(finalizedApp.errors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + code: 'EXTENSION_BOOTSTRAP_API_UNAVAILABLE', + context: expect.objectContaining({ + apiRefId: delayedApiRef.id, + }), + }), + ]), + ); + }); + + it('should gate finalize behind internal async sign-in finalization', async () => { + const identityApi = { + getProfileInfo: async () => ({ displayName: 'Test User' }), + getBackstageIdentity: async () => ({ + type: 'user' as const, + userEntityRef: 'user:default/test-user', + ownershipEntityRefs: ['user:default/test-user'], + }), + getCredentials: async () => ({ token: 'token' }), + signOut: async () => {}, + }; + let onSignInSuccess: ((identity: IdentityApi) => void) | undefined; + + const preparedApp = prepareSpecializedApp({ + features: [ + appPlugin, + createFrontendModule({ + pluginId: 'app', + extensions: [ + appPlugin.getExtension('sign-in-page:app').override({ + factory: () => { + function SignInPage(props: { + onSignInSuccess(identity: IdentityApi): void; + }) { + onSignInSuccess = props.onSignInSuccess; + return
    Custom Sign In
    ; + } + + return [signInPageComponentDataRef(SignInPage)]; + }, + }), + ], + }), + ], + }); + + renderPreparedBootstrap(preparedApp); + await expect( + screen.findByText('Custom Sign In'), + ).resolves.toBeInTheDocument(); + + expect(() => preparedApp.finalize()).toThrow( + 'prepareSpecializedApp requires waiting for the bootstrap app to be ready before calling finalize()', + ); + + if (!onSignInSuccess) { + throw new Error('Expected sign-in success callback to be captured'); + } + const triggerSignInSuccess = onSignInSuccess; + act(() => { + triggerSignInSuccess(identityApi); + }); + expect(() => preparedApp.finalize()).toThrow( + 'prepareSpecializedApp requires waiting for the bootstrap app to be ready before calling finalize()', + ); + }); + }); }); diff --git a/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx b/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx index 9e75d19454..dc2f8e0562 100644 --- a/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx +++ b/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx @@ -14,210 +14,28 @@ * limitations under the License. */ -import { ConfigReader } from '@backstage/config'; import { - ApiBlueprint, - AppTree, - AppTreeApi, - appTreeApiRef, - RouteRef, - ExternalRouteRef, - SubRouteRef, - AnyRouteRefParams, - RouteFunc, - RouteResolutionApi, - createApiFactory, - routeResolutionApiRef, - AppNode, + ApiHolder, + ConfigApi, ExtensionFactoryMiddleware, FrontendFeature, } from '@backstage/frontend-plugin-api'; -import { - AnyApiFactory, - ApiHolder, - ConfigApi, - configApiRef, - featureFlagsApiRef, - identityApiRef, -} from '@backstage/core-plugin-api'; -import { ApiFactoryRegistry, ApiResolver } from '@backstage/core-app-api'; -import { - createExtensionDataContainer, - OpaqueFrontendPlugin, -} from '@internal/frontend'; - -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { - resolveExtensionDefinition, - toInternalExtension, -} from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition'; - -import { - extractRouteInfoFromAppNode, - RouteInfo, -} from '../routing/extractRouteInfoFromAppNode'; - import { CreateAppRouteBinder } from '../routing'; -import { RouteResolver } from '../routing/RouteResolver'; -import { resolveRouteBindings } from '../routing/resolveRouteBindings'; -import { collectRouteIds } from '../routing/collectRouteIds'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { FrontendPluginInfoResolver } from './createPluginInfoAttacher'; import { - toInternalFrontendModule, - isInternalFrontendModule, -} from '../../../frontend-plugin-api/src/wiring/createFrontendModule'; -import { getBasePath } from '../routing/getBasePath'; -import { Root } from '../extensions/Root'; -import { resolveAppTree } from '../tree/resolveAppTree'; -import { resolveAppNodeSpecs } from '../tree/resolveAppNodeSpecs'; -import { readAppExtensionsConfig } from '../tree/readAppExtensionsConfig'; -import { instantiateAppNodeTree } from '../tree/instantiateAppNodeTree'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { ApiRegistry } from '../../../core-app-api/src/apis/system/ApiRegistry'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { AppIdentityProxy } from '../../../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy'; -import { BackstageRouteObject } from '../routing/types'; -import { matchRoutes } from 'react-router-dom'; -import { - createPluginInfoAttacher, - FrontendPluginInfoResolver, -} from './createPluginInfoAttacher'; -import { createRouteAliasResolver } from '../routing/RouteAliasResolver'; -import { - AppError, - createErrorCollector, - ErrorCollector, -} from './createErrorCollector'; + createSessionStateFromApis, + CreateSpecializedAppInternalOptions, + FinalizedSpecializedApp, + prepareSpecializedApp, +} from './prepareSpecializedApp'; -function deduplicateFeatures( - allFeatures: FrontendFeature[], -): FrontendFeature[] { - // Start by removing duplicates by reference - const features = Array.from(new Set(allFeatures)); - - // Plugins are deduplicated by ID, last one wins - const seenIds = new Set(); - return features - .reverse() - .filter(feature => { - if (!OpaqueFrontendPlugin.isType(feature)) { - return true; - } - if (seenIds.has(feature.id)) { - return false; - } - seenIds.add(feature.id); - return true; - }) - .reverse(); -} - -// Helps delay callers from reaching out to the API before the app tree has been materialized -class AppTreeApiProxy implements AppTreeApi { - #routeInfo?: RouteInfo; - private readonly tree: AppTree; - private readonly appBasePath: string; - - constructor(tree: AppTree, appBasePath: string) { - this.tree = tree; - this.appBasePath = appBasePath; - } - - private checkIfInitialized() { - if (!this.#routeInfo) { - throw new Error( - `You can't access the AppTreeApi during initialization of the app tree. Please move occurrences of this out of the initialization of the factory`, - ); - } - } - - getTree() { - this.checkIfInitialized(); - - return { tree: this.tree }; - } - - getNodesByRoutePath(routePath: string): { nodes: AppNode[] } { - this.checkIfInitialized(); - - let path = routePath; - if (path.startsWith(this.appBasePath)) { - path = path.slice(this.appBasePath.length); - } - - const matchedRoutes = matchRoutes(this.#routeInfo!.routeObjects, path); - - const matchedAppNodes = - matchedRoutes - ?.filter(routeObj => !!routeObj.route.appNode) - .map(routeObj => routeObj.route.appNode!) || []; - - return { nodes: matchedAppNodes }; - } - - initialize(routeInfo: RouteInfo) { - this.#routeInfo = routeInfo; - } -} - -// Helps delay callers from reaching out to the API before the app tree has been materialized -class RouteResolutionApiProxy implements RouteResolutionApi { - #delegate: RouteResolutionApi | undefined; - #routeObjects: BackstageRouteObject[] | undefined; - - private readonly routeBindings: Map; - private readonly appBasePath: string; - - constructor( - routeBindings: Map, - appBasePath: string, - ) { - this.routeBindings = routeBindings; - this.appBasePath = appBasePath; - } - - resolve( - anyRouteRef: - | RouteRef - | SubRouteRef - | ExternalRouteRef, - options?: { sourcePath?: string }, - ): RouteFunc | undefined { - if (!this.#delegate) { - throw new Error( - `You can't access the RouteResolver during initialization of the app tree. Please move occurrences of this out of the initialization of the factory`, - ); - } - - return this.#delegate.resolve(anyRouteRef, options); - } - - initialize( - routeInfo: RouteInfo, - routeRefsById: Map, - ) { - this.#delegate = new RouteResolver( - routeInfo.routePaths, - routeInfo.routeParents, - routeInfo.routeObjects, - this.routeBindings, - this.appBasePath, - routeInfo.routeAliasResolver, - routeRefsById, - ); - this.#routeObjects = routeInfo.routeObjects; - - return routeInfo; - } - - getRouteObjects() { - return this.#routeObjects; - } -} +export type { CreateSpecializedAppInternalOptions }; /** * Options for {@link createSpecializedApp}. * + * @deprecated Use `PrepareSpecializedAppOptions` with `prepareSpecializedApp` instead. + * * @public */ export type CreateSpecializedAppOptions = { @@ -246,26 +64,10 @@ export type CreateSpecializedAppOptions = { */ advanced?: { /** - * A replacement API holder implementation to use. - * - * By default, a new API holder will be constructed automatically based on - * the other inputs. If you pass in a custom one here, none of that - * automation will take place - so you will have to take care to supply all - * those APIs yourself. + * APIs to expose to the app during startup. */ apis?: ApiHolder; - /** - * If set to true, the system will silently accept and move on if - * encountering config for extensions that do not exist. The default is to - * reject such config to help catch simple mistakes. - * - * This flag can be useful in some scenarios where you have a dynamic set of - * extensions enabled at different times, but also increases the risk of - * accidentally missing e.g. simple typos in your config. - */ - allowUnknownExtensionConfig?: boolean; - /** * Applies one or more middleware on every extension, as they are added to * the application. @@ -289,178 +91,24 @@ export type CreateSpecializedAppOptions = { * intended for use in tests or specialized setups. Typically you want to use * `createApp` from `@backstage/frontend-defaults` instead. * + * @deprecated Use `prepareSpecializedApp` instead. + * * @public */ -export function createSpecializedApp(options?: CreateSpecializedAppOptions): { - apis: ApiHolder; - tree: AppTree; - errors?: AppError[]; -} { - const config = options?.config ?? new ConfigReader({}, 'empty-config'); - const features = deduplicateFeatures(options?.features ?? []).map( - createPluginInfoAttacher(config, options?.advanced?.pluginInfoResolver), - ); +export function createSpecializedApp( + options?: CreateSpecializedAppOptions, +): FinalizedSpecializedApp { + const sessionState = options?.advanced?.apis + ? createSessionStateFromApis(options.advanced.apis) + : undefined; - const collector = createErrorCollector(); - - const tree = resolveAppTree( - 'root', - resolveAppNodeSpecs({ - features, - builtinExtensions: [ - resolveExtensionDefinition(Root, { namespace: 'root' }), - ], - parameters: readAppExtensionsConfig(config), - forbidden: new Set(['root']), - collector, - }), - collector, - ); - - const factories = createApiFactories({ tree, collector }); - const appBasePath = getBasePath(config); - const appTreeApi = new AppTreeApiProxy(tree, appBasePath); - - const routeRefsById = collectRouteIds(features, collector); - const routeResolutionApi = new RouteResolutionApiProxy( - resolveRouteBindings(options?.bindRoutes, config, routeRefsById, collector), - appBasePath, - ); - - const appIdentityProxy = new AppIdentityProxy(); - const apis = - options?.advanced?.apis ?? - createApiHolder({ - factories, - staticFactories: [ - createApiFactory(appTreeApiRef, appTreeApi), - createApiFactory(configApiRef, config), - createApiFactory(routeResolutionApiRef, routeResolutionApi), - createApiFactory(identityApiRef, appIdentityProxy), - ], - }); - - const featureFlagApi = apis.get(featureFlagsApiRef); - if (featureFlagApi) { - for (const feature of features) { - if (OpaqueFrontendPlugin.isType(feature)) { - OpaqueFrontendPlugin.toInternal(feature).featureFlags.forEach(flag => - featureFlagApi.registerFlag({ - name: flag.name, - pluginId: feature.id, - }), - ); - } - if (isInternalFrontendModule(feature)) { - toInternalFrontendModule(feature).featureFlags.forEach(flag => - featureFlagApi.registerFlag({ - name: flag.name, - pluginId: feature.pluginId, - }), - ); - } - } - } - - // Now instantiate the entire tree, which will skip anything that's already been instantiated - instantiateAppNodeTree( - tree.root, - apis, - collector, - mergeExtensionFactoryMiddleware( - options?.advanced?.extensionFactoryMiddleware, - ), - ); - - const routeInfo = extractRouteInfoFromAppNode( - tree.root, - createRouteAliasResolver(routeRefsById), - ); - - routeResolutionApi.initialize(routeInfo, routeRefsById.routes); - appTreeApi.initialize(routeInfo); - - return { apis, tree, errors: collector.collectErrors() }; -} - -function createApiFactories(options: { - tree: AppTree; - collector: ErrorCollector; -}): AnyApiFactory[] { - const emptyApiHolder = ApiRegistry.from([]); - const factories = new Array(); - - for (const apiNode of options.tree.root.edges.attachments.get('apis') ?? []) { - if (!instantiateAppNodeTree(apiNode, emptyApiHolder, options.collector)) { - continue; - } - const apiFactory = apiNode.instance?.getData(ApiBlueprint.dataRefs.factory); - if (apiFactory) { - factories.push(apiFactory); - } else { - options.collector.report({ - code: 'API_EXTENSION_INVALID', - message: `API extension '${apiNode.spec.id}' did not output an API factory`, - context: { - node: apiNode, - }, - }); - } - } - - return factories; -} - -function createApiHolder(options: { - factories: AnyApiFactory[]; - staticFactories: AnyApiFactory[]; -}): ApiHolder { - const factoryRegistry = new ApiFactoryRegistry(); - - for (const factory of options.factories.slice().reverse()) { - factoryRegistry.register('default', factory); - } - - for (const factory of options.staticFactories) { - factoryRegistry.register('static', factory); - } - - ApiResolver.validateFactories(factoryRegistry, factoryRegistry.getAllApis()); - - return new ApiResolver(factoryRegistry); -} - -function mergeExtensionFactoryMiddleware( - middlewares?: ExtensionFactoryMiddleware | ExtensionFactoryMiddleware[], -): ExtensionFactoryMiddleware | undefined { - if (!middlewares) { - return undefined; - } - if (!Array.isArray(middlewares)) { - return middlewares; - } - if (middlewares.length <= 1) { - return middlewares[0]; - } - return middlewares.reduce((prev, next) => { - if (!prev || !next) { - return prev ?? next; - } - return (orig, ctx) => { - const internalExt = toInternalExtension(ctx.node.spec.extension); - if (internalExt.version !== 'v2') { - return orig(); - } - return next(ctxOverrides => { - return createExtensionDataContainer( - prev(orig, { - node: ctx.node, - apis: ctx.apis, - config: ctxOverrides?.config ?? ctx.config, - }), - 'extension factory middleware', - ); - }, ctx); - }; - }); + return prepareSpecializedApp({ + features: options?.features, + config: options?.config, + bindRoutes: options?.bindRoutes, + advanced: { + ...options?.advanced, + sessionState, + }, + }).finalize(); } diff --git a/packages/frontend-app-api/src/wiring/index.ts b/packages/frontend-app-api/src/wiring/index.ts index 1e18859ddd..d23e60ff0c 100644 --- a/packages/frontend-app-api/src/wiring/index.ts +++ b/packages/frontend-app-api/src/wiring/index.ts @@ -14,9 +14,18 @@ * limitations under the License. */ +export { + type BootstrapSpecializedApp, + type FinalizedSpecializedApp, + prepareSpecializedApp, + type PrepareSpecializedAppOptions, + type PreparedSpecializedApp, + type SpecializedAppSessionState, +} from './prepareSpecializedApp'; export { createSpecializedApp, type CreateSpecializedAppOptions, } from './createSpecializedApp'; export { type FrontendPluginInfoResolver } from './createPluginInfoAttacher'; export { type AppError, type AppErrorTypes } from './createErrorCollector'; +export { type ExtensionFactoryMiddleware } from './types'; diff --git a/packages/frontend-app-api/src/wiring/phaseApis.tsx b/packages/frontend-app-api/src/wiring/phaseApis.tsx new file mode 100644 index 0000000000..781d4c5c1c --- /dev/null +++ b/packages/frontend-app-api/src/wiring/phaseApis.tsx @@ -0,0 +1,289 @@ +/* + * 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 { + AnyApiFactory, + ApiHolder, + AppTree, + AppTreeApi, + appTreeApiRef, + ConfigApi, + configApiRef, + createApiFactory, + ExternalRouteRef, + identityApiRef, + RouteFunc, + RouteRef, + RouteResolutionApi, + routeResolutionApiRef, + SubRouteRef, + type AnyRouteRefParams, + type AppNode, + type ExtensionFactoryMiddleware, + type IdentityApi, +} from '@backstage/frontend-plugin-api'; +import { matchRoutes } from 'react-router-dom'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { AppIdentityProxy } from '../../../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy'; +import { createRouteAliasResolver } from '../routing/RouteAliasResolver'; +import { RouteResolver } from '../routing/RouteResolver'; +import { collectRouteIds } from '../routing/collectRouteIds'; +import { + extractRouteInfoFromAppNode, + type RouteInfo, +} from '../routing/extractRouteInfoFromAppNode'; +import { type BackstageRouteObject } from '../routing/types'; +import { instantiateAppNodeTree } from '../tree/instantiateAppNodeTree'; +import { + FrontendApiRegistry, + FrontendApiResolver, +} from './FrontendApiRegistry'; +import { type ExtensionPredicateContext } from './predicates'; +import { type ErrorCollector } from './createErrorCollector'; + +// Helps delay callers from reaching out to the API before the app tree has been materialized +export class AppTreeApiProxy implements AppTreeApi { + #routeInfo?: RouteInfo; + private readonly tree: AppTree; + private readonly appBasePath: string; + + constructor(tree: AppTree, appBasePath: string) { + this.tree = tree; + this.appBasePath = appBasePath; + } + + private checkIfInitialized() { + if (!this.#routeInfo) { + throw new Error( + `You can't access the AppTreeApi during initialization of the app tree. Please move occurrences of this out of the initialization of the factory`, + ); + } + } + + getTree() { + this.checkIfInitialized(); + + return { tree: this.tree }; + } + + getNodesByRoutePath(routePath: string): { nodes: AppNode[] } { + this.checkIfInitialized(); + const routeInfo = this.#routeInfo; + if (!routeInfo) { + throw new Error( + `You can't access the AppTreeApi during initialization of the app tree. Please move occurrences of this out of the initialization of the factory`, + ); + } + + let path = routePath; + if (path.startsWith(this.appBasePath)) { + path = path.slice(this.appBasePath.length); + } + + const matchedRoutes = matchRoutes(routeInfo.routeObjects, path); + + const matchedAppNodes = + matchedRoutes?.flatMap(routeObj => { + const appNode = routeObj.route.appNode; + return appNode ? [appNode] : []; + }) || []; + + return { nodes: matchedAppNodes }; + } + + initialize(routeInfo: RouteInfo) { + this.#routeInfo = routeInfo; + } +} + +// Helps delay callers from reaching out to the API before the app tree has been materialized +export class RouteResolutionApiProxy implements RouteResolutionApi { + #delegate: RouteResolutionApi | undefined; + #routeObjects: BackstageRouteObject[] | undefined; + + private readonly routeBindings: Map; + private readonly appBasePath: string; + + constructor( + routeBindings: Map, + appBasePath: string, + ) { + this.routeBindings = routeBindings; + this.appBasePath = appBasePath; + } + + resolve( + anyRouteRef: + | RouteRef + | SubRouteRef + | ExternalRouteRef, + options?: { sourcePath?: string }, + ): RouteFunc | undefined { + if (!this.#delegate) { + throw new Error( + `You can't access the RouteResolver during initialization of the app tree. Please move occurrences of this out of the initialization of the factory`, + ); + } + + return this.#delegate.resolve(anyRouteRef, options); + } + + initialize( + routeInfo: RouteInfo, + routeRefsById: Map, + ) { + this.#delegate = new RouteResolver( + routeInfo.routePaths, + routeInfo.routeParents, + routeInfo.routeObjects, + this.routeBindings, + this.appBasePath, + routeInfo.routeAliasResolver, + routeRefsById, + ); + this.#routeObjects = routeInfo.routeObjects; + + return routeInfo; + } + + getRouteObjects() { + return this.#routeObjects; + } +} + +export class PreparedAppIdentityProxy extends AppIdentityProxy { + #onTargetSet?: + | ((identityApi: Parameters[0]) => void) + | undefined; + + setTargetHandlers(options: { + onTargetSet?( + identityApi: Parameters[0], + ): void; + }) { + this.#onTargetSet = options.onTargetSet; + } + + clearTargetHandlers() { + this.#onTargetSet = undefined; + } + + override setTarget( + identityApi: Parameters[0], + targetOptions: Parameters[1], + ) { + super.setTarget(identityApi, targetOptions); + + const onTargetSet = this.#onTargetSet; + if (!onTargetSet) { + return; + } + + this.clearTargetHandlers(); + onTargetSet(identityApi); + } +} + +export function createPhaseApis(options: { + tree: AppTree; + config: ConfigApi; + appApiRegistry: FrontendApiRegistry; + fallbackApis?: ApiHolder; + includeConfigApi: boolean; + appBasePath: string; + routeBindings: Map; + staticFactories: AnyApiFactory[]; +}) { + const appTreeApi = new AppTreeApiProxy(options.tree, options.appBasePath); + const routeResolutionApi = new RouteResolutionApiProxy( + options.routeBindings, + options.appBasePath, + ); + const identityProxy = new PreparedAppIdentityProxy(); + const phaseApiRegistry = new FrontendApiRegistry(); + phaseApiRegistry.registerAll([ + createApiFactory(appTreeApiRef, appTreeApi), + ...(options.includeConfigApi + ? [createApiFactory(configApiRef, options.config)] + : []), + createApiFactory(routeResolutionApiRef, routeResolutionApi), + createApiFactory(identityApiRef, identityProxy), + ...options.staticFactories, + ]); + + const apis = new FrontendApiResolver({ + primaryRegistry: phaseApiRegistry, + secondaryRegistry: options.appApiRegistry, + fallbackApis: options.fallbackApis, + }); + + return { + apis, + routeResolutionApi, + appTreeApi, + identityApiProxy: identityProxy, + }; +} + +export function instantiateAndInitializePhaseTree(options: { + tree: AppTree; + apis: ApiHolder; + collector: ErrorCollector; + extensionFactoryMiddleware?: ExtensionFactoryMiddleware; + routeResolutionApi: RouteResolutionApiProxy; + appTreeApi: AppTreeApiProxy; + routeRefsById: ReturnType; + skipChild?(ctx: { node: AppNode; input: string; child: AppNode }): boolean; + onMissingApi?(ctx: { node: AppNode; apiRefId: string }): void; + predicateContext?: ExtensionPredicateContext; + stopAtAttachment?(ctx: { node: AppNode; input: string }): boolean; +}) { + instantiateAppNodeTree( + options.tree.root, + options.apis, + options.collector, + options.extensionFactoryMiddleware, + { + ...(options.stopAtAttachment + ? { stopAtAttachment: options.stopAtAttachment } + : {}), + skipChild: options.skipChild, + onMissingApi: options.onMissingApi, + predicateContext: options.predicateContext, + }, + ); + + const routeInfo = extractRouteInfoFromAppNode( + options.tree.root, + createRouteAliasResolver(options.routeRefsById), + ); + + options.routeResolutionApi.initialize( + routeInfo, + options.routeRefsById.routes, + ); + options.appTreeApi.initialize(routeInfo); +} + +export function setIdentityApiTarget(options: { + identityApiProxy: AppIdentityProxy; + identityApi: IdentityApi; + signOutTargetUrl: string; +}) { + options.identityApiProxy.setTarget(options.identityApi, { + signOutTargetUrl: options.signOutTargetUrl, + }); +} diff --git a/packages/frontend-app-api/src/wiring/predicates.ts b/packages/frontend-app-api/src/wiring/predicates.ts new file mode 100644 index 0000000000..4321b869c8 --- /dev/null +++ b/packages/frontend-app-api/src/wiring/predicates.ts @@ -0,0 +1,193 @@ +/* + * 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 { + ApiHolder, + createApiRef, + featureFlagsApiRef, +} from '@backstage/frontend-plugin-api'; +import { FilterPredicate } from '@backstage/filter-predicates'; +import type { + EvaluatePermissionRequest, + EvaluatePermissionResponse, +} from '@backstage/plugin-permission-common'; +import { ForwardedError } from '@backstage/errors'; + +export type ExtensionPredicateContext = { + featureFlags: string[]; + permissions: string[]; +}; + +export const EMPTY_PREDICATE_CONTEXT: ExtensionPredicateContext = { + featureFlags: [], + permissions: [], +}; + +// Minimal local permission API interface to avoid a dependency on @backstage/plugin-permission-react +type MinimalPermissionApi = { + authorize( + request: EvaluatePermissionRequest, + ): Promise; +}; + +export const localPermissionApiRef = createApiRef({ + id: 'plugin.permission.api', +}); + +export function createPredicateContextLoader(options: { + apis: ApiHolder; + predicateReferences: ExtensionPredicateContext; +}) { + function getActiveFeatureFlags() { + const featureFlagsApi = options.apis.get(featureFlagsApiRef); + if (!featureFlagsApi) { + return []; + } + + return options.predicateReferences.featureFlags.filter(name => + featureFlagsApi.isActive(name), + ); + } + + function getImmediate(): ExtensionPredicateContext | undefined { + if (options.predicateReferences.permissions.length > 0) { + const permissionApi = options.apis.get(localPermissionApiRef); + if (permissionApi) { + return undefined; + } + } + + return { + featureFlags: getActiveFeatureFlags(), + permissions: [], + }; + } + + async function load() { + const immediatePredicateContext = getImmediate(); + if (immediatePredicateContext) { + return immediatePredicateContext; + } + + let allowedPermissions: string[] = []; + const permissionApi = options.apis.get(localPermissionApiRef); + if (permissionApi) { + try { + const permissionNames = options.predicateReferences.permissions; + const responses = await Promise.all( + permissionNames.map(name => + permissionApi.authorize({ + permission: { name, type: 'basic', attributes: {} }, + }), + ), + ); + allowedPermissions = permissionNames.filter( + (_, i) => responses[i].result === 'ALLOW', + ); + } catch (error) { + throw new ForwardedError( + 'Failed to authorize extension permissions', + error, + ); + } + } + + return { + featureFlags: getActiveFeatureFlags(), + permissions: allowedPermissions, + }; + } + + return { + getImmediate, + load, + }; +} + +export function collectPredicateReferences( + nodes: Iterable<{ spec: { if?: FilterPredicate } }>, +): ExtensionPredicateContext { + const featureFlags = new Set(); + const permissions = new Set(); + + for (const node of nodes) { + if (node.spec.if === undefined) { + continue; + } + + for (const name of extractFeatureFlagNames(node.spec.if)) { + featureFlags.add(name); + } + for (const name of extractPermissionNames(node.spec.if)) { + permissions.add(name); + } + } + + return { + featureFlags: Array.from(featureFlags), + permissions: Array.from(permissions), + }; +} + +/** + * Recursively walks a FilterPredicate and returns all string values referenced + * by `featureFlags: { $contains: '...' }` expressions. This lets us call + * `isActive()` only for the flags that are actually used in predicates rather + * than fetching the full registered-flag list. + */ +function extractFeatureFlagNames(predicate: FilterPredicate): string[] { + return extractPredicateKeyNames(predicate, 'featureFlags'); +} + +/** + * Recursively walks a FilterPredicate and returns all string values referenced + * by `permissions: { $contains: '...' }` expressions. This lets us issue a + * single batched authorize call for only the permissions actually referenced. + */ +function extractPermissionNames(predicate: FilterPredicate): string[] { + return extractPredicateKeyNames(predicate, 'permissions'); +} + +function extractPredicateKeyNames( + predicate: FilterPredicate, + key: string, +): string[] { + if (typeof predicate !== 'object' || predicate === null) { + return []; + } + const obj = predicate as Record; + if (Array.isArray(obj.$all)) { + return (obj.$all as FilterPredicate[]).flatMap(p => + extractPredicateKeyNames(p, key), + ); + } + if (Array.isArray(obj.$any)) { + return (obj.$any as FilterPredicate[]).flatMap(p => + extractPredicateKeyNames(p, key), + ); + } + if (obj.$not !== undefined) { + return extractPredicateKeyNames(obj.$not as FilterPredicate, key); + } + const value = obj[key]; + if (typeof value === 'object' && value !== null && !Array.isArray(value)) { + const contains = (value as Record).$contains; + if (typeof contains === 'string') { + return [contains]; + } + } + return []; +} diff --git a/packages/frontend-app-api/src/wiring/prepareSpecializedApp.tsx b/packages/frontend-app-api/src/wiring/prepareSpecializedApp.tsx new file mode 100644 index 0000000000..3a09804a18 --- /dev/null +++ b/packages/frontend-app-api/src/wiring/prepareSpecializedApp.tsx @@ -0,0 +1,930 @@ +/* + * 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 { ConfigReader } from '@backstage/config'; +import { isError } from '@backstage/errors'; +import { + AnyApiFactory, + ApiHolder, + AppTree, + ConfigApi, + coreExtensionData, + AppNode, + ExtensionFactoryMiddleware, + FrontendFeature, + IdentityApi, + identityApiRef, + createExtensionDataRef, +} from '@backstage/frontend-plugin-api'; +import { + createExtensionDataContainer, + OpaqueFrontendPlugin, +} from '@internal/frontend'; +import { OpaqueType } from '@internal/opaque'; +import { ComponentType, ReactNode } from 'react'; + +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { + resolveExtensionDefinition, + toInternalExtension, +} from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition'; + +import { CreateAppRouteBinder } from '../routing'; +import { resolveRouteBindings } from '../routing/resolveRouteBindings'; +import { collectRouteIds } from '../routing/collectRouteIds'; +import { getBasePath } from '../routing/getBasePath'; +import { Root } from '../extensions/Root'; +import { resolveAppTree } from '../tree/resolveAppTree'; +import { resolveAppNodeSpecs } from '../tree/resolveAppNodeSpecs'; +import { readAppExtensionsConfig } from '../tree/readAppExtensionsConfig'; +import { + createPluginInfoAttacher, + FrontendPluginInfoResolver, +} from './createPluginInfoAttacher'; +import { + AppError, + createErrorCollector, + ErrorCollector, +} from './createErrorCollector'; +import { + createPhaseApis, + instantiateAndInitializePhaseTree, + setIdentityApiTarget, +} from './phaseApis'; +import { + collectPredicateReferences, + createPredicateContextLoader, + EMPTY_PREDICATE_CONTEXT, + type ExtensionPredicateContext, +} from './predicates'; +import { FrontendApiRegistry } from './FrontendApiRegistry'; +import { + ApiFactoryEntry, + collectApiFactoryEntries, + registerFeatureFlagDeclarationsInHolder, + syncFinalApiFactories, + wrapFeatureFlagApiFactory, +} from './apiFactories'; +import { + attachThrowingFinalizationChild, + BootstrapClassification, + classifyBootstrapTree, + clearFinalizationBoundaryInstances, + createBootstrapApp, + prepareFinalizedTree, +} from './treeLifecycle'; + +function deduplicateFeatures( + allFeatures: FrontendFeature[], +): FrontendFeature[] { + // Start by removing duplicates by reference + const features = Array.from(new Set(allFeatures)); + + // Plugins are deduplicated by ID, last one wins + const seenIds = new Set(); + return features + .reverse() + .filter(feature => { + if (!OpaqueFrontendPlugin.isType(feature)) { + return true; + } + if (seenIds.has(feature.id)) { + return false; + } + seenIds.add(feature.id); + return true; + }) + .reverse(); +} + +type SignInPageProps = { + onSignInSuccess(identityApi: IdentityApi): void; + children?: ReactNode; +}; + +/** + * Result of bootstrapping a prepared specialized app. + * + * @public + */ +export type BootstrapSpecializedApp = { + apis: ApiHolder; + element: JSX.Element; + tree: AppTree; +}; + +/** + * Result of finalizing a prepared specialized app. + * + * @public + */ +export type FinalizedSpecializedApp = { + apis: ApiHolder; + element: JSX.Element; + sessionState: SpecializedAppSessionState; + tree: AppTree; + errors?: AppError[]; +}; + +type SignInRuntime = { + readyIdentityApi?: IdentityApi; + requiresSignIn: boolean; +}; + +type FinalizationState = { + started: boolean; + promise: Promise; + resolve(app: FinalizedSpecializedApp): void; + reject(error: unknown): void; +}; + +type FinalizationMode = 'onFinalized' | 'finalize'; + +type InternalSpecializedAppSessionState = { + apis: ApiHolder; + identityApi?: IdentityApi; + predicateContext: ExtensionPredicateContext; +}; + +/** + * Opaque reusable session state for specialized apps. + * + * @public + */ +export type SpecializedAppSessionState = { + $$type: '@backstage/SpecializedAppSessionState'; +}; + +const OpaqueSpecializedAppSessionState = OpaqueType.create<{ + public: SpecializedAppSessionState; + versions: InternalSpecializedAppSessionState & { + version: 'v1'; + }; +}>({ + type: '@backstage/SpecializedAppSessionState', + versions: ['v1'], +}); + +const signInPageComponentDataRef = createExtensionDataRef< + ComponentType +>().with({ id: 'core.sign-in-page.component' }); + +/** + * Options for {@link prepareSpecializedApp}. + * + * @public + */ +export type PrepareSpecializedAppOptions = { + /** + * The list of features to load. + */ + features?: FrontendFeature[]; + + /** + * The config API implementation to use. For most normal apps, this should be + * specified. + * + * If none is given, a new _empty_ config will be used during startup. In + * later stages of the app lifecycle, the config API in the API holder will be + * used. + */ + config?: ConfigApi; + + /** + * Allows for the binding of plugins' external route refs within the app. + */ + bindRoutes?(context: { bind: CreateAppRouteBinder }): void; + + /** + * Advanced, more rarely used options. + */ + advanced?: { + /** + * A reusable specialized app session state to use. + * + * This can be obtained from either the app passed to + * {@link PreparedSpecializedApp.onFinalized} or from + * {@link PreparedSpecializedApp.finalize}, and reused in a future app + * instance to skip sign-in and session preparation. + */ + sessionState?: SpecializedAppSessionState; + + /** + * Applies one or more middleware on every extension, as they are added to + * the application. + * + * This is an advanced use case for modifying extension data on the fly as + * it gets emitted by extensions being instantiated. + */ + extensionFactoryMiddleware?: + | ExtensionFactoryMiddleware + | ExtensionFactoryMiddleware[]; + + /** + * Allows for customizing how plugin info is retrieved. + */ + pluginInfoResolver?: FrontendPluginInfoResolver; + }; +}; + +/** + * Result of {@link prepareSpecializedApp}. + * + * @public + */ +export type PreparedSpecializedApp = { + getBootstrapApp(): BootstrapSpecializedApp; + onFinalized(callback: (app: FinalizedSpecializedApp) => void): () => void; + finalize(): FinalizedSpecializedApp; +}; + +// Internal options type, not exported in the public API +export interface CreateSpecializedAppInternalOptions + extends PrepareSpecializedAppOptions { + __internal?: { + apiFactoryOverrides?: AnyApiFactory[]; + }; +} + +export function createSessionStateFromApis( + apis: ApiHolder, +): SpecializedAppSessionState { + return OpaqueSpecializedAppSessionState.createInstance('v1', { + apis, + identityApi: apis.get(identityApiRef), + predicateContext: EMPTY_PREDICATE_CONTEXT, + }); +} + +/** + * Prepares an app without instantiating the full extension tree. + * + * @remarks + * + * This is useful for split sign-in flows where the sign-in page should be + * rendered first, and the full app finalized once an identity has been + * captured. + * + * @public + */ +export function prepareSpecializedApp( + options?: PrepareSpecializedAppOptions, +): PreparedSpecializedApp { + const internalOptions = options as CreateSpecializedAppInternalOptions; + const config = options?.config ?? new ConfigReader({}, 'empty-config'); + const features = deduplicateFeatures(options?.features ?? []).map( + createPluginInfoAttacher(config, options?.advanced?.pluginInfoResolver), + ); + + const collector = createErrorCollector(); + + const tree = resolveAppTree( + 'root', + resolveAppNodeSpecs({ + features, + builtinExtensions: [ + resolveExtensionDefinition(Root, { namespace: 'root' }), + ], + parameters: readAppExtensionsConfig(config), + forbidden: new Set(['root']), + collector, + }), + collector, + ); + + const appBasePath = getBasePath(config); + const routeRefsById = collectRouteIds(features, collector); + const routeBindings = resolveRouteBindings( + options?.bindRoutes, + config, + routeRefsById, + collector, + ); + + const mergedExtensionFactoryMiddleware = mergeExtensionFactoryMiddleware( + options?.advanced?.extensionFactoryMiddleware, + ); + const providedSessionState = options?.advanced?.sessionState; + const providedSessionData = providedSessionState + ? OpaqueSpecializedAppSessionState.toInternal(providedSessionState) + : undefined; + const providedApis = providedSessionData?.apis; + // Bootstrap only renders the parts of the tree that are known to be safe + // before predicate context and sign-in have been resolved. + const bootstrapClassification = classifyBootstrapTree({ + tree, + collector, + }); + const predicateReferences = collectPredicateReferences(tree.nodes.values()); + const appApiRegistry = new FrontendApiRegistry(); + const internalStaticFactories = + internalOptions?.__internal?.apiFactoryOverrides ?? []; + const phaseStaticFactories = [...internalStaticFactories]; + const bootstrapApiFactoryEntries = new Map(); + const bootstrapMissingApiAccesses = new Map< + string, + { node: AppNode; apiRefId: string } + >(); + + if (providedApis) { + // Reused session state already carries a fully prepared API holder, so the + // bootstrap path only needs to register feature flag declarations on top. + registerFeatureFlagDeclarationsInHolder(providedApis, features); + } else { + // Bootstrap materializes only the immediately visible API factories. Any + // predicate-gated API roots are revisited during finalization. + collectApiFactoryEntries({ + apiNodes: (tree.root.edges.attachments.get('apis') ?? []).filter( + apiNode => !bootstrapClassification.deferredApiRoots.has(apiNode), + ), + collector, + entries: bootstrapApiFactoryEntries, + }); + const apiFactories = Array.from( + bootstrapApiFactoryEntries.values(), + entry => wrapFeatureFlagApiFactory(entry.factory, features), + ); + appApiRegistry.registerAll(apiFactories); + } + const phase = createPhaseApis({ + tree, + config, + appApiRegistry, + fallbackApis: providedApis, + includeConfigApi: !providedApis, + appBasePath, + routeBindings, + staticFactories: phaseStaticFactories, + }); + const predicateContextLoader = createPredicateContextLoader({ + apis: phase.apis, + predicateReferences, + }); + let signInRuntime: SignInRuntime | undefined; + let finalized: FinalizedSpecializedApp | undefined; + let bootstrapApp: BootstrapSpecializedApp | undefined; + + function updateIdentityApiTarget(identityApi?: IdentityApi) { + if (!identityApi) { + return; + } + + setIdentityApiTarget({ + identityApiProxy: phase.identityApiProxy, + identityApi, + signOutTargetUrl: appBasePath || '/', + }); + } + + function createSessionState(predicateContext: ExtensionPredicateContext) { + const identityApi = + signInRuntime?.readyIdentityApi ?? providedSessionData?.identityApi; + // As soon as a real identity is available we swap the phase proxy over so + // the finalized tree observes the same API instance. + updateIdentityApiTarget(identityApi); + const sessionState = OpaqueSpecializedAppSessionState.createInstance('v1', { + apis: phase.apis, + identityApi, + predicateContext, + }); + return sessionState; + } + + function getSynchronousSessionState() { + if (providedSessionState) { + return providedSessionState; + } + // The direct finalize() path is intentionally synchronous. If sign-in is + // still pending we refuse to guess and force the caller to wait. + if (signInRuntime?.requiresSignIn) { + return undefined; + } + + const predicateContext = predicateContextLoader.getImmediate(); + if (!predicateContext) { + return undefined; + } + + return createSessionState(predicateContext); + } + + function loadAsyncSessionState() { + if (providedSessionState) { + return Promise.resolve(providedSessionState); + } + if (signInRuntime?.requiresSignIn && !signInRuntime.readyIdentityApi) { + return Promise.reject( + new Error( + 'prepareSpecializedApp requires waiting for the bootstrap app to be ready before calling finalize()', + ), + ); + } + + // For apps without sign-in we can sometimes finalize immediately from the + // already available predicate context, skipping the async loader. + if (!signInRuntime?.requiresSignIn) { + const immediateSessionState = getSynchronousSessionState(); + if (immediateSessionState) { + return Promise.resolve(immediateSessionState); + } + } + + return predicateContextLoader.load().then(createSessionState); + } + + function finalizeWithSessionState( + finalizedSessionState: SpecializedAppSessionState, + ) { + return finalizeFromSessionState({ + finalized, + finalizedSessionState, + tree, + collector, + phase, + extensionFactoryMiddleware: mergedExtensionFactoryMiddleware, + routeRefsById, + appBasePath, + providedApis, + features, + appApiRegistry, + bootstrapClassification, + bootstrapApiFactoryEntries, + bootstrapMissingApiAccesses, + }); + } + + function finalizeWithBootstrapError( + error: Error, + finalizedSessionState?: SpecializedAppSessionState, + ) { + return finalizeFromBootstrapError({ + finalized, + error, + finalizedSessionState, + tree, + collector, + phase, + extensionFactoryMiddleware: mergedExtensionFactoryMiddleware, + routeRefsById, + signInRuntime, + providedSessionData, + }); + } + + const finalization = createFinalizationController({ + getFinalized() { + return finalized; + }, + setFinalized(finalizedApp) { + finalized = finalizedApp; + }, + finalizeFromSessionState: finalizeWithSessionState, + finalizeFromBootstrapError: finalizeWithBootstrapError, + }); + + function getBootstrapApp() { + if (bootstrapApp) { + return bootstrapApp; + } + + const runtime: SignInRuntime = { + requiresSignIn: false, + }; + if (!providedSessionState) { + phase.identityApiProxy.setTargetHandlers({ + onTargetSet(identityApi) { + runtime.readyIdentityApi = identityApi; + // Sign-in completion only auto-starts finalization for onFinalized(). + // The direct finalize() path stays explicit and synchronous. + if (finalization.getMode() === 'onFinalized') { + finalization.start(loadAsyncSessionState); + } + }, + }); + } + + const result = createBootstrapApp({ + tree, + apis: phase.apis, + collector, + routeRefsById, + routeResolutionApi: phase.routeResolutionApi, + appTreeApi: phase.appTreeApi, + extensionFactoryMiddleware: mergedExtensionFactoryMiddleware, + disableSignIn: Boolean(providedSessionState), + skipBootstrapChild({ child }) { + return bootstrapClassification.deferredRoots.has(child); + }, + onMissingApi({ node, apiRefId }) { + bootstrapMissingApiAccesses.set(`${node.spec.id}:${apiRefId}`, { + node, + apiRefId, + }); + }, + hasSignInPage(signInPageNode) { + return Boolean( + signInPageNode?.instance?.getData(signInPageComponentDataRef), + ); + }, + }); + if (!result.requiresSignIn) { + phase.identityApiProxy.clearTargetHandlers(); + } + + runtime.requiresSignIn = result.requiresSignIn; + signInRuntime = runtime; + bootstrapApp = { ...result.bootstrapApp, apis: phase.apis }; + + return bootstrapApp; + } + + return { + getBootstrapApp, + onFinalized(callback) { + finalization.selectMode('onFinalized'); + // Subscribing to finalization also ensures the bootstrap tree exists, + // because sign-in may need to capture identity before finalization starts. + getBootstrapApp(); + + let subscribed = true; + + if (finalized) { + const finalizedApp = finalized; + Promise.resolve().then(() => { + if (subscribed) { + callback(finalizedApp); + } + }); + return () => { + subscribed = false; + }; + } + + // If sign-in is still in progress we wait for the shared promise created + // by the sign-in callback. Otherwise we can start finalization right away. + const finalizedAppPromise = + signInRuntime?.requiresSignIn && !signInRuntime.readyIdentityApi + ? finalization.getPromise() + : finalization.start(loadAsyncSessionState); + finalizedAppPromise + .then(finalizedApp => { + if (subscribed) { + callback(finalizedApp); + } + }) + .catch(() => {}); + + return () => { + subscribed = false; + }; + }, + finalize() { + finalization.selectMode('finalize'); + if (finalized) { + return finalized; + } + if (!providedSessionState) { + // finalize() still depends on bootstrap classification and sign-in + // discovery unless a reusable session was supplied up front, so we make + // sure the bootstrap tree has been prepared first. + getBootstrapApp(); + } + + // Direct finalization never waits for async session preparation. Callers + // must either provide sessionState during prepareSpecializedApp() or + // invoke finalize() only when the predicate context is already available + // synchronously. + const finalizedSessionState = signInRuntime?.requiresSignIn + ? undefined + : getSynchronousSessionState(); + if (!finalizedSessionState) { + if (signInRuntime?.requiresSignIn) { + throw new Error( + 'prepareSpecializedApp requires waiting for the bootstrap app to be ready before calling finalize()', + ); + } + throw new Error( + 'prepareSpecializedApp requires waiting for asynchronous finalization before calling finalize()', + ); + } + + finalized = finalizeWithSessionState(finalizedSessionState); + return finalized; + }, + }; +} + +/** + * Materializes the fully finalized app tree from a prepared session state. + * + * This is responsible for switching the identity proxy to the resolved target, + * synchronizing any deferred API factories, and re-instantiating the parts of + * the tree that are only valid once predicate context is available. + */ +function finalizeFromSessionState(options: { + finalized?: FinalizedSpecializedApp; + finalizedSessionState: SpecializedAppSessionState; + tree: AppTree; + collector: ErrorCollector; + phase: ReturnType; + extensionFactoryMiddleware?: ExtensionFactoryMiddleware; + routeRefsById: ReturnType; + appBasePath: string; + providedApis?: ApiHolder; + features: FrontendFeature[]; + appApiRegistry: FrontendApiRegistry; + bootstrapClassification: BootstrapClassification; + bootstrapApiFactoryEntries: Map; + bootstrapMissingApiAccesses: Map; +}): FinalizedSpecializedApp { + if (options.finalized) { + return options.finalized; + } + + const sessionStateData = OpaqueSpecializedAppSessionState.toInternal( + options.finalizedSessionState, + ); + if (sessionStateData.identityApi) { + // Finalization retargets the identity proxy before any additional nodes are + // instantiated so the full tree observes the captured identity immediately. + setIdentityApiTarget({ + identityApiProxy: options.phase.identityApiProxy, + identityApi: sessionStateData.identityApi, + signOutTargetUrl: options.appBasePath || '/', + }); + } + if (!options.providedApis) { + // Deferred API roots are synchronized at finalization time, but bootstrap- + // materialized APIs stay frozen if they were already observed earlier. + syncFinalApiFactories({ + deferredApiNodes: options.bootstrapClassification.deferredApiRoots, + appApiRegistry: options.appApiRegistry, + apiResolver: options.phase.apis, + collector: options.collector, + features: options.features, + bootstrapApiFactoryEntries: options.bootstrapApiFactoryEntries, + bootstrapMissingApiAccesses: options.bootstrapMissingApiAccesses, + predicateContext: sessionStateData.predicateContext, + }); + } + + prepareFinalizedTree({ + tree: options.tree, + }); + // Finalization re-instantiates the boundary subtree so predicate-gated app + // content can be re-evaluated without disturbing preserved bootstrap nodes. + clearFinalizationBoundaryInstances(options.tree); + instantiateAndInitializePhaseTree({ + tree: options.tree, + apis: options.phase.apis, + collector: options.collector, + extensionFactoryMiddleware: options.extensionFactoryMiddleware, + routeResolutionApi: options.phase.routeResolutionApi, + appTreeApi: options.phase.appTreeApi, + routeRefsById: options.routeRefsById, + predicateContext: sessionStateData.predicateContext, + }); + + const element = options.tree.root.instance?.getData( + coreExtensionData.reactElement, + ); + if (!element) { + throw new Error('Expected finalized app tree to expose a root element'); + } + + return { + apis: options.phase.apis, + element, + sessionState: options.finalizedSessionState, + tree: options.tree, + errors: options.collector.collectErrors(), + }; +} + +/** + * Builds a finalized app that rethrows a bootstrap-time failure through the + * normal app root boundary. + * + * This keeps the error handling path aligned with normal finalization while + * preserving any session state that was already resolved before the failure. + */ +function finalizeFromBootstrapError(options: { + finalized?: FinalizedSpecializedApp; + error: Error; + finalizedSessionState?: SpecializedAppSessionState; + tree: AppTree; + collector: ErrorCollector; + phase: ReturnType; + extensionFactoryMiddleware?: ExtensionFactoryMiddleware; + routeRefsById: ReturnType; + signInRuntime?: SignInRuntime; + providedSessionData?: InternalSpecializedAppSessionState; +}): FinalizedSpecializedApp { + if (options.finalized) { + return options.finalized; + } + + // If finalization fails after session state was already prepared, keep using + // it so the error app reflects the same identity and API view. + const finalizedSessionState = + options.finalizedSessionState ?? + OpaqueSpecializedAppSessionState.createInstance('v1', { + apis: options.phase.apis, + identityApi: + options.signInRuntime?.readyIdentityApi ?? + options.providedSessionData?.identityApi, + predicateContext: EMPTY_PREDICATE_CONTEXT, + }); + + prepareFinalizedTree({ + tree: options.tree, + }); + clearFinalizationBoundaryInstances(options.tree); + // The final app reports bootstrap failures through app/root.children so the + // normal app root boundary renders the error state for us. + attachThrowingFinalizationChild(options.tree, options.error); + instantiateAndInitializePhaseTree({ + tree: options.tree, + apis: options.phase.apis, + collector: options.collector, + extensionFactoryMiddleware: options.extensionFactoryMiddleware, + routeResolutionApi: options.phase.routeResolutionApi, + appTreeApi: options.phase.appTreeApi, + routeRefsById: options.routeRefsById, + }); + + const element = options.tree.root.instance?.getData( + coreExtensionData.reactElement, + ); + if (!element) { + throw new Error('Expected finalized app tree to expose a root element'); + } + + return { + apis: options.phase.apis, + element, + sessionState: finalizedSessionState, + tree: options.tree, + }; +} + +/** + * Owns the callback-driven finalization lifecycle for a prepared app. + * + * The controller enforces the selected finalization mode, memoizes the shared + * async finalization promise for `onFinalized()` subscribers, and funnels both + * successful and failing async finalization through the same resolution path. + */ +function createFinalizationController(options: { + getFinalized(): FinalizedSpecializedApp | undefined; + setFinalized(finalizedApp: FinalizedSpecializedApp): void; + finalizeFromSessionState( + finalizedSessionState: SpecializedAppSessionState, + ): FinalizedSpecializedApp; + finalizeFromBootstrapError( + error: Error, + finalizedSessionState?: SpecializedAppSessionState, + ): FinalizedSpecializedApp; +}) { + let finalizationState: FinalizationState | undefined; + let finalizationMode: FinalizationMode | undefined; + + function getState(): FinalizationState { + if (finalizationState) { + return finalizationState; + } + + // onFinalized() subscribers all fan into the same promise so that the full + // finalization flow only ever runs once. + let resolve: ((app: FinalizedSpecializedApp) => void) | undefined; + let reject: ((error: unknown) => void) | undefined; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + if (!resolve || !reject) { + throw new Error('Failed to create finalization state'); + } + + finalizationState = { + started: false, + promise, + resolve, + reject, + }; + return finalizationState; + } + + return { + getMode() { + return finalizationMode; + }, + getPromise() { + return getState().promise; + }, + selectMode(mode: FinalizationMode) { + if (finalizationMode && finalizationMode !== mode) { + throw new Error( + `prepareSpecializedApp only supports using either onFinalized() or finalize(), not both`, + ); + } + + // A prepared app now has one owner: either the callback-driven path or + // the direct finalize() path, never both. + finalizationMode = mode; + }, + start(loader: () => Promise) { + const finalized = options.getFinalized(); + if (finalized) { + return Promise.resolve(finalized); + } + + const state = getState(); + if (state.started) { + return state.promise; + } + state.started = true; + + // If loading finishes but final tree materialization fails, we still + // want to preserve the resolved session state when building the error app. + let finalizedSessionState: SpecializedAppSessionState | undefined; + loader() + .then(sessionState => { + finalizedSessionState = sessionState; + const finalizedApp = options.finalizeFromSessionState(sessionState); + options.setFinalized(finalizedApp); + state.resolve(finalizedApp); + }) + .catch(error => { + try { + const bootstrapFailure = isError(error) + ? error + : new Error(String(error)); + const finalizedApp = options.finalizeFromBootstrapError( + bootstrapFailure, + finalizedSessionState, + ); + options.setFinalized(finalizedApp); + state.resolve(finalizedApp); + } catch (finalizationError) { + finalizationState = undefined; + state.reject(finalizationError); + } + }); + + return state.promise; + }, + }; +} + +/** + * Combines one or more extension factory middlewares into a single middleware + * invocation chain that preserves Backstage's extension data container shape. + */ +function mergeExtensionFactoryMiddleware( + middlewares?: ExtensionFactoryMiddleware | ExtensionFactoryMiddleware[], +): ExtensionFactoryMiddleware | undefined { + if (!middlewares) { + return undefined; + } + if (!Array.isArray(middlewares)) { + return middlewares; + } + if (middlewares.length <= 1) { + return middlewares[0]; + } + return middlewares.reduce((prev, next) => { + if (!prev || !next) { + return prev ?? next; + } + return (orig, ctx) => { + const internalExt = toInternalExtension(ctx.node.spec.extension); + if (internalExt.version !== 'v2') { + return orig(); + } + return next(ctxOverrides => { + return createExtensionDataContainer( + prev(orig, { + node: ctx.node, + apis: ctx.apis, + config: ctxOverrides?.config ?? ctx.config, + }), + 'extension factory middleware', + ); + }, ctx); + }; + }); +} diff --git a/packages/frontend-app-api/src/wiring/treeLifecycle.tsx b/packages/frontend-app-api/src/wiring/treeLifecycle.tsx new file mode 100644 index 0000000000..671464359a --- /dev/null +++ b/packages/frontend-app-api/src/wiring/treeLifecycle.tsx @@ -0,0 +1,318 @@ +/* + * 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 { + ApiHolder, + AppNode, + AppNodeInstance, + AppTree, + coreExtensionData, + ExtensionDataRef, + ExtensionFactoryMiddleware, +} from '@backstage/frontend-plugin-api'; +import { FilterPredicate } from '@backstage/filter-predicates'; +import { collectRouteIds } from '../routing/collectRouteIds'; +import { ErrorCollector } from './createErrorCollector'; +import { + AppTreeApiProxy, + instantiateAndInitializePhaseTree, + RouteResolutionApiProxy, +} from './phaseApis'; + +export type BootstrapClassification = { + deferredApiRoots: Set; + deferredElementRoots: Set; + deferredRoots: Set; +}; + +/** + * Instantiates the bootstrap-visible portion of the app tree and returns the + * element that should be rendered while the prepared app is still incomplete. + * + * The bootstrap tree deliberately stops at the session boundary so sign-in and + * other deferred content can be handled separately during finalization. + */ +export function createBootstrapApp(options: { + tree: AppTree; + apis: ApiHolder; + collector: ErrorCollector; + routeRefsById: ReturnType; + routeResolutionApi: RouteResolutionApiProxy; + appTreeApi: AppTreeApiProxy; + extensionFactoryMiddleware?: ExtensionFactoryMiddleware; + disableSignIn?: boolean; + skipBootstrapChild?(ctx: { + node: AppNode; + input: string; + child: AppNode; + }): boolean; + onMissingApi?(ctx: { node: AppNode; apiRefId: string }): void; + hasSignInPage(node?: AppNode): boolean; +}): { + bootstrapApp: { element: JSX.Element; tree: AppTree }; + requiresSignIn: boolean; +} { + const signInPageNode = getAppRootNode(options.tree)?.edges.attachments.get( + 'signInPage', + )?.[0]; + + instantiateAndInitializePhaseTree({ + tree: options.tree, + apis: options.apis, + collector: options.collector, + extensionFactoryMiddleware: options.extensionFactoryMiddleware, + routeResolutionApi: options.routeResolutionApi, + appTreeApi: options.appTreeApi, + routeRefsById: options.routeRefsById, + stopAtAttachment: ({ node, input }) => + isSessionBoundaryAttachment(node, input), + skipChild: options.skipBootstrapChild, + onMissingApi: options.onMissingApi, + }); + + const element = options.tree.root.instance?.getData( + coreExtensionData.reactElement, + ); + if (!element) { + throw new Error('Expected bootstrap tree to expose a root element'); + } + + return { + bootstrapApp: { + element, + tree: options.tree, + }, + requiresSignIn: + !options.disableSignIn && options.hasSignInPage(signInPageNode), + }; +} + +/** + * Splits the app tree into bootstrap-visible and deferred regions. + * + * Predicate-gated roots are deferred to finalization, while any predicate that + * still leaks into the bootstrap-visible region is reported and ignored. + */ +export function classifyBootstrapTree(options: { + tree: AppTree; + collector: ErrorCollector; +}): BootstrapClassification { + const apiNodes = options.tree.root.edges.attachments.get('apis') ?? []; + const deferredApiRoots = new Set( + apiNodes.filter(apiNode => subtreeContainsPredicate(apiNode)), + ); + const appRootElementNodes = + getAppRootNode(options.tree)?.edges.attachments.get('elements') ?? []; + const deferredElementRoots = new Set( + appRootElementNodes.filter(elementNode => + subtreeContainsPredicate(elementNode), + ), + ); + const deferredRoots = new Set([ + ...deferredApiRoots, + ...deferredElementRoots, + ]); + const bootstrapNodes = collectBootstrapVisibleNodes(options.tree, { + deferredRoots, + }); + + for (const node of bootstrapNodes) { + if (node.spec.if === undefined) { + continue; + } + + options.collector.report({ + code: 'EXTENSION_BOOTSTRAP_PREDICATE_IGNORED', + message: + `Extension '${node.spec.id}' uses 'if' during bootstrap, so the predicate was ignored. ` + + "Move it behind 'app/root.children', onto a deferred 'app/root.elements' subtree, or into an API subtree.", + context: { + node, + }, + }); + (node.spec as typeof node.spec & { if?: FilterPredicate }).if = undefined; + } + + return { + deferredApiRoots, + deferredElementRoots, + deferredRoots, + }; +} + +/** + * Prepares the app tree for finalization by removing the bootstrap-only + * sign-in attachment from the app root boundary. + */ +export function prepareFinalizedTree(options: { tree: AppTree }) { + for (const appRootNode of getFinalizationBoundaryNodes(options.tree)) { + const attachments = appRootNode.edges.attachments as Map; + attachments.delete('signInPage'); + } +} + +/** + * Clears instances inside the finalization boundary so those nodes can be + * re-instantiated with finalized predicate context and API availability. + */ +export function clearFinalizationBoundaryInstances(tree: AppTree) { + clearNodeInstance(tree.root); + + const visited = new Set(); + function visit(node: AppNode) { + if (visited.has(node)) { + return; + } + visited.add(node); + clearNodeInstance(node); + + for (const [input, children] of node.edges.attachments) { + // app/root.elements is allowed to keep its bootstrap instances so we only + // re-run the parts of the boundary that actually change at finalization. + if (node.spec.id === 'app/root' && input === 'elements') { + continue; + } + + for (const child of children) { + visit(child); + } + } + } + + for (const appRootNode of getFinalizationBoundaryNodes(tree)) { + visit(appRootNode); + } +} + +/** + * Identifies the attachment that separates bootstrap rendering from the + * children that are deferred until finalization. + */ +export function isSessionBoundaryAttachment(node: AppNode, input: string) { + return node.spec.id === 'app/root' && input === 'children'; +} + +/** + * Injects a synthetic finalization child that throws the captured bootstrap + * error when rendered. + * + * This lets the finalized tree reuse the normal app root error boundary rather + * than introducing a separate error rendering path. + */ +export function attachThrowingFinalizationChild(tree: AppTree, error: Error) { + const bootstrapChildNode = + getAppRootNode(tree)?.edges.attachments.get('children')?.[0]; + if (!bootstrapChildNode) { + throw error; + } + + function ThrowBootstrapError(): never { + throw error; + } + + // This synthetic child gives the finalized tree a stable place to rethrow + // bootstrap failures through the normal extension boundary stack. + (bootstrapChildNode as AppNode & { instance?: AppNodeInstance }).instance = { + getDataRefs() { + return [coreExtensionData.reactElement]; + }, + getData(dataRef: ExtensionDataRef) { + if (dataRef.id === coreExtensionData.reactElement.id) { + return () as TValue; + } + return undefined; + }, + }; +} + +function getAppRootNode(tree: AppTree) { + return tree.nodes.get('app/root'); +} + +function getFinalizationBoundaryNodes(tree: AppTree): AppNode[] { + const nodes = new Set(); + const appRootNode = getAppRootNode(tree); + if (appRootNode) { + nodes.add(appRootNode); + } + const attachedAppRootNode = tree.root.edges.attachments.get('app')?.[0]; + if (attachedAppRootNode) { + nodes.add(attachedAppRootNode); + } + return Array.from(nodes); +} + +function clearNodeInstance(node: AppNode) { + (node as AppNode & { instance?: AppNodeInstance }).instance = undefined; +} + +function collectBootstrapVisibleNodes( + tree: AppTree, + options?: { deferredRoots?: Set }, +) { + const visibleNodes = new Set(); + + function visit(node: AppNode) { + if (visibleNodes.has(node)) { + return; + } + visibleNodes.add(node); + + for (const [input, children] of node.edges.attachments) { + if (isSessionBoundaryAttachment(node, input)) { + continue; + } + + for (const child of children) { + if (options?.deferredRoots?.has(child)) { + continue; + } + visit(child); + } + } + } + + visit(tree.root); + + return visibleNodes; +} + +function subtreeContainsPredicate(root: AppNode) { + const visited = new Set(); + + function visit(node: AppNode): boolean { + if (visited.has(node)) { + return false; + } + visited.add(node); + + if (node.spec.if !== undefined) { + return true; + } + + for (const children of node.edges.attachments.values()) { + for (const child of children) { + if (visit(child)) { + return true; + } + } + } + + return false; + } + + return visit(root); +} diff --git a/packages/frontend-app-api/src/wiring/types.ts b/packages/frontend-app-api/src/wiring/types.ts new file mode 100644 index 0000000000..0bb7497179 --- /dev/null +++ b/packages/frontend-app-api/src/wiring/types.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { JsonObject } from '@backstage/types'; +import { + ApiHolder, + AppNode, + ExtensionDataContainer, + ExtensionDataRef, + ExtensionDataValue, +} from '@backstage/frontend-plugin-api'; + +/** @public */ +export type ExtensionFactoryMiddleware = ( + originalFactory: (contextOverrides?: { + config?: JsonObject; + }) => ExtensionDataContainer, + context: { + node: AppNode; + apis: ApiHolder; + config?: JsonObject; + }, +) => Iterable>; diff --git a/packages/frontend-defaults/CHANGELOG.md b/packages/frontend-defaults/CHANGELOG.md index 651bdeb691..1607fc9779 100644 --- a/packages/frontend-defaults/CHANGELOG.md +++ b/packages/frontend-defaults/CHANGELOG.md @@ -1,5 +1,213 @@ # @backstage/frontend-defaults +## 0.5.1 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0 + - @backstage/plugin-app@0.4.3 + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/core-components@0.18.9 + - @backstage/frontend-app-api@0.16.2 + - @backstage/config@1.3.7 + +## 0.5.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/plugin-app@0.4.3-next.2 + - @backstage/core-components@0.18.9-next.1 + - @backstage/config@1.3.7-next.0 + - @backstage/frontend-app-api@0.16.2-next.2 + - @backstage/frontend-plugin-api@0.16.0-next.2 + +## 0.5.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app@0.4.3-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/frontend-app-api@0.16.2-next.1 + - @backstage/core-components@0.18.9-next.0 + +## 0.5.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app@0.4.3-next.0 + - @backstage/core-components@0.18.9-next.0 + - @backstage/frontend-plugin-api@0.15.2-next.0 + - @backstage/frontend-app-api@0.16.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.5.0 + +### Minor Changes + +- 92af1ae: **BREAKING**: Removed the `allowUnknownExtensionConfig` option from `createApp`. This flag had no effect and was a no-op, so no behavioral changes are expected. +- 33de79d: **BREAKING**: Removed the deprecated `createPublicSignInApp` function. Use `createApp` from `@backstage/frontend-defaults` with `appModulePublicSignIn` from `@backstage/plugin-app/alpha` instead. + +### Patch Changes + +- 5b160f9: Updated `createApp` to use the phased `prepareSpecializedApp` flow, allowing apps to render a bootstrap tree before the full app is finalized. +- Updated dependencies + - @backstage/frontend-app-api@0.16.0 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/plugin-app@0.4.1 + +## 0.5.0-next.1 + +### Minor Changes + +- 92af1ae: **BREAKING**: Removed the `allowUnknownExtensionConfig` option from `createApp`. This flag had no effect and was a no-op, so no behavioral changes are expected. +- 33de79d: **BREAKING**: Removed the deprecated `createPublicSignInApp` function. Use `createApp` from `@backstage/frontend-defaults` with `appModulePublicSignIn` from `@backstage/plugin-app/alpha` instead. + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/frontend-app-api@0.16.0-next.1 + - @backstage/core-components@0.18.8-next.1 + - @backstage/plugin-app@0.4.1-next.2 + +## 0.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/plugin-app@0.4.1-next.0 + - @backstage/config@1.3.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/errors@1.2.7 + - @backstage/frontend-app-api@0.15.1-next.0 + +## 0.4.0 + +### Minor Changes + +- 55b2ef6: **BREAKING**: The `API_FACTORY_CONFLICT` warning is now treated as an error and will prevent the app from starting. + +### Patch Changes + +- 122d39c: Completely removed support for the deprecated `app.experimental.packages` configuration. Replace existing usage directly with `app.packages`. +- c38b74d: Dependency update for tests. +- 53b6549: Plugins in the new frontend system now have a `pluginId` field rather than `id` to better align with naming conventions used throughout the frontend and backend systems. The old field is still present but marked as deprecated. All internal code has been updated to prefer `pluginId` while maintaining backward compatibility by falling back to `id` when needed. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/frontend-app-api@0.15.0 + - @backstage/core-components@0.18.7 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/plugin-app@0.4.0 + +## 0.4.0-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/frontend-app-api@0.15.0-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/plugin-app@0.4.0-next.2 + +## 0.4.0-next.1 + +### Minor Changes + +- 55b2ef6: **BREAKING**: The `API_FACTORY_CONFLICT` warning is now treated as an error and will prevent the app from starting. + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.15.0-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/plugin-app@0.4.0-next.1 + - @backstage/core-components@0.18.7-next.1 + +## 0.3.6-next.0 + +### Patch Changes + +- 17e0eb3: The `API_FACTORY_CONFLICT` error is now treated as a warning and will not prevent the app from starting. +- 122d39c: Completely removed support for the deprecated `app.experimental.packages` configuration. Replace existing usage directly with `app.packages`. +- c38b74d: Dependency update for tests. +- 53b6549: Plugins in the new frontend system now have a `pluginId` field rather than `id` to better align with naming conventions used throughout the frontend and backend systems. The old field is still present but marked as deprecated. All internal code has been updated to prefer `pluginId` while maintaining backward compatibility by falling back to `id` when needed. +- Updated dependencies + - @backstage/frontend-app-api@0.14.1-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/plugin-app@0.4.0-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + - @backstage/plugin-app@0.3.4 + - @backstage/frontend-app-api@0.14.0 + +## 0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/plugin-app@0.3.4-next.1 + +## 0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app@0.3.4-next.0 + - @backstage/frontend-app-api@0.13.4-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## 0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app@0.3.3 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/frontend-app-api@0.13.3 + +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/plugin-app@0.3.3-next.0 + - @backstage/frontend-app-api@0.13.3-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.3.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/frontend-app-api@0.13.2 + - @backstage/core-components@0.18.3 + - @backstage/config@1.3.6 + - @backstage/plugin-app@0.3.2 + ## 0.3.3-next.0 ### Patch Changes diff --git a/packages/frontend-defaults/knip-report.md b/packages/frontend-defaults/knip-report.md index 8c0979b2c4..03c6806468 100644 --- a/packages/frontend-defaults/knip-report.md +++ b/packages/frontend-defaults/knip-report.md @@ -2,7 +2,7 @@ ## Unused dependencies (1) -| Name | Location | Severity | -| :--------------- | :----------- | :------- | -| @react-hookz/web | packages/frontend-defaults/package.json | error | +| Name | Location | Severity | +| :--------------- | :---------------- | :------- | +| @react-hookz/web | package.json:40:6 | error | diff --git a/packages/frontend-defaults/package.json b/packages/frontend-defaults/package.json index 3fcbb2c990..9c618d9656 100644 --- a/packages/frontend-defaults/package.json +++ b/packages/frontend-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-defaults", - "version": "0.3.3-next.0", + "version": "0.5.1", "backstage": { "role": "web-library" }, @@ -42,19 +42,22 @@ "devDependencies": { "@backstage/cli": "workspace:^", "@backstage/core-plugin-api": "workspace:^", + "@backstage/plugin-app-react": "workspace:^", + "@backstage/plugin-permission-common": "workspace:^", + "@backstage/plugin-permission-react": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^16.0.0", "@types/react": "^18.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/packages/frontend-defaults/report.api.md b/packages/frontend-defaults/report.api.md index 75392d2dc1..cfe4f55b2d 100644 --- a/packages/frontend-defaults/report.api.md +++ b/packages/frontend-defaults/report.api.md @@ -23,7 +23,6 @@ export function createApp(options?: CreateAppOptions): { // @public export interface CreateAppOptions { advanced?: { - allowUnknownExtensionConfig?: boolean; configLoader?: () => Promise<{ config: ConfigApi; }>; @@ -37,11 +36,6 @@ export interface CreateAppOptions { features?: (FrontendFeature | FrontendFeatureLoader)[]; } -// @public @deprecated (undocumented) -export function createPublicSignInApp(options?: CreateAppOptions): { - createRoot(): JSX_2; -}; - // @public (undocumented) export function discoverAvailableFeatures(config: Config): { features: (FrontendFeature | FrontendFeatureLoader)[]; diff --git a/packages/frontend-defaults/src/createApp.test.tsx b/packages/frontend-defaults/src/createApp.test.tsx index 7b5e345e12..9fa6b2d91a 100644 --- a/packages/frontend-defaults/src/createApp.test.tsx +++ b/packages/frontend-defaults/src/createApp.test.tsx @@ -16,23 +16,38 @@ import { AppTreeApi, + ApiBlueprint, appTreeApiRef, coreExtensionData, + createApiRef, + createExtensionDataRef, createExtension, + createExtensionBlueprint, + createExtensionInput, PageBlueprint, createFrontendPlugin, createFrontendFeatureLoader, - ThemeBlueprint, createFrontendModule, useAppNode, FrontendPluginInfo, } from '@backstage/frontend-plugin-api'; -import { screen, waitFor } from '@testing-library/react'; +import { ThemeBlueprint } from '@backstage/plugin-app-react'; +import { act, screen, waitFor } from '@testing-library/react'; import { createApp } from './createApp'; import { mockApis, renderWithEffects } from '@backstage/test-utils'; -import { featureFlagsApiRef, useApi } from '@backstage/core-plugin-api'; +import { + featureFlagsApiRef, + IdentityApi, + useApi, +} from '@backstage/core-plugin-api'; import { default as appPluginOriginal } from '@backstage/plugin-app'; -import { useState, useEffect } from 'react'; +import { ComponentType, useState, useEffect } from 'react'; +import { permissionApiRef } from '@backstage/plugin-permission-react'; +import { AuthorizeResult } from '@backstage/plugin-permission-common'; + +const signInPageComponentDataRef = createExtensionDataRef< + ComponentType<{ onSignInSuccess(identity: IdentityApi): void }> +>().with({ id: 'core.sign-in-page.component' }); describe('createApp', () => { const appPlugin = appPluginOriginal.withOverrides({ @@ -43,6 +58,25 @@ describe('createApp', () => { ], }); + function createFeatureFlagsApi(activeFlags: string[]) { + return { + isActive: jest.fn((name: string) => activeFlags.includes(name)), + registerFlag: jest.fn(), + getRegisteredFlags: () => [], + save: jest.fn(), + } as unknown as typeof featureFlagsApiRef.T; + } + + function createPermissionApi(allowedPermissions: string[]) { + return { + authorize: jest.fn(async request => ({ + result: allowedPermissions.includes(request.permission.name) + ? AuthorizeResult.ALLOW + : AuthorizeResult.DENY, + })), + } as typeof permissionApiRef.T; + } + it('should allow themes to be installed', async () => { const app = createApp({ advanced: { @@ -60,8 +94,8 @@ describe('createApp', () => { }), }, features: [ - createFrontendPlugin({ - pluginId: 'test', + createFrontendModule({ + pluginId: 'app', extensions: [ ThemeBlueprint.make({ name: 'derp', @@ -84,6 +118,184 @@ describe('createApp', () => { await expect(screen.findByText('Derp')).resolves.toBeInTheDocument(); }); + it('should provide app APIs to sign-in pages before finalization', async () => { + const signInApiRef = createApiRef<{ value: string }>({ + id: 'test.sign-in-api', + }); + + const app = createApp({ + advanced: { + configLoader: async () => ({ config: mockApis.config() }), + }, + features: [ + appPluginOriginal, + createFrontendPlugin({ + pluginId: 'test', + extensions: [ + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: signInApiRef, + deps: {}, + factory: () => ({ value: 'ok' }), + }), + }), + ], + }), + createFrontendModule({ + pluginId: 'app', + extensions: [ + appPluginOriginal.getExtension('sign-in-page:app').override({ + factory: () => { + const SignInPage = () => { + const api = useApi(signInApiRef); + return
    Sign In API: {api.value}
    ; + }; + + return [signInPageComponentDataRef(SignInPage)]; + }, + }), + ], + }), + ], + }); + + await renderWithEffects(app.createRoot()); + await expect( + screen.findByText('Sign In API: ok'), + ).resolves.toBeInTheDocument(); + }); + + it('should provide feature flags to sign-in pages before finalization', async () => { + const app = createApp({ + advanced: { + configLoader: async () => ({ config: mockApis.config() }), + }, + features: [ + appPluginOriginal, + createFrontendPlugin({ + pluginId: 'test', + featureFlags: [{ name: 'test-flag' }], + extensions: [], + }), + createFrontendModule({ + pluginId: 'app', + extensions: [ + appPluginOriginal.getExtension('sign-in-page:app').override({ + factory: () => { + const SignInPage = () => { + const flagsApi = useApi(featureFlagsApiRef); + return ( +
    + Flags:{' '} + {flagsApi + .getRegisteredFlags() + .map(flag => flag.name) + .join(', ')} +
    + ); + }; + + return [signInPageComponentDataRef(SignInPage)]; + }, + }), + ], + }), + ], + }); + + await renderWithEffects(app.createRoot()); + await expect( + screen.findByText('Flags: test-flag'), + ).resolves.toBeInTheDocument(); + }); + + it('should surface sign-in bootstrap errors through the app root boundary', async () => { + const identityApi = { + getProfileInfo: async () => ({ displayName: 'Test User' }), + getBackstageIdentity: async () => ({ + type: 'user' as const, + userEntityRef: 'user:default/test-user', + ownershipEntityRefs: ['user:default/test-user'], + }), + getCredentials: async () => ({ token: 'token' }), + signOut: async () => {}, + }; + const featureFlagsApi = { + isActive: jest.fn(() => { + throw new Error('sign-in bootstrap failed'); + }), + registerFlag: jest.fn(), + getRegisteredFlags: () => [], + save: jest.fn(), + } as unknown as typeof featureFlagsApiRef.T; + let onSignInSuccess: ((identity: IdentityApi) => void) | undefined; + + const app = createApp({ + advanced: { + configLoader: async () => ({ config: mockApis.config() }), + }, + features: [ + appPluginOriginal, + createFrontendModule({ + pluginId: 'app', + extensions: [ + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: featureFlagsApiRef, + deps: {}, + factory: () => featureFlagsApi, + }), + }), + appPluginOriginal.getExtension('sign-in-page:app').override({ + factory: () => { + function SignInPage(props: { + onSignInSuccess(identity: IdentityApi): void; + }) { + onSignInSuccess = props.onSignInSuccess; + + return
    Custom Sign In
    ; + } + + return [signInPageComponentDataRef(SignInPage)]; + }, + }), + ], + }), + createFrontendPlugin({ + pluginId: 'test', + featureFlags: [{ name: 'test-flag' }], + extensions: [ + PageBlueprint.make({ + if: { featureFlags: { $contains: 'test-flag' } }, + params: { + path: '/', + loader: async () =>
    Flagged Page
    , + }, + }), + ], + }), + ], + }); + + await renderWithEffects(app.createRoot()); + await expect( + screen.findByText('Custom Sign In'), + ).resolves.toBeInTheDocument(); + if (!onSignInSuccess) { + throw new Error('Expected sign-in success callback to be captured'); + } + const triggerSignInSuccess = onSignInSuccess; + act(() => { + triggerSignInSuccess(identityApi); + }); + + await expect( + screen.findByText('sign-in bootstrap failed'), + ).resolves.toBeInTheDocument(); + }); + it('should deduplicate features keeping the last received one', async () => { const duplicatedFeatureId = 'test'; const app = createApp({ @@ -283,40 +495,497 @@ describe('createApp', () => { ).resolves.toBeInTheDocument(); }); - it('should allow unknown extension config if the flag is set', async () => { + it('should evaluate extension if predicates before rendering apps without sign-in', async () => { + const featureFlagsApi = { + isActive: jest.fn((name: string) => name === 'test-flag'), + registerFlag: jest.fn(), + getRegisteredFlags: () => [], + save: jest.fn(), + } as unknown as typeof featureFlagsApiRef.T; const app = createApp({ + advanced: { + configLoader: async () => ({ config: mockApis.config() }), + }, features: [ appPlugin, + createFrontendModule({ + pluginId: 'app', + extensions: [ + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: featureFlagsApiRef, + deps: {}, + factory: () => featureFlagsApi, + }), + }), + ], + }), createFrontendPlugin({ pluginId: 'test', + featureFlags: [{ name: 'test-flag' }], extensions: [ PageBlueprint.make({ + if: { featureFlags: { $contains: 'test-flag' } }, params: { path: '/', - loader: async () =>
    Derp
    , + loader: async () =>
    Flagged Page
    , }, }), ], }), ], - advanced: { - allowUnknownExtensionConfig: true, - configLoader: async () => ({ - config: mockApis.config({ - data: { - app: { - extensions: [{ 'unknown:lols/wut': false }], - }, - }, - }), - }), - }, }); await renderWithEffects(app.createRoot()); - await expect(screen.findByText('Derp')).resolves.toBeInTheDocument(); + await expect( + screen.findByText('Flagged Page'), + ).resolves.toBeInTheDocument(); + expect(featureFlagsApi.isActive).toHaveBeenCalledWith('test-flag'); }); + + it('should support $all feature flag predicates on pages', async () => { + const partialFlagsApi = createFeatureFlagsApi(['experimental-features']); + const partialFlagsApp = createApp({ + advanced: { + configLoader: async () => ({ config: mockApis.config() }), + }, + features: [ + appPlugin, + createFrontendModule({ + pluginId: 'app', + extensions: [ + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: featureFlagsApiRef, + deps: {}, + factory: () => partialFlagsApi, + }), + }), + ], + }), + createFrontendPlugin({ + pluginId: 'test', + featureFlags: [ + { name: 'experimental-features' }, + { name: 'advanced-features' }, + ], + extensions: [ + PageBlueprint.make({ + if: { + $all: [ + { featureFlags: { $contains: 'experimental-features' } }, + { featureFlags: { $contains: 'advanced-features' } }, + ], + }, + params: { + path: '/', + loader: async () =>
    All Flags Page
    , + }, + }), + ], + }), + ], + }); + + const partialRender = await renderWithEffects(partialFlagsApp.createRoot()); + await waitFor(() => + expect(screen.queryByText('All Flags Page')).not.toBeInTheDocument(), + ); + partialRender.unmount(); + + const allFlagsApi = createFeatureFlagsApi([ + 'experimental-features', + 'advanced-features', + ]); + const allFlagsApp = createApp({ + advanced: { + configLoader: async () => ({ config: mockApis.config() }), + }, + features: [ + appPlugin, + createFrontendModule({ + pluginId: 'app', + extensions: [ + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: featureFlagsApiRef, + deps: {}, + factory: () => allFlagsApi, + }), + }), + ], + }), + createFrontendPlugin({ + pluginId: 'test', + featureFlags: [ + { name: 'experimental-features' }, + { name: 'advanced-features' }, + ], + extensions: [ + PageBlueprint.make({ + if: { + $all: [ + { featureFlags: { $contains: 'experimental-features' } }, + { featureFlags: { $contains: 'advanced-features' } }, + ], + }, + params: { + path: '/', + loader: async () =>
    All Flags Page
    , + }, + }), + ], + }), + ], + }); + + await renderWithEffects(allFlagsApp.createRoot()); + await expect( + screen.findByText('All Flags Page'), + ).resolves.toBeInTheDocument(); + expect(allFlagsApi.isActive).toHaveBeenCalledWith('experimental-features'); + expect(allFlagsApi.isActive).toHaveBeenCalledWith('advanced-features'); + }); + + it('should support $any feature flag predicates on pages', async () => { + const noFlagsApi = createFeatureFlagsApi([]); + const noFlagsApp = createApp({ + advanced: { + configLoader: async () => ({ config: mockApis.config() }), + }, + features: [ + appPlugin, + createFrontendModule({ + pluginId: 'app', + extensions: [ + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: featureFlagsApiRef, + deps: {}, + factory: () => noFlagsApi, + }), + }), + ], + }), + createFrontendPlugin({ + pluginId: 'test', + featureFlags: [ + { name: 'experimental-features' }, + { name: 'beta-access' }, + ], + extensions: [ + PageBlueprint.make({ + if: { + $any: [ + { featureFlags: { $contains: 'experimental-features' } }, + { featureFlags: { $contains: 'beta-access' } }, + ], + }, + params: { + path: '/', + loader: async () =>
    Any Flag Page
    , + }, + }), + ], + }), + ], + }); + + const noFlagsRender = await renderWithEffects(noFlagsApp.createRoot()); + await waitFor(() => + expect(screen.queryByText('Any Flag Page')).not.toBeInTheDocument(), + ); + noFlagsRender.unmount(); + + const oneFlagApi = createFeatureFlagsApi(['beta-access']); + const oneFlagApp = createApp({ + advanced: { + configLoader: async () => ({ config: mockApis.config() }), + }, + features: [ + appPlugin, + createFrontendModule({ + pluginId: 'app', + extensions: [ + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: featureFlagsApiRef, + deps: {}, + factory: () => oneFlagApi, + }), + }), + ], + }), + createFrontendPlugin({ + pluginId: 'test', + featureFlags: [ + { name: 'experimental-features' }, + { name: 'beta-access' }, + ], + extensions: [ + PageBlueprint.make({ + if: { + $any: [ + { featureFlags: { $contains: 'experimental-features' } }, + { featureFlags: { $contains: 'beta-access' } }, + ], + }, + params: { + path: '/', + loader: async () =>
    Any Flag Page
    , + }, + }), + ], + }), + ], + }); + + await renderWithEffects(oneFlagApp.createRoot()); + await expect( + screen.findByText('Any Flag Page'), + ).resolves.toBeInTheDocument(); + expect(oneFlagApi.isActive).toHaveBeenCalledWith('experimental-features'); + expect(oneFlagApi.isActive).toHaveBeenCalledWith('beta-access'); + }); + + it('should support permission predicates on pages', async () => { + const deniedPermissionApi = createPermissionApi([]); + const deniedApp = createApp({ + advanced: { + configLoader: async () => ({ config: mockApis.config() }), + }, + features: [ + appPlugin, + createFrontendModule({ + pluginId: 'app', + extensions: [ + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: permissionApiRef, + deps: {}, + factory: () => deniedPermissionApi, + }), + }), + ], + }), + createFrontendPlugin({ + pluginId: 'test', + extensions: [ + PageBlueprint.make({ + if: { permissions: { $contains: 'catalog.entity.create' } }, + params: { + path: '/', + loader: async () =>
    Permission Page
    , + }, + }), + ], + }), + ], + }); + + const deniedRender = await renderWithEffects(deniedApp.createRoot()); + await waitFor(() => + expect(screen.queryByText('Permission Page')).not.toBeInTheDocument(), + ); + deniedRender.unmount(); + + const allowedPermissionApi = createPermissionApi(['catalog.entity.create']); + const allowedApp = createApp({ + advanced: { + configLoader: async () => ({ config: mockApis.config() }), + }, + features: [ + appPlugin, + createFrontendModule({ + pluginId: 'app', + extensions: [ + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: permissionApiRef, + deps: {}, + factory: () => allowedPermissionApi, + }), + }), + ], + }), + createFrontendPlugin({ + pluginId: 'test', + extensions: [ + PageBlueprint.make({ + if: { permissions: { $contains: 'catalog.entity.create' } }, + params: { + path: '/', + loader: async () =>
    Permission Page
    , + }, + }), + ], + }), + ], + }); + + await renderWithEffects(allowedApp.createRoot()); + await expect( + screen.findByText('Permission Page'), + ).resolves.toBeInTheDocument(); + expect(allowedPermissionApi.authorize).toHaveBeenCalledWith({ + permission: { + name: 'catalog.entity.create', + type: 'basic', + attributes: {}, + }, + }); + }); + + it('should support conditional child extensions attached to pages', async () => { + const CardBlueprint = createExtensionBlueprint({ + kind: 'card', + attachTo: { id: 'page:test/card-page', input: 'cards' }, + output: [coreExtensionData.reactElement], + *factory(params: { title: string }) { + yield coreExtensionData.reactElement(
    {params.title}
    ); + }, + }); + + const page = PageBlueprint.makeWithOverrides({ + name: 'card-page', + inputs: { + cards: createExtensionInput([coreExtensionData.reactElement], { + optional: false, + singleton: false, + }), + }, + factory(originalFactory, { inputs }) { + return originalFactory({ + path: '/', + loader: async () => ( +
    + {inputs.cards.map(card => + card.get(coreExtensionData.reactElement), + )} +
    + ), + }); + }, + }); + + const publicCard = CardBlueprint.make({ + name: 'public', + params: { title: 'Public Card' }, + }); + const permissionCard = CardBlueprint.make({ + name: 'permission', + params: { title: 'Permission Card' }, + if: { permissions: { $contains: 'catalog.entity.create' } }, + }); + const featureFlagCard = CardBlueprint.make({ + name: 'feature-flag', + params: { title: 'Feature Flag Card' }, + if: { featureFlags: { $contains: 'experimental-card' } }, + }); + + const hiddenCardsApp = createApp({ + advanced: { + configLoader: async () => ({ config: mockApis.config() }), + }, + features: [ + appPlugin, + createFrontendModule({ + pluginId: 'app', + extensions: [ + ApiBlueprint.make({ + name: 'permission-api', + params: defineParams => + defineParams({ + api: permissionApiRef, + deps: {}, + factory: () => createPermissionApi([]), + }), + }), + ApiBlueprint.make({ + name: 'feature-flags-api', + params: defineParams => + defineParams({ + api: featureFlagsApiRef, + deps: {}, + factory: () => createFeatureFlagsApi([]), + }), + }), + ], + }), + createFrontendPlugin({ + pluginId: 'test', + featureFlags: [{ name: 'experimental-card' }], + extensions: [page, publicCard, permissionCard, featureFlagCard], + }), + ], + }); + + const hiddenCardsRender = await renderWithEffects( + hiddenCardsApp.createRoot(), + ); + await expect(screen.findByText('Public Card')).resolves.toBeInTheDocument(); + await waitFor(() => + expect(screen.queryByText('Permission Card')).not.toBeInTheDocument(), + ); + await waitFor(() => + expect(screen.queryByText('Feature Flag Card')).not.toBeInTheDocument(), + ); + hiddenCardsRender.unmount(); + + const visibleCardsApp = createApp({ + advanced: { + configLoader: async () => ({ config: mockApis.config() }), + }, + features: [ + appPlugin, + createFrontendModule({ + pluginId: 'app', + extensions: [ + ApiBlueprint.make({ + name: 'permission-api', + params: defineParams => + defineParams({ + api: permissionApiRef, + deps: {}, + factory: () => createPermissionApi(['catalog.entity.create']), + }), + }), + ApiBlueprint.make({ + name: 'feature-flags-api', + params: defineParams => + defineParams({ + api: featureFlagsApiRef, + deps: {}, + factory: () => createFeatureFlagsApi(['experimental-card']), + }), + }), + ], + }), + createFrontendPlugin({ + pluginId: 'test', + featureFlags: [{ name: 'experimental-card' }], + extensions: [page, publicCard, permissionCard, featureFlagCard], + }), + ], + }); + + await renderWithEffects(visibleCardsApp.createRoot()); + await expect( + screen.findByText('Permission Card'), + ).resolves.toBeInTheDocument(); + await expect( + screen.findByText('Feature Flag Card'), + ).resolves.toBeInTheDocument(); + }); + it('should make the app structure available through the AppTreeApi', async () => { let appTreeApi: AppTreeApi | undefined = undefined; @@ -357,6 +1026,8 @@ describe('createApp', () => { + + @@ -388,10 +1059,13 @@ describe('createApp', () => { + ] + + ] @@ -418,9 +1092,6 @@ describe('createApp', () => { ] - signInPage [ - - ] ] diff --git a/packages/frontend-defaults/src/createApp.tsx b/packages/frontend-defaults/src/createApp.tsx index 4ff67f6d5a..728ec29e42 100644 --- a/packages/frontend-defaults/src/createApp.tsx +++ b/packages/frontend-defaults/src/createApp.tsx @@ -14,10 +14,9 @@ * limitations under the License. */ -import { JSX, lazy, ReactNode, Suspense } from 'react'; +import { JSX, lazy, ReactNode, Suspense, useEffect, useState } from 'react'; import { ConfigApi, - coreExtensionData, ExtensionFactoryMiddleware, FrontendFeature, FrontendFeatureLoader, @@ -30,7 +29,9 @@ import { overrideBaseUrlConfigs } from '../../core-app-api/src/app/overrideBaseU import { ConfigReader } from '@backstage/config'; import { CreateAppRouteBinder, - createSpecializedApp, + FinalizedSpecializedApp, + prepareSpecializedApp, + PreparedSpecializedApp, FrontendPluginInfoResolver, } from '@backstage/frontend-app-api'; import appPlugin from '@backstage/plugin-app'; @@ -58,17 +59,6 @@ export interface CreateAppOptions { * Advanced, more rarely used options. */ advanced?: { - /** - * If set to true, the system will silently accept and move on if - * encountering config for extensions that do not exist. The default is to - * reject such config to help catch simple mistakes. - * - * This flag can be useful in some scenarios where you have a dynamic set of - * extensions enabled at different times, but also increases the risk of - * accidentally missing e.g. simple typos in your config. - */ - allowUnknownExtensionConfig?: boolean; - /** * Sets a custom config loader, replacing the builtin one. * @@ -130,23 +120,16 @@ export function createApp(options?: CreateAppOptions): { features: [...discoveredFeaturesAndLoaders, ...(options?.features ?? [])], }); - const app = createSpecializedApp({ + const preparedApp = prepareSpecializedApp({ features: [appPlugin, ...loadedFeatures], config, bindRoutes: options?.bindRoutes, advanced: options?.advanced, }); - const errorPage = maybeCreateErrorPage(app); - if (errorPage) { - return { default: () => errorPage }; - } - - const rootEl = app.tree.root.instance!.getData( - coreExtensionData.reactElement, - ); - - return { default: () => rootEl }; + return { + default: () => , + }; } const LazyApp = lazy(appLoader); @@ -161,3 +144,28 @@ export function createApp(options?: CreateAppOptions): { }, }; } + +function PreparedAppRoot(props: { + preparedApp: PreparedSpecializedApp; +}): JSX.Element { + const bootstrapApp = props.preparedApp.getBootstrapApp(); + const [finalizedApp, setFinalizedApp] = useState< + FinalizedSpecializedApp | undefined + >(); + + useEffect( + () => props.preparedApp.onFinalized(setFinalizedApp), + [props.preparedApp], + ); + + if (!finalizedApp) { + return bootstrapApp.element; + } + + const errorPage = maybeCreateErrorPage(finalizedApp); + if (errorPage) { + return errorPage; + } + + return finalizedApp.element; +} diff --git a/packages/frontend-defaults/src/createPublicSignInApp.test.tsx b/packages/frontend-defaults/src/createPublicSignInApp.test.tsx deleted file mode 100644 index e80d058140..0000000000 --- a/packages/frontend-defaults/src/createPublicSignInApp.test.tsx +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - SignInPageBlueprint, - createFrontendModule, -} from '@backstage/frontend-plugin-api'; -import { render, screen, waitFor } from '@testing-library/react'; -import { useEffect } from 'react'; -import { createPublicSignInApp } from './createPublicSignInApp'; -import { mockApis } from '@backstage/test-utils'; - -describe('createPublicSignInApp', () => { - beforeEach(() => { - jest.resetAllMocks(); - }); - - it('should render a sign-in page', async () => { - const app = createPublicSignInApp({ - advanced: { - configLoader: async () => ({ config: mockApis.config() }), - }, - features: [ - createFrontendModule({ - pluginId: 'app', - extensions: [ - SignInPageBlueprint.make({ - params: { - loader: async () => () =>
    Sign in page
    , - }, - }), - ], - }), - ], - }); - - render(app.createRoot()); - - await expect( - screen.findByText('Sign in page'), - ).resolves.toBeInTheDocument(); - }); - - it('should render the form redirect on sign-in', async () => { - const submitSpy = jest - .spyOn(HTMLFormElement.prototype, 'submit') - .mockReturnValue(); - - const app = createPublicSignInApp({ - advanced: { - configLoader: async () => ({ config: mockApis.config() }), - }, - features: [ - createFrontendModule({ - pluginId: 'app', - extensions: [ - SignInPageBlueprint.make({ - params: { - loader: - async () => - ({ onSignInSuccess }) => { - useEffect(() => { - onSignInSuccess( - mockApis.identity({ token: 'mock-token' }), - ); - }, [onSignInSuccess]); - return
    ; - }, - }, - }), - ], - }), - ], - }); - - const { baseElement } = render(app.createRoot()); - - await waitFor(() => { - expect(submitSpy).toHaveBeenCalled(); - }); - - expect(baseElement).toMatchInlineSnapshot(` - -
    -
    - - - - -
    - - `); - }); -}); diff --git a/packages/frontend-defaults/src/discovery.ts b/packages/frontend-defaults/src/discovery.ts index 71d831b58c..584f202c54 100644 --- a/packages/frontend-defaults/src/discovery.ts +++ b/packages/frontend-defaults/src/discovery.ts @@ -26,10 +26,7 @@ interface DiscoveryGlobal { } function readPackageDetectionConfig(config: Config) { - // The experimental key is deprecated, but supported still for backwards compatibility - const packages = - config.getOptional('app.packages') ?? - config.getOptional('app.experimental.packages'); + const packages = config.getOptional('app.packages'); if (packages === undefined || packages === null) { return undefined; } diff --git a/packages/frontend-defaults/src/index.ts b/packages/frontend-defaults/src/index.ts index 37ec62e794..c7f3e468a2 100644 --- a/packages/frontend-defaults/src/index.ts +++ b/packages/frontend-defaults/src/index.ts @@ -21,7 +21,6 @@ */ export { createApp, type CreateAppOptions } from './createApp'; -export { createPublicSignInApp } from './createPublicSignInApp'; export { discoverAvailableFeatures } from './discovery'; export { resolveAsyncFeatures } from './resolution'; export { maybeCreateErrorPage } from './maybeCreateErrorPage'; diff --git a/packages/frontend-defaults/src/maybeCreateErrorPage.tsx b/packages/frontend-defaults/src/maybeCreateErrorPage.tsx index 6bad97f666..8c058b838f 100644 --- a/packages/frontend-defaults/src/maybeCreateErrorPage.tsx +++ b/packages/frontend-defaults/src/maybeCreateErrorPage.tsx @@ -22,7 +22,10 @@ const DEFAULT_WARNING_CODES: Array = [ 'EXTENSION_IGNORED', 'INVALID_EXTENSION_CONFIG_KEY', 'EXTENSION_INPUT_DATA_IGNORED', + 'EXTENSION_INPUT_INTERNAL_IGNORED', 'EXTENSION_OUTPUT_IGNORED', + 'EXTENSION_BOOTSTRAP_PREDICATE_IGNORED', + 'EXTENSION_BOOTSTRAP_API_UNAVAILABLE', ]; function AppErrorItem(props: { error: AppError }): JSX.Element { @@ -33,13 +36,15 @@ function AppErrorItem(props: { error: AppError }): JSX.Element { 'extensionId' in context ? context.extensionId : node?.spec.id; const routeId = 'routeId' in context ? context.routeId : undefined; const plugin = 'plugin' in context ? context.plugin : node?.spec.plugin; - const pluginId = plugin?.id ?? 'N/A'; + const pluginId = plugin?.pluginId ?? 'N/A'; const [info, setInfo] = useState(undefined); useEffect(() => { plugin?.info().then(setInfo, error => { // eslint-disable-next-line no-console - console.error(`Failed to load info for plugin ${plugin.id}: ${error}`); + console.error( + `Failed to load info for plugin ${plugin.pluginId}: ${error}`, + ); }); }, [plugin]); diff --git a/packages/frontend-dev-utils/.eslintrc.js b/packages/frontend-dev-utils/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/packages/frontend-dev-utils/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/packages/frontend-dev-utils/CHANGELOG.md b/packages/frontend-dev-utils/CHANGELOG.md new file mode 100644 index 0000000000..e1950c2ea7 --- /dev/null +++ b/packages/frontend-dev-utils/CHANGELOG.md @@ -0,0 +1,45 @@ +# @backstage/frontend-dev-utils + +## 0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0 + - @backstage/plugin-app@0.4.3 + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/frontend-defaults@0.5.1 + +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app@0.4.3-next.1 + - @backstage/ui@0.14.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/frontend-defaults@0.5.1-next.1 + +## 0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.0 + - @backstage/plugin-app@0.4.3-next.0 + - @backstage/frontend-plugin-api@0.15.2-next.0 + - @backstage/frontend-defaults@0.5.1-next.0 + +## 0.1.0 + +### Minor Changes + +- c25532a: Added `@backstage/frontend-dev-utils`, a new package that provides a minimal helper for wiring up a development app for frontend plugins using the new frontend system. It exports a `createDevApp` function that handles creating and rendering a development app from a `dev/` entry point. The dev app automatically bypasses the sign-in page and loads the `@backstage/ui` CSS. The options interface accepts `features` together with route bindings through `bindRoutes`. + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.13.0 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/plugin-app@0.4.1 + - @backstage/frontend-defaults@0.5.0 diff --git a/packages/frontend-dev-utils/README.md b/packages/frontend-dev-utils/README.md new file mode 100644 index 0000000000..5a590c1d58 --- /dev/null +++ b/packages/frontend-dev-utils/README.md @@ -0,0 +1,19 @@ +# @backstage/frontend-dev-utils + +Utilities for developing Backstage frontend plugins using the new frontend system. + +This package provides a minimal helper for wiring up a development app from a `dev/` entry point, making it easy to run and test frontend plugins in isolation. + +## Installation + +Install the package via Yarn: + +```sh +cd plugins/ # if within a monorepo +yarn add -D @backstage/frontend-dev-utils +``` + +## Documentation + +- [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +- [Backstage Documentation](https://backstage.io/docs) diff --git a/packages/frontend-dev-utils/catalog-info.yaml b/packages/frontend-dev-utils/catalog-info.yaml new file mode 100644 index 0000000000..b05c739594 --- /dev/null +++ b/packages/frontend-dev-utils/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-frontend-dev-utils + title: '@backstage/frontend-dev-utils' + description: Utilities for developing Backstage frontend plugins using the new frontend system. +spec: + lifecycle: experimental + type: backstage-web-library + owner: framework-maintainers diff --git a/packages/frontend-dev-utils/knip-report.md b/packages/frontend-dev-utils/knip-report.md new file mode 100644 index 0000000000..7fffe5ad37 --- /dev/null +++ b/packages/frontend-dev-utils/knip-report.md @@ -0,0 +1,9 @@ +# Knip report + +## Unused devDependencies (2) + +| Name | Location | Severity | +| :------------------------ | :---------------- | :------- | +| @testing-library/jest-dom | package.json:47:6 | error | +| @backstage/test-utils | package.json:46:6 | error | + diff --git a/packages/frontend-dev-utils/package.json b/packages/frontend-dev-utils/package.json new file mode 100644 index 0000000000..76dbab4920 --- /dev/null +++ b/packages/frontend-dev-utils/package.json @@ -0,0 +1,65 @@ +{ + "name": "@backstage/frontend-dev-utils", + "version": "0.1.1", + "description": "Utilities for developing Backstage frontend plugins using the new frontend system.", + "backstage": { + "role": "web-library" + }, + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "keywords": [ + "backstage" + ], + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "packages/frontend-dev-utils" + }, + "license": "Apache-2.0", + "sideEffects": false, + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "backstage-cli package build", + "clean": "backstage-cli package clean", + "lint": "backstage-cli package lint", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "start": "backstage-cli package start", + "test": "backstage-cli package test" + }, + "dependencies": { + "@backstage/frontend-defaults": "workspace:^", + "@backstage/frontend-plugin-api": "workspace:^", + "@backstage/plugin-app": "workspace:^", + "@backstage/ui": "workspace:^" + }, + "devDependencies": { + "@backstage/cli": "workspace:^", + "@backstage/test-utils": "workspace:^", + "@testing-library/jest-dom": "^6.0.0", + "@testing-library/react": "^16.0.0", + "@types/react": "^18.0.0", + "react": "^18.0.2", + "react-dom": "^18.0.2", + "react-router-dom": "^6.30.2" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0", + "react-router-dom": "^6.30.2" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } +} diff --git a/packages/frontend-dev-utils/report.api.md b/packages/frontend-dev-utils/report.api.md new file mode 100644 index 0000000000..39ca2aec2b --- /dev/null +++ b/packages/frontend-dev-utils/report.api.md @@ -0,0 +1,18 @@ +## API Report File for "@backstage/frontend-dev-utils" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { CreateAppOptions } from '@backstage/frontend-defaults'; +import { FrontendFeature } from '@backstage/frontend-plugin-api'; +import { FrontendFeatureLoader } from '@backstage/frontend-plugin-api'; + +// @public +export function createDevApp(options: CreateDevAppOptions): void; + +// @public +export interface CreateDevAppOptions { + bindRoutes?: CreateAppOptions['bindRoutes']; + features: (FrontendFeature | FrontendFeatureLoader)[]; +} +``` diff --git a/packages/app-next/src/index-public-experimental.tsx b/packages/frontend-dev-utils/src/BuiCss.tsx similarity index 62% rename from packages/app-next/src/index-public-experimental.tsx rename to packages/frontend-dev-utils/src/BuiCss.tsx index ce558ed4f0..d5ece19b71 100644 --- a/packages/app-next/src/index-public-experimental.tsx +++ b/packages/frontend-dev-utils/src/BuiCss.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,14 +14,12 @@ * limitations under the License. */ -import ReactDOM from 'react-dom/client'; -import { createApp } from '@backstage/frontend-defaults'; -import { appModulePublicSignIn } from '@backstage/plugin-app/alpha'; - +// This ensures that dev apps always have the BUI CSS loaded. +// eslint-disable-next-line @backstage/no-ui-css-imports-in-non-frontend import '@backstage/ui/css/styles.css'; -const app = createApp({ - features: [appModulePublicSignIn], -}); - -ReactDOM.createRoot(document.getElementById('root')!).render(app.createRoot()); +/** + * Placeholder component to allow lazy loading of the BUI CSS import. This + * ensures that we don't load the CSS as soon as anyone imports this package. + */ +export default () => null; diff --git a/packages/frontend-dev-utils/src/createDevApp.test.tsx b/packages/frontend-dev-utils/src/createDevApp.test.tsx new file mode 100644 index 0000000000..5c0b21ab25 --- /dev/null +++ b/packages/frontend-dev-utils/src/createDevApp.test.tsx @@ -0,0 +1,193 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + PageBlueprint, + createFrontendPlugin, +} from '@backstage/frontend-plugin-api'; +import { waitFor, within } from '@testing-library/react'; +import { createDevApp } from './createDevApp'; + +jest.setTimeout(15000); + +const originalEnv = process.env; + +function loadCreateDevAppIsolated(): typeof import('./createDevApp').createDevApp { + let isolatedCreateDevApp: + | typeof import('./createDevApp').createDevApp + | undefined; + + jest.isolateModules(() => { + ({ createDevApp: isolatedCreateDevApp } = require('./createDevApp')); + }); + + if (!isolatedCreateDevApp) { + throw new Error('Expected createDevApp to be loaded in isolation'); + } + + return isolatedCreateDevApp; +} + +describe('createDevApp', () => { + beforeEach(() => { + process.env = { ...originalEnv }; + }); + + afterEach(() => { + document.body.innerHTML = ''; + jest.resetAllMocks(); + }); + + afterAll(() => { + process.env = originalEnv; + }); + + it('should render a dev app with a plugin', async () => { + const root = document.createElement('div'); + root.id = 'root'; + document.body.appendChild(root); + + const testPlugin = createFrontendPlugin({ + pluginId: 'test', + extensions: [ + PageBlueprint.make({ + params: { + path: '/', + loader: async () =>
    Test Plugin Page
    , + }, + }), + ], + }); + + (process.env as any).APP_CONFIG = [ + { + context: 'test', + data: { + app: { title: 'Test App' }, + backend: { baseUrl: 'http://localhost' }, + }, + }, + ]; + + createDevApp({ + features: [testPlugin], + }); + + const body = within(document.body); + await body.findByText('Test Plugin Page', {}, { timeout: 10000 }); + }); + + it('should forward bindRoutes to createApp', async () => { + jest.resetModules(); + + const bindRoutes = jest.fn(); + const createApp = jest.fn(() => ({ + createRoot: () =>
    Test App Root
    , + })); + const render = jest.fn(); + const createRoot = jest.fn(() => ({ render })); + + jest.doMock('@backstage/frontend-defaults', () => ({ + createApp, + })); + jest.doMock('@backstage/plugin-app', () => ({ + __esModule: true, + default: { + withOverrides: jest.fn(() => 'app-plugin-override'), + getExtension: jest.fn(() => ({ + override: jest.fn(() => 'disabled-sign-in-page'), + })), + }, + })); + jest.doMock('react-dom/client', () => ({ + __esModule: true, + createRoot, + })); + + const root = document.createElement('div'); + root.id = 'root'; + document.body.appendChild(root); + + const isolatedCreateDevApp = loadCreateDevAppIsolated(); + isolatedCreateDevApp({ + bindRoutes, + features: ['plugin-feature'] as any, + }); + + await waitFor(() => { + expect(createApp).toHaveBeenCalledWith({ + bindRoutes, + features: ['app-plugin-override', 'plugin-feature'], + }); + expect(createRoot).toHaveBeenCalledWith(root); + }); + + const renderedNode = render.mock.calls[0][0] as any; + expect(renderedNode.props.children).toHaveLength(2); + expect(renderedNode.props.children[0].props.fallback).toBeNull(); + expect(renderedNode.props.children[1].props.children).toBe('Test App Root'); + }); + + it('should throw a clear error when the root element is missing', () => { + expect(() => createDevApp({ features: [] })).toThrow( + "Could not find the dev app root element '#root'; make sure your dev entry HTML contains a root element with that id.", + ); + }); + + it('should fall back to legacy react-dom rendering when createRoot is unavailable', async () => { + jest.resetModules(); + delete process.env.HAS_REACT_DOM_CLIENT; + + const createApp = jest.fn(() => ({ + createRoot: () =>
    Test App Root
    , + })); + const render = jest.fn(); + + jest.doMock('@backstage/frontend-defaults', () => ({ + createApp, + })); + jest.doMock('@backstage/plugin-app', () => ({ + __esModule: true, + default: { + withOverrides: jest.fn(() => 'app-plugin-override'), + getExtension: jest.fn(() => ({ + override: jest.fn(() => 'disabled-sign-in-page'), + })), + }, + })); + jest.doMock('react-dom', () => ({ + __esModule: true, + render, + })); + + const root = document.createElement('div'); + root.id = 'root'; + document.body.appendChild(root); + + const isolatedCreateDevApp = loadCreateDevAppIsolated(); + isolatedCreateDevApp({ + features: ['plugin-feature'] as any, + }); + + await waitFor(() => { + expect(render).toHaveBeenCalled(); + expect(createApp).toHaveBeenCalledWith({ + bindRoutes: undefined, + features: ['app-plugin-override', 'plugin-feature'], + }); + }); + }); +}); diff --git a/packages/frontend-dev-utils/src/createDevApp.tsx b/packages/frontend-dev-utils/src/createDevApp.tsx new file mode 100644 index 0000000000..80cc95dc22 --- /dev/null +++ b/packages/frontend-dev-utils/src/createDevApp.tsx @@ -0,0 +1,125 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + FrontendFeature, + FrontendFeatureLoader, +} from '@backstage/frontend-plugin-api'; +import { createApp, CreateAppOptions } from '@backstage/frontend-defaults'; +import appPlugin from '@backstage/plugin-app'; +import { Suspense, lazy } from 'react'; +import 'react-dom'; + +type AppPluginWithSimpleOverrides = { + withOverrides(options: { extensions: unknown[] }): FrontendFeature; +}; + +// Collapse the deeply nested override types to avoid excessive instantiation. +const appPluginOverride = ( + appPlugin as unknown as AppPluginWithSimpleOverrides +).withOverrides({ + extensions: [ + appPlugin.getExtension('sign-in-page:app').override({ + disabled: true, + }), + ], +}); + +const BuiCss = lazy(() => import('./BuiCss')); + +let ReactDOMPromise: Promise< + typeof import('react-dom') | typeof import('react-dom/client') +>; +if (process.env.HAS_REACT_DOM_CLIENT) { + ReactDOMPromise = import('react-dom/client'); +} else { + ReactDOMPromise = import('react-dom'); +} + +/** + * Options for {@link createDevApp}. + * + * @public + */ +export interface CreateDevAppOptions { + /** + * The list of features to load in the dev app. + */ + features: (FrontendFeature | FrontendFeatureLoader)[]; + + /** + * Allows for the binding of plugins' external route refs within the dev app. + */ + bindRoutes?: CreateAppOptions['bindRoutes']; +} + +function getRootElement(): HTMLElement { + const rootElement = document.getElementById('root'); + + if (!rootElement) { + throw new Error( + "Could not find the dev app root element '#root'; make sure your dev entry HTML contains a root element with that id.", + ); + } + + return rootElement; +} + +/** + * Creates and renders a minimal development app for the new frontend system. + * + * @example + * ```tsx + * // dev/index.ts + * import { createDevApp } from '@backstage/frontend-dev-utils'; + * import myPlugin from '../src'; + * + * createDevApp({ features: [myPlugin] }); + * ``` + * + * @public + */ +export function createDevApp(options: CreateDevAppOptions): void { + const rootElement = getRootElement(); + const { features, bindRoutes } = options; + const devFeatures: CreateAppOptions['features'] = [ + appPluginOverride, + ...features, + ]; + const appOptions: CreateAppOptions = { + bindRoutes, + features: devFeatures, + }; + const app = createApp(appOptions); + const AppRoot = app.createRoot(); + + ReactDOMPromise.then(ReactDOM => { + const rootNode = ( + <> + + + + {AppRoot} + + ); + + if ('createRoot' in ReactDOM) { + ReactDOM.createRoot(rootElement).render(rootNode); + } else { + ReactDOM.render(rootNode, rootElement); + } + }); +} diff --git a/packages/frontend-dev-utils/src/index.ts b/packages/frontend-dev-utils/src/index.ts new file mode 100644 index 0000000000..d93fa3d473 --- /dev/null +++ b/packages/frontend-dev-utils/src/index.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Utilities for developing Backstage frontend plugins using the new frontend system. + * + * @packageDocumentation + */ + +export { createDevApp, type CreateDevAppOptions } from './createDevApp'; diff --git a/packages/frontend-dynamic-feature-loader/CHANGELOG.md b/packages/frontend-dynamic-feature-loader/CHANGELOG.md index cc2c421a4e..b544986b33 100644 --- a/packages/frontend-dynamic-feature-loader/CHANGELOG.md +++ b/packages/frontend-dynamic-feature-loader/CHANGELOG.md @@ -1,5 +1,123 @@ # @backstage/frontend-dynamic-feature-loader +## 0.1.11 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/config@1.3.7 + - @backstage/module-federation-common@0.1.3 + +## 0.1.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.7-next.0 + - @backstage/frontend-plugin-api@0.16.0-next.2 + - @backstage/module-federation-common@0.1.3-next.0 + +## 0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + +## 0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.2-next.0 + - @backstage/config@1.3.6 + - @backstage/module-federation-common@0.1.2 + +## 0.1.10 + +### Patch Changes + +- e8ba654: Update the README of the `frontend-dynamic-feature-loader` package to mention the new `backstage-cli package bundle` command. +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/module-federation-common@0.1.2 + +## 0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + +## 0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/module-federation-common@0.1.2-next.0 + - @backstage/config@1.3.6 + - @backstage/frontend-plugin-api@0.14.2-next.0 + +## 0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/config@1.3.6 + - @backstage/module-federation-common@0.1.0 + +## 0.1.9 + +### Patch Changes + +- fdbd404: Updated module federation integration to use `@module-federation/enhanced/runtime` `createInstance` API and the new `loadModuleFederationHostShared` from `@backstage/module-federation-common` for loading shared dependencies. Also added support for passing a pre-created `ModuleFederation` instance via the `moduleFederation.instance` option. +- fdbd404: Updated `@module-federation/enhanced`, `@module-federation/runtime`, and `@module-federation/sdk` dependencies from `^0.9.0` to `^0.21.6`. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/module-federation-common@0.1.0 + +## 0.1.9-next.1 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.2 + +## 0.1.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/config@1.3.6 + +## 0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + +## 0.1.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/config@1.3.6 + +## 0.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/config@1.3.6 + ## 0.1.7-next.0 ### Patch Changes diff --git a/packages/frontend-dynamic-feature-loader/README.md b/packages/frontend-dynamic-feature-loader/README.md index a1ab46d7b0..a3802c3901 100644 --- a/packages/frontend-dynamic-feature-loader/README.md +++ b/packages/frontend-dynamic-feature-loader/README.md @@ -22,11 +22,12 @@ The frontend feature loader provided in this package works hand-in-hand with the Adding a frontend plugin (with new frontend system support, possibly in alpha support), is straightforward and consists in: -- building the frontend plugin with the `frontend-dynamic-container` role, which enables the module federation support, and packages the plugin as a module remote -- copying the frontend package folder, with the `dist` folder generated during the build, to the dynamic plugins root folder of the Backstage installation (defined by the `dynamicPlugins.rootDirectory` configuration value, which is usually set as `dynamic-plugins-root`). +- bundling the frontend plugin with the [`backstage-cli package bundle`](../../docs/tooling/cli/03-commands.md#package-bundle) command, thus producing a self-contained bundle based on Module Federation. +- copying the bundle folder into the Backstage installation dynamic plugins root folder for dynamic loading. -So from a frontend plugin package folder, you would use the following command: +So from a `my-backstage-plugin` frontend plugin package folder, you would use the following command: ```bash -yarn build --role frontend-dynamic-container && cp -R $(pwd) /dynamic-plugins-root/ +yarn backstage-cli package bundle --output-destination /path/to/dynamic-plugins-root +# Creates a self-contained bundle in the /path/to/dynamic-plugins-root/my-backstage-plugin/ sub-folder ``` diff --git a/packages/frontend-dynamic-feature-loader/knip-report.md b/packages/frontend-dynamic-feature-loader/knip-report.md index 0645b2b1cc..2661c35327 100644 --- a/packages/frontend-dynamic-feature-loader/knip-report.md +++ b/packages/frontend-dynamic-feature-loader/knip-report.md @@ -1,8 +1,2 @@ # Knip report -## Unused dependencies (1) - -| Name | Location | Severity | -| :---------------- | :----------- | :------- | -| @backstage/config | packages/frontend-dynamic-feature-loader/package.json | error | - diff --git a/packages/frontend-dynamic-feature-loader/package.json b/packages/frontend-dynamic-feature-loader/package.json index ce61447770..94fb9b8e7c 100644 --- a/packages/frontend-dynamic-feature-loader/package.json +++ b/packages/frontend-dynamic-feature-loader/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-dynamic-feature-loader", - "version": "0.1.7-next.0", + "version": "0.1.11", "backstage": { "role": "web-library" }, @@ -34,9 +34,10 @@ "dependencies": { "@backstage/config": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", - "@module-federation/enhanced": "^0.9.0", - "@module-federation/runtime": "^0.9.0", - "@module-federation/sdk": "^0.9.0", + "@backstage/module-federation-common": "workspace:^", + "@module-federation/enhanced": "^2.3.3", + "@module-federation/runtime": "^2.3.3", + "@module-federation/sdk": "^2.3.3", "cross-fetch": "^4.0.0", "uri-template": "^2.0.0" }, @@ -49,13 +50,13 @@ "msw": "^1.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/packages/frontend-dynamic-feature-loader/report.api.md b/packages/frontend-dynamic-feature-loader/report.api.md index 3188551bad..fb14cb7c57 100644 --- a/packages/frontend-dynamic-feature-loader/report.api.md +++ b/packages/frontend-dynamic-feature-loader/report.api.md @@ -3,8 +3,9 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; import { FrontendFeatureLoader } from '@backstage/frontend-plugin-api'; +import { ModuleFederation } from '@module-federation/enhanced/runtime'; +import { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; import { ShareStrategy } from '@module-federation/runtime/types'; import { UserOptions } from '@module-federation/runtime/types'; @@ -18,7 +19,8 @@ export type DynamicFrontendFeaturesLoaderOptions = { moduleFederation: { shared?: UserOptions['shared']; shareStrategy?: ShareStrategy; - plugins?: Array; + plugins?: Array; + instance?: ModuleFederation; }; }; ``` diff --git a/packages/frontend-dynamic-feature-loader/src/loader.test.tsx b/packages/frontend-dynamic-feature-loader/src/loader.test.tsx index b2f6486766..78ed305c5f 100644 --- a/packages/frontend-dynamic-feature-loader/src/loader.test.tsx +++ b/packages/frontend-dynamic-feature-loader/src/loader.test.tsx @@ -15,21 +15,44 @@ */ import { mockApis, registerMswTestHooks } from '@backstage/test-utils'; -import { - DynamicFrontendFeaturesLoaderOptions, - dynamicFrontendFeaturesLoader, -} from './loader'; +import { dynamicFrontendFeaturesLoader } from './loader'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; +import { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; import { RemoteEntryExports } from '@module-federation/runtime/types'; import { Module } from '@module-federation/sdk'; import { createFrontendPlugin } from '@backstage/frontend-plugin-api'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { InternalFrontendFeatureLoader } from '../../frontend-plugin-api/src/wiring/createFrontendFeatureLoader'; import { resetFederationGlobalInfo } from '@module-federation/runtime/core'; +import { Config } from '@backstage/config'; const baseUrl = 'http://localhost:7007'; +function mockDefaultConfig(): Config { + return mockApis.config({ + data: { + app: { + packages: { + include: [], + }, + }, + backend: { + baseUrl, + }, + dynamicPlugins: {}, + }, + }); +} + +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { BACKSTAGE_RUNTIME_SHARED_DEPENDENCIES_GLOBAL } from '../../module-federation-common/src/types'; + +const globalSpy = jest.fn(); +Object.defineProperty(global, BACKSTAGE_RUNTIME_SHARED_DEPENDENCIES_GLOBAL, { + get: globalSpy, +}); + describe('dynamicFrontendFeaturesLoader', () => { const server = setupServer(); registerMswTestHooks(server); @@ -46,31 +69,27 @@ describe('dynamicFrontendFeaturesLoader', () => { }, }; - const getCommonOptions = (): DynamicFrontendFeaturesLoaderOptions => ({ - moduleFederation: { + const testModuleFederationPlugins: ModuleFederationRuntimePlugin[] = [ + { // We add this module federation plugin to mock the // effective retrieval of the remote content, since it // normally requires a host application built with module federation support, // and won't work by default in Jest tests. - plugins: [ - { - name: 'load-entry-mock', - loadEntry: async args => { - return { - get: (id: string) => async () => { - return await mocks.federation.get({ - name: args.remoteInfo.name, - id, - }); - }, - init: async () => {}, - } as RemoteEntryExports; + name: 'load-entry-mock', + loadEntry: async args => { + return { + get: (id: string) => async () => { + return await mocks.federation.get({ + name: args.remoteInfo.name, + id, + }); }, - onLoad: mocks.federation.onLoad, - }, - ], + init: async () => {}, + } as RemoteEntryExports; + }, + onLoad: mocks.federation.onLoad, }, - }); + ]; const manifestDummyData = { metaData: { @@ -104,9 +123,68 @@ describe('dynamicFrontendFeaturesLoader', () => { mocks.console.debug.mockReset(); mocks.federation.get.mockReset(); mocks.federation.onLoad.mockReset(); + globalSpy.mockReset(); resetFederationGlobalInfo(); }); + const mockedDefaultSharedItems = [ + { + name: 'react', + version: '18.3.1', + lib: async () => ({ default: {} }), + shareConfig: { singleton: true, requiredVersion: '*', eager: true }, + }, + { + name: 'react-dom', + version: '18.3.1', + lib: async () => ({ default: {} }), + shareConfig: { singleton: true, requiredVersion: '*', eager: true }, + }, + { + name: 'react-router', + version: '6.28.2', + lib: async () => ({ default: {} }), + shareConfig: { singleton: true, requiredVersion: '*', eager: true }, + }, + { + name: 'react-router-dom', + version: '6.28.2', + lib: async () => ({ default: {} }), + shareConfig: { singleton: true, requiredVersion: '*', eager: true }, + }, + { + name: '@material-ui/core/styles', + version: '4.12.4', + lib: async () => ({ default: {} }), + shareConfig: { singleton: true, requiredVersion: '*', eager: true }, + }, + { + name: '@material-ui/styles', + version: '4.11.5', + lib: async () => ({ default: {} }), + shareConfig: { singleton: true, requiredVersion: '*', eager: true }, + }, + { + name: '@mui/material/styles', + version: '5.16.14', + lib: async () => ({ default: {} }), + shareConfig: { singleton: true, requiredVersion: '*', eager: true }, + }, + { + name: '@emotion/react', + version: '11.13.5', + lib: async () => ({ default: {} }), + shareConfig: { singleton: true, requiredVersion: '*', eager: true }, + }, + ]; + + beforeEach(() => { + globalSpy.mockReturnValue({ + items: mockedDefaultSharedItems, + version: 'v1', + }); + }); + it('should return immediately if dynamic plugins are not enabled in config', async () => { let manifestsEndpointCalled = false; server.use( @@ -121,7 +199,9 @@ describe('dynamicFrontendFeaturesLoader', () => { const features = await ( dynamicFrontendFeaturesLoader({ - ...getCommonOptions(), + moduleFederation: { + plugins: testModuleFederationPlugins, + }, }) as InternalFrontendFeatureLoader ).loader({ config: mockApis.config({ @@ -196,22 +276,12 @@ describe('dynamicFrontendFeaturesLoader', () => { const features = await ( dynamicFrontendFeaturesLoader({ - ...getCommonOptions(), + moduleFederation: { + plugins: testModuleFederationPlugins, + }, }) as InternalFrontendFeatureLoader ).loader({ - config: mockApis.config({ - data: { - app: { - packages: { - include: [], - }, - }, - backend: { - baseUrl, - }, - dynamicPlugins: {}, - }, - }), + config: mockDefaultConfig(), }); const errorCalls = mocks.console.error.mock.calls.flatMap(e => e[0]); @@ -320,22 +390,12 @@ describe('dynamicFrontendFeaturesLoader', () => { const features = await ( dynamicFrontendFeaturesLoader({ - ...getCommonOptions(), + moduleFederation: { + plugins: testModuleFederationPlugins, + }, }) as InternalFrontendFeatureLoader ).loader({ - config: mockApis.config({ - data: { - app: { - packages: { - include: [], - }, - }, - backend: { - baseUrl, - }, - dynamicPlugins: {}, - }, - }), + config: mockDefaultConfig(), }); const errorCalls = mocks.console.error.mock.calls.flatMap(e => e[0]); @@ -435,22 +495,12 @@ describe('dynamicFrontendFeaturesLoader', () => { const features = await ( dynamicFrontendFeaturesLoader({ - ...getCommonOptions(), + moduleFederation: { + plugins: testModuleFederationPlugins, + }, }) as InternalFrontendFeatureLoader ).loader({ - config: mockApis.config({ - data: { - app: { - packages: { - include: [], - }, - }, - backend: { - baseUrl, - }, - dynamicPlugins: {}, - }, - }), + config: mockDefaultConfig(), }); const errorCalls = mocks.console.error.mock.calls.flatMap(e => e[0]); @@ -526,22 +576,12 @@ describe('dynamicFrontendFeaturesLoader', () => { const features = await ( dynamicFrontendFeaturesLoader({ - ...getCommonOptions(), + moduleFederation: { + plugins: testModuleFederationPlugins, + }, }) as InternalFrontendFeatureLoader ).loader({ - config: mockApis.config({ - data: { - app: { - packages: { - include: [], - }, - }, - backend: { - baseUrl, - }, - dynamicPlugins: {}, - }, - }), + config: mockDefaultConfig(), }); const errorCalls = mocks.console.error.mock.calls.flatMap(e => e[0]); @@ -588,22 +628,12 @@ describe('dynamicFrontendFeaturesLoader', () => { const features = await ( dynamicFrontendFeaturesLoader({ - ...getCommonOptions(), + moduleFederation: { + plugins: testModuleFederationPlugins, + }, }) as InternalFrontendFeatureLoader ).loader({ - config: mockApis.config({ - data: { - app: { - packages: { - include: [], - }, - }, - backend: { - baseUrl, - }, - dynamicPlugins: {}, - }, - }), + config: mockDefaultConfig(), }); const errorCalls = mocks.console.error.mock.calls.flatMap(e => e[0]); @@ -635,27 +665,17 @@ describe('dynamicFrontendFeaturesLoader', () => { const features = await ( dynamicFrontendFeaturesLoader({ - ...getCommonOptions(), + moduleFederation: { + plugins: testModuleFederationPlugins, + }, }) as InternalFrontendFeatureLoader ).loader({ - config: mockApis.config({ - data: { - app: { - packages: { - include: [], - }, - }, - backend: { - baseUrl, - }, - dynamicPlugins: {}, - }, - }), + config: mockDefaultConfig(), }); const errorCalls = mocks.console.error.mock.calls.flatMap(e => e[0]); expect(errorCalls).toEqual([ - `Failed fetching module federation configuration of dynamic frontend plugins: FetchError: invalid json response body at http://localhost:7007/.backstage/dynamic-features/remotes reason: Unexpected end of JSON input`, + `Failed fetching module federation configuration of dynamic frontend plugins: SyntaxError: Unexpected end of JSON input`, ]); const warnCalls = mocks.console.warn.mock.calls.flatMap(e => e[0]); expect(warnCalls).toEqual([]); @@ -739,22 +759,12 @@ describe('dynamicFrontendFeaturesLoader', () => { const features = await ( dynamicFrontendFeaturesLoader({ - ...getCommonOptions(), + moduleFederation: { + plugins: testModuleFederationPlugins, + }, }) as InternalFrontendFeatureLoader ).loader({ - config: mockApis.config({ - data: { - app: { - packages: { - include: [], - }, - }, - backend: { - baseUrl, - }, - dynamicPlugins: {}, - }, - }), + config: mockDefaultConfig(), }); const errorCalls = mocks.console.error.mock.calls.flatMap(e => e[0]); @@ -867,22 +877,12 @@ describe('dynamicFrontendFeaturesLoader', () => { const features = await ( dynamicFrontendFeaturesLoader({ - ...getCommonOptions(), + moduleFederation: { + plugins: testModuleFederationPlugins, + }, }) as InternalFrontendFeatureLoader ).loader({ - config: mockApis.config({ - data: { - app: { - packages: { - include: [], - }, - }, - backend: { - baseUrl, - }, - dynamicPlugins: {}, - }, - }), + config: mockDefaultConfig(), }); const errorCalls = mocks.console.error.mock.calls.flatMap(e => e[0]); @@ -978,30 +978,21 @@ describe('dynamicFrontendFeaturesLoader', () => { const features = await ( dynamicFrontendFeaturesLoader({ - ...getCommonOptions(), + moduleFederation: { + plugins: testModuleFederationPlugins, + }, }) as InternalFrontendFeatureLoader ).loader({ - config: mockApis.config({ - data: { - app: { - packages: { - include: [], - }, - }, - backend: { - baseUrl, - }, - dynamicPlugins: {}, - }, - }), + config: mockDefaultConfig(), }); const errorCalls = mocks.console.error.mock.calls.flatMap(e => e[0]); - expect(errorCalls).toEqual([ - "Failed loading remote module 'plugin_1' of dynamic plugin 'plugin-1': Error: [ Federation Runtime ]: [ Federation Runtime ]: [ Federation Runtime ]: http://localhost:7007/.backstage/dynamic-features/remotes/plugin-1/mf-manifest.json is not a federation manifest", - ]); const warnCalls = mocks.console.warn.mock.calls.flatMap(e => e[0]); - expect(warnCalls).toEqual([]); + + expect(warnCalls).toEqual(['[ Federation Runtime ]']); + expect(errorCalls).toEqual([ + `Failed loading remote module 'plugin_1' of dynamic plugin 'plugin-1': Error: [ Federation Runtime ]: "http://localhost:7007/.backstage/dynamic-features/remotes/plugin-1/mf-manifest.json" is not a valid federation manifest for remote "plugin_1". Missing required fields: metaData, exposes, shared.`, + ]); expect(features).toMatchObject([ { $$type: '@backstage/FrontendPlugin', @@ -1086,30 +1077,20 @@ describe('dynamicFrontendFeaturesLoader', () => { const features = await ( dynamicFrontendFeaturesLoader({ - ...getCommonOptions(), + moduleFederation: { + plugins: testModuleFederationPlugins, + }, }) as InternalFrontendFeatureLoader ).loader({ - config: mockApis.config({ - data: { - app: { - packages: { - include: [], - }, - }, - backend: { - baseUrl, - }, - dynamicPlugins: {}, - }, - }), + config: mockDefaultConfig(), }); const errorCalls = mocks.console.error.mock.calls.flatMap(e => e[0]); - expect(errorCalls).toEqual([ - "Failed loading remote module 'plugin_1' of dynamic plugin 'plugin-1': Error: [ Federation Runtime ]: [ Federation Runtime ]: [ Federation Runtime ]: http://localhost:7007/.backstage/dynamic-features/remotes/plugin-1/mf-manifest.json is not a federation manifest", - ]); const warnCalls = mocks.console.warn.mock.calls.flatMap(e => e[0]); - expect(warnCalls).toEqual([]); + expect(warnCalls).toEqual(['[ Federation Runtime ]']); + expect(errorCalls).toEqual([ + `Failed loading remote module 'plugin_1' of dynamic plugin 'plugin-1': Error: [ Federation Runtime ]: "http://localhost:7007/.backstage/dynamic-features/remotes/plugin-1/mf-manifest.json" is not a valid federation manifest for remote "plugin_1". Missing required fields: metaData, exposes, shared.`, + ]); expect(features).toMatchObject([ { $$type: '@backstage/FrontendPlugin', @@ -1133,4 +1114,185 @@ describe('dynamicFrontendFeaturesLoader', () => { }, ]); }); + + it('should initialize module federation with resolved shared dependencies', async () => { + server.use( + rest.get( + `${baseUrl}/.backstage/dynamic-features/remotes`, + (_, res, ctx) => res(ctx.json([])), + ), + ); + + const spyInit = jest.fn(); + await ( + dynamicFrontendFeaturesLoader({ + moduleFederation: { + plugins: [ + { + name: 'spy-init', + init: args => { + spyInit(args); + return args; + }, + }, + ...testModuleFederationPlugins, + ], + }, + }) as InternalFrontendFeatureLoader + ).loader({ + config: mockDefaultConfig(), + }); + + expect(spyInit).toHaveBeenCalledTimes(1); + expect(spyInit.mock.calls[0][0].options).toMatchObject({ + shared: Object.fromEntries( + mockedDefaultSharedItems.map(item => [ + item.name, + [ + { + version: item.version, + shareConfig: { + singleton: item.shareConfig.singleton, + requiredVersion: item.shareConfig.requiredVersion, + eager: item.shareConfig.eager, + }, + strategy: 'version-first', + }, + ], + ]), + ), + }); + }); + + it('should initialize module federation with shareStrategy option', async () => { + server.use( + rest.get( + `${baseUrl}/.backstage/dynamic-features/remotes`, + (_, res, ctx) => res(ctx.json([])), + ), + ); + + const spyInit = jest.fn(); + await ( + dynamicFrontendFeaturesLoader({ + moduleFederation: { + shareStrategy: 'loaded-first', + plugins: [ + { + name: 'spy-init', + init: args => { + spyInit(args); + return args; + }, + }, + ...testModuleFederationPlugins, + ], + }, + }) as InternalFrontendFeatureLoader + ).loader({ + config: mockDefaultConfig(), + }); + + const errorCalls = mocks.console.error.mock.calls.flatMap(e => e[0]); + expect(errorCalls).toEqual([]); + const warnCalls = mocks.console.warn.mock.calls.flatMap(e => e[0]); + expect(warnCalls).toEqual([]); + const infoCalls = mocks.console.info.mock.calls.flatMap(e => e[0]); + expect(infoCalls).toEqual([]); + const debugCalls = mocks.console.debug.mock.calls.flatMap(e => e[0]); + expect(debugCalls).toEqual([]); + expect(spyInit).toHaveBeenCalledTimes(1); + expect(spyInit.mock.calls[0][0].options).toMatchObject({ + shareStrategy: 'loaded-first', + shared: { + react: [ + { + version: '18.3.1', + shareConfig: { + singleton: true, + requiredVersion: '*', + }, + strategy: 'loaded-first', + }, + ], + }, + }); + }); + + it('should return an empty list of features if module federation initialization fails', async () => { + server.use( + rest.get( + `${baseUrl}/.backstage/dynamic-features/remotes`, + (_, res, ctx) => + res( + ctx.json([ + { + packageName: 'plugin-test-dynamic', + exposedModules: ['.'], + remoteInfo: { + name: 'test_plugin', + entry: `${baseUrl}/.backstage/dynamic-features/remotes/plugin-test-dynamic/mf-manifest.json`, + }, + }, + ]), + ), + ), + rest.get( + `${baseUrl}/.backstage/dynamic-features/remotes/plugin-test-dynamic/mf-manifest.json`, + (_, res, ctx) => + res( + ctx.json({ + name: 'test_plugin', + ...manifestDummyData, + exposes: [ + { + id: 'test_plugin:.', + name: '.', + path: '.', + ...manifestExposedRemoteDummyData, + }, + ], + }), + ), + ), + ); + + mocks.federation.get.mockReturnValue({ + default: createFrontendPlugin({ + pluginId: 'test-plugin', + extensions: [], + }), + }); + + const features = await ( + dynamicFrontendFeaturesLoader({ + moduleFederation: { + plugins: [ + ...testModuleFederationPlugins, + { + name: 'fail-init', + init: () => { + throw new Error('An initialization error'); + }, + }, + ], + }, + }) as InternalFrontendFeatureLoader + ).loader({ + config: mockDefaultConfig(), + }); + + const errorCalls = mocks.console.error.mock.calls.flatMap(e => e[0]); + expect(errorCalls).toEqual([ + 'Failed initializing module federation: Error: An initialization error', + ]); + const warnCalls = mocks.console.warn.mock.calls.flatMap(e => e[0]); + expect(warnCalls).toEqual([]); + const infoCalls = mocks.console.info.mock.calls.flatMap(e => e[0]); + expect(infoCalls).toEqual([]); + const debugCalls = mocks.console.debug.mock.calls.flatMap(e => e[0]); + expect(debugCalls).toEqual([]); + expect(mocks.federation.get.mock.calls.flatMap(e => e[0])).toEqual([]); + expect(features).toEqual([]); + }); }); diff --git a/packages/frontend-dynamic-feature-loader/src/loader.ts b/packages/frontend-dynamic-feature-loader/src/loader.ts index 9e2585d1fa..9542977203 100644 --- a/packages/frontend-dynamic-feature-loader/src/loader.ts +++ b/packages/frontend-dynamic-feature-loader/src/loader.ts @@ -15,9 +15,9 @@ */ import { - FederationRuntimePlugin, - init, - loadRemote, + ModuleFederationRuntimePlugin, + createInstance, + ModuleFederation, } from '@module-federation/enhanced/runtime'; import { Module } from '@module-federation/sdk'; import { DefaultApiClient, Remote } from './schema/openapi'; @@ -27,6 +27,7 @@ import { createFrontendFeatureLoader, } from '@backstage/frontend-plugin-api'; import { ShareStrategy, UserOptions } from '@module-federation/runtime/types'; +import { loadModuleFederationHostShared } from '@backstage/module-federation-common'; /** * @@ -39,7 +40,8 @@ export type DynamicFrontendFeaturesLoaderOptions = { moduleFederation: { shared?: UserOptions['shared']; shareStrategy?: ShareStrategy; - plugins?: Array; + plugins?: Array; + instance?: ModuleFederation; }; }; @@ -98,18 +100,49 @@ export function dynamicFrontendFeaturesLoader( return []; } + let instance: ModuleFederation; try { - init({ - ...options?.moduleFederation, - name: appPackageName - .replaceAll('@', '') - .replaceAll('/', '__') - .replaceAll('-', '_'), - remotes: frontendPluginRemotes.map(remote => ({ - alias: remote.packageName, - ...remote.remoteInfo, - })), - }); + if (options?.moduleFederation?.instance) { + instance = options.moduleFederation.instance; + } else { + const shared = await loadModuleFederationHostShared({ + onError: err => error(err.message, err.cause), + }); + + const createOptions: UserOptions = { + name: appPackageName + .replaceAll('@', '') + .replaceAll('/', '__') + .replaceAll('-', '_'), + shared, + remotes: [], + }; + if (options?.moduleFederation?.shareStrategy) { + createOptions.shareStrategy = + options.moduleFederation.shareStrategy; + } + instance = createInstance(createOptions); + } + + const userOptions: UserOptions = { + name: instance.name, + remotes: [], + }; + + if (options?.moduleFederation?.plugins) { + userOptions.plugins = options.moduleFederation.plugins; + } + if (options?.moduleFederation?.shareStrategy) { + userOptions.shareStrategy = options.moduleFederation.shareStrategy; + } + if (options?.moduleFederation?.shared) { + userOptions.shared = options.moduleFederation.shared; + } + userOptions.remotes = frontendPluginRemotes.map(remote => ({ + alias: remote.packageName, + ...remote.remoteInfo, + })); + instance.initOptions(userOptions); } catch (err) { error(`Failed initializing module federation`, err); return []; @@ -131,7 +164,7 @@ export function dynamicFrontendFeaturesLoader( : `${remote.remoteInfo.name}/${exposedModuleName}`; let module: Module; try { - module = await loadRemote(remoteModuleName); + module = await instance.loadRemote(remoteModuleName); } catch (err) { error( `Failed loading remote module '${remoteModuleName}' of dynamic plugin '${remote.packageName}'`, diff --git a/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/apis/Api.client.ts b/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/apis/Api.client.ts index 8623dc3c33..9802afba0f 100644 --- a/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/apis/Api.client.ts +++ b/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/apis/Api.client.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ // ****************************************************************** // * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * // ****************************************************************** + import { DiscoveryApi } from '../types/discovery'; import { FetchApi } from '../types/fetch'; import crossFetch from 'cross-fetch'; diff --git a/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/apis/index.ts b/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/apis/index.ts index fc7c83b736..c2e931caf8 100644 --- a/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/apis/index.ts +++ b/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/apis/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/index.ts b/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/index.ts index dc3055033d..19501a5dcb 100644 --- a/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/index.ts +++ b/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/models/ErrorError.model.ts b/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/models/ErrorError.model.ts index e0265e95d7..853f88cc14 100644 --- a/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/models/ErrorError.model.ts +++ b/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/models/ErrorError.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/models/ErrorRequest.model.ts b/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/models/ErrorRequest.model.ts index 3eb5e15740..1591911bc9 100644 --- a/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/models/ErrorRequest.model.ts +++ b/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/models/ErrorRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/models/ErrorResponse.model.ts b/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/models/ErrorResponse.model.ts index edbcc32df7..1eff0557ed 100644 --- a/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/models/ErrorResponse.model.ts +++ b/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/models/ErrorResponse.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/models/ModelError.model.ts b/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/models/ModelError.model.ts index 958fde7d0b..9d5c36325e 100644 --- a/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/models/ModelError.model.ts +++ b/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/models/ModelError.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,6 @@ import { ErrorResponse } from '../models/ErrorResponse.model'; */ export interface ModelError { [key: string]: any; - error: ErrorError; request?: ErrorRequest; response: ErrorResponse; diff --git a/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/models/Remote.model.ts b/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/models/Remote.model.ts index 7440110ad8..ff5cae705f 100644 --- a/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/models/Remote.model.ts +++ b/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/models/Remote.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/models/RemoteInfo.model.ts b/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/models/RemoteInfo.model.ts index 72edba90d4..6e8bdeff0a 100644 --- a/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/models/RemoteInfo.model.ts +++ b/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/models/RemoteInfo.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/models/index.ts b/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/models/index.ts index 0c54a6585b..4cf296a0b9 100644 --- a/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/models/index.ts +++ b/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/models/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/pluginId.ts b/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/pluginId.ts index eaea330f99..465ea2d806 100644 --- a/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/pluginId.ts +++ b/packages/frontend-dynamic-feature-loader/src/schema/openapi/generated/pluginId.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/frontend-dynamic-feature-loader/src/schema/openapi/index.ts b/packages/frontend-dynamic-feature-loader/src/schema/openapi/index.ts index 196aad553a..fb49a2b9c0 100644 --- a/packages/frontend-dynamic-feature-loader/src/schema/openapi/index.ts +++ b/packages/frontend-dynamic-feature-loader/src/schema/openapi/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/frontend-internal/CHANGELOG.md b/packages/frontend-internal/CHANGELOG.md index 7b31d280cb..a75ff1a6d6 100644 --- a/packages/frontend-internal/CHANGELOG.md +++ b/packages/frontend-internal/CHANGELOG.md @@ -1,5 +1,110 @@ # @internal/frontend +## 0.0.19 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/filter-predicates@0.1.2 + +## 0.0.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/filter-predicates@0.1.2-next.0 + - @backstage/frontend-plugin-api@0.16.0-next.2 + +## 0.0.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + +## 0.0.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.2-next.0 + - @backstage/filter-predicates@0.1.1 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + +## 0.0.18 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/filter-predicates@0.1.1 + +## 0.0.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + +## 0.0.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + +## 0.0.17 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/version-bridge@1.0.12 + +## 0.0.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/version-bridge@1.0.12-next.0 + +## 0.0.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## 0.0.16 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + +## 0.0.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## 0.0.15 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + ## 0.0.15-next.0 ### Patch Changes diff --git a/packages/frontend-internal/knip-report.md b/packages/frontend-internal/knip-report.md index e23233d94e..af80adf53e 100644 --- a/packages/frontend-internal/knip-report.md +++ b/packages/frontend-internal/knip-report.md @@ -1,19 +1,18 @@ # Knip report -## Unused dependencies (2) +## Unused dependencies (1) -| Name | Location | Severity | -| :------------------------ | :----------- | :------- | -| @backstage/version-bridge | packages/frontend-internal/package.json | error | -| zod | packages/frontend-internal/package.json | error | +| Name | Location | Severity | +| :------------------------ | :---------------- | :------- | +| @backstage/version-bridge | package.json:29:6 | error | ## Unused devDependencies (5) -| Name | Location | Severity | -| :----------------------------- | :----------- | :------- | -| @backstage/frontend-test-utils | packages/frontend-internal/package.json | error | -| @backstage/frontend-app-api | packages/frontend-internal/package.json | error | -| @testing-library/jest-dom | packages/frontend-internal/package.json | error | -| @testing-library/react | packages/frontend-internal/package.json | error | -| @backstage/test-utils | packages/frontend-internal/package.json | error | +| Name | Location | Severity | +| :----------------------------- | :---------------- | :------- | +| @backstage/frontend-test-utils | package.json:34:6 | error | +| @backstage/frontend-app-api | package.json:33:6 | error | +| @testing-library/jest-dom | package.json:36:6 | error | +| @testing-library/react | package.json:37:6 | error | +| @backstage/test-utils | package.json:35:6 | error | diff --git a/packages/frontend-internal/package.json b/packages/frontend-internal/package.json index 37727cda97..2e03a7e15a 100644 --- a/packages/frontend-internal/package.json +++ b/packages/frontend-internal/package.json @@ -1,6 +1,6 @@ { "name": "@internal/frontend", - "version": "0.0.15-next.0", + "version": "0.0.19", "backstage": { "role": "web-library", "inline": true @@ -23,10 +23,10 @@ "test": "backstage-cli package test" }, "dependencies": { + "@backstage/filter-predicates": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", "@backstage/types": "workspace:^", - "@backstage/version-bridge": "workspace:^", - "zod": "^3.22.4" + "@backstage/version-bridge": "workspace:^" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/packages/frontend-internal/src/apis/OpaqueApiRef.ts b/packages/frontend-internal/src/apis/OpaqueApiRef.ts new file mode 100644 index 0000000000..8a1b058641 --- /dev/null +++ b/packages/frontend-internal/src/apis/OpaqueApiRef.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ApiRef } from '@backstage/frontend-plugin-api'; +import { OpaqueType } from '@internal/opaque'; + +export const OpaqueApiRef = OpaqueType.create<{ + public: ApiRef & { + readonly $$type: '@backstage/ApiRef'; + }; + versions: { + readonly version: 'v1'; + readonly pluginId?: string; + }; +}>({ + type: '@backstage/ApiRef', + versions: ['v1'], +}); diff --git a/packages/frontend-internal/src/apis/index.ts b/packages/frontend-internal/src/apis/index.ts new file mode 100644 index 0000000000..f445683652 --- /dev/null +++ b/packages/frontend-internal/src/apis/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './OpaqueApiRef'; diff --git a/packages/frontend-internal/src/index.ts b/packages/frontend-internal/src/index.ts index a5728f2ff6..447b488fca 100644 --- a/packages/frontend-internal/src/index.ts +++ b/packages/frontend-internal/src/index.ts @@ -14,4 +14,6 @@ * limitations under the License. */ +export * from './routing'; +export * from './apis'; export * from './wiring'; diff --git a/packages/frontend-internal/src/routing/OpaqueExternalRouteRef.ts b/packages/frontend-internal/src/routing/OpaqueExternalRouteRef.ts new file mode 100644 index 0000000000..9e70dc496a --- /dev/null +++ b/packages/frontend-internal/src/routing/OpaqueExternalRouteRef.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; +import { OpaqueType } from '@internal/opaque'; + +export const OpaqueExternalRouteRef = OpaqueType.create<{ + public: ExternalRouteRef; + versions: { + readonly version: 'v1'; + + getParams(): string[]; + getDescription(): string; + getDefaultTarget(): string | undefined; + + setId(id: string): void; + }; +}>({ + type: '@backstage/ExternalRouteRef', + versions: ['v1'], +}); diff --git a/packages/frontend-internal/src/routing/OpaqueRouteRef.ts b/packages/frontend-internal/src/routing/OpaqueRouteRef.ts new file mode 100644 index 0000000000..83126de8e2 --- /dev/null +++ b/packages/frontend-internal/src/routing/OpaqueRouteRef.ts @@ -0,0 +1,35 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { RouteRef } from '@backstage/frontend-plugin-api'; +import { OpaqueType } from '@internal/opaque'; + +export const OpaqueRouteRef = OpaqueType.create<{ + public: RouteRef; + versions: { + readonly version: 'v1'; + + getParams(): string[]; + getDescription(): string; + + alias: string | undefined; + + setId(id: string): void; + }; +}>({ + type: '@backstage/RouteRef', + versions: ['v1'], +}); diff --git a/packages/frontend-internal/src/routing/OpaqueSubRouteRef.ts b/packages/frontend-internal/src/routing/OpaqueSubRouteRef.ts new file mode 100644 index 0000000000..a8559f76a2 --- /dev/null +++ b/packages/frontend-internal/src/routing/OpaqueSubRouteRef.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 { RouteRef, SubRouteRef } from '@backstage/frontend-plugin-api'; +import { OpaqueType } from '@internal/opaque'; + +export const OpaqueSubRouteRef = OpaqueType.create<{ + public: SubRouteRef; + versions: { + readonly version: 'v1'; + + getParams(): string[]; + getParent(): RouteRef; + getDescription(): string; + }; +}>({ + type: '@backstage/SubRouteRef', + versions: ['v1'], +}); diff --git a/packages/frontend-internal/src/routing/index.ts b/packages/frontend-internal/src/routing/index.ts new file mode 100644 index 0000000000..cc8c0ee5b8 --- /dev/null +++ b/packages/frontend-internal/src/routing/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 { OpaqueRouteRef } from './OpaqueRouteRef'; +export { OpaqueSubRouteRef } from './OpaqueSubRouteRef'; +export { OpaqueExternalRouteRef } from './OpaqueExternalRouteRef'; diff --git a/packages/frontend-internal/src/wiring/InternalExtensionDefinition.ts b/packages/frontend-internal/src/wiring/InternalExtensionDefinition.ts index b7c3674f9a..dcadccb305 100644 --- a/packages/frontend-internal/src/wiring/InternalExtensionDefinition.ts +++ b/packages/frontend-internal/src/wiring/InternalExtensionDefinition.ts @@ -24,9 +24,11 @@ import { ExtensionDefinitionParameters, ExtensionInput, PortableSchema, - ResolvedExtensionInputs, } from '@backstage/frontend-plugin-api'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { ResolvedExtensionInputs } from '../../../frontend-plugin-api/src/wiring/createExtension'; import { OpaqueType } from '@internal/opaque'; +import { FilterPredicate } from '@backstage/filter-predicates'; export const OpaqueExtensionDefinition = OpaqueType.create<{ public: OverridableExtensionDefinition; @@ -69,6 +71,7 @@ export const OpaqueExtensionDefinition = OpaqueType.create<{ readonly name?: string; readonly attachTo: ExtensionDefinitionAttachTo; readonly disabled: boolean; + readonly if?: FilterPredicate; readonly configSchema?: PortableSchema; readonly inputs: { [inputName in string]: ExtensionInput }; readonly output: Array; diff --git a/packages/frontend-internal/src/wiring/InternalExtensionInput.ts b/packages/frontend-internal/src/wiring/InternalExtensionInput.ts index b816890f1a..9090023db9 100644 --- a/packages/frontend-internal/src/wiring/InternalExtensionInput.ts +++ b/packages/frontend-internal/src/wiring/InternalExtensionInput.ts @@ -28,7 +28,7 @@ export const OpaqueExtensionInput = OpaqueType.create<{ versions: { readonly version: undefined; readonly context?: ExtensionInputContext; - withContext(context: ExtensionInputContext): ExtensionInput; + withContext?(context: ExtensionInputContext): ExtensionInput; }; }>({ type: '@backstage/ExtensionInput', diff --git a/packages/frontend-internal/src/wiring/InternalFrontendPlugin.ts b/packages/frontend-internal/src/wiring/InternalFrontendPlugin.ts index 3e964af5ca..31af631a59 100644 --- a/packages/frontend-internal/src/wiring/InternalFrontendPlugin.ts +++ b/packages/frontend-internal/src/wiring/InternalFrontendPlugin.ts @@ -17,8 +17,10 @@ import { Extension, FeatureFlagConfig, + IconElement, OverridableFrontendPlugin, } from '@backstage/frontend-plugin-api'; +import { FilterPredicate } from '@backstage/filter-predicates'; import { JsonObject } from '@backstage/types'; import { OpaqueType } from '@internal/opaque'; @@ -26,8 +28,11 @@ export const OpaqueFrontendPlugin = OpaqueType.create<{ public: OverridableFrontendPlugin; versions: { readonly version: 'v1'; + readonly title?: string; + readonly icon?: IconElement; readonly extensions: Extension[]; readonly featureFlags: FeatureFlagConfig[]; + readonly if?: FilterPredicate; readonly infoOptions?: { packageJson?: () => Promise; manifest?: () => Promise; diff --git a/packages/frontend-plugin-api/CHANGELOG.md b/packages/frontend-plugin-api/CHANGELOG.md index 9c97055ceb..27f044e266 100644 --- a/packages/frontend-plugin-api/CHANGELOG.md +++ b/packages/frontend-plugin-api/CHANGELOG.md @@ -1,5 +1,382 @@ # @backstage/frontend-plugin-api +## 0.16.0 + +### Minor Changes + +- fa55078: **BREAKING**: Removed the deprecated `createSchemaFromZod` helper. Use the new `configSchema` option instead. See the [1.50 migration documentation](https://backstage.io/docs/frontend-system/architecture/migrations#150) for more information. +- 49397c1: Simplified the type signature of `createRouteRef` by replacing the dual `TParams`/`TParamKeys` type parameters with a single `TParamKey` parameter. This is a breaking change for callers that explicitly provided type arguments, but most usage that relies on inference is unaffected. + +### Patch Changes + +- 4c09967: Fixed the duplicate extension error message in `createFrontendModule` to correctly say "Module" instead of "Plugin". +- e4804ab: Added `open` method to `DialogApi` that renders dialogs without any built-in dialog chrome, giving the caller full control over the dialog presentation. This avoids focus trap conflicts that occur when mixing components from different design libraries. The existing `show` and `showModal` methods are now deprecated in favor of `open`. +- ddc5247: Fixed `FlattenedMessages` type to avoid excessive type instantiation depth in TypeScript 6 when using `createTranslationRef` with the `translations` option. +- a2a6c3b: Added a new `configSchema` option for `createExtension` and `createExtensionBlueprint` that accepts direct schema values from any [Standard Schema](https://github.com/standard-schema/standard-schema) compatible library with JSON Schema support, such as zod v4 (`zod@^4.0.0`). The old `config.schema` option is now deprecated. Note that Zod v3 is not supported by the new `configSchema` option, including the `zod/v4` subpath export which does not include JSON Schema conversion support. You must upgrade to the `zod` v4 package. See the [1.50 migration documentation](https://backstage.io/docs/frontend-system/architecture/migrations#150) for more information. +- d66a3ec: Added `titleLink` prop to `PageLayoutProps` so the plugin header title can link back to the plugin root. +- e220589: Removed the unnecessary need to use `defineParams` callback from `PluginHeaderActionBlueprint`. It still works, but is no longer required. +- Updated dependencies + - @backstage/errors@1.3.0 + - @backstage/filter-predicates@0.1.2 + +## 0.16.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/filter-predicates@0.1.2-next.0 + +## 0.16.0-next.1 + +### Minor Changes + +- 49397c1: Simplified the type signature of `createRouteRef` by replacing the dual `TParams`/`TParamKeys` type parameters with a single `TParamKey` parameter. This is a breaking change for callers that explicitly provided type arguments, but most usage that relies on inference is unaffected. + +### Patch Changes + +- ddc5247: Fixed `FlattenedMessages` type to avoid excessive type instantiation depth in TypeScript 6 when using `createTranslationRef` with the `translations` option. +- fa55078: Refactored the internal `createSchemaFromZod` helper to use a schema-first generic pattern, replacing the `ZodSchema` constraint with `TSchema extends ZodType`. This avoids "excessively deep" type inference errors when multiple Zod copies are resolved. + +## 0.15.2-next.0 + +### Patch Changes + +- d66a3ec: Added `titleLink` prop to `PageLayoutProps` so the plugin header title can link back to the plugin root. +- e220589: Removed the unnecessary need to use `defineParams` callback from `PluginHeaderActionBlueprint`. It still works, but is no longer required. +- Updated dependencies + - @backstage/errors@1.2.7 + - @backstage/filter-predicates@0.1.1 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + +## 0.15.0 + +### Minor Changes + +- 5fd78ba: Renamed `PluginOptions` to `CreateFrontendPluginOptions` and deprecated the old name. Removed `ResolvedExtensionInputs` from the main entry point; it is still available as an inline type in extension factory signatures. +- 72991a5: Removed the `ResolvedExtensionInput` and `ExtensionDataRefToValue` helper types from the public API surface to reduce top-level API clutter. These types were internal plumbing that are not needed by plugin authors. If you were relying on `ResolvedExtensionInput`, use the `ResolvedExtensionInputs` type instead, which maps a full set of inputs. If you were using `ExtensionDataRefToValue`, replace it with `ExtensionDataValue` combined with inferred types from your `ExtensionDataRef`. +- 9508514: **BREAKING**: Promoted `PluginWrapperApi`, `pluginWrapperApiRef`, `PluginWrapperBlueprint`, and the new `PluginWrapperDefinition` type from `@alpha` to `@public`. These are now available from the main package entry point rather than only through `/alpha`. + + The `PluginWrapperApi` type now has a required `getRootWrapper()` method that returns a root wrapper component. The `pluginWrapperApiRef` ID changed from `core.plugin-wrapper.alpha` to `core.plugin-wrapper`. + + The `PluginWrapperBlueprint` now accepts `PluginWrapperDefinition` as the loader return type, which supports an optional `useWrapperValue` hook that allows sharing state between wrapper instances. + +- 6573901: **BREAKING**: Removed the deprecated `AnyExtensionDataRef` type. Use `ExtensionDataRef` without type parameters instead. +- a9440f0: **BREAKING**: Simplified the `ExtensionAttachTo` type to only support a single attachment target. The array form for attaching to multiple extension points has been removed. Also removed the deprecated `ExtensionAttachToSpec` type alias. + +### Patch Changes + +- e26e3de: The `icon` field on `AuthProviderInfo` now accepts `IconElement` in addition to `IconComponent`, letting you pass `` instead of `MyIcon`. +- eea95b8: Deprecated `AlertApi` in favor of the new `ToastApi`. + + `AlertApi` is now deprecated and will be removed in a future release. Please migrate to `ToastApi` which provides richer notification features. + + **Why migrate?** + + `ToastApi` offers enhanced capabilities over `AlertApi`: + + - **Title and Description**: Display a prominent title with optional description text + - **Action Links**: Include clickable links within notifications + - **Status Variants**: Support for neutral, info, success, warning, and danger statuses + - **Per-toast Timeout**: Control auto-dismiss timing for each notification individually + - **Programmatic Dismiss**: Close notifications via the `close()` handle returned from `post()` + + **Migration Guide** + + | AlertApi | ToastApi | + | -------------------------------------------- | ------------------------------------------ | + | `message: string` | `title: ReactNode` | + | `severity: 'error'` | `status: 'danger'` | + | `severity: 'success' \| 'info' \| 'warning'` | `status: 'success' \| 'info' \| 'warning'` | + | `display: 'transient'` | `timeout: 5000` (or custom ms) | + | `display: 'permanent'` | omit `timeout` | + | `post()` returns `void` | `post()` returns `{ close(): void }` | + + **Example Migration** + + ```typescript + // Before (AlertApi) + import { alertApiRef, useApi } from '@backstage/core-plugin-api'; + + const alertApi = useApi(alertApiRef); + alertApi.post({ + message: 'Entity saved successfully', + severity: 'success', + display: 'transient', + }); + + // After (ToastApi) + import { toastApiRef, useApi } from '@backstage/frontend-plugin-api'; + + const toastApi = useApi(toastApiRef); + const toast = toastApi.post({ + title: 'Entity saved successfully', + status: 'success', + timeout: 5000, + }); + // Later: toast.close() to dismiss programmatically + ``` + + **Note**: During the migration period, both APIs work simultaneously. The `ToastDisplay` component subscribes to both `AlertApi` and `ToastApi`, so existing code continues to work while you migrate incrementally. + +- 8a3a906: Deprecated `NavItemBlueprint`. Nav items are now automatically inferred from `PageBlueprint` extensions based on their `title` and `icon` params. +- b15a685: Deprecated `withApis`, use the `withApis` export from `@backstage/core-compat-api` instead. +- 0452d02: Add optional `description` field to plugin-level feature flags. +- 1bec049: Fixed inconsistent `JSX.Element` type reference in the `DialogApiDialog.update` method signature. +- 9c81af9: Made the `pluginId` property optional in the `FrontendFeature` type, allowing plugins published against older versions of the framework to be used without type errors. +- 2c383b5: Deprecated `AnalyticsImplementationBlueprint` and `AnalyticsImplementationFactory` in favor of the exports from `@backstage/plugin-app-react`. +- dab6c46: Deprecated the `ExtensionFactoryMiddleware` type, which has been moved to `@backstage/frontend-app-api`. +- aa29b50: Pages created with `PageBlueprint` now render the plugin header by default in the new frontend system. +- 3f36ce1: Clarified the `IconElement` sizing contract for the new frontend system and aligned legacy system icon rendering with the new icon API. +- cc459f7: Added a builder form for `createApiRef` in the new frontend system and deprecated the direct `createApiRef({ ... })` call in favor of `createApiRef().with({ ... })`. The builder form now also preserves literal API ref IDs in the resulting `ApiRef` type. + + The `createApiRef().with({ ... })` form can also use an explicit `pluginId` to declare API ownership without encoding the plugin ID into the API ref ID, while keeping that metadata internal to runtime handling. + +- 5b160f9: Added support for `if` predicates on `createFrontendPlugin` and `createFrontendModule`, applying shared conditions to every extension in the feature. Plugin and extension overrides can now also replace or remove existing `if` predicates. +- d0206c4: Removed the deprecated `defaultPath` migration helper from `PageBlueprint` params. +- edb872c: Renamed the `PageTab` type to `PageLayoutTab`. The old `PageTab` name is now a deprecated type alias. +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- 7e743f4: Introduced a new `ToastApi` for displaying rich toast notifications in the new frontend system. + + The new `ToastApi` provides enhanced notification capabilities compared to the existing `AlertApi`: + + - **Title and Description**: Toasts support both a title and an optional description + - **Custom Timeouts**: Each toast can specify its own timeout duration + - **Links**: Toasts can include action links + - **Status Variants**: Support for neutral, info, success, warning, and danger statuses + - **Programmatic Dismiss**: Toasts can be dismissed programmatically using the `close()` handle returned from `post()` + + **Usage:** + + ```typescript + import { toastApiRef, useApi } from '@backstage/frontend-plugin-api'; + + const toastApi = useApi(toastApiRef); + + // Full-featured toast + toastApi.post({ + title: 'Entity saved', + description: 'Your changes have been saved successfully.', + status: 'success', + timeout: 5000, + links: [{ label: 'View entity', href: '/catalog/entity' }], + }); + + // Programmatic dismiss + const { close } = toastApi.post({ title: 'Uploading...', status: 'info' }); + // Later... + close(); + ``` + + The `ToastDisplay` component subscribes to both `ToastApi` and `AlertApi`, providing a migration path where both systems work side by side until `AlertApi` is fully deprecated. + +- fe848e0: Changed `useApiHolder` to return an empty `ApiHolder` instead of throwing when used outside of an API context. +- Updated dependencies + - @backstage/filter-predicates@0.1.1 + +## 0.15.0-next.1 + +### Minor Changes + +- 6573901: **BREAKING**: Removed the deprecated `AnyExtensionDataRef` type. Use `ExtensionDataRef` without type parameters instead. +- a9440f0: **BREAKING**: Simplified the `ExtensionAttachTo` type to only support a single attachment target. The array form for attaching to multiple extension points has been removed. Also removed the deprecated `ExtensionAttachToSpec` type alias. + +### Patch Changes + +- 8a3a906: Deprecated `NavItemBlueprint`. Nav items are now automatically inferred from `PageBlueprint` extensions based on their `title` and `icon` params. +- b15a685: Deprecated `withApis`, use the `withApis` export from `@backstage/core-compat-api` instead. +- 0452d02: Add optional `description` field to plugin-level feature flags. +- 1bec049: Fixed inconsistent `JSX.Element` type reference in the `DialogApiDialog.update` method signature. +- 2c383b5: Deprecated `AnalyticsImplementationBlueprint` and `AnalyticsImplementationFactory` in favor of the exports from `@backstage/plugin-app-react`. +- dab6c46: Deprecated the `ExtensionFactoryMiddleware` type, which has been moved to `@backstage/frontend-app-api`. +- d0206c4: Removed the deprecated `defaultPath` migration helper from `PageBlueprint` params. +- edb872c: Renamed the `PageTab` type to `PageLayoutTab`. The old `PageTab` name is now a deprecated type alias. +- fe848e0: Changed `useApiHolder` to return an empty `ApiHolder` instead of throwing when used outside of an API context. + +## 0.14.2-next.0 + +### Patch Changes + +- 9c81af9: Made the `pluginId` property optional in the `FrontendFeature` type, allowing plugins published against older versions of the framework to be used without type errors. +- Updated dependencies + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + +## 0.14.0 + +### Minor Changes + +- ef6916e: Added `IconElement` type as a replacement for the deprecated `IconComponent`. The `IconsApi` now has a new `icon()` method that returns `IconElement`, while the existing `getIcon()` method is deprecated. The `IconBundleBlueprint` now accepts both `IconComponent` and `IconElement` values. +- bb9b471: Plugin IDs that do not match the standard format are deprecated (letters, digits, and dashes only, starting with a letter). Plugin IDs that do no match this format will be rejected in a future release. +- ef6916e: Added `SubPageBlueprint` for creating sub-page tabs, `PluginHeaderActionBlueprint` and `PluginHeaderActionsApi` for plugin-scoped header actions, and `PageLayout` as a swappable component. The `PageBlueprint` now supports sub-pages with tabbed navigation, page title, icon, and header actions. Plugins can now specify a `title` and `icon` in `createFrontendPlugin`. +- c38b74d: **BREAKING**: The following blueprints have been removed and are now only available from `@backstage/plugin-app-react`: + + - `IconBundleBlueprint` + - `NavContentBlueprint` + - `RouterBlueprint` + - `SignInPageBlueprint` + - `SwappableComponentBlueprint` + - `ThemeBlueprint` + - `TranslationBlueprint` + +- 10ebed4: **BREAKING**: Removed type support for multiple attachment points in the `ExtensionDefinitionAttachTo` type. Extensions can no longer specify an array of attachment points in the `attachTo` property. + + The runtime still supports multiple attachment points for backward compatibility with existing compiled code, but new code will receive type errors if attempting to use this pattern. + + Extensions that previously used multiple attachment points should migrate to using a Utility API pattern instead. See the [Sharing Extensions Across Multiple Locations](https://backstage.io/docs/frontend-system/architecture/27-sharing-extensions) guide for the recommended approach. + +### Patch Changes + +- 7edb810: Added a new `internal` option to `createExtensionInput` that marks the input as only allowing attachments from the same plugin. +- 9554c36: **DEPRECATED**: Multiple attachment points for extensions have been deprecated. The functionality continues to work for backward compatibility, but will log a deprecation warning and be removed in a future release. + + Extensions using array attachment points should migrate to using Utility APIs instead. See the [Sharing Extensions Across Multiple Locations](https://backstage.io/docs/frontend-system/architecture/27-sharing-extensions) guide for the recommended pattern. + +- 53b6549: Plugins in the new frontend system now have a `pluginId` field rather than `id` to better align with naming conventions used throughout the frontend and backend systems. The old field is still present but marked as deprecated. All internal code has been updated to prefer `pluginId` while maintaining backward compatibility by falling back to `id` when needed. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/version-bridge@1.0.12 + +## 0.14.0-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/version-bridge@1.0.12-next.0 + +## 0.14.0-next.1 + +### Minor Changes + +- bb9b471: Plugin IDs that do not match the standard format are deprecated (letters, digits, and dashes only, starting with a letter). Plugin IDs that do no match this format will be rejected in a future release. +- 10ebed4: **BREAKING**: Removed type support for multiple attachment points in the `ExtensionDefinitionAttachTo` type. Extensions can no longer specify an array of attachment points in the `attachTo` property. + + The runtime still supports multiple attachment points for backward compatibility with existing compiled code, but new code will receive type errors if attempting to use this pattern. + + Extensions that previously used multiple attachment points should migrate to using a Utility API pattern instead. See the [Sharing Extensions Across Multiple Locations](https://backstage.io/docs/frontend-system/architecture/27-sharing-extensions) guide for the recommended approach. + +## 0.14.0-next.0 + +### Minor Changes + +- c38b74d: **BREAKING**: The following blueprints have been removed and are now only available from `@backstage/plugin-app-react`: + + - `IconBundleBlueprint` + - `NavContentBlueprint` + - `RouterBlueprint` + - `SignInPageBlueprint` + - `SwappableComponentBlueprint` + - `ThemeBlueprint` + - `TranslationBlueprint` + +### Patch Changes + +- 7edb810: Added a new `internal` option to `createExtensionInput` that marks the input as only allowing attachments from the same plugin. +- 9554c36: **DEPRECATED**: Multiple attachment points for extensions have been deprecated. The functionality continues to work for backward compatibility, but will log a deprecation warning and be removed in a future release. + + Extensions using array attachment points should migrate to using Utility APIs instead. See the [Sharing Extensions Across Multiple Locations](https://backstage.io/docs/frontend-system/architecture/27-sharing-extensions) guide for the recommended pattern. + +- 53b6549: Plugins in the new frontend system now have a `pluginId` field rather than `id` to better align with naming conventions used throughout the frontend and backend systems. The old field is still present but marked as deprecated. All internal code has been updated to prefer `pluginId` while maintaining backward compatibility by falling back to `id` when needed. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## 0.13.3 + +### Patch Changes + +- 3bd2a1a: Updated documentation for `createApiRef` to clarify the role of the ID in specifying the owning plugin of an API. +- 9ccf84e: The following blueprints are being restricted to only be used in app plugin overrides and modules. They are being moved to the `@backstage/plugin-app-react` package and have been deprecated: + + - `AppRootWrapperBlueprint` + - `IconBundleBlueprint` + - `NavContentBlueprint` + - `RouterBlueprint` + - `SignInPageBlueprint` + - `SwappableComponentBlueprint` + - `ThemeBlueprint` + - `TranslationBlueprint` + +- 4554a4e: Added an alpha `PluginWrapperBlueprint` exported from `@backstage/frontend-plugin-api/alpha`, which can install components that will wrap all plugin elements. +- 872eb91: Upgrade `zod-to-json-schema` to latest version + +## 0.13.2 + +### Patch Changes + +- 75683ed: Added a new `errorPresentation` prop to `ExtensionBoundary` to control how errors are presented to the user. The default is `'error-display'`, which is the current behavior of showing the error in the `ErrorDisplay` component. The new option is `'error-api'`, posts errors to the `ErrorApi` and does not allow retries. + + The `AppRootElementBlueprint` now wraps its element in an `ErrorBoundary` using the new `'error-api'` presentation mode. + +- 0bc1ce9: Fixed a versioning conflict that could result in a `.withContext` is not a function error. +- f3f84f1: Made the return type of `.withOverrides` to be simplified. +- 97cd16f: Reversed the relationship between the old `@backstage/core-plugin-api` and the new `@backstage/frontend-plugin-api`. Previously, the a lot of API definitions and utilities where defined in the old and re-exported from the old, but this change flips that around so that they now reside in the new package and are re-exported from the old. The external API of both packages remain the same, but this is a step towards being able to add further compatibility with the new frontend system built into the old. +- 9b8bde4: Removed unnecessary dependencies on `@backstage/core-components`, `@backstage/config`, `@material-ui/core`, and `lodash`. + +## 0.13.2-next.1 + +### Patch Changes + +- 75683ed: Added a new `errorPresentation` prop to `ExtensionBoundary` to control how errors are presented to the user. The default is `'error-display'`, which is the current behavior of showing the error in the `ErrorDisplay` component. The new option is `'error-api'`, posts errors to the `ErrorApi` and does not allow retries. + + The `AppRootElementBlueprint` now wraps its element in an `ErrorBoundary` using the new `'error-api'` presentation mode. + +- f3f84f1: Made the return type of `.withOverrides` to be simplified. +- Updated dependencies + - @backstage/core-components@0.18.4-next.2 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## 0.13.2-next.0 + +### Patch Changes + +- 0bc1ce9: Fixed a versioning conflict that could result in a `.withContext` is not a function error. +- 97cd16f: Reversed the relationship between the old `@backstage/core-plugin-api` and the new `@backstage/frontend-plugin-api`. Previously, the a lot of API definitions and utilities where defined in the old and re-exported from the old, but this change flips that around so that they now reside in the new package and are re-exported from the old. The external API of both packages remain the same, but this is a step towards being able to add further compatibility with the new frontend system built into the old. +- Updated dependencies + - @backstage/core-components@0.18.4-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## 0.13.0 + +### Minor Changes + +- 7d87b4f: Renamed `ExtensionDefinition` to `OverridableExtensionDefinition` and introduced a slimmer `ExtensionDefinition` type that does not include override methods. The overridable type is generally used as an output type, while plain `ExtensionDefinition`s are used for input. This reduces type conflicts across different of `@backstage/frontend-plugin-api`, improving long-term compatibility. + +### Patch Changes + +- 4d03f08: Internal refactor of route reference implementations with minor updates to the `toString` implementations. +- 7c6a66d: Added support for plugin-relative `attachTo` declarations for extension definitions. This allows for the creation of extension and extension blueprints that attach to other extensions of a particular `kind` in the same plugin, rather than needing to provide the exact extension ID. This is particularly useful when wanting to provide extension blueprints with a built-in hierarchy where the extensions created from one blueprint attach to extensions created from the other blueprint, for example: + + ```ts + // kind: 'tabbed-page' + const parentPage = TabbedPageBlueprint.make({ + params: {....} + }) + // attachTo: { kind: 'tabbed-page', input: 'tabs' } + const child1 = TabContentBlueprint.make({ + name: 'tab1', + params: {....} + }) + ``` + +- 878c251: Updated to `ExtensionInput` to make all type parameters optional. +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + ## 0.12.2-next.2 ### Patch Changes diff --git a/packages/frontend-plugin-api/knip-report.md b/packages/frontend-plugin-api/knip-report.md index d76f9ef7d9..29108d6e3e 100644 --- a/packages/frontend-plugin-api/knip-report.md +++ b/packages/frontend-plugin-api/knip-report.md @@ -1,16 +1,8 @@ # Knip report -## Unused dependencies (3) - -| Name | Location | Severity | -| :------------------------- | :----------- | :------- | -| @backstage/core-components | packages/frontend-plugin-api/package.json | error | -| @material-ui/core | packages/frontend-plugin-api/package.json | error | -| lodash | packages/frontend-plugin-api/package.json | error | - ## Unused devDependencies (1) -| Name | Location | Severity | -| :-------------------------- | :----------- | :------- | -| @backstage/frontend-app-api | packages/frontend-plugin-api/package.json | error | +| Name | Location | Severity | +| :-------------------------- | :---------------- | :------- | +| @backstage/frontend-app-api | package.json:58:6 | error | diff --git a/packages/frontend-plugin-api/package.json b/packages/frontend-plugin-api/package.json index fec762ff63..237e5d01fd 100644 --- a/packages/frontend-plugin-api/package.json +++ b/packages/frontend-plugin-api/package.json @@ -1,13 +1,11 @@ { "name": "@backstage/frontend-plugin-api", - "version": "0.12.2-next.2", + "version": "0.16.0", "backstage": { "role": "web-library" }, "publishConfig": { - "access": "public", - "main": "dist/index.esm.js", - "types": "dist/index.d.ts" + "access": "public" }, "repository": { "type": "git", @@ -16,8 +14,23 @@ }, "license": "Apache-2.0", "sideEffects": false, + "exports": { + ".": "./src/index.ts", + "./alpha": "./src/alpha.ts", + "./package.json": "./package.json" + }, "main": "src/index.ts", "types": "src/index.ts", + "typesVersions": { + "*": { + "alpha": [ + "src/alpha.ts" + ], + "package.json": [ + "package.json" + ] + } + }, "files": [ "dist" ], @@ -31,17 +44,17 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/core-components": "workspace:^", - "@backstage/core-plugin-api": "workspace:^", + "@backstage/errors": "workspace:^", + "@backstage/filter-predicates": "workspace:^", "@backstage/types": "workspace:^", "@backstage/version-bridge": "workspace:^", - "@material-ui/core": "^4.12.4", - "lodash": "^4.17.21", - "zod": "^3.22.4", - "zod-to-json-schema": "^3.21.4" + "@standard-schema/spec": "^1.1.0", + "zod": "^4.0.0", + "zod-to-json-schema": "^3.25.1" }, "devDependencies": { "@backstage/cli": "workspace:^", + "@backstage/config": "workspace:^", "@backstage/frontend-app-api": "workspace:^", "@backstage/frontend-test-utils": "workspace:^", "@backstage/test-utils": "workspace:^", @@ -51,13 +64,13 @@ "history": "^5.3.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/packages/frontend-plugin-api/report-alpha.api.md b/packages/frontend-plugin-api/report-alpha.api.md new file mode 100644 index 0000000000..26cb4d1957 --- /dev/null +++ b/packages/frontend-plugin-api/report-alpha.api.md @@ -0,0 +1,854 @@ +## API Report File for "@backstage/frontend-plugin-api" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { ApiRef as ApiRef_2 } from '@backstage/frontend-plugin-api'; +import { ComponentType } from 'react'; +import { ConfigurableExtensionDataRef as ConfigurableExtensionDataRef_2 } from '@backstage/frontend-plugin-api'; +import { Expand } from '@backstage/types'; +import { ExtensionBlueprint as ExtensionBlueprint_2 } from '@backstage/frontend-plugin-api'; +import { ExtensionBlueprintParams as ExtensionBlueprintParams_2 } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef as ExtensionDataRef_2 } from '@backstage/frontend-plugin-api'; +import { FilterPredicate } from '@backstage/filter-predicates'; +import { JsonObject } from '@backstage/types'; +import { JSX as JSX_2 } from 'react'; +import { ReactNode } from 'react'; +import { StandardSchemaV1 } from '@standard-schema/spec'; +import type { z } from 'zod/v3'; + +// @public +export type AnyRouteRefParams = + | { + [param in string]: string; + } + | undefined; + +// @public +export type ApiHolder = { + get(api: ApiRef): T | undefined; +}; + +// @public +export type ApiRef = { + readonly $$type?: '@backstage/ApiRef'; + readonly id: TId; + readonly T: T; +}; + +// @public +export interface AppNode { + readonly edges: AppNodeEdges; + readonly instance?: AppNodeInstance; + readonly spec: AppNodeSpec; +} + +// @public +export interface AppNodeEdges { + // (undocumented) + readonly attachedTo?: { + node: AppNode; + input: string; + }; + // (undocumented) + readonly attachments: ReadonlyMap; +} + +// @public +export interface AppNodeInstance { + getData(ref: ExtensionDataRef): T | undefined; + getDataRefs(): Iterable>; +} + +// @public +export interface AppNodeSpec { + // (undocumented) + readonly attachTo: ExtensionAttachTo; + // (undocumented) + readonly config?: unknown; + // (undocumented) + readonly disabled: boolean; + // (undocumented) + readonly extension: Extension; + // (undocumented) + readonly id: string; + // (undocumented) + readonly if?: FilterPredicate; + // (undocumented) + readonly plugin: FrontendPlugin; +} + +// @public +export interface AppTree { + readonly nodes: ReadonlyMap; + readonly orphans: Iterable; + readonly root: AppNode; +} + +// @public (undocumented) +export interface ConfigurableExtensionDataRef< + TData, + TId extends string, + TConfig extends { + optional?: true; + } = {}, +> extends ExtensionDataRef { + // (undocumented) + (t: TData): ExtensionDataValue; + // (undocumented) + optional(): ConfigurableExtensionDataRef< + TData, + TId, + TConfig & { + optional: true; + } + >; +} + +// @public +export function createExtensionBlueprintParams( + params: T, +): ExtensionBlueprintParams; + +// @public (undocumented) +export interface Extension { + // (undocumented) + $$type: '@backstage/Extension'; + // (undocumented) + readonly attachTo: ExtensionAttachTo; + // (undocumented) + readonly configSchema?: PortableSchema; + // (undocumented) + readonly disabled: boolean; + // (undocumented) + readonly id: string; +} + +// @public (undocumented) +export type ExtensionAttachTo = { + id: string; + input: string; +}; + +// @public (undocumented) +export interface ExtensionBlueprint< + T extends ExtensionBlueprintParameters = ExtensionBlueprintParameters, +> { + // (undocumented) + dataRefs: T['dataRefs']; + // (undocumented) + make< + TName extends string | undefined, + TParamsInput extends AnyParamsInput>, + UParentInputs extends ExtensionDataRef, + >(args: { + name?: TName; + attachTo?: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo, UParentInputs>; + disabled?: boolean; + if?: FilterPredicate; + params: TParamsInput extends ExtensionBlueprintDefineParams + ? TParamsInput + : T['params'] extends ExtensionBlueprintDefineParams + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `.make({ params: defineParams => defineParams() })`' + : T['params']; + }): OverridableExtensionDefinition<{ + kind: T['kind']; + name: string | undefined extends TName ? undefined : TName; + config: T['config']; + configInput: T['configInput']; + output: T['output']; + inputs: T['inputs']; + params: T['params']; + }>; + makeWithOverrides< + TName extends string | undefined, + UFactoryOutput extends ExtensionDataValue, + UNewOutput extends ExtensionDataRef, + UParentInputs extends ExtensionDataRef, + TExtraInputs extends { + [inputName in string]: ExtensionInput; + } = {}, + TNewExtensionConfigSchema extends { + [key in string]: StandardSchemaV1; + } = {}, + >(args: { + name?: TName; + attachTo?: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo< + ExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UParentInputs + >; + disabled?: boolean; + if?: FilterPredicate; + inputs?: TExtraInputs & { + [KName in keyof T['inputs']]?: `Error: Input '${KName & + string}' is already defined in parent definition`; + }; + output?: Array; + config?: never; + configSchema?: TNewExtensionConfigSchema & { + [KName in keyof T['config']]?: `Error: Config key '${KName & + string}' is already defined in parent schema`; + }; + factory( + originalFactory: < + TParamsInput extends AnyParamsInput>, + >( + params: TParamsInput extends ExtensionBlueprintDefineParams + ? TParamsInput + : T['params'] extends ExtensionBlueprintDefineParams + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(defineParams => defineParams())`' + : T['params'], + context?: { + config?: T['config']; + inputs?: ResolvedInputValueOverrides>; + }, + ) => ExtensionDataContainer>, + context: { + node: AppNode; + apis: ApiHolder; + config: T['config'] & { + [key in keyof TNewExtensionConfigSchema]: StandardSchemaV1.InferOutput< + TNewExtensionConfigSchema[key] + >; + }; + inputs: Expand>; + }, + ): Iterable & + VerifyExtensionFactoryOutput< + ExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UFactoryOutput + >; + }): OverridableExtensionDefinition<{ + config: Expand< + { + [key in keyof TNewExtensionConfigSchema]: StandardSchemaV1.InferOutput< + TNewExtensionConfigSchema[key] + >; + } & T['config'] + >; + configInput: Expand< + { + [key in keyof TNewExtensionConfigSchema]?: StandardSchemaV1.InferInput< + TNewExtensionConfigSchema[key] + >; + } & T['configInput'] + >; + output: ExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; + inputs: Expand; + kind: T['kind']; + name: string | undefined extends TName ? undefined : TName; + params: T['params']; + }>; + // @deprecated (undocumented) + makeWithOverrides< + TName extends string | undefined, + TExtensionConfigSchema extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + }, + UFactoryOutput extends ExtensionDataValue, + UNewOutput extends ExtensionDataRef, + UParentInputs extends ExtensionDataRef, + TExtraInputs extends { + [inputName in string]: ExtensionInput; + } = {}, + >(args: { + name?: TName; + attachTo?: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo< + ExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UParentInputs + >; + disabled?: boolean; + if?: FilterPredicate; + inputs?: TExtraInputs & { + [KName in keyof T['inputs']]?: `Error: Input '${KName & + string}' is already defined in parent definition`; + }; + output?: Array; + configSchema?: never; + config?: { + schema: TExtensionConfigSchema & { + [KName in keyof T['config']]?: `Error: Config key '${KName & + string}' is already defined in parent schema`; + }; + }; + factory( + originalFactory: < + TParamsInput extends AnyParamsInput>, + >( + params: TParamsInput extends ExtensionBlueprintDefineParams + ? TParamsInput + : T['params'] extends ExtensionBlueprintDefineParams + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(defineParams => defineParams())`' + : T['params'], + context?: { + config?: T['config']; + inputs?: ResolvedInputValueOverrides>; + }, + ) => ExtensionDataContainer>, + context: { + node: AppNode; + apis: ApiHolder; + config: T['config'] & { + [key in keyof TExtensionConfigSchema]: z.infer< + ReturnType<((...args: any[]) => any) & TExtensionConfigSchema[key]> + >; + }; + inputs: Expand>; + }, + ): Iterable & + VerifyExtensionFactoryOutput< + ExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UFactoryOutput + >; + }): OverridableExtensionDefinition<{ + config: Expand< + (string extends keyof TExtensionConfigSchema + ? {} + : { + [key in keyof TExtensionConfigSchema]: z.infer< + ReturnType< + ((...args: any[]) => any) & TExtensionConfigSchema[key] + > + >; + }) & + T['config'] + >; + configInput: Expand< + (string extends keyof TExtensionConfigSchema + ? {} + : z.input< + z.ZodObject<{ + [key in keyof TExtensionConfigSchema]: ReturnType< + ((...args: any[]) => any) & TExtensionConfigSchema[key] + >; + }> + >) & + T['configInput'] + >; + output: ExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; + inputs: Expand; + kind: T['kind']; + name: string | undefined extends TName ? undefined : TName; + params: T['params']; + }>; +} + +// @public +export type ExtensionBlueprintDefineParams< + TParams extends object = object, + TInput = any, +> = (params: TInput) => ExtensionBlueprintParams; + +// @public (undocumented) +export type ExtensionBlueprintParameters = { + kind: string; + params?: object | ExtensionBlueprintDefineParams; + configInput?: { + [K in string]: any; + }; + config?: { + [K in string]: any; + }; + output?: ExtensionDataRef; + inputs?: { + [KName in string]: ExtensionInput; + }; + dataRefs?: { + [name in string]: ExtensionDataRef; + }; +}; + +// @public +export type ExtensionBlueprintParams = { + $$type: '@backstage/BlueprintParams'; + T: T; +}; + +// @public (undocumented) +export type ExtensionDataContainer = + Iterable< + UExtensionData extends ExtensionDataRef< + infer IData, + infer IId, + infer IConfig + > + ? IConfig['optional'] extends true + ? never + : ExtensionDataValue + : never + > & { + get( + ref: ExtensionDataRef, + ): UExtensionData extends ExtensionDataRef + ? IConfig['optional'] extends true + ? IData | undefined + : IData + : never; + }; + +// @public (undocumented) +export type ExtensionDataRef< + TData = unknown, + TId extends string = string, + TConfig extends { + optional?: true; + } = { + optional?: true; + }, +> = { + readonly $$type: '@backstage/ExtensionDataRef'; + readonly id: TId; + readonly T: TData; + readonly config: TConfig; +}; + +// @public (undocumented) +export type ExtensionDataValue = { + readonly $$type: '@backstage/ExtensionDataValue'; + readonly id: TId; + readonly value: TData; +}; + +// @public (undocumented) +export interface ExtensionDefinition< + TParams extends ExtensionDefinitionParameters = ExtensionDefinitionParameters, +> { + // (undocumented) + $$type: '@backstage/ExtensionDefinition'; + // (undocumented) + readonly T: TParams; +} + +// @public +export type ExtensionDefinitionAttachTo< + UParentInputs extends ExtensionDataRef = ExtensionDataRef, +> = + | { + id: string; + input: string; + relative?: never; + } + | { + relative: { + kind?: string; + name?: string; + }; + input: string; + id?: never; + } + | ExtensionInput; + +// @public (undocumented) +export type ExtensionDefinitionParameters = { + kind?: string; + name?: string; + configInput?: { + [K in string]: any; + }; + config?: { + [K in string]: any; + }; + output?: ExtensionDataRef; + inputs?: { + [KName in string]: ExtensionInput; + }; + params?: object | ExtensionBlueprintDefineParams; +}; + +// @public (undocumented) +export interface ExtensionInput< + UExtensionData extends ExtensionDataRef< + unknown, + string, + { + optional?: true; + } + > = ExtensionDataRef, + TConfig extends { + singleton: boolean; + optional: boolean; + internal?: boolean; + } = { + singleton: boolean; + optional: boolean; + internal?: boolean; + }, +> { + // (undocumented) + readonly $$type: '@backstage/ExtensionInput'; + // (undocumented) + readonly config: TConfig; + // (undocumented) + readonly extensionData: Array; + // (undocumented) + readonly replaces?: Array<{ + id: string; + input: string; + }>; +} + +// @public +export interface ExternalRouteRef< + TParams extends AnyRouteRefParams = AnyRouteRefParams, +> { + // (undocumented) + readonly $$type: '@backstage/ExternalRouteRef'; + // (undocumented) + readonly T: TParams; +} + +// @public (undocumented) +export interface FrontendPlugin< + TRoutes extends { + [name in string]: RouteRef | SubRouteRef; + } = { + [name in string]: RouteRef | SubRouteRef; + }, + TExternalRoutes extends { + [name in string]: ExternalRouteRef; + } = { + [name in string]: ExternalRouteRef; + }, +> { + // (undocumented) + readonly $$type: '@backstage/FrontendPlugin'; + // (undocumented) + readonly externalRoutes: TExternalRoutes; + readonly icon?: IconElement; + // @deprecated + readonly id: string; + info(): Promise; + readonly pluginId: string; + // (undocumented) + readonly routes: TRoutes; + readonly title?: string; +} + +// @public +export interface FrontendPluginInfo { + description?: string; + links?: Array<{ + title: string; + url: string; + }>; + ownerEntityRefs?: string[]; + packageName?: string; + version?: string; +} + +// @public +export type IconElement = JSX_2.Element | null; + +// @public (undocumented) +export interface OverridableExtensionDefinition< + T extends ExtensionDefinitionParameters = ExtensionDefinitionParameters, +> extends ExtensionDefinition { + readonly inputs: { + [K in keyof T['inputs']]: ExtensionInput< + T['inputs'][K] extends ExtensionInput ? IData : never + >; + }; + // (undocumented) + override< + UFactoryOutput extends ExtensionDataValue, + UNewOutput extends ExtensionDataRef, + TExtraInputs extends { + [inputName in string]: ExtensionInput; + }, + TParamsInput extends AnyParamsInput_2>, + UParentInputs extends ExtensionDataRef, + TNewExtensionConfigSchema extends { + [key in string]: StandardSchemaV1; + } = {}, + >( + args: Expand< + { + attachTo?: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo< + ExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UParentInputs + >; + disabled?: boolean; + if?: FilterPredicate; + inputs?: TExtraInputs & { + [KName in keyof T['inputs']]?: `Error: Input '${KName & + string}' is already defined in parent definition`; + }; + output?: Array; + config?: never; + configSchema?: TNewExtensionConfigSchema & { + [KName in keyof T['config']]?: `Error: Config key '${KName & + string}' is already defined in parent schema`; + }; + factory?( + originalFactory: < + TFactoryParamsReturn extends AnyParamsInput_2< + NonNullable + >, + >( + context?: Expand< + { + config?: T['config']; + inputs?: ResolvedInputValueOverrides>; + } & ([T['params']] extends [never] + ? {} + : { + params?: TFactoryParamsReturn extends ExtensionBlueprintDefineParams + ? TFactoryParamsReturn + : T['params'] extends ExtensionBlueprintDefineParams + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(defineParams => defineParams())`' + : Partial; + }) + >, + ) => ExtensionDataContainer>, + context: { + node: AppNode; + apis: ApiHolder; + config: T['config'] & { + [key in keyof TNewExtensionConfigSchema]: StandardSchemaV1.InferOutput< + TNewExtensionConfigSchema[key] + >; + }; + inputs: Expand>; + }, + ): Iterable; + } & ([T['params']] extends [never] + ? {} + : { + params?: TParamsInput extends ExtensionBlueprintDefineParams + ? TParamsInput + : T['params'] extends ExtensionBlueprintDefineParams + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(defineParams => defineParams())`' + : Partial; + }) + > & + VerifyExtensionFactoryOutput< + ExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UFactoryOutput + >, + ): OverridableExtensionDefinition<{ + kind: T['kind']; + name: T['name']; + output: ExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; + inputs: T['inputs'] & TExtraInputs; + config: T['config'] & { + [key in keyof TNewExtensionConfigSchema]: StandardSchemaV1.InferOutput< + TNewExtensionConfigSchema[key] + >; + }; + configInput: T['configInput'] & { + [key in keyof TNewExtensionConfigSchema]?: StandardSchemaV1.InferInput< + TNewExtensionConfigSchema[key] + >; + }; + }>; + // @deprecated (undocumented) + override< + TExtensionConfigSchema extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + }, + UFactoryOutput extends ExtensionDataValue, + UNewOutput extends ExtensionDataRef, + TExtraInputs extends { + [inputName in string]: ExtensionInput; + }, + TParamsInput extends AnyParamsInput_2>, + UParentInputs extends ExtensionDataRef, + >( + args: Expand< + { + attachTo?: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo< + ExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UParentInputs + >; + disabled?: boolean; + if?: FilterPredicate; + inputs?: TExtraInputs & { + [KName in keyof T['inputs']]?: `Error: Input '${KName & + string}' is already defined in parent definition`; + }; + output?: Array; + configSchema?: never; + config?: { + schema: TExtensionConfigSchema & { + [KName in keyof T['config']]?: `Error: Config key '${KName & + string}' is already defined in parent schema`; + }; + }; + factory?( + originalFactory: < + TFactoryParamsReturn extends AnyParamsInput_2< + NonNullable + >, + >( + context?: Expand< + { + config?: T['config']; + inputs?: ResolvedInputValueOverrides>; + } & ([T['params']] extends [never] + ? {} + : { + params?: TFactoryParamsReturn extends ExtensionBlueprintDefineParams + ? TFactoryParamsReturn + : T['params'] extends ExtensionBlueprintDefineParams + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(defineParams => defineParams())`' + : Partial; + }) + >, + ) => ExtensionDataContainer>, + context: { + node: AppNode; + apis: ApiHolder; + config: T['config'] & { + [key in keyof TExtensionConfigSchema]: z.infer< + ReturnType< + ((...args: any[]) => any) & TExtensionConfigSchema[key] + > + >; + }; + inputs: Expand>; + }, + ): Iterable; + } & ([T['params']] extends [never] + ? {} + : { + params?: TParamsInput extends ExtensionBlueprintDefineParams + ? TParamsInput + : T['params'] extends ExtensionBlueprintDefineParams + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(defineParams => defineParams())`' + : Partial; + }) + > & + VerifyExtensionFactoryOutput< + ExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UFactoryOutput + >, + ): OverridableExtensionDefinition<{ + kind: T['kind']; + name: T['name']; + output: ExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; + inputs: T['inputs'] & TExtraInputs; + config: T['config'] & { + [key in keyof TExtensionConfigSchema]: z.infer< + ReturnType<((...args: any[]) => any) & TExtensionConfigSchema[key]> + >; + }; + configInput: T['configInput'] & + z.input< + z.ZodObject<{ + [key in keyof TExtensionConfigSchema]: ReturnType< + ((...args: any[]) => any) & TExtensionConfigSchema[key] + >; + }> + >; + }>; +} + +// @public +export type PluginWrapperApi = { + getRootWrapper(): ComponentType<{ + children: ReactNode; + }>; + getPluginWrapper(pluginId: string): + | ComponentType<{ + children: ReactNode; + }> + | undefined; +}; + +// @public +export const pluginWrapperApiRef: ApiRef_2< + PluginWrapperApi, + 'core.plugin-wrapper' +> & { + readonly $$type: '@backstage/ApiRef'; +}; + +// @public +export const PluginWrapperBlueprint: ExtensionBlueprint_2<{ + kind: 'plugin-wrapper'; + params: (params: { + loader: () => Promise>; + }) => ExtensionBlueprintParams_2<{ + loader: () => Promise; + }>; + output: ExtensionDataRef_2< + () => Promise, + 'core.plugin-wrapper.loader', + {} + >; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { + wrapper: ConfigurableExtensionDataRef_2< + () => Promise, + 'core.plugin-wrapper.loader', + {} + >; + }; +}>; + +// @public +export type PluginWrapperDefinition = { + useWrapperValue?: () => TValue; + component: ComponentType<{ + children: ReactNode; + value: TValue; + }>; +}; + +// @public (undocumented) +export type PortableSchema = { + parse: (input: TInput) => TOutput; + schema: () => { + schema: JsonObject; + }; +}; + +// @public +export interface RouteRef< + TParams extends AnyRouteRefParams = AnyRouteRefParams, +> { + // (undocumented) + readonly $$type: '@backstage/RouteRef'; + // (undocumented) + readonly T: TParams; +} + +export { StandardSchemaV1 }; + +// @public +export interface SubRouteRef< + TParams extends AnyRouteRefParams = AnyRouteRefParams, +> { + // (undocumented) + readonly $$type: '@backstage/SubRouteRef'; + // (undocumented) + readonly path: string; + // (undocumented) + readonly T: TParams; +} + +// (No @packageDocumentation comment for this package) +``` diff --git a/packages/frontend-plugin-api/report.api.md b/packages/frontend-plugin-api/report.api.md index f2fe60c82a..e908bc7184 100644 --- a/packages/frontend-plugin-api/report.api.md +++ b/packages/frontend-plugin-api/report.api.md @@ -3,103 +3,46 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { AlertApi } from '@backstage/core-plugin-api'; -import { alertApiRef } from '@backstage/core-plugin-api'; -import { AlertMessage } from '@backstage/core-plugin-api'; -import { AnyApiFactory } from '@backstage/core-plugin-api'; -import { AnyApiRef } from '@backstage/core-plugin-api'; import { AnyRouteRefParams as AnyRouteRefParams_2 } from '@backstage/frontend-plugin-api'; -import { ApiFactory } from '@backstage/core-plugin-api'; -import { ApiHolder } from '@backstage/core-plugin-api'; -import { ApiRef } from '@backstage/core-plugin-api'; -import { ApiRefConfig } from '@backstage/core-plugin-api'; -import { AppTheme } from '@backstage/core-plugin-api'; -import { AppThemeApi } from '@backstage/core-plugin-api'; -import { appThemeApiRef } from '@backstage/core-plugin-api'; -import { atlassianAuthApiRef } from '@backstage/core-plugin-api'; -import { AuthProviderInfo } from '@backstage/core-plugin-api'; -import { AuthRequestOptions } from '@backstage/core-plugin-api'; -import { BackstageIdentityApi } from '@backstage/core-plugin-api'; -import { BackstageIdentityResponse } from '@backstage/core-plugin-api'; -import { BackstageUserIdentity } from '@backstage/core-plugin-api'; -import { bitbucketAuthApiRef } from '@backstage/core-plugin-api'; -import { bitbucketServerAuthApiRef } from '@backstage/core-plugin-api'; +import { ApiRef as ApiRef_2 } from '@backstage/frontend-plugin-api'; import { ComponentType } from 'react'; -import { ConfigApi } from '@backstage/core-plugin-api'; -import { configApiRef } from '@backstage/core-plugin-api'; +import type { Config } from '@backstage/config'; import { ConfigurableExtensionDataRef as ConfigurableExtensionDataRef_2 } from '@backstage/frontend-plugin-api'; -import { createApiFactory } from '@backstage/core-plugin-api'; -import { createApiRef } from '@backstage/core-plugin-api'; -import { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; -import { createTranslationRef } from '@backstage/core-plugin-api/alpha'; -import { createTranslationResource } from '@backstage/core-plugin-api/alpha'; -import { DiscoveryApi } from '@backstage/core-plugin-api'; -import { discoveryApiRef } from '@backstage/core-plugin-api'; -import { ErrorApi } from '@backstage/core-plugin-api'; -import { ErrorApiError } from '@backstage/core-plugin-api'; -import { ErrorApiErrorContext } from '@backstage/core-plugin-api'; -import { errorApiRef } from '@backstage/core-plugin-api'; import { Expand } from '@backstage/types'; +import { ExpandRecursive } from '@backstage/types'; import { ExtensionBlueprint as ExtensionBlueprint_2 } from '@backstage/frontend-plugin-api'; +import { ExtensionBlueprintParams as ExtensionBlueprintParams_2 } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef as ExtensionDataRef_2 } from '@backstage/frontend-plugin-api'; -import { FeatureFlag } from '@backstage/core-plugin-api'; -import { FeatureFlagsApi } from '@backstage/core-plugin-api'; -import { featureFlagsApiRef } from '@backstage/core-plugin-api'; -import { FeatureFlagsSaveOptions } from '@backstage/core-plugin-api'; -import { FeatureFlagState } from '@backstage/core-plugin-api'; -import { FetchApi } from '@backstage/core-plugin-api'; -import { fetchApiRef } from '@backstage/core-plugin-api'; -import { githubAuthApiRef } from '@backstage/core-plugin-api'; -import { gitlabAuthApiRef } from '@backstage/core-plugin-api'; -import { googleAuthApiRef } from '@backstage/core-plugin-api'; -import { IconComponent as IconComponent_2 } from '@backstage/frontend-plugin-api'; -import { IconComponent as IconComponent_3 } from '@backstage/core-plugin-api'; -import { IdentityApi } from '@backstage/core-plugin-api'; -import { identityApiRef } from '@backstage/core-plugin-api'; +import { ExtensionInput as ExtensionInput_2 } from '@backstage/frontend-plugin-api'; +import { FilterPredicate } from '@backstage/filter-predicates'; import { JsonObject } from '@backstage/types'; -import { JSX as JSX_2 } from 'react/jsx-runtime'; -import { JSX as JSX_3 } from 'react'; -import { microsoftAuthApiRef } from '@backstage/core-plugin-api'; -import { OAuthApi } from '@backstage/core-plugin-api'; -import { OAuthRequestApi } from '@backstage/core-plugin-api'; -import { oauthRequestApiRef } from '@backstage/core-plugin-api'; -import { OAuthRequester } from '@backstage/core-plugin-api'; -import { OAuthRequesterOptions } from '@backstage/core-plugin-api'; -import { OAuthScope } from '@backstage/core-plugin-api'; -import { oktaAuthApiRef } from '@backstage/core-plugin-api'; -import { oneloginAuthApiRef } from '@backstage/core-plugin-api'; -import { OpenIdConnectApi } from '@backstage/core-plugin-api'; -import { openshiftAuthApiRef } from '@backstage/core-plugin-api'; -import { PendingOAuthRequest } from '@backstage/core-plugin-api'; -import { ProfileInfo } from '@backstage/core-plugin-api'; -import { ProfileInfoApi } from '@backstage/core-plugin-api'; +import { JsonValue } from '@backstage/types'; +import { JSX as JSX_2 } from 'react'; +import { JSX as JSX_3 } from 'react/jsx-runtime'; +import { Observable } from '@backstage/types'; +import { PropsWithChildren } from 'react'; import { ReactNode } from 'react'; -import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api'; -import { SessionApi } from '@backstage/core-plugin-api'; -import { SessionState } from '@backstage/core-plugin-api'; -import { SignInPageProps } from '@backstage/core-plugin-api'; -import { StorageApi } from '@backstage/core-plugin-api'; -import { storageApiRef } from '@backstage/core-plugin-api'; -import { StorageValueSnapshot } from '@backstage/core-plugin-api'; -import { TranslationMessages } from '@backstage/core-plugin-api/alpha'; -import { TranslationMessagesOptions } from '@backstage/core-plugin-api/alpha'; -import { TranslationRef } from '@backstage/core-plugin-api/alpha'; -import { TranslationRefOptions } from '@backstage/core-plugin-api/alpha'; -import { TranslationResource } from '@backstage/core-plugin-api/alpha'; -import { TranslationResourceOptions } from '@backstage/core-plugin-api/alpha'; -import { TypesToApiRefs } from '@backstage/core-plugin-api'; -import { useApi } from '@backstage/core-plugin-api'; -import { useApiHolder } from '@backstage/core-plugin-api'; -import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; -import { vmwareCloudAuthApiRef } from '@backstage/core-plugin-api'; -import { withApis } from '@backstage/core-plugin-api'; -import { z } from 'zod'; +import { StandardSchemaV1 } from '@standard-schema/spec'; +import { SwappableComponentRef as SwappableComponentRef_2 } from '@backstage/frontend-plugin-api'; +import type { z } from 'zod/v3'; -export { AlertApi }; +// @public @deprecated +export type AlertApi = { + post(alert: AlertMessage): void; + alert$(): Observable; +}; -export { alertApiRef }; +// @public @deprecated +export const alertApiRef: ApiRef_2 & { + readonly $$type: '@backstage/ApiRef'; +}; -export { AlertMessage }; +// @public @deprecated +export type AlertMessage = { + message: string; + severity?: 'success' | 'info' | 'warning' | 'error'; + display?: 'permanent' | 'transient'; +}; // @public export type AnalyticsApi = { @@ -107,13 +50,15 @@ export type AnalyticsApi = { }; // @public -export const analyticsApiRef: ApiRef; +export const analyticsApiRef: ApiRef_2 & { + readonly $$type: '@backstage/ApiRef'; +}; // @public export const AnalyticsContext: (options: { attributes: Partial; children: ReactNode; -}) => JSX_2.Element; +}) => JSX_3.Element; // @public export interface AnalyticsContextValue { @@ -142,13 +87,13 @@ export type AnalyticsImplementation = { captureEvent(event: AnalyticsEvent): void; }; -// @public -export const AnalyticsImplementationBlueprint: ExtensionBlueprint<{ +// @public @deprecated +export const AnalyticsImplementationBlueprint: ExtensionBlueprint_2<{ kind: 'analytics'; params: ( params: AnalyticsImplementationFactory, - ) => ExtensionBlueprintParams>; - output: ExtensionDataRef< + ) => ExtensionBlueprintParams_2>; + output: ExtensionDataRef_2< AnalyticsImplementationFactory<{}>, 'core.analytics.factory', {} @@ -157,7 +102,7 @@ export const AnalyticsImplementationBlueprint: ExtensionBlueprint<{ config: {}; configInput: {}; dataRefs: { - factory: ConfigurableExtensionDataRef< + factory: ConfigurableExtensionDataRef_2< AnalyticsImplementationFactory<{}>, 'core.analytics.factory', {} @@ -165,7 +110,7 @@ export const AnalyticsImplementationBlueprint: ExtensionBlueprint<{ }; }>; -// @public (undocumented) +// @public @deprecated (undocumented) export type AnalyticsImplementationFactory< Deps extends { [name in string]: unknown; @@ -187,12 +132,17 @@ export type AnalyticsTracker = { ) => void; }; -export { AnyApiFactory }; +// @public +export type AnyApiFactory = ApiFactory< + unknown, + unknown, + { + [key in string]: unknown; + } +>; -export { AnyApiRef }; - -// @public @deprecated (undocumented) -export type AnyExtensionDataRef = ExtensionDataRef; +// @public +export type AnyApiRef = ApiRef; // @public export type AnyRouteRefParams = @@ -202,7 +152,7 @@ export type AnyRouteRefParams = | undefined; // @public -export const ApiBlueprint: ExtensionBlueprint<{ +export const ApiBlueprint: ExtensionBlueprint_2<{ kind: 'api'; params: < TApi, @@ -210,13 +160,13 @@ export const ApiBlueprint: ExtensionBlueprint<{ TDeps extends { [name in string]: unknown }, >( params: ApiFactory, - ) => ExtensionBlueprintParams; - output: ExtensionDataRef; + ) => ExtensionBlueprintParams_2; + output: ExtensionDataRef_2; inputs: {}; config: {}; configInput: {}; dataRefs: { - factory: ConfigurableExtensionDataRef< + factory: ConfigurableExtensionDataRef_2< AnyApiFactory, 'core.api.factory', {} @@ -224,13 +174,54 @@ export const ApiBlueprint: ExtensionBlueprint<{ }; }>; -export { ApiFactory }; +// @public +export type ApiFactory< + Api, + Impl extends Api, + Deps extends { + [name in string]: unknown; + }, +> = { + api: ApiRef; + deps: TypesToApiRefs; + factory(deps: Deps): Impl; +}; -export { ApiHolder }; +// @public +export type ApiHolder = { + get(api: ApiRef): T | undefined; +}; -export { ApiRef }; +// @public +export type ApiRef = { + readonly $$type?: '@backstage/ApiRef'; + readonly id: TId; + readonly T: T; +}; -export { ApiRefConfig }; +// @public +export type ApiRefConfig = { + id: string; +}; + +// @public (undocumented) +export type AppLanguageApi = { + getAvailableLanguages(): { + languages: string[]; + }; + setLanguage(language?: string): void; + getLanguage(): { + language: string; + }; + language$(): Observable<{ + language: string; + }>; +}; + +// @public (undocumented) +export const appLanguageApiRef: ApiRef_2 & { + readonly $$type: '@backstage/ApiRef'; +}; // @public export interface AppNode { @@ -269,16 +260,18 @@ export interface AppNodeSpec { // (undocumented) readonly id: string; // (undocumented) + readonly if?: FilterPredicate; + // (undocumented) readonly plugin: FrontendPlugin; } // @public -export const AppRootElementBlueprint: ExtensionBlueprint<{ +export const AppRootElementBlueprint: ExtensionBlueprint_2<{ kind: 'app-root-element'; params: { element: JSX.Element; }; - output: ExtensionDataRef; + output: ExtensionDataRef_2; inputs: {}; config: {}; configInput: {}; @@ -286,34 +279,26 @@ export const AppRootElementBlueprint: ExtensionBlueprint<{ }>; // @public -export const AppRootWrapperBlueprint: ExtensionBlueprint<{ - kind: 'app-root-wrapper'; - params: { - Component?: [error: 'Use the `component` parameter instead']; - component: (props: { children: ReactNode }) => JSX.Element | null; - }; - output: ExtensionDataRef< - (props: { children: ReactNode }) => JSX.Element | null, - 'app.root.wrapper', - {} - >; - inputs: {}; - config: {}; - configInput: {}; - dataRefs: { - component: ConfigurableExtensionDataRef< - (props: { children: ReactNode }) => JSX.Element | null, - 'app.root.wrapper', - {} - >; - }; -}>; +export type AppTheme = { + id: string; + title: string; + variant: 'light' | 'dark'; + icon?: React.ReactElement; + Provider(props: { children: ReactNode }): JSX.Element | null; +}; -export { AppTheme }; +// @public +export type AppThemeApi = { + getInstalledThemes(): AppTheme[]; + activeThemeId$(): Observable; + getActiveThemeId(): string | undefined; + setActiveThemeId(themeId?: string): void; +}; -export { AppThemeApi }; - -export { appThemeApiRef }; +// @public +export const appThemeApiRef: ApiRef_2 & { + readonly $$type: '@backstage/ApiRef'; +}; // @public export interface AppTree { @@ -333,27 +318,76 @@ export interface AppTreeApi { } // @public -export const appTreeApiRef: ApiRef; +export const appTreeApiRef: ApiRef_2 & { + readonly $$type: '@backstage/ApiRef'; +}; -export { atlassianAuthApiRef }; +// @public +export const atlassianAuthApiRef: ApiRef_2< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi, + 'core.auth.atlassian' +> & { + readonly $$type: '@backstage/ApiRef'; +}; -export { AuthProviderInfo }; +// @public +export type AuthProviderInfo = { + id: string; + title: string; + icon: IconComponent | IconElement; + message?: string; +}; -export { AuthRequestOptions }; +// @public +export type AuthRequestOptions = { + optional?: boolean; + instantPopup?: boolean; +}; -export { BackstageIdentityApi }; +// @public +export type BackstageIdentityApi = { + getBackstageIdentity( + options?: AuthRequestOptions, + ): Promise; +}; -export { BackstageIdentityResponse }; +// @public +export type BackstageIdentityResponse = { + token: string; + expiresAt?: Date; + identity: BackstageUserIdentity; +}; -export { BackstageUserIdentity }; +// @public +export type BackstageUserIdentity = { + type: 'user'; + userEntityRef: string; + ownershipEntityRefs: string[]; +}; -export { bitbucketAuthApiRef }; +// @public +export const bitbucketAuthApiRef: ApiRef_2< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi, + 'core.auth.bitbucket' +> & { + readonly $$type: '@backstage/ApiRef'; +}; -export { bitbucketServerAuthApiRef }; +// @public +export const bitbucketServerAuthApiRef: ApiRef_2< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi, + 'core.auth.bitbucket-server' +> & { + readonly $$type: '@backstage/ApiRef'; +}; -export { ConfigApi }; +// @public +export type ConfigApi = Config; -export { configApiRef }; +// @public +export const configApiRef: ApiRef_2 & { + readonly $$type: '@backstage/ApiRef'; +}; // @public (undocumented) export interface ConfigurableExtensionDataRef< @@ -377,25 +411,104 @@ export interface ConfigurableExtensionDataRef< // @public (undocumented) export const coreExtensionData: { - title: ConfigurableExtensionDataRef; - reactElement: ConfigurableExtensionDataRef< - JSX_3.Element, + title: ConfigurableExtensionDataRef_2; + icon: ConfigurableExtensionDataRef_2; + reactElement: ConfigurableExtensionDataRef_2< + JSX_2.Element, 'core.reactElement', {} >; - routePath: ConfigurableExtensionDataRef; - routeRef: ConfigurableExtensionDataRef< + routePath: ConfigurableExtensionDataRef_2; + routeRef: ConfigurableExtensionDataRef_2< RouteRef, 'core.routing.ref', {} >; }; -export { createApiFactory }; - -export { createApiRef }; +// @public +export function createApiFactory< + Api, + Impl extends Api, + Deps extends { + [name in string]: unknown; + }, +>(factory: ApiFactory): ApiFactory; // @public +export function createApiFactory( + api: ApiRef, + instance: Impl, +): ApiFactory; + +// @public @deprecated +export function createApiRef(config: ApiRefConfig): ApiRef & { + readonly $$type: '@backstage/ApiRef'; +}; + +// @public +export function createApiRef(): { + with( + config: ApiRefConfig & { + id: TId; + pluginId?: string; + }, + ): ApiRef & { + readonly $$type: '@backstage/ApiRef'; + }; +}; + +// @public +export function createExtension< + UOutput extends ExtensionDataRef, + TInputs extends { + [inputName in string]: ExtensionInput; + }, + UFactoryOutput extends ExtensionDataValue, + const TKind extends string | undefined = undefined, + const TName extends string | undefined = undefined, + UParentInputs extends ExtensionDataRef = ExtensionDataRef, + TNewConfigSchema extends { + [key: string]: StandardSchemaV1; + } = {}, +>( + options: CreateExtensionOptions< + TKind, + TName, + UOutput, + TInputs, + {}, + UFactoryOutput, + UParentInputs, + TNewConfigSchema + > & { + config?: never; + }, +): OverridableExtensionDefinition<{ + config: { + [key in keyof TNewConfigSchema]: StandardSchemaV1.InferOutput< + TNewConfigSchema[key] + >; + }; + configInput: { + [key in keyof TNewConfigSchema]?: StandardSchemaV1.InferInput< + TNewConfigSchema[key] + >; + }; + output: UOutput extends ExtensionDataRef< + infer IData, + infer IId, + infer IConfig + > + ? ExtensionDataRef + : never; + inputs: TInputs; + params: never; + kind: string | undefined extends TKind ? undefined : TKind; + name: string | undefined extends TName ? undefined : TName; +}>; + +// @public @deprecated (undocumented) export function createExtension< UOutput extends ExtensionDataRef, TInputs extends { @@ -416,19 +529,26 @@ export function createExtension< TInputs, TConfigSchema, UFactoryOutput, - UParentInputs - >, + UParentInputs, + {} + > & { + configSchema?: never; + }, ): OverridableExtensionDefinition<{ config: string extends keyof TConfigSchema ? {} : { - [key in keyof TConfigSchema]: z.infer>; + [key in keyof TConfigSchema]: z.infer< + ReturnType<((...args: any[]) => any) & TConfigSchema[key]> + >; }; configInput: string extends keyof TConfigSchema ? {} : z.input< z.ZodObject<{ - [key in keyof TConfigSchema]: ReturnType; + [key in keyof TConfigSchema]: ReturnType< + ((...args: any[]) => any) & TConfigSchema[key] + >; }> >; output: UOutput extends ExtensionDataRef< @@ -445,6 +565,77 @@ export function createExtension< }>; // @public +export function createExtensionBlueprint< + TParams extends object | ExtensionBlueprintDefineParams, + UOutput extends ExtensionDataRef, + TInputs extends { + [inputName in string]: ExtensionInput; + }, + UFactoryOutput extends ExtensionDataValue, + TKind extends string, + UParentInputs extends ExtensionDataRef, + TDataRefs extends { + [name in string]: ExtensionDataRef; + } = never, + TNewConfigSchema extends { + [key in string]: StandardSchemaV1; + } = {}, +>( + options: { + kind: TKind; + attachTo: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo; + disabled?: boolean; + if?: FilterPredicate; + inputs?: TInputs; + output: Array; + config?: never; + configSchema?: TNewConfigSchema; + defineParams?: TParams extends ExtensionBlueprintDefineParams + ? TParams + : 'The defineParams option must be a function if provided, see the docs for details'; + factory( + params: TParams extends ExtensionBlueprintDefineParams + ? ReturnType['T'] + : TParams, + context: { + node: AppNode; + apis: ApiHolder; + config: { + [key in keyof TNewConfigSchema]: StandardSchemaV1.InferOutput< + TNewConfigSchema[key] + >; + }; + inputs: Expand>; + }, + ): Iterable; + dataRefs?: TDataRefs; + } & VerifyExtensionFactoryOutput, +): ExtensionBlueprint<{ + kind: TKind; + params: TParams; + output: UOutput extends ExtensionDataRef< + infer IData, + infer IId, + infer IConfig + > + ? ExtensionDataRef + : never; + inputs: string extends keyof TInputs ? {} : TInputs; + config: { + [key in keyof TNewConfigSchema]: StandardSchemaV1.InferOutput< + TNewConfigSchema[key] + >; + }; + configInput: { + [key in keyof TNewConfigSchema]?: StandardSchemaV1.InferInput< + TNewConfigSchema[key] + >; + }; + dataRefs: TDataRefs; +}>; + +// @public @deprecated (undocumented) export function createExtensionBlueprint< TParams extends object | ExtensionBlueprintDefineParams, UOutput extends ExtensionDataRef, @@ -461,16 +652,38 @@ export function createExtensionBlueprint< [name in string]: ExtensionDataRef; } = never, >( - options: CreateExtensionBlueprintOptions< - TKind, - TParams, - UOutput, - TInputs, - TConfigSchema, - UFactoryOutput, - TDataRefs, - UParentInputs - >, + options: { + kind: TKind; + attachTo: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo; + disabled?: boolean; + if?: FilterPredicate; + inputs?: TInputs; + output: Array; + configSchema?: never; + config?: { + schema: TConfigSchema; + }; + defineParams?: TParams extends ExtensionBlueprintDefineParams + ? TParams + : 'The defineParams option must be a function if provided, see the docs for details'; + factory( + params: TParams extends ExtensionBlueprintDefineParams + ? ReturnType['T'] + : TParams, + context: { + node: AppNode; + apis: ApiHolder; + config: { + [key in keyof TConfigSchema]: z.infer< + ReturnType<((...args: any[]) => any) & TConfigSchema[key]> + >; + }; + inputs: Expand>; + }, + ): Iterable; + dataRefs?: TDataRefs; + } & VerifyExtensionFactoryOutput, ): ExtensionBlueprint<{ kind: TKind; params: TParams; @@ -485,13 +698,17 @@ export function createExtensionBlueprint< config: string extends keyof TConfigSchema ? {} : { - [key in keyof TConfigSchema]: z.infer>; + [key in keyof TConfigSchema]: z.infer< + ReturnType<((...args: any[]) => any) & TConfigSchema[key]> + >; }; configInput: string extends keyof TConfigSchema ? {} : z.input< z.ZodObject<{ - [key in keyof TConfigSchema]: ReturnType; + [key in keyof TConfigSchema]: ReturnType< + ((...args: any[]) => any) & TConfigSchema[key] + >; }> >; dataRefs: TDataRefs; @@ -513,13 +730,18 @@ export type CreateExtensionBlueprintOptions< [name in string]: ExtensionDataRef; }, UParentInputs extends ExtensionDataRef, + TNewConfigSchema extends { + [key in string]: StandardSchemaV1; + } = {}, > = { kind: TKind; attachTo: ExtensionDefinitionAttachTo & VerifyExtensionAttachTo; disabled?: boolean; + if?: FilterPredicate; inputs?: TInputs; output: Array; + configSchema?: TNewConfigSchema; config?: { schema: TConfigSchema; }; @@ -534,7 +756,13 @@ export type CreateExtensionBlueprintOptions< node: AppNode; apis: ApiHolder; config: { - [key in keyof TConfigSchema]: z.infer>; + [key in keyof TNewConfigSchema]: StandardSchemaV1.InferOutput< + TNewConfigSchema[key] + >; + } & { + [key in keyof TConfigSchema]: z.infer< + ReturnType<((...args: any[]) => any) & TConfigSchema[key]> + >; }; inputs: Expand>; }, @@ -554,7 +782,7 @@ export function createExtensionDataRef(): { }): ConfigurableExtensionDataRef; }; -// @public (undocumented) +// @public export function createExtensionInput< UExtensionData extends ExtensionDataRef< unknown, @@ -566,6 +794,7 @@ export function createExtensionInput< TConfig extends { singleton?: boolean; optional?: boolean; + internal?: boolean; }, >( extensionData: Array, @@ -580,6 +809,7 @@ export function createExtensionInput< { singleton: TConfig['singleton'] extends true ? true : false; optional: TConfig['optional'] extends true ? true : false; + internal: TConfig['internal'] extends true ? true : false; } >; @@ -596,14 +826,19 @@ export type CreateExtensionOptions< }, UFactoryOutput extends ExtensionDataValue, UParentInputs extends ExtensionDataRef, + TNewConfigSchema extends { + [key: string]: StandardSchemaV1; + } = {}, > = { kind?: TKind; name?: TName; attachTo: ExtensionDefinitionAttachTo & VerifyExtensionAttachTo; disabled?: boolean; + if?: FilterPredicate; inputs?: TInputs; output: Array; + configSchema?: TNewConfigSchema; config?: { schema: TConfigSchema; }; @@ -611,7 +846,13 @@ export type CreateExtensionOptions< node: AppNode; apis: ApiHolder; config: { - [key in keyof TConfigSchema]: z.infer>; + [key in keyof TNewConfigSchema]: StandardSchemaV1.InferOutput< + TNewConfigSchema[key] + >; + } & { + [key in keyof TConfigSchema]: z.infer< + ReturnType<((...args: any[]) => any) & TConfigSchema[key]> + >; }; inputs: Expand>; }): Iterable; @@ -625,7 +866,7 @@ export function createExternalRouteRef< } | undefined = undefined, TParamKeys extends string = string, ->(options?: { +>(config?: { readonly params?: string extends TParamKeys ? (keyof TParams)[] : TParamKeys[]; @@ -690,6 +931,8 @@ export interface CreateFrontendModuleOptions< // (undocumented) featureFlags?: FeatureFlagConfig[]; // (undocumented) + if?: FilterPredicate; + // (undocumented) pluginId: TPluginId; } @@ -704,7 +947,12 @@ export function createFrontendPlugin< [name in string]: ExternalRouteRef; } = {}, >( - options: PluginOptions, + options: CreateFrontendPluginOptions< + TId, + TRoutes, + TExternalRoutes, + TExtensions + >, ): OverridableFrontendPlugin< TRoutes, TExternalRoutes, @@ -712,25 +960,43 @@ export function createFrontendPlugin< >; // @public -export function createRouteRef< - TParams extends - | { - [param in TParamKeys]: string; - } - | undefined = undefined, - TParamKeys extends string = string, ->(config?: { - readonly params?: string extends TParamKeys - ? (keyof TParams)[] - : TParamKeys[]; +export interface CreateFrontendPluginOptions< + TId extends string, + TRoutes extends { + [name in string]: RouteRef | SubRouteRef; + }, + TExternalRoutes extends { + [name in string]: ExternalRouteRef; + }, + TExtensions extends readonly ExtensionDefinition[], +> { + // (undocumented) + extensions?: TExtensions; + // (undocumented) + externalRoutes?: TExternalRoutes; + // (undocumented) + featureFlags?: FeatureFlagConfig[]; + icon?: IconElement; + // (undocumented) + if?: FilterPredicate; + // (undocumented) + info?: FrontendPluginInfoOptions; + // (undocumented) + pluginId: TId; + // (undocumented) + routes?: TRoutes; + title?: string; +} + +// @public +export function createRouteRef(config?: { + readonly params?: TParamKey[]; aliasFor?: string; }): RouteRef< - keyof TParams extends never + [TParamKey] extends [never] ? undefined - : string extends TParamKeys - ? TParams : { - [param in TParamKeys]: string; + [param in TParamKey]: string; } >; @@ -769,14 +1035,59 @@ export type CreateSwappableComponentOptions< transformProps?: (props: TExternalComponentProps) => TInnerComponentProps; }; -export { createTranslationMessages }; +// @public +export function createTranslationMessages< + TId extends string, + TMessages extends { + [key in string]: string; + }, + TFull extends boolean, +>( + options: TranslationMessagesOptions, +): TranslationMessages; -export { createTranslationRef }; +// @public (undocumented) +export function createTranslationRef< + TId extends string, + const TNestedMessages extends AnyNestedMessages, + TTranslations extends { + [language in string]: () => Promise<{ + default: { + [key in keyof FlattenedMessages]: string | null; + }; + }>; + }, +>( + config: TranslationRefOptions, +): TranslationRef>; -export { createTranslationResource }; +// @public (undocumented) +export function createTranslationResource< + TId extends string, + TMessages extends { + [key in string]: string; + }, + TTranslations extends { + [language in string]: () => Promise<{ + default: + | TranslationMessages + | { + [key in keyof TMessages]: string | null; + }; + }>; + }, +>( + options: TranslationResourceOptions, +): TranslationResource; // @public export interface DialogApi { + open( + elementOrComponent: + | JSX.Element + | ((props: { dialog: DialogApiDialog }) => JSX.Element), + ): DialogApiDialog; + // @deprecated show( elementOrComponent: | JSX.Element @@ -784,6 +1095,7 @@ export interface DialogApi { dialog: DialogApiDialog; }) => JSX.Element), ): DialogApiDialog; + // @deprecated showModal( elementOrComponent: | JSX.Element @@ -799,30 +1111,56 @@ export interface DialogApiDialog { result(): Promise; update( elementOrComponent: - | React.JSX.Element + | JSX.Element | ((props: { dialog: DialogApiDialog }) => JSX.Element), ): void; } // @public -export const dialogApiRef: ApiRef; +export const dialogApiRef: ApiRef_2 & { + readonly $$type: '@backstage/ApiRef'; +}; -export { DiscoveryApi }; +// @public +export type DiscoveryApi = { + getBaseUrl(pluginId: string): Promise; +}; -export { discoveryApiRef }; +// @public +export const discoveryApiRef: ApiRef_2 & { + readonly $$type: '@backstage/ApiRef'; +}; -export { ErrorApi }; +// @public +export type ErrorApi = { + post(error: ErrorApiError, context?: ErrorApiErrorContext): void; + error$(): Observable<{ + error: ErrorApiError; + context?: ErrorApiErrorContext; + }>; +}; -export { ErrorApiError }; +// @public +export type ErrorApiError = { + name: string; + message: string; + stack?: string; +}; -export { ErrorApiErrorContext }; +// @public +export type ErrorApiErrorContext = { + hidden?: boolean; +}; -export { errorApiRef }; +// @public +export const errorApiRef: ApiRef_2 & { + readonly $$type: '@backstage/ApiRef'; +}; // @public (undocumented) export const ErrorDisplay: { (props: ErrorDisplayProps): JSX.Element | null; - ref: SwappableComponentRef; + ref: SwappableComponentRef_2; }; // @public (undocumented) @@ -837,7 +1175,7 @@ export interface Extension { // (undocumented) $$type: '@backstage/Extension'; // (undocumented) - readonly attachTo: ExtensionAttachToSpec; + readonly attachTo: ExtensionAttachTo; // (undocumented) readonly configSchema?: PortableSchema; // (undocumented) @@ -847,18 +1185,10 @@ export interface Extension { } // @public (undocumented) -export type ExtensionAttachTo = - | { - id: string; - input: string; - } - | Array<{ - id: string; - input: string; - }>; - -// @public @deprecated (undocumented) -export type ExtensionAttachToSpec = ExtensionAttachTo; +export type ExtensionAttachTo = { + id: string; + input: string; +}; // @public (undocumented) export interface ExtensionBlueprint< @@ -869,13 +1199,14 @@ export interface ExtensionBlueprint< // (undocumented) make< TName extends string | undefined, - TParamsInput extends AnyParamsInput>, + TParamsInput extends AnyParamsInput_2>, UParentInputs extends ExtensionDataRef, >(args: { name?: TName; attachTo?: ExtensionDefinitionAttachTo & VerifyExtensionAttachTo, UParentInputs>; disabled?: boolean; + if?: FilterPredicate; params: TParamsInput extends ExtensionBlueprintDefineParams ? TParamsInput : T['params'] extends ExtensionBlueprintDefineParams @@ -892,15 +1223,15 @@ export interface ExtensionBlueprint< }>; makeWithOverrides< TName extends string | undefined, - TExtensionConfigSchema extends { - [key in string]: (zImpl: typeof z) => z.ZodType; - }, UFactoryOutput extends ExtensionDataValue, UNewOutput extends ExtensionDataRef, + UParentInputs extends ExtensionDataRef, TExtraInputs extends { [inputName in string]: ExtensionInput; - }, - UParentInputs extends ExtensionDataRef, + } = {}, + TNewExtensionConfigSchema extends { + [key in string]: StandardSchemaV1; + } = {}, >(args: { name?: TName; attachTo?: ExtensionDefinitionAttachTo & @@ -911,11 +1242,98 @@ export interface ExtensionBlueprint< UParentInputs >; disabled?: boolean; + if?: FilterPredicate; inputs?: TExtraInputs & { [KName in keyof T['inputs']]?: `Error: Input '${KName & string}' is already defined in parent definition`; }; output?: Array; + config?: never; + configSchema?: TNewExtensionConfigSchema & { + [KName in keyof T['config']]?: `Error: Config key '${KName & + string}' is already defined in parent schema`; + }; + factory( + originalFactory: < + TParamsInput extends AnyParamsInput_2>, + >( + params: TParamsInput extends ExtensionBlueprintDefineParams + ? TParamsInput + : T['params'] extends ExtensionBlueprintDefineParams + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(defineParams => defineParams())`' + : T['params'], + context?: { + config?: T['config']; + inputs?: ResolvedInputValueOverrides>; + }, + ) => ExtensionDataContainer>, + context: { + node: AppNode; + apis: ApiHolder; + config: T['config'] & { + [key in keyof TNewExtensionConfigSchema]: StandardSchemaV1.InferOutput< + TNewExtensionConfigSchema[key] + >; + }; + inputs: Expand>; + }, + ): Iterable & + VerifyExtensionFactoryOutput< + ExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UFactoryOutput + >; + }): OverridableExtensionDefinition<{ + config: Expand< + { + [key in keyof TNewExtensionConfigSchema]: StandardSchemaV1.InferOutput< + TNewExtensionConfigSchema[key] + >; + } & T['config'] + >; + configInput: Expand< + { + [key in keyof TNewExtensionConfigSchema]?: StandardSchemaV1.InferInput< + TNewExtensionConfigSchema[key] + >; + } & T['configInput'] + >; + output: ExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; + inputs: Expand; + kind: T['kind']; + name: string | undefined extends TName ? undefined : TName; + params: T['params']; + }>; + // @deprecated (undocumented) + makeWithOverrides< + TName extends string | undefined, + TExtensionConfigSchema extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + }, + UFactoryOutput extends ExtensionDataValue, + UNewOutput extends ExtensionDataRef, + UParentInputs extends ExtensionDataRef, + TExtraInputs extends { + [inputName in string]: ExtensionInput; + } = {}, + >(args: { + name?: TName; + attachTo?: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo< + ExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UParentInputs + >; + disabled?: boolean; + if?: FilterPredicate; + inputs?: TExtraInputs & { + [KName in keyof T['inputs']]?: `Error: Input '${KName & + string}' is already defined in parent definition`; + }; + output?: Array; + configSchema?: never; config?: { schema: TExtensionConfigSchema & { [KName in keyof T['config']]?: `Error: Config key '${KName & @@ -924,7 +1342,7 @@ export interface ExtensionBlueprint< }; factory( originalFactory: < - TParamsInput extends AnyParamsInput>, + TParamsInput extends AnyParamsInput_2>, >( params: TParamsInput extends ExtensionBlueprintDefineParams ? TParamsInput @@ -941,7 +1359,7 @@ export interface ExtensionBlueprint< apis: ApiHolder; config: T['config'] & { [key in keyof TExtensionConfigSchema]: z.infer< - ReturnType + ReturnType<((...args: any[]) => any) & TExtensionConfigSchema[key]> >; }; inputs: Expand>; @@ -954,26 +1372,32 @@ export interface ExtensionBlueprint< UFactoryOutput >; }): OverridableExtensionDefinition<{ - config: (string extends keyof TExtensionConfigSchema - ? {} - : { - [key in keyof TExtensionConfigSchema]: z.infer< - ReturnType - >; - }) & - T['config']; - configInput: (string extends keyof TExtensionConfigSchema - ? {} - : z.input< - z.ZodObject<{ - [key in keyof TExtensionConfigSchema]: ReturnType< - TExtensionConfigSchema[key] + config: Expand< + (string extends keyof TExtensionConfigSchema + ? {} + : { + [key in keyof TExtensionConfigSchema]: z.infer< + ReturnType< + ((...args: any[]) => any) & TExtensionConfigSchema[key] + > >; - }> - >) & - T['configInput']; + }) & + T['config'] + >; + configInput: Expand< + (string extends keyof TExtensionConfigSchema + ? {} + : z.input< + z.ZodObject<{ + [key in keyof TExtensionConfigSchema]: ReturnType< + ((...args: any[]) => any) & TExtensionConfigSchema[key] + >; + }> + >) & + T['configInput'] + >; output: ExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; - inputs: T['inputs'] & TExtraInputs; + inputs: Expand; kind: T['kind']; name: string | undefined extends TName ? undefined : TName; params: T['params']; @@ -1012,7 +1436,7 @@ export type ExtensionBlueprintParams = { }; // @public (undocumented) -export function ExtensionBoundary(props: ExtensionBoundaryProps): JSX_2.Element; +export function ExtensionBoundary(props: ExtensionBoundaryProps): JSX_3.Element; // @public (undocumented) export namespace ExtensionBoundary { @@ -1033,6 +1457,8 @@ export interface ExtensionBoundaryProps { // (undocumented) children: ReactNode; // (undocumented) + errorPresentation?: 'error-api' | 'error-display'; + // (undocumented) node: AppNode; } @@ -1074,12 +1500,6 @@ export type ExtensionDataRef< readonly config: TConfig; }; -// @public (undocumented) -export type ExtensionDataRefToValue = - TDataRef extends ExtensionDataRef - ? ExtensionDataValue - : never; - // @public (undocumented) export type ExtensionDataValue = { readonly $$type: '@backstage/ExtensionDataValue'; @@ -1114,23 +1534,7 @@ export type ExtensionDefinitionAttachTo< input: string; id?: never; } - | ExtensionInput - | Array< - | { - id: string; - input: string; - relative?: never; - } - | { - relative: { - kind?: string; - name?: string; - }; - input: string; - id?: never; - } - | ExtensionInput - >; + | ExtensionInput; // @public (undocumented) export type ExtensionDefinitionParameters = { @@ -1149,7 +1553,7 @@ export type ExtensionDefinitionParameters = { params?: object | ExtensionBlueprintDefineParams; }; -// @public (undocumented) +// @public @deprecated (undocumented) export type ExtensionFactoryMiddleware = ( originalFactory: (contextOverrides?: { config?: JsonObject; @@ -1173,9 +1577,11 @@ export interface ExtensionInput< TConfig extends { singleton: boolean; optional: boolean; + internal?: boolean; } = { singleton: boolean; optional: boolean; + internal?: boolean; }, > { // (undocumented) @@ -1201,27 +1607,75 @@ export interface ExternalRouteRef< readonly T: TParams; } -export { FeatureFlag }; +// @public +export type FeatureFlag = { + name: string; + pluginId: string; + description?: string; +}; // @public export type FeatureFlagConfig = { name: string; + description?: string; }; -export { FeatureFlagsApi }; +// @public +export interface FeatureFlagsApi { + getRegisteredFlags(): FeatureFlag[]; + isActive(name: string): boolean; + registerFlag(flag: FeatureFlag): void; + save(options: FeatureFlagsSaveOptions): void; +} -export { featureFlagsApiRef }; +// @public +export const featureFlagsApiRef: ApiRef_2< + FeatureFlagsApi, + 'core.featureflags' +> & { + readonly $$type: '@backstage/ApiRef'; +}; -export { FeatureFlagsSaveOptions }; +// @public +export type FeatureFlagsSaveOptions = { + states: Record; + merge?: boolean; +}; -export { FeatureFlagState }; - -export { FetchApi }; - -export { fetchApiRef }; +// @public +export const FeatureFlagState: { + readonly None: 0; + readonly Active: 1; +}; // @public (undocumented) -export type FrontendFeature = FrontendPlugin | FrontendModule; +export type FeatureFlagState = + (typeof FeatureFlagState)[keyof typeof FeatureFlagState]; + +// @public (undocumented) +export namespace FeatureFlagState { + // (undocumented) + export type Active = typeof FeatureFlagState.Active; + // (undocumented) + export type None = typeof FeatureFlagState.None; +} + +// @public +export type FetchApi = { + fetch: typeof fetch; +}; + +// @public +export const fetchApiRef: ApiRef_2 & { + readonly $$type: '@backstage/ApiRef'; +}; + +// @public (undocumented) +export type FrontendFeature = + | (Omit & { + pluginId?: string; + }) + | FrontendModule; // @public (undocumented) export interface FrontendFeatureLoader { @@ -1254,11 +1708,14 @@ export interface FrontendPlugin< readonly $$type: '@backstage/FrontendPlugin'; // (undocumented) readonly externalRoutes: TExternalRoutes; - // (undocumented) + readonly icon?: IconElement; + // @deprecated readonly id: string; info(): Promise; + readonly pluginId: string; // (undocumented) readonly routes: TRoutes; + readonly title?: string; } // @public @@ -1283,125 +1740,116 @@ export type FrontendPluginInfoOptions = { manifest?: () => Promise; }; -export { githubAuthApiRef }; - -export { gitlabAuthApiRef }; - -export { googleAuthApiRef }; - -// @public (undocumented) -export const IconBundleBlueprint: ExtensionBlueprint<{ - kind: 'icon-bundle'; - params: { - icons: { [key in string]: IconComponent }; - }; - output: ExtensionDataRef< - { - [x: string]: IconComponent; - }, - 'core.icons', - {} - >; - inputs: {}; - config: {}; - configInput: {}; - dataRefs: { - icons: ConfigurableExtensionDataRef< - { - [x: string]: IconComponent; - }, - 'core.icons', - {} - >; - }; -}>; +// @public +export const githubAuthApiRef: ApiRef_2< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi, + 'core.auth.github' +> & { + readonly $$type: '@backstage/ApiRef'; +}; // @public +export const gitlabAuthApiRef: ApiRef_2< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi, + 'core.auth.gitlab' +> & { + readonly $$type: '@backstage/ApiRef'; +}; + +// @public +export const googleAuthApiRef: ApiRef_2< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi, + 'core.auth.google' +> & { + readonly $$type: '@backstage/ApiRef'; +}; + +// @public @deprecated export type IconComponent = ComponentType<{ fontSize?: 'medium' | 'large' | 'small' | 'inherit'; }>; +// @public +export type IconElement = JSX_2.Element | null; + // @public export interface IconsApi { - // (undocumented) + // @deprecated (undocumented) getIcon(key: string): IconComponent | undefined; + icon(key: string): IconElement | undefined; // (undocumented) listIconKeys(): string[]; } // @public -export const iconsApiRef: ApiRef; - -export { IdentityApi }; - -export { identityApiRef }; - -export { microsoftAuthApiRef }; +export const iconsApiRef: ApiRef_2 & { + readonly $$type: '@backstage/ApiRef'; +}; // @public -export const NavContentBlueprint: ExtensionBlueprint_2<{ - kind: 'nav-content'; - params: { - component: NavContentComponent; - }; - output: ExtensionDataRef_2< - NavContentComponent, - 'core.nav-content.component', - {} - >; - inputs: {}; - config: {}; - configInput: {}; - dataRefs: { - component: ConfigurableExtensionDataRef_2< - NavContentComponent, - 'core.nav-content.component', - {} - >; - }; -}>; - -// @public -export type NavContentComponent = ( - props: NavContentComponentProps, -) => JSX.Element | null; - -// @public -export interface NavContentComponentProps { - items: Array<{ - icon: IconComponent_2; - title: string; - routeRef: RouteRef_2; - to: string; - text: string; +export type IdentityApi = { + getProfileInfo(): Promise; + getBackstageIdentity(): Promise; + getCredentials(): Promise<{ + token?: string; }>; -} + signOut(): Promise; +}; // @public -export const NavItemBlueprint: ExtensionBlueprint<{ +export const identityApiRef: ApiRef_2 & { + readonly $$type: '@backstage/ApiRef'; +}; + +// @public +export const microsoftAuthApiRef: ApiRef_2< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi, + 'core.auth.microsoft' +> & { + readonly $$type: '@backstage/ApiRef'; +}; + +// @public @deprecated +export const NavItemBlueprint: ExtensionBlueprint_2<{ kind: 'nav-item'; params: { title: string; - icon: IconComponent_3; + icon: IconComponent; routeRef: RouteRef; }; - output: ExtensionDataRef< + output: ExtensionDataRef_2< { title: string; - icon: IconComponent_3; + icon: IconComponent; routeRef: RouteRef; }, 'core.nav-item.target', {} >; inputs: {}; - config: {}; - configInput: {}; + config: { + title: string | undefined; + }; + configInput: { + title?: string | undefined; + }; dataRefs: { - target: ConfigurableExtensionDataRef< + target: ConfigurableExtensionDataRef_2< { title: string; - icon: IconComponent_3; + icon: IconComponent; routeRef: RouteRef; }, 'core.nav-item.target', @@ -1413,7 +1861,7 @@ export const NavItemBlueprint: ExtensionBlueprint<{ // @public (undocumented) export const NotFoundErrorPage: { (props: NotFoundErrorPageProps): JSX.Element | null; - ref: SwappableComponentRef; + ref: SwappableComponentRef_2; }; // @public (undocumented) @@ -1421,25 +1869,80 @@ export type NotFoundErrorPageProps = { children?: ReactNode; }; -export { OAuthApi }; +// @public +export type OAuthApi = { + getAccessToken( + scope?: OAuthScope, + options?: AuthRequestOptions, + ): Promise; +}; -export { OAuthRequestApi }; +// @public +export type OAuthRequestApi = { + createAuthRequester( + options: OAuthRequesterOptions, + ): OAuthRequester; + authRequest$(): Observable; +}; -export { oauthRequestApiRef }; +// @public +export const oauthRequestApiRef: ApiRef_2< + OAuthRequestApi, + 'core.oauthrequest' +> & { + readonly $$type: '@backstage/ApiRef'; +}; -export { OAuthRequester }; +// @public +export type OAuthRequester = ( + scopes: Set, +) => Promise; -export { OAuthRequesterOptions }; +// @public +export type OAuthRequesterOptions = { + provider: AuthProviderInfo; + onAuthRequest(scopes: Set): Promise; +}; -export { OAuthScope }; +// @public +export type OAuthScope = string | string[]; -export { oktaAuthApiRef }; +// @public +export const oktaAuthApiRef: ApiRef_2< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi, + 'core.auth.okta' +> & { + readonly $$type: '@backstage/ApiRef'; +}; -export { oneloginAuthApiRef }; +// @public +export const oneloginAuthApiRef: ApiRef_2< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi, + 'core.auth.onelogin' +> & { + readonly $$type: '@backstage/ApiRef'; +}; -export { OpenIdConnectApi }; +// @public +export type OpenIdConnectApi = { + getIdToken(options?: AuthRequestOptions): Promise; +}; -export { openshiftAuthApiRef }; +// @public +export const openshiftAuthApiRef: ApiRef_2< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi, + 'core.auth.openshift' +> & { + readonly $$type: '@backstage/ApiRef'; +}; // @public (undocumented) export interface OverridableExtensionDefinition< @@ -1451,6 +1954,104 @@ export interface OverridableExtensionDefinition< >; }; // (undocumented) + override< + UFactoryOutput extends ExtensionDataValue, + UNewOutput extends ExtensionDataRef, + TExtraInputs extends { + [inputName in string]: ExtensionInput; + }, + TParamsInput extends AnyParamsInput>, + UParentInputs extends ExtensionDataRef, + TNewExtensionConfigSchema extends { + [key in string]: StandardSchemaV1; + } = {}, + >( + args: Expand< + { + attachTo?: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo< + ExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UParentInputs + >; + disabled?: boolean; + if?: FilterPredicate; + inputs?: TExtraInputs & { + [KName in keyof T['inputs']]?: `Error: Input '${KName & + string}' is already defined in parent definition`; + }; + output?: Array; + config?: never; + configSchema?: TNewExtensionConfigSchema & { + [KName in keyof T['config']]?: `Error: Config key '${KName & + string}' is already defined in parent schema`; + }; + factory?( + originalFactory: < + TFactoryParamsReturn extends AnyParamsInput< + NonNullable + >, + >( + context?: Expand< + { + config?: T['config']; + inputs?: ResolvedInputValueOverrides>; + } & ([T['params']] extends [never] + ? {} + : { + params?: TFactoryParamsReturn extends ExtensionBlueprintDefineParams + ? TFactoryParamsReturn + : T['params'] extends ExtensionBlueprintDefineParams + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(defineParams => defineParams())`' + : Partial; + }) + >, + ) => ExtensionDataContainer>, + context: { + node: AppNode; + apis: ApiHolder; + config: T['config'] & { + [key in keyof TNewExtensionConfigSchema]: StandardSchemaV1.InferOutput< + TNewExtensionConfigSchema[key] + >; + }; + inputs: Expand>; + }, + ): Iterable; + } & ([T['params']] extends [never] + ? {} + : { + params?: TParamsInput extends ExtensionBlueprintDefineParams + ? TParamsInput + : T['params'] extends ExtensionBlueprintDefineParams + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(defineParams => defineParams())`' + : Partial; + }) + > & + VerifyExtensionFactoryOutput< + ExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UFactoryOutput + >, + ): OverridableExtensionDefinition<{ + kind: T['kind']; + name: T['name']; + output: ExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; + inputs: T['inputs'] & TExtraInputs; + config: T['config'] & { + [key in keyof TNewExtensionConfigSchema]: StandardSchemaV1.InferOutput< + TNewExtensionConfigSchema[key] + >; + }; + configInput: T['configInput'] & { + [key in keyof TNewExtensionConfigSchema]?: StandardSchemaV1.InferInput< + TNewExtensionConfigSchema[key] + >; + }; + }>; + // @deprecated (undocumented) override< TExtensionConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType; @@ -1460,7 +2061,7 @@ export interface OverridableExtensionDefinition< TExtraInputs extends { [inputName in string]: ExtensionInput; }, - TParamsInput extends AnyParamsInput_2>, + TParamsInput extends AnyParamsInput>, UParentInputs extends ExtensionDataRef, >( args: Expand< @@ -1473,11 +2074,13 @@ export interface OverridableExtensionDefinition< UParentInputs >; disabled?: boolean; + if?: FilterPredicate; inputs?: TExtraInputs & { [KName in keyof T['inputs']]?: `Error: Input '${KName & string}' is already defined in parent definition`; }; output?: Array; + configSchema?: never; config?: { schema: TExtensionConfigSchema & { [KName in keyof T['config']]?: `Error: Config key '${KName & @@ -1486,7 +2089,7 @@ export interface OverridableExtensionDefinition< }; factory?( originalFactory: < - TFactoryParamsReturn extends AnyParamsInput_2< + TFactoryParamsReturn extends AnyParamsInput< NonNullable >, >( @@ -1510,7 +2113,9 @@ export interface OverridableExtensionDefinition< apis: ApiHolder; config: T['config'] & { [key in keyof TExtensionConfigSchema]: z.infer< - ReturnType + ReturnType< + ((...args: any[]) => any) & TExtensionConfigSchema[key] + > >; }; inputs: Expand>; @@ -1539,14 +2144,14 @@ export interface OverridableExtensionDefinition< inputs: T['inputs'] & TExtraInputs; config: T['config'] & { [key in keyof TExtensionConfigSchema]: z.infer< - ReturnType + ReturnType<((...args: any[]) => any) & TExtensionConfigSchema[key]> >; }; configInput: T['configInput'] & z.input< z.ZodObject<{ [key in keyof TExtensionConfigSchema]: ReturnType< - TExtensionConfigSchema[key] + ((...args: any[]) => any) & TExtensionConfigSchema[key] >; }> >; @@ -1577,44 +2182,166 @@ export interface OverridableFrontendPlugin< ): OverridableExtensionDefinition; // (undocumented) withOverrides(options: { - extensions: Array; + extensions?: Array; + if?: FilterPredicate; + title?: string; + icon?: IconElement; info?: FrontendPluginInfoOptions; }): OverridableFrontendPlugin; } // @public -export const PageBlueprint: ExtensionBlueprint<{ +export const PageBlueprint: ExtensionBlueprint_2<{ kind: 'page'; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; - loader: () => Promise; + title?: string; + icon?: IconElement; + loader?: () => Promise; routeRef?: RouteRef; + noHeader?: boolean; }; output: - | ExtensionDataRef - | ExtensionDataRef - | ExtensionDataRef< - RouteRef, + | ExtensionDataRef_2 + | ExtensionDataRef_2< + RouteRef, 'core.routing.ref', { optional: true; } + > + | ExtensionDataRef_2 + | ExtensionDataRef_2< + string, + 'core.title', + { + optional: true; + } + > + | ExtensionDataRef_2< + IconElement, + 'core.icon', + { + optional: true; + } >; - inputs: {}; + inputs: { + pages: ExtensionInput_2< + | ConfigurableExtensionDataRef_2 + | ConfigurableExtensionDataRef_2 + | ConfigurableExtensionDataRef_2< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + > + | ConfigurableExtensionDataRef_2< + string, + 'core.title', + { + optional: true; + } + > + | ConfigurableExtensionDataRef_2< + IconElement, + 'core.icon', + { + optional: true; + } + >, + { + singleton: false; + optional: false; + internal: false; + } + >; + }; config: { path: string | undefined; + title: string | undefined; }; configInput: { path?: string | undefined; + title?: string | undefined; }; dataRefs: never; }>; -export { PendingOAuthRequest }; +// @public +export const PageLayout: { + (props: PageLayoutProps): JSX.Element | null; + ref: SwappableComponentRef_2; +}; -// @public (undocumented) -export interface PluginOptions< +// @public +export interface PageLayoutProps { + // (undocumented) + children?: ReactNode; + // (undocumented) + headerActions?: Array; + // (undocumented) + icon?: IconElement; + // (undocumented) + noHeader?: boolean; + // (undocumented) + tabs?: PageLayoutTab[]; + // (undocumented) + title?: string; + // (undocumented) + titleLink?: string; +} + +// @public +export interface PageLayoutTab { + // (undocumented) + href: string; + // (undocumented) + icon?: IconElement; + // (undocumented) + id: string; + // (undocumented) + label: string; +} + +// @public @deprecated (undocumented) +export type PageTab = PageLayoutTab; + +// @public +export type PendingOAuthRequest = { + provider: AuthProviderInfo; + reject(): void; + trigger(): Promise; +}; + +// @public +export const PluginHeaderActionBlueprint: ExtensionBlueprint_2<{ + kind: 'plugin-header-action'; + params: { + loader: () => Promise; + }; + output: ExtensionDataRef_2; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: never; +}>; + +// @public +export type PluginHeaderActionsApi = { + getPluginHeaderActions(pluginId: string): Array; +}; + +// @public +export const pluginHeaderActionsApiRef: ApiRef_2< + PluginHeaderActionsApi, + 'core.plugin-header-actions' +> & { + readonly $$type: '@backstage/ApiRef'; +}; + +// @public @deprecated (undocumented) +export type PluginOptions< TId extends string, TRoutes extends { [name in string]: RouteRef | SubRouteRef; @@ -1623,92 +2350,96 @@ export interface PluginOptions< [name in string]: ExternalRouteRef; }, TExtensions extends readonly ExtensionDefinition[], -> { - // (undocumented) - extensions?: TExtensions; - // (undocumented) - externalRoutes?: TExternalRoutes; - // (undocumented) - featureFlags?: FeatureFlagConfig[]; - // (undocumented) - info?: FrontendPluginInfoOptions; - // (undocumented) - pluginId: TId; - // (undocumented) - routes?: TRoutes; -} - -// @public (undocumented) -export type PortableSchema = { - parse: (input: TInput) => TOutput; - schema: JsonObject; -}; - -export { ProfileInfo }; - -export { ProfileInfoApi }; - -// @public (undocumented) -export const Progress: { - (props: ProgressProps): JSX.Element | null; - ref: SwappableComponentRef; -}; - -// @public (undocumented) -export type ProgressProps = {}; +> = CreateFrontendPluginOptions; // @public -export type ResolvedExtensionInput = - TExtensionInput['extensionData'] extends Array - ? { - node: AppNode; - } & ExtensionDataContainer - : never; - -// @public -export type ResolvedExtensionInputs< - TInputs extends { - [name in string]: ExtensionInput; - }, -> = { - [InputName in keyof TInputs]: false extends TInputs[InputName]['config']['singleton'] - ? Array>> - : false extends TInputs[InputName]['config']['optional'] - ? Expand> - : Expand | undefined>; +export type PluginWrapperApi = { + getRootWrapper(): ComponentType<{ + children: ReactNode; + }>; + getPluginWrapper(pluginId: string): + | ComponentType<{ + children: ReactNode; + }> + | undefined; }; // @public -export type RouteFunc = ( - ...[params]: TParams extends undefined - ? readonly [] - : readonly [params: TParams] -) => string; +export const pluginWrapperApiRef: ApiRef_2< + PluginWrapperApi, + 'core.plugin-wrapper' +> & { + readonly $$type: '@backstage/ApiRef'; +}; -// @public (undocumented) -export const RouterBlueprint: ExtensionBlueprint<{ - kind: 'app-router-component'; - params: { - Component?: [error: 'Use the `component` parameter instead']; - component: (props: { children: ReactNode }) => JSX.Element | null; - }; - output: ExtensionDataRef< - (props: { children: ReactNode }) => JSX.Element | null, - 'app.router.wrapper', +// @public +export const PluginWrapperBlueprint: ExtensionBlueprint_2<{ + kind: 'plugin-wrapper'; + params: (params: { + loader: () => Promise>; + }) => ExtensionBlueprintParams_2<{ + loader: () => Promise; + }>; + output: ExtensionDataRef_2< + () => Promise, + 'core.plugin-wrapper.loader', {} >; inputs: {}; config: {}; configInput: {}; dataRefs: { - component: ConfigurableExtensionDataRef< - (props: { children: ReactNode }) => JSX.Element | null, - 'app.router.wrapper', + wrapper: ConfigurableExtensionDataRef_2< + () => Promise, + 'core.plugin-wrapper.loader', {} >; }; }>; +// @public +export type PluginWrapperDefinition = { + useWrapperValue?: () => TValue; + component: ComponentType<{ + children: ReactNode; + value: TValue; + }>; +}; + +// @public (undocumented) +export type PortableSchema = { + parse: (input: TInput) => TOutput; + schema: () => { + schema: JsonObject; + }; +}; + +// @public +export type ProfileInfo = { + email?: string; + displayName?: string; + picture?: string; +}; + +// @public +export type ProfileInfoApi = { + getProfile(options?: AuthRequestOptions): Promise; +}; + +// @public (undocumented) +export const Progress: { + (props: ProgressProps): JSX.Element | null; + ref: SwappableComponentRef_2; +}; + +// @public (undocumented) +export type ProgressProps = {}; + +// @public +export type RouteFunc = ( + ...input: TParams extends undefined ? readonly [] : readonly [params: TParams] +) => string; + // @public export interface RouteRef< TParams extends AnyRouteRefParams = AnyRouteRefParams, @@ -1734,41 +2465,108 @@ export interface RouteResolutionApi { } // @public -export const routeResolutionApiRef: ApiRef; - -export { SessionApi }; - -export { SessionState }; +export const routeResolutionApiRef: ApiRef_2< + RouteResolutionApi, + 'core.route-resolution' +> & { + readonly $$type: '@backstage/ApiRef'; +}; // @public -export const SignInPageBlueprint: ExtensionBlueprint<{ - kind: 'sign-in-page'; +export type SessionApi = { + signIn(): Promise; + signOut(): Promise; + sessionState$(): Observable; +}; + +// @public +export const SessionState: { + readonly SignedIn: 'SignedIn'; + readonly SignedOut: 'SignedOut'; +}; + +// @public (undocumented) +export type SessionState = (typeof SessionState)[keyof typeof SessionState]; + +// @public (undocumented) +export namespace SessionState { + // (undocumented) + export type SignedIn = typeof SessionState.SignedIn; + // (undocumented) + export type SignedOut = typeof SessionState.SignedOut; +} + +export { StandardSchemaV1 }; + +// @public +export interface StorageApi { + forBucket(name: string): StorageApi; + observe$( + key: string, + ): Observable>; + remove(key: string): Promise; + set(key: string, data: T): Promise; + snapshot(key: string): StorageValueSnapshot; +} + +// @public +export const storageApiRef: ApiRef_2 & { + readonly $$type: '@backstage/ApiRef'; +}; + +// @public +export type StorageValueSnapshot = + | { + key: string; + presence: 'unknown' | 'absent'; + value?: undefined; + } + | { + key: string; + presence: 'present'; + value: TValue; + }; + +// @public +export const SubPageBlueprint: ExtensionBlueprint_2<{ + kind: 'sub-page'; params: { - loader: () => Promise>; + path: string; + title: string; + icon?: IconElement; + loader: () => Promise; + routeRef?: RouteRef; }; - output: ExtensionDataRef< - ComponentType, - 'core.sign-in-page.component', - {} - >; + output: + | ExtensionDataRef_2 + | ExtensionDataRef_2< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + > + | ExtensionDataRef_2 + | ExtensionDataRef_2 + | ExtensionDataRef_2< + IconElement, + 'core.icon', + { + optional: true; + } + >; inputs: {}; - config: {}; - configInput: {}; - dataRefs: { - component: ConfigurableExtensionDataRef< - ComponentType, - 'core.sign-in-page.component', - {} - >; + config: { + path: string | undefined; + title: string | undefined; }; + configInput: { + path?: string | undefined; + title?: string | undefined; + }; + dataRefs: never; }>; -export { StorageApi }; - -export { storageApiRef }; - -export { StorageValueSnapshot }; - // @public export interface SubRouteRef< TParams extends AnyRouteRefParams = AnyRouteRefParams, @@ -1781,65 +2579,6 @@ export interface SubRouteRef< readonly T: TParams; } -// @public -export const SwappableComponentBlueprint: ExtensionBlueprint<{ - kind: 'component'; - params: >(params: { - component: Ref extends SwappableComponentRef< - any, - infer IExternalComponentProps - > - ? { - ref: Ref; - } & ((props: IExternalComponentProps) => JSX.Element | null) - : never; - loader: Ref extends SwappableComponentRef - ? - | (() => (props: IInnerComponentProps) => JSX.Element | null) - | (() => Promise<(props: IInnerComponentProps) => JSX.Element | null>) - : never; - }) => ExtensionBlueprintParams<{ - component: Ref extends SwappableComponentRef< - any, - infer IExternalComponentProps - > - ? { - ref: Ref; - } & ((props: IExternalComponentProps) => JSX.Element | null) - : never; - loader: Ref extends SwappableComponentRef - ? - | (() => (props: IInnerComponentProps) => JSX.Element | null) - | (() => Promise<(props: IInnerComponentProps) => JSX.Element | null>) - : never; - }>; - output: ExtensionDataRef< - { - ref: SwappableComponentRef; - loader: - | (() => (props: {}) => JSX.Element | null) - | (() => Promise<(props: {}) => JSX.Element | null>); - }, - 'core.swappableComponent', - {} - >; - inputs: {}; - config: {}; - configInput: {}; - dataRefs: { - component: ConfigurableExtensionDataRef< - { - ref: SwappableComponentRef; - loader: - | (() => (props: {}) => JSX.Element | null) - | (() => Promise<(props: {}) => JSX.Element | null>); - }, - 'core.swappableComponent', - {} - >; - }; -}>; - // @public (undocumented) export type SwappableComponentRef< TInnerComponentProps extends {} = {}, @@ -1863,80 +2602,227 @@ export interface SwappableComponentsApi { } // @public -export const swappableComponentsApiRef: ApiRef; +export const swappableComponentsApiRef: ApiRef_2< + SwappableComponentsApi, + 'core.swappable-components' +> & { + readonly $$type: '@backstage/ApiRef'; +}; // @public -export const ThemeBlueprint: ExtensionBlueprint<{ - kind: 'theme'; - params: { - theme: AppTheme; - }; - output: ExtensionDataRef; - inputs: {}; - config: {}; - configInput: {}; - dataRefs: { - theme: ConfigurableExtensionDataRef; - }; -}>; +export type ToastApi = { + post(toast: ToastApiMessage): ToastApiPostResult; +}; // @public -export const TranslationBlueprint: ExtensionBlueprint<{ - kind: 'translation'; - params: { - resource: TranslationResource | TranslationMessages; - }; - output: ExtensionDataRef< - | TranslationResource - | TranslationMessages< - string, - { - [x: string]: string; - }, - boolean - >, - 'core.translation.translation', - {} - >; - inputs: {}; - config: {}; - configInput: {}; - dataRefs: { - translation: ConfigurableExtensionDataRef< - | TranslationResource - | TranslationMessages< - string, - { - [x: string]: string; - }, - boolean - >, - 'core.translation.translation', - {} - >; - }; -}>; +export type ToastApiMessage = { + title: ReactNode; + description?: ReactNode; + status?: 'neutral' | 'info' | 'success' | 'warning' | 'danger'; + links?: ToastApiMessageLink[]; + timeout?: number; +}; -export { TranslationMessages }; +// @public +export type ToastApiMessageLink = { + label: string; + href: string; +}; -export { TranslationMessagesOptions }; +// @public +export type ToastApiPostResult = { + close(): void; +}; -export { TranslationRef }; +// @public +export const toastApiRef: ApiRef; -export { TranslationRefOptions }; +// @public (undocumented) +export type TranslationApi = { + getTranslation< + TMessages extends { + [key in string]: string; + }, + >( + translationRef: TranslationRef, + ): TranslationSnapshot; + translation$< + TMessages extends { + [key in string]: string; + }, + >( + translationRef: TranslationRef, + ): Observable>; +}; -export { TranslationResource }; +// @public (undocumented) +export const translationApiRef: ApiRef_2 & { + readonly $$type: '@backstage/ApiRef'; +}; -export { TranslationResourceOptions }; +// @public (undocumented) +export type TranslationFunction< + TMessages extends { + [key in string]: string; + }, +> = CollapsedMessages extends infer IMessages extends { + [key in string]: string; +} + ? { + ( + key: TKey, + ...input: TranslationFunctionOptions< + NestedMessageKeys, + PluralKeys, + IMessages, + string + > + ): IMessages[TKey]; + ( + key: TKey, + ...input: TranslationFunctionOptions< + NestedMessageKeys, + PluralKeys, + IMessages, + string | JSX_2.Element + > + ): JSX_2.Element; + } + : never; -export { TypesToApiRefs }; +// @public +export interface TranslationMessages< + TId extends string = string, + TMessages extends { + [key in string]: string; + } = { + [key in string]: string; + }, + TFull extends boolean = boolean, +> { + // (undocumented) + $$type: '@backstage/TranslationMessages'; + full: TFull; + id: TId; + messages: TMessages; +} + +// @public +export interface TranslationMessagesOptions< + TId extends string, + TMessages extends { + [key in string]: string; + }, + TFull extends boolean, +> { + // (undocumented) + full?: TFull; + // (undocumented) + messages: false extends TFull + ? { + [key in keyof TMessages]?: string | null; + } + : { + [key in keyof TMessages]: string | null; + }; + // (undocumented) + ref: TranslationRef; +} + +// @public (undocumented) +export interface TranslationRef< + TId extends string = string, + TMessages extends { + [key in string]: string; + } = { + [key in string]: string; + }, +> { + // (undocumented) + $$type: '@backstage/TranslationRef'; + // (undocumented) + id: TId; + // (undocumented) + T: TMessages; +} + +// @public (undocumented) +export interface TranslationRefOptions< + TId extends string, + TNestedMessages extends AnyNestedMessages, + TTranslations extends { + [language in string]: () => Promise<{ + default: { + [key in keyof FlattenedMessages]: string | null; + }; + }>; + }, +> { + // (undocumented) + id: TId; + // (undocumented) + messages: TNestedMessages; + // (undocumented) + translations?: TTranslations; +} + +// @public (undocumented) +export interface TranslationResource { + // (undocumented) + $$type: '@backstage/TranslationResource'; + // (undocumented) + id: TId; +} + +// @public (undocumented) +export interface TranslationResourceOptions< + TId extends string, + TMessages extends { + [key in string]: string; + }, + TTranslations extends { + [language in string]: () => Promise<{ + default: + | TranslationMessages + | { + [key in keyof TMessages]: string | null; + }; + }>; + }, +> { + // (undocumented) + ref: TranslationRef; + // (undocumented) + translations: TTranslations; +} + +// @public (undocumented) +export type TranslationSnapshot< + TMessages extends { + [key in string]: string; + }, +> = + | { + ready: false; + } + | { + ready: true; + t: TranslationFunction; + }; + +// @public +export type TypesToApiRefs = { + [key in keyof T]: ApiRef; +}; // @public export function useAnalytics(): AnalyticsTracker; -export { useApi }; +// @public +export function useApi(apiRef: ApiRef): T; -export { useApiHolder }; +// @public +export function useApiHolder(): ApiHolder; // @public export function useAppNode(): AppNode | undefined; @@ -1954,9 +2840,32 @@ export function useRouteRefParams( _routeRef: RouteRef | SubRouteRef, ): Params; -export { useTranslationRef }; +// @public (undocumented) +export const useTranslationRef: ( + translationRef: TranslationRef, +) => { + t: TranslationFunction; +}; -export { vmwareCloudAuthApiRef }; +// @public +export const vmwareCloudAuthApiRef: ApiRef_2< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi, + 'core.auth.vmware-cloud' +> & { + readonly $$type: '@backstage/ApiRef'; +}; -export { withApis }; +// @public @deprecated +export function withApis( + apis: TypesToApiRefs, +): ( + WrappedComponent: ComponentType, +) => { + (props: PropsWithChildren>): JSX_3.Element; + displayName: string; +}; ``` diff --git a/packages/frontend-plugin-api/src/alpha.ts b/packages/frontend-plugin-api/src/alpha.ts new file mode 100644 index 0000000000..d2118994fb --- /dev/null +++ b/packages/frontend-plugin-api/src/alpha.ts @@ -0,0 +1,63 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// These exports are now available from the main entry point and are +// re-exported here only for backwards compatibility. +export { + PluginWrapperBlueprint, + type PluginWrapperDefinition, +} from './blueprints/PluginWrapperBlueprint'; +export type { + ConfigurableExtensionDataRef, + Extension, + ExtensionAttachTo, + ExtensionDefinition, + ExtensionDefinitionParameters, + ExtensionBlueprintDefineParams, + ExtensionBlueprint, + ExtensionBlueprintParameters, + ExtensionBlueprintParams, + ExtensionDataContainer, + ExtensionDataRef, + ExtensionDataValue, + ExtensionDefinitionAttachTo, + ExtensionInput, + FrontendPlugin, + OverridableExtensionDefinition, +} from './wiring'; +export type { + ApiHolder, + ApiRef, + AppNode, + AppNodeEdges, + AppNodeInstance, + AppNodeSpec, + AppTree, +} from './apis'; +export type { PortableSchema, StandardSchemaV1 } from './schema'; +export type { + AnyRouteRefParams, + RouteRef, + SubRouteRef, + ExternalRouteRef, +} from './routing'; +export type { IconElement } from './icons'; +export type { FrontendPluginInfo } from './wiring'; +export { createExtensionBlueprintParams } from './wiring'; +export { + type PluginWrapperApi, + pluginWrapperApiRef, +} from './apis/definitions/PluginWrapperApi'; diff --git a/packages/frontend-plugin-api/src/analytics/useAnalytics.test.tsx b/packages/frontend-plugin-api/src/analytics/useAnalytics.test.tsx index 547c78a7fc..18fe64af30 100644 --- a/packages/frontend-plugin-api/src/analytics/useAnalytics.test.tsx +++ b/packages/frontend-plugin-api/src/analytics/useAnalytics.test.tsx @@ -16,7 +16,7 @@ import { renderHook } from '@testing-library/react'; import { useAnalytics } from './useAnalytics'; -import { analyticsApiRef } from '@backstage/core-plugin-api'; +import { analyticsApiRef } from '../apis/definitions/AnalyticsApi'; import { TestApiProvider } from '@backstage/test-utils'; describe('useAnalytics', () => { diff --git a/packages/frontend-plugin-api/src/analytics/useAnalytics.tsx b/packages/frontend-plugin-api/src/analytics/useAnalytics.tsx index 397906d1d1..5c5f2ad60a 100644 --- a/packages/frontend-plugin-api/src/analytics/useAnalytics.tsx +++ b/packages/frontend-plugin-api/src/analytics/useAnalytics.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { useApi } from '@backstage/core-plugin-api'; +import { useApi } from '../apis/system'; import { useAnalyticsContext } from './AnalyticsContext'; import { analyticsApiRef, AnalyticsTracker, AnalyticsApi } from '../apis'; import { useRef } from 'react'; diff --git a/packages/frontend-plugin-api/src/apis/definitions/AlertApi.ts b/packages/frontend-plugin-api/src/apis/definitions/AlertApi.ts index 649a930b50..88de012707 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/AlertApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/AlertApi.ts @@ -14,8 +14,70 @@ * limitations under the License. */ -export { - type AlertApi, - type AlertMessage, - alertApiRef, -} from '@backstage/core-plugin-api'; +import { createApiRef } from '../system'; +import { Observable } from '@backstage/types'; + +/** + * Message handled by the {@link AlertApi}. + * + * @public + * @deprecated Use {@link ToastApiMessage} from {@link ToastApi} instead. AlertApi will be removed in a future release. + * + * Migration guide: + * - `message` becomes `title` + * - `severity: 'error'` becomes `status: 'danger'` + * - `severity: 'success' | 'info' | 'warning'` becomes `status: 'success' | 'info' | 'warning'` + * - `display: 'transient'` becomes `timeout: 5000` (or custom milliseconds) + * - `display: 'permanent'` means omitting `timeout` + */ +export type AlertMessage = { + message: string; + // Severity will default to success since that is what material ui defaults the value to. + severity?: 'success' | 'info' | 'warning' | 'error'; + display?: 'permanent' | 'transient'; +}; + +/** + * The alert API is used to report alerts to the app, and display them to the user. + * + * @public + * @deprecated Use {@link ToastApi} instead. AlertApi will be removed in a future release. + * + * ToastApi provides richer notification features including: + * - Title and optional description + * - Action links + * - Custom icons + * - Per-toast timeout control + * - Programmatic dismiss via returned key + * + * @example + * ```typescript + * // Before (AlertApi) + * alertApi.post({ message: 'Saved!', severity: 'success', display: 'transient' }); + * + * // After (ToastApi) + * toastApi.post({ title: 'Saved!', status: 'success', timeout: 5000 }); + * ``` + */ +export type AlertApi = { + /** + * Post an alert for handling by the application. + */ + post(alert: AlertMessage): void; + + /** + * Observe alerts posted by other parts of the application. + */ + alert$(): Observable; +}; + +/** + * The {@link ApiRef} of {@link AlertApi}. + * + * @public + * @deprecated Use {@link toastApiRef} instead. AlertApi will be removed in a future release. + */ +export const alertApiRef = createApiRef().with({ + id: 'core.alert', + pluginId: 'app', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/AnalyticsApi.ts b/packages/frontend-plugin-api/src/apis/definitions/AnalyticsApi.ts index 2040966464..bd5d9a4394 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/AnalyticsApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/AnalyticsApi.ts @@ -14,9 +14,8 @@ * limitations under the License. */ -import { ApiRef, createApiRef } from '@backstage/core-plugin-api'; +import { createApiRef } from '../system'; import { AnalyticsContextValue } from '../../analytics/types'; -import type { AnalyticsImplementationBlueprint } from '../../blueprints/'; /** * Represents an event worth tracking in an analytics system that could inform @@ -152,6 +151,7 @@ export type AnalyticsApi = { * * @public */ -export const analyticsApiRef: ApiRef = createApiRef({ +export const analyticsApiRef = createApiRef().with({ id: 'core.analytics', + pluginId: 'app', }); diff --git a/packages/frontend-plugin-api/src/apis/definitions/AppLanguageApi.ts b/packages/frontend-plugin-api/src/apis/definitions/AppLanguageApi.ts index 37ffcfa820..1b1b7437b5 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/AppLanguageApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/AppLanguageApi.ts @@ -14,7 +14,24 @@ * limitations under the License. */ -export { - type AppLanguageApi, - appLanguageApiRef, -} from '@backstage/core-plugin-api/alpha'; +import { createApiRef } from '../system'; +import { Observable } from '@backstage/types'; + +/** @public */ +export type AppLanguageApi = { + getAvailableLanguages(): { languages: string[] }; + + setLanguage(language?: string): void; + + getLanguage(): { language: string }; + + language$(): Observable<{ language: string }>; +}; + +/** + * @public + */ +export const appLanguageApiRef = createApiRef().with({ + id: 'core.applanguage', + pluginId: 'app', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/AppThemeApi.ts b/packages/frontend-plugin-api/src/apis/definitions/AppThemeApi.ts index ce62cd72c3..66669e8cab 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/AppThemeApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/AppThemeApi.ts @@ -14,8 +14,75 @@ * limitations under the License. */ -export { - type AppTheme, - type AppThemeApi, - appThemeApiRef, -} from '@backstage/core-plugin-api'; +import { ReactNode } from 'react'; +import { createApiRef } from '../system'; +import { Observable } from '@backstage/types'; + +/** + * Describes a theme provided by the app. + * + * @public + */ +export type AppTheme = { + /** + * ID used to remember theme selections. + */ + id: string; + + /** + * Title of the theme + */ + title: string; + + /** + * Theme variant + */ + variant: 'light' | 'dark'; + + /** + * An Icon for the theme mode setting. + */ + icon?: React.ReactElement; + + Provider(props: { children: ReactNode }): JSX.Element | null; +}; + +/** + * The AppThemeApi gives access to the current app theme, and allows switching + * to other options that have been registered as a part of the App. + * + * @public + */ +export type AppThemeApi = { + /** + * Get a list of available themes. + */ + getInstalledThemes(): AppTheme[]; + + /** + * Observe the currently selected theme. A value of undefined means no specific theme has been selected. + */ + activeThemeId$(): Observable; + + /** + * Get the current theme ID. Returns undefined if no specific theme is selected. + */ + getActiveThemeId(): string | undefined; + + /** + * Set a specific theme to use in the app, overriding the default theme selection. + * + * Clear the selection by passing in undefined. + */ + setActiveThemeId(themeId?: string): void; +}; + +/** + * The {@link ApiRef} of {@link AppThemeApi}. + * + * @public + */ +export const appThemeApiRef = createApiRef().with({ + id: 'core.apptheme', + pluginId: 'app', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/AppTreeApi.ts b/packages/frontend-plugin-api/src/apis/definitions/AppTreeApi.ts index 6c318d782a..901216b554 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/AppTreeApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/AppTreeApi.ts @@ -14,9 +14,10 @@ * limitations under the License. */ -import { createApiRef } from '@backstage/core-plugin-api'; +import { createApiRef } from '../system'; import { FrontendPlugin, Extension, ExtensionDataRef } from '../../wiring'; import { ExtensionAttachTo } from '../../wiring/resolveExtensionDefinition'; +import { FilterPredicate } from '@backstage/filter-predicates'; /** * The specification for this {@link AppNode} in the {@link AppTree}. @@ -32,6 +33,7 @@ export interface AppNodeSpec { readonly attachTo: ExtensionAttachTo; readonly extension: Extension; readonly disabled: boolean; + readonly if?: FilterPredicate; readonly config?: unknown; readonly plugin: FrontendPlugin; } @@ -117,4 +119,7 @@ export interface AppTreeApi { * * @public */ -export const appTreeApiRef = createApiRef({ id: 'core.app-tree' }); +export const appTreeApiRef = createApiRef().with({ + id: 'core.app-tree', + pluginId: 'app', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/ConfigApi.ts b/packages/frontend-plugin-api/src/apis/definitions/ConfigApi.ts index 968a2843ba..8cc1638d90 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/ConfigApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/ConfigApi.ts @@ -13,5 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { createApiRef } from '../system'; +import type { Config } from '@backstage/config'; -export { type ConfigApi, configApiRef } from '@backstage/core-plugin-api'; +/** + * The Config API is used to provide a mechanism to access the + * runtime configuration of the system. + * + * @public + */ +export type ConfigApi = Config; + +/** + * The {@link ApiRef} of {@link ConfigApi}. + * + * @public + */ +export const configApiRef = createApiRef().with({ + id: 'core.config', + pluginId: 'app', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/DialogApi.ts b/packages/frontend-plugin-api/src/apis/definitions/DialogApi.ts index 801ec88c69..aa8d4da9fc 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/DialogApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/DialogApi.ts @@ -14,44 +14,47 @@ * limitations under the License. */ -import { createApiRef } from '@backstage/core-plugin-api'; +import { createApiRef } from '../system'; /** * A handle for an open dialog that can be used to interact with it. * * @remarks * - * Dialogs can be opened using either {@link DialogApi.show} or {@link DialogApi.showModal}. + * Dialogs are opened using {@link DialogApi.open}. * * @public */ export interface DialogApiDialog { /** - * Closes the dialog with that provided result. + * Closes the dialog with the provided result. * * @remarks * - * If the dialog is a modal dialog a result must always be provided. If it's a regular dialog then passing a result is optional. + * Whether a result is required depends on the `TResult` type parameter + * chosen when the dialog was opened. If the type includes `undefined`, + * calling `close()` without a result is allowed. */ close( ...args: undefined extends TResult ? [result?: TResult] : [result: TResult] ): void; /** - * Replaces the content of the dialog with the provided element or component, causing it to be rerenedered. + * Replaces the rendered dialog with the provided element or component. + * + * @remarks + * + * Just like the element or component passed to {@link DialogApi.open}, the + * caller is responsible for providing the full dialog including all chrome. */ update( elementOrComponent: - | React.JSX.Element + | JSX.Element | ((props: { dialog: DialogApiDialog }) => JSX.Element), ): void; /** * Wait until the dialog is closed and return the result. - * - * @remarks - * - * If the dialog is a modal dialog a result will always be returned. If it's a regular dialog then the result may be `undefined`. */ result(): Promise; } @@ -63,51 +66,67 @@ export interface DialogApiDialog { */ export interface DialogApi { /** - * Opens a modal dialog and returns a handle to it. + * Opens a dialog and returns a handle to it. * * @remarks * - * This dialog can be closed by calling the `close` method on the returned handle, optionally providing a result. - * The dialog can also be closed by the user by clicking the backdrop or pressing the escape key. - * - * If the dialog is closed without a result, the result will be `undefined`. + * The provided element or component is rendered as-is in the app's React tree. + * It is the caller's responsibility to provide all dialog chrome, such as an + * overlay, backdrop, and dialog surface. This makes the method agnostic to the + * design library used for the dialog. * * @example * - * ### Example with inline dialog content + * ### Example with inline dialog element * ```tsx - * const dialog = dialogApi.show( - * - * Are you sure? - * - * - * - * - * + * const dialog = dialogApi.open( + * !isOpen && dialog.close()}> + * Are you sure? + * This action cannot be undone. + * + * + * + * + * * ); * const result = await dialog.result(); * ``` * * @example * - * ### Example with separate dialog component + * ### Example with a dialog component * ```tsx - * function CustomDialog({ dialog }: { dialog: DialogApiDialog }) { + * function ConfirmDialog({ dialog }: { dialog: DialogApiDialog }) { * return ( - * - * Are you sure? - * - * - * - * - * - * ) + * !isOpen && dialog.close()}> + * Are you sure? + * This action cannot be undone. + * + * + * + * + * + * ); * } - * const result = await dialogApi.show(CustomDialog).result(); + * const result = await dialogApi.open(ConfirmDialog).result(); * ``` * + * @param elementOrComponent - The element or component to render. If a component is provided, it will be provided with a `dialog` prop that contains the dialog handle. + */ + open( + elementOrComponent: + | JSX.Element + | ((props: { dialog: DialogApiDialog }) => JSX.Element), + ): DialogApiDialog; + + /** + * Opens a dialog with built-in dialog chrome and returns a handle to it. + * + * @deprecated Use {@link DialogApi.open} instead. The `open` method does not + * render any dialog chrome, giving the caller full control over the dialog + * presentation. This avoids focus trap conflicts across design libraries. + * * @param elementOrComponent - The element or component to render in the dialog. If a component is provided, it will be provided with a `dialog` prop that contains the dialog handle. - * @public */ show( elementOrComponent: @@ -118,48 +137,13 @@ export interface DialogApi { ): DialogApiDialog; /** - * Opens a modal dialog and returns a handle to it. + * Opens a modal dialog with built-in dialog chrome and returns a handle to it. * - * @remarks - * - * This dialog can not be closed in any other way than calling the `close` method on the returned handle and providing a result. - * - * @example - * - * ### Example with inline dialog content - * ```tsx - * const dialog = dialogApi.showModal( - * - * Are you sure? - * - * - * - * - * - * ); - * const result = await dialog.result(); - * ``` - * - * @example - * - * ### Example with separate dialog component - * ```tsx - * function CustomDialog({ dialog }: { dialog: DialogApiDialog }) { - * return ( - * - * Are you sure? - * - * - * - * - * - * ) - * } - * const result = await dialogApi.showModal(CustomDialog).result(); - * ``` + * @deprecated Use {@link DialogApi.open} instead. The `open` method does not + * render any dialog chrome, giving the caller full control over the dialog + * presentation. This avoids focus trap conflicts across design libraries. * * @param elementOrComponent - The element or component to render in the dialog. If a component is provided, it will be provided with a `dialog` prop that contains the dialog handle. - * @public */ showModal( elementOrComponent: @@ -173,6 +157,7 @@ export interface DialogApi { * * @public */ -export const dialogApiRef = createApiRef({ +export const dialogApiRef = createApiRef().with({ id: 'core.dialog', + pluginId: 'app', }); diff --git a/packages/frontend-plugin-api/src/apis/definitions/DiscoveryApi.ts b/packages/frontend-plugin-api/src/apis/definitions/DiscoveryApi.ts index 98b3f0bbf0..824174c8f6 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/DiscoveryApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/DiscoveryApi.ts @@ -13,5 +13,44 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { createApiRef } from '../system'; -export { type DiscoveryApi, discoveryApiRef } from '@backstage/core-plugin-api'; +/** + * The discovery API is used to provide a mechanism for plugins to + * discover the endpoint to use to talk to their backend counterpart. + * + * @remarks + * + * The purpose of the discovery API is to allow for many different deployment + * setups and routing methods through a central configuration, instead + * of letting each individual plugin manage that configuration. + * + * Implementations of the discovery API can be a simple as a URL pattern + * using the pluginId, but could also have overrides for individual plugins, + * or query a separate discovery service. + * + * @public + */ +export type DiscoveryApi = { + /** + * Returns the HTTP base backend URL for a given plugin, without a trailing slash. + * + * This method must always be called just before making a request, as opposed to + * fetching the URL when constructing an API client. That is to ensure that more + * flexible routing patterns can be supported. + * + * For example, asking for the URL for `auth` may return something + * like `https://backstage.example.com/api/auth` + */ + getBaseUrl(pluginId: string): Promise; +}; + +/** + * The {@link ApiRef} of {@link DiscoveryApi}. + * + * @public + */ +export const discoveryApiRef = createApiRef().with({ + id: 'core.discovery', + pluginId: 'app', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/ErrorApi.ts b/packages/frontend-plugin-api/src/apis/definitions/ErrorApi.ts index 57782a05dc..e7be718326 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/ErrorApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/ErrorApi.ts @@ -14,9 +14,79 @@ * limitations under the License. */ -export { - type ErrorApiError, - type ErrorApiErrorContext, - type ErrorApi, - errorApiRef, -} from '@backstage/core-plugin-api'; +import { createApiRef } from '../system'; +import { Observable } from '@backstage/types'; + +/** + * Mirrors the JavaScript Error class, for the purpose of + * providing documentation and optional fields. + * + * @public + */ +export type ErrorApiError = { + name: string; + message: string; + stack?: string; +}; + +/** + * Provides additional information about an error that was posted to the application. + * + * @public + */ +export type ErrorApiErrorContext = { + /** + * If set to true, this error should not be displayed to the user. + * + * Hidden errors are typically not displayed in the UI, but the ErrorApi + * implementation may still report them to error tracking services + * or other utilities that care about all errors. + * + * @defaultValue false + */ + hidden?: boolean; +}; + +/** + * The error API is used to report errors to the app, and display them to the user. + * + * @remarks + * + * Plugins can use this API as a method of displaying errors to the user, but also + * to report errors for collection by error reporting services. + * + * If an error can be displayed inline, e.g. as feedback in a form, that should be + * preferred over relying on this API to display the error. The main use of this API + * for displaying errors should be for asynchronous errors, such as a failing background process. + * + * Even if an error is displayed inline, it should still be reported through this API + * if it would be useful to collect or log it for debugging purposes, but with + * the hidden flag set. For example, an error arising from form field validation + * should probably not be reported, while a failed REST call would be useful to report. + * + * @public + */ +export type ErrorApi = { + /** + * Post an error for handling by the application. + */ + post(error: ErrorApiError, context?: ErrorApiErrorContext): void; + + /** + * Observe errors posted by other parts of the application. + */ + error$(): Observable<{ + error: ErrorApiError; + context?: ErrorApiErrorContext; + }>; +}; + +/** + * The {@link ApiRef} of {@link ErrorApi}. + * + * @public + */ +export const errorApiRef = createApiRef().with({ + id: 'core.error', + pluginId: 'app', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/FeatureFlagsApi.ts b/packages/frontend-plugin-api/src/apis/definitions/FeatureFlagsApi.ts index 820efc0a25..b7d1c4ccaa 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/FeatureFlagsApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/FeatureFlagsApi.ts @@ -13,11 +13,115 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +/* We want to maintain the same information as an enum, so we disable the redeclaration warning */ +/* eslint-disable @typescript-eslint/no-redeclare */ -export { - type FeatureFlag, - type FeatureFlagState, - type FeatureFlagsSaveOptions, - type FeatureFlagsApi, - featureFlagsApiRef, -} from '@backstage/core-plugin-api'; +import { createApiRef } from '../system'; + +/** + * Feature flag descriptor. + * + * @public + */ +export type FeatureFlag = { + name: string; + pluginId: string; + description?: string; +}; + +/** + * Enum representing the state of a feature flag (inactive/active). + * + * @public + */ +export const FeatureFlagState = { + /** + * Feature flag inactive (disabled). + */ + None: 0, + /** + * Feature flag active (enabled). + */ + Active: 1, +} as const; + +/** + * @public + */ +export type FeatureFlagState = + (typeof FeatureFlagState)[keyof typeof FeatureFlagState]; + +/** + * @public + */ +export namespace FeatureFlagState { + export type None = typeof FeatureFlagState.None; + export type Active = typeof FeatureFlagState.Active; +} + +/** + * Options to use when saving feature flags. + * + * @public + */ +export type FeatureFlagsSaveOptions = { + /** + * The new feature flag states to save. + */ + states: Record; + + /** + * Whether the saves states should be merged into the existing ones, or replace them. + * + * Defaults to false. + */ + merge?: boolean; +}; + +/** + * The feature flags API is used to toggle functionality to users across plugins and Backstage. + * + * @remarks + * + * Plugins can use this API to register feature flags that they have available + * for users to enable/disable, and this API will centralize the current user's + * state of which feature flags they would like to enable. + * + * This is ideal for Backstage plugins, as well as your own App, to trial incomplete + * or unstable upcoming features. Although there will be a common interface for users + * to enable and disable feature flags, this API acts as another way to enable/disable. + * + * @public + */ +export interface FeatureFlagsApi { + /** + * Registers a new feature flag. Once a feature flag has been registered it + * can be toggled by users, and read back to enable or disable features. + */ + registerFlag(flag: FeatureFlag): void; + + /** + * Get a list of all registered flags. + */ + getRegisteredFlags(): FeatureFlag[]; + + /** + * Whether the feature flag with the given name is currently activated for the user. + */ + isActive(name: string): boolean; + + /** + * Save the user's choice of feature flag states. + */ + save(options: FeatureFlagsSaveOptions): void; +} + +/** + * The {@link ApiRef} of {@link FeatureFlagsApi}. + * + * @public + */ +export const featureFlagsApiRef = createApiRef().with({ + id: 'core.featureflags', + pluginId: 'app', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/FetchApi.ts b/packages/frontend-plugin-api/src/apis/definitions/FetchApi.ts index bcc9bc6a91..5299878c34 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/FetchApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/FetchApi.ts @@ -14,4 +14,39 @@ * limitations under the License. */ -export { type FetchApi, fetchApiRef } from '@backstage/core-plugin-api'; +import { createApiRef } from '../system'; + +/** + * A wrapper for the fetch API, that has additional behaviors such as the + * ability to automatically inject auth information where necessary. + * + * @public + */ +export type FetchApi = { + /** + * The `fetch` implementation. + */ + fetch: typeof fetch; +}; + +/** + * The {@link ApiRef} of {@link FetchApi}. + * + * @remarks + * + * This is a wrapper for the fetch API, that has additional behaviors such as + * the ability to automatically inject auth information where necessary. + * + * Note that the default behavior of this API (unless overridden by your org), + * is to require that the user is already signed in so that it has auth + * information to inject. Therefore, using the default implementation of this + * utility API e.g. on the `SignInPage` or similar, would cause issues. In + * special circumstances like those, you can use the regular system `fetch` + * instead. + * + * @public + */ +export const fetchApiRef = createApiRef().with({ + id: 'core.fetch', + pluginId: 'app', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/IconsApi.ts b/packages/frontend-plugin-api/src/apis/definitions/IconsApi.ts index aa5b7f4fac..a54ae7f3b1 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/IconsApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/IconsApi.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { createApiRef } from '@backstage/core-plugin-api'; -import { IconComponent } from '../../icons'; +import { createApiRef } from '../system'; +import { IconComponent, IconElement } from '../../icons'; /** * API for accessing app icons. @@ -23,6 +23,14 @@ import { IconComponent } from '../../icons'; * @public */ export interface IconsApi { + /** + * Look up an icon element by key. + */ + icon(key: string): IconElement | undefined; + + /** + * @deprecated Use {@link IconsApi.icon} instead. + */ getIcon(key: string): IconComponent | undefined; listIconKeys(): string[]; @@ -33,6 +41,7 @@ export interface IconsApi { * * @public */ -export const iconsApiRef = createApiRef({ +export const iconsApiRef = createApiRef().with({ id: 'core.icons', + pluginId: 'app', }); diff --git a/packages/frontend-plugin-api/src/apis/definitions/IdentityApi.ts b/packages/frontend-plugin-api/src/apis/definitions/IdentityApi.ts index 03503a8fba..70b6ebf56a 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/IdentityApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/IdentityApi.ts @@ -13,5 +13,45 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { createApiRef } from '../system'; +import { BackstageUserIdentity, ProfileInfo } from './auth'; -export { type IdentityApi, identityApiRef } from '@backstage/core-plugin-api'; +/** + * The Identity API used to identify and get information about the signed in user. + * + * @public + */ +export type IdentityApi = { + /** + * The profile of the signed in user. + */ + getProfileInfo(): Promise; + + /** + * User identity information within Backstage. + */ + getBackstageIdentity(): Promise; + + /** + * Provides credentials in the form of a token which proves the identity of the signed in user. + * + * The token will be undefined if the signed in user does not have a verified + * identity, such as a demo user or mocked user for e2e tests. + */ + getCredentials(): Promise<{ token?: string }>; + + /** + * Sign out the current user + */ + signOut(): Promise; +}; + +/** + * The {@link ApiRef} of {@link IdentityApi}. + * + * @public + */ +export const identityApiRef = createApiRef().with({ + id: 'core.identity', + pluginId: 'app', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/OAuthRequestApi.ts b/packages/frontend-plugin-api/src/apis/definitions/OAuthRequestApi.ts index ff7a0f2852..d94ab44d15 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/OAuthRequestApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/OAuthRequestApi.ts @@ -14,10 +14,119 @@ * limitations under the License. */ -export { - type OAuthRequesterOptions, - type OAuthRequester, - type PendingOAuthRequest, - type OAuthRequestApi, - oauthRequestApiRef, -} from '@backstage/core-plugin-api'; +import { Observable } from '@backstage/types'; +import { createApiRef } from '../system'; +import { AuthProviderInfo } from './auth'; + +/** + * Describes how to handle auth requests. Both how to show them to the user, and what to do when + * the user accesses the auth request. + * + * @public + */ +export type OAuthRequesterOptions = { + /** + * Information about the auth provider, which will be forwarded to auth requests. + */ + provider: AuthProviderInfo; + + /** + * Implementation of the auth flow, which will be called synchronously when + * trigger() is called on an auth requests. + */ + onAuthRequest(scopes: Set): Promise; +}; + +/** + * Function used to trigger new auth requests for a set of scopes. + * + * @remarks + * + * The returned promise will resolve to the same value returned by the onAuthRequest in the + * {@link OAuthRequesterOptions}. Or rejected, if the request is rejected. + * + * This function can be called multiple times before the promise resolves. All calls + * will be merged into one request, and the scopes forwarded to the onAuthRequest will be the + * union of all requested scopes. + * + * @public + */ +export type OAuthRequester = ( + scopes: Set, +) => Promise; + +/** + * An pending auth request for a single auth provider. The request will remain in this pending + * state until either reject() or trigger() is called. + * + * @remarks + * + * Any new requests for the same provider are merged into the existing pending request, meaning + * there will only ever be a single pending request for a given provider. + * + * @public + */ +export type PendingOAuthRequest = { + /** + * Information about the auth provider, as given in the AuthRequesterOptions + */ + provider: AuthProviderInfo; + + /** + * Rejects the request, causing all pending AuthRequester calls to fail with "RejectedError". + */ + reject(): void; + + /** + * Trigger the auth request to continue the auth flow, by for example showing a popup. + * + * Synchronously calls onAuthRequest with all scope currently in the request. + */ + trigger(): Promise; +}; + +/** + * Provides helpers for implemented OAuth login flows within Backstage. + * + * @public + */ +export type OAuthRequestApi = { + /** + * A utility for showing login popups or similar things, and merging together multiple requests for + * different scopes into one request that includes all scopes. + * + * The passed in options provide information about the login provider, and how to handle auth requests. + * + * The returned AuthRequester function is used to request login with new scopes. These requests + * are merged together and forwarded to the auth handler, as soon as a consumer of auth requests + * triggers an auth flow. + * + * See AuthRequesterOptions, AuthRequester, and handleAuthRequests for more info. + */ + createAuthRequester( + options: OAuthRequesterOptions, + ): OAuthRequester; + + /** + * Observers pending auth requests. The returned observable will emit all + * current active auth request, at most one for each created auth requester. + * + * Each request has its own info about the login provider, forwarded from the auth requester options. + * + * Depending on user interaction, the request should either be rejected, or used to trigger the auth handler. + * If the request is rejected, all pending AuthRequester calls will fail with a "RejectedError". + * If a auth is triggered, and the auth handler resolves successfully, then all currently pending + * AuthRequester calls will resolve to the value returned by the onAuthRequest call. + */ + authRequest$(): Observable; +}; + +/** + * The {@link ApiRef} of {@link OAuthRequestApi}. + * + * @public + */ +export const oauthRequestApiRef = createApiRef().with({ + id: 'core.oauthrequest', + pluginId: 'app', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/PluginHeaderActionsApi.ts b/packages/frontend-plugin-api/src/apis/definitions/PluginHeaderActionsApi.ts new file mode 100644 index 0000000000..9e2d1ca0fd --- /dev/null +++ b/packages/frontend-plugin-api/src/apis/definitions/PluginHeaderActionsApi.ts @@ -0,0 +1,47 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { JSX } from 'react'; +import { createApiRef } from '../system'; + +/** + * API for retrieving plugin-scoped header actions. + * + * @remarks + * + * Header actions are provided via + * {@link @backstage/frontend-plugin-api#PluginHeaderActionBlueprint} + * and automatically scoped to the providing plugin. + * + * @public + */ +export type PluginHeaderActionsApi = { + /** + * Returns the header actions for a given plugin. + */ + getPluginHeaderActions(pluginId: string): Array; +}; + +/** + * The `ApiRef` of {@link PluginHeaderActionsApi}. + * + * @public + */ +export const pluginHeaderActionsApiRef = + createApiRef().with({ + id: 'core.plugin-header-actions', + pluginId: 'app', + }); diff --git a/packages/frontend-plugin-api/src/apis/definitions/PluginWrapperApi.ts b/packages/frontend-plugin-api/src/apis/definitions/PluginWrapperApi.ts new file mode 100644 index 0000000000..7965b8ccb4 --- /dev/null +++ b/packages/frontend-plugin-api/src/apis/definitions/PluginWrapperApi.ts @@ -0,0 +1,53 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentType, ReactNode } from 'react'; +import { createApiRef } from '../system'; + +/** + * The Plugin Wrapper API allows plugins to wrap their extensions with + * providers. This API is only intended for internal use by the Backstage + * frontend system. To provide contexts to plugin components, use + * `ExtensionBoundary` instead. + * + * @public + */ +export type PluginWrapperApi = { + /** + * Returns the root wrapper that manages the global plugin state across + * plugin wrapper instances. + */ + getRootWrapper(): ComponentType<{ children: ReactNode }>; + + /** + * Returns a wrapper component for a specific plugin, or undefined if no + * wrappers exist. Do not use this API directly, instead use + * `ExtensionBoundary` to wrap your plugin components if needed. + */ + getPluginWrapper( + pluginId: string, + ): ComponentType<{ children: ReactNode }> | undefined; +}; + +/** + * The API reference of {@link PluginWrapperApi}. + * + * @public + */ +export const pluginWrapperApiRef = createApiRef().with({ + id: 'core.plugin-wrapper', + pluginId: 'app', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/RouteResolutionApi.ts b/packages/frontend-plugin-api/src/apis/definitions/RouteResolutionApi.ts index 6ae25a814e..9dea5d7bc2 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/RouteResolutionApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/RouteResolutionApi.ts @@ -20,7 +20,7 @@ import { SubRouteRef, ExternalRouteRef, } from '../../routing'; -import { createApiRef } from '@backstage/core-plugin-api'; +import { createApiRef } from '../system'; /** * TS magic for handling route parameters. @@ -65,6 +65,7 @@ export interface RouteResolutionApi { * * @public */ -export const routeResolutionApiRef = createApiRef({ +export const routeResolutionApiRef = createApiRef().with({ id: 'core.route-resolution', + pluginId: 'app', }); diff --git a/packages/frontend-plugin-api/src/apis/definitions/StorageApi.ts b/packages/frontend-plugin-api/src/apis/definitions/StorageApi.ts index 2309d2414c..1624f8402c 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/StorageApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/StorageApi.ts @@ -14,8 +14,98 @@ * limitations under the License. */ -export { - type StorageValueSnapshot, - type StorageApi, - storageApiRef, -} from '@backstage/core-plugin-api'; +import { createApiRef } from '../system'; +import { JsonValue, Observable } from '@backstage/types'; + +/** + * A snapshot in time of the current known value of a storage key. + * + * @public + */ +export type StorageValueSnapshot = + | { + key: string; + presence: 'unknown' | 'absent'; + value?: undefined; + } + | { + key: string; + presence: 'present'; + value: TValue; + }; + +/** + * Provides a key-value persistence API. + * + * @public + */ +export interface StorageApi { + /** + * Create a bucket to store data in. + * + * @param name - Namespace for the storage to be stored under, + * will inherit previous namespaces too + */ + forBucket(name: string): StorageApi; + + /** + * Remove persistent data. + * + * @param key - Unique key associated with the data. + */ + remove(key: string): Promise; + + /** + * Save persistent data, and emit messages to anyone that is using + * {@link StorageApi.observe$} for this key. + * + * @param key - Unique key associated with the data. + * @param data - The data to be stored under the key. + */ + set(key: string, data: T): Promise; + + /** + * Observe the value over time for a particular key in the current bucket. + * + * @remarks + * + * The observable will only emit values when the value changes in the underlying + * storage, although multiple values with the same shape may be emitted in a row. + * + * If a {@link StorageApi.snapshot} of a key is retrieved and the presence is + * `'unknown'`, then you are guaranteed to receive a snapshot with a known + * presence, as long as you observe the key within the same tick. + * + * Since the emitted values are shared across all subscribers, it is important + * not to mutate the returned values. The values may be frozen as a precaution. + * + * @param key - Unique key associated with the data + */ + observe$( + key: string, + ): Observable>; + + /** + * Returns an immediate snapshot value for the given key, if possible. + * + * @remarks + * + * Combine with {@link StorageApi.observe$} to get notified of value changes. + * + * Note that this method is synchronous, and some underlying storages may be + * unable to retrieve a value using this method - the result may or may not + * consistently have a presence of 'unknown'. Use {@link StorageApi.observe$} + * to be sure to receive an actual value eventually. + */ + snapshot(key: string): StorageValueSnapshot; +} + +/** + * The {@link ApiRef} of {@link StorageApi}. + * + * @public + */ +export const storageApiRef = createApiRef().with({ + id: 'core.storage', + pluginId: 'app', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/SwappableComponentsApi.ts b/packages/frontend-plugin-api/src/apis/definitions/SwappableComponentsApi.ts index ed2b20ce80..47ffed91ef 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/SwappableComponentsApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/SwappableComponentsApi.ts @@ -15,7 +15,7 @@ */ import { SwappableComponentRef } from '../../components'; -import { createApiRef } from '@backstage/core-plugin-api'; +import { createApiRef } from '../system'; /** * API for looking up components based on component refs. @@ -36,6 +36,8 @@ export interface SwappableComponentsApi { * * @public */ -export const swappableComponentsApiRef = createApiRef({ - id: 'core.swappable-components', -}); +export const swappableComponentsApiRef = + createApiRef().with({ + id: 'core.swappable-components', + pluginId: 'app', + }); diff --git a/packages/frontend-plugin-api/src/apis/definitions/ToastApi.ts b/packages/frontend-plugin-api/src/apis/definitions/ToastApi.ts new file mode 100644 index 0000000000..0a76632c29 --- /dev/null +++ b/packages/frontend-plugin-api/src/apis/definitions/ToastApi.ts @@ -0,0 +1,109 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createApiRef, ApiRef } from '../system'; +import { ReactNode } from 'react'; + +/** + * Link item for toast notifications. + * + * @public + */ +export type ToastApiMessageLink = { + /** Display text for the link */ + label: string; + /** URL the link points to */ + href: string; +}; + +/** + * Message handled by the {@link ToastApi}. + * + * @public + */ +export type ToastApiMessage = { + /** Title of the toast (required) */ + title: ReactNode; + /** Optional description text */ + description?: ReactNode; + /** Status variant of the toast - defaults to 'success' */ + status?: 'neutral' | 'info' | 'success' | 'warning' | 'danger'; + /** Optional array of links to display */ + links?: ToastApiMessageLink[]; + /** Timeout in milliseconds before auto-dismiss. If not set, toast is permanent. */ + timeout?: number; +}; + +/** + * Handle returned by {@link ToastApi.post} that allows programmatic control + * of the posted toast. + * + * @public + */ +export type ToastApiPostResult = { + /** Dismiss the toast. */ + close(): void; +}; + +/** + * The toast API is used to display toast notifications to the user. + * + * @remarks + * This API provides richer notification capabilities than the AlertApi, + * including title/description, links, and per-toast timeout control. + * + * @example + * ```tsx + * const toastApi = useApi(toastApiRef); + * + * // Full-featured toast + * toastApi.post({ + * title: 'Entity saved', + * description: 'Your changes have been saved successfully.', + * status: 'success', + * timeout: 5000, + * links: [{ label: 'View entity', href: '/catalog/default/component/my-service' }], + * }); + * + * // Simple toast + * toastApi.post({ title: 'Processing...', status: 'info' }); + * + * // Programmatic dismiss + * const { close } = toastApi.post({ title: 'Uploading...', status: 'info' }); + * // Later... + * close(); + * ``` + * + * @public + */ +export type ToastApi = { + /** + * Post a toast notification for display to the user. + * + * @param toast - The toast message to display + * @returns A handle with a `close()` method to programmatically dismiss the toast + */ + post(toast: ToastApiMessage): ToastApiPostResult; +}; + +/** + * The {@link ApiRef} of {@link ToastApi}. + * + * @public + */ +export const toastApiRef: ApiRef = createApiRef({ + id: 'core.toast', +}); diff --git a/packages/core-plugin-api/src/apis/definitions/TranslationApi.test.tsx b/packages/frontend-plugin-api/src/apis/definitions/TranslationApi.test.tsx similarity index 100% rename from packages/core-plugin-api/src/apis/definitions/TranslationApi.test.tsx rename to packages/frontend-plugin-api/src/apis/definitions/TranslationApi.test.tsx diff --git a/packages/core-plugin-api/src/apis/definitions/TranslationApi.ts b/packages/frontend-plugin-api/src/apis/definitions/TranslationApi.ts similarity index 97% rename from packages/core-plugin-api/src/apis/definitions/TranslationApi.ts rename to packages/frontend-plugin-api/src/apis/definitions/TranslationApi.ts index 5cfb14abb2..ab50baf5e1 100644 --- a/packages/core-plugin-api/src/apis/definitions/TranslationApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/TranslationApi.ts @@ -14,15 +14,15 @@ * limitations under the License. */ -import { ApiRef, createApiRef } from '@backstage/core-plugin-api'; +import { createApiRef } from '../system'; import { Expand, ExpandRecursive, Observable } from '@backstage/types'; -import { TranslationRef } from '../../translation/TranslationRef'; +import { TranslationRef } from '../../translation'; import { JSX } from 'react'; /** * Base translation options. * - * @alpha + * @ignore */ interface BaseOptions { interpolation?: { @@ -305,7 +305,7 @@ type TranslationFunctionOptions< > >; -/** @alpha */ +/** @public */ export type TranslationFunction = CollapsedMessages extends infer IMessages extends { [key in string]: string; @@ -340,11 +340,11 @@ export type TranslationFunction = } : never; -/** @alpha */ +/** @public */ export type TranslationSnapshot = { ready: false } | { ready: true; t: TranslationFunction }; -/** @alpha */ +/** @public */ export type TranslationApi = { getTranslation( translationRef: TranslationRef, @@ -356,8 +356,9 @@ export type TranslationApi = { }; /** - * @alpha + * @public */ -export const translationApiRef: ApiRef = createApiRef({ +export const translationApiRef = createApiRef().with({ id: 'core.translation', + pluginId: 'app', }); diff --git a/packages/frontend-plugin-api/src/apis/definitions/auth.ts b/packages/frontend-plugin-api/src/apis/definitions/auth.ts index 5a31c1603d..d686ed5d08 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/auth.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/auth.ts @@ -13,29 +13,515 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +/* We want to maintain the same information as an enum, so we disable the redeclaration warning */ +/* eslint-disable @typescript-eslint/no-redeclare */ -export { - type BackstageIdentityApi, - type BackstageIdentityResponse, - type BackstageUserIdentity, - type AuthProviderInfo, - type AuthRequestOptions, - type OAuthScope, - type OAuthApi, - type OpenIdConnectApi, - type ProfileInfoApi, - type ProfileInfo, - type SessionApi, - SessionState, - atlassianAuthApiRef, - bitbucketAuthApiRef, - bitbucketServerAuthApiRef, - githubAuthApiRef, - gitlabAuthApiRef, - googleAuthApiRef, - oktaAuthApiRef, - microsoftAuthApiRef, - oneloginAuthApiRef, - vmwareCloudAuthApiRef, - openshiftAuthApiRef, -} from '@backstage/core-plugin-api'; +import { createApiRef } from '../system'; +import { IconComponent, IconElement } from '../../icons/types'; +import { Observable } from '@backstage/types'; + +/** + * This file contains declarations for common interfaces of auth-related APIs. + * The declarations should be used to signal which type of authentication and + * authorization methods each separate auth provider supports. + * + * For example, a Google OAuth provider that supports OAuth 2 and OpenID Connect, + * would be declared as follows: + * + * const googleAuthApiRef = createApiRef().with({ + * id: 'core.auth.google', + * pluginId: 'app', + * }) + */ + +/** + * Information about the auth provider. + * + * @remarks + * + * This information is used both to connect the correct auth provider in the backend, as + * well as displaying the provider to the user. + * + * @public + */ +export type AuthProviderInfo = { + /** + * The ID of the auth provider. This should match with ID of the provider in the `@backstage/auth-backend`. + */ + id: string; + + /** + * Title for the auth provider, for example "GitHub" + */ + title: string; + + /** + * Icon for the auth provider. + * + * @remarks + * + * Accepts either an `IconElement` (e.g. ``) or an `IconComponent` + * (e.g. `MyIcon`). Prefer passing `IconElement`. + */ + icon: IconComponent | IconElement; + + /** + * Optional user friendly messaage to display for the auth provider. + */ + message?: string; +}; + +/** + * An array of scopes, or a scope string formatted according to the + * auth provider, which is typically a space separated list. + * + * @remarks + * + * See the documentation for each auth provider for the list of scopes + * supported by each provider. + * + * @public + */ +export type OAuthScope = string | string[]; + +/** + * Configuration of an authentication request. + * + * @public + */ +export type AuthRequestOptions = { + /** + * If this is set to true, the user will not be prompted to log in, + * and an empty response will be returned if there is no existing session. + * + * This can be used to perform a check whether the user is logged in, or if you don't + * want to force a user to be logged in, but provide functionality if they already are. + * + * @defaultValue false + */ + optional?: boolean; + + /** + * If this is set to true, the request will bypass the regular oauth login modal + * and open the login popup directly. + * + * The method must be called synchronously from a user action for this to work in all browsers. + * + * @defaultValue false + */ + instantPopup?: boolean; +}; + +/** + * This API provides access to OAuth 2 credentials. It lets you request access tokens, + * which can be used to act on behalf of the user when talking to APIs. + * + * @public + */ +export type OAuthApi = { + /** + * Requests an OAuth 2 Access Token, optionally with a set of scopes. The access token allows + * you to make requests on behalf of the user, and the copes may grant you broader access, depending + * on the auth provider. + * + * Each auth provider has separate handling of scope, so you need to look at the documentation + * for each one to know what scope you need to request. + * + * This method is cheap and should be called each time an access token is used. Do not for example + * store the access token in React component state, as that could cause the token to expire. Instead + * fetch a new access token for each request. + * + * Be sure to include all required scopes when requesting an access token. When testing your implementation + * it is best to log out the Backstage session and then visit your plugin page directly, as + * you might already have some required scopes in your existing session. Not requesting the correct + * scopes can lead to 403 or other authorization errors, which can be tricky to debug. + * + * If the user has not yet granted access to the provider and the set of requested scopes, the user + * will be prompted to log in. The returned promise will not resolve until the user has + * successfully logged in. The returned promise can be rejected, but only if the user rejects the login request. + */ + getAccessToken( + scope?: OAuthScope, + options?: AuthRequestOptions, + ): Promise; +}; + +/** + * This API provides access to OpenID Connect credentials. It lets you request ID tokens, + * which can be passed to backend services to prove the user's identity. + * + * @public + */ +export type OpenIdConnectApi = { + /** + * Requests an OpenID Connect ID Token. + * + * This method is cheap and should be called each time an ID token is used. Do not for example + * store the id token in React component state, as that could cause the token to expire. Instead + * fetch a new id token for each request. + * + * If the user has not yet logged in to Google inside Backstage, the user will be prompted + * to log in. The returned promise will not resolve until the user has successfully logged in. + * The returned promise can be rejected, but only if the user rejects the login request. + */ + getIdToken(options?: AuthRequestOptions): Promise; +}; + +/** + * This API provides access to profile information of the user from an auth provider. + * + * @public + */ +export type ProfileInfoApi = { + /** + * Get profile information for the user as supplied by this auth provider. + * + * If the optional flag is not set, a session is guaranteed to be returned, while if + * the optional flag is set, the session may be undefined. See {@link AuthRequestOptions} for more details. + */ + getProfile(options?: AuthRequestOptions): Promise; +}; + +/** + * This API provides access to the user's identity within Backstage. + * + * @remarks + * + * An auth provider that implements this interface can be used to sign-in to backstage. It is + * not intended to be used directly from a plugin, but instead serves as a connection between + * this authentication method and the app's {@link IdentityApi} + * + * @public + */ +export type BackstageIdentityApi = { + /** + * Get the user's identity within Backstage. This should normally not be called directly, + * use the {@link IdentityApi} instead. + * + * If the optional flag is not set, a session is guaranteed to be returned, while if + * the optional flag is set, the session may be undefined. See {@link AuthRequestOptions} for more details. + */ + getBackstageIdentity( + options?: AuthRequestOptions, + ): Promise; +}; + +/** + * User identity information within Backstage. + * + * @public + */ +export type BackstageUserIdentity = { + /** + * The type of identity that this structure represents. In the frontend app + * this will currently always be 'user'. + */ + type: 'user'; + + /** + * The entityRef of the user in the catalog. + * For example User:default/sandra + */ + userEntityRef: string; + + /** + * The user and group entities that the user claims ownership through + */ + ownershipEntityRefs: string[]; +}; + +/** + * Token and Identity response, with the users claims in the Identity. + * + * @public + */ +export type BackstageIdentityResponse = { + /** + * The token used to authenticate the user within Backstage. + */ + token: string; + + /** + * The time at which the token expires. If not set, it can be assumed that the token does not expire. + */ + expiresAt?: Date; + + /** + * Identity information derived from the token. + */ + identity: BackstageUserIdentity; +}; + +/** + * Profile information of the user. + * + * @public + */ +export type ProfileInfo = { + /** + * Email ID. + */ + email?: string; + + /** + * Display name that can be presented to the user. + */ + displayName?: string; + + /** + * URL to an avatar image of the user. + */ + picture?: string; +}; + +/** + * Session state values passed to subscribers of the SessionApi. + * + * @public + */ +export const SessionState = { + /** + * User signed in. + */ + SignedIn: 'SignedIn', + /** + * User not signed in. + */ + SignedOut: 'SignedOut', +} as const; + +/** + * @public + */ +export type SessionState = (typeof SessionState)[keyof typeof SessionState]; + +/** + * @public + */ +export namespace SessionState { + export type SignedIn = typeof SessionState.SignedIn; + export type SignedOut = typeof SessionState.SignedOut; +} + +/** + * The SessionApi provides basic controls for any auth provider that is tied to a persistent session. + * + * @public + */ +export type SessionApi = { + /** + * Sign in with a minimum set of permissions. + */ + signIn(): Promise; + + /** + * Sign out from the current session. This will reload the page. + */ + signOut(): Promise; + + /** + * Observe the current state of the auth session. Emits the current state on subscription. + */ + sessionState$(): Observable; +}; + +/** + * Provides authentication towards Google APIs and identities. + * + * @public + * @remarks + * + * See {@link https://developers.google.com/identity/protocols/googlescopes} for a full list of supported scopes. + * + * Note that the ID token payload is only guaranteed to contain the user's numerical Google ID, + * email and expiration information. Do not rely on any other fields, as they might not be present. + */ +export const googleAuthApiRef = createApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +>().with({ + id: 'core.auth.google', + pluginId: 'app', +}); + +/** + * Provides authentication towards GitHub APIs. + * + * @public + * @remarks + * + * See {@link https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/} + * for a full list of supported scopes. + */ +export const githubAuthApiRef = createApiRef< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi +>().with({ + id: 'core.auth.github', + pluginId: 'app', +}); + +/** + * Provides authentication towards Okta APIs. + * + * @public + * @remarks + * + * See {@link https://developer.okta.com/docs/guides/implement-oauth-for-okta/scopes/} + * for a full list of supported scopes. + */ +export const oktaAuthApiRef = createApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +>().with({ + id: 'core.auth.okta', + pluginId: 'app', +}); + +/** + * Provides authentication towards GitLab APIs. + * + * @public + * @remarks + * + * See {@link https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#limiting-scopes-of-a-personal-access-token} + * for a full list of supported scopes. + */ +export const gitlabAuthApiRef = createApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +>().with({ + id: 'core.auth.gitlab', + pluginId: 'app', +}); + +/** + * Provides authentication towards Microsoft APIs and identities. + * + * @public + * @remarks + * + * For more info and a full list of supported scopes, see: + * - {@link https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent} + * - {@link https://docs.microsoft.com/en-us/graph/permissions-reference} + */ +export const microsoftAuthApiRef = createApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +>().with({ + id: 'core.auth.microsoft', + pluginId: 'app', +}); + +/** + * Provides authentication towards OneLogin APIs. + * + * @public + */ +export const oneloginAuthApiRef = createApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +>().with({ + id: 'core.auth.onelogin', + pluginId: 'app', +}); + +/** + * Provides authentication towards Bitbucket APIs. + * + * @public + * @remarks + * + * See {@link https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/} + * for a full list of supported scopes. + */ +export const bitbucketAuthApiRef = createApiRef< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi +>().with({ + id: 'core.auth.bitbucket', + pluginId: 'app', +}); + +/** + * Provides authentication towards Bitbucket Server APIs. + * + * @public + * @remarks + * + * See {@link https://confluence.atlassian.com/bitbucketserver/bitbucket-oauth-2-0-provider-api-1108483661.html#BitbucketOAuth2.0providerAPI-scopes} + * for a full list of supported scopes. + */ +export const bitbucketServerAuthApiRef = createApiRef< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi +>().with({ + id: 'core.auth.bitbucket-server', + pluginId: 'app', +}); + +/** + * Provides authentication towards Atlassian APIs. + * + * @public + * @remarks + * + * See {@link https://developer.atlassian.com/cloud/jira/platform/scopes-for-connect-and-oauth-2-3LO-apps/} + * for a full list of supported scopes. + */ +export const atlassianAuthApiRef = createApiRef< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi +>().with({ + id: 'core.auth.atlassian', + pluginId: 'app', +}); + +/** + * Provides authentication towards VMware Cloud APIs and identities. + * + * @public + * @remarks + * + * For more info about VMware Cloud identity and access management: + * - {@link https://docs.vmware.com/en/VMware-Cloud-services/services/Using-VMware-Cloud-Services/GUID-53D39337-D93A-4B84-BD18-DDF43C21479A.html} + */ +export const vmwareCloudAuthApiRef = createApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +>().with({ + id: 'core.auth.vmware-cloud', + pluginId: 'app', +}); + +/** + * Provides authentication towards OpenShift APIs and identities. + * + * @public + * @remarks + * + * See {@link https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html/authentication_and_authorization/configuring-oauth-clients} + * on how to configure the OAuth clients and + * {@link https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html-single/authentication_and_authorization/index#tokens-scoping-about_configuring-internal-oauth} + * for available scopes. + */ +export const openshiftAuthApiRef = createApiRef< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi +>().with({ + id: 'core.auth.openshift', + pluginId: 'app', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/index.ts b/packages/frontend-plugin-api/src/apis/definitions/index.ts index 7533481d01..e33834ff5e 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/index.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/index.ts @@ -33,6 +33,7 @@ export { export * from './auth'; export * from './AlertApi'; +export * from './AppLanguageApi'; export * from './AppThemeApi'; export * from './SwappableComponentsApi'; export * from './ConfigApi'; @@ -47,3 +48,7 @@ export * from './OAuthRequestApi'; export * from './RouteResolutionApi'; export * from './StorageApi'; export * from './AnalyticsApi'; +export * from './ToastApi'; +export * from './TranslationApi'; +export * from './PluginHeaderActionsApi'; +export * from './PluginWrapperApi'; diff --git a/packages/frontend-plugin-api/src/apis/system/ApiRef.test.ts b/packages/frontend-plugin-api/src/apis/system/ApiRef.test.ts new file mode 100644 index 0000000000..37bf0205be --- /dev/null +++ b/packages/frontend-plugin-api/src/apis/system/ApiRef.test.ts @@ -0,0 +1,97 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createApiRef } from './ApiRef'; +import type { ApiRef as ApiRefType } from './types'; + +describe('ApiRef', () => { + it('should be created with config', () => { + const ref = createApiRef({ id: 'abc' }); + expect(ref.$$type).toBe('@backstage/ApiRef'); + expect(ref.id).toBe('abc'); + expect(String(ref)).toBe('apiRef{abc}'); + expect(ref.T).toBeNull(); + }); + + it('should not accept pluginId with deprecated config form', () => { + expect(createApiRef({ id: 'abc' }).id).toBe('abc'); + + // @ts-expect-error pluginId is only supported through .with(...) + createApiRef({ id: 'abc', pluginId: 'test' }); + }); + + it('should keep the deprecated config form id wide', () => { + const ref = createApiRef({ id: 'abc' }); + const wideRef: ApiRefType = ref; + expect(wideRef.id).toBe('abc'); + + // @ts-expect-error deprecated config form should not infer literal ids + const literalRef: ApiRefType = ref; + expect(literalRef.id).toBe('abc'); + }); + + it('should be created with builder pattern', () => { + const ref = createApiRef().with({ id: 'abc', pluginId: 'test' }); + expect(ref.$$type).toBe('@backstage/ApiRef'); + expect(ref.id).toBe('abc'); + expect(String(ref)).toBe('apiRef{abc}'); + expect(ref.T).toBeNull(); + expect((ref as { pluginId?: string }).pluginId).toBe('test'); + + // @ts-expect-error pluginId is internal runtime metadata + expect(ref.pluginId).toBe('test'); + }); + + it('should infer literal ids with builder pattern', () => { + const ref = createApiRef().with({ id: 'abc', pluginId: 'test' }); + const literalRef: ApiRefType = ref; + expect(literalRef.id).toBe('abc'); + + // @ts-expect-error builder pattern should preserve literal ids + const wrongLiteralRef: ApiRefType = ref; + expect(wrongLiteralRef.id).toBe('abc'); + }); + + it('should reject invalid ids', () => { + for (const id of ['a', 'abc', 'ab-c', 'a.b.c', 'a-b.c', 'abc.a-b-c.abc3']) { + expect(createApiRef({ id }).id).toBe(id); + } + + for (const id of [ + '123', + 'ab-3', + 'ab_c', + '.', + '2ac', + 'ab.3a', + '.abc', + 'abc.', + 'ab..s', + '', + '_', + ]) { + expect(() => createApiRef({ id }).id).toThrow( + `API id must only contain period separated lowercase alphanum tokens with dashes, got '${id}'`, + ); + } + }); + + it('should reject invalid ids with builder pattern', () => { + expect(() => createApiRef().with({ id: '123' })).toThrow( + `API id must only contain period separated lowercase alphanum tokens with dashes, got '123'`, + ); + }); +}); diff --git a/packages/frontend-plugin-api/src/apis/system/ApiRef.ts b/packages/frontend-plugin-api/src/apis/system/ApiRef.ts index 0d1f8312cb..557341d98a 100644 --- a/packages/frontend-plugin-api/src/apis/system/ApiRef.ts +++ b/packages/frontend-plugin-api/src/apis/system/ApiRef.ts @@ -14,8 +14,122 @@ * limitations under the License. */ -export { - type ApiRef, - type ApiRefConfig, - createApiRef, -} from '@backstage/core-plugin-api'; +import { OpaqueApiRef } from '@internal/frontend'; +import type { ApiRef } from './types'; + +/** + * API reference configuration - holds an ID of the referenced API. + * + * @public + */ +export type ApiRefConfig = { + id: string; +}; + +type ApiRefBuilderConfig = { + id: TId; + pluginId?: string; +}; + +function validateId(id: string): void { + const valid = id + .split('.') + .flatMap(part => part.split('-')) + .every(part => part.match(/^[a-z][a-z0-9]*$/)); + if (!valid) { + throw new Error( + `API id must only contain period separated lowercase alphanum tokens with dashes, got '${id}'`, + ); + } +} + +function makeApiRef( + config: ApiRefBuilderConfig, +): ApiRef & { readonly $$type: '@backstage/ApiRef' } { + return OpaqueApiRef.createInstance('v1', { + id: config.id, + ...(config.pluginId ? { pluginId: config.pluginId } : {}), + T: null as unknown as T, + toString() { + return `apiRef{${config.id}}`; + }, + }) as ApiRef & { readonly $$type: '@backstage/ApiRef' }; +} + +/** + * Creates a reference to an API. + * + * @remarks + * + * The `id` is a stable identifier for the API implementation. The frontend + * system infers the owning plugin for an API from the `id`. When using the + * builder form, you can instead provide a `pluginId` explicitly. The + * recommended pattern is `plugin..*` (for example, + * `plugin.catalog.entity-presentation`). This ensures that other plugins can't + * mistakenly override your API implementation. + * + * The recommended way to create an API reference is: + * + * ```ts + * const myApiRef = createApiRef().with({ + * id: 'my-api', + * pluginId: 'my-plugin', + * }); + * ``` + * + * The legacy way to create an API reference is: + * + * ```ts + * const myApiRef = createApiRef({ id: 'plugin.my.api' }); + * ``` + * + * @public + */ +/** + * Creates a reference to an API. + * + * @deprecated Use `createApiRef().with(...)` instead. + * @public + */ +export function createApiRef( + config: ApiRefConfig, +): ApiRef & { readonly $$type: '@backstage/ApiRef' }; +/** + * Creates a reference to an API. + * + * @remarks + * + * Returns a builder with a `.with()` method for providing the API reference + * configuration. + * + * @public + */ +export function createApiRef(): { + with( + config: ApiRefConfig & { id: TId; pluginId?: string }, + ): ApiRef & { + readonly $$type: '@backstage/ApiRef'; + }; +}; +export function createApiRef(config?: ApiRefConfig): + | (ApiRef & { readonly $$type: '@backstage/ApiRef' }) + | { + with( + config: ApiRefConfig & { id: TId; pluginId?: string }, + ): ApiRef & { + readonly $$type: '@backstage/ApiRef'; + }; + } { + if (config) { + validateId(config.id); + return makeApiRef(config); + } + return { + with( + withConfig: ApiRefConfig & { id: TId; pluginId?: string }, + ): ApiRef & { readonly $$type: '@backstage/ApiRef' } { + validateId(withConfig.id); + return makeApiRef(withConfig); + }, + }; +} diff --git a/packages/frontend-plugin-api/src/apis/system/helpers.ts b/packages/frontend-plugin-api/src/apis/system/helpers.ts index 5de265be99..eaffed3003 100644 --- a/packages/frontend-plugin-api/src/apis/system/helpers.ts +++ b/packages/frontend-plugin-api/src/apis/system/helpers.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,4 +14,61 @@ * limitations under the License. */ -export { createApiFactory } from '@backstage/core-plugin-api'; +import { ApiRef, ApiFactory, TypesToApiRefs } from './types'; + +/** + * Used to infer types for a standalone {@link ApiFactory} that isn't immediately passed + * to another function. + * + * @remarks + * + * This function doesn't actually do anything, it's only used to infer types. + * + * @public + */ +export function createApiFactory< + Api, + Impl extends Api, + Deps extends { [name in string]: unknown }, +>(factory: ApiFactory): ApiFactory; +/** + * Used to infer types for a standalone {@link ApiFactory} that isn't immediately passed + * to another function. + * + * @param api - Ref of the API that will be produced by the factory. + * @param instance - Implementation of the API to use. + * @public + */ +export function createApiFactory( + api: ApiRef, + instance: Impl, +): ApiFactory; +/** + * Used to infer types for a standalone {@link ApiFactory} that isn't immediately passed + * to another function. + * + * @remarks + * + * Creates factory from {@link ApiRef} or returns the factory itself if provided. + * + * @param factory - Existing factory or {@link ApiRef}. + * @param instance - The instance to be returned by the factory. + * @public + */ +export function createApiFactory< + Api, + Impl extends Api, + Deps extends { [name in string]: unknown }, +>( + factory: ApiFactory | ApiRef, + instance?: Impl, +): ApiFactory { + if ('id' in factory) { + return { + api: factory, + deps: {} as TypesToApiRefs, + factory: () => instance!, + }; + } + return factory; +} diff --git a/packages/frontend-plugin-api/src/apis/system/index.ts b/packages/frontend-plugin-api/src/apis/system/index.ts index b1bd3ac2b3..964750474e 100644 --- a/packages/frontend-plugin-api/src/apis/system/index.ts +++ b/packages/frontend-plugin-api/src/apis/system/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/frontend-plugin-api/src/apis/system/types.ts b/packages/frontend-plugin-api/src/apis/system/types.ts index cc44e19e3f..5bc6136bdf 100644 --- a/packages/frontend-plugin-api/src/apis/system/types.ts +++ b/packages/frontend-plugin-api/src/apis/system/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,11 +14,62 @@ * limitations under the License. */ -export type { - ApiRef, - AnyApiRef, - TypesToApiRefs, - ApiHolder, - ApiFactory, - AnyApiFactory, -} from '@backstage/core-plugin-api'; +/** + * API reference. + * + * @public + */ +export type ApiRef = { + readonly $$type?: '@backstage/ApiRef'; + readonly id: TId; + readonly T: T; +}; + +/** + * Catch-all {@link ApiRef} type. + * + * @public + */ +export type AnyApiRef = ApiRef; + +/** + * Wraps a type with API properties into a type holding their respective {@link ApiRef}s. + * + * @public + */ +export type TypesToApiRefs = { [key in keyof T]: ApiRef }; + +/** + * Provides lookup of APIs through their {@link ApiRef}s. + * + * @public + */ +export type ApiHolder = { + get(api: ApiRef): T | undefined; +}; + +/** + * Describes type returning API implementations. + * + * @public + */ +export type ApiFactory< + Api, + Impl extends Api, + Deps extends { [name in string]: unknown }, +> = { + api: ApiRef; + deps: TypesToApiRefs; + factory(deps: Deps): Impl; +}; + +/** + * Catch-all {@link ApiFactory} type. + * + * @public + */ +export type AnyApiFactory = ApiFactory< + unknown, + unknown, + { [key in string]: unknown } +>; diff --git a/packages/frontend-plugin-api/src/apis/system/useApi.test.tsx b/packages/frontend-plugin-api/src/apis/system/useApi.test.tsx new file mode 100644 index 0000000000..a5cfb627ac --- /dev/null +++ b/packages/frontend-plugin-api/src/apis/system/useApi.test.tsx @@ -0,0 +1,65 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { renderHook } from '@testing-library/react'; +import { createVersionedContextForTesting } from '@backstage/version-bridge'; +import { createApiRef } from './ApiRef'; +import { useApi, useApiHolder } from './useApi'; + +describe('useApiHolder', () => { + const context = createVersionedContextForTesting('api-context'); + + afterEach(() => { + context.reset(); + }); + + it('should return the API holder from context', () => { + const holder = { get: jest.fn() }; + context.set({ 1: holder }); + + const renderedHook = renderHook(() => useApiHolder()); + expect(renderedHook.result.current).toBe(holder); + }); + + it('should return an empty API holder when there is no context', () => { + const renderedHook = renderHook(() => useApiHolder()); + + const holder = renderedHook.result.current; + expect( + holder.get(createApiRef().with({ id: 'x' })), + ).toBeUndefined(); + }); +}); + +describe('useApi', () => { + const context = createVersionedContextForTesting('api-context'); + + afterEach(() => { + context.reset(); + }); + + it('should resolve routes', () => { + const get = jest.fn(() => 'my-api-impl'); + context.set({ 1: { get } }); + + const apiRef = createApiRef().with({ id: 'x' }); + const renderedHook = renderHook(() => useApi(apiRef)); + + const value = renderedHook.result.current; + expect(value).toBe('my-api-impl'); + expect(get).toHaveBeenCalledWith(apiRef); + }); +}); diff --git a/packages/frontend-plugin-api/src/apis/system/useApi.tsx b/packages/frontend-plugin-api/src/apis/system/useApi.tsx index d2efa49fa1..2808f13f1f 100644 --- a/packages/frontend-plugin-api/src/apis/system/useApi.tsx +++ b/packages/frontend-plugin-api/src/apis/system/useApi.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,4 +14,84 @@ * limitations under the License. */ -export { useApiHolder, useApi, withApis } from '@backstage/core-plugin-api'; +import { ComponentType, PropsWithChildren } from 'react'; +import { ApiRef, ApiHolder, TypesToApiRefs } from './types'; +import { useVersionedContext } from '@backstage/version-bridge'; +import { NotImplementedError } from '@backstage/errors'; + +const emptyApiHolder: ApiHolder = Object.freeze({ get: () => undefined }); + +/** + * React hook for retrieving {@link ApiHolder}, an API catalog. + * + * @public + */ +export function useApiHolder(): ApiHolder { + const versionedHolder = useVersionedContext<{ 1: ApiHolder }>('api-context'); + if (!versionedHolder) { + return emptyApiHolder; + } + + const apiHolder = versionedHolder.atVersion(1); + if (!apiHolder) { + throw new NotImplementedError('ApiContext v1 not available'); + } + return apiHolder; +} + +/** + * React hook for retrieving APIs. + * + * @param apiRef - Reference of the API to use. + * @public + */ +export function useApi(apiRef: ApiRef): T { + const apiHolder = useApiHolder(); + + const api = apiHolder.get(apiRef); + if (!api) { + throw new NotImplementedError(`No implementation available for ${apiRef}`); + } + return api; +} + +/** + * Wrapper for giving component an API context. + * + * @param apis - APIs for the context. + * @deprecated Use `withApis` from `@backstage/core-compat-api` instead. + * @public + */ +export function withApis(apis: TypesToApiRefs) { + return function withApisWrapper( + WrappedComponent: ComponentType, + ) { + const Hoc = (props: PropsWithChildren>) => { + const apiHolder = useApiHolder(); + + const impls = {} as T; + + for (const key in apis) { + if (apis.hasOwnProperty(key)) { + const ref = apis[key]; + + const api = apiHolder.get(ref); + if (!api) { + throw new NotImplementedError( + `No implementation available for ${ref}`, + ); + } + impls[key] = api; + } + } + + return ; + }; + const displayName = + WrappedComponent.displayName || WrappedComponent.name || 'Component'; + + Hoc.displayName = `withApis(${displayName})`; + + return Hoc; + }; +} diff --git a/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.test.ts b/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.test.ts index b297b3a3a9..6d43e0352f 100644 --- a/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.test.ts +++ b/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.test.ts @@ -32,15 +32,14 @@ describe('AnalyticsBlueprint', () => { { "$$type": "@backstage/ExtensionDefinition", "T": undefined, - "attachTo": [ - { - "id": "api:app/analytics", - "input": "implementations", - }, - ], + "attachTo": { + "id": "api:app/analytics", + "input": "implementations", + }, "configSchema": undefined, "disabled": false, "factory": [Function], + "if": undefined, "inputs": {}, "kind": "analytics", "name": "test", diff --git a/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.ts index 91a0f27092..e9961c3d48 100644 --- a/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.ts @@ -21,7 +21,10 @@ import { createExtensionDataRef, } from '../wiring'; -/** @public */ +/** + * @public + * @deprecated Use `AnalyticsImplementationFactory` from `@backstage/plugin-app-react` instead. + */ export type AnalyticsImplementationFactory< Deps extends { [name in string]: unknown } = {}, > = { @@ -38,10 +41,11 @@ const factoryDataRef = * Creates analytics implementations. * * @public + * @deprecated Use `AnalyticsImplementationBlueprint` from `@backstage/plugin-app-react` instead. */ export const AnalyticsImplementationBlueprint = createExtensionBlueprint({ kind: 'analytics', - attachTo: [{ id: 'api:app/analytics', input: 'implementations' }], + attachTo: { id: 'api:app/analytics', input: 'implementations' }, output: [factoryDataRef], dataRefs: { factory: factoryDataRef, diff --git a/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts index 988c5a15c0..01527bd614 100644 --- a/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts +++ b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts @@ -16,11 +16,11 @@ import { createExtensionInput } from '../wiring'; import { ApiBlueprint } from './ApiBlueprint'; -import { createApiRef } from '@backstage/core-plugin-api'; +import { createApiRef } from '../apis/system'; describe('ApiBlueprint', () => { it('should create an extension with sensible defaults', () => { - const api = createApiRef<{ foo: string }>({ id: 'test' }); + const api = createApiRef<{ foo: string }>().with({ id: 'test' }); const extension = ApiBlueprint.make({ params: defineParams => @@ -43,6 +43,7 @@ describe('ApiBlueprint', () => { "configSchema": undefined, "disabled": false, "factory": [Function], + "if": undefined, "inputs": {}, "kind": "api", "name": "test", @@ -57,8 +58,8 @@ describe('ApiBlueprint', () => { }); it('should properly type the API factory', () => { - const fooApi = createApiRef<{ foo: string }>({ id: 'foo' }); - const barApi = createApiRef<{ bar: string }>({ id: 'bar' }); + const fooApi = createApiRef<{ foo: string }>().with({ id: 'foo' }); + const barApi = createApiRef<{ bar: string }>().with({ id: 'bar' }); expect('test').not.toBe('failing without assertions'); @@ -152,7 +153,7 @@ describe('ApiBlueprint', () => { }); it('should create an extension with custom factory', () => { - const api = createApiRef<{ foo: string }>({ id: 'test' }); + const api = createApiRef<{ foo: string }>().with({ id: 'test' }); const factory = jest.fn(() => ({ foo: 'bar' })); const extension = ApiBlueprint.makeWithOverrides({ @@ -182,24 +183,16 @@ describe('ApiBlueprint', () => { }, "configSchema": { "parse": [Function], - "schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "test": { - "default": "test", - "type": "string", - }, - }, - "type": "object", - }, + "schema": [Function], }, "disabled": false, "factory": [Function], + "if": undefined, "inputs": { "test": { "$$type": "@backstage/ExtensionInput", "config": { + "internal": false, "optional": false, "singleton": false, }, diff --git a/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.test.tsx index 39f03cfad4..0e477dd2f1 100644 --- a/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.test.tsx @@ -14,7 +14,15 @@ * limitations under the License. */ +import { screen, waitFor } from '@testing-library/react'; +import { MockErrorApi, withLogCollector } from '@backstage/test-utils'; +import { errorApiRef } from '../apis'; +import { + createExtensionTester, + renderInTestApp, +} from '@backstage/frontend-test-utils'; import { AppRootElementBlueprint } from './AppRootElementBlueprint'; +import { ForwardedError } from '@backstage/errors'; describe('AppRootElementBlueprint', () => { it('should create an extension with sensible defaults', () => { @@ -34,6 +42,7 @@ describe('AppRootElementBlueprint', () => { "configSchema": undefined, "disabled": false, "factory": [Function], + "if": undefined, "inputs": {}, "kind": "app-root-element", "name": undefined, @@ -46,4 +55,55 @@ describe('AppRootElementBlueprint', () => { } `); }); + + it('should post error to errorApi and not render children when error occurs', async () => { + const errorApi = new MockErrorApi({ collect: true }); + const errorMessage = 'Test error message'; + const ErrorComponent = () => { + throw new Error(errorMessage); + }; + + await withLogCollector(['error'], async () => { + const extension = AppRootElementBlueprint.make({ + params: { + element: , + }, + }); + + const tester = createExtensionTester(extension); + renderInTestApp(tester.reactElement(), { + apis: [[errorApiRef, errorApi]], + }); + + await waitFor(() => { + const errors = errorApi.getErrors(); + expect(errors.length).toBeGreaterThan(0); + const postedError = errors[0].error; + expect(postedError).toBeInstanceOf(ForwardedError); + expect(postedError.message).toBe( + "Error in extension 'app-root-element:test'; caused by Error: Test error message", + ); + }); + + expect(screen.queryByText(errorMessage)).not.toBeInTheDocument(); + }); + }); + + it('should render children when there is no error', async () => { + const successMessage = 'Success!'; + const SuccessComponent = () =>
    {successMessage}
    ; + + const extension = AppRootElementBlueprint.make({ + params: { + element: , + }, + }); + + const tester = createExtensionTester(extension); + renderInTestApp(tester.reactElement()); + + await waitFor(() => { + expect(screen.getByText(successMessage)).toBeInTheDocument(); + }); + }); }); diff --git a/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.tsx similarity index 78% rename from packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.ts rename to packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.tsx index d1c71155f2..955abce136 100644 --- a/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.tsx @@ -14,6 +14,7 @@ * limitations under the License. */ +import { ExtensionBoundary } from '@backstage/frontend-plugin-api'; import { coreExtensionData, createExtensionBlueprint } from '../wiring'; /** @@ -26,7 +27,11 @@ export const AppRootElementBlueprint = createExtensionBlueprint({ kind: 'app-root-element', attachTo: { id: 'app/root', input: 'elements' }, output: [coreExtensionData.reactElement], - *factory(params: { element: JSX.Element }) { - yield coreExtensionData.reactElement(params.element); + *factory(params: { element: JSX.Element }, { node }) { + yield coreExtensionData.reactElement( + + {params.element} + , + ); }, }); diff --git a/packages/frontend-plugin-api/src/blueprints/NavContentBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/NavContentBlueprint.test.tsx deleted file mode 100644 index a113a2f21d..0000000000 --- a/packages/frontend-plugin-api/src/blueprints/NavContentBlueprint.test.tsx +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { createRouteRef } from '@backstage/frontend-plugin-api'; -import { NavContentBlueprint } from './NavContentBlueprint'; -import { createExtensionTester } from '@backstage/frontend-test-utils'; - -const routeRef = createRouteRef(); - -describe('NavContentBlueprint', () => { - it('should create an extension with sensible defaults', () => { - const extension = NavContentBlueprint.make({ - params: { - component: () =>
    Nav content
    , - }, - }); - - expect(extension).toMatchInlineSnapshot(` - { - "$$type": "@backstage/ExtensionDefinition", - "T": undefined, - "attachTo": { - "id": "app/nav", - "input": "content", - }, - "configSchema": undefined, - "disabled": false, - "factory": [Function], - "inputs": {}, - "kind": "nav-content", - "name": undefined, - "output": [ - [Function], - ], - "override": [Function], - "toString": [Function], - "version": "v2", - } - `); - }); - - it('should return a valid component', () => { - const extension = NavContentBlueprint.make({ - name: 'test', - params: { - component: () =>
    Nav content
    , - }, - }); - - const tester = createExtensionTester(extension); - - expect( - tester.get(NavContentBlueprint.dataRefs.component)({ items: [] }), - ).toEqual(
    Nav content
    ); - }); - - it('should return a valid component with items', () => { - const extension = NavContentBlueprint.make({ - name: 'test', - params: { - component: ({ items }) => ( -
    - ), - }, - }); - - const tester = createExtensionTester(extension); - - expect( - tester.get(NavContentBlueprint.dataRefs.component)({ - items: [ - { - to: '/', - text: 'Home', - title: 'Home', - icon: () => null, - routeRef, - }, - ], - }), - ).toEqual( -
    - Items: - {[ - - Home - , - ]} -
    , - ); - }); -}); diff --git a/packages/frontend-plugin-api/src/blueprints/NavContentBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/NavContentBlueprint.ts deleted file mode 100644 index a6524ea49e..0000000000 --- a/packages/frontend-plugin-api/src/blueprints/NavContentBlueprint.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { IconComponent, RouteRef } from '@backstage/frontend-plugin-api'; -import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; - -/** - * The props for the {@link NavContentComponent}. - * - * @public - */ -export interface NavContentComponentProps { - /** - * The nav items available to the component. These are all the items created - * with the {@link NavItemBlueprint} in the app. - * - * In addition to the original properties from the nav items, these also - * include a resolved route path as `to`, and duplicated `title` as `text` to - * simplify rendering. - */ - items: Array<{ - // Original props from nav items - icon: IconComponent; - title: string; - routeRef: RouteRef; - - // Additional props to simplify item rendering - to: string; - text: string; - }>; -} - -/** - * A component that renders the nav bar content, to be passed to the {@link NavContentBlueprint}. - * - * @public - */ -export type NavContentComponent = ( - props: NavContentComponentProps, -) => JSX.Element | null; - -const componentDataRef = createExtensionDataRef().with({ - id: 'core.nav-content.component', -}); - -/** - * Creates an extension that replaces the entire nav bar with your own component. - * - * @public - */ -export const NavContentBlueprint = createExtensionBlueprint({ - kind: 'nav-content', - attachTo: { id: 'app/nav', input: 'content' }, - output: [componentDataRef], - dataRefs: { - component: componentDataRef, - }, - *factory(params: { component: NavContentComponent }) { - yield componentDataRef(params.component); - }, -}); diff --git a/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.test.tsx index 8656cc7a33..0255d66604 100644 --- a/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.test.tsx @@ -40,19 +40,11 @@ describe('NavItemBlueprint', () => { }, "configSchema": { "parse": [Function], - "schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "title": { - "type": "string", - }, - }, - "type": "object", - }, + "schema": [Function], }, "disabled": false, "factory": [Function], + "if": undefined, "inputs": {}, "kind": "nav-item", "name": undefined, diff --git a/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts index 197f72da73..d3e967fde8 100644 --- a/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -import { IconComponent } from '@backstage/core-plugin-api'; +import { z } from 'zod/v4'; +import { IconComponent } from '../icons/types'; import { RouteRef } from '../routing'; import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; @@ -29,6 +30,10 @@ const targetDataRef = createExtensionDataRef<{ * Creates extensions that make up the items of the nav bar. * * @public + * @deprecated Nav items are now automatically inferred from `PageBlueprint` + * extensions based on their `title` and `icon` params. You can remove your + * `NavItemBlueprint` usage and instead pass `title` and `icon` directly to + * the `PageBlueprint`. */ export const NavItemBlueprint = createExtensionBlueprint({ kind: 'nav-item', @@ -55,9 +60,7 @@ export const NavItemBlueprint = createExtensionBlueprint({ routeRef, }), ], - config: { - schema: { - title: z => z.string().optional(), - }, + configSchema: { + title: z.string().optional(), }, }); diff --git a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.test.tsx index 680d93cd1c..f891590dc0 100644 --- a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.test.tsx @@ -49,20 +49,59 @@ describe('PageBlueprint', () => { }, "configSchema": { "parse": [Function], - "schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "path": { - "type": "string", - }, - }, - "type": "object", - }, + "schema": [Function], }, "disabled": false, "factory": [Function], - "inputs": {}, + "if": undefined, + "inputs": { + "pages": { + "$$type": "@backstage/ExtensionInput", + "config": { + "internal": false, + "optional": false, + "singleton": false, + }, + "context": { + "input": "pages", + "kind": "page", + "name": "test-page", + }, + "extensionData": [ + [Function], + { + "$$type": "@backstage/ExtensionDataRef", + "config": { + "optional": true, + }, + "id": "core.routing.ref", + "optional": [Function], + "toString": [Function], + }, + [Function], + { + "$$type": "@backstage/ExtensionDataRef", + "config": { + "optional": true, + }, + "id": "core.title", + "optional": [Function], + "toString": [Function], + }, + { + "$$type": "@backstage/ExtensionDataRef", + "config": { + "optional": true, + }, + "id": "core.icon", + "optional": [Function], + "toString": [Function], + }, + ], + "replaces": undefined, + "withContext": [Function], + }, + }, "kind": "page", "name": "test-page", "output": [ @@ -77,6 +116,24 @@ describe('PageBlueprint', () => { "optional": [Function], "toString": [Function], }, + { + "$$type": "@backstage/ExtensionDataRef", + "config": { + "optional": true, + }, + "id": "core.title", + "optional": [Function], + "toString": [Function], + }, + { + "$$type": "@backstage/ExtensionDataRef", + "config": { + "optional": true, + }, + "id": "core.icon", + "optional": [Function], + "toString": [Function], + }, ], "override": [Function], "toString": [Function], @@ -153,4 +210,61 @@ describe('PageBlueprint', () => { expect(getByText("I'm a lovely card")).toBeInTheDocument(), ); }); + + it('should produce a correct extension tree snapshot with child extensions', () => { + const myPage = PageBlueprint.makeWithOverrides({ + name: 'test-page', + inputs: { + cards: createExtensionInput([coreExtensionData.reactElement], { + optional: false, + singleton: false, + }), + }, + factory(originalFactory, { inputs }) { + return originalFactory({ + loader: async () => ( +
    + {inputs.cards.map(c => c.get(coreExtensionData.reactElement))} +
    + ), + path: '/test', + routeRef: mockRouteRef, + }); + }, + }); + + const CardBlueprint = createExtensionBlueprint({ + kind: 'card', + attachTo: { id: 'page:test-page', input: 'cards' }, + output: [coreExtensionData.reactElement], + factory() { + return [coreExtensionData.reactElement(
    I'm a lovely card
    )]; + }, + }); + + const tester = createExtensionTester(myPage).add( + CardBlueprint.make({ name: 'card', params: {} }), + ); + + expect(tester.snapshot()).toMatchInlineSnapshot(` + { + "children": { + "cards": [ + { + "id": "card:card", + "outputs": [ + "core.reactElement", + ], + }, + ], + }, + "id": "page:test-page", + "outputs": [ + "core.reactElement", + "core.routing.path", + "core.routing.ref", + ], + } + `); + }); }); diff --git a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx index 92e2ab6172..d9cd2f3819 100644 --- a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx +++ b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx @@ -14,46 +14,186 @@ * limitations under the License. */ +import { z } from 'zod/v4'; +import { JSX } from 'react'; +import { Routes, Route, Navigate } from 'react-router-dom'; +import { IconElement } from '../icons/types'; import { RouteRef } from '../routing'; -import { coreExtensionData, createExtensionBlueprint } from '../wiring'; -import { ExtensionBoundary } from '../components'; +import { + coreExtensionData, + createExtensionBlueprint, + createExtensionInput, +} from '../wiring'; +import { ExtensionBoundary, PageLayout, PageLayoutTab } from '../components'; +import { useApi } from '../apis/system'; +import { routeResolutionApiRef } from '../apis/definitions/RouteResolutionApi'; +import { pluginHeaderActionsApiRef } from '../apis/definitions/PluginHeaderActionsApi'; +import { RouteResolutionApi } from '../apis/definitions/RouteResolutionApi'; + +function resolveTitleLink( + routeResolutionApi: RouteResolutionApi, + routeRef: RouteRef | undefined, +): string | undefined { + if (!routeRef) { + return undefined; + } + try { + return routeResolutionApi.resolve(routeRef)?.(); + } catch { + // Route ref may require params not available in the current context + return undefined; + } +} /** - * Createx extensions that are routable React page components. + * Creates extensions that are routable React page components. * * @public */ export const PageBlueprint = createExtensionBlueprint({ kind: 'page', attachTo: { id: 'app/routes', input: 'routes' }, + inputs: { + pages: createExtensionInput([ + coreExtensionData.routePath, + coreExtensionData.routeRef.optional(), + coreExtensionData.reactElement, + coreExtensionData.title.optional(), + coreExtensionData.icon.optional(), + ]), + }, output: [ coreExtensionData.routePath, coreExtensionData.reactElement, coreExtensionData.routeRef.optional(), + coreExtensionData.title.optional(), + coreExtensionData.icon.optional(), ], - config: { - schema: { - path: z => z.string().optional(), - }, + configSchema: { + path: z.string().optional(), + title: z.string().optional(), }, *factory( params: { - /** - * @deprecated Use the `path` param instead. - */ - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; - loader: () => Promise; + title?: string; + icon?: IconElement; + loader?: () => Promise; routeRef?: RouteRef; + /** + * Hide the default plugin page header, making the page fill up all available space. + */ + noHeader?: boolean; }, - { config, node }, + { config, node, inputs }, ) { + const title = config.title ?? params.title; + const icon = params.icon; + const pluginId = node.spec.plugin.pluginId; + const noHeader = params.noHeader ?? false; + const resolvedTitle = + title ?? node.spec.plugin.title ?? node.spec.plugin.pluginId; + const resolvedIcon = icon ?? node.spec.plugin.icon; + const titleRouteRef = + (node.spec.plugin.routes as { root?: RouteRef }).root ?? params.routeRef; + yield coreExtensionData.routePath(config.path ?? params.path); - yield coreExtensionData.reactElement( - ExtensionBoundary.lazy(node, params.loader), - ); + if (params.loader) { + const loader = params.loader; + const PageContent = () => { + const routeResolutionApi = useApi(routeResolutionApiRef); + const titleLink = resolveTitleLink(routeResolutionApi, titleRouteRef); + const headerActionsApi = useApi(pluginHeaderActionsApiRef); + const headerActions = headerActionsApi.getPluginHeaderActions(pluginId); + + return ( + + {ExtensionBoundary.lazy(node, loader)} + + ); + }; + yield coreExtensionData.reactElement(); + } else if (inputs.pages.length > 0) { + // Parent page with sub-pages - render header with tabs + const tabs: PageLayoutTab[] = inputs.pages.map(page => { + const path = page.get(coreExtensionData.routePath); + const tabTitle = page.get(coreExtensionData.title); + const tabIcon = page.get(coreExtensionData.icon); + return { + id: path, + label: tabTitle || path, + icon: tabIcon, + href: path, + }; + }); + + const PageContent = () => { + const firstPagePath = inputs.pages[0]?.get(coreExtensionData.routePath); + const routeResolutionApi = useApi(routeResolutionApiRef); + const titleLink = resolveTitleLink(routeResolutionApi, titleRouteRef); + + const headerActionsApi = useApi(pluginHeaderActionsApiRef); + const headerActions = headerActionsApi.getPluginHeaderActions(pluginId); + + return ( + + + {firstPagePath && ( + } + /> + )} + {inputs.pages.map((page, index) => { + const path = page.get(coreExtensionData.routePath); + const element = page.get(coreExtensionData.reactElement); + return ( + + ); + })} + + + ); + }; + + yield coreExtensionData.reactElement(); + } else { + const PageContent = () => { + const routeResolutionApi = useApi(routeResolutionApiRef); + const titleLink = resolveTitleLink(routeResolutionApi, titleRouteRef); + const headerActionsApi = useApi(pluginHeaderActionsApiRef); + const headerActions = headerActionsApi.getPluginHeaderActions(pluginId); + return ( + + ); + }; + yield coreExtensionData.reactElement(); + } if (params.routeRef) { yield coreExtensionData.routeRef(params.routeRef); } + if (title) { + yield coreExtensionData.title(title); + } + if (icon) { + yield coreExtensionData.icon(icon); + } }, }); diff --git a/packages/frontend-plugin-api/src/blueprints/PluginHeaderActionBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/PluginHeaderActionBlueprint.tsx new file mode 100644 index 0000000000..eb0c24420d --- /dev/null +++ b/packages/frontend-plugin-api/src/blueprints/PluginHeaderActionBlueprint.tsx @@ -0,0 +1,45 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { lazy as reactLazy } from 'react'; +import { ExtensionBoundary } from '../components'; +import { coreExtensionData, createExtensionBlueprint } from '../wiring'; + +/** + * Creates extensions that provide plugin-scoped header actions. + * + * @remarks + * + * These actions are automatically scoped to the plugin that provides them + * and will appear in the header of all pages belonging to that plugin. + * + * @public + */ +export const PluginHeaderActionBlueprint = createExtensionBlueprint({ + kind: 'plugin-header-action', + attachTo: { id: 'api:app/plugin-header-actions', input: 'actions' }, + output: [coreExtensionData.reactElement], + *factory(params: { loader: () => Promise }, { node }) { + const LazyAction = reactLazy(() => + params.loader().then(element => ({ default: () => element })), + ); + yield coreExtensionData.reactElement( + + + , + ); + }, +}); diff --git a/packages/frontend-plugin-api/src/blueprints/PluginWrapperBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/PluginWrapperBlueprint.tsx new file mode 100644 index 0000000000..5119ae1617 --- /dev/null +++ b/packages/frontend-plugin-api/src/blueprints/PluginWrapperBlueprint.tsx @@ -0,0 +1,78 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentType, ReactNode } from 'react'; +import { + createExtensionBlueprint, + createExtensionBlueprintParams, + createExtensionDataRef, +} from '../wiring'; + +/** + * Defines the structure of a plugin wrapper, optionally including a shared + * hook value. + * + * @remarks + * + * When `useWrapperValue` is provided, the hook is called in a single location + * in the app and the resulting value is forwarded as the `value` prop to the + * component. The hook obeys the rules of React hooks and is not called until a + * component from the plugin is rendered. + * + * @public + */ +export type PluginWrapperDefinition = { + /** + * Creates a shared value that is forwarded as the `value` prop to the + * component. + * + * @remarks + * + * This function obeys the rules of React hooks and is only invoked in a + * single location in the app. Note that the hook will not be called until a + * component from the plugin is rendered. + */ + useWrapperValue?: () => TValue; + component: ComponentType<{ children: ReactNode; value: TValue }>; +}; + +const wrapperDataRef = createExtensionDataRef< + () => Promise +>().with({ id: 'core.plugin-wrapper.loader' }); + +/** + * Creates extensions that wrap plugin extensions with providers. + * + * @public + */ +export const PluginWrapperBlueprint = createExtensionBlueprint({ + kind: 'plugin-wrapper', + attachTo: { id: 'api:app/plugin-wrapper', input: 'wrappers' }, + output: [wrapperDataRef], + dataRefs: { + wrapper: wrapperDataRef, + }, + defineParams(params: { + loader: () => Promise>; + }) { + return createExtensionBlueprintParams( + params as { loader: () => Promise }, + ); + }, + *factory(params) { + yield wrapperDataRef(params.loader); + }, +}); diff --git a/packages/frontend-plugin-api/src/blueprints/SubPageBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/SubPageBlueprint.tsx new file mode 100644 index 0000000000..4d86e864fe --- /dev/null +++ b/packages/frontend-plugin-api/src/blueprints/SubPageBlueprint.tsx @@ -0,0 +1,98 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { z } from 'zod/v4'; +import { IconElement } from '../icons/types'; +import { RouteRef } from '../routing'; +import { coreExtensionData, createExtensionBlueprint } from '../wiring'; +import { ExtensionBoundary } from '../components'; + +/** + * Creates extensions that are sub-page React components attached to a parent page. + * Sub-pages are rendered as tabs within the parent page's header. + * + * @public + * @example + * ```tsx + * const overviewRouteRef = createRouteRef(); + * + * const mySubPage = SubPageBlueprint.make({ + * attachTo: { id: 'page:my-plugin', input: 'pages' }, + * name: 'overview', + * params: { + * path: 'overview', + * title: 'Overview', + * routeRef: overviewRouteRef, + * loader: () => import('./components/Overview').then(m => ), + * }, + * }); + * ``` + */ +export const SubPageBlueprint = createExtensionBlueprint({ + kind: 'sub-page', + attachTo: { relative: { kind: 'page' }, input: 'pages' }, + output: [ + coreExtensionData.routePath, + coreExtensionData.reactElement, + coreExtensionData.title, + coreExtensionData.routeRef.optional(), + coreExtensionData.icon.optional(), + ], + configSchema: { + path: z.string().optional(), + title: z.string().optional(), + }, + *factory( + params: { + /** + * The path for this sub-page, relative to the parent page. Must **not** start with '/'. + * + * @example 'overview', 'settings', 'details' + */ + path: string; + /** + * The title displayed in the tab for this sub-page. + */ + title: string; + /** + * Optional icon for this sub-page, displayed in the tab. + */ + icon?: IconElement; + /** + * A function that returns a promise resolving to the React element to render. + * This enables lazy loading of the sub-page content. + */ + loader: () => Promise; + /** + * Optional route reference for this sub-page. + */ + routeRef?: RouteRef; + }, + { config, node }, + ) { + yield coreExtensionData.routePath(config.path ?? params.path); + yield coreExtensionData.title(config.title ?? params.title); + yield coreExtensionData.reactElement( + ExtensionBoundary.lazy(node, params.loader), + ); + if (params.routeRef) { + yield coreExtensionData.routeRef(params.routeRef); + } + if (params.icon) { + yield coreExtensionData.icon(params.icon); + } + }, +}); diff --git a/packages/frontend-plugin-api/src/blueprints/index.ts b/packages/frontend-plugin-api/src/blueprints/index.ts index c8699b4232..a571f6ef73 100644 --- a/packages/frontend-plugin-api/src/blueprints/index.ts +++ b/packages/frontend-plugin-api/src/blueprints/index.ts @@ -20,17 +20,11 @@ export { } from './AnalyticsImplementationBlueprint'; export { ApiBlueprint } from './ApiBlueprint'; export { AppRootElementBlueprint } from './AppRootElementBlueprint'; -export { AppRootWrapperBlueprint } from './AppRootWrapperBlueprint'; -export { IconBundleBlueprint } from './IconBundleBlueprint'; -export { - NavContentBlueprint, - type NavContentComponent, - type NavContentComponentProps, -} from './NavContentBlueprint'; export { NavItemBlueprint } from './NavItemBlueprint'; export { PageBlueprint } from './PageBlueprint'; -export { RouterBlueprint } from './RouterBlueprint'; -export { SignInPageBlueprint } from './SignInPageBlueprint'; -export { ThemeBlueprint } from './ThemeBlueprint'; -export { TranslationBlueprint } from './TranslationBlueprint'; -export { SwappableComponentBlueprint } from './SwappableComponentBlueprint'; +export { SubPageBlueprint } from './SubPageBlueprint'; +export { PluginHeaderActionBlueprint } from './PluginHeaderActionBlueprint'; +export { + PluginWrapperBlueprint, + type PluginWrapperDefinition, +} from './PluginWrapperBlueprint'; diff --git a/packages/frontend-plugin-api/src/components/AppNodeProvider.test.tsx b/packages/frontend-plugin-api/src/components/AppNodeProvider.test.tsx index fcc3b74c59..0a73f90dde 100644 --- a/packages/frontend-plugin-api/src/components/AppNodeProvider.test.tsx +++ b/packages/frontend-plugin-api/src/components/AppNodeProvider.test.tsx @@ -70,12 +70,10 @@ describe('AppNodeProvider', () => { ).toThrow('AppNodeContext v1 not available'); }); expect(error).toEqual([ - expect.objectContaining({ - detail: new Error('AppNodeContext v1 not available'), - }), - expect.objectContaining({ - detail: new Error('AppNodeContext v1 not available'), - }), + expect.stringContaining('Error: AppNodeContext v1 not available'), + expect.objectContaining({ type: 'unhandled-exception' }), + expect.stringContaining('Error: AppNodeContext v1 not available'), + expect.objectContaining({ type: 'unhandled-exception' }), expect.stringContaining( 'The above error occurred in the component:', ), diff --git a/packages/frontend-plugin-api/src/components/ErrorApiBoundary.tsx b/packages/frontend-plugin-api/src/components/ErrorApiBoundary.tsx new file mode 100644 index 0000000000..e195da5d0f --- /dev/null +++ b/packages/frontend-plugin-api/src/components/ErrorApiBoundary.tsx @@ -0,0 +1,50 @@ +/* + * 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 { Component, ErrorInfo, ReactNode } from 'react'; +import { AppNode, ErrorApi } from '../apis'; +import { ForwardedError } from '@backstage/errors'; + +/** @internal */ +export class ErrorApiBoundary extends Component< + { + children: ReactNode; + node: AppNode; + errorApi?: ErrorApi; + }, + { error?: Error } +> { + static getDerivedStateFromError(error: Error) { + return { error }; + } + + state = { error: undefined }; + + componentDidCatch(error: Error, _errorInfo: ErrorInfo) { + const { node, errorApi } = this.props; + errorApi?.post( + new ForwardedError(`Error in extension '${node.spec.id}'`, error), + ); + } + + render() { + if (this.state.error) { + return null; + } + + return this.props.children; + } +} diff --git a/packages/frontend-plugin-api/src/components/ErrorBoundary.tsx b/packages/frontend-plugin-api/src/components/ErrorDisplayBoundary.tsx similarity index 80% rename from packages/frontend-plugin-api/src/components/ErrorBoundary.tsx rename to packages/frontend-plugin-api/src/components/ErrorDisplayBoundary.tsx index 4cde84ee93..1584f18514 100644 --- a/packages/frontend-plugin-api/src/components/ErrorBoundary.tsx +++ b/packages/frontend-plugin-api/src/components/ErrorDisplayBoundary.tsx @@ -14,25 +14,23 @@ * limitations under the License. */ -import { Component, PropsWithChildren } from 'react'; +import { Component, ReactNode } from 'react'; import { FrontendPlugin } from '../wiring'; import { ErrorDisplay } from './DefaultSwappableComponents'; -type ErrorBoundaryProps = PropsWithChildren<{ - plugin?: FrontendPlugin; -}>; -type ErrorBoundaryState = { error?: Error }; - /** @internal */ -export class ErrorBoundary extends Component< - ErrorBoundaryProps, - ErrorBoundaryState +export class ErrorDisplayBoundary extends Component< + { + children: ReactNode; + plugin: FrontendPlugin; + }, + { error?: Error } > { static getDerivedStateFromError(error: Error) { return { error }; } - state: ErrorBoundaryState = { error: undefined }; + state = { error: undefined }; handleErrorReset = () => { this.setState({ error: undefined }); diff --git a/packages/frontend-plugin-api/src/components/ExtensionBoundary.test.tsx b/packages/frontend-plugin-api/src/components/ExtensionBoundary.test.tsx index 6cce114d4d..d05e7c366f 100644 --- a/packages/frontend-plugin-api/src/components/ExtensionBoundary.test.tsx +++ b/packages/frontend-plugin-api/src/components/ExtensionBoundary.test.tsx @@ -14,21 +14,23 @@ * limitations under the License. */ -import { useEffect } from 'react'; +import { useEffect, ReactNode } from 'react'; import { act, screen, waitFor } from '@testing-library/react'; -import { - mockApis, - TestApiProvider, - withLogCollector, -} from '@backstage/test-utils'; +import { withLogCollector } from '@backstage/test-utils'; import { ExtensionBoundary } from './ExtensionBoundary'; import { coreExtensionData, createExtension } from '../wiring'; -import { analyticsApiRef, useAnalytics } from '@backstage/core-plugin-api'; +import { analyticsApiRef } from '../apis/definitions/AnalyticsApi'; +import { useAnalytics } from '../analytics'; import { createRouteRef } from '../routing'; import { createExtensionTester, renderInTestApp, } from '@backstage/frontend-test-utils'; +import { + pluginWrapperApiRef, + PluginWrapperApi, +} from '../apis/definitions/PluginWrapperApi'; +import { useAppNode } from '@backstage/frontend-plugin-api'; const wrapInBoundaryExtension = (element?: JSX.Element) => { const routeRef = createRouteRef(); @@ -93,7 +95,7 @@ describe('ExtensionBoundary', () => { it('should wrap children with analytics context', async () => { const action = 'render'; const subject = 'analytics'; - const analyticsApiMock = mockApis.analytics(); + const analyticsApiMock = { captureEvent: jest.fn() }; const AnalyticsComponent = () => { const analytics = useAnalytics(); @@ -104,11 +106,12 @@ describe('ExtensionBoundary', () => { }; renderInTestApp( - - {createExtensionTester( - wrapInBoundaryExtension(), - ).reactElement()} - , + createExtensionTester( + wrapInBoundaryExtension(), + ).reactElement(), + { + apis: [[analyticsApiRef, analyticsApiMock]], + }, ); await waitFor(() => { @@ -124,9 +127,99 @@ describe('ExtensionBoundary', () => { }); }); - // TODO(Rugvip): Need a way to be able to override APIs in the app to be able to test this properly - // eslint-disable-next-line jest/no-disabled-tests - it.skip('should emit analytics events if routable', async () => { + it('should wrap children with PluginWrapper when provided', async () => { + const text = 'Wrapped Content'; + const TextComponent = () => { + return

    {text}

    ; + }; + + const WrapperComponent = ({ children }: { children: ReactNode }) => { + const node = useAppNode(); + return ( +
    + Wrapper for {node?.spec.id} + {children} +
    + ); + }; + + const pluginWrapperApi: PluginWrapperApi = { + getRootWrapper: + () => + ({ children }: { children: ReactNode }) => + <>{children}, + getPluginWrapper: jest.fn((pluginId: string) => { + if (pluginId === 'app') { + return WrapperComponent; + } + return undefined; + }), + }; + + renderInTestApp( + createExtensionTester( + wrapInBoundaryExtension(), + ).reactElement(), + { + apis: [[pluginWrapperApiRef, pluginWrapperApi]], + }, + ); + + const wrappers = await screen.findAllByTestId('plugin-wrapper'); + expect(wrappers.length).toBeGreaterThan(1); + expect(screen.getByText('Wrapper for app')).toBeInTheDocument(); + expect(screen.getByText('Wrapper for test')).toBeInTheDocument(); + expect(screen.getByText(text)).toBeInTheDocument(); + expect(pluginWrapperApi.getPluginWrapper).toHaveBeenCalledWith('app'); + }); + + it('should handle errors thrown by PluginWrapper with ErrorDisplayBoundary', async () => { + const errorMsg = 'PluginWrapper error'; + const TextComponent = () => { + return

    Content

    ; + }; + + const ThrowingWrapper = () => { + throw new Error(errorMsg); + }; + + const pluginWrapperApi: PluginWrapperApi = { + getRootWrapper: + () => + ({ children }: { children: ReactNode }) => + <>{children}, + getPluginWrapper: jest.fn((pluginId: string) => { + if (pluginId === 'app') { + return ThrowingWrapper; + } + return undefined; + }), + }; + + const { error } = await withLogCollector(['error'], async () => { + renderInTestApp( + createExtensionTester( + wrapInBoundaryExtension(), + ).reactElement(), + { + apis: [[pluginWrapperApiRef, pluginWrapperApi]], + }, + ); + await waitFor(() => + expect(screen.getByText(errorMsg)).toBeInTheDocument(), + ); + }); + + expect(error).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + message: expect.stringContaining(errorMsg), + }), + ]), + ); + }); + + it('should emit analytics events if routable', async () => { const Emitter = () => { const analytics = useAnalytics(); useEffect(() => { @@ -134,29 +227,32 @@ describe('ExtensionBoundary', () => { }); return null; }; - const analyticsApiMock = mockApis.analytics(); + const analyticsApiMock = { captureEvent: jest.fn() }; await act(async () => { renderInTestApp( createExtensionTester( wrapInBoundaryExtension(), ).reactElement(), - // { apis: [[analyticsApiRef, analyticsApiMock]] }, + { apis: [[analyticsApiRef, analyticsApiMock]] }, ); }); + // The navigate event is emitted by the app's routing, with app context expect(analyticsApiMock.captureEvent).toHaveBeenCalledWith( expect.objectContaining({ action: 'navigate', subject: '/', + }), + ); + // The dummy event from our test extension has the correct extension context + expect(analyticsApiMock.captureEvent).toHaveBeenCalledWith( + expect.objectContaining({ + action: 'dummy', context: expect.objectContaining({ - pluginId: 'root', extensionId: 'test', }), }), ); - expect(analyticsApiMock.captureEvent).toHaveBeenCalledWith( - expect.objectContaining({ action: 'dummy' }), - ); }); }); diff --git a/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx b/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx index 848ea64a2c..09cfc51bd7 100644 --- a/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx +++ b/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx @@ -19,17 +19,38 @@ import { ReactNode, Suspense, useEffect, + useMemo, lazy as reactLazy, } from 'react'; -import { AnalyticsContext, useAnalytics } from '@backstage/core-plugin-api'; -import { ErrorBoundary } from './ErrorBoundary'; +import { AnalyticsContext, useAnalytics } from '../analytics'; +import { ErrorDisplayBoundary } from './ErrorDisplayBoundary'; +import { ErrorApiBoundary } from './ErrorApiBoundary'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { routableExtensionRenderedEvent } from '../../../core-plugin-api/src/analytics/Tracker'; -import { AppNode } from '../apis'; +import { AppNode, ErrorApi, errorApiRef, useApi } from '../apis'; +import { + PluginWrapperApi, + pluginWrapperApiRef, +} from '../apis/definitions/PluginWrapperApi'; import { coreExtensionData } from '../wiring'; import { AppNodeProvider } from './AppNodeProvider'; import { Progress } from './DefaultSwappableComponents'; +function useOptionalErrorApi(): ErrorApi | undefined { + try { + return useApi(errorApiRef); + } catch { + return undefined; + } +} + +function useOptionalPluginWrapperApi(): PluginWrapperApi | undefined { + try { + return useApi(pluginWrapperApiRef); + } catch { + return undefined; + } +} type RouteTrackerProps = PropsWithChildren<{ enabled?: boolean; }>; @@ -53,6 +74,7 @@ const RouteTracker = (props: RouteTrackerProps) => { /** @public */ export interface ExtensionBoundaryProps { + errorPresentation?: 'error-api' | 'error-display'; node: AppNode; children: ReactNode; } @@ -61,27 +83,52 @@ export interface ExtensionBoundaryProps { export function ExtensionBoundary(props: ExtensionBoundaryProps) { const { node, children } = props; + const errorApi = useOptionalErrorApi(); + const hasRoutePathOutput = Boolean( node.instance?.getData(coreExtensionData.routePath), ); const plugin = node.spec.plugin; + const pluginId = plugin.pluginId ?? 'app'; + + const pluginWrapperApi = useOptionalPluginWrapperApi(); + + const PluginWrapper = useMemo(() => { + return pluginWrapperApi?.getPluginWrapper(pluginId); + }, [pluginWrapperApi, pluginId]); // Skipping "routeRef" attribute in the new system, the extension "id" should provide more insight const attributes = { extensionId: node.spec.id, - pluginId: node.spec.plugin?.id ?? 'app', + pluginId, }; + let content = ( + + {children} + + ); + + if (PluginWrapper) { + content = {content}; + } + + if (props.errorPresentation === 'error-api') { + content = ( + + {content} + + ); + } else { + content = ( + {content} + ); + } + return ( - }> - - - {children} - - - + }>{content} ); } diff --git a/packages/frontend-plugin-api/src/components/ExtensionSuspense.test.tsx b/packages/frontend-plugin-api/src/components/ExtensionSuspense.test.tsx index 91aaeb2a25..ee1136fca3 100644 --- a/packages/frontend-plugin-api/src/components/ExtensionSuspense.test.tsx +++ b/packages/frontend-plugin-api/src/components/ExtensionSuspense.test.tsx @@ -31,7 +31,7 @@ describe('ExtensionSuspense', () => { ), ); - expect(screen.getByTestId('progress')).toBeInTheDocument(); + expect(screen.getByTestId('core-progress')).toBeInTheDocument(); }); it('should render the lazy loaded children component', async () => { diff --git a/packages/frontend-plugin-api/src/components/ExtensionSuspense.tsx b/packages/frontend-plugin-api/src/components/ExtensionSuspense.tsx index 0ae8b413e4..9439cafed8 100644 --- a/packages/frontend-plugin-api/src/components/ExtensionSuspense.tsx +++ b/packages/frontend-plugin-api/src/components/ExtensionSuspense.tsx @@ -15,7 +15,7 @@ */ import { ReactNode, Suspense } from 'react'; -import { useApp } from '@backstage/core-plugin-api'; +import { Progress } from './DefaultSwappableComponents'; /** @public */ export interface ExtensionSuspenseProps { @@ -26,8 +26,5 @@ export interface ExtensionSuspenseProps { export function ExtensionSuspense(props: ExtensionSuspenseProps) { const { children } = props; - const app = useApp(); - const { Progress } = app.getComponents(); - return }>{children}; } diff --git a/packages/frontend-plugin-api/src/components/PageLayout.tsx b/packages/frontend-plugin-api/src/components/PageLayout.tsx new file mode 100644 index 0000000000..00060e3c6e --- /dev/null +++ b/packages/frontend-plugin-api/src/components/PageLayout.tsx @@ -0,0 +1,148 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ReactNode } from 'react'; +import { IconElement } from '../icons/types'; +import { createSwappableComponent } from './createSwappableComponent'; + +/** + * Tab configuration for page navigation + * @public + */ +export interface PageLayoutTab { + id: string; + label: string; + icon?: IconElement; + href: string; +} + +/** + * @deprecated Use {@link PageLayoutTab} instead + * @public + */ +export type PageTab = PageLayoutTab; + +/** + * Props for the PageLayout component + * @public + */ +export interface PageLayoutProps { + title?: string; + icon?: IconElement; + noHeader?: boolean; + titleLink?: string; + headerActions?: Array; + tabs?: PageLayoutTab[]; + children?: ReactNode; +} + +/** + * Default implementation of PageLayout using plain HTML elements + */ +function DefaultPageLayout(props: PageLayoutProps): JSX.Element { + const { title, icon, headerActions, tabs, children } = props; + + return ( +
    + {(title || tabs) && ( +
    + {title && ( +
    + {icon} + {title} + {headerActions && ( +
    {headerActions}
    + )} +
    + )} + {tabs && tabs.length > 0 && ( + + )} +
    + )} +
    + {children} +
    +
    + ); +} + +/** + * Swappable component for laying out page content with header and navigation. + * The default implementation uses plain HTML elements. + * Apps can override this with a custom implementation (e.g., using \@backstage/ui). + * + * @public + */ +export const PageLayout = createSwappableComponent({ + id: 'core.page-layout', + loader: () => DefaultPageLayout, +}); diff --git a/packages/frontend-plugin-api/src/components/index.ts b/packages/frontend-plugin-api/src/components/index.ts index 450224bdc4..7cb109932a 100644 --- a/packages/frontend-plugin-api/src/components/index.ts +++ b/packages/frontend-plugin-api/src/components/index.ts @@ -25,3 +25,9 @@ export { } from './createSwappableComponent'; export { useAppNode } from './AppNodeProvider'; export * from './DefaultSwappableComponents'; +export { + PageLayout, + type PageLayoutProps, + type PageLayoutTab, + type PageTab, +} from './PageLayout'; diff --git a/packages/frontend-plugin-api/src/icons/index.ts b/packages/frontend-plugin-api/src/icons/index.ts index 9c9e45e54c..913f38d5bf 100644 --- a/packages/frontend-plugin-api/src/icons/index.ts +++ b/packages/frontend-plugin-api/src/icons/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export type { IconComponent } from './types'; +export type { IconComponent, IconElement } from './types'; diff --git a/packages/frontend-plugin-api/src/icons/types.ts b/packages/frontend-plugin-api/src/icons/types.ts index 2b00e1456f..180763ad09 100644 --- a/packages/frontend-plugin-api/src/icons/types.ts +++ b/packages/frontend-plugin-api/src/icons/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { ComponentType } from 'react'; +import { ComponentType, JSX } from 'react'; /** * IconComponent is the common icon type used throughout Backstage when @@ -31,7 +31,26 @@ import { ComponentType } from 'react'; * also describe your use-case and reasoning of the addition. * * @public + * @deprecated Use {@link IconElement} instead, passing `` rather than `MyIcon`. */ export type IconComponent = ComponentType<{ fontSize?: 'medium' | 'large' | 'small' | 'inherit'; }>; + +/** + * The type used for icon elements throughout Backstage. + * + * @remarks + * + * Icon elements should behave like rendering a plain icon directly, for example + * from `@remixicon/react`, and are expected to be sized by the surrounding UI. + * Icons should be exactly 24x24 pixels in size by default. + * + * Using icons from `@remixicon/react` is preferred. Using icons from + * `@material-ui/icons` or `AppIcon` and its variants from + * `@backstage/core-components` is supported while migrating, but deprecated. + * When using those icons, you must set `fontSize="inherit"` on the element. + * + * @public + */ +export type IconElement = JSX.Element | null; diff --git a/packages/frontend-plugin-api/src/routing/ExternalRouteRef.test.ts b/packages/frontend-plugin-api/src/routing/ExternalRouteRef.test.ts index 97d3dd2264..15366c8a7a 100644 --- a/packages/frontend-plugin-api/src/routing/ExternalRouteRef.test.ts +++ b/packages/frontend-plugin-api/src/routing/ExternalRouteRef.test.ts @@ -14,24 +14,23 @@ * limitations under the License. */ -import { - ExternalRouteRef, - createExternalRouteRef, - toInternalExternalRouteRef, -} from './ExternalRouteRef'; +import { ExternalRouteRef, createExternalRouteRef } from './ExternalRouteRef'; +import { OpaqueExternalRouteRef } from '@internal/frontend'; import { AnyRouteRefParams } from './types'; describe('ExternalRouteRef', () => { it('should be created', () => { const routeRef: ExternalRouteRef = createExternalRouteRef(); - const internal = toInternalExternalRouteRef(routeRef); + const internal = OpaqueExternalRouteRef.toInternal(routeRef); expect(internal.getParams()).toEqual([]); expect(String(internal)).toMatch( - /^ExternalRouteRef\{created at '.*ExternalRouteRef\.test\.ts.*'\}$/, + /^externalRouteRef\{id=undefined,at='.*ExternalRouteRef\.test\.ts.*'\}$/, ); internal.setId('some-id'); - expect(String(internal)).toBe('ExternalRouteRef{some-id}'); + expect(String(internal)).toMatch( + /^externalRouteRef\{id=some-id,at='.*ExternalRouteRef\.test\.ts.*'\}$/, + ); }); it('should be created with params', () => { @@ -39,7 +38,7 @@ describe('ExternalRouteRef', () => { x: string; y: string; }> = createExternalRouteRef({ params: ['x', 'y'] }); - const internal = toInternalExternalRouteRef(routeRef); + const internal = OpaqueExternalRouteRef.toInternal(routeRef); expect(internal.getParams()).toEqual(['x', 'y']); }); diff --git a/packages/frontend-plugin-api/src/routing/ExternalRouteRef.ts b/packages/frontend-plugin-api/src/routing/ExternalRouteRef.ts index 3de276f85c..cd2f98f504 100644 --- a/packages/frontend-plugin-api/src/routing/ExternalRouteRef.ts +++ b/packages/frontend-plugin-api/src/routing/ExternalRouteRef.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { RouteRefImpl } from './RouteRef'; +import { OpaqueExternalRouteRef } from '@internal/frontend'; import { describeParentCallSite } from './describeParentCallSite'; import { AnyRouteRefParams } from './types'; @@ -58,37 +58,6 @@ export function toInternalExternalRouteRef< return r; } -/** @internal */ -export function isExternalRouteRef(opaque: { - $$type: string; -}): opaque is ExternalRouteRef { - return opaque.$$type === '@backstage/ExternalRouteRef'; -} - -/** @internal */ -class ExternalRouteRefImpl - extends RouteRefImpl - implements InternalExternalRouteRef -{ - readonly $$type = '@backstage/ExternalRouteRef' as any; - readonly params: string[]; - readonly defaultTarget: string | undefined; - - constructor( - params: string[] = [], - defaultTarget: string | undefined, - creationSite: string, - ) { - super(params, creationSite); - this.params = params; - this.defaultTarget = defaultTarget; - } - - getDefaultTarget() { - return this.defaultTarget; - } -} - /** * Creates a route descriptor, to be later bound to a concrete route by the app. Used to implement cross-plugin route references. * @@ -102,7 +71,7 @@ class ExternalRouteRefImpl export function createExternalRouteRef< TParams extends { [param in TParamKeys]: string } | undefined = undefined, TParamKeys extends string = string, ->(options?: { +>(config?: { /** * The parameters that will be provided to the external route reference. */ @@ -124,9 +93,38 @@ export function createExternalRouteRef< ? TParams : { [param in TParamKeys]: string } > { - return new ExternalRouteRefImpl( - options?.params as string[] | undefined, - options?.defaultTarget, - describeParentCallSite(), - ); + const params = (config?.params ?? []) as string[]; + const creationSite = describeParentCallSite(); + + let id: string | undefined = undefined; + + return OpaqueExternalRouteRef.createInstance('v1', { + T: undefined as unknown as TParams, + getParams() { + return params; + }, + getDescription() { + if (id) { + return id; + } + return `created at '${creationSite}'`; + }, + getDefaultTarget() { + return config?.defaultTarget; + }, + setId(newId: string) { + if (!newId) { + throw new Error(`ExternalRouteRef id must be a non-empty string`); + } + if (id && id !== newId) { + throw new Error( + `ExternalRouteRef was referenced twice as both '${id}' and '${newId}'`, + ); + } + id = newId; + }, + toString(): string { + return `externalRouteRef{id=${id},at='${creationSite}'}`; + }, + }); } diff --git a/packages/frontend-plugin-api/src/routing/RouteRef.test.ts b/packages/frontend-plugin-api/src/routing/RouteRef.test.ts index 570723df9a..1bd546f821 100644 --- a/packages/frontend-plugin-api/src/routing/RouteRef.test.ts +++ b/packages/frontend-plugin-api/src/routing/RouteRef.test.ts @@ -15,18 +15,19 @@ */ import { AnyRouteRefParams } from './types'; -import { RouteRef, createRouteRef, toInternalRouteRef } from './RouteRef'; +import { RouteRef, createRouteRef } from './RouteRef'; +import { OpaqueRouteRef } from '@internal/frontend'; describe('RouteRef', () => { it('should be created and have a mutable ID', () => { const routeRef: RouteRef = createRouteRef(); - const internal = toInternalRouteRef(routeRef); + const internal = OpaqueRouteRef.toInternal(routeRef); expect(internal.T).toBe(undefined); expect(internal.getParams()).toEqual([]); expect(internal.getDescription()).toMatch(/RouteRef\.test\.ts/); expect(String(internal)).toMatch( - /^RouteRef\{created at .*RouteRef\.test\.ts.*\}$/, + /^routeRef\{id=undefined,at='.*RouteRef\.test\.ts.*'\}$/, ); expect(() => internal.setId('')).toThrow( @@ -34,7 +35,9 @@ describe('RouteRef', () => { ); internal.setId('some-id'); - expect(String(internal)).toBe('RouteRef{some-id}'); + expect(String(internal)).toMatch( + /^routeRef\{id=some-id,at='.*RouteRef\.test\.ts.*'\}$/, + ); internal.setId('some-id'); // Should allow same ID expect(() => internal.setId('some-other-id')).toThrow( @@ -49,7 +52,7 @@ describe('RouteRef', () => { }> = createRouteRef({ params: ['x', 'y'], }); - const internal = toInternalRouteRef(routeRef); + const internal = OpaqueRouteRef.toInternal(routeRef); expect(internal.getParams()).toEqual(['x', 'y']); expect(internal.getDescription()).toMatch(/RouteRef\.test\.ts/); }); diff --git a/packages/frontend-plugin-api/src/routing/RouteRef.ts b/packages/frontend-plugin-api/src/routing/RouteRef.ts index 46076e6258..f66c3e7d77 100644 --- a/packages/frontend-plugin-api/src/routing/RouteRef.ts +++ b/packages/frontend-plugin-api/src/routing/RouteRef.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { OpaqueRouteRef } from '@internal/frontend'; import { describeParentCallSite } from './describeParentCallSite'; import { AnyRouteRefParams } from './types'; @@ -33,93 +34,6 @@ export interface RouteRef< readonly T: TParams; } -/** @internal */ -export interface InternalRouteRef< - TParams extends AnyRouteRefParams = AnyRouteRefParams, -> extends RouteRef { - readonly version: 'v1'; - getParams(): string[]; - getDescription(): string; - - alias: string | undefined; - - setId(id: string): void; -} - -/** @internal */ -export function toInternalRouteRef< - TParams extends AnyRouteRefParams = AnyRouteRefParams, ->(resource: RouteRef): InternalRouteRef { - const r = resource as InternalRouteRef; - if (r.$$type !== '@backstage/RouteRef') { - throw new Error(`Invalid RouteRef, bad type '${r.$$type}'`); - } - - return r; -} - -/** @internal */ -export function isRouteRef(opaque: { $$type: string }): opaque is RouteRef { - return opaque.$$type === '@backstage/RouteRef'; -} - -/** @internal */ -export class RouteRefImpl implements InternalRouteRef { - readonly $$type = '@backstage/RouteRef'; - readonly version = 'v1'; - declare readonly T: never; - - #id?: string; - readonly #params: string[]; - readonly #creationSite: string; - readonly #alias?: string; - - constructor( - readonly params: string[] = [], - creationSite: string, - alias?: string, - ) { - this.#params = params; - this.#creationSite = creationSite; - this.#alias = alias; - } - - getParams(): string[] { - return this.#params; - } - - get alias(): string | undefined { - return this.#alias; - } - - getDescription(): string { - if (this.#id) { - return this.#id; - } - return `created at '${this.#creationSite}'`; - } - - get #name() { - return this.$$type.slice('@backstage/'.length); - } - - setId(id: string): void { - if (!id) { - throw new Error(`${this.#name} id must be a non-empty string`); - } - if (this.#id && this.#id !== id) { - throw new Error( - `${this.#name} was referenced twice as both '${this.#id}' and '${id}'`, - ); - } - this.#id = id; - } - - toString(): string { - return `${this.#name}{${this.getDescription()}}`; - } -} - /** * Create a {@link RouteRef} from a route descriptor. * @@ -127,27 +41,47 @@ export class RouteRefImpl implements InternalRouteRef { * @public */ export function createRouteRef< - // Params is the type that we care about and the one to be embedded in the route ref. - // For example, given the params ['name', 'kind'], Params will be {name: string, kind: string} - TParams extends { [param in TParamKeys]: string } | undefined = undefined, - TParamKeys extends string = string, + // ParamKey is narrowed to the literal union of param name strings. + // Defaulting to never means we get undefined params when the array is empty or omitted. + TParamKey extends string = never, >(config?: { /** A list of parameter names that the path that this route ref is bound to must contain */ - readonly params?: string extends TParamKeys - ? (keyof TParams)[] - : TParamKeys[]; + readonly params?: TParamKey[]; aliasFor?: string; }): RouteRef< - keyof TParams extends never - ? undefined - : string extends TParamKeys - ? TParams - : { [param in TParamKeys]: string } + [TParamKey] extends [never] ? undefined : { [param in TParamKey]: string } > { - return new RouteRefImpl( - config?.params as string[] | undefined, - describeParentCallSite(), - config?.aliasFor, - ) as RouteRef; + const params = (config?.params ?? []) as string[]; + const creationSite = describeParentCallSite(); + + let id: string | undefined = undefined; + + return OpaqueRouteRef.createInstance('v1', { + T: undefined as any, + getParams() { + return params; + }, + getDescription() { + if (id) { + return id; + } + return `created at '${creationSite}'`; + }, + alias: config?.aliasFor, + setId(newId: string) { + if (!newId) { + throw new Error(`RouteRef id must be a non-empty string`); + } + if (id && id !== newId) { + throw new Error( + `RouteRef was referenced twice as both '${id}' and '${newId}'`, + ); + } + id = newId; + }, + toString(): string { + return `routeRef{id=${id},at='${creationSite}'}`; + }, + }); } diff --git a/packages/frontend-plugin-api/src/routing/SubRouteRef.test.ts b/packages/frontend-plugin-api/src/routing/SubRouteRef.test.ts index b04728c49b..4746600f40 100644 --- a/packages/frontend-plugin-api/src/routing/SubRouteRef.test.ts +++ b/packages/frontend-plugin-api/src/routing/SubRouteRef.test.ts @@ -15,33 +15,32 @@ */ import { AnyRouteRefParams } from './types'; -import { - SubRouteRef, - createSubRouteRef, - toInternalSubRouteRef, -} from './SubRouteRef'; -import { createRouteRef, toInternalRouteRef } from './RouteRef'; +import { SubRouteRef, createSubRouteRef } from './SubRouteRef'; +import { createRouteRef } from './RouteRef'; +import { OpaqueRouteRef, OpaqueSubRouteRef } from '@internal/frontend'; const parent = createRouteRef(); const parentX = createRouteRef({ params: ['x'] }); describe('SubRouteRef', () => { it('should be created', () => { - const internalParent = toInternalRouteRef(createRouteRef()); + const internalParent = OpaqueRouteRef.toInternal(createRouteRef()); const routeRef: SubRouteRef = createSubRouteRef({ parent: internalParent, path: '/foo', }); - const internal = toInternalSubRouteRef(routeRef); + const internal = OpaqueSubRouteRef.toInternal(routeRef); expect(internal.path).toBe('/foo'); expect(internal.T).toBe(undefined); expect(internal.getParent()).toBe(internalParent); expect(internal.getParams()).toEqual([]); expect(String(internal)).toMatch( - /SubRouteRef\{at \/foo with parent created at '.*SubRouteRef\.test\.ts.*'\}/, + /^subRouteRef\{path='\/foo',parent=routeRef\{id=undefined,at='.*SubRouteRef\.test\.ts.*'\}\}$/, ); internalParent.setId('some-id'); - expect(String(internal)).toBe('SubRouteRef{at /foo with parent some-id}'); + expect(String(internal)).toMatch( + /^subRouteRef\{path='\/foo',parent=routeRef\{id=some-id,at='.*SubRouteRef\.test\.ts.*'\}\}$/, + ); }); it('should be created with params', () => { @@ -49,7 +48,7 @@ describe('SubRouteRef', () => { parent, path: '/foo/:bar', }); - const internal = toInternalSubRouteRef(routeRef); + const internal = OpaqueSubRouteRef.toInternal(routeRef); expect(internal.path).toBe('/foo/:bar'); expect(internal.getParent()).toBe(parent); expect(internal.getParams()).toEqual(['bar']); @@ -64,7 +63,7 @@ describe('SubRouteRef', () => { parent: parentX, path: '/foo/:y/:z', }); - const internal = toInternalSubRouteRef(routeRef); + const internal = OpaqueSubRouteRef.toInternal(routeRef); expect(internal.path).toBe('/foo/:y/:z'); expect(internal.getParent()).toBe(parentX); expect(internal.getParams()).toEqual(['x', 'y', 'z']); @@ -75,7 +74,7 @@ describe('SubRouteRef', () => { parent: parentX, path: '/foo/bar', }); - const internal = toInternalSubRouteRef(routeRef); + const internal = OpaqueSubRouteRef.toInternal(routeRef); expect(internal.path).toBe('/foo/bar'); expect(internal.getParent()).toBe(parentX); expect(internal.getParams()).toEqual(['x']); diff --git a/packages/frontend-plugin-api/src/routing/SubRouteRef.ts b/packages/frontend-plugin-api/src/routing/SubRouteRef.ts index 74996ab4e7..4fe11ff6fb 100644 --- a/packages/frontend-plugin-api/src/routing/SubRouteRef.ts +++ b/packages/frontend-plugin-api/src/routing/SubRouteRef.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -import { RouteRef, toInternalRouteRef } from './RouteRef'; +import { OpaqueRouteRef, OpaqueSubRouteRef } from '@internal/frontend'; +import { RouteRef } from './RouteRef'; import { AnyRouteRefParams } from './types'; // Should match the pattern in react-router @@ -50,59 +51,6 @@ export interface InternalSubRouteRef< getDescription(): string; } -/** @internal */ -export function toInternalSubRouteRef< - TParams extends AnyRouteRefParams = AnyRouteRefParams, ->(resource: SubRouteRef): InternalSubRouteRef { - const r = resource as InternalSubRouteRef; - if (r.$$type !== '@backstage/SubRouteRef') { - throw new Error(`Invalid SubRouteRef, bad type '${r.$$type}'`); - } - - return r; -} - -/** @internal */ -export function isSubRouteRef(opaque: { - $$type: string; -}): opaque is SubRouteRef { - return opaque.$$type === '@backstage/SubRouteRef'; -} - -/** @internal */ -export class SubRouteRefImpl - implements SubRouteRef -{ - readonly $$type = '@backstage/SubRouteRef'; - readonly version = 'v1'; - declare readonly T: never; - - #params: string[]; - #parent: RouteRef; - - constructor(readonly path: string, params: string[], parent: RouteRef) { - this.#params = params; - this.#parent = parent; - } - - getParams(): string[] { - return this.#params; - } - - getParent(): RouteRef { - return this.#parent; - } - - getDescription(): string { - const parent = toInternalRouteRef(this.#parent); - return `at ${this.path} with parent ${parent.getDescription()}`; - } - - toString(): string { - return `SubRouteRef{${this.getDescription()}}`; - } -} - /** * Used in {@link PathParams} type declaration. * @ignore @@ -168,8 +116,9 @@ export function createSubRouteRef< const { path, parent } = config; type Params = PathParams; - const internalParent = toInternalRouteRef(parent); + const internalParent = OpaqueRouteRef.toInternal(parent); const parentParams = internalParent.getParams(); + const parentDescription = internalParent.getDescription(); // Collect runtime parameters from the path, e.g. ['bar', 'baz'] from '/foo/:bar/:baz' const pathParams = path @@ -195,14 +144,22 @@ export function createSubRouteRef< } } - // We ensure that the type of the return type is sane here - const subRouteRef = new SubRouteRefImpl( + return OpaqueSubRouteRef.createInstance('v1', { + T: undefined as unknown as TrimEmptyParams< + MergeParams + >, path, - params as string[], - parent, - ) as SubRouteRef>>; - - // But skip type checking of the return value itself, because the conditional - // type checking of the parent parameter overlap is tricky to express. - return subRouteRef as any; + getParams() { + return params; + }, + getParent() { + return parent; + }, + getDescription() { + return `at ${path} with parent ${parentDescription}`; + }, + toString() { + return `subRouteRef{path='${path}',parent=${parent}}`; + }, + }); } diff --git a/packages/frontend-plugin-api/src/schema/createPortableSchema.test.ts b/packages/frontend-plugin-api/src/schema/createPortableSchema.test.ts new file mode 100644 index 0000000000..71c7bf1a88 --- /dev/null +++ b/packages/frontend-plugin-api/src/schema/createPortableSchema.test.ts @@ -0,0 +1,383 @@ +/* + * 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 { z as zodV3 } from 'zod/v3'; +import { z as zodV4 } from 'zod/v4'; +import { + createConfigSchema, + createDeprecatedConfigSchema, + mergePortableSchemas, +} from './createPortableSchema'; + +describe('createConfigSchema', () => { + describe('zod v4 schemas', () => { + it('should report a missing required field', () => { + const schema = createConfigSchema({ name: zodV4.string() }); + + expect(() => schema.parse({})).toThrow( + "Invalid input: expected string, received undefined at 'name'", + ); + }); + + it('should report a type mismatch', () => { + const schema = createConfigSchema({ count: zodV4.number() }); + + expect(() => schema.parse({ count: 'not a number' })).toThrow( + "Invalid input: expected number, received string at 'count'", + ); + }); + + it('should report nested object errors with the full path', () => { + const schema = createConfigSchema({ + settings: zodV4.object({ port: zodV4.number() }), + }); + + expect(() => schema.parse({ settings: { port: 'abc' } })).toThrow( + "Invalid input: expected number, received string at 'settings.port'", + ); + }); + + it('should combine errors from multiple fields', () => { + const schema = createConfigSchema({ + name: zodV4.string(), + count: zodV4.number(), + }); + + expect(() => schema.parse({})).toThrow( + "Invalid input: expected string, received undefined at 'name'; " + + "Invalid input: expected number, received undefined at 'count'", + ); + }); + + it('should apply defaults for optional fields with defaults', () => { + const schema = createConfigSchema({ + name: zodV4.string(), + mode: zodV4.enum(['fast', 'slow']).default('fast'), + }); + + expect(schema.parse({ name: 'test' })).toEqual({ + name: 'test', + mode: 'fast', + }); + }); + + it('should parse valid config', () => { + const schema = createConfigSchema({ + name: zodV4.string(), + count: zodV4.number().optional(), + }); + + expect(schema.parse({ name: 'hello' })).toEqual({ name: 'hello' }); + expect(schema.parse({ name: 'hello', count: 5 })).toEqual({ + name: 'hello', + count: 5, + }); + }); + }); + + describe('zod v3 rejection', () => { + it('should reject a direct zod v3 schema', () => { + expect(() => createConfigSchema({ name: zodV3.string() as any })).toThrow( + "Config schema for field 'name' uses a Zod v3 schema, which is " + + 'not supported by the `configSchema` option. Upgrade to the ' + + '`zod` v4 package (`zod@^4.0.0`). Note that the `zod/v4` ' + + 'subpath export from the zod v3 package is also not supported, ' + + 'as it does not include JSON Schema conversion.', + ); + }); + }); + + describe('schema creation errors', () => { + it('should reject a schema that is not a valid Standard Schema', () => { + expect(() => + createConfigSchema({ bad: { notASchema: true } as any }), + ).toThrow("Config schema for field 'bad' is not a valid Standard Schema"); + }); + + it('should reject a Standard Schema without JSON Schema support', () => { + const fakeStandardSchema = { + '~standard': { + version: 1, + vendor: 'fake', + validate: () => ({ value: 'ok' }), + }, + }; + + expect(() => + createConfigSchema({ field: fakeStandardSchema as any }), + ).toThrow( + "Config schema for field 'field' does not support JSON Schema conversion", + ); + }); + }); + + describe('JSON Schema generation', () => { + it('should generate JSON Schema lazily via schema()', () => { + const schema = createConfigSchema({ + title: zodV4.string(), + count: zodV4.number().optional(), + }); + + const result = schema.schema(); + expect(result).toHaveProperty('schema'); + expect(result.schema).toMatchObject({ + type: 'object', + properties: { + title: { type: 'string' }, + count: { type: 'number' }, + }, + required: ['title'], + additionalProperties: false, + }); + }); + + it('should return the schema as a method', () => { + const schema = createConfigSchema({ + title: zodV4.string(), + }); + + const result = schema.schema(); + expect(result.schema.type).toBe('object'); + expect(result.schema.properties).toBeDefined(); + }); + }); + + describe('merging schemas', () => { + it('should merge two zod v4 schemas and parse correctly', () => { + const a = createConfigSchema({ name: zodV4.string() }); + const b = createConfigSchema({ count: zodV4.number().default(0) }); + + const merged = mergePortableSchemas(a, b)!; + expect(merged.parse({ name: 'hello' })).toEqual({ + name: 'hello', + count: 0, + }); + }); + + it('should merge deprecated v3 and new v4 schemas', () => { + const a = createDeprecatedConfigSchema({ name: z => z.string() }); + const b = createConfigSchema({ count: zodV4.number().default(0) }); + + const merged = mergePortableSchemas(a, b)!; + expect(merged.parse({ name: 'hello' })).toEqual({ + name: 'hello', + count: 0, + }); + }); + + it('should produce combined errors after merge', () => { + const a = createDeprecatedConfigSchema({ name: z => z.string() }); + const b = createConfigSchema({ count: zodV4.number() }); + + const merged = mergePortableSchemas(a, b)!; + + expect(() => merged.parse({})).toThrow( + "Missing required value at 'name'; " + + "Invalid input: expected number, received undefined at 'count'", + ); + }); + + it('should produce combined errors for type mismatches after merge', () => { + const a = createDeprecatedConfigSchema({ name: z => z.string() }); + const b = createConfigSchema({ count: zodV4.number() }); + + const merged = mergePortableSchemas(a, b)!; + + expect(() => merged.parse({ name: 123, count: 'not a number' })).toThrow( + "Expected string, received number at 'name'; " + + "Invalid input: expected number, received string at 'count'", + ); + }); + + it('should produce correct JSON Schema after merge', () => { + const a = createDeprecatedConfigSchema({ name: z => z.string() }); + const b = createConfigSchema({ count: zodV4.number().optional() }); + + const merged = mergePortableSchemas(a, b)!; + const result = merged.schema(); + + expect(result.schema).toMatchObject({ + type: 'object', + properties: { + name: { type: 'string' }, + count: { type: 'number' }, + }, + required: ['name'], + additionalProperties: false, + }); + }); + + it('should handle merge with undefined', () => { + const a = createConfigSchema({ name: zodV4.string() }); + + expect(mergePortableSchemas(a, undefined)).toBe(a); + expect(mergePortableSchemas(undefined, a)).toBe(a); + expect(mergePortableSchemas(undefined, undefined)).toBeUndefined(); + }); + + it('should let later fields win when merging overlapping keys', () => { + const a = createDeprecatedConfigSchema({ x: z => z.string() }); + const b = createConfigSchema({ x: zodV4.number() }); + + const merged = mergePortableSchemas(a, b)!; + expect(merged.parse({ x: 42 })).toEqual({ x: 42 }); + expect(() => merged.parse({ x: 'hello' })).toThrow( + "Invalid input: expected number, received string at 'x'", + ); + }); + }); + + describe('edge cases', () => { + it('should handle an empty configSchema', () => { + const schema = createConfigSchema({}); + + expect(schema.parse({})).toEqual({}); + expect(schema.parse(undefined)).toEqual({}); + + const result = schema.schema(); + expect(result.schema).toEqual({ + type: 'object', + properties: {}, + additionalProperties: false, + }); + }); + + it('should throw a clear error for non-object input', () => { + const schema = createConfigSchema({ title: zodV4.string() }); + + expect(() => schema.parse('not an object')).toThrow( + 'Invalid config input, expected object but got string', + ); + expect(() => schema.parse(42)).toThrow( + 'Invalid config input, expected object but got number', + ); + expect(() => schema.parse([1, 2])).toThrow( + 'Invalid config input, expected object but got array', + ); + expect(() => schema.parse(true)).toThrow( + 'Invalid config input, expected object but got boolean', + ); + }); + + it('should not produce undefined keys for absent optional fields', () => { + const schema = createConfigSchema({ + name: zodV4.string(), + title: zodV4.string().optional(), + count: zodV4.number().default(42), + }); + + const result = schema.parse({ name: 'hello' }); + expect(result).toEqual({ name: 'hello', count: 42 }); + expect(Object.keys(result as object)).toEqual(['name', 'count']); + }); + + it('should not mark defaulted fields as required in JSON Schema', () => { + const schema = createConfigSchema({ + name: zodV4.string(), + title: zodV4.string().default('hello'), + count: zodV4.number().optional(), + }); + + const result = schema.schema(); + expect(result.schema).toMatchObject({ + type: 'object', + required: ['name'], + }); + }); + }); +}); + +describe('createDeprecatedConfigSchema', () => { + it('should report a missing required field', () => { + const schema = createDeprecatedConfigSchema({ name: z => z.string() }); + + expect(() => schema.parse({})).toThrow("Missing required value at 'name'"); + expect(() => schema.parse(undefined)).toThrow( + "Missing required value at 'name'", + ); + }); + + it('should report a type mismatch', () => { + const schema = createDeprecatedConfigSchema({ count: z => z.number() }); + + expect(() => schema.parse({ count: 'not a number' })).toThrow( + "Expected number, received string at 'count'", + ); + }); + + it('should report nested object errors with the full path', () => { + const schema = createDeprecatedConfigSchema({ + settings: z => z.object({ port: z.number() }), + }); + + expect(() => schema.parse({ settings: { port: 'abc' } })).toThrow( + "Expected number, received string at 'settings.port'", + ); + }); + + it('should report errors for union types', () => { + const schema = createDeprecatedConfigSchema({ + value: z => z.union([z.string(), z.number()]), + }); + + expect(() => schema.parse({})).toThrow("Missing required value at 'value'"); + }); + + it('should apply defaults for optional fields with defaults', () => { + const schema = createDeprecatedConfigSchema({ + name: z => z.string(), + mode: z => z.enum(['fast', 'slow']).default('fast'), + }); + + expect(schema.parse({ name: 'test' })).toEqual({ + name: 'test', + mode: 'fast', + }); + }); + + it('should generate JSON Schema lazily via schema()', () => { + const schema = createDeprecatedConfigSchema({ + title: z => z.string(), + count: z => z.number().optional(), + }); + + const result = schema.schema(); + expect(result).toHaveProperty('schema'); + expect(result.schema).toMatchObject({ + type: 'object', + properties: { + title: { type: 'string' }, + count: { type: 'number' }, + }, + required: ['title'], + additionalProperties: false, + }); + }); + + it('should not mark defaulted fields as required in JSON Schema', () => { + const schema = createDeprecatedConfigSchema({ + name: z => z.string(), + title: z => z.string().default('hello'), + count: z => z.number().optional(), + }); + + const result = schema.schema(); + expect(result.schema).toMatchObject({ + type: 'object', + required: ['name'], + }); + }); +}); diff --git a/packages/frontend-plugin-api/src/schema/createPortableSchema.ts b/packages/frontend-plugin-api/src/schema/createPortableSchema.ts new file mode 100644 index 0000000000..29c3356b2d --- /dev/null +++ b/packages/frontend-plugin-api/src/schema/createPortableSchema.ts @@ -0,0 +1,357 @@ +/* + * 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 { JsonObject } from '@backstage/types'; +import { z as zodV3, type ZodType } from 'zod/v3'; +import zodToJsonSchema from 'zod-to-json-schema'; +import { PortableSchema } from './types'; + +/** + * The Standard Schema interface. + * @public + */ +export type { StandardSchemaV1 } from '@standard-schema/spec'; +import { type StandardSchemaV1 } from '@standard-schema/spec'; + +/** @internal */ +export function createDeprecatedConfigSchema( + fields: Record ZodType>, +): MergeablePortableSchema { + const resolved: Record = {}; + + for (const [key, field] of Object.entries(fields)) { + resolved[key] = resolveZodField(key, field(zodV3)); + } + + return buildPortableSchema(resolved); +} + +/** + * Per-field resolved schema — validation is eager, JSON Schema is lazy. + * @internal + */ +interface ResolvedField { + validate(value: unknown): { value: unknown } | { errors: string[] }; + toJsonSchema(): JsonObject; + required: boolean; +} + +/** + * Internal representation that carries per-field resolvers alongside the + * public PortableSchema surface, enabling schema merging. + * @internal + */ +export interface MergeablePortableSchema + extends PortableSchema { + /** @internal */ + readonly _fields: Record; +} + +/** + * Resolves each field, eagerly validates JSON Schema support, and returns + * a PortableSchema whose JSON Schema conversion is lazy. + * @internal + */ +export function createConfigSchema( + fields: Record, +): MergeablePortableSchema { + const resolved: Record = {}; + + for (const [key, field] of Object.entries(fields)) { + resolved[key] = resolveField(key, field); + } + + return buildPortableSchema(resolved); +} + +/** + * Combines schemas from different sources for blueprint + override + * composition. Each source may use a completely different schema library. + * Because we track per-field resolvers, merging is just combining the + * field maps. + * @internal + */ +export function mergePortableSchemas( + a: MergeablePortableSchema | undefined, + b: MergeablePortableSchema | undefined, +): MergeablePortableSchema | undefined { + if (!a && !b) { + return undefined; + } + if (!a) { + return b as MergeablePortableSchema; + } + if (!b) { + return a as MergeablePortableSchema; + } + + return buildPortableSchema({ + ...a._fields, + ...b._fields, + }); +} + +/** + * Assembles resolved fields into a PortableSchema with per-field + * validation (eager) and lazy JSON Schema generation. + */ +function buildPortableSchema( + fields: Record, +): MergeablePortableSchema { + function parse(input: unknown) { + if ( + input !== undefined && + input !== null && + (typeof input !== 'object' || Array.isArray(input)) + ) { + throw new Error( + `Invalid config input, expected object but got ${ + Array.isArray(input) ? 'array' : typeof input + }`, + ); + } + const inputObj = (input ?? {}) as Record; + const result: Record = {}; + const errors: string[] = []; + + for (const [key, field] of Object.entries(fields)) { + const validated = field.validate(inputObj[key]); + if ('errors' in validated) { + errors.push(...validated.errors); + } else if (validated.value !== undefined || key in inputObj) { + result[key] = validated.value; + } + } + + if (errors.length > 0) { + throw new Error(errors.join('; ')); + } + + return result as TOutput; + } + + let cached: { schema: JsonObject } | undefined; + + const result: MergeablePortableSchema = { + parse, + schema() { + if (!cached) { + cached = { schema: buildObjectJsonSchema(fields) }; + } + return cached; + }, + } as MergeablePortableSchema; + + Object.defineProperty(result, '_fields', { + value: fields, + enumerable: false, + }); + + return result; +} + +/** + * Wraps a single schema into a ResolvedField. Eagerly validates that + * JSON Schema conversion will be possible, but defers the actual + * conversion until toJsonSchema() is called. + */ +function resolveField(key: string, schema: unknown): ResolvedField { + if (isZodV3Type(schema)) { + throw new Error( + `Config schema for field '${key}' uses a Zod v3 schema, which is ` + + `not supported by the \`configSchema\` option. Upgrade to the ` + + `\`zod\` v4 package (\`zod@^4.0.0\`). Note that the \`zod/v4\` ` + + `subpath export from the zod v3 package is also not supported, ` + + `as it does not include JSON Schema conversion.`, + ); + } + if (isStandardSchema(schema)) { + if (!hasJsonSchemaConverter(schema)) { + throw new Error( + `Config schema for field '${key}' does not support JSON Schema ` + + `conversion. Use a schema library that implements the Standard ` + + `JSON Schema interface (like zod v4+).`, + ); + } + return resolveStandardField(key, schema); + } + throw new Error( + `Config schema for field '${key}' is not a valid Standard Schema`, + ); +} + +function resolveZodField(key: string, schema: ZodType): ResolvedField { + const wrapper = zodV3.object({ [key]: schema }); + + return { + validate(value) { + const result = wrapper.safeParse({ [key]: value }); + if (result.success) { + return { value: result.data[key] }; + } + return { errors: result.error.issues.map(formatZodIssue) }; + }, + toJsonSchema() { + const wholeJsonSchema = zodToJsonSchema(wrapper) as Record; + return (wholeJsonSchema.properties?.[key] ?? {}) as JsonObject; + }, + required: !schema.isOptional(), + }; +} + +function resolveStandardField( + key: string, + schema: StandardSchemaV1 & { + '~standard': { jsonSchema: { input: Function } }; + }, +): ResolvedField { + const required = isFieldRequired(schema); + + return { + validate(value) { + const result = schema['~standard'].validate(value); + if (result instanceof Promise) { + throw new Error( + `Config schema for '${key}' returned a Promise — async schemas are not supported`, + ); + } + if (result.issues) { + return { + errors: Array.from(result.issues).map(issue => + formatStandardIssue(key, issue), + ), + }; + } + return { value: result.value }; + }, + toJsonSchema() { + const raw = schema['~standard'].jsonSchema.input({ target: 'draft-07' }); + const { $schema: _, ...rest } = raw; + return rest as JsonObject; + }, + required, + }; +} + +/** Assembles per-field JSON Schemas into a single object-level JSON Schema. */ +function buildObjectJsonSchema( + fields: Record, +): JsonObject { + const properties: Record = {}; + const required: string[] = []; + + for (const [key, field] of Object.entries(fields)) { + properties[key] = field.toJsonSchema(); + if (field.required) { + required.push(key); + } + } + + const schema: Record = { + type: 'object', + properties, + additionalProperties: false, + }; + + if (required.length > 0) { + schema.required = required; + } + + return schema as JsonObject; +} + +function isZodV3Type(value: unknown): value is ZodType { + return ( + typeof value === 'object' && + value !== null && + typeof (value as any)._parse === 'function' && + '_def' in value + ); +} + +function isStandardSchema(value: unknown): value is StandardSchemaV1 { + return ( + typeof value === 'object' && + value !== null && + '~standard' in value && + typeof (value as any)['~standard']?.validate === 'function' + ); +} + +function hasJsonSchemaConverter( + schema: StandardSchemaV1, +): schema is StandardSchemaV1 & { + '~standard': { jsonSchema: { input: Function } }; +} { + const std = schema['~standard'] as any; + return typeof std?.jsonSchema?.input === 'function'; +} + +function isFieldRequired(schema: StandardSchemaV1): boolean { + const result = schema['~standard'].validate(undefined); + if (result instanceof Promise) { + return true; + } + return (result.issues?.length ?? 0) > 0; +} + +function formatZodIssue(issue: { + code: string; + message: string; + path: Array; + unionErrors?: Array<{ issues: Array }>; +}): string { + if (issue.code === 'invalid_union' && issue.unionErrors?.[0]?.issues?.[0]) { + return formatZodIssue(issue.unionErrors[0].issues[0]); + } + let message = issue.message; + if (message === 'Required') { + message = 'Missing required value'; + } + if (issue.path.length) { + message += ` at '${issue.path.join('.')}'`; + } + return message; +} + +function formatStandardIssue( + fieldKey: string, + issue: StandardSchemaV1.Issue, +): string { + let message = issue.message; + if (message === 'Required') { + message = 'Missing required value'; + } + const path = issue.path?.length + ? `${fieldKey}.${issue.path + .map((p: PropertyKey | StandardSchemaV1.PathSegment) => + typeof p === 'object' ? p.key : p, + ) + .join('.')}` + : fieldKey; + return `${message} at '${path}'`; +} + +/** @internal */ +export function warnConfigSchemaPropDeprecation(callSite: string) { + // eslint-disable-next-line no-console + console.warn( + `DEPRECATION WARNING: The \`config.schema\` option for extension config is deprecated. ` + + `Use the \`configSchema\` option instead with Standard Schema values, for example ` + + `\`configSchema: { title: z.string() }\` using the \`zod\` v4 package ` + + `(\`zod@^4.0.0\`). Declared at ${callSite}`, + ); +} diff --git a/packages/frontend-plugin-api/src/schema/createSchemaFromZod.test.ts b/packages/frontend-plugin-api/src/schema/createSchemaFromZod.test.ts deleted file mode 100644 index 477a1a58c6..0000000000 --- a/packages/frontend-plugin-api/src/schema/createSchemaFromZod.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { createSchemaFromZod } from './createSchemaFromZod'; - -describe('createSchemaFromZod', () => { - it('should provide nice parse errors', () => { - const { parse } = createSchemaFromZod(z => - z.object({ - foo: z.union([z.string(), z.number()]), - derp: z.object({ bar: z.number() }), - }), - ); - - expect(() => { - // @ts-expect-error - return parse({ derp: { bar: 'derp' } }); - }).toThrow( - `Missing required value at 'foo'; Expected number, received string at 'derp.bar'`, - ); - expect(() => { - // @ts-expect-error - return parse(undefined); - }).toThrow(`Missing required value`); - expect(() => { - // @ts-expect-error - return parse('derp'); - }).toThrow(`Expected object, received string`); - }); -}); diff --git a/packages/frontend-plugin-api/src/schema/createSchemaFromZod.ts b/packages/frontend-plugin-api/src/schema/createSchemaFromZod.ts deleted file mode 100644 index 85f9d5c44a..0000000000 --- a/packages/frontend-plugin-api/src/schema/createSchemaFromZod.ts +++ /dev/null @@ -1,56 +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 { JsonObject } from '@backstage/types'; -import { z, ZodSchema, ZodTypeDef } from 'zod'; -import zodToJsonSchema from 'zod-to-json-schema'; -import { PortableSchema } from './types'; - -/** - * @internal - */ -export function createSchemaFromZod( - schemaCreator: (zImpl: typeof z) => ZodSchema, -): PortableSchema { - const schema = schemaCreator(z); - return { - // TODO: Types allow z.array etc here but it will break stuff - parse: input => { - const result = schema.safeParse(input); - if (result.success) { - return result.data; - } - - throw new Error(result.error.issues.map(formatIssue).join('; ')); - }, - // TODO: Verify why we are not compatible with the latest zodToJsonSchema. - schema: zodToJsonSchema(schema) as JsonObject, - }; -} - -function formatIssue(issue: z.ZodIssue): string { - if (issue.code === 'invalid_union') { - return formatIssue(issue.unionErrors[0].issues[0]); - } - let message = issue.message; - if (message === 'Required') { - message = `Missing required value`; - } - if (issue.path.length) { - message += ` at '${issue.path.join('.')}'`; - } - return message; -} diff --git a/packages/frontend-plugin-api/src/schema/index.ts b/packages/frontend-plugin-api/src/schema/index.ts index a8f92a37f4..35d5465cbe 100644 --- a/packages/frontend-plugin-api/src/schema/index.ts +++ b/packages/frontend-plugin-api/src/schema/index.ts @@ -15,3 +15,4 @@ */ export { type PortableSchema } from './types'; +export { type StandardSchemaV1 } from './createPortableSchema'; diff --git a/packages/frontend-plugin-api/src/schema/types.ts b/packages/frontend-plugin-api/src/schema/types.ts index 5644068e90..a1c78513af 100644 --- a/packages/frontend-plugin-api/src/schema/types.ts +++ b/packages/frontend-plugin-api/src/schema/types.ts @@ -17,7 +17,7 @@ import { JsonObject } from '@backstage/types'; /** @public */ -export type PortableSchema = { +export type PortableSchema = { parse: (input: TInput) => TOutput; - schema: JsonObject; + schema: () => { schema: JsonObject }; }; diff --git a/packages/core-plugin-api/src/translation/TranslationMessages.test.ts b/packages/frontend-plugin-api/src/translation/TranslationMessages.test.ts similarity index 100% rename from packages/core-plugin-api/src/translation/TranslationMessages.test.ts rename to packages/frontend-plugin-api/src/translation/TranslationMessages.test.ts diff --git a/packages/core-plugin-api/src/translation/TranslationMessages.ts b/packages/frontend-plugin-api/src/translation/TranslationMessages.ts similarity index 95% rename from packages/core-plugin-api/src/translation/TranslationMessages.ts rename to packages/frontend-plugin-api/src/translation/TranslationMessages.ts index 0108fb9f18..83dd2ef557 100644 --- a/packages/core-plugin-api/src/translation/TranslationMessages.ts +++ b/packages/frontend-plugin-api/src/translation/TranslationMessages.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +import { TranslationRef } from './TranslationRef'; /** * Represents a collection of messages to be provided for a given translation ref. * - * @alpha + * @public * @remarks * * This collection of messages can either be used directly as an override for the @@ -43,7 +43,7 @@ export interface TranslationMessages< /** * Options for {@link createTranslationMessages}. * - * @alpha + * @public */ export interface TranslationMessagesOptions< TId extends string, @@ -62,7 +62,7 @@ export interface TranslationMessagesOptions< /** * Creates a collection of messages for a given translation ref. * - * @alpha + * @public */ export function createTranslationMessages< TId extends string, diff --git a/packages/core-plugin-api/src/translation/TranslationRef.test.ts b/packages/frontend-plugin-api/src/translation/TranslationRef.test.ts similarity index 100% rename from packages/core-plugin-api/src/translation/TranslationRef.test.ts rename to packages/frontend-plugin-api/src/translation/TranslationRef.test.ts diff --git a/packages/core-plugin-api/src/translation/TranslationRef.ts b/packages/frontend-plugin-api/src/translation/TranslationRef.ts similarity index 90% rename from packages/core-plugin-api/src/translation/TranslationRef.ts rename to packages/frontend-plugin-api/src/translation/TranslationRef.ts index c2ac7a5ce3..796c23114b 100644 --- a/packages/core-plugin-api/src/translation/TranslationRef.ts +++ b/packages/frontend-plugin-api/src/translation/TranslationRef.ts @@ -19,7 +19,7 @@ import { TranslationResource, } from './TranslationResource'; -/** @alpha */ +/** @public */ export interface TranslationRef< TId extends string = string, TMessages extends { [key in string]: string } = { [key in string]: string }, @@ -51,16 +51,16 @@ type FlattenedMessages = // final step of flipping this unions around to an intersection by inferring the function parameter. { [TKey in keyof TMessages]: ( - _: TMessages[TKey] extends infer TValue // "local variable" for the value - ? TValue extends AnyNestedMessages - ? FlattenedMessages extends infer TNested // Recurse into nested messages, "local variable" for the result - ? { - [TNestedKey in keyof TNested as `${TKey & string}.${TNestedKey & - string}`]: TNested[TNestedKey]; - } - : never - : { [_ in TKey]: TValue } // Primitive object values are passed through with the same key - : never, + _: TMessages[TKey] extends string + ? { [_ in TKey]: TMessages[TKey] } // String values are leaf nodes, passed through with the same key + : TMessages[TKey] extends AnyNestedMessages + ? FlattenedMessages extends infer TNested // Recurse into nested messages, "local variable" for the result + ? { + [TNestedKey in keyof TNested as `${TKey & string}.${TNestedKey & + string}`]: TNested[TNestedKey]; + } + : never + : never, // Unreachable: TMessages[TKey] is always string or AnyNestedMessages ) => void; // The `[keyof TMessages]` extracts the object values union from our flattened structure, still wrapped up in function parameters. // The `extends (_: infer TIntersection) => void` flips the union to an intersection, at which point we have the correct type. @@ -83,7 +83,7 @@ export interface InternalTranslationRef< getDefaultResource(): TranslationResource | undefined; } -/** @alpha */ +/** @public */ export interface TranslationRefOptions< TId extends string, TNestedMessages extends AnyNestedMessages, @@ -164,7 +164,7 @@ class TranslationRefImpl< } } -/** @alpha */ +/** @public */ export function createTranslationRef< TId extends string, const TNestedMessages extends AnyNestedMessages, diff --git a/packages/core-plugin-api/src/translation/TranslationResource.test.ts b/packages/frontend-plugin-api/src/translation/TranslationResource.test.ts similarity index 100% rename from packages/core-plugin-api/src/translation/TranslationResource.test.ts rename to packages/frontend-plugin-api/src/translation/TranslationResource.test.ts diff --git a/packages/core-plugin-api/src/translation/TranslationResource.ts b/packages/frontend-plugin-api/src/translation/TranslationResource.ts similarity index 94% rename from packages/core-plugin-api/src/translation/TranslationResource.ts rename to packages/frontend-plugin-api/src/translation/TranslationResource.ts index 0184c55b98..c317bda381 100644 --- a/packages/core-plugin-api/src/translation/TranslationResource.ts +++ b/packages/frontend-plugin-api/src/translation/TranslationResource.ts @@ -14,12 +14,10 @@ * limitations under the License. */ -import { - TranslationMessages, - TranslationRef, -} from '@backstage/core-plugin-api/alpha'; +import { TranslationMessages } from './TranslationMessages'; +import { TranslationRef } from './TranslationRef'; -/** @alpha */ +/** @public */ export interface TranslationResource { $$type: '@backstage/TranslationResource'; id: TId; @@ -55,7 +53,7 @@ export function toInternalTranslationResource( return r; } -/** @alpha */ +/** @public */ export interface TranslationResourceOptions< TId extends string, TMessages extends { [key in string]: string }, @@ -72,7 +70,7 @@ export interface TranslationResourceOptions< translations: TTranslations; } -/** @alpha */ +/** @public */ export function createTranslationResource< TId extends string, TMessages extends { [key in string]: string }, diff --git a/packages/core-plugin-api/src/translation/__fixtures__/counting-de.ts b/packages/frontend-plugin-api/src/translation/__fixtures__/counting-de.ts similarity index 100% rename from packages/core-plugin-api/src/translation/__fixtures__/counting-de.ts rename to packages/frontend-plugin-api/src/translation/__fixtures__/counting-de.ts diff --git a/packages/core-plugin-api/src/translation/__fixtures__/counting-sv.json b/packages/frontend-plugin-api/src/translation/__fixtures__/counting-sv.json similarity index 100% rename from packages/core-plugin-api/src/translation/__fixtures__/counting-sv.json rename to packages/frontend-plugin-api/src/translation/__fixtures__/counting-sv.json diff --git a/packages/core-plugin-api/src/translation/__fixtures__/fruits-de.json b/packages/frontend-plugin-api/src/translation/__fixtures__/fruits-de.json similarity index 100% rename from packages/core-plugin-api/src/translation/__fixtures__/fruits-de.json rename to packages/frontend-plugin-api/src/translation/__fixtures__/fruits-de.json diff --git a/packages/core-plugin-api/src/translation/__fixtures__/fruits-sv.ts b/packages/frontend-plugin-api/src/translation/__fixtures__/fruits-sv.ts similarity index 100% rename from packages/core-plugin-api/src/translation/__fixtures__/fruits-sv.ts rename to packages/frontend-plugin-api/src/translation/__fixtures__/fruits-sv.ts diff --git a/packages/core-plugin-api/src/translation/__fixtures__/refs.ts b/packages/frontend-plugin-api/src/translation/__fixtures__/refs.ts similarity index 100% rename from packages/core-plugin-api/src/translation/__fixtures__/refs.ts rename to packages/frontend-plugin-api/src/translation/__fixtures__/refs.ts diff --git a/packages/frontend-plugin-api/src/translation/index.ts b/packages/frontend-plugin-api/src/translation/index.ts index ef3ccccbe3..121ec62c72 100644 --- a/packages/frontend-plugin-api/src/translation/index.ts +++ b/packages/frontend-plugin-api/src/translation/index.ts @@ -17,12 +17,16 @@ export { type TranslationMessages, type TranslationMessagesOptions, + createTranslationMessages, +} from './TranslationMessages'; +export { type TranslationResource, type TranslationResourceOptions, + createTranslationResource, +} from './TranslationResource'; +export { type TranslationRef, type TranslationRefOptions, - createTranslationMessages, - createTranslationResource, createTranslationRef, - useTranslationRef, -} from '@backstage/core-plugin-api/alpha'; +} from './TranslationRef'; +export { useTranslationRef } from './useTranslationRef'; diff --git a/packages/core-plugin-api/src/translation/useTranslationRef.test.tsx b/packages/frontend-plugin-api/src/translation/useTranslationRef.test.tsx similarity index 98% rename from packages/core-plugin-api/src/translation/useTranslationRef.test.tsx rename to packages/frontend-plugin-api/src/translation/useTranslationRef.test.tsx index b1fbd8a904..a7f5bd41ed 100644 --- a/packages/core-plugin-api/src/translation/useTranslationRef.test.tsx +++ b/packages/frontend-plugin-api/src/translation/useTranslationRef.test.tsx @@ -27,11 +27,11 @@ import { useTranslationRef } from './useTranslationRef'; import { I18nextTranslationApi } from '../../../core-app-api/src/apis/implementations/TranslationApi'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { AppLanguageSelector } from '../../..//core-app-api/src/apis/implementations/AppLanguageApi'; +import { createTranslationResource } from './TranslationResource'; import { - createTranslationResource, TranslationApi, translationApiRef, -} from '../alpha'; +} from '../apis/definitions/TranslationApi'; import { ErrorApi, errorApiRef } from '../apis'; const plainRef = createTranslationRef({ diff --git a/packages/core-plugin-api/src/translation/useTranslationRef.ts b/packages/frontend-plugin-api/src/translation/useTranslationRef.ts similarity index 98% rename from packages/core-plugin-api/src/translation/useTranslationRef.ts rename to packages/frontend-plugin-api/src/translation/useTranslationRef.ts index b874ac93d8..2e029f82fa 100644 --- a/packages/core-plugin-api/src/translation/useTranslationRef.ts +++ b/packages/frontend-plugin-api/src/translation/useTranslationRef.ts @@ -20,13 +20,13 @@ import { translationApiRef, TranslationFunction, TranslationSnapshot, -} from '../apis/alpha'; +} from '../apis/definitions/TranslationApi'; import { TranslationRef } from './TranslationRef'; // Make sure we don't fill the logs with loading errors for the same ref const loggedRefs = new WeakSet>(); -/** @alpha */ +/** @public */ export const useTranslationRef = < TMessages extends { [key in string]: string }, >( diff --git a/packages/frontend-defaults/src/createPublicSignInApp.tsx b/packages/frontend-plugin-api/src/wiring/constants.ts similarity index 58% rename from packages/frontend-defaults/src/createPublicSignInApp.tsx rename to packages/frontend-plugin-api/src/wiring/constants.ts index 0015d9403f..5300cfc27f 100644 --- a/packages/frontend-defaults/src/createPublicSignInApp.tsx +++ b/packages/frontend-plugin-api/src/wiring/constants.ts @@ -14,16 +14,17 @@ * limitations under the License. */ -import { appModulePublicSignIn } from '@backstage/plugin-app/alpha'; -import { CreateAppOptions, createApp } from './createApp'; +// NOTE: changing any of these constants need to be reflected in +// @backstage/backend-plugin-api/src/wiring/constants.ts as well /** + * The pattern that IDs must match. + * + * @remarks + * ids must only contain the letters `a` through `z` and digits, in groups separated by + * dashes. Additionally, the very first character of the first group + * must be a letter, not a digit + * * @public - * @deprecated Use {@link @backstage/plugin-app/alpha#appModulePublicSignIn} instead. */ -export function createPublicSignInApp(options?: CreateAppOptions) { - return createApp({ - ...options, - features: [...(options?.features ?? []), appModulePublicSignIn], - }); -} +export const ID_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/i; diff --git a/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts b/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts index b778539101..9b89fe03cb 100644 --- a/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts +++ b/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts @@ -15,12 +15,15 @@ */ import { JSX } from 'react'; +import { IconElement } from '../icons/types'; import { RouteRef } from '../routing/RouteRef'; import { createExtensionDataRef } from './createExtensionDataRef'; /** @public */ export const coreExtensionData = { title: createExtensionDataRef().with({ id: 'core.title' }), + /** An icon element for the extension. Should be exactly 24x24 pixels. */ + icon: createExtensionDataRef().with({ id: 'core.icon' }), reactElement: createExtensionDataRef().with({ id: 'core.reactElement', }), diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts index 4996fbc535..8f259be6f9 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -202,12 +202,14 @@ describe('createExtension', () => { }); }); - it('should create an extension with multiple attachment points', () => { + // Tests for backward compatibility - runtime still supports multiple attachment points + // but the TypeScript types no longer allow them + it('should create an extension with multiple attachment points (backward compat)', () => { const extension = createExtension({ attachTo: [ { id: 'root', input: 'default' }, { id: 'other', input: 'default' }, - ], + ] as any, output: [stringDataRef, numberDataRef.optional()], factory: () => [stringDataRef('bar')], }); @@ -216,14 +218,14 @@ describe('createExtension', () => { ); }); - it('should create an extension with relative attachment points', () => { + it('should create an extension with relative attachment points (backward compat)', () => { const extension = createExtension({ attachTo: [ { relative: {}, input: 'tabs' }, { relative: { kind: 'page' }, input: 'tabs' }, { relative: { name: 'index' }, input: 'tabs' }, { relative: { kind: 'page', name: 'index' }, input: 'tabs' }, - ], + ] as any, output: [stringDataRef], factory: () => [stringDataRef('bar')], }); @@ -232,7 +234,7 @@ describe('createExtension', () => { ); }); - it('should create an extension with relative attachment points by reference', () => { + it('should create an extension with relative attachment points by reference (backward compat)', () => { const baseOpts = { attachTo: { id: 'root', input: 'children' }, inputs: { @@ -269,7 +271,7 @@ describe('createExtension', () => { parent2.inputs.tabs, parent3.inputs.tabs, parent4.inputs.otherTabs, - ], + ] as any, output: [stringDataRef], factory: () => [stringDataRef('bar')], }); @@ -281,7 +283,7 @@ describe('createExtension', () => { parent2.inputs.tabs, parent3.inputs.tabs, parent4.inputs.otherTabs, - ], + ] as any, }); expect(String(overrdeExtension)).toBe( 'ExtensionDefinition{attachTo=page:@tabs+/index@tabs+page:/index@otherTabs}', @@ -1318,4 +1320,26 @@ describe('createExtension', () => { ); }); }); + + it('should old inputs without context support', () => { + const legacyInput = createExtensionInput([numberDataRef]); + + // old API without context + delete (legacyInput as any).context; + delete (legacyInput as any).withContext; + + const extension = createExtension({ + attachTo: { id: 'root', input: 'default' }, + output: [stringDataRef], + inputs: { + foo: legacyInput, + }, + factory({ inputs }) { + unused(inputs.foo); + return [stringDataRef('output')]; + }, + }); + + expect(extension.inputs.foo).toBe(legacyInput); + }); }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index 0268cadf51..6735fc9e5d 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -26,8 +26,15 @@ import { } from '@internal/frontend'; import { ExtensionDataRef, ExtensionDataValue } from './createExtensionDataRef'; import { ExtensionInput } from './createExtensionInput'; -import { z } from 'zod'; -import { createSchemaFromZod } from '../schema/createSchemaFromZod'; +import type { z } from 'zod/v3'; +import { + createConfigSchema, + createDeprecatedConfigSchema, + mergePortableSchemas, + warnConfigSchemaPropDeprecation, +} from '../schema/createPortableSchema'; +import { describeParentCallSite } from '../routing/describeParentCallSite'; +import { type StandardSchemaV1 } from '@standard-schema/spec'; import { OpaqueExtensionDefinition } from '@internal/frontend'; import { ExtensionDataContainer } from './types'; import { @@ -36,6 +43,7 @@ import { } from './createExtensionBlueprint'; import { FrontendPlugin } from './createFrontendPlugin'; import { FrontendModule } from './createFrontendModule'; +import { FilterPredicate } from '@backstage/filter-predicates'; /** * This symbol is used to pass parameter overrides from the extension override to the blueprint factory @@ -43,11 +51,8 @@ import { FrontendModule } from './createFrontendModule'; */ export const ctxParamsSymbol = Symbol('params'); -/** - * Convert a single extension input into a matching resolved input. - * @public - */ -export type ResolvedExtensionInput = +/** @ignore */ +type ResolvedExtensionInput = TExtensionInput['extensionData'] extends Array ? { node: AppNode; @@ -56,7 +61,8 @@ export type ResolvedExtensionInput = /** * Converts an extension input map into a matching collection of resolved inputs. - * @public + * + * @ignore */ export type ResolvedExtensionInputs< TInputs extends { @@ -134,11 +140,10 @@ export type VerifyExtensionAttachTo< * * A standard attachment point declaration will specify the ID of the parent extension, as well as the name of the input to attach to. * - * There are three more advanced forms that are available for more complex use-cases: + * There are two more advanced forms that are available for more complex use-cases: * * 1. Relative attachment points: using the `relative` property instead of `id`, the attachment point is resolved relative to the current plugin. * 2. Extension input references: using a reference in code to another extension's input in the same plugin. These references are always relative. - * 3. Array of attachment points: an array of attachment points can be used to clone and attach to multiple extensions at once. * * @example * ```ts @@ -151,12 +156,6 @@ export type VerifyExtensionAttachTo< * // Attach to a specific input of another extension * const page = ParentBlueprint.make({ ... }); * const child = ChildBlueprint.make({ attachTo: page.inputs.children }); - * - * // Attach to multiple parents at once - * [ - * { id: 'page/home', input: 'widgets' }, - * { relative: { kind: 'page' }, input: 'widgets' }, - * ] * ``` * * @public @@ -166,16 +165,7 @@ export type ExtensionDefinitionAttachTo< > = | { id: string; input: string; relative?: never } | { relative: { kind?: string; name?: string }; input: string; id?: never } - | ExtensionInput - | Array< - | { id: string; input: string; relative?: never } - | { - relative: { kind?: string; name?: string }; - input: string; - id?: never; - } - | ExtensionInput - >; + | ExtensionInput; /** @public */ export type CreateExtensionOptions< @@ -186,22 +176,37 @@ export type CreateExtensionOptions< TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType }, UFactoryOutput extends ExtensionDataValue, UParentInputs extends ExtensionDataRef, + TNewConfigSchema extends { [key: string]: StandardSchemaV1 } = {}, > = { kind?: TKind; name?: TName; attachTo: ExtensionDefinitionAttachTo & VerifyExtensionAttachTo; disabled?: boolean; + if?: FilterPredicate; inputs?: TInputs; output: Array; + configSchema?: TNewConfigSchema; + /** + * @deprecated Use {@link CreateExtensionOptions.configSchema} instead. + */ config?: { + /** + * @deprecated Use {@link CreateExtensionOptions.configSchema} instead. + */ schema: TConfigSchema; }; factory(context: { node: AppNode; apis: ApiHolder; config: { - [key in keyof TConfigSchema]: z.infer>; + [key in keyof TNewConfigSchema]: StandardSchemaV1.InferOutput< + TNewConfigSchema[key] + >; + } & { + [key in keyof TConfigSchema]: z.infer< + ReturnType<((...args: any[]) => any) & TConfigSchema[key]> + >; }; inputs: Expand>; }): Iterable; @@ -253,6 +258,105 @@ export interface OverridableExtensionDefinition< >; }; + override< + UFactoryOutput extends ExtensionDataValue, + UNewOutput extends ExtensionDataRef, + TExtraInputs extends { [inputName in string]: ExtensionInput }, + TParamsInput extends AnyParamsInput>, + UParentInputs extends ExtensionDataRef, + TNewExtensionConfigSchema extends { + [key in string]: StandardSchemaV1; + } = {}, + >( + args: Expand< + { + attachTo?: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo< + ExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UParentInputs + >; + disabled?: boolean; + if?: FilterPredicate; + inputs?: TExtraInputs & { + [KName in keyof T['inputs']]?: `Error: Input '${KName & + string}' is already defined in parent definition`; + }; + output?: Array; + config?: never; + configSchema?: TNewExtensionConfigSchema & { + [KName in keyof T['config']]?: `Error: Config key '${KName & + string}' is already defined in parent schema`; + }; + factory?( + originalFactory: < + TFactoryParamsReturn extends AnyParamsInput< + NonNullable + >, + >( + context?: Expand< + { + config?: T['config']; + inputs?: ResolvedInputValueOverrides>; + } & ([T['params']] extends [never] + ? {} + : { + params?: TFactoryParamsReturn extends ExtensionBlueprintDefineParams + ? TFactoryParamsReturn + : T['params'] extends ExtensionBlueprintDefineParams + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(defineParams => defineParams())`' + : Partial; + }) + >, + ) => ExtensionDataContainer>, + context: { + node: AppNode; + apis: ApiHolder; + config: T['config'] & { + [key in keyof TNewExtensionConfigSchema]: StandardSchemaV1.InferOutput< + TNewExtensionConfigSchema[key] + >; + }; + inputs: Expand>; + }, + ): Iterable; + } & ([T['params']] extends [never] + ? {} + : { + params?: TParamsInput extends ExtensionBlueprintDefineParams + ? TParamsInput + : T['params'] extends ExtensionBlueprintDefineParams + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(defineParams => defineParams())`' + : Partial; + }) + > & + VerifyExtensionFactoryOutput< + ExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UFactoryOutput + >, + ): OverridableExtensionDefinition<{ + kind: T['kind']; + name: T['name']; + output: ExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; + inputs: T['inputs'] & TExtraInputs; + config: T['config'] & { + [key in keyof TNewExtensionConfigSchema]: StandardSchemaV1.InferOutput< + TNewExtensionConfigSchema[key] + >; + }; + configInput: T['configInput'] & { + [key in keyof TNewExtensionConfigSchema]?: StandardSchemaV1.InferInput< + TNewExtensionConfigSchema[key] + >; + }; + }>; + + /** + * @deprecated Use the `configSchema` option instead of `config.schema`. + */ override< TExtensionConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType; @@ -273,11 +377,13 @@ export interface OverridableExtensionDefinition< UParentInputs >; disabled?: boolean; + if?: FilterPredicate; inputs?: TExtraInputs & { [KName in keyof T['inputs']]?: `Error: Input '${KName & string}' is already defined in parent definition`; }; output?: Array; + configSchema?: never; config?: { schema: TExtensionConfigSchema & { [KName in keyof T['config']]?: `Error: Config key '${KName & @@ -310,7 +416,9 @@ export interface OverridableExtensionDefinition< apis: ApiHolder; config: T['config'] & { [key in keyof TExtensionConfigSchema]: z.infer< - ReturnType + ReturnType< + ((...args: any[]) => any) & TExtensionConfigSchema[key] + > >; }; inputs: Expand>; @@ -339,14 +447,14 @@ export interface OverridableExtensionDefinition< inputs: T['inputs'] & TExtraInputs; config: T['config'] & { [key in keyof TExtensionConfigSchema]: z.infer< - ReturnType + ReturnType<((...args: any[]) => any) & TExtensionConfigSchema[key]> >; }; configInput: T['configInput'] & z.input< z.ZodObject<{ [key in keyof TExtensionConfigSchema]: ReturnType< - TExtensionConfigSchema[key] + ((...args: any[]) => any) & TExtensionConfigSchema[key] >; }> >; @@ -368,11 +476,11 @@ function bindInputs( return Object.fromEntries( Object.entries(inputs).map(([inputName, input]) => [ inputName, - OpaqueExtensionInput.toInternal(input).withContext({ + OpaqueExtensionInput.toInternal(input).withContext?.({ kind, name, input: inputName, - }), + }) ?? input, ]), ); } @@ -412,6 +520,53 @@ function bindInputs( * * @public */ +export function createExtension< + UOutput extends ExtensionDataRef, + TInputs extends { [inputName in string]: ExtensionInput }, + UFactoryOutput extends ExtensionDataValue, + const TKind extends string | undefined = undefined, + const TName extends string | undefined = undefined, + UParentInputs extends ExtensionDataRef = ExtensionDataRef, + TNewConfigSchema extends { [key: string]: StandardSchemaV1 } = {}, +>( + options: CreateExtensionOptions< + TKind, + TName, + UOutput, + TInputs, + {}, + UFactoryOutput, + UParentInputs, + TNewConfigSchema + > & { config?: never }, +): OverridableExtensionDefinition<{ + config: { + [key in keyof TNewConfigSchema]: StandardSchemaV1.InferOutput< + TNewConfigSchema[key] + >; + }; + configInput: { + [key in keyof TNewConfigSchema]?: StandardSchemaV1.InferInput< + TNewConfigSchema[key] + >; + }; + output: UOutput extends ExtensionDataRef< + infer IData, + infer IId, + infer IConfig + > + ? ExtensionDataRef + : never; + inputs: TInputs; + params: never; + kind: string | undefined extends TKind ? undefined : TKind; + name: string | undefined extends TName ? undefined : TName; +}>; + +/** + * @deprecated Use the top-level `configSchema` option instead of `config.schema`. + * @public + */ export function createExtension< UOutput extends ExtensionDataRef, TInputs extends { [inputName in string]: ExtensionInput }, @@ -428,22 +583,26 @@ export function createExtension< TInputs, TConfigSchema, UFactoryOutput, - UParentInputs - >, + UParentInputs, + {} + > & { configSchema?: never }, ): OverridableExtensionDefinition<{ config: string extends keyof TConfigSchema ? {} : { - [key in keyof TConfigSchema]: z.infer>; + [key in keyof TConfigSchema]: z.infer< + ReturnType<((...args: any[]) => any) & TConfigSchema[key]> + >; }; configInput: string extends keyof TConfigSchema ? {} : z.input< z.ZodObject<{ - [key in keyof TConfigSchema]: ReturnType; + [key in keyof TConfigSchema]: ReturnType< + ((...args: any[]) => any) & TConfigSchema[key] + >; }> >; - // This inference and remapping back to ExtensionDataRef eliminates any occurrences ConfigurationExtensionDataRef output: UOutput extends ExtensionDataRef< infer IData, infer IId, @@ -455,46 +614,32 @@ export function createExtension< params: never; kind: string | undefined extends TKind ? undefined : TKind; name: string | undefined extends TName ? undefined : TName; -}> { - const schemaDeclaration = options.config?.schema; - const configSchema = - schemaDeclaration && - createSchemaFromZod(innerZ => - innerZ.object( - Object.fromEntries( - Object.entries(schemaDeclaration).map(([k, v]) => [k, v(innerZ)]), - ), - ), - ); +}>; + +/** @internal */ +export function createExtension( + options: any, +): OverridableExtensionDefinition { + if (options.config?.schema) { + warnConfigSchemaPropDeprecation(describeParentCallSite()); + } + const resolvedConfigSchema = mergePortableSchemas( + options.config?.schema + ? createDeprecatedConfigSchema(options.config.schema) + : undefined, + options.configSchema ? createConfigSchema(options.configSchema) : undefined, + ); return OpaqueExtensionDefinition.createInstance('v2', { - T: undefined as unknown as { - config: string extends keyof TConfigSchema - ? {} - : { - [key in keyof TConfigSchema]: z.infer< - ReturnType - >; - }; - configInput: string extends keyof TConfigSchema - ? {} - : z.input< - z.ZodObject<{ - [key in keyof TConfigSchema]: ReturnType; - }> - >; - output: UOutput; - inputs: TInputs; - kind: string | undefined extends TKind ? undefined : TKind; - name: string | undefined extends TName ? undefined : TName; - }, + T: undefined as any, kind: options.kind, name: options.name, attachTo: options.attachTo, disabled: options.disabled ?? false, + if: options.if, inputs: bindInputs(options.inputs, options.kind, options.name), output: options.output, - configSchema, + configSchema: resolvedConfigSchema, factory: options.factory, toString() { const parts: string[] = []; @@ -541,13 +686,17 @@ export function createExtension< parts.push(`attachTo=${attachTo}`); return `ExtensionDefinition{${parts.join(',')}}`; }, - override(overrideOptions) { + override(overrideOptions: any) { if (!Array.isArray(options.output)) { throw new Error( 'Cannot override an extension that is not declared using the new format with outputs as an array', ); } + if (overrideOptions.config?.schema) { + warnConfigSchemaPropDeprecation(describeParentCallSite()); + } + // TODO(Rugvip): Making this a type check would be optimal, but it seems // like it's tricky to add that and still have the type // inference work correctly for the factory output. @@ -563,12 +712,18 @@ export function createExtension< ); } + let ifPredicate = options.if; + if ('if' in overrideOptions) { + ifPredicate = overrideOptions.if; + } + return createExtension({ kind: options.kind, name: options.name, attachTo: (overrideOptions.attachTo ?? options.attachTo) as ExtensionDefinitionAttachTo, disabled: overrideOptions.disabled ?? options.disabled, + if: ifPredicate, inputs: bindInputs( { ...(options.inputs ?? {}), @@ -580,7 +735,7 @@ export function createExtension< output: (overrideOptions.output ?? options.output) as ExtensionDataRef[], config: - options.config || overrideOptions.config + options.config?.schema || overrideOptions.config?.schema ? { schema: { ...options.config?.schema, @@ -588,6 +743,13 @@ export function createExtension< }, } : undefined, + configSchema: + options.configSchema || overrideOptions.configSchema + ? { + ...options.configSchema, + ...overrideOptions.configSchema, + } + : undefined, factory: ({ node, apis, config, inputs }) => { if (!overrideOptions.factory) { return options.factory({ @@ -599,8 +761,8 @@ export function createExtension< }); } const parentResult = overrideOptions.factory( - (innerContext): ExtensionDataContainer => { - return createExtensionDataContainer( + (innerContext: any): ExtensionDataContainer => { + return createExtensionDataContainer( options.factory({ node, apis, diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx index 4759441187..17405c8b9d 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx @@ -31,6 +31,7 @@ import { import { createExtensionInput } from './createExtensionInput'; import { RouteRef } from '../routing'; import { createExtension, ExtensionDefinition } from './createExtension'; +import { z as zodV4 } from 'zod/v4'; import { createExtensionDataContainer, OpaqueExtensionDefinition, @@ -92,11 +93,7 @@ describe('createExtensionBlueprint', () => { it('should allow creation of extension blueprints with a generator', () => { const TestExtensionBlueprint = createExtensionBlueprint({ kind: 'test-extension', - // Try multiple attachment points for this one - attachTo: [ - { id: 'test-1', input: 'default' }, - { id: 'test-2', input: 'default' }, - ], + attachTo: { id: 'test-1', input: 'default' }, output: [coreExtensionData.reactElement], *factory(params: { text: string }) { yield coreExtensionData.reactElement(

    {params.text}

    ); @@ -112,10 +109,7 @@ describe('createExtensionBlueprint', () => { expect(extension).toEqual({ $$type: '@backstage/ExtensionDefinition', - attachTo: [ - { id: 'test-1', input: 'default' }, - { id: 'test-2', input: 'default' }, - ], + attachTo: { id: 'test-1', input: 'default' }, configSchema: undefined, disabled: false, inputs: {}, @@ -255,6 +249,7 @@ describe('createExtensionBlueprint', () => { }, }); + // @ts-expect-error: overlapping config key 'text' TestExtensionBlueprint.makeWithOverrides({ name: 'my-extension', params: { @@ -262,9 +257,8 @@ describe('createExtensionBlueprint', () => { }, config: { schema: { - // @ts-expect-error - text: z => z.number(), - something: z => z.string(), + text: (z: any) => z.number(), + something: (z: any) => z.string(), }, }, }); @@ -316,6 +310,73 @@ describe('createExtensionBlueprint', () => { ); }); + it('should merge configSchema from blueprint with deprecated config.schema from override', () => { + const TestBlueprint = createExtensionBlueprint({ + kind: 'test-extension', + attachTo: { id: 'test', input: 'default' }, + output: [coreExtensionData.reactElement], + configSchema: { + title: zodV4.string().default('default title'), + }, + factory(_, { config }) { + return [ + coreExtensionData.reactElement(
    {String(config.title)}
    ), + ]; + }, + }); + + const extension = TestBlueprint.makeWithOverrides({ + name: 'my-extension', + config: { + schema: { + extra: z => z.string(), + }, + }, + factory(origFactory, { config }) { + const c = config as { title: string; extra: string }; + expect(c.title).toBe('default title'); + expect(c.extra).toBe('extra value'); + return origFactory({}); + }, + }); + + expect.assertions(2); + + renderInTestApp( + createExtensionTester(extension, { + config: { extra: 'extra value' }, + }).reactElement(), + ); + }); + + it('should emit a deprecation warning when using config.schema', () => { + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + + try { + createExtension({ + name: 'test-deprecated-warning', + attachTo: { id: 'test', input: 'default' }, + output: [coreExtensionData.reactElement], + config: { + schema: { + title: z => z.string().default('hello'), + }, + }, + factory() { + return [coreExtensionData.reactElement(
    )]; + }, + }); + + expect(warnSpy).toHaveBeenCalledWith( + expect.stringContaining( + 'DEPRECATION WARNING: The `config.schema` option for extension config is deprecated', + ), + ); + } finally { + warnSpy.mockRestore(); + } + }); + it('should allow getting inputs properly', () => { createExtensionBlueprint({ kind: 'test-extension', @@ -1345,7 +1406,9 @@ describe('createExtensionBlueprint', () => { ).toThrow('Refused to override params and factory at the same time'); }); - describe('with relative attachment points', () => { + // Tests for backward compatibility - runtime still supports multiple attachment points + // but the TypeScript types no longer allow them + describe('with relative attachment points (backward compat)', () => { const dataRef = createExtensionDataRef().with({ id: 'test.data' }); it('should create an extension with relative attachment points', () => { @@ -1356,7 +1419,7 @@ describe('createExtensionBlueprint', () => { { relative: { kind: 'page' }, input: 'tabs' }, { relative: { name: 'index' }, input: 'tabs' }, { relative: { kind: 'page', name: 'index' }, input: 'tabs' }, - ], + ] as any, output: [dataRef], factory: () => [dataRef('bar')], }); @@ -1371,7 +1434,7 @@ describe('createExtensionBlueprint', () => { { relative: { kind: 'page' }, input: 'tabs' }, { relative: { name: 'index' }, input: 'tabs' }, { relative: { kind: 'page', name: 'index' }, input: 'tabs' }, - ], + ] as any, params: {}, }), ), @@ -1431,7 +1494,7 @@ describe('createExtensionBlueprint', () => { parent2.inputs.tabs, parent3.inputs.tabs, parent4.inputs.otherTabs, - ], + ] as any, output: [dataRef], factory: () => [dataRef('bar')], }); @@ -1445,7 +1508,7 @@ describe('createExtensionBlueprint', () => { parent2.inputs.tabs, parent3.inputs.tabs, parent4.inputs.otherTabs, - ], + ] as any, params: {}, }), ), diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index 70b6209ae3..99ed7c1afb 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -26,8 +26,9 @@ import { ctxParamsSymbol, VerifyExtensionAttachTo, } from './createExtension'; -import { z } from 'zod'; +import type { z } from 'zod/v3'; import { ExtensionInput } from './createExtensionInput'; +import { type StandardSchemaV1 } from '@standard-schema/spec'; import { ExtensionDataRef, ExtensionDataValue } from './createExtensionDataRef'; import { createExtensionDataContainer } from '@internal/frontend'; import { @@ -36,6 +37,9 @@ import { } from './resolveInputOverrides'; import { ExtensionDataContainer } from './types'; import { PageBlueprint } from '../blueprints/PageBlueprint'; +import { FilterPredicate } from '@backstage/filter-predicates'; +import { warnConfigSchemaPropDeprecation } from '../schema/createPortableSchema'; +import { describeParentCallSite } from '../routing/describeParentCallSite'; /** * A function used to define a parameter mapping function in order to facilitate @@ -109,14 +113,23 @@ export type CreateExtensionBlueprintOptions< UFactoryOutput extends ExtensionDataValue, TDataRefs extends { [name in string]: ExtensionDataRef }, UParentInputs extends ExtensionDataRef, + TNewConfigSchema extends { [key in string]: StandardSchemaV1 } = {}, > = { kind: TKind; attachTo: ExtensionDefinitionAttachTo & VerifyExtensionAttachTo; disabled?: boolean; + if?: FilterPredicate; inputs?: TInputs; output: Array; + configSchema?: TNewConfigSchema; + /** + * @deprecated Use {@link CreateExtensionBlueprintOptions.configSchema} instead. + */ config?: { + /** + * @deprecated Use {@link CreateExtensionBlueprintOptions.configSchema} instead. + */ schema: TConfigSchema; }; /** @@ -168,7 +181,13 @@ export type CreateExtensionBlueprintOptions< node: AppNode; apis: ApiHolder; config: { - [key in keyof TConfigSchema]: z.infer>; + [key in keyof TNewConfigSchema]: StandardSchemaV1.InferOutput< + TNewConfigSchema[key] + >; + } & { + [key in keyof TConfigSchema]: z.infer< + ReturnType<((...args: any[]) => any) & TConfigSchema[key]> + >; }; inputs: Expand>; }, @@ -221,6 +240,7 @@ export interface ExtensionBlueprint< attachTo?: ExtensionDefinitionAttachTo & VerifyExtensionAttachTo, UParentInputs>; disabled?: boolean; + if?: FilterPredicate; params: TParamsInput extends ExtensionBlueprintDefineParams ? TParamsInput : T['params'] extends ExtensionBlueprintDefineParams @@ -244,13 +264,13 @@ export interface ExtensionBlueprint< */ makeWithOverrides< TName extends string | undefined, - TExtensionConfigSchema extends { - [key in string]: (zImpl: typeof z) => z.ZodType; - }, UFactoryOutput extends ExtensionDataValue, UNewOutput extends ExtensionDataRef, - TExtraInputs extends { [inputName in string]: ExtensionInput }, UParentInputs extends ExtensionDataRef, + TExtraInputs extends { [inputName in string]: ExtensionInput } = {}, + TNewExtensionConfigSchema extends { + [key in string]: StandardSchemaV1; + } = {}, >(args: { name?: TName; attachTo?: ExtensionDefinitionAttachTo & @@ -261,11 +281,99 @@ export interface ExtensionBlueprint< UParentInputs >; disabled?: boolean; + if?: FilterPredicate; inputs?: TExtraInputs & { [KName in keyof T['inputs']]?: `Error: Input '${KName & string}' is already defined in parent definition`; }; output?: Array; + config?: never; + configSchema?: TNewExtensionConfigSchema & { + [KName in keyof T['config']]?: `Error: Config key '${KName & + string}' is already defined in parent schema`; + }; + factory( + originalFactory: < + TParamsInput extends AnyParamsInput>, + >( + params: TParamsInput extends ExtensionBlueprintDefineParams + ? TParamsInput + : T['params'] extends ExtensionBlueprintDefineParams + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(defineParams => defineParams())`' + : T['params'], + context?: { + config?: T['config']; + inputs?: ResolvedInputValueOverrides>; + }, + ) => ExtensionDataContainer>, + context: { + node: AppNode; + apis: ApiHolder; + config: T['config'] & { + [key in keyof TNewExtensionConfigSchema]: StandardSchemaV1.InferOutput< + TNewExtensionConfigSchema[key] + >; + }; + inputs: Expand>; + }, + ): Iterable & + VerifyExtensionFactoryOutput< + ExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UFactoryOutput + >; + }): OverridableExtensionDefinition<{ + config: Expand< + { + [key in keyof TNewExtensionConfigSchema]: StandardSchemaV1.InferOutput< + TNewExtensionConfigSchema[key] + >; + } & T['config'] + >; + configInput: Expand< + { + [key in keyof TNewExtensionConfigSchema]?: StandardSchemaV1.InferInput< + TNewExtensionConfigSchema[key] + >; + } & T['configInput'] + >; + output: ExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; + inputs: Expand; + kind: T['kind']; + name: string | undefined extends TName ? undefined : TName; + params: T['params']; + }>; + + /** + * @deprecated Use the `configSchema` option instead of `config.schema`. + */ + makeWithOverrides< + TName extends string | undefined, + TExtensionConfigSchema extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + }, + UFactoryOutput extends ExtensionDataValue, + UNewOutput extends ExtensionDataRef, + UParentInputs extends ExtensionDataRef, + TExtraInputs extends { [inputName in string]: ExtensionInput } = {}, + >(args: { + name?: TName; + attachTo?: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo< + ExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UParentInputs + >; + disabled?: boolean; + if?: FilterPredicate; + inputs?: TExtraInputs & { + [KName in keyof T['inputs']]?: `Error: Input '${KName & + string}' is already defined in parent definition`; + }; + output?: Array; + configSchema?: never; config?: { schema: TExtensionConfigSchema & { [KName in keyof T['config']]?: `Error: Config key '${KName & @@ -291,7 +399,7 @@ export interface ExtensionBlueprint< apis: ApiHolder; config: T['config'] & { [key in keyof TExtensionConfigSchema]: z.infer< - ReturnType + ReturnType<((...args: any[]) => any) & TExtensionConfigSchema[key]> >; }; inputs: Expand>; @@ -304,26 +412,32 @@ export interface ExtensionBlueprint< UFactoryOutput >; }): OverridableExtensionDefinition<{ - config: (string extends keyof TExtensionConfigSchema - ? {} - : { - [key in keyof TExtensionConfigSchema]: z.infer< - ReturnType - >; - }) & - T['config']; - configInput: (string extends keyof TExtensionConfigSchema - ? {} - : z.input< - z.ZodObject<{ - [key in keyof TExtensionConfigSchema]: ReturnType< - TExtensionConfigSchema[key] + config: Expand< + (string extends keyof TExtensionConfigSchema + ? {} + : { + [key in keyof TExtensionConfigSchema]: z.infer< + ReturnType< + ((...args: any[]) => any) & TExtensionConfigSchema[key] + > >; - }> - >) & - T['configInput']; + }) & + T['config'] + >; + configInput: Expand< + (string extends keyof TExtensionConfigSchema + ? {} + : z.input< + z.ZodObject<{ + [key in keyof TExtensionConfigSchema]: ReturnType< + ((...args: any[]) => any) & TExtensionConfigSchema[key] + >; + }> + >) & + T['configInput'] + >; output: ExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; - inputs: T['inputs'] & TExtraInputs; + inputs: Expand; kind: T['kind']; name: string | undefined extends TName ? undefined : TName; params: T['params']; @@ -411,7 +525,7 @@ function unwrapParams( * in the frontend system documentation. * * Extension blueprints make it much easier for users to create new extensions - * for your plugin. Rather than letting them use {@link createExtension} + * for your plugin. Rather than letting them use `createExtension` * directly, you can define a set of parameters and default factory for your * blueprint, removing a lot of the boilerplate and complexity that is otherwise * needed to create an extension. @@ -449,6 +563,74 @@ function unwrapParams( * ``` * @public */ +export function createExtensionBlueprint< + TParams extends object | ExtensionBlueprintDefineParams, + UOutput extends ExtensionDataRef, + TInputs extends { [inputName in string]: ExtensionInput }, + UFactoryOutput extends ExtensionDataValue, + TKind extends string, + UParentInputs extends ExtensionDataRef, + TDataRefs extends { [name in string]: ExtensionDataRef } = never, + TNewConfigSchema extends { [key in string]: StandardSchemaV1 } = {}, +>( + options: { + kind: TKind; + attachTo: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo; + disabled?: boolean; + if?: FilterPredicate; + inputs?: TInputs; + output: Array; + config?: never; + configSchema?: TNewConfigSchema; + defineParams?: TParams extends ExtensionBlueprintDefineParams + ? TParams + : 'The defineParams option must be a function if provided, see the docs for details'; + factory( + params: TParams extends ExtensionBlueprintDefineParams + ? ReturnType['T'] + : TParams, + context: { + node: AppNode; + apis: ApiHolder; + config: { + [key in keyof TNewConfigSchema]: StandardSchemaV1.InferOutput< + TNewConfigSchema[key] + >; + }; + inputs: Expand>; + }, + ): Iterable; + dataRefs?: TDataRefs; + } & VerifyExtensionFactoryOutput, +): ExtensionBlueprint<{ + kind: TKind; + params: TParams; + output: UOutput extends ExtensionDataRef< + infer IData, + infer IId, + infer IConfig + > + ? ExtensionDataRef + : never; + inputs: string extends keyof TInputs ? {} : TInputs; + config: { + [key in keyof TNewConfigSchema]: StandardSchemaV1.InferOutput< + TNewConfigSchema[key] + >; + }; + configInput: { + [key in keyof TNewConfigSchema]?: StandardSchemaV1.InferInput< + TNewConfigSchema[key] + >; + }; + dataRefs: TDataRefs; +}>; + +/** + * @deprecated Use the top-level `configSchema` option instead of `config.schema`. + * @public + */ export function createExtensionBlueprint< TParams extends object | ExtensionBlueprintDefineParams, UOutput extends ExtensionDataRef, @@ -459,20 +641,41 @@ export function createExtensionBlueprint< UParentInputs extends ExtensionDataRef, TDataRefs extends { [name in string]: ExtensionDataRef } = never, >( - options: CreateExtensionBlueprintOptions< - TKind, - TParams, - UOutput, - TInputs, - TConfigSchema, - UFactoryOutput, - TDataRefs, - UParentInputs - >, + options: { + kind: TKind; + attachTo: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo; + disabled?: boolean; + if?: FilterPredicate; + inputs?: TInputs; + output: Array; + configSchema?: never; + config?: { + schema: TConfigSchema; + }; + defineParams?: TParams extends ExtensionBlueprintDefineParams + ? TParams + : 'The defineParams option must be a function if provided, see the docs for details'; + factory( + params: TParams extends ExtensionBlueprintDefineParams + ? ReturnType['T'] + : TParams, + context: { + node: AppNode; + apis: ApiHolder; + config: { + [key in keyof TConfigSchema]: z.infer< + ReturnType<((...args: any[]) => any) & TConfigSchema[key]> + >; + }; + inputs: Expand>; + }, + ): Iterable; + dataRefs?: TDataRefs; + } & VerifyExtensionFactoryOutput, ): ExtensionBlueprint<{ kind: TKind; params: TParams; - // This inference and remapping back to ExtensionDataRef eliminates any occurrences ConfigurationExtensionDataRef output: UOutput extends ExtensionDataRef< infer IData, infer IId, @@ -483,16 +686,25 @@ export function createExtensionBlueprint< inputs: string extends keyof TInputs ? {} : TInputs; config: string extends keyof TConfigSchema ? {} - : { [key in keyof TConfigSchema]: z.infer> }; + : { + [key in keyof TConfigSchema]: z.infer< + ReturnType<((...args: any[]) => any) & TConfigSchema[key]> + >; + }; configInput: string extends keyof TConfigSchema ? {} : z.input< z.ZodObject<{ - [key in keyof TConfigSchema]: ReturnType; + [key in keyof TConfigSchema]: ReturnType< + ((...args: any[]) => any) & TConfigSchema[key] + >; }> >; dataRefs: TDataRefs; -}> { +}>; + +/** @internal */ +export function createExtensionBlueprint(options: any): any { const defineParams = options.defineParams as | ExtensionBlueprintDefineParams | undefined; @@ -506,27 +718,33 @@ export function createExtensionBlueprint< attachTo: (args.attachTo ?? options.attachTo) as ExtensionDefinitionAttachTo, disabled: args.disabled ?? options.disabled, + if: args.if ?? options.if, inputs: options.inputs, output: options.output as ExtensionDataRef[], config: options.config, + configSchema: options.configSchema as any, factory: ctx => options.factory( unwrapParams(args.params, ctx, defineParams, options.kind), - ctx, + ctx as any, ) as Iterable>, }) as OverridableExtensionDefinition; }, - makeWithOverrides(args) { + makeWithOverrides(args: any) { + if (args.config?.schema) { + warnConfigSchemaPropDeprecation(describeParentCallSite()); + } return createExtension({ kind: options.kind, name: args.name, attachTo: (args.attachTo ?? options.attachTo) as ExtensionDefinitionAttachTo, disabled: args.disabled ?? options.disabled, + if: args.if ?? options.if, inputs: { ...args.inputs, ...options.inputs }, output: (args.output ?? options.output) as ExtensionDataRef[], config: - options.config || args.config + options.config?.schema || args.config?.schema ? { schema: { ...options.config?.schema, @@ -534,19 +752,18 @@ export function createExtensionBlueprint< }, } : undefined, + configSchema: + options.configSchema || args.configSchema + ? { + ...options.configSchema, + ...args.configSchema, + } + : (undefined as any), factory: ctx => { const { node, config, inputs, apis } = ctx; return args.factory( - (innerParams, innerContext) => { - return createExtensionDataContainer< - UOutput extends ExtensionDataRef< - infer IData, - infer IId, - infer IConfig - > - ? ExtensionDataRef - : never - >( + (innerParams: any, innerContext: any) => { + return createExtensionDataContainer( options.factory( unwrapParams(innerParams, ctx, defineParams, options.kind), { @@ -574,23 +791,5 @@ export function createExtensionBlueprint< }, }) as OverridableExtensionDefinition; }, - } as ExtensionBlueprint<{ - kind: TKind; - params: TParams; - output: any; - inputs: string extends keyof TInputs ? {} : TInputs; - config: string extends keyof TConfigSchema - ? {} - : { - [key in keyof TConfigSchema]: z.infer>; - }; - configInput: string extends keyof TConfigSchema - ? {} - : z.input< - z.ZodObject<{ - [key in keyof TConfigSchema]: ReturnType; - }> - >; - dataRefs: TDataRefs; - }>; + } as ExtensionBlueprint; } diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts b/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts index 1bfbb01740..8ae176d249 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts @@ -33,18 +33,12 @@ export type ExtensionDataRef< readonly config: TConfig; }; -/** @public */ -export type ExtensionDataRefToValue = +/** @ignore */ +export type ExtensionDataRefToValue = TDataRef extends ExtensionDataRef ? ExtensionDataValue : never; -/** - * @deprecated Use `ExtensionDataRef` without type parameters instead. - * @public - */ -export type AnyExtensionDataRef = ExtensionDataRef; - /** @public */ export interface ConfigurableExtensionDataRef< TData, diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionInput.test.ts b/packages/frontend-plugin-api/src/wiring/createExtensionInput.test.ts index 8511f79b4e..9b32ca947f 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionInput.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionInput.test.ts @@ -29,28 +29,28 @@ describe('createExtensionInput', () => { expect(input).toEqual({ $$type: '@backstage/ExtensionInput', extensionData: [stringDataRef, numberDataRef], - config: { singleton: false, optional: false }, + config: { singleton: false, optional: false, internal: false }, withContext: expect.any(Function), }); const x1: ExtensionInput< typeof stringDataRef | typeof numberDataRef, - { singleton: false; optional: false } + { singleton: false; optional: false; internal: false } > = input; // @ts-expect-error const x2: ExtensionInput< typeof stringDataRef, - { singleton: false; optional: false } + { singleton: false; optional: false; internal: false } > = input; // @ts-expect-error const x3: ExtensionInput< typeof stringDataRef | typeof numberDataRef, - { singleton: true; optional: false } + { singleton: true; optional: false; internal: false } > = input; // @ts-expect-error const x4: ExtensionInput< typeof stringDataRef | typeof numberDataRef, - { singleton: false; optional: true } + { singleton: false; optional: true; internal: false } > = input; unused(x1, x2, x3, x4); @@ -60,11 +60,11 @@ describe('createExtensionInput', () => { const input = createExtensionInput([stringDataRef, numberDataRef]); const context = { input: 'test1', kind: 'test2', name: 'test3' }; const inputWithContext = - OpaqueExtensionInput.toInternal(input).withContext(context); + OpaqueExtensionInput.toInternal(input).withContext?.(context); expect(inputWithContext).toEqual({ $$type: '@backstage/ExtensionInput', extensionData: [stringDataRef, numberDataRef], - config: { singleton: false, optional: false }, + config: { singleton: false, optional: false, internal: false }, withContext: expect.any(Function), context, }); @@ -77,28 +77,28 @@ describe('createExtensionInput', () => { expect(input).toEqual({ $$type: '@backstage/ExtensionInput', extensionData: [stringDataRef, numberDataRef], - config: { singleton: true, optional: false }, + config: { singleton: true, optional: false, internal: false }, withContext: expect.any(Function), }); const x1: ExtensionInput< typeof stringDataRef | typeof numberDataRef, - { singleton: true; optional: false } + { singleton: true; optional: false; internal: false } > = input; // @ts-expect-error const x2: ExtensionInput< typeof stringDataRef, - { singleton: true; optional: false } + { singleton: true; optional: false; internal: false } > = input; // @ts-expect-error const x3: ExtensionInput< typeof stringDataRef | typeof numberDataRef, - { singleton: false; optional: false } + { singleton: false; optional: false; internal: false } > = input; // @ts-expect-error const x4: ExtensionInput< typeof stringDataRef | typeof numberDataRef, - { singleton: false; optional: true } + { singleton: false; optional: true; internal: false } > = input; unused(x1, x2, x3, x4); @@ -112,28 +112,28 @@ describe('createExtensionInput', () => { expect(input).toEqual({ $$type: '@backstage/ExtensionInput', extensionData: [stringDataRef, numberDataRef], - config: { singleton: true, optional: true }, + config: { singleton: true, optional: true, internal: false }, withContext: expect.any(Function), }); const x1: ExtensionInput< typeof stringDataRef | typeof numberDataRef, - { singleton: true; optional: true } + { singleton: true; optional: true; internal: false } > = input; // @ts-expect-error const x2: ExtensionInput< typeof stringDataRef, - { singleton: true; optional: true } + { singleton: true; optional: true; internal: false } > = input; // @ts-expect-error const x3: ExtensionInput< typeof stringDataRef | typeof numberDataRef, - { singleton: false; optional: false } + { singleton: false; optional: false; internal: false } > = input; // @ts-expect-error const x4: ExtensionInput< typeof stringDataRef | typeof numberDataRef, - { singleton: false; optional: true } + { singleton: false; optional: true; internal: false } > = input; unused(x1, x2, x3, x4); @@ -144,4 +144,14 @@ describe('createExtensionInput', () => { createExtensionInput([stringDataRef, stringDataRef], { singleton: true }), ).toThrow("ExtensionInput may not have duplicate data refs: 'str'"); }); + + it('should create an internal input', () => { + const input = createExtensionInput([stringDataRef], { internal: true }); + expect(input).toEqual({ + $$type: '@backstage/ExtensionInput', + extensionData: [stringDataRef], + config: { singleton: false, optional: false, internal: true }, + withContext: expect.any(Function), + }); + }); }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionInput.ts b/packages/frontend-plugin-api/src/wiring/createExtensionInput.ts index 57040c8adf..760bd4b984 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionInput.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionInput.ts @@ -27,9 +27,14 @@ export interface ExtensionInput< string, { optional?: true } > = ExtensionDataRef, - TConfig extends { singleton: boolean; optional: boolean } = { + TConfig extends { singleton: boolean; optional: boolean; + internal?: boolean; + } = { + singleton: boolean; + optional: boolean; + internal?: boolean; }, > { readonly $$type: '@backstage/ExtensionInput'; @@ -38,10 +43,60 @@ export interface ExtensionInput< readonly replaces?: Array<{ id: string; input: string }>; } -/** @public */ +/** + * Creates a new extension input to be passed to the input map of an extension. + * + * @remarks + * + * Extension inputs created with this function can be passed to any `inputs` map + * as part of creating or overriding an extension. + * + * The array of extension data references defines the data this input expects. + * If the required data is not provided by the attached extension, the + * attachment will fail. + * + * The `config` object can be used to restrict the behavior and shape of the + * input. By default an input will accept zero or more extensions from any + * plugin. The following options are available: + * + * - `singleton`: If set to `true`, only one extension can be attached to the + * input at a time. Additional extensions will trigger an app error and be + * ignored. + * - `optional`: If set to `true`, the input is optional and can be omitted, + * this only has an effect if the `singleton` is set to `true`. + * - `internal`: If set to `true`, only extensions from the same plugin will be + * allowed to attach to this input. Other extensions will trigger an app error + * and be ignored. + * + * @param extensionData - The array of extension data references that this input + * expects. + * @param config - The configuration object for the input. + * @returns An extension input declaration. + * @example + * ```ts + * const extension = createExtension({ + * attachTo: { id: 'example-parent', input: 'example-input' }, + * inputs: { + * content: createExtensionInput([coreExtensionData.reactElement], { + * singleton: true, + * }), + * }, + * output: [coreExtensionData.reactElement], + * *factory({ inputs }) { + * const content = inputs.content?.get(coreExtensionData.reactElement); + * yield coreExtensionData.reactElement({content}); + * }, + * }); + * ``` + * @public + */ export function createExtensionInput< UExtensionData extends ExtensionDataRef, - TConfig extends { singleton?: boolean; optional?: boolean }, + TConfig extends { + singleton?: boolean; + optional?: boolean; + internal?: boolean; + }, >( extensionData: Array, config?: TConfig & { replaces?: Array<{ id: string; input: string }> }, @@ -50,6 +105,7 @@ export function createExtensionInput< { singleton: TConfig['singleton'] extends true ? true : false; optional: TConfig['optional'] extends true ? true : false; + internal: TConfig['internal'] extends true ? true : false; } > { if (process.env.NODE_ENV !== 'production') { @@ -81,6 +137,9 @@ export function createExtensionInput< optional: Boolean(config?.optional) as TConfig['optional'] extends true ? true : false, + internal: Boolean(config?.internal) as TConfig['internal'] extends true + ? true + : false, }, replaces: config?.replaces, }; @@ -90,6 +149,7 @@ export function createExtensionInput< { singleton: TConfig['singleton'] extends true ? true : false; optional: TConfig['optional'] extends true ? true : false; + internal: TConfig['internal'] extends true ? true : false; } > { return OpaqueExtensionInput.createInstance(undefined, { diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendModule.test.ts b/packages/frontend-plugin-api/src/wiring/createFrontendModule.test.ts index d895f6de0f..6f7d8c12c8 100644 --- a/packages/frontend-plugin-api/src/wiring/createFrontendModule.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendModule.test.ts @@ -46,6 +46,7 @@ describe('createFrontendModule', () => { "disabled": false, "factory": [Function], "id": "route:test/test", + "if": undefined, "inputs": {}, "output": [], "toString": [Function], @@ -53,6 +54,7 @@ describe('createFrontendModule', () => { }, ], "featureFlags": [], + "if": undefined, "pluginId": "test", "toString": [Function], "version": "v1", diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendModule.ts b/packages/frontend-plugin-api/src/wiring/createFrontendModule.ts index cea0760e68..5d435102d9 100644 --- a/packages/frontend-plugin-api/src/wiring/createFrontendModule.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendModule.ts @@ -14,13 +14,13 @@ * limitations under the License. */ -import { OpaqueExtensionDefinition } from '@internal/frontend'; import { ExtensionDefinition } from './createExtension'; import { Extension, - resolveExtensionDefinition, + resolveExtensionDefinitions, } from './resolveExtensionDefinition'; import { FeatureFlagConfig } from './types'; +import { FilterPredicate } from '@backstage/filter-predicates'; /** @public */ export interface CreateFrontendModuleOptions< @@ -30,6 +30,7 @@ export interface CreateFrontendModuleOptions< pluginId: TPluginId; extensions?: TExtensions; featureFlags?: FeatureFlagConfig[]; + if?: FilterPredicate; } /** @public */ @@ -43,6 +44,7 @@ export interface InternalFrontendModule extends FrontendModule { readonly version: 'v1'; readonly extensions: Extension[]; readonly featureFlags: FeatureFlagConfig[]; + readonly if?: FilterPredicate; } /** @@ -90,42 +92,17 @@ export function createFrontendModule< >(options: CreateFrontendModuleOptions): FrontendModule { const { pluginId } = options; - const extensions = new Array>(); - const extensionDefinitionsById = new Map< - string, - typeof OpaqueExtensionDefinition.TInternal - >(); - - for (const def of options.extensions ?? []) { - const internal = OpaqueExtensionDefinition.toInternal(def); - const ext = resolveExtensionDefinition(def, { namespace: pluginId }); - extensions.push(ext); - extensionDefinitionsById.set(ext.id, { - ...internal, - namespace: pluginId, - }); - } - - if (extensions.length !== extensionDefinitionsById.size) { - const extensionIds = extensions.map(e => e.id); - const duplicates = Array.from( - new Set( - extensionIds.filter((id, index) => extensionIds.indexOf(id) !== index), - ), - ); - // TODO(Rugvip): This could provide some more information about the kind + name of the extensions - throw new Error( - `Plugin '${pluginId}' provided duplicate extensions: ${duplicates.join( - ', ', - )}`, - ); - } + const { extensions } = resolveExtensionDefinitions(options.extensions ?? [], { + namespace: pluginId, + featureType: 'Module', + }); return { $$type: '@backstage/FrontendModule', version: 'v1', pluginId, featureFlags: options.featureFlags ?? [], + if: options.if, extensions, toString() { return `Module{pluginId=${pluginId}}`; diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts index a3226721a1..a6034db1e2 100644 --- a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts @@ -142,6 +142,17 @@ describe('createFrontendPlugin', () => { expect(String(plugin)).toBe('Plugin{id=test}'); }); + it('should warn about invalid plugin IDs', () => { + const consoleWarn = jest + .spyOn(console, 'warn') + .mockImplementation(() => {}); + createFrontendPlugin({ pluginId: 'invalid&id' }); + expect(consoleWarn).toHaveBeenCalledWith( + expect.stringContaining("The pluginId 'invalid&id' will be invalid soon"), + ); + consoleWarn.mockRestore(); + }); + it('should create a plugin with extension instances', async () => { const plugin = createFrontendPlugin({ pluginId: 'test', @@ -160,6 +171,7 @@ describe('createFrontendPlugin', () => { "configSchema": undefined, "disabled": false, "factory": [Function], + "if": undefined, "inputs": {}, "kind": undefined, "name": "1", @@ -348,6 +360,7 @@ describe('createFrontendPlugin', () => { "configSchema": undefined, "disabled": false, "factory": [Function], + "if": undefined, "inputs": {}, "kind": undefined, "name": "1", diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts index 68b5826baa..9c6095e732 100644 --- a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts @@ -14,22 +14,22 @@ * limitations under the License. */ -import { - OpaqueExtensionDefinition, - OpaqueFrontendPlugin, -} from '@internal/frontend'; +import { OpaqueFrontendPlugin } from '@internal/frontend'; import { ExtensionDefinition, OverridableExtensionDefinition, } from './createExtension'; import { - Extension, resolveExtensionDefinition, + resolveExtensionDefinitions, } from './resolveExtensionDefinition'; import { FeatureFlagConfig } from './types'; import { MakeSortedExtensionsMap } from './MakeSortedExtensionsMap'; import { JsonObject } from '@backstage/types'; +import { IconElement } from '../icons/types'; import { RouteRef, SubRouteRef, ExternalRouteRef } from '../routing'; +import { ID_PATTERN } from './constants'; +import { FilterPredicate } from '@backstage/filter-predicates'; /** * Information about the plugin. @@ -111,7 +111,22 @@ export interface OverridableFrontendPlugin< id: TId, ): OverridableExtensionDefinition; withOverrides(options: { - extensions: Array; + extensions?: Array; + + /** + * Overrides the shared condition that applies to all extensions in the plugin. + */ + if?: FilterPredicate; + + /** + * Overrides the display title of the plugin. + */ + title?: string; + + /** + * Overrides the display icon of the plugin. + */ + icon?: IconElement; /** * Overrides the original info loaders of the plugin one by one. @@ -130,7 +145,25 @@ export interface FrontendPlugin< }, > { readonly $$type: '@backstage/FrontendPlugin'; + /** + * The plugin ID. + */ + readonly pluginId: string; + /** + * Deprecated alias for `pluginId`. + * + * @deprecated Use `pluginId` instead. + */ readonly id: string; + /** + * The display title of the plugin, used in page headers and navigation. + * Falls back to the plugin ID if not provided. + */ + readonly title?: string; + /** + * The display icon of the plugin, used in page headers and navigation. + */ + readonly icon?: IconElement; readonly routes: TRoutes; readonly externalRoutes: TExternalRoutes; @@ -140,21 +173,46 @@ export interface FrontendPlugin< info(): Promise; } -/** @public */ -export interface PluginOptions< +/** + * Options for {@link createFrontendPlugin}. + * + * @public + */ +export interface CreateFrontendPluginOptions< TId extends string, TRoutes extends { [name in string]: RouteRef | SubRouteRef }, TExternalRoutes extends { [name in string]: ExternalRouteRef }, TExtensions extends readonly ExtensionDefinition[], > { pluginId: TId; + /** + * The display title of the plugin, used in page headers and navigation. + * Falls back to the plugin ID if not provided. + */ + title?: string; + /** + * The display icon of the plugin, used in page headers and navigation. + */ + icon?: IconElement; routes?: TRoutes; externalRoutes?: TExternalRoutes; extensions?: TExtensions; featureFlags?: FeatureFlagConfig[]; + if?: FilterPredicate; info?: FrontendPluginInfoOptions; } +/** + * @deprecated Use {@link CreateFrontendPluginOptions} instead. + * @public + */ +export type PluginOptions< + TId extends string, + TRoutes extends { [name in string]: RouteRef | SubRouteRef }, + TExternalRoutes extends { [name in string]: ExternalRouteRef }, + TExtensions extends readonly ExtensionDefinition[], +> = CreateFrontendPluginOptions; + /** * Creates a new plugin that can be installed in a Backstage app. * @@ -191,7 +249,12 @@ export function createFrontendPlugin< TRoutes extends { [name in string]: RouteRef | SubRouteRef } = {}, TExternalRoutes extends { [name in string]: ExternalRouteRef } = {}, >( - options: PluginOptions, + options: CreateFrontendPluginOptions< + TId, + TRoutes, + TExternalRoutes, + TExtensions + >, ): OverridableFrontendPlugin< TRoutes, TExternalRoutes, @@ -199,42 +262,30 @@ export function createFrontendPlugin< > { const pluginId = options.pluginId; - const extensions = new Array>(); - const extensionDefinitionsById = new Map< - string, - typeof OpaqueExtensionDefinition.TInternal - >(); + if (!ID_PATTERN.test(pluginId)) { + // eslint-disable-next-line no-console + console.warn( + `WARNING: The pluginId '${pluginId}' will be invalid soon, please change it to match the pattern ${ID_PATTERN} (letters, digits, and dashes only, starting with a letter)`, + ); + } - for (const def of options.extensions ?? []) { - const internal = OpaqueExtensionDefinition.toInternal(def); - const ext = resolveExtensionDefinition(def, { namespace: pluginId }); - extensions.push(ext); - extensionDefinitionsById.set(ext.id, { - ...internal, + const { extensions, extensionDefinitionsById } = resolveExtensionDefinitions( + options.extensions ?? [], + { namespace: pluginId, - }); - } - - if (extensions.length !== extensionDefinitionsById.size) { - const extensionIds = extensions.map(e => e.id); - const duplicates = Array.from( - new Set( - extensionIds.filter((id, index) => extensionIds.indexOf(id) !== index), - ), - ); - // TODO(Rugvip): This could provide some more information about the kind + name of the extensions - throw new Error( - `Plugin '${pluginId}' provided duplicate extensions: ${duplicates.join( - ', ', - )}`, - ); - } + featureType: 'Plugin', + }, + ); return OpaqueFrontendPlugin.createInstance('v1', { + pluginId, id: pluginId, + title: options.title, + icon: options.icon, routes: options.routes ?? ({} as TRoutes), externalRoutes: options.externalRoutes ?? ({} as TExternalRoutes), featureFlags: options.featureFlags ?? [], + if: options.if, extensions: extensions, infoOptions: options.info, @@ -257,8 +308,13 @@ export function createFrontendPlugin< return `Plugin{id=${pluginId}}`; }, withOverrides(overrides) { + let ifPredicate = options.if; + if ('if' in overrides) { + ifPredicate = overrides.if; + } + const overrideExtensions = overrides.extensions ?? []; const overriddenExtensionIds = new Set( - overrides.extensions.map( + overrideExtensions.map( e => resolveExtensionDefinition(e, { namespace: pluginId }).id, ), ); @@ -271,7 +327,10 @@ export function createFrontendPlugin< return createFrontendPlugin({ ...options, pluginId, - extensions: [...nonOverriddenExtensions, ...overrides.extensions], + if: ifPredicate, + title: overrides.title ?? options.title, + icon: overrides.icon ?? options.icon, + extensions: [...nonOverriddenExtensions, ...overrideExtensions], info: { ...options.info, ...overrides.info, diff --git a/packages/frontend-plugin-api/src/wiring/index.ts b/packages/frontend-plugin-api/src/wiring/index.ts index 6ce1bb4998..f3a39cc60d 100644 --- a/packages/frontend-plugin-api/src/wiring/index.ts +++ b/packages/frontend-plugin-api/src/wiring/index.ts @@ -22,8 +22,6 @@ export { type ExtensionDefinitionParameters, type CreateExtensionOptions, type OverridableExtensionDefinition, - type ResolvedExtensionInput, - type ResolvedExtensionInputs, } from './createExtension'; export { createExtensionInput, @@ -31,14 +29,13 @@ export { } from './createExtensionInput'; export { createExtensionDataRef, - type AnyExtensionDataRef, type ExtensionDataRef, - type ExtensionDataRefToValue, type ExtensionDataValue, type ConfigurableExtensionDataRef, } from './createExtensionDataRef'; export { createFrontendPlugin, + type CreateFrontendPluginOptions, type FrontendPlugin, type OverridableFrontendPlugin, type PluginOptions, @@ -58,7 +55,6 @@ export { export { type Extension, type ExtensionAttachTo, - type ExtensionAttachToSpec, } from './resolveExtensionDefinition'; export { type ExtensionDataContainer, diff --git a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts index d8b90d69f3..e080ab1f64 100644 --- a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts +++ b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts @@ -80,7 +80,7 @@ describe('resolveExtensionDefinition', () => { resolveExtensionDefinition( OpaqueExtensionDefinition.toInternal({ ...baseDef, - attachTo: baseInpuf.withContext({ + attachTo: baseInpuf.withContext?.({ kind: 'parent', name: 'example', input: 'children', @@ -97,7 +97,7 @@ describe('resolveExtensionDefinition', () => { resolveExtensionDefinition( OpaqueExtensionDefinition.toInternal({ ...baseDef, - attachTo: baseInpuf.withContext({ + attachTo: baseInpuf.withContext?.({ name: 'example', input: 'children', }), @@ -113,7 +113,7 @@ describe('resolveExtensionDefinition', () => { resolveExtensionDefinition( OpaqueExtensionDefinition.toInternal({ ...baseDef, - attachTo: baseInpuf.withContext({ + attachTo: baseInpuf.withContext?.({ kind: 'parent', input: 'children', }), @@ -129,7 +129,7 @@ describe('resolveExtensionDefinition', () => { resolveExtensionDefinition( OpaqueExtensionDefinition.toInternal({ ...baseDef, - attachTo: baseInpuf.withContext({ + attachTo: baseInpuf.withContext?.({ input: 'children', }), }), @@ -140,24 +140,25 @@ describe('resolveExtensionDefinition', () => { input: 'children', }); + // Test for backward compatibility - runtime still supports multiple attachment points expect( resolveExtensionDefinition( OpaqueExtensionDefinition.toInternal({ ...baseDef, attachTo: [ - baseInpuf.withContext({ + baseInpuf.withContext?.({ kind: 'k1', input: 'children', }), - baseInpuf.withContext({ + baseInpuf.withContext?.({ kind: 'k2', input: 'children', }), - baseInpuf.withContext({ + baseInpuf.withContext?.({ kind: 'k3', input: 'children', }), - ], + ] as any, }), { namespace: 'test' }, ).attachTo, diff --git a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts index f99d586e87..55ecf314d3 100644 --- a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts +++ b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts @@ -28,23 +28,16 @@ import { OpaqueExtensionDefinition, OpaqueExtensionInput, } from '@internal/frontend'; +import { FilterPredicate } from '@backstage/filter-predicates'; /** @public */ -export type ExtensionAttachTo = - | { id: string; input: string } - | Array<{ id: string; input: string }>; - -/** - * @deprecated Use {@link ExtensionAttachTo} instead. - * @public - */ -export type ExtensionAttachToSpec = ExtensionAttachTo; +export type ExtensionAttachTo = { id: string; input: string }; /** @public */ export interface Extension { $$type: '@backstage/Extension'; readonly id: string; - readonly attachTo: ExtensionAttachToSpec; + readonly attachTo: ExtensionAttachTo; readonly disabled: boolean; readonly configSchema?: PortableSchema; } @@ -82,6 +75,7 @@ export type InternalExtension = Extension< } | { readonly version: 'v2'; + readonly if?: FilterPredicate; readonly inputs: { [inputName in string]: ExtensionInput }; readonly output: Array; factory(options: { @@ -149,11 +143,11 @@ function resolveExtensionId( } function resolveAttachTo( - attachTo: ExtensionDefinitionAttachTo, + attachTo: ExtensionDefinitionAttachTo | ExtensionDefinitionAttachTo[], namespace?: string, -): ExtensionAttachToSpec { +): ExtensionAttachTo | ExtensionAttachTo[] { const resolveSpec = ( - spec: Exclude>, + spec: ExtensionDefinitionAttachTo, ): { id: string; input: string } => { if (OpaqueExtensionInput.isType(spec)) { const { context } = OpaqueExtensionInput.toInternal(spec); @@ -190,6 +184,58 @@ function resolveAttachTo( return resolveSpec(attachTo); } +/** + * Resolves a list of extension definitions into extensions, returning both the + * resolved extensions and a map of extension definitions keyed by resolved ID. + * Throws if any two definitions resolve to the same ID. + * + * @internal + */ +export function resolveExtensionDefinitions( + definitions: Iterable, + context: { namespace: string; featureType: string }, +): { + extensions: Extension[]; + extensionDefinitionsById: Map< + string, + typeof OpaqueExtensionDefinition.TInternal + >; +} { + const extensions = new Array>(); + const extensionDefinitionsById = new Map< + string, + typeof OpaqueExtensionDefinition.TInternal + >(); + + for (const def of definitions) { + const internal = OpaqueExtensionDefinition.toInternal(def); + const ext = resolveExtensionDefinition(def, { + namespace: context.namespace, + }); + extensions.push(ext); + extensionDefinitionsById.set(ext.id, { + ...internal, + namespace: context.namespace, + }); + } + + if (extensions.length !== extensionDefinitionsById.size) { + const extensionIds = extensions.map(e => e.id); + const duplicates = Array.from( + new Set( + extensionIds.filter((id, index) => extensionIds.indexOf(id) !== index), + ), + ); + throw new Error( + `${context.featureType} '${ + context.namespace + }' provided duplicate extensions: ${duplicates.join(', ')}`, + ); + } + + return { extensions, extensionDefinitionsById }; +} + /** @internal */ export function resolveExtensionDefinition< T extends ExtensionDefinitionParameters, @@ -213,7 +259,7 @@ export function resolveExtensionDefinition< return { ...rest, - attachTo: resolveAttachTo(attachTo, namespace), + attachTo: resolveAttachTo(attachTo, namespace) as ExtensionAttachTo, $$type: '@backstage/Extension', version: internalDefinition.version, id, diff --git a/packages/frontend-plugin-api/src/wiring/resolveInputOverrides.ts b/packages/frontend-plugin-api/src/wiring/resolveInputOverrides.ts index b46bb426fa..9a122c8288 100644 --- a/packages/frontend-plugin-api/src/wiring/resolveInputOverrides.ts +++ b/packages/frontend-plugin-api/src/wiring/resolveInputOverrides.ts @@ -16,7 +16,6 @@ import { AppNode } from '../apis'; import { Expand } from '@backstage/types'; -import { ResolvedExtensionInput } from './createExtension'; import { createExtensionDataContainer } from '@internal/frontend'; import { ExtensionDataRefToValue, @@ -37,6 +36,7 @@ export type ResolvedInputValueOverrides< { optional: infer IOptional extends boolean; singleton: boolean; + internal?: boolean; } > ? IOptional extends true @@ -44,7 +44,11 @@ export type ResolvedInputValueOverrides< : KName : never]: TInputs[KName] extends ExtensionInput< infer IDataRefs, - { optional: boolean; singleton: infer ISingleton extends boolean } + { + optional: boolean; + singleton: infer ISingleton extends boolean; + internal?: boolean; + } > ? ISingleton extends true ? Iterable> @@ -56,6 +60,7 @@ export type ResolvedInputValueOverrides< { optional: infer IOptional extends boolean; singleton: boolean; + internal?: boolean; } > ? IOptional extends true @@ -63,7 +68,11 @@ export type ResolvedInputValueOverrides< : never : never]?: TInputs[KName] extends ExtensionInput< infer IDataRefs, - { optional: boolean; singleton: infer ISingleton extends boolean } + { + optional: boolean; + singleton: infer ISingleton extends boolean; + internal?: boolean; + } > ? ISingleton extends true ? Iterable> @@ -114,7 +123,7 @@ export function resolveInputOverrides( ); } newInputs[name] = Object.assign(providedContainer, { - node: (originalInput as ResolvedExtensionInput).node, + node: (originalInput as { node: AppNode }).node, }) as any; } } else { @@ -148,7 +157,7 @@ export function resolveInputOverrides( declaredInput.extensionData, ); return Object.assign(providedContainer, { - node: (originalInput[i] as ResolvedExtensionInput).node, + node: (originalInput[i] as { node: AppNode }).node, }) as any; }); } else if (withNodesCount === providedData.length) { diff --git a/packages/frontend-plugin-api/src/wiring/types.ts b/packages/frontend-plugin-api/src/wiring/types.ts index 460a399871..39f0c5b03c 100644 --- a/packages/frontend-plugin-api/src/wiring/types.ts +++ b/packages/frontend-plugin-api/src/wiring/types.ts @@ -29,6 +29,8 @@ import { FrontendPlugin } from './createFrontendPlugin'; export type FeatureFlagConfig = { /** Feature flag name */ name: string; + /** Feature flag description */ + description?: string; }; /** @public */ @@ -60,7 +62,10 @@ export type ExtensionDataContainer = : never; }; -/** @public */ +/** + * @public + * @deprecated Moved to {@link @backstage/frontend-app-api#ExtensionFactoryMiddleware} + */ export type ExtensionFactoryMiddleware = ( originalFactory: (contextOverrides?: { config?: JsonObject; @@ -73,4 +78,6 @@ export type ExtensionFactoryMiddleware = ( ) => Iterable>; /** @public */ -export type FrontendFeature = FrontendPlugin | FrontendModule; +export type FrontendFeature = + | (Omit & { pluginId?: string }) + | FrontendModule; diff --git a/packages/frontend-test-utils/CHANGELOG.md b/packages/frontend-test-utils/CHANGELOG.md index 1fa18a1696..d9e4148da1 100644 --- a/packages/frontend-test-utils/CHANGELOG.md +++ b/packages/frontend-test-utils/CHANGELOG.md @@ -1,5 +1,399 @@ # @backstage/frontend-test-utils +## 0.5.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app@0.4.3 + - @backstage/core-app-api@1.20.0 + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/filter-predicates@0.1.2 + - @backstage/frontend-app-api@0.16.2 + - @backstage/plugin-permission-react@0.5.0 + - @backstage/test-utils@1.7.17 + - @backstage/config@1.3.7 + - @backstage/core-plugin-api@1.12.5 + - @backstage/plugin-app-react@0.2.2 + - @backstage/plugin-permission-common@0.9.8 + +## 0.5.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.20.0-next.2 + - @backstage/plugin-app@0.4.3-next.2 + - @backstage/config@1.3.7-next.0 + - @backstage/core-plugin-api@1.12.5-next.2 + - @backstage/filter-predicates@0.1.2-next.0 + - @backstage/frontend-app-api@0.16.2-next.2 + - @backstage/frontend-plugin-api@0.16.0-next.2 + - @backstage/plugin-permission-common@0.9.8-next.0 + - @backstage/test-utils@1.7.17-next.2 + - @backstage/plugin-permission-react@0.4.42-next.1 + +## 0.5.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app@0.4.3-next.1 + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/frontend-app-api@0.16.2-next.1 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/test-utils@1.7.17-next.1 + - @backstage/plugin-app-react@0.2.2-next.1 + +## 0.5.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app@0.4.3-next.0 + - @backstage/frontend-plugin-api@0.15.2-next.0 + - @backstage/frontend-app-api@0.16.2-next.0 + - @backstage/core-app-api@1.19.7-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + - @backstage/plugin-app-react@0.2.2-next.0 + - @backstage/test-utils@1.7.17-next.0 + - @backstage/config@1.3.6 + - @backstage/filter-predicates@0.1.1 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/plugin-permission-react@0.4.42-next.0 + +## 0.5.1 + +### Patch Changes + +- b56f573: Deprecated standalone mock API exports in favor of the `mockApis` namespace. This includes the mock classes (`MockAlertApi`, `MockAnalyticsApi`, `MockConfigApi`, `MockErrorApi`, `MockFetchApi`, `MockFeatureFlagsApi`, `MockPermissionApi`, `MockStorageApi`, `MockTranslationApi`), their option types (`MockErrorApiOptions`, `MockFeatureFlagsApiOptions`), and the `ErrorWithContext` type. `MockFetchApiOptions` is kept as a non-deprecated export. Use the `mockApis` namespace instead, for example `mockApis.alert()` or `mockApis.alert.mock()`. +- 479282f: Fixed type inference of `TestApiPair` when using tuple syntax by wrapping `MockWithApiFactory` in `NoInfer`. +- 8e09233: Added a missing dependency on `@backstage/filter-predicates` to `@backstage/frontend-test-utils`. This fixes package metadata for consumers using the frontend test app helpers with predicate-based behavior. +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- 909c742: Switched `MockTranslationApi` and related test utility imports from `@backstage/core-plugin-api/alpha` to the stable `@backstage/frontend-plugin-api` export. The `TranslationApi` type in the API report is now sourced from a single package. This has no effect on runtime behavior. +- Updated dependencies + - @backstage/frontend-app-api@0.16.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/core-app-api@1.19.6 + - @backstage/plugin-app@0.4.1 + - @backstage/plugin-app-react@0.2.1 + - @backstage/plugin-permission-react@0.4.41 + - @backstage/filter-predicates@0.1.1 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/test-utils@1.7.16 + +## 0.5.1-next.2 + +### Patch Changes + +- b56f573: Deprecated standalone mock API exports in favor of the `mockApis` namespace. This includes the mock classes (`MockAlertApi`, `MockAnalyticsApi`, `MockConfigApi`, `MockErrorApi`, `MockFetchApi`, `MockFeatureFlagsApi`, `MockPermissionApi`, `MockStorageApi`, `MockTranslationApi`), their option types (`MockErrorApiOptions`, `MockFeatureFlagsApiOptions`), and the `ErrorWithContext` type. `MockFetchApiOptions` is kept as a non-deprecated export. Use the `mockApis` namespace instead, for example `mockApis.alert()` or `mockApis.alert.mock()`. +- Updated dependencies + - @backstage/core-app-api@1.19.6-next.1 + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/frontend-app-api@0.16.0-next.1 + - @backstage/plugin-app-react@0.2.1-next.1 + - @backstage/plugin-app@0.4.1-next.2 + +## 0.5.1-next.1 + +### Patch Changes + +- 479282f: Fixed type inference of `TestApiPair` when using tuple syntax by wrapping `MockWithApiFactory` in `NoInfer`. +- Updated dependencies + - @backstage/plugin-app@0.4.1-next.1 + - @backstage/config@1.3.6 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/frontend-app-api@0.15.1-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/test-utils@1.7.16-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-app-react@0.2.1-next.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## 0.5.1-next.0 + +### Patch Changes + +- 909c742: Switched `MockTranslationApi` and related test utility imports from `@backstage/core-plugin-api/alpha` to the stable `@backstage/frontend-plugin-api` export. The `TranslationApi` type in the API report is now sourced from a single package. This has no effect on runtime behavior. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/plugin-app@0.4.1-next.0 + - @backstage/config@1.3.6 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/frontend-app-api@0.15.1-next.0 + - @backstage/test-utils@1.7.16-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-app-react@0.2.1-next.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## 0.5.0 + +### Minor Changes + +- 09a6aad: **BREAKING**: Removed the `TestApiRegistry` class, use `TestApiProvider` directly instead, storing reused APIs in a variable, e.g. `const apis = [...] as const`. +- d2ac2ec: Added `MockAlertApi` and `MockFeatureFlagsApi` implementations to the `mockApis` namespace. The mock implementations include useful testing methods like `clearAlerts()`, `waitForAlert()`, `getState()`, `setState()`, and `clearState()` for better test ergonomics. +- 09a6aad: **BREAKING**: The `mockApis` namespace is no longer a re-export from `@backstage/test-utils`. It's now a standalone namespace with mock implementations of most core APIs. Mock API instances can be passed directly to `TestApiProvider`, `renderInTestApp`, and `renderTestApp` without needing `[apiRef, impl]` tuples. As part of this change, the `.factory()` method on some mocks has been removed, since it's now redundant. + + ```tsx + // Before + import { mockApis } from '@backstage/frontend-test-utils'; + + renderInTestApp(, { + apis: [[identityApiRef, mockApis.identity()]], + }); + + // After - mock APIs can be passed directly + renderInTestApp(, { + apis: [mockApis.identity()], + }); + ``` + + This change also adds `createApiMock`, a public utility for creating mock API factories, intended for plugin authors to create their own `.mock()` variants. + +### Patch Changes + +- 22864b7: Added an `apis` option to `createExtensionTester`, `renderInTestApp`, and `renderTestApp` to override APIs when testing extensions. Use the `mockApis` helpers to create mock implementations: + + ```typescript + import { identityApiRef } from '@backstage/frontend-plugin-api'; + import { mockApis } from '@backstage/frontend-test-utils'; + + // Override APIs in createExtensionTester + const tester = createExtensionTester(myExtension, { + apis: [ + [ + identityApiRef, + mockApis.identity({ userEntityRef: 'user:default/guest' }), + ], + ], + }); + + // Override APIs in renderInTestApp + renderInTestApp(, { + apis: [ + [ + identityApiRef, + mockApis.identity({ userEntityRef: 'user:default/guest' }), + ], + ], + }); + + // Override APIs in renderTestApp + renderTestApp({ + extensions: [myExtension], + apis: [ + [ + identityApiRef, + mockApis.identity({ userEntityRef: 'user:default/guest' }), + ], + ], + }); + ``` + +- 15ed3f9: Added `snapshot()` method to `ExtensionTester`, which returns a tree-shaped representation of the resolved extension hierarchy. Convenient to use with `toMatchInlineSnapshot()`. +- 013ec22: Added `mountedRoutes` option to `renderTestApp` for binding route refs to paths, matching the existing option in `renderInTestApp`: + + ```typescript + renderTestApp({ + extensions: [...], + mountedRoutes: { + '/my-path': myRouteRef, + }, + }); + ``` + +- d7dd5bd: Fixed Router deprecation warning and switched to using new `RouterBlueprint` from `@backstage/plugin-app-api`. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/frontend-app-api@0.15.0 + - @backstage/core-app-api@1.19.5 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/plugin-app-react@0.2.0 + - @backstage/plugin-app@0.4.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-permission-react@0.4.40 + - @backstage/version-bridge@1.0.12 + - @backstage/test-utils@1.7.15 + - @backstage/plugin-permission-common@0.9.6 + +## 0.5.0-next.2 + +### Minor Changes + +- 09a6aad: **BREAKING**: Removed the `TestApiRegistry` class, use `TestApiProvider` directly instead, storing reused APIs in a variable, e.g. `const apis = [...] as const`. +- d2ac2ec: Added `MockAlertApi` and `MockFeatureFlagsApi` implementations to the `mockApis` namespace. The mock implementations include useful testing methods like `clearAlerts()`, `waitForAlert()`, `getState()`, `setState()`, and `clearState()` for better test ergonomics. +- 09a6aad: **BREAKING**: The `mockApis` namespace is no longer a re-export from `@backstage/test-utils`. It's now a standalone namespace with mock implementations of most core APIs. Mock API instances can be passed directly to `TestApiProvider`, `renderInTestApp`, and `renderTestApp` without needing `[apiRef, impl]` tuples. As part of this change, the `.factory()` method on some mocks has been removed, since it's now redundant. + + ```tsx + // Before + import { mockApis } from '@backstage/frontend-test-utils'; + + renderInTestApp(, { + apis: [[identityApiRef, mockApis.identity()]], + }); + + // After - mock APIs can be passed directly + renderInTestApp(, { + apis: [mockApis.identity()], + }); + ``` + + This change also adds `createApiMock`, a public utility for creating mock API factories, intended for plugin authors to create their own `.mock()` variants. + +### Patch Changes + +- 15ed3f9: Added `snapshot()` method to `ExtensionTester`, which returns a tree-shaped representation of the resolved extension hierarchy. Convenient to use with `toMatchInlineSnapshot()`. +- 013ec22: Added `mountedRoutes` option to `renderTestApp` for binding route refs to paths, matching the existing option in `renderInTestApp`: + + ```typescript + renderTestApp({ + extensions: [...], + mountedRoutes: { + '/my-path': myRouteRef, + }, + }); + ``` + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/core-app-api@1.19.5-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/frontend-app-api@0.15.0-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-permission-react@0.4.40-next.1 + - @backstage/version-bridge@1.0.12-next.0 + - @backstage/test-utils@1.7.15-next.2 + - @backstage/plugin-app@0.4.0-next.2 + - @backstage/plugin-app-react@0.1.1-next.0 + +## 0.4.6-next.1 + +### Patch Changes + +- 22864b7: Added an `apis` option to `createExtensionTester`, `renderInTestApp`, and `renderTestApp` to override APIs when testing extensions. Use the `mockApis` helpers to create mock implementations: + + ```typescript + import { identityApiRef } from '@backstage/frontend-plugin-api'; + import { mockApis } from '@backstage/frontend-test-utils'; + + // Override APIs in createExtensionTester + const tester = createExtensionTester(myExtension, { + apis: [ + [ + identityApiRef, + mockApis.identity({ userEntityRef: 'user:default/guest' }), + ], + ], + }); + + // Override APIs in renderInTestApp + renderInTestApp(, { + apis: [ + [ + identityApiRef, + mockApis.identity({ userEntityRef: 'user:default/guest' }), + ], + ], + }); + + // Override APIs in renderTestApp + renderTestApp({ + extensions: [myExtension], + apis: [ + [ + identityApiRef, + mockApis.identity({ userEntityRef: 'user:default/guest' }), + ], + ], + }); + ``` + +- Updated dependencies + - @backstage/frontend-app-api@0.15.0-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/plugin-app@0.4.0-next.1 + - @backstage/test-utils@1.7.15-next.1 + +## 0.4.5-next.0 + +### Patch Changes + +- d7dd5bd: Fixed Router deprecation warning and switched to using new `RouterBlueprint` from `@backstage/plugin-app-api`. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/frontend-app-api@0.14.1-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/plugin-app@0.4.0-next.0 + - @backstage/plugin-app-react@0.1.1-next.0 + - @backstage/config@1.3.6 + - @backstage/test-utils@1.7.15-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## 0.4.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/plugin-app@0.3.4 + - @backstage/frontend-app-api@0.14.0 + +## 0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app@0.3.4-next.0 + - @backstage/frontend-app-api@0.13.4-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## 0.4.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app@0.3.3 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/test-utils@1.7.14 + - @backstage/frontend-app-api@0.13.3 + +## 0.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/test-utils@1.7.14-next.0 + - @backstage/plugin-app@0.3.3-next.0 + - @backstage/frontend-app-api@0.13.3-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## 0.4.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/frontend-app-api@0.13.2 + - @backstage/config@1.3.6 + - @backstage/plugin-app@0.3.2 + - @backstage/test-utils@1.7.13 + ## 0.4.1-next.0 ### Patch Changes diff --git a/packages/frontend-test-utils/knip-report.md b/packages/frontend-test-utils/knip-report.md index 5415a4c648..d8527228a4 100644 --- a/packages/frontend-test-utils/knip-report.md +++ b/packages/frontend-test-utils/knip-report.md @@ -1,9 +1,10 @@ # Knip report -## Unused dependencies (2) +## Unused dependencies (3) -| Name | Location | Severity | -| :------------------------ | :----------- | :------- | -| @backstage/version-bridge | packages/frontend-test-utils/package.json | error | -| zod | packages/frontend-test-utils/package.json | error | +| Name | Location | Severity | +| :--------------------------- | :---------------- | :------- | +| @backstage/filter-predicates | package.json:37:6 | error | +| @backstage/version-bridge | package.json:46:6 | error | +| zod | package.json:49:6 | error | diff --git a/packages/frontend-test-utils/package.json b/packages/frontend-test-utils/package.json index 18ab54d197..741e66313d 100644 --- a/packages/frontend-test-utils/package.json +++ b/packages/frontend-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-test-utils", - "version": "0.4.1-next.0", + "version": "0.5.2", "backstage": { "role": "web-library" }, @@ -32,30 +32,45 @@ }, "dependencies": { "@backstage/config": "workspace:^", + "@backstage/core-app-api": "workspace:^", + "@backstage/core-plugin-api": "workspace:^", + "@backstage/filter-predicates": "workspace:^", "@backstage/frontend-app-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", "@backstage/plugin-app": "workspace:^", + "@backstage/plugin-app-react": "workspace:^", + "@backstage/plugin-permission-common": "workspace:^", + "@backstage/plugin-permission-react": "workspace:^", "@backstage/test-utils": "workspace:^", "@backstage/types": "workspace:^", "@backstage/version-bridge": "workspace:^", - "zod": "^3.22.4" + "i18next": "^22.4.15", + "zen-observable": "^0.10.0", + "zod": "^3.25.76 || ^4.0.0" }, "devDependencies": { "@backstage/cli": "workspace:^", "@testing-library/jest-dom": "^6.0.0", + "@types/jest": "*", "@types/react": "^18.0.0", + "@types/zen-observable": "^0.8.0", + "msw": "^2.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@testing-library/react": "^16.0.0", + "@types/jest": "*", "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { + "@types/jest": { + "optional": true + }, "@types/react": { "optional": true } diff --git a/packages/frontend-test-utils/report.api.md b/packages/frontend-test-utils/report.api.md index b99cf843d4..6d9f8964b1 100644 --- a/packages/frontend-test-utils/report.api.md +++ b/packages/frontend-test-utils/report.api.md @@ -3,46 +3,94 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AlertApi } from '@backstage/frontend-plugin-api'; +import { AlertMessage } from '@backstage/frontend-plugin-api'; import { AnalyticsApi } from '@backstage/frontend-plugin-api'; import { AnalyticsEvent } from '@backstage/frontend-plugin-api'; -import { ApiMock } from '@backstage/test-utils'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; +import { ApiRef } from '@backstage/frontend-plugin-api'; import { AppNode } from '@backstage/frontend-plugin-api'; import { AppNodeInstance } from '@backstage/frontend-plugin-api'; -import { ErrorWithContext } from '@backstage/test-utils'; +import { AuthorizeResult } from '@backstage/plugin-permission-common'; +import { Config } from '@backstage/config'; +import { ConfigApi } from '@backstage/core-plugin-api'; +import { ConfigApi as ConfigApi_2 } from '@backstage/frontend-plugin-api'; +import { DiscoveryApi } from '@backstage/frontend-plugin-api'; +import { DiscoveryApi as DiscoveryApi_2 } from '@backstage/core-plugin-api'; +import { ErrorApi } from '@backstage/core-plugin-api'; +import { ErrorApi as ErrorApi_2 } from '@backstage/frontend-plugin-api'; +import { ErrorApiError } from '@backstage/core-plugin-api'; +import { ErrorApiErrorContext } from '@backstage/core-plugin-api'; +import { EvaluatePermissionRequest } from '@backstage/plugin-permission-common'; +import { EvaluatePermissionResponse } from '@backstage/plugin-permission-common'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionDefinitionParameters } from '@backstage/frontend-plugin-api'; +import { FeatureFlag } from '@backstage/frontend-plugin-api'; +import { FeatureFlagsApi } from '@backstage/frontend-plugin-api'; +import { FeatureFlagsSaveOptions } from '@backstage/frontend-plugin-api'; +import { FeatureFlagState } from '@backstage/frontend-plugin-api'; +import { FetchApi } from '@backstage/core-plugin-api'; +import { FetchApi as FetchApi_2 } from '@backstage/frontend-plugin-api'; import { FrontendFeature } from '@backstage/frontend-plugin-api'; +import { IdentityApi } from '@backstage/frontend-plugin-api'; +import { IdentityApi as IdentityApi_2 } from '@backstage/core-plugin-api'; import { JsonObject } from '@backstage/types'; -import { mockApis } from '@backstage/test-utils'; -import { MockConfigApi } from '@backstage/test-utils'; -import { MockErrorApi } from '@backstage/test-utils'; -import { MockErrorApiOptions } from '@backstage/test-utils'; -import { MockFetchApi } from '@backstage/test-utils'; -import { MockFetchApiOptions } from '@backstage/test-utils'; -import { MockPermissionApi } from '@backstage/test-utils'; -import { MockStorageApi } from '@backstage/test-utils'; -import { MockStorageBucket } from '@backstage/test-utils'; +import { JsonValue } from '@backstage/types'; +import { Observable } from '@backstage/types'; +import { PermissionApi } from '@backstage/plugin-permission-react'; +import { ReactNode } from 'react'; import { registerMswTestHooks } from '@backstage/test-utils'; import { RenderResult } from '@testing-library/react'; import { RouteRef } from '@backstage/frontend-plugin-api'; -import { TestApiProvider } from '@backstage/test-utils'; -import { TestApiProviderProps } from '@backstage/test-utils'; -import { TestApiRegistry } from '@backstage/test-utils'; -import { testingLibraryDomTypesQueries } from '@testing-library/dom/types/queries'; +import { StorageApi } from '@backstage/core-plugin-api'; +import { StorageApi as StorageApi_2 } from '@backstage/frontend-plugin-api'; +import { StorageValueSnapshot } from '@backstage/core-plugin-api'; +import { TranslationApi } from '@backstage/frontend-plugin-api'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; +import { TranslationSnapshot } from '@backstage/frontend-plugin-api'; import { withLogCollector } from '@backstage/test-utils'; -export { ApiMock }; +// @public +export type ApiMock = { + [mockApiFactorySymbol]: ApiFactory; +} & { + [Key in keyof TApi]: TApi[Key] extends (...args: infer Args) => infer Return + ? TApi[Key] & jest.MockInstance + : TApi[Key]; +}; + +// @public +export function attachMockApiFactory( + apiRef: ApiRef, + implementation: TImpl, +): TImpl & { + [mockApiFactorySymbol]: ApiFactory; +}; + +// @public +export function createApiMock( + apiRef: ApiRef, + mockFactory: () => jest.Mocked, +): (partialImpl?: Partial) => ApiMock; // @public (undocumented) -export function createExtensionTester( +export function createExtensionTester< + T extends ExtensionDefinitionParameters, + TApiPairs extends any[] = any[], +>( subject: ExtensionDefinition, options?: { config?: T['configInput']; + apis?: readonly [...TestApiPairs]; }, ): ExtensionTester>; -export { ErrorWithContext }; +// @public @deprecated +export type ErrorWithContext = { + error: ErrorApiError; + context?: ErrorApiErrorContext; +}; // @public (undocumented) export class ExtensionQuery { @@ -61,6 +109,14 @@ export class ExtensionQuery { get node(): AppNode; } +// @public +export interface ExtensionSnapshotNode { + children?: Record; + disabled?: true; + id: string; + outputs?: string[]; +} + // @public (undocumented) export class ExtensionTester { // (undocumented) @@ -84,9 +140,24 @@ export class ExtensionTester { ): ExtensionQuery>; // (undocumented) reactElement(): JSX.Element; + snapshot(): ExtensionSnapshotNode; } -// @public +// @public @deprecated +export class MockAlertApi implements AlertApi { + // (undocumented) + alert$(): Observable; + clearAlerts(): void; + getAlerts(): AlertMessage[]; + // (undocumented) + post(alert: AlertMessage): void; + waitForAlert( + predicate: (alert: AlertMessage) => boolean, + timeoutMs?: number, + ): Promise; +} + +// @public @deprecated export class MockAnalyticsApi implements AnalyticsApi { // (undocumented) captureEvent(event: AnalyticsEvent): void; @@ -94,59 +165,320 @@ export class MockAnalyticsApi implements AnalyticsApi { getEvents(): AnalyticsEvent[]; } -export { mockApis }; +// @public +export type MockApiFactorySymbol = typeof mockApiFactorySymbol; -export { MockConfigApi }; +// @public +export namespace mockApis { + export function alert(): MockWithApiFactory; + export namespace alert { + const mock: ( + partialImpl?: Partial | undefined, + ) => ApiMock; + } + export function analytics(): MockAnalyticsApi & + MockWithApiFactory; + export namespace analytics { + const // (undocumented) + mock: ( + partialImpl?: Partial | undefined, + ) => ApiMock; + } + export function config(options?: { + data?: JsonObject; + }): MockConfigApi & MockWithApiFactory; + export namespace config { + const // (undocumented) + mock: (partialImpl?: Partial | undefined) => ApiMock; + } + export function discovery(options?: { + baseUrl?: string; + }): DiscoveryApi & MockWithApiFactory; + export namespace discovery { + const // (undocumented) + mock: ( + partialImpl?: Partial | undefined, + ) => ApiMock; + } + export function error(options?: { + collect?: boolean; + }): MockErrorApi & MockWithApiFactory; + export namespace error { + const // (undocumented) + mock: ( + partialImpl?: Partial | undefined, + ) => ApiMock; + } + export function featureFlags(options?: { + initialStates?: Record; + }): MockWithApiFactory; + export namespace featureFlags { + const mock: ( + partialImpl?: Partial | undefined, + ) => ApiMock; + } + export function fetch( + options?: MockFetchApiOptions, + ): MockFetchApi & MockWithApiFactory; + export namespace fetch { + const // (undocumented) + mock: ( + partialImpl?: Partial | undefined, + ) => ApiMock; + } + export function identity(options?: { + userEntityRef?: string; + ownershipEntityRefs?: string[]; + token?: string; + email?: string; + displayName?: string; + picture?: string; + }): MockWithApiFactory; + export namespace identity { + const // (undocumented) + mock: ( + partialImpl?: Partial | undefined, + ) => ApiMock; + } + export function permission(options?: { + authorize?: + | AuthorizeResult.ALLOW + | AuthorizeResult.DENY + | (( + request: EvaluatePermissionRequest, + ) => AuthorizeResult.ALLOW | AuthorizeResult.DENY); + }): MockPermissionApi & MockWithApiFactory; + export namespace permission { + const // (undocumented) + mock: ( + partialImpl?: Partial | undefined, + ) => ApiMock; + } + export function storage(options?: { + data?: JsonObject; + }): MockStorageApi & MockWithApiFactory; + export namespace storage { + const // (undocumented) + mock: ( + partialImpl?: Partial | undefined, + ) => ApiMock; + } + export function translation(): MockTranslationApi & + MockWithApiFactory; + export namespace translation { + const mock: ( + partialImpl?: Partial | undefined, + ) => ApiMock; + } +} -export { MockErrorApi }; +// @public @deprecated +export class MockConfigApi implements ConfigApi { + constructor(input: { data: JsonObject }); + get(key?: string): T; + getBoolean(key: string): boolean; + getConfig(key: string): Config; + getConfigArray(key: string): Config[]; + getNumber(key: string): number; + getOptional(key?: string): T | undefined; + getOptionalBoolean(key: string): boolean | undefined; + getOptionalConfig(key: string): Config | undefined; + getOptionalConfigArray(key: string): Config[] | undefined; + getOptionalNumber(key: string): number | undefined; + getOptionalString(key: string): string | undefined; + getOptionalStringArray(key: string): string[] | undefined; + getString(key: string): string; + getStringArray(key: string): string[]; + has(key: string): boolean; + keys(): string[]; +} -export { MockErrorApiOptions }; +// @public @deprecated +export class MockErrorApi implements ErrorApi { + constructor(options?: { collect?: boolean }); + // (undocumented) + error$(): Observable<{ + error: ErrorApiError; + context?: ErrorApiErrorContext; + }>; + // (undocumented) + getErrors(): { + error: ErrorApiError; + context?: ErrorApiErrorContext; + }[]; + // (undocumented) + post(error: ErrorApiError, context?: ErrorApiErrorContext): void; + // (undocumented) + waitForError( + pattern: RegExp, + timeoutMs?: number, + ): Promise<{ + error: ErrorApiError; + context?: ErrorApiErrorContext; + }>; +} -export { MockFetchApi }; +// @public @deprecated +export type MockErrorApiOptions = { + collect?: boolean; +}; -export { MockFetchApiOptions }; +// @public @deprecated +export class MockFeatureFlagsApi implements FeatureFlagsApi { + constructor(options?: MockFeatureFlagsApiOptions); + clearState(): void; + // (undocumented) + getRegisteredFlags(): FeatureFlag[]; + getState(): Record; + // (undocumented) + isActive(name: string): boolean; + // (undocumented) + registerFlag(flag: FeatureFlag): void; + // (undocumented) + save(options: FeatureFlagsSaveOptions): void; + setState(states: Record): void; +} -export { MockPermissionApi }; +// @public @deprecated +export interface MockFeatureFlagsApiOptions { + initialStates?: Record; +} -export { MockStorageApi }; +// @public @deprecated +export class MockFetchApi implements FetchApi { + constructor(options?: MockFetchApiOptions); + get fetch(): typeof fetch; +} -export { MockStorageBucket }; +// @public +export interface MockFetchApiOptions { + baseImplementation?: undefined | 'none' | typeof fetch; + injectIdentityAuth?: + | undefined + | { + token: string; + } + | { + identityApi: Pick; + }; + resolvePluginProtocol?: + | undefined + | { + discoveryApi: Pick; + }; +} + +// @public @deprecated +export class MockPermissionApi implements PermissionApi { + constructor( + requestHandler?: ( + request: EvaluatePermissionRequest, + ) => AuthorizeResult.ALLOW | AuthorizeResult.DENY, + ); + // (undocumented) + authorize( + request: EvaluatePermissionRequest, + ): Promise; +} + +// @public @deprecated +export class MockStorageApi implements StorageApi { + // (undocumented) + static create(data?: JsonObject): MockStorageApi; + // (undocumented) + forBucket(name: string): StorageApi; + // (undocumented) + observe$( + key: string, + ): Observable>; + // (undocumented) + remove(key: string): Promise; + // (undocumented) + set(key: string, data: T): Promise; + // (undocumented) + snapshot(key: string): StorageValueSnapshot; +} + +// @public @deprecated +export class MockTranslationApi implements TranslationApi { + // (undocumented) + static create(): MockTranslationApi; + // (undocumented) + getTranslation< + TMessages extends { + [key in string]: string; + }, + >( + translationRef: TranslationRef, + ): TranslationSnapshot; + // (undocumented) + translation$< + TMessages extends { + [key in string]: string; + }, + >(): Observable>; +} + +// @public +export type MockWithApiFactory = TApi & { + [mockApiFactorySymbol]: ApiFactory; +}; export { registerMswTestHooks }; // @public -export function renderInTestApp( +export function renderInTestApp( element: JSX.Element, - options?: TestAppOptions, + options?: TestAppOptions, ): RenderResult; // @public -export function renderTestApp( - options: RenderTestAppOptions, -): RenderResult; +export function renderTestApp( + options?: RenderTestAppOptions, +): RenderResult; // @public -export type RenderTestAppOptions = { +export type RenderTestAppOptions = { config?: JsonObject; extensions?: ExtensionDefinition[]; features?: FrontendFeature[]; initialRouteEntries?: string[]; + mountedRoutes?: { + [path: string]: RouteRef; + }; + apis?: readonly [...TestApiPairs]; }; -export { TestApiProvider }; - -export { TestApiProviderProps }; - -export { TestApiRegistry }; +// @public +export type TestApiPair = + | readonly [ApiRef, TApi extends infer TImpl ? Partial : never] + | MockWithApiFactory>; // @public -export type TestAppOptions = { +export type TestApiPairs = { + [TIndex in keyof TApiPairs]: TestApiPair; +}; + +// @public +export function TestApiProvider( + props: TestApiProviderProps, +): JSX.Element; + +// @public +export type TestApiProviderProps = { + apis: readonly [...TestApiPairs]; + children: ReactNode; +}; + +// @public +export type TestAppOptions = { mountedRoutes?: { [path: string]: RouteRef; }; config?: JsonObject; features?: FrontendFeature[]; initialRouteEntries?: string[]; + apis?: readonly [...TestApiPairs]; }; export { withLogCollector }; diff --git a/packages/frontend-test-utils/src/apis/AlertApi/MockAlertApi.test.ts b/packages/frontend-test-utils/src/apis/AlertApi/MockAlertApi.test.ts new file mode 100644 index 0000000000..ddfbacd3b9 --- /dev/null +++ b/packages/frontend-test-utils/src/apis/AlertApi/MockAlertApi.test.ts @@ -0,0 +1,107 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { MockAlertApi } from './MockAlertApi'; + +describe('MockAlertApi', () => { + it('should collect alerts', () => { + const api = new MockAlertApi(); + + api.post({ message: 'Test alert 1' }); + api.post({ message: 'Test alert 2', severity: 'error' }); + api.post({ + message: 'Test alert 3', + severity: 'warning', + display: 'permanent', + }); + + expect(api.getAlerts()).toHaveLength(3); + expect(api.getAlerts()[0]).toMatchObject({ message: 'Test alert 1' }); + expect(api.getAlerts()[1]).toMatchObject({ + message: 'Test alert 2', + severity: 'error', + }); + }); + + it('should clear alerts', () => { + const api = new MockAlertApi(); + + api.post({ message: 'Test alert' }); + expect(api.getAlerts()).toHaveLength(1); + + api.clearAlerts(); + expect(api.getAlerts()).toHaveLength(0); + }); + + it('should notify observers', async () => { + const api = new MockAlertApi(); + const messages: string[] = []; + + const collected = new Promise(resolve => { + api.alert$().subscribe({ + next: alert => { + messages.push(alert.message); + if (messages.length === 2) { + resolve(); + } + }, + }); + }); + + api.post({ message: 'First' }); + api.post({ message: 'Second' }); + + await collected; + expect(messages).toEqual(['First', 'Second']); + }); + + it('should wait for matching alert', async () => { + const api = new MockAlertApi(); + + setTimeout(() => { + api.post({ message: 'Wrong alert' }); + api.post({ message: 'Right alert', severity: 'error' }); + }, 10); + + const alert = await api.waitForAlert( + a => a.message === 'Right alert', + 1000, + ); + + expect(alert).toMatchObject({ message: 'Right alert', severity: 'error' }); + }); + + it('should timeout if alert never appears', async () => { + const api = new MockAlertApi(); + + await expect( + api.waitForAlert(a => a.message === 'Never posted', 100), + ).rejects.toThrow('Timed out waiting for alert'); + }); + + it('should resolve immediately if alert already exists', async () => { + const api = new MockAlertApi(); + + api.post({ message: 'Already posted' }); + + const alert = await api.waitForAlert( + a => a.message === 'Already posted', + 1000, + ); + + expect(alert).toMatchObject({ message: 'Already posted' }); + }); +}); diff --git a/packages/frontend-test-utils/src/apis/AlertApi/MockAlertApi.ts b/packages/frontend-test-utils/src/apis/AlertApi/MockAlertApi.ts new file mode 100644 index 0000000000..dcd39fdb78 --- /dev/null +++ b/packages/frontend-test-utils/src/apis/AlertApi/MockAlertApi.ts @@ -0,0 +1,103 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AlertMessage } from '@backstage/frontend-plugin-api'; +import { AlertApi } from '@backstage/frontend-plugin-api'; +import { Observable } from '@backstage/types'; +import ObservableImpl from 'zen-observable'; + +/** + * Mock implementation of {@link @backstage/frontend-plugin-api#AlertApi} for testing alert behavior. + * + * @public + * @deprecated Use `mockApis.alert()` instead. + * @example + * ```ts + * const alertApi = mockApis.alert(); + * alertApi.post({ message: 'Test alert' }); + * expect(alertApi.getAlerts()).toHaveLength(1); + * ``` + */ +export class MockAlertApi implements AlertApi { + private readonly alerts: AlertMessage[] = []; + private readonly observers = new Set<(alert: AlertMessage) => void>(); + + post(alert: AlertMessage) { + this.alerts.push(alert); + this.observers.forEach(observer => observer(alert)); + } + + alert$(): Observable { + return new ObservableImpl(subscriber => { + const observer = (alert: AlertMessage) => { + subscriber.next(alert); + }; + this.observers.add(observer); + return () => { + this.observers.delete(observer); + }; + }); + } + + /** + * Get all alerts that have been posted. + */ + getAlerts(): AlertMessage[] { + return this.alerts; + } + + /** + * Clear all collected alerts. + */ + clearAlerts(): void { + this.alerts.length = 0; + } + + /** + * Wait for an alert matching the given predicate. + * + * @param predicate - Function to test each alert + * @param timeoutMs - Maximum time to wait in milliseconds + * @returns Promise that resolves with the matching alert + */ + async waitForAlert( + predicate: (alert: AlertMessage) => boolean, + timeoutMs: number = 2000, + ): Promise { + const existing = this.alerts.find(predicate); + if (existing) { + return existing; + } + const observers = this.observers; + + return new Promise((resolve, reject) => { + const timeoutId = setTimeout(() => { + observers.delete(observer); + reject(new Error('Timed out waiting for alert')); + }, timeoutMs); + + function observer(alert: AlertMessage) { + if (predicate(alert)) { + clearTimeout(timeoutId); + observers.delete(observer); + resolve(alert); + } + } + + observers.add(observer); + }); + } +} diff --git a/packages/frontend-test-utils/src/apis/AlertApi/index.ts b/packages/frontend-test-utils/src/apis/AlertApi/index.ts new file mode 100644 index 0000000000..919149e249 --- /dev/null +++ b/packages/frontend-test-utils/src/apis/AlertApi/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { MockAlertApi } from './MockAlertApi'; diff --git a/packages/frontend-test-utils/src/apis/AnalyticsApi/MockAnalyticsApi.ts b/packages/frontend-test-utils/src/apis/AnalyticsApi/MockAnalyticsApi.ts index f6ccf4e9d1..24117d980f 100644 --- a/packages/frontend-test-utils/src/apis/AnalyticsApi/MockAnalyticsApi.ts +++ b/packages/frontend-test-utils/src/apis/AnalyticsApi/MockAnalyticsApi.ts @@ -21,6 +21,7 @@ import { AnalyticsApi, AnalyticsEvent } from '@backstage/frontend-plugin-api'; * Use getEvents in tests to verify captured events. * * @public + * @deprecated Use `mockApis.analytics()` instead. */ export class MockAnalyticsApi implements AnalyticsApi { private events: AnalyticsEvent[] = []; diff --git a/packages/frontend-test-utils/src/apis/ConfigApi/MockConfigApi.test.ts b/packages/frontend-test-utils/src/apis/ConfigApi/MockConfigApi.test.ts new file mode 100644 index 0000000000..c3157a8aff --- /dev/null +++ b/packages/frontend-test-utils/src/apis/ConfigApi/MockConfigApi.test.ts @@ -0,0 +1,44 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { MockConfigApi } from './MockConfigApi'; + +describe('MockConfigApi', () => { + it('is able to read some basic config', () => { + const mock = new MockConfigApi({ + data: { + app: { + title: 'Hello', + }, + x: 1, + y: false, + z: [{ a: 3 }], + }, + }); + + expect(mock.getString('app.title')).toEqual('Hello'); + expect(mock.getNumber('x')).toEqual(1); + expect(mock.getBoolean('y')).toEqual(false); + expect(mock.getConfigArray('z')[0].getOptionalNumber('a')).toEqual(3); + + expect(() => mock.getString('x')).toThrow( + "Invalid type in config for key 'x' in 'mock-config', got number, wanted string", + ); + expect(() => mock.getString('missing')).toThrow( + "Missing required config value at 'missing'", + ); + }); +}); diff --git a/packages/frontend-test-utils/src/apis/ConfigApi/MockConfigApi.ts b/packages/frontend-test-utils/src/apis/ConfigApi/MockConfigApi.ts new file mode 100644 index 0000000000..ea445fac19 --- /dev/null +++ b/packages/frontend-test-utils/src/apis/ConfigApi/MockConfigApi.ts @@ -0,0 +1,112 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Config, ConfigReader } from '@backstage/config'; +import { JsonObject, JsonValue } from '@backstage/types'; +import { ConfigApi } from '@backstage/core-plugin-api'; + +/** + * MockConfigApi is a thin wrapper around {@link @backstage/config#ConfigReader} + * that can be used to mock configuration using a plain object. + * + * @public + * @deprecated Use `mockApis.config()` instead. + * @example + * ```tsx + * const mockConfig = mockApis.config({ + * data: { app: { baseUrl: 'https://example.com' } }, + * }); + * + * const rendered = await renderInTestApp( + * + * + * , + * ); + * ``` + */ +export class MockConfigApi implements ConfigApi { + private readonly config: ConfigReader; + + // NOTE: not extending in order to avoid inheriting the static `.fromConfigs` + constructor({ data }: { data: JsonObject }) { + this.config = new ConfigReader(data); + } + + /** {@inheritdoc @backstage/config#Config.has} */ + has(key: string): boolean { + return this.config.has(key); + } + /** {@inheritdoc @backstage/config#Config.keys} */ + keys(): string[] { + return this.config.keys(); + } + /** {@inheritdoc @backstage/config#Config.get} */ + get(key?: string): T { + return this.config.get(key); + } + /** {@inheritdoc @backstage/config#Config.getOptional} */ + getOptional(key?: string): T | undefined { + return this.config.getOptional(key); + } + /** {@inheritdoc @backstage/config#Config.getConfig} */ + getConfig(key: string): Config { + return this.config.getConfig(key); + } + /** {@inheritdoc @backstage/config#Config.getOptionalConfig} */ + getOptionalConfig(key: string): Config | undefined { + return this.config.getOptionalConfig(key); + } + /** {@inheritdoc @backstage/config#Config.getConfigArray} */ + getConfigArray(key: string): Config[] { + return this.config.getConfigArray(key); + } + /** {@inheritdoc @backstage/config#Config.getOptionalConfigArray} */ + getOptionalConfigArray(key: string): Config[] | undefined { + return this.config.getOptionalConfigArray(key); + } + /** {@inheritdoc @backstage/config#Config.getNumber} */ + getNumber(key: string): number { + return this.config.getNumber(key); + } + /** {@inheritdoc @backstage/config#Config.getOptionalNumber} */ + getOptionalNumber(key: string): number | undefined { + return this.config.getOptionalNumber(key); + } + /** {@inheritdoc @backstage/config#Config.getBoolean} */ + getBoolean(key: string): boolean { + return this.config.getBoolean(key); + } + /** {@inheritdoc @backstage/config#Config.getOptionalBoolean} */ + getOptionalBoolean(key: string): boolean | undefined { + return this.config.getOptionalBoolean(key); + } + /** {@inheritdoc @backstage/config#Config.getString} */ + getString(key: string): string { + return this.config.getString(key); + } + /** {@inheritdoc @backstage/config#Config.getOptionalString} */ + getOptionalString(key: string): string | undefined { + return this.config.getOptionalString(key); + } + /** {@inheritdoc @backstage/config#Config.getStringArray} */ + getStringArray(key: string): string[] { + return this.config.getStringArray(key); + } + /** {@inheritdoc @backstage/config#Config.getOptionalStringArray} */ + getOptionalStringArray(key: string): string[] | undefined { + return this.config.getOptionalStringArray(key); + } +} diff --git a/packages/cli/src/modules/build/lib/packager/index.ts b/packages/frontend-test-utils/src/apis/ConfigApi/index.ts similarity index 90% rename from packages/cli/src/modules/build/lib/packager/index.ts rename to packages/frontend-test-utils/src/apis/ConfigApi/index.ts index 75f3fdf71d..6418899f79 100644 --- a/packages/cli/src/modules/build/lib/packager/index.ts +++ b/packages/frontend-test-utils/src/apis/ConfigApi/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { createDistWorkspace } from './createDistWorkspace'; +export { MockConfigApi } from './MockConfigApi'; diff --git a/packages/frontend-test-utils/src/apis/ErrorApi/MockErrorApi.test.ts b/packages/frontend-test-utils/src/apis/ErrorApi/MockErrorApi.test.ts new file mode 100644 index 0000000000..1fbfb576bd --- /dev/null +++ b/packages/frontend-test-utils/src/apis/ErrorApi/MockErrorApi.test.ts @@ -0,0 +1,104 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { MockErrorApi } from './MockErrorApi'; + +async function ifResolved(promise: Promise): Promise { + return Promise.race([promise, Promise.resolve<'not-yet'>('not-yet')]); +} + +describe('MockErrorApi', () => { + it('should throw errors by default', () => { + const api = new MockErrorApi(); + expect(() => api.post(new Error('NOPE'))).toThrow( + 'MockErrorApi received unexpected error, Error: NOPE', + ); + }); + + it('should collect errors', () => { + const api = new MockErrorApi({ collect: true }); + + api.post(new Error('e1')); + api.post(new Error('e2'), { hidden: true }); + api.post(new Error('e3')); + + expect(api.getErrors()).toEqual([ + { + error: new Error('e1'), + }, + { + error: new Error('e2'), + context: { hidden: true }, + }, + { + error: new Error('e3'), + }, + ]); + }); + + it('should not emit values', async () => { + const api = new MockErrorApi({ collect: true }); + + const promise = new Promise((resolve, reject) => { + api.error$().subscribe({ + next({ error }) { + reject(error); + }, + error(error) { + reject(error); + }, + complete() { + reject(new Error('observable was completed')); + }, + }); + + setTimeout(() => resolve('timed-out'), 100); + }); + + await expect(promise).resolves.toBe('timed-out'); + }); + + it('should wait for errors', async () => { + const api = new MockErrorApi({ collect: true }); + + const wait1 = api.waitForError(/1/); + const wait2 = api.waitForError(/2/); + + await expect(ifResolved(wait1)).resolves.toBe('not-yet'); + await expect(ifResolved(wait2)).resolves.toBe('not-yet'); + api.post(new Error('e0')); + await expect(ifResolved(wait1)).resolves.toBe('not-yet'); + await expect(ifResolved(wait2)).resolves.toBe('not-yet'); + api.post(new Error('e1')); + await expect(ifResolved(wait1)).resolves.toEqual({ + error: new Error('e1'), + }); + await expect(ifResolved(wait2)).resolves.toBe('not-yet'); + api.post(new Error('e2'), { hidden: true }); + await expect(ifResolved(wait2)).resolves.toEqual({ + error: new Error('e2'), + context: { hidden: true }, + }); + }); + + it('should time out waiting for error', async () => { + const api = new MockErrorApi({ collect: true }); + + await expect(api.waitForError(/1/, 1)).rejects.toThrow( + 'Timed out waiting for error', + ); + }); +}); diff --git a/packages/frontend-test-utils/src/apis/ErrorApi/MockErrorApi.ts b/packages/frontend-test-utils/src/apis/ErrorApi/MockErrorApi.ts new file mode 100644 index 0000000000..0b627d59e4 --- /dev/null +++ b/packages/frontend-test-utils/src/apis/ErrorApi/MockErrorApi.ts @@ -0,0 +1,117 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + ErrorApi, + ErrorApiError, + ErrorApiErrorContext, +} from '@backstage/core-plugin-api'; +import { Observable } from '@backstage/types'; + +/** + * Constructor arguments for {@link MockErrorApi} + * @public + * @deprecated Use `mockApis.error()` instead. + */ +export type MockErrorApiOptions = { + collect?: boolean; +}; + +/** + * ErrorWithContext contains error and ErrorApiErrorContext + * @public + * @deprecated Use the return type of `MockErrorApi.getErrors` instead. + */ +export type ErrorWithContext = { + error: ErrorApiError; + context?: ErrorApiErrorContext; +}; + +type Waiter = { + pattern: RegExp; + resolve: (err: { + error: ErrorApiError; + context?: ErrorApiErrorContext; + }) => void; +}; + +const nullObservable = { + subscribe: () => ({ unsubscribe: () => {}, closed: true }), + + [Symbol.observable]() { + return this; + }, +}; + +/** + * Mock implementation of the {@link core-plugin-api#ErrorApi} to be used in tests. + * Includes withForError and getErrors methods for error testing. + * @public + * @deprecated Use `mockApis.error()` instead. + */ +export class MockErrorApi implements ErrorApi { + private readonly errors = new Array<{ + error: ErrorApiError; + context?: ErrorApiErrorContext; + }>(); + private readonly waiters = new Set(); + + constructor(private readonly options: { collect?: boolean } = {}) {} + + post(error: ErrorApiError, context?: ErrorApiErrorContext) { + if (this.options.collect) { + this.errors.push({ error, context }); + + for (const waiter of this.waiters) { + if (waiter.pattern.test(error.message)) { + this.waiters.delete(waiter); + waiter.resolve({ error, context }); + } + } + + return; + } + + throw new Error(`MockErrorApi received unexpected error, ${error}`); + } + + error$(): Observable<{ + error: ErrorApiError; + context?: ErrorApiErrorContext; + }> { + return nullObservable; + } + + getErrors(): { error: ErrorApiError; context?: ErrorApiErrorContext }[] { + return this.errors; + } + + waitForError( + pattern: RegExp, + timeoutMs: number = 2000, + ): Promise<{ error: ErrorApiError; context?: ErrorApiErrorContext }> { + return new Promise<{ + error: ErrorApiError; + context?: ErrorApiErrorContext; + }>((resolve, reject) => { + setTimeout(() => { + reject(new Error('Timed out waiting for error')); + }, timeoutMs); + + this.waiters.add({ resolve, pattern }); + }); + } +} diff --git a/plugins/kubernetes/src/utils.ts b/packages/frontend-test-utils/src/apis/ErrorApi/index.ts similarity index 80% rename from plugins/kubernetes/src/utils.ts rename to packages/frontend-test-utils/src/apis/ErrorApi/index.ts index 7396ba1a49..1273a91531 100644 --- a/plugins/kubernetes/src/utils.ts +++ b/packages/frontend-test-utils/src/apis/ErrorApi/index.ts @@ -14,7 +14,5 @@ * limitations under the License. */ -// Display the value unknown rather than 'undefined' -export function orUnknown(val: T | undefined): T | string { - return val ?? 'unknown'; -} +export { MockErrorApi } from './MockErrorApi'; +export type { MockErrorApiOptions, ErrorWithContext } from './MockErrorApi'; diff --git a/packages/frontend-test-utils/src/apis/FeatureFlagsApi/MockFeatureFlagsApi.test.ts b/packages/frontend-test-utils/src/apis/FeatureFlagsApi/MockFeatureFlagsApi.test.ts new file mode 100644 index 0000000000..0798fb48fd --- /dev/null +++ b/packages/frontend-test-utils/src/apis/FeatureFlagsApi/MockFeatureFlagsApi.test.ts @@ -0,0 +1,143 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FeatureFlagState } from '@backstage/frontend-plugin-api'; +import { MockFeatureFlagsApi } from './MockFeatureFlagsApi'; + +describe('MockFeatureFlagsApi', () => { + it('should register flags', () => { + const api = new MockFeatureFlagsApi(); + + api.registerFlag({ + name: 'test-flag-1', + pluginId: 'test-plugin', + description: 'Test flag 1', + }); + + api.registerFlag({ + name: 'test-flag-2', + pluginId: 'test-plugin', + description: 'Test flag 2', + }); + + expect(api.getRegisteredFlags()).toHaveLength(2); + expect(api.getRegisteredFlags()[0].name).toBe('test-flag-1'); + }); + + it('should not register duplicate flags', () => { + const api = new MockFeatureFlagsApi(); + + api.registerFlag({ + name: 'test-flag', + pluginId: 'test-plugin', + description: 'Test flag', + }); + + api.registerFlag({ + name: 'test-flag', + pluginId: 'test-plugin', + description: 'Test flag duplicate', + }); + + expect(api.getRegisteredFlags()).toHaveLength(1); + }); + + it('should handle feature flag states', () => { + const api = new MockFeatureFlagsApi(); + + expect(api.isActive('test-flag')).toBe(false); + + api.save({ states: { 'test-flag': FeatureFlagState.Active } }); + expect(api.isActive('test-flag')).toBe(true); + + api.save({ states: { 'test-flag': FeatureFlagState.None } }); + expect(api.isActive('test-flag')).toBe(false); + }); + + it('should initialize with states', () => { + const api = new MockFeatureFlagsApi({ + initialStates: { + 'flag-1': FeatureFlagState.Active, + 'flag-2': FeatureFlagState.None, + }, + }); + + expect(api.isActive('flag-1')).toBe(true); + expect(api.isActive('flag-2')).toBe(false); + }); + + it('should save and replace states', () => { + const api = new MockFeatureFlagsApi({ + initialStates: { 'flag-1': FeatureFlagState.Active }, + }); + + expect(api.isActive('flag-1')).toBe(true); + + api.save({ + states: { + 'flag-2': FeatureFlagState.Active, + }, + }); + + expect(api.isActive('flag-1')).toBe(false); + expect(api.isActive('flag-2')).toBe(true); + }); + + it('should save and merge states', () => { + const api = new MockFeatureFlagsApi({ + initialStates: { 'flag-1': FeatureFlagState.Active }, + }); + + expect(api.isActive('flag-1')).toBe(true); + + api.save({ + states: { + 'flag-2': FeatureFlagState.Active, + }, + merge: true, + }); + + expect(api.isActive('flag-1')).toBe(true); + expect(api.isActive('flag-2')).toBe(true); + }); + + it('should get and set state', () => { + const api = new MockFeatureFlagsApi(); + + api.setState({ + 'flag-1': FeatureFlagState.Active, + 'flag-2': FeatureFlagState.None, + }); + + const state = api.getState(); + expect(state).toEqual({ + 'flag-1': FeatureFlagState.Active, + 'flag-2': FeatureFlagState.None, + }); + }); + + it('should clear state', () => { + const api = new MockFeatureFlagsApi({ + initialStates: { 'flag-1': FeatureFlagState.Active }, + }); + + expect(api.isActive('flag-1')).toBe(true); + + api.clearState(); + expect(api.isActive('flag-1')).toBe(false); + expect(api.getState()).toEqual({}); + }); +}); diff --git a/packages/frontend-test-utils/src/apis/FeatureFlagsApi/MockFeatureFlagsApi.ts b/packages/frontend-test-utils/src/apis/FeatureFlagsApi/MockFeatureFlagsApi.ts new file mode 100644 index 0000000000..591f6a2e95 --- /dev/null +++ b/packages/frontend-test-utils/src/apis/FeatureFlagsApi/MockFeatureFlagsApi.ts @@ -0,0 +1,107 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + FeatureFlag, + FeatureFlagsApi, + FeatureFlagsSaveOptions, + FeatureFlagState, +} from '@backstage/frontend-plugin-api'; + +/** + * Options for configuring {@link MockFeatureFlagsApi}. + * + * @public + * @deprecated Use `mockApis.featureFlags()` instead. + */ +export interface MockFeatureFlagsApiOptions { + /** + * Initial feature flag states. + */ + initialStates?: Record; +} + +/** + * Mock implementation of {@link @backstage/frontend-plugin-api#FeatureFlagsApi} for testing feature flag behavior. + * + * @public + * @deprecated Use `mockApis.featureFlags()` instead. + * @example + * ```ts + * const api = mockApis.featureFlags({ + * initialStates: { 'my-feature': FeatureFlagState.Active }, + * }); + * expect(api.isActive('my-feature')).toBe(true); + * ``` + */ +export class MockFeatureFlagsApi implements FeatureFlagsApi { + private readonly registeredFlags: FeatureFlag[] = []; + private readonly states: Map; + + constructor(options?: MockFeatureFlagsApiOptions) { + this.states = new Map(Object.entries(options?.initialStates ?? {})); + } + + registerFlag(flag: FeatureFlag): void { + if (!this.registeredFlags.some(f => f.name === flag.name)) { + this.registeredFlags.push(flag); + } + } + + getRegisteredFlags(): FeatureFlag[] { + return this.registeredFlags; + } + + isActive(name: string): boolean { + return this.states.get(name) === FeatureFlagState.Active; + } + + save(options: FeatureFlagsSaveOptions): void { + if (options.merge) { + for (const [name, state] of Object.entries(options.states)) { + this.states.set(name, state); + } + } else { + this.states.clear(); + for (const [name, state] of Object.entries(options.states)) { + this.states.set(name, state); + } + } + } + + /** + * Get the current state of all feature flags as a record. + */ + getState(): Record { + return Object.fromEntries(this.states); + } + + /** + * Set the state of multiple feature flags. + */ + setState(states: Record): void { + for (const [name, state] of Object.entries(states)) { + this.states.set(name, state); + } + } + + /** + * Clear all feature flag states. + */ + clearState(): void { + this.states.clear(); + } +} diff --git a/packages/frontend-test-utils/src/apis/FeatureFlagsApi/index.ts b/packages/frontend-test-utils/src/apis/FeatureFlagsApi/index.ts new file mode 100644 index 0000000000..370e9f3b88 --- /dev/null +++ b/packages/frontend-test-utils/src/apis/FeatureFlagsApi/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { MockFeatureFlagsApi } from './MockFeatureFlagsApi'; +export type { MockFeatureFlagsApiOptions } from './MockFeatureFlagsApi'; diff --git a/packages/frontend-test-utils/src/apis/FetchApi/MockFetchApi.test.ts b/packages/frontend-test-utils/src/apis/FetchApi/MockFetchApi.test.ts new file mode 100644 index 0000000000..ae4486d38f --- /dev/null +++ b/packages/frontend-test-utils/src/apis/FetchApi/MockFetchApi.test.ts @@ -0,0 +1,95 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { http, HttpResponse } from 'msw'; +import { setupServer } from 'msw/node'; +import { registerMswTestHooks } from '../../..'; +import { MockFetchApi } from './MockFetchApi'; + +describe('MockFetchApi', () => { + const worker = setupServer(); + registerMswTestHooks(worker); + + it('works with default constructor', async () => { + worker.use( + http.get('http://example.com/data.json', () => + HttpResponse.json({ a: 'foo' }, { status: 200 }), + ), + ); + const m = new MockFetchApi(); + const response = await m.fetch('http://example.com/data.json'); + await expect(response.json()).resolves.toEqual({ a: 'foo' }); + }); + + describe('baseImplementation', () => { + it('works with a mock implementation', async () => { + const inner = jest.fn(); + const m = new MockFetchApi({ baseImplementation: inner }); + await m.fetch('http://example.com/data.json'); + expect(inner).toHaveBeenLastCalledWith('http://example.com/data.json'); + }); + }); + + describe('resolvePluginProtocol', () => { + it('works', async () => { + const inner = jest.fn(); + const m = new MockFetchApi({ + baseImplementation: inner, + resolvePluginProtocol: { + discoveryApi: { + getBaseUrl: async id => `https://blah.com/api/${id}`, + }, + }, + }); + await m.fetch('plugin://the-plugin/a/data.json'); + expect(inner.mock.calls[0][0]).toBe( + 'https://blah.com/api/the-plugin/a/data.json', + ); + }); + }); + + describe('injectIdentityAuth', () => { + it('works with token', async () => { + const inner = jest.fn(); + const m = new MockFetchApi({ + baseImplementation: inner, + injectIdentityAuth: { token: 'hello' }, + }); + await m.fetch('http://example.com/data.json'); + expect(inner.mock.calls[0][0].headers?.get('authorization')).toBe( + 'Bearer hello', + ); + }); + + it('works with identityApi', async () => { + const inner = jest.fn(); + const m = new MockFetchApi({ + baseImplementation: inner, + injectIdentityAuth: { + identityApi: { + async getCredentials() { + return { token: 'hello2' }; + }, + }, + }, + }); + await m.fetch('http://example.com/data.json'); + expect(inner.mock.calls[0][0].headers?.get('authorization')).toBe( + 'Bearer hello2', + ); + }); + }); +}); diff --git a/packages/frontend-test-utils/src/apis/FetchApi/MockFetchApi.ts b/packages/frontend-test-utils/src/apis/FetchApi/MockFetchApi.ts new file mode 100644 index 0000000000..0ea06924f0 --- /dev/null +++ b/packages/frontend-test-utils/src/apis/FetchApi/MockFetchApi.ts @@ -0,0 +1,168 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + createFetchApi, + FetchMiddleware, + FetchMiddlewares, +} from '@backstage/core-app-api'; +import { + DiscoveryApi, + FetchApi, + IdentityApi, +} from '@backstage/core-plugin-api'; +/** + * The options given when constructing a {@link MockFetchApi}. + * + * @public + */ +export interface MockFetchApiOptions { + /** + * Define the underlying base `fetch` implementation. + * + * @defaultValue undefined + * @remarks + * + * Leaving out this parameter or passing `undefined`, makes the API use the + * global `fetch` implementation to make real network requests. + * + * `'none'` swallows all calls and makes no requests at all. + * + * You can also pass in any `fetch` compatible callback, such as a + * `jest.fn()`, if you want to use a custom implementation or to just track + * and assert on calls. + */ + baseImplementation?: undefined | 'none' | typeof fetch; + + /** + * Add translation from `plugin://` URLs to concrete http(s) URLs, basically + * simulating what + * {@link @backstage/core-app-api#FetchMiddlewares.resolvePluginProtocol} + * does. + * + * @defaultValue undefined + * @remarks + * + * Leaving out this parameter or passing `undefined`, disables plugin protocol + * translation. + * + * To enable the feature, pass in a discovery API which is then used to + * resolve the URLs. + */ + resolvePluginProtocol?: + | undefined + | { discoveryApi: Pick }; + + /** + * Add token based Authorization headers to requests, basically simulating + * what {@link @backstage/core-app-api#FetchMiddlewares.injectIdentityAuth} + * does. + * + * @defaultValue undefined + * @remarks + * + * Leaving out this parameter or passing `undefined`, disables auth injection. + * + * To enable the feature, pass in either a static token or an identity API + * which is queried on each request for a token. + */ + injectIdentityAuth?: + | undefined + | { token: string } + | { identityApi: Pick }; +} + +/** + * A test helper implementation of {@link @backstage/core-plugin-api#FetchApi}. + * + * @public + * @deprecated Use `mockApis.fetch()` instead. + */ +export class MockFetchApi implements FetchApi { + private readonly implementation: FetchApi; + + /** + * Creates a mock {@link @backstage/core-plugin-api#FetchApi}. + */ + constructor(options?: MockFetchApiOptions) { + this.implementation = build(options); + } + + /** {@inheritdoc @backstage/core-plugin-api#FetchApi.fetch} */ + get fetch(): typeof fetch { + return this.implementation.fetch; + } +} + +// +// Helpers +// + +function build(options?: MockFetchApiOptions): FetchApi { + return createFetchApi({ + baseImplementation: baseImplementation(options), + middleware: [ + resolvePluginProtocol(options), + injectIdentityAuth(options), + ].filter((x): x is FetchMiddleware => Boolean(x)), + }); +} + +function baseImplementation( + options: MockFetchApiOptions | undefined, +): typeof fetch { + const implementation = options?.baseImplementation; + if (!implementation) { + // Return a wrapper that evaluates global.fetch at call time, not construction time. + // This allows MSW to patch global.fetch after MockFetchApi is constructed. + return (input, init) => global.fetch(input, init); + } else if (implementation === 'none') { + return () => Promise.resolve(new Response()); + } + return implementation; +} + +function resolvePluginProtocol( + allOptions: MockFetchApiOptions | undefined, +): FetchMiddleware | undefined { + const options = allOptions?.resolvePluginProtocol; + if (!options) { + return undefined; + } + + return FetchMiddlewares.resolvePluginProtocol({ + discoveryApi: options.discoveryApi, + }); +} + +function injectIdentityAuth( + allOptions: MockFetchApiOptions | undefined, +): FetchMiddleware | undefined { + const options = allOptions?.injectIdentityAuth; + if (!options) { + return undefined; + } + + const identityApi: Pick = + 'token' in options + ? { getCredentials: async () => ({ token: options.token }) } + : options.identityApi; + + return FetchMiddlewares.injectIdentityAuth({ + identityApi: identityApi as IdentityApi, + allowUrl: () => true, + }); +} diff --git a/packages/frontend-test-utils/src/apis/FetchApi/index.ts b/packages/frontend-test-utils/src/apis/FetchApi/index.ts new file mode 100644 index 0000000000..5ac0c08ff1 --- /dev/null +++ b/packages/frontend-test-utils/src/apis/FetchApi/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { MockFetchApi } from './MockFetchApi'; +export type { MockFetchApiOptions } from './MockFetchApi'; diff --git a/packages/frontend-test-utils/src/apis/MockWithApiFactory.ts b/packages/frontend-test-utils/src/apis/MockWithApiFactory.ts new file mode 100644 index 0000000000..28ce01e314 --- /dev/null +++ b/packages/frontend-test-utils/src/apis/MockWithApiFactory.ts @@ -0,0 +1,94 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ApiRef } from '@backstage/frontend-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; + +/** + * Symbol used to mark mock API instances with their corresponding API factory. + * + * @ignore + */ +export const mockApiFactorySymbol = Symbol.for('@backstage/mock-api'); + +/** + * Symbol used to mark mock API instances with their corresponding API factory. + * This allows mock APIs to be passed directly to test utilities without + * needing to explicitly provide the [apiRef, implementation] tuple. + * + * @public + */ +export type MockApiFactorySymbol = typeof mockApiFactorySymbol; + +/** + * Type for an API instance that has been marked as a mock API. + * + * @public + */ +export type MockWithApiFactory = TApi & { + [mockApiFactorySymbol]: ApiFactory; +}; + +/** + * Attaches mock API factory metadata to an API instance, allowing it to be + * passed directly to test utilities without needing to explicitly provide + * the [apiRef, implementation] tuple. + * + * @public + * @example + * ```ts + * const catalogApi = attachMockApiFactory( + * catalogApiRef, + * new InMemoryCatalogClient() + * ); + * // Can now be passed directly to TestApiProvider + * + * ``` + */ +export function attachMockApiFactory( + apiRef: ApiRef, + implementation: TImpl, +): TImpl & { [mockApiFactorySymbol]: ApiFactory } { + const marked = implementation as TImpl & { + [mockApiFactorySymbol]: ApiFactory; + }; + (marked as any)[mockApiFactorySymbol] = { + api: apiRef, + deps: {}, + factory: () => implementation, + }; + return marked; +} + +/** + * Retrieves the API factory from a mock API instance. + * Returns undefined if the value is not a mock API instance. + * + * @internal + */ +export function getMockApiFactory( + value: unknown, +): ApiFactory | undefined { + if ( + typeof value === 'object' && + value !== null && + mockApiFactorySymbol in value && + typeof (value as any)[mockApiFactorySymbol] === 'object' + ) { + return (value as any)[mockApiFactorySymbol]; + } + return undefined; +} diff --git a/packages/frontend-test-utils/src/apis/PermissionApi/MockPermissionApi.test.ts b/packages/frontend-test-utils/src/apis/PermissionApi/MockPermissionApi.test.ts new file mode 100644 index 0000000000..b617d89cc4 --- /dev/null +++ b/packages/frontend-test-utils/src/apis/PermissionApi/MockPermissionApi.test.ts @@ -0,0 +1,47 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + AuthorizeResult, + createPermission, +} from '@backstage/plugin-permission-common'; +import { MockPermissionApi } from './MockPermissionApi'; + +describe('MockPermissionApi', () => { + it('returns ALLOW by default', async () => { + const api = new MockPermissionApi(); + + await expect( + api.authorize({ + permission: createPermission({ name: 'permission.1', attributes: {} }), + }), + ).resolves.toEqual({ result: AuthorizeResult.ALLOW }); + }); + + it('allows passing a handler to customize the result', async () => { + const api = new MockPermissionApi(request => + request.permission.name === 'permission.2' + ? AuthorizeResult.DENY + : AuthorizeResult.ALLOW, + ); + + await expect( + api.authorize({ + permission: createPermission({ name: 'permission.2', attributes: {} }), + }), + ).resolves.toEqual({ result: AuthorizeResult.DENY }); + }); +}); diff --git a/packages/frontend-test-utils/src/apis/PermissionApi/MockPermissionApi.ts b/packages/frontend-test-utils/src/apis/PermissionApi/MockPermissionApi.ts new file mode 100644 index 0000000000..52f8fa4bb6 --- /dev/null +++ b/packages/frontend-test-utils/src/apis/PermissionApi/MockPermissionApi.ts @@ -0,0 +1,46 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { PermissionApi } from '@backstage/plugin-permission-react'; +import { + EvaluatePermissionResponse, + EvaluatePermissionRequest, + AuthorizeResult, +} from '@backstage/plugin-permission-common'; + +/** + * Mock implementation of + * {@link @backstage/plugin-permission-react#PermissionApi}. Supply a + * requestHandler function to override the mock result returned for a given + * request. + * + * @public + * @deprecated Use `mockApis.permission()` instead. + */ +export class MockPermissionApi implements PermissionApi { + constructor( + private readonly requestHandler: ( + request: EvaluatePermissionRequest, + ) => AuthorizeResult.ALLOW | AuthorizeResult.DENY = () => + AuthorizeResult.ALLOW, + ) {} + + async authorize( + request: EvaluatePermissionRequest, + ): Promise { + return { result: this.requestHandler(request) }; + } +} diff --git a/packages/frontend-test-utils/src/apis/PermissionApi/index.ts b/packages/frontend-test-utils/src/apis/PermissionApi/index.ts new file mode 100644 index 0000000000..f69316b811 --- /dev/null +++ b/packages/frontend-test-utils/src/apis/PermissionApi/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { MockPermissionApi } from './MockPermissionApi'; diff --git a/packages/frontend-test-utils/src/apis/StorageApi/MockStorageApi.test.ts b/packages/frontend-test-utils/src/apis/StorageApi/MockStorageApi.test.ts new file mode 100644 index 0000000000..34908f561a --- /dev/null +++ b/packages/frontend-test-utils/src/apis/StorageApi/MockStorageApi.test.ts @@ -0,0 +1,280 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { StorageApi } from '@backstage/core-plugin-api'; +import { MockStorageApi } from './MockStorageApi'; + +describe('WebStorage Storage API', () => { + const createMockStorage = (): StorageApi => { + return MockStorageApi.create(); + }; + + it('should return undefined for values which are unset', async () => { + const storage = createMockStorage(); + + expect(storage.snapshot('myfakekey').value).toBeUndefined(); + expect(storage.snapshot('myfakekey')).toEqual({ + key: 'myfakekey', + presence: 'absent', + value: undefined, + }); + }); + + it('should allow the setting and snapshotting of the simple data structures', async () => { + const storage = createMockStorage(); + + await storage.set('myfakekey', 'helloimastring'); + await storage.set('mysecondfakekey', 1234); + await storage.set('mythirdfakekey', true); + expect(storage.snapshot('myfakekey').value).toBe('helloimastring'); + expect(storage.snapshot('mysecondfakekey').value).toBe(1234); + expect(storage.snapshot('mythirdfakekey').value).toBe(true); + expect(storage.snapshot('myfakekey')).toEqual({ + key: 'myfakekey', + presence: 'present', + value: 'helloimastring', + }); + expect(storage.snapshot('mysecondfakekey')).toEqual({ + key: 'mysecondfakekey', + presence: 'present', + value: 1234, + }); + expect(storage.snapshot('mythirdfakekey')).toEqual({ + key: 'mythirdfakekey', + presence: 'present', + value: true, + }); + }); + + it('should allow setting of complex datastructures', async () => { + const storage = createMockStorage(); + + const mockData = { + something: 'here', + is: [{ super: { complex: [{ but: 'something', why: true }] } }], + }; + + await storage.set('myfakekey', mockData); + + expect(storage.snapshot('myfakekey').value).toEqual(mockData); + expect(storage.snapshot('myfakekey')).toEqual({ + key: 'myfakekey', + presence: 'present', + value: mockData, + }); + }); + + it('should subscribe to key changes when setting a new value', async () => { + const storage = createMockStorage(); + + const wrongKeyNextHandler = jest.fn(); + const selectedKeyNextHandler = jest.fn(); + const mockData = { hello: 'im a great new value' }; + + await new Promise(resolve => { + storage.observe$('correctKey').subscribe({ + next: (...args) => { + selectedKeyNextHandler(...args); + resolve(); + }, + }); + + storage.observe$('wrongKey').subscribe({ next: wrongKeyNextHandler }); + + storage.set('correctKey', mockData); + }); + + expect(wrongKeyNextHandler).not.toHaveBeenCalled(); + expect(selectedKeyNextHandler).toHaveBeenCalledTimes(1); + expect(selectedKeyNextHandler).toHaveBeenCalledWith({ + key: 'correctKey', + presence: 'present', + value: mockData, + }); + }); + + it('should subscribe to key changes when deleting a value', async () => { + const storage = createMockStorage(); + + const wrongKeyNextHandler = jest.fn(); + const selectedKeyNextHandler = jest.fn(); + const mockData = { hello: 'im a great new value' }; + + storage.set('correctKey', mockData); + + await new Promise(resolve => { + storage.observe$('correctKey').subscribe({ + next: (...args) => { + selectedKeyNextHandler(...args); + resolve(); + }, + }); + + storage.observe$('wrongKey').subscribe({ next: wrongKeyNextHandler }); + + storage.remove('correctKey'); + }); + + expect(wrongKeyNextHandler).not.toHaveBeenCalled(); + expect(selectedKeyNextHandler).toHaveBeenCalledTimes(1); + expect(selectedKeyNextHandler).toHaveBeenCalledWith({ + key: 'correctKey', + presence: 'absent', + value: undefined, + }); + }); + + it('should be able to create different buckets for different uses', async () => { + const rootStorage = createMockStorage(); + + const firstStorage = rootStorage.forBucket('userSettings'); + const secondStorage = rootStorage.forBucket('profileSettings'); + const keyName = 'blobby'; + + await firstStorage.set(keyName, 'boop'); + await secondStorage.set(keyName, 'deerp'); + + expect(firstStorage.snapshot(keyName)).not.toBe( + secondStorage.snapshot(keyName), + ); + expect(firstStorage.snapshot(keyName).value).toBe('boop'); + expect(secondStorage.snapshot(keyName).value).toBe('deerp'); + expect(firstStorage.snapshot(keyName)).not.toEqual( + secondStorage.snapshot(keyName), + ); + expect(firstStorage.snapshot(keyName)).toEqual({ + key: keyName, + presence: 'present', + value: 'boop', + }); + expect(secondStorage.snapshot(keyName)).toEqual({ + key: keyName, + presence: 'present', + value: 'deerp', + }); + }); + + it('should not clash with other namespaces when creating buckets', async () => { + const rootStorage = createMockStorage(); + + // when getting key test2 it will translate to /profile/something/deep/test2 + const firstStorage = rootStorage + .forBucket('profile') + .forBucket('something') + .forBucket('deep'); + // when getting key deep/test2 it will translate to /profile/something/deep/test2 + const secondStorage = rootStorage.forBucket('profile/something'); + + await firstStorage.set('test2', { error: true }); + + expect(secondStorage.snapshot('deep/test2').value).toBe(undefined); + expect(secondStorage.snapshot('deep/test2')).toMatchObject({ + presence: 'absent', + }); + }); + + it('should not reuse storage instances between different rootStorages', async () => { + const rootStorage1 = createMockStorage(); + const rootStorage2 = createMockStorage(); + + const firstStorage = rootStorage1.forBucket('something'); + const secondStorage = rootStorage2.forBucket('something'); + + await firstStorage.set('test2', true); + + expect(firstStorage.snapshot('test2').value).toBe(true); + expect(secondStorage.snapshot('test2').value).toBe(undefined); + expect(firstStorage.snapshot('test2')).toEqual({ + key: 'test2', + presence: 'present', + value: true, + }); + expect(secondStorage.snapshot('test2')).toEqual({ + key: 'test2', + presence: 'absent', + value: undefined, + }); + }); + + it('should freeze the snapshot value', async () => { + const storage = createMockStorage(); + + const data = { foo: 'bar', baz: [{ foo: 'bar' }] }; + storage.set('foo', data); + + const snapshot = storage.snapshot('foo'); + expect(snapshot.value).not.toBe(data); + + if (snapshot.presence !== 'present') { + throw new Error('Invalid presence'); + } + + expect(() => { + snapshot.value.foo = 'buzz'; + }).toThrow(/Cannot assign to read only property/); + expect(() => { + snapshot.value.baz[0].foo = 'buzz'; + }).toThrow(/Cannot assign to read only property/); + expect(() => { + snapshot.value.baz.push({ foo: 'buzz' }); + }).toThrow(/Cannot add property 1, object is not extensible/); + }); + + it('should freeze observed values', async () => { + const storage = createMockStorage(); + + const snapshotPromise = new Promise(resolve => { + storage.observe$('test').subscribe({ + next: resolve, + }); + }); + + storage.set('test', { + foo: { + bar: 'baz', + }, + }); + + const snapshot = await snapshotPromise; + expect(snapshot.presence).toBe('present'); + expect(() => { + snapshot.value!.foo.bar = 'qux'; + }).toThrow(/Cannot assign to read only property 'bar' of object/); + }); + + it('should JSON serialize stored values', async () => { + const storage = createMockStorage(); + + storage.set('test', { + foo: { + toJSON() { + return { + bar: 'baz', + }; + }, + }, + }); + + expect(storage.snapshot('test')).toMatchObject({ + presence: 'present', + value: { + foo: { + bar: 'baz', + }, + }, + }); + }); +}); diff --git a/packages/frontend-test-utils/src/apis/StorageApi/MockStorageApi.ts b/packages/frontend-test-utils/src/apis/StorageApi/MockStorageApi.ts new file mode 100644 index 0000000000..f7ad0fe39a --- /dev/null +++ b/packages/frontend-test-utils/src/apis/StorageApi/MockStorageApi.ts @@ -0,0 +1,142 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { StorageApi, StorageValueSnapshot } from '@backstage/core-plugin-api'; +import { JsonObject, JsonValue, Observable } from '@backstage/types'; +import ObservableImpl from 'zen-observable'; + +/** + * Mock implementation of the {@link core-plugin-api#StorageApi} to be used in tests. + * + * @public + * @deprecated Use `mockApis.storage()` instead. + */ +export class MockStorageApi implements StorageApi { + private readonly namespace: string; + private readonly data: JsonObject; + private readonly bucketStorageApis: Map; + + private constructor( + namespace: string, + bucketStorageApis: Map, + data?: JsonObject, + ) { + this.namespace = namespace; + this.bucketStorageApis = bucketStorageApis; + this.data = { ...data }; + } + + static create(data?: JsonObject) { + // Translate a nested data object structure into a flat object with keys + // like `/a/b` with their corresponding leaf values + const keyValues: { [key: string]: any } = {}; + function put(value: { [key: string]: any }, namespace: string) { + for (const [key, val] of Object.entries(value)) { + if (typeof val === 'object' && val !== null) { + put(val, `${namespace}/${key}`); + } else { + const namespacedKey = `${namespace}/${key.replace(/^\//, '')}`; + keyValues[namespacedKey] = val; + } + } + } + put(data ?? {}, ''); + return new MockStorageApi('', new Map(), keyValues); + } + + forBucket(name: string): StorageApi { + if (!this.bucketStorageApis.has(name)) { + this.bucketStorageApis.set( + name, + new MockStorageApi( + `${this.namespace}/${name}`, + this.bucketStorageApis, + this.data, + ), + ); + } + return this.bucketStorageApis.get(name)!; + } + + snapshot(key: string): StorageValueSnapshot { + if (this.data.hasOwnProperty(this.getKeyName(key))) { + const data = this.data[this.getKeyName(key)]; + return { + key, + presence: 'present', + value: data as T, + }; + } + return { + key, + presence: 'absent', + value: undefined, + }; + } + + async set(key: string, data: T): Promise { + const serialized = JSON.parse(JSON.stringify(data), (_key, value) => { + if (typeof value === 'object' && value !== null) { + Object.freeze(value); + } + return value; + }); + this.data[this.getKeyName(key)] = serialized; + this.notifyChanges({ + key, + presence: 'present', + value: serialized, + }); + } + + async remove(key: string): Promise { + delete this.data[this.getKeyName(key)]; + this.notifyChanges({ + key, + presence: 'absent', + value: undefined, + }); + } + + observe$( + key: string, + ): Observable> { + return this.observable.filter(({ key: messageKey }) => messageKey === key); + } + + private getKeyName(key: string) { + return `${this.namespace}/${encodeURIComponent(key)}`; + } + + private notifyChanges(message: StorageValueSnapshot) { + for (const subscription of this.subscribers) { + subscription.next(message); + } + } + + private subscribers = new Set< + ZenObservable.SubscriptionObserver> + >(); + + private readonly observable = new ObservableImpl< + StorageValueSnapshot + >(subscriber => { + this.subscribers.add(subscriber); + return () => { + this.subscribers.delete(subscriber); + }; + }); +} diff --git a/packages/frontend-test-utils/src/apis/StorageApi/index.ts b/packages/frontend-test-utils/src/apis/StorageApi/index.ts new file mode 100644 index 0000000000..6e2ebc6918 --- /dev/null +++ b/packages/frontend-test-utils/src/apis/StorageApi/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { MockStorageApi } from './MockStorageApi'; diff --git a/packages/frontend-test-utils/src/apis/TestApiProvider.test.tsx b/packages/frontend-test-utils/src/apis/TestApiProvider.test.tsx new file mode 100644 index 0000000000..5d27e3a228 --- /dev/null +++ b/packages/frontend-test-utils/src/apis/TestApiProvider.test.tsx @@ -0,0 +1,107 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createApiRef } from '@backstage/frontend-plugin-api'; +import { TestApiProvider } from './TestApiProvider'; +import { mockApis } from './mockApis'; +import { render, screen } from '@testing-library/react'; + +const xApiRef = createApiRef<{ a: string; b: number }>({ + id: 'x', +}); +const yApiRef = createApiRef({ + id: 'y', +}); + +describe('TestApiProvider', () => { + it('should provide tuple APIs and check types', () => { + render( + +
    + , + ); + + expect(document.body).toBeInTheDocument(); + }); + + it('should allow partial API implementations', () => { + render( + +
    + , + ); + + expect(document.body).toBeInTheDocument(); + }); + + it('should reject mismatched types in tuple syntax', () => { + render( + // @ts-expect-error - a should be a string, not a number + +
    + , + ); + + expect(document.body).toBeInTheDocument(); + }); + + it('should accept MockWithApiFactory entries', () => { + render( + +
    + , + ); + + expect(document.body).toBeInTheDocument(); + }); + + it('should accept a mix of tuples and MockWithApiFactory entries', () => { + render( + +
    + , + ); + + expect(document.body).toBeInTheDocument(); + }); + + it('should allow empty APIs', () => { + render( + +
    + , + ); + + expect(document.body).toBeInTheDocument(); + }); + + it('should provide APIs at runtime', async () => { + const alertApi = mockApis.alert(); + + render( + + rendered + , + ); + + expect(await screen.findByText('rendered')).toBeInTheDocument(); + }); +}); diff --git a/packages/frontend-test-utils/src/apis/TestApiProvider.tsx b/packages/frontend-test-utils/src/apis/TestApiProvider.tsx new file mode 100644 index 0000000000..d0eca87a33 --- /dev/null +++ b/packages/frontend-test-utils/src/apis/TestApiProvider.tsx @@ -0,0 +1,127 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ReactNode } from 'react'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { ApiProvider } from '../../../core-app-api/src/apis/system'; +import { ApiHolder, ApiRef } from '@backstage/frontend-plugin-api'; +import { + getMockApiFactory, + type MockWithApiFactory, +} from './MockWithApiFactory'; + +/** + * Represents a single API implementation, either as a tuple of the reference and the implementation, or a mock with an embedded factory. + * @public + */ +export type TestApiPair = + | readonly [ApiRef, TApi extends infer TImpl ? Partial : never] + | MockWithApiFactory>; + +/** + * Represents an array of mock API implementation. + * @public + */ +export type TestApiPairs = { + [TIndex in keyof TApiPairs]: TestApiPair; +}; + +/** @internal */ +export function resolveTestApiEntries( + apis: readonly [...TestApiPairs], +): ApiHolder { + const apiMap = new Map(); + + for (const entry of apis) { + const mockFactory = getMockApiFactory(entry); + if (mockFactory) { + apiMap.set(mockFactory.api.id, mockFactory.factory({})); + } else { + const [apiRef, impl] = entry as readonly [ApiRef, any]; + apiMap.set(apiRef.id, impl); + } + } + + return { + get: (ref: ApiRef) => apiMap.get(ref.id) as T | undefined, + }; +} + +/** + * Properties for the {@link TestApiProvider} component. + * + * @public + */ +export type TestApiProviderProps = { + apis: readonly [...TestApiPairs]; + children: ReactNode; +}; + +/** + * The `TestApiProvider` is a Utility API context provider for standalone rendering + * scenarios where you're not using `renderInTestApp` or other test utilities. + * + * It lets you provide any number of API implementations, without necessarily + * having to fully implement each of the APIs. + * + * @remarks + * + * For most test scenarios, prefer using the `apis` option in `renderInTestApp` or + * `createExtensionTester` instead of wrapping components with `TestApiProvider`. + * + * @example + * ```tsx + * import { render } from '\@testing-library/react'; + * import { myCustomApiRef } from '../apis'; + * import { TestApiProvider, mockApis } from '\@backstage/frontend-test-utils'; + * + * // Mock custom APIs with tuple syntax + * const myCustomApiMock = { myMethod: jest.fn() }; + * render( + * + * + * + * ); + * + * // Use with built-in mock APIs (no tuples needed) + * render( + * + * + * + * ); + * ``` + * + * @public + */ +export function TestApiProvider( + props: TestApiProviderProps, +): JSX.Element { + return ( + + ); +} diff --git a/packages/frontend-test-utils/src/apis/TranslationApi/MockTranslationApi.test.tsx b/packages/frontend-test-utils/src/apis/TranslationApi/MockTranslationApi.test.tsx new file mode 100644 index 0000000000..8c9a15e677 --- /dev/null +++ b/packages/frontend-test-utils/src/apis/TranslationApi/MockTranslationApi.test.tsx @@ -0,0 +1,212 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createTranslationRef } from '@backstage/frontend-plugin-api'; +import { MockTranslationApi } from './MockTranslationApi'; + +describe('MockTranslationApi', () => { + function snapshotWithMessages< + const TMessages extends { [key in string]: string }, + >(messages: TMessages) { + const translationApi = MockTranslationApi.create(); + const ref = createTranslationRef({ + id: 'test', + messages, + }); + const snapshot = translationApi.getTranslation(ref); + if (!snapshot.ready) { + throw new Error('Translation snapshot is not ready'); + } + return snapshot; + } + + it('should format plain messages', () => { + const snapshot = snapshotWithMessages({ + foo: 'Foo', + bar: 'Bar', + baz: 'Baz', + }); + + expect(snapshot.t('foo')).toBe('Foo'); + expect(snapshot.t('bar')).toBe('Bar'); + expect(snapshot.t('baz')).toBe('Baz'); + }); + + it('should support interpolation', () => { + const snapshot = snapshotWithMessages({ + shallow: 'Foo {{ bar }}', + multiple: 'Foo {{ bar }} {{ baz }}', + deep: 'Foo {{ bar.baz }}', + }); + + // @ts-expect-error + expect(snapshot.t('shallow')).toBe('Foo {{ bar }}'); + expect(snapshot.t('shallow', { bar: 'Bar' })).toBe('Foo Bar'); + + // @ts-expect-error + expect(snapshot.t('multiple')).toBe('Foo {{ bar }} {{ baz }}'); + // @ts-expect-error + expect(snapshot.t('multiple', { bar: 'Bar' })).toBe('Foo Bar {{ baz }}'); + expect(snapshot.t('multiple', { bar: 'Bar', baz: 'Baz' })).toBe( + 'Foo Bar Baz', + ); + + // @ts-expect-error + expect(snapshot.t('deep')).toBe('Foo {{ bar.baz }}'); + expect(snapshot.t('deep', { bar: { baz: 'Baz' } })).toBe('Foo Baz'); + }); + + // Escaping isn't as useful in React, since we don't need to escape HTML in strings + it('should not escape by default', () => { + const snapshot = snapshotWithMessages({ + foo: 'Foo {{ foo }}', + }); + + expect(snapshot.t('foo', { foo: '
    ' })).toBe('Foo
    '); + expect( + snapshot.t('foo', { + foo: '
    ', + interpolation: { escapeValue: true }, + }), + ).toBe('Foo <div>'); + }); + + it('should support nesting', () => { + const snapshot = snapshotWithMessages({ + foo: 'Foo $t(bar) $t(baz)', + bar: 'Nested', + baz: 'Baz {{ qux }}', + }); + + expect(snapshot.t('foo', { qux: 'Deep' })).toBe('Foo Nested Baz Deep'); + }); + + it('should support jsx interpolation', () => { + const snapshot = snapshotWithMessages({ + empty: 'derp', + jsx: '={{ x }}', + jsxNested: '={{ x.y.z }}', + jsxDeep: '<$t(jsx)>', + }); + + expect(snapshot.t('jsx', { x:

    hello

    })).toMatchInlineSnapshot(` + + = +

    + hello +

    +
    + `); + expect(snapshot.t('jsx', { replace: { x:

    hello

    } })) + .toMatchInlineSnapshot(` + + = +

    + hello +

    +
    + `); + expect( + snapshot.t('jsxNested', { replace: { x: { y: { z:

    hello

    } } } }), + ).toMatchInlineSnapshot(` + + = +

    + hello +

    +
    + `); + expect(snapshot.t('jsxDeep', { x:

    hello

    })).toMatchInlineSnapshot(` + + <= +

    + hello +

    + > +
    + `); + }); + + it('should support formatting', () => { + const snapshot = snapshotWithMessages({ + plain: '= {{ x }}', + number: '= {{ x, number }}', + numberFixed: '= {{ x, number(minimumFractionDigits: 2) }}', + relativeTime: '= {{ x, relativeTime }}', + relativeSeconds: '= {{ x, relativeTime(second) }}', + relativeSecondsShort: + '= {{ x, relativeTime(range: second; style: short) }}', + list: '= {{ x, list }}', + }); + + expect(snapshot.t('plain', { x: '5' })).toBe('= 5'); + expect(snapshot.t('number', { x: 5 })).toBe('= 5'); + expect( + snapshot.t('number', { + x: 5, + formatParams: { x: { minimumFractionDigits: 1 } }, + }), + ).toBe('= 5.0'); + expect(snapshot.t('numberFixed', { x: 5 })).toBe('= 5.00'); + expect( + snapshot.t('numberFixed', { + x: 5, + formatParams: { x: { minimumFractionDigits: 3 } }, + }), + ).toBe('= 5.000'); + expect(snapshot.t('relativeTime', { x: 3 })).toBe('= in 3 days'); + expect(snapshot.t('relativeTime', { x: -3 })).toBe('= 3 days ago'); + expect( + snapshot.t('relativeTime', { + x: 15, + formatParams: { x: { range: 'weeks' } }, + }), + ).toBe('= in 15 weeks'); + expect( + snapshot.t('relativeTime', { + x: 15, + formatParams: { x: { range: 'weeks', style: 'short' } }, + }), + ).toBe('= in 15 wk.'); + expect(snapshot.t('relativeSeconds', { x: 1 })).toBe('= in 1 second'); + expect(snapshot.t('relativeSeconds', { x: 2 })).toBe('= in 2 seconds'); + expect(snapshot.t('relativeSeconds', { x: -3 })).toBe('= 3 seconds ago'); + expect(snapshot.t('relativeSeconds', { x: 0 })).toBe('= in 0 seconds'); + expect(snapshot.t('relativeSecondsShort', { x: 1 })).toBe('= in 1 sec.'); + expect(snapshot.t('relativeSecondsShort', { x: 2 })).toBe('= in 2 sec.'); + expect(snapshot.t('relativeSecondsShort', { x: -3 })).toBe('= 3 sec. ago'); + expect(snapshot.t('relativeSecondsShort', { x: 0 })).toBe('= in 0 sec.'); + expect(snapshot.t('list', { x: ['a'] })).toBe('= a'); + expect(snapshot.t('list', { x: ['a', 'b'] })).toBe('= a and b'); + expect(snapshot.t('list', { x: ['a', 'b', 'c'] })).toBe('= a, b, and c'); + }); + + it('should support plurals', () => { + const snapshot = snapshotWithMessages({ + derp_one: 'derp', + derp_other: 'derps', + derpWithCount_one: '{{ count }} derp', + derpWithCount_other: '{{ count }} derps', + }); + + expect(snapshot.t('derp', { count: 1 })).toBe('derp'); + expect(snapshot.t('derp', { count: 2 })).toBe('derps'); + expect(snapshot.t('derp', { count: 0 })).toBe('derps'); + expect(snapshot.t('derpWithCount', { count: 1 })).toBe('1 derp'); + expect(snapshot.t('derpWithCount', { count: 2 })).toBe('2 derps'); + expect(snapshot.t('derpWithCount', { count: 0 })).toBe('0 derps'); + }); +}); diff --git a/packages/frontend-test-utils/src/apis/TranslationApi/MockTranslationApi.ts b/packages/frontend-test-utils/src/apis/TranslationApi/MockTranslationApi.ts new file mode 100644 index 0000000000..91708b0528 --- /dev/null +++ b/packages/frontend-test-utils/src/apis/TranslationApi/MockTranslationApi.ts @@ -0,0 +1,111 @@ +/* + * 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 { + TranslationApi, + TranslationRef, + TranslationSnapshot, +} from '@backstage/frontend-plugin-api'; +import { createInstance as createI18n, type i18n as I18n } from 'i18next'; +import ObservableImpl from 'zen-observable'; + +import { Observable } from '@backstage/types'; +// Internal import to avoid code duplication, this will lead to duplication in build output +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { toInternalTranslationRef } from '../../../../frontend-plugin-api/src/translation/TranslationRef'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { JsxInterpolator } from '../../../../core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi'; + +const DEFAULT_LANGUAGE = 'en'; + +/** + * Mock implementation of {@link @backstage/frontend-plugin-api#TranslationApi}. + * + * @public + * @deprecated Use `mockApis.translation()` instead. + */ +export class MockTranslationApi implements TranslationApi { + static create() { + const i18n = createI18n({ + fallbackLng: DEFAULT_LANGUAGE, + supportedLngs: [DEFAULT_LANGUAGE], + interpolation: { + escapeValue: false, + // Used for the JsxInterpolator format hook + alwaysFormat: true, + }, + ns: [], + defaultNS: false, + fallbackNS: false, + + // Disable resource loading on init, meaning i18n will be ready to use immediately + initImmediate: false, + }); + + i18n.init(); + if (!i18n.isInitialized) { + throw new Error('i18next was unexpectedly not initialized'); + } + + const interpolator = JsxInterpolator.fromI18n(i18n); + + return new MockTranslationApi(i18n, interpolator); + } + + readonly #i18n: I18n; + readonly #interpolator: JsxInterpolator; + readonly #registeredRefs = new Set(); + + private constructor(i18n: I18n, interpolator: JsxInterpolator) { + this.#i18n = i18n; + this.#interpolator = interpolator; + } + + getTranslation( + translationRef: TranslationRef, + ): TranslationSnapshot { + const internalRef = toInternalTranslationRef(translationRef); + + if (!this.#registeredRefs.has(internalRef.id)) { + this.#registeredRefs.add(internalRef.id); + this.#i18n.addResourceBundle( + DEFAULT_LANGUAGE, + internalRef.id, + internalRef.getDefaultMessages(), + false, // do not merge + true, // overwrite existing + ); + } + + const t = this.#interpolator.wrapT( + this.#i18n.getFixedT(null, internalRef.id), + ); + + return { + ready: true, + t, + }; + } + + translation$(): Observable< + TranslationSnapshot + > { + // No need to implement, getTranslation will always return a ready snapshot + return new ObservableImpl>(_subscriber => { + return () => {}; + }); + } +} diff --git a/packages/core-plugin-api/src/apis/alpha.ts b/packages/frontend-test-utils/src/apis/TranslationApi/index.ts similarity index 90% rename from packages/core-plugin-api/src/apis/alpha.ts rename to packages/frontend-test-utils/src/apis/TranslationApi/index.ts index b22d3df574..2c10347545 100644 --- a/packages/core-plugin-api/src/apis/alpha.ts +++ b/packages/frontend-test-utils/src/apis/TranslationApi/index.ts @@ -13,4 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './definitions/alpha'; + +export { MockTranslationApi } from './MockTranslationApi'; diff --git a/packages/frontend-test-utils/src/apis/createApiMock.test.ts b/packages/frontend-test-utils/src/apis/createApiMock.test.ts new file mode 100644 index 0000000000..8b03b6df75 --- /dev/null +++ b/packages/frontend-test-utils/src/apis/createApiMock.test.ts @@ -0,0 +1,86 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createApiRef } from '@backstage/frontend-plugin-api'; +import { createApiMock } from './createApiMock'; +import { getMockApiFactory } from './MockWithApiFactory'; + +describe('createApiMock', () => { + type TestApi = { + greet(name: string): string; + count: number; + }; + + const testApiRef = createApiRef({ id: 'test.create-mock' }); + + it('returns a factory function that produces jest mocks', () => { + const mock = createApiMock(testApiRef, () => ({ + greet: jest.fn(), + count: 0 as any, + })); + + const api = mock(); + api.greet('world'); + expect(api.greet).toHaveBeenCalledTimes(1); + expect(api.greet).toHaveBeenCalledWith('world'); + }); + + it('applies partial implementations via mockImplementation', () => { + const mock = createApiMock(testApiRef, () => ({ + greet: jest.fn(), + count: 0 as any, + })); + + const api = mock({ greet: (name: string) => `Hello ${name}!` }); + expect(api.greet('world')).toBe('Hello world!'); + expect(api.greet).toHaveBeenCalledTimes(1); + }); + + it('preserves non-function partial values', () => { + const mock = createApiMock(testApiRef, () => ({ + greet: jest.fn(), + count: 0 as any, + })); + + const api = mock({ count: 42 }); + expect(api.count).toBe(42); + }); + + it('attaches a mock API factory via the symbol', () => { + const mock = createApiMock(testApiRef, () => ({ + greet: jest.fn(), + count: 0 as any, + })); + + const api = mock(); + const factory = getMockApiFactory(api); + expect(factory).toBeDefined(); + expect(factory!.api).toBe(testApiRef); + }); + + it('creates fresh mocks on each call', () => { + const mock = createApiMock(testApiRef, () => ({ + greet: jest.fn(), + count: 0 as any, + })); + + const api1 = mock(); + const api2 = mock(); + api1.greet('a'); + expect(api1.greet).toHaveBeenCalledTimes(1); + expect(api2.greet).toHaveBeenCalledTimes(0); + }); +}); diff --git a/packages/frontend-test-utils/src/apis/createApiMock.ts b/packages/frontend-test-utils/src/apis/createApiMock.ts new file mode 100644 index 0000000000..759a8a1616 --- /dev/null +++ b/packages/frontend-test-utils/src/apis/createApiMock.ts @@ -0,0 +1,87 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ApiFactory, type ApiRef } from '@backstage/frontend-plugin-api'; +import { mockApiFactorySymbol } from './MockWithApiFactory'; + +/** + * Represents a mocked version of an API, where you automatically have access to + * the mocked versions of all of its methods along with a factory that returns + * that same mock. + * + * @public + */ +export type ApiMock = { + [mockApiFactorySymbol]: ApiFactory; +} & { + [Key in keyof TApi]: TApi[Key] extends (...args: infer Args) => infer Return + ? TApi[Key] & jest.MockInstance + : TApi[Key]; +}; + +/** + * Creates a standardized Backstage Utility API mockfactory function for + * producing mock API instances. + * + * @remarks + * + * Each method in the mock factory is a `jest.fn()`, and you can optionally pass + * partial implementations when calling the returned function. No type + * parameters should be provided to this function, they will be inferred from + * the provided API reference. + * + * @public + * @example + * ```ts + * import { createApiMock } from '@backstage/frontend-test-utils'; + * import { myApiRef } from '../apis'; + * + * // Set up the mock factory + * const mock = createApiMock(myApiRef, () => ({ + * greet: jest.fn(), + * })); + * + * // Create a mock with default behavior + * const api = mock(); + * + * // Or with a partial implementation + * const api = mock({ greet: async () => 'Hello!' }); + * expect(api.greet).toHaveBeenCalledTimes(1); + * ``` + */ +export function createApiMock( + apiRef: ApiRef, + mockFactory: () => jest.Mocked, +): (partialImpl?: Partial) => ApiMock { + return partialImpl => { + const mock = mockFactory(); + if (partialImpl) { + for (const [key, impl] of Object.entries(partialImpl)) { + if (typeof impl === 'function') { + (mock as any)[key].mockImplementation(impl); + } else { + (mock as any)[key] = impl; + } + } + } + (mock as any)[mockApiFactorySymbol] = { + api: apiRef, + deps: {}, + factory: () => mock, + }; + return mock as unknown as ApiMock; + }; +} diff --git a/packages/frontend-test-utils/src/apis/index.ts b/packages/frontend-test-utils/src/apis/index.ts index e5a0786cd0..cee5c47635 100644 --- a/packages/frontend-test-utils/src/apis/index.ts +++ b/packages/frontend-test-utils/src/apis/index.ts @@ -14,18 +14,93 @@ * limitations under the License. */ +export { mockApis } from './mockApis'; +export { createApiMock, type ApiMock } from './createApiMock'; export { - MockConfigApi, - type ErrorWithContext, - MockErrorApi, - type MockErrorApiOptions, - MockFetchApi, - type MockFetchApiOptions, - MockPermissionApi, - MockStorageApi, - type MockStorageBucket, - mockApis, - type ApiMock, -} from '@backstage/test-utils'; + type MockApiFactorySymbol, + type MockWithApiFactory, + attachMockApiFactory, +} from './MockWithApiFactory'; +export { + TestApiProvider, + type TestApiProviderProps, + type TestApiPair, + type TestApiPairs, +} from './TestApiProvider'; -export { MockAnalyticsApi } from './AnalyticsApi/MockAnalyticsApi'; +/** + * @public + * @deprecated Use `mockApis.alert()` instead. + */ +export type { MockAlertApi } from './AlertApi'; + +/** + * @public + * @deprecated Use `mockApis.analytics()` instead. + */ +export type { MockAnalyticsApi } from './AnalyticsApi'; + +/** + * @public + * @deprecated Use `mockApis.config()` instead. + */ +export type { MockConfigApi } from './ConfigApi'; + +/** + * @public + * @deprecated Use `mockApis.error()` instead. + */ +export type { MockErrorApi } from './ErrorApi'; + +/** + * @public + * @deprecated Use `mockApis.error()` instead. + */ +export type { MockErrorApiOptions } from './ErrorApi/MockErrorApi'; + +/** + * @public + * @deprecated Use the return type of `MockErrorApi.getErrors` instead. + */ +export type { ErrorWithContext } from './ErrorApi/MockErrorApi'; + +/** + * @public + * @deprecated Use `mockApis.fetch()` instead. + */ +export type { MockFetchApi } from './FetchApi'; + +/** + * @public + */ +export type { MockFetchApiOptions } from './FetchApi/MockFetchApi'; + +/** + * @public + * @deprecated Use `mockApis.featureFlags()` instead. + */ +export type { MockFeatureFlagsApi } from './FeatureFlagsApi'; + +/** + * @public + * @deprecated Use `mockApis.featureFlags()` instead. + */ +export type { MockFeatureFlagsApiOptions } from './FeatureFlagsApi/MockFeatureFlagsApi'; + +/** + * @public + * @deprecated Use `mockApis.permission()` instead. + */ +export type { MockPermissionApi } from './PermissionApi'; + +/** + * @public + * @deprecated Use `mockApis.storage()` instead. + */ +export type { MockStorageApi } from './StorageApi'; + +/** + * @public + * @deprecated Use `mockApis.translation()` instead. + */ +export type { MockTranslationApi } from './TranslationApi'; diff --git a/packages/frontend-test-utils/src/apis/mockApis.test.ts b/packages/frontend-test-utils/src/apis/mockApis.test.ts new file mode 100644 index 0000000000..9c6310ca9c --- /dev/null +++ b/packages/frontend-test-utils/src/apis/mockApis.test.ts @@ -0,0 +1,119 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FeatureFlagState } from '@backstage/frontend-plugin-api'; +import { mockApis } from './mockApis'; + +describe('mockApis', () => { + describe('alert', () => { + it('can create an instance', () => { + const alert = mockApis.alert(); + alert.post({ message: 'test alert' }); + expect(alert.getAlerts()).toHaveLength(1); + expect(alert.getAlerts()[0]).toMatchObject({ message: 'test alert' }); + }); + + it('can clear alerts', () => { + const alert = mockApis.alert(); + alert.post({ message: 'test' }); + expect(alert.getAlerts()).toHaveLength(1); + alert.clearAlerts(); + expect(alert.getAlerts()).toHaveLength(0); + }); + + it('can create a mock and make assertions on it', () => { + const alert = mockApis.alert.mock({ + post: jest.fn(msg => { + expect(msg).toMatchObject({ message: 'test' }); + }), + }); + alert.post({ message: 'test' }); + expect(alert.post).toHaveBeenCalledTimes(1); + }); + }); + + describe('featureFlags', () => { + it('can create an instance', () => { + const featureFlags = mockApis.featureFlags({ + initialStates: { 'test-flag': FeatureFlagState.Active }, + }); + expect(featureFlags.isActive('test-flag')).toBe(true); + expect(featureFlags.isActive('other-flag')).toBe(false); + }); + + it('can save and merge state', () => { + const featureFlags = mockApis.featureFlags({ + initialStates: { 'flag-1': FeatureFlagState.Active }, + }); + + featureFlags.save({ + states: { 'flag-2': FeatureFlagState.Active }, + merge: true, + }); + + expect(featureFlags.isActive('flag-1')).toBe(true); + expect(featureFlags.isActive('flag-2')).toBe(true); + }); + + it('can set and clear state using helper methods', () => { + const featureFlags = mockApis.featureFlags(); + featureFlags.setState({ 'test-flag': FeatureFlagState.Active }); + expect(featureFlags.getState()).toEqual({ + 'test-flag': FeatureFlagState.Active, + }); + featureFlags.clearState(); + expect(featureFlags.getState()).toEqual({}); + }); + + it('can create a mock and make assertions on it', () => { + const featureFlags = mockApis.featureFlags.mock({ + isActive: jest.fn(() => true), + }); + expect(featureFlags.isActive('test')).toBe(true); + expect(featureFlags.isActive).toHaveBeenCalledTimes(1); + }); + }); + + describe('re-exported APIs from test-utils', () => { + it('should have analytics', () => { + expect(mockApis.analytics).toBeDefined(); + }); + + it('should have config', () => { + expect(mockApis.config).toBeDefined(); + }); + + it('should have discovery', () => { + expect(mockApis.discovery).toBeDefined(); + }); + + it('should have identity', () => { + expect(mockApis.identity).toBeDefined(); + }); + + it('should have permission', () => { + expect(mockApis.permission).toBeDefined(); + }); + + it('should have storage', () => { + expect(mockApis.storage).toBeDefined(); + }); + + it('should have translation', () => { + expect(mockApis.translation).toBeDefined(); + }); + }); +}); diff --git a/packages/frontend-test-utils/src/apis/mockApis.ts b/packages/frontend-test-utils/src/apis/mockApis.ts new file mode 100644 index 0000000000..99b3ebfb67 --- /dev/null +++ b/packages/frontend-test-utils/src/apis/mockApis.ts @@ -0,0 +1,462 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + alertApiRef, + analyticsApiRef, + configApiRef, + discoveryApiRef, + errorApiRef, + fetchApiRef, + featureFlagsApiRef, + identityApiRef, + storageApiRef, + translationApiRef, + type AnalyticsApi, + type ConfigApi, + type DiscoveryApi, + type ErrorApi, + type FetchApi, + type FeatureFlagState, + type IdentityApi, + type StorageApi, + type TranslationApi, +} from '@backstage/frontend-plugin-api'; +import { + permissionApiRef, + type PermissionApi, +} from '@backstage/plugin-permission-react'; +import { JsonObject } from '@backstage/types'; +import { + AuthorizeResult, + EvaluatePermissionRequest, +} from '@backstage/plugin-permission-common'; +import { MockAlertApi } from './AlertApi'; +import { MockFeatureFlagsApi } from './FeatureFlagsApi'; +import { MockAnalyticsApi } from './AnalyticsApi'; +import { MockConfigApi } from './ConfigApi'; +import { MockErrorApi } from './ErrorApi'; +import { MockFetchApi, MockFetchApiOptions } from './FetchApi'; +import { MockStorageApi } from './StorageApi'; +import { MockPermissionApi } from './PermissionApi'; +import { MockTranslationApi } from './TranslationApi'; +import { + attachMockApiFactory, + type MockWithApiFactory, +} from './MockWithApiFactory'; +import { createApiMock } from './createApiMock'; + +/** + * Mock implementations of the core utility APIs, to be used in tests. + * + * @public + * @remarks + * + * There are some variations among the APIs depending on what needs tests + * might have, but overall there are two main usage patterns: + * + * 1: Creating an actual fake API instance, often with a simplified version + * of functionality, by calling the mock API itself as a function. + * + * ```ts + * // The function often accepts parameters that control its behavior + * const foo = mockApis.foo(); + * ``` + * + * 2: Creating a mock API, where all methods are replaced with jest mocks, by + * calling the API's `mock` function. + * + * ```ts + * // You can optionally supply a subset of its methods to implement + * const foo = mockApis.foo.mock({ + * someMethod: () => 'mocked result', + * }); + * // After exercising your test, you can make assertions on the mock: + * expect(foo.someMethod).toHaveBeenCalledTimes(2); + * expect(foo.otherMethod).toHaveBeenCalledWith(testData); + * ``` + */ +export namespace mockApis { + /** + * Fake implementation of {@link @backstage/frontend-plugin-api#AlertApi}. + * + * @public + * @example + * + * ```tsx + * const alertApi = mockApis.alert(); + * alertApi.post({ message: 'Test alert' }); + * expect(alertApi.getAlerts()).toHaveLength(1); + * ``` + */ + export function alert(): MockWithApiFactory { + const instance = new MockAlertApi(); + return attachMockApiFactory( + alertApiRef, + instance, + ) as MockWithApiFactory; + } + /** + * Mock helpers for {@link @backstage/frontend-plugin-api#AlertApi}. + * + * @public + */ + export namespace alert { + /** + * Creates a mock implementation of + * {@link @backstage/frontend-plugin-api#AlertApi}. All methods are + * replaced with jest mock functions, and you can optionally pass in a + * subset of methods with an explicit implementation. + * + * @public + */ + export const mock = createApiMock(alertApiRef, () => ({ + post: jest.fn(), + alert$: jest.fn(), + })); + } + + /** + * Fake implementation of {@link @backstage/frontend-plugin-api#FeatureFlagsApi}. + * + * @public + * @example + * + * ```tsx + * const featureFlagsApi = mockApis.featureFlags({ + * initialStates: { 'my-feature': FeatureFlagState.Active }, + * }); + * expect(featureFlagsApi.isActive('my-feature')).toBe(true); + * ``` + */ + export function featureFlags(options?: { + initialStates?: Record; + }): MockWithApiFactory { + const instance = new MockFeatureFlagsApi(options); + return attachMockApiFactory( + featureFlagsApiRef, + instance, + ) as MockWithApiFactory; + } + /** + * Mock helpers for {@link @backstage/frontend-plugin-api#FeatureFlagsApi}. + * + * @public + */ + export namespace featureFlags { + /** + * Creates a mock implementation of + * {@link @backstage/frontend-plugin-api#FeatureFlagsApi}. All methods are + * replaced with jest mock functions, and you can optionally pass in a + * subset of methods with an explicit implementation. + * + * @public + */ + export const mock = createApiMock(featureFlagsApiRef, () => ({ + registerFlag: jest.fn(), + getRegisteredFlags: jest.fn(), + isActive: jest.fn(), + save: jest.fn(), + })); + } + + /** + * Fake implementation of {@link @backstage/core-plugin-api#AnalyticsApi}. + * + * @public + */ + export function analytics(): MockAnalyticsApi & + MockWithApiFactory { + const instance = new MockAnalyticsApi(); + return attachMockApiFactory(analyticsApiRef, instance) as MockAnalyticsApi & + MockWithApiFactory; + } + + /** + * Mock helpers for {@link @backstage/core-plugin-api#AnalyticsApi}. + * + * @public + */ + export namespace analytics { + export const mock = createApiMock(analyticsApiRef, () => ({ + captureEvent: jest.fn(), + })); + } + + /** + * Fake implementation of {@link @backstage/frontend-plugin-api#TranslationApi}. + * By default returns the default translation. + * + * @public + */ + export function translation(): MockTranslationApi & + MockWithApiFactory { + const instance = MockTranslationApi.create(); + return attachMockApiFactory( + translationApiRef, + instance, + ) as MockTranslationApi & MockWithApiFactory; + } + + /** + * Mock helpers for {@link @backstage/frontend-plugin-api#TranslationApi}. + * + * @public + */ + export namespace translation { + /** + * Creates a mock of {@link @backstage/frontend-plugin-api#TranslationApi}. + * + * @public + */ + export const mock = createApiMock(translationApiRef, () => ({ + getTranslation: jest.fn(), + translation$: jest.fn(), + })); + } + + /** + * Fake implementation of {@link @backstage/core-plugin-api#ConfigApi}. + * + * @public + */ + export function config(options?: { + data?: JsonObject; + }): MockConfigApi & MockWithApiFactory { + const instance = new MockConfigApi({ data: options?.data ?? {} }); + return attachMockApiFactory(configApiRef, instance) as MockConfigApi & + MockWithApiFactory; + } + + /** + * Mock helpers for {@link @backstage/core-plugin-api#ConfigApi}. + * + * @public + */ + export namespace config { + export const mock = createApiMock(configApiRef, () => ({ + has: jest.fn(), + keys: jest.fn(), + get: jest.fn(), + getOptional: jest.fn(), + getConfig: jest.fn(), + getOptionalConfig: jest.fn(), + getConfigArray: jest.fn(), + getOptionalConfigArray: jest.fn(), + getNumber: jest.fn(), + getOptionalNumber: jest.fn(), + getBoolean: jest.fn(), + getOptionalBoolean: jest.fn(), + getString: jest.fn(), + getOptionalString: jest.fn(), + getStringArray: jest.fn(), + getOptionalStringArray: jest.fn(), + })); + } + + /** + * Fake implementation of {@link @backstage/core-plugin-api#DiscoveryApi}. + * + * @public + */ + export function discovery(options?: { + baseUrl?: string; + }): DiscoveryApi & MockWithApiFactory { + const baseUrl = options?.baseUrl ?? 'http://example.com'; + const instance: DiscoveryApi = { + async getBaseUrl(pluginId: string) { + return `${baseUrl}/api/${pluginId}`; + }, + }; + return attachMockApiFactory(discoveryApiRef, instance) as DiscoveryApi & + MockWithApiFactory; + } + + /** + * Mock helpers for {@link @backstage/core-plugin-api#DiscoveryApi}. + * + * @public + */ + export namespace discovery { + export const mock = createApiMock(discoveryApiRef, () => ({ + getBaseUrl: jest.fn(), + })); + } + + /** + * Fake implementation of {@link @backstage/core-plugin-api#IdentityApi}. + * + * @public + */ + export function identity(options?: { + userEntityRef?: string; + ownershipEntityRefs?: string[]; + token?: string; + email?: string; + displayName?: string; + picture?: string; + }): MockWithApiFactory { + const { + userEntityRef = 'user:default/test', + ownershipEntityRefs = ['user:default/test'], + token, + email, + displayName, + picture, + } = options ?? {}; + const instance: IdentityApi = { + async getBackstageIdentity() { + return { type: 'user', ownershipEntityRefs, userEntityRef }; + }, + async getCredentials() { + return { token }; + }, + async getProfileInfo() { + return { email, displayName, picture }; + }, + async signOut() {}, + }; + return attachMockApiFactory(identityApiRef, instance) as IdentityApi & + MockWithApiFactory; + } + + /** + * Mock helpers for {@link @backstage/core-plugin-api#IdentityApi}. + * + * @public + */ + export namespace identity { + export const mock = createApiMock(identityApiRef, () => ({ + getBackstageIdentity: jest.fn(), + getCredentials: jest.fn(), + getProfileInfo: jest.fn(), + signOut: jest.fn(), + })); + } + + /** + * Fake implementation of {@link @backstage/plugin-permission-react#PermissionApi}. + * + * @public + */ + export function permission(options?: { + authorize?: + | AuthorizeResult.ALLOW + | AuthorizeResult.DENY + | (( + request: EvaluatePermissionRequest, + ) => AuthorizeResult.ALLOW | AuthorizeResult.DENY); + }): MockPermissionApi & MockWithApiFactory { + const authorizeInput = options?.authorize; + const handler = + typeof authorizeInput === 'function' + ? authorizeInput + : () => authorizeInput ?? AuthorizeResult.ALLOW; + const instance = new MockPermissionApi(handler); + return attachMockApiFactory( + permissionApiRef, + instance, + ) as MockPermissionApi & MockWithApiFactory; + } + + /** + * Mock helpers for {@link @backstage/plugin-permission-react#PermissionApi}. + * + * @public + */ + export namespace permission { + export const mock = createApiMock(permissionApiRef, () => ({ + authorize: jest.fn(), + })); + } + + /** + * Fake implementation of {@link @backstage/core-plugin-api#StorageApi}. + * + * @public + */ + export function storage(options?: { + data?: JsonObject; + }): MockStorageApi & MockWithApiFactory { + const instance = MockStorageApi.create(options?.data); + return attachMockApiFactory(storageApiRef, instance) as MockStorageApi & + MockWithApiFactory; + } + + /** + * Mock helpers for {@link @backstage/core-plugin-api#StorageApi}. + * + * @public + */ + export namespace storage { + export const mock = createApiMock(storageApiRef, () => ({ + forBucket: jest.fn(), + snapshot: jest.fn(), + set: jest.fn(), + remove: jest.fn(), + observe$: jest.fn(), + })); + } + + /** + * Fake implementation of {@link @backstage/core-plugin-api#ErrorApi}. + * + * @public + */ + export function error(options?: { + collect?: boolean; + }): MockErrorApi & MockWithApiFactory { + const instance = new MockErrorApi(options); + return attachMockApiFactory(errorApiRef, instance) as MockErrorApi & + MockWithApiFactory; + } + + /** + * Mock helpers for {@link @backstage/core-plugin-api#ErrorApi}. + * + * @public + */ + export namespace error { + export const mock = createApiMock(errorApiRef, () => ({ + post: jest.fn(), + error$: jest.fn(), + })); + } + + /** + * Fake implementation of {@link @backstage/core-plugin-api#FetchApi}. + * + * @public + */ + export function fetch( + options?: MockFetchApiOptions, + ): MockFetchApi & MockWithApiFactory { + const instance = new MockFetchApi(options); + return attachMockApiFactory(fetchApiRef, instance) as MockFetchApi & + MockWithApiFactory; + } + + /** + * Mock helpers for {@link @backstage/core-plugin-api#FetchApi}. + * + * @public + */ + export namespace fetch { + export const mock = createApiMock(fetchApiRef, () => ({ + fetch: jest.fn(), + })); + } +} diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx index f412f29b65..9ce1863186 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx @@ -15,12 +15,16 @@ */ import { + analyticsApiRef, coreExtensionData, createExtension, createExtensionDataRef, createExtensionInput, + useAnalytics, } from '@backstage/frontend-plugin-api'; import { createExtensionTester } from './createExtensionTester'; +import { screen } from '@testing-library/react'; +import { renderInTestApp } from './renderInTestApp'; const stringDataRef = createExtensionDataRef().with({ id: 'test.string', @@ -152,4 +156,193 @@ describe('createExtensionTester', () => { expect([test, test2, test3]).toBeDefined(); }); + + it('should support API overrides via options', async () => { + const analyticsApiMock = { captureEvent: jest.fn() }; + + const TestComponent = () => { + const analytics = useAnalytics(); + analytics.captureEvent('test', 'value'); + return
    Test
    ; + }; + + const extension = createExtension({ + attachTo: { id: 'ignored', input: 'ignored' }, + output: [coreExtensionData.reactElement], + factory: () => [coreExtensionData.reactElement()], + }); + + const tester = createExtensionTester(extension, { + apis: [[analyticsApiRef, analyticsApiMock] as const], + }); + + renderInTestApp(tester.reactElement(), { + apis: [[analyticsApiRef, analyticsApiMock] as const], + }); + + expect(screen.getByText('Test')).toBeInTheDocument(); + expect(analyticsApiMock.captureEvent).toHaveBeenCalledWith( + expect.objectContaining({ + action: 'test', + subject: 'value', + }), + ); + }); + + describe('snapshot', () => { + it('should return a snapshot of the extension tree', () => { + const extension = createExtension({ + name: 'root', + attachTo: { id: 'ignored', input: 'ignored' }, + output: [stringDataRef], + factory: () => [stringDataRef('test-text')], + }); + + const tester = createExtensionTester(extension); + + expect(tester.snapshot()).toMatchInlineSnapshot(` + { + "id": "root", + "outputs": [ + "test.string", + ], + } + `); + }); + + it('should include child extensions in the tree', () => { + const childInput = createExtensionInput([stringDataRef]); + + const rootExtension = createExtension({ + name: 'root', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + children: childInput, + }, + output: [coreExtensionData.reactElement], + factory: () => [coreExtensionData.reactElement(
    root
    )], + }); + + const childExtension = createExtension({ + name: 'child', + attachTo: { id: 'root', input: 'children' }, + output: [stringDataRef], + factory: () => [stringDataRef('child-data')], + }); + + const tester = createExtensionTester(rootExtension).add(childExtension); + + expect(tester.snapshot()).toMatchInlineSnapshot(` + { + "children": { + "children": [ + { + "id": "child", + "outputs": [ + "test.string", + ], + }, + ], + }, + "id": "root", + "outputs": [ + "core.reactElement", + ], + } + `); + }); + + it('should include multiple children in sorted order', () => { + const childInput = createExtensionInput([stringDataRef]); + + const rootExtension = createExtension({ + name: 'root', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + children: childInput, + }, + output: [coreExtensionData.reactElement], + factory: () => [coreExtensionData.reactElement(
    root
    )], + }); + + const child1 = createExtension({ + name: 'child1', + attachTo: { id: 'root', input: 'children' }, + output: [stringDataRef], + factory: () => [stringDataRef('child1-data')], + }); + + const child2 = createExtension({ + name: 'child2', + attachTo: { id: 'root', input: 'children' }, + output: [stringDataRef], + factory: () => [stringDataRef('child2-data')], + }); + + const tester = createExtensionTester(rootExtension) + .add(child1) + .add(child2); + + expect(tester.snapshot()).toMatchInlineSnapshot(` + { + "children": { + "children": [ + { + "id": "child1", + "outputs": [ + "test.string", + ], + }, + { + "id": "child2", + "outputs": [ + "test.string", + ], + }, + ], + }, + "id": "root", + "outputs": [ + "core.reactElement", + ], + } + `); + }); + + it('should omit empty children and outputs', () => { + const extension = createExtension({ + name: 'root', + attachTo: { id: 'ignored', input: 'ignored' }, + output: [], + factory: () => [], + }); + + const tester = createExtensionTester(extension); + + expect(tester.snapshot()).toMatchInlineSnapshot(` + { + "id": "root", + } + `); + }); + + it('should produce serializable snapshot data', () => { + const extension = createExtension({ + name: 'root', + attachTo: { id: 'ignored', input: 'ignored' }, + output: [stringDataRef], + factory: () => [stringDataRef('test-text')], + }); + + const tester = createExtensionTester(extension); + const snapshot = tester.snapshot(); + + expect(snapshot).toEqual({ + id: 'root', + outputs: ['test.string'], + }); + + expect(JSON.parse(JSON.stringify(snapshot))).toEqual(snapshot); + }); + }); }); diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.tsx index 50d65fddc7..3a373b3b3a 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.tsx @@ -37,8 +37,24 @@ import { instantiateAppNodeTree } from '../../../frontend-app-api/src/tree/insta import { readAppExtensionsConfig } from '../../../frontend-app-api/src/tree/readAppExtensionsConfig'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { createErrorCollector } from '../../../frontend-app-api/src/wiring/createErrorCollector'; -import { TestApiRegistry } from '@backstage/test-utils'; import { OpaqueExtensionDefinition } from '@internal/frontend'; +import { resolveTestApiEntries, TestApiPairs } from '../apis/TestApiProvider'; + +/** + * Represents a snapshot of an extension in the app tree. + * + * @public + */ +export interface ExtensionSnapshotNode { + /** The ID of the extension */ + id: string; + /** The IDs of output data refs produced by this extension */ + outputs?: string[]; + /** Child extensions organized by input name */ + children?: Record; + /** Whether this extension is disabled */ + disabled?: true; +} /** @public */ export class ExtensionQuery { @@ -78,16 +94,23 @@ export class ExtensionQuery { /** @public */ export class ExtensionTester { /** @internal */ - static forSubject( + static forSubject< + T extends ExtensionDefinitionParameters, + const TApiPairs extends any[], + >( subject: ExtensionDefinition, - options?: { config?: T['configInput'] }, + options?: { + config?: T['configInput']; + apis?: readonly [...TestApiPairs]; + }, ): ExtensionTester> { - const tester = new ExtensionTester(); + const tester = new ExtensionTester(options?.apis); tester.add(subject, options as T['configInput'] & {}); return tester; } #tree?: AppTree; + #apis?: readonly any[]; readonly #extensions = new Array<{ id: string; @@ -96,6 +119,10 @@ export class ExtensionTester { config?: JsonValue; }>(); + private constructor(apis?: readonly any[]) { + this.#apis = apis; + } + add( extension: ExtensionDefinition, options?: { config?: T['configInput'] }, @@ -181,6 +208,54 @@ export class ExtensionTester { return element; } + /** + * Returns a snapshot of the extension tree structure for testing and debugging. + * Convenient to use with Jest's inline snapshot testing. + * + * @example + * ```tsx + * const tester = createExtensionTester(myExtension); + * expect(tester.snapshot()).toMatchInlineSnapshot(); + * ``` + */ + snapshot(): ExtensionSnapshotNode { + const tree = this.#resolveTree(); + + const buildNode = (node: AppNode): ExtensionSnapshotNode => { + const outputs = node.instance + ? Array.from(node.instance.getDataRefs()) + .map(ref => ref.id) + .sort() + : []; + + const children: Record = {}; + for (const [inputName, attachedNodes] of node.edges.attachments) { + children[inputName] = attachedNodes + .map(n => buildNode(n)) + .sort((a, b) => a.id.localeCompare(b.id)); + } + + const result: ExtensionSnapshotNode = { + id: node.spec.id, + }; + + // Only include non-empty/non-default fields + if (outputs.length > 0) { + result.outputs = outputs; + } + if (Object.keys(children).length > 0) { + result.children = children; + } + if (node.spec.disabled) { + result.disabled = true; + } + + return result; + }; + + return buildNode(tree.root); + } + #resolveTree() { if (this.#tree) { return this.#tree; @@ -206,7 +281,9 @@ export class ExtensionTester { collector, ); - instantiateAppNodeTree(tree.root, TestApiRegistry.from(), collector); + const apiHolder = resolveTestApiEntries(this.#apis ?? []); + + instantiateAppNodeTree(tree.root, apiHolder, collector); const errors = collector.collectErrors(); if (errors) { @@ -260,9 +337,15 @@ export class ExtensionTester { } /** @public */ -export function createExtensionTester( +export function createExtensionTester< + T extends ExtensionDefinitionParameters, + TApiPairs extends any[] = any[], +>( subject: ExtensionDefinition, - options?: { config?: T['configInput'] }, + options?: { + config?: T['configInput']; + apis?: readonly [...TestApiPairs]; + }, ): ExtensionTester> { return ExtensionTester.forSubject(subject, options); } diff --git a/packages/frontend-test-utils/src/app/index.ts b/packages/frontend-test-utils/src/app/index.ts index 9db67dbb49..71de66fce6 100644 --- a/packages/frontend-test-utils/src/app/index.ts +++ b/packages/frontend-test-utils/src/app/index.ts @@ -18,6 +18,7 @@ export { createExtensionTester, type ExtensionTester, type ExtensionQuery, + type ExtensionSnapshotNode, } from './createExtensionTester'; export { renderInTestApp, type TestAppOptions } from './renderInTestApp'; diff --git a/packages/frontend-test-utils/src/app/renderInTestApp.test.tsx b/packages/frontend-test-utils/src/app/renderInTestApp.test.tsx index 8e1b1e1ea0..c9d90715f6 100644 --- a/packages/frontend-test-utils/src/app/renderInTestApp.test.tsx +++ b/packages/frontend-test-utils/src/app/renderInTestApp.test.tsx @@ -16,11 +16,8 @@ import { useCallback } from 'react'; import { screen, fireEvent } from '@testing-library/react'; -import { - MockAnalyticsApi, - TestApiProvider, -} from '@backstage/frontend-test-utils'; -import { analyticsApiRef, useAnalytics } from '@backstage/frontend-plugin-api'; +import { mockApis, TestApiProvider } from '@backstage/frontend-test-utils'; +import { useAnalytics } from '@backstage/frontend-plugin-api'; import { Routes, Route } from 'react-router-dom'; import { renderInTestApp } from './renderInTestApp'; @@ -47,10 +44,10 @@ describe('renderInTestApp', () => { ); }; - const analyticsApiMock = new MockAnalyticsApi(); + const analyticsApiMock = mockApis.analytics(); renderInTestApp( - + , ); @@ -80,4 +77,35 @@ describe('renderInTestApp', () => { expect(screen.getByText('Second Page')).toBeInTheDocument(); }); + + it('should support API overrides via options', async () => { + const IndexPage = () => { + const analyticsApi = useAnalytics(); + const handleClick = useCallback(() => { + analyticsApi.captureEvent('click', 'Test action'); + }, [analyticsApi]); + return ( +
    + +
    + ); + }; + + const analyticsApiMock = mockApis.analytics(); + + renderInTestApp(, { + apis: [analyticsApiMock], + }); + + fireEvent.click(screen.getByRole('button', { name: 'Click me' })); + + expect(analyticsApiMock.getEvents()).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + action: 'click', + subject: 'Test action', + }), + ]), + ); + }); }); diff --git a/packages/frontend-test-utils/src/app/renderInTestApp.tsx b/packages/frontend-test-utils/src/app/renderInTestApp.tsx index 747229e8d6..bdb9929635 100644 --- a/packages/frontend-test-utils/src/app/renderInTestApp.tsx +++ b/packages/frontend-test-utils/src/app/renderInTestApp.tsx @@ -16,7 +16,7 @@ import { Fragment } from 'react'; import { Link, MemoryRouter } from 'react-router-dom'; -import { createSpecializedApp } from '@backstage/frontend-app-api'; +import { prepareSpecializedApp } from '@backstage/frontend-app-api'; import { RenderResult, render } from '@testing-library/react'; import { ConfigReader } from '@backstage/config'; import { JsonObject } from '@backstage/types'; @@ -27,12 +27,19 @@ import { RouteRef, useRouteRef, IconComponent, - RouterBlueprint, NavItemBlueprint, createFrontendPlugin, FrontendFeature, + createFrontendModule, + createApiFactory, + type ApiRef, } from '@backstage/frontend-plugin-api'; +import { RouterBlueprint } from '@backstage/plugin-app-react'; import appPlugin from '@backstage/plugin-app'; +import { getMockApiFactory } from '../apis/MockWithApiFactory'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import type { CreateSpecializedAppInternalOptions } from '../../../frontend-app-api/src/wiring/createSpecializedApp'; +import { TestApiPairs } from '../apis/TestApiProvider'; const DEFAULT_MOCK_CONFIG = { app: { baseUrl: 'http://localhost:3000' }, @@ -43,7 +50,7 @@ const DEFAULT_MOCK_CONFIG = { * Options to customize the behavior of the test app. * @public */ -export type TestAppOptions = { +export type TestAppOptions = { /** * An object of paths to mount route ref on, with the key being the path and the value * being the RouteRef that the path will be bound to. This allows the route refs to be @@ -76,6 +83,21 @@ export type TestAppOptions = { * Initial route entries to use for the router. */ initialRouteEntries?: string[]; + + /** + * API overrides to provide to the test app. Use `mockApis` helpers + * from `@backstage/frontend-test-utils` to create mock implementations. + * + * @example + * ```ts + * import { mockApis } from '@backstage/frontend-test-utils'; + * + * renderInTestApp(, { + * apis: [mockApis.identity({ userEntityRef: 'user:default/guest' })], + * }) + * ``` + */ + apis?: readonly [...TestApiPairs]; }; const NavItem = (props: { @@ -115,20 +137,22 @@ const appPluginOverride = appPlugin.withOverrides({ coreExtensionData.reactElement( , ), @@ -142,9 +166,9 @@ const appPluginOverride = appPlugin.withOverrides({ * @public * Renders the given element in a test app, for use in unit tests. */ -export function renderInTestApp( +export function renderInTestApp( element: JSX.Element, - options?: TestAppOptions, + options?: TestAppOptions, ): RenderResult { const extensions: Array = [ createExtension({ @@ -154,15 +178,6 @@ export function renderInTestApp( return [coreExtensionData.reactElement(element)]; }, }), - RouterBlueprint.make({ - params: { - component: ({ children }) => ( - - {children} - - ), - }, - }), ]; if (options?.mountedRoutes) { @@ -189,6 +204,26 @@ export function renderInTestApp( } const features: FrontendFeature[] = [ + createFrontendModule({ + pluginId: 'app', + extensions: [ + RouterBlueprint.make({ + params: { + component: ({ children }) => ( + + {children} + + ), + }, + }), + ], + }), createFrontendPlugin({ pluginId: 'test', extensions, @@ -200,7 +235,7 @@ export function renderInTestApp( features.push(...options.features); } - const app = createSpecializedApp({ + const app = prepareSpecializedApp({ features, config: ConfigReader.fromConfigs([ { @@ -208,7 +243,17 @@ export function renderInTestApp( data: options?.config ?? DEFAULT_MOCK_CONFIG, }, ]), - }); + __internal: options?.apis && { + apiFactoryOverrides: options.apis.map(entry => { + const mockFactory = getMockApiFactory(entry); + if (mockFactory) { + return mockFactory; + } + const [apiRef, implementation] = entry as readonly [ApiRef, any]; + return createApiFactory(apiRef, implementation); + }), + }, + } as CreateSpecializedAppInternalOptions).finalize(); return render( app.tree.root.instance!.getData(coreExtensionData.reactElement), diff --git a/packages/frontend-test-utils/src/app/renderTestApp.tsx b/packages/frontend-test-utils/src/app/renderTestApp.tsx index e8d90cadd6..7708fa4220 100644 --- a/packages/frontend-test-utils/src/app/renderTestApp.tsx +++ b/packages/frontend-test-utils/src/app/renderTestApp.tsx @@ -14,19 +14,29 @@ * limitations under the License. */ -import { createSpecializedApp } from '@backstage/frontend-app-api'; +import { Fragment } from 'react'; +import { prepareSpecializedApp } from '@backstage/frontend-app-api'; import { coreExtensionData, + createApiFactory, + createExtension, + createFrontendModule, createFrontendPlugin, ExtensionDefinition, FrontendFeature, - RouterBlueprint, + RouteRef, + type ApiRef, } from '@backstage/frontend-plugin-api'; -import { render } from '@testing-library/react'; +import { render, type RenderResult } from '@testing-library/react'; import appPlugin from '@backstage/plugin-app'; import { JsonObject } from '@backstage/types'; import { ConfigReader } from '@backstage/config'; import { MemoryRouter } from 'react-router-dom'; +import { RouterBlueprint } from '@backstage/plugin-app-react'; +import { getMockApiFactory } from '../apis/MockWithApiFactory'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import type { CreateSpecializedAppInternalOptions } from '../../../frontend-app-api/src/wiring/createSpecializedApp'; +import { TestApiPairs } from '../apis/TestApiProvider'; const DEFAULT_MOCK_CONFIG = { app: { baseUrl: 'http://localhost:3000' }, @@ -38,7 +48,7 @@ const DEFAULT_MOCK_CONFIG = { * * @public */ -export type RenderTestAppOptions = { +export type RenderTestAppOptions = { /** * Additional configuration passed to the app when rendering elements inside it. */ @@ -57,6 +67,39 @@ export type RenderTestAppOptions = { * Initial route entries to use for the router. */ initialRouteEntries?: string[]; + + /** + * An object of paths to mount route refs on, with the key being the path and + * the value being the RouteRef that the path will be bound to. This allows + * the route refs to be used by `useRouteRef` in the rendered elements. + * + * @example + * ```ts + * renderTestApp({ + * mountedRoutes: { + * '/my-path': myRouteRef, + * }, + * extensions: [...], + * }) + * ``` + */ + mountedRoutes?: { [path: string]: RouteRef }; + + /** + * API overrides to provide to the test app. Use `mockApis` helpers + * from `@backstage/frontend-test-utils` to create mock implementations. + * + * @example + * ```ts + * import { mockApis } from '@backstage/frontend-test-utils'; + * + * renderTestApp({ + * apis: [mockApis.identity({ userEntityRef: 'user:default/guest' })], + * extensions: [...], + * }) + * ``` + */ + apis?: readonly [...TestApiPairs]; }; const appPluginOverride = appPlugin.withOverrides({ @@ -73,21 +116,54 @@ const appPluginOverride = appPlugin.withOverrides({ * * @public */ -export function renderTestApp(options: RenderTestAppOptions) { - const extensions = [ - RouterBlueprint.make({ - params: { - component: ({ children }) => ( - - {children} - - ), - }, - }), - ...(options.extensions ?? []), - ]; +export function renderTestApp( + options?: RenderTestAppOptions, +): RenderResult { + const extensions = [...(options?.extensions ?? [])]; + + if (options?.mountedRoutes) { + for (const [path, routeRef] of Object.entries(options.mountedRoutes)) { + extensions.push( + createExtension({ + kind: 'test-route', + name: path, + attachTo: { id: 'app/routes', input: 'routes' }, + output: [ + coreExtensionData.reactElement, + coreExtensionData.routePath, + coreExtensionData.routeRef, + ], + factory: () => [ + coreExtensionData.reactElement(), + coreExtensionData.routePath(path), + coreExtensionData.routeRef(routeRef), + ], + }), + ); + } + } const features: FrontendFeature[] = [ + createFrontendModule({ + pluginId: 'app', + extensions: [ + RouterBlueprint.make({ + params: { + component: ({ children }) => ( + + {children} + + ), + }, + }), + ], + }), createFrontendPlugin({ pluginId: 'test', extensions, @@ -95,11 +171,11 @@ export function renderTestApp(options: RenderTestAppOptions) { appPluginOverride, ]; - if (options.features) { + if (options?.features) { features.push(...options.features); } - const app = createSpecializedApp({ + const app = prepareSpecializedApp({ features, config: ConfigReader.fromConfigs([ { @@ -107,7 +183,17 @@ export function renderTestApp(options: RenderTestAppOptions) { data: options?.config ?? DEFAULT_MOCK_CONFIG, }, ]), - }); + __internal: options?.apis && { + apiFactoryOverrides: options.apis.map(entry => { + const mockFactory = getMockApiFactory(entry); + if (mockFactory) { + return mockFactory; + } + const [apiRef, implementation] = entry as readonly [ApiRef, any]; + return createApiFactory(apiRef, implementation); + }), + }, + } as CreateSpecializedAppInternalOptions).finalize(); return render( app.tree.root.instance!.getData(coreExtensionData.reactElement), diff --git a/packages/frontend-test-utils/src/index.ts b/packages/frontend-test-utils/src/index.ts index cab66895e0..b4e7a4e007 100644 --- a/packages/frontend-test-utils/src/index.ts +++ b/packages/frontend-test-utils/src/index.ts @@ -23,9 +23,6 @@ export * from './apis'; export * from './app'; -export { TestApiProvider, TestApiRegistry } from '@backstage/test-utils'; -export type { TestApiProviderProps } from '@backstage/test-utils'; - export { withLogCollector } from '@backstage/test-utils'; export { registerMswTestHooks } from '@backstage/test-utils'; diff --git a/packages/integration-aws-node/CHANGELOG.md b/packages/integration-aws-node/CHANGELOG.md index d49f70955d..0da98dc1f4 100644 --- a/packages/integration-aws-node/CHANGELOG.md +++ b/packages/integration-aws-node/CHANGELOG.md @@ -1,5 +1,44 @@ # @backstage/integration-aws-node +## 0.1.21 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0 + - @backstage/config@1.3.7 + +## 0.1.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/config@1.3.7-next.0 + +## 0.1.20 + +### Patch Changes + +- 7455dae: Use node prefix on native imports + +## 0.1.20-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.1.19 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/config@1.3.6 + ## 0.1.19-next.0 ### Patch Changes diff --git a/packages/integration-aws-node/knip-report.md b/packages/integration-aws-node/knip-report.md index efa9f826b6..1204f34a4b 100644 --- a/packages/integration-aws-node/knip-report.md +++ b/packages/integration-aws-node/knip-report.md @@ -2,15 +2,15 @@ ## Unused dependencies (2) -| Name | Location | Severity | -| :-------------------------------- | :----------- | :------- | -| @aws-sdk/credential-provider-node | packages/integration-aws-node/package.json | error | -| @backstage/errors | packages/integration-aws-node/package.json | error | +| Name | Location | Severity | +| :-------------------------------- | :---------------- | :------- | +| @aws-sdk/credential-provider-node | package.json:40:6 | error | +| @backstage/errors | package.json:45:6 | error | ## Unused devDependencies (2) -| Name | Location | Severity | -| :----------------------- | :----------- | :------- | -| @backstage/config-loader | packages/integration-aws-node/package.json | error | -| @backstage/test-utils | packages/integration-aws-node/package.json | error | +| Name | Location | Severity | +| :----------------------- | :---------------- | :------- | +| @backstage/config-loader | package.json:49:6 | error | +| @backstage/test-utils | package.json:50:6 | error | diff --git a/packages/integration-aws-node/package.json b/packages/integration-aws-node/package.json index e3cd09cde0..09e0b03e7c 100644 --- a/packages/integration-aws-node/package.json +++ b/packages/integration-aws-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration-aws-node", - "version": "0.1.19-next.0", + "version": "0.1.21", "description": "Helpers for fetching AWS account credentials", "backstage": { "role": "node-library" diff --git a/packages/integration-aws-node/src/DefaultAwsCredentialsManager.test.ts b/packages/integration-aws-node/src/DefaultAwsCredentialsManager.test.ts index 582b003724..f1ec0ba6dc 100644 --- a/packages/integration-aws-node/src/DefaultAwsCredentialsManager.test.ts +++ b/packages/integration-aws-node/src/DefaultAwsCredentialsManager.test.ts @@ -17,25 +17,27 @@ import { DefaultAwsCredentialsManager } from './DefaultAwsCredentialsManager'; import { mockClient, AwsClientStub } from 'aws-sdk-client-mock'; import 'aws-sdk-client-mock-jest'; -import { - STSClient, - GetCallerIdentityCommand, - AssumeRoleCommand, -} from '@aws-sdk/client-sts'; +import { STSClient, GetCallerIdentityCommand } from '@aws-sdk/client-sts'; import { Config, ConfigReader } from '@backstage/config'; -import { promises } from 'fs'; -import { fromNodeProviderChain } from '@aws-sdk/credential-providers'; +import { + fromNodeProviderChain, + fromTemporaryCredentials, +} from '@aws-sdk/credential-providers'; +import { join } from 'node:path'; +import { mkdtempSync, writeFileSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; const env = process.env; let stsMock: AwsClientStub; let config: Config; +let tmpDir: string; -jest.mock('fs', () => ({ promises: { readFile: jest.fn() } })); jest.mock('@aws-sdk/credential-providers', () => { const originalModule = jest.requireActual('@aws-sdk/credential-providers'); return { ...originalModule, fromNodeProviderChain: jest.fn(), + fromTemporaryCredentials: jest.fn(), }; }); @@ -103,49 +105,44 @@ describe('DefaultAwsCredentialsManager', () => { Account: '123456789012', }; }); - stsMock - .on(AssumeRoleCommand, { - RoleArn: 'arn:aws:iam::111111111111:role/hello', - RoleSessionName: 'backstage', - ExternalId: 'world', - }) - .resolves({ - Credentials: { - AccessKeyId: 'ACCESS_KEY_ID_1', - SecretAccessKey: 'SECRET_ACCESS_KEY_1', - SessionToken: 'SESSION_TOKEN_1', - Expiration: new Date('2022-01-01'), - }, - }); - stsMock - .on(AssumeRoleCommand, { - RoleArn: 'arn:aws-other:iam::222222222222:role/hi', - RoleSessionName: 'backstage', - }) - .resolves({ - Credentials: { - AccessKeyId: 'ACCESS_KEY_ID_2', - SecretAccessKey: 'SECRET_ACCESS_KEY_2', - SessionToken: 'SESSION_TOKEN_2', - Expiration: new Date('2022-01-02'), - }, - }); - - stsMock - .on(AssumeRoleCommand, { - RoleArn: 'arn:aws:iam::999999999999:role/backstage-role', - RoleSessionName: 'backstage', - ExternalId: 'my-id', - }) - .resolves({ - Credentials: { - AccessKeyId: 'ACCESS_KEY_ID_9', - SecretAccessKey: 'SECRET_ACCESS_KEY_9', - SessionToken: 'SESSION_TOKEN_9', - Expiration: new Date('2022-01-09'), - }, - }); + // Mock fromTemporaryCredentials to return credential providers + // based on the RoleArn, instead of mocking internal nested STS clients. + const assumeRoleCredentials: Record< + string, + { + accessKeyId: string; + secretAccessKey: string; + sessionToken: string; + expiration: Date; + } + > = { + 'arn:aws:iam::111111111111:role/hello': { + accessKeyId: 'ACCESS_KEY_ID_1', + secretAccessKey: 'SECRET_ACCESS_KEY_1', + sessionToken: 'SESSION_TOKEN_1', + expiration: new Date('2022-01-01'), + }, + 'arn:aws-other:iam::222222222222:role/hi': { + accessKeyId: 'ACCESS_KEY_ID_2', + secretAccessKey: 'SECRET_ACCESS_KEY_2', + sessionToken: 'SESSION_TOKEN_2', + expiration: new Date('2022-01-02'), + }, + 'arn:aws:iam::999999999999:role/backstage-role': { + accessKeyId: 'ACCESS_KEY_ID_9', + secretAccessKey: 'SECRET_ACCESS_KEY_9', + sessionToken: 'SESSION_TOKEN_9', + expiration: new Date('2022-01-09'), + }, + }; + (fromTemporaryCredentials as jest.Mock).mockImplementation(opts => { + const creds = assumeRoleCredentials[opts.params.RoleArn]; + if (!creds) { + throw new Error(`Unexpected RoleArn: ${opts.params.RoleArn}`); + } + return async () => creds; + }); const testDate = new Date('2022-01-10'); @@ -159,15 +156,24 @@ describe('DefaultAwsCredentialsManager', () => { jest.requireActual('@aws-sdk/credential-providers').fromNodeProviderChain, ); - const mockProfile = `[my-profile] - aws_access_key_id=ACCESS_KEY_ID_9 - aws_secret_access_key=SECRET_ACCESS_KEY_9 - `; - (promises.readFile as jest.Mock).mockResolvedValue(mockProfile); + // Write a temporary AWS credentials file and point the SDK at it + tmpDir = mkdtempSync(join(tmpdir(), 'aws-test-')); + const credFilePath = join(tmpDir, 'credentials'); + const configFilePath = join(tmpDir, 'config'); + writeFileSync( + credFilePath, + '[my-profile]\naws_access_key_id=ACCESS_KEY_ID_9\naws_secret_access_key=SECRET_ACCESS_KEY_9\n', + ); + writeFileSync(configFilePath, ''); + process.env.AWS_SHARED_CREDENTIALS_FILE = credFilePath; + process.env.AWS_CONFIG_FILE = configFilePath; }); afterEach(() => { process.env = env; + if (tmpDir) { + rmSync(tmpDir, { recursive: true, force: true }); + } }); describe('#getCredentialProvider', () => { @@ -187,12 +193,21 @@ describe('DefaultAwsCredentialsManager', () => { expiration: new Date('2022-01-01'), }); + expect(fromTemporaryCredentials).toHaveBeenCalledWith( + expect.objectContaining({ + params: { + RoleArn: 'arn:aws:iam::111111111111:role/hello', + RoleSessionName: 'backstage', + ExternalId: 'world', + }, + }), + ); + const awsCredentialProvider2 = await provider.getCredentialProvider({ accountId: '111111111111', }); expect(awsCredentialProvider).toBe(awsCredentialProvider2); - expect(stsMock).toHaveReceivedCommandTimes(AssumeRoleCommand, 1); }); it('retrieves assume-role creds in another partition for the given account ID', async () => { @@ -210,6 +225,19 @@ describe('DefaultAwsCredentialsManager', () => { sessionToken: 'SESSION_TOKEN_2', expiration: new Date('2022-01-02'), }); + + expect(fromTemporaryCredentials).toHaveBeenCalledWith( + expect.objectContaining({ + params: { + RoleArn: 'arn:aws-other:iam::222222222222:role/hi', + RoleSessionName: 'backstage', + ExternalId: undefined, + }, + clientConfig: expect.objectContaining({ + region: 'not-us-east-1', + }), + }), + ); }); it('retrieves assume-role creds for an account using the account defaults', async () => { @@ -227,6 +255,16 @@ describe('DefaultAwsCredentialsManager', () => { sessionToken: 'SESSION_TOKEN_9', expiration: new Date('2022-01-09'), }); + + expect(fromTemporaryCredentials).toHaveBeenCalledWith( + expect.objectContaining({ + params: { + RoleArn: 'arn:aws:iam::999999999999:role/backstage-role', + RoleSessionName: 'backstage', + ExternalId: 'my-id', + }, + }), + ); }); it('retrieves static creds for the given account ID', async () => { @@ -297,7 +335,7 @@ describe('DefaultAwsCredentialsManager', () => { expect(awsCredentialProvider.accountId).toEqual('555555555555'); const creds = await awsCredentialProvider.sdkCredentialProvider(); - expect(creds).toEqual({ + expect(creds).toMatchObject({ accessKeyId: 'ACCESS_KEY_ID_9', secretAccessKey: 'SECRET_ACCESS_KEY_9', }); @@ -312,7 +350,7 @@ describe('DefaultAwsCredentialsManager', () => { expect(awsCredentialProvider.accountId).toEqual('444444444444'); const creds = await awsCredentialProvider.sdkCredentialProvider(); - expect(creds).toEqual({ + expect(creds).toMatchObject({ accessKeyId: 'ACCESS_KEY_ID_10', secretAccessKey: 'SECRET_ACCESS_KEY_10', sessionToken: 'SESSION_TOKEN_10', @@ -336,7 +374,7 @@ describe('DefaultAwsCredentialsManager', () => { expect(awsCredentialProvider.accountId).toEqual('123456789012'); const creds = await awsCredentialProvider.sdkCredentialProvider(); - expect(creds).toEqual({ + expect(creds).toMatchObject({ accessKeyId: 'ACCESS_KEY_ID_9', secretAccessKey: 'SECRET_ACCESS_KEY_9', }); @@ -354,7 +392,7 @@ describe('DefaultAwsCredentialsManager', () => { expect(awsCredentialProvider.accountId).toEqual('123456789012'); const creds = await awsCredentialProvider.sdkCredentialProvider(); - expect(creds).toEqual({ + expect(creds).toMatchObject({ accessKeyId: 'ACCESS_KEY_ID_10', secretAccessKey: 'SECRET_ACCESS_KEY_10', sessionToken: 'SESSION_TOKEN_10', @@ -372,7 +410,7 @@ describe('DefaultAwsCredentialsManager', () => { expect(awsCredentialProvider.accountId).toEqual('123456789012'); const creds = await awsCredentialProvider.sdkCredentialProvider(); - expect(creds).toEqual({ + expect(creds).toMatchObject({ accessKeyId: 'ACCESS_KEY_ID_10', secretAccessKey: 'SECRET_ACCESS_KEY_10', sessionToken: 'SESSION_TOKEN_10', diff --git a/packages/integration-react/CHANGELOG.md b/packages/integration-react/CHANGELOG.md index 6956f04b08..31b34f7cb9 100644 --- a/packages/integration-react/CHANGELOG.md +++ b/packages/integration-react/CHANGELOG.md @@ -1,5 +1,132 @@ # @backstage/integration-react +## 1.2.17 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.1 + - @backstage/config@1.3.7 + - @backstage/core-plugin-api@1.12.5 + +## 1.2.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.1-next.0 + - @backstage/config@1.3.7-next.0 + - @backstage/core-plugin-api@1.12.5-next.2 + +## 1.2.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.5-next.0 + - @backstage/config@1.3.6 + - @backstage/integration@2.0.0 + +## 1.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.4 + - @backstage/integration@2.0.0 + +## 1.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/config@1.3.6 + - @backstage/core-plugin-api@1.12.4-next.0 + +## 1.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/config@1.3.6 + - @backstage/core-plugin-api@1.12.4-next.0 + +## 1.2.15 + +### Patch Changes + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/core-plugin-api@1.12.3 + +## 1.2.15-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/integration@1.20.0-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + +## 1.2.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + +## 1.2.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.3-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/config@1.3.6 + +## 1.2.14 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2 + +## 1.2.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + +## 1.2.13 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/core-plugin-api@1.12.1 + +## 1.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/config@1.3.6 + +## 1.2.12 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/core-plugin-api@1.12.0 + - @backstage/config@1.3.6 + ## 1.2.12-next.0 ### Patch Changes diff --git a/packages/integration-react/dev/DevPage.tsx b/packages/integration-react/dev/DevPage.tsx index d03baca1d7..1c94ef984a 100644 --- a/packages/integration-react/dev/DevPage.tsx +++ b/packages/integration-react/dev/DevPage.tsx @@ -16,7 +16,7 @@ import { ScmIntegration, ScmIntegrationsGroup } from '@backstage/integration'; import Typography from '@material-ui/core/Typography'; -import { scmIntegrationsApiRef } from '../src/ScmIntegrationsApi'; +import { scmIntegrationsApiRef } from '../src/api/ScmIntegrationsApi'; import { Content } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; @@ -48,10 +48,6 @@ export const DevPage = () => { Azure - - Bitbucket - - Bitbucket Cloud diff --git a/packages/integration-react/dev/index.tsx b/packages/integration-react/dev/index.tsx index 65486d1f06..fbff7ef0c6 100644 --- a/packages/integration-react/dev/index.tsx +++ b/packages/integration-react/dev/index.tsx @@ -15,7 +15,7 @@ */ import { createDevApp } from '@backstage/dev-utils'; import { ScmIntegrations } from '@backstage/integration'; -import { scmIntegrationsApiRef } from '../src/ScmIntegrationsApi'; +import { scmIntegrationsApiRef } from '../src/api/ScmIntegrationsApi'; import { DevPage } from './DevPage'; import { configApiRef, createApiFactory } from '@backstage/core-plugin-api'; diff --git a/packages/integration-react/knip-report.md b/packages/integration-react/knip-report.md index dd0df6e7f4..b486fb085a 100644 --- a/packages/integration-react/knip-report.md +++ b/packages/integration-react/knip-report.md @@ -2,8 +2,8 @@ ## Unused devDependencies (2) -| Name | Location | Severity | -| :------------------- | :----------- | :------- | -| @testing-library/dom | packages/integration-react/package.json | error | -| msw | packages/integration-react/package.json | error | +| Name | Location | Severity | +| :------------------- | :---------------- | :------- | +| @testing-library/dom | package.json:47:6 | error | +| msw | package.json:50:6 | error | diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json index ca9dce00d9..896e236b87 100644 --- a/packages/integration-react/package.json +++ b/packages/integration-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration-react", - "version": "1.2.12-next.0", + "version": "1.2.17", "description": "Frontend package for managing integrations towards external systems", "backstage": { "role": "web-library" @@ -50,13 +50,13 @@ "msw": "^1.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/packages/integration-react/report.api.md b/packages/integration-react/report.api.md index 78da2eb5fa..0974c4821a 100644 --- a/packages/integration-react/report.api.md +++ b/packages/integration-react/report.api.md @@ -3,17 +3,17 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { ApiFactory } from '@backstage/core-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; import { ApiRef } from '@backstage/core-plugin-api'; import { AuthRequestOptions } from '@backstage/core-plugin-api'; -import { BackstageIdentityApi } from '@backstage/core-plugin-api'; +import { BackstageIdentityApi } from '@backstage/frontend-plugin-api'; import { Config } from '@backstage/config'; import { JSX as JSX_2 } from 'react/jsx-runtime'; import { OAuthApi } from '@backstage/core-plugin-api'; -import { OpenIdConnectApi } from '@backstage/core-plugin-api'; -import { ProfileInfoApi } from '@backstage/core-plugin-api'; +import { OpenIdConnectApi } from '@backstage/frontend-plugin-api'; +import { ProfileInfoApi } from '@backstage/frontend-plugin-api'; import { ScmIntegrationRegistry } from '@backstage/integration'; -import { SessionApi } from '@backstage/core-plugin-api'; +import { SessionApi } from '@backstage/frontend-plugin-api'; // @public export class ScmAuth implements ScmAuthApi { diff --git a/packages/integration/CHANGELOG.md b/packages/integration/CHANGELOG.md index bcc2a938ae..0962988e8b 100644 --- a/packages/integration/CHANGELOG.md +++ b/packages/integration/CHANGELOG.md @@ -1,5 +1,193 @@ # @backstage/integration +## 2.0.1 + +### Patch Changes + +- d112499: Fixed `SingleInstanceGithubCredentialsProvider` to return app credentials when `getCredentials` is called with a bare host URL (e.g. `https://github.com`) instead of falling back to a personal access token. +- Updated dependencies + - @backstage/errors@1.3.0 + - @backstage/config@1.3.7 + +## 2.0.1-next.0 + +### Patch Changes + +- d112499: Fixed `SingleInstanceGithubCredentialsProvider` to return app credentials when `getCredentials` is called with a bare host URL (e.g. `https://github.com`) instead of falling back to a personal access token. +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/config@1.3.7-next.0 + +## 2.0.0 + +### Major Changes + +- 527cf88: **BREAKING** Removed deprecated Azure DevOps, Bitbucket, Gerrit and GitHub code: + + - For Azure DevOps, the long deprecated `token` string and `credential` object have been removed from the `config.d.ts`. Use the `credentials` array object instead. + - For Bitbucket, the long deprecated `bitbucket` object has been removed from the `config.d.ts`. Use the `bitbucketCloud` or `bitbucketServer` objects instead. + - For Gerrit, the `parseGerritGitilesUrl` function has been removed, use `parseGitilesUrlRef` instead. The `buildGerritGitilesArchiveUrl` function has also been removed, use `buildGerritGitilesArchiveUrlFromLocation` instead. + - For GitHub, the `getGitHubRequestOptions` function has been removed. + +### Minor Changes + +- d933f62: Add configurable throttling and retry mechanism for GitLab integration. + +### Patch Changes + +- 1513a0b: Fixed a security vulnerability where path traversal sequences in SCM URLs could be used to access unintended API endpoints using server-side integration credentials. +- 993a598: Fixed Azure integration config schema visibility annotations to use per-field `@visibility secret` instead of `@deepVisibility secret` on parent objects, so that non-secret fields like `clientId`, `tenantId`, `organizations`, and `managedIdentityClientId` are no longer incorrectly marked as secret. + +## 2.0.0-next.2 + +### Patch Changes + +- 1513a0b: Fixed a security vulnerability where path traversal sequences in SCM URLs could be used to access unintended API endpoints using server-side integration credentials. + +## 2.0.0-next.1 + +### Major Changes + +- 527cf88: **BREAKING** Removed deprecated Azure DevOps, Bitbucket, Gerrit and GitHub code: + + - For Azure DevOps, the long deprecated `token` string and `credential` object have been removed from the `config.d.ts`. Use the `credentials` array object instead. + - For Bitbucket, the long deprecated `bitbucket` object has been removed from the `config.d.ts`. Use the `bitbucketCloud` or `bitbucketServer` objects instead. + - For Gerrit, the `parseGerritGitilesUrl` function has been removed, use `parseGitilesUrlRef` instead. The `buildGerritGitilesArchiveUrl` function has also been removed, use `buildGerritGitilesArchiveUrlFromLocation` instead. + - For GitHub, the `getGitHubRequestOptions` function has been removed. + +### Patch Changes + +- 993a598: Fixed Azure integration config schema visibility annotations to use per-field `@visibility secret` instead of `@deepVisibility secret` on parent objects, so that non-secret fields like `clientId`, `tenantId`, `organizations`, and `managedIdentityClientId` are no longer incorrectly marked as secret. +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 1.21.0-next.0 + +### Minor Changes + +- d933f62: Add configurable throttling and retry mechanism for GitLab integration. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 1.20.0 + +### Minor Changes + +- 6999f6d: The AzureUrl class in the @backstage/integration package is now able to process BOTH git branches and git tags. Initially this class only processed git branches and threw an error when non-branch Azure URLs were passed in. + +### Patch Changes + +- cc6206e: Added support for `{org}.visualstudio.com` domains used by Azure DevOps +- 7455dae: Use node prefix on native imports + +## 1.20.0-next.2 + +### Patch Changes + +- cc6206e: Added support for `{org}.visualstudio.com` domains used by Azure DevOps + +## 1.20.0-next.1 + +### Minor Changes + +- 6999f6d: The AzureUrl class in the @backstage/integration package is now able to process BOTH git branches and git tags. Initially this class only processed git branches and threw an error when non-branch Azure URLs were passed in. + +## 1.19.3-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 1.19.2 + +### Patch Changes + +- 3afeab4: Implementing `ScmIntegration` for `GoogleGcs` +- 9083273: Rollback the lowercase replacing in GitHub integration config + +## 1.19.2-next.0 + +### Patch Changes + +- 3afeab4: Implementing `ScmIntegration` for `GoogleGcs` +- 9083273: Rollback the lowercase replacing in GitHub integration config + +## 1.19.0 + +### Minor Changes + +- 37fba1d: Added support for Bitbucket Cloud OAuth. This introduces an alternative authentication method using a workspace OAuth consumer, alongside App Passwords (deprecated) and API tokens. OAuth does not require a bot or service account and avoids token expiry issues. + + **BREAKING CHANGES** + + - **@backstage/integration** (`src/bitbucketCloud/core.ts`) + + - `getBitbucketCloudRequestOptions` now returns a `Promise` and **must** be awaited. + + - **@backstage/plugin-scaffolder-backend-module-bitbucket-cloud** (`src/actions/helpers.ts`) + - `getBitbucketClient` now returns a `Promise` and **must** be awaited. + - `getAuthorizationHeader` now returns a `Promise` and **must** be awaited. + + **OAuth usage example** + + ```yaml + integrations: + bitbucketCloud: + - clientId: client-id + clientSecret: client-secret + ``` + +### Patch Changes + +- a26a322: Added support for using a GitHub App installation to generate tokens for public repository access when the `publicAccess` option is enabled. When all other authentication methods fail (e.g., the app is not installed in that organization), the provider will now use an available installation to generate a token that can be used to access public repositories as read only. +- fb029b6: Updated luxon types +- e15fdae: Made the github urls case insensitive. + +## 1.18.3-next.1 + +### Patch Changes + +- fb029b6: Updated luxon types +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 1.18.3-next.0 + +### Patch Changes + +- e15fdae: Made the github urls case insensitive. +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 1.18.2 + +### Patch Changes + +- fa255f5: Support for Bitbucket Cloud's API token was added as `appPassword` is deprecated (no new creation from September 9, 2025) and will be removed on June 9, 2026. + + API token usage example: + + ```yaml + integrations: + bitbucketCloud: + - username: user@domain.com + token: my-token + ``` + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/config@1.3.6 + ## 1.18.2-next.0 ### Patch Changes diff --git a/packages/integration/config.d.ts b/packages/integration/config.d.ts index 298d765697..8ea8dc2caf 100644 --- a/packages/integration/config.d.ts +++ b/packages/integration/config.d.ts @@ -27,40 +27,20 @@ export interface Config { * @visibility frontend */ host: string; - /** - * Token used to authenticate requests. - * @visibility secret - * @deprecated Use `credentials` instead. - */ - token?: string; - - /** - * The credential to use for requests. - * - * If no credential is specified anonymous access is used. - * - * @deepVisibility secret - * @deprecated Use `credentials` instead. - */ - credential?: { - clientId?: string; - clientSecret?: string; - tenantId?: string; - personalAccessToken?: string; - }; /** * The credentials to use for requests. If multiple credentials are specified the first one that matches the organization is used. * If no organization matches the first credential without an organization is used. * * If no credentials are specified at all, either a default credential (for Azure DevOps) or anonymous access (for Azure DevOps Server) is used. - * @deepVisibility secret */ credentials?: { organizations?: string[]; clientId?: string; + /** @visibility secret */ clientSecret?: string; tenantId?: string; + /** @visibility secret */ personalAccessToken?: string; managedIdentityClientId?: string; }[]; @@ -111,7 +91,6 @@ export interface Config { endpoint?: string; /** * Optional credential to use for Azure Active Directory authentication. - * @deepVisibility secret */ aadCredential?: { /** @@ -126,60 +105,40 @@ export interface Config { /** * The client secret for the Azure AD application. + * @visibility secret */ clientSecret: string; }; }>; - /** - * Integration configuration for Bitbucket - * @deprecated replaced by bitbucketCloud and bitbucketServer - */ - bitbucket?: Array<{ - /** - * The hostname of the given Bitbucket instance - * @visibility frontend - */ - host: string; - /** - * Token used to authenticate requests. - * @visibility secret - */ - token?: string; - /** - * The base url for the Bitbucket API, for example https://api.bitbucket.org/2.0 - * @visibility frontend - */ - apiBaseUrl?: string; - /** - * The username to use for authenticated requests. - * @visibility secret - */ - username?: string; - /** - * Bitbucket app password used to authenticate requests. - * @visibility secret - */ - appPassword?: string; - /** - * PGP signing key for signing commits. - * @visibility secret - */ - commitSigningKey?: string; - }>; - /** Integration configuration for Bitbucket Cloud */ bitbucketCloud?: Array<{ /** * The username to use for authenticated requests. * @visibility secret */ - username: string; + username?: string; + /** + * Token used to authenticate requests. + * @visibility secret + */ + token?: string; /** * Bitbucket Cloud app password used to authenticate requests. * @visibility secret + * @deprecated Use `token` instead. */ - appPassword: string; + appPassword?: string; + /** + * OAuth client ID for Bitbucket Cloud. + * @visibility secret + */ + clientId?: string; + /** + * OAuth client secret for Bitbucket Cloud. + * @visibility secret + */ + clientSecret?: string; /** * PGP signing key for signing commits. * @visibility secret @@ -324,6 +283,10 @@ export interface Config { * https://docs.github.com/en/rest/reference/apps#list-installations-for-the-authenticated-app--code-samples */ allowedInstallationOwners?: string[]; + /** + * If true, then an installation token will be issued for access when no other token is available. + */ + publicAccess?: boolean; }>; }>; @@ -366,6 +329,28 @@ export interface Config { * @visibility secret */ commitSigningKey?: string; + + /** + * Retry configuration for requests. + * @visibility frontend + */ + retry?: { + /** + * Maximum number of retries for failed requests. + * @visibility frontend + */ + maxRetries?: number; + /** + * HTTP status codes that should trigger a retry. + * @visibility frontend + */ + retryStatusCodes?: number[]; + /** + * Maximum number of API requests allowed per minute. Set to -1 to disable rate limiting. + * @visibility frontend + */ + maxApiRequestsPerMinute?: number; + }; }>; /** Integration configuration for Google Cloud Storage */ diff --git a/packages/integration/package.json b/packages/integration/package.json index 45928cd59b..de351778b6 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration", - "version": "1.18.2-next.0", + "version": "2.0.1", "description": "Helpers for managing integrations towards external systems", "backstage": { "role": "common-library" @@ -46,12 +46,13 @@ "cross-fetch": "^4.0.0", "git-url-parse": "^15.0.0", "lodash": "^4.17.21", - "luxon": "^3.0.0" + "luxon": "^3.0.0", + "p-throttle": "^4.1.1" }, "devDependencies": { + "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/config-loader": "workspace:^", - "@types/luxon": "^3.0.0", "msw": "^1.0.0" }, "configSchema": "config.d.ts" diff --git a/packages/integration/report.api.md b/packages/integration/report.api.md index f642058ccd..af28eeebea 100644 --- a/packages/integration/report.api.md +++ b/packages/integration/report.api.md @@ -201,8 +201,6 @@ export class AzureIntegration implements ScmIntegration { // @public export type AzureIntegrationConfig = { host: string; - token?: string; - credential?: AzureDevOpsCredential; credentials?: AzureDevOpsCredential[]; commitSigningKey?: string; }; @@ -250,37 +248,8 @@ export type BitbucketCloudIntegrationConfig = { username?: string; appPassword?: string; token?: string; - commitSigningKey?: string; -}; - -// @public @deprecated -export class BitbucketIntegration implements ScmIntegration { - constructor(integrationConfig: BitbucketIntegrationConfig); - // (undocumented) - get config(): BitbucketIntegrationConfig; - // (undocumented) - static factory: ScmIntegrationsFactory; - // (undocumented) - resolveEditUrl(url: string): string; - // (undocumented) - resolveUrl(options: { - url: string; - base: string; - lineNumber?: number; - }): string; - // (undocumented) - get title(): string; - // (undocumented) - get type(): string; -} - -// @public @deprecated -export type BitbucketIntegrationConfig = { - host: string; - apiBaseUrl: string; - token?: string; - username?: string; - appPassword?: string; + clientId?: string; + clientSecret?: string; commitSigningKey?: string; }; @@ -315,14 +284,6 @@ export type BitbucketServerIntegrationConfig = { commitSigningKey?: string; }; -// @public @deprecated -export function buildGerritGitilesArchiveUrl( - config: GerritIntegrationConfig, - project: string, - branch: string, - filePath: string, -): string; - // @public export function buildGerritGitilesArchiveUrlFromLocation( config: GerritIntegrationConfig, @@ -424,14 +385,6 @@ export function getAzureDownloadUrl(url: string): string; // @public export function getAzureFileFetchUrl(url: string): string; -// @public @deprecated -export function getAzureRequestOptions( - config: AzureIntegrationConfig, - additionalHeaders?: Record, -): Promise<{ - headers: Record; -}>; - // @public export function getBitbucketCloudDefaultBranch( url: string, @@ -450,37 +403,18 @@ export function getBitbucketCloudFileFetchUrl( config: BitbucketCloudIntegrationConfig, ): string; +// @public +export function getBitbucketCloudOAuthToken( + clientId: string, + clientSecret: string, +): Promise; + // @public export function getBitbucketCloudRequestOptions( config: BitbucketCloudIntegrationConfig, -): { +): Promise<{ headers: Record; -}; - -// @public @deprecated -export function getBitbucketDefaultBranch( - url: string, - config: BitbucketIntegrationConfig, -): Promise; - -// @public @deprecated -export function getBitbucketDownloadUrl( - url: string, - config: BitbucketIntegrationConfig, -): Promise; - -// @public @deprecated -export function getBitbucketFileFetchUrl( - url: string, - config: BitbucketIntegrationConfig, -): string; - -// @public @deprecated -export function getBitbucketRequestOptions( - config: BitbucketIntegrationConfig, -): { - headers: Record; -}; +}>; // @public export function getBitbucketServerDefaultBranch( @@ -571,14 +505,6 @@ export function getGithubFileFetchUrl( credentials: GithubCredentials, ): string; -// @public @deprecated -export function getGitHubRequestOptions( - config: GithubIntegrationConfig, - credentials: GithubCredentials, -): { - headers: Record; -}; - // @public export function getGitilesAuthenticationUrl( config: GerritIntegrationConfig, @@ -588,7 +514,7 @@ export function getGitilesAuthenticationUrl( export function getGitLabFileFetchUrl( url: string, config: GitLabIntegrationConfig, - token?: string, + _token?: string, ): Promise; // @public @@ -665,6 +591,7 @@ export type GithubAppConfig = { clientId: string; clientSecret: string; allowedInstallationOwners?: string[]; + publicAccess?: boolean; }; // @public @@ -675,7 +602,7 @@ export class GithubAppCredentialsMux { RestEndpointMethodTypes['apps']['listInstallations']['response']['data'] >; // (undocumented) - getAppToken(owner: string, repo?: string): Promise; + getAppToken(owner?: string, repo?: string): Promise; } // @public @@ -750,6 +677,8 @@ export class GitLabIntegration implements ScmIntegration { // (undocumented) static factory: ScmIntegrationsFactory; // (undocumented) + fetch(input: RequestInfo | URL, init?: RequestInit): Promise; + // (undocumented) resolveEditUrl(url: string): string; // (undocumented) resolveUrl(options: { @@ -770,10 +699,37 @@ export type GitLabIntegrationConfig = { token?: string; baseUrl: string; commitSigningKey?: string; + retry?: { + maxRetries?: number; + retryStatusCodes?: number[]; + maxApiRequestsPerMinute?: number; + }; }; +// @public +export class GoogleGcsIntegration implements ScmIntegration { + constructor(integrationConfig: GoogleGcsIntegrationConfig); + // (undocumented) + get config(): GoogleGcsIntegrationConfig; + // (undocumented) + static factory: ScmIntegrationsFactory; + // (undocumented) + resolveEditUrl(url: string): string; + // (undocumented) + resolveUrl(options: { + url: string; + base: string; + lineNumber?: number | undefined; + }): string; + // (undocumented) + get title(): string; + // (undocumented) + get type(): string; +} + // @public export type GoogleGcsIntegrationConfig = { + host: string; clientEmail?: string; privateKey?: string; }; @@ -816,8 +772,6 @@ export interface IntegrationsByType { azure: ScmIntegrationsGroup; // (undocumented) azureBlobStorage: ScmIntegrationsGroup; - // @deprecated (undocumented) - bitbucket: ScmIntegrationsGroup; // (undocumented) bitbucketCloud: ScmIntegrationsGroup; // (undocumented) @@ -831,19 +785,11 @@ export interface IntegrationsByType { // (undocumented) gitlab: ScmIntegrationsGroup; // (undocumented) + googleGcs: ScmIntegrationsGroup; + // (undocumented) harness: ScmIntegrationsGroup; } -// @public @deprecated -export function parseGerritGitilesUrl( - config: GerritIntegrationConfig, - url: string, -): { - branch: string; - filePath: string; - project: string; -}; - // @public export function parseGerritJsonResponse(response: Response): Promise; @@ -949,16 +895,6 @@ export function readBitbucketCloudIntegrationConfigs( configs: Config[], ): BitbucketCloudIntegrationConfig[]; -// @public @deprecated -export function readBitbucketIntegrationConfig( - config: Config, -): BitbucketIntegrationConfig; - -// @public @deprecated -export function readBitbucketIntegrationConfigs( - configs: Config[], -): BitbucketIntegrationConfig[]; - // @public export function readBitbucketServerIntegrationConfig( config: Config, @@ -1045,8 +981,6 @@ export interface ScmIntegrationRegistry azure: ScmIntegrationsGroup; // (undocumented) azureBlobStorage: ScmIntegrationsGroup; - // @deprecated (undocumented) - bitbucket: ScmIntegrationsGroup; // (undocumented) bitbucketCloud: ScmIntegrationsGroup; // (undocumented) @@ -1080,8 +1014,6 @@ export class ScmIntegrations implements ScmIntegrationRegistry { get azure(): ScmIntegrationsGroup; // (undocumented) get azureBlobStorage(): ScmIntegrationsGroup; - // @deprecated (undocumented) - get bitbucket(): ScmIntegrationsGroup; // (undocumented) get bitbucketCloud(): ScmIntegrationsGroup; // (undocumented) @@ -1101,6 +1033,8 @@ export class ScmIntegrations implements ScmIntegrationRegistry { // (undocumented) get gitlab(): ScmIntegrationsGroup; // (undocumented) + get googleGcs(): ScmIntegrationsGroup; + // (undocumented) get harness(): ScmIntegrationsGroup; // (undocumented) list(): ScmIntegration[]; diff --git a/packages/integration/src/ScmIntegrations.test.ts b/packages/integration/src/ScmIntegrations.test.ts index 0e0c181861..254d774a80 100644 --- a/packages/integration/src/ScmIntegrations.test.ts +++ b/packages/integration/src/ScmIntegrations.test.ts @@ -21,8 +21,6 @@ import { BitbucketCloudIntegration, BitbucketCloudIntegrationConfig, } from './bitbucketCloud'; -import { BitbucketIntegrationConfig } from './bitbucket'; -import { BitbucketIntegration } from './bitbucket/BitbucketIntegration'; import { BitbucketServerIntegration, BitbucketServerIntegrationConfig, @@ -43,6 +41,7 @@ import { AzureBlobStorageIntegrationConfig, AzureBlobStorageIntergation, } from './azureBlobStorage'; +import { GoogleGcsIntegration, GoogleGcsIntegrationConfig } from './googleGcs'; describe('ScmIntegrations', () => { const awsS3 = new AwsS3Integration({ @@ -61,10 +60,6 @@ describe('ScmIntegrations', () => { host: 'azureblobstorage.local', } as AzureBlobStorageIntegrationConfig); - const bitbucket = new BitbucketIntegration({ - host: 'bitbucket.local', - } as BitbucketIntegrationConfig); - const bitbucketCloud = new BitbucketCloudIntegration({ host: 'bitbucket.org', } as BitbucketCloudIntegrationConfig); @@ -93,12 +88,15 @@ describe('ScmIntegrations', () => { host: 'harness.local', } as HarnessIntegrationConfig); + const googleGcs = new GoogleGcsIntegration({ + host: 'storage.cloud.google.com', + } as GoogleGcsIntegrationConfig); + const i = new ScmIntegrations({ awsS3: basicIntegrations([awsS3], item => item.config.host), awsCodeCommit: basicIntegrations([awsCodeCommit], item => item.config.host), azure: basicIntegrations([azure], item => item.config.host), azureBlobStorage: basicIntegrations([azureBlob], item => item.config.host), - bitbucket: basicIntegrations([bitbucket], item => item.config.host), bitbucketCloud: basicIntegrations([bitbucketCloud], item => item.title), bitbucketServer: basicIntegrations( [bitbucketServer], @@ -108,6 +106,7 @@ describe('ScmIntegrations', () => { github: basicIntegrations([github], item => item.config.host), gitlab: basicIntegrations([gitlab], item => item.config.host), gitea: basicIntegrations([gitea], item => item.config.host), + googleGcs: basicIntegrations([googleGcs], item => item.config.host), harness: basicIntegrations([harness], item => item.config.host), }); @@ -120,7 +119,6 @@ describe('ScmIntegrations', () => { expect(i.azureBlobStorage.byUrl('https://azureblobstorage.local')).toBe( azureBlob, ); - expect(i.bitbucket.byUrl('https://bitbucket.local')).toBe(bitbucket); expect(i.bitbucketCloud.byUrl('https://bitbucket.org')).toBe( bitbucketCloud, ); @@ -141,7 +139,6 @@ describe('ScmIntegrations', () => { awsCodeCommit, azure, azureBlob, - bitbucket, bitbucketCloud, bitbucketServer, gerrit, @@ -160,7 +157,6 @@ describe('ScmIntegrations', () => { expect(i.azureBlobStorage.byUrl('https://azureblobstorage.local')).toBe( azureBlob, ); - expect(i.byUrl('https://bitbucket.local')).toBe(bitbucket); expect(i.byUrl('https://bitbucket.org')).toBe(bitbucketCloud); expect(i.byUrl('https://bitbucket-server.local')).toBe(bitbucketServer); expect(i.byUrl('https://gerrit.local')).toBe(gerrit); @@ -173,7 +169,6 @@ describe('ScmIntegrations', () => { expect(i.byHost('awscodecommit.local')).toBe(awsCodeCommit); expect(i.byHost('azure.local')).toBe(azure); expect(i.byHost('azureblobstorage.local')).toBe(azureBlob); - expect(i.byHost('bitbucket.local')).toBe(bitbucket); expect(i.byHost('bitbucket.org')).toBe(bitbucketCloud); expect(i.byHost('bitbucket-server.local')).toBe(bitbucketServer); expect(i.byHost('gerrit.local')).toBe(gerrit); diff --git a/packages/integration/src/ScmIntegrations.ts b/packages/integration/src/ScmIntegrations.ts index f11cb3c17e..26a278fdbf 100644 --- a/packages/integration/src/ScmIntegrations.ts +++ b/packages/integration/src/ScmIntegrations.ts @@ -19,7 +19,6 @@ import { AwsS3Integration } from './awsS3/AwsS3Integration'; import { AwsCodeCommitIntegration } from './awsCodeCommit/AwsCodeCommitIntegration'; import { AzureIntegration } from './azure/AzureIntegration'; import { BitbucketCloudIntegration } from './bitbucketCloud/BitbucketCloudIntegration'; -import { BitbucketIntegration } from './bitbucket/BitbucketIntegration'; import { BitbucketServerIntegration } from './bitbucketServer/BitbucketServerIntegration'; import { GerritIntegration } from './gerrit/GerritIntegration'; import { GithubIntegration } from './github/GithubIntegration'; @@ -30,6 +29,7 @@ import { ScmIntegrationRegistry } from './registry'; import { GiteaIntegration } from './gitea'; import { HarnessIntegration } from './harness/HarnessIntegration'; import { AzureBlobStorageIntergation } from './azureBlobStorage'; +import { GoogleGcsIntegration } from './googleGcs/GoogleGcsIntegration'; /** * The set of supported integrations. @@ -41,16 +41,13 @@ export interface IntegrationsByType { awsCodeCommit: ScmIntegrationsGroup; azureBlobStorage: ScmIntegrationsGroup; azure: ScmIntegrationsGroup; - /** - * @deprecated in favor of `bitbucketCloud` and `bitbucketServer` - */ - bitbucket: ScmIntegrationsGroup; bitbucketCloud: ScmIntegrationsGroup; bitbucketServer: ScmIntegrationsGroup; gerrit: ScmIntegrationsGroup; github: ScmIntegrationsGroup; gitlab: ScmIntegrationsGroup; gitea: ScmIntegrationsGroup; + googleGcs: ScmIntegrationsGroup; harness: ScmIntegrationsGroup; } @@ -68,13 +65,13 @@ export class ScmIntegrations implements ScmIntegrationRegistry { awsCodeCommit: AwsCodeCommitIntegration.factory({ config }), azureBlobStorage: AzureBlobStorageIntergation.factory({ config }), azure: AzureIntegration.factory({ config }), - bitbucket: BitbucketIntegration.factory({ config }), bitbucketCloud: BitbucketCloudIntegration.factory({ config }), bitbucketServer: BitbucketServerIntegration.factory({ config }), gerrit: GerritIntegration.factory({ config }), github: GithubIntegration.factory({ config }), gitlab: GitLabIntegration.factory({ config }), gitea: GiteaIntegration.factory({ config }), + googleGcs: GoogleGcsIntegration.factory({ config }), harness: HarnessIntegration.factory({ config }), }); } @@ -99,13 +96,6 @@ export class ScmIntegrations implements ScmIntegrationRegistry { return this.byType.azure; } - /** - * @deprecated in favor of `bitbucketCloud()` and `bitbucketServer()` - */ - get bitbucket(): ScmIntegrationsGroup { - return this.byType.bitbucket; - } - get bitbucketCloud(): ScmIntegrationsGroup { return this.byType.bitbucketCloud; } @@ -130,6 +120,10 @@ export class ScmIntegrations implements ScmIntegrationRegistry { return this.byType.gitea; } + get googleGcs(): ScmIntegrationsGroup { + return this.byType.googleGcs; + } + get harness(): ScmIntegrationsGroup { return this.byType.harness; } @@ -141,20 +135,10 @@ export class ScmIntegrations implements ScmIntegrationRegistry { } byUrl(url: string | URL): ScmIntegration | undefined { - let candidates = Object.values(this.byType) + const candidates = Object.values(this.byType) .map(i => i.byUrl(url)) .filter(Boolean); - // Do not return deprecated integrations if there are other options - if (candidates.length > 1) { - const filteredCandidates = candidates.filter( - x => !(x instanceof BitbucketIntegration), - ); - if (filteredCandidates.length !== 0) { - candidates = filteredCandidates; - } - } - return candidates[0]; } diff --git a/packages/integration/src/azure/AzureIntegration.test.ts b/packages/integration/src/azure/AzureIntegration.test.ts index c30283f432..e6a903814a 100644 --- a/packages/integration/src/azure/AzureIntegration.test.ts +++ b/packages/integration/src/azure/AzureIntegration.test.ts @@ -25,7 +25,7 @@ describe('AzureIntegration', () => { azure: [ { host: 'h.com', - token: 'token', + credentials: [{ personalAccessToken: 'token' }], }, ], }, diff --git a/packages/integration/src/azure/AzureUrl.test.ts b/packages/integration/src/azure/AzureUrl.test.ts index a56f800133..e1285fc38e 100644 --- a/packages/integration/src/azure/AzureUrl.test.ts +++ b/packages/integration/src/azure/AzureUrl.test.ts @@ -27,6 +27,7 @@ describe('AzureUrl', () => { expect(url.getRepo()).toBe('my-project'); expect(url.getRef()).toBeUndefined(); expect(url.getPath()).toBeUndefined(); + expect(url.getPrefix()).toBeUndefined(); expect(url.toRepoUrl()).toBe( 'https://dev.azure.com/my-org/_git/my-project', @@ -52,6 +53,7 @@ describe('AzureUrl', () => { expect(url.getRepo()).toBe('my-project'); expect(url.getRef()).toBeUndefined(); expect(url.getPath()).toBe('/test.yaml'); + expect(url.getPrefix()).toBeUndefined(); expect(url.toRepoUrl()).toBe( 'https://dev.azure.com/my-org/_git/my-project?path=%2Ftest.yaml', @@ -67,7 +69,7 @@ describe('AzureUrl', () => { ); }); - it('should work with the short URL form with a path and ref', () => { + it('should work with the short URL form with a path and branch ref', () => { const url = AzureUrl.fromRepoUrl( 'https://dev.azure.com/my-org/_git/my-project?path=%2Ftest.yaml&version=GBtest-branch', ); @@ -77,6 +79,7 @@ describe('AzureUrl', () => { expect(url.getRepo()).toBe('my-project'); expect(url.getRef()).toBe('test-branch'); expect(url.getPath()).toBe('/test.yaml'); + expect(url.getPrefix()).toBe('GB'); expect(url.toRepoUrl()).toBe( 'https://dev.azure.com/my-org/_git/my-project?path=%2Ftest.yaml&version=GBtest-branch', @@ -92,6 +95,32 @@ describe('AzureUrl', () => { ); }); + it('should work with the short URL form with a path and tag ref', () => { + const url = AzureUrl.fromRepoUrl( + 'https://dev.azure.com/my-org/_git/my-project?path=%2Ftest.yaml&version=GTtest-tag', + ); + + expect(url.getOwner()).toBe('my-org'); + expect(url.getProject()).toBe('my-project'); + expect(url.getRepo()).toBe('my-project'); + expect(url.getRef()).toBe('test-tag'); + expect(url.getPath()).toBe('/test.yaml'); + expect(url.getPrefix()).toBe('GT'); + + expect(url.toRepoUrl()).toBe( + 'https://dev.azure.com/my-org/_git/my-project?path=%2Ftest.yaml&version=GTtest-tag', + ); + expect(url.toFileUrl()).toBe( + 'https://dev.azure.com/my-org/my-project/_apis/git/repositories/my-project/items?api-version=6.0&path=%2Ftest.yaml&version=test-tag', + ); + expect(url.toArchiveUrl()).toBe( + 'https://dev.azure.com/my-org/my-project/_apis/git/repositories/my-project/items?recursionLevel=full&download=true&api-version=6.0&scopePath=%2Ftest.yaml&version=test-tag', + ); + expect(url.toCommitsUrl()).toBe( + 'https://dev.azure.com/my-org/my-project/_apis/git/repositories/my-project/commits?api-version=6.0&searchCriteria.itemVersion.version=test-tag', + ); + }); + it('should work with the long URL', () => { const url = AzureUrl.fromRepoUrl( 'http://my-host/my-org/my-project/_git/my-repo', @@ -102,6 +131,7 @@ describe('AzureUrl', () => { expect(url.getRepo()).toBe('my-repo'); expect(url.getRef()).toBeUndefined(); expect(url.getPath()).toBeUndefined(); + expect(url.getPrefix()).toBeUndefined(); expect(url.toRepoUrl()).toBe( 'http://my-host/my-org/my-project/_git/my-repo', @@ -117,7 +147,7 @@ describe('AzureUrl', () => { ); }); - it('should work with the long URL form with a path and ref', () => { + it('should work with the long URL form with a path and branch ref', () => { const url = AzureUrl.fromRepoUrl( 'http://my-host/my-org/my-project/_git/my-repo?path=%2Ffolder&version=GBtest-branch', ); @@ -127,6 +157,7 @@ describe('AzureUrl', () => { expect(url.getRepo()).toBe('my-repo'); expect(url.getRef()).toBe('test-branch'); expect(url.getPath()).toBe('/folder'); + expect(url.getPrefix()).toBe('GB'); expect(url.toRepoUrl()).toBe( 'http://my-host/my-org/my-project/_git/my-repo?path=%2Ffolder&version=GBtest-branch', @@ -142,6 +173,32 @@ describe('AzureUrl', () => { ); }); + it('should work with the long URL form with a path and tag ref', () => { + const url = AzureUrl.fromRepoUrl( + 'http://my-host/my-org/my-project/_git/my-repo?path=%2Ffolder&version=GTtest-tag', + ); + + expect(url.getOwner()).toBe('my-org'); + expect(url.getProject()).toBe('my-project'); + expect(url.getRepo()).toBe('my-repo'); + expect(url.getRef()).toBe('test-tag'); + expect(url.getPath()).toBe('/folder'); + expect(url.getPrefix()).toBe('GT'); + + expect(url.toRepoUrl()).toBe( + 'http://my-host/my-org/my-project/_git/my-repo?path=%2Ffolder&version=GTtest-tag', + ); + expect(url.toFileUrl()).toBe( + 'http://my-host/my-org/my-project/_apis/git/repositories/my-repo/items?api-version=6.0&path=%2Ffolder&version=test-tag', + ); + expect(url.toArchiveUrl()).toBe( + 'http://my-host/my-org/my-project/_apis/git/repositories/my-repo/items?recursionLevel=full&download=true&api-version=6.0&scopePath=%2Ffolder&version=test-tag', + ); + expect(url.toCommitsUrl()).toBe( + 'http://my-host/my-org/my-project/_apis/git/repositories/my-repo/commits?api-version=6.0&searchCriteria.itemVersion.version=test-tag', + ); + }); + it('should work with the old tfs long URL', () => { const url = AzureUrl.fromRepoUrl( 'http://my-host/tfs/projects/my-project/_git/my-repo', @@ -152,9 +209,10 @@ describe('AzureUrl', () => { expect(url.getRepo()).toBe('my-repo'); expect(url.getRef()).toBeUndefined(); expect(url.getPath()).toBeUndefined(); + expect(url.getPrefix()).toBeUndefined(); }); - it('should work with the old tfs long URL form with a path and ref', () => { + it('should work with the old tfs long URL form with a path and branch ref', () => { const url = AzureUrl.fromRepoUrl( 'http://my-host/tfs/projects/my-project/_git/my-repo?path=%2Ffolder&version=GBtest-branch', ); @@ -164,14 +222,28 @@ describe('AzureUrl', () => { expect(url.getRepo()).toBe('my-repo'); expect(url.getRef()).toBe('test-branch'); expect(url.getPath()).toBe('/folder'); + expect(url.getPrefix()).toBe('GB'); }); - it('should reject non-branch refs', () => { + it('should work with the old tfs long URL form with a path and tag ref', () => { + const url = AzureUrl.fromRepoUrl( + 'http://my-host/tfs/projects/my-project/_git/my-repo?path=%2Ffolder&version=GTtest-tag', + ); + + expect(url.getOwner()).toBe('tfs/projects'); + expect(url.getProject()).toBe('my-project'); + expect(url.getRepo()).toBe('my-repo'); + expect(url.getRef()).toBe('test-tag'); + expect(url.getPath()).toBe('/folder'); + expect(url.getPrefix()).toBe('GT'); + }); + + it('should reject non-branch and non-tag refs', () => { expect(() => AzureUrl.fromRepoUrl( 'https://dev.azure.com/my-org/_git/my-project?version=GC6eead79870d998a3befd4bc7c72cc89e446f2970', ), - ).toThrow('Azure URL version must point to a git branch'); + ).toThrow('Azure URL version must point to a git branch or git tag'); }); it('should reject non-repo URLs', () => { @@ -198,4 +270,53 @@ describe('AzureUrl', () => { ), ).toThrow('Azure URL must point to a git repository'); }); + + it('should work with the old visualstudio long URL', () => { + const url = AzureUrl.fromRepoUrl( + 'https://my-org.visualstudio.com/my-project/_git/my-repo', + ); + + expect(url.getOwner()).toBe('my-org'); + expect(url.getProject()).toBe('my-project'); + expect(url.getRepo()).toBe('my-repo'); + expect(url.getRef()).toBeUndefined(); + expect(url.getPath()).toBeUndefined(); + }); + + it('should work with the old visualstudio long URL form with a path', () => { + const url = AzureUrl.fromRepoUrl( + 'https://my-org.visualstudio.com/my-project/_git/my-repo?path=%2Ftest.yaml', + ); + + expect(url.getOwner()).toBe('my-org'); + expect(url.getProject()).toBe('my-project'); + expect(url.getRepo()).toBe('my-repo'); + expect(url.getRef()).toBeUndefined(); + expect(url.getPath()).toBe('/test.yaml'); + + expect(url.toRepoUrl()).toBe( + 'https://my-org.visualstudio.com/my-project/_git/my-repo?path=%2Ftest.yaml', + ); + expect(url.toFileUrl()).toBe( + 'https://my-org.visualstudio.com/my-project/_apis/git/repositories/my-repo/items?api-version=6.0&path=%2Ftest.yaml', + ); + expect(url.toArchiveUrl()).toBe( + 'https://my-org.visualstudio.com/my-project/_apis/git/repositories/my-repo/items?recursionLevel=full&download=true&api-version=6.0&scopePath=%2Ftest.yaml', + ); + expect(url.toCommitsUrl()).toBe( + 'https://my-org.visualstudio.com/my-project/_apis/git/repositories/my-repo/commits?api-version=6.0', + ); + }); + + it('should work with the old visualstudio long URL form with a path and ref', () => { + const url = AzureUrl.fromRepoUrl( + 'https://my-org.visualstudio.com/my-project/_git/my-repo?path=%2Ffolder&version=GBtest-branch', + ); + + expect(url.getOwner()).toBe('my-org'); + expect(url.getProject()).toBe('my-project'); + expect(url.getRepo()).toBe('my-repo'); + expect(url.getRef()).toBe('test-branch'); + expect(url.getPath()).toBe('/folder'); + }); }); diff --git a/packages/integration/src/azure/AzureUrl.ts b/packages/integration/src/azure/AzureUrl.ts index 1161ab7a1b..58ccff24a4 100644 --- a/packages/integration/src/azure/AzureUrl.ts +++ b/packages/integration/src/azure/AzureUrl.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -const VERSION_PREFIX_GIT_BRANCH = 'GB'; +import { isVisualStudioDomain } from './core'; export class AzureUrl { /** @@ -30,7 +30,12 @@ export class AzureUrl { let repo; const parts = url.pathname.split('/').map(part => decodeURIComponent(part)); - if (parts[2] === '_git') { + + if (isVisualStudioDomain(url.origin) && parts[2] === '_git') { + owner = url.host.split('.')[0]; + project = parts[1]; + repo = parts[3]; + } else if (parts[2] === '_git') { owner = parts[1]; project = repo = parts[3]; } else if (parts[3] === '_git') { @@ -50,16 +55,19 @@ export class AzureUrl { const path = url.searchParams.get('path') ?? undefined; let ref; + let prefix; const version = url.searchParams.get('version'); if (version) { - const prefix = version.slice(0, 2); - if (prefix !== 'GB') { - throw new Error('Azure URL version must point to a git branch'); + prefix = version.slice(0, 2); + if (prefix !== 'GB' && prefix !== 'GT') { + throw new Error( + 'Azure URL version must point to a git branch or git tag', + ); } ref = version.slice(2); } - return new AzureUrl(url.origin, owner, project, repo, path, ref); + return new AzureUrl(url.origin, owner, project, repo, path, ref, prefix); } #origin: string; @@ -68,6 +76,7 @@ export class AzureUrl { #repo: string; #path?: string; #ref?: string; + #prefix?: string; private constructor( origin: string, @@ -76,6 +85,7 @@ export class AzureUrl { repo: string, path?: string, ref?: string, + prefix?: string, ) { this.#origin = origin; this.#owner = owner; @@ -83,6 +93,7 @@ export class AzureUrl { this.#repo = repo; this.#path = path; this.#ref = ref; + this.#prefix = prefix; } #baseUrl = (...parts: string[]): URL => { @@ -98,7 +109,9 @@ export class AzureUrl { */ toRepoUrl(): string { let url; - if (this.#project === this.#repo) { + if (isVisualStudioDomain(this.#origin)) { + url = this.#baseUrl(this.#project, '_git', this.#repo); + } else if (this.#project === this.#repo) { url = this.#baseUrl(this.#owner, '_git', this.#repo); } else { url = this.#baseUrl(this.#owner, this.#project, '_git', this.#repo); @@ -108,7 +121,7 @@ export class AzureUrl { url.searchParams.set('path', this.#path); } if (this.#ref) { - url.searchParams.set('version', VERSION_PREFIX_GIT_BRANCH + this.#ref); + url.searchParams.set('version', this.#prefix + this.#ref); } return url.toString(); @@ -126,15 +139,28 @@ export class AzureUrl { ); } - const url = this.#baseUrl( - this.#owner, - this.#project, - '_apis', - 'git', - 'repositories', - this.#repo, - 'items', - ); + let url; + if (isVisualStudioDomain(this.#origin)) { + url = this.#baseUrl( + this.#project, + '_apis', + 'git', + 'repositories', + this.#repo, + 'items', + ); + } else { + url = this.#baseUrl( + this.#owner, + this.#project, + '_apis', + 'git', + 'repositories', + this.#repo, + 'items', + ); + } + url.searchParams.set('api-version', '6.0'); url.searchParams.set('path', this.#path); @@ -151,15 +177,28 @@ export class AzureUrl { * Throws an error if the URL does not point to a repo. */ toArchiveUrl(): string { - const url = this.#baseUrl( - this.#owner, - this.#project, - '_apis', - 'git', - 'repositories', - this.#repo, - 'items', - ); + let url; + if (isVisualStudioDomain(this.#origin)) { + url = this.#baseUrl( + this.#project, + '_apis', + 'git', + 'repositories', + this.#repo, + 'items', + ); + } else { + url = this.#baseUrl( + this.#owner, + this.#project, + '_apis', + 'git', + 'repositories', + this.#repo, + 'items', + ); + } + url.searchParams.set('recursionLevel', 'full'); url.searchParams.set('download', 'true'); url.searchParams.set('api-version', '6.0'); @@ -180,15 +219,28 @@ export class AzureUrl { * Throws an error if the URL does not point to a commit. */ toCommitsUrl(): string { - const url = this.#baseUrl( - this.#owner, - this.#project, - '_apis', - 'git', - 'repositories', - this.#repo, - 'commits', - ); + let url; + if (isVisualStudioDomain(this.#origin)) { + url = this.#baseUrl( + this.#project, + '_apis', + 'git', + 'repositories', + this.#repo, + 'commits', + ); + } else { + url = this.#baseUrl( + this.#owner, + this.#project, + '_apis', + 'git', + 'repositories', + this.#repo, + 'commits', + ); + } + url.searchParams.set('api-version', '6.0'); if (this.#ref) { @@ -232,4 +284,11 @@ export class AzureUrl { getRef(): string | undefined { return this.#ref; } + + /** + * Returns the git prefix in the repo if the URL contains one. + */ + getPrefix(): string | undefined { + return this.#prefix; + } } diff --git a/packages/integration/src/azure/DefaultAzureDevOpsCredentialsProvider.test.ts b/packages/integration/src/azure/DefaultAzureDevOpsCredentialsProvider.test.ts index 6e171eff77..4b86e9869e 100644 --- a/packages/integration/src/azure/DefaultAzureDevOpsCredentialsProvider.test.ts +++ b/packages/integration/src/azure/DefaultAzureDevOpsCredentialsProvider.test.ts @@ -445,5 +445,25 @@ describe('DefaultAzureDevOpsCredentialProvider', () => { }); }); }); + describe('Azure DevOps (visualstudio.com)', () => { + it('Should return a token when a credential with the same organization is specified', async () => { + const provider = buildProvider([ + { + host: 'org1.visualstudio.com', + credentials: [ + { + personalAccessToken: 'pat', + }, + ], + }, + ]); + + const credentials = provider.getCredentials({ + url: 'https://org1.visualstudio.com/project1', + }); + + expect(credentials).toBeDefined(); + }); + }); }); }); diff --git a/packages/integration/src/azure/DefaultAzureDevOpsCredentialsProvider.ts b/packages/integration/src/azure/DefaultAzureDevOpsCredentialsProvider.ts index 1ff35b0612..29abddb0e9 100644 --- a/packages/integration/src/azure/DefaultAzureDevOpsCredentialsProvider.ts +++ b/packages/integration/src/azure/DefaultAzureDevOpsCredentialsProvider.ts @@ -20,6 +20,7 @@ import { import { CachedAzureDevOpsCredentialsProvider } from './CachedAzureDevOpsCredentialsProvider'; import { ScmIntegrationRegistry } from '../registry'; import { DefaultAzureCredential } from '@azure/identity'; +import { isVisualStudioDomain } from './core'; /** * Default implementation of AzureDevOpsCredentialsProvider. @@ -110,6 +111,17 @@ export class DefaultAzureDevOpsCredentialsProvider return undefined; } + private forVisualStudioOrganization( + url: URL, + ): AzureDevOpsCredentialsProvider | undefined { + const parts = url.host.split('.'); + if (isVisualStudioDomain(url.origin) && parts.length > 0) { + return this.providers.get(url.host); + } + + return undefined; + } + private forHost(url: URL): AzureDevOpsCredentialsProvider | undefined { return this.providers.get(url.host); } @@ -121,6 +133,7 @@ export class DefaultAzureDevOpsCredentialsProvider const provider = this.forAzureDevOpsOrganization(url) ?? this.forAzureDevOpsServerOrganization(url) ?? + this.forVisualStudioOrganization(url) ?? this.forHost(url); if (provider === undefined) { diff --git a/packages/integration/src/azure/config.test.ts b/packages/integration/src/azure/config.test.ts index c1ddf1590f..cd2d916837 100644 --- a/packages/integration/src/azure/config.test.ts +++ b/packages/integration/src/azure/config.test.ts @@ -276,50 +276,6 @@ describe('readAzureIntegrationConfig', () => { expect(output).toEqual({ host: 'dev.azure.com' }); }); - it('maps deprecated token to credentials', () => { - const output = readAzureIntegrationConfig( - buildConfig({ - host: 'dev.azure.com', - token: 't', - }), - ); - - expect(output).toEqual({ - host: 'dev.azure.com', - credentials: [ - { - kind: 'PersonalAccessToken', - personalAccessToken: 't', - }, - ], - }); - }); - - it('maps deprecated credential to credentials', () => { - const output = readAzureIntegrationConfig( - buildConfig({ - host: 'dev.azure.com', - credential: { - clientId: 'id', - clientSecret: 'secret', - tenantId: 'tenantId', - }, - }), - ); - - expect(output).toEqual({ - host: 'dev.azure.com', - credentials: [ - { - kind: 'ClientSecret', - clientId: 'id', - clientSecret: 'secret', - tenantId: 'tenantId', - }, - ], - }); - }); - it('rejects config when host is not valid', () => { expect(() => readAzureIntegrationConfig(buildConfig({ ...valid, host: 7 })), diff --git a/packages/integration/src/azure/config.ts b/packages/integration/src/azure/config.ts index 09b97755ec..3d6244d69a 100644 --- a/packages/integration/src/azure/config.ts +++ b/packages/integration/src/azure/config.ts @@ -32,24 +32,6 @@ export type AzureIntegrationConfig = { */ host: string; - /** - * The authorization token to use for requests. - * - * If no token is specified, anonymous access is used. - * - * @deprecated Use `credentials` instead. - */ - token?: string; - - /** - * The credential to use for requests. - * - * If no credential is specified anonymous access is used. - * - * @deprecated Use `credentials` instead. - */ - credential?: AzureDevOpsCredential; - /** * The credentials to use for requests. If multiple credentials are specified the first one that matches the organization is used. * If not organization matches the first credential without an organization is used. @@ -236,9 +218,17 @@ function asAzureDevOpsCredential( export function readAzureIntegrationConfig( config: Config, ): AzureIntegrationConfig { + deprecatedConfigCheck(config); + const host = config.getOptionalString('host') ?? AZURE_HOST; - let credentialConfigs = config + if (!isValidHost(host)) { + throw new Error( + `Invalid Azure integration config, '${host}' is not a valid host`, + ); + } + + const credentialConfigs = config .getOptionalConfigArray('credentials') ?.map(credential => { const result: Partial = { @@ -257,54 +247,6 @@ export function readAzureIntegrationConfig( return result; }); - const token = config.getOptionalString('token')?.trim(); - - if ( - config.getOptional('credential') !== undefined && - config.getOptional('credentials') !== undefined - ) { - throw new Error( - `Invalid Azure integration config, 'credential' and 'credentials' cannot be used together. Use 'credentials' instead.`, - ); - } - - if ( - config.getOptional('token') !== undefined && - config.getOptional('credentials') !== undefined - ) { - throw new Error( - `Invalid Azure integration config, 'token' and 'credentials' cannot be used together. Use 'credentials' instead.`, - ); - } - - if (token !== undefined) { - const mapped = [{ personalAccessToken: token }]; - credentialConfigs = credentialConfigs?.concat(mapped) ?? mapped; - } - - if (config.getOptional('credential') !== undefined) { - const mapped = [ - { - organizations: config.getOptionalStringArray( - 'credential.organizations', - ), - token: config.getOptionalString('credential.token')?.trim(), - tenantId: config.getOptionalString('credential.tenantId'), - clientId: config.getOptionalString('credential.clientId'), - clientSecret: config - .getOptionalString('credential.clientSecret') - ?.trim(), - }, - ]; - credentialConfigs = credentialConfigs?.concat(mapped) ?? mapped; - } - - if (!isValidHost(host)) { - throw new Error( - `Invalid Azure integration config, '${host}' is not a valid host`, - ); - } - let credentials: AzureDevOpsCredential[] | undefined = undefined; if (credentialConfigs !== undefined) { const errors = credentialConfigs @@ -415,3 +357,19 @@ export function readAzureIntegrationConfigs( return result; } + +/** + * These config sections have been removed but to ensure they + * don't leak sensitive tokens we have this check in place + * to throw an error if found + * + * @internal + * @deprecated To be removed at a later date + */ +function deprecatedConfigCheck(config: Config) { + if (config.getOptional('credential') || config.getOptional('token')) { + throw new Error( + `Invalid Azure integration config, 'credential' and 'token' have been removed. Use 'credentials' instead.`, + ); + } +} diff --git a/packages/integration/src/azure/core.test.ts b/packages/integration/src/azure/core.test.ts index 601057e499..fbd6747f56 100644 --- a/packages/integration/src/azure/core.test.ts +++ b/packages/integration/src/azure/core.test.ts @@ -73,7 +73,7 @@ describe('azure core', () => { }, { url: 'com/a/b/blob/master/path/to/c.yaml', - error: 'Invalid URL: com/a/b/blob/master/path/to/c.yaml', + error: 'Invalid URL', }, ])('should handle error path %#', ({ url, error }) => { expect(() => getAzureFileFetchUrl(url)).toThrow(error); diff --git a/packages/integration/src/azure/core.ts b/packages/integration/src/azure/core.ts index 1c25373962..a004400ba4 100644 --- a/packages/integration/src/azure/core.ts +++ b/packages/integration/src/azure/core.ts @@ -53,3 +53,14 @@ export function getAzureDownloadUrl(url: string): string { export function getAzureCommitsUrl(url: string): string { return AzureUrl.fromRepoUrl(url).toCommitsUrl(); } + +/** + * Given a URL, return true if it contains `.visualstudio.com` and false if it does not + * URLs can be in these two formats: `dev.azure.com/{org}` or the legacy `{org}.visualstudio.com` + * + * @param origin - A URL origin string pointing to an Azure DevOps instance + * @public + */ +export function isVisualStudioDomain(origin: string): boolean { + return origin.endsWith('.visualstudio.com'); +} diff --git a/packages/integration/src/azure/deprecated.test.ts b/packages/integration/src/azure/deprecated.test.ts deleted file mode 100644 index 4411ef5f2f..0000000000 --- a/packages/integration/src/azure/deprecated.test.ts +++ /dev/null @@ -1,128 +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 { getAzureRequestOptions } from './deprecated'; -import { DateTime } from 'luxon'; -import { - AccessToken, - ClientSecretCredential, - ManagedIdentityCredential, -} from '@azure/identity'; - -jest.mock('@azure/identity'); - -const MockedClientSecretCredential = ClientSecretCredential as jest.MockedClass< - typeof ClientSecretCredential ->; - -const MockedManagedIdentityCredential = - ManagedIdentityCredential as jest.MockedClass< - typeof ManagedIdentityCredential - >; - -describe('azure core', () => { - beforeEach(() => { - jest.resetAllMocks(); - - MockedClientSecretCredential.prototype.getToken.mockImplementation(() => - Promise.resolve({ - expiresOnTimestamp: DateTime.local().plus({ days: 1 }).toSeconds(), - token: 'fake-client-secret-token', - } as AccessToken), - ); - MockedManagedIdentityCredential.prototype.getToken.mockImplementation(() => - Promise.resolve({ - expiresOnTimestamp: DateTime.local().plus({ days: 1 }).toSeconds(), - token: 'fake-managed-identity-token', - } as AccessToken), - ); - }); - - describe('getAzureRequestOptions', () => { - it('should not add authorization header when not using token or credential', async () => { - expect(await getAzureRequestOptions({ host: '' })).toEqual( - expect.objectContaining({ - headers: expect.not.objectContaining({ - Authorization: expect.anything(), - }), - }), - ); - }); - - it('should add authorization header when using a personal access token', async () => { - const pat = '0123456789'; - const encoded = Buffer.from(`:${pat}`).toString('base64'); - expect( - await getAzureRequestOptions({ - host: '', - credentials: [ - { - kind: 'PersonalAccessToken', - personalAccessToken: pat, - }, - ], - }), - ).toEqual( - expect.objectContaining({ - headers: expect.objectContaining({ - Authorization: `Basic ${encoded}`, - }), - }), - ); - }); - - it('should add authorization header when using a client secret', async () => { - expect( - await getAzureRequestOptions({ - host: '', - credentials: [ - { - kind: 'ClientSecret', - clientId: 'fake-id', - clientSecret: 'fake-secret', - tenantId: 'fake-tenant', - }, - ], - }), - ).toEqual( - expect.objectContaining({ - headers: expect.objectContaining({ - Authorization: 'Bearer fake-client-secret-token', - }), - }), - ); - }); - - it('should add authorization header when using a managed identity', async () => { - expect( - await getAzureRequestOptions({ - host: '', - credentials: [ - { - kind: 'ManagedIdentity', - clientId: 'fake-id', - }, - ], - }), - ).toEqual( - expect.objectContaining({ - headers: expect.objectContaining({ - Authorization: 'Bearer fake-managed-identity-token', - }), - }), - ); - }); - }); -}); diff --git a/packages/integration/src/azure/deprecated.ts b/packages/integration/src/azure/deprecated.ts deleted file mode 100644 index fd19f3dc14..0000000000 --- a/packages/integration/src/azure/deprecated.ts +++ /dev/null @@ -1,61 +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 { AzureIntegrationConfig } from './config'; -import { CachedAzureDevOpsCredentialsProvider } from './CachedAzureDevOpsCredentialsProvider'; - -/** - * Gets the request options necessary to make requests to a given provider. - * - * @param config - The relevant provider config - * @param additionalHeaders - Additional headers for the request - * @public - * @deprecated Use {@link AzureDevOpsCredentialsProvider} instead. - */ -export async function getAzureRequestOptions( - config: AzureIntegrationConfig, - additionalHeaders?: Record, -): Promise<{ headers: Record }> { - const headers: Record = additionalHeaders - ? { ...additionalHeaders } - : {}; - - /* - * Since we do not have a way to determine which organization the request is for, - * we will use the first credential that does not have an organization specified. - */ - const credentialConfig = config.credentials?.filter( - credential => - credential.organizations === undefined || - credential.organizations.length === 0, - )[0]; - - if (credentialConfig) { - const credentialsProvider = - CachedAzureDevOpsCredentialsProvider.fromAzureDevOpsCredential( - credentialConfig, - ); - const credentials = await credentialsProvider.getCredentials(); - - return { - headers: { - ...credentials?.headers, - ...headers, - }, - }; - } - - return { headers }; -} diff --git a/packages/integration/src/azure/index.ts b/packages/integration/src/azure/index.ts index a447c8ffd8..b04ead862f 100644 --- a/packages/integration/src/azure/index.ts +++ b/packages/integration/src/azure/index.ts @@ -38,5 +38,3 @@ export { export * from './types'; export { DefaultAzureDevOpsCredentialsProvider } from './DefaultAzureDevOpsCredentialsProvider'; - -export * from './deprecated'; diff --git a/packages/integration/src/bitbucket/BitbucketIntegration.test.ts b/packages/integration/src/bitbucket/BitbucketIntegration.test.ts deleted file mode 100644 index 575e1c9b6f..0000000000 --- a/packages/integration/src/bitbucket/BitbucketIntegration.test.ts +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ConfigReader } from '@backstage/config'; -import { BitbucketIntegration } from './BitbucketIntegration'; - -describe('BitbucketIntegration', () => { - describe('factory', () => { - it('works', () => { - const integrations = BitbucketIntegration.factory({ - config: new ConfigReader({ - integrations: { - bitbucket: [ - { - host: 'h.com', - apiBaseUrl: 'a', - token: 't', - username: 'u', - appPassword: 'p', - }, - ], - }, - }), - }); - expect(integrations.list().length).toBe(2); // including default - expect(integrations.list()[0].config.host).toBe('h.com'); - expect(integrations.list()[1].config.host).toBe('bitbucket.org'); - }); - - it('falls back to bitbucketCloud+bitbucketServer', () => { - const integrations = BitbucketIntegration.factory({ - config: new ConfigReader({ - integrations: { - bitbucketCloud: [ - { - username: 'u', - appPassword: 'p', - }, - ], - bitbucketServer: [ - { - host: 'h.com', - apiBaseUrl: 'a', - token: 't', - }, - ], - }, - }), - }); - expect(integrations.list().length).toBe(2); // including default - expect(integrations.list()[0].config.host).toBe('bitbucket.org'); - expect(integrations.list()[1].config.host).toBe('h.com'); - }); - }); - - it('returns the basics', () => { - const integration = new BitbucketIntegration({ host: 'h.com' } as any); - expect(integration.type).toBe('bitbucket'); - expect(integration.title).toBe('h.com'); - }); - - it('resolves url line number correctly for Bitbucket Cloud', () => { - const integration = new BitbucketIntegration({ - host: 'bitbucket.org', - } as any); - - expect( - integration.resolveUrl({ - url: './a.yaml', - base: 'https://bitbucket.org/my-owner/my-project/src/master/README.md', - lineNumber: 14, - }), - ).toBe( - 'https://bitbucket.org/my-owner/my-project/src/master/a.yaml#lines-14', - ); - }); - - it('resolves url line number correctly for Bitbucket Server', () => { - const integration = new BitbucketIntegration({ host: 'h.com' } as any); - - expect( - integration.resolveUrl({ - url: './a.yaml', - base: 'https://bitbucket.org/my-owner/my-project/src/master/README.md', - lineNumber: 14, - }), - ).toBe('https://bitbucket.org/my-owner/my-project/src/master/a.yaml#14'); - }); - - it('resolve edit URL', () => { - const integration = new BitbucketIntegration({ host: 'h.com' } as any); - - expect( - integration.resolveEditUrl( - 'https://bitbucket.org/my-owner/my-project/src/master/README.md', - ), - ).toBe( - 'https://bitbucket.org/my-owner/my-project/src/master/README.md?mode=edit&spa=0&at=master', - ); - }); -}); diff --git a/packages/integration/src/bitbucket/BitbucketIntegration.ts b/packages/integration/src/bitbucket/BitbucketIntegration.ts deleted file mode 100644 index d92532b54a..0000000000 --- a/packages/integration/src/bitbucket/BitbucketIntegration.ts +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import parseGitUrl from 'git-url-parse'; -import { basicIntegrations, defaultScmResolveUrl } from '../helpers'; -import { ScmIntegration, ScmIntegrationsFactory } from '../types'; -import { - BitbucketIntegrationConfig, - readBitbucketIntegrationConfigs, -} from './config'; - -/** - * A Bitbucket based integration. - * - * @public - * @deprecated replaced by the integrations bitbucketCloud and bitbucketServer. - */ -export class BitbucketIntegration implements ScmIntegration { - static factory: ScmIntegrationsFactory = ({ - config, - }) => { - const configs = readBitbucketIntegrationConfigs( - config.getOptionalConfigArray('integrations.bitbucket') ?? [ - // if integrations.bitbucket was not used assume the use was migrated to the new configs - // and backport for the deprecated integration to be usable for other parts of the system - // until these got migrated - ...(config.getOptionalConfigArray('integrations.bitbucketCloud') ?? []), - ...(config.getOptionalConfigArray('integrations.bitbucketServer') ?? - []), - ], - ); - return basicIntegrations( - configs.map(c => new BitbucketIntegration(c)), - i => i.config.host, - ); - }; - - constructor(private readonly integrationConfig: BitbucketIntegrationConfig) {} - - get type(): string { - return 'bitbucket'; - } - - get title(): string { - return this.integrationConfig.host; - } - - get config(): BitbucketIntegrationConfig { - return this.integrationConfig; - } - - resolveUrl(options: { - url: string; - base: string; - lineNumber?: number; - }): string { - const resolved = defaultScmResolveUrl(options); - if (!options.lineNumber) { - return resolved; - } - - const url = new URL(resolved); - - if (this.integrationConfig.host === 'bitbucket.org') { - // Bitbucket Cloud uses the syntax #lines-{start}[:{end}][,...] - url.hash = `lines-${options.lineNumber}`; - } else { - // Bitbucket Server uses the syntax #{start}[-{end}][,...] - url.hash = `${options.lineNumber}`; - } - - return url.toString(); - } - - resolveEditUrl(url: string): string { - const urlData = parseGitUrl(url); - const editUrl = new URL(url); - - editUrl.searchParams.set('mode', 'edit'); - // TODO: Not sure what spa=0 does, at least bitbucket.org doesn't support it - // but this is taken over from the initial implementation. - editUrl.searchParams.set('spa', '0'); - editUrl.searchParams.set('at', urlData.ref); - return editUrl.toString(); - } -} diff --git a/packages/integration/src/bitbucket/config.test.ts b/packages/integration/src/bitbucket/config.test.ts deleted file mode 100644 index 8dd20cade7..0000000000 --- a/packages/integration/src/bitbucket/config.test.ts +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Config, ConfigReader } from '@backstage/config'; -import { loadConfigSchema } from '@backstage/config-loader'; -import { - BitbucketIntegrationConfig, - readBitbucketIntegrationConfig, - readBitbucketIntegrationConfigs, -} from './config'; - -describe('readBitbucketIntegrationConfig', () => { - function buildConfig(data: Partial): Config { - return new ConfigReader(data); - } - - async function buildFrontendConfig( - data: Partial, - ): Promise { - const fullSchema = await loadConfigSchema({ - dependencies: ['@backstage/integration'], - }); - const serializedSchema = fullSchema.serialize() as { - schemas: { value: { properties?: { integrations?: object } } }[]; - }; - const schema = await loadConfigSchema({ - serialized: { - ...serializedSchema, // only include schemas that apply to integrations - schemas: serializedSchema.schemas.filter( - s => s.value?.properties?.integrations, - ), - }, - }); - const processed = schema.process( - [{ data: { integrations: { bitbucket: [data] } }, context: 'app' }], - { visibility: ['frontend'] }, - ); - return new ConfigReader( - (processed[0].data as any).integrations.bitbucket[0], - ); - } - - it('reads all values', () => { - const output = readBitbucketIntegrationConfig( - buildConfig({ - host: 'a.com', - apiBaseUrl: 'https://a.com/api', - token: 't\n\n\n', - username: 'u', - appPassword: '\n\n\np', - }), - ); - expect(output).toEqual({ - host: 'a.com', - apiBaseUrl: 'https://a.com/api', - token: 't', - username: 'u', - appPassword: 'p', - }); - }); - - it('inserts the defaults if missing', () => { - const output = readBitbucketIntegrationConfig(buildConfig({})); - expect(output).toEqual( - expect.objectContaining({ - host: 'bitbucket.org', - apiBaseUrl: 'https://api.bitbucket.org/2.0', - }), - ); - }); - - it('rejects funky configs', () => { - const valid: any = { - host: 'a.com', - apiBaseUrl: 'https://a.com/api', - token: 't', - username: 'u', - appPassword: 'p', - }; - expect(() => - readBitbucketIntegrationConfig(buildConfig({ ...valid, host: 7 })), - ).toThrow(/host/); - expect(() => - readBitbucketIntegrationConfig(buildConfig({ ...valid, apiBaseUrl: 7 })), - ).toThrow(/apiBaseUrl/); - expect(() => - readBitbucketIntegrationConfig(buildConfig({ ...valid, token: 7 })), - ).toThrow(/token/); - expect(() => - readBitbucketIntegrationConfig(buildConfig({ ...valid, username: 7 })), - ).toThrow(/username/); - expect(() => - readBitbucketIntegrationConfig(buildConfig({ ...valid, appPassword: 7 })), - ).toThrow(/appPassword/); - }); - - it('works on the frontend', async () => { - expect( - readBitbucketIntegrationConfig( - await buildFrontendConfig({ - host: 'a.com', - apiBaseUrl: 'https://a.com/api', - token: 't', - username: 'u', - appPassword: 'p', - }), - ), - ).toEqual({ - host: 'a.com', - apiBaseUrl: 'https://a.com/api', - }); - }); -}); - -describe('readBitbucketIntegrationConfigs', () => { - function buildConfig(data: Partial[]): Config[] { - return data.map(item => new ConfigReader(item)); - } - - it('reads all values', () => { - const output = readBitbucketIntegrationConfigs( - buildConfig([ - { - host: 'a.com', - apiBaseUrl: 'https://a.com/api', - token: 't', - username: 'u', - appPassword: 'p', - }, - ]), - ); - expect(output).toContainEqual({ - host: 'a.com', - apiBaseUrl: 'https://a.com/api', - token: 't', - username: 'u', - appPassword: 'p', - }); - }); - - it('adds a default Bitbucket Cloud entry when missing', () => { - const output = readBitbucketIntegrationConfigs(buildConfig([])); - expect(output).toEqual([ - { - host: 'bitbucket.org', - apiBaseUrl: 'https://api.bitbucket.org/2.0', - }, - ]); - }); - - it('injects the correct Bitbucket Cloud API base URL when missing', () => { - const output = readBitbucketIntegrationConfigs( - buildConfig([{ host: 'bitbucket.org' }]), - ); - expect(output).toEqual([ - { - host: 'bitbucket.org', - apiBaseUrl: 'https://api.bitbucket.org/2.0', - }, - ]); - }); -}); diff --git a/packages/integration/src/bitbucket/config.ts b/packages/integration/src/bitbucket/config.ts deleted file mode 100644 index 6939034e7d..0000000000 --- a/packages/integration/src/bitbucket/config.ts +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Config } from '@backstage/config'; -import { trimEnd } from 'lodash'; -import { isValidHost } from '../helpers'; - -const BITBUCKET_HOST = 'bitbucket.org'; -const BITBUCKET_API_BASE_URL = 'https://api.bitbucket.org/2.0'; - -/** - * The configuration parameters for a single Bitbucket API provider. - * - * @public - * @deprecated bitbucket integration replaced by integrations bitbucketCloud and bitbucketServer. - */ -export type BitbucketIntegrationConfig = { - /** - * The host of the target that this matches on, e.g. "bitbucket.org" - */ - host: string; - - /** - * The base URL of the API of this provider, e.g. "https://api.bitbucket.org/2.0", - * with no trailing slash. - * - * Values omitted at the optional property at the app-config will be deduced - * from the "host" value. - */ - apiBaseUrl: string; - - /** - * The authorization token to use for requests to a Bitbucket Server provider. - * - * See https://confluence.atlassian.com/bitbucketserver/personal-access-tokens-939515499.html - * - * If no token is specified, anonymous access is used. - */ - token?: string; - - /** - * The username to use for requests to Bitbucket Cloud (bitbucket.org). - */ - username?: string; - - /** - * Authentication with Bitbucket Cloud (bitbucket.org) is done using app passwords. - * - * See https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/ - */ - appPassword?: string; - - /** - * Signing key for commits - */ - commitSigningKey?: string; -}; - -/** - * Reads a single Bitbucket integration config. - * - * @param config - The config object of a single integration - * @public - * @deprecated bitbucket integration replaced by integrations bitbucketCloud and bitbucketServer. - */ -export function readBitbucketIntegrationConfig( - config: Config, -): BitbucketIntegrationConfig { - const host = config.getOptionalString('host') ?? BITBUCKET_HOST; - let apiBaseUrl = config.getOptionalString('apiBaseUrl'); - const token = config.getOptionalString('token')?.trim(); - const username = config.getOptionalString('username'); - const appPassword = config.getOptionalString('appPassword')?.trim(); - - if (!isValidHost(host)) { - throw new Error( - `Invalid Bitbucket integration config, '${host}' is not a valid host`, - ); - } - - if (apiBaseUrl) { - apiBaseUrl = trimEnd(apiBaseUrl, '/'); - } else if (host === BITBUCKET_HOST) { - apiBaseUrl = BITBUCKET_API_BASE_URL; - } else { - apiBaseUrl = `https://${host}/rest/api/1.0`; - } - - return { - host, - apiBaseUrl, - token, - username, - appPassword, - commitSigningKey: config.getOptionalString('commitSigningKey'), - }; -} - -/** - * Reads a set of Bitbucket integration configs, and inserts some defaults for - * public Bitbucket if not specified. - * - * @param configs - All of the integration config objects - * @public - * @deprecated bitbucket integration replaced by integrations bitbucketCloud and bitbucketServer. - */ -export function readBitbucketIntegrationConfigs( - configs: Config[], -): BitbucketIntegrationConfig[] { - // First read all the explicit integrations - const result = configs.map(readBitbucketIntegrationConfig); - - // If no explicit bitbucket.org integration was added, put one in the list as - // a convenience - if (!result.some(c => c.host === BITBUCKET_HOST)) { - result.push({ - host: BITBUCKET_HOST, - apiBaseUrl: BITBUCKET_API_BASE_URL, - }); - } - - return result; -} diff --git a/packages/integration/src/bitbucket/core.test.ts b/packages/integration/src/bitbucket/core.test.ts deleted file mode 100644 index f1b4e04f15..0000000000 --- a/packages/integration/src/bitbucket/core.test.ts +++ /dev/null @@ -1,304 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { rest } from 'msw'; -import { setupServer } from 'msw/node'; -import { registerMswTestHooks } from '../helpers'; -import { BitbucketIntegrationConfig } from './config'; -import { - getBitbucketDefaultBranch, - getBitbucketDownloadUrl, - getBitbucketFileFetchUrl, - getBitbucketRequestOptions, -} from './core'; - -describe('bitbucket core', () => { - const worker = setupServer(); - registerMswTestHooks(worker); - - describe('getBitbucketRequestOptions', () => { - it('inserts a token when needed', () => { - const withToken: BitbucketIntegrationConfig = { - host: '', - apiBaseUrl: '', - token: 'A', - }; - const withoutToken: BitbucketIntegrationConfig = { - host: '', - apiBaseUrl: '', - }; - expect( - (getBitbucketRequestOptions(withToken).headers as any).Authorization, - ).toEqual('Bearer A'); - expect( - (getBitbucketRequestOptions(withoutToken).headers as any).Authorization, - ).toBeUndefined(); - }); - - it('insert basic auth when needed', () => { - const withUsernameAndPassword: BitbucketIntegrationConfig = { - host: '', - apiBaseUrl: '', - username: 'some-user', - appPassword: 'my-secret', - }; - const withoutUsernameAndPassword: BitbucketIntegrationConfig = { - host: '', - apiBaseUrl: '', - }; - expect( - (getBitbucketRequestOptions(withUsernameAndPassword).headers as any) - .Authorization, - ).toEqual('Basic c29tZS11c2VyOm15LXNlY3JldA=='); - expect( - (getBitbucketRequestOptions(withoutUsernameAndPassword).headers as any) - .Authorization, - ).toBeUndefined(); - }); - }); - - describe('getBitbucketFileFetchUrl', () => { - it('rejects targets that do not look like URLs', () => { - const config: BitbucketIntegrationConfig = { host: '', apiBaseUrl: '' }; - expect(() => getBitbucketFileFetchUrl('a/b', config)).toThrow( - /Incorrect URL: a\/b/, - ); - }); - - it('happy path for Bitbucket Cloud', () => { - const config: BitbucketIntegrationConfig = { - host: 'bitbucket.org', - apiBaseUrl: 'https://api.bitbucket.org/2.0', - }; - expect( - getBitbucketFileFetchUrl( - 'https://bitbucket.org/org-name/repo-name/src/master/templates/my-template.yaml', - config, - ), - ).toEqual( - 'https://api.bitbucket.org/2.0/repositories/org-name/repo-name/src/master/templates/my-template.yaml', - ); - }); - - it('happy path for Bitbucket Server', () => { - const config: BitbucketIntegrationConfig = { - host: 'bitbucket.mycompany.net', - apiBaseUrl: 'https://bitbucket.mycompany.net/rest/api/1.0', - }; - expect( - getBitbucketFileFetchUrl( - 'https://bitbucket.mycompany.net/projects/a/repos/b/browse/path/to/c.yaml', - config, - ), - ).toEqual( - 'https://bitbucket.mycompany.net/rest/api/1.0/projects/a/repos/b/raw/path/to/c.yaml?at=', - ); - }); - }); - - describe('getBitbucketDownloadUrl', () => { - it('add path param if a path is specified for Bitbucket Server', async () => { - const defaultBranchResponse = { - displayId: 'main', - }; - worker.use( - rest.get( - 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/default-branch', - (_, res, ctx) => - res( - ctx.status(200), - ctx.set('Content-Type', 'application/json'), - ctx.json(defaultBranchResponse), - ), - ), - ); - - const config: BitbucketIntegrationConfig = { - host: 'bitbucket.mycompany.net', - apiBaseUrl: 'https://api.bitbucket.mycompany.net/rest/api/1.0', - }; - const result = await getBitbucketDownloadUrl( - 'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse/docs', - config, - ); - expect(result).toEqual( - 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/archive?format=tgz&at=main&prefix=backstage-mock&path=docs', - ); - }); - - it('does not double encode the filepath', async () => { - const config: BitbucketIntegrationConfig = { - host: 'bitbucket.mycompany.net', - apiBaseUrl: 'https://api.bitbucket.mycompany.net/rest/api/1.0', - }; - const result = await getBitbucketDownloadUrl( - 'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse/%2Fdocs?at=some-branch', - config, - ); - expect(result).toEqual( - 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/archive?format=tgz&at=some-branch&prefix=backstage-mock&path=%2Fdocs', - ); - }); - - it('do not add path param if no path is specified for Bitbucket Server', async () => { - const defaultBranchResponse = { - displayId: 'main', - }; - worker.use( - rest.get( - 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/default-branch', - (_, res, ctx) => - res( - ctx.status(200), - ctx.set('Content-Type', 'application/json'), - ctx.json(defaultBranchResponse), - ), - ), - ); - const config: BitbucketIntegrationConfig = { - host: 'bitbucket.mycompany.net', - apiBaseUrl: 'https://api.bitbucket.mycompany.net/rest/api/1.0', - }; - const result = await getBitbucketDownloadUrl( - 'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse', - config, - ); - - expect(result).toEqual( - 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/archive?format=tgz&at=main&prefix=backstage-mock', - ); - }); - - it('get by branch for Bitbucket Server', async () => { - const config: BitbucketIntegrationConfig = { - host: 'bitbucket.mycompany.net', - apiBaseUrl: 'https://api.bitbucket.mycompany.net/rest/api/1.0', - }; - const result = await getBitbucketDownloadUrl( - 'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse/docs?at=some-branch', - config, - ); - expect(result).toEqual( - 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/archive?format=tgz&at=some-branch&prefix=backstage-mock&path=docs', - ); - }); - - it('do not add path param for Bitbucket Cloud', async () => { - const config: BitbucketIntegrationConfig = { - host: 'bitbucket.org', - apiBaseUrl: 'https://api.bitbucket.org/2.0', - }; - const result = await getBitbucketDownloadUrl( - 'https://bitbucket.org/backstage/mock/src/master', - config, - ); - expect(result).toEqual( - 'https://bitbucket.org/backstage/mock/get/master.tar.gz', - ); - }); - }); - - describe('getBitbucketDefaultBranch', () => { - it('return default branch for Bitbucket Cloud', async () => { - const repoInfoResponse = { - mainbranch: { - name: 'main', - }, - }; - worker.use( - rest.get( - 'https://api.bitbucket.org/2.0/repositories/backstage/mock', - (_, res, ctx) => - res( - ctx.status(200), - ctx.set('Content-Type', 'application/json'), - ctx.json(repoInfoResponse), - ), - ), - ); - const config: BitbucketIntegrationConfig = { - host: 'bitbucket.org', - apiBaseUrl: 'https://api.bitbucket.org/2.0', - }; - const defaultBranch = await getBitbucketDefaultBranch( - 'https://bitbucket.org/backstage/mock/src/main', - config, - ); - expect(defaultBranch).toEqual('main'); - }); - - it('return default branch for Bitbucket Server', async () => { - const defaultBranchResponse = { - displayId: 'main', - }; - worker.use( - rest.get( - 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/default-branch', - (_, res, ctx) => - res( - ctx.status(200), - ctx.set('Content-Type', 'application/json'), - ctx.json(defaultBranchResponse), - ), - ), - ); - const config: BitbucketIntegrationConfig = { - host: 'bitbucket.mycompany.net', - apiBaseUrl: 'https://api.bitbucket.mycompany.net/rest/api/1.0', - }; - const defaultBranch = await getBitbucketDefaultBranch( - 'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse/README.md', - config, - ); - expect(defaultBranch).toEqual('main'); - }); - - it('return default branch for Bitbucket Server for bitbucket version 5.11', async () => { - const defaultBranchResponse = { - displayId: 'main', - }; - worker.use( - rest.get( - 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/default-branch', - (_, res, ctx) => - res( - ctx.status(404), - ctx.set('Content-Type', 'application/json'), - ctx.json(defaultBranchResponse), - ), - ), - rest.get( - 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/branches/default', - (_, res, ctx) => - res( - ctx.status(200), - ctx.set('Content-Type', 'application/json'), - ctx.json(defaultBranchResponse), - ), - ), - ); - const config: BitbucketIntegrationConfig = { - host: 'bitbucket.mycompany.net', - apiBaseUrl: 'https://api.bitbucket.mycompany.net/rest/api/1.0', - }; - const defaultBranch = await getBitbucketDefaultBranch( - 'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse/README.md', - config, - ); - expect(defaultBranch).toEqual('main'); - }); - }); -}); diff --git a/packages/integration/src/bitbucket/core.ts b/packages/integration/src/bitbucket/core.ts deleted file mode 100644 index 5c5533155d..0000000000 --- a/packages/integration/src/bitbucket/core.ts +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import fetch from 'cross-fetch'; -import parseGitUrl from 'git-url-parse'; -import { BitbucketIntegrationConfig } from './config'; - -/** - * Given a URL pointing to a path on a provider, returns the default branch. - * - * @param url - A URL pointing to a path - * @param config - The relevant provider config - * @public - * @deprecated no longer in use, bitbucket integration replaced by integrations bitbucketCloud and bitbucketServer. - */ -export async function getBitbucketDefaultBranch( - url: string, - config: BitbucketIntegrationConfig, -): Promise { - const { name: repoName, owner: project, resource } = parseGitUrl(url); - - const isHosted = resource === 'bitbucket.org'; - // Bitbucket Server https://docs.atlassian.com/bitbucket-server/rest/7.9.0/bitbucket-rest.html#idp184 - let branchUrl = isHosted - ? `${config.apiBaseUrl}/repositories/${project}/${repoName}` - : `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/default-branch`; - - let response = await fetch(branchUrl, getBitbucketRequestOptions(config)); - - if (response.status === 404 && !isHosted) { - // First try the new format, and then if it gets specifically a 404 it should try the old format - // (to support old Atlassian Bitbucket v5.11.1 format ) - branchUrl = `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/branches/default`; - response = await fetch(branchUrl, getBitbucketRequestOptions(config)); - } - - if (!response.ok) { - const message = `Failed to retrieve default branch from ${branchUrl}, ${response.status} ${response.statusText}`; - throw new Error(message); - } - - let defaultBranch; - if (isHosted) { - const repoInfo = await response.json(); - defaultBranch = repoInfo.mainbranch.name; - } else { - const { displayId } = await response.json(); - defaultBranch = displayId; - } - if (!defaultBranch) { - throw new Error( - `Failed to read default branch from ${branchUrl}. ` + - `Response ${response.status} ${response.json()}`, - ); - } - return defaultBranch; -} - -/** - * Given a URL pointing to a path on a provider, returns a URL that is suitable - * for downloading the subtree. - * - * @param url - A URL pointing to a path - * @param config - The relevant provider config - * @public - * @deprecated no longer in use, bitbucket integration replaced by integrations bitbucketCloud and bitbucketServer. - */ -export async function getBitbucketDownloadUrl( - url: string, - config: BitbucketIntegrationConfig, -): Promise { - const { - name: repoName, - owner: project, - ref, - protocol, - resource, - filepath, - } = parseGitUrl(url); - - const isHosted = resource === 'bitbucket.org'; - - let branch = ref; - if (!branch) { - branch = await getBitbucketDefaultBranch(url, config); - } - // path will limit the downloaded content - // /docs will only download the docs folder and everything below it - // /docs/index.md will download the docs folder and everything below it - const path = filepath - ? `&path=${encodeURIComponent(decodeURIComponent(filepath))}` - : ''; - const archiveUrl = isHosted - ? `${protocol}://${resource}/${project}/${repoName}/get/${branch}.tar.gz` - : `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/archive?format=tgz&at=${branch}&prefix=${project}-${repoName}${path}`; - - return archiveUrl; -} - -/** - * Given a URL pointing to a file on a provider, returns a URL that is suitable - * for fetching the contents of the data. - * - * @remarks - * - * Converts - * from: https://bitbucket.org/orgname/reponame/src/master/file.yaml - * to: https://api.bitbucket.org/2.0/repositories/orgname/reponame/src/master/file.yaml - * - * @param url - A URL pointing to a file - * @param config - The relevant provider config - * @public - * @deprecated no longer in use, bitbucket integration replaced by integrations bitbucketCloud and bitbucketServer. - */ -export function getBitbucketFileFetchUrl( - url: string, - config: BitbucketIntegrationConfig, -): string { - try { - const { owner, name, ref, filepathtype, filepath } = parseGitUrl(url); - if ( - !owner || - !name || - (filepathtype !== 'browse' && - filepathtype !== 'raw' && - filepathtype !== 'src') - ) { - throw new Error('Invalid Bitbucket URL or file path'); - } - - const pathWithoutSlash = filepath.replace(/^\//, ''); - - if (config.host === 'bitbucket.org') { - if (!ref) { - throw new Error('Invalid Bitbucket URL or file path'); - } - return `${config.apiBaseUrl}/repositories/${owner}/${name}/src/${ref}/${pathWithoutSlash}`; - } - return `${config.apiBaseUrl}/projects/${owner}/repos/${name}/raw/${pathWithoutSlash}?at=${ref}`; - } catch (e) { - throw new Error(`Incorrect URL: ${url}, ${e}`); - } -} - -/** - * Gets the request options necessary to make requests to a given provider. - * - * @param config - The relevant provider config - * @public - * @deprecated no longer in use, bitbucket integration replaced by integrations bitbucketCloud and bitbucketServer. - */ -export function getBitbucketRequestOptions( - config: BitbucketIntegrationConfig, -): { headers: Record } { - const headers: Record = {}; - - if (config.token) { - headers.Authorization = `Bearer ${config.token}`; - } else if (config.username && config.appPassword) { - const buffer = Buffer.from( - `${config.username}:${config.appPassword}`, - 'utf8', - ); - headers.Authorization = `Basic ${buffer.toString('base64')}`; - } - - return { - headers, - }; -} diff --git a/packages/integration/src/bitbucket/index.ts b/packages/integration/src/bitbucket/index.ts deleted file mode 100644 index 356e760fd8..0000000000 --- a/packages/integration/src/bitbucket/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { BitbucketIntegration } from './BitbucketIntegration'; -export { - readBitbucketIntegrationConfig, - readBitbucketIntegrationConfigs, -} from './config'; -export type { BitbucketIntegrationConfig } from './config'; -export { - getBitbucketDefaultBranch, - getBitbucketDownloadUrl, - getBitbucketFileFetchUrl, - getBitbucketRequestOptions, -} from './core'; diff --git a/packages/integration/src/bitbucketCloud/config.test.ts b/packages/integration/src/bitbucketCloud/config.test.ts index 2c68059196..57e8803a14 100644 --- a/packages/integration/src/bitbucketCloud/config.test.ts +++ b/packages/integration/src/bitbucketCloud/config.test.ts @@ -22,70 +22,105 @@ import { readBitbucketCloudIntegrationConfigs, } from './config'; +// Mock constants +const BITBUCKET_CLOUD_HOST = 'bitbucket.org'; +const BITBUCKET_CLOUD_API_BASE_URL = 'https://api.bitbucket.org/2.0'; + +async function buildFrontendConfig( + data: Partial, +): Promise { + const fullSchema = await loadConfigSchema({ + dependencies: ['@backstage/integration'], + }); + const serializedSchema = fullSchema.serialize() as { + schemas: { value: { properties?: { integrations?: object } } }[]; + }; + const schema = await loadConfigSchema({ + serialized: { + ...serializedSchema, // only include schemas that apply to integrations + schemas: serializedSchema.schemas.filter( + s => s.value?.properties?.integrations, + ), + }, + }); + const processed = schema.process( + [{ data: { integrations: { bitbucketCloud: [data] } }, context: 'app' }], + { visibility: ['frontend'] }, + ); + return new ConfigReader(processed[0].data as any); +} + describe('readBitbucketCloudIntegrationConfig', () => { function buildConfig(data: Partial): Config { return new ConfigReader(data); } - async function buildFrontendConfig( - data: Partial, - ): Promise { - const fullSchema = await loadConfigSchema({ - dependencies: ['@backstage/integration'], - }); - const serializedSchema = fullSchema.serialize() as { - schemas: { value: { properties?: { integrations?: object } } }[]; - }; - const schema = await loadConfigSchema({ - serialized: { - ...serializedSchema, // only include schemas that apply to integrations - schemas: serializedSchema.schemas.filter( - s => s.value?.properties?.integrations, - ), - }, - }); - const processed = schema.process( - [{ data: { integrations: { bitbucketCloud: [data] } }, context: 'app' }], - { visibility: ['frontend'] }, - ); - return new ConfigReader(processed[0].data as any); - } - it('reads all values', () => { const output = readBitbucketCloudIntegrationConfig( buildConfig({ username: 'u', - appPassword: '\n\n\np', + token: 't', }), ); expect(output).toEqual({ - apiBaseUrl: 'https://api.bitbucket.org/2.0', - appPassword: 'p', - host: 'bitbucket.org', + host: BITBUCKET_CLOUD_HOST, + apiBaseUrl: BITBUCKET_CLOUD_API_BASE_URL, username: 'u', + token: 't', }); }); it('rejects funky configs', () => { const valid: any = { username: 'u', - appPassword: 'p', + token: 't', }; expect(() => readBitbucketCloudIntegrationConfig( buildConfig({ ...valid, username: 7 }), ), ).toThrow(/username/); + expect(() => + readBitbucketCloudIntegrationConfig(buildConfig({ ...valid, token: 7 })), + ).toThrow(/token/); + }); + + it('reads OAuth configuration', () => { + const output = readBitbucketCloudIntegrationConfig( + buildConfig({ + clientId: 'my-client-id', + clientSecret: 'my-client-secret', + }), + ); + expect(output).toEqual({ + host: BITBUCKET_CLOUD_HOST, + apiBaseUrl: BITBUCKET_CLOUD_API_BASE_URL, + clientId: 'my-client-id', + clientSecret: 'my-client-secret', + }); + }); + + it('rejects incomplete OAuth configuration', () => { expect(() => readBitbucketCloudIntegrationConfig( - buildConfig({ ...valid, appPassword: 7 }), + buildConfig({ + clientId: 'my-client-id', + }), ), - ).toThrow(/appPassword/); + ).toThrow(/incomplete OAuth configuration/); + + expect(() => + readBitbucketCloudIntegrationConfig( + buildConfig({ + clientSecret: 'my-client-secret', + }), + ), + ).toThrow(/incomplete OAuth configuration/); }); it('credentials hidden on the frontend', async () => { const frontendConfig = await buildFrontendConfig({ - appPassword: 'p', + token: 't', username: 'u', }); expect( @@ -95,11 +130,94 @@ describe('readBitbucketCloudIntegrationConfig', () => { ), ).toEqual([ { - apiBaseUrl: 'https://api.bitbucket.org/2.0', - host: 'bitbucket.org', + host: BITBUCKET_CLOUD_HOST, + apiBaseUrl: BITBUCKET_CLOUD_API_BASE_URL, }, ]); }); + + it('OAuth credentials hidden on the frontend', async () => { + const frontendConfig = await buildFrontendConfig({ + clientId: 'my-client-id', + clientSecret: 'my-client-secret', + }); + expect( + readBitbucketCloudIntegrationConfigs( + frontendConfig.getOptionalConfigArray('integrations.bitbucketCloud') ?? + [], + ), + ).toEqual([ + { + host: BITBUCKET_CLOUD_HOST, + apiBaseUrl: BITBUCKET_CLOUD_API_BASE_URL, + }, + ]); + }); + + // TODO: appPassword can be removed once fully + // deprecated by BitBucket on 9th June 2026. + describe('handles deprecated appPassword', () => { + it('reads all values', () => { + const output = readBitbucketCloudIntegrationConfig( + buildConfig({ + appPassword: '\n\np', + username: 'u', + }), + ); + expect(output).toEqual({ + host: BITBUCKET_CLOUD_HOST, + apiBaseUrl: BITBUCKET_CLOUD_API_BASE_URL, + appPassword: 'p', + username: 'u', + }); + }); + + it('rejects funky configs', () => { + const valid: any = { + appPassword: 'p', + username: 'u', + }; + expect(() => + readBitbucketCloudIntegrationConfig( + buildConfig({ ...valid, appPassword: 7 }), + ), + ).toThrow(/appPassword/); + }); + + it('rejects if misconfigured', () => { + const valid: any = { + appPassword: 'p', + token: 't', + username: 'u', + }; + expect(() => + readBitbucketCloudIntegrationConfig( + buildConfig({ ...valid, appPassword: undefined, token: undefined }), + ), + ).toThrow( + /must be configured with as username and either a token or an appPassword/, + ); + }); + + it('credentials hidden on the frontend', async () => { + const frontendConfig = await buildFrontendConfig({ + appPassword: 'p', + username: 'u', + }); + expect( + readBitbucketCloudIntegrationConfigs( + frontendConfig.getOptionalConfigArray( + 'integrations.bitbucketCloud', + ) ?? [], + ), + ).toEqual([ + { + host: BITBUCKET_CLOUD_HOST, + apiBaseUrl: BITBUCKET_CLOUD_API_BASE_URL, + }, + ]); + }); + }); }); describe('readBitbucketCloudIntegrationConfigs', () => { @@ -114,15 +232,15 @@ describe('readBitbucketCloudIntegrationConfigs', () => { buildConfig([ { username: 'u', - appPassword: 'p', + token: 't', }, ]), ); expect(output).toContainEqual({ apiBaseUrl: 'https://api.bitbucket.org/2.0', - appPassword: 'p', host: 'bitbucket.org', username: 'u', + token: 't', }); }); diff --git a/packages/integration/src/bitbucketCloud/config.ts b/packages/integration/src/bitbucketCloud/config.ts index eb4b5c4904..e47b215f98 100644 --- a/packages/integration/src/bitbucketCloud/config.ts +++ b/packages/integration/src/bitbucketCloud/config.ts @@ -49,9 +49,25 @@ export type BitbucketCloudIntegrationConfig = { /** * The access token to use for requests to Bitbucket Cloud (bitbucket.org). + * + * See https://support.atlassian.com/bitbucket-cloud/docs/api-tokens/ */ token?: string; + /** + * The OAuth client ID for Bitbucket Cloud. + * + * See https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/ + */ + clientId?: string; + + /** + * The OAuth client secret for Bitbucket Cloud. + * + * See https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/ + */ + clientSecret?: string; + /** PGP private key for signing commits. */ commitSigningKey?: string; }; @@ -69,14 +85,36 @@ export function readBitbucketCloudIntegrationConfig( const apiBaseUrl = BITBUCKET_CLOUD_API_BASE_URL; // If config is provided, we assume authenticated access is desired // (as the anonymous one is provided by default). - const username = config.getString('username'); - const appPassword = config.getString('appPassword')?.trim(); + const username = config.getOptionalString('username'); + // TODO: appPassword can be removed once fully + // deprecated by BitBucket on 9th June 2026. + const appPassword = config.getOptionalString('appPassword')?.trim(); + const token = config.getOptionalString('token'); + const clientId = config.getOptionalString('clientId')?.trim(); + const clientSecret = config.getOptionalString('clientSecret')?.trim(); + + // Validate: if username is provided, token or appPassword is required + if (username && !token && !appPassword) { + throw new Error( + `Bitbucket Cloud integration must be configured with as username and either a token or an appPassword.`, + ); + } + + // Validate: OAuth requires both clientId and clientSecret + if ((clientId && !clientSecret) || (clientSecret && !clientId)) { + throw new Error( + `Bitbucket Cloud integration has incomplete OAuth configuration. Both clientId and clientSecret are required.`, + ); + } return { host, apiBaseUrl, username, appPassword, + token, + clientId, + clientSecret, commitSigningKey: config.getOptionalString('commitSigningKey'), }; } diff --git a/packages/integration/src/bitbucketCloud/core.test.ts b/packages/integration/src/bitbucketCloud/core.test.ts index 56a53b924d..cbd5c0588d 100644 --- a/packages/integration/src/bitbucketCloud/core.test.ts +++ b/packages/integration/src/bitbucketCloud/core.test.ts @@ -16,7 +16,7 @@ import { rest } from 'msw'; import { setupServer } from 'msw/node'; -import { registerMswTestHooks } from '../helpers'; +import { registerMswTestHooks } from '@backstage/backend-test-utils'; import { BitbucketCloudIntegrationConfig } from './config'; import { getBitbucketCloudDefaultBranch, @@ -25,35 +25,103 @@ import { getBitbucketCloudRequestOptions, } from './core'; +// Mock constants +const BITBUCKET_CLOUD_HOST = 'bitbucket.org'; +const BITBUCKET_CLOUD_API_BASE_URL = 'https://api.bitbucket.org/2.0'; +const BITBUCKET_CLOUD_OAUTH_TOKEN_URL = + 'https://bitbucket.org/site/oauth2/access_token'; + describe('bitbucketCloud core', () => { const worker = setupServer(); registerMswTestHooks(worker); describe('getBitbucketCloudRequestOptions', () => { - it('insert basic auth when needed', () => { + it('insert basic auth when needed', async () => { + const withUsernameAndToken: BitbucketCloudIntegrationConfig = { + host: BITBUCKET_CLOUD_HOST, + apiBaseUrl: BITBUCKET_CLOUD_API_BASE_URL, + username: 'some-user@domain.com', + token: 'my-token', + }; + // TODO: appPassword can be removed once fully + // deprecated by BitBucket on 9th June 2026. const withUsernameAndPassword: BitbucketCloudIntegrationConfig = { - host: 'bitbucket.org', - apiBaseUrl: 'https://api.bitbucket.org/2.0', + host: BITBUCKET_CLOUD_HOST, + apiBaseUrl: BITBUCKET_CLOUD_API_BASE_URL, username: 'some-user', appPassword: 'my-secret', }; - const withoutUsernameAndPassword: BitbucketCloudIntegrationConfig = { - host: 'bitbucket.org', - apiBaseUrl: 'https://api.bitbucket.org/2.0', + const withoutUsername: BitbucketCloudIntegrationConfig = { + host: BITBUCKET_CLOUD_HOST, + apiBaseUrl: BITBUCKET_CLOUD_API_BASE_URL, }; expect( - ( - getBitbucketCloudRequestOptions(withUsernameAndPassword) - .headers as any - ).Authorization, + (await getBitbucketCloudRequestOptions(withUsernameAndToken)).headers + .Authorization, + ).toEqual('Basic c29tZS11c2VyQGRvbWFpbi5jb206bXktdG9rZW4='); + expect( + (await getBitbucketCloudRequestOptions(withUsernameAndPassword)).headers + .Authorization, ).toEqual('Basic c29tZS11c2VyOm15LXNlY3JldA=='); expect( - ( - getBitbucketCloudRequestOptions(withoutUsernameAndPassword) - .headers as any - ).Authorization, + (await getBitbucketCloudRequestOptions(withoutUsername)).headers + .Authorization, ).toBeUndefined(); }); + + it('handles OAuth token fetch errors', async () => { + // Test error handling + worker.use( + rest.post(BITBUCKET_CLOUD_OAUTH_TOKEN_URL, (_, res, ctx) => + res(ctx.status(401), ctx.json({ error: 'invalid_client' })), + ), + ); + + const withOAuth: BitbucketCloudIntegrationConfig = { + host: BITBUCKET_CLOUD_HOST, + apiBaseUrl: BITBUCKET_CLOUD_API_BASE_URL, + clientId: 'test-client-id', + clientSecret: 'test-client-secret', + }; + + await expect(getBitbucketCloudRequestOptions(withOAuth)).rejects.toThrow( + /Failed to fetch OAuth token/, + ); + }); + + it('uses OAuth Bearer token and caches it', async () => { + // Test OAuth + caching + let callCount = 0; + worker.use( + rest.post(BITBUCKET_CLOUD_OAUTH_TOKEN_URL, (_, res, ctx) => { + callCount++; + return res( + ctx.status(200), + ctx.json({ + access_token: 'test-oauth-token', + expires_in: 3600, + }), + ); + }), + ); + + const withOAuth: BitbucketCloudIntegrationConfig = { + host: BITBUCKET_CLOUD_HOST, + apiBaseUrl: BITBUCKET_CLOUD_API_BASE_URL, + clientId: 'test-client-id', + clientSecret: 'test-client-secret', + }; + + // First call should fetch token + const result1 = await getBitbucketCloudRequestOptions(withOAuth); + expect(result1.headers.Authorization).toEqual('Bearer test-oauth-token'); + expect(callCount).toBe(1); + + // Second call should use cached token (proves caching works) + const result2 = await getBitbucketCloudRequestOptions(withOAuth); + expect(result2.headers.Authorization).toEqual('Bearer test-oauth-token'); + expect(callCount).toBe(1); // Still 1, proving cache was used + }); }); describe('getBitbucketCloudFileFetchUrl', () => { diff --git a/packages/integration/src/bitbucketCloud/core.ts b/packages/integration/src/bitbucketCloud/core.ts index 37feb5dad6..b1f95ad2d0 100644 --- a/packages/integration/src/bitbucketCloud/core.ts +++ b/packages/integration/src/bitbucketCloud/core.ts @@ -16,7 +16,102 @@ import fetch from 'cross-fetch'; import parseGitUrl from 'git-url-parse'; +import { parseGitUrlSafe } from '../helpers'; import { BitbucketCloudIntegrationConfig } from './config'; +import { DateTime } from 'luxon'; + +type OAuthTokenData = { + token: string; + expiresAt: DateTime; +}; + +// In-memory token cache (single entry since there's only one Bitbucket Cloud integration) +let cachedToken: OAuthTokenData | undefined; + +// Track in-flight token refresh request to prevent concurrent fetches +let refreshPromise: Promise | undefined; + +/** + * Fetches an OAuth access token from Bitbucket Cloud using client credentials flow. + * Tokens are cached with a 10-minute grace period to account for clock skew. + * Implements concurrent refresh protection to prevent multiple simultaneous token requests. + * + * @param clientId - OAuth client ID + * @param clientSecret - OAuth client secret + * @public + */ +export async function getBitbucketCloudOAuthToken( + clientId: string, + clientSecret: string, +): Promise { + // Check cache + if (cachedToken && DateTime.now() < cachedToken.expiresAt) { + return cachedToken.token; + } + + // Check if there's already a refresh in progress + if (refreshPromise) { + return refreshPromise; + } + + // Start a new token fetch and track it + refreshPromise = (async () => { + try { + // Fetch new token + const credentials = Buffer.from( + `${clientId}:${clientSecret}`, + 'utf8', + ).toString('base64'); + + const response = await fetch( + 'https://bitbucket.org/site/oauth2/access_token', + { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Authorization: `Basic ${credentials}`, + }, + body: 'grant_type=client_credentials', + }, + ); + + if (!response.ok) { + throw new Error( + `Failed to fetch OAuth token from Bitbucket Cloud: ${response.status} ${response.statusText}`, + ); + } + + const data = await response.json(); + + if (!data.access_token) { + throw new Error('OAuth token response missing access_token field'); + } + + // Calculate expiration with 10-minute grace period + const expiresIn = data.expires_in || 3600; // Default to 1 hour + const expiresAt = DateTime.now() + .plus({ seconds: expiresIn }) + .minus({ minutes: 10 }); + + // Cache the token + cachedToken = { + token: data.access_token, + expiresAt, + }; + + return data.access_token; + } catch (error) { + throw new Error( + `Failed to fetch OAuth token for Bitbucket Cloud: ${error}`, + ); + } finally { + // Clean up the in-flight promise tracking + refreshPromise = undefined; + } + })(); + + return refreshPromise; +} /** * Given a URL pointing to a path on a provider, returns the default branch. @@ -34,7 +129,7 @@ export async function getBitbucketCloudDefaultBranch( const branchUrl = `${config.apiBaseUrl}/repositories/${project}/${repoName}`; const response = await fetch( branchUrl, - getBitbucketCloudRequestOptions(config), + await getBitbucketCloudRequestOptions(config), ); if (!response.ok) { @@ -71,7 +166,7 @@ export async function getBitbucketCloudDownloadUrl( ref, protocol, resource, - } = parseGitUrl(url); + } = parseGitUrlSafe(url); let branch = ref; if (!branch) { @@ -99,7 +194,7 @@ export function getBitbucketCloudFileFetchUrl( config: BitbucketCloudIntegrationConfig, ): string { try { - const { owner, name, ref, filepathtype, filepath } = parseGitUrl(url); + const { owner, name, ref, filepathtype, filepath } = parseGitUrlSafe(url); if (!owner || !name || (filepathtype !== 'src' && filepathtype !== 'raw')) { throw new Error('Invalid Bitbucket Cloud URL or file path'); } @@ -117,24 +212,39 @@ export function getBitbucketCloudFileFetchUrl( /** * Gets the request options necessary to make requests to a given provider. + * Returns headers for authenticating with Bitbucket Cloud. + * Supports OAuth (clientId/clientSecret), username/token, and username/appPassword auth. * * @param config - The relevant provider config * @public */ -export function getBitbucketCloudRequestOptions( +export async function getBitbucketCloudRequestOptions( config: BitbucketCloudIntegrationConfig, -): { headers: Record } { +): Promise<{ + headers: Record; +}> { const headers: Record = {}; - if (config.username && config.appPassword) { + // OAuth authentication (clientId/clientSecret) + if (config.clientId && config.clientSecret) { + const token = await getBitbucketCloudOAuthToken( + config.clientId, + config.clientSecret, + ); + headers.Authorization = `Bearer ${token}`; + return { headers }; + } + + // Basic authentication (username + token/appPassword) + // TODO: appPassword can be removed once fully + // deprecated by BitBucket on 9th June 2026. + if (config.username && (config.token ?? config.appPassword)) { const buffer = Buffer.from( - `${config.username}:${config.appPassword}`, + `${config.username}:${config.token ?? config.appPassword}`, 'utf8', ); headers.Authorization = `Basic ${buffer.toString('base64')}`; } - return { - headers, - }; + return { headers }; } diff --git a/packages/integration/src/bitbucketCloud/index.ts b/packages/integration/src/bitbucketCloud/index.ts index 7119d3ea90..d0c11136b3 100644 --- a/packages/integration/src/bitbucketCloud/index.ts +++ b/packages/integration/src/bitbucketCloud/index.ts @@ -24,5 +24,6 @@ export { getBitbucketCloudDefaultBranch, getBitbucketCloudDownloadUrl, getBitbucketCloudFileFetchUrl, + getBitbucketCloudOAuthToken, getBitbucketCloudRequestOptions, } from './core'; diff --git a/packages/integration/src/bitbucketServer/core.test.ts b/packages/integration/src/bitbucketServer/core.test.ts index c1c72b38dd..08deba7c34 100644 --- a/packages/integration/src/bitbucketServer/core.test.ts +++ b/packages/integration/src/bitbucketServer/core.test.ts @@ -16,7 +16,7 @@ import { rest } from 'msw'; import { setupServer } from 'msw/node'; -import { registerMswTestHooks } from '../helpers'; +import { registerMswTestHooks } from '@backstage/backend-test-utils'; import { BitbucketServerIntegrationConfig } from './config'; import { getBitbucketServerDefaultBranch, diff --git a/packages/integration/src/bitbucketServer/core.ts b/packages/integration/src/bitbucketServer/core.ts index 8741b8f0e7..5d042d5f02 100644 --- a/packages/integration/src/bitbucketServer/core.ts +++ b/packages/integration/src/bitbucketServer/core.ts @@ -16,6 +16,7 @@ import fetch from 'cross-fetch'; import parseGitUrl from 'git-url-parse'; +import { parseGitUrlSafe } from '../helpers'; import { BitbucketServerIntegrationConfig } from './config'; /** @@ -74,7 +75,12 @@ export async function getBitbucketServerDownloadUrl( url: string, config: BitbucketServerIntegrationConfig, ): Promise { - const { name: repoName, owner: project, ref, filepath } = parseGitUrl(url); + const { + name: repoName, + owner: project, + ref, + filepath, + } = parseGitUrlSafe(url); let branch = ref; if (!branch) { @@ -108,7 +114,7 @@ export function getBitbucketServerFileFetchUrl( config: BitbucketServerIntegrationConfig, ): string { try { - const { owner, name, ref, filepathtype, filepath } = parseGitUrl(url); + const { owner, name, ref, filepathtype, filepath } = parseGitUrlSafe(url); if ( !owner || !name || diff --git a/packages/integration/src/gerrit/core.test.ts b/packages/integration/src/gerrit/core.test.ts index 5899196f65..bc5b8c4f53 100644 --- a/packages/integration/src/gerrit/core.test.ts +++ b/packages/integration/src/gerrit/core.test.ts @@ -17,10 +17,9 @@ import { rest } from 'msw'; import { setupServer } from 'msw/node'; import fetch from 'cross-fetch'; -import { registerMswTestHooks } from '../helpers'; +import { registerMswTestHooks } from '@backstage/backend-test-utils'; import { GerritIntegrationConfig } from './config'; import { - buildGerritGitilesArchiveUrl, buildGerritGitilesArchiveUrlFromLocation, buildGerritGitilesUrl, getGerritBranchApiUrl, @@ -28,7 +27,6 @@ import { getGerritRequestOptions, parseGerritJsonResponse, parseGitilesUrlRef, - parseGerritGitilesUrl, getGerritFileContentsApiUrl, } from './core'; @@ -36,86 +34,6 @@ describe('gerrit core', () => { const worker = setupServer(); registerMswTestHooks(worker); - describe('buildGerritGitilesArchiveUrl', () => { - const config: GerritIntegrationConfig = { - host: 'gerrit.com', - baseUrl: 'https://gerrit.com', - gitilesBaseUrl: 'https://gerrit.com/gitiles', - }; - const configWithPath: GerritIntegrationConfig = { - host: 'gerrit.com', - baseUrl: 'https://gerrit.com/gerrit', - gitilesBaseUrl: 'https://gerrit.com/gerrit/plugins/gitiles', - }; - const configWithDedicatedGitiles: GerritIntegrationConfig = { - host: 'gerrit.com', - baseUrl: 'https://gerrit.com/gerrit', - gitilesBaseUrl: 'https://dedicated-gitiles-server.com/gerrit/gitiles', - }; - it('can create an archive url for a branch', () => { - expect(buildGerritGitilesArchiveUrl(config, 'repo', 'dev', '')).toEqual( - 'https://gerrit.com/gitiles/repo/+archive/refs/heads/dev.tar.gz', - ); - - expect(buildGerritGitilesArchiveUrl(config, 'repo', 'dev', '/')).toEqual( - 'https://gerrit.com/gitiles/repo/+archive/refs/heads/dev.tar.gz', - ); - }); - it('can create an archive url for a specific directory', () => { - expect( - buildGerritGitilesArchiveUrl(config, 'repo', 'dev', 'docs'), - ).toEqual( - 'https://gerrit.com/gitiles/repo/+archive/refs/heads/dev/docs.tar.gz', - ); - }); - it('can create an authenticated url when auth is enabled', () => { - const authConfig = { - ...config, - username: 'username', - password: 'password', - }; - expect( - buildGerritGitilesArchiveUrl(authConfig, 'repo', 'dev', 'docs'), - ).toEqual( - 'https://gerrit.com/a/gitiles/repo/+archive/refs/heads/dev/docs.tar.gz', - ); - }); - it('can create an authenticated url when auth is enabled and an url-path is used', () => { - const authConfig = { - ...configWithPath, - username: 'username', - password: 'password', - }; - expect( - buildGerritGitilesArchiveUrl(authConfig, 'repo', 'dev', 'docs'), - ).toEqual( - 'https://gerrit.com/gerrit/a/plugins/gitiles/repo/+archive/refs/heads/dev/docs.tar.gz', - ); - }); - it('Cannot build an authenticated url when a dedicated Gitiles server is used', () => { - const authConfig = { - ...configWithDedicatedGitiles, - username: 'username', - password: 'password', - }; - expect(() => - buildGerritGitilesArchiveUrl(authConfig, 'repo', 'dev', 'docs'), - ).toThrow( - 'Since the baseUrl (Gerrit) is not part of the gitilesBaseUrl, an authentication URL could not be constructed.', - ); - }); - it('Build a non-authenticated url when a dedicated Gitiles server is used', () => { - const authConfig = { - ...configWithDedicatedGitiles, - }; - expect( - buildGerritGitilesArchiveUrl(authConfig, 'repo', 'dev', 'docs'), - ).toEqual( - 'https://dedicated-gitiles-server.com/gerrit/gitiles/repo/+archive/refs/heads/dev/docs.tar.gz', - ); - }); - }); - describe('buildGerritGitilesArchiveUrlFromLocation', () => { const config: GerritIntegrationConfig = { host: 'gerrit.com', @@ -238,6 +156,7 @@ describe('gerrit core', () => { ).toBeUndefined(); }); }); + describe('parseGitilesUrlRef', () => { const config: GerritIntegrationConfig = { host: 'gerrit.com', @@ -360,64 +279,6 @@ describe('gerrit core', () => { }); }); }); - describe('parseGerritGitilesUrl', () => { - it('can parse a valid gitiles urls.', () => { - const config: GerritIntegrationConfig = { - host: 'gerrit.com', - gitilesBaseUrl: 'https://gerrit.com/gitiles', - }; - const { branch, filePath, project } = parseGerritGitilesUrl( - config, - 'https://gerrit.com/gitiles/web/project/+/refs/heads/master/README.md', - ); - expect(project).toEqual('web/project'); - expect(branch).toEqual('master'); - expect(filePath).toEqual('README.md'); - - const { filePath: rootPath } = parseGerritGitilesUrl( - config, - 'https://gerrit.com/gitiles/web/project/+/refs/heads/master', - ); - expect(rootPath).toEqual('/'); - }); - it('can parse a valid authenticated gitiles url.', () => { - const config: GerritIntegrationConfig = { - host: 'gerrit.com', - gitilesBaseUrl: 'https://gerrit.com/gitiles', - }; - const { branch, filePath, project } = parseGerritGitilesUrl( - config, - 'https://gerrit.com/a/gitiles/web/project/+/refs/heads/master/README.md', - ); - expect(project).toEqual('web/project'); - expect(branch).toEqual('master'); - expect(filePath).toEqual('README.md'); - - const { filePath: rootPath } = parseGerritGitilesUrl( - config, - 'https://gerrit.com/gitiles/web/project/+/refs/heads/master', - ); - expect(rootPath).toEqual('/'); - }); - it('throws on incorrect gitiles urls.', () => { - const config: GerritIntegrationConfig = { - host: 'gerrit.com', - gitilesBaseUrl: 'https://gerrit.com', - }; - expect(() => - parseGerritGitilesUrl( - config, - 'https://gerrit.com/+/refs/heads/master/README.md', - ), - ).toThrow(/project/); - expect(() => - parseGerritGitilesUrl( - config, - 'https://gerrit.com/web/project/+/refs/changes/1/11/master/README.md', - ), - ).toThrow(/branch/); - }); - }); describe('getGerritBranchApiUrl', () => { it('can create an url for anonymous access.', () => { @@ -450,6 +311,45 @@ describe('gerrit core', () => { 'https://gerrit.com/a/projects/web%2Fproject/branches/master', ); }); + it('throws when ref type is not a branch (tag).', () => { + const config: GerritIntegrationConfig = { + host: 'gerrit.com', + baseUrl: 'https://gerrit.com', + gitilesBaseUrl: 'https://gerrit.com', + }; + expect(() => + getGerritBranchApiUrl( + config, + 'https://gerrit.com/web/project/+/refs/tags/v1.0.0/README.md', + ), + ).toThrow('Unsupported gitiles ref type: tag'); + }); + it('throws when ref type is not a branch (sha).', () => { + const config: GerritIntegrationConfig = { + host: 'gerrit.com', + baseUrl: 'https://gerrit.com', + gitilesBaseUrl: 'https://gerrit.com', + }; + expect(() => + getGerritBranchApiUrl( + config, + 'https://gerrit.com/web/project/+/157f862803d45b9d269f0e390f88aece1ded51e8/README.md', + ), + ).toThrow('Unsupported gitiles ref type: sha'); + }); + it('throws when ref type is not a branch (head).', () => { + const config: GerritIntegrationConfig = { + host: 'gerrit.com', + baseUrl: 'https://gerrit.com', + gitilesBaseUrl: 'https://gerrit.com', + }; + expect(() => + getGerritBranchApiUrl( + config, + 'https://gerrit.com/web/project/+/HEAD/README.md', + ), + ).toThrow('Unsupported gitiles ref type: head'); + }); }); describe('getGerritCloneRepoUrl', () => { diff --git a/packages/integration/src/gerrit/core.ts b/packages/integration/src/gerrit/core.ts index ae6a5baa10..e8db1305ed 100644 --- a/packages/integration/src/gerrit/core.ts +++ b/packages/integration/src/gerrit/core.ts @@ -18,70 +18,6 @@ import { GerritIntegrationConfig } from './config'; const GERRIT_BODY_PREFIX = ")]}'"; -/** - * Parse a Gitiles URL and return branch, file path and project. - * - * @remarks - * - * Gerrit only handles code reviews so it does not have a native way to browse - * or showing the content of gits. Image if Github only had the "pull requests" - * tab. - * - * Any source code browsing is instead handled by optional services outside - * Gerrit. The url format chosen for the Gerrit url reader is the one used by - * the Gitiles project. Gerrit will work perfectly with Backstage without - * having Gitiles installed but there are some places in the Backstage GUI - * with links to the url used by the url reader. These will not work unless - * the urls point to an actual Gitiles installation. - * - * Gitiles url: - * https://g.com/optional_path/\{project\}/+/refs/heads/\{branch\}/\{filePath\} - * https://g.com/a/optional_path/\{project\}/+/refs/heads/\{branch\}/\{filePath\} - * - * - * @param url - An URL pointing to a file stored in git. - * @public - * @deprecated `parseGerritGitilesUrl` is deprecated. Use - * {@link parseGitilesUrlRef} instead. - */ -export function parseGerritGitilesUrl( - config: GerritIntegrationConfig, - url: string, -): { branch: string; filePath: string; project: string } { - const baseUrlParse = new URL(config.gitilesBaseUrl!); - const urlParse = new URL(url); - - // Remove the gerrit authentication prefix '/a/' from the url - // In case of the gitilesBaseUrl is https://review.gerrit.com/plugins/gitiles - // and the url provided is https://review.gerrit.com/a/plugins/gitiles/... - // remove the prefix only if the pathname start with '/a/' - const urlPath = urlParse.pathname - .substring(urlParse.pathname.startsWith('/a/') ? 2 : 0) - .replace(baseUrlParse.pathname, ''); - - const parts = urlPath.split('/').filter(p => !!p); - - const projectEndIndex = parts.indexOf('+'); - - if (projectEndIndex <= 0) { - throw new Error(`Unable to parse project from url: ${url}`); - } - const project = trimStart(parts.slice(0, projectEndIndex).join('/'), '/'); - - const branchIndex = parts.indexOf('heads'); - if (branchIndex <= 0) { - throw new Error(`Unable to parse branch from url: ${url}`); - } - const branch = parts[branchIndex + 1]; - const filePath = parts.slice(branchIndex + 2).join('/'); - - return { - branch, - filePath: filePath === '' ? '/' : filePath, - project, - }; -} - /** * Parses Gitiles urls and returns the following: * @@ -231,30 +167,6 @@ export function buildGerritEditUrl( )}`; } -/** - * Build a Gerrit Gitiles archive url that targets a specific branch and path - * - * @param config - A Gerrit provider config. - * @param project - The name of the git project - * @param branch - The branch we will target. - * @param filePath - The absolute file path. - * @public - * @deprecated `buildGerritGitilesArchiveUrl` is deprecated. Use - * {@link buildGerritGitilesArchiveUrlFromLocation} instead. - */ -export function buildGerritGitilesArchiveUrl( - config: GerritIntegrationConfig, - project: string, - branch: string, - filePath: string, -): string { - const archiveName = - filePath === '/' || filePath === '' ? '.tar.gz' : `/${filePath}.tar.gz`; - return `${getGitilesAuthenticationUrl( - config, - )}/${project}/+archive/refs/heads/${branch}${archiveName}`; -} - /** * Build a Gerrit Gitiles archive url from a Gitiles url. * @@ -350,11 +262,15 @@ export function getGerritBranchApiUrl( config: GerritIntegrationConfig, url: string, ) { - const { branch, project } = parseGerritGitilesUrl(config, url); + const { ref, refType, project } = parseGitilesUrlRef(config, url); + + if (refType !== 'branch') { + throw new Error(`Unsupported gitiles ref type: ${refType}`); + } return `${config.baseUrl}${getAuthenticationPrefix( config, - )}projects/${encodeURIComponent(project)}/branches/${branch}`; + )}projects/${encodeURIComponent(project)}/branches/${ref}`; } /** @@ -367,7 +283,7 @@ export function getGerritCloneRepoUrl( config: GerritIntegrationConfig, url: string, ) { - const { project } = parseGerritGitilesUrl(config, url); + const { project } = parseGitilesUrlRef(config, url); return `${config.cloneUrl}${getAuthenticationPrefix(config)}${project}`; } diff --git a/packages/integration/src/gerrit/index.ts b/packages/integration/src/gerrit/index.ts index 534315cfba..5af75e36ab 100644 --- a/packages/integration/src/gerrit/index.ts +++ b/packages/integration/src/gerrit/index.ts @@ -19,7 +19,6 @@ export { readGerritIntegrationConfigs, } from './config'; export { - buildGerritGitilesArchiveUrl, buildGerritGitilesArchiveUrlFromLocation, getGitilesAuthenticationUrl, getGerritBranchApiUrl, @@ -28,7 +27,6 @@ export { getGerritProjectsApiUrl, getGerritRequestOptions, parseGerritJsonResponse, - parseGerritGitilesUrl, parseGitilesUrlRef, } from './core'; diff --git a/packages/integration/src/gitea/core.test.ts b/packages/integration/src/gitea/core.test.ts index e9d7c592f0..c2c9eef4a0 100644 --- a/packages/integration/src/gitea/core.test.ts +++ b/packages/integration/src/gitea/core.test.ts @@ -15,7 +15,7 @@ */ import { setupServer } from 'msw/node'; -import { registerMswTestHooks } from '../helpers'; +import { registerMswTestHooks } from '@backstage/backend-test-utils'; import { GiteaIntegrationConfig } from './config'; import { getGiteaArchiveUrl, diff --git a/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.test.ts b/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.test.ts index 0468e96e14..ebd884e783 100644 --- a/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.test.ts +++ b/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.test.ts @@ -25,6 +25,12 @@ const octokit = { }, }; +const mockCreateAppAuth = jest.fn(); + +jest.mock('@octokit/auth-app', () => ({ + createAppAuth: (...args: any[]) => mockCreateAppAuth(...args), +})); + jest.mock('@octokit/rest', () => { class Octokit { constructor() { @@ -43,6 +49,12 @@ describe('SingleInstanceGithubCredentialsProvider tests', () => { beforeEach(() => { jest.resetAllMocks(); + mockCreateAppAuth.mockReturnValue(async (opts: { type: string }) => { + if (opts.type === 'app') { + return { token: 'mock-jwt-token' }; + } + throw new Error(`Unexpected auth type: ${opts.type}`); + }); github = SingleInstanceGithubCredentialsProvider.create({ host: 'github.com', apps: [ @@ -563,4 +575,402 @@ describe('SingleInstanceGithubCredentialsProvider tests', () => { 2, ); }); + + describe('public access', () => { + it('should use an installation token when public access is enabled and owner is not in allowed list', async () => { + const githubProvider = SingleInstanceGithubCredentialsProvider.create({ + host: 'github.com', + apps: [ + { + appId: 1, + privateKey: 'privateKey', + webhookSecret: '123', + clientId: 'CLIENT_ID', + clientSecret: 'CLIENT_SECRET', + allowedInstallationOwners: ['other-org'], + publicAccess: true, + }, + ], + }); + + octokit.apps.listInstallations.mockResolvedValue({ + headers: { + etag: '123', + }, + data: [ + { + id: 1, + repository_selection: 'all', + account: { + login: 'other-org', + }, + }, + ], + } as RestEndpointMethodTypes['apps']['listInstallations']['response']); + + octokit.apps.createInstallationAccessToken.mockResolvedValueOnce({ + data: { + expires_at: DateTime.local().plus({ hours: 1 }).toString(), + token: 'public_access_token', + }, + } as RestEndpointMethodTypes['apps']['createInstallationAccessToken']['response']); + + const { token, headers, type } = await githubProvider.getCredentials({ + url: 'https://github.com/some-public-org/some-repo', + }); + + expect(type).toEqual('app'); + expect(token).toEqual('public_access_token'); + expect(headers).toEqual({ Authorization: 'Bearer public_access_token' }); + }); + + it('should use an installation token when public access is enabled and no installation exists for owner', async () => { + const githubProvider = SingleInstanceGithubCredentialsProvider.create({ + host: 'github.com', + apps: [ + { + appId: 1, + privateKey: 'privateKey', + webhookSecret: '123', + clientId: 'CLIENT_ID', + clientSecret: 'CLIENT_SECRET', + publicAccess: true, + }, + ], + }); + + octokit.apps.listInstallations.mockResolvedValue({ + headers: { + etag: '123', + }, + data: [ + { + id: 42, + repository_selection: 'all', + account: { + login: 'installed-org', + }, + }, + ], + } as RestEndpointMethodTypes['apps']['listInstallations']['response']); + + octokit.apps.createInstallationAccessToken.mockResolvedValueOnce({ + data: { + expires_at: DateTime.local().plus({ hours: 1 }).toString(), + token: 'public_installation_token', + }, + } as RestEndpointMethodTypes['apps']['createInstallationAccessToken']['response']); + + const { token, headers, type } = await githubProvider.getCredentials({ + url: 'https://github.com/non-installed-org/some-repo', + }); + + expect(type).toEqual('app'); + expect(token).toEqual('public_installation_token'); + expect(headers).toEqual({ + Authorization: 'Bearer public_installation_token', + }); + }); + + it('should not use public access when normal installation credentials are available', async () => { + const githubProvider = SingleInstanceGithubCredentialsProvider.create({ + host: 'github.com', + apps: [ + { + appId: 1, + privateKey: 'privateKey', + webhookSecret: '123', + clientId: 'CLIENT_ID', + clientSecret: 'CLIENT_SECRET', + publicAccess: true, + }, + ], + }); + + octokit.apps.listInstallations.mockResolvedValue({ + headers: { + etag: '123', + }, + data: [ + { + id: 1, + repository_selection: 'all', + account: { + login: 'backstage', + }, + }, + ], + } as RestEndpointMethodTypes['apps']['listInstallations']['response']); + + octokit.apps.createInstallationAccessToken.mockResolvedValueOnce({ + data: { + expires_at: DateTime.local().plus({ hours: 1 }).toString(), + token: 'normal_token', + }, + } as RestEndpointMethodTypes['apps']['createInstallationAccessToken']['response']); + + const { token, type } = await githubProvider.getCredentials({ + url: 'https://github.com/backstage/repo', + }); + + expect(type).toEqual('app'); + expect(token).toEqual('normal_token'); + // createInstallationAccessToken should only be called once for the normal flow + expect(octokit.apps.createInstallationAccessToken).toHaveBeenCalledTimes( + 1, + ); + }); + + it('should fall back to configured token when public access fails', async () => { + const githubProvider = SingleInstanceGithubCredentialsProvider.create({ + host: 'github.com', + apps: [ + { + appId: 1, + privateKey: 'privateKey', + webhookSecret: '123', + clientId: 'CLIENT_ID', + clientSecret: 'CLIENT_SECRET', + allowedInstallationOwners: ['other-org'], + publicAccess: true, + }, + ], + token: 'fallback_token', + }); + + octokit.apps.listInstallations.mockResolvedValue({ + headers: { + etag: '123', + }, + data: [], + } as unknown as RestEndpointMethodTypes['apps']['listInstallations']['response']); + + const { token, type } = await githubProvider.getCredentials({ + url: 'https://github.com/some-org/repo', + }); + + expect(type).toEqual('token'); + expect(token).toEqual('fallback_token'); + }); + + it('should return undefined when public access is disabled and no installation exists', async () => { + const githubProvider = SingleInstanceGithubCredentialsProvider.create({ + host: 'github.com', + apps: [ + { + appId: 1, + privateKey: 'privateKey', + webhookSecret: '123', + clientId: 'CLIENT_ID', + clientSecret: 'CLIENT_SECRET', + allowedInstallationOwners: ['other-org'], + // publicAccess is not set (defaults to false) + }, + ], + }); + + octokit.apps.listInstallations.mockResolvedValue({ + headers: { + etag: '123', + }, + data: [ + { + id: 1, + repository_selection: 'all', + account: { + login: 'other-org', + }, + }, + ], + } as RestEndpointMethodTypes['apps']['listInstallations']['response']); + + const { token, headers } = await githubProvider.getCredentials({ + url: 'https://github.com/some-org/repo', + }); + + expect(token).toBeUndefined(); + expect(headers).toBeUndefined(); + }); + + it('should cache public access tokens separately from regular tokens', async () => { + const githubProvider = SingleInstanceGithubCredentialsProvider.create({ + host: 'github.com', + apps: [ + { + appId: 1, + privateKey: 'privateKey', + webhookSecret: '123', + clientId: 'CLIENT_ID', + clientSecret: 'CLIENT_SECRET', + allowedInstallationOwners: ['installed-org'], + publicAccess: true, + }, + ], + }); + + octokit.apps.listInstallations.mockResolvedValue({ + headers: { + etag: '123', + }, + data: [ + { + id: 1, + repository_selection: 'all', + account: { + login: 'installed-org', + }, + }, + ], + } as RestEndpointMethodTypes['apps']['listInstallations']['response']); + + octokit.apps.createInstallationAccessToken.mockResolvedValue({ + data: { + expires_at: DateTime.local().plus({ hours: 1 }).toString(), + token: 'public_token', + }, + } as RestEndpointMethodTypes['apps']['createInstallationAccessToken']['response']); + + // First call for a public org + await githubProvider.getCredentials({ + url: 'https://github.com/public-org/repo', + }); + + // Second call for the same public org should use cached token + await githubProvider.getCredentials({ + url: 'https://github.com/public-org/repo', + }); + + // createInstallationAccessToken should only be called once due to caching + expect(octokit.apps.createInstallationAccessToken).toHaveBeenCalledTimes( + 1, + ); + }); + + it('should use public access with multiple apps when only one has publicAccess enabled', async () => { + const githubProvider = SingleInstanceGithubCredentialsProvider.create({ + host: 'github.com', + apps: [ + { + appId: 1, + privateKey: 'privateKey', + webhookSecret: '123', + clientId: 'CLIENT_ID', + clientSecret: 'CLIENT_SECRET', + allowedInstallationOwners: ['org-1'], + // publicAccess not set, defaults to false + }, + { + appId: 2, + privateKey: 'privateKey2', + webhookSecret: '456', + clientId: 'CLIENT_ID_2', + clientSecret: 'CLIENT_SECRET_2', + allowedInstallationOwners: ['org-2'], + publicAccess: true, + }, + ], + }); + + octokit.apps.listInstallations.mockResolvedValue({ + headers: { + etag: '123', + }, + data: [ + { + id: 1, + repository_selection: 'all', + account: { + login: 'org-2', + }, + }, + ], + } as RestEndpointMethodTypes['apps']['listInstallations']['response']); + + octokit.apps.createInstallationAccessToken.mockResolvedValueOnce({ + data: { + expires_at: DateTime.local().plus({ hours: 1 }).toString(), + token: 'public_access_from_app_2', + }, + } as RestEndpointMethodTypes['apps']['createInstallationAccessToken']['response']); + + const { token, type } = await githubProvider.getCredentials({ + url: 'https://github.com/unknown-org/repo', + }); + + expect(type).toEqual('app'); + expect(token).toEqual('public_access_from_app_2'); + }); + }); + + describe('bare host URL (no org/repo)', () => { + it('should return app JWT when URL has no org or repo', async () => { + const { token, headers, type } = await github.getCredentials({ + url: 'https://github.com', + }); + + expect(type).toEqual('app'); + expect(token).toEqual('mock-jwt-token'); + expect(headers).toEqual({ + Authorization: 'Bearer mock-jwt-token', + }); + }); + + it('should return app JWT with multiple apps configured', async () => { + const multiAppProvider = SingleInstanceGithubCredentialsProvider.create({ + host: 'github.com', + apps: [ + { + appId: 1, + privateKey: 'privateKey', + webhookSecret: '123', + clientId: 'CLIENT_ID', + clientSecret: 'CLIENT_SECRET', + }, + { + appId: 2, + privateKey: 'privateKey2', + webhookSecret: '456', + clientId: 'CLIENT_ID_2', + clientSecret: 'CLIENT_SECRET_2', + }, + ], + }); + + const { token, type } = await multiAppProvider.getCredentials({ + url: 'https://github.com', + }); + + expect(type).toEqual('app'); + expect(token).toBeDefined(); + }); + + it('should fall back to configured token when no apps are configured and URL has no org', async () => { + const githubProvider = SingleInstanceGithubCredentialsProvider.create({ + host: 'github.com', + apps: [], + token: 'fallback_token', + }); + + const { token, type } = await githubProvider.getCredentials({ + url: 'https://github.com', + }); + + expect(type).toEqual('token'); + expect(token).toEqual('fallback_token'); + }); + + it('should return undefined token when no apps and no token configured for bare host URL', async () => { + const githubProvider = SingleInstanceGithubCredentialsProvider.create({ + host: 'github.com', + }); + + const { token, headers, type } = await githubProvider.getCredentials({ + url: 'https://github.com', + }); + + expect(type).toEqual('token'); + expect(token).toBeUndefined(); + expect(headers).toBeUndefined(); + }); + }); }); diff --git a/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts b/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts index d2c1e37a65..3ecaa05e0c 100644 --- a/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts +++ b/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts @@ -100,6 +100,7 @@ class GithubAppManager { private readonly baseAuthConfig: { appId: number; privateKey: string }; private readonly cache = new Cache(); private readonly allowedInstallationOwners: string[] | undefined; // undefined allows all installations + public readonly publicAccess: boolean; constructor(config: GithubAppConfig, baseUrl?: string) { this.allowedInstallationOwners = config.allowedInstallationOwners?.map( @@ -116,12 +117,24 @@ class GithubAppManager { authStrategy: createAppAuth, auth: this.baseAuthConfig, }); + this.publicAccess = config.publicAccess ?? false; } async getInstallationCredentials( - owner: string, + owner?: string, repo?: string, ): Promise<{ accessToken: string | undefined }> { + // No owner means a bare host URL (e.g. https://github.com) — return an + // app-level JWT rather than an installation token. + if (!owner) { + const auth = createAppAuth({ + appId: this.baseAuthConfig.appId, + privateKey: this.baseAuthConfig.privateKey, + }); + const { token } = await auth({ type: 'app' }); + return { accessToken: token }; + } + if (this.allowedInstallationOwners) { if ( !this.allowedInstallationOwners?.includes( @@ -170,6 +183,30 @@ class GithubAppManager { }); } + async getPublicInstallationToken(): Promise<{ accessToken: string }> { + const [installation] = await this.getInstallations(); + + if (!installation) { + throw new Error(`No installation found for public app`); + } + + return this.cache.getOrCreateToken( + `public:${installation.id}`, + undefined, + async () => { + const result = await this.appClient.apps.createInstallationAccessToken({ + installation_id: installation.id, + headers: HEADERS, + }); + + return { + token: result.data.token, + expiresAt: DateTime.fromISO(result.data.expires_at), + }; + }, + ); + } + getInstallations(): Promise< RestEndpointMethodTypes['apps']['listInstallations']['response']['data'] > { @@ -185,12 +222,14 @@ class GithubAppManager { inst.account.login?.toLocaleLowerCase('en-US') === owner.toLocaleLowerCase('en-US'), ); + if (installation) { return { installationId: installation.id, suspended: Boolean(installation.suspended_by), }; } + const notFoundError = new Error( `No app installation found for ${owner} in ${this.baseAuthConfig.appId}`, ); @@ -228,7 +267,10 @@ export class GithubAppCredentialsMux { return installs.flat(); } - async getAppToken(owner: string, repo?: string): Promise { + async getAppToken( + owner?: string, + repo?: string, + ): Promise { if (this.apps.length === 0) { return undefined; } @@ -245,10 +287,26 @@ export class GithubAppCredentialsMux { const result = results.find( resultItem => resultItem.credentials?.accessToken, ); + if (result) { return result.credentials!.accessToken; } + // If there was no token returned, then let's find a public access app and use an installation to get a token. + const publicAccessApp = this.apps.find(app => app.publicAccess); + if (publicAccessApp) { + const publicResult = await publicAccessApp + .getPublicInstallationToken() + .then( + credentials => ({ credentials, error: undefined }), + error => ({ credentials: undefined, error }), + ); + + if (publicResult.credentials?.accessToken) { + return publicResult.credentials.accessToken; + } + } + const errors = results.map(r => r.error); const notNotFoundError = errors.find(err => err?.name !== 'NotFoundError'); if (notNotFoundError) { diff --git a/packages/integration/src/github/config.ts b/packages/integration/src/github/config.ts index 74a0267d62..f755b84c2a 100644 --- a/packages/integration/src/github/config.ts +++ b/packages/integration/src/github/config.ts @@ -109,6 +109,10 @@ export type GithubAppConfig = { * https://docs.github.com/en/rest/reference/apps#list-installations-for-the-authenticated-app--code-samples */ allowedInstallationOwners?: string[]; + /** + * If true, then an installation token will be issued for access when no other token is available. + */ + publicAccess?: boolean; }; /** @@ -133,6 +137,7 @@ export function readGithubIntegrationConfig( allowedInstallationOwners: c.getOptionalStringArray( 'allowedInstallationOwners', ), + publicAccess: c.getOptionalBoolean('publicAccess'), })); if (!isValidHost(host)) { diff --git a/packages/integration/src/github/core.test.ts b/packages/integration/src/github/core.test.ts index 982a096b43..d129f57838 100644 --- a/packages/integration/src/github/core.test.ts +++ b/packages/integration/src/github/core.test.ts @@ -15,7 +15,7 @@ */ import { GithubIntegrationConfig } from './config'; -import { getGithubFileFetchUrl, getGitHubRequestOptions } from './core'; +import { getGithubFileFetchUrl } from './core'; import { GithubCredentials } from './types'; describe('github core', () => { @@ -35,28 +35,6 @@ describe('github core', () => { type: 'token', }; - describe('getGitHubRequestOptions', () => { - it('inserts a token when needed', () => { - const withToken: GithubIntegrationConfig = { - host: '', - rawBaseUrl: '', - token: 'A', - }; - const withoutToken: GithubIntegrationConfig = { - host: '', - rawBaseUrl: '', - }; - expect( - (getGitHubRequestOptions(withToken, appCredentials).headers as any) - .Authorization, - ).toEqual('token A'); - expect( - (getGitHubRequestOptions(withoutToken, noCredentials).headers as any) - .Authorization, - ).toBeUndefined(); - }); - }); - describe('getGithubFileFetchUrl', () => { it('rejects targets that do not look like URLs', () => { const config: GithubIntegrationConfig = { host: '', apiBaseUrl: '' }; @@ -176,5 +154,49 @@ describe('github core', () => { ), ).toEqual('https://ghe.mycompany.net/raw/a/b/branchname/path/to/c.yaml'); }); + + it('rejects URLs with encoded path traversal sequences', () => { + const config: GithubIntegrationConfig = { + host: 'github.com', + apiBaseUrl: 'https://api.github.com', + }; + expect(() => + getGithubFileFetchUrl( + 'https://github.com/octocat/Hello-World/blob/main/%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fuser/repos', + config, + tokenCredentials, + ), + ).toThrow(/path traversal/); + }); + + it('rejects URLs with literal path traversal in filepath', () => { + const config: GithubIntegrationConfig = { + host: 'github.com', + apiBaseUrl: 'https://api.github.com', + }; + // Literal ../ is normalized by the URL constructor before git-url-parse + // sees it, so it fails with the existing validation instead + expect(() => + getGithubFileFetchUrl( + 'https://github.com/octocat/Hello-World/blob/main/../../user/repos', + config, + tokenCredentials, + ), + ).toThrow(/Incorrect URL/); + }); + + it('rejects raw endpoint URLs with path traversal', () => { + const config: GithubIntegrationConfig = { + host: 'github.com', + rawBaseUrl: 'https://raw.githubusercontent.com', + }; + expect(() => + getGithubFileFetchUrl( + 'https://github.com/octocat/Hello-World/blob/main/%2e%2e%2f%2e%2e%2fuser/repos', + config, + tokenCredentials, + ), + ).toThrow(/path traversal/); + }); }); }); diff --git a/packages/integration/src/github/core.ts b/packages/integration/src/github/core.ts index a755f3383c..9599338497 100644 --- a/packages/integration/src/github/core.ts +++ b/packages/integration/src/github/core.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import parseGitUrl from 'git-url-parse'; import { GithubIntegrationConfig } from './config'; +import { parseGitUrlSafe } from '../helpers'; import { GithubCredentials } from './types'; /** @@ -39,7 +39,7 @@ export function getGithubFileFetchUrl( credentials: GithubCredentials, ): string { try { - const { owner, name, ref, filepathtype, filepath } = parseGitUrl(url); + const { owner, name, ref, filepathtype, filepath } = parseGitUrlSafe(url); if ( !owner || !name || @@ -63,30 +63,6 @@ export function getGithubFileFetchUrl( } } -/** - * Gets the request options necessary to make requests to a given provider. - * - * @deprecated This function is no longer used internally - * @param config - The relevant provider config - * @public - */ -export function getGitHubRequestOptions( - config: GithubIntegrationConfig, - credentials: GithubCredentials, -): { headers: Record } { - const headers: Record = {}; - - if (chooseEndpoint(config, credentials) === 'api') { - headers.Accept = 'application/vnd.github.v3.raw'; - } - - if (credentials.token) { - headers.Authorization = `token ${credentials.token}`; - } - - return { headers }; -} - export function chooseEndpoint( config: GithubIntegrationConfig, credentials: GithubCredentials, diff --git a/packages/integration/src/github/index.ts b/packages/integration/src/github/index.ts index ea6f18f2e0..450ac38a63 100644 --- a/packages/integration/src/github/index.ts +++ b/packages/integration/src/github/index.ts @@ -19,7 +19,7 @@ export { readGithubIntegrationConfigs, } from './config'; export type { GithubAppConfig, GithubIntegrationConfig } from './config'; -export { getGithubFileFetchUrl, getGitHubRequestOptions } from './core'; +export { getGithubFileFetchUrl } from './core'; export { DefaultGithubCredentialsProvider } from './DefaultGithubCredentialsProvider'; export { GithubAppCredentialsMux, diff --git a/packages/integration/src/gitlab/GitLabIntegration.test.ts b/packages/integration/src/gitlab/GitLabIntegration.test.ts index 575ce75308..7132849a7c 100644 --- a/packages/integration/src/gitlab/GitLabIntegration.test.ts +++ b/packages/integration/src/gitlab/GitLabIntegration.test.ts @@ -14,8 +14,20 @@ * limitations under the License. */ +import { setupServer } from 'msw/node'; +import { rest } from 'msw'; import { ConfigReader } from '@backstage/config'; -import { GitLabIntegration, replaceGitLabUrlType } from './GitLabIntegration'; +import { + GitLabIntegration, + replaceGitLabUrlType, + sleep, +} from './GitLabIntegration'; +import { registerMswTestHooks } from '@backstage/backend-test-utils'; + +// Mock pThrottle to make testing easier +jest.mock('p-throttle', () => { + return jest.fn(() => (fn: any) => fn); +}); describe('GitLabIntegration', () => { it('has a working factory', () => { @@ -45,7 +57,11 @@ describe('GitLabIntegration', () => { }); it('resolve edit URL', () => { - const integration = new GitLabIntegration({ host: 'h.com' } as any); + const integration = new GitLabIntegration({ + host: 'h.com', + apiBaseUrl: 'https://h.com/api/v4', + baseUrl: 'https://h.com', + }); expect( integration.resolveEditUrl( @@ -53,6 +69,349 @@ describe('GitLabIntegration', () => { ), ).toBe('https://gitlab.com/my-org/my-project/-/edit/develop/README.md'); }); + + describe('fetch strategy', () => { + const worker = setupServer(); + registerMswTestHooks(worker); + + beforeAll(() => { + jest.useFakeTimers(); + }); + afterAll(() => { + jest.useRealTimers(); + }); + beforeEach(() => { + jest.clearAllTimers(); + }); + + it('uses plain fetch when no throttling or retries configured', async () => { + const integration = new GitLabIntegration({ + host: 'h.com', + apiBaseUrl: 'https://h.com/api/v4', + baseUrl: 'https://h.com', + }); + + let calledUrl: string | undefined; + worker.use( + rest.get('https://h.com/api/v4', (req, res, ctx) => { + calledUrl = req.url.href; + return res(ctx.status(200)); + }), + ); + + await integration.fetch('https://h.com/api/v4'); + expect(calledUrl).toBe('https://h.com/api/v4'); + }); + + it('applies retry logic when maxRetries > 0', async () => { + let callCount = 0; + worker.use( + rest.get('https://h.com/api/v4', (_req, res, ctx) => { + callCount += 1; + if (callCount === 1) { + return res(ctx.status(429), ctx.json({})); + } + return res(ctx.status(200), ctx.json({})); + }), + ); + + const integration = new GitLabIntegration({ + host: 'h.com', + apiBaseUrl: 'https://h.com/api/v4', + baseUrl: 'https://h.com', + retry: { + maxRetries: 3, + retryStatusCodes: [429], + }, + }); + const responsePromise = integration.fetch('https://h.com/api/v4'); + await jest.advanceTimersByTimeAsync(100); + await jest.advanceTimersByTimeAsync(200); + await jest.advanceTimersByTimeAsync(400); + const response = await responsePromise; + + expect(response.status).toBe(200); + expect(callCount).toBe(2); + }); + + it('does not retry when status code is not in retryStatusCodes', async () => { + let callCount = 0; + worker.use( + rest.get('https://h.com/api/v4', (_req, res, ctx) => { + callCount += 1; + return res(ctx.status(404)); + }), + ); + + const integration = new GitLabIntegration({ + host: 'h.com', + apiBaseUrl: 'https://h.com/api/v4', + baseUrl: 'https://h.com', + retry: { + maxRetries: 3, + retryStatusCodes: [429, 500], + }, + }); + + const responsePromise = integration.fetch('https://h.com/api/v4'); + await jest.advanceTimersByTimeAsync(1000); + const response = await responsePromise; + + expect(response.status).toBe(404); + expect(callCount).toBe(1); + }); + + it('stops retrying after maxRetries attempts', async () => { + let callCount = 0; + worker.use( + rest.get('https://h.com/api/v4', (_req, res, ctx) => { + callCount += 1; + return res(ctx.status(429)); + }), + ); + + const integration = new GitLabIntegration({ + host: 'h.com', + apiBaseUrl: 'https://h.com/api/v4', + baseUrl: 'https://h.com', + retry: { + maxRetries: 2, + retryStatusCodes: [429], + }, + }); + + const responsePromise = integration.fetch('https://h.com/api/v4'); + await jest.advanceTimersByTimeAsync(100); + await jest.advanceTimersByTimeAsync(200); + await jest.advanceTimersByTimeAsync(400); + const response = await responsePromise; + + expect(response.status).toBe(429); + expect(callCount).toBe(3); // initial + 2 retries + }); + + it('applies throttling when limitPerMinute > 0', async () => { + const pThrottle = require('p-throttle'); + const throttleMock = jest.fn(() => (fn: any) => fn); + pThrottle.mockReturnValue(throttleMock); + + const integration = new GitLabIntegration({ + host: 'h.com', + apiBaseUrl: 'https://h.com/api/v4', + baseUrl: 'https://h.com', + retry: { + maxApiRequestsPerMinute: 60, + }, + }); + + await integration.fetch('https://h.com/api/v4'); + + expect(pThrottle).toHaveBeenCalledWith({ + limit: 60, + interval: 60_000, + }); + expect(throttleMock).toHaveBeenCalled(); + }); + + it('applies both throttling and retry when both are configured', async () => { + const pThrottle = require('p-throttle'); + + const throttleMock = jest.fn((fn: any) => fn); + pThrottle.mockReturnValue(throttleMock); + + let callCount = 0; + worker.use( + rest.get('https://h.com/api/v4', (_req, res, ctx) => { + callCount += 1; + if (callCount === 1) { + return res(ctx.status(429), ctx.json({})); + } + return res(ctx.status(200), ctx.json({})); + }), + ); + + const integration = new GitLabIntegration({ + apiBaseUrl: 'https://h.com/api/v4', + host: 'h.com', + baseUrl: 'https://h.com', + retry: { + maxRetries: 3, + retryStatusCodes: [429], + maxApiRequestsPerMinute: 60, + }, + }); + + const responsePromise = integration.fetch('https://h.com/api/v4'); + await jest.advanceTimersByTimeAsync(100); + const response = await responsePromise; + + expect(response.status).toBe(200); + expect(pThrottle).toHaveBeenCalledWith({ + limit: 60, + interval: 60_000, + }); + expect(callCount).toBe(2); + }); + + it('retries based on configured status codes', async () => { + let callCount = 0; + worker.use( + rest.get('https://h.com/api/v4', (_req, res, ctx) => { + callCount += 1; + if (callCount === 1) { + return res( + ctx.status(429), + ctx.set('Retry-After', '1'), + ctx.json({}), + ); + } + return res(ctx.status(200), ctx.json({})); + }), + ); + + const integration = new GitLabIntegration({ + host: 'h.com', + apiBaseUrl: 'https://h.com/api/v4', + baseUrl: 'https://h.com', + retry: { + maxRetries: 3, + retryStatusCodes: [429], + }, + }); + + const responsePromise = integration.fetch('https://h.com/api/v4'); + await jest.advanceTimersByTimeAsync(1000); + const response = await responsePromise; + + expect(response.status).toBe(200); + expect(callCount).toBe(2); + }); + + it('retries multiple times for persistent failures', async () => { + let callCount = 0; + worker.use( + rest.get('https://h.com/api/v4', (_req, res, ctx) => { + callCount += 1; + if (callCount < 3) { + return res(ctx.status(500), ctx.json({})); + } + return res(ctx.status(200), ctx.json({})); + }), + ); + + const integration = new GitLabIntegration({ + host: 'h.com', + apiBaseUrl: 'https://h.com/api/v4', + baseUrl: 'https://h.com', + retry: { + maxRetries: 3, + retryStatusCodes: [500], + }, + }); + + const responsePromise = integration.fetch('https://h.com/api/v4'); + await jest.advanceTimersByTimeAsync(100); + await jest.advanceTimersByTimeAsync(200); + await jest.advanceTimersByTimeAsync(400); + const response = await responsePromise; + + expect(response.status).toBe(200); + expect(callCount).toBe(3); + }); + }); +}); + +describe('sleep', () => { + beforeAll(() => { + jest.useFakeTimers(); + }); + afterAll(() => { + jest.useRealTimers(); + }); + beforeEach(() => { + jest.clearAllTimers(); + }); + + it('should resolve after the specified duration when not aborted', async () => { + const duration = 1000; + const sleepPromise = sleep(duration, null); + + // Fast-forward timers to trigger the timeout + jest.advanceTimersByTimeAsync(duration); + + await expect(sleepPromise).resolves.toBeUndefined(); + }); + + it('should resolve immediately if abortSignal is already aborted', async () => { + const abortController = new AbortController(); + abortController.abort(); + + const sleepPromise = sleep(5000, abortController.signal); + + // Should resolve immediately without needing to advance timers + await expect(sleepPromise).resolves.toBeUndefined(); + }); + + it('should resolve when aborted during wait', async () => { + const abortController = new AbortController(); + const duration = 5000; + + const sleepPromise = sleep(duration, abortController.signal); + + // Abort the signal after starting the sleep + abortController.abort(); + + // Should resolve immediately when aborted + await expect(sleepPromise).resolves.toBeUndefined(); + }); + + it('should handle undefined abortSignal gracefully', async () => { + const duration = 500; + const sleepPromise = sleep(duration, undefined); + + // Fast-forward timers to trigger the timeout + jest.advanceTimersByTimeAsync(duration); + + await expect(sleepPromise).resolves.toBeUndefined(); + }); + + it('should clean up timeout when aborted', async () => { + const abortController = new AbortController(); + const duration = 10000; + + const sleepPromise = sleep(duration, abortController.signal); + + // Check that a timer was set + expect(jest.getTimerCount()).toBe(1); + + // Abort the signal + abortController.abort(); + + // Wait for the promise to resolve + await sleepPromise; + + // Timer should be cleaned up + expect(jest.getTimerCount()).toBe(0); + }); + + it('should clean up abort event listener when timeout completes', async () => { + const abortController = new AbortController(); + const duration = 1000; + + const sleepPromise = sleep(duration, abortController.signal); + + // Fast-forward timers to complete the timeout + jest.advanceTimersByTimeAsync(duration); + + // Wait for the promise to complete and verify it resolves properly + await expect(sleepPromise).resolves.toBeUndefined(); + + // Event listener should be cleaned up - aborting after completion should not cause issues + abortController.abort(); // This should not affect anything since the sleep is already done + + // Verify the sleep function handled cleanup properly + expect(jest.getTimerCount()).toBe(0); + }); }); describe('replaceGitLabUrlType', () => { diff --git a/packages/integration/src/gitlab/GitLabIntegration.ts b/packages/integration/src/gitlab/GitLabIntegration.ts index 0c52799599..68afb6f63e 100644 --- a/packages/integration/src/gitlab/GitLabIntegration.ts +++ b/packages/integration/src/gitlab/GitLabIntegration.ts @@ -13,13 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import { basicIntegrations, defaultScmResolveUrl } from '../helpers'; import { ScmIntegration, ScmIntegrationsFactory } from '../types'; import { GitLabIntegrationConfig, readGitLabIntegrationConfigs, } from './config'; +import pThrottle from 'p-throttle'; + +type FetchFunction = typeof fetch; /** * A GitLab based integration. @@ -37,7 +39,12 @@ export class GitLabIntegration implements ScmIntegration { ); }; - constructor(private readonly integrationConfig: GitLabIntegrationConfig) {} + private readonly fetchImpl: FetchFunction; + + constructor(private readonly integrationConfig: GitLabIntegrationConfig) { + // Configure fetch strategy based on configuration + this.fetchImpl = this.createFetchStrategy(); + } get type(): string { return 'gitlab'; @@ -62,6 +69,97 @@ export class GitLabIntegration implements ScmIntegration { resolveEditUrl(url: string): string { return replaceGitLabUrlType(url, 'edit'); } + + fetch(input: RequestInfo | URL, init?: RequestInit): Promise { + return this.fetchImpl(input, init); + } + + private createFetchStrategy(): FetchFunction { + let fetchFn: FetchFunction = async (url, options) => { + return fetch(url, { ...options, mode: 'same-origin' }); + }; + + const retryConfig = this.integrationConfig.retry; + if (retryConfig) { + // Apply retry wrapper if configured + fetchFn = this.withRetry(fetchFn, retryConfig); + + // Apply throttling wrapper if configured + if ( + retryConfig.maxApiRequestsPerMinute && + retryConfig.maxApiRequestsPerMinute > 0 + ) { + fetchFn = pThrottle({ + limit: retryConfig.maxApiRequestsPerMinute, + interval: 60_000, + })(fetchFn); + } + } + + return fetchFn; + } + + private withRetry( + fetchFn: FetchFunction, + retryConfig: { maxRetries?: number; retryStatusCodes?: number[] }, + ): FetchFunction { + const maxRetries = retryConfig?.maxRetries ?? 0; + const retryStatusCodes = retryConfig?.retryStatusCodes ?? []; + if (maxRetries <= 0 || retryStatusCodes.length === 0) { + return fetchFn; + } + + return async (url, options) => { + const abortSignal = options?.signal; + let response: Response; + let attempt = 0; + for (;;) { + response = await fetchFn(url, options); + // If response is not retryable, return immediately + if (!retryStatusCodes.includes(response.status)) { + break; + } + + // If this was the last allowed attempt, return response + if (attempt++ >= maxRetries) { + break; + } + // Determine delay from Retry-After header if present, otherwise exponential backoff + const retryAfter = response.headers.get('Retry-After'); + const delay = retryAfter + ? parseInt(retryAfter, 10) * 1000 + : Math.min(100 * Math.pow(2, attempt - 1), 10000); // Exponential backoff, cap at 10 seconds + + await sleep(delay, abortSignal); + } + + return response; + }; + } +} + +export async function sleep( + durationMs: number, + abortSignal: AbortSignal | null | undefined, +): Promise { + if (abortSignal?.aborted) { + return; + } + + await new Promise(resolve => { + let timeoutHandle: NodeJS.Timeout | undefined = undefined; + + const done = () => { + if (timeoutHandle) { + clearTimeout(timeoutHandle); + } + abortSignal?.removeEventListener('abort', done); + resolve(); + }; + + timeoutHandle = setTimeout(done, durationMs); + abortSignal?.addEventListener('abort', done); + }); } /** diff --git a/packages/integration/src/gitlab/config.test.ts b/packages/integration/src/gitlab/config.test.ts index f1d17a6d7e..693e31e2e4 100644 --- a/packages/integration/src/gitlab/config.test.ts +++ b/packages/integration/src/gitlab/config.test.ts @@ -58,6 +58,11 @@ describe('readGitLabIntegrationConfig', () => { token: ' t\n', apiBaseUrl: 'https://a.com', baseUrl: 'https://baseurl.for.me/gitlab', + retry: { + maxRetries: 3, + maxApiRequestsPerMinute: 1000, + retryStatusCodes: [429], + }, }), ); @@ -66,6 +71,12 @@ describe('readGitLabIntegrationConfig', () => { token: 't', apiBaseUrl: 'https://a.com', baseUrl: 'https://baseurl.for.me/gitlab', + commitSigningKey: undefined, + retry: { + maxRetries: 3, + maxApiRequestsPerMinute: 1000, + retryStatusCodes: [429], + }, }); }); @@ -77,6 +88,8 @@ describe('readGitLabIntegrationConfig', () => { host: 'gitlab.com', apiBaseUrl: 'https://gitlab.com/api/v4', baseUrl: 'https://gitlab.com', + commitSigningKey: undefined, + retry: undefined, }); }); @@ -89,6 +102,7 @@ describe('readGitLabIntegrationConfig', () => { host: 'gitlab.com', baseUrl: 'https://gitlab.com', apiBaseUrl: 'https://gitlab.com/api/v4', + retry: undefined, }); }); @@ -119,6 +133,9 @@ describe('readGitLabIntegrationConfig', () => { host: 'a.com', apiBaseUrl: 'https://a.com/api', baseUrl: 'https://a.com', + token: undefined, // token is filtered out on frontend + commitSigningKey: undefined, + retry: undefined, }); }); }); @@ -144,6 +161,7 @@ describe('readGitLabIntegrationConfigs', () => { token: 't', apiBaseUrl: 'https://a.com/api/v4', baseUrl: 'https://a.com', + retry: undefined, }); }); diff --git a/packages/integration/src/gitlab/config.ts b/packages/integration/src/gitlab/config.ts index 636919291c..344d054904 100644 --- a/packages/integration/src/gitlab/config.ts +++ b/packages/integration/src/gitlab/config.ts @@ -21,6 +21,32 @@ import { isValidHost, isValidUrl } from '../helpers'; const GITLAB_HOST = 'gitlab.com'; const GITLAB_API_BASE_URL = 'https://gitlab.com/api/v4'; +/** + * Reads an optional number array from config + */ +function readOptionalNumberArray( + config: Config, + key: string, +): number[] | undefined { + const value = config.getOptional(key); + if (value === undefined) { + return undefined; + } + if (!Array.isArray(value)) { + throw new Error( + `Invalid ${key} config: expected an array, got ${typeof value}`, + ); + } + return value.map((item, index) => { + if (typeof item !== 'number') { + throw new Error( + `Invalid ${key} config: all values must be numbers, got ${typeof item} at index ${index}`, + ); + } + return item; + }); +} + /** * The configuration parameters for a single GitLab integration. * @@ -59,6 +85,29 @@ export type GitLabIntegrationConfig = { * Signing key to sign commits */ commitSigningKey?: string; + + /** + * Retry configuration for failed requests. + */ + retry?: { + /** + * Maximum number of retries for failed requests + * @defaultValue 0 + */ + maxRetries?: number; + + /** + * HTTP status codes that should trigger a retry + * @defaultValue [] + */ + retryStatusCodes?: number[]; + + /** + * Rate limit for requests per minute + * @defaultValue -1 + */ + maxApiRequestsPerMinute?: number; + }; }; /** @@ -100,12 +149,25 @@ export function readGitLabIntegrationConfig( ); } + const retryConfig = config.getOptionalConfig('retry'); + + const retry = retryConfig + ? { + maxRetries: retryConfig.getOptionalNumber('maxRetries') ?? 0, + retryStatusCodes: + readOptionalNumberArray(retryConfig, 'retryStatusCodes') ?? [], + maxApiRequestsPerMinute: + retryConfig.getOptionalNumber('maxApiRequestsPerMinute') ?? -1, + } + : undefined; + return { host, token, apiBaseUrl, baseUrl, commitSigningKey: config.getOptionalString('commitSigningKey'), + retry, }; } diff --git a/packages/integration/src/gitlab/core.test.ts b/packages/integration/src/gitlab/core.test.ts index d514dba755..58fcbe34a5 100644 --- a/packages/integration/src/gitlab/core.test.ts +++ b/packages/integration/src/gitlab/core.test.ts @@ -14,29 +14,14 @@ * limitations under the License. */ -import { rest } from 'msw'; -import { setupServer } from 'msw/node'; import { GitLabIntegrationConfig } from './config'; -import { getGitLabFileFetchUrl, getGitLabRequestOptions } from './core'; - -const worker = setupServer(); +import { + getGitLabFileFetchUrl, + getGitLabRequestOptions, + extractProjectPath, +} from './core'; describe('gitlab core', () => { - beforeAll(() => worker.listen({ onUnhandledRequest: 'error' })); - afterAll(() => worker.close()); - afterEach(() => worker.resetHandlers()); - - beforeEach(() => { - worker.use( - rest.get('*/api/v4/projects/group%2Fproject', (_, res, ctx) => - res(ctx.status(200), ctx.json({ id: 12345 })), - ), - rest.get('*/api/v4/projects/group%2Fsubgroup%2Fproject', (_, res, ctx) => - res(ctx.status(200), ctx.json({ id: 12345 })), - ), - ); - }); - const configWithNoToken: GitLabIntegrationConfig = { host: 'gitlab.com', apiBaseUrl: '', @@ -63,7 +48,7 @@ describe('gitlab core', () => { const target = 'https://gitlab.com/group/project/-/blob/branch/folder/file.yaml'; const fetchUrl = - 'https://gitlab.com/api/v4/projects/12345/repository/files/folder%2Ffile.yaml/raw?ref=branch'; + 'https://gitlab.com/api/v4/projects/group%2Fproject/repository/files/folder%2Ffile.yaml/raw?ref=branch'; await expect( getGitLabFileFetchUrl(target, configWithNoToken), ).resolves.toBe(fetchUrl); @@ -73,7 +58,7 @@ describe('gitlab core', () => { const target = 'https://gitlab.com/group/project/-/blob/branch/blob/file.yaml'; const fetchUrl = - 'https://gitlab.com/api/v4/projects/12345/repository/files/blob%2Ffile.yaml/raw?ref=branch'; + 'https://gitlab.com/api/v4/projects/group%2Fproject/repository/files/blob%2Ffile.yaml/raw?ref=branch'; await expect( getGitLabFileFetchUrl(target, configWithNoToken), ).resolves.toBe(fetchUrl); @@ -83,7 +68,7 @@ describe('gitlab core', () => { const target = 'https://gitlab.com/group/subgroup/project/-/blob/branch/folder/file.yaml'; const fetchUrl = - 'https://gitlab.com/api/v4/projects/12345/repository/files/folder%2Ffile.yaml/raw?ref=branch'; + 'https://gitlab.com/api/v4/projects/group%2Fsubgroup%2Fproject/repository/files/folder%2Ffile.yaml/raw?ref=branch'; await expect( getGitLabFileFetchUrl(target, configWithNoToken), ).resolves.toBe(fetchUrl); @@ -93,7 +78,7 @@ describe('gitlab core', () => { const target = 'https://gitlab.com/group/project/-/blob/branch/folder/file.yml'; const fetchUrl = - 'https://gitlab.com/api/v4/projects/12345/repository/files/folder%2Ffile.yml/raw?ref=branch'; + 'https://gitlab.com/api/v4/projects/group%2Fproject/repository/files/folder%2Ffile.yml/raw?ref=branch'; await expect( getGitLabFileFetchUrl(target, configWithNoToken), ).resolves.toBe(fetchUrl); @@ -103,7 +88,7 @@ describe('gitlab core', () => { const target = 'https://gitlab.com/group/project/-/blob/branch/folder/file with spaces.yaml'; const fetchUrl = - 'https://gitlab.com/api/v4/projects/12345/repository/files/folder%2Ffile%20with%20spaces.yaml/raw?ref=branch'; + 'https://gitlab.com/api/v4/projects/group%2Fproject/repository/files/folder%2Ffile%20with%20spaces.yaml/raw?ref=branch'; await expect( getGitLabFileFetchUrl(target, configWithNoToken), ).resolves.toBe(fetchUrl); @@ -114,7 +99,7 @@ describe('gitlab core', () => { const target = 'https://gitlab.mycompany.com/group/project/-/blob/branch/folder/file.yaml'; const fetchUrl = - 'https://gitlab.mycompany.com/api/v4/projects/12345/repository/files/folder%2Ffile.yaml/raw?ref=branch'; + 'https://gitlab.mycompany.com/api/v4/projects/group%2Fproject/repository/files/folder%2Ffile.yaml/raw?ref=branch'; await expect( getGitLabFileFetchUrl(target, configSelfHostedWithoutRelativePath), ).resolves.toBe(fetchUrl); @@ -124,7 +109,7 @@ describe('gitlab core', () => { const target = 'https://gitlab.mycompany.com/group/project/-/blob/branch/folder/file with spaces.yaml'; const fetchUrl = - 'https://gitlab.mycompany.com/api/v4/projects/12345/repository/files/folder%2Ffile%20with%20spaces.yaml/raw?ref=branch'; + 'https://gitlab.mycompany.com/api/v4/projects/group%2Fproject/repository/files/folder%2Ffile%20with%20spaces.yaml/raw?ref=branch'; await expect( getGitLabFileFetchUrl(target, configSelfHostedWithoutRelativePath), ).resolves.toBe(fetchUrl); @@ -135,7 +120,7 @@ describe('gitlab core', () => { const target = 'https://gitlab.mycompany.com/gitlab/group/project/-/blob/branch/folder/file.yaml'; const fetchUrl = - 'https://gitlab.mycompany.com/gitlab/api/v4/projects/12345/repository/files/folder%2Ffile.yaml/raw?ref=branch'; + 'https://gitlab.mycompany.com/gitlab/api/v4/projects/group%2Fproject/repository/files/folder%2Ffile.yaml/raw?ref=branch'; await expect( getGitLabFileFetchUrl(target, configSelfHosteWithRelativePath), ).resolves.toBe(fetchUrl); @@ -145,7 +130,7 @@ describe('gitlab core', () => { const target = 'https://gitlab.mycompany.com/gitlab/group/project/-/blob/branch/folder/file with spaces.yaml'; const fetchUrl = - 'https://gitlab.mycompany.com/gitlab/api/v4/projects/12345/repository/files/folder%2Ffile%20with%20spaces.yaml/raw?ref=branch'; + 'https://gitlab.mycompany.com/gitlab/api/v4/projects/group%2Fproject/repository/files/folder%2Ffile%20with%20spaces.yaml/raw?ref=branch'; await expect( getGitLabFileFetchUrl(target, configSelfHosteWithRelativePath), ).resolves.toBe(fetchUrl); @@ -159,7 +144,7 @@ describe('gitlab core', () => { const target = 'https://gitlab.com/group/project/blob/branch/folder/file.yaml'; const fetchUrl = - 'https://gitlab.com/api/v4/projects/12345/repository/files/folder%2Ffile.yaml/raw?ref=branch'; + 'https://gitlab.com/api/v4/projects/group%2Fproject/repository/files/folder%2Ffile.yaml/raw?ref=branch'; await expect( getGitLabFileFetchUrl(target, configWithNoToken), ).resolves.toBe(fetchUrl); @@ -169,7 +154,7 @@ describe('gitlab core', () => { const target = 'https://gitlab.com/group/subgroup/project/blob/branch/folder/file.yaml'; const fetchUrl = - 'https://gitlab.com/api/v4/projects/12345/repository/files/folder%2Ffile.yaml/raw?ref=branch'; + 'https://gitlab.com/api/v4/projects/group%2Fsubgroup%2Fproject/repository/files/folder%2Ffile.yaml/raw?ref=branch'; await expect( getGitLabFileFetchUrl(target, configWithNoToken), ).resolves.toBe(fetchUrl); @@ -179,7 +164,7 @@ describe('gitlab core', () => { const target = 'https://gitlab.com/group/project/blob/blob/folder/file.yaml'; const fetchUrl = - 'https://gitlab.com/api/v4/projects/12345/repository/files/folder%2Ffile.yaml/raw?ref=blob'; + 'https://gitlab.com/api/v4/projects/group%2Fproject/repository/files/folder%2Ffile.yaml/raw?ref=blob'; await expect( getGitLabFileFetchUrl(target, configWithNoToken), ).resolves.toBe(fetchUrl); @@ -187,8 +172,49 @@ describe('gitlab core', () => { }); }); + describe('extractProjectPath', () => { + it('extracts project path from scoped route', () => { + const target = + 'https://gitlab.com/group/project/-/blob/branch/folder/file.yaml'; + expect(extractProjectPath(target, configWithNoToken)).toBe( + 'group/project', + ); + }); + + it('extracts project path from subgroup', () => { + const target = + 'https://gitlab.com/group/subgroup/project/-/blob/branch/folder/file.yaml'; + expect(extractProjectPath(target, configWithNoToken)).toBe( + 'group/subgroup/project', + ); + }); + + it('extracts project path from unscoped route', () => { + const target = + 'https://gitlab.com/group/project/blob/branch/folder/file.yaml'; + expect(extractProjectPath(target, configWithNoToken)).toBe( + 'group/project', + ); + }); + + it('extracts project path from self-hosted gitlab with relative path', () => { + const target = + 'https://gitlab.mycompany.com/gitlab/group/project/-/blob/branch/folder/file.yaml'; + expect(extractProjectPath(target, configSelfHosteWithRelativePath)).toBe( + 'group/project', + ); + }); + + it('throws error for invalid URLs without blob path', () => { + const target = 'https://gitlab.com/some/random/endpoint'; + expect(() => extractProjectPath(target, configWithNoToken)).toThrow( + 'Failed extracting project path from /some/random/endpoint. Url path must include /blob/.', + ); + }); + }); + describe('getGitLabRequestOptions', () => { - it('should return Authorization bearer header when a token is provided', () => { + it('should return Authorization bearer header when a token is provided', async () => { const token = '1234567890'; const result = getGitLabRequestOptions( configSelfHosteWithRelativePath, @@ -202,7 +228,7 @@ describe('gitlab core', () => { }); }); - it('should return Authorization bearer header using the config token when no token is provided', () => { + it('should return Authorization bearer header using the config token when no token is provided', async () => { const result = getGitLabRequestOptions(configSelfHosteWithRelativePath); expect(result).toEqual({ diff --git a/packages/integration/src/gitlab/core.ts b/packages/integration/src/gitlab/core.ts index 65360840b5..435e5a8a31 100644 --- a/packages/integration/src/gitlab/core.ts +++ b/packages/integration/src/gitlab/core.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import fetch from 'cross-fetch'; import { getGitLabIntegrationRelativePath, GitLabIntegrationConfig, @@ -28,22 +27,25 @@ import { * * Converts * from: https://gitlab.example.com/a/b/blob/master/c.yaml - * to: https://gitlab.com/api/v4/projects/projectId/repository/c.yaml?ref=master + * to: https://gitlab.com/api/v4/projects/a%2Fb/repository/files/c.yaml/raw?ref=master * -or- * from: https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath - * to: https://gitlab.com/api/v4/projects/projectId/repository/files/filepath?ref=branch + * to: https://gitlab.com/api/v4/projects/groupA%2Fteams%2FteamA%2FsubgroupA%2FrepoA/repository/files/filepath/raw?ref=branch * * @param url - A URL pointing to a file * @param config - The relevant provider config + * @param token - An optional auth token (not used in path extraction, kept for compatibility) * @public */ -export async function getGitLabFileFetchUrl( +export function getGitLabFileFetchUrl( url: string, config: GitLabIntegrationConfig, - token?: string, + _token?: string, ): Promise { - const projectID = await getProjectId(url, config, token); - return buildProjectUrl(url, projectID, config).toString(); + // Use project path directly instead of making an API call to get project ID + // Note: _token parameter kept for backward compatibility but not used for path extraction + const projectPath = extractProjectPath(url, config); + return Promise.resolve(buildProjectUrl(url, projectPath, config).toString()); } /** @@ -72,10 +74,10 @@ export function getGitLabRequestOptions( // Converts // from: https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath -// to: https://gitlab.com/api/v4/projects/projectId/repository/files/filepath?ref=branch +// to: https://gitlab.com/api/v4/projects/groupA%2Fteams%2FteamA%2FsubgroupA%2FrepoA/repository/files/filepath/raw?ref=branch export function buildProjectUrl( target: string, - projectID: Number, + projectPathOrID: string | Number, config: GitLabIntegrationConfig, ): URL { try { @@ -88,10 +90,12 @@ export function buildProjectUrl( const [branch, ...filePath] = branchAndFilePath.split('/'); const relativePath = getGitLabIntegrationRelativePath(config); + const projectIdentifier = encodeURIComponent(String(projectPathOrID)); + url.pathname = [ ...(relativePath ? [relativePath] : []), 'api/v4/projects', - projectID, + projectIdentifier, 'repository/files', encodeURIComponent(decodeURIComponent(filePath.join('/'))), 'raw', @@ -105,62 +109,33 @@ export function buildProjectUrl( } } -// Convert -// from: https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath -// to: The project ID that corresponds to the URL -export async function getProjectId( +/** + * Extracts the project path from a GitLab URL + * from: https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath + * to: groupA/teams/teamA/subgroupA/repoA + */ +export function extractProjectPath( target: string, config: GitLabIntegrationConfig, - token?: string, -): Promise { +): string { const url = new URL(target); if (!url.pathname.includes('/blob/')) { throw new Error( - `Failed converting ${url.pathname} to a project id. Url path must include /blob/.`, + `Failed extracting project path from ${url.pathname}. Url path must include /blob/.`, ); } - try { - let repo = url.pathname.split('/-/blob/')[0].split('/blob/')[0]; + let repo = url.pathname.split('/-/blob/')[0].split('/blob/')[0]; - // Get gitlab relative path - const relativePath = getGitLabIntegrationRelativePath(config); + // Get gitlab relative path + const relativePath = getGitLabIntegrationRelativePath(config); - // Check relative path exist and replace it if it's the case. - if (relativePath) { - repo = repo.replace(relativePath, ''); - } - - // Convert - // to: https://gitlab.com/api/v4/projects/groupA%2Fteams%2FsubgroupA%2FteamA%2Frepo - const repoIDLookup = new URL( - `${url.origin}${relativePath}/api/v4/projects/${encodeURIComponent( - repo.replace(/^\//, ''), - )}`, - ); - - const response = await fetch( - repoIDLookup.toString(), - getGitLabRequestOptions(config, token), - ); - - const data = await response.json(); - - if (!response.ok) { - if (response.status === 401) { - throw new Error( - 'GitLab Error: 401 - Unauthorized. The access token used is either expired, or does not have permission to read the project', - ); - } - - throw new Error( - `GitLab Error '${data.error}', ${data.error_description}`, - ); - } - - return Number(data.id); - } catch (e) { - throw new Error(`Could not get GitLab project ID for: ${target}, ${e}`); + // Check relative path exist and replace it if it's the case. + if (relativePath) { + repo = repo.replace(relativePath, ''); } + + // Remove leading slash + return repo.replace(/^\//, ''); } diff --git a/packages/integration/src/googleGcs/GoogleGcsIntegration.test.ts b/packages/integration/src/googleGcs/GoogleGcsIntegration.test.ts new file mode 100644 index 0000000000..5666a0aa5e --- /dev/null +++ b/packages/integration/src/googleGcs/GoogleGcsIntegration.test.ts @@ -0,0 +1,80 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ConfigReader } from '@backstage/config'; +import { GoogleGcsIntegration } from './GoogleGcsIntegration'; + +describe('GoogleGcsIntegration', () => { + it('has a working factory', () => { + const integrations = GoogleGcsIntegration.factory({ + config: new ConfigReader({ + integrations: { + googleGcs: { + clientEmail: 'someone@example.com', + privateKey: 'fake-key', + }, + }, + }), + }); + expect(integrations.list().length).toBe(1); + expect(integrations.list()[0].config.host).toBe('storage.cloud.google.com'); + }); + + it('returns default integration when no config', () => { + const integrations = GoogleGcsIntegration.factory({ + config: new ConfigReader({ + integrations: {}, + }), + }); + expect(integrations.list().length).toBe(1); + expect(integrations.list()[0].config.host).toBe('storage.cloud.google.com'); + }); + + it('returns the basics', () => { + const integration = new GoogleGcsIntegration({ + host: 'storage.cloud.google.com', + }); + expect(integration.type).toBe('googleGcs'); + expect(integration.title).toBe('storage.cloud.google.com'); + }); + + describe('resolveUrl', () => { + it('works for valid urls', () => { + const integration = new GoogleGcsIntegration({ + host: 'storage.cloud.google.com', + }); + + expect( + integration.resolveUrl({ + url: 'https://storage.cloud.google.com/bucket/catalog-info.yaml', + base: 'https://storage.cloud.google.com/bucket/catalog-info.yaml', + }), + ).toBe('https://storage.cloud.google.com/bucket/catalog-info.yaml'); + }); + }); + + it('resolve edit URL', () => { + const integration = new GoogleGcsIntegration({ + host: 'storage.cloud.google.com', + }); + + expect( + integration.resolveEditUrl( + 'https://storage.cloud.google.com/bucket/catalog-info.yaml', + ), + ).toBe('https://storage.cloud.google.com/bucket/catalog-info.yaml'); + }); +}); diff --git a/packages/integration/src/googleGcs/GoogleGcsIntegration.ts b/packages/integration/src/googleGcs/GoogleGcsIntegration.ts new file mode 100644 index 0000000000..11642a231c --- /dev/null +++ b/packages/integration/src/googleGcs/GoogleGcsIntegration.ts @@ -0,0 +1,71 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { basicIntegrations, defaultScmResolveUrl } from '../helpers'; +import { ScmIntegration, ScmIntegrationsFactory } from '../types'; +import { + GoogleGcsIntegrationConfig, + readGoogleGcsIntegrationConfig, + GOOGLE_GCS_HOST, +} from './config'; + +/** + * A Google Cloud Storage based integration. + * + * @public + */ +export class GoogleGcsIntegration implements ScmIntegration { + static factory: ScmIntegrationsFactory = ({ + config, + }) => { + const gcsConfig = config.has('integrations.googleGcs') + ? readGoogleGcsIntegrationConfig( + config.getConfig('integrations.googleGcs'), + ) + : { host: GOOGLE_GCS_HOST }; + + return basicIntegrations( + [new GoogleGcsIntegration(gcsConfig)], + i => i.config.host, + ); + }; + + get type(): string { + return 'googleGcs'; + } + + get title(): string { + return this.integrationConfig.host; + } + + get config(): GoogleGcsIntegrationConfig { + return this.integrationConfig; + } + + constructor(private readonly integrationConfig: GoogleGcsIntegrationConfig) {} + + resolveUrl(options: { + url: string; + base: string; + lineNumber?: number | undefined; + }): string { + return defaultScmResolveUrl(options); + } + + resolveEditUrl(url: string): string { + return url; + } +} diff --git a/packages/integration/src/googleGcs/config.test.ts b/packages/integration/src/googleGcs/config.test.ts index 892440c64a..d074cb7129 100644 --- a/packages/integration/src/googleGcs/config.test.ts +++ b/packages/integration/src/googleGcs/config.test.ts @@ -33,13 +33,16 @@ describe('readGoogleGcsIntegrationConfig', () => { }), ); expect(output).toEqual({ + host: 'storage.cloud.google.com', privateKey: 'fake-key', clientEmail: 'someone@example.com', }); }); - it('does not fail when config is not set', () => { + it('returns default config when config is not set', () => { const output = readGoogleGcsIntegrationConfig(buildConfig({})); - expect(output).toEqual({}); + expect(output).toEqual({ + host: 'storage.cloud.google.com', + }); }); }); diff --git a/packages/integration/src/googleGcs/config.ts b/packages/integration/src/googleGcs/config.ts index 3c03204e4c..9421b9dfba 100644 --- a/packages/integration/src/googleGcs/config.ts +++ b/packages/integration/src/googleGcs/config.ts @@ -16,12 +16,22 @@ import { Config } from '@backstage/config'; +/** + * The default Google Cloud Storage host. + * + */ +export const GOOGLE_GCS_HOST = 'storage.cloud.google.com'; + /** * The configuration parameters for a single Google Cloud Storage provider. * * @public */ export type GoogleGcsIntegrationConfig = { + /** + * The host of the target that this matches on. + */ + host: string; /** * Service account email used to authenticate requests. */ @@ -42,15 +52,19 @@ export function readGoogleGcsIntegrationConfig( config: Config, ): GoogleGcsIntegrationConfig { if (!config) { - return {}; + return { host: GOOGLE_GCS_HOST }; } if (!config.has('clientEmail') && !config.has('privateKey')) { - return {}; + return { host: GOOGLE_GCS_HOST }; } const privateKey = config.getString('privateKey').split('\\n').join('\n'); - const clientEmail = config.getString('clientEmail'); - return { clientEmail: clientEmail, privateKey: privateKey }; + + return { + host: GOOGLE_GCS_HOST, + clientEmail, + privateKey, + }; } diff --git a/packages/integration/src/googleGcs/index.ts b/packages/integration/src/googleGcs/index.ts index 3d7beae59d..c30138fc3e 100644 --- a/packages/integration/src/googleGcs/index.ts +++ b/packages/integration/src/googleGcs/index.ts @@ -16,3 +16,4 @@ export { readGoogleGcsIntegrationConfig } from './config'; export type { GoogleGcsIntegrationConfig } from './config'; +export { GoogleGcsIntegration } from './GoogleGcsIntegration'; diff --git a/packages/integration/src/harness/core.test.ts b/packages/integration/src/harness/core.test.ts index 90f37aaa08..cb4b06f2da 100644 --- a/packages/integration/src/harness/core.test.ts +++ b/packages/integration/src/harness/core.test.ts @@ -15,7 +15,7 @@ */ import { setupServer } from 'msw/node'; -import { registerMswTestHooks } from '../helpers'; +import { registerMswTestHooks } from '@backstage/backend-test-utils'; import { HarnessIntegrationConfig } from './config'; import { getHarnessArchiveUrl, diff --git a/packages/integration/src/helpers.test.ts b/packages/integration/src/helpers.test.ts index f1dd2e7ac8..b12236b031 100644 --- a/packages/integration/src/helpers.test.ts +++ b/packages/integration/src/helpers.test.ts @@ -19,6 +19,7 @@ import { basicIntegrations, defaultScmResolveUrl, isValidHost, + parseGitUrlSafe, } from './helpers'; describe('basicIntegrations', () => { @@ -317,3 +318,99 @@ describe('defaultScmResolveUrl', () => { ).toBe('https://b.com/b.yaml'); }); }); + +describe('parseGitUrlSafe', () => { + it('parses a valid GitHub blob URL', () => { + const result = parseGitUrlSafe( + 'https://github.com/owner/repo/blob/main/path/to/file.yaml', + ); + expect(result.owner).toBe('owner'); + expect(result.name).toBe('repo'); + expect(result.ref).toBe('main'); + expect(result.filepath).toBe('path/to/file.yaml'); + }); + + it('rejects URLs with encoded path traversal in filepath', () => { + expect(() => + parseGitUrlSafe( + 'https://github.com/octocat/Hello-World/blob/main/%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fuser/repos', + ), + ).toThrow('path traversal'); + }); + + it('rejects URLs with double-encoded path traversal in filepath', () => { + expect(() => + parseGitUrlSafe( + 'https://github.com/octocat/Hello-World/blob/main/foo%2f%2e%2e%2fbar%2f%2e%2e%2f%2e%2e%2fsecret', + ), + ).toThrow('path traversal'); + }); + + it('rejects URLs with uppercase %2E encoding in filepath', () => { + expect(() => + parseGitUrlSafe( + 'https://github.com/octocat/Hello-World/blob/main/%2E%2E%2F%2E%2E%2Fuser/repos', + ), + ).toThrow('path traversal'); + }); + + it('rejects URLs with mixed-case percent encoding', () => { + expect(() => + parseGitUrlSafe( + 'https://github.com/octocat/Hello-World/blob/main/%2E%2e%2Fuser/repos', + ), + ).toThrow('path traversal'); + }); + + it('rejects URLs where git-url-parse leaves percent-encoded traversal segments', () => { + expect(() => + parseGitUrlSafe( + 'https://github.com/octocat/Hello-World/blob/main/%252e%252e%252fuser/repos', + ), + ).toThrow('path traversal'); + }); + + it('rejects URLs with triple-encoded path traversal', () => { + expect(() => + parseGitUrlSafe( + 'https://github.com/octocat/Hello-World/blob/main/%25252e%25252e%25252fuser/repos', + ), + ).toThrow('path traversal'); + }); + + it('rejects URLs with literal .. in the middle of the filepath', () => { + // URL normalization resolves foo/../bar to just bar, so the filepath + // won't contain traversal. But we verify parseGitUrlSafe still handles + // the resolved path safely. + const result = parseGitUrlSafe( + 'https://github.com/owner/repo/blob/main/foo/../bar', + ); + expect(result.filepath).toBe('bar'); + }); + + it('handles literal ../ that gets normalized away by URL parsing', () => { + // Literal ../../../ gets resolved by the URL constructor before + // git-url-parse sees it. This mangles owner/name but the filepath + // is empty, so parseGitUrlSafe doesn't reject it. Downstream + // functions reject these via their own validation. + const result = parseGitUrlSafe( + 'https://github.com/owner/repo/blob/main/../../../user/repos', + ); + expect(result.filepath).toBe(''); + expect(result.owner).not.toBe('owner'); + }); + + it('allows filenames that contain dots but are not traversal', () => { + const result = parseGitUrlSafe( + 'https://github.com/owner/repo/blob/main/path/to/some..file.yaml', + ); + expect(result.filepath).toBe('path/to/some..file.yaml'); + }); + + it('allows URLs without a filepath', () => { + const result = parseGitUrlSafe('https://github.com/owner/repo'); + expect(result.owner).toBe('owner'); + expect(result.name).toBe('repo'); + expect(result.filepath).toBe(''); + }); +}); diff --git a/packages/integration/src/helpers.ts b/packages/integration/src/helpers.ts index f56d360325..9ac2769b76 100644 --- a/packages/integration/src/helpers.ts +++ b/packages/integration/src/helpers.ts @@ -18,6 +18,38 @@ import parseGitUrl from 'git-url-parse'; import { trimEnd } from 'lodash'; import { ScmIntegration, ScmIntegrationsGroup } from './types'; +/** + * Wraps git-url-parse and rejects URLs whose filepath contains path traversal + * segments. Without this check, a URL like + * `https://github.com/o/r/blob/main/%2e%2e%2f%2e%2e%2fuser/repos` would be + * decoded to `../../user/repos` and could escape the expected API path when + * interpolated into provider API URLs. + */ +export function parseGitUrlSafe(url: string) { + const parsed = parseGitUrl(url); + if (parsed.filepath) { + let decoded = parsed.filepath; + let previous; + do { + previous = decoded; + try { + decoded = decodeURIComponent(decoded); + } catch { + break; + } + } while (decoded !== previous); + + if ( + decoded.split('/').some(segment => segment === '..' || segment === '.') + ) { + throw new Error( + 'Invalid SCM URL: path traversal is not allowed in the URL', + ); + } + } + return parsed; +} + /** Checks whether the given argument is a valid URL hostname */ export function isValidHost(host: string): boolean { const check = new URL('http://example.com'); @@ -84,7 +116,7 @@ export function defaultScmResolveUrl(options: { if (url.startsWith('/')) { // If it is an absolute path, move relative to the repo root - const { href, filepath } = parseGitUrl(base); + const { href, filepath } = parseGitUrlSafe(base); updated = new URL(href); @@ -106,20 +138,3 @@ export function defaultScmResolveUrl(options: { } return updated.toString(); } - -/** - * Sets up handlers for request mocking - * - * Copied from test-utils, as that is a frontend-only package - * - * @param worker - service worker - */ -export function registerMswTestHooks(worker: { - listen: (t: any) => void; - close: () => void; - resetHandlers: () => void; -}) { - beforeAll(() => worker.listen({ onUnhandledRequest: 'error' })); - afterAll(() => worker.close()); - afterEach(() => worker.resetHandlers()); -} diff --git a/packages/integration/src/index.ts b/packages/integration/src/index.ts index a625f5d83c..1c0919fcbd 100644 --- a/packages/integration/src/index.ts +++ b/packages/integration/src/index.ts @@ -24,7 +24,6 @@ export * from './awsS3'; export * from './awsCodeCommit'; export * from './azureBlobStorage'; export * from './azure'; -export * from './bitbucket'; export * from './bitbucketCloud'; export * from './bitbucketServer'; export * from './gerrit'; diff --git a/packages/integration/src/registry.ts b/packages/integration/src/registry.ts index 2e8111903b..6aca5b197b 100644 --- a/packages/integration/src/registry.ts +++ b/packages/integration/src/registry.ts @@ -19,7 +19,6 @@ import { AwsS3Integration } from './awsS3/AwsS3Integration'; import { AwsCodeCommitIntegration } from './awsCodeCommit'; import { AzureIntegration } from './azure/AzureIntegration'; import { BitbucketCloudIntegration } from './bitbucketCloud/BitbucketCloudIntegration'; -import { BitbucketIntegration } from './bitbucket/BitbucketIntegration'; import { BitbucketServerIntegration } from './bitbucketServer/BitbucketServerIntegration'; import { GerritIntegration } from './gerrit/GerritIntegration'; import { GithubIntegration } from './github/GithubIntegration'; @@ -39,10 +38,6 @@ export interface ScmIntegrationRegistry awsCodeCommit: ScmIntegrationsGroup; azureBlobStorage: ScmIntegrationsGroup; azure: ScmIntegrationsGroup; - /** - * @deprecated in favor of `bitbucketCloud` and `bitbucketServer` - */ - bitbucket: ScmIntegrationsGroup; bitbucketCloud: ScmIntegrationsGroup; bitbucketServer: ScmIntegrationsGroup; gerrit: ScmIntegrationsGroup; diff --git a/packages/integration/src/setupTests.ts b/packages/integration/src/setupTests.ts index d3232290a7..56d793eb48 100644 --- a/packages/integration/src/setupTests.ts +++ b/packages/integration/src/setupTests.ts @@ -14,4 +14,5 @@ * limitations under the License. */ -export {}; +global.setImmediate = require('node:timers').setImmediate; +global.clearImmediate = require('node:timers').clearImmediate; diff --git a/packages/module-federation-common/.eslintrc.js b/packages/module-federation-common/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/packages/module-federation-common/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/packages/module-federation-common/CHANGELOG.md b/packages/module-federation-common/CHANGELOG.md new file mode 100644 index 0000000000..25d751176f --- /dev/null +++ b/packages/module-federation-common/CHANGELOG.md @@ -0,0 +1,39 @@ +# @backstage/module-federation-common + +## 0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0 + - @backstage/config@1.3.7 + +## 0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/config@1.3.7-next.0 + +## 0.1.2 + +### Patch Changes + +- 0cb5646: Fixed the `@mui/material/styles` shared dependency key by removing a trailing slash that caused module resolution failures with MUI package exports. + +## 0.1.2-next.0 + +### Patch Changes + +- 0cb5646: Fixed the `@mui/material/styles` shared dependency key by removing a trailing slash that caused module resolution failures with MUI package exports. +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.1.0 + +### Minor Changes + +- ce12dec: Added new `@backstage/module-federation-common` package that provides shared types, default configurations, and runtime utilities for module federation. It includes `loadModuleFederationHostShared` for loading shared dependencies in parallel at runtime, `defaultHostSharedDependencies` and `defaultRemoteSharedDependencies` for consistent dependency configuration, and types such as `HostSharedDependencies`, `RemoteSharedDependencies`, and `RuntimeSharedDependenciesGlobal`. diff --git a/packages/module-federation-common/README.md b/packages/module-federation-common/README.md new file mode 100644 index 0000000000..fcf1f5726c --- /dev/null +++ b/packages/module-federation-common/README.md @@ -0,0 +1,11 @@ +# @backstage/module-federation-common + +This package provides a helper library for module federation, enabling a consistent management of shared dependencies +in both the module federation host (frontend application) at runtime, and remote modules at build-time through the CLI. + +It provides: + +- TypeScript types for both host and remote shared dependency definitions, +- a default list of shared dependencies (React, React Router, Material-UI, etc.) for both the host and remote modules, +- utilities used by the CLI to resolve versions of the shared dependencies for the host at build-time, +- utilities available for the frontend application to provide the list of resolved shared dependencies at runtime. diff --git a/packages/module-federation-common/catalog-info.yaml b/packages/module-federation-common/catalog-info.yaml new file mode 100644 index 0000000000..a5843ad116 --- /dev/null +++ b/packages/module-federation-common/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-module-federation-common + title: '@backstage/module-federation-common' + description: Helper library for module federation +spec: + lifecycle: experimental + type: backstage-common-library + owner: framework-maintainers diff --git a/packages/module-federation-common/knip-report.md b/packages/module-federation-common/knip-report.md new file mode 100644 index 0000000000..1f5919a22e --- /dev/null +++ b/packages/module-federation-common/knip-report.md @@ -0,0 +1,19 @@ +# Knip report + +## Unused dependencies (3) + +| Name | Location | Severity | +| :------------------------- | :---------------- | :------- | +| @module-federation/runtime | package.json:42:6 | error | +| @backstage/config | package.json:39:6 | error | +| @backstage/types | package.json:41:6 | error | + +## Unused devDependencies (4) + +| Name | Location | Severity | +| :-------------------- | :---------------- | :------- | +| @backstage/test-utils | package.json:46:6 | error | +| react-router-dom | package.json:50:6 | error | +| react-dom | package.json:49:6 | error | +| react | package.json:48:6 | error | + diff --git a/packages/module-federation-common/package.json b/packages/module-federation-common/package.json new file mode 100644 index 0000000000..35f6f6dd4e --- /dev/null +++ b/packages/module-federation-common/package.json @@ -0,0 +1,68 @@ +{ + "name": "@backstage/module-federation-common", + "version": "0.1.3", + "description": "Helper library for module federation", + "backstage": { + "role": "common-library" + }, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "module": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "keywords": [ + "backstage" + ], + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "packages/module-federation-common" + }, + "license": "Apache-2.0", + "sideEffects": false, + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "backstage-cli package build", + "clean": "backstage-cli package clean", + "lint": "backstage-cli package lint", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "test": "backstage-cli package test" + }, + "dependencies": { + "@backstage/config": "workspace:^", + "@backstage/errors": "workspace:^", + "@backstage/types": "workspace:^", + "@module-federation/runtime": "^2.3.3" + }, + "devDependencies": { + "@backstage/cli": "workspace:^", + "@backstage/test-utils": "workspace:^", + "@types/react": "^18.0.0", + "react": "^18.0.2", + "react-dom": "^18.0.2", + "react-router-dom": "^6.30.2" + }, + "peerDependencies": { + "@emotion/react": "^11.10.5", + "@material-ui/core": "^4.12.2", + "@material-ui/styles": "^4.10.0", + "@mui/material": "^5.12.2", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0", + "react-router": "^6.30.2", + "react-router-dom": "^6.30.2" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } +} diff --git a/packages/module-federation-common/report.api.md b/packages/module-federation-common/report.api.md new file mode 100644 index 0000000000..08c28366df --- /dev/null +++ b/packages/module-federation-common/report.api.md @@ -0,0 +1,62 @@ +## API Report File for "@backstage/module-federation-common" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +// @public +export const BACKSTAGE_RUNTIME_SHARED_DEPENDENCIES_GLOBAL = + '__@backstage/module_federation_shared_dependencies__'; + +// @public +export function defaultHostSharedDependencies(): HostSharedDependencies; + +// @public +export function defaultRemoteSharedDependencies(): RemoteSharedDependencies; + +// @public +export type HostSharedDependencies = { + [name: string]: { + singleton: boolean; + eager: boolean; + requiredVersion: string; + version?: string; + }; +}; + +// @public +export type LoadedRuntimeSharedDependency = { + version: string; + lib: () => unknown; + shareConfig: HostSharedDependencies[string]; +}; + +// @public +export function loadModuleFederationHostShared(options?: { + onError?: (error: Error) => void; +}): Promise>; + +// @public +export type RemoteSharedDependencies = { + [name: string]: { + singleton: boolean; + eager: boolean; + import?: false | string; + requiredVersion: string; + }; +}; + +// @public +export type RuntimeSharedDependenciesGlobal = { + items: Array<{ + name: string; + version: string; + lib: () => Promise; + shareConfig: { + singleton: boolean; + requiredVersion: string; + eager: boolean; + }; + }>; + version: 'v1'; +}; +``` diff --git a/packages/module-federation-common/src/defaults.test.ts b/packages/module-federation-common/src/defaults.test.ts new file mode 100644 index 0000000000..f38379e1c9 --- /dev/null +++ b/packages/module-federation-common/src/defaults.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + defaultHostSharedDependencies, + defaultRemoteSharedDependencies, +} from './defaults'; + +describe('defaultHostSharedDependencies', () => { + it('should return shared dependencies with the host-only eager property', () => { + const result = defaultHostSharedDependencies(); + + expect(result.react).toEqual({ + requiredVersion: '*', + singleton: true, + eager: true, + }); + }); +}); + +describe('defaultRemoteSharedDependencies', () => { + it('should return shared dependencies with the remote-only import property', () => { + const result = defaultRemoteSharedDependencies(); + + expect(result.react).toEqual({ + requiredVersion: '*', + singleton: true, + eager: false, + import: false, + }); + }); +}); diff --git a/packages/module-federation-common/src/defaults.ts b/packages/module-federation-common/src/defaults.ts new file mode 100644 index 0000000000..644d4ee480 --- /dev/null +++ b/packages/module-federation-common/src/defaults.ts @@ -0,0 +1,104 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { RemoteSharedDependencies, HostSharedDependencies } from './types'; + +/** + * The list of default shared dependencies, expected to be the same + * for both the module federation host and remotes. + * + * @internal + */ +const defaultSharedDependencies = { + // React + react: { + host: {}, + remote: { import: false }, + }, + 'react-dom': { + host: {}, + remote: { import: false }, + }, + // React Router + 'react-router': { + host: {}, + remote: { import: false }, + }, + 'react-router-dom': { + host: {}, + remote: { import: false }, + }, + // MUI v4 + // not setting import: false for MUI packages as this + // will break once Backstage moves to BUI + '@material-ui/core/styles': { + host: {}, + remote: {}, + }, + '@material-ui/styles': { + host: {}, + remote: {}, + }, + // MUI v5 + // not setting import: false for MUI packages as this + // will break once Backstage moves to BUI + '@mui/material/styles': { + host: {}, + remote: {}, + }, + '@emotion/react': { + host: {}, + remote: {}, + }, +} as const; + +/** + * Returns the list of default shared dependencies for the host, with host-only properties. + * + * @public + */ +export function defaultHostSharedDependencies(): HostSharedDependencies { + return Object.fromEntries( + Object.entries(defaultSharedDependencies).map(([name, p]) => [ + name, + { + eager: true, + singleton: true, + requiredVersion: '*', + ...p.host, + }, + ]), + ); +} + +/** + * Returns the list of default shared dependencies for the remote, with remote-only properties. + * + * @public + */ +export function defaultRemoteSharedDependencies(): RemoteSharedDependencies { + return Object.fromEntries( + Object.entries(defaultSharedDependencies).map(([name, p]) => [ + name, + { + eager: false, + singleton: true, + requiredVersion: '*', + ...p.remote, + }, + ]), + ); +} diff --git a/packages/module-federation-common/src/index.ts b/packages/module-federation-common/src/index.ts new file mode 100644 index 0000000000..290373ada3 --- /dev/null +++ b/packages/module-federation-common/src/index.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Helper library for module federation, enabling a consistent management of shared dependencies + * in both the module federation host (frontend application) at runtime, + * and remote modules at build time through the CLI. + * + * @packageDocumentation + */ + +export { loadModuleFederationHostShared } from './loadModuleFederationHostShared'; +export { + defaultHostSharedDependencies, + defaultRemoteSharedDependencies, +} from './defaults'; +export { + BACKSTAGE_RUNTIME_SHARED_DEPENDENCIES_GLOBAL, + type HostSharedDependencies, + type RemoteSharedDependencies, + type LoadedRuntimeSharedDependency, + type RuntimeSharedDependenciesGlobal, +} from './types'; diff --git a/packages/module-federation-common/src/loadModuleFederationHostShared.test.ts b/packages/module-federation-common/src/loadModuleFederationHostShared.test.ts new file mode 100644 index 0000000000..c62a8b6112 --- /dev/null +++ b/packages/module-federation-common/src/loadModuleFederationHostShared.test.ts @@ -0,0 +1,315 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { loadModuleFederationHostShared } from './loadModuleFederationHostShared'; +import { + BACKSTAGE_RUNTIME_SHARED_DEPENDENCIES_GLOBAL, + RuntimeSharedDependenciesGlobal, +} from './types'; +import { ForwardedError } from '@backstage/errors'; + +const globalSpy = jest.fn(); +Object.defineProperty(global, BACKSTAGE_RUNTIME_SHARED_DEPENDENCIES_GLOBAL, { + get: globalSpy, +}); + +function mockGlobal(items: RuntimeSharedDependenciesGlobal['items']) { + globalSpy.mockReturnValue({ items, version: 'v1' }); +} + +describe('loadModuleFederationHostShared', () => { + afterEach(jest.resetAllMocks); + + it('should preload and return shared dependencies keyed by name', async () => { + const reactMock = { default: { React: 'react' } }; + const reactDomMock = { default: { ReactDom: 'react-dom' } }; + + mockGlobal([ + { + name: 'react', + version: '18.2.0', + lib: async () => reactMock, + shareConfig: { + singleton: true, + requiredVersion: '*', + eager: false, + }, + }, + { + name: 'react-dom', + version: '18.2.0', + lib: async () => reactDomMock, + shareConfig: { + singleton: true, + requiredVersion: '*', + eager: false, + }, + }, + ]); + const shared = await loadModuleFederationHostShared(); + + expect(shared).toEqual({ + react: { + version: '18.2.0', + lib: expect.any(Function), + shareConfig: { + singleton: true, + requiredVersion: '*', + eager: false, + }, + }, + 'react-dom': { + version: '18.2.0', + lib: expect.any(Function), + shareConfig: { + singleton: true, + requiredVersion: '*', + eager: false, + }, + }, + }); + + expect(shared?.react?.lib()).toBe(reactMock); + expect(shared?.['react-dom']?.lib()).toBe(reactDomMock); + }); + + it('should preserve shareConfig properties from each remote', async () => { + mockGlobal([ + { + name: 'react', + version: '18.2.0', + lib: async () => ({ default: {} }), + shareConfig: { + singleton: true, + requiredVersion: '^18.0.0', + eager: true, + }, + }, + { + name: 'lodash', + version: '4.17.21', + lib: async () => ({ default: {} }), + shareConfig: { + singleton: false, + requiredVersion: '^4.17.0', + eager: false, + }, + }, + ]); + const shared = await loadModuleFederationHostShared(); + + expect(shared).toEqual({ + react: { + version: '18.2.0', + lib: expect.any(Function), + shareConfig: { + singleton: true, + requiredVersion: '^18.0.0', + eager: true, + }, + }, + lodash: { + version: '4.17.21', + lib: expect.any(Function), + shareConfig: { + singleton: false, + requiredVersion: '^4.17.0', + eager: false, + }, + }, + }); + }); + + it('should handle empty items', async () => { + mockGlobal([]); + expect(await loadModuleFederationHostShared()).toEqual({}); + }); + + it('should throw on unsupported or missing version', async () => { + globalSpy.mockReturnValue({ items: [], version: 'v2' }); + await expect(loadModuleFederationHostShared()).rejects.toThrow( + 'Unsupported version of the runtime shared dependencies: v2', + ); + + globalSpy.mockReturnValue({ items: [] }); + await expect(loadModuleFederationHostShared()).rejects.toThrow( + 'Unsupported version of the runtime shared dependencies: undefined', + ); + + globalSpy.mockReturnValue(undefined); + await expect(loadModuleFederationHostShared()).rejects.toThrow( + 'Unsupported version of the runtime shared dependencies: undefined', + ); + }); + + it('should report errors via onError and still return successful modules', async () => { + const mockError = new Error('Module import failed'); + const reactMock = { default: { React: 'react' } }; + const lodashMock = { default: { _: 'lodash' } }; + + mockGlobal([ + { + name: 'react', + version: '18.2.0', + lib: async () => reactMock, + shareConfig: { + singleton: true, + requiredVersion: '^18.0.0', + eager: false, + }, + }, + { + name: 'failing-module', + version: '0.0.0', + lib: async () => { + throw mockError; + }, + shareConfig: { + singleton: true, + requiredVersion: '*', + eager: false, + }, + }, + { + name: 'lodash', + version: '4.17.21', + lib: async () => lodashMock, + shareConfig: { + singleton: true, + requiredVersion: '^4.17.0', + eager: false, + }, + }, + ]); + + const errors: Error[] = []; + const shared = await loadModuleFederationHostShared({ + onError: err => errors.push(err), + }); + + expect(errors).toHaveLength(1); + expect(errors[0]).toBeInstanceOf(ForwardedError); + expect(errors[0].message).toContain( + 'Failed to preload module federation shared dependency', + ); + expect(errors[0]).toHaveProperty('cause', mockError); + + expect(shared).toEqual({ + react: { + version: '18.2.0', + lib: expect.any(Function), + shareConfig: { + singleton: true, + requiredVersion: '^18.0.0', + eager: false, + }, + }, + lodash: { + version: '4.17.21', + lib: expect.any(Function), + shareConfig: { + singleton: true, + requiredVersion: '^4.17.0', + eager: false, + }, + }, + }); + + expect(shared?.react?.lib()).toBe(reactMock); + expect(shared?.lodash?.lib()).toBe(lodashMock); + }); + + it('should throw on failure when no onError is provided', async () => { + const mockError = new Error('Module import failed'); + + mockGlobal([ + { + name: 'react', + version: '18.2.0', + lib: async () => ({ default: {} }), + shareConfig: { + singleton: true, + requiredVersion: '*', + eager: false, + }, + }, + { + name: 'failing-module', + version: '0.0.0', + lib: async () => { + throw mockError; + }, + shareConfig: { + singleton: true, + requiredVersion: '*', + eager: false, + }, + }, + ]); + + await expect(loadModuleFederationHostShared()).rejects.toMatchObject({ + message: expect.stringContaining( + 'Failed to preload module federation shared dependency', + ), + cause: mockError, + }); + }); + + it('should report multiple errors via onError', async () => { + const mockError1 = new Error('First module failed'); + const mockError2 = new Error('Second module failed'); + + mockGlobal([ + { + name: 'failing-module-1', + version: '0.0.0', + lib: async () => { + throw mockError1; + }, + shareConfig: { + singleton: true, + requiredVersion: '*', + eager: false, + }, + }, + { + name: 'failing-module-2', + version: '0.0.0', + lib: async () => { + throw mockError2; + }, + shareConfig: { + singleton: true, + requiredVersion: '*', + eager: false, + }, + }, + ]); + + const errors: Error[] = []; + const shared = await loadModuleFederationHostShared({ + onError: err => errors.push(err), + }); + + expect(errors).toHaveLength(2); + expect(errors[0]).toBeInstanceOf(ForwardedError); + expect(errors[0]).toHaveProperty('cause', mockError1); + expect(errors[1]).toBeInstanceOf(ForwardedError); + expect(errors[1]).toHaveProperty('cause', mockError2); + + expect(shared).toEqual({}); + }); +}); diff --git a/packages/module-federation-common/src/loadModuleFederationHostShared.ts b/packages/module-federation-common/src/loadModuleFederationHostShared.ts new file mode 100644 index 0000000000..dd350e583a --- /dev/null +++ b/packages/module-federation-common/src/loadModuleFederationHostShared.ts @@ -0,0 +1,71 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + BACKSTAGE_RUNTIME_SHARED_DEPENDENCIES_GLOBAL, + LoadedRuntimeSharedDependency, + RuntimeSharedDependenciesGlobal, +} from './types'; +import { ForwardedError } from '@backstage/errors'; + +/** + * Preloads and builds the list of shared dependencies provided to the module federation runtime. + * + * @public + */ +export async function loadModuleFederationHostShared(options?: { + onError?: (error: Error) => void; +}): Promise> { + const { items = [], version } = + ( + window as { + [BACKSTAGE_RUNTIME_SHARED_DEPENDENCIES_GLOBAL]?: RuntimeSharedDependenciesGlobal; + } + )[BACKSTAGE_RUNTIME_SHARED_DEPENDENCIES_GLOBAL] ?? {}; + if (version !== 'v1') { + throw new Error( + `Unsupported version of the runtime shared dependencies: ${version}`, + ); + } + + const results = await Promise.allSettled(items.map(item => item.lib())); + + const shared: Record = {}; + for (let i = 0; i < results.length; i++) { + const result = results[i]; + if (result.status === 'fulfilled') { + const mod = result.value; + const item = items[i]; + shared[item.name] = { + version: item.version, + lib: () => mod, + shareConfig: item.shareConfig, + }; + } else { + const error = new ForwardedError( + 'Failed to preload module federation shared dependency', + result.reason, + ); + if (options?.onError) { + options.onError(error); + } else { + throw error; + } + } + } + + return shared; +} diff --git a/packages/module-federation-common/src/types.ts b/packages/module-federation-common/src/types.ts new file mode 100644 index 0000000000..ff528d0844 --- /dev/null +++ b/packages/module-federation-common/src/types.ts @@ -0,0 +1,93 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * The name of the global object used to pass shared dependencies from the CLI to the module federation runtime. + * + * @public + */ +export const BACKSTAGE_RUNTIME_SHARED_DEPENDENCIES_GLOBAL = + '__@backstage/module_federation_shared_dependencies__'; + +/** + * Shared dependencies configuration in module federation remote modules. + * + * @public + */ +export type RemoteSharedDependencies = { + [name: string]: { + /** Whether to share this dependency as a singleton */ + singleton: boolean; + /** Whether to load this dependency eagerly */ + eager: boolean; + /** Provided module for fallback. Set to false to not provide a fallback, or a custom import path. */ + import?: false | string; + /** Required version range. */ + requiredVersion: string; + }; +}; + +/** + * Shared dependencies configuration in module federation host modules. + * + * @public + */ +export type HostSharedDependencies = { + [name: string]: { + /** Whether to share this dependency as a singleton */ + singleton: boolean; + /** Whether to load this dependency eagerly */ + eager: boolean; + /** Required version range. */ + requiredVersion: string; + /** Version of the shared dependency. Will be resolved at build time by default but can be overridden, but not completely removed. */ + version?: string; + }; +}; + +/** + * A single shared runtime dependency from the CLI that has been loaded and ready to be used in the module federation runtime. + * + * @public + */ +export type LoadedRuntimeSharedDependency = { + version: string; + lib: () => unknown; + shareConfig: HostSharedDependencies[string]; +}; + +/** + * The global object used to pass shared dependencies from the CLI to the module federation runtime. + * + * @remarks + * + * Stored in {@link BACKSTAGE_RUNTIME_SHARED_DEPENDENCIES_GLOBAL}. + * + * @public + */ +export type RuntimeSharedDependenciesGlobal = { + items: Array<{ + name: string; + version: string; + lib: () => Promise; + shareConfig: { + singleton: boolean; + requiredVersion: string; + eager: boolean; + }; + }>; + version: 'v1'; +}; diff --git a/packages/opaque-internal/knip-report.md b/packages/opaque-internal/knip-report.md index b49ecc8f5f..41b7d73c60 100644 --- a/packages/opaque-internal/knip-report.md +++ b/packages/opaque-internal/knip-report.md @@ -2,8 +2,8 @@ ## Unused devDependencies (2) -| Name | Location | Severity | -| :------------------------ | :----------- | :------- | -| @testing-library/jest-dom | packages/opaque-internal/package.json | error | -| @testing-library/react | packages/opaque-internal/package.json | error | +| Name | Location | Severity | +| :------------------------ | :---------------- | :------- | +| @testing-library/jest-dom | package.json:27:6 | error | +| @testing-library/react | package.json:28:6 | error | diff --git a/packages/repo-tools/CHANGELOG.md b/packages/repo-tools/CHANGELOG.md index d9ef90fe21..3a5b5e7587 100644 --- a/packages/repo-tools/CHANGELOG.md +++ b/packages/repo-tools/CHANGELOG.md @@ -1,5 +1,268 @@ # @backstage/repo-tools +## 0.17.1 + +### Patch Changes + +- 2e5c5f8: Bumped `glob` dependency from v7/v8/v11 to v13 to address security vulnerabilities in older versions. Bumped `rollup` from v4.27 to v4.59+ to fix a high severity path traversal vulnerability (GHSA-mw96-cpmx-2vgc). +- 482ceed: Migrated from `assertError` to `toError` for error handling. +- 377de36: Fixed the `peer-deps` command to only require peer dependencies that are actually referenced by each package, rather than requiring all tracked peer dependencies for every package with any React dependency. +- 8e9679b: Parallelized CLI report generation, reducing wall-clock time by ~4x. +- ca2dc15: Internal code cleanup. +- 270efef: Added support for packages that only support React 18+ in the `peer-deps` command. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/errors@1.3.0 + - @backstage/catalog-model@1.8.0 + - @backstage/cli-common@0.2.1 + - @backstage/cli-node@0.3.1 + - @backstage/config-loader@1.10.10 + +## 0.17.1-next.2 + +### Patch Changes + +- 482ceed: Migrated from `assertError` to `toError` for error handling. +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/cli-common@0.2.1-next.1 + - @backstage/cli-node@0.3.1-next.1 + - @backstage/config-loader@1.10.10-next.1 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/catalog-model@1.7.8-next.0 + +## 0.17.1-next.1 + +### Patch Changes + +- 2e5c5f8: Bumped `glob` dependency from v7/v8/v11 to v13 to address security vulnerabilities in older versions. Bumped `rollup` from v4.27 to v4.59+ to fix a high severity path traversal vulnerability (GHSA-mw96-cpmx-2vgc). +- 8e9679b: Parallelized CLI report generation, reducing wall-clock time by ~4x. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + +## 0.17.1-next.0 + +### Patch Changes + +- 270efef: Added support for packages that only support React 18+ in the `peer-deps` command. +- Updated dependencies + - @backstage/cli-common@0.2.1-next.0 + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/cli-node@0.3.1-next.0 + - @backstage/config-loader@1.10.10-next.0 + - @backstage/catalog-model@1.7.7 + - @backstage/errors@1.2.7 + +## 0.17.0 + +### Minor Changes + +- 0fbcf23: Added support for OpenAPI 3.1 to all `schema openapi` commands. The commands now auto-detect the OpenAPI version from the spec file and use the appropriate generator, supporting both OpenAPI 3.0.x and 3.1.x specifications. + +### Patch Changes + +- 426edbe: Fixed `generate-catalog-info` command failing with "too many arguments" when invoked by lint-staged via the pre-commit hook. +- d5779e5: Updated the CLI report parser to support cleye-style help output, and strip ANSI escape codes from captured output. +- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1 +- 2a51546: Fixed prettier existence checks in OpenAPI commands to use `fs.pathExists` instead of checking the resolved path string, which was always truthy. +- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`. +- de62a9d: Upgraded `commander` dependency from `^12.0.0` to `^14.0.3` across all CLI packages. +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- 18a946c: Updated `@microsoft/api-extractor` to `7.57.3` and added tests for `getTsDocConfig` +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/cli-node@0.3.0 + - @backstage/cli-common@0.2.0 + - @backstage/config-loader@1.10.9 + - @backstage/catalog-model@1.7.7 + +## 0.17.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/cli-common@0.2.0-next.2 + +## 0.17.0-next.1 + +### Minor Changes + +- 0fbcf23: Added support for OpenAPI 3.1 to all `schema openapi` commands. The commands now auto-detect the OpenAPI version from the spec file and use the appropriate generator, supporting both OpenAPI 3.0.x and 3.1.x specifications. + +### Patch Changes + +- 426edbe: Fixed `generate-catalog-info` command failing with "too many arguments" when invoked by lint-staged via the pre-commit hook. +- d5779e5: Updated the CLI report parser to support cleye-style help output, and strip ANSI escape codes from captured output. +- Updated dependencies + - @backstage/cli-common@0.2.0-next.1 + - @backstage/cli-node@0.2.19-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config-loader@1.10.9-next.0 + - @backstage/errors@1.2.7 + +## 0.16.6-next.0 + +### Patch Changes + +- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1 +- 2a51546: Fixed prettier existence checks in OpenAPI commands to use `fs.pathExists` instead of checking the resolved path string, which was always truthy. +- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`. +- de62a9d: Upgraded `commander` dependency from `^12.0.0` to `^14.0.3` across all CLI packages. +- 18a946c: Updated `@microsoft/api-extractor` to `7.57.3` and added tests for `getTsDocConfig` +- Updated dependencies + - @backstage/cli-common@0.2.0-next.0 + - @backstage/cli-node@0.2.19-next.0 + - @backstage/config-loader@1.10.9-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + +## 0.16.4 + +### Patch Changes + +- cd75ed0: Add newline to OpenAPI license template files. +- 7455dae: Use node prefix on native imports +- 4fc7bf0: Bump to tar v7 +- 6523040: Support Prettier v3 for api-reports +- be7ebad: Updated package-docs exclude list to reflect renamed example app packages. +- df59ee6: The `type-deps` command now follows relative imports and re-exports into declaration chunk files, and detects ambient global types such as the `jest` namespace. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/config-loader@1.10.8 + - @backstage/cli-common@0.1.18 + - @backstage/cli-node@0.2.18 + +## 0.16.4-next.2 + +### Patch Changes + +- be7ebad: Updated package-docs exclude list to reflect renamed example app packages. +- df59ee6: The `type-deps` command now follows relative imports and re-exports into declaration chunk files, and detects ambient global types such as the `jest` namespace. +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/cli-node@0.2.18-next.1 + - @backstage/config-loader@1.10.8-next.0 + +## 0.16.4-next.1 + +### Patch Changes + +- 6523040: Support Prettier v3 for api-reports +- Updated dependencies + - @backstage/cli-node@0.2.18-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## 0.16.3-next.0 + +### Patch Changes + +- cd75ed0: Add newline to OpenAPI license template files. +- 7455dae: Use node prefix on native imports +- 4fc7bf0: Bump to tar v7 +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/config-loader@1.10.8-next.0 + - @backstage/cli-common@0.1.18-next.0 + - @backstage/cli-node@0.2.17-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + +## 0.16.2 + +### Patch Changes + +- 498f9dd: Fixed help text for `backstage-repo-tools package schema openapi generate` command. +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/cli-common@0.1.17 + +## 0.16.1 + +### Patch Changes + +- 688f070: Updated to use new utilities from `@backstage/cli-common`. +- 85895f9: Updates OpenAPI generator templates to preserve original property names (like 'group-name', 'user-id') from OpenAPI specs when propertyNaming=original is specified. Previously, these were always converted to camelCase regardless of the propertyNaming setting. + + - Updates modelGeneric.mustache templates in both client and server generators + - Updates modelTaggedUnion.mustache templates in both client and server generators + - Uses {{baseName}} when available, falls back to {{name}} for backward compatibility + - Maintains backward compatibility - no changes when propertyNaming=original is not used + +- 2bae83a: Bump `@microsoft/api-documenter` and `@microsoft/api-extractor` to latest versions. +- d1e38a7: Properly create workspace in OS temporary directory for `generate-patch` command +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/cli-node@0.2.16 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/cli-common@0.1.16 + - @backstage/config-loader@1.10.7 + +## 0.16.1-next.2 + +### Patch Changes + +- 2bae83a: Bump `@microsoft/api-documenter` and `@microsoft/api-extractor` to latest versions. +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config-loader@1.10.7-next.1 + - @backstage/cli-common@0.1.16-next.2 + - @backstage/catalog-model@1.7.6 + - @backstage/cli-node@0.2.16-next.1 + - @backstage/errors@1.2.7 + +## 0.16.1-next.1 + +### Patch Changes + +- 688f070: Updated to use new utilities from `@backstage/cli-common`. +- d1e38a7: Properly create workspace in OS temporary directory for `generate-patch` command +- Updated dependencies + - @backstage/cli-node@0.2.16-next.1 + - @backstage/cli-common@0.1.16-next.1 + +## 0.16.1-next.0 + +### Patch Changes + +- 85895f9: Updates OpenAPI generator templates to preserve original property names (like 'group-name', 'user-id') from OpenAPI specs when propertyNaming=original is specified. Previously, these were always converted to camelCase regardless of the propertyNaming setting. + + - Updates modelGeneric.mustache templates in both client and server generators + - Updates modelTaggedUnion.mustache templates in both client and server generators + - Uses {{baseName}} when available, falls back to {{name}} for backward compatibility + - Maintains backward compatibility - no changes when propertyNaming=original is not used + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/cli-common@0.1.16-next.0 + - @backstage/cli-node@0.2.16-next.0 + - @backstage/config-loader@1.10.7-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + +## 0.16.0 + +### Minor Changes + +- 11c61f2: The `package-docs` command will now automatically use a `typedoc.json` file if one exists at the root of your project. + +### Patch Changes + +- 8f56eae: Updated knip-reports to detect dependencies in dev/alpha pattern +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config-loader@1.10.6 + - @backstage/cli-node@0.2.15 + - @backstage/catalog-model@1.7.6 + ## 0.16.0-next.2 ### Minor Changes diff --git a/packages/repo-tools/bin/backstage-repo-tools b/packages/repo-tools/bin/backstage-repo-tools index b723d215fd..03efd3c13d 100755 --- a/packages/repo-tools/bin/backstage-repo-tools +++ b/packages/repo-tools/bin/backstage-repo-tools @@ -15,11 +15,13 @@ * limitations under the License. */ -const path = require('path'); +const path = require('node:path'); // Figure out whether we're running inside the backstage repo or as an installed dependency /* eslint-disable-next-line no-restricted-syntax */ -const isLocal = require('fs').existsSync(path.resolve(__dirname, '../src')); +const isLocal = require('node:fs').existsSync( + path.resolve(__dirname, '../src'), +); if (!isLocal || process.env.BACKSTAGE_E2E_CLI_TEST) { require('..'); diff --git a/packages/repo-tools/cli-report.md b/packages/repo-tools/cli-report.md index acde92d742..6b6f2ae710 100644 --- a/packages/repo-tools/cli-report.md +++ b/packages/repo-tools/cli-report.md @@ -160,7 +160,11 @@ Options: Usage: backstage-repo-tools package schema openapi generate [options] Options: + --client-additional-properties [properties] --client-package [package] + --server + --server-additional-properties [properties] + --watch -h, --help ``` diff --git a/packages/repo-tools/knip-report.md b/packages/repo-tools/knip-report.md index f87d954145..deadeeed36 100644 --- a/packages/repo-tools/knip-report.md +++ b/packages/repo-tools/knip-report.md @@ -2,23 +2,23 @@ ## Unused dependencies (4) -| Name | Location | Severity | -| :---------------------------------- | :----------- | :------- | -| @openapitools/openapi-generator-cli | packages/repo-tools/package.json | error | -| @stoplight/spectral-runtime | packages/repo-tools/package.json | error | -| @electric-sql/pglite | packages/repo-tools/package.json | error | -| is-glob | packages/repo-tools/package.json | error | +| Name | Location | Severity | +| :---------------------------------- | :---------------- | :------- | +| @openapitools/openapi-generator-cli | package.json:56:6 | error | +| @stoplight/spectral-runtime | package.json:63:6 | error | +| @electric-sql/pglite | package.json:52:6 | error | +| is-glob | package.json:74:6 | error | ## Unused devDependencies (2) -| Name | Location | Severity | -| :------------- | :----------- | :------- | -| @types/is-glob | packages/repo-tools/package.json | error | -| typedoc | packages/repo-tools/package.json | error | +| Name | Location | Severity | +| :------------- | :---------------- | :------- | +| @types/is-glob | package.json:93:6 | error | +| typedoc | package.json:96:6 | error | ## Referenced optional peerDependencies (1) | Name | Location | Severity | | :------- | :----------- | :------- | -| prettier | packages/repo-tools/package.json | error | +| prettier | package.json | error | diff --git a/packages/repo-tools/openapitools.json b/packages/repo-tools/openapitools.json index 65c0d8ead7..c7593e7a28 100644 --- a/packages/repo-tools/openapitools.json +++ b/packages/repo-tools/openapitools.json @@ -2,6 +2,6 @@ "$schema": "../../node_modules/@openapitools/openapi-generator-cli/config.schema.json", "spaces": 2, "generator-cli": { - "version": "6.5.0" + "version": "7.18.0" } } diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index b250018017..7c8d28095a 100644 --- a/packages/repo-tools/package.json +++ b/packages/repo-tools/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/repo-tools", - "version": "0.16.0-next.2", + "version": "0.17.1", "description": "CLI for Backstage repo tooling ", "backstage": { "role": "cli" @@ -51,9 +51,10 @@ "@backstage/errors": "workspace:^", "@electric-sql/pglite": "^0.3.0", "@manypkg/get-packages": "^1.1.3", - "@microsoft/api-documenter": "^7.25.7", - "@microsoft/api-extractor": "^7.47.2", + "@microsoft/api-documenter": "^7.28.1", + "@microsoft/api-extractor": "^7.57.3", "@openapitools/openapi-generator-cli": "^2.7.0", + "@prettier/sync": "^0.6.1", "@stoplight/spectral-core": "^1.18.0", "@stoplight/spectral-formatters": "^1.1.0", "@stoplight/spectral-functions": "^1.7.2", @@ -66,9 +67,9 @@ "chokidar": "^3.5.3", "codeowners-utils": "^1.0.2", "command-exists": "^1.2.9", - "commander": "^12.0.0", + "commander": "^14.0.3", "fs-extra": "^11.2.0", - "glob": "^8.0.3", + "glob": "^13.0.0", "globby": "^11.0.0", "is-glob": "^4.0.3", "js-yaml": "^4.1.0", @@ -77,20 +78,20 @@ "knex-pglite": "^0.11.0", "knip": "^5.42.0", "lodash": "^4.17.21", - "minimatch": "^9.0.0", + "minimatch": "^10.2.1", "p-limit": "^3.0.2", "portfinder": "^1.0.32", - "tar": "^6.1.12", + "tar": "^7.5.6", "ts-morph": "^24.0.0", "yaml-diff-patch": "^2.0.0", - "zod": "^3.22.4" + "zod": "^3.25.76 || ^4.0.0" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/types": "workspace:^", "@types/is-glob": "^4.0.2", - "@types/node": "^20.16.0", + "@types/node": "^22.13.14", "@types/prettier": "^2.0.0", "typedoc": "^0.28.0" }, @@ -99,7 +100,7 @@ "@microsoft/tsdoc": "*", "@microsoft/tsdoc-config": "*", "@useoptic/optic": "^1.0.0", - "prettier": "^2.8.1", + "prettier": "^2.8.1 || ^3.8.1", "typedoc": "^0.28.0", "typescript": "> 3.0.0" }, diff --git a/packages/repo-tools/src/commands/api-reports/api-reports/buildDocs.ts b/packages/repo-tools/src/commands/api-reports/api-reports/buildDocs.ts index 42b585818d..797d5375b0 100644 --- a/packages/repo-tools/src/commands/api-reports/api-reports/buildDocs.ts +++ b/packages/repo-tools/src/commands/api-reports/api-reports/buildDocs.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { resolve as resolvePath } from 'path'; +import { resolve as resolvePath } from 'node:path'; import fs from 'fs-extra'; import { DocBlockTag, diff --git a/packages/repo-tools/src/commands/api-reports/api-reports/createTemporaryTsConfig.ts b/packages/repo-tools/src/commands/api-reports/api-reports/createTemporaryTsConfig.ts index 538fdd4ceb..5cf62f8bac 100644 --- a/packages/repo-tools/src/commands/api-reports/api-reports/createTemporaryTsConfig.ts +++ b/packages/repo-tools/src/commands/api-reports/api-reports/createTemporaryTsConfig.ts @@ -15,11 +15,11 @@ */ import fs from 'fs-extra'; -import { join } from 'path'; -import { paths as cliPaths } from '../../../lib/paths'; +import { join } from 'node:path'; +import { targetPaths } from '@backstage/cli-common'; export async function createTemporaryTsConfig(includedPackageDirs: string[]) { - const path = cliPaths.resolveTargetRoot('tsconfig.tmp.json'); + const path = targetPaths.resolveRoot('tsconfig.tmp.json'); process.once('exit', () => { fs.removeSync(path); diff --git a/packages/repo-tools/src/commands/api-reports/api-reports/generateTypeDeclarations.ts b/packages/repo-tools/src/commands/api-reports/api-reports/generateTypeDeclarations.ts index d7304553c0..1165c24a77 100644 --- a/packages/repo-tools/src/commands/api-reports/api-reports/generateTypeDeclarations.ts +++ b/packages/repo-tools/src/commands/api-reports/api-reports/generateTypeDeclarations.ts @@ -15,8 +15,8 @@ */ import fs from 'fs-extra'; -import { spawnSync } from 'child_process'; -import { paths as cliPaths } from '../../../lib/paths'; +import { run, ExitCodeError } from '@backstage/cli-common'; +import { targetPaths } from '@backstage/cli-common'; /** * Generates the TypeScript declaration files for the specified project, using the provided `tsconfig.json` file. @@ -29,22 +29,27 @@ import { paths as cliPaths } from '../../../lib/paths'; * @returns {Promise} A promise that resolves when the declaration files have been generated. */ export async function generateTypeDeclarations(tsconfigFilePath: string) { - await fs.remove(cliPaths.resolveTargetRoot('dist-types')); - const { status } = spawnSync( - 'yarn', - [ - 'tsc', - ['--project', tsconfigFilePath], - ['--skipLibCheck', 'false'], - ['--incremental', 'false'], - ].flat(), - { - stdio: 'inherit', - shell: true, - cwd: cliPaths.targetRoot, - }, - ); - if (status !== 0) { - process.exit(status || undefined); + await fs.remove(targetPaths.resolveRoot('dist-types')); + try { + await run( + [ + 'yarn', + 'tsc', + '--project', + tsconfigFilePath, + '--skipLibCheck', + 'false', + '--incremental', + 'false', + ], + { + cwd: targetPaths.rootDir, + }, + ).waitForExit(); + } catch (error) { + if (error instanceof ExitCodeError) { + process.exit(error.code); + } + throw error; } } diff --git a/packages/repo-tools/src/commands/api-reports/api-reports/patchApiReportGeneration.ts b/packages/repo-tools/src/commands/api-reports/api-reports/patchApiReportGeneration.ts index 982707e815..3ac3eef99b 100644 --- a/packages/repo-tools/src/commands/api-reports/api-reports/patchApiReportGeneration.ts +++ b/packages/repo-tools/src/commands/api-reports/api-reports/patchApiReportGeneration.ts @@ -18,6 +18,7 @@ import { ExtractorMessage } from '@microsoft/api-extractor'; import { AstDeclaration } from '@microsoft/api-extractor/lib/analyzer/AstDeclaration'; import { Program } from 'typescript'; import { tryRunPrettier } from '../common'; +import { targetPaths } from '@backstage/cli-common'; let applied = false; @@ -157,7 +158,11 @@ export function patchApiReportGeneration() { collector, ...moreArgs, ); - - return tryRunPrettier(content); + return tryRunPrettier(content, { + parser: 'markdown', + // We need a real-looking filepath for proper config resolution, not just a directory + // Ideally, the real filepath would be better, but it would require too much patching, for very little gain. + filepath: `${targetPaths.rootDir}/report.api.md`, + }); }; } diff --git a/packages/repo-tools/src/commands/api-reports/api-reports/runApiExtraction.test.ts b/packages/repo-tools/src/commands/api-reports/api-reports/runApiExtraction.test.ts new file mode 100644 index 0000000000..c99bdb90eb --- /dev/null +++ b/packages/repo-tools/src/commands/api-reports/api-reports/runApiExtraction.test.ts @@ -0,0 +1,61 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { TSDocTagSyntaxKind } from '@microsoft/tsdoc'; +import { getTsDocConfig } from './runApiExtraction'; + +describe('getTsDocConfig', () => { + it('should load the base TSDoc config from api-extractor', async () => { + const config = await getTsDocConfig(); + + expect(config).toBeDefined(); + expect(config.filePath).toContain('tsdoc-base.json'); + }); + + it('should add @ignore tag definition with ModifierTag syntax', async () => { + const config = await getTsDocConfig(); + + const ignoreTag = config.tagDefinitions.find( + tag => tag.tagName === '@ignore', + ); + expect(ignoreTag).toBeDefined(); + expect(ignoreTag?.tagName).toBe('@ignore'); + expect(ignoreTag?.syntaxKind).toBe(TSDocTagSyntaxKind.ModifierTag); + }); + + it('should add @config tag definition with BlockTag syntax', async () => { + const config = await getTsDocConfig(); + + const configTag = config.tagDefinitions.find( + tag => tag.tagName === '@config', + ); + expect(configTag).toBeDefined(); + expect(configTag?.tagName).toBe('@config'); + expect(configTag?.syntaxKind).toBe(TSDocTagSyntaxKind.BlockTag); + }); + + it('should enable support for @ignore tag', async () => { + const config = await getTsDocConfig(); + + expect(config.supportForTags.get('@ignore')).toBe(true); + }); + + it('should enable support for @config tag', async () => { + const config = await getTsDocConfig(); + + expect(config.supportForTags.get('@config')).toBe(true); + }); +}); diff --git a/packages/repo-tools/src/commands/api-reports/api-reports/runApiExtraction.ts b/packages/repo-tools/src/commands/api-reports/api-reports/runApiExtraction.ts index 1ba8852ed4..6091237fae 100644 --- a/packages/repo-tools/src/commands/api-reports/api-reports/runApiExtraction.ts +++ b/packages/repo-tools/src/commands/api-reports/api-reports/runApiExtraction.ts @@ -25,15 +25,17 @@ import { TSDocConfigFile } from '@microsoft/tsdoc-config'; import fs from 'fs-extra'; import { groupBy } from 'lodash'; import { minimatch } from 'minimatch'; -import { join, relative as relativePath, resolve as resolvePath } from 'path'; +import { + join, + relative as relativePath, + resolve as resolvePath, +} from 'node:path'; import { getPackageExportDetails } from '../../../lib/getPackageExportDetails'; -import { paths as cliPaths } from '../../../lib/paths'; +import { targetPaths } from '@backstage/cli-common'; import { logApiReportInstructions } from '../common'; import { patchApiReportGeneration } from './patchApiReportGeneration'; -const tmpDir = cliPaths.resolveTargetRoot( - './node_modules/.cache/api-extractor', -); +const tmpDir = targetPaths.resolveRoot('./node_modules/.cache/api-extractor'); export async function countApiReportWarnings(reportPath: string) { try { @@ -96,7 +98,7 @@ async function findPackageEntryPoints(packageDirs: string[]): Promise< return Promise.all( packageDirs.map(async packageDir => { const pkg = await fs.readJson( - cliPaths.resolveTargetRoot(packageDir, 'package.json'), + targetPaths.resolveRoot(packageDir, 'package.json'), ); return getPackageExportDetails(pkg).map(details => { @@ -139,11 +141,7 @@ export async function runApiExtraction({ // inspected. const allDistTypesEntryPointPaths = allEntryPoints.map( ({ packageDir, distTypesPath }) => { - return cliPaths.resolveTargetRoot( - './dist-types', - packageDir, - distTypesPath, - ); + return targetPaths.resolveRoot('./dist-types', packageDir, distTypesPath); }, ); @@ -168,17 +166,15 @@ export async function runApiExtraction({ ? allowWarnings.some(aw => aw === packageDir || minimatch(packageDir, aw)) : allowWarnings; - const projectFolder = cliPaths.resolveTargetRoot(packageDir); - const packageFolder = cliPaths.resolveTargetRoot( - './dist-types', - packageDir, - ); + const projectFolder = targetPaths.resolveRoot(packageDir); + const packageFolder = targetPaths.resolveRoot('./dist-types', packageDir); const remainingReportFiles = new Set( fs.readdirSync(projectFolder).filter( filename => // https://regex101.com/r/QDZIV0/2 filename !== 'knip-report.md' && + !filename.startsWith('cli-report') && !filename.endsWith('.sql.md') && // this has to temporarily match all old api report formats filename.match(/^.*?(api-)?report(-[^.-]+)?(.*?)\.md$/), diff --git a/packages/repo-tools/src/commands/api-reports/buildApiReports.test.ts b/packages/repo-tools/src/commands/api-reports/buildApiReports.test.ts index cbe6fedad6..8346296f44 100644 --- a/packages/repo-tools/src/commands/api-reports/buildApiReports.test.ts +++ b/packages/repo-tools/src/commands/api-reports/buildApiReports.test.ts @@ -15,8 +15,8 @@ */ import { createMockDirectory } from '@backstage/backend-test-utils'; -import { normalize } from 'path'; -import * as pathsLib from '../../lib/paths'; +import { normalize } from 'node:path'; +import { overrideTargetPaths } from '@backstage/cli-common/testUtils'; import { categorizePackageDirs } from './categorizePackageDirs'; @@ -51,14 +51,9 @@ jest.mock('./categorizePackageDirs', () => ({ }), })); -const projectPaths = pathsLib.paths; - const mockDir = createMockDirectory(); +overrideTargetPaths(mockDir.path); -jest.spyOn(projectPaths, 'targetRoot', 'get').mockReturnValue(mockDir.path); -jest - .spyOn(projectPaths, 'resolveTargetRoot') - .mockImplementation((...path) => mockDir.resolve(...path)); jest.spyOn(PackageGraph, 'listTargetPackages').mockResolvedValue([ { dir: normalize(mockDir.resolve('packages/package-a')), @@ -85,30 +80,28 @@ jest.spyOn(PackageGraph, 'listTargetPackages').mockResolvedValue([ describe('buildApiReports', () => { beforeEach(() => { mockDir.setContent({ - [projectPaths.targetRoot]: { - 'package.json': JSON.stringify({ - workspaces: { packages: ['packages/*', 'plugins/*'] }, - }), - packages: { - 'package-a': { - 'package.json': '{}', - }, - 'package-b': { - 'package.json': '{}', - }, - 'package-c': {}, - 'README.md': 'Hello World', + 'package.json': JSON.stringify({ + workspaces: { packages: ['packages/*', 'plugins/*'] }, + }), + packages: { + 'package-a': { + 'package.json': '{}', }, - plugins: { - 'plugin-a': { - 'package.json': '{}', - }, - 'plugin-b': { - 'package.json': '{}', - }, - 'plugin-c': { - 'package.json': '{}', - }, + 'package-b': { + 'package.json': '{}', + }, + 'package-c': {}, + 'README.md': 'Hello World', + }, + plugins: { + 'plugin-a': { + 'package.json': '{}', + }, + 'plugin-b': { + 'package.json': '{}', + }, + 'plugin-c': { + 'package.json': '{}', }, }, }); diff --git a/packages/repo-tools/src/commands/api-reports/buildApiReports.ts b/packages/repo-tools/src/commands/api-reports/buildApiReports.ts index 74b110236f..41c6aa8a57 100644 --- a/packages/repo-tools/src/commands/api-reports/buildApiReports.ts +++ b/packages/repo-tools/src/commands/api-reports/buildApiReports.ts @@ -16,7 +16,8 @@ import { OptionValues } from 'commander'; import { categorizePackageDirs } from './categorizePackageDirs'; -import { paths as cliPaths, resolvePackagePaths } from '../../lib/paths'; +import { targetPaths } from '@backstage/cli-common'; +import { resolvePackagePaths } from '../../lib/paths'; import { runSqlExtraction } from './sql-reports'; import { runCliExtraction } from './cli-reports'; import { @@ -37,9 +38,7 @@ type Options = { } & OptionValues; export async function buildApiReports(paths: string[] = [], opts: Options) { - const tmpDir = cliPaths.resolveTargetRoot( - './node_modules/.cache/api-extractor', - ); + const tmpDir = targetPaths.resolveRoot('./node_modules/.cache/api-extractor'); const isCiBuild = opts.ci; const isDocsBuild = opts.docs; @@ -73,7 +72,7 @@ export async function buildApiReports(paths: string[] = [], opts: Options) { temporaryTsConfigPath = await createTemporaryTsConfig(selectedPackageDirs); } const tsconfigFilePath = - temporaryTsConfigPath ?? cliPaths.resolveTargetRoot('tsconfig.json'); + temporaryTsConfigPath ?? targetPaths.resolveRoot('tsconfig.json'); if (runTsc) { console.log('# Compiling TypeScript'); @@ -116,7 +115,7 @@ export async function buildApiReports(paths: string[] = [], opts: Options) { console.log('# Generating package documentation'); await buildDocs({ inputDir: tmpDir, - outputDir: cliPaths.resolveTargetRoot('docs/reference'), + outputDir: targetPaths.resolveRoot('docs/reference'), }); } } diff --git a/packages/repo-tools/src/commands/api-reports/categorizePackageDirs.ts b/packages/repo-tools/src/commands/api-reports/categorizePackageDirs.ts index 86694c3d68..6fdb890100 100644 --- a/packages/repo-tools/src/commands/api-reports/categorizePackageDirs.ts +++ b/packages/repo-tools/src/commands/api-reports/categorizePackageDirs.ts @@ -15,7 +15,7 @@ */ import fs from 'fs-extra'; -import { paths as cliPaths } from '../../lib/paths'; +import { targetPaths } from '@backstage/cli-common'; export async function categorizePackageDirs(packageDirs: string[]) { const dirs = packageDirs.slice(); @@ -34,7 +34,7 @@ export async function categorizePackageDirs(packageDirs: string[]) { } const pkgJson = await fs - .readJson(cliPaths.resolveTargetRoot(dir, 'package.json')) + .readJson(targetPaths.resolveRoot(dir, 'package.json')) .catch(error => { if (error.code === 'ENOENT') { return undefined; @@ -45,9 +45,7 @@ export async function categorizePackageDirs(packageDirs: string[]) { if (!role) { return; // Ignore packages without roles } - if ( - await fs.pathExists(cliPaths.resolveTargetRoot(dir, 'migrations')) - ) { + if (await fs.pathExists(targetPaths.resolveRoot(dir, 'migrations'))) { sqlPackageDirs.push(dir); } // TODO(Rugvip): Inlined packages are ignored because we can't handle @internal exports @@ -56,9 +54,10 @@ export async function categorizePackageDirs(packageDirs: string[]) { if (pkgJson?.backstage?.inline) { return; } - if (role === 'cli') { + if (role === 'cli' || role === 'cli-module') { cliPackageDirs.push(dir); - } else if (role !== 'frontend' && role !== 'backend') { + } + if (role !== 'cli' && role !== 'frontend' && role !== 'backend') { tsPackageDirs.push(dir); } } diff --git a/packages/repo-tools/src/commands/api-reports/cli-reports/runCliExtraction.ts b/packages/repo-tools/src/commands/api-reports/cli-reports/runCliExtraction.ts index 6ab72024c8..156e808ef0 100644 --- a/packages/repo-tools/src/commands/api-reports/cli-reports/runCliExtraction.ts +++ b/packages/repo-tools/src/commands/api-reports/cli-reports/runCliExtraction.ts @@ -18,16 +18,25 @@ import { basename, resolve as resolvePath, relative as relativePath, -} from 'path'; +} from 'node:path'; +import os from 'node:os'; import fs from 'fs-extra'; +import pLimit from 'p-limit'; import { createBinRunner } from '../../util'; import { CliHelpPage, CliModel } from './types'; -import { paths as cliPaths } from '../../../lib/paths'; +import { targetPaths } from '@backstage/cli-common'; import { generateCliReport } from './generateCliReport'; import { logApiReportInstructions } from '../common'; function parseHelpPage(helpPageContent: string) { - const [, usage] = helpPageContent.match(/^\s*Usage: (.*)$/im) ?? []; + let usage: string | undefined; + + // Commander format: "Usage: backstage-cli ..." + const commanderUsage = helpPageContent.match(/^\s*Usage: (.*)$/im); + if (commanderUsage) { + usage = commanderUsage[1]; + } + const lines = helpPageContent.split(/\r?\n/); let options = new Array(); @@ -39,8 +48,8 @@ function parseHelpPage(helpPageContent: string) { lines.shift(); } if (lines.length > 0) { - // Start of a new section, e.g. "Options:" - const sectionName = lines.shift(); + // Start of a new section, e.g. "Options:" or "FLAGS:" + const sectionName = lines.shift()?.toLocaleLowerCase('en-US'); // Take lines until we hit the next section or the end const sectionEndIndex = lines.findIndex( line => line && !line.match(/^\s/), @@ -53,12 +62,18 @@ function parseHelpPage(helpPageContent: string) { .map(line => line.match(/^\s{1,8}(.*?)\s\s+/)?.[1]) .filter(Boolean) as string[]; - if (sectionName?.toLocaleLowerCase('en-US') === 'options:') { + if (sectionName === 'options:' || sectionName === 'flags:') { options = sectionItems; - } else if (sectionName?.toLocaleLowerCase('en-US') === 'commands:') { + } else if (sectionName === 'commands:') { commands = sectionItems; - } else if (sectionName?.toLocaleLowerCase('en-US') === 'arguments:') { + } else if (sectionName === 'arguments:') { commandArguments = sectionItems; + } else if (sectionName === 'usage:') { + // cleye format: usage line is inside the USAGE: section + const usageLine = sectionLines.find(l => l.trim().length > 0)?.trim(); + if (usageLine) { + usage = usageLine; + } } else { throw new Error(`Unknown CLI section: ${sectionName}`); } @@ -79,11 +94,12 @@ function parseHelpPage(helpPageContent: string) { async function exploreCliHelpPages( run: (...args: string[]) => Promise, + limit: ReturnType, ): Promise { const helpPages = new Array(); async function exploreHelpPage(...path: string[]) { - const content = await run(...path, '--help'); + const content = await limit(() => run(...path, '--help')); const parsed = parseHelpPage(content); helpPages.push({ path, ...parsed }); @@ -113,68 +129,79 @@ export async function runCliExtraction({ packageDirs, isLocalBuild, }: CliExtractionOptions) { - for (const packageDir of packageDirs) { - console.log(`## Processing ${packageDir}`); - const fullDir = cliPaths.resolveTargetRoot(packageDir); - const pkgJson = await fs.readJson(resolvePath(fullDir, 'package.json')); + // Share a single concurrency limiter across all packages so that we run + // multiple help-page invocations in parallel without exhausting resources. + const limit = pLimit(os.cpus().length); - if (!pkgJson.bin) { - throw new Error(`CLI Package in ${packageDir} has no bin field`); - } + await Promise.all( + packageDirs.map(async packageDir => { + console.log(`## Processing ${packageDir}`); + const fullDir = targetPaths.resolveRoot(packageDir); + const pkgJson = await fs.readJson(resolvePath(fullDir, 'package.json')); - const models = new Array(); - if (typeof pkgJson.bin === 'string') { - const run = createBinRunner(fullDir, pkgJson.bin); - const helpPages = await exploreCliHelpPages(run); - models.push({ name: basename(pkgJson.bin), helpPages }); - } else { - for (const [name, path] of Object.entries(pkgJson.bin)) { - const run = createBinRunner(fullDir, path); - const helpPages = await exploreCliHelpPages(run); - models.push({ name, helpPages }); + if (!pkgJson.bin) { + if (pkgJson.backstage?.role === 'cli') { + throw new Error( + `CLI package ${pkgJson.name} is missing a "bin" field in its package.json`, + ); + } + return; } - } - for (const model of models) { - const report = generateCliReport({ packageName: pkgJson.name, model }); - - const reportPath = resolvePath( - fullDir, - `cli-report.${models.length === 1 ? '' : `${model.name}.`}md`, - ); - const existingReport = await fs - .readFile(reportPath, 'utf8') - .catch(error => { - if (error.code === 'ENOENT') { - return undefined; - } - throw error; - }); - - if (existingReport !== report) { - if (isLocalBuild) { - console.warn(`CLI report changed for ${packageDir}`); - await fs.writeFile(reportPath, report); - } else { - logApiReportInstructions(); - - if (existingReport) { - console.log(''); - console.log( - `The conflicting file is ${relativePath( - cliPaths.targetRoot, - reportPath, - )}, expecting the following content:`, - ); - console.log(''); - - console.log(report); - - logApiReportInstructions(); - } - throw new Error(`CLI report changed for ${packageDir}, `); + const models = new Array(); + if (typeof pkgJson.bin === 'string') { + const run = createBinRunner(fullDir, pkgJson.bin); + const helpPages = await exploreCliHelpPages(run, limit); + models.push({ name: basename(pkgJson.bin), helpPages }); + } else { + for (const [name, path] of Object.entries(pkgJson.bin)) { + const run = createBinRunner(fullDir, path); + const helpPages = await exploreCliHelpPages(run, limit); + models.push({ name, helpPages }); } } - } - } + + for (const model of models) { + const report = generateCliReport({ packageName: pkgJson.name, model }); + + const reportPath = resolvePath( + fullDir, + `cli-report.${models.length === 1 ? '' : `${model.name}.`}md`, + ); + const existingReport = await fs + .readFile(reportPath, 'utf8') + .catch(error => { + if (error.code === 'ENOENT') { + return undefined; + } + throw error; + }); + + if (existingReport !== report) { + if (isLocalBuild) { + console.warn(`CLI report changed for ${packageDir}`); + await fs.writeFile(reportPath, report); + } else { + logApiReportInstructions(); + + if (existingReport) { + console.log(''); + console.log( + `The conflicting file is ${relativePath( + targetPaths.rootDir, + reportPath, + )}, expecting the following content:`, + ); + console.log(''); + + console.log(report); + + logApiReportInstructions(); + } + throw new Error(`CLI report changed for ${packageDir}`); + } + } + } + }), + ); } diff --git a/packages/repo-tools/src/commands/api-reports/common/index.ts b/packages/repo-tools/src/commands/api-reports/common/index.ts index 34bd95bfbe..820f18fa54 100644 --- a/packages/repo-tools/src/commands/api-reports/common/index.ts +++ b/packages/repo-tools/src/commands/api-reports/common/index.ts @@ -14,5 +14,5 @@ * limitations under the License. */ -export { tryRunPrettier } from './tryRunPrettier'; +export { tryRunPrettier, tryRunPrettierAsync } from './tryRunPrettier'; export { logApiReportInstructions } from './logApiReportInstructions'; diff --git a/packages/repo-tools/src/commands/api-reports/common/tryRunPrettier.ts b/packages/repo-tools/src/commands/api-reports/common/tryRunPrettier.ts index 020853d726..2c95e71647 100644 --- a/packages/repo-tools/src/commands/api-reports/common/tryRunPrettier.ts +++ b/packages/repo-tools/src/commands/api-reports/common/tryRunPrettier.ts @@ -14,22 +14,107 @@ * limitations under the License. */ -import { paths as cliPaths } from '../../../lib/paths'; +import { targetPaths } from '@backstage/cli-common'; import type { Config } from 'prettier'; +/** + * Tries to run prettier asynchronously, with Prettier v2 or v3. + * @param content - The content to format. + * @param extraConfig - Additional prettier config options (defaults to markdown parser). Providing a filepath is recommended for proper config resolution. + * @returns A promise that resolves to the formatted content or the original content if the formatting fails. + * @internal + */ +export async function tryRunPrettierAsync( + content: string, + extraConfig: Config = { parser: 'markdown' }, +): Promise { + try { + const prettier = require('prettier') as typeof import('prettier'); + // Filepath for proper config resolution + const filepath = + extraConfig.filepath ?? + `${targetPaths.rootDir}/should-not-be-ignored.any`; + const config = + (await prettier.resolveConfig(filepath, { editorconfig: true })) ?? {}; + const formattedContent = prettier.format(content, { + ...config, + ...extraConfig, + }); + // XXX: Remove once only v3 is supported + if (typeof formattedContent === 'string') { + return Promise.resolve(formattedContent); + } + return formattedContent; + } catch (e) { + return Promise.resolve(content); + } +} + +/** + * Creates a synchronous prettier formatter function for v2 or v3 via `@prettier/sync`. + * + * @param prettierSync - The prettier instance, either v2 or v3 via `@prettier/sync`. + * @param extraConfig - Additional prettier config options (defaults to markdown parser) + * @returns A function that formats content using the prettier instance and config + */ +export function createPrettierSyncFormatter( + prettierSync: + | typeof import('prettier') + | typeof import('@prettier/sync').default, + extraConfig: Config = {}, +): (content: string) => string { + return function tryRunPrettierInner(content: string): string { + try { + // We need a filepath for proper config resolution, not just a directory + const filepath = + extraConfig.filepath ?? + `${targetPaths.rootDir}/should-not-be-ignored.any`; + const resolveConfig = + // @ts-expect-error: v2 requires .sync, @prettier/sync v3 does not + prettierSync.resolveConfig?.sync ?? prettierSync.resolveConfig; + const config = + resolveConfig(filepath, { + editorconfig: true, + }) ?? {}; + return prettierSync.format(content, { + ...config, + ...extraConfig, + }); + } catch (e) { + return content; + } + }; +} + +/** + * Loads the prettier instance, either v2 or v3 via @prettier/sync. + * XXX: Remove once only v3 is supported + * @returns The prettier instance, either v2 or v3 via @prettier/sync. + */ +function loadPrettierSync(): + | typeof import('prettier') + | typeof import('@prettier/sync').default { + const prettier = require('prettier') as typeof import('prettier'); + if (prettier.version.startsWith('2')) { + return prettier; + } + return require('@prettier/sync').default; +} + +/** + * Tries to run prettier synchronously, with Prettier v2 or v3. + * @param content - The content to format. + * @param extraConfig - Additional prettier config options (defaults to markdown parser) + * @returns The formatted content. + * @internal + */ export function tryRunPrettier( content: string, extraConfig: Config = { parser: 'markdown' }, ): string { - try { - const prettier = require('prettier') as typeof import('prettier'); - - const config = prettier.resolveConfig.sync(cliPaths.targetRoot) ?? {}; - return prettier.format(content, { - ...config, - ...extraConfig, - }); - } catch (e) { - return content; - } + const formatter = createPrettierSyncFormatter( + loadPrettierSync(), + extraConfig, + ); + return formatter(content); } diff --git a/packages/repo-tools/src/commands/api-reports/sql-reports/runSqlExtraction.ts b/packages/repo-tools/src/commands/api-reports/sql-reports/runSqlExtraction.ts index 9462412436..375ce07476 100644 --- a/packages/repo-tools/src/commands/api-reports/sql-reports/runSqlExtraction.ts +++ b/packages/repo-tools/src/commands/api-reports/sql-reports/runSqlExtraction.ts @@ -15,14 +15,14 @@ */ import fs, { readJson } from 'fs-extra'; -import { relative as relativePath } from 'path'; -import { paths as cliPaths } from '../../../lib/paths'; +import { relative as relativePath } from 'node:path'; +import { targetPaths } from '@backstage/cli-common'; import { diff as justDiff } from 'just-diff'; import { SchemaInfo } from './types'; import { getPgSchemaInfo } from './getPgSchemaInfo'; import { generateSqlReport } from './generateSqlReport'; import type { Knex } from 'knex'; -import { logApiReportInstructions, tryRunPrettier } from '../common'; +import { logApiReportInstructions, tryRunPrettierAsync } from '../common'; interface SqlExtractionOptions { packageDirs: string[]; @@ -43,14 +43,14 @@ export async function runSqlExtraction(options: SqlExtractionOptions) { let dbIndex = 1; for (const packageDir of options.packageDirs) { - const migrationDir = cliPaths.resolveTargetRoot(packageDir, 'migrations'); + const migrationDir = targetPaths.resolveRoot(packageDir, 'migrations'); if (!(await fs.pathExists(migrationDir))) { console.log(`No SQL migrations found in ${packageDir}`); continue; } const { name: pkgName } = await readJson( - cliPaths.resolveTargetRoot(packageDir, 'package.json'), + targetPaths.resolveRoot(packageDir, 'package.json'), ); const migrationFiles = await fs.readdir(migrationDir, { @@ -95,7 +95,7 @@ async function runSingleSqlExtraction( knex: Knex, options: SqlExtractionOptions, ) { - const migrationDir = cliPaths.resolveTargetRoot( + const migrationDir = targetPaths.resolveRoot( targetDir, 'migrations', migrationTarget, @@ -152,19 +152,19 @@ async function runSingleSqlExtraction( break; } } - - const report = tryRunPrettier( + const reportPath = targetPaths.resolveRoot( + targetDir, + `report${migrationTarget === '.' ? '' : `-${migrationTarget}`}.sql.md`, + ); + const report = await tryRunPrettierAsync( generateSqlReport({ reportName, failedDownMigration, schemaInfo, }), + { filepath: reportPath }, ); - const reportPath = cliPaths.resolveTargetRoot( - targetDir, - `report${migrationTarget === '.' ? '' : `-${migrationTarget}`}.sql.md`, - ); const existingReport = await fs.readFile(reportPath, 'utf8').catch(error => { if (error.code === 'ENOENT') { return undefined; @@ -182,7 +182,7 @@ async function runSingleSqlExtraction( console.log(''); console.log( `The conflicting file is ${relativePath( - cliPaths.targetRoot, + targetPaths.rootDir, reportPath, )}, expecting the following content:`, ); diff --git a/packages/repo-tools/src/commands/generate-catalog-info/codeowners.ts b/packages/repo-tools/src/commands/generate-catalog-info/codeowners.ts index 25703e4fc2..dae8506e9b 100644 --- a/packages/repo-tools/src/commands/generate-catalog-info/codeowners.ts +++ b/packages/repo-tools/src/commands/generate-catalog-info/codeowners.ts @@ -19,7 +19,7 @@ import { matchFile as matchCodeowner, parse as parseCodeowners, } from 'codeowners-utils'; -import { relative as relativePath, resolve as resolvePath } from 'path'; +import { relative as relativePath, resolve as resolvePath } from 'node:path'; import { readFile } from './utils'; export async function loadCodeowners(): Promise { diff --git a/packages/repo-tools/src/commands/generate-catalog-info/generate-catalog-info.ts b/packages/repo-tools/src/commands/generate-catalog-info/generate-catalog-info.ts index 1893bd25c8..f2db58b790 100644 --- a/packages/repo-tools/src/commands/generate-catalog-info/generate-catalog-info.ts +++ b/packages/repo-tools/src/commands/generate-catalog-info/generate-catalog-info.ts @@ -16,7 +16,7 @@ import YAML from 'js-yaml'; import pLimit from 'p-limit'; -import { relative as relativePath, resolve as resolvePath } from 'path'; +import { relative as relativePath, resolve as resolvePath } from 'node:path'; import { yamlOverwrite } from 'yaml-diff-patch'; import chalk from 'chalk'; import { PackageGraph, PackageRole } from '@backstage/cli-node'; diff --git a/packages/repo-tools/src/commands/generate-catalog-info/utils.ts b/packages/repo-tools/src/commands/generate-catalog-info/utils.ts index 3f23b35112..80449ae656 100644 --- a/packages/repo-tools/src/commands/generate-catalog-info/utils.ts +++ b/packages/repo-tools/src/commands/generate-catalog-info/utils.ts @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import fs from 'fs'; +import fs from 'node:fs'; import { Package } from '@manypkg/get-packages'; import { BackstagePackageJson as BackstagePackageJsonActual, PackageRole, } from '@backstage/cli-node'; -import { promisify } from 'util'; +import { promisify } from 'node:util'; export const readFile = promisify(fs.readFile); export const writeFile = promisify(fs.writeFile); diff --git a/packages/repo-tools/src/commands/generate-patch/generate-patch.ts b/packages/repo-tools/src/commands/generate-patch/generate-patch.ts index 10b31e0584..a18d623ab4 100644 --- a/packages/repo-tools/src/commands/generate-patch/generate-patch.ts +++ b/packages/repo-tools/src/commands/generate-patch/generate-patch.ts @@ -15,20 +15,20 @@ */ import { getPackages, Package, Packages } from '@manypkg/get-packages'; -import os from 'os'; +import os from 'node:os'; import fs from 'fs-extra'; import { relative as relativePath, join as joinPath, resolve as resolvePath, posix, -} from 'path'; +} from 'node:path'; import { exec } from '../../lib/exec'; import { ForwardedError } from '@backstage/errors'; -import { Readable } from 'stream'; -import { finished } from 'stream/promises'; -import { ReadableStream } from 'stream/web'; -import tar from 'tar'; +import { Readable } from 'node:stream'; +import { finished } from 'node:stream/promises'; +import { ReadableStream } from 'node:stream/web'; +import * as tar from 'tar'; // TODO: add option for this const DEFAULT_REGISTRY_URL = 'https://registry.npmjs.org'; @@ -98,7 +98,9 @@ export default async ( throw new Error(`Could not find package ${packageArg} in source repo`); } - const tmpDir = await fs.mkdtemp(os.tmpdir()); + const tmpDir = await fs.mkdtemp( + joinPath(os.tmpdir(), 'backstage-repo-tools-generate-patch-'), + ); const ctx: PatchContext = { sourceRepo, targetRepo, diff --git a/packages/repo-tools/src/commands/index.ts b/packages/repo-tools/src/commands/index.ts index 01288056c7..51dfdaf668 100644 --- a/packages/repo-tools/src/commands/index.ts +++ b/packages/repo-tools/src/commands/index.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { assertError } from '@backstage/errors'; +import { toError } from '@backstage/errors'; import { Command } from 'commander'; import { exitWithError } from '../lib/errors'; @@ -44,24 +44,26 @@ function registerPackageCommand(program: Command) { openApiCommand .command('generate') + .description( + 'Command to generate a client and/or a server stub from an OpenAPI spec.', + ) .option( '--client-package [package]', 'Top-level path to where the client should be generated, ie packages/catalog-client.', ) - .option('--server') - .description( - 'Command to generate a client and/or a server stub from an OpenAPI spec.', - ) - .option('--client-additional-properties [properties]') - .description( + .option('--server', 'Generate server stub') + .option( + '--client-additional-properties [properties]', 'Additional properties that can be passed to @openapitools/openapi-generator-cli', ) - .option('--server-additional-properties [properties]') - .description( + .option( + '--server-additional-properties [properties]', 'Additional properties that can be passed to @openapitools/openapi-generator-cli', ) - .option('--watch') - .description('Watch the OpenAPI spec for changes and regenerate on save.') + .option( + '--watch', + 'Watch the OpenAPI spec for changes and regenerate on save.', + ) .action(lazy(() => import('./package/schema/openapi/generate'), 'command')); openApiCommand @@ -224,6 +226,7 @@ export function registerCommands(program: Command) { 'CI run checks that there are no changes to catalog-info.yaml files', ) .description('Create or fix info yaml files for all backstage packages') + .allowExcessArguments(true) .action( lazy( () => import('./generate-catalog-info/generate-catalog-info'), @@ -299,8 +302,7 @@ export function lazy( process.exit(0); } catch (error) { - assertError(error); - exitWithError(error); + exitWithError(toError(error)); } }; } diff --git a/packages/repo-tools/src/commands/knip-reports/knip-extractor.ts b/packages/repo-tools/src/commands/knip-reports/knip-extractor.ts index af3e32d2f4..d7eadb3523 100644 --- a/packages/repo-tools/src/commands/knip-reports/knip-extractor.ts +++ b/packages/repo-tools/src/commands/knip-reports/knip-extractor.ts @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { paths as cliPaths } from '../../lib/paths'; +import { targetPaths } from '@backstage/cli-common'; import pLimit from 'p-limit'; -import os from 'os'; -import { relative as relativePath, resolve as resolvePath } from 'path'; +import os from 'node:os'; +import { relative as relativePath, resolve as resolvePath } from 'node:path'; import fs from 'fs-extra'; import type { KnipConfig } from 'knip'; import { createBinRunner } from '../util'; @@ -100,9 +100,9 @@ async function handlePackage({ }: KnipPackageOptions) { console.log(`## Processing ${packageDir}`); - const fullDir = cliPaths.resolveTargetRoot(packageDir); + const fullDir = targetPaths.resolveRoot(packageDir); const reportPath = resolvePath(fullDir, 'knip-report.md'); - const run = createBinRunner(cliPaths.targetRoot, ''); + const run = createBinRunner(targetPaths.rootDir, ''); let report = await run( `${knipDir}/knip.js`, @@ -149,7 +149,7 @@ async function handlePackage({ console.log(''); console.log( `The conflicting file is ${relativePath( - cliPaths.targetRoot, + targetPaths.rootDir, reportPath, )}, expecting the following content:`, ); @@ -168,8 +168,8 @@ export async function runKnipReports({ packageDirs, isLocalBuild, }: KnipExtractionOptions) { - const knipDir = cliPaths.resolveTargetRoot('./node_modules/knip/bin/'); - const knipConfigPath = cliPaths.resolveTargetRoot('./knip.json'); + const knipDir = targetPaths.resolveRoot('./node_modules/knip/bin/'); + const knipConfigPath = targetPaths.resolveRoot('./knip.json'); const limiter = pLimit(os.cpus().length); await generateKnipConfig({ knipConfigPath }); diff --git a/packages/repo-tools/src/commands/lint-legacy-backend-exports/lint-legacy-backend-exports.ts b/packages/repo-tools/src/commands/lint-legacy-backend-exports/lint-legacy-backend-exports.ts index 29fb4e7b20..9e2c664e60 100644 --- a/packages/repo-tools/src/commands/lint-legacy-backend-exports/lint-legacy-backend-exports.ts +++ b/packages/repo-tools/src/commands/lint-legacy-backend-exports/lint-legacy-backend-exports.ts @@ -16,11 +16,11 @@ import { Project } from 'ts-morph'; import { BackstagePackageJson, PackageGraph } from '@backstage/cli-node'; import fs from 'fs-extra'; -import { paths as cliPaths } from '../../lib/paths'; -import path from 'path'; +import { targetPaths } from '@backstage/cli-common'; +import path from 'node:path'; const project = new Project({ - tsConfigFilePath: cliPaths.resolveTargetRoot('tsconfig.json'), + tsConfigFilePath: targetPaths.resolveRoot('tsconfig.json'), }); function readPackageJson(pkg: string) { diff --git a/packages/repo-tools/src/commands/package-docs/Cache.test.ts b/packages/repo-tools/src/commands/package-docs/Cache.test.ts index 3fe52dcabd..02c1f5f427 100644 --- a/packages/repo-tools/src/commands/package-docs/Cache.test.ts +++ b/packages/repo-tools/src/commands/package-docs/Cache.test.ts @@ -20,15 +20,17 @@ import { createMockDirectory, MockDirectory, } from '@backstage/backend-test-utils'; -import { readFile } from 'fs/promises'; -import { join as joinPath } from 'path'; +import { readFile } from 'node:fs/promises'; +import { join as joinPath } from 'node:path'; jest.mock('crypto', () => { + const actual = jest.requireActual('crypto'); const hash = { update: jest.fn(), digest: jest.fn().mockReturnValue('test'), }; return { + ...actual, createHash: jest.fn().mockReturnValue(hash), }; }); diff --git a/packages/repo-tools/src/commands/package-docs/Cache.ts b/packages/repo-tools/src/commands/package-docs/Cache.ts index ec0811b77a..0ca38c1c39 100644 --- a/packages/repo-tools/src/commands/package-docs/Cache.ts +++ b/packages/repo-tools/src/commands/package-docs/Cache.ts @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { readFile, writeFile, cp } from 'fs/promises'; +import { readFile, writeFile, cp } from 'node:fs/promises'; import globby from 'globby'; -import { dirname, join as joinPath, relative } from 'path'; -import crypto from 'crypto'; +import { dirname, join as joinPath, relative } from 'node:path'; +import crypto from 'node:crypto'; import { Lockfile } from '@backstage/cli-node'; import { exists, rm, mkdirp } from 'fs-extra'; -import { z } from 'zod'; +import { z } from 'zod/v3'; import { CACHE_DIR, CACHE_FILE } from './constants'; const version = '1'; diff --git a/packages/repo-tools/src/commands/package-docs/command.ts b/packages/repo-tools/src/commands/package-docs/command.ts index 28255193f0..777f0f5883 100644 --- a/packages/repo-tools/src/commands/package-docs/command.ts +++ b/packages/repo-tools/src/commands/package-docs/command.ts @@ -13,17 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { exec } from 'child_process'; -import { promisify } from 'util'; -import { paths as cliPaths, resolvePackagePaths } from '../../lib/paths'; +import { exec } from 'node:child_process'; +import { promisify } from 'node:util'; +import { targetPaths } from '@backstage/cli-common'; +import { resolvePackagePaths } from '../../lib/paths'; import { createTemporaryTsConfig } from './utils'; -import { readFile, rm, writeFile } from 'fs/promises'; +import { readFile, rm, writeFile } from 'node:fs/promises'; import pLimit from 'p-limit'; import { mkdirp } from 'fs-extra'; import { PackageDocsCache } from './Cache'; import { Lockfile } from '@backstage/cli-node'; import { glob } from 'glob'; -import { existsSync } from 'fs'; +import { existsSync } from 'node:fs'; const limit = pLimit(8); @@ -31,8 +32,8 @@ const execAsync = promisify(exec); const EXCLUDE = [ 'packages/app', - 'packages/app-next', - 'packages/app-next-example-plugin', + 'packages/app-legacy', + 'packages/app-example-plugin', 'packages/cli', 'packages/cli-common', 'packages/cli-node', @@ -74,7 +75,7 @@ async function generateDocJson(pkg: string) { const temporaryTsConfigPath: string = await createTemporaryTsConfig(pkg); const packageJson = JSON.parse( - await readFile(cliPaths.resolveTargetRoot(pkg, 'package.json'), 'utf-8'), + await readFile(targetPaths.resolveRoot(pkg, 'package.json'), 'utf-8'), ); const exports = getExports(packageJson); @@ -85,17 +86,17 @@ async function generateDocJson(pkg: string) { return false; } - await mkdirp(cliPaths.resolveTargetRoot(`dist-types`, pkg)); + await mkdirp(targetPaths.resolveRoot(`dist-types`, pkg)); const { stdout, stderr } = await execAsync( [ - cliPaths.resolveTargetRoot('node_modules/.bin/typedoc'), + targetPaths.resolveRoot('node_modules/.bin/typedoc'), '--json', - cliPaths.resolveTargetRoot(`dist-types`, pkg, 'docs.json'), + targetPaths.resolveRoot(`dist-types`, pkg, 'docs.json'), '--tsconfig', temporaryTsConfigPath, '--basePath', - cliPaths.targetRoot, + targetPaths.rootDir, '--skipErrorChecking', ...(getExports(packageJson).flatMap(e => [ '--entryPoints', @@ -117,7 +118,8 @@ export default async function packageDocs(paths: string[] = [], opts: any) { console.warn('!!! This is an experimental command !!!'); const existingDocsJsonPaths = glob.sync( - cliPaths.resolveTargetRoot('dist-types/**/docs.json'), + targetPaths.resolveRoot('dist-types/**/docs.json'), + { windowsPathsNoEscape: true }, ); if (existingDocsJsonPaths.length > 0) { console.warn( @@ -129,7 +131,7 @@ export default async function packageDocs(paths: string[] = [], opts: any) { } } console.warn('!!! Deleting existing docs output !!!'); - await rm(cliPaths.resolveTargetRoot('type-docs'), { + await rm(targetPaths.resolveRoot('type-docs'), { recursive: true, force: true, }); @@ -140,8 +142,8 @@ export default async function packageDocs(paths: string[] = [], opts: any) { }); const cache = await PackageDocsCache.loadAsync( - cliPaths.resolveTargetRoot(), - await Lockfile.load(cliPaths.resolveTargetRoot('yarn.lock')), + targetPaths.rootDir, + await Lockfile.load(targetPaths.resolveRoot('yarn.lock')), ); console.log(`### Generating docs.`); @@ -149,10 +151,7 @@ export default async function packageDocs(paths: string[] = [], opts: any) { selectedPackageDirs.map(pkg => limit(async () => { const pkgJson = JSON.parse( - await readFile( - cliPaths.resolveTargetRoot(pkg, 'package.json'), - 'utf-8', - ), + await readFile(targetPaths.resolveRoot(pkg, 'package.json'), 'utf-8'), ); if (EXCLUDE.includes(pkg) || pkgJson.name.startsWith('@internal/')) { return; @@ -171,10 +170,7 @@ export default async function packageDocs(paths: string[] = [], opts: any) { console.log(`### Processing ${pkg}`); const success = await generateDocJson(pkg); if (success) { - await cache.write( - pkg, - cliPaths.resolveTargetRoot(`dist-types`, pkg), - ); + await cache.write(pkg, targetPaths.resolveRoot(`dist-types`, pkg)); } } catch (e) { console.error('Failed to generate docs for', pkg); @@ -187,7 +183,7 @@ export default async function packageDocs(paths: string[] = [], opts: any) { const generatedPackageDirs = []; for (const pkg of selectedPackageDirs) { try { - const docsJsonPath = cliPaths.resolveTargetRoot( + const docsJsonPath = targetPaths.resolveRoot( `dist-types/${pkg}/docs.json`, ); const docsJson = JSON.parse(await readFile(docsJsonPath, 'utf-8')); @@ -211,7 +207,7 @@ export default async function packageDocs(paths: string[] = [], opts: any) { const { stdout, stderr } = await execAsync( [ - cliPaths.resolveTargetRoot('node_modules/.bin/typedoc'), + targetPaths.resolveRoot('node_modules/.bin/typedoc'), '--entryPointStrategy', 'merge', ...generatedPackageDirs.flatMap(pkg => [ @@ -220,13 +216,13 @@ export default async function packageDocs(paths: string[] = [], opts: any) { ]), ...HIGHLIGHT_LANGUAGES.flatMap(e => ['--highlightLanguages', e]), '--out', - cliPaths.resolveTargetRoot('type-docs'), - ...(existsSync(cliPaths.resolveTargetRoot('typedoc.json')) - ? ['--options', cliPaths.resolveTargetRoot('typedoc.json')] + targetPaths.resolveRoot('type-docs'), + ...(existsSync(targetPaths.resolveRoot('typedoc.json')) + ? ['--options', targetPaths.resolveRoot('typedoc.json')] : []), ].join(' '), { - cwd: cliPaths.targetRoot, + cwd: targetPaths.rootDir, }, ); diff --git a/packages/repo-tools/src/commands/package-docs/utils.ts b/packages/repo-tools/src/commands/package-docs/utils.ts index 2cb95443b6..21294daa79 100644 --- a/packages/repo-tools/src/commands/package-docs/utils.ts +++ b/packages/repo-tools/src/commands/package-docs/utils.ts @@ -14,11 +14,15 @@ * limitations under the License. */ +import { findOwnPaths } from '@backstage/cli-common'; import fs from 'fs-extra'; -import { paths as cliPaths } from '../../lib/paths'; export async function createTemporaryTsConfig(dir: string) { - const path = cliPaths.resolveOwnRoot(dir, 'tsconfig.typedoc.tmp.json'); + /* eslint-disable-next-line no-restricted-syntax */ + const path = findOwnPaths(__dirname).resolveRoot( + dir, + 'tsconfig.typedoc.tmp.json', + ); process.once('exit', () => { fs.removeSync(path); diff --git a/packages/repo-tools/src/commands/package/schema/openapi/diff.ts b/packages/repo-tools/src/commands/package/schema/openapi/diff.ts index 3cb0baf313..851b2b3a48 100644 --- a/packages/repo-tools/src/commands/package/schema/openapi/diff.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/diff.ts @@ -16,11 +16,11 @@ import chalk from 'chalk'; import { exec } from '../../../../lib/exec'; import { getPathToCurrentOpenApiSpec } from '../../../../lib/openapi/helpers'; -import { paths as cliPaths } from '../../../../lib/paths'; +import { targetPaths } from '@backstage/cli-common'; import { OptionValues } from 'commander'; -import { env } from 'process'; -import { readFile, rm } from 'fs/promises'; -import { resolve } from 'path'; +import { env } from 'node:process'; +import { readFile, rm } from 'node:fs/promises'; +import { resolve } from 'node:path'; const reduceOpticOutput = (output: string) => { return output @@ -53,7 +53,7 @@ async function check(opts: OptionValues) { baseRef, ], { - cwd: cliPaths.targetRoot, + cwd: targetPaths.rootDir, env: { CI: opts.json ? '1' : undefined, ...env }, }, ); @@ -65,7 +65,7 @@ async function check(opts: OptionValues) { if (opts.json) { const file = ( - await readFile(resolve(cliPaths.targetRoot, 'ci-run-details.json')) + await readFile(resolve(targetPaths.rootDir, 'ci-run-details.json')) ).toString(); const results = JSON.parse(file); console.log(file); @@ -73,7 +73,7 @@ async function check(opts: OptionValues) { throw new Error('Some checks failed'); } - await rm(resolve(cliPaths.targetRoot, 'ci-run-details.json')); + await rm(resolve(targetPaths.rootDir, 'ci-run-details.json')); } else { console.log(reduceOpticOutput(output)); if (!opts.ignore && failed) { diff --git a/packages/repo-tools/src/commands/package/schema/openapi/fuzz.ts b/packages/repo-tools/src/commands/package/schema/openapi/fuzz.ts index 7ff0f30fb0..12e5d7d527 100644 --- a/packages/repo-tools/src/commands/package/schema/openapi/fuzz.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/fuzz.ts @@ -14,13 +14,13 @@ * limitations under the License. */ import fs from 'fs-extra'; -import { paths as cliPaths } from '../../../../lib/paths'; +import { targetPaths } from '@backstage/cli-common'; import chalk from 'chalk'; import { spawn } from '../../../../lib/exec'; import { getPathToCurrentOpenApiSpec } from '../../../../lib/openapi/helpers'; import { ConfigSources } from '@backstage/config-loader'; import YAML from 'js-yaml'; -import { join } from 'path'; +import { join } from 'node:path'; import { OptionValues } from 'commander'; import { sync as existsSync } from 'command-exists'; @@ -40,7 +40,7 @@ async function fuzz(opts: OptionValues) { await fs.readFile(resolvedOpenapiPath, 'utf8'), ) as { info: { title: string } }; const configSource = ConfigSources.default({ - rootDir: cliPaths.targetRoot, + rootDir: targetPaths.rootDir, }); const config = await ConfigSources.toConfig(configSource); const pluginId = openapiSpec.info.title; @@ -48,7 +48,7 @@ async function fuzz(opts: OptionValues) { if (opts.debug) { args.push( '--cassette-path', - cliPaths.resolveTargetRoot(join('.cassettes', `${pluginId}.yml`)), + targetPaths.resolveRoot(join('.cassettes', `${pluginId}.yml`)), ); } diff --git a/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts b/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts index 8a8817c721..42e9a9e682 100644 --- a/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts @@ -14,21 +14,22 @@ * limitations under the License. */ +import { resolvePackagePath } from '@backstage/backend-plugin-api'; import chalk from 'chalk'; -import { resolve } from 'path'; +import fs from 'fs-extra'; +import { resolve } from 'node:path'; +import { exec } from '../../../../../lib/exec'; import { OPENAPI_IGNORE_FILES, OUTPUT_PATH, } from '../../../../../lib/openapi/constants'; -import { paths as cliPaths } from '../../../../../lib/paths'; -import fs from 'fs-extra'; -import { exec } from '../../../../../lib/exec'; -import { resolvePackagePath } from '@backstage/backend-plugin-api'; +import { deduplicateImports } from '../../../../../lib/openapi/dedupe-imports'; +import { targetPaths } from '@backstage/cli-common'; import { + getOpenApiGeneratorKey, getPathToCurrentOpenApiSpec, toGeneratorAdditionalProperties, } from '../../../../../lib/openapi/helpers'; -import { deduplicateImports } from '../../../../../lib/openapi/dedupe-imports'; async function generate( outputDirectory: string, @@ -36,13 +37,14 @@ async function generate( abortSignal?: AbortController, ) { const resolvedOpenapiPath = await getPathToCurrentOpenApiSpec(); - const resolvedOutputDirectory = cliPaths.resolveTargetRoot( + const resolvedOutputDirectory = targetPaths.resolveRoot( outputDirectory, OUTPUT_PATH, ); const additionalProperties = toGeneratorAdditionalProperties({ initialValue: clientAdditionalProperties, }); + const generatorKey = await getOpenApiGeneratorKey(resolvedOpenapiPath); await fs.emptyDir(resolvedOutputDirectory); @@ -68,7 +70,7 @@ async function generate( 'templates/typescript-backstage-client.yaml', ), '--generator-key', - 'v3.0', + generatorKey, additionalProperties ? `--additional-properties=${additionalProperties}` : '', @@ -87,7 +89,7 @@ async function generate( await fs.writeFile( resolve(parentDirectory, 'index.ts'), - `// + `// export * from './generated';`, ); @@ -95,8 +97,8 @@ async function generate( signal: abortSignal?.signal, }); - const prettier = cliPaths.resolveTargetRoot('node_modules/.bin/prettier'); - if (prettier) { + const prettier = targetPaths.resolveRoot('node_modules/.bin/prettier'); + if (await fs.pathExists(prettier)) { await exec(`${prettier} --write ${parentDirectory}`, [], { signal: abortSignal?.signal, }); @@ -111,7 +113,12 @@ async function generate( } fs.removeSync(resolve(resolvedOutputDirectory, '.openapi-generator-ignore')); + fs.removeSync(resolve(resolvedOutputDirectory, '.gitattributes')); + fs.rmSync(resolve(resolvedOutputDirectory, 'docs'), { + recursive: true, + force: true, + }); fs.rmSync(resolve(resolvedOutputDirectory, '.openapi-generator'), { recursive: true, force: true, diff --git a/packages/repo-tools/src/commands/package/schema/openapi/generate/index.test.ts b/packages/repo-tools/src/commands/package/schema/openapi/generate/index.test.ts index 20de746b25..65586c6206 100644 --- a/packages/repo-tools/src/commands/package/schema/openapi/generate/index.test.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/generate/index.test.ts @@ -15,7 +15,7 @@ */ import { createMockDirectory } from '@backstage/backend-test-utils'; -import path from 'path'; +import path from 'node:path'; jest.mock( 'lodash', diff --git a/packages/repo-tools/src/commands/package/schema/openapi/generate/server.ts b/packages/repo-tools/src/commands/package/schema/openapi/generate/server.ts index 130c93cf71..daaf619450 100644 --- a/packages/repo-tools/src/commands/package/schema/openapi/generate/server.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/generate/server.ts @@ -18,7 +18,7 @@ import { resolvePackagePath } from '@backstage/backend-plugin-api'; import chalk from 'chalk'; import fs from 'fs-extra'; import YAML from 'js-yaml'; -import { dirname, join, resolve } from 'path'; +import { dirname, join, resolve } from 'node:path'; import { exec } from '../../../../../lib/exec'; import { OLD_SCHEMA_PATH, @@ -27,23 +27,24 @@ import { TS_SCHEMA_PATH, } from '../../../../../lib/openapi/constants'; import { deduplicateImports } from '../../../../../lib/openapi/dedupe-imports'; +import { targetPaths } from '@backstage/cli-common'; import { + getOpenApiGeneratorKey, getPathToCurrentOpenApiSpec, getRelativePathToFile, toGeneratorAdditionalProperties, } from '../../../../../lib/openapi/helpers'; -import { paths as cliPaths } from '../../../../../lib/paths'; async function generateSpecFile() { const openapiPath = await getPathToCurrentOpenApiSpec(); const yaml = YAML.load(await fs.readFile(openapiPath, 'utf8')); - const tsPath = cliPaths.resolveTarget(TS_SCHEMA_PATH); + const tsPath = targetPaths.resolve(TS_SCHEMA_PATH); const schemaDir = dirname(tsPath); await fs.mkdirp(schemaDir); - const oldTsPath = cliPaths.resolveTarget(OLD_SCHEMA_PATH); + const oldTsPath = targetPaths.resolve(OLD_SCHEMA_PATH); if (fs.existsSync(oldTsPath)) { console.warn(`Removing old schema file at ${oldTsPath}`); fs.removeSync(oldTsPath); @@ -77,9 +78,11 @@ export const createOpenApiRouter = async ( ); await exec(`yarn backstage-cli package lint`, ['--fix', tsPath, indexFile]); - if (await cliPaths.resolveTargetRoot('node_modules/.bin/prettier')) { + if ( + await fs.pathExists(targetPaths.resolveRoot('node_modules/.bin/prettier')) + ) { await exec(`yarn prettier`, ['--write', tsPath, indexFile], { - cwd: cliPaths.targetRoot, + cwd: targetPaths.rootDir, }); } } @@ -101,6 +104,7 @@ async function generate( const additionalProperties = toGeneratorAdditionalProperties({ initialValue: serverAdditionalProperties, }); + const generatorKey = await getOpenApiGeneratorKey(resolvedOpenapiPath); await exec( 'node', @@ -119,7 +123,7 @@ async function generate( 'templates/typescript-backstage-server.yaml', ), '--generator-key', - 'v3.0', + generatorKey, additionalProperties ? `--additional-properties=${additionalProperties}` : '', @@ -150,7 +154,7 @@ async function generate( }, ); - const prettier = cliPaths.resolveTargetRoot('node_modules/.bin/prettier'); + const prettier = targetPaths.resolveRoot('node_modules/.bin/prettier'); if (prettier) { await exec(`${prettier} --write ${resolvedOutputDirectory}`, [], { signal: abortSignal?.signal, @@ -158,7 +162,12 @@ async function generate( } fs.removeSync(resolve(resolvedOutputDirectory, '.openapi-generator-ignore')); + fs.removeSync(resolve(resolvedOutputDirectory, '.gitattributes')); + fs.rmSync(resolve(resolvedOutputDirectory, 'docs'), { + recursive: true, + force: true, + }); fs.rmSync(resolve(resolvedOutputDirectory, '.openapi-generator'), { recursive: true, force: true, diff --git a/packages/repo-tools/src/commands/package/schema/openapi/init.ts b/packages/repo-tools/src/commands/package/schema/openapi/init.ts index 00e82f65df..04b6e73483 100644 --- a/packages/repo-tools/src/commands/package/schema/openapi/init.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/init.ts @@ -14,8 +14,8 @@ * limitations under the License. */ import fs from 'fs-extra'; +import { targetPaths } from '@backstage/cli-common'; import { YAML_SCHEMA_PATH } from '../../../../lib/openapi/constants'; -import { paths as cliPaths } from '../../../../lib/paths'; import chalk from 'chalk'; import { exec } from '../../../../lib/exec'; import { @@ -49,7 +49,7 @@ capture: ${YAML_SCHEMA_PATH}: # 🔧 Runnable example with simple get requests. # Run with "PORT=3000 optic capture ${YAML_SCHEMA_PATH} --update interactive" in '${ - cliPaths.targetDir + targetPaths.dir }' # You can change the server and the 'requests' section to experiment server: @@ -61,10 +61,12 @@ capture: # 🔧 Specify a command that will generate traffic command: yarn backstage-cli package test --no-watch ${ROUTER_TEST_PATHS.map( e => `"${e}"`, - ).join(' ')} + ).join(' ')} `, ); - if (await cliPaths.resolveTargetRoot('node_modules/.bin/prettier')) { + if ( + await fs.pathExists(targetPaths.resolveRoot('node_modules/.bin/prettier')) + ) { await exec(`yarn prettier`, ['--write', opticConfigFilePath]); } } diff --git a/packages/repo-tools/src/commands/peer-deps/peer-deps.ts b/packages/repo-tools/src/commands/peer-deps/peer-deps.ts index af89560ff4..021e1d3835 100644 --- a/packages/repo-tools/src/commands/peer-deps/peer-deps.ts +++ b/packages/repo-tools/src/commands/peer-deps/peer-deps.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import { resolve as resolvePath } from 'path'; +import { resolve as resolvePath } from 'node:path'; import { getPackages, Package } from '@manypkg/get-packages'; -import { writeFileSync } from 'fs'; +import { writeFileSync } from 'node:fs'; type ExtendedPackageJSON = Package['packageJson'] & { peerDependenciesMeta?: Record; @@ -29,16 +29,26 @@ const desiredLocalVersionsOfDependencies = { '@types/react': '^18.0.0', react: '^18.0.2', 'react-dom': '^18.0.2', - 'react-router-dom': '^6.3.0', + 'react-router-dom': '^6.30.2', }; const peerDependencies = { '@types/react': '^17.0.0 || ^18.0.0', react: '^17.0.0 || ^18.0.0', 'react-dom': '^17.0.0 || ^18.0.0', - 'react-router-dom': '^6.3.0', + 'react-router-dom': '^6.30.2', }; +// Packages that have dropped React 17 support and only support React 18+ +const peerDependenciesReact18Only = { + '@types/react': '^18.0.0', + react: '^18.0.0', + 'react-dom': '^18.0.0', + 'react-router-dom': '^6.30.2', +}; + +const react18OnlyPackages = new Set(['@backstage/ui']); + const groupsOfPeerDependencies = [['@types/react', 'react', 'react-dom']]; const optionalPeerDependencies = new Set(['@types/react']); @@ -79,14 +89,21 @@ const matchesDependency = (dep: string, packageJson: ExtendedPackageJSON) => { ); }; +const getExpectedPeerDependencies = (packageJson: ExtendedPackageJSON) => { + if (react18OnlyPackages.has(packageJson.name!)) { + return peerDependenciesReact18Only; + } + return peerDependencies; +}; + const matchesPeerDependency = ( dep: string, packageJson: ExtendedPackageJSON, ) => { + const expected = getExpectedPeerDependencies(packageJson); return ( packageJson.peerDependencies && - packageJson.peerDependencies[dep] === - peerDependencies[dep as keyof typeof peerDependencies] + packageJson.peerDependencies[dep] === expected[dep as keyof typeof expected] ); }; @@ -125,7 +142,23 @@ export default async ({ fix }: { fix: boolean }) => { for (const pkg of packagesWithRelevantDependencies) { const packageJson = pkg.packageJson as ExtendedPackageJSON; + const expectedPeerDeps = getExpectedPeerDependencies(packageJson); + const allDeps = { + ...packageJson.dependencies, + ...packageJson.devDependencies, + ...packageJson.peerDependencies, + }; for (const dep of Object.keys(peerDependencies)) { + const isReferencedInPackage = !!allDeps[dep]; + const isInGroupWithReferencedDep = groupsOfPeerDependencies.some( + group => + group.includes(dep) && group.some(groupDep => !!allDeps[groupDep]), + ); + + if (!isReferencedInPackage && !isInGroupWithReferencedDep) { + continue; + } + // Validate that the peer dependencies are present. if (isPeerDependency(dep, packageJson)) { if (isOptional(dep) && !isMarkedAsOptional(dep, packageJson)) { @@ -182,7 +215,7 @@ export default async ({ fix }: { fix: boolean }) => { packageJson.peerDependencies = packageJson.peerDependencies || {}; packageJson.peerDependencies[groupDep] = - peerDependencies[groupDep as keyof typeof peerDependencies]; + expectedPeerDeps[groupDep as keyof typeof expectedPeerDeps]; }); } } @@ -209,7 +242,7 @@ export default async ({ fix }: { fix: boolean }) => { attemptToApplyFix(() => { packageJson.peerDependencies = packageJson.peerDependencies || {}; packageJson.peerDependencies[dep] = - peerDependencies[dep as keyof typeof peerDependencies]; + expectedPeerDeps[dep as keyof typeof expectedPeerDeps]; }); } } else { @@ -219,7 +252,7 @@ export default async ({ fix }: { fix: boolean }) => { attemptToApplyFix(() => { packageJson.peerDependencies = packageJson.peerDependencies || {}; packageJson.peerDependencies[dep] = - peerDependencies[dep as keyof typeof peerDependencies]; + expectedPeerDeps[dep as keyof typeof expectedPeerDeps]; }); } } diff --git a/packages/repo-tools/src/commands/repo/schema/openapi/diff.ts b/packages/repo-tools/src/commands/repo/schema/openapi/diff.ts index 02f3db0c43..2648f78f06 100644 --- a/packages/repo-tools/src/commands/repo/schema/openapi/diff.ts +++ b/packages/repo-tools/src/commands/repo/schema/openapi/diff.ts @@ -16,16 +16,16 @@ import { PackageGraph } from '@backstage/cli-node'; import { OptionValues } from 'commander'; import { exec } from '../../../../lib/exec'; +import { targetPaths } from '@backstage/cli-common'; import { CiRunDetails, generateCompareSummaryMarkdown, } from '../../../../lib/openapi/optic/helpers'; -import { paths as cliPaths } from '../../../../lib/paths'; import { YAML_SCHEMA_PATH } from '../../../../lib/openapi/constants'; function cleanUpApiName(e: { apiName: string }) { e.apiName = e.apiName - .replace(cliPaths.targetDir, '') + .replace(targetPaths.dir, '') .replace(YAML_SCHEMA_PATH, ''); } @@ -46,7 +46,7 @@ export async function command(opts: OptionValues) { const changedOpenApiSpecs = changedFiles .split('\n') .filter(e => e.endsWith(YAML_SCHEMA_PATH)) - .map(e => cliPaths.resolveTarget(e)); + .map(e => targetPaths.resolve(e)); // filter packages by changedFiles packages = packages.filter(pkg => diff --git a/packages/repo-tools/src/commands/repo/schema/openapi/lint.ts b/packages/repo-tools/src/commands/repo/schema/openapi/lint.ts index 515d1d68ed..91f43c4cee 100644 --- a/packages/repo-tools/src/commands/repo/schema/openapi/lint.ts +++ b/packages/repo-tools/src/commands/repo/schema/openapi/lint.ts @@ -49,13 +49,15 @@ async function lint(directoryPath: string, config?: { strict: boolean }) { { extends: [oas, ruleset], rules: { - 'allow-reserved-in-params': { - given: '$.paths..parameters[*]', + 'allow-reserved-in-query-params': { + given: '$.paths..parameters[?(@.in == "query")]', then: { field: 'allowReserved', function: truthy, }, severity: 'error', + message: + 'Query parameters must specify allowReserved (true or false)', }, }, overrides: [ @@ -68,6 +70,8 @@ async function lint(directoryPath: string, config?: { strict: boolean }) { 'operation-tags': 'off', 'hosts-https-only-oas3': 'off', 'no-unknown-error-format': 'off', + // Disabled as it currently gives false positives when 'example' is used as a property name. + 'oas3-valid-media-example': 'off', }, }, ], diff --git a/packages/repo-tools/src/commands/repo/schema/openapi/test.ts b/packages/repo-tools/src/commands/repo/schema/openapi/test.ts index 833de181e3..e735e729c6 100644 --- a/packages/repo-tools/src/commands/repo/schema/openapi/test.ts +++ b/packages/repo-tools/src/commands/repo/schema/openapi/test.ts @@ -15,11 +15,11 @@ */ import fs from 'fs-extra'; -import { join } from 'path'; +import { join } from 'node:path'; import chalk from 'chalk'; +import { findOwnPaths, targetPaths } from '@backstage/cli-common'; import { runner } from '../../../../lib/runner'; import { YAML_SCHEMA_PATH } from '../../../../lib/openapi/constants'; -import { paths as cliPaths } from '../../../../lib/paths'; import { exec } from '../../../../lib/exec'; import { getPathToOpenApiSpec } from '../../../../lib/openapi/helpers'; @@ -42,7 +42,10 @@ async function test( let opticLocation = ''; try { opticLocation = ( - await exec(`yarn bin optic`, [], { cwd: cliPaths.ownRoot }) + await exec(`yarn bin optic`, [], { + /* eslint-disable-next-line no-restricted-syntax */ + cwd: findOwnPaths(__dirname).rootDir, + }) ).stdout as string; } catch (err) { throw new Error( @@ -79,7 +82,9 @@ async function test( throw err; } if ( - (await cliPaths.resolveTargetRoot('node_modules/.bin/prettier')) && + (await fs.pathExists( + targetPaths.resolveRoot('node_modules/.bin/prettier'), + )) && options?.update ) { await exec(`yarn prettier`, ['--write', openapiPath]); diff --git a/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts b/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts index 9eab1c77f5..9834009d09 100644 --- a/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts +++ b/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts @@ -16,11 +16,11 @@ import fs from 'fs-extra'; import { isEqual } from 'lodash'; -import { join } from 'path'; +import { join } from 'node:path'; import chalk from 'chalk'; -import { relative as relativePath, resolve as resolvePath } from 'path'; +import { relative as relativePath, resolve as resolvePath } from 'node:path'; +import { targetPaths } from '@backstage/cli-common'; import { runner } from '../../../../lib/runner'; -import { paths as cliPaths } from '../../../../lib/paths'; import { OLD_SCHEMA_PATH, TS_SCHEMA_PATH, @@ -60,7 +60,7 @@ async function verify(directoryPath: string) { throw new Error(`\`${TS_SCHEMA_PATH}\` needs to have a 'spec' export.`); } if (!isEqual(schema.spec, yaml)) { - const path = relativePath(cliPaths.targetRoot, directoryPath); + const path = relativePath(targetPaths.rootDir, directoryPath); throw new Error( `\`${YAML_SCHEMA_PATH}\` and \`${TS_SCHEMA_PATH}\` do not match. Please run \`yarn backstage-repo-tools package schema openapi generate\` from '${path}' to regenerate \`${TS_SCHEMA_PATH}\`.`, ); diff --git a/packages/repo-tools/src/commands/type-deps/type-deps.ts b/packages/repo-tools/src/commands/type-deps/type-deps.ts index 877228a238..46dd0f67c5 100644 --- a/packages/repo-tools/src/commands/type-deps/type-deps.ts +++ b/packages/repo-tools/src/commands/type-deps/type-deps.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import fs from 'fs'; -import { resolve as resolvePath } from 'path'; +import fs from 'node:fs'; +import { resolve as resolvePath } from 'node:path'; // Cba polluting root package.json, we'll have this // eslint-disable-next-line @backstage/no-undeclared-imports import chalk from 'chalk'; @@ -78,6 +78,50 @@ function shouldCheckTypes(pkg: Package & PackageJsonWithTypes) { ); } +/** + * Read a declaration file and recursively follow all relative imports and + * re-exports, collecting the source content of each file encountered. + */ +function collectDeclSources(entryPath: string): string[] { + const visited = new Set(); + const sources: string[] = []; + + function visit(filePath: string) { + const resolved = resolvePath(filePath); + if (visited.has(resolved)) { + return; + } + visited.add(resolved); + + // Handle .js -> .d.ts extension mapping used by declaration chunk files + let actualPath = resolved; + if (!fs.existsSync(actualPath)) { + actualPath = resolved.replace(/\.js$/, '.d.ts'); + } + if (!fs.existsSync(actualPath)) { + return; + } + + const src = fs.readFileSync(actualPath, 'utf8'); + sources.push(src); + + // Follow relative imports and re-exports into chunk files + const relativeRefs = ( + src.match(/^(?:import|export) .* from '\..*';$/gm) || [] + ) + .map(match => match.match(/from '(.*)'/)?.[1] ?? '') + .filter(n => n.startsWith('.')); + + const dir = resolvePath(actualPath, '..'); + for (const ref of relativeRefs) { + visit(resolvePath(dir, ref)); + } + } + + visit(entryPath); + return sources; +} + function findAllDeps(declSrc: string) { const importedDeps = (declSrc.match(/^import .* from '.*';$/gm) || []) .map(match => match.match(/from '(.*)'/)?.[1] ?? '') @@ -89,7 +133,21 @@ function findAllDeps(declSrc: string) { .filter(n => !n.startsWith('.')) // We allow references to these without an explicit dependency. .filter(n => !['node', 'react'].includes(n)); - return Array.from(new Set([...importedDeps, ...referencedDeps])); + + // Detect ambient global type namespaces (e.g. jest.Mocked, jest.MockInstance) + // that are used in type positions but don't appear as explicit imports. + // Strip comments first to avoid false positives from JSDoc examples. + const strippedSrc = declSrc + .replace(/\/\*[\s\S]*?\*\//g, '') + .replace(/\/\/.*$/gm, ''); + const ambientDeps: string[] = []; + if (/\bjest\.\w/.test(strippedSrc)) { + ambientDeps.push('jest'); + } + + return Array.from( + new Set([...importedDeps, ...referencedDeps, ...ambientDeps]), + ); } /** @@ -98,11 +156,8 @@ function findAllDeps(declSrc: string) { */ function checkTypes(pkg: Package) { const allDeps = getPackageExportDetails(pkg.packageJson).flatMap(exp => { - const typeDecl = fs.readFileSync( - resolvePath(pkg.dir, 'dist', exp.distPath), - 'utf8', - ); - return findAllDeps(typeDecl); + const entryPath = resolvePath(pkg.dir, 'dist', exp.distPath); + return collectDeclSources(entryPath).flatMap(src => findAllDeps(src)); }); const deps = Array.from(new Set(allDeps)); diff --git a/packages/repo-tools/src/commands/util.ts b/packages/repo-tools/src/commands/util.ts index c232a8aff2..043d2aedf3 100644 --- a/packages/repo-tools/src/commands/util.ts +++ b/packages/repo-tools/src/commands/util.ts @@ -14,58 +14,75 @@ * limitations under the License. */ -import { spawn } from 'child_process'; -import os from 'os'; -import pLimit from 'p-limit'; +import { spawn } from 'node:child_process'; +import { randomUUID } from 'node:crypto'; +import { openSync, closeSync, readFileSync, unlinkSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; -// Some commands launch full node processes doing heavy work, which at high -// concurrency levels risk exhausting system resources. Placing the limiter here -// at the root level ensures that the concurrency boundary applies globally, not -// just per-runner. -const limiter = pLimit(os.cpus().length); +// Matches ANSI SGR escape sequences (e.g. bold, color, reset) +const ansiPattern = new RegExp(`${String.fromCharCode(0x1b)}\\[[0-9;]*m`, 'g'); +/** + * Redirect stdout to a temp file so that output is captured reliably even when + * child processes call process.exit() before async buffers have been flushed. + * + * Uses async spawn so that multiple invocations can run concurrently when + * combined with a concurrency limiter. + */ export function createBinRunner(cwd: string, path: string) { - return async (...command: string[]) => - limiter( - () => - new Promise((resolve, reject) => { - // Handle the case where path is empty and the script path is the first command argument - const args = path ? [path, ...command] : command; - const child = spawn('node', args, { - cwd, - stdio: ['ignore', 'pipe', 'pipe'], - }); + return (...command: string[]) => { + return new Promise((resolve, reject) => { + const args = path ? [path, ...command] : command; + const outPath = join(tmpdir(), `backstage-cli-out-${randomUUID()}.txt`); + const outFd = openSync(outPath, 'w'); - let stdout = ''; - let stderr = ''; + const child = spawn('node', args, { + cwd, + env: { ...process.env, NO_COLOR: '1' }, + stdio: ['ignore', outFd, 'pipe'], + }); - child.stdout?.on('data', data => { - stdout += data.toString(); - }); + // The fd is duplicated by the OS for the child process, so we can + // close our copy immediately after spawn. + closeSync(outFd); - child.stderr?.on('data', data => { - stderr += data.toString(); - }); + const stderrChunks: Buffer[] = []; + child.stderr?.on('data', chunk => stderrChunks.push(chunk)); - child.on('error', err => { - reject(new Error(`Process error: ${err.message}`)); - }); + child.on('error', err => { + try { + unlinkSync(outPath); + } catch { + /* ignore cleanup errors */ + } + reject(new Error(`Process error: ${err.message}`)); + }); - child.on('close', (code, signal) => { - if (signal) { - reject( - new Error( - `Process was killed with signal ${signal}\n${stderr}`, - ), - ); - } else if (code !== 0) { - reject(new Error(`Process exited with code ${code}\n${stderr}`)); - } else if (stderr.trim()) { - reject(new Error(`Command printed error output: ${stderr}`)); - } else { - resolve(stdout); - } - }); - }), - ); + child.on('close', (code, signal) => { + try { + const stdout = readFileSync(outPath, 'utf8').replace(ansiPattern, ''); + const stderr = Buffer.concat(stderrChunks).toString(); + + if (signal) { + reject( + new Error(`Process was killed with signal ${signal}\n${stderr}`), + ); + } else if (code !== 0) { + reject(new Error(`Process exited with code ${code}\n${stderr}`)); + } else if (stderr.trim()) { + reject(new Error(`Command printed error output: ${stderr}`)); + } else { + resolve(stdout); + } + } finally { + try { + unlinkSync(outPath); + } catch { + /* ignore cleanup errors */ + } + } + }); + }); + }; } diff --git a/packages/repo-tools/src/lib/errors.ts b/packages/repo-tools/src/lib/errors.ts index dd3955a10a..8e5d2b1423 100644 --- a/packages/repo-tools/src/lib/errors.ts +++ b/packages/repo-tools/src/lib/errors.ts @@ -14,27 +14,9 @@ * limitations under the License. */ +import { ExitCodeError } from '@backstage/cli-common'; import chalk from 'chalk'; -export class CustomError extends Error { - get name(): string { - return this.constructor.name; - } -} - -export class ExitCodeError extends CustomError { - readonly code: number; - - constructor(code: number, command?: string) { - super( - command - ? `Command '${command}' exited with code ${code}` - : `Child exited with code ${code}`, - ); - this.code = code; - } -} - export function exitWithError(error: Error): never { if (error instanceof ExitCodeError) { process.stderr.write(`\n${chalk.red(error.message)}\n\n`); @@ -44,5 +26,3 @@ export function exitWithError(error: Error): never { process.exit(1); } } - -export class NotFoundError extends CustomError {} diff --git a/packages/repo-tools/src/lib/exec.ts b/packages/repo-tools/src/lib/exec.ts index 248ed34f22..db0ddfb0fe 100644 --- a/packages/repo-tools/src/lib/exec.ts +++ b/packages/repo-tools/src/lib/exec.ts @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { promisify } from 'util'; +import { promisify } from 'node:util'; import { ExecOptions, SpawnOptions, exec as execCb, spawn as spawnOriginal, -} from 'child_process'; +} from 'node:child_process'; const execPromise = promisify(execCb); diff --git a/packages/repo-tools/src/lib/getPackageExportDetails.ts b/packages/repo-tools/src/lib/getPackageExportDetails.ts index 6889bb2e77..926c88b620 100644 --- a/packages/repo-tools/src/lib/getPackageExportDetails.ts +++ b/packages/repo-tools/src/lib/getPackageExportDetails.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { extname } from 'path'; +import { extname } from 'node:path'; import type { JsonObject } from '@backstage/types'; export function getPackageExportDetails(pkg: JsonObject): Array<{ diff --git a/packages/repo-tools/src/lib/openapi/constants.ts b/packages/repo-tools/src/lib/openapi/constants.ts index 22f52e75a9..b6262b68ea 100644 --- a/packages/repo-tools/src/lib/openapi/constants.ts +++ b/packages/repo-tools/src/lib/openapi/constants.ts @@ -18,15 +18,9 @@ export const YAML_SCHEMA_PATH = 'src/schema/openapi.yaml'; export const OUTPUT_PATH = 'src/schema/openapi/generated'; -export const TS_MODULE = `${OUTPUT_PATH}/router`; - export const OLD_SCHEMA_PATH = `src/schema/openapi.generated.ts`; -export const TS_SCHEMA_PATH = `${TS_MODULE}.ts`; - -export const GENERATOR_VERSION = `1.0.0`; -export const GENERATOR_NAME = 'typescript-backstage'; -export const GENERATOR_FILE = `packages/template-openapi-plugin-client/generator/target/${GENERATOR_NAME}-openapi-generator-${GENERATOR_VERSION}.jar`; +export const TS_SCHEMA_PATH = `${OUTPUT_PATH}/router.ts`; export const OPENAPI_IGNORE_FILES = [ // Get rid of the default files. diff --git a/packages/repo-tools/src/lib/openapi/dedupe-imports.ts b/packages/repo-tools/src/lib/openapi/dedupe-imports.ts index c398a43e82..9715b2f015 100644 --- a/packages/repo-tools/src/lib/openapi/dedupe-imports.ts +++ b/packages/repo-tools/src/lib/openapi/dedupe-imports.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import fs from 'fs'; +import fs from 'node:fs'; /** * Removes duplicate import and export statements from TypeScript/JavaScript files. diff --git a/packages/repo-tools/src/lib/openapi/helpers.ts b/packages/repo-tools/src/lib/openapi/helpers.ts index 0eaa090ac4..c4cd1564c6 100644 --- a/packages/repo-tools/src/lib/openapi/helpers.ts +++ b/packages/repo-tools/src/lib/openapi/helpers.ts @@ -18,8 +18,8 @@ import Parser from '@apidevtools/swagger-parser'; import fs, { pathExists } from 'fs-extra'; import YAML from 'js-yaml'; import { cloneDeep } from 'lodash'; -import { resolve } from 'path'; -import { paths } from '../paths'; +import { targetPaths } from '@backstage/cli-common'; +import { resolve } from 'node:path'; import { YAML_SCHEMA_PATH } from './constants'; export const getPathToFile = async (directory: string, filename: string) => { @@ -27,7 +27,7 @@ export const getPathToFile = async (directory: string, filename: string) => { }; export const getRelativePathToFile = async (filename: string) => { - return await getPathToFile(paths.targetDir, filename); + return await getPathToFile(targetPaths.dir, filename); }; export const assertExists = async (path: string) => { @@ -70,3 +70,32 @@ export function toGeneratorAdditionalProperties({ .map(([key, value]) => `${key}=${value}`) .join(','); } + +export async function getOpenApiGeneratorKey( + specPath: string, +): Promise { + const yaml = (await loadAndValidateOpenApiYaml(specPath)) as any; + const version = yaml.openapi; + + if (!version) { + throw new Error(`Could not determine OpenAPI version from ${specPath}`); + } + + const semver = /^(\d+)\.(\d+)\.(\d+)(-.+)?$/.exec(version); + if (!semver) { + throw new Error(`Invalid OpenAPI version format ${version} in ${specPath}`); + } + const [, major, minor] = semver; + const supportedVersions = ['3.0', '3.1']; + + const majorMinor = `${major}.${minor}`; + if (!supportedVersions.includes(majorMinor)) { + throw new Error( + `Unsupported OpenAPI version ${version} in ${specPath}. Supported versions are: ${supportedVersions.join( + ', ', + )}`, + ); + } + + return `v${majorMinor}`; +} diff --git a/packages/repo-tools/src/lib/paths.test.ts b/packages/repo-tools/src/lib/paths.test.ts index b87502adfc..509861073e 100644 --- a/packages/repo-tools/src/lib/paths.test.ts +++ b/packages/repo-tools/src/lib/paths.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { resolve as resolvePath, sep } from 'path'; +import { resolve as resolvePath, sep } from 'node:path'; import { resolvePackagePaths } from './paths'; describe('resolvePackages', () => { diff --git a/packages/repo-tools/src/lib/paths.ts b/packages/repo-tools/src/lib/paths.ts index 0dcec32ff1..fea456ea3e 100644 --- a/packages/repo-tools/src/lib/paths.ts +++ b/packages/repo-tools/src/lib/paths.ts @@ -14,13 +14,10 @@ * limitations under the License. */ -import { findPaths } from '@backstage/cli-common'; +import { targetPaths } from '@backstage/cli-common'; import { PackageGraph } from '@backstage/cli-node'; import { Minimatch } from 'minimatch'; -import { isAbsolute, relative as relativePath } from 'path'; - -/* eslint-disable-next-line no-restricted-syntax */ -export const paths = findPaths(__dirname); +import { isAbsolute, relative as relativePath } from 'node:path'; /** @internal */ export interface ResolvePackagesOptions { @@ -41,7 +38,7 @@ export async function resolvePackagePaths( for (const path of providedPaths) { const matches = packages.some( ({ dir }) => - new Minimatch(path).match(relativePath(paths.targetRoot, dir)) || + new Minimatch(path).match(relativePath(targetPaths.rootDir, dir)) || isChildPath(dir, path), ); if (!matches) { @@ -57,7 +54,7 @@ export async function resolvePackagePaths( packages = packages.filter(({ dir }) => providedPaths.some( path => - new Minimatch(path).match(relativePath(paths.targetRoot, dir)) || + new Minimatch(path).match(relativePath(targetPaths.rootDir, dir)) || isChildPath(dir, path), ), ); @@ -66,7 +63,9 @@ export async function resolvePackagePaths( if (include) { packages = packages.filter(pkg => include.some(pattern => - new Minimatch(pattern).match(relativePath(paths.targetRoot, pkg.dir)), + new Minimatch(pattern).match( + relativePath(targetPaths.rootDir, pkg.dir), + ), ), ); } @@ -76,13 +75,13 @@ export async function resolvePackagePaths( exclude.some( pattern => !new Minimatch(pattern).match( - relativePath(paths.targetRoot, pkg.dir), + relativePath(targetPaths.rootDir, pkg.dir), ), ), ); } - return packages.map(pkg => relativePath(paths.targetRoot, pkg.dir)); + return packages.map(pkg => relativePath(targetPaths.rootDir, pkg.dir)); } /** @internal */ diff --git a/packages/repo-tools/src/lib/runner.ts b/packages/repo-tools/src/lib/runner.ts index 8389caa63a..193a267cf0 100644 --- a/packages/repo-tools/src/lib/runner.ts +++ b/packages/repo-tools/src/lib/runner.ts @@ -14,10 +14,10 @@ * limitations under the License. */ +import { targetPaths } from '@backstage/cli-common'; import { resolvePackagePaths } from './paths'; import pLimit from 'p-limit'; -import { relative as relativePath } from 'path'; -import { paths as cliPaths } from './paths'; +import { relative as relativePath } from 'node:path'; import portFinder from 'portfinder'; export async function runner( @@ -58,7 +58,7 @@ export async function runner( } return { - relativeDir: relativePath(cliPaths.targetRoot, pkg), + relativeDir: relativePath(targetPaths.rootDir, pkg), resultText, }; }), diff --git a/packages/repo-tools/templates/typescript-backstage-client/licenseInfo.mustache b/packages/repo-tools/templates/typescript-backstage-client/licenseInfo.mustache index 7a1b10e780..f3855e8015 100644 --- a/packages/repo-tools/templates/typescript-backstage-client/licenseInfo.mustache +++ b/packages/repo-tools/templates/typescript-backstage-client/licenseInfo.mustache @@ -2,4 +2,4 @@ // ****************************************************************** // * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * -// ****************************************************************** \ No newline at end of file +// ****************************************************************** diff --git a/packages/repo-tools/templates/typescript-backstage-client/modelGeneric.mustache b/packages/repo-tools/templates/typescript-backstage-client/modelGeneric.mustache index d01ea29260..d246837155 100644 --- a/packages/repo-tools/templates/typescript-backstage-client/modelGeneric.mustache +++ b/packages/repo-tools/templates/typescript-backstage-client/modelGeneric.mustache @@ -18,7 +18,7 @@ export {{#isNullable}}type{{/isNullable}}{{^isNullable}}interface{{/isNullable}} * {{{.}}} */ {{/description}} - '{{name}}'{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isNullable}} | null{{/isNullable}}; + '{{#baseName}}{{baseName}}{{/baseName}}{{^baseName}}{{name}}{{/baseName}}'{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isNullable}} | null{{/isNullable}}; {{/vars}} }{{#isNullable}} | null{{/isNullable}} diff --git a/packages/repo-tools/templates/typescript-backstage-client/modelTaggedUnion.mustache b/packages/repo-tools/templates/typescript-backstage-client/modelTaggedUnion.mustache index a3f5e00904..3f5a3baf5e 100644 --- a/packages/repo-tools/templates/typescript-backstage-client/modelTaggedUnion.mustache +++ b/packages/repo-tools/templates/typescript-backstage-client/modelTaggedUnion.mustache @@ -12,7 +12,7 @@ export interface {{classname}} { {{>modelGenericAdditionalProperties}} * {{{.}}} */ {{/description}} - {{name}}{{^required}}?{{/required}}: {{#discriminatorValue}}'{{.}}'{{/discriminatorValue}}{{^discriminatorValue}}{{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{/discriminatorValue}}{{#isNullable}} | null{{/isNullable}}; + '{{#baseName}}{{baseName}}{{/baseName}}{{^baseName}}{{name}}{{/baseName}}'{{^required}}?{{/required}}: {{#discriminatorValue}}'{{.}}'{{/discriminatorValue}}{{^discriminatorValue}}{{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{/discriminatorValue}}{{#isNullable}} | null{{/isNullable}}; {{/allVars}} } {{>modelGenericEnums}} diff --git a/packages/repo-tools/templates/typescript-backstage-server/licenseInfo.mustache b/packages/repo-tools/templates/typescript-backstage-server/licenseInfo.mustache index 7a1b10e780..f3855e8015 100644 --- a/packages/repo-tools/templates/typescript-backstage-server/licenseInfo.mustache +++ b/packages/repo-tools/templates/typescript-backstage-server/licenseInfo.mustache @@ -2,4 +2,4 @@ // ****************************************************************** // * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * -// ****************************************************************** \ No newline at end of file +// ****************************************************************** diff --git a/packages/repo-tools/templates/typescript-backstage-server/modelGeneric.mustache b/packages/repo-tools/templates/typescript-backstage-server/modelGeneric.mustache index d01ea29260..d246837155 100644 --- a/packages/repo-tools/templates/typescript-backstage-server/modelGeneric.mustache +++ b/packages/repo-tools/templates/typescript-backstage-server/modelGeneric.mustache @@ -18,7 +18,7 @@ export {{#isNullable}}type{{/isNullable}}{{^isNullable}}interface{{/isNullable}} * {{{.}}} */ {{/description}} - '{{name}}'{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isNullable}} | null{{/isNullable}}; + '{{#baseName}}{{baseName}}{{/baseName}}{{^baseName}}{{name}}{{/baseName}}'{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isNullable}} | null{{/isNullable}}; {{/vars}} }{{#isNullable}} | null{{/isNullable}} diff --git a/packages/repo-tools/templates/typescript-backstage-server/modelTaggedUnion.mustache b/packages/repo-tools/templates/typescript-backstage-server/modelTaggedUnion.mustache index a3f5e00904..3f5a3baf5e 100644 --- a/packages/repo-tools/templates/typescript-backstage-server/modelTaggedUnion.mustache +++ b/packages/repo-tools/templates/typescript-backstage-server/modelTaggedUnion.mustache @@ -12,7 +12,7 @@ export interface {{classname}} { {{>modelGenericAdditionalProperties}} * {{{.}}} */ {{/description}} - {{name}}{{^required}}?{{/required}}: {{#discriminatorValue}}'{{.}}'{{/discriminatorValue}}{{^discriminatorValue}}{{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{/discriminatorValue}}{{#isNullable}} | null{{/isNullable}}; + '{{#baseName}}{{baseName}}{{/baseName}}{{^baseName}}{{name}}{{/baseName}}'{{^required}}?{{/required}}: {{#discriminatorValue}}'{{.}}'{{/discriminatorValue}}{{^discriminatorValue}}{{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{/discriminatorValue}}{{#isNullable}} | null{{/isNullable}}; {{/allVars}} } {{>modelGenericEnums}} diff --git a/packages/scaffolder-internal/CHANGELOG.md b/packages/scaffolder-internal/CHANGELOG.md index 3b3a6f7c8c..b02f7ea224 100644 --- a/packages/scaffolder-internal/CHANGELOG.md +++ b/packages/scaffolder-internal/CHANGELOG.md @@ -1,5 +1,109 @@ # @internal/scaffolder +## 0.0.20 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/plugin-scaffolder-react@1.20.1 + +## 0.0.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/plugin-scaffolder-react@1.20.1-next.1 + +## 0.0.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.20.1-next.0 + - @backstage/frontend-plugin-api@0.15.2-next.0 + +## 0.0.19 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/plugin-scaffolder-react@1.20.0 + +## 0.0.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/plugin-scaffolder-react@1.20.0-next.2 + +## 0.0.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/plugin-scaffolder-react@1.19.8-next.0 + +## 0.0.18 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/plugin-scaffolder-react@1.19.7 + +## 0.0.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/plugin-scaffolder-react@1.19.6-next.0 + +## 0.0.17 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/plugin-scaffolder-react@1.19.5 + +## 0.0.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.19.5-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## 0.0.16 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/plugin-scaffolder-react@1.19.4 + +## 0.0.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/plugin-scaffolder-react@1.19.4-next.0 + +## 0.0.15 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/plugin-scaffolder-react@1.19.3 + ## 0.0.15-next.0 ### Patch Changes diff --git a/packages/scaffolder-internal/package.json b/packages/scaffolder-internal/package.json index 256ec5b5e3..89975eab5b 100644 --- a/packages/scaffolder-internal/package.json +++ b/packages/scaffolder-internal/package.json @@ -1,6 +1,6 @@ { "name": "@internal/scaffolder", - "version": "0.0.15-next.0", + "version": "0.0.20", "backstage": { "role": "web-library", "inline": true @@ -25,7 +25,7 @@ "dependencies": { "@backstage/frontend-plugin-api": "workspace:^", "@backstage/plugin-scaffolder-react": "workspace:^", - "zod": "^3.22.4" + "zod": "^3.25.76 || ^4.0.0" }, "devDependencies": { "@backstage/cli": "workspace:^" diff --git a/packages/scaffolder-internal/src/wiring/InternalFormDecorator.ts b/packages/scaffolder-internal/src/wiring/InternalFormDecorator.ts index 7942544acb..39de69a5bf 100644 --- a/packages/scaffolder-internal/src/wiring/InternalFormDecorator.ts +++ b/packages/scaffolder-internal/src/wiring/InternalFormDecorator.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { OpaqueType } from '@internal/opaque'; -import { z } from 'zod'; +import { z } from 'zod/v3'; import { ScaffolderFormDecorator, diff --git a/packages/scaffolder-internal/src/wiring/InternalFormField.ts b/packages/scaffolder-internal/src/wiring/InternalFormField.ts index b711341ea5..24e279f47f 100644 --- a/packages/scaffolder-internal/src/wiring/InternalFormField.ts +++ b/packages/scaffolder-internal/src/wiring/InternalFormField.ts @@ -15,7 +15,7 @@ */ import { OpaqueType } from '@internal/opaque'; -import { z } from 'zod'; +import { z } from 'zod/v3'; import { FormFieldExtensionData, diff --git a/packages/techdocs-cli-embedded-app/CHANGELOG.md b/packages/techdocs-cli-embedded-app/CHANGELOG.md index 3fe53ec2d6..9b98a6f4c5 100644 --- a/packages/techdocs-cli-embedded-app/CHANGELOG.md +++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md @@ -1,5 +1,360 @@ # techdocs-cli-embedded-app +## 0.2.119 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0 + - @backstage/theme@0.7.3 + - @backstage/cli@0.36.1 + - @backstage/plugin-catalog@2.0.2 + - @backstage/catalog-model@1.8.0 + - @backstage/core-app-api@1.20.0 + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/core-components@0.18.9 + - @backstage/plugin-techdocs@1.17.3 + - @backstage/frontend-defaults@0.5.1 + - @backstage/test-utils@1.7.17 + - @backstage/config@1.3.7 + - @backstage/integration-react@1.2.17 + - @backstage/plugin-app-react@0.2.2 + - @backstage/plugin-techdocs-react@1.3.10 + +## 0.2.119-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.2 + - @backstage/theme@0.7.3-next.0 + - @backstage/plugin-catalog@2.0.2-next.2 + - @backstage/core-app-api@1.20.0-next.2 + - @backstage/cli@0.36.1-next.2 + - @backstage/core-components@0.18.9-next.1 + - @backstage/plugin-techdocs@1.17.3-next.2 + - @backstage/catalog-model@1.7.8-next.0 + - @backstage/config@1.3.7-next.0 + - @backstage/frontend-defaults@0.5.1-next.2 + - @backstage/frontend-plugin-api@0.16.0-next.2 + - @backstage/test-utils@1.7.17-next.2 + - @backstage/plugin-techdocs-react@1.3.10-next.2 + - @backstage/integration-react@1.2.17-next.1 + +## 0.2.119-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.36.1-next.1 + - @backstage/ui@0.14.0-next.1 + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/test-utils@1.7.17-next.1 + - @backstage/plugin-catalog@2.0.2-next.1 + - @backstage/plugin-techdocs@1.17.3-next.1 + - @backstage/frontend-defaults@0.5.1-next.1 + - @backstage/plugin-app-react@0.2.2-next.1 + - @backstage/plugin-techdocs-react@1.3.10-next.1 + +## 0.2.119-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@2.0.2-next.0 + - @backstage/ui@0.14.0-next.0 + - @backstage/plugin-techdocs@1.17.3-next.0 + - @backstage/core-components@0.18.9-next.0 + - @backstage/frontend-plugin-api@0.15.2-next.0 + - @backstage/core-app-api@1.19.7-next.0 + - @backstage/cli@0.36.1-next.0 + - @backstage/frontend-defaults@0.5.1-next.0 + - @backstage/integration-react@1.2.17-next.0 + - @backstage/plugin-techdocs-react@1.3.10-next.0 + - @backstage/plugin-app-react@0.2.2-next.0 + - @backstage/test-utils@1.7.17-next.0 + - @backstage/catalog-model@1.7.7 + - @backstage/config@1.3.6 + - @backstage/theme@0.7.2 + +## 0.2.118 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.36.0 + - @backstage/ui@0.13.0 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/core-app-api@1.19.6 + - @backstage/plugin-techdocs@1.17.1 + - @backstage/plugin-catalog@2.0.0 + - @backstage/plugin-app-react@0.2.1 + - @backstage/frontend-defaults@0.5.0 + - @backstage/catalog-model@1.7.7 + - @backstage/integration-react@1.2.16 + - @backstage/test-utils@1.7.16 + - @backstage/plugin-techdocs-react@1.3.9 + +## 0.2.118-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.13.0-next.2 + - @backstage/cli@0.36.0-next.2 + - @backstage/core-app-api@1.19.6-next.1 + - @backstage/plugin-techdocs@1.17.1-next.2 + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-components@0.18.8-next.1 + - @backstage/plugin-app-react@0.2.1-next.1 + - @backstage/frontend-defaults@0.5.0-next.1 + - @backstage/plugin-catalog@2.0.0-next.2 + - @backstage/plugin-techdocs-react@1.3.9-next.1 + +## 0.2.118-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.13.0-next.1 + - @backstage/cli@0.36.0-next.1 + - @backstage/plugin-techdocs@1.17.1-next.1 + - @backstage/plugin-catalog@1.34.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/frontend-defaults@0.4.1-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/integration-react@1.2.16-next.1 + - @backstage/test-utils@1.7.16-next.0 + - @backstage/theme@0.7.2 + - @backstage/plugin-app-react@0.2.1-next.0 + - @backstage/plugin-techdocs-react@1.3.9-next.0 + +## 0.2.118-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.12.1-next.0 + - @backstage/cli@0.35.5-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/plugin-catalog@1.33.1-next.0 + - @backstage/plugin-techdocs@1.17.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/frontend-defaults@0.4.1-next.0 + - @backstage/integration-react@1.2.16-next.0 + - @backstage/test-utils@1.7.16-next.0 + - @backstage/theme@0.7.2 + - @backstage/plugin-app-react@0.2.1-next.0 + - @backstage/plugin-techdocs-react@1.3.9-next.0 + +## 0.2.117 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.12.0 + - @backstage/frontend-defaults@0.4.0 + - @backstage/cli@0.35.4 + - @backstage/core-components@0.18.7 + - @backstage/plugin-catalog@1.33.0 + - @backstage/core-app-api@1.19.5 + - @backstage/plugin-techdocs@1.17.0 + - @backstage/theme@0.7.2 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/plugin-app-react@0.2.0 + - @backstage/plugin-techdocs-react@1.3.8 + - @backstage/integration-react@1.2.15 + - @backstage/test-utils@1.7.15 + +## 0.2.117-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.33.0-next.1 + - @backstage/ui@0.12.0-next.1 + - @backstage/theme@0.7.2-next.0 + - @backstage/cli@0.35.4-next.1 + - @backstage/plugin-techdocs@1.16.3-next.1 + - @backstage/app-defaults@1.7.5-next.1 + - @backstage/core-components@0.18.7-next.1 + - @backstage/test-utils@1.7.15-next.1 + - @backstage/plugin-techdocs-react@1.3.8-next.0 + - @backstage/integration-react@1.2.15-next.1 + +## 0.2.117-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.35.3-next.0 + - @backstage/ui@0.12.0-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/plugin-techdocs@1.16.3-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/plugin-techdocs-react@1.3.8-next.0 + - @backstage/core-app-api@1.19.4-next.0 + - @backstage/plugin-catalog@1.32.3-next.0 + - @backstage/app-defaults@1.7.5-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/integration-react@1.2.15-next.0 + - @backstage/test-utils@1.7.15-next.0 + - @backstage/theme@0.7.1 + +## 0.2.116 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.11.0 + - @backstage/plugin-catalog@1.32.2 + - @backstage/cli@0.35.2 + - @backstage/core-components@0.18.5 + - @backstage/plugin-techdocs@1.16.2 + - @backstage/app-defaults@1.7.4 + - @backstage/integration-react@1.2.14 + - @backstage/plugin-techdocs-react@1.3.7 + +## 0.2.116-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/app-defaults@1.7.4-next.0 + - @backstage/cli@0.35.2-next.1 + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-catalog@1.32.2-next.1 + - @backstage/plugin-techdocs@1.16.2-next.1 + - @backstage/plugin-techdocs-react@1.3.7-next.0 + +## 0.2.116-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.35.2-next.1 + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-techdocs@1.16.2-next.0 + - @backstage/plugin-catalog@1.32.2-next.0 + +## 0.2.116-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.11.0-next.0 + - @backstage/cli@0.35.2-next.0 + - @backstage/app-defaults@1.7.3 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-app-api@1.19.3 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/integration-react@1.2.13 + - @backstage/test-utils@1.7.14 + - @backstage/theme@0.7.1 + - @backstage/plugin-catalog@1.32.1 + - @backstage/plugin-techdocs@1.16.1 + - @backstage/plugin-techdocs-react@1.3.6 + +## 0.2.115 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/cli@0.35.0 + - @backstage/ui@0.10.0 + - @backstage/plugin-techdocs@1.16.1 + - @backstage/plugin-catalog@1.32.1 + - @backstage/core-components@0.18.4 + - @backstage/test-utils@1.7.14 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/app-defaults@1.7.3 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-techdocs-react@1.3.6 + +## 0.2.115-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3-next.1 + - @backstage/cli@0.35.0-next.2 + - @backstage/plugin-techdocs@1.16.1-next.2 + - @backstage/plugin-catalog@1.32.1-next.1 + - @backstage/core-components@0.18.4-next.2 + - @backstage/app-defaults@1.7.3-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/test-utils@1.7.14-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/ui@0.10.0-next.1 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + +## 0.2.115-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.10.0-next.1 + - @backstage/core-components@0.18.4-next.1 + - @backstage/cli@0.34.6-next.1 + - @backstage/plugin-techdocs@1.16.1-next.1 + +## 0.2.115-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.9.1-next.0 + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/test-utils@1.7.14-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-techdocs@1.16.1-next.0 + - @backstage/plugin-catalog@1.32.1-next.0 + - @backstage/cli@0.34.6-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/app-defaults@1.7.3-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + +## 0.2.114 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.9.0 + - @backstage/cli@0.34.5 + - @backstage/plugin-techdocs@1.16.0 + - @backstage/plugin-catalog@1.32.0 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-techdocs-react@1.3.5 + - @backstage/app-defaults@1.7.2 + - @backstage/integration-react@1.2.12 + - @backstage/test-utils@1.7.13 + ## 0.2.114-next.2 ### Patch Changes diff --git a/packages/techdocs-cli-embedded-app/app-config.yaml b/packages/techdocs-cli-embedded-app/app-config.yaml index c8dfa1683c..09553edc8a 100644 --- a/packages/techdocs-cli-embedded-app/app-config.yaml +++ b/packages/techdocs-cli-embedded-app/app-config.yaml @@ -1,6 +1,11 @@ app: title: Techdocs Preview App baseUrl: http://localhost:3000 + extensions: + - sign-in-page:app: false + - page:techdocs/reader: + config: + withoutSearch: true backend: baseUrl: http://localhost:3000 diff --git a/packages/techdocs-cli-embedded-app/knip-report.md b/packages/techdocs-cli-embedded-app/knip-report.md index 3711ac996d..507eae5944 100644 --- a/packages/techdocs-cli-embedded-app/knip-report.md +++ b/packages/techdocs-cli-embedded-app/knip-report.md @@ -1,17 +1,15 @@ # Knip report -## Unused dependencies (2) +## Unused dependencies (1) -| Name | Location | Severity | -| :-------- | :----------- | :------- | -| react-use | packages/techdocs-cli-embedded-app/package.json | error | -| history | packages/techdocs-cli-embedded-app/package.json | error | +| Name | Location | Severity | +| :-------- | :---------------- | :------- | +| react-use | package.json:55:6 | error | -## Unused devDependencies (3) +## Unused devDependencies (2) -| Name | Location | Severity | -| :-------------------------- | :----------- | :------- | -| @testing-library/user-event | packages/techdocs-cli-embedded-app/package.json | error | -| @testing-library/dom | packages/techdocs-cli-embedded-app/package.json | error | -| cross-env | packages/techdocs-cli-embedded-app/package.json | error | +| Name | Location | Severity | +| :-------------------------- | :---------------- | :------- | +| @testing-library/user-event | package.json:61:6 | error | +| cross-env | package.json:64:6 | error | diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json index 024d8039cd..192455a934 100644 --- a/packages/techdocs-cli-embedded-app/package.json +++ b/packages/techdocs-cli-embedded-app/package.json @@ -1,6 +1,6 @@ { "name": "techdocs-cli-embedded-app", - "version": "0.2.114-next.2", + "version": "0.2.119", "backstage": { "role": "frontend" }, @@ -32,14 +32,15 @@ }, "prettier": "@backstage/cli/config/prettier", "dependencies": { - "@backstage/app-defaults": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/config": "workspace:^", "@backstage/core-app-api": "workspace:^", "@backstage/core-components": "workspace:^", - "@backstage/core-plugin-api": "workspace:^", + "@backstage/frontend-defaults": "workspace:^", + "@backstage/frontend-plugin-api": "workspace:^", "@backstage/integration-react": "workspace:^", + "@backstage/plugin-app-react": "workspace:^", "@backstage/plugin-catalog": "workspace:^", "@backstage/plugin-techdocs": "workspace:^", "@backstage/plugin-techdocs-react": "workspace:^", @@ -48,21 +49,19 @@ "@backstage/ui": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", - "history": "^5.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0", + "react-router-dom": "^6.30.2", "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "workspace:^", "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.0.0", "@types/react": "*", "@types/react-dom": "*", - "cross-env": "^7.0.0" + "cross-env": "^10.0.0" }, "bundled": true } diff --git a/packages/techdocs-cli-embedded-app/src/App.test.tsx b/packages/techdocs-cli-embedded-app/src/App.test.tsx index ed479e7e75..fc6d25863c 100644 --- a/packages/techdocs-cli-embedded-app/src/App.test.tsx +++ b/packages/techdocs-cli-embedded-app/src/App.test.tsx @@ -15,13 +15,27 @@ */ import { renderWithEffects } from '@backstage/test-utils'; -import App from './App'; +import app from './App'; jest.mock('./config', () => ({ configLoader: async () => [ { data: { - app: { title: 'Test' }, + app: { + title: 'Test', + extensions: [ + { + 'sign-in-page:app': false, + }, + { + 'page:techdocs/reader': { + config: { + withoutSearch: true, + }, + }, + }, + ], + }, backend: { baseUrl: 'http://localhost:7007' }, techdocs: { storageUrl: 'http://localhost:7007/api/techdocs/static/docs', @@ -34,7 +48,7 @@ jest.mock('./config', () => ({ describe('App', () => { it('should render', async () => { - const rendered = await renderWithEffects(); + const rendered = await renderWithEffects(app); expect(rendered.getByText('Docs Preview')).toBeInTheDocument(); }); }); diff --git a/packages/techdocs-cli-embedded-app/src/App.tsx b/packages/techdocs-cli-embedded-app/src/App.tsx index 13d5b925f0..229db3142d 100644 --- a/packages/techdocs-cli-embedded-app/src/App.tsx +++ b/packages/techdocs-cli-embedded-app/src/App.tsx @@ -14,85 +14,43 @@ * limitations under the License. */ -import { Navigate, Route } from 'react-router-dom'; +import techdocsPlugin from '@backstage/plugin-techdocs/alpha'; -import { - DefaultTechDocsHome, - TechDocsIndexPage, - TechDocsReaderPage, - techdocsPlugin, -} from '@backstage/plugin-techdocs'; -import { - createTechDocsAddonExtension, - TechDocsAddons, - TechDocsAddonLocations, -} from '@backstage/plugin-techdocs-react'; -import { createApp } from '@backstage/app-defaults'; -import { FlatRoutes } from '@backstage/core-app-api'; -import { CatalogEntityPage } from '@backstage/plugin-catalog'; +import { createApp } from '@backstage/frontend-defaults'; +import { ConfigReader } from '@backstage/core-app-api'; +import catalogPlugin from '@backstage/plugin-catalog/alpha'; -import { apis } from './apis'; -import * as plugins from './plugins'; +import { appApis, techdocsPluginApis } from './apis'; import { configLoader } from './config'; -import { Root } from './components/Root'; -import { techDocsPage, TechDocsThemeToggle } from './components/TechDocsPage'; -import { TechDocsLiveReload } from './LiveReloadAddon'; -const app = createApp({ - apis, - configLoader, - plugins: Object.values(plugins), +import { createFrontendModule } from '@backstage/frontend-plugin-api'; +import { SidebarContent } from './components/Sidebar'; +import { + techDocsThemeToggleAddonModule, + techdocsLiveReloadAddonModule, +} from './addons'; + +const appPlugin = createFrontendModule({ + pluginId: 'app', + extensions: [...appApis, SidebarContent], }); -const AppProvider = app.getProvider(); -const AppRouter = app.getRouter(); +const app = createApp({ + features: [ + appPlugin, + techdocsPlugin.withOverrides({ + extensions: [...techdocsPluginApis], + }), + catalogPlugin, + techDocsThemeToggleAddonModule, + techdocsLiveReloadAddonModule, + ], + advanced: { + async configLoader() { + const appConfigs = await configLoader(); + return { config: ConfigReader.fromConfigs(appConfigs) }; + }, + }, +}); -const ThemeToggleAddon = techdocsPlugin.provide( - createTechDocsAddonExtension({ - name: 'ThemeToggleAddon', - component: TechDocsThemeToggle, - location: TechDocsAddonLocations.Header, - }), -); - -const LiveReloadAddon = techdocsPlugin.provide( - createTechDocsAddonExtension({ - name: 'LiveReloadAddon', - component: TechDocsLiveReload, - location: TechDocsAddonLocations.Content, - }), -); - -const routes = ( - - - {/* we need this route as TechDocs header links relies on it */} - } - /> - }> - - - } - > - {techDocsPage} - - - - - - -); - -const App = () => ( - - - {routes} - - -); - -export default App; +export default app.createRoot(); diff --git a/packages/techdocs-cli-embedded-app/src/addons.tsx b/packages/techdocs-cli-embedded-app/src/addons.tsx new file mode 100644 index 0000000000..26ead00959 --- /dev/null +++ b/packages/techdocs-cli-embedded-app/src/addons.tsx @@ -0,0 +1,48 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { AddonBlueprint } from '@backstage/plugin-techdocs-react/alpha'; +import { TechDocsAddonLocations } from '@backstage/plugin-techdocs-react'; +import { createFrontendModule } from '@backstage/frontend-plugin-api'; +import { TechDocsThemeToggle } from './components/TechDocsPage'; +import { TechDocsLiveReload } from './components/LiveReload/LiveReloadAddon'; + +const techDocsThemeToggleAddonExtension = AddonBlueprint.make({ + name: 'techdocs-theme-toggle-addon', + params: { + name: 'ThemeToggleAddon', + component: TechDocsThemeToggle, + location: TechDocsAddonLocations.Header, + }, +}); + +export const techDocsThemeToggleAddonModule = createFrontendModule({ + pluginId: 'techdocs', + extensions: [techDocsThemeToggleAddonExtension], +}); + +const techdocsLiveReloadAddonExtension = AddonBlueprint.make({ + name: 'techdocs-live-reload-addon', + params: { + name: 'LiveReloadAddon', + component: TechDocsLiveReload, + location: TechDocsAddonLocations.Content, + }, +}); + +export const techdocsLiveReloadAddonModule = createFrontendModule({ + pluginId: 'techdocs', + extensions: [techdocsLiveReloadAddonExtension], +}); diff --git a/packages/techdocs-cli-embedded-app/src/apis.ts b/packages/techdocs-cli-embedded-app/src/apis.ts index 5bb19a563d..d6f7a2df41 100644 --- a/packages/techdocs-cli-embedded-app/src/apis.ts +++ b/packages/techdocs-cli-embedded-app/src/apis.ts @@ -13,6 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { + ApiBlueprint, + configApiRef, + DiscoveryApi, + discoveryApiRef, + IdentityApi, + identityApiRef, +} from '@backstage/frontend-plugin-api'; import { CompoundEntityRef } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; @@ -20,15 +28,6 @@ import { scmIntegrationsApiRef, ScmIntegrationsApi, } from '@backstage/integration-react'; -import { - AnyApiFactory, - configApiRef, - createApiFactory, - DiscoveryApi, - discoveryApiRef, - IdentityApi, - identityApiRef, -} from '@backstage/core-plugin-api'; import { SyncResult, TechDocsApi, @@ -158,38 +157,53 @@ class TechDocsDevApi implements TechDocsApi { } } -export const apis: AnyApiFactory[] = [ - createApiFactory({ - api: techdocsStorageApiRef, - deps: { - configApi: configApiRef, - discoveryApi: discoveryApiRef, - identityApi: identityApiRef, - }, - factory: ({ configApi, discoveryApi, identityApi }) => - new TechDocsDevStorageApi({ - configApi, - discoveryApi, - identityApi, +export const techdocsPluginApis = [ + ApiBlueprint.make({ + name: 'techdocs-dev-storage', + params: defineParams => + defineParams({ + api: techdocsStorageApiRef, + deps: { + configApi: configApiRef, + discoveryApi: discoveryApiRef, + identityApi: identityApiRef, + }, + factory: ({ configApi, discoveryApi, identityApi }) => + new TechDocsDevStorageApi({ + configApi, + discoveryApi, + identityApi, + }), }), }), - createApiFactory({ - api: techdocsApiRef, - deps: { - configApi: configApiRef, - discoveryApi: discoveryApiRef, - identityApi: identityApiRef, - }, - factory: ({ configApi, discoveryApi, identityApi }) => - new TechDocsDevApi({ - configApi, - discoveryApi, - identityApi, + ApiBlueprint.make({ + name: 'techdocs-dev', + params: defineParams => + defineParams({ + api: techdocsApiRef, + deps: { + configApi: configApiRef, + discoveryApi: discoveryApiRef, + identityApi: identityApiRef, + }, + factory: ({ configApi, discoveryApi, identityApi }) => + new TechDocsDevApi({ + configApi, + discoveryApi, + identityApi, + }), + }), + }), +]; + +export const appApis = [ + ApiBlueprint.make({ + name: 'scm-integrations', + params: defineParams => + defineParams({ + api: scmIntegrationsApiRef, + deps: { configApi: configApiRef }, + factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi), }), }), - createApiFactory({ - api: scmIntegrationsApiRef, - deps: { configApi: configApiRef }, - factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi), - }), ]; diff --git a/packages/techdocs-cli-embedded-app/src/LiveReloadAddon.test.tsx b/packages/techdocs-cli-embedded-app/src/components/LiveReload/LiveReloadAddon.test.tsx similarity index 81% rename from packages/techdocs-cli-embedded-app/src/LiveReloadAddon.test.tsx rename to packages/techdocs-cli-embedded-app/src/components/LiveReload/LiveReloadAddon.test.tsx index 1a80def9e9..ec99f405ae 100644 --- a/packages/techdocs-cli-embedded-app/src/LiveReloadAddon.test.tsx +++ b/packages/techdocs-cli-embedded-app/src/components/LiveReload/LiveReloadAddon.test.tsx @@ -15,7 +15,7 @@ */ import { render } from '@testing-library/react'; -import { TechDocsLiveReload } from './LiveReloadAddon'; +import { TechDocsLiveReload, utils } from './LiveReloadAddon'; jest.mock('@backstage/plugin-techdocs-react', () => ({ useShadowRootElements: jest.fn(() => [ @@ -38,12 +38,11 @@ jest.mock('@backstage/plugin-techdocs-react', () => ({ describe('TechDocsLiveReload', () => { const originalXHR = global.XMLHttpRequest; - let originalLocation: Location; let openSpy: jest.Mock; let sendSpy: jest.Mock; + let reloadPageSpy: jest.SpyInstance; beforeEach(() => { - originalLocation = window.location; openSpy = jest.fn(); sendSpy = jest.fn(function (this: any) { // simulate long-poll response that does NOT trigger reload (epoch unchanged) @@ -65,9 +64,11 @@ describe('TechDocsLiveReload', () => { global.XMLHttpRequest = MockXHR as any; - // Replace window.location with a mutable object for tests - delete (window as any).location; - (window as any).location = { ...originalLocation, reload: jest.fn() }; + // Spy on the utils object's reloadPage method + reloadPageSpy = jest + .spyOn(utils, 'reloadPage') + .mockImplementation(() => {}); + jest.spyOn(window, 'addEventListener').mockImplementation(() => {}); jest.spyOn(window, 'removeEventListener').mockImplementation(() => {}); Object.defineProperty(document, 'visibilityState', { @@ -79,23 +80,17 @@ describe('TechDocsLiveReload', () => { afterEach(() => { global.XMLHttpRequest = originalXHR; jest.restoreAllMocks(); - // restore original window.location - delete (window as any).location; - (window as any).location = originalLocation; }); it('polls livereload endpoint and does not reload when epoch unchanged', async () => { - const reloadSpy = window.location.reload as unknown as jest.Mock; render(); expect(openSpy).toHaveBeenCalledWith('GET', '/.livereload/10/1'); // give microtask queue a tick await new Promise(res => setTimeout(res, 0)); - expect(reloadSpy).not.toHaveBeenCalled(); + expect(reloadPageSpy).not.toHaveBeenCalled(); }); it('reloads when server epoch increases', async () => { - const reloadSpy = window.location.reload as unknown as jest.Mock; - sendSpy.mockImplementation(function (this: any) { setTimeout(() => { (this as any).status = 200; @@ -106,6 +101,6 @@ describe('TechDocsLiveReload', () => { render(); await new Promise(res => setTimeout(res, 0)); - expect(reloadSpy).toHaveBeenCalled(); + expect(reloadPageSpy).toHaveBeenCalled(); }); }); diff --git a/packages/techdocs-cli-embedded-app/src/LiveReloadAddon.tsx b/packages/techdocs-cli-embedded-app/src/components/LiveReload/LiveReloadAddon.tsx similarity index 94% rename from packages/techdocs-cli-embedded-app/src/LiveReloadAddon.tsx rename to packages/techdocs-cli-embedded-app/src/components/LiveReload/LiveReloadAddon.tsx index 905a339c72..393704da97 100644 --- a/packages/techdocs-cli-embedded-app/src/LiveReloadAddon.tsx +++ b/packages/techdocs-cli-embedded-app/src/components/LiveReload/LiveReloadAddon.tsx @@ -17,6 +17,13 @@ import { useShadowRootElements } from '@backstage/plugin-techdocs-react'; import { useEffect, useRef } from 'react'; +/** @internal Exported for testing - allows spying on reloads without spying + * on window.location. + */ +export const utils = { + reloadPage: () => window.location.reload(), +}; + interface TechDocsLiveReloadProps { /** Whether to enable livereload (default: true in development) */ enabled?: boolean; @@ -69,7 +76,7 @@ export const TechDocsLiveReload = ({ reqRef.current = new XMLHttpRequest(); reqRef.current.onloadend = function handleLoadEnd(this: XMLHttpRequest) { if (parseFloat(this.responseText) > epoch) { - window.location.reload(); + utils.reloadPage(); } else { timeoutRef.current = setTimeout(poll, this.status === 200 ? 0 : 3000); } diff --git a/packages/techdocs-cli-embedded-app/src/components/Root/LogoFull.tsx b/packages/techdocs-cli-embedded-app/src/components/Sidebar/LogoFull.tsx similarity index 100% rename from packages/techdocs-cli-embedded-app/src/components/Root/LogoFull.tsx rename to packages/techdocs-cli-embedded-app/src/components/Sidebar/LogoFull.tsx diff --git a/packages/techdocs-cli-embedded-app/src/components/Root/LogoIcon.tsx b/packages/techdocs-cli-embedded-app/src/components/Sidebar/LogoIcon.tsx similarity index 100% rename from packages/techdocs-cli-embedded-app/src/components/Root/LogoIcon.tsx rename to packages/techdocs-cli-embedded-app/src/components/Sidebar/LogoIcon.tsx diff --git a/packages/techdocs-cli-embedded-app/src/components/Root/Root.tsx b/packages/techdocs-cli-embedded-app/src/components/Sidebar/Sidebar.tsx similarity index 89% rename from packages/techdocs-cli-embedded-app/src/components/Root/Root.tsx rename to packages/techdocs-cli-embedded-app/src/components/Sidebar/Sidebar.tsx index 6a00fc78cf..fed1f0f36d 100644 --- a/packages/techdocs-cli-embedded-app/src/components/Root/Root.tsx +++ b/packages/techdocs-cli-embedded-app/src/components/Sidebar/Sidebar.tsx @@ -13,9 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import { PropsWithChildren } from 'react'; - import { makeStyles } from '@material-ui/core/styles'; import LibraryBooks from '@material-ui/icons/LibraryBooks'; import LogoFull from './LogoFull'; @@ -30,6 +27,7 @@ import { useSidebarOpenState, Link, } from '@backstage/core-components'; +import { NavContentBlueprint } from '@backstage/plugin-app-react'; const useSidebarLogoStyles = makeStyles({ root: { @@ -63,7 +61,7 @@ const SidebarLogo = () => { ); }; -export const Root = ({ children }: PropsWithChildren<{}>) => ( +export const SidebarNav = (): React.ReactElement => ( @@ -76,6 +74,11 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( /> {/* End global nav */} - {children} ); + +export const SidebarContent = NavContentBlueprint.make({ + params: { + component: () => , + }, +}); diff --git a/packages/techdocs-cli-embedded-app/src/components/Root/index.ts b/packages/techdocs-cli-embedded-app/src/components/Sidebar/index.ts similarity index 93% rename from packages/techdocs-cli-embedded-app/src/components/Root/index.ts rename to packages/techdocs-cli-embedded-app/src/components/Sidebar/index.ts index dff706f08f..485e09a70c 100644 --- a/packages/techdocs-cli-embedded-app/src/components/Root/index.ts +++ b/packages/techdocs-cli-embedded-app/src/components/Sidebar/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { Root } from './Root'; +export { SidebarContent } from './Sidebar'; diff --git a/packages/techdocs-cli-embedded-app/src/components/TechDocsPage/TechDocsPage.tsx b/packages/techdocs-cli-embedded-app/src/components/TechDocsPage/TechDocsPage.tsx index dfe728da86..eae55d3424 100644 --- a/packages/techdocs-cli-embedded-app/src/components/TechDocsPage/TechDocsPage.tsx +++ b/packages/techdocs-cli-embedded-app/src/components/TechDocsPage/TechDocsPage.tsx @@ -25,7 +25,7 @@ import IconButton from '@material-ui/core/IconButton'; import LightIcon from '@material-ui/icons/Brightness7'; import DarkIcon from '@material-ui/icons/Brightness4'; -import { appThemeApiRef, useApi } from '@backstage/core-plugin-api'; +import { appThemeApiRef, useApi } from '@backstage/frontend-plugin-api'; import { TechDocsReaderPage, @@ -93,7 +93,7 @@ export const TechDocsThemeToggle = () => { ); }; -const DefaultTechDocsPage = () => { +export const DefaultTechDocsPage = () => { return ( diff --git a/packages/techdocs-cli-embedded-app/src/index.tsx b/packages/techdocs-cli-embedded-app/src/index.tsx index f50db079b0..84d77cc7e1 100644 --- a/packages/techdocs-cli-embedded-app/src/index.tsx +++ b/packages/techdocs-cli-embedded-app/src/index.tsx @@ -19,4 +19,4 @@ import ReactDOM from 'react-dom/client'; import '@backstage/ui/css/styles.css'; import App from './App'; -ReactDOM.createRoot(document.getElementById('root')!).render(); +ReactDOM.createRoot(document.getElementById('root')!).render(App); diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md index 13413f46c8..ce76e73bd8 100644 --- a/packages/techdocs-cli/CHANGELOG.md +++ b/packages/techdocs-cli/CHANGELOG.md @@ -1,5 +1,197 @@ # @techdocs/cli +## 1.10.7 + +### Patch Changes + +- 980b7f5: Updated proxy documentation to recommend Node.js built-in proxy support via `NODE_USE_ENV_PROXY` instead of `global-agent`. +- Updated dependencies + - @backstage/backend-defaults@0.17.0 + - @backstage/catalog-model@1.8.0 + - @backstage/cli-common@0.2.1 + - @backstage/plugin-techdocs-node@1.14.5 + - @backstage/config@1.3.7 + +## 1.10.7-next.1 + +### Patch Changes + +- 980b7f5: Updated proxy documentation to recommend Node.js built-in proxy support via `NODE_USE_ENV_PROXY` instead of `global-agent`. +- Updated dependencies + - @backstage/backend-defaults@0.16.1-next.2 + - @backstage/cli-common@0.2.1-next.1 + - @backstage/plugin-techdocs-node@1.14.5-next.2 + - @backstage/catalog-model@1.7.8-next.0 + - @backstage/config@1.3.7-next.0 + +## 1.10.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.2.1-next.0 + - @backstage/backend-defaults@0.16.1-next.0 + - @backstage/plugin-techdocs-node@1.14.5-next.0 + - @backstage/catalog-model@1.7.7 + - @backstage/config@1.3.6 + +## 1.10.6 + +### Patch Changes + +- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`. +- de62a9d: Upgraded `commander` dependency from `^12.0.0` to `^14.0.3` across all CLI packages. +- Updated dependencies + - @backstage/backend-defaults@0.16.0 + - @backstage/cli-common@0.2.0 + - @backstage/plugin-techdocs-node@1.14.4 + - @backstage/catalog-model@1.7.7 + +## 1.10.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.2.0-next.1 + - @backstage/plugin-techdocs-node@1.14.3-next.1 + - @backstage/backend-defaults@0.16.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + +## 1.10.6-next.0 + +### Patch Changes + +- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`. +- de62a9d: Upgraded `commander` dependency from `^12.0.0` to `^14.0.3` across all CLI packages. +- Updated dependencies + - @backstage/cli-common@0.2.0-next.0 + - @backstage/backend-defaults@0.15.3-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-techdocs-node@1.14.3-next.0 + +## 1.10.5 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 27798df: Migrate the Techdocs CLI embedded app to the New Frontend System (NFS) +- 508d127: Updated dependency `find-process` to `^2.0.0`. +- Updated dependencies + - @backstage/backend-defaults@0.15.2 + - @backstage/plugin-techdocs-node@1.14.2 + - @backstage/cli-common@0.1.18 + +## 1.10.5-next.1 + +### Patch Changes + +- 508d127: Updated dependency `find-process` to `^2.0.0`. +- Updated dependencies + - @backstage/backend-defaults@0.15.2-next.1 + - @backstage/plugin-techdocs-node@1.14.2-next.1 + +## 1.10.5-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-defaults@0.15.1-next.0 + - @backstage/plugin-techdocs-node@1.14.1-next.0 + - @backstage/cli-common@0.1.18-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + +## 1.10.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0 + - @backstage/cli-common@0.1.17 + - @backstage/plugin-techdocs-node@1.14.0 + +## 1.10.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + +## 1.10.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.1-next.1 + - @backstage/plugin-techdocs-node@1.13.11-next.0 + +## 1.10.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/cli-common@0.1.16 + - @backstage/config@1.3.6 + - @backstage/plugin-techdocs-node@1.13.10 + +## 1.10.3 + +### Patch Changes + +- 43629b1: Updated to use new utilities from `@backstage/cli-common`. +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/backend-defaults@0.14.0 + - @backstage/plugin-techdocs-node@1.13.10 + - @backstage/cli-common@0.1.16 + +## 1.10.3-next.2 + +### Patch Changes + +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/plugin-techdocs-node@1.13.10-next.1 + - @backstage/cli-common@0.1.16-next.2 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + +## 1.10.3-next.1 + +### Patch Changes + +- 43629b1: Updated to use new utilities from `@backstage/cli-common`. +- Updated dependencies + - @backstage/cli-common@0.1.16-next.1 + +## 1.10.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/cli-common@0.1.16-next.0 + - @backstage/plugin-techdocs-node@1.13.10-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + +## 1.10.2 + +### Patch Changes + +- c2a2017: Fix for missing styles due to move to BUI. +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/plugin-techdocs-node@1.13.9 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + ## 1.10.1-next.0 ### Patch Changes diff --git a/packages/techdocs-cli/README.md b/packages/techdocs-cli/README.md index 22f0093067..82dd0911cf 100644 --- a/packages/techdocs-cli/README.md +++ b/packages/techdocs-cli/README.md @@ -44,9 +44,10 @@ yarn techdocs-cli:dev [...options] ```sh # Prior to executing the techdocs-cli command -export GLOBAL_AGENT_HTTP_PROXY=${HTTP_PROXY} -export GLOBAL_AGENT_HTTPS_PROXY=${HTTPS_PROXY} -export GLOBAL_AGENT_NO_PROXY=${NO_PROXY} +export HTTP_PROXY=http://proxy.company.com:8080 +export HTTPS_PROXY=http://proxy.company.com:8080 +export NO_PROXY=localhost,internal.company.com +export NODE_USE_ENV_PROXY=1 ``` ### Using an example docs project diff --git a/packages/techdocs-cli/bin/techdocs-cli b/packages/techdocs-cli/bin/techdocs-cli index a43ae6324b..d6e0ef6df5 100755 --- a/packages/techdocs-cli/bin/techdocs-cli +++ b/packages/techdocs-cli/bin/techdocs-cli @@ -15,11 +15,13 @@ * limitations under the License. */ -const path = require('path'); +const path = require('node:path'); // Figure out whether we're running inside the backstage repo or as an installed dependency /* eslint-disable-next-line no-restricted-syntax */ -const isLocal = require('fs').existsSync(path.resolve(__dirname, '../src')); +const isLocal = require('node:fs').existsSync( + path.resolve(__dirname, '../src'), +); if (!isLocal) { require('..'); diff --git a/packages/techdocs-cli/cli-e2e-test.config.js b/packages/techdocs-cli/cli-e2e-test.config.js index b2e6e53918..6695dcbbd1 100644 --- a/packages/techdocs-cli/cli-e2e-test.config.js +++ b/packages/techdocs-cli/cli-e2e-test.config.js @@ -14,7 +14,7 @@ * limitations under the License. */ -const path = require('path'); +const path = require('node:path'); module.exports = require('@backstage/cli/config/jest').then(baseConfig => ({ ...baseConfig, diff --git a/packages/techdocs-cli/cli-e2e-tests/techdocs-cli.test.ts b/packages/techdocs-cli/cli-e2e-tests/techdocs-cli.test.ts index dbbc219ab6..a2720c934e 100644 --- a/packages/techdocs-cli/cli-e2e-tests/techdocs-cli.test.ts +++ b/packages/techdocs-cli/cli-e2e-tests/techdocs-cli.test.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { execSync, spawn, SpawnOptionsWithoutStdio } from 'child_process'; -import path from 'path'; +import { execSync, spawn, SpawnOptionsWithoutStdio } from 'node:child_process'; +import path from 'node:path'; import findProcess from 'find-process'; diff --git a/packages/techdocs-cli/cli-report.md b/packages/techdocs-cli/cli-report.md index 4cb7988add..667c4fb6b6 100644 --- a/packages/techdocs-cli/cli-report.md +++ b/packages/techdocs-cli/cli-report.md @@ -39,7 +39,7 @@ Options: --source-dir --techdocs-ref -h, --help - -v --verbose + -v, --verbose ``` ### `techdocs-cli migrate` @@ -62,7 +62,7 @@ Options: --removeOriginal --storage-name -h, --help - -v --verbose + -v, --verbose ``` ### `techdocs-cli publish` @@ -112,7 +112,7 @@ Options: -c, --mkdocs-config-file-name -h, --help -i, --docker-image - -v --verbose + -v, --verbose ``` ### `techdocs-cli serve:mkdocs` @@ -128,5 +128,5 @@ Options: -h, --help -i, --docker-image -p, --port - -v --verbose + -v, --verbose ``` diff --git a/packages/techdocs-cli/knip-report.md b/packages/techdocs-cli/knip-report.md index f13d2d7ab6..d1ef4d7897 100644 --- a/packages/techdocs-cli/knip-report.md +++ b/packages/techdocs-cli/knip-report.md @@ -1,15 +1,8 @@ # Knip report -## Unused dependencies (1) +## Unused devDependencies (1) -| Name | Location | Severity | -| :----------- | :----------- | :------- | -| global-agent | packages/techdocs-cli/package.json | error | - -## Unused devDependencies (2) - -| Name | Location | Severity | -| :------------------------ | :----------- | :------- | -| techdocs-cli-embedded-app | packages/techdocs-cli/package.json | error | -| @types/webpack-env | packages/techdocs-cli/package.json | error | +| Name | Location | Severity | +| :----------------- | :---------------- | :------- | +| @types/webpack-env | package.json:65:6 | error | diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index 5f978adada..1e2419fa60 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -1,6 +1,6 @@ { "name": "@techdocs/cli", - "version": "1.10.2-next.0", + "version": "1.10.7", "description": "Utility CLI for managing TechDocs sites in Backstage.", "backstage": { "role": "cli" @@ -49,9 +49,8 @@ "@backstage/cli-common": "workspace:^", "@backstage/config": "workspace:^", "@backstage/plugin-techdocs-node": "workspace:^", - "commander": "^12.0.0", + "commander": "^14.0.3", "fs-extra": "^11.0.0", - "global-agent": "^3.0.0", "http-proxy": "^1.18.1", "react-dev-utils": "^12.0.0-next.60", "serve-handler": "^6.1.3", @@ -59,13 +58,12 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@types/commander": "^2.12.2", "@types/fs-extra": "^11.0.0", "@types/http-proxy": "^1.17.4", - "@types/node": "^20.16.0", + "@types/node": "^22.13.14", "@types/serve-handler": "^6.1.0", "@types/webpack-env": "^1.15.3", - "find-process": "^1.4.5", + "find-process": "^2.0.0", "nodemon": "^3.0.1", "techdocs-cli-embedded-app": "link:../techdocs-cli-embedded-app" } diff --git a/packages/techdocs-cli/src/commands/generate/generate.ts b/packages/techdocs-cli/src/commands/generate/generate.ts index 4553c18810..9a009aff00 100644 --- a/packages/techdocs-cli/src/commands/generate/generate.ts +++ b/packages/techdocs-cli/src/commands/generate/generate.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { resolve } from 'path'; +import { resolve } from 'node:path'; import { OptionValues } from 'commander'; import fs from 'fs-extra'; import { diff --git a/packages/techdocs-cli/src/commands/index.ts b/packages/techdocs-cli/src/commands/index.ts index 253a2900fd..e69cf4671a 100644 --- a/packages/techdocs-cli/src/commands/index.ts +++ b/packages/techdocs-cli/src/commands/index.ts @@ -59,7 +59,7 @@ export function registerCommands(program: Command) { 'Name for site when using default MkDocs config', 'Documentation Site', ) - .option('-v --verbose', 'Enable verbose output.', false) + .option('-v, --verbose', 'Enable verbose output.', false) .option( '--omitTechdocsCoreMkdocsPlugin', "Don't patch MkDocs file automatically with techdocs-core plugin.", @@ -142,7 +142,7 @@ export function registerCommands(program: Command) { 'Optional Controls the number of API requests allowed to be performed simultaneously.', '25', ) - .option('-v --verbose', 'Enable verbose output.', false) + .option('-v, --verbose', 'Enable verbose output.', false) .action(lazy(() => import('./migrate/migrate'), 'default')); program @@ -253,7 +253,7 @@ export function registerCommands(program: Command) { 'Documentation Site', ) .option('-p, --port ', 'Port to serve documentation locally', '8000') - .option('-v --verbose', 'Enable verbose output.', false) + .option('-v, --verbose', 'Enable verbose output.', false) .action(lazy(() => import('./serve/mkdocs'), 'default')); program @@ -284,7 +284,7 @@ export function registerCommands(program: Command) { 'Documentation Site', ) .option('--mkdocs-port ', 'Port for MkDocs server to use', '8000') - .option('-v --verbose', 'Enable verbose output.', false) + .option('-v, --verbose', 'Enable verbose output.', false) .option( '--preview-app-bundle-path ', 'Preview documentation using another web app', diff --git a/packages/techdocs-cli/src/commands/publish/publish.ts b/packages/techdocs-cli/src/commands/publish/publish.ts index ded578546a..1da91adb3a 100644 --- a/packages/techdocs-cli/src/commands/publish/publish.ts +++ b/packages/techdocs-cli/src/commands/publish/publish.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { resolve } from 'path'; +import { resolve } from 'node:path'; import { OptionValues } from 'commander'; import { createLogger } from '../../lib/utility'; import { HostDiscovery } from '@backstage/backend-defaults/discovery'; diff --git a/packages/techdocs-cli/src/commands/serve/mkdocs.ts b/packages/techdocs-cli/src/commands/serve/mkdocs.ts index 00c5e5f0ed..cd1072a88f 100644 --- a/packages/techdocs-cli/src/commands/serve/mkdocs.ts +++ b/packages/techdocs-cli/src/commands/serve/mkdocs.ts @@ -18,7 +18,7 @@ import { OptionValues } from 'commander'; import openBrowser from 'react-dev-utils/openBrowser'; import { createLogger } from '../../lib/utility'; import { runMkdocsServer } from '../../lib/mkdocsServer'; -import { LogFunc, waitForSignal } from '../../lib/run'; +import { RunOnOutput } from '@backstage/cli-common'; import { getMkdocsYml } from '@backstage/plugin-techdocs-node'; import fs from 'fs-extra'; import { checkIfDockerIsOperational } from './utils'; @@ -45,7 +45,7 @@ export default async function serveMkdocs(opts: OptionValues) { // We want to open browser only once based on a log. let boolOpenBrowserTriggered = false; - const logFunc: LogFunc = data => { + const logFunc: RunOnOutput = data => { // Sometimes the lines contain an unnecessary extra new line in between const logLines = data.toString().split('\n'); const logPrefix = opts.docker ? '[docker/mkdocs]' : '[mkdocs]'; @@ -74,18 +74,18 @@ export default async function serveMkdocs(opts: OptionValues) { // Had me questioning this whole implementation for half an hour. // Commander stores --no-docker in cmd.docker variable - const childProcess = await runMkdocsServer({ + const childProcess = runMkdocsServer({ port: opts.port, dockerImage: opts.dockerImage, dockerEntrypoint: opts.dockerEntrypoint, dockerOptions: opts.dockerOption, useDocker: opts.docker, - stdoutLogFunc: logFunc, - stderrLogFunc: logFunc, + onStdout: logFunc, + onStderr: logFunc, }); // Keep waiting for user to cancel the process - await waitForSignal([childProcess]); + await childProcess.waitForExit(); if (configIsTemporary) { process.on('exit', async () => { diff --git a/packages/techdocs-cli/src/commands/serve/serve.ts b/packages/techdocs-cli/src/commands/serve/serve.ts index 27f432c22d..10b7e1498f 100644 --- a/packages/techdocs-cli/src/commands/serve/serve.ts +++ b/packages/techdocs-cli/src/commands/serve/serve.ts @@ -15,12 +15,11 @@ */ import { OptionValues } from 'commander'; -import path from 'path'; +import path from 'node:path'; import openBrowser from 'react-dev-utils/openBrowser'; -import { findPaths } from '@backstage/cli-common'; +import { findOwnPaths, RunOnOutput } from '@backstage/cli-common'; import HTTPServer from '../../lib/httpServer'; import { runMkdocsServer } from '../../lib/mkdocsServer'; -import { LogFunc, waitForSignal } from '../../lib/run'; import { createLogger } from '../../lib/utility'; import { getMkdocsYml } from '@backstage/plugin-techdocs-node'; import fs from 'fs-extra'; @@ -40,8 +39,8 @@ function findPreviewBundlePath(): string { // This can be tested by running `yarn pack` and extracting the resulting tarball into a directory. // Within the extracted directory, run `npm install --only=prod`. // Once that's done you can test the CLI in any directory using `node /package `. - // eslint-disable-next-line no-restricted-syntax - return findPaths(__dirname).resolveOwn('dist/embedded-app'); + /* eslint-disable-next-line no-restricted-syntax */ + return findOwnPaths(__dirname).resolve('dist/embedded-app'); } } @@ -83,7 +82,7 @@ export default async function serve(opts: OptionValues) { } let mkdocsServerHasStarted = false; - const mkdocsLogFunc: LogFunc = data => { + const mkdocsLogFunc: RunOnOutput = data => { // Sometimes the lines contain an unnecessary extra new line const logLines = data.toString().split('\n'); const logPrefix = opts.docker ? '[docker/mkdocs]' : '[mkdocs]'; @@ -107,14 +106,14 @@ export default async function serve(opts: OptionValues) { // https://github.com/mkdocs/mkdocs/issues/879#issuecomment-203536006 // Had me questioning this whole implementation for half an hour. logger.info('Starting mkdocs server.'); - const mkdocsChildProcess = await runMkdocsServer({ + const mkdocsChildProcess = runMkdocsServer({ port: opts.mkdocsPort, dockerImage: opts.dockerImage, dockerEntrypoint: opts.dockerEntrypoint, dockerOptions: opts.dockerOption, useDocker: opts.docker, - stdoutLogFunc: mkdocsLogFunc, - stderrLogFunc: mkdocsLogFunc, + onStdout: mkdocsLogFunc, + onStderr: mkdocsLogFunc, mkdocsConfigFileName: mkdocsYmlPath, mkdocsParameterClean: opts.mkdocsParameterClean, mkdocsParameterDirtyReload: opts.mkdocsParameterDirtyreload, @@ -161,7 +160,7 @@ export default async function serve(opts: OptionValues) { ); }); - await waitForSignal([mkdocsChildProcess]); + await mkdocsChildProcess.waitForExit(); if (configIsTemporary) { process.on('exit', async () => { diff --git a/packages/techdocs-cli/src/commands/serve/utils.ts b/packages/techdocs-cli/src/commands/serve/utils.ts index 2b19b0fbc0..9ecb8bc29b 100644 --- a/packages/techdocs-cli/src/commands/serve/utils.ts +++ b/packages/techdocs-cli/src/commands/serve/utils.ts @@ -14,25 +14,22 @@ * limitations under the License. */ -import { promisify } from 'util'; import * as winston from 'winston'; -import { execFile } from 'child_process'; +import { runCheck } from '@backstage/cli-common'; export async function checkIfDockerIsOperational( logger: winston.Logger, ): Promise { logger.info('Checking Docker status...'); - try { - const runCheck = promisify(execFile); - await runCheck('docker', ['info'], { shell: true }); + const isOperational = await runCheck(['docker', 'info']); + if (isOperational) { logger.info( 'Docker is up and running. Proceed to starting up mkdocs server', ); return true; - } catch { - logger.error( - 'Docker is not running. Exiting. Please check status of Docker daemon with `docker info` before re-running', - ); - return false; } + logger.error( + 'Docker is not running. Exiting. Please check status of Docker daemon with `docker info` before re-running', + ); + return false; } diff --git a/packages/techdocs-cli/src/lib/httpServer.ts b/packages/techdocs-cli/src/lib/httpServer.ts index d7e8f62ea0..8ce38330b5 100644 --- a/packages/techdocs-cli/src/lib/httpServer.ts +++ b/packages/techdocs-cli/src/lib/httpServer.ts @@ -15,7 +15,7 @@ */ import serveHandler from 'serve-handler'; -import http from 'http'; +import http from 'node:http'; import httpProxy from 'http-proxy'; import { createLogger } from './utility'; import { diff --git a/packages/techdocs-cli/src/lib/livereload.test.ts b/packages/techdocs-cli/src/lib/livereload.test.ts index b21251c45a..aaf4ef7dfd 100644 --- a/packages/techdocs-cli/src/lib/livereload.test.ts +++ b/packages/techdocs-cli/src/lib/livereload.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import http from 'http'; +import http from 'node:http'; import { injectLivereloadParameters, proxyHtmlWithLivereloadInjection, diff --git a/packages/techdocs-cli/src/lib/livereload.ts b/packages/techdocs-cli/src/lib/livereload.ts index c108971b82..90814d413a 100644 --- a/packages/techdocs-cli/src/lib/livereload.ts +++ b/packages/techdocs-cli/src/lib/livereload.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import http from 'http'; +import http from 'node:http'; import httpProxy from 'http-proxy'; /** diff --git a/packages/techdocs-cli/src/lib/mkdocsServer.test.ts b/packages/techdocs-cli/src/lib/mkdocsServer.test.ts index 83f9c9f382..5acd1784a3 100644 --- a/packages/techdocs-cli/src/lib/mkdocsServer.test.ts +++ b/packages/techdocs-cli/src/lib/mkdocsServer.test.ts @@ -15,9 +15,9 @@ */ import { runMkdocsServer } from './mkdocsServer'; -import { run } from './run'; +import { run } from '@backstage/cli-common'; -jest.mock('./run', () => { +jest.mock('@backstage/cli-common', () => { return { run: jest.fn(), }; @@ -29,12 +29,12 @@ describe('runMkdocsServer', () => { }); describe('docker', () => { - it('should run docker directly by default', async () => { - await runMkdocsServer({}); + it('should run docker directly by default', () => { + runMkdocsServer({}); expect(run).toHaveBeenCalledWith( - 'docker', expect.arrayContaining([ + 'docker', 'run', `${process.cwd()}:/content`, '8000:8000', @@ -47,26 +47,24 @@ describe('runMkdocsServer', () => { ); }); - it('should accept port option', async () => { - await runMkdocsServer({ port: '5678' }); + it('should accept port option', () => { + runMkdocsServer({ port: '5678' }); expect(run).toHaveBeenCalledWith( - 'docker', - expect.arrayContaining(['5678:5678', '0.0.0.0:5678']), + expect.arrayContaining(['docker', '5678:5678', '0.0.0.0:5678']), expect.objectContaining({}), ); }); - it('should accept custom docker image', async () => { - await runMkdocsServer({ dockerImage: 'my-org/techdocs' }); + it('should accept custom docker image', () => { + runMkdocsServer({ dockerImage: 'my-org/techdocs' }); expect(run).toHaveBeenCalledWith( - 'docker', - expect.arrayContaining(['my-org/techdocs']), + expect.arrayContaining(['docker', 'my-org/techdocs']), expect.objectContaining({}), ); }); - it('should accept custom docker options', async () => { - await runMkdocsServer({ + it('should accept custom docker options', () => { + runMkdocsServer({ dockerOptions: [ '--add-host=internal.host:192.168.11.12', '--name', @@ -75,8 +73,8 @@ describe('runMkdocsServer', () => { }); expect(run).toHaveBeenCalledWith( - 'docker', expect.arrayContaining([ + 'docker', 'run', '--rm', '-w', @@ -98,14 +96,14 @@ describe('runMkdocsServer', () => { ); }); - it('should accept additinoal mkdocs CLI parameters', async () => { - await runMkdocsServer({ + it('should accept additinoal mkdocs CLI parameters', () => { + runMkdocsServer({ mkdocsParameterClean: true, mkdocsParameterStrict: true, }); expect(run).toHaveBeenCalledWith( - 'docker', expect.arrayContaining([ + 'docker', 'serve', '--dev-addr', '0.0.0.0:8000', @@ -118,21 +116,24 @@ describe('runMkdocsServer', () => { }); describe('mkdocs', () => { - it('should run mkdocs if specified', async () => { - await runMkdocsServer({ useDocker: false }); + it('should run mkdocs if specified', () => { + runMkdocsServer({ useDocker: false }); expect(run).toHaveBeenCalledWith( - 'mkdocs', - expect.arrayContaining(['serve', '--dev-addr', '127.0.0.1:8000']), + expect.arrayContaining([ + 'mkdocs', + 'serve', + '--dev-addr', + '127.0.0.1:8000', + ]), expect.objectContaining({}), ); }); - it('should accept port option', async () => { - await runMkdocsServer({ useDocker: false, port: '5678' }); + it('should accept port option', () => { + runMkdocsServer({ useDocker: false, port: '5678' }); expect(run).toHaveBeenCalledWith( - 'mkdocs', - expect.arrayContaining(['127.0.0.1:5678']), + expect.arrayContaining(['mkdocs', '127.0.0.1:5678']), expect.objectContaining({}), ); }); diff --git a/packages/techdocs-cli/src/lib/mkdocsServer.ts b/packages/techdocs-cli/src/lib/mkdocsServer.ts index a793643367..c557015256 100644 --- a/packages/techdocs-cli/src/lib/mkdocsServer.ts +++ b/packages/techdocs-cli/src/lib/mkdocsServer.ts @@ -14,30 +14,29 @@ * limitations under the License. */ -import { ChildProcess } from 'child_process'; -import { run, LogFunc } from './run'; +import { run, RunChildProcess, RunOnOutput } from '@backstage/cli-common'; -export const runMkdocsServer = async (options: { +export const runMkdocsServer = (options: { port?: string; useDocker?: boolean; dockerImage?: string; dockerEntrypoint?: string; dockerOptions?: string[]; - stdoutLogFunc?: LogFunc; - stderrLogFunc?: LogFunc; + onStdout?: RunOnOutput; + onStderr?: RunOnOutput; mkdocsConfigFileName?: string; mkdocsParameterClean?: boolean; mkdocsParameterDirtyReload?: boolean; mkdocsParameterStrict?: boolean; -}): Promise => { +}): RunChildProcess => { const port = options.port ?? '8000'; const useDocker = options.useDocker ?? true; const dockerImage = options.dockerImage ?? 'spotify/techdocs'; if (useDocker) { - return await run( - 'docker', + return run( [ + 'docker', 'run', '--rm', '-w', @@ -63,15 +62,15 @@ export const runMkdocsServer = async (options: { ...(options.mkdocsParameterStrict ? ['--strict'] : []), ], { - stdoutLogFunc: options.stdoutLogFunc, - stderrLogFunc: options.stderrLogFunc, + onStdout: options.onStdout, + onStderr: options.onStderr, }, ); } - return await run( - 'mkdocs', + return run( [ + 'mkdocs', 'serve', '--dev-addr', `127.0.0.1:${port}`, @@ -83,8 +82,8 @@ export const runMkdocsServer = async (options: { ...(options.mkdocsParameterStrict ? ['--strict'] : []), ], { - stdoutLogFunc: options.stdoutLogFunc, - stderrLogFunc: options.stderrLogFunc, + onStdout: options.onStdout, + onStderr: options.onStderr, }, ); }; diff --git a/packages/techdocs-cli/src/lib/run.ts b/packages/techdocs-cli/src/lib/run.ts deleted file mode 100644 index 13ac7d9d4c..0000000000 --- a/packages/techdocs-cli/src/lib/run.ts +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { spawn, SpawnOptions, ChildProcess } from 'child_process'; - -export type LogFunc = (data: Buffer | string) => void; -type SpawnOptionsPartialEnv = Omit & { - env?: Partial; - // Pipe stdout to this log function - stdoutLogFunc?: LogFunc; - // Pipe stderr to this log function - stderrLogFunc?: LogFunc; -}; - -// TODO: Accept log functions to pipe logs with. -// Runs a child command, returning the child process instance. -// Use it along with waitForSignal to run a long running process e.g. mkdocs serve -export const run = async ( - name: string, - args: string[] = [], - options: SpawnOptionsPartialEnv = {}, -): Promise => { - const { stdoutLogFunc, stderrLogFunc } = options; - - const env: NodeJS.ProcessEnv = { - ...process.env, - FORCE_COLOR: 'true', - ...(options.env ?? {}), - }; - - // Refer: https://nodejs.org/api/child_process.html#child_process_subprocess_stdio - const stdio = [ - 'inherit', - stdoutLogFunc ? 'pipe' : 'inherit', - stderrLogFunc ? 'pipe' : 'inherit', - ] as ('inherit' | 'pipe')[]; - - const childProcess = spawn(name, args, { - stdio: stdio, - ...options, - env, - }); - - if (stdoutLogFunc && childProcess.stdout) { - childProcess.stdout.on('data', stdoutLogFunc); - } - if (stderrLogFunc && childProcess.stderr) { - childProcess.stderr.on('data', stderrLogFunc); - } - - return childProcess; -}; - -// Block indefinitely and wait for a signal to stop the child process(es) -// Throw error if any child process errors -// Resolves only when all processes exit with status code 0 -export async function waitForSignal( - childProcesses: Array, -): Promise { - const promises: Array> = []; - - for (const signal of ['SIGINT', 'SIGTERM'] as const) { - process.on(signal, () => { - childProcesses.forEach(childProcess => { - childProcess.kill(); - }); - }); - } - - childProcesses.forEach(childProcess => { - if (typeof childProcess.exitCode === 'number') { - if (childProcess.exitCode) { - throw new Error(`Non zero exit code from child process`); - } - return; - } - - promises.push( - new Promise((resolve, reject) => { - childProcess.once('error', reject); - childProcess.once('exit', resolve); - }), - ); - }); - - await Promise.all(promises); -} diff --git a/packages/techdocs-cli/src/lib/utility.ts b/packages/techdocs-cli/src/lib/utility.ts index 00927fd1df..173815df08 100644 --- a/packages/techdocs-cli/src/lib/utility.ts +++ b/packages/techdocs-cli/src/lib/utility.ts @@ -18,8 +18,8 @@ import { ParsedLocationAnnotation, } from '@backstage/plugin-techdocs-node'; import * as winston from 'winston'; -import { Writable } from 'stream'; -import { stdout } from 'process'; +import { Writable } from 'node:stream'; +import { stdout } from 'node:process'; export const convertTechDocsRefToLocationAnnotation = ( techdocsRef: string, diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index 7beeed27c1..75975d2492 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,154 @@ # @backstage/test-utils +## 1.7.17 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.7.3 + - @backstage/core-app-api@1.20.0 + - @backstage/plugin-permission-react@0.5.0 + - @backstage/config@1.3.7 + - @backstage/core-plugin-api@1.12.5 + - @backstage/plugin-permission-common@0.9.8 + +## 1.7.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.7.3-next.0 + - @backstage/core-app-api@1.20.0-next.2 + - @backstage/config@1.3.7-next.0 + - @backstage/core-plugin-api@1.12.5-next.2 + - @backstage/plugin-permission-common@0.9.8-next.0 + - @backstage/plugin-permission-react@0.4.42-next.1 + +## 1.7.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/core-plugin-api@1.12.5-next.1 + +## 1.7.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.7-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + - @backstage/config@1.3.6 + - @backstage/theme@0.7.2 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/plugin-permission-react@0.4.42-next.0 + +## 1.7.16 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-app-api@1.19.6 + - @backstage/plugin-permission-react@0.4.41 + - @backstage/plugin-permission-common@0.9.7 + +## 1.7.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/theme@0.7.2 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## 1.7.15 + +### Patch Changes + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- 68eb322: Added `@types/jest` as an optional peer dependency, since jest types are exposed in the public API surface. +- Updated dependencies + - @backstage/core-app-api@1.19.5 + - @backstage/theme@0.7.2 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-permission-react@0.4.40 + - @backstage/plugin-permission-common@0.9.6 + +## 1.7.15-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- 68eb322: Added `@types/jest` as an optional peer dependency, since jest types are exposed in the public API surface. +- Updated dependencies + - @backstage/core-app-api@1.19.5-next.1 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-permission-react@0.4.40-next.1 + - @backstage/theme@0.7.2-next.1 + +## 1.7.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.7.2-next.0 + +## 1.7.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/core-app-api@1.19.4-next.0 + - @backstage/config@1.3.6 + - @backstage/theme@0.7.1 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-react@0.4.40-next.0 + +## 1.7.14 + +### Patch Changes + +- 97cd16f: Internal update of translation imports. +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/plugin-permission-react@0.4.39 + +## 1.7.14-next.0 + +### Patch Changes + +- 97cd16f: Internal update of translation imports. +- Updated dependencies + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + +## 1.7.13 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/core-app-api@1.19.2 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/config@1.3.6 + ## 1.7.13-next.0 ### Patch Changes diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index b40fa8a0d8..1143309661 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/test-utils", - "version": "1.7.13-next.0", + "version": "1.7.17", "description": "Utilities to test Backstage plugins and apps.", "backstage": { "role": "web-library" @@ -70,16 +70,20 @@ "msw": "^1.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@testing-library/react": "^16.0.0", + "@types/jest": "*", "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { + "@types/jest": { + "optional": true + }, "@types/react": { "optional": true } diff --git a/packages/test-utils/src/testUtils/apis/FetchApi/MockFetchApi.ts b/packages/test-utils/src/testUtils/apis/FetchApi/MockFetchApi.ts index 3d02db0124..661d202379 100644 --- a/packages/test-utils/src/testUtils/apis/FetchApi/MockFetchApi.ts +++ b/packages/test-utils/src/testUtils/apis/FetchApi/MockFetchApi.ts @@ -127,7 +127,9 @@ function baseImplementation( ): typeof crossFetch { const implementation = options?.baseImplementation; if (!implementation) { - return crossFetch; + // Return a wrapper that evaluates global.fetch at call time, not construction time. + // This allows MSW to patch global.fetch after MockFetchApi is constructed. + return (input, init) => global.fetch(input, init); } else if (implementation === 'none') { return () => Promise.resolve(new Response()); } diff --git a/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.ts b/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.ts index 344679355a..2989207743 100644 --- a/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.ts +++ b/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.ts @@ -25,7 +25,7 @@ import ObservableImpl from 'zen-observable'; import { Observable } from '@backstage/types'; // Internal import to avoid code duplication, this will lead to duplication in build output // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { toInternalTranslationRef } from '../../../../../core-plugin-api/src/translation/TranslationRef'; +import { toInternalTranslationRef } from '../../../../../frontend-plugin-api/src/translation/TranslationRef'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { JsxInterpolator } from '../../../../../core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi'; diff --git a/packages/test-utils/src/testUtils/appWrappers.test.tsx b/packages/test-utils/src/testUtils/appWrappers.test.tsx index 42a3f8e092..158499374e 100644 --- a/packages/test-utils/src/testUtils/appWrappers.test.tsx +++ b/packages/test-utils/src/testUtils/appWrappers.test.tsx @@ -92,17 +92,15 @@ describe('wrapInTestApp', () => { }); expect(error).toEqual([ - expect.objectContaining({ - detail: new Error( - 'MockErrorApi received unexpected error, Error: NOPE', - ), - }), - expect.objectContaining({ - detail: new Error( - 'MockErrorApi received unexpected error, Error: NOPE', - ), - }), - expect.stringMatching(/^The above error occurred in the component:/), + expect.stringContaining( + 'Error: MockErrorApi received unexpected error, Error: NOPE', + ), + expect.objectContaining({ type: 'unhandled-exception' }), + expect.stringContaining( + 'Error: MockErrorApi received unexpected error, Error: NOPE', + ), + expect.objectContaining({ type: 'unhandled-exception' }), + expect.stringContaining('The above error occurred in the component:'), ]); }); diff --git a/packages/theme/CHANGELOG.md b/packages/theme/CHANGELOG.md index 1b5b158102..e4152cd04a 100644 --- a/packages/theme/CHANGELOG.md +++ b/packages/theme/CHANGELOG.md @@ -1,5 +1,52 @@ # @backstage/theme +## 0.7.3 + +### Patch Changes + +- a0100d4: Fixes occasional duplication of v5 class name prefix for MUI 5 components. + + Documentation added to explain how to resolve missing v5 prefix in class names when using MUI 5 components in main app. + +## 0.7.3-next.0 + +### Patch Changes + +- a0100d4: Fixes occasional duplication of v5 class name prefix for MUI 5 components. + + Documentation added to explain how to resolve missing v5 prefix in class names when using MUI 5 components in main app. + +## 0.7.2 + +### Patch Changes + +- 1c52dcc: add square shape +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. + +## 0.7.2-next.1 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. + +## 0.7.2-next.0 + +### Patch Changes + +- 1c52dcc: add square shape + +## 0.7.1 + +### Patch Changes + +- fa06f6b: Added a `themeName` prop to `UnifiedThemeProvider`, enabling Backstage UI `data-theme-name` CSS attribute to be set based on active theme. + +## 0.7.1-next.0 + +### Patch Changes + +- fa06f6b: Added a `themeName` prop to `UnifiedThemeProvider`, enabling Backstage UI `data-theme-name` CSS attribute to be set based on active theme. + ## 0.7.0 ### Minor Changes diff --git a/packages/theme/knip-report.md b/packages/theme/knip-report.md index 270d625e91..18b0ddfcf0 100644 --- a/packages/theme/knip-report.md +++ b/packages/theme/knip-report.md @@ -2,7 +2,7 @@ ## Unused devDependencies (1) -| Name | Location | Severity | -| :--------------- | :----------- | :------- | -| react-router-dom | packages/theme/package.json | error | +| Name | Location | Severity | +| :--------------- | :---------------- | :------- | +| react-router-dom | package.json:51:6 | error | diff --git a/packages/theme/package.json b/packages/theme/package.json index cb7d5c12ce..3d5db36f6a 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/theme", - "version": "0.7.0", + "version": "0.7.3", "description": "material-ui theme for use with Backstage.", "backstage": { "role": "web-library" @@ -48,14 +48,14 @@ "@types/react": "^18.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@material-ui/core": "^4.12.2", "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/packages/theme/report.api.md b/packages/theme/report.api.md index 18ac96ecec..064f6f3e6c 100644 --- a/packages/theme/report.api.md +++ b/packages/theme/report.api.md @@ -185,7 +185,7 @@ export function createBaseThemeOptions( palette: PaletteOptions; typography: BackstageTypography; page: PageTheme; - getPageTheme: ({ themeId }: PageThemeSelector) => PageTheme; + getPageTheme: (input: PageThemeSelector) => PageTheme; }; // @public @deprecated @@ -460,5 +460,6 @@ export interface UnifiedThemeProviderProps { children: ReactNode; // (undocumented) theme: UnifiedTheme; + themeName?: string; } ``` diff --git a/packages/theme/src/base/pageTheme.ts b/packages/theme/src/base/pageTheme.ts index 3e1e8641fc..8bbd270f5f 100644 --- a/packages/theme/src/base/pageTheme.ts +++ b/packages/theme/src/base/pageTheme.ts @@ -36,6 +36,7 @@ export const shapes: Record = { wave: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='1368' height='400' fill='none'%3e%3cmask id='a' width='1368' height='401' x='0' y='0' maskUnits='userSpaceOnUse'%3e%3cpath fill='url(%23paint0_linear)' d='M437 116C223 116 112 0 112 0h1256v400c-82 0-225-21-282-109-112-175-436-175-649-175z'/%3e%3cpath fill='url(%23paint1_linear)' d='M1368 400V282C891-29 788 40 711 161 608 324 121 372 0 361v39h1368z'/%3e%3cpath fill='url(%23paint2_linear)' d='M1368 244v156H0V94c92-24 198-46 375 0l135 41c176 51 195 109 858 109z'/%3e%3cpath fill='url(%23paint3_linear)' d='M1252 400h116c-14-7-35-14-116-16-663-14-837-128-1013-258l-85-61C98 28 46 8 0 0v400h1252z'/%3e%3c/mask%3e%3cg mask='url(%23a)'%3e%3cpath fill='white' d='M-172-98h1671v601H-172z'/%3e%3c/g%3e%3cdefs%3e%3clinearGradient id='paint0_linear' x1='602' x2='1093.5' y1='-960.5' y2='272' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint1_linear' x1='482' x2='480' y1='1058.5' y2='70.5' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint2_linear' x1='424' x2='446.1' y1='-587.5' y2='274.6' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint3_linear' x1='587' x2='349' y1='-1120.5' y2='341' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3c/defs%3e%3c/svg%3e")`, wave2: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='1368' height='400' fill='none'%3e%3cmask id='a' width='1764' height='479' x='-229' y='-6' maskUnits='userSpaceOnUse'%3e%3cpath fill='url(%23paint0_linear)' d='M0 400h1350C1321 336 525 33 179-2c-345-34-395 236-408 402H0z'/%3e%3cpath fill='url(%23paint1_linear)' d='M1378 177v223H0V217s219 75 327 52C436 246 717-35 965 45s254 144 413 132z'/%3e%3cpath fill='url(%23paint2_linear)' d='M26 400l-78-16c-170 205-44-6-137-30l-4-1 4 1 137 30c37-45 89-110 159-201 399-514-45 238 1176-50 275-65 354-39 91 267H26z'/%3e%3c/mask%3e%3cg mask='url(%23a)'%3e%3cpath fill='white' d='M0 0h1368v400H0z'/%3e%3c/g%3e%3cdefs%3e%3clinearGradient id='paint0_linear' x1='431' x2='397.3' y1='-599' y2='372.8' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint1_linear' x1='236.5' x2='446.6' y1='-586' y2='381.5' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint2_linear' x1='851.8' x2='640.4' y1='-867.2' y2='363.7' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3c/defs%3e%3c/svg%3e")`, round: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='1368' height='400' fill='none'%3e%3cmask id='a' width='2269' height='1408' x='-610' y='-509' maskUnits='userSpaceOnUse'%3e%3ccircle cx='1212.8' cy='74.8' r='317.5' fill='url(%23paint0_linear)' transform='rotate(-52 1213 75)'/%3e%3ccircle cx='737.8' cy='445.8' r='317.5' fill='url(%23paint1_linear)' transform='rotate(-116 738 446)'/%3e%3ccircle cx='601.8' cy='52.8' r='418.6' fill='url(%23paint2_linear)' transform='rotate(-117 602 53)'/%3e%3ccircle cx='999.8' cy='364' r='389.1' fill='url(%23paint3_linear)' transform='rotate(31 1000 364)'/%3e%3cellipse cx='-109.2' cy='263.5' fill='url(%23paint4_linear)' rx='429.2' ry='465.8' transform='rotate(-85 -109 264)'/%3e%3c/mask%3e%3cg mask='url(%23a)'%3e%3cpath fill='white' d='M0 0h1368v400H0z'/%3e%3c/g%3e%3cdefs%3e%3clinearGradient id='paint0_linear' x1='1301.2' x2='161.4' y1='-1879.7' y2='-969.6' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint1_linear' x1='826.2' x2='-313.6' y1='-1508.7' y2='-598.6' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint2_linear' x1='718.4' x2='-784.3' y1='-2524' y2='-1324.2' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint3_linear' x1='1108.2' x2='-288.6' y1='-2031.1' y2='-915.9' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint4_linear' x1='10.4' x2='-1626.5' y1='-2603.8' y2='-1399.5' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3c/defs%3e%3c/svg%3e")`, + square: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='1368' height='400' fill='none' viewBox='0 0 1368 400'%3e%3cmask id='e' width='1744' height='984' x='-179' y='-215' maskUnits='userSpaceOnUse' style='mask-type:alpha'%3e%3cpath fill='url(%23a)' d='M707 334.693h801v322H707z' transform='rotate(-28.703 707 334.693)'/%3e%3cpath fill='url(%23b)' d='M781.853 570.341h583.106v370.945H781.853z' transform='rotate(-117.346 781.853 570.341)'/%3e%3cpath fill='url(%23c)' d='M-131 685.309h583.106v370.945H-131z' transform='rotate(-77.064 -131 685.309)'/%3e%3cpath fill='url(%23d)' d='M-53.026-214.551h1151.06v322H-53.026z' transform='rotate(22.904 -53.026 -214.551)'/%3e%3c/mask%3e%3cg mask='url(%23e)'%3e%3cpath fill='white' d='M0 0h1368v400H0z'/%3e%3c/g%3e%3cdefs%3e%3clinearGradient id='a' x1='1219.07' x2='742.968' y1='-495.388' y2='450.243' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='b' x1='1154.63' x2='489.125' y1='-385.914' y2='449.365' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='c' x1='241.773' x2='-423.728' y1='-270.947' y2='564.332' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='d' x1='682.837' x2='312.949' y1='-1044.63' y2='11.113' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3c/defs%3e%3c/svg%3e")`, }; /** diff --git a/packages/theme/src/unified/UnifiedThemeProvider.test.tsx b/packages/theme/src/unified/UnifiedThemeProvider.test.tsx index 85a47dbb1e..52769b65a3 100644 --- a/packages/theme/src/unified/UnifiedThemeProvider.test.tsx +++ b/packages/theme/src/unified/UnifiedThemeProvider.test.tsx @@ -20,11 +20,17 @@ import { } from '@material-ui/core/styles'; import { useTheme as useV5Theme } from '@mui/material/styles'; import { makeStyles as makeV5Styles } from '@mui/styles'; -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import { UnifiedThemeProvider } from './UnifiedThemeProvider'; import { themes } from './themes'; describe('UnifiedThemeProvider', () => { + afterEach(() => { + document.body.removeAttribute('data-theme-name'); + document.body.removeAttribute('data-theme-mode'); + document.body.removeAttribute('data-unified-theme-stack'); + }); + it('provides a themes for v4 and v5 directly', () => { function MyV4Component() { const theme = useV4Theme(); @@ -81,4 +87,17 @@ describe('UnifiedThemeProvider', () => { 'rgb(158, 158, 158)', ); }); + + it('applies theme attributes based on the provided options', async () => { + render( + + theme + , + ); + + await waitFor(() => + expect(document.body.getAttribute('data-theme-name')).toBe('aperture'), + ); + expect(document.body.getAttribute('data-theme-mode')).toBe('light'); + }); }); diff --git a/packages/theme/src/unified/UnifiedThemeProvider.tsx b/packages/theme/src/unified/UnifiedThemeProvider.tsx index d0886cd146..f10320393d 100644 --- a/packages/theme/src/unified/UnifiedThemeProvider.tsx +++ b/packages/theme/src/unified/UnifiedThemeProvider.tsx @@ -37,6 +37,8 @@ import { unstable_ClassNameGenerator as ClassNameGenerator } from '@mui/material export interface UnifiedThemeProviderProps { children: ReactNode; theme: UnifiedTheme; + /** Optional override for the value written to the `data-theme-name` attribute. */ + themeName?: string; } /** @@ -45,6 +47,9 @@ export interface UnifiedThemeProviderProps { * This call needs to be in the same module as the `UnifiedThemeProvider` to ensure that it doesn't get removed by tree shaking */ ClassNameGenerator.configure(componentName => { + if ((componentName ?? '').startsWith('v5-')) { + return componentName; + } return `v5-${componentName}`; }); @@ -66,15 +71,14 @@ import { useApplyThemeAttributes } from './useApplyThemeAttributes'; export function UnifiedThemeProvider( props: UnifiedThemeProviderProps, ): JSX.Element { - const { children, theme } = props; + const { children, theme, themeName } = props; const v4Theme = theme.getTheme('v4') as Mui4Theme; const v5Theme = theme.getTheme('v5') as Mui5Theme; - useApplyThemeAttributes( - v4Theme ? v4Theme.palette.type : v5Theme?.palette.mode, - 'backstage', - ); + const themeMode = v4Theme ? v4Theme.palette.type : v5Theme?.palette.mode; + + useApplyThemeAttributes(themeMode, themeName ?? 'backstage'); let result = children as JSX.Element; diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md index 7347058a0c..4d15d47a4f 100644 --- a/packages/ui/CHANGELOG.md +++ b/packages/ui/CHANGELOG.md @@ -1,12 +1,1640 @@ # @backstage/ui -## 0.9.0-next.3 +## 0.14.0 ### Minor Changes +- 8659f33: **BREAKING**: The `Header` component's `tabs` prop now uses `HeaderNavTabItem[]` instead of `HeaderTab[]`. Tabs render as a `
    + - ... + - ... + -
    + - + +const columns: ColumnConfig[] = [ + + { id: 'name', label: 'Name', isRowHeader: true, cell: item => }, + + { id: 'type', label: 'Type', cell: item => }, + +]; + + + + + ``` + + **Affected components:** Table, TableRoot, TablePagination + +- 95246eb: **BREAKING**: Updating color tokens to match the new neutral style on different surfaces. + + **Migration:** + + There's no direct replacement for the old tint tokens but you can use the new neutral set of color tokens on surface 0 or 1 as a replacement. + + - `--bui-bg-tint` can be replaced by `--bui-bg-neutral-on-surface-0` + - `--bui-bg-tint-hover` can be replaced by `--bui-bg-neutral-on-surface-0-hover` + - `--bui-bg-tint-pressed` can be replaced by `--bui-bg-neutral-on-surface-0-pressed` + - `--bui-bg-tint-disabled` can be replaced by `--bui-bg-neutral-on-surface-0-disabled` + +- ea0c6d8: **BREAKING**: Introduce new `ToggleButton` & `ToggleButtonGroup` components in Backstage UI + + **Affected components:** ToggleButton, ToggleButtonGroup + +- 4ea1d15: **BREAKING**: Renamed CSS variable `--bui-bg` to `--bui-bg-surface-0` for consistency. + +### Patch Changes + +- 1880402: Fixes app background color on dark mode. + + **Affected components:** Box + +- d2fdded: Added indeterminate state support to the Checkbox component for handling partial selection scenarios like table header checkboxes. + + **Affected components:** Checkbox + +- 4fb15d2: Added missing `aria-label` attributes to `SearchField` components in `Select`, `MenuAutocomplete`, and `MenuAutocompleteListbox` to fix accessibility warnings. + + **Affected components:** Select, MenuAutocomplete, MenuAutocompleteListbox + +- 21c87cc: Fixes disabled state in primary and secondary buttons in Backstage UI. + + **Affected components:** Button + +- 9c76682: build(deps-dev): bump `storybook` from 10.1.9 to 10.1.10 +- de80336: Fixed disabled tertiary buttons incorrectly showing hover effects on surfaces. + + **Affected components:** Button + +- 133d5c6: Added new Popover component for Backstage UI with automatic overflow handling, and full placement support. Also introduced `--bui-shadow` token for consistent elevation styling across overlay components (Popover, Tooltip, Menu). + + **Affected components:** Popover + +- 973c839: Fixed Table sorting indicator not being visible when a column is actively sorted. + + **Affected components:** Table, Column + +- df40cfc: Fixed Menu component trigger button not toggling correctly. Removed custom click-outside handler that was interfering with React Aria's built-in state management, allowing the menu to properly open and close when clicking the trigger button. + + **Affected components:** Menu + +- b01ab96: Added support for column width configuration in Table component. Columns now accept `width`, `defaultWidth`, `minWidth`, and `maxWidth` props for responsive layout control. + + **Affected components:** Table, Column + +- b4a4911: Fixed SearchField `startCollapsed` prop not working correctly in Backstage UI. The field now properly starts in a collapsed state, expands when clicked and focused, and collapses back when unfocused with no input. Also fixed CSS logic to work correctly in all layout contexts (flex row, flex column, and regular containers). + + **Affected components:** SearchField + +- b3253b6: Fixed `Link` component causing hard page refreshes for internal routes. The component now properly uses React Router's navigation instead of full page reloads. +- fe7fe69: Added support for custom pagination options in `useTable` hook and `Table` component. You can now configure `pageSizeOptions` to customize the page size dropdown, and hook into pagination events via `onPageSizeChange`, `onNextPage`, and `onPreviousPage` callbacks. When `pageSize` doesn't match any option, the first option is used and a warning is logged. + + **Affected components:** Table, TablePagination + +- cfac8a4: Fixed missing border styles on table selection cells in multi-select mode. + + **Affected components:** Table + +- 2532d2a: Added `className` and `style` props to the `Table` component. + + **Affected components:** Table + +## 0.11.0-next.1 + +### Minor Changes + +- 243e5e7: **BREAKING**: Redesigned Table component with new `useTable` hook API. + + - The `Table` component (React Aria wrapper) is renamed to `TableRoot` + - New high-level `Table` component that handles data display, pagination, sorting, and selection + - The `useTable` hook is completely redesigned with a new API supporting three pagination modes (complete, offset, cursor) + - New types: `ColumnConfig`, `TableProps`, `TableItem`, `UseTableOptions`, `UseTableResult` + + New features include unified pagination modes, debounced query changes, stale data preservation during reloads, and row selection with toggle/replace behaviors. + + **Migration:** + + 1. Update imports and use the new `useTable` hook: + + ```diff + -import { Table, useTable } from '@backstage/ui'; + -const { data, paginationProps } = useTable({ data: items, pagination: {...} }); + +import { Table, useTable, type ColumnConfig } from '@backstage/ui'; + +const { tableProps } = useTable({ + + mode: 'complete', + + getData: () => items, + +}); + ``` + + 2. Define columns and render with the new Table API: + + ```diff + -
    + - ... + - ... + -
    + - + +const columns: ColumnConfig[] = [ + + { id: 'name', label: 'Name', isRowHeader: true, cell: item => }, + + { id: 'type', label: 'Type', cell: item => }, + +]; + + + + + ``` + + **Affected components:** Table, TableRoot, TablePagination + +- 95246eb: **BREAKING**: Updating color tokens to match the new neutral style on different surfaces. + + **Migration:** + + There's no direct replacement for the old tint tokens but you can use the new neutral set of color tokens on surface 0 or 1 as a replacement. + + - `--bui-bg-tint` can be replaced by `--bui-bg-neutral-on-surface-0` + - `--bui-bg-tint-hover` can be replaced by `--bui-bg-neutral-on-surface-0-hover` + - `--bui-bg-tint-pressed` can be replaced by `--bui-bg-neutral-on-surface-0-pressed` + - `--bui-bg-tint-disabled` can be replaced by `--bui-bg-neutral-on-surface-0-disabled` + +- ea0c6d8: Introduce new `ToggleButton` & `ToggleButtonGroup` components in Backstage UI + +### Patch Changes + +- 21c87cc: Fixes disabled state in primary and secondary buttons in Backstage UI. +- b3253b6: Fixed `Link` component causing hard page refreshes for internal routes. The component now properly uses React Router's navigation instead of full page reloads. + +## 0.11.0-next.0 + +### Minor Changes + +- 4ea1d15: **BREAKING**: Renamed CSS variable `--bui-bg` to `--bui-bg-surface-0` for consistency. + +### Patch Changes + +- 1880402: Fixes app background color on dark mode. +- 9c76682: build(deps-dev): bump `storybook` from 10.1.9 to 10.1.10 +- b4a4911: Fixed SearchField `startCollapsed` prop not working correctly in Backstage UI. The field now properly starts in a collapsed state, expands when clicked and focused, and collapses back when unfocused with no input. Also fixed CSS logic to work correctly in all layout contexts (flex row, flex column, and regular containers). + + **Affected components:** SearchField + +## 0.10.0 + +### Minor Changes + +- 16543fa: **BREAKING**: The `Cell` component has been refactored to be a generic wrapper component that accepts `children` for custom cell content. The text-specific functionality (previously part of `Cell`) has been moved to a new `CellText` component. + + **Migration:** + + If you were using `Cell` with text-specific props (`title`, `description`, `leadingIcon`, `href`), you need to update your code to use `CellText` instead: + + **Before:** + + ```tsx + } + href="/path" + /> + ``` + + **After:** + + ```tsx + } + href="/path" + /> + ``` + + For custom cell content, use the new generic `Cell` component: + + ```tsx + {/* Your custom content */} + ``` + +### Patch Changes + +- 50b7927: Fixed Checkbox indicator showing checkmark color when unchecked. + + **Affected components:** Checkbox + +- 5bacf55: Fixed `ButtonIcon` incorrectly applying `className` to inner elements instead of only the root element. + + **Affected components:** ButtonIcon + +- b3ad928: Fixed Table Row component to correctly handle cases where no `href` is provided, preventing unnecessary router provider wrapping and fixing the cursor incorrectly showing as a pointer despite the element not being a link. + + **Affected components:** Row + +- a20d317: Added row selection support with visual state styling for hover, selected, and pressed states. Fixed checkbox rendering to only show for multi-select toggle mode. + + **Affected components:** Table, TableHeader, Row, Column + +- fe7c751: Fixed `useTable` hook to prioritize `providedRowCount` over data length for accurate row count in server-side pagination scenarios. +- c145031: Fixed Table column sorting indicator to show up arrow when no sort is active, correctly indicating that clicking will sort ascending. + + **Affected components:** Column + +## 0.10.0-next.1 + +### Minor Changes + +- 16543fa: **BREAKING**: The `Cell` component has been refactored to be a generic wrapper component that accepts `children` for custom cell content. The text-specific functionality (previously part of `Cell`) has been moved to a new `CellText` component. + + **Migration:** + + If you were using `Cell` with text-specific props (`title`, `description`, `leadingIcon`, `href`), you need to update your code to use `CellText` instead: + + **Before:** + + ```tsx + } + href="/path" + /> + ``` + + **After:** + + ```tsx + } + href="/path" + /> + ``` + + For custom cell content, use the new generic `Cell` component: + + ```tsx + {/* Your custom content */} + ``` + +### Patch Changes + +- 50b7927: Fixed Checkbox indicator showing checkmark color when unchecked. + + **Affected components:** Checkbox + +- 5bacf55: Fixed `ButtonIcon` incorrectly applying `className` to inner elements instead of only the root element. + + **Affected components:** ButtonIcon + +- a20d317: Added row selection support with visual state styling for hover, selected, and pressed states. Fixed checkbox rendering to only show for multi-select toggle mode. + + **Affected components:** Table, TableHeader, Row, Column + +## 0.9.1-next.0 + +### Patch Changes + +- b3ad928: Fixed Table Row component to correctly handle cases where no `href` is provided, preventing unnecessary router provider wrapping and fixing the cursor incorrectly showing as a pointer despite the element not being a link. + + **Affected components:** Row + +- fe7c751: Fixed `useTable` hook to prioritize `providedRowCount` over data length for accurate row count in server-side pagination scenarios. +- c145031: Fixed Table column sorting indicator to show up arrow when no sort is active, correctly indicating that clicking will sort ascending. + + **Affected components:** Column + +## 0.9.0 + +### Minor Changes + +- 539cf26: **BREAKING**: Migrated Avatar component from Base UI to custom implementation with size changes: + + - Base UI-specific props are no longer supported + - Size values have been updated: + - New `x-small` size added (1.25rem / 20px) + - `small` size unchanged (1.5rem / 24px) + - `medium` size unchanged (2rem / 32px, default) + - `large` size **changed from 3rem to 2.5rem** (40px) + - New `x-large` size added (3rem / 48px) + + **Migration:** + + ```diff + # Remove Base UI-specific props + - + + + + # Update large size usage to x-large for same visual size + - + + + ``` + + Added `purpose` prop for accessibility control (`'informative'` or `'decoration'`). + + **Affected components:** Avatar + +- 5c614ff: **BREAKING**: Migrated Checkbox component from Base UI to React Aria Components. + + API changes required: + + - `checked` → `isSelected` + - `defaultChecked` → `defaultSelected` + - `disabled` → `isDisabled` + - `required` → `isRequired` + - `label` prop removed - use `children` instead + - CSS: `bui-CheckboxLabel` class removed + - Data attribute: `data-checked` → `data-selected` + - Use without label is no longer supported + + **Migration:** + + Before: + + ```tsx + + ``` + + After: + + ```tsx + + Accept terms + + ``` + + Before: + + ```tsx + + ``` + + After: + + ```tsx + Option + ``` + + Before: + + ```tsx + + ``` + + After: + + ```tsx + + Accessible label + + ``` + +- 134151f: **BREAKING**: Fixing styles on SearchField in Backstage UI after migration to CSS modules. `SearchField` has now its own set of class names. We previously used class names from `TextField` but this approach was creating some confusion so going forward in your theme you'll be able to theme `TextField` and `SearchField` separately. +- a67670d: **BREAKING**: Removed central `componentDefinitions` object and related type utilities (`ComponentDefinitionName`, `ComponentClassNames`). + + Component definitions are primarily intended for documenting the CSS class API for theming purposes, not for programmatic use in JavaScript/TypeScript. + + **Migration:** + + If you were using component definitions or class names to build custom components, we recommend migrating to either: + + - Use Backstage UI components directly as building blocks, or + - Duplicate the component CSS in your own stylesheets instead of relying on internal class names + +- b78fc45: **BREAKING**: Changed className prop behavior to augment default styles instead of being ignored or overriding them. + + If you were passing custom className values to any of these components that relied on the previous behavior, you may need to adjust your styles to account for the default classes now being applied alongside your custom classes. + + **Affected components:** Menu, MenuListBox, MenuAutocomplete, MenuAutocompleteListbox, MenuItem, MenuListBoxItem, MenuSection, MenuSeparator, Switch, Skeleton, FieldLabel, Header, HeaderToolbar, HeaderPage, Tabs, TabList, Tab, TabPanel + - 83c100e: **BREAKING**: Removed `Collapsible` component. Migrate to `Accordion` or use React Aria `Disclosure`. - ## Migration Path 1: Accordion (Opinionated Styled Component) + **Migration:** + + **Path 1: Accordion (Opinionated Styled Component)** Accordion provides preset styling with a similar component structure. @@ -27,7 +1655,7 @@ CSS classes: `.bui-CollapsibleRoot` → `.bui-Accordion`, `.bui-CollapsibleTrigger` → `.bui-AccordionTrigger` (now on heading element), `.bui-CollapsiblePanel` → `.bui-AccordionPanel` - ## Migration Path 2: React Aria Disclosure (Full Customization) + **Path 2: React Aria Disclosure (Full Customization)** For custom styling without preset styles: @@ -44,7 +1672,123 @@ Added searchable and multiple selection support to Select component. The component now accepts `searchable`, `selectionMode`, and `searchPlaceholder` props to enable filtering and multi-selection modes. - Migration: If you're using `SelectProps` type directly, update from `SelectProps` to `SelectProps<'single' | 'multiple'>`. Component usage remains backward compatible. + **Migration:** + + If you're using `SelectProps` type directly, update from `SelectProps` to `SelectProps<'single' | 'multiple'>`. Component usage remains backward compatible. + + **Affected components:** Select + +### Patch Changes + +- d01de00: Fix broken external links in Backstage UI Header component. + + **Affected components:** Header + +- 35a3614: Fixed CSS issues in Select component including popover width constraints, focus outline behavior, and overflow handling. + + **Affected components:** Select + +- 01476f0: Improved visual consistency of PasswordField, SearchField, and MenuAutocomplete components. + + **Affected components:** PasswordField, SearchField, MenuAutocomplete + +- 26c6a78: Fix default text color in Backstage UI + + **Affected components:** Text + +- deaa427: Fixed Text component to prevent `truncate` prop from being spread to the underlying DOM element. + + **Affected components:** Text + +- 1059f95: Improved the Link component structure in Backstage UI. + + **Affected components:** Link + +- 836b0c7: Fixed dialog backdrop appearance in dark mode. + + **Affected components:** Dialog + +- 6874094: Migrated CellProfile component from Base UI Avatar to Backstage UI Avatar component. + + **Affected components:** Avatar + +- 719d772: Avatar components in x-small and small sizes now display only one initial instead of two, improving readability at smaller dimensions. + + **Affected components:** Avatar + +- 6d35a6b: Removed `@base-ui-components/react` dependency as all components now use React Aria Components. +- dac851f: Fix the default font size in Backstage UI. +- 3c0ea67: Fix CSS layer ordering in Backstage UI to make sure component styles are loaded after tokens and base declarations. +- 3b18d80: Fixed RadioGroup radio button ellipse distortion by preventing flex shrink and grow. + + **Affected components:** RadioGroup + +- 4eb455c: Fix font smoothing as default in Backstage UI. +- ff9f0c3: Enable tree-shaking of imports other than `*.css`. +- 7839e7b: Added `loading` prop to Button and ButtonIcon components for displaying spinner during async operations. + + **Affected components:** Button, ButtonIcon + +- a00fb88: Fixed Table Row component to properly support opening links in new tabs via right-click or Cmd+Click when using the `href` prop. + + **Affected components:** Table + +- e16ece5: Set the color-scheme property depending on theme +- 1ef3ca4: Added new VisuallyHidden component for hiding content visually while keeping it accessible to screen readers. + + **Affected components:** VisuallyHidden + +- 00bfb83: Fix default font weight and font family in Backstage UI. + +## 0.9.0-next.3 + +### Minor Changes + +- 83c100e: **BREAKING**: Removed `Collapsible` component. Migrate to `Accordion` or use React Aria `Disclosure`. + + **Migration:** + + **Path 1: Accordion (Opinionated Styled Component)** + + Accordion provides preset styling with a similar component structure. + + ```diff + - import { Collapsible } from '@backstage/ui'; + + import { Accordion, AccordionTrigger, AccordionPanel } from '@backstage/ui'; + + - + - } /> + - Content + - + + + + + + + Content + + + ``` + + CSS classes: `.bui-CollapsibleRoot` → `.bui-Accordion`, `.bui-CollapsibleTrigger` → `.bui-AccordionTrigger` (now on heading element), `.bui-CollapsiblePanel` → `.bui-AccordionPanel` + + **Path 2: React Aria Disclosure (Full Customization)** + + For custom styling without preset styles: + + ```tsx + import { Disclosure, Button, DisclosurePanel } from 'react-aria-components'; + + + + Content + ; + ``` + +- 816af0f: **BREAKING**: The `SelectProps` interface now accepts a generic type parameter for selection mode. + + Added searchable and multiple selection support to Select component. The component now accepts `searchable`, `selectionMode`, and `searchPlaceholder` props to enable filtering and multi-selection modes. + + **Migration:** + + If you're using `SelectProps` type directly, update from `SelectProps` to `SelectProps<'single' | 'multiple'>`. Component usage remains backward compatible. ### Patch Changes @@ -69,7 +1813,7 @@ - `large` size **changed from 3rem to 2.5rem** (40px) - New `x-large` size added (3rem / 48px) - Migration: + **Migration:** ```diff # Remove Base UI-specific props @@ -83,7 +1827,7 @@ Added `purpose` prop for accessibility control (`'informative'` or `'decoration'`). -- 134151f: Fixing styles on SearchField in Backstage UI after migration to CSS modules. `SearchField` has now its own set of class names. We previously used class names from `TextField` but this approach was creating some confusion so going forward in your theme you'll be able to theme `TextField` and `SearchField` separately. +- 134151f: **BREAKING**: Fixing styles on SearchField in Backstage UI after migration to CSS modules. `SearchField` has now its own set of class names. We previously used class names from `TextField` but this approach was creating some confusion so going forward in your theme you'll be able to theme `TextField` and `SearchField` separately. ### Patch Changes @@ -112,7 +1856,7 @@ - Data attribute: `data-checked` → `data-selected` - Use without label is no longer supported - Migration examples: + **Migration:** Before: @@ -156,18 +1900,10 @@ - b78fc45: **BREAKING**: Changed className prop behavior to augment default styles instead of being ignored or overriding them. - Affected components: - - - Menu, MenuListBox, MenuAutocomplete, MenuAutocompleteListbox, MenuItem, MenuListBoxItem, MenuSection, MenuSeparator - - Switch - - Skeleton - - FieldLabel - - Header, HeaderToolbar - - HeaderPage - - Tabs, TabList, Tab, TabPanel - If you were passing custom className values to any of these components that relied on the previous behavior, you may need to adjust your styles to account for the default classes now being applied alongside your custom classes. + **Affected components:** Menu, MenuListBox, MenuAutocomplete, MenuAutocompleteListbox, MenuItem, MenuListBoxItem, MenuSection, MenuSeparator, Switch, Skeleton, FieldLabel, Header, HeaderToolbar, HeaderPage, Tabs, TabList, Tab, TabPanel + ### Patch Changes - ff9f0c3: Enable tree-shaking of imports other than `*.css`. @@ -181,7 +1917,7 @@ - dac851f: Fix the default font size in Backstage UI. - 3c0ea67: Fix CSS layer ordering in Backstage UI to make sure component styles are loaded after tokens and base declarations. - 4eb455c: Fix font smoothing as default in Backstage UI. -- 00bfb83: Fix default font wight and font family in Backstage UI. +- 00bfb83: Fix default font weight and font family in Backstage UI. ## 0.8.0 @@ -254,10 +1990,10 @@ ### Minor Changes -- 0615e54: We are moving our DataTable component to React Aria. We removed our DataTable to only use Table as a single and opinionated option for tables. This new structure is made possible by using React Aria under the hood. -- b245c9d: Backstage UI - HeaderPage - We are updating the breadcrumb to be more visible and accessible. -- 800f593: **Breaking change** We are updating the Menu component to use React Aria under the hood. The structure and all props are changing to follow React Aria's guidance. -- b0e47f3: **Breaking** We are upgrading our `Text` component to support all font sizes making the `Heading` component redundant. The new `Text` component introduces 4 sizes for title and 4 sizes for body text. All of these work in multiple colors and font weights. We improved the `as` prop to include all possible values. The `Link` component has also been updated to match the new `Text` component. +- 0615e54: **BREAKING**: We are moving our DataTable component to React Aria. We removed our DataTable to only use Table as a single and opinionated option for tables. This new structure is made possible by using React Aria under the hood. +- b245c9d: **BREAKING**: Backstage UI - HeaderPage - We are updating the breadcrumb to be more visible and accessible. +- 800f593: **BREAKING**: We are updating the Menu component to use React Aria under the hood. The structure and all props are changing to follow React Aria's guidance. +- b0e47f3: **BREAKING**: We are upgrading our `Text` component to support all font sizes making the `Heading` component redundant. The new `Text` component introduces 4 sizes for title and 4 sizes for body text. All of these work in multiple colors and font weights. We improved the `as` prop to include all possible values. The `Link` component has also been updated to match the new `Text` component. ### Patch Changes @@ -266,7 +2002,7 @@ - f761306: Add new TagGroup component to Backstage UI. - 75fead9: Fixes a couple of small bugs in BUI including setting H1 and H2 correctly on the Header and HeaderPage. - e7ff178: Update styling of Tooltip element -- 230b410: **Breaking change** Move breadcrumb to fit in the `HeaderPage` instead of the `Header` in Backstage UI. +- 230b410: **BREAKING**: Move breadcrumb to fit in the `HeaderPage` instead of the `Header` in Backstage UI. - 2f9a084: We are motion away from `motion` to use `gsap` instead to make Backstage UI backward compatible with React 17. - d4e603e: Updated Menu component in Backstage UI to use useId() from React Aria instead of React to support React 17. - 8bdc491: Remove stylesheet import from Select component. @@ -277,11 +2013,11 @@ ### Minor Changes -- 0615e54: We are moving our DataTable component to React Aria. We removed our DataTable to only use Table as a single and opinionated option for tables. This new structure is made possible by using React Aria under the hood. +- 0615e54: **BREAKING**: We are moving our DataTable component to React Aria. We removed our DataTable to only use Table as a single and opinionated option for tables. This new structure is made possible by using React Aria under the hood. ### Patch Changes -- 230b410: **Breaking change** Move breadcrumb to fit in the `HeaderPage` instead of the `Header` in Backstage UI. +- 230b410: **BREAKING**: Move breadcrumb to fit in the `HeaderPage` instead of the `Header` in Backstage UI. - 8bdc491: Remove stylesheet import from Select component. - 404b426: Add `startCollapsed` prop on the `SearchField` component in BUI. @@ -303,7 +2039,7 @@ ### Minor Changes -- b0e47f3: **Breaking** We are upgrading our `Text` component to support all font sizes making the `Heading` component redundant. The new `Text` component introduces 4 sizes for title and 4 sizes for body text. All of these work in multiple colors and font weights. We improved the `as` prop to include all possible values. The `Link` component has also been updated to match the new `Text` component. +- b0e47f3: **BREAKING**: We are upgrading our `Text` component to support all font sizes making the `Heading` component redundant. The new `Text` component introduces 4 sizes for title and 4 sizes for body text. All of these work in multiple colors and font weights. We improved the `as` prop to include all possible values. The `Link` component has also been updated to match the new `Text` component. ### Patch Changes @@ -314,13 +2050,13 @@ ### Minor Changes -- e92bb9b: Canon has been renamed to Backstage UI. This means that `@backstage/canon` has been deprecated and replaced by `@backstage/ui`. +- e92bb9b: **BREAKING**: Canon has been renamed to Backstage UI. This means that `@backstage/canon` has been deprecated and replaced by `@backstage/ui`. ## 0.6.0-next.1 ### Minor Changes -- 2e30459: We are moving our Tooltip component to use React Aria under the hood. In doing so, the structure of the component and its prop are changing to follow the new underlying structure. +- 2e30459: **BREAKING**: We are moving our Tooltip component to use React Aria under the hood. In doing so, the structure of the component and its prop are changing to follow the new underlying structure. ### Patch Changes @@ -335,7 +2071,7 @@ ### Minor Changes -- 4c6d891: **BREAKING CHANGES** +- 4c6d891: **BREAKING**: We’re updating our Button component to provide better support for button links. @@ -358,12 +2094,12 @@ ### Minor Changes -- 621fac9: We are updating the default size of the Button component in Canon to be small instead of medium. -- a842554: We set the default size for IconButton in Canon to be small instead of medium. -- 35fd51d: Move TextField component to use react Aria under the hood. Introducing a new FieldLabel component to help build custom fields. -- 78204a2: **Breaking** We are adding a new as prop on the Heading and Text component to make it easier to change the component tag. We are removing the render prop in favour of the as prop. -- c49e335: TextField in Canon now has multiple label sizes as well as the capacity to hide label and description but still make them available for screen readers. -- 24b45ef: Fixes spacing props on layout components and aligned on naming for the Grid component. You should now call the Grid root component using instead of just . +- 621fac9: **BREAKING**: We are updating the default size of the Button component in Canon to be small instead of medium. +- a842554: **BREAKING**: We set the default size for IconButton in Canon to be small instead of medium. +- 35fd51d: **BREAKING**: Move TextField component to use react Aria under the hood. Introducing a new FieldLabel component to help build custom fields. +- 78204a2: **BREAKING**: We are adding a new as prop on the Heading and Text component to make it easier to change the component tag. We are removing the render prop in favour of the as prop. +- c49e335: **BREAKING**: TextField in Canon now has multiple label sizes as well as the capacity to hide label and description but still make them available for screen readers. +- 24b45ef: **BREAKING**: Fixes spacing props on layout components and aligned on naming for the Grid component. You should now call the Grid root component using `` instead of just ``. ### Patch Changes @@ -393,7 +2129,7 @@ ### Minor Changes -- 24b45ef: Fixes spacing props on layout components and aligned on naming for the Grid component. You should now call the Grid root component using instead of just . +- 24b45ef: Fixes spacing props on layout components and aligned on naming for the Grid component. You should now call the Grid root component using `` instead of just ``. ### Patch Changes @@ -403,9 +2139,9 @@ ### Minor Changes -- ea36f74: **Breaking Change** Icons on Button and IconButton now need to be imported and placed like this:
    - - Name - Owner - Type - Lifecycle - - - {data1.map(item => ( - - } - description={item.description} - /> - - - - - ))} - -
    - ); - }, -}; - -export const WithPaginationUncontrolled: Story = { - render: () => { - const { data, paginationProps } = useTable({ data: data1 }); - - return ( - <> - - - Name - Owner - Type - Lifecycle - - - {data?.map(item => ( - - } - description={item.description} - /> - - - - - ))} - -
    - - - ); - }, -}; - -export const WithPaginationControlled: Story = { - render: () => { - const [offset, setOffset] = useState(0); - const [pageSize, setPageSize] = useState(5); - - const { data, paginationProps } = useTable({ - data: data4, - pagination: { - offset, - pageSize, - onOffsetChange: setOffset, - onPageSizeChange: setPageSize, - onNextPage: () => console.log('Next page analytics'), - onPreviousPage: () => console.log('Previous page analytics'), - }, - }); - - return ( - <> - - - Band name - Genre - Year formed - Albums - - - {data?.map(item => ( - - - - - - - ))} - -
    - -
    - Current state: offset={offset}, pageSize={pageSize} -
    - - ); - }, -}; - -export const Sorting: Story = { - render: () => { - return ( - - - - Name - - Owner - Type - Lifecycle - - - {data1.map(item => ( - - } - description={item.description} - /> - - - - - ))} - -
    - ); - }, -}; - -export const TableRockBand: Story = { - render: () => { - const { data, paginationProps } = useTable({ - data: data4, - pagination: { - defaultPageSize: 5, - }, - }); - - return ( - <> - - - Band name - Genre - Year formed - Albums - - - {data?.map(item => ( - - - - - - - ))} - -
    - - - ); - }, -}; - -export const RowClick: Story = { - render: () => { - const { data, paginationProps } = useTable({ - data: data4, - pagination: { - defaultPageSize: 5, - }, - }); - - return ( - <> - - - Band name - Genre - Year formed - Albums - - - {data?.map(item => ( - alert('Row clicked')}> - - - - - - ))} - -
    - - - ); - }, -}; - -export const RowLink: Story = { - render: () => { - const { data, paginationProps } = useTable({ - data: data4, - pagination: { - defaultPageSize: 5, - }, - }); - - return ( - <> - - - Band name - Genre - Year formed - Albums - - - {data?.map(item => ( - - - - - - - ))} - -
    - - - ); - }, -}; - -export const CellText: Story = { - render: () => { - return ( - - - Name - - - {data2.map(item => ( - - - - ))} - -
    - ); - }, -}; - -export const CellProfile: Story = { - render: () => { - return ( - - - Name - - - {data3.map(item => ( - - - - ))} - -
    - ); - }, -}; diff --git a/packages/ui/src/components/Table/components/Cell.tsx b/packages/ui/src/components/Table/components/Cell.tsx index 65be8482a7..844cfb3111 100644 --- a/packages/ui/src/components/Table/components/Cell.tsx +++ b/packages/ui/src/components/Table/components/Cell.tsx @@ -14,66 +14,21 @@ * limitations under the License. */ -import clsx from 'clsx'; -import { Text } from '../../Text'; -import { Link } from '../../Link'; import { Cell as ReactAriaCell } from 'react-aria-components'; import type { CellProps } from '../types'; -import { useStyles } from '../../../hooks/useStyles'; -import { TableDefinition } from '../definition'; -import styles from '../Table.module.css'; +import { useDefinition } from '../../../hooks/useDefinition'; +import { CellDefinition } from '../definition'; -/** @public */ +/** + * A low-level table cell primitive for building custom cell content. + * For standard use cases, prefer `CellText` or `CellProfile`. + * + * @public + */ const Cell = (props: CellProps) => { - const { classNames, cleanedProps } = useStyles(TableDefinition, { - color: 'primary' as const, - ...props, - }); - const { className, title, description, color, leadingIcon, href, ...rest } = - cleanedProps; + const { ownProps, restProps } = useDefinition(CellDefinition, props); - return ( - -
    - {leadingIcon && ( -
    - {leadingIcon} -
    - )} -
    - {href ? ( - - {title} - - ) : ( - - {title} - - )} - {description && ( - - {description} - - )} -
    -
    -
    - ); + return ; }; Cell.displayName = 'Cell'; diff --git a/packages/ui/src/components/Table/components/CellProfile.tsx b/packages/ui/src/components/Table/components/CellProfile.tsx index 4abef56e01..4154aeaf45 100644 --- a/packages/ui/src/components/Table/components/CellProfile.tsx +++ b/packages/ui/src/components/Table/components/CellProfile.tsx @@ -14,45 +14,30 @@ * limitations under the License. */ -import clsx from 'clsx'; import { CellProfileProps } from '../types'; import { Text } from '../../Text/Text'; import { Link } from '../../Link/Link'; import { Avatar } from '../../Avatar'; -import { useStyles } from '../../../hooks/useStyles'; -import { TableDefinition } from '../definition'; +import { useDefinition } from '../../../hooks/useDefinition'; +import { CellProfileDefinition } from '../definition'; import { Cell as ReactAriaCell } from 'react-aria-components'; -import styles from '../Table.module.css'; -/** @public */ +/** + * A table cell that renders an avatar image alongside a name, with an optional description and link. + * + * @public + */ export const CellProfile = (props: CellProfileProps) => { - const { classNames, cleanedProps } = useStyles(TableDefinition, { - color: 'primary' as const, - ...props, - }); - const { className, src, name, href, description, color, ...rest } = - cleanedProps; + const { ownProps, restProps } = useDefinition(CellProfileDefinition, props); + const { classes, src, name, href, description, color } = ownProps; return ( - -
    + +
    {src && name && ( )} -
    +
    {name && href ? ( {name} ) : ( diff --git a/packages/ui/src/components/Table/components/CellText.tsx b/packages/ui/src/components/Table/components/CellText.tsx new file mode 100644 index 0000000000..64809a7419 --- /dev/null +++ b/packages/ui/src/components/Table/components/CellText.tsx @@ -0,0 +1,77 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Text } from '../../Text'; +import { Link } from '../../Link'; +import { Cell as ReactAriaCell } from 'react-aria-components'; +import type { CellTextProps } from '../types'; +import { useDefinition } from '../../../hooks/useDefinition'; +import { CellTextDefinition } from '../definition'; + +/** + * A table cell that renders a primary text title with an optional secondary description, leading icon, and link. + * + * @public + */ +const CellText = (props: CellTextProps) => { + const { ownProps, restProps } = useDefinition(CellTextDefinition, props); + const { classes, title, description, color, leadingIcon, href } = ownProps; + + return ( + +
    + {leadingIcon &&
    {leadingIcon}
    } +
    + {href ? ( + + {title} + + ) : ( + + {title} + + )} + {description && ( + + {description} + + )} +
    +
    +
    + ); +}; + +CellText.displayName = 'CellText'; + +export { CellText }; diff --git a/packages/ui/src/components/Table/components/Column.tsx b/packages/ui/src/components/Table/components/Column.tsx index f534da565a..77fd997c0f 100644 --- a/packages/ui/src/components/Table/components/Column.tsx +++ b/packages/ui/src/components/Table/components/Column.tsx @@ -15,44 +15,28 @@ */ import { Column as ReactAriaColumn } from 'react-aria-components'; -import { useStyles } from '../../../hooks/useStyles'; -import { TableDefinition } from '../definition'; -import styles from '../Table.module.css'; -import clsx from 'clsx'; +import { useDefinition } from '../../../hooks/useDefinition'; +import { ColumnDefinition } from '../definition'; import { ColumnProps } from '../types'; -import { RiArrowUpLine, RiArrowDownLine } from '@remixicon/react'; +import { RiArrowUpLine } from '@remixicon/react'; -/** @public */ +/** + * A table column header cell with an optional sort toggle and support for resizable widths. + * + * @public + */ export const Column = (props: ColumnProps) => { - const { classNames, cleanedProps } = useStyles(TableDefinition, props); - const { children, ...rest } = cleanedProps; + const { ownProps, restProps } = useDefinition(ColumnDefinition, props); + const { classes, children } = ownProps; return ( - - {({ allowsSorting, sortDirection }) => ( -
    - {children} + + {({ allowsSorting }) => ( +
    + {children} {allowsSorting && ( -
    diff --git a/packages/ui/src/components/Table/components/Row.tsx b/packages/ui/src/components/Table/components/Row.tsx index 106f97baff..e293dc7b4e 100644 --- a/packages/ui/src/components/Table/components/Row.tsx +++ b/packages/ui/src/components/Table/components/Row.tsx @@ -16,65 +16,87 @@ import { Row as ReactAriaRow, - RowProps, useTableOptions, - Cell, + Cell as ReactAriaCell, Collection, - Checkbox, - RouterProvider, } from 'react-aria-components'; -import { useStyles } from '../../../hooks/useStyles'; -import { TableDefinition } from '../definition'; -import { useNavigate } from 'react-router-dom'; -import { useHref } from 'react-router-dom'; -import { isExternalLink } from '../../../utils/isExternalLink'; -import styles from '../Table.module.css'; +import { Checkbox } from '../../Checkbox'; +import { useDefinition } from '../../../hooks/useDefinition'; +import { RowDefinition } from '../definition'; +import type { RowProps } from '../types'; +import { isExternalLink } from '../../../utils/linkUtils'; import clsx from 'clsx'; +import { Flex } from '../../Flex'; -/** @public */ +/** + * A table row that can optionally act as a navigation link or trigger an action when clicked, with analytics tracking. + * + * @public + */ export function Row(props: RowProps) { - const { classNames, cleanedProps } = useStyles(TableDefinition, props); - const { id, columns, children, href, ...rest } = cleanedProps; - const navigate = useNavigate(); + const { ownProps, restProps, dataAttributes, analytics } = useDefinition( + RowDefinition, + props, + ); + const { classes, columns, children, href } = ownProps; const isExternal = isExternalLink(href); + const hasInternalHref = !!href && !isExternal; + const hasExternalHref = !!href && isExternal; + const hasInteraction = !!restProps.onAction || !!href; - let { selectionBehavior } = useTableOptions(); + // Derive the effective target, defaulting to _blank for external links. + const effectiveTarget = hasExternalHref ? '_blank' : restProps.target; + // Always include noopener noreferrer when target=_blank, merging any + // consumer-provided rel tokens to avoid reverse-tabnabbing risk. + const effectiveRel = + effectiveTarget === '_blank' + ? [ + ...new Set([ + 'noopener', + 'noreferrer', + ...(restProps.rel?.split(/\s+/).filter(Boolean) ?? []), + ]), + ].join(' ') + : restProps.rel; + + const handlePress = hasInteraction + ? () => { + restProps.onAction?.(); + if (href) { + analytics.captureEvent('click', href, { + attributes: { to: String(href) }, + }); + } + } + : undefined; + + let { selectionBehavior, selectionMode } = useTableOptions(); const content = ( <> - {selectionBehavior === 'toggle' && ( - - - + {selectionBehavior === 'toggle' && selectionMode === 'multiple' && ( + + + + + )} {children} ); - if (isExternal) { - return ( - - {content} - - ); - } - return ( - - - {content} - - + + {content} + ); } diff --git a/packages/ui/src/components/Table/components/Table.tsx b/packages/ui/src/components/Table/components/Table.tsx index ffd0ab231d..450e40d281 100644 --- a/packages/ui/src/components/Table/components/Table.tsx +++ b/packages/ui/src/components/Table/components/Table.tsx @@ -14,24 +14,272 @@ * limitations under the License. */ -import { useStyles } from '../../../hooks/useStyles'; -import { TableDefinition } from '../definition'; +import { useId } from 'react-aria'; import { - Table as ReactAriaTable, - type TableProps, + type Key, + ResizableTableContainer, + Virtualizer, } from 'react-aria-components'; -import styles from '../Table.module.css'; -import clsx from 'clsx'; +import { TableLayout } from 'react-stately'; +import { useDefinition } from '../../../hooks/useDefinition'; +import { TableWrapperDefinition } from '../definition'; +import { TableRoot } from './TableRoot'; +import { TableHeader } from './TableHeader'; +import { TableBody } from './TableBody'; +import { Row } from './Row'; +import { Column } from './Column'; +import { TablePagination } from '../../TablePagination'; +import type { + TableProps, + TableItem, + RowConfig, + RowRenderFn, + TablePaginationType, +} from '../types'; +import { useMemo } from 'react'; +import { VisuallyHidden } from '../../VisuallyHidden'; +import { Flex } from '../../Flex'; +import { TableBodySkeleton } from './TableBodySkeleton'; -/** @public */ -export const Table = (props: TableProps) => { - const { classNames, cleanedProps } = useStyles(TableDefinition, props); +function isRowRenderFn( + rowConfig: RowConfig | RowRenderFn | undefined, +): rowConfig is RowRenderFn { + return typeof rowConfig === 'function'; +} + +function useDisabledRows({ + data, + rowConfig, +}: Pick, 'data' | 'rowConfig'>): Set | undefined { + return useMemo(() => { + if (!data || typeof rowConfig === 'function' || !rowConfig?.getIsDisabled) { + return; + } + + return data.reduce>((set, item) => { + const isDisabled = rowConfig.getIsDisabled?.(item); + if (isDisabled) { + set.add(String(item.id)); + } + return set; + }, new Set()); + }, [data, rowConfig]); +} + +function useLiveRegionLabel( + pagination: TablePaginationType, + isStale: boolean, + isLoading: boolean, + hasData: boolean, +): string { + if (isLoading) { + return 'Loading table data.'; + } + + if (!hasData || pagination.type === 'none') { + return ''; + } + + const { pageSize, offset, totalCount, getLabel } = pagination; + + if (isStale) { + return 'Loading table data.'; + } + + let liveRegionLabel = 'Table page loaded. '; + + if (getLabel) { + liveRegionLabel += getLabel({ pageSize, offset, totalCount }); + } else if (offset !== undefined) { + const fromCount = offset + 1; + const toCount = Math.min(offset + pageSize, totalCount ?? 0); + liveRegionLabel += `Showing ${fromCount} to ${toCount} of ${totalCount}`; + } + return liveRegionLabel; +} + +/** + * A full-featured data table with built-in pagination, sorting, row selection, loading and error states, and optional virtualization. + * Pair with `useTable` to manage data fetching and state, or pass `data`, `columnConfig`, and `pagination` directly for manual control. + * + * @public + */ +export function Table({ + columnConfig, + data, + loading = false, + isStale = false, + error, + pagination, + sort, + rowConfig, + selection, + emptyState, + className, + style, + virtualized, +}: TableProps) { + const { + ownProps: { classes }, + } = useDefinition(TableWrapperDefinition, { className }); + const liveRegionId = useId(); + + const visibleColumns = useMemo( + () => columnConfig.filter(col => !col.isHidden), + [columnConfig], + ); + const disabledRows = useDisabledRows({ data, rowConfig }); + + const { + mode: selectionMode, + selected: selectedKeys, + behavior: selectionBehavior, + onSelectionChange, + } = selection || {}; + + const isInitialLoading = loading && !data; + + if (error) { + return ( +
    + Error: {error.message} +
    + ); + } + + const liveRegionLabel = useLiveRegionLabel( + pagination, + isStale, + isInitialLoading, + data !== undefined, + ); + + const manualColumnSizing = columnConfig.some( + col => + col.width != null || + col.minWidth != null || + col.maxWidth != null || + col.defaultWidth != null, + ); + + const wrapResizable = manualColumnSizing + ? (elem: React.ReactNode) => ( + + {elem} + + ) + : (elem: React.ReactNode) => <>{elem}; + + const layoutOptions = + typeof virtualized === 'object' ? virtualized : undefined; + + const wrapVirtualized = (elem: React.ReactNode) => + virtualized ? ( + + {elem} + + ) : ( + elem + ); return ( - +
    + + {liveRegionLabel} + + {wrapResizable( + wrapVirtualized( + + + {column => + column.header ? ( + column.header() + ) : ( + + {column.label} + + ) + } + + {isInitialLoading ? ( + + ) : ( + {emptyState} : undefined + } + > + {item => { + const itemIndex = data?.indexOf(item) ?? -1; + + if (isRowRenderFn(rowConfig)) { + return rowConfig({ + item, + index: itemIndex, + }); + } + + return ( + rowConfig?.onClick?.(item) + : undefined + } + > + {column => column.cell(item)} + + ); + }} + + )} + , + ), + )} + {pagination.type === 'page' && ( + + )} +
    ); -}; +} diff --git a/packages/ui/src/components/Table/components/TableBody.tsx b/packages/ui/src/components/Table/components/TableBody.tsx index 960d37bf84..cee109eb37 100644 --- a/packages/ui/src/components/Table/components/TableBody.tsx +++ b/packages/ui/src/components/Table/components/TableBody.tsx @@ -14,23 +14,20 @@ * limitations under the License. */ -import { - TableBody as ReactAriaTableBody, - type TableBodyProps, -} from 'react-aria-components'; -import { useStyles } from '../../../hooks/useStyles'; -import { TableDefinition } from '../definition'; -import styles from '../Table.module.css'; -import clsx from 'clsx'; +import { TableBody as ReactAriaTableBody } from 'react-aria-components'; +import { useDefinition } from '../../../hooks/useDefinition'; +import { TableBodyDefinition } from '../definition'; +import type { TableBodyProps } from '../types'; -/** @public */ +/** + * The body section of a table that renders data rows from the provided items collection. + * + * @public + */ export const TableBody = (props: TableBodyProps) => { - const { classNames, cleanedProps } = useStyles(TableDefinition, props); + const { ownProps, restProps } = useDefinition(TableBodyDefinition, props); return ( - + ); }; diff --git a/packages/ui/src/components/Table/components/TableBodySkeleton.tsx b/packages/ui/src/components/Table/components/TableBodySkeleton.tsx new file mode 100644 index 0000000000..16387fbbcc --- /dev/null +++ b/packages/ui/src/components/Table/components/TableBodySkeleton.tsx @@ -0,0 +1,67 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { TableBody } from './TableBody'; +import { Row } from './Row'; +import { Cell } from './Cell'; +import { Skeleton } from '../../Skeleton'; + +const SKELETON_ROW_COUNT = 5; +const SKELETON_WIDTHS = ['75%', '50%', '60%', '45%', '70%']; + +const skeletonItems = Array.from({ length: SKELETON_ROW_COUNT }, (_, i) => ({ + id: `skeleton-${i}`, + index: i, +})); + +/** + * A table body that renders animated skeleton rows as a loading placeholder. + * + * @remarks + * Accepts any column array whose items have an `id` property, making it + * compatible with both `ColumnConfig` and custom column types. + * + * @public + */ +export function TableBodySkeleton({ + columns, +}: { + columns: readonly T[]; +}) { + return ( + + {item => { + const rowIndex = item.index; + return ( + + {column => ( + + )} + + ); + }} + + ); +} diff --git a/packages/ui/src/components/Table/components/TableHeader.tsx b/packages/ui/src/components/Table/components/TableHeader.tsx index d8eea45271..eea73bdf2a 100644 --- a/packages/ui/src/components/Table/components/TableHeader.tsx +++ b/packages/ui/src/components/Table/components/TableHeader.tsx @@ -16,33 +16,39 @@ import { TableHeader as ReactAriaTableHeader, - type TableHeaderProps, - Checkbox, + Collection, + useTableOptions, } from 'react-aria-components'; -import { Collection, useTableOptions } from 'react-aria-components'; +import { Checkbox } from '../../Checkbox'; import { Column } from './Column'; -import { useStyles } from '../../../hooks/useStyles'; -import { TableDefinition } from '../definition'; -import styles from '../Table.module.css'; -import clsx from 'clsx'; +import { useDefinition } from '../../../hooks/useDefinition'; +import { TableHeaderDefinition } from '../definition'; +import type { TableHeaderProps } from '../types'; +import { Flex } from '../../Flex'; -/** @public */ +/** + * The header row of a table, rendering column labels and an optional select-all checkbox for toggle selection mode. + * + * @public + */ export const TableHeader = (props: TableHeaderProps) => { - let { selectionBehavior, selectionMode, allowsDragging } = useTableOptions(); + let { selectionBehavior, selectionMode } = useTableOptions(); - const { classNames, cleanedProps } = useStyles(TableDefinition, props); - const { columns, children, ...rest } = cleanedProps; + const { ownProps, restProps } = useDefinition(TableHeaderDefinition, props); + const { classes, columns, children } = ownProps; return ( - - {/* Add extra columns for drag and drop and selection. */} - {allowsDragging && } - {selectionBehavior === 'toggle' && ( - - {selectionMode === 'multiple' && } + + {selectionBehavior === 'toggle' && selectionMode === 'multiple' && ( + + + + )} {children} diff --git a/packages/ui/src/components/Table/components/TableRoot.tsx b/packages/ui/src/components/Table/components/TableRoot.tsx new file mode 100644 index 0000000000..5ea21046df --- /dev/null +++ b/packages/ui/src/components/Table/components/TableRoot.tsx @@ -0,0 +1,43 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useDefinition } from '../../../hooks/useDefinition'; +import { TableDefinition } from '../definition'; +import { Table as ReactAriaTable } from 'react-aria-components'; +import { TableRootProps } from '../types'; + +/** + * The low-level table root element for building custom table layouts from atomic components. + * For most use cases, prefer the `Table` convenience wrapper. + * + * @public + */ +export const TableRoot = (props: TableRootProps) => { + const { ownProps, restProps, dataAttributes } = useDefinition( + TableDefinition, + props, + ); + + return ( + + ); +}; diff --git a/packages/ui/src/components/Table/definition.ts b/packages/ui/src/components/Table/definition.ts index 8ec9dfb1f0..e2236e21b7 100644 --- a/packages/ui/src/components/Table/definition.ts +++ b/packages/ui/src/components/Table/definition.ts @@ -14,30 +14,168 @@ * limitations under the License. */ -import type { ComponentDefinition } from '../../types'; +import { defineComponent } from '../../hooks/useDefinition'; +import type { + TableRootOwnProps, + TableHeaderOwnProps, + TableBodyOwnProps, + RowOwnProps, + ColumnOwnProps, + CellOwnProps, + CellTextOwnProps, + CellProfileOwnProps, +} from './types'; +import styles from './Table.module.css'; + +/** @internal */ +export const TableWrapperDefinition = defineComponent<{ + className?: string; +}>()({ + styles, + classNames: { + root: 'bui-TableWrapper', + resizableContainer: 'bui-TableResizableContainer', + }, + propDefs: { + className: {}, + }, +}); /** * Component definition for Table * @public */ -export const TableDefinition = { +export const TableDefinition = defineComponent()({ + styles, classNames: { - table: 'bui-Table', - header: 'bui-TableHeader', - body: 'bui-TableBody', - row: 'bui-TableRow', - head: 'bui-TableHead', - headContent: 'bui-TableHeadContent', - headSortButton: 'bui-TableHeadSortButton', - caption: 'bui-TableCaption', + root: 'bui-Table', + }, + propDefs: { + stale: { dataAttribute: true }, + loading: { dataAttribute: true }, + }, +}); + +/** + * Component definition for TableHeader + * @internal + */ +export const TableHeaderDefinition = defineComponent()({ + styles, + classNames: { + root: 'bui-TableHeader', + headSelection: 'bui-TableHeadSelection', + }, + propDefs: { + columns: {}, + children: {}, + }, +}); + +/** + * Component definition for TableBody + * @internal + */ +export const TableBodyDefinition = defineComponent()({ + styles, + classNames: { + root: 'bui-TableBody', + }, + propDefs: {}, +}); + +/** + * Component definition for Row + * @internal + */ +export const RowDefinition = defineComponent()({ + styles, + analytics: true, + bg: 'consumer', + classNames: { + root: 'bui-TableRow', cell: 'bui-TableCell', + cellSelection: 'bui-TableCellSelection', + }, + propDefs: { + columns: {}, + children: {}, + href: {}, + noTrack: {}, + }, +}); + +/** + * Component definition for Column + * @internal + */ +export const ColumnDefinition = defineComponent()({ + styles, + classNames: { + root: 'bui-TableHead', + headContent: 'bui-TableHeadContent', + headLabel: 'bui-TableHeadLabel', + headSortButton: 'bui-TableHeadSortButton', + }, + propDefs: { + children: {}, + className: {}, + }, +}); + +/** + * Component definition for Cell + * @internal + */ +export const CellDefinition = defineComponent()({ + styles, + classNames: { + root: 'bui-TableCell', + }, + propDefs: { + className: {}, + }, +}); + +/** + * Component definition for CellText + * @internal + */ +export const CellTextDefinition = defineComponent()({ + styles, + classNames: { + root: 'bui-TableCell', cellContentWrapper: 'bui-TableCellContentWrapper', cellContent: 'bui-TableCellContent', cellIcon: 'bui-TableCellIcon', - cellProfileAvatar: 'bui-TableCellProfileAvatar', - cellProfileAvatarImage: 'bui-TableCellProfileAvatarImage', - cellProfileAvatarFallback: 'bui-TableCellProfileAvatarFallback', - cellProfileName: 'bui-TableCellProfileName', - cellProfileLink: 'bui-TableCellProfileLink', }, -} as const satisfies ComponentDefinition; + propDefs: { + title: {}, + description: {}, + color: { default: 'primary' }, + leadingIcon: {}, + href: {}, + className: {}, + }, +}); + +/** + * Component definition for CellProfile + * @internal + */ +export const CellProfileDefinition = defineComponent()({ + styles, + classNames: { + root: 'bui-TableCell', + cellContentWrapper: 'bui-TableCellContentWrapper', + cellContent: 'bui-TableCellContent', + }, + propDefs: { + src: {}, + name: {}, + href: {}, + description: {}, + color: { default: 'primary' }, + className: {}, + }, +}); diff --git a/packages/ui/src/components/Table/hooks/getEffectivePageSize.ts b/packages/ui/src/components/Table/hooks/getEffectivePageSize.ts new file mode 100644 index 0000000000..41b246da9f --- /dev/null +++ b/packages/ui/src/components/Table/hooks/getEffectivePageSize.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 type { PageSizeOption } from '../../TablePagination/types'; +import type { PaginationOptions } from './types'; + +const DEFAULT_PAGE_SIZE = 20; + +function getOptionValue(option: number | PageSizeOption): number { + return typeof option === 'number' ? option : option.value; +} + +/** @internal */ +export function getEffectivePageSize( + paginationOptions: PaginationOptions, +): number { + const { pageSize, pageSizeOptions } = paginationOptions; + + if (!pageSizeOptions) { + return pageSize ?? DEFAULT_PAGE_SIZE; + } + + const firstValue = getOptionValue(pageSizeOptions[0]); + + if (pageSize === undefined) { + return firstValue; + } + + const isValid = pageSizeOptions.some(opt => getOptionValue(opt) === pageSize); + + if (isValid) { + return pageSize; + } + + console.warn( + `useTable: pageSize ${pageSize} is not in pageSizeOptions, using ${firstValue} instead`, + ); + return firstValue; +} diff --git a/packages/ui/src/components/Table/hooks/types.ts b/packages/ui/src/components/Table/hooks/types.ts index 8c93d678fc..06c980efdf 100644 --- a/packages/ui/src/components/Table/hooks/types.ts +++ b/packages/ui/src/components/Table/hooks/types.ts @@ -15,73 +15,169 @@ */ import type { TablePaginationProps } from '../../TablePagination/types'; +import type { SortDescriptor, TableItem, TableProps } from '../types'; /** @public */ -export interface UseTablePaginationConfig { - /** Total number of rows in the dataset - only needed when data is not provided at the top level */ - rowCount?: number; - - // Controlled pagination with offset/pageSize (Backstage style) - /** Current offset. When provided, pagination is controlled */ - offset?: number; - /** Current page size. When provided, pagination is controlled */ - pageSize?: number; - /** Callback when offset changes */ - onOffsetChange?: (offset: number) => void; - /** Callback when page size changes */ - onPageSizeChange?: (pageSize: number) => void; - - // Uncontrolled pagination defaults - /** Default page size for uncontrolled mode */ - defaultPageSize?: number; - /** Default offset for uncontrolled mode */ - defaultOffset?: number; - - // Analytics callbacks - /** Callback when next page is clicked */ - onNextPage?: () => void; - /** Callback when previous page is clicked */ - onPreviousPage?: () => void; - - // UI options - /** Whether to show page size options */ - showPageSizeOptions?: boolean; +export interface FilterState { + value: TFilter | undefined; + onChange: (value: TFilter) => void; } /** @public */ -export interface UseTablePagination { - /** Props to pass to TablePagination component */ - paginationProps: TablePaginationProps; - /** Current offset */ +export interface SearchState { + value: string; + onChange: (value: string) => void; +} + +/** @public */ +export interface QueryOptions { + initialSort?: SortDescriptor; + sort?: SortDescriptor | null; + onSortChange?: (sort: SortDescriptor) => void; + + initialFilter?: TFilter; + filter?: TFilter; + onFilterChange?: (filter: TFilter) => void; + + initialSearch?: string; + search?: string; + onSearchChange?: (search: string) => void; +} + +/** @public */ +export interface PaginationOptions + extends Partial< + Pick< + TablePaginationProps, + | 'pageSize' + | 'pageSizeOptions' + | 'onPageSizeChange' + | 'onNextPage' + | 'onPreviousPage' + | 'showPageSizeOptions' + | 'getLabel' + | 'showPaginationLabel' + > + > { + initialOffset?: number; +} + +/** @public */ +export interface CompletePaginationOptions extends PaginationOptions { + type?: 'page' | 'none'; +} + +/** @public */ +export interface OffsetParams { offset: number; - /** Current page size */ pageSize: number; - /** Sliced data for current page - only available when data is provided to useTable */ - data?: T[]; - /** Go to next page */ - nextPage: () => void; - /** Go to previous page */ - previousPage: () => void; - /** Set specific offset */ - setOffset: (offset: number) => void; - /** Set page size */ - setPageSize: (pageSize: number) => void; + sort: SortDescriptor | null; + filter: TFilter | undefined; + search: string; + signal: AbortSignal; } /** @public */ -export interface UseTableConfig { - /** Full dataset - when provided, rowCount is calculated automatically and sliced data is returned */ - data?: T[]; - /** Pagination configuration */ - pagination?: UseTablePaginationConfig; +export interface CursorParams { + cursor: string | undefined; + pageSize: number; + sort: SortDescriptor | null; + filter: TFilter | undefined; + search: string; + signal: AbortSignal; } /** @public */ -export interface UseTableResult { - /** Sliced data for current page */ - data?: T[]; - /** Props to pass to TablePagination component */ - paginationProps: TablePaginationProps; - /** Pagination utilities */ - pagination: UseTablePagination; +export interface OffsetResponse { + data: T[]; + totalCount: number; +} + +/** @public */ +export interface CursorResponse { + data: T[]; + nextCursor?: string; + prevCursor?: string; + totalCount?: number; +} + +/** @public */ +export type UseTableCompleteOptions< + T extends TableItem, + TFilter = unknown, +> = QueryOptions & { + mode: 'complete'; + paginationOptions?: CompletePaginationOptions; + sortFn?: (data: T[], sort: SortDescriptor) => T[]; + filterFn?: (data: T[], filter: TFilter) => T[]; + searchFn?: (data: T[], search: string) => T[]; +} & ( + | { + data: T[] | undefined; + getData?: never; + } + | { + data?: never; + getData: () => T[] | Promise; + } + ); + +/** @public */ +export interface UseTableOffsetOptions + extends QueryOptions { + mode: 'offset'; + getData: (params: OffsetParams) => Promise>; + paginationOptions?: PaginationOptions; +} + +/** @public */ +export interface UseTableCursorOptions + extends QueryOptions { + mode: 'cursor'; + getData: (params: CursorParams) => Promise>; + paginationOptions?: Omit; +} + +/** @public */ +export type UseTableOptions = + | UseTableCompleteOptions + | UseTableOffsetOptions + | UseTableCursorOptions; + +/** @public */ +export interface UseTableResult { + tableProps: Omit< + TableProps, + 'columnConfig' | 'rowConfig' | 'selection' | 'emptyState' + >; + reload: () => void; + filter: FilterState; + search: SearchState; +} + +/** @internal */ +export interface PaginationResult { + data: T[] | undefined; + loading: boolean; + error: Error | undefined; + totalCount: number | undefined; + offset?: number; + pageSize: number; + hasNextPage: boolean; + hasPreviousPage: boolean; + onNextPage: () => void; + onPreviousPage: () => void; + onPageSizeChange: (size: number) => void; +} + +/** @internal */ +export interface QueryState { + sort: SortDescriptor | null; + setSort: (sort: SortDescriptor) => void; + + filter: TFilter | undefined; + setFilter: (filter: TFilter) => void; + + search: string; + setSearch: (search: string) => void; } diff --git a/packages/ui/src/components/Table/hooks/useCompletePagination.ts b/packages/ui/src/components/Table/hooks/useCompletePagination.ts new file mode 100644 index 0000000000..f34e2c8c06 --- /dev/null +++ b/packages/ui/src/components/Table/hooks/useCompletePagination.ts @@ -0,0 +1,179 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useState, useCallback, useMemo, useEffect, useRef } from 'react'; +import type { TableItem } from '../types'; +import type { + PaginationResult, + QueryState, + UseTableCompleteOptions, +} from './types'; +import { useStableCallback } from './useStableCallback'; +import { getEffectivePageSize } from './getEffectivePageSize'; + +/** @internal */ +export function useCompletePagination( + options: UseTableCompleteOptions, + query: QueryState, +): PaginationResult & { reload: () => void } { + const { + data, + getData: getDataProp = () => [], + paginationOptions = {}, + sortFn, + filterFn, + searchFn, + } = options; + const hasGetData = 'getData' in options; + const noPagination = paginationOptions.type === 'none'; + const { initialOffset = 0 } = paginationOptions; + const defaultPageSize = noPagination + ? Infinity + : getEffectivePageSize(paginationOptions); + + const getData = useStableCallback(getDataProp); + const { sort, filter, search } = query; + + const [items, setItems] = useState(undefined); + const [isLoading, setIsLoading] = useState(!data); + const [error, setError] = useState(undefined); + const [loadCount, setLoadCount] = useState(0); + + const [offset, setOffset] = useState(initialOffset); + const [pageSize, setPageSize] = useState(defaultPageSize); + + // Sync pageSize when the caller changes paginationOptions.pageSize + useEffect(() => { + setPageSize(defaultPageSize); + setOffset(0); + }, [defaultPageSize]); + + // Load data on mount and when loadCount changes (reload trigger) + useEffect(() => { + if (data) { + setIsLoading(false); + return; + } + + if (!hasGetData) { + return; + } + + let cancelled = false; + setIsLoading(true); + setError(undefined); + + (async () => { + try { + const result = getData(); + const resolvedData = result instanceof Promise ? await result : result; + if (!cancelled) { + setItems(resolvedData); + setIsLoading(false); + } + } catch (err) { + if (!cancelled) { + setError(err instanceof Error ? err : new Error(String(err))); + setIsLoading(false); + } + } + })(); + + return () => { + cancelled = true; + }; + }, [data, getData, hasGetData, loadCount]); + + // Reset offset when query changes (query object is memoized) + const prevQueryRef = useRef(query); + useEffect(() => { + if (prevQueryRef.current !== query) { + prevQueryRef.current = query; + setOffset(0); + } + }, [query]); + + const resolvedItems = useMemo(() => data ?? items, [data, items]); + + // Process data client-side (filter, search, sort) + const processedData = useMemo(() => { + if (!resolvedItems) { + return undefined; + } + let result = [...resolvedItems]; + if (filter !== undefined && filterFn) { + result = filterFn(result, filter); + } + if (search && searchFn) { + result = searchFn(result, search); + } + if (sort && sortFn) { + result = sortFn(result, sort); + } + return result; + }, [resolvedItems, sort, filter, search, filterFn, searchFn, sortFn]); + + const totalCount = processedData?.length ?? 0; + + // Paginate the processed data + const paginatedData = useMemo( + () => + noPagination + ? processedData + : processedData?.slice(offset, offset + pageSize), + [processedData, offset, pageSize, noPagination], + ); + + const hasNextPage = !noPagination && offset + pageSize < totalCount; + const hasPreviousPage = !noPagination && offset > 0; + + const onNextPage = useCallback(() => { + if (offset + pageSize < totalCount) { + setOffset(offset + pageSize); + } + }, [offset, pageSize, totalCount]); + + const onPreviousPage = useCallback(() => { + if (offset > 0) { + setOffset(Math.max(0, offset - pageSize)); + } + }, [offset, pageSize]); + + const onPageSizeChange = useCallback((newSize: number) => { + setPageSize(newSize); + setOffset(0); + }, []); + + const reload = useCallback(() => { + setOffset(0); + setLoadCount(c => c + 1); + }, []); + + return { + data: paginatedData, + loading: isLoading, + error, + totalCount, + offset, + pageSize, + hasNextPage, + hasPreviousPage, + onNextPage, + onPreviousPage, + onPageSizeChange, + reload, + }; +} diff --git a/packages/ui/src/components/Table/hooks/useCursorPagination.ts b/packages/ui/src/components/Table/hooks/useCursorPagination.ts new file mode 100644 index 0000000000..5da56b3ce4 --- /dev/null +++ b/packages/ui/src/components/Table/hooks/useCursorPagination.ts @@ -0,0 +1,93 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { useState, useCallback } from 'react'; +import type { TableItem } from '../types'; +import type { + UseTableCursorOptions, + CursorParams, + QueryState, + PaginationResult, +} from './types'; +import { usePageCache } from './usePageCache'; +import { useStableCallback } from './useStableCallback'; +import { useDebouncedReload } from './useDebouncedReload'; +import { getEffectivePageSize } from './getEffectivePageSize'; + +export function useCursorPagination( + options: UseTableCursorOptions, + query: QueryState, +): PaginationResult & { reload: () => void } { + const { getData: getDataProp, paginationOptions = {} } = options; + const defaultPageSize = getEffectivePageSize(paginationOptions); + + const getData = useStableCallback(getDataProp); + const { sort, filter, search } = query; + + const [pageSize, setPageSize] = useState(defaultPageSize); + + const wrappedGetData = useCallback( + async ({ + cursor, + signal, + }: { + cursor: string | undefined; + signal: AbortSignal; + }) => { + const params: CursorParams = { + cursor, + pageSize, + sort, + filter, + search, + signal, + }; + + const response = await getData(params); + + return { + data: response.data, + prevCursor: response.prevCursor, + nextCursor: response.nextCursor, + totalCount: response.totalCount, + }; + }, + [getData, pageSize, sort, filter, search], + ); + + const cache = usePageCache({ getData: wrappedGetData }); + + useDebouncedReload(query, pageSize, cache.reload); + + const onPageSizeChange = useCallback( + (newSize: number) => setPageSize(newSize), + [], + ); + + return { + data: cache.data, + loading: cache.loading, + error: cache.error, + totalCount: cache.totalCount, + offset: undefined, + pageSize, + hasNextPage: cache.hasNextPage, + hasPreviousPage: cache.hasPreviousPage, + onNextPage: cache.onNextPage, + onPreviousPage: cache.onPreviousPage, + onPageSizeChange, + reload: cache.reload, + }; +} diff --git a/packages/ui/src/components/Table/hooks/useDebouncedReload.ts b/packages/ui/src/components/Table/hooks/useDebouncedReload.ts new file mode 100644 index 0000000000..d90afa8861 --- /dev/null +++ b/packages/ui/src/components/Table/hooks/useDebouncedReload.ts @@ -0,0 +1,42 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useEffect, useRef } from 'react'; +import type { QueryState } from './types'; + +/** + * Triggers a debounced reload when query or pageSize changes. + * Debouncing reduces backend load during rapid changes (e.g., typing in search). + */ +/** @internal */ +export function useDebouncedReload( + query: QueryState, + pageSize: number, + reload: () => void, + delay: number = 200, +): void { + const prevDepsRef = useRef({ query, pageSize }); + + useEffect(() => { + const prev = prevDepsRef.current; + if (prev.query !== query || prev.pageSize !== pageSize) { + prevDepsRef.current = { query, pageSize }; + const timer = setTimeout(reload, delay); + return () => clearTimeout(timer); + } + return undefined; + }, [query, pageSize, reload, delay]); +} diff --git a/packages/ui/src/components/Table/hooks/useOffsetPagination.ts b/packages/ui/src/components/Table/hooks/useOffsetPagination.ts new file mode 100644 index 0000000000..b34dbe8a6c --- /dev/null +++ b/packages/ui/src/components/Table/hooks/useOffsetPagination.ts @@ -0,0 +1,106 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { useState, useCallback } from 'react'; +import type { TableItem } from '../types'; +import type { + UseTableOffsetOptions, + OffsetParams, + QueryState, + PaginationResult, +} from './types'; +import { usePageCache } from './usePageCache'; +import { useStableCallback } from './useStableCallback'; +import { useDebouncedReload } from './useDebouncedReload'; +import { getEffectivePageSize } from './getEffectivePageSize'; + +export function useOffsetPagination( + options: UseTableOffsetOptions, + query: QueryState, +): PaginationResult & { reload: () => void } { + const { getData: getDataProp, paginationOptions = {} } = options; + const { initialOffset = 0 } = paginationOptions; + const defaultPageSize = getEffectivePageSize(paginationOptions); + + const getData = useStableCallback(getDataProp); + const { sort, filter, search } = query; + + const [pageSize, setPageSize] = useState(defaultPageSize); + + const wrappedGetData = useCallback( + async ({ + cursor, + signal, + }: { + cursor: number | undefined; + signal: AbortSignal; + }) => { + const currentOffset = cursor ?? 0; + + const params: OffsetParams = { + offset: currentOffset, + pageSize, + sort, + filter, + search, + signal, + }; + + const response = await getData(params); + + const prevCursor = + currentOffset > 0 ? Math.max(0, currentOffset - pageSize) : undefined; + const nextCursor = + currentOffset + pageSize < response.totalCount + ? currentOffset + pageSize + : undefined; + + return { + data: response.data, + prevCursor, + nextCursor, + totalCount: response.totalCount, + }; + }, + [getData, pageSize, sort, filter, search], + ); + + const cache = usePageCache({ + getData: wrappedGetData, + initialCurrentCursor: initialOffset > 0 ? initialOffset : undefined, + }); + + useDebouncedReload(query, pageSize, cache.reload); + + const onPageSizeChange = useCallback( + (newSize: number) => setPageSize(newSize), + [], + ); + + return { + data: cache.data, + loading: cache.loading, + error: cache.error, + totalCount: cache.totalCount, + offset: cache.currentCursor ?? 0, + pageSize, + hasNextPage: cache.hasNextPage, + hasPreviousPage: cache.hasPreviousPage, + onNextPage: cache.onNextPage, + onPreviousPage: cache.onPreviousPage, + onPageSizeChange, + reload: cache.reload, + }; +} diff --git a/packages/ui/src/components/Table/hooks/usePageCache.ts b/packages/ui/src/components/Table/hooks/usePageCache.ts new file mode 100644 index 0000000000..9bf864749b --- /dev/null +++ b/packages/ui/src/components/Table/hooks/usePageCache.ts @@ -0,0 +1,280 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useState, useCallback, useRef, useEffect } from 'react'; + +const FIRST_PAGE_CURSOR = Symbol('firstPage'); + +type CursorType = string | number; + +type InternalCursor = + | TCursor + | typeof FIRST_PAGE_CURSOR; + +interface PageEntry { + data: T[] | undefined; + nextCursor: InternalCursor | undefined; + prevCursor: InternalCursor | undefined; +} + +interface GetDataResult { + data: T[]; + nextCursor?: TCursor; + prevCursor?: TCursor; + totalCount?: number; +} + +/** @internal */ +export interface UsePageCacheOptions { + getData: (params: { + cursor: TCursor | undefined; + signal: AbortSignal; + }) => Promise>; + initialCurrentCursor?: TCursor; +} + +/** @internal */ +export interface UsePageCacheResult { + loading: boolean; + error: Error | undefined; + data: T[] | undefined; + totalCount: number | undefined; + currentCursor: TCursor | undefined; + hasPreviousPage: boolean; + onPreviousPage: () => void; + hasNextPage: boolean; + onNextPage: () => void; + reload: (options?: { keepCurrentCursor?: boolean }) => void; +} + +type Direction = 'mount' | 'reset' | 'refresh' | 'next' | 'prev'; + +class PageCacheStore { + private cache = new Map, PageEntry>(); + + get(cursor: InternalCursor): PageEntry | undefined { + return this.cache.get(cursor); + } + + getOrCreate(cursor: InternalCursor): PageEntry { + const existing = this.cache.get(cursor); + if (existing) { + return existing; + } + const entry: PageEntry = { + data: undefined, + nextCursor: undefined, + prevCursor: undefined, + }; + this.cache.set(cursor, entry); + return entry; + } + + clear() { + this.cache.clear(); + } + + getTargetCursor( + direction: Direction, + currentCursor: InternalCursor, + initialCurrentCursor: TCursor | undefined, + ): InternalCursor | undefined { + if (direction === 'mount') { + return toInternalCursor(initialCurrentCursor); + } + if (direction === 'reset') { + return FIRST_PAGE_CURSOR; + } + if (direction === 'refresh') { + return currentCursor; + } + const currentEntry = this.cache.get(currentCursor); + + if (!currentEntry) { + return; + } + + return direction === 'next' + ? currentEntry.nextCursor + : currentEntry.prevCursor; + } + + linkEntryToSource( + entry: PageEntry, + direction: Direction, + currentCursor: InternalCursor, + ) { + if (direction === 'next') { + entry.prevCursor = currentCursor; + } else if (direction === 'prev') { + entry.nextCursor = currentCursor; + } + } +} + +function toInternalCursor( + cursor: TCursor | undefined, +): InternalCursor { + return cursor === undefined ? FIRST_PAGE_CURSOR : cursor; +} + +function toExternalCursor( + cursor: InternalCursor, +): TCursor | undefined { + return cursor === FIRST_PAGE_CURSOR ? undefined : cursor; +} + +/** @internal */ +export function usePageCache( + options: UsePageCacheOptions, +): UsePageCacheResult { + const { getData, initialCurrentCursor } = options; + + const [currentCursor, setCurrentCursor] = useState>( + () => toInternalCursor(initialCurrentCursor), + ); + + const cacheStore = useRef(new PageCacheStore()).current; + + const [loading, setLoading] = useState(true); + const [error, setError] = useState(undefined); + const [totalCount, setTotalCount] = useState(undefined); + + const abortControllerRef = useRef(null); + + const currentPage = cacheStore.get(currentCursor); + const data = currentPage?.data; + const hasNextPage = currentPage?.nextCursor !== undefined; + const hasPreviousPage = currentPage?.prevCursor !== undefined; + + const goToPage = useCallback( + async (direction: Direction) => { + const targetCursor = cacheStore.getTargetCursor( + direction, + currentCursor, + initialCurrentCursor, + ); + + if (!targetCursor) { + return; + } + + const existingEntry = cacheStore.get(targetCursor); + if (existingEntry?.data !== undefined) { + setCurrentCursor(targetCursor); + return; + } + + const entry = cacheStore.getOrCreate(targetCursor); + cacheStore.linkEntryToSource(entry, direction, currentCursor); + setCurrentCursor(targetCursor); + + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + + const abortController = new AbortController(); + abortControllerRef.current = abortController; + + setLoading(true); + setError(undefined); + + try { + const result = await getData({ + cursor: toExternalCursor(targetCursor), + signal: abortController.signal, + }); + + if (abortController.signal.aborted) { + return; + } + + entry.data = result.data; + + if (entry.nextCursor === undefined && result.nextCursor !== undefined) { + entry.nextCursor = result.nextCursor; + } + if (entry.prevCursor === undefined && result.prevCursor !== undefined) { + entry.prevCursor = result.prevCursor; + } + + if (result.totalCount !== undefined) { + setTotalCount(result.totalCount); + } + + setLoading(false); + } catch (err) { + if (abortController.signal.aborted) { + return; + } + + setError(err instanceof Error ? err : new Error(String(err))); + setLoading(false); + } + }, + [getData, initialCurrentCursor, currentCursor, cacheStore], + ); + + useEffect(() => { + goToPage('mount'); + + return () => { + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + }; + }, []); + + const onNextPage = useCallback(() => { + if (loading) return; + const page = cacheStore.get(currentCursor); + if (!page?.nextCursor) return; + goToPage('next'); + }, [loading, currentCursor, goToPage, cacheStore]); + + const onPreviousPage = useCallback(() => { + if (loading) return; + const page = cacheStore.get(currentCursor); + if (!page?.prevCursor) return; + goToPage('prev'); + }, [loading, currentCursor, goToPage, cacheStore]); + + const reload = useCallback( + (reloadOptions?: { keepCurrentCursor?: boolean }) => { + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + + cacheStore.clear(); + + goToPage(reloadOptions?.keepCurrentCursor ? 'refresh' : 'reset'); + }, + [goToPage, cacheStore], + ); + + return { + loading, + error, + data, + totalCount, + currentCursor: toExternalCursor(currentCursor), + hasPreviousPage, + onPreviousPage, + hasNextPage, + onNextPage, + reload, + }; +} diff --git a/packages/ui/src/components/Table/hooks/useQueryState.ts b/packages/ui/src/components/Table/hooks/useQueryState.ts new file mode 100644 index 0000000000..308d9fd385 --- /dev/null +++ b/packages/ui/src/components/Table/hooks/useQueryState.ts @@ -0,0 +1,68 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useMemo, useState, useCallback } from 'react'; +import type { QueryOptions, QueryState } from './types'; + +function useControlledStateHelper( + initialValue: TInitial, + controlledValue: TControlled | undefined, + onChange: ((value: T) => void) | undefined, +) { + const [internalValue, setInternalValue] = useState(initialValue); + + const value = controlledValue !== undefined ? controlledValue : internalValue; + + const setValue = useCallback( + (newValue: T) => { + if (controlledValue === undefined) { + setInternalValue(newValue as unknown as TInitial); + } + if (onChange) { + onChange(newValue); + } + }, + [controlledValue, onChange], + ); + + return [value, setValue] as const; +} + +/** @internal */ +export function useQueryState( + options: QueryOptions, +): QueryState { + const [sort, setSort] = useControlledStateHelper( + options.initialSort ?? null, + options.sort, + options.onSortChange, + ); + const [filter, setFilter] = useControlledStateHelper( + options.initialFilter, + options.filter, + options.onFilterChange, + ); + const [search, setSearch] = useControlledStateHelper( + options.initialSearch ?? '', + options.search, + options.onSearchChange, + ); + + return useMemo( + () => ({ sort, setSort, filter, setFilter, search, setSearch }), + [sort, setSort, filter, setFilter, search, setSearch], + ); +} diff --git a/packages/ui/src/components/Table/hooks/useStableCallback.ts b/packages/ui/src/components/Table/hooks/useStableCallback.ts new file mode 100644 index 0000000000..1764e07dd0 --- /dev/null +++ b/packages/ui/src/components/Table/hooks/useStableCallback.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 { useRef, useCallback } from 'react'; + +/** + * Returns a stable callback reference that always calls the latest version + * of the provided function. Useful for callbacks passed as props that may + * change on every render but shouldn't trigger effect re-runs. + * + * @internal + */ +export function useStableCallback any>(fn: T): T { + const ref = useRef(fn); + ref.current = fn; + + return useCallback((...args: Parameters) => ref.current(...args), []) as T; +} diff --git a/packages/ui/src/components/Table/hooks/useTable.ts b/packages/ui/src/components/Table/hooks/useTable.ts index 2759fcde41..7d1019f541 100644 --- a/packages/ui/src/components/Table/hooks/useTable.ts +++ b/packages/ui/src/components/Table/hooks/useTable.ts @@ -13,154 +13,171 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import { useState, useMemo, useCallback } from 'react'; -import type { TablePaginationProps } from '../../TablePagination/types'; +import { useMemo, useRef } from 'react'; +import type { SortState, TableItem, TableProps } from '../types'; import type { - UseTableConfig, + PaginationResult, + UseTableCompleteOptions, + UseTableCursorOptions, + UseTableOffsetOptions, + UseTableOptions, UseTableResult, - UseTablePagination, } from './types'; +import { useQueryState } from './useQueryState'; +import { useCompletePagination } from './useCompletePagination'; +import { useCursorPagination } from './useCursorPagination'; +import { useOffsetPagination } from './useOffsetPagination'; + +function useTableProps( + paginationResult: PaginationResult, + sortState: SortState, + paginationOptions: UseTableCompleteOptions['paginationOptions'] = {}, +): Omit< + TableProps, + 'columnConfig' | 'rowConfig' | 'selection' | 'emptyState' +> { + const { + showPageSizeOptions = true, + pageSizeOptions, + onPageSizeChange: onPageSizeChangeCallback, + onNextPage: onNextPageCallback, + onPreviousPage: onPreviousPageCallback, + getLabel, + showPaginationLabel, + } = paginationOptions; + + const previousDataRef = useRef(paginationResult.data); + if (paginationResult.data) { + previousDataRef.current = paginationResult.data; + } + + const displayData = paginationResult.data ?? previousDataRef.current; + const isStale = paginationResult.loading && displayData !== undefined; + + const pagination = useMemo(() => { + if (paginationOptions.type === 'none') { + return { type: 'none' as const }; + } + return { + type: 'page' as const, + pageSize: paginationResult.pageSize, + pageSizeOptions, + offset: paginationResult.offset, + totalCount: paginationResult.totalCount, + hasNextPage: paginationResult.hasNextPage, + hasPreviousPage: paginationResult.hasPreviousPage, + onNextPage: () => { + paginationResult.onNextPage(); + onNextPageCallback?.(); + }, + onPreviousPage: () => { + paginationResult.onPreviousPage(); + onPreviousPageCallback?.(); + }, + onPageSizeChange: (size: number) => { + paginationResult.onPageSizeChange(size); + onPageSizeChangeCallback?.(size); + }, + showPageSizeOptions, + getLabel, + showPaginationLabel, + }; + }, [ + paginationOptions.type, + paginationResult.pageSize, + pageSizeOptions, + paginationResult.offset, + paginationResult.totalCount, + paginationResult.hasNextPage, + paginationResult.hasPreviousPage, + paginationResult.onNextPage, + paginationResult.onPreviousPage, + paginationResult.onPageSizeChange, + onNextPageCallback, + onPreviousPageCallback, + onPageSizeChangeCallback, + showPageSizeOptions, + getLabel, + showPaginationLabel, + ]); + + return useMemo( + () => ({ + data: displayData, + loading: paginationResult.loading, + isStale, + error: paginationResult.error, + pagination, + sort: sortState, + }), + [ + displayData, + paginationResult.loading, + isStale, + paginationResult.error, + pagination, + sortState, + ], + ); +} /** - * Hook for managing table state including pagination and future features like sorting. - * Supports both controlled and uncontrolled modes using offset/pageSize pattern (Backstage style). + * A hook that manages table data fetching, pagination, sorting, search, and filtering. + * Supports three modes: `complete` for client-side data, `offset` for server-side offset pagination, and `cursor` for server-side cursor pagination. * * @public */ -export function useTable( - config: UseTableConfig = {}, -): UseTableResult { - const { data, pagination: paginationConfig = {} } = config; +export function useTable( + options: UseTableCompleteOptions, +): UseTableResult; +/** @public */ +export function useTable( + options: UseTableOffsetOptions, +): UseTableResult; +/** @public */ +export function useTable( + options: UseTableCursorOptions, +): UseTableResult; +export function useTable( + options: UseTableOptions, +): UseTableResult { + const query = useQueryState(options); - const { - rowCount: providedRowCount, - offset: controlledOffset, - pageSize: controlledPageSize, - onOffsetChange, - onPageSizeChange, - defaultPageSize = 10, - defaultOffset = 0, - onNextPage, - onPreviousPage, - showPageSizeOptions = true, - } = paginationConfig; + const initialModeRef = useRef(options.mode); + if (initialModeRef.current !== options.mode) { + throw new Error( + `useTable mode cannot change from '${initialModeRef.current}' to '${options.mode}'. ` + + `The mode must remain stable for the lifetime of the component.`, + ); + } - // Determine if we're in controlled mode - const isControlled = - controlledOffset !== undefined || controlledPageSize !== undefined; + let pagination: PaginationResult & { reload: () => void }; - // Calculate row count from data or use provided value - const rowCount = data?.length ?? providedRowCount ?? 0; + if (options.mode === 'complete') { + pagination = useCompletePagination(options, query); + } else if (options.mode === 'offset') { + pagination = useOffsetPagination(options, query); + } else if (options.mode === 'cursor') { + pagination = useCursorPagination(options, query); + } else { + throw new Error('Invalid mode'); + } - // Internal state for uncontrolled mode - const [internalOffset, setInternalOffset] = useState(defaultOffset); - const [internalPageSize, setInternalPageSize] = useState(defaultPageSize); - - // Calculate current values - const currentOffset = controlledOffset ?? internalOffset; - const currentPageSize = controlledPageSize ?? internalPageSize; - - // Calculate sliced data if data array is provided - const currentData = useMemo(() => { - if (!data) return undefined; - return data.slice(currentOffset, currentOffset + currentPageSize); - }, [data, currentOffset, currentPageSize]); - - // Update functions - const setOffset = useCallback( - (newOffset: number) => { - if (isControlled) { - onOffsetChange?.(newOffset); - } else { - setInternalOffset(newOffset); - } - }, - [isControlled, onOffsetChange], + const sortState: SortState = useMemo( + () => ({ descriptor: query.sort, onSortChange: query.setSort }), + [query.sort, query.setSort], ); - const setPageSize = useCallback( - (newPageSize: number) => { - // When changing page size, reset to first page to avoid showing empty results - const newOffset = 0; - - if (isControlled) { - onPageSizeChange?.(newPageSize); - onOffsetChange?.(newOffset); - } else { - setInternalPageSize(newPageSize); - setInternalOffset(newOffset); - } - }, - [isControlled, onPageSizeChange, onOffsetChange], - ); - - const nextPage = useCallback(() => { - const nextOffset = currentOffset + currentPageSize; - if (nextOffset < rowCount) { - onNextPage?.(); - setOffset(nextOffset); - } - }, [currentOffset, currentPageSize, rowCount, onNextPage, setOffset]); - - const previousPage = useCallback(() => { - if (currentOffset > 0) { - onPreviousPage?.(); - const prevOffset = Math.max(0, currentOffset - currentPageSize); - setOffset(prevOffset); - } - }, [currentOffset, currentPageSize, onPreviousPage, setOffset]); - - // Pagination props for TablePagination component - const paginationProps: TablePaginationProps = useMemo( - () => ({ - offset: currentOffset, - pageSize: currentPageSize, - rowCount, - setOffset, - setPageSize, - onNextPage, - onPreviousPage, - showPageSizeOptions, - }), - [ - currentOffset, - currentPageSize, - rowCount, - setOffset, - setPageSize, - onNextPage, - onPreviousPage, - showPageSizeOptions, - ], - ); - - const pagination: UseTablePagination = useMemo( - () => ({ - paginationProps, - offset: currentOffset, - pageSize: currentPageSize, - data: currentData, - nextPage, - previousPage, - setOffset, - setPageSize, - }), - [ - paginationProps, - currentOffset, - currentPageSize, - currentData, - nextPage, - previousPage, - setOffset, - setPageSize, - ], + const tableProps = useTableProps( + pagination, + sortState, + options.paginationOptions ?? {}, ); return { - data: currentData, - paginationProps, - pagination, + tableProps, + reload: pagination.reload, + filter: { value: query.filter, onChange: query.setFilter }, + search: { value: query.search, onChange: query.setSearch }, }; } diff --git a/packages/ui/src/components/Table/index.ts b/packages/ui/src/components/Table/index.ts index 8d6603ee00..07bb497426 100644 --- a/packages/ui/src/components/Table/index.ts +++ b/packages/ui/src/components/Table/index.ts @@ -15,20 +15,62 @@ */ export { Table } from './components/Table'; +export { TableRoot } from './components/TableRoot'; export { TableHeader } from './components/TableHeader'; export { TableBody } from './components/TableBody'; export { Column } from './components/Column'; export { Row } from './components/Row'; export { Cell } from './components/Cell'; +export { CellText } from './components/CellText'; export { CellProfile } from './components/CellProfile'; +export { TableBodySkeleton } from './components/TableBodySkeleton'; export { useTable } from './hooks/useTable'; -export type { CellProps, CellProfileProps, ColumnProps } from './types'; export type { - UseTableConfig, + CellOwnProps, + CellProps, + CellTextOwnProps, + CellTextProps, + CellProfileOwnProps, + CellProfileProps, + ColumnOwnProps, + ColumnProps, + RowOwnProps, + RowProps, + TableBodyOwnProps, + TableBodyProps, + TableHeaderOwnProps, + TableHeaderProps, + TableProps, + TableRootOwnProps, + TableRootProps, + TableItem, + ColumnConfig, + RowConfig, + RowRenderFn, + TableSelection, + SortState, + SortDescriptor, + NoPagination, + PagePagination, + TablePaginationType, + VirtualizedProp, +} from './types'; +export type { + UseTableOptions, UseTableResult, - UseTablePagination, - UseTablePaginationConfig, + UseTableCompleteOptions, + UseTableOffsetOptions, + UseTableCursorOptions, + OffsetParams, + OffsetResponse, + CursorParams, + CursorResponse, + FilterState, + SearchState, + QueryOptions, + PaginationOptions, + CompletePaginationOptions, } from './hooks/types'; export { TableDefinition } from './definition'; diff --git a/packages/ui/src/components/Table/mocked-data2.ts b/packages/ui/src/components/Table/mocked-data2.ts deleted file mode 100644 index 30ca36a689..0000000000 --- a/packages/ui/src/components/Table/mocked-data2.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2025 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { createElement } from 'react'; -import { RiCactusLine } from '@remixicon/react'; - -export interface DataProps { - name: string; - description?: string; - icon?: React.ReactNode; -} - -export const data: DataProps[] = [ - { - name: 'Cell with title only', - }, - { - name: 'Cell with title and description', - description: - 'A comprehensive service handling user authentication and role-based access control across all applications.', - }, - { - name: 'Cell with title and icon', - icon: createElement(RiCactusLine), - }, - { - name: 'Cell with title, description and icon', - - description: - 'A comprehensive service handling user authentication and role-based access control across all applications.', - icon: createElement(RiCactusLine), - }, -]; diff --git a/packages/ui/src/components/Table/stories/Table.dev.stories.tsx b/packages/ui/src/components/Table/stories/Table.dev.stories.tsx new file mode 100644 index 0000000000..24b8246c4d --- /dev/null +++ b/packages/ui/src/components/Table/stories/Table.dev.stories.tsx @@ -0,0 +1,1117 @@ +/* eslint-disable no-restricted-syntax */ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useState, Fragment } from 'react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { + Table, + TableRoot, + TableHeader, + TableBody, + Column, + Row, + CellText, + CellProfile, + useTable, + type ColumnConfig, +} from '..'; +import { Button } from '../../Button'; +import { Select } from '../../Select'; +import { Flex } from '../../Flex'; +import { data as data1 } from './mocked-data1'; +import { data as data4 } from './mocked-data4'; +import { selectionData, selectionColumns, tableStoriesMeta } from './utils'; +import { SearchField } from '../../SearchField'; + +const meta = { + title: 'Backstage UI/Table/dev', + ...tableStoriesMeta, +} satisfies Meta; + +export default meta; +type Story = StoryObj; +type Data1Item = (typeof data1)[0]; +type Data4Item = (typeof data4)[0]; + +export const BasicLocalData: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + defaultWidth: '4fr', + cell: item => ( + + ), + }, + { + id: 'owner', + label: 'Owner', + defaultWidth: '1fr', + cell: item => , + }, + { + id: 'type', + label: 'Type', + defaultWidth: '1fr', + cell: item => , + }, + { + id: 'lifecycle', + label: 'Lifecycle', + defaultWidth: '1fr', + cell: item => , + }, + ]; + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => data1, + paginationOptions: { pageSize: 5 }, + }); + + return ; + }, +}; + +export const Sorting: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + isSortable: true, + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + isSortable: true, + }, + { + id: 'type', + label: 'Type', + cell: item => , + isSortable: true, + }, + { + id: 'lifecycle', + label: 'Lifecycle', + cell: item => , + isSortable: true, + }, + ]; + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => data1, + paginationOptions: { pageSize: 5 }, + initialSort: { column: 'name', direction: 'ascending' }, + sortFn: (items, { column, direction }) => { + return [...items].sort((a, b) => { + let aVal: string; + let bVal: string; + if (column === 'name') { + aVal = a.name; + bVal = b.name; + } else if (column === 'owner') { + aVal = a.owner.name; + bVal = b.owner.name; + } else if (column === 'type') { + aVal = a.type; + bVal = b.type; + } else { + aVal = a.lifecycle; + bVal = b.lifecycle; + } + const cmp = aVal.localeCompare(bVal); + return direction === 'descending' ? -cmp : cmp; + }); + }, + }); + + return
    ; + }, +}; + +export const Search: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + isSortable: true, + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + { + id: 'type', + label: 'Type', + cell: item => , + }, + ]; + + const { tableProps, search } = useTable({ + mode: 'complete', + getData: () => data1, + paginationOptions: { pageSize: 5 }, + searchFn: (items, query) => { + const lowerQuery = query.toLowerCase(); + return items.filter( + item => + item.name.toLowerCase().includes(lowerQuery) || + item.owner.name.toLowerCase().includes(lowerQuery) || + item.type.toLowerCase().includes(lowerQuery), + ); + }, + }); + + return ( +
    + +
    No results found + ) : ( +
    No data available
    + ) + } + {...tableProps} + /> + + ); + }, +}; + +export const Selection: Story = { + render: () => { + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + { + id: 'type', + label: 'Type', + cell: item => , + }, + ]; + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => data1, + paginationOptions: { pageSize: 5 }, + }); + + return ( +
    + ); + }, +}; + +export const RowLinks: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Band name', + isRowHeader: true, + cell: item => , + }, + { + id: 'genre', + label: 'Genre', + cell: item => , + }, + { + id: 'yearFormed', + label: 'Year formed', + cell: item => , + }, + ]; + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => data4, + paginationOptions: { pageSize: 5 }, + }); + + return ( +
    `/bands/${item.id}` }} + /> + ); + }, +}; + +export const Reload: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'type', + label: 'Type', + cell: item => , + }, + ]; + + const { tableProps, reload } = useTable({ + mode: 'complete', + getData: () => data1, + paginationOptions: { pageSize: 5 }, + }); + + return ( +
    + +
    + + ); + }, +}; + +export const ServerSidePaginationOffset: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + { + id: 'type', + label: 'Type', + cell: item => , + }, + ]; + + const { tableProps } = useTable({ + mode: 'offset', + getData: async ({ offset, pageSize }) => { + await new Promise(resolve => setTimeout(resolve, 500)); + return { + data: data1.slice(offset, offset + pageSize), + totalCount: data1.length, + }; + }, + paginationOptions: { pageSize: 5 }, + }); + + return
    ; + }, +}; + +export const ServerSidePaginationCursor: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Band name', + isRowHeader: true, + cell: item => , + }, + { + id: 'genre', + label: 'Genre', + cell: item => , + }, + ]; + + const { tableProps } = useTable({ + mode: 'cursor', + getData: async ({ cursor, pageSize }) => { + await new Promise(resolve => setTimeout(resolve, 500)); + const startIndex = cursor ? parseInt(cursor, 10) : 0; + const nextIndex = startIndex + pageSize; + return { + data: data4.slice(startIndex, nextIndex), + totalCount: data4.length, + nextCursor: nextIndex < data4.length ? String(nextIndex) : undefined, + prevCursor: + startIndex > 0 + ? String(Math.max(0, startIndex - pageSize)) + : undefined, + }; + }, + paginationOptions: { pageSize: 5 }, + }); + + return
    ; + }, +}; + +export const CustomRowRender: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'type', + label: 'Type', + cell: item => , + }, + { + id: 'lifecycle', + label: 'Lifecycle', + cell: item => , + }, + ]; + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => data1, + paginationOptions: { pageSize: 5 }, + }); + + return ( +
    ( + + {column => ( + + {column.id === 'name' ? ( + + ) : ( + column.cell(item) + )} + + )} + + )} + /> + ); + }, +}; + +export const AtomicComponents: Story = { + render: () => { + const displayData = data1.slice(0, 5); + + return ( + + + Name + Owner + Type + + + {displayData.map(item => ( + + + + + + ))} + + + ); + }, +}; + +export const RowClick: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Band name', + isRowHeader: true, + cell: item => ( + + ), + }, + { + id: 'genre', + label: 'Genre', + cell: item => , + }, + { + id: 'yearFormed', + label: 'Year formed', + cell: item => , + }, + { + id: 'albums', + label: 'Albums', + cell: item => , + }, + ]; + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => data4, + paginationOptions: { pageSize: 5 }, + }); + + return ( +
    alert(`Clicked: ${item.name}`) }} + /> + ); + }, +}; + +export const SelectionSingleToggle: Story = { + render: () => { + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => selectionData, + paginationOptions: { pageSize: 10 }, + }); + + return ( +
    + ); + }, +}; + +export const SelectionMultiToggle: Story = { + render: () => { + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => selectionData, + paginationOptions: { pageSize: 10 }, + }); + + return ( +
    + ); + }, +}; + +export const SelectionWithRowClick: Story = { + render: () => { + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => selectionData, + paginationOptions: { pageSize: 10 }, + }); + + return ( +
    alert(`Clicked: ${item.name}`) }} + /> + ); + }, +}; + +export const SelectionWithRowLinks: Story = { + render: () => { + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => selectionData, + paginationOptions: { pageSize: 10 }, + }); + + return ( +
    `/items/${item.id}` }} + /> + ); + }, +}; + +export const SelectionWithPagination: Story = { + render: () => { + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => data1, + paginationOptions: { pageSize: 5 }, + }); + + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + { + id: 'type', + label: 'Type', + cell: item => , + }, + ]; + + return ( +
    + ); + }, +}; + +export const SelectionSingleReplace: Story = { + render: () => { + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => selectionData, + paginationOptions: { pageSize: 10 }, + }); + + return ( +
    + ); + }, +}; + +export const SelectionMultiReplace: Story = { + render: () => { + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => selectionData, + paginationOptions: { pageSize: 10 }, + }); + + return ( +
    + ); + }, +}; + +export const SelectionReplaceWithRowClick: Story = { + render: () => { + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => selectionData, + paginationOptions: { pageSize: 10 }, + }); + + return ( +
    alert(`Opening ${item.name}`) }} + /> + ); + }, +}; + +export const SelectionReplaceWithRowLinks: Story = { + render: () => { + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => selectionData, + paginationOptions: { pageSize: 10 }, + }); + + return ( +
    `/items/${item.id}` }} + /> + ); + }, +}; + +export const VirtualizedTable: Story = { + render: () => { + const largeData = Array.from({ length: 500 }, (_, i) => ({ + id: String(i), + name: `Service ${i}`, + owner: { name: `Team ${i % 10}` }, + type: ['service', 'website', 'library'][i % 3], + lifecycle: ['production', 'experimental'][i % 2], + description: `Description for service ${i}`, + })); + + const columns: ColumnConfig<(typeof largeData)[0]>[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => ( + + ), + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + { + id: 'type', + label: 'Type', + cell: item => , + }, + ]; + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => largeData, + paginationOptions: { pageSize: 50 }, + }); + + return ( +
    + ); + }, +}; + +export const VirtualizedWithCustomRowHeight: Story = { + render: () => { + const largeData = Array.from({ length: 500 }, (_, i) => ({ + id: String(i), + name: `Service ${i}`, + owner: { name: `Team ${i % 10}` }, + type: ['service', 'website', 'library'][i % 3], + lifecycle: ['production', 'experimental'][i % 2], + description: `Description for service ${i}`, + })); + + const columns: ColumnConfig<(typeof largeData)[0]>[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => ( + + ), + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + { + id: 'type', + label: 'Type', + cell: item => , + }, + ]; + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => largeData, + paginationOptions: { pageSize: 50 }, + }); + + return ( +
    + ); + }, +}; + +export const VirtualizedWithEstimatedRowHeight: Story = { + render: () => { + const largeData = Array.from({ length: 500 }, (_, i) => ({ + id: String(i), + name: `Service ${i}`, + owner: { name: `Team ${i % 10}` }, + type: ['service', 'website', 'library'][i % 3], + lifecycle: ['production', 'experimental'][i % 2], + description: + i % 5 === 0 + ? `This is a much longer description for service ${i} that spans multiple lines to demonstrate variable height row rendering in the virtualized table` + : `Description for service ${i}`, + })); + + const columns: ColumnConfig<(typeof largeData)[0]>[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => ( + + ), + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + { + id: 'type', + label: 'Type', + cell: item => , + }, + ]; + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => largeData, + paginationOptions: { pageSize: 50 }, + }); + + return ( +
    + ); + }, +}; + +// Type filter interface for ComprehensiveServerSide story +interface TypeFilter { + type: string | null; +} + +/** + * Comprehensive example showcasing a common complex use case: + * - Server-side offset pagination + * - Search/filtering + * - Sorting + * - Multi-selection + * - Type filter dropdown + */ +export const ComprehensiveServerSide: Story = { + render: () => { + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const typeOptions = [ + { value: '', label: 'All types' }, + { value: 'service', label: 'Service' }, + { value: 'website', label: 'Website' }, + { value: 'library', label: 'Library' }, + { value: 'documentation', label: 'Documentation' }, + { value: 'other', label: 'Other' }, + ]; + + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + isSortable: true, + cell: item => ( + + ), + }, + { + id: 'owner', + label: 'Owner', + isSortable: true, + cell: item => , + }, + { + id: 'type', + label: 'Type', + isSortable: true, + cell: item => , + }, + { + id: 'lifecycle', + label: 'Lifecycle', + isSortable: true, + cell: item => , + }, + ]; + + const { tableProps, search, filter } = useTable({ + mode: 'offset', + initialSort: { column: 'name', direction: 'ascending' }, + getData: async ({ + offset, + pageSize, + sort, + filter: typeFilter, + search: searchQuery, + }) => { + // Simulate server-side filtering, sorting, and pagination + // with slower and slower responses + const page = Math.floor(offset / pageSize) + 1; + await new Promise(resolve => setTimeout(resolve, 300 * page)); + + let filtered = [...data1]; + + // Apply search filter + if (searchQuery) { + const query = searchQuery.toLowerCase(); + filtered = filtered.filter( + item => + item.name.toLowerCase().includes(query) || + item.owner.name.toLowerCase().includes(query) || + item.description?.toLowerCase().includes(query), + ); + } + + // Apply type filter + if (typeFilter?.type) { + filtered = filtered.filter(item => item.type === typeFilter.type); + } + + // Apply sorting + if (sort) { + filtered.sort((a, b) => { + let aVal: string; + let bVal: string; + switch (sort.column) { + case 'owner': + aVal = a.owner.name; + bVal = b.owner.name; + break; + case 'type': + aVal = a.type; + bVal = b.type; + break; + case 'lifecycle': + aVal = a.lifecycle; + bVal = b.lifecycle; + break; + default: + aVal = a.name; + bVal = b.name; + } + const cmp = aVal.localeCompare(bVal); + return sort.direction === 'descending' ? -cmp : cmp; + }); + } + + return { + data: filtered.slice(offset, offset + pageSize), + totalCount: filtered.length, + }; + }, + paginationOptions: { pageSize: 10 }, + }); + + return ( + + + +
    No results match your filters + ) : ( +
    No data available
    + ) + } + /> + + ); + }, +}; diff --git a/packages/ui/src/components/Table/stories/Table.visual.stories.tsx b/packages/ui/src/components/Table/stories/Table.visual.stories.tsx new file mode 100644 index 0000000000..23810660f1 --- /dev/null +++ b/packages/ui/src/components/Table/stories/Table.visual.stories.tsx @@ -0,0 +1,389 @@ +/* eslint-disable no-restricted-syntax */ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useState } from 'react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { Table, CellText, CellProfile, useTable, type ColumnConfig } from '..'; +import { data as data1 } from './mocked-data1'; +import { data as data4 } from './mocked-data4'; +import { selectionData, selectionColumns, tableStoriesMeta } from './utils'; + +const meta = { + title: 'Backstage UI/Table/visual', + ...tableStoriesMeta, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +type Data1Item = (typeof data1)[0]; +type Data4Item = (typeof data4)[0]; +type CellTextVariantsItem = (typeof cellTextVariantsData)[0]; + +export const ProfileCells: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Band name', + isRowHeader: true, + cell: item => ( + + ), + }, + { + id: 'genre', + label: 'Genre', + cell: item => , + }, + { + id: 'yearFormed', + label: 'Year formed', + cell: item => , + }, + { + id: 'albums', + label: 'Albums', + cell: item => , + }, + ]; + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => data4, + paginationOptions: { pageSize: 5 }, + }); + + return
    ; + }, +}; + +export const EmptyState: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'type', + label: 'Type', + cell: item => , + }, + ]; + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => [], + paginationOptions: { pageSize: 5 }, + }); + + return ( +
    No data available} + /> + ); + }, +}; + +export const NoPagination: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner of the component or service in the organization', + defaultWidth: 120, + cell: item => , + }, + { + id: 'type', + label: 'Type', + cell: item => , + }, + ]; + + return ( +
    + ); + }, +}; + +export const SelectionWithDisabledRows: Story = { + render: () => { + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => selectionData, + paginationOptions: { pageSize: 10 }, + }); + + return ( +
    item.id === 2, + }} + /> + ); + }, +}; + +// Data for CellTextVariants story showcasing multiple features +const cellTextVariantsData = [ + { + id: 1, + name: 'Authentication Service', + description: 'Handles user login and session management', + type: 'service', + owner: 'Platform Team', + }, + { + id: 2, + name: 'A very long component name that should be truncated when it exceeds the available column width', + description: + 'This is also a very long description that demonstrates text truncation behavior in the table cells', + type: 'library', + owner: 'Frontend Team', + }, + { + id: 3, + name: 'API Gateway', + description: 'Routes and validates API requests', + type: 'service', + owner: 'Backend Team', + }, +]; + +export const CellTextVariants: Story = { + render: () => { + const [selected, setSelected] = useState | 'all'>( + new Set(['1', '3']), + ); + const [sortDescriptor, setSortDescriptor] = useState<{ + column: string; + direction: 'ascending' | 'descending'; + }>({ column: 'name', direction: 'ascending' }); + + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + isSortable: true, + cell: item => ( + + ), + }, + { + id: 'type', + label: 'Type', + isSortable: true, + cell: item => ( + 📦} + /> + ), + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + ]; + + return ( +
    + setSortDescriptor({ + column: String(descriptor.column), + direction: descriptor.direction, + }), + }} + /> + ); + }, +}; + +export const LoadingState: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'type', + label: 'Type', + cell: item => , + }, + ]; + + return ( +
    + ); + }, +}; + +export const ErrorState: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'type', + label: 'Type', + cell: item => , + }, + ]; + + return ( +
    + ); + }, +}; + +export const StaleState: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + { + id: 'type', + label: 'Type', + cell: item => , + }, + ]; + + return ( +
    + ); + }, +}; + +export const CustomPageSizeOptions: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + { + id: 'type', + label: 'Type', + cell: item => , + }, + ]; + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => data1, + paginationOptions: { + pageSize: 3, + pageSizeOptions: [ + { label: '2 per page', value: 2 }, + { label: '3 per page', value: 3 }, + { label: '5 per page', value: 5 }, + { label: '7 per page', value: 7 }, + ], + onPageSizeChange: size => { + console.log('Page size changed to:', size); + }, + onNextPage: () => { + console.log('Navigated to next page'); + }, + onPreviousPage: () => { + console.log('Navigated to previous page'); + }, + }, + }); + + return
    ; + }, +}; diff --git a/packages/ui/src/components/Table/mocked-data1.ts b/packages/ui/src/components/Table/stories/mocked-data1.ts similarity index 93% rename from packages/ui/src/components/Table/mocked-data1.ts rename to packages/ui/src/components/Table/stories/mocked-data1.ts index ea5a9f2db8..b508bce387 100644 --- a/packages/ui/src/components/Table/mocked-data1.ts +++ b/packages/ui/src/components/Table/stories/mocked-data1.ts @@ -15,6 +15,7 @@ */ export interface DataProps { + id: string; name: string; owner: { name: string; @@ -29,6 +30,7 @@ export interface DataProps { export const data: DataProps[] = [ { + id: 'authentication-and-authorization-service', name: 'authentication-and-authorization-service', owner: { name: 'security-team', @@ -42,6 +44,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'user-interface-dashboard-and-analytics-platform', name: 'user-interface-dashboard-and-analytics-platform', owner: { name: 'frontend-team', @@ -55,6 +58,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'payment-gateway', name: 'payment-gateway', owner: { name: 'finance-team', @@ -68,6 +72,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'real-time-analytics-processing-and-visualization-engine', name: 'real-time-analytics-processing-and-visualization-engine', owner: { name: 'data-team', @@ -81,6 +86,7 @@ export const data: DataProps[] = [ lifecycle: 'experimental', }, { + id: 'notification-center', name: 'notification-center', owner: { name: 'platform-team', @@ -94,6 +100,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'administrative-control-panel-and-user-management-interface', name: 'administrative-control-panel-and-user-management-interface', owner: { name: 'frontend-team', @@ -107,6 +114,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'search-indexer', name: 'search-indexer', owner: { name: 'search-team', @@ -120,6 +128,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'cross-platform-mobile-application-framework', name: 'cross-platform-mobile-application-framework', owner: { name: 'mobile-team', @@ -133,6 +142,7 @@ export const data: DataProps[] = [ lifecycle: 'experimental', }, { + id: 'database-migration', name: 'database-migration', owner: { name: 'devops-team', @@ -146,6 +156,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'api-gateway', name: 'api-gateway', owner: { name: 'platform-team', @@ -159,6 +170,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'content-management', name: 'content-management', owner: { name: 'content-team', @@ -172,6 +184,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'enterprise-reporting-and-analytics-dashboard', name: 'enterprise-reporting-and-analytics-dashboard', owner: { name: 'analytics-team', @@ -185,6 +198,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'image-processing-and-optimization-service', name: 'image-processing-and-optimization-service', owner: { name: 'media-team', @@ -198,6 +212,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'customer-portal', name: 'customer-portal', owner: { name: 'frontend-team', @@ -211,6 +226,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'log-aggregator', name: 'log-aggregator', owner: { name: 'devops-team', @@ -224,6 +240,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'identity-provider', name: 'identity-provider', owner: { name: 'security-team', @@ -237,6 +254,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'document-storage', name: 'document-storage', owner: { name: 'storage-team', @@ -250,6 +268,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'workflow-engine', name: 'workflow-engine', owner: { name: 'platform-team', @@ -263,6 +282,7 @@ export const data: DataProps[] = [ lifecycle: 'experimental', }, { + id: 'mobile-backend', name: 'mobile-backend', owner: { name: 'mobile-team', @@ -276,6 +296,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'system-monitoring-and-alerting-dashboard', name: 'system-monitoring-and-alerting-dashboard', owner: { name: 'devops-team', @@ -289,6 +310,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'email-service', name: 'email-service', owner: { name: 'communication-team', @@ -302,6 +324,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'data-pipeline', name: 'data-pipeline', owner: { name: 'data-team', @@ -315,6 +338,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'configuration-manager', name: 'configuration-manager', owner: { name: 'platform-team', @@ -328,6 +352,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'testing-framework', name: 'testing-framework', owner: { name: 'qa-team', @@ -341,6 +366,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'cache-service', name: 'cache-service', owner: { name: 'platform-team', @@ -354,6 +380,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'billing-system', name: 'billing-system', owner: { name: 'finance-team', @@ -367,6 +394,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'comprehensive-product-documentation-and-api-reference', name: 'comprehensive-product-documentation-and-api-reference', owner: { name: 'docs-team', @@ -380,6 +408,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'queue-manager', name: 'queue-manager', owner: { name: 'platform-team', @@ -393,6 +422,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'security-scanner', name: 'security-scanner', owner: { name: 'security-team', @@ -406,6 +436,7 @@ export const data: DataProps[] = [ lifecycle: 'experimental', }, { + id: 'user-profile', name: 'user-profile', owner: { name: 'frontend-team', @@ -419,6 +450,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'data-warehouse', name: 'data-warehouse', owner: { name: 'data-team', @@ -432,6 +464,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'deployment-automation', name: 'deployment-automation', owner: { name: 'devops-team', @@ -445,6 +478,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'chat-service', name: 'chat-service', owner: { name: 'communication-team', @@ -458,6 +492,7 @@ export const data: DataProps[] = [ lifecycle: 'experimental', }, { + id: 'analytics-dashboard', name: 'analytics-dashboard', owner: { name: 'analytics-team', @@ -471,6 +506,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'file-uploader', name: 'file-uploader', owner: { name: 'storage-team', @@ -484,6 +520,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'search-service', name: 'search-service', owner: { name: 'search-team', @@ -497,6 +534,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'mobile-sdk', name: 'mobile-sdk', owner: { name: 'mobile-team', @@ -510,6 +548,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'performance-monitor', name: 'performance-monitor', owner: { name: 'devops-team', @@ -523,6 +562,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'content-delivery', name: 'content-delivery', owner: { name: 'media-team', @@ -536,6 +576,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'user-authentication', name: 'user-authentication', owner: { name: 'security-team', @@ -549,6 +590,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'data-export', name: 'data-export', owner: { name: 'data-team', @@ -562,6 +604,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'admin-api', name: 'admin-api', owner: { name: 'platform-team', @@ -575,6 +618,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'testing-dashboard', name: 'testing-dashboard', owner: { name: 'qa-team', @@ -587,6 +631,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'message-broker', name: 'message-broker', owner: { name: 'platform-team', @@ -600,6 +645,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'payment-processor', name: 'payment-processor', owner: { name: 'finance-team', @@ -613,6 +659,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'document-viewer', name: 'document-viewer', owner: { name: 'frontend-team', @@ -625,6 +672,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'load-balancer', name: 'load-balancer', owner: { name: 'devops-team', @@ -638,6 +686,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'security-audit', name: 'security-audit', owner: { name: 'security-team', @@ -651,6 +700,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'user-settings', name: 'user-settings', owner: { name: 'frontend-team', @@ -664,6 +714,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'data-import', name: 'data-import', owner: { name: 'data-team', @@ -677,6 +728,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'infrastructure-monitor', name: 'infrastructure-monitor', owner: { name: 'devops-team', @@ -690,6 +742,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'notification-manager', name: 'notification-manager', owner: { name: 'communication-team', @@ -703,6 +756,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'analytics-processor', name: 'analytics-processor', owner: { name: 'analytics-team', @@ -716,6 +770,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'file-manager', name: 'file-manager', owner: { name: 'storage-team', @@ -728,6 +783,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'search-index', name: 'search-index', owner: { name: 'search-team', @@ -740,6 +796,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'mobile-authentication', name: 'mobile-authentication', owner: { name: 'mobile-team', @@ -753,6 +810,7 @@ export const data: DataProps[] = [ lifecycle: 'experimental', }, { + id: 'system-monitor', name: 'system-monitor', owner: { name: 'devops-team', @@ -766,6 +824,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'media-processor', name: 'media-processor', owner: { name: 'media-team', @@ -778,6 +837,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'user-management', name: 'user-management', owner: { name: 'security-team', @@ -790,6 +850,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'data-transformer', name: 'data-transformer', owner: { name: 'data-team', @@ -803,6 +864,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'admin-dashboard', name: 'admin-dashboard', owner: { name: 'platform-team', @@ -816,6 +878,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'test-automation', name: 'test-automation', owner: { name: 'qa-team', @@ -828,6 +891,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'event-bus', name: 'event-bus', owner: { name: 'platform-team', @@ -840,6 +904,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'invoice-generator', name: 'invoice-generator', owner: { name: 'finance-team', @@ -852,6 +917,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'document-editor', name: 'document-editor', owner: { name: 'frontend-team', @@ -864,6 +930,7 @@ export const data: DataProps[] = [ lifecycle: 'experimental', }, { + id: 'service-discovery', name: 'service-discovery', owner: { name: 'devops-team', @@ -876,6 +943,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'security-monitor', name: 'security-monitor', owner: { name: 'security-team', @@ -888,6 +956,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'user-preferences', name: 'user-preferences', owner: { name: 'frontend-team', @@ -900,6 +969,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'data-validator', name: 'data-validator', owner: { name: 'data-team', @@ -912,6 +982,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'infrastructure-automation', name: 'infrastructure-automation', owner: { name: 'devops-team', @@ -925,6 +996,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'notification-dispatcher', name: 'notification-dispatcher', owner: { name: 'communication-team', @@ -938,6 +1010,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'analytics-collector', name: 'analytics-collector', owner: { name: 'analytics-team', @@ -950,6 +1023,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'file-processor', name: 'file-processor', owner: { name: 'storage-team', @@ -962,6 +1036,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'search-analyzer', name: 'search-analyzer', owner: { name: 'search-team', @@ -974,6 +1049,7 @@ export const data: DataProps[] = [ lifecycle: 'experimental', }, { + id: 'mobile-notifications', name: 'mobile-notifications', owner: { name: 'mobile-team', @@ -986,6 +1062,7 @@ export const data: DataProps[] = [ lifecycle: 'experimental', }, { + id: 'system-alerts', name: 'system-alerts', owner: { name: 'devops-team', @@ -998,6 +1075,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'media-encoder', name: 'media-encoder', owner: { name: 'media-team', @@ -1010,6 +1088,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'user-authorization', name: 'user-authorization', owner: { name: 'security-team', @@ -1022,6 +1101,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'data-aggregator', name: 'data-aggregator', owner: { name: 'data-team', @@ -1034,6 +1114,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'admin-authentication', name: 'admin-authentication', owner: { name: 'platform-team', @@ -1046,6 +1127,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'test-coverage', name: 'test-coverage', owner: { name: 'qa-team', @@ -1058,6 +1140,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'event-processor', name: 'event-processor', owner: { name: 'platform-team', @@ -1070,6 +1153,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'payment-validator', name: 'payment-validator', owner: { name: 'finance-team', @@ -1082,6 +1166,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'document-converter', name: 'document-converter', owner: { name: 'frontend-team', @@ -1094,6 +1179,7 @@ export const data: DataProps[] = [ lifecycle: 'experimental', }, { + id: 'service-health', name: 'service-health', owner: { name: 'devops-team', @@ -1106,6 +1192,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'security-logger', name: 'security-logger', owner: { name: 'security-team', @@ -1118,6 +1205,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'user-analytics', name: 'user-analytics', owner: { name: 'frontend-team', @@ -1131,6 +1219,7 @@ export const data: DataProps[] = [ lifecycle: 'experimental', }, { + id: 'data-cleaner', name: 'data-cleaner', owner: { name: 'data-team', @@ -1143,6 +1232,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'infrastructure-deployer', name: 'infrastructure-deployer', owner: { name: 'devops-team', @@ -1155,6 +1245,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'notification-queue', name: 'notification-queue', owner: { name: 'communication-team', @@ -1167,6 +1258,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'analytics-exporter', name: 'analytics-exporter', owner: { name: 'analytics-team', @@ -1179,6 +1271,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'file-validator', name: 'file-validator', owner: { name: 'storage-team', @@ -1191,6 +1284,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'search-optimizer', name: 'search-optimizer', owner: { name: 'search-team', @@ -1203,6 +1297,7 @@ export const data: DataProps[] = [ lifecycle: 'experimental', }, { + id: 'mobile-analytics', name: 'mobile-analytics', owner: { name: 'mobile-team', @@ -1215,6 +1310,7 @@ export const data: DataProps[] = [ lifecycle: 'experimental', }, { + id: 'system-logger', name: 'system-logger', owner: { name: 'devops-team', @@ -1227,6 +1323,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'media-validator', name: 'media-validator', owner: { name: 'media-team', @@ -1239,6 +1336,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'user-audit', name: 'user-audit', owner: { name: 'security-team', @@ -1251,6 +1349,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'data-normalizer', name: 'data-normalizer', owner: { name: 'data-team', @@ -1263,6 +1362,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'admin-authorization', name: 'admin-authorization', owner: { name: 'platform-team', @@ -1275,6 +1375,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'test-reporting', name: 'test-reporting', owner: { name: 'qa-team', diff --git a/packages/ui/src/components/Table/stories/mocked-data2.ts b/packages/ui/src/components/Table/stories/mocked-data2.ts new file mode 100644 index 0000000000..0aff7bcd98 --- /dev/null +++ b/packages/ui/src/components/Table/stories/mocked-data2.ts @@ -0,0 +1,87 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createElement } from 'react'; +import { RiCactusLine } from '@remixicon/react'; + +export interface DataProps { + name: string; + description?: string; + icon?: React.ReactNode; + href?: string; +} + +export const data: DataProps[] = [ + { + name: 'Cell with title only', + }, + { + name: 'Cell with title and description', + description: + 'A comprehensive service handling user authentication and role-based access control across all applications.', + }, + { + name: 'Cell with title and icon', + icon: createElement(RiCactusLine), + }, + { + name: 'Cell with title, description and icon', + + description: + 'A comprehensive service handling user authentication and role-based access control across all applications.', + icon: createElement(RiCactusLine), + }, + { + icon: createElement(RiCactusLine), + name: 'Cell with extremely long title that demonstrates text wrapping behavior and how the component handles content that extends well beyond typical cell widths in table layouts', + description: + 'This is a comprehensive and detailed description that contains a substantial amount of text to demonstrate how the CellText component handles lengthy content. It includes multiple sentences and covers various aspects of the service or component being described. The text should wrap appropriately within the cell boundaries while maintaining readability and proper spacing. This example helps ensure that the table remains visually consistent and user-friendly even when dealing with extensive content that might otherwise break the layout or cause accessibility issues. The component should gracefully handle such scenarios by providing appropriate text wrapping, maintaining proper line heights, and ensuring that all content remains accessible and readable regardless of its length.', + }, + { + name: 'Cell with title and URL', + href: '/catalog/default/service/auth-service', + }, + { + name: 'Cell with title, description and URL', + description: + 'A comprehensive service handling user authentication and role-based access control across all applications.', + href: '/catalog/default/service/auth-service', + }, + { + name: 'Cell with title, icon and URL', + icon: createElement(RiCactusLine), + href: '/catalog/default/service/auth-service', + }, + { + name: 'Cell with title, description, icon and URL', + description: + 'A comprehensive service handling user authentication and role-based access control across all applications.', + icon: createElement(RiCactusLine), + href: '/catalog/default/service/auth-service', + }, + { + icon: createElement(RiCactusLine), + name: 'Cell with extremely long title that demonstrates text wrapping behavior and how the component handles content that extends well beyond typical cell widths in table layouts', + description: + 'This is a comprehensive and detailed description that contains a substantial amount of text to demonstrate how the CellText component handles lengthy content. It includes multiple sentences and covers various aspects of the service or component being described. The text should wrap appropriately within the cell boundaries while maintaining readability and proper spacing. This example helps ensure that the table remains visually consistent and user-friendly even when dealing with extensive content that might otherwise break the layout or cause accessibility issues. The component should gracefully handle such scenarios by providing appropriate text wrapping, maintaining proper line heights, and ensuring that all content remains accessible and readable regardless of its length.', + href: '/catalog/default/service/auth-service', + }, + { + name: 'External link example', + description: 'This is an external link that opens in a new tab', + href: 'https://backstage.io', + }, +]; diff --git a/packages/ui/src/components/Table/mocked-data3.ts b/packages/ui/src/components/Table/stories/mocked-data3.ts similarity index 100% rename from packages/ui/src/components/Table/mocked-data3.ts rename to packages/ui/src/components/Table/stories/mocked-data3.ts diff --git a/packages/ui/src/components/Table/mocked-data4.ts b/packages/ui/src/components/Table/stories/mocked-data4.ts similarity index 99% rename from packages/ui/src/components/Table/mocked-data4.ts rename to packages/ui/src/components/Table/stories/mocked-data4.ts index b86d4c903f..5cedd2cbae 100644 --- a/packages/ui/src/components/Table/mocked-data4.ts +++ b/packages/ui/src/components/Table/stories/mocked-data4.ts @@ -15,6 +15,7 @@ */ export interface RockBandDataProps { + id: string; name: string; image: string; description: string; @@ -31,6 +32,7 @@ export interface RockBandDataProps { export const data: RockBandDataProps[] = [ { + id: 'The Beatles', name: 'The Beatles', image: 'https://upload.wikimedia.org/wikipedia/en/thumb/4/42/Beatles_-_Abbey_Road.jpg/250px-Beatles_-_Abbey_Road.jpg', @@ -57,6 +59,7 @@ export const data: RockBandDataProps[] = [ website: 'https://www.thebeatles.com', }, { + id: 'Led Zeppelin', name: 'Led Zeppelin', image: 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxMTEhUSExMWFRUXGBcYFxgYGBcXGBgXGBUYFhgYFhgYHSggGBolHRcXITEhJSkrLi4uGB8zODMtNygtLysBCgoKBQUFDgUFDisZExkrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrK//AABEIALwBDAMBIgACEQEDEQH/xAAcAAABBQEBAQAAAAAAAAAAAAAGAQIEBQcAAwj/xABCEAACAQIEBAQDBQYFBAEFAQABAhEAAwQSITEFBkFREyJhcTKBkQdCobHwFCNScsHRM2KC4fEkg5KiQxdTc6OyFf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwDZzS0gpaBK6urqDq6urqDq6krqBa6krqDjSVxrqDprqSuoFmuFJSigWaWkFdQdS0grqBa411JQcaQmuNV/FMVlUgHUg/Kg8+Kcat2d5J7Dt3J6CgXiX2nFLoVbQKGAGLFNZgk5htU7ia3C9uyozggEkgEmQRm1I2jodMwq24fyxYyL41pHYDqJGpkmDufU0HYTnFAiteAAZgilD4gk6DNG1FStQVxjhdq0gVhlsh1dGk/unVsyySdLZOnpMbHS14HxwXGyaHyzI1HyNARUtMFOoOpaSloENIwp1IaBa6urqDjSV1dQdSV1NJoFmummzXTQOmups0s0C00murzuXANaB80mas/5u+0uxhly2Ct+6egMqv8AOR19KAb/ANquPZgQ1tB/CEBB9y0mg37NSg1mvLP2rWLxS3iVNm4YGfQ2idt5lJ9RA71ottwdZoJE11eeanTQPpKQGloFrqSaaTQI7UL8UZ7twqNEUEluuYEZQo7DUyav8ZdgGgbGY4XRdt+IbfSREn/bSJ9aCnt8cvJde5cVFbNaQKznKLbZir5gCczHy6CdYANGvBOLXWA8ZbYVhKFPEB9mW4oI+cH0FZ7wJ7jYgC4ZHiIkn7xV7t4R1O2Yeh13FaPjQ0L4YUkEEyxUR1JIU9KCvxXBXvNcLvnVwR+8NwhQWMqltWVAMvWJmJmq7lu0bd5VyeGgQrl3lywOYHqIHyJb3orw+IUyuZSd8oIJH4z86EP2/NiblvT93d9hHhp17h80j1HpIaHhrkivcVU8KxIIg79R2q0BoHUtJS0HUhNdXRQLXV1JQcaSuNJNB01E4jjUs22uOYVRJqSxrNPtI4mbpOHRiAozN2LfdX8Z+QoKHmT7QMRec28OSqzpl/239++3QlmBw3GLdv8AaPGugAzkZ2JIjcqdI9Pwq0+zfhVhE8a5ke6zsqgssrl8p8p3Myd9BEAdT3H4lVGp37KWj1IUGB6nSgqeR+cRjEyuAt5PiXoY0zL/AG6UVrdFfN9jibYTGi6mkPJH+Q7r66Vu3D+IB1V1MhhI9qC7a7WZfbJx69ZtW7Vpiq3S6uwMGAF8oO+snUfwx1oz4zjclm4wmcpC5d8x0UCeskV8+80cZxN5hYxDqxsMy+UAeYHKSdPT060FITVry/y/fxj5LKgxGZmMKs9+p9gDUPhPD2xF1bKQGadTsIE6kdK2HlfhRwFpxoSVYkobgBI+9lZ2Ab1AG3yoM85v5QuYEW2JLo2jMBoH3AjoCJj2o1+yfm27cP7JdYMESbbHeAYyk9YG3tULnzh998O7sXyIM+t5rgYSCcyPOQjUgqemwnTPeFYtrbhlYqRsRvPSg+pUea9laqPgGJL2LRZgWKISRtmKifxq2VqCQDTpryBp4NA4mvN2pSa8bjUFNzFfXIVZsoI1PaszFyzbwl5XuFyZJZN7h+6oIGg9Onzo15o4rZC3EuifKTHcCAfzrL8JxbC2UvhJAurGQnNqCCGXSFKwdTQQsFxh7dxbuY5sNcLNaJMOpyWXZT/EoCAg9NRs1azwfjWExoUAqxWDkY6g9MyHSR3rEOJ4sXGzARmMvMeZtQDp6GfcmtK5G4Vh71pWa3bdlGudA2nY5pmgM+N8wYfBWWZmWVHltrGZmOygD5a9BrWT8Hxd/wAR7l7zLdJuFQ0f4p302AD9egoo+0kWksC2iKh3VUXKJJCzCiOtBXBsViLcMqgquYCQJYspABPWPK3+kUG38HxS5c/whjOu8DQfgKIsPckVlvK9u81xrjHLaMMqGSdAAzDspaSAe4o54NxIMPhI1iOo96C/paYhp1AtLSUtAlJTqaaBDTTSk01jQeOJuQpPpWScxXApu3mbLmMDX7ijOzR10P4VovM3EVs2WdjA0/E/2k/KsB5y4hcuuB/8YUZOxDksD81y/IUG2YLFWLGFth3ChUtlpkwzgHUDqSfxqEDbxGIuqtycqqDkdkZT7qQw9CPX51+C5lwYwqXFJ8RUgbBx8OYHMIIOVTqCDAPrVBzBxixaU4y04OIYgJoiEZRqGVAM0yJMaiKAU5ow+W9clizJedGY7nyZhPcwN+5o/wCQOJZsKoJ1Qlfl0/Csrs4trmdn8zPdLn1LW7gY6fzfhV9yZxXwwyk/rSPzP0oNS5lxwTC3LhjyZW120YHUQfbbrWB8Rv8AiXXeZzOzTrrLEzrrrWkcx4hsVhGtoTPlYgSZykHLHWdNO461l7CgJ/s+41awuJL3pylSJABg7++u3zo1v8Y4hfQ3USzYRklA5ctcRirL5x5dVn/MCdgNayKp1ji99MgW84Fv4BmJC+gU6R6UGhcyc3h8A9l7fhYhv3TWt4UqDnkwYKnqJk1m1kjMJMCRJ3gTrp1pL99nZndizMSWYmSSdyaaKD6Q5XwapbDKB5gDIEAyN46TqT6zRAKoOVOIW79hHtHywAPSAJHrG3uCOlXyig9lp001a4mgVjUTFv5TUkmvC+ukUGRcxcRuG4ywrqJbKfhJGaJH3ogwD1A3ql4lwSzcRbiIUzDMf4VAEkKOu/6g1ofE+XrbM8j4o/D/AJqoxfALp8NSwyJoCB5gNDrB1EjWgyhbAZoE5Zj1rUOTeEPaRWtvBI+8Mw19ARTuD8nAXCWiFJmRM6LB+ep9oo4wmECwBsAP+KAC5l5fvMRca9cueYEjKoVR3CqJb5k1R30gPbRXuWoU6+RkZSTIEHTzEQd962XwxXi/DbRbNkE9436UATyxZNxQrMdRAEnYAkAmNxP5UYcPwnhBQO+skknytqSfap+HwqJqqKvcgAflXnxHEpbALsFB2nqQCx/AGgn4a6c0dI1+pA/XpU8GqPB3wxLDbQT7CfrJP0qzw9/7p+VBLpaQUooENNNKahcQx6WhLddgN/8Aig9cTfVFLMYA/WnrWe89c1ObFy1ZDKWAh1kMokE6g+U6HbvTeZOPNdMAwBMDoNvqaEb1wtMmd567bigGuM8wYm6uW7ddx/mZm2/mPtVH4jvCST2BO3TSdhRRisJsQBG4Y/D6T2Hrt6jerLlTB4ZHS5i8Oly1cOXPJYI3lynytEHMfYQdtSE//wClmI8PTEoJE5HQ6EjUSGP1HahDm/lq5gjbF24rs+YgKDoBEyT7ivodro1Pb8omsa+0HjFrFOCFHh22Kh9nfMN0/wAmgMddD6AAKziisQBoSfckR+U/WiH7PeFjE4ko2qpbZwpJAd5CorEahTmO3b3oev213EgSAJ1O25rT/so4Kf2S9fH+JcaLZ9LJlZ7TczA+goJ3OGFY4VXwwVMmVskBf3Z006AqdwR/FWR47Ctbco4hhBOs7iRr863TjjCEuR+6ulAw/h8WEefQqxP8yetCHE+UlxNws1xrbrC3AFBnIsErJ0mBrrQD/LuHstaS04Du7m6Vg/4eUI6M3RjlFwAa/uukiZXFfs+a0j3hfTw1BMFWzR0Gkyx0FGvC+UbNi2PDktoczHWYIEwBpJmKUXmYxeAW3bcC2Bq125EiFHRJPzE6BaDJOKcKewLef4nWSNfKdDlPrDA/OvPhuDa7cW2ilmYwACB0k6nTp1rQMZyi2JutcuO6eKxcqCDlAGVIkQDGUewjoCLDgfKdvC3ZR2ZyCudoELoXygCJlrag+re1AW8gcMfD4S3auMrMC5OWcozOzZRIG0xtGlFKmqDgjhRk7Ex7EkwPQaiOgirpHoPeaQmmFq82ujvQSAaj3moM419p2DsFkXNeYaeQDLP87aH5UM437XJPkw5y9Qza/Uf2oD+83nPy96W0vUQR+torOcL9pSFpuW2UekGPfXX5UacL4vavIHQyDtoQfnIoLGw2VmUiIiNtQVGv1DD5VNtvVLxe9HhOBtcAY7EK3liBvLFNNuvSrOxcmgmA0tu2SNyD+u4pgbpUix8I+v1oFt2QNdSfUz9BsPkKoua+XmxeSLuQIHhcshmYAAsZkACR/qNEIr0VaCl4RFqbLCHXUn+Kdc49CZPvI6VazpTMfgM4BU5XX4W/NT/lOn0mq4cTCSt3yMIkN6mBB6gnqKC+wt/7p+R71KmqA4odNatcNiQV8xg0DOL4s2rRuCNCszsAWAJPsDWecZ4sWlmJnY+laXiEBBBEgiCKyzifKn/VpZGIdLNzNAgMysAWCI3Y6xmBiPWgE8fjiWCICWPQDMdTGw9qm4fl/FqGuXFyWlU5VkTJZYZguh6mSe2laZwXgdmwMiWwon1M+rMSSx96sOK4fPZuIBup/vQYoWI9+2leZJXJcRRNu5mgroWIUgHTY+F+fep2PtQxgfnXjgbIuP4Wvn3iSVyqWzDuQAw101Oo3AWHH+actj9ntMzZoMEzFsqDldt41mNzMHy7gWKZmMsZP5egHQVPvBDDAtJALkx8XULH3YiJqDiWiggXDr7VvnI2F8HAYdNj4Ydvd5uN+LGsDW0W8o3chV9zoPxNfR6IEXINguUR0gQKCm4tbDW72HJjOjZD2YhmUj1Dbfy164i0M5uxAdDmHYxp+cfKq3nS6bdpbw+4wk+nQ/WfrVt+0BrfplH6ig9cJflOxA27VEt4YZ5gljmAmNM2rR6Qv/se9ehgFSN9BvpBIG20yRSJdVrwUhlORmWZX7+Vtj6CPT50Eu6AuwkxHX8e1QMEQ965AMWYtz0Nxl8R8vsH19Y7VPuXcsNAIGrSYhRqW13jtSYDD5LQB+M5nf8Ancl217AmB6KKBtsw5jsP1+NW9m9pNDpvnNU3D4oxFBa3MVWd/anxe7+5wdtsgvSbjElQVBChWP8ABuW9FHSiC/j4eJqk5j4et+/Zus8KiXBllwWlkIAKCQIDT6DtNAG8d5VWzZzC4ouIMxBYnOvUrplX0HpQjW3eHcZllZQKRaAKxbYiCSFBDfX86yvm/g5w19hkyIxJQTsOqj0BP5UFLNHX2a8UIZrBOnxL/UfrvQGKteWcUbeJtttrB+Yj84oNyxFkXLTITGZSPaRuPak4FeLWkLfEVBb001+h0qFhsf5O57VX8vcSRQ6vcUXrl14QkSqZyEUD2gn1mgOMPqJ7n8NhUpRUSy20bDb5VLWgeKkrUW3UpTQegoV57u22W1YKhrjuGXuioQWYdp0X1k9qJr95UVnc5VUFmJ2AAkk/IVnPCL7Yu/cxTA+YwojRbY0VTrv39Se9AUcNtwoM9KtbdhmEgabVEwNjMwXtq39vc1foABA2oEcUIc3JkC3hvadX+SsCY+Q/GjA1W8Yso1tg4kEHTqfQTQOuoD5h/wAivPEN5GMxode2m9VnKuM/d/szyLlpRAYam1sp7Er8BjsN5BM7iVwKjMSIg77GdAPmYHzoMr42hBPlOpJ9Iih5mIIMAnTcAjfsdJoq4phgAcp8saDeOunYabUL4xhqDp7dNd9qCDxTOrZXQIQF8oED4QQTGkkEEnuTVFibpqfiXWYTMVAGrKFMkCdNes1W4jvQX3I2A8XG4VYkK3it7W5uL/7BB863PF4dyv7sqG7MCQT7g6fjQ1ynyxbwqC/ZJN65Ztgi4QVBgMwQgArJjedhVxhuKJcIBORx8SMYdT2I6jsRoaAb5gxrXMPibN63lZbbbSRKjMNx6Aio3J2PN20M2oWBPcif9q9uc7muIU7tbOU9SCka99QfrVHy3jLduyqsSInodaA3fzPbj4VbMfWFOWfnB+QpLd/96zCJACDtMlj77ih/D4+9fRzZT92cyhsxVmOxKkfDBkT71ZcKRrazeBL6gAGQxmZAn9RQSOLYoJ4aEy967bTX+Gc7COgKIwqxv4omVUZmjpsB3YnT+tZXzNzIRi7RGosXA7dZOgYD5SPnWg47iioMudA5+6WURP3m13oPRrMCZk9fWa8bWKAJBNQcLxBQ+UMGVo2OYZge47iqLmDjARzlNBacUx4FwQfpVNxdP2m9ZSFcKGm2zZVbMVElsrBTEicp3NDmN4z9ajcG4qqXc10ZgdJ1OX101oNV5P4TdwwuBiPCIXJbztc8JhOYB2UEqZ/Cs9+03F+JjIj4EUfUlv60V43m8WcNmQrcJ0GUkiTPxem1ZljMU1241xzLMZP9PlQRJp6XCCCNxB+lNakoNW5Yxy3kzZoYgSp6mIJXvVqeB2bhlhmPykf71mvL3EnSESJkkT+IPejfC4nEnzeGI7qcw3/h+L6A0Fth+H4qwf3Fw5f4XOZfoduuxq2tcw31Ui5hiXAMZSAGMaDXQCfXSqvD8ZdIDqf7+mVgG/Cp1njNljBgbbgr89aCVY5sRR+9VlMAkBLmk6aGIb3U1f3uMWLaq73VCsQqkSxYnYAKCT9NOtVOCxdh9FZT6ZlP9amXcGhGqj5/regHuYuLjG3v2Oy/7pSPFZTOdgZyAj7gMT3PoNb7h/DxaRURdf1rUSzwayrZwvm/i1zD2JP5UQcGuFlJKt/MVKzBjYgdtxoaCZgsPkWOu5Pc1JFNFOFA014XbQJn0j+ule9NNAKcewFwMl+wVW7bn4vhZW0ZHjUqdPYgHpQxzXzOL9r9ju2GtX7jaqScpFuX8SxeAhiHW3uARJMaVomLAkA9dB79qDeab4Ft2bDftFq2Zu29r9ob+JabZl0kQRsdZUgBn+H4xIZWYGNCSMreoddg3quhImBVbj7hzSNuka+tTeN4SyQuLw19bttWUFbhW3fTNMI4YDOp6HrG53qpxOLcg6BgeogkfIRH5UEfHsxbzlZhdEyx8Ag+TSYj51U4oyrH0NWN/DlSpKlAVBOYr3IJ8vwiR8JEiK8+E4fxL9q2R5bl60nuGuqp/A0H0Fh3EJMLKiAYGpAge/SKj8XQBczKDGxYAr7E6ZT06b1D5yN39lveCoa5kbKuXNM6EBCCGOWYWNag8ENq9ZS7YvX7OYee0HNwI+zoVuq+WCCIEdO9BT8YdMQz21bwrqrlyOPKc2iFLm2p+6SD6daDcFgrpmXYDqBbBIiZHmmKvudsO9gm/afK4QL5ZTyscpyptImTBj4TAgznpxVyCPEcf6m/vrQaxwu02HtW5IS2tsl80A52hsxYbQSwiOtQ+K8yeIMti9YCkHPd8VAyjsoIOXfeZ30FZ3juKXLpYFmCEyLZdmUdgMxqCaC8CWXxCIhlA652118wDHMd+uvWtQXjZtOoCqLTkgHKUYPuBkgZs2sHrH1x7hX+JHcR9WFbHcw7C35VAYZIYk9GElvlOg9utBR8538gDoCs9IykEkT5TrHyoMym55p0kLPqWA/rNXnNElGOUAMyLmGn3gIK/d2JionA+GFrQIOp1K+qkH2BgR7xQQOKctvbGYNmMarB/A9a9eXeXS48e8wt2l18w0ZYIMmfIPU1od7l13BZLmXPuGGYHSOhBG3eq+3hLuHBXEAtaMDOsshHZ7cSu280HpwLCgZ7NxRnt/ECBDp9y4NNZGhjqCKEOdb9lCMPaQAhizkAAeYaARvp9KPsfbDWwRJYwEZIzjNoHTTza6lOonfQUNpyTce8t7GMi5iGKWzmBiBlZtInbT11oM+t4VmRnAkIRm7jNMGO2kV4CtVvco2lvM6TbUrlKiChkRAUqc07mZk7a1T8L5HW7b8VmNt80ZEYELBgyTJB30n6bUAzwqwqkG4vlJgHNlAPow2NaVy5j7RK2i03DOVxKh4EkEbZgO41iYr34RwFLByICzvvOuW3J8zNuFkNoIzEr0oT5vt3sFiB4eUWn89s5ElG6qGiQQdQR0PoaDVrFto/xCB7LPygVHx3B7V8BbigrmkLAInKwlgZDfFOs6gHpQZytzsjEJigoOwuiQv/AHF+5/MNN9q0PxBAA+Ua/QDegjjhFtVCqqBRoPux7RpUjAcHWSVZ0jQLmlCO5ToZnYiq/H8zYa02Rjnuf/btjxHB7NlkJ00Jq65cx4voXCZCDlKkox9zkJjrQe2G4Y33yInZZ1Gu50irW2gAAGgFItPFAopRXV00CUhpTSGgrOMt5MobI7SLbdnymD9YrMsFzDcFlTdn9osrCNBPjps1i9HUxpc1WQGncMVc+JduRbt2ncASSpUan+fRumlZRxzCXrJzMGtj/Pbu2lJPa4pNon3oIXFbai9dOHz27JabaSMyAqJUrJGXMWgTERVa7SZZbbR3UK31iKk3MYQAXUr6nzIf9S7H2+lNbFr8TCR7Bh/5D8jBoK+86tAyqkD7uxMnU9zrv6CrTk+yf2/CCZ/fI3/ic5/AGqq54ZmJHbWf6Cin7L8Nnx6tEi1bd56AkC2J9YdvpQH/ANofF2w+Hz22y3AylTvqD1HUdx1E1L5c5js4q012VtOCBfGgAuEAAhj8QIWAd/LHShH7Vmnwl9SxGuwHShfk3jxwuLV2I8N/JcBgLrORj0GVoM9AWoNE554X4tpoOuUjrvuP171ih37enat05lxRQEZz77bjQSABuND3rF+MpF5zr5jOoAJneY/PrQQq6kmumgtOWMH4uLsW+9wE+yec/gpra8VoNfnWP8kqfHLzGVY9yxA+QAzE+3rV9f4ybRdkuTAZo1ALAEgMDowMD110jegg8531d0tjKWUElh1DEZR9BPzFXPAmTJroGk5p0Vog6ERBgn9CgLx8zl2GZmM5RIHsOsDaOwo74Jg3Nk3CwZ2AK2wIVcuqqC2k+u0/WgLeEYksqq7DMqgDXoPKD5T1I9fep37RnMDKwjbQgjudCIrNOXOYbKljiGImR8LEwWkCVGnQfKi/C80cNyx4oUdQVuSffMutBGSw9vH4dLfltXc+mwR1RmJXfLIG1EvEHa3GYMVgyQczLtqVHxjfuaruGY3D3bymxiEbKjnLmMgmF2LCNGI0irl8ShcI28DUnfU9PlQVVu95wzEKg+F2IysIBkdI1ivcWlS6riDbYy8fdfUAj0J0PqR3q1S0sELlB2BjT9fPWqXm3ia4WwxUKb7EIgEAs9xsoJURmiZIO8dJoLZ8WgkrqWbKoG7kDX5AddhQp9o3DmOEe8TNxWtkxstsMVKL6AvmJ3JXoAALngOANm0nimWVAvsBv8ydT/sKkcSxVl7bWXZIug2wJEnMMug3nWgxe20Ixyroh7nXfXT9RU/B8ZusoVrz5AAIZ2VIHSJiPeK8cFoSjbjMp94yn8jVFhrpEake29AcW8cq+W1Zb/8AJkuZT/IAskesijj7NeJE3Gtk6Ms6oysSNRqzsYAJ0rKsClsnMwYt3PmJ95o25RxS271sgtow3002PptIoNoBp4piU+gUUtJSg0CGmk0401qCr4hbknb5iRt1HUUD8awNsEz5AetsujAeuVoiY0MjTUUd4vrQrxskLMaid9ent60Ge4LhNv8AaTh2cHOha2UhVuATqogqriDmUAqdxl2PhxTljww2UoxbLAYm0SCwOWDpOkzp023qHxPHvbZWQDyP4ls75X3bSIIOxH9aJuAYq/cwoOQNdA3V0W6UMshOaQBBgFtTQZ9//k3GIAVC7GFtrcVrjR2RSSev0q+4KcXgfFZMLdD3cmyXCqLmbKolWnUxq0wBrua0Dk7h4F97jtfZ0QALejyZpkrlRVaQsZhOxqy5r4g1i290jyJbcnWMzQFVfQksevT1oMd49x67iro8VAhthhABBBmCGzHp8qo7zRJ7U/CJDQR0b/8AkkfiKseFcJN65Blbaea68E5VB/AnYToN+lAV4zH30wdvDXgQ9u1DkDPlEzbz6GCoyg/6tdKCOJl2MtLHvoflI3/OjrG8Ww902ma0pN1ibjSVJzMQkDqWEnXuO9DGMSzN1AgkO+SGaAF8pOp69qAbrqm4+woVHUyGmfQiOn1qCaA05J4OWttegmTAWYBVeraTvOlJxu0c5WJnYBSPcep9KXifM13DpYsWMqZbQz+QEydANdNhO3Wm8O422MC4e4AL/wD8VwHKHYfdcbAnXUb7dpCtwWHyz+7QGZzvnBXplAVlI1nr32qdhbruHsWXzEgzlRzkzGCVIJIEe5B1BGteJtXTeew4HjDMSJ8rjLJjuf7V78OsXiuUFcPZBIJGjXCNCZ/rQVa8LuI2S5bKMIkaEazGqkjodJ6HtRPwzgKtAIzMw20ED85qwTh+GdALhe6qmQpJVc38RC6t7tNPxGENwHwQtjI0LkAUlhIIjqP7Ggfwjkq0fF8S2D5lC66iFJMHf7w+lJj+Rbk/usSUGkA5jEfMn8qtOW+MeGht33BcMSzBWKZYABLCQug1n366E9vFoVDBlIOxDAg+x0mgBOA8uYlC4vY24iD4QsSx7+eY9ory45ytiPEt3rd5rz22DLbuwAxUz5GELP8AlMe9aEgDCRHsYrksINCqiewAP4b0AVg8ab8HF4V0EwSXvFAe7WvLlH1/rRbwjhlm2ueylqDrKAa+s71YG0GETI9fpt+B+deNjBhI8MkbyCSRPqTrQY3ibYTE31P3b11f/wBjChG2aL+ZGUYzFZdvGef5p8//ALzQgtBcYI7UV8Dchh39PX9fjQfg3os4K5kGO1BvODuZkVu6g/UVIqt4DcnD2j/lH9qsgaBRSikpaBDTGp9Nagrr4kGhXmMgKZOnf301oqudaFebbOa2wzZWYeQn4c28HtO1BjvHD5nAWHBIyjXXpA9f61peD4euRPKSEUIrKzB1CjLKsPPGgMTQbyuhvYsK6k+Cl9ySfgi2UUEAeaLjWyNf99H4YhyggevlH4Mh1B9s1BEuWrtpCA7XbWXVkAGJtiZlQBF0AdIzRPxbVU8x8T8SxZsu63/EFwhlUhbiCCGJ+FXA0K99QANiHHsVUsIEbGTIPp+ulZtxbEtcxgNoQVDXbgB8sqpDXAOkggGN/nQDF+wbT6yTbYBvVdCD/qX+tGdjhrJg7OEtmLmOuN4pGpGGtEgwegIBIn+IivS9wYYpPESBdC5WUiQ6jUabyJ0ZQTqQQdCKvCX72GuECBcS2FMqSApGylgG6yRA1JoPTjHEFxHFMNbRAtuzetLlABkrcUsNNCAFCj2NRuI3VfigDWpBDKV7kq7A9iNqr1z4a6l9RLo4cFpKsZJObUb6zHeop45c/bBjSFNwPnywcm2XLEzGXTegh4xAouJEEXjodwIaNPlUKP11r3xl43He4d3ZmMbDMZgemteMUEniOL8W61yIBPlG+VQIUT10Ak9TJrwXTUaEagjQgjUEHoQfypy+kep/XvTxFBa8R4utxbV4ZkxaMM5gZHAHxzM5iYkerdKkPxOSr5wQybZvhM+ZSJ0GbbuINUfh5tqjssGgNsBx62qw9wDp5QTA3MiDP161Z4XmnDAGbgMzodJG2ummnoeu9Z3YidSB7/8ANXdviVlFg5WP8K2RqfV7jkAeuU+1AXNxFcSviJcsnLmNu2TbzSDoBbd1CtI+Jg/QiJp2FLElmLh2AkwtxWO0NlzJ0qmwWMZbOuDs3s5Z8hZDdRAgljbW2PLFsnTXXUCdUwVzCuQYGHYnLleHtZu2aM6n309qA04fx1hAcT0BXbt7qfeiTCcQVhv9dJ+Y0P4UM4Hl0AA6LOxVio36DUfQ1Kfgt2ZRzPWMqMfU6FWPyFAV2o3HXf1ry4hiVso91jCopdvZVk/lQviMfewuXPDzBMeVgCSNwSJAj3g0Bc0c93sXbNkILNsnzAMXZ4MgFoELIGgGsbxpQUj4gsWdviclj7sSx/E1Cx+E8Mp2e1auA/z21LfR84/00oOmhqbxd1fDYNwIZVvWG/7d3xVP/jiKCPgjRVwYyV66x+VCGFOtFXBr3wqoJYmPruaDdOVz/wBNb9AR9GNW4qk5SYeAFBnIxQ+4AJ/EmrxaBaWkNcKBaa1OppoK27pNDvNGHDWXAGaekwdp8poljpQhxrEMl1rI1QoXg65SI+E9BQBXIKBr+MJWX8NLfbRrozA++QT7Vo2Ew4AAnXaT+TAdaCfsqsgvinMyWtr8hnI+fmNHVyRcIBgEA9N5igGua8ettXMhX0BBMGAJGn3hrv6Gqj7N+EB0v4t1kXSbSTrNtf8AEPsWhf8Atmon2nEhh1IBUE75dNPxP1rR+C4JLWGs20EKttI76rJJ9SSSfegAcLhmtm5a/hMAkTtsY9oPzoc4+5GJDMCZAE+cht9s066jqRptNaLxrDqLuYDVlE/JoH4flQVzi2V7QG2RjHSQU1oInHnlIkEgCR2nXTvpQdiySS3ei61YBw9tjqXzM099dvpQhNB4FKTLXvFNig8gKeq16RXCgl2oUZj9P1+tar7gJJNXPD7Cu1tG1GW4x1OpRCwB9NOlRsSgg+8foUFYVpakuIpjLrQIl1pBk6TGvff61Iu4u41vwyZXNm1EmYjft/Ydq8Ipy0Htw3iN6ySbN17ZJk5WgE92U6N8waKcDz7jFGVstyNSYFt49CAVX/woSRAQTXuEgAjTWg1XDXbWOti9bus8KFZHyhrZBJ1CiCddxoRFZbxnBCzfu29wrmPY+YD5Ax8qNPsiuE4m8k+VrBYjpmW7bUH6O31ql+0jCqnELuWfOttz7lADHp5Z+ZoBWKsywOByx5kxRM9ku2BofdrOnsag5KmYCwGTEzP7uyLiwYGYYmxa1HXy3X+tBBsrrRDwXFZGU9R/Yg1Q2jrU/COZFBvn2cD/AKJZ3LuT7sZ/rRUKEfs1cnCx2On0ouoOFOpBXUH/2Q==', @@ -74,6 +77,7 @@ export const data: RockBandDataProps[] = [ website: 'https://www.ledzeppelin.com', }, { + id: 'Pink Floyd', name: 'Pink Floyd', image: 'https://upload.wikimedia.org/wikipedia/en/d/d6/Pink_Floyd_-_all_members.jpg', @@ -97,6 +101,7 @@ export const data: RockBandDataProps[] = [ website: 'https://www.pinkfloyd.com', }, { + id: 'The Rolling Stones', name: 'The Rolling Stones', image: 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxMTEhUTExIWFhUXGBgXGBcYFx0YHhoYHRgXFxgdFxcdHSggGBolHhcWIjEhJSkrLi4uGB8zODMtNygtLisBCgoKDg0OGhAQGy0lICUtLS0tLS0tLS0tNS0tLy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLf/AABEIAL8BCAMBIgACEQEDEQH/xAAbAAACAwEBAQAAAAAAAAAAAAAEBQIDBgEAB//EAEMQAAIBAgQDBgMGBAUCBQUAAAECEQADBBIhMQVBUQYTImFxgTKRoSNCscHR8BRSYuEHcoKS8SRTFTNDstIWc5Oiw//EABoBAAIDAQEAAAAAAAAAAAAAAAIDAAEEBQb/xAAvEQACAgIBAwIEBQQDAAAAAAAAAQIRAyESBDFBIlETMmFxBRSBkbHB0eHwQmKh/9oADAMBAAIRAxEAPwDN8F7GqozYskSoZVTlqPj+e1aO5igAEUqAsADKBpoBVV6+S0TJKk/Dpz2PXSgsRYklo1yxofXlXNnmcjZGHuF2nm6AygTppGs/jXLzBEvWQd7V0AHmCjfnSjE44LoWhgQQJj9ipcZxYa2l5GHeIYj+ZGEEHqKLEnotrwaS9jCpytBDKCp1112nrpUQ5zaBgOupjSkOG4qLyZHEaGD57j0qdvieZe6RjnOk7/8AJ0rTGDboF6Qn7QIBeeDOv150lY1o73DV1LP1mevnSvH4S2oOViSACJ59Yrb2VGenYLhsQVMjpFWWVkgdaEtbxTq1w0qM+YGIO1VzS7lqEpK0LcTaIOoijuHYoW7YZVAuK4KtGscweoq3FOtx1zSBzqvH4busoncaj3qOW0icNN+Bl20wqzh8Qog4i0HYaRmAUGBy3rR9m8f3tgLmhlABB8tOlYVLgIhszQIHkJnToN6YcGx4s3QwBA2InlRNWqE1oY9ucH4bd0AfDlYjryn61h7tfUOK2hdtXEEHMDl33BkV8zxFhhqVIExtzpaeqGRKVorD0KtE2KKJbGto6VReFW2DpVd6m+AUX4a73im0xHVSeumlLbikEg7ipFoM0Vi1FxO8HxD4h+dKemGLLgqmKvaqiKplFls0ysNIpYlH4VqOILF2PXelZFPMetJnQztS59wolRrhq4WDUhYFLsYkyqydaINdW1pIGg5+sxr7H5VwtQtjYQ3TOg1yog612qcku4+OGb+Vn1dDopMRrrp5Ug47x/u0HdDUyuYkQD5Dek/F+0DGVUnQmByH6n6Uhe4WJLamsmLpm3chE5JKhlxC0fC5JbOAZP1prxvD23weHvI3iSLdwE6zGkD96GgsPdzYcSPgPTkfyqItd8MqkCNdRG1bIypbM8LnryDYMkxqd6Y4S/kYMpggyD6VT/Cd1IZgdjpRGDVOk+9PxtPaLmmtMbcSxFu6BdLAXGJDrEDbQge31oNbqRBAOnQV5sIh1Jj0il2ItsDCmRNXNJ6JCTiWXrxYgC2qwdxzkVYhuvCDUnz3q/hQP3kVtjqaeYVkS6GFhUIDAEsSM0aT06+1LSSLc23RS/C7YAFxwptmGZfEpOUtAI32I051dj+zs2+9tXluECWA1gbiDOp5nSB1Ok5Tity8JlwZMkAjnIB3mCBty08q7wniF22YVo011OvrVcnZNdg5EM76iaji38Rjy/Ciu5B8QgBoMA7TIMTrQmJSDWiIuWkDXcfdH/qN86XX8Qx3Ynnvzom/QN2hkSJ5WphwvCPduLbQSzEAClqV9Q4Hg7WAyXLsm+yjL90IzbAjeADqQPKOqpT4oYo2Hp2NsYayWuMLrD4yzFUXSSEC+InbUz6Cdc3xfhVgoLli6Mx1a0fuzsAenrzonG9qc7MFhVk+OCSeh1JgkzzPLUxSW3h2N/MskMCzdCJhiRsBJny0pKyzvYzhES3gQSDuKlg8TlbXY6H0q/iFhpDkEBxmUnmJKz8waDNoVptNC62WY+wEbQyp1BoQqavZ6iTVE4kVSi8OtULV9s0SKo5jBzpZcOtMsQJpXfSgmMiRJFWWSJ2oYOf+akt2OVIlFtG7DnxRabQVecxHImflIH4mg7lWrczMq7SQJOwkxJ+c0x4rwwW2IXvsqgS3dhuUkhgwAXlrEQd6uEGkL6jPGeXkuwnAjU16r8VhQsyWzCNGABMx0c8jXqtr3FfFfgrNdAp3iuy+IU+Be8UiQysNR6EivWOzGIPxKEG8swH4Gg+Pjq+SM/Bg3B7oDFJgOCp6a7H51PDXjZYwQTqNRPlRtjs/4ot4iy1wa5Ad/Q0v4th2S54lgnWOh2NSM4ylQCUoStE8Ri2uEFgNBGmlWWEoG29GWLla8aS0iTbbthF2QKEa8w50Y9zSg7rijkgERt49h5/uaNGOzq4EhspYD0gnX0B+VLHIo3AYG45DIhiQAzeFZOkZjoSZ+ESTOgNAsbk6SDslhcA7IhMHMYRZ1MgmSJ2gR70fc4Y4t3IScoJMfdgyd9fhnT5itFjOErZZEdQQoDITrlBGYToJIEbxFOuNXUFm/csqi95bZs4XKWYwozSIaWI16b9azyUlLiaYwi1Z85tAwJ/etWttV13COhAdSsiRI3B5qdmHmNK8V0rdFUjG9sV36CuIelNb4oC6elJmMigdE9Px/wCa2uL4r3uGs96+YnRm3KEGJb1kGB1rN4vg92zBv/ZEx4WnPHUoASo9YpzwO9YCXVyFityzLsAQbcqXhdQpBB6/CNSJBVODaDjNJ0aHCdn07nPeDu7eLwyW8oXWZ31FRxvAFto7v3oQoXXPACnVBmI2Mx4RJ0GlObeMCAg6soP+316ab+RpN2u44HtW7AaWMOw8Pw65JymCCTI/y+dIXqeja4JaMrj7i5VRSSqAhTqNSQSYPL5a0rd6IvN9NBQbGtCVIV1PBZKx9lr7/X/fBwmpKarJrqmrEF6VcgqhDRVsUSKqz13alN5efXb0p06yPWluMXWpIIXGo1NqiaSQ5FdR9QST0mdYrwUmvMuk1CNEWGpivV5n6aV6oFQTYxlxNEuOo6BiB8gYrl3Eu/xu7f5mJ/E1TR/C+EXLpEDKv8zTHt1qS4x2xKth/Z3xvbRbYzK2c3JM5eYI2pn23uKxBG+/tEGiLeJs4NMttgWM5jzJ18tKzOJ4q7OXLQxEDXYVlheTLz7JDJqo0C2zRVg1EYm42uaZ3NXWieZroRYlotY6ULcBphIih2WTqYHM9ABJPsJNG7ekSMS/A/YqHyg3HErInKs/d6O0aHcCI3Na7sqq3Li3GLMxkh7hDFVEZ4ERbX7sKCWZgNjFYji2Jm6xGgBIA/lVdIHlAplwbFOshWIJIVY5Rvz5ZgR5zXchwx43Apxsf9pOI5MVcuSVW4FClgcgdfjDKCzK3jU+UwRKmVXEePX8SoQ5AoXN4J8XiCSZAJ1YQIFHYrEEL3EHuTowYCGAzLM7r4iTMg6b6zSvCOllhIJ7t7ZCwG+zXvM4M6Ez3fkZO0VyJRcZPL+x0/yuTFhk5rSSr9WbfsdxX+Cs/wAPjjbe1cV2t2rkO1pwCQGQghUePYxpLGF/aq7gQB3CEXCTBQnJEKQWV5OoOhWAd4qj/EazatFQloLcyoCytoIUBbYta5IUA6nYjfllBi2uubjtmYkSfRQo0Hko+VM/CoLqMHxJ+TnqPGVojiDNG9nQLbPimH/kwUkTN1pyH/TBb1y0HdplxDCm3grQaFDsbkE+JyYAKrvkCwCx0zCBOsKjG3sVOWgG/wAUuYhwbzG42sFtY/cUBfwgMgTBO06eU1LhyeNj0Ee53/L50aLf79qU9go1/AMTgjZQ38UGuIB4bhyFSBqsEw8HSdZrHYuwLmKu3bbZlYk5uk6ZYO4EASNOm1LL25PUk/WnXBhAEckWfrM/Ks6xqLtGueZzSTAMRbOpIjU8+UkaeVCNVrYrOTz8/wB/6vpVT00XF2VmvVIWya9EUNjVjlV1ovw5otaX2m1p/wBn+FNiby2gYG7NEwo3PTmPnV2asLXFwfZgy0BilrdrZwVgtbyC9Hha5ccLrromoWfTXz5Uk43wMLb7+202y5XKRDLzGsnMOU6fpfKzGlsxjprVgsACalid/Kp3fhH75f3rPkbTSOjgwQ+HKcvC0V3BoPQH8aouLIorEb+w/OqbR3/ysPmpqsbtWL6yPDI4+yBGrlTG3nA19thXqYZr0aD/AMZRJ7rD216EiTpVGN41iH+JoBAMKI05UoNw1diCwySDqgIkcpMR5b0CwxW6FczjsTrua+5dk7mGs2baKMv2eaVUksQpLsSoObY6n9K+Fo06RvX1zhnGLy2U7xhba7DAagSdCFhWEmTKbieVH2G44pq7M326wFkXzdtEBWVGIUQDcLXATHIlVDbanN1rOExp+5pv2o4ybx7yQS7vB/pXwj6s8eUCkCGihb2NzTxxx8EvV5f9AwGajeEo58go9WP/AMQ1dtmq8WYQeZJ/AD5eL51t6aPLIvoZI6QPfxcXg6/dZXHrIfbpPKn3DnyvaKgkC8yqJjVRcKknqSfoKzmFXPdQciyz0ALAT6ain3B0DHDo40d7jFddf/MCg+R19prU53yZUPmVjfjNi+hzEBckEHMRo0tBOUCQdI31jWBVWCD4lyzKbrF8hMBfs1A+NgugIbVmGgUU+7V4q2uGcZSAzhI2kBcylIMFZiZ6AaVT/h3dJu20Ughg6lSYbKc0uvUrIEdLk/d0wdTPjBtHU6jM3iS/t9/5Be0VlsRjLeHZcjMWLgNm1VCJk8zlby0nnWOwtwwpHRZ+k0/7SY//AKvE3gTKq6CdCGKm3I10IZyfYUiUj7uo29q6nRRccaX/AFX7vZzZPbDrFnO6p/MQNNdzBgCmfaQozlst57oHiZyiqqjRVtWlJZEAEAN4uoBmruxrqMUpIJhLhGUEtmCMRlA1J00iD5jcbPtl/h5duYf+IDs95VBe2TnOUfEEfcsBy5xv15raSMeRtM+UYIwp89fnqPpFGYlgqMeiyPXYfWKL4FgA16GEgSxHLoPaSKH7V5VJAjV4AGmm506SBVSVQsqM05UZ43SBrH78qc8Fb7F25nNH/tH50jxbbCKZ4K6ww+UCTmI9pJNINDZTgkGQGNf3v5n8q61FWcA6KqlWzMfDzzbDwxuZIEfnVtrgmIuXGtLabMrFXnQKQdQxOgPlv0mqbSVsfgVuNeQGyYk0Xwbgd7EsBbU5Jhrh0VRz1OjH+ka1sMF2Vw9i33mJdWjfN4bYPSN31+fSl/Ge3MfZ4RcsQBcYRH/27ewHmem1ZPiubaxr9fB1OqnFqMU9Jf8ArG9jsph7S+IZiIksdyAy6HQiQ0kTEqDyqLcVw9q4LFtMxbQhdQCQqw25Ywig7/CJ2rA3r97EXBndrj6wWO3XfRR8hTHh2FVMtw3/ABgyq21zfDqCzkgKNOQOlFHppS+eTZil1EIPtRteGfZwVVZ0Bk8idYP719SaV9s7xOVSArHUhWzAgSFJ01Op18/lRwnjiG2JuKG5gnJB1JgkRloPGu+Jz4hYKLA1ZQQOXhJBJMToDvT4RfK2DKcVHRmr6eIVC/uP3z/SjLieIaVVxTA3LeVmWAdB8p8XTTl7bg0vJ86OpFpdMn7tfyDYjcegoXPXmbz9/wB7CoVIR4qjB1WZZcjkiA2Ferw2r1GZl2Clwo5t8qsu2AYAY6CNfn5da4t1SN6IvNPT28hEAbxp0qxdIf8AALtlUVABmjxSACTuSCTr5Aa6Vo8FhA0MWZbKXEZzOg1AkTpmg6/XSK+eWbbNoqk+g/E8q244kxs2rTQcigMBsxA+9G7fvc1LvQp41CXxG3RHjvAbN0zbAtZAU8LZ1JDHxEQILbmOdYkKQSDuDB9RvWvxnEgEheZA/T22pAmEFzEtbL5CzeE5S0sxBA01G++tGlRUcjnJv9ihDpRl2wDh1aBJLaxyGgk8hvRvGeAGy6LaLXg6kyEIMgw3g1gQVO533qg4fLZUZpJcyFZTAhdG10g8v7Gt/QNfE/QfJNKjOJKsx8tD55hz9dacYbiBV7VyDNsxvuCH05QfE306VYOFG6C4OVFILMxAgaDbdtWG3PnQzYIyFyuoBJLMCARAjKCAxnWNIMjbWmZZY4SlFNWSFKSb7DHtLxlb3dqilAsswJBl23Og8hTvs7xdLVpXj7REfI0CCzLcTxGZGXMToDMLqvMHhOVSFgMihmcQBnRQXfOefhBAkwOVKcLdN2zbs2Q/fbZSwKlswK5RplmNSx5Vzs0Fl0/ua3nWV8IKlvv7A2IAZVOYlmuuW13ACEfUsCec/O+5hTAYBvlptyIFNMVwu1bTvs7F0dw6BGKJqTla4wA7wZGmAdfYnlh2W6t62pTIwPe5oWQ7owBchTECVEnQ/Fz6X5+EZNRjf1MUnQHgsRcS4r2yy3FIKldweVfYeznbG42GVkwzPcU90QLiqmZQp3MsPCy7yZr5RgUe+6Wgc9y4fuSgLZi2Yu2wCyT4ObanSvomP7NLawjW7Fy6tyRccq7KGYKFbKAZjKAACSPDsK5OfqseN1N1YMsLyepLsI8bjpxV0d3ZwrAkXG+4RLFRIYhLhBXQblD5gr73Zu3i7qpbvlshY3XyBcoDQFXQatJAmQYLTuKL7JpYTEnvrQcIveOGQPLeNcpmdSSpExrOmmuut27F3D3bah8Ozs1xb9lUgTJUHKBAG2UmehE1WbJKUHHG9lYMUV6uOgGz2dw6J3a2EyRBlQ2b/Mx1J86+dLgblvFXsPYQtl7xQdws+K2WY7QG1k60fwfhf26m9fuOe8e2PtGEXbZkLcaZhkAYaiZjyOk41x2zYBGly4ToshQWmOerxpMSND6HmYcWXpsjXLk3/Jvm4ZsdtUkT4fwO2zW7txyWRgVYHdhlCZyfFPgzDkSxoPtR2l/hW7pUzXSAxZtFGadYBlj5GPWg8D2wVLTlkY3c3hUGEykAROvwlQdROvqRjuK425fuNcuGWbpoAOQUclA/ZMmt/wADlTyeBMJKEVDEU8T4ldvtnuuXPKdgP6VGij0oFevyqbVFqbSWkXdL6kht60wvrCe0fgKXMeXtTDFnw+4H50+Gos52d3OKADTjhVzImh1aZMD0IncHKfeTSnLtp++lOuE2hlZn+DU67GAc3sBE/wDNDGajtl5ccpxqIZwqyEtC/e0n4JE6bgjqZ5aaQdjNIuPcTa8/MIPhXptJMcz/AGqeO4ibrjeIMA8h1PKTp+xS++KzrHvnLv8AwdBZLjxXZA5rhrxqJNWUcr1RZ69UoBSVEmSKua2cobkDE+Zkj8D8qnYwdx2CKjMSYAAmT5fMfOjF4deYLbW2xbMZWD8X3RG2xO/nR0KKbTuLcqSoBIJBiZ/MGfmae8Nuwm0j5dDp+NVjABbSrcDyAQwEmMzGJ5D9aEGJy+HKQRuW0nptP6VdCZXLQysd3dxNlG2Z4bWNgSNj1jnzNazE8IT+ON7CLItkIzOZRHCrDKJLOYMa6Ag76RkcPwMXmX/qbFtiVgfasdRpGVGBPQA/pWt4Bg+4GW5eRiULKwDAMMxGXxAHMMv3gOUTSc03GLce5r6eCtKRscVwwMtm8FTMgacqSzfzAsxMzAOw1iZGlfIOO2Yu3O7T7JcsH4cxMCYUeew6edfTuI9prFvCd2rZnZu7G4gsDJk81UMY8h1rCX5ZlCiJgAbzv5+dN6aWRJtvx9vuVmcU6RruzuGt2cP3VxB3h7tLxMwzP4IgaZJOXXeTsIAy/HrVk3fCSIL2wNxNtipHXUaj1A56WYziuK+MIiozspJ2B5A+LMxkM20grI3qHch7lxngq5FwGMniCiT7meQmTQQg07DnKMo8UD2mJBFu3q66hgNnQiOQClWInprSfC8TXD4m3mIGUsDlGYKGOWSdiAJ1UGRtyq/jvGblh2bDTZzaAosRbA2B2YEzy0IaSTtlWVjJ1LHUkmST1JO9NkZk3Z9e4dxrBtbc3rlp2gHKCLhBAKAgqGJYA5RcMGIB51iePcS7y8dz8KJmLHwgBiQ5EvsonoBzpDwuywJJ5a++9HTz/t9Nh6VMcVFqXsMcnLuaj/Dq8q4szEm04X/NKkx/oFz619WVywBHyr4bwFj/ABNmHCfaL4iYgTrr5iR719m4XxFLltWQhhtKmRI0aD0ma8/+NY5KccnjsbelapxBMHg0GJ7q6p7thKkNlHPQneBty3XU5oGT43irlhbjQGFu81vVmtkMAACwXw3ATPhIBiYI3G944gWwbjwI1SZJZgJhANS0AxHODWE4pcxmLAtraUWnAYsTrGYghjPgbw7AMYYSRMVo6PO4YovhS8yel/lg5I8pP1X9O5hMfxW6S/j0uFGYQIJT4JEbid+fOajcxlvEX85t3mvXGDQjqdZB8EoWAHJTMARm50xxmHw+D8Fy2uIxB1YM027QOy/1tH7HOlO091QRZSzYB/7VsKfnrXQeR5PVCP2bdf5oyUo6k/0J8bwfc3Xtfy5eYMEqrFSRoSpJE84mlF0miQ5YSSSdZJ1J5yTzOtDXq0pPirBvegZmNQza1J6qNCXYTaWWUjXX1+VOMNw67fRzatlwvQgagTAkiT5DWlvBe8NwC0md9SFAn/j1Omtb3h2Im7ctBvHbPiCtoOba7GIjTmI30oZZJRVR7lRwRySTbqjJYPBLcdVkhFA7xx1LGAojeI9IbpFbfBYBMhBFthlKeCYyERDToTvtB1rPdqMSchAYK1xpaB8QgAkECJgLLedJcLdvOcvf3SVAyjOYgHaCY08/Khgnk2MnkjhTvZbxTgZw5kNmWSAdjOpgjYmAdRppy2pPdNN+LY5rrAsIyjKANhrJgcvx6k0puim062UmvAMxqoirWqpqCi3IjdWD7da9UsRbKmCNYB+YBH0NeqwDe8JbCqVLXh4CMoa4RoSQ2QBtNYaN/wAaYDAIMViLasfuOoDEfcBktMzOb6e1vZqxZt3GhF+FYlSTJA5mSOf0q8OqYq82YDwWhr/KEf8AetcaWZuUuLfby/sdJY6Suu/9wDiGCzuqIHNx9PiY6jUliW2G+um+ort3smpH2l4kR4WtiCSZ0GaZkQY684ElxhGtWLZysAsS5mSCxzAEiTAmff8App92bwwuxcM5RooPyPMzJBJPpWzpZTfoV/Vv/e4jOor1MR4Ts41yFSzbtyJkpM+ZLBh00j8zV2J7H4gFYuIx1ISFykCJzAW1EbDnuvtrcVh7puyjBRAhoJy8oC5gDILHUGIHU0WLXhAukXCDMlAPkOXOugsaRjeRnyzjFxmy9+pW6jTbIA7twQEhdIGksCJiI1kwtlRJHxArrJ05llPPzG40r6zxnCpftlLgkH5g8iDyYbzXyDtBh/4dij+JlOkeGRyJnaRBgTuKOqBbsqxllgZdiZ8QLGRyBJM7gQPlVnEcfaFle6vsb4OiKqukRqXLSOe2s+0hDxzEG4QM5ygDMsZfGJB2+LSNTVdhQBsNP0FC5eCkrGWH46yW7lt0W4WXwkwkNJIYhRFzX7p00HSkFm6DvvTS+nhWRzGVhsdfErf1Azr09QaUmyAY13oWWN+HgSAZgnX05xT3FYXAZT3dy+H+6XKFdxuAgJ0nY7xWfwY1AqR2ol2GRVk72HgE50MdDr8orSf4f9oRYdrdwgWm8WY/caQs+hkA+x61kLhq2yn2N1+htp/uLP8A/wAvrSs2GGaDhNaCU3B2j7jxK/buFGNwMNMqkiJH3kHMx+FZrtT2ot2Qbdhs93bqE828+i1l+wBJN204hL9oqhOmnwsU9S6CRoSI1jTOKCJBEEGCPMaGsS/C/leSTkl2X2Gfm9NRVAnEmJOYkkk6k8ydST5zVdtTRF8SSPT8qHUwYro0ZBhhwctVXhV+GOhqGIWi8BJC96ngcKbt1LYMF2Cz011MeQk+1cuCp8PxRtXUuDdGDQefUHpIke9KldOgl3Pq/D8DZwtrwAIvMndjsCx5knQeukV8+4hh2wd1mtz8UHMc28kBiIzAgEz5V9HVbWLw4PxW3HuD+TA/uKXXcIMSf4eT3KZRdcjW4wUBArNOw1Jjp1153T5VHk5d/Jrywuq/QyPDb9xhYuYllSzb+AlQblxQZyW15rpGYgCOZoK7iwb5uqgQFmIUbAGYHTpVfaLG95ibjgeFT3aDYBV8IA8pk+9ApePSK34lXq7GOdNOL7BbtMk7nWhbtWBqquU5lWDPVRq1xUCKAlkLvL0r1ducvSvVRZ9I4TehoQeMqiidiSM2Y9FUMJ/Uiq+K3Ft4y4rZmW4EnXlladDMaqI5gA76g08dweLsZrtlXSwsKrrdMxoBnIadWMCeQUbjXOX+JXbrG47sWA3LMTGukknQFzp/Uazx6bhK78b0aZZuWqNh/GIDc8BU5kj7sAKogrHpzERX0Dsef+ksnmUE+vP618jNxLq+BiWQA7eJrQnNKzq6jXciBE7Ct72F4qP4e2pZgrXntpK/EzMXAkE5NWO/tTOnxcJyfuBmnyijVccxDrYud2ftMukbjUSR0IWTWE7D47FG46/FpmZS5yfENc2pV4mNNdZ202tjEgmJJB8Y0A8M6CefLfWqeI3GttmRSQZJUQJjc+ZitdCEzq3MUTrasqvQ3GJPXUJA/tXz/t5YL3lF1MjFdMjhpiAZLKP6YOn6fR8LjFcSD5+28+lfMe1nE0v4hgpAAcAsdRAEctQBzAG6g8hVS8IiMxxcpP2awM7TJDEyF57ldDGgG/nVNs/Leu8StqhyhpIIaI11AOhBII5htjOlSsXcqloBjkRIOvMHcUsgItw7GfizR0MdPf6VS4+E+X4afpRBtlmJUEzsAJJ9I3OoNVusIP8AM3yhajKDsGdZqeIWPQ6g+X68varuF2SLffEjKGiOe0gxERy60fxPDIbdtldSLubKNAVdSAcyyYB0EzroaB5OM1Frv5+vsbceKLwSyKStP5fNe/7mcuNU8Jjrlok23KkiDGxG8EHQiQDrzAPKqrggkHccqqJphhex92UxDtjEcu5bLcliHuHS04XQAsYbKfKKu7SWl743UIKXibiwZgk+IHQQQZkVb2URrNt8SrWhcIy2kd7amJ8T5bhgrIKiASfF8OjUHj+I3cQ695llcwAUBR4nZzoNPiY/SnV6Ng/8tEreGUqCVExvr+tLuJYfKwIGhH12pyqbD2qnjljwIehj5ifyrMpbNMo+kC4ZcAdcwBEwZ6HStKcJbCv9khIAYSOQMN+M+xrLYO3LAdSB9YrS3L7C6ioJMiZjUE5SJOmoJ1OlTInQfTZVDurAHNv/ALFr/bNUME/7Vv8A2ivXpUkHlVDPSuHsbfzEWNuE8ffDnwomQ/EgBWY0BBkwfOP7E43tlmsMti33RJy6kSMwZiygaciJnQkdazmbXXUdKAShjgg3bRkz5m+xWN4HpFSTeoXSAxI21I9/Pn6+VeQ1oRlkX1FqKwqoT4yQP6QD+JFRvEchp5ijBAXWo5KMt2C85RtvVBoLV0O+FKMVOS0+z9wa5XqZcJwK3XYMSIA29wfy+depU80YumSONtWiWDu6fOrWtlw2VZIjNCCMo1ktGjSQJ3jTWlNtz1q9S/Ikct4pzBSfsOcHYSy6kvcS6JbLkETIyycw0OpkbaRtTjC8RCW5shlNprt5JUBBchWQATp8MZTM1jW7z+Y/7jXZcAnOfTMaFJ33D8dj6ta7WIhDNbYE27awxAGoLl5I+DaNZMHQVb/9UPcjIbdsoGYLdB9J0YAL6718jF1v5j86tVz1PzqTi5O02gFJLwfTOLcTfuvs7tlpCyEBBkkA6i4cv3jMiNuhrInBLqSQCQfCrLAbWNt1/X5p0arre4+fy1o4xpbdlOdvsDYtJunY68gI9v39KNt3MqkNa7wGNSSIg8iOeny9aBsakmvYxvF7D9fzqeCgjC4wWyJtAwP5iJPXQ6eggVDGXbbCVQJEaTMzO3U6fvSgwaItEhSRpqBsPM8/T8KhTbGXCwVBYgHQ6Osj4TGeNtJgGhjYU73AD7n0JgE/KfSieGAHrMHXQaRJlYg+tBsoo/BS7kMYASDmBMDNvv8ALpFD4fDZ7iJPxMFkcgTBOvQa+1XMBRfCbYktzAyjyLTt7Bh71SVui3pWH9ocWrlURQEtjKvkAIAB6aD5VXwi0Ac52B/5oJ9d/ei7mPAtLljMQdOmsEmrzyb7eSsSXkaDCkOTQfaKQqCBqzHzlQNuX3/w6Uk74jUk/sURefMlv0Y+5Yj8FFKhDdjZ5LVFvCVm4nWfw1rR3kncCPUVlbBiTVrXfWjlGwIT4odYjBNdVGWMwlHHmvwn0ykfKqTwJ+b2x6t/alN8+Genn5afKgHcdKU4P3GLKvYf4vg5RGY3bfhVjAaSYBMDqdKz1pajm8ql91vSrjGgZysqcADeTP0rtqqUoixuZPI1aBYbhADM6jLcOvUWnK//ALBaruj9iooPCfb8YqSmjKSK+8YfDInQxOvr1qplNEmq7209KEO3VN6Qy7NLDuP6V/P9a9XezgIusDvkn6j9a9XM6m/iM14vlM1YuUfaelCtRdq/XRsVhyKL2MiK4mh/GgzjfKojFE8qqx8s2Nl7W4MV1a53kivTRJmOaV6CLZr1y9FV54FCX2Jq3IBIMwtzlVd1pJPUk0NbJFWW2mqTIyYoiwdI8/y6/Oq0sFtAQTyExPpPOmNmwq4Yl7b53uwrZdAttSGAmJlrmsbd2vpRIFhnDhlRyInI+5ifCSY/D1IoF1B22onCXyisJEZGBJOokEDKp5zGup9KATEsohTFMe0ik6ONbrQ47hRw1vCq3x3M11x0ByhF9QBr5saV8I729etpmYguM0NrlGrx55QxojieLdnZndmaTowzRrPxNJgSRvVwpbJJ3oX4q5lB1A5Cfr9KFDA7H+1V4u6GM/ONvYVSL0bGKTKVsbF1DjQRiGAG/wC4oC5j3010GgHQSTp7mrOJ3yzfEWACgE+QH0Bml1DYLG2F4mPhfqDmHvuPenC2xGkmsjNaDgZd7ZADHKY0nY68vejjL3Kqwx15a6yPoI+v40I+H8j8qZXcIxtgd22bMTm12gaRGhkbz5VTh+Ggkm65tAbHuw8nplLL+dRsP4ckrpi42Y5VW48Jp1fwtnwzigR4RHdMDAAXSHbWBVvFr2FKFbCQ4y5WyuJMjNO8+U9POqRHFVoy6mrbGrCTAJirLOEc7Lz3On4xpUwvcurvlOVlbJM5oYGDGwMRQpkcHV0E2rYyP1An5EflNV2W8vkK7a7SZHlcLYynQo6s+ZSZILFs0HbSKs4XfS7cCIvduxyqC2YEnZQxAIPITM6a0y0ArI3GjcH5VSXB0E6+lau92Jx7CP4W9/8Aiaq7fYnGpr/C3uf/AKftzFC37B8ZCbs8x78zzRv/AHJXqMHDb1vEhSrq5ttoQJgFBtG23KvVizYnKVo0Y3oxIqxGqqpA1qMiZcgk1bmiqbVyK7cuzUoKy23LGBTG3wx+elL8DizbbMpIPlROK4zdfe45/wBRoklWyWcxS5TlqzC5R8e1LTcJOprly4aolhuOxCT4Nuh/WgQ9Vk14GqKsIV61XY60MXiLeHv3LgBBVGXUyJYKJ0UHxa8idtax4anfZ7Gi3dR+asDzEwZiRqKuPcvlqj6d2l7DrZw9w2cSMhIYrc0OVZMWyCFdyYjQEhSBOtYM8LH85/2f3r7HwHtXZxVt2vYeQBsWDSdAI8AKnz69KwHGeI4dLjNlIUk5RqZHKOnSneAYuN0xZ2f4cq3JZtAGMFd9MsDXeGJ/00o49iwzQp0Gg+ZOp5nX8Ko4rxkufAMg9daSNcPWgc9UE0rtFzETvrUe6HWqFbnXWuUsll6kVXdtgnSqWapLdqUS0Ra0aKwXEblkEJpJk7+nI0OLprszUInW0Gnj97+YfX9aqbi94/eoJhXKspyb7sL/APE7v/cP0/SotxG6fvn6fpQteqWUXnF3P52+dSvXGgAkk+etDTVl061CJkM1dDVGvVCWb7sJxPE4lzh+8zEIWUvcC6AgEZmMHfbfStbjMDetAlzbaNTF4NAG5LAECPWvlnZO3mxKAgEQxIIkHwkbe9P+1lu1bsgpZtqxcDMEAIEM2h9hV/cOMqQTf44TfS6tsHIjJBaJkgztIGm34V6sX/GHrXqlILmf/9k=', @@ -119,6 +124,7 @@ export const data: RockBandDataProps[] = [ website: 'https://www.rollingstones.com', }, { + id: 'Queen', name: 'Queen', image: 'https://i.scdn.co/image/af2b8e57f6d7b5d43a616bd1e27ba552cd8bfd42', description: @@ -134,6 +140,7 @@ export const data: RockBandDataProps[] = [ website: 'https://www.queenonline.com', }, { + id: 'The Who', name: 'The Who', image: 'https://encrypted-tbn3.gstatic.com/images?q=tbn:ANd9GcQoKRCDsHx1DyrnXBxOgpPUHZZIwebCq91Ge8oQylUJXlWzlOwtzQtepNBzfbT_497Ymm1sU1Oq2OV0qGmxa6igziOxHJX4Eo8oyk088RpF', @@ -155,6 +162,7 @@ export const data: RockBandDataProps[] = [ website: 'https://www.thewho.com', }, { + id: 'Black Sabbath', name: 'Black Sabbath', image: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ8qFdD0l8GNdMw30NkViSV7BNfUab-7wZXWs1jC3qYIt6llh-WVpJ9MLmgv4QKo9Gq-FOOYJ3-aOr9jQjDhcToYWj2VO9LbBIh7MO6e34PNw', @@ -171,6 +179,7 @@ export const data: RockBandDataProps[] = [ website: 'https://www.blacksabbath.com', }, { + id: 'The Doors', name: 'The Doors', image: 'https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcQVPqssHHK-u-EaYkVo8YuiCBINkJYVsAkqIlhojm2swhdc1Xn9lWkzokObi1_lAcr1CZ_dEbKnBTD9hfkUcPJ9vrwg-ARlgurH2rA5I4iwXQ', @@ -188,6 +197,7 @@ export const data: RockBandDataProps[] = [ website: 'https://www.thedoors.com', }, { + id: 'Jimi Hendrix Experience', name: 'Jimi Hendrix Experience', image: 'https://encrypted-tbn3.gstatic.com/images?q=tbn:ANd9GcQkDuNxy0xKisGP4lUk7N0QqrOQpyHNy9U2lpQhKBH5BqnRn6drBm8HYWVwcFfea1brVTxs7jSSVH1hjyrwTlzGRZCgfC91C-YgEStSzuvMwg', @@ -205,6 +215,7 @@ export const data: RockBandDataProps[] = [ website: 'https://www.jimihendrix.com', }, { + id: 'Cream', name: 'Cream', image: 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxMTEhUTExMWFhUXGBoYGRgYGB0XGhodGhcXFxcdGRgaHiggGB0lGxgYITEhJSkrLi4uGh8zODMtNygtLisBCgoKBQUFDgUFDisZExkrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrK//AABEIALcBFAMBIgACEQEDEQH/xAAcAAACAgMBAQAAAAAAAAAAAAAFBgQHAAIDAQj/xAA/EAABAgQEAwYDCAIBAgcBAAABAhEAAwQhBRIxQQZRYRMicYGRoQcysRRCUsHR4fDxI2IWU6IkMzRDRHKyF//EABQBAQAAAAAAAAAAAAAAAAAAAAD/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwAlV4xnUSm3m0Da7EUgEG5Hh7mF2uxm7JBvYk6wOTVAuTb3JeAMzq9GUlS7GwEB62uCu7cjkLDzJ3iPPmJFspcX2AjlUZiB3SX0/m8AawbGJKRkVLSQOjnreDdQUyimdKPcUWKf1hew2SmnR2kwZpig6U/h5ExGm4kpZbMEOfSAaqnGJcwORtfn4QCk4gEqBJAJ1HK/9RlM5Uy2J1CxorxjtW4P94A9QesA0UdehUrurS/59YZcIrzkZmPTSKuoxlLeGjw6YPiSUsSG2LwDZRYktKmNwPKGCmr0qhORVJV8uvS39x2TPIuGcHzgHlJj2I1Gt0JPSO5MB7AzFsYlyR3jfYCMxLEMoISxV9PGEWtnqWs951M6l8nfSAnV3FExRZNuQH7XjlT1izeYoWDtqRyd4Bzq2XKLJLqOj8yHBJiCawiWpzc3J8NYA9WY8EghLliz8yenK0c1YqEh1Eg+Jt06mE04mElg5O5015fzSOdXWTVDMyUpGj+zB2/uAdRxRMT8pLdf3gvhfFhXqb8jb05xTypyz8xUU/6gv/POJuH4jKll0yVK2da+WjszeDmAupePlIfUfvE2nxkHlFXorMycyWlga5SWIF9FCJUviGUkOFt0LsfNoCxp2JKN0B9j/UBq6tn5TuL7NAPBsd7RJVpcszN5h4PU9ao6F+adx4c4ABPrZirAMrx1bl16Rzn4mUtmNyQD0P8AcHauUlQKmuDe1x1DQicQzFSyQrKS/e6/hPQ6QB//AJHmsSwFucdp2P8AduoDVyduUVlIxElTA/UfnHWZXldlajQtYwDyeIjnzBbhtIKYdjqlr12ZoqgVRK+6bDfaGnBavIkXuq4L9d4C2sIW6nd7fxoPIhBwfEcqQxDn7x/mkPFGXSLuYCUBGR6IyA+PRiBdhBjC6JayCx56c7RC4OwwzqlKWcC5i78K4fSlLsHf6QCdR4B3MygM2ge+XrGtTgqmD947bCLLGGjlbwj1dAlrjSApDEXSshYN7+HhEOmuoMwP3XZvAvDzxhhySoM1x/PeEcpax2MATRLUpRSUhChe3yny2g3h1aFDKb2b02eA+G1+ZJlqdx3knfqI6005KFlOxVmv1gN59NmXmQSPxAlm8+UF5FQMrZhb8Jf9jEZS0JfQuN9NN4DqqkhVgBfbT94BwpArLmChby9oKS6gljv1DGFg1qclrkjb9YlYbiCkkA94PaAtCgq2QATEPEeIUpdKWJvp0+g6wl1+OFMs5DdlEnYAa/31hGreJZjlKH6nc+PIdIB2xTiJrFQCXc9Tf+NtCzUY7MWciDlBNxqb8+sA5BUogrJMxVgCLIG5bnyiSqYJScsvvLIueT7DrAZWVOU8iLku5c6frHGdPWoJcs4MdZGHZiQW0zKJ2Yf2Yj4nND2By6DZ20YbCA3TMALAFh6nwjAlc0upVh6AX9TtHGikqX31gJCdBt19PrE6oKUnIksEgW5kh/eA0OQCwZPM6n8z9Ik0NOlQKilkjSzdXflA5EkrWl+g6XIAgjjWIFLpT3QLWGwtbrADsVrVqOUem9+mwt5xF7FYQUvYEC+gJd/pEnDmSRMNyo2HU8+Z0iUkBQPJ2bmdz5WgC/DeaUBd0bjn5cxrDpQpCklctRUBs7keI/OEbDVlMzIN7j1D225wYw2pXJmKUjQEFtilQdj4G3lAH5NW5UEuCLF/HrrCLxiDmJmF1E7eFriLBNOiekTZasr+j8i3X6QkcVYbNQFKUXBfKAXfSzne8Ah9oXygteJilrSDd1EX5geEQ1ylJAWNXLHw+t40plrzBg5J994DvTKWo5QCx1tDGiYJZCQrYWO0RKOpQGBUytLAfWNKlAzOltXfk3WAaqLEFuGudRsB67Q+8O8QKAaZc6vpFQ0lQc1j69fHrB2gxYhkJJcfNs+2vKAuVHEErctGRXdNjCcrZQW1L/tHsAk/CSQ9Qo/6xedMkBLRR3whm99Y3tF6UibB4D1IMaVEtTW1ialIjZSAYCr+N6dQUCE23OvWECdSla+4Rn3SSz72P5ReOP4N2oZ2in+KMCMqbADqrD5iUpUzKTqG28IhKrDnzaGCCMWUhGVaiWte9uTmB85KVkqTz/ggJ8mqz2OsbJlqfvDu+/7RGprDkfaJPapBIU/XrAczMIUQLX0P5QbpVZXubByoNZ7nzv7wuqSkHMlyH8Wjeoqj2YB0J7zWzAaOfGAl1tbnJN8pYeQ0/WIktGdQRJSMx3Op5noBHCjSZyr+XIf1DHRolUcszFEKWQyR01PqW9IDmvD+zGVJdTDMd9DYeNzHOXQ9jMJJCrP0FiSSfLXpHKXXqKHUe8pbq8wH9BaOGO4iCpSU/Kf+5mYeFoDtV16CjKPl15FXU/68h1iGkpSDMVfpoE+fP3iIqaAHd1FPu+p5ty0jgqYZhAJZKTpzO56k84Av9oBT2irJDMNi10j19hETDZKlrJPzKc6aPqY3z5hpYBkp2H6mJ0hIQQ5Ys55trARUz8qybMOnLSI2KLClBWymP89faMxioGgIcjTlENNS6HVZiwP1gJBUUpBF7FmO5LP0tEmVUgBIGrW8SdW8oHSKoKdO5unx6RIoLKc/sGgGCnAlhKn75UD4XEF8PqUqm9mVauB4Akp87n0hTm1Lrz3azHYc7xCRiikzlLBuF5h5O/8AOsBZ2F1/YrKVOxJH9/UGOuLze4op7yVbgsfMFwqFWRjaZlwNRpyYXHhHcYiycuo5A3G501gBfEElAloAIHfUSAlKctkhrWOhPnCzWU5llKg/MFm6xNr67/IVEnVgCHy32JjnVTjMsolntz9ICBLSp7wWo0FnUH5dYhrpTLPe0EETNeUCMxI2a0B0ky+85Ntn+jx0lYhkLgMef0jSZWyuzSnIQvUqBP0iGs9HHv5wB6kkKWnNn16tHsc6KvyoACj6CMgJHwhXLliZNmFgC1/aLFmcdSA4Cgw3e0Uhh8qZ9jJQ7FZdojqw6cQe6pwCo2sB1PPpAXPM+JNOC2d35CC+D8ZSpxYKHrHzgmSo6A89ILcPT5iJ0tnZSgm3UtAfRC8cQV5X2eEri6dKmKJUpiNImcRcMLlyFTpSzmSl2PQXilKvFJi1OVEwBqsmIch3jhQlKFH8JgGuoUdY2RUQDWlaD/Nf3iHiOVixci46iB0ueSloiT56nDwE/D68BTH5SbvEmslJKmQ7E2bTz/SBtPhkyYnPLGcD5kpupN906t1EMWEUKyHSgSkjVawSfU/SA1lICVJALZUuS25P9RErJ5nLB2+VI5AQQx0oQCkKuWJV0Y5f50iHgi0JSFki2nkXP5QG2LzwhfZJ0QkAn/fU/wD6gMqcTdt7eH9GOVZXFUxbksSfcvHKXO0G+0BNIJOUcmj2T3bk/rEZM0gO+8b0ySSnxgDmFd8uBYecT1U65s05Q+UeF+UdsEwwsTnygn84cMKn00hDWJ5loCt8cwlaSCxJe5ZmtAyokpTTkk9/MwHT7xizMZxymynMgN/NIXpdbhs0FClFJVa4sPPaAV8IwRc1IXte/hEqTQzJa2mS1EHkHB6iH/hnC5MpJyTgtB2sb8x5Qco6NOZSkkMn6wCDW4SpclMyWkswDMWfTSEOaopUQdQbx9NSJSFIuxilvidgYlT+1lhkqd+TwADCKllDlBmbUM5HL+4U6dZEF6Sozam7f1AR61ehs/Qfy8e0s5h3nJOh5RrOlEnxFvWO0qRoHHnAd5s0lICi42iZhGICWCDe9v0jmqjOU3fe0bYRhqllmJ8BARq6YVqUo78oyjngHvaQWxfCMic0Ly1DSAMrSglwsgGPIHInBt4yAtL4Z4QldAhwLkn3iRjvDc8LzyDlJFwdD4coj/CrFAKVCH6e5EPa57nlAV9K4bqG/wAqpbaMkFzBzD+GJacuZKQ12YawzLCUubPEGmrHU2rmAKYtLenUDplL+kUP/wATCyoJbciPoCvDylDofpFSYROH2lSQ+pgEmvwLs0uZaj1Fx7aQKp6HMe6CIveZhKVaecDZmDyU6pD+EBWNNg5QCoi0AK9LFotPH1y0y2AiqsTX3z4wBDApYzBRmZALsLk+ABt5wUrMTzqUVOUBXdD2ADDQed4V6MnMAPOOsyeQL/zlASa3EjNzHcq9gGHtEIzlANsIm8M0nbLUj73zN9Y1x2hMpYS2odoAdMuXjwRsmUoaiNkJfm8Bsg2hh4cw5SyFAWGkDsMoysgNvFtcLYQkBL2GmkApVtYZQZaSBC7V8RqLhILmL2qOHpE0ATJaVeIhfxbg6UhQmS5CVAfdTY+VoCk66qUq6kt0e+m4/OIqG5RYfEGEyJy85lTUqZiEpZ25hjfqGjrwzwUmbMKjKmBHJbAdG3gF/hGimrmASyT4GLRxaWulpMrFzdSuu94KcPcIy6UhSR3oNcW4aqoop8lFlqlqCfFnHvAUwr4iLR3UhwOcZM4qTXS1yZqAFEd1Q2O0JeK0C5KglTuUhWnP67jyjKCc12uICMzEjcWghIAa+kQAoqUTzL+8EKmeyAlhd3/L6+0B0RNzKH86QVpsORnuXhbppzG4eD+H1jkWvANuG4alSFvytEnA8MXKfKM3QEC3R7RArcQXKQlWUsQ1x6QJHFtQn5CAOTPAPVdgvbSy6QjxIcluloq7GaIyZhSfKCqOJqgllzG8Rb0jTiNKpktMwsoP8w6wC7n6R7GmSMgCfC2OKkJYHQn3ixsK4uzh1RSlPN2EPXA+E/aDZTAG8BaIxRcwMgOG1ELdb8R5NMUyEoUVILLJG738YcaJMillncgX3cxVvFNAiqnrmS0Mo3tv+sA4V/xQp+ytctpCpgONionBSUsoEk+B0hEnYVND9xTDW0PfwwTKlqUJllK3P0gH6XigGsDcTxYMb+cSMbwsqSVSSH1MV5iQmpPeJgOWO1xUVQl1CnVB6rXaF2ZMZUBMw+U2ZXIN6t+TxGqJo19OggpKrAJKgwc2SwZzufIfy8CpySBdiYDpgmJmnnonC+U3HMGyh6RalVw3LxFMuokzBla25G5SRzEUyowX4b4hqKReaSsgH5km6VeI59YBgx7AjImKcqb/AGFz6QDopZVMCQLkwYxvi9VSO8gAnzuwFoH8NozT5Y5qHteAsTAMDSlAIF936w34cgjKwsI8oqU9mGETqSjKS5Pj+cAXpakGJoIMBZCSNb9YJUyoDFUgVqPbWOolpQLMI2XMYPC/i2JjOkFQSh79fOAPy5gVpHdOkBFY5SywM05A6Zh9Iky8epzpNT6wFW8UYHMRNUJkjt0Z1FKg2ZIJJAO8V5xFh5p1BLNnct0BsI+jqkIWokKBsHa/80ijvi4B9uCRomUn3Kj+kAqUaXvyiZNpsyCp77DnE7BsMURobsPUtDQOFVEM9vCAryVTqfQwy8JUSu3Q4cOHhooOEkgZlAmDuCYOEmw3gD/FfDaZ9IlKAAUlJty0MIkvhNMlYK8x6ZXA8TFyyEPLSOgjqqmSRcCAqb/iNNPmZk5iSPkTYPzPIRLxPgXsaOakKe2YdGvFny5aRoAPKIGPqAkTH/CfpAfMpjIxZubbxkAuyjeLBwCpVSALSf8AzEuR4Qgy5Z1a3OH+skhciVMQzBLH+bQHSdxhVKCwmQVDmAbRAwzF8RzPKkqLl+6kGJmHSqoI/wALH94GVUvEUKzCXMSf9d/IQBfEOI8SWCn7EUAjvf4TfqXEA5OMTELBVJUC2gG72LR0VVYspwpNQedjG2E0dYpdpKgdyoX94BmwPjVQIStJCSWL2Ie0dsflCY5S2kBMdw6ZLAz6n2grS0p7EqUdt4BExNTEsekBQrvc4n4grvEdYidhcMYArhaAsh02HUt6aeUQ8TcqOw5RKkVRCcocP7RGqkn2gBZESKGSVKCRqbRqUwzfD6g7WqAIskPAR8UwUyUJUX73tZ4I8ASQupS+wLRY/F+AImSfldhy03tCHwfJMmqUDbx8YC6KFACRE9AG0BsPqwUi8FJU02Zm3/m0B2cuwTyvtHfM2unOPZXgGiQpDwEQoMw2cJ+sRMe4el1EkylOncKGoPPrHTE8YlyRdQHmIHHi2nSM02ehA6m/kNYCq8e+G8ySFzDMC23Yv5wCwCjnz1iQlRSAHcubPtFqYjx/QLHZd9SFd0zAGA6sbn0jlQYhhypwRTTAZiQ4swIGoB3IF2gCWFyBTUwQFE/iUrUneKXxuu+1VsyYbgqYf/VPdHqz+cWj8SMSEikmKGqmSm+6rW8A58opXC5zLBPOAsvh+nKlJUQGSzDqLOYdqOnKr7Qm4FNsG35Q9YWhw0BNVSJCLQJoFoSQtawkBZs+sMiZGYMYqrjnhiaZylJmHIS4F7c4C5qatlEAhaWOlxEiRNBfKoEdC8fOWHS5+dMoqWz5dTF7cF4MimpwlJJKu8okuSTAF52kJ3GdaRTzsp0QfpDlOTYxXfH8zJTTE7qtAUsEmMjuERkAQrqCXLp0y8veK1F/VvpAOTWlA7Mu3KLF4jwQqkpWLKSSfQl4Q8bw4gBY3gLH4QxqQmSHZ0/WDtXxPJ+UMTbyeKFkVakGxIjFYosFwoudYC8qnjCVLcaveJMniWnUAqzNc8niglYksi5JjpLxRaQADblAWZxfj0orAQoEa87wrY1xUVS+zQIWBPJudTGqZTmAlYbh8yepkpKlE7QRpEopzNRUSiZjd0HY84tX4ZYJKRTImBlKU5UdweXSDHE3CFPVsZiWUNFCx/eAoCUXUw3P5xPxaiUQWD5ACttnFreRPlFtYD8L6aWsLUpayDZyw/eGap4XlZ86EgBUvs1pYd4Bwk31IClC/OA+YFGLP+CuGuZ048wgeQc/WBmLfDmb2j04JSqbMQAoMU5FZb9LEv4Ra/BnDwo5CZQ1F1HmTrAFKimduW4bWK641wnslJmoDNYt1i1UyrQF4koBNllJG0BXWEY6zObi0NmHYySRsOTi8VTjFIqSop22idg2NZWCngLnpcQsCD5R4cWKiUuB53hRwbHkqDD00PW0MaKRM4XAt6uPCAhV/C9PUOZ+db6d4oI8MrPC9P4EwlbgTZwUP9ySOjHUw8DD5jd1rDkxH6whcV8JVSiZkqWSrcg38oAPXfClSnNLVoWNkrsfAlP6QoLoavD6hHaSyFJLpI7yTqLEW5xInUtdIUX7VHqH84YpOJLXTkVC3LedruTAAuPscXORTSj91HaEdVWT6JH/AHQrUyrx0xSr7aapexskckgMn2Eb0aQ+kA78IYkQoJMWlhM0BtYqXAZyEkKa4h5w/FXcPaAbarHQh7gAWeF/EMfpSHnTkjoLn0EC5OFJmzM09ZUl7JdvpePKnD8MlFzJY9XUPeAk0/EGFA9o68ydm16w04dx7QlLIm5T+FQI9IR5OO4clX/okE88oMPHDlbSTQCmnQnqUAeloBqoq5E5AUgggxTvxMxN5ikDQFv1i06dcuWshAYHYaRUHH8xE2rMuUB3dW3UdYAJhYlZP8iXL82sw/eMi7eCeHBJo5aJ0qUpd1EsFFlFw5I1vHsBFThHaJWldg5b6veEDiPAhJzIJzC5SdPERatNOJJBhe43w4zJRUgXTfx5wHzzXU2VZfR4jECGDGKcOS0CFoHKAgTCI8SmJhljlG6ZUByRLjqnu3jqmXHtNQTahWSTLUshnYWD2BUo2SOpIgLU+CtcVS50vUJIUOjxZE1BMVRwLKOGzFdqtKysBxKOYDRhmIAPzDp1i2J2KSkmShTpVPAyAAm5G/IdYDemtEHiLFZskyEyhLzTZmR5j5QMpVtuWtBaUZYmdmT32zNzHMDk5gdxVhqZ0q6M+QlWQ6KsXHjuOoEARRKGpYnm0a9neAfANb2lMEEKGRsuZnKDdBLE3A7p6pMMi0QGZbRHqKdwYmoTHikwFWcZ4JmJIDxWFbSZFaNH0NjVGFQgY9w2lb7QFbU09aC6SYb+HeNFSy0z1hcxTClyj0gQqbeAvjDuNJZ19Xhgk8QSVJfMI+cKacQQyiIdsK4LxGakLHdBDgrVlcG4tqIC1Z1VT1CSnuqG/SKW+LEyTJUKeTqrvL/1D2A5Al7dI6Y7VV1AQicCl/lIuFeChY/WEKumzJ8xcxRKlG5JgIcrWCqKcgAwMpqdS1hCElSibAaw4UVDMSOzmoKFgOx3EAIp55SYKUuJKcMq8Qq+jymIiC0BaGAI7axUz9YcKThWQR3zm8YpfCsbmSS6TDJI46nNAWhSYBSJLCUjxa8G0YNJbuhvCKY/5fN1zGC+GcW1MyySYB+rqaYcyKcAr0zEsEPuf2hJ/wD53WypnaDJNcuSlTG5vZTRY3CkkiTmV8yy5P8APODiRAQ6NU1SASgS9spOY2G5FoyJ4EZAKeG1ecJUxAWkEOGfy25x0xWR2iCgFs1oCY5xOino/tKxcMAgG+c6J6X9o84H4iVWSkzJiAklRZtxqk+n0gK242wE083K7hQcH6iEqfKaL8+JeFCdSlYHelHN5fe9r+UUtIwybUTOzkIVMUT90aPzOiR1MAGTLiZTUylqCEJUpR0ADk+QiyuH/hCsgKq5uQf9OWxPms2Hk8WVgnDlNSJaRKSl2dWqi3NRvAVbwx8KZk1l1ilSkf8ATS2c+JuEj1PhB/i6gpqSmTR08sIStQdjctclRJzLLPryiyMsIXxKpWVJnEsgKyENpmSpObUaPvs/OARqanM2clCZikhSkJY2IcgDzPd03i5ZtbJE1Ehjn0T3VMGD2WzAsNHG0VdhakyKwKKgpl5iLF0HL3ksByQphtpFiq4iRL7k0BBKVLlqBdEwAP3Dza7HrrACOJKycVidJkTCZLtODHdlJMpwpSbEP7wz4diCZ0tMxNgpKVB/9khX5t5QCqcRM+np6qVYqUApOZu6p0zGcOopDlmctChxbNrO0lmhkTkokgZTkUm4GV8pHecOCGe8A210gUdSmpDiTMOSYHOVBUfmyizFTEk6X5mG6WoEAguIR8K4iFdTrp5yTT1SkKGRaSl3Hzywsd4dLke8bcB41/8ADVKyTJIZd99Ab3Vm1fqOcA9iNFxpnjhMqdWBtAcK7rCxi6mL7QdqVKOunjCvjgUxvaATuIpgUDziu6uT3rQ14ytTnUwDkyCtWkAb+HuA9vUS83ygufK5j6JQLMNIrr4W0ASlc1uSQfc/l6xYIWWtAV78bMdTKpRTAArn3vfKlJDkdSbDzikAsiWWbvW1v6RYfxwlNUylkuVSyG5ZVbesJNTgcxFMKhQDKUAL3DuzpaAMfDaZSS5kxdRNEs5WSSPVrawa4u4rpJhlIp0qOU3mqDODsBrrCDJw6cqWqamWrs0h1KawGmsNHBnAy65ImdoEIz5TZyANTrYvYCA6VNJnBPOF2ppikxZGKcMLo2Tmzo0CyGPgRzhexSgcPAKiBHeU8brkMYkU0l4DtQ0ylG0WDgeHZAkAX3hdwqUEM9yYeaCYEyzMVokE+0B0oeNewrTImEdgwS/4F7nw5iLKlLBuI+WZ1Ypc1SzqpRV6mLm+H/GKFy5cib3VJAQlWx2D8uUBYjxkax5AfPPG2KSp1AQgkE1JN9CA+VudiDG3wvx9CP8AEtQSQoW5gnukeCrEcjAXjFXaCVKTlupSzlYXygOw08OkTOCeA500pnTGSM3+JKrdo3zKO4QPc+4XvUBCwQohlJLjpu/6wJwSpoZSuwkKloJZkggFXg91GEnG+IDKlfZUKzF3mrQfnUSxSlTfKlgH3ZrR3wOX9hNNOrEjtJ6iAVf+ylhtsouH5CAs949eNEKSwIIY3BdwX0bnrHQCAyAnF1B21NMTuzjy19oO5Y0WgHaAoudMQqnC1ghScwKmICW+ZGYfdUoWewdW7GJ9FP8At1IKW+cf5KZRUxP45b82Nn1tpaJfE9OKWtXLPdlVSQAcrhKg+UhLMSCwOtrb3FTqg9lLWJYSUn5kM0sgAyyAL5goKYfhcXIDBvwcaxK0qBqBSyipKjLSFEH5lAoUCSHLmxIdosTFlTKiSj7JPl9qRmGeyZiWILM5DP8Ad0Ni0cuEMaTMQtBZFR86wLAkgDtUjkbO2hvvHLDa5NTTzZapGdcmapMwIZMwXOWcgczckDcKZ9CHvEc6mT2AqUlKc4SibtLmAuO+C6C6WfTV44cS0cn7TKVLnS0VZy9zNlVOQ+njYsYTKmlVU/8AhlVCxlX3O2KgklTsm93N73D8nu3pwtCpkmZX0k4TacJCJiM01ByF0k9m6tbsp/HWAdKGqExL5SkiyknUFn2sbEaRtNQ8K+MzZlNVy6tz9lWgJWLpY97vKSdCxs4GhGrQ3SVhSQpOhAI216HSAEVyikQkY5XAlosTE5Kciio5QASTyADkxUNLiCKpauzCgzOFi7HQ2MAOr0BR0jlJw5g7Q4UfD6iXYARrWUffypGnpAZwridTTz0UypYVTzAFJUBdJUNSdxmBB5RZVLdA8IrHAElWLyWKyg0aZmUEhKSXBJDsXLXD6iG3hzG/80yjmghUtaky1nRaQXSH/Fl9WgFj444ZmppU8B+zmMfBX7gRWvEvE6KiVLky5SpSEsVjM4UQBbwF2i/eOMGNVRTpKSApQBBOgIUCPpHz9xZwpMo8hUUqSpxmT+IXNjpYwHPEOJVLkimlJySgGN7qALjN+kMXw444lUEqbLnS1rzKC05G1ZiC5HIR0+HvCUtaDU1DFLEoSbC2/XTSAODYBUYlUTOxQhOqlFsktPIWBYnl4wBvij4lTaoIQmUmUhK8x72YqZwASwYXjaXUiagKTofWFHHsJXS1EyRMYqQWcaGzgh9oe6niOlqZcmVTSJiFy5YCiyWYAA6F1X36wC7V0/KOCJagQwgpNN7xIpcgLmAIYNQrIClQQ4xxDsqXsx8y7eW8dqesGUFmAhN4mqzMXrYbQEXh+iE6oRLP3vXy6xcdJwnLk1EifLITLSwWFaWFj0JPPdopPDp65cxMxBZSSFA9RpFkzOPJ66YpMi0wFJmXyuR3sobXo9oCz5+PU6FFKpoBGo5RkUxSz1FO56xkBy4C4cNZMNbNAElDS5aHvMVYd4jRLm51P1ZeOcZ+yp7FNpi0s6bdnLe+U81nlpblGRkAK4WwiWhMysnB5FOSoJFytYygOCdE+6i8NOP05xCTQ5XTLnHOpRCSUvLJ8XYqZt2J0jIyAYcGwpVORJCiuUzpKiMySGGVmuLghuRgvLSrMrMzfdI16gjxjyMgO2WPCiMjICrfjfKKZUiaNQsj2zD6H1gJSkz6chBexVMb7ic2d0hYAV3nOr2trGRkBDwqonWnBf8AmlHMk6Ol150qGjES1H152tHhyQmZUKq5bhFVToPIhUtRSoePfF+kZGQEHE+FpyqCaifPM9ctRmyln5wlL91SjYkpcbXMMHBlaZ9JKWp8zFJe5OUlIJO5IAPnHkZAFK+gROQqXMSFJVqD6g+IN4HYGhaQuRMY9mQEEaqQQMpVyLghukZGQHnFtCZlHUpT8ypKwL2+UmKi+FuEzlVSlobsRaY5vldg3XMPQHnGRkBbWNzkU9PNmAfIhSgG5BxAXAuHZiM6ps0ThMAWhWXIRmBdOVywulrmMjIDhw/gBkVkuZMvM7Jco5VdwBIlGWkDe2cu2vlC/wAdSlSa3tEFjMSJqCCRdLi7cmfxjIyAeeG8cFVTBSwywEpmACzqsCOhPpFMfE/E5cyYinlgnsMwUo/eV0HRtYyMgBcniucJPZZU2RkQoWKbMot94kekS+CuMV4fLnJRKStcwghSiQAwa4HzD0j2MgAWN1kyomKnzGzKN2DD0jng8+ZLmpmSlZVpuOvMEbg8oyMgDCsYE6YSpCZebZD5Qd2G14J0WHFREZGQB+tpChIRuYVsVpCkttGRkAyUvC0nsaGao3mzMq03ZQdxcacofOK/s9PTdgadIlLSoJIA7q2sw1HjGRkAg4T2aUNMzu7hm0YNGRkZAf/Z', diff --git a/packages/ui/src/components/Table/stories/utils.tsx b/packages/ui/src/components/Table/stories/utils.tsx new file mode 100644 index 0000000000..130469e5d7 --- /dev/null +++ b/packages/ui/src/components/Table/stories/utils.tsx @@ -0,0 +1,57 @@ +/* eslint-disable no-restricted-syntax */ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Meta } from '@storybook/react-vite'; +import { MemoryRouter } from 'react-router-dom'; +import { BUIProvider } from '../../../provider'; +import { CellText, type ColumnConfig } from '..'; + +// Selection demo data +export const selectionData = [ + { id: 1, name: 'Component Library', owner: 'Design System', type: 'library' }, + { id: 2, name: 'API Gateway', owner: 'Platform', type: 'service' }, + { id: 3, name: 'Documentation Site', owner: 'DevEx', type: 'website' }, +]; + +// Selection demo columns +export const selectionColumns: ColumnConfig<(typeof selectionData)[0]>[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + { id: 'type', label: 'Type', cell: item => }, +]; + +// Shared meta config for Table stories +export const tableStoriesMeta = { + decorators: [ + (Story: () => JSX.Element) => ( + + + + + + ), + ], +} satisfies Partial; diff --git a/packages/ui/src/components/Table/types.ts b/packages/ui/src/components/Table/types.ts index be9e860a0c..800a56052a 100644 --- a/packages/ui/src/components/Table/types.ts +++ b/packages/ui/src/components/Table/types.ts @@ -16,29 +16,262 @@ import { CellProps as ReactAriaCellProps, - ColumnProps as AriaColumnProps, + ColumnProps as ReactAriaColumnProps, + TableProps as ReactAriaTableProps, + TableHeaderProps as ReactAriaTableHeaderProps, + TableBodyProps as ReactAriaTableBodyProps, + RowProps as ReactAriaRowProps, } from 'react-aria-components'; +import type { ReactElement, ReactNode } from 'react'; +import type { SortDescriptor as ReactStatelySortDescriptor } from 'react-stately'; +import type { ColumnSize, ColumnStaticSize } from 'react-stately'; import type { TextColors } from '../../types'; +import { TablePaginationProps } from '../TablePagination'; + +/** + * @public + */ +export type SortDescriptor = ReactStatelySortDescriptor; /** @public */ -export interface CellProps extends ReactAriaCellProps { +export interface SortState { + descriptor: SortDescriptor | null; + onSortChange: (descriptor: SortDescriptor) => void; +} + +/** @public */ +export type TableRootOwnProps = { + stale?: boolean; + loading?: boolean; +}; + +/** @public */ +export interface TableRootProps + extends TableRootOwnProps, + Omit {} + +/** @public */ +export type TableHeaderOwnProps = { + columns?: ReactAriaTableHeaderProps['columns']; + children?: ReactAriaTableHeaderProps['children']; +}; + +/** @public */ +export interface TableHeaderProps + extends TableHeaderOwnProps, + Omit, keyof TableHeaderOwnProps> {} + +/** @public */ +export type TableBodyOwnProps = {}; + +/** @public */ +export interface TableBodyProps + extends TableBodyOwnProps, + Omit, keyof TableBodyOwnProps> {} + +/** @public */ +export type RowOwnProps = { + columns?: ReactAriaRowProps['columns']; + children?: ReactAriaRowProps['children']; + href?: string; + noTrack?: boolean; +}; + +/** @public */ +export interface RowProps + extends RowOwnProps, + Omit, keyof RowOwnProps> {} + +/** @public */ +export type ColumnOwnProps = { + children?: React.ReactNode; + className?: string; +}; + +/** @public */ +export interface ColumnProps + extends ColumnOwnProps, + Omit {} + +/** + * Own props for the {@link Cell} component. + * + * @public + */ +export type CellOwnProps = { + className?: string; +}; + +/** + * Props for the {@link Cell} component. + * + * `Cell` is a generic cell wrapper for custom cell content. When rendering + * cells via {@link ColumnConfig.cell} or a custom {@link RowRenderFn}, the + * returned element **must** be a cell component (`Cell`, `CellText`, or + * `CellProfile`) at the top level. Returning bare text or other elements + * without a cell wrapper will break the table layout. + * + * @public + */ +export interface CellProps + extends CellOwnProps, + Omit {} + +/** + * Own props for the {@link CellText} component. + * + * @public + */ +export type CellTextOwnProps = { title: string; description?: string; color?: TextColors; leadingIcon?: React.ReactNode | null; href?: string; -} + className?: string; +}; -/** @public */ -export interface CellProfileProps extends ReactAriaCellProps { +/** + * Props for the {@link CellText} component. + * + * `CellText` renders a table cell with a title and optional description. It + * is one of the cell components (`Cell`, `CellText`, `CellProfile`) that + * **must** be used as the top-level element returned from + * {@link ColumnConfig.cell} or a custom {@link RowRenderFn}. + * + * @public + */ +export interface CellTextProps + extends CellTextOwnProps, + Omit {} + +/** + * Own props for the {@link CellProfile} component. + * + * @public + */ +export type CellProfileOwnProps = { src?: string; name?: string; href?: string; description?: string; color?: TextColors; + className?: string; +}; + +/** + * Props for the {@link CellProfile} component. + * + * `CellProfile` renders a table cell with an avatar, name, and optional + * description. It is one of the cell components (`Cell`, `CellText`, + * `CellProfile`) that **must** be used as the top-level element returned + * from {@link ColumnConfig.cell} or a custom {@link RowRenderFn}. + * + * @public + */ +export interface CellProfileProps + extends CellProfileOwnProps, + Omit {} + +/** @public */ +export interface TableItem { + id: string | number; } /** @public */ -export interface ColumnProps extends Omit { - children?: React.ReactNode; +export interface NoPagination { + type: 'none'; +} + +/** @public */ +export interface PagePagination extends TablePaginationProps { + type: 'page'; +} + +/** @public */ +export type TablePaginationType = NoPagination | PagePagination; + +/** + * Configuration for a single table column. + * + * @public + */ +export interface ColumnConfig { + id: string; + label: string; + /** + * Renders the cell content for this column. + * + * **Important:** The returned element **must** be a cell component at the + * top level — either `Cell`, `CellText`, or `CellProfile`. Returning bare + * text, fragments, or other elements without a cell wrapper will break the + * table layout. + * + * @example + * ```tsx + * cell: item => + * ``` + */ + cell: (item: T) => ReactElement; + header?: () => ReactElement; + isSortable?: boolean; + isHidden?: boolean; + width?: ColumnSize | null; + defaultWidth?: ColumnSize | null; + minWidth?: ColumnStaticSize | null; + maxWidth?: ColumnStaticSize | null; + isRowHeader?: boolean; +} + +/** @public */ +export interface RowConfig { + getHref?: (item: T) => string | undefined; + onClick?: (item: T) => void; + getIsDisabled?: (item: T) => boolean; +} + +/** + * Custom render function for table rows. + * + * When using a custom row render function, each cell rendered inside the row + * **must** use a cell component (`Cell`, `CellText`, or `CellProfile`) as + * the top-level element. Returning bare text or other elements without a + * cell wrapper will break the table layout. + * + * @public + */ +export type RowRenderFn = (params: { + item: T; + index: number; +}) => ReactNode; + +/** @public */ +export interface TableSelection { + mode?: ReactAriaTableProps['selectionMode']; + behavior?: ReactAriaTableProps['selectionBehavior']; + selected?: ReactAriaTableProps['selectedKeys']; + onSelectionChange?: ReactAriaTableProps['onSelectionChange']; +} + +/** @public */ +export type VirtualizedProp = + | boolean + | { rowHeight: number } + | { estimatedRowHeight: number }; + +/** @public */ +export interface TableProps { + columnConfig: readonly ColumnConfig[]; + data: T[] | undefined; + loading?: boolean; + isStale?: boolean; + error?: Error; + pagination: TablePaginationType; + sort?: SortState; + rowConfig?: RowConfig | RowRenderFn; + selection?: TableSelection; + emptyState?: ReactNode; + className?: string; + style?: React.CSSProperties; + virtualized?: VirtualizedProp; } diff --git a/packages/ui/src/components/TablePagination/TablePagination.stories.tsx b/packages/ui/src/components/TablePagination/TablePagination.stories.tsx index 4550d00abb..dd76f7c55a 100644 --- a/packages/ui/src/components/TablePagination/TablePagination.stories.tsx +++ b/packages/ui/src/components/TablePagination/TablePagination.stories.tsx @@ -14,21 +14,21 @@ * limitations under the License. */ -// TODO: Bring useArgs() back when we update Storybook to 9 -// import { useArgs } from 'storybook/preview-api'; import type { Meta, StoryObj } from '@storybook/react-vite'; import { TablePagination } from './TablePagination'; +const noop = () => {}; + const meta = { title: 'Backstage UI/TablePagination', component: TablePagination, argTypes: { offset: { control: 'number' }, pageSize: { control: 'radio', options: [5, 10, 20, 30, 40, 50] }, - rowCount: { control: 'number' }, - showPageSizeOptions: { control: 'boolean', defaultValue: true }, - setOffset: { action: 'setOffset' }, - setPageSize: { action: 'setPageSize' }, + totalCount: { control: 'number' }, + hasNextPage: { control: 'boolean' }, + hasPreviousPage: { control: 'boolean' }, + showPageSizeOptions: { control: 'boolean' }, }, } satisfies Meta; @@ -39,21 +39,70 @@ export const Default: Story = { args: { offset: 0, pageSize: 10, - rowCount: 100, - }, - render: args => { - // const [{}, updateArgs] = useArgs(); - - return ( - { - // updateArgs({ offset: value }); - // }} - // setPageSize={value => { - // updateArgs({ pageSize: value }); - // }} - /> - ); + totalCount: 100, + hasNextPage: true, + hasPreviousPage: false, + onNextPage: noop, + onPreviousPage: noop, + onPageSizeChange: noop, + showPageSizeOptions: true, + }, +}; + +export const FirstPage: Story = { + args: { + ...Default.args, + }, +}; + +export const LastPage: Story = { + args: { + ...Default.args, + offset: 90, + hasNextPage: false, + hasPreviousPage: true, + }, +}; + +export const MiddlePage: Story = { + args: { + ...Default.args, + offset: 40, + hasPreviousPage: true, + }, +}; + +export const WithoutPageSizeOptions: Story = { + args: { + ...Default.args, + showPageSizeOptions: false, + }, +}; + +export const CursorPagination: Story = { + args: { + ...Default.args, + offset: undefined, + }, +}; + +export const CustomLabel: Story = { + args: { + ...Default.args, + offset: 20, + hasPreviousPage: true, + getLabel: ({ offset, pageSize, totalCount }) => { + const page = Math.floor((offset ?? 0) / pageSize) + 1; + const totalPages = Math.ceil((totalCount ?? 0) / pageSize); + return `Page ${page} of ${totalPages}`; + }, + }, +}; + +export const EmptyState: Story = { + args: { + ...Default.args, + totalCount: 0, + hasNextPage: false, }, }; diff --git a/packages/ui/src/components/TablePagination/TablePagination.tsx b/packages/ui/src/components/TablePagination/TablePagination.tsx index fd891be073..b17286e262 100644 --- a/packages/ui/src/components/TablePagination/TablePagination.tsx +++ b/packages/ui/src/components/TablePagination/TablePagination.tsx @@ -14,13 +14,37 @@ * limitations under the License. */ -import clsx from 'clsx'; -import { Text, ButtonIcon, Select } from '../..'; -import type { TablePaginationProps } from './types'; -import { useStyles } from '../../hooks/useStyles'; +import { useId } from 'react-aria'; +import { Text } from '../Text'; +import { ButtonIcon } from '../ButtonIcon'; +import { Select } from '../Select'; +import type { TablePaginationProps, PageSizeOption } from './types'; +import { useDefinition } from '../../hooks/useDefinition'; import { TablePaginationDefinition } from './definition'; -import styles from './TablePagination.module.css'; import { RiArrowLeftSLine, RiArrowRightSLine } from '@remixicon/react'; +import { useMemo } from 'react'; + +function getOptionValue(option: number | PageSizeOption): number { + return typeof option === 'number' ? option : option.value; +} + +function isNumberArray( + options: number[] | PageSizeOption[], +): options is number[] { + return options.length > 0 && typeof options[0] === 'number'; +} + +function normalizePageSizeOptions( + options: number[] | PageSizeOption[], +): PageSizeOption[] { + if (isNumberArray(options)) { + return options.map(value => ({ + label: `Show ${value} results`, + value, + })); + } + return options; +} /** * Pagination controls for Table components with page navigation and size selection. @@ -28,102 +52,100 @@ import { RiArrowLeftSLine, RiArrowRightSLine } from '@remixicon/react'; * @public */ export function TablePagination(props: TablePaginationProps) { - const { classNames, cleanedProps } = useStyles(TablePaginationDefinition, { - showPageSizeOptions: true, - ...props, - }); + const { ownProps } = useDefinition(TablePaginationDefinition, props); const { - className, - offset, + classes, pageSize, - rowCount, + pageSizeOptions, + offset, + totalCount, + hasNextPage, + hasPreviousPage, onNextPage, onPreviousPage, onPageSizeChange, - setOffset, - setPageSize, showPageSizeOptions, - ...rest - } = cleanedProps; + getLabel, + showPaginationLabel, + } = ownProps; - const currentOffset = offset ?? 0; - const currentPageSize = pageSize ?? 10; + const labelId = useId(); + const normalizedOptions = useMemo( + () => normalizePageSizeOptions(pageSizeOptions), + [pageSizeOptions], + ); - const fromCount = currentOffset + 1; - const toCount = Math.min(currentOffset + currentPageSize, rowCount ?? 0); - - const nextPage = () => { - const totalRows = rowCount ?? 0; - const nextOffset = currentOffset + currentPageSize; - - // Check if there are more items to navigate to - if (nextOffset < totalRows) { - onNextPage?.(); // Analytics tracking - setOffset?.(nextOffset); // Navigate to next page + const effectivePageSize = useMemo(() => { + const isValid = pageSizeOptions.some( + opt => getOptionValue(opt) === pageSize, + ); + if (isValid) { + return pageSize; } - }; + const firstValue = getOptionValue(pageSizeOptions[0]); + console.warn( + `TablePagination: pageSize ${pageSize} is not in pageSizeOptions, using ${firstValue} instead`, + ); + return firstValue; + }, [pageSize, pageSizeOptions]); - const previousPage = () => { - // Check if we can go to previous page - if (currentOffset > 0) { - onPreviousPage?.(); // Analytics tracking - const prevOffset = Math.max(0, currentOffset - currentPageSize); - setOffset?.(prevOffset); // Navigate to previous page - } - }; + const hasItems = totalCount !== undefined && totalCount !== 0; + + const showLabel = hasItems && showPaginationLabel !== false; + + let label = `${totalCount} items`; + if (getLabel) { + label = getLabel({ pageSize: effectivePageSize, offset, totalCount }); + } else if (offset !== undefined) { + const fromCount = offset + 1; + const toCount = Math.min(offset + effectivePageSize, totalCount ?? 0); + label = `${fromCount} - ${toCount} of ${totalCount}`; + } return ( -
    -
    +
    +
    {showPageSizeOptions && ( diff --git a/packages/ui/src/components/TextField/definition.ts b/packages/ui/src/components/TextField/definition.ts index c1cab8095d..3d9dcc88fc 100644 --- a/packages/ui/src/components/TextField/definition.ts +++ b/packages/ui/src/components/TextField/definition.ts @@ -14,13 +14,16 @@ * limitations under the License. */ -import type { ComponentDefinition } from '../../types'; +import { defineComponent } from '../../hooks/useDefinition'; +import type { TextFieldOwnProps } from './types'; +import styles from './TextField.module.css'; /** * Component definition for TextField * @public */ -export const TextFieldDefinition = { +export const TextFieldDefinition = defineComponent()({ + styles, classNames: { root: 'bui-TextField', inputWrapper: 'bui-InputWrapper', @@ -28,9 +31,14 @@ export const TextFieldDefinition = { inputIcon: 'bui-InputIcon', inputAction: 'bui-InputAction', }, - dataAttributes: { - invalid: [true, false] as const, - disabled: [true, false] as const, - size: ['small', 'medium'] as const, + bg: 'consumer', + propDefs: { + size: { dataAttribute: true, default: 'small' }, + className: {}, + icon: {}, + placeholder: {}, + label: {}, + description: {}, + secondaryLabel: {}, }, -} as const satisfies ComponentDefinition; +}); diff --git a/packages/ui/src/components/TextField/types.ts b/packages/ui/src/components/TextField/types.ts index 456d6964a0..e65473d0c9 100644 --- a/packages/ui/src/components/TextField/types.ts +++ b/packages/ui/src/components/TextField/types.ts @@ -15,14 +15,39 @@ */ import type { TextFieldProps as AriaTextFieldProps } from 'react-aria-components'; -import { ReactNode } from 'react'; +import type { ReactNode } from 'react'; import type { Breakpoint } from '../../types'; import type { FieldLabelProps } from '../FieldLabel/types'; +/** @public */ +export type TextFieldOwnProps = { + /** + * The size of the text field + * @defaultValue 'medium' + */ + size?: 'small' | 'medium' | Partial>; + + className?: string; + + /** + * An icon to render before the input + */ + icon?: ReactNode; + + /** + * Text to display in the input when it has no value + */ + placeholder?: string; + + label?: FieldLabelProps['label']; + description?: FieldLabelProps['description']; + secondaryLabel?: FieldLabelProps['secondaryLabel']; +}; + /** @public */ export interface TextFieldProps - extends AriaTextFieldProps, - Omit { + extends Omit, + TextFieldOwnProps { /** * The HTML input type for the text field * @@ -31,19 +56,4 @@ export interface TextFieldProps * search inputs and `PasswordField` for password inputs. */ type?: 'text' | 'email' | 'tel' | 'url'; - /** - * An icon to render before the input - */ - icon?: ReactNode; - - /** - * The size of the text field - * @defaultValue 'medium' - */ - size?: 'small' | 'medium' | Partial>; - - /** - * Text to display in the input when it has no value - */ - placeholder?: string; } diff --git a/packages/ui/src/components/ToggleButton/ToggleButton.module.css b/packages/ui/src/components/ToggleButton/ToggleButton.module.css new file mode 100644 index 0000000000..729ede2af2 --- /dev/null +++ b/packages/ui/src/components/ToggleButton/ToggleButton.module.css @@ -0,0 +1,109 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@layer tokens, base, components, utilities; + +@layer components { + .bui-ToggleButton { + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--bui-space-1_5); + border: none; + border-radius: var(--bui-radius-2); + background: var(--bui-bg-neutral-1); + color: var(--bui-fg-primary); + font-family: var(--bui-font-regular); + font-weight: var(--bui-font-weight-bold); + padding: 0 var(--bui-space-2); + cursor: pointer; + transition: background-color 150ms ease, box-shadow 150ms ease, + color 150ms ease, transform 100ms ease; + + &[data-selected], + &[data-pressed] { + background: var(--bui-bg-solid); + color: var(--bui-fg-solid); + } + + &:not([data-selected])[data-hovered] { + background: var(--bui-bg-neutral-2); + } + + &[data-disabled] { + background: var(--bui-bg-neutral-1-disabled); + color: var(--bui-fg-disabled); + } + + &[data-disabled][data-hovered] { + background: var(--bui-bg-neutral-1-disabled); + } + + &[data-disabled][data-selected] { + background: var(--bui-bg-solid-disabled); + color: var(--bui-fg-disabled); + } + } + + .bui-ToggleButton[data-focus-visible] { + outline: none; + box-shadow: inset 0 0 0 2px var(--bui-ring); + } + + .bui-ToggleButton[data-disabled] { + cursor: not-allowed; + } + + .bui-ToggleButton[data-pressed] { + transform: scale(0.98); + } + + .bui-ToggleButton[data-disabled][data-pressed] { + transform: none; + } + + .bui-ToggleButton[data-size='small'] { + height: 2rem; + font-size: var(--bui-font-size-3); + padding: 0 var(--bui-space-2); + + svg { + width: 1rem; + height: 1rem; + } + } + + .bui-ToggleButton[data-size='medium'] { + height: 2.5rem; + font-size: var(--bui-font-size-4); + padding: 0 var(--bui-space-3); + + svg { + width: 1.25rem; + height: 1.25rem; + } + } + + .bui-ToggleButtonContent { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--bui-space-1_5); + height: 100%; + width: 100%; + } +} diff --git a/packages/ui/src/components/ToggleButton/ToggleButton.stories.tsx b/packages/ui/src/components/ToggleButton/ToggleButton.stories.tsx new file mode 100644 index 0000000000..876ff654b0 --- /dev/null +++ b/packages/ui/src/components/ToggleButton/ToggleButton.stories.tsx @@ -0,0 +1,306 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import preview from '../../../../../.storybook/preview'; +import { ToggleButton } from './ToggleButton'; +import { Box } from '../Box'; +import { Flex } from '../Flex'; +import { Text } from '../Text'; +import { useState } from 'react'; +import { + RiCheckLine, + RiStarFill, + RiStarLine, + RiCloudLine, + RiArrowRightSLine, +} from '@remixicon/react'; + +const meta = preview.meta({ + title: 'Backstage UI/ToggleButton', + component: ToggleButton, + argTypes: { + size: { + control: 'select', + options: ['small', 'medium'], + }, + }, +}); + +export const Default = meta.story({ + args: { + children: 'Toggle', + }, +}); + +export const Backgrounds = meta.story({ + args: { + children: 'Toggle', + }, + parameters: { + argTypes: { + size: { + control: false, + }, + }, + }, + render: () => ( + + + Default + + Toggle + + + + On Neutral 1 + + Toggle + + + + On Neutral 2 + + + Toggle + + + + + On Neutral 3 + + + + Toggle + + + + + + ), +}); + +export const Sizes = meta.story({ + args: { + children: 'Toggle', + }, + parameters: { + argTypes: { + size: { + control: false, + }, + }, + }, + render: () => ( + + Small + Medium + + ), +}); + +export const WithIcons = meta.story({ + args: { + children: 'Favorite', + }, + render: args => ( + + } /> + } defaultSelected /> + } /> + } /> + + ), +}); + +export const IconsOnly = meta.story({ + render: () => ( + + } /> + } + /> + } /> + + ), +}); + +export const IconsAndText = meta.story({ + render: () => ( + + }>Cloud + }>Star + }>Next + + ), +}); + +export const Disabled = meta.story({ + render: () => ( + + Disabled + + Selected + + + ), +}); + +export const Controlled = meta.story({ + render: () => { + const [selected, setSelected] = useState(false); + return ( + + : } + > + {selected ? 'Starred' : 'Not starred'} + + State: {selected ? 'selected' : 'unselected'} + + ); + }, +}); + +export const FunctionChildren = meta.story({ + render: () => { + const [disabled, setDisabled] = useState(false); + return ( + + + + {({ isDisabled, isSelected }) => + isDisabled + ? `Disabled ${isSelected ? '(Selected)' : '(Unselected)'}` + : `Enabled ${isSelected ? '(Selected)' : '(Unselected)'}` + } + + setDisabled(!disabled)} + > + {disabled ? 'Enable' : 'Disable'} + + + + Toggle the button to change the disabled state and see text update + + + ); + }, +}); + +export const DynamicContent = meta.story({ + render: () => { + return ( + + + Example 1: Selection State + + + {({ isSelected }) => (isSelected ? '✓ Selected' : 'Not Selected')} + + + {({ isSelected }) => (isSelected ? '✓ Selected' : 'Not Selected')} + + + + + + Example 2: Multiple States + + + {({ isSelected, isHovered }) => { + const states = []; + if (isSelected) states.push('on'); + else states.push('off'); + if (isHovered) states.push('hovered'); + return `Email (${states.join(', ')})`; + }} + + + {({ isSelected, isHovered }) => { + const states = []; + if (isSelected) states.push('on'); + else states.push('off'); + if (isHovered) states.push('hovered'); + return `Push (${states.join(', ')})`; + }} + + + + + + Example 3: Conditional Icons + + + {({ isSelected }) => ( + <> + {isSelected ? : } + {isSelected ? 'Starred' : 'Star'} + + )} + + + + + + Example 4: Status Indicators + + + {({ isSelected }) => ( + + + Active + + )} + + + {({ isSelected }) => ( + + + Inactive + + )} + + + + + ); + }, +}); diff --git a/packages/ui/src/components/ToggleButton/ToggleButton.tsx b/packages/ui/src/components/ToggleButton/ToggleButton.tsx new file mode 100644 index 0000000000..430ced34a6 --- /dev/null +++ b/packages/ui/src/components/ToggleButton/ToggleButton.tsx @@ -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 { forwardRef, Ref } from 'react'; +import { ToggleButton as AriaToggleButton } from 'react-aria-components'; +import type { ToggleButtonProps } from './types'; +import { useDefinition } from '../../hooks/useDefinition'; +import { ToggleButtonDefinition } from './definition'; + +/** + * A button that maintains a pressed or unpressed state, with optional start and end icon slots. + * + * @public + */ +export const ToggleButton = forwardRef( + (props: ToggleButtonProps, ref: Ref) => { + const { ownProps, restProps, dataAttributes } = useDefinition( + ToggleButtonDefinition, + props, + ); + const { classes, children, iconStart, iconEnd } = ownProps; + + return ( + + {renderProps => { + // If children is a function, call it with render props; otherwise use children as-is + const renderedChildren = + typeof children === 'function' ? children(renderProps) : children; + + return ( + + {iconStart} + {renderedChildren} + {iconEnd} + + ); + }} + + ); + }, +); + +ToggleButton.displayName = 'ToggleButton'; diff --git a/packages/ui/src/components/ToggleButton/definition.ts b/packages/ui/src/components/ToggleButton/definition.ts new file mode 100644 index 0000000000..5137714417 --- /dev/null +++ b/packages/ui/src/components/ToggleButton/definition.ts @@ -0,0 +1,38 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { defineComponent } from '../../hooks/useDefinition'; +import type { ToggleButtonOwnProps } from './types'; +import styles from './ToggleButton.module.css'; + +/** + * Component definition for ToggleButton + * @public + */ +export const ToggleButtonDefinition = defineComponent()({ + styles, + classNames: { + root: 'bui-ToggleButton', + content: 'bui-ToggleButtonContent', + }, + propDefs: { + size: { dataAttribute: true, default: 'small' }, + iconStart: {}, + iconEnd: {}, + children: {}, + className: {}, + }, +}); diff --git a/packages/ui/src/components/ToggleButton/index.ts b/packages/ui/src/components/ToggleButton/index.ts new file mode 100644 index 0000000000..2d50f072e4 --- /dev/null +++ b/packages/ui/src/components/ToggleButton/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './ToggleButton'; +export * from './types'; +export { ToggleButtonDefinition } from './definition'; diff --git a/packages/ui/src/components/ToggleButton/types.ts b/packages/ui/src/components/ToggleButton/types.ts new file mode 100644 index 0000000000..ba6569c2a2 --- /dev/null +++ b/packages/ui/src/components/ToggleButton/types.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 type { Breakpoint } from '../..'; +import type { ReactElement } from 'react'; +import type { ToggleButtonProps as AriaToggleButtonProps } from 'react-aria-components'; + +/** @public */ +export type ToggleButtonOwnProps = { + size?: 'small' | 'medium' | Partial>; + iconStart?: ReactElement; + iconEnd?: ReactElement; + children?: AriaToggleButtonProps['children']; + className?: string; +}; + +/** + * Properties for {@link ToggleButton} + * + * @public + */ +export interface ToggleButtonProps + extends Omit, + ToggleButtonOwnProps {} diff --git a/packages/ui/src/components/ToggleButtonGroup/ToggleButtonGroup.module.css b/packages/ui/src/components/ToggleButtonGroup/ToggleButtonGroup.module.css new file mode 100644 index 0000000000..827b7f18d6 --- /dev/null +++ b/packages/ui/src/components/ToggleButtonGroup/ToggleButtonGroup.module.css @@ -0,0 +1,109 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@layer tokens, base, components, utilities; + +@layer components { + .bui-ToggleButtonGroup { + display: inline-flex; + align-items: center; + flex-wrap: nowrap; + border-radius: var(--bui-radius-2); + overflow: hidden; + box-shadow: inset 0 0 0 1px var(--bui-border-2); + width: fit-content; + } + + .bui-ToggleButtonGroup[data-orientation='vertical'] { + flex-direction: column; + align-items: stretch; + width: fit-content; + } + + .bui-ToggleButtonGroup > :global(.bui-ToggleButton) { + border-radius: 0; + margin: 0; + box-shadow: none; + border: 0; + } + + /* Horizontal radius rules (default orientation) */ + .bui-ToggleButtonGroup:not([data-orientation='vertical']) + > :global(.bui-ToggleButton) { + border-radius: 0; + } + + .bui-ToggleButtonGroup:not([data-orientation='vertical']) + > :global(.bui-ToggleButton):first-child { + border-radius: var(--bui-radius-2) 0 0 var(--bui-radius-2); + } + + .bui-ToggleButtonGroup:not([data-orientation='vertical']) + > :global(.bui-ToggleButton):last-child { + border-radius: 0 var(--bui-radius-2) var(--bui-radius-2) 0; + } + + .bui-ToggleButtonGroup:not([data-orientation='vertical']) + > :global(.bui-ToggleButton):only-child { + border-radius: var(--bui-radius-2); + } + + /* Horizontal dividers */ + .bui-ToggleButtonGroup:not([data-orientation='vertical']) + :global(.bui-ToggleButton) + + :global(.bui-ToggleButton) { + border-left: 1px solid var(--bui-border-2); + } + + /* Vertical dividers */ + .bui-ToggleButtonGroup[data-orientation='vertical'] + :global(.bui-ToggleButton) { + width: 100%; + } + + .bui-ToggleButtonGroup[data-orientation='vertical'] + :global(.bui-ToggleButton) + + :global(.bui-ToggleButton) { + border-top: 1px solid var(--bui-border-2); + } + + /* Vertical radius rules */ + .bui-ToggleButtonGroup[data-orientation='vertical'] + > :global(.bui-ToggleButton) { + border-radius: 0; + } + + .bui-ToggleButtonGroup[data-orientation='vertical'] + > :global(.bui-ToggleButton):first-child { + border-radius: var(--bui-radius-2) var(--bui-radius-2) 0 0; + } + + .bui-ToggleButtonGroup[data-orientation='vertical'] + > :global(.bui-ToggleButton):last-child { + border-radius: 0 0 var(--bui-radius-2) var(--bui-radius-2); + } + + .bui-ToggleButtonGroup[data-orientation='vertical'] + > :global(.bui-ToggleButton):only-child { + border-radius: var(--bui-radius-2); + } + + /* Focus ring on group surface */ + .bui-ToggleButtonGroup[data-focus-visible] { + outline: 2px solid var(--bui-ring); + outline-offset: 2px; + } +} diff --git a/packages/ui/src/components/ToggleButtonGroup/ToggleButtonGroup.stories.tsx b/packages/ui/src/components/ToggleButtonGroup/ToggleButtonGroup.stories.tsx new file mode 100644 index 0000000000..c98a2e18f1 --- /dev/null +++ b/packages/ui/src/components/ToggleButtonGroup/ToggleButtonGroup.stories.tsx @@ -0,0 +1,259 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import preview from '../../../../../.storybook/preview'; +import { ToggleButtonGroup } from './ToggleButtonGroup'; +import { ToggleButton } from '../ToggleButton/ToggleButton'; +import { Box } from '../Box'; +import { Flex } from '../Flex'; +import { Text } from '../Text'; +import { useState } from 'react'; +import type { Selection } from 'react-aria-components'; +import { + RiCloudLine, + RiStarLine, + RiStarFill, + RiArrowRightSLine, +} from '@remixicon/react'; + +const meta = preview.meta({ + title: 'Backstage UI/ToggleButtonGroup', + component: ToggleButtonGroup, + argTypes: { + selectionMode: { + control: 'select', + options: ['single', 'multiple'], + }, + orientation: { + control: 'select', + options: ['horizontal', 'vertical'], + }, + }, +}); + +export const SingleSelection = meta.story({ + args: { + selectionMode: 'single', + defaultSelectedKeys: ['dogs'], + }, + render: args => ( + + Dogs + Cats + Birds + + ), +}); + +export const MultipleSelection = meta.story({ + args: { + selectionMode: 'multiple', + defaultSelectedKeys: ['frontend'], + }, + render: args => ( + + Frontend + Backend + Platform + + ), +}); + +export const Backgrounds = meta.story({ + args: { + selectionMode: 'single', + defaultSelectedKeys: ['option1'], + }, + parameters: { + argTypes: { + selectionMode: { + control: false, + }, + }, + }, + render: () => ( + + + Default + + + Option 1 + Option 2 + Option 3 + + + + + On Neutral 1 + + + Option 1 + Option 2 + Option 3 + + + + + On Neutral 2 + + + + Option 1 + Option 2 + Option 3 + + + + + + On Neutral 3 + + + + + Option 1 + Option 2 + Option 3 + + + + + + + ), +}); + +export const DisabledGroup = meta.story({ + args: { + selectionMode: 'single', + isDisabled: true, + }, + render: args => ( + + Cat + Dog + Bird + + ), +}); + +export const DisallowEmptySelection = meta.story({ + args: { + selectionMode: 'single', + disallowEmptySelection: true, + defaultSelectedKeys: ['one'], + }, + render: args => ( + + One + Two + Three + + ), +}); + +export const MixedDisabled = meta.story({ + render: () => ( + + One + + Two + + Three + + ), +}); + +export const Orientation = meta.story({ + args: { + orientation: 'vertical', + }, + render: args => ( + + Morning + Afternoon + Evening + + ), +}); + +export const ControlledGroup = meta.story({ + render: () => { + const [selectedKeys, setSelectedKeys] = useState( + new Set(['beta']), + ); + + return ( + + + Alpha + Beta + Gamma + + Selected: {[...selectedKeys].join(', ') || 'none'} + + ); + }, +}); + +export const WithIcons = meta.story({ + args: { + selectionMode: 'single', + }, + + render: () => ( + + } /> + } + /> + }> + Star + + }> + Next + + + ), +}); + +export const IconsOnly = meta.story({ + render: () => ( + + } /> + } /> + } /> + + ), +}); diff --git a/packages/ui/src/components/ToggleButtonGroup/ToggleButtonGroup.tsx b/packages/ui/src/components/ToggleButtonGroup/ToggleButtonGroup.tsx new file mode 100644 index 0000000000..e37c4da2d8 --- /dev/null +++ b/packages/ui/src/components/ToggleButtonGroup/ToggleButtonGroup.tsx @@ -0,0 +1,44 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { forwardRef, Ref } from 'react'; +import { ToggleButtonGroup as AriaToggleButtonGroup } from 'react-aria-components'; +import type { ToggleButtonGroupProps } from './types'; +import { useDefinition } from '../../hooks/useDefinition'; +import { ToggleButtonGroupDefinition } from './definition'; + +/** + * A container that groups ToggleButton items and manages their collective selection state. + * + * @public + */ +export const ToggleButtonGroup = forwardRef( + (props: ToggleButtonGroupProps, ref: Ref) => { + const { ownProps, restProps } = useDefinition( + ToggleButtonGroupDefinition, + props, + ); + const { classes, children } = ownProps; + + return ( + + {children} + + ); + }, +); + +ToggleButtonGroup.displayName = 'ToggleButtonGroup'; diff --git a/packages/ui/src/components/ToggleButtonGroup/definition.ts b/packages/ui/src/components/ToggleButtonGroup/definition.ts new file mode 100644 index 0000000000..9e31b8d6cc --- /dev/null +++ b/packages/ui/src/components/ToggleButtonGroup/definition.ts @@ -0,0 +1,35 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { defineComponent } from '../../hooks/useDefinition'; +import type { ToggleButtonGroupOwnProps } from './types'; +import styles from './ToggleButtonGroup.module.css'; + +/** + * Component definition for ToggleButtonGroup + * @public + */ +export const ToggleButtonGroupDefinition = + defineComponent()({ + styles, + classNames: { + root: 'bui-ToggleButtonGroup', + }, + propDefs: { + className: {}, + children: {}, + }, + }); diff --git a/packages/ui/src/components/ToggleButtonGroup/index.ts b/packages/ui/src/components/ToggleButtonGroup/index.ts new file mode 100644 index 0000000000..63893ec3f4 --- /dev/null +++ b/packages/ui/src/components/ToggleButtonGroup/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './ToggleButtonGroup'; +export * from './types'; +export { ToggleButtonGroupDefinition } from './definition'; diff --git a/packages/ui/src/components/ToggleButtonGroup/types.ts b/packages/ui/src/components/ToggleButtonGroup/types.ts new file mode 100644 index 0000000000..b2bdb1e831 --- /dev/null +++ b/packages/ui/src/components/ToggleButtonGroup/types.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 type { ReactNode } from 'react'; +import type { ToggleButtonGroupProps as AriaToggleButtonGroupProps } from 'react-aria-components'; + +/** @public */ +export type ToggleButtonGroupOwnProps = { + className?: string; + children?: ReactNode; +}; + +/** @public */ +export interface ToggleButtonGroupProps + extends Omit, + ToggleButtonGroupOwnProps {} diff --git a/packages/ui/src/components/Tooltip/Tooltip.module.css b/packages/ui/src/components/Tooltip/Tooltip.module.css index 47fedff82d..51ef56eaaf 100644 --- a/packages/ui/src/components/Tooltip/Tooltip.module.css +++ b/packages/ui/src/components/Tooltip/Tooltip.module.css @@ -18,14 +18,13 @@ @layer components { .bui-Tooltip { - box-shadow: 0 10px 15px -3px rgba(0 0 0 / 0.1), - 0 4px 6px -4px rgba(0 0 0 / 0.1); - border-radius: 4px; - background: var(--bui-bg-surface-1); - border: 1px solid var(--bui-gray-3); + --tooltip-border-radius: 4px; + box-shadow: var(--bui-shadow); + border-radius: var(--tooltip-border-radius); + background: var(--bui-bg-app); + border: 1px solid var(--bui-border-1); forced-color-adjust: none; outline: none; - padding: var(--bui-space-2) var(--bui-space-3); max-width: 240px; /* fixes FF gap */ transform: translate3d(0, 0, 0); @@ -63,6 +62,11 @@ } } + .bui-TooltipContent { + padding: var(--bui-space-2) var(--bui-space-3); + border-radius: var(--tooltip-border-radius); + } + .bui-TooltipArrow { & svg { display: block; @@ -74,12 +78,15 @@ we split the stroke and fill across separate elements in order to guarantee that the stroke is always overlaying a consistent color. */ - path:nth-child(1) { - fill: var(--bui-bg-surface-1); + use:nth-of-type(1) { + fill: var(--bui-bg-app); + } + use:nth-of-type(2) { + fill: var(--bui-bg-neutral-1); } - path:nth-child(2) { - fill: var(--bui-gray-3); + path { + fill: var(--bui-border-1); } /* The arrow svg overlaps the tooltip by 2px, so we @@ -106,22 +113,4 @@ transform: rotate(-90deg); } } - - [data-theme='dark'] { - .bui-Tooltip { - background: var(--bui-bg-surface-2); - box-shadow: none; - border: 1px solid var(--bui-gray-4); - } - - .bui-TooltipArrow { - svg path:nth-child(1) { - fill: var(--bui-bg-surface-2); - } - - svg path:nth-child(2) { - fill: var(--bui-gray-4); - } - } - } } diff --git a/packages/ui/src/components/Tooltip/Tooltip.stories.tsx b/packages/ui/src/components/Tooltip/Tooltip.stories.tsx index cc6b513ed4..c10d63f315 100644 --- a/packages/ui/src/components/Tooltip/Tooltip.stories.tsx +++ b/packages/ui/src/components/Tooltip/Tooltip.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * Copyright 2025 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,13 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import type { Meta, StoryObj } from '@storybook/react-vite'; -import { Placement } from '@react-types/overlays'; +import preview from '../../../../../.storybook/preview'; import { TooltipTrigger, Tooltip } from './Tooltip'; import { Button } from '../Button/Button'; -const meta = { +const meta = preview.meta({ title: 'Backstage UI/Tooltip', component: TooltipTrigger, parameters: { layout: 'centered' }, @@ -30,10 +28,6 @@ const meta = { isDisabled: { control: { type: 'boolean' }, }, - placement: { - options: ['top', 'right', 'bottom', 'left'], - control: { type: 'inline-radio' }, - }, delay: { control: { type: 'number' }, }, @@ -41,7 +35,7 @@ const meta = { control: { type: 'number' }, }, }, - render: ({ children, isOpen, isDisabled, placement, delay, closeDelay }) => ( + render: ({ children, isOpen, isDisabled, delay, closeDelay }) => ( - {children ?? 'I am a tooltip'} + {children ?? 'I am a tooltip'} ), -} as Meta<{ - children?: string; - isOpen?: boolean; - isDisabled?: boolean; - placement?: Placement; - delay?: number; - closeDelay?: number; -}>; +}); -export default meta; -type Story = StoryObj; - -export const Default: Story = { +export const Default = meta.story({ args: { children: 'I am a tooltip', }, -}; +}); -export const IsOpen: Story = { +export const IsOpen = meta.story({ + parameters: { layout: 'fullscreen' }, + decorators: [ + Story => ( +
    + +
    + ), + ], args: { - ...Default.args, + ...Default.input.args, isOpen: true, }, -}; +}); -export const IsDisabled: Story = { +export const IsDisabled = meta.story({ args: { - ...Default.args, + ...Default.input.args, isDisabled: true, }, -}; +}); -export const NoDelays: Story = { +export const NoDelays = meta.story({ args: { - ...Default.args, + ...Default.input.args, delay: 0, closeDelay: 0, }, -}; +}); -export const OrthogonalPlacements: Story = { +export const OrthogonalPlacements = meta.story({ parameters: { controls: { exclude: ['placement'], }, }, args: { - ...Default.args, + ...Default.input.args, isOpen: true, }, render: ({ isOpen, children }) => { @@ -113,13 +115,13 @@ export const OrthogonalPlacements: Story = { ); }, -}; +}); -export const WithLongText: Story = { +export const WithLongText = meta.story({ args: { - ...Default.args, + ...Default.input.args, isOpen: true, children: 'I am a tooltip with a very long text. orem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', }, -}; +}); diff --git a/packages/ui/src/components/Tooltip/Tooltip.tsx b/packages/ui/src/components/Tooltip/Tooltip.tsx index 9099f1eef2..2d5b42265a 100644 --- a/packages/ui/src/components/Tooltip/Tooltip.tsx +++ b/packages/ui/src/components/Tooltip/Tooltip.tsx @@ -15,50 +15,64 @@ */ import { forwardRef } from 'react'; +import { useId } from 'react-aria'; import { OverlayArrow, Tooltip as AriaTooltip, TooltipTrigger as AriaTooltipTrigger, TooltipTriggerComponentProps, } from 'react-aria-components'; -import clsx from 'clsx'; -import { TooltipProps } from './types'; -import { useStyles } from '../../hooks/useStyles'; +import type { TooltipProps } from './types'; +import { useDefinition } from '../../hooks/useDefinition'; import { TooltipDefinition } from './definition'; -import styles from './Tooltip.module.css'; +import { Box } from '../Box'; +import { BgReset } from '../../hooks/useBg'; -/** @public */ +/** + * A wrapper that connects a trigger element to a Tooltip, controlling its show and hide behavior with a configurable delay. + * + * @public + */ export const TooltipTrigger = (props: TooltipTriggerComponentProps) => { const { delay = 600 } = props; return ; }; -/** @public */ +/** + * A floating label that provides contextual information about an element when it receives hover or focus. + * + * @public + */ export const Tooltip = forwardRef( (props, ref) => { - const { classNames, cleanedProps } = useStyles(TooltipDefinition, props); - const { className, children, ...rest } = cleanedProps; + const { ownProps, restProps } = useDefinition(TooltipDefinition, props); + const { classes, children } = ownProps; + const svgPathId = useId(); return ( - - + + - + + + + + + + - {children} + + + {children} + + ); }, diff --git a/packages/ui/src/components/Tooltip/definition.ts b/packages/ui/src/components/Tooltip/definition.ts index a5b3d61ea8..226e4cef9d 100644 --- a/packages/ui/src/components/Tooltip/definition.ts +++ b/packages/ui/src/components/Tooltip/definition.ts @@ -14,15 +14,23 @@ * limitations under the License. */ -import type { ComponentDefinition } from '../../types'; +import { defineComponent } from '../../hooks/useDefinition'; +import type { TooltipOwnProps } from './types'; +import styles from './Tooltip.module.css'; /** * Component definition for Tooltip * @public */ -export const TooltipDefinition = { +export const TooltipDefinition = defineComponent()({ + styles, classNames: { tooltip: 'bui-Tooltip', + content: 'bui-TooltipContent', arrow: 'bui-TooltipArrow', }, -} as const satisfies ComponentDefinition; + propDefs: { + children: {}, + className: {}, + }, +}); diff --git a/packages/ui/src/components/Tooltip/index.ts b/packages/ui/src/components/Tooltip/index.ts index 43bd385c65..b70bce61b8 100644 --- a/packages/ui/src/components/Tooltip/index.ts +++ b/packages/ui/src/components/Tooltip/index.ts @@ -15,5 +15,5 @@ */ export { TooltipTrigger, Tooltip } from './Tooltip'; -export type { TooltipProps } from './types'; +export type { TooltipOwnProps, TooltipProps } from './types'; export { TooltipDefinition } from './definition'; diff --git a/packages/ui/src/components/Tooltip/types.ts b/packages/ui/src/components/Tooltip/types.ts index 2ef44fbbf4..d395ce9004 100644 --- a/packages/ui/src/components/Tooltip/types.ts +++ b/packages/ui/src/components/Tooltip/types.ts @@ -14,9 +14,15 @@ * limitations under the License. */ -import { TooltipProps as AriaTooltipProps } from 'react-aria-components'; +import type { TooltipProps as AriaTooltipProps } from 'react-aria-components'; /** @public */ -export interface TooltipProps extends Omit { +export type TooltipOwnProps = { children: React.ReactNode; -} + className?: string; +}; + +/** @public */ +export interface TooltipProps + extends Omit, + TooltipOwnProps {} diff --git a/packages/ui/src/components/VisuallyHidden/VisuallyHidden.stories.tsx b/packages/ui/src/components/VisuallyHidden/VisuallyHidden.stories.tsx index 9706edfc9b..cc75adbd5e 100644 --- a/packages/ui/src/components/VisuallyHidden/VisuallyHidden.stories.tsx +++ b/packages/ui/src/components/VisuallyHidden/VisuallyHidden.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * Copyright 2025 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,13 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import type { Meta, StoryObj } from '@storybook/react-vite'; +import preview from '../../../../../.storybook/preview'; import { VisuallyHidden } from './VisuallyHidden'; import { Text } from '../Text'; import { Flex } from '../Flex'; -const meta = { +const meta = preview.meta({ title: 'Backstage UI/VisuallyHidden', component: VisuallyHidden, parameters: { @@ -30,12 +29,9 @@ const meta = { }, }, }, -} satisfies Meta; +}); -export default meta; -type Story = StoryObj; - -export const Default: Story = { +export const Default = meta.story({ render: () => ( @@ -48,9 +44,9 @@ export const Default: Story = { ), -}; +}); -export const ExampleUsage: Story = { +export const ExampleUsage = meta.story({ render: () => ( @@ -70,4 +66,4 @@ export const ExampleUsage: Story = { ), -}; +}); diff --git a/packages/ui/src/components/VisuallyHidden/VisuallyHidden.tsx b/packages/ui/src/components/VisuallyHidden/VisuallyHidden.tsx index cfc796ef9e..ea20d2f6cd 100644 --- a/packages/ui/src/components/VisuallyHidden/VisuallyHidden.tsx +++ b/packages/ui/src/components/VisuallyHidden/VisuallyHidden.tsx @@ -14,11 +14,9 @@ * limitations under the License. */ -import { useStyles } from '../../hooks/useStyles'; +import { useDefinition } from '../../hooks/useDefinition'; import { VisuallyHiddenDefinition } from './definition'; import { VisuallyHiddenProps } from './types'; -import styles from './VisuallyHidden.module.css'; -import clsx from 'clsx'; /** * Visually hides content while keeping it accessible to screen readers. @@ -30,16 +28,11 @@ import clsx from 'clsx'; * @public */ export const VisuallyHidden = (props: VisuallyHiddenProps) => { - const { classNames, cleanedProps } = useStyles( + const { ownProps, restProps } = useDefinition( VisuallyHiddenDefinition, props, ); - const { className, ...rest } = cleanedProps; + const { classes } = ownProps; - return ( -
    - ); + return
    ; }; diff --git a/packages/ui/src/components/VisuallyHidden/definition.ts b/packages/ui/src/components/VisuallyHidden/definition.ts index 5be52f89ec..c7aaf0a6da 100644 --- a/packages/ui/src/components/VisuallyHidden/definition.ts +++ b/packages/ui/src/components/VisuallyHidden/definition.ts @@ -14,14 +14,21 @@ * limitations under the License. */ -import type { ComponentDefinition } from '../../types'; +import { defineComponent } from '../../hooks/useDefinition'; +import type { VisuallyHiddenOwnProps } from './types'; +import styles from './VisuallyHidden.module.css'; /** * Component definition for VisuallyHidden * @public */ -export const VisuallyHiddenDefinition = { - classNames: { - root: 'bui-VisuallyHidden', - }, -} as const satisfies ComponentDefinition; +export const VisuallyHiddenDefinition = + defineComponent()({ + styles, + classNames: { + root: 'bui-VisuallyHidden', + }, + propDefs: { + className: {}, + }, + }); diff --git a/packages/ui/src/components/VisuallyHidden/index.ts b/packages/ui/src/components/VisuallyHidden/index.ts index c3668df403..3f036c7c8c 100644 --- a/packages/ui/src/components/VisuallyHidden/index.ts +++ b/packages/ui/src/components/VisuallyHidden/index.ts @@ -15,5 +15,5 @@ */ export { VisuallyHidden } from './VisuallyHidden'; -export type { VisuallyHiddenProps } from './types'; +export type { VisuallyHiddenOwnProps, VisuallyHiddenProps } from './types'; export { VisuallyHiddenDefinition } from './definition'; diff --git a/packages/ui/src/components/VisuallyHidden/types.ts b/packages/ui/src/components/VisuallyHidden/types.ts index 88951d7ee2..d9c5700780 100644 --- a/packages/ui/src/components/VisuallyHidden/types.ts +++ b/packages/ui/src/components/VisuallyHidden/types.ts @@ -14,13 +14,18 @@ * limitations under the License. */ -import { ComponentProps } from 'react'; +import type { ComponentProps } from 'react'; + +/** @public */ +export type VisuallyHiddenOwnProps = { + className?: string; +}; /** * Properties for {@link VisuallyHidden} * * @public */ -export interface VisuallyHiddenProps extends ComponentProps<'div'> { - children?: React.ReactNode; -} +export interface VisuallyHiddenProps + extends Omit, 'className'>, + VisuallyHiddenOwnProps {} diff --git a/packages/ui/src/css/colors.stories.tsx b/packages/ui/src/css/colors.stories.tsx new file mode 100644 index 0000000000..2d1a3614a7 --- /dev/null +++ b/packages/ui/src/css/colors.stories.tsx @@ -0,0 +1,153 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import preview from '../../../../.storybook/preview'; +import { Box } from '../components/Box'; +import { Flex } from '../components/Flex'; +import { Text } from '../components/Text'; + +const meta = preview.meta({ + title: 'Backstage UI/Colors', + tags: ['!manifest'], +}); + +export const Default = meta.story({ + render: () => ( +
    + + + Neutral 1 + + Hover + + + Pressed + + + Disabled + + + + + Neutral 2 + + Hover + + + Pressed + + + Disabled + + + + + Neutral 3 + + Hover + + + Pressed + + + + Disabled + + + + + + Neutral 4 + + Hover + + + Pressed + + + + Disabled + + + + + + + +
    + ), +}); diff --git a/packages/ui/src/css/core.css b/packages/ui/src/css/core.css index 5be23e7a1b..1047215ee8 100644 --- a/packages/ui/src/css/core.css +++ b/packages/ui/src/css/core.css @@ -27,7 +27,7 @@ } body { - background-color: var(--bui-bg); + background-color: var(--bui-bg-app); /* Text defaults */ color: var(--bui-fg-primary); diff --git a/packages/ui/src/css/tokens.css b/packages/ui/src/css/tokens.css index 028c60311d..9426993ea5 100644 --- a/packages/ui/src/css/tokens.css +++ b/packages/ui/src/css/tokens.css @@ -15,8 +15,9 @@ */ @layer tokens { - /* Light theme tokens */ - :root { + /* Light theme tokens (also used for nested light theme switching) */ + :root, + [data-theme-mode='light'] { /* Font families */ --bui-font-regular: system-ui; --bui-font-monospace: ui-monospace, 'Menlo', 'Monaco', 'Consolas', @@ -70,117 +71,146 @@ --bui-black: #000000; --bui-white: #ffffff; - /* Gray scale */ - --bui-gray-1: #f8f8f8; - --bui-gray-2: #ececec; - --bui-gray-3: #d9d9d9; - --bui-gray-4: #c1c1c1; - --bui-gray-5: #9e9e9e; - --bui-gray-6: #8c8c8c; - --bui-gray-7: #757575; - --bui-gray-8: #595959; - - /* Background Colors */ - --bui-bg: var(--bui-gray-1); - --bui-bg-surface-1: var(--bui-white); - --bui-bg-surface-2: var(--bui-gray-2); + /* Solid background colors */ --bui-bg-solid: #1f5493; --bui-bg-solid-hover: #163a66; --bui-bg-solid-pressed: #0f2b4e; - --bui-bg-solid-disabled: #ebebeb; - --bui-bg-tint: transparent; - --bui-bg-tint-hover: rgba(31, 84, 147, 0.4); - --bui-bg-tint-pressed: rgba(31, 84, 147, 0.6); - --bui-bg-tint-disabled: #ebebeb; - --bui-bg-danger: #feebe7; - --bui-bg-warning: #fff2b2; - --bui-bg-success: #e6f6eb; + --bui-bg-solid-disabled: #163a66; - /* Foreground Colors */ + /* Neutral background colors */ + --bui-bg-app: #f8f8f8; + + --bui-bg-neutral-1: #fff; + --bui-bg-neutral-1-hover: oklch(0% 0 0 / 6%); + --bui-bg-neutral-1-pressed: oklch(0% 0 0 / 12%); + --bui-bg-neutral-1-disabled: oklch(0% 0 0 / 6%); + + --bui-bg-neutral-2: oklch(0% 0 0 / 6%); + --bui-bg-neutral-2-hover: oklch(0% 0 0 / 12%); + --bui-bg-neutral-2-pressed: oklch(0% 0 0 / 16%); + --bui-bg-neutral-2-disabled: oklch(0% 0 0 / 6%); + + --bui-bg-neutral-3: oklch(0% 0 0 / 6%); + --bui-bg-neutral-3-hover: oklch(0% 0 0 / 12%); + --bui-bg-neutral-3-pressed: oklch(0% 0 0 / 16%); + --bui-bg-neutral-3-disabled: oklch(0% 0 0 / 6%); + + --bui-bg-neutral-4: oklch(0% 0 0 / 6%); + --bui-bg-neutral-4-hover: oklch(0% 0 0 / 12%); + --bui-bg-neutral-4-pressed: oklch(0% 0 0 / 16%); + --bui-bg-neutral-4-disabled: oklch(0% 0 0 / 6%); + + /* Status background colors */ + --bui-bg-danger: #ffe2e2; + --bui-bg-warning: #ffedd5; + --bui-bg-success: #dcfce7; + --bui-bg-info: #dbeafe; + + /* Foreground colors */ --bui-fg-primary: var(--bui-black); - --bui-fg-secondary: var(--bui-gray-7); - --bui-fg-link: #1f5493; - --bui-fg-link-hover: #1f2d5c; - --bui-fg-disabled: #9e9e9e; + --bui-fg-secondary: oklch(0% 0 0 / 50%); + --bui-fg-disabled: oklch(0% 0 0 / 28%); --bui-fg-solid: var(--bui-white); - --bui-fg-solid-disabled: #9c9c9c; - --bui-fg-tint: #1f5493; - --bui-fg-tint-disabled: var(--bui-gray-5); - --bui-fg-danger: #e22b2b; - --bui-fg-warning: #e36d05; - --bui-fg-success: #1db954; + --bui-fg-solid-disabled: #98a8bc; - /* Border Colors */ - --bui-border: rgba(0, 0, 0, 0.1); - --bui-border-hover: rgba(0, 0, 0, 0.2); - --bui-border-pressed: rgba(0, 0, 0, 0.4); - --bui-border-disabled: rgba(0, 0, 0, 0.1); + /* Foreground Statuses */ + --bui-fg-danger-on-bg: #991919; + --bui-fg-warning-on-bg: #92310a; + --bui-fg-success-on-bg: #116932; + --bui-fg-info-on-bg: #173da6; + --bui-fg-danger: #ec3b18; + --bui-fg-warning: #ef7a32; + --bui-fg-success: #1aaf4f; + --bui-fg-info: #0d74ce; + + /* Border colors */ + --bui-border-1: #d5d5d5; + --bui-border-2: #bababa; + --bui-border-info: #7ea9d6; --bui-border-danger: #f87a7a; --bui-border-warning: #e36d05; --bui-border-success: #53db83; - /* Special Colors */ + /* Special colors */ --bui-ring: #1f5493; --bui-scrollbar: #a0a0a03b; --bui-scrollbar-thumb: #a0a0a0; + /* Shadows */ + --bui-shadow: 0 10px 15px -3px rgba(0 0 0 / 0.1), + 0 4px 6px -4px rgba(0 0 0 / 0.1); + --bui-animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; } /* Dark theme tokens */ [data-theme-mode='dark'] { - /* Gray scale */ - --bui-gray-1: #191919; - --bui-gray-2: #242424; - --bui-gray-3: #373737; - --bui-gray-4: #464646; - --bui-gray-5: #575757; - --bui-gray-6: #7b7b7b; - --bui-gray-7: #9e9e9e; - --bui-gray-8: #b4b4b4; - - /* Background Colors */ - --bui-bg: #333333; - --bui-bg-surface-1: #424242; - --bui-bg-surface-2: var(--bui-gray-2); + /* Solid background colors */ --bui-bg-solid: #9cc9ff; --bui-bg-solid-hover: #83b9fd; --bui-bg-solid-pressed: #83b9fd; - --bui-bg-solid-disabled: #222222; - --bui-bg-tint: transparent; - --bui-bg-tint-hover: rgba(156, 201, 255, 0.12); - --bui-bg-tint-pressed: rgba(156, 201, 255, 0.16); - --bui-bg-tint-disabled: transparent; - --bui-bg-danger: #3b1219; + --bui-bg-solid-disabled: #1b3d68; + + /* Neutral background colors */ + --bui-bg-app: #333333; + + --bui-bg-neutral-1: oklch(100% 0 0 / 10%); + --bui-bg-neutral-1-hover: oklch(100% 0 0 / 14%); + --bui-bg-neutral-1-pressed: oklch(100% 0 0 / 20%); + --bui-bg-neutral-1-disabled: oklch(100% 0 0 / 10%); + + --bui-bg-neutral-2: oklch(100% 0 0 / 6%); + --bui-bg-neutral-2-hover: oklch(100% 0 0 / 10%); + --bui-bg-neutral-2-pressed: oklch(100% 0 0 / 16%); + --bui-bg-neutral-2-disabled: oklch(100% 0 0 / 6%); + + --bui-bg-neutral-3: oklch(100% 0 0 / 8%); + --bui-bg-neutral-3-hover: oklch(100% 0 0 / 12%); + --bui-bg-neutral-3-pressed: oklch(100% 0 0 / 20%); + --bui-bg-neutral-3-disabled: oklch(100% 0 0 / 8%); + + --bui-bg-neutral-4: oklch(100% 0 0 / 8%); + --bui-bg-neutral-4-hover: oklch(100% 0 0 / 12%); + --bui-bg-neutral-4-pressed: oklch(100% 0 0 / 20%); + --bui-bg-neutral-4-disabled: oklch(100% 0 0 / 8%); + + /* Status background colors */ + --bui-bg-danger: #300c0c; --bui-bg-warning: #302008; - --bui-bg-success: #132d21; + --bui-bg-success: #042713; + --bui-bg-info: #132049; - /* Foreground Colors */ + /* Foreground colors */ --bui-fg-primary: var(--bui-white); - --bui-fg-secondary: var(--bui-gray-7); - --bui-fg-link: #9cc9ff; - --bui-fg-link-hover: #7eb5f7; - --bui-fg-disabled: var(--bui-gray-7); + --bui-fg-secondary: oklch(100% 0 0 / 50%); + --bui-fg-disabled: oklch(100% 0 0 / 28%); --bui-fg-solid: #101821; - --bui-fg-solid-disabled: var(--bui-gray-5); - --bui-fg-tint: #9cc9ff; - --bui-fg-tint-disabled: var(--bui-gray-5); - --bui-fg-danger: #e22b2b; - --bui-fg-warning: #e36d05; - --bui-fg-success: #1db954; + --bui-fg-solid-disabled: #6191cc; - /* Border Colors */ - --bui-border: rgba(255, 255, 255, 0.12); - --bui-border-hover: rgba(255, 255, 255, 0.4); - --bui-border-pressed: rgba(255, 255, 255, 0.5); - --bui-border-disabled: rgba(255, 255, 255, 0.2); + /* Foreground statuses */ + --bui-fg-danger-on-bg: #fca5a5; + --bui-fg-warning-on-bg: #fdba74; + --bui-fg-success-on-bg: #86efac; + --bui-fg-info-on-bg: #a3cfff; + --bui-fg-danger: #ff5a30; + --bui-fg-warning: #ffa057; + --bui-fg-success: #1ed760; + --bui-fg-info: #70b8ff; + + /* Border colors */ + --bui-border-1: #434343; + --bui-border-2: #585858; + --bui-border-info: #7ea9d6; --bui-border-danger: #f87a7a; --bui-border-warning: #e36d05; --bui-border-success: #53db83; - /* Special Colors */ + /* Special colors */ --bui-ring: #1f5493; --bui-scrollbar: #3636363a; --bui-scrollbar-thumb: #575757; + + /* Shadows */ + --bui-shadow: none; } } diff --git a/packages/ui/src/css/utilities/sm.css b/packages/ui/src/css/utilities/sm.css index a9c8ea8ab5..4ceb7600ed 100644 --- a/packages/ui/src/css/utilities/sm.css +++ b/packages/ui/src/css/utilities/sm.css @@ -21,7 +21,7 @@ } .bui-sm-border-base { - border-color: var(--bui-border); + border-color: var(--bui-border-2); border-width: 1px; border-style: solid; } @@ -37,12 +37,6 @@ border-width: 0; } - .bui-sm-border-selected { - border-color: var(--bui-border-pressed); - border-width: 1px; - border-style: solid; - } - .bui-sm-border-warning { border-color: var(--bui-border-warning); border-width: 1px; diff --git a/packages/ui/src/css/utilities/xs.css b/packages/ui/src/css/utilities/xs.css index 2907c7d468..12d62f1dd8 100644 --- a/packages/ui/src/css/utilities/xs.css +++ b/packages/ui/src/css/utilities/xs.css @@ -20,7 +20,7 @@ } .bui-border-base { - border-color: var(--bui-border); + border-color: var(--bui-border-2); border-width: 1px; border-style: solid; } @@ -36,12 +36,6 @@ border-width: 0; } - .bui-border-selected { - border-color: var(--bui-border-pressed); - border-width: 1px; - border-style: solid; - } - .bui-border-warning { border-color: var(--bui-border-warning); border-width: 1px; diff --git a/packages/ui/src/definitions.ts b/packages/ui/src/definitions.ts index a0dc412697..1d16c67f2a 100644 --- a/packages/ui/src/definitions.ts +++ b/packages/ui/src/definitions.ts @@ -19,14 +19,22 @@ * @packageDocumentation */ -export { AccordionDefinition } from './components/Accordion/definition'; +export { + AccordionDefinition, + AccordionTriggerDefinition, + AccordionPanelDefinition, + AccordionGroupDefinition, +} from './components/Accordion/definition'; +export { AlertDefinition } from './components/Alert/definition'; export { AvatarDefinition } from './components/Avatar/definition'; +export { BadgeDefinition } from './components/Badge/definition'; export { BoxDefinition } from './components/Box/definition'; export { ButtonDefinition } from './components/Button/definition'; export { ButtonIconDefinition } from './components/ButtonIcon/definition'; export { ButtonLinkDefinition } from './components/ButtonLink/definition'; export { CardDefinition } from './components/Card/definition'; export { CheckboxDefinition } from './components/Checkbox/definition'; +export { CheckboxGroupDefinition } from './components/CheckboxGroup/definition'; export { ContainerDefinition } from './components/Container/definition'; export { DialogDefinition } from './components/Dialog/definition'; export { FieldErrorDefinition } from './components/FieldError/definition'; @@ -36,17 +44,31 @@ export { GridDefinition, GridItemDefinition, } from './components/Grid/definition'; -export { HeaderDefinition } from './components/Header/definition'; -export { HeaderPageDefinition } from './components/HeaderPage/definition'; +export { PluginHeaderDefinition } from './components/PluginHeader/definition'; +export { + HeaderDefinition, + HeaderPageDefinition, +} from './components/Header/definition'; export { LinkDefinition } from './components/Link/definition'; +export { + ListDefinition, + ListRowDefinition, +} from './components/List/definition'; export { MenuDefinition } from './components/Menu/definition'; export { PasswordFieldDefinition } from './components/PasswordField/definition'; export { PopoverDefinition } from './components/Popover/definition'; export { RadioGroupDefinition } from './components/RadioGroup/definition'; +export { + SearchAutocompleteDefinition, + SearchAutocompleteItemDefinition, +} from './components/SearchAutocomplete/definition'; export { SearchFieldDefinition } from './components/SearchField/definition'; export { SelectDefinition } from './components/Select/definition'; export { SkeletonDefinition } from './components/Skeleton/definition'; +export { SliderDefinition } from './components/Slider/definition'; export { SwitchDefinition } from './components/Switch/definition'; +export { ToggleButtonDefinition } from './components/ToggleButton/definition'; +export { ToggleButtonGroupDefinition } from './components/ToggleButtonGroup/definition'; export { TableDefinition } from './components/Table/definition'; export { TablePaginationDefinition } from './components/TablePagination/definition'; export { TabsDefinition } from './components/Tabs/definition'; diff --git a/packages/ui/src/guidelines/CardsWithTable.stories.tsx b/packages/ui/src/guidelines/CardsWithTable.stories.tsx new file mode 100644 index 0000000000..23e5923327 --- /dev/null +++ b/packages/ui/src/guidelines/CardsWithTable.stories.tsx @@ -0,0 +1,282 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* eslint-disable no-restricted-syntax */ + +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { MemoryRouter } from 'react-router-dom'; +import { BUIProvider } from '../provider'; +import { + Card, + CardHeader, + CardBody, + Container, + Grid, + Flex, + Text, + Table, + CellText, + CellProfile, + useTable, + type ColumnConfig, + PluginHeader, + Header, + Button, +} from '..'; + +// --------------------------------------------------------------------------- +// Metric card data +// --------------------------------------------------------------------------- + +interface MetricCard { + label: string; + value: string; + trend: string; + trendColor: 'primary' | 'secondary' | 'success' | 'danger' | 'warning'; +} + +const metrics: MetricCard[] = [ + { + label: 'Total Components', + value: '142', + trend: '+12 this week', + trendColor: 'success', + }, + { + label: 'Active Services', + value: '58', + trend: '94% healthy', + trendColor: 'success', + }, + { + label: 'Recent Deployments', + value: '23', + trend: 'in the last 24 h', + trendColor: 'secondary', + }, +]; + +// --------------------------------------------------------------------------- +// Table data +// --------------------------------------------------------------------------- + +interface ServiceItem { + id: string; + name: string; + owner: string; + ownerAvatar: string; + type: 'service' | 'library' | 'website' | 'documentation'; + lifecycle: 'production' | 'experimental'; + description: string; +} + +const services: ServiceItem[] = [ + { + id: '1', + name: 'authentication-service', + owner: 'security-team', + ownerAvatar: 'https://github.com/identicons/security-team.png', + type: 'service', + lifecycle: 'production', + description: 'Handles user authentication, sessions and token refresh.', + }, + { + id: '2', + name: 'api-gateway', + owner: 'platform-team', + ownerAvatar: 'https://github.com/identicons/platform-team.png', + type: 'service', + lifecycle: 'production', + description: 'Routes and validates all inbound API requests.', + }, + { + id: '3', + name: 'frontend-core', + owner: 'design-system', + ownerAvatar: 'https://github.com/identicons/design-system.png', + type: 'library', + lifecycle: 'production', + description: 'Shared UI components and design tokens.', + }, + { + id: '4', + name: 'data-pipeline', + owner: 'data-team', + ownerAvatar: 'https://github.com/identicons/data-team.png', + type: 'service', + lifecycle: 'experimental', + description: 'Streaming data ingestion pipeline for analytics.', + }, + { + id: '5', + name: 'developer-portal', + owner: 'devex-team', + ownerAvatar: 'https://github.com/identicons/devex-team.png', + type: 'website', + lifecycle: 'production', + description: 'Internal developer portal built on Backstage.', + }, + { + id: '6', + name: 'notification-service', + owner: 'platform-team', + ownerAvatar: 'https://github.com/identicons/platform-team.png', + type: 'service', + lifecycle: 'production', + description: 'Sends emails, Slack messages and push notifications.', + }, + { + id: '7', + name: 'search-indexer', + owner: 'search-team', + ownerAvatar: 'https://github.com/identicons/search-team.png', + type: 'service', + lifecycle: 'experimental', + description: 'Indexes catalog entities for full-text search.', + }, + { + id: '8', + name: 'billing-service', + owner: 'payments-team', + ownerAvatar: 'https://github.com/identicons/payments-team.png', + type: 'service', + lifecycle: 'production', + description: 'Manages subscriptions, invoices and payment processing.', + }, +]; + +// --------------------------------------------------------------------------- +// Stat card component +// --------------------------------------------------------------------------- + +const StatCard = ({ label, value, trend, trendColor }: MetricCard) => ( + + {label} + + + + {value} + + + {trend} + + + + +); + +// --------------------------------------------------------------------------- +// Page layout component (the actual story render) +// --------------------------------------------------------------------------- + +const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + defaultWidth: '3fr', + cell: item => ( + + ), + }, + { + id: 'owner', + label: 'Owner', + defaultWidth: '2fr', + cell: item => , + }, + { + id: 'type', + label: 'Type', + defaultWidth: '1fr', + cell: item => , + }, + { + id: 'lifecycle', + label: 'Lifecycle', + defaultWidth: '1fr', + cell: item => ( + + ), + }, +]; + +const CardsWithTableLayout = () => { + const { tableProps } = useTable({ + mode: 'complete', + getData: () => services, + paginationOptions: { pageSize: 5 }, + }); + + return ( + <> + +
    Custom action} + /> + + + + {metrics.map(metric => ( + + ))} + +
    + + + + ); +}; + +// --------------------------------------------------------------------------- +// Storybook meta +// --------------------------------------------------------------------------- + +const meta = { + title: 'Guidelines/Cards with Table', + parameters: { + layout: 'fullscreen', + }, + decorators: [ + (Story: () => JSX.Element) => ( + + + + + + ), + ], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => , +}; diff --git a/packages/ui/src/hooks/index.ts b/packages/ui/src/hooks/index.ts new file mode 100644 index 0000000000..b231a4ff93 --- /dev/null +++ b/packages/ui/src/hooks/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './useDefinition'; diff --git a/packages/ui/src/hooks/useBg.tsx b/packages/ui/src/hooks/useBg.tsx new file mode 100644 index 0000000000..2284e5f337 --- /dev/null +++ b/packages/ui/src/hooks/useBg.tsx @@ -0,0 +1,146 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useContext, ReactNode } from 'react'; +import { + createVersionedContext, + createVersionedValueMap, +} from '@backstage/version-bridge'; +import { ContainerBg, ProviderBg, Responsive } from '../types'; +import { useBreakpoint } from './useBreakpoint'; +import { resolveResponsiveValue } from './useDefinition/helpers'; + +/** @public */ +export interface BgContextValue { + bg: ContainerBg | undefined; +} + +/** @public */ +export interface BgProviderProps { + bg: ContainerBg; + children: ReactNode; +} + +const BgContext = createVersionedContext<{ + 1: BgContextValue; +}>('bg-context'); + +/** + * Increments a neutral bg level by one, capping at 'neutral-3'. + * Intent backgrounds (danger, warning, success) pass through unchanged. + * + * The 'neutral-4' level is reserved for consumer component CSS and is + * never set on providers. + * + * @internal + */ +function incrementNeutralBg(bg: ContainerBg | undefined): ContainerBg { + if (!bg) return 'neutral-1'; + if (bg === 'neutral-1') return 'neutral-2'; + if (bg === 'neutral-2') return 'neutral-3'; + if (bg === 'neutral-3') return 'neutral-3'; // capped at neutral-3 + // Intent values pass through unchanged + return bg; +} + +/** + * Provider component that establishes the bg context for child components. + * + * @public + */ +export const BgProvider = ({ bg, children }: BgProviderProps) => { + return ( + + {children} + + ); +}; + +/** + * Resets the bg context to undefined, cutting any inherited neutral chain. + * Use this inside overlay components (Popover, Tooltip, Dialog, Menu) so + * their content always starts from neutral-1 regardless of where the trigger + * is placed in the tree. + * + * @internal + */ +export const BgReset = ({ children }: { children: ReactNode }) => { + return ( + + {children} + + ); +}; + +/** + * Hook for consumer components (e.g. Button) to read the parent bg context. + * + * Returns the parent container's bg unchanged. The consumer component's CSS + * handles the visual step-up (e.g. on a neutral-1 surface, the consumer + * uses neutral-2 tokens via `data-on-bg`). + * + * @public + */ +export function useBgConsumer(): BgContextValue { + const value = useContext(BgContext)?.atVersion(1); + return value ?? { bg: undefined }; +} + +/** + * Hook for provider components (e.g. Box, Card) to resolve and provide bg context. + * + * **Resolution rules:** + * + * - `bg` is `undefined` -- transparent, no context change, returns `{ bg: undefined }`. + * This is the default for Box, Flex, and Grid (they do **not** auto-increment). + * - `bg` is `'neutral'` -- when the parent bg is neutral, increments the neutral + * level from the parent context, capping at `neutral-3`. When the parent bg is + * an intent (`'danger'` | `'warning'` | `'success'`), the intent passes through + * unchanged (i.e. `bg: 'neutral'` does not override the parent intent). The + * increment is always relative to the parent; it is not possible to pin a + * container to an explicit neutral level. + * - `bg` is `'danger'` | `'warning'` | `'success'` -- sets the bg to that intent + * explicitly, regardless of the parent value. + * + * **Capping:** + * + * Provider components cap at `neutral-3`. The `neutral-4` level is **not** a valid + * prop value -- it exists only in consumer component CSS (e.g. a Button on a + * `neutral-3` surface renders with `neutral-4` tokens via `data-on-bg`). + * + * The caller is responsible for wrapping children with `BgProvider` when the + * resolved bg is defined. + * + * @public + */ +export function useBgProvider(bg?: Responsive): BgContextValue { + const { breakpoint } = useBreakpoint(); + const context = useBgConsumer(); + + if (bg === undefined) { + return { bg: undefined }; + } + + const resolved = resolveResponsiveValue(bg, breakpoint); + + if (resolved === 'neutral') { + return { bg: incrementNeutralBg(context.bg) }; + } + + return { bg: resolved }; +} diff --git a/packages/ui/src/hooks/useBreakpoint.ts b/packages/ui/src/hooks/useBreakpoint.ts index ac2d0567be..bd37eec4c4 100644 --- a/packages/ui/src/hooks/useBreakpoint.ts +++ b/packages/ui/src/hooks/useBreakpoint.ts @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { useMediaQuery } from './useMediaQuery'; +import { useMemo } from 'react'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import type { Breakpoint } from '../types'; export const breakpoints: { name: string; id: Breakpoint; value: number }[] = [ @@ -25,40 +26,95 @@ export const breakpoints: { name: string; id: Breakpoint; value: number }[] = [ { name: 'Extra Large', id: 'xl', value: 1536 }, ]; -/** @public */ -export const useBreakpoint = () => { - // Call all media queries at the top level - const matches = breakpoints.map(breakpoint => { - return useMediaQuery(`(min-width: ${breakpoint.value}px)`); - }); +const breakpointIndex = new Map( + breakpoints.map((bp, i) => [bp.id, i]), +); - // Pre-calculate all the up/down values we need - const upMatches = new Map( - breakpoints.map(bp => [bp.id, useMediaQuery(`(min-width: ${bp.value}px)`)]), - ); +function bpIndex(key: Breakpoint): number { + return breakpointIndex.get(key) ?? 0; +} - const downMatches = new Map( - breakpoints.map(bp => [ - bp.id, - useMediaQuery(`(max-width: ${bp.value - 1}px)`), - ]), - ); +const IS_SERVER = + typeof window === 'undefined' || typeof window.matchMedia === 'undefined'; - let breakpoint: Breakpoint = breakpoints[0].id; - for (let i = matches.length - 1; i >= 0; i--) { - if (matches[i]) { - breakpoint = breakpoints[i].id; - break; +function computeBreakpoint(): Breakpoint { + if (IS_SERVER) { + return 'initial'; + } + for (let i = breakpoints.length - 1; i >= 0; i--) { + if (window.matchMedia(`(min-width: ${breakpoints[i].value}px)`).matches) { + return breakpoints[i].id; } } + return 'initial'; +} - return { - breakpoint, - up: (key: Breakpoint): boolean => { - return upMatches.get(key) ?? false; - }, - down: (key: Breakpoint): boolean => { - return downMatches.get(key) ?? false; - }, +// --- Module-scoped singleton store --- +// This is intentionally not a global singleton. Multiple copies of this module +// (e.g. different package versions or module federation remotes) each get their +// own store. This avoids cross-version coupling at the cost of a few extra +// listeners, which is a fine trade-off. +// `current` is initialized lazily on first client-side access. + +let current: Breakpoint | undefined; +const listeners = new Set<() => void>(); +let initialized = false; + +function ensureInitialized(): void { + if (initialized || IS_SERVER) { + return; + } + initialized = true; + current = computeBreakpoint(); + + // Register one listener per breakpoint. When any query fires, re-evaluate + // all breakpoints to find the new active one. Notify subscribers only if + // the active breakpoint actually changed. + for (const bp of breakpoints) { + const mql = window.matchMedia(`(min-width: ${bp.value}px)`); + mql.addEventListener('change', () => { + const next = computeBreakpoint(); + if (next !== current) { + current = next; + for (const cb of listeners) { + cb(); + } + } + }); + } +} + +function subscribe(callback: () => void): () => void { + ensureInitialized(); + listeners.add(callback); + return () => { + listeners.delete(callback); }; +} + +function getSnapshot(): Breakpoint { + ensureInitialized(); + return current ?? 'initial'; +} + +function getServerSnapshot(): Breakpoint { + return 'initial'; +} + +/** @public */ +export const useBreakpoint = () => { + const breakpoint = useSyncExternalStore( + subscribe, + getSnapshot, + getServerSnapshot, + ); + + return useMemo( + () => ({ + breakpoint, + up: (key: Breakpoint): boolean => bpIndex(breakpoint) >= bpIndex(key), + down: (key: Breakpoint): boolean => bpIndex(breakpoint) < bpIndex(key), + }), + [breakpoint], + ); }; diff --git a/packages/ui/src/hooks/useDefinition/defineComponent.ts b/packages/ui/src/hooks/useDefinition/defineComponent.ts new file mode 100644 index 0000000000..fc58a6774e --- /dev/null +++ b/packages/ui/src/hooks/useDefinition/defineComponent.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { + ComponentConfig, + BgPropsConstraint, + AnalyticsPropsConstraint, +} from './types'; + +export function defineComponent

    >() { + return < + const S extends Record, + const C extends ComponentConfig, + >( + config: C & + BgPropsConstraint & + AnalyticsPropsConstraint, + ): C => config; +} diff --git a/packages/ui/src/hooks/useDefinition/helpers.ts b/packages/ui/src/hooks/useDefinition/helpers.ts new file mode 100644 index 0000000000..8a990b4b6a --- /dev/null +++ b/packages/ui/src/hooks/useDefinition/helpers.ts @@ -0,0 +1,165 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { breakpoints } from '../useBreakpoint'; +import { utilityClassMap } from '../../utils/utilityClassMap'; +import type { ComponentConfig, UnwrapResponsive, UtilityStyle } from './types'; + +const namedBreakpoints = breakpoints.filter(b => b.id !== 'initial'); + +function isResponsiveObject(value: unknown): value is Record { + return ( + typeof value === 'object' && + value !== null && + namedBreakpoints.some(b => b.id in value) + ); +} + +export function resolveResponsiveValue( + value: T, + breakpoint: string, +): UnwrapResponsive { + if (!isResponsiveObject(value)) { + return value as UnwrapResponsive; + } + + const index = breakpoints.findIndex(b => b.id === breakpoint); + + // Look for value at current breakpoint or smaller + for (let i = index; i >= 0; i--) { + const key = breakpoints[i].id; + if (key in value && value[key] !== undefined) { + return value[key] as UnwrapResponsive; + } + } + + // If no value found, check from smallest breakpoint up + for (let i = 0; i < breakpoints.length; i++) { + const key = breakpoints[i].id; + if (key in value && value[key] !== undefined) { + return value[key] as UnwrapResponsive; + } + } + + return value as UnwrapResponsive; +} + +export function resolveDefinitionProps>( + definition: D, + props: Record, + breakpoint: string, +): { + ownPropsResolved: Record; + restProps: Record; +} { + const ownPropKeys = new Set(Object.keys(definition.propDefs)); + const utilityPropKeys = new Set(definition.utilityProps ?? []); + + const ownPropsRaw: Record = {}; + const restProps: Record = {}; + + for (const [key, value] of Object.entries(props)) { + if (ownPropKeys.has(key)) { + ownPropsRaw[key] = value; + } else if (!(utilityPropKeys as Set).has(key)) { + restProps[key] = value; + } + } + + const ownPropsResolved: Record = {}; + + for (const [key, config] of Object.entries(definition.propDefs)) { + const rawValue = ownPropsRaw[key]; + const resolvedValue = resolveResponsiveValue(rawValue, breakpoint); + const finalValue = resolvedValue ?? (config as any).default; + if (finalValue !== undefined) { + ownPropsResolved[key] = finalValue; + } + } + + return { ownPropsResolved, restProps }; +} + +export function processUtilityProps( + props: Record, + utilityPropKeys: readonly Keys[], +): { utilityClasses: string; utilityStyle: UtilityStyle } { + const utilityClassList: string[] = []; + const generatedStyle: Record = {}; + + const handleUtilityValue = ( + key: string, + val: unknown, + prefix: string = '', + ) => { + // Get utility class configuration for this key + const utilityConfig = utilityClassMap[key as keyof typeof utilityClassMap]; + + if (!utilityConfig) { + // Skip if no config found for this key + return; + } + + // Check if value is in the list of valid values for this utility + const values = utilityConfig.values as readonly (string | number)[]; + if (values.length > 0 && values.includes(val as string | number)) { + // Generate utility class with value suffix and optional breakpoint prefix + const className = prefix + ? `${prefix}${utilityConfig.class}-${val}` + : `${utilityConfig.class}-${val}`; + utilityClassList.push(className); + } else if ('cssVar' in utilityConfig && utilityConfig.cssVar) { + // Custom value - add CSS custom property AND utility class name + // Only if cssVar is defined (properties with fixed values don't have cssVar) + const cssVar = utilityConfig.cssVar; + const cssVarKey = prefix ? `${cssVar}-${prefix.slice(0, -1)}` : cssVar; + // CSS custom properties need to be set on the style object + generatedStyle[cssVarKey] = val; + + // Add utility class name (without value suffix) with optional breakpoint prefix + const className = prefix + ? `${prefix}${utilityConfig.class}` + : utilityConfig.class; + utilityClassList.push(className); + } + // If no cssVar and value is not in valid values, skip (invalid value for fixed-value property) + }; + + for (const key of utilityPropKeys) { + const value = props[key]; + if (value === undefined || value === null) { + continue; + } + + // Check if value is a responsive object + if (typeof value === 'object' && value !== null) { + const breakpointValues = value as { [key: string]: unknown }; + // Handle responsive object values + for (const bp in breakpointValues) { + const prefix = bp === 'initial' ? '' : `${bp}:`; + handleUtilityValue(key, breakpointValues[bp], prefix); + } + } else { + // Handle simple value + handleUtilityValue(key, value); + } + } + + return { + utilityClasses: utilityClassList.join(' '), + utilityStyle: generatedStyle as UtilityStyle, + }; +} diff --git a/packages/ui/src/hooks/useDefinition/index.ts b/packages/ui/src/hooks/useDefinition/index.ts new file mode 100644 index 0000000000..a180645975 --- /dev/null +++ b/packages/ui/src/hooks/useDefinition/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { useDefinition } from './useDefinition'; +export { defineComponent } from './defineComponent'; +export type { + ComponentConfig, + PropDefConfig, + UseDefinitionOptions, + UseDefinitionResult, +} from './types'; diff --git a/packages/ui/src/hooks/useDefinition/types.ts b/packages/ui/src/hooks/useDefinition/types.ts new file mode 100644 index 0000000000..a819cb5d35 --- /dev/null +++ b/packages/ui/src/hooks/useDefinition/types.ts @@ -0,0 +1,172 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ReactNode } from 'react'; +import type { Responsive } from '../../types'; +import type { AnalyticsTracker } from '../../analytics/types'; +import type { utilityClassMap } from '../../utils/utilityClassMap'; + +export type UnwrapResponsive = T extends Responsive ? U : T; + +export interface PropDefConfig { + dataAttribute?: boolean; + default?: UnwrapResponsive; +} + +export type UtilityPropKey = keyof typeof utilityClassMap; + +export interface ComponentConfig< + P extends Record, + S extends Record, +> { + styles: S; + classNames: Record; + propDefs: { [K in keyof P]: PropDefConfig }; + // readonly for compatibility with const inference from factory + utilityProps?: readonly UtilityPropKey[]; + /** + * How this component participates in the bg system. + * + * - `'provider'` — calls `useBgProvider`, sets `data-bg`, wraps children in `BgProvider` + * - `'consumer'` — calls `useBgConsumer`, sets `data-on-bg` + */ + bg?: 'provider' | 'consumer'; + /** + * Whether this component fires analytics events. + * When true, `useDefinition` will call `useAnalytics()` and return + * an `analytics` tracker. The component's own props type must include + * `noTrack?: boolean`. + */ + analytics?: boolean; +} + +/** + * Type constraint that validates bg props are present in the props type. + * - Provider components must include 'bg' in their props and 'children' in propDefs + * - Consumer components don't need a bg prop + */ +export type BgPropsConstraint = Bg extends 'provider' + ? 'bg' extends keyof P + ? 'children' extends keyof P + ? {} extends Pick + ? { + __error: 'Bg provider components cannot have children as optional.'; + } + : {} + : { + __error: 'Bg provider components must include children in own props type.'; + } + : { + __error: 'Bg provider components must include bg in own props type.'; + } + : {}; + +/** + * Type constraint that validates analytics props are present in the props type. + * Components with analytics: true must include 'noTrack' in their props. + */ +export type AnalyticsPropsConstraint = Analytics extends true + ? 'noTrack' extends keyof P + ? {} + : { + __error: 'Analytics components must include noTrack in own props type.'; + } + : {}; + +export interface UseDefinitionOptions> { + utilityTarget?: keyof D['classNames'] | null; + classNameTarget?: keyof D['classNames'] | null; +} + +// Resolve prop type: unwrap Responsive, make non-nullable if default exists +// Uses "inverse check" pattern: check if undefined is assignable to the default type +type ResolvePropType< + T, + Config extends PropDefConfig, +> = undefined extends Config['default'] + ? UnwrapResponsive // Default is missing/undefined -> keep original type + : Exclude, undefined>; // Default exists -> remove undefined + +// Build ownProps shape from propDefs +// Iterates over PropDefs keys (not P keys) to preserve literal config types +type ResolvedOwnProps< + P, + PropDefs extends Record>, +> = { + [K in keyof PropDefs & keyof P]: ResolvePropType; +}; + +type BaseOwnProps< + D extends ComponentConfig, + P extends Record, +> = { + classes: Record; +} & ResolvedOwnProps; + +type ResolveBgProps< + D extends ComponentConfig, + TBase, +> = D['bg'] extends 'provider' + ? Omit & { childrenWithBgProvider: ReactNode } + : TBase; + +type DataAttributeKeys = { + [K in keyof PropDefs]: PropDefs[K] extends { dataAttribute: true } + ? K + : never; +}[keyof PropDefs]; + +type DataAttributes = { + [K in DataAttributeKeys as `data-${Lowercase< + string & K + >}`]?: string; +} & { 'data-bg'?: string; 'data-on-bg'?: string }; + +export type UtilityKeys> = + D['utilityProps'] extends ReadonlyArray ? K : never; + +type UtilityMapType = typeof utilityClassMap; + +// Extract CSS variable key for a given prop (e.g., 'p' -> '--p') +type GetCssVarKey = K extends keyof UtilityMapType + ? UtilityMapType[K] extends { cssVar: infer V extends string } + ? V + : never + : never; + +export type UtilityStyle = { + [K in Keys as GetCssVarKey]?: string | number; +}; + +type ResolvedUtilityStyle> = UtilityStyle< + UtilityKeys +>; + +export type UseDefinitionResult< + D extends ComponentConfig, + P extends Record, +> = { + ownProps: ResolveBgProps>; + + // Rest props excludes both propDefs keys AND utility prop keys + restProps: keyof Omit> extends never + ? Record + : Omit>; + + dataAttributes: DataAttributes; + + utilityStyle: ResolvedUtilityStyle; +} & (D['analytics'] extends true ? { analytics: AnalyticsTracker } : {}); diff --git a/packages/ui/src/hooks/useDefinition/useDefinition.tsx b/packages/ui/src/hooks/useDefinition/useDefinition.tsx new file mode 100644 index 0000000000..a791343c15 --- /dev/null +++ b/packages/ui/src/hooks/useDefinition/useDefinition.tsx @@ -0,0 +1,149 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ReactNode } from 'react'; +import clsx from 'clsx'; +import { useBreakpoint } from '../useBreakpoint'; +import { useBgProvider, useBgConsumer, BgProvider } from '../useBg'; +import { resolveDefinitionProps, processUtilityProps } from './helpers'; +import { useAnalytics } from '../../analytics/useAnalytics'; +import { noopTracker } from '../../analytics/useAnalytics'; +import { useInRouterContext, useHref } from 'react-router-dom'; +import type { + ComponentConfig, + UseDefinitionOptions, + UseDefinitionResult, + UtilityKeys, +} from './types'; + +export function useDefinition< + D extends ComponentConfig, + P extends Record, +>( + definition: D, + props: P, + options?: UseDefinitionOptions, +): UseDefinitionResult { + const { breakpoint } = useBreakpoint(); + + // Turn relative href into an absolute path using the current route + // context, so that client-side navigation works correctly. + let hrefResolvedProps = props; + const hasRouter = useInRouterContext(); + // useHref throws outside a Router, so we guard with useInRouterContext. + // The guard is safe because a component's router context does not + // change during its lifetime, keeping the hook call count stable. + if (hasRouter) { + const absoluteHref = useHref((props as any).href ?? ''); + if ((props as any).href !== undefined) { + hrefResolvedProps = { ...props, href: absoluteHref } as P; + } + } + + // Resolve all props centrally — applies responsive values and defaults + const { ownPropsResolved, restProps } = resolveDefinitionProps( + definition, + hrefResolvedProps, + breakpoint, + ); + + const dataAttributes: Record = {}; + + for (const [key, config] of Object.entries(definition.propDefs)) { + const finalValue = ownPropsResolved[key]; + + if (finalValue !== undefined) { + // Skip data-bg for bg prop when the provider path handles it + if (key === 'bg' && definition.bg === 'provider') continue; + + if ((config as any).dataAttribute) { + // eslint-disable-next-line no-restricted-syntax + dataAttributes[`data-${key.toLowerCase()}`] = String(finalValue); + } + } + } + + // Provider: resolve bg and provide context for children + const providerBg = useBgProvider( + definition.bg === 'provider' ? ownPropsResolved.bg : undefined, + ); + + // Consumer: read parent context bg + const consumerBg = useBgConsumer(); + + // Provider: set data-bg from the resolved provider bg + if (definition.bg === 'provider' && providerBg.bg !== undefined) { + dataAttributes['data-bg'] = String(providerBg.bg); + } + + // Consumer: set data-on-bg from the parent context + if (definition.bg === 'consumer' && consumerBg.bg !== undefined) { + dataAttributes['data-on-bg'] = String(consumerBg.bg); + } + + const { utilityClasses, utilityStyle } = processUtilityProps>( + props, + (definition.utilityProps ?? []) as readonly UtilityKeys[], + ); + + // Analytics: conditionally call useAnalytics based on definition flag + let analytics = noopTracker; + if (definition.analytics) { + const tracker = useAnalytics(); + analytics = ownPropsResolved.noTrack ? noopTracker : tracker; + } + + const utilityTarget = options?.utilityTarget ?? 'root'; + const classNameTarget = options?.classNameTarget ?? 'root'; + + const classes: Record = {}; + + for (const [name, cssKey] of Object.entries(definition.classNames)) { + classes[name] = clsx( + cssKey as string, + definition.styles[cssKey as keyof typeof definition.styles], + utilityTarget === name && utilityClasses, + classNameTarget === name && ownPropsResolved.className, + ); + } + + let children: ReactNode | undefined; + let childrenWithBgProvider: ReactNode | undefined; + + if (definition.bg === 'provider') { + childrenWithBgProvider = providerBg.bg ? ( + {props.children} + ) : ( + props.children + ); + } else { + children = props.children; + } + + return { + ownProps: { + classes, + ...ownPropsResolved, + ...(definition.bg === 'provider' + ? { childrenWithBgProvider } + : { children }), + }, + restProps, + dataAttributes, + utilityStyle, + ...(definition.analytics ? { analytics } : {}), + } as unknown as UseDefinitionResult; +} diff --git a/packages/ui/src/hooks/useStyles.ts b/packages/ui/src/hooks/useStyles.ts deleted file mode 100644 index cc05f5f362..0000000000 --- a/packages/ui/src/hooks/useStyles.ts +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright 2025 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { useBreakpoint, breakpoints } from './useBreakpoint'; -import type { ComponentDefinition } from '../types'; -import { utilityClassMap } from '../utils/utilityClassMap'; - -/** - * Resolve a responsive value based on the current breakpoint - * @param value - The responsive value (string or object with breakpoint keys) - * @param breakpoint - The current breakpoint - * @returns The resolved value for the current breakpoint - */ -function resolveResponsiveValue( - value: string | Record, - breakpoint: string, -): string | undefined { - if (typeof value === 'string') { - return value; - } - - if (typeof value === 'object' && value !== null) { - const index = breakpoints.findIndex(b => b.id === breakpoint); - - // Look for value at current breakpoint or smaller - for (let i = index; i >= 0; i--) { - if (value[breakpoints[i].id]) { - return value[breakpoints[i].id]; - } - } - - // If no value found, check from smallest breakpoint up - for (let i = 0; i < breakpoints.length; i++) { - if (value[breakpoints[i].id]) { - return value[breakpoints[i].id]; - } - } - } - - return undefined; -} - -/** - * React hook to get class names and data attributes for a component with responsive support - * @param componentDefinition - The component's definition object - * @param props - All component props - * @returns Object with classNames, dataAttributes, utilityClasses, style, and cleanedProps - */ -export function useStyles< - T extends ComponentDefinition, - P extends Record = Record, ->( - componentDefinition: T, - props: P = {} as P, -): { - classNames: T['classNames']; - dataAttributes: Record; - utilityClasses: string; - style: React.CSSProperties; - cleanedProps: P; -} { - const { breakpoint } = useBreakpoint(); - const classNames = componentDefinition.classNames; - const utilityPropNames = - ('utilityProps' in componentDefinition - ? componentDefinition.utilityProps - : []) || []; - - // Extract data attribute names from component definition - const dataAttributeNames = - 'dataAttributes' in componentDefinition - ? Object.keys(componentDefinition.dataAttributes || {}) - : []; - - // Extract existing style from props - const incomingStyle = props.style || {}; - - // Generate data attributes from component definition - const dataAttributes: Record = {}; - for (const key of dataAttributeNames) { - const value = props[key]; - if (value !== undefined && value !== null) { - // Handle boolean and number values directly - if (typeof value === 'boolean' || typeof value === 'number') { - dataAttributes[`data-${key}`] = String(value); - } else { - const resolvedValue = resolveResponsiveValue(value, breakpoint); - if (resolvedValue !== undefined) { - dataAttributes[`data-${key}`] = resolvedValue; - } - } - } - } - - // Generate utility classes and custom styles from component's allowed utility props - const utilityClassList: string[] = []; - const generatedStyle: React.CSSProperties = {}; - - const handleUtilityValue = ( - key: string, - val: unknown, - prefix: string = '', - ) => { - // Get utility class configuration for this key - const utilityConfig = utilityClassMap[key as keyof typeof utilityClassMap]; - - if (!utilityConfig) { - // Skip if no config found for this key - return; - } - - // Check if value is in the list of valid values for this utility - if ( - utilityConfig.values.length > 0 && - utilityConfig.values.includes(val as string | number) - ) { - // Generate utility class with value suffix and optional breakpoint prefix - const className = prefix - ? `${prefix}${utilityConfig.class}-${val}` - : `${utilityConfig.class}-${val}`; - utilityClassList.push(className); - } else if (utilityConfig.cssVar) { - // Custom value - add CSS custom property AND utility class name - // Only if cssVar is defined (properties with fixed values don't have cssVar) - const cssVarKey = prefix - ? `${utilityConfig.cssVar}-${prefix.slice(0, -1)}` - : utilityConfig.cssVar; - // CSS custom properties need to be set on the style object as strings - (generatedStyle as Record)[cssVarKey] = val; - - // Add utility class name (without value suffix) with optional breakpoint prefix - const className = prefix - ? `${prefix}${utilityConfig.class}` - : utilityConfig.class; - utilityClassList.push(className); - } - // If no cssVar and value is not in valid values, skip (invalid value for fixed-value property) - }; - - for (const key of utilityPropNames) { - const value = props[key]; - if (value === undefined || value === null) { - continue; - } - - // Check if value is a responsive object - if (typeof value === 'object' && value !== null) { - const breakpointValues = value as { [key: string]: unknown }; - // Handle responsive object values - for (const bp in breakpointValues) { - const prefix = bp === 'initial' ? '' : `${bp}:`; - handleUtilityValue(key, breakpointValues[bp], prefix); - } - } else { - // Handle simple value - handleUtilityValue(key, value); - } - } - - // Create cleaned props by excluding only utility props - // All other props (including data attributes, style, children, etc.) remain - const utilityPropsSet = new Set(utilityPropNames); - - const cleanedPropsBase = Object.keys(props).reduce((acc, key) => { - if (!utilityPropsSet.has(key)) { - acc[key] = props[key]; - } - return acc; - }, {} as any); - - // Merge incoming style with generated styles (incoming styles take precedence) - const mergedStyle = { - ...generatedStyle, - ...incomingStyle, - }; - - // Add merged style to cleanedProps - const cleanedProps = { - ...cleanedPropsBase, - style: mergedStyle, - } as P; - - return { - classNames, - dataAttributes, - utilityClasses: utilityClassList.join(' '), - style: mergedStyle, - cleanedProps, - }; -} diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 03ae3245e3..708e816349 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -25,33 +25,44 @@ export * from './components/Box'; export * from './components/Grid'; export * from './components/Flex'; export * from './components/Container'; +export * from './components/FullPage'; // UI components +export * from './components/Accordion'; +export * from './components/Alert'; export * from './components/Avatar'; +export * from './components/Badge'; export * from './components/Button'; export * from './components/Card'; -export * from './components/Accordion'; export * from './components/Dialog'; export * from './components/FieldLabel'; +export * from './components/PluginHeader'; export * from './components/Header'; -export * from './components/HeaderPage'; export * from './components/ButtonIcon'; export * from './components/ButtonLink'; export * from './components/Checkbox'; +export * from './components/CheckboxGroup'; export * from './components/RadioGroup'; +export * from './components/Slider'; export * from './components/Table'; export * from './components/TablePagination'; export * from './components/Tabs'; export * from './components/TagGroup'; export * from './components/Text'; export * from './components/TextField'; +export * from './components/PasswordField'; export * from './components/Tooltip'; export * from './components/Menu'; +export * from './components/Popover'; +export * from './components/SearchAutocomplete'; export * from './components/SearchField'; export * from './components/Link'; +export * from './components/List'; export * from './components/Select'; export * from './components/Skeleton'; export * from './components/Switch'; +export * from './components/ToggleButton'; +export * from './components/ToggleButtonGroup'; export * from './components/VisuallyHidden'; // Types @@ -59,3 +70,17 @@ export * from './types'; // Hooks export { useBreakpoint } from './hooks/useBreakpoint'; +export { useBgProvider, useBgConsumer, BgProvider } from './hooks/useBg'; +export type { BgContextValue, BgProviderProps } from './hooks/useBg'; + +// Provider +export { BUIProvider } from './provider'; +export type { BUIProviderProps } from './provider'; + +// Analytics +export { useAnalytics, getNodeText } from './analytics'; +export type { + AnalyticsTracker, + AnalyticsEventAttributes, + UseAnalyticsFn, +} from './analytics'; diff --git a/packages/ui/src/provider/BUIProvider.tsx b/packages/ui/src/provider/BUIProvider.tsx new file mode 100644 index 0000000000..6576a9eabb --- /dev/null +++ b/packages/ui/src/provider/BUIProvider.tsx @@ -0,0 +1,77 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useMemo, type ReactNode } from 'react'; +import { RouterProvider } from 'react-aria-components'; +import { useInRouterContext, useNavigate, useHref } from 'react-router-dom'; +import { createVersionedValueMap } from '@backstage/version-bridge'; +import { BUIContext } from '../analytics/useAnalytics'; +import type { UseAnalyticsFn } from '../analytics/types'; + +/** @public */ +export type BUIProviderProps = { + useAnalytics?: UseAnalyticsFn; + children: ReactNode; +}; + +/** + * Provides integration capabilities to all descendant BUI components. + * + * @example + * ```tsx + * import { BUIProvider } from '@backstage/ui'; + * import { useAnalytics as useBackstageAnalytics } from '@backstage/core-plugin-api'; + * + * function App() { + * return ( + * + * + * + * ); + * } + * ``` + * + * @public + */ +export function BUIProvider(props: BUIProviderProps) { + const { useAnalytics, children } = props; + const value = useMemo( + () => + createVersionedValueMap({ + 1: { useAnalytics }, + }), + [useAnalytics], + ); + + const content = ( + {children} + ); + + if (useInRouterContext()) { + return {content}; + } + + return content; +} + +function RoutedContent({ children }: { children: ReactNode }) { + const navigate = useNavigate(); + return ( + + {children} + + ); +} diff --git a/packages/ui/src/provider/index.ts b/packages/ui/src/provider/index.ts new file mode 100644 index 0000000000..7a99794d47 --- /dev/null +++ b/packages/ui/src/provider/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { BUIProvider } from './BUIProvider'; +export type { BUIProviderProps } from './BUIProvider'; diff --git a/packages/ui/src/recipes/CardsWithList.stories.tsx b/packages/ui/src/recipes/CardsWithList.stories.tsx new file mode 100644 index 0000000000..21113d99a4 --- /dev/null +++ b/packages/ui/src/recipes/CardsWithList.stories.tsx @@ -0,0 +1,268 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import preview from '../../../../.storybook/preview'; +import type { StoryFn } from '@storybook/react-vite'; +import { MemoryRouter } from 'react-router-dom'; +import { + Card, + CardHeader, + CardBody, + Container, + Grid, + Flex, + Text, + MenuItem, + TagGroup, + Tag, + List, + ListRow, +} from '..'; +import { + RiAccountCircleLine, + RiCloudLine, + RiCodeLine, + RiDeleteBinLine, + RiEdit2Line, + RiGitBranchLine, + RiJavascriptLine, + RiReactjsLine, + RiServerLine, + RiShareBoxLine, + RiShieldLine, + RiTerminalLine, +} from '@remixicon/react'; + +// --------------------------------------------------------------------------- +// Data +// --------------------------------------------------------------------------- + +interface ServiceItem { + id: string; + label: string; + description: string; + icon: React.ReactElement; + tags: string[]; +} + +const frontendServices: ServiceItem[] = [ + { + id: 'portal', + label: 'developer-portal', + description: 'Internal developer portal built on Backstage', + icon: , + tags: ['website', 'production'], + }, + { + id: 'design-system', + label: 'design-system', + description: 'Shared UI components and design tokens', + icon: , + tags: ['library', 'production'], + }, + { + id: 'docs-site', + label: 'docs-site', + description: 'Engineering documentation and runbooks', + icon: , + tags: ['website', 'production'], + }, + { + id: 'admin-ui', + label: 'admin-ui', + description: 'Internal tooling for platform administrators', + icon: , + tags: ['website', 'experimental'], + }, + { + id: 'onboarding-flow', + label: 'onboarding-flow', + description: 'New hire onboarding wizard and checklist', + icon: , + tags: ['website', 'experimental'], + }, +]; + +const backendServices: ServiceItem[] = [ + { + id: 'auth', + label: 'authentication-service', + description: 'Handles user authentication, sessions and token refresh', + icon: , + tags: ['service', 'production'], + }, + { + id: 'api-gateway', + label: 'api-gateway', + description: 'Routes and validates all inbound API requests', + icon: , + tags: ['service', 'production'], + }, + { + id: 'search', + label: 'search-indexer', + description: 'Indexes catalog entities for full-text search', + icon: , + tags: ['service', 'experimental'], + }, + { + id: 'ci-runner', + label: 'ci-runner', + description: 'Orchestrates and executes CI pipeline jobs', + icon: , + tags: ['service', 'production'], + }, + { + id: 'infra-provisioner', + label: 'infra-provisioner', + description: 'Terraform-based cloud resource provisioner', + icon: , + tags: ['service', 'experimental'], + }, +]; + +// --------------------------------------------------------------------------- +// Service list card +// --------------------------------------------------------------------------- + +interface ServiceListCardProps { + title: string; + items: ServiceItem[]; + description?: boolean; + icons?: boolean; +} + +const ServiceListCard = ({ + title, + items, + description = false, + icons = true, +}: ServiceListCardProps) => ( + + + + + + {title} + + + + + + + {items.map(item => ( + + }>Edit + }>Share + } color="danger"> + Delete + + + } + customActions={ + + {item.tags.map(tag => ( + {tag} + ))} + + } + > + {item.label} + + ))} + + + +); + +const withRouter = (Story: StoryFn) => ( + + + +); + +const meta = preview.meta({ + title: 'Recipes/Cards with List', + parameters: { + layout: 'fullscreen', + }, +}); + +export const Default = meta.story({ + decorators: [withRouter], + render: () => ( + + + + + + + ), +}); + +export const WithNoIcons = meta.story({ + decorators: [withRouter], + args: { + icons: false, + description: true, + }, + render: args => ( + + + + + + + ), +}); + +export const WithDescription = meta.story({ + args: { + description: true, + }, + render: args => ( + + + + + + + ), +}); diff --git a/packages/ui/src/recipes/PluginHeaderAndHeader.stories.tsx b/packages/ui/src/recipes/PluginHeaderAndHeader.stories.tsx new file mode 100644 index 0000000000..a43252a9b7 --- /dev/null +++ b/packages/ui/src/recipes/PluginHeaderAndHeader.stories.tsx @@ -0,0 +1,268 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import preview from '../../../../.storybook/preview'; +import type { StoryFn } from '@storybook/react-vite'; +import { MemoryRouter } from 'react-router-dom'; +import { BUIProvider } from '../provider'; +import type { HeaderNavTabItem } from '../components/Header/types'; +import { + PluginHeader, + Header, + Button, + ButtonIcon, + Card, + Container, + Flex, + MenuTrigger, + Menu, + MenuItem, +} from '..'; +import { + RiBox3Line, + RiCodeSSlashLine, + RiDownloadLine, + RiEdit2Line, + RiGitBranchLine, + RiMore2Line, + RiPlayLine, + RiRefreshLine, + RiSettings4Line, + RiShareBoxLine, +} from '@remixicon/react'; + +// --------------------------------------------------------------------------- +// Shared page content placeholder +// --------------------------------------------------------------------------- + +const PageContent = () => ( + + + + + + + +); + +// --------------------------------------------------------------------------- +// Shared layout decorator +// --------------------------------------------------------------------------- + +const withLayout = (Story: StoryFn) => ( + + + + + +); + +// --------------------------------------------------------------------------- +// Meta +// --------------------------------------------------------------------------- + +const meta = preview.meta({ + title: 'Recipes/PluginHeader and Header', + parameters: { + layout: 'fullscreen', + }, +}); + +export const NoHeader = meta.story({ + decorators: [withLayout], + render: () => ( + <> + } title="APIs" /> + + + ), +}); + +export const SimpleHeader = meta.story({ + decorators: [withLayout], + render: () => ( + <> + } title="APIs" /> +

    + + + ), +}); + +export const WithTabs = meta.story({ + decorators: [withLayout], + render: () => ( + <> + } + title="Catalog" + titleLink="/" + tabs={[ + { id: 'catalog', label: 'Catalog', href: '/catalog' }, + { id: 'apis', label: 'APIs', href: '/apis' }, + { id: 'resources', label: 'Resources', href: '/resources' }, + { id: 'templates', label: 'Templates', href: '/templates' }, + { id: 'docs', label: 'Docs', href: '/docs' }, + ]} + customActions={ + <> + } + aria-label="Settings" + /> + + } + aria-label="More options" + /> + + Import component + Register existing + View documentation + + + + } + /> +
    + + + + } + /> + + + ), +}); + +export const WithBreadcrumb = meta.story({ + decorators: [withLayout], + render: () => ( + <> + } + title="CI/CD" + titleLink="/" + tabs={[ + { id: 'builds', label: 'Builds', href: '/builds' }, + { id: 'pipelines', label: 'Pipelines', href: '/pipelines' }, + { id: 'deployments', label: 'Deployments', href: '/deployments' }, + { id: 'settings', label: 'Settings', href: '/settings' }, + ]} + customActions={ + <> + } + aria-label="Refresh" + /> + + } + /> +
    + + + + } + /> + + + ), +}); + +const subTabs: HeaderNavTabItem[] = [ + { id: 'summary', label: 'Summary', href: '/summary' }, + { id: 'steps', label: 'Steps', href: '/steps' }, + { id: 'artifacts', label: 'Artifacts', href: '/artifacts' }, + { id: 'logs', label: 'Logs', href: '/logs' }, +]; + +export const WithSubTabs = meta.story({ + decorators: [ + (Story: StoryFn) => ( + + + + + + ), + ], + render: () => { + return ( + <> + } + title="CI/CD" + titleLink="/" + tabs={[ + { id: 'builds', label: 'Builds', href: '/builds' }, + { id: 'pipelines', label: 'Pipelines', href: '/pipelines' }, + { id: 'deployments', label: 'Deployments', href: '/deployments' }, + { id: 'settings', label: 'Settings', href: '/settings' }, + ]} + customActions={ + <> + } + aria-label="Refresh" + /> + + } + /> +
    + + + + } + /> + + + ); + }, +}); diff --git a/packages/ui/src/types.ts b/packages/ui/src/types.ts index 7166194091..113eb06a5c 100644 --- a/packages/ui/src/types.ts +++ b/packages/ui/src/types.ts @@ -72,7 +72,11 @@ export type BorderRadius = | 'xl' | '2xl'; -/** @public */ +/** + * Border variants available for UI utility props. + * + * @public + */ export type Border = 'none' | 'base' | 'error' | 'warning' | 'selected'; /** @public */ @@ -92,7 +96,7 @@ export type Columns = | 'auto'; /** @public */ -export interface SpaceProps { +export interface MarginProps { m?: Responsive; mb?: Responsive; ml?: Responsive; @@ -100,6 +104,10 @@ export interface SpaceProps { mt?: Responsive; mx?: Responsive; my?: Responsive; +} + +/** @public */ +export interface PaddingProps { p?: Responsive; pb?: Responsive; pl?: Responsive; @@ -109,6 +117,9 @@ export interface SpaceProps { py?: Responsive; } +/** @public */ +export interface SpaceProps extends MarginProps, PaddingProps {} + /** @public */ export type TextVariants = | 'title-large' @@ -124,52 +135,76 @@ export type TextVariants = export type TextColors = 'primary' | 'secondary'; /** @public */ -export type TextColorStatus = 'danger' | 'warning' | 'success'; +export type TextColorStatus = 'danger' | 'warning' | 'success' | 'info'; /** @public */ export type TextWeights = 'regular' | 'bold'; -/** @public */ +/** + * Shared utility props supported by layout-oriented UI components. + * + * @public + */ export interface UtilityProps extends SpaceProps { + /** Aligns children on the cross axis in flex and grid layouts. */ alignItems?: Responsive; + /** Applies a semantic border variant. */ border?: Responsive; + /** Applies a semantic border radius token. */ borderRadius?: Responsive; + /** Sets the ending grid column line. */ colEnd?: Responsive; + /** Sets the number of grid columns to span. */ colSpan?: Responsive; + /** Sets the starting grid column line. */ colStart?: Responsive; + /** Sets the number of columns for grid containers. */ columns?: Responsive; + /** Controls the CSS display value. */ display?: Responsive; + /** Controls the direction of flex items. */ flexDirection?: Responsive; + /** Controls how flex items wrap. */ flexWrap?: Responsive; + /** Sets spacing between children in flex and grid layouts. */ gap?: Responsive; + /** Aligns children on the main axis in flex and grid layouts. */ justifyContent?: Responsive; + /** Sets the number of grid rows to span. */ rowSpan?: Responsive; } /** - * Base type for the component styles structure + * Resolved background level stored in context and applied as `data-bg` on DOM elements. + * Background type for the neutral bg system. + * + * Supports neutral levels ('neutral-1' through 'neutral-3') and + * intent backgrounds ('danger', 'warning', 'success'). + * + * The 'neutral-4' level is not exposed as a prop value -- it is reserved + * for leaf component CSS (e.g. Button on a 'neutral-3' surface). + * + * This is the resolved/internal representation used by the bg context system. + * For the prop type accepted by container components, use `ProviderBg` instead. + * * @public */ -export type ClassNamesMap = Record; +export type ContainerBg = + | 'neutral-1' + | 'neutral-2' + | 'neutral-3' + | 'danger' + | 'warning' + | 'success'; /** - * Base type for the component styles structure + * Background values accepted by provider components (Box, Flex, Grid, Card, etc.). + * + * - `'neutral'` — automatically increments the neutral level from the parent context, + * capping at the maximum level. This is always incremental; explicit levels cannot + * be set directly. + * - `'danger'` | `'warning'` | `'success'` — intent backgrounds used as-is. + * * @public */ -export type DataAttributeValues = readonly (string | number | boolean)[]; - -/** - * Base type for the component styles structure - * @public - */ -export type DataAttributesMap = Record; - -/** - * Base type for the component styles structure - * @public - */ -export interface ComponentDefinition { - classNames: ClassNamesMap; - dataAttributes?: DataAttributesMap; - utilityProps?: string[]; -} +export type ProviderBg = 'neutral' | 'danger' | 'warning' | 'success'; diff --git a/packages/ui/src/utils/isExternalLink.ts b/packages/ui/src/utils/linkUtils.ts similarity index 85% rename from packages/ui/src/utils/isExternalLink.ts rename to packages/ui/src/utils/linkUtils.ts index a874579ab5..8b2ded55c4 100644 --- a/packages/ui/src/utils/isExternalLink.ts +++ b/packages/ui/src/utils/linkUtils.ts @@ -40,3 +40,12 @@ export function isExternalLink(href?: string): boolean { return false; } + +/** + * Checks if an href is an internal link (not external and not empty). + * + * @internal + */ +export function isInternalLink(href: string | undefined): href is string { + return !!href && !isExternalLink(href); +} diff --git a/packages/ui/src/utils/utilityClassMap.ts b/packages/ui/src/utils/utilityClassMap.ts index 261d43632d..f70052d588 100644 --- a/packages/ui/src/utils/utilityClassMap.ts +++ b/packages/ui/src/utils/utilityClassMap.ts @@ -50,10 +50,7 @@ const columnsValues = [ 'auto', ] as const; -export const utilityClassMap: Record< - string, - { class: string; cssVar?: string; values: readonly (string | number)[] } -> = { +export const utilityClassMap = { m: { class: 'bui-m', cssVar: '--m', @@ -199,4 +196,7 @@ export const utilityClassMap: Record< class: 'bui-row-span', values: columnsValues, }, -}; +} as const satisfies Record< + string, + { class: string; cssVar?: string; values: readonly (string | number)[] } +>; diff --git a/packages/ui/static/favicon.svg b/packages/ui/static/favicon.svg index 12cad81e95..493d2b2508 100644 --- a/packages/ui/static/favicon.svg +++ b/packages/ui/static/favicon.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/packages/version-bridge/CHANGELOG.md b/packages/version-bridge/CHANGELOG.md index d3fa635f38..7ca00c7591 100644 --- a/packages/version-bridge/CHANGELOG.md +++ b/packages/version-bridge/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/version-bridge +## 1.0.12 + +### Patch Changes + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. + +## 1.0.12-next.0 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. + ## 1.0.11 ### Patch Changes diff --git a/packages/version-bridge/knip-report.md b/packages/version-bridge/knip-report.md index 2bdccfc4d2..b4719ad4d6 100644 --- a/packages/version-bridge/knip-report.md +++ b/packages/version-bridge/knip-report.md @@ -2,7 +2,7 @@ ## Unused devDependencies (1) -| Name | Location | Severity | -| :--------------- | :----------- | :------- | -| react-router-dom | packages/version-bridge/package.json | error | +| Name | Location | Severity | +| :--------------- | :---------------- | :------- | +| react-router-dom | package.json:45:6 | error | diff --git a/packages/version-bridge/package.json b/packages/version-bridge/package.json index d18d01359f..1587705367 100644 --- a/packages/version-bridge/package.json +++ b/packages/version-bridge/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/version-bridge", - "version": "1.0.11", + "version": "1.0.12", "description": "Utilities used by @backstage packages to support multiple concurrent versions", "backstage": { "role": "web-library" @@ -42,13 +42,13 @@ "@types/react": "^18.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/packages/yarn-plugin/CHANGELOG.md b/packages/yarn-plugin/CHANGELOG.md index 9ca9cbdf6f..32571f92d7 100644 --- a/packages/yarn-plugin/CHANGELOG.md +++ b/packages/yarn-plugin/CHANGELOG.md @@ -1,5 +1,78 @@ # yarn-plugin-backstage +## 0.0.11 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0 + - @backstage/cli-common@0.2.1 + +## 0.0.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/cli-common@0.2.1-next.1 + +## 0.0.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.2.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/release-manifests@0.0.13 + +## 0.0.10 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.2.0 + +## 0.0.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.2.0-next.0 + - @backstage/errors@1.2.7 + - @backstage/release-manifests@0.0.13 + +## 0.0.9 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.18 + +## 0.0.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.18-next.0 + - @backstage/errors@1.2.7 + - @backstage/release-manifests@0.0.13 + +## 0.0.8 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.16 + +## 0.0.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.16-next.0 + - @backstage/release-manifests@0.0.13 + - @backstage/errors@1.2.7 + ## 0.0.7 ### Patch Changes diff --git a/packages/yarn-plugin/package.json b/packages/yarn-plugin/package.json index 4bd43fce40..f3a478f501 100644 --- a/packages/yarn-plugin/package.json +++ b/packages/yarn-plugin/package.json @@ -1,6 +1,6 @@ { "name": "yarn-plugin-backstage", - "version": "0.0.7", + "version": "0.0.11", "description": "Yarn plugin for working with Backstage monorepos", "backstage": { "role": "node-library" @@ -46,7 +46,7 @@ "@yarnpkg/builder": "^4.2.1", "fs-extra": "^11.2.0", "nodemon": "^3.0.1", - "snyk-nodejs-lockfile-parser": "^1.58.14", + "snyk-nodejs-lockfile-parser": "^2.0.0", "yaml": "^2.0.0" } } diff --git a/packages/yarn-plugin/src/index.test.ts b/packages/yarn-plugin/src/index.test.ts index 75f120c157..4acd6a446f 100644 --- a/packages/yarn-plugin/src/index.test.ts +++ b/packages/yarn-plugin/src/index.test.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { join as joinPath } from 'path'; -import { spawn, SpawnOptionsWithoutStdio } from 'child_process'; +import { join as joinPath } from 'node:path'; +import { spawn, SpawnOptionsWithoutStdio } from 'node:child_process'; import fs from 'fs-extra'; import yaml from 'yaml'; import { buildDepTreeFromFiles } from 'snyk-nodejs-lockfile-parser'; -import { findPaths } from '@backstage/cli-common'; +import { targetPaths } from '@backstage/cli-common'; import { createMockDirectory } from '@backstage/backend-test-utils'; jest.setTimeout(30_000); @@ -86,7 +86,7 @@ describe('Backstage yarn plugin', () => { let initialLockFileContent: string | undefined; beforeAll(async () => { - const { targetRoot } = findPaths(process.cwd()); + const targetRoot = targetPaths.rootDir; await executeCommand('yarn', ['build'], { cwd: joinPath(targetRoot, 'packages/yarn-plugin'), }); diff --git a/packages/yarn-plugin/src/util/getCurrentBackstageVersion.ts b/packages/yarn-plugin/src/util/getCurrentBackstageVersion.ts index be80d29376..1221d14d7f 100644 --- a/packages/yarn-plugin/src/util/getCurrentBackstageVersion.ts +++ b/packages/yarn-plugin/src/util/getCurrentBackstageVersion.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import assert from 'assert'; +import assert from 'node:assert'; import { valid as semverValid } from 'semver'; import { ppath, xfs } from '@yarnpkg/fslib'; import { BACKSTAGE_JSON } from '@backstage/cli-common'; diff --git a/packages/yarn-plugin/src/util/getPackageVersion.ts b/packages/yarn-plugin/src/util/getPackageVersion.ts index 9ff7198c30..bb4761dab5 100644 --- a/packages/yarn-plugin/src/util/getPackageVersion.ts +++ b/packages/yarn-plugin/src/util/getPackageVersion.ts @@ -25,7 +25,7 @@ import { getManifestByVersion } from '@backstage/release-manifests'; import { PROTOCOL } from '../constants'; import { getCurrentBackstageVersion } from './getCurrentBackstageVersion'; -import { env } from 'process'; +import { env } from 'node:process'; export const getPackageVersion = async ( descriptor: Descriptor, diff --git a/packages/yarn-plugin/src/util/getWorkspaceRoot.test.ts b/packages/yarn-plugin/src/util/getWorkspaceRoot.test.ts index 11538b3d50..083baae907 100644 --- a/packages/yarn-plugin/src/util/getWorkspaceRoot.test.ts +++ b/packages/yarn-plugin/src/util/getWorkspaceRoot.test.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -import { findPaths, Paths } from '@backstage/cli-common'; - const setPlatform = (platform: string) => { Object.defineProperty(process, `platform`, { configurable: true, @@ -37,7 +35,6 @@ describe('getWorkspaceRoot', () => { `('platform: $platform', ({ platform, native, portable }) => { let realPlatform: string; let getWorkspaceRoot: () => string; - let mockFindPaths: jest.MockedFunction; beforeEach(() => { realPlatform = process.platform; @@ -45,13 +42,10 @@ describe('getWorkspaceRoot', () => { jest.resetModules(); - mockFindPaths = jest.fn(); - - jest.doMock('@backstage/cli-common', () => ({ - ...jest.requireActual('@backstage/cli-common'), - findPaths: mockFindPaths, - })); - + const { + overrideTargetPaths, + } = require('@backstage/cli-common/testUtils'); + overrideTargetPaths(native); getWorkspaceRoot = require('./getWorkspaceRoot').getWorkspaceRoot; }); @@ -60,10 +54,6 @@ describe('getWorkspaceRoot', () => { }); it('returns an appropriately-formatted workspace root path', () => { - mockFindPaths.mockReturnValue({ - targetRoot: native, - } as Paths); - expect(getWorkspaceRoot()).toEqual(portable); }); }); diff --git a/packages/yarn-plugin/src/util/getWorkspaceRoot.ts b/packages/yarn-plugin/src/util/getWorkspaceRoot.ts index d6f3e98e10..57e03daabe 100644 --- a/packages/yarn-plugin/src/util/getWorkspaceRoot.ts +++ b/packages/yarn-plugin/src/util/getWorkspaceRoot.ts @@ -14,11 +14,9 @@ * limitations under the License. */ -import { npath, ppath } from '@yarnpkg/fslib'; -import { findPaths } from '@backstage/cli-common'; +import { npath } from '@yarnpkg/fslib'; +import { targetPaths } from '@backstage/cli-common'; export const getWorkspaceRoot = () => { - return npath.toPortablePath( - findPaths(npath.fromPortablePath(ppath.cwd())).targetRoot, - ); + return npath.toPortablePath(targetPaths.rootDir); }; diff --git a/plugins/api-docs-module-protoc-gen-doc/CHANGELOG.md b/plugins/api-docs-module-protoc-gen-doc/CHANGELOG.md index ba93f2ee6a..cc8c9cde71 100644 --- a/plugins/api-docs-module-protoc-gen-doc/CHANGELOG.md +++ b/plugins/api-docs-module-protoc-gen-doc/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-api-docs-module-protoc-gen-doc +## 0.1.11 + +### Patch Changes + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. + +## 0.1.11-next.0 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. + ## 0.1.10 ### Patch Changes diff --git a/plugins/api-docs-module-protoc-gen-doc/knip-report.md b/plugins/api-docs-module-protoc-gen-doc/knip-report.md index 5933dc809c..456244b7c1 100644 --- a/plugins/api-docs-module-protoc-gen-doc/knip-report.md +++ b/plugins/api-docs-module-protoc-gen-doc/knip-report.md @@ -2,8 +2,8 @@ ## Unused devDependencies (2) -| Name | Location | Severity | -| :--------------- | :----------- | :------- | -| react-router-dom | plugins/api-docs-module-protoc-gen-doc/package.json | error | -| react-dom | plugins/api-docs-module-protoc-gen-doc/package.json | error | +| Name | Location | Severity | +| :--------------- | :---------------- | :------- | +| react-router-dom | package.json:48:6 | error | +| react-dom | package.json:47:6 | error | diff --git a/plugins/api-docs-module-protoc-gen-doc/package.json b/plugins/api-docs-module-protoc-gen-doc/package.json index 9fef705026..b11973a67f 100644 --- a/plugins/api-docs-module-protoc-gen-doc/package.json +++ b/plugins/api-docs-module-protoc-gen-doc/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-api-docs-module-protoc-gen-doc", - "version": "0.1.10", + "version": "0.1.11", "description": "Additional functionalities for the api-docs plugin that renders the output of the protoc-gen-doc", "backstage": { "role": "frontend-plugin-module", @@ -45,13 +45,13 @@ "@types/react": "^18.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index abf9b70796..153faf432e 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,297 @@ # @backstage/plugin-api-docs +## 0.14.0 + +### Minor Changes + +- b871d4e: Use Entity Presentation API for entity display in api-docs plugin + +### Patch Changes + +- da17844: Update readme to add instructions for custom api base URL +- Updated dependencies + - @backstage/ui@0.14.0 + - @backstage/plugin-catalog@2.0.2 + - @backstage/catalog-model@1.8.0 + - @backstage/plugin-catalog-react@2.1.2 + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/core-components@0.18.9 + - @backstage/plugin-permission-react@0.5.0 + - @backstage/core-plugin-api@1.12.5 + - @backstage/plugin-catalog-common@1.1.9 + +## 0.13.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.2 + - @backstage/plugin-catalog@2.0.2-next.2 + - @backstage/core-components@0.18.9-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.2 + - @backstage/catalog-model@1.7.8-next.0 + - @backstage/core-plugin-api@1.12.5-next.2 + - @backstage/frontend-plugin-api@0.16.0-next.2 + - @backstage/plugin-catalog-common@1.1.9-next.0 + - @backstage/plugin-permission-react@0.4.42-next.1 + +## 0.13.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-catalog@2.0.2-next.1 + +## 0.13.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@2.0.2-next.0 + - @backstage/ui@0.14.0-next.0 + - @backstage/plugin-catalog-react@2.1.1-next.0 + - @backstage/core-components@0.18.9-next.0 + - @backstage/frontend-plugin-api@0.15.2-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + - @backstage/catalog-model@1.7.7 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-react@0.4.42-next.0 + +## 0.13.5 + +### Patch Changes + +- 9c9d425: Fixed invisible text in parameter input fields when using dark mode in OpenAPI definition pages +- 538c985: Updated installation documentation to use feature discovery as the default. +- 30e08df: Added default entity content groups for the API docs entity content tabs. The API definition tab defaults to the `documentation` group and the APIs tab defaults to the `development` group. +- c548a0f: Migrated entity table cards (`ConsumedApisCard`, `ProvidedApisCard`, `HasApisCard`, `ConsumingComponentsCard`, `ProvidingComponentsCard`) to use BUI when no legacy props are passed. The old `variant`, `columns`, and `tableOptions` props are deprecated but still supported — passing any of them triggers the legacy MUI-based rendering. The new `columnConfig` prop accepts `EntityColumnConfig[]` for BUI-based rendering. +- aa29b50: New frontend system pages now use the default plugin header together with `HeaderPage` instead of the legacy core page header pattern. +- 3f36ce1: Updated alpha plugin icons to follow the new frontend icon sizing rules when rendered in plugin and navigation surfaces. +- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point. +- ca277ef: Updated dependency `graphiql` to `3.9.0` to address security vulnerability in `markdown-it` package. + Updated dependency `@graphiql/react` to `0.29.0` to match the version used by `graphiql`. + Moved dependency `graphql-config` to `devDependencies` as it is needed only for types. +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/ui@0.13.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/plugin-catalog@2.0.0 + - @backstage/plugin-permission-react@0.4.41 + - @backstage/catalog-model@1.7.7 + +## 0.13.5-next.2 + +### Patch Changes + +- ca277ef: Updated dependency `graphiql` to `3.9.0` to address security vulnerability in `markdown-it` package. + Updated dependency `@graphiql/react` to `0.29.0` to match the version used by `graphiql`. + Moved dependency `graphql-config` to `devDependencies` as it is needed only for types. +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.2 + - @backstage/core-components@0.18.8-next.1 + - @backstage/plugin-catalog@2.0.0-next.2 + +## 0.13.5-next.1 + +### Patch Changes + +- 30e08df: Added default entity content groups for the API docs entity content tabs. The API definition tab defaults to the `documentation` group and the APIs tab defaults to the `development` group. +- Updated dependencies + - @backstage/plugin-catalog@1.34.0-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## 0.13.5-next.0 + +### Patch Changes + +- 9c9d425: Fixed invisible text in parameter input fields when using dark mode in OpenAPI definition pages +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/plugin-catalog@1.33.1-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## 0.13.4 + +### Patch Changes + +- ac9bead: Added `@backstage/frontend-test-utils` dev dependency. +- 7455dae: Use node prefix on native imports +- 7feb83b: Adjusted to use the new `@backstage/filter-predicates` types for predicate expressions. +- 491a06c: Add the ability to show icons for the tabs on the entity page (new frontend) +- 4183614: Updated usage of deprecated APIs in the new frontend system. +- 018ca87: Added `title` and `icon` to the plugin definition for the new frontend system. +- 629c3ec: Add `tableOptions` and `title` to Components cards of APIs +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/core-components@0.18.7 + - @backstage/plugin-catalog@1.33.0 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-permission-react@0.4.40 + - @backstage/plugin-catalog-common@1.1.8 + +## 0.13.4-next.2 + +### Patch Changes + +- ac9bead: Added `@backstage/frontend-test-utils` dev dependency. +- 7feb83b: Adjusted to use the new `@backstage/filter-predicates` types for predicate expressions. +- 491a06c: Add the ability to show icons for the tabs on the entity page (new frontend) +- 4183614: Updated usage of deprecated APIs in the new frontend system. +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/plugin-catalog@1.33.0-next.2 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-permission-react@0.4.40-next.1 + +## 0.13.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/plugin-catalog@1.33.0-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/core-components@0.18.7-next.1 + +## 0.13.4-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/plugin-catalog@1.32.3-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.8-next.0 + - @backstage/plugin-permission-react@0.4.40-next.0 + +## 0.13.3 + +### Patch Changes + +- 0216090: Updated dependency `@types/swagger-ui-react` to `^5.0.0`. +- abeba2b: Fix types with new bumped dependency +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/plugin-catalog@1.32.2 + - @backstage/core-components@0.18.5 + - @backstage/plugin-catalog-react@1.21.5 + +## 0.13.3-next.2 + +### Patch Changes + +- 0216090: Updated dependency `@types/swagger-ui-react` to `^5.0.0`. +- abeba2b: Fix types with new bumped dependency +- Updated dependencies + - @backstage/plugin-catalog@1.32.2-next.2 + +## 0.13.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/plugin-catalog@1.32.2-next.1 + - @backstage/plugin-catalog-react@1.21.5-next.1 + +## 0.13.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.32.2-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## 0.13.2 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/plugin-catalog@1.32.1 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/plugin-permission-react@0.4.39 + +## 0.13.2-next.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- Updated dependencies + - @backstage/plugin-catalog@1.32.1-next.1 + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-model@1.7.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-permission-react@0.4.39-next.0 + +## 0.13.2-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/plugin-catalog@1.32.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## 0.13.1 + +### Patch Changes + +- 7c281a5: Add i18n support for Raw tab title and an error message +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-catalog@1.32.0 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + ## 0.13.1-next.1 ### Patch Changes diff --git a/plugins/api-docs/README-alpha.md b/plugins/api-docs/README-alpha.md index 1c30d62cd4..a4c3979787 100644 --- a/plugins/api-docs/README-alpha.md +++ b/plugins/api-docs/README-alpha.md @@ -1,8 +1,6 @@ -# Api Docs +# Api Docs - Extension Reference -> [!WARNING] -> This documentation is made for those using the experimental new Frontend system. -> If you are not using the new frontend system, please go [here](./README.md). +This page contains detailed documentation for all extensions provided by the `@backstage/plugin-api-docs` plugin. For general information about the plugin, see the [README](./README.md). This is an extension for the catalog plugin that provides components to discover and display API entities. APIs define the interface between components, see the [system model](https://backstage.io/docs/features/software-catalog/system-model) for details. @@ -77,12 +75,14 @@ To link that a component provides or consumes an API, see the [`providesApis`](h - entity-card:api-docs/providing-components: config: # Presenting the card only for entities of kind api - filter: kind:api + filter: + kind: api # Shows a table of components that consumes a particular api - entity-card:api-docs/consuming-components: config: # Presenting the card only for entities of kind api - filter: kind:api + filter: + kind: api # Enabling some contents # The contents will be displayed in the same order it appears in this setting list # Shows a "Definition" tab for entities of kind api @@ -352,7 +352,8 @@ app: # The default value is a function that verifies it is a components has api pat of relations # For more information about entity cards filters, check out this pull request # https://github.com/backstage/backstage/pull/21480 - filter: 'kind:component' + filter: + kind: component ``` ###### Override @@ -361,17 +362,19 @@ Use extension overrides for completely re-implementing the has apis entity card ```tsx import { createFrontendModule } from '@backstage/backstage-plugin-api'; -import { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha'; +import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha'; export default createFrontendModule({ pluginId: 'api-docs', extensions: [ - createEntityCardExtension({ + EntityCardBlueprint.make({ // Name is necessary so the system knows that this extension will override the default 'has-apis' entity card extension provided by the 'api-docs' plugin name: 'has-apis', - // Returning a custom card component - loader: () => - import('./components').then(m => ), + params: { + // Returning a custom card component + loader: () => + import('./components').then(m => ), + }, }), ], }); @@ -422,29 +425,34 @@ app: # /: - entity-card:api-docs/definition: config: - # Default to 'kind:api' + # Default to { kind: 'api' } # For more information about entity cards filters, check out this pull request # https://github.com/backstage/backstage/pull/21480 - filter: 'kind:component' + filter: + kind: component ``` ###### Override -Use extension overrides for completely re-implementing the has apis entity card extension: +Use extension overrides for completely re-implementing the definition entity card extension: ```tsx import { createFrontendModule } from '@backstage/backstage-plugin-api'; -import { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha'; +import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha'; export default createFrontendModule({ pluginId: 'api-docs', extensions: [ - createEntityCardExtension({ + EntityCardBlueprint.make({ // Name is necessary so the system knows that this extension will override the default 'definition' entity card extension provided by the 'api-docs' plugin name: 'definition', - // Returning a custom card component - loader: () => - import('./components').then(m => ), + params: { + // Returning a custom card component + loader: () => + import('./components').then(m => ( + + )), + }, }), ], }); @@ -495,29 +503,34 @@ app: # /: - entity-card:api-docs/provided-apis: config: - # Default to 'kind:component' + # Default to { kind: 'component' } # For more information about entity cards filters, check out this pull request # https://github.com/backstage/backstage/pull/21480 - filter: 'kind:component' + filter: + kind: component ``` ###### Override -Use extension overrides for completely re-implementing the has apis entity card extension: +Use extension overrides for completely re-implementing the provided-apis entity card extension: ```tsx import { createFrontendModule } from '@backstage/backstage-plugin-api'; -import { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha'; +import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha'; export default createFrontendModule({ pluginId: 'api-docs', extensions: [ - createEntityCardExtension({ + EntityCardBlueprint.make({ // Name is necessary so the system knows that this extension will override the default 'provided-apis' entity card extension provided by the 'api-docs' plugin name: 'provided-apis', - // Returning a custom card component - loader: () => - import('./components').then(m => ), + params: { + // Returning a custom card component + loader: () => + import('./components').then(m => ( + + )), + }, }), ], }); @@ -568,29 +581,34 @@ app: # /: - entity-card:api-docs/consumed-apis: config: - # Default to 'kind:component' + # Default to { kind: 'component' } # For more information about entity cards filters, check out this pull request # https://github.com/backstage/backstage/pull/21480 - filter: 'kind:component' + filter: + kind: component ``` ###### Override -Use extension overrides for completely re-implementing the has apis entity card extension: +Use extension overrides for completely re-implementing the consumed-apis entity card extension: ```tsx import { createFrontendModule } from '@backstage/backstage-plugin-api'; -import { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha'; +import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha'; export default createFrontendModule({ pluginId: 'api-docs', extensions: [ - createEntityCardExtension({ + EntityCardBlueprint.make({ // Name is necessary so the system knows that this extension will override the default 'consumed-apis' entity card extension provided by the 'api-docs' plugin name: 'consumed-apis', - // Returning a custom card component - loader: () => - import('./components').then(m => ), + params: { + // Returning a custom card component + loader: () => + import('./components').then(m => ( + + )), + }, }), ], }); @@ -641,31 +659,34 @@ app: # /: - entity-card:api-docs/providing-components: config: - # Default to 'kind:api' + # Default to { kind: 'api' } # For more information about entity cards filters, check out this pull request # https://github.com/backstage/backstage/pull/21480 - filter: 'kind:component' + filter: + kind: component ``` ###### Override -Use extension overrides for completely re-implementing the has apis entity card extension: +Use extension overrides for completely re-implementing the providing-components entity card extension: ```tsx import { createFrontendModule } from '@backstage/backstage-plugin-api'; -import { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha'; +import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha'; export default createFrontendModule({ pluginId: 'api-docs', extensions: [ - createEntityCardExtension({ + EntityCardBlueprint.make({ // Name is necessary so the system knows that this extension will override the default 'providing-components' entity card extension provided by the 'api-docs' plugin name: 'providing-components', - // Returning a custom card component - loader: () => - import('./components').then(m => ( - - )), + params: { + // Returning a custom card component + loader: () => + import('./components').then(m => ( + + )), + }, }), ], }); @@ -716,31 +737,34 @@ app: # /: - entity-card:api-docs/consuming-components: config: - # Default to 'kind:api' + # Default to { kind: 'api' } # For more information about entity cards filters, check out this pull request # https://github.com/backstage/backstage/pull/21480 - filter: 'kind:component' + filter: + kind: component ``` ###### Override -Use extension overrides for completely re-implementing the has apis entity card extension: +Use extension overrides for completely re-implementing the consuming-components entity card extension: ```tsx import { createFrontendModule } from '@backstage/backstage-plugin-api'; -import { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha'; +import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha'; export default createFrontendModule({ pluginId: 'api-docs', extensions: [ - createEntityCardExtension({ + EntityCardBlueprint.make({ // Name is necessary so the system knows that this extension will override the default 'consuming-components' entity card extension provided by the 'api-docs' plugin name: 'consuming-components', - // Returning a custom card component - loader: () => - import('./components').then(m => ( - - )), + params: { + // Returning a custom card component + loader: () => + import('./components').then(m => ( + + )), + }, }), ], }); @@ -798,11 +822,12 @@ app: # /: - entity-content:api-docs/definition: config: - # A text-based query used to filter whether the entity contentextension should be rendered or not. + # An entity predicate used to filter whether the entity content extension should be rendered or not. # For more information about entity cards filters, check out this pull request # https://github.com/backstage/backstage/pull/21480 - # defaults to 'kind:api' - filter: 'kind:api' + # defaults to { kind: 'api' } + filter: + kind: api # The entity content table title # defaults to 'Definition' title: 'Definition' @@ -813,23 +838,25 @@ app: ###### Override -Use extension overrides for completely re-implementing the has apis entity card extension: +Use extension overrides for completely re-implementing the definition entity content extension: ```tsx import { createFrontendModule } from '@backstage/backstage-plugin-api'; -import { createEntityContentExtension } from '@backstage/plugin-catalog-react/alpha'; +import { EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha'; export default createFrontendModule({ pluginId: 'api-docs', extensions: [ - createEntityContentExtension({ + EntityContentBlueprint.make({ // Name is necessary so the system knows that this extension will override the default 'definition' entity content extension provided by the 'api-docs' plugin name: 'definition', - // Returning a custom content component - loader: () => - import('./components').then(m => ( - - )), + params: { + // Returning a custom content component + loader: () => + import('./components').then(m => ( + + )), + }, }), ], }); @@ -878,11 +905,12 @@ app: # /: - entity-content:api-docs/apis: config: - # A text-based query used to filter whether the entity contentextension should be rendered or not. + # An entity predicate used to filter whether the entity content extension should be rendered or not. # For more information about entity cards filters, check out this pull request # https://github.com/backstage/backstage/pull/21480 - # defaults to 'kind:component' - filter: 'kind:component' + # defaults to { kind: 'component' } + filter: + kind: component # The entity content table title # defaults to 'Definition' title: 'Definition' @@ -897,17 +925,19 @@ Use extension overrides for completely re-implementing the apis entity content e ```tsx import { createFrontendModule } from '@backstage/backstage-plugin-api'; -import { createEntityContentExtension } from '@backstage/plugin-catalog-react/alpha'; +import { EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha'; export default createFrontendModule({ pluginId: 'api-docs', extensions: [ - createEntityContentExtension({ + EntityContentBlueprint.make({ // Name is necessary so the system knows that this extension will override the default 'apis' entity content extension provided by the 'api-docs' plugin name: 'apis', - // Returning a custom content component - loader: () => - import('./components').then(m => ), + params: { + // Returning a custom content component + loader: () => + import('./components').then(m => ), + }, }), ], }); diff --git a/plugins/api-docs/README.md b/plugins/api-docs/README.md index 139a97d4c8..5cb15e923f 100644 --- a/plugins/api-docs/README.md +++ b/plugins/api-docs/README.md @@ -1,8 +1,5 @@ # API Documentation -> Disclaimer: -> If you are looking for documentation on the experimental new frontend system support, please go [here](./README-alpha.md). - This is an extension for the catalog plugin that provides components to discover and display API entities. APIs define the interface between components, see the [system model](https://backstage.io/docs/features/software-catalog/system-model) for details. They are defined in machine readable formats and provide a human readable documentation. @@ -28,77 +25,32 @@ To link that a component provides or consumes an API, see the [`providesApis`](h > The plugin is already added when using `npx @backstage/create-app` so you can skip these steps. -1. Install the API docs plugin - ```bash # From your Backstage root directory yarn --cwd packages/app add @backstage/plugin-api-docs ``` -2. Add the `ApiExplorerPage` extension to the app: +Once installed, the plugin is automatically available in your app through the default feature discovery. For more details and alternative installation methods, see [installing plugins](https://backstage.io/docs/frontend-system/building-apps/installing-plugins). -```tsx -// In packages/app/src/App.tsx +You can enable entity cards and tabs on the catalog entity page through configuration: -import { ApiExplorerPage } from '@backstage/plugin-api-docs'; - -} />; +```yaml +# app-config.yaml +app: + extensions: + - entity-card:api-docs/providing-components: + config: + filter: + kind: api + - entity-card:api-docs/consuming-components: + config: + filter: + kind: api + - entity-content:api-docs/definition + - entity-content:api-docs/apis ``` -3. Add one of the provided widgets to the EntityPage: - -```tsx -// packages/app/src/components/catalog/EntityPage.tsx - -import { - EntityAboutCard, - EntityApiDefinitionCard, - EntityConsumingComponentsCard, - EntityProvidingComponentsCard, -} from '@backstage/plugin-api-docs'; - -const apiPage = ( - - - - - - - - - - - - - - - - - - - - - - - - - - - -); - -// ... - -export const entityPage = ( - - // ... - - // ... - -); -``` - -There are other components to discover in [`./src/components`](./src/components) that are also added by the default app. +For the full list of available extensions and their configuration options, see the [README-alpha.md](./README-alpha.md). ## Customizations @@ -388,6 +340,103 @@ import { ApiExplorerPage } from '@backstage/plugin-api-docs'; />; ``` +## Troubleshooting + +### "Try it out" sends requests to the wrong host + +If the **Try it out** feature in the OpenAPI widget sends requests to your Backstage app's URL instead of the actual API host, the OpenAPI spec is missing a `servers` entry. +When no `servers` field is present, Swagger UI falls back to `window.location.origin` — the current page's host — as the base URL for all requests. + +**Fix**: add a `servers` field to your API entity's `spec.definition`: + +```yaml +# catalog-info.yaml +apiVersion: backstage.io/v1alpha1 +kind: API +metadata: + name: my-api +spec: + type: openapi + definition: | + openapi: "3.0.0" + info: + title: My API + version: v1 + servers: + - url: https://api.example.com/v1 # ← specify your api base URL +``` + +If you cannot modify the spec (e.g. it is generated or fetched from an external source), you can work around this by adding a `requestInterceptor` that rewrites the URL — see [Adding `requestInterceptor` to Swagger UI](#adding-requestinterceptor-to-swagger-ui) above. + +## Old Frontend System + +If your Backstage app uses the old frontend system, you need to manually wire the plugin into your app as outlined in this section. If you are on the new frontend system, you can skip this. + +1. Add the `ApiExplorerPage` extension to the app: + +```tsx +// In packages/app/src/App.tsx + +import { ApiExplorerPage } from '@backstage/plugin-api-docs'; + +} />; +``` + +2. Add one of the provided widgets to the EntityPage: + +```tsx +// packages/app/src/components/catalog/EntityPage.tsx + +import { + EntityAboutCard, + EntityApiDefinitionCard, + EntityConsumingComponentsCard, + EntityProvidingComponentsCard, +} from '@backstage/plugin-api-docs'; + +const apiPage = ( + + + + + + + + + + + + + + + + + + + + + + + + + + + +); + +// ... + +export const entityPage = ( + + // ... + + // ... + +); +``` + +There are other components to discover in [`./src/components`](./src/components) that are also added by the default app. + ## Links - [The Backstage homepage](https://backstage.io) diff --git a/plugins/api-docs/dev/trpc-example-api.yaml b/plugins/api-docs/dev/trpc-example-api.yaml index f5807537b3..f1847154ce 100644 --- a/plugins/api-docs/dev/trpc-example-api.yaml +++ b/plugins/api-docs/dev/trpc-example-api.yaml @@ -8,7 +8,7 @@ spec: lifecycle: experimental owner: team-c definition: | - import { z } from 'zod'; + import { z } from 'zod/v3'; import { publicProcedure, router } from '../trpc'; export const apiRouter = router({ diff --git a/plugins/api-docs/knip-report.md b/plugins/api-docs/knip-report.md index 406a44f365..bf57458a6d 100644 --- a/plugins/api-docs/knip-report.md +++ b/plugins/api-docs/knip-report.md @@ -1,17 +1,16 @@ # Knip report -## Unused dependencies (3) +## Unused dependencies (1) -| Name | Location | Severity | -| :------------------- | :----------- | :------- | -| isomorphic-form-data | plugins/api-docs/package.json | error | -| graphql-config | plugins/api-docs/package.json | error | -| graphql-ws | plugins/api-docs/package.json | error | +| Name | Location | Severity | +| :--------- | :---------------- | :------- | +| graphql-ws | package.json:73:6 | error | -## Unused devDependencies (2) +## Unused devDependencies (3) -| Name | Location | Severity | -| :---------------------- | :----------- | :------- | -| @backstage/core-app-api | plugins/api-docs/package.json | error | -| @types/highlightjs | plugins/api-docs/package.json | error | +| Name | Location | Severity | +| :---------------------- | :---------------- | :------- | +| @backstage/core-app-api | package.json:78:6 | error | +| @types/highlightjs | package.json:86:6 | error | +| graphql-config | package.json:89:6 | error | diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 9215ed5101..8e95a66cec 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-api-docs", - "version": "0.13.1-next.1", + "version": "0.14.0", "description": "A Backstage plugin that helps represent API entities in the frontend", "backstage": { "role": "frontend-plugin", @@ -55,7 +55,6 @@ "dependencies": { "@asyncapi/react-component": "^2.3.3", "@backstage/catalog-model": "workspace:^", - "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", @@ -63,13 +62,14 @@ "@backstage/plugin-catalog-common": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", "@backstage/plugin-permission-react": "workspace:^", - "@graphiql/react": "^0.23.0", + "@backstage/ui": "workspace:^", + "@graphiql/react": "0.29.0", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", - "graphiql": "3.1.1", + "@remixicon/react": "^4.6.0", + "graphiql": "^3.9.0", "graphql": "^16.0.0", - "graphql-config": "^5.0.2", "graphql-ws": "^5.4.1", "swagger-ui-react": "^5.27.1" }, @@ -77,6 +77,7 @@ "@backstage/cli": "workspace:^", "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", + "@backstage/frontend-test-utils": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.0.0", @@ -84,16 +85,17 @@ "@testing-library/user-event": "^14.0.0", "@types/highlightjs": "^10.1.0", "@types/react": "^18.0.0", - "@types/swagger-ui-react": "^4.18.0", + "@types/swagger-ui-react": "^5.0.0", + "graphql-config": "^5.1.6", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/plugins/api-docs/report-alpha.api.md b/plugins/api-docs/report-alpha.api.md index 7d134dcb6d..630e6aef89 100644 --- a/plugins/api-docs/report-alpha.api.md +++ b/plugins/api-docs/report-alpha.api.md @@ -6,27 +6,35 @@ import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { ApiFactory } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { defaultEntityContentGroups } from '@backstage/plugin-catalog-react/alpha'; import { Entity } from '@backstage/catalog-model'; import { EntityCardType } from '@backstage/plugin-catalog-react/alpha'; -import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; -import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; -import { IconComponent } from '@backstage/core-plugin-api'; +import { ExternalRouteRef } from '@backstage/core-plugin-api'; +import { FilterPredicate } from '@backstage/filter-predicates'; +import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; +import { JSXElementConstructor } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; -import { RouteRef } from '@backstage/frontend-plugin-api'; +import { ReactElement } from 'react'; +import { RouteRef } from '@backstage/core-plugin-api'; +import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api'; import { TranslationRef } from '@backstage/frontend-plugin-api'; -// @alpha (undocumented) +// @alpha @deprecated (undocumented) export const apiDocsTranslationRef: TranslationRef< 'api-docs', { + readonly 'apiDefinitionCard.error.title': 'Could not fetch the API'; + readonly 'apiDefinitionCard.rawButtonTitle': 'Raw'; readonly 'apiDefinitionDialog.closeButtonTitle': 'Close'; readonly 'apiDefinitionDialog.tabsAriaLabel': 'API definition options'; + readonly 'apiDefinitionDialog.rawButtonTitle': 'Raw'; readonly 'apiDefinitionDialog.toggleButtonAriaLabel': 'Toggle API Definition Dialog'; readonly 'defaultApiExplorerPage.title': 'APIs'; readonly 'defaultApiExplorerPage.subtitle': '{{orgName}} API Explorer'; @@ -50,7 +58,7 @@ export const apiDocsTranslationRef: TranslationRef< readonly 'providingComponentsCard.error.title': 'Could not load components'; readonly 'providingComponentsCard.title': 'Providers'; readonly 'providingComponentsCard.emptyContent.title': 'No component provides this API.'; - readonly apisCardHelpLinkTitle: 'Learn how to change this'; + readonly apisCardHelpLinkTitle: 'Learn how to change this.'; } >; @@ -60,7 +68,7 @@ const _default: OverridableFrontendPlugin< root: RouteRef; }, { - registerApi: ExternalRouteRef; + registerApi: ExternalRouteRef; }, { 'api:api-docs/config': OverridableExtensionDefinition<{ @@ -82,12 +90,12 @@ const _default: OverridableFrontendPlugin< kind: 'entity-card'; name: 'consumed-apis'; config: { - filter: EntityPredicate | undefined; - type: 'content' | 'summary' | 'info' | undefined; + filter: FilterPredicate | undefined; + type: 'content' | 'info' | undefined; }; configInput: { - filter?: EntityPredicate | undefined; - type?: 'content' | 'summary' | 'info' | undefined; + filter?: FilterPredicate | undefined; + type?: 'content' | 'info' | undefined; }; output: | ExtensionDataRef @@ -115,7 +123,7 @@ const _default: OverridableFrontendPlugin< inputs: {}; params: { loader: () => Promise; - filter?: string | EntityPredicate | ((entity: Entity) => boolean); + filter?: string | FilterPredicate | ((entity: Entity) => boolean); type?: EntityCardType; }; }>; @@ -123,12 +131,12 @@ const _default: OverridableFrontendPlugin< kind: 'entity-card'; name: 'consuming-components'; config: { - filter: EntityPredicate | undefined; - type: 'content' | 'summary' | 'info' | undefined; + filter: FilterPredicate | undefined; + type: 'content' | 'info' | undefined; }; configInput: { - filter?: EntityPredicate | undefined; - type?: 'content' | 'summary' | 'info' | undefined; + filter?: FilterPredicate | undefined; + type?: 'content' | 'info' | undefined; }; output: | ExtensionDataRef @@ -156,7 +164,7 @@ const _default: OverridableFrontendPlugin< inputs: {}; params: { loader: () => Promise; - filter?: string | EntityPredicate | ((entity: Entity) => boolean); + filter?: string | FilterPredicate | ((entity: Entity) => boolean); type?: EntityCardType; }; }>; @@ -164,12 +172,12 @@ const _default: OverridableFrontendPlugin< kind: 'entity-card'; name: 'definition'; config: { - filter: EntityPredicate | undefined; - type: 'content' | 'summary' | 'info' | undefined; + filter: FilterPredicate | undefined; + type: 'content' | 'info' | undefined; }; configInput: { - filter?: EntityPredicate | undefined; - type?: 'content' | 'summary' | 'info' | undefined; + filter?: FilterPredicate | undefined; + type?: 'content' | 'info' | undefined; }; output: | ExtensionDataRef @@ -197,7 +205,7 @@ const _default: OverridableFrontendPlugin< inputs: {}; params: { loader: () => Promise; - filter?: string | EntityPredicate | ((entity: Entity) => boolean); + filter?: string | FilterPredicate | ((entity: Entity) => boolean); type?: EntityCardType; }; }>; @@ -205,12 +213,12 @@ const _default: OverridableFrontendPlugin< kind: 'entity-card'; name: 'has-apis'; config: { - filter: EntityPredicate | undefined; - type: 'content' | 'summary' | 'info' | undefined; + filter: FilterPredicate | undefined; + type: 'content' | 'info' | undefined; }; configInput: { - filter?: EntityPredicate | undefined; - type?: 'content' | 'summary' | 'info' | undefined; + filter?: FilterPredicate | undefined; + type?: 'content' | 'info' | undefined; }; output: | ExtensionDataRef @@ -238,7 +246,7 @@ const _default: OverridableFrontendPlugin< inputs: {}; params: { loader: () => Promise; - filter?: string | EntityPredicate | ((entity: Entity) => boolean); + filter?: string | FilterPredicate | ((entity: Entity) => boolean); type?: EntityCardType; }; }>; @@ -246,12 +254,12 @@ const _default: OverridableFrontendPlugin< kind: 'entity-card'; name: 'provided-apis'; config: { - filter: EntityPredicate | undefined; - type: 'content' | 'summary' | 'info' | undefined; + filter: FilterPredicate | undefined; + type: 'content' | 'info' | undefined; }; configInput: { - filter?: EntityPredicate | undefined; - type?: 'content' | 'summary' | 'info' | undefined; + filter?: FilterPredicate | undefined; + type?: 'content' | 'info' | undefined; }; output: | ExtensionDataRef @@ -279,7 +287,7 @@ const _default: OverridableFrontendPlugin< inputs: {}; params: { loader: () => Promise; - filter?: string | EntityPredicate | ((entity: Entity) => boolean); + filter?: string | FilterPredicate | ((entity: Entity) => boolean); type?: EntityCardType; }; }>; @@ -287,12 +295,12 @@ const _default: OverridableFrontendPlugin< kind: 'entity-card'; name: 'providing-components'; config: { - filter: EntityPredicate | undefined; - type: 'content' | 'summary' | 'info' | undefined; + filter: FilterPredicate | undefined; + type: 'content' | 'info' | undefined; }; configInput: { - filter?: EntityPredicate | undefined; - type?: 'content' | 'summary' | 'info' | undefined; + filter?: FilterPredicate | undefined; + type?: 'content' | 'info' | undefined; }; output: | ExtensionDataRef @@ -320,7 +328,7 @@ const _default: OverridableFrontendPlugin< inputs: {}; params: { loader: () => Promise; - filter?: string | EntityPredicate | ((entity: Entity) => boolean); + filter?: string | FilterPredicate | ((entity: Entity) => boolean); type?: EntityCardType; }; }>; @@ -330,25 +338,27 @@ const _default: OverridableFrontendPlugin< config: { path: string | undefined; title: string | undefined; - filter: EntityPredicate | undefined; + filter: FilterPredicate | undefined; group: string | false | undefined; + icon: string | undefined; }; configInput: { - filter?: EntityPredicate | undefined; - title?: string | undefined; path?: string | undefined; + title?: string | undefined; + filter?: FilterPredicate | undefined; group?: string | false | undefined; + icon?: string | undefined; }; output: | ExtensionDataRef - | ExtensionDataRef | ExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; } > + | ExtensionDataRef | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', @@ -370,6 +380,13 @@ const _default: OverridableFrontendPlugin< { optional: true; } + > + | ExtensionDataRef< + string | ReactElement>, + 'catalog.entity-content-icon', + { + optional: true; + } >; inputs: {}; params: { @@ -379,9 +396,10 @@ const _default: OverridableFrontendPlugin< title: string; defaultGroup?: [Error: `Use the 'group' param instead`]; group?: keyof defaultEntityContentGroups | (string & {}); + icon?: string | ReactElement; loader: () => Promise; - routeRef?: RouteRef; - filter?: string | EntityPredicate | ((entity: Entity) => boolean); + routeRef?: RouteRef_2; + filter?: string | FilterPredicate | ((entity: Entity) => boolean); }; }>; 'entity-content:api-docs/definition': OverridableExtensionDefinition<{ @@ -390,25 +408,27 @@ const _default: OverridableFrontendPlugin< config: { path: string | undefined; title: string | undefined; - filter: EntityPredicate | undefined; + filter: FilterPredicate | undefined; group: string | false | undefined; + icon: string | undefined; }; configInput: { - filter?: EntityPredicate | undefined; - title?: string | undefined; path?: string | undefined; + title?: string | undefined; + filter?: FilterPredicate | undefined; group?: string | false | undefined; + icon?: string | undefined; }; output: | ExtensionDataRef - | ExtensionDataRef | ExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; } > + | ExtensionDataRef | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', @@ -430,6 +450,13 @@ const _default: OverridableFrontendPlugin< { optional: true; } + > + | ExtensionDataRef< + string | ReactElement>, + 'catalog.entity-content-icon', + { + optional: true; + } >; inputs: {}; params: { @@ -439,21 +466,26 @@ const _default: OverridableFrontendPlugin< title: string; defaultGroup?: [Error: `Use the 'group' param instead`]; group?: keyof defaultEntityContentGroups | (string & {}); + icon?: string | ReactElement; loader: () => Promise; - routeRef?: RouteRef; - filter?: string | EntityPredicate | ((entity: Entity) => boolean); + routeRef?: RouteRef_2; + filter?: string | FilterPredicate | ((entity: Entity) => boolean); }; }>; 'nav-item:api-docs': OverridableExtensionDefinition<{ kind: 'nav-item'; name: undefined; - config: {}; - configInput: {}; + config: { + title: string | undefined; + }; + configInput: { + title?: string | undefined; + }; output: ExtensionDataRef< { title: string; icon: IconComponent; - routeRef: RouteRef; + routeRef: RouteRef_2; }, 'core.nav-item.target', {} @@ -462,46 +494,85 @@ const _default: OverridableFrontendPlugin< params: { title: string; icon: IconComponent; - routeRef: RouteRef; + routeRef: RouteRef_2; }; }>; 'page:api-docs': OverridableExtensionDefinition<{ config: { initiallySelectedFilter: 'all' | 'owned' | 'starred' | undefined; - } & { path: string | undefined; + title: string | undefined; }; configInput: { initiallySelectedFilter?: 'all' | 'owned' | 'starred' | undefined; - } & { path?: string | undefined; + title?: string | undefined; }; output: | ExtensionDataRef + | ExtensionDataRef< + RouteRef_2, + 'core.routing.ref', + { + optional: true; + } + > | ExtensionDataRef | ExtensionDataRef< - RouteRef, - 'core.routing.ref', + string, + 'core.title', + { + optional: true; + } + > + | ExtensionDataRef< + IconElement, + 'core.icon', { optional: true; } >; inputs: { - [x: string]: ExtensionInput< - ExtensionDataRef, + pages: ExtensionInput< + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef_2, + 'core.routing.ref', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >, { - singleton: boolean; - optional: boolean; + singleton: false; + optional: false; + internal: false; } >; }; kind: 'page'; name: undefined; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; - loader: () => Promise; - routeRef?: RouteRef; + title?: string; + icon?: IconElement; + loader?: () => Promise; + routeRef?: RouteRef_2; + noHeader?: boolean; }; }>; } diff --git a/plugins/api-docs/report.api.md b/plugins/api-docs/report.api.md index fb37b2b33b..5e7dd8f056 100644 --- a/plugins/api-docs/report.api.md +++ b/plugins/api-docs/report.api.md @@ -4,10 +4,11 @@ ```ts import { ApiEntity } from '@backstage/catalog-model'; -import { ApiRef } from '@backstage/core-plugin-api'; +import { ApiRef } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { CatalogTableRow } from '@backstage/plugin-catalog'; import { ComponentEntity } from '@backstage/catalog-model'; +import { EntityColumnConfig } from '@backstage/plugin-catalog-react/alpha'; import { EntityListPagination } from '@backstage/plugin-catalog-react'; import { EntityOwnerPickerProps } from '@backstage/plugin-catalog-react'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; @@ -18,6 +19,7 @@ import { RouteRef } from '@backstage/core-plugin-api'; import { TableColumn } from '@backstage/core-components'; import { TableOptions } from '@backstage/core-components'; import { TableProps } from '@backstage/core-components'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; import { UserListFilterKind } from '@backstage/plugin-catalog-react'; // @public (undocumented) @@ -61,6 +63,42 @@ const apiDocsPlugin: BackstagePlugin< export { apiDocsPlugin }; export { apiDocsPlugin as plugin }; +// @public (undocumented) +export const apiDocsTranslationRef: TranslationRef< + 'api-docs', + { + readonly 'apiDefinitionCard.error.title': 'Could not fetch the API'; + readonly 'apiDefinitionCard.rawButtonTitle': 'Raw'; + readonly 'apiDefinitionDialog.closeButtonTitle': 'Close'; + readonly 'apiDefinitionDialog.tabsAriaLabel': 'API definition options'; + readonly 'apiDefinitionDialog.rawButtonTitle': 'Raw'; + readonly 'apiDefinitionDialog.toggleButtonAriaLabel': 'Toggle API Definition Dialog'; + readonly 'defaultApiExplorerPage.title': 'APIs'; + readonly 'defaultApiExplorerPage.subtitle': '{{orgName}} API Explorer'; + readonly 'defaultApiExplorerPage.pageTitleOverride': 'APIs'; + readonly 'defaultApiExplorerPage.createButtonTitle': 'Register Existing API'; + readonly 'defaultApiExplorerPage.supportButtonTitle': 'All your APIs'; + readonly 'consumedApisCard.error.title': 'Could not load APIs'; + readonly 'consumedApisCard.title': 'Consumed APIs'; + readonly 'consumedApisCard.emptyContent.title': 'This {{entity}} does not consume any APIs.'; + readonly 'hasApisCard.error.title': 'Could not load APIs'; + readonly 'hasApisCard.title': 'APIs'; + readonly 'hasApisCard.emptyContent.title': 'This {{entity}} does not contain any APIs.'; + readonly 'providedApisCard.error.title': 'Could not load APIs'; + readonly 'providedApisCard.title': 'Provided APIs'; + readonly 'providedApisCard.emptyContent.title': 'This {{entity}} does not provide any APIs.'; + readonly 'apiEntityColumns.typeTitle': 'Type'; + readonly 'apiEntityColumns.apiDefinitionTitle': 'API Definition'; + readonly 'consumingComponentsCard.error.title': 'Could not load components'; + readonly 'consumingComponentsCard.title': 'Consumers'; + readonly 'consumingComponentsCard.emptyContent.title': 'No component consumes this API.'; + readonly 'providingComponentsCard.error.title': 'Could not load components'; + readonly 'providingComponentsCard.title': 'Providers'; + readonly 'providingComponentsCard.emptyContent.title': 'No component provides this API.'; + readonly apisCardHelpLinkTitle: 'Learn how to change this.'; + } +>; + // @public export const ApiExplorerIndexPage: ( props: DefaultApiExplorerPageProps, @@ -94,18 +132,54 @@ export type AsyncApiResolver = { }; // @public (undocumented) -export const ConsumedApisCard: (props: { - variant?: InfoCardVariants; - title?: string; +export const ConsumedApisCard: ( + props: ConsumedApisCardProps | ConsumedApisCardLegacyProps, +) => JSX_2.Element; + +// @public @deprecated +export interface ConsumedApisCardLegacyProps { + // @deprecated (undocumented) columns?: TableColumn[]; + // @deprecated (undocumented) tableOptions?: TableOptions; -}) => JSX_2.Element; + // (undocumented) + title?: string; + // @deprecated (undocumented) + variant?: InfoCardVariants; +} // @public (undocumented) -export const ConsumingComponentsCard: (props: { - variant?: InfoCardVariants; +export interface ConsumedApisCardProps { + // (undocumented) + columnConfig?: EntityColumnConfig[]; + // (undocumented) + title?: string; +} + +// @public (undocumented) +export const ConsumingComponentsCard: ( + props: ConsumingComponentsCardProps | ConsumingComponentsCardLegacyProps, +) => JSX_2.Element; + +// @public @deprecated +export interface ConsumingComponentsCardLegacyProps { + // @deprecated (undocumented) columns?: TableColumn[]; -}) => JSX_2.Element; + // @deprecated (undocumented) + tableOptions?: TableOptions; + // (undocumented) + title?: string; + // @deprecated (undocumented) + variant?: InfoCardVariants; +} + +// @public (undocumented) +export interface ConsumingComponentsCardProps { + // (undocumented) + columnConfig?: EntityColumnConfig[]; + // (undocumented) + title?: string; +} // @public export const DefaultApiExplorerPage: ( @@ -128,40 +202,29 @@ export function defaultDefinitionWidgets(): ApiDefinitionWidget[]; export const EntityApiDefinitionCard: () => JSX_2.Element; // @public (undocumented) -export const EntityConsumedApisCard: (props: { - variant?: InfoCardVariants; - title?: string; - columns?: TableColumn[]; - tableOptions?: TableOptions; -}) => JSX_2.Element; +export const EntityConsumedApisCard: ( + props: ConsumedApisCardProps | ConsumedApisCardLegacyProps, +) => JSX_2.Element; // @public (undocumented) -export const EntityConsumingComponentsCard: (props: { - variant?: InfoCardVariants; - columns?: TableColumn[]; -}) => JSX_2.Element; +export const EntityConsumingComponentsCard: ( + props: ConsumingComponentsCardProps | ConsumingComponentsCardLegacyProps, +) => JSX_2.Element; // @public (undocumented) -export const EntityHasApisCard: (props: { - variant?: InfoCardVariants; - title?: string; - columns?: TableColumn[]; - tableOptions?: TableOptions; -}) => JSX_2.Element; +export const EntityHasApisCard: ( + props: HasApisCardProps | HasApisCardLegacyProps, +) => JSX_2.Element; // @public (undocumented) -export const EntityProvidedApisCard: (props: { - variant?: InfoCardVariants; - title?: string; - columns?: TableColumn[]; - tableOptions?: TableOptions; -}) => JSX_2.Element; +export const EntityProvidedApisCard: ( + props: ProvidedApisCardProps | ProvidedApisCardLegacyProps, +) => JSX_2.Element; // @public (undocumented) -export const EntityProvidingComponentsCard: (props: { - variant?: InfoCardVariants; - columns?: TableColumn[]; -}) => JSX_2.Element; +export const EntityProvidingComponentsCard: ( + props: ProvidingComponentsCardProps | ProvidingComponentsCardLegacyProps, +) => JSX_2.Element; // @public (undocumented) export const GraphQlDefinitionWidget: ( @@ -174,12 +237,29 @@ export type GraphQlDefinitionWidgetProps = { }; // @public (undocumented) -export const HasApisCard: (props: { - variant?: InfoCardVariants; - title?: string; +export const HasApisCard: ( + props: HasApisCardProps | HasApisCardLegacyProps, +) => JSX_2.Element; + +// @public @deprecated +export interface HasApisCardLegacyProps { + // @deprecated (undocumented) columns?: TableColumn[]; + // @deprecated (undocumented) tableOptions?: TableOptions; -}) => JSX_2.Element; + // (undocumented) + title?: string; + // @deprecated (undocumented) + variant?: InfoCardVariants; +} + +// @public (undocumented) +export interface HasApisCardProps { + // (undocumented) + columnConfig?: EntityColumnConfig[]; + // (undocumented) + title?: string; +} // @public (undocumented) export const OpenApiDefinitionWidget: ( @@ -205,18 +285,54 @@ export type PlainApiDefinitionWidgetProps = { }; // @public (undocumented) -export const ProvidedApisCard: (props: { - variant?: InfoCardVariants; - title?: string; +export const ProvidedApisCard: ( + props: ProvidedApisCardProps | ProvidedApisCardLegacyProps, +) => JSX_2.Element; + +// @public @deprecated +export interface ProvidedApisCardLegacyProps { + // @deprecated (undocumented) columns?: TableColumn[]; + // @deprecated (undocumented) tableOptions?: TableOptions; -}) => JSX_2.Element; + // (undocumented) + title?: string; + // @deprecated (undocumented) + variant?: InfoCardVariants; +} // @public (undocumented) -export const ProvidingComponentsCard: (props: { - variant?: InfoCardVariants; +export interface ProvidedApisCardProps { + // (undocumented) + columnConfig?: EntityColumnConfig[]; + // (undocumented) + title?: string; +} + +// @public (undocumented) +export const ProvidingComponentsCard: ( + props: ProvidingComponentsCardProps | ProvidingComponentsCardLegacyProps, +) => JSX_2.Element; + +// @public @deprecated +export interface ProvidingComponentsCardLegacyProps { + // @deprecated (undocumented) columns?: TableColumn[]; -}) => JSX_2.Element; + // @deprecated (undocumented) + tableOptions?: TableOptions; + // (undocumented) + title?: string; + // @deprecated (undocumented) + variant?: InfoCardVariants; +} + +// @public (undocumented) +export interface ProvidingComponentsCardProps { + // (undocumented) + columnConfig?: EntityColumnConfig[]; + // (undocumented) + title?: string; +} // @public (undocumented) export const TrpcApiDefinitionWidget: ( diff --git a/plugins/api-docs/src/alpha.test.tsx b/plugins/api-docs/src/alpha.test.tsx new file mode 100644 index 0000000000..ee58981515 --- /dev/null +++ b/plugins/api-docs/src/alpha.test.tsx @@ -0,0 +1,180 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { screen } from '@testing-library/react'; +import { renderTestApp } from '@backstage/frontend-test-utils'; +import { ApiEntity, ComponentEntity } from '@backstage/catalog-model'; +import { createTestEntityPage } from '@backstage/plugin-catalog-react/testUtils'; +import apiDocsPlugin from './alpha'; + +const apiDocsDefinitionEntityCard = apiDocsPlugin.getExtension( + 'entity-card:api-docs/definition', +); +const apiDocsDefinitionEntityContent = apiDocsPlugin.getExtension( + 'entity-content:api-docs/definition', +); +const apiDocsConfigApi = apiDocsPlugin.getExtension('api:api-docs/config'); + +describe('api-docs plugin entity extensions', () => { + describe('apiDocsDefinitionEntityCard', () => { + it('should render for API entities', async () => { + const entity: ApiEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'API', + metadata: { + name: 'my-api', + title: 'My API', + }, + spec: { + type: 'openapi', + lifecycle: 'production', + owner: 'team-a', + definition: + 'openapi: 3.0.0\ninfo:\n title: My API\n version: 1.0.0', + }, + }; + + renderTestApp({ + extensions: [ + createTestEntityPage({ entity }), + apiDocsConfigApi, + apiDocsDefinitionEntityCard, + ], + }); + + expect( + await screen.findByText('My API', {}, { timeout: 10000 }), + ).toBeInTheDocument(); + }); + + it('should not render for non-API entities', async () => { + const entity: ComponentEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'my-component', + }, + spec: { + type: 'service', + lifecycle: 'production', + owner: 'team-a', + }, + }; + + renderTestApp({ + extensions: [ + createTestEntityPage({ entity }), + apiDocsConfigApi, + apiDocsDefinitionEntityCard, + ], + }); + + expect( + await screen.findByTestId('empty-entity-page'), + ).toBeInTheDocument(); + }); + + it('should display the API definition content', async () => { + const entity: ApiEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'API', + metadata: { + name: 'pet-api', + }, + spec: { + type: 'openapi', + lifecycle: 'production', + owner: 'team-a', + definition: JSON.stringify({ + openapi: '3.0.0', + info: { title: 'Pet Store API', version: '1.0.0' }, + }), + }, + }; + + renderTestApp({ + extensions: [ + createTestEntityPage({ entity }), + apiDocsConfigApi, + apiDocsDefinitionEntityCard, + ], + }); + + expect( + await screen.findByText('pet-api', {}, { timeout: 10000 }), + ).toBeInTheDocument(); + }); + }); + + describe('apiDocsDefinitionEntityContent', () => { + it('should render for API entities', async () => { + const entity: ApiEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'API', + metadata: { + name: 'content-api', + title: 'Content API', + }, + spec: { + type: 'graphql', + lifecycle: 'production', + owner: 'team-b', + definition: 'type Query { hello: String }', + }, + }; + + renderTestApp({ + extensions: [ + createTestEntityPage({ entity }), + apiDocsConfigApi, + apiDocsDefinitionEntityContent, + ], + }); + + expect( + await screen.findByText('Content API', {}, { timeout: 10000 }), + ).toBeInTheDocument(); + }); + + it('should not render for non-API entities', async () => { + const entity: ComponentEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'my-service', + }, + spec: { + type: 'service', + lifecycle: 'production', + owner: 'team-a', + }, + }; + + renderTestApp({ + extensions: [ + createTestEntityPage({ entity }), + apiDocsConfigApi, + apiDocsDefinitionEntityContent, + ], + }); + + // Content should not render for Components + expect( + await screen.findByTestId('empty-entity-page'), + ).toBeInTheDocument(); + }); + }); +}); diff --git a/plugins/api-docs/src/alpha.tsx b/plugins/api-docs/src/alpha.tsx index 7693b97259..af4190bd13 100644 --- a/plugins/api-docs/src/alpha.tsx +++ b/plugins/api-docs/src/alpha.tsx @@ -23,11 +23,6 @@ import { createFrontendPlugin, } from '@backstage/frontend-plugin-api'; -import { - compatWrapper, - convertLegacyRouteRef, -} from '@backstage/core-compat-api'; - import { ApiEntity, parseEntityRef, @@ -47,8 +42,8 @@ import { const apiDocsNavItem = NavItemBlueprint.make({ params: { title: 'APIs', - routeRef: convertLegacyRouteRef(rootRoute), - icon: () => compatWrapper(), + routeRef: rootRoute, + icon: () => , }, }); @@ -80,13 +75,13 @@ const apiDocsExplorerPage = PageBlueprint.makeWithOverrides({ factory(originalFactory, { config }) { return originalFactory({ path: '/api-docs', - routeRef: convertLegacyRouteRef(rootRoute), + routeRef: rootRoute, loader: () => - import('./components/ApiExplorerPage').then(m => - compatWrapper( - ( + , + /> ), ), }); @@ -109,21 +104,18 @@ const apiDocsHasApisEntityCard = EntityCardBlueprint.make({ )!! ); }, - loader: () => - import('./components/ApisCards').then(m => - compatWrapper(), - ), + loader: () => import('./components/ApisCards').then(m => ), }, }); const apiDocsDefinitionEntityCard = EntityCardBlueprint.make({ name: 'definition', params: { - filter: 'kind:api', + filter: { kind: 'api' }, loader: () => - import('./components/ApiDefinitionCard').then(m => - compatWrapper(), - ), + import('./components/ApiDefinitionCard').then(m => ( + + )), }, }); @@ -133,11 +125,9 @@ const apiDocsConsumedApisEntityCard = EntityCardBlueprint.make({ // Omitting configSchema for now // We are skipping variants and columns are too complex to map to zod // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 - filter: 'kind:component', + filter: { kind: 'component' }, loader: () => - import('./components/ApisCards').then(m => - compatWrapper(), - ), + import('./components/ApisCards').then(m => ), }, }); @@ -147,11 +137,9 @@ const apiDocsProvidedApisEntityCard = EntityCardBlueprint.make({ // Omitting configSchema for now // We are skipping variants and columns are too complex to map to zod // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 - filter: 'kind:component', + filter: { kind: 'component' }, loader: () => - import('./components/ApisCards').then(m => - compatWrapper(), - ), + import('./components/ApisCards').then(m => ), }, }); @@ -161,11 +149,11 @@ const apiDocsConsumingComponentsEntityCard = EntityCardBlueprint.make({ // Omitting configSchema for now // We are skipping variants // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 - filter: 'kind:api', + filter: { kind: 'api' }, loader: () => - import('./components/ComponentsCards').then(m => - compatWrapper(), - ), + import('./components/ComponentsCards').then(m => ( + + )), }, }); @@ -175,11 +163,11 @@ const apiDocsProvidingComponentsEntityCard = EntityCardBlueprint.make({ // Omitting configSchema for now // We are skipping variants // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 - filter: 'kind:api', + filter: { kind: 'api' }, loader: () => - import('./components/ComponentsCards').then(m => - compatWrapper(), - ), + import('./components/ComponentsCards').then(m => ( + + )), }, }); @@ -188,17 +176,16 @@ const apiDocsDefinitionEntityContent = EntityContentBlueprint.make({ params: { path: '/definition', title: 'Definition', - filter: 'kind:api', + group: 'documentation', + filter: { kind: 'api' }, loader: async () => - import('./components/ApiDefinitionCard').then(m => - compatWrapper( - - - - - , - ), - ), + import('./components/ApiDefinitionCard').then(m => ( + + + + + + )), }, }); @@ -207,31 +194,32 @@ const apiDocsApisEntityContent = EntityContentBlueprint.make({ params: { path: '/apis', title: 'APIs', - filter: 'kind:component', + group: 'development', + filter: { kind: 'component' }, loader: async () => - import('./components/ApisCards').then(m => - compatWrapper( - - - - - - - - , - ), - ), + import('./components/ApisCards').then(m => ( + + + + + + + + + )), }, }); export default createFrontendPlugin({ pluginId: 'api-docs', + title: 'APIs', + icon: , info: { packageJson: () => import('../package.json') }, routes: { - root: convertLegacyRouteRef(rootRoute), + root: rootRoute, }, externalRoutes: { - registerApi: convertLegacyRouteRef(registerComponentRouteRef), + registerApi: registerComponentRouteRef, }, extensions: [ apiDocsNavItem, @@ -248,4 +236,10 @@ export default createFrontendPlugin({ ], }); -export { apiDocsTranslationRef } from './translation'; +import { apiDocsTranslationRef as _apiDocsTranslationRef } from './translation'; + +/** + * @alpha + * @deprecated Import from `@backstage/plugin-api-docs` instead. + */ +export const apiDocsTranslationRef = _apiDocsTranslationRef; diff --git a/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.tsx b/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.tsx index e033f93b3c..acc15dccca 100644 --- a/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.tsx +++ b/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.tsx @@ -15,34 +15,39 @@ */ import { ApiEntity } from '@backstage/catalog-model'; -import { useEntity } from '@backstage/plugin-catalog-react'; -import Alert from '@material-ui/lab/Alert'; -import { apiDocsConfigRef } from '../../config'; -import { PlainApiDefinitionWidget } from '../PlainApiDefinitionWidget'; - +import { + useEntity, + useEntityPresentation, +} from '@backstage/plugin-catalog-react'; import { CardTab, TabbedCard } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import Alert from '@material-ui/lab/Alert'; +import { apiDocsConfigRef } from '../../config'; +import { apiDocsTranslationRef } from '../../translation'; +import { PlainApiDefinitionWidget } from '../PlainApiDefinitionWidget'; /** @public */ export const ApiDefinitionCard = () => { const { entity } = useEntity(); const config = useApi(apiDocsConfigRef); const { getApiDefinitionWidget } = config; + const { t } = useTranslationRef(apiDocsTranslationRef); + const { primaryTitle } = useEntityPresentation(entity); if (!entity) { - return Could not fetch the API; + return {t('apiDefinitionCard.error.title')}; } const definitionWidget = getApiDefinitionWidget(entity); - const entityTitle = entity.metadata.title ?? entity.metadata.name; if (definitionWidget) { return ( - + {definitionWidget.component(entity.spec.definition)} - + { return ( diff --git a/plugins/api-docs/src/components/ApiDefinitionDialog/ApiDefinitionDialog.tsx b/plugins/api-docs/src/components/ApiDefinitionDialog/ApiDefinitionDialog.tsx index 4a7a249b3c..835b97aabf 100644 --- a/plugins/api-docs/src/components/ApiDefinitionDialog/ApiDefinitionDialog.tsx +++ b/plugins/api-docs/src/components/ApiDefinitionDialog/ApiDefinitionDialog.tsx @@ -16,6 +16,7 @@ import { ApiEntity } from '@backstage/catalog-model'; import { useApi } from '@backstage/core-plugin-api'; +import { EntityDisplayName } from '@backstage/plugin-catalog-react'; import Box from '@material-ui/core/Box'; import Button from '@material-ui/core/Button'; import Dialog from '@material-ui/core/Dialog'; @@ -133,10 +134,11 @@ export function ApiDefinitionDialog(props: { > - API - {definitionWidget?.title ?? 'Raw'} + API -{' '} + {definitionWidget?.title ?? t('apiDefinitionDialog.rawButtonTitle')} - {entity.metadata.title ?? entity.metadata.name} + @@ -151,7 +153,10 @@ export function ApiDefinitionDialog(props: { {definitionWidget ? ( ) : null} - + {definitionWidget ? ( diff --git a/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.test.tsx b/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.test.tsx index 2825c29528..3d2a126f9b 100644 --- a/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.test.tsx +++ b/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.test.tsx @@ -58,7 +58,12 @@ describe('DefaultApiExplorerPage', () => { ], }), getLocationByRef: () => - Promise.resolve({ id: 'id', type: 'url', target: 'url' }), + Promise.resolve({ + id: 'id', + type: 'url', + target: 'url', + entityRef: 'location:default/generated-id', + }), getEntitiesByRefs: () => Promise.resolve({ items: [] }), getEntityFacets: async () => ({ facets: { 'relations.ownedBy': [] }, diff --git a/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.tsx b/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.tsx index 625fdbe31c..20a8a1a7d0 100644 --- a/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.tsx +++ b/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.tsx @@ -24,6 +24,7 @@ import { TableProps, } from '@backstage/core-components'; import { configApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api'; +import { HeaderPage } from '@backstage/ui'; import { CatalogTable, CatalogTableRow } from '@backstage/plugin-catalog'; import { EntityKindPicker, @@ -67,6 +68,42 @@ export type DefaultApiExplorerPageProps = { pagination?: EntityListPagination; }; +type ApiExplorerPageContentProps = { + initiallySelectedFilter: UserListFilterKind; + columns?: TableColumn[]; + actions?: TableProps['actions']; + ownerPickerMode?: EntityOwnerPickerProps['mode']; + pagination?: EntityListPagination; +}; + +function ApiExplorerPageContent(props: ApiExplorerPageContentProps) { + const { + initiallySelectedFilter, + columns, + actions, + ownerPickerMode, + pagination, + } = props; + + return ( + + + + + + + + + + ); +} + /** * DefaultApiExplorerPage * @public @@ -89,6 +126,19 @@ export const DefaultApiExplorerPage = (props: DefaultApiExplorerPageProps) => { const { allowed } = usePermission({ permission: catalogEntityCreatePermission, }); + const headerActions = ( + <> + {allowed && ( + + )} + + {t('defaultApiExplorerPage.supportButtonTitle')} + + + ); return ( { pageTitleOverride={t('defaultApiExplorerPage.pageTitleOverride')} > - - {allowed && ( - - )} - - {t('defaultApiExplorerPage.supportButtonTitle')} - - - - - - - - - - - + {headerActions} + ); }; + +export const NfsApiExplorerPage = (props: DefaultApiExplorerPageProps) => { + const { + initiallySelectedFilter = 'all', + columns, + actions, + ownerPickerMode, + pagination, + } = props; + + const configApi = useApi(configApiRef); + const { t } = useTranslationRef(apiDocsTranslationRef); + const generatedSubtitle = t('defaultApiExplorerPage.subtitle', { + orgName: configApi.getOptionalString('organization.name') ?? 'Backstage', + }); + const registerComponentLink = useRouteRef(registerComponentRouteRef); + const { allowed } = usePermission({ + permission: catalogEntityCreatePermission, + }); + const headerActions = ( + <> + {allowed && ( + + )} + + {t('defaultApiExplorerPage.supportButtonTitle')} + + + ); + + return ( + <> + + + + + + ); +}; diff --git a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx index e8ed6bedb5..b62616e98c 100644 --- a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx +++ b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx @@ -60,7 +60,7 @@ describe('', () => { relations: [], }; - const { getByText, getByRole, container } = await renderInTestApp( + const { getByText, getByRole } = await renderInTestApp( @@ -76,16 +76,11 @@ describe('', () => { expect(getByText(/Consumed APIs/i)).toBeInTheDocument(); expect(getByText(/does not consume any APIs/i)).toBeInTheDocument(); - // Also render external link icon const externalLink = getByRole('link'); expect(externalLink).toHaveAttribute( 'href', 'https://backstage.io/docs/features/software-catalog/descriptor-format#specconsumesapis-optional', ); - const externalLinkIcon: HTMLElement | null = container.querySelector( - 'svg[class*="externalLink"]', - ); - expect(externalLink).toContainElement(externalLinkIcon); }); it('shows consumed APIs', async () => { diff --git a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx index 5dc6e4a473..2e289a7009 100644 --- a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx +++ b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx @@ -21,7 +21,11 @@ import { useEntity, useRelatedEntities, } from '@backstage/plugin-catalog-react'; -import { getApiEntityColumns } from './presets'; +import { + EntityRelationCard, + EntityColumnConfig, +} from '@backstage/plugin-catalog-react/alpha'; +import { getApiEntityColumns, getApiEntityColumnConfig } from './presets'; import { CodeSnippet, InfoCard, @@ -35,15 +39,34 @@ import { import { useTranslationRef } from '@backstage/frontend-plugin-api'; import { apiDocsTranslationRef } from '../../translation'; +/** @public */ +export interface ConsumedApisCardProps { + title?: string; + columnConfig?: EntityColumnConfig[]; +} + /** + * Props for the legacy MUI-based rendering. + * @deprecated Use {@link ConsumedApisCardProps} instead. * @public */ -export const ConsumedApisCard = (props: { - variant?: InfoCardVariants; +export interface ConsumedApisCardLegacyProps { title?: string; + /** @deprecated Use `columnConfig` instead. */ + variant?: InfoCardVariants; + /** @deprecated Use `columnConfig` instead. */ columns?: TableColumn[]; + /** @deprecated Use `columnConfig` instead. */ tableOptions?: TableOptions; -}) => { +} + +function isLegacyProps( + props: ConsumedApisCardProps | ConsumedApisCardLegacyProps, +): props is ConsumedApisCardLegacyProps { + return 'variant' in props || 'columns' in props || 'tableOptions' in props; +} + +function ConsumedApisCardLegacy(props: ConsumedApisCardLegacyProps) { const { t } = useTranslationRef(apiDocsTranslationRef); const { variant = 'gridItem', @@ -102,4 +125,38 @@ export const ConsumedApisCard = (props: { entities={entities as ApiEntity[]} /> ); +} + +/** + * @public + */ +export const ConsumedApisCard = ( + props: ConsumedApisCardProps | ConsumedApisCardLegacyProps, +) => { + const { t } = useTranslationRef(apiDocsTranslationRef); + const { entity } = useEntity(); + + if (isLegacyProps(props)) { + return ; + } + + const { + title = t('consumedApisCard.title'), + columnConfig = getApiEntityColumnConfig(t), + } = props; + + return ( + + ); }; diff --git a/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx b/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx index f0777e87a8..66f6d0b4f4 100644 --- a/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx +++ b/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx @@ -21,8 +21,12 @@ import { useEntity, useRelatedEntities, } from '@backstage/plugin-catalog-react'; +import { + EntityRelationCard, + EntityColumnConfig, +} from '@backstage/plugin-catalog-react/alpha'; import { useMemo } from 'react'; -import { createSpecApiTypeColumn } from './presets'; +import { createSpecApiTypeColumn, getHasApisColumnConfig } from './presets'; import { CodeSnippet, InfoCard, @@ -36,15 +40,34 @@ import { import { useTranslationRef } from '@backstage/frontend-plugin-api'; import { apiDocsTranslationRef } from '../../translation'; +/** @public */ +export interface HasApisCardProps { + title?: string; + columnConfig?: EntityColumnConfig[]; +} + /** + * Props for the legacy MUI-based rendering. + * @deprecated Use {@link HasApisCardProps} instead. * @public */ -export const HasApisCard = (props: { - variant?: InfoCardVariants; +export interface HasApisCardLegacyProps { title?: string; + /** @deprecated Use `columnConfig` instead. */ + variant?: InfoCardVariants; + /** @deprecated Use `columnConfig` instead. */ columns?: TableColumn[]; + /** @deprecated Use `columnConfig` instead. */ tableOptions?: TableOptions; -}) => { +} + +function isLegacyProps( + props: HasApisCardProps | HasApisCardLegacyProps, +): props is HasApisCardLegacyProps { + return 'variant' in props || 'columns' in props || 'tableOptions' in props; +} + +function HasApisCardLegacy(props: HasApisCardLegacyProps) { const { t } = useTranslationRef(apiDocsTranslationRef); const presetColumns: TableColumn[] = useMemo(() => { return [ @@ -110,4 +133,39 @@ export const HasApisCard = (props: { entities={entities as ApiEntity[]} /> ); +} + +/** + * @public + */ +export const HasApisCard = ( + props: HasApisCardProps | HasApisCardLegacyProps, +) => { + const { t } = useTranslationRef(apiDocsTranslationRef); + const { entity } = useEntity(); + + if (isLegacyProps(props)) { + return ; + } + + const { + title = t('hasApisCard.title'), + columnConfig = getHasApisColumnConfig(t), + } = props; + + return ( + + ); }; diff --git a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx index df478566e9..b49da7a9a1 100644 --- a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx +++ b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx @@ -60,7 +60,7 @@ describe('', () => { relations: [], }; - const { getByText, getByRole, container } = await renderInTestApp( + const { getByText, getByRole } = await renderInTestApp( @@ -77,16 +77,11 @@ describe('', () => { expect(getByText(/does not provide any APIs/i)).toBeInTheDocument(); expect(getByText(/Learn how to change this/)).toBeInTheDocument(); - // Also render external link icon const externalLink = getByRole('link'); expect(externalLink).toHaveAttribute( 'href', 'https://backstage.io/docs/features/software-catalog/descriptor-format#specprovidesapis-optional', ); - const externalLinkIcon: HTMLElement | null = container.querySelector( - 'svg[class*="externalLink"]', - ); - expect(externalLink).toContainElement(externalLinkIcon); }); it('shows consumed APIs', async () => { diff --git a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx index 784bfe2a2a..f99c10e176 100644 --- a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx +++ b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx @@ -21,7 +21,11 @@ import { useEntity, useRelatedEntities, } from '@backstage/plugin-catalog-react'; -import { getApiEntityColumns } from './presets'; +import { + EntityRelationCard, + EntityColumnConfig, +} from '@backstage/plugin-catalog-react/alpha'; +import { getApiEntityColumns, getApiEntityColumnConfig } from './presets'; import { CodeSnippet, InfoCard, @@ -35,15 +39,34 @@ import { import { useTranslationRef } from '@backstage/frontend-plugin-api'; import { apiDocsTranslationRef } from '../../translation'; +/** @public */ +export interface ProvidedApisCardProps { + title?: string; + columnConfig?: EntityColumnConfig[]; +} + /** + * Props for the legacy MUI-based rendering. + * @deprecated Use {@link ProvidedApisCardProps} instead. * @public */ -export const ProvidedApisCard = (props: { - variant?: InfoCardVariants; +export interface ProvidedApisCardLegacyProps { title?: string; + /** @deprecated Use `columnConfig` instead. */ + variant?: InfoCardVariants; + /** @deprecated Use `columnConfig` instead. */ columns?: TableColumn[]; + /** @deprecated Use `columnConfig` instead. */ tableOptions?: TableOptions; -}) => { +} + +function isLegacyProps( + props: ProvidedApisCardProps | ProvidedApisCardLegacyProps, +): props is ProvidedApisCardLegacyProps { + return 'variant' in props || 'columns' in props || 'tableOptions' in props; +} + +function ProvidedApisCardLegacy(props: ProvidedApisCardLegacyProps) { const { t } = useTranslationRef(apiDocsTranslationRef); const { variant = 'gridItem', @@ -102,4 +125,38 @@ export const ProvidedApisCard = (props: { entities={entities as ApiEntity[]} /> ); +} + +/** + * @public + */ +export const ProvidedApisCard = ( + props: ProvidedApisCardProps | ProvidedApisCardLegacyProps, +) => { + const { t } = useTranslationRef(apiDocsTranslationRef); + const { entity } = useEntity(); + + if (isLegacyProps(props)) { + return ; + } + + const { + title = t('providedApisCard.title'), + columnConfig = getApiEntityColumnConfig(t), + } = props; + + return ( + + ); }; diff --git a/plugins/api-docs/src/components/ApisCards/index.ts b/plugins/api-docs/src/components/ApisCards/index.ts index 4a74549755..d78454bf28 100644 --- a/plugins/api-docs/src/components/ApisCards/index.ts +++ b/plugins/api-docs/src/components/ApisCards/index.ts @@ -15,5 +15,14 @@ */ export { ConsumedApisCard } from './ConsumedApisCard'; +export type { + ConsumedApisCardLegacyProps, + ConsumedApisCardProps, +} from './ConsumedApisCard'; export { HasApisCard } from './HasApisCard'; +export type { HasApisCardLegacyProps, HasApisCardProps } from './HasApisCard'; export { ProvidedApisCard } from './ProvidedApisCard'; +export type { + ProvidedApisCardLegacyProps, + ProvidedApisCardProps, +} from './ProvidedApisCard'; diff --git a/plugins/api-docs/src/components/ApisCards/presets.tsx b/plugins/api-docs/src/components/ApisCards/presets.tsx index 8341ce2c2b..3489797eb6 100644 --- a/plugins/api-docs/src/components/ApisCards/presets.tsx +++ b/plugins/api-docs/src/components/ApisCards/presets.tsx @@ -17,8 +17,12 @@ import { ApiEntity } from '@backstage/catalog-model'; import { TableColumn } from '@backstage/core-components'; import { EntityTable } from '@backstage/plugin-catalog-react'; -import ExtensionIcon from '@material-ui/icons/Extension'; -import ToggleButton from '@material-ui/lab/ToggleButton'; +import { + EntityColumnConfig, + entityDataTableColumns, +} from '@backstage/plugin-catalog-react/alpha'; +import { ButtonIcon, Cell } from '@backstage/ui'; +import { RiPuzzleLine } from '@remixicon/react'; import { useState } from 'react'; import { ApiTypeTitle } from '../ApiDefinitionCard'; import { ApiDefinitionDialog } from '../ApiDefinitionDialog'; @@ -28,6 +32,7 @@ import { } from '@backstage/core-plugin-api/alpha'; import { apiDocsTranslationRef } from '../../translation'; +/** @deprecated Use `getApiEntityColumnConfig` instead. */ export function createSpecApiTypeColumn( t: TranslationFunction, ): TableColumn { @@ -43,13 +48,13 @@ const ApiDefinitionButton = ({ apiEntity }: { apiEntity: ApiEntity }) => { const { t } = useTranslationRef(apiDocsTranslationRef); return ( <> - setDialogOpen(!dialogOpen)} - value={dialogOpen} - > - - + onPress={() => setDialogOpen(!dialogOpen)} + variant="tertiary" + size="small" + icon={} + /> , ): TableColumn[] => { @@ -81,3 +87,62 @@ export const getApiEntityColumns = ( createApiDefinitionColumn(t), ]; }; + +// Column config presets + +function createSpecApiTypeColumnConfig( + t: TranslationFunction, +): EntityColumnConfig { + return { + id: 'apiType', + label: t('apiEntityColumns.typeTitle'), + isSortable: true, + cell: entity => ( + + + + ), + sortValue: entity => + (entity.spec as Record | undefined)?.type ?? '', + }; +} + +function createApiDefinitionColumnConfig( + t: TranslationFunction, +): EntityColumnConfig { + return { + id: 'apiDefinition', + label: t('apiEntityColumns.apiDefinitionTitle'), + cell: entity => ( + + + + ), + }; +} + +export function getApiEntityColumnConfig( + t: TranslationFunction, +): EntityColumnConfig[] { + return [ + entityDataTableColumns.createEntityRefColumn({ defaultKind: 'API' }), + entityDataTableColumns.createSystemColumn(), + entityDataTableColumns.createOwnerColumn(), + createSpecApiTypeColumnConfig(t), + entityDataTableColumns.createSpecLifecycleColumn(), + entityDataTableColumns.createMetadataDescriptionColumn(), + createApiDefinitionColumnConfig(t), + ]; +} + +export function getHasApisColumnConfig( + t: TranslationFunction, +): EntityColumnConfig[] { + return [ + entityDataTableColumns.createEntityRefColumn({ defaultKind: 'API' }), + entityDataTableColumns.createOwnerColumn(), + createSpecApiTypeColumnConfig(t), + entityDataTableColumns.createSpecLifecycleColumn(), + entityDataTableColumns.createMetadataDescriptionColumn(), + ]; +} diff --git a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx index 12bc930c37..1006c069f6 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx @@ -24,6 +24,11 @@ import { useEntity, useRelatedEntities, } from '@backstage/plugin-catalog-react'; +import { + EntityRelationCard, + EntityColumnConfig, + entityColumnPresets, +} from '@backstage/plugin-catalog-react/alpha'; import { CodeSnippet, InfoCard, @@ -31,29 +36,57 @@ import { Link, Progress, TableColumn, + TableOptions, WarningPanel, } from '@backstage/core-components'; import { useTranslationRef } from '@backstage/frontend-plugin-api'; import { apiDocsTranslationRef } from '../../translation'; +/** @public */ +export interface ConsumingComponentsCardProps { + title?: string; + columnConfig?: EntityColumnConfig[]; +} + /** + * Props for the legacy MUI-based rendering. + * @deprecated Use {@link ConsumingComponentsCardProps} instead. * @public */ -export const ConsumingComponentsCard = (props: { +export interface ConsumingComponentsCardLegacyProps { + title?: string; + /** @deprecated Use `columnConfig` instead. */ variant?: InfoCardVariants; + /** @deprecated Use `columnConfig` instead. */ columns?: TableColumn[]; -}) => { - const { variant = 'gridItem', columns = EntityTable.componentEntityColumns } = - props; + /** @deprecated Use `columnConfig` instead. */ + tableOptions?: TableOptions; +} + +function isLegacyProps( + props: ConsumingComponentsCardProps | ConsumingComponentsCardLegacyProps, +): props is ConsumingComponentsCardLegacyProps { + return 'variant' in props || 'columns' in props || 'tableOptions' in props; +} + +function ConsumingComponentsCardLegacy( + props: ConsumingComponentsCardLegacyProps, +) { + const { t } = useTranslationRef(apiDocsTranslationRef); + const { + variant = 'gridItem', + title = t('consumingComponentsCard.title'), + columns = EntityTable.componentEntityColumns, + tableOptions = {}, + } = props; const { entity } = useEntity(); const { entities, loading, error } = useRelatedEntities(entity, { type: RELATION_API_CONSUMED_BY, }); - const { t } = useTranslationRef(apiDocsTranslationRef); if (loading) { return ( - + ); @@ -61,7 +94,7 @@ export const ConsumingComponentsCard = (props: { if (error || !entities) { return ( - + @@ -88,7 +121,39 @@ export const ConsumingComponentsCard = (props: { } columns={columns} + tableOptions={tableOptions} entities={entities as ComponentEntity[]} /> ); +} + +/** + * @public + */ +export const ConsumingComponentsCard = ( + props: ConsumingComponentsCardProps | ConsumingComponentsCardLegacyProps, +) => { + const { t } = useTranslationRef(apiDocsTranslationRef); + + if (isLegacyProps(props)) { + return ; + } + + const { + title = t('consumingComponentsCard.title'), + columnConfig = entityColumnPresets.component.columns, + } = props; + + return ( + + ); }; diff --git a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx index c271c33380..e1be0c777d 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx @@ -24,6 +24,11 @@ import { useEntity, useRelatedEntities, } from '@backstage/plugin-catalog-react'; +import { + EntityRelationCard, + EntityColumnConfig, + entityColumnPresets, +} from '@backstage/plugin-catalog-react/alpha'; import { CodeSnippet, InfoCard, @@ -31,27 +36,57 @@ import { Link, Progress, TableColumn, + TableOptions, WarningPanel, } from '@backstage/core-components'; import { useTranslationRef } from '@backstage/frontend-plugin-api'; import { apiDocsTranslationRef } from '../../translation'; /** @public */ -export const ProvidingComponentsCard = (props: { +export interface ProvidingComponentsCardProps { + title?: string; + columnConfig?: EntityColumnConfig[]; +} + +/** + * Props for the legacy MUI-based rendering. + * @deprecated Use {@link ProvidingComponentsCardProps} instead. + * @public + */ +export interface ProvidingComponentsCardLegacyProps { + title?: string; + /** @deprecated Use `columnConfig` instead. */ variant?: InfoCardVariants; + /** @deprecated Use `columnConfig` instead. */ columns?: TableColumn[]; -}) => { - const { variant = 'gridItem', columns = EntityTable.componentEntityColumns } = - props; + /** @deprecated Use `columnConfig` instead. */ + tableOptions?: TableOptions; +} + +function isLegacyProps( + props: ProvidingComponentsCardProps | ProvidingComponentsCardLegacyProps, +): props is ProvidingComponentsCardLegacyProps { + return 'variant' in props || 'columns' in props || 'tableOptions' in props; +} + +function ProvidingComponentsCardLegacy( + props: ProvidingComponentsCardLegacyProps, +) { + const { t } = useTranslationRef(apiDocsTranslationRef); + const { + variant = 'gridItem', + title = t('providingComponentsCard.title'), + columns = EntityTable.componentEntityColumns, + tableOptions = {}, + } = props; const { entity } = useEntity(); const { entities, loading, error } = useRelatedEntities(entity, { type: RELATION_API_PROVIDED_BY, }); - const { t } = useTranslationRef(apiDocsTranslationRef); if (loading) { return ( - + ); @@ -59,7 +94,7 @@ export const ProvidingComponentsCard = (props: { if (error || !entities) { return ( - + @@ -86,7 +121,37 @@ export const ProvidingComponentsCard = (props: { } columns={columns} + tableOptions={tableOptions} entities={entities as ComponentEntity[]} /> ); +} + +/** @public */ +export const ProvidingComponentsCard = ( + props: ProvidingComponentsCardProps | ProvidingComponentsCardLegacyProps, +) => { + const { t } = useTranslationRef(apiDocsTranslationRef); + + if (isLegacyProps(props)) { + return ; + } + + const { + title = t('providingComponentsCard.title'), + columnConfig = entityColumnPresets.component.columns, + } = props; + + return ( + + ); }; diff --git a/plugins/api-docs/src/components/ComponentsCards/index.ts b/plugins/api-docs/src/components/ComponentsCards/index.ts index 84dec071fb..33def6180c 100644 --- a/plugins/api-docs/src/components/ComponentsCards/index.ts +++ b/plugins/api-docs/src/components/ComponentsCards/index.ts @@ -15,4 +15,12 @@ */ export { ConsumingComponentsCard } from './ConsumingComponentsCard'; +export type { + ConsumingComponentsCardLegacyProps, + ConsumingComponentsCardProps, +} from './ConsumingComponentsCard'; export { ProvidingComponentsCard } from './ProvidingComponentsCard'; +export type { + ProvidingComponentsCardLegacyProps, + ProvidingComponentsCardProps, +} from './ProvidingComponentsCard'; diff --git a/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinition.test.tsx b/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinition.test.tsx index 78aae18b19..649b63ae3c 100644 --- a/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinition.test.tsx +++ b/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinition.test.tsx @@ -96,13 +96,13 @@ paths: `; const requestInterceptor = (req: any) => req; - const supportedSubmitMethods = ['get', 'post', 'put', 'delete']; + const supportedSubmitMethods = ['get', 'post', 'put', 'delete'] as const; const { findByRole, getByRole, getByLabelText } = await renderInTestApp( , ); diff --git a/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinition.tsx b/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinition.tsx index 8fcc29b829..ac7f31ee81 100644 --- a/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinition.tsx +++ b/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinition.tsx @@ -16,7 +16,7 @@ import { makeStyles } from '@material-ui/core/styles'; import { useEffect, useState } from 'react'; -import SwaggerUI, { SwaggerUIProps } from 'swagger-ui-react'; +import SwaggerUI from 'swagger-ui-react'; import 'swagger-ui-react/swagger-ui.css'; const useStyles = makeStyles(theme => ({ @@ -173,13 +173,23 @@ const useStyles = makeStyles(theme => ({ { color: theme.palette.warning.dark, }, + [`& input[type=text], + & input[type=password], + & input[type=email], + & input[type=number], + & textarea, + & select`]: { + backgroundColor: theme.palette.background.paper, + color: theme.palette.text.primary, + borderColor: theme.palette.divider, + }, }, }, })); export type OpenApiDefinitionProps = { definition: string; -} & Omit; +} & Omit, 'spec'>; export const OpenApiDefinition = ({ definition, diff --git a/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinitionWidget.tsx b/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinitionWidget.tsx index 290c8808fd..b1dbd75b0d 100644 --- a/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinitionWidget.tsx +++ b/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinitionWidget.tsx @@ -43,7 +43,7 @@ export const OpenApiDefinitionWidget = ( }> ); diff --git a/plugins/api-docs/src/index.ts b/plugins/api-docs/src/index.ts index 49b2805aad..ae344890b0 100644 --- a/plugins/api-docs/src/index.ts +++ b/plugins/api-docs/src/index.ts @@ -34,3 +34,4 @@ export { EntityProvidedApisCard, EntityProvidingComponentsCard, } from './plugin'; +export { apiDocsTranslationRef } from './translation'; diff --git a/plugins/api-docs/src/setupTests.ts b/plugins/api-docs/src/setupTests.ts index 9bcfe5e887..19b6d29795 100644 --- a/plugins/api-docs/src/setupTests.ts +++ b/plugins/api-docs/src/setupTests.ts @@ -17,9 +17,12 @@ import '@testing-library/jest-dom'; Object.defineProperty(global, 'TextEncoder', { - value: require('util').TextEncoder, + value: require('node:util').TextEncoder, }); Object.defineProperty(global, 'TextDecoder', { - value: require('util').TextDecoder, + value: require('node:util').TextDecoder, }); + +// Use a 15s timeout to accommodate the slowest API docs rendering tests under concurrency. +jest.setTimeout(15_000); diff --git a/plugins/api-docs/src/translation.ts b/plugins/api-docs/src/translation.ts index 6ae2668c7c..289115fd3a 100644 --- a/plugins/api-docs/src/translation.ts +++ b/plugins/api-docs/src/translation.ts @@ -17,15 +17,22 @@ import { createTranslationRef } from '@backstage/frontend-plugin-api'; /** - * @alpha + * @public */ export const apiDocsTranslationRef = createTranslationRef({ id: 'api-docs', messages: { + apiDefinitionCard: { + error: { + title: 'Could not fetch the API', + }, + rawButtonTitle: 'Raw', + }, apiDefinitionDialog: { closeButtonTitle: 'Close', tabsAriaLabel: 'API definition options', toggleButtonAriaLabel: 'Toggle API Definition Dialog', + rawButtonTitle: 'Raw', }, defaultApiExplorerPage: { title: 'APIs', @@ -83,6 +90,6 @@ export const apiDocsTranslationRef = createTranslationRef({ title: 'No component provides this API.', }, }, - apisCardHelpLinkTitle: 'Learn how to change this', + apisCardHelpLinkTitle: 'Learn how to change this.', }, }); diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md index 881a20e0f9..bfc44df3b3 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,166 @@ # @backstage/plugin-app-backend +## 0.5.13 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/errors@1.3.0 + - @backstage/plugin-auth-node@0.7.0 + - @backstage/config-loader@1.10.10 + - @backstage/config@1.3.7 + - @backstage/plugin-app-node@0.1.44 + +## 0.5.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/plugin-auth-node@0.7.0-next.2 + - @backstage/config-loader@1.10.10-next.1 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/config@1.3.7-next.0 + +## 0.5.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/plugin-app-node@0.1.44-next.1 + +## 0.5.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/config-loader@1.10.10-next.0 + - @backstage/plugin-auth-node@0.6.15-next.0 + - @backstage/plugin-app-node@0.1.44-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.5.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/config-loader@1.10.9 + - @backstage/plugin-auth-node@0.6.14 + - @backstage/plugin-app-node@0.1.43 + +## 0.5.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-app-node@0.1.43-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + +## 0.5.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.9-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-app-node@0.1.43-next.0 + - @backstage/plugin-auth-node@0.6.14-next.0 + +## 0.5.11 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/config-loader@1.10.8 + - @backstage/plugin-auth-node@0.6.13 + - @backstage/plugin-app-node@0.1.42 + +## 0.5.11-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/config-loader@1.10.8-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/plugin-app-node@0.1.42-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.5.10 + +### Patch Changes + +- 9ccf84e: Updated plugin metadata. +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-app-node@0.1.41 + - @backstage/plugin-auth-node@0.6.11 + +## 0.5.9 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-app-node@0.1.40 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/config-loader@1.10.7 + +## 0.5.9-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-app-node@0.1.40-next.1 + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config-loader@1.10.7-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.5.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-app-node@0.1.40-next.0 + - @backstage/config@1.3.6 + - @backstage/config-loader@1.10.7-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.5.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config-loader@1.10.6 + - @backstage/config@1.3.6 + - @backstage/plugin-app-node@0.1.39 + ## 0.5.8-next.1 ### Patch Changes diff --git a/plugins/app-backend/knip-report.md b/plugins/app-backend/knip-report.md index 55ec2572b5..c47a46e79f 100644 --- a/plugins/app-backend/knip-report.md +++ b/plugins/app-backend/knip-report.md @@ -1,15 +1,14 @@ # Knip report -## Unused dependencies (2) +## Unused dependencies (1) -| Name | Location | Severity | -| :-------------------------- | :----------- | :------- | -| @backstage/plugin-auth-node | plugins/app-backend/package.json | error | -| yn | plugins/app-backend/package.json | error | +| Name | Location | Severity | +| :-------------------------- | :---------------- | :------- | +| @backstage/plugin-auth-node | package.json:62:6 | error | ## Unused devDependencies (1) -| Name | Location | Severity | -| :------------------------- | :----------- | :------- | -| @backstage/backend-app-api | plugins/app-backend/package.json | error | +| Name | Location | Severity | +| :------------------------- | :---------------- | :------- | +| @backstage/backend-app-api | package.json:74:6 | error | diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index 67bec42447..eae2ad7bfc 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app-backend", - "version": "0.5.8-next.1", + "version": "0.5.13", "description": "A Backstage backend plugin that serves the Backstage frontend app", "backstage": { "role": "backend-plugin", @@ -8,7 +8,8 @@ "pluginPackages": [ "@backstage/plugin-app", "@backstage/plugin-app-backend", - "@backstage/plugin-app-node" + "@backstage/plugin-app-node", + "@backstage/plugin-app-react" ] }, "publishConfig": { @@ -60,22 +61,20 @@ "@backstage/plugin-app-node": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "@backstage/types": "workspace:^", - "express": "^4.17.1", + "express": "^4.22.0", "express-promise-router": "^4.1.0", "fs-extra": "^11.2.0", "globby": "^11.0.0", "helmet": "^6.0.0", "knex": "^3.0.0", "lodash": "^4.17.21", - "luxon": "^3.0.0", - "yn": "^4.0.0" + "luxon": "^3.0.0" }, "devDependencies": { "@backstage/backend-app-api": "workspace:^", "@backstage/backend-defaults": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "@backstage/types": "workspace:^", "@types/express": "^4.17.6", "@types/supertest": "^2.0.8", "supertest": "^7.0.0" diff --git a/plugins/app-backend/src/lib/assets/createStaticAssetMiddleware.ts b/plugins/app-backend/src/lib/assets/createStaticAssetMiddleware.ts index 60458436dd..1bc652e970 100644 --- a/plugins/app-backend/src/lib/assets/createStaticAssetMiddleware.ts +++ b/plugins/app-backend/src/lib/assets/createStaticAssetMiddleware.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { extname } from 'path'; +import { extname } from 'node:path'; import { RequestHandler } from 'express'; import { StaticAssetProvider } from './types'; import { CACHE_CONTROL_MAX_CACHE } from '../headers'; diff --git a/plugins/app-backend/src/lib/config/injectConfigIntoHtml.ts b/plugins/app-backend/src/lib/config/injectConfigIntoHtml.ts index dbd03e6ab5..38ab117fda 100644 --- a/plugins/app-backend/src/lib/config/injectConfigIntoHtml.ts +++ b/plugins/app-backend/src/lib/config/injectConfigIntoHtml.ts @@ -15,7 +15,7 @@ */ import fs from 'fs-extra'; -import { resolve as resolvePath } from 'path'; +import { resolve as resolvePath } from 'node:path'; import { InjectOptions } from './types'; import compileTemplate from 'lodash/template'; import { Config, ConfigReader } from '@backstage/config'; @@ -70,7 +70,7 @@ ${JSON.stringify(appConfigs, null, 2) return Buffer.from(indexHtmlContentWithConfig, 'utf8'); } -export function resolvePublicPath(config: Config) { +function resolvePublicPath(config: Config) { const baseUrl = new URL( config.getOptionalString('app.baseUrl') ?? '/', 'http://localhost:7007', diff --git a/plugins/app-backend/src/lib/config/injectConfigIntoStatic.ts b/plugins/app-backend/src/lib/config/injectConfigIntoStatic.ts index 29975b0c2a..e2f5ca9972 100644 --- a/plugins/app-backend/src/lib/config/injectConfigIntoStatic.ts +++ b/plugins/app-backend/src/lib/config/injectConfigIntoStatic.ts @@ -15,7 +15,7 @@ */ import fs from 'fs-extra'; -import { resolve as resolvePath } from 'path'; +import { resolve as resolvePath } from 'node:path'; import { InjectOptions } from './types'; /** diff --git a/plugins/app-backend/src/lib/config/readFrontendConfig.ts b/plugins/app-backend/src/lib/config/readFrontendConfig.ts index f2b22b720f..7414c2621f 100644 --- a/plugins/app-backend/src/lib/config/readFrontendConfig.ts +++ b/plugins/app-backend/src/lib/config/readFrontendConfig.ts @@ -15,7 +15,7 @@ */ import fs from 'fs-extra'; -import { resolve as resolvePath } from 'path'; +import { resolve as resolvePath } from 'node:path'; import { AppConfig, Config } from '@backstage/config'; import { JsonObject } from '@backstage/types'; import { diff --git a/plugins/app-backend/src/migrations.test.ts b/plugins/app-backend/src/migrations.test.ts index 8a413559d2..eb327e3f6a 100644 --- a/plugins/app-backend/src/migrations.test.ts +++ b/plugins/app-backend/src/migrations.test.ts @@ -16,7 +16,7 @@ import { Knex } from 'knex'; import { TestDatabases } from '@backstage/backend-test-utils'; -import fs from 'fs'; +import fs from 'node:fs'; const migrationsDir = `${__dirname}/../migrations`; const migrationsFiles = fs.readdirSync(migrationsDir).sort(); diff --git a/plugins/app-backend/src/service/router.test.ts b/plugins/app-backend/src/service/router.test.ts index 12b471c1f2..c5e0edf7d3 100644 --- a/plugins/app-backend/src/service/router.test.ts +++ b/plugins/app-backend/src/service/router.test.ts @@ -17,7 +17,7 @@ import { AppConfig } from '@backstage/config'; import express from 'express'; import Router from 'express-promise-router'; -import { resolve as resolvePath } from 'path'; +import { resolve as resolvePath } from 'node:path'; import request from 'supertest'; import { createRouter } from './router'; import { loadConfigSchema } from '@backstage/config-loader'; diff --git a/plugins/app-backend/src/service/router.ts b/plugins/app-backend/src/service/router.ts index cf2ad886d7..4841f2546a 100644 --- a/plugins/app-backend/src/service/router.ts +++ b/plugins/app-backend/src/service/router.ts @@ -24,7 +24,7 @@ import helmet from 'helmet'; import express, { Request, Response } from 'express'; import Router from 'express-promise-router'; import fs from 'fs-extra'; -import { resolve as resolvePath } from 'path'; +import { resolve as resolvePath } from 'node:path'; import { createStaticAssetMiddleware, findStaticAssets, diff --git a/plugins/app-node/CHANGELOG.md b/plugins/app-node/CHANGELOG.md index fae80d8087..2a577627df 100644 --- a/plugins/app-node/CHANGELOG.md +++ b/plugins/app-node/CHANGELOG.md @@ -1,5 +1,111 @@ # @backstage/plugin-app-node +## 0.1.44 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/config-loader@1.10.10 + +## 0.1.44-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + +## 0.1.44-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/config-loader@1.10.10-next.0 + +## 0.1.43 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/config-loader@1.10.9 + +## 0.1.43-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + +## 0.1.43-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.9-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + +## 0.1.42 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/config-loader@1.10.8 + +## 0.1.42-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/config-loader@1.10.8-next.0 + +## 0.1.41 + +### Patch Changes + +- 9ccf84e: Updated plugin metadata. +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + +## 0.1.40 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/config-loader@1.10.7 + +## 0.1.40-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config-loader@1.10.7-next.1 + +## 0.1.40-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/config-loader@1.10.7-next.0 + +## 0.1.39 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config-loader@1.10.6 + ## 0.1.39-next.1 ### Patch Changes diff --git a/plugins/app-node/package.json b/plugins/app-node/package.json index dcd1c6f812..953edfd727 100644 --- a/plugins/app-node/package.json +++ b/plugins/app-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app-node", - "version": "0.1.39-next.1", + "version": "0.1.44", "description": "Node.js library for the app plugin", "backstage": { "role": "node-library", @@ -8,7 +8,8 @@ "pluginPackages": [ "@backstage/plugin-app", "@backstage/plugin-app-backend", - "@backstage/plugin-app-node" + "@backstage/plugin-app-node", + "@backstage/plugin-app-react" ] }, "publishConfig": { @@ -39,7 +40,7 @@ "@backstage/backend-plugin-api": "workspace:^", "@backstage/config-loader": "workspace:^", "@types/express": "^4.17.6", - "express": "^4.17.1", + "express": "^4.22.0", "fs-extra": "^11.2.0" }, "devDependencies": { diff --git a/plugins/app-node/src/schema.ts b/plugins/app-node/src/schema.ts index a38fd585d9..6e3a01c841 100644 --- a/plugins/app-node/src/schema.ts +++ b/plugins/app-node/src/schema.ts @@ -15,7 +15,7 @@ */ import fs from 'fs-extra'; -import { resolve as resolvePath } from 'path'; +import { resolve as resolvePath } from 'node:path'; import { ConfigSchema, loadConfigSchema } from '@backstage/config-loader'; /** diff --git a/plugins/app-react/.eslintrc.js b/plugins/app-react/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/app-react/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/app-react/CHANGELOG.md b/plugins/app-react/CHANGELOG.md new file mode 100644 index 0000000000..79644f8a30 --- /dev/null +++ b/plugins/app-react/CHANGELOG.md @@ -0,0 +1,97 @@ +# @backstage/plugin-app-react + +## 0.2.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/core-plugin-api@1.12.5 + +## 0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-plugin-api@1.12.5-next.1 + +## 0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.2-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + +## 0.2.1 + +### Patch Changes + +- 5f3f5d2: `NavContentBlueprint` nav item collections now keep previously collected `rest()` results in sync when additional items are taken later in the same render, making it easier to place items across multiple sidebar sections. +- 2c383b5: Added `AnalyticsImplementationBlueprint` and `AnalyticsImplementationFactory`, migrated from `@backstage/frontend-plugin-api`. +- Updated dependencies + - @backstage/core-plugin-api@1.12.4 + - @backstage/frontend-plugin-api@0.15.0 + +## 0.2.1-next.1 + +### Patch Changes + +- 2c383b5: Added `AnalyticsImplementationBlueprint` and `AnalyticsImplementationFactory`, migrated from `@backstage/frontend-plugin-api`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + +## 0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + +## 0.2.0 + +### Minor Changes + +- a2133be: Added new `NavContentNavItem`, `NavContentNavItems`, and `navItems` prop to `NavContentComponentProps` for auto-discovering navigation items from page extensions. The new `navItems` collection supports `take(id)` and `rest()` methods for placing specific items in custom sidebar positions, as well as `withComponent(Component)` which returns a `NavContentNavItemsWithComponent` for rendering items directly as elements. The existing `items` prop is now deprecated in favor of `navItems`. + +### Patch Changes + +- ef6916e: Added `IconElement` type as a replacement for the deprecated `IconComponent`. The `IconsApi` now has a new `icon()` method that returns `IconElement`, while the existing `getIcon()` method is deprecated. The `IconBundleBlueprint` now accepts both `IconComponent` and `IconElement` values. +- 409af72: Internal refactor to move implementation of blueprints from `@backstage/frontend-plugin-api` to this package. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/core-plugin-api@1.12.3 + +## 0.1.1-next.0 + +### Patch Changes + +- 409af72: Internal refactor to move implementation of blueprints from `@backstage/frontend-plugin-api` to this package. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + +## 0.1.0 + +### Minor Changes + +- 9ccf84e: Initial release of this web library for `@backstage/plugin-app`. + +### Patch Changes + +- 9ccf84e: Moved the following blueprints from `@backstage/frontend-plugin-api`: + + - `AppRootWrapperBlueprint` + - `IconBundleBlueprint` + - `NavContentBlueprint` + - `RouterBlueprint` + - `SignInPageBlueprint` + - `SwappableComponentBlueprint` + - `ThemeBlueprint` + - `TranslationBlueprint` + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 diff --git a/plugins/app-react/README.md b/plugins/app-react/README.md new file mode 100644 index 0000000000..c7e7fb2823 --- /dev/null +++ b/plugins/app-react/README.md @@ -0,0 +1,5 @@ +# @backstage/plugin-app-react + +Welcome to the web library package for the app plugin! + +_This plugin was created through the Backstage CLI_ diff --git a/plugins/app-react/catalog-info.yaml b/plugins/app-react/catalog-info.yaml new file mode 100644 index 0000000000..1ef48d3b2d --- /dev/null +++ b/plugins/app-react/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-app-react + title: '@backstage/plugin-app-react' + description: Web library for the app plugin +spec: + lifecycle: experimental + type: backstage-web-library + owner: framework-maintainers diff --git a/plugins/app-react/knip-report.md b/plugins/app-react/knip-report.md new file mode 100644 index 0000000000..16b8d5562d --- /dev/null +++ b/plugins/app-react/knip-report.md @@ -0,0 +1,15 @@ +# Knip report + +## Unused dependencies (2) + +| Name | Location | Severity | +| :------------------------- | :---------------- | :------- | +| @backstage/core-plugin-api | package.json:42:6 | error | +| @material-ui/core | package.json:44:6 | error | + +## Unused devDependencies (1) + +| Name | Location | Severity | +| :-------------------- | :---------------- | :------- | +| @backstage/test-utils | package.json:49:6 | error | + diff --git a/plugins/app-react/package.json b/plugins/app-react/package.json new file mode 100644 index 0000000000..642f581342 --- /dev/null +++ b/plugins/app-react/package.json @@ -0,0 +1,68 @@ +{ + "name": "@backstage/plugin-app-react", + "version": "0.2.2", + "description": "Web library for the app plugin", + "backstage": { + "role": "web-library", + "pluginId": "app", + "pluginPackages": [ + "@backstage/plugin-app", + "@backstage/plugin-app-backend", + "@backstage/plugin-app-node", + "@backstage/plugin-app-react" + ] + }, + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/app-react" + }, + "license": "Apache-2.0", + "sideEffects": false, + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "backstage-cli package build", + "clean": "backstage-cli package clean", + "lint": "backstage-cli package lint", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "start": "backstage-cli package start", + "test": "backstage-cli package test" + }, + "dependencies": { + "@backstage/core-plugin-api": "workspace:^", + "@backstage/frontend-plugin-api": "workspace:^", + "@material-ui/core": "^4.9.13" + }, + "devDependencies": { + "@backstage/cli": "workspace:^", + "@backstage/frontend-test-utils": "workspace:^", + "@backstage/test-utils": "workspace:^", + "@testing-library/jest-dom": "^6.0.0", + "@testing-library/react": "^16.0.0", + "@types/react": "^18.0.0", + "react": "^18.0.2", + "react-dom": "^18.0.2", + "react-router-dom": "^6.30.2" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0", + "react-router-dom": "^6.30.2" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } +} diff --git a/plugins/app-react/report.api.md b/plugins/app-react/report.api.md new file mode 100644 index 0000000000..a4e63e254b --- /dev/null +++ b/plugins/app-react/report.api.md @@ -0,0 +1,337 @@ +## API Report File for "@backstage/plugin-app-react" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { AnalyticsImplementation } from '@backstage/frontend-plugin-api'; +import { AppNode } from '@backstage/frontend-plugin-api'; +import { AppTheme } from '@backstage/frontend-plugin-api'; +import { ComponentType } from 'react'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; +import { IdentityApi } from '@backstage/frontend-plugin-api'; +import { ReactNode } from 'react'; +import { RouteRef } from '@backstage/frontend-plugin-api'; +import { SwappableComponentRef } from '@backstage/frontend-plugin-api'; +import { TranslationMessages } from '@backstage/frontend-plugin-api'; +import { TranslationResource } from '@backstage/frontend-plugin-api'; +import { TypesToApiRefs } from '@backstage/frontend-plugin-api'; + +// @public +export const AnalyticsImplementationBlueprint: ExtensionBlueprint<{ + kind: 'analytics'; + params: ( + params: AnalyticsImplementationFactory, + ) => ExtensionBlueprintParams>; + output: ExtensionDataRef< + AnalyticsImplementationFactory<{}>, + 'core.analytics.factory', + {} + >; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { + factory: ConfigurableExtensionDataRef< + AnalyticsImplementationFactory<{}>, + 'core.analytics.factory', + {} + >; + }; +}>; + +// @public (undocumented) +export type AnalyticsImplementationFactory< + Deps extends { + [name in string]: unknown; + } = {}, +> = { + deps: TypesToApiRefs; + factory(deps: Deps): AnalyticsImplementation; +}; + +// @public +export const AppRootWrapperBlueprint: ExtensionBlueprint<{ + kind: 'app-root-wrapper'; + params: { + Component?: [error: 'Use the `component` parameter instead']; + component: (props: { children: ReactNode }) => JSX.Element | null; + }; + output: ExtensionDataRef< + (props: { children: ReactNode }) => JSX.Element | null, + 'app.root.wrapper', + {} + >; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { + component: ConfigurableExtensionDataRef< + (props: { children: ReactNode }) => JSX.Element | null, + 'app.root.wrapper', + {} + >; + }; +}>; + +// @public +export const IconBundleBlueprint: ExtensionBlueprint<{ + kind: 'icon-bundle'; + params: { + icons: { [key in string]: IconComponent | IconElement }; + }; + output: ExtensionDataRef< + { + [x: string]: IconComponent | IconElement; + }, + 'core.icons', + {} + >; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { + icons: ConfigurableExtensionDataRef< + { + [x: string]: IconComponent | IconElement; + }, + 'core.icons', + {} + >; + }; +}>; + +// @public +export const NavContentBlueprint: ExtensionBlueprint<{ + kind: 'nav-content'; + params: { + component: NavContentComponent; + }; + output: ExtensionDataRef< + NavContentComponent, + 'core.nav-content.component', + {} + >; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { + component: ConfigurableExtensionDataRef< + NavContentComponent, + 'core.nav-content.component', + {} + >; + }; +}>; + +// @public +export type NavContentComponent = ( + props: NavContentComponentProps, +) => JSX.Element | null; + +// @public +export interface NavContentComponentProps { + // @deprecated + items: Array<{ + icon: IconComponent; + title: string; + routeRef: RouteRef; + to: string; + text: string; + }>; + navItems: NavContentNavItems; +} + +// @public +export interface NavContentNavItem { + href: string; + icon: IconElement; + node: AppNode; + routeRef: RouteRef; + title: string; +} + +// @public +export interface NavContentNavItems { + clone(): NavContentNavItems; + rest(): NavContentNavItem[]; + take(id: string): NavContentNavItem | undefined; + withComponent( + Component: ComponentType, + ): NavContentNavItemsWithComponent; +} + +// @public +export interface NavContentNavItemsWithComponent { + rest(options?: { sortBy?: 'title' }): JSX.Element[]; + take(id: string): JSX.Element | null; +} + +// @public +export const RouterBlueprint: ExtensionBlueprint<{ + kind: 'app-router-component'; + params: { + Component?: [error: 'Use the `component` parameter instead']; + component: (props: { children: ReactNode }) => JSX.Element | null; + }; + output: ExtensionDataRef< + (props: { children: ReactNode }) => JSX.Element | null, + 'app.router.wrapper', + {} + >; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { + component: ConfigurableExtensionDataRef< + (props: { children: ReactNode }) => JSX.Element | null, + 'app.router.wrapper', + {} + >; + }; +}>; + +// @public +export const SignInPageBlueprint: ExtensionBlueprint<{ + kind: 'sign-in-page'; + params: { + loader: () => Promise>; + }; + output: ExtensionDataRef< + ComponentType, + 'core.sign-in-page.component', + {} + >; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { + component: ConfigurableExtensionDataRef< + ComponentType, + 'core.sign-in-page.component', + {} + >; + }; +}>; + +// @public +export type SignInPageProps = { + onSignInSuccess(identityApi: IdentityApi): void; + children?: ReactNode; +}; + +// @public +export const SwappableComponentBlueprint: ExtensionBlueprint<{ + kind: 'component'; + params: >(params: { + component: Ref extends SwappableComponentRef< + any, + infer IExternalComponentProps + > + ? { + ref: Ref; + } & ((props: IExternalComponentProps) => JSX.Element | null) + : never; + loader: Ref extends SwappableComponentRef + ? + | (() => (props: IInnerComponentProps) => JSX.Element | null) + | (() => Promise<(props: IInnerComponentProps) => JSX.Element | null>) + : never; + }) => ExtensionBlueprintParams<{ + component: Ref extends SwappableComponentRef< + any, + infer IExternalComponentProps + > + ? { + ref: Ref; + } & ((props: IExternalComponentProps) => JSX.Element | null) + : never; + loader: Ref extends SwappableComponentRef + ? + | (() => (props: IInnerComponentProps) => JSX.Element | null) + | (() => Promise<(props: IInnerComponentProps) => JSX.Element | null>) + : never; + }>; + output: ExtensionDataRef< + { + ref: SwappableComponentRef; + loader: + | (() => (props: {}) => JSX.Element | null) + | (() => Promise<(props: {}) => JSX.Element | null>); + }, + 'core.swappableComponent', + {} + >; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { + component: ConfigurableExtensionDataRef< + { + ref: SwappableComponentRef; + loader: + | (() => (props: {}) => JSX.Element | null) + | (() => Promise<(props: {}) => JSX.Element | null>); + }, + 'core.swappableComponent', + {} + >; + }; +}>; + +// @public +export const ThemeBlueprint: ExtensionBlueprint<{ + kind: 'theme'; + params: { + theme: AppTheme; + }; + output: ExtensionDataRef; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { + theme: ConfigurableExtensionDataRef; + }; +}>; + +// @public +export const TranslationBlueprint: ExtensionBlueprint<{ + kind: 'translation'; + params: { + resource: TranslationResource | TranslationMessages; + }; + output: ExtensionDataRef< + | TranslationResource + | TranslationMessages< + string, + { + [x: string]: string; + }, + boolean + >, + 'core.translation.translation', + {} + >; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { + translation: ConfigurableExtensionDataRef< + | TranslationResource + | TranslationMessages< + string, + { + [x: string]: string; + }, + boolean + >, + 'core.translation.translation', + {} + >; + }; +}>; +``` diff --git a/plugins/app-react/src/blueprints/AnalyticsImplementationBlueprint.test.ts b/plugins/app-react/src/blueprints/AnalyticsImplementationBlueprint.test.ts new file mode 100644 index 0000000000..6d43e0352f --- /dev/null +++ b/plugins/app-react/src/blueprints/AnalyticsImplementationBlueprint.test.ts @@ -0,0 +1,55 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AnalyticsImplementationBlueprint } from './AnalyticsImplementationBlueprint'; + +describe('AnalyticsBlueprint', () => { + it('should create an extension with sensible defaults', () => { + const factory = { + deps: {}, + factory: () => ({ captureEvent: () => {} }), + }; + + const extension = AnalyticsImplementationBlueprint.make({ + params: define => define(factory), + name: 'test', + }); + + expect(extension).toMatchInlineSnapshot(` + { + "$$type": "@backstage/ExtensionDefinition", + "T": undefined, + "attachTo": { + "id": "api:app/analytics", + "input": "implementations", + }, + "configSchema": undefined, + "disabled": false, + "factory": [Function], + "if": undefined, + "inputs": {}, + "kind": "analytics", + "name": "test", + "output": [ + [Function], + ], + "override": [Function], + "toString": [Function], + "version": "v2", + } + `); + }); +}); diff --git a/plugins/app-react/src/blueprints/AnalyticsImplementationBlueprint.ts b/plugins/app-react/src/blueprints/AnalyticsImplementationBlueprint.ts new file mode 100644 index 0000000000..d85c926ee0 --- /dev/null +++ b/plugins/app-react/src/blueprints/AnalyticsImplementationBlueprint.ts @@ -0,0 +1,56 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + AnalyticsImplementation, + TypesToApiRefs, + createExtensionBlueprint, + createExtensionBlueprintParams, + createExtensionDataRef, +} from '@backstage/frontend-plugin-api'; + +/** @public */ +export type AnalyticsImplementationFactory< + Deps extends { [name in string]: unknown } = {}, +> = { + deps: TypesToApiRefs; + factory(deps: Deps): AnalyticsImplementation; +}; + +const factoryDataRef = + createExtensionDataRef().with({ + id: 'core.analytics.factory', + }); + +/** + * Creates analytics implementations. + * + * @public + */ +export const AnalyticsImplementationBlueprint = createExtensionBlueprint({ + kind: 'analytics', + attachTo: { id: 'api:app/analytics', input: 'implementations' }, + output: [factoryDataRef], + dataRefs: { + factory: factoryDataRef, + }, + defineParams: ( + params: AnalyticsImplementationFactory, + ) => createExtensionBlueprintParams(params as AnalyticsImplementationFactory), + *factory(params) { + yield factoryDataRef(params); + }, +}); diff --git a/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.test.tsx b/plugins/app-react/src/blueprints/AppRootWrapperBlueprint.test.tsx similarity index 79% rename from packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.test.tsx rename to plugins/app-react/src/blueprints/AppRootWrapperBlueprint.test.tsx index 0f26af3243..956e4bbaee 100644 --- a/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.test.tsx +++ b/plugins/app-react/src/blueprints/AppRootWrapperBlueprint.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,8 @@ import { coreExtensionData, createExtension, createExtensionInput, -} from '../wiring'; + createFrontendModule, +} from '@backstage/frontend-plugin-api'; import { renderTestApp } from '@backstage/frontend-test-utils'; describe('AppRootWrapperBlueprint', () => { @@ -42,6 +43,7 @@ describe('AppRootWrapperBlueprint', () => { "configSchema": undefined, "disabled": false, "factory": [Function], + "if": undefined, "inputs": {}, "kind": "app-root-wrapper", "name": undefined, @@ -63,7 +65,11 @@ describe('AppRootWrapperBlueprint', () => { }, }); - renderTestApp({ extensions: [extension] }); + renderTestApp({ + features: [ + createFrontendModule({ pluginId: 'app', extensions: [extension] }), + ], + }); await waitFor(() => expect(screen.getByText('Hello')).toBeInTheDocument()); }); @@ -95,20 +101,28 @@ describe('AppRootWrapperBlueprint', () => { }); renderTestApp({ - extensions: [ - extension, - createExtension({ - name: 'test-child', - attachTo: { id: 'app-root-wrapper:test', input: 'children' }, - output: [coreExtensionData.reactElement], - factory: () => [coreExtensionData.reactElement(
    Its Me
    )], + extensions: [], + features: [ + createFrontendModule({ + pluginId: 'app', + extensions: [ + extension, + createExtension({ + name: 'test-child', + attachTo: extension.inputs.children, + output: [coreExtensionData.reactElement], + factory: () => [ + coreExtensionData.reactElement(
    Its Me
    ), + ], + }), + ], }), ], config: { app: { extensions: [ { - 'app-root-wrapper:test': { config: { name: 'Robin' } }, + 'app-root-wrapper:app': { config: { name: 'Robin' } }, }, ], }, diff --git a/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx b/plugins/app-react/src/blueprints/AppRootWrapperBlueprint.tsx similarity index 87% rename from packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx rename to plugins/app-react/src/blueprints/AppRootWrapperBlueprint.tsx index 493fba05f6..bfeef61e1d 100644 --- a/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx +++ b/plugins/app-react/src/blueprints/AppRootWrapperBlueprint.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,10 @@ */ import { ReactNode } from 'react'; -import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; +import { + createExtensionBlueprint, + createExtensionDataRef, +} from '@backstage/frontend-plugin-api'; const componentDataRef = createExtensionDataRef< (props: { children: ReactNode }) => JSX.Element | null @@ -24,7 +27,7 @@ const componentDataRef = createExtensionDataRef< /** * Creates a extensions that render a React wrapper at the app root, enclosing * the app layout. This is useful for example for adding global React contexts - * and similar. + * and similar. This blueprint is limited to use by the app plugin. * * @public */ diff --git a/packages/frontend-plugin-api/src/blueprints/IconBundleBlueprint.ts b/plugins/app-react/src/blueprints/IconBundleBlueprint.ts similarity index 62% rename from packages/frontend-plugin-api/src/blueprints/IconBundleBlueprint.ts rename to plugins/app-react/src/blueprints/IconBundleBlueprint.ts index 303d1a3fd2..99c6abcd72 100644 --- a/packages/frontend-plugin-api/src/blueprints/IconBundleBlueprint.ts +++ b/plugins/app-react/src/blueprints/IconBundleBlueprint.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,21 +14,28 @@ * limitations under the License. */ -import { IconComponent } from '../icons'; -import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; +import { IconComponent, IconElement } from '@backstage/frontend-plugin-api'; +import { + createExtensionBlueprint, + createExtensionDataRef, +} from '@backstage/frontend-plugin-api'; const iconsDataRef = createExtensionDataRef<{ - [key in string]: IconComponent; + [key in string]: IconComponent | IconElement; }>().with({ id: 'core.icons' }); -/** @public */ +/** + * Creates an extension that adds icon bundles to your app. This blueprint is limited to use by the app plugin. + * + * @public + */ export const IconBundleBlueprint = createExtensionBlueprint({ kind: 'icon-bundle', attachTo: { id: 'api:app/icons', input: 'icons' }, output: [iconsDataRef], - factory: (params: { icons: { [key in string]: IconComponent } }) => [ - iconsDataRef(params.icons), - ], + factory: (params: { + icons: { [key in string]: IconComponent | IconElement }; + }) => [iconsDataRef(params.icons)], dataRefs: { icons: iconsDataRef, }, diff --git a/plugins/app-react/src/blueprints/NavContentBlueprint.test.tsx b/plugins/app-react/src/blueprints/NavContentBlueprint.test.tsx new file mode 100644 index 0000000000..eb0e8c1007 --- /dev/null +++ b/plugins/app-react/src/blueprints/NavContentBlueprint.test.tsx @@ -0,0 +1,363 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AppNode, createRouteRef } from '@backstage/frontend-plugin-api'; +import { + NavContentBlueprint, + NavContentNavItem, + NavContentNavItems, +} from './NavContentBlueprint'; +import { createExtensionTester } from '@backstage/frontend-test-utils'; +import { render, screen } from '@testing-library/react'; + +const routeRef = createRouteRef(); + +function mockNode(id: string): AppNode { + return { spec: { id } } as AppNode; +} + +function mockNavItems(items: NavContentNavItem[]): NavContentNavItems { + const taken = new Set(); + let restItems: NavContentNavItem[] | undefined; + return { + take(id: string) { + const item = items.find(i => i.node.spec.id === id); + if (item && !taken.has(id)) { + taken.add(id); + if (restItems) { + const index = restItems.findIndex( + restItem => restItem.node.spec.id === id, + ); + if (index !== -1) { + restItems.splice(index, 1); + } + } + } + return item; + }, + rest: () => { + if (!restItems) { + restItems = items.filter(i => !taken.has(i.node.spec.id)); + } + return restItems; + }, + clone() { + return mockNavItems(items); + }, + withComponent(Component: (props: NavContentNavItem) => JSX.Element) { + let renderedItems: JSX.Element[] | undefined; + return { + take: (id: string) => { + const item = items.find(i => i.node.spec.id === id); + if (item && !taken.has(id)) { + taken.add(id); + if (restItems) { + const index = restItems.findIndex( + restItem => restItem.node.spec.id === id, + ); + if (index !== -1) { + restItems.splice(index, 1); + } + } + if (renderedItems) { + const index = renderedItems.findIndex( + renderedItem => renderedItem.key === item.node.spec.id, + ); + if (index !== -1) { + renderedItems.splice(index, 1); + } + } + return ; + } + return null; + }, + rest: (options?: { sortBy?: 'title' }) => { + if (!restItems) { + restItems = items.filter(i => !taken.has(i.node.spec.id)); + } + if (!renderedItems) { + if (options?.sortBy === 'title') { + restItems.sort((a, b) => a.title.localeCompare(b.title)); + } + renderedItems = restItems.map(item => ( + + )); + } + return renderedItems; + }, + }; + }, + }; +} + +describe('NavContentBlueprint', () => { + it('should create an extension with sensible defaults', () => { + const extension = NavContentBlueprint.make({ + params: { + component: () =>
    Nav content
    , + }, + }); + + expect(extension).toMatchInlineSnapshot(` + { + "$$type": "@backstage/ExtensionDefinition", + "T": undefined, + "attachTo": { + "id": "app/nav", + "input": "content", + }, + "configSchema": undefined, + "disabled": false, + "factory": [Function], + "if": undefined, + "inputs": {}, + "kind": "nav-content", + "name": undefined, + "output": [ + [Function], + ], + "override": [Function], + "toString": [Function], + "version": "v2", + } + `); + }); + + it('should return a valid component with legacy items', () => { + const extension = NavContentBlueprint.make({ + name: 'test', + params: { + component: ({ items }) => ( +
    + Items: + {items.map((item, index) => ( + + {item.title} + + ))} +
    + ), + }, + }); + + const tester = createExtensionTester(extension); + + expect( + tester.get(NavContentBlueprint.dataRefs.component)({ + navItems: mockNavItems([]), + items: [ + { + to: '/', + text: 'Home', + title: 'Home', + icon: () => null, + routeRef, + }, + ], + }), + ).toEqual( +
    + Items: + {[ + + Home + , + ]} +
    , + ); + }); + + it('should return a valid component with navItems', () => { + const items: NavContentNavItem[] = [ + { + node: mockNode('page:home'), + href: '/', + title: 'Home', + icon: home, + routeRef, + }, + { + node: mockNode('page:catalog'), + href: '/catalog', + title: 'Catalog', + icon: catalog, + routeRef, + }, + { + node: mockNode('page:docs'), + href: '/docs', + title: 'Docs', + icon: docs, + routeRef, + }, + ]; + + const extension = NavContentBlueprint.make({ + name: 'test', + params: { + component: ({ navItems }) => ( +
    + {navItems.rest().map(item => ( + + {item.title} + + ))} +
    + ), + }, + }); + + const tester = createExtensionTester(extension); + + expect( + tester.get(NavContentBlueprint.dataRefs.component)({ + navItems: mockNavItems(items), + items: [], + }), + ).toEqual( +
    + {[ + + Home + , + + Catalog + , + + Docs + , + ]} +
    , + ); + }); + + it('should support withComponent for take and rest', () => { + const items: NavContentNavItem[] = [ + { + node: mockNode('page:home'), + href: '/', + title: 'Home', + icon: home, + routeRef, + }, + { + node: mockNode('page:catalog'), + href: '/catalog', + title: 'Catalog', + icon: catalog, + routeRef, + }, + { + node: mockNode('page:docs'), + href: '/docs', + title: 'Docs', + icon: docs, + routeRef, + }, + ]; + + const extension = NavContentBlueprint.make({ + name: 'test', + params: { + component: ({ navItems }) => { + const nav = navItems.withComponent(item => ( + {item.title} + )); + return ( +
    +
    {nav.take('page:home')}
    + +
    + ); + }, + }, + }); + + const tester = createExtensionTester(extension); + const Component = tester.get(NavContentBlueprint.dataRefs.component); + + render(); + + const homeLink = screen.getByText('Home'); + expect(homeLink).toBeInTheDocument(); + expect(homeLink.closest('header')).toBeTruthy(); + + const catalogLink = screen.getByText('Catalog'); + expect(catalogLink).toBeInTheDocument(); + expect(catalogLink.closest('nav')).toBeTruthy(); + + const docsLink = screen.getByText('Docs'); + expect(docsLink).toBeInTheDocument(); + expect(docsLink.closest('nav')).toBeTruthy(); + }); + + it('should keep the rendered rest array in sync with later take calls', () => { + const items: NavContentNavItem[] = [ + { + node: mockNode('page:catalog'), + href: '/catalog', + title: 'Catalog', + icon: catalog, + routeRef, + }, + { + node: mockNode('page:devtools'), + href: '/devtools', + title: 'DevTools', + icon: devtools, + routeRef, + }, + { + node: mockNode('page:user-settings'), + href: '/settings', + title: 'Settings', + icon: settings, + routeRef, + }, + ]; + + const extension = NavContentBlueprint.make({ + name: 'test', + params: { + component: ({ navItems }) => { + const nav = navItems.withComponent(item => ( + {item.title} + )); + const rest = nav.rest({ sortBy: 'title' }); + + return ( +
    + + +
    {nav.take('page:user-settings')}
    +
    + ); + }, + }, + }); + + const tester = createExtensionTester(extension); + const Component = tester.get(NavContentBlueprint.dataRefs.component); + + render(); + + expect(screen.getByText('Catalog').closest('nav')).toBeTruthy(); + expect(screen.queryByText('DevTools')?.closest('nav')).toBeFalsy(); + expect(screen.queryByText('Settings')?.closest('nav')).toBeFalsy(); + expect(screen.getByText('DevTools').closest('aside')).toBeTruthy(); + expect(screen.getByText('Settings').closest('footer')).toBeTruthy(); + }); +}); diff --git a/plugins/app-react/src/blueprints/NavContentBlueprint.ts b/plugins/app-react/src/blueprints/NavContentBlueprint.ts new file mode 100644 index 0000000000..8cfbf7a276 --- /dev/null +++ b/plugins/app-react/src/blueprints/NavContentBlueprint.ts @@ -0,0 +1,144 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentType } from 'react'; +import { + AppNode, + IconComponent, + IconElement, + RouteRef, +} from '@backstage/frontend-plugin-api'; +import { + createExtensionBlueprint, + createExtensionDataRef, +} from '@backstage/frontend-plugin-api'; + +/** + * A navigation item auto-discovered from a page extension in the app. + * + * @public + */ +export interface NavContentNavItem { + /** The app node of the page extension that this nav item points to */ + node: AppNode; + /** The resolved route path */ + href: string; + /** The display title */ + title: string; + /** The display icon */ + icon: IconElement; + /** The route ref of the source page */ + routeRef: RouteRef; +} + +/** + * A pre-bound renderer that wraps {@link NavContentNavItems} with a component, + * so that `take` and `rest` return rendered elements directly. + * + * @public + */ +export interface NavContentNavItemsWithComponent { + /** Render and take a specific item by extension ID. Returns null if not found. */ + take(id: string): JSX.Element | null; + /** + * Render all remaining items not yet taken, optionally sorted. + * + * The returned array is live and is updated when later `take` calls remove + * items from the collection. + */ + rest(options?: { sortBy?: 'title' }): JSX.Element[]; +} + +/** + * A collection of nav items that supports picking specific items by ID + * and retrieving whatever remains. Created fresh for each render. + * + * @public + */ +export interface NavContentNavItems { + /** Take an item by extension ID, removing it from the collection. */ + take(id: string): NavContentNavItem | undefined; + /** + * All items not yet taken. + * + * The returned array is live and is updated when later `take` calls remove + * items from the collection. + */ + rest(): NavContentNavItem[]; + /** Create a copy of the collection preserving the current taken state. */ + clone(): NavContentNavItems; + /** Create a renderer that wraps take/rest to return pre-rendered elements. */ + withComponent( + Component: ComponentType, + ): NavContentNavItemsWithComponent; +} + +/** + * The props for the {@link NavContentComponent}. + * + * @public + */ +export interface NavContentComponentProps { + /** + * Nav items auto-discovered from page extensions, with take/rest semantics + * for placing specific items in specific positions. + */ + navItems: NavContentNavItems; + + /** + * Flat list of nav items for simple rendering. Use `navItems` for more + * control over item placement. + * + * @deprecated Use `navItems` instead. + */ + items: Array<{ + icon: IconComponent; + title: string; + routeRef: RouteRef; + to: string; + text: string; + }>; +} + +/** + * A component that renders the nav bar content, to be passed to the {@link NavContentBlueprint}. + * + * @public + */ +export type NavContentComponent = ( + props: NavContentComponentProps, +) => JSX.Element | null; + +const componentDataRef = createExtensionDataRef().with({ + id: 'core.nav-content.component', +}); + +/** + * Creates an extension that replaces the entire nav bar with your own component. This blueprint is limited to use by the app plugin. + * + * @public + */ +export const NavContentBlueprint = createExtensionBlueprint({ + kind: 'nav-content', + attachTo: { id: 'app/nav', input: 'content' }, + output: [componentDataRef], + dataRefs: { + component: componentDataRef, + }, + *factory(params: { component: NavContentComponent }) { + yield componentDataRef(params.component); + }, +}); diff --git a/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.test.tsx b/plugins/app-react/src/blueprints/RouterBlueprint.test.tsx similarity index 98% rename from packages/frontend-plugin-api/src/blueprints/RouterBlueprint.test.tsx rename to plugins/app-react/src/blueprints/RouterBlueprint.test.tsx index f2e5199124..899cb34903 100644 --- a/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.test.tsx +++ b/plugins/app-react/src/blueprints/RouterBlueprint.test.tsx @@ -20,7 +20,7 @@ import { coreExtensionData, createExtension, createExtensionInput, -} from '../wiring'; +} from '@backstage/frontend-plugin-api'; import { createExtensionTester } from '@backstage/frontend-test-utils'; describe('RouterBlueprint', () => { @@ -42,6 +42,7 @@ describe('RouterBlueprint', () => { "configSchema": undefined, "disabled": false, "factory": [Function], + "if": undefined, "inputs": {}, "kind": "app-router-component", "name": undefined, diff --git a/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.tsx b/plugins/app-react/src/blueprints/RouterBlueprint.tsx similarity index 81% rename from packages/frontend-plugin-api/src/blueprints/RouterBlueprint.tsx rename to plugins/app-react/src/blueprints/RouterBlueprint.tsx index c4b7c16bd6..dde2bc83e8 100644 --- a/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.tsx +++ b/plugins/app-react/src/blueprints/RouterBlueprint.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,13 +15,20 @@ */ import { ReactNode } from 'react'; -import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; +import { + createExtensionBlueprint, + createExtensionDataRef, +} from '@backstage/frontend-plugin-api'; const componentDataRef = createExtensionDataRef< (props: { children: ReactNode }) => JSX.Element | null >().with({ id: 'app.router.wrapper' }); -/** @public */ +/** + * Creates an extension that replaces the router component. This blueprint is limited to use by the app plugin. + * + * @public + */ export const RouterBlueprint = createExtensionBlueprint({ kind: 'app-router-component', attachTo: { id: 'app/root', input: 'router' }, diff --git a/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx b/plugins/app-react/src/blueprints/SignInPageBlueprint.test.tsx similarity index 98% rename from packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx rename to plugins/app-react/src/blueprints/SignInPageBlueprint.test.tsx index aa1f49c075..d7db57efad 100644 --- a/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx +++ b/plugins/app-react/src/blueprints/SignInPageBlueprint.test.tsx @@ -38,6 +38,7 @@ describe('SignInPageBlueprint', () => { "configSchema": undefined, "disabled": false, "factory": [Function], + "if": undefined, "inputs": {}, "kind": "sign-in-page", "name": undefined, diff --git a/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.tsx b/plugins/app-react/src/blueprints/SignInPageBlueprint.tsx similarity index 67% rename from packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.tsx rename to plugins/app-react/src/blueprints/SignInPageBlueprint.tsx index 5e4112a8d1..fd60947b0a 100644 --- a/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.tsx +++ b/plugins/app-react/src/blueprints/SignInPageBlueprint.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,17 +14,37 @@ * limitations under the License. */ -import { ComponentType, lazy } from 'react'; -import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; -import { SignInPageProps } from '@backstage/core-plugin-api'; -import { ExtensionBoundary } from '../components'; +import { ComponentType, lazy, ReactNode } from 'react'; +import { + createExtensionBlueprint, + createExtensionDataRef, + ExtensionBoundary, + IdentityApi, +} from '@backstage/frontend-plugin-api'; + +/** + * Props for the `SignInPage` component. + * + * @public + */ +export type SignInPageProps = { + /** + * Set the IdentityApi on successful sign-in. This should only be called once. + */ + onSignInSuccess(identityApi: IdentityApi): void; + + /** + * The children to render. + */ + children?: ReactNode; +}; const componentDataRef = createExtensionDataRef< ComponentType >().with({ id: 'core.sign-in-page.component' }); /** - * Creates an extension that replaces the sign in page. + * Creates an extension that replaces the sign in page. This blueprint is limited to use by the app plugin. * * @public */ diff --git a/packages/frontend-plugin-api/src/blueprints/SwappableComponentBlueprint.test.tsx b/plugins/app-react/src/blueprints/SwappableComponentBlueprint.test.tsx similarity index 97% rename from packages/frontend-plugin-api/src/blueprints/SwappableComponentBlueprint.test.tsx rename to plugins/app-react/src/blueprints/SwappableComponentBlueprint.test.tsx index 63436308ed..8d9f886c58 100644 --- a/packages/frontend-plugin-api/src/blueprints/SwappableComponentBlueprint.test.tsx +++ b/plugins/app-react/src/blueprints/SwappableComponentBlueprint.test.tsx @@ -14,9 +14,11 @@ * limitations under the License. */ import { renderTestApp } from '@backstage/frontend-test-utils'; -import { createSwappableComponent } from '../components'; +import { + createSwappableComponent, + PageBlueprint, +} from '@backstage/frontend-plugin-api'; import { SwappableComponentBlueprint } from './SwappableComponentBlueprint'; -import { PageBlueprint } from './PageBlueprint'; import { screen } from '@testing-library/react'; describe('SwappableComponentBlueprint', () => { diff --git a/packages/frontend-plugin-api/src/blueprints/SwappableComponentBlueprint.ts b/plugins/app-react/src/blueprints/SwappableComponentBlueprint.ts similarity index 89% rename from packages/frontend-plugin-api/src/blueprints/SwappableComponentBlueprint.ts rename to plugins/app-react/src/blueprints/SwappableComponentBlueprint.ts index b72d14d87f..801e258367 100644 --- a/packages/frontend-plugin-api/src/blueprints/SwappableComponentBlueprint.ts +++ b/plugins/app-react/src/blueprints/SwappableComponentBlueprint.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,12 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { SwappableComponentRef } from '../components'; + +import { SwappableComponentRef } from '@backstage/frontend-plugin-api'; import { createExtensionBlueprint, createExtensionBlueprintParams, createExtensionDataRef, -} from '../wiring'; +} from '@backstage/frontend-plugin-api'; export const componentDataRef = createExtensionDataRef<{ ref: SwappableComponentRef; @@ -28,7 +29,7 @@ export const componentDataRef = createExtensionDataRef<{ }>().with({ id: 'core.swappableComponent' }); /** - * Blueprint for creating swappable components from a SwappableComponentRef and a loader + * Blueprint for creating swappable components from a SwappableComponentRef and a loader. This blueprint is limited to use by the app plugin. * * @public */ diff --git a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.test.ts b/plugins/app-react/src/blueprints/ThemeBlueprint.test.ts similarity index 95% rename from packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.test.ts rename to plugins/app-react/src/blueprints/ThemeBlueprint.test.ts index 5ec164fa8b..5ab14e7617 100644 --- a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.test.ts +++ b/plugins/app-react/src/blueprints/ThemeBlueprint.test.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { AppTheme } from '@backstage/core-plugin-api'; +import { AppTheme } from '@backstage/frontend-plugin-api'; import { ThemeBlueprint } from './ThemeBlueprint'; import { createExtensionTester } from '@backstage/frontend-test-utils'; @@ -39,6 +39,7 @@ describe('ThemeBlueprint', () => { "configSchema": undefined, "disabled": false, "factory": [Function], + "if": undefined, "inputs": {}, "kind": "theme", "name": "light", diff --git a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts b/plugins/app-react/src/blueprints/ThemeBlueprint.ts similarity index 75% rename from packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts rename to plugins/app-react/src/blueprints/ThemeBlueprint.ts index 323078bc4e..559b442f4c 100644 --- a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts +++ b/plugins/app-react/src/blueprints/ThemeBlueprint.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,15 +14,18 @@ * limitations under the License. */ -import { AppTheme } from '@backstage/core-plugin-api'; -import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; +import { AppTheme } from '@backstage/frontend-plugin-api'; +import { + createExtensionBlueprint, + createExtensionDataRef, +} from '@backstage/frontend-plugin-api'; const themeDataRef = createExtensionDataRef().with({ id: 'core.theme.theme', }); /** - * Creates an extension that adds/replaces an app theme. + * Creates an extension that adds/replaces an app theme. This blueprint is limited to use by the app plugin. * * @public */ diff --git a/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.test.ts b/plugins/app-react/src/blueprints/TranslationBlueprint.test.ts similarity index 97% rename from packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.test.ts rename to plugins/app-react/src/blueprints/TranslationBlueprint.test.ts index 07e02c1562..47206e91b1 100644 --- a/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.test.ts +++ b/plugins/app-react/src/blueprints/TranslationBlueprint.test.ts @@ -17,7 +17,7 @@ import { createExtensionTester } from '@backstage/frontend-test-utils'; import { createTranslationMessages, createTranslationRef, -} from '../translation'; +} from '@backstage/frontend-plugin-api'; import { TranslationBlueprint } from './TranslationBlueprint'; describe('TranslationBlueprint', () => { @@ -54,6 +54,7 @@ describe('TranslationBlueprint', () => { "configSchema": undefined, "disabled": false, "factory": [Function], + "if": undefined, "inputs": {}, "kind": "translation", "name": "blob", diff --git a/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.ts b/plugins/app-react/src/blueprints/TranslationBlueprint.ts similarity index 78% rename from packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.ts rename to plugins/app-react/src/blueprints/TranslationBlueprint.ts index dd4a9ddc09..a45fb26b49 100644 --- a/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.ts +++ b/plugins/app-react/src/blueprints/TranslationBlueprint.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,15 +14,19 @@ * limitations under the License. */ -import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; -import { TranslationMessages, TranslationResource } from '../translation'; +import { + createExtensionBlueprint, + createExtensionDataRef, + TranslationMessages, + TranslationResource, +} from '@backstage/frontend-plugin-api'; const translationDataRef = createExtensionDataRef< TranslationResource | TranslationMessages >().with({ id: 'core.translation.translation' }); /** - * Creates an extension that adds translations to your app. + * Creates an extension that adds translations to your app. This blueprint is limited to use by the app plugin. * * @public */ diff --git a/plugins/app-react/src/blueprints/index.ts b/plugins/app-react/src/blueprints/index.ts new file mode 100644 index 0000000000..ad9226f2a6 --- /dev/null +++ b/plugins/app-react/src/blueprints/index.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { + AnalyticsImplementationBlueprint, + type AnalyticsImplementationFactory, +} from './AnalyticsImplementationBlueprint'; +export { AppRootWrapperBlueprint } from './AppRootWrapperBlueprint'; +export { IconBundleBlueprint } from './IconBundleBlueprint'; +export { NavContentBlueprint } from './NavContentBlueprint'; +export type { + NavContentComponent, + NavContentComponentProps, + NavContentNavItem, + NavContentNavItemsWithComponent, + NavContentNavItems, +} from './NavContentBlueprint'; +export { RouterBlueprint } from './RouterBlueprint'; +export { SignInPageBlueprint } from './SignInPageBlueprint'; +export type { SignInPageProps } from './SignInPageBlueprint'; +export { SwappableComponentBlueprint } from './SwappableComponentBlueprint'; +export { ThemeBlueprint } from './ThemeBlueprint'; +export { TranslationBlueprint } from './TranslationBlueprint'; diff --git a/plugins/app-react/src/index.ts b/plugins/app-react/src/index.ts new file mode 100644 index 0000000000..bcdb919775 --- /dev/null +++ b/plugins/app-react/src/index.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Web library for the app plugin. + * + * @packageDocumentation + */ + +export * from './blueprints'; diff --git a/plugins/app-react/src/setupTests.ts b/plugins/app-react/src/setupTests.ts new file mode 100644 index 0000000000..5f11bab1c2 --- /dev/null +++ b/plugins/app-react/src/setupTests.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import '@testing-library/jest-dom'; diff --git a/plugins/app-visualizer/CHANGELOG.md b/plugins/app-visualizer/CHANGELOG.md index bca80d09ff..ccaaf695f8 100644 --- a/plugins/app-visualizer/CHANGELOG.md +++ b/plugins/app-visualizer/CHANGELOG.md @@ -1,5 +1,189 @@ # @backstage/plugin-app-visualizer +## 0.2.2 + +### Patch Changes + +- e220589: Updated `PluginHeaderActionBlueprint` usage to pass params as a plain object. +- Updated dependencies + - @backstage/ui@0.14.0 + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/core-components@0.18.9 + - @backstage/core-plugin-api@1.12.5 + +## 0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + +## 0.2.2-next.0 + +### Patch Changes + +- e220589: Updated `PluginHeaderActionBlueprint` usage to pass params as a plain object. +- Updated dependencies + - @backstage/ui@0.14.0-next.0 + - @backstage/core-components@0.18.9-next.0 + - @backstage/frontend-plugin-api@0.15.2-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + +## 0.2.1 + +### Patch Changes + +- c25532a: Switched dev entry point to use `createDevApp` from `@backstage/frontend-dev-utils`. +- Updated dependencies + - @backstage/ui@0.13.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + +## 0.2.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.13.0-next.2 + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/core-components@0.18.8-next.1 + +## 0.2.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.13.0-next.1 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + +## 0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.12.1-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + +## 0.2.0 + +### Minor Changes + +- ef6916e: Migrated to use `SubPageBlueprint` for tabbed navigation and added a copy-tree-as-JSON plugin header action using `PluginHeaderActionBlueprint`. The plugin now specifies a `title` and `icon`. + +### Patch Changes + +- cb090b4: Bump react-aria-components to v1.14.0 +- c38b74d: Internal updates for blueprint moves to `@backstage/plugin-app-react`. +- 4137a43: Updated CSS token references to use renamed `--bui-border-2` token. +- 4d50e1f: Improved rendering performance of the details page. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/ui@0.12.0 + - @backstage/core-components@0.18.7 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/core-plugin-api@1.12.3 + +## 0.1.28-next.1 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/ui@0.12.0-next.2 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + +## 0.1.28-next.0 + +### Patch Changes + +- cb090b4: Bump react-aria-components to v1.14.0 +- c38b74d: Internal updates for blueprint moves to `@backstage/plugin-app-react`. +- 4d50e1f: Improved rendering performance of the details page. +- Updated dependencies + - @backstage/ui@0.12.0-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + +## 0.1.27 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.11.0 + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + +## 0.1.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + +## 0.1.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.11.0-next.0 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/frontend-plugin-api@0.13.2 + +## 0.1.26 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/ui@0.10.0 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + +## 0.1.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.10.0-next.1 + - @backstage/core-components@0.18.4-next.1 + +## 0.1.26-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/ui@0.9.1-next.0 + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/core-components@0.18.4-next.0 + +## 0.1.25 + +### Patch Changes + +- e81b3f0: Improve tree visualizer to use a horizontal layout and fill the content space. +- 722e2df: Migrated to use `@backstage/ui`. +- Updated dependencies + - @backstage/ui@0.9.0 + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + ## 0.1.25-next.2 ### Patch Changes diff --git a/plugins/app-visualizer/dev/index.ts b/plugins/app-visualizer/dev/index.ts index fa49e3360b..5a5222ffbf 100644 --- a/plugins/app-visualizer/dev/index.ts +++ b/plugins/app-visualizer/dev/index.ts @@ -14,12 +14,7 @@ * limitations under the License. */ -import ReactDOM from 'react-dom/client'; -import { createApp } from '@backstage/frontend-defaults'; +import { createDevApp } from '@backstage/frontend-dev-utils'; import { default as plugin } from '../src'; -const app = createApp({ - features: [plugin], -}); - -ReactDOM.createRoot(document.getElementById('root')!).render(app.createRoot()); +createDevApp({ features: [plugin] }); diff --git a/plugins/app-visualizer/knip-report.md b/plugins/app-visualizer/knip-report.md index 2661c35327..16e220d756 100644 --- a/plugins/app-visualizer/knip-report.md +++ b/plugins/app-visualizer/knip-report.md @@ -1,2 +1,8 @@ # Knip report +## Unused dependencies (1) + +| Name | Location | Severity | +| :------------------------- | :---------------- | :------- | +| @backstage/core-plugin-api | package.json:38:6 | error | + diff --git a/plugins/app-visualizer/package.json b/plugins/app-visualizer/package.json index 1d664e951d..dc200b406a 100644 --- a/plugins/app-visualizer/package.json +++ b/plugins/app-visualizer/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app-visualizer", - "version": "0.1.25-next.2", + "version": "0.2.2", "description": "Visualizes the Backstage app structure", "backstage": { "role": "frontend-plugin", @@ -39,21 +39,21 @@ "@backstage/frontend-plugin-api": "workspace:^", "@backstage/ui": "workspace:^", "@remixicon/react": "^4.6.0", - "react-aria-components": "^1.13.0" + "react-aria-components": "~1.17.0" }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/frontend-defaults": "workspace:^", + "@backstage/frontend-dev-utils": "workspace:^", "@types/react": "^18.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/plugins/app-visualizer/report.api.md b/plugins/app-visualizer/report.api.md index f3b7e6ae61..f8636d8b4e 100644 --- a/plugins/app-visualizer/report.api.md +++ b/plugins/app-visualizer/report.api.md @@ -4,8 +4,11 @@ ```ts import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; -import { IconComponent } from '@backstage/core-plugin-api'; +import { ExtensionInput } from '@backstage/frontend-plugin-api'; +import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; @@ -19,8 +22,12 @@ const visualizerPlugin: OverridableFrontendPlugin< 'nav-item:app-visualizer': OverridableExtensionDefinition<{ kind: 'nav-item'; name: undefined; - config: {}; - configInput: {}; + config: { + title: string | undefined; + }; + configInput: { + title?: string | undefined; + }; output: ExtensionDataRef< { title: string; @@ -42,24 +49,198 @@ const visualizerPlugin: OverridableFrontendPlugin< name: undefined; config: { path: string | undefined; + title: string | undefined; }; configInput: { path?: string | undefined; + title?: string | undefined; }; output: | ExtensionDataRef - | ExtensionDataRef | ExtensionDataRef< RouteRef, 'core.routing.ref', { optional: true; } + > + | ExtensionDataRef + | ExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >; + inputs: { + pages: ExtensionInput< + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >, + { + singleton: false; + optional: false; + internal: false; + } + >; + }; + params: { + path: string; + title?: string; + icon?: IconElement; + loader?: () => Promise; + routeRef?: RouteRef; + noHeader?: boolean; + }; + }>; + 'plugin-header-action:app-visualizer': OverridableExtensionDefinition<{ + kind: 'plugin-header-action'; + name: undefined; + config: {}; + configInput: {}; + output: ExtensionDataRef; + inputs: {}; + params: { + loader: () => Promise; + }; + }>; + 'sub-page:app-visualizer/details': OverridableExtensionDefinition<{ + kind: 'sub-page'; + name: 'details'; + config: { + path: string | undefined; + title: string | undefined; + }; + configInput: { + path?: string | undefined; + title?: string | undefined; + }; + output: + | ExtensionDataRef + | ExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + > + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } >; inputs: {}; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; + title: string; + icon?: IconElement; + loader: () => Promise; + routeRef?: RouteRef; + }; + }>; + 'sub-page:app-visualizer/text': OverridableExtensionDefinition<{ + kind: 'sub-page'; + name: 'text'; + config: { + path: string | undefined; + title: string | undefined; + }; + configInput: { + path?: string | undefined; + title?: string | undefined; + }; + output: + | ExtensionDataRef + | ExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + > + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >; + inputs: {}; + params: { + path: string; + title: string; + icon?: IconElement; + loader: () => Promise; + routeRef?: RouteRef; + }; + }>; + 'sub-page:app-visualizer/tree': OverridableExtensionDefinition<{ + kind: 'sub-page'; + name: 'tree'; + config: { + path: string | undefined; + title: string | undefined; + }; + configInput: { + path?: string | undefined; + title?: string | undefined; + }; + output: + | ExtensionDataRef + | ExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + > + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >; + inputs: {}; + params: { + path: string; + title: string; + icon?: IconElement; loader: () => Promise; routeRef?: RouteRef; }; diff --git a/plugins/app-visualizer/src/components/AppVisualizerPage/AppVisualizerPage.tsx b/plugins/app-visualizer/src/components/AppVisualizerPage/AppVisualizerPage.tsx index c005ccb18f..7bdd1e8900 100644 --- a/plugins/app-visualizer/src/components/AppVisualizerPage/AppVisualizerPage.tsx +++ b/plugins/app-visualizer/src/components/AppVisualizerPage/AppVisualizerPage.tsx @@ -15,8 +15,6 @@ */ import { Content, Header, HeaderTabs, Page } from '@backstage/core-components'; -import { useApi } from '@backstage/core-plugin-api'; -import { appTreeApiRef } from '@backstage/frontend-plugin-api'; import { Flex } from '@backstage/ui'; import { useCallback, useEffect, useMemo } from 'react'; import { DetailedVisualizer } from './DetailedVisualizer'; @@ -31,31 +29,28 @@ import { } from 'react-router-dom'; export function AppVisualizerPage() { - const appTreeApi = useApi(appTreeApiRef); - const { tree } = appTreeApi.getTree(); - const tabs = useMemo( () => [ { id: 'tree', path: 'tree', label: 'Tree', - element: , + element: , }, { id: 'detailed', path: 'detailed', label: 'Detailed', - element: , + element: , }, { id: 'text', path: 'text', label: 'Text', - element: , + element: , }, ], - [tree], + [], ); const location = useLocation(); diff --git a/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx b/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx index 1d38ec8267..e88f994627 100644 --- a/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx +++ b/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx @@ -16,21 +16,29 @@ import { AppNode, - AppTree, ExtensionDataRef, coreExtensionData, ApiBlueprint, NavItemBlueprint, - ThemeBlueprint, useApi, routeResolutionApiRef, + appTreeApiRef, } from '@backstage/frontend-plugin-api'; -import { Box, Flex, Link, Text, Tooltip, TooltipTrigger } from '@backstage/ui'; +import { + Box, + Flex, + FullPage, + Link, + Text, + Tooltip, + TooltipTrigger, +} from '@backstage/ui'; import { RiInputField as InputIcon, RiCloseCircleLine as DisabledIcon, } from '@remixicon/react'; import { Focusable } from 'react-aria-components'; +import { memo, useMemo, useState, useEffect, useRef, Fragment } from 'react'; function getContrastColor(bgColor: string): string { const hex = bgColor.replace('#', ''); @@ -78,7 +86,6 @@ const getOutputColor = createOutputColorGenerator( [coreExtensionData.routePath.id]: '#ffeb3b', [coreExtensionData.routeRef.id]: '#9c27b0', [ApiBlueprint.dataRefs.factory.id]: '#2196f3', - [ThemeBlueprint.dataRefs.theme.id]: '#cddc39', [NavItemBlueprint.dataRefs.target.id]: '#ff9800', }, @@ -105,131 +112,149 @@ function getFullPath(node?: AppNode): string { return getFullPath(parent) + part; } -function Output(props: { dataRef: ExtensionDataRef; node?: AppNode }) { - const { dataRef, node } = props; - const { id } = dataRef; - const instance = node?.instance; - - const routeResolutionApi = useApi(routeResolutionApiRef); - - const { backgroundColor, color } = getOutputColor(id); - - const chipStyle: React.CSSProperties = { - height: 20, - padding: '0 10px', - borderRadius: '10px', - color, - backgroundColor, - display: 'flex', - alignItems: 'center', - fontWeight: - 'var(--bui-font-weight-regular)' as React.CSSProperties['fontWeight'], - }; - - if (id === coreExtensionData.routeRef.id && node) { - try { - const routeRef = props.node?.instance?.getData( - coreExtensionData.routeRef, - ); - const link = routeRef && routeResolutionApi.resolve(routeRef)?.(); - if (link) { - return ( - - - link - - {id} - - ); - } - } catch { - /* ignore */ - } - } - - let tooltip = id; - let text: string | undefined = undefined; - if (id === coreExtensionData.routePath.id) { - text = String(instance?.getData(dataRef) ?? ''); - tooltip = getFullPath(node); - } - - return ( - - - {text} - - {tooltip} - - ); -} - -function Attachments(props: { - node: AppNode; - enabled: boolean; - depth: number; +function ProgressiveCollection({ + items, + batchSize = 10, +}: { + items: Array<() => React.ReactElement>; + batchSize?: number; }) { - const { node, depth } = props; - const { attachments } = node.edges; + const [maxIndex, setMaxIndex] = useState(0); + const timeoutRef = useRef | null>(null); - if (attachments.size === 0) { - return null; - } + useEffect(() => { + if (maxIndex >= items.length) { + return undefined; + } + + const processBatch = () => { + const nextIndex = Math.min(maxIndex + batchSize, items.length); + setMaxIndex(nextIndex); + + if (nextIndex < items.length) { + timeoutRef.current = setTimeout(processBatch, 0); + } + }; + + timeoutRef.current = setTimeout(processBatch, 0); + + // eslint-disable-next-line consistent-return + return () => { + if (timeoutRef.current !== null) { + clearTimeout(timeoutRef.current); + timeoutRef.current = null; + } + }; + }, [maxIndex, items.length, batchSize]); return ( - - {[...attachments.entries()] - .sort(([a], [b]) => a.localeCompare(b)) - .map(([key, children], idx) => { - return ( - - - -
    {key}
    -
    - - {children.map(childNode => ( - - ))} - -
    - ); - })} -
    + <> + {items.slice(0, maxIndex).map((item, index) => ( + {item()} + ))} + ); } -function Extension(props: { node: AppNode; depth: number }) { - const { node, depth } = props; +const Output = memo( + function Output(props: { + dataRef: ExtensionDataRef; + node?: AppNode; + }) { + const { dataRef, node } = props; + const { id } = dataRef; + const instance = node?.instance; + const routeResolutionApi = useApi(routeResolutionApiRef); + + const { backgroundColor, color } = getOutputColor(id); + + const chipStyle: React.CSSProperties = { + height: 20, + padding: '0 10px', + borderRadius: '10px', + color, + backgroundColor, + display: 'flex', + alignItems: 'center', + fontWeight: + 'var(--bui-font-weight-regular)' as React.CSSProperties['fontWeight'], + }; + + if (id === coreExtensionData.routeRef.id && node) { + try { + const routeRef = props.node?.instance?.getData( + coreExtensionData.routeRef, + ); + const link = routeRef && routeResolutionApi.resolve(routeRef)?.(); + if (link) { + return ( + + + link + + {id} + + ); + } + } catch { + /* ignore */ + } + } + + let tooltip = id; + let text: string | undefined = undefined; + if (id === coreExtensionData.routePath.id) { + text = String(instance?.getData(dataRef) ?? ''); + tooltip = getFullPath(node); + } + + return ( + + + {text} + + {tooltip} + + ); + }, + (prevProps, nextProps) => { + return ( + prevProps.dataRef.id === nextProps.dataRef.id && + prevProps.node?.spec.id === nextProps.node?.spec.id + ); + }, +); + +function Extension({ node, depth }: { node: AppNode; depth: number }) { const enabled = Boolean(node.instance); - const dataRefs = node.instance && [...node.instance.getDataRefs()]; - // Build tooltip text - const tooltipParts = []; - let currentNode = node; - tooltipParts.push(currentNode.spec.id); - while (currentNode.edges.attachedTo) { - const input = currentNode.edges.attachedTo.input; - currentNode = currentNode.edges.attachedTo.node; - tooltipParts.push(`${currentNode.spec.id} [${input}]`); - } - tooltipParts.reverse(); - const tooltipText = tooltipParts.join('\n'); + const tooltipText = useMemo(() => { + const tooltipParts = []; + let currentNode = node; + tooltipParts.push(currentNode.spec.id); + while (currentNode.edges.attachedTo) { + const input = currentNode.edges.attachedTo.input; + currentNode = currentNode.edges.attachedTo.node; + tooltipParts.push(`${currentNode.spec.id} [${input}]`); + } + tooltipParts.reverse(); + return tooltipParts.join('\n'); + }, [node]); + + const sortedDataRefs = useMemo(() => { + if (!node.instance) { + return []; + } + const dataRefs = [...node.instance.getDataRefs()]; + return dataRefs.sort((a, b) => a.id.localeCompare(b.id)); + }, [node.instance]); + + const sortedAttachments = useMemo(() => { + return [...node.edges.attachments.entries()].sort(([a], [b]) => + a.localeCompare(b), + ); + }, [node.edges.attachments]); return ( - {dataRefs && - dataRefs.length > 0 && - dataRefs - .sort((a, b) => a.id.localeCompare(b.id)) - .map(ref => )} + {sortedDataRefs.length > 0 && + sortedDataRefs.map(ref => ( + + ))} {!enabled && } - + {sortedAttachments.length > 0 && ( + + {sortedAttachments.map(([key, children], idx) => ( + + + +
    {key}
    +
    + + () => ( + + ))} + /> + +
    + ))} +
    + )}
    ); } @@ -280,7 +336,6 @@ const legendMap = { 'Route Path': coreExtensionData.routePath, 'Route Ref': coreExtensionData.routeRef, 'Nav Target': NavItemBlueprint.dataRefs.target, - Theme: ThemeBlueprint.dataRefs.theme, }; function Legend() { @@ -304,24 +359,29 @@ function Legend() { ); } -export function DetailedVisualizer({ tree }: { tree: AppTree }) { - return ( - - - - +export function DetailedVisualizer() { + const appTreeApi = useApi(appTreeApiRef); + const { tree } = appTreeApi.getTree(); - - - - + return ( + + + + + + + + + + + ); } diff --git a/plugins/app-visualizer/src/components/AppVisualizerPage/TextVisualizer.tsx b/plugins/app-visualizer/src/components/AppVisualizerPage/TextVisualizer.tsx index 82fd913bf2..954c1b7a5c 100644 --- a/plugins/app-visualizer/src/components/AppVisualizerPage/TextVisualizer.tsx +++ b/plugins/app-visualizer/src/components/AppVisualizerPage/TextVisualizer.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { AppNode, AppTree } from '@backstage/frontend-plugin-api'; +import { AppNode, useApi, appTreeApiRef } from '@backstage/frontend-plugin-api'; import { Box, Checkbox } from '@backstage/ui'; import { ReactNode, useState } from 'react'; @@ -77,7 +77,9 @@ function nodeToText( ]); } -export function TextVisualizer({ tree }: { tree: AppTree }) { +export function TextVisualizer() { + const appTreeApi = useApi(appTreeApiRef); + const { tree } = appTreeApi.getTree(); const [showOutputs, setShowOutputs] = useState(false); const [showDisabled, setShowDisabled] = useState(false); @@ -92,8 +94,8 @@ export function TextVisualizer({ tree }: { tree: AppTree }) { py="2" px="4" style={{ - background: 'var(--bui-bg-surface-1)', - borderTop: '1px solid var(--bui-border)', + background: 'var(--bui-bg-neutral-1)', + borderTop: '1px solid var(--bui-border-2)', }} > diff --git a/plugins/app-visualizer/src/components/AppVisualizerPage/TreeVisualizer.tsx b/plugins/app-visualizer/src/components/AppVisualizerPage/TreeVisualizer.tsx index d3a9145bb3..b851df0b26 100644 --- a/plugins/app-visualizer/src/components/AppVisualizerPage/TreeVisualizer.tsx +++ b/plugins/app-visualizer/src/components/AppVisualizerPage/TreeVisualizer.tsx @@ -18,8 +18,13 @@ import { DependencyGraph, DependencyGraphTypes, } from '@backstage/core-components'; -import { AppNode, AppTree } from '@backstage/frontend-plugin-api'; -import { Flex } from '@backstage/ui'; +import { + AppNode, + AppTree, + useApi, + appTreeApiRef, +} from '@backstage/frontend-plugin-api'; +import { Flex, FullPage } from '@backstage/ui'; import { useLayoutEffect, useMemo, useRef, useState } from 'react'; type NodeType = @@ -137,28 +142,25 @@ export function Node(props: { node: NodeType }) { ); } -export function TreeVisualizer({ tree }: { tree: AppTree }) { +export function TreeVisualizer() { + const appTreeApi = useApi(appTreeApiRef); + const { tree } = appTreeApi.getTree(); const graphData = useMemo(() => resolveGraphData(tree), [tree]); return ( - - - + + + + + ); } diff --git a/plugins/app-visualizer/src/components/CopyTreeButton.tsx b/plugins/app-visualizer/src/components/CopyTreeButton.tsx new file mode 100644 index 0000000000..4b430e65dc --- /dev/null +++ b/plugins/app-visualizer/src/components/CopyTreeButton.tsx @@ -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 { useState } from 'react'; +import { + useApi, + appTreeApiRef, + type AppNode, +} from '@backstage/frontend-plugin-api'; +import { Button } from '@backstage/ui'; +import { RiFileCopyLine, RiCheckLine } from '@remixicon/react'; + +function nodeToJson(node: AppNode): object { + const attachments: Record = {}; + for (const [input, children] of node.edges.attachments) { + attachments[input] = children.map(nodeToJson); + } + + return { + id: node.spec.id, + plugin: node.spec.plugin.pluginId, + disabled: node.spec.disabled || undefined, + ...(Object.keys(attachments).length > 0 ? { attachments } : {}), + }; +} + +export function CopyTreeButton() { + const appTreeApi = useApi(appTreeApiRef); + const [copied, setCopied] = useState(false); + + const handlePress = () => { + const { tree } = appTreeApi.getTree(); + const json = JSON.stringify(nodeToJson(tree.root), null, 2); + window.navigator.clipboard.writeText(json).then(() => { + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }); + }; + + return ( + + ); +} diff --git a/plugins/app-visualizer/src/plugin.tsx b/plugins/app-visualizer/src/plugin.tsx index c9c7857c77..fab8479c54 100644 --- a/plugins/app-visualizer/src/plugin.tsx +++ b/plugins/app-visualizer/src/plugin.tsx @@ -19,8 +19,10 @@ import { createRouteRef, NavItemBlueprint, PageBlueprint, + PluginHeaderActionBlueprint, + SubPageBlueprint, } from '@backstage/frontend-plugin-api'; -import { RiEyeLine as VisualizerIcon } from '@remixicon/react'; +import { RiEyeLine } from '@remixicon/react'; const rootRouteRef = createRouteRef(); @@ -28,17 +30,62 @@ const appVisualizerPage = PageBlueprint.make({ params: { path: '/visualizer', routeRef: rootRouteRef, + title: 'Visualizer', + }, +}); + +const treeRouteRef = createRouteRef(); +const detailedRouteRef = createRouteRef(); +const textRouteRef = createRouteRef(); + +const appVisualizerTreePage = SubPageBlueprint.make({ + name: 'tree', + params: { + path: 'tree', + routeRef: treeRouteRef, + title: 'Tree', loader: () => - import('./components/AppVisualizerPage').then(m => ( - + import('./components/AppVisualizerPage/TreeVisualizer').then(m => ( + )), }, }); +const appVisualizerDetailedPage = SubPageBlueprint.make({ + name: 'details', + params: { + path: 'details', + routeRef: detailedRouteRef, + title: 'Detailed', + loader: () => + import('./components/AppVisualizerPage/DetailedVisualizer').then(m => ( + + )), + }, +}); +const appVisualizerTextPage = SubPageBlueprint.make({ + name: 'text', + params: { + path: 'text', + routeRef: textRouteRef, + title: 'Text', + loader: () => + import('./components/AppVisualizerPage/TextVisualizer').then(m => ( + + )), + }, +}); + +const copyTreeAsJson = PluginHeaderActionBlueprint.make({ + params: { + loader: () => + import('./components/CopyTreeButton').then(m => ), + }, +}); export const appVisualizerNavItem = NavItemBlueprint.make({ params: { title: 'Visualizer', - icon: () => , + icon: () => , routeRef: rootRouteRef, }, }); @@ -46,6 +93,15 @@ export const appVisualizerNavItem = NavItemBlueprint.make({ /** @public */ export const visualizerPlugin = createFrontendPlugin({ pluginId: 'app-visualizer', + title: 'App Visualizer', + icon: , info: { packageJson: () => import('../package.json') }, - extensions: [appVisualizerPage, appVisualizerNavItem], + extensions: [ + appVisualizerPage, + appVisualizerTreePage, + appVisualizerDetailedPage, + appVisualizerTextPage, + appVisualizerNavItem, + copyTreeAsJson, + ], }); diff --git a/plugins/app/CHANGELOG.md b/plugins/app/CHANGELOG.md index c785f548ca..90d47f8c1f 100644 --- a/plugins/app/CHANGELOG.md +++ b/plugins/app/CHANGELOG.md @@ -1,5 +1,377 @@ # @backstage/plugin-app +## 0.4.3 + +### Patch Changes + +- effa7bf: Migrated `AppLanguageApi` extension to use the new `configSchema` option. +- e5baa20: Added support for configuring URL redirects on the `app/routes` extension. Redirects can be configured through `app-config` as an array of `{from, to}` path pairs, which will cause navigation to the `from` path to be redirected to the `to` path. + + For example: + + ```yaml + app: + extensions: + - app/routes: + config: + redirects: + - from: /old-path + to: /new-path + ``` + +- 9244b70: The default auth implementation now checks for a `logoutUrl` in the logout response body. If the auth provider returns one (e.g. Auth0 federated logout), the browser is redirected to that URL to clear the provider's session cookies. This is backward compatible — providers that return an empty response are unaffected. +- e4804ab: Updated the default `DialogApi` implementation to support the new `open` method. The dialog display layer no longer renders any dialog chrome — callers provide their own dialog component. The deprecated `show` and `showModal` methods now use `open` internally with a Material UI dialog wrapper for backward compatibility. +- d66a3ec: Updated the `PageLayout` swap to pass a clickable `titleLink` on the `PluginHeader`, resolved from the plugin's root route ref. +- Updated dependencies + - @backstage/ui@0.14.0 + - @backstage/theme@0.7.3 + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/core-components@0.18.9 + - @backstage/filter-predicates@0.1.2 + - @backstage/plugin-permission-react@0.5.0 + - @backstage/core-plugin-api@1.12.5 + - @backstage/integration-react@1.2.17 + - @backstage/plugin-app-react@0.2.2 + +## 0.4.3-next.2 + +### Patch Changes + +- 9244b70: The default auth implementation now checks for a `logoutUrl` in the logout response body. If the auth provider returns one (e.g. Auth0 federated logout), the browser is redirected to that URL to clear the provider's session cookies. This is backward compatible — providers that return an empty response are unaffected. +- Updated dependencies + - @backstage/ui@0.14.0-next.2 + - @backstage/theme@0.7.3-next.0 + - @backstage/core-components@0.18.9-next.1 + - @backstage/core-plugin-api@1.12.5-next.2 + - @backstage/filter-predicates@0.1.2-next.0 + - @backstage/frontend-plugin-api@0.16.0-next.2 + - @backstage/integration-react@1.2.17-next.1 + - @backstage/plugin-permission-react@0.4.42-next.1 + +## 0.4.3-next.1 + +### Patch Changes + +- e5baa20: Added support for configuring URL redirects on the `app/routes` extension. Redirects can be configured through `app-config` as an array of `{from, to}` path pairs, which will cause navigation to the `from` path to be redirected to the `to` path. + + For example: + + ```yaml + app: + extensions: + - app/routes: + config: + redirects: + - from: /old-path + to: /new-path + ``` + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-app-react@0.2.2-next.1 + +## 0.4.3-next.0 + +### Patch Changes + +- d66a3ec: Updated the `PageLayout` swap to pass a clickable `titleLink` on the `PluginHeader`, resolved from the plugin's root route ref. +- Updated dependencies + - @backstage/ui@0.14.0-next.0 + - @backstage/core-components@0.18.9-next.0 + - @backstage/frontend-plugin-api@0.15.2-next.0 + - @backstage/integration-react@1.2.17-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + - @backstage/plugin-app-react@0.2.2-next.0 + - @backstage/filter-predicates@0.1.1 + - @backstage/theme@0.7.2 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-permission-react@0.4.42-next.0 + +## 0.4.1 + +### Patch Changes + +- 5f3f5d2: `NavContentBlueprint` nav item collections now keep previously collected `rest()` results in sync when additional items are taken later in the same render, making it easier to place items across multiple sidebar sections. +- aa29b50: Pages created with `PageBlueprint` now render the plugin header by default in the new frontend system. +- c0ab376: The app nav now falls back to `plugin.icon` for navigation items that don't have an explicit icon set. +- 12d8afe: Added `BUIProvider` from `@backstage/ui` to the app root, enabling BUI components to fire analytics events through the Backstage analytics system. +- 5fec07d: Updated the default app root to better support phased app preparation by allowing the app layout to be absent during bootstrap, routing bootstrap failures through the app root boundary, and avoiding installation of a guest identity in protected apps that do not provide a sign-in page. +- 9508514: Updated the default `PluginWrapperApi` implementation to support the new `useWrapperValue` hook and root wrapper. The root wrapper is now rendered in the app root to manage shared hook state across plugin wrapper instances. +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- 42f8c9b: Moved `BUIProvider` inside the app router to enable automatic client-side routing for all BUI components. +- 909c742: Switched translation API imports (`translationApiRef`, `appLanguageApiRef`) from the alpha `@backstage/core-plugin-api/alpha` path to the stable `@backstage/frontend-plugin-api` export. This has no effect on runtime behavior. +- 7e743f4: Introduced a new `ToastApi` for displaying rich toast notifications in the new frontend system. + + The new `ToastApi` provides enhanced notification capabilities compared to the existing `AlertApi`: + + - **Title and Description**: Toasts support both a title and an optional description + - **Custom Timeouts**: Each toast can specify its own timeout duration + - **Links**: Toasts can include action links + - **Status Variants**: Support for neutral, info, success, warning, and danger statuses + - **Programmatic Dismiss**: Toasts can be dismissed programmatically using the `close()` handle returned from `post()` + + **Usage:** + + ```typescript + import { toastApiRef, useApi } from '@backstage/frontend-plugin-api'; + + const toastApi = useApi(toastApiRef); + + // Full-featured toast + toastApi.post({ + title: 'Entity saved', + description: 'Your changes have been saved successfully.', + status: 'success', + timeout: 5000, + links: [{ label: 'View entity', href: '/catalog/entity' }], + }); + + // Programmatic dismiss + const { close } = toastApi.post({ title: 'Uploading...', status: 'info' }); + // Later... + close(); + ``` + + The `ToastDisplay` component subscribes to both `ToastApi` and `AlertApi`, providing a migration path where both systems work side by side until `AlertApi` is fully deprecated. + +- Updated dependencies + - @backstage/ui@0.13.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/plugin-app-react@0.2.1 + - @backstage/plugin-permission-react@0.4.41 + - @backstage/filter-predicates@0.1.1 + - @backstage/integration-react@1.2.16 + +## 0.4.1-next.2 + +### Patch Changes + +- 12d8afe: Added `BUIProvider` from `@backstage/ui` to the app root, enabling BUI components to fire analytics events through the Backstage analytics system. +- Updated dependencies + - @backstage/ui@0.13.0-next.2 + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/core-components@0.18.8-next.1 + - @backstage/plugin-app-react@0.2.1-next.1 + +## 0.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.13.0-next.1 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/integration-react@1.2.16-next.1 + - @backstage/theme@0.7.2 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-app-react@0.2.1-next.0 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## 0.4.1-next.0 + +### Patch Changes + +- 909c742: Switched translation API imports (`translationApiRef`, `appLanguageApiRef`) from the alpha `@backstage/core-plugin-api/alpha` path to the stable `@backstage/frontend-plugin-api` export. This has no effect on runtime behavior. +- Updated dependencies + - @backstage/ui@0.12.1-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/integration-react@1.2.16-next.0 + - @backstage/theme@0.7.2 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-app-react@0.2.1-next.0 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## 0.4.0 + +### Minor Changes + +- ef6916e: Added `SubPageBlueprint` for creating sub-page tabs, `PluginHeaderActionBlueprint` and `PluginHeaderActionsApi` for plugin-scoped header actions, and `PageLayout` as a swappable component. The `PageBlueprint` now supports sub-pages with tabbed navigation, page title, icon, and header actions. Plugins can now specify a `title` and `icon` in `createFrontendPlugin`. +- 7edb810: **BREAKING**: Extensions created with the following blueprints must now be provided via an override or a module for the `app` plugin. Extensions from other plugins will now trigger a warning in the app and be ignored. + + - `IconBundleBlueprint` + - `NavContentBlueprint` + - `RouterBlueprint` + - `SignInPageBlueprint` + - `SwappableComponentBlueprint` + - `ThemeBlueprint` + - `TranslationBlueprint` + +### Patch Changes + +- a2133be: Added new `NavContentNavItem`, `NavContentNavItems`, and `navItems` prop to `NavContentComponentProps` for auto-discovering navigation items from page extensions. The new `navItems` collection supports `take(id)` and `rest()` methods for placing specific items in custom sidebar positions, as well as `withComponent(Component)` which returns a `NavContentNavItemsWithComponent` for rendering items directly as elements. The existing `items` prop is now deprecated in favor of `navItems`. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/ui@0.12.0 + - @backstage/core-components@0.18.7 + - @backstage/theme@0.7.2 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/plugin-app-react@0.2.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/integration-react@1.2.15 + - @backstage/plugin-permission-react@0.4.40 + - @backstage/version-bridge@1.0.12 + +## 0.4.0-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/integration-react@1.2.15-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-permission-react@0.4.40-next.1 + - @backstage/version-bridge@1.0.12-next.0 + - @backstage/theme@0.7.2-next.1 + - @backstage/plugin-app-react@0.1.1-next.0 + +## 0.4.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.7.2-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/core-components@0.18.7-next.1 + - @backstage/integration-react@1.2.15-next.1 + +## 0.4.0-next.0 + +### Minor Changes + +- 7edb810: **BREAKING**: Extensions created with the following blueprints must now be provided via an override or a module for the `app` plugin. Extensions from other plugins will now trigger a warning in the app and be ignored. + + - `IconBundleBlueprint` + - `NavContentBlueprint` + - `RouterBlueprint` + - `SignInPageBlueprint` + - `SwappableComponentBlueprint` + - `ThemeBlueprint` + - `TranslationBlueprint` + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/core-components@0.18.6-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/plugin-app-react@0.1.1-next.0 + - @backstage/integration-react@1.2.15-next.0 + - @backstage/theme@0.7.1 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-permission-react@0.4.40-next.0 + +## 0.3.4 + +### Patch Changes + +- 4554a4e: Implemented support for the new `PluginWrapperBlueprint` from `@backstage/frontend-plugin-api/alpha`. +- 9ccf84e: The following blueprints are being restricted to only be used in app plugin overrides and modules. They will now produce a deprecation warning when used outside of the app plugin: + + - `AppRootWrapperBlueprint` + - `IconBundleBlueprint` + - `NavContentBlueprint` + - `RouterBlueprint` + - `SignInPageBlueprint` + - `SwappableComponentBlueprint` + - `ThemeBlueprint` + - `TranslationBlueprint` + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/plugin-app-react@0.1.0 + - @backstage/core-components@0.18.5 + - @backstage/integration-react@1.2.14 + +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/integration-react@1.2.14-next.0 + +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-react@1.2.14-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## 0.3.3 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- f7bc228: Support to set `defaultLanguage` and `availableLanguages` for the app language API in the new frontend system +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-permission-react@0.4.39 + +## 0.3.3-next.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- f7bc228: Support to set `defaultLanguage` and `availableLanguages` for the app language API in the new frontend system +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/core-components@0.18.4-next.2 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-permission-react@0.4.39-next.0 + +## 0.3.3-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## 0.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/integration-react@1.2.12 + ## 0.3.2-next.0 ### Patch Changes diff --git a/plugins/app/knip-report.md b/plugins/app/knip-report.md index 7b3dcb2e34..ea56eef3cf 100644 --- a/plugins/app/knip-report.md +++ b/plugins/app/knip-report.md @@ -1,18 +1,17 @@ # Knip report -## Unused dependencies (4) +## Unused dependencies (3) -| Name | Location | Severity | -| :------------------------ | :----------- | :------- | -| @backstage/version-bridge | plugins/app/package.json | error | -| @material-ui/lab | plugins/app/package.json | error | -| react-use | plugins/app/package.json | error | -| zod | plugins/app/package.json | error | +| Name | Location | Severity | +| :--------------------------- | :---------------- | :------- | +| @backstage/filter-predicates | package.json:56:6 | error | +| @backstage/version-bridge | package.json:64:6 | error | +| @material-ui/lab | package.json:67:6 | error | ## Unused devDependencies (2) -| Name | Location | Severity | -| :------------------- | :----------- | :------- | -| @backstage/dev-utils | plugins/app/package.json | error | -| msw | plugins/app/package.json | error | +| Name | Location | Severity | +| :------------------- | :---------------- | :------- | +| @backstage/dev-utils | package.json:79:6 | error | +| msw | package.json:87:6 | error | diff --git a/plugins/app/package.json b/plugins/app/package.json index 403ee9095c..91e3460300 100644 --- a/plugins/app/package.json +++ b/plugins/app/package.json @@ -1,13 +1,14 @@ { "name": "@backstage/plugin-app", - "version": "0.3.2-next.0", + "version": "0.4.3", "backstage": { "role": "frontend-plugin", "pluginId": "app", "pluginPackages": [ "@backstage/plugin-app", "@backstage/plugin-app-backend", - "@backstage/plugin-app-node" + "@backstage/plugin-app-node", + "@backstage/plugin-app-react" ] }, "publishConfig": { @@ -52,18 +53,26 @@ "dependencies": { "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", + "@backstage/filter-predicates": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", "@backstage/integration-react": "workspace:^", + "@backstage/plugin-app-react": "workspace:^", "@backstage/plugin-permission-react": "workspace:^", "@backstage/theme": "workspace:^", "@backstage/types": "workspace:^", + "@backstage/ui": "workspace:^", "@backstage/version-bridge": "workspace:^", "@material-ui/core": "^4.9.13", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "^4.0.0-alpha.61", "@react-hookz/web": "^24.0.0", + "@remixicon/react": "^4.6.0", + "motion": "^12.0.0", + "react-aria": "~3.48.0", + "react-stately": "~3.46.0", "react-use": "^17.2.4", - "zod": "^3.22.4" + "zen-observable": "^0.10.0", + "zod": "^4.0.0" }, "devDependencies": { "@backstage/cli": "workspace:^", @@ -78,13 +87,13 @@ "msw": "^1.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/plugins/app/report.api.md b/plugins/app/report.api.md index bd8c05bfe4..ea1391d4a4 100644 --- a/plugins/app/report.api.md +++ b/plugins/app/report.api.md @@ -13,15 +13,16 @@ import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; -import { IconComponent } from '@backstage/core-plugin-api'; -import { IconComponent as IconComponent_2 } from '@backstage/frontend-plugin-api'; +import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; -import { NavContentComponent } from '@backstage/frontend-plugin-api'; +import { NavContentComponent } from '@backstage/plugin-app-react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; +import { PluginWrapperDefinition } from '@backstage/frontend-plugin-api'; import { ReactNode } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; -import { SignInPageProps } from '@backstage/core-plugin-api'; +import { SignInPageProps } from '@backstage/plugin-app-react'; import { SwappableComponentRef } from '@backstage/frontend-plugin-api'; import { TranslationMessages } from '@backstage/frontend-plugin-api'; import { TranslationResource } from '@backstage/frontend-plugin-api'; @@ -41,6 +42,7 @@ const appPlugin: OverridableFrontendPlugin< { singleton: true; optional: false; + internal: false; } >; }; @@ -58,6 +60,7 @@ const appPlugin: OverridableFrontendPlugin< { singleton: true; optional: false; + internal: false; } >; content: ExtensionInput< @@ -65,6 +68,7 @@ const appPlugin: OverridableFrontendPlugin< { singleton: true; optional: false; + internal: false; } >; }; @@ -90,6 +94,7 @@ const appPlugin: OverridableFrontendPlugin< { singleton: false; optional: false; + internal: false; } >; content: ExtensionInput< @@ -101,6 +106,7 @@ const appPlugin: OverridableFrontendPlugin< { singleton: true; optional: true; + internal: true; } >; }; @@ -122,6 +128,7 @@ const appPlugin: OverridableFrontendPlugin< { singleton: true; optional: true; + internal: true; } >; signInPage: ExtensionInput< @@ -133,13 +140,15 @@ const appPlugin: OverridableFrontendPlugin< { singleton: true; optional: true; + internal: true; } >; children: ExtensionInput< ConfigurableExtensionDataRef, { singleton: true; - optional: false; + optional: true; + internal: false; } >; elements: ExtensionInput< @@ -147,6 +156,7 @@ const appPlugin: OverridableFrontendPlugin< { singleton: false; optional: false; + internal: false; } >; wrappers: ExtensionInput< @@ -158,6 +168,7 @@ const appPlugin: OverridableFrontendPlugin< { singleton: false; optional: false; + internal: true; } >; }; @@ -166,8 +177,22 @@ const appPlugin: OverridableFrontendPlugin< name: 'root'; }>; 'app/routes': OverridableExtensionDefinition<{ - config: {}; - configInput: {}; + config: { + redirects: + | { + from: string; + to: string; + }[] + | undefined; + }; + configInput: { + redirects?: + | { + from: string; + to: string; + }[] + | undefined; + }; output: ExtensionDataRef; inputs: { routes: ExtensionInput< @@ -183,6 +208,7 @@ const appPlugin: OverridableFrontendPlugin< { singleton: false; optional: false; + internal: false; } >; }; @@ -219,6 +245,7 @@ const appPlugin: OverridableFrontendPlugin< { singleton: false; optional: false; + internal: false; } >; }; @@ -233,12 +260,18 @@ const appPlugin: OverridableFrontendPlugin< ) => ExtensionBlueprintParams; }>; 'api:app/app-language': OverridableExtensionDefinition<{ - kind: 'api'; - name: 'app-language'; - config: {}; - configInput: {}; + config: { + defaultLanguage: string | undefined; + availableLanguages: string[] | undefined; + }; + configInput: { + defaultLanguage?: string | undefined; + availableLanguages?: string[] | undefined; + }; output: ExtensionDataRef; inputs: {}; + kind: 'api'; + name: 'app-language'; params: < TApi, TImpl extends TApi, @@ -257,6 +290,7 @@ const appPlugin: OverridableFrontendPlugin< { singleton: false; optional: false; + internal: true; } >; }; @@ -458,7 +492,7 @@ const appPlugin: OverridableFrontendPlugin< icons: ExtensionInput< ConfigurableExtensionDataRef< { - [x: string]: IconComponent_2; + [x: string]: IconComponent | IconElement; }, 'core.icons', {} @@ -466,6 +500,7 @@ const appPlugin: OverridableFrontendPlugin< { singleton: false; optional: false; + internal: true; } >; }; @@ -569,6 +604,58 @@ const appPlugin: OverridableFrontendPlugin< params: ApiFactory, ) => ExtensionBlueprintParams; }>; + 'api:app/plugin-header-actions': OverridableExtensionDefinition<{ + config: {}; + configInput: {}; + output: ExtensionDataRef; + inputs: { + actions: ExtensionInput< + ConfigurableExtensionDataRef, + { + singleton: false; + optional: false; + internal: false; + } + >; + }; + kind: 'api'; + name: 'plugin-header-actions'; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; + }>; + 'api:app/plugin-wrapper': OverridableExtensionDefinition<{ + config: {}; + configInput: {}; + output: ExtensionDataRef; + inputs: { + wrappers: ExtensionInput< + ConfigurableExtensionDataRef< + () => Promise, + 'core.plugin-wrapper.loader', + {} + >, + { + singleton: false; + optional: false; + internal: false; + } + >; + }; + kind: 'api'; + name: 'plugin-wrapper'; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; + }>; 'api:app/scm-auth': OverridableExtensionDefinition<{ kind: 'api'; name: 'scm-auth'; @@ -633,6 +720,7 @@ const appPlugin: OverridableFrontendPlugin< { singleton: false; optional: false; + internal: true; } >; }; @@ -646,6 +734,36 @@ const appPlugin: OverridableFrontendPlugin< params: ApiFactory, ) => ExtensionBlueprintParams; }>; + 'api:app/toast': OverridableExtensionDefinition<{ + kind: 'api'; + name: 'toast'; + config: {}; + configInput: {}; + output: ExtensionDataRef; + inputs: {}; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; + }>; + 'api:app/toast-forwarder': OverridableExtensionDefinition<{ + kind: 'api'; + name: 'toast-forwarder'; + config: {}; + configInput: {}; + output: ExtensionDataRef; + inputs: {}; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; + }>; 'api:app/translations': OverridableExtensionDefinition<{ config: {}; configInput: {}; @@ -667,6 +785,7 @@ const appPlugin: OverridableFrontendPlugin< { singleton: false; optional: false; + internal: true; } >; }; @@ -699,29 +818,21 @@ const appPlugin: OverridableFrontendPlugin< config: { transientTimeoutMs: number; anchorOrigin: { - horizontal: 'center' | 'left' | 'right'; vertical: 'top' | 'bottom'; + horizontal: 'center' | 'left' | 'right'; }; }; configInput: { + transientTimeoutMs?: number | undefined; anchorOrigin?: | { - horizontal?: 'center' | 'left' | 'right' | undefined; vertical?: 'top' | 'bottom' | undefined; + horizontal?: 'center' | 'left' | 'right' | undefined; } | undefined; - transientTimeoutMs?: number | undefined; }; output: ExtensionDataRef; - inputs: { - [x: string]: ExtensionInput< - ExtensionDataRef, - { - singleton: boolean; - optional: boolean; - } - >; - }; + inputs: {}; kind: 'app-root-element'; name: 'alert-display'; params: { @@ -732,15 +843,7 @@ const appPlugin: OverridableFrontendPlugin< config: {}; configInput: {}; output: ExtensionDataRef; - inputs: { - [x: string]: ExtensionInput< - ExtensionDataRef, - { - singleton: boolean; - optional: boolean; - } - >; - }; + inputs: {}; kind: 'app-root-element'; name: 'dialog-display'; params: { @@ -870,6 +973,62 @@ const appPlugin: OverridableFrontendPlugin< : never; }>; }>; + 'component:app/core-page-layout': OverridableExtensionDefinition<{ + kind: 'component'; + name: 'core-page-layout'; + config: {}; + configInput: {}; + output: ExtensionDataRef< + { + ref: SwappableComponentRef; + loader: + | (() => (props: {}) => JSX.Element | null) + | (() => Promise<(props: {}) => JSX.Element | null>); + }, + 'core.swappableComponent', + {} + >; + inputs: {}; + params: >(params: { + component: Ref extends SwappableComponentRef< + any, + infer IExternalComponentProps + > + ? { + ref: Ref; + } & ((props: IExternalComponentProps) => JSX.Element | null) + : never; + loader: Ref extends SwappableComponentRef< + infer IInnerComponentProps, + any + > + ? + | (() => (props: IInnerComponentProps) => JSX.Element | null) + | (() => Promise< + (props: IInnerComponentProps) => JSX.Element | null + >) + : never; + }) => ExtensionBlueprintParams<{ + component: Ref extends SwappableComponentRef< + any, + infer IExternalComponentProps + > + ? { + ref: Ref; + } & ((props: IExternalComponentProps) => JSX.Element | null) + : never; + loader: Ref extends SwappableComponentRef< + infer IInnerComponentProps, + any + > + ? + | (() => (props: IInnerComponentProps) => JSX.Element | null) + | (() => Promise< + (props: IInnerComponentProps) => JSX.Element | null + >) + : never; + }>; + }>; 'component:app/core-progress': OverridableExtensionDefinition<{ kind: 'component'; name: 'core-progress'; diff --git a/plugins/app/src/alpha/appModulePublicSignIn.test.tsx b/plugins/app/src/alpha/appModulePublicSignIn.test.tsx index 787b8949e5..c657eaa633 100644 --- a/plugins/app/src/alpha/appModulePublicSignIn.test.tsx +++ b/plugins/app/src/alpha/appModulePublicSignIn.test.tsx @@ -14,10 +14,8 @@ * limitations under the License. */ -import { - SignInPageBlueprint, - createFrontendModule, -} from '@backstage/frontend-plugin-api'; +import { createFrontendModule } from '@backstage/frontend-plugin-api'; +import { SignInPageBlueprint } from '@backstage/plugin-app-react'; import { render, screen, waitFor } from '@testing-library/react'; import { useEffect } from 'react'; import { appModulePublicSignIn } from './appModulePublicSignIn'; @@ -102,6 +100,14 @@ describe('appModulePublicSignIn', () => { data-unified-theme-stack="[{"mode":"light","name":"backstage"}]" >
    +
    }) => React.JSX.Element; - modal: boolean; -}) => DialogApiDialog; - -/** - * Default implementation for the {@link DialogApi}. - * @internal - */ -export class DefaultDialogApi implements DialogApi { - #onShow?: OnShowDialog; - - show( - elementOrComponent: - | JSX.Element - | ((props: { - dialog: DialogApiDialog; - }) => JSX.Element), - ): DialogApiDialog { - if (!this.#onShow) { - throw new Error('Dialog API has not been connected'); - } - return this.#onShow({ - component: - typeof elementOrComponent === 'function' - ? elementOrComponent - : () => elementOrComponent, - modal: false, - }) as DialogApiDialog; - } - - showModal( - elementOrComponent: - | JSX.Element - | ((props: { dialog: DialogApiDialog }) => JSX.Element), - ): DialogApiDialog { - if (!this.#onShow) { - throw new Error('Dialog API has not been connected'); - } - return this.#onShow({ - component: - typeof elementOrComponent === 'function' - ? elementOrComponent - : () => elementOrComponent, - modal: true, - }) as DialogApiDialog; - } - - connect(onShow: OnShowDialog): void { - this.#onShow = onShow; - } -} diff --git a/plugins/app/src/apis/DefaultDialogApi.tsx b/plugins/app/src/apis/DefaultDialogApi.tsx new file mode 100644 index 0000000000..c683cc5293 --- /dev/null +++ b/plugins/app/src/apis/DefaultDialogApi.tsx @@ -0,0 +1,141 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DialogApi, DialogApiDialog } from '@backstage/frontend-plugin-api'; +import Dialog from '@material-ui/core/Dialog'; + +export type OnOpenDialog = (options: { + component: (props: { dialog: DialogApiDialog }) => JSX.Element; +}) => DialogApiDialog; + +/** + * Default implementation for the {@link DialogApi}. + * @internal + */ +export class DefaultDialogApi implements DialogApi { + #onOpen?: OnOpenDialog; + + open( + elementOrComponent: + | JSX.Element + | ((props: { dialog: DialogApiDialog }) => JSX.Element), + ): DialogApiDialog { + if (!this.#onOpen) { + throw new Error('Dialog API has not been connected'); + } + return this.#onOpen({ + component: + typeof elementOrComponent === 'function' + ? elementOrComponent + : () => elementOrComponent, + }) as DialogApiDialog; + } + + /** @deprecated Use {@link DefaultDialogApi.open} instead */ + show( + elementOrComponent: + | JSX.Element + | ((props: { + dialog: DialogApiDialog; + }) => JSX.Element), + ): DialogApiDialog { + // eslint-disable-next-line no-console + console.warn( + 'DialogApi.show() is deprecated and will be removed in a future release. Use DialogApi.open() instead.', + ); + const innerDialog = this.open(({ dialog }) => ( + + )); + return wrapDialogHandle(innerDialog, false); + } + + /** @deprecated Use {@link DefaultDialogApi.open} instead */ + showModal( + elementOrComponent: + | JSX.Element + | ((props: { dialog: DialogApiDialog }) => JSX.Element), + ): DialogApiDialog { + // eslint-disable-next-line no-console + console.warn( + 'DialogApi.showModal() is deprecated and will be removed in a future release. Use DialogApi.open() instead.', + ); + const innerDialog = this.open(({ dialog }) => ( + + )); + return wrapDialogHandle(innerDialog, true); + } + + connect(onOpen: OnOpenDialog): void { + this.#onOpen = onOpen; + } +} + +function DeprecatedMuiDialogWrapper({ + dialog, + content, + modal, +}: { + dialog: DialogApiDialog; + content: + | JSX.Element + | ((props: { dialog: DialogApiDialog }) => JSX.Element); + modal: boolean; +}) { + if (typeof content === 'function') { + const Content = content; + return ( + dialog.close()}> + + + ); + } + return ( + dialog.close()}> + {content} + + ); +} + +function wrapDialogHandle( + innerDialog: DialogApiDialog, + modal: boolean, +): DialogApiDialog { + return { + close(...args: any[]) { + (innerDialog.close as any)(...args); + }, + result() { + return innerDialog.result(); + }, + update(newContent: any) { + innerDialog.update(({ dialog }: { dialog: DialogApiDialog }) => ( + + )); + }, + }; +} diff --git a/plugins/app/src/apis/PluginHeaderActionsApi/DefaultPluginHeaderActionsApi.test.tsx b/plugins/app/src/apis/PluginHeaderActionsApi/DefaultPluginHeaderActionsApi.test.tsx new file mode 100644 index 0000000000..9023c86ce8 --- /dev/null +++ b/plugins/app/src/apis/PluginHeaderActionsApi/DefaultPluginHeaderActionsApi.test.tsx @@ -0,0 +1,72 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { render, screen } from '@testing-library/react'; +import { DefaultPluginHeaderActionsApi } from './DefaultPluginHeaderActionsApi'; + +describe('DefaultPluginHeaderActionsApi', () => { + it('should return actions for a specific plugin', () => { + const api = DefaultPluginHeaderActionsApi.fromActions([ + { + element: , + pluginId: 'plugin-a', + }, + { + element: , + pluginId: 'plugin-b', + }, + ]); + + expect(api.getPluginHeaderActions('plugin-a')).toHaveLength(1); + expect(api.getPluginHeaderActions('plugin-b')).toHaveLength(1); + + render(<>{api.getPluginHeaderActions('plugin-a')}); + expect( + screen.getByRole('button', { name: 'Action A' }), + ).toBeInTheDocument(); + }); + + it('should return an empty array for unknown plugins', () => { + const api = DefaultPluginHeaderActionsApi.fromActions([ + { + element: Action, + pluginId: 'plugin-a', + }, + ]); + + expect(api.getPluginHeaderActions('unknown-plugin')).toEqual([]); + }); + + it('should group multiple actions by plugin', () => { + const api = DefaultPluginHeaderActionsApi.fromActions([ + { + element: , + pluginId: 'plugin-a', + }, + { + element: , + pluginId: 'plugin-a', + }, + ]); + + const actions = api.getPluginHeaderActions('plugin-a'); + expect(actions).toHaveLength(2); + + render(<>{actions}); + expect(screen.getByRole('button', { name: 'First' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Second' })).toBeInTheDocument(); + }); +}); diff --git a/plugins/app/src/apis/PluginHeaderActionsApi/DefaultPluginHeaderActionsApi.tsx b/plugins/app/src/apis/PluginHeaderActionsApi/DefaultPluginHeaderActionsApi.tsx new file mode 100644 index 0000000000..e566b84ecc --- /dev/null +++ b/plugins/app/src/apis/PluginHeaderActionsApi/DefaultPluginHeaderActionsApi.tsx @@ -0,0 +1,66 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { cloneElement, JSX } from 'react'; +import { type PluginHeaderActionsApi } from '@backstage/frontend-plugin-api'; + +// Stable reference +const EMPTY_ACTIONS = new Array(); + +type ActionInput = { + element: JSX.Element; + pluginId: string; +}; + +/** + * Default implementation of PluginHeaderActionsApi. + * + * @internal + */ +export class DefaultPluginHeaderActionsApi implements PluginHeaderActionsApi { + constructor( + private readonly actionsByPlugin: Map>, + ) {} + + getPluginHeaderActions(pluginId: string): Array { + return this.actionsByPlugin.get(pluginId) ?? EMPTY_ACTIONS; + } + + static fromActions( + actions: Array, + ): DefaultPluginHeaderActionsApi { + const actionsByPlugin = new Map>(); + + for (const action of actions) { + let pluginActions = actionsByPlugin.get(action.pluginId); + if (!pluginActions) { + pluginActions = []; + actionsByPlugin.set(action.pluginId, pluginActions); + } + + const index = pluginActions.length; + pluginActions.push( + cloneElement(action.element, { + key: + action.element.key ?? + `plugin-header-action-${action.pluginId}-${index}`, + }), + ); + } + + return new DefaultPluginHeaderActionsApi(actionsByPlugin); + } +} diff --git a/plugins/app/src/apis/PluginHeaderActionsApi/index.ts b/plugins/app/src/apis/PluginHeaderActionsApi/index.ts new file mode 100644 index 0000000000..be7906a2a7 --- /dev/null +++ b/plugins/app/src/apis/PluginHeaderActionsApi/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { DefaultPluginHeaderActionsApi } from './DefaultPluginHeaderActionsApi'; diff --git a/plugins/app/src/apis/PluginWrapperApi/DefaultPluginWrapperApi.test.tsx b/plugins/app/src/apis/PluginWrapperApi/DefaultPluginWrapperApi.test.tsx new file mode 100644 index 0000000000..bbf9ce6294 --- /dev/null +++ b/plugins/app/src/apis/PluginWrapperApi/DefaultPluginWrapperApi.test.tsx @@ -0,0 +1,279 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { render, screen } from '@testing-library/react'; +import { DefaultPluginWrapperApi } from './DefaultPluginWrapperApi'; +import { PluginWrapperDefinition } from '@backstage/frontend-plugin-api'; +import { ReactNode, useState } from 'react'; +import userEvent from '@testing-library/user-event'; + +type TestInc = { count: number; increment: () => void }; + +function useTestInc(): TestInc { + const [value, setValue] = useState(0); + return { + count: value, + increment: () => setValue(val => val + 1), + }; +} + +function makeTestIncWrapper( + label: string = '', + renderSpy?: () => void, +): (props: { children: ReactNode; value: TestInc }) => JSX.Element { + return ({ children, value }: { children: ReactNode; value: TestInc }) => { + renderSpy?.(); + return ( +
    + Wrapper{label}#{value.count} {children} + +
    + ); + }; +} + +describe('DefaultPluginWrapperApi', () => { + it('should wrap multiple components with a single wrapper', async () => { + const api = DefaultPluginWrapperApi.fromWrappers([ + { + loader: async () => ({ + component: ({ children }) => <>Wrapper({children}), + }), + pluginId: 'plugin-1', + }, + ]); + + const Wrapper1 = api.getPluginWrapper('plugin-1')!; + const Wrapper2 = api.getPluginWrapper('plugin-1')!; + const Wrapper3 = api.getPluginWrapper('plugin-1')!; + + expect(Wrapper1).toBeDefined(); + expect(Wrapper2).toBeDefined(); + expect(Wrapper3).toBeDefined(); + + const RootWrapper = api.getRootWrapper(); + + render( + +
    + 1 +
    +
    + 2 +
    +
    + 3 +
    +
    , + ); + + await expect(screen.findByText('Wrapper(1)')).resolves.toBeInTheDocument(); + await expect(screen.findByText('Wrapper(2)')).resolves.toBeInTheDocument(); + await expect(screen.findByText('Wrapper(3)')).resolves.toBeInTheDocument(); + }); + + it('should wrap multiple components with multiple wrappers', async () => { + const api = DefaultPluginWrapperApi.fromWrappers([ + { + loader: async () => ({ + component: ({ children }) => <>WrapperA({children}), + }), + pluginId: 'plugin-1', + }, + { + loader: async () => ({ + component: ({ children }) => <>WrapperB({children}), + }), + pluginId: 'plugin-1', + }, + ]); + + const Wrapper1 = api.getPluginWrapper('plugin-1')!; + const Wrapper2 = api.getPluginWrapper('plugin-1')!; + + expect(Wrapper1).toBeDefined(); + expect(Wrapper2).toBeDefined(); + + const RootWrapper = api.getRootWrapper(); + + render( + +
    + 1 +
    +
    + 2 +
    +
    , + ); + + await expect( + screen.findByText('WrapperB(WrapperA(1))'), + ).resolves.toBeInTheDocument(); + await expect( + screen.findByText('WrapperB(WrapperA(2))'), + ).resolves.toBeInTheDocument(); + }); + + it('should share a single value across multiple wrappers', async () => { + const api = DefaultPluginWrapperApi.fromWrappers([ + { + loader: async (): Promise> => ({ + component: ({ children, value }) => ( + <> + Wrapper({children}:{value}) + + ), + useWrapperValue: () => 'foo', + }), + pluginId: 'plugin-1', + }, + ]); + + const Wrapper1 = api.getPluginWrapper('plugin-1')!; + const Wrapper2 = api.getPluginWrapper('plugin-1')!; + + expect(Wrapper1).toBeDefined(); + expect(Wrapper2).toBeDefined(); + + const RootWrapper = api.getRootWrapper(); + + render( + +
    + 1 +
    +
    + 2 +
    +
    , + ); + + await expect( + screen.findByText('Wrapper(1:foo)'), + ).resolves.toBeInTheDocument(); + await expect( + screen.findByText('Wrapper(2:foo)'), + ).resolves.toBeInTheDocument(); + }); + + it('should share a single stateful value across multiple wrappers', async () => { + const api = DefaultPluginWrapperApi.fromWrappers([ + { + loader: async (): Promise> => ({ + component: makeTestIncWrapper(), + useWrapperValue: useTestInc, + }), + pluginId: 'plugin-1', + }, + ]); + + const Wrapper1 = api.getPluginWrapper('plugin-1')!; + const Wrapper2 = api.getPluginWrapper('plugin-1')!; + + expect(Wrapper1).toBeDefined(); + expect(Wrapper2).toBeDefined(); + + const RootWrapper = api.getRootWrapper(); + + render( + + X + Y + , + ); + + await expect(screen.findByText('Wrapper#0 X')).resolves.toBeInTheDocument(); + await expect(screen.findByText('Wrapper#0 Y')).resolves.toBeInTheDocument(); + + await userEvent.click(screen.getAllByText('Increment')[0]); + + await expect(screen.findByText('Wrapper#1 X')).resolves.toBeInTheDocument(); + await expect(screen.findByText('Wrapper#1 Y')).resolves.toBeInTheDocument(); + + await userEvent.click(screen.getAllByText('Increment')[1]); + + await expect(screen.findByText('Wrapper#2 X')).resolves.toBeInTheDocument(); + await expect(screen.findByText('Wrapper#2 Y')).resolves.toBeInTheDocument(); + }); + + it('should not rerender adjacent hooks on update', async () => { + let renderCountA = 0; + let renderCountB = 0; + + const api = DefaultPluginWrapperApi.fromWrappers([ + { + loader: async (): Promise> => ({ + component: makeTestIncWrapper('A', () => { + renderCountA += 1; + }), + useWrapperValue: useTestInc, + }), + pluginId: 'plugin-a', + }, + { + loader: async (): Promise> => ({ + component: makeTestIncWrapper('B', () => { + renderCountB += 1; + }), + useWrapperValue: useTestInc, + }), + pluginId: 'plugin-b', + }, + ]); + + const WrapperA = api.getPluginWrapper('plugin-a')!; + const WrapperB = api.getPluginWrapper('plugin-b')!; + + expect(WrapperA).toBeDefined(); + expect(WrapperB).toBeDefined(); + + const RootWrapper = api.getRootWrapper(); + + render( + + X + Y + , + ); + + await expect( + screen.findByText('WrapperA#0 X'), + ).resolves.toBeInTheDocument(); + await expect( + screen.findByText('WrapperB#0 Y'), + ).resolves.toBeInTheDocument(); + + expect(renderCountA).toBe(1); + expect(renderCountB).toBe(1); + + await userEvent.click(screen.getByText('IncrementA')); + + expect(screen.getByText('WrapperA#1 X')).toBeInTheDocument(); + expect(screen.getByText('WrapperB#0 Y')).toBeInTheDocument(); + + expect(renderCountA).toBe(2); + expect(renderCountB).toBe(1); + + await userEvent.click(screen.getByText('IncrementB')); + + expect(screen.getByText('WrapperA#1 X')).toBeInTheDocument(); + expect(screen.getByText('WrapperB#1 Y')).toBeInTheDocument(); + + expect(renderCountA).toBe(2); + expect(renderCountB).toBe(2); + }); +}); diff --git a/plugins/app/src/apis/PluginWrapperApi/DefaultPluginWrapperApi.tsx b/plugins/app/src/apis/PluginWrapperApi/DefaultPluginWrapperApi.tsx new file mode 100644 index 0000000000..1c6037a6f3 --- /dev/null +++ b/plugins/app/src/apis/PluginWrapperApi/DefaultPluginWrapperApi.tsx @@ -0,0 +1,279 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + PluginWrapperApi, + PluginWrapperDefinition, +} from '@backstage/frontend-plugin-api'; +import { + ComponentType, + ReactNode, + createContext, + useContext, + useEffect, + useMemo, + useState, + useSyncExternalStore, +} from 'react'; + +interface HookStore { + getSnapshot: () => { value: unknown } | undefined; + subscribe: (listener: () => void) => () => void; +} + +interface HookRegistryContextValue { + registerHook: (key: any, hook: () => unknown) => HookStore; +} + +const HookRegistryContext = createContext( + undefined, +); + +type WrapperInput = { + loader: () => Promise>; + pluginId: string; +}; + +/** + * Default implementation of PluginWrapperApi. + * + * @internal + */ +export class DefaultPluginWrapperApi implements PluginWrapperApi { + constructor( + private readonly rootWrapper: ComponentType<{ children: ReactNode }>, + private readonly pluginWrappers: Map< + string, + ComponentType<{ children: ReactNode }> + >, + ) {} + + getRootWrapper(): ComponentType<{ children: ReactNode }> { + return this.rootWrapper; + } + + getPluginWrapper( + pluginId: string, + ): ComponentType<{ children: ReactNode }> | undefined { + return this.pluginWrappers.get(pluginId); + } + + static fromWrappers(wrappers: Array): DefaultPluginWrapperApi { + const loadersByPlugin = new Map< + string, + Array<() => Promise>> + >(); + + for (const wrapper of wrappers) { + let loaders = loadersByPlugin.get(wrapper.pluginId); + if (!loaders) { + loaders = []; + loadersByPlugin.set(wrapper.pluginId, loaders); + } + loaders.push(wrapper.loader); + } + + const composedWrappers = new Map< + string, + ComponentType<{ children: ReactNode }> + >(); + + for (const [pluginId, loaders] of loadersByPlugin) { + if (loaders.length === 0) { + continue; + } + + const WrapperWithState = ({ + loader, + component: WrapperComponent, + useWrapperValue, + children, + }: { + loader: () => Promise; + component: ComponentType<{ + children: ReactNode; + value: unknown; + }>; + useWrapperValue: () => unknown; + children: ReactNode; + }) => { + const hookContext = useContext(HookRegistryContext); + if (!hookContext) { + throw new Error( + 'Attempted to render a wrapped plugin component without a root wrapper context', + ); + } + const store = useMemo(() => { + return hookContext.registerHook(loader, useWrapperValue); + }, [hookContext, loader, useWrapperValue]); + const container = useSyncExternalStore( + store.subscribe, + store.getSnapshot, + ); + + if (!container) { + return null; + } + + return ( + + {children} + + ); + }; + + const ComposedWrapper = (props: { children: ReactNode }) => { + const [loadedWrappers, setLoadedWrappers] = useState< + Array> | undefined + >(undefined); + const [error, setError] = useState(undefined); + + useEffect(() => { + Promise.all(loaders.map(loader => loader())) + .then(results => { + const normalizedResults = results.map( + ({ component, useWrapperValue }, index) => { + const loader = loaders[index]; + + if (!useWrapperValue) { + return component as ComponentType<{ children: ReactNode }>; + } + + return ({ children }: { children: ReactNode }) => ( + + {children} + + ); + }, + ); + + setLoadedWrappers(normalizedResults); + }) + .catch(setError); + }, []); + + if (error) { + throw error; + } + + if (!loadedWrappers) { + return null; + } + + let content = props.children; + + for (const Wrapper of loadedWrappers) { + content = {content}; + } + + return <>{content}; + }; + + composedWrappers.set(pluginId, ComposedWrapper); + } + + return new DefaultPluginWrapperApi( + DefaultPluginWrapperApi.createRootWrapper(), + composedWrappers, + ); + } + + /** + * Creates the root wrapper component that is responsible for rendering and + * forwarding the values of the common `useWrapperValue` hooks. + */ + static createRootWrapper() { + const renderers = new Map(); + const renderUpdateListeners = new Set<() => void>(); + + let renderElements = new Array(); + + const createHookRenderer = (hook: () => unknown): HookStore => { + const listeners = new Set<() => void>(); + let container: { value: unknown } | undefined = undefined; + + const HookRenderer = () => { + container = { value: hook() }; + useEffect(() => { + for (const listener of listeners) { + listener(); + } + }); + return null; + }; + + renderElements = [ + ...renderElements, + , + ]; + + return { + getSnapshot: () => container, + subscribe(listener: () => void) { + listeners.add(listener); + return () => listeners.delete(listener); + }, + }; + }; + + const registerHook = (key: any, hook: () => unknown) => { + let renderer = renderers.get(key); + if (!renderer) { + renderer = createHookRenderer(hook); + renderers.set(key, renderer); + + queueMicrotask(() => { + for (const listener of renderUpdateListeners) { + listener(); + } + }); + } + return renderer; + }; + + const subscribeToRenderUpdates = (listener: () => void) => { + renderUpdateListeners.add(listener); + return () => renderUpdateListeners.delete(listener); + }; + const getRenderElements = () => renderElements; + + const RootWrapper = (props: { children: ReactNode }) => { + const elements = useSyncExternalStore( + subscribeToRenderUpdates, + getRenderElements, + ); + + return ( + <> + <>{elements} + + {props.children} + + + ); + }; + + return RootWrapper; + } +} diff --git a/plugins/app/src/apis/PluginWrapperApi/index.ts b/plugins/app/src/apis/PluginWrapperApi/index.ts new file mode 100644 index 0000000000..f11e40fdf0 --- /dev/null +++ b/plugins/app/src/apis/PluginWrapperApi/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { DefaultPluginWrapperApi } from './DefaultPluginWrapperApi'; diff --git a/plugins/app/src/apis/SwappableComponentsApi/DefaultSwappableComponentsApi.test.tsx b/plugins/app/src/apis/SwappableComponentsApi/DefaultSwappableComponentsApi.test.tsx index bdd81f338c..9d3483319f 100644 --- a/plugins/app/src/apis/SwappableComponentsApi/DefaultSwappableComponentsApi.test.tsx +++ b/plugins/app/src/apis/SwappableComponentsApi/DefaultSwappableComponentsApi.test.tsx @@ -20,9 +20,9 @@ import { createExtensionInput, createFrontendModule, createSwappableComponent, - SwappableComponentBlueprint, swappableComponentsApiRef, } from '@backstage/frontend-plugin-api'; +import { SwappableComponentBlueprint } from '@backstage/plugin-app-react'; import { DefaultSwappableComponentsApi } from './DefaultSwappableComponentsApi'; import { render, screen } from '@testing-library/react'; import { renderInTestApp, renderTestApp } from '@backstage/frontend-test-utils'; diff --git a/plugins/app/src/apis/SwappableComponentsApi/DefaultSwappableComponentsApi.tsx b/plugins/app/src/apis/SwappableComponentsApi/DefaultSwappableComponentsApi.tsx index 593fff24ff..b7c496b3d1 100644 --- a/plugins/app/src/apis/SwappableComponentsApi/DefaultSwappableComponentsApi.tsx +++ b/plugins/app/src/apis/SwappableComponentsApi/DefaultSwappableComponentsApi.tsx @@ -17,8 +17,8 @@ import { SwappableComponentRef, SwappableComponentsApi, - SwappableComponentBlueprint, } from '@backstage/frontend-plugin-api'; +import { SwappableComponentBlueprint } from '@backstage/plugin-app-react'; import { OpaqueSwappableComponentRef } from '@internal/frontend'; import { lazy } from 'react'; diff --git a/plugins/app/src/apis/ToastApiForwarder.test.ts b/plugins/app/src/apis/ToastApiForwarder.test.ts new file mode 100644 index 0000000000..6a0b152322 --- /dev/null +++ b/plugins/app/src/apis/ToastApiForwarder.test.ts @@ -0,0 +1,234 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ToastApiForwarder } from './ToastApiForwarder'; + +describe('ToastApiForwarder', () => { + let forwarder: ToastApiForwarder; + + beforeEach(() => { + forwarder = new ToastApiForwarder(); + }); + + describe('post', () => { + it('should return a result with a close method', () => { + const result = forwarder.post({ title: 'Toast 1' }); + + expect(result).toBeDefined(); + expect(typeof result.close).toBe('function'); + }); + + it('should emit toast to subscribers', () => { + const received: Array<{ title: unknown; key: string }> = []; + + forwarder.toast$().subscribe(toast => { + received.push(toast); + }); + + forwarder.post({ title: 'Test Toast', status: 'success' }); + + expect(received).toHaveLength(1); + expect(received[0].title).toBe('Test Toast'); + expect(received[0].key).toBeDefined(); + }); + + it('should include all toast properties in emitted message', () => { + const received: Array<{ + title: unknown; + description?: unknown; + status?: string; + timeout?: number; + }> = []; + + forwarder.toast$().subscribe(toast => { + received.push(toast); + }); + + forwarder.post({ + title: 'Title', + description: 'Description', + status: 'warning', + timeout: 5000, + links: [{ label: 'Link', href: '/test' }], + }); + + expect(received[0]).toMatchObject({ + title: 'Title', + description: 'Description', + status: 'warning', + timeout: 5000, + links: [{ label: 'Link', href: '/test' }], + }); + }); + }); + + describe('close', () => { + it('should notify onClose listeners when close() is called', () => { + const onCloseFn = jest.fn(); + + const received: Array<{ onClose: (cb: () => void) => void }> = []; + forwarder.toast$().subscribe(toast => { + received.push(toast); + }); + + const result = forwarder.post({ title: 'Test' }); + received[0].onClose(onCloseFn); + result.close(); + + expect(onCloseFn).toHaveBeenCalledTimes(1); + }); + + it('should only close once even if called multiple times', () => { + const onCloseFn = jest.fn(); + + const received: Array<{ onClose: (cb: () => void) => void }> = []; + forwarder.toast$().subscribe(toast => { + received.push(toast); + }); + + const result = forwarder.post({ title: 'Test' }); + received[0].onClose(onCloseFn); + result.close(); + result.close(); + + expect(onCloseFn).toHaveBeenCalledTimes(1); + }); + + it('should remove toast from replay buffer', () => { + const result = forwarder.post({ title: 'Test' }); + result.close(); + + // New subscriber should not receive the closed toast + const received: Array<{ key: string }> = []; + forwarder.toast$().subscribe(toast => { + received.push(toast); + }); + + expect(received).toHaveLength(0); + }); + + it('should immediately call onClose callback if already closed', () => { + const onCloseFn = jest.fn(); + + const received: Array<{ onClose: (cb: () => void) => void }> = []; + forwarder.toast$().subscribe(toast => { + received.push(toast); + }); + + const result = forwarder.post({ title: 'Test' }); + result.close(); + + // Register callback after close - should fire immediately + received[0].onClose(onCloseFn); + expect(onCloseFn).toHaveBeenCalledTimes(1); + }); + }); + + describe('toast$ replay', () => { + it('should replay recent toasts to new subscribers', async () => { + forwarder.post({ title: 'Toast 1' }); + forwarder.post({ title: 'Toast 2' }); + + const received: Array<{ title: unknown }> = []; + + await new Promise(resolve => { + const subscription = forwarder.toast$().subscribe({ + next: toast => { + received.push(toast); + // After receiving replayed toasts, unsubscribe + if (received.length === 2) { + subscription.unsubscribe(); + resolve(); + } + }, + }); + // Also resolve after a short timeout in case no toasts are replayed + setTimeout(() => resolve(), 100); + }); + + expect(received).toHaveLength(2); + expect(received[0].title).toBe('Toast 1'); + expect(received[1].title).toBe('Toast 2'); + }); + + it('should not replay closed toasts to new subscribers', async () => { + const result1 = forwarder.post({ title: 'Toast 1' }); + forwarder.post({ title: 'Toast 2' }); + + result1.close(); + + const received: Array<{ title: unknown }> = []; + + await new Promise(resolve => { + const subscription = forwarder.toast$().subscribe({ + next: toast => { + received.push(toast); + subscription.unsubscribe(); + resolve(); + }, + }); + setTimeout(() => resolve(), 100); + }); + + expect(received).toHaveLength(1); + expect(received[0].title).toBe('Toast 2'); + }); + + it('should limit replay buffer size', async () => { + // Post more than maxBufferSize (10) toasts + for (let i = 0; i < 15; i++) { + forwarder.post({ title: `Toast ${i}` }); + } + + const received: Array<{ title: unknown }> = []; + + await new Promise(resolve => { + const subscription = forwarder.toast$().subscribe({ + next: toast => { + received.push(toast); + if (received.length === 10) { + subscription.unsubscribe(); + resolve(); + } + }, + }); + setTimeout(() => resolve(), 100); + }); + + // Should only have last 10 toasts + expect(received).toHaveLength(10); + expect(received[0].title).toBe('Toast 5'); + expect(received[9].title).toBe('Toast 14'); + }); + }); + + describe('subscription cleanup', () => { + it('should stop receiving toasts after unsubscribe', () => { + const received: Array<{ title: unknown }> = []; + + const subscription = forwarder.toast$().subscribe(toast => { + received.push(toast); + }); + + forwarder.post({ title: 'Before unsubscribe' }); + subscription.unsubscribe(); + forwarder.post({ title: 'After unsubscribe' }); + + expect(received).toHaveLength(1); + expect(received[0].title).toBe('Before unsubscribe'); + }); + }); +}); diff --git a/plugins/app/src/apis/ToastApiForwarder.ts b/plugins/app/src/apis/ToastApiForwarder.ts new file mode 100644 index 0000000000..10a778f388 --- /dev/null +++ b/plugins/app/src/apis/ToastApiForwarder.ts @@ -0,0 +1,154 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + ToastApiMessage, + ToastApiPostResult, +} from '@backstage/frontend-plugin-api'; +import { Observable } from '@backstage/types'; +import ObservableImpl from 'zen-observable'; +import { + ToastApiForwarderApi, + ToastApiForwarderMessage, +} from './toastApiForwarderRef'; + +let toastKeyCounter = 0; + +/** + * Generates a unique key for a toast message. + */ +function generateToastKey(): string { + toastKeyCounter += 1; + return `toast-${toastKeyCounter}-${Date.now()}`; +} + +/** + * A simple publish subject for broadcasting values to subscribers. + */ +class PublishSubject { + private subscribers = new Set>(); + private isClosed = false; + + private readonly observable = new ObservableImpl(subscriber => { + if (this.isClosed) { + subscriber.complete(); + return () => {}; + } + this.subscribers.add(subscriber); + return () => { + this.subscribers.delete(subscriber); + }; + }); + + next(value: T) { + if (this.isClosed) { + throw new Error('PublishSubject is closed'); + } + this.subscribers.forEach(subscriber => subscriber.next(value)); + } + + subscribe(observer: ZenObservable.Observer): ZenObservable.Subscription { + return this.observable.subscribe(observer); + } + + /** + * Creates an Observable that replays buffered values and then subscribes to live updates. + */ + asObservable(replayBuffer: T[] = []): Observable { + return new ObservableImpl(subscriber => { + // Replay buffered values + for (const value of replayBuffer) { + subscriber.next(value); + } + // Subscribe to live updates + return this.subscribe(subscriber); + }); + } +} + +/** + * Base implementation for the ToastApi that forwards toast messages to consumers. + * + * Recent toasts are buffered and replayed to new subscribers to prevent + * missing toasts that were posted before subscription. + * + * @internal + */ +export class ToastApiForwarder implements ToastApiForwarderApi { + private readonly subject = new PublishSubject(); + private readonly recentToasts: ToastApiForwarderMessage[] = []; + private readonly closedKeys = new Set(); + private readonly maxBufferSize = 10; + + post(toast: ToastApiMessage): ToastApiPostResult { + const key = generateToastKey(); + const closeCallbacks: Array<() => void> = []; + let closed = false; + + const close = () => { + if (closed) return; + closed = true; + + // Track closed keys to prevent replaying dismissed toasts + this.closedKeys.add(key); + + // Remove from recent buffer if still there + const index = this.recentToasts.findIndex(t => t.key === key); + if (index !== -1) { + this.recentToasts.splice(index, 1); + } + + // Clean up old closed keys when buffer is cleared + if (this.recentToasts.length === 0) { + this.closedKeys.clear(); + } + + // Notify registered listeners (e.g. the toast display) + closeCallbacks.forEach(fn => fn()); + }; + + const onClose = (callback: () => void) => { + if (closed) { + callback(); + } else { + closeCallbacks.push(callback); + } + }; + + const toastWithKey: ToastApiForwarderMessage = { + ...toast, + key, + close, + onClose, + }; + + this.recentToasts.push(toastWithKey); + if (this.recentToasts.length > this.maxBufferSize) { + this.recentToasts.shift(); + } + this.subject.next(toastWithKey); + + return { close }; + } + + toast$(): Observable { + // Filter out any toasts that were closed to handle race conditions + const activeToasts = this.recentToasts.filter( + t => !this.closedKeys.has(t.key), + ); + return this.subject.asObservable(activeToasts); + } +} diff --git a/plugins/app/src/apis/index.ts b/plugins/app/src/apis/index.ts new file mode 100644 index 0000000000..dc8189b7ee --- /dev/null +++ b/plugins/app/src/apis/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 { ToastApiForwarder } from './ToastApiForwarder'; +export { toastApiForwarderRef } from './toastApiForwarderRef'; +export type { ToastApiForwarderApi } from './toastApiForwarderRef'; diff --git a/plugins/app/src/apis/toastApiForwarderRef.ts b/plugins/app/src/apis/toastApiForwarderRef.ts new file mode 100644 index 0000000000..2b2ddce0ab --- /dev/null +++ b/plugins/app/src/apis/toastApiForwarderRef.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 { + ToastApi, + ToastApiMessage, + createApiRef, +} from '@backstage/frontend-plugin-api'; +import { Observable } from '@backstage/types'; + +/** + * Internal enriched toast message emitted by the forwarder's observable. + * + * @internal + */ +export type ToastApiForwarderMessage = ToastApiMessage & { + /** Unique key for tracking this toast */ + key: string; + /** Dismiss this toast programmatically */ + close(): void; + /** + * Register a callback that fires when this toast is closed. + * Used by the toast display to sync with the rendering queue. + */ + onClose(callback: () => void): void; +}; + +/** + * Internal extension of the public ToastApi that adds an observable + * for the toast display to subscribe to. This interface is only used + * within the app plugin and can be freely evolved. + * + * @internal + */ +export type ToastApiForwarderApi = ToastApi & { + /** Observe toasts posted by other parts of the application. */ + toast$(): Observable; +}; + +/** + * Internal API ref for the toast forwarder. The public `toastApiRef` + * delegates to this, and the toast display subscribes to it directly. + * + * @internal + */ +export const toastApiForwarderRef = createApiRef({ + id: 'app.toast.internal-forwarder', +}); diff --git a/plugins/app/src/components/Toast/Toast.module.css b/plugins/app/src/components/Toast/Toast.module.css new file mode 100644 index 0000000000..4111ce9c23 --- /dev/null +++ b/plugins/app/src/components/Toast/Toast.module.css @@ -0,0 +1,227 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Toast Container - Container for all toasts (bottom center) */ +.container { + position: fixed; + bottom: 1rem; + left: 50%; + transform: translateX(-50%); + z-index: 100050; + width: 300px; + outline: none; +} + +@media (min-width: 500px) { + .container { + width: 350px; + bottom: 2rem; + } +} + +/* Individual Toast */ +.toast { + /* CSS Variables */ + --toast-peek: 0.75rem; + --toast-scale: calc(max(0, 1 - (var(--toast-index) * 0.05))); + + /* Layout */ + position: absolute; + bottom: 0; + left: 0; + width: 100%; + margin: 0 auto; + box-sizing: border-box; + + outline: none; + cursor: default; + user-select: none; + /* All toasts need pointer-events to trigger hover expansion */ + pointer-events: auto; + + /* Stacking */ + z-index: calc(1000 - var(--toast-index)); + transform-origin: bottom center; +} + +.surface { + width: 100%; + box-sizing: border-box; + display: flex; + align-items: flex-start; + padding: var(--bui-space-4); + gap: var(--bui-space-3); + border-radius: var(--bui-radius-3); + font-size: var(--bui-font-size-3); + background-color: var(--bui-bg-app); + border: 1px solid var(--bui-border-1); + color: var(--bui-fg-primary); + box-shadow: var(--bui-shadow); +} + +.toast:focus-visible { + outline: 2px solid var(--bui-ring); + outline-offset: 2px; +} + +/* Extend hover area above each toast to fill gaps when expanded */ +/* This prevents the stack from collapsing when hovering between toasts */ +.toast::before { + content: ''; + position: absolute; + bottom: 100%; + left: 0; + right: 0; + /* Height matches the expanded gap to ensure continuous hover */ + height: 12px; + pointer-events: auto; +} + +/* Status variants - color icon and title */ +.toast[data-status='neutral'] .title { + color: var(--bui-fg-primary); +} + +.toast[data-status='info'] .icon, +.toast[data-status='info'] .title { + color: var(--bui-fg-info); +} + +.toast[data-status='success'] .icon, +.toast[data-status='success'] .title { + color: var(--bui-fg-success); +} + +.toast[data-status='warning'] .icon, +.toast[data-status='warning'] .title { + color: var(--bui-fg-warning); +} + +.toast[data-status='danger'] .icon, +.toast[data-status='danger'] .title { + color: var(--bui-fg-danger); +} + +.wrapper { + display: flex; + align-items: flex-start; + gap: var(--bui-space-3); + flex: 1; + min-width: 0; +} + +.content { + display: flex; + flex-direction: column; + gap: var(--bui-space-1); +} + +/* Icon */ +.icon { + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + margin-top: var(--bui-space-0_5); +} + +.icon svg { + width: 1rem; + height: 1rem; +} + +/* Title */ +.title { + font-weight: var(--bui-font-weight-bold); + font-size: var(--bui-font-size-3); + word-wrap: break-word; + margin-top: var(--bui-space-0_5); +} + +/* Description */ +.description { + color: var(--bui-fg-secondary); + font-size: var(--bui-font-size-3); + opacity: 0.9; + word-wrap: break-word; +} + +/* Links */ +.links { + display: flex; + flex-wrap: wrap; + gap: var(--bui-space-3); + margin-top: var(--bui-space-1); +} + +.links a { + font-family: var(--bui-font-regular); + padding: 0; + margin: 0; + cursor: pointer; + display: inline-block; + + text-decoration-line: underline; + text-decoration-style: solid; + text-decoration-thickness: min(2px, max(1px, 0.05em)); + text-underline-offset: calc(0.025em + 2px); + text-decoration-color: color-mix(in srgb, currentColor 30%, transparent); +} + +.links a:hover { + text-decoration-line: underline; + text-decoration-style: solid; + text-decoration-thickness: min(2px, max(1px, 0.05em)); + text-underline-offset: calc(0.025em + 2px); + text-decoration-color: color-mix(in srgb, currentColor 30%, transparent); +} + +/* Close Button */ +.closeButton { + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + width: 1.5rem; + height: 1.5rem; + margin: -0.25rem -0.25rem 0 0; + padding: 0; + border: none; + border-radius: var(--bui-radius-2); + background: transparent; + color: inherit; + cursor: pointer; + transition: background-color 0.15s ease; + color: var(--bui-fg-primary); +} + +.closeButton svg { + width: 1rem; + height: 1rem; +} + +.closeButton:hover { + background-color: var(--bui-bg-neutral-1-hover); +} + +.closeButton:focus-visible { + outline: 2px solid var(--bui-ring); + outline-offset: 1px; +} + +.closeButton:active { + background-color: var(--bui-bg-neutral-1-pressed); +} diff --git a/plugins/app/src/components/Toast/Toast.stories.tsx b/plugins/app/src/components/Toast/Toast.stories.tsx new file mode 100644 index 0000000000..a23bb5d8da --- /dev/null +++ b/plugins/app/src/components/Toast/Toast.stories.tsx @@ -0,0 +1,589 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { useState } from 'react'; +/* eslint-disable @backstage/no-relative-monorepo-imports */ +import preview from '../../../../../.storybook/preview'; +import { Button, Flex, Text } from '../../../../../packages/ui/src'; +/* eslint-enable @backstage/no-relative-monorepo-imports */ +import { ToastQueue } from 'react-stately'; +import { ToastContainer } from './index'; +import type { ToastApiMessageContent } from './types'; +import { MemoryRouter } from 'react-router-dom'; + +const toastQueue = new ToastQueue({ + maxVisibleToasts: 4, +}); + +const meta = preview.meta({ + title: 'App/Toast', + component: ToastContainer, + parameters: { + layout: 'centered', + }, +}); + +const randomToasts = [ + // Title only - short + { title: 'Saved', status: 'success' as const }, + { title: 'Error', status: 'danger' as const }, + { title: 'New notification', status: 'info' as const }, + { title: 'Warning', status: 'warning' as const }, + { title: 'Task completed', status: 'neutral' as const }, + // Title only - medium + { title: 'Changes saved successfully', status: 'success' as const }, + { title: 'Connection restored', status: 'info' as const }, + { title: 'Action could not be completed', status: 'danger' as const }, + { title: 'Background sync in progress', status: 'neutral' as const }, + // Title + short description + { + title: 'Files uploaded', + description: '3 files uploaded.', + status: 'success' as const, + }, + { + title: 'Update available', + description: 'Version 2.0 is ready.', + status: 'info' as const, + }, + { + title: 'Request failed', + description: 'Please try again.', + status: 'danger' as const, + }, + { + title: 'Storage warning', + description: '90% used.', + status: 'warning' as const, + }, + { + title: 'Clipboard updated', + description: 'Text copied.', + status: 'neutral' as const, + }, + // Title + medium description + { + title: 'Deployment complete', + description: + 'Your application has been deployed to production successfully.', + status: 'success' as const, + }, + { + title: 'Session expiring', + description: + 'Your session will expire in 5 minutes. Please save your work.', + status: 'warning' as const, + }, + { + title: 'Permission denied', + description: 'You do not have access to perform this action.', + status: 'danger' as const, + }, + { + title: 'Preferences updated', + description: 'Your display settings have been saved to your profile.', + status: 'neutral' as const, + }, + // Title + long description + { + title: 'Sync completed', + description: + 'All your files have been synchronized across devices. This includes 47 documents, 23 images, and 12 configuration files that were updated in the last hour.', + status: 'success' as const, + }, + { + title: 'Rate limit exceeded', + description: + 'You have exceeded the maximum number of API requests allowed. Please wait a few minutes before trying again or upgrade your plan for higher limits.', + status: 'warning' as const, + }, + { + title: 'Critical error', + description: + 'The server encountered an unexpected error while processing your request. Our team has been notified and is investigating the issue. Please try again later.', + status: 'danger' as const, + }, + // Long title only + { + title: 'Your subscription has been renewed successfully for another year', + status: 'success' as const, + }, + { + title: 'Multiple users are currently editing this document', + status: 'info' as const, + }, + { + title: 'Your workspace has been switched to the new project', + status: 'neutral' as const, + }, +]; + +export const Default = meta.story({ + render: () => ( + <> + + + + + + + ), +}); + +const previewQueue = new ToastQueue({ + maxVisibleToasts: 4, +}); +previewQueue.add({ + title: 'Changes saved successfully', + description: 'Your changes have been saved.', + status: 'success', +}); + +export const PreviewToast = meta.story({ + render: () => , +}); + +const timerQueue = new ToastQueue({ + maxVisibleToasts: 4, +}); +timerQueue.add( + { + title: 'Auto-dismissing in 5 seconds', + description: 'This toast will disappear automatically.', + status: 'info', + }, + { timeout: 5000 }, +); + +export const PreviewToastWithTimer = meta.story({ + render: () => , +}); + +export const StatusVariants = meta.story({ + render: () => ( + <> + + + + + + + + + + ), +}); + +export const WithoutDescription = meta.story({ + render: () => ( + <> + + + + + + + ), +}); + +export const NeutralStatus = meta.story({ + render: () => ( + <> + + + + + + + ), +}); + +export const WithLinks = meta.story({ + render: () => ( + + + + + + + + + ), +}); + +export const AutoDismiss = meta.story({ + render: () => ( + <> + + + + + + + + ), +}); + +function ProgrammaticDismissStory() { + const [toastKey, setToastKey] = useState(null); + + return ( + <> + + + + ); +} + +export const ProgrammaticDismiss = meta.story({ + render: () => , +}); + +export const QueueManagement = meta.story({ + render: () => ( + <> + + + + + + + + ), +}); + +export const AlertApiIntegration = meta.story({ + name: 'AlertApi Integration', + render: () => { + // This story demonstrates how alerts posted via the AlertApi + // would be displayed using the ToastDisplay component. + // The ToastDisplay bridges AlertApi.post() calls to the toast queue. + + return ( + <> + + + + + AlertApi Severity Mapping: + + success → success (green) + info → info (blue) + warning → warning (orange) + error → danger (red) + + (ToastApi also supports neutral - no icon, primary color) + + + + + + + + + + + + ); + }, +}); + +export default meta; diff --git a/plugins/app/src/components/Toast/Toast.tsx b/plugins/app/src/components/Toast/Toast.tsx new file mode 100644 index 0000000000..d10ea68820 --- /dev/null +++ b/plugins/app/src/components/Toast/Toast.tsx @@ -0,0 +1,269 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { forwardRef, Ref, useRef, useLayoutEffect, useState } from 'react'; +import { useToast, useButton } from 'react-aria'; +import { motion } from 'motion/react'; +import { Box } from '@backstage/ui'; +import { + RiInformationLine, + RiCheckLine, + RiErrorWarningLine, + RiAlertLine, + RiCloseLine, +} from '@remixicon/react'; +import type { ToastApiMessageProps } from './types'; +import styles from './Toast.module.css'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { BgReset } from '../../../../../packages/ui/src/hooks/useBg'; + +// Track which toasts are being manually closed (vs auto-timeout) +// This allows different exit animations for each case +const manuallyClosingToasts = new Set(); + +/** + * A Toast displays a brief, temporary notification of actions, errors, or other events in an application. + * + * @remarks + * The Toast component is used internally by ToastContainer and managed by a ToastQueue. + * It supports multiple status variants (neutral, info, success, warning, danger) and can display + * a title and description. Toasts can be dismissed manually or automatically. + * + * @internal + */ +export const Toast = forwardRef( + (props: ToastApiMessageProps, ref: Ref) => { + const { + toast, + state, + index = 0, + isExpanded = false, + onClose, + status, + expandedY: expandedYProp = 0, + collapsedHeight, + naturalHeight, + onHeightChange, + } = props; + + // Use internal ref if none provided + const internalRef = useRef(null); + const toastRef = (ref as React.RefObject) || internalRef; + + // Get ARIA props from useToast hook + const { toastProps, titleProps, closeButtonProps } = useToast( + { toast }, + state, + toastRef, + ); + + // Close button ref for useButton hook + const closeButtonRef = useRef(null); + + // Extract only ARIA and accessibility props from toastProps to avoid + // conflicts with motion.div's event handler types (motion has its own drag API) + const ariaProps = { + role: toastProps.role, + tabIndex: toastProps.tabIndex, + 'aria-label': toastProps['aria-label'], + 'aria-labelledby': toastProps['aria-labelledby'], + 'aria-describedby': toastProps['aria-describedby'], + 'aria-posinset': toastProps['aria-posinset'], + 'aria-setsize': toastProps['aria-setsize'], + }; + + // Track whether we've measured this toast's natural height + const [hasMeasured, setHasMeasured] = useState(false); + // Store the measured natural height locally to avoid re-measurement issues + const naturalHeightRef = useRef(null); + + // Measure this toast's natural height on mount (before paint) + // Using useLayoutEffect ensures we measure before the browser paints + useLayoutEffect(() => { + if (!onHeightChange) return; + if (naturalHeightRef.current) return; // Already measured + + const element = toastRef.current; + if (!element) return; + + // Measure immediately - useLayoutEffect runs before paint + const height = element.getBoundingClientRect().height; + if (height > 0) { + naturalHeightRef.current = height; + onHeightChange(toast.key, height); + setHasMeasured(true); + } + }, [toast.key, onHeightChange, toastRef]); + + // Close button handler + const handleClose = () => { + // Mark this toast as manually closed for exit animation + manuallyClosingToasts.add(toast.key); + onClose?.(); + state.close(toast.key); + }; + + // Get button props from useButton hook + const { buttonProps } = useButton( + { + 'aria-label': closeButtonProps['aria-label'], + onPress: handleClose, + }, + closeButtonRef, + ); + + // Get content from toast + const content = toast.content; + const finalStatus = status || content.status || 'info'; + + // Determine which icon to render based on status + const getStatusIcon = () => { + switch (finalStatus) { + case 'neutral': + // Neutral status has no icon + return null; + case 'success': + return
    + ); +} diff --git a/plugins/catalog-react/src/components/EntityDataTable/columnFactories.tsx b/plugins/catalog-react/src/components/EntityDataTable/columnFactories.tsx new file mode 100644 index 0000000000..80a4cc8070 --- /dev/null +++ b/plugins/catalog-react/src/components/EntityDataTable/columnFactories.tsx @@ -0,0 +1,205 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Entity, + RELATION_OWNED_BY, + RELATION_PART_OF, +} from '@backstage/catalog-model'; +import { Cell, CellText, Column, ColumnConfig, TableItem } from '@backstage/ui'; +import { EntityRefLink, EntityRefLinks } from '../EntityRefLink'; +import { + entityPresentationSnapshot, + EntityPresentationApi, +} from '@backstage/plugin-catalog-react'; +import { EntityTableColumnTitle } from '../EntityTable/TitleColumn'; +import { getEntityRelations } from '../../utils'; + +/** @public */ +export type EntityRow = Entity & TableItem; + +/** @public */ +export interface EntityColumnConfig extends ColumnConfig { + sortValue?: (entity: EntityRow) => string; +} + +/** @public */ +export const columnFactories = Object.freeze({ + createEntityRefColumn(options: { + defaultKind?: string; + isRowHeader?: boolean; + entityPresentationApi?: EntityPresentationApi; + }): EntityColumnConfig { + const isRowHeader = options.isRowHeader ?? true; + return { + id: 'name', + label: 'Name', + header: () => ( + + + + ), + isRowHeader, + isSortable: true, + cell: entity => ( + + + + ), + sortValue: entity => + entityPresentationSnapshot( + entity, + { defaultKind: options.defaultKind }, + options.entityPresentationApi, + ).primaryTitle, + }; + }, + + createEntityRelationColumn(options: { + id: string; + translationKey: 'owner' | 'system' | 'domain'; + relation: string; + defaultKind?: string; + filter?: { kind: string }; + entityPresentationApi?: EntityPresentationApi; + }): EntityColumnConfig { + return { + id: options.id, + label: options.id.charAt(0).toUpperCase() + options.id.slice(1), + header: () => ( + + + + ), + isSortable: true, + cell: entity => ( + + + + ), + sortValue: entity => + getEntityRelations(entity, options.relation, options.filter) + .map( + r => + entityPresentationSnapshot( + r, + { defaultKind: options.defaultKind }, + options.entityPresentationApi, + ).primaryTitle, + ) + .join(', '), + }; + }, + + createOwnerColumn(): EntityColumnConfig { + return columnFactories.createEntityRelationColumn({ + id: 'owner', + translationKey: 'owner', + relation: RELATION_OWNED_BY, + defaultKind: 'group', + }); + }, + + createSystemColumn(): EntityColumnConfig { + return columnFactories.createEntityRelationColumn({ + id: 'system', + translationKey: 'system', + relation: RELATION_PART_OF, + defaultKind: 'system', + filter: { kind: 'system' }, + }); + }, + + createDomainColumn(): EntityColumnConfig { + return columnFactories.createEntityRelationColumn({ + id: 'domain', + translationKey: 'domain', + relation: RELATION_PART_OF, + defaultKind: 'domain', + filter: { kind: 'domain' }, + }); + }, + + createMetadataDescriptionColumn(): EntityColumnConfig { + return { + id: 'description', + label: 'Description', + header: () => ( + + + + ), + isSortable: true, + cell: entity => , + sortValue: entity => entity.metadata.description ?? '', + }; + }, + + createSpecTypeColumn(): EntityColumnConfig { + return { + id: 'type', + label: 'Type', + header: () => ( + + + + ), + isSortable: true, + cell: entity => ( + | undefined)?.type ?? '' + } + /> + ), + sortValue: entity => + (entity.spec as Record | undefined)?.type ?? '', + }; + }, + + createSpecLifecycleColumn(): EntityColumnConfig { + return { + id: 'lifecycle', + label: 'Lifecycle', + header: () => ( + + + + ), + isSortable: true, + cell: entity => ( + | undefined)?.lifecycle ?? '' + } + /> + ), + sortValue: entity => + (entity.spec as Record | undefined)?.lifecycle ?? '', + }; + }, +}); diff --git a/plugins/catalog-react/src/components/EntityDataTable/index.ts b/plugins/catalog-react/src/components/EntityDataTable/index.ts new file mode 100644 index 0000000000..a313d3401e --- /dev/null +++ b/plugins/catalog-react/src/components/EntityDataTable/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { EntityDataTable } from './EntityDataTable'; +export type { EntityDataTableProps } from './EntityDataTable'; +export { columnFactories as entityDataTableColumns } from './columnFactories'; +export type { EntityRow, EntityColumnConfig } from './columnFactories'; +export { entityColumnPresets } from './presets'; diff --git a/plugins/catalog-react/src/components/EntityDataTable/presets.tsx b/plugins/catalog-react/src/components/EntityDataTable/presets.tsx new file mode 100644 index 0000000000..7bbd94e7d7 --- /dev/null +++ b/plugins/catalog-react/src/components/EntityDataTable/presets.tsx @@ -0,0 +1,61 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { columnFactories, EntityColumnConfig } from './columnFactories'; + +/** @alpha */ +export const entityColumnPresets = { + component: { + columns: [ + columnFactories.createEntityRefColumn({ defaultKind: 'component' }), + columnFactories.createOwnerColumn(), + columnFactories.createSpecTypeColumn(), + columnFactories.createSpecLifecycleColumn(), + columnFactories.createMetadataDescriptionColumn(), + ] as EntityColumnConfig[], + helpLink: + 'https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component', + }, + resource: { + columns: [ + columnFactories.createEntityRefColumn({ defaultKind: 'resource' }), + columnFactories.createOwnerColumn(), + columnFactories.createSpecTypeColumn(), + columnFactories.createSpecLifecycleColumn(), + columnFactories.createMetadataDescriptionColumn(), + ] as EntityColumnConfig[], + helpLink: + 'https://backstage.io/docs/features/software-catalog/descriptor-format#kind-resource', + }, + system: { + columns: [ + columnFactories.createEntityRefColumn({ defaultKind: 'system' }), + columnFactories.createOwnerColumn(), + columnFactories.createMetadataDescriptionColumn(), + ] as EntityColumnConfig[], + helpLink: + 'https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system', + }, + domain: { + columns: [ + columnFactories.createEntityRefColumn({ defaultKind: 'domain' }), + columnFactories.createOwnerColumn(), + columnFactories.createMetadataDescriptionColumn(), + ] as EntityColumnConfig[], + helpLink: + 'https://backstage.io/docs/features/software-catalog/descriptor-format#kind-domain', + }, +} as const; diff --git a/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.stories.tsx b/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.stories.tsx index 8ae4e8fd50..07faaae45a 100644 --- a/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.stories.tsx +++ b/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.stories.tsx @@ -25,6 +25,7 @@ const defaultArgs = { export default { title: 'Catalog /EntityDisplayName', decorators: [(Story: ComponentType<{}>) => wrapInTestApp()], + tags: ['!manifest'], }; export const Default = (args: EntityDisplayNameProps) => ( diff --git a/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.tsx b/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.tsx index a4134df465..1f60333acd 100644 --- a/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.tsx +++ b/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.tsx @@ -62,6 +62,16 @@ export type EntityDisplayNameProps = { /** * Shows a nice representation of a reference to an entity. * + * @remarks + * + * This component uses the {@link useEntityPresentation} hook internally and + * renders the entity's primary title with optional icon and tooltip. It is + * the simplest way to display an entity name in JSX. + * + * For more control over the presentation data, use the + * {@link useEntityPresentation} hook directly. For non-React contexts, use + * {@link entityPresentationSnapshot}. + * * @public */ export const EntityDisplayName = ( diff --git a/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.test.tsx b/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.test.tsx new file mode 100644 index 0000000000..b3b0efd596 --- /dev/null +++ b/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.test.tsx @@ -0,0 +1,102 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { renderInTestApp } from '@backstage/test-utils'; +import { screen } from '@testing-library/react'; +import { EntityInfoCard } from './EntityInfoCard'; + +describe('', () => { + it('renders children in the card body', async () => { + await renderInTestApp( + +
    Body content
    +
    , + ); + + expect(screen.getByText('Body content')).toBeInTheDocument(); + }); + + it('renders the title when provided', async () => { + await renderInTestApp( + +
    Body
    +
    , + ); + + expect( + screen.getByRole('heading', { name: 'My Card Title' }), + ).toBeInTheDocument(); + }); + + it('does not render a heading when title is not provided', async () => { + await renderInTestApp( + +
    Body
    +
    , + ); + + expect(screen.queryByRole('heading')).not.toBeInTheDocument(); + }); + + it('renders header actions next to the title', async () => { + await renderInTestApp( + Edit}> +
    Body
    +
    , + ); + + expect(screen.getByRole('heading', { name: 'Title' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Edit' })).toBeInTheDocument(); + }); + + it('renders footer actions when provided', async () => { + await renderInTestApp( + Next Page}> +
    Body
    +
    , + ); + + expect( + screen.getByRole('button', { name: 'Next Page' }), + ).toBeInTheDocument(); + }); + + it('does not render footer when footerActions is not provided', async () => { + const { container } = await renderInTestApp( + +
    Body
    +
    , + ); + + expect(container.querySelector('.bui-CardFooter')).not.toBeInTheDocument(); + }); + + it('renders JSX titles (e.g., icon + text)', async () => { + await renderInTestApp( + + 🏠 Home Card + + } + > +
    Body
    +
    , + ); + + expect(screen.getByText('Home Card')).toBeInTheDocument(); + }); +}); diff --git a/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.tsx b/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.tsx new file mode 100644 index 0000000000..8d28665757 --- /dev/null +++ b/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.tsx @@ -0,0 +1,84 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ReactNode } from 'react'; +import { + Card, + CardHeader, + CardBody, + CardFooter, + Text, + Flex, +} from '@backstage/ui'; +import { makeStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; + +const useStyles = makeStyles({ + root: { + height: '100%', + }, + title: { + minWidth: 0, + overflow: 'hidden', + }, + footer: { + display: 'flex', + justifyContent: 'flex-end', + }, +}); + +/** @public */ +export interface EntityInfoCardProps { + title?: ReactNode; + headerActions?: ReactNode; + footerActions?: ReactNode; + children?: ReactNode; + className?: string; +} + +/** @public */ +export function EntityInfoCard(props: EntityInfoCardProps) { + const { title, headerActions, footerActions, children, className } = props; + const classes = useStyles(); + + return ( + + {title && ( + + + + {title} + + {headerActions && ( + + {headerActions} + + )} + + + )} + {children} + {footerActions && ( + {footerActions} + )} + + ); +} diff --git a/plugins/catalog-react/src/components/EntityInfoCard/index.ts b/plugins/catalog-react/src/components/EntityInfoCard/index.ts new file mode 100644 index 0000000000..1999a228b1 --- /dev/null +++ b/plugins/catalog-react/src/components/EntityInfoCard/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { EntityInfoCard } from './EntityInfoCard'; +export type { EntityInfoCardProps } from './EntityInfoCard'; diff --git a/plugins/catalog-react/src/components/EntityKindPicker/EntityKindPicker.test.tsx b/plugins/catalog-react/src/components/EntityKindPicker/EntityKindPicker.test.tsx index b2de7522b6..9fa8e6e097 100644 --- a/plugins/catalog-react/src/components/EntityKindPicker/EntityKindPicker.test.tsx +++ b/plugins/catalog-react/src/components/EntityKindPicker/EntityKindPicker.test.tsx @@ -19,6 +19,7 @@ import { Entity } from '@backstage/catalog-model'; import { ApiProvider } from '@backstage/core-app-api'; import { alertApiRef } from '@backstage/core-plugin-api'; import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils'; +import { mockApis } from '@backstage/frontend-test-utils'; import { fireEvent, waitFor, screen, within } from '@testing-library/react'; import { capitalize } from 'lodash'; import { catalogApiRef } from '../../api'; @@ -65,12 +66,7 @@ describe('', () => { } as GetEntityFacetsResponse), }, ], - [ - alertApiRef, - { - post: jest.fn(), - }, - ], + [alertApiRef, mockApis.alert()], ); it('renders available entity kinds', async () => { diff --git a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.test.tsx b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.test.tsx index b8c8e58d32..87c5c9a002 100644 --- a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.test.tsx +++ b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.test.tsx @@ -362,6 +362,76 @@ describe('', () => { owners: new EntityOwnerFilter(['group:default/team-b']), }); }); + + it('calls fetch with lowercased input and displays results', async () => { + const updateFilters = jest.fn(); + const someOwnerEntities: Entity[] = [ + { + apiVersion: '1', + kind: 'Group', + metadata: { + name: 'some-owner', + }, + }, + { + apiVersion: '1', + kind: 'Group', + metadata: { + name: 'some-owner-2', + }, + spec: { + profile: { + displayName: 'Some Owner 2', + }, + }, + }, + ]; + mockCatalogApi.queryEntities.mockImplementation(async _request => { + const totalItems = 2; + return { + items: someOwnerEntities, + pageInfo: { + nextCursor: '', + }, + totalItems, + }; + }); + await renderInTestApp( + + + + + , + ); + + expect(mockCatalogApi.getEntitiesByRefs).not.toHaveBeenCalled(); + expect(updateFilters).toHaveBeenLastCalledWith({ + owners: undefined, + }); + + const input = screen.getByRole('textbox'); + fireEvent.change(input, { target: { value: 'Some-Owner' } }); + + await waitFor(() => + expect(screen.getByText('some-owner')).toBeInTheDocument(), + ); + expect(mockCatalogApi.queryEntities).toHaveBeenLastCalledWith( + expect.objectContaining({ + fullTextFilter: expect.objectContaining({ + term: 'some-owner', + }), + }), + ); + + fireEvent.click(screen.getByText('some-owner')); + expect(updateFilters).toHaveBeenLastCalledWith({ + owners: new EntityOwnerFilter(['group:default/some-owner']), + }); + }); }); describe('', () => { diff --git a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx index 247a3784f4..291fcf5376 100644 --- a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx +++ b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx @@ -33,11 +33,15 @@ import { EntityOwnerFilter } from '../../filters'; import { useDebouncedEffect } from '@react-hookz/web'; import PersonIcon from '@material-ui/icons/Person'; import GroupIcon from '@material-ui/icons/Group'; -import { humanizeEntity, humanizeEntityRef } from '../EntityRefLink/humanize'; +import { + entityPresentationApiRef, + entityPresentationSnapshot, +} from '../../apis'; import { useFetchEntities } from './useFetchEntities'; import { withStyles } from '@material-ui/core/styles'; import { useEntityPresentation } from '../../apis'; import { catalogReactTranslationRef } from '../../translation'; +import { useApiHolder } from '@backstage/core-plugin-api'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { CatalogAutocomplete } from '../CatalogAutocomplete'; @@ -124,6 +128,8 @@ function RenderOptionLabel(props: { entity: Entity; isSelected: boolean }) { export const EntityOwnerPicker = (props?: EntityOwnerPickerProps) => { const classes = useStyles(); const { mode = 'owners-only' } = props || {}; + const apis = useApiHolder(); + const entityPresentationApi = apis.get(entityPresentationApiRef); const { updateFilters, filters, @@ -146,7 +152,11 @@ export const EntityOwnerPicker = (props?: EntityOwnerPickerProps) => { mode, initialSelectedOwnersRefs: selectedOwners, }); - useDebouncedEffect(() => handleFetch({ text }), [text, handleFetch], 250); + useDebouncedEffect( + () => handleFetch({ text: text.toLocaleLowerCase('en-US') }), + [text, handleFetch], + 250, + ); const availableOwners = value?.items || []; @@ -199,7 +209,11 @@ export const EntityOwnerPicker = (props?: EntityOwnerPickerProps) => { defaultNamespace: 'default', }) : o; - return humanizeEntity(entity, humanizeEntityRef(entity)); + return entityPresentationSnapshot( + entity, + undefined, + entityPresentationApi, + ).primaryTitle; }} onChange={(_: object, owners) => { setText(''); diff --git a/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.stories.tsx b/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.stories.tsx index 7c663de0d6..a9cf501728 100644 --- a/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.stories.tsx +++ b/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.stories.tsx @@ -89,6 +89,7 @@ export default { }, ), ], + tags: ['!manifest'], }; export const Default = (args: EntityPeekAheadPopoverProps) => ( diff --git a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.stories.tsx b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.stories.tsx index f75c3e743b..9bdb077228 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.stories.tsx +++ b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.stories.tsx @@ -33,6 +33,7 @@ export default { }, }), ], + tags: ['!manifest'], }; export const Default = (args: EntityRefLinkProps) => ( diff --git a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.tsx b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.tsx index 40c738a66e..8af051db8c 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.tsx +++ b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.tsx @@ -17,7 +17,7 @@ import { CompoundEntityRef, Entity } from '@backstage/catalog-model'; import { Link, LinkProps } from '@backstage/core-components'; import { useRouteRef } from '@backstage/core-plugin-api'; -import { ReactNode, forwardRef } from 'react'; +import { ReactNode, forwardRef, useCallback } from 'react'; import { entityRouteParams, entityRouteRef } from '../../routes'; import { EntityDisplayName } from '../EntityDisplayName'; @@ -54,7 +54,7 @@ export const EntityRefLink = forwardRef( disableTooltip, ...linkProps } = props; - const entityRoute = useEntityRoute(props.entityRef); + const entityLink = useEntityRefLink(); const content = children ?? title ?? ( ( ); return ( - + {content} ); }, ) as (props: EntityRefLinkProps) => JSX.Element; -// Hook that computes the route to a given entity / ref. This is a bit -// contrived, because it tries to retain the casing of the entity name if -// present, but not of other parts. This is in an attempt to make slightly more -// nice-looking URLs. -function useEntityRoute( +/** + * Returns a function that generates a route path to the given entity. + * + * @public + */ +export function useEntityRefLink(): ( entityRef: Entity | CompoundEntityRef | string, -): string { +) => string { const entityRoute = useRouteRef(entityRouteRef); - const routeParams = entityRouteParams(entityRef, { encodeParams: true }); - - return entityRoute(routeParams); + return useCallback( + (entityRef: Entity | CompoundEntityRef | string) => { + const routeParams = entityRouteParams(entityRef, { encodeParams: true }); + return entityRoute(routeParams); + }, + [entityRoute], + ); } diff --git a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.stories.tsx b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.stories.tsx index b34b5db8d7..ade279b1b5 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.stories.tsx +++ b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.stories.tsx @@ -34,6 +34,7 @@ export default { }, }), ], + tags: ['!manifest'], }; export const Default = ( diff --git a/plugins/catalog-react/src/components/EntityRefLink/humanize.ts b/plugins/catalog-react/src/components/EntityRefLink/humanize.ts index 478f0dce9d..fe697365b6 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/humanize.ts +++ b/plugins/catalog-react/src/components/EntityRefLink/humanize.ts @@ -25,6 +25,12 @@ import get from 'lodash/get'; * @param defaultNamespace - if set to false then namespace is never omitted, * if set to string which matches namespace of entity then omitted * + * @deprecated Use {@link useEntityPresentation} or {@link EntityDisplayName} + * in React components. In non-React contexts such as sort comparators or + * data mappers, use {@link entityPresentationSnapshot}. These provide richer + * display names using `metadata.title` and `spec.profile.displayName` in + * addition to the entity ref. + * * @public **/ export function humanizeEntityRef( @@ -76,6 +82,10 @@ export function humanizeEntityRef( * * If neither of those are found or populated, fallback to `defaultName`. * + * @deprecated Use {@link useEntityPresentation} or {@link EntityDisplayName} + * in React components. In non-React contexts, use + * {@link entityPresentationSnapshot}. + * * @param entity - Entity to convert. * @param defaultName - If entity readable name is not available, `defaultName` will be returned. * @returns Readable name, defaults to `defaultName`. diff --git a/plugins/catalog-react/src/components/EntityRefLink/index.ts b/plugins/catalog-react/src/components/EntityRefLink/index.ts index 50ac3d4534..82773da141 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/index.ts +++ b/plugins/catalog-react/src/components/EntityRefLink/index.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -export { EntityRefLink } from './EntityRefLink'; +export { EntityRefLink, useEntityRefLink } from './EntityRefLink'; export type { EntityRefLinkProps } from './EntityRefLink'; export { EntityRefLinks } from './EntityRefLinks'; export type { EntityRefLinksProps } from './EntityRefLinks'; diff --git a/plugins/catalog-react/src/components/EntityRelationCard/EntityRelationCard.test.tsx b/plugins/catalog-react/src/components/EntityRelationCard/EntityRelationCard.test.tsx new file mode 100644 index 0000000000..06e9059ee3 --- /dev/null +++ b/plugins/catalog-react/src/components/EntityRelationCard/EntityRelationCard.test.tsx @@ -0,0 +1,194 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Entity, RELATION_HAS_PART } from '@backstage/catalog-model'; +import { + catalogApiRef, + EntityProvider, + entityRouteRef, +} from '@backstage/plugin-catalog-react'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { screen } from '@testing-library/react'; +import { ComponentType, PropsWithChildren, ReactNode } from 'react'; +import { EntityRelationCard } from './EntityRelationCard'; +import { entityColumnPresets } from '../EntityDataTable/presets'; +import { catalogApiMock } from '../../testUtils/catalogApiMock'; + +describe('', () => { + const catalogApi = catalogApiMock.mock(); + let Wrapper: ComponentType>; + + beforeEach(() => { + Wrapper = ({ children }: { children?: ReactNode }) => ( + + {children} + + ); + }); + + afterEach(() => jest.resetAllMocks()); + + it('renders the card title', async () => { + const entity: Entity = { + apiVersion: 'v1', + kind: 'System', + metadata: { name: 'my-system', namespace: 'my-namespace' }, + relations: [], + }; + + await renderInTestApp( + + + + + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name': entityRouteRef, + }, + }, + ); + + expect(screen.getByText('Has components')).toBeInTheDocument(); + }); + + it('shows empty state message and help link when no related entities', async () => { + const entity: Entity = { + apiVersion: 'v1', + kind: 'System', + metadata: { name: 'my-system', namespace: 'my-namespace' }, + relations: [], + }; + + await renderInTestApp( + + + + + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name': entityRouteRef, + }, + }, + ); + + expect( + screen.getByText(/No component is part of this system/), + ).toBeInTheDocument(); + const link = screen.getByRole('link'); + expect(link).toHaveAttribute('href', 'https://backstage.io/docs'); + }); + + it('shows related entities in the table', async () => { + const entity: Entity = { + apiVersion: 'v1', + kind: 'System', + metadata: { name: 'my-system', namespace: 'my-namespace' }, + relations: [ + { + targetRef: 'component:my-namespace/my-component', + type: RELATION_HAS_PART, + }, + ], + }; + catalogApi.getEntitiesByRefs.mockResolvedValue({ + items: [ + { + apiVersion: 'v1', + kind: 'Component', + metadata: { name: 'my-component', namespace: 'my-namespace' }, + spec: { type: 'service', lifecycle: 'production' }, + }, + ], + }); + + await renderInTestApp( + + + + + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name': entityRouteRef, + }, + }, + ); + + expect(screen.getByText('Has components')).toBeInTheDocument(); + expect(await screen.findByText(/my-component/)).toBeInTheDocument(); + }); + + it('shows error state when loading fails', async () => { + const entity: Entity = { + apiVersion: 'v1', + kind: 'System', + metadata: { name: 'my-system', namespace: 'my-namespace' }, + relations: [ + { + targetRef: 'component:my-namespace/my-component', + type: RELATION_HAS_PART, + }, + ], + }; + catalogApi.getEntitiesByRefs.mockRejectedValue( + new Error('Request failed with 500'), + ); + + await renderInTestApp( + + + + + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name': entityRouteRef, + }, + }, + ); + + expect(screen.getByText('Has components')).toBeInTheDocument(); + expect( + await screen.findByText(/Request failed with 500/), + ).toBeInTheDocument(); + }); +}); diff --git a/plugins/catalog-react/src/components/EntityRelationCard/EntityRelationCard.tsx b/plugins/catalog-react/src/components/EntityRelationCard/EntityRelationCard.tsx new file mode 100644 index 0000000000..6554881fd5 --- /dev/null +++ b/plugins/catalog-react/src/components/EntityRelationCard/EntityRelationCard.tsx @@ -0,0 +1,85 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Alert, Link, Text } from '@backstage/ui'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { EntityInfoCard } from '../EntityInfoCard'; +import { EntityDataTable } from '../EntityDataTable'; +import { EntityColumnConfig } from '../EntityDataTable/columnFactories'; +import { useEntity, useRelatedEntities } from '../../hooks'; +import { catalogReactTranslationRef } from '../../translation'; + +/** @public */ +export interface EntityRelationCardProps { + title: string; + relationType: string; + entityKind?: string; + columnConfig: EntityColumnConfig[]; + emptyState?: { + message: string; + helpLink?: string; + }; + className?: string; +} + +/** @public */ +export function EntityRelationCard(props: EntityRelationCardProps) { + const { + title, + relationType, + entityKind, + columnConfig, + emptyState, + className, + } = props; + const { t } = useTranslationRef(catalogReactTranslationRef); + const { entity } = useEntity(); + const { entities, loading, error } = useRelatedEntities(entity, { + type: relationType, + kind: entityKind, + }); + + return ( + + {error ? ( + + ) : ( + + {emptyState.message}{' '} + {emptyState.helpLink && ( + + {t('entityRelationCard.emptyHelpLinkTitle')} + + )} + + ) + } + /> + )} + + ); +} diff --git a/plugins/catalog-react/src/components/EntityRelationCard/index.ts b/plugins/catalog-react/src/components/EntityRelationCard/index.ts new file mode 100644 index 0000000000..0f42b61fb7 --- /dev/null +++ b/plugins/catalog-react/src/components/EntityRelationCard/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { EntityRelationCard } from './EntityRelationCard'; +export type { EntityRelationCardProps } from './EntityRelationCard'; diff --git a/plugins/catalog-react/src/components/EntityTable/columns.tsx b/plugins/catalog-react/src/components/EntityTable/columns.tsx index 1bea56dc27..82dd8e7309 100644 --- a/plugins/catalog-react/src/components/EntityTable/columns.tsx +++ b/plugins/catalog-react/src/components/EntityTable/columns.tsx @@ -22,26 +22,23 @@ import { } from '@backstage/catalog-model'; import { OverflowTooltip, TableColumn } from '@backstage/core-components'; import { getEntityRelations } from '../../utils'; -import { - EntityRefLink, - EntityRefLinks, - humanizeEntityRef, -} from '../EntityRefLink'; +import { EntityRefLink, EntityRefLinks } from '../EntityRefLink'; +import { entityPresentationSnapshot, EntityPresentationApi } from '../../apis'; import { EntityTableColumnTitle } from './TitleColumn'; /** @public */ export const columnFactories = Object.freeze({ createEntityRefColumn(options: { defaultKind?: string; + entityPresentationApi?: EntityPresentationApi; }): TableColumn { - const { defaultKind } = options; + const { defaultKind, entityPresentationApi } = options; function formatContent(entity: T): string { - return ( - entity.metadata?.title || - humanizeEntityRef(entity, { - defaultKind, - }) - ); + return entityPresentationSnapshot( + entity, + { defaultKind }, + entityPresentationApi, + ).primaryTitle; } return { @@ -75,8 +72,15 @@ export const columnFactories = Object.freeze({ relation: string; defaultKind?: string; filter?: { kind: string }; + entityPresentationApi?: EntityPresentationApi; }): TableColumn { - const { title, relation, defaultKind, filter: entityFilter } = options; + const { + title, + relation, + defaultKind, + filter: entityFilter, + entityPresentationApi, + } = options; function getRelations(entity: T): CompoundEntityRef[] { return getEntityRelations(entity, relation, entityFilter); @@ -84,7 +88,14 @@ export const columnFactories = Object.freeze({ function formatContent(entity: T): string { return getRelations(entity) - .map(r => humanizeEntityRef(r, { defaultKind })) + .map( + r => + entityPresentationSnapshot( + r, + { defaultKind }, + entityPresentationApi, + ).primaryTitle, + ) .join(', '); } diff --git a/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx b/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx index 281b94251e..58e731f197 100644 --- a/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx +++ b/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx @@ -23,6 +23,7 @@ import { EntityKindFilter, EntityTypeFilter } from '../../filters'; import { alertApiRef } from '@backstage/core-plugin-api'; import { ApiProvider } from '@backstage/core-app-api'; import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils'; +import { mockApis } from '@backstage/frontend-test-utils'; import { GetEntityFacetsResponse } from '@backstage/catalog-client'; const entities: Entity[] = [ @@ -72,12 +73,7 @@ const apis = TestApiRegistry.from( } as GetEntityFacetsResponse), }, ], - [ - alertApiRef, - { - post: jest.fn(), - }, - ], + [alertApiRef, mockApis.alert()], ); describe('', () => { diff --git a/plugins/catalog-react/src/components/InspectEntityDialog/InspectEntityDialog.tsx b/plugins/catalog-react/src/components/InspectEntityDialog/InspectEntityDialog.tsx index 2b047bf762..fa5e87fcd8 100644 --- a/plugins/catalog-react/src/components/InspectEntityDialog/InspectEntityDialog.tsx +++ b/plugins/catalog-react/src/components/InspectEntityDialog/InspectEntityDialog.tsx @@ -15,16 +15,16 @@ */ import { Entity } from '@backstage/catalog-model'; -import Box from '@material-ui/core/Box'; -import Button from '@material-ui/core/Button'; -import Dialog from '@material-ui/core/Dialog'; -import DialogActions from '@material-ui/core/DialogActions'; -import DialogContent from '@material-ui/core/DialogContent'; -import DialogTitle from '@material-ui/core/DialogTitle'; -import Tab from '@material-ui/core/Tab'; -import Tabs from '@material-ui/core/Tabs'; -import { makeStyles } from '@material-ui/core/styles'; -import { ComponentProps, useEffect, useState, ReactNode, useMemo } from 'react'; +import { + Dialog, + DialogHeader, + DialogBody, + Tabs, + TabList, + Tab, + TabPanel, +} from '@backstage/ui'; +import { useMemo } from 'react'; import { AncestryPage } from './components/AncestryPage'; import { ColocatedPage } from './components/ColocatedPage'; import { JsonPage } from './components/JsonPage'; @@ -33,64 +33,68 @@ import { YamlPage } from './components/YamlPage'; import { catalogReactTranslationRef } from '../../translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; -const useStyles = makeStyles(theme => ({ - fullHeightDialog: { - height: 'calc(100% - 64px)', - }, - root: { - display: 'flex', - flexGrow: 1, - width: '100%', - backgroundColor: theme.palette.background.paper, - }, - tabs: { - borderRight: `1px solid ${theme.palette.divider}`, - flexShrink: 0, - }, - tabContents: { - flexGrow: 1, - overflowX: 'auto', - }, -})); - -function TabPanel(props: { - children?: ReactNode; - index: number; - value: number; -}) { - const { children, value, index, ...other } = props; - const classes = useStyles(); - return ( - - ); -} - -function a11yProps(index: number) { - return { - id: `vertical-tab-${index}`, - 'aria-controls': `vertical-tabpanel-${index}`, - }; -} - type TabKey = 'overview' | 'ancestry' | 'colocated' | 'json' | 'yaml'; -type TabNames = Record< - NonNullable['initialTab']>, - string ->; +const TAB_KEYS: TabKey[] = [ + 'overview', + 'ancestry', + 'colocated', + 'json', + 'yaml', +]; + +function DialogContents(props: { + entity: Entity; + initialTab?: TabKey; + onSelect?: (tab: string) => void; +}) { + const { entity, initialTab, onSelect } = props; + const { t } = useTranslationRef(catalogReactTranslationRef); + + const tabNames = useMemo( + () => ({ + overview: t('inspectEntityDialog.tabNames.overview'), + ancestry: t('inspectEntityDialog.tabNames.ancestry'), + colocated: t('inspectEntityDialog.tabNames.colocated'), + json: t('inspectEntityDialog.tabNames.json'), + yaml: t('inspectEntityDialog.tabNames.yaml'), + }), + [t], + ); + + const tabContent: Record = { + overview: , + ancestry: , + colocated: , + json: , + yaml: , + }; + + return ( + <> + {t('inspectEntityDialog.title')} + + onSelect?.(key as string)} + > + + {TAB_KEYS.map(tab => ( + + {tabNames[tab]} + + ))} + + {TAB_KEYS.map(tab => ( + + {tabContent[tab]} + + ))} + + + + ); +} /** * A dialog that lets users inspect the low level details of their entities. @@ -104,90 +108,26 @@ export function InspectEntityDialog(props: { onClose: () => void; onSelect?: (tab: string) => void; }) { - const classes = useStyles(); - const { t } = useTranslationRef(catalogReactTranslationRef); + const { open, entity, initialTab, onClose, onSelect } = props; - const tabNames: TabNames = useMemo( - () => ({ - overview: t('inspectEntityDialog.tabNames.overview'), - ancestry: t('inspectEntityDialog.tabNames.ancestry'), - colocated: t('inspectEntityDialog.tabNames.colocated'), - json: t('inspectEntityDialog.tabNames.json'), - yaml: t('inspectEntityDialog.tabNames.yaml'), - }), - [t], - ); - - const tabs = Object.keys(tabNames) as TabKey[]; - - const [activeTab, setActiveTab] = useState( - getTabIndex(tabs, props.initialTab), - ); - - useEffect(() => { - getTabIndex(tabs, props.initialTab); - }, [props.open, props.initialTab, tabs]); - - if (!props.entity) { + if (!entity) { return null; } return ( !isOpen && onClose()} + width="940px" + height="100vh" > - - {t('inspectEntityDialog.title')} - - -
    - { - setActiveTab(tabIndex); - props.onSelect?.(tabs[tabIndex]); - }} - aria-label={t('inspectEntityDialog.tabsAriaLabel')} - className={classes.tabs} - > - {tabs.map((tab, index) => ( - - ))} - - - - - - - - - - - - - - - - - -
    -
    - - - + {open && ( + + )}
    ); } - -function getTabIndex(allTabs: string[], initialTab: TabKey | undefined) { - return initialTab ? allTabs.indexOf(initialTab) : 0; -} diff --git a/plugins/catalog-react/src/components/InspectEntityDialog/components/AncestryPage.tsx b/plugins/catalog-react/src/components/InspectEntityDialog/components/AncestryPage.tsx index 771eb7b279..d7b36882e8 100644 --- a/plugins/catalog-react/src/components/InspectEntityDialog/components/AncestryPage.tsx +++ b/plugins/catalog-react/src/components/InspectEntityDialog/components/AncestryPage.tsx @@ -27,16 +27,15 @@ import { ResponseErrorPanel, } from '@backstage/core-components'; import { useApi, useApp, useRouteRef } from '@backstage/core-plugin-api'; -import Box from '@material-ui/core/Box'; -import DialogContentText from '@material-ui/core/DialogContentText'; +import { Text, Box } from '@backstage/ui'; import { makeStyles } from '@material-ui/core/styles'; import classNames from 'classnames'; import { useLayoutEffect, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import useAsync from 'react-use/esm/useAsync'; import { catalogApiRef } from '../../../api'; -import { humanizeEntityRef } from '../../EntityRefLink'; import { entityRouteRef } from '../../../routes'; +import { useEntityPresentation } from '../../../apis'; import { EntityKindIcon } from './EntityKindIcon'; import { catalogReactTranslationRef } from '../../../translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; @@ -137,15 +136,7 @@ function CustomNode({ node }: DependencyGraphTypes.RenderNodeProps) { const paddedWidth = paddedIconWidth + width + padding * 2; const paddedHeight = height + padding * 2; - const displayTitle = - node.metadata.title || - (node.kind && node.metadata.name && node.metadata.namespace - ? humanizeEntityRef({ - kind: node.kind, - name: node.metadata.name, - namespace: node.metadata.namespace || '', - }) - : node.id); + const { primaryTitle: displayTitle } = useEntityPresentation(node); const onClick = () => { navigate( @@ -209,19 +200,18 @@ export function AncestryPage(props: { entity: Entity }) { return ( <> - - {t('inspectEntityDialog.ancestryPage.title')} - - - {t('inspectEntityDialog.ancestryPage.description', { - processorsLink: ( - - {t('inspectEntityDialog.ancestryPage.processorsLink')} - - ), - })} - - + + + {t('inspectEntityDialog.ancestryPage.description', { + processorsLink: ( + + {t('inspectEntityDialog.ancestryPage.processorsLink')} + + ), + })} + + + and
    are used intentionally for +// semantic markup. The react/forbid-elements rule predates the BUI migration. +/* eslint-disable react/forbid-elements */ + import { Entity, ANNOTATION_LOCATION, @@ -22,22 +26,40 @@ import { } from '@backstage/catalog-model'; import { Progress, ResponseErrorPanel } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; -import DialogContentText from '@material-ui/core/DialogContentText'; -import List from '@material-ui/core/List'; -import ListItem from '@material-ui/core/ListItem'; -import { makeStyles } from '@material-ui/core/styles'; -import Alert from '@material-ui/lab/Alert'; +import { Text, Alert } from '@backstage/ui'; import useAsync from 'react-use/esm/useAsync'; import { catalogApiRef } from '../../../api'; import { EntityRefLink } from '../../EntityRefLink'; -import { KeyValueListItem, ListItemText } from './common'; +import { makeStyles } from '@material-ui/core/styles'; +import { ListSection, ListItemRow } from './common'; + import { catalogReactTranslationRef } from '../../../translation'; + import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; const useStyles = makeStyles({ - root: { - display: 'flex', - flexDirection: 'column', + header: { + paddingLeft: 'var(--bui-space-4)', + marginTop: 'var(--bui-space-4)', + marginBottom: 'var(--bui-space-4)', + }, + headerLabel: { + margin: 0, + fontFamily: 'monospace', + fontSize: 'var(--bui-font-size-3)', + fontWeight: 'var(--bui-font-weight-regular)' as any, + }, + entityList: { + marginTop: 'var(--bui-space-4)', + }, + headerValue: { + margin: 0, + marginTop: 'var(--bui-space-1)', + fontFamily: 'monospace', + fontSize: 'var(--bui-font-size-3)', + overflow: 'hidden', + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', }, }); @@ -63,7 +85,11 @@ function useColocated(entity: Entity): { ? [{ [`metadata.annotations.${ANNOTATION_LOCATION}`]: location }] : []), ...(origin - ? [{ [`metadata.annotations.${ANNOTATION_ORIGIN_LOCATION}`]: origin }] + ? [ + { + [`metadata.annotations.${ANNOTATION_ORIGIN_LOCATION}`]: origin, + }, + ] : []), ], }); @@ -82,15 +108,27 @@ function useColocated(entity: Entity): { } function EntityList(props: { entities: Entity[]; header?: [string, string] }) { + const classes = useStyles(); + const { t } = useTranslationRef(catalogReactTranslationRef); return ( - - {props.header && } - {props.entities.map(entity => ( - - } /> - - ))} - + <> + {props.header && ( +
    +

    {props.header[0]}

    +

    {props.header[1]}

    +
    + )} + + {props.entities.map(entity => ( + + + + ))} + + ); } @@ -108,15 +146,19 @@ function Contents(props: { entity: Entity }) { if (!location && !originLocation) { return ( - - {t('inspectEntityDialog.colocatedPage.alertNoLocation')} - + ); } else if (!colocatedEntities?.length) { return ( - - {t('inspectEntityDialog.colocatedPage.alertNoEntity')} - + ); } @@ -157,19 +199,11 @@ function Contents(props: { entity: Entity }) { } export function ColocatedPage(props: { entity: Entity }) { - const classes = useStyles(); const { t } = useTranslationRef(catalogReactTranslationRef); return ( <> - - {t('inspectEntityDialog.colocatedPage.title')} - - - {t('inspectEntityDialog.colocatedPage.description')} - -
    - -
    + {t('inspectEntityDialog.colocatedPage.description')} + ); } diff --git a/plugins/catalog-react/src/components/InspectEntityDialog/components/JsonPage.tsx b/plugins/catalog-react/src/components/InspectEntityDialog/components/JsonPage.tsx index b5cfdfa219..e238cd0475 100644 --- a/plugins/catalog-react/src/components/InspectEntityDialog/components/JsonPage.tsx +++ b/plugins/catalog-react/src/components/InspectEntityDialog/components/JsonPage.tsx @@ -16,7 +16,7 @@ import { Entity } from '@backstage/catalog-model'; import { CodeSnippet } from '@backstage/core-components'; -import DialogContentText from '@material-ui/core/DialogContentText'; +import { Text } from '@backstage/ui'; import { sortKeys } from './util'; import { catalogReactTranslationRef } from '../../../translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; @@ -25,21 +25,14 @@ export function JsonPage(props: { entity: Entity }) { const { t } = useTranslationRef(catalogReactTranslationRef); return ( <> - - {t('inspectEntityDialog.jsonPage.title')} - - - {t('inspectEntityDialog.jsonPage.description')} - - -
    - -
    -
    + {t('inspectEntityDialog.jsonPage.description')} +
    + +
    ); } diff --git a/plugins/catalog-react/src/components/InspectEntityDialog/components/OverviewPage.test.tsx b/plugins/catalog-react/src/components/InspectEntityDialog/components/OverviewPage.test.tsx new file mode 100644 index 0000000000..077e3c1be5 --- /dev/null +++ b/plugins/catalog-react/src/components/InspectEntityDialog/components/OverviewPage.test.tsx @@ -0,0 +1,103 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { renderInTestApp } from '@backstage/test-utils'; +import { screen } from '@testing-library/react'; +import { entityRouteRef } from '../../../routes'; +import { OverviewPage } from './OverviewPage'; + +const mountedRoutes = { + '/catalog/:namespace/:kind/:name/*': entityRouteRef, +}; + +const entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + namespace: 'default', + name: 'test-component', + uid: 'test-uid-123', + etag: 'test-etag-456', + annotations: { + 'backstage.io/source-location': 'url:https://github.com/example/repo', + 'backstage.io/techdocs-ref': 'dir:.', + }, + labels: { + 'backstage.io/custom': 'value', + }, + tags: ['java', 'data'], + }, + spec: { + type: 'service', + lifecycle: 'production', + owner: 'team-a', + }, + relations: [ + { type: 'ownedBy', targetRef: 'group:default/team-a' }, + { type: 'ownedBy', targetRef: 'group:default/team-b' }, + { type: 'dependsOn', targetRef: 'component:default/other' }, + ], +} as any; + +describe('OverviewPage', () => { + it('renders identity key-value pairs', async () => { + await renderInTestApp(, { mountedRoutes }); + + const terms = screen.getAllByRole('term'); + const definitions = screen.getAllByRole('definition'); + const termTexts = terms.map(el => el.textContent); + + expect(termTexts).toContain('apiVersion'); + expect(termTexts).toContain('kind'); + expect(termTexts).toContain('uid'); + expect(termTexts).toContain('etag'); + expect(termTexts).toContain('entityRef'); + + const defTexts = definitions.map(el => el.textContent); + expect(defTexts).toContain('backstage.io/v1alpha1'); + expect(defTexts).toContain('Component'); + expect(defTexts).toContain('test-uid-123'); + expect(defTexts).toContain('test-etag-456'); + expect(defTexts).toContain('component:default/test-component'); + }); + + it('renders annotation values as links when they start with https:// or url:https://', async () => { + await renderInTestApp(, { mountedRoutes }); + + // url:https:// prefix is stripped from href but full value shown as link text + // The accessible name includes ", Opens in a new window" appended by the Link component + const sourceLocationLink = await screen.findByRole('link', { + name: /url:https:\/\/github\.com\/example\/repo/, + }); + expect(sourceLocationLink).toHaveAttribute( + 'href', + 'https://github.com/example/repo', + ); + + // Plain non-URL annotation value renders as text, not a link + expect( + screen.queryByRole('link', { name: 'dir:.' }), + ).not.toBeInTheDocument(); + expect(screen.getByText('dir:.')).toBeInTheDocument(); + }); + + it('renders tags', async () => { + await renderInTestApp(, { mountedRoutes }); + + expect(await screen.findByText('java')).toBeInTheDocument(); + expect(screen.getByText('data')).toBeInTheDocument(); + }); +}); diff --git a/plugins/catalog-react/src/components/InspectEntityDialog/components/OverviewPage.tsx b/plugins/catalog-react/src/components/InspectEntityDialog/components/OverviewPage.tsx index 4feabf9c38..c7ff12a486 100644 --- a/plugins/catalog-react/src/components/InspectEntityDialog/components/OverviewPage.tsx +++ b/plugins/catalog-react/src/components/InspectEntityDialog/components/OverviewPage.tsx @@ -15,36 +15,228 @@ */ import { AlphaEntity } from '@backstage/catalog-model/alpha'; -import Box from '@material-ui/core/Box'; -import DialogContentText from '@material-ui/core/DialogContentText'; -import List from '@material-ui/core/List'; -import ListItem from '@material-ui/core/ListItem'; -import ListItemIcon from '@material-ui/core/ListItemIcon'; -import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'; -import Typography from '@material-ui/core/Typography'; +import { Link } from '@backstage/core-components'; +import { + Text, + Box, + Flex, + Card, + CardHeader, + CardBody, + TagGroup, + Tag, + ButtonIcon, + ButtonLink, + VisuallyHidden, +} from '@backstage/ui'; import { makeStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import { RiFileCopyLine, RiCheckLine, RiQuestionLine } from '@remixicon/react'; import groupBy from 'lodash/groupBy'; import sortBy from 'lodash/sortBy'; +import { ReactNode, useEffect, useRef, useState } from 'react'; import { EntityRefLink } from '../../EntityRefLink'; -import { - Container, - HelpIcon, - KeyValueListItem, - ListItemText, - ListSubheader, -} from './common'; +import { ListSection, ListItemRow } from './common'; import { stringifyEntityRef } from '@backstage/catalog-model'; -import { CopyTextButton } from '@backstage/core-components'; import { catalogReactTranslationRef } from '../../../translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; const useStyles = makeStyles({ - root: { + headingWithIcon: { display: 'flex', - flexDirection: 'column', + alignItems: 'center', + }, + definitionList: { + margin: 0, + padding: 0, + }, + definitionItem: { + display: 'flex', + alignItems: 'flex-start', + marginTop: 'var(--bui-space-4)', + paddingLeft: 'var(--bui-space-4)', + fontFamily: 'monospace', + fontSize: 'var(--bui-font-size-3)', + '&:first-child': { + marginTop: 0, + }, + }, + definitionContent: { + flex: 1, + minWidth: 0, + }, + definitionKey: { + fontWeight: 'bold', + }, + definitionValue: { + margin: 0, + marginTop: 'var(--bui-space-1)', + overflow: 'hidden', + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + }, + copyAction: { + marginLeft: 'var(--bui-space-2)', + flexShrink: 0, + }, + relationGroup: { + '& + &': { + marginTop: 'var(--bui-space-4)', + }, + }, + monospace: { + fontFamily: 'monospace', + }, + sectionHeading: { + marginTop: 'var(--bui-space-3)', + }, + metadataList: { + marginTop: 'var(--bui-space-2)', + }, + relationList: { + marginTop: 'var(--bui-space-2)', + }, + tagGroup: { + marginTop: 'var(--bui-space-3)', + paddingLeft: 'var(--bui-space-4)', }, }); +// Extracts a link from a value, if possible +function findLink(value: string): string | undefined { + if (value.match(/^url:https?:\/\//)) { + return value.slice('url:'.length); + } + if (value.match(/^https?:\/\//)) { + return value; + } + return undefined; +} + +function entriesToItems(entries: [string, string][]) { + return entries.map(([key, value]) => { + const link = findLink(value); + return { + key, + value: link ? {value} : value, + }; + }); +} + +function CopyButton({ text, label }: { text: string; label: string }) { + const { t } = useTranslationRef(catalogReactTranslationRef); + const [copied, setCopied] = useState(false); + const timerRef = useRef>(); + + useEffect(() => () => clearTimeout(timerRef.current), []); + + const handlePress = async () => { + try { + await window.navigator.clipboard.writeText(text); + setCopied(true); + timerRef.current = setTimeout(() => setCopied(false), 2000); + } catch { + // Clipboard access denied or unavailable + } + }; + + return ( + <> + : } + aria-label={t('inspectEntityDialog.overviewPage.copyAriaLabel', { + label, + })} + variant="tertiary" + size="small" + onPress={handlePress} + /> + + {copied ? t('inspectEntityDialog.overviewPage.copiedStatus') : ''} + + + ); +} + +function HelpIcon(props: { to: string }) { + const { t } = useTranslationRef(catalogReactTranslationRef); + return ( + } + aria-label={t('inspectEntityDialog.overviewPage.helpLinkAriaLabel')} + /> + ); +} + +function Container(props: { + title: ReactNode; + helpLink?: string; + children: ReactNode; +}) { + const classes = useStyles(); + return ( + + + + {props.title} + {props.helpLink && } + + + {props.children} + + ); +} + +function ListSubheader(props: { children: ReactNode; className?: string }) { + const classes = useStyles(); + return ( + + {props.children} + + ); +} + +function KeyValueList(props: { + items: { key: string; value: ReactNode; copyable?: boolean }[]; + className?: string; + 'aria-label'?: string; +}) { + const classes = useStyles(); + return ( +
    + {props.items.map(item => ( +
    +
    +
    {item.key}
    +
    {item.value}
    +
    + {item.copyable && typeof item.value === 'string' && ( +
    + +
    + )} +
    + ))} +
    + ); +} + export function OverviewPage(props: { entity: AlphaEntity }) { const classes = useStyles(); const { @@ -64,142 +256,109 @@ export function OverviewPage(props: { entity: AlphaEntity }) { const entityRef = stringifyEntityRef(props.entity); return ( - <> - - {t('inspectEntityDialog.overviewPage.title')} - -
    - - - - - - - - - {spec?.type && ( - - - - )} - {metadata.uid && ( - - - - - - - )} - {metadata.etag && ( - - - - - - - )} - - - - - - - - + + + + - - {!!Object.keys(metadata.annotations || {}).length && ( - - {t('inspectEntityDialog.overviewPage.annotations')} - - - } - > - {Object.entries(metadata.annotations!).map(entry => ( - - ))} - - )} - {!!Object.keys(metadata.labels || {}).length && ( - - {t('inspectEntityDialog.overviewPage.labels')} - - } - > - {Object.entries(metadata.labels!).map(entry => ( - - ))} - - )} - {!!metadata.tags?.length && ( - - {t('inspectEntityDialog.overviewPage.tags')} - - } - > - {metadata.tags.map((tag, index) => ( - - - - - ))} - - )} - - - {!!relations.length && ( - - {Object.entries(groupedRelations).map( - ([type, groupRelations], index) => ( -
    - {type}}> - {groupRelations.map(group => ( - - - } - /> - - ))} - -
    - ), - )} -
    + + {!!Object.keys(metadata.annotations || {}).length && ( + <> + + {t('inspectEntityDialog.overviewPage.annotations')} + + + + )} - - {!!status.items?.length && ( - - {status.items.map((item, index) => ( -
    - - {item.level}: {item.type} - - {item.message} -
    - ))} -
    + {!!Object.keys(metadata.labels || {}).length && ( + <> + + {t('inspectEntityDialog.overviewPage.labels')} + + + )} -
    - + {!!metadata.tags?.length && ( + <> + + {t('inspectEntityDialog.overviewPage.tags')} + + + {metadata.tags.map(tag => ( + + {tag} + + ))} + + + )} + + + {!!relations.length && ( + + {Object.entries(groupedRelations).map(([type, groupRelations]) => ( +
    + + {type} + + + {groupRelations.map(group => ( + + + + ))} + +
    + ))} +
    + )} + + {!!status.items?.length && ( + + {status.items.map((item, index) => ( +
    + + {item.level}: {item.type} + + {item.message} +
    + ))} +
    + )} + ); } diff --git a/plugins/catalog-react/src/components/InspectEntityDialog/components/YamlPage.tsx b/plugins/catalog-react/src/components/InspectEntityDialog/components/YamlPage.tsx index 6763707810..e04fa3ae0b 100644 --- a/plugins/catalog-react/src/components/InspectEntityDialog/components/YamlPage.tsx +++ b/plugins/catalog-react/src/components/InspectEntityDialog/components/YamlPage.tsx @@ -16,7 +16,7 @@ import { Entity } from '@backstage/catalog-model'; import { CodeSnippet } from '@backstage/core-components'; -import DialogContentText from '@material-ui/core/DialogContentText'; +import { Text } from '@backstage/ui'; import YAML from 'yaml'; import { sortKeys } from './util'; import { catalogReactTranslationRef } from '../../../translation'; @@ -26,21 +26,14 @@ export function YamlPage(props: { entity: Entity }) { const { t } = useTranslationRef(catalogReactTranslationRef); return ( <> - - {t('inspectEntityDialog.yamlPage.title')} - - - {t('inspectEntityDialog.yamlPage.description')} - - -
    - -
    -
    + {t('inspectEntityDialog.yamlPage.description')} +
    + +
    ); } diff --git a/plugins/catalog-react/src/components/InspectEntityDialog/components/common.tsx b/plugins/catalog-react/src/components/InspectEntityDialog/components/common.tsx index ff8a66e10d..b335ff8e9f 100644 --- a/plugins/catalog-react/src/components/InspectEntityDialog/components/common.tsx +++ b/plugins/catalog-react/src/components/InspectEntityDialog/components/common.tsx @@ -14,113 +14,57 @@ * limitations under the License. */ -import { Link } from '@backstage/core-components'; -import Box from '@material-ui/core/Box'; -import Card from '@material-ui/core/Card'; -import CardContent from '@material-ui/core/CardContent'; -import ListItem from '@material-ui/core/ListItem'; -import ListItemIcon from '@material-ui/core/ListItemIcon'; -import MuiListItemText from '@material-ui/core/ListItemText'; -import MuiListSubheader from '@material-ui/core/ListSubheader'; -import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; -import HelpOutlineIcon from '@material-ui/icons/HelpOutline'; +import classNames from 'classnames'; import { ReactNode } from 'react'; -const useStyles = makeStyles(theme => ({ - root: { +const useStyles = makeStyles({ + list: { + listStyle: 'none', + margin: 0, + padding: 0, + }, + indented: { + paddingLeft: 'var(--bui-space-4)', + }, + listItem: { display: 'flex', - flexDirection: 'column', - }, - marginTop: { - marginTop: theme.spacing(2), - }, - helpIcon: { - marginLeft: theme.spacing(1), - color: theme.palette.text.disabled, - }, - monospace: { + alignItems: 'flex-start', + marginTop: 'var(--bui-space-1)', + paddingLeft: 'var(--bui-space-4)', fontFamily: 'monospace', + fontSize: 'var(--bui-font-size-3)', + '&:first-child': { + marginTop: 0, + }, }, -})); +}); -export function ListItemText(props: { - primary: ReactNode; - secondary?: ReactNode; -}) { - const classes = useStyles(); - return ( - - ); -} - -export function ListSubheader(props: { children?: ReactNode }) { - const classes = useStyles(); - return ( - - {props.children} - - ); -} - -export function Container(props: { - title: ReactNode; - helpLink?: string; +export function ListSection(props: { children: ReactNode; -}) { - return ( - - - - - {props.title} - {props.helpLink && } - - {props.children} - - - - ); -} - -// Extracts a link from a value, if possible -function findLink(value: string): string | undefined { - if (value.match(/^url:https?:\/\//)) { - return value.slice('url:'.length); - } - if (value.match(/^https?:\/\//)) { - return value; - } - return undefined; -} - -export function KeyValueListItem(props: { indent?: boolean; - entry: [string, string]; + className?: string; + 'aria-label'?: string; }) { - const [key, value] = props.entry; - const link = findLink(value); - - return ( - - {props.indent && } - {value} : value} - /> - - ); -} - -export function HelpIcon(props: { to: string }) { const classes = useStyles(); return ( - - - +
      + {props.children} +
    ); } + +/** + * A dense monospace list item. + */ +export function ListItemRow(props: { children: ReactNode }) { + const classes = useStyles(); + return
  • {props.children}
  • ; +} diff --git a/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.test.tsx b/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.test.tsx index 595cd70fc4..eea5f95e89 100644 --- a/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.test.tsx +++ b/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.test.tsx @@ -25,22 +25,12 @@ import { catalogApiRef } from '../../api'; import { entityRouteRef } from '../../routes'; import { screen, waitFor } from '@testing-library/react'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { mockApis } from '@backstage/frontend-test-utils'; import * as state from './useUnregisterEntityDialogState'; -import { AlertApi, alertApiRef } from '@backstage/core-plugin-api'; +import { alertApiRef } from '@backstage/core-plugin-api'; describe('UnregisterEntityDialog', () => { - const alertApi: AlertApi = { - post() { - return undefined; - }, - alert$() { - throw new Error('not implemented'); - }, - }; - - beforeEach(() => { - jest.spyOn(alertApi, 'post').mockImplementation(() => {}); - }); + const alertApi = mockApis.alert(); const entity = { apiVersion: 'backstage.io/v1alpha1', @@ -340,11 +330,13 @@ describe('UnregisterEntityDialog', () => { await waitFor(() => { expect(deleteEntity).toHaveBeenCalled(); expect(onConfirm).toHaveBeenCalled(); - expect(alertApi.post).toHaveBeenCalledWith({ - message: 'Removed entity n', - severity: 'success', - display: 'transient', - }); + expect(alertApi.getAlerts()).toContainEqual( + expect.objectContaining({ + message: 'Removed entity n', + severity: 'success', + display: 'transient', + }), + ); }); }); }); diff --git a/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx b/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx index 54086a55a5..aed0fd2072 100644 --- a/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx +++ b/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx @@ -14,256 +14,300 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; +import { CompoundEntityRef, Entity } from '@backstage/catalog-model'; import { EntityRefLink } from '../EntityRefLink'; -import Box from '@material-ui/core/Box'; -import Button from '@material-ui/core/Button'; -import Dialog from '@material-ui/core/Dialog'; -import DialogActions from '@material-ui/core/DialogActions'; -import DialogContent from '@material-ui/core/DialogContent'; -import DialogContentText from '@material-ui/core/DialogContentText'; -import DialogTitle from '@material-ui/core/DialogTitle'; -import Divider from '@material-ui/core/Divider'; import { makeStyles } from '@material-ui/core/styles'; -import Alert from '@material-ui/lab/Alert'; -import { useCallback, useState } from 'react'; -import { useUnregisterEntityDialogState } from './useUnregisterEntityDialogState'; +import { ReactNode, useCallback, useState } from 'react'; +import { + UseUnregisterEntityDialogState, + useUnregisterEntityDialogState, +} from './useUnregisterEntityDialogState'; import { alertApiRef, configApiRef, useApi } from '@backstage/core-plugin-api'; import { Progress, ResponseErrorPanel } from '@backstage/core-components'; -import { assertError } from '@backstage/errors'; +import { toError } from '@backstage/errors'; import { catalogReactTranslationRef } from '../../translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { + Accordion, + AccordionPanel, + AccordionTrigger, + Alert, + Box, + Button, + Dialog, + DialogBody, + DialogFooter, + DialogHeader, + Text, +} from '@backstage/ui'; const useStyles = makeStyles({ - advancedButton: { - fontSize: '0.7em', - }, - dialogActions: { - display: 'inline-block', + bodyContent: { + overflowWrap: 'break-word', }, }); -const Contents = ({ - entity, - onConfirm, - onClose, -}: { - entity: Entity; - onConfirm: () => any; - onClose: () => any; -}) => { +type DialogHandlers = { + state: UseUnregisterEntityDialogState; + busyAction: 'unregister' | 'delete' | null; + onUnregister: () => Promise; + onDelete: () => Promise; + onClose: () => void; +}; + +function useUnregisterDialogHandlers( + entity: Entity, + onConfirm: () => void, + onClose: () => void, +): DialogHandlers { const alertApi = useApi(alertApiRef); - const configApi = useApi(configApiRef); - const classes = useStyles(); const state = useUnregisterEntityDialogState(entity); - const [showDelete, setShowDelete] = useState(false); - const [busy, setBusy] = useState(false); - const appTitle = configApi.getOptionalString('app.title') ?? 'Backstage'; + const [busyAction, setBusyAction] = useState<'unregister' | 'delete' | null>( + null, + ); const { t } = useTranslationRef(catalogReactTranslationRef); - const onUnregister = useCallback( - async function onUnregisterFn() { - if ('unregisterLocation' in state) { - setBusy(true); - try { - await state.unregisterLocation(); - onConfirm(); - } catch (err) { - assertError(err); - alertApi.post({ message: err.message }); - } finally { - setBusy(false); - } + const onUnregister = useCallback(async () => { + if ('unregisterLocation' in state) { + setBusyAction('unregister'); + try { + await state.unregisterLocation(); + onConfirm(); + } catch (err) { + alertApi.post({ message: toError(err).message }); + } finally { + setBusyAction(null); } - }, - [alertApi, onConfirm, state], - ); + } + }, [alertApi, onConfirm, state]); - const onDelete = useCallback( - async function onDeleteFn() { - if ('deleteEntity' in state) { - setBusy(true); - try { - await state.deleteEntity(); - const entityName = entity.metadata.title ?? entity.metadata.name; - onConfirm(); - alertApi.post({ - message: t('unregisterEntityDialog.deleteEntitySuccessMessage', { - entityName, - }), - severity: 'success', - display: 'transient', - }); - } catch (err) { - assertError(err); - alertApi.post({ message: err.message }); - } finally { - setBusy(false); - } + const onDelete = useCallback(async () => { + if ('deleteEntity' in state) { + setBusyAction('delete'); + try { + await state.deleteEntity(); + const entityName = entity.metadata.title ?? entity.metadata.name; + onConfirm(); + alertApi.post({ + message: t('unregisterEntityDialog.deleteEntitySuccessMessage', { + entityName, + }), + severity: 'success', + display: 'transient', + }); + } catch (err) { + alertApi.post({ message: toError(err).message }); + } finally { + setBusyAction(null); } - }, - [alertApi, onConfirm, state, entity, t], - ); + } + }, [alertApi, onConfirm, state, entity, t]); - const DialogActionsPanel = () => ( - - - - ); + return { state, busyAction, onUnregister, onDelete, onClose }; +} - if (state.type === 'loading') { - return ; - } +function AdvancedDeleteAccordion({ + triggerTitle, + description, + onDelete, + busyAction, +}: { + triggerTitle: string; + description: string; + onDelete: () => void; + busyAction: 'unregister' | 'delete' | null; +}) { + const { t } = useTranslationRef(catalogReactTranslationRef); - if (state.type === 'error') { - return ; - } - - if (state.type === 'bootstrap') { - return ( - <> - - {t('unregisterEntityDialog.bootstrapState.title', { - appTitle, - location: state.location, - })} - - - - {!showDelete && ( - <> - - - - )} - - {showDelete && ( - <> - - {t('unregisterEntityDialog.bootstrapState.advancedDescription')} - - - - - )} - - - ); - } - - if (state.type === 'only-delete') { - return ( - <> - - {t('unregisterEntityDialog.onlyDeleteStateTitle')} - - - - - ); - } - - if (state.type === 'unregister') { - return ( - <> - - {t('unregisterEntityDialog.unregisterState.title')} - - - {state.colocatedEntities.map(e => ( -
  • - -
  • - ))} -
    - - {t('unregisterEntityDialog.unregisterState.subTitle')} - - -
  • {state.location}
  • -
    - - {t('unregisterEntityDialog.unregisterState.description', { - appTitle, - })} - - - - {!showDelete && ( - - - - )} - - - {showDelete && ( - <> - - - - - {t('unregisterEntityDialog.unregisterState.advancedDescription')} - + return ( + + + + + {description} + - - )} - - ); - } + + + + + ); +} + +function BootstrapBody({ + location, + appTitle, + onDelete, + busyAction, +}: { + location: string; + appTitle: string; + onDelete: () => void; + busyAction: 'unregister' | 'delete' | null; +}) { + const { t } = useTranslationRef(catalogReactTranslationRef); return ( - - {t('unregisterEntityDialog.errorStateTitle')} - + <> + + + ); -}; +} + +function OnlyDeleteBody() { + const { t } = useTranslationRef(catalogReactTranslationRef); + + return {t('unregisterEntityDialog.onlyDeleteStateTitle')}; +} + +function UnregisterBody({ + state, + appTitle, + onDelete, + busyAction, +}: { + state: Extract; + appTitle: string; + onDelete: () => void; + busyAction: 'unregister' | 'delete' | null; +}) { + const { t } = useTranslationRef(catalogReactTranslationRef); + + return ( + <> + {t('unregisterEntityDialog.unregisterState.title')} +
      + {state.colocatedEntities.map((e: CompoundEntityRef) => ( +
    • + +
    • + ))} +
    + {t('unregisterEntityDialog.unregisterState.subTitle')} +
      +
    • {state.location}
    • +
    + + {t('unregisterEntityDialog.unregisterState.description', { + appTitle, + })} + + + + ); +} + +function useDialogContent( + handlers: DialogHandlers, + appTitle: string, +): { body: ReactNode; actionButton: ReactNode | null } { + const { t } = useTranslationRef(catalogReactTranslationRef); + const { state, busyAction, onUnregister, onDelete } = handlers; + + switch (state.type) { + case 'loading': + return { body: , actionButton: null }; + case 'error': + return { + body: , + actionButton: null, + }; + case 'bootstrap': + return { + body: ( + + ), + actionButton: null, + }; + case 'only-delete': + return { + body: , + actionButton: ( + + ), + }; + case 'unregister': + return { + body: ( + + ), + actionButton: ( + + ), + }; + default: + return { + body: ( + + ), + actionButton: null, + }; + } +} /** @public */ export type UnregisterEntityDialogProps = { @@ -273,18 +317,54 @@ export type UnregisterEntityDialogProps = { entity: Entity; }; +function DialogContents({ + entity, + onConfirm, + onClose, +}: { + entity: Entity; + onConfirm: () => void; + onClose: () => void; +}) { + const classes = useStyles(); + const { t } = useTranslationRef(catalogReactTranslationRef); + const configApi = useApi(configApiRef); + const appTitle = configApi.getOptionalString('app.title') ?? 'Backstage'; + + const handlers = useUnregisterDialogHandlers(entity, onConfirm, onClose); + const { body, actionButton } = useDialogContent(handlers, appTitle); + + return ( + <> + {t('unregisterEntityDialog.title')} + {body} + + + {actionButton} + + + ); +} + /** @public */ export const UnregisterEntityDialog = (props: UnregisterEntityDialogProps) => { const { open, onConfirm, onClose, entity } = props; - const { t } = useTranslationRef(catalogReactTranslationRef); + return ( - - - {t('unregisterEntityDialog.title')} - - - - + !isOpen && onClose()} + width={600} + > + {open && ( + + )} ); }; diff --git a/plugins/catalog-react/src/components/UnregisterEntityDialog/useUnregisterEntityDialogState.test.tsx b/plugins/catalog-react/src/components/UnregisterEntityDialog/useUnregisterEntityDialogState.test.tsx index 675cf7ddad..dfd310cd1c 100644 --- a/plugins/catalog-react/src/components/UnregisterEntityDialog/useUnregisterEntityDialogState.test.tsx +++ b/plugins/catalog-react/src/components/UnregisterEntityDialog/useUnregisterEntityDialogState.test.tsx @@ -72,7 +72,12 @@ describe('useUnregisterEntityDialogState', () => { expect(rendered.result.current).toEqual({ type: 'loading' }); - resolveLocation({ type: 'url', target: 'https://example.com', id: 'x' }); + resolveLocation({ + type: 'url', + target: 'https://example.com', + id: 'x', + entityRef: 'location:default/generated-x', + }); resolveColocatedEntities([entity]); await waitFor(() => { @@ -94,7 +99,12 @@ describe('useUnregisterEntityDialogState', () => { wrapper: Wrapper, }); - resolveLocation({ type: 'bootstrap', target: 'bootstrap', id: 'x' }); + resolveLocation({ + type: 'bootstrap', + target: 'bootstrap', + id: 'x', + entityRef: '', + }); resolveColocatedEntities([]); await waitFor(() => { diff --git a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx index 2e3ddff882..1004585856 100644 --- a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx +++ b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx @@ -45,6 +45,8 @@ import { } from '@backstage/core-plugin-api'; import { MockStarredEntitiesApi, starredEntitiesApiRef } from '../../apis'; import { DefaultEntityFilters } from '../../hooks'; +import { NewEntityListContext } from '../../hooks/useEntityListProvider'; +import { createVersionedValueMap } from '@backstage/version-bridge'; const mockUser: UserEntity = { apiVersion: 'backstage.io/v1alpha1', @@ -694,4 +696,75 @@ describe('', () => { }); }); }); + describe('when initialFilter is not provided', () => { + it('updates filter options automatically when the kind changes', async () => { + let setFilters: (f: any) => void = () => {}; + + const TestProvider = ({ children }: { children?: React.ReactNode }) => { + const [filtersState, _setFiltersState] = require('react').useState({ + kind: new EntityKindFilter('component', 'Component'), + } as any); + setFilters = _setFiltersState; + + const updateFilters = (update: any) => + _setFiltersState((prev: any) => + typeof update === 'function' + ? update(prev) + : { ...prev, ...update }, + ); + + const value = { + entities: [], + backendEntities: [], + updateFilters, + filters: filtersState, + loading: false, + queryParameters: {}, + error: undefined, + totalItems: 0, + limit: 20, + offset: 0, + setLimit: () => {}, + setOffset: () => {}, + paginationMode: 'none', + } as any; + + return ( + + {children} + + ); + }; + + await renderInTestApp( + + + + + , + ); + + await waitFor(() => + expect(mockIdentityApi.getBackstageIdentity).toHaveBeenCalled(), + ); + + await waitFor(() => + expect( + screen.getAllByRole('menuitem').map(({ textContent }) => textContent), + ).toEqual(['Owned 3', 'Starred 2', 'All 10']), + ); + + require('react').act(() => + setFilters({ kind: new EntityKindFilter('user', 'User') } as any), + ); + + await waitFor(() => + expect( + screen.getAllByRole('menuitem').map(({ textContent }) => textContent), + ).toEqual(['Starred 2', 'All 10']), + ); + }); + }); }); diff --git a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx index dc4173fd5f..245f82122c 100644 --- a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx +++ b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx @@ -138,8 +138,9 @@ export const UserListPicker = (props: UserListPickerProps) => { const { filters, updateFilters, - queryParameters: { kind: kindParameter, user: userParameter }, + queryParameters: { user: userParameter }, } = useEntityList(); + const kindParameter = filters.kind?.value?.toLowerCase(); // Remove group items that aren't in availableFilters and exclude // any now-empty groups. diff --git a/plugins/catalog-react/src/components/UserListPicker/useAllEntitiesCount.test.tsx b/plugins/catalog-react/src/components/UserListPicker/useAllEntitiesCount.test.tsx index 0ba231b72a..07eaa479fd 100644 --- a/plugins/catalog-react/src/components/UserListPicker/useAllEntitiesCount.test.tsx +++ b/plugins/catalog-react/src/components/UserListPicker/useAllEntitiesCount.test.tsx @@ -68,15 +68,15 @@ describe('useAllEntitiesCount', () => { ), }); - await waitFor(() => + await waitFor(() => { expect(mockCatalogApi.queryEntities).toHaveBeenCalledWith({ filter: { 'relations.ownedBy': ['user:default/owner'], }, limit: 0, - }), - ); - expect(result.current).toEqual({ count: 10, loading: false }); + }); + expect(result.current).toEqual({ count: 10, loading: false }); + }); }); it(`shouldn't invoke the endpoint at startup, when filters are missing`, async () => { diff --git a/plugins/catalog-react/src/components/index.ts b/plugins/catalog-react/src/components/index.ts index 8a1ebd1835..971d6086f7 100644 --- a/plugins/catalog-react/src/components/index.ts +++ b/plugins/catalog-react/src/components/index.ts @@ -35,3 +35,4 @@ export * from './EntityProcessingStatusPicker'; export * from './EntityNamespacePicker'; export * from './EntityAutocompletePicker'; export * from './MissingAnnotationEmptyState'; +export * from './EntityInfoCard'; diff --git a/plugins/catalog-react/src/deprecated.tsx b/plugins/catalog-react/src/deprecated.tsx index 74186d9c29..7d347dda6b 100644 --- a/plugins/catalog-react/src/deprecated.tsx +++ b/plugins/catalog-react/src/deprecated.tsx @@ -15,10 +15,12 @@ */ import { PropsWithChildren, useCallback, useMemo, useState } from 'react'; +import { createVersionedValueMap } from '@backstage/version-bridge'; import { DefaultEntityFilters, - EntityListContext, EntityListContextProps, + NewEntityListContext, + OldEntityListContext, } from './hooks/useEntityListProvider'; /** @@ -81,8 +83,18 @@ export function MockEntityListContextProvider< ); return ( - + {children} - + ); } + +/** + * Creates new context for entity listing and filtering. + * + * @public + * @deprecated Please use `EntityListProvider` and `EntityListProvider` instead. + */ +export const EntityListContext = OldEntityListContext; diff --git a/plugins/catalog-react/src/hooks/index.ts b/plugins/catalog-react/src/hooks/index.ts index befcce520d..2bce5a4673 100644 --- a/plugins/catalog-react/src/hooks/index.ts +++ b/plugins/catalog-react/src/hooks/index.ts @@ -24,11 +24,7 @@ export type { EntityProviderProps, AsyncEntityProviderProps, } from './useEntity'; -export { - EntityListContext, - EntityListProvider, - useEntityList, -} from './useEntityListProvider'; +export { EntityListProvider, useEntityList } from './useEntityListProvider'; export type { DefaultEntityFilters, EntityListContextProps, diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx index f7090f330b..a5fda1bf5b 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx @@ -25,7 +25,8 @@ import { } from '@backstage/core-plugin-api'; import { translationApiRef } from '@backstage/core-plugin-api/alpha'; import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils'; -import { mockApis, TestApiProvider } from '@backstage/test-utils'; +import { TestApiProvider } from '@backstage/test-utils'; +import { mockApis } from '@backstage/frontend-test-utils'; import { useMountEffect } from '@react-hookz/web'; import { act, renderHook, waitFor } from '@testing-library/react'; import qs from 'qs'; @@ -42,7 +43,13 @@ import { } from '../filters'; import { createDeferred } from '@backstage/types'; import { EntityListPagination } from '../types'; -import { EntityListProvider, useEntityList } from './useEntityListProvider'; +import { + EntityListContextProps, + EntityListProvider, + NewEntityListContext, + useEntityList, +} from './useEntityListProvider'; +import { createVersionedValueMap } from '@backstage/version-bridge'; const entities: Entity[] = [ { @@ -105,7 +112,7 @@ const createWrapper = [identityApiRef, mockIdentityApi], [storageApiRef, mockApis.storage()], [starredEntitiesApiRef, new MockStarredEntitiesApi()], - [alertApiRef, { post: jest.fn() }], + [alertApiRef, mockApis.alert()], [translationApiRef, mockApis.translation()], [errorApiRef, { error$: jest.fn(), post: jest.fn() }], ]} @@ -216,6 +223,27 @@ describe('', () => { }); }); + it('resolves query param filter values with large arrays', async () => { + const largeArray = Array.from({ length: 50 }, (_, i) => `owner-${i}`); + const query = qs.stringify({ + filters: { kind: 'component', owners: largeArray }, + }); + const { result } = renderHook(() => useEntityList(), { + wrapper: createWrapper({ + location: `/catalog?${query}`, + pagination, + }), + }); + + await waitFor(() => { + expect(result.current.queryParameters).toBeTruthy(); + }); + expect(result.current.queryParameters).toEqual({ + kind: 'component', + owners: largeArray, + }); + }); + it('does not fetch when only frontend filters change', async () => { const { result } = renderHook(() => useEntityList(), { wrapper: createWrapper({ pagination }), @@ -1048,3 +1076,59 @@ describe(``, () => { ); }); }); + +describe('versioned context', () => { + it('should work explicitly with new versioned contexts', () => { + const value: EntityListContextProps = { + filters: {}, + entities: [], + backendEntities: [], + updateFilters: jest.fn(), + queryParameters: {}, + loading: true, + limit: 277, + setLimit: jest.fn(), + setOffset: jest.fn(), + paginationMode: 'none', + }; + + const { result } = renderHook(() => useEntityList(), { + wrapper: ({ children }) => { + const InitialFiltersWrapper = (f: PropsWithChildren<{}>) => { + const { updateFilters } = useEntityList(); + useMountEffect(() => { + updateFilters({ + kind: new EntityKindFilter('component', 'Component'), + }); + }); + return <>{f.children}; + }; + + return ( + + + + {children} + + + + ); + }, + }); + + expect(result.current.limit).toBe(277); + }); +}); diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx index 8c4927ad0a..a18575befa 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx @@ -17,6 +17,11 @@ import { QueryEntitiesResponse } from '@backstage/catalog-client'; import { Entity } from '@backstage/catalog-model'; import { useApi } from '@backstage/core-plugin-api'; +import { + createVersionedContext, + createVersionedValueMap, + useVersionedContext, +} from '@backstage/version-bridge'; import { compact, isEqual } from 'lodash'; import qs from 'qs'; import { @@ -122,11 +127,17 @@ export type EntityListContextProps< paginationMode: PaginationMode; }; +// This context has support for multiple concurrent versions of this package. +// It is currently used in parallel with the old context in order to provide +// a smooth transition, but will eventually be the only context we use. +export const NewEntityListContext = createVersionedContext<{ + 1: EntityListContextProps; +}>('entity-list-context'); + /** * Creates new context for entity listing and filtering. - * @public */ -export const EntityListContext = createContext< +export const OldEntityListContext = createContext< EntityListContextProps | undefined >(undefined); @@ -188,6 +199,7 @@ export const EntityListProvider = ( } = useMemo(() => { const parsed = qs.parse(location.search, { ignoreQueryPrefix: true, + arrayLimit: 10000, }); let limit = paginationLimit; @@ -387,6 +399,7 @@ export const EntityListProvider = ( const oldParams = qs.parse(location.search, { ignoreQueryPrefix: true, + arrayLimit: 10000, }); const newParams = qs.stringify( { @@ -441,30 +454,36 @@ export const EntityListProvider = ( [paginationMode], ); + // Use resolvedValue directly when available to avoid an extra render cycle. + // Without this, there's a render where loading has flipped back to false but + // outputState hasn't been updated yet (it syncs via useEffect), causing a + // flash of stale data between the loading state and the new results. + const latestOutput = resolvedValue ?? outputState; + const pageInfo = useMemo(() => { if (paginationMode !== 'cursor') { return undefined; } - const prevCursor = outputState.pageInfo?.prevCursor; - const nextCursor = outputState.pageInfo?.nextCursor; + const prevCursor = latestOutput.pageInfo?.prevCursor; + const nextCursor = latestOutput.pageInfo?.nextCursor; return { prev: prevCursor ? () => setCursor(prevCursor) : undefined, next: nextCursor ? () => setCursor(nextCursor) : undefined, }; - }, [paginationMode, outputState.pageInfo]); + }, [paginationMode, latestOutput.pageInfo]); const value = useMemo( () => ({ - filters: outputState.appliedFilters, - entities: outputState.entities, - backendEntities: outputState.backendEntities, + filters: latestOutput.appliedFilters, + entities: latestOutput.entities, + backendEntities: latestOutput.backendEntities, updateFilters, queryParameters, loading, error, pageInfo, - totalItems: outputState.totalItems, + totalItems: latestOutput.totalItems, limit, offset, setLimit, @@ -472,7 +491,7 @@ export const EntityListProvider = ( paginationMode, }), [ - outputState, + latestOutput, updateFilters, queryParameters, loading, @@ -487,9 +506,13 @@ export const EntityListProvider = ( ); return ( - - {props.children} - + + + {props.children} + + ); }; @@ -500,8 +523,22 @@ export const EntityListProvider = ( export function useEntityList< EntityFilters extends DefaultEntityFilters = DefaultEntityFilters, >(): EntityListContextProps { - const context = useContext(EntityListContext); - if (!context) - throw new Error('useEntityList must be used within EntityListProvider'); - return context; + const versionedHolder = useVersionedContext<{ + 1: EntityListContextProps; + }>('entity-list-context'); + const oldContext = useContext(OldEntityListContext); + + if (versionedHolder) { + const value = versionedHolder.atVersion(1); + if (!value) { + throw new Error('EntityListContext v1 not available'); + } + return value; + } + + if (oldContext) { + return oldContext; + } + + throw new Error('useEntityList must be used within EntityListProvider'); } diff --git a/plugins/catalog-react/src/index.ts b/plugins/catalog-react/src/index.ts index 2b1664abe2..1f58e5f6f9 100644 --- a/plugins/catalog-react/src/index.ts +++ b/plugins/catalog-react/src/index.ts @@ -34,3 +34,4 @@ export * from './overridableComponents'; export { getEntityRelations, getEntitySourceLocation } from './utils'; export type { EntitySourceLocation } from './utils'; export * from './deprecated'; +export { catalogReactTranslationRef } from './translation'; diff --git a/plugins/catalog-react/src/testUtils.ts b/plugins/catalog-react/src/testUtils.ts index ffabf4f10c..806d0628e0 100644 --- a/plugins/catalog-react/src/testUtils.ts +++ b/plugins/catalog-react/src/testUtils.ts @@ -22,3 +22,7 @@ export { catalogApiMock } from './testUtils/catalogApiMock'; export { MockEntityListContextProvider } from './testUtils/MockEntityListContextProvider'; +export { + createTestEntityPage, + type TestEntityPageOptions, +} from './testUtils/createTestEntityPage'; diff --git a/plugins/catalog-react/src/testUtils/MockEntityListContextProvider.tsx b/plugins/catalog-react/src/testUtils/MockEntityListContextProvider.tsx index 0dcbbed8e8..e75e7e20e3 100644 --- a/plugins/catalog-react/src/testUtils/MockEntityListContextProvider.tsx +++ b/plugins/catalog-react/src/testUtils/MockEntityListContextProvider.tsx @@ -17,9 +17,10 @@ import { PropsWithChildren, useCallback, useMemo, useState } from 'react'; import { DefaultEntityFilters, - EntityListContext, EntityListContextProps, } from '@backstage/plugin-catalog-react'; +import { createVersionedValueMap } from '@backstage/version-bridge'; +import { NewEntityListContext } from '../hooks/useEntityListProvider'; /** * Simplifies testing of code that uses the entity list hooks. @@ -82,8 +83,10 @@ export function MockEntityListContextProvider< ); return ( - + {children} - + ); } diff --git a/plugins/catalog-react/src/testUtils/catalogApiMock.ts b/plugins/catalog-react/src/testUtils/catalogApiMock.ts index a37d2669ad..0ae37f10f2 100644 --- a/plugins/catalog-react/src/testUtils/catalogApiMock.ts +++ b/plugins/catalog-react/src/testUtils/catalogApiMock.ts @@ -14,42 +14,16 @@ * limitations under the License. */ -import { - ApiFactory, - ApiRef, - createApiFactory, -} from '@backstage/frontend-plugin-api'; +import { ApiFactory, createApiFactory } from '@backstage/frontend-plugin-api'; import { InMemoryCatalogClient } from '@backstage/catalog-client/testUtils'; import { Entity } from '@backstage/catalog-model'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; import { CatalogApi } from '@backstage/catalog-client'; -import { ApiMock } from '@backstage/frontend-test-utils'; - -/** @internal */ -function simpleMock( - ref: ApiRef, - mockFactory: () => jest.Mocked, -): (partialImpl?: Partial) => ApiMock { - return partialImpl => { - const mock = mockFactory(); - if (partialImpl) { - for (const [key, impl] of Object.entries(partialImpl)) { - if (typeof impl === 'function') { - (mock as any)[key].mockImplementation(impl); - } else { - (mock as any)[key] = impl; - } - } - } - return Object.assign(mock, { - factory: createApiFactory({ - api: ref, - deps: {}, - factory: () => mock, - }), - }) as ApiMock; - }; -} +import { + createApiMock, + attachMockApiFactory, + type MockWithApiFactory, +} from '@backstage/frontend-test-utils'; /** * Creates a fake catalog client that handles entities in memory storage. Note @@ -58,8 +32,11 @@ function simpleMock( * * @public */ -export function catalogApiMock(options?: { entities?: Entity[] }): CatalogApi { - return new InMemoryCatalogClient(options); +export function catalogApiMock(options?: { + entities?: Entity[]; +}): MockWithApiFactory { + const instance = new InMemoryCatalogClient(options); + return attachMockApiFactory(catalogApiRef, instance); } /** @@ -85,7 +62,7 @@ export namespace catalogApiMock { * Creates a catalog client whose methods are mock functions, possibly with * some of them overloaded by the caller. */ - export const mock = simpleMock(catalogApiRef, () => ({ + export const mock = createApiMock(catalogApiRef, () => ({ getEntities: jest.fn(), getEntitiesByRefs: jest.fn(), queryEntities: jest.fn(), @@ -95,10 +72,13 @@ export namespace catalogApiMock { refreshEntity: jest.fn(), getEntityFacets: jest.fn(), getLocations: jest.fn(), + queryLocations: jest.fn(), + streamLocations: jest.fn(), getLocationById: jest.fn(), getLocationByRef: jest.fn(), addLocation: jest.fn(), removeLocationById: jest.fn(), + updateLocation: jest.fn(), getLocationByEntity: jest.fn(), validateEntity: jest.fn(), analyzeLocation: jest.fn(), diff --git a/plugins/catalog-react/src/testUtils/createTestEntityPage.test.tsx b/plugins/catalog-react/src/testUtils/createTestEntityPage.test.tsx new file mode 100644 index 0000000000..4fa8465797 --- /dev/null +++ b/plugins/catalog-react/src/testUtils/createTestEntityPage.test.tsx @@ -0,0 +1,397 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { screen } from '@testing-library/react'; +import { renderTestApp } from '@backstage/frontend-test-utils'; +import { Entity } from '@backstage/catalog-model'; +import { createTestEntityPage } from './createTestEntityPage'; +import { catalogApiMock } from './catalogApiMock'; +import { + EntityCardBlueprint, + EntityContentBlueprint, +} from '../alpha/blueprints'; + +const mockEntity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test-component', + namespace: 'default', + }, + spec: { + type: 'service', + lifecycle: 'production', + }, +}; + +describe('createTestEntityPage', () => { + describe('entity cards', () => { + it('should render a card for a matching entity', async () => { + const testCard = EntityCardBlueprint.make({ + name: 'test-card', + params: { + loader: async () =>
    Test Card Content
    , + }, + }); + + renderTestApp({ + extensions: [createTestEntityPage({ entity: mockEntity }), testCard], + }); + + expect(await screen.findByText('Test Card Content')).toBeInTheDocument(); + }); + + it('should filter cards based on entity predicate', async () => { + const apiOnlyCard = EntityCardBlueprint.make({ + name: 'api-card', + params: { + loader: async () =>
    API Card
    , + filter: entity => entity.kind === 'API', + }, + }); + + const componentCard = EntityCardBlueprint.make({ + name: 'component-card', + params: { + loader: async () =>
    Component Card
    , + filter: entity => entity.kind === 'Component', + }, + }); + + renderTestApp({ + extensions: [ + createTestEntityPage({ entity: mockEntity }), + apiOnlyCard, + componentCard, + ], + }); + + expect(await screen.findByText('Component Card')).toBeInTheDocument(); + expect(screen.queryByText('API Card')).not.toBeInTheDocument(); + }); + + it('should filter cards based on predicate config', async () => { + const kindCard = EntityCardBlueprint.make({ + name: 'kind-card', + params: { + loader: async () =>
    Kind Card
    , + filter: { kind: 'API' }, + }, + }); + + const sentinelCard = EntityCardBlueprint.make({ + name: 'sentinel-card', + params: { + loader: async () =>
    Sentinel
    , + }, + }); + + renderTestApp({ + extensions: [ + createTestEntityPage({ entity: mockEntity }), + kindCard, + sentinelCard, + ], + }); + + // Wait for rendering to complete via sentinel + await screen.findByText('Sentinel'); + expect(screen.queryByText('Kind Card')).not.toBeInTheDocument(); + }); + + it('should render multiple cards', async () => { + const card1 = EntityCardBlueprint.make({ + name: 'card-1', + params: { + loader: async () =>
    Card 1
    , + }, + }); + + const card2 = EntityCardBlueprint.make({ + name: 'card-2', + params: { + loader: async () =>
    Card 2
    , + }, + }); + + renderTestApp({ + extensions: [ + createTestEntityPage({ entity: mockEntity }), + card1, + card2, + ], + }); + + expect(await screen.findByText('Card 1')).toBeInTheDocument(); + expect(await screen.findByText('Card 2')).toBeInTheDocument(); + }); + }); + + describe('entity content', () => { + it('should render a single content extension at root path', async () => { + const testContent = EntityContentBlueprint.make({ + name: 'test-content', + params: { + path: '/details', + title: 'Details', + loader: async () =>
    Test Content
    , + }, + }); + + renderTestApp({ + extensions: [createTestEntityPage({ entity: mockEntity }), testContent], + }); + + expect(await screen.findByText('Test Content')).toBeInTheDocument(); + }); + + it('should filter content based on entity predicate', async () => { + const apiContent = EntityContentBlueprint.make({ + name: 'api-content', + params: { + path: '/api-details', + title: 'API Details', + loader: async () =>
    API Content
    , + filter: entity => entity.kind === 'API', + }, + }); + + const sentinelCard = EntityCardBlueprint.make({ + name: 'sentinel-card', + params: { + loader: async () =>
    Sentinel
    , + }, + }); + + renderTestApp({ + extensions: [ + createTestEntityPage({ entity: mockEntity }), + apiContent, + sentinelCard, + ], + }); + + // Wait for rendering to complete via sentinel + await screen.findByText('Sentinel'); + expect(screen.queryByText('API Content')).not.toBeInTheDocument(); + }); + + it('should render multiple content extensions with routing', async () => { + const contentA = EntityContentBlueprint.make({ + name: 'content-a', + params: { + path: '/a', + title: 'Content A', + loader: async () =>
    Content A
    , + }, + }); + + const contentB = EntityContentBlueprint.make({ + name: 'content-b', + params: { + path: '/b', + title: 'Content B', + loader: async () =>
    Content B
    , + }, + }); + + renderTestApp({ + extensions: [ + createTestEntityPage({ entity: mockEntity }), + contentA, + contentB, + ], + }); + + // First content should be rendered at root by default + expect(await screen.findByText('Content A')).toBeInTheDocument(); + }); + }); + + describe('combined usage', () => { + it('should render cards and content together', async () => { + const testCard = EntityCardBlueprint.make({ + name: 'combined-card', + params: { + loader: async () =>
    Combined Card
    , + }, + }); + + const testContent = EntityContentBlueprint.make({ + name: 'combined-content', + params: { + path: '/combined', + title: 'Combined', + loader: async () =>
    Combined Content
    , + }, + }); + + renderTestApp({ + extensions: [ + createTestEntityPage({ entity: mockEntity }), + testCard, + testContent, + ], + }); + + expect(await screen.findByText('Combined Card')).toBeInTheDocument(); + expect(await screen.findByText('Combined Content')).toBeInTheDocument(); + }); + + it('should work with different entity kinds', async () => { + const apiEntity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'API', + metadata: { name: 'test-api' }, + spec: { type: 'openapi' }, + }; + + const apiCard = EntityCardBlueprint.make({ + name: 'api-specific-card', + params: { + loader: async () =>
    API Card
    , + filter: entity => entity.kind === 'API', + }, + }); + + renderTestApp({ + extensions: [createTestEntityPage({ entity: apiEntity }), apiCard], + }); + + expect(await screen.findByText('API Card')).toBeInTheDocument(); + }); + + it('should provide entity context to cards', async () => { + const entityNameCard = EntityCardBlueprint.make({ + name: 'entity-name-card', + params: { + loader: async () => { + const { useEntity } = await import('../hooks/useEntity'); + const EntityNameDisplay = () => { + const { entity } = useEntity(); + return
    Entity: {entity.metadata.name}
    ; + }; + return ; + }, + }, + }); + + renderTestApp({ + extensions: [ + createTestEntityPage({ entity: mockEntity }), + entityNameCard, + ], + }); + + expect( + await screen.findByText('Entity: test-component'), + ).toBeInTheDocument(); + }); + }); + + describe('catalog API', () => { + it('should provide a mock catalog API with the entity', async () => { + const catalogApiCard = EntityCardBlueprint.make({ + name: 'catalog-api-card', + params: { + loader: async () => { + const { useApi } = await import('@backstage/core-plugin-api'); + const { catalogApiRef: ref } = await import('../api'); + const React = await import('react'); + const CatalogApiDisplay = () => { + const catalogApi = useApi(ref); + const [entities, setEntities] = React.useState([]); + React.useEffect(() => { + catalogApi + .getEntities() + .then(response => + setEntities(response.items.map(e => e.metadata.name)), + ); + }, [catalogApi]); + return
    Entities: {entities.join(', ') || 'loading'}
    ; + }; + return ; + }, + }, + }); + + renderTestApp({ + extensions: [ + createTestEntityPage({ entity: mockEntity }), + catalogApiCard, + ], + }); + + expect( + await screen.findByText('Entities: test-component'), + ).toBeInTheDocument(); + }); + + it('should allow user-provided catalog API to take precedence', async () => { + const customEntities: Entity[] = [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { name: 'custom-a', namespace: 'default' }, + spec: { type: 'service' }, + }, + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { name: 'custom-b', namespace: 'default' }, + spec: { type: 'service' }, + }, + ]; + + const catalogApiCard = EntityCardBlueprint.make({ + name: 'catalog-api-card', + params: { + loader: async () => { + const { useApi } = await import('@backstage/core-plugin-api'); + const { catalogApiRef: ref } = await import('../api'); + const React = await import('react'); + const CatalogApiDisplay = () => { + const catalogApi = useApi(ref); + const [entities, setEntities] = React.useState([]); + React.useEffect(() => { + catalogApi + .getEntities() + .then(response => + setEntities(response.items.map(e => e.metadata.name)), + ); + }, [catalogApi]); + return
    Entities: {entities.join(', ') || 'loading'}
    ; + }; + return ; + }, + }, + }); + + renderTestApp({ + extensions: [ + createTestEntityPage({ entity: mockEntity }), + catalogApiCard, + ], + apis: [catalogApiMock({ entities: customEntities })], + }); + + // Should use the user-provided catalog API with custom entities, not mockEntity + expect( + await screen.findByText('Entities: custom-a, custom-b'), + ).toBeInTheDocument(); + }); + }); +}); diff --git a/plugins/catalog-react/src/testUtils/createTestEntityPage.tsx b/plugins/catalog-react/src/testUtils/createTestEntityPage.tsx new file mode 100644 index 0000000000..bf62a5a440 --- /dev/null +++ b/plugins/catalog-react/src/testUtils/createTestEntityPage.tsx @@ -0,0 +1,221 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Fragment, ReactNode } from 'react'; +import { Entity } from '@backstage/catalog-model'; +import { + coreExtensionData, + createExtensionInput, + PageBlueprint, + ExtensionDefinition, + useApiHolder, +} from '@backstage/frontend-plugin-api'; +import { Routes, Route } from 'react-router-dom'; +import { EntityProvider } from '../hooks/useEntity'; +import { + EntityCardBlueprint, + EntityContentBlueprint, +} from '../alpha/blueprints'; +import { entityRouteRef } from '../routes'; +import { catalogApiRef } from '../api'; +import { catalogApiMock } from './catalogApiMock'; +import { TestApiProvider } from '@backstage/frontend-test-utils'; + +/** + * Options for creating a test entity page. + * @public + */ +export interface TestEntityPageOptions { + /** + * The entity to provide in the EntityProvider context. + */ + entity: Entity; +} + +/** + * Creates a test entity page extension that provides EntityProvider context + * and accepts entity extensions (cards, content) as inputs. + * + * This utility simplifies testing entity extensions by eliminating the need + * for manual EntityProvider wrapping and route configuration. + * + * @remarks + * + * The test page uses input redirects to accept both: + * - Entity cards (normally attach to `entity-content:catalog/overview`) + * - Entity content (normally attach to `page:catalog/entity`) + * + * Cards are rendered in a simple container. Content extensions follow + * standard routing behavior, with a single content optimization that + * renders directly without routing. + * + * @example + * ```tsx + * import { createTestEntityPage } from '@backstage/plugin-catalog-react/testUtils'; + * import { renderTestApp } from '@backstage/frontend-test-utils'; + * + * const mockEntity = { + * apiVersion: 'backstage.io/v1alpha1', + * kind: 'Component', + * metadata: { name: 'test' }, + * spec: { type: 'service' }, + * }; + * + * // Testing an entity card + * renderTestApp({ + * extensions: [createTestEntityPage({ entity: mockEntity }), myEntityCard], + * }); + * + * // Testing entity content + * renderTestApp({ + * extensions: [createTestEntityPage({ entity: mockEntity }), myEntityContent], + * }); + * ``` + * + * @public + */ +export function createTestEntityPage( + options: TestEntityPageOptions, +): ExtensionDefinition { + const { entity } = options; + + return PageBlueprint.makeWithOverrides({ + name: 'test-entity', + inputs: { + // Redirect cards from entity-content:catalog/overview + cards: createExtensionInput( + [ + coreExtensionData.reactElement, + EntityContentBlueprint.dataRefs.filterFunction.optional(), + EntityContentBlueprint.dataRefs.filterExpression.optional(), + EntityCardBlueprint.dataRefs.type.optional(), + ], + { + replaces: [{ id: 'entity-content:catalog/overview', input: 'cards' }], + }, + ), + // Redirect contents from page:catalog/entity + contents: createExtensionInput( + [ + coreExtensionData.reactElement, + coreExtensionData.routePath, + coreExtensionData.routeRef.optional(), + EntityContentBlueprint.dataRefs.title, + EntityContentBlueprint.dataRefs.filterFunction.optional(), + EntityContentBlueprint.dataRefs.filterExpression.optional(), + EntityContentBlueprint.dataRefs.group.optional(), + ], + { + replaces: [{ id: 'page:catalog/entity', input: 'contents' }], + }, + ), + }, + factory: (originalFactory, { inputs }) => { + return originalFactory({ + path: '/', + routeRef: entityRouteRef, + loader: async () => { + // Process cards with entity filtering + const cards = inputs.cards + .filter(card => + ( + card.get(EntityContentBlueprint.dataRefs.filterFunction) ?? + (() => true) + )(entity), + ) + .map(card => ({ + element: card.get(coreExtensionData.reactElement), + type: card.get(EntityCardBlueprint.dataRefs.type), + })); + + // Process contents with entity filtering + const contents = inputs.contents + .filter(content => + ( + content.get(EntityContentBlueprint.dataRefs.filterFunction) ?? + (() => true) + )(entity), + ) + .map(content => ({ + element: content.get(coreExtensionData.reactElement), + path: content.get(coreExtensionData.routePath), + title: content.get(EntityContentBlueprint.dataRefs.title), + })); + + if (contents.length === 0 && cards.length === 0) { + return
    ; + } + + return ( + + + {cards.map((card, index) => ( + {card.element} + ))} + {contents.length === 1 && contents[0].element} + {contents.length > 1 && ( + + {contents.map(content => ( + + ))} + + + )} + + + ); + }, + }); + }, + }); +} + +/** + * A mock provider that injects a catalog API with the single entity of the test entity page. + * + * Users can still install their own catalog API implementations and they will take precedence over the mock. + * @internal + */ +function MockEntityApiProvider({ + entity, + children, +}: { + entity: Entity; + children: ReactNode; +}) { + const parentHolder = useApiHolder(); + + // If catalog API is already provided, don't override it + if (parentHolder.get(catalogApiRef)) { + return <>{children}; + } + + // Provide a mock catalog API with the single entity + const mockApi = catalogApiMock({ entities: [entity] }); + return ( + + {children} + + ); +} diff --git a/plugins/catalog-react/src/translation.ts b/plugins/catalog-react/src/translation.ts index 4411f7e004..b9a43eb1cf 100644 --- a/plugins/catalog-react/src/translation.ts +++ b/plugins/catalog-react/src/translation.ts @@ -16,7 +16,7 @@ import { createTranslationRef } from '@backstage/core-plugin-api/alpha'; -/** @alpha */ +/** @public */ export const catalogReactTranslationRef = createTranslationRef({ id: 'catalog-react', messages: { @@ -82,6 +82,7 @@ export const catalogReactTranslationRef = createTranslationRef({ alertNoEntity: 'There were no other entities on this location.', locationHeader: 'At the same location', originHeader: 'At the same origin', + entityListAriaLabel: 'Colocated entities', }, jsonPage: { title: 'Entity as JSON', @@ -105,6 +106,9 @@ export const catalogReactTranslationRef = createTranslationRef({ annotations: 'Annotations', labels: 'Labels', tags: 'Tags', + copyAriaLabel: 'Copy {{label}}', + copiedStatus: 'Copied', + helpLinkAriaLabel: 'Learn more', }, yamlPage: { title: 'Entity as YAML', @@ -168,6 +172,9 @@ export const catalogReactTranslationRef = createTranslationRef({ label: 'Label', domain: 'Domain', }, + entityRelationCard: { + emptyHelpLinkTitle: 'Learn how to change this.', + }, missingAnnotationEmptyState: { title: 'Missing Annotation', readMore: 'Read more', diff --git a/plugins/catalog-unprocessed-entities-common/CHANGELOG.md b/plugins/catalog-unprocessed-entities-common/CHANGELOG.md index 8bed47be6a..78cfdc2aa0 100644 --- a/plugins/catalog-unprocessed-entities-common/CHANGELOG.md +++ b/plugins/catalog-unprocessed-entities-common/CHANGELOG.md @@ -1,5 +1,76 @@ # @backstage/plugin-catalog-unprocessed-entities-common +## 0.0.14 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0 + - @backstage/catalog-model@1.8.0 + - @backstage/plugin-permission-common@0.9.8 + +## 0.0.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/catalog-model@1.7.8-next.0 + - @backstage/plugin-permission-common@0.9.8-next.0 + +## 0.0.13 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.6 + +## 0.0.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + +## 0.0.12 + +### Patch Changes + +- df4d646: Moved types, API and client to the common package, allowing both frontend and + backend plugins to use the `CatalogUnprocessedEntitiesClient`. + + The following types, clients and interfaces have been deprecated and should be + imported from the `@backstage/plugin-catalog-unprocessed-entities-common` instead: + `CatalogUnprocessedEntitiesApi`, `CatalogUnprocessedEntitiesApiResponse`, `UnprocessedEntity`, + `UnprocessedEntityCache`, `UnprocessedEntityError`, `CatalogUnprocessedEntitiesClient`. + + All those types, clients and interfaces are re-exported temporarily in the + `@backstage/plugin-catalog-unprocessed-entities` package until cleaned up. + +## 0.0.12-next.0 + +### Patch Changes + +- df4d646: Moved types, API and client to the common package, allowing both frontend and + backend plugins to use the `CatalogUnprocessedEntitiesClient`. + + The following types, clients and interfaces have been deprecated and should be + imported from the `@backstage/plugin-catalog-unprocessed-entities-common` instead: + `CatalogUnprocessedEntitiesApi`, `CatalogUnprocessedEntitiesApiResponse`, `UnprocessedEntity`, + `UnprocessedEntityCache`, `UnprocessedEntityError`, `CatalogUnprocessedEntitiesClient`. + + All those types, clients and interfaces are re-exported temporarily in the + `@backstage/plugin-catalog-unprocessed-entities` package until cleaned up. + +## 0.0.11 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.3 + ## 0.0.11-next.0 ### Patch Changes diff --git a/plugins/catalog-unprocessed-entities-common/package.json b/plugins/catalog-unprocessed-entities-common/package.json index 82842b88bf..5ee06eb61c 100644 --- a/plugins/catalog-unprocessed-entities-common/package.json +++ b/plugins/catalog-unprocessed-entities-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-unprocessed-entities-common", - "version": "0.0.11-next.0", + "version": "0.0.14", "description": "Common functionalities for the catalog-unprocessed-entities plugin", "backstage": { "role": "common-library", @@ -37,6 +37,8 @@ "test": "backstage-cli package test" }, "dependencies": { + "@backstage/catalog-model": "workspace:^", + "@backstage/errors": "workspace:^", "@backstage/plugin-permission-common": "workspace:^" }, "devDependencies": { diff --git a/plugins/catalog-unprocessed-entities-common/report.api.md b/plugins/catalog-unprocessed-entities-common/report.api.md index 03b316eebb..e373a3f45b 100644 --- a/plugins/catalog-unprocessed-entities-common/report.api.md +++ b/plugins/catalog-unprocessed-entities-common/report.api.md @@ -4,6 +4,53 @@ ```ts import { BasicPermission } from '@backstage/plugin-permission-common'; +import { Entity } from '@backstage/catalog-model'; + +// @public +export interface CatalogUnprocessedEntitiesApi { + delete( + entityId: string, + options?: UnprocessedEntitiesRequestOptions, + ): Promise; + failed( + options?: UnprocessedEntitiesRequestOptions, + ): Promise; + pending( + options?: UnprocessedEntitiesRequestOptions, + ): Promise; +} + +// @public +export type CatalogUnprocessedEntitiesApiResponse = { + entities: UnprocessedEntity[]; +}; + +// @public +export class CatalogUnprocessedEntitiesClient + implements CatalogUnprocessedEntitiesApi +{ + constructor( + discovery: { + getBaseUrl(pluginId: string): Promise; + }, + fetchApi?: { + fetch: typeof fetch; + }, + ); + // (undocumented) + delete( + entityId: string, + options?: UnprocessedEntitiesRequestOptions, + ): Promise; + // (undocumented) + failed( + options?: UnprocessedEntitiesRequestOptions, + ): Promise; + // (undocumented) + pending( + options?: UnprocessedEntitiesRequestOptions, + ): Promise; +} // @public export const unprocessedEntitiesDeletePermission: BasicPermission; @@ -12,4 +59,42 @@ export const unprocessedEntitiesDeletePermission: BasicPermission; export const unprocessedEntitiesPermissions: { unprocessedEntitiesDeletePermission: BasicPermission; }; + +// @public +export interface UnprocessedEntitiesRequestOptions { + // (undocumented) + token?: string; +} + +// @public +export type UnprocessedEntity = { + entity_id: string; + entity_ref: string; + unprocessed_entity: Entity; + unprocessed_hash?: string; + processed_entity?: Entity; + result_hash?: string; + cache?: UnprocessedEntityCache; + next_update_at: string | Date; + last_discovery_at: string | Date; + errors?: UnprocessedEntityError[]; + location_key?: string; +}; + +// @public +export type UnprocessedEntityCache = { + ttl: number; + cache: object; +}; + +// @public +export type UnprocessedEntityError = { + name: string; + message: string; + cause: { + name: string; + message: string; + stack: string; + }; +}; ``` diff --git a/plugins/catalog-unprocessed-entities-common/src/api/api.ts b/plugins/catalog-unprocessed-entities-common/src/api/api.ts new file mode 100644 index 0000000000..02b230aaec --- /dev/null +++ b/plugins/catalog-unprocessed-entities-common/src/api/api.ts @@ -0,0 +1,124 @@ +/* + * 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 { CatalogUnprocessedEntitiesApiResponse } from '../types'; +import { ResponseError } from '@backstage/errors'; + +/** + * Options you can pass into a catalog request for additional information. + * + * @public + */ +export interface UnprocessedEntitiesRequestOptions { + token?: string; +} + +/** + * Interface for the CatalogUnprocessedEntitiesApi. + * + * @public + */ +export interface CatalogUnprocessedEntitiesApi { + /** + * Returns a list of entities with state 'pending' + * + * @param options - Additional options + */ + pending( + options?: UnprocessedEntitiesRequestOptions, + ): Promise; + /** + * Returns a list of entities with state 'failed' + * + * @param options - Additional options + */ + failed( + options?: UnprocessedEntitiesRequestOptions, + ): Promise; + /** + * Deletes an entity from the refresh_state table + * + * @param entityId - The ID of the entity to delete + * @param options - Additional options + */ + delete( + entityId: string, + options?: UnprocessedEntitiesRequestOptions, + ): Promise; +} + +/** + * Default API implementation for the Catalog Unprocessed Entities plugin + * + * @public + */ +export class CatalogUnprocessedEntitiesClient + implements CatalogUnprocessedEntitiesApi +{ + private readonly discovery: { getBaseUrl(pluginId: string): Promise }; + private readonly fetchApi: { fetch: typeof fetch }; + + constructor( + discovery: { getBaseUrl(pluginId: string): Promise }, + fetchApi?: { fetch: typeof fetch }, + ) { + this.discovery = discovery; + this.fetchApi = fetchApi ?? { fetch }; + } + + private async fetch( + method: string, + path: string, + options?: UnprocessedEntitiesRequestOptions, + ): Promise { + const url = await this.discovery.getBaseUrl('catalog'); + const resp = await this.fetchApi.fetch(`${url}/${path}`, { + method, + headers: { + ...(options?.token ? { Authorization: `Bearer ${options.token}` } : {}), + }, + }); + + if (!resp.ok) { + throw await ResponseError.fromResponse(resp); + } + + return resp.status === 204 ? (resp as T) : await resp.json(); + } + + async pending( + options?: UnprocessedEntitiesRequestOptions, + ): Promise { + return await this.fetch('GET', 'entities/unprocessed/pending', options); + } + + async failed( + options?: UnprocessedEntitiesRequestOptions, + ): Promise { + return await this.fetch('GET', 'entities/unprocessed/failed', options); + } + + async delete( + entityId: string, + options?: UnprocessedEntitiesRequestOptions, + ): Promise { + await this.fetch( + 'DELETE', + `entities/unprocessed/delete/${entityId}`, + options, + ); + } +} diff --git a/plugins/catalog-unprocessed-entities-common/src/api/index.ts b/plugins/catalog-unprocessed-entities-common/src/api/index.ts new file mode 100644 index 0000000000..5b8bf17e1d --- /dev/null +++ b/plugins/catalog-unprocessed-entities-common/src/api/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './api'; diff --git a/plugins/catalog-unprocessed-entities-common/src/index.ts b/plugins/catalog-unprocessed-entities-common/src/index.ts index cd5a39c731..d11cb4fdb5 100644 --- a/plugins/catalog-unprocessed-entities-common/src/index.ts +++ b/plugins/catalog-unprocessed-entities-common/src/index.ts @@ -20,4 +20,6 @@ * @packageDocumentation */ +export * from './api'; export * from './permissions'; +export * from './types'; diff --git a/plugins/catalog-unprocessed-entities-common/src/types.ts b/plugins/catalog-unprocessed-entities-common/src/types.ts new file mode 100644 index 0000000000..d7af485e83 --- /dev/null +++ b/plugins/catalog-unprocessed-entities-common/src/types.ts @@ -0,0 +1,66 @@ +/* + * 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 { Entity } from '@backstage/catalog-model'; + +/** + * Unprocessed entity data stored in the database. + * @public + */ +export type UnprocessedEntity = { + entity_id: string; + entity_ref: string; + unprocessed_entity: Entity; + unprocessed_hash?: string; + processed_entity?: Entity; + result_hash?: string; + cache?: UnprocessedEntityCache; + next_update_at: string | Date; + last_discovery_at: string | Date; // remove? + errors?: UnprocessedEntityError[]; + location_key?: string; +}; + +/** + * Unprocessed entity cache stored in the database. + * @public + */ +export type UnprocessedEntityCache = { + ttl: number; + cache: object; +}; + +/** + * Unprocessed entity error information stored in the database. + * @public + */ +export type UnprocessedEntityError = { + name: string; + message: string; + cause: { + name: string; + message: string; + stack: string; + }; +}; + +/** + * Response expected by the {@link CatalogUnprocessedEntitiesApi} + * + * @public + */ +export type CatalogUnprocessedEntitiesApiResponse = { + entities: UnprocessedEntity[]; +}; diff --git a/plugins/catalog-unprocessed-entities/CHANGELOG.md b/plugins/catalog-unprocessed-entities/CHANGELOG.md index 106b5edf2d..07a2af83aa 100644 --- a/plugins/catalog-unprocessed-entities/CHANGELOG.md +++ b/plugins/catalog-unprocessed-entities/CHANGELOG.md @@ -1,5 +1,234 @@ # @backstage/plugin-catalog-unprocessed-entities +## 0.2.29 + +### Patch Changes + +- 482ceed: Migrated from `assertError` to `toError` for error handling. +- b6f1fae: The unprocessed entities view is now primarily intended for use as a tab within the DevTools plugin. The standalone page is still available but disabled by default. To re-enable it, add the following to your `app-config.yaml`: + + ```yaml + app: + extensions: + - page:catalog-unprocessed-entities + ``` + +- Updated dependencies + - @backstage/ui@0.14.0 + - @backstage/errors@1.3.0 + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/core-components@0.18.9 + - @backstage/core-compat-api@0.5.10 + - @backstage/core-plugin-api@1.12.5 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.14 + +## 0.2.29-next.2 + +### Patch Changes + +- 482ceed: Migrated from `assertError` to `toError` for error handling. +- Updated dependencies + - @backstage/ui@0.14.0-next.2 + - @backstage/errors@1.3.0-next.0 + - @backstage/core-components@0.18.9-next.1 + - @backstage/core-compat-api@0.5.10-next.2 + - @backstage/core-plugin-api@1.12.5-next.2 + - @backstage/frontend-plugin-api@0.16.0-next.2 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.14-next.0 + +## 0.2.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-compat-api@0.5.10-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + +## 0.2.29-next.0 + +### Patch Changes + +- b6f1fae: The unprocessed entities view is now primarily intended for use as a tab within the DevTools plugin. The standalone page is still available but disabled by default. To re-enable it, add the following to your `app-config.yaml`: + + ```yaml + app: + extensions: + - page:catalog-unprocessed-entities + ``` + +- Updated dependencies + - @backstage/ui@0.14.0-next.0 + - @backstage/core-components@0.18.9-next.0 + - @backstage/frontend-plugin-api@0.15.2-next.0 + - @backstage/core-compat-api@0.5.10-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.13 + +## 0.2.27 + +### Patch Changes + +- 538c985: Updated installation documentation to use feature discovery as the default. +- aa29b50: New frontend system pages now use the default plugin header together with `HeaderPage` instead of the legacy core page header pattern. +- 3f36ce1: Updated alpha plugin icons to follow the new frontend icon sizing rules when rendered in plugin and navigation surfaces. +- f4a1edd: Removed the deprecated `DevToolsContentBlueprint` from `@backstage/plugin-devtools-react`. DevTools pages in the new frontend system now use `SubPageBlueprint` tabs instead, and the catalog unprocessed entities alpha extension now attaches to DevTools as a subpage. +- Updated dependencies + - @backstage/ui@0.13.0 + - @backstage/core-compat-api@0.5.9 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + +## 0.2.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.5.9-next.2 + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/core-components@0.18.8-next.1 + - @backstage/plugin-devtools-react@0.1.2-next.1 + +## 0.2.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/core-compat-api@0.5.9-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.13 + - @backstage/plugin-devtools-react@0.1.2-next.0 + +## 0.2.26 + +### Patch Changes + +- 018ca87: Added `title` and `icon` to the plugin definition for the new frontend system. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/core-components@0.18.7 + - @backstage/core-compat-api@0.5.8 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/plugin-devtools-react@0.1.1 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.13 + +## 0.2.26-next.1 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/plugin-devtools-react@0.1.1-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-compat-api@0.5.8-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + +## 0.2.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.6-next.0 + - @backstage/core-compat-api@0.5.7-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.13-next.0 + - @backstage/plugin-devtools-react@0.1.1-next.0 + +## 0.2.25 + +### Patch Changes + +- be6cef5: Add support for adding `unprocessed-entities` and other tabs to `devtools` when using the New Frontend system +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + - @backstage/plugin-devtools-react@0.1.0 + - @backstage/core-compat-api@0.5.6 + +## 0.2.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + +## 0.2.24 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- df4d646: Moved types, API and client to the common package, allowing both frontend and + backend plugins to use the `CatalogUnprocessedEntitiesClient`. + + The following types, clients and interfaces have been deprecated and should be + imported from the `@backstage/plugin-catalog-unprocessed-entities-common` instead: + `CatalogUnprocessedEntitiesApi`, `CatalogUnprocessedEntitiesApiResponse`, `UnprocessedEntity`, + `UnprocessedEntityCache`, `UnprocessedEntityError`, `CatalogUnprocessedEntitiesClient`. + + All those types, clients and interfaces are re-exported temporarily in the + `@backstage/plugin-catalog-unprocessed-entities` package until cleaned up. + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.12 + +## 0.2.24-next.1 + +### Patch Changes + +- df4d646: Moved types, API and client to the common package, allowing both frontend and + backend plugins to use the `CatalogUnprocessedEntitiesClient`. + + The following types, clients and interfaces have been deprecated and should be + imported from the `@backstage/plugin-catalog-unprocessed-entities-common` instead: + `CatalogUnprocessedEntitiesApi`, `CatalogUnprocessedEntitiesApiResponse`, `UnprocessedEntity`, + `UnprocessedEntityCache`, `UnprocessedEntityError`, `CatalogUnprocessedEntitiesClient`. + + All those types, clients and interfaces are re-exported temporarily in the + `@backstage/plugin-catalog-unprocessed-entities` package until cleaned up. + +- Updated dependencies + - @backstage/core-components@0.18.4-next.1 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.12-next.0 + +## 0.2.24-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + +## 0.2.23 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/catalog-model@1.7.6 + ## 0.2.23-next.0 ### Patch Changes diff --git a/plugins/catalog-unprocessed-entities/README.md b/plugins/catalog-unprocessed-entities/README.md index ebadbb7054..fc20168b80 100644 --- a/plugins/catalog-unprocessed-entities/README.md +++ b/plugins/catalog-unprocessed-entities/README.md @@ -32,7 +32,26 @@ Requires the `@backstage/plugin-catalog-backend-module-unprocessed` module to be yarn --cwd packages/app add @backstage/plugin-catalog-unprocessed-entities ``` -Import into your `App.tsx` and include into the `` component: +Once installed, the plugin is automatically available in your app through the default feature discovery. For more details and alternative installation methods, see [installing plugins](https://backstage.io/docs/frontend-system/building-apps/installing-plugins). + +You can optionally add unprocessed entities as a tab in DevTools through configuration: + +```yaml +app: + extensions: + # Enable the catalog-unprocessed-entities tab in devtools + - devtools-content:catalog-unprocessed-entities: true + # Disable the catalog-unprocessed-entities element outside devtools including the sidebar + - page:catalog-unprocessed-entities: false +``` + +## Old Frontend System + +If your Backstage app uses the old frontend system, you need to manually wire the +plugin into your app as outlined in this section. If you are on the new frontend +system, you can skip this. + +Import it into your `App.tsx` and include it in the `` component: ```tsx title="packages/app/src/App.tsx" import { CatalogUnprocessedEntitiesPage } from '@backstage/plugin-catalog-unprocessed-entities'; @@ -44,26 +63,6 @@ import { CatalogUnprocessedEntitiesPage } from '@backstage/plugin-catalog-unproc />; ``` -### Integrating with the New Frontend System - -Follow this section if you are using Backstage's [new frontend system](https://backstage.io/docs/frontend-system/). - -Import `catalogUnprocessedEntitiesPlugin` in your `App.tsx` and add it to your app's `features` array: - -```typescript -import catalogUnprocessedEntitiesPlugin from '@backstage/plugin-catalog-unprocessed-entities'; - -// ... - -export const app = createApp({ - features: [ - // ... - catalogUnprocessedEntitiesPlugin, - // ... - ], -}); -``` - ## Customization If you want to use the provided endpoints in a different way, you can use the ApiRef doing the following: diff --git a/plugins/catalog-unprocessed-entities/knip-report.md b/plugins/catalog-unprocessed-entities/knip-report.md index 5f40723fab..53f15337d0 100644 --- a/plugins/catalog-unprocessed-entities/knip-report.md +++ b/plugins/catalog-unprocessed-entities/knip-report.md @@ -1,8 +1,15 @@ # Knip report +## Unused dependencies (2) + +| Name | Location | Severity | +| :------------------------- | :---------------- | :------- | +| @backstage/core-compat-api | package.json:53:6 | error | +| @backstage/errors | package.json:56:6 | error | + ## Unused devDependencies (1) -| Name | Location | Severity | -| :--------------------- | :----------- | :------- | -| @testing-library/react | plugins/catalog-unprocessed-entities/package.json | error | +| Name | Location | Severity | +| :--------------------- | :---------------- | :------- | +| @testing-library/react | package.json:70:6 | error | diff --git a/plugins/catalog-unprocessed-entities/package.json b/plugins/catalog-unprocessed-entities/package.json index 05984864cf..9142204dd0 100644 --- a/plugins/catalog-unprocessed-entities/package.json +++ b/plugins/catalog-unprocessed-entities/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-unprocessed-entities", - "version": "0.2.23-next.0", + "version": "0.2.29", "backstage": { "role": "frontend-plugin", "pluginId": "catalog-unprocessed-entities", @@ -50,12 +50,13 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/catalog-model": "workspace:^", "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", + "@backstage/plugin-catalog-unprocessed-entities-common": "workspace:^", + "@backstage/ui": "workspace:^", "@material-ui/core": "^4.9.13", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "^4.0.0-alpha.60", @@ -70,13 +71,13 @@ "@types/react": "^18.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/plugins/catalog-unprocessed-entities/report-alpha.api.md b/plugins/catalog-unprocessed-entities/report-alpha.api.md index 5518f6d273..23d7e7b931 100644 --- a/plugins/catalog-unprocessed-entities/report-alpha.api.md +++ b/plugins/catalog-unprocessed-entities/report-alpha.api.md @@ -6,13 +6,17 @@ import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { ApiFactory } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; -import { IconComponent } from '@backstage/core-plugin-api'; +import { ExtensionInput } from '@backstage/frontend-plugin-api'; +import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; -import { RouteRef } from '@backstage/frontend-plugin-api'; +import { RouteRef } from '@backstage/core-plugin-api'; +import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) const _default: OverridableFrontendPlugin< @@ -39,13 +43,17 @@ const _default: OverridableFrontendPlugin< 'nav-item:catalog-unprocessed-entities': OverridableExtensionDefinition<{ kind: 'nav-item'; name: undefined; - config: {}; - configInput: {}; + config: { + title: string | undefined; + }; + configInput: { + title?: string | undefined; + }; output: ExtensionDataRef< { title: string; icon: IconComponent; - routeRef: RouteRef; + routeRef: RouteRef_2; }, 'core.nav-item.target', {} @@ -54,7 +62,7 @@ const _default: OverridableFrontendPlugin< params: { title: string; icon: IconComponent; - routeRef: RouteRef; + routeRef: RouteRef_2; }; }>; 'page:catalog-unprocessed-entities': OverridableExtensionDefinition<{ @@ -62,26 +70,113 @@ const _default: OverridableFrontendPlugin< name: undefined; config: { path: string | undefined; + title: string | undefined; }; configInput: { path?: string | undefined; + title?: string | undefined; }; output: | ExtensionDataRef + | ExtensionDataRef< + RouteRef_2, + 'core.routing.ref', + { + optional: true; + } + > | ExtensionDataRef | ExtensionDataRef< - RouteRef, + string, + 'core.title', + { + optional: true; + } + > + | ExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >; + inputs: { + pages: ExtensionInput< + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef_2, + 'core.routing.ref', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >, + { + singleton: false; + optional: false; + internal: false; + } + >; + }; + params: { + path: string; + title?: string; + icon?: IconElement; + loader?: () => Promise; + routeRef?: RouteRef_2; + noHeader?: boolean; + }; + }>; + 'sub-page:catalog-unprocessed-entities': OverridableExtensionDefinition<{ + kind: 'sub-page'; + name: undefined; + config: { + path: string | undefined; + title: string | undefined; + }; + configInput: { + path?: string | undefined; + title?: string | undefined; + }; + output: + | ExtensionDataRef + | ExtensionDataRef< + RouteRef_2, 'core.routing.ref', { optional: true; } + > + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } >; inputs: {}; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; + title: string; + icon?: IconElement; loader: () => Promise; - routeRef?: RouteRef; + routeRef?: RouteRef_2; }; }>; } diff --git a/plugins/catalog-unprocessed-entities/report.api.md b/plugins/catalog-unprocessed-entities/report.api.md index adaf7af643..deeafb8a7c 100644 --- a/plugins/catalog-unprocessed-entities/report.api.md +++ b/plugins/catalog-unprocessed-entities/report.api.md @@ -3,26 +3,26 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { ApiRef } from '@backstage/core-plugin-api'; +import { ApiRef } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; -import { Entity } from '@backstage/catalog-model'; +import { CatalogUnprocessedEntitiesApi as CatalogUnprocessedEntitiesApi_2 } from '@backstage/plugin-catalog-unprocessed-entities-common'; +import { CatalogUnprocessedEntitiesApiResponse as CatalogUnprocessedEntitiesApiResponse_2 } from '@backstage/plugin-catalog-unprocessed-entities-common'; import { JSX as JSX_2 } from 'react/jsx-runtime'; import { RouteRef } from '@backstage/core-plugin-api'; +import type { UnprocessedEntity as UnprocessedEntity_2 } from '@backstage/plugin-catalog-unprocessed-entities-common'; +import type { UnprocessedEntityCache as UnprocessedEntityCache_2 } from '@backstage/plugin-catalog-unprocessed-entities-common'; +import type { UnprocessedEntityError as UnprocessedEntityError_2 } from '@backstage/plugin-catalog-unprocessed-entities-common'; -// @public -export interface CatalogUnprocessedEntitiesApi { - delete(entityId: string): Promise; - failed(): Promise; - pending(): Promise; -} +// @public @deprecated +export interface CatalogUnprocessedEntitiesApi + extends CatalogUnprocessedEntitiesApi_2 {} // @public export const catalogUnprocessedEntitiesApiRef: ApiRef; -// @public -export type CatalogUnprocessedEntitiesApiResponse = { - entities: UnprocessedEntity[]; -}; +// @public @deprecated +export type CatalogUnprocessedEntitiesApiResponse = + CatalogUnprocessedEntitiesApiResponse_2; // @public export const CatalogUnprocessedEntitiesPage: () => JSX_2.Element; @@ -38,37 +38,14 @@ export const catalogUnprocessedEntitiesPlugin: BackstagePlugin< // @public (undocumented) export const UnprocessedEntitiesContent: () => JSX_2.Element; -// @public -export type UnprocessedEntity = { - entity_id: string; - entity_ref: string; - unprocessed_entity: Entity; - unprocessed_hash?: string; - processed_entity?: Entity; - result_hash?: string; - cache?: UnprocessedEntityCache; - next_update_at: string | Date; - last_discovery_at: string | Date; - errors?: UnprocessedEntityError[]; - location_key?: string; -}; +// @public @deprecated +export type UnprocessedEntity = UnprocessedEntity_2; -// @public -export type UnprocessedEntityCache = { - ttl: number; - cache: object; -}; +// @public @deprecated +export type UnprocessedEntityCache = UnprocessedEntityCache_2; -// @public -export type UnprocessedEntityError = { - name: string; - message: string; - cause: { - name: string; - message: string; - stack: string; - }; -}; +// @public @deprecated +export type UnprocessedEntityError = UnprocessedEntityError_2; // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx b/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx index 39a51184d3..1bf2bc2bc4 100644 --- a/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx +++ b/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx @@ -21,18 +21,16 @@ import { ApiBlueprint, PageBlueprint, NavItemBlueprint, + SubPageBlueprint, } from '@backstage/frontend-plugin-api'; import { catalogUnprocessedEntitiesApiRef, CatalogUnprocessedEntitiesClient, } from '../api'; -import { - compatWrapper, - convertLegacyRouteRef, -} from '@backstage/core-compat-api'; import QueueIcon from '@material-ui/icons/Queue'; import { rootRouteRef } from '../routes'; +import { Container } from '@backstage/ui'; /** @alpha */ export const catalogUnprocessedEntitiesApi = ApiBlueprint.make({ @@ -50,13 +48,14 @@ export const catalogUnprocessedEntitiesApi = ApiBlueprint.make({ /** @alpha */ export const catalogUnprocessedEntitiesPage = PageBlueprint.make({ + disabled: true, params: { path: '/catalog-unprocessed-entities', - routeRef: convertLegacyRouteRef(rootRouteRef), + routeRef: rootRouteRef, loader: () => - import('../components/UnprocessedEntities').then(m => - compatWrapper(), - ), + import('../components/UnprocessedEntities').then(m => ( + + )), }, }); @@ -64,21 +63,43 @@ export const catalogUnprocessedEntitiesPage = PageBlueprint.make({ export const catalogUnprocessedEntitiesNavItem = NavItemBlueprint.make({ params: { title: 'Unprocessed Entities', - routeRef: convertLegacyRouteRef(rootRouteRef), + routeRef: rootRouteRef, icon: QueueIcon, }, }); +/** + * DevTools content for catalog unprocessed entities. + * + * @alpha + */ +export const unprocessedEntitiesDevToolsContent = SubPageBlueprint.make({ + attachTo: { id: 'page:devtools', input: 'pages' }, + params: { + path: 'unprocessed-entities', + title: 'Unprocessed Entities', + loader: () => + import('../components/UnprocessedEntities').then(m => ( + + + + )), + }, +}); + /** @alpha */ export default createFrontendPlugin({ pluginId: 'catalog-unprocessed-entities', + title: 'Unprocessed Entities', + icon: , info: { packageJson: () => import('../../package.json') }, routes: { - root: convertLegacyRouteRef(rootRouteRef), + root: rootRouteRef, }, extensions: [ catalogUnprocessedEntitiesApi, catalogUnprocessedEntitiesPage, catalogUnprocessedEntitiesNavItem, + unprocessedEntitiesDevToolsContent, ], }); diff --git a/plugins/catalog-unprocessed-entities/src/api/index.ts b/plugins/catalog-unprocessed-entities/src/api/index.ts index 67daa81319..cec9bd29bd 100644 --- a/plugins/catalog-unprocessed-entities/src/api/index.ts +++ b/plugins/catalog-unprocessed-entities/src/api/index.ts @@ -13,13 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { createApiRef } from '@backstage/core-plugin-api'; import { - DiscoveryApi, - createApiRef, - FetchApi, -} from '@backstage/core-plugin-api'; -import { ResponseError } from '@backstage/errors'; -import { UnprocessedEntity } from '../types'; + CatalogUnprocessedEntitiesApiResponse as CommonCatalogUnprocessedEntitiesApiResponse, + CatalogUnprocessedEntitiesApi as CommonCatalogUnprocessedEntitiesApi, + CatalogUnprocessedEntitiesClient as CommonCatalogUnprocessedEntitiesClient, +} from '@backstage/plugin-catalog-unprocessed-entities-common'; /** * {@link @backstage/core-plugin-api#ApiRef} for the {@link CatalogUnprocessedEntitiesApi} @@ -35,69 +34,24 @@ export const catalogUnprocessedEntitiesApiRef = * Response expected by the {@link CatalogUnprocessedEntitiesApi} * * @public + * @deprecated Use the type imported from `@backstage/plugin-catalog-unprocessed-entities-common` instead. */ -export type CatalogUnprocessedEntitiesApiResponse = { - entities: UnprocessedEntity[]; -}; +export type CatalogUnprocessedEntitiesApiResponse = + CommonCatalogUnprocessedEntitiesApiResponse; /** * Interface for the CatalogUnprocessedEntitiesApi. * * @public + * @deprecated Use the type imported from `@backstage/plugin-catalog-unprocessed-entities-common` instead. */ -export interface CatalogUnprocessedEntitiesApi { - /** - * Returns a list of entities with state 'pending' - */ - pending(): Promise; - /** - * Returns a list of entities with state 'failed' - */ - failed(): Promise; - /** - * Deletes an entity from the refresh_state table - */ - delete(entityId: string): Promise; -} +export interface CatalogUnprocessedEntitiesApi + extends CommonCatalogUnprocessedEntitiesApi {} /** * Default API implementation for the Catalog Unprocessed Entities plugin * * @public + * @deprecated Use the client imported from `@backstage/plugin-catalog-unprocessed-entities-common` instead. */ -export class CatalogUnprocessedEntitiesClient - implements CatalogUnprocessedEntitiesApi -{ - public discovery: DiscoveryApi; - public fetchApi: FetchApi; - - constructor(discovery: DiscoveryApi, fetchApi: FetchApi) { - this.discovery = discovery; - this.fetchApi = fetchApi; - } - - private async fetch(path: string, init?: RequestInit): Promise { - const url = await this.discovery.getBaseUrl('catalog'); - const resp = await this.fetchApi.fetch(`${url}/${path}`, init); - - if (!resp.ok) { - throw await ResponseError.fromResponse(resp); - } - - return resp.status === 204 ? (resp as T) : await resp.json(); - } - - async pending(): Promise { - return await this.fetch('entities/unprocessed/pending'); - } - - async failed(): Promise { - return await this.fetch('entities/unprocessed/failed'); - } - - async delete(entityId: string): Promise { - await this.fetch(`entities/unprocessed/delete/${entityId}`, { - method: 'DELETE', - }); - } -} +export class CatalogUnprocessedEntitiesClient extends CommonCatalogUnprocessedEntitiesClient {} diff --git a/plugins/catalog-unprocessed-entities/src/components/DeleteEntityConfirmationDialog.tsx b/plugins/catalog-unprocessed-entities/src/components/DeleteEntityConfirmationDialog.tsx index 91a67551c9..4718491024 100644 --- a/plugins/catalog-unprocessed-entities/src/components/DeleteEntityConfirmationDialog.tsx +++ b/plugins/catalog-unprocessed-entities/src/components/DeleteEntityConfirmationDialog.tsx @@ -19,7 +19,6 @@ import Dialog from '@material-ui/core/Dialog'; import DialogActions from '@material-ui/core/DialogActions'; import DialogTitle from '@material-ui/core/DialogTitle'; import { useState } from 'react'; -import { assertError } from '@backstage/errors'; interface DeleteEntityConfirmationProps { open: boolean; @@ -36,8 +35,8 @@ export function DeleteEntityConfirmationDialog( setBusy(true); try { onConfirm(); - } catch (err) { - assertError(err); + } catch { + // ignored } finally { setBusy(false); } diff --git a/plugins/catalog-unprocessed-entities/src/components/UnprocessedEntities.tsx b/plugins/catalog-unprocessed-entities/src/components/UnprocessedEntities.tsx index 425683be25..fdbd90fb30 100644 --- a/plugins/catalog-unprocessed-entities/src/components/UnprocessedEntities.tsx +++ b/plugins/catalog-unprocessed-entities/src/components/UnprocessedEntities.tsx @@ -16,6 +16,7 @@ import { ChangeEvent, useState } from 'react'; import { Page, Header, Content } from '@backstage/core-components'; +import { HeaderPage } from '@backstage/ui'; import Tab from '@material-ui/core/Tab'; import { makeStyles } from '@material-ui/core/styles'; import TabContext from '@material-ui/lab/TabContext'; @@ -66,3 +67,14 @@ export const UnprocessedEntities = () => { ); }; + +export const NfsUnprocessedEntities = () => { + return ( + <> + + + + + + ); +}; diff --git a/plugins/catalog-unprocessed-entities/src/types.ts b/plugins/catalog-unprocessed-entities/src/types.ts index 65f21970f5..38b4b9dad7 100644 --- a/plugins/catalog-unprocessed-entities/src/types.ts +++ b/plugins/catalog-unprocessed-entities/src/types.ts @@ -13,45 +13,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; +import type { + UnprocessedEntity as CommonUnprocessedEntity, + UnprocessedEntityCache as CommonUnprocessedEntityCache, + UnprocessedEntityError as CommonUnprocessedEntityError, +} from '@backstage/plugin-catalog-unprocessed-entities-common'; /** * Unprocessed entity data stored in the database. * @public + * @deprecated Use the type imported from `@backstage/plugin-catalog-unprocessed-entities-common` instead. */ -export type UnprocessedEntity = { - entity_id: string; - entity_ref: string; - unprocessed_entity: Entity; - unprocessed_hash?: string; - processed_entity?: Entity; - result_hash?: string; - cache?: UnprocessedEntityCache; - next_update_at: string | Date; - last_discovery_at: string | Date; // remove? - errors?: UnprocessedEntityError[]; - location_key?: string; -}; +export type UnprocessedEntity = CommonUnprocessedEntity; /** * Unprocessed entity cache stored in the database. * @public + * @deprecated Use the type imported from `@backstage/plugin-catalog-unprocessed-entities-common` instead. */ -export type UnprocessedEntityCache = { - ttl: number; - cache: object; -}; +export type UnprocessedEntityCache = CommonUnprocessedEntityCache; /** * Unprocessed entity error information stored in the database. * @public + * @deprecated Use the type imported from `@backstage/plugin-catalog-unprocessed-entities-common` instead. */ -export type UnprocessedEntityError = { - name: string; - message: string; - cause: { - name: string; - message: string; - stack: string; - }; -}; +export type UnprocessedEntityError = CommonUnprocessedEntityError; diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 20c29790d7..d5b1d09388 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,505 @@ # @backstage/plugin-catalog +## 2.0.2 + +### Patch Changes + +- e4804ab: Migrated the unregister entity context menu item from the deprecated `DialogApi.showModal` to the new `DialogApi.open` method. +- d7b6077: Disabled the default page layout header for the catalog entity page in the new frontend system. The entity page already renders its own header through the `EntityHeader` extension, so the page layout header was redundant. +- ee1531d: Exported the NFS variant of the catalog index page as `CatalogIndexPage` from the `./alpha` entry point, along with supporting types `CatalogIndexPageProps`, `CatalogTableRow`, and `CatalogTableColumnsFunc`. This allows adopters to use and customize the catalog index page within a `PageBlueprint` in the new frontend system. +- 482ceed: Migrated from `assertError` to `toError` for error handling. +- 744f904: Fixed the catalog table briefly showing an empty loading state when changing filters. The table now keeps displaying stale results until new data arrives. +- c193ef1: Added Kind field to the About Card. Tags moved before Type and Lifecycle, Kind placed after them. A new `aboutCard.kindField.label` translation key was added. +- e5af44c: Replaced deprecated `humanizeEntityRef` usage with the Catalog Presentation API. +- Updated dependencies + - @backstage/ui@0.14.0 + - @backstage/errors@1.3.0 + - @backstage/catalog-model@1.8.0 + - @backstage/plugin-catalog-react@2.1.2 + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/core-components@0.18.9 + - @backstage/core-compat-api@0.5.10 + - @backstage/catalog-client@1.15.0 + - @backstage/plugin-scaffolder-common@2.1.0 + - @backstage/plugin-permission-react@0.5.0 + - @backstage/core-plugin-api@1.12.5 + - @backstage/integration-react@1.2.17 + - @backstage/plugin-catalog-common@1.1.9 + - @backstage/plugin-search-common@1.2.23 + - @backstage/plugin-search-react@1.11.1 + - @backstage/plugin-techdocs-react@1.3.10 + +## 2.0.2-next.2 + +### Patch Changes + +- ee1531d: Exported the NFS variant of the catalog index page as `CatalogIndexPage` from the `./alpha` entry point, along with supporting types `CatalogIndexPageProps`, `CatalogTableRow`, and `CatalogTableColumnsFunc`. This allows adopters to use and customize the catalog index page within a `PageBlueprint` in the new frontend system. +- 482ceed: Migrated from `assertError` to `toError` for error handling. +- c193ef1: Added Kind field to the About Card. Tags moved before Type and Lifecycle, Kind placed after them. A new `aboutCard.kindField.label` translation key was added. +- e5af44c: Replaced deprecated `humanizeEntityRef` usage with the Catalog Presentation API. +- Updated dependencies + - @backstage/ui@0.14.0-next.2 + - @backstage/errors@1.3.0-next.0 + - @backstage/core-components@0.18.9-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.2 + - @backstage/catalog-client@1.14.1-next.0 + - @backstage/catalog-model@1.7.8-next.0 + - @backstage/core-compat-api@0.5.10-next.2 + - @backstage/core-plugin-api@1.12.5-next.2 + - @backstage/frontend-plugin-api@0.16.0-next.2 + - @backstage/plugin-scaffolder-common@2.0.1-next.0 + - @backstage/plugin-search-react@1.11.1-next.2 + - @backstage/plugin-techdocs-react@1.3.10-next.2 + - @backstage/integration-react@1.2.17-next.1 + - @backstage/plugin-catalog-common@1.1.9-next.0 + - @backstage/plugin-permission-react@0.4.42-next.1 + - @backstage/plugin-search-common@1.2.23-next.0 + +## 2.0.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-compat-api@0.5.10-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-search-react@1.11.1-next.1 + - @backstage/plugin-techdocs-react@1.3.10-next.1 + +## 2.0.2-next.0 + +### Patch Changes + +- d7b6077: Disabled the default page layout header for the catalog entity page in the new frontend system. The entity page already renders its own header through the `EntityHeader` extension, so the page layout header was redundant. +- 744f904: Fixed the catalog table briefly showing an empty loading state when changing filters. The table now keeps displaying stale results until new data arrives. +- Updated dependencies + - @backstage/ui@0.14.0-next.0 + - @backstage/plugin-catalog-react@2.1.1-next.0 + - @backstage/core-components@0.18.9-next.0 + - @backstage/frontend-plugin-api@0.15.2-next.0 + - @backstage/core-compat-api@0.5.10-next.0 + - @backstage/integration-react@1.2.17-next.0 + - @backstage/plugin-search-react@1.11.1-next.0 + - @backstage/plugin-techdocs-react@1.3.10-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + - @backstage/catalog-client@1.14.0 + - @backstage/catalog-model@1.7.7 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-react@0.4.42-next.0 + - @backstage/plugin-scaffolder-common@2.0.0 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-techdocs-common@0.1.1 + +## 2.0.0 + +### Major Changes + +- 5fc35bb: Migrated `EntityAboutCard`, `EntityLinksCard`, `EntityLabelsCard`, `GroupProfileCard`, and `UserProfileCard` from MUI/InfoCard to use the new BUI card layout and BUI components where possible. + + **BREAKING**: Removed `variant` prop from EntityAboutCard, EntityUserProfileCard, EntityGroupProfileCard, EntityLabelsCard, EntityLinksCard. Removed `gridSizes` prop from `AboutField`. + + **Migration:** + + Simply delete the obsolete `variant` and `gridSizes` props, e.g: + + ```diff + - + + + ``` + + ```diff + - + + + ``` + +### Minor Changes + +- 4d58894: Added support for group alias IDs and configurable content ordering on the entity page. Groups can now declare `aliases` so that content targeting an aliased group is included in the group. A new `defaultContentOrder` option (default `title`) controls how content items within each group are sorted, with support for both a page-level default and per-group overrides. +- c548a0f: Migrated entity table cards (`HasComponentsCard`, `HasResourcesCard`, `HasSubcomponentsCard`, `HasSubdomainsCard`, `HasSystemsCard`, `DependsOnComponentsCard`, `DependsOnResourcesCard`, `DependencyOfComponentsCard`) to use BUI when no legacy props are passed. The old `variant`, `columns`, and `tableOptions` props are deprecated but still supported — passing any of them triggers the legacy MUI-based rendering. The new `columnConfig` prop accepts `EntityColumnConfig[]` for BUI-based rendering. + + `RelatedEntitiesCard` is deprecated — use `EntityRelationCard` from `@backstage/plugin-catalog-react/alpha` instead. + +- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point. + +### Patch Changes + +- 538c985: Updated installation documentation to use feature discovery as the default. +- 07ba746: Fixed entity page tab groups not respecting the ordering from the `groups` configuration. +- aa29b50: New frontend system pages now use the default plugin header together with `HeaderPage` instead of the legacy core page header pattern. +- 3f36ce1: Updated alpha plugin icons to follow the new frontend icon sizing rules when rendered in plugin and navigation surfaces. +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/ui@0.13.0 + - @backstage/core-compat-api@0.5.9 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/plugin-search-react@1.11.0 + - @backstage/catalog-client@1.14.0 + - @backstage/plugin-permission-react@0.4.41 + - @backstage/plugin-scaffolder-common@2.0.0 + - @backstage/catalog-model@1.7.7 + - @backstage/integration-react@1.2.16 + - @backstage/plugin-techdocs-react@1.3.9 + +## 2.0.0-next.2 + +### Major Changes + +- 5fc35bb: Migrated `EntityAboutCard`, `EntityLinksCard`, `EntityLabelsCard`, `GroupProfileCard`, and `UserProfileCard` from MUI/InfoCard to use the new BUI card layout and BUI components where possible. + + **BREAKING**: Removed `variant` prop from EntityAboutCard, EntityUserProfileCard, EntityGroupProfileCard, EntityLabelsCard, EntityLinksCard. Removed `gridSizes` prop from `AboutField`. + + **Migration:** + + Simply delete the obsolete `variant` and `gridSizes` props, e.g: + + ```diff + - + + + ``` + + ```diff + - + + + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.5.9-next.2 + - @backstage/ui@0.13.0-next.2 + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/catalog-client@1.14.0-next.2 + - @backstage/plugin-catalog-react@2.1.0-next.2 + - @backstage/core-components@0.18.8-next.1 + - @backstage/plugin-scaffolder-common@2.0.0-next.2 + - @backstage/plugin-search-react@1.10.5-next.1 + - @backstage/plugin-techdocs-react@1.3.9-next.1 + +## 1.34.0-next.1 + +### Minor Changes + +- 4d58894: Added support for group alias IDs and configurable content ordering on the entity page. Groups can now declare `aliases` so that content targeting an aliased group is included in the group. A new `defaultContentOrder` option (default `title`) controls how content items within each group are sorted, with support for both a page-level default and per-group overrides. + +### Patch Changes + +- 07ba746: Fixed entity page tab groups not respecting the ordering from the `groups` configuration. +- Updated dependencies + - @backstage/ui@0.13.0-next.1 + - @backstage/catalog-client@1.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/plugin-scaffolder-common@2.0.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-compat-api@0.5.9-next.1 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/integration-react@1.2.16-next.1 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-react@0.4.41-next.0 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-search-react@1.10.5-next.0 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-react@1.3.9-next.0 + +## 1.33.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.12.1-next.0 + - @backstage/plugin-search-react@1.10.5-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/catalog-client@1.13.1-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/core-compat-api@0.5.9-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.16-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-react@0.4.41-next.0 + - @backstage/plugin-scaffolder-common@1.7.7-next.0 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-react@1.3.9-next.0 + +## 1.33.0 + +### Minor Changes + +- 491a06c: Add the ability to show icons for the tabs on the entity page (new frontend) +- 05aac34: Migrated `DeleteEntityDialog` and `EntityOrphanWarning` components to Backstage UI. + + The `deleteEntity.description` translation key no longer includes "Click here to delete" text. A new `deleteEntity.actionButtonTitle` key was added for the action button. + +### Patch Changes + +- 220d6c3: Add missing translation entries for catalog UI text. + + This change adds translation keys and updates relevant UI components to use the correct localized labels and text in the catalog plugin. It ensures that catalog screens such as entity layout, tabs, search result items, table labels, and other UI elements correctly reference the i18n system for translation. + + No functional behavior is changed aside from the improved internationalization support. + +- 8d4c48b: Fixed vertical spacing between tags in the catalog table. +- 7feb83b: Adjusted to use the new `@backstage/filter-predicates` types for predicate expressions. +- 018ca87: Added `title` and `icon` to the plugin definition for the new frontend system. +- e8258d0: The default entity content layout still supports rendering summary cards at runtime for backward compatibility, but logs a console warning when they are detected to help identify where migration is needed. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- 75ac651: Migrated `EntityRelationWarning` and `EntityProcessingErrorsPanel` components from Material UI to Backstage UI. +- Updated dependencies + - @backstage/ui@0.12.0 + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/core-components@0.18.7 + - @backstage/plugin-search-common@1.2.22 + - @backstage/core-compat-api@0.5.8 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/catalog-client@1.13.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-techdocs-react@1.3.8 + - @backstage/integration-react@1.2.15 + - @backstage/plugin-permission-react@0.4.40 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-search-react@1.10.3 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-scaffolder-common@1.7.6 + +## 1.33.0-next.2 + +### Minor Changes + +- 491a06c: Add the ability to show icons for the tabs on the entity page (new frontend) + +### Patch Changes + +- 220d6c3: Add missing translation entries for catalog UI text. + + This change adds translation keys and updates relevant UI components to use the correct localized labels and text in the catalog plugin. It ensures that catalog screens such as entity layout, tabs, search result items, table labels, and other UI elements correctly reference the i18n system for translation. + + No functional behavior is changed aside from the improved internationalization support. + +- 7feb83b: Adjusted to use the new `@backstage/filter-predicates` types for predicate expressions. +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- 75ac651: Migrated `EntityRelationWarning` and `EntityProcessingErrorsPanel` components from Material UI to Backstage UI. +- Updated dependencies + - @backstage/ui@0.12.0-next.2 + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/catalog-client@1.12.2-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/integration-react@1.2.15-next.2 + - @backstage/core-compat-api@0.5.8-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-permission-react@0.4.40-next.1 + - @backstage/version-bridge@1.0.12-next.0 + - @backstage/plugin-techdocs-react@1.3.8-next.1 + - @backstage/plugin-search-react@1.10.3-next.2 + +## 1.33.0-next.1 + +### Minor Changes + +- 05aac34: Migrated `DeleteEntityDialog` and `EntityOrphanWarning` components to Backstage UI. + + The `deleteEntity.description` translation key no longer includes "Click here to delete" text. A new `deleteEntity.actionButtonTitle` key was added for the action button. + +### Patch Changes + +- 8d4c48b: Fixed vertical spacing between tags in the catalog table. +- e8258d0: The default entity content layout still supports rendering summary cards at runtime for backward compatibility, but logs a console warning when they are detected to help identify where migration is needed. +- Updated dependencies + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/ui@0.12.0-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/core-compat-api@0.5.8-next.1 + - @backstage/plugin-search-react@1.10.3-next.1 + - @backstage/core-components@0.18.7-next.1 + - @backstage/plugin-techdocs-react@1.3.8-next.0 + - @backstage/integration-react@1.2.15-next.1 + - @backstage/plugin-scaffolder-common@1.7.6-next.1 + +## 1.32.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/core-compat-api@0.5.7-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/plugin-techdocs-react@1.3.8-next.0 + - @backstage/plugin-search-react@1.10.3-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.15-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.8-next.0 + - @backstage/plugin-permission-react@0.4.40-next.0 + - @backstage/plugin-scaffolder-common@1.7.6-next.0 + - @backstage/plugin-techdocs-common@0.1.1 + +## 1.32.2 + +### Patch Changes + +- 7ca91e8: Header in EntityLayout should always be shown. + Monitoring the loading status caused flickering when the refresh() method of the Async Entity was invoked. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + - @backstage/core-compat-api@0.5.6 + - @backstage/plugin-search-react@1.10.2 + - @backstage/integration-react@1.2.14 + - @backstage/plugin-catalog-react@1.21.5 + - @backstage/plugin-scaffolder-common@1.7.5 + - @backstage/plugin-techdocs-react@1.3.7 + +## 1.32.2-next.2 + +### Patch Changes + +- 7ca91e8: Header in EntityLayout should always be shown. + Monitoring the loading status caused flickering when the refresh() method of the Async Entity was invoked. + +## 1.32.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.1 + - @backstage/plugin-search-react@1.10.2-next.0 + - @backstage/plugin-techdocs-react@1.3.7-next.0 + +## 1.32.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-scaffolder-common@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.0 + - @backstage/core-compat-api@0.5.6-next.0 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/plugin-search-react@1.10.1 + +## 1.32.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- 91f5ed8: Fixed `catalogAboutEntityCard` to filter icon links before calling useProps(), preventing side effects from hooks in filtered-out links +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-search-react@1.10.1 + - @backstage/core-plugin-api@1.12.1 + - @backstage/core-compat-api@0.5.5 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-permission-react@0.4.39 + - @backstage/plugin-scaffolder-common@1.7.4 + - @backstage/plugin-techdocs-react@1.3.6 + +## 1.32.1-next.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- 91f5ed8: Fixed `catalogAboutEntityCard` to filter icon links before calling useProps(), preventing side effects from hooks in filtered-out links +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-compat-api@0.5.5-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-search-react@1.10.1-next.0 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + +## 1.32.1-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/core-compat-api@0.5.5-next.0 + - @backstage/plugin-search-react@1.10.1-next.0 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-techdocs-common@0.1.1 + +## 1.32.0 + +### Minor Changes + +- a521911: Add support for customizable icons in `SearchResultListItemBlueprint` and related components + +### Patch Changes + +- b500bcc: Fix React warnings for elements not having stable keys. +- a87c0a5: Fixed responsiveness issues with catalog entity content layout using the new frontend system. +- aff04d3: Fixed missing margins for entity warning content area on catalog entity page in NFS. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-search-react@1.10.0 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-techdocs-react@1.3.5 + - @backstage/catalog-client@1.12.1 + - @backstage/integration-react@1.2.12 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-scaffolder-common@1.7.3 + - @backstage/plugin-search-common@1.2.21 + ## 1.32.0-next.1 ### Minor Changes diff --git a/plugins/catalog/README.md b/plugins/catalog/README.md index e8de1118ac..af8c82b35c 100644 --- a/plugins/catalog/README.md +++ b/plugins/catalog/README.md @@ -15,13 +15,17 @@ To check if you already have the package, look under `@backstage/plugin-catalog`. The instructions below walk through restoring the plugin, if you previously removed it. -### Install the package - ```bash # From your Backstage root directory yarn --cwd packages/app add @backstage/plugin-catalog ``` +Once installed, the plugin is automatically available in your app through the default feature discovery. For more details and alternative installation methods, see [installing plugins](https://backstage.io/docs/frontend-system/building-apps/installing-plugins). + +## Old Frontend System + +If your Backstage app uses the old frontend system, you need to manually wire the plugin into your app as outlined in this section. If you are on the new frontend system, you can skip this. + ### Add the plugin to your `packages/app` Add the two pages that the catalog plugin provides to your app. You can choose diff --git a/plugins/catalog/knip-report.md b/plugins/catalog/knip-report.md index 5efd01f3a6..2661c35327 100644 --- a/plugins/catalog/knip-report.md +++ b/plugins/catalog/knip-report.md @@ -1,8 +1,2 @@ # Knip report -## Unused dependencies (1) - -| Name | Location | Severity | -| :------ | :----------- | :------- | -| history | plugins/catalog/package.json | error | - diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 31f0694844..80482076d7 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "1.32.0-next.1", + "version": "2.0.2", "description": "The Backstage plugin for browsing the Backstage catalog", "backstage": { "role": "frontend-plugin", @@ -75,6 +75,7 @@ "@backstage/plugin-techdocs-common": "workspace:^", "@backstage/plugin-techdocs-react": "workspace:^", "@backstage/types": "workspace:^", + "@backstage/ui": "workspace:^", "@backstage/version-bridge": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -82,12 +83,12 @@ "@mui/utils": "^5.14.15", "classnames": "^2.3.1", "dataloader": "^2.0.0", - "history": "^5.0.0", "lodash": "^4.17.21", "pluralize": "^8.0.0", "react-helmet": "6.1.0", "react-use": "^17.2.4", - "zen-observable": "^0.10.0" + "zen-observable": "^0.10.0", + "zod": "^4.0.0" }, "devDependencies": { "@backstage/cli": "workspace:^", @@ -104,14 +105,14 @@ "@types/react": "^18.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0", + "react-router-dom": "^6.30.2", "swr": "^2.2.5" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/plugins/catalog/report-alpha.api.md b/plugins/catalog/report-alpha.api.md index 6f63f50b0b..b3074b11bf 100644 --- a/plugins/catalog/report-alpha.api.md +++ b/plugins/catalog/report-alpha.api.md @@ -6,44 +6,110 @@ import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { ApiFactory } from '@backstage/frontend-plugin-api'; +import { CompoundEntityRef } from '@backstage/catalog-model'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { defaultEntityContentGroups } from '@backstage/plugin-catalog-react/alpha'; import { Entity } from '@backstage/catalog-model'; import { EntityCardType } from '@backstage/plugin-catalog-react/alpha'; import { EntityContentLayoutProps } from '@backstage/plugin-catalog-react/alpha'; import { EntityContextMenuItemParams } from '@backstage/plugin-catalog-react/alpha'; -import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha'; +import { EntityListContextProps } from '@backstage/plugin-catalog-react'; +import { EntityListPagination } from '@backstage/plugin-catalog-react'; +import { EntityOwnerPickerProps } from '@backstage/plugin-catalog-react'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; -import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; -import { IconComponent } from '@backstage/core-plugin-api'; +import { ExternalRouteRef } from '@backstage/core-plugin-api'; +import { FilterPredicate } from '@backstage/filter-predicates'; +import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { IconLinkVerticalProps } from '@backstage/core-components'; import { JSX as JSX_2 } from 'react'; +import { JSX as JSX_3 } from 'react/jsx-runtime'; +import { JSXElementConstructor } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; -import { RouteRef } from '@backstage/frontend-plugin-api'; +import { ReactElement } from 'react'; +import { ReactNode } from 'react'; +import { RouteRef } from '@backstage/core-plugin-api'; +import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api'; import { SearchResultItemExtensionComponent } from '@backstage/plugin-search-react/alpha'; import { SearchResultItemExtensionPredicate } from '@backstage/plugin-search-react/alpha'; import { SearchResultListItemBlueprintParams } from '@backstage/plugin-search-react/alpha'; -import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +import { TableColumn } from '@backstage/core-components'; +import { TableProps } from '@backstage/core-components'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; +import { UserListFilterKind } from '@backstage/plugin-catalog-react'; -// @alpha (undocumented) +// @public (undocumented) +export function CatalogIndexPage(props: CatalogIndexPageProps): JSX_3.Element; + +// @public +export interface CatalogIndexPageProps { + // (undocumented) + actions?: TableProps['actions']; + // (undocumented) + columns?: TableColumn[] | CatalogTableColumnsFunc; + // (undocumented) + emptyContent?: ReactNode; + // (undocumented) + filters?: ReactNode; + // (undocumented) + initialKind?: string; + // (undocumented) + initiallySelectedFilter?: UserListFilterKind; + // (undocumented) + initiallySelectedNamespaces?: string[]; + // (undocumented) + ownerPickerMode?: EntityOwnerPickerProps['mode']; + // (undocumented) + pagination?: EntityListPagination; + // (undocumented) + tableOptions?: TableProps['options']; +} + +// @public +export type CatalogTableColumnsFunc = ( + entityListContext: EntityListContextProps, +) => TableColumn[]; + +// @public (undocumented) +export interface CatalogTableRow { + // (undocumented) + entity: Entity; + // (undocumented) + resolved: { + name: string; + entityRef: string; + partOfSystemRelationTitle?: string; + partOfSystemRelations: CompoundEntityRef[]; + ownedByRelationsTitle?: string; + ownedByRelations: CompoundEntityRef[]; + }; +} + +// @alpha @deprecated (undocumented) export const catalogTranslationRef: TranslationRef< 'catalog', { - readonly 'deleteEntity.description': 'This entity is not referenced by any location and is therefore not receiving updates. Click here to delete.'; + readonly 'deleteEntity.description': 'This entity is not referenced by any location and is therefore not receiving updates.'; readonly 'deleteEntity.cancelButtonTitle': 'Cancel'; readonly 'deleteEntity.deleteButtonTitle': 'Delete'; readonly 'deleteEntity.dialogTitle': 'Are you sure you want to delete this entity?'; + readonly 'deleteEntity.actionButtonTitle': 'Delete entity'; readonly 'indexPage.title': '{{orgName}} Catalog'; readonly 'indexPage.createButtonTitle': 'Create'; readonly 'indexPage.supportButtonContent': 'All your software catalog entities'; + readonly 'entityPage.notFoundMessage': 'There is no {{kind}} with the requested {{link}}.'; + readonly 'entityPage.notFoundLinkText': 'kind, namespace, and name'; readonly 'aboutCard.title': 'About'; + readonly 'aboutCard.unknown': 'unknown'; readonly 'aboutCard.refreshButtonTitle': 'Schedule entity refresh'; readonly 'aboutCard.editButtonTitle': 'Edit Metadata'; + readonly 'aboutCard.editButtonAriaLabel': 'Edit'; readonly 'aboutCard.createSimilarButtonTitle': 'Create something similar'; readonly 'aboutCard.refreshScheduledMessage': 'Refresh scheduled'; + readonly 'aboutCard.refreshButtonAriaLabel': 'Refresh'; readonly 'aboutCard.launchTemplate': 'Launch Template'; readonly 'aboutCard.viewTechdocs': 'View TechDocs'; readonly 'aboutCard.viewSource': 'View Source'; @@ -57,58 +123,66 @@ export const catalogTranslationRef: TranslationRef< readonly 'aboutCard.systemField.label': 'System'; readonly 'aboutCard.parentComponentField.value': 'No Parent Component'; readonly 'aboutCard.parentComponentField.label': 'Parent Component'; + readonly 'aboutCard.kindField.label': 'Kind'; readonly 'aboutCard.typeField.label': 'Type'; readonly 'aboutCard.lifecycleField.label': 'Lifecycle'; readonly 'aboutCard.tagsField.value': 'No Tags'; readonly 'aboutCard.tagsField.label': 'Tags'; readonly 'aboutCard.targetsField.label': 'Targets'; + readonly 'searchResultItem.type': 'Type'; + readonly 'searchResultItem.kind': 'Kind'; + readonly 'searchResultItem.owner': 'Owner'; readonly 'searchResultItem.lifecycle': 'Lifecycle'; - readonly 'searchResultItem.Owner': 'Owner'; + readonly 'catalogTable.allFilters': 'All'; readonly 'catalogTable.warningPanelTitle': 'Could not fetch catalog entities.'; readonly 'catalogTable.viewActionTitle': 'View'; readonly 'catalogTable.editActionTitle': 'Edit'; readonly 'catalogTable.starActionTitle': 'Add to favorites'; readonly 'catalogTable.unStarActionTitle': 'Remove from favorites'; readonly 'dependencyOfComponentsCard.title': 'Dependency of components'; - readonly 'dependencyOfComponentsCard.emptyMessage': 'No component depends on this component'; + readonly 'dependencyOfComponentsCard.emptyMessage': 'No component depends on this component.'; readonly 'dependsOnComponentsCard.title': 'Depends on components'; - readonly 'dependsOnComponentsCard.emptyMessage': 'No component is a dependency of this component'; + readonly 'dependsOnComponentsCard.emptyMessage': 'No component is a dependency of this component.'; readonly 'dependsOnResourcesCard.title': 'Depends on resources'; - readonly 'dependsOnResourcesCard.emptyMessage': 'No resource is a dependency of this component'; + readonly 'dependsOnResourcesCard.emptyMessage': 'No resource is a dependency of this component.'; readonly 'entityContextMenu.copiedMessage': 'Copied!'; readonly 'entityContextMenu.moreButtonTitle': 'More'; readonly 'entityContextMenu.inspectMenuTitle': 'Inspect entity'; readonly 'entityContextMenu.copyURLMenuTitle': 'Copy entity URL'; readonly 'entityContextMenu.unregisterMenuTitle': 'Unregister entity'; + readonly 'entityContextMenu.moreButtonAriaLabel': 'more'; readonly 'entityLabelsCard.title': 'Labels'; - readonly 'entityLabelsCard.emptyDescription': 'No labels defined for this entity. You can add labels to your entity YAML as shown in the highlighted example below:'; readonly 'entityLabelsCard.readMoreButtonTitle': 'Read more'; - readonly 'entityLabels.warningPanelTitle': 'Entity not found'; + readonly 'entityLabelsCard.columnKeyLabel': 'Label'; + readonly 'entityLabelsCard.columnValueLabel': 'Value'; + readonly 'entityLabelsCard.emptyDescription': 'No labels defined for this entity. You can add labels to your entity YAML as shown in the highlighted example below:'; readonly 'entityLabels.ownerLabel': 'Owner'; + readonly 'entityLabels.warningPanelTitle': 'Entity not found'; readonly 'entityLabels.lifecycleLabel': 'Lifecycle'; readonly 'entityLinksCard.title': 'Links'; - readonly 'entityLinksCard.emptyDescription': 'No links defined for this entity. You can add links to your entity YAML as shown in the highlighted example below:'; readonly 'entityLinksCard.readMoreButtonTitle': 'Read more'; + readonly 'entityLinksCard.emptyDescription': 'No links defined for this entity. You can add links to your entity YAML as shown in the highlighted example below:'; readonly 'entityNotFound.title': 'Entity was not found'; readonly 'entityNotFound.description': 'Want to help us build this? Check out our Getting Started documentation.'; readonly 'entityNotFound.docButtonTitle': 'DOCS'; + readonly 'entityTabs.tabsAriaLabel': 'Tabs'; readonly entityProcessingErrorsDescription: 'The error below originates from'; readonly entityRelationWarningDescription: "This entity has relations to other entities, which can't be found in the catalog.\n Entities not found are: "; readonly 'hasComponentsCard.title': 'Has components'; - readonly 'hasComponentsCard.emptyMessage': 'No component is part of this system'; + readonly 'hasComponentsCard.emptyMessage': 'No component is part of this system.'; readonly 'hasResourcesCard.title': 'Has resources'; - readonly 'hasResourcesCard.emptyMessage': 'No resource is part of this system'; + readonly 'hasResourcesCard.emptyMessage': 'No resource is part of this system.'; readonly 'hasSubcomponentsCard.title': 'Has subcomponents'; - readonly 'hasSubcomponentsCard.emptyMessage': 'No subcomponent is part of this component'; + readonly 'hasSubcomponentsCard.emptyMessage': 'No subcomponent is part of this component.'; readonly 'hasSubdomainsCard.title': 'Has subdomains'; - readonly 'hasSubdomainsCard.emptyMessage': 'No subdomain is part of this domain'; + readonly 'hasSubdomainsCard.emptyMessage': 'No subdomain is part of this domain.'; readonly 'hasSystemsCard.title': 'Has systems'; - readonly 'hasSystemsCard.emptyMessage': 'No system is part of this domain'; - readonly 'relatedEntitiesCard.emptyHelpLinkTitle': 'Learn how to change this'; + readonly 'hasSystemsCard.emptyMessage': 'No system is part of this domain.'; + readonly 'relatedEntitiesCard.emptyHelpLinkTitle': 'Learn how to change this.'; readonly 'systemDiagramCard.title': 'System Diagram'; readonly 'systemDiagramCard.description': 'Use pinch & zoom to move around the diagram.'; - readonly 'systemDiagramCard.edgeLabels.dependsOn': 'depends on'; readonly 'systemDiagramCard.edgeLabels.partOf': 'part of'; + readonly 'systemDiagramCard.edgeLabels.dependsOn': 'depends on'; readonly 'systemDiagramCard.edgeLabels.provides': 'provides'; } >; @@ -124,17 +198,23 @@ const _default: OverridableFrontendPlugin< }>; }, { - viewTechDoc: ExternalRouteRef<{ - name: string; - kind: string; - namespace: string; - }>; - createComponent: ExternalRouteRef; - createFromTemplate: ExternalRouteRef<{ - namespace: string; - templateName: string; - }>; - unregisterRedirect: ExternalRouteRef; + viewTechDoc: ExternalRouteRef< + { + name: string; + kind: string; + namespace: string; + }, + true + >; + createComponent: ExternalRouteRef; + createFromTemplate: ExternalRouteRef< + { + namespace: string; + templateName: string; + }, + true + >; + unregisterRedirect: ExternalRouteRef; }, { 'api:catalog': OverridableExtensionDefinition<{ @@ -190,15 +270,7 @@ const _default: OverridableFrontendPlugin< initialFilter?: string | undefined; }; output: ExtensionDataRef; - inputs: { - [x: string]: ExtensionInput< - ExtensionDataRef, - { - singleton: boolean; - optional: boolean; - } - >; - }; + inputs: {}; kind: 'catalog-filter'; name: 'kind'; params: { @@ -224,15 +296,7 @@ const _default: OverridableFrontendPlugin< initialFilter?: 'all' | 'owned' | 'starred' | undefined; }; output: ExtensionDataRef; - inputs: { - [x: string]: ExtensionInput< - ExtensionDataRef, - { - singleton: boolean; - optional: boolean; - } - >; - }; + inputs: {}; kind: 'catalog-filter'; name: 'list'; params: { @@ -247,15 +311,7 @@ const _default: OverridableFrontendPlugin< mode?: 'all' | 'owners-only' | undefined; }; output: ExtensionDataRef; - inputs: { - [x: string]: ExtensionInput< - ExtensionDataRef, - { - singleton: boolean; - optional: boolean; - } - >; - }; + inputs: {}; kind: 'catalog-filter'; name: 'mode'; params: { @@ -308,12 +364,12 @@ const _default: OverridableFrontendPlugin< }>; 'entity-card:catalog/about': OverridableExtensionDefinition<{ config: { - filter: EntityPredicate | undefined; - type: 'content' | 'summary' | 'info' | undefined; + filter: FilterPredicate | undefined; + type: 'content' | 'info' | undefined; }; configInput: { - filter?: EntityPredicate | undefined; - type?: 'content' | 'summary' | 'info' | undefined; + filter?: FilterPredicate | undefined; + type?: 'content' | 'info' | undefined; }; output: | ExtensionDataRef @@ -362,6 +418,7 @@ const _default: OverridableFrontendPlugin< { singleton: false; optional: false; + internal: false; } >; }; @@ -369,7 +426,7 @@ const _default: OverridableFrontendPlugin< name: 'about'; params: { loader: () => Promise; - filter?: string | EntityPredicate | ((entity: Entity) => boolean); + filter?: string | FilterPredicate | ((entity: Entity) => boolean); type?: EntityCardType; }; }>; @@ -377,12 +434,12 @@ const _default: OverridableFrontendPlugin< kind: 'entity-card'; name: 'depends-on-components'; config: { - filter: EntityPredicate | undefined; - type: 'content' | 'summary' | 'info' | undefined; + filter: FilterPredicate | undefined; + type: 'content' | 'info' | undefined; }; configInput: { - filter?: EntityPredicate | undefined; - type?: 'content' | 'summary' | 'info' | undefined; + filter?: FilterPredicate | undefined; + type?: 'content' | 'info' | undefined; }; output: | ExtensionDataRef @@ -410,7 +467,7 @@ const _default: OverridableFrontendPlugin< inputs: {}; params: { loader: () => Promise; - filter?: string | EntityPredicate | ((entity: Entity) => boolean); + filter?: string | FilterPredicate | ((entity: Entity) => boolean); type?: EntityCardType; }; }>; @@ -418,12 +475,12 @@ const _default: OverridableFrontendPlugin< kind: 'entity-card'; name: 'depends-on-resources'; config: { - filter: EntityPredicate | undefined; - type: 'content' | 'summary' | 'info' | undefined; + filter: FilterPredicate | undefined; + type: 'content' | 'info' | undefined; }; configInput: { - filter?: EntityPredicate | undefined; - type?: 'content' | 'summary' | 'info' | undefined; + filter?: FilterPredicate | undefined; + type?: 'content' | 'info' | undefined; }; output: | ExtensionDataRef @@ -451,7 +508,7 @@ const _default: OverridableFrontendPlugin< inputs: {}; params: { loader: () => Promise; - filter?: string | EntityPredicate | ((entity: Entity) => boolean); + filter?: string | FilterPredicate | ((entity: Entity) => boolean); type?: EntityCardType; }; }>; @@ -459,12 +516,12 @@ const _default: OverridableFrontendPlugin< kind: 'entity-card'; name: 'has-components'; config: { - filter: EntityPredicate | undefined; - type: 'content' | 'summary' | 'info' | undefined; + filter: FilterPredicate | undefined; + type: 'content' | 'info' | undefined; }; configInput: { - filter?: EntityPredicate | undefined; - type?: 'content' | 'summary' | 'info' | undefined; + filter?: FilterPredicate | undefined; + type?: 'content' | 'info' | undefined; }; output: | ExtensionDataRef @@ -492,7 +549,7 @@ const _default: OverridableFrontendPlugin< inputs: {}; params: { loader: () => Promise; - filter?: string | EntityPredicate | ((entity: Entity) => boolean); + filter?: string | FilterPredicate | ((entity: Entity) => boolean); type?: EntityCardType; }; }>; @@ -500,12 +557,12 @@ const _default: OverridableFrontendPlugin< kind: 'entity-card'; name: 'has-resources'; config: { - filter: EntityPredicate | undefined; - type: 'content' | 'summary' | 'info' | undefined; + filter: FilterPredicate | undefined; + type: 'content' | 'info' | undefined; }; configInput: { - filter?: EntityPredicate | undefined; - type?: 'content' | 'summary' | 'info' | undefined; + filter?: FilterPredicate | undefined; + type?: 'content' | 'info' | undefined; }; output: | ExtensionDataRef @@ -533,7 +590,7 @@ const _default: OverridableFrontendPlugin< inputs: {}; params: { loader: () => Promise; - filter?: string | EntityPredicate | ((entity: Entity) => boolean); + filter?: string | FilterPredicate | ((entity: Entity) => boolean); type?: EntityCardType; }; }>; @@ -541,12 +598,12 @@ const _default: OverridableFrontendPlugin< kind: 'entity-card'; name: 'has-subcomponents'; config: { - filter: EntityPredicate | undefined; - type: 'content' | 'summary' | 'info' | undefined; + filter: FilterPredicate | undefined; + type: 'content' | 'info' | undefined; }; configInput: { - filter?: EntityPredicate | undefined; - type?: 'content' | 'summary' | 'info' | undefined; + filter?: FilterPredicate | undefined; + type?: 'content' | 'info' | undefined; }; output: | ExtensionDataRef @@ -574,7 +631,7 @@ const _default: OverridableFrontendPlugin< inputs: {}; params: { loader: () => Promise; - filter?: string | EntityPredicate | ((entity: Entity) => boolean); + filter?: string | FilterPredicate | ((entity: Entity) => boolean); type?: EntityCardType; }; }>; @@ -582,12 +639,12 @@ const _default: OverridableFrontendPlugin< kind: 'entity-card'; name: 'has-subdomains'; config: { - filter: EntityPredicate | undefined; - type: 'content' | 'summary' | 'info' | undefined; + filter: FilterPredicate | undefined; + type: 'content' | 'info' | undefined; }; configInput: { - filter?: EntityPredicate | undefined; - type?: 'content' | 'summary' | 'info' | undefined; + filter?: FilterPredicate | undefined; + type?: 'content' | 'info' | undefined; }; output: | ExtensionDataRef @@ -615,7 +672,7 @@ const _default: OverridableFrontendPlugin< inputs: {}; params: { loader: () => Promise; - filter?: string | EntityPredicate | ((entity: Entity) => boolean); + filter?: string | FilterPredicate | ((entity: Entity) => boolean); type?: EntityCardType; }; }>; @@ -623,12 +680,12 @@ const _default: OverridableFrontendPlugin< kind: 'entity-card'; name: 'has-systems'; config: { - filter: EntityPredicate | undefined; - type: 'content' | 'summary' | 'info' | undefined; + filter: FilterPredicate | undefined; + type: 'content' | 'info' | undefined; }; configInput: { - filter?: EntityPredicate | undefined; - type?: 'content' | 'summary' | 'info' | undefined; + filter?: FilterPredicate | undefined; + type?: 'content' | 'info' | undefined; }; output: | ExtensionDataRef @@ -656,7 +713,7 @@ const _default: OverridableFrontendPlugin< inputs: {}; params: { loader: () => Promise; - filter?: string | EntityPredicate | ((entity: Entity) => boolean); + filter?: string | FilterPredicate | ((entity: Entity) => boolean); type?: EntityCardType; }; }>; @@ -664,12 +721,12 @@ const _default: OverridableFrontendPlugin< kind: 'entity-card'; name: 'labels'; config: { - filter: EntityPredicate | undefined; - type: 'content' | 'summary' | 'info' | undefined; + filter: FilterPredicate | undefined; + type: 'content' | 'info' | undefined; }; configInput: { - filter?: EntityPredicate | undefined; - type?: 'content' | 'summary' | 'info' | undefined; + filter?: FilterPredicate | undefined; + type?: 'content' | 'info' | undefined; }; output: | ExtensionDataRef @@ -697,7 +754,7 @@ const _default: OverridableFrontendPlugin< inputs: {}; params: { loader: () => Promise; - filter?: string | EntityPredicate | ((entity: Entity) => boolean); + filter?: string | FilterPredicate | ((entity: Entity) => boolean); type?: EntityCardType; }; }>; @@ -705,12 +762,12 @@ const _default: OverridableFrontendPlugin< kind: 'entity-card'; name: 'links'; config: { - filter: EntityPredicate | undefined; - type: 'content' | 'summary' | 'info' | undefined; + filter: FilterPredicate | undefined; + type: 'content' | 'info' | undefined; }; configInput: { - filter?: EntityPredicate | undefined; - type?: 'content' | 'summary' | 'info' | undefined; + filter?: FilterPredicate | undefined; + type?: 'content' | 'info' | undefined; }; output: | ExtensionDataRef @@ -738,7 +795,7 @@ const _default: OverridableFrontendPlugin< inputs: {}; params: { loader: () => Promise; - filter?: string | EntityPredicate | ((entity: Entity) => boolean); + filter?: string | FilterPredicate | ((entity: Entity) => boolean); type?: EntityCardType; }; }>; @@ -746,25 +803,27 @@ const _default: OverridableFrontendPlugin< config: { path: string | undefined; title: string | undefined; - filter: EntityPredicate | undefined; + filter: FilterPredicate | undefined; group: string | false | undefined; + icon: string | undefined; }; configInput: { - filter?: EntityPredicate | undefined; - title?: string | undefined; path?: string | undefined; + title?: string | undefined; + filter?: FilterPredicate | undefined; group?: string | false | undefined; + icon?: string | undefined; }; output: | ExtensionDataRef - | ExtensionDataRef | ExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; } > + | ExtensionDataRef | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', @@ -786,6 +845,13 @@ const _default: OverridableFrontendPlugin< { optional: true; } + > + | ExtensionDataRef< + string | ReactElement>, + 'catalog.entity-content-icon', + { + optional: true; + } >; inputs: { layouts: ExtensionInput< @@ -811,6 +877,7 @@ const _default: OverridableFrontendPlugin< { singleton: false; optional: false; + internal: false; } >; cards: ExtensionInput< @@ -839,6 +906,7 @@ const _default: OverridableFrontendPlugin< { singleton: false; optional: false; + internal: false; } >; }; @@ -851,19 +919,20 @@ const _default: OverridableFrontendPlugin< title: string; defaultGroup?: [Error: `Use the 'group' param instead`]; group?: keyof defaultEntityContentGroups | (string & {}); + icon?: string | ReactElement; loader: () => Promise; - routeRef?: RouteRef; - filter?: string | EntityPredicate | ((entity: Entity) => boolean); + routeRef?: RouteRef_2; + filter?: string | FilterPredicate | ((entity: Entity) => boolean); }; }>; 'entity-context-menu-item:catalog/copy-entity-url': OverridableExtensionDefinition<{ kind: 'entity-context-menu-item'; name: 'copy-entity-url'; config: { - filter: EntityPredicate | undefined; + filter: FilterPredicate | undefined; }; configInput: { - filter?: EntityPredicate | undefined; + filter?: FilterPredicate | undefined; }; output: | ExtensionDataRef @@ -881,10 +950,10 @@ const _default: OverridableFrontendPlugin< kind: 'entity-context-menu-item'; name: 'inspect-entity'; config: { - filter: EntityPredicate | undefined; + filter: FilterPredicate | undefined; }; configInput: { - filter?: EntityPredicate | undefined; + filter?: FilterPredicate | undefined; }; output: | ExtensionDataRef @@ -902,10 +971,10 @@ const _default: OverridableFrontendPlugin< kind: 'entity-context-menu-item'; name: 'unregister-entity'; config: { - filter: EntityPredicate | undefined; + filter: FilterPredicate | undefined; }; configInput: { - filter?: EntityPredicate | undefined; + filter?: FilterPredicate | undefined; }; output: | ExtensionDataRef @@ -925,12 +994,12 @@ const _default: OverridableFrontendPlugin< config: { label: string | undefined; title: string | undefined; - filter: EntityPredicate | undefined; + filter: FilterPredicate | undefined; }; configInput: { - filter?: EntityPredicate | undefined; label?: string | undefined; title?: string | undefined; + filter?: FilterPredicate | undefined; }; output: | ExtensionDataRef< @@ -955,19 +1024,23 @@ const _default: OverridableFrontendPlugin< inputs: {}; params: { useProps: () => Omit; - filter?: EntityPredicate | ((entity: Entity) => boolean); + filter?: FilterPredicate | ((entity: Entity) => boolean); }; }>; 'nav-item:catalog': OverridableExtensionDefinition<{ kind: 'nav-item'; name: undefined; - config: {}; - configInput: {}; + config: { + title: string | undefined; + }; + configInput: { + title?: string | undefined; + }; output: ExtensionDataRef< { title: string; icon: IconComponent; - routeRef: RouteRef; + routeRef: RouteRef_2; }, 'core.nav-item.target', {} @@ -976,7 +1049,7 @@ const _default: OverridableFrontendPlugin< params: { title: string; icon: IconComponent; - routeRef: RouteRef; + routeRef: RouteRef_2; }; }>; 'page:catalog': OverridableExtensionDefinition<{ @@ -985,50 +1058,97 @@ const _default: OverridableFrontendPlugin< | boolean | { mode: 'offset' | 'cursor'; - offset?: number | undefined; limit?: number | undefined; + offset?: number | undefined; }; - } & { path: string | undefined; + title: string | undefined; }; configInput: { pagination?: | boolean | { mode: 'offset' | 'cursor'; - offset?: number | undefined; limit?: number | undefined; + offset?: number | undefined; } | undefined; - } & { path?: string | undefined; + title?: string | undefined; }; output: | ExtensionDataRef + | ExtensionDataRef< + RouteRef_2, + 'core.routing.ref', + { + optional: true; + } + > | ExtensionDataRef | ExtensionDataRef< - RouteRef, - 'core.routing.ref', + string, + 'core.title', + { + optional: true; + } + > + | ExtensionDataRef< + IconElement, + 'core.icon', { optional: true; } >; inputs: { + pages: ExtensionInput< + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef_2, + 'core.routing.ref', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >, + { + singleton: false; + optional: false; + internal: false; + } + >; filters: ExtensionInput< ConfigurableExtensionDataRef, { singleton: false; optional: false; + internal: false; } >; }; kind: 'page'; name: undefined; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; - loader: () => Promise; - routeRef?: RouteRef; + title?: string; + icon?: IconElement; + loader?: () => Promise; + routeRef?: RouteRef_2; + noHeader?: boolean; }; }>; 'page:catalog/entity': OverridableExtensionDefinition<{ @@ -1038,11 +1158,16 @@ const _default: OverridableFrontendPlugin< string, { title: string; + icon?: string | undefined; + aliases?: string[] | undefined; + contentOrder?: 'title' | 'natural' | undefined; } >[] | undefined; - } & { + defaultContentOrder: 'title' | 'natural'; + showNavItemIcons: boolean; path: string | undefined; + title: string | undefined; }; configInput: { groups?: @@ -1050,23 +1175,72 @@ const _default: OverridableFrontendPlugin< string, { title: string; + icon?: string | undefined; + aliases?: string[] | undefined; + contentOrder?: 'title' | 'natural' | undefined; } >[] | undefined; - } & { + defaultContentOrder?: 'title' | 'natural' | undefined; + showNavItemIcons?: boolean | undefined; path?: string | undefined; + title?: string | undefined; }; output: | ExtensionDataRef + | ExtensionDataRef< + RouteRef_2, + 'core.routing.ref', + { + optional: true; + } + > | ExtensionDataRef | ExtensionDataRef< - RouteRef, - 'core.routing.ref', + string, + 'core.title', + { + optional: true; + } + > + | ExtensionDataRef< + IconElement, + 'core.icon', { optional: true; } >; inputs: { + pages: ExtensionInput< + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef_2, + 'core.routing.ref', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >, + { + singleton: false; + optional: false; + internal: false; + } + >; headers: ExtensionInput< | ConfigurableExtensionDataRef< (entity: Entity) => boolean, @@ -1085,13 +1259,14 @@ const _default: OverridableFrontendPlugin< { singleton: false; optional: false; + internal: false; } >; contents: ExtensionInput< | ConfigurableExtensionDataRef | ConfigurableExtensionDataRef | ConfigurableExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; @@ -1122,10 +1297,18 @@ const _default: OverridableFrontendPlugin< { optional: true; } + > + | ConfigurableExtensionDataRef< + string | ReactElement>, + 'catalog.entity-content-icon', + { + optional: true; + } >, { singleton: false; optional: false; + internal: false; } >; contextMenuItems: ExtensionInput< @@ -1140,16 +1323,19 @@ const _default: OverridableFrontendPlugin< { singleton: false; optional: false; + internal: false; } >; }; kind: 'page'; name: 'entity'; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; - loader: () => Promise; - routeRef?: RouteRef; + title?: string; + icon?: IconElement; + loader?: () => Promise; + routeRef?: RouteRef_2; + noHeader?: boolean; }; }>; 'search-result-list-item:catalog': OverridableExtensionDefinition<{ diff --git a/plugins/catalog/report.api.md b/plugins/catalog/report.api.md index 52b13e1c43..e89ceecf15 100644 --- a/plugins/catalog/report.api.md +++ b/plugins/catalog/report.api.md @@ -11,6 +11,7 @@ import { CompoundEntityRef } from '@backstage/catalog-model'; import { DomainEntity } from '@backstage/catalog-model'; import { ElementType } from 'react'; import { Entity } from '@backstage/catalog-model'; +import { EntityColumnConfig } from '@backstage/plugin-catalog-react/alpha'; import { EntityListContextProps } from '@backstage/plugin-catalog-react'; import { EntityListPagination } from '@backstage/plugin-catalog-react'; import { EntityOwnerPickerProps } from '@backstage/plugin-catalog-react'; @@ -38,13 +39,9 @@ import { TableColumn } from '@backstage/core-components'; import { TableOptions } from '@backstage/core-components'; import { TableProps } from '@backstage/core-components'; import { TabProps } from '@material-ui/core/Tab'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; import { UserListFilterKind } from '@backstage/plugin-catalog-react'; -// @public -export type AboutCardProps = { - variant?: InfoCardVariants; -}; - // @public (undocumented) export function AboutContent(props: AboutContentProps): JSX_2.Element; @@ -64,8 +61,6 @@ export interface AboutFieldProps { // (undocumented) className?: string; // (undocumented) - gridSizes?: Record; - // (undocumented) label: string; // (undocumented) value?: string; @@ -158,6 +153,7 @@ export const CatalogTable: { columns: Readonly<{ createNameColumn(options?: { defaultKind?: string; + entityPresentationApi?: EntityPresentationApi; }): TableColumn; createSystemColumn(): TableColumn; createOwnerColumn(): TableColumn; @@ -221,6 +217,105 @@ export interface CatalogTableRow { // @public (undocumented) export type CatalogTableToolbarClassKey = 'root' | 'text'; +// @public (undocumented) +export const catalogTranslationRef: TranslationRef< + 'catalog', + { + readonly 'deleteEntity.description': 'This entity is not referenced by any location and is therefore not receiving updates.'; + readonly 'deleteEntity.cancelButtonTitle': 'Cancel'; + readonly 'deleteEntity.deleteButtonTitle': 'Delete'; + readonly 'deleteEntity.dialogTitle': 'Are you sure you want to delete this entity?'; + readonly 'deleteEntity.actionButtonTitle': 'Delete entity'; + readonly 'indexPage.title': '{{orgName}} Catalog'; + readonly 'indexPage.createButtonTitle': 'Create'; + readonly 'indexPage.supportButtonContent': 'All your software catalog entities'; + readonly 'entityPage.notFoundMessage': 'There is no {{kind}} with the requested {{link}}.'; + readonly 'entityPage.notFoundLinkText': 'kind, namespace, and name'; + readonly 'aboutCard.title': 'About'; + readonly 'aboutCard.unknown': 'unknown'; + readonly 'aboutCard.refreshButtonTitle': 'Schedule entity refresh'; + readonly 'aboutCard.editButtonTitle': 'Edit Metadata'; + readonly 'aboutCard.editButtonAriaLabel': 'Edit'; + readonly 'aboutCard.createSimilarButtonTitle': 'Create something similar'; + readonly 'aboutCard.refreshScheduledMessage': 'Refresh scheduled'; + readonly 'aboutCard.refreshButtonAriaLabel': 'Refresh'; + readonly 'aboutCard.launchTemplate': 'Launch Template'; + readonly 'aboutCard.viewTechdocs': 'View TechDocs'; + readonly 'aboutCard.viewSource': 'View Source'; + readonly 'aboutCard.descriptionField.value': 'No description'; + readonly 'aboutCard.descriptionField.label': 'Description'; + readonly 'aboutCard.ownerField.value': 'No Owner'; + readonly 'aboutCard.ownerField.label': 'Owner'; + readonly 'aboutCard.domainField.value': 'No Domain'; + readonly 'aboutCard.domainField.label': 'Domain'; + readonly 'aboutCard.systemField.value': 'No System'; + readonly 'aboutCard.systemField.label': 'System'; + readonly 'aboutCard.parentComponentField.value': 'No Parent Component'; + readonly 'aboutCard.parentComponentField.label': 'Parent Component'; + readonly 'aboutCard.kindField.label': 'Kind'; + readonly 'aboutCard.typeField.label': 'Type'; + readonly 'aboutCard.lifecycleField.label': 'Lifecycle'; + readonly 'aboutCard.tagsField.value': 'No Tags'; + readonly 'aboutCard.tagsField.label': 'Tags'; + readonly 'aboutCard.targetsField.label': 'Targets'; + readonly 'searchResultItem.type': 'Type'; + readonly 'searchResultItem.kind': 'Kind'; + readonly 'searchResultItem.owner': 'Owner'; + readonly 'searchResultItem.lifecycle': 'Lifecycle'; + readonly 'catalogTable.allFilters': 'All'; + readonly 'catalogTable.warningPanelTitle': 'Could not fetch catalog entities.'; + readonly 'catalogTable.viewActionTitle': 'View'; + readonly 'catalogTable.editActionTitle': 'Edit'; + readonly 'catalogTable.starActionTitle': 'Add to favorites'; + readonly 'catalogTable.unStarActionTitle': 'Remove from favorites'; + readonly 'dependencyOfComponentsCard.title': 'Dependency of components'; + readonly 'dependencyOfComponentsCard.emptyMessage': 'No component depends on this component.'; + readonly 'dependsOnComponentsCard.title': 'Depends on components'; + readonly 'dependsOnComponentsCard.emptyMessage': 'No component is a dependency of this component.'; + readonly 'dependsOnResourcesCard.title': 'Depends on resources'; + readonly 'dependsOnResourcesCard.emptyMessage': 'No resource is a dependency of this component.'; + readonly 'entityContextMenu.copiedMessage': 'Copied!'; + readonly 'entityContextMenu.moreButtonTitle': 'More'; + readonly 'entityContextMenu.inspectMenuTitle': 'Inspect entity'; + readonly 'entityContextMenu.copyURLMenuTitle': 'Copy entity URL'; + readonly 'entityContextMenu.unregisterMenuTitle': 'Unregister entity'; + readonly 'entityContextMenu.moreButtonAriaLabel': 'more'; + readonly 'entityLabelsCard.title': 'Labels'; + readonly 'entityLabelsCard.readMoreButtonTitle': 'Read more'; + readonly 'entityLabelsCard.columnKeyLabel': 'Label'; + readonly 'entityLabelsCard.columnValueLabel': 'Value'; + readonly 'entityLabelsCard.emptyDescription': 'No labels defined for this entity. You can add labels to your entity YAML as shown in the highlighted example below:'; + readonly 'entityLabels.ownerLabel': 'Owner'; + readonly 'entityLabels.warningPanelTitle': 'Entity not found'; + readonly 'entityLabels.lifecycleLabel': 'Lifecycle'; + readonly 'entityLinksCard.title': 'Links'; + readonly 'entityLinksCard.readMoreButtonTitle': 'Read more'; + readonly 'entityLinksCard.emptyDescription': 'No links defined for this entity. You can add links to your entity YAML as shown in the highlighted example below:'; + readonly 'entityNotFound.title': 'Entity was not found'; + readonly 'entityNotFound.description': 'Want to help us build this? Check out our Getting Started documentation.'; + readonly 'entityNotFound.docButtonTitle': 'DOCS'; + readonly 'entityTabs.tabsAriaLabel': 'Tabs'; + readonly entityProcessingErrorsDescription: 'The error below originates from'; + readonly entityRelationWarningDescription: "This entity has relations to other entities, which can't be found in the catalog.\n Entities not found are: "; + readonly 'hasComponentsCard.title': 'Has components'; + readonly 'hasComponentsCard.emptyMessage': 'No component is part of this system.'; + readonly 'hasResourcesCard.title': 'Has resources'; + readonly 'hasResourcesCard.emptyMessage': 'No resource is part of this system.'; + readonly 'hasSubcomponentsCard.title': 'Has subcomponents'; + readonly 'hasSubcomponentsCard.emptyMessage': 'No subcomponent is part of this component.'; + readonly 'hasSubdomainsCard.title': 'Has subdomains'; + readonly 'hasSubdomainsCard.emptyMessage': 'No subdomain is part of this domain.'; + readonly 'hasSystemsCard.title': 'Has systems'; + readonly 'hasSystemsCard.emptyMessage': 'No system is part of this domain.'; + readonly 'relatedEntitiesCard.emptyHelpLinkTitle': 'Learn how to change this.'; + readonly 'systemDiagramCard.title': 'System Diagram'; + readonly 'systemDiagramCard.description': 'Use pinch & zoom to move around the diagram.'; + readonly 'systemDiagramCard.edgeLabels.partOf': 'part of'; + readonly 'systemDiagramCard.edgeLabels.dependsOn': 'depends on'; + readonly 'systemDiagramCard.edgeLabels.provides': 'provides'; + } +>; + // @public (undocumented) export type ColumnBreakpoints = Record; @@ -297,44 +392,68 @@ export class DefaultStarredEntitiesApi implements StarredEntitiesApi { toggleStarred(entityRef: string): Promise; } -// @public (undocumented) -export interface DependencyOfComponentsCardProps { - // (undocumented) +// @public @deprecated +export interface DependencyOfComponentsCardLegacyProps { + // @deprecated (undocumented) columns?: TableColumn[]; - // (undocumented) + // @deprecated (undocumented) tableOptions?: TableOptions; // (undocumented) title?: string; + // @deprecated (undocumented) + variant?: InfoCardVariants; +} + +// @public (undocumented) +export interface DependencyOfComponentsCardProps { // (undocumented) + columnConfig?: EntityColumnConfig[]; + // (undocumented) + title?: string; +} + +// @public @deprecated +export interface DependsOnComponentsCardLegacyProps { + // @deprecated (undocumented) + columns?: TableColumn[]; + // @deprecated (undocumented) + tableOptions?: TableOptions; + // (undocumented) + title?: string; + // @deprecated (undocumented) variant?: InfoCardVariants; } // @public (undocumented) export interface DependsOnComponentsCardProps { // (undocumented) - columns?: TableColumn[]; + columnConfig?: EntityColumnConfig[]; // (undocumented) + title?: string; +} + +// @public @deprecated +export interface DependsOnResourcesCardLegacyProps { + // @deprecated (undocumented) + columns?: TableColumn[]; + // @deprecated (undocumented) tableOptions?: TableOptions; // (undocumented) title?: string; - // (undocumented) + // @deprecated (undocumented) variant?: InfoCardVariants; } // @public (undocumented) export interface DependsOnResourcesCardProps { // (undocumented) - columns?: TableColumn[]; - // (undocumented) - tableOptions?: TableOptions; + columnConfig?: EntityColumnConfig[]; // (undocumented) title?: string; - // (undocumented) - variant?: InfoCardVariants; } // @public -export const EntityAboutCard: (props: AboutCardProps) => JSX.Element; +export const EntityAboutCard: () => JSX.Element; // @public (undocumented) export type EntityContextMenuClassKey = 'button'; @@ -384,8 +503,6 @@ export const EntityLabelsCard: (props: EntityLabelsCardProps) => JSX_2.Element; export interface EntityLabelsCardProps { // (undocumented) title?: string; - // (undocumented) - variant?: InfoCardVariants; } // @public (undocumented) @@ -435,8 +552,6 @@ export const EntityLinksCard: (props: EntityLinksCardProps) => JSX_2.Element; export interface EntityLinksCardProps { // (undocumented) cols?: ColumnBreakpoints | number; - // (undocumented) - variant?: InfoCardVariants; } // @public (undocumented) @@ -513,18 +628,26 @@ export function hasCatalogProcessingErrors( }, ): Promise; -// @public (undocumented) -export interface HasComponentsCardProps { - // (undocumented) +// @public @deprecated +export interface HasComponentsCardLegacyProps { + // @deprecated (undocumented) columns?: TableColumn[]; - // (undocumented) + // @deprecated (undocumented) tableOptions?: TableOptions; // (undocumented) title?: string; - // (undocumented) + // @deprecated (undocumented) variant?: InfoCardVariants; } +// @public (undocumented) +export interface HasComponentsCardProps { + // (undocumented) + columnConfig?: EntityColumnConfig[]; + // (undocumented) + title?: string; +} + // @public export function hasLabels(entity: Entity): boolean; @@ -536,54 +659,88 @@ export function hasRelationWarnings( }, ): Promise; -// @public (undocumented) -export interface HasResourcesCardProps { - // (undocumented) +// @public @deprecated +export interface HasResourcesCardLegacyProps { + // @deprecated (undocumented) columns?: TableColumn[]; - // (undocumented) + // @deprecated (undocumented) tableOptions?: TableOptions; // (undocumented) title?: string; + // @deprecated (undocumented) + variant?: InfoCardVariants; +} + +// @public (undocumented) +export interface HasResourcesCardProps { // (undocumented) + columnConfig?: EntityColumnConfig[]; + // (undocumented) + title?: string; +} + +// @public @deprecated +export interface HasSubcomponentsCardLegacyProps { + // @deprecated (undocumented) + columns?: TableColumn[]; + // (undocumented) + kind?: string; + // @deprecated (undocumented) + tableOptions?: TableOptions; + // (undocumented) + title?: string; + // @deprecated (undocumented) variant?: InfoCardVariants; } // @public (undocumented) export interface HasSubcomponentsCardProps { // (undocumented) - columns?: TableColumn[]; + columnConfig?: EntityColumnConfig[]; // (undocumented) kind?: string; // (undocumented) + title?: string; +} + +// @public @deprecated +export interface HasSubdomainsCardLegacyProps { + // @deprecated (undocumented) + columns?: TableColumn[]; + // @deprecated (undocumented) tableOptions?: TableOptions; // (undocumented) title?: string; - // (undocumented) + // @deprecated (undocumented) variant?: InfoCardVariants; } // @public (undocumented) export interface HasSubdomainsCardProps { // (undocumented) - columns?: TableColumn[]; + columnConfig?: EntityColumnConfig[]; // (undocumented) + title?: string; +} + +// @public @deprecated +export interface HasSystemsCardLegacyProps { + // @deprecated (undocumented) + columns?: TableColumn[]; + // @deprecated (undocumented) tableOptions?: TableOptions; // (undocumented) title?: string; - // (undocumented) + // @deprecated (undocumented) variant?: InfoCardVariants; } // @public (undocumented) export interface HasSystemsCardProps { // (undocumented) - columns?: TableColumn[]; - // (undocumented) - tableOptions?: TableOptions; + columnConfig?: EntityColumnConfig[]; // (undocumented) title?: string; - // (undocumented) - variant?: InfoCardVariants; } // @public diff --git a/plugins/catalog/src/alpha/DefaultEntityContentLayout.tsx b/plugins/catalog/src/alpha/DefaultEntityContentLayout.tsx index d0bb4aeb8d..f18ce1b193 100644 --- a/plugins/catalog/src/alpha/DefaultEntityContentLayout.tsx +++ b/plugins/catalog/src/alpha/DefaultEntityContentLayout.tsx @@ -32,6 +32,9 @@ import { } from '../components/EntityProcessingErrorsPanel'; import { HorizontalScrollGrid } from '@backstage/core-components'; +// Module-level flag to ensure deprecation warning is only logged once +let hasLoggedSummaryWarning = false; + const useStyles = makeStyles< Theme, { infoCards: boolean; summaryCards: boolean; contentCards: boolean } @@ -137,11 +140,21 @@ export function DefaultEntityContentLayout(props: EntityContentLayoutProps) { const { cards } = props; const infoCards = cards.filter(card => card.type === 'info'); - const summaryCards = cards.filter(card => card.type === 'summary'); + // Keep support for 'summary' type at runtime for backward compatibility + // even though it's been removed from the type system + const summaryCards = cards.filter(card => card.type === ('summary' as any)); const contentCards = cards.filter( card => !card.type || card.type === 'content', ); + if (summaryCards.length > 0 && !hasLoggedSummaryWarning) { + hasLoggedSummaryWarning = true; + // eslint-disable-next-line no-console + console.warn( + "The 'summary' entity card type has been removed. Please update your cards to use 'content' or 'info' types instead.", + ); + } + const classes = useStyles({ infoCards: !!infoCards.length, summaryCards: !!summaryCards.length, diff --git a/plugins/catalog/src/alpha/components/EntityLayout/EntityLayout.tsx b/plugins/catalog/src/alpha/components/EntityLayout/EntityLayout.tsx index 8f5838d545..895f36150f 100644 --- a/plugins/catalog/src/alpha/components/EntityLayout/EntityLayout.tsx +++ b/plugins/catalog/src/alpha/components/EntityLayout/EntityLayout.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { ComponentProps, ReactNode } from 'react'; +import { ComponentProps, ReactNode, ReactElement } from 'react'; import Alert from '@material-ui/lab/Alert'; @@ -40,11 +40,13 @@ import { import { catalogTranslationRef } from '../../translation'; import { EntityHeader } from '../EntityHeader'; import { EntityTabs } from '../EntityTabs'; +import { EntityContentGroupDefinitions } from '@backstage/plugin-catalog-react/alpha'; export type EntityLayoutRouteProps = { path: string; title: string; - group: string; + group?: string; + icon?: string | ReactElement; children: JSX.Element; if?: (entity: Entity) => boolean; }; @@ -77,6 +79,9 @@ export interface EntityLayoutProps { * It adds breadcrumbs in the Entity page to enhance user navigation and context awareness. */ parentEntityRelations?: string[]; + groupDefinitions: EntityContentGroupDefinitions; + defaultContentOrder?: 'title' | 'natural'; + showNavItemIcons?: boolean; } /** @@ -102,21 +107,16 @@ export const EntityLayout = (props: EntityLayoutProps) => { UNSTABLE_contextMenuOptions, contextMenuItems, children, + header, NotFoundComponent, parentEntityRelations, + groupDefinitions, + defaultContentOrder, + showNavItemIcons, } = props; const { kind } = useRouteRefParams(entityRouteRef); const { entity, loading, error } = useAsyncEntity(); - const header = props.header ?? ( - - ); - const routes = useElementFilter( children, elements => @@ -140,6 +140,7 @@ export const EntityLayout = (props: EntityLayoutProps) => { title: elementProps.title, group: elementProps.group, children: elementProps.children, + icon: elementProps.icon, }, ]; }), @@ -150,11 +151,25 @@ export const EntityLayout = (props: EntityLayoutProps) => { return ( - {!loading && header} + {header ?? ( + + )} {loading && } - {entity && } + {entity && ( + + )} {error && ( @@ -168,11 +183,14 @@ export const EntityLayout = (props: EntityLayoutProps) => { NotFoundComponent ) : ( - There is no {kind} with the requested{' '} - - kind, namespace, and name - - . + {t('entityPage.notFoundMessage', { + kind, + link: ( + + {t('entityPage.notFoundLinkText')} + + ), + })} )} diff --git a/plugins/catalog/src/alpha/components/EntityTabs/EntityTabs.test.tsx b/plugins/catalog/src/alpha/components/EntityTabs/EntityTabs.test.tsx new file mode 100644 index 0000000000..a85d795cd8 --- /dev/null +++ b/plugins/catalog/src/alpha/components/EntityTabs/EntityTabs.test.tsx @@ -0,0 +1,297 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { screen } from '@testing-library/react'; +import { useSelectedSubRoute } from './EntityTabs'; +import { EntityTabsList } from './EntityTabsList'; +import { MemoryRouter, Route, Routes } from 'react-router-dom'; +import { render } from '@testing-library/react'; +import { renderInTestApp } from '@backstage/frontend-test-utils'; + +function TestSubRouteHook(props: { + subRoutes: Array<{ + group: string; + path: string; + title: string; + children: JSX.Element; + }>; +}) { + const { index, route, element } = useSelectedSubRoute(props.subRoutes); + return ( +
    +
    {index}
    +
    {route?.title}
    +
    {element}
    +
    + ); +} + +describe('EntityTabsList', () => { + it('should render groups in the order defined by groupDefinitions', () => { + const tabs = [ + { id: '/cicd', label: 'CI/CD', path: 'cicd', group: 'cicd' }, + { + id: '/overview', + label: 'Overview', + path: 'overview', + group: 'overview', + }, + { + id: '/techdocs', + label: 'TechDocs', + path: 'techdocs', + group: 'techdocs', + }, + ]; + + const groupDefinitions = { + overview: { title: 'Overview' }, + techdocs: { title: 'TechDocs' }, + cicd: { title: 'CI/CD' }, + }; + + renderInTestApp( + , + ); + + const tabElements = screen.getAllByRole('tab'); + expect(tabElements).toHaveLength(3); + expect(tabElements[0]).toHaveTextContent('Overview'); + expect(tabElements[1]).toHaveTextContent('TechDocs'); + expect(tabElements[2]).toHaveTextContent('CI/CD'); + }); + + it('should place ungrouped tabs after defined groups', () => { + const tabs = [ + { id: '/standalone', label: 'Standalone', path: 'standalone' }, + { + id: '/overview', + label: 'Overview', + path: 'overview', + group: 'overview', + }, + { + id: '/techdocs', + label: 'TechDocs', + path: 'techdocs', + group: 'techdocs', + }, + ]; + + const groupDefinitions = { + overview: { title: 'Overview' }, + techdocs: { title: 'TechDocs' }, + }; + + renderInTestApp( + , + ); + + const tabElements = screen.getAllByRole('tab'); + expect(tabElements).toHaveLength(3); + expect(tabElements[0]).toHaveTextContent('Overview'); + expect(tabElements[1]).toHaveTextContent('TechDocs'); + expect(tabElements[2]).toHaveTextContent('Standalone'); + }); +}); + +describe('EntityTabs', () => { + const subRoutes = [ + { + group: 'default', + path: '/overview', + title: 'Overview', + children:
    Overview Content
    , + }, + { + group: 'default', + path: '/details', + title: 'Details', + children:
    Details Content
    , + }, + { + group: 'docs', + path: '/docs', + title: 'Documentation', + children:
    Documentation Content
    , + }, + ]; + + describe('useSelectedSubRoute', () => { + it('should render the first route at root path', () => { + render( + + + } + /> + + , + ); + + expect(screen.getByTestId('selected-index')).toHaveTextContent('0'); + expect(screen.getByTestId('selected-route-title')).toHaveTextContent( + 'Overview', + ); + }); + + it('should render a route at non-root path', () => { + render( + + + } + /> + + , + ); + + expect(screen.getByTestId('selected-index')).toHaveTextContent('1'); + expect(screen.getByTestId('selected-route-title')).toHaveTextContent( + 'Details', + ); + }); + + it('should handle nested paths under a route (splat path behavior)', () => { + render( + + + } + /> + + , + ); + + expect(screen.getByTestId('selected-index')).toHaveTextContent('1'); + expect(screen.getByTestId('selected-route-title')).toHaveTextContent( + 'Details', + ); + }); + + it('should render correct content for matched route', () => { + render( + + + } + /> + + , + ); + + expect(screen.getByTestId('element-container')).toHaveTextContent( + 'Documentation Content', + ); + }); + + it('should support relative links within routes', () => { + const routesWithRelativeLinks = [ + { + group: 'default', + path: '/entity', + title: 'Entity', + children: ( +
    + Entity Content + Go to child +
    + ), + }, + ]; + + render( + + + } + /> + + , + ); + + expect(screen.getByText('Entity Content')).toBeInTheDocument(); + expect(screen.getByText('Go to child')).toHaveAttribute( + 'href', + './child', + ); + }); + + it('should handle routes with nested path segments', () => { + const nestedPathRoutes = [ + { + group: 'default', + path: '/catalog/entities', + title: 'Entities', + children:
    Entities Content
    , + }, + { + group: 'default', + path: '/catalog', + title: 'Catalog', + children:
    Catalog Content
    , + }, + ]; + + render( + + + } + /> + + , + ); + + expect(screen.getByTestId('selected-index')).toHaveTextContent('0'); + expect(screen.getByTestId('selected-route-title')).toHaveTextContent( + 'Entities', + ); + }); + + it('should fall back to first route for unknown paths', () => { + render( + + + } + /> + + , + ); + + expect(screen.getByTestId('selected-index')).toHaveTextContent('0'); + expect(screen.getByTestId('selected-route-title')).toHaveTextContent( + 'Overview', + ); + }); + }); +}); diff --git a/plugins/catalog/src/alpha/components/EntityTabs/EntityTabs.tsx b/plugins/catalog/src/alpha/components/EntityTabs/EntityTabs.tsx index e7e417eace..102df76042 100644 --- a/plugins/catalog/src/alpha/components/EntityTabs/EntityTabs.tsx +++ b/plugins/catalog/src/alpha/components/EntityTabs/EntityTabs.tsx @@ -13,16 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { useMemo } from 'react'; +import { ReactElement, useMemo } from 'react'; import { Helmet } from 'react-helmet'; import { matchRoutes, useParams, useRoutes } from 'react-router-dom'; import { EntityTabsPanel } from './EntityTabsPanel'; import { EntityTabsList } from './EntityTabsList'; +import { EntityContentGroupDefinitions } from '@backstage/plugin-catalog-react/alpha'; type SubRoute = { - group: string; + group?: string; path: string; title: string; + icon?: string | ReactElement; children: JSX.Element; }; @@ -69,17 +71,20 @@ export function useSelectedSubRoute(subRoutes: SubRoute[]): { type EntityTabsProps = { routes: SubRoute[]; + groupDefinitions: EntityContentGroupDefinitions; + defaultContentOrder?: 'title' | 'natural'; + showIcons?: boolean; }; export function EntityTabs(props: EntityTabsProps) { - const { routes } = props; + const { routes, groupDefinitions, defaultContentOrder, showIcons } = props; const { index, route, element } = useSelectedSubRoute(routes); const tabs = useMemo( () => routes.map(t => { - const { path, title, group } = t; + const { path, title, group, icon } = t; let to = path; // Remove trailing /* to = to.replace(/\/\*$/, ''); @@ -90,6 +95,7 @@ export function EntityTabs(props: EntityTabsProps) { id: path, path: to, label: title, + icon, }; }), [routes], @@ -97,7 +103,13 @@ export function EntityTabs(props: EntityTabsProps) { return ( <> - + {element} diff --git a/plugins/catalog/src/alpha/components/EntityTabs/EntityTabsGroup.tsx b/plugins/catalog/src/alpha/components/EntityTabs/EntityTabsGroup.tsx index 564b83bd25..c21b3ef242 100644 --- a/plugins/catalog/src/alpha/components/EntityTabs/EntityTabsGroup.tsx +++ b/plugins/catalog/src/alpha/components/EntityTabs/EntityTabsGroup.tsx @@ -19,17 +19,24 @@ import { useState, MouseEvent, MouseEventHandler, + ReactElement, } from 'react'; import { Link } from 'react-router-dom'; import classnames from 'classnames'; import Typography from '@material-ui/core/Typography'; -import ButtonBase from '@material-ui/core/ButtonBase'; import Popover from '@material-ui/core/Popover'; import { TabProps, TabClassKey } from '@material-ui/core/Tab'; import { capitalize } from '@material-ui/core/utils'; import { createStyles, Theme, withStyles } from '@material-ui/core/styles'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import Button from '@material-ui/core/Button'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemIcon from '@material-ui/core/ListItemIcon'; +import ListItemText from '@material-ui/core/ListItemText'; +import List from '@material-ui/core/List'; +import { useApi } from '@backstage/core-plugin-api'; +import { IconsApi, iconsApiRef } from '@backstage/frontend-plugin-api'; const styles = (theme: Theme) => createStyles({ @@ -53,9 +60,6 @@ const styles = (theme: Theme) => minWidth: 160, }, }, - popInButton: { - width: '100%', - }, defaultTab: { ...theme.typography.caption, padding: theme.spacing(3, 3), @@ -138,22 +142,41 @@ const styles = (theme: Theme) => type EntityTabsGroupItem = { id: string; - index: number; label: string; path: string; - group: string; + icon?: string | ReactElement; }; type EntityTabsGroupProps = TabProps & { classes?: Partial>; indicator?: ReactNode; - highlightedButton?: number; + highlightedButton?: string; items: EntityTabsGroupItem[]; - onSelectTab: MouseEventHandler; + onSelectTab?: MouseEventHandler; + showIcons?: boolean; }; +function resolveIcon( + icon: string | ReactElement | undefined, + iconsApi: IconsApi, + showIcons: boolean, +) { + if (!showIcons) { + return undefined; + } + if (typeof icon === 'string') { + const Icon = iconsApi.getIcon(icon); + if (Icon) { + return ; + } + return undefined; + } + return icon; +} + const Tab = forwardRef(function Tab(props: EntityTabsGroupProps, ref: any) { const [anchorEl, setAnchorEl] = useState(null); + const iconsApi = useApi(iconsApiRef); const open = Boolean(anchorEl); const submenuId = open ? 'tabbed-submenu' : undefined; @@ -165,7 +188,6 @@ const Tab = forwardRef(function Tab(props: EntityTabsGroupProps, ref: any) { disableFocusRipple = false, items, fullWidth, - icon, indicator, label, onSelectTab, @@ -173,8 +195,10 @@ const Tab = forwardRef(function Tab(props: EntityTabsGroupProps, ref: any) { textColor = 'inherit', wrapped = false, highlightedButton, + showIcons = false, } = props; + const groupIcon = resolveIcon(props.icon, iconsApi, showIcons); const testId = 'data-testid' in props && props['data-testid']; const handleMenuClose = () => { @@ -191,31 +215,24 @@ const Tab = forwardRef(function Tab(props: EntityTabsGroupProps, ref: any) { classes && { [classes.disabled!]: disabled, [classes.selected!]: selected, - [classes.labelIcon!]: label && icon, + [classes.labelIcon!]: label && groupIcon, [classes.fullWidth!]: fullWidth, [classes.wrapped!]: wrapped, }, className, ]; - const innerButtonClasses = [ - classes?.root, - classes?.[`textColor${capitalize(textColor)}` as TabClassKey], - classes?.defaultTab, - classes && { - [classes.disabled!]: disabled, - [classes.labelIcon!]: label && icon, - [classes.fullWidth!]: fullWidth, - [classes.wrapped!]: wrapped, - }, - ]; - if (items.length === 1) { return ( - - {icon} {items[0].label} {indicator} - + ); } + const hasIcons = showIcons && items.some(i => i.icon); return ( <> - {label} - + - {items.map((i, idx) => ( -
    - { - handleMenuClose(); - onSelectTab(e); - }} - to={i.path} - > - - {icon} - {i.label} - - {indicator} - -
    - ))} + + {items.map(i => { + const itemIcon = resolveIcon(i.icon, iconsApi, showIcons); + return ( + { + handleMenuClose(); + onSelectTab?.(e); + }} + to={i.path} + > + {itemIcon && {itemIcon}} + + {i.label} + {indicator} + + } + /> + + ); + })} +
    ); diff --git a/plugins/catalog/src/alpha/components/EntityTabs/EntityTabsList.tsx b/plugins/catalog/src/alpha/components/EntityTabs/EntityTabsList.tsx index c5475ed710..9abbfb69d9 100644 --- a/plugins/catalog/src/alpha/components/EntityTabs/EntityTabsList.tsx +++ b/plugins/catalog/src/alpha/components/EntityTabs/EntityTabsList.tsx @@ -14,11 +14,15 @@ * limitations under the License. */ -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { ReactElement, useMemo } from 'react'; import Box from '@material-ui/core/Box'; import Tabs from '@material-ui/core/Tabs'; import { makeStyles } from '@material-ui/core/styles'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { EntityContentGroupDefinitions } from '@backstage/plugin-catalog-react/alpha'; + import { EntityTabsGroup } from './EntityTabsGroup'; +import { catalogTranslationRef } from '../../translation'; /** @public */ export type HeaderTabsClassKey = @@ -59,51 +63,119 @@ type Tab = { id: string; label: string; path: string; - group: string; + group?: string; + icon?: string | ReactElement; }; -type TabItem = { - group: string; - id: string; - index: number; - label: string; - path: string; +type TabGroup = { + group?: { + title: string; + icon?: string | ReactElement; + }; + items: Array>; }; type EntityTabsListProps = { tabs: Tab[]; + groupDefinitions: EntityContentGroupDefinitions; + defaultContentOrder?: 'title' | 'natural'; + showIcons?: boolean; selectedIndex?: number; - onChange?: (index: number) => void; }; +function resolveGroupId( + tabGroup: string | undefined, + groupDefinitions: EntityContentGroupDefinitions, + aliasToGroup: Record, +): string | undefined { + if (!tabGroup) { + return undefined; + } + if (groupDefinitions[tabGroup]) { + return tabGroup; + } + return aliasToGroup[tabGroup]; +} + export function EntityTabsList(props: EntityTabsListProps) { const styles = useStyles(); + const { t } = useTranslationRef(catalogTranslationRef); - const { tabs: items, onChange, selectedIndex: selectedItem = 0 } = props; + const { + tabs: items, + selectedIndex = 0, + showIcons, + groupDefinitions, + defaultContentOrder = 'title', + } = props; - const groups = useMemo( - () => [...new Set(items.map(item => item.group))], - [items], + const aliasToGroup = useMemo( + () => + Object.entries(groupDefinitions).reduce((map, [groupId, def]) => { + for (const alias of def.aliases ?? []) { + map[alias] = groupId; + } + return map; + }, {} as Record), + [groupDefinitions], ); - const [selectedGroup, setSelectedGroup] = useState( - selectedItem && items[selectedItem] - ? groups.indexOf(items[selectedItem].group) - : 0, + const groups = useMemo(() => { + const byKey = items.reduce((result, tab) => { + const resolvedGroupId = resolveGroupId( + tab.group, + groupDefinitions, + aliasToGroup, + ); + const group = resolvedGroupId + ? groupDefinitions[resolvedGroupId] + : undefined; + const groupOrId = group && resolvedGroupId ? resolvedGroupId : tab.id; + result[groupOrId] = result[groupOrId] ?? { + group, + items: [], + }; + result[groupOrId].items.push(tab); + return result; + }, {} as Record); + + const groupOrder = Object.keys(groupDefinitions); + const sorted = Object.entries(byKey).sort(([a], [b]) => { + const ai = groupOrder.indexOf(a); + const bi = groupOrder.indexOf(b); + if (ai !== -1 && bi !== -1) { + return ai - bi; + } + if (ai !== -1) { + return -1; + } + if (bi !== -1) { + return 1; + } + return 0; + }); + + for (const [id, tabGroup] of sorted) { + const groupDef = groupDefinitions[id]; + if (groupDef) { + const order = groupDef.contentOrder ?? defaultContentOrder; + if (order === 'title') { + tabGroup.items.sort((a, b) => + a.label.localeCompare(b.label, undefined, { sensitivity: 'base' }), + ); + } + } + } + + return sorted; + }, [items, groupDefinitions, aliasToGroup, defaultContentOrder]); + + const selectedItem = items[selectedIndex]; + const selectedGroup = resolveGroupId( + selectedItem?.group, + groupDefinitions, + aliasToGroup, ); - - const handleChange = useCallback( - (index: number) => { - if (selectedItem !== index) onChange?.(index); - }, - [selectedItem, onChange], - ); - - useEffect(() => { - if (selectedItem === undefined || !items[selectedItem]) return; - setSelectedGroup(groups.indexOf(items[selectedItem].group)); - }, [items, selectedItem, groups, setSelectedGroup]); - return ( - {groups.map((group, groupIndex) => { - const groupItems: TabItem[] = []; - items.forEach((item, itemIndex) => { - if (item.group === group) { - groupItems.push({ - ...item, - index: itemIndex, - }); - } - }); - return ( - handleChange(groupIndex)} - /> - ); - })} + {groups.map(([id, tabGroup]) => ( + + ))} ); diff --git a/plugins/catalog/src/alpha/contextMenuItems.tsx b/plugins/catalog/src/alpha/contextMenuItems.tsx index 4173a4d074..24dfe037ab 100644 --- a/plugins/catalog/src/alpha/contextMenuItems.tsx +++ b/plugins/catalog/src/alpha/contextMenuItems.tsx @@ -26,7 +26,6 @@ import { alertApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api'; import { dialogApiRef, useTranslationRef, - type DialogApiDialog, } from '@backstage/frontend-plugin-api'; import { catalogTranslationRef } from './translation'; import { useNavigate, useSearchParams } from 'react-router-dom'; @@ -37,7 +36,6 @@ import { import { rootRouteRef, unregisterRedirectRouteRef } from '../routes'; import { catalogEntityDeletePermission } from '@backstage/plugin-catalog-common/alpha'; import { useEffect } from 'react'; -import { compatWrapper } from '@backstage/core-compat-api'; export const copyEntityUrlContextMenuItem = EntityContextMenuItemBlueprint.make( { @@ -111,23 +109,21 @@ export const unregisterEntityContextMenuItem = title: t('entityContextMenu.unregisterMenuTitle'), disabled: !unregisterPermission.allowed, onClick: async () => { - dialogApi.showModal(({ dialog }: { dialog: DialogApiDialog }) => - compatWrapper( - dialog.close()} - onConfirm={() => { - dialog.close(); - navigate( - unregisterRedirectRoute - ? unregisterRedirectRoute() - : catalogRoute(), - ); - }} - />, - ), - ); + dialogApi.open(({ dialog }) => ( + dialog.close()} + onConfirm={() => { + dialog.close(); + navigate( + unregisterRedirectRoute + ? unregisterRedirectRoute() + : catalogRoute(), + ); + }} + /> + )); }, }; }, diff --git a/plugins/catalog/src/alpha/entityCards.test.tsx b/plugins/catalog/src/alpha/entityCards.test.tsx new file mode 100644 index 0000000000..37fb1af5c9 --- /dev/null +++ b/plugins/catalog/src/alpha/entityCards.test.tsx @@ -0,0 +1,113 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { screen } from '@testing-library/react'; +import { renderTestApp } from '@backstage/frontend-test-utils'; +import { Entity } from '@backstage/catalog-model'; +import { createTestEntityPage } from '@backstage/plugin-catalog-react/testUtils'; +import { catalogLinksEntityCard, catalogLabelsEntityCard } from './entityCards'; + +describe('catalog entity cards', () => { + describe('catalogLinksEntityCard', () => { + it('should render for entities with links', async () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test', + links: [{ url: 'https://example.com', title: 'Example' }], + }, + spec: { type: 'service' }, + }; + + renderTestApp({ + extensions: [createTestEntityPage({ entity }), catalogLinksEntityCard], + }); + + expect(await screen.findByText('Links')).toBeInTheDocument(); + expect(await screen.findByText('Example')).toBeInTheDocument(); + }); + + it('should not render for entities without links', async () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test', + labels: { team: 'platform' }, + }, + spec: { type: 'service' }, + }; + + renderTestApp({ + extensions: [ + createTestEntityPage({ entity }), + catalogLinksEntityCard, + catalogLabelsEntityCard, + ], + }); + + // Labels card renders as sentinel + expect(await screen.findByText('Labels')).toBeInTheDocument(); + expect(screen.queryByText('Links')).not.toBeInTheDocument(); + }); + }); + + describe('catalogLabelsEntityCard', () => { + it('should render for entities with labels', async () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test', + labels: { team: 'platform' }, + }, + spec: { type: 'service' }, + }; + + renderTestApp({ + extensions: [createTestEntityPage({ entity }), catalogLabelsEntityCard], + }); + + expect(await screen.findByText('Labels')).toBeInTheDocument(); + expect(await screen.findByText('team')).toBeInTheDocument(); + }); + + it('should not render for entities without labels', async () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test', + links: [{ url: 'https://example.com', title: 'Example' }], + }, + spec: { type: 'service' }, + }; + + renderTestApp({ + extensions: [ + createTestEntityPage({ entity }), + catalogLinksEntityCard, + catalogLabelsEntityCard, + ], + }); + + // Links card renders as sentinel + expect(await screen.findByText('Links')).toBeInTheDocument(); + expect(screen.queryByText('Labels')).not.toBeInTheDocument(); + }); + }); +}); diff --git a/plugins/catalog/src/alpha/entityCards.tsx b/plugins/catalog/src/alpha/entityCards.tsx index f99c5961a1..bd48eebaaf 100644 --- a/plugins/catalog/src/alpha/entityCards.tsx +++ b/plugins/catalog/src/alpha/entityCards.tsx @@ -18,7 +18,6 @@ import { EntityIconLinkBlueprint, EntityCardBlueprint, } from '@backstage/plugin-catalog-react/alpha'; -import { compatWrapper } from '@backstage/core-compat-api'; import { createExtensionInput } from '@backstage/frontend-plugin-api'; import { HeaderIconLinkRow, @@ -42,12 +41,14 @@ export const catalogAboutEntityCard = EntityCardBlueprint.makeWithOverrides({ // The "useProps" functions may be calling other hooks, so we need to // call them in a component function to avoid breaking the rules of hooks. const links = inputs.iconLinks.reduce((rest, iconLink) => { - const props = iconLink.get(EntityIconLinkBlueprint.dataRefs.useProps)(); const filter = buildFilterFn( iconLink.get(EntityIconLinkBlueprint.dataRefs.filterFunction), iconLink.get(EntityIconLinkBlueprint.dataRefs.filterExpression), ); if (filter(entity)) { + const props = iconLink.get( + EntityIconLinkBlueprint.dataRefs.useProps, + )(); return [...rest, props]; } return rest; @@ -65,9 +66,7 @@ export const catalogAboutEntityCard = EntityCardBlueprint.makeWithOverrides({ const { InternalAboutCard } = await import( '../components/AboutCard/AboutCard' ); - return compatWrapper( - } />, - ); + return } />; }, }); }, @@ -79,9 +78,7 @@ export const catalogLinksEntityCard = EntityCardBlueprint.make({ type: 'info', filter: { 'metadata.links': { $exists: true } }, loader: async () => - import('../components/EntityLinksCard').then(m => - compatWrapper(), - ), + import('../components/EntityLinksCard').then(m => ), }, }); @@ -91,9 +88,9 @@ export const catalogLabelsEntityCard = EntityCardBlueprint.make({ type: 'info', filter: { 'metadata.labels': { $exists: true } }, loader: async () => - import('../components/EntityLabelsCard').then(m => - compatWrapper(), - ), + import('../components/EntityLabelsCard').then(m => ( + + )), }, }); @@ -102,9 +99,9 @@ export const catalogDependsOnComponentsEntityCard = EntityCardBlueprint.make({ params: { filter: { kind: 'component' }, loader: async () => - import('../components/DependsOnComponentsCard').then(m => - compatWrapper(), - ), + import('../components/DependsOnComponentsCard').then(m => ( + + )), }, }); @@ -113,9 +110,9 @@ export const catalogDependsOnResourcesEntityCard = EntityCardBlueprint.make({ params: { filter: { kind: 'component' }, loader: async () => - import('../components/DependsOnResourcesCard').then(m => - compatWrapper(), - ), + import('../components/DependsOnResourcesCard').then(m => ( + + )), }, }); @@ -124,9 +121,9 @@ export const catalogHasComponentsEntityCard = EntityCardBlueprint.make({ params: { filter: { kind: 'system' }, loader: async () => - import('../components/HasComponentsCard').then(m => - compatWrapper(), - ), + import('../components/HasComponentsCard').then(m => ( + + )), }, }); @@ -135,9 +132,9 @@ export const catalogHasResourcesEntityCard = EntityCardBlueprint.make({ params: { filter: { kind: 'system' }, loader: async () => - import('../components/HasResourcesCard').then(m => - compatWrapper(), - ), + import('../components/HasResourcesCard').then(m => ( + + )), }, }); @@ -146,9 +143,9 @@ export const catalogHasSubcomponentsEntityCard = EntityCardBlueprint.make({ params: { filter: { kind: 'component' }, loader: async () => - import('../components/HasSubcomponentsCard').then(m => - compatWrapper(), - ), + import('../components/HasSubcomponentsCard').then(m => ( + + )), }, }); @@ -157,9 +154,9 @@ export const catalogHasSubdomainsEntityCard = EntityCardBlueprint.make({ params: { filter: { kind: 'domain' }, loader: async () => - import('../components/HasSubdomainsCard').then(m => - compatWrapper(), - ), + import('../components/HasSubdomainsCard').then(m => ( + + )), }, }); @@ -168,9 +165,7 @@ export const catalogHasSystemsEntityCard = EntityCardBlueprint.make({ params: { filter: { kind: 'domain' }, loader: async () => - import('../components/HasSystemsCard').then(m => - compatWrapper(), - ), + import('../components/HasSystemsCard').then(m => ), }, }); diff --git a/plugins/catalog/src/alpha/filters.tsx b/plugins/catalog/src/alpha/filters.tsx index d72e299fbb..32fef3dc29 100644 --- a/plugins/catalog/src/alpha/filters.tsx +++ b/plugins/catalog/src/alpha/filters.tsx @@ -15,6 +15,7 @@ */ import { CatalogFilterBlueprint } from '@backstage/plugin-catalog-react/alpha'; +import { z } from 'zod/v4'; const catalogTagCatalogFilter = CatalogFilterBlueprint.make({ name: 'tag', @@ -30,10 +31,8 @@ const catalogTagCatalogFilter = CatalogFilterBlueprint.make({ const catalogKindCatalogFilter = CatalogFilterBlueprint.makeWithOverrides({ name: 'kind', - config: { - schema: { - initialFilter: z => z.string().default('component'), - }, + configSchema: { + initialFilter: z.string().default('component'), }, factory(originalFactory, { config }) { return originalFactory({ @@ -61,10 +60,8 @@ const catalogTypeCatalogFilter = CatalogFilterBlueprint.make({ const catalogModeCatalogFilter = CatalogFilterBlueprint.makeWithOverrides({ name: 'mode', - config: { - schema: { - mode: z => z.enum(['owners-only', 'all']).optional(), - }, + configSchema: { + mode: z.enum(['owners-only', 'all']).optional(), }, factory(originalFactory, { config }) { return originalFactory({ @@ -116,10 +113,8 @@ const catalogProcessingStatusCatalogFilter = CatalogFilterBlueprint.make({ const catalogListCatalogFilter = CatalogFilterBlueprint.makeWithOverrides({ name: 'list', - config: { - schema: { - initialFilter: z => z.enum(['owned', 'starred', 'all']).default('owned'), - }, + configSchema: { + initialFilter: z.enum(['owned', 'starred', 'all']).default('owned'), }, factory(originalFactory, { config }) { return originalFactory({ diff --git a/plugins/catalog/src/alpha/index.ts b/plugins/catalog/src/alpha/index.ts index c9f4c8c4b7..a99f7c4ad4 100644 --- a/plugins/catalog/src/alpha/index.ts +++ b/plugins/catalog/src/alpha/index.ts @@ -16,4 +16,17 @@ export { default } from './plugin'; -export * from './translation'; +export { NfsDefaultCatalogPage as CatalogIndexPage } from '../components/CatalogPage'; +export type { DefaultCatalogPageProps as CatalogIndexPageProps } from '../components/CatalogPage'; +export type { + CatalogTableRow, + CatalogTableColumnsFunc, +} from '../components/CatalogTable'; + +import { catalogTranslationRef as _catalogTranslationRef } from './translation'; + +/** + * @alpha + * @deprecated Import from `@backstage/plugin-catalog` instead. + */ +export const catalogTranslationRef = _catalogTranslationRef; diff --git a/plugins/catalog/src/alpha/navItems.tsx b/plugins/catalog/src/alpha/navItems.tsx index 0eb4e6827e..e72c20ebf4 100644 --- a/plugins/catalog/src/alpha/navItems.tsx +++ b/plugins/catalog/src/alpha/navItems.tsx @@ -15,13 +15,12 @@ */ import HomeIcon from '@material-ui/icons/Home'; -import { convertLegacyRouteRef } from '@backstage/core-compat-api'; import { NavItemBlueprint } from '@backstage/frontend-plugin-api'; import { rootRouteRef } from '../routes'; export const catalogNavItem = NavItemBlueprint.make({ params: { - routeRef: convertLegacyRouteRef(rootRouteRef), + routeRef: rootRouteRef, title: 'Catalog', icon: HomeIcon, }, diff --git a/plugins/catalog/src/alpha/pages.test.tsx b/plugins/catalog/src/alpha/pages.test.tsx index cc83b9168b..a19c9017c0 100644 --- a/plugins/catalog/src/alpha/pages.test.tsx +++ b/plugins/catalog/src/alpha/pages.test.tsx @@ -19,7 +19,6 @@ import userEvent from '@testing-library/user-event'; import { createExtensionTester, renderInTestApp, - TestApiProvider, } from '@backstage/frontend-test-utils'; import { catalogEntityPage } from './pages'; import { @@ -150,29 +149,23 @@ describe('Entity page', () => { .add(techdocsEntityContent) .add(apidocsEntityContent); - await renderInTestApp( - - {tester.reactElement()} - , - { - config: { - app: { - title: 'Custom app', - }, - backend: { baseUrl: 'http://localhost:7000' }, - }, - mountedRoutes: { - '/catalog': convertLegacyRouteRef(rootRouteRef), - '/catalog/:namespace/:kind/:name': - convertLegacyRouteRef(entityRouteRef), + await renderInTestApp(tester.reactElement(), { + apis: [ + [catalogApiRef, mockCatalogApi], + [starredEntitiesApiRef, mockStarredEntitiesApi], + ], + config: { + app: { + title: 'Custom app', }, + backend: { baseUrl: 'http://localhost:7000' }, }, - ); + mountedRoutes: { + '/catalog': convertLegacyRouteRef(rootRouteRef), + '/catalog/:namespace/:kind/:name': + convertLegacyRouteRef(entityRouteRef), + }, + }); await waitFor(() => expect( @@ -212,29 +205,23 @@ describe('Entity page', () => { .add(techdocsEntityContent) .add(apidocsEntityContent); - await renderInTestApp( - - {tester.reactElement()} - , - { - config: { - app: { - title: 'Custom app', - }, - backend: { baseUrl: 'http://localhost:7000' }, - }, - mountedRoutes: { - '/catalog': convertLegacyRouteRef(rootRouteRef), - '/catalog/:namespace/:kind/:name': - convertLegacyRouteRef(entityRouteRef), + await renderInTestApp(tester.reactElement(), { + apis: [ + [catalogApiRef, mockCatalogApi], + [starredEntitiesApiRef, mockStarredEntitiesApi], + ], + config: { + app: { + title: 'Custom app', }, + backend: { baseUrl: 'http://localhost:7000' }, }, - ); + mountedRoutes: { + '/catalog': convertLegacyRouteRef(rootRouteRef), + '/catalog/:namespace/:kind/:name': + convertLegacyRouteRef(entityRouteRef), + }, + }); await waitFor(() => expect(screen.queryByRole('tab', { name: /Docs/ })).toBeInTheDocument(), @@ -267,29 +254,23 @@ describe('Entity page', () => { }, }); - await renderInTestApp( - - {tester.reactElement()} - , - { - config: { - app: { - title: 'Custom app', - }, - backend: { baseUrl: 'http://localhost:7000' }, - }, - mountedRoutes: { - '/catalog': convertLegacyRouteRef(rootRouteRef), - '/catalog/:namespace/:kind/:name': - convertLegacyRouteRef(entityRouteRef), + await renderInTestApp(tester.reactElement(), { + apis: [ + [catalogApiRef, mockCatalogApi], + [starredEntitiesApiRef, mockStarredEntitiesApi], + ], + config: { + app: { + title: 'Custom app', }, + backend: { baseUrl: 'http://localhost:7000' }, }, - ); + mountedRoutes: { + '/catalog': convertLegacyRouteRef(rootRouteRef), + '/catalog/:namespace/:kind/:name': + convertLegacyRouteRef(entityRouteRef), + }, + }); await waitFor(() => expect( @@ -334,29 +315,23 @@ describe('Entity page', () => { }, }); - await renderInTestApp( - - {tester.reactElement()} - , - { - config: { - app: { - title: 'Custom app', - }, - backend: { baseUrl: 'http://localhost:7000' }, - }, - mountedRoutes: { - '/catalog': convertLegacyRouteRef(rootRouteRef), - '/catalog/:namespace/:kind/:name': - convertLegacyRouteRef(entityRouteRef), + await renderInTestApp(tester.reactElement(), { + apis: [ + [catalogApiRef, mockCatalogApi], + [starredEntitiesApiRef, mockStarredEntitiesApi], + ], + config: { + app: { + title: 'Custom app', }, + backend: { baseUrl: 'http://localhost:7000' }, }, - ); + mountedRoutes: { + '/catalog': convertLegacyRouteRef(rootRouteRef), + '/catalog/:namespace/:kind/:name': + convertLegacyRouteRef(entityRouteRef), + }, + }); await waitFor(() => expect(screen.getByRole('tab', { name: /Docs/ })).toBeInTheDocument(), @@ -390,29 +365,23 @@ describe('Entity page', () => { }, }); - await renderInTestApp( - - {tester.reactElement()} - , - { - config: { - app: { - title: 'Custom app', - }, - backend: { baseUrl: 'http://localhost:7000' }, - }, - mountedRoutes: { - '/catalog': convertLegacyRouteRef(rootRouteRef), - '/catalog/:namespace/:kind/:name': - convertLegacyRouteRef(entityRouteRef), + await renderInTestApp(tester.reactElement(), { + apis: [ + [catalogApiRef, mockCatalogApi], + [starredEntitiesApiRef, mockStarredEntitiesApi], + ], + config: { + app: { + title: 'Custom app', }, + backend: { baseUrl: 'http://localhost:7000' }, }, - ); + mountedRoutes: { + '/catalog': convertLegacyRouteRef(rootRouteRef), + '/catalog/:namespace/:kind/:name': + convertLegacyRouteRef(entityRouteRef), + }, + }); await waitFor(() => expect( @@ -435,29 +404,23 @@ describe('Entity page', () => { .add(apidocsEntityContent) .add(overviewEntityContent); - await renderInTestApp( - - {tester.reactElement()} - , - { - config: { - app: { - title: 'Custom app', - }, - backend: { baseUrl: 'http://localhost:7000' }, - }, - mountedRoutes: { - '/catalog': convertLegacyRouteRef(rootRouteRef), - '/catalog/:namespace/:kind/:name': - convertLegacyRouteRef(entityRouteRef), + await renderInTestApp(tester.reactElement(), { + apis: [ + [catalogApiRef, mockCatalogApi], + [starredEntitiesApiRef, mockStarredEntitiesApi], + ], + config: { + app: { + title: 'Custom app', }, + backend: { baseUrl: 'http://localhost:7000' }, }, - ); + mountedRoutes: { + '/catalog': convertLegacyRouteRef(rootRouteRef), + '/catalog/:namespace/:kind/:name': + convertLegacyRouteRef(entityRouteRef), + }, + }); await waitFor(() => expect(screen.getAllByRole('tab')).toHaveLength(2)); @@ -465,6 +428,186 @@ describe('Entity page', () => { expect(screen.getAllByRole('tab')[1]).toHaveTextContent('Overview'); }); + it('Should resolve group aliases', async () => { + const tester = createExtensionTester( + Object.assign({ namespace: 'catalog' }, catalogEntityPage), + { + config: { + groups: [ + { + docs: { title: 'Docs', aliases: ['documentation'] }, + }, + ], + }, + }, + ) + .add(techdocsEntityContent) + .add(apidocsEntityContent); + + await renderInTestApp(tester.reactElement(), { + apis: [ + [catalogApiRef, mockCatalogApi], + [starredEntitiesApiRef, mockStarredEntitiesApi], + ], + config: { + app: { + title: 'Custom app', + }, + backend: { baseUrl: 'http://localhost:7000' }, + }, + mountedRoutes: { + '/catalog': convertLegacyRouteRef(rootRouteRef), + '/catalog/:namespace/:kind/:name': + convertLegacyRouteRef(entityRouteRef), + }, + }); + + await waitFor(() => + expect(screen.getByRole('tab', { name: /Docs/ })).toBeInTheDocument(), + ); + + await userEvent.click(screen.getByRole('tab', { name: /Docs/ })); + + await waitFor(() => + expect( + screen.getByRole('button', { name: /TechDocs/ }), + ).toHaveAttribute('href', '/techdocs'), + ); + + await waitFor(() => + expect(screen.getByRole('button', { name: /ApiDocs/ })).toHaveAttribute( + 'href', + '/apidocs', + ), + ); + }); + + it('Should sort content by title by default', async () => { + const tester = createExtensionTester( + Object.assign({ namespace: 'catalog' }, catalogEntityPage), + ) + .add(techdocsEntityContent) + .add(apidocsEntityContent); + + await renderInTestApp(tester.reactElement(), { + apis: [ + [catalogApiRef, mockCatalogApi], + [starredEntitiesApiRef, mockStarredEntitiesApi], + ], + config: { + app: { + title: 'Custom app', + }, + backend: { baseUrl: 'http://localhost:7000' }, + }, + mountedRoutes: { + '/catalog': convertLegacyRouteRef(rootRouteRef), + '/catalog/:namespace/:kind/:name': + convertLegacyRouteRef(entityRouteRef), + }, + }); + + await userEvent.click( + await screen.findByRole('tab', { name: /Documentation/ }), + ); + + const buttons = await screen.findAllByRole('button', { + name: /Docs/, + }); + expect(buttons[0]).toHaveTextContent('ApiDocs'); + expect(buttons[1]).toHaveTextContent('TechDocs'); + }); + + it('Should preserve natural order when configured', async () => { + const tester = createExtensionTester( + Object.assign({ namespace: 'catalog' }, catalogEntityPage), + { + config: { + defaultContentOrder: 'natural', + }, + }, + ) + .add(techdocsEntityContent) + .add(apidocsEntityContent); + + await renderInTestApp(tester.reactElement(), { + apis: [ + [catalogApiRef, mockCatalogApi], + [starredEntitiesApiRef, mockStarredEntitiesApi], + ], + config: { + app: { + title: 'Custom app', + }, + backend: { baseUrl: 'http://localhost:7000' }, + }, + mountedRoutes: { + '/catalog': convertLegacyRouteRef(rootRouteRef), + '/catalog/:namespace/:kind/:name': + convertLegacyRouteRef(entityRouteRef), + }, + }); + + await userEvent.click( + await screen.findByRole('tab', { name: /Documentation/ }), + ); + + const buttons = await screen.findAllByRole('button', { + name: /Docs/, + }); + expect(buttons[0]).toHaveTextContent('TechDocs'); + expect(buttons[1]).toHaveTextContent('ApiDocs'); + }); + + it('Should support per-group content order override', async () => { + const tester = createExtensionTester( + Object.assign({ namespace: 'catalog' }, catalogEntityPage), + { + config: { + defaultContentOrder: 'title', + groups: [ + { + documentation: { + title: 'Documentation', + contentOrder: 'natural', + }, + }, + ], + }, + }, + ) + .add(techdocsEntityContent) + .add(apidocsEntityContent); + + await renderInTestApp(tester.reactElement(), { + apis: [ + [catalogApiRef, mockCatalogApi], + [starredEntitiesApiRef, mockStarredEntitiesApi], + ], + config: { + app: { + title: 'Custom app', + }, + backend: { baseUrl: 'http://localhost:7000' }, + }, + mountedRoutes: { + '/catalog': convertLegacyRouteRef(rootRouteRef), + '/catalog/:namespace/:kind/:name': + convertLegacyRouteRef(entityRouteRef), + }, + }); + + await userEvent.click( + await screen.findByRole('tab', { name: /Documentation/ }), + ); + + const buttons = await screen.findAllByRole('button', { + name: /Docs/, + }); + expect(buttons[0]).toHaveTextContent('TechDocs'); + expect(buttons[1]).toHaveTextContent('ApiDocs'); + }); + it('Should render groups on the correct order', async () => { const tester = createExtensionTester( Object.assign({ namespace: 'catalog' }, catalogEntityPage), @@ -485,29 +628,23 @@ describe('Entity page', () => { }, }); - await renderInTestApp( - - {tester.reactElement()} - , - { - config: { - app: { - title: 'Custom app', - }, - backend: { baseUrl: 'http://localhost:7000' }, - }, - mountedRoutes: { - '/catalog': convertLegacyRouteRef(rootRouteRef), - '/catalog/:namespace/:kind/:name': - convertLegacyRouteRef(entityRouteRef), + await renderInTestApp(tester.reactElement(), { + apis: [ + [catalogApiRef, mockCatalogApi], + [starredEntitiesApiRef, mockStarredEntitiesApi], + ], + config: { + app: { + title: 'Custom app', }, + backend: { baseUrl: 'http://localhost:7000' }, }, - ); + mountedRoutes: { + '/catalog': convertLegacyRouteRef(rootRouteRef), + '/catalog/:namespace/:kind/:name': + convertLegacyRouteRef(entityRouteRef), + }, + }); await waitFor(() => expect(screen.getAllByRole('tab')).toHaveLength(2)); @@ -522,29 +659,23 @@ describe('Entity page', () => { Object.assign({ namespace: 'catalog' }, catalogEntityPage), ); - await renderInTestApp( - - {tester.reactElement()} - , - { - config: { - app: { - title: 'Custom app', - }, - backend: { baseUrl: 'http://localhost:7000' }, - }, - mountedRoutes: { - '/catalog': convertLegacyRouteRef(rootRouteRef), - '/catalog/:namespace/:kind/:name': - convertLegacyRouteRef(entityRouteRef), + await renderInTestApp(tester.reactElement(), { + apis: [ + [catalogApiRef, mockCatalogApi], + [starredEntitiesApiRef, mockStarredEntitiesApi], + ], + config: { + app: { + title: 'Custom app', }, + backend: { baseUrl: 'http://localhost:7000' }, }, - ); + mountedRoutes: { + '/catalog': convertLegacyRouteRef(rootRouteRef), + '/catalog/:namespace/:kind/:name': + convertLegacyRouteRef(entityRouteRef), + }, + }); await waitFor(() => expect(screen.getByText(/artist-lookup/)).toBeInTheDocument(), @@ -567,29 +698,23 @@ describe('Entity page', () => { Object.assign({ namespace: 'catalog' }, catalogEntityPage), ).add(customEntityHeader); - await renderInTestApp( - - {tester.reactElement()} - , - { - config: { - app: { - title: 'Custom app', - }, - backend: { baseUrl: 'http://localhost:7000' }, - }, - mountedRoutes: { - '/catalog': convertLegacyRouteRef(rootRouteRef), - '/catalog/:namespace/:kind/:name': - convertLegacyRouteRef(entityRouteRef), + await renderInTestApp(tester.reactElement(), { + apis: [ + [catalogApiRef, mockCatalogApi], + [starredEntitiesApiRef, mockStarredEntitiesApi], + ], + config: { + app: { + title: 'Custom app', }, + backend: { baseUrl: 'http://localhost:7000' }, }, - ); + mountedRoutes: { + '/catalog': convertLegacyRouteRef(rootRouteRef), + '/catalog/:namespace/:kind/:name': + convertLegacyRouteRef(entityRouteRef), + }, + }); await waitFor(() => expect( @@ -634,33 +759,28 @@ describe('Entity page', () => { Object.assign({ namespace: 'catalog' }, catalogEntityPage), ).add(menuItem); - renderInTestApp( - - {tester.reactElement()} - , - { - config: { - app: { - title: 'Custom app', - }, - backend: { baseUrl: 'http://localhost:7000' }, - }, - mountedRoutes: { - '/catalog': convertLegacyRouteRef(rootRouteRef), - '/catalog/:namespace/:kind/:name': - convertLegacyRouteRef(entityRouteRef), + await renderInTestApp(tester.reactElement(), { + apis: [ + [catalogApiRef, mockCatalogApi], + [starredEntitiesApiRef, mockStarredEntitiesApi], + ], + config: { + app: { + title: 'Custom app', }, + backend: { baseUrl: 'http://localhost:7000' }, }, - ); + mountedRoutes: { + '/catalog': convertLegacyRouteRef(rootRouteRef), + '/catalog/:namespace/:kind/:name': + convertLegacyRouteRef(entityRouteRef), + }, + }); const { disabled } = params.useProps(); + await userEvent.click(await screen.findByTestId('menu-button')); + await waitFor(async () => { - await userEvent.click(screen.getByTestId('menu-button')); expect(screen.getByText('Test Title')).toBeInTheDocument(); expect(screen.getByText('Test Icon')).toBeInTheDocument(); const anchor = screen.getByText('Test Title').closest('a'); @@ -696,43 +816,46 @@ describe('Entity page', () => { Object.assign({ namespace: 'catalog' }, catalogEntityPage), ).add(menuItem); - renderInTestApp( - - {tester.reactElement()} - , - { - config: { - app: { - title: 'Custom app', - }, - backend: { baseUrl: 'http://localhost:7000' }, - }, - mountedRoutes: { - '/catalog': convertLegacyRouteRef(rootRouteRef), - '/catalog/:namespace/:kind/:name': - convertLegacyRouteRef(entityRouteRef), + await renderInTestApp(tester.reactElement(), { + apis: [ + [catalogApiRef, mockCatalogApi], + [starredEntitiesApiRef, mockStarredEntitiesApi], + ], + config: { + app: { + title: 'Custom app', }, + backend: { baseUrl: 'http://localhost:7000' }, }, - ); + mountedRoutes: { + '/catalog': convertLegacyRouteRef(rootRouteRef), + '/catalog/:namespace/:kind/:name': + convertLegacyRouteRef(entityRouteRef), + }, + }); const { disabled } = params.useProps(); - await waitFor(async () => { - await userEvent.click(screen.getByTestId('menu-button')); - expect(screen.getByText('Test Title')).toBeInTheDocument(); - expect(screen.getByText('Test Icon')).toBeInTheDocument(); - const listItem = screen.getByText('Test Title').closest('li'); - expect(listItem).toHaveAttribute('aria-disabled', disabled.toString()); - if (!disabled) { - await userEvent.click(screen.getByText('Test Title')); - } - expect(onClickMock).toHaveBeenCalledTimes(disabled ? 0 : 1); - }); + // Wait for entity to load first + await waitFor(() => + expect(screen.getByText(/artist-lookup/)).toBeInTheDocument(), + ); + + await userEvent.click(screen.getByTestId('menu-button')); + + // Wait for menu to open + await waitFor(() => + expect(screen.getByText('Test Title')).toBeInTheDocument(), + ); + + expect(screen.getByText('Test Icon')).toBeInTheDocument(); + const listItem = screen.getByText('Test Title').closest('li'); + expect(listItem).toHaveAttribute('aria-disabled', disabled.toString()); + if (!disabled) { + await userEvent.click(screen.getByText('Test Title')); + } + + expect(onClickMock).toHaveBeenCalledTimes(disabled ? 0 : 1); }); it.each([ @@ -787,32 +910,27 @@ describe('Entity page', () => { .add(menuItem) .add(filteredMenuItem); - renderInTestApp( - - {tester.reactElement()} - , - { - config: { - app: { - title: 'Custom app', - }, - backend: { baseUrl: 'http://localhost:7000' }, - }, - mountedRoutes: { - '/catalog': convertLegacyRouteRef(rootRouteRef), - '/catalog/:namespace/:kind/:name': - convertLegacyRouteRef(entityRouteRef), + await renderInTestApp(tester.reactElement(), { + config: { + app: { + title: 'Custom app', }, + backend: { baseUrl: 'http://localhost:7000' }, }, - ); + mountedRoutes: { + '/catalog': convertLegacyRouteRef(rootRouteRef), + '/catalog/:namespace/:kind/:name': + convertLegacyRouteRef(entityRouteRef), + }, + apis: [ + [catalogApiRef, mockCatalogApi], + [starredEntitiesApiRef, mockStarredEntitiesApi], + ], + }); + + await userEvent.click(await screen.findByTestId('menu-button')); await waitFor(async () => { - await userEvent.click(screen.getByTestId('menu-button')); expect(screen.getByText('Should Render')).toBeInTheDocument(); expect( screen.queryByText('Should Not Render'), diff --git a/plugins/catalog/src/alpha/pages.tsx b/plugins/catalog/src/alpha/pages.tsx index e4e1a78151..ac35931892 100644 --- a/plugins/catalog/src/alpha/pages.tsx +++ b/plugins/catalog/src/alpha/pages.tsx @@ -14,25 +14,25 @@ * limitations under the License. */ -import { - compatWrapper, - convertLegacyRouteRef, -} from '@backstage/core-compat-api'; +import { convertLegacyRouteRef } from '@backstage/core-compat-api'; import { coreExtensionData, createExtensionInput, PageBlueprint, } from '@backstage/frontend-plugin-api'; +import { z } from 'zod/v4'; import { AsyncEntityProvider, entityRouteRef, } from '@backstage/plugin-catalog-react'; import { - EntityHeaderBlueprint, + defaultEntityContentGroupDefinitions, EntityContentBlueprint, - defaultEntityContentGroups, EntityContextMenuItemBlueprint, + EntityHeaderBlueprint, + EntityContentGroupDefinitions, } from '@backstage/plugin-catalog-react/alpha'; +import CategoryIcon from '@material-ui/icons/Category'; import { rootRouteRef } from '../routes'; import { useEntityFromUrl } from '../components/CatalogEntityPage/useEntityFromUrl'; import { buildFilterFn } from './filter/FilterWrapper'; @@ -41,35 +41,36 @@ export const catalogPage = PageBlueprint.makeWithOverrides({ inputs: { filters: createExtensionInput([coreExtensionData.reactElement]), }, - config: { - schema: { - pagination: z => - z - .union([ - z.boolean(), - z.object({ - mode: z.enum(['cursor', 'offset']), - limit: z.number().optional(), - offset: z.number().optional(), - }), - ]) - .default(true), - }, + configSchema: { + pagination: z + .union([ + z.boolean(), + z.object({ + mode: z.enum(['cursor', 'offset']), + limit: z.number().optional(), + offset: z.number().optional(), + }), + ]) + .default(true), }, factory(originalFactory, { inputs, config }) { return originalFactory({ path: '/catalog', - routeRef: convertLegacyRouteRef(rootRouteRef), + routeRef: rootRouteRef, + icon: , + title: 'Catalog', loader: async () => { - const { BaseCatalogPage } = await import('../components/CatalogPage'); + const { NfsDefaultCatalogPage } = await import( + '../components/CatalogPage/DefaultCatalogPage' + ); const filters = inputs.filters.map(filter => filter.get(coreExtensionData.reactElement), ); - return compatWrapper( - {filters}} pagination={config.pagination} - />, + /> ); }, }); @@ -91,24 +92,45 @@ export const catalogEntityPage = PageBlueprint.makeWithOverrides({ EntityContentBlueprint.dataRefs.filterFunction.optional(), EntityContentBlueprint.dataRefs.filterExpression.optional(), EntityContentBlueprint.dataRefs.group.optional(), + EntityContentBlueprint.dataRefs.icon.optional(), ]), contextMenuItems: createExtensionInput([ coreExtensionData.reactElement, EntityContextMenuItemBlueprint.dataRefs.filterFunction.optional(), ]), }, - config: { - schema: { - groups: z => - z - .array(z.record(z.string(), z.object({ title: z.string() }))) - .optional(), - }, + configSchema: { + groups: z + .array( + z.record( + z.string(), + z.object({ + title: z.string(), + icon: z.string().optional(), + aliases: z.array(z.string()).optional(), + contentOrder: z.enum(['title', 'natural']).optional(), + }), + ), + ) + .optional(), + defaultContentOrder: z + .enum(['title', 'natural']) + .optional() + .default('title'), + showNavItemIcons: z.boolean().optional().default(false), }, factory(originalFactory, { config, inputs }) { return originalFactory({ path: '/catalog/:namespace/:kind/:name', - routeRef: convertLegacyRouteRef(entityRouteRef), + noHeader: true, + title: 'Catalog Entity', + // NOTE: The `convertLegacyRouteRef` call here ensures that this route ref + // is mutated to support the new frontend system. Removing this conversion + // is a potentially breaking change since this is a singleton and the + // route refs from `core-plugin-api` used to not support the new format. + // This shouldn't be removed until we completely deprecate the + // `core-compat-api` package. + routeRef: convertLegacyRouteRef(entityRouteRef), // READ THE ABOVE loader: async () => { const { EntityLayout } = await import('./components/EntityLayout'); @@ -119,11 +141,6 @@ export const catalogEntityPage = PageBlueprint.makeWithOverrides({ (() => true), })); - type Groups = Record< - string, - { title: string; items: Array<(typeof inputs.contents)[0]> } - >; - // Get available headers, sorted by if they have a filter function or not. // TODO(blam): we should really have priority or some specificity here which can be used to sort the headers. // That can be done with embedding the priority in the dataRef alongside the filter function. @@ -138,39 +155,11 @@ export const catalogEntityPage = PageBlueprint.makeWithOverrides({ return 0; }); - let groups = Object.entries(defaultEntityContentGroups).reduce( - (rest, group) => { - const [groupId, groupValue] = group; - return { - ...rest, - [groupId]: { title: groupValue, items: [] }, - }; - }, - {}, - ); - - // config groups override default groups - if (config.groups) { - groups = config.groups.reduce((rest, group) => { - const [groupId, groupValue] = Object.entries(group)[0]; - return { - ...rest, - [groupId]: { title: groupValue.title, items: [] }, - }; - }, {}); - } - - for (const output of inputs.contents) { - const itemId = output.node.spec.id; - const itemTitle = output.get(EntityContentBlueprint.dataRefs.title); - const itemGroup = output.get(EntityContentBlueprint.dataRefs.group); - const group = itemGroup && groups[itemGroup]; - if (!group) { - groups[itemId] = { title: itemTitle, items: [output] }; - continue; - } - group.items.push(output); - } + const groupDefinitions = + config.groups?.reduce( + (rest, group) => ({ ...rest, ...group }), + {} as EntityContentGroupDefinitions, + ) ?? defaultEntityContentGroupDefinitions; const Component = () => { const entityFromUrl = useEntityFromUrl(); @@ -188,33 +177,35 @@ export const catalogEntityPage = PageBlueprint.makeWithOverrides({ - {Object.values(groups).flatMap(({ title, items }) => - items.map(output => ( - - {output.get(coreExtensionData.reactElement)} - - )), - )} + {inputs.contents.map(output => ( + + {output.get(coreExtensionData.reactElement)} + + ))} ); }; - return compatWrapper(); + return ; }, }); }, diff --git a/plugins/catalog/src/alpha/plugin.tsx b/plugins/catalog/src/alpha/plugin.tsx index ff26d20003..243b93db9b 100644 --- a/plugins/catalog/src/alpha/plugin.tsx +++ b/plugins/catalog/src/alpha/plugin.tsx @@ -14,10 +14,9 @@ * limitations under the License. */ -import { convertLegacyRouteRefs } from '@backstage/core-compat-api'; import { createFrontendPlugin } from '@backstage/frontend-plugin-api'; - import { entityRouteRef } from '@backstage/plugin-catalog-react'; +import CategoryIcon from '@material-ui/icons/Category'; import { createComponentRouteRef, @@ -40,17 +39,21 @@ import contextMenuItems from './contextMenuItems'; /** @alpha */ export default createFrontendPlugin({ pluginId: 'catalog', - info: { packageJson: () => import('../../package.json') }, - routes: convertLegacyRouteRefs({ + title: 'Catalog', + icon: , + info: { + packageJson: () => import('../../package.json'), + }, + routes: { catalogIndex: rootRouteRef, catalogEntity: entityRouteRef, - }), - externalRoutes: convertLegacyRouteRefs({ + }, + externalRoutes: { viewTechDoc: viewTechDocRouteRef, createComponent: createComponentRouteRef, createFromTemplate: createFromTemplateRouteRef, unregisterRedirect: unregisterRedirectRouteRef, - }), + }, extensions: [ ...apis, ...pages, diff --git a/plugins/catalog/src/alpha/translation.ts b/plugins/catalog/src/alpha/translation.ts index 2dc2f2d34e..ef35cece84 100644 --- a/plugins/catalog/src/alpha/translation.ts +++ b/plugins/catalog/src/alpha/translation.ts @@ -16,7 +16,7 @@ import { createTranslationRef } from '@backstage/core-plugin-api/alpha'; -/** @alpha */ +/** @public */ export const catalogTranslationRef = createTranslationRef({ id: 'catalog', messages: { @@ -25,15 +25,22 @@ export const catalogTranslationRef = createTranslationRef({ createButtonTitle: 'Create', supportButtonContent: 'All your software catalog entities', }, + entityPage: { + notFoundMessage: 'There is no {{kind}} with the requested {{link}}.', + notFoundLinkText: 'kind, namespace, and name', + }, aboutCard: { title: 'About', refreshButtonTitle: 'Schedule entity refresh', editButtonTitle: 'Edit Metadata', + editButtonAriaLabel: 'Edit', createSimilarButtonTitle: 'Create something similar', refreshScheduledMessage: 'Refresh scheduled', + refreshButtonAriaLabel: 'Refresh', launchTemplate: 'Launch Template', viewTechdocs: 'View TechDocs', viewSource: 'View Source', + unknown: 'unknown', descriptionField: { label: 'Description', value: 'No description', @@ -54,6 +61,9 @@ export const catalogTranslationRef = createTranslationRef({ label: 'Parent Component', value: 'No Parent Component', }, + kindField: { + label: 'Kind', + }, typeField: { label: 'Type', }, @@ -69,8 +79,10 @@ export const catalogTranslationRef = createTranslationRef({ }, }, searchResultItem: { + kind: 'Kind', + type: 'Type', lifecycle: 'Lifecycle', - Owner: 'Owner', + owner: 'Owner', }, catalogTable: { warningPanelTitle: 'Could not fetch catalog entities.', @@ -78,18 +90,19 @@ export const catalogTranslationRef = createTranslationRef({ editActionTitle: 'Edit', starActionTitle: 'Add to favorites', unStarActionTitle: 'Remove from favorites', + allFilters: 'All', }, dependencyOfComponentsCard: { title: 'Dependency of components', - emptyMessage: 'No component depends on this component', + emptyMessage: 'No component depends on this component.', }, dependsOnComponentsCard: { title: 'Depends on components', - emptyMessage: 'No component is a dependency of this component', + emptyMessage: 'No component is a dependency of this component.', }, dependsOnResourcesCard: { title: 'Depends on resources', - emptyMessage: 'No resource is a dependency of this component', + emptyMessage: 'No resource is a dependency of this component.', }, entityContextMenu: { copiedMessage: 'Copied!', @@ -97,9 +110,12 @@ export const catalogTranslationRef = createTranslationRef({ inspectMenuTitle: 'Inspect entity', copyURLMenuTitle: 'Copy entity URL', unregisterMenuTitle: 'Unregister entity', + moreButtonAriaLabel: 'more', }, entityLabelsCard: { title: 'Labels', + columnKeyLabel: 'Label', + columnValueLabel: 'Value', emptyDescription: 'No labels defined for this entity. You can add labels to your entity YAML as shown in the highlighted example below:', readMoreButtonTitle: 'Read more', @@ -121,38 +137,42 @@ export const catalogTranslationRef = createTranslationRef({ 'Want to help us build this? Check out our Getting Started documentation.', docButtonTitle: 'DOCS', }, + entityTabs: { + tabsAriaLabel: 'Tabs', + }, deleteEntity: { dialogTitle: 'Are you sure you want to delete this entity?', deleteButtonTitle: 'Delete', cancelButtonTitle: 'Cancel', description: - 'This entity is not referenced by any location and is therefore not receiving updates. Click here to delete.', + 'This entity is not referenced by any location and is therefore not receiving updates.', + actionButtonTitle: 'Delete entity', }, entityProcessingErrorsDescription: 'The error below originates from', entityRelationWarningDescription: "This entity has relations to other entities, which can't be found in the catalog.\n Entities not found are: ", hasComponentsCard: { title: 'Has components', - emptyMessage: 'No component is part of this system', + emptyMessage: 'No component is part of this system.', }, hasResourcesCard: { title: 'Has resources', - emptyMessage: 'No resource is part of this system', + emptyMessage: 'No resource is part of this system.', }, hasSubcomponentsCard: { title: 'Has subcomponents', - emptyMessage: 'No subcomponent is part of this component', + emptyMessage: 'No subcomponent is part of this component.', }, hasSubdomainsCard: { title: 'Has subdomains', - emptyMessage: 'No subdomain is part of this domain', + emptyMessage: 'No subdomain is part of this domain.', }, hasSystemsCard: { title: 'Has systems', - emptyMessage: 'No system is part of this domain', + emptyMessage: 'No system is part of this domain.', }, relatedEntitiesCard: { - emptyHelpLinkTitle: 'Learn how to change this', + emptyHelpLinkTitle: 'Learn how to change this.', }, systemDiagramCard: { title: 'System Diagram', diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx index fd1c05ba5f..065d786a13 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx @@ -16,11 +16,6 @@ import { useCallback } from 'react'; -import { makeStyles } from '@material-ui/core/styles'; -import Card from '@material-ui/core/Card'; -import CardContent from '@material-ui/core/CardContent'; -import CardHeader from '@material-ui/core/CardHeader'; -import Divider from '@material-ui/core/Divider'; import IconButton from '@material-ui/core/IconButton'; import CachedIcon from '@material-ui/icons/Cached'; import EditIcon from '@material-ui/icons/Edit'; @@ -31,9 +26,9 @@ import { AppIcon, HeaderIconLinkRow, IconLinkVerticalProps, - InfoCardVariants, Link, } from '@backstage/core-components'; +import { EntityInfoCard } from '@backstage/plugin-catalog-react'; import { alertApiRef, errorApiRef, @@ -77,6 +72,16 @@ import { createFromTemplateRouteRef, viewTechDocRouteRef } from '../../routes'; import { catalogTranslationRef } from '../../alpha/translation'; import { useSourceTemplateCompoundEntityRef } from './hooks'; import { AboutContent } from './AboutContent'; +import { makeStyles } from '@material-ui/core/styles'; + +const useStyles = makeStyles({ + linkContainer: { + border: '1px solid var(--bui-border-1)', + borderLeft: 'none', + borderRight: 'none', + marginBottom: 'var(--bui-space-6)', + }, +}); export function useCatalogSourceIconLinkProps() { const { entity } = useEntity(); @@ -152,41 +157,13 @@ function DefaultAboutCardSubheader() { return ; } -const useStyles = makeStyles({ - gridItemCard: { - display: 'flex', - flexDirection: 'column', - height: 'calc(100% - 10px)', // for pages without content header - marginBottom: '10px', - }, - fullHeightCard: { - display: 'flex', - flexDirection: 'column', - height: '100%', - }, - gridItemCardContent: { - flex: 1, - }, - fullHeightCardContent: { - flex: 1, - }, -}); - -/** - * Props for {@link EntityAboutCard}. - * - * @public - */ -export type AboutCardProps = { - variant?: InfoCardVariants; -}; - -export interface InternalAboutCardProps extends AboutCardProps { - subheader?: JSX.Element; +export interface InternalAboutCardProps { + /** Icon link row rendered at the top of the card body. */ + iconLinks?: JSX.Element; } export function InternalAboutCard(props: InternalAboutCardProps) { - const { variant, subheader } = props; + const { iconLinks } = props; const classes = useStyles(); const { entity } = useEntity(); const catalogApi = useApi(catalogApiRef); @@ -202,20 +179,6 @@ export function InternalAboutCard(props: InternalAboutCardProps) { const entityMetadataEditUrl = entity.metadata.annotations?.[ANNOTATION_EDIT_URL]; - let cardClass = ''; - if (variant === 'gridItem') { - cardClass = classes.gridItemCard; - } else if (variant === 'fullHeight') { - cardClass = classes.fullHeightCard; - } - - let cardContentClass = ''; - if (variant === 'gridItem') { - cardContentClass = classes.gridItemCardContent; - } else if (variant === 'fullHeight') { - cardContentClass = classes.fullHeightCardContent; - } - const entityLocation = entity.metadata.annotations?.[ANNOTATION_LOCATION]; // Limiting the ability to manually refresh to the less expensive locations const allowRefresh = @@ -234,60 +197,58 @@ export function InternalAboutCard(props: InternalAboutCardProps) { }, [catalogApi, entity, alertApi, t, errorApi]); return ( - - - {allowRefresh && canRefresh && ( - - - - )} + + {allowRefresh && canRefresh && ( + + + + )} + + + + {sourceTemplateRef && templateRoute && ( - + - {sourceTemplateRef && templateRoute && ( - - - - )} - - } - subheader={subheader ?? } - /> - - - - - + )} + + } + > +
    + {iconLinks ?? } +
    + + ); } /** * Exported publicly via the EntityAboutCard * - * NOTE: We generally do not accept pull requests to extend this class with more - * props and customizability. If you need to tweak it, consider making a bespoke - * card in your own repository instead, that is perfect for your own needs. + * NOTE: We generally do not accept pull requests to extend this class with props + * and customizability. If you need to tweak it, consider making a bespoke card + * in your own repository instead, that is perfect for your own needs. */ -export function AboutCard(props: AboutCardProps) { - return ; +export function AboutCard() { + return ; } diff --git a/plugins/catalog/src/components/AboutCard/AboutContent.test.tsx b/plugins/catalog/src/components/AboutCard/AboutContent.test.tsx index d91d5e96d9..4f6d12cbc1 100644 --- a/plugins/catalog/src/components/AboutCard/AboutContent.test.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutContent.test.tsx @@ -83,6 +83,8 @@ describe('', () => { expect(screen.getByText('Type').nextSibling).toHaveTextContent('t'); expect(screen.getByText('Lifecycle')).toBeInTheDocument(); expect(screen.getByText('Lifecycle').nextSibling).toHaveTextContent('l'); + expect(screen.getByText('Kind')).toBeInTheDocument(); + expect(screen.getByText('Kind').nextSibling).toHaveTextContent('Unknown'); expect(screen.getByText('Tags')).toBeInTheDocument(); expect(screen.getByText('Tags').nextSibling).toHaveTextContent('tag-1'); }); @@ -111,6 +113,7 @@ describe('', () => { expect(screen.queryByText('Parent Component')).not.toBeInTheDocument(); expect(screen.queryByText('Type')).not.toBeInTheDocument(); expect(screen.queryByText('Lifecycle')).not.toBeInTheDocument(); + expect(screen.getByText('Kind')).toBeInTheDocument(); }); }); @@ -362,7 +365,9 @@ describe('', () => { expect(screen.getByText('Owner').nextSibling).toHaveTextContent( 'user:guest', ); - expect(screen.queryByText('Domain')).not.toBeInTheDocument(); + expect( + screen.queryByRole('heading', { name: 'Domain' }), + ).not.toBeInTheDocument(); expect(screen.queryByText('System')).not.toBeInTheDocument(); expect(screen.queryByText('Parent Component')).not.toBeInTheDocument(); expect(screen.queryByText('Type')).not.toBeInTheDocument(); @@ -389,7 +394,9 @@ describe('', () => { expect(screen.getByText('Owner').nextSibling).toHaveTextContent( 'No Owner', ); - expect(screen.queryByText('Domain')).not.toBeInTheDocument(); + expect( + screen.queryByRole('heading', { name: 'Domain' }), + ).not.toBeInTheDocument(); expect(screen.queryByText('System')).not.toBeInTheDocument(); expect(screen.queryByText('Parent Component')).not.toBeInTheDocument(); expect(screen.queryByText('Type')).not.toBeInTheDocument(); @@ -617,7 +624,9 @@ describe('', () => { expect(screen.getByText('Domain').nextSibling).toHaveTextContent( 'domain', ); - expect(screen.queryByText('System')).not.toBeInTheDocument(); + expect( + screen.queryByRole('heading', { name: 'System' }), + ).not.toBeInTheDocument(); expect(screen.queryByText('Parent Component')).not.toBeInTheDocument(); expect(screen.queryByText('Type')).not.toBeInTheDocument(); expect(screen.queryByText('Lifecycle')).not.toBeInTheDocument(); @@ -648,7 +657,9 @@ describe('', () => { expect(screen.getByText('Domain').nextSibling).toHaveTextContent( 'No Domain', ); - expect(screen.queryByText('System')).not.toBeInTheDocument(); + expect( + screen.queryByRole('heading', { name: 'System' }), + ).not.toBeInTheDocument(); expect(screen.queryByText('Parent Component')).not.toBeInTheDocument(); expect(screen.queryByText('Type')).not.toBeInTheDocument(); expect(screen.queryByText('Lifecycle')).not.toBeInTheDocument(); diff --git a/plugins/catalog/src/components/AboutCard/AboutContent.tsx b/plugins/catalog/src/components/AboutCard/AboutContent.tsx index 96f4fd8790..18d17d06da 100644 --- a/plugins/catalog/src/components/AboutCard/AboutContent.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutContent.tsx @@ -26,8 +26,8 @@ import { } from '@backstage/plugin-catalog-react'; import { JsonArray } from '@backstage/types'; import Chip from '@material-ui/core/Chip'; -import Grid from '@material-ui/core/Grid'; import { makeStyles } from '@material-ui/core/styles'; +import { Grid } from '@backstage/ui'; import { MarkdownContent } from '@backstage/core-components'; import { AboutField } from './AboutField'; import { LinksGridList } from '../EntityLinksCard/LinksGridList'; @@ -114,25 +114,25 @@ export function AboutContent(props: AboutContentProps) { entitySourceLocation = undefined; } + const columns = { initial: '1', sm: '2', lg: '3' } as const; + return ( - - - - + + + + + + {ownedByRelations.length > 0 && ( @@ -142,7 +142,6 @@ export function AboutContent(props: AboutContentProps) { {partOfDomainRelations.length > 0 && ( {partOfSystemRelations.length > 0 && ( )} + + {(entity?.metadata?.tags || []).map(tag => ( + + ))} + + {(isAPI || isComponent || isResource || @@ -191,7 +197,6 @@ export function AboutContent(props: AboutContentProps) { )} {(isAPI || @@ -200,38 +205,29 @@ export function AboutContent(props: AboutContentProps) { )} - - {(entity?.metadata?.tags || []).map(tag => ( - - ))} - {isLocation && (entity?.spec?.targets || entity?.spec?.target) && ( - - target as string) - .map(target => ({ - text: target, - href: getLocationTargetHref( - target, - (entity?.spec?.type || 'unknown') as string, - entitySourceLocation!, - ), - }))} - /> - + + + target as string) + .map(target => ({ + text: target, + href: getLocationTargetHref( + target, + (entity?.spec?.type || t('aboutCard.unknown')) as string, + entitySourceLocation!, + ), + }))} + /> + + )} - + ); } diff --git a/plugins/catalog/src/components/AboutCard/AboutField.tsx b/plugins/catalog/src/components/AboutCard/AboutField.tsx index db3255997b..725a82612a 100644 --- a/plugins/catalog/src/components/AboutCard/AboutField.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutField.tsx @@ -15,10 +15,11 @@ */ import { useElementFilter } from '@backstage/core-plugin-api'; -import Grid from '@material-ui/core/Grid'; import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; import { ReactNode } from 'react'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { catalogTranslationRef } from '../../alpha/translation'; const useStyles = makeStyles(theme => ({ value: { @@ -46,15 +47,15 @@ const useStyles = makeStyles(theme => ({ export interface AboutFieldProps { label: string; value?: string; - gridSizes?: Record; children?: ReactNode; className?: string; } /** @public */ export function AboutField(props: AboutFieldProps) { - const { label, value, gridSizes, children, className } = props; + const { label, value, children, className } = props; const classes = useStyles(); + const { t } = useTranslationRef(catalogTranslationRef); const childElements = useElementFilter(children, c => c.getElements()); @@ -64,15 +65,15 @@ export function AboutField(props: AboutFieldProps) { childElements ) : ( - {value || `unknown`} + {value || t('aboutCard.unknown')} ); return ( - +
    {label} {content} - +
    ); } diff --git a/plugins/catalog/src/components/AboutCard/index.ts b/plugins/catalog/src/components/AboutCard/index.ts index 9660a5e2e2..f6a11dfbb7 100644 --- a/plugins/catalog/src/components/AboutCard/index.ts +++ b/plugins/catalog/src/components/AboutCard/index.ts @@ -15,7 +15,6 @@ */ export { AboutCard } from './AboutCard'; -export type { AboutCardProps } from './AboutCard'; export { AboutContent } from './AboutContent'; export type { AboutContentProps } from './AboutContent'; export { AboutField } from './AboutField'; diff --git a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx index b5f0898829..ed76aa49b2 100644 --- a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx @@ -24,6 +24,7 @@ import { TableProps, } from '@backstage/core-components'; import { configApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api'; +import { HeaderPage } from '@backstage/ui'; import { CatalogFilterLayout, DefaultFilters, @@ -48,9 +49,51 @@ export type BaseCatalogPageProps = { pagination?: EntityListPagination; }; +function CatalogPageContent(props: BaseCatalogPageProps) { + const { filters, content = , pagination } = props; + + return ( + + + {filters} + {content} + + + ); +} + /** @internal */ export function BaseCatalogPage(props: BaseCatalogPageProps) { - const { filters, content = , pagination } = props; + const orgName = + useApi(configApiRef).getOptionalString('organization.name') ?? 'Backstage'; + const createComponentLink = useRouteRef(createComponentRouteRef); + const { t } = useTranslationRef(catalogTranslationRef); + const { allowed } = usePermission({ + permission: catalogEntityCreatePermission, + }); + const headerActions = ( + <> + {allowed && ( + + )} + {t('indexPage.supportButtonContent')} + + ); + + return ( + + + {headerActions} + + + + ); +} + +function NfsBaseCatalogPage(props: BaseCatalogPageProps) { const orgName = useApi(configApiRef).getOptionalString('organization.name') ?? 'Backstage'; const createComponentLink = useRouteRef(createComponentRouteRef); @@ -60,25 +103,25 @@ export function BaseCatalogPage(props: BaseCatalogPageProps) { }); return ( - + <> + + {allowed && ( + + )} + {t('indexPage.supportButtonContent')} + + } + /> - - {allowed && ( - - )} - {t('indexPage.supportButtonContent')} - - - - {filters} - {content} - - + - + ); } @@ -138,3 +181,43 @@ export function DefaultCatalogPage(props: DefaultCatalogPageProps) { /> ); } + +/** @public */ +export function NfsDefaultCatalogPage(props: DefaultCatalogPageProps) { + const { + columns, + actions, + initiallySelectedFilter = 'owned', + initialKind = 'component', + tableOptions = {}, + emptyContent, + pagination, + ownerPickerMode, + filters, + initiallySelectedNamespaces, + } = props; + + return ( + + ) + } + content={ + + } + pagination={pagination} + /> + ); +} diff --git a/plugins/catalog/src/components/CatalogPage/index.ts b/plugins/catalog/src/components/CatalogPage/index.ts index 258fedf15c..5dea5787ba 100644 --- a/plugins/catalog/src/components/CatalogPage/index.ts +++ b/plugins/catalog/src/components/CatalogPage/index.ts @@ -15,7 +15,11 @@ */ export { CatalogPage } from './CatalogPage'; -export { BaseCatalogPage, DefaultCatalogPage } from './DefaultCatalogPage'; +export { + BaseCatalogPage, + DefaultCatalogPage, + NfsDefaultCatalogPage, +} from './DefaultCatalogPage'; export type { BaseCatalogPageProps, DefaultCatalogPageProps, diff --git a/plugins/catalog/src/components/CatalogSearchResultListItem/CatalogSearchResultListItem.tsx b/plugins/catalog/src/components/CatalogSearchResultListItem/CatalogSearchResultListItem.tsx index f33c9cd412..0e604f2d5b 100644 --- a/plugins/catalog/src/components/CatalogSearchResultListItem/CatalogSearchResultListItem.tsx +++ b/plugins/catalog/src/components/CatalogSearchResultListItem/CatalogSearchResultListItem.tsx @@ -127,8 +127,18 @@ export function CatalogSearchResultListItem( } /> - {result.kind && } - {result.type && } + {result.kind && ( + + )} + {result.type && ( + + )} {result.lifecycle && ( )} diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index 464688c590..b021aae0e1 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -29,16 +29,19 @@ import { WarningPanel, } from '@backstage/core-components'; import { + entityPresentationApiRef, + entityPresentationSnapshot, getEntityRelations, - humanizeEntityRef, useEntityList, useStarredEntities, + type EntityPresentationApi, } from '@backstage/plugin-catalog-react'; +import CircularProgress from '@material-ui/core/CircularProgress'; import Typography from '@material-ui/core/Typography'; import { visuallyHidden } from '@mui/utils'; import Edit from '@material-ui/icons/Edit'; import OpenInNew from '@material-ui/icons/OpenInNew'; -import { capitalize } from 'lodash'; +import { capitalize, sortBy } from 'lodash'; import pluralize from 'pluralize'; import { ReactNode, useMemo } from 'react'; import { columnFactories } from './columns'; @@ -46,6 +49,7 @@ import { CatalogTableColumnsFunc, CatalogTableRow } from './types'; import { OffsetPaginatedCatalogTable } from './OffsetPaginatedCatalogTable'; import { CursorPaginatedCatalogTable } from './CursorPaginatedCatalogTable'; import { defaultCatalogTableColumnsFunc } from './defaultCatalogTableColumnsFunc'; +import { useApiHolder } from '@backstage/core-plugin-api'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { catalogTranslationRef } from '../../alpha'; import { FavoriteToggleIcon } from '@backstage/core-components'; @@ -68,14 +72,13 @@ export interface CatalogTableProps { subtitle?: string; } -const refCompare = (a: Entity, b: Entity) => { - const toRef = (entity: Entity) => - entity.metadata.title || - humanizeEntityRef(entity, { - defaultKind: 'Component', - }); - - return toRef(a).localeCompare(toRef(b)); +const sortEntities = (entities: Entity[], api?: EntityPresentationApi) => { + return sortBy( + entities, + e => + entityPresentationSnapshot(e, { defaultKind: 'Component' }, api) + .primaryTitle, + ); }; /** @@ -96,6 +99,8 @@ export const CatalogTable = (props: CatalogTableProps) => { emptyContent, } = props; const { isStarredEntity, toggleStarredEntity } = useStarredEntities(); + const apis = useApiHolder(); + const entityPresentationApi = apis.get(entityPresentationApiRef); const entityListContext = useEntityList(); const { @@ -108,6 +113,14 @@ export const CatalogTable = (props: CatalogTableProps) => { paginationMode, } = entityListContext; + // For non-paginated tables, only show the full loading indicator when + // there's no data yet (initial load). During filter changes we keep stale + // data visible and let the new results swap in seamlessly. For paginated + // tables we always show loading, since stale data from a different page + // would be misleading. + const isLoading = + paginationMode === 'none' ? loading && entities.length === 0 : loading; + const tableColumns = useMemo( () => typeof columns === 'function' ? columns(entityListContext) : columns, @@ -186,18 +199,31 @@ export const CatalogTable = (props: CatalogTableProps) => { const currentType = filters.type?.value || ''; const currentCount = typeof totalItems === 'number' ? `(${totalItems})` : ''; // TODO(timbonicus): remove the title from the CatalogTable once using EntitySearchBar - const titlePreamble = capitalize(filters.user?.value ?? 'all'); - const title = + const titlePreamble = capitalize( + filters.user?.value ?? t('catalogTable.allFilters'), + ); + const titleText = props.title || [titlePreamble, currentType, pluralize(currentKind), currentCount] .filter(s => s) .join(' '); + const title = + loading && !isLoading ? ( + + {titleText} + + + ) : ( + titleText + ); const actions = props.actions || defaultActions; const options: TableProps['options'] = { actionsColumnIndex: -1, loadingType: 'linear' as const, - showEmptyDataSourceMessage: !loading, + showEmptyDataSourceMessage: !isLoading, padding: 'dense' as const, ...tableOptions, }; @@ -207,12 +233,12 @@ export const CatalogTable = (props: CatalogTableProps) => { toEntityRow(e, entityPresentationApi))} next={pageInfo?.next} prev={pageInfo?.prev} /> @@ -222,23 +248,25 @@ export const CatalogTable = (props: CatalogTableProps) => { toEntityRow(e, entityPresentationApi))} /> ); } - const rows = entities.sort(refCompare).map(toEntityRow); + const rows = sortEntities(entities, entityPresentationApi).map(e => + toEntityRow(e, entityPresentationApi), + ); const pageSize = 20; const showPagination = rows.length > pageSize; return ( - isLoading={loading} + isLoading={isLoading} columns={tableColumns} options={{ paging: showPagination, @@ -258,7 +286,7 @@ export const CatalogTable = (props: CatalogTableProps) => { CatalogTable.columns = columnFactories; CatalogTable.defaultColumnsFunc = defaultCatalogTableColumnsFunc; -function toEntityRow(entity: Entity) { +function toEntityRow(entity: Entity, api?: EntityPresentationApi) { const partOfSystemRelations = getEntityRelations(entity, RELATION_PART_OF, { kind: 'system', }); @@ -270,19 +298,25 @@ function toEntityRow(entity: Entity) { // This name is here for backwards compatibility mostly; the // presentation of refs in the table should in general be handled with // EntityRefLink / EntityName components - name: humanizeEntityRef(entity, { - defaultKind: 'Component', - }), + name: entityPresentationSnapshot( + entity, + { defaultKind: 'Component' }, + api, + ).primaryTitle, entityRef: stringifyEntityRef(entity), ownedByRelationsTitle: ownedByRelations - .map(r => humanizeEntityRef(r, { defaultKind: 'group' })) + .map( + r => + entityPresentationSnapshot(r, { defaultKind: 'group' }, api) + .primaryTitle, + ) .join(', '), ownedByRelations, partOfSystemRelationTitle: partOfSystemRelations - .map(r => - humanizeEntityRef(r, { - defaultKind: 'system', - }), + .map( + r => + entityPresentationSnapshot(r, { defaultKind: 'system' }, api) + .primaryTitle, ) .join(', '), partOfSystemRelations, diff --git a/plugins/catalog/src/components/CatalogTable/columns.tsx b/plugins/catalog/src/components/CatalogTable/columns.tsx index 65094bc568..55b2d5c6a8 100644 --- a/plugins/catalog/src/components/CatalogTable/columns.tsx +++ b/plugins/catalog/src/components/CatalogTable/columns.tsx @@ -14,9 +14,10 @@ * limitations under the License. */ import { - humanizeEntityRef, + defaultEntityPresentation, EntityRefLink, EntityRefLinks, + type EntityPresentationApi, } from '@backstage/plugin-catalog-react'; import Chip from '@material-ui/core/Chip'; import { CatalogTableRow } from './types'; @@ -31,14 +32,17 @@ import { EntityTableColumnTitle } from '@backstage/plugin-catalog-react/alpha'; export const columnFactories = Object.freeze({ createNameColumn(options?: { defaultKind?: string; + entityPresentationApi?: EntityPresentationApi; }): TableColumn { function formatContent(entity: Entity): string { - return ( - entity.metadata?.title || - humanizeEntityRef(entity, { + if (options?.entityPresentationApi) { + return options.entityPresentationApi.forEntity(entity, { defaultKind: options?.defaultKind, - }) - ); + }).snapshot.primaryTitle; + } + return defaultEntityPresentation(entity, { + defaultKind: options?.defaultKind, + }).primaryTitle; } return { @@ -174,7 +178,7 @@ export const columnFactories = Object.freeze({ label={t} size="small" variant="outlined" - style={{ marginBottom: '0px' }} + style={{ margin: '2px' }} /> ))} diff --git a/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.tsx b/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.tsx index 72f6d5b85b..0bd2e9e094 100644 --- a/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.tsx +++ b/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.tsx @@ -23,10 +23,15 @@ import { TableColumn, TableOptions, } from '@backstage/core-components'; +import { + EntityRelationCard, + EntityColumnConfig, + entityColumnPresets, +} from '@backstage/plugin-catalog-react/alpha'; import { asComponentEntities, componentEntityColumns, - componentEntityHelpLink, + componentEntityHelpLink as legacyHelpLink, RelatedEntitiesCard, } from '../RelatedEntitiesCard'; import { catalogTranslationRef } from '../../alpha/translation'; @@ -34,33 +39,76 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ export interface DependencyOfComponentsCardProps { - variant?: InfoCardVariants; title?: string; + columnConfig?: EntityColumnConfig[]; +} + +/** + * Props for the legacy MUI-based rendering. + * @deprecated Use {@link DependencyOfComponentsCardProps} instead. + * @public + */ +export interface DependencyOfComponentsCardLegacyProps { + title?: string; + /** @deprecated Use `columnConfig` instead. */ + variant?: InfoCardVariants; + /** @deprecated Use `columnConfig` instead. */ columns?: TableColumn[]; + /** @deprecated Use `columnConfig` instead. */ tableOptions?: TableOptions; } +function isLegacyProps( + props: + | DependencyOfComponentsCardProps + | DependencyOfComponentsCardLegacyProps, +): props is DependencyOfComponentsCardLegacyProps { + return 'variant' in props || 'columns' in props || 'tableOptions' in props; +} + export function DependencyOfComponentsCard( - props: DependencyOfComponentsCardProps, + props: + | DependencyOfComponentsCardProps + | DependencyOfComponentsCardLegacyProps, ) { const { t } = useTranslationRef(catalogTranslationRef); + + if (isLegacyProps(props)) { + const { + variant = 'gridItem', + title = t('dependencyOfComponentsCard.title'), + columns = componentEntityColumns, + tableOptions = {}, + } = props; + return ( + + ); + } + const { - variant = 'gridItem', title = t('dependencyOfComponentsCard.title'), - columns = componentEntityColumns, - tableOptions = {}, + columnConfig = entityColumnPresets.component.columns, } = props; return ( - ); } diff --git a/plugins/catalog/src/components/DependencyOfComponentsCard/index.ts b/plugins/catalog/src/components/DependencyOfComponentsCard/index.ts index 3d324e865d..a7067237ff 100644 --- a/plugins/catalog/src/components/DependencyOfComponentsCard/index.ts +++ b/plugins/catalog/src/components/DependencyOfComponentsCard/index.ts @@ -15,4 +15,7 @@ */ export { DependencyOfComponentsCard } from './DependencyOfComponentsCard'; -export type { DependencyOfComponentsCardProps } from './DependencyOfComponentsCard'; +export type { + DependencyOfComponentsCardLegacyProps, + DependencyOfComponentsCardProps, +} from './DependencyOfComponentsCard'; diff --git a/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.tsx b/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.tsx index 7276e3bf08..f92e917d62 100644 --- a/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.tsx +++ b/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.tsx @@ -20,10 +20,15 @@ import { TableColumn, TableOptions, } from '@backstage/core-components'; +import { + EntityRelationCard, + EntityColumnConfig, + entityColumnPresets, +} from '@backstage/plugin-catalog-react/alpha'; import { asComponentEntities, componentEntityColumns, - componentEntityHelpLink, + componentEntityHelpLink as legacyHelpLink, RelatedEntitiesCard, } from '../RelatedEntitiesCard'; import { catalogTranslationRef } from '../../alpha/translation'; @@ -31,31 +36,72 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ export interface DependsOnComponentsCardProps { - variant?: InfoCardVariants; title?: string; + columnConfig?: EntityColumnConfig[]; +} + +/** + * Props for the legacy MUI-based rendering. + * @deprecated Use {@link DependsOnComponentsCardProps} instead. + * @public + */ +export interface DependsOnComponentsCardLegacyProps { + title?: string; + /** @deprecated Use `columnConfig` instead. */ + variant?: InfoCardVariants; + /** @deprecated Use `columnConfig` instead. */ columns?: TableColumn[]; + /** @deprecated Use `columnConfig` instead. */ tableOptions?: TableOptions; } -export function DependsOnComponentsCard(props: DependsOnComponentsCardProps) { +function isLegacyProps( + props: DependsOnComponentsCardProps | DependsOnComponentsCardLegacyProps, +): props is DependsOnComponentsCardLegacyProps { + return 'variant' in props || 'columns' in props || 'tableOptions' in props; +} + +export function DependsOnComponentsCard( + props: DependsOnComponentsCardProps | DependsOnComponentsCardLegacyProps, +) { const { t } = useTranslationRef(catalogTranslationRef); + + if (isLegacyProps(props)) { + const { + variant = 'gridItem', + title = t('dependsOnComponentsCard.title'), + columns = componentEntityColumns, + tableOptions = {}, + } = props; + return ( + + ); + } + const { - variant = 'gridItem', title = t('dependsOnComponentsCard.title'), - columns = componentEntityColumns, - tableOptions = {}, + columnConfig = entityColumnPresets.component.columns, } = props; return ( - ); } diff --git a/plugins/catalog/src/components/DependsOnComponentsCard/index.ts b/plugins/catalog/src/components/DependsOnComponentsCard/index.ts index 8292526270..f62d7995aa 100644 --- a/plugins/catalog/src/components/DependsOnComponentsCard/index.ts +++ b/plugins/catalog/src/components/DependsOnComponentsCard/index.ts @@ -15,4 +15,7 @@ */ export { DependsOnComponentsCard } from './DependsOnComponentsCard'; -export type { DependsOnComponentsCardProps } from './DependsOnComponentsCard'; +export type { + DependsOnComponentsCardLegacyProps, + DependsOnComponentsCardProps, +} from './DependsOnComponentsCard'; diff --git a/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.tsx b/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.tsx index 64153e98af..7d8ff6a650 100644 --- a/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.tsx +++ b/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.tsx @@ -20,42 +20,88 @@ import { TableColumn, TableOptions, } from '@backstage/core-components'; +import { + EntityRelationCard, + EntityColumnConfig, + entityColumnPresets, +} from '@backstage/plugin-catalog-react/alpha'; import { asResourceEntities, - componentEntityHelpLink, - RelatedEntitiesCard, + componentEntityHelpLink as legacyHelpLink, resourceEntityColumns, + RelatedEntitiesCard, } from '../RelatedEntitiesCard'; import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ export interface DependsOnResourcesCardProps { - variant?: InfoCardVariants; title?: string; + columnConfig?: EntityColumnConfig[]; +} + +/** + * Props for the legacy MUI-based rendering. + * @deprecated Use {@link DependsOnResourcesCardProps} instead. + * @public + */ +export interface DependsOnResourcesCardLegacyProps { + title?: string; + /** @deprecated Use `columnConfig` instead. */ + variant?: InfoCardVariants; + /** @deprecated Use `columnConfig` instead. */ columns?: TableColumn[]; + /** @deprecated Use `columnConfig` instead. */ tableOptions?: TableOptions; } -export function DependsOnResourcesCard(props: DependsOnResourcesCardProps) { +function isLegacyProps( + props: DependsOnResourcesCardProps | DependsOnResourcesCardLegacyProps, +): props is DependsOnResourcesCardLegacyProps { + return 'variant' in props || 'columns' in props || 'tableOptions' in props; +} + +export function DependsOnResourcesCard( + props: DependsOnResourcesCardProps | DependsOnResourcesCardLegacyProps, +) { const { t } = useTranslationRef(catalogTranslationRef); + + if (isLegacyProps(props)) { + const { + variant = 'gridItem', + title = t('dependsOnResourcesCard.title'), + columns = resourceEntityColumns, + tableOptions = {}, + } = props; + return ( + + ); + } + const { - variant = 'gridItem', title = t('dependsOnResourcesCard.title'), - columns = resourceEntityColumns, - tableOptions = {}, + columnConfig = entityColumnPresets.resource.columns, } = props; return ( - ); } diff --git a/plugins/catalog/src/components/DependsOnResourcesCard/index.ts b/plugins/catalog/src/components/DependsOnResourcesCard/index.ts index 8d638e16a2..1384df4da3 100644 --- a/plugins/catalog/src/components/DependsOnResourcesCard/index.ts +++ b/plugins/catalog/src/components/DependsOnResourcesCard/index.ts @@ -15,4 +15,7 @@ */ export { DependsOnResourcesCard } from './DependsOnResourcesCard'; -export type { DependsOnResourcesCardProps } from './DependsOnResourcesCard'; +export type { + DependsOnResourcesCardLegacyProps, + DependsOnResourcesCardProps, +} from './DependsOnResourcesCard'; diff --git a/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.tsx b/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.tsx index 2d9c392322..23dc9c1efb 100644 --- a/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.tsx +++ b/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.tsx @@ -161,7 +161,7 @@ export function EntityContextMenu(props: EntityContextMenuProps) { <> ({ - key: { - fontWeight: 'bold', - }, -})); +interface LabelItem extends TableItem { + id: string; + key: string; + value: string; +} export const EntityLabelsCard = (props: EntityLabelsCardProps) => { - const { variant, title } = props; + const { title } = props; const { entity } = useEntity(); - const classes = useStyles(); const { t } = useTranslationRef(catalogTranslationRef); - const columns: TableColumn<{ key: string; value: string }>[] = [ - { - render: row => { - return ( - - {row.key} - - ); - }, - }, - { - field: 'value', - }, - ]; - const labels = entity?.metadata?.labels; + const columnConfig: ColumnConfig[] = useMemo( + () => [ + { + id: 'key', + label: t('entityLabelsCard.columnKeyLabel'), + isRowHeader: true, + cell: item => , + }, + { + id: 'value', + label: t('entityLabelsCard.columnValueLabel'), + cell: item => , + }, + ], + [t], + ); + + const data = useMemo( + () => + Object.keys(labels ?? {}).map(labelKey => ({ + id: labelKey, + key: labelKey, + value: labels![labelKey], + })), + [labels], + ); + + const { tableProps } = useTable({ + mode: 'complete', + data, + paginationOptions: { pageSize: 5 }, + }); + return ( - + {!labels || Object.keys(labels).length === 0 ? ( ) : ( -
    ({ - key: labelKey, - value: labels[labelKey], - }))} - options={{ - search: false, - showTitle: true, - loadingType: 'linear', - header: false, - padding: 'dense', - pageSize: 5, - toolbar: false, - paging: Object.keys(labels).length > 5, - }} - /> +
    )} - + ); }; diff --git a/plugins/catalog/src/components/EntityLayout/EntityLayout.test.tsx b/plugins/catalog/src/components/EntityLayout/EntityLayout.test.tsx index 9ae448a9fc..c43db8746c 100644 --- a/plugins/catalog/src/components/EntityLayout/EntityLayout.test.tsx +++ b/plugins/catalog/src/components/EntityLayout/EntityLayout.test.tsx @@ -32,11 +32,11 @@ import { import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils'; import { permissionApiRef } from '@backstage/plugin-permission-react'; import { - mockApis, renderInTestApp, TestApiProvider, TestApiRegistry, } from '@backstage/test-utils'; +import { mockApis } from '@backstage/frontend-test-utils'; import { act, fireEvent, screen, waitFor } from '@testing-library/react'; import { EntityLayout } from './EntityLayout'; import { rootRouteRef, unregisterRedirectRouteRef } from '../../routes'; @@ -52,7 +52,7 @@ describe('EntityLayout', () => { const apis = TestApiRegistry.from( [catalogApiRef, catalogApiMock()], - [alertApiRef, {} as AlertApi], + [alertApiRef, mockApis.alert()], [starredEntitiesApiRef, new MockStarredEntitiesApi()], [permissionApiRef, mockApis.permission()], ); diff --git a/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx b/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx index 65f904c285..5ee6a7f0f1 100644 --- a/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx +++ b/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx @@ -389,11 +389,14 @@ export const EntityLayout = (props: EntityLayoutProps) => { NotFoundComponent ) : ( - There is no {kind} with the requested{' '} - - kind, namespace, and name - - . + {t('entityPage.notFoundMessage', { + kind, + link: ( + + {t('entityPage.notFoundLinkText')} + + ), + })} )} diff --git a/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx b/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx index 39088b0a16..e090effb55 100644 --- a/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx +++ b/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx @@ -14,24 +14,22 @@ * limitations under the License. */ -import { useEntity } from '@backstage/plugin-catalog-react'; +import { useEntity, EntityInfoCard } from '@backstage/plugin-catalog-react'; import LanguageIcon from '@material-ui/icons/Language'; import { EntityLinksEmptyState } from './EntityLinksEmptyState'; import { LinksGridList } from './LinksGridList'; import { ColumnBreakpoints } from './types'; import { IconComponent, useApp } from '@backstage/core-plugin-api'; -import { InfoCard, InfoCardVariants } from '@backstage/core-components'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { catalogTranslationRef } from '../../alpha/translation'; /** @public */ export interface EntityLinksCardProps { cols?: ColumnBreakpoints | number; - variant?: InfoCardVariants; } export const EntityLinksCard = (props: EntityLinksCardProps) => { - const { cols = undefined, variant } = props; + const { cols = undefined } = props; const { entity } = useEntity(); const app = useApp(); const { t } = useTranslationRef(catalogTranslationRef); @@ -42,7 +40,7 @@ export const EntityLinksCard = (props: EntityLinksCardProps) => { const links = entity?.metadata?.links; return ( - + {!links || links.length === 0 ? ( ) : ( @@ -55,6 +53,6 @@ export const EntityLinksCard = (props: EntityLinksCardProps) => { }))} /> )} - + ); }; diff --git a/plugins/catalog/src/components/EntityLinksCard/LinksGridList.tsx b/plugins/catalog/src/components/EntityLinksCard/LinksGridList.tsx index 797428df33..dfb0b280ff 100644 --- a/plugins/catalog/src/components/EntityLinksCard/LinksGridList.tsx +++ b/plugins/catalog/src/components/EntityLinksCard/LinksGridList.tsx @@ -14,8 +14,7 @@ * limitations under the License. */ -import ImageList from '@material-ui/core/ImageList'; -import ImageListItem from '@material-ui/core/ImageListItem'; +import { Grid, type Columns } from '@backstage/ui'; import { IconLink } from './IconLink'; import { ColumnBreakpoints } from './types'; import { useDynamicColumns } from './useDynamicColumns'; @@ -37,12 +36,13 @@ export function LinksGridList(props: LinksGridListProps) { const numOfCols = useDynamicColumns(cols); return ( - + {items.map(({ text, href, Icon }, i) => ( - - - + ))} - + ); } diff --git a/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.test.tsx b/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.test.tsx index 80d97a2e1e..40085d195d 100644 --- a/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.test.tsx +++ b/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.test.tsx @@ -22,13 +22,11 @@ import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; import { screen, waitFor } from '@testing-library/react'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; -import { AlertApi, alertApiRef } from '@backstage/core-plugin-api'; +import { alertApiRef } from '@backstage/core-plugin-api'; +import { mockApis } from '@backstage/frontend-test-utils'; describe('DeleteEntityDialog', () => { - const alertApi: jest.Mocked = { - post: jest.fn(), - alert$: jest.fn(), - }; + const alertApi = mockApis.alert(); const catalogClient = catalogApiMock.mock(); @@ -123,7 +121,9 @@ describe('DeleteEntityDialog', () => { await waitFor(() => { expect(catalogClient.removeEntityByUid).toHaveBeenCalledWith('123'); - expect(alertApi.post).toHaveBeenCalledWith({ message: 'no no no' }); + expect(alertApi.getAlerts()).toContainEqual( + expect.objectContaining({ message: 'no no no' }), + ); }); }); }); diff --git a/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.tsx b/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.tsx index e435ac2c48..6c3f5b4117 100644 --- a/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.tsx +++ b/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.tsx @@ -16,15 +16,12 @@ import { Entity } from '@backstage/catalog-model'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; -import Button from '@material-ui/core/Button'; -import Dialog from '@material-ui/core/Dialog'; -import DialogActions from '@material-ui/core/DialogActions'; -import DialogTitle from '@material-ui/core/DialogTitle'; import { useState } from 'react'; import { alertApiRef, useApi } from '@backstage/core-plugin-api'; -import { assertError } from '@backstage/errors'; +import { toError } from '@backstage/errors'; import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { Button, Dialog, DialogFooter, DialogHeader } from '@backstage/ui'; interface DeleteEntityDialogProps { open: boolean; @@ -47,31 +44,23 @@ export function DeleteEntityDialog(props: DeleteEntityDialogProps) { await catalogApi.removeEntityByUid(uid!); onConfirm(); } catch (err) { - assertError(err); - alertApi.post({ message: err.message }); + alertApi.post({ message: toError(err).message }); } finally { setBusy(false); } }; return ( - - - {t('deleteEntity.dialogTitle')} - - - - - + + ); } diff --git a/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.test.tsx b/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.test.tsx index bcf75ad08b..0b9f5889a0 100644 --- a/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.test.tsx +++ b/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.test.tsx @@ -61,8 +61,11 @@ describe('', () => { ); expect( screen.getByText( - 'This entity is not referenced by any location and is therefore not receiving updates. Click here to delete.', + 'This entity is not referenced by any location and is therefore not receiving updates.', ), ).toBeInTheDocument(); + expect( + screen.getByRole('button', { name: 'Delete entity' }), + ).toBeInTheDocument(); }); }); diff --git a/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.tsx b/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.tsx index 582e1a0a44..c179cd8662 100644 --- a/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.tsx +++ b/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.tsx @@ -16,7 +16,6 @@ import { Entity } from '@backstage/catalog-model'; import { useEntity } from '@backstage/plugin-catalog-react'; -import Alert from '@material-ui/lab/Alert'; import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { DeleteEntityDialog } from './DeleteEntityDialog'; @@ -24,6 +23,7 @@ import { useRouteRef } from '@backstage/core-plugin-api'; import { rootRouteRef } from '../../routes'; import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { Alert, Button } from '@backstage/ui'; /** * Returns true if the given entity has the orphan annotation given by the @@ -55,9 +55,21 @@ export function EntityOrphanWarning() { return ( <> - setConfirmationDialogOpen(true)}> - {t('deleteEntity.description')} - + setConfirmationDialogOpen(true)} + > + {t('deleteEntity.actionButtonTitle')} + + } + /> i.error && @@ -100,7 +100,7 @@ export function EntityProcessingErrorsPanel() { if (error) { return ( - + ); @@ -113,10 +113,10 @@ export function EntityProcessingErrorsPanel() { return ( <> {value.items.map((ancestorError, index) => ( - + {stringifyEntityRef(entity) !== stringifyEntityRef(ancestorError.entity) && ( - + {t('entityProcessingErrorsDescription')}{' '} diff --git a/plugins/catalog/src/components/EntityRelationWarning/EntityRelationWarning.tsx b/plugins/catalog/src/components/EntityRelationWarning/EntityRelationWarning.tsx index b69993e190..ce50392173 100644 --- a/plugins/catalog/src/components/EntityRelationWarning/EntityRelationWarning.tsx +++ b/plugins/catalog/src/components/EntityRelationWarning/EntityRelationWarning.tsx @@ -20,13 +20,12 @@ import { catalogApiRef, useEntity, } from '@backstage/plugin-catalog-react'; -import Alert from '@material-ui/lab/Alert'; import useAsync from 'react-use/esm/useAsync'; -import Box from '@material-ui/core/Box'; import { ResponseErrorPanel } from '@backstage/core-components'; import { useApi, ApiHolder } from '@backstage/core-plugin-api'; import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { Alert, Box } from '@backstage/ui'; async function getRelationWarnings(entity: Entity, catalogApi: CatalogApi) { const entityRefRelations = entity.relations?.map( @@ -85,7 +84,7 @@ export function EntityRelationWarning() { if (error) { return ( - + ); @@ -96,10 +95,15 @@ export function EntityRelationWarning() { } return ( - <> - - {t('entityRelationWarningDescription')} {value.join(', ')} - - + + {t('entityRelationWarningDescription')} {value.join(', ')} + + } + /> ); } diff --git a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx index 66affd86f1..cb22475be6 100644 --- a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx +++ b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx @@ -23,15 +23,12 @@ import { render, screen } from '@testing-library/react'; import { ReactNode, useEffect } from 'react'; import { isKind } from './conditions'; import { EntitySwitch } from './EntitySwitch'; -import { featureFlagsApiRef } from '@backstage/core-plugin-api'; -import { LocalStorageFeatureFlags } from '@backstage/core-app-api'; -import { TestApiProvider } from '@backstage/test-utils'; +import { TestApiProvider, mockApis } from '@backstage/frontend-test-utils'; + +const mockFeatureFlagsApi = mockApis.featureFlags.mock(); -const mockFeatureFlagsApi = new LocalStorageFeatureFlags(); const Wrapper = ({ children }: { children?: ReactNode }) => ( - - {children} - + {children} ); describe('EntitySwitch', () => { diff --git a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx index 2917cdf1e1..c41eaf865d 100644 --- a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx +++ b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx @@ -20,10 +20,15 @@ import { TableColumn, TableOptions, } from '@backstage/core-components'; +import { + EntityRelationCard, + EntityColumnConfig, + entityColumnPresets, +} from '@backstage/plugin-catalog-react/alpha'; import { asComponentEntities, componentEntityColumns, - componentEntityHelpLink, + componentEntityHelpLink as legacyHelpLink, RelatedEntitiesCard, } from '../RelatedEntitiesCard'; import { catalogTranslationRef } from '../../alpha/translation'; @@ -31,31 +36,72 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ export interface HasComponentsCardProps { - variant?: InfoCardVariants; title?: string; + columnConfig?: EntityColumnConfig[]; +} + +/** + * Props for the legacy MUI-based rendering. + * @deprecated Use {@link HasComponentsCardProps} instead. + * @public + */ +export interface HasComponentsCardLegacyProps { + title?: string; + /** @deprecated Use `columnConfig` instead. */ + variant?: InfoCardVariants; + /** @deprecated Use `columnConfig` instead. */ columns?: TableColumn[]; + /** @deprecated Use `columnConfig` instead. */ tableOptions?: TableOptions; } -export function HasComponentsCard(props: HasComponentsCardProps) { +function isLegacyProps( + props: HasComponentsCardProps | HasComponentsCardLegacyProps, +): props is HasComponentsCardLegacyProps { + return 'variant' in props || 'columns' in props || 'tableOptions' in props; +} + +export function HasComponentsCard( + props: HasComponentsCardProps | HasComponentsCardLegacyProps, +) { const { t } = useTranslationRef(catalogTranslationRef); + + if (isLegacyProps(props)) { + const { + variant = 'gridItem', + title = t('hasComponentsCard.title'), + columns = componentEntityColumns, + tableOptions = {}, + } = props; + return ( + + ); + } + const { - variant = 'gridItem', title = t('hasComponentsCard.title'), - columns = componentEntityColumns, - tableOptions = {}, + columnConfig = entityColumnPresets.component.columns, } = props; return ( - ); } diff --git a/plugins/catalog/src/components/HasComponentsCard/index.ts b/plugins/catalog/src/components/HasComponentsCard/index.ts index 86038b6030..96278c4513 100644 --- a/plugins/catalog/src/components/HasComponentsCard/index.ts +++ b/plugins/catalog/src/components/HasComponentsCard/index.ts @@ -15,4 +15,7 @@ */ export { HasComponentsCard } from './HasComponentsCard'; -export type { HasComponentsCardProps } from './HasComponentsCard'; +export type { + HasComponentsCardLegacyProps, + HasComponentsCardProps, +} from './HasComponentsCard'; diff --git a/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx index 81cd432290..34659c4930 100644 --- a/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx +++ b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx @@ -20,42 +20,88 @@ import { TableColumn, TableOptions, } from '@backstage/core-components'; +import { + EntityRelationCard, + EntityColumnConfig, + entityColumnPresets, +} from '@backstage/plugin-catalog-react/alpha'; import { asResourceEntities, - RelatedEntitiesCard, resourceEntityColumns, - resourceEntityHelpLink, + resourceEntityHelpLink as legacyHelpLink, + RelatedEntitiesCard, } from '../RelatedEntitiesCard'; import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ export interface HasResourcesCardProps { - variant?: InfoCardVariants; title?: string; + columnConfig?: EntityColumnConfig[]; +} + +/** + * Props for the legacy MUI-based rendering. + * @deprecated Use {@link HasResourcesCardProps} instead. + * @public + */ +export interface HasResourcesCardLegacyProps { + title?: string; + /** @deprecated Use `columnConfig` instead. */ + variant?: InfoCardVariants; + /** @deprecated Use `columnConfig` instead. */ columns?: TableColumn[]; + /** @deprecated Use `columnConfig` instead. */ tableOptions?: TableOptions; } -export function HasResourcesCard(props: HasResourcesCardProps) { +function isLegacyProps( + props: HasResourcesCardProps | HasResourcesCardLegacyProps, +): props is HasResourcesCardLegacyProps { + return 'variant' in props || 'columns' in props || 'tableOptions' in props; +} + +export function HasResourcesCard( + props: HasResourcesCardProps | HasResourcesCardLegacyProps, +) { const { t } = useTranslationRef(catalogTranslationRef); + + if (isLegacyProps(props)) { + const { + variant = 'gridItem', + title = t('hasResourcesCard.title'), + columns = resourceEntityColumns, + tableOptions = {}, + } = props; + return ( + + ); + } + const { - variant = 'gridItem', title = t('hasResourcesCard.title'), - columns = resourceEntityColumns, - tableOptions = {}, + columnConfig = entityColumnPresets.resource.columns, } = props; return ( - ); } diff --git a/plugins/catalog/src/components/HasResourcesCard/index.ts b/plugins/catalog/src/components/HasResourcesCard/index.ts index cdbbef680b..171808d75c 100644 --- a/plugins/catalog/src/components/HasResourcesCard/index.ts +++ b/plugins/catalog/src/components/HasResourcesCard/index.ts @@ -15,4 +15,7 @@ */ export { HasResourcesCard } from './HasResourcesCard'; -export type { HasResourcesCardProps } from './HasResourcesCard'; +export type { + HasResourcesCardLegacyProps, + HasResourcesCardProps, +} from './HasResourcesCard'; diff --git a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx index 84087ed082..7307379d5d 100644 --- a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx +++ b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx @@ -20,6 +20,11 @@ import { TableColumn, TableOptions, } from '@backstage/core-components'; +import { + EntityRelationCard, + EntityColumnConfig, + entityColumnPresets, +} from '@backstage/plugin-catalog-react/alpha'; import { asComponentEntities, componentEntityColumns, @@ -30,33 +35,77 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ export interface HasSubcomponentsCardProps { - variant?: InfoCardVariants; title?: string; + columnConfig?: EntityColumnConfig[]; + kind?: string; +} + +/** + * Props for the legacy MUI-based rendering. + * @deprecated Use {@link HasSubcomponentsCardProps} instead. + * @public + */ +export interface HasSubcomponentsCardLegacyProps { + title?: string; + /** @deprecated Use `columnConfig` instead. */ + variant?: InfoCardVariants; + /** @deprecated Use `columnConfig` instead. */ columns?: TableColumn[]; + /** @deprecated Use `columnConfig` instead. */ tableOptions?: TableOptions; kind?: string; } -export function HasSubcomponentsCard(props: HasSubcomponentsCardProps) { +function isLegacyProps( + props: HasSubcomponentsCardProps | HasSubcomponentsCardLegacyProps, +): props is HasSubcomponentsCardLegacyProps { + return 'variant' in props || 'columns' in props || 'tableOptions' in props; +} + +export function HasSubcomponentsCard( + props: HasSubcomponentsCardProps | HasSubcomponentsCardLegacyProps, +) { const { t } = useTranslationRef(catalogTranslationRef); + + if (isLegacyProps(props)) { + const { + variant = 'gridItem', + title = t('hasSubcomponentsCard.title'), + columns = componentEntityColumns, + tableOptions = {}, + kind = 'Component', + } = props; + return ( + + ); + } + const { - variant = 'gridItem', title = t('hasSubcomponentsCard.title'), - columns = componentEntityColumns, - tableOptions = {}, + columnConfig = entityColumnPresets.component.columns, kind = 'Component', } = props; return ( - ); } diff --git a/plugins/catalog/src/components/HasSubcomponentsCard/index.ts b/plugins/catalog/src/components/HasSubcomponentsCard/index.ts index 6f43967d50..44a6faf125 100644 --- a/plugins/catalog/src/components/HasSubcomponentsCard/index.ts +++ b/plugins/catalog/src/components/HasSubcomponentsCard/index.ts @@ -15,4 +15,7 @@ */ export { HasSubcomponentsCard } from './HasSubcomponentsCard'; -export type { HasSubcomponentsCardProps } from './HasSubcomponentsCard'; +export type { + HasSubcomponentsCardLegacyProps, + HasSubcomponentsCardProps, +} from './HasSubcomponentsCard'; diff --git a/plugins/catalog/src/components/HasSubdomainsCard/HasSubdomainsCard.tsx b/plugins/catalog/src/components/HasSubdomainsCard/HasSubdomainsCard.tsx index 367dbe6c94..63c349ec60 100644 --- a/plugins/catalog/src/components/HasSubdomainsCard/HasSubdomainsCard.tsx +++ b/plugins/catalog/src/components/HasSubdomainsCard/HasSubdomainsCard.tsx @@ -20,10 +20,15 @@ import { TableColumn, TableOptions, } from '@backstage/core-components'; +import { + EntityRelationCard, + EntityColumnConfig, + entityColumnPresets, +} from '@backstage/plugin-catalog-react/alpha'; import { asDomainEntities, domainEntityColumns, - domainEntityHelpLink, + domainEntityHelpLink as legacyHelpLink, RelatedEntitiesCard, } from '../RelatedEntitiesCard'; import { catalogTranslationRef } from '../../alpha/translation'; @@ -31,31 +36,72 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ export interface HasSubdomainsCardProps { - variant?: InfoCardVariants; - tableOptions?: TableOptions; - columns?: TableColumn[]; title?: string; + columnConfig?: EntityColumnConfig[]; } -export function HasSubdomainsCard(props: HasSubdomainsCardProps) { +/** + * Props for the legacy MUI-based rendering. + * @deprecated Use {@link HasSubdomainsCardProps} instead. + * @public + */ +export interface HasSubdomainsCardLegacyProps { + title?: string; + /** @deprecated Use `columnConfig` instead. */ + variant?: InfoCardVariants; + /** @deprecated Use `columnConfig` instead. */ + tableOptions?: TableOptions; + /** @deprecated Use `columnConfig` instead. */ + columns?: TableColumn[]; +} + +function isLegacyProps( + props: HasSubdomainsCardProps | HasSubdomainsCardLegacyProps, +): props is HasSubdomainsCardLegacyProps { + return 'variant' in props || 'columns' in props || 'tableOptions' in props; +} + +export function HasSubdomainsCard( + props: HasSubdomainsCardProps | HasSubdomainsCardLegacyProps, +) { const { t } = useTranslationRef(catalogTranslationRef); + + if (isLegacyProps(props)) { + const { + variant = 'gridItem', + title = t('hasSubdomainsCard.title'), + columns = domainEntityColumns, + tableOptions = {}, + } = props; + return ( + + ); + } + const { - variant = 'gridItem', title = t('hasSubdomainsCard.title'), - columns = domainEntityColumns, - tableOptions = {}, + columnConfig = entityColumnPresets.domain.columns, } = props; return ( - ); } diff --git a/plugins/catalog/src/components/HasSubdomainsCard/index.ts b/plugins/catalog/src/components/HasSubdomainsCard/index.ts index a740abbcc2..57353f4226 100644 --- a/plugins/catalog/src/components/HasSubdomainsCard/index.ts +++ b/plugins/catalog/src/components/HasSubdomainsCard/index.ts @@ -15,4 +15,7 @@ */ export { HasSubdomainsCard } from './HasSubdomainsCard'; -export type { HasSubdomainsCardProps } from './HasSubdomainsCard'; +export type { + HasSubdomainsCardLegacyProps, + HasSubdomainsCardProps, +} from './HasSubdomainsCard'; diff --git a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx index fab9cf4769..1234c45d6f 100644 --- a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx +++ b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx @@ -20,42 +20,88 @@ import { TableColumn, TableOptions, } from '@backstage/core-components'; +import { + EntityRelationCard, + EntityColumnConfig, + entityColumnPresets, +} from '@backstage/plugin-catalog-react/alpha'; import { asSystemEntities, - RelatedEntitiesCard, systemEntityColumns, - systemEntityHelpLink, + systemEntityHelpLink as legacyHelpLink, + RelatedEntitiesCard, } from '../RelatedEntitiesCard'; import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ export interface HasSystemsCardProps { - variant?: InfoCardVariants; title?: string; + columnConfig?: EntityColumnConfig[]; +} + +/** + * Props for the legacy MUI-based rendering. + * @deprecated Use {@link HasSystemsCardProps} instead. + * @public + */ +export interface HasSystemsCardLegacyProps { + title?: string; + /** @deprecated Use `columnConfig` instead. */ + variant?: InfoCardVariants; + /** @deprecated Use `columnConfig` instead. */ columns?: TableColumn[]; + /** @deprecated Use `columnConfig` instead. */ tableOptions?: TableOptions; } -export function HasSystemsCard(props: HasSystemsCardProps) { +function isLegacyProps( + props: HasSystemsCardProps | HasSystemsCardLegacyProps, +): props is HasSystemsCardLegacyProps { + return 'variant' in props || 'columns' in props || 'tableOptions' in props; +} + +export function HasSystemsCard( + props: HasSystemsCardProps | HasSystemsCardLegacyProps, +) { const { t } = useTranslationRef(catalogTranslationRef); + + if (isLegacyProps(props)) { + const { + variant = 'gridItem', + title = t('hasSystemsCard.title'), + columns = systemEntityColumns, + tableOptions = {}, + } = props; + return ( + + ); + } + const { - variant = 'gridItem', title = t('hasSystemsCard.title'), - columns = systemEntityColumns, - tableOptions = {}, + columnConfig = entityColumnPresets.system.columns, } = props; return ( - ); } diff --git a/plugins/catalog/src/components/HasSystemsCard/index.ts b/plugins/catalog/src/components/HasSystemsCard/index.ts index 71fb7539b1..fd5c204cdd 100644 --- a/plugins/catalog/src/components/HasSystemsCard/index.ts +++ b/plugins/catalog/src/components/HasSystemsCard/index.ts @@ -15,4 +15,7 @@ */ export { HasSystemsCard } from './HasSystemsCard'; -export type { HasSystemsCardProps } from './HasSystemsCard'; +export type { + HasSystemsCardLegacyProps, + HasSystemsCardProps, +} from './HasSystemsCard'; diff --git a/plugins/catalog/src/components/RelatedEntitiesCard/RelatedEntitiesCard.tsx b/plugins/catalog/src/components/RelatedEntitiesCard/RelatedEntitiesCard.tsx index 1337a6dc12..e07a949965 100644 --- a/plugins/catalog/src/components/RelatedEntitiesCard/RelatedEntitiesCard.tsx +++ b/plugins/catalog/src/components/RelatedEntitiesCard/RelatedEntitiesCard.tsx @@ -70,6 +70,7 @@ export type RelatedEntitiesCardProps = { * this card as its implementation with some of the props set to the appropriate * values. * + * @deprecated Use {@link @backstage/plugin-catalog-react/alpha#EntityRelationCard} instead. * @public */ export const RelatedEntitiesCard = ( diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts index f3cc7d3d0b..2c1e81f803 100644 --- a/plugins/catalog/src/index.ts +++ b/plugins/catalog/src/index.ts @@ -23,7 +23,6 @@ export * from './apis'; export type { - AboutCardProps, AboutContentProps, AboutFieldProps, } from './components/AboutCard'; @@ -58,9 +57,18 @@ export { CatalogSearchResultListItem, } from './plugin'; -export type { DependencyOfComponentsCardProps } from './components/DependencyOfComponentsCard'; -export type { DependsOnComponentsCardProps } from './components/DependsOnComponentsCard'; -export type { DependsOnResourcesCardProps } from './components/DependsOnResourcesCard'; +export type { + DependencyOfComponentsCardLegacyProps, + DependencyOfComponentsCardProps, +} from './components/DependencyOfComponentsCard'; +export type { + DependsOnComponentsCardLegacyProps, + DependsOnComponentsCardProps, +} from './components/DependsOnComponentsCard'; +export type { + DependsOnResourcesCardLegacyProps, + DependsOnResourcesCardProps, +} from './components/DependsOnResourcesCard'; export type { EntityLinksEmptyStateClassKey, EntityLinksCardProps, @@ -70,10 +78,26 @@ export type { export type { SystemDiagramCardClassKey } from './components/SystemDiagramCard'; export type { DefaultCatalogPageProps } from './components/CatalogPage'; export type { EntityContextMenuClassKey } from './components/EntityContextMenu'; -export type { HasComponentsCardProps } from './components/HasComponentsCard'; -export type { HasResourcesCardProps } from './components/HasResourcesCard'; -export type { HasSubcomponentsCardProps } from './components/HasSubcomponentsCard'; -export type { HasSubdomainsCardProps } from './components/HasSubdomainsCard'; -export type { HasSystemsCardProps } from './components/HasSystemsCard'; +export type { + HasComponentsCardLegacyProps, + HasComponentsCardProps, +} from './components/HasComponentsCard'; +export type { + HasResourcesCardLegacyProps, + HasResourcesCardProps, +} from './components/HasResourcesCard'; +export type { + HasSubcomponentsCardLegacyProps, + HasSubcomponentsCardProps, +} from './components/HasSubcomponentsCard'; +export type { + HasSubdomainsCardLegacyProps, + HasSubdomainsCardProps, +} from './components/HasSubdomainsCard'; +export type { + HasSystemsCardLegacyProps, + HasSystemsCardProps, +} from './components/HasSystemsCard'; export type { RelatedEntitiesCardProps } from './components/RelatedEntitiesCard'; export type { CatalogSearchResultListItemProps } from './components/CatalogSearchResultListItem'; +export { catalogTranslationRef } from './alpha/translation'; diff --git a/plugins/catalog/src/plugin.ts b/plugins/catalog/src/plugin.ts index 50d858a45f..6fe531418b 100644 --- a/plugins/catalog/src/plugin.ts +++ b/plugins/catalog/src/plugin.ts @@ -43,7 +43,6 @@ import { SearchResultListItemExtensionProps, } from '@backstage/plugin-search-react'; import { DefaultStarredEntitiesApi } from './apis'; -import { AboutCardProps } from './components/AboutCard'; import { DefaultCatalogPageProps } from './components/CatalogPage'; import { DependencyOfComponentsCardProps } from './components/DependencyOfComponentsCard'; import { DependsOnComponentsCardProps } from './components/DependsOnComponentsCard'; @@ -128,15 +127,14 @@ export const CatalogEntityPage: () => JSX.Element = catalogPlugin.provide( * not extremely customizable; feel free to make a copy of it as a starting * point if you like. */ -export const EntityAboutCard: (props: AboutCardProps) => JSX.Element = - catalogPlugin.provide( - createComponentExtension({ - name: 'EntityAboutCard', - component: { - lazy: () => import('./components/AboutCard').then(m => m.AboutCard), - }, - }), - ); +export const EntityAboutCard: () => JSX.Element = catalogPlugin.provide( + createComponentExtension({ + name: 'EntityAboutCard', + component: { + lazy: () => import('./components/AboutCard').then(m => m.AboutCard), + }, + }), +); /** @public */ export const EntityLinksCard = catalogPlugin.provide( diff --git a/plugins/config-schema/CHANGELOG.md b/plugins/config-schema/CHANGELOG.md index be1bf61051..902e0cd4d0 100644 --- a/plugins/config-schema/CHANGELOG.md +++ b/plugins/config-schema/CHANGELOG.md @@ -1,5 +1,119 @@ # @backstage/plugin-config-schema +## 0.1.79 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0 + - @backstage/core-components@0.18.9 + - @backstage/core-plugin-api@1.12.5 + +## 0.1.79-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/core-components@0.18.9-next.1 + - @backstage/core-plugin-api@1.12.5-next.2 + +## 0.1.79-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.1.78 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + +## 0.1.78-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.1.77 + +### Patch Changes + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/core-components@0.18.7 + - @backstage/core-plugin-api@1.12.3 + +## 0.1.77-next.1 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + +## 0.1.77-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.6-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.1.76 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5 + +## 0.1.76-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + +## 0.1.75 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + +## 0.1.75-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.1.74 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + ## 0.1.74-next.0 ### Patch Changes diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index af7fd4f06f..9f3032ed74 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-config-schema", - "version": "0.1.74-next.0", + "version": "0.1.79", "description": "A Backstage plugin that lets you browse the configuration schema of your app", "backstage": { "role": "frontend-plugin", @@ -57,13 +57,13 @@ "@types/react": "^18.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/plugins/config-schema/report.api.md b/plugins/config-schema/report.api.md index c78e56aa0e..66c856f4d8 100644 --- a/plugins/config-schema/report.api.md +++ b/plugins/config-schema/report.api.md @@ -3,7 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { ApiRef } from '@backstage/core-plugin-api'; +import { ApiRef } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { JSX as JSX_2 } from 'react/jsx-runtime'; import { Observable } from '@backstage/types'; diff --git a/plugins/devtools-backend/CHANGELOG.md b/plugins/devtools-backend/CHANGELOG.md index 9d89d50392..80e9d4d770 100644 --- a/plugins/devtools-backend/CHANGELOG.md +++ b/plugins/devtools-backend/CHANGELOG.md @@ -1,5 +1,233 @@ # @backstage/plugin-devtools-backend +## 0.5.16 + +### Patch Changes + +- 482ceed: Migrated from `assertError` to `toError` for error handling. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/errors@1.3.0 + - @backstage/cli-common@0.2.1 + - @backstage/config-loader@1.10.10 + - @backstage/plugin-permission-node@0.10.12 + - @backstage/config@1.3.7 + - @backstage/plugin-devtools-common@0.1.24 + - @backstage/plugin-permission-common@0.9.8 + +## 0.5.16-next.2 + +### Patch Changes + +- 482ceed: Migrated from `assertError` to `toError` for error handling. +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/cli-common@0.2.1-next.1 + - @backstage/config-loader@1.10.10-next.1 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/config@1.3.7-next.0 + - @backstage/plugin-permission-common@0.9.8-next.0 + - @backstage/plugin-permission-node@0.10.12-next.2 + - @backstage/plugin-devtools-common@0.1.24-next.0 + +## 0.5.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-permission-node@0.10.12-next.1 + +## 0.5.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.2.1-next.0 + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/config-loader@1.10.10-next.0 + - @backstage/plugin-permission-node@0.10.12-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-devtools-common@0.1.23 + - @backstage/plugin-permission-common@0.9.7 + +## 0.5.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/cli-common@0.2.0 + - @backstage/plugin-devtools-common@0.1.23 + - @backstage/config-loader@1.10.9 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/plugin-permission-node@0.10.11 + +## 0.5.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/cli-common@0.2.0-next.2 + - @backstage/plugin-permission-node@0.10.11-next.1 + +## 0.5.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.2.0-next.0 + - @backstage/config-loader@1.10.9-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-devtools-common@0.1.22 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.11-next.0 + +## 0.5.14 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 8148621: Moved `@backstage/backend-defaults` from `dependencies` to `devDependencies`. +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/config-loader@1.10.8 + - @backstage/cli-common@0.1.18 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.10 + - @backstage/plugin-devtools-common@0.1.22 + +## 0.5.14-next.1 + +### Patch Changes + +- 8148621: Moved `@backstage/backend-defaults` from `dependencies` to `devDependencies`. +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/config-loader@1.10.8-next.0 + - @backstage/plugin-permission-node@0.10.10-next.0 + +## 0.5.14-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/backend-defaults@0.15.1-next.0 + - @backstage/config-loader@1.10.8-next.0 + - @backstage/cli-common@0.1.18-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/plugin-permission-node@0.10.9-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-devtools-common@0.1.22-next.0 + +## 0.5.13 + +### Patch Changes + +- be6cef5: Add support for adding `unprocessed-entities` and other tabs to `devtools` when using the New Frontend system +- Updated dependencies + - @backstage/backend-defaults@0.15.0 + - @backstage/backend-plugin-api@1.6.1 + - @backstage/cli-common@0.1.17 + - @backstage/plugin-devtools-common@0.1.21 + - @backstage/plugin-permission-common@0.9.4 + - @backstage/plugin-permission-node@0.10.8 + +## 0.5.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + - @backstage/plugin-permission-node@0.10.7 + +## 0.5.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.1-next.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/cli-common@0.1.16 + - @backstage/config@1.3.6 + - @backstage/config-loader@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-devtools-common@0.1.20 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-permission-node@0.10.7 + +## 0.5.12 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 291bf9d: Added scheduled tasks UI feature for the DevTools plugin +- Updated dependencies + - @backstage/backend-defaults@0.14.0 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/cli-common@0.1.16 + - @backstage/config-loader@1.10.7 + - @backstage/plugin-devtools-common@0.1.20 + +## 0.5.12-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config-loader@1.10.7-next.1 + - @backstage/cli-common@0.1.16-next.2 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-devtools-common@0.1.19 + - @backstage/plugin-permission-common@0.9.3 + +## 0.5.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/cli-common@0.1.16-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/config@1.3.6 + - @backstage/config-loader@1.10.7-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-devtools-common@0.1.19 + - @backstage/plugin-permission-common@0.9.3 + +## 0.5.11 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-devtools-common@0.1.19 + - @backstage/config-loader@1.10.6 + - @backstage/config@1.3.6 + - @backstage/plugin-permission-node@0.10.6 + ## 0.5.11-next.1 ### Patch Changes diff --git a/plugins/devtools-backend/knip-report.md b/plugins/devtools-backend/knip-report.md index 904730f95f..7464db835f 100644 --- a/plugins/devtools-backend/knip-report.md +++ b/plugins/devtools-backend/knip-report.md @@ -1,10 +1,8 @@ # Knip report -## Unused dependencies (3) +## Unused dependencies (1) -| Name | Location | Severity | -| :-------------------------------- | :----------- | :------- | -| @backstage/plugin-permission-node | plugins/devtools-backend/package.json | error | -| semver | plugins/devtools-backend/package.json | error | -| yn | plugins/devtools-backend/package.json | error | +| Name | Location | Severity | +| :-------------------------------- | :---------------- | :------- | +| @backstage/plugin-permission-node | package.json:49:6 | error | diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json index f823902224..7730a74f57 100644 --- a/plugins/devtools-backend/package.json +++ b/plugins/devtools-backend/package.json @@ -1,13 +1,14 @@ { "name": "@backstage/plugin-devtools-backend", - "version": "0.5.11-next.1", + "version": "0.5.16", "backstage": { "role": "backend-plugin", "pluginId": "devtools", "pluginPackages": [ "@backstage/plugin-devtools", "@backstage/plugin-devtools-backend", - "@backstage/plugin-devtools-common" + "@backstage/plugin-devtools-common", + "@backstage/plugin-devtools-react" ] }, "publishConfig": { @@ -38,7 +39,6 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/backend-defaults": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/cli-common": "workspace:^", "@backstage/config": "workspace:^", @@ -51,13 +51,11 @@ "@manypkg/get-packages": "^1.1.3", "@yarnpkg/lockfile": "^1.1.0", "@yarnpkg/parsers": "^3.0.0", - "express": "^4.18.1", + "express": "^4.22.0", "express-promise-router": "^4.1.0", "fs-extra": "^11.0.0", "lodash": "^4.17.21", - "ping": "^0.4.1", - "semver": "^7.5.3", - "yn": "^4.0.0" + "ping": "^0.4.1" }, "devDependencies": { "@backstage/backend-defaults": "workspace:^", diff --git a/plugins/devtools-backend/src/api/DevToolsBackendApi.ts b/plugins/devtools-backend/src/api/DevToolsBackendApi.ts index 21eedf7b0b..e785dfbb2b 100644 --- a/plugins/devtools-backend/src/api/DevToolsBackendApi.ts +++ b/plugins/devtools-backend/src/api/DevToolsBackendApi.ts @@ -28,11 +28,11 @@ import { JsonObject } from '@backstage/types'; import { findPaths } from '@backstage/cli-common'; import { getPackages } from '@manypkg/get-packages'; import ping from 'ping'; -import os from 'os'; +import os from 'node:os'; import fs from 'fs-extra'; import { Lockfile } from '../util/Lockfile'; import { memoize } from 'lodash'; -import { assertError } from '@backstage/errors'; +import { toError } from '@backstage/errors'; import { LoggerService } from '@backstage/backend-plugin-api'; /** @public */ @@ -179,7 +179,7 @@ export class DevToolsBackendApi { const data = ConfigReader.fromConfigs(sanitizedConfigs).get(); configInfo.config = data; } catch (error) { - assertError(error); + const err = toError(error); // The config is not valid for some reason but we want to be able to see it still const config = { data: this.config.get() as JsonObject, @@ -194,10 +194,10 @@ export class DevToolsBackendApi { const data = ConfigReader.fromConfigs(sanitizedConfigs).get(); configInfo.config = data; configInfo.error = { - name: error.name, - message: error.message, - messages: error.messages as string[] | undefined, - stack: error.stack, + name: err.name, + message: err.message, + messages: err.messages as string[] | undefined, + stack: err.stack, }; } @@ -258,13 +258,3 @@ export class DevToolsBackendApi { return info; } } - -export function isValidUrl(url: string): boolean { - try { - // eslint-disable-next-line no-new - new URL(url); - return true; - } catch { - return false; - } -} diff --git a/plugins/devtools-backend/src/plugin.ts b/plugins/devtools-backend/src/plugin.ts index d3fefda343..d7ac3ff6d9 100644 --- a/plugins/devtools-backend/src/plugin.ts +++ b/plugins/devtools-backend/src/plugin.ts @@ -20,6 +20,10 @@ import { } from '@backstage/backend-plugin-api'; import { createRouter } from './service/router'; import { devToolsPermissions } from '@backstage/plugin-devtools-common'; +import { + devToolsTaskSchedulerReadPermission, + devToolsTaskSchedulerCreatePermission, +} from '@backstage/plugin-devtools-common/alpha'; /** * DevTools backend plugin @@ -61,7 +65,11 @@ export const devtoolsPlugin = createBackendPlugin({ path: '/health', allow: 'unauthenticated', }); - permissionsRegistry.addPermissions(devToolsPermissions); + permissionsRegistry.addPermissions([ + ...devToolsPermissions, + devToolsTaskSchedulerReadPermission, + devToolsTaskSchedulerCreatePermission, + ]); }, }); }, diff --git a/plugins/devtools-common/CHANGELOG.md b/plugins/devtools-common/CHANGELOG.md index 841994b7e2..38fef20e70 100644 --- a/plugins/devtools-common/CHANGELOG.md +++ b/plugins/devtools-common/CHANGELOG.md @@ -1,5 +1,64 @@ # @backstage/plugin-devtools-common +## 0.1.24 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.8 + +## 0.1.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.8-next.0 + +## 0.1.23 + +### Patch Changes + +- f80195e: Added `cancelScheduledTask` to the DevTools API and a cancel button to the scheduled tasks UI. +- Updated dependencies + - @backstage/plugin-permission-common@0.9.7 + +## 0.1.22 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.6 + +## 0.1.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/types@1.2.2 + +## 0.1.21 + +### Patch Changes + +- be6cef5: Add support for adding `unprocessed-entities` and other tabs to `devtools` when using the New Frontend system +- Updated dependencies + - @backstage/plugin-permission-common@0.9.4 + +## 0.1.20 + +### Patch Changes + +- 291bf9d: Added scheduled tasks UI feature for the DevTools plugin + +## 0.1.19 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/plugin-permission-common@0.9.3 + ## 0.1.19-next.1 ### Patch Changes diff --git a/plugins/devtools-common/package.json b/plugins/devtools-common/package.json index 72c5ae7bbc..d50669a4b9 100644 --- a/plugins/devtools-common/package.json +++ b/plugins/devtools-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools-common", - "version": "0.1.19-next.1", + "version": "0.1.24", "description": "Common functionalities for the devtools plugin", "backstage": { "role": "common-library", @@ -8,14 +8,12 @@ "pluginPackages": [ "@backstage/plugin-devtools", "@backstage/plugin-devtools-backend", - "@backstage/plugin-devtools-common" + "@backstage/plugin-devtools-common", + "@backstage/plugin-devtools-react" ] }, "publishConfig": { - "access": "public", - "main": "dist/index.cjs.js", - "module": "dist/index.esm.js", - "types": "dist/index.d.ts" + "access": "public" }, "homepage": "https://backstage.io", "repository": { @@ -25,8 +23,23 @@ }, "license": "Apache-2.0", "sideEffects": false, + "exports": { + ".": "./src/index.ts", + "./alpha": "./src/alpha.ts", + "./package.json": "./package.json" + }, "main": "src/index.ts", "types": "src/index.ts", + "typesVersions": { + "*": { + "alpha": [ + "src/alpha.ts" + ], + "package.json": [ + "package.json" + ] + } + }, "files": [ "dist" ], diff --git a/plugins/devtools-common/report-alpha.api.md b/plugins/devtools-common/report-alpha.api.md new file mode 100644 index 0000000000..3c3b7ff89e --- /dev/null +++ b/plugins/devtools-common/report-alpha.api.md @@ -0,0 +1,69 @@ +## API Report File for "@backstage/plugin-devtools-common" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { BasicPermission } from '@backstage/plugin-permission-common'; +import { JsonObject } from '@backstage/types'; + +// @alpha (undocumented) +export const devToolsTaskSchedulerCreatePermission: BasicPermission; + +// @alpha (undocumented) +export const devToolsTaskSchedulerReadPermission: BasicPermission; + +// @alpha (undocumented) +export type ScheduledTasks = { + scheduledTasks?: TaskApiTasksResponse[]; + error?: string; +}; + +// @alpha +export interface TaskApiTasksResponse { + // (undocumented) + pluginId: string; + // (undocumented) + scope: 'global' | 'local'; + // (undocumented) + settings: { + version: number; + } & JsonObject; + // (undocumented) + taskId: string; + // (undocumented) + taskState: + | { + status: 'running'; + startedAt: string; + timesOutAt?: string; + lastRunError?: string; + lastRunEndedAt?: string; + } + | { + status: 'idle'; + startsAt?: string; + lastRunError?: string; + lastRunEndedAt?: string; + } + | null; + // (undocumented) + workerState: + | { + status: 'initial-wait'; + } + | { + status: 'idle'; + } + | { + status: 'running'; + } + | null; +} + +// @alpha (undocumented) +export type TriggerScheduledTask = { + error?: string; +}; + +// (No @packageDocumentation comment for this package) +``` diff --git a/packages/core-plugin-api/src/apis/definitions/alpha.ts b/plugins/devtools-common/src/alpha.ts similarity index 70% rename from packages/core-plugin-api/src/apis/definitions/alpha.ts rename to plugins/devtools-common/src/alpha.ts index e7f6f13fef..7abe4b1f5e 100644 --- a/packages/core-plugin-api/src/apis/definitions/alpha.ts +++ b/plugins/devtools-common/src/alpha.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2025 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,10 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + export { - translationApiRef, - type TranslationApi, - type TranslationFunction, - type TranslationSnapshot, -} from './TranslationApi'; -export { appLanguageApiRef, type AppLanguageApi } from './AppLanguageApi'; + devToolsTaskSchedulerReadPermission, + devToolsTaskSchedulerCreatePermission, +} from './permissions'; +export type { + ScheduledTasks, + TaskApiTasksResponse, + TriggerScheduledTask, +} from './types'; diff --git a/plugins/devtools-common/src/index.ts b/plugins/devtools-common/src/index.ts index be7426394b..88bc0d545f 100644 --- a/plugins/devtools-common/src/index.ts +++ b/plugins/devtools-common/src/index.ts @@ -20,5 +20,19 @@ * @packageDocumentation */ -export * from './types'; -export * from './permissions'; +export type { + ConfigError, + ConfigInfo, + DevToolsInfo, + Endpoint, + ExternalDependency, + PackageDependency, +} from './types'; +export { ExternalDependencyStatus } from './types'; +export { + devToolsAdministerPermission, + devToolsConfigReadPermission, + devToolsExternalDependenciesReadPermission, + devToolsInfoReadPermission, + devToolsPermissions, +} from './permissions'; diff --git a/plugins/devtools-common/src/permissions.ts b/plugins/devtools-common/src/permissions.ts index ba464f7d80..42a7efe777 100644 --- a/plugins/devtools-common/src/permissions.ts +++ b/plugins/devtools-common/src/permissions.ts @@ -48,6 +48,22 @@ export const devToolsExternalDependenciesReadPermission = createPermission({ attributes: { action: 'read' }, }); +/** + * @alpha + */ +export const devToolsTaskSchedulerReadPermission = createPermission({ + name: 'devtools.scheduler.read', + attributes: { action: 'read' }, +}); + +/** + * @alpha + */ +export const devToolsTaskSchedulerCreatePermission = createPermission({ + name: 'devtools.scheduler.trigger', + attributes: { action: 'update' }, +}); + /** * List of all Devtools permissions * diff --git a/plugins/devtools-common/src/types.ts b/plugins/devtools-common/src/types.ts index f1e60d9c2d..dc35188fde 100644 --- a/plugins/devtools-common/src/types.ts +++ b/plugins/devtools-common/src/types.ts @@ -16,7 +16,7 @@ /* We want to maintain the same information as an enum, so we disable the redeclaration warning */ /* eslint-disable @typescript-eslint/no-redeclare */ -import { JsonValue } from '@backstage/types'; +import { JsonObject, JsonValue } from '@backstage/types'; /** @public */ export type Endpoint = { @@ -82,3 +82,54 @@ export type ConfigError = { messages?: string[]; stack?: string; }; + +/** + * The shape of a task definition as returned by the service's REST API. + * This is a duplication of the below: + * @see https://github.com/backstage/backstage/blob/master/packages/backend-defaults/src/entrypoints/scheduler/lib/types.ts + * + * @alpha + */ +export interface TaskApiTasksResponse { + taskId: string; + pluginId: string; + scope: 'global' | 'local'; + settings: { version: number } & JsonObject; + taskState: + | { + status: 'running'; + startedAt: string; + timesOutAt?: string; + lastRunError?: string; + lastRunEndedAt?: string; + } + | { + status: 'idle'; + startsAt?: string; + lastRunError?: string; + lastRunEndedAt?: string; + } + | null; + workerState: + | { + status: 'initial-wait'; + } + | { + status: 'idle'; + } + | { + status: 'running'; + } + | null; +} + +/** @alpha */ +export type ScheduledTasks = { + scheduledTasks?: TaskApiTasksResponse[]; + error?: string; +}; + +/** @alpha */ +export type TriggerScheduledTask = { + error?: string; +}; diff --git a/plugins/devtools-react/.eslintrc.js b/plugins/devtools-react/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/devtools-react/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/devtools-react/CHANGELOG.md b/plugins/devtools-react/CHANGELOG.md new file mode 100644 index 0000000000..d5d7b990bd --- /dev/null +++ b/plugins/devtools-react/CHANGELOG.md @@ -0,0 +1,90 @@ +# @backstage/plugin-devtools-react + +## 0.2.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/core-plugin-api@1.12.5 + +## 0.2.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-plugin-api@1.12.5-next.1 + +## 0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.2-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + +## 0.2.0 + +### Minor Changes + +- f4a1edd: Removed the deprecated `DevToolsContentBlueprint` from `@backstage/plugin-devtools-react`. DevTools pages in the new frontend system now use `SubPageBlueprint` tabs instead, and the catalog unprocessed entities alpha extension now attaches to DevTools as a subpage. + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.4 + - @backstage/frontend-plugin-api@0.15.0 + +## 0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + +## 0.1.1 + +### Patch Changes + +- 9fbb270: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/core-plugin-api@1.12.3 + +## 0.1.1-next.1 + +### Patch Changes + +- 9fbb270: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + +## 0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + +## 0.1.0 + +### Minor Changes + +- be6cef5: Add support for adding `unprocessed-entities` and other tabs to `devtools` when using the New Frontend system + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 diff --git a/plugins/devtools-react/README.md b/plugins/devtools-react/README.md new file mode 100644 index 0000000000..fe08b182a7 --- /dev/null +++ b/plugins/devtools-react/README.md @@ -0,0 +1,5 @@ +# @backstage/plugin-devtools-react + +Welcome to the web library package for the `devtools` plugin! + +_This plugin was created through the Backstage CLI_ diff --git a/plugins/devtools-react/catalog-info.yaml b/plugins/devtools-react/catalog-info.yaml new file mode 100644 index 0000000000..98b847bee3 --- /dev/null +++ b/plugins/devtools-react/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-devtools-react + title: '@backstage/plugin-devtools-react' + description: Web library for the devtools plugin +spec: + lifecycle: experimental + type: backstage-web-library + owner: maintainers diff --git a/plugins/devtools-react/knip-report.md b/plugins/devtools-react/knip-report.md new file mode 100644 index 0000000000..5ba534983c --- /dev/null +++ b/plugins/devtools-react/knip-report.md @@ -0,0 +1,21 @@ +# Knip report + +## Unused dependencies (3) + +| Name | Location | Severity | +| :----------------------------- | :---------------- | :------- | +| @backstage/frontend-plugin-api | package.json:43:6 | error | +| @backstage/core-plugin-api | package.json:42:6 | error | +| @material-ui/core | package.json:44:6 | error | + +## Unused devDependencies (6) + +| Name | Location | Severity | +| :------------------------ | :---------------- | :------- | +| @testing-library/jest-dom | package.json:49:6 | error | +| @testing-library/react | package.json:50:6 | error | +| @backstage/test-utils | package.json:48:6 | error | +| react-router-dom | package.json:54:6 | error | +| react-dom | package.json:53:6 | error | +| react | package.json:52:6 | error | + diff --git a/plugins/devtools-react/package.json b/plugins/devtools-react/package.json new file mode 100644 index 0000000000..d1a88c9797 --- /dev/null +++ b/plugins/devtools-react/package.json @@ -0,0 +1,67 @@ +{ + "name": "@backstage/plugin-devtools-react", + "version": "0.2.1", + "description": "Web library for the devtools plugin", + "backstage": { + "role": "web-library", + "pluginId": "devtools", + "pluginPackages": [ + "@backstage/plugin-devtools", + "@backstage/plugin-devtools-backend", + "@backstage/plugin-devtools-common", + "@backstage/plugin-devtools-react" + ] + }, + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/devtools-react" + }, + "license": "Apache-2.0", + "sideEffects": false, + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "backstage-cli package build", + "clean": "backstage-cli package clean", + "lint": "backstage-cli package lint", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "start": "backstage-cli package start", + "test": "backstage-cli package test" + }, + "dependencies": { + "@backstage/core-plugin-api": "workspace:^", + "@backstage/frontend-plugin-api": "workspace:^", + "@material-ui/core": "^4.9.13" + }, + "devDependencies": { + "@backstage/cli": "workspace:^", + "@backstage/test-utils": "workspace:^", + "@testing-library/jest-dom": "^6.0.0", + "@testing-library/react": "^16.0.0", + "@types/react": "^18.0.0", + "react": "^18.0.2", + "react-dom": "^18.0.2", + "react-router-dom": "^6.30.2" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0", + "react-router-dom": "^6.30.2" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } +} diff --git a/plugins/devtools-react/report.api.md b/plugins/devtools-react/report.api.md new file mode 100644 index 0000000000..c99c9f953e --- /dev/null +++ b/plugins/devtools-react/report.api.md @@ -0,0 +1,6 @@ +## API Report File for "@backstage/plugin-devtools-react" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +``` diff --git a/plugins/devtools-react/src/index.ts b/plugins/devtools-react/src/index.ts new file mode 100644 index 0000000000..1a71bef82f --- /dev/null +++ b/plugins/devtools-react/src/index.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Web library for the devtools plugin. + * + * @packageDocumentation + */ + +export {}; diff --git a/plugins/devtools/CHANGELOG.md b/plugins/devtools/CHANGELOG.md index c9a695ec75..264eb4001c 100644 --- a/plugins/devtools/CHANGELOG.md +++ b/plugins/devtools/CHANGELOG.md @@ -1,5 +1,227 @@ # @backstage/plugin-devtools +## 0.1.38 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0 + - @backstage/errors@1.3.0 + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/core-components@0.18.9 + - @backstage/core-compat-api@0.5.10 + - @backstage/plugin-permission-react@0.5.0 + - @backstage/core-plugin-api@1.12.5 + - @backstage/plugin-devtools-common@0.1.24 + +## 0.1.38-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.2 + - @backstage/errors@1.3.0-next.0 + - @backstage/core-components@0.18.9-next.1 + - @backstage/core-compat-api@0.5.10-next.2 + - @backstage/core-plugin-api@1.12.5-next.2 + - @backstage/frontend-plugin-api@0.16.0-next.2 + - @backstage/plugin-permission-react@0.4.42-next.1 + - @backstage/plugin-devtools-common@0.1.24-next.0 + +## 0.1.38-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-compat-api@0.5.10-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + +## 0.1.38-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.0 + - @backstage/core-components@0.18.9-next.0 + - @backstage/frontend-plugin-api@0.15.2-next.0 + - @backstage/core-compat-api@0.5.10-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-devtools-common@0.1.23 + - @backstage/plugin-permission-react@0.4.42-next.0 + +## 0.1.37 + +### Patch Changes + +- afabb37: Fixed URL encoding of task IDs for the trigger feature (tasks that contained a "/" in their ID were not triggered) +- f80195e: Added `cancelScheduledTask` to the DevTools API and a cancel button to the scheduled tasks UI. +- 538c985: Updated installation documentation to use feature discovery as the default. +- aa29b50: New frontend system pages now use the default plugin header together with `HeaderPage` instead of the legacy core page header pattern. +- 3f36ce1: Updated alpha plugin icons to follow the new frontend icon sizing rules when rendered in plugin and navigation surfaces. +- f4a1edd: Removed the deprecated `DevToolsContentBlueprint` from `@backstage/plugin-devtools-react`. DevTools pages in the new frontend system now use `SubPageBlueprint` tabs instead, and the catalog unprocessed entities alpha extension now attaches to DevTools as a subpage. +- Updated dependencies + - @backstage/ui@0.13.0 + - @backstage/core-compat-api@0.5.9 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/plugin-devtools-common@0.1.23 + - @backstage/plugin-permission-react@0.4.41 + +## 0.1.37-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.5.9-next.2 + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/core-components@0.18.8-next.1 + - @backstage/plugin-devtools-react@0.1.2-next.1 + +## 0.1.37-next.1 + +### Patch Changes + +- afabb37: Fixed URL encoding of task IDs for the trigger feature (tasks that contained a "/" in their ID were not triggered) +- Updated dependencies + - @backstage/core-compat-api@0.5.9-next.1 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/plugin-devtools-common@0.1.22 + - @backstage/plugin-devtools-react@0.1.2-next.0 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## 0.1.37-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/core-compat-api@0.5.9-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-devtools-common@0.1.22 + - @backstage/plugin-devtools-react@0.1.2-next.0 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## 0.1.36 + +### Patch Changes + +- 018ca87: Added `title` and `icon` to the plugin definition for the new frontend system. +- f2612c2: Fixes an issue where a user lacking permission to schedule tasks can now easily see the issue through a custom icon + tooltip. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/core-components@0.18.7 + - @backstage/core-compat-api@0.5.8 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/plugin-devtools-react@0.1.1 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-permission-react@0.4.40 + - @backstage/plugin-devtools-common@0.1.22 + +## 0.1.36-next.1 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/plugin-devtools-react@0.1.1-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-compat-api@0.5.8-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-permission-react@0.4.40-next.1 + +## 0.1.36-next.0 + +### Patch Changes + +- f2612c2: Fixes an issue where a user lacking permission to schedule tasks can now easily see the issue through a custom icon + tooltip. +- Updated dependencies + - @backstage/core-components@0.18.6-next.0 + - @backstage/core-compat-api@0.5.7-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-devtools-common@0.1.22-next.0 + - @backstage/plugin-devtools-react@0.1.1-next.0 + - @backstage/plugin-permission-react@0.4.40-next.0 + +## 0.1.35 + +### Patch Changes + +- be6cef5: Add support for adding `unprocessed-entities` and other tabs to `devtools` when using the New Frontend system +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + - @backstage/plugin-devtools-react@0.1.0 + - @backstage/plugin-devtools-common@0.1.21 + - @backstage/core-compat-api@0.5.6 + +## 0.1.35-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + +## 0.1.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.5.6-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## 0.1.34 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- 291bf9d: Added scheduled tasks UI feature for the DevTools plugin +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/plugin-devtools-common@0.1.20 + - @backstage/core-compat-api@0.5.5 + - @backstage/plugin-permission-react@0.4.39 + +## 0.1.34-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-devtools-common@0.1.19 + +## 0.1.33 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-devtools-common@0.1.19 + - @backstage/plugin-permission-react@0.4.38 + ## 0.1.33-next.0 ### Patch Changes diff --git a/plugins/devtools/README.md b/plugins/devtools/README.md index 3d82190c2f..3f9a3da747 100644 --- a/plugins/devtools/README.md +++ b/plugins/devtools/README.md @@ -34,6 +34,12 @@ Lists the configuration being used by your current running Backstage instance. ![Example of Config tab](./docs/devtools-config-tab.png) +### Scheduled Tasks + +Scheduled tasks can be viewed and triggered under the `Scheduled Tasks` tab. [See below to configure](#scheduled-tasks-configuration). + +![Example of Scheduled Tasks tab](./docs/devtools-scheduled-tasks-tab.png) + ## Optional Features The DevTools plugin can be setup with other tabs with additional helpful features. @@ -60,42 +66,14 @@ You need to setup the [DevTools backend plugin](../devtools-backend/README.md) b ### Frontend -To setup the DevTools frontend you'll need to do the following steps: +Install the `@backstage/plugin-devtools` package in your frontend app: -1. First we need to add the `@backstage/plugin-devtools` package to your frontend app: +```sh +# From your Backstage root directory +yarn --cwd packages/app add @backstage/plugin-devtools +``` - ```sh - # From your Backstage root directory - yarn --cwd packages/app add @backstage/plugin-devtools - ``` - -2. Now open the `packages/app/src/App.tsx` file -3. Then after all the import statements add the following line: - - ```ts - import { DevToolsPage } from '@backstage/plugin-devtools'; - ``` - -4. In this same file just before the closing ``, this will be near the bottom of the file, add this line: - - ```ts - } /> - ``` - -5. Next open the `packages/app/src/components/Root/Root.tsx` file -6. We want to add this icon import after all the existing import statements: - - ```ts - import BuildIcon from '@material-ui/icons/Build'; - ``` - -7. Then add this line just after the `` line: - - ```ts - - ``` - -8. Now run `yarn start` from the root of your project and you should see the DevTools option show up just below Settings in your sidebar and clicking on it will get you to the [Info tab](#info) +Once installed, the plugin is automatically available in your app through the default feature discovery. For more details and alternative installation methods, see [installing plugins](https://backstage.io/docs/frontend-system/building-apps/installing-plugins). ## Customizing @@ -160,6 +138,37 @@ You can also add tabs to show content from other plugins that fit well with the #### Catalog Unprocessed Entities Tab +Create an extension and/or load a 3rd party extension to add additional tabs. + +```shell +yarn --cwd plugins/ add @backstage/plugin-devtools-react +``` + +```tsx +import { DevToolsContentBlueprint } from '@backstage/plugin-devtools-react'; + +export const unprocessedEntitiesDevToolsContent = DevToolsContentBlueprint.make( + { + disabled: true, + params: { + path: 'unprocessed-entities', + title: 'Unprocessed Entities', + loader: () => + import('../components/UnprocessedEntities').then(m => ( + + )), + }, + }, +); + +const appFeature = createFrontendModule({ + pluginId: 'catalog-unprocessed-entities', + extensions: [unprocessedEntitiesDevToolsContent], +}); +``` + +##### Old Frontend System + Here's how to add the Catalog Unprocessed Entities tab: 1. Install and setup the [Catalog Unprocessed Entities plugin](https://github.com/backstage/backstage/tree/master/plugins/catalog-unprocessed-entities) as per its documentation @@ -191,6 +200,38 @@ Here's how to add the Catalog Unprocessed Entities tab: 4. Now run `yarn start` and navigate to the DevTools you'll see a new tab for Unprocessed Entities +## Old Frontend System + +If your Backstage app uses the old frontend system, you need to manually wire the plugin into your app as outlined in this section. If you are on the new frontend system, you can skip this. + +1. Open the `packages/app/src/App.tsx` file +2. Then after all the import statements add the following line: + + ```ts + import { DevToolsPage } from '@backstage/plugin-devtools'; + ``` + +3. In this same file just before the closing ``, this will be near the bottom of the file, add this line: + + ```ts + } /> + ``` + +4. Next open the `packages/app/src/components/Root/Root.tsx` file +5. We want to add this icon import after all the existing import statements: + + ```ts + import BuildIcon from '@material-ui/icons/Build'; + ``` + +6. Then add this line just after the `` line: + + ```ts + + ``` + +7. Now run `yarn start` from the root of your project and you should see the DevTools option show up just below Settings in your sidebar and clicking on it will get you to the [Info tab](#info) + ## Permissions The DevTools plugin supports the [permissions framework](https://backstage.io/docs/permissions/overview), the following sections outline how you can use them with the assumption that you have the permissions framework setup and working. @@ -456,3 +497,14 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ apt-get update && \ apt-get install -y ... iputils-ping ``` + +### Scheduled Tasks Configuration + +Scheduled tasks can be viewed and triggered under the `Scheduled Tasks` tab. You first must add the list of plugins for scheduled tasks to your config: + +```yaml +devTools: + scheduledTasks: + plugins: + - catalog +``` diff --git a/packages/backend-plugin-api/src/alpha/InstanceMetadataService.ts b/plugins/devtools/config.d.ts similarity index 67% rename from packages/backend-plugin-api/src/alpha/InstanceMetadataService.ts rename to plugins/devtools/config.d.ts index a82a261d9d..841bb0b042 100644 --- a/packages/backend-plugin-api/src/alpha/InstanceMetadataService.ts +++ b/plugins/devtools/config.d.ts @@ -13,20 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -/** @alpha */ -export type BackendFeatureMeta = - | { - type: 'plugin'; - pluginId: string; - } - | { - type: 'module'; - pluginId: string; - moduleId: string; +export interface Config { + devTools?: { + /** + * Scheduled tasks configuration + * @visibility frontend + */ + scheduledTasks?: { + /** + * A list of plugin IDs to select from, e.g. ['catalog', 'scaffolder'] + * @visibility frontend + */ + plugins: string[]; }; - -/** @alpha */ -export interface InstanceMetadataService { - getInstalledFeatures: () => BackendFeatureMeta[]; + }; } diff --git a/plugins/devtools/docs/devtools-scheduled-tasks-tab.png b/plugins/devtools/docs/devtools-scheduled-tasks-tab.png new file mode 100644 index 0000000000..90e5e57d83 Binary files /dev/null and b/plugins/devtools/docs/devtools-scheduled-tasks-tab.png differ diff --git a/plugins/devtools/knip-report.md b/plugins/devtools/knip-report.md index 2661c35327..42d4381027 100644 --- a/plugins/devtools/knip-report.md +++ b/plugins/devtools/knip-report.md @@ -1,2 +1,16 @@ # Knip report +## Unused dependencies (3) + +| Name | Location | Severity | +| :------------------------- | :---------------- | :------- | +| @backstage/core-compat-api | package.json:56:6 | error | +| @backstage/ui | package.json:63:6 | error | +| lodash | package.json:67:6 | error | + +## Unused devDependencies (1) + +| Name | Location | Severity | +| :------------ | :---------------- | :------- | +| @types/lodash | package.json:75:6 | error | + diff --git a/plugins/devtools/package.json b/plugins/devtools/package.json index 10959ac740..5cc26ac5f1 100644 --- a/plugins/devtools/package.json +++ b/plugins/devtools/package.json @@ -1,13 +1,14 @@ { "name": "@backstage/plugin-devtools", - "version": "0.1.33-next.0", + "version": "0.1.38", "backstage": { "role": "frontend-plugin", "pluginId": "devtools", "pluginPackages": [ "@backstage/plugin-devtools", "@backstage/plugin-devtools-backend", - "@backstage/plugin-devtools-common" + "@backstage/plugin-devtools-common", + "@backstage/plugin-devtools-react" ] }, "publishConfig": { @@ -39,7 +40,8 @@ } }, "files": [ - "dist" + "dist", + "config.d.ts" ], "scripts": { "build": "backstage-cli package build", @@ -58,9 +60,11 @@ "@backstage/frontend-plugin-api": "workspace:^", "@backstage/plugin-devtools-common": "workspace:^", "@backstage/plugin-permission-react": "workspace:^", + "@backstage/ui": "workspace:^", "@material-ui/core": "^4.9.13", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "^4.0.0-alpha.57", + "lodash": "^4.17.21", "react-json-view": "^1.21.3", "react-use": "^17.2.4" }, @@ -68,20 +72,22 @@ "@backstage/cli": "workspace:^", "@backstage/dev-utils": "workspace:^", "@testing-library/jest-dom": "^6.0.0", + "@types/lodash": "^4.14.151", "@types/react": "^18.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { "optional": true } - } + }, + "configSchema": "config.d.ts" } diff --git a/plugins/devtools/report-alpha.api.md b/plugins/devtools/report-alpha.api.md index c114924f4a..b911a82b48 100644 --- a/plugins/devtools/report-alpha.api.md +++ b/plugins/devtools/report-alpha.api.md @@ -6,13 +6,17 @@ import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { ApiFactory } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; -import { IconComponent } from '@backstage/core-plugin-api'; +import { ExtensionInput } from '@backstage/frontend-plugin-api'; +import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; -import { RouteRef } from '@backstage/frontend-plugin-api'; +import { RouteRef } from '@backstage/core-plugin-api'; +import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) const _default: OverridableFrontendPlugin< @@ -39,13 +43,17 @@ const _default: OverridableFrontendPlugin< 'nav-item:devtools': OverridableExtensionDefinition<{ kind: 'nav-item'; name: undefined; - config: {}; - configInput: {}; + config: { + title: string | undefined; + }; + configInput: { + title?: string | undefined; + }; output: ExtensionDataRef< { title: string; icon: IconComponent; - routeRef: RouteRef; + routeRef: RouteRef_2; }, 'core.nav-item.target', {} @@ -54,34 +62,197 @@ const _default: OverridableFrontendPlugin< params: { title: string; icon: IconComponent; - routeRef: RouteRef; + routeRef: RouteRef_2; }; }>; 'page:devtools': OverridableExtensionDefinition<{ - kind: 'page'; - name: undefined; config: { path: string | undefined; + title: string | undefined; }; configInput: { path?: string | undefined; + title?: string | undefined; }; output: | ExtensionDataRef + | ExtensionDataRef< + RouteRef_2, + 'core.routing.ref', + { + optional: true; + } + > | ExtensionDataRef | ExtensionDataRef< - RouteRef, + string, + 'core.title', + { + optional: true; + } + > + | ExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >; + inputs: { + pages: ExtensionInput< + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef_2, + 'core.routing.ref', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >, + { + singleton: false; + optional: false; + internal: false; + } + >; + }; + kind: 'page'; + name: undefined; + params: { + path: string; + title?: string; + icon?: IconElement; + loader?: () => Promise; + routeRef?: RouteRef_2; + noHeader?: boolean; + }; + }>; + 'sub-page:devtools/config': OverridableExtensionDefinition<{ + kind: 'sub-page'; + name: 'config'; + config: { + path: string | undefined; + title: string | undefined; + }; + configInput: { + path?: string | undefined; + title?: string | undefined; + }; + output: + | ExtensionDataRef + | ExtensionDataRef< + RouteRef_2, 'core.routing.ref', { optional: true; } + > + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } >; inputs: {}; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; + title: string; + icon?: IconElement; loader: () => Promise; - routeRef?: RouteRef; + routeRef?: RouteRef_2; + }; + }>; + 'sub-page:devtools/info': OverridableExtensionDefinition<{ + kind: 'sub-page'; + name: 'info'; + config: { + path: string | undefined; + title: string | undefined; + }; + configInput: { + path?: string | undefined; + title?: string | undefined; + }; + output: + | ExtensionDataRef + | ExtensionDataRef< + RouteRef_2, + 'core.routing.ref', + { + optional: true; + } + > + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >; + inputs: {}; + params: { + path: string; + title: string; + icon?: IconElement; + loader: () => Promise; + routeRef?: RouteRef_2; + }; + }>; + 'sub-page:devtools/scheduled-tasks': OverridableExtensionDefinition<{ + kind: 'sub-page'; + name: 'scheduled-tasks'; + config: { + path: string | undefined; + title: string | undefined; + }; + configInput: { + path?: string | undefined; + title?: string | undefined; + }; + output: + | ExtensionDataRef + | ExtensionDataRef< + RouteRef_2, + 'core.routing.ref', + { + optional: true; + } + > + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >; + inputs: {}; + params: { + path: string; + title: string; + icon?: IconElement; + loader: () => Promise; + routeRef?: RouteRef_2; }; }>; } diff --git a/plugins/devtools/report.api.md b/plugins/devtools/report.api.md index e7a42f4377..a74dd7eb23 100644 --- a/plugins/devtools/report.api.md +++ b/plugins/devtools/report.api.md @@ -6,16 +6,18 @@ import { BackstagePlugin } from '@backstage/core-plugin-api'; import { ElementType } from 'react'; import { JSX as JSX_2 } from 'react/jsx-runtime'; +import { ReactElement } from 'react'; import { ReactNode } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; import { TabProps } from '@material-ui/core/Tab'; +import { TaskApiTasksResponse } from '@backstage/plugin-devtools-common/alpha'; // @public (undocumented) export const ConfigContent: () => JSX_2.Element; // @public export const DevToolsLayout: { - ({ children, title, subtitle }: DevToolsLayoutProps): JSX_2.Element; + (input: DevToolsLayoutProps): JSX_2.Element; Route: (props: SubRoute) => null; }; @@ -27,7 +29,23 @@ export type DevToolsLayoutProps = { }; // @public (undocumented) -export const DevToolsPage: () => JSX_2.Element; +export const DevToolsPage: (input: DevToolsPageProps) => JSX_2.Element; + +// @public (undocumented) +export interface DevToolsPageContent { + // (undocumented) + children: ReactElement; + // (undocumented) + path: string; + // (undocumented) + title: string; +} + +// @public (undocumented) +export interface DevToolsPageProps { + // (undocumented) + contents?: DevToolsPageContent[]; +} // @public (undocumented) export const devToolsPlugin: BackstagePlugin< @@ -43,6 +61,14 @@ export const ExternalDependenciesContent: () => JSX_2.Element; // @public (undocumented) export const InfoContent: () => JSX_2.Element; +// @public (undocumented) +export const ScheduledTaskDetailPanel: (input: { + rowData: TaskApiTasksResponse; +}) => JSX_2.Element; + +// @public (undocumented) +export const ScheduledTasksContent: () => JSX_2.Element; + // @public (undocumented) export type SubRoute = { path: string; diff --git a/plugins/devtools/src/alpha/plugin.tsx b/plugins/devtools/src/alpha/plugin.tsx index 0f6d9ec4a1..0144308069 100644 --- a/plugins/devtools/src/alpha/plugin.tsx +++ b/plugins/devtools/src/alpha/plugin.tsx @@ -16,20 +16,25 @@ import { createFrontendPlugin, + coreExtensionData, discoveryApiRef, fetchApiRef, ApiBlueprint, PageBlueprint, NavItemBlueprint, + SubPageBlueprint, } from '@backstage/frontend-plugin-api'; import { devToolsApiRef, DevToolsClient } from '../api'; -import { - compatWrapper, - convertLegacyRouteRef, -} from '@backstage/core-compat-api'; import BuildIcon from '@material-ui/icons/Build'; +import { Content } from '@backstage/core-components'; import { rootRouteRef } from '../routes'; +import { + devToolsConfigReadPermission, + devToolsInfoReadPermission, +} from '@backstage/plugin-devtools-common'; +import { devToolsTaskSchedulerReadPermission } from '@backstage/plugin-devtools-common/alpha'; +import { RequirePermission } from '@backstage/plugin-permission-react'; /** @alpha */ export const devToolsApi = ApiBlueprint.make({ @@ -46,14 +51,85 @@ export const devToolsApi = ApiBlueprint.make({ }); /** @alpha */ -export const devToolsPage = PageBlueprint.make({ +export const devToolsPage = PageBlueprint.makeWithOverrides({ + factory(originalFactory, { inputs }) { + const pages = [...inputs.pages].sort((left, right) => { + const leftPath = left.get(coreExtensionData.routePath); + const rightPath = right.get(coreExtensionData.routePath); + + if (leftPath === 'info' && rightPath !== 'info') { + return -1; + } + if (leftPath !== 'info' && rightPath === 'info') { + return 1; + } + + return 0; + }); + + return originalFactory( + { + path: '/devtools', + routeRef: rootRouteRef, + title: 'DevTools', + }, + { + inputs: { + pages, + }, + }, + ); + }, +}); + +/** @alpha */ +export const devToolsInfoPage = SubPageBlueprint.make({ + name: 'info', params: { - path: '/devtools', - routeRef: convertLegacyRouteRef(rootRouteRef), + path: 'info', + title: 'Info', loader: () => - import('../components/DevToolsPage').then(m => - compatWrapper(), - ), + import('../components/Content').then(m => ( + + + + + + )), + }, +}); + +/** @alpha */ +export const devToolsConfigPage = SubPageBlueprint.make({ + name: 'config', + params: { + path: 'config', + title: 'Config', + loader: () => + import('../components/Content').then(m => ( + + + + + + )), + }, +}); + +/** @alpha */ +export const devToolsScheduledTasksPage = SubPageBlueprint.make({ + name: 'scheduled-tasks', + params: { + path: 'scheduled-tasks', + title: 'Scheduled Tasks', + loader: () => + import('../components/Content').then(m => ( + + + + + + )), }, }); @@ -61,7 +137,7 @@ export const devToolsPage = PageBlueprint.make({ export const devToolsNavItem = NavItemBlueprint.make({ params: { title: 'DevTools', - routeRef: convertLegacyRouteRef(rootRouteRef), + routeRef: rootRouteRef, icon: BuildIcon, }, }); @@ -69,9 +145,18 @@ export const devToolsNavItem = NavItemBlueprint.make({ /** @alpha */ export default createFrontendPlugin({ pluginId: 'devtools', + title: 'DevTools', + icon: , info: { packageJson: () => import('../../package.json') }, routes: { - root: convertLegacyRouteRef(rootRouteRef), + root: rootRouteRef, }, - extensions: [devToolsApi, devToolsPage, devToolsNavItem], + extensions: [ + devToolsApi, + devToolsPage, + devToolsInfoPage, + devToolsConfigPage, + devToolsScheduledTasksPage, + devToolsNavItem, + ], }); diff --git a/plugins/devtools/src/api/DevToolsApi.ts b/plugins/devtools/src/api/DevToolsApi.ts index ff58a4ea16..2aaad49aa8 100644 --- a/plugins/devtools/src/api/DevToolsApi.ts +++ b/plugins/devtools/src/api/DevToolsApi.ts @@ -20,6 +20,10 @@ import { DevToolsInfo, ExternalDependency, } from '@backstage/plugin-devtools-common'; +import { + ScheduledTasks, + TriggerScheduledTask, +} from '@backstage/plugin-devtools-common/alpha'; export const devToolsApiRef = createApiRef({ id: 'plugin.devtools.service', @@ -29,4 +33,10 @@ export interface DevToolsApi { getConfig(): Promise; getExternalDependencies(): Promise; getInfo(): Promise; + getScheduledTasksByPlugin(plugin: string): Promise; + triggerScheduledTask( + plugin: string, + taskId: string, + ): Promise; + cancelScheduledTask(plugin: string, taskId: string): Promise; } diff --git a/plugins/devtools/src/api/DevToolsClient.test.ts b/plugins/devtools/src/api/DevToolsClient.test.ts new file mode 100644 index 0000000000..d1972cd528 --- /dev/null +++ b/plugins/devtools/src/api/DevToolsClient.test.ts @@ -0,0 +1,49 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DevToolsClient } from './DevToolsClient'; + +describe('DevToolsClient', () => { + const mockBaseUrl = 'http://backstage/api/catalog'; + const discoveryApi = { + getBaseUrl: async (pluginId: string) => `${mockBaseUrl}/${pluginId}`, + }; + const mockFetch = jest.fn(); + const fetchApi = { fetch: mockFetch }; + + let client: DevToolsClient; + beforeEach(() => { + mockFetch.mockResolvedValue({ + ok: true, + json: async () => ({ status: 'triggered' }), + }); + client = new DevToolsClient({ discoveryApi, fetchApi }); + }); + + afterEach(() => jest.resetAllMocks()); + + it('should URL-encode the taskId when triggering a scheduled task', async () => { + await client.triggerScheduledTask( + 'my-plugin', + 'task/with/slashes:and-special-chars', + ); + + expect(mockFetch).toHaveBeenCalledWith( + `${mockBaseUrl}/my-plugin/.backstage/scheduler/v1/tasks/task%2Fwith%2Fslashes%3Aand-special-chars/trigger`, + { method: 'POST' }, + ); + }); +}); diff --git a/plugins/devtools/src/api/DevToolsClient.ts b/plugins/devtools/src/api/DevToolsClient.ts index e0b387a6ca..b6d9d4b119 100644 --- a/plugins/devtools/src/api/DevToolsClient.ts +++ b/plugins/devtools/src/api/DevToolsClient.ts @@ -20,7 +20,11 @@ import { DevToolsInfo, ExternalDependency, } from '@backstage/plugin-devtools-common'; -import { ResponseError } from '@backstage/errors'; +import { + ScheduledTasks, + TriggerScheduledTask, +} from '@backstage/plugin-devtools-common/alpha'; +import { ResponseError, NotFoundError, ConflictError } from '@backstage/errors'; import { DevToolsApi } from './DevToolsApi'; export class DevToolsClient implements DevToolsApi { @@ -42,6 +46,69 @@ export class DevToolsClient implements DevToolsApi { return configInfo; } + public async getScheduledTasksByPlugin( + plugin: string, + ): Promise { + const baseUrl = `${await this.discoveryApi.getBaseUrl(plugin)}/`; + const url = new URL('.backstage/scheduler/v1/tasks', baseUrl); + + const response = await this.fetchApi.fetch(url.toString()); + + if (!response.ok) { + throw await ResponseError.fromResponse(response); + } + + const scheduledTasks = await response.json(); + return { + scheduledTasks: scheduledTasks.tasks, + }; + } + + public async triggerScheduledTask( + plugin: string, + taskId: string, + ): Promise { + const baseUrl = `${await this.discoveryApi.getBaseUrl(plugin)}/`; + const url = new URL( + `.backstage/scheduler/v1/tasks/${encodeURIComponent(taskId)}/trigger`, + baseUrl, + ); + + const response = await this.fetchApi.fetch(url.toString(), { + method: 'POST', + }); + + if (!response.ok) { + throw await ResponseError.fromResponse(response); + } + + return {}; + } + + public async cancelScheduledTask( + plugin: string, + taskId: string, + ): Promise { + const baseUrl = `${await this.discoveryApi.getBaseUrl(plugin)}/`; + const url = new URL( + `.backstage/scheduler/v1/tasks/${encodeURIComponent(taskId)}/cancel`, + baseUrl, + ); + + const response = await this.fetchApi.fetch(url.toString(), { + method: 'POST', + }); + + if (!response.ok) { + if (response.status === 404) { + throw new NotFoundError(`Task ${taskId} not found`); + } else if (response.status === 409) { + throw new ConflictError(`Task ${taskId} is not running`); + } + throw await ResponseError.fromResponse(response); + } + } + public async getExternalDependencies(): Promise< ExternalDependency[] | undefined > { diff --git a/plugins/devtools/src/components/Content/ScheduledTasksContent/ScheduledTaskDetailedPanel.tsx b/plugins/devtools/src/components/Content/ScheduledTasksContent/ScheduledTaskDetailedPanel.tsx new file mode 100644 index 0000000000..a29e1eb10e --- /dev/null +++ b/plugins/devtools/src/components/Content/ScheduledTasksContent/ScheduledTaskDetailedPanel.tsx @@ -0,0 +1,104 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { TaskApiTasksResponse } from '@backstage/plugin-devtools-common/alpha'; +import Grid from '@material-ui/core/Grid'; +import Typography from '@material-ui/core/Typography'; +import Box from '@material-ui/core/Box'; +import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; +import Alert from '@material-ui/lab/Alert'; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + detailPanel: { + padding: theme.spacing(2), + backgroundColor: theme.palette.background.default, + }, + detailLabel: { + fontWeight: 'bold', + marginRight: theme.spacing(1), + }, + errorIcon: { + color: theme.palette.error.main, + marginRight: theme.spacing(1), + fontSize: '1.2rem', + }, + detailPanelAlert: { + marginBottom: theme.spacing(2), + }, + }), +); + +/** @public */ +export const ScheduledTaskDetailPanel = ({ + rowData, +}: { + rowData: TaskApiTasksResponse; +}) => { + const classes = useStyles(); + const lastRunError = rowData.taskState?.lastRunError; + + const DetailItem = ({ title, value }: { title: string; value: any }) => ( + <> + + + {title}: + + + + + {value || 'N/A'} + + + + ); + + return ( + + {lastRunError && ( + + Last Run Error: {lastRunError} + + )} + + + + + + + + + + ); +}; diff --git a/plugins/devtools/src/components/Content/ScheduledTasksContent/ScheduledTasksContent.tsx b/plugins/devtools/src/components/Content/ScheduledTasksContent/ScheduledTasksContent.tsx new file mode 100644 index 0000000000..af86b9b237 --- /dev/null +++ b/plugins/devtools/src/components/Content/ScheduledTasksContent/ScheduledTasksContent.tsx @@ -0,0 +1,344 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useState } from 'react'; +import Box from '@material-ui/core/Box'; +import Typography from '@material-ui/core/Typography'; +import IconButton from '@material-ui/core/IconButton'; +import Tooltip from '@material-ui/core/Tooltip'; +import Autocomplete from '@material-ui/lab/Autocomplete'; +import TextField from '@material-ui/core/TextField'; +import { makeStyles, createStyles, Theme } from '@material-ui/core/styles'; +import { + ErrorPanel, + Progress, + Table, + TableColumn, +} from '@backstage/core-components'; +import Alert from '@material-ui/lab/Alert'; +import { useScheduledTasks, useScheduledTasksOperations } from '../../../hooks'; +import { TaskApiTasksResponse } from '@backstage/plugin-devtools-common/alpha'; +import { alertApiRef, configApiRef, useApi } from '@backstage/core-plugin-api'; +import RefreshIcon from '@material-ui/icons/Refresh'; +import StopIcon from '@material-ui/icons/Stop'; +import NightsStay from '@material-ui/icons/NightsStay'; +import ErrorIcon from '@material-ui/icons/Error'; +import BlockIcon from '@material-ui/icons/Block'; +import CircularProgress from '@material-ui/core/CircularProgress'; +import { ScheduledTaskDetailPanel } from './ScheduledTaskDetailedPanel'; +import { RequirePermission } from '@backstage/plugin-permission-react'; +import { devToolsTaskSchedulerCreatePermission } from '@backstage/plugin-devtools-common/alpha'; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + paperStyle: { + display: 'flex', + marginBottom: theme.spacing(2), + }, + flexContainer: { + display: 'flex', + flexDirection: 'row', + padding: 0, + }, + formControl: { + minWidth: 240, + marginBottom: theme.spacing(2), + }, + detailPanel: { + padding: theme.spacing(2), + backgroundColor: theme.palette.background.default, + }, + detailLabel: { + fontWeight: 'bold', + marginRight: theme.spacing(1), + }, + errorIcon: { + color: theme.palette.error.main, + marginRight: theme.spacing(1), + fontSize: '1.2rem', + }, + detailPanelAlert: { + marginBottom: theme.spacing(2), + }, + }), +); + +const StatusDisplay = ({ + icon, + text, +}: { + icon: React.ReactNode; + text: string; +}) => ( + + {icon} + + {text} + + +); + +const CreateNotAllowed = () => ( + + + +); + +/** @public */ +export const ScheduledTasksContent = () => { + const classes = useStyles(); + const configApi = useApi(configApiRef); + const alertApi = useApi(alertApiRef); + const plugins = + configApi.getOptionalStringArray('devTools.scheduledTasks.plugins') || []; + const [selectedPlugin, setSelectedPlugin] = useState(plugins[0] || ''); + const { scheduledTasks, loading, error } = useScheduledTasks(selectedPlugin); + const { triggerTask, cancelTask, isLoading } = useScheduledTasksOperations(); + + const [inputValue, setInputValue] = useState(''); + + const handleAutocompleteChange = (_event: any, newValue: string | null) => { + setSelectedPlugin(newValue || ''); + }; + + const handleCommitChange = () => { + if (inputValue !== selectedPlugin) { + setSelectedPlugin(inputValue); + } + }; + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'Enter') { + handleCommitChange(); + // Prevent Autocomplete's default behavior (which might select a filtered item) + event.preventDefault(); + event.stopPropagation(); + } + }; + + if (!plugins || plugins.length === 0) { + return ( + + No plugins configured for scheduled tasks. Please configure + `devTools.scheduledTasks.plugins` in app-config.yaml. + + ); + } + + const columns: TableColumn[] = [ + { + title: 'Task ID', + field: 'taskId', + width: '35%', + render: (rowData: TaskApiTasksResponse) => { + const errorIconStyle: React.CSSProperties = { + color: '#f44336', + marginRight: '8px', + fontSize: '1.2rem', + verticalAlign: 'middle', + }; + + return ( + + {rowData.taskState?.lastRunError && ( + + )} + {rowData.taskId} + + ); + }, + }, + { + title: 'Status', + field: 'taskState.status', + width: '15%', + render: (rowData: TaskApiTasksResponse) => { + const status = rowData.taskState?.status; + + if (status === 'idle') { + return ( + } text="Idle" /> + ); + } + + if (status === 'running') { + return ( + } + text="Running" + /> + ); + } + + return {status || 'N/A'}; + }, + }, + { + title: 'Last Run', + field: 'taskState.lastRunEndedAt', + width: '25%', + render: (rowData: TaskApiTasksResponse) => + rowData.taskState?.lastRunEndedAt + ? new Date(rowData.taskState.lastRunEndedAt).toLocaleString() + : 'N/A', + }, + { + title: 'Next Run', + width: '15%', + render: (rowData: TaskApiTasksResponse) => + rowData.taskState?.status === 'idle' && rowData.taskState.startsAt + ? new Date(rowData.taskState.startsAt).toLocaleString() + : 'N/A', + }, + { + title: 'Actions', + render: (rowData: TaskApiTasksResponse) => ( + } + > + + + { + try { + await triggerTask(selectedPlugin, rowData.taskId); + alertApi.post({ + message: `Successfully triggered task ${rowData.taskId}`, + severity: 'success', + }); + } catch (e) { + alertApi.post({ + message: `Error triggering task ${rowData.taskId}: ${e.message}`, + severity: 'error', + }); + } + }} + > + + + + + { + try { + await cancelTask(selectedPlugin, rowData.taskId); + alertApi.post({ + message: `Successfully cancelled task ${rowData.taskId}`, + severity: 'success', + }); + } catch (e) { + alertApi.post({ + message: `Error cancelling task ${rowData.taskId}: ${e.message}`, + severity: 'error', + }); + } + }} + > + + + + + + ), + sorting: false, + width: '10%', + align: 'center', + }, + ]; + + return ( + + { + setInputValue(newInputValue); + }} + renderInput={params => ( + + )} + /> + + {loading && !scheduledTasks && } + + {error && ( + + + The plugin ID "{selectedPlugin}" doesn't have any scheduled tasks or + may contain a typo. + + + Please verify: + +
      +
    • + + The plugin ID is spelled correctly + +
    • +
    • + + The plugin has registered scheduled tasks + +
    • +
    +
    + )} + + {scheduledTasks && ( +
    + No scheduled tasks found for {selectedPlugin}. + + } + detailPanel={({ rowData }) => { + return ; + }} + /> + )} + + ); +}; diff --git a/plugins/devtools/src/components/Content/ScheduledTasksContent/fixtures/scheduledTasksErrors.json b/plugins/devtools/src/components/Content/ScheduledTasksContent/fixtures/scheduledTasksErrors.json new file mode 100644 index 0000000000..89f81b740d --- /dev/null +++ b/plugins/devtools/src/components/Content/ScheduledTasksContent/fixtures/scheduledTasksErrors.json @@ -0,0 +1,205 @@ +{ + "tasks": [ + { + "taskId": "cool-provider", + "pluginId": "catalog", + "scope": "global", + "settings": { + "version": 2, + "cadence": "PT24H", + "timeoutAfterDuration": "PT60M" + }, + "taskState": { + "status": "running", + "startedAt": "2025-10-31T21:02:36.461+00:00", + "timesOutAt": "2025-10-31T22:02:36.461+00:00", + "lastRunEndedAt": "2025-10-31T19:57:15.674+00:00", + "lastRunError": "{\"name\":\"error\",\"message\":\"insert into \\\"refresh_state\\\" (\\\"entity_id\\\", \\\"entity_ref\\\", \\\"errors\\\", \\\"last_discovery_at\\\", \\\"location_key\\\", \\\"next_update_at\\\", \\\"unprocessed_entity\\\", \\\"unprocessed_hash\\\") values ($1, $2, $3, CURRENT_TIMESTAMP, $4, CURRENT_TIMESTAMP, $5, $6), ($7, $8, $9, CURRENT_TIMESTAMP, $10, CURRENT_TIMESTAMP, $11, $12), ($13, $14, $15, CURRENT_TIMESTAMP, $16, CURRENT_TIMESTAMP, $17, $18), ($19, $20, $21, CURRENT_TIMESTAMP, $22, CURRENT_TIMESTAMP, $23, $24), ($25, $26, $27, CURRENT_TIMESTAMP, $28, CURRENT_TIMESTAMP, $29, $30), ($31, $32, $33, CURRENT_TIMESTAMP, $34, CURRENT_TIMESTAMP, $35, $36), ($37, $38, $39, CURRENT_TIMESTAMP, $40, CURRENT_TIMESTAMP, $41, $42), ($43, $44, $45, CURRENT_TIMESTAMP, $46, CURRENT_TIMESTAMP, $47, $48), ($49, $50, $51, CURRENT_TIMESTAMP, $52, CURRENT_TIMESTAMP, $53, $54), ($55, $56, $57, CURRENT_TIMESTAMP, $58, CURRENT_TIMESTAMP, $59, $60), ($61, $62, $63, CURRENT_TIMESTAMP, $64, CURRENT_TIMESTAMP, $65, $66), ($67, $68, $69, CURRENT_TIMESTAMP, $70, CURRENT_TIMESTAMP, $71, $72), ($73, $74, $75, CURRENT_TIMESTAMP, $76, CURRENT_TIMESTAMP, $77, $78), ($79, $80, $81, CURRENT_TIMESTAMP, $82, CURRENT_TIMESTAMP, $83, $84), ($85, $86, $87, CURRENT_TIMESTAMP, $88, CURRENT_TIMESTAMP, $89, $90), ($91, $92, $93, CURRENT_TIMESTAMP, $94, CURRENT_TIMESTAMP, $95, $96), ($97, $98, $99, CURRENT_TIMESTAMP, $100, CURRENT_TIMESTAMP, $101, $102), ($103, $104, $105, CURRENT_TIMESTAMP, $106, CURRENT_TIMESTAMP, $107, $108), ($109, $110, $111, CURRENT_TIMESTAMP, $112, CURRENT_TIMESTAMP, $113, $114), ($115, $116, $117, CURRENT_TIMESTAMP, $118, CURRENT_TIMESTAMP, $119, $120), ($121, $122, $123, CURRENT_TIMESTAMP, $124, CURRENT_TIMESTAMP, $125, $126), ($127, $128, $129, CURRENT_TIMESTAMP, $130, CURRENT_TIMESTAMP, $131, $132), ($133, $134, $135, CURRENT_TIMESTAMP, $136, CURRENT_TIMESTAMP, $137, $138), ($139, $140, $141, CURRENT_TIMESTAMP, $142, CURRENT_TIMESTAMP, $143, $144), ($145, $146, $147, CURRENT_TIMESTAMP, $148, CURRENT_TIMESTAMP, $149, $150), ($151, $152, $153, CURRENT_TIMESTAMP, $154, CURRENT_TIMESTAMP, $155, $156), ($157, $158, $159, CURRENT_TIMESTAMP, $160, CURRENT_TIMESTAMP, $161, $162), ($163, $164, $165, CURRENT_TIMESTAMP, $166, CURRENT_TIMESTAMP, $167, $168), ($169, $170, $171, CURRENT_TIMESTAMP, $172, CURRENT_TIMESTAMP, $173, $174), ($175, $176, $177, CURRENT_TIMESTAMP, $178, CURRENT_TIMESTAMP, $179, $180), ($181, $182, $183, CURRENT_TIMESTAMP, $184, CURRENT_TIMESTAMP, $185, $186), ($187, $188, $189, CURRENT_TIMESTAMP, $190, CURRENT_TIMESTAMP, $191, $192), ($193, $194, $195, CURRENT_TIMESTAMP, $196, CURRENT_TIMESTAMP, $197, $198), ($199, $200, $201, CURRENT_TIMESTAMP, $202, CURRENT_TIMESTAMP, $203, $204), ($205, $206, $207, CURRENT_TIMESTAMP, $208, CURRENT_TIMESTAMP, $209, $210), ($211, $212, $213, CURRENT_TIMESTAMP, $214, CURRENT_TIMESTAMP, $215, $216), ($217, $218, $219, CURRENT_TIMESTAMP, $220, CURRENT_TIMESTAMP, $221, $222), ($223, $224, $225, CURRENT_TIMESTAMP, $226, CURRENT_TIMESTAMP, $227, $228), ($229, $230, $231, CURRENT_TIMESTAMP, $232, CURRENT_TIMESTAMP, $233, $234), ($235, $236, $237, CURRENT_TIMESTAMP, $238, CURRENT_TIMESTAMP, $239, $240), ($241, $242, $243, CURRENT_TIMESTAMP, $244, CURRENT_TIMESTAMP, $245, $246), ($247, $248, $249, CURRENT_TIMESTAMP, $250, CURRENT_TIMESTAMP, $251, $252), ($253, $254, $255, CURRENT_TIMESTAMP, $256, CURRENT_TIMESTAMP, $257, $258), ($259, $260, $261, CURRENT_TIMESTAMP, $262, CURRENT_TIMESTAMP, $263, $264), ($265, $266, $267, CURRENT_TIMESTAMP, $268, CURRENT_TIMESTAMP, $269, $270), ($271, $272, $273, CURRENT_TIMESTAMP, $274, CURRENT_TIMESTAMP, $275, $276), ($277, $278, $279, CURRENT_TIMESTAMP, $280, CURRENT_TIMESTAMP, $281, $282), ($283, $284, $285, CURRENT_TIMESTAMP, $286, CURRENT_TIMESTAMP, $287, $288), ($289, $290, $291, CURRENT_TIMESTAMP, $292, CURRENT_TIMESTAMP, $293, $294), ($295, $296, $297, CURRENT_TIMESTAMP, $298, CURRENT_TIMESTAMP, $299, $300) - value too long for type character varying(255)\",\"length\":99,\"severity\":\"ERROR\",\"code\":\"22001\",\"file\":\"varchar.c\",\"line\":\"637\",\"routine\":\"varchar\"}" + }, + "workerState": { + "status": "running" + } + }, + { + "taskId": "some-provider", + "pluginId": "catalog", + "scope": "global", + "settings": { + "version": 2, + "cadence": "PT24H", + "timeoutAfterDuration": "PT24H" + }, + "taskState": { + "status": "idle", + "startsAt": "2025-11-01T21:02:34.850+00:00", + "lastRunEndedAt": "2025-10-31T21:02:35.556+00:00" + }, + "workerState": { + "status": "idle" + } + }, + { + "taskId": "three-provider", + "pluginId": "catalog", + "scope": "global", + "settings": { + "version": 2, + "cadence": "PT30M", + "timeoutAfterDuration": "PT2M" + }, + "taskState": { + "status": "idle", + "startsAt": "2025-10-31T21:16:04.145+00:00", + "lastRunEndedAt": "2025-10-31T20:46:09.184+00:00", + "lastRunError": "{\"name\":\"TypeError\",\"message\":\"String.prototype.replaceAll called with a non-global RegExp argument\"}" + }, + "workerState": { + "status": "idle" + } + }, + { + "taskId": "a-provider", + "pluginId": "catalog", + "scope": "global", + "settings": { + "version": 2, + "cadence": "PT60M", + "timeoutAfterDuration": "PT24H" + }, + "taskState": { + "status": "idle", + "startsAt": "2025-10-31T21:16:04.152+00:00", + "lastRunEndedAt": "2025-10-31T20:16:08.713+00:00" + }, + "workerState": { + "status": "idle" + } + }, + { + "taskId": "github-provider:provider123:refresh", + "pluginId": "catalog", + "scope": "global", + "settings": { + "version": 2, + "cadence": "PT24H", + "timeoutAfterDuration": "PT1H" + }, + "taskState": { + "status": "idle", + "startsAt": "2025-11-01T18:16:04.153+00:00", + "lastRunEndedAt": "2025-10-31T18:18:56.176+00:00" + }, + "workerState": { + "status": "idle" + } + }, + { + "taskId": "github-provider:provider123:refresh", + "pluginId": "catalog", + "scope": "global", + "settings": { + "version": 2, + "cadence": "PT24H", + "timeoutAfterDuration": "PT1H" + }, + "taskState": { + "status": "idle", + "startsAt": "2025-11-01T18:16:04.156+00:00", + "lastRunEndedAt": "2025-10-31T18:16:04.264+00:00" + }, + "workerState": { + "status": "idle" + } + }, + { + "taskId": "github-provider:provider234:refresh", + "pluginId": "catalog", + "scope": "global", + "settings": { + "version": 2, + "cadence": "PT24H", + "timeoutAfterDuration": "PT1H" + }, + "taskState": { + "status": "idle", + "startsAt": "2025-11-01T18:16:04.157+00:00", + "lastRunEndedAt": "2025-10-31T18:16:08.564+00:00" + }, + "workerState": { + "status": "idle" + } + }, + { + "taskId": "github-provider:provider567:refresh", + "pluginId": "catalog", + "scope": "global", + "settings": { + "version": 2, + "cadence": "PT24H", + "timeoutAfterDuration": "PT1H" + }, + "taskState": { + "status": "idle", + "startsAt": "2025-11-01T18:16:04.154+00:00", + "lastRunEndedAt": "2025-10-31T18:16:06.978+00:00" + }, + "workerState": { + "status": "idle" + } + }, + { + "taskId": "github-provider:provider910:refresh", + "pluginId": "catalog", + "scope": "global", + "settings": { + "version": 2, + "cadence": "PT24H", + "timeoutAfterDuration": "PT1H" + }, + "taskState": { + "status": "idle", + "startsAt": "2025-11-01T18:16:04.160+00:00", + "lastRunEndedAt": "2025-10-31T18:16:08.573+00:00" + }, + "workerState": { + "status": "idle" + } + }, + { + "taskId": "github-provider:provider000:refresh", + "pluginId": "catalog", + "scope": "global", + "settings": { + "version": 2, + "cadence": "PT24H", + "timeoutAfterDuration": "PT1H" + }, + "taskState": { + "status": "idle", + "startsAt": "2025-11-01T18:16:04.161+00:00", + "lastRunEndedAt": "2025-10-31T18:18:54.339+00:00" + }, + "workerState": { + "status": "idle" + } + }, + { + "taskId": "catalog_orphan_cleanup", + "pluginId": "catalog", + "scope": "global", + "settings": { + "version": 2, + "cadence": "PT30S", + "timeoutAfterDuration": "PT24S" + }, + "taskState": { + "status": "idle", + "startsAt": "2025-10-31T21:06:35.672+00:00", + "lastRunEndedAt": "2025-10-31T21:06:09.106+00:00" + }, + "workerState": { + "status": "idle" + } + } + ] +} diff --git a/plugins/devtools/src/components/Content/ScheduledTasksContent/index.ts b/plugins/devtools/src/components/Content/ScheduledTasksContent/index.ts new file mode 100644 index 0000000000..dff8bcf653 --- /dev/null +++ b/plugins/devtools/src/components/Content/ScheduledTasksContent/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { ScheduledTasksContent } from './ScheduledTasksContent'; +export { ScheduledTaskDetailPanel } from './ScheduledTaskDetailedPanel'; diff --git a/plugins/devtools/src/components/Content/index.ts b/plugins/devtools/src/components/Content/index.ts index f5bd64f7c0..964e7116d9 100644 --- a/plugins/devtools/src/components/Content/index.ts +++ b/plugins/devtools/src/components/Content/index.ts @@ -17,3 +17,4 @@ export * from './ConfigContent'; export * from './InfoContent'; export * from './ExternalDependenciesContent'; +export * from './ScheduledTasksContent'; diff --git a/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx b/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx index b4e5d3db5b..06d7c2ed4e 100644 --- a/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx +++ b/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx @@ -19,13 +19,16 @@ import { devToolsInfoReadPermission, } from '@backstage/plugin-devtools-common'; -import { ConfigContent } from '../Content/ConfigContent'; -import { DevToolsLayout } from '../DevToolsLayout'; -import { InfoContent } from '../Content/InfoContent'; +import { ConfigContent } from '../Content'; +import { devToolsTaskSchedulerReadPermission } from '@backstage/plugin-devtools-common/alpha'; +import { DevToolsLayout } from '../DevToolsLayout/DevToolsLayout'; +import { InfoContent } from '../Content'; import { RequirePermission } from '@backstage/plugin-permission-react'; +import { ScheduledTasksContent } from '../Content/ScheduledTasksContent'; +import { DevToolsPageProps } from '../DevToolsPage'; /** @public */ -export const DefaultDevToolsPage = () => ( +export const DefaultDevToolsPage = ({ contents }: DevToolsPageProps) => ( @@ -37,5 +40,19 @@ export const DefaultDevToolsPage = () => ( + + + + + + {contents?.map((content, index) => ( + + {content.children} + + ))} ); diff --git a/plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx b/plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx index 07a549f8eb..89305b2c1a 100644 --- a/plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx +++ b/plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx @@ -16,9 +16,26 @@ import { useOutlet } from 'react-router-dom'; import { DefaultDevToolsPage } from '../DefaultDevToolsPage'; +import { ReactElement } from 'react'; -export const DevToolsPage = () => { +/** + @public + */ +export interface DevToolsPageProps { + contents?: DevToolsPageContent[]; +} + +/** + @public + */ +export interface DevToolsPageContent { + title: string; + path: string; + children: ReactElement; +} + +export const DevToolsPage = ({ contents }: DevToolsPageProps) => { const outlet = useOutlet(); - return <>{outlet || }; + return <>{outlet || }; }; diff --git a/plugins/devtools/src/components/DevToolsPage/index.ts b/plugins/devtools/src/components/DevToolsPage/index.ts index 88d8bc06fa..a0a4028d10 100644 --- a/plugins/devtools/src/components/DevToolsPage/index.ts +++ b/plugins/devtools/src/components/DevToolsPage/index.ts @@ -14,4 +14,8 @@ * limitations under the License. */ -export { DevToolsPage } from './DevToolsPage'; +export { + DevToolsPage, + type DevToolsPageProps, + type DevToolsPageContent, +} from './DevToolsPage'; diff --git a/plugins/devtools/src/components/index.ts b/plugins/devtools/src/components/index.ts index 9938866bcc..9744a4b801 100644 --- a/plugins/devtools/src/components/index.ts +++ b/plugins/devtools/src/components/index.ts @@ -16,3 +16,7 @@ export * from './Content'; export * from './DevToolsLayout'; +export { + type DevToolsPageProps, + type DevToolsPageContent, +} from './DevToolsPage'; diff --git a/plugins/devtools/src/hooks/index.ts b/plugins/devtools/src/hooks/index.ts index bb2e83d06f..9d212bc3af 100644 --- a/plugins/devtools/src/hooks/index.ts +++ b/plugins/devtools/src/hooks/index.ts @@ -17,3 +17,5 @@ export { useConfig } from './useConfig'; export { useExternalDependencies } from './useExternalDependencies'; export { useInfo } from './useInfo'; +export { useScheduledTasks } from './useScheduledTasks'; +export { useScheduledTasksOperations } from './useScheduledTasksOperations'; diff --git a/plugins/devtools/src/hooks/useScheduledTasks.ts b/plugins/devtools/src/hooks/useScheduledTasks.ts new file mode 100644 index 0000000000..2797526dec --- /dev/null +++ b/plugins/devtools/src/hooks/useScheduledTasks.ts @@ -0,0 +1,44 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { devToolsApiRef } from '../api'; +import { useApi } from '@backstage/core-plugin-api'; +import useAsync from 'react-use/esm/useAsync'; + +export const useScheduledTasks = (plugin: string) => { + const api = useApi(devToolsApiRef); + + const { + value, + loading, + error: asyncError, + } = useAsync(async () => { + return api.getScheduledTasksByPlugin(plugin); + }, [api, plugin]); + + if (asyncError) { + return { + scheduledTasks: undefined, + loading: false, + error: asyncError.message, + }; + } + + return { + scheduledTasks: value?.scheduledTasks, + loading, + error: value?.error, + }; +}; diff --git a/plugins/devtools/src/hooks/useScheduledTasksOperations.ts b/plugins/devtools/src/hooks/useScheduledTasksOperations.ts new file mode 100644 index 0000000000..ff9d02b675 --- /dev/null +++ b/plugins/devtools/src/hooks/useScheduledTasksOperations.ts @@ -0,0 +1,65 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { useState, useCallback } from 'react'; +import { devToolsApiRef } from '../api'; +import { useApi } from '@backstage/core-plugin-api'; + +export const useScheduledTasksOperations = () => { + const api = useApi(devToolsApiRef); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(); + + const triggerTask = useCallback( + async (plugin: string, taskId: string) => { + setIsLoading(true); + setError(undefined); + + try { + await api.triggerScheduledTask(plugin, taskId); + } catch (e) { + setError(e); + throw e; + } finally { + setIsLoading(false); + } + }, + [api], + ); + + const cancelTask = useCallback( + async (plugin: string, taskId: string) => { + setIsLoading(true); + setError(undefined); + + try { + await api.cancelScheduledTask(plugin, taskId); + } catch (e) { + setError(e); + throw e; + } finally { + setIsLoading(false); + } + }, + [api], + ); + + return { + triggerTask, + cancelTask, + isLoading, + error: error?.message, + }; +}; diff --git a/plugins/events-backend-module-aws-sqs/CHANGELOG.md b/plugins/events-backend-module-aws-sqs/CHANGELOG.md index 397e319643..80f3773284 100644 --- a/plugins/events-backend-module-aws-sqs/CHANGELOG.md +++ b/plugins/events-backend-module-aws-sqs/CHANGELOG.md @@ -1,5 +1,123 @@ # @backstage/plugin-events-backend-module-aws-sqs +## 0.4.21 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/config@1.3.7 + - @backstage/plugin-events-node@0.4.21 + +## 0.4.21-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/config@1.3.7-next.0 + - @backstage/plugin-events-node@0.4.21-next.2 + +## 0.4.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## 0.4.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-events-node@0.4.21-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## 0.4.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-events-node@0.4.20 + +## 0.4.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-events-node@0.4.20-next.1 + +## 0.4.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.20-next.0 + +## 0.4.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-events-node@0.4.19 + +## 0.4.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## 0.4.18 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## 0.4.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## 0.4.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## 0.4.17 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + ## 0.4.17-next.1 ### Patch Changes diff --git a/plugins/events-backend-module-aws-sqs/knip-report.md b/plugins/events-backend-module-aws-sqs/knip-report.md index 3f4259cf67..e3b689266c 100644 --- a/plugins/events-backend-module-aws-sqs/knip-report.md +++ b/plugins/events-backend-module-aws-sqs/knip-report.md @@ -2,7 +2,7 @@ ## Unused devDependencies (1) -| Name | Location | Severity | -| :------------- | :----------- | :------- | -| @aws-sdk/types | plugins/events-backend-module-aws-sqs/package.json | error | +| Name | Location | Severity | +| :------------- | :---------------- | :------- | +| @aws-sdk/types | package.json:58:6 | error | diff --git a/plugins/events-backend-module-aws-sqs/package.json b/plugins/events-backend-module-aws-sqs/package.json index afbcd821fc..29822be9e3 100644 --- a/plugins/events-backend-module-aws-sqs/package.json +++ b/plugins/events-backend-module-aws-sqs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-aws-sqs", - "version": "0.4.17-next.1", + "version": "0.4.21", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-azure/CHANGELOG.md b/plugins/events-backend-module-azure/CHANGELOG.md index d2a07c5e2c..04f76e3bcb 100644 --- a/plugins/events-backend-module-azure/CHANGELOG.md +++ b/plugins/events-backend-module-azure/CHANGELOG.md @@ -1,5 +1,101 @@ # @backstage/plugin-events-backend-module-azure +## 0.2.30 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/plugin-events-node@0.4.21 + +## 0.2.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## 0.2.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-events-node@0.4.21-next.0 + +## 0.2.29 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-events-node@0.4.20 + +## 0.2.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-events-node@0.4.20-next.1 + +## 0.2.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/plugin-events-node@0.4.20-next.0 + +## 0.2.28 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-events-node@0.4.19 + +## 0.2.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + +## 0.2.27 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## 0.2.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 0.2.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + +## 0.2.26 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + ## 0.2.26-next.1 ### Patch Changes diff --git a/plugins/events-backend-module-azure/package.json b/plugins/events-backend-module-azure/package.json index 4b699250fa..cc3d4a87d7 100644 --- a/plugins/events-backend-module-azure/package.json +++ b/plugins/events-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-azure", - "version": "0.2.26-next.1", + "version": "0.2.30", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md index cfc5dd7526..347a11bf88 100644 --- a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,101 @@ # @backstage/plugin-events-backend-module-bitbucket-cloud +## 0.2.30 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/plugin-events-node@0.4.21 + +## 0.2.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## 0.2.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-events-node@0.4.21-next.0 + +## 0.2.29 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-events-node@0.4.20 + +## 0.2.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-events-node@0.4.20-next.1 + +## 0.2.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/plugin-events-node@0.4.20-next.0 + +## 0.2.28 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-events-node@0.4.19 + +## 0.2.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + +## 0.2.27 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## 0.2.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 0.2.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + +## 0.2.26 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + ## 0.2.26-next.1 ### Patch Changes diff --git a/plugins/events-backend-module-bitbucket-cloud/package.json b/plugins/events-backend-module-bitbucket-cloud/package.json index 9a7f477b10..c1c7f2136d 100644 --- a/plugins/events-backend-module-bitbucket-cloud/package.json +++ b/plugins/events-backend-module-bitbucket-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-bitbucket-cloud", - "version": "0.2.26-next.1", + "version": "0.2.30", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-bitbucket-server/CHANGELOG.md b/plugins/events-backend-module-bitbucket-server/CHANGELOG.md index c02e3cc244..15979357d7 100644 --- a/plugins/events-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/events-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,101 @@ # @backstage/plugin-events-backend-module-bitbucket-server +## 0.1.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/plugin-events-node@0.4.21 + +## 0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## 0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-events-node@0.4.21-next.0 + +## 0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-events-node@0.4.20 + +## 0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-events-node@0.4.20-next.1 + +## 0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/plugin-events-node@0.4.20-next.0 + +## 0.1.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-events-node@0.4.19 + +## 0.1.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + +## 0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## 0.1.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 0.1.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + +## 0.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + ## 0.1.7-next.1 ### Patch Changes diff --git a/plugins/events-backend-module-bitbucket-server/package.json b/plugins/events-backend-module-bitbucket-server/package.json index a7a7f6caf1..64497b8d15 100644 --- a/plugins/events-backend-module-bitbucket-server/package.json +++ b/plugins/events-backend-module-bitbucket-server/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-bitbucket-server", - "version": "0.1.7-next.1", + "version": "0.1.11", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-gerrit/CHANGELOG.md b/plugins/events-backend-module-gerrit/CHANGELOG.md index b06e283f18..0126641fc4 100644 --- a/plugins/events-backend-module-gerrit/CHANGELOG.md +++ b/plugins/events-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,101 @@ # @backstage/plugin-events-backend-module-gerrit +## 0.2.30 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/plugin-events-node@0.4.21 + +## 0.2.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## 0.2.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-events-node@0.4.21-next.0 + +## 0.2.29 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-events-node@0.4.20 + +## 0.2.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-events-node@0.4.20-next.1 + +## 0.2.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/plugin-events-node@0.4.20-next.0 + +## 0.2.28 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-events-node@0.4.19 + +## 0.2.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + +## 0.2.27 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## 0.2.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 0.2.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + +## 0.2.26 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + ## 0.2.26-next.1 ### Patch Changes diff --git a/plugins/events-backend-module-gerrit/package.json b/plugins/events-backend-module-gerrit/package.json index c45bccd840..1fb7ef8dd6 100644 --- a/plugins/events-backend-module-gerrit/package.json +++ b/plugins/events-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-gerrit", - "version": "0.2.26-next.1", + "version": "0.2.30", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-github/CHANGELOG.md b/plugins/events-backend-module-github/CHANGELOG.md index 9fa76b1bd1..b0a44a763d 100644 --- a/plugins/events-backend-module-github/CHANGELOG.md +++ b/plugins/events-backend-module-github/CHANGELOG.md @@ -1,5 +1,168 @@ # @backstage/plugin-events-backend-module-github +## 0.4.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/integration@2.0.1 + - @backstage/config@1.3.7 + - @backstage/plugin-events-node@0.4.21 + +## 0.4.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.1-next.0 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/config@1.3.7-next.0 + - @backstage/plugin-events-node@0.4.21-next.2 + +## 0.4.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## 0.4.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-events-node@0.4.21-next.0 + - @backstage/config@1.3.6 + - @backstage/integration@2.0.0 + - @backstage/types@1.2.2 + +## 0.4.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/integration@2.0.0 + - @backstage/plugin-events-node@0.4.20 + +## 0.4.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-events-node@0.4.20-next.1 + +## 0.4.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.20-next.0 + +## 0.4.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.20-next.0 + +## 0.4.9 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-events-node@0.4.19 + +## 0.4.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## 0.4.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## 0.4.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/integration@1.19.2 + +## 0.4.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + +## 0.4.7 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## 0.4.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## 0.4.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## 0.4.6 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + ## 0.4.6-next.1 ### Patch Changes diff --git a/plugins/events-backend-module-github/package.json b/plugins/events-backend-module-github/package.json index fa74c6767f..47ba4f82ab 100644 --- a/plugins/events-backend-module-github/package.json +++ b/plugins/events-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-github", - "version": "0.4.6-next.1", + "version": "0.4.11", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-gitlab/CHANGELOG.md b/plugins/events-backend-module-gitlab/CHANGELOG.md index 696f147edc..bbfccc42aa 100644 --- a/plugins/events-backend-module-gitlab/CHANGELOG.md +++ b/plugins/events-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,117 @@ # @backstage/plugin-events-backend-module-gitlab +## 0.3.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/config@1.3.7 + - @backstage/plugin-events-node@0.4.21 + +## 0.3.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/config@1.3.7-next.0 + - @backstage/plugin-events-node@0.4.21-next.2 + +## 0.3.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## 0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-events-node@0.4.21-next.0 + - @backstage/config@1.3.6 + +## 0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-events-node@0.4.20 + +## 0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-events-node@0.4.20-next.1 + +## 0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-events-node@0.4.20-next.0 + +## 0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-events-node@0.4.19 + +## 0.3.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/config@1.3.6 + +## 0.3.8 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## 0.3.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + +## 0.3.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + +## 0.3.7 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + ## 0.3.7-next.1 ### Patch Changes diff --git a/plugins/events-backend-module-gitlab/package.json b/plugins/events-backend-module-gitlab/package.json index 9f8810d87f..92e8f68b26 100644 --- a/plugins/events-backend-module-gitlab/package.json +++ b/plugins/events-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-gitlab", - "version": "0.3.7-next.1", + "version": "0.3.11", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-google-pubsub/CHANGELOG.md b/plugins/events-backend-module-google-pubsub/CHANGELOG.md index 0416ecf6bd..8053a9f5cd 100644 --- a/plugins/events-backend-module-google-pubsub/CHANGELOG.md +++ b/plugins/events-backend-module-google-pubsub/CHANGELOG.md @@ -1,5 +1,143 @@ # @backstage/plugin-events-backend-module-google-pubsub +## 0.2.2 + +### Patch Changes + +- eacf362: Migrated internal metrics in `GooglePubSubConsumingEventPublisher` and `EventConsumingGooglePubSubPublisher` to use the new alpha `MetricsService` +- 79453c0: Updated dependency `wait-for-expect` to `^4.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/errors@1.3.0 + - @backstage/filter-predicates@0.1.2 + - @backstage/config@1.3.7 + - @backstage/plugin-events-node@0.4.21 + +## 0.2.2-next.2 + +### Patch Changes + +- eacf362: Migrated internal metrics in `GooglePubSubConsumingEventPublisher` and `EventConsumingGooglePubSubPublisher` to use the new alpha `MetricsService` +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/config@1.3.7-next.0 + - @backstage/filter-predicates@0.1.2-next.0 + - @backstage/plugin-events-node@0.4.21-next.2 + +## 0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## 0.2.2-next.0 + +### Patch Changes + +- 79453c0: Updated dependency `wait-for-expect` to `^4.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-events-node@0.4.21-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/filter-predicates@0.1.1 + - @backstage/types@1.2.2 + +## 0.2.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/filter-predicates@0.1.1 + - @backstage/plugin-events-node@0.4.20 + +## 0.2.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-events-node@0.4.20-next.1 + +## 0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/filter-predicates@0.1.0 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.20-next.0 + +## 0.2.0 + +### Minor Changes + +- 80905b3: Added an optional `filter` property to PubSub consumers/publishers + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/filter-predicates@0.1.0 + - @backstage/plugin-events-node@0.4.19 + +## 0.1.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## 0.1.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.1.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + ## 0.1.6-next.1 ### Patch Changes diff --git a/plugins/events-backend-module-google-pubsub/config.d.ts b/plugins/events-backend-module-google-pubsub/config.d.ts index 1097b71e00..b70681f61a 100644 --- a/plugins/events-backend-module-google-pubsub/config.d.ts +++ b/plugins/events-backend-module-google-pubsub/config.d.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { FilterPredicate } from '@backstage/filter-predicates'; + export interface Config { events?: { modules?: { @@ -67,6 +69,37 @@ export interface Config { */ targetTopic: string; + /** + * Message filter predicate expression. + * + * @remarks + * + * The value should be a JSON object that represents a filter predicate expression. + * The object being passed to the filter is on the following form: + * + * ```js + * { + * message: { + * // The raw JSON parsed message data + * data: { ... }, + * // The message attributes as key-value pairs + * attributes: { key: 'value', ... }, + * } + * } + * ``` + * + * @example + * + * ```yaml + * filter: + * $any: + * - 'message.attributes.x-github-event': 'push' + * - 'message.attributes.x-github-event': 'repository' + * 'message.data.action': { $in: ['created', 'deleted'] } + * ``` + */ + filter?: FilterPredicate; + /** * Pub/Sub message attributes are by default copied to the event * metadata field. This setting allows you to override or amend @@ -128,6 +161,39 @@ export interface Config { */ targetTopicName: string; + /** + * Event filter predicate expression. + * + * @remarks + * + * The value should be a JSON object that represents a filter predicate expression. + * The object being passed to the filter is on the following form: + * + * ```js + * { + * event: { + * // The event topic + * topic: '...', + * // The raw event payload + * eventPayload: { ... }, + * // The event metadata as key-value pairs + * metadata: { key: 'value', ... }, + * } + * } + * ``` + * + * @example + * + * ```yaml + * filter: + * $any: + * - 'event.topic': 'github.push' + * - 'event.topic': 'github.repository' + * 'event.eventPayload.action': { $in: ['created', 'deleted'] } + * ``` + */ + filter?: FilterPredicate; + /** * Event metadata fields are by default copied to the Pub/Sub * message attribute. This setting allows you to override or amend diff --git a/plugins/events-backend-module-google-pubsub/package.json b/plugins/events-backend-module-google-pubsub/package.json index a64a5363af..f64bf4918d 100644 --- a/plugins/events-backend-module-google-pubsub/package.json +++ b/plugins/events-backend-module-google-pubsub/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-google-pubsub", - "version": "0.1.6-next.1", + "version": "0.2.2", "description": "The google-pubsub backend module for the events plugin.", "backstage": { "role": "backend-plugin-module", @@ -37,17 +37,17 @@ "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", + "@backstage/filter-predicates": "workspace:^", "@backstage/plugin-events-node": "workspace:^", "@backstage/types": "workspace:^", - "@google-cloud/pubsub": "^4.10.0", - "@opentelemetry/api": "^1.9.0" + "@google-cloud/pubsub": "^4.10.0" }, "devDependencies": { "@backstage/backend-defaults": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/plugin-events-backend": "workspace:^", - "wait-for-expect": "^3.0.2" + "wait-for-expect": "^4.0.0" }, "configSchema": "config.d.ts" } diff --git a/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/EventConsumingGooglePubSubPublisher.test.ts b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/EventConsumingGooglePubSubPublisher.test.ts index e5af68add7..23fa0f0763 100644 --- a/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/EventConsumingGooglePubSubPublisher.test.ts +++ b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/EventConsumingGooglePubSubPublisher.test.ts @@ -15,6 +15,7 @@ */ import { mockServices } from '@backstage/backend-test-utils'; +import { metricsServiceMock } from '@backstage/backend-test-utils/alpha'; import { EventParams } from '@backstage/plugin-events-node'; import { PubSub } from '@google-cloud/pubsub'; import waitFor from 'wait-for-expect'; @@ -23,6 +24,7 @@ import { EventConsumingGooglePubSubPublisher } from './EventConsumingGooglePubSu describe('EventConsumingGooglePubSubPublisher', () => { const logger = mockServices.logger.mock(); const events = mockServices.events.mock(); + const metrics = metricsServiceMock.mock(); let onEvent: undefined | ((event: EventParams) => void); events.subscribe.mockImplementation(async options => { @@ -47,13 +49,18 @@ describe('EventConsumingGooglePubSubPublisher', () => { const publisher = new EventConsumingGooglePubSubPublisher({ logger, events, + metrics, tasks: [ { id: 'my-id', sourceTopics: ['my-topic'], targetTopicPattern: 'projects/my-project/topics/my-topic', + filter: () => true, mapToTopic: () => ({ project: 'my-project', topic: 'my-topic' }), - mapToAttributes: m => ({ ...m.metadata, more: 'yes' }), + mapToAttributes: context => ({ + ...context.event.metadata, + more: 'yes', + }), }, ], pubSubFactory, diff --git a/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/EventConsumingGooglePubSubPublisher.ts b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/EventConsumingGooglePubSubPublisher.ts index 4a88bc2e79..280025b531 100644 --- a/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/EventConsumingGooglePubSubPublisher.ts +++ b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/EventConsumingGooglePubSubPublisher.ts @@ -21,9 +21,13 @@ import { } from '@backstage/backend-plugin-api'; import { EventsService } from '@backstage/plugin-events-node'; import { PubSub } from '@google-cloud/pubsub'; -import { Counter, metrics } from '@opentelemetry/api'; +import type { + MetricsService, + MetricsServiceCounter, +} from '@backstage/backend-plugin-api/alpha'; import { readSubscriptionTasksFromConfig } from './config'; -import { SubscriptionTask } from './types'; +import { EventContext, SubscriptionTask } from './types'; +import { JsonValue } from '@backstage/types'; /** * Reads messages off of the events system and forwards them into Google Pub/Sub @@ -34,7 +38,9 @@ export class EventConsumingGooglePubSubPublisher { readonly #events: EventsService; readonly #tasks: SubscriptionTask[]; readonly #pubSubFactory: (projectId: string) => PubSub; - readonly #metrics: { messages: Counter }; + readonly #metrics: { + messages: MetricsServiceCounter<{ subscription: string; status: string }>; + }; #activeClientsByProjectId: Map; static create(options: { @@ -42,10 +48,12 @@ export class EventConsumingGooglePubSubPublisher { logger: LoggerService; rootLifecycle: RootLifecycleService; events: EventsService; + metrics: MetricsService; }) { const publisher = new EventConsumingGooglePubSubPublisher({ logger: options.logger, events: options.events, + metrics: options.metrics, tasks: readSubscriptionTasksFromConfig(options.config), pubSubFactory: projectId => new PubSub({ projectId }), }); @@ -64,6 +72,7 @@ export class EventConsumingGooglePubSubPublisher { constructor(options: { logger: LoggerService; events: EventsService; + metrics: MetricsService; tasks: SubscriptionTask[]; pubSubFactory: (projectId: string) => PubSub; }) { @@ -72,14 +81,13 @@ export class EventConsumingGooglePubSubPublisher { this.#tasks = options.tasks; this.#pubSubFactory = options.pubSubFactory; - const meter = metrics.getMeter('default'); this.#metrics = { - messages: meter.createCounter( + messages: options.metrics.createCounter( 'events.google.pubsub.publisher.messages.total', { description: 'Number of Pub/Sub messages sent by EventConsumingGooglePubSubPublisher', - unit: 'short', + unit: '{message}', }, ), }; @@ -103,7 +111,20 @@ export class EventConsumingGooglePubSubPublisher { onEvent: async event => { let status: 'success' | 'failed' | 'ignored' = 'failed'; try { - const topic = task.mapToTopic(event); + const context: EventContext = { + event: { + topic: event.topic, + eventPayload: event.eventPayload as JsonValue, + metadata: event.metadata, + }, + }; + + if (!task.filter(context)) { + status = 'ignored'; + return; + } + + const topic = task.mapToTopic(context); if (!topic) { status = 'ignored'; return; @@ -117,7 +138,7 @@ export class EventConsumingGooglePubSubPublisher { await pubsub.topic(topic.topic).publishMessage({ json: event.eventPayload, - attributes: task.mapToAttributes(event), + attributes: task.mapToAttributes(context), }); status = 'success'; diff --git a/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/config.test.ts b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/config.test.ts index c3a5c1a5bb..a71f40bc78 100644 --- a/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/config.test.ts +++ b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/config.test.ts @@ -16,6 +16,7 @@ import { mockServices } from '@backstage/backend-test-utils'; import { readSubscriptionTasksFromConfig } from './config'; +import { FilterPredicate } from '@backstage/filter-predicates'; describe('readSubscriptionTasksFromConfig', () => { it('reads with basic targetTopic', () => { @@ -49,6 +50,7 @@ describe('readSubscriptionTasksFromConfig', () => { id: 'subKey1', sourceTopics: ['my-topic'], targetTopicPattern: 'projects/pid/topics/tid', + filter: expect.any(Function), mapToTopic: expect.any(Function), mapToAttributes: expect.any(Function), }, @@ -56,6 +58,7 @@ describe('readSubscriptionTasksFromConfig', () => { id: 'subKey2', sourceTopics: ['my-topic-1', 'my-topic-2'], targetTopicPattern: 'projects/pid/topics/tid.{{ event.topic }}', + filter: expect.any(Function), mapToTopic: expect.any(Function), mapToAttributes: expect.any(Function), }, @@ -63,16 +66,20 @@ describe('readSubscriptionTasksFromConfig', () => { expect( result[0].mapToTopic({ - topic: 'a', - eventPayload: { foo: 'bar' }, - metadata: { attr: 'yes' }, + event: { + topic: 'a', + eventPayload: { foo: 'bar' }, + metadata: { attr: 'yes' }, + }, }), ).toEqual({ project: 'pid', topic: 'tid' }); expect( result[0].mapToAttributes({ - topic: 'a', - eventPayload: { foo: 'bar' }, - metadata: { attr: 'yes' }, + event: { + topic: 'a', + eventPayload: { foo: 'bar' }, + metadata: { attr: 'yes' }, + }, }), ).toEqual({ attr: 'yes' }); }); @@ -116,6 +123,7 @@ describe('readSubscriptionTasksFromConfig', () => { id: 'sub1', sourceTopics: ['my-topic'], targetTopicPattern: 'projects/pid/topics/tid.{{ event.topic }}', + filter: expect.any(Function), mapToTopic: expect.any(Function), mapToAttributes: expect.any(Function), }, @@ -124,6 +132,7 @@ describe('readSubscriptionTasksFromConfig', () => { sourceTopics: ['my-topic'], targetTopicPattern: 'projects/pid/topics/tid.{{ event.metadata.missing }}', + filter: expect.any(Function), mapToTopic: expect.any(Function), mapToAttributes: expect.any(Function), }, @@ -131,16 +140,28 @@ describe('readSubscriptionTasksFromConfig', () => { expect( result[0].mapToTopic({ - topic: 'a', - eventPayload: { foo: 'bar' }, - metadata: { exists: 'exists', attr1: 'original1', attr2: 'original2' }, + event: { + topic: 'a', + eventPayload: { foo: 'bar' }, + metadata: { + exists: 'exists', + attr1: 'original1', + attr2: 'original2', + }, + }, }), ).toEqual({ project: 'pid', topic: 'tid.a' }); // Message attribute existed, successfully routed expect( result[0].mapToAttributes({ - topic: 'a', - eventPayload: { foo: 'bar' }, - metadata: { exists: 'exists', attr1: 'original1', attr2: 'original2' }, + event: { + topic: 'a', + eventPayload: { foo: 'bar' }, + metadata: { + exists: 'exists', + attr1: 'original1', + attr2: 'original2', + }, + }, }), ).toEqual({ exists: 'exists', @@ -150,16 +171,28 @@ describe('readSubscriptionTasksFromConfig', () => { expect( result[1].mapToTopic({ - topic: 'a', - eventPayload: { foo: 'bar' }, - metadata: { exists: 'exists', attr1: 'original1', attr2: 'original2' }, + event: { + topic: 'a', + eventPayload: { foo: 'bar' }, + metadata: { + exists: 'exists', + attr1: 'original1', + attr2: 'original2', + }, + }, }), ).toBeUndefined(); // Message attribute did not exist, could not be routed expect( result[1].mapToAttributes({ - topic: 'a', - eventPayload: { foo: 'bar' }, - metadata: { exists: 'exists', attr1: 'original1', attr2: 'original2' }, + event: { + topic: 'a', + eventPayload: { foo: 'bar' }, + metadata: { + exists: 'exists', + attr1: 'original1', + attr2: 'original2', + }, + }, }), ).toEqual({ exists: 'exists', @@ -169,6 +202,71 @@ describe('readSubscriptionTasksFromConfig', () => { }); }); + it('reads with filter', () => { + const exampleFilter: FilterPredicate = { + 'event.topic': 'push', + }; + + const data = { + events: { + modules: { + googlePubSub: { + eventConsumingGooglePubSubPublisher: { + subscriptions: { + subKey: { + sourceTopic: 'my-topic', + targetTopicName: 'projects/pid/topics/tid.{{ event.topic }}', + filter: exampleFilter, + }, + }, + }, + }, + }, + }, + }; + + const result = readSubscriptionTasksFromConfig( + mockServices.rootConfig({ data }), + ); + expect(result).toEqual([ + { + id: 'subKey', + sourceTopics: ['my-topic'], + targetTopicPattern: 'projects/pid/topics/tid.{{ event.topic }}', + filter: expect.any(Function), + mapToTopic: expect.any(Function), + mapToAttributes: expect.any(Function), + }, + ]); + + expect( + result[0].filter({ + event: { + topic: 'push', + eventPayload: { foo: 'bar' }, + metadata: { + exists: 'exists', + attr1: 'original1', + attr2: 'original2', + }, + }, + }), + ).toBe(true); + expect( + result[0].filter({ + event: { + topic: 'pull_request', + eventPayload: { foo: 'bar' }, + metadata: { + exists: 'exists', + attr1: 'original1', + attr2: 'original2', + }, + }, + }), + ).toBe(false); + }); + it('rejects malformed subscription name', () => { const data = { events: { diff --git a/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/config.ts b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/config.ts index 7ccd78cfcb..fd39130636 100644 --- a/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/config.ts +++ b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/config.ts @@ -17,9 +17,12 @@ import { RootConfigService } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { InputError } from '@backstage/errors'; -import { EventParams } from '@backstage/plugin-events-node'; +import { + readOptionalFilterPredicateFromConfig, + filterPredicateToFilterFunction, +} from '@backstage/filter-predicates'; import { createPatternResolver } from '../util/createPatternResolver'; -import { SubscriptionTask } from './types'; +import { EventContext, SubscriptionTask } from './types'; export function readSubscriptionTasksFromConfig( rootConfig: RootConfigService, @@ -40,6 +43,7 @@ export function readSubscriptionTasksFromConfig( const config = subscriptionsConfig.getConfig(subscriptionId); const sourceTopics = readSourceTopics(config); + const filter = readFilter(config); const mapToTopic = readTopicMapper(config); const mapToAttributes = readAttributeMapper(config); @@ -47,6 +51,7 @@ export function readSubscriptionTasksFromConfig( id: subscriptionId, sourceTopics: sourceTopics, targetTopicPattern: config.getString('targetTopicName'), + filter, mapToTopic, mapToAttributes, }; @@ -60,12 +65,26 @@ function readSourceTopics(config: Config): string[] { return [config.getString('sourceTopic')]; } +/** + * Handles the `filter` configuration field. + */ +function readFilter(config: Config): (context: EventContext) => boolean { + const predicate = readOptionalFilterPredicateFromConfig(config, { + key: 'filter', + }); + if (!predicate) { + return () => true; + } + + return filterPredicateToFilterFunction(predicate); +} + /** * Handles the `targetTopicName` configuration field. */ function readTopicMapper( config: Config, -): (event: EventParams) => { project: string; topic: string } | undefined { +): (context: EventContext) => { project: string; topic: string } | undefined { const regex = /^projects\/([^/]+)\/topics\/(.+)$/; const targetTopicPattern = config.getString('targetTopicName'); @@ -78,9 +97,9 @@ function readTopicMapper( const patternResolver = createPatternResolver(targetTopicPattern); - return event => { + return context => { try { - parts = patternResolver({ event }).match(regex); + parts = patternResolver(context).match(regex); if (!parts) { return undefined; } @@ -100,10 +119,10 @@ function readTopicMapper( */ function readAttributeMapper( config: Config, -): (event: EventParams) => Record { +): (context: EventContext) => Record { const setters = new Array< (options: { - event: EventParams; + context: EventContext; attributes: Record; }) => void >(); @@ -113,9 +132,9 @@ function readAttributeMapper( for (const key of eventMetadata?.keys() ?? []) { const valuePattern = eventMetadata.getString(key); const patternResolver = createPatternResolver(valuePattern); - setters.push(({ event, attributes }) => { + setters.push(({ context, attributes }) => { try { - const value = patternResolver({ event }); + const value = patternResolver(context); if (value) { attributes[key] = value; } @@ -126,9 +145,9 @@ function readAttributeMapper( } } - return event => { + return context => { const result: Record = {}; - for (const [key, value] of Object.entries(event.metadata ?? {})) { + for (const [key, value] of Object.entries(context.event.metadata ?? {})) { if (value) { if (typeof value === 'string') { result[key] = value; @@ -139,7 +158,7 @@ function readAttributeMapper( } } for (const setter of setters) { - setter({ event, attributes: result }); + setter({ context, attributes: result }); } return result; }; diff --git a/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/module.ts b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/module.ts index 498996fee0..db2503389b 100644 --- a/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/module.ts +++ b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/module.ts @@ -18,6 +18,7 @@ import { coreServices, createBackendModule, } from '@backstage/backend-plugin-api'; +import { metricsServiceRef } from '@backstage/backend-plugin-api/alpha'; import { eventsServiceRef } from '@backstage/plugin-events-node'; import { EventConsumingGooglePubSubPublisher } from './EventConsumingGooglePubSubPublisher'; @@ -38,13 +39,15 @@ export const eventsModuleEventConsumingGooglePubSubPublisher = logger: coreServices.logger, rootLifecycle: coreServices.rootLifecycle, events: eventsServiceRef, + metrics: metricsServiceRef, }, - async init({ config, logger, rootLifecycle, events }) { + async init({ config, logger, rootLifecycle, events, metrics }) { EventConsumingGooglePubSubPublisher.create({ config, logger, rootLifecycle, events, + metrics, }); }, }); diff --git a/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/types.ts b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/types.ts index 36b3aeaa78..a106866080 100644 --- a/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/types.ts +++ b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/types.ts @@ -14,7 +14,21 @@ * limitations under the License. */ -import { EventParams } from '@backstage/plugin-events-node'; +import { JsonObject, JsonValue } from '@backstage/types'; + +/** + * The contextual information about an event, as given to + * configured filters and mappers. + */ +export interface EventContext extends JsonObject { + // Actually the raw EventParams; slightly modified payload type to avoid type + // inference issues. + event: { + topic: string; + eventPayload: JsonValue; + metadata?: Record; + }; +} /** * A configured subscription task. @@ -23,8 +37,9 @@ export interface SubscriptionTask { id: string; sourceTopics: string[]; targetTopicPattern: string; + filter: (context: EventContext) => boolean; mapToTopic: ( - event: EventParams, + context: EventContext, ) => { project: string; topic: string } | undefined; - mapToAttributes: (event: EventParams) => Record; + mapToAttributes: (context: EventContext) => Record; } diff --git a/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/GooglePubSubConsumingEventPublisher.test.ts b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/GooglePubSubConsumingEventPublisher.test.ts index 60c6816729..a3c4979aca 100644 --- a/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/GooglePubSubConsumingEventPublisher.test.ts +++ b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/GooglePubSubConsumingEventPublisher.test.ts @@ -15,6 +15,7 @@ */ import { mockServices } from '@backstage/backend-test-utils'; +import { metricsServiceMock } from '@backstage/backend-test-utils/alpha'; import { Message, PubSub } from '@google-cloud/pubsub'; import { GooglePubSubConsumingEventPublisher } from './GooglePubSubConsumingEventPublisher'; import { JsonObject } from '@backstage/types'; @@ -36,6 +37,7 @@ function makeMessage( describe('GooglePubSubConsumingEventPublisher', () => { const logger = mockServices.logger.mock(); const events = mockServices.events.mock(); + const metrics = metricsServiceMock.mock(); let onMessage: undefined | ((message: Message) => void); const subscription = { @@ -62,13 +64,15 @@ describe('GooglePubSubConsumingEventPublisher', () => { const publisher = new GooglePubSubConsumingEventPublisher({ logger, events, + metrics, tasks: [ { id: 'my-id', project: 'my-project', subscription: 'my-subscription', + filter: () => true, mapToTopic: () => 'my-topic', - mapToMetadata: m => ({ ...m.attributes, more: 'yes' }), + mapToMetadata: m => ({ ...m.message.attributes, more: 'yes' }), }, ], pubSubFactory, diff --git a/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/GooglePubSubConsumingEventPublisher.ts b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/GooglePubSubConsumingEventPublisher.ts index 4844c216fb..d04b0ad4ed 100644 --- a/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/GooglePubSubConsumingEventPublisher.ts +++ b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/GooglePubSubConsumingEventPublisher.ts @@ -19,11 +19,16 @@ import { RootConfigService, RootLifecycleService, } from '@backstage/backend-plugin-api'; +import { ForwardedError } from '@backstage/errors'; import { EventParams, EventsService } from '@backstage/plugin-events-node'; +import { JsonValue } from '@backstage/types'; import { Message, PubSub, Subscription } from '@google-cloud/pubsub'; -import { Counter, metrics } from '@opentelemetry/api'; +import type { + MetricsService, + MetricsServiceCounter, +} from '@backstage/backend-plugin-api/alpha'; import { readSubscriptionTasksFromConfig } from './config'; -import { SubscriptionTask } from './types'; +import { MessageContext, SubscriptionTask } from './types'; /** * Reads messages off of Google Pub/Sub subscriptions and forwards them into the @@ -34,7 +39,9 @@ export class GooglePubSubConsumingEventPublisher { readonly #events: EventsService; readonly #tasks: SubscriptionTask[]; readonly #pubSubFactory: (projectId: string) => PubSub; - readonly #metrics: { messages: Counter }; + readonly #metrics: { + messages: MetricsServiceCounter<{ subscription: string; status: string }>; + }; #activeClientsByProjectId: Map; #activeSubscriptions: Subscription[]; @@ -43,10 +50,12 @@ export class GooglePubSubConsumingEventPublisher { logger: LoggerService; rootLifecycle: RootLifecycleService; events: EventsService; + metrics: MetricsService; }) { const publisher = new GooglePubSubConsumingEventPublisher({ logger: options.logger, events: options.events, + metrics: options.metrics, tasks: readSubscriptionTasksFromConfig(options.config), pubSubFactory: projectId => new PubSub({ projectId }), }); @@ -65,6 +74,7 @@ export class GooglePubSubConsumingEventPublisher { constructor(options: { logger: LoggerService; events: EventsService; + metrics: MetricsService; tasks: SubscriptionTask[]; pubSubFactory: (projectId: string) => PubSub; }) { @@ -73,14 +83,13 @@ export class GooglePubSubConsumingEventPublisher { this.#tasks = options.tasks; this.#pubSubFactory = options.pubSubFactory; - const meter = metrics.getMeter('default'); this.#metrics = { - messages: meter.createCounter( + messages: options.metrics.createCounter( 'events.google.pubsub.consumer.messages.total', { description: 'Number of Pub/Sub messages received by GooglePubSubConsumingEventPublisher', - unit: 'short', + unit: '{message}', }, ), }; @@ -197,14 +206,36 @@ export class GooglePubSubConsumingEventPublisher { message: Message, task: SubscriptionTask, ): EventParams | undefined { - const topic = task.mapToTopic(message); + let eventPayload: JsonValue; + try { + eventPayload = JSON.parse(message.data.toString()); + } catch (error) { + throw new ForwardedError('Payload was not valid JSON', error); + } + + const attributes = message.attributes; + + const context: MessageContext = { + message: { + data: eventPayload, + attributes, + }, + }; + + if (!task.filter(context)) { + return undefined; + } + + const topic = task.mapToTopic(context); if (!topic) { return undefined; } + + const metadata = task.mapToMetadata(context); return { topic, - eventPayload: JSON.parse(message.data.toString()), - metadata: task.mapToMetadata(message), + eventPayload, + metadata, }; } } diff --git a/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/config.test.ts b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/config.test.ts index b4034123f0..8303a50d5b 100644 --- a/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/config.test.ts +++ b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/config.test.ts @@ -16,20 +16,20 @@ import { mockServices } from '@backstage/backend-test-utils'; import { JsonObject } from '@backstage/types'; -import { Message } from '@google-cloud/pubsub'; +import { FilterPredicate } from '@backstage/filter-predicates'; import { readSubscriptionTasksFromConfig } from './config'; +import { MessageContext } from './types'; -function makeMessage( +function makeContext( data: JsonObject, attributes: Record, -): Message { - const message: Partial = { - attributes, - data: Buffer.from(JSON.stringify(data), 'utf-8'), - ack: jest.fn(), - nack: jest.fn(), +): MessageContext { + return { + message: { + data, + attributes, + }, }; - return message as Message; } describe('readSubscriptionTasksFromConfig', () => { @@ -60,16 +60,17 @@ describe('readSubscriptionTasksFromConfig', () => { id: 'subKey', project: 'pid', subscription: 'sid', + filter: expect.any(Function), mapToTopic: expect.any(Function), mapToMetadata: expect.any(Function), }, ]); expect( - result[0].mapToTopic(makeMessage({ foo: 'bar' }, { attr: 'yes' })), + result[0].mapToTopic(makeContext({ foo: 'bar' }, { attr: 'yes' })), ).toBe('my-topic'); expect( - result[0].mapToMetadata(makeMessage({ foo: 'bar' }, { attr: 'yes' })), + result[0].mapToMetadata(makeContext({ foo: 'bar' }, { attr: 'yes' })), ).toEqual({ attr: 'yes' }); }); @@ -111,6 +112,7 @@ describe('readSubscriptionTasksFromConfig', () => { id: 'sub1', project: 'pid', subscription: 'sid', + filter: expect.any(Function), mapToTopic: expect.any(Function), mapToMetadata: expect.any(Function), }, @@ -118,6 +120,7 @@ describe('readSubscriptionTasksFromConfig', () => { id: 'sub2', project: 'pid', subscription: 'sid', + filter: expect.any(Function), mapToTopic: expect.any(Function), mapToMetadata: expect.any(Function), }, @@ -125,7 +128,7 @@ describe('readSubscriptionTasksFromConfig', () => { expect( result[0].mapToTopic( - makeMessage( + makeContext( { foo: 'bar' }, { exists: 'exists', meta1: 'original1', meta2: 'original2' }, ), @@ -133,7 +136,7 @@ describe('readSubscriptionTasksFromConfig', () => { ).toBe('t.exists'); // Message attribute existed, successfully routed expect( result[0].mapToMetadata( - makeMessage( + makeContext( { foo: 'bar' }, { exists: 'exists', meta1: 'original1', meta2: 'original2' }, ), @@ -146,7 +149,7 @@ describe('readSubscriptionTasksFromConfig', () => { expect( result[1].mapToTopic( - makeMessage( + makeContext( { foo: 'bar' }, { exists: 'exists', meta1: 'original1', meta2: 'original2' }, ), @@ -154,7 +157,7 @@ describe('readSubscriptionTasksFromConfig', () => { ).toBeUndefined(); // Message attribute did not exist, could not be routed expect( result[1].mapToMetadata( - makeMessage( + makeContext( { foo: 'bar' }, { exists: 'exists', meta1: 'original1', meta2: 'original2' }, ), @@ -167,6 +170,55 @@ describe('readSubscriptionTasksFromConfig', () => { }); }); + const exampleFilter: FilterPredicate = { + 'message.attributes.x-github-event': 'push', + }; + + it('reads with filter', () => { + const data = { + events: { + modules: { + googlePubSub: { + googlePubSubConsumingEventPublisher: { + subscriptions: { + subKey: { + subscriptionName: 'projects/pid/subscriptions/sid', + targetTopic: 'github.{{ message.attributes.x-github-event }}', + filter: exampleFilter, + }, + }, + }, + }, + }, + }, + }; + + const result = readSubscriptionTasksFromConfig( + mockServices.rootConfig({ data }), + ); + expect(result).toEqual([ + { + id: 'subKey', + project: 'pid', + subscription: 'sid', + filter: expect.any(Function), + mapToTopic: expect.any(Function), + mapToMetadata: expect.any(Function), + }, + ]); + + expect( + result[0].filter( + makeContext({ foo: 'bar' }, { 'x-github-event': 'push' }), + ), + ).toBe(true); + expect( + result[0].filter( + makeContext({ foo: 'bar' }, { 'x-github-event': 'pull_request' }), + ), + ).toBe(false); + }); + it('rejects malformed subscription name', () => { const data = { events: { diff --git a/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/config.ts b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/config.ts index d8dfb2ada2..d93a442df7 100644 --- a/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/config.ts +++ b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/config.ts @@ -17,9 +17,12 @@ import { RootConfigService } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { InputError } from '@backstage/errors'; -import { Message } from '@google-cloud/pubsub'; +import { + readOptionalFilterPredicateFromConfig, + filterPredicateToFilterFunction, +} from '@backstage/filter-predicates'; import { createPatternResolver } from '../util/createPatternResolver'; -import { SubscriptionTask } from './types'; +import { MessageContext, SubscriptionTask } from './types'; export function readSubscriptionTasksFromConfig( rootConfig: RootConfigService, @@ -40,6 +43,7 @@ export function readSubscriptionTasksFromConfig( const config = subscriptionsConfig.getConfig(subscriptionId); const { project, subscription } = readSubscriptionName(config); + const filter = readFilter(config); const mapToTopic = readTopicMapper(config); const mapToMetadata = readMetadataMapper(config); @@ -47,12 +51,24 @@ export function readSubscriptionTasksFromConfig( id: subscriptionId, project, subscription, + filter, mapToTopic, mapToMetadata, }; }); } +function readFilter(config: Config): (message: MessageContext) => boolean { + const predicate = readOptionalFilterPredicateFromConfig(config, { + key: 'filter', + }); + if (!predicate) { + return () => true; + } + + return filterPredicateToFilterFunction(predicate); +} + function readSubscriptionName(config: Config): { project: string; subscription: string; @@ -77,12 +93,12 @@ function readSubscriptionName(config: Config): { */ function readTopicMapper( config: Config, -): (message: Message) => string | undefined { +): (message: MessageContext) => string | undefined { const targetTopicPattern = config.getString('targetTopic'); const patternResolver = createPatternResolver(targetTopicPattern); return message => { try { - return patternResolver({ message }); + return patternResolver(message); } catch { // could not map to a topic return undefined; @@ -95,9 +111,12 @@ function readTopicMapper( */ function readMetadataMapper( config: Config, -): (message: Message) => Record { +): (message: MessageContext) => Record { const setters = new Array< - (options: { message: Message; metadata: Record }) => void + (options: { + context: MessageContext; + metadata: Record; + }) => void >(); const eventMetadata = config.getOptionalConfig('eventMetadata'); @@ -105,9 +124,9 @@ function readMetadataMapper( for (const key of eventMetadata?.keys() ?? []) { const valuePattern = eventMetadata.getString(key); const patternResolver = createPatternResolver(valuePattern); - setters.push(({ message, metadata }) => { + setters.push(({ context, metadata }) => { try { - const value = patternResolver({ message }); + const value = patternResolver(context); if (value) { metadata[key] = value; } @@ -118,12 +137,12 @@ function readMetadataMapper( } } - return message => { + return context => { const result: Record = { - ...message.attributes, + ...context.message.attributes, }; for (const setter of setters) { - setter({ message, metadata: result }); + setter({ context, metadata: result }); } return result; }; diff --git a/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/module.ts b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/module.ts index 3d5fce14ee..e76e2f2cb2 100644 --- a/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/module.ts +++ b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/module.ts @@ -18,6 +18,7 @@ import { coreServices, createBackendModule, } from '@backstage/backend-plugin-api'; +import { metricsServiceRef } from '@backstage/backend-plugin-api/alpha'; import { eventsServiceRef } from '@backstage/plugin-events-node'; import { GooglePubSubConsumingEventPublisher } from './GooglePubSubConsumingEventPublisher'; @@ -36,13 +37,15 @@ export const eventsModuleGooglePubsubConsumingEventPublisher = logger: coreServices.logger, rootLifecycle: coreServices.rootLifecycle, events: eventsServiceRef, + metrics: metricsServiceRef, }, - async init({ config, logger, rootLifecycle, events }) { + async init({ config, logger, rootLifecycle, events, metrics }) { GooglePubSubConsumingEventPublisher.create({ config, logger, rootLifecycle, events, + metrics, }); }, }); diff --git a/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/types.ts b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/types.ts index 08c1e11999..113212e038 100644 --- a/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/types.ts +++ b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/types.ts @@ -14,7 +14,23 @@ * limitations under the License. */ -import { Message } from '@google-cloud/pubsub'; +import { JsonObject, JsonValue } from '@backstage/types'; + +/** + * The contextual information about a Google Pub/Sub message, as given to + * configured filters and mappers. + */ +export interface MessageContext extends JsonObject { + /** + * Parsed subset of the original message. + */ + message: { + data: JsonValue; + attributes: { + [key in string]: string; + }; + }; +} /** * A configured subscription task. @@ -23,6 +39,7 @@ export interface SubscriptionTask { id: string; project: string; subscription: string; - mapToTopic: (message: Message) => string | undefined; - mapToMetadata: (message: Message) => Record; + filter: (context: MessageContext) => boolean; + mapToTopic: (context: MessageContext) => string | undefined; + mapToMetadata: (context: MessageContext) => Record; } diff --git a/plugins/events-backend-module-google-pubsub/src/util/createPatternResolver.test.ts b/plugins/events-backend-module-google-pubsub/src/util/createPatternResolver.test.ts index 555ec40476..c2115dee00 100644 --- a/plugins/events-backend-module-google-pubsub/src/util/createPatternResolver.test.ts +++ b/plugins/events-backend-module-google-pubsub/src/util/createPatternResolver.test.ts @@ -40,11 +40,6 @@ describe('createPatternResolver', () => { a: { b: ['first', 'second'] }, }), ).toEqual('-second-'); - expect( - createPatternResolver('-{{ a.b.0 }}-')({ - a: { b: ['first', 'second'] }, - }), - ).toEqual('-first-'); }); it('throws on bad / missing context values', () => { diff --git a/plugins/events-backend-module-google-pubsub/src/util/createPatternResolver.ts b/plugins/events-backend-module-google-pubsub/src/util/createPatternResolver.ts index 01e0b74e2e..5fb414e832 100644 --- a/plugins/events-backend-module-google-pubsub/src/util/createPatternResolver.ts +++ b/plugins/events-backend-module-google-pubsub/src/util/createPatternResolver.ts @@ -15,16 +15,20 @@ */ import { InputError } from '@backstage/errors'; +import { JsonObject, JsonValue } from '@backstage/types'; +import { getJsonValueAtPath } from '@backstage/filter-predicates'; /** * Takes a pattern string that may contain `{{ path.to.value }}` placeholders, - * and returns a function that accepts a context object and returns strings that + * and returns a function that accepts an input object and returns strings that * have had its placeholders filled in by following the dot separated path of - * properties accordingly on the context. + * properties accordingly on the input. + * + * @internal */ -export function createPatternResolver( +export function createPatternResolver( pattern: string, -): (context: TContext) => string { +): (input: JsonObject) => string { // This split results in an array where even elements are static strings // between placeholders, and odd elements are the contents inside // placeholders. @@ -35,7 +39,7 @@ export function createPatternResolver( // ['', 'foo', '-', 'bar', '', 'baz', '.'] const patternParts = pattern.split(/{{\s*([\w\[\]'"_.-]*)\s*}}/g); - const resolvers = new Array<(context: TContext) => string>(); + const resolvers = new Array<(input: JsonObject) => string>(); for (let i = 0; i < patternParts.length; i += 2) { const staticPart = patternParts[i]; @@ -46,9 +50,9 @@ export function createPatternResolver( } if (placeholderPart) { - const getter = createGetter(placeholderPart); - resolvers.push(context => { - const value = getter(context); + const getter = createGetter(placeholderPart); + resolvers.push(input => { + const value = getter(input); if (typeof value === 'string' || Number.isFinite(value)) { return String(value); } else if (!value) { @@ -62,50 +66,61 @@ export function createPatternResolver( } } - return context => resolvers.map(resolver => resolver(context)).join(''); + return input => resolvers.map(resolver => resolver(input)).join(''); } -function createGetter( +/** + * Takes a path string that indexes into an object, and returns a function that + * fetches values out of such objects. + * + * @internal + */ +export function createGetter( path: string, -): (context: TContext) => unknown | undefined { - // The resulti of the split contains quads: +): (input: JsonObject) => JsonValue | undefined { + // The result of the split contains pairs (with maybe no last element): // - // - any "regular" part - // - pure digits that were within brackets, if applicable - // - contents of a single quoted string that was within brackets, if applicable - // - contents of a double quoted string that was within brackets, if applicable + // - any "regular" dot separated parts, if applicable + // - any "exact" match parts within square brackets, if applicable // // For example, the path: // foo.bar[0].baz["qux.e"]a + // // will result in: // [ - // 'foo', undefined, undefined, undefined, - // 'bar', '0', undefined, undefined, - // 'baz', undefined, 'qux.e', undefined, - // 'a' + // 'foo.bar', '0', + // 'baz', 'qux.e', + // 'a', // ] - // and then the empty elements are stripped away const parts = path - .split(/\.|\[(?:(\d+)|'([^']+)'|"([^"]+)")\]\.?/g) + .split(/\[(?:(\d+)|'([^']+)'|"([^"]+)")\]\.?/g) .filter(Boolean); - return (context: TContext): unknown | undefined => { - let current = context; - for (const part of parts) { - if (typeof current !== 'object' || !current) { - return undefined; + return input => { + let current: JsonValue | undefined = input; + for (let i = 0; i < parts.length; i += 2) { + const regularPart = parts[i]; + const exactPart = parts[i + 1]; + + if (regularPart) { + current = getJsonValueAtPath(current, regularPart); } - if (Array.isArray(current)) { - if (!part.match(/^\d+$/)) { + if (exactPart) { + if (typeof current !== 'object' || !current) { return undefined; + } else if (Array.isArray(current)) { + if (exactPart.match(/^\d+$/)) { + current = current[Number(exactPart)]; + } else { + return undefined; + } + } else { + if (!Object.hasOwn(current, exactPart)) { + return undefined; + } + current = current[exactPart]; } - current = (current as any[])[Number(part)]; - } else { - if (!Object.hasOwn(current, part)) { - return undefined; - } - current = (current as any)[part]; } } diff --git a/plugins/events-backend-module-kafka/CHANGELOG.md b/plugins/events-backend-module-kafka/CHANGELOG.md index 711fbced96..73b6f75ae4 100644 --- a/plugins/events-backend-module-kafka/CHANGELOG.md +++ b/plugins/events-backend-module-kafka/CHANGELOG.md @@ -1,5 +1,147 @@ # @backstage/plugin-events-backend-module-kafka +## 0.3.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/config@1.3.7 + - @backstage/plugin-events-node@0.4.21 + +## 0.3.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/config@1.3.7-next.0 + - @backstage/plugin-events-node@0.4.21-next.2 + +## 0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## 0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-events-node@0.4.21-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## 0.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-events-node@0.4.20 + +## 0.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-events-node@0.4.20-next.1 + +## 0.3.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.20-next.0 + +## 0.3.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-events-node@0.4.19 + +## 0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## 0.3.0 + +### Minor Changes + +- ef5bbd8: Add support for Kafka offset configuration (`fromBeginning`) and `autoCommit` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + +## 0.3.0-next.0 + +### Minor Changes + +- ef5bbd8: Add support for Kafka offset configuration (`fromBeginning`) and `autoCommit` + +## 0.2.0 + +### Minor Changes + +- 2c74ea9: Added support for multiple named instances in `kafkaConsumingEventPublisher` configuration. The previous single configuration format is still supported for backward compatibility. +- 2c74ea9: Added `KafkaPublishingEventConsumer` to support sending Backstage events to Kafka topics. + + This addition enables Backstage to publish events to external Kafka systems, complementing the existing ability to receive events from Kafka. This allows for better integration with external systems that rely on Kafka for event streaming. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## 0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## 0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## 0.1.5 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + ## 0.1.5-next.1 ### Patch Changes diff --git a/plugins/events-backend-module-kafka/README.md b/plugins/events-backend-module-kafka/README.md index dbc5bf5607..ebc4b5f292 100644 --- a/plugins/events-backend-module-kafka/README.md +++ b/plugins/events-backend-module-kafka/README.md @@ -2,65 +2,182 @@ Welcome to the `events-backend-module-kafka` backend module! -This package is a module for the `events-backend` backend plugin and extends the events system with an `KafkaConsumingEventPublisher.` +This package is a module for the `events-backend` backend plugin and extends the events system with a `KafkaConsumingEventPublisher` and `KafkaPublishingEventConsumer` -This event publisher will allow you to receive events from an Kafka queue and will publish these to the used `EventsService` implementation. +This module provides two-way integration with Kafka: + +- **KafkaConsumingEventPublisher**: Receives events from Kafka queues and publishes them to the Backstage events system +- **KafkaPublishingEventConsumer**: Consumes events from Backstage and publishes them to Kafka queues ## Configuration -To set up Kafka queues, you need to configure the following values: +To set up Kafka integration, you need to configure one or both of the following components: + +### KafkaConsumingEventPublisher Configuration + +To receive events from Kafka queues and publish them to Backstage: ```yaml events: modules: kafka: kafkaConsumingEventPublisher: - clientId: your-client-id # (Required) Client ID used by Backstage to identify when connecting to the Kafka cluster. - brokers: # (Required) List of brokers in the Kafka cluster to connect to. - - broker1 - - broker2 - topics: - - topic: 'backstage.topic' # (Required) Replace with actual topic name as expected by subscribers - kafka: - topics: # (Required) The Kafka topics to subscribe to. - - topic1 - groupId: your-group-id # (Required) The GroupId to be used by the topic consumers. + production: # Instance name, will be included in logs + clientId: your-client-id # (Required) Client ID used by Backstage to identify when connecting to the Kafka cluster. + brokers: # (Required) List of brokers in the Kafka cluster to connect to. + - broker1 + - broker2 + topics: + - topic: 'backstage.topic' # (Required) Replace with actual topic name as expected by subscribers + kafka: + topics: # (Required) The Kafka topics to subscribe to. + - topic1 + groupId: your-group-id # (Required) The GroupId to be used by the topic consumers. + # Optional offset management settings (these can be omitted to use defaults): + # fromBeginning: false # Start from earliest offset when no committed offset exists. Default: not set (latest) + # autoCommit: true # Enable auto-commit. Default: true (for backward compatibility) + # pauseOnError: false # Pause consumer on error. Default: false (for backward compatibility) +``` + +### KafkaPublishingEventConsumer Configuration + +To publish events from Backstage to Kafka queues, you can configure the `KafkaPublishingEventConsumer`: + +```yaml +events: + modules: + kafka: + kafkaPublishingEventConsumer: + production: # Instance name, will be included in logs + clientId: your-client-id # (Required) Client ID used by Backstage to identify when connecting to the Kafka cluster. + brokers: # (Required) List of brokers in the Kafka cluster to connect to. + - broker1 + - broker2 + topics: + - topic: 'catalog.entity.created' # (Required) The Backstage topic to consume from + kafka: + topic: kafka-topic-name # (Required) The Kafka topic to publish to ``` For a complete list of all available fields that can be configured, refer to the [config.d.ts file](./config.d.ts). +### Offset Management + +The plugin supports configurable offset management to control message delivery semantics: + +#### Auto Commit (Default - Backward Compatible) + +By default (`autoCommit: true` or not specified), Kafka automatically commits offsets at regular intervals. This is the original behavior and ensures backward compatibility. + +#### Manual Commit (Opt-in for Reliability) + +When you explicitly set `autoCommit: false`, the plugin will: + +1. Start consuming from the last committed offset for the consumer group +2. Process each message by publishing it to the Backstage events system +3. Only commit the offset after successful processing +4. If processing fails, pause the consumer and do not commit the offset + +**Example configuration for manual commit:** + +```yaml +kafka: + topics: + - topic1 + groupId: my-group + autoCommit: false # Enable manual commit +``` + +#### Error Handling + +The `pauseOnError` option controls how the consumer behaves when message processing fails: + +**Skip Failed Messages (Default - Backward Compatible)** + +By default (`pauseOnError: false` or not specified), the consumer will skip failed messages and continue processing: + +- The consumer logs the error but continues processing subsequent messages +- If `autoCommit: false`, the offset is still committed to skip the failed message +- If `autoCommit: true`, Kafka's auto-commit handles the offset +- Recommended when occasional message failures are acceptable and should not block processing + +**Pause on Error (Opt-in)** + +When you explicitly set `pauseOnError: true`, the consumer will pause when an error occurs during message processing: + +- The consumer pauses and stops processing new messages +- The failed message offset is not committed +- The error is re-thrown and logged +- Recommended when you want to investigate and fix issues before continuing + +**Example configuration to pause on error:** + +```yaml +kafka: + topics: + - topic1 + groupId: my-group + autoCommit: false + pauseOnError: true # Pause consumer when a message fails +``` + +**Note:** When using the default behavior (`pauseOnError: false`) with `autoCommit: false`, failed messages will have their offsets committed, meaning they will be skipped and not reprocessed. Use this configuration carefully based on your application's requirements. + +#### Starting Position + +The `fromBeginning` option controls where the consumer starts when no committed offset exists: + +- `fromBeginning: true` - Start from the earliest available message +- `fromBeginning: false` (default) - Start from the latest message (only new messages) + +Once the consumer group has committed an offset, it will always resume from that position, regardless of the `fromBeginning` setting. + ### Optional SSL Configuration -If your Kafka cluster requires SSL, you can configure it as follows: +If your Kafka cluster requires SSL, you can configure it for both `kafkaConsumingEventPublisher` and `kafkaPublishingEventConsumer` instances: ```yaml events: modules: kafka: kafkaConsumingEventPublisher: - ssl: - rejectUnauthorized: true # (Optional) If true, the server certificate is verified against the list of supplied CAs. - ca: [path/to/ca-cert] # (Optional) Array of trusted certificates in PEM format. - key: path/to/client-key # (Optional) Private key in PEM format. - cert: path/to/client-cert # (Optional) Public x509 certificate in PEM format. + production: + # ... other configuration ... + ssl: + rejectUnauthorized: true # (Optional) If true, the server certificate is verified against the list of supplied CAs. + ca: [path/to/ca-cert] # (Optional) Array of trusted certificates in PEM format. + key: path/to/client-key # (Optional) Private key in PEM format. + cert: path/to/client-cert # (Optional) Public x509 certificate in PEM format. + kafkaPublishingEventConsumer: + production: + # ... other configuration ... + ssl: + # Same SSL configuration options as above ``` ### Optional SASL Authentication Configuration -If your Kafka cluster requires `SASL` authentication, you can configure it as follows: +If your Kafka cluster requires SASL authentication, you can configure it for both components: ```yaml events: modules: kafka: kafkaConsumingEventPublisher: - sasl: - mechanism: 'plain' # SASL mechanism ('plain', 'scram-sha-256' or 'scram-sha-512') - username: your-username # SASL username - password: your-password # SASL password + production: + # ... other configuration ... + sasl: + mechanism: 'plain' # SASL mechanism ('plain', 'scram-sha-256' or 'scram-sha-512') + username: your-username # SASL username + password: your-password # SASL password + kafkaPublishingEventConsumer: + production: + # ... other configuration ... + sasl: + # Same SASL configuration options as above ``` -This section includes optional `SSL` and `SASL` authentication configuration for enhanced security. +These SSL and SASL configurations apply to both Kafka components and provide enhanced security for your Kafka connections. ## Installation diff --git a/plugins/events-backend-module-kafka/config.d.ts b/plugins/events-backend-module-kafka/config.d.ts index 49c417098c..1413f98a49 100644 --- a/plugins/events-backend-module-kafka/config.d.ts +++ b/plugins/events-backend-module-kafka/config.d.ts @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import { HumanDuration } from '@backstage/types'; export interface Config { @@ -25,179 +24,594 @@ export interface Config { kafka?: { /** * Configuration for KafkaConsumingEventPublisher + * + * Supports either: + * 1. Single configuration object (legacy format) + * 2. Multiple named instances as a record where each key is a unique name for the Kafka instance */ - kafkaConsumingEventPublisher?: { - /** - * (Required) Client ID used by Backstage to identify when connecting to the Kafka cluster. - */ - clientId: string; - /** - * (Required) List of brokers in the Kafka cluster to connect to. - */ - brokers: string[]; - /** - * Optional SSL connection parameters to connect to the cluster. Passed directly to Node tls.connect. - * See https://nodejs.org/dist/latest-v8.x/docs/api/tls.html#tls_tls_createsecurecontext_options - */ - ssl?: - | { - ca?: string[]; + kafkaConsumingEventPublisher?: + | { + /** + * (Required) Client ID used by Backstage to identify when connecting to the Kafka cluster. + */ + clientId: string; + /** + * (Required) List of brokers in the Kafka cluster to connect to. + */ + brokers: string[]; + /** + * Optional SSL connection parameters to connect to the cluster. Passed directly to Node tls.connect. + * See https://nodejs.org/dist/latest-v8.x/docs/api/tls.html#tls_tls_createsecurecontext_options + */ + ssl?: + | { + ca?: string[]; + /** @visibility secret */ + key?: string; + cert?: string; + rejectUnauthorized?: boolean; + } + | boolean; + /** + * Optional SASL connection parameters. + */ + sasl?: { + mechanism: 'plain' | 'scram-sha-256' | 'scram-sha-512'; + username: string; /** @visibility secret */ - key?: string; - cert?: string; - rejectUnauthorized?: boolean; - } - | boolean; - /** - * Optional SASL connection parameters. - */ - sasl?: { - mechanism: 'plain' | 'scram-sha-256' | 'scram-sha-512'; - username: string; - /** @visibility secret */ - password: string; - }; + password: string; + }; - /** - * Optional retry connection parameters. - */ - retry: { - /** - * (Optional) Maximum wait time for a retry - * Default: 30000 ms. - */ - maxRetryTime: HumanDuration | string; - - /** - * (Optional) Initial value used to calculate the retry (This is still randomized following the randomization factor) - * Default: 300 ms. - */ - initialRetryTime: HumanDuration | string; - - /** - * (Optional) Randomization factor - * Default: 0.2. - */ - factor: number; - - /** - * (Optional) Exponential factor - * Default: 2. - */ - multiplier: number; - - /** - * (Optional) Max number of retries per call - * Default: 5. - */ - retries: number; - }; - - /** - * (Optional) Timeout for authentication requests. - * Default: 10000 ms. - */ - authenticationTimeout: HumanDuration | string; - - /** - * (Optional) Time to wait for a successful connection. - * Default: 1000 ms. - */ - connectionTimeout: HumanDuration | string; - - /** - * (Optional) Time to wait for a successful request. - * Default: 30000 ms. - */ - requestTimeout: HumanDuration | string; - - /** - * (Optional) The request timeout can be disabled by setting enforceRequestTimeout to false. - * Default: true - */ - enforceRequestTimeout: boolean; - - /** - * Contains a object per topic for which an Kafka queue - * should be used as source of events. - */ - topics: Array<{ - /** - * (Required) The Backstage topic to publish to - */ - topic: string; - /** - * (Required) KafkaConsumer-related configuration. - */ - kafka: { /** - * (Required) The Kafka topics to subscribe to + * Optional retry connection parameters. */ - topics: string[]; - /** - * (Required) The GroupId to be used by the topic consumers - */ - groupId: string; + retry?: { + /** + * (Optional) Maximum wait time for a retry + * Default: 30000 ms. + */ + maxRetryTime?: HumanDuration | string; + + /** + * (Optional) Initial value used to calculate the retry (This is still randomized following the randomization factor) + * Default: 300 ms. + */ + initialRetryTime?: HumanDuration | string; + + /** + * (Optional) Randomization factor + * Default: 0.2. + */ + factor?: number; + + /** + * (Optional) Exponential factor + * Default: 2. + */ + multiplier?: number; + + /** + * (Optional) Max number of retries per call + * Default: 5. + */ + retries?: number; + }; /** - * (Optional) Timeout used to detect failures. - * The consumer sends periodic heartbeats to indicate its liveness to the broker. - * If no heartbeats are received by the broker before the expiration of this session timeout, - * then the broker will remove this consumer from the group and initiate a rebalance + * (Optional) Timeout for authentication requests. + * Default: 10000 ms. + */ + authenticationTimeout?: HumanDuration | string; + + /** + * (Optional) Time to wait for a successful connection. + * Default: 1000 ms. + */ + connectionTimeout?: HumanDuration | string; + + /** + * (Optional) Time to wait for a successful request. * Default: 30000 ms. */ - sessionTimeout: HumanDuration | string; + requestTimeout?: HumanDuration | string; /** - * (Optional) The maximum time that the coordinator will wait for each member to rejoin when rebalancing the group - * Default: 60000 ms. + * (Optional) The request timeout can be disabled by setting enforceRequestTimeout to false. + * Default: true */ - rebalanceTimeout: HumanDuration | string; + enforceRequestTimeout?: boolean; /** - * (Optional) The expected time between heartbeats to the consumer coordinator. - * Heartbeats are used to ensure that the consumer's session stays active. - * The value must be set lower than session timeout - * Default: 3000 ms. + * Contains an object per topic for which a Kafka queue + * should be used as source of events. */ - heartbeatInterval: HumanDuration | string; + topics: Array<{ + /** + * (Required) The Backstage topic to publish to + */ + topic: string; + /** + * (Required) KafkaConsumer-related configuration. + */ + kafka: { + /** + * (Required) The Kafka topics to subscribe to + */ + topics: string[]; + /** + * (Required) The GroupId to be used by the topic consumers + */ + groupId: string; - /** - * (Optional) The period of time after which we force a refresh of metadata - * even if we haven't seen any partition leadership changes to proactively discover any new brokers or partitions - * Default: 300000 ms (5 minutes). - */ - metadataMaxAge: HumanDuration | string; + /** + * (Optional) Timeout used to detect failures. + * The consumer sends periodic heartbeats to indicate its liveness to the broker. + * If no heartbeats are received by the broker before the expiration of this session timeout, + * then the broker will remove this consumer from the group and initiate a rebalance + * Default: 30000 ms. + */ + sessionTimeout?: HumanDuration | string; - /** - * (Optional) The maximum amount of data per-partition the server will return. - * This size must be at least as large as the maximum message size the server allows - * or else it is possible for the producer to send messages larger than the consumer can fetch. - * If that happens, the consumer can get stuck trying to fetch a large message on a certain partition - * Default: 1048576 (1MB) - */ - maxBytesPerPartition: number; + /** + * (Optional) The maximum time that the coordinator will wait for each member to rejoin when rebalancing the group + * Default: 60000 ms. + */ + rebalanceTimeout?: HumanDuration | string; - /** - * (Optional) Minimum amount of data the server should return for a fetch request, otherwise wait up to maxWaitTime for more data to accumulate. - * Default: 1 - */ - minBytes: number; + /** + * (Optional) The expected time between heartbeats to the consumer coordinator. + * Heartbeats are used to ensure that the consumer's session stays active. + * The value must be set lower than session timeout + * Default: 3000 ms. + */ + heartbeatInterval?: HumanDuration | string; - /** - * (Optional) Maximum amount of bytes to accumulate in the response. Supported by Kafka >= 0.10.1.0 - * Default: 10485760 (10MB) - */ - maxBytes: number; + /** + * (Optional) The period of time after which we force a refresh of metadata + * even if we haven't seen any partition leadership changes to proactively discover any new brokers or partitions + * Default: 300000 ms (5 minutes). + */ + metadataMaxAge?: HumanDuration | string; - /** - * (Optional) The maximum amount of time the server will block before answering the fetch request - * if there isn’t sufficient data to immediately satisfy the requirement given by minBytes - * Default: 5000 - */ - maxWaitTime: HumanDuration | string; + /** + * (Optional) The maximum amount of data per-partition the server will return. + * This size must be at least as large as the maximum message size the server allows + * or else it is possible for the producer to send messages larger than the consumer can fetch. + * If that happens, the consumer can get stuck trying to fetch a large message on a certain partition + * Default: 1048576 (1MB) + */ + maxBytesPerPartition?: number; + + /** + * (Optional) Minimum amount of data the server should return for a fetch request, otherwise wait up to maxWaitTime for more data to accumulate. + * Default: 1 + */ + minBytes?: number; + + /** + * (Optional) Maximum amount of bytes to accumulate in the response. Supported by Kafka >= 0.10.1.0 + * Default: 10485760 (10MB) + */ + maxBytes?: number; + + /** + * (Optional) The maximum amount of time the server will block before answering the fetch request + * if there isn't sufficient data to immediately satisfy the requirement given by minBytes + * Default: 5000 + */ + maxWaitTime?: HumanDuration | string; + + /** + * (Optional) If true, the consumer group will start from the earliest offset when no committed offset is found. + * If false or not specified, it will start from the latest offset. + * Default: undefined (start from latest) + */ + fromBeginning?: boolean; + + /** + * (Optional) Enable auto-commit of offsets. + * When true (default), offsets are automatically committed at regular intervals (at-most-once delivery). + * When false, offsets are only committed after successful message processing (at-least-once delivery). + * Default: true (auto-commit enabled for backward compatibility) + */ + autoCommit?: boolean; + + /** + * (Optional) When true, the consumer will pause on error and stop processing messages. + * When false (default), the consumer will skip failed messages and continue processing. + * Note: When pauseOnError is false and autoCommit is also false, failed messages will still have their offsets committed. + * Default: false (skip errors for backward compatibility) + */ + pauseOnError?: boolean; + }; + }>; + } + | { + [name: string]: { + /** + * (Required) Client ID used by Backstage to identify when connecting to the Kafka cluster. + */ + clientId: string; + /** + * (Required) List of brokers in the Kafka cluster to connect to. + */ + brokers: string[]; + /** + * Optional SSL connection parameters to connect to the cluster. Passed directly to Node tls.connect. + * See https://nodejs.org/dist/latest-v8.x/docs/api/tls.html#tls_tls_createsecurecontext_options + */ + ssl?: + | { + ca?: string[]; + /** @visibility secret */ + key?: string; + cert?: string; + rejectUnauthorized?: boolean; + } + | boolean; + /** + * Optional SASL connection parameters. + */ + sasl?: { + mechanism: 'plain' | 'scram-sha-256' | 'scram-sha-512'; + username: string; + /** @visibility secret */ + password: string; + }; + + /** + * Optional retry connection parameters. + */ + retry?: { + /** + * (Optional) Maximum wait time for a retry + * Default: 30000 ms. + */ + maxRetryTime?: HumanDuration | string; + + /** + * (Optional) Initial value used to calculate the retry (This is still randomized following the randomization factor) + * Default: 300 ms. + */ + initialRetryTime?: HumanDuration | string; + + /** + * (Optional) Randomization factor + * Default: 0.2. + */ + factor?: number; + + /** + * (Optional) Exponential factor + * Default: 2. + */ + multiplier?: number; + + /** + * (Optional) Max number of retries per call + * Default: 5. + */ + retries?: number; + }; + + /** + * (Optional) Timeout for authentication requests. + * Default: 10000 ms. + */ + authenticationTimeout?: HumanDuration | string; + + /** + * (Optional) Time to wait for a successful connection. + * Default: 1000 ms. + */ + connectionTimeout?: HumanDuration | string; + + /** + * (Optional) Time to wait for a successful request. + * Default: 30000 ms. + */ + requestTimeout?: HumanDuration | string; + + /** + * (Optional) The request timeout can be disabled by setting enforceRequestTimeout to false. + * Default: true + */ + enforceRequestTimeout?: boolean; + + /** + * Contains an object per topic for which a Kafka queue + * should be used as source of events. + */ + topics: Array<{ + /** + * (Required) The Backstage topic to publish to + */ + topic: string; + /** + * (Required) KafkaConsumer-related configuration. + */ + kafka: { + /** + * (Required) The Kafka topics to subscribe to + */ + topics: string[]; + /** + * (Required) The GroupId to be used by the topic consumers + */ + groupId: string; + + /** + * (Optional) Timeout used to detect failures. + * The consumer sends periodic heartbeats to indicate its liveness to the broker. + * If no heartbeats are received by the broker before the expiration of this session timeout, + * then the broker will remove this consumer from the group and initiate a rebalance + * Default: 30000 ms. + */ + sessionTimeout?: HumanDuration | string; + + /** + * (Optional) The maximum time that the coordinator will wait for each member to rejoin when rebalancing the group + * Default: 60000 ms. + */ + rebalanceTimeout?: HumanDuration | string; + + /** + * (Optional) The expected time between heartbeats to the consumer coordinator. + * Heartbeats are used to ensure that the consumer's session stays active. + * The value must be set lower than session timeout + * Default: 3000 ms. + */ + heartbeatInterval?: HumanDuration | string; + + /** + * (Optional) The period of time after which we force a refresh of metadata + * even if we haven't seen any partition leadership changes to proactively discover any new brokers or partitions + * Default: 300000 ms (5 minutes). + */ + metadataMaxAge?: HumanDuration | string; + + /** + * (Optional) The maximum amount of data per-partition the server will return. + * This size must be at least as large as the maximum message size the server allows + * or else it is possible for the producer to send messages larger than the consumer can fetch. + * If that happens, the consumer can get stuck trying to fetch a large message on a certain partition + * Default: 1048576 (1MB) + */ + maxBytesPerPartition?: number; + + /** + * (Optional) Minimum amount of data the server should return for a fetch request, otherwise wait up to maxWaitTime for more data to accumulate. + * Default: 1 + */ + minBytes?: number; + + /** + * (Optional) Maximum amount of bytes to accumulate in the response. Supported by Kafka >= 0.10.1.0 + * Default: 10485760 (10MB) + */ + maxBytes?: number; + + /** + * (Optional) The maximum amount of time the server will block before answering the fetch request + * if there isn't sufficient data to immediately satisfy the requirement given by minBytes + * Default: 5000 + */ + maxWaitTime?: HumanDuration | string; + + /** + * (Optional) If true, the consumer group will start from the earliest offset when no committed offset is found. + * If false or not specified, it will start from the latest offset. + * Default: undefined (start from latest) + */ + fromBeginning?: boolean; + + /** + * (Optional) Enable auto-commit of offsets. + * When true (default), offsets are automatically committed at regular intervals (at-most-once delivery). + * When false, offsets are only committed after successful message processing (at-least-once delivery). + * Default: true (auto-commit enabled for backward compatibility) + */ + autoCommit?: boolean; + + /** + * (Optional) When true, the consumer will pause on error and stop processing messages. + * When false (default), the consumer will skip failed messages and continue processing. + * Note: When pauseOnError is false and autoCommit is also false, failed messages will still have their offsets committed. + * Default: false (skip errors for backward compatibility) + */ + pauseOnError?: boolean; + }; + }>; + }; }; - }>; + + /** + * Configuration for KafkaPublishingEventConsumer + * + * Supports multiple named instances as a record where each key is a unique name + * for the Kafka producer configuration. + */ + kafkaPublishingEventConsumer?: { + [name: string]: { + /** + * (Required) Client ID used by Backstage to identify when connecting to the Kafka cluster. + */ + clientId: string; + /** + * (Required) List of brokers in the Kafka cluster to connect to. + */ + brokers: string[]; + /** + * Optional SSL connection parameters to connect to the cluster. Passed directly to Node tls.connect. + * See https://nodejs.org/dist/latest-v8.x/docs/api/tls.html#tls_tls_createsecurecontext_options + */ + ssl?: + | { + ca?: string[]; + /** @visibility secret */ + key?: string; + cert?: string; + rejectUnauthorized?: boolean; + } + | boolean; + /** + * Optional SASL connection parameters. + */ + sasl?: { + mechanism: 'plain' | 'scram-sha-256' | 'scram-sha-512'; + username: string; + /** @visibility secret */ + password: string; + }; + + /** + * Optional retry connection parameters. + */ + retry?: { + /** + * (Optional) Maximum wait time for a retry + * Default: 30000 ms. + */ + maxRetryTime?: HumanDuration | string; + + /** + * (Optional) Initial value used to calculate the retry (This is still randomized following the randomization factor) + * Default: 300 ms. + */ + initialRetryTime?: HumanDuration | string; + + /** + * (Optional) Randomization factor + * Default: 0.2. + */ + factor?: number; + + /** + * (Optional) Exponential factor + * Default: 2. + */ + multiplier?: number; + + /** + * (Optional) Max number of retries per call + * Default: 5. + */ + retries?: number; + }; + + /** + * (Optional) Timeout for authentication requests. + * Default: 10000 ms. + */ + authenticationTimeout?: HumanDuration | string; + + /** + * (Optional) Time to wait for a successful connection. + * Default: 1000 ms. + */ + connectionTimeout?: HumanDuration | string; + + /** + * (Optional) Time to wait for a successful request. + * Default: 30000 ms. + */ + requestTimeout?: HumanDuration | string; + + /** + * (Optional) The request timeout can be disabled by setting enforceRequestTimeout to false. + * Default: true + */ + enforceRequestTimeout?: boolean; + + /** + * Contains an object per topic for which a Kafka queue + * should be used as destination for events. + */ + topics: Array<{ + /** + * (Required) The Backstage topic to consume from + */ + topic: string; + /** + * (Required) KafkaProducer-related configuration. + */ + kafka: { + /** + * (Required) The Kafka topic to publish to + */ + topic: string; + + /** + * (Optional) Allow topic creation when querying metadata for non-existent topics. + * Default: true + */ + allowAutoTopicCreation?: boolean; + + /** + * (Optional) The period of time after which we force a refresh of metadata + * even if we haven't seen any partition leadership changes to proactively discover any new brokers or partitions + * Default: 300000 ms (5 minutes). + */ + metadataMaxAge?: HumanDuration | string; + + /** + * (Optional) The maximum amount of time in ms that the transaction coordinator will wait for a transaction status update + * from the producer before proactively aborting the ongoing transaction. + * If this value is larger than the `transaction.max.timeout.ms`` setting in the broker, the request will fail with a `InvalidTransactionTimeout` error + * Default: 60000 ms. + */ + transactionTimeout?: HumanDuration | string; + + /** + * (Optional) Experimental. If enabled producer will ensure each message is written exactly once. Acks must be set to -1 ("all"). + * Retries will default to MAX_SAFE_INTEGER. + * Default: false. + */ + idempotent?: boolean; + + /** + * (Optional) Max number of requests that may be in progress at any time. If falsey then no limit. + * Default: null. + */ + maxInFlightRequests?: number; + + /** + * Optional retry connection parameters. + */ + retry?: { + /** + * (Optional) Maximum wait time for a retry + * Default: 30000 ms. + */ + maxRetryTime?: HumanDuration | string; + + /** + * (Optional) Initial value used to calculate the retry (This is still randomized following the randomization factor) + * Default: 300 ms. + */ + initialRetryTime?: HumanDuration | string; + + /** + * (Optional) Randomization factor + * Default: 0.2. + */ + factor?: number; + + /** + * (Optional) Exponential factor + * Default: 2. + */ + multiplier?: number; + + /** + * (Optional) Max number of retries per call + * Default: 5. + */ + retries?: number; + }; + }; + }>; + }; }; }; }; diff --git a/plugins/events-backend-module-kafka/package.json b/plugins/events-backend-module-kafka/package.json index 160a56e6cb..2275b5fcfd 100644 --- a/plugins/events-backend-module-kafka/package.json +++ b/plugins/events-backend-module-kafka/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-kafka", - "version": "0.1.5-next.1", + "version": "0.3.3", "description": "The kafka backend module for the events plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/events-backend-module-kafka/report.api.md b/plugins/events-backend-module-kafka/report.api.md index a6b129b82e..c5e6d29c6a 100644 --- a/plugins/events-backend-module-kafka/report.api.md +++ b/plugins/events-backend-module-kafka/report.api.md @@ -6,6 +6,6 @@ import { BackendFeature } from '@backstage/backend-plugin-api'; // @public -const eventsModuleKafkaConsumingEventPublisher: BackendFeature; -export default eventsModuleKafkaConsumingEventPublisher; +const _default: BackendFeature; +export default _default; ``` diff --git a/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/KafkaConsumingEventPublisher.test.ts b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/KafkaConsumingEventPublisher.test.ts new file mode 100644 index 0000000000..b0070166e1 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/KafkaConsumingEventPublisher.test.ts @@ -0,0 +1,504 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { KafkaConsumingEventPublisher } from './KafkaConsumingEventPublisher'; +import { Kafka } from 'kafkajs'; +import { ConfigReader } from '@backstage/config'; +import { mockServices } from '@backstage/backend-test-utils'; + +jest.mock('kafkajs'); + +describe('KafkaConsumingEventPublisher', () => { + const mockLogger = mockServices.logger.mock(); + const mockEvents = mockServices.events.mock(); + + const mockConsumer = { + connect: jest.fn(), + disconnect: jest.fn(), + subscribe: jest.fn(), + run: jest.fn(), + commitOffsets: jest.fn(), + }; + + const mockKafkaClient = { + consumer: jest.fn().mockReturnValue(mockConsumer), + } as unknown as Kafka; + + jest.mocked(Kafka).mockImplementation(() => mockKafkaClient); + + const mockConfig = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaConsumingEventPublisher: { + dev: { + clientId: 'backstage-events', + brokers: ['kafka1:9092', 'kafka2:9092'], + topics: [ + { + topic: 'backstage-topic', + kafka: { + topics: ['test-topic'], + groupId: 'test-group', + }, + }, + ], + }, + }, + }, + }, + }, + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should create instances from config', () => { + const consumers = KafkaConsumingEventPublisher.fromConfig({ + config: mockConfig, + events: mockEvents, + logger: mockLogger, + }); + + expect(consumers).toBeInstanceOf(Array); + expect(consumers).toHaveLength(1); + expect(consumers[0]).toBeInstanceOf(KafkaConsumingEventPublisher); + }); + + it('should return empty array when no config', () => { + const consumers = KafkaConsumingEventPublisher.fromConfig({ + config: new ConfigReader({}), + events: mockEvents, + logger: mockLogger, + }); + + expect(consumers).toEqual([]); + }); + + it('should start all consumers', async () => { + const consumers = KafkaConsumingEventPublisher.fromConfig({ + config: mockConfig, + events: mockEvents, + logger: mockLogger, + }); + + expect(consumers).toHaveLength(1); + + await consumers[0].start(); + + expect(mockConsumer.connect).toHaveBeenCalled(); + expect(mockConsumer.subscribe).toHaveBeenCalledWith({ + topics: ['test-topic'], + }); + expect(mockConsumer.run).toHaveBeenCalled(); + }); + + it('should shutdown all consumers', async () => { + const consumers = KafkaConsumingEventPublisher.fromConfig({ + config: mockConfig, + events: mockEvents, + logger: mockLogger, + }); + + expect(consumers).toHaveLength(1); + + await consumers[0].shutdown(); + + expect(mockConsumer.disconnect).toHaveBeenCalled(); + }); + + it('should handle multiple consumer configs', () => { + const multiConsumerConfig = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaConsumingEventPublisher: { + dev: { + clientId: 'backstage-events', + brokers: ['kafka1:9092'], + topics: [ + { + topic: 'topic1', + kafka: { + topics: ['kafka-topic-1'], + groupId: 'group1', + }, + }, + { + topic: 'topic2', + kafka: { + topics: ['kafka-topic-2'], + groupId: 'group2', + }, + }, + ], + }, + }, + }, + }, + }, + }); + + const consumers = KafkaConsumingEventPublisher.fromConfig({ + config: multiConsumerConfig, + events: mockEvents, + logger: mockLogger, + }); + + expect(consumers).toHaveLength(1); + expect(mockKafkaClient.consumer).toHaveBeenCalledTimes(2); + }); + + describe('Offset Management', () => { + it('should commit offset after successful message processing when autoCommit is false', async () => { + const configWithManualCommit = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaConsumingEventPublisher: { + dev: { + clientId: 'backstage-events', + brokers: ['kafka1:9092'], + topics: [ + { + topic: 'backstage-topic', + kafka: { + topics: ['test-topic'], + groupId: 'test-group', + autoCommit: false, + }, + }, + ], + }, + }, + }, + }, + }, + }); + + const consumers = KafkaConsumingEventPublisher.fromConfig({ + config: configWithManualCommit, + events: mockEvents, + logger: mockLogger, + }); + + mockConsumer.run.mockImplementation(async ({ eachMessage }: any) => { + await eachMessage({ + topic: 'test-kafka-topic', + partition: 2, + message: { + key: Buffer.from('test-key'), + value: Buffer.from(JSON.stringify({ data: 'test-data' })), + offset: '12345', + timestamp: '1234567890', + headers: { + 'custom-header': Buffer.from('header-value'), + }, + }, + heartbeat: jest.fn(), + pause: jest.fn(), + }); + }); + + await consumers[0].start(); + + expect(mockEvents.publish).toHaveBeenCalledWith({ + topic: 'backstage-topic', + eventPayload: { data: 'test-data' }, + metadata: { + 'custom-header': 'header-value', + }, + }); + + // Verify offset + 1 + expect(mockConsumer.commitOffsets).toHaveBeenCalledWith([ + { topic: 'test-kafka-topic', partition: 2, offset: '12346' }, + ]); + }); + + it('should not commit offset when autoCommit is true (default)', async () => { + const configWithAutoCommit = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaConsumingEventPublisher: { + dev: { + clientId: 'backstage-events', + brokers: ['kafka1:9092'], + topics: [ + { + topic: 'backstage-topic', + kafka: { + topics: ['test-topic'], + groupId: 'test-group', + autoCommit: true, + }, + }, + ], + }, + }, + }, + }, + }, + }); + + const consumers = KafkaConsumingEventPublisher.fromConfig({ + config: configWithAutoCommit, + events: mockEvents, + logger: mockLogger, + }); + + mockConsumer.run.mockImplementation(async ({ eachMessage }: any) => { + await eachMessage({ + topic: 'test-kafka-topic', + partition: 2, + message: { + value: Buffer.from(JSON.stringify({ data: 'test-data' })), + offset: '12345', + }, + heartbeat: jest.fn(), + pause: jest.fn(), + }); + }); + + await consumers[0].start(); + + expect(mockEvents.publish).toHaveBeenCalled(); + expect(mockConsumer.commitOffsets).not.toHaveBeenCalled(); + }); + + it('should not commit offset when message processing fails and pauseOnError is true', async () => { + const pauseMock = jest.fn(); + const failingEvents = { + ...mockEvents, + publish: jest.fn().mockRejectedValue(new Error('Processing failed')), + }; + + const configWithPauseOnError = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaConsumingEventPublisher: { + dev: { + clientId: 'backstage-events', + brokers: ['kafka1:9092'], + topics: [ + { + topic: 'backstage-topic', + kafka: { + topics: ['test-topic'], + groupId: 'test-group', + autoCommit: false, + pauseOnError: true, + }, + }, + ], + }, + }, + }, + }, + }, + }); + + const consumers = KafkaConsumingEventPublisher.fromConfig({ + config: configWithPauseOnError, + events: failingEvents, + logger: mockLogger, + }); + + mockConsumer.run.mockImplementation(async ({ eachMessage }: any) => { + await expect( + eachMessage({ + topic: 'test-kafka-topic', + partition: 2, + message: { + value: Buffer.from(JSON.stringify({ data: 'test-data' })), + offset: '12345', + }, + heartbeat: jest.fn(), + pause: pauseMock, + }), + ).rejects.toThrow('Processing failed'); + }); + + await consumers[0].start(); + + expect(mockConsumer.commitOffsets).not.toHaveBeenCalled(); + expect(pauseMock).toHaveBeenCalled(); + }); + + it('should skip failed message and commit offset when pauseOnError is false and autoCommit is false', async () => { + const pauseMock = jest.fn(); + const failingEvents = { + ...mockEvents, + publish: jest.fn().mockRejectedValue(new Error('Processing failed')), + }; + + const configWithSkipOnError = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaConsumingEventPublisher: { + dev: { + clientId: 'backstage-events', + brokers: ['kafka1:9092'], + topics: [ + { + topic: 'backstage-topic', + kafka: { + topics: ['test-topic'], + groupId: 'test-group', + autoCommit: false, + pauseOnError: false, + }, + }, + ], + }, + }, + }, + }, + }, + }); + + const consumers = KafkaConsumingEventPublisher.fromConfig({ + config: configWithSkipOnError, + events: failingEvents, + logger: mockLogger, + }); + + mockConsumer.run.mockImplementation(async ({ eachMessage }: any) => { + await eachMessage({ + topic: 'test-kafka-topic', + partition: 2, + message: { + value: Buffer.from(JSON.stringify({ data: 'test-data' })), + offset: '12345', + }, + heartbeat: jest.fn(), + pause: pauseMock, + }); + }); + + await consumers[0].start(); + + // Should commit offset to skip the failed message + expect(mockConsumer.commitOffsets).toHaveBeenCalledWith([ + { topic: 'test-kafka-topic', partition: 2, offset: '12346' }, + ]); + expect(pauseMock).not.toHaveBeenCalled(); + }); + + it('should skip failed message without committing when pauseOnError is false and autoCommit is true', async () => { + const pauseMock = jest.fn(); + const failingEvents = { + ...mockEvents, + publish: jest.fn().mockRejectedValue(new Error('Processing failed')), + }; + + const configWithAutoCommitSkipOnError = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaConsumingEventPublisher: { + dev: { + clientId: 'backstage-events', + brokers: ['kafka1:9092'], + topics: [ + { + topic: 'backstage-topic', + kafka: { + topics: ['test-topic'], + groupId: 'test-group', + autoCommit: true, + pauseOnError: false, + }, + }, + ], + }, + }, + }, + }, + }, + }); + + const consumers = KafkaConsumingEventPublisher.fromConfig({ + config: configWithAutoCommitSkipOnError, + events: failingEvents, + logger: mockLogger, + }); + + mockConsumer.run.mockImplementation(async ({ eachMessage }: any) => { + await eachMessage({ + topic: 'test-kafka-topic', + partition: 2, + message: { + value: Buffer.from(JSON.stringify({ data: 'test-data' })), + offset: '12345', + }, + heartbeat: jest.fn(), + pause: pauseMock, + }); + }); + + await consumers[0].start(); + + // Should not commit offset (autoCommit handles it) + expect(mockConsumer.commitOffsets).not.toHaveBeenCalled(); + expect(pauseMock).not.toHaveBeenCalled(); + }); + + it('should pass autoCommit setting to consumer.run()', async () => { + const configWithManualCommit = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaConsumingEventPublisher: { + dev: { + clientId: 'backstage-events', + brokers: ['kafka1:9092'], + topics: [ + { + topic: 'backstage-topic', + kafka: { + topics: ['test-topic'], + groupId: 'test-group', + autoCommit: false, + }, + }, + ], + }, + }, + }, + }, + }, + }); + + const consumers = KafkaConsumingEventPublisher.fromConfig({ + config: configWithManualCommit, + events: mockEvents, + logger: mockLogger, + }); + + await consumers[0].start(); + + expect(mockConsumer.run).toHaveBeenCalledWith( + expect.objectContaining({ + autoCommit: false, + }), + ); + }); + }); +}); diff --git a/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/KafkaConsumingEventPublisher.ts b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/KafkaConsumingEventPublisher.ts new file mode 100644 index 0000000000..cfb9af8a4c --- /dev/null +++ b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/KafkaConsumingEventPublisher.ts @@ -0,0 +1,152 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { LoggerService } from '@backstage/backend-plugin-api'; +import { EventsService } from '@backstage/plugin-events-node'; +import { Consumer, Kafka } from 'kafkajs'; +import { + KafkaConsumerConfig, + KafkaConsumingEventPublisherConfig, + readConsumerConfig, +} from './config'; +import { Config } from '@backstage/config'; +import { loggerServiceAdapter } from '../utils/LoggerServiceAdapter'; +import { convertHeadersToMetadata } from '../utils/kafkaTransformers'; + +type KafkaConsumer = { + consumer: Consumer; + config: KafkaConsumerConfig; +}; + +/** + * This class subscribes to Kafka topics and publishes events received to the registered subscriber. + * The message payload will be used as the event payload and passed to the subscribers. + */ +export class KafkaConsumingEventPublisher { + private readonly kafkaConsumers: KafkaConsumer[]; + private readonly logger: LoggerService; + + static fromConfig(env: { + config: Config; + events: EventsService; + logger: LoggerService; + }): KafkaConsumingEventPublisher[] { + const configs = readConsumerConfig(env.config, env.logger); + + return configs.map( + kafkaConfig => + new KafkaConsumingEventPublisher(env.logger, env.events, kafkaConfig), + ); + } + + private constructor( + logger: LoggerService, + private readonly events: EventsService, + config: KafkaConsumingEventPublisherConfig, + ) { + this.logger = logger.child({ + class: KafkaConsumingEventPublisher.prototype.constructor.name, + instance: config.instance, + }); + + const kafka = new Kafka({ + ...config.kafkaConfig, + logCreator: loggerServiceAdapter(this.logger), + }); + + this.kafkaConsumers = config.kafkaConsumerConfigs.map(consumerConfig => ({ + consumer: kafka.consumer(consumerConfig.consumerConfig), + config: consumerConfig, + })); + } + + async start(): Promise { + await Promise.all( + this.kafkaConsumers.map(async ({ consumer, config }) => { + const consumerLogger = this.logger.child({ + id: `events.kafka.publisher:${config.backstageTopic}`, + groupId: config.consumerConfig.groupId, + kafkaTopics: config.consumerSubscribeTopics.topics.toString(), + backstageTopic: config.backstageTopic, + }); + try { + await consumer.connect(); + await consumer.subscribe(config.consumerSubscribeTopics); + + await consumer.run({ + autoCommit: config.autoCommit, + eachMessage: async ({ + topic, + partition, + message, + heartbeat, + pause, + }) => { + try { + await this.events.publish({ + topic: config.backstageTopic, + eventPayload: JSON.parse(message.value?.toString()!), + metadata: convertHeadersToMetadata(message.headers), + }); + + // Only commit offset manually if autoCommit is disabled + if (!config.autoCommit) { + await consumer.commitOffsets([ + { + topic, + partition, + offset: (parseInt(message.offset, 10) + 1).toString(), + }, + ]); + } + + await heartbeat(); + } catch (error: any) { + consumerLogger.error( + `Failed to process message at offset ${message.offset} on partition ${partition} of topic ${topic}`, + error, + ); + + if (config.pauseOnError) { + pause(); + throw error; + } + + // Skip the failed message by committing its offset if autoCommit is disabled + if (!config.autoCommit) { + await consumer.commitOffsets([ + { + topic, + partition, + offset: (parseInt(message.offset, 10) + 1).toString(), + }, + ]); + } + } + }, + }); + } catch (error: any) { + consumerLogger.error('Kafka consumer connection failed', error); + } + }), + ); + } + + async shutdown(): Promise { + await Promise.all( + this.kafkaConsumers.map(({ consumer }) => consumer.disconnect()), + ); + } +} diff --git a/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/config.test.ts b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/config.test.ts new file mode 100644 index 0000000000..0f7bff4dbe --- /dev/null +++ b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/config.test.ts @@ -0,0 +1,416 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ConfigReader } from '@backstage/config'; +import { readConsumerConfig } from './config'; +import { mockServices } from '@backstage/backend-test-utils'; + +const mockLogger = mockServices.logger.mock(); + +describe('readConsumerConfig', () => { + it('not configured', () => { + const publisherConfigs = readConsumerConfig( + new ConfigReader({}), + mockLogger, + ); + + expect(publisherConfigs).toEqual([]); + }); + + it('only required fields configured', () => { + const config = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaConsumingEventPublisher: { + dev: { + clientId: 'backstage-events', + brokers: ['kafka1:9092', 'kafka2:9092'], + topics: [ + { + topic: 'fake1', + kafka: { + topics: ['topic-A'], + groupId: 'my-group', + }, + }, + { + topic: 'fake2', + kafka: { + topics: ['topic-B'], + groupId: 'my-group', + }, + }, + ], + }, + }, + }, + }, + }, + }); + + const publisherConfigs = readConsumerConfig(config, mockLogger); + + expect(publisherConfigs).toBeDefined(); + expect(Array.isArray(publisherConfigs)).toBe(true); + expect(publisherConfigs).toHaveLength(1); + + const devConfig = publisherConfigs[0]; + expect(devConfig.instance).toBe('dev'); + expect(devConfig.kafkaConsumerConfigs.length).toBe(2); + + expect(devConfig.kafkaConfig.clientId).toEqual('backstage-events'); + expect(devConfig.kafkaConfig.brokers).toEqual([ + 'kafka1:9092', + 'kafka2:9092', + ]); + + expect(devConfig.kafkaConsumerConfigs).toEqual([ + { + backstageTopic: 'fake1', + consumerConfig: { + groupId: 'my-group', + }, + consumerSubscribeTopics: { + topics: ['topic-A'], + }, + autoCommit: true, + pauseOnError: false, + }, + { + backstageTopic: 'fake2', + consumerConfig: { + groupId: 'my-group', + }, + consumerSubscribeTopics: { + topics: ['topic-B'], + }, + autoCommit: true, + pauseOnError: false, + }, + ]); + }); + + it('all fields configured', () => { + const config = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaConsumingEventPublisher: { + dev: { + clientId: 'backstage-events', + brokers: ['kafka1:9092', 'kafka2:9092'], + ssl: true, + sasl: { + mechanism: 'plain', + username: 'username', + password: 'password', + }, + retry: { + maxRetryTime: { milliseconds: 20000 }, + initialRetryTime: { milliseconds: 200 }, + factor: '0.4', + multiplier: '4', + retries: '10', + }, + authenticationTimeout: { milliseconds: 20000 }, + connectionTimeout: { milliseconds: 1500 }, + requestTimeout: { milliseconds: 20000 }, + enforceRequestTimeout: false, + topics: [ + { + topic: 'fake1', + kafka: { + topics: ['topic-A'], + groupId: 'my-group', + sessionTimeout: { milliseconds: 20000 }, + rebalanceTimeout: { milliseconds: 50000 }, + heartbeatInterval: { milliseconds: 2000 }, + metadataMaxAge: { milliseconds: 400000 }, + maxBytesPerPartition: 50000, + minBytes: 2, + maxBytes: 500000, + maxWaitTime: { milliseconds: 4000 }, + }, + }, + { + topic: 'fake2', + kafka: { + topics: ['topic-B'], + groupId: 'my-group', + }, + }, + ], + }, + }, + }, + }, + }, + }); + + const publisherConfigs = readConsumerConfig(config, mockLogger); + + expect(publisherConfigs).toBeDefined(); + expect(Array.isArray(publisherConfigs)).toBe(true); + expect(publisherConfigs).toHaveLength(1); + + const devConfig = publisherConfigs[0]; + expect(devConfig.instance).toBe('dev'); + + // Client configuration + expect(devConfig.kafkaConfig.clientId).toEqual('backstage-events'); + expect(devConfig.kafkaConfig.brokers).toEqual([ + 'kafka1:9092', + 'kafka2:9092', + ]); + expect(devConfig.kafkaConfig.ssl).toBeTruthy(); + expect(devConfig.kafkaConfig.sasl).toStrictEqual({ + mechanism: 'plain', + username: 'username', + password: 'password', + }); + expect(devConfig.kafkaConfig.authenticationTimeout).toBe(20000); + expect(devConfig.kafkaConfig.connectionTimeout).toBe(1500); + expect(devConfig.kafkaConfig.requestTimeout).toBe(20000); + expect(devConfig.kafkaConfig.enforceRequestTimeout).toBeFalsy(); + expect(devConfig.kafkaConfig.retry).toStrictEqual({ + maxRetryTime: 20000, + initialRetryTime: 200, + factor: 0.4, + multiplier: 4, + retries: 10, + }); + + // Consumer configuration + expect(devConfig.kafkaConsumerConfigs.length).toBe(2); + + expect(devConfig.kafkaConsumerConfigs).toEqual([ + { + backstageTopic: 'fake1', + consumerConfig: { + groupId: 'my-group', + sessionTimeout: 20000, + rebalanceTimeout: 50000, + heartbeatInterval: 2000, + metadataMaxAge: 400000, + maxBytesPerPartition: 50000, + minBytes: 2, + maxBytes: 500000, + maxWaitTimeInMs: 4000, + }, + consumerSubscribeTopics: { + topics: ['topic-A'], + }, + autoCommit: true, + pauseOnError: false, + }, + { + backstageTopic: 'fake2', + consumerConfig: { + groupId: 'my-group', + }, + consumerSubscribeTopics: { + topics: ['topic-B'], + }, + autoCommit: true, + pauseOnError: false, + }, + ]); + }); + + it('should handle HumanDuration and string values for durations and timeouts', () => { + const config = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaConsumingEventPublisher: { + dev: { + clientId: 'backstage-events', + brokers: ['kafka1:9092', 'kafka2:9092'], + retry: { + maxRetryTime: { seconds: 1 }, + initialRetryTime: { minutes: 1 }, + factor: 0.4, + multiplier: 4, + retries: 10, + }, + authenticationTimeout: { hours: 1 }, + connectionTimeout: { days: 1 }, + topics: [], + requestTimeout: '1m', + }, + }, + }, + }, + }, + }); + + const publisherConfigs = readConsumerConfig(config, mockLogger); + + expect(publisherConfigs).toBeDefined(); + expect(Array.isArray(publisherConfigs)).toBe(true); + expect(publisherConfigs).toHaveLength(1); + + const devConfig = publisherConfigs[0]; + expect(devConfig.instance).toBe('dev'); + + // Client configuration + expect(devConfig.kafkaConfig.clientId).toEqual('backstage-events'); + expect(devConfig.kafkaConfig.brokers).toEqual([ + 'kafka1:9092', + 'kafka2:9092', + ]); + expect(devConfig.kafkaConfig.authenticationTimeout).toBe(3600000); + expect(devConfig.kafkaConfig.connectionTimeout).toBe(86400000); + expect(devConfig.kafkaConfig.requestTimeout).toBe(60000); + expect(devConfig.kafkaConfig.retry).toStrictEqual({ + maxRetryTime: 1000, + initialRetryTime: 60000, + factor: 0.4, + multiplier: 4, + retries: 10, + }); + + // Consumer configuration + expect(devConfig.kafkaConsumerConfigs.length).toBe(0); + }); + + it('single instance configuration (legacy format)', () => { + const config = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaConsumingEventPublisher: { + clientId: 'backstage-events', + brokers: ['kafka1:9092', 'kafka2:9092'], + topics: [ + { + topic: 'fake1', + kafka: { + topics: ['topic-A'], + groupId: 'my-group', + }, + }, + { + topic: 'fake2', + kafka: { + topics: ['topic-B'], + groupId: 'my-group', + }, + }, + ], + }, + }, + }, + }, + }); + + const publisherConfigs = readConsumerConfig(config, mockLogger); + + expect(publisherConfigs).toBeDefined(); + expect(Array.isArray(publisherConfigs)).toBe(true); + expect(publisherConfigs).toHaveLength(1); + + const defaultConfig = publisherConfigs[0]; + expect(defaultConfig.instance).toBe('default'); + expect(defaultConfig.kafkaConsumerConfigs.length).toBe(2); + + expect(defaultConfig.kafkaConfig.clientId).toEqual('backstage-events'); + expect(defaultConfig.kafkaConfig.brokers).toEqual([ + 'kafka1:9092', + 'kafka2:9092', + ]); + + expect(defaultConfig.kafkaConsumerConfigs).toEqual([ + { + backstageTopic: 'fake1', + consumerConfig: { + groupId: 'my-group', + }, + consumerSubscribeTopics: { + topics: ['topic-A'], + }, + autoCommit: true, + pauseOnError: false, + }, + { + backstageTopic: 'fake2', + consumerConfig: { + groupId: 'my-group', + }, + consumerSubscribeTopics: { + topics: ['topic-B'], + }, + autoCommit: true, + pauseOnError: false, + }, + ]); + + // Verify deprecation warning was logged + expect(mockLogger.warn).toHaveBeenCalledWith( + 'Legacy single config format detected at events.modules.kafka.kafkaConsumingEventPublisher.', + ); + }); + + it('offset management fields (autoCommit, pauseOnError, fromBeginning)', () => { + const config = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaConsumingEventPublisher: { + dev: { + clientId: 'backstage-events', + brokers: ['kafka1:9092'], + topics: [ + { + topic: 'fake1', + kafka: { + topics: ['topic-A'], + groupId: 'my-group', + autoCommit: false, + pauseOnError: true, + fromBeginning: true, + }, + }, + ], + }, + }, + }, + }, + }, + }); + + const publisherConfigs = readConsumerConfig(config, mockLogger); + + expect(publisherConfigs).toBeDefined(); + expect(publisherConfigs).toHaveLength(1); + + const devConfig = publisherConfigs[0]; + expect(devConfig.kafkaConsumerConfigs).toEqual([ + { + backstageTopic: 'fake1', + consumerConfig: { + groupId: 'my-group', + }, + consumerSubscribeTopics: { + topics: ['topic-A'], + fromBeginning: true, + }, + autoCommit: false, + pauseOnError: true, + }, + ]); + }); +}); diff --git a/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/config.ts b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/config.ts new file mode 100644 index 0000000000..327bc57255 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/config.ts @@ -0,0 +1,127 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Config } from '@backstage/config'; +import { ConsumerConfig, ConsumerSubscribeTopics, KafkaConfig } from 'kafkajs'; +import { + readKafkaConfig, + readOptionalHumanDurationInMs, +} from '../utils/config'; +import { LoggerService } from '@backstage/backend-plugin-api'; + +export interface KafkaConsumerConfig { + backstageTopic: string; + consumerConfig: ConsumerConfig; + consumerSubscribeTopics: ConsumerSubscribeTopics; + autoCommit: boolean; + pauseOnError: boolean; +} + +export interface KafkaConsumingEventPublisherConfig { + instance: string; + kafkaConfig: KafkaConfig; + kafkaConsumerConfigs: KafkaConsumerConfig[]; +} + +const CONFIG_PREFIX_PUBLISHER = + 'events.modules.kafka.kafkaConsumingEventPublisher'; + +const processSinglePublisher = ( + instanceName: string, + publisherConfig: Config, +): KafkaConsumingEventPublisherConfig => { + return { + instance: instanceName, + kafkaConfig: readKafkaConfig(publisherConfig), + kafkaConsumerConfigs: publisherConfig + .getConfigArray('topics') + .map(topicConfig => { + return { + backstageTopic: topicConfig.getString('topic'), + consumerConfig: { + groupId: topicConfig.getString('kafka.groupId'), + sessionTimeout: readOptionalHumanDurationInMs( + topicConfig, + 'kafka.sessionTimeout', + ), + rebalanceTimeout: readOptionalHumanDurationInMs( + topicConfig, + 'kafka.rebalanceTimeout', + ), + heartbeatInterval: readOptionalHumanDurationInMs( + topicConfig, + 'kafka.heartbeatInterval', + ), + metadataMaxAge: readOptionalHumanDurationInMs( + topicConfig, + 'kafka.metadataMaxAge', + ), + maxBytesPerPartition: topicConfig.getOptionalNumber( + 'kafka.maxBytesPerPartition', + ), + minBytes: topicConfig.getOptionalNumber('kafka.minBytes'), + maxBytes: topicConfig.getOptionalNumber('kafka.maxBytes'), + maxWaitTimeInMs: readOptionalHumanDurationInMs( + topicConfig, + 'kafka.maxWaitTime', + ), + }, + consumerSubscribeTopics: { + topics: topicConfig.getStringArray('kafka.topics'), + fromBeginning: topicConfig.getOptionalBoolean( + 'kafka.fromBeginning', + ), + }, + // Default autoCommit to true to match KafkaJS default and ensure consistency + // between KafkaJS's auto-commit behavior and our manual commit logic + autoCommit: + topicConfig.getOptionalBoolean('kafka.autoCommit') ?? true, + pauseOnError: + topicConfig.getOptionalBoolean('kafka.pauseOnError') ?? false, + }; + }), + }; +}; + +export const readConsumerConfig = ( + config: Config, + logger: LoggerService, +): KafkaConsumingEventPublisherConfig[] => { + const publishersConfig = config.getOptionalConfig(CONFIG_PREFIX_PUBLISHER); + + // Check for legacy single publisher format + if (publishersConfig?.getOptionalString('clientId')) { + logger.warn( + 'Legacy single config format detected at events.modules.kafka.kafkaConsumingEventPublisher.', + ); + return [ + processSinglePublisher( + 'default', // use `default` as instance name for legacy single config + publishersConfig, + ), + ]; + } + + return ( + publishersConfig + ?.keys() + ?.map(publisherKey => + processSinglePublisher( + publisherKey, + publishersConfig.getConfig(publisherKey), + ), + ) ?? [] + ); +}; diff --git a/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/index.ts b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/index.ts new file mode 100644 index 0000000000..94d36a81e0 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/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 { eventsModuleKafkaConsumingEventPublisher } from './module'; diff --git a/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/module.test.ts b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/module.test.ts new file mode 100644 index 0000000000..19ac969800 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/module.test.ts @@ -0,0 +1,122 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createServiceFactory } from '@backstage/backend-plugin-api'; +import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; +import { eventsServiceRef } from '@backstage/plugin-events-node'; +import { TestEventsService } from '@backstage/plugin-events-backend-test-utils'; +import { eventsModuleKafkaConsumingEventPublisher } from './module'; +import { KafkaConsumingEventPublisher } from './KafkaConsumingEventPublisher'; + +jest.mock('./KafkaConsumingEventPublisher'); + +describe('eventsModuleKafkaConsumingEventPublisher', () => { + it('should be correctly wired and set up', async () => { + const events = new TestEventsService(); + const eventsServiceFactory = createServiceFactory({ + service: eventsServiceRef, + deps: {}, + async factory({}) { + return events; + }, + }); + + const mockKafkaConsumingEventPublisher = { + start: jest.fn(), + shutdown: jest.fn(), + } as unknown as KafkaConsumingEventPublisher; + + jest + .mocked(KafkaConsumingEventPublisher.fromConfig) + .mockReturnValue([mockKafkaConsumingEventPublisher]); + + await startTestBackend({ + features: [ + eventsServiceFactory, + eventsModuleKafkaConsumingEventPublisher, + mockServices.rootConfig.factory({ + data: { + events: { + modules: { + kafka: { + kafkaConsumingEventPublisher: { + dev: { + clientId: 'backstage-events', + brokers: ['kafka1:9092', 'kafka2:9092'], + topics: { + fake1: { + kafka: { + topics: ['topic-A'], + groupId: 'my-group', + }, + }, + fake2: { + kafka: { + topics: ['topic-B'], + groupId: 'my-group', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }), + ], + }); + + // Verify that the Kafka consumer client was started + expect(mockKafkaConsumingEventPublisher.start).toHaveBeenCalled(); + + // Verify that the shutdown hook was registered + expect(mockKafkaConsumingEventPublisher.shutdown).not.toHaveBeenCalled(); + }); + + it('should handle empty configuration gracefully', async () => { + const events = new TestEventsService(); + const eventsServiceFactory = createServiceFactory({ + service: eventsServiceRef, + deps: {}, + async factory({}) { + return events; + }, + }); + + jest.mocked(KafkaConsumingEventPublisher.fromConfig).mockReturnValue([]); + + await startTestBackend({ + features: [ + eventsServiceFactory, + eventsModuleKafkaConsumingEventPublisher, + mockServices.rootConfig.factory({ + data: { + events: { + modules: { + kafka: { + // No kafkaConsumingEventPublisher config + }, + }, + }, + }, + }), + ], + }); + + // Verify that fromConfig was called but returned empty array + expect(KafkaConsumingEventPublisher.fromConfig).toHaveBeenCalled(); + }); +}); diff --git a/plugins/events-backend-module-kafka/src/service/eventsModuleKafkaConsumingEventPublisher.ts b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/module.ts similarity index 69% rename from plugins/events-backend-module-kafka/src/service/eventsModuleKafkaConsumingEventPublisher.ts rename to plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/module.ts index b524454ca0..8ad3396c76 100644 --- a/plugins/events-backend-module-kafka/src/service/eventsModuleKafkaConsumingEventPublisher.ts +++ b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/module.ts @@ -17,11 +17,11 @@ import { coreServices, createBackendModule, } from '@backstage/backend-plugin-api'; -import { KafkaConsumerClient } from '../publisher/KafkaConsumerClient'; import { eventsServiceRef } from '@backstage/plugin-events-node'; +import { KafkaConsumingEventPublisher } from './KafkaConsumingEventPublisher'; /** - * Kafka module for the Events plugin. + * Reads messages off of Kafka topics and forwards them into the Backstage events system. * * @public */ @@ -34,22 +34,22 @@ export const eventsModuleKafkaConsumingEventPublisher = createBackendModule({ config: coreServices.rootConfig, events: eventsServiceRef, logger: coreServices.logger, - lifecycle: coreServices.lifecycle, + lifecycle: coreServices.rootLifecycle, }, async init({ config, logger, events, lifecycle }) { - const kafka = KafkaConsumerClient.fromConfig({ + const consumers = KafkaConsumingEventPublisher.fromConfig({ config, events, logger, }); - if (!kafka) { - return; - } + lifecycle.addStartupHook(async () => { + await Promise.all(consumers.map(consumer => consumer.start())); + }); - await kafka.start(); - - lifecycle.addShutdownHook(async () => await kafka.shutdown()); + lifecycle.addShutdownHook(async () => { + await Promise.all(consumers.map(consumer => consumer.shutdown())); + }); }, }); }, diff --git a/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/KafkaPublishingEventConsumer.test.ts b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/KafkaPublishingEventConsumer.test.ts new file mode 100644 index 0000000000..6379605901 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/KafkaPublishingEventConsumer.test.ts @@ -0,0 +1,106 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { KafkaPublishingEventConsumer } from './KafkaPublishingEventConsumer'; +import { Kafka } from 'kafkajs'; +import { mockServices } from '@backstage/backend-test-utils'; +import { ConfigReader } from '@backstage/config'; + +jest.mock('kafkajs'); + +describe('KafkaPublishingEventConsumer', () => { + const mockLogger = mockServices.logger.mock(); + const mockEvents = mockServices.events.mock(); + + const mockProducer = { + connect: jest.fn(), + disconnect: jest.fn(), + send: jest.fn(), + }; + + const mockKafkaClient = { + producer: jest.fn().mockReturnValue(mockProducer), + } as unknown as Kafka; + + jest.mocked(Kafka).mockImplementation(() => mockKafkaClient); + + const mockConfig = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaPublishingEventConsumer: { + dev: { + clientId: 'backstage-events', + brokers: ['kafka1:9092'], + topics: [ + { + topic: 'backstage-topic', + kafka: { + topic: 'kafka-topic', + allowAutoTopicCreation: true, + }, + }, + ], + }, + }, + }, + }, + }, + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should create instances from config', () => { + const consumers = KafkaPublishingEventConsumer.fromConfig({ + config: mockConfig, + events: mockEvents, + logger: mockLogger, + }); + + expect(consumers).toHaveLength(1); + expect(consumers[0]).toBeInstanceOf(KafkaPublishingEventConsumer); + }); + + it('should start the consumer and subscribe to events', async () => { + const consumers = KafkaPublishingEventConsumer.fromConfig({ + config: mockConfig, + events: mockEvents, + logger: mockLogger, + }); + + await consumers[0].start(); + + expect(mockProducer.connect).toHaveBeenCalled(); + expect(mockEvents.subscribe).toHaveBeenCalledWith({ + id: 'kafka:publisher:backstage-topic', + topics: ['backstage-topic'], + onEvent: expect.any(Function), + }); + }); + + it('should shutdown the producer', async () => { + const consumers = KafkaPublishingEventConsumer.fromConfig({ + config: mockConfig, + events: mockEvents, + logger: mockLogger, + }); + + await consumers[0].shutdown(); + + expect(mockProducer.disconnect).toHaveBeenCalled(); + }); +}); diff --git a/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/KafkaPublishingEventConsumer.ts b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/KafkaPublishingEventConsumer.ts new file mode 100644 index 0000000000..f9443f2d69 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/KafkaPublishingEventConsumer.ts @@ -0,0 +1,115 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { LoggerService } from '@backstage/backend-plugin-api'; +import { EventParams, EventsService } from '@backstage/plugin-events-node'; +import { Kafka, Producer } from 'kafkajs'; +import { + KafkaPublisherConfig, + KafkaPublishingEventConsumerConfig, + readPublisherConfig, +} from './config'; +import { Config } from '@backstage/config'; +import { loggerServiceAdapter } from '../utils/LoggerServiceAdapter'; +import { payloadToBuffer } from '../utils/kafkaTransformers'; + +type KafkaPublisher = { + producer: Producer; + config: KafkaPublisherConfig; +}; + +/** + * This class subscribes to Backstage internal events and publishes them to Kafka topics. + * The internal event payload will be serialized and sent to the configured Kafka topic. + */ +export class KafkaPublishingEventConsumer { + private readonly kafkaPublishers: KafkaPublisher[]; + private readonly logger: LoggerService; + + static fromConfig(env: { + config: Config; + events: EventsService; + logger: LoggerService; + }): KafkaPublishingEventConsumer[] { + const configs = readPublisherConfig(env.config); + + return configs.map( + kafkaConfig => + new KafkaPublishingEventConsumer(env.logger, env.events, kafkaConfig), + ); + } + + private constructor( + logger: LoggerService, + private readonly events: EventsService, + config: KafkaPublishingEventConsumerConfig, + ) { + this.logger = logger.child({ + class: KafkaPublishingEventConsumer.prototype.constructor.name, + instance: config.instance, + }); + + const kafka = new Kafka({ + ...config.kafkaConfig, + logCreator: loggerServiceAdapter(this.logger), + }); + + this.kafkaPublishers = config.kafkaPublisherConfigs.map( + publisherConfig => ({ + producer: kafka.producer(publisherConfig.producerConfig), + config: publisherConfig, + }), + ); + } + + async start(): Promise { + await Promise.all( + this.kafkaPublishers.map(async ({ producer, config }) => { + try { + await producer.connect(); + + this.events.subscribe({ + id: `kafka:publisher:${config.backstageTopic}`, + topics: [config.backstageTopic], + onEvent: async (params: EventParams) => { + await producer.send({ + topic: config.kafkaTopic, + messages: [ + { + value: payloadToBuffer(params.eventPayload), + }, + ], + }); + }, + }); + this.logger.info( + `Subscribed to EventService, publishing events to external topic: ${config.backstageTopic}`, + ); + } catch (error: any) { + this.logger.error( + `Kafka producer connection failed for topic ${config.backstageTopic}`, + error, + ); + } + }), + ); + } + + async shutdown(): Promise { + await Promise.all( + this.kafkaPublishers.map(({ producer }) => producer.disconnect()), + ); + } +} diff --git a/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/config.test.ts b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/config.test.ts new file mode 100644 index 0000000000..bd4d622238 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/config.test.ts @@ -0,0 +1,360 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ConfigReader } from '@backstage/config'; +import { readPublisherConfig } from './config'; + +describe('readPublisherConfig', () => { + it('not configured', () => { + const publisherConfigs = readPublisherConfig(new ConfigReader({})); + + expect(publisherConfigs).toEqual([]); + }); + + it('only required fields configured', () => { + const config = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaPublishingEventConsumer: { + dev: { + clientId: 'backstage-events', + brokers: ['kafka1:9092', 'kafka2:9092'], + topics: [ + { + topic: 'fake1', + kafka: { + topic: 'topic-A', + }, + }, + { + topic: 'fake2', + kafka: { + topic: 'topic-B', + }, + }, + ], + }, + }, + }, + }, + }, + }); + + const publisherConfigs = readPublisherConfig(config); + + expect(publisherConfigs).toBeDefined(); + expect(Array.isArray(publisherConfigs)).toBe(true); + expect(publisherConfigs).toHaveLength(1); + + const devConfig = publisherConfigs[0]; + expect(devConfig.instance).toBe('dev'); + expect(devConfig.kafkaPublisherConfigs.length).toBe(2); + + expect(devConfig.kafkaConfig.clientId).toEqual('backstage-events'); + expect(devConfig.kafkaConfig.brokers).toEqual([ + 'kafka1:9092', + 'kafka2:9092', + ]); + + expect(devConfig.kafkaPublisherConfigs).toEqual([ + { + backstageTopic: 'fake1', + kafkaTopic: 'topic-A', + producerConfig: { + allowAutoTopicCreation: undefined, + metadataMaxAge: undefined, + transactionTimeout: undefined, + idempotent: undefined, + maxInFlightRequests: undefined, + retry: {}, + }, + }, + { + backstageTopic: 'fake2', + kafkaTopic: 'topic-B', + producerConfig: { + allowAutoTopicCreation: undefined, + metadataMaxAge: undefined, + transactionTimeout: undefined, + idempotent: undefined, + maxInFlightRequests: undefined, + retry: {}, + }, + }, + ]); + }); + + it('all fields configured', () => { + const config = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaPublishingEventConsumer: { + dev: { + clientId: 'backstage-events', + brokers: ['kafka1:9092', 'kafka2:9092'], + ssl: true, + sasl: { + mechanism: 'plain', + username: 'username', + password: 'password', + }, + retry: { + maxRetryTime: { milliseconds: 20000 }, + initialRetryTime: { milliseconds: 200 }, + factor: '0.4', + multiplier: '4', + retries: '10', + }, + authenticationTimeout: { milliseconds: 20000 }, + connectionTimeout: { milliseconds: 1500 }, + requestTimeout: { milliseconds: 20000 }, + enforceRequestTimeout: false, + topics: [ + { + topic: 'fake1', + kafka: { + topic: 'topic-A', + allowAutoTopicCreation: true, + metadataMaxAge: { milliseconds: 400000 }, + transactionTimeout: { milliseconds: 30000 }, + idempotent: true, + maxInFlightRequests: 5, + retry: { + maxRetryTime: { milliseconds: 15000 }, + initialRetryTime: { milliseconds: 100 }, + factor: '0.2', + multiplier: '2', + retries: '5', + }, + }, + }, + { + topic: 'fake2', + kafka: { + topic: 'topic-B', + }, + }, + ], + }, + }, + }, + }, + }, + }); + + const publisherConfigs = readPublisherConfig(config); + + expect(publisherConfigs).toBeDefined(); + expect(Array.isArray(publisherConfigs)).toBe(true); + expect(publisherConfigs).toHaveLength(1); + + const devConfig = publisherConfigs[0]; + expect(devConfig.instance).toBe('dev'); + + // Client configuration + expect(devConfig.kafkaConfig.clientId).toEqual('backstage-events'); + expect(devConfig.kafkaConfig.brokers).toEqual([ + 'kafka1:9092', + 'kafka2:9092', + ]); + expect(devConfig.kafkaConfig.ssl).toBeTruthy(); + expect(devConfig.kafkaConfig.sasl).toStrictEqual({ + mechanism: 'plain', + username: 'username', + password: 'password', + }); + expect(devConfig.kafkaConfig.authenticationTimeout).toBe(20000); + expect(devConfig.kafkaConfig.connectionTimeout).toBe(1500); + expect(devConfig.kafkaConfig.requestTimeout).toBe(20000); + expect(devConfig.kafkaConfig.enforceRequestTimeout).toBeFalsy(); + expect(devConfig.kafkaConfig.retry).toStrictEqual({ + maxRetryTime: 20000, + initialRetryTime: 200, + factor: 0.4, + multiplier: 4, + retries: 10, + }); + + // Publisher configuration + expect(devConfig.kafkaPublisherConfigs.length).toBe(2); + + expect(devConfig.kafkaPublisherConfigs).toEqual([ + { + backstageTopic: 'fake1', + kafkaTopic: 'topic-A', + producerConfig: { + allowAutoTopicCreation: true, + metadataMaxAge: 400000, + transactionTimeout: 30000, + idempotent: true, + maxInFlightRequests: 5, + retry: { + maxRetryTime: 15000, + initialRetryTime: 100, + factor: 0.2, + multiplier: 2, + retries: 5, + }, + }, + }, + { + backstageTopic: 'fake2', + kafkaTopic: 'topic-B', + producerConfig: { + allowAutoTopicCreation: undefined, + metadataMaxAge: undefined, + transactionTimeout: undefined, + idempotent: undefined, + maxInFlightRequests: undefined, + retry: {}, + }, + }, + ]); + }); + + it('should handle HumanDuration and string values for durations and timeouts', () => { + const config = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaPublishingEventConsumer: { + dev: { + clientId: 'backstage-events', + brokers: ['kafka1:9092', 'kafka2:9092'], + retry: { + maxRetryTime: { seconds: 1 }, + initialRetryTime: { minutes: 1 }, + factor: 0.4, + multiplier: 4, + retries: 10, + }, + authenticationTimeout: { hours: 1 }, + connectionTimeout: { days: 1 }, + topics: [ + { + topic: 'fake1', + kafka: { + topic: 'topic-A', + metadataMaxAge: { seconds: 300 }, + transactionTimeout: '30s', + }, + }, + ], + requestTimeout: '1m', + }, + }, + }, + }, + }, + }); + + const publisherConfigs = readPublisherConfig(config); + + expect(publisherConfigs).toBeDefined(); + expect(Array.isArray(publisherConfigs)).toBe(true); + expect(publisherConfigs).toHaveLength(1); + + const devConfig = publisherConfigs[0]; + expect(devConfig.instance).toBe('dev'); + + // Client configuration + expect(devConfig.kafkaConfig.clientId).toEqual('backstage-events'); + expect(devConfig.kafkaConfig.brokers).toEqual([ + 'kafka1:9092', + 'kafka2:9092', + ]); + expect(devConfig.kafkaConfig.authenticationTimeout).toBe(3600000); + expect(devConfig.kafkaConfig.connectionTimeout).toBe(86400000); + expect(devConfig.kafkaConfig.requestTimeout).toBe(60000); + expect(devConfig.kafkaConfig.retry).toStrictEqual({ + maxRetryTime: 1000, + initialRetryTime: 60000, + factor: 0.4, + multiplier: 4, + retries: 10, + }); + + // Publisher configuration + expect(devConfig.kafkaPublisherConfigs.length).toBe(1); + expect( + devConfig.kafkaPublisherConfigs[0].producerConfig.metadataMaxAge, + ).toBe(300000); + expect( + devConfig.kafkaPublisherConfigs[0].producerConfig.transactionTimeout, + ).toBe(30000); + }); + + it('should handle multiple instances', () => { + const config = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaPublishingEventConsumer: { + dev: { + clientId: 'backstage-dev', + brokers: ['kafka-dev:9092'], + topics: [ + { + topic: 'dev-topic', + kafka: { + topic: 'kafka-dev-topic', + }, + }, + ], + }, + prod: { + clientId: 'backstage-prod', + brokers: ['kafka-prod1:9092', 'kafka-prod2:9092'], + topics: [ + { + topic: 'prod-topic', + kafka: { + topic: 'kafka-prod-topic', + }, + }, + ], + }, + }, + }, + }, + }, + }); + + const publisherConfigs = readPublisherConfig(config); + + expect(publisherConfigs).toBeDefined(); + expect(Array.isArray(publisherConfigs)).toBe(true); + expect(publisherConfigs).toHaveLength(2); + + const devConfig = publisherConfigs.find(c => c.instance === 'dev')!; + expect(devConfig.kafkaConfig.clientId).toBe('backstage-dev'); + expect(devConfig.kafkaConfig.brokers).toEqual(['kafka-dev:9092']); + expect(devConfig.kafkaPublisherConfigs).toHaveLength(1); + expect(devConfig.kafkaPublisherConfigs[0].backstageTopic).toBe('dev-topic'); + + const prodConfig = publisherConfigs.find(c => c.instance === 'prod')!; + expect(prodConfig.kafkaConfig.clientId).toBe('backstage-prod'); + expect(prodConfig.kafkaConfig.brokers).toEqual([ + 'kafka-prod1:9092', + 'kafka-prod2:9092', + ]); + expect(prodConfig.kafkaPublisherConfigs).toHaveLength(1); + expect(prodConfig.kafkaPublisherConfigs[0].backstageTopic).toBe( + 'prod-topic', + ); + }); +}); diff --git a/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/config.ts b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/config.ts new file mode 100644 index 0000000000..8439e24405 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/config.ts @@ -0,0 +1,82 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Config } from '@backstage/config'; +import { + readKafkaConfig, + readOptionalHumanDurationInMs, + readRetryConfig, +} from '../utils/config'; +import { KafkaConfig, ProducerConfig } from 'kafkajs'; + +export interface KafkaPublisherConfig { + backstageTopic: string; + kafkaTopic: string; + producerConfig: ProducerConfig; +} + +export interface KafkaPublishingEventConsumerConfig { + instance: string; + kafkaConfig: KafkaConfig; + kafkaPublisherConfigs: KafkaPublisherConfig[]; +} + +const CONFIG_PREFIX_PUBLISHER = + 'events.modules.kafka.kafkaPublishingEventConsumer'; + +export const readPublisherConfig = ( + config: Config, +): KafkaPublishingEventConsumerConfig[] => { + const publishers = config.getOptionalConfig(CONFIG_PREFIX_PUBLISHER); + + return ( + publishers?.keys()?.map(publisherKey => { + const publisherConfig = publishers.getConfig(publisherKey); + + return { + instance: publisherKey, + kafkaConfig: readKafkaConfig(publisherConfig), + kafkaPublisherConfigs: publisherConfig + .getConfigArray('topics') + .map(topicConfig => { + return { + backstageTopic: topicConfig.getString('topic'), + kafkaTopic: topicConfig.getString('kafka.topic'), + producerConfig: { + allowAutoTopicCreation: topicConfig.getOptionalBoolean( + 'kafka.allowAutoTopicCreation', + ), + metadataMaxAge: readOptionalHumanDurationInMs( + topicConfig, + 'kafka.metadataMaxAge', + ), + transactionTimeout: readOptionalHumanDurationInMs( + topicConfig, + 'kafka.transactionTimeout', + ), + idempotent: topicConfig.getOptionalBoolean('kafka.idempotent'), + maxInFlightRequests: topicConfig.getOptionalNumber( + 'kafka.maxInFlightRequests', + ), + retry: readRetryConfig( + topicConfig.getOptionalConfig('kafka.retry'), + ), + }, + }; + }), + }; + }) ?? [] + ); +}; diff --git a/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/index.ts b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/index.ts new file mode 100644 index 0000000000..42fadd2a5a --- /dev/null +++ b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/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 { eventsModuleKafkaPublishingEventConsumer } from './module'; diff --git a/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/module.test.ts b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/module.test.ts new file mode 100644 index 0000000000..39c8268300 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/module.test.ts @@ -0,0 +1,122 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createServiceFactory } from '@backstage/backend-plugin-api'; +import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; +import { eventsServiceRef } from '@backstage/plugin-events-node'; +import { TestEventsService } from '@backstage/plugin-events-backend-test-utils'; +import { eventsModuleKafkaPublishingEventConsumer } from './module'; +import { KafkaPublishingEventConsumer } from './KafkaPublishingEventConsumer'; + +jest.mock('./KafkaPublishingEventConsumer'); + +describe('eventsModuleKafkaPublishingEventConsumer', () => { + it('should be correctly wired and set up', async () => { + const events = new TestEventsService(); + const eventsServiceFactory = createServiceFactory({ + service: eventsServiceRef, + deps: {}, + async factory({}) { + return events; + }, + }); + + const mockKafkaPublishingEventConsumer = { + start: jest.fn(), + shutdown: jest.fn(), + } as unknown as KafkaPublishingEventConsumer; + + jest + .mocked(KafkaPublishingEventConsumer.fromConfig) + .mockReturnValue([mockKafkaPublishingEventConsumer]); + + await startTestBackend({ + features: [ + eventsServiceFactory, + eventsModuleKafkaPublishingEventConsumer, + mockServices.rootConfig.factory({ + data: { + events: { + modules: { + kafka: { + kafkaPublishingEventConsumer: { + dev: { + clientId: 'backstage-events', + brokers: ['kafka1:9092', 'kafka2:9092'], + topics: [ + { + topic: 'fake1', + kafka: { + topic: 'topic-A', + }, + }, + { + topic: 'fake2', + kafka: { + topic: 'topic-B', + }, + }, + ], + }, + }, + }, + }, + }, + }, + }), + ], + }); + + // Verify that the Kafka publishing event consumer was started + expect(mockKafkaPublishingEventConsumer.start).toHaveBeenCalled(); + + // Verify that the shutdown hook was registered (but not called yet) + expect(mockKafkaPublishingEventConsumer.shutdown).not.toHaveBeenCalled(); + }); + + it('should handle empty configuration gracefully', async () => { + const events = new TestEventsService(); + const eventsServiceFactory = createServiceFactory({ + service: eventsServiceRef, + deps: {}, + async factory({}) { + return events; + }, + }); + + jest.mocked(KafkaPublishingEventConsumer.fromConfig).mockReturnValue([]); + + await startTestBackend({ + features: [ + eventsServiceFactory, + eventsModuleKafkaPublishingEventConsumer, + mockServices.rootConfig.factory({ + data: { + events: { + modules: { + kafka: { + // No kafkaPublishingEventConsumer config + }, + }, + }, + }, + }), + ], + }); + + // Verify that fromConfig was called but returned empty array + expect(KafkaPublishingEventConsumer.fromConfig).toHaveBeenCalled(); + }); +}); diff --git a/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/module.ts b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/module.ts new file mode 100644 index 0000000000..dd5f0c50ab --- /dev/null +++ b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/module.ts @@ -0,0 +1,56 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + coreServices, + createBackendModule, +} from '@backstage/backend-plugin-api'; +import { eventsServiceRef } from '@backstage/plugin-events-node'; +import { KafkaPublishingEventConsumer } from './KafkaPublishingEventConsumer'; + +/** + * Reads internal Backstage events and forwards them to Kafka topics. + * + * @public + */ +export const eventsModuleKafkaPublishingEventConsumer = createBackendModule({ + pluginId: 'events', + moduleId: 'kafka-publishing-event-consumer', + register(env) { + env.registerInit({ + deps: { + config: coreServices.rootConfig, + events: eventsServiceRef, + logger: coreServices.logger, + lifecycle: coreServices.rootLifecycle, + }, + async init({ config, logger, events, lifecycle }) { + const consumers = KafkaPublishingEventConsumer.fromConfig({ + config, + events, + logger, + }); + + lifecycle.addStartupHook(async () => { + await Promise.all(consumers.map(consumer => consumer.start())); + }); + + lifecycle.addShutdownHook(async () => { + await Promise.all(consumers.map(consumer => consumer.shutdown())); + }); + }, + }); + }, +}); diff --git a/plugins/events-backend-module-kafka/src/index.ts b/plugins/events-backend-module-kafka/src/index.ts index 3ba6207d7d..af4fb02bf1 100644 --- a/plugins/events-backend-module-kafka/src/index.ts +++ b/plugins/events-backend-module-kafka/src/index.ts @@ -13,14 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { createBackendFeatureLoader } from '@backstage/backend-plugin-api'; +import { eventsModuleKafkaConsumingEventPublisher } from './KafkaConsumingEventPublisher'; +import { eventsModuleKafkaPublishingEventConsumer } from './KafkaPublishingEventConsumer'; /** * The module "kafka" for the Backstage backend plugin "events" - * adding an Kafka-based publisher, - * receiving events from an Kafka topic and passing it to the - * internal event broker. + * adding Kafka-based event handling: + * - Consumer: receives events from Kafka topics and passes them to the internal event broker + * - Publisher: receives internal events and publishes them to Kafka topics * * @packageDocumentation */ -export { eventsModuleKafkaConsumingEventPublisher as default } from './service/eventsModuleKafkaConsumingEventPublisher'; +export default createBackendFeatureLoader({ + *loader() { + yield eventsModuleKafkaConsumingEventPublisher; + yield eventsModuleKafkaPublishingEventConsumer; + }, +}); diff --git a/plugins/events-backend-module-kafka/src/publisher/KafkaConsumerClient.test.ts b/plugins/events-backend-module-kafka/src/publisher/KafkaConsumerClient.test.ts deleted file mode 100644 index 5824bf4d41..0000000000 --- a/plugins/events-backend-module-kafka/src/publisher/KafkaConsumerClient.test.ts +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2025 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { KafkaConsumerClient } from './KafkaConsumerClient'; -import { ConfigReader } from '@backstage/config'; -import { KafkaConsumingEventPublisher } from './KafkaConsumingEventPublisher'; -import { mockServices } from '@backstage/backend-test-utils'; - -jest.mock('kafkajs'); -jest.mock('./KafkaConsumingEventPublisher'); - -describe('KafkaConsumerClient', () => { - const mockLogger = mockServices.logger.mock(); - const mockEvents = mockServices.events.mock(); - - const mockConfig = new ConfigReader({ - events: { - modules: { - kafka: { - kafkaConsumingEventPublisher: { - clientId: 'backstage-events', - brokers: ['kafka1:9092', 'kafka2:9092'], - topics: [ - { - topic: 'fake1', - kafka: { - topics: ['topic-A'], - groupId: 'my-group', - }, - }, - { - topic: 'fake2', - kafka: { - topics: ['topic-B'], - groupId: 'my-group', - }, - }, - ], - }, - }, - }, - }, - }); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should create an instance from config', () => { - const client = KafkaConsumerClient.fromConfig({ - config: mockConfig, - events: mockEvents, - logger: mockLogger, - }); - - expect(client).toBeInstanceOf(KafkaConsumerClient); - }); - - it('should not create an instance from config', () => { - const client = KafkaConsumerClient.fromConfig({ - config: new ConfigReader({}), - events: mockEvents, - logger: mockLogger, - }); - - expect(client).toBeUndefined(); - }); - - it('should create a consumer for each topic from config', () => { - KafkaConsumerClient.fromConfig({ - config: mockConfig, - events: mockEvents, - logger: mockLogger, - }); - - expect(KafkaConsumingEventPublisher.fromConfig).toHaveBeenCalledTimes(2); - }); - - it('should start all consumers', async () => { - const mockConsumer = { - start: jest.fn().mockResolvedValue(undefined), - }; - (KafkaConsumingEventPublisher.fromConfig as jest.Mock).mockReturnValue( - mockConsumer, - ); - - const client = KafkaConsumerClient.fromConfig({ - config: mockConfig, - events: mockEvents, - logger: mockLogger, - }); - - expect(client).toBeDefined(); - - await client?.start(); - - expect(mockConsumer.start).toHaveBeenCalled(); - }); - - it('should shutdown all consumers', async () => { - const mockConsumer = { - shutdown: jest.fn().mockResolvedValue(undefined), - }; - (KafkaConsumingEventPublisher.fromConfig as jest.Mock).mockReturnValue( - mockConsumer, - ); - - const client = KafkaConsumerClient.fromConfig({ - config: mockConfig, - events: mockEvents, - logger: mockLogger, - }); - - expect(client).toBeDefined(); - - await client?.shutdown(); - - expect(mockConsumer.shutdown).toHaveBeenCalled(); - }); -}); diff --git a/plugins/events-backend-module-kafka/src/publisher/KafkaConsumerClient.ts b/plugins/events-backend-module-kafka/src/publisher/KafkaConsumerClient.ts deleted file mode 100644 index d506e4a6af..0000000000 --- a/plugins/events-backend-module-kafka/src/publisher/KafkaConsumerClient.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2025 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { LoggerService } from '@backstage/backend-plugin-api'; -import { Config } from '@backstage/config'; -import { EventsService } from '@backstage/plugin-events-node'; -import { Kafka } from 'kafkajs'; -import { KafkaEventSourceConfig, readConfig } from './config'; -import { KafkaConsumingEventPublisher } from './KafkaConsumingEventPublisher'; -import { loggerServiceAdapter } from './LoggerServiceAdapter'; - -/** - * KafkaConsumerClient - * - * This class creates the Kafka client that will be used to create the KafkaConsumingEventPublisher - */ -export class KafkaConsumerClient { - private readonly kafka: Kafka; - private readonly consumers: KafkaConsumingEventPublisher[]; - - static fromConfig(options: { - config: Config; - events: EventsService; - logger: LoggerService; - }): KafkaConsumerClient | undefined { - const kafkaConfig = readConfig(options.config); - - if (!kafkaConfig) { - options.logger.info( - 'Kafka consumer not configured, skipping initialization', - ); - return undefined; - } - - return new KafkaConsumerClient(options.logger, options.events, kafkaConfig); - } - - private constructor( - logger: LoggerService, - events: EventsService, - config: KafkaEventSourceConfig, - ) { - this.kafka = new Kafka({ - ...config.kafkaConfig, - logCreator: loggerServiceAdapter(logger), - }); - - this.consumers = config.kafkaConsumerConfigs.map(consumerConfig => - KafkaConsumingEventPublisher.fromConfig({ - kafkaClient: this.kafka, - config: consumerConfig, - logger, - events, - }), - ); - } - - async start(): Promise { - this.consumers.map(async consumer => await consumer.start()); - } - - async shutdown(): Promise { - this.consumers.map(async consumer => await consumer.shutdown()); - } -} diff --git a/plugins/events-backend-module-kafka/src/publisher/KafkaConsumingEventPublisher.test.ts b/plugins/events-backend-module-kafka/src/publisher/KafkaConsumingEventPublisher.test.ts deleted file mode 100644 index 64c53595ad..0000000000 --- a/plugins/events-backend-module-kafka/src/publisher/KafkaConsumingEventPublisher.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2025 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { KafkaConsumingEventPublisher } from './KafkaConsumingEventPublisher'; -import { Kafka } from 'kafkajs'; -import { KafkaConsumerConfig } from './config'; -import { mockServices } from '@backstage/backend-test-utils'; - -jest.mock('kafkajs'); - -describe('KafkaConsumingEventPublisher', () => { - const mockLogger = mockServices.logger.mock(); - const mockEvents = mockServices.events.mock(); - - const mockConsumer = { - connect: jest.fn(), - disconnect: jest.fn(), - subscribe: jest.fn(), - run: jest.fn(), - }; - - const mockKafkaClient = { - consumer: jest.fn().mockReturnValue(mockConsumer), - } as unknown as Kafka; - - const kafkaConsumerConfig: KafkaConsumerConfig = { - consumerConfig: { - groupId: 'test-group', - }, - consumerSubscribeTopics: { - topics: ['test-topic'], - }, - backstageTopic: 'backstage-topic', - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should create an instance from config', () => { - const consumer = KafkaConsumingEventPublisher.fromConfig({ - kafkaClient: mockKafkaClient, - config: kafkaConsumerConfig, - events: mockEvents, - logger: mockLogger, - }); - - expect(consumer).toBeInstanceOf(KafkaConsumingEventPublisher); - }); - - it('should start the consumer', async () => { - const consumer = KafkaConsumingEventPublisher.fromConfig({ - kafkaClient: mockKafkaClient, - config: kafkaConsumerConfig, - events: mockEvents, - logger: mockLogger, - }); - - await consumer.start(); - - expect(mockConsumer.connect).toHaveBeenCalled(); - expect(mockConsumer.subscribe).toHaveBeenCalledWith( - kafkaConsumerConfig.consumerSubscribeTopics, - ); - expect(mockConsumer.run).toHaveBeenCalled(); - }); - - it('should shutdown the consumer', async () => { - const consumer = KafkaConsumingEventPublisher.fromConfig({ - kafkaClient: mockKafkaClient, - config: kafkaConsumerConfig, - events: mockEvents, - logger: mockLogger, - }); - - await consumer.shutdown(); - - expect(mockConsumer.disconnect).toHaveBeenCalled(); - }); -}); diff --git a/plugins/events-backend-module-kafka/src/publisher/KafkaConsumingEventPublisher.ts b/plugins/events-backend-module-kafka/src/publisher/KafkaConsumingEventPublisher.ts deleted file mode 100644 index dfc6186fb5..0000000000 --- a/plugins/events-backend-module-kafka/src/publisher/KafkaConsumingEventPublisher.ts +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2025 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { LoggerService } from '@backstage/backend-plugin-api'; -import { EventParams, EventsService } from '@backstage/plugin-events-node'; -import { Consumer, ConsumerSubscribeTopics, IHeaders, Kafka } from 'kafkajs'; -import { KafkaConsumerConfig } from './config'; - -type EventMetadata = EventParams['metadata']; - -/** - * This class subscribes to Kafka topics and publishes events received to the registered subscriber. - * The message payload will be used as the event payload and passed to the subscribers. - */ -export class KafkaConsumingEventPublisher { - private readonly kafkaConsumer: Consumer; - private readonly consumerSubscribeTopics: ConsumerSubscribeTopics; - private readonly backstageTopic: string; - private readonly logger: LoggerService; - - static fromConfig(env: { - kafkaClient: Kafka; - config: KafkaConsumerConfig; - events: EventsService; - logger: LoggerService; - }): KafkaConsumingEventPublisher { - return new KafkaConsumingEventPublisher( - env.kafkaClient, - env.logger, - env.events, - env.config, - ); - } - - private readonly events: EventsService; - - private constructor( - kafkaClient: Kafka, - logger: LoggerService, - events: EventsService, - config: KafkaConsumerConfig, - ) { - this.events = events; - this.kafkaConsumer = kafkaClient.consumer(config.consumerConfig); - this.consumerSubscribeTopics = config.consumerSubscribeTopics; - this.backstageTopic = config.backstageTopic; - const id = `events.kafka.publisher:${this.backstageTopic}`; - this.logger = logger.child({ - class: KafkaConsumingEventPublisher.prototype.constructor.name, - groupId: config.consumerConfig.groupId, - kafkaTopics: config.consumerSubscribeTopics.topics.toString(), - backstageTopic: config.backstageTopic, - taskId: id, - }); - } - - async start(): Promise { - try { - await this.kafkaConsumer.connect(); - - await this.kafkaConsumer.subscribe(this.consumerSubscribeTopics); - - await this.kafkaConsumer.run({ - eachMessage: async ({ message }) => { - this.events.publish({ - topic: this.backstageTopic, - eventPayload: JSON.parse(message.value?.toString()!), - metadata: this.convertHeadersToMetadata(message.headers), - }); - }, - }); - } catch (error: any) { - this.logger.error('Kafka consumer connection failed ', error); - } - } - - async shutdown(): Promise { - await this.kafkaConsumer.disconnect(); - } - - private convertHeadersToMetadata = ( - headers: IHeaders | undefined, - ): EventParams['metadata'] => { - if (!headers) return undefined; - - const metadata: EventMetadata = {}; - - Object.entries(headers).forEach(([key, value]) => { - // If value is an array use toString() on all values converting any Buffer types to valid strings - if (Array.isArray(value)) metadata[key] = value.map(v => v.toString()); - // Always return the values using toString() to catch all Buffer types that should be converted to strings - else metadata[key] = value?.toString(); - }); - - return metadata; - }; -} diff --git a/plugins/events-backend-module-kafka/src/publisher/config.test.ts b/plugins/events-backend-module-kafka/src/publisher/config.test.ts deleted file mode 100644 index fc7e3049b0..0000000000 --- a/plugins/events-backend-module-kafka/src/publisher/config.test.ts +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright 2025 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { ConfigReader } from '@backstage/config'; -import { readConfig } from './config'; - -describe('readConfig', () => { - it('not configured', () => { - const publisherConfigs = readConfig(new ConfigReader({})); - - expect(publisherConfigs).toBeUndefined(); - }); - - it('only required fields configured', () => { - const config = new ConfigReader({ - events: { - modules: { - kafka: { - kafkaConsumingEventPublisher: { - clientId: 'backstage-events', - brokers: ['kafka1:9092', 'kafka2:9092'], - topics: [ - { - topic: 'fake1', - kafka: { - topics: ['topic-A'], - groupId: 'my-group', - }, - }, - { - topic: 'fake2', - kafka: { - topics: ['topic-B'], - groupId: 'my-group', - }, - }, - ], - }, - }, - }, - }, - }); - - const publisherConfigs = readConfig(config); - - expect(publisherConfigs).toBeDefined(); - - expect(publisherConfigs?.kafkaConsumerConfigs.length).toBe(2); - - expect(publisherConfigs?.kafkaConfig.clientId).toEqual('backstage-events'); - expect(publisherConfigs?.kafkaConfig.brokers).toEqual([ - 'kafka1:9092', - 'kafka2:9092', - ]); - expect(publisherConfigs?.kafkaConsumerConfigs[0].backstageTopic).toEqual( - 'fake1', - ); - expect( - publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.groupId, - ).toEqual('my-group'); - expect( - publisherConfigs?.kafkaConsumerConfigs[0].consumerSubscribeTopics.topics, - ).toEqual(['topic-A']); - }); - - it('all fields configured', () => { - const config = new ConfigReader({ - events: { - modules: { - kafka: { - kafkaConsumingEventPublisher: { - clientId: 'backstage-events', - brokers: ['kafka1:9092', 'kafka2:9092'], - ssl: true, - sasl: { - mechanism: 'plain', - username: 'username', - password: 'password', - }, - retry: { - maxRetryTime: { milliseconds: 20000 }, - initialRetryTime: { milliseconds: 200 }, - factor: '0.4', - multiplier: '4', - retries: '10', - }, - authenticationTimeout: { milliseconds: 20000 }, - connectionTimeout: { milliseconds: 1500 }, - requestTimeout: { milliseconds: 20000 }, - enforceRequestTimeout: false, - topics: [ - { - topic: 'fake1', - kafka: { - topics: ['topic-A'], - groupId: 'my-group', - sessionTimeout: { milliseconds: 20000 }, - rebalanceTimeout: { milliseconds: 50000 }, - heartbeatInterval: { milliseconds: 2000 }, - metadataMaxAge: { milliseconds: 400000 }, - maxBytesPerPartition: 50000, - minBytes: 2, - maxBytes: 500000, - maxWaitTime: { milliseconds: 4000 }, - }, - }, - { - topic: 'fake2', - kafka: { - topics: ['topic-B'], - groupId: 'my-group', - }, - }, - ], - }, - }, - }, - }, - }); - - const publisherConfigs = readConfig(config); - - expect(publisherConfigs).toBeDefined(); - - // Client configuration - expect(publisherConfigs?.kafkaConfig.clientId).toEqual('backstage-events'); - expect(publisherConfigs?.kafkaConfig.brokers).toEqual([ - 'kafka1:9092', - 'kafka2:9092', - ]); - expect(publisherConfigs?.kafkaConfig.ssl).toBeTruthy(); - expect(publisherConfigs?.kafkaConfig.sasl).toStrictEqual({ - mechanism: 'plain', - username: 'username', - password: 'password', - }); - expect(publisherConfigs?.kafkaConfig.authenticationTimeout).toBe(20000); - expect(publisherConfigs?.kafkaConfig.connectionTimeout).toBe(1500); - expect(publisherConfigs?.kafkaConfig.requestTimeout).toBe(20000); - expect(publisherConfigs?.kafkaConfig.enforceRequestTimeout).toBeFalsy(); - expect(publisherConfigs?.kafkaConfig.retry).toStrictEqual({ - maxRetryTime: 20000, - initialRetryTime: 200, - factor: 0.4, - multiplier: 4, - retries: 10, - }); - - // Consumer configuration - expect(publisherConfigs?.kafkaConsumerConfigs.length).toBe(2); - expect(publisherConfigs?.kafkaConsumerConfigs[0].backstageTopic).toEqual( - 'fake1', - ); - expect( - publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.groupId, - ).toEqual('my-group'); - expect( - publisherConfigs?.kafkaConsumerConfigs[0].consumerSubscribeTopics.topics, - ).toEqual(['topic-A']); - - expect( - publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.sessionTimeout, - ).toBe(20000); - expect( - publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.rebalanceTimeout, - ).toBe(50000); - expect( - publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig - .heartbeatInterval, - ).toBe(2000); - expect( - publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.metadataMaxAge, - ).toBe(400000); - expect( - publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig - .maxBytesPerPartition, - ).toBe(50000); - expect( - publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.minBytes, - ).toBe(2); - expect( - publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.maxBytes, - ).toBe(500000); - expect( - publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.maxWaitTimeInMs, - ).toBe(4000); - }); - - it('should handle HumanDuration and string values for durations and timeouts', () => { - const config = new ConfigReader({ - events: { - modules: { - kafka: { - kafkaConsumingEventPublisher: { - clientId: 'backstage-events', - brokers: ['kafka1:9092', 'kafka2:9092'], - retry: { - maxRetryTime: { seconds: 1 }, - initialRetryTime: { minutes: 1 }, - factor: 0.4, - multiplier: 4, - retries: 10, - }, - authenticationTimeout: { hours: 1 }, - connectionTimeout: { days: 1 }, - topics: [], - requestTimeout: '1m', - }, - }, - }, - }, - }); - - const publisherConfigs = readConfig(config); - - expect(publisherConfigs).toBeDefined(); - - // Client configuration - expect(publisherConfigs?.kafkaConfig.clientId).toEqual('backstage-events'); - expect(publisherConfigs?.kafkaConfig.brokers).toEqual([ - 'kafka1:9092', - 'kafka2:9092', - ]); - expect(publisherConfigs?.kafkaConfig.authenticationTimeout).toBe(3600000); - expect(publisherConfigs?.kafkaConfig.connectionTimeout).toBe(86400000); - expect(publisherConfigs?.kafkaConfig.requestTimeout).toBe(60000); - expect(publisherConfigs?.kafkaConfig.retry).toStrictEqual({ - maxRetryTime: 1000, - initialRetryTime: 60000, - factor: 0.4, - multiplier: 4, - retries: 10, - }); - - // Consumer configuration - expect(publisherConfigs?.kafkaConsumerConfigs.length).toBe(0); - }); -}); diff --git a/plugins/events-backend-module-kafka/src/publisher/config.ts b/plugins/events-backend-module-kafka/src/publisher/config.ts deleted file mode 100644 index 7534ce4d38..0000000000 --- a/plugins/events-backend-module-kafka/src/publisher/config.ts +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2025 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { Config, readDurationFromConfig } from '@backstage/config'; -import { durationToMilliseconds } from '@backstage/types'; -import { ConsumerConfig, ConsumerSubscribeTopics, KafkaConfig } from 'kafkajs'; - -export interface KafkaConsumerConfig { - backstageTopic: string; - consumerConfig: ConsumerConfig; - consumerSubscribeTopics: ConsumerSubscribeTopics; -} - -export interface KafkaEventSourceConfig { - kafkaConfig: KafkaConfig; - kafkaConsumerConfigs: KafkaConsumerConfig[]; -} - -const CONFIG_PREFIX_PUBLISHER = - 'events.modules.kafka.kafkaConsumingEventPublisher'; - -/** - * Reads an optional HumanDuration from the config and returns the value in milliseconds if the key is defined. - * - * @param config - The configuration object to read from. - * @param key - The key to look up in the configuration. - * @returns The duration in milliseconds, or undefined if the key is not defined. - */ -const readOptionalHumanDurationInMs = ( - config: Config, - key: string, -): number | undefined => { - const humanDuration = config.has(key) - ? readDurationFromConfig(config, { key }) - : undefined; - - if (!humanDuration) return undefined; - - return durationToMilliseconds(humanDuration); -}; - -export const readConfig = ( - config: Config, -): KafkaEventSourceConfig | undefined => { - const kafkaConfig = config.getOptionalConfig(CONFIG_PREFIX_PUBLISHER); - - if (!kafkaConfig) { - return undefined; - } - - const clientId = kafkaConfig.getString('clientId'); - const brokers = kafkaConfig.getStringArray('brokers'); - - const authenticationTimeout = readOptionalHumanDurationInMs( - kafkaConfig, - 'authenticationTimeout', - ); - - const connectionTimeout = readOptionalHumanDurationInMs( - kafkaConfig, - 'connectionTimeout', - ); - const requestTimeout = readOptionalHumanDurationInMs( - kafkaConfig, - 'requestTimeout', - ); - const enforceRequestTimeout = kafkaConfig.getOptionalBoolean( - 'enforceRequestTimeout', - ); - - const ssl = kafkaConfig.getOptional('ssl') as KafkaConfig['ssl']; - const sasl = kafkaConfig.getOptional('sasl') as KafkaConfig['sasl']; - - const retry: KafkaConfig['retry'] = { - maxRetryTime: readOptionalHumanDurationInMs( - kafkaConfig, - 'retry.maxRetryTime', - ), - initialRetryTime: readOptionalHumanDurationInMs( - kafkaConfig, - 'retry.initialRetryTime', - ), - factor: kafkaConfig.getOptionalNumber('retry.factor'), - multiplier: kafkaConfig.getOptionalNumber('retry.multiplier'), - retries: kafkaConfig.getOptionalNumber('retry.retries'), - }; - - const kafkaConsumerConfigs: KafkaConsumerConfig[] = kafkaConfig - .getConfigArray('topics') - .map(topic => { - return { - backstageTopic: topic.getString('topic'), - consumerConfig: { - groupId: topic.getString('kafka.groupId'), - sessionTimeout: readOptionalHumanDurationInMs( - topic, - 'kafka.sessionTimeout', - ), - rebalanceTimeout: readOptionalHumanDurationInMs( - topic, - 'kafka.rebalanceTimeout', - ), - heartbeatInterval: readOptionalHumanDurationInMs( - topic, - 'kafka.heartbeatInterval', - ), - metadataMaxAge: readOptionalHumanDurationInMs( - topic, - 'kafka.metadataMaxAge', - ), - maxBytesPerPartition: topic.getOptionalNumber( - 'kafka.maxBytesPerPartition', - ), - minBytes: topic.getOptionalNumber('kafka.minBytes'), - maxBytes: topic.getOptionalNumber('kafka.maxBytes'), - maxWaitTimeInMs: readOptionalHumanDurationInMs( - topic, - 'kafka.maxWaitTime', - ), - }, - consumerSubscribeTopics: { - topics: topic.getStringArray('kafka.topics'), - }, - }; - }); - - return { - kafkaConfig: { - clientId, - brokers, - ssl, - sasl, - authenticationTimeout, - connectionTimeout, - requestTimeout, - enforceRequestTimeout, - retry, - }, - kafkaConsumerConfigs, - }; -}; diff --git a/plugins/events-backend-module-kafka/src/service/eventsModuleKafkaConsumingEventPublisher.test.ts b/plugins/events-backend-module-kafka/src/service/eventsModuleKafkaConsumingEventPublisher.test.ts deleted file mode 100644 index 816793f635..0000000000 --- a/plugins/events-backend-module-kafka/src/service/eventsModuleKafkaConsumingEventPublisher.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2025 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { createServiceFactory } from '@backstage/backend-plugin-api'; -import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; -import { eventsServiceRef } from '@backstage/plugin-events-node'; -import { TestEventsService } from '@backstage/plugin-events-backend-test-utils'; -import { eventsModuleKafkaConsumingEventPublisher } from './eventsModuleKafkaConsumingEventPublisher'; -import { KafkaConsumerClient } from '../publisher/KafkaConsumerClient'; - -jest.mock('../publisher/KafkaConsumerClient'); - -describe('eventsModuleKafkaConsumingEventPublisher', () => { - it('should be correctly wired and set up', async () => { - const events = new TestEventsService(); - const eventsServiceFactory = createServiceFactory({ - service: eventsServiceRef, - deps: {}, - async factory({}) { - return events; - }, - }); - - const mockKafkaConsumerClient = { - start: jest.fn(), - shutdown: jest.fn(), - }; - (KafkaConsumerClient.fromConfig as jest.Mock).mockReturnValue( - mockKafkaConsumerClient, - ); - - await startTestBackend({ - features: [ - eventsServiceFactory, - eventsModuleKafkaConsumingEventPublisher, - mockServices.rootConfig.factory({ - data: { - events: { - modules: { - kafka: { - kafkaConsumingEventPublisher: { - clientId: 'backstage-events', - brokers: ['kafka1:9092', 'kafka2:9092'], - topics: { - fake1: { - kafka: { - topics: ['topic-A'], - groupId: 'my-group', - }, - }, - fake2: { - kafka: { - topics: ['topic-B'], - groupId: 'my-group', - }, - }, - }, - }, - }, - }, - }, - }, - }), - ], - }); - - // Verify that the Kafka consumer client was started - expect(mockKafkaConsumerClient.start).toHaveBeenCalled(); - - // Verify that the shutdown hook was registered - expect(mockKafkaConsumerClient.shutdown).not.toHaveBeenCalled(); - }); -}); diff --git a/plugins/events-backend-module-kafka/src/publisher/LoggerServiceAdapter.ts b/plugins/events-backend-module-kafka/src/utils/LoggerServiceAdapter.ts similarity index 100% rename from plugins/events-backend-module-kafka/src/publisher/LoggerServiceAdapter.ts rename to plugins/events-backend-module-kafka/src/utils/LoggerServiceAdapter.ts diff --git a/plugins/events-backend-module-kafka/src/utils/config.test.ts b/plugins/events-backend-module-kafka/src/utils/config.test.ts new file mode 100644 index 0000000000..2bd6121ba2 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/utils/config.test.ts @@ -0,0 +1,235 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ConfigReader } from '@backstage/config'; +import { readRetryConfig, readKafkaConfig } from './config'; + +describe('readRetryConfig', () => { + it('should return empty object when config is undefined', () => { + const result = readRetryConfig(undefined); + expect(result).toEqual({}); + }); + + it('should return empty object when config is empty', () => { + const config = new ConfigReader({}); + const result = readRetryConfig(config); + expect(result).toEqual({}); + }); + + it('should read retry configuration with milliseconds', () => { + const config = new ConfigReader({ + maxRetryTime: { milliseconds: 20000 }, + initialRetryTime: { milliseconds: 200 }, + factor: 0.4, + multiplier: 4, + retries: 10, + }); + + const result = readRetryConfig(config); + + expect(result).toEqual({ + maxRetryTime: 20000, + initialRetryTime: 200, + factor: 0.4, + multiplier: 4, + retries: 10, + }); + }); + + it('should read retry configuration with string values', () => { + const config = new ConfigReader({ + maxRetryTime: { seconds: 20 }, + initialRetryTime: { minutes: 1 }, + factor: '0.4', + multiplier: '4', + retries: '10', + }); + + const result = readRetryConfig(config); + + expect(result).toEqual({ + maxRetryTime: 20000, + initialRetryTime: 60000, + factor: 0.4, + multiplier: 4, + retries: 10, + }); + }); + + it('should handle HumanDuration values', () => { + const config = new ConfigReader({ + maxRetryTime: { hours: 1 }, + initialRetryTime: { days: 1 }, + }); + + const result = readRetryConfig(config); + + expect(result).toEqual({ + maxRetryTime: 3600000, + initialRetryTime: 86400000, + factor: undefined, + multiplier: undefined, + retries: undefined, + }); + }); + + it('should handle partial configuration', () => { + const config = new ConfigReader({ + maxRetryTime: { milliseconds: 15000 }, + retries: 5, + }); + + const result = readRetryConfig(config); + + expect(result).toEqual({ + maxRetryTime: 15000, + initialRetryTime: undefined, + factor: undefined, + multiplier: undefined, + retries: 5, + }); + }); +}); + +describe('readKafkaConfig', () => { + it('should read minimal kafka configuration', () => { + const config = new ConfigReader({ + clientId: 'test-client', + brokers: ['kafka1:9092', 'kafka2:9092'], + }); + + const result = readKafkaConfig(config); + + expect(result).toEqual({ + clientId: 'test-client', + brokers: ['kafka1:9092', 'kafka2:9092'], + authenticationTimeout: undefined, + connectionTimeout: undefined, + requestTimeout: undefined, + enforceRequestTimeout: undefined, + ssl: undefined, + sasl: undefined, + retry: {}, + }); + }); + + it('should read full kafka configuration with all optional fields', () => { + const config = new ConfigReader({ + clientId: 'backstage-events', + brokers: ['kafka1:9092', 'kafka2:9092'], + authenticationTimeout: { milliseconds: 20000 }, + connectionTimeout: { milliseconds: 1500 }, + requestTimeout: { milliseconds: 20000 }, + enforceRequestTimeout: false, + ssl: true, + sasl: { + mechanism: 'plain', + username: 'username', + password: 'password', + }, + retry: { + maxRetryTime: { milliseconds: 20000 }, + initialRetryTime: { milliseconds: 200 }, + factor: 0.4, + multiplier: 4, + retries: 10, + }, + }); + + const result = readKafkaConfig(config); + + expect(result).toEqual({ + clientId: 'backstage-events', + brokers: ['kafka1:9092', 'kafka2:9092'], + authenticationTimeout: 20000, + connectionTimeout: 1500, + requestTimeout: 20000, + enforceRequestTimeout: false, + ssl: true, + sasl: { + mechanism: 'plain', + username: 'username', + password: 'password', + }, + retry: { + maxRetryTime: 20000, + initialRetryTime: 200, + factor: 0.4, + multiplier: 4, + retries: 10, + }, + }); + }); + + it('should handle HumanDuration values for timeouts', () => { + const config = new ConfigReader({ + clientId: 'test-client', + brokers: ['kafka:9092'], + authenticationTimeout: { hours: 1 }, + connectionTimeout: { days: 1 }, + requestTimeout: { minutes: 5 }, + }); + + const result = readKafkaConfig(config); + + expect(result.authenticationTimeout).toBe(3600000); + expect(result.connectionTimeout).toBe(86400000); + expect(result.requestTimeout).toBe(300000); + }); + + it('should handle complex SSL configuration', () => { + const config = new ConfigReader({ + clientId: 'secure-client', + brokers: ['secure-kafka:9093'], + ssl: { + rejectUnauthorized: false, + ca: 'ca-certificate', + key: 'client-key', + cert: 'client-cert', + }, + }); + + const result = readKafkaConfig(config); + + expect(result.ssl).toEqual({ + rejectUnauthorized: false, + ca: 'ca-certificate', + key: 'client-key', + cert: 'client-cert', + }); + }); + + it('should handle complex SASL configuration', () => { + const config = new ConfigReader({ + clientId: 'sasl-client', + brokers: ['sasl-kafka:9094'], + sasl: { + mechanism: 'scram-sha-256', + username: 'kafka-user', + password: 'kafka-password', + authorizationIdentity: 'authz-user', + }, + }); + + const result = readKafkaConfig(config); + + expect(result.sasl).toEqual({ + mechanism: 'scram-sha-256', + username: 'kafka-user', + password: 'kafka-password', + authorizationIdentity: 'authz-user', + }); + }); +}); diff --git a/plugins/events-backend-module-kafka/src/utils/config.ts b/plugins/events-backend-module-kafka/src/utils/config.ts new file mode 100644 index 0000000000..4e140d26a3 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/utils/config.ts @@ -0,0 +1,84 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Config, readDurationFromConfig } from '@backstage/config'; +import { durationToMilliseconds } from '@backstage/types'; +import { KafkaConfig, RetryOptions } from 'kafkajs'; + +/** + * Reads an optional HumanDuration from the config and returns the value in milliseconds if the key is defined. + * + * @param config - The configuration object to read from. + * @param key - The key to look up in the configuration. + * @returns The duration in milliseconds, or undefined if the key is not defined. + */ +export const readOptionalHumanDurationInMs = ( + config: Config, + key: string, +): number | undefined => { + const humanDuration = config.has(key) + ? readDurationFromConfig(config, { key }) + : undefined; + + if (!humanDuration) return undefined; + + return durationToMilliseconds(humanDuration); +}; + +/** + * Reads retry configuration options from the provided config object. + * + * @param config - The configuration object to read retry options from, or undefined. + * @returns A RetryOptions object with optional retry settings, or an empty object if config is undefined. + */ +export const readRetryConfig = (config: Config | undefined): RetryOptions => { + if (!config) { + return {}; + } + + return { + maxRetryTime: readOptionalHumanDurationInMs(config, 'maxRetryTime'), + initialRetryTime: readOptionalHumanDurationInMs(config, 'initialRetryTime'), + factor: config.getOptionalNumber('factor'), + multiplier: config.getOptionalNumber('multiplier'), + retries: config.getOptionalNumber('retries'), + }; +}; + +/** + * Reads Kafka configuration from the provided config object. + * + * @param config - The configuration object containing Kafka settings. + * @returns A KafkaConfig object with all necessary Kafka connection and authentication settings. + */ +export const readKafkaConfig = (config: Config): KafkaConfig => { + return { + clientId: config.getString('clientId'), + brokers: config.getStringArray('brokers'), + authenticationTimeout: readOptionalHumanDurationInMs( + config, + 'authenticationTimeout', + ), + connectionTimeout: readOptionalHumanDurationInMs( + config, + 'connectionTimeout', + ), + requestTimeout: readOptionalHumanDurationInMs(config, 'requestTimeout'), + enforceRequestTimeout: config.getOptionalBoolean('enforceRequestTimeout'), + ssl: config.getOptional('ssl') as KafkaConfig['ssl'], + sasl: config.getOptional('sasl') as KafkaConfig['sasl'], + retry: readRetryConfig(config.getOptionalConfig('retry')), + }; +}; diff --git a/plugins/events-backend-module-kafka/src/utils/kafkaTransformers.test.ts b/plugins/events-backend-module-kafka/src/utils/kafkaTransformers.test.ts new file mode 100644 index 0000000000..bfb373ea53 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/utils/kafkaTransformers.test.ts @@ -0,0 +1,137 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { convertHeadersToMetadata, payloadToBuffer } from './kafkaTransformers'; + +describe('kafka-transformers', () => { + describe('convertHeadersToMetadata', () => { + it('should return undefined when headers is undefined', () => { + const result = convertHeadersToMetadata(undefined); + expect(result).toBeUndefined(); + }); + + it('should convert string headers to metadata', () => { + const headers = { + 'content-type': 'application/json', + 'user-id': '12345', + }; + + const result = convertHeadersToMetadata(headers); + + expect(result).toEqual({ + 'content-type': 'application/json', + 'user-id': '12345', + }); + }); + + it('should convert Buffer headers to string metadata', () => { + const headers = { + 'content-type': Buffer.from('application/json'), + 'correlation-id': Buffer.from('abc-123'), + }; + + const result = convertHeadersToMetadata(headers); + + expect(result).toEqual({ + 'content-type': 'application/json', + 'correlation-id': 'abc-123', + }); + }); + + it('should convert array headers to string array metadata', () => { + const headers = { + tags: ['tag1', 'tag2'], + 'buffer-tags': [Buffer.from('tag3'), Buffer.from('tag4')], + 'mixed-tags': ['tag5', Buffer.from('tag6')], + }; + + const result = convertHeadersToMetadata(headers); + + expect(result).toEqual({ + tags: ['tag1', 'tag2'], + 'buffer-tags': ['tag3', 'tag4'], + 'mixed-tags': ['tag5', 'tag6'], + }); + }); + + it('should handle mixed header types', () => { + const headers = { + 'string-header': 'value', + 'buffer-header': Buffer.from('buffer-value'), + 'array-header': ['item1', Buffer.from('item2')], + 'undefined-header': undefined, + }; + + const result = convertHeadersToMetadata(headers); + + expect(result).toEqual({ + 'string-header': 'value', + 'buffer-header': 'buffer-value', + 'array-header': ['item1', 'item2'], + 'undefined-header': undefined, + }); + }); + + it('should handle empty headers object', () => { + const headers = {}; + const result = convertHeadersToMetadata(headers); + expect(result).toEqual({}); + }); + }); + + describe('payloadToBuffer', () => { + it('should return the same Buffer when payload is already a Buffer', () => { + const originalBuffer = Buffer.from('test data'); + + const result = payloadToBuffer(originalBuffer); + + expect(result).toBe(originalBuffer); + expect(Buffer.isBuffer(result)).toBe(true); + }); + + it('should convert string to Buffer', () => { + const payload = 'hello world'; + + const result = payloadToBuffer(payload); + + expect(Buffer.isBuffer(result)).toBe(true); + expect(result.toString()).toBe('hello world'); + }); + + it('should convert object to JSON Buffer', () => { + const payload = { name: 'John', age: 30 }; + + const result = payloadToBuffer(payload); + + expect(Buffer.isBuffer(result)).toBe(true); + expect(JSON.parse(result.toString())).toEqual(payload); + }); + + it('should convert array to JSON Buffer', () => { + const payload = [1, 2, 3, 'test']; + + const result = payloadToBuffer(payload); + + expect(Buffer.isBuffer(result)).toBe(true); + expect(JSON.parse(result.toString())).toEqual(payload); + }); + + it('should convert primitives to JSON Buffer', () => { + expect(payloadToBuffer(42).toString()).toBe('42'); + expect(payloadToBuffer(true).toString()).toBe('true'); + expect(payloadToBuffer(null).toString()).toBe('null'); + }); + }); +}); diff --git a/plugins/events-backend-module-kafka/src/utils/kafkaTransformers.ts b/plugins/events-backend-module-kafka/src/utils/kafkaTransformers.ts new file mode 100644 index 0000000000..871cb08a04 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/utils/kafkaTransformers.ts @@ -0,0 +1,49 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { EventParams } from '@backstage/plugin-events-node'; +import { IHeaders } from 'kafkajs'; + +type EventMetadata = EventParams['metadata']; + +export const convertHeadersToMetadata = ( + headers: IHeaders | undefined, +): EventMetadata => { + if (!headers) return undefined; + + const metadata: EventMetadata = {}; + + Object.entries(headers).forEach(([key, value]) => { + // If value is an array use toString() on all values converting any Buffer types to valid strings + if (Array.isArray(value)) metadata[key] = value.map(v => v.toString()); + // Always return the values using toString() to catch all Buffer types that should be converted to strings + else metadata[key] = value?.toString(); + }); + + return metadata; +}; + +export const payloadToBuffer = (payload: unknown): Buffer => { + if (Buffer.isBuffer(payload)) { + return payload; + } + + if (typeof payload === 'string') { + return Buffer.from(payload, 'utf8'); // More explicit encoding + } + + // Convert to JSON string then encode + return Buffer.from(JSON.stringify(payload), 'utf8'); +}; diff --git a/plugins/events-backend-test-utils/CHANGELOG.md b/plugins/events-backend-test-utils/CHANGELOG.md index 8c3f506f9b..2126b75dac 100644 --- a/plugins/events-backend-test-utils/CHANGELOG.md +++ b/plugins/events-backend-test-utils/CHANGELOG.md @@ -1,5 +1,68 @@ # @backstage/plugin-events-backend-test-utils +## 0.1.54 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.21 + +## 0.1.54-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.21-next.0 + +## 0.1.53 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.20 + +## 0.1.53-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.20-next.0 + +## 0.1.52 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.19 + +## 0.1.52-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.19-next.0 + +## 0.1.51 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + +## 0.1.51-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.0 + +## 0.1.50 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.17 + ## 0.1.50-next.0 ### Patch Changes diff --git a/plugins/events-backend-test-utils/package.json b/plugins/events-backend-test-utils/package.json index 407984484f..c2872e584e 100644 --- a/plugins/events-backend-test-utils/package.json +++ b/plugins/events-backend-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-test-utils", - "version": "0.1.50-next.0", + "version": "0.1.54", "description": "The plugin-events-backend-test-utils for @backstage/plugin-events-node", "backstage": { "role": "node-library", diff --git a/plugins/events-backend/CHANGELOG.md b/plugins/events-backend/CHANGELOG.md index 401fd93b3b..223eb807df 100644 --- a/plugins/events-backend/CHANGELOG.md +++ b/plugins/events-backend/CHANGELOG.md @@ -1,5 +1,187 @@ # @backstage/plugin-events-backend +## 0.6.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/errors@1.3.0 + - @backstage/backend-openapi-utils@0.6.8 + - @backstage/config@1.3.7 + - @backstage/plugin-events-node@0.4.21 + +## 0.6.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/backend-openapi-utils@0.6.8-next.2 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/config@1.3.7-next.0 + - @backstage/plugin-events-node@0.4.21-next.2 + +## 0.6.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-openapi-utils@0.6.8-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## 0.6.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/backend-openapi-utils@0.6.8-next.0 + - @backstage/plugin-events-node@0.4.21-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.6.0 + +### Minor Changes + +- 0fbcf23: Migrated OpenAPI schemas to 3.1. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/backend-openapi-utils@0.6.7 + - @backstage/plugin-events-node@0.4.20 + +## 0.6.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/backend-openapi-utils@0.6.7-next.1 + - @backstage/plugin-events-node@0.4.20-next.1 + +## 0.6.0-next.1 + +### Minor Changes + +- 0fbcf23: Migrated OpenAPI schemas to 3.1. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.7-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.20-next.0 + +## 0.5.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/backend-openapi-utils@0.6.7-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.20-next.0 + +## 0.5.11 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.6 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-events-node@0.4.19 + +## 0.5.11-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.6-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.5.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/backend-openapi-utils@0.6.5 + +## 0.5.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.5-next.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.18 + +## 0.5.9 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.4 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## 0.5.9-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.4-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.5.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/backend-openapi-utils@0.6.4-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.5.8 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + - @backstage/backend-openapi-utils@0.6.3 + ## 0.5.8-next.1 ### Patch Changes diff --git a/plugins/events-backend/knip-report.md b/plugins/events-backend/knip-report.md index 9da2b3fe7c..96c219cbd4 100644 --- a/plugins/events-backend/knip-report.md +++ b/plugins/events-backend/knip-report.md @@ -2,7 +2,7 @@ ## Unused devDependencies (1) -| Name | Location | Severity | -| :------------------------- | :----------- | :------- | -| @backstage/backend-app-api | plugins/events-backend/package.json | error | +| Name | Location | Severity | +| :------------------------- | :---------------- | :------- | +| @backstage/backend-app-api | package.json:69:6 | error | diff --git a/plugins/events-backend/package.json b/plugins/events-backend/package.json index 37b743d637..7163c31460 100644 --- a/plugins/events-backend/package.json +++ b/plugins/events-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend", - "version": "0.5.8-next.1", + "version": "0.6.1", "backstage": { "role": "backend-plugin", "pluginId": "events", @@ -61,7 +61,7 @@ "@backstage/types": "workspace:^", "@types/express": "^4.17.6", "content-type": "^1.0.5", - "express": "^4.17.1", + "express": "^4.22.0", "express-promise-router": "^4.1.0", "knex": "^3.0.0" }, diff --git a/plugins/events-backend/src/migrations.test.ts b/plugins/events-backend/src/migrations.test.ts index e308a1d57d..39be44947a 100644 --- a/plugins/events-backend/src/migrations.test.ts +++ b/plugins/events-backend/src/migrations.test.ts @@ -16,7 +16,7 @@ import { Knex } from 'knex'; import { TestDatabases } from '@backstage/backend-test-utils'; -import fs from 'fs'; +import fs from 'node:fs'; const migrationsDir = `${__dirname}/../migrations`; const migrationsFiles = fs.readdirSync(migrationsDir).sort(); diff --git a/plugins/events-backend/src/schema/openapi.yaml b/plugins/events-backend/src/schema/openapi.yaml index 97f7705f87..6ff064d660 100644 --- a/plugins/events-backend/src/schema/openapi.yaml +++ b/plugins/events-backend/src/schema/openapi.yaml @@ -1,4 +1,4 @@ -openapi: 3.0.3 +openapi: 3.1.0 info: title: events version: '1' @@ -17,7 +17,6 @@ components: name: subscriptionId in: path required: true - allowReserved: true schema: type: string requestBodies: {} diff --git a/plugins/events-backend/src/schema/openapi/generated/apis/Api.server.ts b/plugins/events-backend/src/schema/openapi/generated/apis/Api.server.ts index 0b116fe012..62130143f9 100644 --- a/plugins/events-backend/src/schema/openapi/generated/apis/Api.server.ts +++ b/plugins/events-backend/src/schema/openapi/generated/apis/Api.server.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/events-backend/src/schema/openapi/generated/apis/index.ts b/plugins/events-backend/src/schema/openapi/generated/apis/index.ts index 8d81cbaf39..9a0ffed740 100644 --- a/plugins/events-backend/src/schema/openapi/generated/apis/index.ts +++ b/plugins/events-backend/src/schema/openapi/generated/apis/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/events-backend/src/schema/openapi/generated/index.ts b/plugins/events-backend/src/schema/openapi/generated/index.ts index dec4b8804e..58fc52487a 100644 --- a/plugins/events-backend/src/schema/openapi/generated/index.ts +++ b/plugins/events-backend/src/schema/openapi/generated/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/events-backend/src/schema/openapi/generated/models/ErrorError.model.ts b/plugins/events-backend/src/schema/openapi/generated/models/ErrorError.model.ts index 809cd3fa80..d72822b815 100644 --- a/plugins/events-backend/src/schema/openapi/generated/models/ErrorError.model.ts +++ b/plugins/events-backend/src/schema/openapi/generated/models/ErrorError.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/events-backend/src/schema/openapi/generated/models/ErrorRequest.model.ts b/plugins/events-backend/src/schema/openapi/generated/models/ErrorRequest.model.ts index 3eb5e15740..1591911bc9 100644 --- a/plugins/events-backend/src/schema/openapi/generated/models/ErrorRequest.model.ts +++ b/plugins/events-backend/src/schema/openapi/generated/models/ErrorRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/events-backend/src/schema/openapi/generated/models/ErrorResponse.model.ts b/plugins/events-backend/src/schema/openapi/generated/models/ErrorResponse.model.ts index edbcc32df7..1eff0557ed 100644 --- a/plugins/events-backend/src/schema/openapi/generated/models/ErrorResponse.model.ts +++ b/plugins/events-backend/src/schema/openapi/generated/models/ErrorResponse.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/events-backend/src/schema/openapi/generated/models/Event.model.ts b/plugins/events-backend/src/schema/openapi/generated/models/Event.model.ts index 02e52a81d9..04cf4b5d3d 100644 --- a/plugins/events-backend/src/schema/openapi/generated/models/Event.model.ts +++ b/plugins/events-backend/src/schema/openapi/generated/models/Event.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,8 +26,5 @@ export interface Event { * The topic that the event is published on */ topic: string; - /** - * The event payload - */ payload: any | null; } diff --git a/plugins/events-backend/src/schema/openapi/generated/models/GetSubscriptionEvents200Response.model.ts b/plugins/events-backend/src/schema/openapi/generated/models/GetSubscriptionEvents200Response.model.ts index ff15eef3ce..cd0b45e19d 100644 --- a/plugins/events-backend/src/schema/openapi/generated/models/GetSubscriptionEvents200Response.model.ts +++ b/plugins/events-backend/src/schema/openapi/generated/models/GetSubscriptionEvents200Response.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/events-backend/src/schema/openapi/generated/models/ModelError.model.ts b/plugins/events-backend/src/schema/openapi/generated/models/ModelError.model.ts index 3914d73531..c4af31b48c 100644 --- a/plugins/events-backend/src/schema/openapi/generated/models/ModelError.model.ts +++ b/plugins/events-backend/src/schema/openapi/generated/models/ModelError.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/events-backend/src/schema/openapi/generated/models/PostEventRequest.model.ts b/plugins/events-backend/src/schema/openapi/generated/models/PostEventRequest.model.ts index 6bbb2974b5..c097e10d11 100644 --- a/plugins/events-backend/src/schema/openapi/generated/models/PostEventRequest.model.ts +++ b/plugins/events-backend/src/schema/openapi/generated/models/PostEventRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/events-backend/src/schema/openapi/generated/models/PutSubscriptionRequest.model.ts b/plugins/events-backend/src/schema/openapi/generated/models/PutSubscriptionRequest.model.ts index 786f0a0644..dabc823807 100644 --- a/plugins/events-backend/src/schema/openapi/generated/models/PutSubscriptionRequest.model.ts +++ b/plugins/events-backend/src/schema/openapi/generated/models/PutSubscriptionRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/events-backend/src/schema/openapi/generated/models/index.ts b/plugins/events-backend/src/schema/openapi/generated/models/index.ts index 6baa8ef449..b3cd7dc096 100644 --- a/plugins/events-backend/src/schema/openapi/generated/models/index.ts +++ b/plugins/events-backend/src/schema/openapi/generated/models/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/events-backend/src/schema/openapi/generated/router.ts b/plugins/events-backend/src/schema/openapi/generated/router.ts index f1b9de2099..ce89146f9b 100644 --- a/plugins/events-backend/src/schema/openapi/generated/router.ts +++ b/plugins/events-backend/src/schema/openapi/generated/router.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import { createValidatedOpenApiRouterFromGeneratedEndpointMap } from '@backstage import { EndpointMap } from './apis'; export const spec = { - openapi: '3.0.3', + openapi: '3.1.0', info: { title: 'events', version: '1', @@ -45,7 +45,6 @@ export const spec = { name: 'subscriptionId', in: 'path', required: true, - allowReserved: true, schema: { type: 'string', }, diff --git a/plugins/events-backend/src/schema/openapi/index.ts b/plugins/events-backend/src/schema/openapi/index.ts index 196aad553a..fb49a2b9c0 100644 --- a/plugins/events-backend/src/schema/openapi/index.ts +++ b/plugins/events-backend/src/schema/openapi/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/events-node/CHANGELOG.md b/plugins/events-node/CHANGELOG.md index 94064cbafd..5fe13de3aa 100644 --- a/plugins/events-node/CHANGELOG.md +++ b/plugins/events-node/CHANGELOG.md @@ -1,5 +1,111 @@ # @backstage/plugin-events-node +## 0.4.21 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/errors@1.3.0 + +## 0.4.21-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/backend-plugin-api@1.9.0-next.2 + +## 0.4.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + +## 0.4.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.4.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + +## 0.4.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + +## 0.4.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.4.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + +## 0.4.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.4.18 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + +## 0.4.18-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.4.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.4.17 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + ## 0.4.17-next.1 ### Patch Changes diff --git a/plugins/events-node/knip-report.md b/plugins/events-node/knip-report.md index cb8d19b560..f8ac50933b 100644 --- a/plugins/events-node/knip-report.md +++ b/plugins/events-node/knip-report.md @@ -2,7 +2,7 @@ ## Unused dependencies (1) -| Name | Location | Severity | -| :--------------- | :----------- | :------- | -| @backstage/types | plugins/events-node/package.json | error | +| Name | Location | Severity | +| :--------------- | :---------------- | :------- | +| @backstage/types | package.json:57:6 | error | diff --git a/plugins/events-node/package.json b/plugins/events-node/package.json index ea63e68408..9836e801b1 100644 --- a/plugins/events-node/package.json +++ b/plugins/events-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-node", - "version": "0.4.17-next.1", + "version": "0.4.21", "description": "The plugin-events-node module for @backstage/plugin-events-backend", "backstage": { "role": "node-library", @@ -59,13 +59,13 @@ "@types/express": "^4.17.6", "content-type": "^1.0.5", "cross-fetch": "^4.0.0", - "express": "^4.17.1", + "express": "^4.22.0", "uri-template": "^2.0.0" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "msw": "^1.0.0" + "msw": "^2.0.0" }, "configSchema": "config.d.ts" } diff --git a/plugins/events-node/src/api/DefaultEventsService.test.ts b/plugins/events-node/src/api/DefaultEventsService.test.ts index 13a6a68fa4..a62cc24f14 100644 --- a/plugins/events-node/src/api/DefaultEventsService.test.ts +++ b/plugins/events-node/src/api/DefaultEventsService.test.ts @@ -17,7 +17,7 @@ import { DefaultEventsService } from './DefaultEventsService'; import { EventParams } from './EventParams'; import { EVENTS_NOTIFY_TIMEOUT_HEADER } from './EventsService'; -import { rest } from 'msw'; +import { http, HttpResponse } from 'msw'; import { setupServer } from 'msw/node'; import { mockServices, @@ -129,18 +129,18 @@ describe('DefaultEventsService', () => { }); mswServer.use( - rest.put( + http.put( 'http://localhost:0/api/events/bus/v1/subscriptions/a.tester', - (_req, res, ctx) => res(ctx.status(200)), + () => new HttpResponse(null, { status: 200 }), ), - rest.get( + http.get( 'http://localhost:0/api/events/bus/v1/subscriptions/a.tester/events', - (_req, res, ctx) => - res( - ctx.status(200), - ctx.json({ + () => + HttpResponse.json( + { events: [{ topic: 'test', payload: { foo: 'bar' } }], - }), + }, + { status: 200 }, ), ), ); @@ -180,38 +180,36 @@ describe('DefaultEventsService', () => { }); mswServer.use( - rest.put( + http.put( 'http://localhost:0/api/events/bus/v1/subscriptions/a.tester', - (_req, res, ctx) => res(ctx.status(200)), + () => new HttpResponse(null, { status: 200 }), ), // The first and third calls result in a blocking 202 that is resolved after 100ms // The second and fourth calls result in a 200 with an event // The fifth call blocks until the end of the test // No more than 5 calls should be made - rest.get( + http.get( 'http://localhost:0/api/events/bus/v1/subscriptions/a.tester/events', - (_req, res, ctx) => { + () => { callCount += 1; if (callCount === 1 || callCount === 3) { - return res( - ctx.status(202), - ctx.body( - new ReadableStream({ - start(controller) { - setTimeout(() => controller.close(), 100); - }, - }), - ), + return new HttpResponse( + new ReadableStream({ + start(controller) { + setTimeout(() => controller.close(), 100); + }, + }), + { status: 202 }, ); } else if (callCount === 2 || callCount === 4) { - return res( - ctx.status(200), - ctx.json({ + return HttpResponse.json( + { events: [{ topic: 'test', payload: { callCount } }], - }), + }, + { status: 200 }, ); } else if (callCount === 5) { - return res(ctx.status(202), ctx.body(blockingStream)); + return new HttpResponse(blockingStream, { status: 202 }); } throw new Error(`events endpoint called too many times`); }, @@ -268,45 +266,42 @@ describe('DefaultEventsService', () => { }); mswServer.use( - rest.put( + http.put( 'http://localhost:0/api/events/bus/v1/subscriptions/a.tester', - (_req, res, ctx) => res(ctx.status(200)), + () => new HttpResponse(null, { status: 200 }), ), // The first and third calls result in a blocking 202 that is resolved after 100ms // The second and fourth calls result in a 200 with an event // The fifth call blocks until the end of the test // No more than 5 calls should be made - rest.get( + http.get( 'http://localhost:0/api/events/bus/v1/subscriptions/a.tester/events', - (_req, res, ctx) => { + () => { callCount += 1; if (callCount === 1 || callCount === 3) { - return res( - ctx.status(202), - ctx.body( - new ReadableStream({ - start(controller) { - setTimeout(() => controller.close(), 100); - }, - }), - ), + return new HttpResponse( + new ReadableStream({ + start(controller) { + setTimeout(() => controller.close(), 100); + }, + }), + { status: 202 }, ); } else if (callCount === 2 || callCount === 4) { - return res( - ctx.status(200), - ctx.json({ + return HttpResponse.json( + { events: [{ topic: 'test', payload: { callCount } }], - }), + }, + { status: 200 }, ); } else if (callCount === 5) { // 5th call has a timeout header so polling should proceed to the next call - return res( - ctx.set(EVENTS_NOTIFY_TIMEOUT_HEADER, '100'), - ctx.status(202), - ctx.body(blockingStream), - ); + return new HttpResponse(blockingStream, { + status: 202, + headers: { [EVENTS_NOTIFY_TIMEOUT_HEADER]: '100' }, + }); } else if (callCount === 6) { - return res(ctx.status(202), ctx.body(blockingStream)); + return new HttpResponse(blockingStream, { status: 202 }); } throw new Error(`events endpoint called too many times`); }, @@ -358,11 +353,11 @@ describe('DefaultEventsService', () => { let calledApi = false; mswServer.use( - rest.put( + http.put( 'http://localhost:0/api/events/bus/v1/subscriptions/a.tester', - (_req, res, ctx) => { + () => { calledApi = true; - res(ctx.status(200)); + return new HttpResponse(null, { status: 200 }); }, ), ); @@ -393,9 +388,9 @@ describe('DefaultEventsService', () => { }); mswServer.use( - rest.put( + http.put( 'http://localhost:0/api/events/bus/v1/subscriptions/a.tester', - (_req, res, ctx) => res(ctx.status(404)), + () => new HttpResponse(null, { status: 404 }), ), ); @@ -432,9 +427,9 @@ describe('DefaultEventsService', () => { }); mswServer.use( - rest.put( + http.put( 'http://localhost:0/api/events/bus/v1/subscriptions/a.tester', - (_req, res, ctx) => res(ctx.status(404)), + () => new HttpResponse(null, { status: 404 }), ), ); diff --git a/plugins/events-node/src/schema/openapi/generated/apis/Api.client.ts b/plugins/events-node/src/schema/openapi/generated/apis/Api.client.ts index 71e3767af2..a03ea2e7fd 100644 --- a/plugins/events-node/src/schema/openapi/generated/apis/Api.client.ts +++ b/plugins/events-node/src/schema/openapi/generated/apis/Api.client.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ // ****************************************************************** // * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * // ****************************************************************** + import { DiscoveryApi } from '../types/discovery'; import { FetchApi } from '../types/fetch'; import crossFetch from 'cross-fetch'; diff --git a/plugins/events-node/src/schema/openapi/generated/apis/index.ts b/plugins/events-node/src/schema/openapi/generated/apis/index.ts index fc7c83b736..c2e931caf8 100644 --- a/plugins/events-node/src/schema/openapi/generated/apis/index.ts +++ b/plugins/events-node/src/schema/openapi/generated/apis/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/events-node/src/schema/openapi/generated/index.ts b/plugins/events-node/src/schema/openapi/generated/index.ts index dc3055033d..19501a5dcb 100644 --- a/plugins/events-node/src/schema/openapi/generated/index.ts +++ b/plugins/events-node/src/schema/openapi/generated/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/events-node/src/schema/openapi/generated/models/ErrorError.model.ts b/plugins/events-node/src/schema/openapi/generated/models/ErrorError.model.ts index 809cd3fa80..d72822b815 100644 --- a/plugins/events-node/src/schema/openapi/generated/models/ErrorError.model.ts +++ b/plugins/events-node/src/schema/openapi/generated/models/ErrorError.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/events-node/src/schema/openapi/generated/models/ErrorRequest.model.ts b/plugins/events-node/src/schema/openapi/generated/models/ErrorRequest.model.ts index 3eb5e15740..1591911bc9 100644 --- a/plugins/events-node/src/schema/openapi/generated/models/ErrorRequest.model.ts +++ b/plugins/events-node/src/schema/openapi/generated/models/ErrorRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/events-node/src/schema/openapi/generated/models/ErrorResponse.model.ts b/plugins/events-node/src/schema/openapi/generated/models/ErrorResponse.model.ts index edbcc32df7..1eff0557ed 100644 --- a/plugins/events-node/src/schema/openapi/generated/models/ErrorResponse.model.ts +++ b/plugins/events-node/src/schema/openapi/generated/models/ErrorResponse.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/events-node/src/schema/openapi/generated/models/Event.model.ts b/plugins/events-node/src/schema/openapi/generated/models/Event.model.ts index 02e52a81d9..04cf4b5d3d 100644 --- a/plugins/events-node/src/schema/openapi/generated/models/Event.model.ts +++ b/plugins/events-node/src/schema/openapi/generated/models/Event.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,8 +26,5 @@ export interface Event { * The topic that the event is published on */ topic: string; - /** - * The event payload - */ payload: any | null; } diff --git a/plugins/events-node/src/schema/openapi/generated/models/GetSubscriptionEvents200Response.model.ts b/plugins/events-node/src/schema/openapi/generated/models/GetSubscriptionEvents200Response.model.ts index ff15eef3ce..cd0b45e19d 100644 --- a/plugins/events-node/src/schema/openapi/generated/models/GetSubscriptionEvents200Response.model.ts +++ b/plugins/events-node/src/schema/openapi/generated/models/GetSubscriptionEvents200Response.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/events-node/src/schema/openapi/generated/models/ModelError.model.ts b/plugins/events-node/src/schema/openapi/generated/models/ModelError.model.ts index 3914d73531..c4af31b48c 100644 --- a/plugins/events-node/src/schema/openapi/generated/models/ModelError.model.ts +++ b/plugins/events-node/src/schema/openapi/generated/models/ModelError.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/events-node/src/schema/openapi/generated/models/PostEventRequest.model.ts b/plugins/events-node/src/schema/openapi/generated/models/PostEventRequest.model.ts index 6bbb2974b5..c097e10d11 100644 --- a/plugins/events-node/src/schema/openapi/generated/models/PostEventRequest.model.ts +++ b/plugins/events-node/src/schema/openapi/generated/models/PostEventRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/events-node/src/schema/openapi/generated/models/PutSubscriptionRequest.model.ts b/plugins/events-node/src/schema/openapi/generated/models/PutSubscriptionRequest.model.ts index 786f0a0644..dabc823807 100644 --- a/plugins/events-node/src/schema/openapi/generated/models/PutSubscriptionRequest.model.ts +++ b/plugins/events-node/src/schema/openapi/generated/models/PutSubscriptionRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/events-node/src/schema/openapi/generated/models/index.ts b/plugins/events-node/src/schema/openapi/generated/models/index.ts index 6baa8ef449..b3cd7dc096 100644 --- a/plugins/events-node/src/schema/openapi/generated/models/index.ts +++ b/plugins/events-node/src/schema/openapi/generated/models/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/events-node/src/schema/openapi/generated/pluginId.ts b/plugins/events-node/src/schema/openapi/generated/pluginId.ts index b5fc50cfc0..5351088e31 100644 --- a/plugins/events-node/src/schema/openapi/generated/pluginId.ts +++ b/plugins/events-node/src/schema/openapi/generated/pluginId.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/events-node/src/schema/openapi/index.ts b/plugins/events-node/src/schema/openapi/index.ts index 196aad553a..fb49a2b9c0 100644 --- a/plugins/events-node/src/schema/openapi/index.ts +++ b/plugins/events-node/src/schema/openapi/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/example-todo-list-backend/CHANGELOG.md b/plugins/example-todo-list-backend/CHANGELOG.md index ab8ce6917e..e1c880d698 100644 --- a/plugins/example-todo-list-backend/CHANGELOG.md +++ b/plugins/example-todo-list-backend/CHANGELOG.md @@ -1,5 +1,103 @@ # @internal/plugin-todo-list-backend +## 1.0.49 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/errors@1.3.0 + +## 1.0.49-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/backend-plugin-api@1.9.0-next.2 + +## 1.0.49-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + +## 1.0.49-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/errors@1.2.7 + +## 1.0.48 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + +## 1.0.48-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + +## 1.0.48-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/errors@1.2.7 + +## 1.0.47 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + +## 1.0.47-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/errors@1.2.7 + +## 1.0.46 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + +## 1.0.46-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/errors@1.2.7 + +## 1.0.46-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/errors@1.2.7 + +## 1.0.45 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + ## 1.0.45-next.1 ### Patch Changes diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json index d6509e94c6..d139a00cd7 100644 --- a/plugins/example-todo-list-backend/package.json +++ b/plugins/example-todo-list-backend/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list-backend", - "version": "1.0.45-next.1", + "version": "1.0.49", "backstage": { "role": "backend-plugin", "pluginId": "todo-list", @@ -41,7 +41,7 @@ "@backstage/backend-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", "@types/express": "^4.17.6", - "express": "^4.17.1", + "express": "^4.22.0", "express-promise-router": "^4.1.0", "uuid": "^11.0.0" }, diff --git a/plugins/example-todo-list-common/CHANGELOG.md b/plugins/example-todo-list-common/CHANGELOG.md index c3bcdb7990..fa490df772 100644 --- a/plugins/example-todo-list-common/CHANGELOG.md +++ b/plugins/example-todo-list-common/CHANGELOG.md @@ -1,5 +1,40 @@ # @internal/plugin-todo-list-common +## 1.0.30 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.8 + +## 1.0.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.8-next.0 + +## 1.0.29 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.6 + +## 1.0.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.5-next.0 + +## 1.0.28 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.3 + ## 1.0.28-next.0 ### Patch Changes diff --git a/plugins/example-todo-list-common/package.json b/plugins/example-todo-list-common/package.json index 75bc78e24d..da0e420405 100644 --- a/plugins/example-todo-list-common/package.json +++ b/plugins/example-todo-list-common/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list-common", - "version": "1.0.28-next.0", + "version": "1.0.30", "backstage": { "role": "common-library", "pluginId": "todo-list", diff --git a/plugins/example-todo-list/CHANGELOG.md b/plugins/example-todo-list/CHANGELOG.md index d6d35bfcc0..3f3a70b651 100644 --- a/plugins/example-todo-list/CHANGELOG.md +++ b/plugins/example-todo-list/CHANGELOG.md @@ -1,5 +1,91 @@ # @internal/plugin-todo-list +## 1.0.50 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.9 + - @backstage/core-plugin-api@1.12.5 + +## 1.0.50-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + +## 1.0.49 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + +## 1.0.49-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + +## 1.0.48 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.7 + - @backstage/core-plugin-api@1.12.3 + +## 1.0.48-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.6-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + +## 1.0.47 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5 + +## 1.0.47-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + +## 1.0.46 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + +## 1.0.46-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/core-components@0.18.4-next.0 + +## 1.0.45 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + ## 1.0.45-next.0 ### Patch Changes diff --git a/plugins/example-todo-list/knip-report.md b/plugins/example-todo-list/knip-report.md index 552227ac1d..2104bcf178 100644 --- a/plugins/example-todo-list/knip-report.md +++ b/plugins/example-todo-list/knip-report.md @@ -2,8 +2,8 @@ ## Unused devDependencies (2) -| Name | Location | Severity | -| :--------------------- | :----------- | :------- | -| @testing-library/react | plugins/example-todo-list/package.json | error | -| @testing-library/dom | plugins/example-todo-list/package.json | error | +| Name | Location | Severity | +| :--------------------- | :---------------- | :------- | +| @testing-library/react | package.json:52:6 | error | +| @testing-library/dom | package.json:50:6 | error | diff --git a/plugins/example-todo-list/package.json b/plugins/example-todo-list/package.json index ecec99b269..a4127b6270 100644 --- a/plugins/example-todo-list/package.json +++ b/plugins/example-todo-list/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list", - "version": "1.0.45-next.0", + "version": "1.0.50", "backstage": { "role": "frontend-plugin", "pluginId": "todo-list", @@ -53,13 +53,13 @@ "@types/react": "^18.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/plugins/gateway-backend/CHANGELOG.md b/plugins/gateway-backend/CHANGELOG.md index 7f97c68371..f0f74a0eda 100644 --- a/plugins/gateway-backend/CHANGELOG.md +++ b/plugins/gateway-backend/CHANGELOG.md @@ -1,5 +1,99 @@ # @backstage/plugin-gateway-backend +## 1.1.4 + +### Patch Changes + +- 79453c0: Updated dependency `wait-for-expect` to `^4.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + +## 1.1.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + +## 1.1.4-next.0 + +### Patch Changes + +- 79453c0: Updated dependency `wait-for-expect` to `^4.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + +## 1.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + +## 1.1.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + +## 1.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + +## 1.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + +## 1.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + +## 1.1.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 7e860dd: Updated dependency `eventsource` to `^4.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + +## 1.1.1-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 1.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + +## 1.1.0 + +### Minor Changes + +- 229f63e: Added hop count tracking to prevent proxy loops. The gateway now tracks the number of proxy hops using the `backstage-gateway-hops` header and rejects requests that exceed 3 hops with a 508 Loop Detected error. +- a17d9df: Update usage of the `instanceMetadata` service. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + ## 1.1.0-next.1 ### Minor Changes diff --git a/plugins/gateway-backend/package.json b/plugins/gateway-backend/package.json index 553f17f2fa..4f34266740 100644 --- a/plugins/gateway-backend/package.json +++ b/plugins/gateway-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-gateway-backend", - "version": "1.1.0-next.1", + "version": "1.1.4", "backstage": { "role": "backend-plugin", "pluginId": "gateway", @@ -37,7 +37,7 @@ "@backstage/backend-plugin-api": "workspace:^", "@opentelemetry/api": "^1.9.0", "@opentelemetry/core": "^1.29.0", - "express": "^4.17.1", + "express": "^4.22.0", "http-proxy-middleware": "^3.0.3" }, "devDependencies": { @@ -46,7 +46,7 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@types/express": "^4.17.6", - "eventsource": "^3.0.6", - "wait-for-expect": "^3.0.2" + "eventsource": "^4.0.0", + "wait-for-expect": "^4.0.0" } } diff --git a/plugins/gateway-backend/src/plugin.test.ts b/plugins/gateway-backend/src/plugin.test.ts index 078fde044e..301eae3abb 100644 --- a/plugins/gateway-backend/src/plugin.test.ts +++ b/plugins/gateway-backend/src/plugin.test.ts @@ -19,7 +19,6 @@ import { mockServices } from '@backstage/backend-test-utils'; import { coreServices, createBackendPlugin, - createServiceFactory, } from '@backstage/backend-plugin-api'; import { Router } from 'express'; import { EventSource } from 'eventsource'; @@ -48,12 +47,13 @@ describe('gateway', () => { }, }); - const discovery = mockServices.discovery.mock(); - discovery.getBaseUrl.mockImplementation(async (pluginId: string) => { - if (pluginId === 'external-plugin') { - return 'http://localhost:7778/api/external-plugin'; - } - return `http://localhost:7777/api/${pluginId}`; + const discovery = mockServices.discovery.mock({ + async getBaseUrl(pluginId) { + if (pluginId === 'external-plugin') { + return 'http://localhost:7778/api/external-plugin'; + } + return `http://localhost:7777/api/${pluginId}`; + }, }); beforeAll(async () => { @@ -68,13 +68,7 @@ describe('gateway', () => { ); backend.add(mockServices.auth.factory()); backend.add(mockServices.httpAuth.factory()); - backend.add( - createServiceFactory({ - service: coreServices.discovery, - deps: {}, - factory: () => discovery, - }), - ); + backend.add(discovery.factory); backend.add(dummyPlugin); backend.add(import('./')); @@ -161,6 +155,41 @@ describe('gateway', () => { expect(data).toEqual({ bar: true }); }); + it('should detect looped requests', async () => { + const response = await fetch( + 'http://localhost:7777/api/nonexistent-plugin/foo', + ); + expect(response.status).toBe(508); + + const data = await response.json(); + expect(data).toEqual({ + error: { + name: 'LoopDetectedError', + message: 'Maximum proxy hop count exceeded (3)', + }, + }); + }); + + it('should detect looped requests with intentional negative hop count', async () => { + const response = await fetch( + 'http://localhost:7777/api/nonexistent-plugin/foo', + { + headers: { + 'backstage-gateway-hops': '-1000000', + }, + }, + ); + expect(response.status).toBe(508); + + const data = await response.json(); + expect(data).toEqual({ + error: { + name: 'LoopDetectedError', + message: 'Maximum proxy hop count exceeded (3)', + }, + }); + }); + it('should close the response for sse connections', async () => { const eventSource = new EventSource( 'http://localhost:7777/api/external-plugin/endpoint-sse', diff --git a/plugins/gateway-backend/src/router.ts b/plugins/gateway-backend/src/router.ts index 8263fb27ab..6c9b4ba6c7 100644 --- a/plugins/gateway-backend/src/router.ts +++ b/plugins/gateway-backend/src/router.ts @@ -23,9 +23,13 @@ import { createProxyMiddleware } from 'http-proxy-middleware'; import { context } from '@opentelemetry/api'; import { getRPCMetadata } from '@opentelemetry/core'; +const MAX_HOPS = 3; +const HOPS_HEADER = 'backstage-gateway-hops'; + export async function createRouter({ discovery, instanceMeta, + logger, }: { discovery: DiscoveryService; instanceMeta: RootInstanceMetadataService; @@ -41,6 +45,12 @@ export async function createRouter({ return discovery.getBaseUrl(pluginId); }, on: { + proxyReq(proxyReq, req: Request<{ pluginId: string }>) { + const currentHops = + Math.max(parseInt(req.headers[HOPS_HEADER] as string, 10), 0) || 0; + + proxyReq.setHeader(HOPS_HEADER, currentHops + 1); + }, proxyRes(proxyRes, _req, res) { // https://github.com/chimurai/http-proxy-middleware/discussions/765 proxyRes.on('close', () => { @@ -62,6 +72,20 @@ export async function createRouter({ return; } + const currentHops = parseInt(req.headers[HOPS_HEADER] as string, 10) || 0; + if (currentHops >= MAX_HOPS) { + logger.warn( + `Proxy loop detected for plugin '${req.params.pluginId}': request exceeded maximum hop count (${currentHops})`, + ); + res.status(508).json({ + error: { + name: 'LoopDetectedError', + message: `Maximum proxy hop count exceeded (${currentHops})`, + }, + }); + return; + } + const rpcMetadata = getRPCMetadata(context.active()); if (rpcMetadata) { rpcMetadata.route = req.baseUrl; diff --git a/plugins/home-react/CHANGELOG.md b/plugins/home-react/CHANGELOG.md index 8b3c322325..a5102e3a3a 100644 --- a/plugins/home-react/CHANGELOG.md +++ b/plugins/home-react/CHANGELOG.md @@ -1,5 +1,158 @@ # @backstage/plugin-home-react +## 0.1.37 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/core-components@0.18.9 + - @backstage/core-compat-api@0.5.10 + - @backstage/core-plugin-api@1.12.5 + +## 0.1.37-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-compat-api@0.5.10-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + +## 0.1.37-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.9-next.0 + - @backstage/frontend-plugin-api@0.15.2-next.0 + - @backstage/core-compat-api@0.5.10-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + +## 0.1.36 + +### Patch Changes + +- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point. +- Updated dependencies + - @backstage/core-compat-api@0.5.9 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + +## 0.1.36-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.5.9-next.2 + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/core-components@0.18.8-next.1 + +## 0.1.36-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/core-compat-api@0.5.9-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + +## 0.1.35 + +### Patch Changes + +- 90956a6: Support new frontend system in the homepage plugin +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/core-components@0.18.7 + - @backstage/core-compat-api@0.5.8 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/core-plugin-api@1.12.3 + +## 0.1.35-next.1 + +### Patch Changes + +- 90956a6: Support new frontend system in the homepage plugin +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-compat-api@0.5.8-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + +## 0.1.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.6-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + +## 0.1.34 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + +## 0.1.34-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + +## 0.1.33 + +### Patch Changes + +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + +## 0.1.33-next.1 + +### Patch Changes + +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/core-components@0.18.4-next.2 + - @backstage/core-plugin-api@1.12.1-next.0 + +## 0.1.33-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/core-components@0.18.4-next.0 + +## 0.1.32 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + ## 0.1.32-next.0 ### Patch Changes diff --git a/plugins/home-react/knip-report.md b/plugins/home-react/knip-report.md index 3fea06e47b..b8785259df 100644 --- a/plugins/home-react/knip-report.md +++ b/plugins/home-react/knip-report.md @@ -1,8 +1,14 @@ # Knip report +## Unused dependencies (1) + +| Name | Location | Severity | +| :------------------------- | :---------------- | :------- | +| @backstage/core-compat-api | package.json:58:6 | error | + ## Unused devDependencies (1) -| Name | Location | Severity | -| :----------------------- | :----------- | :------- | -| @types/react-grid-layout | plugins/home-react/package.json | error | +| Name | Location | Severity | +| :----------------------- | :---------------- | :------- | +| @types/react-grid-layout | package.json:69:6 | error | diff --git a/plugins/home-react/package.json b/plugins/home-react/package.json index 8375222222..5c2c71da97 100644 --- a/plugins/home-react/package.json +++ b/plugins/home-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home-react", - "version": "0.1.32-next.0", + "version": "0.1.37", "description": "A Backstage plugin that contains react components helps you build a home page", "backstage": { "role": "web-library", @@ -55,12 +55,13 @@ "test": "backstage-cli package test" }, "dependencies": { + "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", - "@rjsf/utils": "5.23.2" + "@rjsf/utils": "5.24.13" }, "devDependencies": { "@backstage/cli": "workspace:^", @@ -68,13 +69,13 @@ "@types/react-grid-layout": "^1.3.2", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/plugins/home-react/report-alpha.api.md b/plugins/home-react/report-alpha.api.md index fad4c5a2bc..4cadb413bd 100644 --- a/plugins/home-react/report-alpha.api.md +++ b/plugins/home-react/report-alpha.api.md @@ -3,9 +3,129 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AppNode } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { JSX as JSX_2 } from 'react'; +import { ReactElement } from 'react'; +import { RJSFSchema } from '@rjsf/utils'; import { TranslationRef } from '@backstage/frontend-plugin-api'; +import { UiSchema } from '@rjsf/utils'; -// @alpha (undocumented) +// @public (undocumented) +export type CardLayout = { + width?: { + minColumns?: number; + maxColumns?: number; + defaultColumns?: number; + }; + height?: { + minRows?: number; + maxRows?: number; + defaultRows?: number; + }; +}; + +// @public (undocumented) +export type CardSettings = { + schema?: RJSFSchema; + uiSchema?: UiSchema; +}; + +// @public (undocumented) +export type ComponentParts = { + Content: (props?: any) => JSX.Element; + Actions?: () => JSX.Element; + Settings?: () => JSX.Element; + ContextProvider?: (props: any) => JSX.Element; +}; + +// @alpha +export const HomePageLayoutBlueprint: ExtensionBlueprint<{ + kind: 'home-page-layout'; + params: HomePageLayoutBlueprintParams; + output: ExtensionDataRef< + (props: HomePageLayoutProps) => JSX_2.Element, + 'home.layout.component', + {} + >; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { + component: ConfigurableExtensionDataRef< + (props: HomePageLayoutProps) => JSX_2.Element, + 'home.layout.component', + {} + >; + }; +}>; + +// @alpha +export interface HomePageLayoutBlueprintParams { + loader: () => Promise<(props: HomePageLayoutProps) => JSX_2.Element>; +} + +// @alpha +export const homePageLayoutComponentDataRef: ConfigurableExtensionDataRef< + (props: HomePageLayoutProps) => JSX_2.Element, + 'home.layout.component', + {} +>; + +// @alpha +export interface HomePageLayoutProps { + widgets: Array; +} + +// @alpha +export const HomePageWidgetBlueprint: ExtensionBlueprint<{ + kind: 'home-page-widget'; + params: HomePageWidgetBlueprintParams; + output: ExtensionDataRef; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { + widget: ConfigurableExtensionDataRef< + HomePageWidgetData, + 'home.widget.data', + {} + >; + }; +}>; + +// @alpha +export interface HomePageWidgetBlueprintParams { + componentProps?: Record; + components: () => Promise; + description?: string; + layout?: CardLayout; + name?: string; + settings?: CardSettings; + title?: string; +} + +// @alpha +export interface HomePageWidgetData { + component: ReactElement; + description?: string; + layout?: CardLayout; + name?: string; + node: AppNode; + settings?: CardSettings; + title?: string; +} + +// @alpha +export const homePageWidgetDataRef: ConfigurableExtensionDataRef< + HomePageWidgetData, + 'home.widget.data', + {} +>; + +// @alpha @deprecated (undocumented) export const homeReactTranslationRef: TranslationRef< 'home-react', { diff --git a/plugins/home-react/report.api.md b/plugins/home-react/report.api.md index 26ef6267aa..0444010dea 100644 --- a/plugins/home-react/report.api.md +++ b/plugins/home-react/report.api.md @@ -9,6 +9,7 @@ import { JSX as JSX_3 } from 'react'; import { Overrides } from '@material-ui/core/styles/overrides'; import { RJSFSchema } from '@rjsf/utils'; import { StyleRules } from '@material-ui/core/styles/withStyles'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; import { UiSchema } from '@rjsf/utils'; // @public (undocumented) @@ -81,6 +82,16 @@ export function createCardExtension(options: { settings?: CardSettings; }): Extension<(props: CardExtensionProps) => JSX_2.Element>; +// @public +export const homeReactTranslationRef: TranslationRef< + 'home-react', + { + readonly 'settingsModal.title': 'Settings'; + readonly 'settingsModal.closeButtonTitle': 'Close'; + readonly 'cardExtension.settingsButtonTitle': 'Settings'; + } +>; + // @public (undocumented) export type PluginHomeComponentsNameToClassKey = { PluginHomeContentModal: PluginHomeContentModalClassKey; diff --git a/plugins/home-react/src/alpha.ts b/plugins/home-react/src/alpha.ts index 3a08699f66..c89ff12afd 100644 --- a/plugins/home-react/src/alpha.ts +++ b/plugins/home-react/src/alpha.ts @@ -13,4 +13,35 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { homeReactTranslationRef } from './translation'; + +/** + * React components and utilities for the home plugin's new frontend system. + * + * @remarks + * This package provides React components, blueprints, and utilities for building + * customizable home pages with the new Backstage frontend system. + * + * @packageDocumentation + */ +import { homeReactTranslationRef as _homeReactTranslationRef } from './translation'; + +/** + * @alpha + * @deprecated Import from `@backstage/plugin-home-react` instead. + */ +export const homeReactTranslationRef = _homeReactTranslationRef; +export { + HomePageWidgetBlueprint, + type HomePageWidgetBlueprintParams, +} from './alpha/blueprints/HomePageWidgetBlueprint'; +export { + HomePageLayoutBlueprint, + type HomePageLayoutBlueprintParams, +} from './alpha/blueprints/HomePageLayoutBlueprint'; +export { + homePageWidgetDataRef, + type HomePageWidgetData, + homePageLayoutComponentDataRef, + type HomePageLayoutProps, +} from './alpha/dataRefs'; +export type { ComponentParts, CardLayout, CardSettings } from './extensions'; diff --git a/plugins/home-react/src/alpha/blueprints/HomePageLayoutBlueprint.tsx b/plugins/home-react/src/alpha/blueprints/HomePageLayoutBlueprint.tsx new file mode 100644 index 0000000000..9610b79993 --- /dev/null +++ b/plugins/home-react/src/alpha/blueprints/HomePageLayoutBlueprint.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 { JSX } from 'react'; +import { + createExtensionBlueprint, + ExtensionBoundary, +} from '@backstage/frontend-plugin-api'; +import { + homePageLayoutComponentDataRef, + HomePageLayoutProps, +} from '../dataRefs'; + +/** + * Parameters for creating a home page layout extension. + * + * @alpha + */ +export interface HomePageLayoutBlueprintParams { + /** + * Async loader that returns the layout component. + * The component receives the collected widgets and renders them. + */ + loader: () => Promise<(props: HomePageLayoutProps) => JSX.Element>; +} + +/** + * Blueprint for creating custom home page layouts. + * + * A layout receives the list of installed widgets and is responsible for + * arranging them on the home page. This follows the same pattern as + * `EntityContentLayoutBlueprint` in the catalog plugin. + * + * If no layout extension is installed, the home page uses a built-in default. + * Users can install their own layout to customize how widgets are arranged. + * + * @alpha + */ +export const HomePageLayoutBlueprint = createExtensionBlueprint({ + kind: 'home-page-layout', + attachTo: { id: 'page:home', input: 'layout' }, + output: [homePageLayoutComponentDataRef], + dataRefs: { + component: homePageLayoutComponentDataRef, + }, + *factory({ loader }: HomePageLayoutBlueprintParams, { node }) { + yield homePageLayoutComponentDataRef( + ExtensionBoundary.lazyComponent(node, loader), + ); + }, +}); diff --git a/plugins/home-react/src/alpha/blueprints/HomePageWidgetBlueprint.tsx b/plugins/home-react/src/alpha/blueprints/HomePageWidgetBlueprint.tsx new file mode 100644 index 0000000000..4cd38754fa --- /dev/null +++ b/plugins/home-react/src/alpha/blueprints/HomePageWidgetBlueprint.tsx @@ -0,0 +1,128 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { lazy, ReactElement } from 'react'; +import { + createExtensionBlueprint, + ExtensionBoundary, +} from '@backstage/frontend-plugin-api'; +import { attachComponentData } from '@backstage/core-plugin-api'; +import { + CardExtension, + CardExtensionProps, + CardLayout, + CardSettings, + ComponentParts, +} from '../../extensions'; +import { homePageWidgetDataRef } from '../dataRefs'; + +/** + * Parameters for creating a home page widget extension. + * + * @alpha + */ +export interface HomePageWidgetBlueprintParams { + /** + * Optional name for the widget. If not provided, the extension will use only its kind + * in the extension ID. + */ + name?: string; + /** + * Optional title displayed for the widget, used as the default card heading. + */ + title?: string; + /** + * Optional description shown in the widget catalog when adding new cards. + */ + description?: string; + /** + * Component parts rendered within the card. + */ + components: () => Promise; + /** + * Layout hints used by the customizable grid. + */ + layout?: CardLayout; + /** + * Schema used to configure widget settings. + */ + settings?: CardSettings; + /** + * Default props forwarded to the rendered widget component. + */ + componentProps?: Record; +} + +const DEFAULT_WIDGET_ATTACH_POINT = { + id: 'page:home', + input: 'widgets', +} as const; + +/** + * Creates widgets that can be installed into the home page grid. + * + * @alpha + */ +export const HomePageWidgetBlueprint = createExtensionBlueprint({ + kind: 'home-page-widget', + attachTo: DEFAULT_WIDGET_ATTACH_POINT, + dataRefs: { + widget: homePageWidgetDataRef, + }, + output: [homePageWidgetDataRef], + *factory(params: HomePageWidgetBlueprintParams, { node }) { + const isCustomizable = params.settings?.schema !== undefined; + const widgetName = params.name ?? node.spec.id; + const LazyCard = lazy(() => + params.components().then(parts => ({ + default: (props: CardExtensionProps>) => ( + + ), + })), + ); + + const Widget = ( + props: CardExtensionProps>, + ): ReactElement => ( + + + + ); + + attachComponentData(Widget, 'core.extensionName', widgetName); + attachComponentData(Widget, 'title', params.title); + attachComponentData(Widget, 'description', params.description); + attachComponentData(Widget, 'home.widget.config', { + layout: params.layout, + settings: params.settings, + }); + + yield homePageWidgetDataRef({ + node, + component: , + name: widgetName, + title: params.title, + description: params.description, + layout: params.layout, + settings: params.settings, + }); + }, +}); diff --git a/plugins/home-react/src/alpha/dataRefs.ts b/plugins/home-react/src/alpha/dataRefs.ts new file mode 100644 index 0000000000..b626289541 --- /dev/null +++ b/plugins/home-react/src/alpha/dataRefs.ts @@ -0,0 +1,100 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + createExtensionDataRef, + type AppNode, +} from '@backstage/frontend-plugin-api'; +import { JSX, ReactElement } from 'react'; +import type { CardLayout, CardSettings } from '../extensions'; + +/** + * Extension data for homepage widgets, bundling the rendered component + * with its metadata. + * + * @alpha + */ +export interface HomePageWidgetData { + /** + * The originating app node for this widget. + */ + node: AppNode; + /** + * The rendered widget component (typically a card with header, content, etc.) + */ + component: ReactElement; + /** + * Optional name identifier for the widget + */ + name?: string; + /** + * Optional title displayed in the widget header + */ + title?: string; + /** + * Optional description shown in widget catalogs or configuration UIs + */ + description?: string; + /** + * Optional layout hints for positioning and sizing + */ + layout?: CardLayout; + /** + * Optional settings schema for widget configuration + */ + settings?: CardSettings; +} + +/** + * Extension data ref for homepage widgets. + * + * This follows the pattern from FormFieldBlueprint, bundling the component + * and metadata into a single comprehensive data ref rather than outputting + * them separately. + * + * @alpha + */ +export const homePageWidgetDataRef = + createExtensionDataRef().with({ + id: 'home.widget.data', + }); + +/** + * Props provided to a home page layout component. + * + * @alpha + */ +export interface HomePageLayoutProps { + /** + * The list of widget elements and metadata to render on the home page. + */ + widgets: Array; +} + +/** + * Extension data ref for home page layout components. + * + * A layout receives the collected widgets and is responsible for arranging + * them on the home page. This follows the same pattern as + * EntityContentLayoutBlueprint in the catalog plugin. + * + * @alpha + */ +export const homePageLayoutComponentDataRef = createExtensionDataRef< + (props: HomePageLayoutProps) => JSX.Element +>().with({ + id: 'home.layout.component', +}); diff --git a/plugins/home-react/src/extensions.tsx b/plugins/home-react/src/extensions.tsx index 7795e20c73..699a3f9c9f 100644 --- a/plugins/home-react/src/extensions.tsx +++ b/plugins/home-react/src/extensions.tsx @@ -121,7 +121,7 @@ type CardExtensionComponentProps = CardExtensionProps & overrideTitle?: string; }; -function CardExtension(props: CardExtensionComponentProps) { +export function CardExtension(props: CardExtensionComponentProps) { const { Renderer, Content, diff --git a/plugins/home-react/src/index.ts b/plugins/home-react/src/index.ts index 54602dc814..f24d85b2b8 100644 --- a/plugins/home-react/src/index.ts +++ b/plugins/home-react/src/index.ts @@ -31,3 +31,4 @@ export type { CardConfig, } from './extensions'; export * from './overridableComponents'; +export { homeReactTranslationRef } from './translation'; diff --git a/plugins/home-react/src/translation.ts b/plugins/home-react/src/translation.ts index 8de5bb1ff1..ab67d4b37e 100644 --- a/plugins/home-react/src/translation.ts +++ b/plugins/home-react/src/translation.ts @@ -16,7 +16,10 @@ import { createTranslationRef } from '@backstage/frontend-plugin-api'; /** - * @alpha + * Translation reference for the home-react plugin. + * Contains localized text strings for home page components and widgets. + * + * @public */ export const homeReactTranslationRef = createTranslationRef({ id: 'home-react', diff --git a/plugins/home/CHANGELOG.md b/plugins/home/CHANGELOG.md index 03213110af..dd8053a3b7 100644 --- a/plugins/home/CHANGELOG.md +++ b/plugins/home/CHANGELOG.md @@ -1,5 +1,307 @@ # @backstage/plugin-home +## 0.9.4 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.7.3 + - @backstage/catalog-model@1.8.0 + - @backstage/plugin-catalog-react@2.1.2 + - @backstage/core-app-api@1.20.0 + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/core-components@0.18.9 + - @backstage/core-compat-api@0.5.10 + - @backstage/catalog-client@1.15.0 + - @backstage/config@1.3.7 + - @backstage/core-plugin-api@1.12.5 + - @backstage/plugin-home-react@0.1.37 + +## 0.9.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.7.3-next.0 + - @backstage/core-app-api@1.20.0-next.2 + - @backstage/core-components@0.18.9-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.2 + - @backstage/catalog-client@1.14.1-next.0 + - @backstage/catalog-model@1.7.8-next.0 + - @backstage/config@1.3.7-next.0 + - @backstage/core-compat-api@0.5.10-next.2 + - @backstage/core-plugin-api@1.12.5-next.2 + - @backstage/frontend-plugin-api@0.16.0-next.2 + +## 0.9.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-compat-api@0.5.10-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-home-react@0.1.37-next.1 + +## 0.9.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.1-next.0 + - @backstage/core-components@0.18.9-next.0 + - @backstage/frontend-plugin-api@0.15.2-next.0 + - @backstage/core-compat-api@0.5.10-next.0 + - @backstage/core-app-api@1.19.7-next.0 + - @backstage/plugin-home-react@0.1.37-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + - @backstage/catalog-client@1.14.0 + - @backstage/catalog-model@1.7.7 + - @backstage/config@1.3.6 + - @backstage/theme@0.7.2 + +## 0.9.3 + +### Patch Changes + +- 538c985: Updated installation documentation to use feature discovery as the default. +- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point. +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/core-compat-api@0.5.9 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/catalog-client@1.14.0 + - @backstage/core-app-api@1.19.6 + - @backstage/plugin-home-react@0.1.36 + - @backstage/catalog-model@1.7.7 + +## 0.9.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.5.9-next.2 + - @backstage/core-app-api@1.19.6-next.1 + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/catalog-client@1.14.0-next.2 + - @backstage/plugin-catalog-react@2.1.0-next.2 + - @backstage/core-components@0.18.8-next.1 + - @backstage/plugin-home-react@0.1.36-next.1 + +## 0.9.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-compat-api@0.5.9-next.1 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/theme@0.7.2 + - @backstage/plugin-home-react@0.1.36-next.0 + +## 0.9.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/catalog-client@1.13.1-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-compat-api@0.5.9-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/theme@0.7.2 + - @backstage/plugin-home-react@0.1.36-next.0 + +## 0.9.2 + +### Patch Changes + +- 018ca87: Added `title` and `icon` to the plugin definition for the new frontend system. +- 90956a6: Support new frontend system in the homepage plugin +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/core-components@0.18.7 + - @backstage/core-app-api@1.19.5 + - @backstage/core-compat-api@0.5.8 + - @backstage/theme@0.7.2 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/catalog-client@1.13.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-home-react@0.1.35 + +## 0.9.2-next.2 + +### Patch Changes + +- 90956a6: Support new frontend system in the homepage plugin +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/core-app-api@1.19.5-next.1 + - @backstage/catalog-client@1.12.2-next.0 + - @backstage/plugin-home-react@0.1.35-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-compat-api@0.5.8-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/theme@0.7.2-next.1 + +## 0.9.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/theme@0.7.2-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/core-components@0.18.7-next.1 + +## 0.9.1-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/core-app-api@1.19.4-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/theme@0.7.1 + - @backstage/plugin-home-react@0.1.35-next.0 + +## 0.9.0 + +### Minor Changes + +- e091a83: Widget configurations are now only saved to storage when the Save button is explicitly clicked. Added a Cancel button that allows users to discard unsaved changes and revert to the last saved state. + +### Patch Changes + +- bdda543: Updated WidgetOverlay color to use `alpha(theme.palette.background.paper, 0.93)` for better theme alignment instead of hardcoded RGBA +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + - @backstage/plugin-catalog-react@1.21.5 + - @backstage/plugin-home-react@0.1.34 + +## 0.9.0-next.2 + +### Minor Changes + +- e091a83: Widget configurations are now only saved to storage when the Save button is explicitly clicked. Added a Cancel button that allows users to discard unsaved changes and revert to the last saved state. + +## 0.8.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.1 + - @backstage/plugin-home-react@0.1.34-next.0 + +## 0.8.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.5-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## 0.8.15 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/plugin-home-react@0.1.33 + - @backstage/theme@0.7.1 + +## 0.8.15-next.1 + +### Patch Changes + +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- Updated dependencies + - @backstage/core-app-api@1.19.3-next.1 + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/plugin-home-react@0.1.33-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + +## 0.8.15-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/plugin-home-react@0.1.33-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + +## 0.8.14 + +### Patch Changes + +- 2ac5d29: Allow customization of `VisitList` by adding optional `enrichVisit`, `transformPathname`, `canSave` functions to `VisitsStorageApi`, along with `VisitDisplayProvider` for colors, labels +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-home-react@0.1.32 + - @backstage/catalog-client@1.12.1 + ## 0.8.14-next.1 ### Patch Changes diff --git a/plugins/home/README.md b/plugins/home/README.md index 242e66ff40..2c46530c3d 100644 --- a/plugins/home/README.md +++ b/plugins/home/README.md @@ -4,15 +4,151 @@ The Home plugin introduces a system for composing a Home Page for Backstage in o For App Integrators, the system is designed to be composable to give total freedom in designing a Home Page that suits the needs of the organization. From the perspective of a Component Developer who wishes to contribute with building blocks to be included in Home Pages, there's a convenient interface for bundling the different parts and exporting them with both error boundary and lazy loading handled under the surface. -## Getting started - -If you have a standalone app (you didn't clone this repo), then do +## Installation ```bash # From your Backstage root directory yarn --cwd packages/app add @backstage/plugin-home ``` +Once installed, the plugin is automatically available in your app through the default feature discovery. For more details and alternative installation methods, see [installing plugins](https://backstage.io/docs/frontend-system/building-apps/installing-plugins). + +The plugin will automatically provide: + +- A homepage at `/home` with customizable widget grid +- A "Home" navigation item in the sidebar + +## Creating Custom Homepage Layouts + +Use the `HomePageLayoutBlueprint` from `@backstage/plugin-home-react/alpha` to +create custom homepage layouts. A layout receives the installed widgets and is +responsible for arranging them on the page. If no custom layout is installed, the +plugin provides a built-in default. + +```ts +import { HomePageLayoutBlueprint } from '@backstage/plugin-home-react/alpha'; +import { CustomHomepageGrid } from '@backstage/plugin-home'; +import { Content, Header, Page } from '@backstage/core-components'; +import { Fragment } from 'react'; + +const myHomePageLayout = HomePageLayoutBlueprint.make({ + params: { + loader: async () => + function MyHomePageLayout({ widgets }) { + return ( + +
    + + + {widgets.map((widget, index) => ( + + {widget.component} + + ))} + + + + ); + }, + }, +}); +``` + +Then register the layout in a frontend module: + +```ts +import { createFrontendModule } from '@backstage/frontend-plugin-api'; + +const homeModule = createFrontendModule({ + pluginId: 'home', + extensions: [myHomePageLayout], +}); +``` + +## Visit Tracking (Optional) + +Visit tracking is an **optional feature** that must be explicitly enabled. When enabled, it provides intelligent storage fallbacks: + +**Enabling Visit Tracking:** + +Add the following to your `app-config.yaml`: + +```yaml +app: + extensions: + # Enable visit tracking API (disabled by default) + - api:home/visits: true + # Enable visit listener (disabled by default) + - app-root-element:home/visit-listener: true +``` + +**Storage Strategy (when enabled):** + +1. **Custom Storage API**: If you have `storageApiRef` configured (like database-backed `UserSettingsStorage`), visit data uses your custom storage +2. **Browser Local Storage Fallback**: If no custom storage is configured, automatically falls back to browser local storage + +**Note**: Visit tracking extensions are disabled by default to give users control over data collection and storage. + +## Creating Homepage Widgets + +Homepage widgets are React components that can be added to customizable home pages. + +Create widgets using the `HomePageWidgetBlueprint`: + +```ts +import { HomePageWidgetBlueprint } from '@backstage/plugin-home-react/alpha'; + +const myWidget = HomePageWidgetBlueprint.make({ + name: 'my-widget', + params: { + name: 'MyWidget', + title: 'My Custom Widget', + description: 'A custom widget for the homepage', + components: () => + import('./MyWidgetComponent').then(m => ({ + Content: m.Content, + })), + layout: { + height: { minRows: 4 }, + width: { minColumns: 3 }, + }, + settings: { + schema: { + title: 'Widget Settings', + type: 'object', + properties: { + color: { + title: 'Color', + type: 'string', + default: 'blue', + enum: ['blue', 'red', 'green'], + }, + }, + }, + }, + }, +}); +``` + +> **Example**: See [dev/index.tsx](dev/index.tsx) for a comprehensive example of creating multiple homepage widgets and layouts. + +## Contributing + +### Homepage Components + +We believe that people have great ideas for what makes a useful Home Page, and we want to make it easy for everyone to benefit from the effort you put in to create something cool for the Home Page. Therefore, a great way of contributing is by simply creating more Home Page Components that can then be used by everyone when composing their own Home Page. If they are tightly coupled to an existing plugin, it is recommended to allow them to live within that plugin, for convenience and to limit complex dependencies. On the other hand, if there's no clear plugin that the component is based on, it's also fine to contribute them into the [home plugin](/plugins/home/src/homePageComponents) + +Additionally, the API is at a very early state, so contributing additional use cases may expose weaknesses in the current solution that we may iterate on to provide more flexibility and ease of use for those who wish to develop components for the Home Page. + +### Homepage Templates + +We are hoping that we together can build up a collection of Homepage templates. We therefore put together a place where we can collect all the templates for the Home Plugin in the [storybook](https://backstage.io/storybook/?path=/story/plugins-home-templates). +If you would like to contribute with a template, start by taking a look at the [DefaultTemplate storybook example](/packages/app-legacy/src/components/home/templates/DefaultTemplate.stories.tsx) or [CustomizableTemplate storybook example](/packages/app-legacy/src/components/home/templates/CustomizableTemplate.stories.tsx) to create your own, and then open a PR with your suggestion. + +## Old Frontend System + +This section covers the home plugin setup and usage for apps that still use the old frontend system. + ### Setting up the Home Page 1. Create a Home Page Component that will be used for composition. @@ -40,28 +176,26 @@ import { homePage } from './components/home/HomePage'; // ... ``` -### Creating Components +### Creating Widgets (Old Frontend System) -The Home Page can be composed with regular React components, so there's no magic in creating components to be used for composition 🪄 🎩 . However, in order to assure that your component fits into a diverse set of Home Pages, there's an extension creator for this purpose, that creates a Card-based layout, for consistency between components (read more about extensions [here](https://backstage.io/docs/plugins/composability#extensions)). The extension creator requires two fields: `title` and `components`. The `components` field is expected to be an asynchronous import that should at least contain a `Content` field. Additionally, you can optionally provide `settings`, `actions` and `contextProvider` as well. These parts will be combined to create a card, where the `content`, `actions` and `settings` will be wrapped within the `contextProvider` in order to be able to access to context and effectively communicate with one another. - -Finally, the `createCardExtension` also accepts a generic, such that Component Developers can indicate to App Integrators what custom props their component will accept, such as the example below where the default category of the random jokes can be set. +In the old frontend system, use the `createCardExtension` helper to create homepage widgets: ```tsx import { createCardExtension } from '@backstage/plugin-home-react'; -export const RandomJokeHomePageComponent = homePlugin.provide( +export const MyWidget = homePlugin.provide( createCardExtension<{ defaultCategory?: 'programming' | 'any' }>({ - title: 'Random Joke', - components: () => import('./homePageComponents/RandomJoke'), + title: 'My Custom Widget', + components: () => import('./homePageComponents/MyWidget'), }), ); ``` -In summary: it is not necessary to use the `createCardExtension` extension creator to register a home page component, although it is convenient since it provides error boundary and lazy loading, and it also may hook into other functionality in the future. +The `createCardExtension` provides error boundary and lazy loading, and accepts generics for custom props that App Integrators can configure. -### Composing a Home Page +### Composing a Home Page (Old Frontend System) -Composing a Home Page is no different from creating a regular React Component, i.e. the App Integrator is free to include whatever content they like. However, there are components developed with the Home Page in mind, as described in the previous section. If created by the `createCardExtension` extension creator, they are rendered like so +Composing a Home Page is done by creating regular React components. Components created with `createCardExtension` are rendered like so: ```tsx import Grid from '@material-ui/core/Grid'; @@ -78,7 +212,7 @@ export const homePage = ( Additionally, the App Integrator is provided an escape hatch in case the way the card is rendered does not fit their requirements. They may optionally pass the `Renderer`-prop, which will receive the `title`, `content` and optionally `actions`, `settings` and `contextProvider`, if they exist for the component. This allows the App Integrator to render the content in any way they want. -## Customizable home page +### Customizable Home Page (Old Frontend System) If you want to allow users to customize the components that are shown in the home page, you can use CustomHomePageGrid component. By adding the allowed components inside the grid, the user can add, configure, remove and move the components around in their @@ -110,7 +244,7 @@ export const homePage = ( > [!NOTE] > You can provide a title to the grid by passing it as a prop: ``. This will be displayed as a header above the grid layout. -### Creating Customizable Components +#### Creating Customizable Components (Old Frontend System) The custom home page can use the default components created by using the default `createCardExtension` method but if you want to add additional configuration like component size or settings, you can define those in the `layout` @@ -168,7 +302,7 @@ Available home page properties that are used for homepage widgets are: | `layout.height.maxRows` | integer | Maximum height of the widget (1-12) | | `settings.schema` | object | Customization settings of the widget, see below | -#### Widget Specific Settings +#### Widget Specific Settings (Old Frontend System) To define settings that the users can change for your component, you should define the `layout` and `settings` properties. The `settings.schema` object should follow @@ -220,7 +354,7 @@ Each widget has its own settings and the setting values are passed to the underl In case your `CardExtension` had `Settings` component defined, it will automatically disappear when you add the `settingsSchema` to the component data structure. -### Adding Default Layout +#### Adding Default Layout (Old Frontend System) You can set the default layout of the customizable home page by passing configuration to the `CustomHomepageGrid` component: @@ -242,7 +376,7 @@ const defaultConfig = [ ``` -## Page visit homepage component (HomePageTopVisited / HomePageRecentlyVisited) +### Page Visit Homepage Component (Old Frontend System) This component shows the homepage user a view for "Recently visited" or "Top visited". Being provided by the `` and `` component, see it in use on a homepage example below: @@ -293,7 +427,7 @@ export const apis: AnyApiFactory[] = [ VisitsStorageApi.create({ storageApi, identityApi }), }), - // Or a localStorage data implementation, relies on WebStorage implementation of storageApi + // Or a local storage data implementation, relies on WebStorage implementation of storageApi createApiFactory({ api: visitsApiRef, deps: { @@ -356,11 +490,11 @@ home: In order to validate the config you can use `backstage/cli config:check` -### Customizing the VisitList +#### Customizing the VisitList (Old Frontend System) If you want more control over the recent and top visited lists, you can write your own functions to transform the path names and determine which visits to save. You can also enrich each visit with other fields and customize the chip colors/labels in the visit lists. -#### Transform Pathname Function +##### Transform Pathname Function Provide a `transformPathname` function to transform the pathname before it's processed for visit tracking. This can be used for transforming the pathname for the visit (before any other consideration). As an example, you can treat multiple sub-path visits to be counted as a singular path, e.g. `/entity-path/sub1` , `/entity-path/sub-2`, `/entity-path/sub-2/sub-sub-2` can all be mapped to `/entity-path` so visits to any of those routes are all counted as the same. @@ -399,7 +533,7 @@ export const apis: AnyApiFactory[] = [ ]; ``` -#### Can Save Function +##### Can Save Function Provide a `canSave` function to determine which visits should be tracked and saved. This allows you to conditionally save visits to the list: @@ -437,7 +571,7 @@ export const apis: AnyApiFactory[] = [ ]; ``` -#### Enrich Visit Function +##### Enrich Visit Function You can also add the `enrichVisit` function to put additional values on each `Visit`. The values could later be used to customize the chips in the `VisitList`. For example, you could add the entity `type` on the `Visit` so that `type` is used for labels instead of `kind`. @@ -488,7 +622,7 @@ export const apis: AnyApiFactory[] = [ ]; ``` -#### Custom Chip Colors and Labels +##### Custom Chip Colors and Labels To provide your own chip colors and/or labels for the recent and top visited lists, wrap the components in `VisitDisplayProvider` with `getChipColor` and `getChipLabel` functions. The colors provided will be used instead of the hard coded [`colorVariants`](https://github.com/backstage/backstage/blob/2da352043425bcab4c4422e4d2820c26c0a83382/packages/theme/src/base/pageTheme.ts#L46) provided via `@backstage/theme`. @@ -531,16 +665,3 @@ export default function HomePage() { ); } ``` - -## Contributing - -### Homepage Components - -We believe that people have great ideas for what makes a useful Home Page, and we want to make it easy for everyone to benefit from the effort you put in to create something cool for the Home Page. Therefore, a great way of contributing is by simply creating more Home Page Components that can then be used by everyone when composing their own Home Page. If they are tightly coupled to an existing plugin, it is recommended to allow them to live within that plugin, for convenience and to limit complex dependencies. On the other hand, if there's no clear plugin that the component is based on, it's also fine to contribute them into the [home plugin](/plugins/home/src/homePageComponents) - -Additionally, the API is at a very early state, so contributing additional use cases may expose weaknesses in the current solution that we may iterate on to provide more flexibility and ease of use for those who wish to develop components for the Home Page. - -### Homepage Templates - -We are hoping that we together can build up a collection of Homepage templates. We therefore put together a place where we can collect all the templates for the Home Plugin in the [storybook](https://backstage.io/storybook/?path=/story/plugins-home-templates). -If you would like to contribute with a template, start by taking a look at the [DefaultTemplate storybook example](/packages/app/src/components/home/templates/DefaultTemplate.stories.tsx) or [CustomizableTemplate storybook example](/packages/app/src/components/home/templates/CustomizableTemplate.stories.tsx) to create your own, and then open a PR with your suggestion. diff --git a/plugins/home/dev/index.tsx b/plugins/home/dev/index.tsx index 341253a49e..954bf21740 100644 --- a/plugins/home/dev/index.tsx +++ b/plugins/home/dev/index.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2021 The Backstage Authors + * Copyright 2025 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,14 +13,215 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { createDevApp } from '@backstage/dev-utils'; -import { homePlugin, HomepageCompositionRoot } from '../src/plugin'; -createDevApp() - .registerPlugin(homePlugin) - .addPage({ - element: , - title: 'Root Page', - path: '/', - }) - .render(); +import { Content, Header, Page } from '@backstage/core-components'; +import { createApp } from '@backstage/frontend-defaults'; +import { catalogApiRef } from '@backstage/plugin-catalog-react'; +import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils'; +import catalogPlugin from '@backstage/plugin-catalog/alpha'; +import HomeIcon from '@material-ui/icons/Home'; +import ReactDOM from 'react-dom/client'; +import { Fragment } from 'react'; + +import { + ApiBlueprint, + createFrontendModule, +} from '@backstage/frontend-plugin-api'; +import { + HomePageWidgetBlueprint, + HomePageLayoutBlueprint, +} from '@backstage/plugin-home-react/alpha'; +import { HeaderWorldClock, WelcomeTitle, type ClockConfig } from '../src'; +import homePlugin from '../src/alpha'; +import { CustomHomepageGrid } from '../src/components'; +import type { LayoutConfiguration } from '../src/components/CustomHomepage/types'; + +const clockConfigs: ClockConfig[] = [ + { label: 'NYC', timeZone: 'America/New_York' }, + { label: 'UTC', timeZone: 'UTC' }, + { label: 'STO', timeZone: 'Europe/Stockholm' }, + { label: 'TYO', timeZone: 'Asia/Tokyo' }, +]; + +const timeFormat: Intl.DateTimeFormatOptions = { + hour: '2-digit', + minute: '2-digit', + hour12: false, +}; + +const defaultGridConfig: LayoutConfiguration[] = [ + { + component: 'HomePageToolkit', + x: 0, + y: 0, + width: 12, + height: 4, + movable: false, + resizable: false, + }, + { + component: 'HomePageStarredEntities', + x: 0, + y: 4, + width: 6, + height: 5, + }, + { + component: 'HomePageRandomJoke', + x: 6, + y: 4, + width: 6, + height: 5, + }, +]; + +const homePageLayout = HomePageLayoutBlueprint.make({ + params: { + loader: async () => + function CustomHomePageLayout({ widgets }) { + return ( + +
    } pageTitleOverride="Home"> + +
    + + + {widgets.map((widget, index) => ( + + {widget.component} + + ))} + + +
    + ); + }, + }, +}); + +const homePageToolkitWidget = HomePageWidgetBlueprint.make({ + name: 'home-toolkit', + params: { + name: 'HomePageToolkit', + title: 'Toolkit', + components: () => + import('../src/homePageComponents/Toolkit').then(m => ({ + Content: m.Content, + ContextProvider: m.ContextProvider, + })), + componentProps: { + tools: [ + { + url: 'https://backstage.io', + label: 'Backstage Homepage', + icon: , + }, + ], + }, + }, +}); + +const homePageStarredEntitiesWidget = HomePageWidgetBlueprint.make({ + name: 'home-starred-entities', + params: { + name: 'HomePageStarredEntities', + title: 'Your Starred Entities', + components: () => + import('../src/homePageComponents/StarredEntities').then(m => ({ + Content: m.Content, + })), + }, +}); + +const homePageRandomJokeWidget = HomePageWidgetBlueprint.make({ + name: 'home-random-joke', + params: { + name: 'HomePageRandomJoke', + title: 'Random Joke', + description: 'Shows a random programming joke', + components: () => + import('../src/homePageComponents/RandomJoke').then(m => ({ + Content: m.Content, + Settings: m.Settings, + Actions: m.Actions, + ContextProvider: m.ContextProvider, + })), + layout: { + height: { minRows: 4 }, + width: { minColumns: 3 }, + }, + settings: { + schema: { + title: 'Random Joke settings', + type: 'object', + properties: { + defaultCategory: { + title: 'Category', + type: 'string', + enum: ['any', 'programming', 'dad'], + default: 'any', + }, + }, + }, + }, + }, +}); + +const homeDevModule = createFrontendModule({ + pluginId: 'home', + extensions: [ + homePageLayout, + homePageToolkitWidget, + homePageStarredEntitiesWidget, + homePageRandomJokeWidget, + ], +}); + +const entities = [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'example', + annotations: { + 'backstage.io/managed-by-location': 'file:/path/to/catalog-info.yaml', + }, + }, + spec: { + type: 'service', + lifecycle: 'production', + owner: 'guest', + }, + }, +]; + +const catalogApi = catalogApiMock({ entities }); + +const catalogPluginOverrides = createFrontendModule({ + pluginId: 'catalog', + extensions: [ + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: catalogApiRef, + deps: {}, + factory: () => catalogApi, + }), + }), + ], +}); + +const app = createApp({ + features: [ + catalogPlugin, + catalogPluginOverrides, + homePlugin, // Load the home plugin + homeDevModule, // Load the widgets and homepage content + ], +}); + +const root = app.createRoot(); +ReactDOM.createRoot(document.getElementById('root')!).render(root); diff --git a/plugins/home/knip-report.md b/plugins/home/knip-report.md index 2661c35327..65b13428c4 100644 --- a/plugins/home/knip-report.md +++ b/plugins/home/knip-report.md @@ -1,2 +1,14 @@ # Knip report +## Unused dependencies (1) + +| Name | Location | Severity | +| :------------------------- | :---------------- | :------- | +| @backstage/core-compat-api | package.json:63:6 | error | + +## Unused devDependencies (1) + +| Name | Location | Severity | +| :------------------- | :---------------- | :------- | +| @backstage/dev-utils | package.json:86:6 | error | + diff --git a/plugins/home/package.json b/plugins/home/package.json index 41ef166872..a7bd3a3476 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home", - "version": "0.8.14-next.1", + "version": "0.9.4", "description": "A Backstage plugin that helps you build a home page", "backstage": { "role": "frontend-plugin", @@ -70,20 +70,22 @@ "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", - "@rjsf/core": "5.23.2", - "@rjsf/material-ui": "5.23.2", - "@rjsf/utils": "5.23.2", - "@rjsf/validator-ajv8": "5.23.2", + "@rjsf/core": "5.24.13", + "@rjsf/material-ui": "5.24.13", + "@rjsf/utils": "5.24.13", + "@rjsf/validator-ajv8": "5.24.13", "lodash": "^4.17.21", "luxon": "^3.4.3", "react-grid-layout": "1.3.4", "react-resizable": "^3.0.4", "react-use": "^17.2.4", - "zod": "^3.22.4" + "zod": "^3.25.76 || ^4.0.0" }, "devDependencies": { "@backstage/cli": "workspace:^", "@backstage/dev-utils": "workspace:^", + "@backstage/frontend-defaults": "workspace:^", + "@backstage/plugin-catalog": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.0.0", @@ -93,13 +95,13 @@ "@types/react-grid-layout": "^1.3.2", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/plugins/home/report-alpha.api.md b/plugins/home/report-alpha.api.md index 58ceef751d..1014c626a2 100644 --- a/plugins/home/report-alpha.api.md +++ b/plugins/home/report-alpha.api.md @@ -10,13 +10,18 @@ import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; +import { HomePageLayoutProps } from '@backstage/plugin-home-react/alpha'; +import { HomePageWidgetBlueprintParams } from '@backstage/plugin-home-react/alpha'; +import { HomePageWidgetData } from '@backstage/plugin-home-react/alpha'; +import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; import { RouteRef } from '@backstage/frontend-plugin-api'; import { TranslationRef } from '@backstage/frontend-plugin-api'; -// @alpha (undocumented) +// @alpha const _default: OverridableFrontendPlugin< { root: RouteRef; @@ -49,64 +54,169 @@ const _default: OverridableFrontendPlugin< element: JSX.Element; }; }>; + 'home-page-widget:home/random-joke': OverridableExtensionDefinition<{ + kind: 'home-page-widget'; + name: 'random-joke'; + config: {}; + configInput: {}; + output: ExtensionDataRef; + inputs: {}; + params: HomePageWidgetBlueprintParams; + }>; + 'home-page-widget:home/starred-entities': OverridableExtensionDefinition<{ + kind: 'home-page-widget'; + name: 'starred-entities'; + config: {}; + configInput: {}; + output: ExtensionDataRef; + inputs: {}; + params: HomePageWidgetBlueprintParams; + }>; + 'home-page-widget:home/toolkit': OverridableExtensionDefinition<{ + kind: 'home-page-widget'; + name: 'toolkit'; + config: {}; + configInput: {}; + output: ExtensionDataRef; + inputs: {}; + params: HomePageWidgetBlueprintParams; + }>; + 'nav-item:home': OverridableExtensionDefinition<{ + kind: 'nav-item'; + name: undefined; + config: { + title: string | undefined; + }; + configInput: { + title?: string | undefined; + }; + output: ExtensionDataRef< + { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }, + 'core.nav-item.target', + {} + >; + inputs: {}; + params: { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }; + }>; 'page:home': OverridableExtensionDefinition<{ config: { path: string | undefined; + title: string | undefined; }; configInput: { path?: string | undefined; + title?: string | undefined; }; output: | ExtensionDataRef - | ExtensionDataRef | ExtensionDataRef< RouteRef, 'core.routing.ref', { optional: true; } + > + | ExtensionDataRef + | ExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } >; inputs: { - props: ExtensionInput< + pages: ExtensionInput< + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef | ConfigurableExtensionDataRef< - JSX_2.Element, - 'core.reactElement', + RouteRef, + 'core.routing.ref', { optional: true; } > | ConfigurableExtensionDataRef< string, - 'title', + 'core.title', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + IconElement, + 'core.icon', { optional: true; } >, + { + singleton: false; + optional: false; + internal: false; + } + >; + widgets: ExtensionInput< + ConfigurableExtensionDataRef< + HomePageWidgetData, + 'home.widget.data', + {} + >, + { + singleton: false; + optional: false; + internal: false; + } + >; + layout: ExtensionInput< + ConfigurableExtensionDataRef< + (props: HomePageLayoutProps) => JSX_2.Element, + 'home.layout.component', + {} + >, { singleton: true; optional: true; + internal: true; } >; }; kind: 'page'; name: undefined; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; - loader: () => Promise; + title?: string; + icon?: IconElement; + loader?: () => Promise; routeRef?: RouteRef; + noHeader?: boolean; }; }>; } >; export default _default; -// @alpha (undocumented) +// @alpha @deprecated (undocumented) export const homeTranslationRef: TranslationRef< 'home', { readonly 'starredEntities.noStarredEntitiesMessage': 'Click the star beside an entity name to add it to this list!'; readonly 'addWidgetDialog.title': 'Add new widget to dashboard'; + readonly 'customHomepageButtons.cancel': 'Cancel'; readonly 'customHomepageButtons.clearAll': 'Clear all'; readonly 'customHomepageButtons.edit': 'Edit'; readonly 'customHomepageButtons.restoreDefaults': 'Restore defaults'; @@ -133,12 +243,5 @@ export const homeTranslationRef: TranslationRef< } >; -// @alpha (undocumented) -export const titleExtensionDataRef: ConfigurableExtensionDataRef< - string, - 'title', - {} ->; - // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/home/report.api.md b/plugins/home/report.api.md index 7036e9d6d2..2d5f8ddecc 100644 --- a/plugins/home/report.api.md +++ b/plugins/home/report.api.md @@ -3,7 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { ApiRef } from '@backstage/core-plugin-api'; +import { ApiRef } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { CardConfig as CardConfig_2 } from '@backstage/plugin-home-react'; import { CardExtensionProps as CardExtensionProps_2 } from '@backstage/plugin-home-react'; @@ -23,6 +23,7 @@ import { ReactNode } from 'react'; import { RendererProps as RendererProps_2 } from '@backstage/plugin-home-react'; import { RouteRef } from '@backstage/core-plugin-api'; import { StorageApi } from '@backstage/core-plugin-api'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; import { Variant } from '@material-ui/core/styles/createTypography'; // @public @@ -176,6 +177,39 @@ export const homePlugin: BackstagePlugin< {} >; +// @public +export const homeTranslationRef: TranslationRef< + 'home', + { + readonly 'starredEntities.noStarredEntitiesMessage': 'Click the star beside an entity name to add it to this list!'; + readonly 'addWidgetDialog.title': 'Add new widget to dashboard'; + readonly 'customHomepageButtons.cancel': 'Cancel'; + readonly 'customHomepageButtons.clearAll': 'Clear all'; + readonly 'customHomepageButtons.edit': 'Edit'; + readonly 'customHomepageButtons.restoreDefaults': 'Restore defaults'; + readonly 'customHomepageButtons.addWidget': 'Add widget'; + readonly 'customHomepageButtons.save': 'Save'; + readonly 'customHomepage.noWidgets': "No widgets added. Start by clicking the 'Add widget' button."; + readonly 'widgetSettingsOverlay.cancelButtonTitle': 'Cancel'; + readonly 'widgetSettingsOverlay.editSettingsTooptip': 'Edit settings'; + readonly 'widgetSettingsOverlay.deleteWidgetTooltip': 'Delete widget'; + readonly 'widgetSettingsOverlay.submitButtonTitle': 'Submit'; + readonly 'starredEntityListItem.removeFavoriteEntityTitle': 'Remove entity from favorites'; + readonly 'visitList.empty.title': 'There are no visits to show yet.'; + readonly 'visitList.empty.description': 'Once you start using Backstage, your visits will appear here as a quick link to carry on where you left off.'; + readonly 'visitList.few.title': 'The more pages you visit, the more pages will appear here.'; + readonly 'quickStart.title': 'Onboarding'; + readonly 'quickStart.description': 'Get started with Backstage'; + readonly 'quickStart.learnMoreLinkTitle': 'Learn more'; + readonly 'visitedByType.action.viewMore': 'View more'; + readonly 'visitedByType.action.viewLess': 'View less'; + readonly 'featuredDocsCard.empty.title': 'No documents to show'; + readonly 'featuredDocsCard.empty.description': 'Create your own document. Check out our Getting Started Information'; + readonly 'featuredDocsCard.empty.learnMoreLinkTitle': 'DOCS'; + readonly 'featuredDocsCard.learnMoreTitle': 'LEARN MORE'; + } +>; + // @public export const isOperator: (s: string) => s is Operators; @@ -273,11 +307,9 @@ export interface VisitDisplayContextValue { } // @public -export const VisitDisplayProvider: ({ - children, - getChipColor, - getLabel, -}: VisitDisplayProviderProps) => JSX_2.Element; +export const VisitDisplayProvider: ( + input: VisitDisplayProviderProps, +) => JSX_2.Element; // @public export interface VisitDisplayProviderProps { @@ -309,14 +341,10 @@ export type VisitInput = { }; // @public -export const VisitListener: ({ - children, - toEntityRef, - visitName, -}: { +export const VisitListener: (input: { children?: ReactNode; - toEntityRef?: ({ pathname }: { pathname: string }) => string | undefined; - visitName?: ({ pathname }: { pathname: string }) => string; + toEntityRef?: (input: { pathname: string }) => string | undefined; + visitName?: (input: { pathname: string }) => string; }) => JSX.Element; // @public @@ -344,7 +372,7 @@ export type VisitsApiQueryParams = { }>; }; -// @public (undocumented) +// @public export const visitsApiRef: ApiRef; // @public @@ -389,10 +417,7 @@ export type VisitsWebStorageApiOptions = { }; // @public -export const WelcomeTitle: ({ - language, - variant, -}: WelcomeTitleLanguageProps) => JSX_2.Element; +export const WelcomeTitle: (input: WelcomeTitleLanguageProps) => JSX_2.Element; // @public (undocumented) export type WelcomeTitleLanguageProps = { diff --git a/plugins/home/src/alpha.test.ts b/plugins/home/src/alpha.test.ts new file mode 100644 index 0000000000..caa7d45cf7 --- /dev/null +++ b/plugins/home/src/alpha.test.ts @@ -0,0 +1,111 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { mockApis } from '@backstage/test-utils'; +import homePlugin from './alpha'; +import { VisitsStorageApi, VisitsWebStorageApi } from './api'; + +// Mock localStorage for testing +const mockLocalStorage = new Map(); +Object.defineProperty(window, 'localStorage', { + value: { + getItem: (key: string) => mockLocalStorage.get(key) || null, + setItem: (key: string, value: string) => mockLocalStorage.set(key, value), + removeItem: (key: string) => mockLocalStorage.delete(key), + clear: () => mockLocalStorage.clear(), + }, +}); + +describe('Home Plugin Alpha', () => { + beforeEach(() => { + mockLocalStorage.clear(); + }); + + describe('Core Extensions (Always Enabled)', () => { + it('should export core home page extension', () => { + expect(homePlugin.getExtension('page:home')).toBeDefined(); + }); + + it('should export navigation item extension', () => { + expect(homePlugin.getExtension('nav-item:home')).toBeDefined(); + }); + }); + + describe('Optional Extensions (Disabled by Default)', () => { + it('should export visit tracking API extension', () => { + const visitTrackingExtension = homePlugin.getExtension('api:home/visits'); + expect(visitTrackingExtension).toBeDefined(); + }); + + it('should export visit listener extension', () => { + const visitListenerExtension = homePlugin.getExtension( + 'app-root-element:home/visit-listener', + ); + expect(visitListenerExtension).toBeDefined(); + }); + }); + + describe('API Implementation Classes', () => { + it('should create VisitsStorageApi with custom storage', () => { + const mockStorageApi = mockApis.storage(); + const mockIdentityApi = mockApis.identity({ + userEntityRef: 'user:default/testuser', + }); + + const visitsApi = VisitsStorageApi.create({ + storageApi: mockStorageApi, + identityApi: mockIdentityApi, + }); + + expect(visitsApi).toBeInstanceOf(VisitsStorageApi); + }); + + it('should create localStorage fallback API when no custom storage is available', () => { + const mockIdentityApi = mockApis.identity({ + userEntityRef: 'user:default/testuser', + }); + const mockErrorApi = { post: jest.fn(), error$: jest.fn() }; + + // Test that VisitsWebStorageApi can be created without custom storage + const visitsApi = VisitsWebStorageApi.create({ + identityApi: mockIdentityApi, + errorApi: mockErrorApi, + }); + + // VisitsWebStorageApi.create returns a VisitsStorageApi instance + expect(visitsApi).toBeInstanceOf(VisitsStorageApi); + }); + }); + + describe('Plugin Structure', () => { + it('should have correct plugin metadata', () => { + expect(homePlugin.id).toBe('home'); + expect(homePlugin.routes.root).toBeDefined(); + }); + + it('should include all extensions in the correct order', () => { + // Core extensions (always enabled) + expect(homePlugin.getExtension('page:home')).toBeDefined(); + expect(homePlugin.getExtension('nav-item:home')).toBeDefined(); + + // Optional extensions (disabled by default) + expect(homePlugin.getExtension('api:home/visits')).toBeDefined(); + expect( + homePlugin.getExtension('app-root-element:home/visit-listener'), + ).toBeDefined(); + }); + }); +}); diff --git a/plugins/home/src/alpha.tsx b/plugins/home/src/alpha.tsx index a92316c7f0..bdce683cee 100644 --- a/plugins/home/src/alpha.tsx +++ b/plugins/home/src/alpha.tsx @@ -14,63 +14,88 @@ * limitations under the License. */ +/** + * The home plugin for Backstage's new frontend system. + * + * @remarks + * This package provides the new frontend system implementation of the home plugin, + * which offers customizable home pages with widget support and optional visit tracking. + * + * @packageDocumentation + */ + +import { lazy as reactLazy } from 'react'; import { - coreExtensionData, - createExtensionDataRef, createExtensionInput, PageBlueprint, + NavItemBlueprint, createFrontendPlugin, createRouteRef, AppRootElementBlueprint, identityApiRef, storageApiRef, + errorApiRef, ApiBlueprint, + ExtensionBoundary, } from '@backstage/frontend-plugin-api'; -import { compatWrapper } from '@backstage/core-compat-api'; import { VisitListener } from './components/'; -import { visitsApiRef, VisitsStorageApi } from './api'; +import { visitsApiRef, VisitsStorageApi, VisitsWebStorageApi } from './api'; +import HomeIcon from '@material-ui/icons/Home'; +import { + homePageWidgetDataRef, + homePageLayoutComponentDataRef, + HomePageLayoutBlueprint, + HomePageWidgetBlueprint, + type HomePageLayoutProps, +} from '@backstage/plugin-home-react/alpha'; const rootRouteRef = createRouteRef(); -/** - * @alpha - */ -export const titleExtensionDataRef = createExtensionDataRef().with({ - id: 'title', -}); - const homePage = PageBlueprint.makeWithOverrides({ inputs: { - props: createExtensionInput( - [ - coreExtensionData.reactElement.optional(), - titleExtensionDataRef.optional(), - ], - { - singleton: true, - optional: true, - }, - ), + widgets: createExtensionInput([homePageWidgetDataRef]), + layout: createExtensionInput([HomePageLayoutBlueprint.dataRefs.component], { + singleton: true, + optional: true, + internal: true, + }), }, - factory: (originalFactory, { inputs }) => { + factory(originalFactory, { node, inputs }) { return originalFactory({ path: '/home', + noHeader: true, routeRef: rootRouteRef, - loader: () => - import('./components/').then(m => - compatWrapper( - , - ), - ), + loader: async () => { + const LazyDefaultLayout = reactLazy(() => + import('./alpha/DefaultHomePageLayout').then(m => ({ + default: m.DefaultHomePageLayout, + })), + ); + + const DefaultLayoutComponent = (props: HomePageLayoutProps) => ( + + + + ); + + const Layout = + inputs.layout?.get(homePageLayoutComponentDataRef) ?? + DefaultLayoutComponent; + + const widgets = inputs.widgets.map(widget => ({ + ...widget.get(homePageWidgetDataRef), + node: widget.node, + })); + + return ; + }, }); }, }); const visitListenerAppRootElement = AppRootElementBlueprint.make({ name: 'visit-listener', + disabled: true, params: { element: , }, @@ -78,28 +103,132 @@ const visitListenerAppRootElement = AppRootElementBlueprint.make({ const visitsApi = ApiBlueprint.make({ name: 'visits', + disabled: true, params: defineParams => defineParams({ api: visitsApiRef, deps: { storageApi: storageApiRef, identityApi: identityApiRef, + errorApi: errorApiRef, + }, + factory: ({ storageApi, identityApi, errorApi }) => { + // Smart fallback: use custom storage API if available, otherwise localStorage + if (storageApi) { + return VisitsStorageApi.create({ storageApi, identityApi }); + } + return VisitsWebStorageApi.create({ identityApi, errorApi }); }, - factory: ({ storageApi, identityApi }) => - VisitsStorageApi.create({ storageApi, identityApi }), }), }); +const homeNavItem = NavItemBlueprint.make({ + params: { + title: 'Home', + routeRef: rootRouteRef, + icon: HomeIcon, + }, +}); + +const homePageToolkitWidget = HomePageWidgetBlueprint.make({ + name: 'toolkit', + params: { + name: 'HomePageToolkit', + title: 'Toolkit', + components: () => + import('./homePageComponents/Toolkit').then(m => ({ + Content: m.Content, + ContextProvider: m.ContextProvider, + })), + componentProps: { + tools: [ + { + url: 'https://backstage.io', + label: 'Backstage Docs', + icon: , + }, + ], + }, + }, +}); + +const homePageStarredEntitiesWidget = HomePageWidgetBlueprint.make({ + name: 'starred-entities', + params: { + name: 'HomePageStarredEntities', + title: 'Your Starred Entities', + components: () => + import('./homePageComponents/StarredEntities').then(m => ({ + Content: m.Content, + })), + }, +}); + +const homePageRandomJokeWidget = HomePageWidgetBlueprint.make({ + name: 'random-joke', + params: { + name: 'HomePageRandomJoke', + title: 'Random Joke', + description: 'Shows a random programming joke', + components: () => + import('./homePageComponents/RandomJoke').then(m => ({ + Content: m.Content, + Settings: m.Settings, + Actions: m.Actions, + ContextProvider: m.ContextProvider, + })), + layout: { + height: { minRows: 4 }, + width: { minColumns: 3 }, + }, + settings: { + schema: { + title: 'Random Joke settings', + type: 'object', + properties: { + defaultCategory: { + title: 'Category', + type: 'string', + enum: ['any', 'programming', 'dad'], + default: 'any', + }, + }, + }, + }, + }, +}); + /** + * Home plugin for the new frontend system. + * + * Provides core homepage functionality with optional visit tracking extensions. + * Visit tracking extensions are disabled by default and can be enabled via app-config.yaml. + * * @alpha */ export default createFrontendPlugin({ pluginId: 'home', + title: 'Home', + icon: , info: { packageJson: () => import('../package.json') }, - extensions: [homePage, visitsApi, visitListenerAppRootElement], + extensions: [ + homePage, + homeNavItem, + visitsApi, + visitListenerAppRootElement, + homePageToolkitWidget, + homePageStarredEntitiesWidget, + homePageRandomJokeWidget, + ], routes: { root: rootRouteRef, }, }); -export { homeTranslationRef } from './translation'; +import { homeTranslationRef as _homeTranslationRef } from './translation'; + +/** + * @alpha + * @deprecated Import from `@backstage/plugin-home` instead. + */ +export const homeTranslationRef = _homeTranslationRef; diff --git a/plugins/home/src/alpha/DefaultHomePageLayout.tsx b/plugins/home/src/alpha/DefaultHomePageLayout.tsx new file mode 100644 index 0000000000..f8d63a635f --- /dev/null +++ b/plugins/home/src/alpha/DefaultHomePageLayout.tsx @@ -0,0 +1,39 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Fragment } from 'react'; +import { Content } from '@backstage/core-components'; +import type { HomePageLayoutProps } from '@backstage/plugin-home-react/alpha'; +import { CustomHomepageGrid } from '../components'; + +/** + * The default home page layout component. + * + * Renders widgets inside a `` wrapped in ``. + * This is used when no custom layout extension is installed. + */ +export function DefaultHomePageLayout(props: HomePageLayoutProps) { + const { widgets } = props; + return ( + + + {widgets.map((widget, index) => ( + {widget.component} + ))} + + + ); +} diff --git a/plugins/home/src/api/VisitsApi.ts b/plugins/home/src/api/VisitsApi.ts index 5cbd65fab3..3345128e08 100644 --- a/plugins/home/src/api/VisitsApi.ts +++ b/plugins/home/src/api/VisitsApi.ts @@ -146,7 +146,12 @@ export interface VisitsApi { ): Promise> | Record; } -/** @public */ +/** + * API reference for the visits tracking service. + * Provides functionality to track and retrieve user page visit history. + * + * @public + */ export const visitsApiRef = createApiRef({ id: 'homepage.visits', }); diff --git a/plugins/home/src/components/CustomHomepage/CustomHomepageButtons.tsx b/plugins/home/src/components/CustomHomepage/CustomHomepageButtons.tsx index 0496e923de..b7e397c995 100644 --- a/plugins/home/src/components/CustomHomepage/CustomHomepageButtons.tsx +++ b/plugins/home/src/components/CustomHomepage/CustomHomepageButtons.tsx @@ -45,6 +45,7 @@ interface CustomHomepageButtonsProps { changeEditMode: (mode: boolean) => void; defaultConfigAvailable: boolean; restoreDefault: () => void; + cancel: () => void; } export const CustomHomepageButtons = (props: CustomHomepageButtonsProps) => { const { @@ -55,6 +56,7 @@ export const CustomHomepageButtons = (props: CustomHomepageButtonsProps) => { changeEditMode, defaultConfigAvailable, restoreDefault, + cancel, } = props; const styles = useStyles(); const { t } = useTranslationRef(homeTranslationRef); @@ -73,6 +75,9 @@ export const CustomHomepageButtons = (props: CustomHomepageButtonsProps) => { ) : ( <> + {defaultConfigAvailable && (
    { + const { t } = useTranslationRef(kubernetesReactTranslationRef); const namespace = ingress.metadata?.namespace; return ( {namespace && ( - + )} diff --git a/plugins/kubernetes-react/src/components/KubernetesDialog/KubernetesDialog.tsx b/plugins/kubernetes-react/src/components/KubernetesDialog/KubernetesDialog.tsx index d1a2a8a825..182265d23e 100644 --- a/plugins/kubernetes-react/src/components/KubernetesDialog/KubernetesDialog.tsx +++ b/plugins/kubernetes-react/src/components/KubernetesDialog/KubernetesDialog.tsx @@ -22,6 +22,8 @@ import IconButton from '@material-ui/core/IconButton'; import { Theme, createStyles, makeStyles } from '@material-ui/core/styles'; import CloseIcon from '@material-ui/icons/Close'; import { ReactNode, useState } from 'react'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../translation'; const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -59,6 +61,7 @@ export const KubernetesDialog = ({ title, }: KubernetesDialogProps) => { const classes = useStyles(); + const { t } = useTranslationRef(kubernetesReactTranslationRef); const [open, setOpen] = useState(false); const openDialog = () => { @@ -80,7 +83,7 @@ export const KubernetesDialog = ({ {title} diff --git a/plugins/kubernetes-react/src/components/KubernetesDrawer/KubernetesDrawer.tsx b/plugins/kubernetes-react/src/components/KubernetesDrawer/KubernetesDrawer.tsx index 323ce650d9..aa0609ca06 100644 --- a/plugins/kubernetes-react/src/components/KubernetesDrawer/KubernetesDrawer.tsx +++ b/plugins/kubernetes-react/src/components/KubernetesDrawer/KubernetesDrawer.tsx @@ -31,6 +31,8 @@ import { } from '@material-ui/core/styles'; import CloseIcon from '@material-ui/icons/Close'; import { ManifestYaml } from './ManifestYaml'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../translation'; const useDrawerContentStyles = makeStyles((_theme: Theme) => createStyles({ @@ -72,6 +74,7 @@ const KubernetesDrawerContent = ({ close, }: KubernetesDrawerContentProps) => { const classes = useDrawerContentStyles(); + const { t } = useTranslationRef(kubernetesReactTranslationRef); const [isYaml, setIsYaml] = useState(false); return ( @@ -86,7 +89,7 @@ const KubernetesDrawerContent = ({ close()} color="inherit" > @@ -104,10 +107,10 @@ const KubernetesDrawerContent = ({ onChange={event => { setIsYaml(event.target.checked); }} - name="YAML" + name={t('kubernetesDrawer.yaml')} /> } - label="YAML" + label={t('kubernetesDrawer.yaml')} /> diff --git a/plugins/kubernetes-react/src/components/KubernetesDrawer/KubernetesStructuredMetadataTableDrawer.tsx b/plugins/kubernetes-react/src/components/KubernetesDrawer/KubernetesStructuredMetadataTableDrawer.tsx index fb52063177..fb9977fb4a 100644 --- a/plugins/kubernetes-react/src/components/KubernetesDrawer/KubernetesStructuredMetadataTableDrawer.tsx +++ b/plugins/kubernetes-react/src/components/KubernetesDrawer/KubernetesStructuredMetadataTableDrawer.tsx @@ -38,6 +38,8 @@ import { ManifestYaml } from './ManifestYaml'; import { useApi } from '@backstage/core-plugin-api'; import { kubernetesClusterLinkFormatterApiRef } from '../../api'; import useAsync from 'react-use/esm/useAsync'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../translation'; const useDrawerStyles = makeStyles((theme: Theme) => createStyles({ @@ -102,20 +104,24 @@ export type LinkErrorPanelProps = { export const LinkErrorPanel = ({ cluster, errorMessage, -}: LinkErrorPanelProps) => ( - - {errorMessage && ( - Errors: {errorMessage} - )} - -); +}: LinkErrorPanelProps) => { + const { t } = useTranslationRef(kubernetesReactTranslationRef); + return ( + + {errorMessage && ( + + {t('linkErrorPanel.errorsLabel')} {errorMessage} + + )} + + ); +}; /** * @@ -152,6 +158,7 @@ const KubernetesStructuredMetadataTableDrawerContent = < renderObject, kind, }: KubernetesStructuredMetadataTableDrawerContentProps) => { + const { t } = useTranslationRef(kubernetesReactTranslationRef); const [isYaml, setIsYaml] = useState(false); const formatter = useApi(kubernetesClusterLinkFormatterApiRef); @@ -175,13 +182,13 @@ const KubernetesStructuredMetadataTableDrawerContent = < - {object.metadata?.name ?? 'unknown name'} + {object.metadata?.name ?? t('kubernetesDrawer.unknownName')} toggleDrawer(e, false)} color="inherit" > @@ -201,10 +208,10 @@ const KubernetesStructuredMetadataTableDrawerContent = < onChange={event => { setIsYaml(event.target.checked); }} - name="YAML" + name={t('kubernetesDrawer.yaml')} /> } - label="YAML" + label={t('kubernetesDrawer.yaml')} /> diff --git a/plugins/kubernetes-react/src/components/KubernetesDrawer/ManifestYaml.tsx b/plugins/kubernetes-react/src/components/KubernetesDrawer/ManifestYaml.tsx index 3ccd32fcfb..604efc3055 100644 --- a/plugins/kubernetes-react/src/components/KubernetesDrawer/ManifestYaml.tsx +++ b/plugins/kubernetes-react/src/components/KubernetesDrawer/ManifestYaml.tsx @@ -18,6 +18,8 @@ import FormControlLabel from '@material-ui/core/FormControlLabel'; import Switch from '@material-ui/core/Switch'; import jsyaml from 'js-yaml'; import { useState } from 'react'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../translation'; /** * Props of ManifestYaml @@ -34,6 +36,7 @@ export interface ManifestYamlProps { * @public */ export const ManifestYaml = ({ object }: ManifestYamlProps) => { + const { t } = useTranslationRef(kubernetesReactTranslationRef); // Toggle whether the Kubernetes resource managed fields should be shown in // the YAML display. This toggle is only available when the YAML is being // shown because managed fields are never visible in the structured display. @@ -47,10 +50,10 @@ export const ManifestYaml = ({ object }: ManifestYamlProps) => { onChange={event => { setManagedFields(event.target.checked); }} - name="Managed Fields" + name={t('kubernetesDrawer.managedFields')} /> } - label="Managed Fields" + label={t('kubernetesDrawer.managedFields')} /> { + it('should render 1 persistent volume with summary', async () => { + const wrapper = kubernetesProviders( + onePersistentVolumesFixture, + new Set(), + ); + + await renderInTestApp(wrapper()); + + expect(screen.getByText('PersistentVolumes')).toBeInTheDocument(); + expect(screen.getByText('1 volumes')).toBeInTheDocument(); + expect(screen.getByText('0 bound')).toBeInTheDocument(); + }); + + it('should render 9 persistent volumes with summary', async () => { + const wrapper = kubernetesProviders( + twoPersistentVolumesFixture, + new Set(), + ); + + await renderInTestApp(wrapper()); + + expect(screen.getByText('PersistentVolumes')).toBeInTheDocument(); + expect(screen.getByText('9 volumes')).toBeInTheDocument(); + expect(screen.getByText('5 bound')).toBeInTheDocument(); + }); +}); diff --git a/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesAccordions.tsx b/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesAccordions.tsx new file mode 100644 index 0000000000..2d486bc383 --- /dev/null +++ b/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesAccordions.tsx @@ -0,0 +1,137 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ReactNode, useContext } from 'react'; +import Accordion from '@material-ui/core/Accordion'; +import AccordionDetails from '@material-ui/core/AccordionDetails'; +import AccordionSummary from '@material-ui/core/AccordionSummary'; +import Grid from '@material-ui/core/Grid'; +import Typography from '@material-ui/core/Typography'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import type { V1PersistentVolume } from '@kubernetes/client-node'; +import { PersistentVolumesTable } from './PersistentVolumesTable'; +import { GroupedResponsesContext } from '../../hooks'; +import { StatusOK, StatusError } from '@backstage/core-components'; + +type PersistentVolumesAccordionsProps = { + children?: ReactNode; +}; + +type PersistentVolumesAccordionProps = { + persistentVolumes: V1PersistentVolume[]; + children?: ReactNode; +}; + +type PersistentVolumesSummaryProps = { + numberOfPersistentVolumes: number; + numberOfBoundVolumes: number; + numberOfFailedVolumes: number; + children?: ReactNode; +}; + +const PersistentVolumesSummary = ({ + numberOfPersistentVolumes, + numberOfBoundVolumes, + numberOfFailedVolumes, +}: PersistentVolumesSummaryProps) => { + return ( + + + + PersistentVolumes + + + + + {numberOfPersistentVolumes} volumes + + + {numberOfBoundVolumes} bound + + {numberOfFailedVolumes > 0 && ( + + + {numberOfFailedVolumes} volume + {numberOfFailedVolumes > 1 ? 's' : ''} failed + + + )} + + + ); +}; + +const PersistentVolumesAccordion = ({ + persistentVolumes, +}: PersistentVolumesAccordionProps) => { + const boundVolumes = persistentVolumes.filter( + pv => pv.status?.phase === 'Bound', + ); + const failedVolumes = persistentVolumes.filter( + pv => pv.status?.phase === 'Failed', + ); + + return ( + + }> + + + + + + + ); +}; + +export const PersistentVolumesAccordions = + ({}: PersistentVolumesAccordionsProps) => { + const groupedResponses = useContext(GroupedResponsesContext); + + return ( + + + + + + + + ); + }; diff --git a/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesDrawer.test.tsx b/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesDrawer.test.tsx new file mode 100644 index 0000000000..85911016ac --- /dev/null +++ b/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesDrawer.test.tsx @@ -0,0 +1,38 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import * as onePersistentVolumesFixture from '../../__fixtures__/1-persistentvolumes.json'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { PersistentVolumesDrawer } from './PersistentVolumesDrawer'; +import { kubernetesClusterLinkFormatterApiRef } from '../../api'; + +describe('PersistentVolumesDrawer', () => { + it('should render persistent volume drawer', async () => { + const { getByText, getAllByText } = await renderInTestApp( + + + , + ); + + expect(getAllByText('pv-hostpath')).toHaveLength(3); + expect(getAllByText('PersistentVolume')).toHaveLength(3); + expect(getByText('YAML')).toBeInTheDocument(); + }); +}); diff --git a/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesDrawer.tsx b/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesDrawer.tsx new file mode 100644 index 0000000000..7da0d16842 --- /dev/null +++ b/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesDrawer.tsx @@ -0,0 +1,64 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import type { V1PersistentVolume } from '@kubernetes/client-node'; +import { KubernetesStructuredMetadataTableDrawer } from '../KubernetesDrawer'; +import Typography from '@material-ui/core/Typography'; +import Grid from '@material-ui/core/Grid'; +import Chip from '@material-ui/core/Chip'; + +export const PersistentVolumesDrawer = ({ + persistentVolume, + expanded, +}: { + persistentVolume: V1PersistentVolume; + expanded?: boolean; +}) => { + const namespace = persistentVolume.metadata?.namespace; + return ( + { + return persistentVolumeObject || {}; + }} + > + + + + {persistentVolume.metadata?.name ?? 'unknown object'} + + + + + PersistentVolume + + + {namespace && ( + + + + )} + + + ); +}; diff --git a/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesTable.test.tsx b/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesTable.test.tsx new file mode 100644 index 0000000000..a1684748cb --- /dev/null +++ b/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesTable.test.tsx @@ -0,0 +1,81 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { screen } from '@testing-library/react'; +import * as onePersistentVolumesFixture from '../../__fixtures__/1-persistentvolumes.json'; +import * as twoPersistentVolumesFixture from '../../__fixtures__/2-persistentvolumes.json'; +import { renderInTestApp } from '@backstage/test-utils'; +import { PersistentVolumesTable } from './PersistentVolumesTable'; + +describe('PersistentVolumesTable', () => { + it('should render persistent volume table with columns', async () => { + await renderInTestApp( + , + ); + + expect(screen.getByText('name')).toBeInTheDocument(); + expect(screen.getByText('phase')).toBeInTheDocument(); + expect(screen.getByText('status')).toBeInTheDocument(); + expect(screen.getByText('capacity')).toBeInTheDocument(); + expect(screen.getByText('type')).toBeInTheDocument(); + expect(screen.getByText('claim')).toBeInTheDocument(); + + expect(screen.getByText('pv-hostpath')).toBeInTheDocument(); + expect(screen.getAllByText('Available').length).toBeGreaterThanOrEqual(1); + expect(screen.getByText('10Gi')).toBeInTheDocument(); + }); + + it('should render multiple persistent volumes with cloud provider types', async () => { + await renderInTestApp( + , + ); + + expect(screen.getByText('name')).toBeInTheDocument(); + expect(screen.getByText('phase')).toBeInTheDocument(); + expect(screen.getByText('status')).toBeInTheDocument(); + expect(screen.getByText('capacity')).toBeInTheDocument(); + expect(screen.getByText('type')).toBeInTheDocument(); + expect(screen.getByText('claim')).toBeInTheDocument(); + + expect(screen.getByText('pv-aws-ebs')).toBeInTheDocument(); + expect(screen.getByText('pv-aws-efs')).toBeInTheDocument(); + expect(screen.getByText('pv-aws-s3')).toBeInTheDocument(); + expect(screen.getByText('pv-gcp-pd')).toBeInTheDocument(); + expect(screen.getByText('pv-gcp-filestore')).toBeInTheDocument(); + + expect(screen.getByText('AWS EBS Volume')).toBeInTheDocument(); + expect(screen.getByText('AWS EFS')).toBeInTheDocument(); + expect(screen.getByText('S3 Bucket')).toBeInTheDocument(); + expect(screen.getByText('GCP Persistent Disk')).toBeInTheDocument(); + expect(screen.getByText('GCP Filestore')).toBeInTheDocument(); + + expect(screen.getAllByText('Bound').length).toBeGreaterThanOrEqual(3); + expect(screen.getAllByText('Available').length).toBeGreaterThanOrEqual(2); + + expect(screen.getByText('100Gi')).toBeInTheDocument(); + expect(screen.getByText('5Ti')).toBeInTheDocument(); + expect(screen.getByText('10Ti')).toBeInTheDocument(); + expect(screen.getByText('200Gi')).toBeInTheDocument(); + }); +}); diff --git a/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesTable.tsx b/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesTable.tsx new file mode 100644 index 0000000000..6c9ed7c339 --- /dev/null +++ b/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesTable.tsx @@ -0,0 +1,152 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ReactNode } from 'react'; +import { PersistentVolumesDrawer } from './PersistentVolumesDrawer'; +import { Table, TableColumn } from '@backstage/core-components'; +import type { V1PersistentVolume } from '@kubernetes/client-node'; +import { + StatusError, + StatusOK, + StatusPending, +} from '@backstage/core-components'; +import { getPersistentVolumeType } from '../../utils/persistentVolumes'; + +export type PersistentVolumesTableProps = { + persistentVolumes: V1PersistentVolume[]; + children?: ReactNode; +}; + +const PersistentVolumeDrawerTrigger = ({ + persistentVolume, +}: { + persistentVolume: V1PersistentVolume; +}) => { + return ; +}; + +const renderPhaseStatus = (persistentVolume: V1PersistentVolume) => { + const phase = persistentVolume.status?.phase; + + if (phase === 'Bound') { + return Bound; + } + if (phase === 'Available') { + return Available; + } + if (phase === 'Released') { + return Released; + } + if (phase === 'Pending') { + return Pending; + } + if (phase === 'Failed') { + return Failed; + } + return <>{phase ?? 'Unknown'}; +}; + +export const PersistentVolumesTable = ({ + persistentVolumes, +}: PersistentVolumesTableProps) => { + const defaultColumns: TableColumn[] = [ + { + title: 'ID', + field: 'metadata.uid', + hidden: true, + }, + { + title: 'name', + highlight: true, + render: (persistentVolume: V1PersistentVolume) => { + return ( + + ); + }, + cellStyle: { + width: '20%', + }, + }, + { + title: 'phase', + render: renderPhaseStatus, + cellStyle: { + width: '9%', + }, + }, + { + title: 'status', + render: (persistentVolume: V1PersistentVolume) => + persistentVolume.status?.phase ?? 'Unknown', + cellStyle: { + width: '9%', + }, + }, + { + title: 'capacity', + render: (persistentVolume: V1PersistentVolume) => + persistentVolume.spec?.capacity?.storage ?? 'N/A', + cellStyle: { + width: '10%', + }, + }, + { + title: 'type', + render: (persistentVolume: V1PersistentVolume) => { + const driver = persistentVolume.spec?.csi?.driver; + return getPersistentVolumeType(driver) ?? 'N/A'; + }, + cellStyle: { + width: '15%', + }, + }, + { + title: 'claim', + render: (persistentVolume: V1PersistentVolume) => { + const claim = persistentVolume.spec?.claimRef; + if (claim?.namespace && claim?.name) { + return `${claim.namespace}/${claim.name}`; + } + return 'N/A'; + }, + cellStyle: { + width: '37%', + }, + }, + ]; + + const columns: TableColumn[] = [...defaultColumns]; + + const tableStyle = { + minWidth: '0', + width: '100%', + }; + + return ( +
    +
    ({ + ...pv, + id: pv?.metadata?.uid, + })) as any as V1PersistentVolume[] + } + columns={columns} + /> + + ); +}; diff --git a/packages/cli/src/modules/build/lib/urls.ts b/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/index.ts similarity index 78% rename from packages/cli/src/modules/build/lib/urls.ts rename to plugins/kubernetes-react/src/components/PersistentVolumesAccordions/index.ts index 848cea25d9..29123cc6fc 100644 --- a/packages/cli/src/modules/build/lib/urls.ts +++ b/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/index.ts @@ -13,13 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -export function isValidUrl(url: string): boolean { - try { - // eslint-disable-next-line no-new - new URL(url); - return true; - } catch { - return false; - } -} +export * from './PersistentVolumesAccordions'; +export * from './PersistentVolumesTable'; +export * from './PersistentVolumesDrawer'; diff --git a/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsAccordions.test.tsx b/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsAccordions.test.tsx new file mode 100644 index 0000000000..2497b2fa04 --- /dev/null +++ b/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsAccordions.test.tsx @@ -0,0 +1,51 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { screen } from '@testing-library/react'; +import { PersistentVolumeClaimsAccordions } from './PersistentVolumeClaimsAccordions'; +import * as onePersistentVolumeClaimsFixture from '../../__fixtures__/1-persistentvolumeclaims.json'; +import * as twoPersistentVolumeClaimsFixture from '../../__fixtures__/2-persistentvolumeclaims.json'; +import { renderInTestApp } from '@backstage/test-utils'; +import { kubernetesProviders } from '../../hooks/test-utils'; + +describe('PersistentVolumeClaimsAccordions', () => { + it('should render 1 persistent volume claim with summary', async () => { + const wrapper = kubernetesProviders( + onePersistentVolumeClaimsFixture, + new Set(), + ); + + await renderInTestApp(wrapper()); + + expect(screen.getByText('PersistentVolumeClaims')).toBeInTheDocument(); + expect(screen.getByText('1 claims')).toBeInTheDocument(); + expect(screen.getByText('1 bound')).toBeInTheDocument(); + }); + + it('should render multiple persistent volume claims with summary', async () => { + const wrapper = kubernetesProviders( + twoPersistentVolumeClaimsFixture, + new Set(), + ); + + await renderInTestApp(wrapper()); + + expect(screen.getByText('PersistentVolumeClaims')).toBeInTheDocument(); + expect(screen.getByText('5 claims')).toBeInTheDocument(); + expect(screen.getByText('3 bound')).toBeInTheDocument(); + expect(screen.getByText('1 claim lost')).toBeInTheDocument(); + }); +}); diff --git a/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsAccordions.tsx b/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsAccordions.tsx new file mode 100644 index 0000000000..c16b1399f1 --- /dev/null +++ b/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsAccordions.tsx @@ -0,0 +1,139 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ReactNode, useContext } from 'react'; +import Accordion from '@material-ui/core/Accordion'; +import AccordionDetails from '@material-ui/core/AccordionDetails'; +import AccordionSummary from '@material-ui/core/AccordionSummary'; +import Grid from '@material-ui/core/Grid'; +import Typography from '@material-ui/core/Typography'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import type { V1PersistentVolumeClaim } from '@kubernetes/client-node'; +import { PersistentVolumeClaimsTable } from './PersistentVolumeClaimsTable'; +import { GroupedResponsesContext } from '../../hooks'; +import { StatusOK, StatusError } from '@backstage/core-components'; + +type PersistentVolumeClaimsAccordionsProps = { + children?: ReactNode; +}; + +type PersistentVolumeClaimsAccordionProps = { + persistentVolumeClaims: V1PersistentVolumeClaim[]; + children?: ReactNode; +}; + +type PersistentVolumeClaimsSummaryProps = { + numberOfPersistentVolumeClaims: number; + numberOfBoundClaims: number; + numberOfLostClaims: number; + children?: ReactNode; +}; + +const PersistentVolumeClaimsSummary = ({ + numberOfPersistentVolumeClaims, + numberOfBoundClaims, + numberOfLostClaims, +}: PersistentVolumeClaimsSummaryProps) => { + return ( + + + + PersistentVolumeClaims + + + + + {numberOfPersistentVolumeClaims} claims + + + {numberOfBoundClaims} bound + + {numberOfLostClaims > 0 && ( + + + {numberOfLostClaims} claim + {numberOfLostClaims > 1 ? 's' : ''} lost + + + )} + + + ); +}; + +const PersistentVolumeClaimsAccordion = ({ + persistentVolumeClaims, +}: PersistentVolumeClaimsAccordionProps) => { + const boundClaims = persistentVolumeClaims.filter( + pvc => pvc.status?.phase === 'Bound', + ); + const lostClaims = persistentVolumeClaims.filter( + pvc => pvc.status?.phase === 'Lost', + ); + + return ( + + }> + + + + + + + ); +}; + +export const PersistentVolumeClaimsAccordions = + ({}: PersistentVolumeClaimsAccordionsProps) => { + const groupedResponses = useContext(GroupedResponsesContext); + + return ( + + + + + + + + ); + }; diff --git a/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsDrawer.test.tsx b/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsDrawer.test.tsx new file mode 100644 index 0000000000..1340a176b5 --- /dev/null +++ b/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsDrawer.test.tsx @@ -0,0 +1,39 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import * as onePersistentVolumeClaimsFixture from '../../__fixtures__/1-persistentvolumeclaims.json'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { PersistentVolumeClaimsDrawer } from './PersistentVolumeClaimsDrawer'; +import { kubernetesClusterLinkFormatterApiRef } from '../../api'; + +describe('PersistentVolumeClaimsDrawer', () => { + it('should render persistent volume claim drawer', async () => { + const { getByText, getAllByText } = await renderInTestApp( + + + , + ); + + expect(getAllByText('pvc-web-storage')).toHaveLength(3); + expect(getAllByText('PersistentVolumeClaim')).toHaveLength(3); + expect(getByText('YAML')).toBeInTheDocument(); + expect(getByText('namespace: default')).toBeInTheDocument(); + }); +}); diff --git a/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsDrawer.tsx b/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsDrawer.tsx new file mode 100644 index 0000000000..d0af9caf48 --- /dev/null +++ b/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsDrawer.tsx @@ -0,0 +1,64 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import type { V1PersistentVolumeClaim } from '@kubernetes/client-node'; +import { KubernetesStructuredMetadataTableDrawer } from '../KubernetesDrawer'; +import Typography from '@material-ui/core/Typography'; +import Grid from '@material-ui/core/Grid'; +import Chip from '@material-ui/core/Chip'; + +export const PersistentVolumeClaimsDrawer = ({ + persistentVolumeClaim, + expanded, +}: { + persistentVolumeClaim: V1PersistentVolumeClaim; + expanded?: boolean; +}) => { + const namespace = persistentVolumeClaim.metadata?.namespace; + return ( + { + return persistentVolumeClaimObject || {}; + }} + > + + + + {persistentVolumeClaim.metadata?.name ?? 'unknown object'} + + + + + PersistentVolumeClaim + + + {namespace && ( + + + + )} + + + ); +}; diff --git a/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsTable.test.tsx b/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsTable.test.tsx new file mode 100644 index 0000000000..06230a85b7 --- /dev/null +++ b/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsTable.test.tsx @@ -0,0 +1,68 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { screen } from '@testing-library/react'; +import * as onePersistentVolumeClaimsFixture from '../../__fixtures__/1-persistentvolumeclaims.json'; +import * as twoPersistentVolumeClaimsFixture from '../../__fixtures__/2-persistentvolumeclaims.json'; +import { renderInTestApp } from '@backstage/test-utils'; +import { PersistentVolumeClaimsTable } from './PersistentVolumeClaimsTable'; + +describe('PersistentVolumeClaimsTable', () => { + it('should render persistent volume claim table with columns', async () => { + await renderInTestApp( + , + ); + + expect(screen.getByText('name')).toBeInTheDocument(); + expect(screen.getByText('phase')).toBeInTheDocument(); + expect(screen.getByText('status')).toBeInTheDocument(); + expect(screen.getByText('capacity')).toBeInTheDocument(); + expect(screen.getByText('volume')).toBeInTheDocument(); + + expect(screen.getByText('pvc-web-storage')).toBeInTheDocument(); + expect(screen.getAllByText('Bound').length).toBeGreaterThanOrEqual(1); + expect(screen.getByText('10Gi')).toBeInTheDocument(); + }); + + it('should render multiple persistent volume claims', async () => { + await renderInTestApp( + , + ); + + expect(screen.getByText('name')).toBeInTheDocument(); + expect(screen.getByText('phase')).toBeInTheDocument(); + expect(screen.getByText('status')).toBeInTheDocument(); + expect(screen.getByText('capacity')).toBeInTheDocument(); + expect(screen.getByText('volume')).toBeInTheDocument(); + + expect(screen.getByText('pvc-web-storage')).toBeInTheDocument(); + expect(screen.getByText('pvc-database-storage')).toBeInTheDocument(); + + expect(screen.getAllByText('Bound').length).toBeGreaterThanOrEqual(1); + expect(screen.getAllByText('Pending').length).toBeGreaterThanOrEqual(1); + + expect(screen.getByText('10Gi')).toBeInTheDocument(); + expect(screen.getByText('50Gi')).toBeInTheDocument(); + }); +}); diff --git a/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsTable.tsx b/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsTable.tsx new file mode 100644 index 0000000000..eb801fc1b8 --- /dev/null +++ b/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsTable.tsx @@ -0,0 +1,136 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ReactNode } from 'react'; +import { PersistentVolumeClaimsDrawer } from './PersistentVolumeClaimsDrawer'; +import { Table, TableColumn } from '@backstage/core-components'; +import type { V1PersistentVolumeClaim } from '@kubernetes/client-node'; +import { + StatusError, + StatusOK, + StatusPending, +} from '@backstage/core-components'; + +export type PersistentVolumeClaimsTableProps = { + persistentVolumeClaims: V1PersistentVolumeClaim[]; + children?: ReactNode; +}; + +const PersistentVolumeClaimDrawerTrigger = ({ + persistentVolumeClaim, +}: { + persistentVolumeClaim: V1PersistentVolumeClaim; +}) => { + return ( + + ); +}; + +const renderPhaseStatus = (persistentVolumeClaim: V1PersistentVolumeClaim) => { + const phase = persistentVolumeClaim.status?.phase; + + if (phase === 'Bound') { + return Bound; + } + if (phase === 'Pending') { + return Pending; + } + if (phase === 'Lost') { + return Lost; + } + return <>{phase ?? 'Unknown'}; +}; + +export const PersistentVolumeClaimsTable = ({ + persistentVolumeClaims, +}: PersistentVolumeClaimsTableProps) => { + const defaultColumns: TableColumn[] = [ + { + title: 'ID', + field: 'metadata.uid', + hidden: true, + }, + { + title: 'name', + highlight: true, + render: (persistentVolumeClaim: V1PersistentVolumeClaim) => { + return ( + + ); + }, + cellStyle: { + width: '25%', + }, + }, + { + title: 'phase', + render: renderPhaseStatus, + cellStyle: { + width: '10%', + }, + }, + { + title: 'status', + render: (persistentVolumeClaim: V1PersistentVolumeClaim) => + persistentVolumeClaim.status?.phase ?? 'Unknown', + cellStyle: { + width: '10%', + }, + }, + { + title: 'capacity', + render: (persistentVolumeClaim: V1PersistentVolumeClaim) => + persistentVolumeClaim.status?.capacity?.storage ?? 'N/A', + cellStyle: { + width: '10%', + }, + }, + { + title: 'volume', + render: (persistentVolumeClaim: V1PersistentVolumeClaim) => + persistentVolumeClaim.spec?.volumeName ?? 'N/A', + cellStyle: { + width: '45%', + }, + }, + ]; + + const columns: TableColumn[] = [...defaultColumns]; + + const tableStyle = { + minWidth: '0', + width: '100%', + }; + + return ( +
    +
    ({ + ...pvc, + id: pvc?.metadata?.uid, + })) as any as V1PersistentVolumeClaim[] + } + columns={columns} + /> + + ); +}; diff --git a/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/index.ts b/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/index.ts new file mode 100644 index 0000000000..63308960fe --- /dev/null +++ b/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './PersistentVolumeClaimsAccordions'; +export * from './PersistentVolumeClaimsTable'; +export * from './PersistentVolumeClaimsDrawer'; diff --git a/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminal.test.tsx b/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminal.test.tsx index 5830043042..46a226e601 100644 --- a/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminal.test.tsx +++ b/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminal.test.tsx @@ -27,7 +27,7 @@ import WS from 'jest-websocket-mock'; import './matchMedia.mock'; import { PodExecTerminal } from './PodExecTerminal'; -global.TextEncoder = require('util').TextEncoder; +global.TextEncoder = require('node:util').TextEncoder; const textEncoder = new TextEncoder(); diff --git a/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminalDialog.tsx b/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminalDialog.tsx index 3570e6c3e0..9a599058c8 100644 --- a/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminalDialog.tsx +++ b/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminalDialog.tsx @@ -19,6 +19,8 @@ import OpenInBrowserIcon from '@material-ui/icons/OpenInBrowser'; import { KubernetesDialog } from '../KubernetesDialog'; import { useIsPodExecTerminalSupported } from '../../hooks'; import { PodExecTerminal, PodExecTerminalProps } from './PodExecTerminal'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../translation'; /** * Opens a terminal connected to the given pod's container in a dialog @@ -27,23 +29,25 @@ import { PodExecTerminal, PodExecTerminalProps } from './PodExecTerminal'; */ export const PodExecTerminalDialog = (props: PodExecTerminalProps) => { const { cluster, containerName, podName } = props; - + const { t } = useTranslationRef(kubernetesReactTranslationRef); const isPodExecTerminalSupported = useIsPodExecTerminalSupported(); return ( !isPodExecTerminalSupported.loading && isPodExecTerminalSupported.value && ( } - buttonText="Terminal" + buttonText={t('podExecTerminal.buttonText')} disabled={ isPodExecTerminalSupported.loading || !isPodExecTerminalSupported.value } - title={`${podName} - ${containerName} terminal shell on cluster ${ - cluster.title || cluster.name - }`} + title={t('podExecTerminal.titleTemplate', { + podName, + containerName, + clusterName: cluster.title || cluster.name, + })} > diff --git a/plugins/kubernetes-react/src/components/Pods/ErrorList/ErrorList.test.tsx b/plugins/kubernetes-react/src/components/Pods/ErrorList/ErrorList.test.tsx index e460abea8b..ec7b24644a 100644 --- a/plugins/kubernetes-react/src/components/Pods/ErrorList/ErrorList.test.tsx +++ b/plugins/kubernetes-react/src/components/Pods/ErrorList/ErrorList.test.tsx @@ -17,44 +17,57 @@ import { render } from '@testing-library/react'; import { ErrorList } from './ErrorList'; import { Pod } from 'kubernetes-models/v1'; +import { TestApiProvider, mockApis } from '@backstage/test-utils'; +import { translationApiRef } from '@backstage/core-plugin-api/alpha'; +import { errorApiRef } from '@backstage/core-plugin-api'; describe('ErrorList', () => { + const translationApi = mockApis.translation(); + const errorApi = { post: jest.fn(), error$: jest.fn() }; + it('error highlight should render', () => { const { getByText } = render( - + , + ], + }, + ]} + /> + , ); expect(getByText('some-pod')).toBeInTheDocument(); expect(getByText('some error message')).toBeInTheDocument(); diff --git a/plugins/kubernetes-react/src/components/Pods/Events/Events.test.tsx b/plugins/kubernetes-react/src/components/Pods/Events/Events.test.tsx index 3f70bb700a..b74929a2e5 100644 --- a/plugins/kubernetes-react/src/components/Pods/Events/Events.test.tsx +++ b/plugins/kubernetes-react/src/components/Pods/Events/Events.test.tsx @@ -17,63 +17,25 @@ import { EventsContent } from './Events'; import { render } from '@testing-library/react'; import { Event } from 'kubernetes-models/v1'; import { DateTime } from 'luxon'; +import { TestApiProvider, mockApis } from '@backstage/test-utils'; +import { translationApiRef } from '@backstage/core-plugin-api/alpha'; +import { errorApiRef } from '@backstage/core-plugin-api'; describe('EventsContent', () => { const oneHourAgo = DateTime.now().minus({ hours: 1 }).toISO(); + const translationApi = mockApis.translation(); + const errorApi = { post: jest.fn(), error$: jest.fn() }; + it('should show info events', () => { const { getByText } = render( - , - ); - expect(getByText('First event 1 hour ago (count: 52)')).toBeInTheDocument(); - expect(getByText('something happened: hello there')).toBeInTheDocument(); - }); - it('should show warning events', () => { - const { getByText } = render( - , - ); - expect(getByText('First event 1 hour ago (count: 23)')).toBeInTheDocument(); - expect(getByText('something happened: uh oh')).toBeInTheDocument(); - }); - - it('should only show warning events when warningEventsOnly set', () => { - const { getByText, queryByText } = render( - + { metadata: { creationTimestamp: oneHourAgo, }, - }, - ] as Event[] - } - />, + } as Event, + ]} + /> + , + ); + expect(getByText('First event 1 hour ago (count: 52)')).toBeInTheDocument(); + expect(getByText('something happened: hello there')).toBeInTheDocument(); + }); + it('should show warning events', () => { + const { getByText } = render( + + + , + ); + expect(getByText('First event 1 hour ago (count: 23)')).toBeInTheDocument(); + expect(getByText('something happened: uh oh')).toBeInTheDocument(); + }); + + it('should only show warning events when warningEventsOnly set', () => { + const { getByText, queryByText } = render( + + + , ); expect(queryByText('First event 1 hour ago (count: 52)')).toBeNull(); expect(queryByText('something happened: hello there')).toBeNull(); diff --git a/plugins/kubernetes-react/src/components/Pods/Events/Events.tsx b/plugins/kubernetes-react/src/components/Pods/Events/Events.tsx index 94fc768e61..dc6abb6fd9 100644 --- a/plugins/kubernetes-react/src/components/Pods/Events/Events.tsx +++ b/plugins/kubernetes-react/src/components/Pods/Events/Events.tsx @@ -31,6 +31,8 @@ import { useEvents } from './useEvents'; import Skeleton from '@material-ui/lab/Skeleton'; import { DismissableBanner } from '@backstage/core-components'; import { Event } from 'kubernetes-models/v1'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../../translation'; /** * Props for Events @@ -59,8 +61,9 @@ export const EventsContent = ({ events, warningEventsOnly, }: EventsContentProps) => { + const { t } = useTranslationRef(kubernetesReactTranslationRef); if (events.length === 0) { - return No events found; + return {t('events.noEventsFound')}; } return ( diff --git a/plugins/kubernetes-react/src/components/Pods/FixDialog/FixDialog.tsx b/plugins/kubernetes-react/src/components/Pods/FixDialog/FixDialog.tsx index 644c17606b..65961dc22d 100644 --- a/plugins/kubernetes-react/src/components/Pods/FixDialog/FixDialog.tsx +++ b/plugins/kubernetes-react/src/components/Pods/FixDialog/FixDialog.tsx @@ -35,6 +35,8 @@ import { Events } from '../Events'; import { LinkButton } from '@backstage/core-components'; import List from '@material-ui/core/List'; import ListItem from '@material-ui/core/ListItem'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../../translation'; const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -72,6 +74,7 @@ export const FixDialog: FC = ({ }: FixDialogProps) => { const [isOpen, setOpen] = useState(!!open); const classes = useStyles(); + const { t } = useTranslationRef(kubernetesReactTranslationRef); const openDialog = () => { setOpen(true); @@ -87,21 +90,27 @@ export const FixDialog: FC = ({ return ( - Detected error: + {t('fixDialog.detectedError')} {error.message} - Cause explanation: + + {t('fixDialog.causeExplanation')} + - {error.proposedFix?.rootCauseExplanation ?? 'unknown'} + {error.proposedFix?.rootCauseExplanation ?? t('podsTable.unknown')} - Fix: + {t('fixDialog.fix')} {(error.proposedFix?.actions ?? []).map((fix, i) => { return ( - + {fix} ); @@ -112,14 +121,15 @@ export const FixDialog: FC = ({ {pf && pf.type === 'logs' && ( <> - Crash logs: + {t('fixDialog.crashLogs')} = ({ {pf && pf.type === 'events' && ( <> - Events: + {t('fixDialog.events')} = ({ <> - {pod.metadata?.name} - {error.type} + {t('fixDialog.title', { + podName: pod.metadata?.name ?? '', + errorType: error.type, + })} @@ -178,7 +191,7 @@ export const FixDialog: FC = ({ target="_blank" rel="noopener" > - Open docs + {t('fixDialog.openDocs')} )} diff --git a/plugins/kubernetes-react/src/components/Pods/PodDrawer/ContainerCard.tsx b/plugins/kubernetes-react/src/components/Pods/PodDrawer/ContainerCard.tsx index 74ef538bf1..c2b750a7d9 100644 --- a/plugins/kubernetes-react/src/components/Pods/PodDrawer/ContainerCard.tsx +++ b/plugins/kubernetes-react/src/components/Pods/PodDrawer/ContainerCard.tsx @@ -30,6 +30,8 @@ import { bytesToMiB, formatMillicores } from '../../../utils/resources'; import { PodExecTerminalDialog } from '../../PodExecTerminal/PodExecTerminalDialog'; import { ResourceUtilization } from '../../ResourceUtilization'; import { PodLogsDialog, PodScope } from '../PodLogs'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../../translation'; const getContainerHealthChecks = ( containerSpec: IContainer, @@ -112,6 +114,7 @@ export const ContainerCard: FC = ({ containerStatus, containerMetrics, }: ContainerCardProps) => { + const { t } = useTranslationRef(kubernetesReactTranslationRef); const isPodExecTerminalEnabled = useIsPodExecTerminalEnabled(); // This should never be undefined @@ -183,13 +186,13 @@ export const ContainerCard: FC = ({ - Resource utilization + {t('podDrawer.resourceUtilization')} = ({ /> = ({ /> = ({ /> createStyles({ @@ -77,6 +79,7 @@ export const PodDrawer = ({ podAndErrors, open }: PodDrawerProps) => { const classes = useDrawerContentStyles(); const podMetrics = usePodMetrics(podAndErrors.cluster.name, podAndErrors.pod); const isPodDeleteEnabled = useIsPodDeleteEnabled(); + const { t } = useTranslationRef(kubernetesReactTranslationRef); return ( { {podMetrics && ( - Resource utilization + + {t('podDrawer.resourceUtilization')} + { = ({ containerScope, previous, }: PodLogsProps) => { + const { t } = useTranslationRef(kubernetesReactTranslationRef); const { value, error, loading } = usePodLogs({ containerScope, previous, @@ -72,8 +75,8 @@ export const PodLogs: FC = ({ (value.text === '' ? ( ) : ( diff --git a/plugins/kubernetes-react/src/components/Pods/PodLogs/PodLogsDialog.tsx b/plugins/kubernetes-react/src/components/Pods/PodLogs/PodLogsDialog.tsx index cb4ccfdf5d..59524e4ed1 100644 --- a/plugins/kubernetes-react/src/components/Pods/PodLogs/PodLogsDialog.tsx +++ b/plugins/kubernetes-react/src/components/Pods/PodLogs/PodLogsDialog.tsx @@ -18,6 +18,8 @@ import SubjectIcon from '@material-ui/icons/Subject'; import { KubernetesDialog } from '../../KubernetesDialog'; import { PodLogs } from './PodLogs'; import { ContainerScope } from './types'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../../translation'; /** * Props for PodLogsDialog @@ -34,17 +36,19 @@ export interface PodLogsDialogProps { * @public */ export const PodLogsDialog = ({ containerScope }: PodLogsDialogProps) => { + const { t } = useTranslationRef(kubernetesReactTranslationRef); return ( } - buttonText="Logs" + buttonText={t('podLogs.buttonText')} disabled={false} - title={`${containerScope.podName} - ${ - containerScope.containerName - } logs on cluster ${ - containerScope.cluster.title || containerScope.cluster.name - }`} + title={t('podLogs.titleTemplate', { + podName: containerScope.podName, + containerName: containerScope.containerName, + clusterName: + containerScope.cluster.title || containerScope.cluster.name, + })} > diff --git a/plugins/kubernetes-react/src/components/Pods/PodsTable.tsx b/plugins/kubernetes-react/src/components/Pods/PodsTable.tsx index 8d3c14d86a..ee2fce2822 100644 --- a/plugins/kubernetes-react/src/components/Pods/PodsTable.tsx +++ b/plugins/kubernetes-react/src/components/Pods/PodsTable.tsx @@ -30,6 +30,8 @@ import { Pod } from 'kubernetes-models/v1/Pod'; import type { V1Pod } from '@kubernetes/client-node'; import { usePodMetrics } from '../../hooks/usePodMetrics'; import Typography from '@material-ui/core/Typography'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../translation'; /** * @@ -63,22 +65,6 @@ export type PodsTablesProps = { children?: ReactNode; }; -const READY: TableColumn[] = [ - { - title: 'containers ready', - align: 'center', - render: containersReady, - width: 'auto', - }, - { - title: 'total restarts', - align: 'center', - render: totalRestarts, - type: 'numeric', - width: 'auto', - }, -]; - const PodDrawerTrigger = ({ pod }: { pod: Pod }) => { const errors = useMatchingErrors({ kind: 'Pod', @@ -98,9 +84,10 @@ const PodDrawerTrigger = ({ pod }: { pod: Pod }) => { const Cpu = ({ clusterName, pod }: { clusterName: string; pod: Pod }) => { const metrics = usePodMetrics(clusterName, pod); + const { t } = useTranslationRef(kubernetesReactTranslationRef); if (!metrics) { - return unknown; + return {t('podsTable.unknown')}; } return <>{podStatusToCpuUtil(metrics)}; @@ -108,9 +95,10 @@ const Cpu = ({ clusterName, pod }: { clusterName: string; pod: Pod }) => { const Memory = ({ clusterName, pod }: { clusterName: string; pod: Pod }) => { const metrics = usePodMetrics(clusterName, pod); + const { t } = useTranslationRef(kubernetesReactTranslationRef); if (!metrics) { - return unknown; + return {t('podsTable.unknown')}; } return <>{podStatusToMemoryUtil(metrics)}; @@ -123,26 +111,44 @@ const Memory = ({ clusterName, pod }: { clusterName: string; pod: Pod }) => { */ export const PodsTable = ({ pods, extraColumns = [] }: PodsTablesProps) => { const cluster = useContext(ClusterContext); + const { t } = useTranslationRef(kubernetesReactTranslationRef); + + const READY: TableColumn[] = [ + { + title: t('podsTable.columns.containersReady'), + align: 'center', + render: containersReady, + width: 'auto', + }, + { + title: t('podsTable.columns.totalRestarts'), + align: 'center', + render: totalRestarts, + type: 'numeric', + width: 'auto', + }, + ]; + const defaultColumns: TableColumn[] = [ { - title: 'ID', + title: t('podsTable.columns.id'), field: 'metadata.uid', hidden: true, }, { - title: 'name', + title: t('podsTable.columns.name'), highlight: true, render: (pod: Pod) => { return ; }, }, { - title: 'phase', - render: (pod: Pod) => pod.status?.phase ?? 'unknown', + title: t('podsTable.columns.phase'), + render: (pod: Pod) => pod.status?.phase ?? t('podsTable.unknown'), width: 'auto', }, { - title: 'status', + title: t('podsTable.columns.status'), render: containerStatuses, }, ]; @@ -154,14 +160,14 @@ export const PodsTable = ({ pods, extraColumns = [] }: PodsTablesProps) => { if (extraColumns.includes(RESOURCE_COLUMNS)) { const resourceColumns: TableColumn[] = [ { - title: 'CPU usage %', + title: t('podsTable.columns.cpuUsage'), render: (pod: Pod) => { return ; }, width: 'auto', }, { - title: 'Memory usage %', + title: t('podsTable.columns.memoryUsage'), render: (pod: Pod) => { return ; }, diff --git a/plugins/kubernetes-react/src/components/SecretsAccordions/SecretsAccordions.test.tsx b/plugins/kubernetes-react/src/components/SecretsAccordions/SecretsAccordions.test.tsx new file mode 100644 index 0000000000..1bdafa293b --- /dev/null +++ b/plugins/kubernetes-react/src/components/SecretsAccordions/SecretsAccordions.test.tsx @@ -0,0 +1,48 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { screen } from '@testing-library/react'; +import { SecretsAccordions } from './SecretsAccordions'; +import * as oneSecretsFixture from '../../__fixtures__/1-secrets.json'; +import * as twoSecretsFixture from '../../__fixtures__/2-secrets.json'; +import { renderInTestApp } from '@backstage/test-utils'; +import { kubernetesProviders } from '../../hooks/test-utils'; + +describe('SecretsAccordions', () => { + it('should render 1 secret', async () => { + const wrapper = kubernetesProviders(oneSecretsFixture, new Set()); + + await renderInTestApp(wrapper()); + + expect(screen.getByText('app-secret')).toBeInTheDocument(); + expect(screen.getByText('Secret')).toBeInTheDocument(); + expect(screen.getByText('namespace: default')).toBeInTheDocument(); + expect(screen.getByText('Data Count: 4')).toBeInTheDocument(); + }); + + it('should render 2 secrets', async () => { + const wrapper = kubernetesProviders(twoSecretsFixture, new Set()); + + await renderInTestApp(wrapper()); + + expect(screen.getByText('app-secret')).toBeInTheDocument(); + expect(screen.getByText('redis-secret')).toBeInTheDocument(); + expect(screen.getAllByText('Secret')).toHaveLength(2); + expect(screen.getAllByText('namespace: default')).toHaveLength(2); + expect(screen.getByText('Data Count: 4')).toBeInTheDocument(); + expect(screen.getByText('Data Count: 3')).toBeInTheDocument(); + }); +}); diff --git a/plugins/kubernetes-react/src/components/SecretsAccordions/SecretsAccordions.tsx b/plugins/kubernetes-react/src/components/SecretsAccordions/SecretsAccordions.tsx new file mode 100644 index 0000000000..4271a2af8b --- /dev/null +++ b/plugins/kubernetes-react/src/components/SecretsAccordions/SecretsAccordions.tsx @@ -0,0 +1,108 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { useContext } from 'react'; +import Accordion from '@material-ui/core/Accordion'; +import AccordionDetails from '@material-ui/core/AccordionDetails'; +import AccordionSummary from '@material-ui/core/AccordionSummary'; +import Grid from '@material-ui/core/Grid'; +import Typography from '@material-ui/core/Typography'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import type { V1Secret } from '@kubernetes/client-node'; +import { SecretsDrawer } from './SecretsDrawer.tsx'; +import { GroupedResponsesContext } from '../../hooks'; +import { StructuredMetadataTable } from '@backstage/core-components'; + +type SecretSummaryProps = { + secret: V1Secret; +}; + +const SecretSummary = ({ secret }: SecretSummaryProps) => { + return ( + + + + + + + + Data Count: {secret.data ? Object.keys(secret.data).length : 0} + + + + ); +}; + +type SecretsCardProps = { + secret: V1Secret; +}; + +const SecretCard = ({ secret }: SecretsCardProps) => { + const metadata: any = {}; + + metadata.data = secret.data; + + return ( + + ); +}; + +export type SecretsAccordionsProps = {}; + +type SecretsAccordionProps = { + secret: V1Secret; +}; + +const SecretsAccordion = ({ secret }: SecretsAccordionProps) => { + return ( + + }> + + + + + + + ); +}; + +export const SecretsAccordions = ({}: SecretsAccordionsProps) => { + const groupedResponses = useContext(GroupedResponsesContext); + return ( + + {groupedResponses.secrets.map((secret, i) => ( + + + + ))} + + ); +}; diff --git a/plugins/kubernetes-react/src/components/SecretsAccordions/SecretsDrawer.test.tsx b/plugins/kubernetes-react/src/components/SecretsAccordions/SecretsDrawer.test.tsx new file mode 100644 index 0000000000..6dd1230ee4 --- /dev/null +++ b/plugins/kubernetes-react/src/components/SecretsAccordions/SecretsDrawer.test.tsx @@ -0,0 +1,37 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import * as oneSecretsFixture from '../../__fixtures__/1-secrets.json'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { SecretsDrawer } from './SecretsDrawer'; +import { kubernetesClusterLinkFormatterApiRef } from '../../api'; + +describe('SecretsDrawer', () => { + it('should render secret drawer', async () => { + const { getByText, getAllByText } = await renderInTestApp( + + + , + ); + + expect(getAllByText('app-secret')).toHaveLength(3); + expect(getAllByText('Secret')).toHaveLength(3); + expect(getByText('YAML')).toBeInTheDocument(); + expect(getByText('namespace: default')).toBeInTheDocument(); + }); +}); diff --git a/plugins/kubernetes-react/src/components/SecretsAccordions/SecretsDrawer.tsx b/plugins/kubernetes-react/src/components/SecretsAccordions/SecretsDrawer.tsx new file mode 100644 index 0000000000..72df6bdae9 --- /dev/null +++ b/plugins/kubernetes-react/src/components/SecretsAccordions/SecretsDrawer.tsx @@ -0,0 +1,64 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { KubernetesStructuredMetadataTableDrawer } from '../KubernetesDrawer'; +import Typography from '@material-ui/core/Typography'; +import Grid from '@material-ui/core/Grid'; +import Chip from '@material-ui/core/Chip'; +import type { V1Secret } from '@kubernetes/client-node'; + +export const SecretsDrawer = ({ + secret, + expanded, +}: { + secret: V1Secret; + expanded?: boolean; +}) => { + const namespace = secret.metadata?.namespace; + return ( + { + return secretObject || {}; + }} + > + + + + {secret.metadata?.name ?? 'unknown object'} + + + + + Secret + + + {namespace && ( + + + + )} + + + ); +}; diff --git a/plugins/kubernetes-react/src/components/SecretsAccordions/index.ts b/plugins/kubernetes-react/src/components/SecretsAccordions/index.ts new file mode 100644 index 0000000000..461537fe72 --- /dev/null +++ b/plugins/kubernetes-react/src/components/SecretsAccordions/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './SecretsAccordions.tsx'; diff --git a/plugins/kubernetes-react/src/components/ServicesAccordions/ServiceDrawer.tsx b/plugins/kubernetes-react/src/components/ServicesAccordions/ServiceDrawer.tsx index a9f7b7b5ad..d9d08d04cb 100644 --- a/plugins/kubernetes-react/src/components/ServicesAccordions/ServiceDrawer.tsx +++ b/plugins/kubernetes-react/src/components/ServicesAccordions/ServiceDrawer.tsx @@ -19,6 +19,8 @@ import { KubernetesStructuredMetadataTableDrawer } from '../KubernetesDrawer'; import Typography from '@material-ui/core/Typography'; import Grid from '@material-ui/core/Grid'; import Chip from '@material-ui/core/Chip'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../translation'; export const ServiceDrawer = ({ service, @@ -27,6 +29,7 @@ export const ServiceDrawer = ({ service: V1Service; expanded?: boolean; }) => { + const { t } = useTranslationRef(kubernetesReactTranslationRef); const namespace = service.metadata?.namespace; return ( {namespace && ( - + )} diff --git a/plugins/kubernetes-react/src/components/StatefulSetsAccordions/StatefulSetDrawer.tsx b/plugins/kubernetes-react/src/components/StatefulSetsAccordions/StatefulSetDrawer.tsx index 7b7a2b7bb2..ebb446b40c 100644 --- a/plugins/kubernetes-react/src/components/StatefulSetsAccordions/StatefulSetDrawer.tsx +++ b/plugins/kubernetes-react/src/components/StatefulSetsAccordions/StatefulSetDrawer.tsx @@ -21,6 +21,8 @@ import { renderCondition } from '../../utils/pod'; import Typography from '@material-ui/core/Typography'; import Grid from '@material-ui/core/Grid'; import Chip from '@material-ui/core/Chip'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../translation'; export const StatefulSetDrawer = ({ statefulset, @@ -29,6 +31,7 @@ export const StatefulSetDrawer = ({ statefulset: V1StatefulSet; expanded?: boolean; }) => { + const { t } = useTranslationRef(kubernetesReactTranslationRef); const namespace = statefulset.metadata?.namespace; return ( {namespace && ( - + )} diff --git a/plugins/kubernetes-react/src/components/StatefulSetsAccordions/StatefulSetsAccordions.tsx b/plugins/kubernetes-react/src/components/StatefulSetsAccordions/StatefulSetsAccordions.tsx index e3ae9776bd..e1a33474f2 100644 --- a/plugins/kubernetes-react/src/components/StatefulSetsAccordions/StatefulSetsAccordions.tsx +++ b/plugins/kubernetes-react/src/components/StatefulSetsAccordions/StatefulSetsAccordions.tsx @@ -36,6 +36,8 @@ import { } from '../../hooks'; import { StatusError, StatusOK } from '@backstage/core-components'; import { READY_COLUMNS, RESOURCE_COLUMNS } from '../Pods/PodsTable'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../translation'; type StatefulSetsAccordionsProps = { children?: ReactNode; @@ -62,6 +64,7 @@ const StatefulSetSummary = ({ numberOfPodsWithErrors, hpa, }: StatefulSetSummaryProps) => { + const { t } = useTranslationRef(kubernetesReactTranslationRef); const specCpuUtil = hpa?.spec?.metrics?.find( metric => metric.type === 'Resource' && metric.resource?.name === 'cpu', )?.resource?.target.averageUtilization; @@ -94,18 +97,18 @@ const StatefulSetSummary = ({ > - min replicas {hpa.spec?.minReplicas ?? '?'} / max replicas{' '} - {hpa.spec?.maxReplicas ?? '?'} + {t('hpa.minReplicas')} {hpa.spec?.minReplicas ?? '?'} /{' '} + {t('hpa.maxReplicas')} {hpa.spec?.maxReplicas ?? '?'} - current CPU usage: {cpuUtil ?? '?'}% + {t('hpa.currentCpuUsage')} {cpuUtil ?? '?'}% - target CPU usage: {specCpuUtil ?? '?'}% + {t('hpa.targetCpuUsage')} {specCpuUtil ?? '?'}% @@ -122,16 +125,15 @@ const StatefulSetSummary = ({ spacing={0} > - {numberOfCurrentPods} pods + {t('pods.pods', { count: numberOfCurrentPods })} {numberOfPodsWithErrors > 0 ? ( - {numberOfPodsWithErrors} pod - {numberOfPodsWithErrors > 1 ? 's' : ''} with errors + {t('cluster.podsWithErrors', { count: numberOfPodsWithErrors })} ) : ( - No pods with errors + {t('cluster.noPodsWithErrors')} )} diff --git a/plugins/kubernetes-react/src/hooks/GroupedResponses.ts b/plugins/kubernetes-react/src/hooks/GroupedResponses.ts index 1dbf16c57e..81ad9af048 100644 --- a/plugins/kubernetes-react/src/hooks/GroupedResponses.ts +++ b/plugins/kubernetes-react/src/hooks/GroupedResponses.ts @@ -28,10 +28,13 @@ export const GroupedResponsesContext = createContext({ daemonSets: [], services: [], configMaps: [], + secrets: [], horizontalPodAutoscalers: [], ingresses: [], jobs: [], cronJobs: [], customResources: [], statefulsets: [], + persistentVolumes: [], + persistentVolumeClaims: [], }); diff --git a/plugins/kubernetes-react/src/hooks/useIsPodExecTerminalSupported.test.ts b/plugins/kubernetes-react/src/hooks/useIsPodExecTerminalSupported.test.ts index c4df347c6f..26ba8f5e91 100644 --- a/plugins/kubernetes-react/src/hooks/useIsPodExecTerminalSupported.test.ts +++ b/plugins/kubernetes-react/src/hooks/useIsPodExecTerminalSupported.test.ts @@ -55,7 +55,7 @@ describe('useIsPodExecTerminalSupported', () => { }, { condition: 'AuthProvider is google', - returnValue: false, + returnValue: true, testClusters: [{ authProvider: 'google' }], }, { diff --git a/plugins/kubernetes-react/src/hooks/useIsPodExecTerminalSupported.ts b/plugins/kubernetes-react/src/hooks/useIsPodExecTerminalSupported.ts index 5795b900ae..06c1a8808f 100644 --- a/plugins/kubernetes-react/src/hooks/useIsPodExecTerminalSupported.ts +++ b/plugins/kubernetes-react/src/hooks/useIsPodExecTerminalSupported.ts @@ -34,8 +34,8 @@ export const useIsPodExecTerminalSupported = (): AsyncState => { } const { authProvider } = clusters[0]; - const isClientAuthProvider = ['aks', 'google', 'oidc'].some( - authProviderName => authProvider.includes(authProviderName), + const isClientAuthProvider = ['aks', 'oidc'].some(authProviderName => + authProvider.includes(authProviderName), ); return !isClientAuthProvider; diff --git a/plugins/kubernetes-react/src/index.ts b/plugins/kubernetes-react/src/index.ts index 326b363b05..e52ac76d49 100644 --- a/plugins/kubernetes-react/src/index.ts +++ b/plugins/kubernetes-react/src/index.ts @@ -28,3 +28,4 @@ export * from './api'; export * from './kubernetes-auth-provider'; export * from './components'; export * from './types'; +export { kubernetesReactTranslationRef } from './translation'; diff --git a/plugins/kubernetes-react/src/translation.ts b/plugins/kubernetes-react/src/translation.ts index 5b8a26b0d5..bc8f3d99da 100644 --- a/plugins/kubernetes-react/src/translation.ts +++ b/plugins/kubernetes-react/src/translation.ts @@ -16,14 +16,134 @@ import { createTranslationRef } from '@backstage/core-plugin-api/alpha'; -/** @alpha */ +/** @public */ export const kubernetesReactTranslationRef = createTranslationRef({ id: 'kubernetes-react', messages: { + cluster: { + label: 'Cluster', + pods: 'pods', + pods_one: '{{count}} pod', + pods_other: '{{count}} pods', + podsWithErrors: 'pods with errors', + podsWithErrors_one: '{{count}} pod with errors', + podsWithErrors_other: '{{count}} pods with errors', + noPodsWithErrors: 'No pods with errors', + }, + podsTable: { + columns: { + id: 'ID', + name: 'name', + phase: 'phase', + status: 'status', + containersReady: 'containers ready', + totalRestarts: 'total restarts', + cpuUsage: 'CPU usage %', + memoryUsage: 'Memory usage %', + }, + unknown: 'unknown', + status: { + running: 'Running', + ok: 'OK', + }, + }, + errorPanel: { + title: 'There was a problem retrieving Kubernetes objects', + message: + 'There was a problem retrieving some Kubernetes resources for the entity: {{entityName}}. This could mean that the Error Reporting card is not completely accurate.', + errorsLabel: 'Errors', + clusterLabel: 'Cluster', + clusterLabelValue: 'Cluster: {{cluster}}', + fetchError: + 'Error communicating with Kubernetes: {{errorType}}, message: {{message}}', + resourceError: + "Error fetching Kubernetes resource: '{{resourcePath}}', error: {{errorType}}, status code: {{statusCode}}", + }, + fixDialog: { + helpButton: 'Help', + title: '{{podName}} - {{errorType}}', + detectedError: 'Detected error:', + causeExplanation: 'Cause explanation:', + fix: 'Fix:', + crashLogs: 'Crash logs:', + events: 'Events:', + openDocs: 'Open docs', + ariaLabels: { + fixIssue: 'fix issue', + close: 'close', + }, + }, podDrawer: { buttons: { delete: 'Delete Pod', }, + cpuRequests: 'CPU requests', + cpuLimits: 'CPU limits', + memoryRequests: 'Memory requests', + memoryLimits: 'Memory limits', + resourceUtilization: 'Resource utilization', + }, + hpa: { + minReplicas: 'min replicas', + maxReplicas: 'max replicas', + replicasSummary: 'min replicas {{min}} / max replicas {{max}}', + currentCpuUsage: 'current CPU usage:', + currentCpuUsageLabel: 'current CPU usage: {{value}}%', + targetCpuUsage: 'target CPU usage:', + targetCpuUsageLabel: 'target CPU usage: {{value}}%', + }, + pods: { + pods_one: '{{count}} pod', + pods_other: '{{count}} pods', + }, + errorReporting: { + title: 'Error Reporting', + columns: { + cluster: 'cluster', + namespace: 'namespace', + kind: 'kind', + name: 'name', + messages: 'messages', + }, + }, + podLogs: { + title: 'No logs emitted', + description: 'No logs were emitted by the container', + buttonText: 'Logs', + buttonAriaLabel: 'get logs', + titleTemplate: + '{{podName}} - {{containerName}} logs on cluster {{clusterName}}', + }, + podExecTerminal: { + buttonText: 'Terminal', + buttonAriaLabel: 'open terminal', + titleTemplate: + '{{podName}} - {{containerName}} terminal shell on cluster {{clusterName}}', + }, + events: { + noEventsFound: 'No events found', + eventTooltip: '{{eventType}} event', + firstEvent: 'First event {{timeAgo}} (count: {{count}})', + }, + kubernetesDrawer: { + closeDrawer: 'Close the drawer', + yaml: 'YAML', + managedFields: 'Managed Fields', + unknownName: 'unknown name', + }, + linkErrorPanel: { + title: + 'There was a problem formatting the link to the Kubernetes dashboard', + message: + "Could not format the link to the dashboard of your cluster named '{{clusterName}}'. Its dashboardApp property has been set to '{{dashboardApp}}.'", + errorsLabel: 'Errors:', + }, + namespace: { + label: 'namespace:', + labelWithValue: 'namespace: {{namespace}}', + }, + kubernetesDialog: { + closeAriaLabel: 'close', }, }, }); diff --git a/plugins/kubernetes-react/src/utils/persistentVolumes.test.ts b/plugins/kubernetes-react/src/utils/persistentVolumes.test.ts new file mode 100644 index 0000000000..ab583b7c9d --- /dev/null +++ b/plugins/kubernetes-react/src/utils/persistentVolumes.test.ts @@ -0,0 +1,83 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getPersistentVolumeType } from './persistentVolumes'; + +describe('getPersistentVolumeType', () => { + it('should return null for undefined driver', () => { + expect(getPersistentVolumeType(undefined)).toBeNull(); + }); + + it('should return null for empty driver', () => { + expect(getPersistentVolumeType('')).toBeNull(); + }); + + describe('AWS drivers', () => { + it('should return AWS EBS Volume for aws ebs driver', () => { + expect(getPersistentVolumeType('ebs.csi.aws.com')).toBe('AWS EBS Volume'); + }); + + it('should return AWS EFS for aws efs driver', () => { + expect(getPersistentVolumeType('efs.csi.aws.com')).toBe('AWS EFS'); + }); + + it('should return S3 Bucket for aws s3 driver', () => { + expect(getPersistentVolumeType('s3.csi.aws.com')).toBe('S3 Bucket'); + }); + }); + + describe('GCP drivers', () => { + it('should return GCP Persistent Disk for gcp pd driver', () => { + expect(getPersistentVolumeType('pd.csi.storage.gke.io')).toBe( + 'GCP Persistent Disk', + ); + }); + + it('should return GCP Filestore for gcp filestore driver', () => { + expect(getPersistentVolumeType('filestore.csi.storage.gke.io')).toBe( + 'GCP Filestore', + ); + }); + + it('should return GCS Fuse for gcp gcsfuse driver', () => { + expect(getPersistentVolumeType('gcsfuse.csi.storage.gke.io')).toBe( + 'GCS Fuse', + ); + }); + }); + + describe('Azure drivers', () => { + it('should return Azure Disk for azure disk driver', () => { + expect(getPersistentVolumeType('disk.csi.azure.com')).toBe('Azure Disk'); + }); + + it('should return Azure File for azure file driver', () => { + expect(getPersistentVolumeType('file.csi.azure.com')).toBe('Azure File'); + }); + + it('should return Azure Blob for azure blob driver', () => { + expect(getPersistentVolumeType('blob.csi.azure.com')).toBe('Azure Blob'); + }); + }); + + it('should return the original driver for unknown drivers', () => { + expect(getPersistentVolumeType('unknown.driver.com')).toBe( + 'unknown.driver.com', + ); + expect(getPersistentVolumeType('local-storage')).toBe('local-storage'); + expect(getPersistentVolumeType('nfs')).toBe('nfs'); + }); +}); diff --git a/plugins/kubernetes-react/src/utils/persistentVolumes.ts b/plugins/kubernetes-react/src/utils/persistentVolumes.ts new file mode 100644 index 0000000000..348e442cb1 --- /dev/null +++ b/plugins/kubernetes-react/src/utils/persistentVolumes.ts @@ -0,0 +1,39 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const getPersistentVolumeType = (driver?: string): string | null => { + if (!driver) return null; + + if (driver.includes('aws')) { + if (driver.includes('ebs')) return `AWS EBS Volume`; + if (driver.includes('efs')) return `AWS EFS`; + if (driver.includes('s3')) return `S3 Bucket`; + } + + if (driver.includes('gke')) { + if (driver.includes('gcsfuse')) return `GCS Fuse`; + if (driver.includes('filestore')) return `GCP Filestore`; + if (driver.includes('pd')) return `GCP Persistent Disk`; + } + + if (driver.includes('azure')) { + if (driver.includes('disk')) return `Azure Disk`; + if (driver.includes('file')) return `Azure File`; + if (driver.includes('blob')) return `Azure Blob`; + } + + return driver; +}; diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index f8dcfb151f..6f206cd3bc 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,254 @@ # @backstage/plugin-kubernetes +## 0.12.18 + +### Patch Changes + +- d156cf4: Added `title` and `icon` to the new frontend system plugin definition. +- Updated dependencies + - @backstage/catalog-model@1.8.0 + - @backstage/plugin-catalog-react@2.1.2 + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/core-components@0.18.9 + - @backstage/plugin-permission-react@0.5.0 + - @backstage/core-plugin-api@1.12.5 + - @backstage/plugin-kubernetes-common@0.9.11 + - @backstage/plugin-kubernetes-react@0.5.18 + +## 0.12.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.9-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.2 + - @backstage/catalog-model@1.7.8-next.0 + - @backstage/core-plugin-api@1.12.5-next.2 + - @backstage/frontend-plugin-api@0.16.0-next.2 + - @backstage/plugin-kubernetes-react@0.5.18-next.1 + - @backstage/plugin-kubernetes-common@0.9.11-next.0 + - @backstage/plugin-permission-react@0.4.42-next.1 + +## 0.12.18-next.1 + +### Patch Changes + +- d156cf4: Added `title` and `icon` to the new frontend system plugin definition. +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-kubernetes-react@0.5.18-next.0 + +## 0.12.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.1-next.0 + - @backstage/core-components@0.18.9-next.0 + - @backstage/frontend-plugin-api@0.15.2-next.0 + - @backstage/plugin-kubernetes-react@0.5.18-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + - @backstage/catalog-model@1.7.7 + - @backstage/plugin-kubernetes-common@0.9.10 + - @backstage/plugin-permission-react@0.4.42-next.0 + +## 0.12.17 + +### Patch Changes + +- 538c985: Updated installation documentation to use feature discovery as the default. +- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point. +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/plugin-permission-react@0.4.41 + - @backstage/plugin-kubernetes-react@0.5.17 + - @backstage/catalog-model@1.7.7 + +## 0.12.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.2 + - @backstage/core-components@0.18.8-next.1 + +## 0.12.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/plugin-kubernetes-common@0.9.10 + - @backstage/plugin-kubernetes-react@0.5.17-next.0 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## 0.12.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/plugin-kubernetes-common@0.9.10 + - @backstage/plugin-kubernetes-react@0.5.17-next.0 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## 0.12.16 + +### Patch Changes + +- 7feb83b: Adjusted to use the new `@backstage/filter-predicates` types for predicate expressions. +- 491a06c: Add the ability to show icons for the tabs on the entity page (new frontend) +- 4183614: Updated usage of deprecated APIs in the new frontend system. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/core-components@0.18.7 + - @backstage/plugin-kubernetes-react@0.5.16 + - @backstage/plugin-kubernetes-common@0.9.10 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-permission-react@0.4.40 + +## 0.12.16-next.2 + +### Patch Changes + +- 7feb83b: Adjusted to use the new `@backstage/filter-predicates` types for predicate expressions. +- 491a06c: Add the ability to show icons for the tabs on the entity page (new frontend) +- 4183614: Updated usage of deprecated APIs in the new frontend system. +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/plugin-kubernetes-react@0.5.16-next.2 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-permission-react@0.4.40-next.1 + +## 0.12.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/plugin-kubernetes-common@0.9.10-next.1 + - @backstage/plugin-kubernetes-react@0.5.16-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/core-components@0.18.7-next.1 + +## 0.12.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/plugin-kubernetes-react@0.5.16-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-kubernetes-common@0.9.10-next.0 + - @backstage/plugin-permission-react@0.4.40-next.0 + +## 0.12.15 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + - @backstage/plugin-catalog-react@1.21.5 + - @backstage/plugin-kubernetes-react@0.5.15 + +## 0.12.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.1 + - @backstage/plugin-kubernetes-react@0.5.15-next.0 + +## 0.12.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.5-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## 0.12.14 + +### Patch Changes + +- f15d5f1: add missing i18n support for kubernetes and kubernetes-react +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/plugin-kubernetes-react@0.5.14 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-kubernetes-common@0.9.9 + - @backstage/core-plugin-api@1.12.1 + - @backstage/plugin-permission-react@0.4.39 + +## 0.12.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-react@0.5.14-next.1 + - @backstage/core-components@0.18.4-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.1 + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + +## 0.12.14-next.0 + +### Patch Changes + +- f15d5f1: add missing i18n support for kubernetes and kubernetes-react +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/plugin-kubernetes-react@0.5.14-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-kubernetes-common@0.9.8 + +## 0.12.13 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/plugin-kubernetes-common@0.9.8 + - @backstage/plugin-kubernetes-react@0.5.13 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/catalog-model@1.7.6 + ## 0.12.13-next.0 ### Patch Changes diff --git a/plugins/kubernetes/README.md b/plugins/kubernetes/README.md index 380c1dffbd..fe64aea257 100644 --- a/plugins/kubernetes/README.md +++ b/plugins/kubernetes/README.md @@ -8,15 +8,20 @@ It will elevate the visibility of errors where identified, and provide drill dow It directly interfaces with the [Kubernetes Backend Plugin (`@backstage-plugin-kubernetes-backend`)](https://github.com/backstage/backstage/tree/master/plugins/kubernetes-backend). -_This plugin was created through the Backstage CLI_ - ## Introduction See our announcement blog post [New Backstage feature: Kubernetes for Service Owners](https://backstage.io/blog/2021/01/12/new-backstage-feature-kubernetes-for-service-owners) to learn more about the motivation behind developing the plugin. ## Setup & Configuration -This plugin must be explicitly added to a Backstage app, along with it's peer backend plugin. +This plugin must be installed in a Backstage app, along with its peer backend plugin. + +```bash +# From your Backstage root directory +yarn --cwd packages/app add @backstage/plugin-kubernetes +``` + +Once installed, the plugin is automatically available in your app through the default feature discovery. For more details and alternative installation methods, see [installing plugins](https://backstage.io/docs/frontend-system/building-apps/installing-plugins). It requires configuration in the Backstage `app-config.yaml` to connect to a Kubernetes API control plane. @@ -24,35 +29,9 @@ In addition, configuration of an entity's `catalog-info.yaml` helps identify whi For more information, see the [formal documentation about the Kubernetes feature in Backstage](https://backstage.io/docs/features/kubernetes/overview). -## Getting started +### Enabling the entity content tab -Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/kubernetes](http://localhost:3000/catalog/default/component/:component-name/kubernetes). - -You can also serve the plugin in isolation by running `yarn start` in the plugin directory. -This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads. -It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory. - -### Integrating with `EntityPage` (New Frontend System) - -Follow this section if you are using Backstage's [new frontend system](https://backstage.io/docs/frontend-system/). - -1. Import `kubernetesPlugin` in your `App.tsx` and add it to your app's `features` array: - -```typescript -import kubernetesPlugin from '@backstage/plugin-kubernetes/alpha'; - -// ... - -export const app = createApp({ - features: [ - // ... - kubernetesPlugin, - // ... - ], -}); -``` - -2. Next, enable your desired extensions in `app-config.yaml`. +Enable the Kubernetes entity content extension in your `app-config.yaml`: ```yaml app: @@ -69,5 +48,19 @@ app: extensions: - entity-content:kubernetes/kubernetes: config: - filter: kind:component,api,resource,system + filter: + kind: + $in: + - component + - api + - resource + - system ``` + +## Getting started + +Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/kubernetes](http://localhost:3000/catalog/default/component/:component-name/kubernetes). + +You can also serve the plugin in isolation by running `yarn start` in the plugin directory. +This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads. +It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory. diff --git a/plugins/kubernetes/knip-report.md b/plugins/kubernetes/knip-report.md index 97d5b385fd..2661c35327 100644 --- a/plugins/kubernetes/knip-report.md +++ b/plugins/kubernetes/knip-report.md @@ -1,3 +1,2 @@ # Knip report - diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index f191ce2a0b..0bea3bf4d0 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes", - "version": "0.12.13-next.0", + "version": "0.12.18", "description": "A Backstage plugin that integrates towards Kubernetes", "backstage": { "role": "frontend-plugin", @@ -59,7 +59,6 @@ }, "dependencies": { "@backstage/catalog-model": "workspace:^", - "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", @@ -79,13 +78,13 @@ "@types/react": "^18.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/plugins/kubernetes/report-alpha.api.md b/plugins/kubernetes/report-alpha.api.md index 571e09d546..841c5dcb95 100644 --- a/plugins/kubernetes/report-alpha.api.md +++ b/plugins/kubernetes/report-alpha.api.md @@ -6,16 +6,22 @@ import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { ApiFactory } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { defaultEntityContentGroups } from '@backstage/plugin-catalog-react/alpha'; import { Entity } from '@backstage/catalog-model'; -import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionInput } from '@backstage/frontend-plugin-api'; +import { FilterPredicate } from '@backstage/filter-predicates'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; +import { JSXElementConstructor } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; -import { RouteRef } from '@backstage/frontend-plugin-api'; -import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +import { ReactElement } from 'react'; +import { RouteRef } from '@backstage/core-plugin-api'; +import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; // @public (undocumented) const _default: OverridableFrontendPlugin< @@ -90,25 +96,27 @@ const _default: OverridableFrontendPlugin< config: { path: string | undefined; title: string | undefined; - filter: EntityPredicate | undefined; + filter: FilterPredicate | undefined; group: string | false | undefined; + icon: string | undefined; }; configInput: { - filter?: EntityPredicate | undefined; - title?: string | undefined; path?: string | undefined; + title?: string | undefined; + filter?: FilterPredicate | undefined; group?: string | false | undefined; + icon?: string | undefined; }; output: | ExtensionDataRef - | ExtensionDataRef | ExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; } > + | ExtensionDataRef | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', @@ -130,6 +138,13 @@ const _default: OverridableFrontendPlugin< { optional: true; } + > + | ExtensionDataRef< + string | ReactElement>, + 'catalog.entity-content-icon', + { + optional: true; + } >; inputs: {}; params: { @@ -139,9 +154,10 @@ const _default: OverridableFrontendPlugin< title: string; defaultGroup?: [Error: `Use the 'group' param instead`]; group?: keyof defaultEntityContentGroups | (string & {}); + icon?: string | ReactElement; loader: () => Promise; - routeRef?: RouteRef; - filter?: string | EntityPredicate | ((entity: Entity) => boolean); + routeRef?: RouteRef_2; + filter?: string | FilterPredicate | ((entity: Entity) => boolean); }; }>; 'page:kubernetes': OverridableExtensionDefinition<{ @@ -149,36 +165,89 @@ const _default: OverridableFrontendPlugin< name: undefined; config: { path: string | undefined; + title: string | undefined; }; configInput: { path?: string | undefined; + title?: string | undefined; }; output: | ExtensionDataRef - | ExtensionDataRef | ExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; } + > + | ExtensionDataRef + | ExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } >; - inputs: {}; + inputs: { + pages: ExtensionInput< + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef_2, + 'core.routing.ref', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >, + { + singleton: false; + optional: false; + internal: false; + } + >; + }; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; - loader: () => Promise; - routeRef?: RouteRef; + title?: string; + icon?: IconElement; + loader?: () => Promise; + routeRef?: RouteRef_2; + noHeader?: boolean; }; }>; } >; export default _default; -// @alpha (undocumented) +// @alpha @deprecated (undocumented) export const kubernetesTranslationRef: TranslationRef< 'kubernetes', { + readonly 'entityContent.title': 'Kubernetes'; + readonly 'kubernetesContentPage.title': 'Your Clusters'; + readonly 'kubernetesContentPage.emptyState.title': 'No Kubernetes resources'; + readonly 'kubernetesContentPage.emptyState.description': 'No resources on any known clusters for {{entityName}}'; readonly 'kubernetesContentPage.permissionAlert.message': "To view Kubernetes objects, contact your portal administrator to give you the 'kubernetes.clusters.read' and 'kubernetes.resources.read' permission."; readonly 'kubernetesContentPage.permissionAlert.title': 'Permission required'; } diff --git a/plugins/kubernetes/report.api.md b/plugins/kubernetes/report.api.md index b9450e650d..8a6b02a7aa 100644 --- a/plugins/kubernetes/report.api.md +++ b/plugins/kubernetes/report.api.md @@ -7,6 +7,7 @@ import { BackstagePlugin } from '@backstage/core-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { JSX as JSX_2 } from 'react/jsx-runtime'; import { RouteRef } from '@backstage/core-plugin-api'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; // Warning: (ae-missing-release-tag) "EntityKubernetesContent" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -37,6 +38,19 @@ const kubernetesPlugin: BackstagePlugin< export { kubernetesPlugin }; export { kubernetesPlugin as plugin }; +// @public (undocumented) +export const kubernetesTranslationRef: TranslationRef< + 'kubernetes', + { + readonly 'entityContent.title': 'Kubernetes'; + readonly 'kubernetesContentPage.title': 'Your Clusters'; + readonly 'kubernetesContentPage.emptyState.title': 'No Kubernetes resources'; + readonly 'kubernetesContentPage.emptyState.description': 'No resources on any known clusters for {{entityName}}'; + readonly 'kubernetesContentPage.permissionAlert.message': "To view Kubernetes objects, contact your portal administrator to give you the 'kubernetes.clusters.read' and 'kubernetes.resources.read' permission."; + readonly 'kubernetesContentPage.permissionAlert.title': 'Permission required'; + } +>; + // Warning: (ae-missing-release-tag) "Router" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/plugins/kubernetes/src/KubernetesContent.tsx b/plugins/kubernetes/src/KubernetesContent.tsx index a8d62a5a3d..4f7c7e73e2 100644 --- a/plugins/kubernetes/src/KubernetesContent.tsx +++ b/plugins/kubernetes/src/KubernetesContent.tsx @@ -31,6 +31,8 @@ import { } from '@backstage/plugin-kubernetes-common'; import { EmptyState, Progress } from '@backstage/core-components'; import { RequireKubernetesPermissions } from './RequireKubernetesPermissions'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesTranslationRef } from './alpha/translation'; type KubernetesContentProps = { entity: Entity; @@ -46,6 +48,7 @@ export const KubernetesContent = ({ entity, refreshIntervalMs, ); + const { t } = useTranslationRef(kubernetesTranslationRef); const clusters = kubernetesObjects?.items.map(item => item.cluster) ?? []; @@ -97,7 +100,9 @@ export const KubernetesContent = ({ /> - Your Clusters + + {t('kubernetesContentPage.title')} + {kubernetesObjects?.items.length <= 0 && ( @@ -111,8 +116,11 @@ export const KubernetesContent = ({ diff --git a/plugins/kubernetes/src/alpha/entityContents.tsx b/plugins/kubernetes/src/alpha/entityContents.tsx index d75893c6e4..0a85cadaed 100644 --- a/plugins/kubernetes/src/alpha/entityContents.tsx +++ b/plugins/kubernetes/src/alpha/entityContents.tsx @@ -14,7 +14,6 @@ * limitations under the License. */ -import { compatWrapper } from '@backstage/core-compat-api'; import { EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha'; import { isKubernetesAvailable } from '../Router'; @@ -26,8 +25,6 @@ export const entityKubernetesContent = EntityContentBlueprint.make({ group: 'deployment', filter: isKubernetesAvailable, loader: () => - import('./KubernetesContentPage').then(m => - compatWrapper(), - ), + import('./KubernetesContentPage').then(m => ), }, }); diff --git a/plugins/kubernetes/src/alpha/index.ts b/plugins/kubernetes/src/alpha/index.ts index 23888f968f..c3419cfa76 100644 --- a/plugins/kubernetes/src/alpha/index.ts +++ b/plugins/kubernetes/src/alpha/index.ts @@ -13,5 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { kubernetesTranslationRef } from './translation'; +import { kubernetesTranslationRef as _kubernetesTranslationRef } from './translation'; + +/** + * @alpha + * @deprecated Import from `@backstage/plugin-kubernetes` instead. + */ +export const kubernetesTranslationRef = _kubernetesTranslationRef; export { default } from './plugin'; diff --git a/plugins/kubernetes/src/alpha/pages.tsx b/plugins/kubernetes/src/alpha/pages.tsx index 6a2d98ddcc..3069d25cd6 100644 --- a/plugins/kubernetes/src/alpha/pages.tsx +++ b/plugins/kubernetes/src/alpha/pages.tsx @@ -14,20 +14,13 @@ * limitations under the License. */ -import { PageBlueprint } from '@backstage/frontend-plugin-api'; // Add this line to import React -import { - compatWrapper, - convertLegacyRouteRef, -} from '@backstage/core-compat-api'; +import { PageBlueprint } from '@backstage/frontend-plugin-api'; import { rootCatalogKubernetesRouteRef } from '../plugin'; export const kubernetesPage = PageBlueprint.make({ params: { path: '/kubernetes', - // you can reuse the existing routeRef - // by wrapping into the convertLegacyRouteRef. - routeRef: convertLegacyRouteRef(rootCatalogKubernetesRouteRef), - // these inputs usually match the props required by the component. - loader: () => import('../Router').then(m => compatWrapper()), + routeRef: rootCatalogKubernetesRouteRef, + loader: () => import('../Router').then(m => ), }, }); diff --git a/plugins/kubernetes/src/alpha/plugin.tsx b/plugins/kubernetes/src/alpha/plugin.tsx index db7b2439bf..283961b514 100644 --- a/plugins/kubernetes/src/alpha/plugin.tsx +++ b/plugins/kubernetes/src/alpha/plugin.tsx @@ -14,7 +14,6 @@ * limitations under the License. */ -import { convertLegacyRouteRefs } from '@backstage/core-compat-api'; import { createFrontendPlugin } from '@backstage/frontend-plugin-api'; import { kubernetesPage } from './pages'; import { entityKubernetesContent } from './entityContents'; @@ -26,8 +25,23 @@ import { kubernetesProxyApi, } from './apis'; +// Kubernetes helm wheel from Simple Icons (https://simpleicons.org/?q=kubernetes), licensed under CC0 +const KubernetesIcon = () => ( + + + +); + export default createFrontendPlugin({ pluginId: 'kubernetes', + title: 'Kubernetes', + icon: , info: { packageJson: () => import('../../package.json') }, extensions: [ kubernetesPage, @@ -37,5 +51,5 @@ export default createFrontendPlugin({ kubernetesAuthProvidersApi, kubernetesClusterLinkFormatterApi, ], - routes: convertLegacyRouteRefs({ kubernetes: rootCatalogKubernetesRouteRef }), + routes: { kubernetes: rootCatalogKubernetesRouteRef }, }); diff --git a/plugins/kubernetes/src/alpha/translation.ts b/plugins/kubernetes/src/alpha/translation.ts index 7f9704af84..17ebe90de0 100644 --- a/plugins/kubernetes/src/alpha/translation.ts +++ b/plugins/kubernetes/src/alpha/translation.ts @@ -15,7 +15,7 @@ */ import { createTranslationRef } from '@backstage/core-plugin-api/alpha'; -/** @alpha */ +/** @public */ export const kubernetesTranslationRef = createTranslationRef({ id: 'kubernetes', messages: { @@ -25,6 +25,14 @@ export const kubernetesTranslationRef = createTranslationRef({ message: "To view Kubernetes objects, contact your portal administrator to give you the 'kubernetes.clusters.read' and 'kubernetes.resources.read' permission.", }, + title: 'Your Clusters', + emptyState: { + title: 'No Kubernetes resources', + description: 'No resources on any known clusters for {{entityName}}', + }, + }, + entityContent: { + title: 'Kubernetes', }, }, }); diff --git a/plugins/kubernetes/src/index.ts b/plugins/kubernetes/src/index.ts index 3651a861fc..54abb07bc3 100644 --- a/plugins/kubernetes/src/index.ts +++ b/plugins/kubernetes/src/index.ts @@ -29,3 +29,4 @@ export type { EntityKubernetesContentProps } from './plugin'; export { Router, isKubernetesAvailable } from './Router'; // TODO remove this re-export as a breaking change after a couple of releases export * from '@backstage/plugin-kubernetes-react'; +export { kubernetesTranslationRef } from './alpha/translation'; diff --git a/plugins/mcp-actions-backend/CHANGELOG.md b/plugins/mcp-actions-backend/CHANGELOG.md index d9afa89a60..fb92c71056 100644 --- a/plugins/mcp-actions-backend/CHANGELOG.md +++ b/plugins/mcp-actions-backend/CHANGELOG.md @@ -1,5 +1,226 @@ # @backstage/plugin-mcp-actions-backend +## 0.1.12 + +### Patch Changes + +- 282c114: Fix OAuth 2.0 Protected Resource Metadata endpoint returning internal plugin URL, preventing some MCP clients like Claude Code from authenticating +- 482ceed: Migrated from `assertError` to `toError` for error handling. +- c6abc52: Fixed the `.well-known/oauth-protected-resource` resource URL to comply with + [RFC 9728 Section 7.3](https://datatracker.ietf.org/doc/html/rfc9728#name-impersonation-attacks). Enabling dynamic resource paths. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/errors@1.3.0 + - @backstage/plugin-catalog-node@2.2.0 + - @backstage/catalog-client@1.15.0 + - @backstage/config@1.3.7 + +## 0.1.12-next.2 + +### Patch Changes + +- 282c114: Fix OAuth 2.0 Protected Resource Metadata endpoint returning internal plugin URL, preventing some MCP clients like Claude Code from authenticating +- 482ceed: Migrated from `assertError` to `toError` for error handling. +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/plugin-catalog-node@2.2.0-next.2 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/catalog-client@1.14.1-next.0 + - @backstage/config@1.3.7-next.0 + +## 0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + +## 0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-catalog-node@2.1.1-next.0 + - @backstage/catalog-client@1.14.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.1.10 + +### Patch Changes + +- 62f0a53: Fixed error forwarding in the actions registry so that known errors like `InputError` and `NotFoundError` thrown by actions preserve their original status codes and messages instead of being wrapped in `ForwardedError` and coerced to 500. +- dee4283: Added `mcpActions.name` and `mcpActions.description` config options to customize the MCP server identity. Namespaced tool names now use dot separator to align with the MCP spec convention. +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- c74b697: Added support for splitting MCP actions into multiple servers via `mcpActions.servers` configuration. Each server gets its own endpoint at `/api/mcp-actions/v1/{key}` with actions scoped using include/exclude filter rules. Tool names are now namespaced with the plugin ID by default, configurable via `mcpActions.namespacedToolNames`. When `mcpActions.servers` is not configured, the plugin continues to serve a single server at `/api/mcp-actions/v1`. +- dc81af1: Adds two new metrics to track MCP server operations and sessions. + + - `mcp.server.operation.duration`: The duration taken to process an individual MCP operation + - `mcp.server.session.duration`: The duration of the MCP session from the perspective of the server + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/catalog-client@1.14.0 + - @backstage/plugin-catalog-node@2.1.0 + +## 0.1.10-next.2 + +### Patch Changes + +- c74b697: Added support for splitting MCP actions into multiple servers via `mcpActions.servers` configuration. Each server gets its own endpoint at `/api/mcp-actions/v1/{key}` with actions scoped using include/exclude filter rules. Tool names are now namespaced with the plugin ID by default, configurable via `mcpActions.namespacedToolNames`. When `mcpActions.servers` is not configured, the plugin continues to serve a single server at `/api/mcp-actions/v1`. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/catalog-client@1.14.0-next.2 + - @backstage/plugin-catalog-node@2.1.0-next.2 + +## 0.1.10-next.1 + +### Patch Changes + +- 62f0a53: Fixed error forwarding in the actions registry so that known errors like `InputError` and `NotFoundError` thrown by actions preserve their original status codes and messages instead of being wrapped in `ForwardedError` and coerced to 500. +- Updated dependencies + - @backstage/catalog-client@1.14.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.1.10-next.0 + +### Patch Changes + +- dc81af1: Adds two new metrics to track MCP server operations and sessions. + + - `mcp.server.operation.duration`: The duration taken to process an individual MCP operation + - `mcp.server.session.duration`: The duration of the MCP session from the perspective of the server + +- Updated dependencies + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-client@1.13.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.1.9 + +### Patch Changes + +- 31de2c9: Added OAuth Protected Resource Metadata endpoint (`/.well-known/oauth-protected-resource`) per RFC 9728. This allows MCP clients to discover the authorization server for the resource. + + Also enabled OAuth well-known endpoints when CIMD (Client ID Metadata Documents) is configured, not just when DCR is enabled. + +- 8148621: Moved `@backstage/backend-defaults` from `dependencies` to `devDependencies`. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/catalog-client@1.13.0 + +## 0.1.9-next.1 + +### Patch Changes + +- 8148621: Moved `@backstage/backend-defaults` from `dependencies` to `devDependencies`. +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/catalog-client@1.12.2-next.0 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## 0.1.8-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/backend-defaults@0.15.1-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.1.7 + +### Patch Changes + +- 4d82a35: build(deps): bump `@modelcontextprotocol/sdk` from 1.24.3 to 1.25.2 +- Updated dependencies + - @backstage/backend-defaults@0.15.0 + - @backstage/backend-plugin-api@1.6.1 + +## 0.1.7-next.1 + +### Patch Changes + +- 4d82a35: build(deps): bump `@modelcontextprotocol/sdk` from 1.24.3 to 1.25.2 +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + +## 0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.1-next.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/catalog-client@1.12.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1 + +## 0.1.6 + +### Patch Changes + +- e83e038: Added `@cfworker/json-schema` as a dependency to this package part of the `@modelcontextprotocol/sdk` bump as it's required in the types +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 79ef471: Clarify error handling in readme and update handleError.ts to include all backstage/errors +- Updated dependencies + - @backstage/backend-defaults@0.14.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## 0.1.6-next.1 + +### Patch Changes + +- e83e038: Added `@cfworker/json-schema` as a dependency to this package part of the `@modelcontextprotocol/sdk` bump as it's required in the types +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## 0.1.6-next.0 + +### Patch Changes + +- 79ef471: Clarify error handling in readme and update handleError.ts to include all backstage/errors +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.1.5 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/catalog-client@1.12.1 + ## 0.1.5-next.1 ### Patch Changes diff --git a/plugins/mcp-actions-backend/README.md b/plugins/mcp-actions-backend/README.md index d81d0207c5..d496cf0f03 100644 --- a/plugins/mcp-actions-backend/README.md +++ b/plugins/mcp-actions-backend/README.md @@ -71,6 +71,89 @@ export const myPlugin = createBackendPlugin({ }); ``` +### Namespaced Tool Names + +By default, MCP tool names include the plugin ID prefix to avoid collisions across plugins. For example, an action registered as `greet-user` by `my-custom-plugin` is exposed as `my-custom-plugin.greet-user`. + +You can disable this if you need the short names for backward compatibility: + +```yaml +mcpActions: + namespacedToolNames: false +``` + +### Multiple MCP Servers + +By default, the plugin serves a single MCP server at `/api/mcp-actions/v1` that exposes all available actions. You can split actions into multiple focused servers by configuring `mcpActions.servers`, where each key becomes a separate MCP server endpoint. + +```yaml +mcpActions: + servers: + catalog: + name: 'Backstage Catalog' + description: 'Tools for interacting with the software catalog' + filter: + include: + - id: 'catalog:*' + scaffolder: + name: 'Backstage Scaffolder' + description: 'Tools for creating new software from templates' + filter: + include: + - id: 'scaffolder:*' +``` + +This creates two MCP server endpoints: + +- `http://localhost:7007/api/mcp-actions/v1/catalog` +- `http://localhost:7007/api/mcp-actions/v1/scaffolder` + +Each server uses include filter rules with glob patterns on action IDs to control which actions are exposed. For example, `id: 'catalog:*'` matches all actions registered by the catalog plugin. + +When `mcpActions.servers` is not configured, the plugin behaves exactly as before with a single server at `/api/mcp-actions/v1`. + +#### Filter Rules + +Include and exclude filter rules support glob patterns on action IDs and attribute matching. Exclude rules take precedence over include rules. When include rules are specified, actions must match at least one include rule to be exposed. + +```yaml +mcpActions: + servers: + catalog: + name: 'Backstage Catalog' + filter: + include: + - id: 'catalog:*' + exclude: + - attributes: + destructive: true +``` + +### Error Handling + +When errors are thrown from MCP actions, the backend will handle and surface error message for any error from `@backstage/errors`. Unknown errors will be handled by `@modelcontextprotocol/sdk`'s default error handling, which may result in a generic `500 Server Error` being returned. As a result, we recommend using errors from `@backstage/errors` when applicable. + +See [Backstage Errors](https://backstage.io/docs/reference/errors/) for a full list of supported errors. + +When writing MCP tools, use the appropriate error from `@backstage/errors` when applicable: + +```ts +action: async ({ input }) => { + // ... get current user and some resource + + if (!resource) { + throw new NotFoundError(`Resource ${input.id} not found`); + } + + // Check if the user has permissions to access/use the resource + if (!hasPermission(user, resource)) { + throw new NotAllowedError( + `user does not have sufficient permissions for ${resource}`, + ); + } +}; +``` + ### Authentication Configuration By default, the Backstage backend requires authentication for all requests. @@ -134,16 +217,15 @@ The MCP server supports both Server-Sent Events (SSE) and Streamable HTTP protoc The SSE protocol is deprecated, and should be avoided as it will be removed in a future release. +### Single Server (default) + - `Streamable HTTP`: `http://localhost:7007/api/mcp-actions/v1` - `SSE`: `http://localhost:7007/api/mcp-actions/v1/sse` -There's a few different ways to configure MCP tools, but here's a snippet of the most common. - ```json { "mcpServers": { "backstage-actions": { - // you can also replace this with the public / internal URL of the deployed backend. "url": "http://localhost:7007/api/mcp-actions/v1", "headers": { "Authorization": "Bearer ${MCP_TOKEN}" @@ -153,6 +235,39 @@ There's a few different ways to configure MCP tools, but here's a snippet of the } ``` +### Multiple Servers + +When `mcpActions.servers` is configured, each server key becomes part of the URL. For example, with servers named `catalog` and `scaffolder`: + +- `http://localhost:7007/api/mcp-actions/v1/catalog` +- `http://localhost:7007/api/mcp-actions/v1/scaffolder` + +```json +{ + "mcpServers": { + "backstage-catalog": { + "url": "http://localhost:7007/api/mcp-actions/v1/catalog", + "headers": { + "Authorization": "Bearer ${MCP_TOKEN}" + } + }, + "backstage-scaffolder": { + "url": "http://localhost:7007/api/mcp-actions/v1/scaffolder", + "headers": { + "Authorization": "Bearer ${MCP_TOKEN}" + } + } + } +} +``` + +## Metrics + +The MCP Actions Backend emits metrics for the following operations: + +- `mcp.server.operation.duration`: The duration taken to process an individual MCP operation +- `mcp.server.session.duration`: The duration of the MCP session from the perspective of the server + ## Development This plugin backend can be started in a standalone mode from directly in this package with `yarn start`. It is a limited setup that is most convenient when developing the plugin backend itself. diff --git a/plugins/mcp-actions-backend/config.d.ts b/plugins/mcp-actions-backend/config.d.ts new file mode 100644 index 0000000000..a530f5b038 --- /dev/null +++ b/plugins/mcp-actions-backend/config.d.ts @@ -0,0 +1,75 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface Config { + mcpActions?: { + /** + * Display name for the MCP server. Defaults to "backstage". + * Used when running a single bundled server without mcpActions.servers. + */ + name?: string; + + /** + * Description of the MCP server. + * Used when running a single bundled server without mcpActions.servers. + */ + description?: string; + + /** + * When true, MCP tool names include the plugin ID prefix to avoid + * collisions across plugins. For example an action registered as + * "get-entity" by the catalog plugin becomes "catalog.get-entity". + * Defaults to true. + */ + namespacedToolNames?: boolean; + + /** + * Named MCP servers, each exposed at /api/mcp-actions/v1/{key}. + * When not configured, the plugin serves a single server at /api/mcp-actions/v1. + */ + servers?: { + [serverKey: string]: { + /** Display name for the MCP server. */ + name: string; + /** Description of the MCP server. */ + description?: string; + /** Filter rules to include or exclude specific actions. */ + filter?: { + include?: Array<{ + /** Glob pattern matched against action ID. */ + id?: string; + /** Match actions by their attribute flags. */ + attributes?: { + destructive?: boolean; + readOnly?: boolean; + idempotent?: boolean; + }; + }>; + exclude?: Array<{ + /** Glob pattern matched against action ID. */ + id?: string; + /** Match actions by their attribute flags. */ + attributes?: { + destructive?: boolean; + readOnly?: boolean; + idempotent?: boolean; + }; + }>; + }; + }; + }; + }; +} diff --git a/plugins/mcp-actions-backend/knip-report.md b/plugins/mcp-actions-backend/knip-report.md index 79729ec010..01afc5a780 100644 --- a/plugins/mcp-actions-backend/knip-report.md +++ b/plugins/mcp-actions-backend/knip-report.md @@ -1,9 +1,8 @@ # Knip report -## Unused dependencies (2) +## Unused dependencies (1) -| Name | Location | Severity | -| :------------------------ | :----------- | :------- | -| @backstage/catalog-client | plugins/mcp-actions-backend/package.json | error | -| zod | plugins/mcp-actions-backend/package.json | error | +| Name | Location | Severity | +| :------------------------ | :---------------- | :------- | +| @backstage/catalog-client | package.json:40:6 | error | diff --git a/plugins/mcp-actions-backend/package.json b/plugins/mcp-actions-backend/package.json index 074c2f970c..ffc4d0da70 100644 --- a/plugins/mcp-actions-backend/package.json +++ b/plugins/mcp-actions-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-mcp-actions-backend", - "version": "0.1.5-next.1", + "version": "0.1.12", "backstage": { "role": "backend-plugin", "pluginId": "mcp-actions", @@ -21,8 +21,10 @@ "license": "Apache-2.0", "main": "src/index.ts", "types": "src/index.ts", + "configSchema": "config.d.ts", "files": [ - "dist" + "dist", + "config.d.ts" ], "scripts": { "build": "backstage-cli package build", @@ -34,20 +36,25 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/backend-defaults": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/catalog-client": "workspace:^", + "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-catalog-node": "workspace:^", "@backstage/types": "workspace:^", - "@modelcontextprotocol/sdk": "^1.12.3", - "express": "^4.17.1", + "@cfworker/json-schema": "^4.1.1", + "@modelcontextprotocol/sdk": "^1.25.2", + "express": "^4.22.0", "express-promise-router": "^4.1.0", - "zod": "^3.22.4" + "minimatch": "^10.2.1", + "zod": "^3.25.76 || ^4.0.0" }, "devDependencies": { + "@backstage/backend-defaults": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "@types/express": "^4.17.6" + "@types/express": "^4.17.6", + "@types/supertest": "^2.0.8", + "supertest": "^7.0.0" } } diff --git a/plugins/mcp-actions-backend/src/config.ts b/plugins/mcp-actions-backend/src/config.ts new file mode 100644 index 0000000000..5ff2a442bb --- /dev/null +++ b/plugins/mcp-actions-backend/src/config.ts @@ -0,0 +1,97 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Config } from '@backstage/config'; +import { Minimatch } from 'minimatch'; + +export type FilterRule = { + idMatcher?: Minimatch; + attributes?: Partial< + Record<'destructive' | 'readOnly' | 'idempotent', boolean> + >; +}; + +export type McpServerConfig = { + name: string; + description?: string; + includeRules: FilterRule[]; + excludeRules: FilterRule[]; +}; + +const SERVER_KEY_PATTERN = /^[a-z0-9][a-z0-9-]*$/; + +export function parseFilterRules(configArray: Config[]): FilterRule[] { + return configArray.map(ruleConfig => { + const idPattern = ruleConfig.getOptionalString('id'); + const attributesConfig = ruleConfig.getOptionalConfig('attributes'); + + const rule: FilterRule = {}; + + if (idPattern) { + rule.idMatcher = new Minimatch(idPattern); + } + + if (attributesConfig) { + rule.attributes = {}; + for (const key of ['destructive', 'readOnly', 'idempotent'] as const) { + const value = attributesConfig.getOptionalBoolean(key); + if (value !== undefined) { + rule.attributes[key] = value; + } + } + } + + return rule; + }); +} + +export function parseServerConfigs( + config: Config, +): Map | undefined { + const serversConfig = config.getOptionalConfig('mcpActions.servers'); + if (!serversConfig) { + return undefined; + } + + const servers = new Map(); + + for (const key of serversConfig.keys()) { + if (!SERVER_KEY_PATTERN.test(key)) { + throw new Error( + `Invalid MCP server key "${key}": must be lowercase alphanumeric with hyphens`, + ); + } + + const serverConfig = serversConfig.getConfig(key); + + const filterConfig = serverConfig.getOptionalConfig('filter'); + const includeRules = parseFilterRules( + filterConfig?.getOptionalConfigArray('include') ?? [], + ); + const excludeRules = parseFilterRules( + filterConfig?.getOptionalConfigArray('exclude') ?? [], + ); + + servers.set(key, { + name: serverConfig.getString('name'), + description: serverConfig.getOptionalString('description'), + includeRules, + excludeRules, + }); + } + + return servers; +} diff --git a/plugins/mcp-actions-backend/src/metrics.ts b/plugins/mcp-actions-backend/src/metrics.ts new file mode 100644 index 0000000000..ddaa75edbd --- /dev/null +++ b/plugins/mcp-actions-backend/src/metrics.ts @@ -0,0 +1,73 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { MetricAttributes } from '@backstage/backend-plugin-api/alpha'; + +/** + * Attributes for mcp.server.operation.duration + * Following OTel requirement levels from the spec + * + * @see https://opentelemetry.io/docs/specs/semconv/gen-ai/mcp/#metric-mcpserveroperationduration + */ +export interface McpServerOperationAttributes extends MetricAttributes { + // Required + 'mcp.method.name': string; + + // Conditionally Required + 'error.type'?: string; + 'gen_ai.tool.name'?: string; + 'gen_ai.prompt.name'?: string; + 'mcp.resource.uri'?: string; + 'rpc.response.status_code'?: string; + + // Recommended + 'gen_ai.operation.name'?: 'execute_tool'; + 'mcp.protocol.version'?: string; + 'mcp.session.id'?: string; + 'network.transport'?: 'tcp' | 'quic' | 'pipe' | 'unix'; + 'network.protocol.name'?: string; + 'network.protocol.version'?: string; +} + +/** + * Attributes for mcp.server.session.duration + * Following OTel requirement levels from the spec + * + * @see https://opentelemetry.io/docs/specs/semconv/gen-ai/mcp/#metric-mcpserversessionduration + */ +export interface McpServerSessionAttributes extends MetricAttributes { + // Conditionally Required + 'error.type'?: string; + + // Recommended + 'mcp.protocol.version'?: string; + 'network.transport'?: 'tcp' | 'quic' | 'pipe' | 'unix'; + 'network.protocol.name'?: string; + 'network.protocol.version'?: string; +} + +/** + * OTel recommended bucket boundaries for MCP metrics + * + * @remarks + * + * Based on the MCP metrics defined in the OTel semantic conventions v1.39.0 + * @see https://opentelemetry.io/docs/specs/semconv/gen-ai/mcp/ + * + */ +export const bucketBoundaries = [ + 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 30, 60, 120, 300, +]; diff --git a/plugins/mcp-actions-backend/src/plugin.test.ts b/plugins/mcp-actions-backend/src/plugin.test.ts index ace988cfc1..31dce6bad5 100644 --- a/plugins/mcp-actions-backend/src/plugin.test.ts +++ b/plugins/mcp-actions-backend/src/plugin.test.ts @@ -14,13 +14,15 @@ * limitations under the License. */ import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; +import { metricsServiceMock } from '@backstage/backend-test-utils/alpha'; import { mcpPlugin } from './plugin'; import { actionsRegistryServiceRef } from '@backstage/backend-plugin-api/alpha'; import { createBackendPlugin } from '@backstage/backend-plugin-api'; import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'; -import { ListToolsResultSchema } from '@modelcontextprotocol/sdk/types'; +import { ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js'; +import request from 'supertest'; describe('Mcp Backend', () => { const mockPluginWithActions = createBackendPlugin({ @@ -51,6 +53,7 @@ describe('Mcp Backend', () => { features: [ mcpPlugin, mockPluginWithActions, + metricsServiceMock.mock().factory, mockServices.rootConfig.factory({ data: { backend: { @@ -115,7 +118,7 @@ describe('Mcp Backend', () => { required: ['name'], type: 'object', }, - name: 'make-greeting', + name: 'local.make-greeting', }, ]); }); @@ -158,8 +161,281 @@ describe('Mcp Backend', () => { required: ['name'], type: 'object', }, - name: 'make-greeting', + name: 'local.make-greeting', }, ]); }); + + describe('multi-server routing', () => { + const mockCatalogPlugin = createBackendPlugin({ + pluginId: 'catalog-actions', + register({ registerInit }) { + registerInit({ + deps: { actionsRegistry: actionsRegistryServiceRef }, + async init({ actionsRegistry }) { + actionsRegistry.register({ + name: 'get-entity', + title: 'Get Entity', + description: 'Fetch an entity', + schema: { + input: z => z.object({ name: z.string() }), + output: z => z.object({ entity: z.string() }), + }, + action: async ({ input }) => ({ + output: { entity: input.name }, + }), + }); + }, + }); + }, + }); + + const mockScaffolderPlugin = createBackendPlugin({ + pluginId: 'scaffolder-actions', + register({ registerInit }) { + registerInit({ + deps: { actionsRegistry: actionsRegistryServiceRef }, + async init({ actionsRegistry }) { + actionsRegistry.register({ + name: 'create-app', + title: 'Create App', + description: 'Create an app from template', + schema: { + input: z => z.object({ template: z.string() }), + output: z => z.object({ name: z.string() }), + }, + action: async ({ input }) => ({ + output: { name: input.template }, + }), + }); + }, + }); + }, + }); + + it('should route to per-server endpoints when mcpActions.servers is configured', async () => { + const { server } = await startTestBackend({ + features: [ + mcpPlugin, + mockCatalogPlugin, + mockScaffolderPlugin, + metricsServiceMock.mock().factory, + mockServices.rootConfig.factory({ + data: { + backend: { + actions: { + pluginSources: ['catalog-actions', 'scaffolder-actions'], + }, + }, + mcpActions: { + servers: { + catalog: { + name: 'Catalog Server', + filter: { + include: [{ id: 'catalog-actions:*' }], + }, + }, + scaffolder: { + name: 'Scaffolder Server', + filter: { + include: [{ id: 'scaffolder-actions:*' }], + }, + }, + }, + }, + }, + }), + ], + }); + + const address = server.address(); + if (typeof address !== 'object' || !('port' in address!)) { + throw new Error('server broke'); + } + const serverAddress = `http://localhost:${address.port}`; + + const catalogClient = new Client({ name: 'test', version: '1.0' }); + const catalogTransport = new StreamableHTTPClientTransport( + new URL(`${serverAddress}/api/mcp-actions/v1/catalog`), + ); + await catalogClient.connect(catalogTransport); + const catalogResult = await catalogClient.request( + { method: 'tools/list' }, + ListToolsResultSchema, + ); + expect(catalogResult.tools).toHaveLength(1); + expect(catalogResult.tools[0].name).toBe('catalog-actions.get-entity'); + + const scaffolderClient = new Client({ name: 'test', version: '1.0' }); + const scaffolderTransport = new StreamableHTTPClientTransport( + new URL(`${serverAddress}/api/mcp-actions/v1/scaffolder`), + ); + await scaffolderClient.connect(scaffolderTransport); + const scaffolderResult = await scaffolderClient.request( + { method: 'tools/list' }, + ListToolsResultSchema, + ); + expect(scaffolderResult.tools).toHaveLength(1); + expect(scaffolderResult.tools[0].name).toBe( + 'scaffolder-actions.create-app', + ); + }); + }); + + describe('OAuth well-known endpoints', () => { + it('should not expose oauth endpoints when neither DCR nor CIMD is enabled', async () => { + const { server } = await startTestBackend({ + features: [ + mcpPlugin, + mockPluginWithActions, + mockServices.rootConfig.factory({ + data: { + backend: { + actions: { + pluginSources: ['local'], + }, + }, + }, + }), + ], + }); + + const response = await request(server).get( + '/.well-known/oauth-protected-resource', + ); + expect(response.status).toBe(404); + }); + + it('should expose default oauth-protected-resource when DCR is enabled', async () => { + const mockExternalBaseUrl = 'http://external.local:0/api'; + const mockDiscovery = mockServices.discovery.mock({ + getExternalBaseUrl: async pluginId => + `${mockExternalBaseUrl}/${pluginId}`, + }); + + const { server } = await startTestBackend({ + features: [ + mcpPlugin, + mockPluginWithActions, + mockDiscovery.factory, + mockServices.rootConfig.factory({ + data: { + backend: { + actions: { + pluginSources: ['local'], + }, + }, + auth: { + experimentalDynamicClientRegistration: { + enabled: true, + }, + }, + }, + }), + ], + }); + + const response = await request(server).get( + '/.well-known/oauth-protected-resource/api/mcp-actions/v1', + ); + expect(response.status).toBe(200); + expect(response.body.resource).toMatch(/\/api\/mcp-actions\/v1$/); + expect(response.body.authorization_servers).toHaveLength(1); + expect(response.body.authorization_servers[0]).toMatch(/\/api\/auth$/); + expect(response.body.resource).toContain(`${mockExternalBaseUrl}`); + expect(response.body.authorization_servers[0]).toContain( + `${mockExternalBaseUrl}/`, + ); + }); + + const pathTestCases = [ + { name: 'auth', suffix: '/v1/auth' }, + { name: 'catalog', suffix: '/v1/catalog' }, + { name: 'scaffolder', suffix: '/v1/scaffolder' }, + ]; + + it.each(pathTestCases)( + 'should expose dynamic oauth-protected-resource for $name', + async ({ suffix }) => { + const mockExternalBaseUrl = 'http://external.local:0/api'; + const mockDiscovery = mockServices.discovery.mock({ + getExternalBaseUrl: async pluginId => + `${mockExternalBaseUrl}/${pluginId}`, + }); + + const { server } = await startTestBackend({ + features: [ + mcpPlugin, + mockPluginWithActions, + mockDiscovery.factory, + mockServices.rootConfig.factory({ + data: { + backend: { + actions: { + pluginSources: ['local'], + }, + }, + auth: { + experimentalDynamicClientRegistration: { + enabled: true, + }, + }, + mcpActions: { + servers: { + auth: { name: 'Auth', filter: { include: [] } }, + catalog: { name: 'Catalog', filter: { include: [] } }, + scaffolder: { name: 'Scaffolder', filter: { include: [] } }, + }, + }, + }, + }), + ], + }); + + const response = await request(server).get( + `/.well-known/oauth-protected-resource/api/mcp-actions${suffix}`, + ); + expect(response.status).toBe(200); + const expectedResourceRegex = new RegExp(`/api/mcp-actions${suffix}$`); + expect(response.body.resource).toMatch(expectedResourceRegex); + expect(response.body.authorization_servers).toHaveLength(1); + expect(response.body.authorization_servers[0]).toMatch(/\/api\/auth$/); + expect(response.body.resource).toContain(`${mockExternalBaseUrl}`); + expect(response.body.authorization_servers[0]).toContain( + `${mockExternalBaseUrl}/`, + ); + }, + ); + + it('should expose oauth-protected-resource when CIMD is enabled', async () => { + const { server } = await startTestBackend({ + features: [ + mcpPlugin, + mockPluginWithActions, + mockServices.rootConfig.factory({ + data: { + backend: { + actions: { + pluginSources: ['local'], + }, + }, + auth: { + experimentalClientIdMetadataDocuments: { + enabled: true, + }, + }, + }, + }), + ], + }); + + const response = await request(server).get( + '/.well-known/oauth-protected-resource/api/mcp-actions/v1', + ); + expect(response.status).toBe(200); + expect(response.body.resource).toMatch(/\/api\/mcp-actions\/v1$/); + expect(response.body.authorization_servers).toHaveLength(1); + expect(response.body.authorization_servers[0]).toMatch(/\/api\/auth$/); + }); + }); }); diff --git a/plugins/mcp-actions-backend/src/plugin.ts b/plugins/mcp-actions-backend/src/plugin.ts index d04df6cdf5..6baaf8489d 100644 --- a/plugins/mcp-actions-backend/src/plugin.ts +++ b/plugins/mcp-actions-backend/src/plugin.ts @@ -25,7 +25,9 @@ import { createSseRouter } from './routers/createSseRouter'; import { actionsRegistryServiceRef, actionsServiceRef, + metricsServiceRef, } from '@backstage/backend-plugin-api/alpha'; +import { parseServerConfigs } from './config'; /** * mcpPlugin backend plugin @@ -46,6 +48,7 @@ export const mcpPlugin = createBackendPlugin({ rootRouter: coreServices.rootHttpRouter, discovery: coreServices.discovery, config: coreServices.rootConfig, + metrics: metricsServiceRef, }, async init({ actions, @@ -55,37 +58,74 @@ export const mcpPlugin = createBackendPlugin({ rootRouter, discovery, config, + metrics, }) { + const serverConfigs = parseServerConfigs(config); + const namespacedToolNames = config.getOptionalBoolean( + 'mcpActions.namespacedToolNames', + ); + const mcpService = await McpService.create({ actions, - }); - - const sseRouter = createSseRouter({ - mcpService, - httpAuth, - }); - - const streamableRouter = createStreamableRouter({ - mcpService, - httpAuth, - logger, + metrics, + namespacedToolNames, }); const router = Router(); router.use(json()); - router.use('/v1/sse', sseRouter); - router.use('/v1', streamableRouter); + if (serverConfigs && serverConfigs.size > 0) { + for (const [key, serverConfig] of serverConfigs) { + const streamableRouter = createStreamableRouter({ + mcpService, + httpAuth, + logger, + metrics, + serverConfig, + }); + + router.use(`/v1/${key}`, streamableRouter); + } + } else { + const serverConfig = { + name: config.getOptionalString('mcpActions.name') ?? 'backstage', + description: config.getOptionalString('mcpActions.description'), + includeRules: [], + excludeRules: [], + }; + + const sseRouter = createSseRouter({ + mcpService, + httpAuth, + serverConfig, + }); + + const streamableRouter = createStreamableRouter({ + mcpService, + httpAuth, + logger, + metrics, + serverConfig, + }); + + router.use('/v1/sse', sseRouter); + router.use('/v1', streamableRouter); + } httpRouter.use(router); - if ( + const oauthEnabled = config.getOptionalBoolean( 'auth.experimentalDynamicClientRegistration.enabled', - ) - ) { + ) || + config.getOptionalBoolean( + 'auth.experimentalClientIdMetadataDocuments.enabled', + ); + + if (oauthEnabled) { + // OAuth Authorization Server Metadata (RFC 8414) // This should be replaced with throwing a WWW-Authenticate header, but that doesn't seem to be supported by - // many of the MCP client as of yet. So this seems to be the oldest version of the spec thats implemented. + // many of the MCP clients as of yet. So this seems to be the oldest version of the spec that's implemented. rootRouter.use( '/.well-known/oauth-authorization-server', async (_, res) => { @@ -93,10 +133,35 @@ export const mcpPlugin = createBackendPlugin({ const oidcResponse = await fetch( `${authBaseUrl}/.well-known/openid-configuration`, ); - res.json(await oidcResponse.json()); }, ); + + // Protected Resource Metadata (RFC 9728) + // https://datatracker.ietf.org/doc/html/rfc9728 + // This allows MCP clients to discover the authorization server for this resource + const serverSuffixes = serverConfigs?.size + ? [...serverConfigs.keys()].map(key => `/v1/${key}`) + : ['/v1']; + + for (const suffix of serverSuffixes) { + const mcpBasePath = `/api/mcp-actions${suffix}`; + + rootRouter.use( + `/.well-known/oauth-protected-resource${mcpBasePath}`, + async (_req, res) => { + const [authBaseUrl, mcpBaseUrl] = await Promise.all([ + discovery.getExternalBaseUrl('auth'), + discovery.getExternalBaseUrl('mcp-actions'), + ]); + + res.json({ + resource: `${mcpBaseUrl}${suffix}`, + authorization_servers: [authBaseUrl], + }); + }, + ); + } } }, }); diff --git a/plugins/mcp-actions-backend/src/routers/createSseRouter.ts b/plugins/mcp-actions-backend/src/routers/createSseRouter.ts index 19bcf288e9..954d28af4e 100644 --- a/plugins/mcp-actions-backend/src/routers/createSseRouter.ts +++ b/plugins/mcp-actions-backend/src/routers/createSseRouter.ts @@ -18,6 +18,7 @@ import { Router } from 'express'; import { McpService } from '../services/McpService'; import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; import { HttpAuthService } from '@backstage/backend-plugin-api'; +import { McpServerConfig } from '../config'; /** * Legacy SSE endpoint for older clients, hopefully will not be needed for much longer. @@ -25,9 +26,11 @@ import { HttpAuthService } from '@backstage/backend-plugin-api'; export const createSseRouter = ({ mcpService, httpAuth, + serverConfig, }: { mcpService: McpService; httpAuth: HttpAuthService; + serverConfig?: McpServerConfig; }): Router => { const router = PromiseRouter(); const transportsToSessionId = new Map(); @@ -35,6 +38,7 @@ export const createSseRouter = ({ router.get('/', async (req, res) => { const server = mcpService.getServer({ credentials: await httpAuth.credentials(req), + serverConfig, }); const transport = new SSEServerTransport( diff --git a/plugins/mcp-actions-backend/src/routers/createStreamableRouter.ts b/plugins/mcp-actions-backend/src/routers/createStreamableRouter.ts index 9d6fd1840b..517aa18824 100644 --- a/plugins/mcp-actions-backend/src/routers/createStreamableRouter.ts +++ b/plugins/mcp-actions-backend/src/routers/createStreamableRouter.ts @@ -15,26 +15,54 @@ */ import PromiseRouter from 'express-promise-router'; import { Router } from 'express'; +import { performance } from 'node:perf_hooks'; import { McpService } from '../services/McpService'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; +import { LATEST_PROTOCOL_VERSION } from '@modelcontextprotocol/sdk/types.js'; import { HttpAuthService, LoggerService } from '@backstage/backend-plugin-api'; -import { isError } from '@backstage/errors'; +import { toError } from '@backstage/errors'; +import { MetricsService } from '@backstage/backend-plugin-api/alpha'; +import { bucketBoundaries, McpServerSessionAttributes } from '../metrics'; +import { McpServerConfig } from '../config'; export const createStreamableRouter = ({ mcpService, httpAuth, logger, + metrics, + serverConfig, }: { mcpService: McpService; logger: LoggerService; httpAuth: HttpAuthService; + metrics: MetricsService; + serverConfig?: McpServerConfig; }): Router => { const router = PromiseRouter(); + const sessionDuration = metrics.createHistogram( + 'mcp.server.session.duration', + { + description: + 'The duration of the MCP session as observed on the MCP server', + unit: 's', + advice: { explicitBucketBoundaries: bucketBoundaries }, + }, + ); + router.post('/', async (req, res) => { + const sessionStart = performance.now(); + + const baseAttributes: McpServerSessionAttributes = { + 'mcp.protocol.version': LATEST_PROTOCOL_VERSION, + 'network.transport': 'tcp', + 'network.protocol.name': 'http', + }; + try { const server = mcpService.getServer({ credentials: await httpAuth.credentials(req), + serverConfig, }); const transport = new StreamableHTTPServerTransport({ @@ -49,11 +77,16 @@ export const createStreamableRouter = ({ res.on('close', () => { transport.close(); server.close(); + + const durationSeconds = (performance.now() - sessionStart) / 1000; + + sessionDuration.record(durationSeconds, baseAttributes); }); } catch (error) { - if (isError(error)) { - logger.error(error.message); - } + const err = toError(error); + const errorType = err.name; + + logger.error(err.message); if (!res.headersSent) { res.status(500).json({ @@ -65,6 +98,13 @@ export const createStreamableRouter = ({ id: null, }); } + + const durationSeconds = (performance.now() - sessionStart) / 1000; + + sessionDuration.record(durationSeconds, { + ...baseAttributes, + 'error.type': errorType, + }); } }); diff --git a/plugins/mcp-actions-backend/src/services/McpService.test.ts b/plugins/mcp-actions-backend/src/services/McpService.test.ts index cd1c98f5b1..1a7dc507c3 100644 --- a/plugins/mcp-actions-backend/src/services/McpService.test.ts +++ b/plugins/mcp-actions-backend/src/services/McpService.test.ts @@ -16,13 +16,20 @@ import { mockCredentials } from '@backstage/backend-test-utils'; import { McpService } from './McpService'; -import { actionsRegistryServiceMock } from '@backstage/backend-test-utils/alpha'; +import { + actionsRegistryServiceMock, + metricsServiceMock, +} from '@backstage/backend-test-utils/alpha'; import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js'; import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { CallToolResultSchema, ListToolsResultSchema, } from '@modelcontextprotocol/sdk/types.js'; +import { InputError, NotFoundError } from '@backstage/errors'; +import { McpServerConfig, parseFilterRules } from '../config'; +import { ActionsService } from '@backstage/backend-plugin-api/alpha'; +import { ConfigReader } from '@backstage/config'; describe('McpService', () => { it('should list the available actions as tools in the mcp backend', async () => { @@ -38,8 +45,10 @@ describe('McpService', () => { action: async () => ({ output: { output: 'test' } }), }); + const mockMetrics = metricsServiceMock.mock(); const mcpService = await McpService.create({ actions: mockActionsRegistry, + metrics: mockMetrics, }); const server = mcpService.getServer({ @@ -87,9 +96,63 @@ describe('McpService', () => { required: ['input'], type: 'object', }, - name: 'mock-action', + name: 'test.mock-action', }, ]); + + const histogram = mockMetrics.createHistogram.mock.results[0]?.value; + expect(histogram.record).toHaveBeenCalledTimes(1); + expect(histogram.record).toHaveBeenCalledWith( + expect.any(Number), + expect.objectContaining({ + 'mcp.method.name': 'tools/list', + }), + ); + expect(histogram.record.mock.calls[0][1]).not.toHaveProperty('error.type'); + }); + + it('should record metrics with error.type when tools/list fails', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + mockActionsRegistry.list = jest + .fn() + .mockRejectedValue(new Error('List failed')); + + const mockMetrics = metricsServiceMock.mock(); + const mcpService = await McpService.create({ + actions: mockActionsRegistry, + metrics: mockMetrics, + }); + + const server = mcpService.getServer({ + credentials: mockCredentials.user(), + }); + + const client = new Client({ + name: 'test client', + version: '1.0', + }); + + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + await expect( + client.request({ method: 'tools/list' }, ListToolsResultSchema), + ).rejects.toThrow(); + + const histogram = mockMetrics.createHistogram.mock.results[0]?.value; + expect(histogram.record).toHaveBeenCalledTimes(1); + expect(histogram.record).toHaveBeenCalledWith( + expect.any(Number), + expect.objectContaining({ + 'mcp.method.name': 'tools/list', + 'error.type': 'Error', + }), + ); }); it('should call the action when the tool is invoked', async () => { @@ -107,8 +170,10 @@ describe('McpService', () => { action: mockAction, }); + const mockMetrics = metricsServiceMock.mock(); const mcpService = await McpService.create({ actions: mockActionsRegistry, + metrics: mockMetrics, }); const server = mcpService.getServer({ @@ -131,7 +196,7 @@ describe('McpService', () => { const result = await client.request( { method: 'tools/call', - params: { name: 'mock-action', arguments: { input: 'test' } }, + params: { name: 'test.mock-action', arguments: { input: 'test' } }, }, CallToolResultSchema, ); @@ -154,11 +219,25 @@ describe('McpService', () => { ].join('\n'), }, ]); + + const histogram = mockMetrics.createHistogram.mock.results[0]?.value; + expect(histogram.record).toHaveBeenCalledTimes(1); + expect(histogram.record).toHaveBeenCalledWith( + expect.any(Number), + expect.objectContaining({ + 'mcp.method.name': 'tools/call', + 'gen_ai.tool.name': 'test.mock-action', + 'gen_ai.operation.name': 'execute_tool', + }), + ); + expect(histogram.record.mock.calls[0][1]).not.toHaveProperty('error.type'); }); it('should return an error when the action is not found', async () => { + const mockMetrics = metricsServiceMock.mock(); const mcpService = await McpService.create({ actions: actionsRegistryServiceMock(), + metrics: mockMetrics, }); const server = mcpService.getServer({ @@ -181,18 +260,651 @@ describe('McpService', () => { const result = await client.request( { method: 'tools/call', - params: { name: 'mock-action', arguments: { input: 'test' } }, + params: { name: 'nonexistent-action', arguments: { input: 'test' } }, }, CallToolResultSchema, ); await expect(result).toEqual({ content: [ { - text: expect.stringMatching('Action "mock-action" not found'), + text: expect.stringMatching('Action "nonexistent-action" not found'), type: 'text', }, ], isError: true, }); + + const histogram = mockMetrics.createHistogram.mock.results[0]?.value; + expect(histogram.record).toHaveBeenCalledTimes(1); + expect(histogram.record).toHaveBeenCalledWith( + expect.any(Number), + expect.objectContaining({ + 'mcp.method.name': 'tools/call', + 'gen_ai.tool.name': 'nonexistent-action', + 'gen_ai.operation.name': 'execute_tool', + 'error.type': 'tool_error', + }), + ); + }); + + it('should record metrics with error.type when tool invocation throws', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const customError = new Error('Action failed'); + customError.name = 'CustomError'; + mockActionsRegistry.register({ + name: 'failing-action', + title: 'Failing', + description: 'Fails', + schema: { + input: z => z.object({}), + output: z => z.object({}), + }, + action: jest.fn().mockRejectedValue(customError), + }); + + const mockMetrics = metricsServiceMock.mock(); + const mcpService = await McpService.create({ + actions: mockActionsRegistry, + metrics: mockMetrics, + }); + + const server = mcpService.getServer({ + credentials: mockCredentials.user(), + }); + + const client = new Client({ + name: 'test client', + version: '1.0', + }); + + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + await expect( + client.request( + { + method: 'tools/call', + params: { name: 'test.failing-action', arguments: {} }, + }, + CallToolResultSchema, + ), + ).rejects.toThrow('Action failed'); + + const histogram = mockMetrics.createHistogram.mock.results[0]?.value; + expect(histogram.record).toHaveBeenCalledTimes(1); + expect(histogram.record).toHaveBeenCalledWith( + expect.any(Number), + expect.objectContaining({ + 'mcp.method.name': 'tools/call', + 'gen_ai.tool.name': 'test.failing-action', + 'gen_ai.operation.name': 'execute_tool', + 'error.type': 'CustomError', + }), + ); + }); + + it('should forward the original InputError when an action throws one', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + mockActionsRegistry.register({ + name: 'failing-action', + title: 'Failing', + description: 'An action that throws InputError', + schema: { + input: z => z.object({ value: z.string() }), + output: z => z.object({}), + }, + action: async () => { + throw new InputError('the value was invalid'); + }, + }); + + const mcpService = await McpService.create({ + actions: mockActionsRegistry, + metrics: metricsServiceMock.mock(), + }); + + const server = mcpService.getServer({ + credentials: mockCredentials.user(), + }); + + const client = new Client({ + name: 'test client', + version: '1.0', + }); + + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + const result = await client.request( + { + method: 'tools/call', + params: { name: 'test.failing-action', arguments: { value: 'test' } }, + }, + CallToolResultSchema, + ); + + expect(result).toEqual({ + content: [ + { + type: 'text', + text: 'InputError: the value was invalid', + }, + ], + isError: true, + }); + }); + + it('should forward the original NotFoundError when an action throws one', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + mockActionsRegistry.register({ + name: 'not-found-action', + title: 'Not Found', + description: 'An action that throws NotFoundError', + schema: { + input: z => z.object({ id: z.string() }), + output: z => z.object({}), + }, + action: async () => { + throw new NotFoundError('entity does not exist'); + }, + }); + + const mcpService = await McpService.create({ + actions: mockActionsRegistry, + metrics: metricsServiceMock.mock(), + }); + + const server = mcpService.getServer({ + credentials: mockCredentials.user(), + }); + + const client = new Client({ + name: 'test client', + version: '1.0', + }); + + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + const result = await client.request( + { + method: 'tools/call', + params: { name: 'test.not-found-action', arguments: { id: 'abc' } }, + }, + CallToolResultSchema, + ); + + expect(result).toEqual({ + content: [ + { + type: 'text', + text: 'NotFoundError: entity does not exist', + }, + ], + isError: true, + }); + }); + + describe('per-server filtering', () => { + const fakeActions = [ + { + id: 'catalog:get-entity', + pluginId: 'catalog', + name: 'get-entity', + title: 'Get Entity', + description: 'Fetch an entity', + schema: { + input: { type: 'object' as const }, + output: { type: 'object' as const }, + }, + attributes: { destructive: false, readOnly: true, idempotent: true }, + }, + { + id: 'catalog:delete-entity', + pluginId: 'catalog', + name: 'delete-entity', + title: 'Delete Entity', + description: 'Delete an entity', + schema: { + input: { type: 'object' as const }, + output: { type: 'object' as const }, + }, + attributes: { destructive: true, readOnly: false, idempotent: false }, + }, + { + id: 'scaffolder:create-app', + pluginId: 'scaffolder', + name: 'create-app', + title: 'Create App', + description: 'Create an app', + schema: { + input: { type: 'object' as const }, + output: { type: 'object' as const }, + }, + attributes: { destructive: false, readOnly: false, idempotent: false }, + }, + ]; + + const fakeActionsService: ActionsService = { + list: jest.fn(async () => ({ actions: fakeActions })), + invoke: jest.fn(async () => ({ output: {} })), + }; + + it('should return all actions when no filter rules are set', async () => { + const mcpService = await McpService.create({ + actions: fakeActionsService, + metrics: metricsServiceMock.mock(), + }); + + const serverConfig: McpServerConfig = { + name: 'All Actions', + includeRules: [], + excludeRules: [], + }; + + const server = mcpService.getServer({ + credentials: mockCredentials.user(), + serverConfig, + }); + + const client = new Client({ name: 'test', version: '1.0' }); + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + const result = await client.request( + { method: 'tools/list' }, + ListToolsResultSchema, + ); + + expect(result.tools).toHaveLength(3); + }); + + it('should scope actions using include filter rules', async () => { + const mcpService = await McpService.create({ + actions: fakeActionsService, + metrics: metricsServiceMock.mock(), + }); + + const serverConfig: McpServerConfig = { + name: 'Catalog Only', + includeRules: parseFilterRules( + new ConfigReader({ + include: [{ id: 'catalog:*' }], + }).getConfigArray('include'), + ), + excludeRules: [], + }; + + const server = mcpService.getServer({ + credentials: mockCredentials.user(), + serverConfig, + }); + + const client = new Client({ name: 'test', version: '1.0' }); + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + const result = await client.request( + { method: 'tools/list' }, + ListToolsResultSchema, + ); + + expect(result.tools).toHaveLength(2); + expect(result.tools.map(t => t.name)).toEqual([ + 'catalog.get-entity', + 'catalog.delete-entity', + ]); + }); + + it('should apply exclude filter rules to remove destructive actions', async () => { + const mcpService = await McpService.create({ + actions: fakeActionsService, + metrics: metricsServiceMock.mock(), + }); + + const serverConfig: McpServerConfig = { + name: 'Catalog', + includeRules: parseFilterRules( + new ConfigReader({ + include: [{ id: 'catalog:*' }], + }).getConfigArray('include'), + ), + excludeRules: [{ attributes: { destructive: true } }], + }; + + const server = mcpService.getServer({ + credentials: mockCredentials.user(), + serverConfig, + }); + + const client = new Client({ name: 'test', version: '1.0' }); + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + const result = await client.request( + { method: 'tools/list' }, + ListToolsResultSchema, + ); + + expect(result.tools).toHaveLength(1); + expect(result.tools[0].name).toBe('catalog.get-entity'); + }); + + it('should apply include filter rules with glob patterns', async () => { + const mcpService = await McpService.create({ + actions: fakeActionsService, + metrics: metricsServiceMock.mock(), + }); + + const serverConfig: McpServerConfig = { + name: 'Catalog', + includeRules: parseFilterRules( + new ConfigReader({ + include: [{ id: 'catalog:get-*' }], + }).getConfigArray('include'), + ), + excludeRules: [], + }; + + const server = mcpService.getServer({ + credentials: mockCredentials.user(), + serverConfig, + }); + + const client = new Client({ name: 'test', version: '1.0' }); + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + const result = await client.request( + { method: 'tools/list' }, + ListToolsResultSchema, + ); + + expect(result.tools).toHaveLength(1); + expect(result.tools[0].name).toBe('catalog.get-entity'); + }); + + it('should reject tool calls for actions outside the filtered set', async () => { + const mcpService = await McpService.create({ + actions: fakeActionsService, + metrics: metricsServiceMock.mock(), + }); + + const serverConfig: McpServerConfig = { + name: 'Scaffolder', + includeRules: parseFilterRules( + new ConfigReader({ + include: [{ id: 'scaffolder:*' }], + }).getConfigArray('include'), + ), + excludeRules: [], + }; + + const server = mcpService.getServer({ + credentials: mockCredentials.user(), + serverConfig, + }); + + const client = new Client({ name: 'test', version: '1.0' }); + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + const result = await client.request( + { + method: 'tools/call', + params: { name: 'catalog.get-entity', arguments: {} }, + }, + CallToolResultSchema, + ); + + expect(result).toEqual({ + content: [ + { + type: 'text', + text: expect.stringContaining( + 'Action "catalog.get-entity" not found', + ), + }, + ], + isError: true, + }); + }); + }); + + describe('server name and description', () => { + it('should default server name to backstage when no config is provided', async () => { + const mcpService = await McpService.create({ + actions: actionsRegistryServiceMock(), + metrics: metricsServiceMock.mock(), + }); + + const server = mcpService.getServer({ + credentials: mockCredentials.user(), + }); + + const client = new Client({ name: 'test', version: '1.0' }); + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + const serverInfo = client.getServerVersion(); + expect(serverInfo?.name).toBe('backstage'); + expect(serverInfo?.description).toBeUndefined(); + }); + + it('should use name and description from server config', async () => { + const mcpService = await McpService.create({ + actions: actionsRegistryServiceMock(), + metrics: metricsServiceMock.mock(), + }); + + const server = mcpService.getServer({ + credentials: mockCredentials.user(), + serverConfig: { + name: 'My Custom Server', + description: 'A custom MCP server for testing', + includeRules: [], + excludeRules: [], + }, + }); + + const client = new Client({ name: 'test', version: '1.0' }); + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + const serverInfo = client.getServerVersion(); + expect(serverInfo?.name).toBe('My Custom Server'); + expect(serverInfo?.description).toBe('A custom MCP server for testing'); + }); + + it('should omit description when not provided in config', async () => { + const mcpService = await McpService.create({ + actions: actionsRegistryServiceMock(), + metrics: metricsServiceMock.mock(), + }); + + const server = mcpService.getServer({ + credentials: mockCredentials.user(), + serverConfig: { + name: 'Named Server', + includeRules: [], + excludeRules: [], + }, + }); + + const client = new Client({ name: 'test', version: '1.0' }); + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + const serverInfo = client.getServerVersion(); + expect(serverInfo?.name).toBe('Named Server'); + expect(serverInfo?.description).toBeUndefined(); + }); + }); + + describe('namespaced tool names', () => { + it('should use action ID as tool name by default', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + mockActionsRegistry.register({ + name: 'mock-action', + title: 'Test', + description: 'Test', + schema: { + input: z => z.object({}), + output: z => z.object({}), + }, + action: async () => ({ output: {} }), + }); + + const mcpService = await McpService.create({ + actions: mockActionsRegistry, + metrics: metricsServiceMock.mock(), + }); + + const server = mcpService.getServer({ + credentials: mockCredentials.user(), + }); + + const client = new Client({ name: 'test', version: '1.0' }); + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + const result = await client.request( + { method: 'tools/list' }, + ListToolsResultSchema, + ); + + expect(result.tools[0].name).toBe('test.mock-action'); + }); + + it('should use short action name when namespacing is disabled', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + mockActionsRegistry.register({ + name: 'mock-action', + title: 'Test', + description: 'Test', + schema: { + input: z => z.object({}), + output: z => z.object({}), + }, + action: async () => ({ output: {} }), + }); + + const mcpService = await McpService.create({ + actions: mockActionsRegistry, + metrics: metricsServiceMock.mock(), + namespacedToolNames: false, + }); + + const server = mcpService.getServer({ + credentials: mockCredentials.user(), + }); + + const client = new Client({ name: 'test', version: '1.0' }); + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + const result = await client.request( + { method: 'tools/list' }, + ListToolsResultSchema, + ); + + expect(result.tools[0].name).toBe('mock-action'); + }); + + it('should match tool calls using the namespaced name', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + mockActionsRegistry.register({ + name: 'mock-action', + title: 'Test', + description: 'Test', + schema: { + input: z => z.object({}), + output: z => z.object({}), + }, + action: async () => ({ output: {} }), + }); + + const mcpService = await McpService.create({ + actions: mockActionsRegistry, + metrics: metricsServiceMock.mock(), + }); + + const server = mcpService.getServer({ + credentials: mockCredentials.user(), + }); + + const client = new Client({ name: 'test', version: '1.0' }); + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + const result = await client.request( + { + method: 'tools/call', + params: { name: 'test.mock-action', arguments: {} }, + }, + CallToolResultSchema, + ); + + expect(result.isError).toBeUndefined(); + }); }); }); diff --git a/plugins/mcp-actions-backend/src/services/McpService.ts b/plugins/mcp-actions-backend/src/services/McpService.ts index c6cdf901d7..49eea11d72 100644 --- a/plugins/mcp-actions-backend/src/services/McpService.ts +++ b/plugins/mcp-actions-backend/src/services/McpService.ts @@ -20,87 +20,231 @@ import { CallToolRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { JsonObject } from '@backstage/types'; -import { ActionsService } from '@backstage/backend-plugin-api/alpha'; +import { + ActionsService, + ActionsServiceAction, + MetricsServiceHistogram, + MetricsService, +} from '@backstage/backend-plugin-api/alpha'; import { version } from '@backstage/plugin-mcp-actions-backend/package.json'; import { NotFoundError } from '@backstage/errors'; +import { performance } from 'node:perf_hooks'; import { handleErrors } from './handleErrors'; +import { bucketBoundaries, McpServerOperationAttributes } from '../metrics'; +import { FilterRule, McpServerConfig } from '../config'; export class McpService { private readonly actions: ActionsService; + private readonly namespacedToolNames: boolean; + private readonly operationDuration: MetricsServiceHistogram; - constructor(actions: ActionsService) { + constructor( + actions: ActionsService, + metrics: MetricsService, + namespacedToolNames?: boolean, + ) { this.actions = actions; + this.namespacedToolNames = namespacedToolNames ?? true; + this.operationDuration = + metrics.createHistogram( + 'mcp.server.operation.duration', + { + description: 'MCP request duration as observed on the receiver', + unit: 's', + advice: { explicitBucketBoundaries: bucketBoundaries }, + }, + ); } - static async create({ actions }: { actions: ActionsService }) { - return new McpService(actions); + static async create({ + actions, + metrics, + namespacedToolNames, + }: { + actions: ActionsService; + metrics: MetricsService; + namespacedToolNames?: boolean; + }) { + return new McpService(actions, metrics, namespacedToolNames); } - getServer({ credentials }: { credentials: BackstageCredentials }) { + getServer({ + credentials, + serverConfig, + }: { + credentials: BackstageCredentials; + serverConfig?: McpServerConfig; + }) { const server = new McpServer( { - name: 'backstage', + name: serverConfig?.name ?? 'backstage', // TODO: this version will most likely change in the future. version, + ...(serverConfig?.description && { + description: serverConfig.description, + }), }, { capabilities: { tools: {} } }, ); server.setRequestHandler(ListToolsRequestSchema, async () => { - // TODO: switch this to be configuration based later - const { actions } = await this.actions.list({ credentials }); + const startTime = performance.now(); + let errorType: string | undefined; - return { - tools: actions.map(action => ({ - inputSchema: action.schema.input, - // todo(blam): this is unfortunately not supported by most clients yet. - // When this is provided you need to provide structuredContent instead. - // outputSchema: action.schema.output, - name: action.name, - description: action.description, - annotations: { - title: action.title, - destructiveHint: action.attributes.destructive, - idempotentHint: action.attributes.idempotent, - readOnlyHint: action.attributes.readOnly, - openWorldHint: false, - }, - })), - }; + try { + const { actions: allActions } = await this.actions.list({ + credentials, + }); + const actions = serverConfig + ? this.filterActions(allActions, serverConfig) + : allActions; + + return { + tools: actions.map(action => ({ + inputSchema: action.schema.input, + // todo(blam): this is unfortunately not supported by most clients yet. + // When this is provided you need to provide structuredContent instead. + // outputSchema: action.schema.output, + name: this.getToolName(action), + description: action.description, + annotations: { + title: action.title, + destructiveHint: action.attributes.destructive, + idempotentHint: action.attributes.idempotent, + readOnlyHint: action.attributes.readOnly, + openWorldHint: false, + }, + })), + }; + } catch (err) { + errorType = err instanceof Error ? err.name : 'Error'; + throw err; + } finally { + const durationSeconds = (performance.now() - startTime) / 1000; + + this.operationDuration.record(durationSeconds, { + 'mcp.method.name': 'tools/list', + ...(errorType && { 'error.type': errorType }), + }); + } }); server.setRequestHandler(CallToolRequestSchema, async ({ params }) => { - return handleErrors(async () => { - const { actions } = await this.actions.list({ credentials }); - const action = actions.find(a => a.name === params.name); + const startTime = performance.now(); + let errorType: string | undefined; + let isError = false; - if (!action) { - throw new NotFoundError(`Action "${params.name}" not found`); - } + try { + const result = await handleErrors(async () => { + const { actions: allActions } = await this.actions.list({ + credentials, + }); + const actions = serverConfig + ? this.filterActions(allActions, serverConfig) + : allActions; - const { output } = await this.actions.invoke({ - id: action.id, - input: params.arguments as JsonObject, - credentials, + const action = actions.find(a => this.getToolName(a) === params.name); + + if (!action) { + throw new NotFoundError(`Action "${params.name}" not found`); + } + + const { output } = await this.actions.invoke({ + id: action.id, + input: params.arguments as JsonObject, + credentials, + }); + + return { + // todo(blam): unfortunately structuredContent is not supported by most clients yet. + // so the validation for the output happens in the default actions registry + // and we return it as json text instead for now. + content: [ + { + type: 'text', + text: ['```json', JSON.stringify(output, null, 2), '```'].join( + '\n', + ), + }, + ], + }; }); - return { - // todo(blam): unfortunately structuredContent is not supported by most clients yet. - // so the validation for the output happens in the default actions registry - // and we return it as json text instead for now. - content: [ - { - type: 'text', - text: ['```json', JSON.stringify(output, null, 2), '```'].join( - '\n', - ), - }, - ], - }; - }); + isError = !!(result as { isError?: boolean })?.isError; + return result; + } catch (err) { + errorType = err instanceof Error ? err.name : 'Error'; + throw err; + } finally { + const durationSeconds = (performance.now() - startTime) / 1000; + + // Determine error.type per OTel MCP spec: + // - Thrown exceptions use the error name + // - CallToolResult with isError=true uses 'tool_error' + let errorAttribute: string | undefined = errorType; + if (!errorAttribute && isError) { + errorAttribute = 'tool_error'; + } + + this.operationDuration.record(durationSeconds, { + 'mcp.method.name': 'tools/call', + 'gen_ai.tool.name': params.name, + 'gen_ai.operation.name': 'execute_tool', + ...(errorAttribute && { 'error.type': errorAttribute }), + }); + } }); return server; } + + private filterActions( + actions: ActionsServiceAction[], + serverConfig: McpServerConfig, + ): ActionsServiceAction[] { + const { includeRules, excludeRules } = serverConfig; + if (includeRules.length === 0 && excludeRules.length === 0) { + return actions; + } + + return actions.filter(action => { + if (excludeRules.some(rule => this.matchesRule(action, rule))) { + return false; + } + + if (includeRules.length === 0) { + return true; + } + + return includeRules.some(rule => this.matchesRule(action, rule)); + }); + } + + private getToolName(action: ActionsServiceAction): string { + if (this.namespacedToolNames) { + return `${action.pluginId}.${action.name}`; + } + return action.name; + } + + private matchesRule(action: ActionsServiceAction, rule: FilterRule): boolean { + if (rule.idMatcher && !rule.idMatcher.match(action.id)) { + return false; + } + + if (rule.attributes) { + for (const [key, value] of Object.entries(rule.attributes)) { + if ( + action.attributes[ + key as 'destructive' | 'readOnly' | 'idempotent' + ] !== value + ) { + return false; + } + } + } + + return true; + } } diff --git a/plugins/mcp-actions-backend/src/services/handleErrors.test.ts b/plugins/mcp-actions-backend/src/services/handleErrors.test.ts new file mode 100644 index 0000000000..17fe6de0a6 --- /dev/null +++ b/plugins/mcp-actions-backend/src/services/handleErrors.test.ts @@ -0,0 +1,144 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + InputError, + NotFoundError, + NotAllowedError, + ForwardedError, + ResponseError, +} from '@backstage/errors'; +import { handleErrors } from './handleErrors'; + +describe('handleErrors', () => { + it('should return the error description for a known error', async () => { + const result = await handleErrors(async () => { + throw new InputError('bad input'); + }); + + expect(result).toEqual({ + content: [{ type: 'text', text: 'InputError: bad input' }], + isError: true, + }); + }); + + it('should return the error description for a NotFoundError', async () => { + const result = await handleErrors(async () => { + throw new NotFoundError('entity not found'); + }); + + expect(result).toEqual({ + content: [{ type: 'text', text: 'NotFoundError: entity not found' }], + isError: true, + }); + }); + + it('should return the error description for a NotAllowedError', async () => { + const result = await handleErrors(async () => { + throw new NotAllowedError('forbidden'); + }); + + expect(result).toEqual({ + content: [{ type: 'text', text: 'NotAllowedError: forbidden' }], + isError: true, + }); + }); + + it('should rethrow an unknown error', async () => { + await expect( + handleErrors(async () => { + throw new Error('unknown problem'); + }), + ).rejects.toThrow('unknown problem'); + }); + + it('should handle a ForwardedError that inherits the cause name', async () => { + const result = await handleErrors(async () => { + throw new ForwardedError( + 'wrapper message', + new InputError('original error'), + ); + }); + + // ForwardedError inherits name from cause and CustomErrorBase + // concatenates the cause message into the full message + expect(result).toEqual({ + content: [ + { + type: 'text', + text: 'InputError: wrapper message; caused by InputError: original error', + }, + ], + isError: true, + }); + }); + + it('should extract the cause from a ResponseError', async () => { + const response = { + ok: false, + status: 400, + statusText: 'Bad Request', + headers: new Headers({ 'content-type': 'application/json' }), + text: async () => + JSON.stringify({ + error: { name: 'InputError', message: 'bad value' }, + response: { statusCode: 400 }, + }), + }; + + const responseError = await ResponseError.fromResponse(response as any); + + const result = await handleErrors(async () => { + throw responseError; + }); + + expect(result).toEqual({ + content: [{ type: 'text', text: 'InputError: bad value' }], + isError: true, + }); + }); + + it('should recursively extract through nested ResponseErrors', async () => { + const innerResponse = { + ok: false, + status: 400, + statusText: 'Bad Request', + headers: new Headers({ 'content-type': 'application/json' }), + text: async () => + JSON.stringify({ + error: { + name: 'ResponseError', + message: 'Request failed with 400 Bad Request', + cause: { name: 'InputError', message: 'deeply nested error' }, + }, + response: { statusCode: 400 }, + }), + }; + + const responseError = await ResponseError.fromResponse( + innerResponse as any, + ); + + const result = await handleErrors(async () => { + throw responseError; + }); + + expect(result).toEqual({ + content: [{ type: 'text', text: 'InputError: deeply nested error' }], + isError: true, + }); + }); +}); diff --git a/plugins/mcp-actions-backend/src/services/handleErrors.ts b/plugins/mcp-actions-backend/src/services/handleErrors.ts index 67d92664eb..97070bb723 100644 --- a/plugins/mcp-actions-backend/src/services/handleErrors.ts +++ b/plugins/mcp-actions-backend/src/services/handleErrors.ts @@ -32,16 +32,17 @@ const knownErrors = new Set([ 'NotModifiedError', 'NotImplementedError', 'ResponseError', + 'ServiceUnavailableError', ]); -// Extracts the cause error, if the provided error is `ResponseError` or -// `ForwardedError` with a cause. +// Recursively extracts the innermost cause from ResponseError or +// ForwardedError wrappers to surface the original error. function extractCause(err: ErrorLike): ErrorLike { if ( (err.name === 'ResponseError' || err instanceof ForwardedError) && isError(err.cause) ) { - return err.cause; + return extractCause(err.cause); } return err; } diff --git a/plugins/mui-to-bui/CHANGELOG.md b/plugins/mui-to-bui/CHANGELOG.md index 69aee6adb3..24c4004a4f 100644 --- a/plugins/mui-to-bui/CHANGELOG.md +++ b/plugins/mui-to-bui/CHANGELOG.md @@ -1,5 +1,208 @@ # @backstage/plugin-mui-to-bui +## 0.2.6 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0 + - @backstage/theme@0.7.3 + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/core-plugin-api@1.12.5 + +## 0.2.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.2 + - @backstage/theme@0.7.3-next.0 + - @backstage/core-plugin-api@1.12.5-next.2 + - @backstage/frontend-plugin-api@0.16.0-next.2 + +## 0.2.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-plugin-api@1.12.5-next.1 + +## 0.2.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.0 + - @backstage/frontend-plugin-api@0.15.2-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + - @backstage/theme@0.7.2 + +## 0.2.5 + +### Patch Changes + +- 538c985: Updated installation documentation to use feature discovery as the default. +- ad7c883: Updated the MUI to BUI theme converter page to use the renamed `Header` component from `@backstage/ui`. +- Updated dependencies + - @backstage/ui@0.13.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/frontend-plugin-api@0.15.0 + +## 0.2.5-next.2 + +### Patch Changes + +- ad7c883: Updated the MUI to BUI theme converter page to use the renamed `Header` component from `@backstage/ui`. +- Updated dependencies + - @backstage/ui@0.13.0-next.2 + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + +## 0.2.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.13.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/theme@0.7.2 + +## 0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.12.1-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/theme@0.7.2 + +## 0.2.4 + +### Patch Changes + +- 4137a43: Updated CSS token references to use renamed `--bui-bg-app` and `--bui-border-2` tokens. +- a88c437: Updated MUI to BUI theme converter to align with latest token changes + + **Changes:** + + - Removed generation of deprecated tokens: `--bui-fg-link`, `--bui-fg-link-hover`, `--bui-fg-tint`, `--bui-fg-tint-disabled`, `--bui-bg-tint` and all its variants + - Added generation of new `info` status tokens: `--bui-fg-info`, `--bui-fg-info-on-bg`, `--bui-bg-info`, `--bui-border-info` + - Updated status color mapping to generate both standalone and `-on-bg` variants for danger, warning, success, and info + - Status colors now use `.main` for standalone variants and `.dark` for `-on-bg` variants, providing better visual hierarchy + + The converter now generates tokens that match the updated BUI design system structure, with clear distinction between status colors for standalone use vs. use on colored backgrounds. + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/ui@0.12.0 + - @backstage/theme@0.7.2 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/core-plugin-api@1.12.3 + +## 0.2.4-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/ui@0.12.0-next.2 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/theme@0.7.2-next.1 + +## 0.2.4-next.1 + +### Patch Changes + +- a88c437: Updated MUI to BUI theme converter to align with latest token changes + + **Changes:** + + - Removed generation of deprecated tokens: `--bui-fg-link`, `--bui-fg-link-hover`, `--bui-fg-tint`, `--bui-fg-tint-disabled`, `--bui-bg-tint` and all its variants + - Added generation of new `info` status tokens: `--bui-fg-info`, `--bui-fg-info-on-bg`, `--bui-bg-info`, `--bui-border-info` + - Updated status color mapping to generate both standalone and `-on-bg` variants for danger, warning, success, and info + - Status colors now use `.main` for standalone variants and `.dark` for `-on-bg` variants, providing better visual hierarchy + + The converter now generates tokens that match the updated BUI design system structure, with clear distinction between status colors for standalone use vs. use on colored backgrounds. + +- Updated dependencies + - @backstage/ui@0.12.0-next.1 + - @backstage/theme@0.7.2-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.1 + +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.12.0-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/theme@0.7.1 + +## 0.2.3 + +### Patch Changes + +- f157f43: Fix installation command +- e4a1180: Updated tokens from `--bui-bg` to `--bui-bg-surface-0` +- Updated dependencies + - @backstage/ui@0.11.0 + - @backstage/frontend-plugin-api@0.13.3 + +## 0.2.3-next.0 + +### Patch Changes + +- e4a1180: Updated tokens from `--bui-bg` to `--bui-bg-surface-0` +- Updated dependencies + - @backstage/ui@0.11.0-next.0 + - @backstage/core-plugin-api@1.12.1 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/theme@0.7.1 + +## 0.2.2 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.10.0 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + +## 0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.10.0-next.1 + +## 0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.9.1-next.0 + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + +## 0.2.1 + +### Patch Changes + +- 5c614ff: Updated BUI checkbox preview example to align with new component API. +- Updated dependencies + - @backstage/ui@0.9.0 + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/core-plugin-api@1.12.0 + ## 0.2.1-next.1 ### Patch Changes diff --git a/plugins/mui-to-bui/README.md b/plugins/mui-to-bui/README.md index 4e11db20b6..0b760c4ef2 100644 --- a/plugins/mui-to-bui/README.md +++ b/plugins/mui-to-bui/README.md @@ -6,17 +6,24 @@ The Backstage UI Themer helps you convert an existing MUI v5 theme into Backstag ## Installation -### 1) Add the dependency to your app - -Run this from your Backstage repo root: +Add the dependency to your app: ```bash -yarn add --cwd packages/app @backstage/plugin-mui-to-bui +yarn --cwd packages/app add @backstage/plugin-mui-to-bui ``` -### 2) Wire it up depending on your frontend system +Once installed, the plugin is automatically available in your app through the default feature discovery. For more details and alternative installation methods, see [installing plugins](https://backstage.io/docs/frontend-system/building-apps/installing-plugins). -#### Old frontend system (legacy `App.tsx` with ``) +## Accessing the Themer page + +- Navigate to `/mui-to-bui` in your Backstage app (for example `http://localhost:3000/mui-to-bui`). +- Optional: Add a sidebar/link in your app that points to `/mui-to-bui` if you want a permanent navigation entry. + +## Old Frontend System + +If your Backstage app uses the old frontend system, you need to manually wire the +plugin into your app as outlined in this section. If you are on the new frontend +system, you can skip this. Add a route for the page in your app: @@ -34,30 +41,3 @@ export const App = () => ( ); ``` - -#### New frontend system - -If package discovery is enabled in your app, this plugin is picked up automatically after installation — no code changes required. Just navigate to `/mui-to-bui`. - -If you prefer explicit registration (or don't use discovery), register the plugin as a feature. The page route (`/mui-to-bui`) is provided by the plugin. - -```tsx -// packages/app/src/App.tsx (or your app entry where you call createApp) -import React from 'react'; -import { createApp } from '@backstage/frontend-defaults'; -import buiThemerPlugin from '@backstage/plugin-mui-to-bui'; - -const app = createApp({ - features: [ - // ...other features - buiThemerPlugin, - ], -}); - -export default app.createRoot(); -``` - -## Accessing the Themer page - -- Navigate to `/mui-to-bui` in your Backstage app (for example `http://localhost:3000/mui-to-bui`). -- Optional: Add a sidebar/link in your app that points to `/mui-to-bui` if you want a permanent navigation entry. diff --git a/plugins/mui-to-bui/package.json b/plugins/mui-to-bui/package.json index 9eedd5a0be..4a65fa9188 100644 --- a/plugins/mui-to-bui/package.json +++ b/plugins/mui-to-bui/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-mui-to-bui", - "version": "0.2.1-next.1", + "version": "0.2.6", "backstage": { "role": "frontend-plugin", "pluginId": "mui-to-bui", @@ -35,7 +35,6 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/core-compat-api": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", "@backstage/theme": "workspace:^", @@ -52,13 +51,13 @@ "@types/react": "^18.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/plugins/mui-to-bui/report.api.md b/plugins/mui-to-bui/report.api.md index 519f0c1bc7..c1c97a49a2 100644 --- a/plugins/mui-to-bui/report.api.md +++ b/plugins/mui-to-bui/report.api.md @@ -5,13 +5,16 @@ ```ts import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionInput } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { JSX as JSX_3 } from 'react/jsx-runtime'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; -import { RouteRef } from '@backstage/frontend-plugin-api'; -import { RouteRef as RouteRef_2 } from '@backstage/core-plugin-api'; +import { RouteRef } from '@backstage/core-plugin-api'; +import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api'; // @public (undocumented) export const BuiThemerPage: () => JSX_3.Element; @@ -19,7 +22,7 @@ export const BuiThemerPage: () => JSX_3.Element; // @public (undocumented) export const buiThemerPlugin: BackstagePlugin< { - root: RouteRef_2; + root: RouteRef; }, {} >; @@ -36,26 +39,75 @@ const _default: OverridableFrontendPlugin< name: undefined; config: { path: string | undefined; + title: string | undefined; }; configInput: { path?: string | undefined; + title?: string | undefined; }; output: | ExtensionDataRef - | ExtensionDataRef | ExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; } + > + | ExtensionDataRef + | ExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } >; - inputs: {}; + inputs: { + pages: ExtensionInput< + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef_2, + 'core.routing.ref', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >, + { + singleton: false; + optional: false; + internal: false; + } + >; + }; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; - loader: () => Promise; - routeRef?: RouteRef; + title?: string; + icon?: IconElement; + loader?: () => Promise; + routeRef?: RouteRef_2; + noHeader?: boolean; }; }>; } diff --git a/plugins/mui-to-bui/src/components/BuiThemerPage/BuiThemePreview.test.tsx b/plugins/mui-to-bui/src/components/BuiThemerPage/BuiThemePreview.test.tsx index 375f9411a7..4b6024885b 100644 --- a/plugins/mui-to-bui/src/components/BuiThemerPage/BuiThemePreview.test.tsx +++ b/plugins/mui-to-bui/src/components/BuiThemerPage/BuiThemePreview.test.tsx @@ -25,15 +25,15 @@ describe('BuiThemePreview', () => { , ); diff --git a/plugins/mui-to-bui/src/components/BuiThemerPage/BuiThemePreview.tsx b/plugins/mui-to-bui/src/components/BuiThemerPage/BuiThemePreview.tsx index 0b62134178..2f1edbff9f 100644 --- a/plugins/mui-to-bui/src/components/BuiThemerPage/BuiThemePreview.tsx +++ b/plugins/mui-to-bui/src/components/BuiThemerPage/BuiThemePreview.tsx @@ -46,10 +46,10 @@ export function BuiThemePreview({ mode, styleObject }: IsolatedPreviewProps) { // This creates a scoped context where the variables take precedence ...styleObject, width: '100%', - backgroundColor: 'var(--bui-bg-surface-2)', + backgroundColor: 'var(--bui-bg-neutral-2)', padding: 'var(--bui-space-3)', borderRadius: 'var(--bui-radius-2)', - border: '1px solid var(--bui-border)', + border: '1px solid var(--bui-border-2)', }} > diff --git a/plugins/mui-to-bui/src/components/BuiThemerPage/BuiThemerPage.tsx b/plugins/mui-to-bui/src/components/BuiThemerPage/BuiThemerPage.tsx index 7f0e9f32fc..a9b2c07318 100644 --- a/plugins/mui-to-bui/src/components/BuiThemerPage/BuiThemerPage.tsx +++ b/plugins/mui-to-bui/src/components/BuiThemerPage/BuiThemerPage.tsx @@ -16,7 +16,7 @@ import { useApi } from '@backstage/core-plugin-api'; import { appThemeApiRef } from '@backstage/core-plugin-api'; -import { Box, Card, Container, Flex, HeaderPage, Text } from '@backstage/ui'; +import { Box, Card, Container, Flex, Header, Text } from '@backstage/ui'; import { ThemeContent } from './ThemeContent'; import { MuiThemeExtractor } from './MuiThemeExtractor'; @@ -26,7 +26,7 @@ export const BuiThemerPage = () => { return ( - +
    Convert your MUI v5 theme into BUI CSS variables. Pick a theme to view diff --git a/plugins/mui-to-bui/src/components/BuiThemerPage/ThemeContent.tsx b/plugins/mui-to-bui/src/components/BuiThemerPage/ThemeContent.tsx index 0974b8f15d..6341348862 100644 --- a/plugins/mui-to-bui/src/components/BuiThemerPage/ThemeContent.tsx +++ b/plugins/mui-to-bui/src/components/BuiThemerPage/ThemeContent.tsx @@ -98,8 +98,8 @@ export function ThemeContent({ { // Border radius tokens are only generated when radius is 0 expect(result.css).not.toContain('--bui-radius-3:'); // Background default maps to surface-2 - expect(result.css).toContain('--bui-bg: #f5f5f5;'); - expect(result.css).toContain('--bui-bg-surface-2: #f5f5f5;'); - expect(result.css).toContain('--bui-bg-surface-1: #ffffff;'); + expect(result.css).toContain('--bui-bg-app: #f5f5f5;'); + expect(result.css).toContain('--bui-bg-neutral-2: #f5f5f5;'); + expect(result.css).toContain('--bui-bg-neutral-1: #ffffff;'); expect(result.css).toContain('--bui-fg-primary: #000000;'); // Secondary may not be present without Backstage additions, so don't assert it expect(result.css).toContain('--bui-bg-solid: #1976d2;'); @@ -98,13 +98,13 @@ describe('convertMuiToBuiTheme', () => { expect(result.css).toContain("[data-theme-mode='dark'] {"); // Background default maps to surface-2 in dark mode as well - expect(result.css).toContain('--bui-bg: #121212;'); - expect(result.css).toContain('--bui-bg-surface-2: #121212;'); - expect(result.css).toContain('--bui-bg-surface-1: #1e1e1e;'); + expect(result.css).toContain('--bui-bg-app: #121212;'); + expect(result.css).toContain('--bui-bg-neutral-2: #121212;'); + expect(result.css).toContain('--bui-bg-neutral-1: #1e1e1e;'); expect(result.css).toContain('--bui-fg-primary: #ffffff;'); // Test style object - expect(result.styleObject).toHaveProperty('--bui-bg-surface-1', '#1e1e1e'); + expect(result.styleObject).toHaveProperty('--bui-bg-neutral-1', '#1e1e1e'); expect(result.styleObject).toHaveProperty('--bui-fg-primary', '#ffffff'); }); @@ -151,6 +151,10 @@ describe('convertMuiToBuiTheme', () => { expect(result.css).toContain('--bui-bg-danger: #ffcdd2;'); expect(result.css).toContain('--bui-bg-warning: #ffe0b2;'); expect(result.css).toContain('--bui-bg-success: #c8e6c9;'); + // Info background may be hex or rgba depending on theme + expect(result.css).toMatch( + /--bui-bg-info:\s*(#[0-9a-f]{6}|rgba?\([^)]*\));/i, + ); }); it('should generate foreground colors correctly', () => { @@ -178,10 +182,8 @@ describe('convertMuiToBuiTheme', () => { const result = convertMuiToBuiTheme(theme); - expect(result.css).toContain('--bui-fg-link: #1976d2;'); - expect(result.css).toContain('--bui-fg-link-hover: #115293;'); expect(result.css).toContain('--bui-fg-disabled: #cccccc;'); - // Foreground danger/warning/success may be hex or rgb depending on theme + // Foreground status colors (standalone) expect(result.css).toMatch( /--bui-fg-danger:\s*(#[0-9a-f]{6}|rgb\([^)]*\));/i, ); @@ -191,6 +193,22 @@ describe('convertMuiToBuiTheme', () => { expect(result.css).toMatch( /--bui-fg-success:\s*(#[0-9a-f]{6}|rgb\([^)]*\));/i, ); + expect(result.css).toMatch( + /--bui-fg-info:\s*(#[0-9a-f]{6}|rgb\([^)]*\));/i, + ); + // Foreground status colors (on background) + expect(result.css).toMatch( + /--bui-fg-danger-on-bg:\s*(#[0-9a-f]{6}|rgb\([^)]*\));/i, + ); + expect(result.css).toMatch( + /--bui-fg-warning-on-bg:\s*(#[0-9a-f]{6}|rgb\([^)]*\));/i, + ); + expect(result.css).toMatch( + /--bui-fg-success-on-bg:\s*(#[0-9a-f]{6}|rgb\([^)]*\));/i, + ); + expect(result.css).toMatch( + /--bui-fg-info-on-bg:\s*(#[0-9a-f]{6}|rgb\([^)]*\));/i, + ); }); it('should generate border colors correctly', () => { @@ -215,6 +233,10 @@ describe('convertMuiToBuiTheme', () => { expect(result.css).toContain('--bui-border-danger: #f44336;'); expect(result.css).toContain('--bui-border-warning: #ff9800;'); expect(result.css).toContain('--bui-border-success: #4caf50;'); + // Info border may be hex or rgb depending on theme + expect(result.css).toMatch( + /--bui-border-info:\s*(#[0-9a-f]{6}|rgb\([^)]*\));/i, + ); }); it('should handle function-based spacing', () => { diff --git a/plugins/mui-to-bui/src/components/BuiThemerPage/convertMuiToBuiTheme.ts b/plugins/mui-to-bui/src/components/BuiThemerPage/convertMuiToBuiTheme.ts index 1b8528922b..7f32621dd2 100644 --- a/plugins/mui-to-bui/src/components/BuiThemerPage/convertMuiToBuiTheme.ts +++ b/plugins/mui-to-bui/src/components/BuiThemerPage/convertMuiToBuiTheme.ts @@ -94,38 +94,37 @@ function generateBuiVariables(theme: Mui5Theme): Record { Object.entries({ primary: palette.text.primary, secondary: palette.textSubtle, - link: palette.link ?? palette.primary.main, - 'link-hover': palette.linkHover ?? palette.primary.dark, disabled: palette.text.disabled, solid: palette.primary.contrastText, 'solid-disabled': palette.text.disabled, - tint: palette.textSubtle, - 'tint-disabled': palette.textVerySubtle, - danger: palette.error.dark, - warning: palette.warning.dark, - success: palette.success.dark, + danger: palette.error.main, + warning: palette.warning.main, + success: palette.success.main, + info: palette.info?.main ?? palette.primary.main, + 'danger-on-bg': palette.error.dark, + 'warning-on-bg': palette.warning.dark, + 'success-on-bg': palette.success.dark, + 'info-on-bg': palette.info?.dark ?? palette.primary.dark, }).forEach(([key, value]) => { styleObject[`--bui-fg-${key}`] = value; }); - // Generate surface colors + // Generate neutral background colors + styleObject['--bui-bg-app'] = palette.background.default; Object.entries({ - '': palette.background.default, - 'surface-1': palette.background.paper, - 'surface-2': palette.background.default, + 'neutral-1': palette.background.paper, + 'neutral-2': palette.background.default, + 'neutral-3': palette.background.default, solid: palette.primary.main, 'solid-hover': blend(palette.primary.main, palette.primary.dark, 0.5), 'solid-pressed': palette.primary.dark, 'solid-disabled': palette.action.disabledBackground, - tint: 'transparent', - 'tint-hover': alpha(palette.primary.main, 0.4), - 'tint-pressed': alpha(palette.primary.main, 0.6), - 'tint-disabled': palette.action.disabledBackground, danger: palette.error.light, warning: palette.warning.light, success: palette.success.light, + info: palette.info?.light ?? alpha(palette.primary.main, 0.1), }).forEach(([key, value]) => { - styleObject[`--bui-bg${key ? `-${key}` : ''}`] = value; + styleObject[`--bui-bg-${key}`] = value; }); // Border colors @@ -133,12 +132,13 @@ function generateBuiVariables(theme: Mui5Theme): Record { danger: palette.error.main, warning: palette.warning.main, success: palette.success.main, + info: palette.info?.main ?? palette.primary.main, }).forEach(([key, value]) => { styleObject[`--bui-border-${key}`] = value; }); // Base border color if available - styleObject['--bui-border'] = palette.border || palette.divider; + styleObject['--bui-border-2'] = palette.border || palette.divider; styleObject['--bui-border-danger'] = palette.error.main; styleObject['--bui-border-warning'] = palette.warning.main; styleObject['--bui-border-success'] = palette.success.main; diff --git a/plugins/mui-to-bui/src/plugin.tsx b/plugins/mui-to-bui/src/plugin.tsx index aca19c9978..ccebe6e379 100644 --- a/plugins/mui-to-bui/src/plugin.tsx +++ b/plugins/mui-to-bui/src/plugin.tsx @@ -14,10 +14,6 @@ * limitations under the License. */ -import { - convertLegacyRouteRef, - convertLegacyRouteRefs, -} from '@backstage/core-compat-api'; import { createPlugin, createRoutableExtension, @@ -57,11 +53,11 @@ export default createFrontendPlugin({ path: '/mui-to-bui', loader: () => import('./components/BuiThemerPage').then(m => ), - routeRef: convertLegacyRouteRef(rootRouteRef), + routeRef: rootRouteRef, }, }), ], - routes: convertLegacyRouteRefs({ + routes: { root: rootRouteRef, - }), + }, }); diff --git a/plugins/notifications-backend-module-email/CHANGELOG.md b/plugins/notifications-backend-module-email/CHANGELOG.md index dafe434598..24ed62eb23 100644 --- a/plugins/notifications-backend-module-email/CHANGELOG.md +++ b/plugins/notifications-backend-module-email/CHANGELOG.md @@ -1,5 +1,223 @@ # @backstage/plugin-notifications-backend-module-email +## 0.3.20 + +### Patch Changes + +- 19ef9fb: build(deps): bump `nodemailer` from 7.0.13 to 8.0.4 +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/catalog-model@1.8.0 + - @backstage/plugin-catalog-node@2.2.0 + - @backstage/catalog-client@1.15.0 + - @backstage/plugin-notifications-node@0.2.25 + - @backstage/config@1.3.7 + - @backstage/integration-aws-node@0.1.21 + - @backstage/plugin-notifications-common@0.2.2 + +## 0.3.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.2.0-next.2 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/catalog-client@1.14.1-next.0 + - @backstage/catalog-model@1.7.8-next.0 + - @backstage/config@1.3.7-next.0 + - @backstage/integration-aws-node@0.1.21-next.0 + - @backstage/plugin-notifications-node@0.2.25-next.2 + - @backstage/plugin-notifications-common@0.2.2-next.0 + +## 0.3.20-next.1 + +### Patch Changes + +- 19ef9fb: build(deps): bump `nodemailer` from 7.0.13 to 8.0.4 +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-notifications-node@0.2.25-next.1 + +## 0.3.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-catalog-node@2.1.1-next.0 + - @backstage/plugin-notifications-node@0.2.25-next.0 + - @backstage/catalog-client@1.14.0 + - @backstage/catalog-model@1.7.7 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.20 + - @backstage/types@1.2.2 + - @backstage/plugin-notifications-common@0.2.1 + +## 0.3.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/catalog-client@1.14.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/catalog-model@1.7.7 + - @backstage/plugin-notifications-node@0.2.24 + +## 0.3.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/catalog-client@1.14.0-next.2 + - @backstage/plugin-catalog-node@2.1.0-next.2 + - @backstage/plugin-notifications-node@0.2.24-next.2 + +## 0.3.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.14.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.1 + - @backstage/plugin-notifications-node@0.2.24-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.20 + - @backstage/types@1.2.2 + - @backstage/plugin-notifications-common@0.2.1 + +## 0.3.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-client@1.13.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.20 + - @backstage/types@1.2.2 + - @backstage/plugin-notifications-common@0.2.1 + - @backstage/plugin-notifications-node@0.2.24-next.0 + +## 0.3.18 + +### Patch Changes + +- e9eb400: Allow configuring included topics for email notifications. +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/integration-aws-node@0.1.20 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/catalog-client@1.13.0 + - @backstage/plugin-notifications-common@0.2.1 + - @backstage/plugin-notifications-node@0.2.23 + +## 0.3.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/catalog-client@1.12.2-next.0 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-notifications-node@0.2.23-next.1 + +## 0.3.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/integration-aws-node@0.1.20-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-notifications-node@0.2.23-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + - @backstage/plugin-notifications-common@0.2.0 + +## 0.3.17 + +### Patch Changes + +- a5d5b3a: SES config for the notification email processor now supports utilizing an ARN for the SES identity when sending an email after the SES SDK V2 update. + + The `sesConfig.fromArn` will set the `fromEmailAddressIdentityArn` option for the SES `SendEmailCommand`. The `sesConfig.sourceArn` field is removed since no equivalent option is available in the send email command options. Setting `sesConfig.sourceArn` will have no effect and log a warning. Example changes: + + ```diff + notifications: + processors: + email: + transportConfig: + transport: "ses" + region: "us-west-2" + sender: "sender@mycompany.com" + replyTo: "no-reply@mycompany.com" + sesConfig: + - sourceArn: "arn:aws:ses:us-west-2:123456789012:identity/example.com" + fromArn: "arn:aws:ses:us-west-2:123456789012:identity/example.com" + ``` + +- b267aea: Updated dependency `@types/nodemailer` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-notifications-node@0.2.22 + +## 0.3.17-next.1 + +### Patch Changes + +- b267aea: Updated dependency `@types/nodemailer` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/plugin-notifications-node@0.2.22-next.1 + +## 0.3.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-notifications-node@0.2.22-next.0 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + - @backstage/plugin-notifications-common@0.2.0 + +## 0.3.16 + +### Patch Changes + +- 22a5362: Updated `AWS SES` client to version 2 to support `nodemailer` version 7. +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-notifications-node@0.2.21 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/catalog-client@1.12.1 + ## 0.3.16-next.2 ### Patch Changes diff --git a/plugins/notifications-backend-module-email/README.md b/plugins/notifications-backend-module-email/README.md index c1ead48876..248bb2c501 100644 --- a/plugins/notifications-backend-module-email/README.md +++ b/plugins/notifications-backend-module-email/README.md @@ -81,7 +81,6 @@ notifications: receiver: 'users' # Optional SES config # sesConfig: - # sourceArn: 'arn:aws:ses:us-west-2:123456789012:identity/example.com' # fromArn: 'arn:aws:ses:us-west-2:123456789012:identity/example.com' # configurationSetName: 'custom-config' # How many emails to send concurrently, defaults to 2 diff --git a/plugins/notifications-backend-module-email/config.d.ts b/plugins/notifications-backend-module-email/config.d.ts index 7116826c5d..97c1bc814b 100644 --- a/plugins/notifications-backend-module-email/config.d.ts +++ b/plugins/notifications-backend-module-email/config.d.ts @@ -136,10 +136,6 @@ export interface Config { * Optional SES config for mail options. Allows for delegated sender */ sesConfig?: { - /** - * ARN of the identity to use as the source of the email - */ - sourceArn?: string; /** * ARN of the identity to use for the "From"/sender address of the email */ @@ -165,9 +161,14 @@ export interface Config { */ maxSeverity?: NotificationSeverity; /** - * A notification who's topic is in this array will not be emailed + * A notification with topic is in this array will not be emailed */ excludedTopics?: string[]; + /** + * A notification with topic in this array will be emailed. If not defined, only + * excludedTopics takes effect. + */ + includedTopics?: string[]; }; /** * White list of addresses to send email to diff --git a/plugins/notifications-backend-module-email/knip-report.md b/plugins/notifications-backend-module-email/knip-report.md index 97d5b385fd..2661c35327 100644 --- a/plugins/notifications-backend-module-email/knip-report.md +++ b/plugins/notifications-backend-module-email/knip-report.md @@ -1,3 +1,2 @@ # Knip report - diff --git a/plugins/notifications-backend-module-email/package.json b/plugins/notifications-backend-module-email/package.json index 2c112604a7..addd144787 100644 --- a/plugins/notifications-backend-module-email/package.json +++ b/plugins/notifications-backend-module-email/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend-module-email", - "version": "0.3.16-next.2", + "version": "0.3.20", "description": "The email backend module for the notifications plugin.", "backstage": { "role": "backend-plugin-module", @@ -47,13 +47,13 @@ "@backstage/plugin-notifications-node": "workspace:^", "@backstage/types": "workspace:^", "lodash": "^4.17.21", - "nodemailer": "^7.0.7", + "nodemailer": "^8.0.4", "p-throttle": "^4.1.1" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "@types/nodemailer": "^6.4.14" + "@types/nodemailer": "^7.0.0" }, "configSchema": "config.d.ts" } diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts index ebe1b90f35..ad0817521f 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts @@ -497,9 +497,13 @@ describe('NotificationsEmailProcessor', () => { text: 'https://example.org/notifications', to: 'mock@backstage.io', ses: { - SourceArn: 'arn:aws:ses:us-west-2:123456789012:identity/example.com', - FromArn: 'arn:aws:ses:us-west-2:123456789012:identity/example.com', + FromEmailAddressIdentityArn: + 'arn:aws:ses:us-west-2:123456789012:identity/example.com', }, }); + + expect(logger.warn).toHaveBeenCalledWith( + 'sourceArn is not supported in SESv2 and will be ignored', + ); }); }); diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts index eadfe7747e..e0e27232c2 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts @@ -45,6 +45,7 @@ import { DefaultAwsCredentialsManager } from '@backstage/integration-aws-node'; import { NotificationTemplateRenderer } from '../extensions'; import Mail from 'nodemailer/lib/mailer'; import pThrottle from 'p-throttle'; +import { SendEmailCommandInput } from '@aws-sdk/client-sesv2'; export class NotificationsEmailProcessor implements NotificationProcessor { private transporter: any; @@ -53,6 +54,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { private readonly sender: string; private readonly replyTo?: string; private readonly sesConfig?: Config; + private readonly sesOptions?: Partial; private readonly cacheTtl: number; private readonly concurrencyLimit: number; private readonly throttleInterval: number; @@ -91,6 +93,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { this.sender = emailProcessorConfig.getString('sender'); this.replyTo = emailProcessorConfig.getOptionalString('replyTo'); this.sesConfig = emailProcessorConfig.getOptionalConfig('sesConfig'); + this.sesOptions = this.getSesOptions(); this.concurrencyLimit = emailProcessorConfig.getOptionalNumber('concurrencyLimit') ?? 2; this.throttleInterval = emailProcessorConfig.has('throttleInterval') @@ -307,20 +310,23 @@ export class NotificationsEmailProcessor implements NotificationProcessor { return contentParts.join('\n\n'); } - private async getSesOptions() { + private getSesOptions(): Partial | undefined { if (!this.sesConfig) { return undefined; } - const ses: Record = {}; - const sourceArn = this.sesConfig.getOptionalString('sourceArn'); + const ses: Partial = {}; const fromArn = this.sesConfig.getOptionalString('fromArn'); + const sourceArn = this.sesConfig.getOptionalString('sourceArn'); const configurationSetName = this.sesConfig.getOptionalString( 'configurationSetName', ); - if (sourceArn) ses.SourceArn = sourceArn; - if (fromArn) ses.FromArn = fromArn; + if (fromArn) ses.FromEmailAddressIdentityArn = fromArn; if (configurationSetName) ses.ConfigurationSetName = configurationSetName; + if (sourceArn) + this.logger.warn( + 'sourceArn is not supported in SESv2 and will be ignored', + ); return Object.keys(ses).length > 0 ? ses : undefined; } @@ -332,7 +338,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { html: this.getHtmlContent(notification), text: this.getTextContent(notification), replyTo: this.replyTo, - ses: await this.getSesOptions(), + ses: this.sesOptions, }; await this.sendMails(mailOptions, emails); @@ -350,7 +356,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { html: await this.templateRenderer?.getHtml?.(notification), text: await this.templateRenderer?.getText?.(notification), replyTo: this.replyTo, - ses: await this.getSesOptions(), + ses: this.sesOptions, }; await this.sendMails(mailOptions, emails); diff --git a/plugins/notifications-backend-module-slack/CHANGELOG.md b/plugins/notifications-backend-module-slack/CHANGELOG.md index 49d99dbe2e..998689b55b 100644 --- a/plugins/notifications-backend-module-slack/CHANGELOG.md +++ b/plugins/notifications-backend-module-slack/CHANGELOG.md @@ -1,5 +1,192 @@ # @backstage/plugin-notifications-backend-module-slack +## 0.4.1 + +### Patch Changes + +- 482ceed: Migrated from `assertError` to `toError` for error handling. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/errors@1.3.0 + - @backstage/catalog-model@1.8.0 + - @backstage/plugin-catalog-node@2.2.0 + - @backstage/plugin-notifications-node@0.2.25 + - @backstage/config@1.3.7 + - @backstage/plugin-notifications-common@0.2.2 + +## 0.4.1-next.2 + +### Patch Changes + +- 482ceed: Migrated from `assertError` to `toError` for error handling. +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/plugin-catalog-node@2.2.0-next.2 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/catalog-model@1.7.8-next.0 + - @backstage/config@1.3.7-next.0 + - @backstage/plugin-notifications-node@0.2.25-next.2 + - @backstage/plugin-notifications-common@0.2.2-next.0 + +## 0.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-notifications-node@0.2.25-next.1 + +## 0.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-catalog-node@2.1.1-next.0 + - @backstage/plugin-notifications-node@0.2.25-next.0 + - @backstage/catalog-model@1.7.7 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-notifications-common@0.2.1 + +## 0.4.0 + +### Minor Changes + +- cd62d78: **BREAKING**: Only send direct messages to user entity recipients. Notifications sent to non-user entities no longer send Slack direct messages to resolved users. +- 749ba60: Add an extension for custom Slack message layouts + +### Patch Changes + +- e7c6c32: The Slack notification processor now uses the `MetricsService` to create metrics, providing plugin-scoped attribution. `{message}` unit has also been added. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/catalog-model@1.7.7 + - @backstage/plugin-notifications-node@0.2.24 + +## 0.4.0-next.1 + +### Minor Changes + +- cd62d78: **BREAKING**: Only send direct messages to user entity recipients. Notifications sent to non-user entities no longer send Slack direct messages to resolved users. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.2 + - @backstage/plugin-notifications-node@0.2.24-next.2 + +## 0.4.0-next.0 + +### Minor Changes + +- 749ba60: Add an extension for custom Slack message layouts + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-notifications-common@0.2.1 + - @backstage/plugin-notifications-node@0.2.24-next.0 + +## 0.3.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-notifications-common@0.2.1 + - @backstage/plugin-notifications-node@0.2.23 + +## 0.3.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-notifications-node@0.2.23-next.1 + +## 0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-notifications-node@0.2.23-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-notifications-common@0.2.0 + +## 0.3.0 + +### Minor Changes + +- f95a516: Enables optional routes to Slack channels for broadcast notifications based on origin and/or topics. + +### Patch Changes + +- b80857a: Slack notification handler throttling can now be configured with the `concurrencyLimit` and `throttleInterval` options. +- f8230e4: Updated dependency `@faker-js/faker` to `^10.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-notifications-node@0.2.22 + +## 0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/plugin-notifications-node@0.2.22-next.1 + +## 0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-notifications-node@0.2.22-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-notifications-common@0.2.0 + +## 0.2.1 + +### Patch Changes + +- d959bec: When an error message is logged due to inability to send a message with the Slack SDK, include the Slack Channel ID in the message to aid debugging. +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-notifications-node@0.2.21 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + ## 0.2.1-next.2 ### Patch Changes diff --git a/plugins/notifications-backend-module-slack/config.d.ts b/plugins/notifications-backend-module-slack/config.d.ts index 8cd450940e..17a4e25e0d 100644 --- a/plugins/notifications-backend-module-slack/config.d.ts +++ b/plugins/notifications-backend-module-slack/config.d.ts @@ -13,6 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { HumanDuration } from '@backstage/types'; + export interface Config { notifications?: { processors?: { @@ -26,8 +28,40 @@ export interface Config { * Broadcast notification receivers when receiver is set to config * These can be Slack User IDs, Slack User Email addresses, Slack Channel * Names, or Slack Channel IDs. Any valid identifier that chat.postMessage can accept. + * @deprecated Use broadcastRoutes instead for more granular control */ broadcastChannels?: string[]; + /** + * Optional username to display as the sender of the notification + */ + username?: string; + /** + * Routes for broadcast notifications based on origin and/or topic. + * Routes are evaluated in order, first match wins. + * Origin+topic matches take precedence over origin-only matches. + */ + broadcastRoutes?: Array<{ + /** + * The origin to match (e.g., 'plugin:catalog', 'external:my-service') + */ + origin?: string; + /** + * The topic to match (e.g., 'entity-updated', 'alerts') + */ + topic?: string; + /** + * The Slack channel(s) to send to. Can be channel IDs, channel names, or user IDs. + */ + channel: string | string[]; + }>; + /** + * Concurrency limit for Slack notifications per backend instance of the notifications plugin, defaults to 10. + */ + concurrencyLimit?: number; + /** + * Throttle duration between Slack notifications per backend instance of the notifications plugin, defaults to 1 minute. + */ + throttleInterval?: HumanDuration | string; }>; }; }; diff --git a/plugins/notifications-backend-module-slack/knip-report.md b/plugins/notifications-backend-module-slack/knip-report.md index d3eb017b29..5050f5baa6 100644 --- a/plugins/notifications-backend-module-slack/knip-report.md +++ b/plugins/notifications-backend-module-slack/knip-report.md @@ -2,16 +2,16 @@ ## Unused dependencies (2) -| Name | Location | Severity | -| :----------- | :----------- | :------- | -| @slack/types | plugins/notifications-backend-module-slack/package.json | error | -| @slack/bolt | plugins/notifications-backend-module-slack/package.json | error | +| Name | Location | Severity | +| :----------- | :---------------- | :------- | +| @slack/types | package.json:46:6 | error | +| @slack/bolt | package.json:45:6 | error | ## Unused devDependencies (3) -| Name | Location | Severity | -| :-------------------- | :----------- | :------- | -| @backstage/test-utils | plugins/notifications-backend-module-slack/package.json | error | -| @faker-js/faker | plugins/notifications-backend-module-slack/package.json | error | -| msw | plugins/notifications-backend-module-slack/package.json | error | +| Name | Location | Severity | +| :-------------------- | :---------------- | :------- | +| @backstage/test-utils | package.json:54:6 | error | +| @faker-js/faker | package.json:55:6 | error | +| msw | package.json:56:6 | error | diff --git a/plugins/notifications-backend-module-slack/migrations/20260327000000_create_slack_message_timestamps.js b/plugins/notifications-backend-module-slack/migrations/20260327000000_create_slack_message_timestamps.js new file mode 100644 index 0000000000..6bddab3e54 --- /dev/null +++ b/plugins/notifications-backend-module-slack/migrations/20260327000000_create_slack_message_timestamps.js @@ -0,0 +1,44 @@ +// @ts-check +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @param {import('knex').Knex} knex + */ +exports.up = async function up(knex) { + await knex.schema.createTable( + 'slack_message_timestamps', + function tableSetup(table) { + table.string('origin', 256).notNullable(); + table.string('scope', 512).notNullable(); + table.string('channel', 64).notNullable(); + table.string('ts', 64).notNullable(); + table + .timestamp('created_at', { useTz: true }) + .defaultTo(knex.fn.now()) + .notNullable(); + table.primary(['origin', 'scope', 'channel']); + table.index('created_at', 'idx_slack_message_timestamps_created_at'); + }, + ); +}; + +/** + * @param {import('knex').Knex} knex + */ +exports.down = async function down(knex) { + await knex.schema.dropTable('slack_message_timestamps'); +}; diff --git a/plugins/notifications-backend-module-slack/package.json b/plugins/notifications-backend-module-slack/package.json index eb5634b607..8708d9e77a 100644 --- a/plugins/notifications-backend-module-slack/package.json +++ b/plugins/notifications-backend-module-slack/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend-module-slack", - "version": "0.2.1-next.2", + "version": "0.4.1", "description": "The slack backend module for the notifications plugin.", "backstage": { "role": "backend-plugin-module", @@ -22,7 +22,8 @@ "types": "src/index.ts", "files": [ "dist", - "config.d.ts" + "config.d.ts", + "migrations" ], "scripts": { "build": "backstage-cli package build", @@ -42,19 +43,18 @@ "@backstage/plugin-notifications-common": "workspace:^", "@backstage/plugin-notifications-node": "workspace:^", "@backstage/types": "workspace:^", - "@opentelemetry/api": "^1.9.0", "@slack/bolt": "^3.21.4", "@slack/types": "^2.14.0", "@slack/web-api": "^7.5.0", "dataloader": "^2.0.0", + "knex": "^3.0.0", "p-throttle": "^4.1.1" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "@backstage/plugin-catalog-node": "workspace:^", "@backstage/test-utils": "workspace:^", - "@faker-js/faker": "^8.4.1", + "@faker-js/faker": "^10.0.0", "msw": "^2.0.0" }, "configSchema": "config.d.ts" diff --git a/plugins/notifications-backend-module-slack/report.api.md b/plugins/notifications-backend-module-slack/report.api.md index 3c4ddb494d..6fb477c1ee 100644 --- a/plugins/notifications-backend-module-slack/report.api.md +++ b/plugins/notifications-backend-module-slack/report.api.md @@ -4,6 +4,9 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; +import { ExtensionPoint } from '@backstage/backend-plugin-api'; +import { KnownBlock } from '@slack/web-api'; +import { NotificationPayload } from '@backstage/plugin-notifications-common'; // @public export const ANNOTATION_SLACK_BOT_NOTIFY = 'slack.com/bot-notify'; @@ -11,4 +14,18 @@ export const ANNOTATION_SLACK_BOT_NOTIFY = 'slack.com/bot-notify'; // @public const notificationsModuleSlack: BackendFeature; export default notificationsModuleSlack; + +// @public +export interface NotificationsSlackBlockKitExtensionPoint { + // (undocumented) + setBlockKitRenderer(renderer: SlackBlockKitRenderer): void; +} + +// @public (undocumented) +export const notificationsSlackBlockKitExtensionPoint: ExtensionPoint; + +// @public (undocumented) +export type SlackBlockKitRenderer = ( + payload: NotificationPayload, +) => KnownBlock[]; ``` diff --git a/plugins/notifications-backend-module-slack/report.sql.md b/plugins/notifications-backend-module-slack/report.sql.md new file mode 100644 index 0000000000..daff159f33 --- /dev/null +++ b/plugins/notifications-backend-module-slack/report.sql.md @@ -0,0 +1,18 @@ +## SQL Report file for "@backstage/plugin-notifications-backend-module-slack" + +> Do not edit this file. It is a report generated by `yarn build:api-reports` + +## Table `slack_message_timestamps` + +| Column | Type | Nullable | Max Length | Default | +| ------------ | -------------------------- | -------- | ---------- | ------------------- | +| `channel` | `character varying` | false | 64 | - | +| `created_at` | `timestamp with time zone` | false | - | `CURRENT_TIMESTAMP` | +| `origin` | `character varying` | false | 256 | - | +| `scope` | `character varying` | false | 512 | - | +| `ts` | `character varying` | false | 64 | - | + +### Indices + +- `idx_slack_message_timestamps_created_at` (`created_at`) +- `slack_message_timestamps_pkey` (`origin`, `scope`, `channel`) unique primary diff --git a/plugins/notifications-backend-module-slack/src/extensions.ts b/plugins/notifications-backend-module-slack/src/extensions.ts new file mode 100644 index 0000000000..c2465b69cf --- /dev/null +++ b/plugins/notifications-backend-module-slack/src/extensions.ts @@ -0,0 +1,43 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createExtensionPoint } from '@backstage/backend-plugin-api'; +import { NotificationPayload } from '@backstage/plugin-notifications-common'; +import { KnownBlock } from '@slack/web-api'; + +/** + * @public + */ +export type SlackBlockKitRenderer = ( + payload: NotificationPayload, +) => KnownBlock[]; + +/** + * @public + * + * Extension point for customizing how notification payloads are rendered into + * Slack Block Kit messages before they're sent. + */ +export interface NotificationsSlackBlockKitExtensionPoint { + setBlockKitRenderer(renderer: SlackBlockKitRenderer): void; +} + +/** + * @public + */ +export const notificationsSlackBlockKitExtensionPoint = + createExtensionPoint({ + id: 'notifications.slack.blockkit', + }); diff --git a/plugins/notifications-backend-module-slack/src/index.ts b/plugins/notifications-backend-module-slack/src/index.ts index f8a9e248f7..178c5dbc54 100644 --- a/plugins/notifications-backend-module-slack/src/index.ts +++ b/plugins/notifications-backend-module-slack/src/index.ts @@ -22,3 +22,4 @@ export { ANNOTATION_SLACK_BOT_NOTIFY } from './lib'; export { notificationsModuleSlack as default } from './module'; +export * from './extensions'; diff --git a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts index ad4bf01e9f..ddf34b6b89 100644 --- a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts +++ b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts @@ -15,10 +15,28 @@ */ import { mockServices } from '@backstage/backend-test-utils'; +import { metricsServiceMock } from '@backstage/backend-test-utils/alpha'; import { SlackNotificationProcessor } from './SlackNotificationProcessor'; import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; -import { WebClient } from '@slack/web-api'; +import { KnownBlock, WebClient } from '@slack/web-api'; import { Entity } from '@backstage/catalog-model'; +import { Knex } from 'knex'; +import pThrottle from 'p-throttle'; +import { durationToMilliseconds } from '@backstage/types'; + +const throttleConfigs: Array<{ limit: number; interval: number }> = []; + +jest.mock('p-throttle', () => ({ + __esModule: true, + default: jest.fn((config: { limit: number; interval: number }) => { + throttleConfigs.push(config); + return Promise | any>(fn: T) => + (...args: Parameters) => + Promise.resolve(fn(...args)); + }), +})); + +const mockedPThrottle = pThrottle as jest.MockedFunction; jest.mock('@slack/web-api', () => { const mockSlack = { @@ -28,6 +46,11 @@ jest.mock('@slack/web-api', () => { ts: '1234567890.123456', channel: 'C12345678', })), + update: jest.fn(() => ({ + ok: true, + ts: '1234567890.123456', + channel: 'C12345678', + })), }, conversations: { list: jest.fn(() => ({ @@ -109,6 +132,7 @@ const DEFAULT_ENTITIES_RESPONSE = { describe('SlackNotificationProcessor', () => { const logger = mockServices.logger.mock(); const auth = mockServices.auth(); + const metrics = metricsServiceMock.mock(); const config = mockServices.rootConfig({ data: { app: { @@ -128,6 +152,8 @@ describe('SlackNotificationProcessor', () => { beforeEach(() => { jest.clearAllMocks(); + throttleConfigs.length = 0; + mockedPThrottle.mockClear(); }); it('should send a notification to a group', async () => { @@ -139,6 +165,7 @@ describe('SlackNotificationProcessor', () => { catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, }), + metrics, slack, })[0]; @@ -191,6 +218,44 @@ describe('SlackNotificationProcessor', () => { }); }); + it('should use a custom block kit renderer when provided', async () => { + const slack = new WebClient(); + const customBlocks: KnownBlock[] = [ + { + type: 'section', + text: { type: 'mrkdwn', text: 'Custom block' }, + }, + ]; + + const processor = SlackNotificationProcessor.fromConfig(config, { + auth, + logger, + catalog: catalogServiceMock({ + entities: DEFAULT_ENTITIES_RESPONSE.items, + }), + metrics, + slack, + blockKitRenderer: () => customBlocks, + })[0]; + + await processor.processOptions({ + recipients: { type: 'entity', entityRef: 'group:default/mock' }, + payload: { title: 'notification' }, + }); + + expect(slack.chat.postMessage).toHaveBeenCalledWith({ + channel: 'C12345678', + text: 'notification', + attachments: [ + { + color: '#00A699', + blocks: customBlocks, + fallback: 'notification', + }, + ], + }); + }); + describe('when a user notification is sent directly', () => { it('should send a notification to a user', async () => { const slack = new WebClient(); @@ -201,6 +266,7 @@ describe('SlackNotificationProcessor', () => { catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, }), + metrics, slack, })[0]; @@ -276,6 +342,7 @@ describe('SlackNotificationProcessor', () => { catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, }), + metrics, slack, })[0]; @@ -300,6 +367,52 @@ describe('SlackNotificationProcessor', () => { }); }); + describe('when recipients include both users and a group', () => { + it('should still DM explicit user recipients', async () => { + const slack = new WebClient(); + + const processor = SlackNotificationProcessor.fromConfig(config, { + auth, + logger, + catalog: catalogServiceMock({ + entities: DEFAULT_ENTITIES_RESPONSE.items, + }), + metrics, + slack, + })[0]; + + await processor.processOptions({ + recipients: { + type: 'entity', + entityRef: ['group:default/mock', 'user:default/mock'], + }, + payload: { title: 'notification' }, + }); + + await processor.postProcess( + { + origin: 'plugin', + id: 'explicit-user-1', + user: 'user:default/mock', + created: new Date(), + payload: { title: 'notification' }, + }, + { + recipients: { + type: 'entity', + entityRef: ['group:default/mock', 'user:default/mock'], + }, + payload: { title: 'notification' }, + }, + ); + + expect(slack.chat.postMessage).toHaveBeenCalledTimes(2); + expect(slack.chat.postMessage).toHaveBeenCalledWith( + expect.objectContaining({ channel: 'U12345678' }), + ); + }); + }); + describe('when broadcast channels are not configured', () => { it('should not send broadcast messages', async () => { const slack = new WebClient(); @@ -310,6 +423,7 @@ describe('SlackNotificationProcessor', () => { catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, }), + metrics, slack, })[0]; @@ -365,6 +479,7 @@ describe('SlackNotificationProcessor', () => { catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, }), + metrics, slack, })[0]; @@ -393,6 +508,470 @@ describe('SlackNotificationProcessor', () => { }); }); + describe('when broadcast routes are configured', () => { + it('should route by origin and topic (highest priority)', async () => { + const slack = new WebClient(); + const routesConfig = mockServices.rootConfig({ + data: { + app: { + baseUrl: 'https://example.org', + }, + notifications: { + processors: { + slack: [ + { + token: 'mock-token', + broadcastChannels: ['default-channel'], + broadcastRoutes: [ + { origin: 'plugin:catalog', channel: 'catalog-channel' }, + { topic: 'alerts', channel: 'alerts-channel' }, + { + origin: 'plugin:catalog', + topic: 'alerts', + channel: 'catalog-alerts-channel', + }, + ], + }, + ], + }, + }, + }, + }); + + const processor = SlackNotificationProcessor.fromConfig(routesConfig, { + auth, + logger, + catalog: catalogServiceMock({ + entities: DEFAULT_ENTITIES_RESPONSE.items, + }), + metrics, + slack, + })[0]; + + await processor.postProcess( + { + origin: 'plugin:catalog', + id: '1234', + user: null, + created: new Date(), + payload: { + title: 'notification', + topic: 'alerts', + }, + }, + { + recipients: { type: 'broadcast' }, + payload: { title: 'notification', topic: 'alerts' }, + }, + ); + + expect(slack.chat.postMessage).toHaveBeenCalledTimes(1); + expect(slack.chat.postMessage).toHaveBeenCalledWith( + expect.objectContaining({ channel: 'catalog-alerts-channel' }), + ); + }); + + it('should route by origin only when no origin+topic match', async () => { + const slack = new WebClient(); + const routesConfig = mockServices.rootConfig({ + data: { + app: { + baseUrl: 'https://example.org', + }, + notifications: { + processors: { + slack: [ + { + token: 'mock-token', + broadcastRoutes: [ + { origin: 'plugin:catalog', channel: 'catalog-channel' }, + { topic: 'alerts', channel: 'alerts-channel' }, + ], + }, + ], + }, + }, + }, + }); + + const processor = SlackNotificationProcessor.fromConfig(routesConfig, { + auth, + logger, + catalog: catalogServiceMock({ + entities: DEFAULT_ENTITIES_RESPONSE.items, + }), + metrics, + slack, + })[0]; + + await processor.postProcess( + { + origin: 'plugin:catalog', + id: '1234', + user: null, + created: new Date(), + payload: { + title: 'notification', + topic: 'updates', + }, + }, + { + recipients: { type: 'broadcast' }, + payload: { title: 'notification', topic: 'updates' }, + }, + ); + + expect(slack.chat.postMessage).toHaveBeenCalledTimes(1); + expect(slack.chat.postMessage).toHaveBeenCalledWith( + expect.objectContaining({ channel: 'catalog-channel' }), + ); + }); + + it('should route by topic only when no origin match', async () => { + const slack = new WebClient(); + const routesConfig = mockServices.rootConfig({ + data: { + app: { + baseUrl: 'https://example.org', + }, + notifications: { + processors: { + slack: [ + { + token: 'mock-token', + broadcastRoutes: [ + { origin: 'plugin:catalog', channel: 'catalog-channel' }, + { topic: 'alerts', channel: 'alerts-channel' }, + ], + }, + ], + }, + }, + }, + }); + + const processor = SlackNotificationProcessor.fromConfig(routesConfig, { + auth, + logger, + catalog: catalogServiceMock({ + entities: DEFAULT_ENTITIES_RESPONSE.items, + }), + metrics, + slack, + })[0]; + + await processor.postProcess( + { + origin: 'plugin:unknown', + id: '1234', + user: null, + created: new Date(), + payload: { + title: 'notification', + topic: 'alerts', + }, + }, + { + recipients: { type: 'broadcast' }, + payload: { title: 'notification', topic: 'alerts' }, + }, + ); + + expect(slack.chat.postMessage).toHaveBeenCalledTimes(1); + expect(slack.chat.postMessage).toHaveBeenCalledWith( + expect.objectContaining({ channel: 'alerts-channel' }), + ); + }); + + it('should fall back to broadcastChannels when no route matches', async () => { + const slack = new WebClient(); + const routesConfig = mockServices.rootConfig({ + data: { + app: { + baseUrl: 'https://example.org', + }, + notifications: { + processors: { + slack: [ + { + token: 'mock-token', + broadcastChannels: ['default-channel'], + broadcastRoutes: [ + { origin: 'plugin:catalog', channel: 'catalog-channel' }, + ], + }, + ], + }, + }, + }, + }); + + const processor = SlackNotificationProcessor.fromConfig(routesConfig, { + auth, + logger, + catalog: catalogServiceMock({ + entities: DEFAULT_ENTITIES_RESPONSE.items, + }), + metrics, + slack, + })[0]; + + await processor.postProcess( + { + origin: 'plugin:unknown', + id: '1234', + user: null, + created: new Date(), + payload: { + title: 'notification', + }, + }, + { + recipients: { type: 'broadcast' }, + payload: { title: 'notification' }, + }, + ); + + expect(slack.chat.postMessage).toHaveBeenCalledTimes(1); + expect(slack.chat.postMessage).toHaveBeenCalledWith( + expect.objectContaining({ channel: 'default-channel' }), + ); + }); + + it('should support multiple channels in a route', async () => { + const slack = new WebClient(); + const routesConfig = mockServices.rootConfig({ + data: { + app: { + baseUrl: 'https://example.org', + }, + notifications: { + processors: { + slack: [ + { + token: 'mock-token', + broadcastRoutes: [ + { + origin: 'plugin:catalog', + channel: ['channel1', 'channel2', 'channel3'], + }, + ], + }, + ], + }, + }, + }, + }); + + const processor = SlackNotificationProcessor.fromConfig(routesConfig, { + auth, + logger, + catalog: catalogServiceMock({ + entities: DEFAULT_ENTITIES_RESPONSE.items, + }), + metrics, + slack, + })[0]; + + await processor.postProcess( + { + origin: 'plugin:catalog', + id: '1234', + user: null, + created: new Date(), + payload: { + title: 'notification', + }, + }, + { + recipients: { type: 'broadcast' }, + payload: { title: 'notification' }, + }, + ); + + expect(slack.chat.postMessage).toHaveBeenCalledTimes(3); + expect(slack.chat.postMessage).toHaveBeenCalledWith( + expect.objectContaining({ channel: 'channel1' }), + ); + expect(slack.chat.postMessage).toHaveBeenCalledWith( + expect.objectContaining({ channel: 'channel2' }), + ); + expect(slack.chat.postMessage).toHaveBeenCalledWith( + expect.objectContaining({ channel: 'channel3' }), + ); + }); + + it('should support single channel as string in a route', async () => { + const slack = new WebClient(); + const routesConfig = mockServices.rootConfig({ + data: { + app: { + baseUrl: 'https://example.org', + }, + notifications: { + processors: { + slack: [ + { + token: 'mock-token', + broadcastRoutes: [ + { topic: 'alerts', channel: 'single-alerts-channel' }, + ], + }, + ], + }, + }, + }, + }); + + const processor = SlackNotificationProcessor.fromConfig(routesConfig, { + auth, + logger, + catalog: catalogServiceMock({ + entities: DEFAULT_ENTITIES_RESPONSE.items, + }), + metrics, + slack, + })[0]; + + await processor.postProcess( + { + origin: 'plugin:any', + id: '1234', + user: null, + created: new Date(), + payload: { + title: 'notification', + topic: 'alerts', + }, + }, + { + recipients: { type: 'broadcast' }, + payload: { title: 'notification', topic: 'alerts' }, + }, + ); + + expect(slack.chat.postMessage).toHaveBeenCalledTimes(1); + expect(slack.chat.postMessage).toHaveBeenCalledWith( + expect.objectContaining({ channel: 'single-alerts-channel' }), + ); + }); + + it('should not send when no route matches and no broadcastChannels configured', async () => { + const slack = new WebClient(); + const routesConfig = mockServices.rootConfig({ + data: { + app: { + baseUrl: 'https://example.org', + }, + notifications: { + processors: { + slack: [ + { + token: 'mock-token', + broadcastRoutes: [ + { origin: 'plugin:catalog', channel: 'catalog-channel' }, + ], + }, + ], + }, + }, + }, + }); + + const processor = SlackNotificationProcessor.fromConfig(routesConfig, { + auth, + logger, + catalog: catalogServiceMock({ + entities: DEFAULT_ENTITIES_RESPONSE.items, + }), + metrics, + slack, + })[0]; + + await processor.postProcess( + { + origin: 'plugin:unknown', + id: '1234', + user: null, + created: new Date(), + payload: { + title: 'notification', + topic: 'unknown-topic', + }, + }, + { + recipients: { type: 'broadcast' }, + payload: { title: 'notification', topic: 'unknown-topic' }, + }, + ); + + expect(slack.chat.postMessage).not.toHaveBeenCalled(); + }); + + it('should match origin+topic route over origin-only route', async () => { + const slack = new WebClient(); + const routesConfig = mockServices.rootConfig({ + data: { + app: { + baseUrl: 'https://example.org', + }, + notifications: { + processors: { + slack: [ + { + token: 'mock-token', + broadcastRoutes: [ + // Origin-only route defined first + { origin: 'plugin:catalog', channel: 'general-catalog' }, + // More specific origin+topic route defined second + { + origin: 'plugin:catalog', + topic: 'entity-deleted', + channel: 'catalog-deletions', + }, + ], + }, + ], + }, + }, + }, + }); + + const processor = SlackNotificationProcessor.fromConfig(routesConfig, { + auth, + logger, + catalog: catalogServiceMock({ + entities: DEFAULT_ENTITIES_RESPONSE.items, + }), + metrics, + slack, + })[0]; + + await processor.postProcess( + { + origin: 'plugin:catalog', + id: '1234', + user: null, + created: new Date(), + payload: { + title: 'notification', + topic: 'entity-deleted', + }, + }, + { + recipients: { type: 'broadcast' }, + payload: { title: 'notification', topic: 'entity-deleted' }, + }, + ); + + // Should use the origin+topic match, not the origin-only match + expect(slack.chat.postMessage).toHaveBeenCalledTimes(1); + expect(slack.chat.postMessage).toHaveBeenCalledWith( + expect.objectContaining({ channel: 'catalog-deletions' }), + ); + }); + }); + describe('when slack.com/bot-notify annotation is missing', () => { it('should not send notification to a group without annotation', async () => { const slack = new WebClient(); @@ -403,6 +982,7 @@ describe('SlackNotificationProcessor', () => { catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, }), + metrics, slack, })[0]; @@ -426,6 +1006,7 @@ describe('SlackNotificationProcessor', () => { catalog: catalogServiceMock({ entities: [DEFAULT_ENTITIES_RESPONSE.items[2]], }), + metrics, slack, })[0]; @@ -465,6 +1046,7 @@ describe('SlackNotificationProcessor', () => { catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, }), + metrics, slack, })[0]; @@ -510,6 +1092,7 @@ describe('SlackNotificationProcessor', () => { catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, }), + metrics, slack, })[0]; @@ -569,6 +1152,7 @@ describe('SlackNotificationProcessor', () => { catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, }), + metrics, slack, })[0]; @@ -648,6 +1232,7 @@ describe('SlackNotificationProcessor', () => { catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, }), + metrics, slack, })[0]; @@ -742,6 +1327,7 @@ describe('SlackNotificationProcessor', () => { catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, }), + metrics, slack, }, )[0]; @@ -819,6 +1405,7 @@ describe('SlackNotificationProcessor', () => { catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, }), + metrics, slack, })[0]; @@ -885,6 +1472,7 @@ describe('SlackNotificationProcessor', () => { catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, }), + metrics, slack, })[0]; @@ -925,6 +1513,7 @@ describe('SlackNotificationProcessor', () => { catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, }), + metrics, slack, })[0]; @@ -953,4 +1542,461 @@ describe('SlackNotificationProcessor', () => { ); }); }); + + describe('when throttling is not configured', () => { + it('should use default concurrency limit of 10 per minute', async () => { + const slack = new WebClient(); + + const processor = SlackNotificationProcessor.fromConfig(config, { + auth, + logger, + catalog: catalogServiceMock({ + entities: DEFAULT_ENTITIES_RESPONSE.items, + }), + metrics, + slack, + })[0]; + + await processor.processOptions({ + recipients: { type: 'entity', entityRef: 'group:default/mock' }, + payload: { title: 'notification' }, + }); + + expect(slack.chat.postMessage).toHaveBeenCalled(); + expect(throttleConfigs).toEqual([ + { + limit: 10, + interval: durationToMilliseconds({ minutes: 1 }), + }, + ]); + }); + }); + + describe('when throttling is configured', () => { + it('should use custom concurrency limit and interval values', async () => { + const slack = new WebClient(); + const throttlingConfig = mockServices.rootConfig({ + data: { + app: { + baseUrl: 'https://example.org', + }, + notifications: { + processors: { + slack: [ + { + token: 'mock-token', + concurrencyLimit: 5, + throttleInterval: 'PT30S', + }, + ], + }, + }, + }, + }); + + const processor = SlackNotificationProcessor.fromConfig( + throttlingConfig, + { + auth, + logger, + catalog: catalogServiceMock({ + entities: DEFAULT_ENTITIES_RESPONSE.items, + }), + metrics, + slack, + }, + )[0]; + + await processor.processOptions({ + recipients: { type: 'entity', entityRef: 'group:default/mock' }, + payload: { title: 'notification' }, + }); + + expect(slack.chat.postMessage).toHaveBeenCalled(); + expect(throttleConfigs).toEqual([ + { + limit: 5, + interval: durationToMilliseconds({ seconds: 30 }), + }, + ]); + }); + }); + + describe('scope-based message updates', () => { + function createMockDb() { + const store = new Map< + string, + { + origin: string; + scope: string; + channel: string; + ts: string; + created_at: Date; + } + >(); + + function storeKey(origin: string, scope: string, channel: string) { + return `${origin}:${scope}:${channel}`; + } + + // Each call to db() creates a fresh query builder that tracks its own + // chained .where()/.insert() arguments, avoiding cross-call interference. + function createQueryBuilder(): any { + let lastWhereArgs: any; + let lastInsertRow: any; + const qb: any = { + where: jest.fn().mockImplementation((args: any) => { + lastWhereArgs = args; + return qb; + }), + first: jest.fn().mockImplementation(() => { + if (lastWhereArgs) { + const key = storeKey( + lastWhereArgs.origin, + lastWhereArgs.scope, + lastWhereArgs.channel, + ); + return Promise.resolve(store.get(key)); + } + return Promise.resolve(undefined); + }), + insert: jest.fn().mockImplementation((row: any) => { + lastInsertRow = row; + store.set(storeKey(row.origin, row.scope, row.channel), row); + return qb; + }), + onConflict: jest.fn().mockReturnThis(), + merge: jest.fn().mockImplementation((row: any) => { + if (lastInsertRow) { + const key = storeKey( + lastInsertRow.origin, + lastInsertRow.scope, + lastInsertRow.channel, + ); + const existing = store.get(key); + if (existing) { + store.set(key, { ...existing, ...row }); + } + } + return Promise.resolve(); + }), + delete: jest.fn().mockResolvedValue(0), + }; + return qb; + } + + const db = jest + .fn() + .mockImplementation(() => createQueryBuilder()) as unknown as Knex; + (db as any).fn = { now: jest.fn().mockReturnValue(new Date()) }; + return { db, store, storeKey }; + } + + function createProcessorWithDb( + slack: WebClient, + db: Knex, + ): SlackNotificationProcessor { + const processor = SlackNotificationProcessor.fromConfig(config, { + auth, + logger, + catalog: catalogServiceMock({ + entities: DEFAULT_ENTITIES_RESPONSE.items, + }), + metrics, + slack, + })[0]; + processor.setDatabase(db); + return processor; + } + + it('should store the message timestamp after initial scoped send', async () => { + const slack = new WebClient(); + const { db, store, storeKey } = createMockDb(); + const processor = createProcessorWithDb(slack, db); + + await processor.postProcess( + { + origin: 'plugin', + id: '1234', + user: 'user:default/mock', + created: new Date(), + payload: { + title: 'notification', + scope: 'deployment-failure/my-service/42', + }, + }, + { + recipients: { type: 'entity', entityRef: 'user:default/mock' }, + payload: { + title: 'notification', + scope: 'deployment-failure/my-service/42', + }, + }, + ); + + expect(slack.chat.postMessage).toHaveBeenCalledTimes(1); + expect(store.size).toBe(1); + const key = storeKey( + 'plugin', + 'deployment-failure/my-service/42', + 'U12345678', + ); + expect(store.get(key)).toEqual( + expect.objectContaining({ + origin: 'plugin', + scope: 'deployment-failure/my-service/42', + channel: 'U12345678', + ts: '1234567890.123456', + }), + ); + }); + + it('should use chat.update when the notification has been updated and a stored ts exists', async () => { + const slack = new WebClient(); + const { db, store, storeKey } = createMockDb(); + + // Pre-populate the store with a previously sent message. + store.set( + storeKey('plugin', 'deployment-failure/my-service/42', 'U12345678'), + { + origin: 'plugin', + scope: 'deployment-failure/my-service/42', + channel: 'U12345678', + ts: '1111111111.111111', + created_at: new Date(), + }, + ); + + const processor = createProcessorWithDb(slack, db); + + await processor.postProcess( + { + origin: 'plugin', + id: '1234', + user: 'user:default/mock', + created: new Date(), + updated: new Date(), + payload: { + title: 'notification', + description: 'Updated with analysis', + scope: 'deployment-failure/my-service/42', + }, + }, + { + recipients: { type: 'entity', entityRef: 'user:default/mock' }, + payload: { + title: 'notification', + description: 'Updated with analysis', + scope: 'deployment-failure/my-service/42', + }, + }, + ); + + expect(slack.chat.postMessage).not.toHaveBeenCalled(); + expect(slack.chat.update).toHaveBeenCalledTimes(1); + + // Verify chat.update receives only the fields it supports (channel, ts, + // text, blocks, attachments) and not the full ChatPostMessageArguments. + const updateArgs = (slack.chat.update as jest.Mock).mock.calls[0][0]; + const updateKeys = Object.keys(updateArgs).sort(); + expect(updateKeys).toEqual(['attachments', 'channel', 'text', 'ts']); + expect(updateArgs.channel).toBe('U12345678'); + expect(updateArgs.ts).toBe('1111111111.111111'); + }); + + it('should fall back to chat.postMessage when notification is updated but no stored ts exists', async () => { + const slack = new WebClient(); + const { db } = createMockDb(); + const processor = createProcessorWithDb(slack, db); + + await processor.postProcess( + { + origin: 'plugin', + id: '1234', + user: 'user:default/mock', + created: new Date(), + updated: new Date(), + payload: { + title: 'notification', + scope: 'deployment-failure/my-service/42', + }, + }, + { + recipients: { type: 'entity', entityRef: 'user:default/mock' }, + payload: { + title: 'notification', + scope: 'deployment-failure/my-service/42', + }, + }, + ); + + // Should fall back to postMessage since there is no stored ts. + expect(slack.chat.update).not.toHaveBeenCalled(); + expect(slack.chat.postMessage).toHaveBeenCalledTimes(1); + }); + + it('should not interact with the database for non-scoped notifications', async () => { + const slack = new WebClient(); + const { db } = createMockDb(); + const processor = createProcessorWithDb(slack, db); + + await processor.postProcess( + { + origin: 'plugin', + id: '1234', + user: 'user:default/mock', + created: new Date(), + payload: { + title: 'notification', + }, + }, + { + recipients: { type: 'entity', entityRef: 'user:default/mock' }, + payload: { title: 'notification' }, + }, + ); + + expect(slack.chat.postMessage).toHaveBeenCalledTimes(1); + // The db function should not have been called since there is no scope. + expect(db).not.toHaveBeenCalled(); + }); + + it('should work without a database (graceful degradation)', async () => { + const slack = new WebClient(); + + // No db set — the processor should still send messages normally. + const processor = SlackNotificationProcessor.fromConfig(config, { + auth, + logger, + catalog: catalogServiceMock({ + entities: DEFAULT_ENTITIES_RESPONSE.items, + }), + metrics, + slack, + })[0]; + + await processor.postProcess( + { + origin: 'plugin', + id: '1234', + user: 'user:default/mock', + created: new Date(), + payload: { + title: 'notification', + scope: 'deployment-failure/my-service/42', + }, + }, + { + recipients: { type: 'entity', entityRef: 'user:default/mock' }, + payload: { + title: 'notification', + scope: 'deployment-failure/my-service/42', + }, + }, + ); + + expect(slack.chat.postMessage).toHaveBeenCalledTimes(1); + expect(slack.chat.update).not.toHaveBeenCalled(); + }); + + it('should handle concurrent postProcess calls with different scopes correctly', async () => { + const slack = new WebClient(); + const { db, store, storeKey } = createMockDb(); + + // Pre-populate stored timestamps for both scopes. + store.set(storeKey('plugin', 'scope-a', 'U12345678'), { + origin: 'plugin', + scope: 'scope-a', + channel: 'U12345678', + ts: '1111111111.111111', + created_at: new Date(), + }); + store.set(storeKey('plugin', 'scope-b', 'U12345678'), { + origin: 'plugin', + scope: 'scope-b', + channel: 'U12345678', + ts: '2222222222.222222', + created_at: new Date(), + }); + + const processor = createProcessorWithDb(slack, db); + + // Fire both postProcess calls concurrently with different scopes. + await Promise.all([ + processor.postProcess( + { + origin: 'plugin', + id: '1', + user: 'user:default/mock', + created: new Date(), + updated: new Date(), + payload: { title: 'A', scope: 'scope-a' }, + }, + { + recipients: { type: 'entity', entityRef: 'user:default/mock' }, + payload: { title: 'A', scope: 'scope-a' }, + }, + ), + processor.postProcess( + { + origin: 'plugin', + id: '2', + user: 'user:default/mock', + created: new Date(), + updated: new Date(), + payload: { title: 'B', scope: 'scope-b' }, + }, + { + recipients: { type: 'entity', entityRef: 'user:default/mock' }, + payload: { title: 'B', scope: 'scope-b' }, + }, + ), + ]); + + // Both should use chat.update, not postMessage. + expect(slack.chat.postMessage).not.toHaveBeenCalled(); + expect(slack.chat.update).toHaveBeenCalledTimes(2); + + // Each update should use the correct ts for its scope. + const updateCalls = (slack.chat.update as jest.Mock).mock.calls; + const timestamps = updateCalls.map((call: any[]) => call[0].ts).sort(); + expect(timestamps).toEqual(['1111111111.111111', '2222222222.222222']); + }); + + it('should not collide across different origins with the same scope', async () => { + const slack = new WebClient(); + const { db, store, storeKey } = createMockDb(); + + // Pre-populate a stored timestamp for origin-a only. + store.set(storeKey('origin-a', 'shared-scope', 'U12345678'), { + origin: 'origin-a', + scope: 'shared-scope', + channel: 'U12345678', + ts: '1111111111.111111', + created_at: new Date(), + }); + + const processor = createProcessorWithDb(slack, db); + + // Send an update from origin-b with the same scope — should NOT find + // the stored ts from origin-a, and should fall back to postMessage. + await processor.postProcess( + { + origin: 'origin-b', + id: '1', + user: 'user:default/mock', + created: new Date(), + updated: new Date(), + payload: { title: 'From B', scope: 'shared-scope' }, + }, + { + recipients: { type: 'entity', entityRef: 'user:default/mock' }, + payload: { title: 'From B', scope: 'shared-scope' }, + }, + ); + + expect(slack.chat.update).not.toHaveBeenCalled(); + expect(slack.chat.postMessage).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts index f9b00c9a58..bcbce0676c 100644 --- a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts +++ b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts @@ -15,27 +15,44 @@ */ import { AuthService, LoggerService } from '@backstage/backend-plugin-api'; +import { + MetricsService, + MetricsServiceCounter, +} from '@backstage/backend-plugin-api/alpha'; import { Entity, isUserEntity, parseEntityRef, + stringifyEntityRef, UserEntity, } from '@backstage/catalog-model'; -import { Config } from '@backstage/config'; -import { NotFoundError } from '@backstage/errors'; +import { Config, readDurationFromConfig } from '@backstage/config'; +import { NotFoundError, toError } from '@backstage/errors'; import { Notification } from '@backstage/plugin-notifications-common'; import { NotificationProcessor, NotificationSendOptions, } from '@backstage/plugin-notifications-node'; import { durationToMilliseconds } from '@backstage/types'; -import { Counter, metrics } from '@opentelemetry/api'; -import { ChatPostMessageArguments, WebClient } from '@slack/web-api'; +import { + ChatPostMessageArguments, + ChatUpdateArguments, + WebClient, +} from '@slack/web-api'; import DataLoader from 'dataloader'; +import { Knex } from 'knex'; import pThrottle from 'p-throttle'; import { ANNOTATION_SLACK_BOT_NOTIFY } from './constants'; +import { BroadcastRoute } from './types'; import { ExpiryMap, toChatPostMessageArgs } from './util'; import { CatalogService } from '@backstage/plugin-catalog-node'; +import { SlackBlockKitRenderer } from '../extensions'; + +interface ScopeContext { + origin: string; + scope?: string; + isUpdate?: boolean; +} export class SlackNotificationProcessor implements NotificationProcessor { private readonly logger: LoggerService; @@ -44,12 +61,19 @@ export class SlackNotificationProcessor implements NotificationProcessor { private readonly slack: WebClient; private readonly sendNotifications: ( opts: ChatPostMessageArguments[], + scopeContext?: ScopeContext, ) => Promise; - private readonly messagesSent: Counter; - private readonly messagesFailed: Counter; + private readonly messagesSent: MetricsServiceCounter; + private readonly messagesFailed: MetricsServiceCounter; + private readonly messagesUpdated: MetricsServiceCounter; + private db?: Knex; private readonly broadcastChannels?: string[]; + private readonly broadcastRoutes?: BroadcastRoute[]; private readonly entityLoader: DataLoader; private readonly username?: string; + private readonly concurrencyLimit: number; + private readonly throttleInterval: number; + private readonly blockKitRenderer?: SlackBlockKitRenderer; static fromConfig( config: Config, @@ -57,8 +81,10 @@ export class SlackNotificationProcessor implements NotificationProcessor { auth: AuthService; logger: LoggerService; catalog: CatalogService; + metrics: MetricsService; slack?: WebClient; broadcastChannels?: string[]; + blockKitRenderer?: SlackBlockKitRenderer; }, ): SlackNotificationProcessor[] { const slackConfig = @@ -68,10 +94,23 @@ export class SlackNotificationProcessor implements NotificationProcessor { const slack = options.slack ?? new WebClient(token); const broadcastChannels = c.getOptionalStringArray('broadcastChannels'); const username = c.getOptionalString('username'); + const broadcastRoutesConfig = c.getOptionalConfigArray('broadcastRoutes'); + const broadcastRoutes = broadcastRoutesConfig?.map(route => + this.parseBroadcastRoute(route), + ); + const concurrencyLimit = c.getOptionalNumber('concurrencyLimit') ?? 10; + const throttleInterval = c.has('throttleInterval') + ? durationToMilliseconds( + readDurationFromConfig(c, { key: 'throttleInterval' }), + ) + : durationToMilliseconds({ minutes: 1 }); return new SlackNotificationProcessor({ slack, broadcastChannels, + broadcastRoutes, username, + concurrencyLimit, + throttleInterval, ...options, }); }); @@ -82,17 +121,38 @@ export class SlackNotificationProcessor implements NotificationProcessor { auth: AuthService; logger: LoggerService; catalog: CatalogService; + metrics: MetricsService; broadcastChannels?: string[]; + broadcastRoutes?: BroadcastRoute[]; username?: string; + concurrencyLimit?: number; + throttleInterval?: number; + blockKitRenderer?: SlackBlockKitRenderer; }) { - const { auth, catalog, logger, slack, broadcastChannels, username } = - options; + const { + auth, + catalog, + logger, + metrics, + slack, + broadcastChannels, + broadcastRoutes, + username, + concurrencyLimit, + throttleInterval, + blockKitRenderer, + } = options; this.logger = logger; this.catalog = catalog; this.auth = auth; this.slack = slack; this.broadcastChannels = broadcastChannels; + this.broadcastRoutes = broadcastRoutes; this.username = username; + this.concurrencyLimit = concurrencyLimit ?? 10; + this.throttleInterval = + throttleInterval ?? durationToMilliseconds({ minutes: 1 }); + this.blockKitRenderer = blockKitRenderer; this.entityLoader = new DataLoader( async entityRefs => { @@ -119,38 +179,56 @@ export class SlackNotificationProcessor implements NotificationProcessor { }, ); - const meter = metrics.getMeter('default'); - this.messagesSent = meter.createCounter( + this.messagesSent = metrics.createCounter( 'notifications.processors.slack.sent.count', { description: 'Number of messages sent to Slack successfully', + unit: '{message}', }, ); - this.messagesFailed = meter.createCounter( + this.messagesFailed = metrics.createCounter( 'notifications.processors.slack.error.count', { description: 'Number of messages that failed to send to Slack', + unit: '{message}', + }, + ); + this.messagesUpdated = metrics.createCounter( + 'notifications.processors.slack.update.count', + { + description: + 'Number of existing Slack messages updated via scope matching', + unit: '{message}', }, ); const throttle = pThrottle({ - limit: 10, - interval: durationToMilliseconds({ minutes: 1 }), + limit: this.concurrencyLimit, + interval: this.throttleInterval, }); - const throttled = throttle((opts: ChatPostMessageArguments) => - this.sendNotification(opts), + const throttled = throttle( + (opts: ChatPostMessageArguments, ctx?: ScopeContext) => + this.sendNotification(opts, ctx), ); - this.sendNotifications = async (opts: ChatPostMessageArguments[]) => { + this.sendNotifications = async ( + opts: ChatPostMessageArguments[], + scopeContext?: ScopeContext, + ) => { const results = await Promise.allSettled( - opts.map(message => throttled(message)), + opts.map(message => throttled(message, scopeContext)), ); - let successCount = 0; + let sentCount = 0; + let updateCount = 0; let failureCount = 0; results.forEach((result, index) => { if (result.status === 'fulfilled') { - successCount++; + if (result.value === 'updated') { + updateCount++; + } else { + sentCount++; + } } else { this.logger.error( `Failed to send Slack channel notification to ${opts[index].channel}: ${result.reason.message}`, @@ -159,11 +237,16 @@ export class SlackNotificationProcessor implements NotificationProcessor { } }); - this.messagesSent.add(successCount); + this.messagesSent.add(sentCount); + this.messagesUpdated.add(updateCount); this.messagesFailed.add(failureCount); }; } + setDatabase(db: Knex): void { + this.db = db; + } + getName(): string { return 'SlackNotificationProcessor'; } @@ -191,9 +274,7 @@ export class SlackNotificationProcessor implements NotificationProcessor { channel = await this.getSlackNotificationTarget(entityRef); } catch (error) { this.logger.error( - `Failed to get Slack channel for entity: ${ - (error as Error).message - }`, + `Failed to get Slack channel for entity: ${toError(error).message}`, ); return; } @@ -213,6 +294,7 @@ export class SlackNotificationProcessor implements NotificationProcessor { channel, payload: options.payload, username: this.username, + blockKitRenderer: this.blockKitRenderer, }); this.logger.debug( @@ -235,12 +317,20 @@ export class SlackNotificationProcessor implements NotificationProcessor { // Handle broadcast case if (notification.user === null) { - destinations.push(...(this.broadcastChannels ?? [])); + const routedChannels = this.getBroadcastDestinations(notification); + destinations.push(...routedChannels); } else if (options.recipients.type === 'entity') { // Handle user-specific notification const entityRefs = [options.recipients.entityRef].flat(); - if (entityRefs.some(e => parseEntityRef(e).kind === 'group')) { - // We've already dispatched a slack channel message, so let's not send a DM. + const explicitUserEntityRefs = entityRefs + .filter(entityRef => parseEntityRef(entityRef).kind === 'user') + .map(entityRef => stringifyEntityRef(parseEntityRef(entityRef))); + const normalizedUserRef = stringifyEntityRef( + parseEntityRef(notification.user), + ); + + if (!explicitUserEntityRefs.includes(normalizedUserRef)) { + // This user was resolved from a non-user entity. Skip sending a DM. return; } @@ -272,6 +362,7 @@ export class SlackNotificationProcessor implements NotificationProcessor { channel, payload: formattedPayload, username: this.username, + blockKitRenderer: this.blockKitRenderer, }), ); @@ -280,8 +371,11 @@ export class SlackNotificationProcessor implements NotificationProcessor { this.logger.debug(`Sending notification: ${JSON.stringify(payload)}`); }); - // Send notifications - await this.sendNotifications(outbound); + await this.sendNotifications(outbound, { + origin: notification.origin, + scope: notification.payload.scope, + isUpdate: !!notification.updated, + }); } private async formatPayloadDescriptionForSlack( @@ -377,11 +471,176 @@ export class SlackNotificationProcessor implements NotificationProcessor { } } - async sendNotification(args: ChatPostMessageArguments): Promise { + async sendNotification( + args: ChatPostMessageArguments, + scopeContext?: ScopeContext, + ): Promise<'sent' | 'updated'> { + const channel = args.channel as string; + const scope = scopeContext?.scope; + + // If this is a scoped update, try to update the existing Slack message. + const origin = scopeContext?.origin; + if (scopeContext?.isUpdate && origin && scope && this.db) { + const storedTs = await this.getStoredTimestamp(origin, scope, channel); + if (storedTs) { + const updateArgs = { + channel, + ts: storedTs, + ...('text' in args ? { text: args.text } : {}), + ...('blocks' in args ? { blocks: args.blocks } : {}), + ...('attachments' in args ? { attachments: args.attachments } : {}), + } as ChatUpdateArguments; + const updateResponse = await this.slack.chat.update(updateArgs); + + if (!updateResponse.ok) { + throw new Error( + `Failed to update notification: ${updateResponse.error}`, + ); + } + + return 'updated'; + } + } + + // Send a new message. const response = await this.slack.chat.postMessage(args); if (!response.ok) { throw new Error(`Failed to send notification: ${response.error}`); } + + // Persist the message timestamp for future scope-based updates. + if (origin && scope && response.ts && this.db) { + await this.saveTimestamp(origin, scope, channel, response.ts); + } + + return 'sent'; + } + + private async getStoredTimestamp( + origin: string, + scope: string, + channel: string, + ): Promise { + try { + const row = await this.db!('slack_message_timestamps') + .where({ origin, scope, channel }) + .first(); + return row?.ts; + } catch (error) { + this.logger.warn('Failed to look up stored Slack message timestamp', { + origin, + scope, + channel, + error, + }); + return undefined; + } + } + + private async saveTimestamp( + origin: string, + scope: string, + channel: string, + ts: string, + ): Promise { + try { + const now = this.db!.fn.now(); + await this.db!('slack_message_timestamps') + .insert({ origin, scope, channel, ts, created_at: now }) + .onConflict(['origin', 'scope', 'channel']) + .merge({ ts, created_at: now }); + } catch (error) { + this.logger.warn('Failed to persist Slack message timestamp', { + origin, + scope, + channel, + error, + }); + } + } + + private static parseBroadcastRoute(route: Config): BroadcastRoute { + const channelValue = route.getOptional('channel'); + let channels: string[]; + + if (typeof channelValue === 'string') { + channels = [channelValue]; + } else if (Array.isArray(channelValue)) { + channels = channelValue as string[]; + } else { + throw new Error( + 'broadcastRoutes entry must have a channel property (string or string[])', + ); + } + + return { + origin: route.getOptionalString('origin'), + topic: route.getOptionalString('topic'), + channels, + }; + } + + /** + * Gets the destination channels for a broadcast notification based on + * configured routes. Routes are matched by origin and/or topic. + * + * Matching precedence: + * 1. Routes with both origin AND topic matching (most specific) + * 2. Routes with only origin matching + * 3. Routes with only topic matching + * 4. Default broadcastChannels (least specific fallback) + * + * The first matching route wins within each precedence level. + */ + private getBroadcastDestinations(notification: Notification): string[] { + const { origin } = notification; + const { topic } = notification.payload; + + if (!this.broadcastRoutes || this.broadcastRoutes.length === 0) { + // Fall back to legacy broadcastChannels config + return this.broadcastChannels ?? []; + } + + // Find most specific match + // Priority 1: origin AND topic match + const originAndTopicMatch = this.broadcastRoutes.find( + route => + route.origin !== undefined && + route.topic !== undefined && + route.origin === origin && + route.topic === topic, + ); + + if (originAndTopicMatch) { + return originAndTopicMatch.channels; + } + + // Priority 2: origin-only match (no topic specified in route) + const originOnlyMatch = this.broadcastRoutes.find( + route => + route.origin !== undefined && + route.topic === undefined && + route.origin === origin, + ); + + if (originOnlyMatch) { + return originOnlyMatch.channels; + } + + // Priority 3: topic-only match (no origin specified in route) + const topicOnlyMatch = this.broadcastRoutes.find( + route => + route.topic !== undefined && + route.origin === undefined && + route.topic === topic, + ); + + if (topicOnlyMatch) { + return topicOnlyMatch.channels; + } + + // No match found, fall back to legacy broadcastChannels + return this.broadcastChannels ?? []; } } diff --git a/plugins/notifications-backend-module-slack/src/lib/types.ts b/plugins/notifications-backend-module-slack/src/lib/types.ts index bd87e9656c..316df03462 100644 --- a/plugins/notifications-backend-module-slack/src/lib/types.ts +++ b/plugins/notifications-backend-module-slack/src/lib/types.ts @@ -18,3 +18,16 @@ export interface SlackNotificationOptions { url: string; payload: string; } + +/** + * Configuration for routing broadcast notifications to specific Slack channels + * based on origin and/or topic. + */ +export type BroadcastRoute = { + /** The origin to match (e.g., 'plugin:catalog', 'external:my-service') */ + origin?: string; + /** The topic to match (e.g., 'entity-updated', 'alerts') */ + topic?: string; + /** The Slack channel(s) to send to */ + channels: string[]; +}; diff --git a/plugins/notifications-backend-module-slack/src/lib/util.ts b/plugins/notifications-backend-module-slack/src/lib/util.ts index 33293ea24f..0c13b21be6 100644 --- a/plugins/notifications-backend-module-slack/src/lib/util.ts +++ b/plugins/notifications-backend-module-slack/src/lib/util.ts @@ -19,13 +19,16 @@ import { NotificationSeverity, } from '@backstage/plugin-notifications-common'; import { ChatPostMessageArguments, KnownBlock } from '@slack/web-api'; +import { SlackBlockKitRenderer } from '../extensions'; export function toChatPostMessageArgs(options: { channel: string; payload: NotificationPayload; username?: string; + blockKitRenderer?: SlackBlockKitRenderer; }): ChatPostMessageArguments { - const { channel, payload, username } = options; + const { channel, payload, username, blockKitRenderer } = options; + const blocks = (blockKitRenderer ?? toSlackBlockKit)(payload); const args: ChatPostMessageArguments = { channel, @@ -34,7 +37,7 @@ export function toChatPostMessageArgs(options: { attachments: [ { color: getColor(payload.severity), - blocks: toSlackBlockKit(payload), + blocks, fallback: payload.title, }, ], diff --git a/plugins/notifications-backend-module-slack/src/module.ts b/plugins/notifications-backend-module-slack/src/module.ts index ff783ef104..450d10f120 100644 --- a/plugins/notifications-backend-module-slack/src/module.ts +++ b/plugins/notifications-backend-module-slack/src/module.ts @@ -16,10 +16,34 @@ import { coreServices, createBackendModule, + resolvePackagePath, } from '@backstage/backend-plugin-api'; +import { Knex } from 'knex'; +import { metricsServiceRef } from '@backstage/backend-plugin-api/alpha'; import { notificationsProcessingExtensionPoint } from '@backstage/plugin-notifications-node'; import { SlackNotificationProcessor } from './lib/SlackNotificationProcessor'; import { catalogServiceRef } from '@backstage/plugin-catalog-node'; +import { + notificationsSlackBlockKitExtensionPoint, + SlackBlockKitRenderer, +} from './extensions'; + +const MIGRATIONS_DIR = resolvePackagePath( + '@backstage/plugin-notifications-backend-module-slack', + 'migrations', +); + +const DB_MIGRATIONS_TABLE = 'notifications_module_slack__knex_migrations'; +const CLEANUP_RETENTION_SECONDS = 24 * 60 * 60; // 24 hours + +function nowMinus(knex: Knex, seconds: number): Knex.Raw { + if (knex.client.config.client.includes('sqlite3')) { + return knex.raw(`datetime('now', ?)`, [`-${seconds} seconds`]); + } else if (knex.client.config.client.includes('mysql')) { + return knex.raw(`now() - interval ${seconds} second`); + } + return knex.raw(`now() - interval '${seconds} seconds'`); +} /** * The Slack notification processor for use with the notifications plugin. @@ -31,6 +55,16 @@ export const notificationsModuleSlack = createBackendModule({ pluginId: 'notifications', moduleId: 'slack', register(reg) { + let blockKitRenderer: SlackBlockKitRenderer | undefined; + reg.registerExtensionPoint(notificationsSlackBlockKitExtensionPoint, { + setBlockKitRenderer(renderer) { + if (blockKitRenderer) { + throw new Error(`Slack block kit renderer was already registered`); + } + blockKitRenderer = renderer; + }, + }); + reg.registerInit({ deps: { auth: coreServices.auth, @@ -38,15 +72,70 @@ export const notificationsModuleSlack = createBackendModule({ logger: coreServices.logger, catalog: catalogServiceRef, notifications: notificationsProcessingExtensionPoint, + metrics: metricsServiceRef, + database: coreServices.database, + scheduler: coreServices.scheduler, }, - async init({ auth, config, logger, catalog, notifications }) { - notifications.addProcessor( - SlackNotificationProcessor.fromConfig(config, { - auth, - logger, - catalog, - }), - ); + async init({ + auth, + config, + logger, + catalog, + notifications, + metrics, + database, + scheduler, + }) { + const processors = SlackNotificationProcessor.fromConfig(config, { + auth, + logger, + catalog, + metrics, + blockKitRenderer, + }); + + if (processors.length === 0) { + return; + } + + const db = await database.getClient(); + + if (!database.migrations?.skip) { + await db.migrate.latest({ + directory: MIGRATIONS_DIR, + tableName: DB_MIGRATIONS_TABLE, + }); + } + + // Attach the DB to each processor now that migrations have run. + for (const processor of processors) { + processor.setDatabase(db); + } + + notifications.addProcessor(processors); + + // Clean up old message timestamp records daily. These records are only + // needed for the short window between initial send and scope-based + // update (typically minutes), so a 24-hour retention is sufficient. + await scheduler.scheduleTask({ + id: 'slack-message-timestamps-cleanup', + frequency: { hours: 24 }, + timeout: { minutes: 5 }, + initialDelay: { hours: 2 }, + scope: 'global', + fn: async () => { + const deleted = await db('slack_message_timestamps') + .where( + 'created_at', + '<=', + nowMinus(db, CLEANUP_RETENTION_SECONDS), + ) + .delete(); + logger.info('Cleaned up old Slack message timestamps', { + deleted, + }); + }, + }); }, }); }, diff --git a/plugins/notifications-backend/CHANGELOG.md b/plugins/notifications-backend/CHANGELOG.md index 521aaac7bf..9ead670787 100644 --- a/plugins/notifications-backend/CHANGELOG.md +++ b/plugins/notifications-backend/CHANGELOG.md @@ -1,5 +1,213 @@ # @backstage/plugin-notifications-backend +## 0.6.4 + +### Patch Changes + +- 4c1fd43: Added an action to get a user's notifications +- 070af42: Fix handling of `limit=0` in `getNotifications` query to return empty results instead of all notifications +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/errors@1.3.0 + - @backstage/catalog-model@1.8.0 + - @backstage/plugin-catalog-node@2.2.0 + - @backstage/plugin-signals-node@0.2.0 + - @backstage/plugin-notifications-node@0.2.25 + - @backstage/config@1.3.7 + - @backstage/plugin-notifications-common@0.2.2 + +## 0.6.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/plugin-catalog-node@2.2.0-next.2 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/catalog-model@1.7.8-next.0 + - @backstage/config@1.3.7-next.0 + - @backstage/plugin-notifications-node@0.2.25-next.2 + - @backstage/plugin-notifications-common@0.2.2-next.0 + - @backstage/plugin-signals-node@0.1.30-next.2 + +## 0.6.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-notifications-node@0.2.25-next.1 + - @backstage/plugin-signals-node@0.1.30-next.1 + +## 0.6.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-catalog-node@2.1.1-next.0 + - @backstage/plugin-notifications-node@0.2.25-next.0 + - @backstage/plugin-signals-node@0.1.30-next.0 + - @backstage/catalog-model@1.7.7 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-notifications-common@0.2.1 + +## 0.6.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/catalog-model@1.7.7 + - @backstage/plugin-notifications-node@0.2.24 + - @backstage/plugin-signals-node@0.1.29 + +## 0.6.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.2 + - @backstage/plugin-notifications-node@0.2.24-next.2 + - @backstage/plugin-signals-node@0.1.29-next.1 + +## 0.6.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-notifications-common@0.2.1 + - @backstage/plugin-notifications-node@0.2.24-next.0 + - @backstage/plugin-signals-node@0.1.29-next.0 + +## 0.6.2 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 5e3ef57: Added `peerModules` metadata declaring recommended modules for cross-plugin integrations. +- e9eb400: Allow configuring included topics for email notifications. +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-notifications-common@0.2.1 + - @backstage/plugin-notifications-node@0.2.23 + - @backstage/plugin-signals-node@0.1.28 + +## 0.6.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-notifications-node@0.2.23-next.1 + +## 0.6.2-next.1 + +### Patch Changes + +- 5e3ef57: Added `peerModules` metadata declaring recommended modules for cross-plugin integrations. +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.1 + +## 0.6.2-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-notifications-node@0.2.23-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/plugin-signals-node@0.1.28-next.0 + +## 0.6.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-signals-node@0.1.27 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-notifications-node@0.2.22 + +## 0.6.1-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-signals-node@0.1.27-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/plugin-notifications-node@0.2.22-next.1 + +## 0.6.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-signals-node@0.1.27-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-notifications-node@0.2.22-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-notifications-common@0.2.0 + +## 0.6.0 + +### Minor Changes + +- 87e597c: Adds support for default configuration for an entire notification channel. + This setting will also be inherited down to origins and topics while still respecting the users individual choices. + + This will be handy if you want to use a "opt-in" strategy. + +### Patch Changes + +- 15fb764: Show default settings for notifications even before receiving first notification. + + Previously, it was not possible for the users to see or modify their notification settings until they had received at + least one notification from specific origin or topic. + This update ensures that default settings are displayed from the outset, + allowing users to customize their preferences immediately. + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-notifications-node@0.2.21 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-signals-node@0.1.26 + ## 0.6.0-next.2 ### Minor Changes diff --git a/plugins/notifications-backend/knip-report.md b/plugins/notifications-backend/knip-report.md index 97d5b385fd..2661c35327 100644 --- a/plugins/notifications-backend/knip-report.md +++ b/plugins/notifications-backend/knip-report.md @@ -1,3 +1,2 @@ # Knip report - diff --git a/plugins/notifications-backend/migrations/20250317_addTopic.js b/plugins/notifications-backend/migrations/20250317_addTopic.js index c2d4d7b10e..13085d5b1c 100644 --- a/plugins/notifications-backend/migrations/20250317_addTopic.js +++ b/plugins/notifications-backend/migrations/20250317_addTopic.js @@ -16,7 +16,7 @@ // @ts-check -const crypto = require('crypto'); +const crypto = require('node:crypto'); /** * @param {import('knex').Knex} knex diff --git a/plugins/notifications-backend/package.json b/plugins/notifications-backend/package.json index 6c4a73afba..98dd686a3b 100644 --- a/plugins/notifications-backend/package.json +++ b/plugins/notifications-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend", - "version": "0.6.0-next.2", + "version": "0.6.4", "backstage": { "role": "backend-plugin", "pluginId": "notifications", @@ -9,6 +9,9 @@ "@backstage/plugin-notifications-backend", "@backstage/plugin-notifications-common", "@backstage/plugin-notifications-node" + ], + "peerModules": [ + "@backstage/plugin-scaffolder-backend-module-notifications" ] }, "publishConfig": { @@ -48,7 +51,7 @@ "@backstage/plugin-notifications-node": "workspace:^", "@backstage/plugin-signals-node": "workspace:^", "@backstage/types": "workspace:^", - "express": "^4.17.1", + "express": "^4.22.0", "express-promise-router": "^4.1.0", "knex": "^3.0.0", "p-throttle": "^4.1.1", diff --git a/plugins/notifications-backend/src/actions/createGetNotificationsAction.test.ts b/plugins/notifications-backend/src/actions/createGetNotificationsAction.test.ts new file mode 100644 index 0000000000..19dfbeaffe --- /dev/null +++ b/plugins/notifications-backend/src/actions/createGetNotificationsAction.test.ts @@ -0,0 +1,215 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createGetNotificationsAction } from './createGetNotificationsAction'; +import { actionsRegistryServiceMock } from '@backstage/backend-test-utils/alpha'; +import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; +import { NotificationsStore } from '../database'; +import { Notification } from '@backstage/plugin-notifications-common'; + +const mockNotification: Notification = { + id: 'notif-1', + user: 'user:default/test', + created: new Date('2025-04-01T10:00:00.000Z'), + origin: 'catalog', + payload: { + title: 'Test notification', + severity: 'normal', + }, +}; + +function createMockStore( + overrides: Partial = {}, +): NotificationsStore { + return { + getNotifications: jest.fn().mockResolvedValue([]), + getNotificationsCount: jest.fn().mockResolvedValue(0), + saveNotification: jest.fn(), + saveBroadcast: jest.fn(), + getExistingScopeNotification: jest.fn(), + getExistingScopeBroadcast: jest.fn(), + restoreExistingNotification: jest.fn(), + getNotification: jest.fn(), + getStatus: jest.fn(), + markRead: jest.fn(), + markUnread: jest.fn(), + markSaved: jest.fn(), + markUnsaved: jest.fn(), + getUserNotificationOrigins: jest.fn(), + getUserNotificationTopics: jest.fn(), + getNotificationSettings: jest.fn(), + saveNotificationSettings: jest.fn(), + getTopics: jest.fn(), + clearNotifications: jest.fn(), + ...overrides, + } as unknown as NotificationsStore; +} + +describe('createGetNotificationsAction', () => { + const userCredentials = mockCredentials.user('user:default/test'); + const auth = mockServices.auth(); + + it('defaults to returning only unread notifications', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const store = createMockStore({ + getNotifications: jest.fn().mockResolvedValue([mockNotification]), + getNotificationsCount: jest.fn().mockResolvedValue(1), + }); + + createGetNotificationsAction({ + store, + auth, + actionsRegistry: mockActionsRegistry, + }); + + const result = await mockActionsRegistry.invoke({ + id: 'test:get-notifications', + input: {}, + credentials: userCredentials, + }); + + expect(store.getNotifications).toHaveBeenCalledWith( + expect.objectContaining({ + user: 'user:default/test', + read: false, + saved: undefined, + offset: 0, + limit: 10, + }), + ); + expect(result).toEqual({ + output: { notifications: [mockNotification], totalCount: 1 }, + }); + }); + + it('passes read: true when view is "read"', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const store = createMockStore(); + + createGetNotificationsAction({ + store, + auth, + actionsRegistry: mockActionsRegistry, + }); + + await mockActionsRegistry.invoke({ + id: 'test:get-notifications', + input: { view: 'read' }, + credentials: userCredentials, + }); + + expect(store.getNotifications).toHaveBeenCalledWith( + expect.objectContaining({ read: true, saved: undefined }), + ); + }); + + it('passes saved: true when view is "saved"', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const store = createMockStore(); + + createGetNotificationsAction({ + store, + auth, + actionsRegistry: mockActionsRegistry, + }); + + await mockActionsRegistry.invoke({ + id: 'test:get-notifications', + input: { view: 'saved' }, + credentials: userCredentials, + }); + + expect(store.getNotifications).toHaveBeenCalledWith( + expect.objectContaining({ saved: true, read: undefined }), + ); + }); + + it('passes no read or saved filter when view is "all"', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const store = createMockStore(); + + createGetNotificationsAction({ + store, + auth, + actionsRegistry: mockActionsRegistry, + }); + + await mockActionsRegistry.invoke({ + id: 'test:get-notifications', + input: { view: 'all' }, + credentials: userCredentials, + }); + + expect(store.getNotifications).toHaveBeenCalledWith( + expect.objectContaining({ read: undefined, saved: undefined }), + ); + }); + + it('forwards search, topic, minimumSeverity, createdAfter, offset, and limit', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const store = createMockStore(); + + createGetNotificationsAction({ + store, + auth, + actionsRegistry: mockActionsRegistry, + }); + + await mockActionsRegistry.invoke({ + id: 'test:get-notifications', + input: { + view: 'all', + search: 'deploy', + topic: 'ci/cd', + minimumSeverity: 'high', + createdAfter: '2025-04-01T00:00:00Z', + offset: 20, + limit: 50, + }, + credentials: userCredentials, + }); + + expect(store.getNotifications).toHaveBeenCalledWith( + expect.objectContaining({ + user: 'user:default/test', + search: 'deploy', + topic: 'ci/cd', + minimumSeverity: 'high', + createdAfter: new Date('2025-04-01T00:00:00Z'), + offset: 20, + limit: 50, + }), + ); + }); + + it('throws InputError when called without user credentials', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const store = createMockStore(); + + createGetNotificationsAction({ + store, + auth, + actionsRegistry: mockActionsRegistry, + }); + + await expect( + mockActionsRegistry.invoke({ + id: 'test:get-notifications', + input: {}, + credentials: mockCredentials.service(), + }), + ).rejects.toThrow('This action requires user credentials'); + }); +}); diff --git a/plugins/notifications-backend/src/actions/createGetNotificationsAction.ts b/plugins/notifications-backend/src/actions/createGetNotificationsAction.ts new file mode 100644 index 0000000000..2f6b980fce --- /dev/null +++ b/plugins/notifications-backend/src/actions/createGetNotificationsAction.ts @@ -0,0 +1,213 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ActionsRegistryService } from '@backstage/backend-plugin-api/alpha'; +import { AuthService } from '@backstage/backend-plugin-api'; +import { NotAllowedError } from '@backstage/errors'; +import { NotificationsStore } from '../database'; + +export const createGetNotificationsAction = ({ + store, + auth, + actionsRegistry, +}: { + store: NotificationsStore; + auth: AuthService; + actionsRegistry: ActionsRegistryService; +}) => { + actionsRegistry.register({ + name: 'get-notifications', + title: 'Get Notifications', + description: ` +Fetches notifications for the currently authenticated user from the Backstage notifications system. + +Each notification has an \`id\`, \`origin\` (the plugin or service that sent it), a \`payload\` with +\`title\`, optional \`description\`, optional \`link\`, \`severity\` (one of "critical", "high", "normal", "low"), +and optional \`topic\`. Notifications also carry \`created\` and optionally \`read\`, \`saved\`, and \`updated\` timestamps. + +## Filters + +Use the \`view\` field to control which notifications are returned: +- \`"unread"\` (default) — only notifications the user has not yet read. Use this to surface new, actionable items. +- \`"read"\` — only notifications the user has already read. +- \`"saved"\` — only notifications the user has explicitly saved/bookmarked. +- \`"all"\` — all notifications regardless of read or saved status. + +Additional filters can be combined with \`view\`: +- \`search\` — free-text search across notification titles and descriptions. +- \`topic\` — filter to a specific topic string, e.g. "ci/cd" or "deployment". +- \`minimumSeverity\` — minimum severity level; returns this severity and anything more severe ("critical" > "high" > "normal" > "low"). +- \`createdAfter\` — ISO 8601 datetime string; only return notifications created after this time. + +## Pagination + +Use \`offset\` and \`limit\` to paginate. The response includes \`totalCount\` so you can calculate further pages. + +## Examples + +- Get my unread notifications: use default values (no input required). +- Get all notifications from the past week: set \`view: "all"\` and \`createdAfter\` to 7 days ago. +- Get high-priority unread alerts: set \`minimumSeverity: "high"\` (view defaults to "unread"). + `, + attributes: { + destructive: false, + readOnly: true, + idempotent: true, + }, + schema: { + input: z => + z.object({ + view: z + .enum(['unread', 'read', 'saved', 'all']) + .optional() + .describe( + 'Which notifications to return. Defaults to "unread". "unread" returns only unread notifications, "read" returns only read notifications, "saved" returns bookmarked notifications, "all" returns everything.', + ), + search: z + .string() + .optional() + .describe( + 'Free-text search string to filter notifications by title or description.', + ), + topic: z + .string() + .optional() + .describe( + 'Filter to notifications with this specific topic, e.g. "ci/cd".', + ), + minimumSeverity: z + .enum(['critical', 'high', 'normal', 'low']) + .optional() + .describe( + 'Minimum severity to include. "critical" is most severe, "low" is least. For example, "high" returns "critical" and "high" notifications only.', + ), + createdAfter: z + .string() + .optional() + .describe( + 'ISO 8601 datetime string. Only return notifications created after this time, e.g. "2025-01-01T00:00:00Z".', + ), + offset: z + .number() + .int() + .min(0) + .optional() + .describe( + 'Number of notifications to skip for pagination. Defaults to 0.', + ), + limit: z + .number() + .int() + .min(1) + .max(100) + .optional() + .describe( + 'Maximum number of notifications to return (1–100). Defaults to 10.', + ), + }), + output: z => + z.object({ + notifications: z + .array(z.object({}).passthrough()) + .describe('List of notifications matching the filters.'), + totalCount: z + .number() + .describe( + 'Total number of notifications matching the filters, useful for pagination.', + ), + }), + }, + examples: [ + { + title: 'Get my unread notifications', + description: 'Returns up to 10 unread notifications (the default).', + input: {}, + output: { + notifications: [ + { + id: 'abc123', + origin: 'catalog', + created: '2025-04-01T10:00:00.000Z', + payload: { + title: 'Component entity missing owner', + severity: 'high', + link: '/catalog/default/component/my-service', + }, + }, + ], + totalCount: 1, + }, + }, + { + title: 'Get all notifications from the past week', + input: { + view: 'all', + createdAfter: '2025-04-03T00:00:00Z', + limit: 25, + }, + }, + { + title: 'Get high-priority unread alerts', + input: { minimumSeverity: 'high' }, + }, + ], + action: async ({ input, credentials, logger }) => { + if (!auth.isPrincipal(credentials, 'user')) { + throw new NotAllowedError('This action requires user credentials'); + } + + const { userEntityRef } = credentials.principal; + + logger.info( + `Fetching notifications for user "${userEntityRef}" (view=${ + input.view ?? 'unread' + })`, + ); + + let read: boolean | undefined; + if (input.view === 'unread' || input.view === undefined) { + read = false; + } else if (input.view === 'read') { + read = true; + } + + const opts = { + user: userEntityRef, + offset: input.offset ?? 0, + limit: input.limit ?? 10, + search: input.search, + topic: input.topic, + minimumSeverity: input.minimumSeverity, + createdAfter: input.createdAfter + ? new Date(input.createdAfter) + : undefined, + read, + saved: input.view === 'saved' ? true : undefined, + }; + + const [notifications, totalCount] = await Promise.all([ + store.getNotifications(opts), + store.getNotificationsCount(opts), + ]); + + return { + output: { + notifications, + totalCount, + }, + }; + }, + }); +}; diff --git a/plugins/notifications-backend/src/actions/index.ts b/plugins/notifications-backend/src/actions/index.ts new file mode 100644 index 0000000000..8dd527d6f2 --- /dev/null +++ b/plugins/notifications-backend/src/actions/index.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ActionsRegistryService } from '@backstage/backend-plugin-api/alpha'; +import { AuthService } from '@backstage/backend-plugin-api'; +import { NotificationsStore } from '../database'; +import { createGetNotificationsAction } from './createGetNotificationsAction'; + +export const createNotificationsActions = (options: { + actionsRegistry: ActionsRegistryService; + auth: AuthService; + store: NotificationsStore; +}) => { + createGetNotificationsAction(options); +}; diff --git a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts index d8ae3faf57..429aea4f3d 100644 --- a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts +++ b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts @@ -469,6 +469,11 @@ describe.each(databases.eachSupportedId())( id3, ]); }); + + it('should return empty array when limit is 0', async () => { + const result = await storage.getNotifications({ user, limit: 0 }); + expect(result).toEqual([]); + }); }); describe('getNotifications sorting', () => { diff --git a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts index 9e49b1b0cd..6ebd64f4f4 100644 --- a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts +++ b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts @@ -30,7 +30,7 @@ import { NotificationSeverity, } from '@backstage/plugin-notifications-common'; import { Knex } from 'knex'; -import crypto from 'crypto'; +import crypto from 'node:crypto'; import { durationToMilliseconds, HumanDuration } from '@backstage/types'; const migrationsDir = resolvePackagePath( @@ -289,11 +289,11 @@ export class DatabaseNotificationsStore implements NotificationsStore { } } - if (options.limit) { + if (options.limit !== undefined) { query.limit(options.limit); } - if (options.offset) { + if (options.offset !== undefined) { query.offset(options.offset); } diff --git a/plugins/notifications-backend/src/plugin.ts b/plugins/notifications-backend/src/plugin.ts index 49b3924a68..db25c1a94e 100644 --- a/plugins/notifications-backend/src/plugin.ts +++ b/plugins/notifications-backend/src/plugin.ts @@ -18,7 +18,9 @@ import { coreServices, createBackendPlugin, } from '@backstage/backend-plugin-api'; +import { actionsRegistryServiceRef } from '@backstage/backend-plugin-api/alpha'; import { createRouter } from './service/router'; +import { createNotificationsActions } from './actions'; import { signalsServiceRef } from '@backstage/plugin-signals-node'; import { NotificationProcessor, @@ -89,6 +91,7 @@ export const notificationsPlugin = createBackendPlugin({ config: coreServices.rootConfig, catalog: catalogServiceRef, scheduler: coreServices.scheduler, + actionsRegistry: actionsRegistryServiceRef, }, async init({ auth, @@ -101,6 +104,7 @@ export const notificationsPlugin = createBackendPlugin({ config, catalog, scheduler, + actionsRegistry, }) { const store = await DatabaseNotificationsStore.create({ database }); @@ -130,6 +134,8 @@ export const notificationsPlugin = createBackendPlugin({ store, ); await cleaner.initTaskRunner(); + + createNotificationsActions({ actionsRegistry, auth, store }); }, }); }, diff --git a/plugins/notifications-backend/src/service/parseEntityOrderFieldParams.ts b/plugins/notifications-backend/src/service/parseEntityOrderFieldParams.ts index 913e04993c..fa47a0fa36 100644 --- a/plugins/notifications-backend/src/service/parseEntityOrderFieldParams.ts +++ b/plugins/notifications-backend/src/service/parseEntityOrderFieldParams.ts @@ -23,10 +23,7 @@ import { EntityOrder } from '../database'; * Takes a single unknown parameter and makes sure that it's a single string or * an array of strings, and returns as an array. */ -export function parseStringsParam( - param: unknown, - ctx: string, -): string[] | undefined { +function parseStringsParam(param: unknown, ctx: string): string[] | undefined { if (param === undefined) { return undefined; } @@ -57,6 +54,6 @@ export function parseEntityOrderFieldParams( }); } -export function isOrder(order: string): order is 'asc' | 'desc' { +function isOrder(order: string): order is 'asc' | 'desc' { return ['asc', 'desc'].includes(order); } diff --git a/plugins/notifications-backend/src/service/router.test.ts b/plugins/notifications-backend/src/service/router.test.ts index 62a37acbbb..b15f753e1b 100644 --- a/plugins/notifications-backend/src/service/router.test.ts +++ b/plugins/notifications-backend/src/service/router.test.ts @@ -26,6 +26,7 @@ import { TestDatabases, } from '@backstage/backend-test-utils'; import { + NotificationProcessor, NotificationRecipientResolver, NotificationSendOptions, } from '@backstage/plugin-notifications-node'; @@ -827,6 +828,118 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { }); }); + describe('POST /notifications with custom processor', () => { + const httpAuth = mockServices.httpAuth({ + defaultCredentials: mockCredentials.service(), + }); + + const customProcessor: NotificationProcessor = { + getName: () => 'customProcessor', + processOptions: jest.fn(), + preProcess: jest.fn(), + postProcess: jest.fn(), + getNotificationFilters: jest.fn(), + }; + + beforeEach(async () => { + jest.resetAllMocks(); + const client = await database.getClient(); + await client('notification').del(); + await client('broadcast').del(); + await client('user_settings').del(); + + (customProcessor.processOptions as jest.Mock).mockImplementation( + opts => opts, + ); + (customProcessor.preProcess as jest.Mock).mockImplementation( + (notification, _options) => notification, + ); + + const router = await createRouter({ + logger: mockServices.logger.mock(), + store, + signals: signalService, + userInfo, + config, + httpAuth, + auth, + catalog, + processors: [customProcessor], + }); + app = express().use(router).use(mockErrorHandler()); + }); + + const sendNotification = async (data: NotificationSendOptions) => + request(app) + .post('/notifications') + .send(data) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + it('should not call processor preProcess if topic is excluded', async () => { + (customProcessor.getNotificationFilters as jest.Mock).mockReturnValue({ + excludedTopics: ['topic1'], + }); + // Should be processed + await sendNotification({ + recipients: { + type: 'broadcast', + }, + payload: { + title: 'test notification', + topic: 'topic2', + }, + }); + // Excluded, should not be processed + await sendNotification({ + recipients: { + type: 'broadcast', + }, + payload: { + title: 'test notification', + topic: 'topic1', + }, + }); + expect(customProcessor.preProcess).toHaveBeenCalledTimes(1); + }); + + it('should not call processor preProcess if topic is not included', async () => { + (customProcessor.getNotificationFilters as jest.Mock).mockReturnValue({ + includedTopics: ['topic1'], + }); + // Should not be processed, not included topic + await sendNotification({ + recipients: { + type: 'broadcast', + }, + payload: { + title: 'test notification', + topic: 'topic2', + }, + }); + // Should not be processed, no topic + await sendNotification({ + recipients: { + type: 'broadcast', + }, + payload: { + title: 'test notification', + }, + }); + // Included, should be processed + await sendNotification({ + recipients: { + type: 'broadcast', + }, + payload: { + title: 'test notification', + topic: 'topic1', + }, + }); + expect(customProcessor.preProcess).toHaveBeenCalledTimes(1); + }); + }); + describe('GET /', () => { const httpAuth = mockServices.httpAuth({ defaultCredentials: mockCredentials.user(), diff --git a/plugins/notifications-backend/src/service/router.ts b/plugins/notifications-backend/src/service/router.ts index 9e410cb964..3bdd4d4c6a 100644 --- a/plugins/notifications-backend/src/service/router.ts +++ b/plugins/notifications-backend/src/service/router.ts @@ -365,6 +365,15 @@ export async function createRouter( continue; } } + + if (filters.includedTopics) { + if ( + !payload.topic || + !filters.includedTopics.includes(payload.topic) + ) { + continue; + } + } } result.push(processor); } @@ -418,14 +427,16 @@ export async function createRouter( ) => { const filtered = await filterProcessors(notification); for (const processor of filtered) { - if (processor.postProcess) { - try { - await processor.postProcess(notification, opts); - } catch (e) { - logger.error( - `Error while post processing notification with ${processor.getName()}: ${e}`, - ); - } + if (!processor.postProcess) { + continue; + } + + try { + await processor.postProcess(notification, opts); + } catch (e) { + logger.error( + `Error while post processing notification with ${processor.getName()}: ${e}`, + ); } } }; diff --git a/plugins/notifications-backend/src/tests/migrations.test.ts b/plugins/notifications-backend/src/tests/migrations.test.ts index aa3ba7ae9a..65d8285e7f 100644 --- a/plugins/notifications-backend/src/tests/migrations.test.ts +++ b/plugins/notifications-backend/src/tests/migrations.test.ts @@ -16,7 +16,7 @@ import { Knex } from 'knex'; import { TestDatabases } from '@backstage/backend-test-utils'; -import fs from 'fs'; +import fs from 'node:fs'; const migrationsDir = `${__dirname}/../../migrations`; const migrationsFiles = fs.readdirSync(migrationsDir).sort(); diff --git a/plugins/notifications-common/CHANGELOG.md b/plugins/notifications-common/CHANGELOG.md index d604d1de47..38d375994f 100644 --- a/plugins/notifications-common/CHANGELOG.md +++ b/plugins/notifications-common/CHANGELOG.md @@ -1,5 +1,39 @@ # @backstage/plugin-notifications-common +## 0.2.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.7 + +## 0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.7-next.0 + +## 0.2.1 + +### Patch Changes + +- e9eb400: Allow configuring included topics for email notifications. + +## 0.2.0 + +### Minor Changes + +- 87e597c: Adds support for default configuration for an entire notification channel. + This setting will also be inherited down to origins and topics while still respecting the users individual choices. + + This will be handy if you want to use a "opt-in" strategy. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.6 + ## 0.2.0-next.1 ### Minor Changes diff --git a/plugins/notifications-common/knip-report.md b/plugins/notifications-common/knip-report.md index 90eba3c41e..2661c35327 100644 --- a/plugins/notifications-common/knip-report.md +++ b/plugins/notifications-common/knip-report.md @@ -1,8 +1,2 @@ # Knip report -## Unused dependencies (1) - -| Name | Location | Severity | -| :----------------- | :----------- | :------- | -| @material-ui/icons | plugins/notifications-common/package.json | error | - diff --git a/plugins/notifications-common/package.json b/plugins/notifications-common/package.json index fa7ce981ea..ce9a701721 100644 --- a/plugins/notifications-common/package.json +++ b/plugins/notifications-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-common", - "version": "0.2.0-next.1", + "version": "0.2.2", "description": "Common functionalities for the notifications plugin", "backstage": { "role": "common-library", @@ -40,8 +40,7 @@ }, "dependencies": { "@backstage/config": "workspace:^", - "@backstage/types": "workspace:^", - "@material-ui/icons": "^4.9.1" + "@backstage/types": "workspace:^" }, "devDependencies": { "@backstage/cli": "workspace:^" diff --git a/plugins/notifications-common/report.api.md b/plugins/notifications-common/report.api.md index ecc0ec8ef0..4ec181a940 100644 --- a/plugins/notifications-common/report.api.md +++ b/plugins/notifications-common/report.api.md @@ -64,6 +64,7 @@ export type NotificationProcessorFilters = { minSeverity?: NotificationSeverity; maxSeverity?: NotificationSeverity; excludedTopics?: string[]; + includedTopics?: string[]; }; // @public (undocumented) diff --git a/plugins/notifications-common/src/filters.ts b/plugins/notifications-common/src/filters.ts index 725cfcf714..401618cc39 100644 --- a/plugins/notifications-common/src/filters.ts +++ b/plugins/notifications-common/src/filters.ts @@ -43,5 +43,8 @@ export const getProcessorFiltersFromConfig = (config: Config) => { filter.excludedTopics = config.getOptionalStringArray( 'filter.excludedTopics', ); + filter.includedTopics = config.getOptionalStringArray( + 'filter.includedTopics', + ); return filter; }; diff --git a/plugins/notifications-common/src/types.ts b/plugins/notifications-common/src/types.ts index 41b335a4c2..ddb854b602 100644 --- a/plugins/notifications-common/src/types.ts +++ b/plugins/notifications-common/src/types.ts @@ -130,6 +130,7 @@ export type NotificationProcessorFilters = { minSeverity?: NotificationSeverity; maxSeverity?: NotificationSeverity; excludedTopics?: string[]; + includedTopics?: string[]; }; /** diff --git a/plugins/notifications-node/CHANGELOG.md b/plugins/notifications-node/CHANGELOG.md index 2afdd1647b..a2a7b486cc 100644 --- a/plugins/notifications-node/CHANGELOG.md +++ b/plugins/notifications-node/CHANGELOG.md @@ -1,5 +1,158 @@ # @backstage/plugin-notifications-node +## 0.2.25 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/catalog-model@1.8.0 + - @backstage/catalog-client@1.15.0 + - @backstage/plugin-signals-node@0.2.0 + - @backstage/plugin-notifications-common@0.2.2 + +## 0.2.25-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/catalog-client@1.14.1-next.0 + - @backstage/catalog-model@1.7.8-next.0 + - @backstage/plugin-notifications-common@0.2.2-next.0 + - @backstage/plugin-signals-node@0.1.30-next.2 + +## 0.2.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-signals-node@0.1.30-next.1 + +## 0.2.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-signals-node@0.1.30-next.0 + - @backstage/catalog-client@1.14.0 + - @backstage/catalog-model@1.7.7 + - @backstage/plugin-notifications-common@0.2.1 + +## 0.2.24 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/catalog-client@1.14.0 + - @backstage/catalog-model@1.7.7 + - @backstage/plugin-signals-node@0.1.29 + +## 0.2.24-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/catalog-client@1.14.0-next.2 + - @backstage/plugin-signals-node@0.1.29-next.1 + +## 0.2.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.14.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-notifications-common@0.2.1 + - @backstage/plugin-signals-node@0.1.29-next.0 + +## 0.2.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-client@1.13.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-notifications-common@0.2.1 + - @backstage/plugin-signals-node@0.1.29-next.0 + +## 0.2.23 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/catalog-client@1.13.0 + - @backstage/plugin-notifications-common@0.2.1 + - @backstage/plugin-signals-node@0.1.28 + +## 0.2.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.12.2-next.0 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## 0.2.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/plugin-signals-node@0.1.28-next.0 + +## 0.2.22 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-signals-node@0.1.27 + - @backstage/backend-plugin-api@1.6.0 + +## 0.2.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-signals-node@0.1.27-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-notifications-common@0.2.0 + +## 0.2.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-signals-node@0.1.27-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-notifications-common@0.2.0 + +## 0.2.21 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/catalog-model@1.7.6 + - @backstage/catalog-client@1.12.1 + - @backstage/plugin-signals-node@0.1.26 + ## 0.2.21-next.2 ### Patch Changes diff --git a/plugins/notifications-node/knip-report.md b/plugins/notifications-node/knip-report.md index 624533dcfc..2661c35327 100644 --- a/plugins/notifications-node/knip-report.md +++ b/plugins/notifications-node/knip-report.md @@ -1,12 +1,2 @@ # Knip report -## Unused dependencies (5) - -| Name | Location | Severity | -| :----------------------------- | :----------- | :------- | -| @backstage/plugin-signals-node | plugins/notifications-node/package.json | error | -| @backstage/catalog-client | plugins/notifications-node/package.json | error | -| @backstage/catalog-model | plugins/notifications-node/package.json | error | -| knex | plugins/notifications-node/package.json | error | -| uuid | plugins/notifications-node/package.json | error | - diff --git a/plugins/notifications-node/package.json b/plugins/notifications-node/package.json index ca746418db..da90bb87a3 100644 --- a/plugins/notifications-node/package.json +++ b/plugins/notifications-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-node", - "version": "0.2.21-next.2", + "version": "0.2.25", "description": "Node.js library for the notifications plugin", "backstage": { "role": "node-library", @@ -38,12 +38,7 @@ }, "dependencies": { "@backstage/backend-plugin-api": "workspace:^", - "@backstage/catalog-client": "workspace:^", - "@backstage/catalog-model": "workspace:^", - "@backstage/plugin-notifications-common": "workspace:^", - "@backstage/plugin-signals-node": "workspace:^", - "knex": "^3.0.0", - "uuid": "^11.0.0" + "@backstage/plugin-notifications-common": "workspace:^" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", diff --git a/plugins/notifications/.eslintrc.js b/plugins/notifications/.eslintrc.js index a2d8179106..e487f765b2 100644 --- a/plugins/notifications/.eslintrc.js +++ b/plugins/notifications/.eslintrc.js @@ -1,5 +1,5 @@ module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, { - rules: { - '@backstage/no-top-level-material-ui-4-imports': 'error', - }, + rules: { + '@backstage/no-top-level-material-ui-4-imports': 'error', + }, }); diff --git a/plugins/notifications/CHANGELOG.md b/plugins/notifications/CHANGELOG.md index 581cbd110a..acbb0d3ce3 100644 --- a/plugins/notifications/CHANGELOG.md +++ b/plugins/notifications/CHANGELOG.md @@ -1,5 +1,202 @@ # @backstage/plugin-notifications +## 0.5.16 + +### Patch Changes + +- 19a2a03: Migrated notifications plugin to use backstage UI +- d156cf4: Added `title` and `icon` to the new frontend system plugin definition. +- Updated dependencies + - @backstage/ui@0.14.0 + - @backstage/errors@1.3.0 + - @backstage/theme@0.7.3 + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/core-components@0.18.9 + - @backstage/core-plugin-api@1.12.5 + - @backstage/plugin-notifications-common@0.2.2 + - @backstage/plugin-signals-react@0.0.21 + +## 0.5.16-next.2 + +### Patch Changes + +- 19a2a03: Migrated notifications plugin to use backstage UI +- Updated dependencies + - @backstage/ui@0.14.0-next.2 + - @backstage/errors@1.3.0-next.0 + - @backstage/theme@0.7.3-next.0 + - @backstage/core-components@0.18.9-next.1 + - @backstage/core-plugin-api@1.12.5-next.2 + - @backstage/frontend-plugin-api@0.16.0-next.2 + - @backstage/plugin-notifications-common@0.2.2-next.0 + +## 0.5.16-next.1 + +### Patch Changes + +- d156cf4: Added `title` and `icon` to the new frontend system plugin definition. +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + +## 0.5.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.0 + - @backstage/core-components@0.18.9-next.0 + - @backstage/frontend-plugin-api@0.15.2-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + - @backstage/errors@1.2.7 + - @backstage/theme@0.7.2 + - @backstage/plugin-notifications-common@0.2.1 + - @backstage/plugin-signals-react@0.0.21-next.0 + +## 0.5.15 + +### Patch Changes + +- aa29b50: New frontend system pages now use the default plugin header together with `HeaderPage` instead of the legacy core page header pattern. +- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point. +- Updated dependencies + - @backstage/ui@0.13.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/plugin-signals-react@0.0.20 + +## 0.5.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/core-components@0.18.8-next.1 + +## 0.5.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/theme@0.7.2 + - @backstage/plugin-notifications-common@0.2.1 + - @backstage/plugin-signals-react@0.0.20-next.0 + +## 0.5.14 + +### Patch Changes + +- 8005286: Added `renderItem` prop to `NotificationsSidebarItem` component, allowing custom UI rendering while retaining all built-in notification logic (unread count, snackbar, signals, web notifications). +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/core-components@0.18.7 + - @backstage/theme@0.7.2 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/plugin-notifications-common@0.2.1 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-signals-react@0.0.19 + +## 0.5.14-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-signals-react@0.0.19-next.1 + - @backstage/theme@0.7.2-next.1 + +## 0.5.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.7.2-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/core-components@0.18.7-next.1 + +## 0.5.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.6-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/errors@1.2.7 + - @backstage/theme@0.7.1 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/plugin-signals-react@0.0.19-next.0 + +## 0.5.13 + +### Patch Changes + +- 4452d15: Added i18n support. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + +## 0.5.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + +## 0.5.12 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- 53347cc: Move long notification descriptions behind `Show more/less` button. + + This improves readability of the notifications list by preventing long descriptions from taking up too much space + or rendering very small scrollable areas. + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/plugin-signals-react@0.0.18 + +## 0.5.12-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-signals-react@0.0.18-next.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-notifications-common@0.2.0 + +## 0.5.11 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-signals-react@0.0.17 + ## 0.5.11-next.1 ### Patch Changes diff --git a/plugins/notifications/dev/index.tsx b/plugins/notifications/dev/index.tsx index 81354e2a73..df2258372d 100644 --- a/plugins/notifications/dev/index.tsx +++ b/plugins/notifications/dev/index.tsx @@ -21,7 +21,18 @@ import { } from '../src'; import { signalsPlugin } from '@backstage/plugin-signals'; import { SidebarItem } from '@backstage/core-components'; -import AddAlert from '@material-ui/icons/AddAlert'; +import { IconComponent } from '@backstage/core-plugin-api'; +import { RiBellLine } from '@remixicon/react'; + +const AddAlertIcon: IconComponent = props => { + let size = 24; + if (props.fontSize === 'large') { + size = 32; + } else if (props.fontSize === 'small') { + size = 16; + } + return ; +}; createDevApp() .registerPlugin(notificationsPlugin) @@ -38,7 +49,7 @@ createDevApp() .addSidebarItem() .addSidebarItem( { fetch('http://localhost:7007/api/notifications-debug/', { diff --git a/plugins/notifications/knip-report.md b/plugins/notifications/knip-report.md index 97d5b385fd..4d0019370a 100644 --- a/plugins/notifications/knip-report.md +++ b/plugins/notifications/knip-report.md @@ -1,3 +1,8 @@ # Knip report +## Unlisted dependencies (1) + +| Name | Location | Severity | +| :-------------------- | :----------------------------------------------------------------- | :------- | +| react-aria-components | src/components/NotificationsFilters/NotificationsFilters.tsx:17:27 | error | diff --git a/plugins/notifications/package.json b/plugins/notifications/package.json index d1df83e79f..1e5741bf50 100644 --- a/plugins/notifications/package.json +++ b/plugins/notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications", - "version": "0.5.11-next.1", + "version": "0.5.16", "backstage": { "role": "frontend-plugin", "pluginId": "notifications", @@ -51,7 +51,6 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", @@ -59,11 +58,11 @@ "@backstage/plugin-notifications-common": "workspace:^", "@backstage/plugin-signals-react": "workspace:^", "@backstage/theme": "workspace:^", - "@material-ui/core": "^4.9.13", - "@material-ui/icons": "^4.9.1", + "@backstage/ui": "workspace:^", + "@remixicon/react": "^4.6.0", "lodash": "^4.17.21", - "material-ui-confirm": "^3.0.12", "notistack": "^3.0.1", + "react-aria-components": "~1.17.0", "react-relative-time": "^0.0.9", "react-use": "^17.2.4" }, @@ -78,13 +77,13 @@ "msw": "^1.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/plugins/notifications/report-alpha.api.md b/plugins/notifications/report-alpha.api.md index 713d4c3998..393e726fd0 100644 --- a/plugins/notifications/report-alpha.api.md +++ b/plugins/notifications/report-alpha.api.md @@ -6,12 +6,17 @@ import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { ApiFactory } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionInput } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; -import { RouteRef } from '@backstage/frontend-plugin-api'; +import { RouteRef } from '@backstage/core-plugin-api'; +import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) const _default: OverridableFrontendPlugin< @@ -40,31 +45,146 @@ const _default: OverridableFrontendPlugin< name: undefined; config: { path: string | undefined; + title: string | undefined; }; configInput: { path?: string | undefined; + title?: string | undefined; }; output: | ExtensionDataRef - | ExtensionDataRef | ExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; } + > + | ExtensionDataRef + | ExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } >; - inputs: {}; + inputs: { + pages: ExtensionInput< + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef_2, + 'core.routing.ref', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >, + { + singleton: false; + optional: false; + internal: false; + } + >; + }; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; - loader: () => Promise; - routeRef?: RouteRef; + title?: string; + icon?: IconElement; + loader?: () => Promise; + routeRef?: RouteRef_2; + noHeader?: boolean; }; }>; } >; export default _default; +// @alpha @deprecated (undocumented) +export const notificationsTranslationRef: TranslationRef< + 'plugin.notifications', + { + readonly 'table.errors.markAllReadFailed': 'Failed to mark all notifications as read'; + readonly 'table.pagination.labelDisplayedRows': '{from}-{to} of {count}'; + readonly 'table.pagination.firstTooltip': 'First Page'; + readonly 'table.pagination.labelRowsSelect': 'rows'; + readonly 'table.pagination.lastTooltip': 'Last Page'; + readonly 'table.pagination.nextTooltip': 'Next Page'; + readonly 'table.pagination.previousTooltip': 'Previous Page'; + readonly 'table.emptyMessage': 'No records to display'; + readonly 'table.bulkActions.markAllRead': 'Mark all read'; + readonly 'table.bulkActions.markSelectedAsRead': 'Mark selected as read'; + readonly 'table.bulkActions.returnSelectedAmongUnread': 'Return selected among unread'; + readonly 'table.bulkActions.saveSelectedForLater': 'Save selected for later'; + readonly 'table.bulkActions.undoSaveForSelected': 'Undo save for selected'; + readonly 'table.confirmDialog.cancel': 'Cancel'; + readonly 'table.confirmDialog.title': 'Are you sure?'; + readonly 'table.confirmDialog.markAllReadDescription': 'Mark all notifications as read.'; + readonly 'table.confirmDialog.markAllReadConfirmation': 'Mark All'; + readonly 'filters.view.all': 'All'; + readonly 'filters.view.label': 'View'; + readonly 'filters.view.read': 'Read notifications'; + readonly 'filters.view.saved': 'Saved'; + readonly 'filters.view.unread': 'Unread notifications'; + readonly 'filters.title': 'Filters'; + readonly 'filters.severity.normal': 'Normal'; + readonly 'filters.severity.high': 'High'; + readonly 'filters.severity.low': 'Low'; + readonly 'filters.severity.label': 'Min severity'; + readonly 'filters.severity.critical': 'Critical'; + readonly 'filters.topic.label': 'Topic'; + readonly 'filters.topic.anyTopic': 'Any topic'; + readonly 'filters.createdAfter.label': 'Sent out'; + readonly 'filters.createdAfter.placeholder': 'Notifications since'; + readonly 'filters.createdAfter.last24h': 'Last 24h'; + readonly 'filters.createdAfter.lastWeek': 'Last week'; + readonly 'filters.createdAfter.anyTime': 'Any time'; + readonly 'filters.sortBy.origin': 'Origin'; + readonly 'filters.sortBy.label': 'Sort by'; + readonly 'filters.sortBy.placeholder': 'Field to sort by'; + readonly 'filters.sortBy.newest': 'Newest on top'; + readonly 'filters.sortBy.oldest': 'Oldest on top'; + readonly 'filters.sortBy.topic': 'Topic'; + readonly 'settings.table.origin': 'Origin'; + readonly 'settings.table.topic': 'Topic'; + readonly 'settings.title': 'Notification settings'; + readonly 'settings.errors.useNotificationFormat': 'useNotificationFormat must be used within a NotificationFormatProvider'; + readonly 'settings.errorTitle': 'Failed to load settings'; + readonly 'settings.noSettingsAvailable': 'No notification settings available, check back later'; + readonly 'sidebar.title': 'Notifications'; + readonly 'sidebar.errors.markAsReadFailed': 'Failed to mark notification as read'; + readonly 'sidebar.errors.fetchNotificationFailed': 'Failed to fetch notification'; + readonly 'notificationsPage.title': 'Notifications'; + readonly 'notificationsPage.tableTitle.all_one': 'All notifications ({{count}})'; + readonly 'notificationsPage.tableTitle.all_other': 'All notifications ({{count}})'; + readonly 'notificationsPage.tableTitle.saved_one': 'Saved notifications ({{count}})'; + readonly 'notificationsPage.tableTitle.saved_other': 'Saved notifications ({{count}})'; + readonly 'notificationsPage.tableTitle.unread_one': 'Unread notifications ({{count}})'; + readonly 'notificationsPage.tableTitle.unread_other': 'Unread notifications ({{count}})'; + readonly 'notificationsPage.tableTitle.read_one': 'Read notifications ({{count}})'; + readonly 'notificationsPage.tableTitle.read_other': 'Read notifications ({{count}})'; + } +>; + // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/notifications/report.api.md b/plugins/notifications/report.api.md index a9aeb64cda..ff39a03a8a 100644 --- a/plugins/notifications/report.api.md +++ b/plugins/notifications/report.api.md @@ -3,7 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { ApiRef } from '@backstage/core-plugin-api'; +import { ApiRef } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { DiscoveryApi } from '@backstage/core-plugin-api'; import { FetchApi } from '@backstage/core-plugin-api'; @@ -13,9 +13,9 @@ import { Notification as Notification_2 } from '@backstage/plugin-notifications- import { NotificationSettings } from '@backstage/plugin-notifications-common'; import { NotificationSeverity } from '@backstage/plugin-notifications-common'; import { NotificationStatus } from '@backstage/plugin-notifications-common'; -import * as React_2 from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; import { TableProps } from '@backstage/core-components'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; // @public (undocumented) export type GetNotificationsCommonOptions = { @@ -111,10 +111,10 @@ export type NotificationSnackbarProperties = { }; dense?: boolean; maxSnack?: number; - snackStyle?: React_2.CSSProperties; - iconVariant?: Partial>; + snackStyle?: React.CSSProperties; + iconVariant?: Partial>; Components?: { - [key in NotificationSeverity]: React_2.JSXElementConstructor; + [key in NotificationSeverity]: React.JSXElementConstructor; }; }; @@ -142,6 +142,13 @@ export const notificationsPlugin: BackstagePlugin< {} >; +// @public +export type NotificationsRenderItemProps = { + unreadCount: number; + to: string; + onClick: () => void; +}; + // @public (undocumented) export const NotificationsSidebarItem: ( props?: NotificationsSideBarItemProps, @@ -159,23 +166,13 @@ export type NotificationsSideBarItemProps = { text?: string; disableHighlight?: boolean; noTrack?: boolean; + renderItem?: (props: NotificationsRenderItemProps) => React.ReactNode; }; // @public (undocumented) -export const NotificationsTable: ({ - title, - markAsReadOnLinkOpen, - isLoading, - notifications, - isUnread, - onUpdate, - setContainsText, - onPageChange, - onRowsPerPageChange, - page, - pageSize, - totalCount, -}: NotificationsTableProps) => JSX_2.Element; +export const NotificationsTable: ( + input: NotificationsTableProps, +) => JSX_2.Element; // @public (undocumented) export type NotificationsTableProps = Pick< @@ -191,6 +188,72 @@ export type NotificationsTableProps = Pick< pageSize: number; }; +// @public (undocumented) +export const notificationsTranslationRef: TranslationRef< + 'plugin.notifications', + { + readonly 'table.errors.markAllReadFailed': 'Failed to mark all notifications as read'; + readonly 'table.pagination.labelDisplayedRows': '{from}-{to} of {count}'; + readonly 'table.pagination.firstTooltip': 'First Page'; + readonly 'table.pagination.labelRowsSelect': 'rows'; + readonly 'table.pagination.lastTooltip': 'Last Page'; + readonly 'table.pagination.nextTooltip': 'Next Page'; + readonly 'table.pagination.previousTooltip': 'Previous Page'; + readonly 'table.emptyMessage': 'No records to display'; + readonly 'table.bulkActions.markAllRead': 'Mark all read'; + readonly 'table.bulkActions.markSelectedAsRead': 'Mark selected as read'; + readonly 'table.bulkActions.returnSelectedAmongUnread': 'Return selected among unread'; + readonly 'table.bulkActions.saveSelectedForLater': 'Save selected for later'; + readonly 'table.bulkActions.undoSaveForSelected': 'Undo save for selected'; + readonly 'table.confirmDialog.cancel': 'Cancel'; + readonly 'table.confirmDialog.title': 'Are you sure?'; + readonly 'table.confirmDialog.markAllReadDescription': 'Mark all notifications as read.'; + readonly 'table.confirmDialog.markAllReadConfirmation': 'Mark All'; + readonly 'filters.view.all': 'All'; + readonly 'filters.view.label': 'View'; + readonly 'filters.view.read': 'Read notifications'; + readonly 'filters.view.saved': 'Saved'; + readonly 'filters.view.unread': 'Unread notifications'; + readonly 'filters.title': 'Filters'; + readonly 'filters.severity.normal': 'Normal'; + readonly 'filters.severity.high': 'High'; + readonly 'filters.severity.low': 'Low'; + readonly 'filters.severity.label': 'Min severity'; + readonly 'filters.severity.critical': 'Critical'; + readonly 'filters.topic.label': 'Topic'; + readonly 'filters.topic.anyTopic': 'Any topic'; + readonly 'filters.createdAfter.label': 'Sent out'; + readonly 'filters.createdAfter.placeholder': 'Notifications since'; + readonly 'filters.createdAfter.last24h': 'Last 24h'; + readonly 'filters.createdAfter.lastWeek': 'Last week'; + readonly 'filters.createdAfter.anyTime': 'Any time'; + readonly 'filters.sortBy.origin': 'Origin'; + readonly 'filters.sortBy.label': 'Sort by'; + readonly 'filters.sortBy.placeholder': 'Field to sort by'; + readonly 'filters.sortBy.newest': 'Newest on top'; + readonly 'filters.sortBy.oldest': 'Oldest on top'; + readonly 'filters.sortBy.topic': 'Topic'; + readonly 'settings.table.origin': 'Origin'; + readonly 'settings.table.topic': 'Topic'; + readonly 'settings.title': 'Notification settings'; + readonly 'settings.errors.useNotificationFormat': 'useNotificationFormat must be used within a NotificationFormatProvider'; + readonly 'settings.errorTitle': 'Failed to load settings'; + readonly 'settings.noSettingsAvailable': 'No notification settings available, check back later'; + readonly 'sidebar.title': 'Notifications'; + readonly 'sidebar.errors.markAsReadFailed': 'Failed to mark notification as read'; + readonly 'sidebar.errors.fetchNotificationFailed': 'Failed to fetch notification'; + readonly 'notificationsPage.title': 'Notifications'; + readonly 'notificationsPage.tableTitle.all_one': 'All notifications ({{count}})'; + readonly 'notificationsPage.tableTitle.all_other': 'All notifications ({{count}})'; + readonly 'notificationsPage.tableTitle.saved_one': 'Saved notifications ({{count}})'; + readonly 'notificationsPage.tableTitle.saved_other': 'Saved notifications ({{count}})'; + readonly 'notificationsPage.tableTitle.unread_one': 'Unread notifications ({{count}})'; + readonly 'notificationsPage.tableTitle.unread_other': 'Unread notifications ({{count}})'; + readonly 'notificationsPage.tableTitle.read_one': 'Read notifications ({{count}})'; + readonly 'notificationsPage.tableTitle.read_other': 'Read notifications ({{count}})'; + } +>; + // @public (undocumented) export type UpdateNotificationsOptions = { ids: string[]; diff --git a/plugins/notifications/src/alpha.tsx b/plugins/notifications/src/alpha.tsx index ca4be58209..aa51d22261 100644 --- a/plugins/notifications/src/alpha.tsx +++ b/plugins/notifications/src/alpha.tsx @@ -21,22 +21,18 @@ import { discoveryApiRef, fetchApiRef, } from '@backstage/frontend-plugin-api'; +import { RiNotification3Line } from '@remixicon/react'; import { rootRouteRef } from './routes'; -import { - compatWrapper, - convertLegacyRouteRef, - convertLegacyRouteRefs, -} from '@backstage/core-compat-api'; import { NotificationsClient, notificationsApiRef } from './api'; const page = PageBlueprint.make({ params: { path: '/notifications', - routeRef: convertLegacyRouteRef(rootRouteRef), + routeRef: rootRouteRef, loader: () => - import('./components/NotificationsPage').then(m => - compatWrapper(), - ), + import('./components/NotificationsPage').then(m => ( + + )), }, }); @@ -53,10 +49,20 @@ const api = ApiBlueprint.make({ /** @alpha */ export default createFrontendPlugin({ pluginId: 'notifications', + title: 'Notifications', + icon: , info: { packageJson: () => import('../package.json') }, - routes: convertLegacyRouteRefs({ + routes: { root: rootRouteRef, - }), + }, // TODO(Rugvip): Nav item (i.e. NotificationsSidebarItem) currently needs to be installed manually extensions: [page, api], }); + +import { notificationsTranslationRef as _notificationsTranslationRef } from './translation'; + +/** + * @alpha + * @deprecated Import from `@backstage/plugin-notifications` instead. + */ +export const notificationsTranslationRef = _notificationsTranslationRef; diff --git a/plugins/notifications/src/components/NotificationsFilters/NotificationsFilters.tsx b/plugins/notifications/src/components/NotificationsFilters/NotificationsFilters.tsx index 7e3f519b0c..6f68763c1b 100644 --- a/plugins/notifications/src/components/NotificationsFilters/NotificationsFilters.tsx +++ b/plugins/notifications/src/components/NotificationsFilters/NotificationsFilters.tsx @@ -13,15 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ChangeEvent } from 'react'; - -import Divider from '@material-ui/core/Divider'; -import FormControl from '@material-ui/core/FormControl'; -import Grid from '@material-ui/core/Grid'; -import InputLabel from '@material-ui/core/InputLabel'; -import MenuItem from '@material-ui/core/MenuItem'; -import Select from '@material-ui/core/Select'; -import Typography from '@material-ui/core/Typography'; +import { Select, Text, Flex } from '@backstage/ui'; +import type { Key } from 'react-aria-components'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { notificationsTranslationRef } from '../../translation'; import { GetNotificationsOptions } from '../../api'; import { NotificationSeverity } from '@backstage/plugin-notifications-common'; @@ -47,58 +42,53 @@ export type NotificationsFiltersProps = { const ALL = '___all___'; -export const CreatedAfterOptions: { - [key: string]: { label: string; getDate: () => Date }; -} = { +type TranslationKey = keyof (typeof notificationsTranslationRef)['T']; + +export const CreatedAfterOptions = { last24h: { - label: 'Last 24h', + labelKey: 'filters.createdAfter.last24h' satisfies TranslationKey, getDate: () => new Date(Date.now() - 24 * 3600 * 1000), }, lastWeek: { - label: 'Last week', + labelKey: 'filters.createdAfter.lastWeek' satisfies TranslationKey, getDate: () => new Date(Date.now() - 7 * 24 * 3600 * 1000), }, all: { - label: 'Any time', + labelKey: 'filters.createdAfter.anyTime' satisfies TranslationKey, getDate: () => new Date(0), }, -}; +} as const; -export const SortByOptions: { - [key: string]: { - label: string; - sortBy: SortBy; - }; -} = { +export const SortByOptions = { newest: { - label: 'Newest on top', + labelKey: 'filters.sortBy.newest' satisfies TranslationKey, sortBy: { - sort: 'created', - sortOrder: 'desc', + sort: 'created' as const, + sortOrder: 'desc' as const, }, }, oldest: { - label: 'Oldest on top', + labelKey: 'filters.sortBy.oldest' satisfies TranslationKey, sortBy: { - sort: 'created', - sortOrder: 'asc', + sort: 'created' as const, + sortOrder: 'asc' as const, }, }, topic: { - label: 'Topic', + labelKey: 'filters.sortBy.topic' satisfies TranslationKey, sortBy: { - sort: 'topic', - sortOrder: 'asc', + sort: 'topic' as const, + sortOrder: 'asc' as const, }, }, origin: { - label: 'Origin', + labelKey: 'filters.sortBy.origin' satisfies TranslationKey, sortBy: { - sort: 'origin', - sortOrder: 'asc', + sort: 'origin' as const, + sortOrder: 'asc' as const, }, }, -}; +} as const; const getSortByText = (sortBy?: SortBy): string => { if (sortBy?.sort === 'created' && sortBy?.sortOrder === 'asc') { @@ -114,13 +104,6 @@ const getSortByText = (sortBy?: SortBy): string => { return 'newest'; }; -const AllSeverityOptions: { [key in NotificationSeverity]: string } = { - critical: 'Critical', - high: 'High', - normal: 'Normal', - low: 'Low', -}; - export const NotificationsFilters = ({ sorting, onSortingChanged, @@ -136,24 +119,23 @@ export const NotificationsFilters = ({ onTopicChanged, allTopics, }: NotificationsFiltersProps) => { + const { t } = useTranslationRef(notificationsTranslationRef); const sortByText = getSortByText(sorting); - const handleOnCreatedAfterChanged = ( - event: ChangeEvent<{ name?: string; value: unknown }>, - ) => { - onCreatedAfterChanged(event.target.value as string); + const handleOnCreatedAfterChanged = (key: Key | Key[] | null) => { + if (key !== null && !Array.isArray(key)) + onCreatedAfterChanged(key as string); }; - const handleOnViewChanged = ( - event: ChangeEvent<{ name?: string; value: unknown }>, - ) => { - if (event.target.value === 'unread') { + const handleOnViewChanged = (key: Key | Key[] | null) => { + const value = Array.isArray(key) ? key[0] : key; + if (value === 'unread') { onUnreadOnlyChanged(true); onSavedChanged(undefined); - } else if (event.target.value === 'read') { + } else if (value === 'read') { onUnreadOnlyChanged(false); onSavedChanged(undefined); - } else if (event.target.value === 'saved') { + } else if (value === 'saved') { onUnreadOnlyChanged(undefined); onSavedChanged(true); } else { @@ -163,10 +145,9 @@ export const NotificationsFilters = ({ } }; - const handleOnSortByChanged = ( - event: ChangeEvent<{ name?: string; value: unknown }>, - ) => { - const idx = (event.target.value as string) || 'newest'; + const handleOnSortByChanged = (key: Key | Key[] | null) => { + const idx = (((Array.isArray(key) ? key[0] : key) as string) || + 'newest') as keyof typeof SortByOptions; const option = SortByOptions[idx]; onSortingChanged({ ...option.sortBy }); }; @@ -180,132 +161,86 @@ export const NotificationsFilters = ({ viewValue = 'read'; } - const handleOnSeverityChanged = ( - event: ChangeEvent<{ name?: string; value: unknown }>, - ) => { + const handleOnSeverityChanged = (key: Key | Key[] | null) => { const value: NotificationSeverity = - (event.target.value as NotificationSeverity) || 'normal'; + ((Array.isArray(key) ? key[0] : key) as NotificationSeverity) || 'normal'; onSeverityChanged(value); }; - const handleOnTopicChanged = ( - event: ChangeEvent<{ name?: string; value: unknown }>, - ) => { - const value = event.target.value as string; + const handleOnTopicChanged = (key: Key | Key[] | null) => { + const value = (Array.isArray(key) ? key[0] : key) as string; onTopicChanged(value === ALL ? undefined : value); }; - const sortedAllTopics = (allTopics || []).sort((a, b) => a.localeCompare(b)); + const sortedAllTopics = [...(allTopics ?? [])].sort((a, b) => + a.localeCompare(b), + ); return ( - <> - - - Filters - - + +
    + {t('filters.title')} +
    - - - View - - - + ({ + value: key, + label: t( + CreatedAfterOptions[key as keyof typeof CreatedAfterOptions] + .labelKey, + ), + }))} + /> - - -
    + ({ + value: key, + label: t(`filters.severity.${key}`), + }))} + /> - - - - - - - - Min severity - - - - - - - - - Topic - - - - - - +
    +); diff --git a/plugins/notifications/src/components/UserNotificationSettingsCard/OriginRow.tsx b/plugins/notifications/src/components/UserNotificationSettingsCard/OriginRow.tsx index 4483b4c634..6120b1d09a 100644 --- a/plugins/notifications/src/components/UserNotificationSettingsCard/OriginRow.tsx +++ b/plugins/notifications/src/components/UserNotificationSettingsCard/OriginRow.tsx @@ -19,12 +19,8 @@ import { NotificationSettings, OriginSetting, } from '@backstage/plugin-notifications-common'; -import IconButton from '@material-ui/core/IconButton'; -import Switch from '@material-ui/core/Switch'; -import TableRow from '@material-ui/core/TableRow'; -import Tooltip from '@material-ui/core/Tooltip'; -import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown'; -import KeyboardArrowUpIcon from '@material-ui/icons/KeyboardArrowUp'; +import { ButtonIcon, Switch, Tooltip, TooltipTrigger } from '@backstage/ui'; +import { RiArrowDownSLine, RiArrowUpSLine } from '@remixicon/react'; import { NoBorderTableCell } from './NoBorderTableCell'; import { useNotificationFormat } from './UserNotificationSettingsCard'; @@ -43,45 +39,50 @@ export const OriginRow = (props: { const { origin, settings, handleChange, open, handleRowToggle } = props; const { formatOriginName } = useNotificationFormat(); return ( - + {origin.topics && origin.topics.length > 0 && ( - - + handleRowToggle(origin.id)} - > - {open ? : } - - + onPress={() => handleRowToggle(origin.id)} + icon={ + open ? ( + + ) : ( + + ) + } + variant="secondary" + /> + {`Show Topics for the ${formatOriginName( + origin.id, + )} origin`} + )} {formatOriginName(origin.id)}all {settings.channels.map(ch => ( - + ) => { - handleChange(ch.id, origin.id, null, event.target.checked); + onChange={(isSelected: boolean) => { + handleChange(ch.id, origin.id, null, isSelected); }} /> - + {`Enable or disable ${ch.id.toLocaleLowerCase( + 'en-US', + )} notifications from ${formatOriginName(origin.id)}`} + ))} - + ); }; diff --git a/plugins/notifications/src/components/UserNotificationSettingsCard/TopicRow.tsx b/plugins/notifications/src/components/UserNotificationSettingsCard/TopicRow.tsx index ed2ca8fd05..cf9b708b1f 100644 --- a/plugins/notifications/src/components/UserNotificationSettingsCard/TopicRow.tsx +++ b/plugins/notifications/src/components/UserNotificationSettingsCard/TopicRow.tsx @@ -20,19 +20,10 @@ import { OriginSetting, TopicSetting, } from '@backstage/plugin-notifications-common'; -import TableRow from '@material-ui/core/TableRow'; -import Tooltip from '@material-ui/core/Tooltip'; -import Switch from '@material-ui/core/Switch'; -import { withStyles } from '@material-ui/core/styles'; +import { Switch, Tooltip, TooltipTrigger } from '@backstage/ui'; import { NoBorderTableCell } from './NoBorderTableCell'; import { useNotificationFormat } from './UserNotificationSettingsCard'; -const TopicTableRow = withStyles({ - root: { - paddingLeft: '4px', - }, -})(TableRow); - export const TopicRow = (props: { topic: TopicSetting; origin: OriginSetting; @@ -47,33 +38,32 @@ export const TopicRow = (props: { const { topic, origin, settings, handleChange } = props; const { formatOriginName, formatTopicName } = useNotificationFormat(); return ( - + {formatTopicName(topic.id)} {settings.channels.map(ch => ( - - + + ) => { - handleChange(ch.id, origin.id, topic.id, event.target.checked); + onChange={(isSelected: boolean) => { + handleChange(ch.id, origin.id, topic.id, isSelected); }} /> - + {`Enable or disable ${ch.id.toLocaleLowerCase( + 'en-US', + )} notifications for the ${formatTopicName( + topic.id, + )} topic from ${formatOriginName(origin.id)}`} + ))} - + ); }; diff --git a/plugins/notifications/src/components/UserNotificationSettingsCard/UserNotificationSettingsCard.tsx b/plugins/notifications/src/components/UserNotificationSettingsCard/UserNotificationSettingsCard.tsx index fbf7fafc28..e4d85b5748 100644 --- a/plugins/notifications/src/components/UserNotificationSettingsCard/UserNotificationSettingsCard.tsx +++ b/plugins/notifications/src/components/UserNotificationSettingsCard/UserNotificationSettingsCard.tsx @@ -20,6 +20,8 @@ import { useNotificationsApi } from '../../hooks'; import { NotificationSettings } from '@backstage/plugin-notifications-common'; import { notificationsApiRef } from '../../api'; import { useApi } from '@backstage/core-plugin-api'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { notificationsTranslationRef } from '../../translation'; import { UserNotificationSettingsPanel } from './UserNotificationSettingsPanel'; import { capitalize } from 'lodash'; @@ -33,11 +35,9 @@ const NotificationFormatContext = createContext( ); export const useNotificationFormat = () => { + const { t } = useTranslationRef(notificationsTranslationRef); const context = useContext(NotificationFormatContext); - if (!context) - throw new Error( - 'useNotificationFormat must be used within a NotificationFormatProvider', - ); + if (!context) throw new Error(t('settings.errors.useNotificationFormat')); return context; }; @@ -83,6 +83,7 @@ export const UserNotificationSettingsCard = (props: { originNames?: Record; topicNames?: Record; }) => { + const { t } = useTranslationRef(notificationsTranslationRef); const [settings, setNotificationSettings] = useState< NotificationSettings | undefined >(undefined); @@ -105,9 +106,9 @@ export const UserNotificationSettingsCard = (props: { }; return ( - + {loading && } - {error && } + {error && } {settings && ( { - it('renders each origin only once even if present in multiple channels', () => { + it('renders each origin only once even if present in multiple channels', async () => { const settings = { channels: [ { @@ -43,7 +44,7 @@ describe('UserNotificationSettingsPanel', () => { }, ], }; - render( + await renderInTestApp( ; }) => { const { settings, onChange } = props; + const { t } = useTranslationRef(notificationsTranslationRef); const [expandedRows, setExpandedRows] = useState>(new Set()); const handleRowToggle = (originId: string) => { @@ -106,9 +98,7 @@ export const UserNotificationSettingsPanel = (props: { if (settings.channels.length === 0) { return ( - - No notification settings available, check back later - + {t('settings.noSettingsAvailable')} ); } @@ -125,26 +115,29 @@ export const UserNotificationSettingsPanel = (props: { const uniqueOrigins = Array.from(uniqueOriginsMap); return ( -
    + {children} +
    - - - - - Origin - - - Topic - +
    + + + + {settings.channels.map(channel => ( - - + ))} - - - + + + {uniqueOrigins.flatMap(origin => [ -
    + + {t('settings.table.origin')} + + {t('settings.table.topic')} + + {channel.id} - - + +
    + + ); }; diff --git a/plugins/notifications/src/index.ts b/plugins/notifications/src/index.ts index 25565f1ae1..b67ae9b108 100644 --- a/plugins/notifications/src/index.ts +++ b/plugins/notifications/src/index.ts @@ -17,3 +17,4 @@ export { notificationsPlugin, NotificationsPage } from './plugin'; export * from './api'; export { useNotificationsApi } from './hooks'; export * from './components'; +export { notificationsTranslationRef } from './translation'; diff --git a/plugins/notifications/src/translation.ts b/plugins/notifications/src/translation.ts new file mode 100644 index 0000000000..b2d4fe2e52 --- /dev/null +++ b/plugins/notifications/src/translation.ts @@ -0,0 +1,121 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createTranslationRef } from '@backstage/frontend-plugin-api'; + +/** @public */ +export const notificationsTranslationRef = createTranslationRef({ + id: 'plugin.notifications', + messages: { + notificationsPage: { + title: 'Notifications', + tableTitle: { + all_one: 'All notifications ({{count}})', + all_other: 'All notifications ({{count}})', + saved_one: 'Saved notifications ({{count}})', + saved_other: 'Saved notifications ({{count}})', + unread_one: 'Unread notifications ({{count}})', + unread_other: 'Unread notifications ({{count}})', + read_one: 'Read notifications ({{count}})', + read_other: 'Read notifications ({{count}})', + }, + }, + filters: { + title: 'Filters', + view: { + label: 'View', + unread: 'Unread notifications', + read: 'Read notifications', + saved: 'Saved', + all: 'All', + }, + createdAfter: { + label: 'Sent out', + placeholder: 'Notifications since', + last24h: 'Last 24h', + lastWeek: 'Last week', + anyTime: 'Any time', + }, + sortBy: { + label: 'Sort by', + placeholder: 'Field to sort by', + newest: 'Newest on top', + oldest: 'Oldest on top', + topic: 'Topic', + origin: 'Origin', + }, + severity: { + label: 'Min severity', + critical: 'Critical', + high: 'High', + normal: 'Normal', + low: 'Low', + }, + topic: { + label: 'Topic', + anyTopic: 'Any topic', + }, + }, + table: { + emptyMessage: 'No records to display', + pagination: { + firstTooltip: 'First Page', + labelDisplayedRows: '{from}-{to} of {count}', + labelRowsSelect: 'rows', + lastTooltip: 'Last Page', + nextTooltip: 'Next Page', + previousTooltip: 'Previous Page', + }, + bulkActions: { + markAllRead: 'Mark all read', + markSelectedAsRead: 'Mark selected as read', + returnSelectedAmongUnread: 'Return selected among unread', + saveSelectedForLater: 'Save selected for later', + undoSaveForSelected: 'Undo save for selected', + }, + confirmDialog: { + title: 'Are you sure?', + markAllReadDescription: 'Mark all notifications as read.', + markAllReadConfirmation: 'Mark All', + cancel: 'Cancel', + }, + errors: { + markAllReadFailed: 'Failed to mark all notifications as read', + }, + }, + sidebar: { + title: 'Notifications', + errors: { + markAsReadFailed: 'Failed to mark notification as read', + fetchNotificationFailed: 'Failed to fetch notification', + }, + }, + settings: { + title: 'Notification settings', + errorTitle: 'Failed to load settings', + noSettingsAvailable: + 'No notification settings available, check back later', + table: { + origin: 'Origin', + topic: 'Topic', + }, + errors: { + useNotificationFormat: + 'useNotificationFormat must be used within a NotificationFormatProvider', + }, + }, + }, +}); diff --git a/plugins/org-react/CHANGELOG.md b/plugins/org-react/CHANGELOG.md index 24f9d46559..9609b48073 100644 --- a/plugins/org-react/CHANGELOG.md +++ b/plugins/org-react/CHANGELOG.md @@ -1,5 +1,168 @@ # @backstage/plugin-org-react +## 0.1.49 + +### Patch Changes + +- e5af44c: Replaced deprecated `humanizeEntityRef` usage with the Catalog Presentation API. +- Updated dependencies + - @backstage/catalog-model@1.8.0 + - @backstage/plugin-catalog-react@2.1.2 + - @backstage/core-components@0.18.9 + - @backstage/catalog-client@1.15.0 + - @backstage/core-plugin-api@1.12.5 + +## 0.1.49-next.1 + +### Patch Changes + +- e5af44c: Replaced deprecated `humanizeEntityRef` usage with the Catalog Presentation API. +- Updated dependencies + - @backstage/core-components@0.18.9-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.2 + - @backstage/catalog-client@1.14.1-next.0 + - @backstage/catalog-model@1.7.8-next.0 + - @backstage/core-plugin-api@1.12.5-next.2 + +## 0.1.49-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.1-next.0 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + - @backstage/catalog-client@1.14.0 + - @backstage/catalog-model@1.7.7 + +## 0.1.48 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/catalog-client@1.14.0 + - @backstage/catalog-model@1.7.7 + +## 0.1.48-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + +## 0.1.48-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.13.1-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + +## 0.1.47 + +### Patch Changes + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/core-components@0.18.7 + - @backstage/catalog-client@1.13.0 + - @backstage/core-plugin-api@1.12.3 + +## 0.1.47-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/catalog-client@1.12.2-next.0 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + +## 0.1.47-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/core-components@0.18.7-next.1 + +## 0.1.47-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + +## 0.1.46 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5 + - @backstage/plugin-catalog-react@1.21.5 + +## 0.1.46-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.1 + +## 0.1.46-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.5-next.0 + +## 0.1.45 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + +## 0.1.45-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + +## 0.1.44 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/catalog-model@1.7.6 + - @backstage/catalog-client@1.12.1 + ## 0.1.44-next.0 ### Patch Changes diff --git a/plugins/org-react/knip-report.md b/plugins/org-react/knip-report.md index a2a90b227e..17b0d45cc2 100644 --- a/plugins/org-react/knip-report.md +++ b/plugins/org-react/knip-report.md @@ -2,7 +2,7 @@ ## Unused dependencies (1) -| Name | Location | Severity | -| :------------------------ | :----------- | :------- | -| @backstage/catalog-client | plugins/org-react/package.json | error | +| Name | Location | Severity | +| :------------------------ | :---------------- | :------- | +| @backstage/catalog-client | package.json:43:6 | error | diff --git a/plugins/org-react/package.json b/plugins/org-react/package.json index aae4e281ac..0aace7204f 100644 --- a/plugins/org-react/package.json +++ b/plugins/org-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org-react", - "version": "0.1.44-next.0", + "version": "0.1.49", "backstage": { "role": "web-library", "pluginId": "org", @@ -61,13 +61,13 @@ "@types/react": "^18.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/plugins/org-react/src/components/GroupListPicker/GroupListPicker.test.tsx b/plugins/org-react/src/components/GroupListPicker/GroupListPicker.test.tsx index eac96a5d14..6581b19467 100644 --- a/plugins/org-react/src/components/GroupListPicker/GroupListPicker.test.tsx +++ b/plugins/org-react/src/components/GroupListPicker/GroupListPicker.test.tsx @@ -14,13 +14,12 @@ * limitations under the License. */ -import { render, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { ApiProvider } from '@backstage/core-app-api'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; import { GroupListPicker } from '../GroupListPicker'; import { GroupEntity } from '@backstage/catalog-model'; -import { TestApiRegistry } from '@backstage/test-utils'; +import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils'; import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils'; const mockGroups: GroupEntity[] = [ @@ -64,7 +63,9 @@ const apis = TestApiRegistry.from([catalogApiRef, mockCatalogApi]); describe('', () => { it('can choose a group', async () => { - const { getByText, getByTestId } = render( + const user = userEvent.setup(); + + const { findByText, getByTestId } = await renderInTestApp( ', () => { , ); - await userEvent.click(getByTestId('group-list-picker-button')); - const input = getByTestId('group-list-picker-input').querySelector('input'); - await userEvent.type(input as HTMLElement, 'GR'); + await user.click(getByTestId('group-list-picker-button')); - await waitFor(async () => { - expect(getByText('Group A')).toBeInTheDocument(); - await userEvent.click(getByText('Group A')); - expect(getByText('Group A')).toBeInTheDocument(); - }); + const input = getByTestId('group-list-picker-input').querySelector('input'); + await user.type(input as HTMLElement, 'GR'); + + await user.click(await findByText('Group A')); + + await expect(findByText('Group A')).resolves.toBeInTheDocument(); }); }); diff --git a/plugins/org-react/src/components/GroupListPicker/GroupListPicker.tsx b/plugins/org-react/src/components/GroupListPicker/GroupListPicker.tsx index 12dc69073b..b193d4e092 100644 --- a/plugins/org-react/src/components/GroupListPicker/GroupListPicker.tsx +++ b/plugins/org-react/src/components/GroupListPicker/GroupListPicker.tsx @@ -17,15 +17,16 @@ import { MouseEvent, useState, useCallback } from 'react'; import { catalogApiRef, - humanizeEntityRef, + defaultEntityPresentation, + entityPresentationApiRef, } from '@backstage/plugin-catalog-react'; import TextField from '@material-ui/core/TextField'; import Autocomplete from '@material-ui/lab/Autocomplete'; import useAsync from 'react-use/esm/useAsync'; import Popover from '@material-ui/core/Popover'; -import { useApi } from '@backstage/core-plugin-api'; +import { useApi, useApiHolder } from '@backstage/core-plugin-api'; import { ResponseErrorPanel } from '@backstage/core-components'; -import { Entity, GroupEntity } from '@backstage/catalog-model'; +import { GroupEntity } from '@backstage/catalog-model'; import { GroupListPickerButton } from './GroupListPickerButton'; /** @@ -43,6 +44,8 @@ export type GroupListPickerProps = { /** @public */ export const GroupListPicker = (props: GroupListPickerProps) => { const catalogApi = useApi(catalogApiRef); + const apis = useApiHolder(); + const entityPresentationApi = apis.get(entityPresentationApiRef); const { onChange, groupTypes, placeholder = '', defaultValue = '' } = props; const [anchorEl, setAnchorEl] = useState(null); @@ -85,8 +88,6 @@ export const GroupListPicker = (props: GroupListPickerProps) => { return ; } - const getHumanEntityRef = (entity: Entity) => humanizeEntityRef(entity); - return ( <> { options={groups ?? []} groupBy={option => option.spec.type} getOptionLabel={option => - option.spec.profile?.displayName ?? getHumanEntityRef(option) + entityPresentationApi + ? entityPresentationApi.forEntity(option).snapshot.primaryTitle + : defaultEntityPresentation(option).primaryTitle } inputValue={inputValue} onInputChange={(_, value) => setInputValue(value)} diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index 5455ba07bc..49a9382ae8 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,319 @@ # @backstage/plugin-org +## 0.7.1 + +### Patch Changes + +- 64c9a20: The `MembersListCard` now prefers `metadata.title` over `metadata.name` when displaying the group membership card, similarly to the rest of the group profile cards +- 87eb31c: Fixed `GroupProfileCard` and `UserProfileCard` content overflowing on narrow screens. +- d156cf4: Added `title` and `icon` to the new frontend system plugin definition. +- f1f59b1: Replaced deprecated `humanizeEntityRef` usage with the Catalog Presentation API. +- Updated dependencies + - @backstage/ui@0.14.0 + - @backstage/catalog-model@1.8.0 + - @backstage/plugin-catalog-react@2.1.2 + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/core-components@0.18.9 + - @backstage/core-plugin-api@1.12.5 + - @backstage/plugin-catalog-common@1.1.9 + +## 0.7.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.2 + - @backstage/core-components@0.18.9-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.2 + - @backstage/catalog-model@1.7.8-next.0 + - @backstage/core-plugin-api@1.12.5-next.2 + - @backstage/frontend-plugin-api@0.16.0-next.2 + - @backstage/plugin-catalog-common@1.1.9-next.0 + +## 0.7.1-next.1 + +### Patch Changes + +- 87eb31c: Fixed `GroupProfileCard` and `UserProfileCard` content overflowing on narrow screens. +- d156cf4: Added `title` and `icon` to the new frontend system plugin definition. +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + +## 0.7.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.0 + - @backstage/plugin-catalog-react@2.1.1-next.0 + - @backstage/core-components@0.18.9-next.0 + - @backstage/frontend-plugin-api@0.15.2-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + - @backstage/catalog-model@1.7.7 + - @backstage/plugin-catalog-common@1.1.8 + +## 0.7.0 + +### Minor Changes + +- d14b6e0: **BREAKING**: Migrated `MembersListCard`, `OwnershipCard`, and `CatalogGraphCard` to use BUI card primitives via `EntityInfoCard`. + + - `OwnershipCard`: Removed `variant` and `maxScrollHeight` props. Card height and scrolling are now controlled by the parent container — the card fills its container and the body scrolls automatically when content overflows. + - `CatalogGraphCard`: Removed `variant` prop. + - `MembersListCard`: Translation keys `subtitle`, `paginationLabel`, `aggregateMembersToggle.directMembers`, `aggregateMembersToggle.aggregatedMembers`, and `aggregateMembersToggle.ariaLabel` have been removed. The `title` key now includes `{{groupName}}`. New keys added: `cardLabel`, `noSearchResult`, `aggregateMembersToggle.label`. + - `OwnershipCard`: Translation keys `aggregateRelationsToggle.directRelations`, `aggregateRelationsToggle.aggregatedRelations`, and `aggregateRelationsToggle.ariaLabel` have been removed. New key added: `aggregateRelationsToggle.label`. + - Removed `MemberComponentClassKey` export, and `root` and `cardContent` from `MembersListCardClassKey`, `card` from `OwnershipCardClassKey`, and `card` from `CatalogGraphCardClassKey`. + + **Migration:** + + ```diff + - + + + ``` + + ```diff + - + + + ``` + +- 5fc35bb: Migrated `EntityAboutCard`, `EntityLinksCard`, `EntityLabelsCard`, `GroupProfileCard`, and `UserProfileCard` from MUI/InfoCard to use the new BUI card layout and BUI components where possible. + + **BREAKING**: Removed `variant` prop from EntityAboutCard, EntityUserProfileCard, EntityGroupProfileCard, EntityLabelsCard, EntityLinksCard. Removed `gridSizes` prop from `AboutField`. + + **Migration:** + + Simply delete the obsolete `variant` and `gridSizes` props, e.g: + + ```diff + - + + + ``` + + ```diff + - + + + ``` + +### Patch Changes + +- 538c985: Updated installation documentation to use feature discovery as the default. +- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point. +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/ui@0.13.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/catalog-model@1.7.7 + +## 0.7.0-next.2 + +### Minor Changes + +- d14b6e0: **BREAKING**: Migrated `MembersListCard`, `OwnershipCard`, and `CatalogGraphCard` to use BUI card primitives via `EntityInfoCard`. + + - `OwnershipCard`: Removed `variant` and `maxScrollHeight` props. Card height and scrolling are now controlled by the parent container — the card fills its container and the body scrolls automatically when content overflows. + - `CatalogGraphCard`: Removed `variant` prop. + - `MembersListCard`: Translation keys `subtitle`, `paginationLabel`, `aggregateMembersToggle.directMembers`, `aggregateMembersToggle.aggregatedMembers`, and `aggregateMembersToggle.ariaLabel` have been removed. The `title` key now includes `{{groupName}}`. New keys added: `cardLabel`, `noSearchResult`, `aggregateMembersToggle.label`. + - `OwnershipCard`: Translation keys `aggregateRelationsToggle.directRelations`, `aggregateRelationsToggle.aggregatedRelations`, and `aggregateRelationsToggle.ariaLabel` have been removed. New key added: `aggregateRelationsToggle.label`. + - Removed `MemberComponentClassKey` export, and `root` and `cardContent` from `MembersListCardClassKey`, `card` from `OwnershipCardClassKey`, and `card` from `CatalogGraphCardClassKey`. + + **Migration:** + + ```diff + - + + + ``` + + ```diff + - + + + ``` + +- 5fc35bb: Migrated `EntityAboutCard`, `EntityLinksCard`, `EntityLabelsCard`, `GroupProfileCard`, and `UserProfileCard` from MUI/InfoCard to use the new BUI card layout and BUI components where possible. + + **BREAKING**: Removed `variant` prop from EntityAboutCard, EntityUserProfileCard, EntityGroupProfileCard, EntityLabelsCard, EntityLinksCard. Removed `gridSizes` prop from `AboutField`. + + **Migration:** + + Simply delete the obsolete `variant` and `gridSizes` props, e.g: + + ```diff + - + + + ``` + + ```diff + - + + + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.13.0-next.2 + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.2 + - @backstage/core-components@0.18.8-next.1 + +## 0.6.50-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/plugin-catalog-common@1.1.8 + +## 0.6.50-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/plugin-catalog-common@1.1.8 + +## 0.6.49 + +### Patch Changes + +- ac9bead: Added `@backstage/frontend-test-utils` dev dependency. +- 7feb83b: Adjusted to use the new `@backstage/filter-predicates` types for predicate expressions. +- 4183614: Updated usage of deprecated APIs in the new frontend system. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- 1dee6de: Add search functionality in MembersListCard +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/core-components@0.18.7 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-catalog-common@1.1.8 + +## 0.6.49-next.2 + +### Patch Changes + +- ac9bead: Added `@backstage/frontend-test-utils` dev dependency. +- 7feb83b: Adjusted to use the new `@backstage/filter-predicates` types for predicate expressions. +- 4183614: Updated usage of deprecated APIs in the new frontend system. +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + +## 0.6.49-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/core-components@0.18.7-next.1 + +## 0.6.49-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.8-next.0 + +## 0.6.48 + +### Patch Changes + +- db3cfd7: Adds the Resource kind to the `useGetEntities` fallback so that Resources are included by default when no specific kinds are provided in the group ownership card. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + - @backstage/plugin-catalog-react@1.21.5 + +## 0.6.48-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.1 + +## 0.6.48-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.5-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## 0.6.47 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + +## 0.6.47-next.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-model@1.7.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-common@1.1.7 + +## 0.6.47-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## 0.6.46 + +### Patch Changes + +- 6db9e7e: Improved responsiveness of GroupProfileCard component +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + ## 0.6.46-next.1 ### Patch Changes diff --git a/plugins/org/README-alpha.md b/plugins/org/README-alpha.md index 35258800f2..db8a84f35c 100644 --- a/plugins/org/README-alpha.md +++ b/plugins/org/README-alpha.md @@ -1,8 +1,6 @@ -# Org Plugin +# Org Plugin - Extension Reference -> [!WARNING] -> This documentation is made for those using the experimental new Frontend system. -> If you are not using the new frontend system, please go [here](./README.md). +This page contains detailed documentation for all extensions provided by the `@backstage/plugin-org` plugin. For general information about the plugin, see the [README](./README.md). This is a plugin that extends the Catalog entity page with some users and groups overview cards: @@ -99,9 +97,9 @@ This [entity card](https://github.com/backstage/backstage/blob/master/plugins/ca Currently, this entity card extension has only one configuration: -| Config key | Default value | Description | -| ---------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -| `filter` | `kind:group` | An [entity filter](https://github.com/backstage/backstage/pull/21480) that determines when the card should be displayed on the entity page. | +| Config key | Default value | Description | +| ---------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| `filter` | `{ kind: 'group' }` | An [entity filter](https://github.com/backstage/backstage/pull/21480) that determines when the card should be displayed on the entity page. | This is how to configure the `group-profile` extension in the `app-config.yaml` file: @@ -119,19 +117,23 @@ Use extension overrides for completely re-implementing the group-profile entity ```tsx import { createFrontendModule } from '@backstage/backstage-plugin-api'; -import { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha'; +import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha'; export default createFrontendModule({ pluginId: 'org', extensions: [ - createEntityCardExtension({ + EntityCardBlueprint.make({ // Name is necessary so the system knows that this extension will override the default 'group-profile' entity card extension provided by the 'org' plugin name: 'group-profile', - // By default, this card will show up only for groups - filter: 'kind:group' - // Returning a custom card component - loader: () => - import('./components').then(m => ), + params: { + // By default, this card will show up only for groups + filter: { kind: 'group' }, + // Returning a custom card component + loader: () => + import('./components').then(m => ( + + )), + }, }), ], }); @@ -151,9 +153,9 @@ An [entity card](https://github.com/backstage/backstage/blob/master/plugins/cata Currently, this entity card extension has only one configuration: -| Config key | Default value | Description | -| ---------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -| `filter` | `kind:group` | An [entity filter](https://github.com/backstage/backstage/pull/21480) that determines when the card should be displayed on the entity page. | +| Config key | Default value | Description | +| ---------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| `filter` | `{ kind: 'group' }` | An [entity filter](https://github.com/backstage/backstage/pull/21480) that determines when the card should be displayed on the entity page. | This is how to configure the `members-list` extension in the `app-config.yaml` file: @@ -171,19 +173,21 @@ Use extension overrides for completely re-implementing the members-list entity c ```tsx import { createFrontendModule } from '@backstage/backstage-plugin-api'; -import { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha'; +import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha'; export default createFrontendModule({ pluginId: 'org', extensions: [ - createEntityCardExtension({ + EntityCardBlueprint.make({ // Name is necessary so the system knows that this extension will override the default 'members-list' entity card extension provided by the 'org' plugin name: 'members-list', - // By default, this card will show up only for groups - filter: 'kind:group' - // Returning a custom card component - loader: () => - import('./components').then(m => ), + params: { + // By default, this card will show up only for groups + filter: { kind: 'group' }, + // Returning a custom card component + loader: () => + import('./components').then(m => ), + }, }), ], }); @@ -203,9 +207,9 @@ An [entity card](https://github.com/backstage/backstage/blob/master/plugins/cata Currently, this entity card extension has only one configuration: -| Config key | Default value | Description | -| ---------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -| `filter` | `kind:group,user` | An [entity filter](https://github.com/backstage/backstage/pull/21480) that determines when the card should be displayed on the entity page. | +| Config key | Default value | Description | +| ---------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| `filter` | `{ kind: { $in: ['group', 'user'] } }` | An [entity filter](https://github.com/backstage/backstage/pull/21480) that determines when the card should be displayed on the entity page. | This is how to configure the `ownership` extension in the `app-config.yaml` file: @@ -223,19 +227,21 @@ Use extension overrides for completely re-implementing the ownership entity card ```tsx import { createFrontendModule } from '@backstage/backstage-plugin-api'; -import { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha'; +import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha'; export default createFrontendModule({ pluginId: 'org', extensions: [ - createEntityCardExtension({ + EntityCardBlueprint.make({ // Name is necessary so the system knows that this extension will override the default 'ownership' entity card extension provided by the 'org' plugin name: 'ownership', - // By default, this card will show up only for groups or users - filter: 'kind:group,user' - // Returning a custom card component - loader: () => - import('./components').then(m => ), + params: { + // By default, this card will show up only for groups or users + filter: { kind: { $in: ['group', 'user'] } }, + // Returning a custom card component + loader: () => + import('./components').then(m => ), + }, }), ], }); @@ -255,9 +261,9 @@ This [entity card](https://github.com/backstage/backstage/blob/master/plugins/ca Currently, this entity card extension has only one configuration: -| Config key | Default value | Description | -| ---------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -| `filter` | `kind:user` | An [entity filter](https://github.com/backstage/backstage/pull/21480) that determines when the card should be displayed on the entity page. | +| Config key | Default value | Description | +| ---------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- | +| `filter` | `{ kind: 'user' }` | An [entity filter](https://github.com/backstage/backstage/pull/21480) that determines when the card should be displayed on the entity page. | This is how to configure the `user-profile` extension in the `app-config.yaml` file: @@ -275,19 +281,21 @@ Use extension overrides for completely re-implementing the user-profile entity c ```tsx import { createFrontendModule } from '@backstage/backstage-plugin-api'; -import { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha'; +import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha'; export default createFrontendModule({ pluginId: 'org', extensions: [ - createEntityCardExtension({ + EntityCardBlueprint.make({ // Name is necessary so the system knows that this extension will override the default 'user-profile' entity card extension provided by the 'org' plugin name: 'user-profile', - // By default, this card will show up only for groups or users - filter: 'kind:user' - // Returning a custom card component - loader: () => - import('./components').then(m => ), + params: { + // By default, this card will show up only for groups or users + filter: { kind: 'user' }, + // Returning a custom card component + loader: () => + import('./components').then(m => ), + }, }), ], }); diff --git a/plugins/org/README.md b/plugins/org/README.md index 9cf165f06d..062f88fa98 100644 --- a/plugins/org/README.md +++ b/plugins/org/README.md @@ -1,8 +1,5 @@ # Org Plugin for Backstage -> Disclaimer: -> If you are looking for documentation on the experimental new frontend system support, please go [here](./README-alpha.md). - ## Features - Show Group Page @@ -21,6 +18,33 @@ Here's an example of what the User Profile looks like: ![Group Page example](./docs/user-profile-example.png) +## Installation + +```bash +# From your Backstage root directory +yarn --cwd packages/app add @backstage/plugin-org +``` + +Once installed, the plugin is automatically available in your app through the default feature discovery. For more details and alternative installation methods, see [installing plugins](https://backstage.io/docs/frontend-system/building-apps/installing-plugins). + +You can enable entity cards on the catalog entity page through configuration: + +```yaml +# app-config.yaml +app: + extensions: + - entity-card:org/group-profile + - entity-card:org/members-list + - entity-card:org/ownership + - entity-card:org/user-profile +``` + +For the full list of available extensions and their configuration options, see the [README-alpha.md](./README-alpha.md). + +## Old Frontend System + +If your Backstage app uses the old frontend system, you need to manually wire the plugin into your app as outlined in this section. If you are on the new frontend system, you can skip this. + ### MyGroupsSidebarItem The MyGroupsSidebarItem provides quick access to the group(s) the logged in user is a member of directly in the sidebar. diff --git a/plugins/org/knip-report.md b/plugins/org/knip-report.md index 45c5be2d03..e7f3d3ce4c 100644 --- a/plugins/org/knip-report.md +++ b/plugins/org/knip-report.md @@ -2,7 +2,7 @@ ## Unused devDependencies (1) -| Name | Location | Severity | -| :---------------------------------- | :----------- | :------- | -| @backstage/plugin-permission-common | plugins/org/package.json | error | +| Name | Location | Severity | +| :---------------------------------- | :---------------- | :------- | +| @backstage/plugin-permission-common | package.json:79:6 | error | diff --git a/plugins/org/package.json b/plugins/org/package.json index 275dd8538b..b2f25f142a 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org", - "version": "0.6.46-next.1", + "version": "0.7.1", "description": "A Backstage plugin that helps you create entity pages for your organization", "backstage": { "role": "frontend-plugin", @@ -52,26 +52,29 @@ }, "dependencies": { "@backstage/catalog-model": "workspace:^", - "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", "@backstage/plugin-catalog-common": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", + "@backstage/ui": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", + "@remixicon/react": "^4.6.0", "lodash": "^4.17.21", "p-limit": "^3.1.0", "pluralize": "^8.0.0", "qs": "^6.10.1", - "react-use": "^17.2.4" + "react-use": "^17.2.4", + "zod": "^4.0.0" }, "devDependencies": { "@backstage/catalog-client": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", + "@backstage/frontend-test-utils": "workspace:^", "@backstage/plugin-catalog": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", "@backstage/plugin-permission-react": "workspace:^", @@ -84,13 +87,13 @@ "@types/react": "^18.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/plugins/org/report-alpha.api.md b/plugins/org/report-alpha.api.md index 3e113868a6..3409f7ffd1 100644 --- a/plugins/org/report-alpha.api.md +++ b/plugins/org/report-alpha.api.md @@ -5,10 +5,9 @@ ```ts import { Entity } from '@backstage/catalog-model'; import { EntityCardType } from '@backstage/plugin-catalog-react/alpha'; -import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; -import { ExtensionInput } from '@backstage/frontend-plugin-api'; -import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; +import { ExternalRouteRef } from '@backstage/core-plugin-api'; +import { FilterPredicate } from '@backstage/filter-predicates'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; @@ -18,19 +17,19 @@ import { TranslationRef } from '@backstage/frontend-plugin-api'; const _default: OverridableFrontendPlugin< {}, { - catalogIndex: ExternalRouteRef; + catalogIndex: ExternalRouteRef; }, { 'entity-card:org/group-profile': OverridableExtensionDefinition<{ kind: 'entity-card'; name: 'group-profile'; config: { - filter: EntityPredicate | undefined; - type: 'content' | 'summary' | 'info' | undefined; + filter: FilterPredicate | undefined; + type: 'content' | 'info' | undefined; }; configInput: { - filter?: EntityPredicate | undefined; - type?: 'content' | 'summary' | 'info' | undefined; + filter?: FilterPredicate | undefined; + type?: 'content' | 'info' | undefined; }; output: | ExtensionDataRef @@ -58,7 +57,7 @@ const _default: OverridableFrontendPlugin< inputs: {}; params: { loader: () => Promise; - filter?: string | EntityPredicate | ((entity: Entity) => boolean); + filter?: string | FilterPredicate | ((entity: Entity) => boolean); type?: EntityCardType; }; }>; @@ -66,16 +65,14 @@ const _default: OverridableFrontendPlugin< config: { initialRelationAggregation: 'direct' | 'aggregated' | undefined; showAggregateMembersToggle: boolean | undefined; - } & { - filter: EntityPredicate | undefined; - type: 'content' | 'summary' | 'info' | undefined; + filter: FilterPredicate | undefined; + type: 'content' | 'info' | undefined; }; configInput: { - showAggregateMembersToggle?: boolean | undefined; initialRelationAggregation?: 'direct' | 'aggregated' | undefined; - } & { - filter?: EntityPredicate | undefined; - type?: 'content' | 'summary' | 'info' | undefined; + showAggregateMembersToggle?: boolean | undefined; + filter?: FilterPredicate | undefined; + type?: 'content' | 'info' | undefined; }; output: | ExtensionDataRef @@ -100,20 +97,12 @@ const _default: OverridableFrontendPlugin< optional: true; } >; - inputs: { - [x: string]: ExtensionInput< - ExtensionDataRef, - { - singleton: boolean; - optional: boolean; - } - >; - }; + inputs: {}; kind: 'entity-card'; name: 'members-list'; params: { loader: () => Promise; - filter?: string | EntityPredicate | ((entity: Entity) => boolean); + filter?: string | FilterPredicate | ((entity: Entity) => boolean); type?: EntityCardType; }; }>; @@ -121,16 +110,16 @@ const _default: OverridableFrontendPlugin< config: { initialRelationAggregation: 'direct' | 'aggregated' | undefined; showAggregateMembersToggle: boolean | undefined; - } & { - filter: EntityPredicate | undefined; - type: 'content' | 'summary' | 'info' | undefined; + ownedKinds: string[] | undefined; + filter: FilterPredicate | undefined; + type: 'content' | 'info' | undefined; }; configInput: { - showAggregateMembersToggle?: boolean | undefined; initialRelationAggregation?: 'direct' | 'aggregated' | undefined; - } & { - filter?: EntityPredicate | undefined; - type?: 'content' | 'summary' | 'info' | undefined; + showAggregateMembersToggle?: boolean | undefined; + ownedKinds?: string[] | undefined; + filter?: FilterPredicate | undefined; + type?: 'content' | 'info' | undefined; }; output: | ExtensionDataRef @@ -155,20 +144,12 @@ const _default: OverridableFrontendPlugin< optional: true; } >; - inputs: { - [x: string]: ExtensionInput< - ExtensionDataRef, - { - singleton: boolean; - optional: boolean; - } - >; - }; + inputs: {}; kind: 'entity-card'; name: 'ownership'; params: { loader: () => Promise; - filter?: string | EntityPredicate | ((entity: Entity) => boolean); + filter?: string | FilterPredicate | ((entity: Entity) => boolean); type?: EntityCardType; }; }>; @@ -176,16 +157,14 @@ const _default: OverridableFrontendPlugin< config: { maxRelations: number | undefined; hideIcons: boolean; - } & { - filter: EntityPredicate | undefined; - type: 'content' | 'summary' | 'info' | undefined; + filter: FilterPredicate | undefined; + type: 'content' | 'info' | undefined; }; configInput: { - hideIcons?: boolean | undefined; maxRelations?: number | undefined; - } & { - filter?: EntityPredicate | undefined; - type?: 'content' | 'summary' | 'info' | undefined; + hideIcons?: boolean | undefined; + filter?: FilterPredicate | undefined; + type?: 'content' | 'info' | undefined; }; output: | ExtensionDataRef @@ -210,20 +189,12 @@ const _default: OverridableFrontendPlugin< optional: true; } >; - inputs: { - [x: string]: ExtensionInput< - ExtensionDataRef, - { - singleton: boolean; - optional: boolean; - } - >; - }; + inputs: {}; kind: 'entity-card'; name: 'user-profile'; params: { loader: () => Promise; - filter?: string | EntityPredicate | ((entity: Entity) => boolean); + filter?: string | FilterPredicate | ((entity: Entity) => boolean); type?: EntityCardType; }; }>; @@ -231,7 +202,7 @@ const _default: OverridableFrontendPlugin< >; export default _default; -// @alpha (undocumented) +// @alpha @deprecated (undocumented) export const orgTranslationRef: TranslationRef< 'org', { @@ -243,17 +214,13 @@ export const orgTranslationRef: TranslationRef< readonly 'groupProfileCard.listItemTitle.entityRef': 'Entity Ref'; readonly 'groupProfileCard.listItemTitle.parentGroup': 'Parent Group'; readonly 'groupProfileCard.listItemTitle.childGroups': 'Child Groups'; - readonly 'membersListCard.title': 'Members'; - readonly 'membersListCard.subtitle': 'of {{groupName}}'; - readonly 'membersListCard.paginationLabel': ', page {{page}} of {{nbPages}}'; + readonly 'membersListCard.title': '{{groupName}} members'; + readonly 'membersListCard.cardLabel': 'User page for {{memberName}}'; readonly 'membersListCard.noMembersDescription': 'This group has no members.'; - readonly 'membersListCard.aggregateMembersToggle.ariaLabel': 'Users Type Switch'; - readonly 'membersListCard.aggregateMembersToggle.directMembers': 'Direct Members'; - readonly 'membersListCard.aggregateMembersToggle.aggregatedMembers': 'Aggregated Members'; + readonly 'membersListCard.noSearchResult': 'Found no members matching "{{searchTerm}}".'; + readonly 'membersListCard.aggregateMembersToggle.label': 'Include subgroups'; readonly 'ownershipCard.title': 'Ownership'; - readonly 'ownershipCard.aggregateRelationsToggle.ariaLabel': 'Ownership Type Switch'; - readonly 'ownershipCard.aggregateRelationsToggle.directRelations': 'Direct Relations'; - readonly 'ownershipCard.aggregateRelationsToggle.aggregatedRelations': 'Aggregated Relations'; + readonly 'ownershipCard.aggregateRelationsToggle.label': 'Include indirect ownership'; readonly 'userProfileCard.editIconButtonTitle': 'Edit Metadata'; readonly 'userProfileCard.listItemTitle.email': 'Email'; readonly 'userProfileCard.listItemTitle.memberOf': 'Member of'; diff --git a/plugins/org/report.api.md b/plugins/org/report.api.md index 13143d0e4c..58b3b8fc2c 100644 --- a/plugins/org/report.api.md +++ b/plugins/org/report.api.md @@ -6,8 +6,8 @@ import { BackstagePlugin } from '@backstage/core-plugin-api'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; -import { InfoCardVariants } from '@backstage/core-components'; import { JSX as JSX_2 } from 'react/jsx-runtime'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; // @public (undocumented) export type ComponentsGridClassKey = @@ -18,7 +18,6 @@ export type ComponentsGridClassKey = // @public (undocumented) export const EntityGroupProfileCard: (props: { - variant?: InfoCardVariants; showLinks?: boolean; }) => JSX_2.Element; @@ -34,13 +33,11 @@ export const EntityMembersListCard: (props: { // @public (undocumented) export const EntityOwnershipCard: (props: { - variant?: InfoCardVariants; entityFilterKind?: string[]; hideRelationsToggle?: boolean; relationsType?: EntityRelationAggregation; relationAggregation?: EntityRelationAggregation; entityLimit?: number; - maxScrollHeight?: string; }) => JSX_2.Element; // @public (undocumented) @@ -48,7 +45,6 @@ export type EntityRelationAggregation = 'direct' | 'aggregated'; // @public (undocumented) export const EntityUserProfileCard: (props: { - variant?: InfoCardVariants; showLinks?: boolean; maxRelations?: number; hideIcons?: boolean; @@ -56,13 +52,9 @@ export const EntityUserProfileCard: (props: { // @public (undocumented) export const GroupProfileCard: (props: { - variant?: InfoCardVariants; showLinks?: boolean; }) => JSX_2.Element; -// @public (undocumented) -export type MemberComponentClassKey = 'card' | 'avatar'; - // @public (undocumented) export const MembersListCard: (props: { memberDisplayTitle?: string; @@ -74,7 +66,7 @@ export const MembersListCard: (props: { }) => JSX_2.Element; // @public (undocumented) -export type MembersListCardClassKey = 'root' | 'cardContent' | 'memberList'; +export type MembersListCardClassKey = 'memberList'; // @public export const MyGroupsSidebarItem: (props: { @@ -94,29 +86,49 @@ const orgPlugin: BackstagePlugin< export { orgPlugin }; export { orgPlugin as plugin }; +// @public (undocumented) +export const orgTranslationRef: TranslationRef< + 'org', + { + readonly 'groupProfileCard.groupNotFound': 'Group not found'; + readonly 'groupProfileCard.editIconButtonTitle': 'Edit Metadata'; + readonly 'groupProfileCard.refreshIconButtonTitle': 'Schedule entity refresh'; + readonly 'groupProfileCard.refreshIconButtonAriaLabel': 'Refresh'; + readonly 'groupProfileCard.listItemTitle.email': 'Email'; + readonly 'groupProfileCard.listItemTitle.entityRef': 'Entity Ref'; + readonly 'groupProfileCard.listItemTitle.parentGroup': 'Parent Group'; + readonly 'groupProfileCard.listItemTitle.childGroups': 'Child Groups'; + readonly 'membersListCard.title': '{{groupName}} members'; + readonly 'membersListCard.cardLabel': 'User page for {{memberName}}'; + readonly 'membersListCard.noMembersDescription': 'This group has no members.'; + readonly 'membersListCard.noSearchResult': 'Found no members matching "{{searchTerm}}".'; + readonly 'membersListCard.aggregateMembersToggle.label': 'Include subgroups'; + readonly 'ownershipCard.title': 'Ownership'; + readonly 'ownershipCard.aggregateRelationsToggle.label': 'Include indirect ownership'; + readonly 'userProfileCard.editIconButtonTitle': 'Edit Metadata'; + readonly 'userProfileCard.listItemTitle.email': 'Email'; + readonly 'userProfileCard.listItemTitle.memberOf': 'Member of'; + readonly 'userProfileCard.userNotFound': 'User not found'; + readonly 'userProfileCard.moreGroupButtonTitle': '...More ({{number}})'; + readonly 'userProfileCard.allGroupDialog.title': "All {{name}}'s groups:"; + readonly 'userProfileCard.allGroupDialog.closeButtonTitle': 'Close'; + } +>; + // @public (undocumented) export const OwnershipCard: (props: { - variant?: InfoCardVariants; entityFilterKind?: string[]; hideRelationsToggle?: boolean; relationsType?: EntityRelationAggregation; relationAggregation?: EntityRelationAggregation; entityLimit?: number; - maxScrollHeight?: string; }) => JSX_2.Element; // @public (undocumented) -export type OwnershipCardClassKey = - | 'card' - | 'cardContent' - | 'list' - | 'listItemText' - | 'listItemSecondaryAction' - | 'grid'; +export type OwnershipCardClassKey = 'grid'; // @public (undocumented) export const UserProfileCard: (props: { - variant?: InfoCardVariants; showLinks?: boolean; maxRelations?: number; hideIcons?: boolean; @@ -126,5 +138,6 @@ export const UserProfileCard: (props: { export type UserProfileCardClassKey = | 'closeButton' | 'moreButton' - | 'dialogPaper'; + | 'dialogPaper' + | 'list'; ``` diff --git a/plugins/org/src/alpha.test.tsx b/plugins/org/src/alpha.test.tsx new file mode 100644 index 0000000000..1a42f36e54 --- /dev/null +++ b/plugins/org/src/alpha.test.tsx @@ -0,0 +1,266 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { screen } from '@testing-library/react'; +import { renderTestApp } from '@backstage/frontend-test-utils'; +import { GroupEntity, UserEntity } from '@backstage/catalog-model'; +import { + createTestEntityPage, + catalogApiMock, +} from '@backstage/plugin-catalog-react/testUtils'; +import orgPlugin from './alpha'; + +const EntityGroupProfileCard = orgPlugin.getExtension( + 'entity-card:org/group-profile', +); +const EntityUserProfileCard = orgPlugin.getExtension( + 'entity-card:org/user-profile', +); +const EntityMembersListCard = orgPlugin.getExtension( + 'entity-card:org/members-list', +); + +describe('org plugin entity cards', () => { + describe('EntityGroupProfileCard', () => { + it('should render for Group entities', async () => { + const entity: GroupEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + name: 'platform-team', + description: 'The platform team', + }, + spec: { + type: 'team', + children: [], + }, + }; + + renderTestApp({ + extensions: [createTestEntityPage({ entity }), EntityGroupProfileCard], + }); + + expect(await screen.findByText('platform-team')).toBeInTheDocument(); + expect(await screen.findByText('The platform team')).toBeInTheDocument(); + }); + + it('should not render for non-Group entities', async () => { + const entity: UserEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + name: 'test-user', + }, + spec: { + memberOf: [], + }, + }; + + renderTestApp({ + extensions: [ + createTestEntityPage({ entity }), + EntityGroupProfileCard, + EntityUserProfileCard, + ], + }); + + // User profile card renders as sentinel + expect(await screen.findByText('test-user')).toBeInTheDocument(); + // Group profile card should not render (no group-specific elements) + expect( + screen.queryByText('group:default/test-user'), + ).not.toBeInTheDocument(); + }); + + it('should display group profile with email', async () => { + const entity: GroupEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + name: 'platform-team', + }, + spec: { + type: 'team', + children: [], + profile: { + displayName: 'Platform Team', + email: 'platform@example.com', + }, + }, + }; + + renderTestApp({ + extensions: [createTestEntityPage({ entity }), EntityGroupProfileCard], + }); + + expect(await screen.findByText('Platform Team')).toBeInTheDocument(); + expect( + await screen.findByText('platform@example.com'), + ).toBeInTheDocument(); + }); + }); + + describe('EntityUserProfileCard', () => { + it('should render for User entities', async () => { + const entity: UserEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + name: 'jdoe', + description: 'Software Engineer', + }, + spec: { + profile: { + displayName: 'John Doe', + email: 'jdoe@example.com', + }, + memberOf: [], + }, + }; + + renderTestApp({ + extensions: [createTestEntityPage({ entity }), EntityUserProfileCard], + }); + + expect(await screen.findByText('John Doe')).toBeInTheDocument(); + expect(await screen.findByText('Software Engineer')).toBeInTheDocument(); + expect(await screen.findByText('jdoe@example.com')).toBeInTheDocument(); + }); + + it('should not render for non-User entities', async () => { + const entity: GroupEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + name: 'platform-team', + }, + spec: { + type: 'team', + children: [], + }, + }; + + renderTestApp({ + extensions: [ + createTestEntityPage({ entity }), + EntityGroupProfileCard, + EntityUserProfileCard, + ], + }); + + // Group profile card renders as sentinel + expect(await screen.findByText('platform-team')).toBeInTheDocument(); + // Check that user-specific text doesn't appear + expect(screen.queryByText('jdoe')).not.toBeInTheDocument(); + }); + + it('should fall back to metadata name when no display name', async () => { + const entity: UserEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + name: 'jdoe', + }, + spec: { + memberOf: [], + }, + }; + + renderTestApp({ + extensions: [createTestEntityPage({ entity }), EntityUserProfileCard], + }); + + expect(await screen.findByText('jdoe')).toBeInTheDocument(); + }); + }); + + describe('EntityMembersListCard', () => { + it('should show members when catalog API returns them', async () => { + const groupEntity: GroupEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + name: 'platform-team', + namespace: 'default', + }, + spec: { + type: 'team', + children: [], + }, + }; + + const memberUser: UserEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + name: 'alice', + namespace: 'default', + }, + spec: { + profile: { + displayName: 'Alice Smith', + email: 'alice@example.com', + }, + memberOf: ['group:default/platform-team'], + }, + relations: [ + { + type: 'memberOf', + targetRef: 'group:default/platform-team', + }, + ], + }; + + renderTestApp({ + extensions: [ + createTestEntityPage({ entity: groupEntity }), + EntityMembersListCard, + ], + apis: [catalogApiMock({ entities: [groupEntity, memberUser] })], + }); + + expect(await screen.findByText('Alice Smith')).toBeInTheDocument(); + expect(await screen.findByText('alice@example.com')).toBeInTheDocument(); + }); + + it('should show no members message when catalog API returns empty', async () => { + const groupEntity: GroupEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + name: 'empty-team', + namespace: 'default', + }, + spec: { + type: 'team', + children: [], + }, + }; + + renderTestApp({ + extensions: [ + createTestEntityPage({ entity: groupEntity }), + EntityMembersListCard, + ], + apis: [catalogApiMock({ entities: [groupEntity] })], + }); + + expect( + await screen.findByText('This group has no members.'), + ).toBeInTheDocument(); + }); + }); +}); diff --git a/plugins/org/src/alpha.tsx b/plugins/org/src/alpha.tsx index ddb8094688..2e79f01a52 100644 --- a/plugins/org/src/alpha.tsx +++ b/plugins/org/src/alpha.tsx @@ -14,11 +14,9 @@ * limitations under the License. */ -import { - compatWrapper, - convertLegacyRouteRefs, -} from '@backstage/core-compat-api'; import { createFrontendPlugin } from '@backstage/frontend-plugin-api'; +import { z } from 'zod/v4'; +import { RiTeamLine } from '@remixicon/react'; import { catalogIndexRouteRef } from './routes'; import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha'; @@ -29,8 +27,8 @@ const EntityGroupProfileCard = EntityCardBlueprint.make({ type: 'info', filter: { kind: 'group' }, loader: async () => - import('./components/Cards/Group/GroupProfile/GroupProfileCard').then(m => - compatWrapper(), + import('./components/Cards/Group/GroupProfile/GroupProfileCard').then( + m => , ), }, }); @@ -38,23 +36,20 @@ const EntityGroupProfileCard = EntityCardBlueprint.make({ /** @alpha */ const EntityMembersListCard = EntityCardBlueprint.makeWithOverrides({ name: 'members-list', - config: { - schema: { - initialRelationAggregation: z => - z.enum(['direct', 'aggregated']).optional(), - showAggregateMembersToggle: z => z.boolean().optional(), - }, + configSchema: { + initialRelationAggregation: z.enum(['direct', 'aggregated']).optional(), + showAggregateMembersToggle: z.boolean().optional(), }, factory(originalFactory, { config }) { return originalFactory({ filter: { kind: 'group' }, loader: async () => - import('./components/Cards/Group/MembersList/MembersListCard').then(m => - compatWrapper( + import('./components/Cards/Group/MembersList/MembersListCard').then( + m => ( , + /> ), ), }); @@ -64,30 +59,28 @@ const EntityMembersListCard = EntityCardBlueprint.makeWithOverrides({ /** @alpha */ const EntityOwnershipCard = EntityCardBlueprint.makeWithOverrides({ name: 'ownership', - config: { - schema: { - initialRelationAggregation: z => - z.enum(['direct', 'aggregated']).optional(), - showAggregateMembersToggle: z => z.boolean().optional(), - }, + configSchema: { + initialRelationAggregation: z.enum(['direct', 'aggregated']).optional(), + showAggregateMembersToggle: z.boolean().optional(), + ownedKinds: z.array(z.string()).optional(), }, factory(originalFactory, { config }) { return originalFactory({ filter: { kind: { $in: ['group', 'user'] } }, loader: async () => - import('./components/Cards/OwnershipCard/OwnershipCard').then(m => - compatWrapper( - , - ), - ), + import('./components/Cards/OwnershipCard/OwnershipCard').then(m => ( + + )), }); }, }); @@ -95,11 +88,9 @@ const EntityOwnershipCard = EntityCardBlueprint.makeWithOverrides({ /** @alpha */ const EntityUserProfileCard = EntityCardBlueprint.makeWithOverrides({ name: 'user-profile', - config: { - schema: { - maxRelations: z => z.number().optional(), - hideIcons: z => z.boolean().default(false), - }, + configSchema: { + maxRelations: z.number().optional(), + hideIcons: z.boolean().default(false), }, factory(originalFactory, { config }) { return originalFactory({ @@ -107,13 +98,12 @@ const EntityUserProfileCard = EntityCardBlueprint.makeWithOverrides({ filter: { kind: 'user' }, loader: async () => import('./components/Cards/User/UserProfileCard/UserProfileCard').then( - m => - compatWrapper( - , - ), + m => ( + + ), ), }); }, @@ -122,6 +112,8 @@ const EntityUserProfileCard = EntityCardBlueprint.makeWithOverrides({ /** @alpha */ export default createFrontendPlugin({ pluginId: 'org', + title: 'Org', + icon: , info: { packageJson: () => import('../package.json') }, extensions: [ EntityGroupProfileCard, @@ -129,9 +121,15 @@ export default createFrontendPlugin({ EntityOwnershipCard, EntityUserProfileCard, ], - externalRoutes: convertLegacyRouteRefs({ + externalRoutes: { catalogIndex: catalogIndexRouteRef, - }), + }, }); -export { orgTranslationRef } from './translation'; +import { orgTranslationRef as _orgTranslationRef } from './translation'; + +/** + * @alpha + * @deprecated Import from `@backstage/plugin-org` instead. + */ +export const orgTranslationRef = _orgTranslationRef; diff --git a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx index 89eba12b97..97a6c94ef0 100644 --- a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx +++ b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx @@ -22,13 +22,7 @@ import { RELATION_PARENT_OF, stringifyEntityRef, } from '@backstage/catalog-model'; -import { - Avatar, - InfoCard, - InfoCardVariants, - Link, -} from '@backstage/core-components'; -import Box from '@material-ui/core/Box'; +import { Link } from '@backstage/core-components'; import IconButton from '@material-ui/core/IconButton'; import List from '@material-ui/core/List'; import ListItem from '@material-ui/core/ListItem'; @@ -36,6 +30,7 @@ import ListItemIcon from '@material-ui/core/ListItemIcon'; import ListItemText from '@material-ui/core/ListItemText'; import Tooltip from '@material-ui/core/Tooltip'; import { + EntityInfoCard, EntityRefLinks, catalogApiRef, getEntityRelations, @@ -57,6 +52,7 @@ import { catalogEntityRefreshPermission } from '@backstage/plugin-catalog-common import { useTranslationRef } from '@backstage/frontend-plugin-api'; import { orgTranslationRef } from '../../../../translation'; import { makeStyles } from '@material-ui/core/styles'; +import { Avatar, Flex, Box, Text } from '@backstage/ui'; const useStyles = makeStyles(theme => ({ container: { @@ -68,21 +64,25 @@ const useStyles = makeStyles(theme => ({ list: { padding: 0, marginLeft: theme.spacing(0.5), + overflowWrap: 'anywhere', }, })); -const CardTitle = (props: { title: string }) => ( - - - {props.title} - +const CardTitle = (props: { title: string; pictureSrc?: string }) => ( + + + {props.title} + ); /** @public */ -export const GroupProfileCard = (props: { - variant?: InfoCardVariants; - showLinks?: boolean; -}) => { +export const GroupProfileCard = (props: { showLinks?: boolean }) => { + const { showLinks } = props; const catalogApi = useApi(catalogApiRef); const alertApi = useApi(alertApiRef); const { entity: group } = useEntity(); @@ -148,11 +148,9 @@ export const GroupProfileCard = (props: { ); return ( - } - subheader={description} - variant={props.variant} - action={ + } + headerActions={ <> {allowRefresh && canRefresh && ( } > - - + {description && {description}} + @@ -234,9 +232,9 @@ export const GroupProfileCard = (props: { secondary={t('groupProfileCard.listItemTitle.childGroups')} /> - {props?.showLinks && } + {showLinks && } - + ); }; diff --git a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx index bdc9456fc1..5ae60fb9b2 100644 --- a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx +++ b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx @@ -124,19 +124,11 @@ describe('MemberTab Test', () => { }, }); - expect(screen.getByAltText('Tara MacGovern')).toHaveAttribute( - 'src', - 'https://example.com/staff/tara.jpeg', - ); + expect(screen.getByText('Tara MacGovern')).toBeInTheDocument(); expect(screen.getByText('tara-macgovern@example.com')).toBeInTheDocument(); - expect(screen.getByText('Tara MacGovern').closest('a')).toHaveAttribute( - 'href', - '/catalog/foo-bar/user/tara.macgovern', - ); - expect(screen.getByText('Super Awesome Developer')).toBeInTheDocument(); - expect(screen.getByText('Members (1)')).toBeInTheDocument(); + expect(screen.getByText('team-d members (1 of 1)')).toBeInTheDocument(); }); it('Can render different member display title', async () => { @@ -154,7 +146,7 @@ describe('MemberTab Test', () => { }, ); - expect(screen.getByText('Testers (1)')).toBeInTheDocument(); + expect(screen.getByText('Testers (1 of 1)')).toBeInTheDocument(); }); it('Can query a different relationship', async () => { @@ -205,8 +197,7 @@ describe('MemberTab Test', () => { }, }, ); - const toggleSwitch = screen.queryByRole('checkbox'); - expect(toggleSwitch).toBeNull(); + expect(screen.queryByRole('switch')).not.toBeInTheDocument(); }); it('Shows the aggregate members toggle if the showAggregateMembersToggle prop is true', async () => { @@ -233,8 +224,9 @@ describe('MemberTab Test', () => { }, }, ); - expect(screen.queryByRole('checkbox')).toBeInTheDocument(); + expect(screen.getByRole('switch')).toBeInTheDocument(); }); + it('Shows only direct members if the showAggregateMembersToggle prop is undefined', async () => { await renderInTestApp( { }, }, ); - const displayedMemberNames = screen.queryAllByTestId('user-link'); const duplicatedUserText = screen.getByText('Duplicated User'); const groupAUserOneText = screen.getByText('Group A User One'); - expect(displayedMemberNames).toHaveLength(2); + expect(screen.getAllByRole('heading', { level: 4 })).toHaveLength(2); expect(duplicatedUserText).toBeInTheDocument(); expect(groupAUserOneText).toBeInTheDocument(); expect( @@ -294,10 +285,9 @@ describe('MemberTab Test', () => { }, }, ); - const displayedMemberNames = screen.queryAllByTestId('user-link'); const duplicatedUserText = screen.getByText('Duplicated User'); const groupAUserOneText = screen.getByText('Group A User One'); - expect(displayedMemberNames).toHaveLength(2); + expect(screen.getAllByRole('heading', { level: 4 })).toHaveLength(2); expect(duplicatedUserText).toBeInTheDocument(); expect(groupAUserOneText).toBeInTheDocument(); expect( @@ -331,18 +321,16 @@ describe('MemberTab Test', () => { ); // Should show only direct users on initial load - const displayedMemberNamesBefore = screen.queryAllByTestId('user-link'); - expect(displayedMemberNamesBefore).toHaveLength(2); + expect(screen.getAllByRole('heading', { level: 4 })).toHaveLength(2); // Click the toggle switch - await userEvent.click(screen.getByRole('checkbox')); - const displayedMemberNamesAfter = screen.queryAllByTestId('user-link'); + await userEvent.click(screen.getByRole('switch')); const duplicatedUserText = screen.getByText('Duplicated User'); const groupAUserOneText = screen.getByText('Group A User One'); const groupBUserOneText = screen.getByText('Group B User One'); const groupDUserOneText = screen.getByText('Group D User One'); const groupEUserOneText = screen.getByText('Group E User One'); - expect(displayedMemberNamesAfter).toHaveLength(5); + expect(screen.getAllByRole('heading', { level: 4 })).toHaveLength(5); expect(duplicatedUserText).toBeInTheDocument(); expect(groupAUserOneText).toBeInTheDocument(); expect(groupBUserOneText).toBeInTheDocument(); @@ -392,15 +380,13 @@ describe('MemberTab Test', () => { ); // Should show aggregated users on initial load - const displayedMemberNamesBefore = screen.queryAllByTestId('user-link'); - expect(displayedMemberNamesBefore).toHaveLength(5); + expect(screen.getAllByRole('heading', { level: 4 })).toHaveLength(5); // Click the toggle switch - await userEvent.click(screen.getByRole('checkbox')); + await userEvent.click(screen.getByRole('switch')); // Should now show only direct users - const displayedMemberNamesAfter = screen.queryAllByTestId('user-link'); - expect(displayedMemberNamesAfter).toHaveLength(2); + expect(screen.getAllByRole('heading', { level: 4 })).toHaveLength(2); }); it('Can show aggregated members without the aggregate members toggle', async () => { @@ -428,11 +414,62 @@ describe('MemberTab Test', () => { }, ); - // aggregated relations checkbox should not be rendered - expect(screen.queryByRole('checkbox')).toBeNull(); + // aggregated relations switch should not be rendered + expect(screen.queryByRole('switch')).not.toBeInTheDocument(); // Should show all descendant users on load - const displayedMemberNames = screen.queryAllByTestId('user-link'); - expect(displayedMemberNames).toHaveLength(5); + expect(screen.getAllByRole('heading', { level: 4 })).toHaveLength(5); + }); + + describe('Search', () => { + it('filters members by name', async () => { + await renderInTestApp( + + + + + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name': entityRouteRef, + '/catalog': rootRouteRef, + }, + }, + ); + + // type into the search field + await userEvent.type(screen.getByPlaceholderText(/search/i), 'Tara'); + + // Tara should show + expect(await screen.findByText('Tara MacGovern')).toBeInTheDocument(); + + // Someone else not in results should be missing + expect(screen.queryByText('Nigel Manning')).not.toBeInTheDocument(); + }); + + it('shows no members message when search yields no results', async () => { + await renderInTestApp( + + + + + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name': entityRouteRef, + '/catalog': rootRouteRef, + }, + }, + ); + + await userEvent.type( + screen.getByPlaceholderText(/search/i), + 'XYZDoesNotExist', + ); + + expect( + screen.getByText(/Found no members matching/i), + ).toBeInTheDocument(); + }); }); }); diff --git a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx index 07119ba85b..90ad671a43 100644 --- a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx +++ b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx @@ -23,25 +23,14 @@ import { import { catalogApiRef, useEntity, - EntityRefLink, + EntityInfoCard, + useEntityRefLink, } from '@backstage/plugin-catalog-react'; -import Box from '@material-ui/core/Box'; -import Grid from '@material-ui/core/Grid'; -import Switch from '@material-ui/core/Switch'; -import Typography from '@material-ui/core/Typography'; -import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; -import Pagination from '@material-ui/lab/Pagination'; -import { useState, ChangeEvent } from 'react'; +import { makeStyles } from '@material-ui/core/styles'; +import { useState, useEffect } from 'react'; import useAsync from 'react-use/esm/useAsync'; -import { - Avatar, - InfoCard, - Progress, - ResponseErrorPanel, - Link, - OverflowTooltip, -} from '@backstage/core-components'; +import { Progress, ResponseErrorPanel } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; import { getAllDesendantMembersForGroupEntity, @@ -50,100 +39,107 @@ import { import { EntityRelationAggregation } from '../../types'; import { useTranslationRef } from '@backstage/frontend-plugin-api'; import { orgTranslationRef } from '../../../../translation'; +import { + Avatar, + Box, + Card, + Flex, + Link, + SearchField, + Switch, + TablePagination, + Text, +} from '@backstage/ui'; -/** @public */ -export type MemberComponentClassKey = 'card' | 'avatar'; - -const useStyles = makeStyles( - (theme: Theme) => - createStyles({ - card: { - border: `1px solid ${theme.palette.divider}`, - boxShadow: theme.shadows[2], - borderRadius: '4px', - overflow: 'visible', - position: 'relative', - margin: theme.spacing(4, 1, 1), - flex: '1', - minWidth: '0px', - }, - avatar: { - position: 'absolute', - top: '-2rem', - }, - }), - { name: 'PluginOrgMemberComponent' }, -); +const useMemberStyles = makeStyles({ + card: { + display: 'flex', + gap: 'var(--bui-space-3)', + padding: 'var(--bui-space-3)', + alignItems: 'flex-start', + flexDirection: 'row', + height: 140, + overflow: 'hidden', + }, + avatar: { + flexShrink: 0, + }, + cardTextContainer: { + overflow: 'hidden', + }, + singlelineEllipsis: { + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + }, + multilineEllipsis: { + display: '-webkit-box', + '-webkit-line-clamp': '3', + '-webkit-box-orient': 'vertical', + overflow: 'hidden', + }, +}); const MemberComponent = (props: { member: UserEntity }) => { - const classes = useStyles(); + const { t } = useTranslationRef(orgTranslationRef); + const classes = useMemberStyles(); const { metadata: { name: metaName, description }, spec: { profile }, } = props.member; const displayName = profile?.displayName ?? metaName; + const entityLink = useEntityRefLink(); return ( - - - - - - - - {profile?.email && ( - - - - )} - {description && ( - - - - )} - - - + + + + + {displayName} + + {profile?.email && ( + + {profile.email} + + )} + {description && ( + {description} + )} + + ); }; /** @public */ -export type MembersListCardClassKey = 'root' | 'cardContent' | 'memberList'; +export type MembersListCardClassKey = 'memberList'; const useListStyles = makeStyles( - theme => ({ - root: { - height: '100%', - }, - cardContent: { - overflow: 'auto', - }, + () => ({ memberList: { display: 'grid', - gap: theme.spacing(1.5), - gridTemplateColumns: `repeat(auto-fit, minmax(auto, ${theme.spacing( - 34, - )}px))`, + gap: 'var(--bui-space-3)', + gridTemplateColumns: `repeat(auto-fit, minmax(275px, 1fr))`, + gridAutoRows: '1fr', + margin: 0, + padding: 0, + paddingTop: 'var(--bui-space-3)', + listStyle: 'none', + }, + memberListItem: { + display: 'contents', }, }), { name: 'PluginOrgMembersListCardComponent' }, @@ -161,7 +157,7 @@ export const MembersListCard = (props: { }) => { const { t } = useTranslationRef(orgTranslationRef); const { - memberDisplayTitle = t('membersListCard.title'), + memberDisplayTitle, pageSize = 50, showAggregateMembersToggle, relationType = 'memberof', @@ -172,24 +168,30 @@ export const MembersListCard = (props: { const { entity: groupEntity } = useEntity(); const { - metadata: { name: groupName, namespace: grpNamespace }, + metadata: { name: groupName, namespace: grpNamespace, title: groupTitle }, spec: { profile }, } = groupEntity; const catalogApi = useApi(catalogApiRef); - const displayName = profile?.displayName ?? groupName; + const displayName = profile?.displayName ?? groupTitle ?? groupName; + const cardTitle = + memberDisplayTitle ?? + t('membersListCard.title', { groupName: displayName }); const groupNamespace = grpNamespace || DEFAULT_NAMESPACE; - const [page, setPage] = useState(1); - const pageChange = (_: ChangeEvent, pageIndex: number) => { - setPage(pageIndex); - }; + const [offset, setOffset] = useState(0); const [showAggregateMembers, setShowAggregateMembers] = useState( relationAggregation === 'aggregated', ); + const [searchTerm, setSearchTerm] = useState(''); + + useEffect(() => { + setOffset(0); + }, [searchTerm, showAggregateMembers]); + const { loading: loadingDescendantMembers, value: descendantMembers } = useAsync(async () => { if (!showAggregateMembers) { @@ -238,79 +240,94 @@ export const MembersListCard = (props: { return ; } - const nbPages = Math.ceil((members?.length || 0) / pageSize); - const paginationLabel = - nbPages < 2 - ? '' - : t('membersListCard.paginationLabel', { - page: String(page), - nbPages: String(nbPages), - }); + const filteredMembers = members.filter(member => { + const fields = [ + member.metadata.name, + member.metadata.title, + member.spec?.profile?.displayName, + member.spec?.profile?.email, + ]; + return fields.some(val => + val + ?.toLocaleLowerCase('en-US') + .includes(searchTerm.toLocaleLowerCase('en-US')), + ); + }); - const pagination = ( - - ); + const membersToRender = searchTerm ? filteredMembers : members; + const totalCount = membersToRender.length; + const hasNextPage = offset + pageSize < totalCount; + const hasPreviousPage = offset > 0; + + const pagination = + totalCount > pageSize ? ( + setOffset(prev => prev + pageSize)} + onPreviousPage={() => setOffset(prev => Math.max(0, prev - pageSize))} + /> + ) : undefined; let memberList: JSX.Element; - if (members && members.length > 0) { + if (membersToRender.length > 0) { memberList = ( - - {members.slice(pageSize * (page - 1), pageSize * page).map(member => ( - +
      + {membersToRender.slice(offset, offset + pageSize).map(member => ( +
    • + +
    • ))} - +
    ); } else { memberList = ( - - {t('membersListCard.noMembersDescription')} + + + {searchTerm + ? t('membersListCard.noSearchResult', { searchTerm }) + : t('membersListCard.noMembersDescription')} + ); } return ( - - - {showAggregateMembersToggle && ( - <> - {t('membersListCard.aggregateMembersToggle.directMembers')} - { - setShowAggregateMembers(!showAggregateMembers); - }} - inputProps={{ - 'aria-label': t( - 'membersListCard.aggregateMembersToggle.ariaLabel', - ), - }} - /> - {t('membersListCard.aggregateMembersToggle.aggregatedMembers')} - - )} - {showAggregateMembers && loadingDescendantMembers ? ( - - ) : ( - memberList - )} - - + + ) + } + footerActions={pagination} + > + {showAggregateMembers && loadingDescendantMembers ? ( + + ) : ( + <> + setSearchTerm('')} + /> + {memberList} + + )} + ); }; diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx index 67d1073dab..ce0712f076 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx @@ -29,6 +29,7 @@ import { OwnershipCard } from './OwnershipCard'; export default { title: 'Plugins/Org/Ownership Card', component: OwnershipCard, + tags: ['!manifest'], }; const defaultEntity: GroupEntity = { diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx index 6ea0f0cbc9..780b9d4837 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx @@ -17,7 +17,7 @@ import { Entity, GroupEntity, UserEntity } from '@backstage/catalog-model'; import { catalogApiRef, EntityProvider } from '@backstage/plugin-catalog-react'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; -import { queryByText } from '@testing-library/react'; +import { queryByText, screen } from '@testing-library/react'; import { catalogIndexRouteRef } from '../../../routes'; import { OwnershipCard } from './OwnershipCard'; import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils'; @@ -36,11 +36,21 @@ const items = [ { type: 'ownedBy', targetRef: 'group:default/my-team', - target: { - name: 'my-team', - namespace: 'default', - kind: 'group', - }, + }, + ], + }, + { + kind: 'Resource', + metadata: { + name: 'my-resource', + }, + spec: { + type: 'database', + }, + relations: [ + { + type: 'ownedBy', + targetRef: 'group:default/my-team', }, ], }, @@ -56,11 +66,6 @@ const items = [ { type: 'ownedBy', targetRef: 'group:default/my-team', - target: { - name: 'my-team', - namespace: 'default', - kind: 'group', - }, }, ], }, @@ -77,11 +82,6 @@ const items = [ { type: 'ownedBy', targetRef: 'group:default/my-team', - target: { - name: 'my-team', - namespace: 'default', - kind: 'group', - }, }, ], }, @@ -160,7 +160,7 @@ describe('OwnershipCard', () => { expect(mockedGetEntities).toHaveBeenCalledWith({ filter: [ { - kind: ['Component', 'API', 'System'], + kind: ['Component', 'API', 'System', 'Resource'], 'relations.ownedBy': ['group:default/my-team'], }, ], @@ -275,7 +275,7 @@ describe('OwnershipCard', () => { it('shows relations toggle', async () => { const catalogApi = catalogApiMock({ entities: items }); - const { getByTitle } = await renderInTestApp( + await renderInTestApp( @@ -288,13 +288,15 @@ describe('OwnershipCard', () => { }, ); - expect(getByTitle('Direct Relations')).toBeInTheDocument(); + expect( + screen.getByRole('switch', { name: 'Include indirect ownership' }), + ).toBeInTheDocument(); }); it('hides relations toggle', async () => { const catalogApi = catalogApiMock({ entities: items }); - const rendered = await renderInTestApp( + await renderInTestApp( @@ -307,13 +309,13 @@ describe('OwnershipCard', () => { }, ); - expect(rendered.queryByText('Direct Relations')).toBeNull(); + expect(screen.queryByRole('switch')).not.toBeInTheDocument(); }); it('overrides relation type', async () => { const catalogApi = catalogApiMock({ entities: items }); - const { getByTitle } = await renderInTestApp( + await renderInTestApp( @@ -326,13 +328,13 @@ describe('OwnershipCard', () => { }, ); - expect(getByTitle('Aggregated Relations')).toBeInTheDocument(); + await expect(screen.findByRole('switch')).resolves.toBeChecked(); }); it('defaults to aggregated for User entity kind', async () => { const catalogApi = catalogApiMock({ entities: items }); - const { getByLabelText } = await renderInTestApp( + await renderInTestApp( @@ -345,13 +347,13 @@ describe('OwnershipCard', () => { }, ); - expect(getByLabelText('Ownership Type Switch')).toBeChecked(); + await expect(screen.findByRole('switch')).resolves.toBeChecked(); }); it('defaults to direct for all entity kinds except User', async () => { const catalogApi = catalogApiMock({ entities: items }); - const { getByLabelText } = await renderInTestApp( + await renderInTestApp( @@ -364,13 +366,13 @@ describe('OwnershipCard', () => { }, ); - expect(getByLabelText('Ownership Type Switch')).not.toBeChecked(); + expect(screen.getByRole('switch')).not.toBeChecked(); }); it('defaults to provided relationsType', async () => { const catalogApi = catalogApiMock({ entities: items }); - const { getByLabelText } = await renderInTestApp( + await renderInTestApp( @@ -383,7 +385,7 @@ describe('OwnershipCard', () => { }, ); - expect(getByLabelText('Ownership Type Switch')).not.toBeChecked(); + expect(screen.getByRole('switch')).not.toBeChecked(); }); }); }); diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx index 1df55c62c2..d756fdaca3 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx @@ -14,71 +14,25 @@ * limitations under the License. */ -import { InfoCard, InfoCardVariants } from '@backstage/core-components'; -import { useEntity } from '@backstage/plugin-catalog-react'; -import List from '@material-ui/core/List'; -import ListItem from '@material-ui/core/ListItem'; -import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'; -import ListItemText from '@material-ui/core/ListItemText'; -import Switch from '@material-ui/core/Switch'; -import Tooltip from '@material-ui/core/Tooltip'; +import { EntityInfoCard, useEntity } from '@backstage/plugin-catalog-react'; +import { Switch } from '@backstage/ui'; import { createStyles, makeStyles } from '@material-ui/core/styles'; import { useEffect, useState } from 'react'; import { ComponentsGrid } from './ComponentsGrid'; import { EntityRelationAggregation } from '../types'; import { useTranslationRef } from '@backstage/frontend-plugin-api'; import { orgTranslationRef } from '../../../translation'; -import Box from '@material-ui/core/Box'; /** @public */ -export type OwnershipCardClassKey = - | 'card' - | 'cardContent' - | 'list' - | 'listItemText' - | 'listItemSecondaryAction' - | 'grid'; +export type OwnershipCardClassKey = 'grid'; const useStyles = makeStyles( - theme => + () => createStyles({ - card: { - maxHeight: '100%', - }, - cardContent: { - display: 'flex', - flexDirection: 'column', - overflow: 'hidden', - }, - list: { - [theme.breakpoints.down('xs')]: { - padding: `0 0 12px`, - }, - }, - listItemText: { - [theme.breakpoints.down('xs')]: { - paddingRight: 0, - paddingLeft: 0, - }, - }, - listItemSecondaryAction: { - [theme.breakpoints.down('xs')]: { - width: '100%', - top: 'auto', - right: 'auto', - position: 'relative', - transform: 'unset', - }, - }, grid: { overflowY: 'auto', marginTop: 0, }, - box: { - overflowY: 'auto', - padding: theme.spacing(0, 1, 1), - margin: theme.spacing(0, -1), - }, }), { name: 'PluginOrgOwnershipCard', @@ -87,27 +41,17 @@ const useStyles = makeStyles( /** @public */ export const OwnershipCard = (props: { - variant?: InfoCardVariants; entityFilterKind?: string[]; hideRelationsToggle?: boolean; /** @deprecated Please use relationAggregation instead */ relationsType?: EntityRelationAggregation; relationAggregation?: EntityRelationAggregation; entityLimit?: number; - maxScrollHeight?: string; }) => { - const { - variant, - entityFilterKind, - hideRelationsToggle, - entityLimit = 6, - maxScrollHeight: propMaxScrollHeight, - } = props; + const { entityFilterKind, hideRelationsToggle, entityLimit = 6 } = props; const relationAggregation = props.relationAggregation ?? props.relationsType; const relationsToggle = hideRelationsToggle === undefined ? false : hideRelationsToggle; - const maxScrollHeight = - variant !== 'fullHeight' ? propMaxScrollHeight : undefined; const classes = useStyles(); const { entity } = useEntity(); const { t } = useTranslationRef(orgTranslationRef); @@ -125,65 +69,27 @@ export const OwnershipCard = (props: { }, [setRelationAggregation, defaultRelationAggregation, relationAggregation]); return ( - + setRelationAggregation(isSelected ? 'aggregated' : 'direct') + } + label={t('ownershipCard.aggregateRelationsToggle.label')} + /> + ) + } > - {!relationsToggle && ( - - - - - {t('ownershipCard.aggregateRelationsToggle.directRelations')} - - { - const updatedRelationAggregation = - getRelationAggregation === 'direct' - ? 'aggregated' - : 'direct'; - setRelationAggregation(updatedRelationAggregation); - }} - name="pin" - inputProps={{ - 'aria-label': t( - 'ownershipCard.aggregateRelationsToggle.ariaLabel', - ), - }} - /> - - {t('ownershipCard.aggregateRelationsToggle.aggregatedRelations')} - - - - )} - - - - + + ); }; diff --git a/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.test.ts b/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.test.ts index b7331479ef..caa657b16d 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.test.ts +++ b/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.test.ts @@ -18,6 +18,7 @@ import { CompoundEntityRef, Entity } from '@backstage/catalog-model'; import { useGetEntities } from './useGetEntities'; import { renderHook, waitFor } from '@testing-library/react'; import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils'; +import qs from 'qs'; const givenParentGroup = 'team.squad1'; const givenLeafGroup = 'team.squad2'; @@ -45,8 +46,10 @@ const getEntityRelationsMock: jest.Mock< [Entity | undefined] > = jest.fn(); jest.mock('@backstage/plugin-catalog-react', () => { + const actual = jest.requireActual('@backstage/plugin-catalog-react'); return { catalogApiRef: {}, + entityPresentationSnapshot: actual.entityPresentationSnapshot, getEntityRelations: jest.fn(entity => { return getEntityRelationsMock(entity); }) as any, @@ -283,6 +286,144 @@ describe('useGetEntities', () => { }); }); + describe('queryParams generation', () => { + afterEach(() => { + getEntityRelationsMock.mockRestore(); + catalogApi.getEntities.mockRestore(); + catalogApi.getEntitiesByRefs.mockRestore(); + }); + + it('should produce query params with humanized entity refs as owners', async () => { + getEntityRelationsMock.mockReturnValue([]); + catalogApi.getEntities.mockResolvedValueOnce({ + items: [ + { + kind: 'Component', + metadata: { name: 'my-service', namespace: 'default' }, + spec: { type: 'service' }, + } as Partial as Entity, + ], + }); + + const { result } = renderHook( + ({ entity }) => useGetEntities(entity, 'direct'), + { initialProps: { entity: givenLeafGroupEntity } }, + ); + + await waitFor(() => expect(result.current.loading).toBe(false)); + + expect(result.current.componentsWithCounters).toHaveLength(1); + const params = qs.parse( + result.current.componentsWithCounters![0].queryParams, + ); + expect(params).toEqual({ + filters: { + kind: 'component', + type: 'service', + owners: givenLeafGroup, + user: 'all', + }, + }); + }); + + it('should include multiple owners as an array in query params', async () => { + getEntityRelationsMock + .mockReturnValueOnce([createGroupRefFromName(givenLeafGroup)]) + .mockReturnValue([]); + + catalogApi.getEntitiesByRefs.mockResolvedValueOnce({ + items: [givenLeafGroupEntity], + }); + catalogApi.getEntities.mockResolvedValueOnce({ + items: [ + { + kind: 'API', + metadata: { name: 'my-api', namespace: 'default' }, + spec: { type: 'openapi' }, + } as Partial as Entity, + ], + }); + + const { result } = renderHook( + ({ entity }) => useGetEntities(entity, 'aggregated'), + { initialProps: { entity: givenParentGroupEntity } }, + ); + + await waitFor(() => expect(result.current.loading).toBe(false)); + + expect(result.current.componentsWithCounters).toHaveLength(1); + const params = qs.parse( + result.current.componentsWithCounters![0].queryParams, + ); + expect(params).toEqual({ + filters: { + kind: 'api', + type: 'openapi', + owners: expect.arrayContaining([givenLeafGroup, givenParentGroup]), + user: 'all', + }, + }); + }); + + it('should group entities by kind and type in query params', async () => { + getEntityRelationsMock.mockReturnValue([]); + catalogApi.getEntities.mockResolvedValueOnce({ + items: [ + { + kind: 'Component', + metadata: { name: 'svc-1', namespace: 'default' }, + spec: { type: 'service' }, + } as Partial as Entity, + { + kind: 'Component', + metadata: { name: 'svc-2', namespace: 'default' }, + spec: { type: 'service' }, + } as Partial as Entity, + { + kind: 'API', + metadata: { name: 'api-1', namespace: 'default' }, + spec: { type: 'openapi' }, + } as Partial as Entity, + ], + }); + + const { result } = renderHook( + ({ entity }) => useGetEntities(entity, 'direct'), + { initialProps: { entity: givenLeafGroupEntity } }, + ); + + await waitFor(() => expect(result.current.loading).toBe(false)); + + expect(result.current.componentsWithCounters).toHaveLength(2); + + const componentEntry = result.current.componentsWithCounters!.find( + c => c.kind === 'Component', + )!; + expect(componentEntry.counter).toBe(2); + + const componentParams = qs.parse(componentEntry.queryParams); + expect(componentParams).toEqual({ + filters: expect.objectContaining({ + kind: 'component', + type: 'service', + }), + }); + + const apiEntry = result.current.componentsWithCounters!.find( + c => c.kind === 'API', + )!; + expect(apiEntry.counter).toBe(1); + + const apiParams = qs.parse(apiEntry.queryParams); + expect(apiParams).toEqual({ + filters: expect.objectContaining({ + kind: 'api', + type: 'openapi', + }), + }); + }); + }); + describe('useGetEntities exceeding default 16384 bytes header size', () => { const largeNumberOfGroups = Array.from({ length: 600 }, (_, i) => createGroupEntityFromName(`very-long-group-name-${i + 1}`), diff --git a/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.ts b/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.ts index a5a5363192..9a98ad788e 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.ts +++ b/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.ts @@ -16,7 +16,6 @@ import { Entity, - parseEntityRef, RELATION_MEMBER_OF, RELATION_PARENT_OF, stringifyEntityRef, @@ -24,8 +23,8 @@ import { import { CatalogApi, catalogApiRef, + entityPresentationSnapshot, getEntityRelations, - humanizeEntityRef, } from '@backstage/plugin-catalog-react'; import limiterFactory from 'p-limit'; import { useApi } from '@backstage/core-plugin-api'; @@ -47,8 +46,9 @@ const getQueryParams = ( selectedEntity: EntityTypeProps, ): string => { const { kind, type } = selectedEntity; - const owners = ownersEntityRef.map(owner => - humanizeEntityRef(parseEntityRef(owner), { defaultKind: 'group' }), + const owners = ownersEntityRef.map( + ref => + entityPresentationSnapshot(ref, { defaultKind: 'group' }).primaryTitle, ); const filters = { kind: kind.toLocaleLowerCase('en-US'), @@ -198,7 +198,7 @@ export function useGetEntities( error?: Error; } { const catalogApi = useApi(catalogApiRef); - const kinds = entityFilterKind ?? ['Component', 'API', 'System']; + const kinds = entityFilterKind ?? ['Component', 'API', 'System', 'Resource']; const { loading, diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx index f485a4e92d..aa1bf978e6 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx @@ -55,7 +55,7 @@ export const Default = () => ( - + @@ -82,7 +82,7 @@ export const NoImage = () => ( - + @@ -99,6 +99,7 @@ export default { }, }), ], + tags: ['!manifest'], }; const extraDetailsEntity: UserEntity = { @@ -133,7 +134,7 @@ export const ExtraDetails = () => ( - + diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx index b5932dfb8e..45936c564f 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx @@ -51,7 +51,7 @@ describe('UserSummary Test', () => { it('Display Profile Card', async () => { await renderInTestApp( - + , { mountedRoutes: { @@ -61,10 +61,10 @@ describe('UserSummary Test', () => { ); expect(screen.getByText('calum-leavy@example.com')).toBeInTheDocument(); - expect(screen.getByAltText('Calum Leavy')).toHaveAttribute( - 'src', - 'https://example.com/staff/calum.jpeg', - ); + // BUI Avatar is decorative (aria-hidden), because the name is + // present as text right beside it. + expect(screen.getByRole('img', { hidden: true })).toBeInTheDocument(); + expect(screen.getByText('Calum Leavy')).toBeInTheDocument(); expect(screen.getByText('examplegroup').closest('a')).toHaveAttribute( 'href', '/catalog/default/group/examplegroup', @@ -87,7 +87,7 @@ describe('UserSummary Test', () => { it('Should limit the number of displayed groups in the card', async () => { await renderInTestApp( - + , { mountedRoutes: { @@ -109,7 +109,7 @@ describe('UserSummary Test', () => { it('Show more groups button when there are more user relations than the maximum', async () => { await renderInTestApp( - + , { mountedRoutes: { @@ -128,7 +128,7 @@ describe('UserSummary Test', () => { it('Hide more groups button when limit value is less than or equal to user relations', async () => { await renderInTestApp( - + , { mountedRoutes: { @@ -148,7 +148,7 @@ describe('UserSummary Test', () => { it('Hide all groups when max relations is equals to zero', async () => { await renderInTestApp( - + , { mountedRoutes: { @@ -193,7 +193,7 @@ describe('Edit Button', () => { await renderInTestApp( - + , { mountedRoutes: { @@ -234,7 +234,7 @@ describe('Edit Button', () => { await renderInTestApp( - + , { mountedRoutes: { @@ -285,7 +285,7 @@ describe('Edit Button', () => { await renderInTestApp( - + , { mountedRoutes: { @@ -337,7 +337,7 @@ describe('Edit Button', () => { await renderInTestApp( - + , { mountedRoutes: { diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx index ba3ee9d561..cbdfed6f14 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx @@ -19,15 +19,10 @@ import { RELATION_MEMBER_OF, UserEntity, } from '@backstage/catalog-model'; -import { - Avatar, - InfoCard, - InfoCardVariants, - Link, -} from '@backstage/core-components'; +import { Link } from '@backstage/core-components'; +import { EntityInfoCard } from '@backstage/plugin-catalog-react'; +import { Avatar, Box, Flex, Text } from '@backstage/ui'; import { createStyles, makeStyles } from '@material-ui/core/styles'; -import Box from '@material-ui/core/Box'; -import Grid from '@material-ui/core/Grid'; import BaseButton from '@material-ui/core/ButtonBase'; import IconButton from '@material-ui/core/IconButton'; import List from '@material-ui/core/List'; @@ -52,7 +47,6 @@ import EditIcon from '@material-ui/icons/Edit'; import EmailIcon from '@material-ui/icons/Email'; import GroupIcon from '@material-ui/icons/Group'; import { LinksGroup } from '../../Meta'; -import PersonIcon from '@material-ui/icons/Person'; import { useCallback, useState } from 'react'; import { useTranslationRef } from '@backstage/frontend-plugin-api'; @@ -62,7 +56,8 @@ import { orgTranslationRef } from '../../../../translation'; export type UserProfileCardClassKey = | 'closeButton' | 'moreButton' - | 'dialogPaper'; + | 'dialogPaper' + | 'list'; const useStyles = makeStyles( theme => @@ -80,21 +75,28 @@ const useStyles = makeStyles( dialogPaper: { minHeight: 400, }, + list: { + overflowWrap: 'anywhere', + }, }), { name: 'PluginOrgUserProfileCard' }, ); -const CardTitle = (props: { title?: string }) => +const CardTitle = (props: { title: string; pictureSrc?: string }) => props.title ? ( - - - {props.title} - + + + {props.title} + ) : null; /** @public */ export const UserProfileCard = (props: { - variant?: InfoCardVariants; showLinks?: boolean; maxRelations?: number; hideIcons?: boolean; @@ -132,11 +134,9 @@ export const UserProfileCard = (props: { }); return ( - } - subheader={description} - variant={props.variant} - action={ + } + headerActions={ <> {entityMetadataEditUrl && ( } > - - - - + {description && {description}} + + + {profile?.email && ( + + + + + + + + {profile.email} + + + )} - - - {profile?.email && ( - - - - - - - - {profile.email} - - - )} - - {maxRelations === undefined || maxRelations > 0 ? ( - - - - - - - - - {maxRelations && memberOfRelations.length > maxRelations ? ( - <> - , - - {t('userProfileCard.moreGroupButtonTitle', { - number: String( - memberOfRelations.length - maxRelations, - ), - })} - - - ) : null} - - - ) : null} - {props?.showLinks && } - - - + {maxRelations === undefined || maxRelations > 0 ? ( + + + + + + + + + {maxRelations && memberOfRelations.length > maxRelations ? ( + <> + , + + {t('userProfileCard.moreGroupButtonTitle', { + number: String(memberOfRelations.length - maxRelations), + })} + + + ) : null} + + + ) : null} + {props?.showLinks && } + + - + ); }; diff --git a/plugins/org/src/index.ts b/plugins/org/src/index.ts index 33671697d6..90f475bb60 100644 --- a/plugins/org/src/index.ts +++ b/plugins/org/src/index.ts @@ -29,3 +29,4 @@ export { EntityUserProfileCard, } from './plugin'; export * from './components'; +export { orgTranslationRef } from './translation'; diff --git a/plugins/org/src/overridableComponents.ts b/plugins/org/src/overridableComponents.ts index b4c5f31212..a5057e57ec 100644 --- a/plugins/org/src/overridableComponents.ts +++ b/plugins/org/src/overridableComponents.ts @@ -18,7 +18,6 @@ import { Overrides } from '@material-ui/core/styles/overrides'; import { StyleRules } from '@material-ui/core/styles/withStyles'; import { ComponentsGridClassKey, - MemberComponentClassKey, MembersListCardClassKey, OwnershipCardClassKey, UserProfileCardClassKey, @@ -26,7 +25,6 @@ import { /** @public */ export type CatalogReactComponentsNameToClassKey = { - PluginOrgMemberComponent: MemberComponentClassKey; PluginOrgMembersListCardComponent: MembersListCardClassKey; PluginOrgOwnershipCard: OwnershipCardClassKey; PluginOrgComponentsGrid: ComponentsGridClassKey; diff --git a/plugins/org/src/translation.ts b/plugins/org/src/translation.ts index b7dfda6937..aa1c248c17 100644 --- a/plugins/org/src/translation.ts +++ b/plugins/org/src/translation.ts @@ -16,7 +16,7 @@ import { createTranslationRef } from '@backstage/frontend-plugin-api'; /** - * @alpha + * @public */ export const orgTranslationRef = createTranslationRef({ id: 'org', @@ -34,22 +34,18 @@ export const orgTranslationRef = createTranslationRef({ }, }, membersListCard: { - title: 'Members', - subtitle: 'of {{groupName}}', - paginationLabel: ', page {{page}} of {{nbPages}}', + cardLabel: 'User page for {{memberName}}', + title: '{{groupName}} members', noMembersDescription: 'This group has no members.', + noSearchResult: 'Found no members matching "{{searchTerm}}".', aggregateMembersToggle: { - directMembers: 'Direct Members', - aggregatedMembers: 'Aggregated Members', - ariaLabel: 'Users Type Switch', + label: 'Include subgroups', }, }, ownershipCard: { title: 'Ownership', aggregateRelationsToggle: { - directRelations: 'Direct Relations', - aggregatedRelations: 'Aggregated Relations', - ariaLabel: 'Ownership Type Switch', + label: 'Include indirect ownership', }, }, userProfileCard: { diff --git a/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md b/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md index 882f6fdb23..f8d25d7728 100644 --- a/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md +++ b/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md @@ -1,5 +1,132 @@ # @backstage/plugin-permission-backend-module-allow-all-policy +## 0.2.18 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/plugin-auth-node@0.7.0 + - @backstage/plugin-permission-node@0.10.12 + - @backstage/plugin-permission-common@0.9.8 + +## 0.2.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.7.0-next.2 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/plugin-permission-common@0.9.8-next.0 + - @backstage/plugin-permission-node@0.10.12-next.2 + +## 0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/plugin-permission-node@0.10.12-next.1 + +## 0.2.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-auth-node@0.6.15-next.0 + - @backstage/plugin-permission-node@0.10.12-next.0 + - @backstage/plugin-permission-common@0.9.7 + +## 0.2.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/plugin-permission-node@0.10.11 + - @backstage/plugin-auth-node@0.6.14 + +## 0.2.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + - @backstage/plugin-permission-node@0.10.11-next.1 + +## 0.2.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/plugin-auth-node@0.6.14-next.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.11-next.0 + +## 0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.10 + +## 0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/plugin-permission-node@0.10.9-next.0 + +## 0.2.15 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + +## 0.2.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/plugin-permission-common@0.9.3 + +## 0.2.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/plugin-permission-common@0.9.3 + +## 0.2.14 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/plugin-permission-node@0.10.6 + ## 0.2.14-next.1 ### Patch Changes diff --git a/plugins/permission-backend-module-policy-allow-all/knip-report.md b/plugins/permission-backend-module-policy-allow-all/knip-report.md index 3790422ce9..c8beb397ed 100644 --- a/plugins/permission-backend-module-policy-allow-all/knip-report.md +++ b/plugins/permission-backend-module-policy-allow-all/knip-report.md @@ -2,13 +2,13 @@ ## Unused dependencies (1) -| Name | Location | Severity | -| :-------------------------- | :----------- | :------- | -| @backstage/plugin-auth-node | plugins/permission-backend-module-policy-allow-all/package.json | error | +| Name | Location | Severity | +| :-------------------------- | :---------------- | :------- | +| @backstage/plugin-auth-node | package.json:37:6 | error | ## Unused devDependencies (1) -| Name | Location | Severity | -| :---------------------------- | :----------- | :------- | -| @backstage/backend-test-utils | plugins/permission-backend-module-policy-allow-all/package.json | error | +| Name | Location | Severity | +| :---------------------------- | :---------------- | :------- | +| @backstage/backend-test-utils | package.json:42:6 | error | diff --git a/plugins/permission-backend-module-policy-allow-all/package.json b/plugins/permission-backend-module-policy-allow-all/package.json index c270cf5ceb..da8a7cbce9 100644 --- a/plugins/permission-backend-module-policy-allow-all/package.json +++ b/plugins/permission-backend-module-policy-allow-all/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-backend-module-allow-all-policy", - "version": "0.2.14-next.1", + "version": "0.2.18", "description": "Allow all policy backend module for the permission plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/permission-backend/CHANGELOG.md b/plugins/permission-backend/CHANGELOG.md index a7e6bceedd..b863092623 100644 --- a/plugins/permission-backend/CHANGELOG.md +++ b/plugins/permission-backend/CHANGELOG.md @@ -1,5 +1,155 @@ # @backstage/plugin-permission-backend +## 0.7.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/errors@1.3.0 + - @backstage/plugin-auth-node@0.7.0 + - @backstage/plugin-permission-node@0.10.12 + - @backstage/config@1.3.7 + - @backstage/plugin-permission-common@0.9.8 + +## 0.7.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/plugin-auth-node@0.7.0-next.2 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/config@1.3.7-next.0 + - @backstage/plugin-permission-common@0.9.8-next.0 + - @backstage/plugin-permission-node@0.10.12-next.2 + +## 0.7.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/plugin-permission-node@0.10.12-next.1 + +## 0.7.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-auth-node@0.6.15-next.0 + - @backstage/plugin-permission-node@0.10.12-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.7 + +## 0.7.10 + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/plugin-permission-node@0.10.11 + - @backstage/plugin-auth-node@0.6.14 + +## 0.7.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + - @backstage/plugin-permission-node@0.10.11-next.1 + +## 0.7.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-auth-node@0.6.14-next.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.11-next.0 + +## 0.7.9 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.10 + +## 0.7.8-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/plugin-permission-node@0.10.9-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.7.7 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + +## 0.7.7-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.3 + +## 0.7.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.3 + +## 0.7.6 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config@1.3.6 + - @backstage/plugin-permission-node@0.10.6 + ## 0.7.6-next.1 ### Patch Changes diff --git a/plugins/permission-backend/knip-report.md b/plugins/permission-backend/knip-report.md index 61ea01af65..2661c35327 100644 --- a/plugins/permission-backend/knip-report.md +++ b/plugins/permission-backend/knip-report.md @@ -1,8 +1,2 @@ # Knip report -## Unused dependencies (1) - -| Name | Location | Severity | -| :- | :----------- | :------- | -| yn | plugins/permission-backend/package.json | error | - diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index fe68fbdc2b..a89036f1e8 100644 --- a/plugins/permission-backend/package.json +++ b/plugins/permission-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-backend", - "version": "0.7.6-next.1", + "version": "0.7.11", "backstage": { "role": "backend-plugin", "pluginId": "permission", @@ -58,11 +58,10 @@ "@backstage/plugin-permission-common": "workspace:^", "@backstage/plugin-permission-node": "workspace:^", "dataloader": "^2.0.0", - "express": "^4.17.1", + "express": "^4.22.0", "express-promise-router": "^4.1.0", "lodash": "^4.17.21", - "yn": "^4.0.0", - "zod": "^3.22.4" + "zod": "^3.25.76 || ^4.0.0" }, "devDependencies": { "@backstage/backend-defaults": "workspace:^", diff --git a/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts b/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts index ece99f08c7..7f0caa9670 100644 --- a/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts +++ b/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { AddressInfo } from 'net'; -import { Server } from 'http'; +import { AddressInfo } from 'node:net'; +import { Server } from 'node:http'; import express, { Router, RequestHandler } from 'express'; import { RestContext, rest } from 'msw'; import { setupServer, SetupServer } from 'msw/node'; @@ -30,7 +30,7 @@ import { createPermissionRule, } from '@backstage/plugin-permission-node'; import { PermissionIntegrationClient } from './PermissionIntegrationClient'; -import { z } from 'zod'; +import { z } from 'zod/v3'; import { DiscoveryService } from '@backstage/backend-plugin-api'; describe('PermissionIntegrationClient', () => { @@ -148,9 +148,7 @@ describe('PermissionIntegrationClient', () => { ], ); - expect(response).toEqual( - expect.objectContaining([{ id: '123', result: AuthorizeResult.ALLOW }]), - ); + expect(response).toEqual([{ id: '123', result: AuthorizeResult.ALLOW }]); }); it('should not include authorization headers if no token is supplied', async () => { diff --git a/plugins/permission-backend/src/service/PermissionIntegrationClient.ts b/plugins/permission-backend/src/service/PermissionIntegrationClient.ts index 610eb8b34b..83dc7780f3 100644 --- a/plugins/permission-backend/src/service/PermissionIntegrationClient.ts +++ b/plugins/permission-backend/src/service/PermissionIntegrationClient.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { z } from 'zod'; +import { z } from 'zod/v3'; import { AuthorizeResult, ConditionalPolicyDecision, diff --git a/plugins/permission-backend/src/service/router.ts b/plugins/permission-backend/src/service/router.ts index b6f570562b..92a2e40259 100644 --- a/plugins/permission-backend/src/service/router.ts +++ b/plugins/permission-backend/src/service/router.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { z } from 'zod'; +import { z } from 'zod/v3'; import express, { Request, Response } from 'express'; import Router from 'express-promise-router'; import { InputError } from '@backstage/errors'; diff --git a/plugins/permission-common/CHANGELOG.md b/plugins/permission-common/CHANGELOG.md index 2c85c4a50f..061e34295f 100644 --- a/plugins/permission-common/CHANGELOG.md +++ b/plugins/permission-common/CHANGELOG.md @@ -1,5 +1,57 @@ # @backstage/plugin-permission-common +## 0.9.8 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0 + - @backstage/config@1.3.7 + +## 0.9.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/config@1.3.7-next.0 + +## 0.9.7 + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. + +## 0.9.6 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features + +## 0.9.5-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.9.4 + +### Patch Changes + +- 872eb91: Upgrade `zod-to-json-schema` to latest version + +## 0.9.3 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/config@1.3.6 + ## 0.9.3-next.1 ### Patch Changes diff --git a/plugins/permission-common/package.json b/plugins/permission-common/package.json index 2aca379d34..147d4910e9 100644 --- a/plugins/permission-common/package.json +++ b/plugins/permission-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-common", - "version": "0.9.3-next.1", + "version": "0.9.8", "description": "Isomorphic types and client for Backstage permissions and authorization", "backstage": { "role": "common-library", @@ -53,8 +53,8 @@ "@backstage/types": "workspace:^", "cross-fetch": "^4.0.0", "uuid": "^11.0.0", - "zod": "^3.22.4", - "zod-to-json-schema": "^3.20.4" + "zod": "^3.25.76 || ^4.0.0", + "zod-to-json-schema": "^3.25.1" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/plugins/permission-common/src/PermissionClient.ts b/plugins/permission-common/src/PermissionClient.ts index f801ba2a4b..d178dcf2af 100644 --- a/plugins/permission-common/src/PermissionClient.ts +++ b/plugins/permission-common/src/PermissionClient.ts @@ -18,7 +18,7 @@ import { Config } from '@backstage/config'; import { ResponseError } from '@backstage/errors'; import fetch from 'cross-fetch'; import * as uuid from 'uuid'; -import { z } from 'zod'; +import { z } from 'zod/v3'; import { AuthorizeResult, PermissionMessageBatch, diff --git a/plugins/permission-node/CHANGELOG.md b/plugins/permission-node/CHANGELOG.md index ad134fa3eb..a11c12b626 100644 --- a/plugins/permission-node/CHANGELOG.md +++ b/plugins/permission-node/CHANGELOG.md @@ -1,5 +1,149 @@ # @backstage/plugin-permission-node +## 0.10.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/errors@1.3.0 + - @backstage/plugin-auth-node@0.7.0 + - @backstage/config@1.3.7 + - @backstage/plugin-permission-common@0.9.8 + +## 0.10.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/plugin-auth-node@0.7.0-next.2 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/config@1.3.7-next.0 + - @backstage/plugin-permission-common@0.9.8-next.0 + +## 0.10.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + +## 0.10.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-auth-node@0.6.15-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.7 + +## 0.10.11 + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/plugin-auth-node@0.6.14 + +## 0.10.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + +## 0.10.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-auth-node@0.6.14-next.0 + - @backstage/plugin-permission-common@0.9.6 + +## 0.10.10 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + - @backstage/plugin-permission-common@0.9.6 + +## 0.10.9-next.0 + +### Patch Changes + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.10.8 + +### Patch Changes + +- 872eb91: Upgrade `zod-to-json-schema` to latest version +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-auth-node@0.6.11 + - @backstage/plugin-permission-common@0.9.4 + +## 0.10.7 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.10.7-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.3 + +## 0.10.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.3 + +## 0.10.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config@1.3.6 + ## 0.10.6-next.1 ### Patch Changes diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json index 5957c18bae..66b0cbb88a 100644 --- a/plugins/permission-node/package.json +++ b/plugins/permission-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-node", - "version": "0.10.6-next.1", + "version": "0.10.12", "description": "Common permission and authorization utilities for backend plugins", "backstage": { "role": "node-library", @@ -62,10 +62,10 @@ "@backstage/plugin-auth-node": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", "@types/express": "^4.17.6", - "express": "^4.17.1", + "express": "^4.22.0", "express-promise-router": "^4.1.0", - "zod": "^3.22.4", - "zod-to-json-schema": "^3.20.4" + "zod": "^3.25.76 || ^4.0.0", + "zod-to-json-schema": "^3.25.1" }, "devDependencies": { "@backstage/backend-defaults": "workspace:^", diff --git a/plugins/permission-node/report.api.md b/plugins/permission-node/report.api.md index 6345861c93..09452b969b 100644 --- a/plugins/permission-node/report.api.md +++ b/plugins/permission-node/report.api.md @@ -30,7 +30,7 @@ import { PermissionsServiceRequestOptions } from '@backstage/backend-plugin-api' import { PolicyDecision } from '@backstage/plugin-permission-common'; import { QueryPermissionRequest } from '@backstage/plugin-permission-common'; import { ResourcePermission } from '@backstage/plugin-permission-common'; -import { z } from 'zod'; +import { z } from 'zod/v3'; // @public export type ApplyConditionsRequest = { diff --git a/plugins/permission-node/src/integration/createConditionExports.test.ts b/plugins/permission-node/src/integration/createConditionExports.test.ts index 926e607baa..341b4e73e1 100644 --- a/plugins/permission-node/src/integration/createConditionExports.test.ts +++ b/plugins/permission-node/src/integration/createConditionExports.test.ts @@ -18,7 +18,7 @@ import { AuthorizeResult, createPermission, } from '@backstage/plugin-permission-common'; -import { z } from 'zod'; +import { z } from 'zod/v3'; import { createConditionExports } from './createConditionExports'; import { createPermissionRule } from './createPermissionRule'; import { createPermissionResourceRef } from './createPermissionResourceRef'; diff --git a/plugins/permission-node/src/integration/createConditionFactory.test.ts b/plugins/permission-node/src/integration/createConditionFactory.test.ts index 520fcc01f9..02d01e7d14 100644 --- a/plugins/permission-node/src/integration/createConditionFactory.test.ts +++ b/plugins/permission-node/src/integration/createConditionFactory.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { z } from 'zod'; +import { z } from 'zod/v3'; import { createConditionFactory } from './createConditionFactory'; import { createPermissionRule } from './createPermissionRule'; diff --git a/plugins/permission-node/src/integration/createConditionTransformer.test.ts b/plugins/permission-node/src/integration/createConditionTransformer.test.ts index a5507f0b44..ce72567902 100644 --- a/plugins/permission-node/src/integration/createConditionTransformer.test.ts +++ b/plugins/permission-node/src/integration/createConditionTransformer.test.ts @@ -18,7 +18,7 @@ import { PermissionCondition, PermissionCriteria, } from '@backstage/plugin-permission-common'; -import { z } from 'zod'; +import { z } from 'zod/v3'; import { createConditionTransformer } from './createConditionTransformer'; import { createPermissionRule } from './createPermissionRule'; diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts index 0d28ef710f..52352dc454 100644 --- a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts +++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts @@ -21,7 +21,7 @@ import { } from '@backstage/plugin-permission-common'; import express from 'express'; import request, { Response } from 'supertest'; -import { z } from 'zod'; +import { z } from 'zod/v3'; import { createPermissionIntegrationRouter, CreatePermissionIntegrationRouterResourceOptions, diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts index 522e60bac5..6d96e45659 100644 --- a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts +++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts @@ -16,7 +16,7 @@ import express, { Response } from 'express'; import Router from 'express-promise-router'; -import { z } from 'zod'; +import { z } from 'zod/v3'; import zodToJsonSchema from 'zod-to-json-schema'; import { InputError } from '@backstage/errors'; import { diff --git a/plugins/permission-node/src/integration/createPermissionRule.ts b/plugins/permission-node/src/integration/createPermissionRule.ts index 36f8205cf8..f1970a0254 100644 --- a/plugins/permission-node/src/integration/createPermissionRule.ts +++ b/plugins/permission-node/src/integration/createPermissionRule.ts @@ -19,7 +19,7 @@ import { PermissionRuleParams, } from '@backstage/plugin-permission-common'; import { NoInfer, PermissionRule } from '../types'; -import { z } from 'zod'; +import { z } from 'zod/v3'; import { PermissionResourceRef } from './createPermissionResourceRef'; /** diff --git a/plugins/permission-node/src/types.ts b/plugins/permission-node/src/types.ts index 578117a4f1..a8a337ca88 100644 --- a/plugins/permission-node/src/types.ts +++ b/plugins/permission-node/src/types.ts @@ -18,7 +18,7 @@ import type { PermissionCriteria, PermissionRuleParams, } from '@backstage/plugin-permission-common'; -import { z } from 'zod'; +import { z } from 'zod/v3'; /** * Prevent use of type parameter from contributing to type inference. diff --git a/plugins/permission-react/CHANGELOG.md b/plugins/permission-react/CHANGELOG.md index af53c0adc2..bf001ea469 100644 --- a/plugins/permission-react/CHANGELOG.md +++ b/plugins/permission-react/CHANGELOG.md @@ -1,5 +1,106 @@ # @backstage/plugin-permission-react +## 0.5.0 + +### Minor Changes + +- 53954e1: **BREAKING**: Removed the deprecated `PermissionedRoute` component. Use `RequirePermission` instead. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.7 + - @backstage/core-plugin-api@1.12.5 + - @backstage/plugin-permission-common@0.9.8 + +## 0.4.42-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.7-next.0 + - @backstage/core-plugin-api@1.12.5-next.2 + - @backstage/plugin-permission-common@0.9.8-next.0 + +## 0.4.42-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.5-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-permission-common@0.9.7 + +## 0.4.41 + +### Patch Changes + +- 5fec07d: Permission checks made in the same tick are now batched into a single call to the permission backend. +- Updated dependencies + - @backstage/core-plugin-api@1.12.4 + - @backstage/plugin-permission-common@0.9.7 + +## 0.4.41-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/plugin-permission-common@0.9.6 + +## 0.4.40 + +### Patch Changes + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-permission-common@0.9.6 + +## 0.4.40-next.1 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/core-plugin-api@1.12.3-next.1 + +## 0.4.40-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/config@1.3.6 + +## 0.4.39 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1 + +## 0.4.39-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-permission-common@0.9.3 + +## 0.4.38 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/config@1.3.6 + ## 0.4.38-next.0 ### Patch Changes diff --git a/plugins/permission-react/package.json b/plugins/permission-react/package.json index f5c473cd6b..86352ac2fe 100644 --- a/plugins/permission-react/package.json +++ b/plugins/permission-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-react", - "version": "0.4.38-next.0", + "version": "0.5.0", "backstage": { "role": "web-library", "pluginId": "permission", @@ -45,6 +45,7 @@ "@backstage/config": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", + "dataloader": "^2.0.0", "swr": "^2.0.0" }, "devDependencies": { @@ -54,14 +55,12 @@ "@testing-library/react": "^16.0.0", "@types/react": "^18.0.0", "react": "^18.0.2", - "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-dom": "^18.0.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-dom": "^17.0.0 || ^18.0.0" }, "peerDependenciesMeta": { "@types/react": { diff --git a/plugins/permission-react/report.api.md b/plugins/permission-react/report.api.md index e0c5f4772c..1f56e143a3 100644 --- a/plugins/permission-react/report.api.md +++ b/plugins/permission-react/report.api.md @@ -11,9 +11,7 @@ import { DiscoveryApi } from '@backstage/core-plugin-api'; import { EvaluatePermissionRequest } from '@backstage/plugin-permission-common'; import { EvaluatePermissionResponse } from '@backstage/plugin-permission-common'; import { IdentityApi } from '@backstage/core-plugin-api'; -import { JSX as JSX_2 } from 'react/jsx-runtime'; import { Permission } from '@backstage/plugin-permission-common'; -import { ReactElement } from 'react'; import { ReactNode } from 'react'; import { ResourcePermission } from '@backstage/plugin-permission-common'; @@ -48,26 +46,6 @@ export type PermissionApi = { // @public export const permissionApiRef: ApiRef; -// @public @deprecated -export const PermissionedRoute: ( - props: { - caseSensitive?: boolean; - children?: ReactNode; - element?: ReactElement | null; - path?: string; - errorComponent?: ReactElement | null; - } & ( - | { - permission: Exclude; - resourceRef?: never; - } - | { - permission: ResourcePermission; - resourceRef: string | undefined; - } - ), -) => JSX_2.Element; - // @public export function RequirePermission( props: RequirePermissionProps, diff --git a/plugins/permission-react/src/apis/IdentityPermissionApi.test.ts b/plugins/permission-react/src/apis/IdentityPermissionApi.test.ts new file mode 100644 index 0000000000..42b40e38da --- /dev/null +++ b/plugins/permission-react/src/apis/IdentityPermissionApi.test.ts @@ -0,0 +1,97 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ConfigReader } from '@backstage/config'; +import { mockApis } from '@backstage/test-utils'; +import { + createPermission, + PermissionClient, +} from '@backstage/plugin-permission-common'; +import { AuthorizeResult } from '@backstage/plugin-permission-common'; +import { IdentityPermissionApi } from './IdentityPermissionApi'; + +describe('IdentityPermissionApi', () => { + const permission = createPermission({ + name: 'test.permission', + attributes: {}, + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('should batch requests that arrive on the same tick', async () => { + const authorizeSpy = jest + .spyOn(PermissionClient.prototype, 'authorize') + .mockResolvedValue([ + { result: AuthorizeResult.ALLOW }, + { result: AuthorizeResult.DENY }, + ]); + const api = IdentityPermissionApi.create({ + config: new ConfigReader({}), + discovery: mockApis.discovery(), + identity: mockApis.identity(), + }); + + const firstRequest = { permission }; + const secondRequest = { permission }; + const [firstResponse, secondResponse] = await Promise.all([ + api.authorize(firstRequest), + api.authorize(secondRequest), + ]); + + expect(firstResponse.result).toBe(AuthorizeResult.ALLOW); + expect(secondResponse.result).toBe(AuthorizeResult.DENY); + expect(authorizeSpy).toHaveBeenCalledTimes(1); + expect(authorizeSpy).toHaveBeenCalledWith( + [firstRequest, secondRequest], + expect.anything(), + ); + }); + + it('should not cache requests across ticks', async () => { + const authorizeSpy = jest + .spyOn(PermissionClient.prototype, 'authorize') + .mockResolvedValue([{ result: AuthorizeResult.ALLOW }]); + const identityApi = mockApis.identity(); + const credentialsSpy = jest + .spyOn(identityApi, 'getCredentials') + .mockResolvedValueOnce({ token: 'first-token' }) + .mockResolvedValueOnce({ token: 'second-token' }); + const api = IdentityPermissionApi.create({ + config: new ConfigReader({}), + discovery: mockApis.discovery(), + identity: identityApi, + }); + + const request = { permission }; + await api.authorize(request); + await api.authorize(request); + + expect(authorizeSpy).toHaveBeenCalledTimes(2); + expect(credentialsSpy).toHaveBeenCalledTimes(2); + expect(authorizeSpy).toHaveBeenNthCalledWith( + 1, + [request], + expect.objectContaining({ token: 'first-token' }), + ); + expect(authorizeSpy).toHaveBeenNthCalledWith( + 2, + [request], + expect.objectContaining({ token: 'second-token' }), + ); + }); +}); diff --git a/plugins/permission-react/src/apis/IdentityPermissionApi.ts b/plugins/permission-react/src/apis/IdentityPermissionApi.ts index dc1fc3b5a3..f7c3c95107 100644 --- a/plugins/permission-react/src/apis/IdentityPermissionApi.ts +++ b/plugins/permission-react/src/apis/IdentityPermissionApi.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import DataLoader from 'dataloader'; import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api'; import { PermissionApi } from './PermissionApi'; import { @@ -24,20 +25,30 @@ import { import { Config } from '@backstage/config'; /** - * The default implementation of the PermissionApi, which simply calls the authorize method of the given - * {@link @backstage/plugin-permission-common#PermissionClient}. + * The default implementation of the PermissionApi, which batches calls to + * {@link @backstage/plugin-permission-common#PermissionClient} that are made + * within the same microtask into a single HTTP request. * @public */ export class IdentityPermissionApi implements PermissionApi { - private readonly permissionClient: PermissionClient; - private readonly identityApi: IdentityApi; + private readonly loader: DataLoader< + AuthorizePermissionRequest, + AuthorizePermissionResponse + >; private constructor( permissionClient: PermissionClient, identityApi: IdentityApi, ) { - this.permissionClient = permissionClient; - this.identityApi = identityApi; + this.loader = new DataLoader( + async (requests: readonly AuthorizePermissionRequest[]) => { + const credentials = await identityApi.getCredentials(); + return permissionClient.authorize([...requests], credentials); + }, + { + cache: false, + }, + ); } static create(options: { @@ -50,13 +61,12 @@ export class IdentityPermissionApi implements PermissionApi { return new IdentityPermissionApi(permissionClient, identity); } + async authorize( + request: AuthorizePermissionRequest, + ): Promise; async authorize( request: AuthorizePermissionRequest, ): Promise { - const response = await this.permissionClient.authorize( - [request], - await this.identityApi.getCredentials(), - ); - return response[0]; + return await this.loader.load(request); } } diff --git a/plugins/permission-react/src/components/PermissionedRoute.tsx b/plugins/permission-react/src/components/PermissionedRoute.tsx deleted file mode 100644 index efc1d4867c..0000000000 --- a/plugins/permission-react/src/components/PermissionedRoute.tsx +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ReactElement, ReactNode } from 'react'; -import { Route } from 'react-router-dom'; -import { useApp } from '@backstage/core-plugin-api'; -import { usePermission } from '../hooks'; -import { - isResourcePermission, - Permission, - ResourcePermission, -} from '@backstage/plugin-permission-common'; - -/** - * Returns a React Router Route which only renders the element when authorized. If unauthorized, the Route will render a - * NotFoundErrorPage (see {@link @backstage/core-app-api#AppComponents}). - * - * @public - * @deprecated This component no longer works with the most recent version of `@backstage/core-app-api` and react-router v6, use {@link RequirePermission} instead. - */ -export const PermissionedRoute = ( - props: { - caseSensitive?: boolean; - children?: ReactNode; - element?: ReactElement | null; - path?: string; - errorComponent?: ReactElement | null; - } & ( - | { - permission: Exclude; - resourceRef?: never; - } - | { - permission: ResourcePermission; - resourceRef: string | undefined; - } - ), -) => { - const { permission, resourceRef, errorComponent, ...otherProps } = props; - - const permissionResult = usePermission( - isResourcePermission(permission) - ? { permission, resourceRef } - : { permission }, - ); - const app = useApp(); - const { NotFoundErrorPage } = app.getComponents(); - - let shownElement: ReactElement | null | undefined = - errorComponent === undefined ? : errorComponent; - - if (permissionResult.loading) { - shownElement = null; - } else if (permissionResult.allowed) { - shownElement = props.element; - } - - return ; -}; diff --git a/plugins/permission-react/src/components/index.ts b/plugins/permission-react/src/components/index.ts index e443ad0b5e..6d33109952 100644 --- a/plugins/permission-react/src/components/index.ts +++ b/plugins/permission-react/src/components/index.ts @@ -14,6 +14,5 @@ * limitations under the License. */ -export { PermissionedRoute } from './PermissionedRoute'; export { RequirePermission } from './RequirePermission'; export type { RequirePermissionProps } from './RequirePermission'; diff --git a/plugins/proxy-backend/CHANGELOG.md b/plugins/proxy-backend/CHANGELOG.md index 36fb824a9d..c2dee5359c 100644 --- a/plugins/proxy-backend/CHANGELOG.md +++ b/plugins/proxy-backend/CHANGELOG.md @@ -1,5 +1,110 @@ # @backstage/plugin-proxy-backend +## 0.6.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/plugin-proxy-node@0.1.14 + +## 0.6.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-proxy-node@0.1.14-next.1 + +## 0.6.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-proxy-node@0.1.14-next.0 + - @backstage/types@1.2.2 + +## 0.6.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-proxy-node@0.1.13 + +## 0.6.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-proxy-node@0.1.13-next.1 + +## 0.6.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/types@1.2.2 + - @backstage/plugin-proxy-node@0.1.13-next.0 + +## 0.6.10 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-proxy-node@0.1.12 + +## 0.6.10-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-proxy-node@0.1.12-next.0 + - @backstage/types@1.2.2 + +## 0.6.9 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-proxy-node@0.1.11 + +## 0.6.9-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/types@1.2.2 + - @backstage/plugin-proxy-node@0.1.11-next.1 + +## 0.6.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-proxy-node@0.1.11-next.0 + - @backstage/types@1.2.2 + +## 0.6.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-proxy-node@0.1.10 + ## 0.6.8-next.1 ### Patch Changes diff --git a/plugins/proxy-backend/knip-report.md b/plugins/proxy-backend/knip-report.md index 3f66c63579..98988d3831 100644 --- a/plugins/proxy-backend/knip-report.md +++ b/plugins/proxy-backend/knip-report.md @@ -1,8 +1,9 @@ # Knip report -## Unused devDependencies (1) +## Unused devDependencies (2) -| Name | Location | Severity | -| :------------------------- | :----------- | :------- | -| @backstage/backend-app-api | plugins/proxy-backend/package.json | error | +| Name | Location | Severity | +| :--------------------------- | :---------------- | :------- | +| @types/http-proxy-middleware | package.json:66:6 | error | +| @backstage/backend-app-api | package.json:59:6 | error | diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index b125dae54d..b321e697eb 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-proxy-backend", - "version": "0.6.8-next.1", + "version": "0.6.12", "description": "A Backstage backend plugin that helps you set up proxy endpoints in the backend", "backstage": { "role": "backend-plugin", @@ -64,7 +64,7 @@ "@backstage/errors": "workspace:^", "@types/express": "^4.17.6", "@types/http-proxy-middleware": "^1.0.0", - "express": "^4.17.1", + "express": "^4.22.0", "msw": "^2.0.0" }, "configSchema": "config.d.ts" diff --git a/plugins/proxy-backend/src/service/router.test.ts b/plugins/proxy-backend/src/service/router.test.ts index 7d9a3dadbc..f850a29c97 100644 --- a/plugins/proxy-backend/src/service/router.test.ts +++ b/plugins/proxy-backend/src/service/router.test.ts @@ -16,7 +16,7 @@ import { mockServices } from '@backstage/backend-test-utils'; import { Request, Response } from 'express'; -import * as http from 'http'; +import * as http from 'node:http'; import { createProxyMiddleware, fixRequestBody, diff --git a/plugins/proxy-backend/src/service/router.ts b/plugins/proxy-backend/src/service/router.ts index 3fb33eb572..86dc69453e 100644 --- a/plugins/proxy-backend/src/service/router.ts +++ b/plugins/proxy-backend/src/service/router.ts @@ -21,7 +21,7 @@ import { fixRequestBody, RequestHandler, } from 'http-proxy-middleware'; -import http from 'http'; +import http from 'node:http'; import { JsonObject } from '@backstage/types'; import { DiscoveryService, diff --git a/plugins/proxy-node/CHANGELOG.md b/plugins/proxy-node/CHANGELOG.md index 9b79a20cae..7ef0f45756 100644 --- a/plugins/proxy-node/CHANGELOG.md +++ b/plugins/proxy-node/CHANGELOG.md @@ -1,5 +1,89 @@ # @backstage/plugin-proxy-node +## 0.1.14 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + +## 0.1.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + +## 0.1.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + +## 0.1.13 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + +## 0.1.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + +## 0.1.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + +## 0.1.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + +## 0.1.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + +## 0.1.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + +## 0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + +## 0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + ## 0.1.10-next.1 ### Patch Changes diff --git a/plugins/proxy-node/knip-report.md b/plugins/proxy-node/knip-report.md index 7d1ae72c58..b1b230c887 100644 --- a/plugins/proxy-node/knip-report.md +++ b/plugins/proxy-node/knip-report.md @@ -1,9 +1,10 @@ # Knip report -## Unused devDependencies (2) +## Unused devDependencies (3) -| Name | Location | Severity | -| :---------------------------- | :----------- | :------- | -| @backstage/backend-test-utils | plugins/proxy-node/package.json | error | -| @backstage/config | plugins/proxy-node/package.json | error | +| Name | Location | Severity | +| :---------------------------- | :---------------- | :------- | +| @backstage/backend-test-utils | package.json:55:6 | error | +| @types/http-proxy-middleware | package.json:58:6 | error | +| @backstage/config | package.json:57:6 | error | diff --git a/plugins/proxy-node/package.json b/plugins/proxy-node/package.json index 8510c559c2..3086df43ce 100644 --- a/plugins/proxy-node/package.json +++ b/plugins/proxy-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-proxy-node", - "version": "0.1.10-next.1", + "version": "0.1.14", "description": "The plugin-proxy-node module for @backstage/plugin-proxy-backend", "backstage": { "role": "node-library", diff --git a/plugins/scaffolder-backend-module-azure/CHANGELOG.md b/plugins/scaffolder-backend-module-azure/CHANGELOG.md index a6ef29c587..da26ae7cae 100644 --- a/plugins/scaffolder-backend-module-azure/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-azure/CHANGELOG.md @@ -1,5 +1,174 @@ # @backstage/plugin-scaffolder-backend-module-azure +## 0.2.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/errors@1.3.0 + - @backstage/plugin-scaffolder-node@0.13.2 + - @backstage/integration@2.0.1 + - @backstage/config@1.3.7 + +## 0.2.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/plugin-scaffolder-node@0.13.2-next.2 + - @backstage/integration@2.0.1-next.0 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/config@1.3.7-next.0 + +## 0.2.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + +## 0.2.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-scaffolder-node@0.13.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration@2.0.0 + +## 0.2.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-scaffolder-node@0.13.0 + - @backstage/integration@2.0.0 + +## 0.2.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + +## 0.2.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.2.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + +## 0.2.18 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-scaffolder-node@0.12.5 + +## 0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## 0.2.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.2.17 + +### Patch Changes + +- 88abcc6: Improved README with clearer setup and usage guidance. +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + - @backstage/integration@1.19.2 + +## 0.2.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + +## 0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## 0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.2.15 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + ## 0.2.15-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-azure/README.md b/plugins/scaffolder-backend-module-azure/README.md index 505ef36db1..dcfb1d63ab 100644 --- a/plugins/scaffolder-backend-module-azure/README.md +++ b/plugins/scaffolder-backend-module-azure/README.md @@ -1,5 +1,133 @@ # @backstage/plugin-scaffolder-backend-module-azure -The azure module for [@backstage/plugin-scaffolder-backend](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend). +The Azure DevOps module for [@backstage/plugin-scaffolder-backend](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend). -_This plugin was created through the Backstage CLI_ +This module provides scaffolder actions for Azure DevOps integration. + +## Installation + +```bash +# From your Backstage root directory +yarn --cwd packages/backend add @backstage/plugin-scaffolder-backend-module-azure +``` + +Then add it to your backend: + +```ts title="packages/backend/src/index.ts" +import { createBackend } from '@backstage/backend-defaults'; + +const backend = createBackend(); + +// ... other plugins + +backend.add(import('@backstage/plugin-scaffolder-backend')); +backend.add(import('@backstage/plugin-scaffolder-backend-module-azure')); + +backend.start(); +``` + +## Actions + +### `publish:azure` + +Initializes a git repository with the content in the workspace and publishes it to Azure DevOps. + +#### Input Parameters + +| Parameter | Type | Required | Description | +| ------------------ | --------- | -------- | ------------------------------------------------------------------------------------------------------- | +| `repoUrl` | `string` | Yes | Repository URL in the format: `dev.azure.com?organization=&project=&repo=` | +| `description` | `string` | No | Repository description | +| `defaultBranch` | `string` | No | Default branch for the repository. Default: `master` | +| `sourcePath` | `string` | No | Path within the workspace to use as repository root. If omitted, the entire workspace will be published | +| `token` | `string` | No | Personal Access Token for Azure DevOps authentication | +| `gitCommitMessage` | `string` | No | Initial commit message. Default: `initial commit` | +| `gitAuthorName` | `string` | No | Author name for the commit. Default: `Scaffolder` | +| `gitAuthorEmail` | `string` | No | Author email for the commit | +| `signCommit` | `boolean` | No | Sign the commit with the configured PGP private key | + +#### Output Parameters + +| Parameter | Type | Description | +| ----------------- | -------- | --------------------------------------- | +| `remoteUrl` | `string` | URL to the repository | +| `repoContentsUrl` | `string` | URL to the root of the repository (web) | +| `repositoryId` | `string` | ID of the created repository | +| `commitHash` | `string` | Git commit hash of the initial commit | + +#### Examples + +**Basic usage:** + +```yaml +steps: + - id: publish + action: publish:azure + name: Publish to Azure DevOps + input: + repoUrl: 'dev.azure.com?organization=myorg&project=myproject&repo=myrepo' +``` + +**With custom branch and commit message:** + +```yaml +steps: + - id: publish + action: publish:azure + name: Publish to Azure DevOps + input: + repoUrl: 'dev.azure.com?organization=myorg&project=myproject&repo=myrepo' + defaultBranch: main + gitCommitMessage: 'Initial project setup' +``` + +**With author information:** + +```yaml +steps: + - id: publish + action: publish:azure + name: Publish to Azure DevOps + input: + repoUrl: 'dev.azure.com?organization=myorg&project=myproject&repo=myrepo' + gitAuthorName: 'John Doe' + gitAuthorEmail: 'john.doe@example.com' +``` + +**Using a specific source path:** + +```yaml +steps: + - id: publish + action: publish:azure + name: Publish to Azure DevOps + input: + repoUrl: 'dev.azure.com?organization=myorg&project=myproject&repo=myrepo' + sourcePath: 'packages/my-app' +``` + +**With authentication token:** + +```yaml +steps: + - id: publish + action: publish:azure + name: Publish to Azure DevOps + input: + repoUrl: 'dev.azure.com?organization=myorg&project=myproject&repo=myrepo' + token: ${{ secrets.AZURE_TOKEN }} +``` + +## Configuration + +To use this module, you need to configure Azure DevOps integration in your `app-config.yaml`: + +```yaml +integrations: + azure: + - host: dev.azure.com + credentials: + - personalAccessToken: ${AZURE_TOKEN} +``` + +For more information on Azure DevOps integration, see the [Azure DevOps integration documentation](https://backstage.io/docs/integrations/azure/locations). diff --git a/plugins/scaffolder-backend-module-azure/package.json b/plugins/scaffolder-backend-module-azure/package.json index 91ef328b80..1281de8aad 100644 --- a/plugins/scaffolder-backend-module-azure/package.json +++ b/plugins/scaffolder-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-azure", - "version": "0.2.15-next.1", + "version": "0.2.20", "description": "The azure module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md index 310c87b32a..6e15d9105f 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,230 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket-cloud +## 0.3.5 + +### Patch Changes + +- ed6b53c: Removed bare catch-and-rethrow blocks that served no purpose. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/errors@1.3.0 + - @backstage/plugin-scaffolder-node@0.13.2 + - @backstage/integration@2.0.1 + - @backstage/config@1.3.7 + - @backstage/plugin-bitbucket-cloud-common@0.3.9 + +## 0.3.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/plugin-scaffolder-node@0.13.2-next.2 + - @backstage/integration@2.0.1-next.0 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/config@1.3.7-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.9-next.0 + +## 0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + +## 0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-scaffolder-node@0.13.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration@2.0.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.8 + +## 0.3.4 + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-scaffolder-node@0.13.0 + - @backstage/integration@2.0.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.8 + +## 0.3.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-bitbucket-cloud-common@0.3.8-next.1 + +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-bitbucket-cloud-common@0.3.8-next.0 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + +## 0.3.3 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 14741e2: Fully enable API token functionality for Bitbucket-Cloud. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.7 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-scaffolder-node@0.12.5 + +## 0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-bitbucket-cloud-common@0.3.7-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## 0.3.2-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 14741e2: Fully enable API token functionality for Bitbucket-Cloud. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/plugin-bitbucket-cloud-common@0.3.7-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.3.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + - @backstage/integration@1.19.2 + - @backstage/plugin-bitbucket-cloud-common@0.3.6 + +## 0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.6-next.0 + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + +## 0.3.0 + +### Minor Changes + +- 37fba1d: Added support for Bitbucket Cloud OAuth. This introduces an alternative authentication method using a workspace OAuth consumer, alongside App Passwords (deprecated) and API tokens. OAuth does not require a bot or service account and avoids token expiry issues. + + **BREAKING CHANGES** + + - **@backstage/integration** (`src/bitbucketCloud/core.ts`) + + - `getBitbucketCloudRequestOptions` now returns a `Promise` and **must** be awaited. + + - **@backstage/plugin-scaffolder-backend-module-bitbucket-cloud** (`src/actions/helpers.ts`) + - `getBitbucketClient` now returns a `Promise` and **must** be awaited. + - `getAuthorizationHeader` now returns a `Promise` and **must** be awaited. + + **OAuth usage example** + + ```yaml + integrations: + bitbucketCloud: + - clientId: client-id + clientSecret: client-secret + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.5 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-bitbucket-cloud-common@0.3.5-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## 0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.5-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.2.15 + +### Patch Changes + +- fa255f5: Support for Bitbucket Cloud's API token was added as `appPassword` is deprecated (no new creation from September 9, 2025) and will be removed on June 9, 2026. + + API token usage example: + + ```yaml + integrations: + bitbucketCloud: + - username: user@domain.com + token: my-token + ``` + +- Updated dependencies + - @backstage/plugin-bitbucket-cloud-common@0.3.4 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + ## 0.2.15-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json index db3c65f54c..9089395393 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud", - "version": "0.2.15-next.1", + "version": "0.3.5", "description": "The Bitbucket Cloud module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", @@ -51,7 +51,7 @@ "bitbucket": "^2.12.0", "fs-extra": "^11.2.0", "yaml": "^2.0.0", - "zod": "^3.22.4" + "zod": "^3.25.76 || ^4.0.0" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.examples.test.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.examples.test.ts index 6b4b77879e..892fdcebec 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.examples.test.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.examples.test.ts @@ -34,7 +34,7 @@ import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; import { initRepoAndPush } from '@backstage/plugin-scaffolder-node'; import yaml from 'yaml'; -import { sep } from 'path'; +import { sep } from 'node:path'; import { examples } from './bitbucketCloud.examples'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.test.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.test.ts index 6cac8a0e1c..9e55be473a 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.test.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.test.ts @@ -481,4 +481,104 @@ describe('publish:bitbucketCloud', () => { 'https://bitbucket.org/workspace/repo/src/main', ); }); + + it('should call initAndPush with username and token credentials', async () => { + const tokenConfig = new ConfigReader({ + integrations: { + bitbucketCloud: [ + { + username: 'test-user', + token: 'api-token-123', + }, + ], + }, + }); + + const tokenIntegrations = ScmIntegrations.fromConfig(tokenConfig); + const tokenAction = createPublishBitbucketCloudAction({ + integrations: tokenIntegrations, + config: tokenConfig, + }); + + server.use( + rest.post( + 'https://api.bitbucket.org/2.0/repositories/workspace/repo', + (_, res, ctx) => + res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + links: { + html: { + href: 'https://bitbucket.org/workspace/repo', + }, + clone: [ + { + name: 'https', + href: 'https://bitbucket.org/workspace/cloneurl', + }, + ], + }, + }), + ), + ), + ); + + await tokenAction.handler(mockContext); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + remoteUrl: 'https://bitbucket.org/workspace/cloneurl', + defaultBranch: 'master', + auth: { + username: 'x-bitbucket-api-token-auth', + password: 'api-token-123', + }, + logger: mockContext.logger, + gitAuthorInfo: {}, + }); + }); + + it('should call initAndPush with token passed through ctx.input', async () => { + server.use( + rest.post( + 'https://api.bitbucket.org/2.0/repositories/workspace/repo', + (_, res, ctx) => + res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + links: { + html: { + href: 'https://bitbucket.org/workspace/repo', + }, + clone: [ + { + name: 'https', + href: 'https://bitbucket.org/workspace/cloneurl', + }, + ], + }, + }), + ), + ), + ); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + token: 'input-token-override', + }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + remoteUrl: 'https://bitbucket.org/workspace/cloneurl', + defaultBranch: 'master', + auth: { username: 'x-token-auth', password: 'input-token-override' }, + logger: mockContext.logger, + gitAuthorInfo: {}, + }); + }); }); 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 722bf01303..d34235f633 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts @@ -24,7 +24,7 @@ import { } from '@backstage/plugin-scaffolder-node'; import { Config } from '@backstage/config'; -import { getAuthorizationHeader } from './helpers'; +import { getAuthorizationHeader, getGitAuth } from './helpers'; import { examples } from './bitbucketCloud.examples'; const createRepository = async (opts: { @@ -216,7 +216,7 @@ export function createPublishBitbucketCloudAction(options: { ); } - const authorization = getAuthorizationHeader( + const authorization = await getAuthorizationHeader( ctx.input.token ? { token: ctx.input.token } : integrationConfig.config, ); @@ -242,28 +242,9 @@ export function createPublishBitbucketCloudAction(options: { email: config.getOptionalString('scaffolder.defaultAuthor.email'), }; - let auth; - - if (ctx.input.token) { - auth = { - username: 'x-token-auth', - password: ctx.input.token, - }; - } else { - if ( - !integrationConfig.config.username || - !integrationConfig.config.appPassword - ) { - throw new Error( - 'Credentials for Bitbucket Cloud integration required for this action.', - ); - } - - auth = { - username: integrationConfig.config.username, - password: integrationConfig.config.appPassword, - }; - } + const auth = await getGitAuth( + ctx.input.token ? { token: ctx.input.token } : integrationConfig.config, + ); const signingKey = integrationConfig.config.commitSigningKey ?? diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.test.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.test.ts index 55ee430091..2ac54894d5 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.test.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.test.ts @@ -22,11 +22,11 @@ jest.mock('bitbucket', () => ({ })); describe('bitbucketCloud:branchRestriction:create', () => { - it('getBitbucketClient should return the correct headers with username and password', () => { + it('getBitbucketClient should return the correct headers with username and password', async () => { expect.assertions(1); const username = 'username'; const password = 'password'; - getBitbucketClient({ username: username, appPassword: password }); + await getBitbucketClient({ username: username, appPassword: password }); expect(Bitbucket).toHaveBeenCalledWith({ auth: { username: username, @@ -35,12 +35,14 @@ describe('bitbucketCloud:branchRestriction:create', () => { }); }); - it('getBitbucketClient should throw if only one of username or password is provided', () => { + it('getBitbucketClient should throw if only one of username or password is provided', async () => { expect.assertions(2); const username = 'username'; const password = 'password'; - expect(() => getBitbucketClient({ username })).toThrow(Error); - expect(() => getBitbucketClient({ appPassword: password })).toThrow(Error); + await expect(getBitbucketClient({ username })).rejects.toThrow(Error); + await expect(getBitbucketClient({ appPassword: password })).rejects.toThrow( + Error, + ); }); }); diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.ts index 233818e00a..a12f63f980 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.ts @@ -37,6 +37,8 @@ const createBitbucketCloudBranchRestriction = async (opts: { token?: string; username?: string; appPassword?: string; + clientId?: string; + clientSecret?: string; }; }) => { const { @@ -52,7 +54,7 @@ const createBitbucketCloudBranchRestriction = async (opts: { authorization, } = opts; - const bitbucket = getBitbucketClient(authorization); + const bitbucket = await getBitbucketClient(authorization); return await bitbucket.branchrestrictions.create({ _body: { groups: groups, diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.ts index 4bf9226db7..372f45c928 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.ts @@ -68,7 +68,7 @@ export const createBitbucketPipelinesRunAction = (options: { const host = 'bitbucket.org'; const integrationConfig = integrations.bitbucketCloud.byHost(host); - const authorization = getAuthorizationHeader( + const authorization = await getAuthorizationHeader( token ? { token } : integrationConfig!.config, ); let response: Response; diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPullRequest.test.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPullRequest.test.ts index b4db3f78c6..c0947a616f 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPullRequest.test.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPullRequest.test.ts @@ -415,4 +415,50 @@ describe('publish:bitbucketCloud:pull-request', () => { 'https://bitbucket.org/workspace/repo/pull-requests/1', ); }); + + it('should use username and token credentials from integration config', async () => { + const tokenConfig = new ConfigReader({ + integrations: { + bitbucketCloud: [ + { + username: 'test-user', + token: 'api-token-123', + }, + ], + }, + }); + + const tokenIntegrations = ScmIntegrations.fromConfig(tokenConfig); + const tokenAction = createPublishBitbucketCloudPullRequestAction({ + integrations: tokenIntegrations, + config: tokenConfig, + }); + + server.use( + ...handlers, + rest.post( + 'https://api.bitbucket.org/2.0/repositories/workspace/repo/refs/branches', + (_, res, ctx) => { + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }, + ), + ); + + await tokenAction.handler({ + ...mockContext, + input: { + ...mockContext.input, + sourceBranch: 'new-branch', + }, + }); + + expect(mockContext.output).toHaveBeenCalledWith( + 'pullRequestUrl', + 'https://bitbucket.org/workspace/repo/pull-requests/1', + ); + }); }); diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPullRequest.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPullRequest.ts index 9969f13eb2..c620490cf2 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPullRequest.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPullRequest.ts @@ -27,7 +27,7 @@ import { } from '@backstage/plugin-scaffolder-node'; import { Config } from '@backstage/config'; import fs from 'fs-extra'; -import { getAuthorizationHeader } from './helpers'; +import { getAuthorizationHeader, getGitAuth } from './helpers'; import { examples } from './bitbucketCloudPullRequest.examples'; const createPullRequest = async (opts: { @@ -199,7 +199,6 @@ const getDefaultBranch = async (opts: { apiBaseUrl: string; }): Promise => { const { workspace, repo, authorization, apiBaseUrl } = opts; - let response: Response; const options: RequestInit = { method: 'GET', @@ -209,14 +208,10 @@ const getDefaultBranch = async (opts: { }, }; - try { - response = await fetch( - `${apiBaseUrl}/repositories/${workspace}/${repo}`, - options, - ); - } catch (error) { - throw error; - } + const response = await fetch( + `${apiBaseUrl}/repositories/${workspace}/${repo}`, + options, + ); const { mainbranch } = await response.json(); const defaultBranch = mainbranch.name; @@ -317,7 +312,7 @@ export function createPublishBitbucketCloudPullRequestAction(options: { ); } - const authorization = getAuthorizationHeader( + const authorization = await getAuthorizationHeader( ctx.input.token ? { token: ctx.input.token } : integrationConfig.config, ); @@ -358,28 +353,11 @@ export function createPublishBitbucketCloudPullRequestAction(options: { const remoteUrl = `https://${host}/${workspace}/${repo}.git`; - let auth; - - if (ctx.input.token) { - auth = { - username: 'x-token-auth', - password: ctx.input.token, - }; - } else { - if ( - !integrationConfig.config.username || - !integrationConfig.config.appPassword - ) { - throw new Error( - 'Credentials for Bitbucket Cloud integration required for this action.', - ); - } - - auth = { - username: integrationConfig.config.username, - password: integrationConfig.config.appPassword, - }; - } + const auth = await getGitAuth( + ctx.input.token + ? { token: ctx.input.token } + : integrationConfig.config, + ); const gitAuthorInfo = { name: diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/helpers.test.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/helpers.test.ts new file mode 100644 index 0000000000..c236bb941a --- /dev/null +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/helpers.test.ts @@ -0,0 +1,240 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + getBitbucketClient, + getAuthorizationHeader, + getGitAuth, +} from './helpers'; +import { getBitbucketCloudOAuthToken } from '@backstage/integration'; + +jest.mock('@backstage/integration', () => ({ + getBitbucketCloudOAuthToken: jest.fn(), +})); + +describe('helpers', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('getBitbucketClient', () => { + it('should create client with OAuth token', async () => { + (getBitbucketCloudOAuthToken as jest.Mock).mockResolvedValue( + 'oauth-token-123', + ); + + const client = await getBitbucketClient({ + clientId: 'client-id', + clientSecret: 'client-secret', + }); + + expect(client).toBeDefined(); + expect(getBitbucketCloudOAuthToken).toHaveBeenCalledWith( + 'client-id', + 'client-secret', + ); + }); + + it('should create client with standalone token', async () => { + const client = await getBitbucketClient({ + token: 'standalone-token', + }); + + expect(client).toBeDefined(); + }); + + it('should create client with username and API token', async () => { + const client = await getBitbucketClient({ + username: 'test-user', + token: 'api-token', + }); + + expect(client).toBeDefined(); + }); + + it('should create client with username and appPassword', async () => { + const client = await getBitbucketClient({ + username: 'test-user', + appPassword: 'app-password', + }); + + expect(client).toBeDefined(); + }); + + it('should throw error when no credentials provided', async () => { + await expect(getBitbucketClient({})).rejects.toThrow( + /Authorization has not been provided for Bitbucket Cloud/, + ); + }); + }); + + describe('getAuthorizationHeader', () => { + it('should return Bearer token for OAuth credentials', async () => { + (getBitbucketCloudOAuthToken as jest.Mock).mockResolvedValue( + 'oauth-token-123', + ); + + const result = await getAuthorizationHeader({ + clientId: 'client-id', + clientSecret: 'client-secret', + }); + + expect(result).toBe('Bearer oauth-token-123'); + expect(getBitbucketCloudOAuthToken).toHaveBeenCalledWith( + 'client-id', + 'client-secret', + ); + }); + + it('should return Basic auth for username and token', async () => { + const result = await getAuthorizationHeader({ + username: 'test-user', + token: 'api-token', + }); + + const expectedAuth = Buffer.from('test-user:api-token', 'utf8').toString( + 'base64', + ); + expect(result).toBe(`Basic ${expectedAuth}`); + }); + + it('should return Basic auth for username and appPassword', async () => { + const result = await getAuthorizationHeader({ + username: 'test-user', + appPassword: 'app-password', + }); + + const expectedAuth = Buffer.from( + 'test-user:app-password', + 'utf8', + ).toString('base64'); + expect(result).toBe(`Basic ${expectedAuth}`); + }); + + it('should prefer token over appPassword when both are provided', async () => { + const result = await getAuthorizationHeader({ + username: 'test-user', + token: 'api-token', + appPassword: 'app-password', + }); + + const expectedAuth = Buffer.from('test-user:api-token', 'utf8').toString( + 'base64', + ); + expect(result).toBe(`Basic ${expectedAuth}`); + }); + + it('should return Bearer token for standalone token', async () => { + const result = await getAuthorizationHeader({ + token: 'standalone-token', + }); + + expect(result).toBe('Bearer standalone-token'); + }); + + it('should throw error when no credentials provided', async () => { + await expect(getAuthorizationHeader({})).rejects.toThrow( + /Authorization has not been provided for Bitbucket Cloud/, + ); + }); + }); + + describe('getGitAuth', () => { + it('should return x-token-auth for OAuth credentials', async () => { + (getBitbucketCloudOAuthToken as jest.Mock).mockResolvedValue( + 'oauth-token-123', + ); + + const result = await getGitAuth({ + clientId: 'client-id', + clientSecret: 'client-secret', + }); + + expect(result).toEqual({ + username: 'x-token-auth', + password: 'oauth-token-123', + }); + expect(getBitbucketCloudOAuthToken).toHaveBeenCalledWith( + 'client-id', + 'client-secret', + ); + }); + + it('should return x-token-auth for standalone token', async () => { + const result = await getGitAuth({ + token: 'standalone-token', + }); + + expect(result).toEqual({ + username: 'x-token-auth', + password: 'standalone-token', + }); + }); + + it('should return x-bitbucket-api-token-auth for username + API token', async () => { + const result = await getGitAuth({ + username: 'test-user', + token: 'api-token', + }); + + expect(result).toEqual({ + username: 'x-bitbucket-api-token-auth', + password: 'api-token', + }); + }); + + it('should return username and appPassword for username + appPassword', async () => { + const result = await getGitAuth({ + username: 'test-user', + appPassword: 'app-password', + }); + + expect(result).toEqual({ + username: 'test-user', + password: 'app-password', + }); + }); + + it('should prefer token over appPassword when both are provided', async () => { + const result = await getGitAuth({ + username: 'test-user', + token: 'api-token', + appPassword: 'app-password', + }); + + expect(result).toEqual({ + username: 'x-bitbucket-api-token-auth', + password: 'api-token', + }); + }); + + it('should throw error when no credentials provided', async () => { + await expect(getGitAuth({})).rejects.toThrow( + /Authorization has not been provided for Bitbucket Cloud/, + ); + }); + + it('should throw error when only username is provided', async () => { + await expect( + getGitAuth({ + username: 'test-user', + }), + ).rejects.toThrow( + /Authorization has not been provided for Bitbucket Cloud/, + ); + }); + }); +}); diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/helpers.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/helpers.ts index 1abffea393..3c7ee8f17a 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/helpers.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/helpers.ts @@ -15,12 +15,49 @@ */ import { Bitbucket } from 'bitbucket'; +import { getBitbucketCloudOAuthToken } from '@backstage/integration'; -export const getBitbucketClient = (config: { +export const getBitbucketClient = async (config: { token?: string; username?: string; appPassword?: string; + clientId?: string; + clientSecret?: string; }) => { + // If OAuth credentials provided, fetch token + if (config.clientId && config.clientSecret) { + const token = await getBitbucketCloudOAuthToken( + config.clientId, + config.clientSecret, + ); + return new Bitbucket({ + auth: { + token, + }, + }); + } + + // Standalone token (Bearer) + if (config.token && !config.username) { + return new Bitbucket({ + auth: { + token: config.token, + }, + }); + } + + // Username + API token (new method) + if (config.username && config.token) { + return new Bitbucket({ + auth: { + username: config.username, + password: config.token, + }, + }); + } + + // TODO: appPassword can be removed once fully + // deprecated by BitBucket on 9th June 2026. if (config.username && config.appPassword) { return new Bitbucket({ auth: { @@ -28,29 +65,36 @@ export const getBitbucketClient = (config: { password: config.appPassword, }, }); - } else if (config.token) { - return new Bitbucket({ - auth: { - token: config.token, - }, - }); } + throw new Error( - `Authorization has not been provided for Bitbucket Cloud. Please add either username + appPassword to the Integrations config or a user login auth token`, + `Authorization has not been provided for Bitbucket Cloud. Please provide either OAuth credentials (clientId/clientSecret), username and token, or username and appPassword in the Integrations config`, ); }; -export const getAuthorizationHeader = (config: { +export const getAuthorizationHeader = async (config: { username?: string; appPassword?: string; token?: string; -}) => { - if (config.username && config.appPassword) { + clientId?: string; + clientSecret?: string; +}): Promise => { + // OAuth authentication + if (config.clientId && config.clientSecret) { + const token = await getBitbucketCloudOAuthToken( + config.clientId, + config.clientSecret, + ); + return `Bearer ${token}`; + } + + // TODO: appPassword can be removed once fully + // deprecated by BitBucket on 9th June 2026. + if (config.username && (config.token ?? config.appPassword)) { const buffer = Buffer.from( - `${config.username}:${config.appPassword}`, + `${config.username}:${config.token ?? config.appPassword}`, 'utf8', ); - return `Basic ${buffer.toString('base64')}`; } @@ -59,6 +103,57 @@ export const getAuthorizationHeader = (config: { } throw new Error( - `Authorization has not been provided for Bitbucket Cloud. Please add either username + appPassword to the Integrations config or a user login auth token`, + `Authorization has not been provided for Bitbucket Cloud. Please provide either OAuth credentials (clientId/clientSecret), username and token, or username and appPassword in the Integrations config`, + ); +}; + +export const getGitAuth = async (config: { + username?: string; + appPassword?: string; + token?: string; + clientId?: string; + clientSecret?: string; +}): Promise<{ username: string; password: string }> => { + // OAuth authentication + if (config.clientId && config.clientSecret) { + const token = await getBitbucketCloudOAuthToken( + config.clientId, + config.clientSecret, + ); + return { + username: 'x-token-auth', + password: token, + }; + } + + // Standalone token (Bearer) + if (config.token && !config.username) { + return { + username: 'x-token-auth', + password: config.token, + }; + } + + // Username + API token (new method) + // For git operations, use the static username 'x-bitbucket-api-token-auth' + // https://support.atlassian.com/bitbucket-cloud/docs/using-api-tokens/ + if (config.username && config.token) { + return { + username: 'x-bitbucket-api-token-auth', + password: config.token, + }; + } + + // TODO: appPassword can be removed once fully + // deprecated by BitBucket on 9th June 2026. + if (config.username && config.appPassword) { + return { + username: config.username, + password: config.appPassword, + }; + } + + throw new Error( + `Authorization has not been provided for Bitbucket Cloud. Please provide either OAuth credentials (clientId/clientSecret), username and token, or username and appPassword in the Integrations config`, ); }; diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/inputProperties.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/inputProperties.ts index fadc8bcefb..ed35bfeab8 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/inputProperties.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/inputProperties.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { z as zod } from 'zod'; +import { z as zod } from 'zod/v3'; const repoUrl = (z: typeof zod) => z.string({ diff --git a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md index 1830fe34b9..e7b5209b6d 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,176 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket-server +## 0.2.20 + +### Patch Changes + +- ed6b53c: Removed bare catch-and-rethrow blocks that served no purpose. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/errors@1.3.0 + - @backstage/plugin-scaffolder-node@0.13.2 + - @backstage/integration@2.0.1 + - @backstage/config@1.3.7 + +## 0.2.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/plugin-scaffolder-node@0.13.2-next.2 + - @backstage/integration@2.0.1-next.0 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/config@1.3.7-next.0 + +## 0.2.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + +## 0.2.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-scaffolder-node@0.13.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration@2.0.0 + +## 0.2.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-scaffolder-node@0.13.0 + - @backstage/integration@2.0.0 + +## 0.2.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + +## 0.2.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.2.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + +## 0.2.18 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-scaffolder-node@0.12.5 + +## 0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## 0.2.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.2.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + - @backstage/integration@1.19.2 + +## 0.2.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + +## 0.2.16 + +### Patch Changes + +- 5a6aca2: Improve error message when provided target branch is missing +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.2.16-next.1 + +### Patch Changes + +- 5a6aca2: Improve error message when provided target branch is missing +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## 0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.2.15 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + ## 0.2.15-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket-server/knip-report.md b/plugins/scaffolder-backend-module-bitbucket-server/knip-report.md index 97d5b385fd..2661c35327 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/knip-report.md +++ b/plugins/scaffolder-backend-module-bitbucket-server/knip-report.md @@ -1,3 +1,2 @@ # Knip report - diff --git a/plugins/scaffolder-backend-module-bitbucket-server/package.json b/plugins/scaffolder-backend-module-bitbucket-server/package.json index c580b8b568..d01ca7f1c7 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/package.json +++ b/plugins/scaffolder-backend-module-bitbucket-server/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket-server", - "version": "0.2.15-next.1", + "version": "0.2.20", "description": "The Bitbucket Server module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.test.ts b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.test.ts index 37df535fec..1c7b0372f3 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.test.ts +++ b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.test.ts @@ -399,4 +399,33 @@ describe('publish:bitbucketServer:pull-request', () => { 'https://hosted.bitbucket.com/projects/project/repos/repo/pull-requests/1', ); }); + + it('should throw an error when the target branch is not found', async () => { + server.use( + rest.get( + 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos/repo/branches', + (_, res, ctx) => { + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json(responseOfBranches), + ); + }, + ), + ); + + await expect( + action.handler({ + ...mockContext, + input: { + ...mockContext.input, + repoUrl: 'hosted.bitbucket.com?project=project&repo=repo', + targetBranch: 'non-existent-branch', + sourceBranch: 'develop', + }, + }), + ).rejects.toThrow( + /Target branch 'non-existent-branch' not found in repository project\/repo/, + ); + }); }); diff --git a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.ts b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.ts index 8717a76ae1..9d75f304a4 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.ts +++ b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.ts @@ -213,7 +213,6 @@ const getDefaultBranch = async (opts: { apiBaseUrl: string; }) => { const { project, repo, authorization, apiBaseUrl } = opts; - let response: Response; const options: RequestInit = { method: 'GET', @@ -223,14 +222,10 @@ const getDefaultBranch = async (opts: { }, }; - try { - response = await fetch( - `${apiBaseUrl}/projects/${project}/repos/${repo}/default-branch`, - options, - ); - } catch (error) { - throw error; - } + const response = await fetch( + `${apiBaseUrl}/projects/${project}/repos/${repo}/default-branch`, + options, + ); const { displayId } = await response.json(); const defaultBranch = displayId; @@ -380,6 +375,12 @@ export function createPublishBitbucketServerPullRequestAction(options: { apiBaseUrl, }); + if (!toRef) { + throw new InputError( + `Target branch '${finalTargetBranch}' not found in repository ${project}/${repo}. Please ensure the branch exists before creating a pull request.`, + ); + } + let fromRef = await findBranches({ project, repo, diff --git a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md deleted file mode 100644 index 8b3a918a10..0000000000 --- a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md +++ /dev/null @@ -1,1210 +0,0 @@ -# @backstage/plugin-scaffolder-backend-module-bitbucket - -## 0.3.16-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-plugin-api@1.5.0-next.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.15-next.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.15-next.1 - - @backstage/plugin-scaffolder-node@0.12.1-next.1 - -## 0.3.16-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/config@1.3.6-next.0 - - @backstage/integration@1.18.2-next.0 - - @backstage/plugin-scaffolder-node@0.12.1-next.0 - - @backstage/backend-plugin-api@1.4.5-next.0 - - @backstage/errors@1.2.7 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.15-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.15-next.0 - -## 0.3.15 - -### Patch Changes - -- c8aa210: Updating import for the `scaffolderActionsExtensionPoint` to be the main export -- Updated dependencies - - @backstage/integration@1.18.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.14 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.14 - - @backstage/plugin-scaffolder-node@0.12.0 - - @backstage/config@1.3.5 - - @backstage/backend-plugin-api@1.4.4 - -## 0.3.15-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/config@1.3.4-next.0 - - @backstage/integration@1.18.1-next.1 - - @backstage/backend-plugin-api@1.4.4-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.14-next.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.14-next.1 - - @backstage/plugin-scaffolder-node@0.12.0-next.1 - -## 0.3.15-next.0 - -### Patch Changes - -- c8aa210: Updating import for the `scaffolderActionsExtensionPoint` to be the main export -- Updated dependencies - - @backstage/integration@1.18.1-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.14-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.14-next.0 - - @backstage/plugin-scaffolder-node@0.12.0-next.0 - - @backstage/backend-plugin-api@1.4.3 - - @backstage/config@1.3.3 - - @backstage/errors@1.2.7 - -## 0.3.14 - -### Patch Changes - -- Updated dependencies - - @backstage/integration@1.18.0 - - @backstage/backend-plugin-api@1.4.3 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.13 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.13 - - @backstage/plugin-scaffolder-node@0.11.1 - -## 0.3.14-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/integration@1.18.0-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.13-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.13-next.0 - - @backstage/plugin-scaffolder-node@0.11.1-next.0 - - @backstage/backend-plugin-api@1.4.3-next.0 - -## 0.3.13 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-node@0.11.0 - - @backstage/backend-plugin-api@1.4.2 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.12 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.12 - -## 0.3.13-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-node@0.11.0-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.12-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.12-next.0 - - @backstage/backend-plugin-api@1.4.2-next.0 - - @backstage/config@1.3.3 - - @backstage/errors@1.2.7 - - @backstage/integration@1.17.1 - -## 0.3.12 - -### Patch Changes - -- Updated dependencies - - @backstage/config@1.3.3 - - @backstage/plugin-scaffolder-node@0.10.0 - - @backstage/integration@1.17.1 - - @backstage/backend-plugin-api@1.4.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.11 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.11 - -## 0.3.12-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-node@0.10.0-next.2 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.11-next.2 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.11-next.2 - -## 0.3.12-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/config@1.3.3-next.0 - - @backstage/integration@1.17.1-next.1 - - @backstage/backend-plugin-api@1.4.1-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.11-next.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.11-next.1 - - @backstage/plugin-scaffolder-node@0.9.1-next.1 - -## 0.3.12-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-node@0.9.1-next.0 - - @backstage/integration@1.17.1-next.0 - - @backstage/backend-plugin-api@1.4.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.11-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.11-next.0 - - @backstage/config@1.3.2 - - @backstage/errors@1.2.7 - -## 0.3.11 - -### Patch Changes - -- 7f710d2: Migrating `bitbucket` actions to use the new `zod` format -- Updated dependencies - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.10 - - @backstage/plugin-scaffolder-node@0.9.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.10 - - @backstage/backend-plugin-api@1.4.0 - - @backstage/config@1.3.2 - - @backstage/errors@1.2.7 - - @backstage/integration@1.17.0 - -## 0.3.11-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-node@0.9.0-next.2 - - @backstage/backend-plugin-api@1.4.0-next.1 - - @backstage/config@1.3.2 - - @backstage/errors@1.2.7 - - @backstage/integration@1.17.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.10-next.2 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.10-next.2 - -## 0.3.11-next.1 - -### Patch Changes - -- 7f710d2: Migrating `bitbucket` actions to use the new `zod` format -- Updated dependencies - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.10-next.1 - - @backstage/plugin-scaffolder-node@0.8.3-next.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.10-next.1 - - @backstage/backend-plugin-api@1.4.0-next.1 - - @backstage/config@1.3.2 - - @backstage/errors@1.2.7 - - @backstage/integration@1.17.0 - -## 0.3.11-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.10-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.10-next.0 - - @backstage/plugin-scaffolder-node@0.8.3-next.0 - - @backstage/backend-plugin-api@1.4.0-next.0 - -## 0.3.10 - -### Patch Changes - -- 72d019d: Removed various typos -- Updated dependencies - - @backstage/integration@1.17.0 - - @backstage/backend-plugin-api@1.3.1 - - @backstage/plugin-scaffolder-node@0.8.2 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.9 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.9 - - @backstage/config@1.3.2 - - @backstage/errors@1.2.7 - -## 0.3.10-next.3 - -### Patch Changes - -- Updated dependencies - - @backstage/integration@1.17.0-next.3 - - @backstage/plugin-scaffolder-node@0.8.2-next.3 - - @backstage/backend-plugin-api@1.3.1-next.2 - - @backstage/config@1.3.2 - - @backstage/errors@1.2.7 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.9-next.3 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.9-next.3 - -## 0.3.10-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/integration@1.17.0-next.2 - - @backstage/config@1.3.2 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.9-next.2 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.9-next.2 - - @backstage/plugin-scaffolder-node@0.8.2-next.2 - - @backstage/backend-plugin-api@1.3.1-next.1 - - @backstage/errors@1.2.7 - -## 0.3.10-next.1 - -### Patch Changes - -- 72d019d: Removed various typos -- Updated dependencies - - @backstage/backend-plugin-api@1.3.1-next.1 - - @backstage/integration@1.16.4-next.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.9-next.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.9-next.1 - - @backstage/plugin-scaffolder-node@0.8.2-next.1 - - @backstage/config@1.3.2 - - @backstage/errors@1.2.7 - -## 0.3.10-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/integration@1.16.4-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.9-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.9-next.0 - - @backstage/plugin-scaffolder-node@0.8.2-next.0 - - @backstage/backend-plugin-api@1.3.1-next.0 - - @backstage/config@1.3.2 - - @backstage/errors@1.2.7 - -## 0.3.9 - -### Patch Changes - -- adfceee: Made "publish:bitbucket" action idempotent -- Updated dependencies - - @backstage/plugin-scaffolder-node@0.8.1 - - @backstage/backend-plugin-api@1.3.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.8 - - @backstage/integration@1.16.3 - - @backstage/config@1.3.2 - - @backstage/errors@1.2.7 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.8 - -## 0.3.9-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/integration@1.16.3-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.8-next.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.8-next.1 - - @backstage/plugin-scaffolder-node@0.8.1-next.1 - - @backstage/backend-plugin-api@1.2.1 - - @backstage/config@1.3.2 - - @backstage/errors@1.2.7 - -## 0.3.9-next.0 - -### Patch Changes - -- adfceee: Made "publish:bitbucket" action idempotent -- Updated dependencies - - @backstage/plugin-scaffolder-node@0.8.1-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.8-next.0 - - @backstage/backend-plugin-api@1.2.1 - - @backstage/config@1.3.2 - - @backstage/errors@1.2.7 - - @backstage/integration@1.16.2 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.8-next.0 - -## 0.3.8 - -### Patch Changes - -- 4f8b5b6: Allow signing git commits using configured private PGP key in scaffolder -- Updated dependencies - - @backstage/integration@1.16.2 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.7 - - @backstage/plugin-scaffolder-node@0.8.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.7 - - @backstage/backend-plugin-api@1.2.1 - - @backstage/config@1.3.2 - - @backstage/errors@1.2.7 - -## 0.3.8-next.2 - -### Patch Changes - -- 4f8b5b6: Allow signing git commits using configured private PGP key in scaffolder -- Updated dependencies - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.7-next.2 - - @backstage/plugin-scaffolder-node@0.8.0-next.2 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.7-next.2 - - @backstage/integration@1.16.2-next.0 - - @backstage/backend-plugin-api@1.2.1-next.1 - - @backstage/config@1.3.2 - - @backstage/errors@1.2.7 - -## 0.3.8-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-node@0.7.1-next.1 - - @backstage/backend-plugin-api@1.2.1-next.1 - - @backstage/config@1.3.2 - - @backstage/errors@1.2.7 - - @backstage/integration@1.16.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.7-next.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.7-next.1 - -## 0.3.8-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-plugin-api@1.2.1-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.7-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.7-next.0 - - @backstage/plugin-scaffolder-node@0.7.1-next.0 - -## 0.3.7 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-plugin-api@1.2.0 - - @backstage/plugin-scaffolder-node@0.7.0 - - @backstage/config@1.3.2 - - @backstage/errors@1.2.7 - - @backstage/integration@1.16.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.6 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.6 - -## 0.3.7-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-node@0.7.0-next.2 - - @backstage/backend-plugin-api@1.2.0-next.2 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.6-next.2 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.6-next.2 - - @backstage/config@1.3.2 - - @backstage/errors@1.2.7 - - @backstage/integration@1.16.1 - -## 0.3.7-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-plugin-api@1.2.0-next.1 - - @backstage/config@1.3.2 - - @backstage/errors@1.2.7 - - @backstage/integration@1.16.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.6-next.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.6-next.1 - - @backstage/plugin-scaffolder-node@0.7.0-next.1 - -## 0.3.7-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-plugin-api@1.2.0-next.0 - - @backstage/plugin-scaffolder-node@0.7.0-next.0 - - @backstage/config@1.3.2 - - @backstage/errors@1.2.7 - - @backstage/integration@1.16.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.6-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.6-next.0 - -## 0.3.6 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-node@0.6.3 - - @backstage/integration@1.16.1 - - @backstage/backend-plugin-api@1.1.1 - - @backstage/config@1.3.2 - - @backstage/errors@1.2.7 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.5 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.5 - -## 0.3.6-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-plugin-api@1.1.1-next.1 - - @backstage/config@1.3.2-next.0 - - @backstage/errors@1.2.7-next.0 - - @backstage/plugin-scaffolder-node@0.6.3-next.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.5-next.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.5-next.1 - - @backstage/integration@1.16.1-next.0 - -## 0.3.6-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-node@0.6.3-next.0 - - @backstage/backend-plugin-api@1.1.1-next.0 - - @backstage/config@1.3.1 - - @backstage/errors@1.2.6 - - @backstage/integration@1.16.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.5-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.5-next.0 - -## 0.3.5 - -### Patch Changes - -- 5f04976: Fixed a bug that caused missing code in published packages. -- 5c9cc05: Use native fetch instead of node-fetch -- Updated dependencies - - @backstage/integration@1.16.0 - - @backstage/backend-plugin-api@1.1.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.4 - - @backstage/plugin-scaffolder-node@0.6.2 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.4 - - @backstage/errors@1.2.6 - - @backstage/config@1.3.1 - -## 0.3.5-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-plugin-api@1.1.0-next.2 - - @backstage/errors@1.2.6-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.4-next.2 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.4-next.2 - - @backstage/plugin-scaffolder-node@0.6.2-next.2 - - @backstage/config@1.3.1-next.0 - - @backstage/integration@1.16.0-next.1 - -## 0.3.5-next.1 - -### Patch Changes - -- 5c9cc05: Use native fetch instead of node-fetch -- Updated dependencies - - @backstage/plugin-scaffolder-node@0.6.2-next.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.4-next.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.4-next.1 - - @backstage/backend-plugin-api@1.1.0-next.1 - - @backstage/config@1.3.0 - - @backstage/errors@1.2.5 - - @backstage/integration@1.16.0-next.0 - -## 0.3.4-next.0 - -### Patch Changes - -- 5f04976: Fixed a bug that caused missing code in published packages. -- Updated dependencies - - @backstage/integration@1.16.0-next.0 - - @backstage/backend-plugin-api@1.0.3-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.3-next.0 - - @backstage/plugin-scaffolder-node@0.6.1-next.0 - - @backstage/config@1.3.0 - - @backstage/errors@1.2.5 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.3-next.0 - -## 0.3.2 - -### Patch Changes - -- Updated dependencies - - @backstage/config@1.3.0 - - @backstage/backend-plugin-api@1.0.2 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.2 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.2 - - @backstage/plugin-scaffolder-node@0.6.0 - - @backstage/errors@1.2.5 - - @backstage/integration@1.15.2 - -## 0.3.2-next.3 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-plugin-api@1.0.2-next.2 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/integration@1.15.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.2-next.3 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.2-next.3 - - @backstage/plugin-scaffolder-node@0.5.1-next.3 - -## 0.3.2-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-plugin-api@1.0.2-next.2 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/integration@1.15.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.2-next.2 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.2-next.2 - - @backstage/plugin-scaffolder-node@0.5.1-next.2 - -## 0.3.2-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.2-next.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.2-next.1 - - @backstage/backend-plugin-api@1.0.2-next.1 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/integration@1.15.1 - - @backstage/plugin-scaffolder-node@0.5.1-next.1 - -## 0.3.2-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-plugin-api@1.0.2-next.0 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/integration@1.15.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.2-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.2-next.0 - - @backstage/plugin-scaffolder-node@0.5.1-next.0 - -## 0.3.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-node@0.5.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.1 - - @backstage/integration@1.15.1 - - @backstage/backend-plugin-api@1.0.1 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.1 - -## 0.3.1-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.1-next.2 - - @backstage/integration@1.15.1-next.1 - - @backstage/plugin-scaffolder-node@0.5.0-next.2 - - @backstage/backend-plugin-api@1.0.1-next.1 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.1-next.2 - -## 0.3.1-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/integration@1.15.1-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.1-next.1 - - @backstage/backend-plugin-api@1.0.1-next.0 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.1-next.1 - - @backstage/plugin-scaffolder-node@0.5.0-next.1 - -## 0.3.1-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-node@0.5.0-next.0 - - @backstage/backend-plugin-api@1.0.1-next.0 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/integration@1.15.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.1-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.1-next.0 - -## 0.3.0 - -### Minor Changes - -- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. - - This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. - - As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. - -### Patch Changes - -- Updated dependencies - - @backstage/backend-plugin-api@1.0.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.0 - - @backstage/integration@1.15.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.0 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/plugin-scaffolder-node@0.4.11 - -## 0.3.0-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.0-next.2 - - @backstage/backend-plugin-api@1.0.0-next.2 - - @backstage/integration@1.15.0-next.0 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.0-next.2 - - @backstage/plugin-scaffolder-node@0.4.11-next.2 - -## 0.3.0-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-plugin-api@0.9.0-next.1 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/integration@1.14.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.0-next.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.0-next.1 - - @backstage/plugin-scaffolder-node@0.4.11-next.1 - -## 0.3.0-next.0 - -### Minor Changes - -- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. - - This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. - - As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. - -### Patch Changes - -- Updated dependencies - - @backstage/backend-plugin-api@0.9.0-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.0-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.0-next.0 - - @backstage/plugin-scaffolder-node@0.4.11-next.0 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/integration@1.14.0 - -## 0.2.13 - -### Patch Changes - -- 93095ee: Make sure node-fetch is version 2.7.0 or greater -- Updated dependencies - - @backstage/backend-plugin-api@0.8.0 - - @backstage/plugin-scaffolder-node@0.4.9 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13 - - @backstage/integration@1.14.0 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - -## 0.2.13-next.3 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13-next.3 - - @backstage/backend-plugin-api@0.8.0-next.3 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/integration@1.14.0-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13-next.3 - - @backstage/plugin-scaffolder-node@0.4.9-next.3 - -## 0.2.13-next.2 - -### Patch Changes - -- 93095ee: Make sure node-fetch is version 2.7.0 or greater -- Updated dependencies - - @backstage/backend-plugin-api@0.8.0-next.2 - - @backstage/plugin-scaffolder-node@0.4.9-next.2 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13-next.2 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13-next.2 - - @backstage/integration@1.14.0-next.0 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - -## 0.2.13-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13-next.1 - - @backstage/backend-plugin-api@0.7.1-next.1 - - @backstage/integration@1.14.0-next.0 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13-next.1 - - @backstage/plugin-scaffolder-node@0.4.9-next.1 - -## 0.2.13-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/integration@1.14.0-next.0 - - @backstage/backend-plugin-api@0.7.1-next.0 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13-next.0 - - @backstage/plugin-scaffolder-node@0.4.9-next.0 - -## 0.2.12 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-plugin-api@0.7.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.12 - - @backstage/integration@1.13.0 - - @backstage/plugin-scaffolder-node@0.4.8 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.12 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - -## 0.2.12-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-plugin-api@0.6.22-next.1 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/integration@1.13.0-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.12-next.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.12-next.1 - - @backstage/plugin-scaffolder-node@0.4.8-next.1 - -## 0.2.11-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-plugin-api@0.6.21-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.11-next.0 - - @backstage/integration@1.13.0-next.0 - - @backstage/plugin-scaffolder-node@0.4.7-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.11-next.0 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - -## 0.2.9 - -### Patch Changes - -- 78a0b08: Internal refactor to handle `BackendFeature` contract change. -- d44a20a: Added additional plugin metadata to `package.json`. -- Updated dependencies - - @backstage/backend-plugin-api@0.6.19 - - @backstage/integration@1.12.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.9 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.9 - - @backstage/plugin-scaffolder-node@0.4.5 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - -## 0.2.9-next.2 - -### Patch Changes - -- d44a20a: Added additional plugin metadata to `package.json`. -- Updated dependencies - - @backstage/backend-plugin-api@0.6.19-next.3 - - @backstage/integration@1.12.0-next.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.9-next.2 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.9-next.2 - - @backstage/plugin-scaffolder-node@0.4.5-next.3 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - -## 0.2.9-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-plugin-api@0.6.19-next.2 - - @backstage/integration@1.12.0-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.9-next.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.9-next.1 - - @backstage/plugin-scaffolder-node@0.4.5-next.2 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - -## 0.2.9-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-plugin-api@0.6.19-next.0 - - @backstage/plugin-scaffolder-node@0.4.5-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.9-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.9-next.0 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/integration@1.11.0 - -## 0.2.8 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-plugin-api@0.6.18 - - @backstage/plugin-scaffolder-node@0.4.4 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.8 - - @backstage/integration@1.11.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.8 - -## 0.2.8-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-node@0.4.4-next.2 - - @backstage/integration@1.11.0-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.8-next.2 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.8-next.2 - -## 0.2.8-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-node@0.4.4-next.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.8-next.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.8-next.1 - - @backstage/backend-plugin-api@0.6.18-next.1 - -## 0.2.8-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.8-next.0 - - @backstage/backend-plugin-api@0.6.18-next.0 - - @backstage/plugin-scaffolder-node@0.4.4-next.0 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/integration@1.10.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.8-next.0 - -## 0.2.7 - -### Patch Changes - -- 33f958a: Improve examples to ensure consistency across all publish actions -- Updated dependencies - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.7 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.7 - - @backstage/backend-plugin-api@0.6.17 - - @backstage/integration@1.10.0 - - @backstage/plugin-scaffolder-node@0.4.3 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - -## 0.2.7-next.1 - -### Patch Changes - -- 33f958a: Improve examples to ensure consistency across all publish actions -- Updated dependencies - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.7-next.1 - - @backstage/backend-plugin-api@0.6.17-next.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.7-next.1 - - @backstage/plugin-scaffolder-node@0.4.3-next.1 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/integration@1.10.0-next.0 - -## 0.2.7-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.7-next.0 - - @backstage/integration@1.10.0-next.0 - - @backstage/backend-plugin-api@0.6.17-next.0 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.7-next.0 - - @backstage/plugin-scaffolder-node@0.4.3-next.0 - -## 0.2.6 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-plugin-api@0.6.16 - - @backstage/plugin-scaffolder-node@0.4.2 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/integration@1.9.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.6 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.6 - -## 0.2.5 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-node@0.4.1 - - @backstage/backend-plugin-api@0.6.15 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/integration@1.9.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.5 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.5 - -## 0.2.4 - -### Patch Changes - -- 2bd1410: Removed unused dependencies -- f44589d: Introduced `createMockActionContext` to unify the way of creating scaffolder mock context. - - It will help to maintain tests in a long run during structural changes of action context. - -- Updated dependencies - - @backstage/plugin-scaffolder-node@0.4.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.4 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.4 - - @backstage/integration@1.9.1 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/backend-plugin-api@0.6.14 - -## 0.2.4-next.2 - -### Patch Changes - -- 2bd1410: Removed unused dependencies -- Updated dependencies - - @backstage/plugin-scaffolder-node@0.4.0-next.2 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.4-next.2 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.4-next.2 - - @backstage/integration@1.9.1-next.2 - - @backstage/backend-plugin-api@0.6.14-next.2 - - @backstage/config@1.2.0-next.1 - - @backstage/errors@1.2.4-next.0 - -## 0.2.4-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/config@1.2.0-next.1 - - @backstage/plugin-scaffolder-node@0.4.0-next.1 - - @backstage/backend-common@0.21.4-next.1 - - @backstage/backend-plugin-api@0.6.14-next.1 - - @backstage/integration@1.9.1-next.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.4-next.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.4-next.1 - - @backstage/errors@1.2.4-next.0 - -## 0.2.3-next.0 - -### Patch Changes - -- f44589d: Introduced `createMockActionContext` to unify the way of creating scaffolder mock context. - - It will help to maintain tests in a long run during structural changes of action context. - -- Updated dependencies - - @backstage/backend-common@0.21.3-next.0 - - @backstage/errors@1.2.4-next.0 - - @backstage/plugin-scaffolder-node@0.3.3-next.0 - - @backstage/backend-plugin-api@0.6.13-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.3-next.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.3-next.0 - - @backstage/config@1.1.2-next.0 - - @backstage/integration@1.9.1-next.0 - -## 0.2.0 - -### Minor Changes - -- 5eb6882: Split `@backstage/plugin-scaffolder-backend-module-bitbucket` into - `@backstage/plugin-scaffolder-backend-module-bitbucket-cloud` and - `@backstage/plugin-scaffolder-backend-module-bitbucket-server`. - - `@backstage/plugin-scaffolder-backend-module-bitbucket` was **deprecated** in favor of these two replacements. - - Please use any of the two replacements depending on your needs. - - ```diff - - backend.add(import('@backstage/plugin-scaffolder-backend-module-bitbucket')); - + backend.add(import('@backstage/plugin-scaffolder-backend-module-bitbucket-cloud')); - + backend.add(import('@backstage/plugin-scaffolder-backend-module-bitbucket-server')); - ``` - -### Patch Changes - -- e9a5228: Exporting a default module for the new Backend System -- 8472188: Added or fixed the `repository` field in `package.json`. -- 6bb6f3e: Updated dependency `fs-extra` to `^11.2.0`. - Updated dependency `@types/fs-extra` to `^11.0.0`. -- fc98bb6: Enhanced the pull request action to allow for adding new content to the PR as described in this issue #21762 -- Updated dependencies - - @backstage/backend-common@0.21.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.0 - - @backstage/backend-plugin-api@0.6.10 - - @backstage/integration@1.9.0 - - @backstage/plugin-scaffolder-node@0.3.0 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.0 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - -## 0.2.0-next.3 - -### Patch Changes - -- 8472188: Added or fixed the `repository` field in `package.json`. -- Updated dependencies - - @backstage/backend-common@0.21.0-next.3 - - @backstage/integration@1.9.0-next.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.0-next.1 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.0-next.1 - - @backstage/plugin-scaffolder-node@0.3.0-next.3 - - @backstage/backend-plugin-api@0.6.10-next.3 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - -## 0.2.0-next.2 - -### Minor Changes - -- 5eb6882: Split `@backstage/plugin-scaffolder-backend-module-bitbucket` into - `@backstage/plugin-scaffolder-backend-module-bitbucket-cloud` and - `@backstage/plugin-scaffolder-backend-module-bitbucket-server`. - - `@backstage/plugin-scaffolder-backend-module-bitbucket` was **deprecated** in favor of these two replacements. - - Please use any of the two replacements depending on your needs. - - ```diff - - backend.add(import('@backstage/plugin-scaffolder-backend-module-bitbucket')); - + backend.add(import('@backstage/plugin-scaffolder-backend-module-bitbucket-cloud')); - + backend.add(import('@backstage/plugin-scaffolder-backend-module-bitbucket-server')); - ``` - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.0-next.0 - - @backstage/backend-common@0.21.0-next.2 - - @backstage/backend-plugin-api@0.6.10-next.2 - - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.0-next.0 - - @backstage/plugin-scaffolder-node@0.3.0-next.2 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - - @backstage/integration@1.9.0-next.0 - -## 0.1.2-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-plugin-api@0.6.10-next.1 - - @backstage/backend-common@0.21.0-next.1 - - @backstage/integration@1.9.0-next.0 - - @backstage/plugin-scaffolder-node@0.3.0-next.1 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - -## 0.1.2-next.0 - -### Patch Changes - -- e9a5228: Exporting a default module for the new Backend System -- fc98bb6: Enhanced the pull request action to allow for adding new content to the PR as described in this issue #21762 -- Updated dependencies - - @backstage/backend-common@0.21.0-next.0 - - @backstage/plugin-scaffolder-node@0.3.0-next.0 - - @backstage/backend-plugin-api@0.6.10-next.0 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - - @backstage/integration@1.8.0 - -## 0.1.1 - -### Patch Changes - -- a694f71: The Scaffolder builtin actions now contains an action for running pipelines from Bitbucket Cloud Rest API -- Updated dependencies - - @backstage/backend-common@0.20.1 - - @backstage/plugin-scaffolder-node@0.2.10 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - - @backstage/integration@1.8.0 - -## 0.1.1-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.20.1-next.2 - - @backstage/plugin-scaffolder-node@0.2.10-next.2 - -## 0.1.1-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.20.1-next.1 - - @backstage/integration@1.8.0 - - @backstage/config@1.1.1 - - @backstage/plugin-scaffolder-node@0.2.10-next.1 - - @backstage/errors@1.2.3 - -## 0.1.1-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.20.1-next.0 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - - @backstage/integration@1.8.0 - - @backstage/plugin-scaffolder-node@0.2.10-next.0 - -## 0.1.0 - -### Minor Changes - -- 219d7f0: Create new scaffolder module for external integrations - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.20.0 - - @backstage/plugin-scaffolder-node@0.2.9 - - @backstage/integration@1.8.0 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - -## 0.1.0-next.0 - -### Minor Changes - -- 219d7f0: Create new scaffolder module for external integrations - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-node@0.2.9-next.3 - - @backstage/backend-common@0.20.0-next.3 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - - @backstage/integration@1.8.0-next.1 diff --git a/plugins/scaffolder-backend-module-bitbucket/README.md b/plugins/scaffolder-backend-module-bitbucket/README.md deleted file mode 100644 index dde4207c0a..0000000000 --- a/plugins/scaffolder-backend-module-bitbucket/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# @backstage/plugin-scaffolder-backend-module-bitbucket - -**Deprecated!** - -Please use one of the following modules instead: - -- [@backstage/plugin-scaffolder-backend-module-bitbucket-cloud](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend-module-bitbucket-cloud) -- [@backstage/plugin-scaffolder-backend-module-bitbucket-server](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend-module-bitbucket-server). diff --git a/plugins/scaffolder-backend-module-bitbucket/catalog-info.yaml b/plugins/scaffolder-backend-module-bitbucket/catalog-info.yaml deleted file mode 100644 index b0a94d1c13..0000000000 --- a/plugins/scaffolder-backend-module-bitbucket/catalog-info.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: backstage.io/v1alpha1 -kind: Component -metadata: - name: backstage-plugin-scaffolder-backend-module-bitbucket - title: '@backstage/plugin-scaffolder-backend-module-bitbucket' - description: The bitbucket module for @backstage/plugin-scaffolder-backend -spec: - lifecycle: experimental - type: backstage-backend-plugin-module - owner: maintainers diff --git a/plugins/scaffolder-backend-module-bitbucket/knip-report.md b/plugins/scaffolder-backend-module-bitbucket/knip-report.md deleted file mode 100644 index c8b2aa1788..0000000000 --- a/plugins/scaffolder-backend-module-bitbucket/knip-report.md +++ /dev/null @@ -1,8 +0,0 @@ -# Knip report - -## Unused dependencies (1) - -| Name | Location | Severity | -| :------- | :----------- | :------- | -| fs-extra | plugins/scaffolder-backend-module-bitbucket/package.json | error | - diff --git a/plugins/scaffolder-backend-module-bitbucket/package.json b/plugins/scaffolder-backend-module-bitbucket/package.json deleted file mode 100644 index eb4004acba..0000000000 --- a/plugins/scaffolder-backend-module-bitbucket/package.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "name": "@backstage/plugin-scaffolder-backend-module-bitbucket", - "version": "0.3.16-next.1", - "description": "The bitbucket module for @backstage/plugin-scaffolder-backend", - "backstage": { - "role": "backend-plugin-module", - "pluginId": "scaffolder", - "pluginPackage": "@backstage/plugin-scaffolder-backend" - }, - "publishConfig": { - "access": "public" - }, - "repository": { - "type": "git", - "url": "https://github.com/backstage/backstage", - "directory": "plugins/scaffolder-backend-module-bitbucket" - }, - "license": "Apache-2.0", - "exports": { - ".": "./src/index.ts", - "./package.json": "./package.json" - }, - "main": "src/index.ts", - "types": "src/index.ts", - "typesVersions": { - "*": { - "package.json": [ - "package.json" - ] - } - }, - "files": [ - "dist" - ], - "scripts": { - "build": "backstage-cli package build", - "clean": "backstage-cli package clean", - "lint": "backstage-cli package lint", - "prepack": "backstage-cli package prepack", - "postpack": "backstage-cli package postpack", - "start": "backstage-cli package start", - "test": "backstage-cli package test" - }, - "dependencies": { - "@backstage/backend-plugin-api": "workspace:^", - "@backstage/config": "workspace:^", - "@backstage/errors": "workspace:^", - "@backstage/integration": "workspace:^", - "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud": "workspace:^", - "@backstage/plugin-scaffolder-backend-module-bitbucket-server": "workspace:^", - "@backstage/plugin-scaffolder-node": "workspace:^", - "fs-extra": "^11.2.0", - "yaml": "^2.0.0" - }, - "devDependencies": { - "@backstage/backend-test-utils": "workspace:^", - "@backstage/cli": "workspace:^", - "@backstage/plugin-scaffolder-node-test-utils": "workspace:^", - "msw": "^1.0.0" - }, - "deprecated": true -} diff --git a/plugins/scaffolder-backend-module-bitbucket/report.api.md b/plugins/scaffolder-backend-module-bitbucket/report.api.md deleted file mode 100644 index 33c9bcc9bd..0000000000 --- a/plugins/scaffolder-backend-module-bitbucket/report.api.md +++ /dev/null @@ -1,110 +0,0 @@ -## API Report File for "@backstage/plugin-scaffolder-backend-module-bitbucket" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts -import { BackendFeature } from '@backstage/backend-plugin-api'; -import * as bitbucketCloud from '@backstage/plugin-scaffolder-backend-module-bitbucket-cloud'; -import * as bitbucketServer from '@backstage/plugin-scaffolder-backend-module-bitbucket-server'; -import { Config } from '@backstage/config'; -import { ScmIntegrationRegistry } from '@backstage/integration'; -import { TemplateAction } from '@backstage/plugin-scaffolder-node'; - -// @public @deprecated -const bitbucketModule: BackendFeature; -export default bitbucketModule; - -// @public @deprecated (undocumented) -export const createBitbucketPipelinesRunAction: (options: { - integrations: ScmIntegrationRegistry; -}) => TemplateAction< - { - workspace: string; - repo_slug: string; - body?: - | { - target?: - | { - type?: string | undefined; - source?: string | undefined; - selector?: - | { - type: string; - pattern: string; - } - | undefined; - pull_request?: - | { - id: string; - } - | undefined; - commit?: - | { - type: string; - hash: string; - } - | undefined; - destination?: string | undefined; - ref_name?: string | undefined; - ref_type?: string | undefined; - destination_commit?: - | { - hash: string; - } - | undefined; - } - | undefined; - variables?: - | { - key: string; - value: string; - secured: boolean; - }[] - | undefined; - } - | undefined; - token?: string | undefined; - }, - { - buildNumber?: number | undefined; - repoUrl?: string | undefined; - pipelinesUrl?: string | undefined; - }, - 'v2' ->; - -// @public @deprecated -export function createPublishBitbucketAction(options: { - integrations: ScmIntegrationRegistry; - config: Config; -}): TemplateAction< - { - repoUrl: string; - description?: string | undefined; - repoVisibility?: 'private' | 'public' | undefined; - defaultBranch?: string | undefined; - sourcePath?: string | undefined; - enableLFS?: boolean | undefined; - token?: string | undefined; - gitCommitMessage?: string | undefined; - gitAuthorName?: string | undefined; - gitAuthorEmail?: string | undefined; - signCommit?: boolean | undefined; - }, - { - remoteUrl?: string | undefined; - repoContentsUrl?: string | undefined; - commitHash?: string | undefined; - }, - 'v2' ->; - -// @public @deprecated (undocumented) -export const createPublishBitbucketCloudAction: typeof bitbucketCloud.createPublishBitbucketCloudAction; - -// @public @deprecated (undocumented) -export const createPublishBitbucketServerAction: typeof bitbucketServer.createPublishBitbucketServerAction; - -// @public @deprecated (undocumented) -export const createPublishBitbucketServerPullRequestAction: typeof bitbucketServer.createPublishBitbucketServerPullRequestAction; -``` diff --git a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.examples.test.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.examples.test.ts deleted file mode 100644 index d452f2e11f..0000000000 --- a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.examples.test.ts +++ /dev/null @@ -1,368 +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. - */ - -jest.mock('@backstage/plugin-scaffolder-node', () => { - return { - ...jest.requireActual('@backstage/plugin-scaffolder-node'), - initRepoAndPush: jest.fn().mockResolvedValue({ - commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6', - }), - commitAndPushRepo: jest.fn().mockResolvedValue({ - commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6', - }), - }; -}); - -import { createPublishBitbucketAction } from './bitbucket'; -import { rest } from 'msw'; -import { setupServer } from 'msw/node'; -import { registerMswTestHooks } from '@backstage/backend-test-utils'; -import { ScmIntegrations } from '@backstage/integration'; -import { ConfigReader } from '@backstage/config'; -import { initRepoAndPush } from '@backstage/plugin-scaffolder-node'; -import yaml from 'yaml'; -import { sep } from 'path'; -import { examples } from './bitbucket.examples'; -import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; - -describe('publish:bitbucket', () => { - const config = new ConfigReader({ - integrations: { - bitbucket: [ - { - host: 'bitbucket.org', - token: 'tokenlols', - }, - { - host: 'hosted.bitbucket.com', - token: 'thing', - apiBaseUrl: 'https://hosted.bitbucket.com/rest/api/1.0', - }, - { - host: 'notoken.bitbucket.com', - }, - ], - }, - }); - - const integrations = ScmIntegrations.fromConfig(config); - const action = createPublishBitbucketAction({ integrations, config }); - const mockContext = createMockActionContext({ - input: { - repoUrl: 'bitbucket.org?workspace=workspace&project=project&repo=repo', - repoVisibility: 'private' as const, - }, - }); - const server = setupServer(); - registerMswTestHooks(server); - - beforeEach(() => { - jest.resetAllMocks(); - }); - - it('should call initAndPush with the correct values', async () => { - server.use( - rest.post( - 'https://api.bitbucket.org/2.0/repositories/workspace/repo', - (_, res, ctx) => - res( - ctx.status(200), - ctx.set('Content-Type', 'application/json'), - ctx.json({ - links: { - html: { - href: 'https://bitbucket.org/workspace/repo', - }, - clone: [ - { - name: 'https', - href: 'https://bitbucket.org/workspace/cloneurl', - }, - ], - }, - }), - ), - ), - ); - - await action.handler({ - ...mockContext, - input: yaml.parse(examples[0].example).steps[0].input, - }); - - expect(initRepoAndPush).toHaveBeenCalledWith({ - dir: mockContext.workspacePath, - remoteUrl: 'https://bitbucket.org/workspace/cloneurl', - defaultBranch: 'master', - auth: { username: 'x-token-auth', password: 'tokenlols' }, - logger: mockContext.logger, - commitMessage: 'initial commit', - gitAuthorInfo: { email: undefined, name: undefined }, - }); - }); - - it('should call initAndPush with the correct default branch', async () => { - server.use( - rest.post( - 'https://api.bitbucket.org/2.0/repositories/workspace/repo', - (_, res, ctx) => - res( - ctx.status(200), - ctx.set('Content-Type', 'application/json'), - ctx.json({ - links: { - html: { - href: 'https://bitbucket.org/workspace/repo', - }, - clone: [ - { - name: 'https', - href: 'https://bitbucket.org/workspace/cloneurl', - }, - ], - }, - }), - ), - ), - ); - - await action.handler({ - ...mockContext, - input: yaml.parse(examples[3].example).steps[0].input, - }); - - expect(initRepoAndPush).toHaveBeenCalledWith({ - dir: mockContext.workspacePath, - remoteUrl: 'https://bitbucket.org/workspace/cloneurl', - defaultBranch: 'main', - auth: { username: 'x-token-auth', password: 'tokenlols' }, - logger: mockContext.logger, - commitMessage: 'initial commit', - gitAuthorInfo: { email: undefined, name: undefined }, - }); - }); - - it('should call initAndPush with the specified source path', async () => { - server.use( - rest.post( - 'https://api.bitbucket.org/2.0/repositories/workspace/repo', - (_, res, ctx) => - res( - ctx.status(200), - ctx.set('Content-Type', 'application/json'), - ctx.json({ - links: { - html: { - href: 'https://bitbucket.org/workspace/repo', - }, - clone: [ - { - name: 'https', - href: 'https://bitbucket.org/workspace/cloneurl', - }, - ], - }, - }), - ), - ), - ); - - await action.handler({ - ...mockContext, - input: yaml.parse(examples[4].example).steps[0].input, - }); - expect(initRepoAndPush).toHaveBeenCalledWith({ - dir: `${mockContext.workspacePath}${sep}repoRoot`, - remoteUrl: 'https://bitbucket.org/workspace/cloneurl', - auth: { username: 'x-token-auth', password: 'tokenlols' }, - logger: mockContext.logger, - defaultBranch: 'master', - commitMessage: 'initial commit', - gitAuthorInfo: { email: undefined, name: undefined }, - }); - }); - - it('should call initAndPush with the authentication token', async () => { - server.use( - rest.post( - 'https://api.bitbucket.org/2.0/repositories/workspace/repo', - (_, res, ctx) => - res( - ctx.status(200), - ctx.set('Content-Type', 'application/json'), - ctx.json({ - links: { - html: { - href: 'https://bitbucket.org/workspace/repo', - }, - clone: [ - { - name: 'https', - href: 'https://bitbucket.org/workspace/cloneurl', - }, - ], - }, - }), - ), - ), - ); - - await action.handler({ - ...mockContext, - input: yaml.parse(examples[6].example).steps[0].input, - }); - expect(initRepoAndPush).toHaveBeenCalledWith({ - dir: mockContext.workspacePath, - remoteUrl: 'https://bitbucket.org/workspace/cloneurl', - auth: { username: 'x-token-auth', password: 'your-auth-token' }, - logger: mockContext.logger, - defaultBranch: 'master', - commitMessage: 'initial commit', - gitAuthorInfo: { - email: undefined, - name: undefined, - }, - }); - }); - - it('should call initAndPush with the custom commit message', async () => { - server.use( - rest.post( - 'https://api.bitbucket.org/2.0/repositories/workspace/repo', - (_, res, ctx) => - res( - ctx.status(200), - ctx.set('Content-Type', 'application/json'), - ctx.json({ - links: { - html: { - href: 'https://bitbucket.org/workspace/repo', - }, - clone: [ - { - name: 'https', - href: 'https://bitbucket.org/workspace/cloneurl', - }, - ], - }, - }), - ), - ), - ); - - await action.handler({ - ...mockContext, - input: yaml.parse(examples[7].example).steps[0].input, - }); - - expect(initRepoAndPush).toHaveBeenCalledWith({ - dir: mockContext.workspacePath, - remoteUrl: 'https://bitbucket.org/workspace/cloneurl', - auth: { username: 'x-token-auth', password: 'tokenlols' }, - logger: mockContext.logger, - defaultBranch: 'master', - commitMessage: 'Initial commit with custom message', - gitAuthorInfo: { email: undefined, name: undefined }, - }); - }); - - it('should call initAndPush with the custom author name and email for the commit.', async () => { - server.use( - rest.post( - 'https://api.bitbucket.org/2.0/repositories/workspace/repo', - (_, res, ctx) => - res( - ctx.status(200), - ctx.set('Content-Type', 'application/json'), - ctx.json({ - links: { - html: { - href: 'https://bitbucket.org/workspace/repo', - }, - clone: [ - { - name: 'https', - href: 'https://bitbucket.org/workspace/cloneurl', - }, - ], - }, - }), - ), - ), - ); - - await action.handler({ - ...mockContext, - input: yaml.parse(examples[8].example).steps[0].input, - }); - - expect(initRepoAndPush).toHaveBeenCalledWith({ - dir: mockContext.workspacePath, - remoteUrl: 'https://bitbucket.org/workspace/cloneurl', - auth: { username: 'x-token-auth', password: 'tokenlols' }, - logger: mockContext.logger, - defaultBranch: 'master', - commitMessage: 'initial commit', - gitAuthorInfo: { email: 'your.email@example.com', name: 'Your Name' }, - }); - }); - - describe('LFS for hosted bitbucket', () => { - const repoCreationResponse = { - links: { - self: [ - { - href: 'https://bitbucket.mycompany.com/projects/project/repos/repo', - }, - ], - clone: [ - { - name: 'http', - href: 'https://bitbucket.mycompany.com/scm/project/repo', - }, - ], - }, - }; - - it('should call the correct APIs to enable LFS if requested and the host is hosted bitbucket', async () => { - expect.assertions(1); - server.use( - rest.post( - 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos', - (_, res, ctx) => { - return res( - ctx.status(201), - ctx.set('Content-Type', 'application/json'), - ctx.json(repoCreationResponse), - ); - }, - ), - rest.put( - 'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled', - (req, res, ctx) => { - expect(req.headers.get('Authorization')).toBe('Bearer thing'); - return res(ctx.status(204)); - }, - ), - ); - - await action.handler({ - ...mockContext, - input: yaml.parse(examples[5].example).steps[0].input, - }); - }); - }); -}); diff --git a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.examples.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.examples.ts deleted file mode 100644 index 6248c991f8..0000000000 --- a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.examples.ts +++ /dev/null @@ -1,203 +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 { TemplateExample } from '@backstage/plugin-scaffolder-node'; -import yaml from 'yaml'; - -export const examples: TemplateExample[] = [ - { - description: - 'Initializes a git repository with the content in the workspace, and publishes it to Bitbucket with the default configuration.', - example: yaml.stringify({ - steps: [ - { - id: 'publish', - action: 'publish:bitbucket', - name: 'Publish to Bitbucket', - input: { - repoUrl: - 'bitbucket.org?repo=repo&workspace=workspace&project=project', - }, - }, - ], - }), - }, - { - description: 'Initializes a Bitbucket repository with a description.', - example: yaml.stringify({ - steps: [ - { - id: 'publish', - action: 'publish:bitbucket', - name: 'Publish to Bitbucket', - input: { - repoUrl: - 'bitbucket.org?repo=repo&workspace=workspace&project=project', - description: 'Initialize a git repository', - }, - }, - ], - }), - }, - { - description: - 'Initializes a Bitbucket repository with public repo visibility, if not set defaults to private', - example: yaml.stringify({ - steps: [ - { - id: 'publish', - action: 'publish:bitbucket', - name: 'Publish to Bitbucket', - input: { - repoUrl: - 'bitbucket.org?repo=repo&workspace=workspace&project=project', - repoVisibility: 'public', - }, - }, - ], - }), - }, - { - description: - 'Initializes a Bitbucket repository with a default branch, if not set defaults to master', - example: yaml.stringify({ - steps: [ - { - id: 'publish', - action: 'publish:bitbucket', - name: 'Publish to Bitbucket', - input: { - repoUrl: - 'bitbucket.org?repo=repo&workspace=workspace&project=project', - defaultBranch: 'main', - }, - }, - ], - }), - }, - { - description: - 'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository', - example: yaml.stringify({ - steps: [ - { - id: 'publish', - action: 'publish:bitbucket', - name: 'Publish to Bitbucket', - input: { - repoUrl: - 'bitbucket.org?repo=repo&workspace=workspace&project=project', - sourcePath: './repoRoot', - }, - }, - ], - }), - }, - { - description: 'Initializes a Bitbucket repository with LFS enabled', - example: yaml.stringify({ - steps: [ - { - id: 'publish', - action: 'publish:bitbucket', - name: 'Publish to Bitbucket', - input: { - repoUrl: 'hosted.bitbucket.com?project=project&repo=repo', - enableLFS: true, - }, - }, - ], - }), - }, - { - description: - 'Initializes a Bitbucket repository with a custom authentication token', - example: yaml.stringify({ - steps: [ - { - id: 'publish', - action: 'publish:bitbucket', - name: 'Publish to Bitbucket', - input: { - repoUrl: - 'bitbucket.org?repo=repo&workspace=workspace&project=project', - token: 'your-auth-token', - }, - }, - ], - }), - }, - { - description: - 'Initializes a Bitbucket repository with an initial commit message, if not set defaults to `initial commit`', - example: yaml.stringify({ - steps: [ - { - id: 'publish', - action: 'publish:bitbucket', - name: 'Publish to Bitbucket', - input: { - repoUrl: - 'bitbucket.org?repo=repo&workspace=workspace&project=project', - gitCommitMessage: 'Initial commit with custom message', - }, - }, - ], - }), - }, - { - description: 'Initializes a Bitbucket repository with a custom author', - example: yaml.stringify({ - steps: [ - { - id: 'publish', - action: 'publish:bitbucket', - name: 'Publish to Bitbucket', - input: { - repoUrl: - 'bitbucket.org?repo=repo&workspace=workspace&project=project', - gitAuthorName: 'Your Name', - gitAuthorEmail: 'your.email@example.com', - }, - }, - ], - }), - }, - { - description: - 'Initializes a Bitbucket repository with all properties being set', - example: yaml.stringify({ - steps: [ - { - id: 'publish', - action: 'publish:bitbucket', - name: 'Publish to Bitbucket', - input: { - repoUrl: - 'bitbucket.org?repo=repo&workspace=workspace&project=project', - description: 'Initialize a git repository', - repoVisibility: 'public', - defaultBranch: 'main', - token: 'your-auth-token', - gitCommitMessage: 'Initial commit with custom message', - gitAuthorName: 'Your Name', - gitAuthorEmail: 'your.email@example.com', - }, - }, - ], - }), - }, -]; diff --git a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.test.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.test.ts deleted file mode 100644 index 654b0fffcb..0000000000 --- a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.test.ts +++ /dev/null @@ -1,619 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -jest.mock('@backstage/plugin-scaffolder-node', () => { - return { - ...jest.requireActual('@backstage/plugin-scaffolder-node'), - initRepoAndPush: jest.fn().mockResolvedValue({ - commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6', - }), - commitAndPushRepo: jest.fn().mockResolvedValue({ - commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6', - }), - }; -}); -import { createPublishBitbucketAction } from './bitbucket'; -import { rest } from 'msw'; -import { setupServer } from 'msw/node'; -import { registerMswTestHooks } from '@backstage/backend-test-utils'; -import { ScmIntegrations } from '@backstage/integration'; -import { ConfigReader } from '@backstage/config'; -import { initRepoAndPush } from '@backstage/plugin-scaffolder-node'; -import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; - -describe('publish:bitbucket', () => { - const config = new ConfigReader({ - integrations: { - bitbucket: [ - { - host: 'bitbucket.org', - token: 'tokenlols', - }, - { - host: 'hosted.bitbucket.com', - token: 'thing', - apiBaseUrl: 'https://hosted.bitbucket.com/rest/api/1.0', - }, - { - host: 'notoken.bitbucket.com', - }, - ], - }, - }); - - const integrations = ScmIntegrations.fromConfig(config); - const action = createPublishBitbucketAction({ integrations, config }); - const mockContext = createMockActionContext({ - input: { - repoUrl: 'bitbucket.org?workspace=workspace&project=project&repo=repo', - repoVisibility: 'private' as const, - }, - }); - const server = setupServer(); - registerMswTestHooks(server); - - beforeEach(() => { - jest.resetAllMocks(); - }); - - it('should throw an error when the repoUrl is not well formed', async () => { - await expect( - action.handler({ - ...mockContext, - input: { repoUrl: 'bitbucket.org?project=project&repo=repo' }, - }), - ).rejects.toThrow(/missing workspace/); - - await expect( - action.handler({ - ...mockContext, - input: { repoUrl: 'bitbucket.org?workspace=workspace&repo=repo' }, - }), - ).rejects.toThrow(/missing project/); - - await expect( - action.handler({ - ...mockContext, - input: { repoUrl: 'bitbucket.org?workspace=workspace&project=project' }, - }), - ).rejects.toThrow(/missing repo/); - }); - - it('should throw if there is no integration config provided', async () => { - await expect( - action.handler({ - ...mockContext, - input: { - repoUrl: 'missing.com?workspace=workspace&project=project&repo=repo', - }, - }), - ).rejects.toThrow(/No matching integration configuration/); - }); - - it('should throw if there is no token in the integration config that is returned', async () => { - await expect( - action.handler({ - ...mockContext, - input: { - repoUrl: - 'notoken.bitbucket.com?workspace=workspace&project=project&repo=repo', - }, - }), - ).rejects.toThrow(/Authorization has not been provided for Bitbucket/); - }); - - it('should call the correct APIs when the host is bitbucket cloud', async () => { - expect.assertions(2); - server.use( - rest.post( - 'https://api.bitbucket.org/2.0/repositories/workspace/repo', - (req, res, ctx) => { - expect(req.headers.get('Authorization')).toBe('Bearer tokenlols'); - expect(req.body).toEqual({ - is_private: true, - scm: 'git', - project: { key: 'project' }, - }); - return res( - ctx.status(200), - ctx.set('Content-Type', 'application/json'), - ctx.json({ - links: { - html: { - href: 'https://bitbucket.org/workspace/repo', - }, - clone: [ - { - name: 'https', - href: 'https://bitbucket.org/workspace/repo', - }, - ], - }, - }), - ); - }, - ), - ); - - await action.handler({ - ...mockContext, - input: { - ...mockContext.input, - repoUrl: 'bitbucket.org?workspace=workspace&project=project&repo=repo', - }, - }); - }); - - it('should call the correct APIs when the host is hosted bitbucket', async () => { - expect.assertions(2); - server.use( - rest.post( - 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos', - (req, res, ctx) => { - expect(req.headers.get('Authorization')).toBe('Bearer thing'); - expect(req.body).toEqual({ public: false, name: 'repo' }); - return res( - ctx.status(201), - ctx.set('Content-Type', 'application/json'), - ctx.json({ - links: { - self: [ - { - href: 'https://bitbucket.mycompany.com/projects/project/repos/repo', - }, - ], - clone: [ - { - name: 'http', - href: 'https://bitbucket.mycompany.com/scm/project/repo', - }, - ], - }, - }), - ); - }, - ), - ); - - await action.handler({ - ...mockContext, - input: { - ...mockContext.input, - repoUrl: 'hosted.bitbucket.com?project=project&repo=repo', - }, - }); - }); - - it('should work if the token is provided through ctx.input', async () => { - expect.assertions(2); - server.use( - rest.post( - 'https://notoken.bitbucket.com/rest/api/1.0/projects/project/repos', - (req, res, ctx) => { - expect(req.headers.get('Authorization')).toBe('Bearer lols'); - expect(req.body).toEqual({ public: false, name: 'repo' }); - return res( - ctx.status(201), - ctx.set('Content-Type', 'application/json'), - ctx.json({ - links: { - self: [ - { - href: 'https://bitbucket.mycompany.com/projects/project/repos/repo', - }, - ], - clone: [ - { - name: 'http', - href: 'https://bitbucket.mycompany.com/scm/project/repo', - }, - ], - }, - }), - ); - }, - ), - ); - await action.handler({ - ...mockContext, - input: { - repoUrl: 'notoken.bitbucket.com?project=project&repo=repo', - token: 'lols', - }, - }); - }); - - describe('LFS for hosted bitbucket', () => { - const repoCreationResponse = { - links: { - self: [ - { - href: 'https://bitbucket.mycompany.com/projects/project/repos/repo', - }, - ], - clone: [ - { - name: 'http', - href: 'https://bitbucket.mycompany.com/scm/project/repo', - }, - ], - }, - }; - - it('should call the correct APIs to enable LFS if requested and the host is hosted bitbucket', async () => { - expect.assertions(1); - server.use( - rest.post( - 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos', - (_, res, ctx) => { - return res( - ctx.status(201), - ctx.set('Content-Type', 'application/json'), - ctx.json(repoCreationResponse), - ); - }, - ), - rest.put( - 'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled', - (req, res, ctx) => { - expect(req.headers.get('Authorization')).toBe('Bearer thing'); - return res(ctx.status(204)); - }, - ), - ); - - await action.handler({ - ...mockContext, - input: { - ...mockContext.input, - repoUrl: 'hosted.bitbucket.com?project=project&repo=repo', - enableLFS: true, - }, - }); - }); - - it('should report an error if enabling LFS fails', async () => { - server.use( - rest.post( - 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos', - (_, res, ctx) => { - return res( - ctx.status(201), - ctx.set('Content-Type', 'application/json'), - ctx.json(repoCreationResponse), - ); - }, - ), - rest.put( - 'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled', - (_, res, ctx) => { - return res(ctx.status(500)); - }, - ), - ); - - await expect( - action.handler({ - ...mockContext, - input: { - ...mockContext.input, - repoUrl: 'hosted.bitbucket.com?project=project&repo=repo', - enableLFS: true, - }, - }), - ).rejects.toThrow(/Failed to enable LFS/); - }); - }); - - it('should call initAndPush with the correct values', async () => { - server.use( - rest.post( - 'https://api.bitbucket.org/2.0/repositories/workspace/repo', - (_, res, ctx) => - res( - ctx.status(200), - ctx.set('Content-Type', 'application/json'), - ctx.json({ - links: { - html: { - href: 'https://bitbucket.org/workspace/repo', - }, - clone: [ - { - name: 'https', - href: 'https://bitbucket.org/workspace/cloneurl', - }, - ], - }, - }), - ), - ), - ); - - await action.handler(mockContext); - - expect(initRepoAndPush).toHaveBeenCalledWith({ - dir: mockContext.workspacePath, - remoteUrl: 'https://bitbucket.org/workspace/cloneurl', - defaultBranch: 'master', - auth: { username: 'x-token-auth', password: 'tokenlols' }, - logger: mockContext.logger, - commitMessage: 'initial commit', - gitAuthorInfo: {}, - }); - }); - - it('should call initAndPush with the correct default branch', async () => { - server.use( - rest.post( - 'https://api.bitbucket.org/2.0/repositories/workspace/repo', - (_, res, ctx) => - res( - ctx.status(200), - ctx.set('Content-Type', 'application/json'), - ctx.json({ - links: { - html: { - href: 'https://bitbucket.org/workspace/repo', - }, - clone: [ - { - name: 'https', - href: 'https://bitbucket.org/workspace/cloneurl', - }, - ], - }, - }), - ), - ), - ); - - await action.handler({ - ...mockContext, - input: { - ...mockContext.input, - defaultBranch: 'main', - }, - }); - - expect(initRepoAndPush).toHaveBeenCalledWith({ - dir: mockContext.workspacePath, - remoteUrl: 'https://bitbucket.org/workspace/cloneurl', - defaultBranch: 'main', - auth: { username: 'x-token-auth', password: 'tokenlols' }, - logger: mockContext.logger, - commitMessage: 'initial commit', - gitAuthorInfo: {}, - }); - }); - - it('should call initAndPush with the configured defaultAuthor', async () => { - const customAuthorConfig = new ConfigReader({ - integrations: { - bitbucket: [ - { - host: 'bitbucket.org', - token: 'tokenlols', - }, - { - host: 'hosted.bitbucket.com', - token: 'thing', - apiBaseUrl: 'https://hosted.bitbucket.com/rest/api/1.0', - }, - { - host: 'notoken.bitbucket.com', - }, - ], - }, - scaffolder: { - defaultAuthor: { - name: 'Test', - email: 'example@example.com', - }, - }, - }); - - const customAuthorIntegrations = - ScmIntegrations.fromConfig(customAuthorConfig); - const customAuthorAction = createPublishBitbucketAction({ - integrations: customAuthorIntegrations, - config: customAuthorConfig, - }); - - server.use( - rest.post( - 'https://api.bitbucket.org/2.0/repositories/workspace/repo', - (_, res, ctx) => - res( - ctx.status(200), - ctx.set('Content-Type', 'application/json'), - ctx.json({ - links: { - html: { - href: 'https://bitbucket.org/workspace/repo', - }, - clone: [ - { - name: 'https', - href: 'https://bitbucket.org/workspace/cloneurl', - }, - ], - }, - }), - ), - ), - ); - - await customAuthorAction.handler(mockContext); - - expect(initRepoAndPush).toHaveBeenCalledWith({ - dir: mockContext.workspacePath, - remoteUrl: 'https://bitbucket.org/workspace/cloneurl', - auth: { username: 'x-token-auth', password: 'tokenlols' }, - logger: mockContext.logger, - defaultBranch: 'master', - commitMessage: 'initial commit', - gitAuthorInfo: { name: 'Test', email: 'example@example.com' }, - }); - }); - - it('should call initAndPush with the configured defaultCommitMessage', async () => { - const customAuthorConfig = new ConfigReader({ - integrations: { - bitbucket: [ - { - host: 'bitbucket.org', - token: 'tokenlols', - }, - { - host: 'hosted.bitbucket.com', - token: 'thing', - apiBaseUrl: 'https://hosted.bitbucket.com/rest/api/1.0', - }, - { - host: 'notoken.bitbucket.com', - }, - ], - }, - scaffolder: { - defaultCommitMessage: 'Test commit message', - }, - }); - - const customAuthorIntegrations = - ScmIntegrations.fromConfig(customAuthorConfig); - const customAuthorAction = createPublishBitbucketAction({ - integrations: customAuthorIntegrations, - config: customAuthorConfig, - }); - - server.use( - rest.post( - 'https://api.bitbucket.org/2.0/repositories/workspace/repo', - (_, res, ctx) => - res( - ctx.status(200), - ctx.set('Content-Type', 'application/json'), - ctx.json({ - links: { - html: { - href: 'https://bitbucket.org/workspace/repo', - }, - clone: [ - { - name: 'https', - href: 'https://bitbucket.org/workspace/cloneurl', - }, - ], - }, - }), - ), - ), - ); - - await customAuthorAction.handler(mockContext); - - expect(initRepoAndPush).toHaveBeenCalledWith({ - dir: mockContext.workspacePath, - remoteUrl: 'https://bitbucket.org/workspace/cloneurl', - auth: { username: 'x-token-auth', password: 'tokenlols' }, - logger: mockContext.logger, - defaultBranch: 'master', - commitMessage: 'initial commit', - gitAuthorInfo: { email: undefined, name: undefined }, - }); - }); - - it('should call outputs with the correct urls', async () => { - server.use( - rest.post( - 'https://api.bitbucket.org/2.0/repositories/workspace/repo', - (_, res, ctx) => - res( - ctx.status(200), - ctx.set('Content-Type', 'application/json'), - ctx.json({ - links: { - html: { - href: 'https://bitbucket.org/workspace/repo', - }, - clone: [ - { - name: 'https', - href: 'https://bitbucket.org/workspace/cloneurl', - }, - ], - }, - }), - ), - ), - ); - - await action.handler(mockContext); - - expect(mockContext.output).toHaveBeenCalledWith( - 'remoteUrl', - 'https://bitbucket.org/workspace/cloneurl', - ); - expect(mockContext.output).toHaveBeenCalledWith( - 'repoContentsUrl', - 'https://bitbucket.org/workspace/repo/src/master', - ); - }); - - it('should call outputs with the correct urls with correct default branch', async () => { - server.use( - rest.post( - 'https://api.bitbucket.org/2.0/repositories/workspace/repo', - (_, res, ctx) => - res( - ctx.status(200), - ctx.set('Content-Type', 'application/json'), - ctx.json({ - links: { - html: { - href: 'https://bitbucket.org/workspace/repo', - }, - clone: [ - { - name: 'https', - href: 'https://bitbucket.org/workspace/cloneurl', - }, - ], - }, - }), - ), - ), - ); - - await action.handler({ - ...mockContext, - input: { - ...mockContext.input, - defaultBranch: 'main', - }, - }); - - expect(mockContext.output).toHaveBeenCalledWith( - 'remoteUrl', - 'https://bitbucket.org/workspace/cloneurl', - ); - expect(mockContext.output).toHaveBeenCalledWith( - 'repoContentsUrl', - 'https://bitbucket.org/workspace/repo/src/main', - ); - }); -}); diff --git a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.ts deleted file mode 100644 index f25bc0a7f1..0000000000 --- a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.ts +++ /dev/null @@ -1,450 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { InputError } from '@backstage/errors'; -import { - BitbucketIntegrationConfig, - ScmIntegrationRegistry, -} from '@backstage/integration'; -import { - createTemplateAction, - getRepoSourceDirectory, - initRepoAndPush, - parseRepoUrl, -} from '@backstage/plugin-scaffolder-node'; -import { Config } from '@backstage/config'; -import { examples } from './bitbucket.examples'; - -const createBitbucketCloudRepository = async (opts: { - workspace: string; - project: string; - repo: string; - description?: string; - repoVisibility: 'private' | 'public'; - mainBranch: string; - authorization: string; - apiBaseUrl: string; -}) => { - const { - workspace, - project, - repo, - description, - repoVisibility, - mainBranch, - authorization, - apiBaseUrl, - } = opts; - - const options: RequestInit = { - method: 'POST', - body: JSON.stringify({ - scm: 'git', - description: description, - is_private: repoVisibility === 'private', - project: { key: project }, - }), - headers: { - Authorization: authorization, - 'Content-Type': 'application/json', - }, - }; - - let response: Response; - try { - response = await fetch( - `${apiBaseUrl}/repositories/${workspace}/${repo}`, - options, - ); - } catch (e) { - throw new Error(`Unable to create repository, ${e}`); - } - - if (response.status !== 200) { - throw new Error( - `Unable to create repository, ${response.status} ${ - response.statusText - }, ${await response.text()}`, - ); - } - - const r = await response.json(); - let remoteUrl = ''; - for (const link of r.links.clone) { - if (link.name === 'https') { - remoteUrl = link.href; - } - } - - // "mainbranch.name" cannot be set neither at create nor update of the repo - // the first pushed branch will be set as "main branch" then - const repoContentsUrl = `${r.links.html.href}/src/${mainBranch}`; - return { remoteUrl, repoContentsUrl }; -}; - -const createBitbucketServerRepository = async (opts: { - project: string; - repo: string; - description?: string; - repoVisibility: 'private' | 'public'; - authorization: string; - apiBaseUrl: string; -}) => { - const { - project, - repo, - description, - authorization, - repoVisibility, - apiBaseUrl, - } = opts; - - let response: Response; - const options: RequestInit = { - method: 'POST', - body: JSON.stringify({ - name: repo, - description: description, - public: repoVisibility === 'public', - }), - headers: { - Authorization: authorization, - 'Content-Type': 'application/json', - }, - }; - - try { - response = await fetch(`${apiBaseUrl}/projects/${project}/repos`, options); - } catch (e) { - throw new Error(`Unable to create repository, ${e}`); - } - - if (response.status !== 201) { - throw new Error( - `Unable to create repository, ${response.status} ${ - response.statusText - }, ${await response.text()}`, - ); - } - - const r = await response.json(); - let remoteUrl = ''; - for (const link of r.links.clone) { - if (link.name === 'http') { - remoteUrl = link.href; - } - } - - const repoContentsUrl = `${r.links.self[0].href}`; - return { remoteUrl, repoContentsUrl }; -}; - -const getAuthorizationHeader = (config: BitbucketIntegrationConfig) => { - if (config.username && config.appPassword) { - const buffer = Buffer.from( - `${config.username}:${config.appPassword}`, - 'utf8', - ); - - return `Basic ${buffer.toString('base64')}`; - } - - if (config.token) { - return `Bearer ${config.token}`; - } - - throw new Error( - `Authorization has not been provided for Bitbucket. Please add either username + appPassword or token to the Integrations config`, - ); -}; - -const performEnableLFS = async (opts: { - authorization: string; - host: string; - project: string; - repo: string; -}) => { - const { authorization, host, project, repo } = opts; - - const options: RequestInit = { - method: 'PUT', - headers: { - Authorization: authorization, - }, - }; - - const { ok, status, statusText } = await fetch( - `https://${host}/rest/git-lfs/admin/projects/${project}/repos/${repo}/enabled`, - options, - ); - - if (!ok) - throw new Error( - `Failed to enable LFS in the repository, ${status}: ${statusText}`, - ); -}; - -/** - * Creates a new action that initializes a git repository of the content in the workspace - * and publishes it to Bitbucket. - * @public - * @deprecated in favor of "createPublishBitbucketCloudAction" by \@backstage/plugin-scaffolder-backend-module-bitbucket-cloud and "createPublishBitbucketServerAction" by \@backstage/plugin-scaffolder-backend-module-bitbucket-server - */ -export function createPublishBitbucketAction(options: { - integrations: ScmIntegrationRegistry; - config: Config; -}) { - const { integrations, config } = options; - - return createTemplateAction({ - id: 'publish:bitbucket', - description: - 'Initializes a git repository of the content in the workspace, and publishes it to Bitbucket.', - examples, - schema: { - input: { - repoUrl: z => - z.string({ - description: 'Repository Location', - }), - description: z => - z - .string({ - description: 'Repository Description', - }) - .optional(), - repoVisibility: z => - z - .enum(['private', 'public'], { - description: 'Repository Visibility', - }) - .optional(), - defaultBranch: z => - z - .string({ - description: `Sets the default branch on the repository. The default value is 'master'`, - }) - .optional(), - sourcePath: z => - z - .string({ - description: - 'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.', - }) - .optional(), - enableLFS: z => - z - .boolean({ - description: - 'Enable LFS for the repository. Only available for hosted Bitbucket.', - }) - .optional(), - token: z => - z - .string({ - description: 'The token to use for authorization to BitBucket', - }) - .optional(), - gitCommitMessage: z => - z - .string({ - description: `Sets the commit message on the repository. The default value is 'initial commit'`, - }) - .optional(), - gitAuthorName: z => - z - .string({ - description: `Sets the default author name for the commit. The default value is 'Scaffolder'`, - }) - .optional(), - gitAuthorEmail: z => - z - .string({ - description: `Sets the default author email for the commit.`, - }) - .optional(), - signCommit: z => - z - .boolean({ - description: 'Sign commit with configured PGP private key', - }) - .optional(), - }, - output: { - remoteUrl: z => - z - .string({ - description: 'A URL to the repository with the provider', - }) - .optional(), - repoContentsUrl: z => - z - .string({ - description: 'A URL to the root of the repository', - }) - .optional(), - commitHash: z => - z - .string({ - description: 'The git commit hash of the initial commit', - }) - .optional(), - }, - }, - async handler(ctx) { - ctx.logger.warn( - `[Deprecated] Please migrate the use of action "publish:bitbucket" to "publish:bitbucketCloud" or "publish:bitbucketServer".`, - ); - const { - repoUrl, - description, - defaultBranch = 'master', - repoVisibility = 'private', - enableLFS = false, - gitCommitMessage = 'initial commit', - gitAuthorName, - gitAuthorEmail, - signCommit, - } = ctx.input; - - const { workspace, project, repo, host } = parseRepoUrl( - repoUrl, - integrations, - ); - - // Workspace is only required for bitbucket cloud - if (host === 'bitbucket.org') { - if (!workspace) { - throw new InputError( - `Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing workspace`, - ); - } - } - - // Project is required for both bitbucket cloud and bitbucket server - if (!project) { - throw new InputError( - `Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing project`, - ); - } - - const integrationConfig = integrations.bitbucket.byHost(host); - - if (!integrationConfig) { - throw new InputError( - `No matching integration configuration for host ${host}, please check your integrations config`, - ); - } - - const authorization = getAuthorizationHeader( - ctx.input.token - ? { - host: integrationConfig.config.host, - apiBaseUrl: integrationConfig.config.apiBaseUrl, - token: ctx.input.token, - } - : integrationConfig.config, - ); - - const apiBaseUrl = integrationConfig.config.apiBaseUrl; - - const createMethod = - host === 'bitbucket.org' - ? createBitbucketCloudRepository - : createBitbucketServerRepository; - - 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 = { - name: gitAuthorName - ? gitAuthorName - : config.getOptionalString('scaffolder.defaultAuthor.name'), - email: gitAuthorEmail - ? gitAuthorEmail - : config.getOptionalString('scaffolder.defaultAuthor.email'), - }; - const signingKey = - integrationConfig.config.commitSigningKey ?? - config.getOptionalString('scaffolder.defaultCommitSigningKey'); - if (signCommit && !signingKey) { - throw new Error( - 'Signing commits is enabled but no signing key is provided in the configuration', - ); - } - - let auth; - - if (ctx.input.token) { - auth = { - username: 'x-token-auth', - password: ctx.input.token, - }; - } else { - auth = { - username: integrationConfig.config.username - ? integrationConfig.config.username - : 'x-token-auth', - password: integrationConfig.config.appPassword - ? integrationConfig.config.appPassword - : integrationConfig.config.token ?? '', - }; - } - - 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', commitHash); - ctx.output('remoteUrl', remoteUrl); - ctx.output('repoContentsUrl', repoContentsUrl); - }, - }); -} diff --git a/plugins/scaffolder-backend-module-bitbucket/src/deprecated.ts b/plugins/scaffolder-backend-module-bitbucket/src/deprecated.ts deleted file mode 100644 index a9daa771f0..0000000000 --- a/plugins/scaffolder-backend-module-bitbucket/src/deprecated.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import * as bitbucketCloud from '@backstage/plugin-scaffolder-backend-module-bitbucket-cloud'; -import * as bitbucketServer from '@backstage/plugin-scaffolder-backend-module-bitbucket-server'; - -export { createPublishBitbucketAction } from './actions/bitbucket'; - -/** - * @public - * @deprecated use import from `@backstage/plugin-scaffolder-backend-module-bitbucket-cloud` instead - */ -export const createPublishBitbucketCloudAction = - bitbucketCloud.createPublishBitbucketCloudAction; - -/** - * @public - * @deprecated use import from `@backstage/plugin-scaffolder-backend-module-bitbucket-cloud` instead - */ -export const createBitbucketPipelinesRunAction = - bitbucketCloud.createBitbucketPipelinesRunAction; - -/** - * @public - * @deprecated use import from `@backstage/plugin-scaffolder-backend-module-bitbucket-server` instead - */ -export const createPublishBitbucketServerAction = - bitbucketServer.createPublishBitbucketServerAction; - -/** - * @public - * @deprecated use import from `@backstage/plugin-scaffolder-backend-module-bitbucket-server` instead - */ -export const createPublishBitbucketServerPullRequestAction = - bitbucketServer.createPublishBitbucketServerPullRequestAction; diff --git a/plugins/scaffolder-backend-module-bitbucket/src/module.ts b/plugins/scaffolder-backend-module-bitbucket/src/module.ts deleted file mode 100644 index 884c8be004..0000000000 --- a/plugins/scaffolder-backend-module-bitbucket/src/module.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { - coreServices, - createBackendModule, -} from '@backstage/backend-plugin-api'; -import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node'; -import { - createBitbucketPipelinesRunAction, - createPublishBitbucketCloudAction, - createPublishBitbucketServerAction, - createPublishBitbucketServerPullRequestAction, -} from './deprecated'; -import { ScmIntegrations } from '@backstage/integration'; - -/** - * The Bitbucket Module for the Scaffolder Backend - * @public - * @deprecated use module by \@backstage/plugin-scaffolder-backend-module-bitbucket-cloud or \@backstage/plugin-scaffolder-backend-module-bitbucket-server instead - */ -export const bitbucketModule = createBackendModule({ - moduleId: 'bitbucket', - pluginId: 'scaffolder', - register({ registerInit }) { - registerInit({ - deps: { - scaffolder: scaffolderActionsExtensionPoint, - config: coreServices.rootConfig, - }, - async init({ scaffolder, config }) { - const integrations = ScmIntegrations.fromConfig(config); - - scaffolder.addActions( - createPublishBitbucketCloudAction({ integrations, config }), - createPublishBitbucketServerAction({ integrations, config }), - createPublishBitbucketServerPullRequestAction({ - integrations, - config, - }), - createBitbucketPipelinesRunAction({ integrations }), - ); - }, - }); - }, -}); diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md index 7af1650858..0fe1bdd47a 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md @@ -1,5 +1,175 @@ # @backstage/plugin-scaffolder-backend-module-confluence-to-markdown +## 0.3.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/errors@1.3.0 + - @backstage/plugin-scaffolder-node@0.13.2 + - @backstage/integration@2.0.1 + - @backstage/config@1.3.7 + +## 0.3.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/plugin-scaffolder-node@0.13.2-next.2 + - @backstage/integration@2.0.1-next.0 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/config@1.3.7-next.0 + +## 0.3.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + +## 0.3.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-scaffolder-node@0.13.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration@2.0.0 + +## 0.3.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-scaffolder-node@0.13.0 + - @backstage/integration@2.0.0 + +## 0.3.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + +## 0.3.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.3.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + +## 0.3.18 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-scaffolder-node@0.12.5 + +## 0.3.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## 0.3.18-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.3.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + - @backstage/integration@1.19.2 + +## 0.3.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + +## 0.3.16 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.3.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## 0.3.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.3.15 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + ## 0.3.15-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json index b47d147b67..9187e3b2b3 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown", - "version": "0.3.15-next.1", + "version": "0.3.20", "description": "The confluence-to-markdown module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts index 04d475677c..2f2035cc81 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts @@ -17,7 +17,7 @@ import { Config } from '@backstage/config'; import { ResponseError, ConflictError } from '@backstage/errors'; import fs from 'fs-extra'; -import { Readable } from 'stream'; +import { Readable } from 'node:stream'; interface Links { webui: string; diff --git a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md index 168369373a..86ed641201 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -1,5 +1,218 @@ # @backstage/plugin-scaffolder-backend-module-cookiecutter +## 0.3.22 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/backend-defaults@0.17.0 + - @backstage/errors@1.3.0 + - @backstage/plugin-scaffolder-node@0.13.2 + - @backstage/integration@2.0.1 + - @backstage/config@1.3.7 + +## 0.3.22-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/backend-defaults@0.16.1-next.2 + - @backstage/plugin-scaffolder-node@0.13.2-next.2 + - @backstage/integration@2.0.1-next.0 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/config@1.3.7-next.0 + +## 0.3.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-defaults@0.16.1-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + +## 0.3.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.16.1-next.0 + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-scaffolder-node@0.13.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration@2.0.0 + - @backstage/types@1.2.2 + +## 0.3.21 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/backend-defaults@0.16.0 + - @backstage/plugin-scaffolder-node@0.13.0 + - @backstage/integration@2.0.0 + +## 0.3.21-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/backend-defaults@0.16.0-next.2 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + +## 0.3.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.16.0-next.1 + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.3.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.3-next.0 + - @backstage/integration@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + +## 0.3.20 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/backend-defaults@0.15.2 + - @backstage/plugin-scaffolder-node@0.12.5 + +## 0.3.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/backend-defaults@0.15.2-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## 0.3.20-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/backend-defaults@0.15.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.3.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0 + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + - @backstage/integration@1.19.2 + +## 0.3.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + +## 0.3.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.1-next.1 + - @backstage/integration@1.19.2-next.0 + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + +## 0.3.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.1-next.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration@1.19.0 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.3.18 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-defaults@0.14.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.3.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## 0.3.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.3.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + ## 0.3.17-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-cookiecutter/knip-report.md b/plugins/scaffolder-backend-module-cookiecutter/knip-report.md index 5d34678cdf..2661c35327 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/knip-report.md +++ b/plugins/scaffolder-backend-module-cookiecutter/knip-report.md @@ -1,10 +1,2 @@ # Knip report -## Unused dependencies (3) - -| Name | Location | Severity | -| :-------------------------- | :----------- | :------- | -| @backstage/backend-defaults | plugins/scaffolder-backend-module-cookiecutter/package.json | error | -| winston | plugins/scaffolder-backend-module-cookiecutter/package.json | error | -| yn | plugins/scaffolder-backend-module-cookiecutter/package.json | error | - diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index 9a6bc9265e..037c493fb3 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-cookiecutter", - "version": "0.3.17-next.1", + "version": "0.3.22", "description": "A module for the scaffolder backend that lets you template projects using cookiecutter", "backstage": { "role": "backend-plugin-module", @@ -43,18 +43,14 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/backend-defaults": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-scaffolder-node": "workspace:^", "@backstage/types": "workspace:^", "command-exists": "^1.2.9", "fs-extra": "^11.2.0", - "winston": "^3.2.1", - "yaml": "^2.0.0", - "yn": "^4.0.0" + "yaml": "^2.0.0" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", diff --git a/plugins/scaffolder-backend-module-cookiecutter/report.api.md b/plugins/scaffolder-backend-module-cookiecutter/report.api.md index a60a8c0396..43303508e7 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/report.api.md +++ b/plugins/scaffolder-backend-module-cookiecutter/report.api.md @@ -4,12 +4,12 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; -import { objectOutputType } from 'zod'; +import { objectOutputType } from 'zod/v3'; import { ScmIntegrations } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; import { UrlReaderService } from '@backstage/backend-plugin-api'; -import { Writable } from 'stream'; -import { ZodTypeAny } from 'zod'; +import { Writable } from 'node:stream'; +import { ZodTypeAny } from 'zod/v3'; // @public export interface ContainerRunner { diff --git a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/ContainerRunner.ts b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/ContainerRunner.ts index a0b639148f..595f2dd342 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/ContainerRunner.ts +++ b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/ContainerRunner.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Writable } from 'stream'; +import { Writable } from 'node:stream'; /** * Handles the running of containers. diff --git a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.examples.test.ts b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.examples.test.ts index 37615ea95b..3797651420 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.examples.test.ts +++ b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.examples.test.ts @@ -14,12 +14,14 @@ * limitations under the License. */ -import { ConfigReader } from '@backstage/config'; import { JsonObject } from '@backstage/types'; import { ScmIntegrations } from '@backstage/integration'; -import { createMockDirectory } from '@backstage/backend-test-utils'; +import { + createMockDirectory, + mockServices, +} from '@backstage/backend-test-utils'; import { createFetchCookiecutterAction } from './cookiecutter'; -import { join } from 'path'; +import { join } from 'node:path'; import type { ActionContext } from '@backstage/plugin-scaffolder-node'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { examples } from './cookiecutter.examples'; @@ -46,16 +48,7 @@ jest.mock( describe('fetch:cookiecutter', () => { const mockDir = createMockDirectory({ mockOsTmpDir: true }); - const integrations = ScmIntegrations.fromConfig( - new ConfigReader({ - integrations: { - azure: [ - { host: 'dev.azure.com', token: 'tokenlols' }, - { host: 'myazurehostnotoken.com' }, - ], - }, - }), - ); + const integrations = ScmIntegrations.fromConfig(mockServices.rootConfig()); const mockTmpDir = mockDir.path; diff --git a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts index 8428536bb5..9b5dd31fc6 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts +++ b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts @@ -14,15 +14,17 @@ * limitations under the License. */ -import { ConfigReader } from '@backstage/config'; import { JsonObject } from '@backstage/types'; import { ScmIntegrations } from '@backstage/integration'; -import { createMockDirectory } from '@backstage/backend-test-utils'; +import { + createMockDirectory, + mockServices, +} from '@backstage/backend-test-utils'; import { createFetchCookiecutterAction } from './cookiecutter'; -import { join } from 'path'; +import { join } from 'node:path'; import type { ActionContext } from '@backstage/plugin-scaffolder-node'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; -import { Writable } from 'stream'; +import { Writable } from 'node:stream'; import { UrlReaderService } from '@backstage/backend-plugin-api'; import { ContainerRunner } from './ContainerRunner'; @@ -45,16 +47,7 @@ jest.mock( describe('fetch:cookiecutter', () => { const mockDir = createMockDirectory({ mockOsTmpDir: true }); - const integrations = ScmIntegrations.fromConfig( - new ConfigReader({ - integrations: { - azure: [ - { host: 'dev.azure.com', token: 'tokenlols' }, - { host: 'myazurehostnotoken.com' }, - ], - }, - }), - ); + const integrations = ScmIntegrations.fromConfig(mockServices.rootConfig()); const mockTmpDir = mockDir.path; diff --git a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts index 9a9df95dea..ef950dbb29 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts +++ b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts @@ -23,8 +23,8 @@ import { InputError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; import commandExists from 'command-exists'; import fs from 'fs-extra'; -import path, { resolve as resolvePath } from 'path'; -import { PassThrough, Writable } from 'stream'; +import path, { resolve as resolvePath } from 'node:path'; +import { PassThrough, Writable } from 'node:stream'; import { createTemplateAction, fetchContents, diff --git a/plugins/scaffolder-backend-module-gcp/CHANGELOG.md b/plugins/scaffolder-backend-module-gcp/CHANGELOG.md index b15077959e..87a9db9b80 100644 --- a/plugins/scaffolder-backend-module-gcp/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gcp/CHANGELOG.md @@ -1,5 +1,174 @@ # @backstage/plugin-scaffolder-backend-module-gcp +## 0.2.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/errors@1.3.0 + - @backstage/plugin-scaffolder-node@0.13.2 + - @backstage/integration@2.0.1 + - @backstage/config@1.3.7 + +## 0.2.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/plugin-scaffolder-node@0.13.2-next.2 + - @backstage/integration@2.0.1-next.0 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/config@1.3.7-next.0 + +## 0.2.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + +## 0.2.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-scaffolder-node@0.13.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration@2.0.0 + +## 0.2.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-scaffolder-node@0.13.0 + - @backstage/integration@2.0.0 + +## 0.2.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + +## 0.2.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.2.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + +## 0.2.18 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-scaffolder-node@0.12.5 + +## 0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## 0.2.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.2.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + - @backstage/integration@1.19.2 + +## 0.2.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + +## 0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## 0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.2.15 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + ## 0.2.15-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gcp/knip-report.md b/plugins/scaffolder-backend-module-gcp/knip-report.md index 3e771319e4..ff389fef28 100644 --- a/plugins/scaffolder-backend-module-gcp/knip-report.md +++ b/plugins/scaffolder-backend-module-gcp/knip-report.md @@ -2,14 +2,14 @@ ## Unused dependencies (2) -| Name | Location | Severity | -| :--------------------- | :----------- | :------- | -| @backstage/integration | plugins/scaffolder-backend-module-gcp/package.json | error | -| @backstage/errors | plugins/scaffolder-backend-module-gcp/package.json | error | +| Name | Location | Severity | +| :--------------------- | :---------------- | :------- | +| @backstage/integration | package.json:49:6 | error | +| @backstage/errors | package.json:48:6 | error | ## Unused devDependencies (1) -| Name | Location | Severity | -| :------------------------------------------- | :----------- | :------- | -| @backstage/plugin-scaffolder-node-test-utils | plugins/scaffolder-backend-module-gcp/package.json | error | +| Name | Location | Severity | +| :------------------------------------------- | :---------------- | :------- | +| @backstage/plugin-scaffolder-node-test-utils | package.json:56:6 | error | diff --git a/plugins/scaffolder-backend-module-gcp/package.json b/plugins/scaffolder-backend-module-gcp/package.json index 664f28f024..60a6b5b375 100644 --- a/plugins/scaffolder-backend-module-gcp/package.json +++ b/plugins/scaffolder-backend-module-gcp/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gcp", - "version": "0.2.15-next.1", + "version": "0.2.20", "description": "The GCP Bucket module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md index 5ecc69931c..edafc38b69 100644 --- a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,175 @@ # @backstage/plugin-scaffolder-backend-module-gerrit +## 0.2.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/errors@1.3.0 + - @backstage/plugin-scaffolder-node@0.13.2 + - @backstage/integration@2.0.1 + - @backstage/config@1.3.7 + +## 0.2.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/plugin-scaffolder-node@0.13.2-next.2 + - @backstage/integration@2.0.1-next.0 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/config@1.3.7-next.0 + +## 0.2.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + +## 0.2.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-scaffolder-node@0.13.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration@2.0.0 + +## 0.2.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-scaffolder-node@0.13.0 + - @backstage/integration@2.0.0 + +## 0.2.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + +## 0.2.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.2.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + +## 0.2.18 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-scaffolder-node@0.12.5 + +## 0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## 0.2.18-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.2.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + - @backstage/integration@1.19.2 + +## 0.2.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + +## 0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## 0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.2.15 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + ## 0.2.15-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gerrit/package.json b/plugins/scaffolder-backend-module-gerrit/package.json index 114ef18cc8..6c496d645b 100644 --- a/plugins/scaffolder-backend-module-gerrit/package.json +++ b/plugins/scaffolder-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gerrit", - "version": "0.2.15-next.1", + "version": "0.2.20", "description": "The gerrit module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.examples.test.ts b/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.examples.test.ts index 16ffd198fb..09b7d4b4dd 100644 --- a/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.examples.test.ts +++ b/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.examples.test.ts @@ -26,7 +26,7 @@ jest.mock('@backstage/plugin-scaffolder-node', () => { }; }); -import path from 'path'; +import path from 'node:path'; import { createPublishGerritAction } from './gerrit'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; diff --git a/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.test.ts b/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.test.ts index c102353209..067cdc80f3 100644 --- a/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.test.ts +++ b/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.test.ts @@ -26,7 +26,7 @@ jest.mock('@backstage/plugin-scaffolder-node', () => { }; }); -import path from 'path'; +import path from 'node:path'; import { createPublishGerritAction } from './gerrit'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; diff --git a/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.ts b/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.ts index 3cc3c47cd1..8f762370d7 100644 --- a/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.ts +++ b/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import crypto from 'crypto'; +import crypto from 'node:crypto'; import { InputError } from '@backstage/errors'; import { Config } from '@backstage/config'; import { diff --git a/plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.ts b/plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.ts index 5688ba8b7f..63b8c97a30 100644 --- a/plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.ts +++ b/plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import crypto from 'crypto'; +import crypto from 'node:crypto'; import { InputError } from '@backstage/errors'; import { Config } from '@backstage/config'; import { ScmIntegrationRegistry } from '@backstage/integration'; diff --git a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md index e9c58bd507..55d73fc016 100644 --- a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md @@ -1,5 +1,175 @@ # @backstage/plugin-scaffolder-backend-module-gitea +## 0.2.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/errors@1.3.0 + - @backstage/plugin-scaffolder-node@0.13.2 + - @backstage/integration@2.0.1 + - @backstage/config@1.3.7 + +## 0.2.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/plugin-scaffolder-node@0.13.2-next.2 + - @backstage/integration@2.0.1-next.0 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/config@1.3.7-next.0 + +## 0.2.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + +## 0.2.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-scaffolder-node@0.13.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration@2.0.0 + +## 0.2.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-scaffolder-node@0.13.0 + - @backstage/integration@2.0.0 + +## 0.2.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + +## 0.2.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.2.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + +## 0.2.18 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-scaffolder-node@0.12.5 + +## 0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## 0.2.18-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.2.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + - @backstage/integration@1.19.2 + +## 0.2.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + +## 0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## 0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.2.15 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + ## 0.2.15-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gitea/package.json b/plugins/scaffolder-backend-module-gitea/package.json index b5e11fae26..deb4ae9a3f 100644 --- a/plugins/scaffolder-backend-module-gitea/package.json +++ b/plugins/scaffolder-backend-module-gitea/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitea", - "version": "0.2.15-next.1", + "version": "0.2.20", "description": "The gitea module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts index 160725c7bd..f326e171da 100644 --- a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts +++ b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts @@ -29,7 +29,7 @@ import { parseRepoUrl, } from '@backstage/plugin-scaffolder-node'; import { examples } from './gitea.examples'; -import crypto from 'crypto'; +import crypto from 'node:crypto'; const checkGiteaContentUrl = async ( config: GiteaIntegrationConfig, diff --git a/plugins/scaffolder-backend-module-github/CHANGELOG.md b/plugins/scaffolder-backend-module-github/CHANGELOG.md index 8f5405ec63..0136f2c200 100644 --- a/plugins/scaffolder-backend-module-github/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-github/CHANGELOG.md @@ -1,5 +1,264 @@ # @backstage/plugin-scaffolder-backend-module-github +## 0.9.8 + +### Patch Changes + +- 482ceed: Migrated from `assertError` to `toError` for error handling. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/errors@1.3.0 + - @backstage/catalog-model@1.8.0 + - @backstage/plugin-catalog-node@2.2.0 + - @backstage/plugin-scaffolder-node@0.13.2 + - @backstage/integration@2.0.1 + - @backstage/config@1.3.7 + +## 0.9.8-next.2 + +### Patch Changes + +- 482ceed: Migrated from `assertError` to `toError` for error handling. +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/plugin-catalog-node@2.2.0-next.2 + - @backstage/plugin-scaffolder-node@0.13.2-next.2 + - @backstage/integration@2.0.1-next.0 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/catalog-model@1.7.8-next.0 + - @backstage/config@1.3.7-next.0 + +## 0.9.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + +## 0.9.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-catalog-node@2.1.1-next.0 + - @backstage/plugin-scaffolder-node@0.13.1-next.0 + - @backstage/catalog-model@1.7.7 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration@2.0.0 + - @backstage/types@1.2.2 + +## 0.9.7 + +### Patch Changes + +- a761a48: Added optional `returnWorkflowRunDetails` input to `github:actions:dispatch` action. When true, exposes `workflowRunId`, `workflowRunUrl`, and `workflowRunHtmlUrl` as outputs using the GitHub API `return_run_details` parameter. +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- b2591f6: Fixed environment `waitTime` description incorrectly asking for milliseconds instead of minutes. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-scaffolder-node@0.13.0 + - @backstage/integration@2.0.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/catalog-model@1.7.7 + +## 0.9.7-next.2 + +### Patch Changes + +- b2591f6: Fixed environment `waitTime` description incorrectly asking for milliseconds instead of minutes. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-catalog-node@2.1.0-next.2 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + +## 0.9.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.9.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + +## 0.9.6 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 82ca951: cleaned up repo creation to make the unique portions explicit +- 672b972: Updated dependency `libsodium-wrappers` to `^0.8.0`. + Updated dependency `@types/libsodium-wrappers` to `^0.8.0`. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-scaffolder-node@0.12.5 + +## 0.9.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.2 + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## 0.9.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## 0.9.5-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 82ca951: cleaned up repo creation to make the unique portions explicit +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.9.4 + +### Patch Changes + +- bb7088b: Added options to set [workflow access level][access-level] for repositories to `github:repo:create` + + This is useful when creating repositories for GitHub Actions to manage access + to the workflows during creation. + + ```diff + - action: github:repo:create + id: create-repo + input: + repoUrl: github.com?owner=owner&repo=repo + visibility: private + + workflowAccess: organization + ``` + + [access-level]: https://docs.github.com/en/rest/actions/permissions?apiVersion=2022-11-28#set-the-level-of-access-for-workflows-outside-of-the-repository + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + - @backstage/integration@1.19.2 + +## 0.9.4-next.1 + +### Patch Changes + +- bb7088b: Added options to set [workflow access level][access-level] for repositories to `github:repo:create` + + This is useful when creating repositories for GitHub Actions to manage access + to the workflows during creation. + + ```diff + - action: github:repo:create + id: create-repo + input: + repoUrl: github.com?owner=owner&repo=repo + visibility: private + + workflowAccess: organization + ``` + + [access-level]: https://docs.github.com/en/rest/actions/permissions?apiVersion=2022-11-28#set-the-level-of-access-for-workflows-outside-of-the-repository + +## 0.9.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + +## 0.9.3 + +### Patch Changes + +- dab3d3f: Added autocompletion resource `owners` for retrieving GitHub repository owners. +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.9.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## 0.9.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.9.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-scaffolder-node@0.12.1 + ## 0.9.2-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-github/knip-report.md b/plugins/scaffolder-backend-module-github/knip-report.md index 75e49c21a1..5313599439 100644 --- a/plugins/scaffolder-backend-module-github/knip-report.md +++ b/plugins/scaffolder-backend-module-github/knip-report.md @@ -1,8 +1,14 @@ # Knip report +## Unused devDependencies (1) + +| Name | Location | Severity | +| :------------------------ | :---------------- | :------- | +| @types/libsodium-wrappers | package.json:64:6 | error | + ## Unlisted dependencies (1) -| Name | Location | Severity | -| :------------ | :---------- | :------- | -| @octokit/core | plugins/scaffolder-backend-module-github/src/util.ts | error | +| Name | Location | Severity | +| :------------ | :---------------- | :------- | +| @octokit/core | src/util.ts:24:33 | error | diff --git a/plugins/scaffolder-backend-module-github/package.json b/plugins/scaffolder-backend-module-github/package.json index 7941c59e0e..f5a94fcea6 100644 --- a/plugins/scaffolder-backend-module-github/package.json +++ b/plugins/scaffolder-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-github", - "version": "0.9.2-next.1", + "version": "0.9.8", "description": "The github module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", @@ -50,18 +50,19 @@ "@backstage/plugin-catalog-node": "workspace:^", "@backstage/plugin-scaffolder-node": "workspace:^", "@backstage/types": "workspace:^", + "@octokit/core": "^5.0.0", "@octokit/webhooks": "^10.9.2", - "libsodium-wrappers": "^0.7.11", + "libsodium-wrappers": "^0.8.0", "octokit": "^3.0.0", "octokit-plugin-create-pull-request": "^5.0.0", "yaml": "^2.0.0", - "zod": "^3.22.4" + "zod": "^3.25.76 || ^4.0.0" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/plugin-scaffolder-node-test-utils": "workspace:^", - "@types/libsodium-wrappers": "^0.7.10", + "@types/libsodium-wrappers": "^0.8.0", "fs-extra": "^11.2.0", "jsonschema": "^1.2.6" } diff --git a/plugins/scaffolder-backend-module-github/report.api.md b/plugins/scaffolder-backend-module-github/report.api.md index ed109eb9e3..9e930a7866 100644 --- a/plugins/scaffolder-backend-module-github/report.api.md +++ b/plugins/scaffolder-backend-module-github/report.api.md @@ -24,10 +24,13 @@ export function createGithubActionsDispatchAction(options: { workflowId: string; branchOrTagName: string; workflowInputs?: Record | undefined; + returnWorkflowRunDetails?: boolean | undefined; token?: string | undefined; }, { - [x: string]: any; + workflowRunId?: number | undefined; + workflowRunUrl?: string | undefined; + workflowRunHtmlUrl?: string | undefined; }, 'v2' >; @@ -227,10 +230,15 @@ export function createGithubRepoCreateAction(options: { }): TemplateAction< { repoUrl: string; - description?: string | undefined; - homepage?: string | undefined; access?: string | undefined; - requireCodeOwnerReviews?: boolean | undefined; + allowAutoMerge?: boolean | undefined; + allowMergeCommit?: boolean | undefined; + allowRebaseMerge?: boolean | undefined; + allowSquashMerge?: boolean | undefined; + allowUpdateBranch?: boolean | undefined; + autoInit?: boolean | undefined; + blockCreations?: boolean | undefined; + branch?: string | undefined; bypassPullRequestAllowances?: | { apps?: string[] | undefined; @@ -238,30 +246,6 @@ export function createGithubRepoCreateAction(options: { users?: string[] | undefined; } | undefined; - requiredApprovingReviewCount?: number | undefined; - restrictions?: - | { - teams: string[]; - users: string[]; - apps?: string[] | undefined; - } - | undefined; - requiredStatusCheckContexts?: string[] | undefined; - requireBranchesToBeUpToDate?: boolean | undefined; - requiredConversationResolution?: boolean | undefined; - repoVisibility?: 'internal' | 'private' | 'public' | undefined; - deleteBranchOnMerge?: boolean | undefined; - allowMergeCommit?: boolean | undefined; - allowSquashMerge?: boolean | undefined; - squashMergeCommitTitle?: 'PR_TITLE' | 'COMMIT_OR_PR_TITLE' | undefined; - squashMergeCommitMessage?: - | 'PR_BODY' - | 'COMMIT_MESSAGES' - | 'BLANK' - | undefined; - allowRebaseMerge?: boolean | undefined; - allowAutoMerge?: boolean | undefined; - allowUpdateBranch?: boolean | undefined; collaborators?: | ( | { @@ -274,24 +258,55 @@ export function createGithubRepoCreateAction(options: { } )[] | undefined; + customProperties?: Record | undefined; + defaultBranch?: string | undefined; + deleteBranchOnMerge?: boolean | undefined; + description?: string | undefined; + dismissStaleReviews?: boolean | undefined; + gitAuthorEmail?: string | undefined; + gitAuthorName?: string | undefined; + gitCommitMessage?: string | undefined; + hasIssues?: boolean | undefined; hasProjects?: boolean | undefined; hasWiki?: boolean | undefined; - hasIssues?: boolean | undefined; - token?: string | undefined; - topics?: string[] | undefined; - repoVariables?: Record | undefined; - secrets?: Record | undefined; + homepage?: string | undefined; oidcCustomization?: | { useDefault: boolean; includeClaimKeys?: string[] | undefined; } | undefined; + protectDefaultBranch?: boolean | undefined; + protectEnforceAdmins?: boolean | undefined; + repoVariables?: Record | undefined; + repoVisibility?: 'internal' | 'private' | 'public' | undefined; + requireBranchesToBeUpToDate?: boolean | undefined; + requireCodeOwnerReviews?: boolean | undefined; + requiredApprovingReviewCount?: number | undefined; requiredCommitSigning?: boolean | undefined; + requiredConversationResolution?: boolean | undefined; requiredLinearHistory?: boolean | undefined; - customProperties?: Record | undefined; + requiredStatusCheckContexts?: string[] | undefined; + requireLastPushApproval?: boolean | undefined; + restrictions?: + | { + teams: string[]; + users: string[]; + apps?: string[] | undefined; + } + | undefined; + secrets?: Record | undefined; + sourcePath?: string | undefined; + squashMergeCommitMessage?: + | 'PR_BODY' + | 'COMMIT_MESSAGES' + | 'BLANK' + | undefined; + squashMergeCommitTitle?: 'PR_TITLE' | 'COMMIT_OR_PR_TITLE' | undefined; subscribe?: boolean | undefined; - autoInit?: boolean | undefined; + token?: string | undefined; + topics?: string[] | undefined; + workflowAccess?: 'none' | 'organization' | 'user' | undefined; }, { remoteUrl: string; diff --git a/plugins/scaffolder-backend-module-github/src/actions/gitHelpers.ts b/plugins/scaffolder-backend-module-github/src/actions/gitHelpers.ts index d7ef3496dd..ad1b188348 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/gitHelpers.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/gitHelpers.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { assertError } from '@backstage/errors'; +import { toError } from '@backstage/errors'; import { Octokit } from 'octokit'; import { LoggerService } from '@backstage/backend-plugin-api'; @@ -109,9 +109,9 @@ export const enableBranchProtectionOnDefaultRepoBranch = async ({ }); } } catch (e) { - assertError(e); + const error = toError(e); if ( - e.message.includes( + error.message.includes( 'Upgrade to GitHub Pro or make this repository public to enable this feature', ) ) { @@ -119,7 +119,7 @@ export const enableBranchProtectionOnDefaultRepoBranch = async ({ 'Branch protection was not enabled as it requires GitHub Pro for private repositories', ); } else { - throw e; + throw error; } } }; diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.test.ts index 5583b745d5..077af89c2a 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.test.ts @@ -124,4 +124,43 @@ describe('github:actions:dispatch', () => { inputs: workflowInputs, }); }); + + it('should call createWorkflowDispatch with return_run_details when using the returnWorkflowRunDetails example', async () => { + mockOctokit.rest.actions.createWorkflowDispatch.mockResolvedValue({ + data: { + workflow_run_id: 123, + run_url: 'https://api.github.com/repos/owner/repo/actions/runs/123', + html_url: 'https://github.com/owner/repo/actions/runs/123', + }, + }); + + const exampleInput = yaml.parse(examples[3].example).steps[0].input; + const ctx = Object.assign({}, mockContext, { + input: { + ...exampleInput, + repoUrl: 'github.com?repo=repo&owner=owner', + }, + }); + await action.handler(ctx); + + expect( + mockOctokit.rest.actions.createWorkflowDispatch, + ).toHaveBeenCalledWith( + expect.objectContaining({ + owner: 'owner', + repo: 'repo', + return_run_details: true, + }), + ); + + expect(ctx.output).toHaveBeenCalledWith('workflowRunId', 123); + expect(ctx.output).toHaveBeenCalledWith( + 'workflowRunUrl', + 'https://api.github.com/repos/owner/repo/actions/runs/123', + ); + expect(ctx.output).toHaveBeenCalledWith( + 'workflowRunHtmlUrl', + 'https://github.com/owner/repo/actions/runs/123', + ); + }); }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.ts b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.ts index 33ad86dd89..2b8baafdbe 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.ts @@ -71,4 +71,21 @@ export const examples: TemplateExample[] = [ ], }), }, + { + description: 'GitHub Action Workflow returning run details', + example: yaml.stringify({ + steps: [ + { + action: 'github:actions:dispatch', + name: 'Dispatch GitHub Action Workflow and get run ID', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + workflowId: 'WORKFLOW_ID', + branchOrTagName: 'main', + returnWorkflowRunDetails: true, + }, + }, + ], + }), + }, ]; diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.test.ts index 94a51e5b4e..fc8cfcce0b 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.test.ts @@ -130,4 +130,64 @@ describe('github:actions:dispatch', () => { inputs: workflowInputs, }); }); + + it('should call createWorkflowDispatch with return_run_details when returnWorkflowRunDetails is true', async () => { + mockOctokit.rest.actions.createWorkflowDispatch.mockResolvedValue({ + data: { + workflow_run_id: 123, + run_url: 'https://api.github.com/repos/owner/repo/actions/runs/123', + html_url: 'https://github.com/owner/repo/actions/runs/123', + }, + }); + + const ctx = Object.assign({}, mockContext, { + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + workflowId: 'dispatch_workflow', + branchOrTagName: 'main', + returnWorkflowRunDetails: true, + }, + }); + await action.handler(ctx); + + expect( + mockOctokit.rest.actions.createWorkflowDispatch, + ).toHaveBeenCalledWith( + expect.objectContaining({ + owner: 'owner', + repo: 'repo', + workflow_id: 'dispatch_workflow', + ref: 'main', + return_run_details: true, + }), + ); + + expect(ctx.output).toHaveBeenCalledWith('workflowRunId', 123); + expect(ctx.output).toHaveBeenCalledWith( + 'workflowRunUrl', + 'https://api.github.com/repos/owner/repo/actions/runs/123', + ); + expect(ctx.output).toHaveBeenCalledWith( + 'workflowRunHtmlUrl', + 'https://github.com/owner/repo/actions/runs/123', + ); + }); + + it('should not set outputs when returnWorkflowRunDetails is false', async () => { + mockOctokit.rest.actions.createWorkflowDispatch.mockResolvedValue({ + data: undefined, + }); + + const ctx = Object.assign({}, mockContext, { + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + workflowId: 'dispatch_workflow', + branchOrTagName: 'main', + returnWorkflowRunDetails: false, + }, + }); + await action.handler(ctx); + + expect(ctx.output).not.toHaveBeenCalled(); + }); }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts index f35cbcc04c..5fd97c30d0 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { InputError } from '@backstage/errors'; +import { InputError, toError } from '@backstage/errors'; import { GithubCredentialsProvider, ScmIntegrations, @@ -62,7 +62,14 @@ export function createGithubActionsDispatchAction(options: { z .record(z.string(), { description: - 'Inputs keys and values to send to GitHub Action configured on the workflow file. The maximum number of properties is 10.', + 'Inputs keys and values to send to GitHub Action configured on the workflow file. The maximum number of properties is 25.', + }) + .optional(), + returnWorkflowRunDetails: z => + z + .boolean({ + description: + 'If true, returns the workflow run ID and URLs as action outputs.', }) .optional(), token: z => @@ -73,6 +80,14 @@ export function createGithubActionsDispatchAction(options: { }) .optional(), }, + output: { + workflowRunId: z => + z.number({ description: 'The triggered workflow run ID' }).optional(), + workflowRunUrl: z => + z.string({ description: 'API URL of the workflow run' }).optional(), + workflowRunHtmlUrl: z => + z.string({ description: 'HTML URL of the workflow run' }).optional(), + }, }, async handler(ctx) { const { @@ -80,6 +95,7 @@ export function createGithubActionsDispatchAction(options: { workflowId, branchOrTagName, workflowInputs, + returnWorkflowRunDetails, token: providedToken, } = ctx.input; @@ -106,20 +122,65 @@ export function createGithubActionsDispatchAction(options: { log: ctx.logger, }); - 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, - }); + try { + const runDetails = await ctx.checkpoint({ + key: `create.workflow.dispatch.${owner}.${repo}.${workflowId}`, + fn: async () => { + const dispatchParams = { + owner, + repo, + workflow_id: workflowId, + ref: branchOrTagName, + inputs: workflowInputs, + ...(returnWorkflowRunDetails ? { return_run_details: true } : {}), + }; + const response = await client.rest.actions.createWorkflowDispatch( + dispatchParams, + ); - ctx.logger.info(`Workflow ${workflowId} dispatched successfully`); - }, - }); + ctx.logger.info(`Workflow ${workflowId} dispatched successfully`); + + if (returnWorkflowRunDetails && response.data) { + // GitHub's API returns 200 with run details when return_run_details is true. + // @octokit/openapi-types still types this as OctokitResponse because + // it hasn't picked up the updated GitHub OpenAPI spec yet. + // See: https://github.blog/changelog/2026-02-19-workflow-dispatch-api-now-returns-run-ids/ + // This cast can be removed once @octokit/openapi-types includes the updated spec. + // Note: only supported on GitHub.com and GitHub Enterprise Cloud, not GitHub Enterprise Server + const data = response.data as unknown as { + workflow_run_id: number; + run_url: string; + html_url: string; + }; + + return { + workflowRunId: data.workflow_run_id, + workflowRunUrl: data.run_url, + workflowRunHtmlUrl: data.html_url, + }; + } + return null; + }, + }); + + if (runDetails) { + ctx.output('workflowRunId', runDetails.workflowRunId); + ctx.output('workflowRunUrl', runDetails.workflowRunUrl); + ctx.output('workflowRunHtmlUrl', runDetails.workflowRunHtmlUrl); + + if (runDetails.workflowRunHtmlUrl) { + ctx.logger.info( + `Workflow run url: ${runDetails.workflowRunHtmlUrl}`, + ); + } + } + } catch (e) { + const error = toError(e); + ctx.logger.warn( + `Failed: dispatching workflow '${workflowId}' on repo: '${repo}', ${error.message}`, + ); + throw error; + } }, }); } diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.ts b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.ts index 4a36be3b5b..7e2c880b5d 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.ts @@ -111,7 +111,7 @@ Wildcard characters will not match \`/\`. For example, to match tags that begin z .number({ description: - 'The time to wait before creating or updating the environment (in milliseconds)', + 'The time to wait before creating or updating the environment (in minutes)', }) .optional(), preventSelfReview: z => diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubIssuesCreate.ts b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesCreate.ts index 5c5703e20d..1dd845277d 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubIssuesCreate.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesCreate.ts @@ -22,7 +22,7 @@ import { createTemplateAction, parseRepoUrl, } from '@backstage/plugin-scaffolder-node'; -import { assertError, InputError } from '@backstage/errors'; +import { InputError, toError } from '@backstage/errors'; import { Octokit } from 'octokit'; import { getOctokitOptions } from '../util'; import { examples } from './githubIssuesCreate.examples'; @@ -171,11 +171,11 @@ export function createGithubIssuesCreateAction(options: { `Successfully created issue #${issue.number}: ${issue.html_url}`, ); } catch (e) { - assertError(e); + const error = toError(e); ctx.logger.warn( - `Failed: creating issue '${title}' on repo: '${repo}', ${e.message}`, + `Failed: creating issue '${title}' on repo: '${repo}', ${error.message}`, ); - throw e; + throw error; } }, }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.ts b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.ts index bbda5a1839..52c8eab484 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.ts @@ -22,7 +22,7 @@ import { createTemplateAction, parseRepoUrl, } from '@backstage/plugin-scaffolder-node'; -import { assertError, InputError } from '@backstage/errors'; +import { InputError, toError } from '@backstage/errors'; import { Octokit } from 'octokit'; import { getOctokitOptions } from '../util'; import { examples } from './githubIssuesLabel.examples'; @@ -101,9 +101,10 @@ export function createGithubIssuesLabelAction(options: { }, }); } catch (e) { - assertError(e); ctx.logger.warn( - `Failed: adding labels to issue: '${number}' on repo: '${repo}', ${e.message}`, + `Failed: adding labels to issue: '${number}' on repo: '${repo}', ${ + toError(e).message + }`, ); } }, diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts index 5a8019579b..f9679a928c 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts @@ -606,28 +606,13 @@ describe('createPublishGithubPullRequestAction', () => { ctx = createMockActionContext({ input, workspacePath }); }); - it('creates a pull request', async () => { - await instance.handler(ctx); - expect(fakeClient.createPullRequest).toHaveBeenCalledWith({ - owner: 'myorg', - repo: 'myrepo', - title: 'Create my new app', - head: 'new-app', - body: 'This PR is really good', - changes: [ - { - commit: 'Create my new app', - files: { - Makefile: { - content: Buffer.from('../../nothing/yet').toString('utf-8'), - encoding: 'utf-8', - mode: '120000', - }, - }, - }, - ], - }); + it('throws an error', async () => { + await expect( + instance.handler(ctx), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Relative path is not allowed to refer to a directory outside its parent"`, + ); }); }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts index 7d1e88d0c7..df65f8f010 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import path from 'path'; +import path from 'node:path'; import { GithubCredentialsProvider, ScmIntegrationRegistry, diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.test.ts index c37e9c222b..e2104736f7 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.test.ts @@ -56,6 +56,7 @@ const mockOctokit = { createRepoVariable: jest.fn(), createOrUpdateRepoSecret: jest.fn(), getRepoPublicKey: jest.fn(), + setWorkflowAccessToRepository: jest.fn(), }, }, request: jest.fn().mockResolvedValue({}), @@ -130,13 +131,7 @@ describe('github:repo:create examples', () => { allow_rebase_merge: true, allow_auto_merge: false, allow_update_branch: false, - custom_properties: undefined, - has_issues: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, visibility: 'private', - auto_init: undefined, }); }); @@ -165,12 +160,7 @@ describe('github:repo:create examples', () => { allow_rebase_merge: true, allow_auto_merge: false, allow_update_branch: false, - has_issues: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, visibility: 'private', - auto_init: undefined, }); }); @@ -190,7 +180,6 @@ describe('github:repo:create examples', () => { name: 'repo', org: 'owner', private: true, - description: undefined, delete_branch_on_merge: false, allow_squash_merge: true, squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', @@ -199,13 +188,9 @@ describe('github:repo:create examples', () => { allow_rebase_merge: true, allow_auto_merge: false, allow_update_branch: false, - custom_properties: undefined, visibility: 'private', has_issues: false, // disable issues - has_projects: undefined, has_wiki: false, // disable wiki - homepage: undefined, - auto_init: undefined, }); }); @@ -239,17 +224,11 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, homepage: 'https://example.com', - auto_init: undefined, }); }); @@ -283,17 +262,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -327,17 +299,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -371,17 +336,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -415,17 +373,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: false, allow_rebase_merge: false, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -459,17 +410,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'pull_request_title', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -503,17 +447,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'blank', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -547,17 +484,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: true, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -591,17 +521,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -635,17 +558,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -679,17 +595,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -723,17 +632,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -767,17 +669,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -811,17 +706,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -855,17 +743,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -899,17 +780,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -943,17 +817,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -987,17 +854,12 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'internal', - has_issues: undefined, description: 'A repository for project XYZ', - has_projects: undefined, - has_wiki: undefined, homepage: 'https://project-xyz.com', - auto_init: undefined, }); }); @@ -1031,17 +893,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -1075,17 +930,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -1119,17 +967,11 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, has_projects: true, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -1163,17 +1005,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: false, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -1207,17 +1042,12 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'internal', has_issues: false, - description: undefined, has_projects: false, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -1251,17 +1081,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -1295,17 +1118,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -1339,17 +1155,11 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, description: 'Repository for project ABC', - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -1383,17 +1193,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'public', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -1427,17 +1230,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -1471,17 +1267,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -1515,17 +1304,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -1559,17 +1341,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -1603,17 +1378,12 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'internal', - has_issues: undefined, description: 'Internal repository for team collaboration', - has_projects: undefined, - has_wiki: undefined, homepage: 'https://internal.example.com', - auto_init: undefined, }); }); @@ -1647,17 +1417,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -1691,17 +1454,11 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, has_projects: true, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -1735,17 +1492,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -1779,17 +1529,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -1823,17 +1566,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'internal', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -1867,17 +1603,12 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, description: 'Repository for web application project', - has_projects: undefined, - has_wiki: undefined, homepage: 'https://webapp.example.com', - auto_init: undefined, }); }); @@ -1911,17 +1642,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'pull_request_description', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: false, allow_rebase_merge: false, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -1955,17 +1679,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: false, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -1999,17 +1716,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -2043,17 +1753,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -2087,17 +1790,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'public', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -2131,17 +1827,13 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', has_issues: false, description: 'Repository for backend service', - has_projects: undefined, has_wiki: false, - homepage: undefined, - auto_init: undefined, }); }); @@ -2175,17 +1867,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -2219,17 +1904,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -2263,17 +1941,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -2307,17 +1978,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: false, allow_rebase_merge: true, allow_auto_merge: true, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -2351,17 +2015,11 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, homepage: 'https://example.com', - auto_init: undefined, }); }); @@ -2395,17 +2053,11 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, description: 'Repository for microservice development', - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -2439,17 +2091,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -2483,17 +2128,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -2527,17 +2165,10 @@ describe('github:repo:create examples', () => { squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_update_branch: false, - custom_properties: undefined, allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -2569,19 +2200,12 @@ describe('github:repo:create examples', () => { delete_branch_on_merge: false, allow_squash_merge: true, allow_update_branch: false, - custom_properties: undefined, squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); }); @@ -2613,19 +2237,43 @@ describe('github:repo:create examples', () => { delete_branch_on_merge: false, allow_squash_merge: true, allow_update_branch: false, - custom_properties: undefined, squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', squash_merge_commit_message: 'COMMIT_MESSAGES', allow_merge_commit: true, allow_rebase_merge: true, allow_auto_merge: false, visibility: 'private', - has_issues: undefined, - description: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, auto_init: true, }); }); + + it(`Should ${examples[59].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[59].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect( + mockOctokit.rest.actions.setWorkflowAccessToRepository, + ).toHaveBeenCalledWith({ + access_level: input.workflowAccess, + owner: 'owner', + repo: 'repo', + }); + }); }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.ts index 41c6a15fba..698a85779e 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.ts @@ -1021,4 +1021,19 @@ export const examples: TemplateExample[] = [ ], }), }, + { + description: 'Allow workflow access for organization.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository that allows org-wide access to its workflows', + input: { + repoUrl: 'github.com?owner=owner&repo=repo', + workflowAccess: 'organization', + }, + }, + ], + }), + }, ]; diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts index adad5237da..56159e207a 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts @@ -57,6 +57,7 @@ const mockOctokit = { createRepoVariable: jest.fn(), createOrUpdateRepoSecret: jest.fn(), getRepoPublicKey: jest.fn(), + setWorkflowAccessToRepository: jest.fn(), }, activity: { setRepoSubscription: jest.fn(), @@ -152,12 +153,7 @@ describe('github:repo:create', () => { allow_rebase_merge: true, allow_auto_merge: false, allow_update_branch: false, - has_issues: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, visibility: 'private', - auto_init: undefined, }); await action.handler({ @@ -180,13 +176,7 @@ describe('github:repo:create', () => { allow_rebase_merge: true, allow_auto_merge: false, allow_update_branch: false, - custom_properties: undefined, - has_issues: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, visibility: 'public', - auto_init: undefined, }); await action.handler({ @@ -210,11 +200,7 @@ describe('github:repo:create', () => { allow_rebase_merge: true, allow_auto_merge: false, allow_update_branch: false, - has_issues: undefined, - has_projects: undefined, - has_wiki: undefined, visibility: 'private', - auto_init: undefined, }); await action.handler({ @@ -239,13 +225,8 @@ describe('github:repo:create', () => { allow_rebase_merge: true, allow_auto_merge: false, allow_update_branch: false, - custom_properties: undefined, visibility: 'private', - has_wiki: undefined, - has_projects: undefined, - has_issues: undefined, homepage: 'https://example.com', - auto_init: undefined, }); await action.handler({ @@ -270,12 +251,7 @@ describe('github:repo:create', () => { allow_rebase_merge: true, allow_auto_merge: false, allow_update_branch: false, - custom_properties: undefined, visibility: 'private', - has_wiki: undefined, - has_projects: undefined, - has_issues: undefined, - auto_init: undefined, }); await action.handler({ @@ -302,13 +278,8 @@ describe('github:repo:create', () => { allow_rebase_merge: true, allow_auto_merge: false, allow_update_branch: false, - custom_properties: undefined, - has_issues: undefined, - has_projects: undefined, - has_wiki: undefined, homepage: 'https://example.com', visibility: 'private', - auto_init: undefined, }); await action.handler({ @@ -332,11 +303,6 @@ describe('github:repo:create', () => { allow_rebase_merge: true, allow_auto_merge: false, allow_update_branch: false, - custom_properties: undefined, - has_issues: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, visibility: 'private', auto_init: true, }); @@ -362,11 +328,6 @@ describe('github:repo:create', () => { allow_rebase_merge: true, allow_auto_merge: false, allow_update_branch: false, - custom_properties: undefined, - has_issues: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, visibility: 'private', auto_init: false, }); @@ -396,11 +357,6 @@ describe('github:repo:create', () => { allow_rebase_merge: true, allow_auto_merge: false, allow_update_branch: false, - has_issues: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); await action.handler({ @@ -424,11 +380,6 @@ describe('github:repo:create', () => { allow_rebase_merge: true, allow_auto_merge: false, allow_update_branch: false, - has_issues: undefined, - has_projects: undefined, - has_wiki: undefined, - homepage: undefined, - auto_init: undefined, }); await action.handler({ @@ -453,10 +404,6 @@ describe('github:repo:create', () => { allow_rebase_merge: true, allow_auto_merge: false, allow_update_branch: false, - has_issues: undefined, - has_projects: undefined, - has_wiki: undefined, - auto_init: undefined, }); await action.handler({ @@ -482,11 +429,7 @@ describe('github:repo:create', () => { allow_rebase_merge: true, allow_auto_merge: false, allow_update_branch: false, - has_wiki: undefined, - has_projects: undefined, - has_issues: undefined, homepage: 'https://example.com', - auto_init: undefined, }); await action.handler({ @@ -512,11 +455,7 @@ describe('github:repo:create', () => { allow_rebase_merge: true, allow_auto_merge: false, allow_update_branch: false, - has_wiki: undefined, - has_projects: undefined, - has_issues: undefined, homepage: 'https://example.com', - auto_init: undefined, }); // Custom properties on user repos should be ignored @@ -545,11 +484,7 @@ describe('github:repo:create', () => { allow_rebase_merge: true, allow_auto_merge: false, allow_update_branch: false, - has_issues: undefined, - has_projects: undefined, - has_wiki: undefined, homepage: 'https://example.com', - auto_init: undefined, }); await action.handler({ @@ -573,10 +508,6 @@ describe('github:repo:create', () => { allow_rebase_merge: true, allow_auto_merge: false, allow_update_branch: false, - has_wiki: undefined, - has_projects: undefined, - has_issues: undefined, - homepage: undefined, auto_init: true, }); @@ -601,10 +532,6 @@ describe('github:repo:create', () => { allow_rebase_merge: true, allow_auto_merge: false, allow_update_branch: false, - has_wiki: undefined, - has_projects: undefined, - has_issues: undefined, - homepage: undefined, auto_init: false, }); }); @@ -966,4 +893,30 @@ describe('github:repo:create', () => { ignored: false, }); }); + + it.each(['none', 'organization', 'user'])( + 'should set workflow access level - %s', + async expected => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + workflowAccess: expected, + }, + }); + + expect( + mockOctokit.rest.actions.setWorkflowAccessToRepository, + ).toHaveBeenCalledWith({ + access_level: expected, + owner: 'owner', + repo: 'repo', + }); + }, + ); }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts index 952bf514d4..6278d2537e 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts @@ -47,41 +47,7 @@ export function createGithubRepoCreateAction(options: { examples, schema: { input: { - repoUrl: inputProps.repoUrl, - description: inputProps.description, - homepage: inputProps.homepage, - access: inputProps.access, - requireCodeOwnerReviews: inputProps.requireCodeOwnerReviews, - bypassPullRequestAllowances: inputProps.bypassPullRequestAllowances, - requiredApprovingReviewCount: inputProps.requiredApprovingReviewCount, - restrictions: inputProps.restrictions, - requiredStatusCheckContexts: inputProps.requiredStatusCheckContexts, - requireBranchesToBeUpToDate: inputProps.requireBranchesToBeUpToDate, - requiredConversationResolution: - inputProps.requiredConversationResolution, - repoVisibility: inputProps.repoVisibility, - deleteBranchOnMerge: inputProps.deleteBranchOnMerge, - allowMergeCommit: inputProps.allowMergeCommit, - allowSquashMerge: inputProps.allowSquashMerge, - squashMergeCommitTitle: inputProps.squashMergeCommitTitle, - squashMergeCommitMessage: inputProps.squashMergeCommitMessage, - allowRebaseMerge: inputProps.allowRebaseMerge, - allowAutoMerge: inputProps.allowAutoMerge, - allowUpdateBranch: inputProps.allowUpdateBranch, - collaborators: inputProps.collaborators, - hasProjects: inputProps.hasProjects, - hasWiki: inputProps.hasWiki, - hasIssues: inputProps.hasIssues, - token: inputProps.token, - topics: inputProps.topics, - repoVariables: inputProps.repoVariables, - secrets: inputProps.secrets, - oidcCustomization: inputProps.oidcCustomization, - requiredCommitSigning: inputProps.requiredCommitSigning, - requiredLinearHistory: inputProps.requiredLinearHistory, - customProperties: inputProps.customProperties, - subscribe: inputProps.subscribe, - autoInit: inputProps.autoInit, + ...inputProps, }, output: { remoteUrl: outputProps.remoteUrl, @@ -115,6 +81,7 @@ export function createGithubRepoCreateAction(options: { subscribe, token: providedToken, autoInit = undefined, + workflowAccess, } = ctx.input; const { host, owner, repo } = parseRepoUrl(repoUrl, integrations); @@ -167,6 +134,7 @@ export function createGithubRepoCreateAction(options: { subscribe, ctx.logger, autoInit, + workflowAccess, ); return newRepo.clone_url; }, diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts index 3faab03f13..61010f8bf8 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts @@ -23,7 +23,7 @@ import { parseRepoUrl, } from '@backstage/plugin-scaffolder-node'; import { emitterEventNames } from '@octokit/webhooks'; -import { assertError, InputError } from '@backstage/errors'; +import { InputError, toError } from '@backstage/errors'; import { Octokit } from 'octokit'; import { getOctokitOptions } from '../util'; import { examples } from './githubWebhook.examples'; @@ -173,9 +173,10 @@ export function createGithubWebhookAction(options: { ctx.logger.info(`Webhook '${webhookUrl}' created successfully`); } catch (e) { - assertError(e); ctx.logger.warn( - `Failed: create webhook '${webhookUrl}' on repo: '${repo}', ${e.message}`, + `Failed: create webhook '${webhookUrl}' on repo: '${repo}', ${ + toError(e).message + }`, ); } }, diff --git a/plugins/scaffolder-backend-module-github/src/actions/helper.test.ts b/plugins/scaffolder-backend-module-github/src/actions/helpers.test.ts similarity index 100% rename from plugins/scaffolder-backend-module-github/src/actions/helper.test.ts rename to plugins/scaffolder-backend-module-github/src/actions/helpers.test.ts diff --git a/plugins/scaffolder-backend-module-github/src/actions/helpers.ts b/plugins/scaffolder-backend-module-github/src/actions/helpers.ts index a3d0caed33..d0ef19a1f2 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/helpers.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/helpers.ts @@ -15,7 +15,7 @@ */ import { Config } from '@backstage/config'; -import { assertError, NotFoundError } from '@backstage/errors'; +import { NotFoundError, toError } from '@backstage/errors'; import { Octokit } from 'octokit'; import { @@ -79,8 +79,8 @@ export async function createGithubRepoWithCollaboratorsAndTopics( subscribe: boolean | undefined, logger: LoggerService, autoInit?: boolean | undefined, + workflowAccess?: 'none' | 'organization' | 'user', ) { - // eslint-disable-next-line testing-library/no-await-sync-queries const user = await client.rest.users.getByUsername({ username: owner, }); @@ -89,63 +89,50 @@ export async function createGithubRepoWithCollaboratorsAndTopics( await validateAccessTeam(client, access); } + const baseRepoParams = { + allow_auto_merge: allowAutoMerge, + allow_merge_commit: allowMergeCommit, + allow_rebase_merge: allowRebaseMerge, + allow_squash_merge: allowSquashMerge, + allow_update_branch: allowUpdateBranch, + auto_init: autoInit, + delete_branch_on_merge: deleteBranchOnMerge, + description, + has_issues: hasIssues, + has_projects: hasProjects, + has_wiki: hasWiki, + homepage, + name: repo, + private: repoVisibility === 'private', + squash_merge_commit_message: squashMergeCommitMessage, + squash_merge_commit_title: squashMergeCommitTitle, + }; const repoCreationPromise = user.data.type === 'Organization' ? client.rest.repos.createInOrg({ - name: repo, - org: owner, - private: repoVisibility === 'private', - // @ts-ignore https://github.com/octokit/types.ts/issues/522 - visibility: repoVisibility, - description: description, - delete_branch_on_merge: deleteBranchOnMerge, - allow_merge_commit: allowMergeCommit, - allow_squash_merge: allowSquashMerge, - squash_merge_commit_title: squashMergeCommitTitle, - squash_merge_commit_message: squashMergeCommitMessage, - allow_rebase_merge: allowRebaseMerge, - allow_auto_merge: allowAutoMerge, - allow_update_branch: allowUpdateBranch, - homepage: homepage, - has_projects: hasProjects, - has_wiki: hasWiki, - has_issues: hasIssues, - auto_init: autoInit, + ...baseRepoParams, // Custom properties only available on org repos custom_properties: customProperties, + org: owner, + // @ts-ignore https://github.com/octokit/types.ts/issues/522 + visibility: repoVisibility, }) - : client.rest.repos.createForAuthenticatedUser({ - name: repo, - private: repoVisibility === 'private', - description: description, - delete_branch_on_merge: deleteBranchOnMerge, - allow_merge_commit: allowMergeCommit, - allow_squash_merge: allowSquashMerge, - squash_merge_commit_title: squashMergeCommitTitle, - squash_merge_commit_message: squashMergeCommitMessage, - allow_rebase_merge: allowRebaseMerge, - allow_auto_merge: allowAutoMerge, - allow_update_branch: allowUpdateBranch, - homepage: homepage, - has_projects: hasProjects, - has_wiki: hasWiki, - has_issues: hasIssues, - auto_init: autoInit, - }); + : client.rest.repos.createForAuthenticatedUser(baseRepoParams); let newRepo; try { newRepo = (await repoCreationPromise).data; } catch (e) { - assertError(e); - if (e.message === 'Resource not accessible by integration') { + if (toError(e).message === 'Resource not accessible by integration') { logger.warn( `The GitHub app or token provided may not have the required permissions to create the ${user.data.type} repository ${owner}/${repo}.`, ); } throw new Error( - `Failed to create the ${user.data.type} repository ${owner}/${repo}, ${e.message}`, + `Failed to create the ${user.data.type} repository ${owner}/${repo}, ${ + toError(e).message + }`, ); } @@ -188,10 +175,11 @@ export async function createGithubRepoWithCollaboratorsAndTopics( }); } } catch (e) { - assertError(e); const name = extractCollaboratorName(collaborator); logger.warn( - `Skipping ${collaborator.access} access for ${name}, ${e.message}`, + `Skipping ${collaborator.access} access for ${name}, ${ + toError(e).message + }`, ); } } @@ -205,8 +193,7 @@ export async function createGithubRepoWithCollaboratorsAndTopics( names: topics.map(t => t.toLowerCase()), }); } catch (e) { - assertError(e); - logger.warn(`Skipping topics ${topics.join(' ')}, ${e.message}`); + logger.warn(`Skipping topics ${topics.join(' ')}, ${toError(e).message}`); } } @@ -272,6 +259,14 @@ export async function createGithubRepoWithCollaboratorsAndTopics( }); } + if (workflowAccess) { + await client.rest.actions.setWorkflowAccessToRepository({ + access_level: workflowAccess, + owner, + repo, + }); + } + return newRepo; } @@ -282,7 +277,7 @@ export async function initRepoPushAndProtect( sourcePath: string | undefined, defaultBranch: string, protectDefaultBranch: boolean, - protectEnforceAdmins: boolean, + enforceAdmins: boolean, owner: string, client: Octokit, repo: string, @@ -356,15 +351,16 @@ export async function initRepoPushAndProtect( requireBranchesToBeUpToDate, requiredConversationResolution, requireLastPushApproval, - enforceAdmins: protectEnforceAdmins, - dismissStaleReviews: dismissStaleReviews, - requiredCommitSigning: requiredCommitSigning, - requiredLinearHistory: requiredLinearHistory, + enforceAdmins, + dismissStaleReviews, + requiredCommitSigning, + requiredLinearHistory, }); } catch (e) { - assertError(e); logger.warn( - `Skipping: default branch protection on '${repo}', ${e.message}`, + `Skipping: default branch protection on '${repo}', ${ + toError(e).message + }`, ); } } @@ -375,8 +371,12 @@ export async function initRepoPushAndProtect( function extractCollaboratorName( collaborator: { user: string } | { team: string } | { username: string }, ) { - if ('username' in collaborator) return collaborator.username; - if ('user' in collaborator) return collaborator.user; + if ('username' in collaborator) { + return collaborator.username; + } + if ('user' in collaborator) { + return collaborator.user; + } return collaborator.team; } diff --git a/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts b/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts index 8a901b0e99..a2e0ae0bf3 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { z as zod } from 'zod'; +import { z as zod } from 'zod/v3'; const repoUrl = (z: typeof zod) => z.string({ @@ -421,50 +421,59 @@ const autoInit = (z: typeof zod) => .default(false) .optional(); +const workflowAccess = (z: typeof zod) => + z + .enum(['none', 'organization', 'user'], { + description: + 'Level of access for workflows outside of the repository. Default is "none".', + }) + .optional(); + export { - repoUrl, - description, - homepage, access, - requireCodeOwnerReviews, - dismissStaleReviews, - requiredStatusCheckContexts, - requireBranchesToBeUpToDate, - requiredConversationResolution, - requireLastPushApproval, - repoVisibility, - deleteBranchOnMerge, - gitAuthorName, - gitAuthorEmail, + allowAutoMerge, allowMergeCommit, + allowRebaseMerge, allowSquashMerge, allowUpdateBranch, - squashMergeCommitTitle, - squashMergeCommitMessage, - allowRebaseMerge, - allowAutoMerge, + autoInit, + blockCreations, + branch, + bypassPullRequestAllowances, collaborators, + customProperties, + defaultBranch, + deleteBranchOnMerge, + description, + dismissStaleReviews, + gitAuthorEmail, + gitAuthorName, + gitCommitMessage, + hasIssues, hasProjects, hasWiki, - hasIssues, - token, - topics, - defaultBranch, - gitCommitMessage, - sourcePath, - repoVariables, - secrets, + homepage, oidcCustomization, - customProperties, - subscribe, - requiredApprovingReviewCount, - restrictions, - requiredCommitSigning, - requiredLinearHistory, protectDefaultBranch, protectEnforceAdmins, - bypassPullRequestAllowances, - branch, - blockCreations, - autoInit, + repoUrl, + repoVariables, + repoVisibility, + requireBranchesToBeUpToDate, + requireCodeOwnerReviews, + requiredApprovingReviewCount, + requiredCommitSigning, + requiredConversationResolution, + requiredLinearHistory, + requiredStatusCheckContexts, + requireLastPushApproval, + restrictions, + secrets, + sourcePath, + squashMergeCommitMessage, + squashMergeCommitTitle, + subscribe, + token, + topics, + workflowAccess, }; diff --git a/plugins/scaffolder-backend-module-github/src/actions/outputProperties.ts b/plugins/scaffolder-backend-module-github/src/actions/outputProperties.ts index a1ee53fec5..50a727be88 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/outputProperties.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/outputProperties.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { z as zod } from 'zod'; +import { z as zod } from 'zod/v3'; const remoteUrl = (z: typeof zod) => z.string({ @@ -31,6 +31,4 @@ const commitHash = (z: typeof zod) => description: 'The git commit hash of the initial commit', }); -export { remoteUrl }; -export { repoContentsUrl }; -export { commitHash }; +export { commitHash, remoteUrl, repoContentsUrl }; diff --git a/plugins/scaffolder-backend-module-github/src/autocomplete/autocomplete.test.ts b/plugins/scaffolder-backend-module-github/src/autocomplete/autocomplete.test.ts index 9b9aaff9c2..8076a4de01 100644 --- a/plugins/scaffolder-backend-module-github/src/autocomplete/autocomplete.test.ts +++ b/plugins/scaffolder-backend-module-github/src/autocomplete/autocomplete.test.ts @@ -31,6 +31,9 @@ const mockOctokit = { listForAuthenticatedUser: jest.fn(), listBranches: jest.fn(), }, + orgs: { + listForAuthenticatedUser: jest.fn(), + }, }, }; jest.mock('octokit', () => ({ @@ -95,6 +98,30 @@ describe('handleAutocompleteRequest', () => { }); }); + it('should return owners', async () => { + const handleAutocompleteRequest = createHandleAutocompleteRequest({ + integrations: mockIntegrations, + }); + + mockOctokit.rest.orgs.listForAuthenticatedUser.mockResolvedValue({ + data: [ + { + login: 'backstage', + }, + ], + }); + + const result = await handleAutocompleteRequest({ + resource: 'owners', + token: 'token', + context: {}, + }); + + expect(result).toEqual({ + results: [{ id: 'backstage' }], + }); + }); + it('should throw an error for invalid resource', async () => { const handleAutocompleteRequest = createHandleAutocompleteRequest({ integrations: mockIntegrations, diff --git a/plugins/scaffolder-backend-module-github/src/autocomplete/autocomplete.ts b/plugins/scaffolder-backend-module-github/src/autocomplete/autocomplete.ts index 35d6b4f930..b3efde9333 100644 --- a/plugins/scaffolder-backend-module-github/src/autocomplete/autocomplete.ts +++ b/plugins/scaffolder-backend-module-github/src/autocomplete/autocomplete.ts @@ -64,6 +64,15 @@ export function createHandleAutocompleteRequest(options: { return { results }; } + case 'owners': { + const orgs = await client.paginate( + client.rest.orgs.listForAuthenticatedUser, + ); + + const results = orgs.map(r => ({ id: r.login })); + + return { results }; + } default: throw new InputError(`Invalid resource: ${resource}`); } diff --git a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md index 703220bd48..4d116cfaee 100644 --- a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,215 @@ # @backstage/plugin-scaffolder-backend-module-gitlab +## 0.11.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/errors@1.3.0 + - @backstage/plugin-scaffolder-node@0.13.2 + - @backstage/integration@2.0.1 + - @backstage/config@1.3.7 + +## 0.11.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/plugin-scaffolder-node@0.13.2-next.2 + - @backstage/integration@2.0.1-next.0 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/config@1.3.7-next.0 + +## 0.11.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + +## 0.11.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-scaffolder-node@0.13.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration@2.0.0 + +## 0.11.4 + +### Patch Changes + +- 5730c8e: Added `maskedAndHidden` option to `gitlab:projectVariable:create` and `publish:gitlab` action to support creating GitLab project variables that are both masked and hidden. Updated gitbeaker to version 43.8.0 for proper type support. +- 0c1726a: Added new `gitlab:group:access` scaffolder action to add or remove users and groups as members of GitLab groups. The action supports specifying members via `userIds` and/or `groupIds` array parameters, configurable access levels (Guest, Reporter, Developer, Maintainer, Owner), and defaults to the 'add' action when not specified. +- 4b8fcf0: Added two optional inputs to the `publish:gitlab` action: + + - `settings.name`: set a custom human-readable project title that differs from the repository slug. + - `ownerUsername`: add a specific GitLab user as project owner (access level 50) of the newly created repository. Requires a privileged token in the integration configuration. + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-scaffolder-node@0.13.0 + - @backstage/integration@2.0.0 + +## 0.11.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + +## 0.11.4-next.1 + +### Patch Changes + +- 0c1726a: Added new `gitlab:group:access` scaffolder action to add or remove users and groups as members of GitLab groups. The action supports specifying members via `userIds` and/or `groupIds` array parameters, configurable access levels (Guest, Reporter, Developer, Maintainer, Owner), and defaults to the 'add' action when not specified. +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.11.4-next.0 + +### Patch Changes + +- 5730c8e: Added `maskedAndHidden` option to `gitlab:projectVariable:create` and `publish:gitlab` action to support creating GitLab project variables that are both masked and hidden. Updated gitbeaker to version 43.8.0 for proper type support. +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + +## 0.11.3 + +### Patch Changes + +- 6b5e7d9: Allow setting optional description on group creation +- 7455dae: Use node prefix on native imports +- f0f9403: Changed `gitlab:group:ensureExists` action to use `Groups.show` API instead of `Groups.search` for checking if a group path exists. This is more efficient as it directly retrieves the group by path rather than searching and filtering results. +- 32c51c0: Added new `gitlab:user:info` scaffolder action that retrieves information about a GitLab user. The action can fetch either the current authenticated user or a specific user by ID. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-scaffolder-node@0.12.5 + +## 0.11.3-next.2 + +### Patch Changes + +- 6b5e7d9: Allow setting optional description on group creation +- f0f9403: Changed `gitlab:group:ensureExists` action to use `Groups.show` API instead of `Groups.search` for checking if a group path exists. This is more efficient as it directly retrieves the group by path rather than searching and filtering results. +- Updated dependencies + - @backstage/integration@1.20.0-next.2 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## 0.11.3-next.1 + +### Patch Changes + +- 32c51c0: Added new `gitlab:user:info` scaffolder action that retrieves information about a GitLab user. The action can fetch either the current authenticated user or a specific user by ID. +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## 0.11.2-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.11.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + - @backstage/integration@1.19.2 + +## 0.11.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + +## 0.11.0 + +### Minor Changes + +- f2d034b: In the `gitlabRepoPush` action, add 'auto' possibility for `commitAction` input. + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.11.0-next.1 + +### Minor Changes + +- f2d034b: In the `gitlabRepoPush` action, add 'auto' possibility for `commitAction` input. + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## 0.10.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.10.0 + +### Minor Changes + +- ff96d7e: fix scaffolder action createDeployToken to allow usage of oauth tokens + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + ## 0.10.0-next.1 ### Minor Changes diff --git a/plugins/scaffolder-backend-module-gitlab/README.md b/plugins/scaffolder-backend-module-gitlab/README.md index 4542ec4198..1579f2e55b 100644 --- a/plugins/scaffolder-backend-module-gitlab/README.md +++ b/plugins/scaffolder-backend-module-gitlab/README.md @@ -117,6 +117,7 @@ spec: value: "${{ steps['gitlab-access-token'].output.access_token }}" variableType: 'env_var' masked: true + maskedAndHidden: false variableProtected: false raw: false environmentScope: '*' diff --git a/plugins/scaffolder-backend-module-gitlab/knip-report.md b/plugins/scaffolder-backend-module-gitlab/knip-report.md index 97d5b385fd..2661c35327 100644 --- a/plugins/scaffolder-backend-module-gitlab/knip-report.md +++ b/plugins/scaffolder-backend-module-gitlab/knip-report.md @@ -1,3 +1,2 @@ # Knip report - diff --git a/plugins/scaffolder-backend-module-gitlab/package.json b/plugins/scaffolder-backend-module-gitlab/package.json index fb04268858..e4964c0e8c 100644 --- a/plugins/scaffolder-backend-module-gitlab/package.json +++ b/plugins/scaffolder-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitlab", - "version": "0.10.0-next.1", + "version": "0.11.5", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", @@ -50,11 +50,12 @@ "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-scaffolder-node": "workspace:^", - "@gitbeaker/requester-utils": "^41.2.0", - "@gitbeaker/rest": "^41.2.0", + "@gitbeaker/core": "^43.8.0", + "@gitbeaker/requester-utils": "^43.8.0", + "@gitbeaker/rest": "^43.8.0", "luxon": "^3.0.0", "yaml": "^2.0.0", - "zod": "^3.22.4" + "zod": "^3.25.76 || ^4.0.0" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", diff --git a/plugins/scaffolder-backend-module-gitlab/report.api.md b/plugins/scaffolder-backend-module-gitlab/report.api.md index 1da4c704b1..a5a4de0f8b 100644 --- a/plugins/scaffolder-backend-module-gitlab/report.api.md +++ b/plugins/scaffolder-backend-module-gitlab/report.api.md @@ -8,6 +8,28 @@ import { Config } from '@backstage/config'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; +// @public +export const createGitlabGroupAccessAction: (options: { + integrations: ScmIntegrationRegistry; +}) => TemplateAction< + { + repoUrl: string; + path: string | number; + token?: string | undefined; + userIds?: number[] | undefined; + groupIds?: number[] | undefined; + action?: 'add' | 'remove' | undefined; + accessLevel?: string | number | undefined; + }, + { + userIds?: number[] | undefined; + groupIds?: number[] | undefined; + path?: string | number | undefined; + accessLevel?: number | undefined; + }, + 'v2' +>; + // @public export const createGitlabGroupEnsureExistsAction: (options: { integrations: ScmIntegrationRegistry; @@ -22,6 +44,7 @@ export const createGitlabGroupEnsureExistsAction: (options: { } )[]; token?: string | undefined; + description?: string | undefined; }, { groupId?: number | undefined; @@ -110,6 +133,7 @@ export const createGitlabProjectVariableAction: (options: { token?: string | undefined; variableProtected?: boolean | undefined; masked?: boolean | undefined; + maskedAndHidden?: boolean | undefined; raw?: boolean | undefined; environmentScope?: string | undefined; }, @@ -130,7 +154,7 @@ export const createGitlabRepoPushAction: (options: { sourcePath?: string | undefined; targetPath?: string | undefined; token?: string | undefined; - commitAction?: 'update' | 'delete' | 'create' | undefined; + commitAction?: 'auto' | 'update' | 'create' | 'delete' | undefined; }, { projectid: string; @@ -140,6 +164,28 @@ export const createGitlabRepoPushAction: (options: { 'v2' >; +// @public +export const createGitlabUserInfoAction: (options: { + integrations: ScmIntegrationRegistry; +}) => TemplateAction< + { + repoUrl: string; + token?: string | undefined; + userId?: number | undefined; + }, + { + id: number; + username: string; + name: string; + state: string; + webUrl: string; + email?: string | undefined; + createdAt?: string | undefined; + publicEmail?: string | undefined; + }, + 'v2' +>; + // @public export function createPublishGitlabAction(options: { integrations: ScmIntegrationRegistry; @@ -157,9 +203,11 @@ export function createPublishGitlabAction(options: { skipExisting?: boolean | undefined; token?: string | undefined; setUserAsOwner?: boolean | undefined; + ownerUsername?: string | undefined; topics?: string[] | undefined; settings?: | { + name?: string | undefined; visibility?: 'internal' | 'private' | 'public' | undefined; path?: string | undefined; description?: string | undefined; @@ -198,6 +246,7 @@ export function createPublishGitlabAction(options: { variable_type?: 'file' | 'env_var' | undefined; masked?: boolean | undefined; environment_scope?: string | undefined; + masked_and_hidden?: boolean | undefined; }[] | undefined; }, @@ -224,7 +273,7 @@ export const createPublishGitlabMergeRequestAction: (options: { sourcePath?: string | undefined; targetPath?: string | undefined; token?: string | undefined; - commitAction?: 'auto' | 'update' | 'delete' | 'create' | 'skip' | undefined; + commitAction?: 'auto' | 'update' | 'create' | 'delete' | 'skip' | undefined; projectid?: string | undefined; removeSourceBranch?: boolean | undefined; assignee?: string | undefined; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.examples.ts index ea2ff41ec5..4875b1812c 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.examples.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.examples.ts @@ -221,4 +221,40 @@ export const examples: TemplateExample[] = [ ], }), }, + { + description: + 'Initializes a GitLab repository with a custom project title that differs from the repository slug.', + example: yaml.stringify({ + steps: [ + { + id: 'publish', + action: 'publish:gitlab', + name: 'Publish to GitLab', + input: { + repoUrl: 'gitlab.com?repo=my-project-slug&owner=group_name', + settings: { + name: 'My Project Title', + }, + }, + }, + ], + }), + }, + { + description: + 'Initializes a GitLab repository and adds a specific user as project owner.', + example: yaml.stringify({ + steps: [ + { + id: 'publish', + action: 'publish:gitlab', + name: 'Publish to GitLab', + input: { + repoUrl: 'gitlab.com?repo=project_name&owner=group_name', + ownerUsername: 'john.doe', + }, + }, + ], + }), + }, ]; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.test.ts index 1e3ac40d35..b668a2a2cd 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.test.ts @@ -45,6 +45,7 @@ const mockGitlabClient = { Users: { showCurrentUser: jest.fn(), allProjects: jest.fn(), + all: jest.fn(), }, ProjectMembers: { add: jest.fn(), @@ -146,6 +147,19 @@ describe('publish:gitlab', () => { ], }, }); + const mockContextWithMaskedAndHiddenVariable = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + repoVisibility: 'private' as const, + projectVariables: [ + { + key: 'secret', + value: 'secret-value', + masked_and_hidden: true, + }, + ], + }, + }); beforeEach(() => { jest.resetAllMocks(); @@ -207,6 +221,7 @@ describe('publish:gitlab', () => { expect(mockGitlabClient.Projects.create).toHaveBeenCalledWith({ namespaceId: 1234, name: 'bob', + path: 'bob', visibility: 'private', }); expect(mockGitlabClient.Branches.create).not.toHaveBeenCalled(); @@ -227,6 +242,7 @@ describe('publish:gitlab', () => { expect(mockGitlabClient.Projects.create).toHaveBeenCalledWith({ namespaceId: 1234, name: 'repo', + path: 'repo', visibility: 'private', ci_config_path: '.gitlab-ci.yml', }); @@ -250,6 +266,7 @@ describe('publish:gitlab', () => { expect(mockGitlabClient.Projects.create).toHaveBeenCalledWith({ namespaceId: 12345, name: 'repo', + path: 'repo', visibility: 'private', ci_config_path: '.gitlab-ci.yml', }); @@ -335,6 +352,7 @@ describe('publish:gitlab', () => { expect(mockGitlabClient.Projects.create).toHaveBeenCalledWith({ namespaceId: 1234, name: 'repo', + path: 'repo', visibility: 'private', ci_config_path: '.gitlab-ci.yml', }); @@ -359,6 +377,7 @@ describe('publish:gitlab', () => { expect(mockGitlabClient.Projects.create).toHaveBeenCalledWith({ namespaceId: 1234, name: 'repo', + path: 'repo', visibility: 'internal', topics: ['topic1', 'topic2'], ci_config_path: '.gitlab-ci.yml', @@ -385,6 +404,7 @@ describe('publish:gitlab', () => { expect(mockGitlabClient.Projects.create).toHaveBeenCalledWith({ namespaceId: 1234, name: 'repo', + path: 'repo', visibility: 'private', }); expect(mockGitlabClient.Branches.create).toHaveBeenCalledTimes(2); @@ -429,6 +449,7 @@ describe('publish:gitlab', () => { expect(mockGitlabClient.Projects.create).toHaveBeenCalledWith({ namespaceId: 1234, name: 'repo', + path: 'repo', visibility: 'private', }); @@ -441,6 +462,36 @@ describe('publish:gitlab', () => { variableType: 'env_var', protected: true, masked: true, + masked_and_hidden: false, + raw: false, + environmentScope: '*', + }, + ); + }); + + it('should call the correct Gitlab APIs for variables with masked_and_hidden option', async () => { + mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 }); + mockGitlabClient.Namespaces.show.mockResolvedValue({ + id: 1234, + kind: 'group', + }); + mockGitlabClient.Groups.allProjects.mockResolvedValue([]); + mockGitlabClient.Projects.create.mockResolvedValue({ + id: 123456, + http_url_to_repo: 'http://mockurl.git', + }); + + await action.handler(mockContextWithMaskedAndHiddenVariable); + + expect(mockGitlabClient.ProjectVariables.create).toHaveBeenCalledWith( + 123456, + 'secret', + 'secret-value', + { + variableType: 'env_var', + protected: false, + masked: false, + masked_and_hidden: true, raw: false, environmentScope: '*', }, @@ -725,12 +776,13 @@ describe('publish:gitlab', () => { expect(mockGitlabClient.Users.showCurrentUser).toHaveBeenCalled(); expect(mockGitlabClient.ProjectMembers.add).toHaveBeenCalledWith( 123456, - 12345, 50, + { userId: 12345 }, ); expect(mockGitlabClient.Projects.create).toHaveBeenCalledWith({ namespaceId: 1234, name: 'repo', + path: 'repo', visibility: 'private', }); }); @@ -755,4 +807,137 @@ describe('publish:gitlab', () => { `The namespace ${owner} is not found or the user doesn't have permissions to access it`, ); }); + + it('should use settings.name as the project title when provided', async () => { + mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 }); + mockGitlabClient.Namespaces.show.mockResolvedValue({ + id: 1234, + kind: 'group', + }); + mockGitlabClient.Groups.allProjects.mockResolvedValue([]); + mockGitlabClient.Projects.create.mockResolvedValue({ + http_url_to_repo: 'http://mockurl.git', + }); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=my-project-slug&owner=owner', + settings: { + name: 'My Project Title', + }, + }, + }); + + expect(mockGitlabClient.Projects.create).toHaveBeenCalledWith({ + namespaceId: 1234, + name: 'My Project Title', + path: 'my-project-slug', + visibility: 'private', + }); + }); + + it('should add ownerUsername as project owner when provided', async () => { + mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 }); + mockGitlabClient.Namespaces.show.mockResolvedValue({ + id: 1234, + kind: 'group', + }); + mockGitlabClient.Groups.allProjects.mockResolvedValue([]); + mockGitlabClient.Projects.create.mockResolvedValue({ + id: 123456, + http_url_to_repo: 'http://mockurl.git', + }); + mockGitlabClient.Users.all.mockResolvedValue([{ id: 99999 }]); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + ownerUsername: 'target-owner', + }, + }); + + expect(mockGitlabClient.Users.all).toHaveBeenCalledWith({ + username: 'target-owner', + }); + expect(mockGitlabClient.ProjectMembers.add).toHaveBeenCalledWith( + 123456, + 50, + { userId: 99999 }, + ); + }); + + it('should warn and continue when ownerUsername is not found', async () => { + mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 }); + mockGitlabClient.Namespaces.show.mockResolvedValue({ + id: 1234, + kind: 'group', + }); + mockGitlabClient.Groups.allProjects.mockResolvedValue([]); + mockGitlabClient.Projects.create.mockResolvedValue({ + id: 123456, + http_url_to_repo: 'http://mockurl.git', + }); + mockGitlabClient.Users.all.mockResolvedValue([]); + + const ctx = { + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + ownerUsername: 'unknown-user', + }, + }; + ctx.logger.warn = jest.fn(); + + await action.handler(ctx); + + expect(mockGitlabClient.Users.all).toHaveBeenCalledWith({ + username: 'unknown-user', + }); + expect(ctx.logger.warn).toHaveBeenCalledWith( + expect.stringContaining('Could not find GitLab user'), + ); + expect(mockGitlabClient.ProjectMembers.add).not.toHaveBeenCalled(); + }); + + it('should warn and continue when adding ownerUsername as project member fails', async () => { + mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 }); + mockGitlabClient.Namespaces.show.mockResolvedValue({ + id: 1234, + kind: 'group', + }); + mockGitlabClient.Groups.allProjects.mockResolvedValue([]); + mockGitlabClient.Projects.create.mockResolvedValue({ + id: 123456, + http_url_to_repo: 'http://mockurl.git', + }); + mockGitlabClient.Users.all.mockResolvedValue([{ id: 99999 }]); + mockGitlabClient.ProjectMembers.add.mockRejectedValue( + new Error('Forbidden'), + ); + + const ctx = { + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + ownerUsername: 'target-owner', + }, + }; + ctx.logger.warn = jest.fn(); + + await action.handler(ctx); + + expect(mockGitlabClient.ProjectMembers.add).toHaveBeenCalledWith( + 123456, + 50, + { userId: 99999 }, + ); + expect(ctx.logger.warn).toHaveBeenCalledWith( + expect.stringContaining('Failed to add user'), + ); + expect(ctx.logger.warn).toHaveBeenCalledWith( + expect.stringContaining('Forbidden'), + ); + }); }); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts index 7f3f673260..f634244f27 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts @@ -112,6 +112,13 @@ export function createPublishGitlabAction(options: { 'Set the token user as owner of the newly created repository. Requires a token authorized to do the edit in the integration configuration for the matching host', }) .optional(), + ownerUsername: z => + z + .string({ + description: + 'Username of a GitLab user to add as owner (access level 50) of the newly created project. Requires a privileged token in the integration configuration for the matching host.', + }) + .optional(), topics: z => z .array(z.string(), { @@ -122,6 +129,12 @@ export function createPublishGitlabAction(options: { settings: z => z .object({ + name: z + .string({ + description: + 'Human-readable project name (title). If not provided, the repository name from repoUrl is used.', + }) + .optional(), path: z .string({ description: @@ -206,6 +219,7 @@ export function createPublishGitlabAction(options: { variable_type: z.enum(['env_var', 'file']).optional(), protected: z.boolean().optional(), masked: z.boolean().optional(), + masked_and_hidden: z.boolean().optional(), raw: z.boolean().optional(), environment_scope: z.string().optional(), }), @@ -322,6 +336,7 @@ export function createPublishGitlabAction(options: { await client.Projects.create({ namespaceId: targetNamespaceId, name: repo, + path: repo, visibility: repoVisibility, ...(topics.length ? { topics } : {}), ...(Object.keys(settings).length ? { ...settings } : {}), @@ -339,7 +354,34 @@ export function createPublishGitlabAction(options: { token: integrationConfig.config.token, }); - await adminClient.ProjectMembers.add(projectId, userId, 50); + await adminClient.ProjectMembers.add(projectId, 50, { userId }); + } + + if (ctx.input.ownerUsername && integrationConfig.config.token) { + try { + const adminClient = new Gitlab({ + host: integrationConfig.config.baseUrl, + token: integrationConfig.config.token, + }); + + const users = await adminClient.Users.all({ + username: ctx.input.ownerUsername, + }); + if (users.length > 0) { + await adminClient.ProjectMembers.add(projectId, 50, { + userId: users[0].id, + }); + } else { + ctx.logger.warn( + `Could not find GitLab user with username '${ctx.input.ownerUsername}'. Skipping owner assignment.`, + ); + } + } catch (e) { + const errorMessage = e instanceof Error ? e.message : String(e); + ctx.logger.warn( + `Failed to add user '${ctx.input.ownerUsername}' as owner: ${errorMessage}. Proceeding without owner assignment.`, + ); + } } const remoteUrl = (http_url_to_repo as string).replace(/\.git$/, ''); @@ -435,6 +477,7 @@ export function createPublishGitlabAction(options: { 'env_var') as VariableType, protected: variable.protected ?? false, masked: variable.masked ?? false, + masked_and_hidden: variable.masked_and_hidden ?? false, raw: variable.raw ?? false, environment_scope: variable.environment_scope ?? '*', }); @@ -448,6 +491,7 @@ export function createPublishGitlabAction(options: { variableType: variableWithDefaults.variable_type, protected: variableWithDefaults.protected, masked: variableWithDefaults.masked, + masked_and_hidden: variableWithDefaults.masked_and_hidden, environmentScope: variableWithDefaults.environment_scope, description: variableWithDefaults.description, raw: variableWithDefaults.raw, diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupAccessAction.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupAccessAction.examples.test.ts new file mode 100644 index 0000000000..f49986a344 --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupAccessAction.examples.test.ts @@ -0,0 +1,164 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ScmIntegrations } from '@backstage/integration'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import yaml from 'yaml'; +import { createGitlabGroupAccessAction } from './gitlabGroupAccessAction'; +import { examples } from './gitlabGroupAccessAction.examples'; +import { mockServices } from '@backstage/backend-test-utils'; + +const mockGitlabClient = { + GroupMembers: { + add: jest.fn(), + remove: jest.fn(), + }, + Groups: { + share: jest.fn(), + unshare: jest.fn(), + }, +}; + +jest.mock('@gitbeaker/rest', () => ({ + Gitlab: class { + constructor() { + return mockGitlabClient; + } + }, +})); + +describe('gitlab:group:access examples', () => { + const mockContext = createMockActionContext(); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'tokenlols', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + ], + }, + }, + }); + const integrations = ScmIntegrations.fromConfig(config); + + const action = createGitlabGroupAccessAction({ integrations }); + + it(`Should ${examples[0].description}`, async () => { + mockGitlabClient.GroupMembers.add.mockResolvedValue({}); + + await action.handler({ + ...mockContext, + input: yaml.parse(examples[0].example).steps[0].input, + }); + + expect(mockGitlabClient.GroupMembers.add).toHaveBeenCalledTimes(2); + expect(mockGitlabClient.GroupMembers.add).toHaveBeenCalledWith(123, 30, { + userId: 456, + }); + expect(mockGitlabClient.GroupMembers.add).toHaveBeenCalledWith(123, 30, { + userId: 789, + }); + + expect(mockContext.output).toHaveBeenCalledWith('userIds', [456, 789]); + expect(mockContext.output).toHaveBeenCalledWith('path', 123); + expect(mockContext.output).toHaveBeenCalledWith('accessLevel', 30); + }); + + it(`Should ${examples[1].description}`, async () => { + mockGitlabClient.GroupMembers.add.mockResolvedValue({}); + + await action.handler({ + ...mockContext, + input: yaml.parse(examples[1].example).steps[0].input, + }); + + expect(mockGitlabClient.GroupMembers.add).toHaveBeenCalledWith( + 'group1', + 30, + { userId: 456 }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('userIds', [456]); + expect(mockContext.output).toHaveBeenCalledWith('path', 'group1'); + expect(mockContext.output).toHaveBeenCalledWith('accessLevel', 30); + }); + + it(`Should ${examples[2].description}`, async () => { + mockGitlabClient.GroupMembers.remove.mockResolvedValue(undefined); + + await action.handler({ + ...mockContext, + input: yaml.parse(examples[2].example).steps[0].input, + }); + + expect(mockGitlabClient.GroupMembers.remove).toHaveBeenCalledTimes(2); + expect(mockGitlabClient.GroupMembers.remove).toHaveBeenCalledWith(123, 456); + expect(mockGitlabClient.GroupMembers.remove).toHaveBeenCalledWith(123, 789); + + expect(mockContext.output).toHaveBeenCalledWith('userIds', [456, 789]); + expect(mockContext.output).toHaveBeenCalledWith('path', 123); + expect(mockContext.output).not.toHaveBeenCalledWith( + 'accessLevel', + expect.anything(), + ); + }); + + it(`Should ${examples[3].description}`, async () => { + mockGitlabClient.Groups.share.mockResolvedValue({}); + + await action.handler({ + ...mockContext, + input: yaml.parse(examples[3].example).steps[0].input, + }); + + expect(mockGitlabClient.Groups.share).toHaveBeenCalledWith( + 123, + 456, + 30, + {}, + ); + + expect(mockContext.output).toHaveBeenCalledWith('groupIds', [456]); + expect(mockContext.output).toHaveBeenCalledWith('path', 123); + expect(mockContext.output).toHaveBeenCalledWith('accessLevel', 30); + }); + + it(`Should ${examples[4].description}`, async () => { + mockGitlabClient.Groups.unshare.mockResolvedValue(undefined); + + await action.handler({ + ...mockContext, + input: yaml.parse(examples[4].example).steps[0].input, + }); + + expect(mockGitlabClient.Groups.unshare).toHaveBeenCalledWith(123, 456, {}); + + expect(mockContext.output).toHaveBeenCalledWith('groupIds', [456]); + expect(mockContext.output).toHaveBeenCalledWith('path', 123); + expect(mockContext.output).not.toHaveBeenCalledWith( + 'accessLevel', + expect.anything(), + ); + }); +}); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupAccessAction.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupAccessAction.examples.ts new file mode 100644 index 0000000000..8ff69f3b9d --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupAccessAction.examples.ts @@ -0,0 +1,110 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { TemplateExample } from '@backstage/plugin-scaffolder-node'; +import yaml from 'yaml'; + +export const examples: TemplateExample[] = [ + { + description: 'Add users to a group using numeric group ID', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabGroupAccess', + name: 'Add Users to Group', + action: 'gitlab:group:access', + input: { + repoUrl: 'gitlab.com', + path: 123, + userIds: [456, 789], + accessLevel: 30, + }, + }, + ], + }), + }, + { + description: 'Add users to a group using string path', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabGroupAccess', + name: 'Add Users to Group', + action: 'gitlab:group:access', + input: { + repoUrl: 'gitlab.com', + path: 'group1', + userIds: [456], + accessLevel: 'developer', + }, + }, + ], + }), + }, + { + description: 'Remove users from a group', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabGroupAccess', + name: 'Remove Users from Group', + action: 'gitlab:group:access', + input: { + repoUrl: 'gitlab.com', + path: 123, + userIds: [456, 789], + action: 'remove', + }, + }, + ], + }), + }, + { + description: 'Share a group with another group', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabGroupAccess', + name: 'Share Group', + action: 'gitlab:group:access', + input: { + repoUrl: 'gitlab.com', + path: 123, + groupIds: [456], + accessLevel: 30, + }, + }, + ], + }), + }, + { + description: 'Unshare a group', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabGroupAccess', + name: 'Unshare Group', + action: 'gitlab:group:access', + input: { + repoUrl: 'gitlab.com', + path: 123, + groupIds: [456], + action: 'remove', + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupAccessAction.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupAccessAction.test.ts new file mode 100644 index 0000000000..04491c3c4e --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupAccessAction.test.ts @@ -0,0 +1,690 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ScmIntegrations } from '@backstage/integration'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { createGitlabGroupAccessAction } from './gitlabGroupAccessAction'; +import { getClient } from '../util'; +import { mockServices } from '@backstage/backend-test-utils'; + +const mockGitlabClient = { + GroupMembers: { + add: jest.fn(), + edit: jest.fn(), + remove: jest.fn(), + }, + Groups: { + share: jest.fn(), + unshare: jest.fn(), + }, +}; + +jest.mock('@gitbeaker/rest', () => ({ + Gitlab: class { + constructor() { + return mockGitlabClient; + } + }, +})); + +jest.mock('../util', () => ({ + getClient: jest.fn().mockImplementation(() => mockGitlabClient), + parseRepoHost: (repoUrl: string) => repoUrl, +})); + +describe('gitlab:group:access', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'tokenlols', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + ], + }, + }, + }); + const integrations = ScmIntegrations.fromConfig(config); + + const action = createGitlabGroupAccessAction({ integrations }); + + const mockContext = createMockActionContext(); + + // User tests + it('should add a single user to a group with the specified access level', async () => { + mockGitlabClient.GroupMembers.add.mockResolvedValue({ + id: 1, + user_id: 456, + group_id: 123, + access_level: 30, + }); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + path: 123, + userIds: [456], + accessLevel: 30, + }, + }); + + expect(mockGitlabClient.GroupMembers.add).toHaveBeenCalledWith(123, 30, { + userId: 456, + }); + + expect(mockContext.output).toHaveBeenCalledWith('userIds', [456]); + expect(mockContext.output).toHaveBeenCalledWith('path', 123); + expect(mockContext.output).toHaveBeenCalledWith('accessLevel', 30); + }); + + it('should add multiple users to a group with the specified access level', async () => { + mockGitlabClient.GroupMembers.add.mockResolvedValue({}); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + path: 123, + userIds: [456, 789, 101], + accessLevel: 30, + }, + }); + + expect(mockGitlabClient.GroupMembers.add).toHaveBeenCalledTimes(3); + expect(mockGitlabClient.GroupMembers.add).toHaveBeenCalledWith(123, 30, { + userId: 456, + }); + expect(mockGitlabClient.GroupMembers.add).toHaveBeenCalledWith(123, 30, { + userId: 789, + }); + expect(mockGitlabClient.GroupMembers.add).toHaveBeenCalledWith(123, 30, { + userId: 101, + }); + + expect(mockContext.output).toHaveBeenCalledWith('userIds', [456, 789, 101]); + expect(mockContext.output).toHaveBeenCalledWith('path', 123); + expect(mockContext.output).toHaveBeenCalledWith('accessLevel', 30); + }); + + it('should default to add action when action is not specified', async () => { + mockGitlabClient.GroupMembers.add.mockResolvedValue({}); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + path: 123, + userIds: [456], + accessLevel: 30, + }, + }); + + expect(mockGitlabClient.GroupMembers.add).toHaveBeenCalledWith(123, 30, { + userId: 456, + }); + expect(mockGitlabClient.GroupMembers.remove).not.toHaveBeenCalled(); + }); + + it('should remove a single user from a group', async () => { + mockGitlabClient.GroupMembers.remove.mockResolvedValue(undefined); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + path: 123, + userIds: [456], + action: 'remove', + }, + }); + + expect(mockGitlabClient.GroupMembers.remove).toHaveBeenCalledWith(123, 456); + + expect(mockContext.output).toHaveBeenCalledWith('userIds', [456]); + expect(mockContext.output).toHaveBeenCalledWith('path', 123); + expect(mockContext.output).not.toHaveBeenCalledWith( + 'accessLevel', + expect.anything(), + ); + }); + + it('should remove multiple users from a group', async () => { + mockGitlabClient.GroupMembers.remove.mockResolvedValue(undefined); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + path: 123, + userIds: [456, 789], + action: 'remove', + }, + }); + + expect(mockGitlabClient.GroupMembers.remove).toHaveBeenCalledTimes(2); + expect(mockGitlabClient.GroupMembers.remove).toHaveBeenCalledWith(123, 456); + expect(mockGitlabClient.GroupMembers.remove).toHaveBeenCalledWith(123, 789); + + expect(mockContext.output).toHaveBeenCalledWith('userIds', [456, 789]); + expect(mockContext.output).toHaveBeenCalledWith('path', 123); + }); + + it('should default to accessLevel 30 (Developer) when not specified', async () => { + mockGitlabClient.GroupMembers.add.mockResolvedValue({}); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + path: 123, + userIds: [456], + }, + }); + + expect(mockGitlabClient.GroupMembers.add).toHaveBeenCalledWith(123, 30, { + userId: 456, + }); + expect(mockContext.output).toHaveBeenCalledWith('accessLevel', 30); + }); + + it('should not call API on dryRun for add action', async () => { + await action.handler({ + ...mockContext, + isDryRun: true, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + path: 123, + userIds: [456, 789], + accessLevel: 30, + }, + }); + + expect(mockGitlabClient.GroupMembers.add).not.toHaveBeenCalled(); + + expect(mockContext.output).toHaveBeenCalledWith('userIds', [456, 789]); + expect(mockContext.output).toHaveBeenCalledWith('path', 123); + expect(mockContext.output).toHaveBeenCalledWith('accessLevel', 30); + }); + + it('should not call API on dryRun for remove action', async () => { + await action.handler({ + ...mockContext, + isDryRun: true, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + path: 123, + userIds: [456], + action: 'remove', + }, + }); + + expect(mockGitlabClient.GroupMembers.remove).not.toHaveBeenCalled(); + + expect(mockContext.output).toHaveBeenCalledWith('userIds', [456]); + expect(mockContext.output).toHaveBeenCalledWith('path', 123); + expect(mockContext.output).not.toHaveBeenCalledWith( + 'accessLevel', + expect.anything(), + ); + }); + + it('should use the token from the integration config when none is provided', async () => { + mockGitlabClient.GroupMembers.add.mockResolvedValue({}); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + path: 123, + userIds: [456], + accessLevel: 30, + }, + }); + + expect(getClient).toHaveBeenCalledWith( + expect.not.objectContaining({ + token: expect.anything(), + }), + ); + }); + + it('should use a provided token for authentication', async () => { + mockGitlabClient.GroupMembers.add.mockResolvedValue({}); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + path: 123, + userIds: [456], + accessLevel: 30, + token: 'mysecrettoken', + }, + }); + + expect(getClient).toHaveBeenCalledWith( + expect.objectContaining({ + token: 'mysecrettoken', + }), + ); + }); + + it('should add users as Guest (accessLevel 10)', async () => { + mockGitlabClient.GroupMembers.add.mockResolvedValue({}); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + path: 123, + userIds: [456], + accessLevel: 10, + }, + }); + + expect(mockGitlabClient.GroupMembers.add).toHaveBeenCalledWith(123, 10, { + userId: 456, + }); + }); + + it('should add users as Maintainer (accessLevel 40)', async () => { + mockGitlabClient.GroupMembers.add.mockResolvedValue({}); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + path: 123, + userIds: [456], + accessLevel: 40, + }, + }); + + expect(mockGitlabClient.GroupMembers.add).toHaveBeenCalledWith(123, 40, { + userId: 456, + }); + }); + + it('should add users as Owner (accessLevel 50)', async () => { + mockGitlabClient.GroupMembers.add.mockResolvedValue({}); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + path: 123, + userIds: [456], + accessLevel: 50, + }, + }); + + expect(mockGitlabClient.GroupMembers.add).toHaveBeenCalledWith(123, 50, { + userId: 456, + }); + }); + + it('should accept string accessLevel "developer"', async () => { + mockGitlabClient.GroupMembers.add.mockResolvedValue({}); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + path: 123, + userIds: [456], + accessLevel: 'developer', + }, + }); + + expect(mockGitlabClient.GroupMembers.add).toHaveBeenCalledWith(123, 30, { + userId: 456, + }); + expect(mockContext.output).toHaveBeenCalledWith('accessLevel', 30); + }); + + it('should accept string accessLevel "maintainer" (case insensitive)', async () => { + mockGitlabClient.GroupMembers.add.mockResolvedValue({}); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + path: 123, + userIds: [456], + accessLevel: 'MAINTAINER', + }, + }); + + expect(mockGitlabClient.GroupMembers.add).toHaveBeenCalledWith(123, 40, { + userId: 456, + }); + expect(mockContext.output).toHaveBeenCalledWith('accessLevel', 40); + }); + + it('should throw an error for invalid string accessLevel', async () => { + await expect( + action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + path: 123, + userIds: [456], + accessLevel: 'invalid_level', + }, + }), + ).rejects.toThrow('Invalid access level: "invalid_level"'); + + expect(mockGitlabClient.GroupMembers.add).not.toHaveBeenCalled(); + }); + + it('should handle 409 conflict by editing existing user member', async () => { + mockGitlabClient.GroupMembers.add.mockRejectedValue({ + cause: { response: { status: 409 } }, + }); + mockGitlabClient.GroupMembers.edit.mockResolvedValue({}); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + path: 123, + userIds: [456], + accessLevel: 30, + }, + }); + + expect(mockGitlabClient.GroupMembers.add).toHaveBeenCalledWith(123, 30, { + userId: 456, + }); + expect(mockGitlabClient.GroupMembers.edit).toHaveBeenCalledWith( + 123, + 456, + 30, + ); + expect(mockContext.output).toHaveBeenCalledWith('userIds', [456]); + }); + + // Group sharing tests + it('should share a single group with another group', async () => { + mockGitlabClient.Groups.share.mockResolvedValue({}); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + path: 123, + groupIds: [456], + accessLevel: 30, + }, + }); + + expect(mockGitlabClient.Groups.share).toHaveBeenCalledWith( + 123, + 456, + 30, + {}, + ); + + expect(mockContext.output).toHaveBeenCalledWith('groupIds', [456]); + expect(mockContext.output).toHaveBeenCalledWith('path', 123); + expect(mockContext.output).toHaveBeenCalledWith('accessLevel', 30); + }); + + it('should share multiple groups with a group', async () => { + mockGitlabClient.Groups.share.mockResolvedValue({}); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + path: 123, + groupIds: [456, 789], + accessLevel: 30, + }, + }); + + expect(mockGitlabClient.Groups.share).toHaveBeenCalledTimes(2); + expect(mockGitlabClient.Groups.share).toHaveBeenCalledWith( + 123, + 456, + 30, + {}, + ); + expect(mockGitlabClient.Groups.share).toHaveBeenCalledWith( + 123, + 789, + 30, + {}, + ); + + expect(mockContext.output).toHaveBeenCalledWith('groupIds', [456, 789]); + expect(mockContext.output).toHaveBeenCalledWith('path', 123); + expect(mockContext.output).toHaveBeenCalledWith('accessLevel', 30); + }); + + it('should unshare groups from a group', async () => { + mockGitlabClient.Groups.unshare.mockResolvedValue(undefined); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + path: 123, + groupIds: [456, 789], + action: 'remove', + }, + }); + + expect(mockGitlabClient.Groups.unshare).toHaveBeenCalledTimes(2); + expect(mockGitlabClient.Groups.unshare).toHaveBeenCalledWith(123, 456, {}); + expect(mockGitlabClient.Groups.unshare).toHaveBeenCalledWith(123, 789, {}); + + expect(mockContext.output).toHaveBeenCalledWith('groupIds', [456, 789]); + expect(mockContext.output).toHaveBeenCalledWith('path', 123); + }); + + it('should handle 409 conflict for group sharing by re-sharing', async () => { + mockGitlabClient.Groups.share.mockRejectedValueOnce({ + cause: { response: { status: 409 } }, + }); + mockGitlabClient.Groups.unshare.mockResolvedValue(undefined); + mockGitlabClient.Groups.share.mockResolvedValueOnce({}); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + path: 123, + groupIds: [456], + accessLevel: 30, + }, + }); + + expect(mockGitlabClient.Groups.share).toHaveBeenCalledTimes(2); + expect(mockGitlabClient.Groups.unshare).toHaveBeenCalledWith(123, 456, {}); + expect(mockContext.output).toHaveBeenCalledWith('groupIds', [456]); + }); + + it('should not call API on dryRun for group sharing', async () => { + await action.handler({ + ...mockContext, + isDryRun: true, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + path: 123, + groupIds: [456, 789], + accessLevel: 30, + }, + }); + + expect(mockGitlabClient.Groups.share).not.toHaveBeenCalled(); + + expect(mockContext.output).toHaveBeenCalledWith('groupIds', [456, 789]); + expect(mockContext.output).toHaveBeenCalledWith('path', 123); + expect(mockContext.output).toHaveBeenCalledWith('accessLevel', 30); + }); + + // Mixed mode tests + it('should add users and share groups simultaneously', async () => { + mockGitlabClient.GroupMembers.add.mockResolvedValue({}); + mockGitlabClient.Groups.share.mockResolvedValue({}); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + path: 123, + userIds: [456, 789], + groupIds: [101, 102], + accessLevel: 30, + }, + }); + + expect(mockGitlabClient.GroupMembers.add).toHaveBeenCalledTimes(2); + expect(mockGitlabClient.GroupMembers.add).toHaveBeenCalledWith(123, 30, { + userId: 456, + }); + expect(mockGitlabClient.GroupMembers.add).toHaveBeenCalledWith(123, 30, { + userId: 789, + }); + + expect(mockGitlabClient.Groups.share).toHaveBeenCalledTimes(2); + expect(mockGitlabClient.Groups.share).toHaveBeenCalledWith( + 123, + 101, + 30, + {}, + ); + expect(mockGitlabClient.Groups.share).toHaveBeenCalledWith( + 123, + 102, + 30, + {}, + ); + + expect(mockContext.output).toHaveBeenCalledWith('userIds', [456, 789]); + expect(mockContext.output).toHaveBeenCalledWith('groupIds', [101, 102]); + expect(mockContext.output).toHaveBeenCalledWith('path', 123); + expect(mockContext.output).toHaveBeenCalledWith('accessLevel', 30); + }); + + it('should remove users and unshare groups simultaneously', async () => { + mockGitlabClient.GroupMembers.remove.mockResolvedValue(undefined); + mockGitlabClient.Groups.unshare.mockResolvedValue(undefined); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + path: 123, + userIds: [456], + groupIds: [789], + action: 'remove', + }, + }); + + expect(mockGitlabClient.GroupMembers.remove).toHaveBeenCalledWith(123, 456); + expect(mockGitlabClient.Groups.unshare).toHaveBeenCalledWith(123, 789, {}); + + expect(mockContext.output).toHaveBeenCalledWith('userIds', [456]); + expect(mockContext.output).toHaveBeenCalledWith('groupIds', [789]); + expect(mockContext.output).toHaveBeenCalledWith('path', 123); + }); + + // Validation tests + it('should throw an error when neither userIds nor groupIds provided', async () => { + await expect( + action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + path: 123, + }, + }), + ).rejects.toThrow( + 'At least one of userIds or groupIds must be provided and non-empty', + ); + + expect(mockGitlabClient.GroupMembers.add).not.toHaveBeenCalled(); + expect(mockGitlabClient.Groups.share).not.toHaveBeenCalled(); + }); + + it('should throw an error when userIds and groupIds are both empty arrays', async () => { + await expect( + action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + path: 123, + userIds: [], + groupIds: [], + }, + }), + ).rejects.toThrow( + 'At least one of userIds or groupIds must be provided and non-empty', + ); + + expect(mockGitlabClient.GroupMembers.add).not.toHaveBeenCalled(); + expect(mockGitlabClient.Groups.share).not.toHaveBeenCalled(); + }); + + it('should not output userIds when only groupIds are provided', async () => { + mockGitlabClient.Groups.share.mockResolvedValue({}); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + path: 123, + groupIds: [456], + accessLevel: 30, + }, + }); + + expect(mockContext.output).not.toHaveBeenCalledWith( + 'userIds', + expect.anything(), + ); + expect(mockContext.output).toHaveBeenCalledWith('groupIds', [456]); + }); + + it('should not output groupIds when only userIds are provided', async () => { + mockGitlabClient.GroupMembers.add.mockResolvedValue({}); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + path: 123, + userIds: [456], + accessLevel: 30, + }, + }); + + expect(mockContext.output).toHaveBeenCalledWith('userIds', [456]); + expect(mockContext.output).not.toHaveBeenCalledWith( + 'groupIds', + expect.anything(), + ); + }); +}); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupAccessAction.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupAccessAction.ts new file mode 100644 index 0000000000..7ad295880e --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupAccessAction.ts @@ -0,0 +1,259 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { InputError } from '@backstage/errors'; +import { ScmIntegrationRegistry } from '@backstage/integration'; +import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; +import { AccessLevel } from '@gitbeaker/core'; +import { getClient, parseRepoHost } from '../util'; +import { examples } from './gitlabGroupAccessAction.examples'; + +type NonAdminAccessLevel = Exclude; + +const accessLevelMapping: Record = { + no_access: 0, + minimal_access: 5, + guest: 10, + planner: 15, + reporter: 20, + developer: 30, + maintainer: 40, + owner: 50, +}; + +function resolveAccessLevel(level: string | number): number { + if (typeof level === 'number') return level; + const resolved = accessLevelMapping[level.toLocaleLowerCase('en-US')]; + if (resolved === undefined) { + throw new InputError( + `Invalid access level: "${level}". Valid values are: ${Object.keys( + accessLevelMapping, + ).join(', ')} or a numeric GitLab access level`, + ); + } + return resolved; +} + +/** + * Creates a `gitlab:group:access` Scaffolder action. + * + * @public + */ +export const createGitlabGroupAccessAction = (options: { + integrations: ScmIntegrationRegistry; +}) => { + const { integrations } = options; + + return createTemplateAction({ + id: 'gitlab:group:access', + description: 'Adds or removes users and groups from a GitLab group', + supportsDryRun: true, + examples, + schema: { + input: { + repoUrl: z => + z.string({ + description: + "The host of the GitLab instance, for example 'gitlab.com' or 'gitlab.my-company.com'.", + }), + token: z => + z + .string({ + description: 'The token to use for authorization to GitLab', + }) + .optional(), + path: z => + z.union([z.number(), z.string()], { + description: + 'The ID or path of the group to add/remove members from', + }), + userIds: z => + z + .array(z.number(), { + description: 'The IDs of the users to add/remove', + }) + .optional(), + groupIds: z => + z + .array(z.number(), { + description: + 'The IDs of the groups to share with or unshare from the target group', + }) + .optional(), + action: z => + z + .enum(['add', 'remove'], { + description: + 'The action to perform: add or remove the members. Defaults to "add".', + }) + .default('add') + .optional(), + accessLevel: z => + z + .union([z.number(), z.string()], { + description: + 'The access level for the members. Can be a number (0=No access, 5=Minimal access, 10=Guest, 15=Planner, 20=Reporter, 30=Developer, 40=Maintainer, 50=Owner) or a string (e.g., "guest", "developer"). Defaults to 30 (Developer).', + }) + .default(30) + .optional(), + }, + output: { + userIds: z => + z + .array(z.number(), { + description: 'The IDs of the users that were added or removed', + }) + .optional(), + groupIds: z => + z + .array(z.number(), { + description: + 'The IDs of the groups that were shared with or unshared from', + }) + .optional(), + path: z => + z + .union([z.number(), z.string()], { + description: + 'The ID or path of the group the members were added to or removed from', + }) + .optional(), + accessLevel: z => + z + .number({ + description: + 'The access level granted to the members (only for add action)', + }) + .optional(), + }, + }, + async handler(ctx) { + const { + token, + repoUrl, + path, + userIds = [], + groupIds = [], + accessLevel: rawAccessLevel = 30, + action = 'add', + } = ctx.input; + + if (userIds.length === 0 && groupIds.length === 0) { + throw new InputError( + 'At least one of userIds or groupIds must be provided and non-empty', + ); + } + + const accessLevel = resolveAccessLevel(rawAccessLevel); + + if (ctx.isDryRun) { + if (userIds.length > 0) { + ctx.output('userIds', userIds); + } + if (groupIds.length > 0) { + ctx.output('groupIds', groupIds); + } + ctx.output('path', path); + if (action === 'add') { + ctx.output('accessLevel', accessLevel); + } + return; + } + + const host = parseRepoHost(repoUrl); + + const api = getClient({ host, integrations, token }); + + // Process users + for (const userId of userIds) { + ctx.logger.info( + `${action === 'add' ? 'Adding' : 'Removing'} user ${userId} ${ + action === 'add' ? 'to' : 'from' + } group ${path}`, + ); + await ctx.checkpoint({ + key: `gitlab.group.member.user.${action}.${path}.${userId}`, + fn: async () => { + if (action === 'add') { + try { + await api.GroupMembers.add( + path, + accessLevel as NonAdminAccessLevel, + { userId }, + ); + } catch (error: any) { + // If member already exists, try to edit instead + if (error.cause?.response?.status === 409) { + await api.GroupMembers.edit( + path, + userId, + accessLevel as NonAdminAccessLevel, + ); + return; + } + throw error; + } + } else { + await api.GroupMembers.remove(path, userId); + } + }, + }); + } + + // Process groups + for (const sharedGroupId of groupIds) { + ctx.logger.info( + `${action === 'add' ? 'Adding' : 'Removing'} group ${sharedGroupId} ${ + action === 'add' ? 'to' : 'from' + } group ${path}`, + ); + await ctx.checkpoint({ + key: `gitlab.group.member.group.${action}.${path}.${sharedGroupId}`, + fn: async () => { + if (action === 'add') { + try { + await api.Groups.share(path, sharedGroupId, accessLevel, {}); + } catch (error: any) { + // If group is already shared, unshare and re-share + if (error.cause?.response?.status === 409) { + await api.Groups.unshare(path, sharedGroupId, {}); + await api.Groups.share(path, sharedGroupId, accessLevel, {}); + return; + } + throw error; + } + } else { + await api.Groups.unshare(path, sharedGroupId, {}); + } + }, + }); + } + + ctx.output('path', path); + + if (userIds.length > 0) { + ctx.output('userIds', userIds); + } + if (groupIds.length > 0) { + ctx.output('groupIds', groupIds); + } + + if (action === 'add') { + ctx.output('accessLevel', accessLevel); + } + }, + }); +}; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts index 449c5980a4..f8004f105e 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts @@ -23,7 +23,7 @@ import { mockServices } from '@backstage/backend-test-utils'; const mockGitlabClient = { Groups: { - search: jest.fn(), + show: jest.fn(), create: jest.fn(), }, }; @@ -44,7 +44,9 @@ describe('gitlab:group:ensureExists', () => { }); it(`Should ${examples[0].description}`, async () => { - mockGitlabClient.Groups.search.mockResolvedValue([]); + mockGitlabClient.Groups.show.mockRejectedValue({ + cause: { response: { status: 404 } }, + }); mockGitlabClient.Groups.create.mockResolvedValue({ id: 3, full_path: 'group1', @@ -75,19 +77,16 @@ describe('gitlab:group:ensureExists', () => { expect(mockGitlabClient.Groups.create).toHaveBeenCalledWith( 'group1', 'group1', - {}, + { description: 'This is a top-level group' }, ); expect(mockContext.output).toHaveBeenCalledWith('groupId', 3); }); it(`Should ${examples[1].description}`, async () => { - mockGitlabClient.Groups.search.mockResolvedValue([ - { - id: 1, - full_path: 'group1', - }, - ]); + mockGitlabClient.Groups.show + .mockResolvedValueOnce({ id: 1, full_path: 'group1' }) + .mockRejectedValueOnce({ cause: { response: { status: 404 } } }); mockGitlabClient.Groups.create.mockResolvedValue({ id: 3, full_path: 'group1/group2', @@ -127,16 +126,10 @@ describe('gitlab:group:ensureExists', () => { }); it(`Should ${examples[2].description}`, async () => { - mockGitlabClient.Groups.search.mockResolvedValue([ - { - id: 1, - full_path: 'group1', - }, - { - id: 2, - full_path: 'group1/group2', - }, - ]); + mockGitlabClient.Groups.show + .mockResolvedValueOnce({ id: 1, full_path: 'group1' }) + .mockResolvedValueOnce({ id: 2, full_path: 'group1/group2' }) + .mockRejectedValueOnce({ cause: { response: { status: 404 } } }); mockGitlabClient.Groups.create.mockResolvedValue({ id: 3, full_path: 'group1/group2/group3', @@ -199,23 +192,17 @@ describe('gitlab:group:ensureExists', () => { input: yaml.parse(examples[3].example).steps[0].input, }); - expect(mockGitlabClient.Groups.search).not.toHaveBeenCalled(); + expect(mockGitlabClient.Groups.show).not.toHaveBeenCalled(); expect(mockGitlabClient.Groups.create).not.toHaveBeenCalled(); expect(mockContext.output).toHaveBeenCalledWith('groupId', 42); }); it(`Should ${examples[4].description}`, async () => { - mockGitlabClient.Groups.search.mockResolvedValue([ - { - id: 1, - full_path: 'group1', - }, - { - id: 2, - full_path: 'group1/group2', - }, - ]); + mockGitlabClient.Groups.show + .mockResolvedValueOnce({ id: 1, full_path: 'group1' }) + .mockResolvedValueOnce({ id: 2, full_path: 'group1/group2' }) + .mockRejectedValueOnce({ cause: { response: { status: 404 } } }); mockGitlabClient.Groups.create.mockResolvedValue({ id: 3, full_path: 'group1/group2/group3', @@ -255,20 +242,11 @@ describe('gitlab:group:ensureExists', () => { }); it(`Should ${examples[5].description}`, async () => { - mockGitlabClient.Groups.search.mockResolvedValue([ - { - id: 1, - full_path: 'group1', - }, - { - id: 2, - full_path: 'group1/group2', - }, - { - id: 3, - full_path: 'group1/group2/group3', - }, - ]); + mockGitlabClient.Groups.show + .mockResolvedValueOnce({ id: 1, full_path: 'group1' }) + .mockResolvedValueOnce({ id: 2, full_path: 'group1/group2' }) + .mockResolvedValueOnce({ id: 3, full_path: 'group1/group2/group3' }) + .mockRejectedValueOnce({ cause: { response: { status: 404 } } }); mockGitlabClient.Groups.create.mockResolvedValue({ id: 4, full_path: 'group1/group2/group3/group4', diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.ts index f379bd38ec..967c0c55e7 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.ts @@ -18,7 +18,7 @@ import yaml from 'yaml'; export const examples: TemplateExample[] = [ { - description: 'Creating a group at the top level', + description: 'Creating a group at the top level, with a description', example: yaml.stringify({ steps: [ { @@ -28,6 +28,7 @@ export const examples: TemplateExample[] = [ input: { repoUrl: 'gitlab.com', path: ['group1'], + description: 'This is a top-level group', }, }, ], diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.test.ts index 8e52ee999e..f74c80742c 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.test.ts @@ -22,7 +22,7 @@ import { mockServices } from '@backstage/backend-test-utils'; const mockGitlabClient = { Groups: { - search: jest.fn(), + show: jest.fn(), create: jest.fn(), }, }; @@ -65,16 +65,9 @@ describe('gitlab:group:ensureExists', () => { const mockContext = createMockActionContext(); it('should create a new group from string if it does not exists', async () => { - mockGitlabClient.Groups.search.mockResolvedValue([ - { - id: 1, - full_path: 'bar', - }, - { - id: 2, - full_path: 'foo', - }, - ]); + mockGitlabClient.Groups.show + .mockResolvedValueOnce({ id: 2, full_path: 'foo' }) + .mockRejectedValueOnce({ cause: { response: { status: 404 } } }); mockGitlabClient.Groups.create.mockResolvedValue({ id: 3, @@ -86,33 +79,81 @@ describe('gitlab:group:ensureExists', () => { input: { repoUrl: 'gitlab.com?repo=repo&owner=owner', path: ['foo', 'bar'], + description: 'my cool group', }, }); expect(mockGitlabClient.Groups.create).toHaveBeenCalledWith('bar', 'bar', { + description: 'my cool group', parentId: 2, }); expect(mockContext.output).toHaveBeenCalledWith('groupId', 3); }); - it('should create a new group from pathstring if it does not exists', async () => { - mockGitlabClient.Groups.search.mockResolvedValue([ - { + it('should only apply description to the last created group', async () => { + mockGitlabClient.Groups.create + .mockResolvedValueOnce({ id: 1, - full_path: 'bar', - }, - { - id: 2, full_path: 'foo', - }, - ]); + }) + .mockResolvedValueOnce({ + id: 2, + full_path: 'foo/bar', + }) + .mockResolvedValueOnce({ + id: 3, + full_path: 'foo/bar/baz', + }); - mockGitlabClient.Groups.create.mockResolvedValue({ - id: 3, - full_path: 'foo/bar', + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + path: ['foo', 'bar', 'baz'], + description: 'only for innermost group', + }, }); + expect(mockGitlabClient.Groups.create).toHaveBeenCalledTimes(3); + + // First group: no description + expect(mockGitlabClient.Groups.create).toHaveBeenNthCalledWith( + 1, + 'foo', + 'foo', + {}, + ); + + // Second group: no description + expect(mockGitlabClient.Groups.create).toHaveBeenNthCalledWith( + 2, + 'bar', + 'bar', + { parentId: 1 }, + ); + + // Third (last) group: has description + expect(mockGitlabClient.Groups.create).toHaveBeenNthCalledWith( + 3, + 'baz', + 'baz', + { parentId: 2, description: 'only for innermost group' }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('groupId', 3); + }); + + it('should create a new group from pathstring if it does not exists', async () => { + mockGitlabClient.Groups.show + .mockResolvedValueOnce({ id: 2, full_path: 'foo' }) + .mockRejectedValueOnce({ cause: { response: { status: 404 } } }) + .mockRejectedValueOnce({ cause: { response: { status: 404 } } }); + + mockGitlabClient.Groups.create + .mockResolvedValueOnce({ id: 3, full_path: 'foo/bar' }) + .mockResolvedValueOnce({ id: 4, full_path: 'foo/bar/baz' }); + await action.handler({ ...mockContext, input: { @@ -121,24 +162,31 @@ describe('gitlab:group:ensureExists', () => { }, }); - expect(mockGitlabClient.Groups.create).toHaveBeenCalledWith('bar', 'bar', { - parentId: 2, - }); + expect(mockGitlabClient.Groups.create).toHaveBeenNthCalledWith( + 1, + 'bar', + 'bar', + { + parentId: 2, + }, + ); - expect(mockContext.output).toHaveBeenCalledWith('groupId', 3); + expect(mockGitlabClient.Groups.create).toHaveBeenNthCalledWith( + 2, + 'baz', + 'baz', + { + parentId: 3, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('groupId', 4); }); it('should create a new group from object if it does not exists', async () => { - mockGitlabClient.Groups.search.mockResolvedValue([ - { - id: 1, - full_path: 'bar', - }, - { - id: 2, - full_path: 'foo', - }, - ]); + mockGitlabClient.Groups.show + .mockResolvedValueOnce({ id: 2, full_path: 'foo' }) + .mockRejectedValueOnce({ cause: { response: { status: 404 } } }); mockGitlabClient.Groups.create.mockResolvedValue({ id: 3, @@ -168,20 +216,9 @@ describe('gitlab:group:ensureExists', () => { }); it('should return existing group if it does exists', async () => { - mockGitlabClient.Groups.search.mockResolvedValue([ - { - id: 1, - full_path: 'bar', - }, - { - id: 2, - full_path: 'foo', - }, - { - id: 42, - full_path: 'foo/bar', - }, - ]); + mockGitlabClient.Groups.show + .mockResolvedValueOnce({ id: 2, full_path: 'foo' }) + .mockResolvedValueOnce({ id: 42, full_path: 'foo/bar' }); await action.handler({ ...mockContext, @@ -206,19 +243,17 @@ describe('gitlab:group:ensureExists', () => { }, }); - expect(mockGitlabClient.Groups.search).not.toHaveBeenCalled(); + expect(mockGitlabClient.Groups.show).not.toHaveBeenCalled(); expect(mockGitlabClient.Groups.create).not.toHaveBeenCalled(); expect(mockContext.output).toHaveBeenCalledWith('groupId', 42); }); it('should use the token from the integration config when none is provided', async () => { - mockGitlabClient.Groups.search.mockResolvedValue([ - { - id: 1, - full_path: 'foobar', - }, - ]); + mockGitlabClient.Groups.show.mockResolvedValue({ + id: 1, + full_path: 'foobar', + }); await action.handler({ ...mockContext, @@ -236,12 +271,10 @@ describe('gitlab:group:ensureExists', () => { }); it('should use a provided token as bearer authentication', async () => { - mockGitlabClient.Groups.search.mockResolvedValue([ - { - id: 1, - full_path: 'foobar', - }, - ]); + mockGitlabClient.Groups.show.mockResolvedValue({ + id: 1, + full_path: 'foobar', + }); await action.handler({ ...mockContext, diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.ts index c3d4fd74bf..b9b5a295b3 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.ts @@ -16,7 +16,7 @@ import { ScmIntegrationRegistry } from '@backstage/integration'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; -import { GroupSchema } from '@gitbeaker/rest'; +import { GroupSchema, CreateGroupOptions } from '@gitbeaker/rest'; import { getClient, parseRepoUrl } from '../util'; import { examples } from './gitlabGroupEnsureExists.examples'; @@ -47,6 +47,12 @@ export const createGitlabGroupEnsureExistsAction = (options: { description: 'The token to use for authorization to GitLab', }) .optional(), + description: z => + z + .string({ + description: 'The group’s description', + }) + .optional(), path: z => z .array( @@ -77,8 +83,7 @@ export const createGitlabGroupEnsureExistsAction = (options: { ctx.output('groupId', 42); return; } - - const { token, repoUrl, path } = ctx.input; + const { token, repoUrl, path, description } = ctx.input; const { host } = parseRepoUrl(repoUrl, integrations); @@ -86,14 +91,23 @@ export const createGitlabGroupEnsureExistsAction = (options: { let currentPath: string | null = null; let parentId: number | null = null; - for (const { name, slug } of pathIterator(path)) { + const pathParts = [...pathIterator(path)]; + const lastIndex = pathParts.length - 1; + for (let i = 0; i < pathParts.length; i++) { + const { name, slug } = pathParts[i]; const fullPath: string = currentPath ? `${currentPath}/${slug}` : slug; - const result = (await api.Groups.search( - fullPath, - )) as unknown as Array; // recast since the return type for search is wrong in the gitbeaker typings - const subGroup = result.find( - searchPathElem => searchPathElem.full_path === fullPath, - ); + let subGroup: GroupSchema | undefined; + try { + subGroup = (await api.Groups.show( + fullPath, + )) as unknown as GroupSchema; + } catch (error: any) { + // 404 means the group doesn't exist - we'll create it below + // Any other error should be thrown + if (error.cause?.response?.status !== 404) { + throw error; + } + } if (!subGroup) { ctx.logger.info(`creating missing group ${fullPath}`); @@ -101,17 +115,11 @@ export const createGitlabGroupEnsureExistsAction = (options: { key: `ensure.${name}.${slug}.${parentId}`, // eslint-disable-next-line no-loop-func fn: async () => { - return ( - await api.Groups.create( - name, - slug, - parentId - ? { - parentId: parentId, - } - : {}, - ) - )?.id; + const groupOptions: CreateGroupOptions = { + ...(parentId ? { parentId } : {}), + ...(description && i === lastIndex ? { description } : {}), + }; + return (await api.Groups.create(name, slug, groupOptions))?.id; }, }); } else { diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts index 66c72b4c46..c85341dd50 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts @@ -28,60 +28,14 @@ import { RepositoryTreeSchema, SimpleUserSchema, } from '@gitbeaker/rest'; -import path from 'path'; +import path from 'node:path'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { InputError } from '@backstage/errors'; -import { - LoggerService, - resolveSafeChildPath, -} from '@backstage/backend-plugin-api'; +import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { createGitlabApi, getErrorMessage } from './helpers'; import { examples } from './gitlabMergeRequest.examples'; -import { createHash } from 'crypto'; -function computeSha256(file: SerializedFile): string { - const hash = createHash('sha256'); - hash.update(file.content); - return hash.digest('hex'); -} - -async function getFileAction( - fileInfo: { file: SerializedFile; targetPath?: string }, - target: { repoID: string; branch: string }, - api: InstanceType, - logger: LoggerService, - remoteFiles: RepositoryTreeSchema[], - defaultCommitAction: - | 'create' - | 'delete' - | 'update' - | 'skip' - | 'auto' = 'auto', -): Promise<'create' | 'delete' | 'update' | 'skip'> { - if (defaultCommitAction === 'auto') { - const filePath = path.join(fileInfo.targetPath ?? '', fileInfo.file.path); - - if (remoteFiles?.some(remoteFile => remoteFile.path === filePath)) { - try { - const targetFile = await api.RepositoryFiles.show( - target.repoID, - filePath, - target.branch, - ); - if (computeSha256(fileInfo.file) === targetFile.content_sha256) { - return 'skip'; - } - } catch (error) { - logger.warn( - `Unable to retrieve detailed information for remote file ${filePath}`, - ); - } - return 'update'; - } - return 'create'; - } - return defaultCommitAction; -} +import { getFileAction } from '../util'; async function getReviewersFromApprovalRules( api: InstanceType, diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.examples.test.ts index 99b26682d8..be844f83e3 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.examples.test.ts @@ -85,6 +85,7 @@ describe('gitlab:projectVariableAction: create examples', () => { variableType: 'env_var', // Correctly using variableType environmentScope: '*', masked: false, + masked_and_hidden: false, protected: false, raw: false, }, @@ -109,6 +110,7 @@ describe('gitlab:projectVariableAction: create examples', () => { variableType: 'file', // Correctly using variableType protected: false, masked: false, + masked_and_hidden: false, raw: false, environmentScope: '*', }, @@ -131,6 +133,7 @@ describe('gitlab:projectVariableAction: create examples', () => { 'my_value', { masked: false, + masked_and_hidden: false, raw: false, environmentScope: '*', variableType: 'env_var', // Correctly using variableType @@ -159,6 +162,7 @@ describe('gitlab:projectVariableAction: create examples', () => { environmentScope: '*', variableType: 'env_var', // Correctly using variableType masked: true, + masked_and_hidden: false, }, ); }); @@ -182,6 +186,7 @@ describe('gitlab:projectVariableAction: create examples', () => { environmentScope: '*', variableType: 'env_var', // Correctly using variableType masked: false, + masked_and_hidden: false, raw: true, }, ); @@ -205,6 +210,7 @@ describe('gitlab:projectVariableAction: create examples', () => { protected: false, variableType: 'env_var', // Correctly using variableType masked: false, + masked_and_hidden: false, raw: false, environmentScope: 'production', }, @@ -229,6 +235,32 @@ describe('gitlab:projectVariableAction: create examples', () => { protected: false, variableType: 'env_var', // Correctly using variableType masked: false, + masked_and_hidden: false, + raw: false, + environmentScope: '*', + }, + ); + }); + + it(`Should ${examples[7].description}`, async () => { + mockGitlabClient.ProjectVariables.create.mockResolvedValue({ + token: 'TOKEN', + }); + + await action.handler({ + ...mockContext, + input: yaml.parse(examples[7].example).steps[0].input, + }); + + expect(mockGitlabClient.ProjectVariables.create).toHaveBeenCalledWith( + '999', + 'SECRET_TOKEN', + 'super-secret-token', + { + protected: false, + variableType: 'env_var', + masked: false, + masked_and_hidden: true, raw: false, environmentScope: '*', }, diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.examples.ts index 81d526f7c8..e1d040f028 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.examples.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.examples.ts @@ -157,4 +157,24 @@ export const examples: TemplateExample[] = [ ], }), }, + { + description: 'Create a GitLab project variable that is masked and hidden.', + example: yaml.stringify({ + steps: [ + { + id: 'createVariable', + action: 'gitlab:projectVariable:create', + name: 'Create GitLab Project Variable', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '999', + key: 'SECRET_TOKEN', + value: 'super-secret-token', + variableType: 'env_var', + maskedAndHidden: true, + }, + }, + ], + }), + }, ]; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.ts index 824a8566f7..031ff028bb 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.ts @@ -78,6 +78,13 @@ export const createGitlabProjectVariableAction = (options: { }) .default(false) .optional(), + maskedAndHidden: z => + z + .boolean({ + description: 'Whether the variable is masked and hidden', + }) + .default(false) + .optional(), raw: z => z .boolean({ @@ -103,6 +110,7 @@ export const createGitlabProjectVariableAction = (options: { variableType, variableProtected = false, masked = false, + maskedAndHidden = false, raw = false, environmentScope = '*', token, @@ -119,6 +127,7 @@ export const createGitlabProjectVariableAction = (options: { variableType: variableType as VariableType, protected: variableProtected, masked, + masked_and_hidden: maskedAndHidden, raw, environmentScope, }); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts index a3ae415704..24f2907041 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import path from 'path'; +import path from 'node:path'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { InputError } from '@backstage/errors'; import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; @@ -26,6 +26,9 @@ import { import { CommitAction } from '@gitbeaker/rest'; import { createGitlabApi, getErrorMessage } from './helpers'; import { examples } from './gitlabRepoPush.examples'; +import { getFileAction } from '../util'; +import { SerializedFile } from '@backstage/plugin-scaffolder-node'; +import { RepositoryTreeSchema } from '@gitbeaker/rest'; /** * Create a new action that commits into a gitlab repository. @@ -75,7 +78,7 @@ export const createGitlabRepoPushAction = (options: { .optional(), commitAction: z => z - .enum(['create', 'update', 'delete'], { + .enum(['create', 'update', 'delete', 'auto'], { description: 'The action to be used for git commit. Defaults to create, but can be set to update or delete', }) @@ -126,15 +129,50 @@ export const createGitlabRepoPushAction = (options: { gitignore: true, }); - const actions: CommitAction[] = fileContents.map(file => ({ - action: commitAction ?? 'create', - filePath: targetPath - ? path.posix.join(targetPath, file.path) - : file.path, - encoding: 'base64', - content: file.content.toString('base64'), - execute_filemode: file.executable, - })); + let remoteFiles: RepositoryTreeSchema[] = []; + if ((ctx.input.commitAction ?? 'auto') === 'auto') { + try { + remoteFiles = await api.Repositories.allRepositoryTrees(repoID, { + ref: branchName, + recursive: true, + path: targetPath ?? undefined, + }); + } catch (e) { + ctx.logger.warn( + `Could not retrieve the list of files for ${repoID} (branch: ${branchName}) : ${getErrorMessage( + e, + )}`, + ); + } + } + + const fileActionMap: { + file: SerializedFile; + action: 'create' | 'delete' | 'update' | 'skip'; + }[] = []; + for (const file of fileContents) { + const action = await getFileAction( + { file, targetPath }, + { repoID, branch: branchName }, + api, + ctx.logger, + remoteFiles, + ctx.input.commitAction, + ); + fileActionMap.push({ file, action }); + } + + const actions: CommitAction[] = fileActionMap + .filter(o => o.action !== 'skip') + .map(({ file, action }) => ({ + action: action as CommitAction['action'], + filePath: targetPath + ? path.posix.join(targetPath, file.path) + : file.path, + encoding: 'base64', + content: file.content.toString('base64'), + execute_filemode: file.executable, + })); const branchExists = await ctx.checkpoint({ key: `branch.exists.${repoID}.${branchName}`, diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabUserInfo.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabUserInfo.examples.test.ts new file mode 100644 index 0000000000..1d110fa64e --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabUserInfo.examples.test.ts @@ -0,0 +1,157 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ScmIntegrations } from '@backstage/integration'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { createGitlabUserInfoAction } from './gitlabUserInfo'; +import { examples } from './gitlabUserInfo.examples'; +import yaml from 'yaml'; +import { mockServices } from '@backstage/backend-test-utils'; + +const mockGitlabClient = { + Users: { + show: jest.fn(), + showCurrentUser: jest.fn(), + }, +}; +jest.mock('@gitbeaker/rest', () => ({ + Gitlab: class { + constructor() { + return mockGitlabClient; + } + }, +})); + +describe('gitlab:user:info examples', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'sample-token', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + { + host: 'gitlab.example.com', + token: 'example-token', + apiBaseUrl: 'https://gitlab.example.com/api/v4', + }, + ], + }, + }, + }); + const integrations = ScmIntegrations.fromConfig(config); + + const action = createGitlabUserInfoAction({ integrations }); + + it(`should ${examples[0].description}`, async () => { + const input = yaml.parse(examples[0].example).steps[0].input; + + const mockContext = createMockActionContext({ + input, + workspacePath: '/tmp/workspace', + }); + + mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ + id: 1, + username: 'currentuser', + name: 'Current User', + state: 'active', + web_url: 'https://gitlab.com/currentuser', + }); + + await action.handler(mockContext); + + expect(mockGitlabClient.Users.showCurrentUser).toHaveBeenCalled(); + expect(mockContext.output).toHaveBeenCalledWith('id', 1); + expect(mockContext.output).toHaveBeenCalledWith('username', 'currentuser'); + }); + + it(`should ${examples[1].description}`, async () => { + const input = yaml.parse(examples[1].example).steps[0].input; + + const mockContext = createMockActionContext({ + input, + workspacePath: '/tmp/workspace', + }); + + mockGitlabClient.Users.show.mockResolvedValue({ + id: 12345, + username: 'user12345', + name: 'User 12345', + state: 'active', + web_url: 'https://gitlab.com/user12345', + }); + + await action.handler(mockContext); + + expect(mockGitlabClient.Users.show).toHaveBeenCalledWith(12345); + expect(mockContext.output).toHaveBeenCalledWith('id', 12345); + }); + + it(`should ${examples[2].description}`, async () => { + const input = yaml.parse(examples[2].example).steps[0].input; + + const mockContext = createMockActionContext({ + input, + workspacePath: '/tmp/workspace', + }); + + mockGitlabClient.Users.show.mockResolvedValue({ + id: 12345, + username: 'user12345', + name: 'User 12345', + state: 'active', + web_url: 'https://gitlab.com/user12345', + }); + + await action.handler(mockContext); + + expect(mockGitlabClient.Users.show).toHaveBeenCalledWith(12345); + expect(mockContext.output).toHaveBeenCalledWith('id', 12345); + }); + + it(`should ${examples[3].description}`, async () => { + const input = yaml.parse(examples[3].example).steps[0].input; + + const mockContext = createMockActionContext({ + input, + workspacePath: '/tmp/workspace', + }); + + mockGitlabClient.Users.show.mockResolvedValue({ + id: 12345, + username: 'user12345', + name: 'User 12345', + state: 'active', + web_url: 'https://gitlab.example.com/user12345', + }); + + await action.handler(mockContext); + + expect(mockGitlabClient.Users.show).toHaveBeenCalledWith(12345); + expect(mockContext.output).toHaveBeenCalledWith('id', 12345); + expect(mockContext.output).toHaveBeenCalledWith( + 'webUrl', + 'https://gitlab.example.com/user12345', + ); + }); +}); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabUserInfo.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabUserInfo.examples.ts new file mode 100644 index 0000000000..7c84d6b475 --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabUserInfo.examples.ts @@ -0,0 +1,84 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { TemplateExample } from '@backstage/plugin-scaffolder-node'; +import yaml from 'yaml'; + +export const examples: TemplateExample[] = [ + { + description: 'Get current authenticated user information', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabUserInfo', + name: 'Get Current User Info', + action: 'gitlab:user:info', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + }, + }, + ], + }), + }, + { + description: 'Get user information by user ID', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabUserInfo', + name: 'Get User Info', + action: 'gitlab:user:info', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + userId: 12345, + }, + }, + ], + }), + }, + { + description: 'Get user information with a custom token', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabUserInfo', + name: 'Get User Info', + action: 'gitlab:user:info', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + token: '${{ secrets.GITLAB_TOKEN }}', + userId: 12345, + }, + }, + ], + }), + }, + { + description: 'Get user information from a self-hosted GitLab instance', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabUserInfo', + name: 'Get User Info', + action: 'gitlab:user:info', + input: { + repoUrl: 'gitlab.example.com?repo=repo&owner=owner', + userId: 12345, + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabUserInfo.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabUserInfo.test.ts new file mode 100644 index 0000000000..33dd95797d --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabUserInfo.test.ts @@ -0,0 +1,208 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ScmIntegrations } from '@backstage/integration'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { createGitlabUserInfoAction } from './gitlabUserInfo'; +import { mockServices } from '@backstage/backend-test-utils'; + +const mockGitlabClient = { + Users: { + show: jest.fn(), + showCurrentUser: jest.fn(), + }, +}; +jest.mock('@gitbeaker/rest', () => ({ + Gitlab: class { + constructor() { + return mockGitlabClient; + } + }, +})); + +describe('gitlab:user:info', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'myIntegrationsToken', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + ], + }, + }, + }); + const integrations = ScmIntegrations.fromConfig(config); + + const action = createGitlabUserInfoAction({ integrations }); + + it('should return current user info when no userId is specified', async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + }, + workspacePath: '/tmp/workspace', + }); + + mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ + id: 1, + username: 'johndoe', + name: 'John Doe', + state: 'active', + web_url: 'https://gitlab.com/johndoe', + email: 'john@example.com', + created_at: '2020-01-01T00:00:00Z', + public_email: 'john.public@example.com', + }); + + await action.handler(mockContext); + + expect(mockGitlabClient.Users.showCurrentUser).toHaveBeenCalled(); + expect(mockGitlabClient.Users.show).not.toHaveBeenCalled(); + + expect(mockContext.output).toHaveBeenCalledWith('id', 1); + expect(mockContext.output).toHaveBeenCalledWith('username', 'johndoe'); + expect(mockContext.output).toHaveBeenCalledWith('name', 'John Doe'); + expect(mockContext.output).toHaveBeenCalledWith('state', 'active'); + expect(mockContext.output).toHaveBeenCalledWith( + 'webUrl', + 'https://gitlab.com/johndoe', + ); + expect(mockContext.output).toHaveBeenCalledWith( + 'email', + 'john@example.com', + ); + expect(mockContext.output).toHaveBeenCalledWith( + 'createdAt', + '2020-01-01T00:00:00Z', + ); + expect(mockContext.output).toHaveBeenCalledWith( + 'publicEmail', + 'john.public@example.com', + ); + }); + + it('should return user info when userId is specified', async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + userId: 123, + }, + workspacePath: '/tmp/workspace', + }); + + mockGitlabClient.Users.show.mockResolvedValue({ + id: 123, + username: 'janedoe', + name: 'Jane Doe', + state: 'active', + web_url: 'https://gitlab.com/janedoe', + }); + + await action.handler(mockContext); + + expect(mockGitlabClient.Users.show).toHaveBeenCalledWith(123); + expect(mockGitlabClient.Users.showCurrentUser).not.toHaveBeenCalled(); + + expect(mockContext.output).toHaveBeenCalledWith('id', 123); + expect(mockContext.output).toHaveBeenCalledWith('username', 'janedoe'); + expect(mockContext.output).toHaveBeenCalledWith('name', 'Jane Doe'); + expect(mockContext.output).toHaveBeenCalledWith('state', 'active'); + expect(mockContext.output).toHaveBeenCalledWith( + 'webUrl', + 'https://gitlab.com/janedoe', + ); + }); + + it('should work with a custom token', async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + token: 'custom-oauth-token', + }, + workspacePath: '/tmp/workspace', + }); + + mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ + id: 1, + username: 'johndoe', + name: 'John Doe', + state: 'active', + web_url: 'https://gitlab.com/johndoe', + }); + + await action.handler(mockContext); + + expect(mockGitlabClient.Users.showCurrentUser).toHaveBeenCalled(); + expect(mockContext.output).toHaveBeenCalledWith('id', 1); + }); + + it('should handle minimal user response', async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + userId: 456, + }, + workspacePath: '/tmp/workspace', + }); + + mockGitlabClient.Users.show.mockResolvedValue({ + id: 456, + username: 'minimaluser', + name: 'Minimal User', + state: 'blocked', + web_url: 'https://gitlab.com/minimaluser', + }); + + await action.handler(mockContext); + + expect(mockContext.output).toHaveBeenCalledWith('id', 456); + expect(mockContext.output).toHaveBeenCalledWith('username', 'minimaluser'); + expect(mockContext.output).toHaveBeenCalledWith('name', 'Minimal User'); + expect(mockContext.output).toHaveBeenCalledWith('state', 'blocked'); + expect(mockContext.output).toHaveBeenCalledWith( + 'webUrl', + 'https://gitlab.com/minimaluser', + ); + // Optional fields should not be output if not present + expect(mockContext.output).not.toHaveBeenCalledWith( + 'email', + expect.anything(), + ); + }); + + it('should throw an error when the API call fails', async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + userId: 999, + }, + workspacePath: '/tmp/workspace', + }); + + mockGitlabClient.Users.show.mockRejectedValue(new Error('User not found')); + + await expect(action.handler(mockContext)).rejects.toThrow( + 'Failed to retrieve GitLab user info: Error: User not found', + ); + }); +}); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabUserInfo.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabUserInfo.ts new file mode 100644 index 0000000000..987b0568d6 --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabUserInfo.ts @@ -0,0 +1,136 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { InputError } from '@backstage/errors'; +import { ScmIntegrationRegistry } from '@backstage/integration'; +import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; +import { examples } from './gitlabUserInfo.examples'; +import { getClient, parseRepoUrl } from '../util'; +import { getErrorMessage } from './helpers'; + +/** + * Creates a `gitlab:user:info` Scaffolder action. + * + * @param options - Templating configuration. + * @public + */ +export const createGitlabUserInfoAction = (options: { + integrations: ScmIntegrationRegistry; +}) => { + const { integrations } = options; + + return createTemplateAction({ + id: 'gitlab:user:info', + description: + 'Retrieves information about a GitLab user or the current authenticated user', + examples, + schema: { + input: { + repoUrl: z => + z.string({ + description: `Accepts the format 'gitlab.com?repo=project_name&owner=group_name' where 'project_name' is the repository name and 'group_name' is a group or username`, + }), + token: z => + z + .string({ + description: 'The token to use for authorization to GitLab', + }) + .optional(), + userId: z => + z + .number({ + description: + 'User ID. If not provided, returns current authenticated user', + }) + .optional(), + }, + output: { + id: z => + z.number({ + description: 'The user ID', + }), + username: z => + z.string({ + description: 'The username', + }), + name: z => + z.string({ + description: 'The display name', + }), + state: z => + z.string({ + description: 'User state (active, blocked, etc.)', + }), + webUrl: z => + z.string({ + description: 'URL to user profile', + }), + email: z => + z + .string({ + description: + 'Email address (only available for current user or admins)', + }) + .optional(), + createdAt: z => + z + .string({ + description: 'User creation date', + }) + .optional(), + publicEmail: z => + z + .string({ + description: 'Public email address', + }) + .optional(), + }, + }, + async handler(ctx) { + try { + const { repoUrl, token, userId } = ctx.input; + + const { host } = parseRepoUrl(repoUrl, integrations); + const api = getClient({ host, integrations, token }); + + const userInfo = + userId !== undefined + ? await api.Users.show(userId) + : await api.Users.showCurrentUser(); + + ctx.output('id', userInfo.id); + ctx.output('username', userInfo.username); + ctx.output('name', userInfo.name); + ctx.output('state', userInfo.state); + ctx.output('webUrl', userInfo.web_url as string); + + if (userInfo.email) { + ctx.output('email', userInfo.email as string); + } + if (userInfo.created_at) { + ctx.output('createdAt', userInfo.created_at as string); + } + if (userInfo.public_email) { + ctx.output('publicEmail', userInfo.public_email as string); + } + } catch (error: any) { + throw new InputError( + `Failed to retrieve GitLab user info: ${getErrorMessage(error)}`, + ); + } + }, + }); +}; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/index.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/index.ts index c601c4656e..17364e9916 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/index.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/index.ts @@ -15,6 +15,7 @@ */ export * from './gitlab'; export * from './gitlabGroupEnsureExists'; +export * from './gitlabGroupAccessAction'; export * from './gitlabIssueCreate'; export * from './gitlabIssueEdit'; export * from './gitlabMergeRequest'; @@ -23,4 +24,5 @@ export * from './gitlabProjectAccessTokenCreate'; export * from './gitlabProjectDeployTokenCreate'; export * from './gitlabProjectVariableCreate'; export * from './gitlabRepoPush'; +export * from './gitlabUserInfo'; export { IssueType, IssueStateEvent } from '../commonGitlabConfig'; diff --git a/plugins/scaffolder-backend-module-gitlab/src/commonGitlabConfig.ts b/plugins/scaffolder-backend-module-gitlab/src/commonGitlabConfig.ts index 5e8d92338e..4208cdb805 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/commonGitlabConfig.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/commonGitlabConfig.ts @@ -16,7 +16,7 @@ /* We want to maintain the same information as an enum, so we disable the redeclaration warning */ /* eslint-disable @typescript-eslint/no-redeclare */ -import { z } from 'zod'; +import { z } from 'zod/v3'; const commonGitlabConfig = z.object({ repoUrl: z.string({ description: 'Repository Location' }), diff --git a/plugins/scaffolder-backend-module-gitlab/src/module.ts b/plugins/scaffolder-backend-module-gitlab/src/module.ts index 3c04f57077..653ce6e377 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/module.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/module.ts @@ -21,11 +21,13 @@ import { ScmIntegrations } from '@backstage/integration'; import { scaffolderAutocompleteExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha'; import { createGitlabGroupEnsureExistsAction, + createGitlabGroupAccessAction, createGitlabIssueAction, createGitlabProjectAccessTokenAction, createGitlabProjectDeployTokenAction, createGitlabProjectVariableAction, createGitlabRepoPushAction, + createGitlabUserInfoAction, createPublishGitlabAction, createPublishGitlabMergeRequestAction, createTriggerGitlabPipelineAction, @@ -54,12 +56,14 @@ export const gitlabModule = createBackendModule({ scaffolder.addActions( createGitlabGroupEnsureExistsAction({ integrations }), + createGitlabGroupAccessAction({ integrations }), createGitlabProjectMigrateAction({ integrations }), createGitlabIssueAction({ integrations }), createGitlabProjectAccessTokenAction({ integrations }), createGitlabProjectDeployTokenAction({ integrations }), createGitlabProjectVariableAction({ integrations }), createGitlabRepoPushAction({ integrations }), + createGitlabUserInfoAction({ integrations }), editGitlabIssueAction({ integrations }), createPublishGitlabAction({ config, integrations }), createPublishGitlabMergeRequestAction({ integrations }), diff --git a/plugins/scaffolder-backend-module-gitlab/src/util.ts b/plugins/scaffolder-backend-module-gitlab/src/util.ts index db1468b180..328f9d237e 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/util.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/util.ts @@ -14,15 +14,21 @@ * limitations under the License. */ +import { LoggerService } from '@backstage/backend-plugin-api'; import { InputError } from '@backstage/errors'; import { GitLabIntegration, ScmIntegrationRegistry, } from '@backstage/integration'; -import { Gitlab, GroupSchema } from '@gitbeaker/rest'; -import { z } from 'zod'; +import { Gitlab, GroupSchema, RepositoryTreeSchema } from '@gitbeaker/rest'; +import { z } from 'zod/v3'; import commonGitlabConfig from './commonGitlabConfig'; +import { SerializedFile } from '@backstage/plugin-scaffolder-node'; + +import { createHash } from 'node:crypto'; +import path from 'node:path'; + export const parseRepoHost = (repoUrl: string): string => { let parsed; try { @@ -184,3 +190,47 @@ export async function checkEpicScope( throw new InputError(`Could not find epic scope: ${error.message}`); } } + +function computeSha256(file: SerializedFile): string { + const hash = createHash('sha256'); + hash.update(file.content); + return hash.digest('hex'); +} + +export async function getFileAction( + fileInfo: { file: SerializedFile; targetPath?: string }, + target: { repoID: string; branch: string }, + api: InstanceType, + logger: LoggerService, + remoteFiles: RepositoryTreeSchema[], + defaultCommitAction: + | 'create' + | 'delete' + | 'update' + | 'skip' + | 'auto' = 'auto', +): Promise<'create' | 'delete' | 'update' | 'skip'> { + if (defaultCommitAction === 'auto') { + const filePath = path.join(fileInfo.targetPath ?? '', fileInfo.file.path); + + if (remoteFiles?.some(remoteFile => remoteFile.path === filePath)) { + try { + const targetFile = await api.RepositoryFiles.show( + target.repoID, + filePath, + target.branch, + ); + if (computeSha256(fileInfo.file) === targetFile.content_sha256) { + return 'skip'; + } + } catch (error) { + logger.warn( + `Unable to retrieve detailed information for remote file ${filePath}`, + ); + } + return 'update'; + } + return 'create'; + } + return defaultCommitAction; +} diff --git a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md index de28e741e9..9990df9a5d 100644 --- a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md @@ -1,5 +1,156 @@ # @backstage/plugin-scaffolder-backend-module-notifications +## 0.1.21 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/plugin-scaffolder-node@0.13.2 + - @backstage/plugin-notifications-node@0.2.25 + - @backstage/plugin-notifications-common@0.2.2 + +## 0.1.21-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.13.2-next.2 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/plugin-notifications-node@0.2.25-next.2 + - @backstage/plugin-notifications-common@0.2.2-next.0 + +## 0.1.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-notifications-node@0.2.25-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + +## 0.1.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-notifications-node@0.2.25-next.0 + - @backstage/plugin-scaffolder-node@0.13.1-next.0 + - @backstage/plugin-notifications-common@0.2.1 + +## 0.1.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-scaffolder-node@0.13.0 + - @backstage/plugin-notifications-node@0.2.24 + +## 0.1.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-notifications-node@0.2.24-next.2 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + +## 0.1.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/plugin-notifications-node@0.2.24-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/plugin-notifications-common@0.2.1 + +## 0.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/plugin-notifications-common@0.2.1 + - @backstage/plugin-notifications-node@0.2.24-next.0 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + +## 0.1.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-scaffolder-node@0.12.5 + - @backstage/plugin-notifications-common@0.2.1 + - @backstage/plugin-notifications-node@0.2.23 + +## 0.1.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/plugin-notifications-node@0.2.23-next.0 + - @backstage/plugin-notifications-common@0.2.0 + +## 0.1.18 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + +## 0.1.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + +## 0.1.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-notifications-node@0.2.22 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.1.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/plugin-notifications-node@0.2.22-next.1 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## 0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-notifications-node@0.2.22-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/plugin-notifications-common@0.2.0 + +## 0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-notifications-node@0.2.21 + - @backstage/plugin-scaffolder-node@0.12.1 + ## 0.1.16-next.2 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-notifications/package.json b/plugins/scaffolder-backend-module-notifications/package.json index 5d1d5e49ae..47f61a6aef 100644 --- a/plugins/scaffolder-backend-module-notifications/package.json +++ b/plugins/scaffolder-backend-module-notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-notifications", - "version": "0.1.16-next.2", + "version": "0.1.21", "description": "The notifications backend module for the scaffolder plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-rails/CHANGELOG.md b/plugins/scaffolder-backend-module-rails/CHANGELOG.md index a0f211073e..933d7d529a 100644 --- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md @@ -1,5 +1,186 @@ # @backstage/plugin-scaffolder-backend-module-rails +## 0.5.20 + +### Patch Changes + +- 2905c59: Removed unused `jest-when` dev dependency. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/errors@1.3.0 + - @backstage/plugin-scaffolder-node@0.13.2 + - @backstage/integration@2.0.1 + - @backstage/config@1.3.7 + +## 0.5.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/plugin-scaffolder-node@0.13.2-next.2 + - @backstage/integration@2.0.1-next.0 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/config@1.3.7-next.0 + +## 0.5.20-next.1 + +### Patch Changes + +- 2905c59: Removed unused `jest-when` dev dependency. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + +## 0.5.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-scaffolder-node@0.13.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration@2.0.0 + - @backstage/types@1.2.2 + +## 0.5.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-scaffolder-node@0.13.0 + - @backstage/integration@2.0.0 + +## 0.5.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + +## 0.5.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.5.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + +## 0.5.18 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-scaffolder-node@0.12.5 + +## 0.5.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## 0.5.18-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.5.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + - @backstage/integration@1.19.2 + +## 0.5.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + +## 0.5.16 + +### Patch Changes + +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.5.16-next.1 + +### Patch Changes + +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## 0.5.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.5.15 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + ## 0.5.15-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-rails/knip-report.md b/plugins/scaffolder-backend-module-rails/knip-report.md index c7301f2b85..bf8f3fb300 100644 --- a/plugins/scaffolder-backend-module-rails/knip-report.md +++ b/plugins/scaffolder-backend-module-rails/knip-report.md @@ -1,8 +1,8 @@ # Knip report -## Unused devDependencies (1) +## Unused dependencies (1) -| Name | Location | Severity | -| :-------- | :----------- | :------- | -| jest-when | plugins/scaffolder-backend-module-rails/package.json | error | +| Name | Location | Severity | +| :---------------- | :---------------- | :------- | +| @backstage/config | package.json:47:6 | error | diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index 0e625263f7..b6c7609970 100644 --- a/plugins/scaffolder-backend-module-rails/package.json +++ b/plugins/scaffolder-backend-module-rails/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-rails", - "version": "0.5.15-next.1", + "version": "0.5.20", "description": "A module for the scaffolder backend that lets you template projects using Rails", "backstage": { "role": "backend-plugin-module", @@ -59,7 +59,6 @@ "@backstage/plugin-scaffolder-node-test-utils": "workspace:^", "@types/command-exists": "^1.2.0", "@types/fs-extra": "^11.0.0", - "@types/node": "^20.16.0", - "jest-when": "^3.1.0" + "@types/node": "^22.13.14" } } diff --git a/plugins/scaffolder-backend-module-rails/report.api.md b/plugins/scaffolder-backend-module-rails/report.api.md index c7bbd19a72..b09b75591b 100644 --- a/plugins/scaffolder-backend-module-rails/report.api.md +++ b/plugins/scaffolder-backend-module-rails/report.api.md @@ -7,7 +7,7 @@ import { BackendFeature } from '@backstage/backend-plugin-api'; import { ScmIntegrations } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; import { UrlReaderService } from '@backstage/backend-plugin-api'; -import { Writable } from 'stream'; +import { Writable } from 'node:stream'; // @public export interface ContainerRunner { @@ -49,6 +49,7 @@ export function createFetchRailsAction(options: { | { template?: string | undefined; api?: boolean | undefined; + force?: boolean | undefined; database?: | 'sqlite3' | 'mysql' @@ -60,7 +61,6 @@ export function createFetchRailsAction(options: { | 'jdbcpostgresql' | 'jdbc' | undefined; - force?: boolean | undefined; minimal?: boolean | undefined; railsVersion?: 'edge' | 'master' | 'dev' | 'fromImage' | undefined; skipActionCable?: boolean | undefined; diff --git a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/ContainerRunner.ts b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/ContainerRunner.ts index a0b639148f..595f2dd342 100644 --- a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/ContainerRunner.ts +++ b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/ContainerRunner.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Writable } from 'stream'; +import { Writable } from 'node:stream'; /** * Handles the running of containers. diff --git a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.examples.test.ts b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.examples.test.ts index 5753c2f1ce..5ad1eba2fb 100644 --- a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.examples.test.ts +++ b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.examples.test.ts @@ -27,12 +27,14 @@ jest.mock('./railsNewRunner', () => { }; }); -import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; -import { resolve as resolvePath } from 'path'; +import { resolve as resolvePath } from 'node:path'; import { createFetchRailsAction } from './index'; import { fetchContents } from '@backstage/plugin-scaffolder-node'; -import { createMockDirectory } from '@backstage/backend-test-utils'; +import { + createMockDirectory, + mockServices, +} from '@backstage/backend-test-utils'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { examples } from './index.examples'; import yaml from 'yaml'; @@ -41,16 +43,7 @@ import { ContainerRunner } from './ContainerRunner'; describe('fetch:rails', () => { const mockDir = createMockDirectory(); - const integrations = ScmIntegrations.fromConfig( - new ConfigReader({ - integrations: { - azure: [ - { host: 'dev.azure.com', token: 'tokenlols' }, - { host: 'myazurehostnotoken.com' }, - ], - }, - }), - ); + const integrations = ScmIntegrations.fromConfig(mockServices.rootConfig()); const mockContext = createMockActionContext({ input: { diff --git a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.test.ts b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.test.ts index aaff033c21..a538066f59 100644 --- a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.test.ts +++ b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.test.ts @@ -27,29 +27,22 @@ jest.mock('./railsNewRunner', () => { }; }); -import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; -import { resolve as resolvePath } from 'path'; +import { resolve as resolvePath } from 'node:path'; import { createFetchRailsAction } from './index'; import { fetchContents } from '@backstage/plugin-scaffolder-node'; -import { createMockDirectory } from '@backstage/backend-test-utils'; +import { + createMockDirectory, + mockServices, +} from '@backstage/backend-test-utils'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; -import { Writable } from 'stream'; +import { Writable } from 'node:stream'; import { UrlReaderService } from '@backstage/backend-plugin-api'; import { ContainerRunner } from './ContainerRunner'; describe('fetch:rails', () => { const mockDir = createMockDirectory(); - const integrations = ScmIntegrations.fromConfig( - new ConfigReader({ - integrations: { - azure: [ - { host: 'dev.azure.com', token: 'tokenlols' }, - { host: 'myazurehostnotoken.com' }, - ], - }, - }), - ); + const integrations = ScmIntegrations.fromConfig(mockServices.rootConfig()); const mockContext = createMockActionContext({ input: { diff --git a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts index ca8416654a..83dc83bf98 100644 --- a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts +++ b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts @@ -22,9 +22,9 @@ import { fetchContents, } from '@backstage/plugin-scaffolder-node'; -import { resolve as resolvePath } from 'path'; +import { resolve as resolvePath } from 'node:path'; import { RailsNewRunner } from './railsNewRunner'; -import { PassThrough } from 'stream'; +import { PassThrough } from 'node:stream'; import { examples } from './index.examples'; import { UrlReaderService } from '@backstage/backend-plugin-api'; import { ContainerRunner } from './ContainerRunner'; diff --git a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsArgumentResolver.test.ts b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsArgumentResolver.test.ts index 935c8f96bd..424e5d30f8 100644 --- a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsArgumentResolver.test.ts +++ b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsArgumentResolver.test.ts @@ -15,8 +15,8 @@ */ import { railsArgumentResolver } from './railsArgumentResolver'; -import { sep as separatorPath } from 'path'; -import os from 'os'; +import { sep as separatorPath } from 'node:path'; +import os from 'node:os'; describe('railsArgumentResolver', () => { describe('when provide the parameter', () => { diff --git a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsArgumentResolver.ts b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsArgumentResolver.ts index 3887c405ae..225e4f95eb 100644 --- a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsArgumentResolver.ts +++ b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsArgumentResolver.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { sep as separatorPath } from 'path'; +import { sep as separatorPath } from 'node:path'; const Webpacker = { react: 'react', diff --git a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsNewRunner.test.ts b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsNewRunner.test.ts index be5da9383e..79c5945891 100644 --- a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsNewRunner.test.ts +++ b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsNewRunner.test.ts @@ -27,8 +27,8 @@ jest.mock( commandExists(...args), ); -import path from 'path'; -import { PassThrough } from 'stream'; +import path from 'node:path'; +import { PassThrough } from 'node:stream'; import { RailsNewRunner } from './railsNewRunner'; import { createMockDirectory } from '@backstage/backend-test-utils'; import { ContainerRunner } from './ContainerRunner'; diff --git a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsNewRunner.ts b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsNewRunner.ts index 73dd4417ad..0faf9b3a6f 100644 --- a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsNewRunner.ts +++ b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsNewRunner.ts @@ -15,7 +15,7 @@ */ import fs from 'fs-extra'; -import path from 'path'; +import path from 'node:path'; import { executeShellCommand } from '@backstage/plugin-scaffolder-node'; import commandExists from 'command-exists'; import { @@ -23,7 +23,7 @@ import { RailsRunOptions, } from './railsArgumentResolver'; import { JsonObject } from '@backstage/types'; -import { Writable } from 'stream'; +import { Writable } from 'node:stream'; import { ContainerRunner } from './ContainerRunner'; export class RailsNewRunner { diff --git a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md index f6122253a0..b952a7a401 100644 --- a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md @@ -1,5 +1,155 @@ # @backstage/plugin-scaffolder-backend-module-sentry +## 0.3.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/errors@1.3.0 + - @backstage/plugin-scaffolder-node@0.13.2 + - @backstage/config@1.3.7 + +## 0.3.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/plugin-scaffolder-node@0.13.2-next.2 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/config@1.3.7-next.0 + +## 0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + +## 0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-scaffolder-node@0.13.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-scaffolder-node@0.13.0 + +## 0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + +## 0.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.3.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + +## 0.3.1 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-scaffolder-node@0.12.5 + +## 0.3.1-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.3.0 + +### Minor Changes + +- ab606b2: Add ability to configure the API Base URL + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + +## 0.2.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + +## 0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## 0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.2.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + ## 0.2.15-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-sentry/README.md b/plugins/scaffolder-backend-module-sentry/README.md index ad5ee96d54..29f020da02 100644 --- a/plugins/scaffolder-backend-module-sentry/README.md +++ b/plugins/scaffolder-backend-module-sentry/README.md @@ -36,6 +36,14 @@ scaffolder: token: ${SENTRY_TOKEN} ``` +You can optionally override the default Sentry API Base URL (`https://sentry.io/api/0`) in your `app-config.yaml`: + +```yaml +scaffolder: + sentry: + apiBaseUrl: API-BASE-URL +``` + After that you can use the action in your template: ```yaml diff --git a/plugins/scaffolder-backend-module-sentry/config.d.ts b/plugins/scaffolder-backend-module-sentry/config.d.ts new file mode 100644 index 0000000000..2bd36cf6fb --- /dev/null +++ b/plugins/scaffolder-backend-module-sentry/config.d.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface Config { + scaffolder?: { + sentry?: { + apiBaseUrl?: string; + token?: string; + }; + }; +} diff --git a/plugins/scaffolder-backend-module-sentry/package.json b/plugins/scaffolder-backend-module-sentry/package.json index bc7b5173e7..3caf09ee99 100644 --- a/plugins/scaffolder-backend-module-sentry/package.json +++ b/plugins/scaffolder-backend-module-sentry/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-sentry", - "version": "0.2.15-next.1", + "version": "0.3.3", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", @@ -30,7 +30,8 @@ } }, "files": [ - "dist" + "dist", + "config.d.ts" ], "scripts": { "build": "backstage-cli package build", @@ -54,5 +55,6 @@ "@backstage/plugin-scaffolder-node-test-utils": "workspace:^", "@backstage/types": "workspace:^", "msw": "^2.0.0" - } + }, + "configSchema": "config.d.ts" } diff --git a/plugins/scaffolder-backend-module-sentry/report.api.md b/plugins/scaffolder-backend-module-sentry/report.api.md index 6fbece93c8..d9b38822a1 100644 --- a/plugins/scaffolder-backend-module-sentry/report.api.md +++ b/plugins/scaffolder-backend-module-sentry/report.api.md @@ -18,6 +18,7 @@ export function createSentryCreateProjectAction(options: { slug?: string | undefined; platform?: string | undefined; authToken?: string | undefined; + apiBaseUrl?: string | undefined; }, { [x: string]: any; @@ -33,6 +34,7 @@ export function createSentryFetchDSNAction(options: { organizationSlug: string; projectSlug: string; authToken?: string | undefined; + apiBaseUrl?: string | undefined; }, { dsn?: string | undefined; diff --git a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.examples.test.ts b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.examples.test.ts index 6fab15f59f..da9c342d7e 100644 --- a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.examples.test.ts +++ b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.examples.test.ts @@ -19,7 +19,7 @@ import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test- import { ConfigReader } from '@backstage/config'; import { ActionContext } from '@backstage/plugin-scaffolder-node'; import { JsonObject } from '@backstage/types'; -import { randomBytes } from 'crypto'; +import { randomBytes } from 'node:crypto'; import { setupServer } from 'msw/node'; import { HttpResponse, http } from 'msw'; import { createSentryCreateProjectAction } from './createProject'; @@ -45,6 +45,7 @@ describe('sentry:project:create action', () => { slug?: string; platform?: string; authToken?: string; + apiBaseUrl?: string; }> => createMockActionContext({ workspacePath: './dev/proj', @@ -279,4 +280,47 @@ describe('sentry:project:create action', () => { }, }); }); + + it(`should ${examples[3].description}`, async () => { + expect.assertions(3); + + let input; + try { + input = yaml.parse(examples[3].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + const action = createSentryCreateProjectAction(createScaffolderConfig()); + const actionContext = getActionContext(); + + worker.use( + http.post( + `${input.apiBaseUrl || 'https://sentry.io/api/0'}/teams/${ + input.organizationSlug + }/${input.teamSlug}/projects/`, + async ({ request }) => { + expect(request.headers.get('Authorization')).toBe( + `Bearer d16711beb516e1e910d2ede554dc1bf725654ef3c75e5a9106de9aec13d6gf95`, + ); + expect(request.headers.get('Content-Type')).toBe(`application/json`); + await expect(request.json()).resolves.toEqual({ + name: 'Scaffolded project A', + }); + return new HttpResponse(JSON.stringify({ id: 'mock-id' }), { + status: 201, + headers: { 'content-type': 'application/json' }, + }); + }, + ), + ); + + await action.handler({ + ...actionContext, + input: { + ...actionContext.input, + ...input, + }, + }); + }); }); diff --git a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.examples.ts b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.examples.ts index 1744a995a9..3d4d2af2d1 100644 --- a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.examples.ts +++ b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.examples.ts @@ -100,4 +100,24 @@ export const examples: TemplateExample[] = [ ], }), }, + { + description: 'Creates a Sentry project with a custom API base URL.', + example: yaml.stringify({ + steps: [ + { + id: 'create-sentry-project', + action: 'sentry:project:create', + name: 'Create a Sentry project with custom API base URL.', + input: { + organizationSlug: 'my-org', + teamSlug: 'team-a', + name: 'Scaffolded project A', + apiBaseUrl: 'https://custom.sentry.io/api/0', + authToken: + 'd16711beb516e1e910d2ede554dc1bf725654ef3c75e5a9106de9aec13d6gf95', + }, + }, + ], + }), + }, ]; diff --git a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.test.ts b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.test.ts index 0279c198a9..253ae44b61 100644 --- a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.test.ts +++ b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.test.ts @@ -20,7 +20,7 @@ import { ConfigReader } from '@backstage/config'; import { InputError } from '@backstage/errors'; import { ActionContext } from '@backstage/plugin-scaffolder-node'; import { JsonObject } from '@backstage/types'; -import { randomBytes } from 'crypto'; +import { randomBytes } from 'node:crypto'; import { setupServer } from 'msw/node'; import { HttpResponse, http } from 'msw'; import { createSentryCreateProjectAction } from './createProject'; @@ -44,6 +44,7 @@ describe('sentry:project:create action', () => { slug?: string; platform?: string; authToken?: string; + apiBaseUrl?: string; }> => createMockActionContext({ workspacePath: './dev/proj', @@ -245,4 +246,64 @@ describe('sentry:project:create action', () => { new InputError(`Sentry Response was: OUCH`), ); }); + + it('should create a Sentry project with custom apiBaseUrl.', async () => { + expect.assertions(3); + + const action = createSentryCreateProjectAction(createScaffolderConfig()); + const actionContext = getActionContext(); + actionContext.input = { + ...actionContext.input, + apiBaseUrl: 'https://custom.sentry.io/api/0', + }; + + worker.use( + http.post( + `https://custom.sentry.io/api/0/teams/${actionContext.input.organizationSlug}/${actionContext.input.teamSlug}/projects/`, + async ({ request }) => { + expect(request.headers.get('Authorization')).toBe( + `Bearer ${actionContext.input.authToken}`, + ); + expect(request.headers.get('Content-Type')).toBe(`application/json`); + await expect(request.json()).resolves.toEqual({ + name: actionContext.input.name, + }); + return HttpResponse.json({ id: 'mock-id' }, { status: 201 }); + }, + ), + ); + + await action.handler(actionContext); + }); + + it('should create a Sentry project with apiBaseUrl from config.', async () => { + expect.assertions(3); + + const action = createSentryCreateProjectAction( + createScaffolderConfig({ + sentry: { + apiBaseUrl: 'https://config.sentry.io/api/0', + }, + }), + ); + const actionContext = getActionContext(); + + worker.use( + http.post( + `https://config.sentry.io/api/0/teams/${actionContext.input.organizationSlug}/${actionContext.input.teamSlug}/projects/`, + async ({ request }) => { + expect(request.headers.get('Authorization')).toBe( + `Bearer ${actionContext.input.authToken}`, + ); + expect(request.headers.get('Content-Type')).toBe(`application/json`); + await expect(request.json()).resolves.toEqual({ + name: actionContext.input.name, + }); + return HttpResponse.json({ id: 'mock-id' }, { status: 201 }); + }, + ), + ); + + await action.handler(actionContext); + }); }); diff --git a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts index 57f8b349e0..421f0e005e 100644 --- a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts +++ b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts @@ -67,11 +67,25 @@ export function createSentryCreateProjectAction(options: { config: Config }) { 'authenticate via bearer auth token. Requires scope: project:write', }) .optional(), + apiBaseUrl: z => + z + .string({ + description: + 'Optional base URL for the Sentry API. e.g. https://sentry.io/api/0', + }) + .optional(), }, }, async handler(ctx) { - const { organizationSlug, teamSlug, name, slug, platform, authToken } = - ctx.input; + const { + organizationSlug, + teamSlug, + name, + slug, + platform, + authToken, + apiBaseUrl, + } = ctx.input; const body: any = { name: name, @@ -93,11 +107,16 @@ export function createSentryCreateProjectAction(options: { config: Config }) { throw new InputError(`No valid sentry token given`); } + const baseUrl = + apiBaseUrl || + config.getOptionalString('scaffolder.sentry.apiBaseUrl') || + 'https://sentry.io/api/0'; + 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/`, + `${baseUrl}/teams/${organizationSlug}/${teamSlug}/projects/`, { method: 'POST', headers: { diff --git a/plugins/scaffolder-backend-module-sentry/src/actions/fetchDSN.examples.test.ts b/plugins/scaffolder-backend-module-sentry/src/actions/fetchDSN.examples.test.ts index 0eed483cbe..556149bd1f 100644 --- a/plugins/scaffolder-backend-module-sentry/src/actions/fetchDSN.examples.test.ts +++ b/plugins/scaffolder-backend-module-sentry/src/actions/fetchDSN.examples.test.ts @@ -42,6 +42,7 @@ describe('sentry:fetch:dsn action', () => { organizationSlug: string; projectSlug: string; authToken?: string; + apiBaseUrl?: string; }> => createMockActionContext({ workspacePath: './dev/proj', @@ -147,4 +148,50 @@ describe('sentry:fetch:dsn action', () => { ); }, ); + + it(`should ${examples[2].description}`, async () => { + expect.assertions(3); + + let input; + try { + input = yaml.parse(examples[2].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + const action = createSentryFetchDSNAction(createScaffolderConfig()); + const actionContext = getActionContext(null); + + worker.use( + http.get( + `${input.apiBaseUrl || 'https://sentry.io/api/0'}/projects/${ + input.organizationSlug + }/${input.projectSlug}/keys/`, + async ({ request }) => { + expect(request.headers.get('Authorization')).toBe( + `Bearer b14711beb516e1e910d2ede554dc1bf725654ef3c75e5a9106de9aec13d5df97`, + ); + expect(request.headers.get('Content-Type')).toBe(`application/json`); + return HttpResponse.json( + [{ dsn: { public: 'https://abcdef1234567890@sentry.io/67890' } }], + { + status: 200, + }, + ); + }, + ), + ); + + await action.handler({ + ...actionContext, + input: { + ...actionContext.input, + ...input, + }, + }); + expect(actionContext.output).toHaveBeenCalledWith( + 'dsn', + 'https://abcdef1234567890@sentry.io/67890', + ); + }); }); diff --git a/plugins/scaffolder-backend-module-sentry/src/actions/fetchDSN.examples.ts b/plugins/scaffolder-backend-module-sentry/src/actions/fetchDSN.examples.ts index 0b1454ebac..6043cf3de7 100644 --- a/plugins/scaffolder-backend-module-sentry/src/actions/fetchDSN.examples.ts +++ b/plugins/scaffolder-backend-module-sentry/src/actions/fetchDSN.examples.ts @@ -54,4 +54,23 @@ export const examples: TemplateExample[] = [ ], }), }, + { + description: 'Fetch the DSN for a Sentry project with custom API base URL.', + example: yaml.stringify({ + steps: [ + { + id: 'fetch-sentry-dsn', + action: 'sentry:fetch:dsn', + name: 'Fetch DSN with custom API base URL', + input: { + organizationSlug: 'my-org', + projectSlug: 'my-project', + apiBaseUrl: 'https://sentry.io/api/0/custom', + authToken: + 'b14711beb516e1e910d2ede554dc1bf725654ef3c75e5a9106de9aec13d5df97', + }, + }, + ], + }), + }, ]; diff --git a/plugins/scaffolder-backend-module-sentry/src/actions/fetchDSN.test.ts b/plugins/scaffolder-backend-module-sentry/src/actions/fetchDSN.test.ts index 4140bf893b..87d9c93eaf 100644 --- a/plugins/scaffolder-backend-module-sentry/src/actions/fetchDSN.test.ts +++ b/plugins/scaffolder-backend-module-sentry/src/actions/fetchDSN.test.ts @@ -20,7 +20,7 @@ import { ConfigReader } from '@backstage/config'; import { InputError } from '@backstage/errors'; import { ActionContext } from '@backstage/plugin-scaffolder-node'; import { JsonObject } from '@backstage/types'; -import { randomBytes } from 'crypto'; +import { randomBytes } from 'node:crypto'; import { setupServer } from 'msw/node'; import { HttpResponse, http } from 'msw'; import { createSentryFetchDSNAction } from './fetchDSN'; @@ -41,6 +41,7 @@ describe('sentry:fetch:dsn action', () => { organizationSlug: string; projectSlug: string; authToken?: string; + apiBaseUrl?: string; }> => createMockActionContext({ workspacePath: './dev/proj', @@ -196,4 +197,66 @@ describe('sentry:fetch:dsn action', () => { new InputError('No public DSN found in project keys'), ); }); + + it('should fetch DSN with custom apiBaseUrl.', async () => { + expect.assertions(3); + + const action = createSentryFetchDSNAction(createScaffolderConfig()); + const actionContext = getActionContext(); + actionContext.input = { + ...actionContext.input, + apiBaseUrl: 'https://custom.sentry.io/api/0', + }; + const mockDSN = 'https://test@sentry.io/123'; + + worker.use( + http.get( + `https://custom.sentry.io/api/0/projects/${actionContext.input.organizationSlug}/${actionContext.input.projectSlug}/keys/`, + async ({ request }) => { + expect(request.headers.get('Authorization')).toBe( + `Bearer ${actionContext.input.authToken}`, + ); + expect(request.headers.get('Content-Type')).toBe(`application/json`); + return HttpResponse.json([{ dsn: { public: mockDSN } }], { + status: 200, + }); + }, + ), + ); + + await action.handler(actionContext); + expect(actionContext.output).toHaveBeenCalledWith('dsn', mockDSN); + }); + + it('should fetch DSN with apiBaseUrl from config.', async () => { + expect.assertions(3); + + const action = createSentryFetchDSNAction( + createScaffolderConfig({ + sentry: { + apiBaseUrl: 'https://config.sentry.io/api/0', + }, + }), + ); + const actionContext = getActionContext(); + const mockDSN = 'https://test@sentry.io/123'; + + worker.use( + http.get( + `https://config.sentry.io/api/0/projects/${actionContext.input.organizationSlug}/${actionContext.input.projectSlug}/keys/`, + async ({ request }) => { + expect(request.headers.get('Authorization')).toBe( + `Bearer ${actionContext.input.authToken}`, + ); + expect(request.headers.get('Content-Type')).toBe(`application/json`); + return HttpResponse.json([{ dsn: { public: mockDSN } }], { + status: 200, + }); + }, + ), + ); + + await action.handler(actionContext); + expect(actionContext.output).toHaveBeenCalledWith('dsn', mockDSN); + }); }); diff --git a/plugins/scaffolder-backend-module-sentry/src/actions/fetchDSN.ts b/plugins/scaffolder-backend-module-sentry/src/actions/fetchDSN.ts index 47fd4be39f..f77332b6f7 100644 --- a/plugins/scaffolder-backend-module-sentry/src/actions/fetchDSN.ts +++ b/plugins/scaffolder-backend-module-sentry/src/actions/fetchDSN.ts @@ -51,6 +51,13 @@ export function createSentryFetchDSNAction(options: { config: Config }) { 'authenticate via bearer auth token. Requires one of the following scopes: project:admin, project:read, project:write', }) .optional(), + apiBaseUrl: z => + z + .string({ + description: + 'Optional base URL for the Sentry API. e.g. https://sentry.io/api/0', + }) + .optional(), }, output: { dsn: z => @@ -62,7 +69,8 @@ export function createSentryFetchDSNAction(options: { config: Config }) { }, }, async handler(ctx) { - const { organizationSlug, projectSlug, authToken } = ctx.input; + const { organizationSlug, projectSlug, authToken, apiBaseUrl } = + ctx.input; const token = authToken ? authToken @@ -72,8 +80,13 @@ export function createSentryFetchDSNAction(options: { config: Config }) { throw new InputError(`No valid sentry token given`); } + const baseUrl = + apiBaseUrl || + config.getOptionalString('scaffolder.sentry.apiBaseUrl') || + 'https://sentry.io/api/0'; + const response = await fetch( - `https://sentry.io/api/0/projects/${organizationSlug}/${projectSlug}/keys/`, + `${baseUrl}/projects/${organizationSlug}/${projectSlug}/keys/`, { method: 'GET', headers: { diff --git a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md index d28d3230b7..e4bf94a038 100644 --- a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md @@ -1,5 +1,157 @@ # @backstage/plugin-scaffolder-backend-module-yeoman +## 0.4.21 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/plugin-scaffolder-node@0.13.2 + - @backstage/plugin-scaffolder-node-test-utils@0.3.10 + +## 0.4.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + - @backstage/plugin-scaffolder-node-test-utils@0.3.10-next.1 + +## 0.4.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-scaffolder-node@0.13.1-next.0 + - @backstage/plugin-scaffolder-node-test-utils@0.3.10-next.0 + - @backstage/types@1.2.2 + +## 0.4.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-scaffolder-node@0.13.0 + - @backstage/plugin-scaffolder-node-test-utils@0.3.9 + +## 0.4.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + - @backstage/plugin-scaffolder-node-test-utils@0.3.9-next.2 + +## 0.4.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node-test-utils@0.3.9-next.1 + +## 0.4.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + - @backstage/plugin-scaffolder-node-test-utils@0.3.9-next.0 + +## 0.4.19 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-scaffolder-node-test-utils@0.3.8 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-scaffolder-node@0.12.5 + +## 0.4.19-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-scaffolder-node-test-utils@0.3.8-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/types@1.2.2 + +## 0.4.18 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + - @backstage/plugin-scaffolder-node-test-utils@0.3.7 + +## 0.4.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + - @backstage/plugin-scaffolder-node-test-utils@0.3.7-next.1 + +## 0.4.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.2 + - @backstage/plugin-scaffolder-node-test-utils@0.3.7-next.0 + +## 0.4.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + - @backstage/plugin-scaffolder-node-test-utils@0.3.6 + +## 0.4.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + - @backstage/plugin-scaffolder-node-test-utils@0.3.6-next.1 + +## 0.4.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/plugin-scaffolder-node-test-utils@0.3.6-next.0 + - @backstage/types@1.2.2 + +## 0.4.16 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-scaffolder-node-test-utils@0.3.5 + - @backstage/plugin-scaffolder-node@0.12.1 + ## 0.4.16-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-yeoman/knip-report.md b/plugins/scaffolder-backend-module-yeoman/knip-report.md index 56fbe222c7..2661c35327 100644 --- a/plugins/scaffolder-backend-module-yeoman/knip-report.md +++ b/plugins/scaffolder-backend-module-yeoman/knip-report.md @@ -1,8 +1,2 @@ # Knip report -## Unused dependencies (1) - -| Name | Location | Severity | -| :------ | :----------- | :------- | -| winston | plugins/scaffolder-backend-module-yeoman/package.json | error | - diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json index ad0569e820..3d79b4d190 100644 --- a/plugins/scaffolder-backend-module-yeoman/package.json +++ b/plugins/scaffolder-backend-module-yeoman/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-yeoman", - "version": "0.4.16-next.1", + "version": "0.4.21", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", @@ -46,7 +46,6 @@ "@backstage/plugin-scaffolder-node": "workspace:^", "@backstage/plugin-scaffolder-node-test-utils": "workspace:^", "@backstage/types": "workspace:^", - "winston": "^3.2.1", "yaml": "^2.0.0", "yeoman-environment": "^3.9.1" }, diff --git a/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.examples.test.ts b/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.examples.test.ts index 4875089090..d7c0d0d20a 100644 --- a/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.examples.test.ts +++ b/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.examples.test.ts @@ -19,7 +19,7 @@ import { yeomanRun } from './yeomanRun'; jest.mock('./yeomanRun'); import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; -import os from 'os'; +import os from 'node:os'; import { createRunYeomanAction } from './yeoman'; import type { ActionContext } from '@backstage/plugin-scaffolder-node'; import { JsonObject } from '@backstage/types'; diff --git a/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.test.ts b/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.test.ts index 52bd95a3f9..0542bd2229 100644 --- a/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.test.ts +++ b/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.test.ts @@ -19,7 +19,7 @@ import { yeomanRun } from './yeomanRun'; jest.mock('./yeomanRun'); import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; -import os from 'os'; +import os from 'node:os'; import { createRunYeomanAction } from './yeoman'; import type { ActionContext } from '@backstage/plugin-scaffolder-node'; import { JsonObject } from '@backstage/types'; diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index b110b8b0cf..5c141ac3c9 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,529 @@ # @backstage/plugin-scaffolder-backend +## 3.4.0 + +### Minor Changes + +- 309b712: Added a new `execute-template` actions registry action that executes a scaffolder template with provided input values and returns a task ID for tracking progress. +- 5af48e7: Migrated permission registration to use the `PermissionsRegistryService` instead of the deprecated `createPermissionIntegrationRouter`. This fixes an issue where scaffolder permissions were not visible to RBAC plugins because the `actionsRegistryServiceRef` dependency caused an empty permissions metadata router to shadow the scaffolder's actual permission metadata. The old `createPermissionIntegrationRouter` path is retained as a fallback for standalone `createRouter` usage. + +### Patch Changes + +- 482ceed: Migrated from `assertError` to `toError` for error handling. +- 961e274: Migrated OpenTelemetry metrics to use the `MetricsService` from `@backstage/backend-plugin-api/alpha` instead of the raw `@opentelemetry/api` meter. +- 8a42f77: Fix handling of `after=0` in task events endpoint +- 4559806: Removed unnecessary empty `examples` array from actions bridged via the actions registry. +- 79453c0: Updated dependency `wait-for-expect` to `^4.0.0`. +- 3ef6078: Added support for conditional `if` filtering on output `links` and `text` items. Items where the `if` condition evaluates to false are now excluded from the task output. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/errors@1.3.0 + - @backstage/catalog-model@1.8.0 + - @backstage/plugin-catalog-node@2.2.0 + - @backstage/plugin-scaffolder-common@2.1.0 + - @backstage/plugin-scaffolder-node@0.13.2 + - @backstage/backend-openapi-utils@0.6.8 + - @backstage/integration@2.0.1 + - @backstage/plugin-permission-node@0.10.12 + - @backstage/config@1.3.7 + - @backstage/plugin-events-node@0.4.21 + - @backstage/plugin-permission-common@0.9.8 + +## 3.4.0-next.2 + +### Minor Changes + +- 5af48e7: Migrated permission registration to use the `PermissionsRegistryService` instead of the deprecated `createPermissionIntegrationRouter`. This fixes an issue where scaffolder permissions were not visible to RBAC plugins because the `actionsRegistryServiceRef` dependency caused an empty permissions metadata router to shadow the scaffolder's actual permission metadata. The old `createPermissionIntegrationRouter` path is retained as a fallback for standalone `createRouter` usage. + +### Patch Changes + +- 482ceed: Migrated from `assertError` to `toError` for error handling. +- 961e274: Migrated OpenTelemetry metrics to use the `MetricsService` from `@backstage/backend-plugin-api/alpha` instead of the raw `@opentelemetry/api` meter. +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/plugin-catalog-node@2.2.0-next.2 + - @backstage/plugin-scaffolder-node@0.13.2-next.2 + - @backstage/integration@2.0.1-next.0 + - @backstage/backend-openapi-utils@0.6.8-next.2 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/catalog-model@1.7.8-next.0 + - @backstage/config@1.3.7-next.0 + - @backstage/plugin-events-node@0.4.21-next.2 + - @backstage/plugin-permission-common@0.9.8-next.0 + - @backstage/plugin-permission-node@0.10.12-next.2 + - @backstage/plugin-scaffolder-common@2.0.1-next.0 + +## 3.3.0-next.1 + +### Minor Changes + +- 309b712: Added a new `execute-template` actions registry action that executes a scaffolder template with provided input values and returns a task ID for tracking progress. + +### Patch Changes + +- 4559806: Removed unnecessary empty `examples` array from actions bridged via the actions registry. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-openapi-utils@0.6.8-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + - @backstage/plugin-permission-node@0.10.12-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + +## 3.2.1-next.0 + +### Patch Changes + +- 79453c0: Updated dependency `wait-for-expect` to `^4.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-permission-node@0.10.12-next.0 + - @backstage/backend-openapi-utils@0.6.8-next.0 + - @backstage/plugin-catalog-node@2.1.1-next.0 + - @backstage/plugin-events-node@0.4.21-next.0 + - @backstage/plugin-scaffolder-node@0.13.1-next.0 + - @backstage/catalog-model@1.7.7 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration@2.0.0 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/plugin-scaffolder-common@2.0.0 + +## 3.2.0 + +### Minor Changes + +- c9b11eb: Added a new `list-scaffolder-tasks` action that allows querying scaffolder tasks with optional ownership filtering and pagination support +- 1b42218: Adds a new `get-scaffolder-task-logs` action to `@backstage/plugin-scaffolder-backend` that retrieves log events for a given scaffolder task, with optional support for retrieving only new events after a given event ID. +- 0fbcf23: Migrated OpenAPI schemas to 3.1. +- 7695dd2: Added a new `list-scaffolder-actions` action that returns all installed scaffolder actions with their schemas and examples +- e8736ea: Added secrets schema validation for task creation, retry, and dry-run endpoints. When a template defines `spec.secrets.schema`, the API validates provided secrets against the schema and returns a `400` error if validation fails. + +### Patch Changes + +- e27bd4e: Removed `@backstage/plugin-scaffolder-backend-module-bitbucket` from `package.json` as the package itself has been deprecated and the code deleted. +- 4f5ed06: Fixed a security vulnerability where server-configured environment secrets were exposed through the scaffolder dry-run endpoint. +- 30ff981: Fixed a security vulnerability where secrets could bypass log redaction when transformed through Nunjucks filters in scaffolder templates. +- 4e39e63: Removed unused dependencies +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- ccc20cf: create scaffolder MCP action to dry run a provided scaffolder template +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-scaffolder-node@0.13.0 + - @backstage/integration@2.0.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/plugin-scaffolder-common@2.0.0 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/plugin-permission-node@0.10.11 + - @backstage/catalog-model@1.7.7 + - @backstage/backend-openapi-utils@0.6.7 + - @backstage/plugin-events-node@0.4.20 + +## 3.2.0-next.2 + +### Minor Changes + +- e8736ea: Added secrets schema validation for task creation, retry, and dry-run endpoints. When a template defines `spec.secrets.schema`, the API validates provided secrets against the schema and returns a `400` error if validation fails. + +### Patch Changes + +- 30ff981: Fixed a security vulnerability where secrets could bypass log redaction when transformed through Nunjucks filters in scaffolder templates. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-scaffolder-common@2.0.0-next.2 + - @backstage/backend-openapi-utils@0.6.7-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.2 + - @backstage/plugin-events-node@0.4.20-next.1 + - @backstage/plugin-permission-node@0.10.11-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + +## 3.2.0-next.1 + +### Minor Changes + +- c9b11eb: Added a new `list-scaffolder-tasks` action that allows querying scaffolder tasks with optional ownership filtering and pagination support +- 0fbcf23: Migrated OpenAPI schemas to 3.1. +- 7695dd2: Added a new `list-scaffolder-actions` action that returns all installed scaffolder actions with their schemas and examples + +### Patch Changes + +- e27bd4e: Removed `@backstage/plugin-scaffolder-backend-module-bitbucket` from `package.json` as the package itself has been deprecated and the code deleted. +- ccc20cf: create scaffolder MCP action to dry run a provided scaffolder template +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-scaffolder-common@2.0.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.1 + - @backstage/backend-openapi-utils@0.6.7-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.20-next.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.11-next.0 + +## 3.1.4-next.0 + +### Patch Changes + +- 4e39e63: Removed unused dependencies +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/backend-openapi-utils@0.6.7-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.20-next.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.11-next.0 + - @backstage/plugin-scaffolder-common@1.7.7-next.0 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + +## 3.1.3 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 4fc7bf0: Removed unused dependency +- 0ce78b0: Support `if` conditions inside `each` loops for scaffolder steps +- 5e3ef57: Added `peerModules` metadata declaring recommended modules for cross-plugin integrations. +- 8148621: Moved `@backstage/backend-defaults` from `dependencies` to `devDependencies`. +- 1e669cc: Migrate audit events reference docs to http://backstage.io/docs. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-gitlab@0.11.3 + - @backstage/integration@1.20.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.17 + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.3 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.19 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.18 + - @backstage/plugin-scaffolder-backend-module-github@0.9.6 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.18 + - @backstage/backend-openapi-utils@0.6.6 + - @backstage/plugin-bitbucket-cloud-common@0.3.7 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-scaffolder-node@0.12.5 + - @backstage/plugin-auth-node@0.6.13 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.10 + - @backstage/plugin-events-node@0.4.19 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.18 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.18 + - @backstage/plugin-scaffolder-common@1.7.6 + +## 3.1.3-next.2 + +### Patch Changes + +- 8148621: Moved `@backstage/backend-defaults` from `dependencies` to `devDependencies`. +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-gitlab@0.11.3-next.2 + - @backstage/integration@1.20.0-next.2 + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-auth-node@0.6.13-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.17-next.1 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/plugin-permission-node@0.10.10-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.19-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.3-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.18-next.1 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.18-next.1 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.18-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.9.6-next.2 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## 3.1.3-next.1 + +### Patch Changes + +- 5e3ef57: Added `peerModules` metadata declaring recommended modules for cross-plugin integrations. +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.11.3-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/backend-defaults@0.15.2-next.1 + - @backstage/plugin-bitbucket-cloud-common@0.3.7-next.1 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.18-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.19-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.3-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.18-next.1 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.18-next.1 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.18-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.9.6-next.1 + - @backstage/plugin-scaffolder-common@1.7.6-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## 3.1.2-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 4fc7bf0: Removed unused dependency +- 1e669cc: Migrate audit events reference docs to http://backstage.io/docs. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.17-next.0 + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.2-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.19-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.18-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.9.5-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.11.2-next.0 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.18-next.0 + - @backstage/backend-openapi-utils@0.6.6-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.7-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/backend-defaults@0.15.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/plugin-permission-node@0.10.9-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.18-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.18-next.0 + - @backstage/plugin-scaffolder-common@1.7.6-next.0 + +## 3.1.1 + +### Patch Changes + +- 5012852: Remove unused abort controller in debug:wait action +- c641c14: Wrap some of the action logic with `resolveSafeChildPath` and improve symlink handling when fetching remote and local files +- 27f9061: REwrite] +- 872eb91: Upgrade `zod-to-json-schema` to latest version +- Updated dependencies + - @backstage/backend-defaults@0.15.0 + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + - @backstage/integration@1.19.2 + - @backstage/backend-openapi-utils@0.6.5 + - @backstage/plugin-scaffolder-backend-module-github@0.9.4 + - @backstage/plugin-auth-node@0.6.11 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.17 + - @backstage/plugin-permission-common@0.9.4 + - @backstage/plugin-permission-node@0.10.8 + - @backstage/plugin-bitbucket-cloud-common@0.3.6 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.16 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.18 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.17 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.17 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.17 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.11.1 + - @backstage/plugin-scaffolder-common@1.7.5 + +## 3.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + - @backstage/plugin-scaffolder-backend-module-github@0.9.4-next.1 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-permission-node@0.10.7 + +## 3.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.1-next.1 + - @backstage/integration@1.19.2-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.6-next.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.17-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.18-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.1-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.17-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.17-next.0 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.17-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.9.4-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.11.1-next.0 + - @backstage/plugin-scaffolder-common@1.7.5-next.0 + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.16-next.0 + +## 3.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.5-next.0 + - @backstage/backend-defaults@0.14.1-next.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration@1.19.0 + - @backstage/types@1.2.2 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-bitbucket-cloud-common@0.3.5 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-events-node@0.4.18 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.16 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.17 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.16 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.16 + - @backstage/plugin-scaffolder-backend-module-github@0.9.3 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.11.0 + - @backstage/plugin-scaffolder-common@1.7.4 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 3.1.0 + +### Minor Changes + +- a4cd405: Add `defaultEnvironment` config to scaffolder to enable more flexible and custom templates. Now it's possible enable access to default parameters and secrets in templates, improving security and reducing complexity. + +### Patch Changes + +- be5972b: Fixed a bug where config was not passed to NunjucksWorkflowRunner, causing defaultEnvironment to be undefined +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 2bae83a: Updated `isolated-vm` to `6.0.1` +- 25b560e: Internal change to support new versions of the `logform` library +- 8f4aded: Fixing OpenAPI definition +- 1226647: Updated dependency `esbuild` to `^0.27.0`. +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-gitlab@0.11.0 + - @backstage/integration@1.19.0 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.5 + - @backstage/backend-defaults@0.14.0 + - @backstage/backend-openapi-utils@0.6.4 + - @backstage/plugin-events-node@0.4.18 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-backend-module-github@0.9.3 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.17 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.16 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.16 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.16 + - @backstage/plugin-scaffolder-common@1.7.4 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 3.1.0-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 2bae83a: Updated `isolated-vm` to `6.0.1` +- 25b560e: Internal change to support new versions of the `logform` library +- 1226647: Updated dependency `esbuild` to `^0.27.0`. +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-gitlab@0.11.0-next.1 + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/backend-openapi-utils@0.6.4-next.1 + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-bitbucket-cloud-common@0.3.5-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15-next.1 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.16-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.17-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.16-next.1 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.16-next.1 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.16-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.9.3-next.1 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## 3.1.0-next.0 + +### Minor Changes + +- a4cd405: Add `defaultEnvironment` config to scaffolder to enable more flexible and custom templates. Now it's possible enable access to default parameters and secrets in templates, improving security and reducing complexity. + +### Patch Changes + +- 8f4aded: Fixing OpenAPI definition +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/backend-openapi-utils@0.6.4-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.16-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.17-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.16-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.16-next.0 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.16-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.9.3-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.10.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.5-next.0 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + +## 3.0.1 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.15 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.16 + - @backstage/plugin-bitbucket-cloud-common@0.3.4 + - @backstage/integration@1.18.2 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.10.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-events-node@0.4.17 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-scaffolder-node@0.12.1 + - @backstage/backend-openapi-utils@0.6.3 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.14 + - @backstage/plugin-permission-node@0.10.6 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.15 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.15 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.15 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.15 + - @backstage/plugin-scaffolder-backend-module-github@0.9.2 + - @backstage/plugin-scaffolder-common@1.7.3 + ## 3.0.1-next.1 ### Patch Changes @@ -5180,7 +5704,7 @@ - `step`: The name of the step that was run - `result`: A string describing whether the task ran successfully, failed, or was skipped - You can find a guide for running Prometheus metrics here: https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/prometheus-metrics.md + You can find a guide for running Prometheus metrics here: https://github.com/backstage/backstage/blob/384b7bac2e/contrib/docs/tutorials/prometheus-metrics.md - 5921b5ce49: - The GitLab Project ID for the `publish:gitlab:merge-request` action is now passed through the query parameter `project` in the `repoUrl`. It still allows people to not use the `projectid` and use the `repoUrl` with the `owner` and `repo` query parameters instead. This makes it easier to publish to repositories instead of writing the full path to the project. - 5025d2e8b6: Adds the ability to pass (an optional) array of strings that will be applied to the newly scaffolded repository as topic labels. @@ -5273,7 +5797,7 @@ - `step`: The name of the step that was run - `result`: A string describing whether the task ran successfully, failed, or was skipped - You can find a guide for running Prometheus metrics here: https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/prometheus-metrics.md + You can find a guide for running Prometheus metrics here: https://github.com/backstage/backstage/blob/384b7bac2e/contrib/docs/tutorials/prometheus-metrics.md ### Patch Changes diff --git a/plugins/scaffolder-backend/README.md b/plugins/scaffolder-backend/README.md index 34a378ba44..1e7dc91384 100644 --- a/plugins/scaffolder-backend/README.md +++ b/plugins/scaffolder-backend/README.md @@ -64,31 +64,63 @@ you will not have any templates available to use. These need to be [added to the To get up and running and try out some templates quickly, you can or copy the catalog locations from the [create-app template](https://github.com/backstage/backstage/blob/master/packages/create-app/templates/default-app/app-config.yaml.hbs). -## Audit Events +## Configuration -The Scaffolder backend emits audit events for various operations. Events are grouped logically by `eventId`, with `subEventId` providing further distinction when needed. +### Default Environment -**Template Events:** +The scaffolder supports a `defaultEnvironment` configuration that provides default parameters and secrets to all templates. This reduces template complexity and improves security by centralizing common values. -- **`template-parameter-schema`**: Retrieves template parameter schemas. (GET `/v2/templates/:namespace/:kind/:name/parameter-schema`) +```yaml +scaffolder: + defaultEnvironment: + parameters: + region: eu-west-1 + organizationName: acme-corp + defaultRegistry: registry.acme-corp.com + secrets: + AWS_ACCESS_KEY: ${AWS_ACCESS_KEY} + GITHUB_TOKEN: ${GITHUB_TOKEN} + DOCKER_REGISTRY_TOKEN: ${DOCKER_REGISTRY_TOKEN} +``` -**Action Events:** +#### Default parameters -- **`action-fetch`**: Retrieves installed actions. (GET `/v2/actions`) +Default parameters are accessible via `${{ environment.parameters.* }}` in templates. Default parameters are isolated in their own context to avoid naming conflicts. -**Task Events:** +```yaml + parameters: + - title: Fill in some steps + required: + - organizationName + properties: + organizationName: + title: organizationName + type: string + description: Unique name of the organization + ui:autofocus: true + ui:options: + rows: 5 -- **`task`**: Operations related to Scaffolder tasks. + steps: + - id: deploy + name: Deploy Application + action: aws:deploy + input: + region: ${{ environment.parameters.region }} # Resolves to defaultEnvironment.parameters.region + organization: ${{ parameters.organizationName }} # Resolves to frontend input value + otherOrganization: ${{ environment.parameters.organizationName }} # Resolves to defaultEnvironment.parameters.organizationName +``` - Filter on `actionType`. +#### Secrets - - **`create`**: Creates a new task. (POST `/v2/tasks`) - - **`list`**: Fetches details of all tasks. (GET `/v2/tasks`) - - **`get`**: Fetches details of a specific task. (GET `/v2/tasks/:taskId`) - - **`cancel`**: Cancels a running task. (POST `/v2/tasks/:taskId/cancel`) - - **`retry`**: Retries a failed task. (POST `/v2/tasks/:taskId/retry`) - - **`stream`**: Retrieves a stream of task logs. (GET `/v2/tasks/:taskId/eventstream`) - - **`events`**: Retrieves a snapshot of task logs. (GET `/v2/tasks/:taskId/events`) - - **`dry-run`**: Creates a dry-run task. (POST `/v2/dry-run`) All audit logs for events associated with dry runs have the `meta.isDryLog` flag set to `true`. - - **`stale-cancel`**: Automated cancellation of stale tasks. - - **`execute`**: Tracks the initiation and completion of a real scaffolder task execution. This event will not occur during dry runs. +Default secrets are resolved from environment variables and accessible via `${{ environment.secrets.* }}` in template actions. Secrets are only available during action execution, not in frontend forms. + +```yaml +- id: deploy + name: Deploy with credentials + action: aws:deploy + input: + accessKey: ${{ environment.secrets.AWS_ACCESS_KEY }} # Resolves to defaultEnvironment.secrets.AWS_ACCESS_KEY +``` + +**Security Note:** Secrets are automatically masked in logs and are only available to backend actions, never exposed to the frontend. diff --git a/plugins/scaffolder-backend/config.d.ts b/plugins/scaffolder-backend/config.d.ts index b4fb532901..866ededb50 100644 --- a/plugins/scaffolder-backend/config.d.ts +++ b/plugins/scaffolder-backend/config.d.ts @@ -108,5 +108,26 @@ export interface Config { auditor?: { taskParameterMaxLength?: number; }; + + /** + * Default environment variables and secrets available to all templates. + */ + defaultEnvironment?: { + /** + * Default parameters accessible via ${{ environment.parameters.* }} in templates. + */ + parameters?: { + [key: string]: string; + }; + + /** + * Secret values from environment variables accessible via ${{ environment.secrets.* }} in templates. + * Values should reference environment variables like ${SECRET_NAME}. + * @visibility secret + */ + secrets?: { + [key: string]: string; + }; + }; }; } diff --git a/plugins/scaffolder-backend/dev/index.ts b/plugins/scaffolder-backend/dev/index.ts new file mode 100644 index 0000000000..21ec460129 --- /dev/null +++ b/plugins/scaffolder-backend/dev/index.ts @@ -0,0 +1,22 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createBackend } from '@backstage/backend-defaults'; + +const backend = createBackend(); +backend.add(import('../src')); + +backend.start(); diff --git a/plugins/scaffolder-backend/knip-report.md b/plugins/scaffolder-backend/knip-report.md index eb8ddd05aa..2661c35327 100644 --- a/plugins/scaffolder-backend/knip-report.md +++ b/plugins/scaffolder-backend/knip-report.md @@ -1,27 +1,2 @@ # Knip report -## Unused dependencies (14) - -| Name | Location | Severity | -| :--------------------------------------------------------------- | :----------- | :------- | -| @backstage/plugin-catalog-backend-module-scaffolder-entity-model | plugins/scaffolder-backend/package.json | error | -| @backstage/plugin-scaffolder-backend-module-bitbucket-server | plugins/scaffolder-backend/package.json | error | -| @backstage/plugin-scaffolder-backend-module-bitbucket-cloud | plugins/scaffolder-backend/package.json | error | -| @backstage/plugin-scaffolder-backend-module-bitbucket | plugins/scaffolder-backend/package.json | error | -| @backstage/plugin-scaffolder-backend-module-gerrit | plugins/scaffolder-backend/package.json | error | -| @backstage/plugin-scaffolder-backend-module-github | plugins/scaffolder-backend/package.json | error | -| @backstage/plugin-scaffolder-backend-module-gitlab | plugins/scaffolder-backend/package.json | error | -| @backstage/plugin-scaffolder-backend-module-azure | plugins/scaffolder-backend/package.json | error | -| @backstage/plugin-scaffolder-backend-module-gitea | plugins/scaffolder-backend/package.json | error | -| @backstage/plugin-bitbucket-cloud-common | plugins/scaffolder-backend/package.json | error | -| @backstage/plugin-auth-node | plugins/scaffolder-backend/package.json | error | -| concat-stream | plugins/scaffolder-backend/package.json | error | -| p-limit | plugins/scaffolder-backend/package.json | error | -| tar | plugins/scaffolder-backend/package.json | error | - -## Unused devDependencies (1) - -| Name | Location | Severity | -| :------------------------- | :----------- | :------- | -| @backstage/backend-app-api | plugins/scaffolder-backend/package.json | error | - diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index ca17de1459..b17b0f1972 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "3.0.1-next.1", + "version": "3.4.0", "description": "The Backstage backend plugin that helps you create new things", "backstage": { "role": "backend-plugin", @@ -12,6 +12,9 @@ "@backstage/plugin-scaffolder-node", "@backstage/plugin-scaffolder-node-test-utils", "@backstage/plugin-scaffolder-react" + ], + "peerModules": [ + "@backstage/plugin-catalog-backend-module-scaffolder-entity-model" ] }, "publishConfig": { @@ -62,60 +65,43 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/backend-defaults": "workspace:^", "@backstage/backend-openapi-utils": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", - "@backstage/plugin-auth-node": "workspace:^", - "@backstage/plugin-bitbucket-cloud-common": "workspace:^", - "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "workspace:^", "@backstage/plugin-catalog-node": "workspace:^", "@backstage/plugin-events-node": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", "@backstage/plugin-permission-node": "workspace:^", - "@backstage/plugin-scaffolder-backend-module-azure": "workspace:^", - "@backstage/plugin-scaffolder-backend-module-bitbucket": "workspace:^", - "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud": "workspace:^", - "@backstage/plugin-scaffolder-backend-module-bitbucket-server": "workspace:^", - "@backstage/plugin-scaffolder-backend-module-gerrit": "workspace:^", - "@backstage/plugin-scaffolder-backend-module-gitea": "workspace:^", - "@backstage/plugin-scaffolder-backend-module-github": "workspace:^", - "@backstage/plugin-scaffolder-backend-module-gitlab": "workspace:^", "@backstage/plugin-scaffolder-common": "workspace:^", "@backstage/plugin-scaffolder-node": "workspace:^", "@backstage/types": "workspace:^", - "@opentelemetry/api": "^1.9.0", "@types/luxon": "^3.0.0", - "concat-stream": "^2.0.0", - "express": "^4.17.1", + "express": "^4.22.0", "fs-extra": "^11.2.0", "globby": "^11.0.0", "isbinaryfile": "^5.0.0", - "isolated-vm": "^5.0.1", + "isolated-vm": "^6.0.1", "jsonschema": "^1.5.0", "knex": "^3.0.0", "lodash": "^4.17.21", "logform": "^2.3.2", "luxon": "^3.0.0", "nunjucks": "^3.2.3", - "p-limit": "^3.1.0", "p-queue": "^6.6.2", "prom-client": "^15.0.0", - "tar": "^6.1.12", "triple-beam": "^1.4.1", "uuid": "^11.0.0", "winston": "^3.2.1", "winston-transport": "^4.7.0", "yaml": "^2.0.0", "zen-observable": "^0.10.0", - "zod": "^3.22.4", - "zod-to-json-schema": "^3.20.4" + "zod": "^3.25.76 || ^4.0.0", + "zod-to-json-schema": "^3.25.1" }, "devDependencies": { - "@backstage/backend-app-api": "workspace:^", "@backstage/backend-defaults": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", @@ -126,10 +112,10 @@ "@types/nunjucks": "^3.1.4", "@types/supertest": "^2.0.8", "@types/zen-observable": "^0.8.0", - "esbuild": "^0.25.0", + "esbuild": "^0.27.0", "strip-ansi": "^7.1.0", "supertest": "^7.0.0", - "wait-for-expect": "^3.0.2" + "wait-for-expect": "^4.0.0" }, "configSchema": "config.d.ts" } diff --git a/plugins/scaffolder-backend/sample-templates/all-templates.yaml b/plugins/scaffolder-backend/sample-templates/all-templates.yaml index 1e1fb5baf1..859bd717aa 100644 --- a/plugins/scaffolder-backend/sample-templates/all-templates.yaml +++ b/plugins/scaffolder-backend/sample-templates/all-templates.yaml @@ -7,6 +7,7 @@ spec: targets: - ./remote-templates.yaml - ./notifications-demo/template.yaml + - ./conditional-output-demo/template.yaml # For local development of a template, you can reference your local templates here. # Examples: # diff --git a/plugins/scaffolder-backend/sample-templates/bitbucket-demo/template.yaml b/plugins/scaffolder-backend/sample-templates/bitbucket-demo/template.yaml index 0a7c841d8f..a05411a7d0 100644 --- a/plugins/scaffolder-backend/sample-templates/bitbucket-demo/template.yaml +++ b/plugins/scaffolder-backend/sample-templates/bitbucket-demo/template.yaml @@ -21,7 +21,7 @@ spec: allowedHosts: - bitbucket.org # The rest of these options are optional. - # You can read more at: https://backstage.io/docs/reference/plugin-scaffolder.repourlpickerfieldextension + # You can read more at: https://backstage.io/api/stable/variables/_backstage_plugin-scaffolder.index.RepoUrlPickerFieldExtension.html # allowedOwners: # - WORKSPACE1 # - WORKSPACE2 @@ -69,7 +69,7 @@ spec: url: https://github.com/backstage/community/tree/main/backstage-community-sessions - id: publish name: Publish - action: publish:bitbucket + action: publish:bitbucketCloud input: description: This is ${{ parameters.name }} repoUrl: ${{ parameters.repoUrl }} @@ -81,10 +81,28 @@ spec: repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} catalogInfoPath: '/catalog-info.yaml' + - id: create-pr-file + name: Create PR File + action: fetch:plain + input: + targetPath: ./pr-demo + url: https://github.com/backstage/community/blob/main/README.md + - id: create-pr + name: Create Pull Request + action: publish:bitbucketCloud:pull-request + input: + repoUrl: ${{ parameters.repoUrl }} + title: Test PR for ${{ parameters.name }} + description: This is a test pull request created by the scaffolder template to test OAuth support + sourceBranch: scaffolder-pr-${{ parameters.name }} + targetBranch: master + output: links: - title: Repository url: ${{ steps['publish'].output.remoteUrl }} + - title: Pull Request + url: ${{ steps['create-pr'].output.pullRequestUrl }} - title: Open in catalog icon: catalog entityRef: ${{ steps['register'].output.entityRef }} diff --git a/plugins/scaffolder-backend/sample-templates/conditional-output-demo/template.yaml b/plugins/scaffolder-backend/sample-templates/conditional-output-demo/template.yaml new file mode 100644 index 0000000000..5ee5df76db --- /dev/null +++ b/plugins/scaffolder-backend/sample-templates/conditional-output-demo/template.yaml @@ -0,0 +1,62 @@ +apiVersion: scaffolder.backstage.io/v1beta3 +kind: Template +metadata: + name: conditional-output-demo + title: Conditional Output Demo + description: Demonstrates conditional if on output links and text +spec: + owner: backstage/techdocs-core + type: service + + parameters: + - title: Options + required: + - name + properties: + name: + title: Name + type: string + description: Unique name of the component + ui:autofocus: true + enableCI: + title: Enable CI + type: string + enum: + - 'Yes' + - 'No' + default: 'Yes' + showDetails: + title: Show Details + type: boolean + default: true + + steps: + - id: log + name: Log + action: debug:log + input: + message: 'Creating ${{ parameters.name }}' + + output: + links: + - title: Backstage Homepage + url: https://backstage.io + - if: ${{ parameters.enableCI === "Yes" }} + title: CI Dashboard + icon: dashboard + url: https://ci.example.com/${{ parameters.name }} + - if: ${{ parameters.enableCI === "Yes" }} + title: CI Docs + url: https://ci.example.com/docs + text: + - title: Summary + content: | + **Component:** `${{ parameters.name }}` + - if: ${{ parameters.showDetails }} + title: CI Details + content: | + **CI enabled:** ${{ parameters.enableCI }} + - if: ${{ parameters.enableCI === "Yes" }} + title: CI Setup Instructions + content: | + Run `ci-setup --name ${{ parameters.name }}` to configure CI. diff --git a/plugins/scaffolder-backend/scripts/Cookiecutter.dockerfile b/plugins/scaffolder-backend/scripts/Cookiecutter.dockerfile index d3b56cdacb..c6bf22ce29 100644 --- a/plugins/scaffolder-backend/scripts/Cookiecutter.dockerfile +++ b/plugins/scaffolder-backend/scripts/Cookiecutter.dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.22 +FROM alpine:3.23 RUN apk add --update \ git \ diff --git a/plugins/scaffolder-backend/scripts/build-nunjucks.js b/plugins/scaffolder-backend/scripts/build-nunjucks.js index 8183de77ee..df1d743829 100755 --- a/plugins/scaffolder-backend/scripts/build-nunjucks.js +++ b/plugins/scaffolder-backend/scripts/build-nunjucks.js @@ -18,7 +18,7 @@ /* eslint-disable no-restricted-syntax */ /* eslint-disable @backstage/no-undeclared-imports */ -const path = require('path'); +const path = require('node:path'); const NUNJUCKS_LICENSE = `/** * Copyright (c) 2012-2015, James Long diff --git a/plugins/scaffolder-backend/src/ScaffolderPlugin.test.ts b/plugins/scaffolder-backend/src/ScaffolderPlugin.test.ts index f3b3456e79..63886dd1fc 100644 --- a/plugins/scaffolder-backend/src/ScaffolderPlugin.test.ts +++ b/plugins/scaffolder-backend/src/ScaffolderPlugin.test.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { IncomingMessage } from 'http'; +import { IncomingMessage } from 'node:http'; import request from 'supertest'; import waitForExpect from 'wait-for-expect'; @@ -27,6 +27,12 @@ import { stringifyEntityRef } from '@backstage/catalog-model'; import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { scaffolderAutocompleteExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha'; +import { + scaffolderPermissions, + RESOURCE_TYPE_SCAFFOLDER_TEMPLATE, + RESOURCE_TYPE_SCAFFOLDER_ACTION, + RESOURCE_TYPE_SCAFFOLDER_TASK, +} from '@backstage/plugin-scaffolder-common/alpha'; import { scaffolderPlugin } from './ScaffolderPlugin'; @@ -1216,6 +1222,32 @@ describe('scaffolderPlugin', () => { }); }); + it('exposes permissions metadata via the well-known endpoint', async () => { + const { server } = await startTestBackend({ + features: [scaffolderPlugin], + }); + + const { body, status } = await request(server).get( + '/api/scaffolder/.well-known/backstage/permissions/metadata', + ); + + expect(status).toBe(200); + + const permissionNames = body.permissions.map( + (p: { name: string }) => p.name, + ); + for (const permission of scaffolderPermissions) { + expect(permissionNames).toContain(permission.name); + } + + const ruleResourceTypes = body.rules.map( + (r: { resourceType: string }) => r.resourceType, + ); + expect(ruleResourceTypes).toContain(RESOURCE_TYPE_SCAFFOLDER_TEMPLATE); + expect(ruleResourceTypes).toContain(RESOURCE_TYPE_SCAFFOLDER_ACTION); + expect(ruleResourceTypes).toContain(RESOURCE_TYPE_SCAFFOLDER_TASK); + }); + it('supports listing templating extensions', async () => { const { server } = await startTestBackend({ features: [scaffolderPlugin], diff --git a/plugins/scaffolder-backend/src/ScaffolderPlugin.ts b/plugins/scaffolder-backend/src/ScaffolderPlugin.ts index 39bab85c7e..559adf66bb 100644 --- a/plugins/scaffolder-backend/src/ScaffolderPlugin.ts +++ b/plugins/scaffolder-backend/src/ScaffolderPlugin.ts @@ -23,6 +23,7 @@ import { catalogServiceRef } from '@backstage/plugin-catalog-node'; import { eventsServiceRef } from '@backstage/plugin-events-node'; import { scaffolderActionsExtensionPoint, + scaffolderServiceRef, TaskBroker, TemplateAction, } from '@backstage/plugin-scaffolder-node'; @@ -59,7 +60,12 @@ import { convertFiltersToRecord, convertGlobalsToRecord, } from './util/templating'; -import { actionsServiceRef } from '@backstage/backend-plugin-api/alpha'; +import { + actionsServiceRef, + actionsRegistryServiceRef, + metricsServiceRef, +} from '@backstage/backend-plugin-api/alpha'; +import { createScaffolderActions } from './actions'; /** * Scaffolder plugin @@ -136,6 +142,7 @@ export const scaffolderPlugin = createBackendPlugin({ lifecycle: coreServices.rootLifecycle, reader: coreServices.urlReader, permissions: coreServices.permissions, + permissionsRegistry: coreServices.permissionsRegistry, database: coreServices.database, auth: coreServices.auth, httpRouter: coreServices.httpRouter, @@ -144,6 +151,9 @@ export const scaffolderPlugin = createBackendPlugin({ catalog: catalogServiceRef, events: eventsServiceRef, actionsRegistry: actionsServiceRef, + actionsRegistryService: actionsRegistryServiceRef, + scaffolderService: scaffolderServiceRef, + metrics: metricsServiceRef, }, async init({ logger, @@ -156,9 +166,13 @@ export const scaffolderPlugin = createBackendPlugin({ httpAuth, catalog, permissions, + permissionsRegistry, events, auditor, actionsRegistry, + actionsRegistryService, + scaffolderService, + metrics, }) { const log = loggerToWinstonLogger(logger); const integrations = ScmIntegrations.fromConfig(config); @@ -211,6 +225,12 @@ export const scaffolderPlugin = createBackendPlugin({ `Starting scaffolder with the following actions enabled ${actionIds}`, ); + createScaffolderActions({ + actionsRegistry: actionsRegistryService, + scaffolderService, + auth, + }); + const router = await createRouter({ logger, config, @@ -224,11 +244,13 @@ export const scaffolderPlugin = createBackendPlugin({ auth, httpAuth, permissions, + permissionsRegistry, autocompleteHandlers, additionalWorkspaceProviders, events, auditor, actionsRegistry, + metrics, }); httpRouter.use(router); }, diff --git a/plugins/scaffolder-backend/src/actions/createDryRunTemplateAction.test.ts b/plugins/scaffolder-backend/src/actions/createDryRunTemplateAction.test.ts new file mode 100644 index 0000000000..cdd69d1541 --- /dev/null +++ b/plugins/scaffolder-backend/src/actions/createDryRunTemplateAction.test.ts @@ -0,0 +1,267 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createDryRunTemplateAction } from './createDryRunTemplateAction'; +import { actionsRegistryServiceMock } from '@backstage/backend-test-utils/alpha'; +import { scaffolderServiceMock } from '@backstage/plugin-scaffolder-node/testUtils'; + +type DryRunTemplateOutput = { + valid: boolean; + message: string; + errors?: string[]; + log?: Array<{ message: string; stepId?: string; status?: string }>; + output?: Record; + steps?: Array<{ id: string; name: string; action: string }>; +}; + +const validTemplateYaml = ` +apiVersion: scaffolder.backstage.io/v1beta3 +kind: Template +metadata: + name: test-template + namespace: default + title: Test Template +spec: + type: service + steps: + - id: step-1 + name: Step One + action: debug:log + input: + message: hello +`; + +const invalidYaml = ` +apiVersion: scaffolder.backstage.io/v1beta3 +kind: Template +metadata: + name: test + invalid: yaml: syntax: error +spec: + type: service + steps: [ +`; + +describe('createDryRunTemplateAction', () => { + const mockScaffolderService = scaffolderServiceMock.mock(); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('should return success with logs when dry-run succeeds', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const dryRunResult = { + log: [ + { + body: { + message: 'Step completed', + stepId: 'step-1', + status: 'completed' as const, + }, + }, + ], + output: { result: 'ok' }, + steps: [{ id: 'step-1', name: 'Step One', action: 'debug:log' }], + directoryContents: [], + }; + + mockScaffolderService.dryRun.mockResolvedValue(dryRunResult); + + createDryRunTemplateAction({ + actionsRegistry: mockActionsRegistry, + scaffolderService: mockScaffolderService, + }); + + const result = await mockActionsRegistry.invoke({ + id: 'test:dry-run-template', + input: { templateYaml: validTemplateYaml }, + }); + + expect(result.output).toEqual({ + valid: true, + message: 'Template validation successful', + log: [ + { + message: 'Step completed', + stepId: 'step-1', + status: 'completed', + }, + ], + output: { result: 'ok' }, + steps: [{ id: 'step-1', name: 'Step One', action: 'debug:log' }], + }); + + expect(mockScaffolderService.dryRun).toHaveBeenCalledWith( + { + template: expect.objectContaining({ + apiVersion: 'scaffolder.backstage.io/v1beta3', + kind: 'Template', + metadata: expect.objectContaining({ + name: 'test-template', + }), + }), + values: {}, + directoryContents: [], + }, + { credentials: expect.anything() }, + ); + }); + + it('should pass values and files to the scaffolder service', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + mockScaffolderService.dryRun.mockResolvedValue({ + log: [], + output: {}, + steps: [], + directoryContents: [], + }); + + createDryRunTemplateAction({ + actionsRegistry: mockActionsRegistry, + scaffolderService: mockScaffolderService, + }); + + const values = { name: 'my-app' }; + const files = [ + { + path: 'README.md', + content: 'hello', + }, + ]; + + await mockActionsRegistry.invoke({ + id: 'test:dry-run-template', + input: { + templateYaml: validTemplateYaml, + values, + files, + }, + }); + + expect(mockScaffolderService.dryRun).toHaveBeenCalledWith( + { + template: expect.any(Object), + values, + directoryContents: [ + { + path: 'README.md', + base64Content: Buffer.from('hello').toString('base64'), + }, + ], + }, + { credentials: expect.anything() }, + ); + }); + + it('should return validation errors when YAML is invalid', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + + createDryRunTemplateAction({ + actionsRegistry: mockActionsRegistry, + scaffolderService: mockScaffolderService, + }); + + const result = await mockActionsRegistry.invoke({ + id: 'test:dry-run-template', + input: { templateYaml: invalidYaml }, + }); + + expect(result.output).toEqual({ + valid: false, + message: 'Failed to parse YAML template', + errors: expect.arrayContaining([ + expect.stringContaining('YAML parsing error'), + ]), + }); + + expect(mockScaffolderService.dryRun).not.toHaveBeenCalled(); + }); + + it('should propagate errors from the scaffolder service', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + + mockScaffolderService.dryRun.mockRejectedValue( + new Error('Authentication error'), + ); + + createDryRunTemplateAction({ + actionsRegistry: mockActionsRegistry, + scaffolderService: mockScaffolderService, + }); + + await expect( + mockActionsRegistry.invoke({ + id: 'test:dry-run-template', + input: { templateYaml: validTemplateYaml }, + }), + ).rejects.toThrow('Authentication error'); + }); + + it('should use default empty values and files when not provided', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + mockScaffolderService.dryRun.mockResolvedValue({ + log: [], + output: {}, + steps: [], + directoryContents: [], + }); + + createDryRunTemplateAction({ + actionsRegistry: mockActionsRegistry, + scaffolderService: mockScaffolderService, + }); + + await mockActionsRegistry.invoke({ + id: 'test:dry-run-template', + input: { templateYaml: validTemplateYaml }, + }); + + expect(mockScaffolderService.dryRun).toHaveBeenCalledWith( + { + template: expect.any(Object), + values: {}, + directoryContents: [], + }, + { credentials: expect.anything() }, + ); + }); + + it('should map log entries from body fields', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + mockScaffolderService.dryRun.mockResolvedValue({ + log: [{ body: { message: 'Plain log message' } }], + output: {}, + steps: [], + directoryContents: [], + }); + + createDryRunTemplateAction({ + actionsRegistry: mockActionsRegistry, + scaffolderService: mockScaffolderService, + }); + + const result = await mockActionsRegistry.invoke({ + id: 'test:dry-run-template', + input: { templateYaml: validTemplateYaml }, + }); + + const output = result.output as DryRunTemplateOutput; + expect(output.valid).toBe(true); + expect(output.log).toEqual([ + { message: 'Plain log message', stepId: undefined, status: undefined }, + ]); + }); +}); diff --git a/plugins/scaffolder-backend/src/actions/createDryRunTemplateAction.ts b/plugins/scaffolder-backend/src/actions/createDryRunTemplateAction.ts new file mode 100644 index 0000000000..a96e52fb4d --- /dev/null +++ b/plugins/scaffolder-backend/src/actions/createDryRunTemplateAction.ts @@ -0,0 +1,156 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ActionsRegistryService } from '@backstage/backend-plugin-api/alpha'; +import { ScaffolderService } from '@backstage/plugin-scaffolder-node'; +import { JsonObject } from '@backstage/types'; +import yaml from 'yaml'; + +const MAX_CONTENT_SIZE = 64 * 1024; + +function base64EncodeContent(content: string): string { + if (content.length > MAX_CONTENT_SIZE) { + return Buffer.from('', 'utf8').toString('base64'); + } + return Buffer.from(content, 'utf8').toString('base64'); +} + +export const createDryRunTemplateAction = ({ + actionsRegistry, + scaffolderService, +}: { + actionsRegistry: ActionsRegistryService; + scaffolderService: ScaffolderService; +}) => { + actionsRegistry.register({ + name: 'dry-run-template', + title: 'Dry Run Scaffolder Template', + attributes: { + destructive: false, + readOnly: true, + idempotent: true, + }, + description: + 'Dry-runs a scaffolder template to validate it without making changes. Returns success with execution logs, or errors for validation failures.', + schema: { + input: z => + z.object({ + templateYaml: z + .string() + .describe( + 'The YAML content of the scaffolder template to validate', + ), + values: z + .record(z.unknown()) + .optional() + .describe('Input values for template parameters'), + files: z + .array( + z.object({ + path: z + .string() + .describe('File path relative to template root'), + content: z.string().describe('File content'), + }), + ) + .optional() + .describe('Files required for running the template'), + }), + output: z => + z.object({ + valid: z.boolean().describe('Whether the template is valid'), + message: z + .string() + .describe('Success message or validation error details'), + errors: z + .array(z.string()) + .optional() + .describe('List of validation errors'), + log: z + .array( + z.object({ + message: z.string(), + stepId: z.string().optional(), + status: z.string().optional(), + }), + ) + .optional() + .describe('Execution log from dry-run'), + output: z + .record(z.unknown()) + .optional() + .describe('Template output values'), + steps: z + .array( + z.object({ + id: z.string(), + name: z.string(), + action: z.string(), + }), + ) + .optional() + .describe('Parsed template steps'), + }), + }, + action: async ({ input, credentials }) => { + const { templateYaml, values = {}, files = [] } = input; + + let template; + try { + template = yaml.parse(templateYaml); + } catch (parseError) { + const yamlError = parseError as yaml.YAMLParseError; + return { + output: { + valid: false, + message: 'Failed to parse YAML template', + errors: [ + `YAML parsing error: ${yamlError.message}`, + yamlError.linePos + ? `At line ${yamlError.linePos[0].line}, column ${yamlError.linePos[0].col}` + : '', + ].filter(Boolean), + }, + }; + } + + const result = await scaffolderService.dryRun( + { + template, + values: values as JsonObject, + directoryContents: files.map(file => ({ + path: file.path, + base64Content: base64EncodeContent(file.content), + })), + }, + { credentials }, + ); + + return { + output: { + valid: true, + message: 'Template validation successful', + log: result.log?.map(entry => ({ + message: entry.body.message, + stepId: entry.body.stepId, + status: entry.body.status, + })), + output: result.output, + steps: result.steps, + }, + }; + }, + }); +}; diff --git a/plugins/scaffolder-backend/src/actions/createExecuteTemplateAction.test.ts b/plugins/scaffolder-backend/src/actions/createExecuteTemplateAction.test.ts new file mode 100644 index 0000000000..236ea7083f --- /dev/null +++ b/plugins/scaffolder-backend/src/actions/createExecuteTemplateAction.test.ts @@ -0,0 +1,132 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createExecuteTemplateAction } from './createExecuteTemplateAction'; +import { actionsRegistryServiceMock } from '@backstage/backend-test-utils/alpha'; +import { scaffolderServiceMock } from '@backstage/plugin-scaffolder-node/testUtils'; + +describe('createExecuteTemplateAction', () => { + const mockScaffolderService = scaffolderServiceMock.mock(); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('should scaffold a template and return the taskId', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + mockScaffolderService.scaffold.mockResolvedValue({ + taskId: 'task-abc-123', + }); + + createExecuteTemplateAction({ + actionsRegistry: mockActionsRegistry, + scaffolderService: mockScaffolderService, + }); + + const result = await mockActionsRegistry.invoke({ + id: 'test:execute-template', + input: { + templateRef: 'template:default/my-template', + values: { name: 'my-app', owner: 'team-a' }, + }, + }); + + expect(result.output).toEqual({ taskId: 'task-abc-123' }); + + expect(mockScaffolderService.scaffold).toHaveBeenCalledWith( + { + templateRef: 'template:default/my-template', + values: { name: 'my-app', owner: 'team-a' }, + }, + { credentials: expect.anything() }, + ); + }); + + it('should forward empty values and pass secrets to the scaffolder service', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + mockScaffolderService.scaffold.mockResolvedValue({ + taskId: 'task-with-secrets', + }); + + createExecuteTemplateAction({ + actionsRegistry: mockActionsRegistry, + scaffolderService: mockScaffolderService, + }); + + const result = await mockActionsRegistry.invoke({ + id: 'test:execute-template', + input: { + templateRef: 'template:default/secret-template', + values: {}, + secrets: { apiKey: 'super-secret' }, + }, + }); + + expect(result.output).toEqual({ taskId: 'task-with-secrets' }); + expect(mockScaffolderService.scaffold).toHaveBeenCalledWith( + { + templateRef: 'template:default/secret-template', + values: {}, + secrets: { apiKey: 'super-secret' }, + }, + { credentials: expect.anything() }, + ); + }); + + it('should not include secrets when not provided', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + mockScaffolderService.scaffold.mockResolvedValue({ + taskId: 'task-no-secrets', + }); + + createExecuteTemplateAction({ + actionsRegistry: mockActionsRegistry, + scaffolderService: mockScaffolderService, + }); + + await mockActionsRegistry.invoke({ + id: 'test:execute-template', + input: { + templateRef: 'template:default/my-template', + values: { name: 'my-app' }, + }, + }); + + const scaffoldCall = mockScaffolderService.scaffold.mock.calls[0][0]; + expect(scaffoldCall).not.toHaveProperty('secrets'); + }); + + it('should propagate errors from the scaffolder service', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + mockScaffolderService.scaffold.mockRejectedValue( + new Error('Permission denied'), + ); + + createExecuteTemplateAction({ + actionsRegistry: mockActionsRegistry, + scaffolderService: mockScaffolderService, + }); + + await expect( + mockActionsRegistry.invoke({ + id: 'test:execute-template', + input: { + templateRef: 'template:default/my-template', + values: { name: 'my-app' }, + }, + }), + ).rejects.toThrow('Permission denied'); + }); +}); diff --git a/plugins/scaffolder-backend/src/actions/createExecuteTemplateAction.ts b/plugins/scaffolder-backend/src/actions/createExecuteTemplateAction.ts new file mode 100644 index 0000000000..b523655a3b --- /dev/null +++ b/plugins/scaffolder-backend/src/actions/createExecuteTemplateAction.ts @@ -0,0 +1,81 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ActionsRegistryService } from '@backstage/backend-plugin-api/alpha'; +import { ScaffolderService } from '@backstage/plugin-scaffolder-node'; +import { JsonValue } from '@backstage/types'; + +export const createExecuteTemplateAction = ({ + actionsRegistry, + scaffolderService, +}: { + actionsRegistry: ActionsRegistryService; + scaffolderService: ScaffolderService; +}) => { + actionsRegistry.register({ + name: 'execute-template', + title: 'Execute Scaffolder Template', + attributes: { + destructive: true, + readOnly: false, + idempotent: false, + }, + description: `Executes a Scaffolder template with its template ref and input parameter values. +The template is run using the credentials provided to this action, and respects any RBAC permissions associated with those credentials. +Returns a taskId that can be used to track execution progress. +Use the catalog.get-catalog-entity action to fetch the Template entity and discover its parameter schema and secrets definition before calling this action.`, + schema: { + input: z => + z.object({ + templateRef: z + .string() + .describe( + 'The template entity reference to execute, e.g. "template:default/my-template"', + ), + values: z + .record(z.unknown()) + .describe( + 'Input parameter values required by the template. Use catalog.get-catalog-entity to discover the required parameters for the template.', + ), + secrets: z + .record(z.string()) + .optional() + .describe( + 'Optional secrets to pass to the template execution. Use catalog.get-catalog-entity to discover the secrets definition on the Template entity.', + ), + }), + output: z => + z.object({ + taskId: z + .string() + .describe( + 'The task ID for the scaffolder execution. Use this to track progress or retrieve logs.', + ), + }), + }, + action: async ({ input, credentials }) => { + const { taskId } = await scaffolderService.scaffold( + { + templateRef: input.templateRef, + values: input.values as Record, + ...(input.secrets && { secrets: input.secrets }), + }, + { credentials }, + ); + + return { output: { taskId } }; + }, + }); +}; diff --git a/plugins/scaffolder-backend/src/actions/createGetScaffolderTaskLogsAction.test.ts b/plugins/scaffolder-backend/src/actions/createGetScaffolderTaskLogsAction.test.ts new file mode 100644 index 0000000000..7ddf498d9d --- /dev/null +++ b/plugins/scaffolder-backend/src/actions/createGetScaffolderTaskLogsAction.test.ts @@ -0,0 +1,148 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { actionsRegistryServiceMock } from '@backstage/backend-test-utils/alpha'; +import { scaffolderServiceMock } from '@backstage/plugin-scaffolder-node/testUtils'; +import { LogEvent } from '@backstage/plugin-scaffolder-common'; +import { createGetScaffolderTaskLogsAction } from './createGetScaffolderTaskLogsAction'; + +describe('createGetScaffolderTaskLogsAction', () => { + it('should return log events for a task', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockScaffolderService = scaffolderServiceMock.mock(); + + const mockEvents: LogEvent[] = [ + { + id: 1, + taskId: 'task-1', + createdAt: '2025-01-01T00:00:00Z', + type: 'log', + body: { message: 'Starting step', stepId: 'step-1' }, + }, + { + id: 2, + taskId: 'task-1', + createdAt: '2025-01-01T00:00:01Z', + type: 'log', + body: { message: 'Step complete', stepId: 'step-1' }, + }, + { + id: 3, + taskId: 'task-1', + createdAt: '2025-01-01T00:00:02Z', + type: 'completion', + body: { message: 'Task completed', status: 'completed' }, + }, + ]; + + mockScaffolderService.getLogs.mockResolvedValue(mockEvents); + + createGetScaffolderTaskLogsAction({ + actionsRegistry: mockActionsRegistry, + scaffolderService: mockScaffolderService, + }); + + const result = await mockActionsRegistry.invoke({ + id: 'test:get-scaffolder-task-logs', + input: { taskId: 'task-1' }, + }); + + expect(result.output).toEqual({ + events: [ + { + id: 1, + taskId: 'task-1', + createdAt: '2025-01-01T00:00:00Z', + type: 'log', + body: { + message: 'Starting step', + stepId: 'step-1', + status: undefined, + }, + }, + { + id: 2, + taskId: 'task-1', + createdAt: '2025-01-01T00:00:01Z', + type: 'log', + body: { + message: 'Step complete', + stepId: 'step-1', + status: undefined, + }, + }, + { + id: 3, + taskId: 'task-1', + createdAt: '2025-01-01T00:00:02Z', + type: 'completion', + body: { + message: 'Task completed', + stepId: undefined, + status: 'completed', + }, + }, + ], + }); + expect(mockScaffolderService.getLogs).toHaveBeenCalledWith( + { taskId: 'task-1', after: undefined }, + expect.objectContaining({ credentials: expect.anything() }), + ); + }); + + it('should pass the after parameter through to the service', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockScaffolderService = scaffolderServiceMock.mock(); + + mockScaffolderService.getLogs.mockResolvedValue([]); + + createGetScaffolderTaskLogsAction({ + actionsRegistry: mockActionsRegistry, + scaffolderService: mockScaffolderService, + }); + + const result = await mockActionsRegistry.invoke({ + id: 'test:get-scaffolder-task-logs', + input: { taskId: 'task-2', after: 42 }, + }); + + expect(result.output).toEqual({ events: [] }); + expect(mockScaffolderService.getLogs).toHaveBeenCalledWith( + { taskId: 'task-2', after: 42 }, + expect.objectContaining({ credentials: expect.anything() }), + ); + }); + + it('should throw when the service call fails', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockScaffolderService = scaffolderServiceMock.mock(); + + mockScaffolderService.getLogs.mockRejectedValue( + new Error('Internal Server Error'), + ); + + createGetScaffolderTaskLogsAction({ + actionsRegistry: mockActionsRegistry, + scaffolderService: mockScaffolderService, + }); + + await expect( + mockActionsRegistry.invoke({ + id: 'test:get-scaffolder-task-logs', + input: { taskId: 'task-3' }, + }), + ).rejects.toThrow('Internal Server Error'); + }); +}); diff --git a/plugins/scaffolder-backend/src/actions/createGetScaffolderTaskLogsAction.ts b/plugins/scaffolder-backend/src/actions/createGetScaffolderTaskLogsAction.ts new file mode 100644 index 0000000000..08eb3a504f --- /dev/null +++ b/plugins/scaffolder-backend/src/actions/createGetScaffolderTaskLogsAction.ts @@ -0,0 +1,112 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ActionsRegistryService } from '@backstage/backend-plugin-api/alpha'; +import { ScaffolderService } from '@backstage/plugin-scaffolder-node'; + +export const createGetScaffolderTaskLogsAction = ({ + actionsRegistry, + scaffolderService, +}: { + actionsRegistry: ActionsRegistryService; + scaffolderService: ScaffolderService; +}) => { + actionsRegistry.register({ + name: 'get-scaffolder-task-logs', + title: 'Get Scaffolder Task Logs', + attributes: { + destructive: false, + readOnly: true, + idempotent: true, + }, + description: ` +Retrieve the log events for a scaffolder task. +Each log event has a type (log, completion, cancelled, or recovered), a body containing a message and optional step ID and status. +Use the after parameter to fetch only events after a specific event ID for incremental polling. + `, + schema: { + input: z => + z.object({ + taskId: z.string().describe('The ID of the scaffolder task'), + after: z + .number() + .int() + .min(0) + .optional() + .describe( + 'Return only log events after this event ID for incremental polling', + ), + }), + output: z => + z + .object({ + events: z + .array( + z.object({ + id: z.number().describe('The event ID'), + taskId: z + .string() + .describe('The ID of the task this event belongs to'), + createdAt: z + .string() + .describe('Timestamp when the event was created'), + type: z + .string() + .describe( + 'Event type: log, completion, cancelled, or recovered', + ), + body: z + .object({ + message: z.string().describe('The log message'), + stepId: z + .string() + .optional() + .describe('The step ID associated with this event'), + status: z + .string() + .optional() + .describe('The task status at the time of this event'), + }) + .describe('The event body'), + }), + ) + .describe('The list of log events for the task'), + }) + .describe('Object containing the events array'), + }, + action: async ({ input, credentials }) => { + const events = await scaffolderService.getLogs( + { taskId: input.taskId, after: input.after }, + { credentials }, + ); + + return { + output: { + events: events.map(event => ({ + id: event.id, + taskId: event.taskId, + createdAt: event.createdAt, + type: event.type, + body: { + message: event.body.message, + stepId: event.body.stepId, + status: event.body.status, + }, + })), + }, + }; + }, + }); +}; diff --git a/plugins/scaffolder-backend/src/actions/createListScaffolderActionsAction.test.ts b/plugins/scaffolder-backend/src/actions/createListScaffolderActionsAction.test.ts new file mode 100644 index 0000000000..145e3deeed --- /dev/null +++ b/plugins/scaffolder-backend/src/actions/createListScaffolderActionsAction.test.ts @@ -0,0 +1,164 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createListScaffolderActionsAction } from './createListScaffolderActionsAction'; +import { actionsRegistryServiceMock } from '@backstage/backend-test-utils/alpha'; +import { scaffolderServiceMock } from '@backstage/plugin-scaffolder-node/testUtils'; +import type { ListActionsResponse } from '@backstage/plugin-scaffolder-common'; + +type ListActionsOutput = { + actions: Array<{ + id: string; + description: string; + schema: { input: object; output: object }; + examples: Array<{ description: string; example: string }>; + }>; +}; + +describe('createListScaffolderActionsAction', () => { + it('should list all scaffolder actions sorted by id with full properties', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockScaffolderService = scaffolderServiceMock.mock(); + mockScaffolderService.listActions.mockResolvedValue(createMockActions()); + + createListScaffolderActionsAction({ + actionsRegistry: mockActionsRegistry, + scaffolderService: mockScaffolderService, + }); + + const result = await mockActionsRegistry.invoke({ + id: 'test:list-scaffolder-actions', + input: {}, + }); + + const output = result.output as ListActionsOutput; + expect(output.actions).toHaveLength(3); + + const actionIds = output.actions.map(a => a.id); + expect(actionIds).toEqual([ + 'catalog:register', + 'debug:log', + 'fetch:template', + ]); + + expect(output.actions[0]).toEqual({ + id: 'catalog:register', + description: 'Registers entities in the catalog', + schema: { + input: { type: 'object' }, + output: { type: 'object' }, + }, + examples: [{ description: 'Basic usage', example: 'register entity' }], + }); + }); + + it('should handle actions without descriptions, schemas, or examples', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockScaffolderService = scaffolderServiceMock.mock(); + mockScaffolderService.listActions.mockResolvedValue([ + { id: 'minimal-action' }, + ]); + + createListScaffolderActionsAction({ + actionsRegistry: mockActionsRegistry, + scaffolderService: mockScaffolderService, + }); + + const result = await mockActionsRegistry.invoke({ + id: 'test:list-scaffolder-actions', + input: {}, + }); + + const output = result.output as ListActionsOutput; + expect(output.actions).toHaveLength(1); + expect(output.actions[0]).toEqual({ + id: 'minimal-action', + description: '', + schema: { input: {}, output: {} }, + examples: [], + }); + }); + + it('should return empty array when no actions are registered', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockScaffolderService = scaffolderServiceMock.mock(); + mockScaffolderService.listActions.mockResolvedValue([]); + + createListScaffolderActionsAction({ + actionsRegistry: mockActionsRegistry, + scaffolderService: mockScaffolderService, + }); + + const result = await mockActionsRegistry.invoke({ + id: 'test:list-scaffolder-actions', + input: {}, + }); + + const output = result.output as ListActionsOutput; + expect(output.actions).toEqual([]); + }); + + it('should propagate errors from the scaffolder service', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockScaffolderService = scaffolderServiceMock.mock(); + mockScaffolderService.listActions.mockRejectedValue( + new Error('Service unavailable'), + ); + + createListScaffolderActionsAction({ + actionsRegistry: mockActionsRegistry, + scaffolderService: mockScaffolderService, + }); + + await expect( + mockActionsRegistry.invoke({ + id: 'test:list-scaffolder-actions', + input: {}, + }), + ).rejects.toThrow('Service unavailable'); + }); +}); + +function createMockActions(): ListActionsResponse { + return [ + { + id: 'fetch:template', + description: 'Fetches a template', + schema: { + input: { type: 'object' }, + output: { type: 'object' }, + }, + examples: [], + }, + { + id: 'catalog:register', + description: 'Registers entities in the catalog', + schema: { + input: { type: 'object' }, + output: { type: 'object' }, + }, + examples: [{ description: 'Basic usage', example: 'register entity' }], + }, + { + id: 'debug:log', + description: 'Logs debug information', + schema: { + input: { type: 'object' }, + output: { type: 'object' }, + }, + examples: [], + }, + ]; +} diff --git a/plugins/scaffolder-backend/src/actions/createListScaffolderActionsAction.ts b/plugins/scaffolder-backend/src/actions/createListScaffolderActionsAction.ts new file mode 100644 index 0000000000..4450207e16 --- /dev/null +++ b/plugins/scaffolder-backend/src/actions/createListScaffolderActionsAction.ts @@ -0,0 +1,83 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ActionsRegistryService } from '@backstage/backend-plugin-api/alpha'; +import { ScaffolderService } from '@backstage/plugin-scaffolder-node'; + +export const createListScaffolderActionsAction = ({ + actionsRegistry, + scaffolderService, +}: { + actionsRegistry: ActionsRegistryService; + scaffolderService: ScaffolderService; +}) => { + actionsRegistry.register({ + name: 'list-scaffolder-actions', + title: 'List Scaffolder Actions', + attributes: { + destructive: false, + readOnly: true, + idempotent: true, + }, + description: `Lists all installed Scaffolder actions. +Each action includes: +- id: The action identifier +- description: What the action does +- schema: Input and output JSON schemas +- examples: Usage examples when available`, + schema: { + input: z => z.object({}).describe('No input is required'), + output: z => + z.object({ + actions: z.array( + z.object({ + id: z.string(), + description: z.string(), + schema: z.object({ + input: z + .object({}) + .passthrough() + .describe('JSON Schema for input of Action'), + output: z + .object({}) + .passthrough() + .describe('JSON Schema for output of Action'), + }), + examples: z.array(z.any()).optional(), + }), + ), + }), + }, + action: async ({ credentials }) => { + const actions = await scaffolderService.listActions(undefined, { + credentials, + }); + const scaffolderActions = actions.map(action => ({ + id: action.id, + description: action.description ?? '', + schema: { + input: { ...(action.schema?.input ?? {}) }, + output: { ...(action.schema?.output ?? {}) }, + }, + examples: action.examples ?? [], + })); + return { + output: { + actions: scaffolderActions.sort((a, b) => a.id.localeCompare(b.id)), + }, + }; + }, + }); +}; diff --git a/plugins/scaffolder-backend/src/actions/index.ts b/plugins/scaffolder-backend/src/actions/index.ts new file mode 100644 index 0000000000..239c7bcb78 --- /dev/null +++ b/plugins/scaffolder-backend/src/actions/index.ts @@ -0,0 +1,39 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ActionsRegistryService } from '@backstage/backend-plugin-api/alpha'; +import { AuthService } from '@backstage/backend-plugin-api'; +import { createListScaffolderTasksAction } from './listScaffolderTasksAction'; +import { ScaffolderService } from '@backstage/plugin-scaffolder-node'; +import { createDryRunTemplateAction } from './createDryRunTemplateAction'; +import { createListScaffolderActionsAction } from './createListScaffolderActionsAction'; +import { createExecuteTemplateAction } from './createExecuteTemplateAction'; +import { createGetScaffolderTaskLogsAction } from './createGetScaffolderTaskLogsAction'; + +export const createScaffolderActions = (options: { + actionsRegistry: ActionsRegistryService; + scaffolderService: ScaffolderService; + auth: AuthService; +}) => { + createListScaffolderTasksAction({ + actionsRegistry: options.actionsRegistry, + auth: options.auth, + scaffolderService: options.scaffolderService, + }); + createDryRunTemplateAction(options); + createListScaffolderActionsAction(options); + createExecuteTemplateAction(options); + createGetScaffolderTaskLogsAction(options); +}; diff --git a/plugins/scaffolder-backend/src/actions/listScaffolderTasksAction.test.ts b/plugins/scaffolder-backend/src/actions/listScaffolderTasksAction.test.ts new file mode 100644 index 0000000000..075ba919f1 --- /dev/null +++ b/plugins/scaffolder-backend/src/actions/listScaffolderTasksAction.test.ts @@ -0,0 +1,268 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { actionsRegistryServiceMock } from '@backstage/backend-test-utils/alpha'; +import { mockServices, mockCredentials } from '@backstage/backend-test-utils'; +import { NotAllowedError } from '@backstage/errors'; +import { ScaffolderTask } from '@backstage/plugin-scaffolder-common'; +import { scaffolderServiceMock } from '@backstage/plugin-scaffolder-node/testUtils'; +import { createListScaffolderTasksAction } from './listScaffolderTasksAction'; +import { ListTasksResponse } from '../schema/openapi/generated/models/ListTasksResponse.model'; + +describe('createListScaffolderTasksAction', () => { + it('should list tasks successfully', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockAuth = mockServices.auth.mock(); + const mockScaffolderService = scaffolderServiceMock.mock(); + const mockTasks = generateMockTasks(); + + mockScaffolderService.listTasks.mockResolvedValue({ + items: mockTasks.tasks.map(task => ({ + id: task.id, + spec: task.spec, + status: task.status, + createdAt: task.createdAt, + lastHeartbeatAt: task.lastHeartbeatAt, + })) as ScaffolderTask[], + totalItems: mockTasks.totalTasks ?? 0, + }); + + createListScaffolderTasksAction({ + actionsRegistry: mockActionsRegistry, + auth: mockAuth, + scaffolderService: mockScaffolderService, + }); + + const result = await mockActionsRegistry.invoke({ + id: 'test:list-scaffolder-tasks', + input: {}, + }); + + const expectedTasks: ScaffolderTask[] = mockTasks.tasks.map(task => ({ + id: task.id, + spec: task.spec, + status: task.status, + createdAt: task.createdAt, + lastHeartbeatAt: task.lastHeartbeatAt, + })); + + expect(result.output).toEqual({ + tasks: expectedTasks, + totalTasks: mockTasks.totalTasks ?? 0, + }); + expect(mockScaffolderService.listTasks).toHaveBeenCalledWith( + { createdBy: undefined, limit: undefined, offset: undefined }, + expect.objectContaining({ credentials: expect.anything() }), + ); + }); + + it('should pass limit and offset through to the API and return paginated results', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockAuth = mockServices.auth.mock(); + const mockScaffolderService = scaffolderServiceMock.mock(); + const paginatedTasks = [ + { + id: 'task-2', + spec: {}, + status: 'completed', + createdAt: '2025-01-01T00:00:00Z', + lastHeartbeatAt: '2025-01-01T00:01:00Z', + }, + { + id: 'task-3', + spec: {}, + status: 'processing', + createdAt: '2025-01-01T00:00:00Z', + lastHeartbeatAt: '2025-01-01T00:02:00Z', + }, + ]; + + mockScaffolderService.listTasks.mockResolvedValue({ + items: paginatedTasks as ScaffolderTask[], + totalItems: 10, + }); + + createListScaffolderTasksAction({ + actionsRegistry: mockActionsRegistry, + auth: mockAuth, + scaffolderService: mockScaffolderService, + }); + + const result = await mockActionsRegistry.invoke({ + id: 'test:list-scaffolder-tasks', + input: { limit: 2, offset: 1 }, + }); + + expect(mockScaffolderService.listTasks).toHaveBeenCalledWith( + { createdBy: undefined, limit: 2, offset: 1 }, + expect.objectContaining({ credentials: expect.anything() }), + ); + + const expectedTasks = paginatedTasks.map(task => ({ + id: task.id, + spec: task.spec, + status: task.status, + createdAt: task.createdAt, + lastHeartbeatAt: task.lastHeartbeatAt, + })); + + expect(result.output).toEqual({ + tasks: expectedTasks, + totalTasks: 10, + }); + }); + + it('should throw an error if the service call fails', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockAuth = mockServices.auth.mock(); + const mockScaffolderService = scaffolderServiceMock.mock(); + + mockScaffolderService.listTasks.mockRejectedValue( + new Error('Internal Server Error'), + ); + + createListScaffolderTasksAction({ + actionsRegistry: mockActionsRegistry, + auth: mockAuth, + scaffolderService: mockScaffolderService, + }); + + await expect( + mockActionsRegistry.invoke({ + id: 'test:list-scaffolder-tasks', + input: {}, + }), + ).rejects.toThrow('Internal Server Error'); + }); + + it('should use createdBy filter when owned is true with user identity', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockAuth = mockServices.auth.mock(); + const mockScaffolderService = scaffolderServiceMock.mock(); + const mockTasks = generateMockTasks(); + + mockAuth.isPrincipal.mockImplementation( + (creds, type) => + type === 'user' && + (creds?.principal as { type?: string })?.type === 'user' && + typeof (creds.principal as { userEntityRef?: string }).userEntityRef === + 'string', + ); + mockScaffolderService.listTasks.mockResolvedValue({ + items: mockTasks.tasks.map(task => ({ + id: task.id, + spec: task.spec, + status: task.status, + createdAt: task.createdAt, + lastHeartbeatAt: task.lastHeartbeatAt, + })) as ScaffolderTask[], + totalItems: mockTasks.totalTasks ?? 0, + }); + + createListScaffolderTasksAction({ + actionsRegistry: mockActionsRegistry, + auth: mockAuth, + scaffolderService: mockScaffolderService, + }); + + await mockActionsRegistry.invoke({ + id: 'test:list-scaffolder-tasks', + input: { owned: true }, + credentials: mockCredentials.user('user:default/alice'), + }); + + expect(mockScaffolderService.listTasks).toHaveBeenCalledWith( + { + createdBy: 'user:default/alice', + limit: undefined, + offset: undefined, + }, + expect.objectContaining({ credentials: expect.anything() }), + ); + }); + + it('should throw NotAllowedError when owned is true without user identity', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockAuth = mockServices.auth.mock(); + const mockScaffolderService = scaffolderServiceMock.mock(); + + mockAuth.isPrincipal.mockReturnValue(false); + + createListScaffolderTasksAction({ + actionsRegistry: mockActionsRegistry, + auth: mockAuth, + scaffolderService: mockScaffolderService, + }); + + await expect( + mockActionsRegistry.invoke({ + id: 'test:list-scaffolder-tasks', + input: { owned: true }, + credentials: mockCredentials.service(), + }), + ).rejects.toThrow(NotAllowedError); + + expect(mockScaffolderService.listTasks).not.toHaveBeenCalled(); + }); +}); + +// Return a mocked ListTasksResponse that contains a number of different mocked tasks +function generateMockTasks(): ListTasksResponse { + return { + tasks: [ + { + id: 'task-1', + spec: {}, + status: 'completed', + createdAt: '2025-01-01T00:00:00Z', + lastHeartbeatAt: '2025-01-01T00:01:00Z', + createdBy: 'user:default/guest', + }, + { + id: 'task-2', + spec: {}, + status: 'completed', + createdAt: '2025-01-01T00:00:00Z', + lastHeartbeatAt: '2025-01-01T00:01:00Z', + createdBy: 'user:default/guest', + }, + { + id: 'task-3', + spec: {}, + status: 'processing', + createdAt: '2025-01-01T00:00:00Z', + lastHeartbeatAt: '2025-01-01T00:02:00Z', + createdBy: 'user:default/admin', + }, + { + id: 'task-4', + spec: {}, + status: 'failed', + createdAt: '2025-01-01T00:00:00Z', + lastHeartbeatAt: '2025-01-01T00:02:00Z', + createdBy: 'user:default/admin', + }, + { + id: 'task-5', + spec: {}, + status: 'cancelled', + createdAt: '2025-01-01T00:00:00Z', + lastHeartbeatAt: '2025-01-01T00:02:00Z', + createdBy: 'user:default/admin', + }, + ], + totalTasks: 5, + }; +} diff --git a/plugins/scaffolder-backend/src/actions/listScaffolderTasksAction.ts b/plugins/scaffolder-backend/src/actions/listScaffolderTasksAction.ts new file mode 100644 index 0000000000..70977c9d12 --- /dev/null +++ b/plugins/scaffolder-backend/src/actions/listScaffolderTasksAction.ts @@ -0,0 +1,133 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ActionsRegistryService } from '@backstage/backend-plugin-api/alpha'; +import { AuthService } from '@backstage/backend-plugin-api'; +import { NotAllowedError } from '@backstage/errors'; +import { ScaffolderService } from '@backstage/plugin-scaffolder-node'; + +export const createListScaffolderTasksAction = ({ + actionsRegistry, + auth, + scaffolderService, +}: { + actionsRegistry: ActionsRegistryService; + auth: AuthService; + scaffolderService: ScaffolderService; +}) => { + actionsRegistry.register({ + name: 'list-scaffolder-tasks', + title: 'List Scaffolder Tasks', + attributes: { + destructive: false, + readOnly: true, + idempotent: true, + }, + description: ` +This allows you to list scaffolder tasks that have been created. +Each task has a unique id, specification, and status (one of open, processing, completed, failed, cancelled, skipped). +Each task includes a timestamp for when it was created, and an optional last heartbeat timestamp indicating the most recent activity. +Set owned to true to return only tasks created by the current user; omit or set to false for all tasks the credentials can see. +Pagination is supported via limit and offset. + `, + schema: { + input: z => + z.object({ + owned: z + .boolean() + .optional() + .default(false) + .describe( + 'If true, return only tasks created by the current user. Requires a user identity.', + ), + limit: z + .number() + .int() + .min(1) + .max(1000) + .describe('The maximum number of tasks to return for pagination') + .optional(), + offset: z + .number() + .int() + .min(0) + .describe('The offset to start from for pagination') + .optional(), + }), + output: z => + z + .object({ + tasks: z + .array( + z.object({ + id: z.string().describe('The task identifier'), + spec: z.unknown().describe('The task specification'), + status: z + .string() + .describe( + 'Task status: open, processing, completed, failed, cancelled, or skipped', + ), + createdAt: z + .string() + .describe('Timestamp when the task was created'), + lastHeartbeatAt: z + .string() + .optional() + .describe('Timestamp of the last heartbeat'), + }), + ) + .describe('The list of scaffolder tasks'), + totalTasks: z + .number() + .describe('Total number of tasks matching the filter'), + }) + .describe('Object containing a tasks array and totalTasks count'), + }, + action: async ({ input, credentials }) => { + if (input.owned && !auth.isPrincipal(credentials, 'user')) { + throw new NotAllowedError( + 'Filtering by owned tasks requires a user identity.', + ); + } + + const createdBy = + input.owned && auth.isPrincipal(credentials, 'user') + ? credentials.principal.userEntityRef + : undefined; + + const { items, totalItems } = await scaffolderService.listTasks( + { + createdBy, + limit: input.limit, + offset: input.offset, + }, + { credentials }, + ); + + return { + output: { + tasks: items.map(task => ({ + id: task.id, + spec: task.spec, + status: task.status, + createdAt: task.createdAt, + lastHeartbeatAt: task.lastHeartbeatAt, + })), + totalTasks: totalItems, + }, + }; + }, + }); +}; diff --git a/plugins/scaffolder-backend/src/lib/defaultEnvironment.test.ts b/plugins/scaffolder-backend/src/lib/defaultEnvironment.test.ts new file mode 100644 index 0000000000..c47863e0a2 --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/defaultEnvironment.test.ts @@ -0,0 +1,87 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ConfigReader } from '@backstage/config'; +import { resolveDefaultEnvironment } from './defaultEnvironment'; + +describe('defaultEnvironment', () => { + describe('resolveDefaultEnvironment', () => { + it('should return empty when no defaultEnvironment config is provided', () => { + const config = new ConfigReader({}); + const result = resolveDefaultEnvironment(config); + + expect(result).toEqual({ + parameters: {}, + secrets: {}, + }); + }); + + it('should resolve parameters and secrets from config', () => { + const config = new ConfigReader({ + scaffolder: { + defaultEnvironment: { + parameters: { + region: 'us-east-1', + organizationName: 'acme-corp', + version: '1.0.0', + }, + secrets: { + AWS_ACCESS_KEY: 'test-secret-value', + DATABASE_PASSWORD: 'db-password', + LITERAL_SECRET: 'literal-value', + }, + }, + }, + }); + + const result = resolveDefaultEnvironment(config); + + expect(result).toEqual({ + parameters: { + region: 'us-east-1', + organizationName: 'acme-corp', + version: '1.0.0', + }, + secrets: { + AWS_ACCESS_KEY: 'test-secret-value', + DATABASE_PASSWORD: 'db-password', + LITERAL_SECRET: 'literal-value', + }, + }); + }); + + it('should handle only parameters without secrets', () => { + const config = new ConfigReader({ + scaffolder: { + defaultEnvironment: { + parameters: { + region: 'eu-west-1', + }, + }, + }, + }); + + const result = resolveDefaultEnvironment(config); + + expect(result).toEqual({ + parameters: { + region: 'eu-west-1', + }, + secrets: {}, + }); + }); + }); +}); diff --git a/plugins/scaffolder-backend/src/lib/defaultEnvironment.ts b/plugins/scaffolder-backend/src/lib/defaultEnvironment.ts new file mode 100644 index 0000000000..cfb47a0130 --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/defaultEnvironment.ts @@ -0,0 +1,66 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Config } from '@backstage/config'; +import { JsonObject } from '@backstage/types'; + +export interface DefaultEnvironmentConfig { + parameters?: Record; + secrets?: Record; +} + +export interface ResolvedDefaultEnvironment { + parameters: JsonObject; + secrets: Record; +} + +export function resolveDefaultEnvironment( + config: Config, +): ResolvedDefaultEnvironment { + const defaultEnvConfig = config.getOptionalConfig( + 'scaffolder.defaultEnvironment', + ); + if (!defaultEnvConfig) { + return { + parameters: {}, + secrets: {}, + }; + } + + const parameters: JsonObject = {}; + const secrets: Record = {}; + + const parametersConfig = defaultEnvConfig.getOptionalConfig('parameters'); + if (parametersConfig) { + for (const paramKey of parametersConfig.keys()) { + const paramValue = parametersConfig.getString(paramKey); + parameters[paramKey] = paramValue; + } + } + + const secretsConfig = defaultEnvConfig.getOptionalConfig('secrets'); + if (secretsConfig) { + for (const secretKey of secretsConfig.keys()) { + const secretValue = secretsConfig.getString(secretKey); + secrets[secretKey] = secretValue; + } + } + + return { + parameters, + secrets, + }; +} diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts b/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts index dab1447eee..90ee05cc17 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts @@ -93,7 +93,6 @@ export class DefaultTemplateActionRegistry implements TemplateActionRegistry { ret.set(action.id, { id: action.id, description: action.description, - examples: [], supportsDryRun: action.attributes?.readOnly === true && action.attributes?.destructive === false, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.examples.test.ts index f900e87a1d..51374d5d18 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.examples.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.examples.test.ts @@ -22,10 +22,10 @@ const fsMock = fs as jest.Mocked; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { createCatalogWriteAction } from './write'; -import { resolve as resolvePath } from 'path'; +import { resolve as resolvePath } from 'node:path'; import * as yaml from 'yaml'; import { examples } from './write.examples'; -import os from 'os'; +import os from 'node:os'; describe('catalog:write', () => { const action = createCatalogWriteAction(); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.test.ts index 469b4d5c6d..85833ce585 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.test.ts @@ -20,11 +20,11 @@ jest.mock('fs-extra'); const fsMock = fs as jest.Mocked; -import os from 'os'; +import os from 'node:os'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { ANNOTATION_ORIGIN_LOCATION } from '@backstage/catalog-model'; import { createCatalogWriteAction } from './write'; -import { resolve as resolvePath } from 'path'; +import { resolve as resolvePath } from 'node:path'; import * as yaml from 'yaml'; describe('catalog:write', () => { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.examples.test.ts index 0c37db9eb9..f37023bc19 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.examples.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.examples.test.ts @@ -16,7 +16,7 @@ import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { createDebugLogAction } from './log'; -import { join } from 'path'; +import { join } from 'node:path'; import yaml from 'yaml'; import { examples } from './log.examples'; import { createMockDirectory } from '@backstage/backend-test-utils'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.test.ts index 4606a01e0c..5d3c362842 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.test.ts @@ -17,9 +17,10 @@ import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { Logger } from 'winston'; import { createDebugLogAction } from './log'; -import { join } from 'path'; +import { join } from 'node:path'; import yaml from 'yaml'; import { createMockDirectory } from '@backstage/backend-test-utils'; +import fs from 'fs-extra'; describe('debug:log', () => { const logger = { @@ -139,4 +140,30 @@ describe('debug:log', () => { expect.stringContaining('Hello Backstage!'), ); }); + + it('should handle symlinks to external paths', async () => { + const externalContent = 'external-file-content'; + const externalPath = mockDir.resolve('external.txt'); + await fs.writeFile(externalPath, externalContent); + + const linkPath = join(mockContext.workspacePath, 'link'); + await fs.symlink(externalPath, linkPath); + + const context = { + ...mockContext, + input: { + listWorkspace: 'with-contents' as const, + }, + }; + + await action.handler(context); + + expect(logger.info).toHaveBeenCalledWith(expect.stringContaining('link')); + expect(logger.info).not.toHaveBeenCalledWith( + expect.stringContaining(externalContent), + ); + expect(logger.info).toHaveBeenCalledWith( + expect.stringContaining('[skipped]'), + ); + }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.ts index 6eae1794f8..f9c6ea3703 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.ts @@ -14,11 +14,12 @@ * limitations under the License. */ +import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { readdir, stat } from 'fs-extra'; -import { join, relative } from 'path'; +import { join, relative } from 'node:path'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { examples } from './log.examples'; -import fs from 'fs'; +import fs from 'node:fs'; const id = 'debug:log'; @@ -66,8 +67,16 @@ export function createDebugLogAction() { .map(f => { const relativePath = relative(ctx.workspacePath, f); if (ctx.input?.listWorkspace === 'with-contents') { - const content = fs.readFileSync(f, 'utf-8'); - return ` - ${relativePath}:\n\n ${content}`; + try { + const safePath = resolveSafeChildPath( + ctx.workspacePath, + relativePath, + ); + const content = fs.readFileSync(safePath, 'utf-8'); + return ` - ${relativePath}:\n\n ${content}`; + } catch { + return ` - ${relativePath}: [skipped]`; + } } return ` - ${relativePath}`; }) diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.ts index e048720f29..73d81e50bf 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.ts @@ -84,16 +84,14 @@ export function createWaitAction(options?: { ); } - await new Promise(resolve => { - const controller = new AbortController(); + await new Promise(resolve => { const timeoutHandle = setTimeout(abort, delayTime.toMillis()); ctx.signal?.addEventListener('abort', abort); function abort() { ctx.signal?.removeEventListener('abort', abort); - clearTimeout(timeoutHandle!); - controller.abort(); - resolve('finished'); + clearTimeout(timeoutHandle); + resolve(); } }); }, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.examples.test.ts index 9dbb1f9a03..2ce8b9b4a5 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.examples.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.examples.test.ts @@ -16,7 +16,7 @@ import yaml from 'yaml'; -import { resolve as resolvePath } from 'path'; +import { resolve as resolvePath } from 'node:path'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import { createFetchPlainAction } from './plain'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts index 8a52ef7392..9e814f33b1 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts @@ -19,7 +19,7 @@ jest.mock('@backstage/plugin-scaffolder-node', () => { return { ...actual, fetchContents: jest.fn() }; }); -import { resolve as resolvePath } from 'path'; +import { resolve as resolvePath } from 'node:path'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.examples.test.ts index a091a6ecfe..b97abf4988 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.examples.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.examples.test.ts @@ -22,7 +22,7 @@ jest.mock('@backstage/plugin-scaffolder-node', () => { }); import yaml from 'yaml'; -import { resolve as resolvePath } from 'path'; +import { resolve as resolvePath } from 'node:path'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import { createFetchPlainFileAction } from './plainFile'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.test.ts index 637a209bd9..fcf62b66ec 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.test.ts @@ -19,7 +19,7 @@ jest.mock('@backstage/plugin-scaffolder-node', () => { return { ...actual, fetchFile: jest.fn() }; }); -import { resolve as resolvePath } from 'path'; +import { resolve as resolvePath } from 'node:path'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.examples.test.ts index 41ce875317..da6a4d73a5 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.examples.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.examples.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { join as joinPath, sep as pathSep } from 'path'; +import { join as joinPath, sep as pathSep } from 'node:path'; import fs from 'fs-extra'; import { UrlReaderService } from '@backstage/backend-plugin-api'; import { resolvePackagePath } from '@backstage/backend-plugin-api'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts index 6494e562d2..092194df46 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts @@ -19,7 +19,7 @@ jest.mock('@backstage/plugin-scaffolder-node', () => { return { ...actual, fetchContents: jest.fn() }; }); -import { join as joinPath, sep as pathSep } from 'path'; +import { join as joinPath, sep as pathSep } from 'node:path'; import fs from 'fs-extra'; import { UrlReaderService, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateActionHandler.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateActionHandler.ts index a8a4dbafd7..bfd4d24b8c 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateActionHandler.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateActionHandler.ts @@ -27,10 +27,10 @@ import { import fs from 'fs-extra'; import globby from 'globby'; import { isBinaryFile } from 'isbinaryfile'; -import { extname } from 'path'; import { createDefaultFilters } from '../../../../lib/templating/filters/createDefaultFilters'; -import { SecureTemplater } from '../../../../lib/templating/SecureTemplater'; import { convertFiltersToRecord } from '../../../../util/templating'; +import { SecureTemplater } from '../../../../lib/templating/SecureTemplater'; +import { extname } from 'node:path'; export type TemplateActionInput = { targetPath?: string; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.examples.test.ts index c7823ee9d8..bbb970cb44 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.examples.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.examples.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { join as joinPath } from 'path'; +import { join as joinPath } from 'node:path'; import fs from 'fs-extra'; import { UrlReaderService } from '@backstage/backend-plugin-api'; import { ScmIntegrations } from '@backstage/integration'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.test.ts index 23721e685a..0e37fee946 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.test.ts @@ -19,7 +19,7 @@ jest.mock('@backstage/plugin-scaffolder-node', () => { return { ...actual, fetchFile: jest.fn() }; }); -import { join as joinPath } from 'path'; +import { join as joinPath } from 'node:path'; import fs from 'fs-extra'; import { UrlReaderService } from '@backstage/backend-plugin-api'; import { ScmIntegrations } from '@backstage/integration'; 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 2cfe16f900..f206859c2e 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.ts @@ -22,7 +22,7 @@ import { TemplateFilter, TemplateGlobal, } from '@backstage/plugin-scaffolder-node'; -import path from 'path'; +import path from 'node:path'; import { examples } from './templateFile.examples'; import { createTemplateFileActionHandler } from './templateFileActionHandler'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFileActionHandler.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFileActionHandler.ts index f5663ff220..ceaeb9b984 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFileActionHandler.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFileActionHandler.ts @@ -21,8 +21,10 @@ import { TemplateGlobal, } from '@backstage/plugin-scaffolder-node'; import fs from 'fs-extra'; -import path from 'path'; import { createDefaultFilters } from '../../../../lib/templating/filters/createDefaultFilters'; +import { convertFiltersToRecord } from '../../../../util/templating'; +import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; +import path from 'node:path'; import { SecureTemplater } from '../../../../lib/templating/SecureTemplater'; import { convertFiltersToRecord } from '../../../../util/templating'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/workspaceTemplate.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/workspaceTemplate.test.ts index 2a9b4ef68e..6326ef69b4 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/workspaceTemplate.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/workspaceTemplate.test.ts @@ -23,7 +23,7 @@ import { } from '@backstage/plugin-scaffolder-node'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import fs from 'fs-extra'; -import { join as joinPath, sep as pathSep } from 'path'; +import { join as joinPath, sep as pathSep } from 'node:path'; import { createWorkspaceTemplateAction } from './workspaceTemplate'; import { TemplateActionInput } from './templateActionHandler'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/workspaceTemplateFile.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/workspaceTemplateFile.test.ts index 9436b160c5..241a1bcfc5 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/workspaceTemplateFile.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/workspaceTemplateFile.test.ts @@ -22,7 +22,7 @@ import { } from '@backstage/plugin-scaffolder-node'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import fs from 'fs-extra'; -import { join as joinPath } from 'path'; +import { join as joinPath } from 'node:path'; import { createWorkspaceTemplateFileAction } from './workspaceTemplateFile'; import { TemplateFileActionInput } from './templateFileActionHandler'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.examples.test.ts index d11b1e4087..d3d45bf195 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.examples.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.examples.test.ts @@ -16,7 +16,7 @@ import { createFilesystemDeleteAction } from './delete'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; -import { resolve as resolvePath } from 'path'; +import { resolve as resolvePath } from 'node:path'; import fs from 'fs-extra'; import yaml from 'yaml'; import { examples } from './delete.examples'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.test.ts index efc2fe3143..ca4c6615ee 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { resolve as resolvePath } from 'path'; +import { resolve as resolvePath } from 'node:path'; import { createFilesystemDeleteAction } from './delete'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import fs from 'fs-extra'; @@ -229,4 +229,26 @@ describe('fs:delete', () => { expect(fileExists).toBe(false); }); }); + + it('should not delete files outside workspace via symlinks', async () => { + // Create an external file that should not be deleted + const externalDir = resolvePath(mockDir.path, 'external'); + const externalFile = resolvePath(externalDir, 'config.yaml'); + await fs.ensureDir(externalDir); + await fs.writeFile(externalFile, 'external content'); + + // Create a symlink inside workspace pointing to external directory + const linkPath = resolvePath(workspacePath, 'link'); + await fs.symlink(externalDir, linkPath); + + // Try to delete files through the symlink + await expect(() => + action.handler({ + ...mockContext, + input: { files: ['link/**'] }, + }), + ).rejects.toThrow( + /Relative path is not allowed to refer to a directory outside its parent/, + ); + }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts index 0290414323..1c4ab6592b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts @@ -58,10 +58,11 @@ export const createFilesystemDeleteAction = () => { for (const filepath of resolvedPaths) { try { - await fs.remove(filepath); - ctx.logger.info(`File ${filepath} deleted successfully`); + const safePath = resolveSafeChildPath(ctx.workspacePath, filepath); + await fs.remove(safePath); + ctx.logger.info(`File ${safePath} deleted successfully`); } catch (err) { - ctx.logger.error(`Failed to delete file ${filepath}:`, err); + ctx.logger.error(`Failed to delete file`, err); throw err; } } diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/read.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/read.examples.test.ts index 5654fc4c07..c761d744c4 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/read.examples.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/read.examples.test.ts @@ -16,7 +16,7 @@ import { createFilesystemReadDirAction } from './read'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; -import { resolve as resolvePath } from 'path'; +import { resolve as resolvePath } from 'node:path'; import { createMockDirectory } from '@backstage/backend-test-utils'; import { examples } from './read.examples'; import yaml from 'yaml'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/read.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/read.ts index 62d3bde0df..84fa7a33e5 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/read.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/read.ts @@ -15,9 +15,9 @@ */ import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; -import fs from 'fs/promises'; -import path from 'path'; -import { z as zod } from 'zod'; +import fs from 'node:fs/promises'; +import path from 'node:path'; +import { z as zod } from 'zod/v3'; import { examples } from './read.examples'; const contentSchema = (z: typeof zod) => diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/rename.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/rename.examples.test.ts index 0881bb8260..30c2a4e2b6 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/rename.examples.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/rename.examples.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { resolve as resolvePath } from 'path'; +import { resolve as resolvePath } from 'node:path'; import { createFilesystemRenameAction } from './rename'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import fs from 'fs-extra'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/rename.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/rename.test.ts index 6696fda693..00930023d3 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/rename.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/rename.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { resolve as resolvePath } from 'path'; +import { resolve as resolvePath } from 'node:path'; import { createFilesystemRenameAction } from './rename'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import fs from 'fs-extra'; diff --git a/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts b/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts index 43c5aea40f..1c56248081 100644 --- a/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts @@ -19,7 +19,9 @@ import { BackstageCredentials, LoggerService, } from '@backstage/backend-plugin-api'; +import type { MetricsService } from '@backstage/backend-plugin-api/alpha'; import type { UserEntity } from '@backstage/catalog-model'; +import { Config } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { @@ -38,8 +40,8 @@ import { } from '@backstage/plugin-scaffolder-node'; import { JsonObject } from '@backstage/types'; import fs from 'fs-extra'; -import path from 'path'; -import { fileURLToPath } from 'url'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; import { v4 as uuid } from 'uuid'; import { NunjucksWorkflowRunner } from '../tasks/NunjucksWorkflowRunner'; import { DecoratedActionsRegistry } from './DecoratedActionsRegistry'; @@ -79,6 +81,8 @@ export type TemplateTesterCreateOptions = { additionalTemplateFilters?: Record; additionalTemplateGlobals?: Record; permissions?: PermissionEvaluator; + config?: Config; + metrics: MetricsService; }; /** @@ -105,6 +109,7 @@ export function createDryRunner(options: TemplateTesterCreateOptions) { }, }), ]), + config: options.config, }); // Extracting contentsPath and dryRunId from the baseUrl diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts index 2c661fab56..8187070ffa 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts @@ -20,7 +20,7 @@ import { TemplateActionRegistry, } from '../actions'; import { ScmIntegrations } from '@backstage/integration'; -import { JsonObject } from '@backstage/types'; +import { JsonArray, JsonObject } from '@backstage/types'; import { ConfigReader } from '@backstage/config'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { @@ -39,7 +39,10 @@ import { mockCredentials, mockServices, } from '@backstage/backend-test-utils'; -import { actionsRegistryServiceMock } from '@backstage/backend-test-utils/alpha'; +import { + actionsRegistryServiceMock, + metricsServiceMock, +} from '@backstage/backend-test-utils/alpha'; describe('NunjucksWorkflowRunner', () => { let actionRegistry: TemplateActionRegistry; @@ -57,6 +60,16 @@ describe('NunjucksWorkflowRunner', () => { const integrations = ScmIntegrations.fromConfig( new ConfigReader({ + scaffolder: { + defaultEnvironment: { + parameters: { + region: 'us-east-1', + }, + secrets: { + AWS_ACCESS_KEY: 'test-secret-value', + }, + }, + }, integrations: { github: [{ host: 'github.com', token: 'token' }], }, @@ -219,12 +232,27 @@ describe('NunjucksWorkflowRunner', () => { { result: AuthorizeResult.ALLOW }, ]); + const config = new ConfigReader({ + scaffolder: { + defaultEnvironment: { + parameters: { + region: 'us-east-1', + }, + secrets: { + AWS_ACCESS_KEY: 'test-secret-value', + }, + }, + }, + }); + runner = new NunjucksWorkflowRunner({ actionRegistry, integrations, workingDirectory: mockDir.path, logger, permissions: mockedPermissionApi, + config, + metrics: metricsServiceMock.mock(), }); }); @@ -491,6 +519,136 @@ describe('NunjucksWorkflowRunner', () => { }); }); + describe('conditional output items', () => { + it('should include output links without an if condition', async () => { + const task = createMockTaskWithSpec({ + steps: [{ id: 'test', name: 'test', action: 'jest-mock-action' }], + output: { + links: [{ title: 'Always', url: 'https://example.com' }], + }, + }); + + const { output } = await runner.execute(task); + expect(output.links).toEqual([ + { title: 'Always', url: 'https://example.com' }, + ]); + }); + + it('should filter out output links where if is false', async () => { + const task = createMockTaskWithSpec({ + steps: [{ id: 'test', name: 'test', action: 'jest-mock-action' }], + output: { + links: [ + { title: 'Always', url: 'https://example.com' }, + { if: false, title: 'Hidden', url: 'https://hidden.com' }, + ], + }, + }); + + const { output } = await runner.execute(task); + expect(output.links).toEqual([ + { title: 'Always', url: 'https://example.com' }, + ]); + }); + + it('should include output links where if is true', async () => { + const task = createMockTaskWithSpec({ + steps: [{ id: 'test', name: 'test', action: 'jest-mock-action' }], + output: { + links: [{ if: true, title: 'Visible', url: 'https://visible.com' }], + }, + }); + + const { output } = await runner.execute(task); + expect(output.links).toEqual([ + { title: 'Visible', url: 'https://visible.com' }, + ]); + }); + + it('should filter output links based on templated if condition', async () => { + const task = createMockTaskWithSpec({ + steps: [{ id: 'test', name: 'test', action: 'output-action' }], + output: { + links: [ + { + if: '${{ parameters.enableCI === "Yes" }}', + title: 'CI', + url: 'https://ci.example.com', + }, + { + if: '${{ parameters.enableCI === "Yes" }}', + title: 'CI Docs', + url: 'https://ci.example.com/docs', + }, + ], + }, + parameters: { enableCI: 'No' }, + }); + + const { output } = await runner.execute(task); + expect(output.links).toEqual([]); + }); + + it('should include output links when templated if condition is truthy', async () => { + const task = createMockTaskWithSpec({ + steps: [{ id: 'test', name: 'test', action: 'output-action' }], + output: { + links: [ + { + if: '${{ parameters.enableCI === "Yes" }}', + title: 'CI', + url: 'https://ci.example.com', + }, + ], + }, + parameters: { enableCI: 'Yes' }, + }); + + const { output } = await runner.execute(task); + expect(output.links).toEqual([ + { title: 'CI', url: 'https://ci.example.com' }, + ]); + }); + + it('should filter output text items based on if condition', async () => { + const task = createMockTaskWithSpec({ + steps: [{ id: 'test', name: 'test', action: 'jest-mock-action' }], + output: { + text: [ + { title: 'Always', content: 'visible' }, + { if: false, title: 'Hidden', content: 'hidden' }, + { + if: '${{ parameters.show }}', + title: 'Conditional', + content: 'conditional', + }, + ], + }, + parameters: { show: true }, + }); + + const { output } = await runner.execute(task); + expect(output.text).toEqual([ + { title: 'Always', content: 'visible' }, + { title: 'Conditional', content: 'conditional' }, + ]); + }); + + it('should strip the if field from output items that pass the condition', async () => { + const task = createMockTaskWithSpec({ + steps: [{ id: 'test', name: 'test', action: 'jest-mock-action' }], + output: { + links: [{ if: true, title: 'Link', url: 'https://example.com' }], + text: [{ if: true, title: 'Text', content: 'content' }], + }, + }); + + const { output } = await runner.execute(task); + expect((output.links as JsonArray)[0]).not.toHaveProperty('if'); + expect((output.text as JsonArray)[0]).not.toHaveProperty('if'); + }); + }); + describe('templating', () => { it('should template the input to an action', async () => { const task = createMockTaskWithSpec({ @@ -501,6 +659,7 @@ describe('NunjucksWorkflowRunner', () => { action: 'jest-mock-action', input: { foo: '${{parameters.input | lower }}', + region: '${{environment.parameters.region}}', }, }, ], @@ -512,7 +671,9 @@ describe('NunjucksWorkflowRunner', () => { await runner.execute(task); expect(fakeActionHandler).toHaveBeenCalledWith( - expect.objectContaining({ input: { foo: 'backstage' } }), + expect.objectContaining({ + input: { foo: 'backstage', region: 'us-east-1' }, + }), ); }); @@ -784,6 +945,49 @@ describe('NunjucksWorkflowRunner', () => { expectTaskLog('info: ***'); }); + // eslint-disable-next-line jest/expect-expect + it('should redact secrets that are passed in the environment', async () => { + actionRegistry.register({ + id: 'log-secret', + description: 'Mock action for testing', + supportsDryRun: true, + handler: async ctx => { + ctx.logger.info(ctx.input.secret); + }, + schema: { + input: { + type: 'object', + required: ['secret'], + properties: { + secret: { + type: 'string', + }, + }, + }, + }, + }); + + const task = createMockTaskWithSpec( + { + steps: [ + { + id: 'test', + name: 'name', + action: 'log-secret', + input: { + secret: '${{ environment.secrets.AWS_ACCESS_KEY }}', + }, + }, + ], + }, + { secret: 'my-secret-value' }, + ); + + await runner.execute(task); + + expectTaskLog('info: ***'); + }); + // eslint-disable-next-line jest/expect-expect it('should redact meta fields properly', async () => { actionRegistry.register({ @@ -826,6 +1030,405 @@ describe('NunjucksWorkflowRunner', () => { expectTaskLog('info: *** {"thing":"***"}'); }); + + // eslint-disable-next-line jest/expect-expect + it('should redact secrets that have been transformed with a replace filter', async () => { + actionRegistry.register({ + id: 'log-secret', + description: 'Mock action for testing', + supportsDryRun: true, + handler: async ctx => { + ctx.logger.info(ctx.input.secret); + }, + schema: { + input: { + type: 'object', + required: ['secret'], + properties: { + secret: { + type: 'string', + }, + }, + }, + }, + }); + + const task = createMockTaskWithSpec( + { + steps: [ + { + id: 'test', + name: 'name', + action: 'log-secret', + input: { + secret: "${{ secrets.backstageToken | replace('.', '_DOT_') }}", + }, + }, + ], + }, + { backstageToken: 'header.payload.signature' }, + ); + + await runner.execute(task); + + expectTaskLog('info: ***'); + }); + + // eslint-disable-next-line jest/expect-expect + it('should redact secrets transformed with the upper filter', async () => { + actionRegistry.register({ + id: 'log-secret', + description: 'Mock action for testing', + supportsDryRun: true, + handler: async ctx => { + ctx.logger.info(ctx.input.secret); + }, + schema: { + input: { + type: 'object', + required: ['secret'], + properties: { + secret: { + type: 'string', + }, + }, + }, + }, + }); + + const task = createMockTaskWithSpec( + { + steps: [ + { + id: 'test', + name: 'name', + action: 'log-secret', + input: { + secret: '${{ secrets.mySecret | upper }}', + }, + }, + ], + }, + { mySecret: 'super-secret-token' }, + ); + + await runner.execute(task); + + expectTaskLog('info: ***'); + }); + + // eslint-disable-next-line jest/expect-expect + it('should redact secrets embedded in a larger string with other text', async () => { + actionRegistry.register({ + id: 'log-secret', + description: 'Mock action for testing', + supportsDryRun: true, + handler: async ctx => { + ctx.logger.info(ctx.input.message); + }, + schema: { + input: { + type: 'object', + required: ['message'], + properties: { + message: { + type: 'string', + }, + }, + }, + }, + }); + + const task = createMockTaskWithSpec( + { + steps: [ + { + id: 'test', + name: 'name', + action: 'log-secret', + input: { + message: + "scaffold-init:${{ secrets.backstageToken | replace('.', '_DOT_') }}", + }, + }, + ], + }, + { backstageToken: 'header.payload.signature' }, + ); + + await runner.execute(task); + + expectTaskLog('info: ***'); + }); + + // eslint-disable-next-line jest/expect-expect + it('should redact environment secrets that have been transformed', async () => { + actionRegistry.register({ + id: 'log-secret', + description: 'Mock action for testing', + supportsDryRun: true, + handler: async ctx => { + ctx.logger.info(ctx.input.secret); + }, + schema: { + input: { + type: 'object', + required: ['secret'], + properties: { + secret: { + type: 'string', + }, + }, + }, + }, + }); + + const task = createMockTaskWithSpec( + { + steps: [ + { + id: 'test', + name: 'name', + action: 'log-secret', + input: { + secret: '${{ environment.secrets.AWS_ACCESS_KEY | upper }}', + }, + }, + ], + }, + {}, + ); + + await runner.execute(task); + + expectTaskLog('info: ***'); + }); + + it('should not redact non-secret values in rendered input', async () => { + actionRegistry.register({ + id: 'log-secret', + description: 'Mock action for testing', + supportsDryRun: true, + handler: async ctx => { + ctx.logger.info(ctx.input.name); + }, + schema: { + input: { + type: 'object', + required: ['name'], + properties: { + name: { + type: 'string', + }, + }, + }, + }, + }); + + const task = createMockTaskWithSpec({ + steps: [ + { + id: 'test', + name: 'name', + action: 'log-secret', + input: { + name: '${{ parameters.serviceName }}', + }, + }, + ], + parameters: { serviceName: 'my-service' }, + }); + + await runner.execute(task); + + expectTaskLog('info: my-service'); + }); + + // eslint-disable-next-line jest/expect-expect + it('should redact secrets in deeply nested input objects', async () => { + actionRegistry.register({ + id: 'log-secret', + description: 'Mock action for testing', + supportsDryRun: true, + handler: async ctx => { + ctx.logger.info(ctx.input.nested.deep.secret); + }, + schema: { + input: { + type: 'object', + required: ['nested'], + properties: { + nested: { + type: 'object', + properties: { + deep: { + type: 'object', + properties: { + secret: { type: 'string' }, + }, + }, + }, + }, + }, + }, + }, + }); + + const task = createMockTaskWithSpec( + { + steps: [ + { + id: 'test', + name: 'name', + action: 'log-secret', + input: { + nested: { + deep: { + secret: "${{ secrets.token | replace('.', '-') }}", + }, + }, + }, + }, + ], + }, + { token: 'aaa.bbb.ccc' }, + ); + + await runner.execute(task); + + expectTaskLog('info: ***'); + }); + + // eslint-disable-next-line jest/expect-expect + it('should redact secrets in arrays within input', async () => { + actionRegistry.register({ + id: 'log-secret', + description: 'Mock action for testing', + supportsDryRun: true, + handler: async ctx => { + ctx.logger.info(ctx.input.items[0]); + }, + schema: { + input: { + type: 'object', + required: ['items'], + properties: { + items: { + type: 'array', + items: { type: 'string' }, + }, + }, + }, + }, + }); + + const task = createMockTaskWithSpec( + { + steps: [ + { + id: 'test', + name: 'name', + action: 'log-secret', + input: { + items: ['${{ secrets.token | upper }}', 'not-a-secret'], + }, + }, + ], + }, + { token: 'my-secret' }, + ); + + await runner.execute(task); + + expectTaskLog('info: ***'); + }); + + // eslint-disable-next-line jest/expect-expect + it('should redact multiple different transformed secrets in the same step', async () => { + actionRegistry.register({ + id: 'log-secret', + description: 'Mock action for testing', + supportsDryRun: true, + handler: async ctx => { + ctx.logger.info(`${ctx.input.a} ${ctx.input.b}`); + }, + schema: { + input: { + type: 'object', + required: ['a', 'b'], + properties: { + a: { type: 'string' }, + b: { type: 'string' }, + }, + }, + }, + }); + + const task = createMockTaskWithSpec( + { + steps: [ + { + id: 'test', + name: 'name', + action: 'log-secret', + input: { + a: '${{ secrets.s1 | upper }}', + b: "${{ secrets.s2 | replace('.', '_') }}", + }, + }, + ], + }, + { s1: 'first-secret', s2: 'second.secret' }, + ); + + await runner.execute(task); + + expectTaskLog('info: *** ***'); + }); + + it('should still pass the correct transformed value to the action input', async () => { + actionRegistry.register({ + id: 'log-secret', + description: 'Mock action for testing', + supportsDryRun: true, + handler: fakeActionHandler, + schema: { + input: { + type: 'object', + required: ['secret'], + properties: { + secret: { + type: 'string', + }, + }, + }, + }, + }); + + const task = createMockTaskWithSpec( + { + steps: [ + { + id: 'test', + name: 'name', + action: 'log-secret', + input: { + secret: "${{ secrets.backstageToken | replace('.', '_DOT_') }}", + }, + }, + ], + }, + { backstageToken: 'header.payload.signature' }, + ); + + await runner.execute(task); + + expect(fakeActionHandler).toHaveBeenCalledWith( + expect.objectContaining({ + input: { secret: 'header_DOT_payload_DOT_signature' }, + }), + ); + }); }); describe('each', () => { @@ -996,6 +1599,50 @@ describe('NunjucksWorkflowRunner', () => { } }); + it('should run a step repeatedly - only iterations where the "if" condition is truthy', async () => { + const truthyConditions = [true, 1, 'a', {}]; + const falsyConditions = [false, 0, null, '']; + const conditions = [...truthyConditions, ...falsyConditions]; + const task = createMockTaskWithSpec({ + steps: [ + { + id: 'test', + name: 'name', + each: '${{parameters.conditions}}', + action: 'jest-mock-action', + input: { condition: '${{each.value}}' }, + if: '${{each.value}}', + }, + ], + parameters: { + conditions, + }, + }); + await runner.execute(task); + + truthyConditions.forEach((condition, idx) => { + expectTaskLog( + `info: Running step each: {"key":"${idx}","value":"${condition}"}`, + ); + expect(fakeActionHandler).toHaveBeenCalledWith( + expect.objectContaining({ input: { condition } }), + ); + }); + + falsyConditions.forEach((condition, idx) => { + expectTaskLog( + `info: Skipping step each: {"key":"${ + idx + truthyConditions.length + }","value":"${condition}"}`, + ); + expect(fakeActionHandler).not.toHaveBeenCalledWith( + expect.objectContaining({ input: { condition } }), + ); + }); + + expect(fakeActionHandler).toHaveBeenCalledTimes(truthyConditions.length); + }); + it('should run a step repeatedly with validation of single-expression value', async () => { const numbers = [5, 7, 9]; const task = createMockTaskWithSpec({ @@ -1090,7 +1737,9 @@ describe('NunjucksWorkflowRunner', () => { await runner.execute(task); expect(fakeActionHandler).toHaveBeenCalledWith( - expect.objectContaining({ secrets: { foo: 'bar' } }), + expect.objectContaining({ + secrets: { foo: 'bar' }, + }), ); }); @@ -1104,6 +1753,7 @@ describe('NunjucksWorkflowRunner', () => { action: 'jest-mock-action', input: { b: '${{ secrets.foo }}', + aws_key: '${{ environment.secrets.AWS_ACCESS_KEY }}', }, }, ], @@ -1114,7 +1764,41 @@ describe('NunjucksWorkflowRunner', () => { await runner.execute(task); expect(fakeActionHandler).toHaveBeenCalledWith( - expect.objectContaining({ input: { b: 'bar' } }), + expect.objectContaining({ + input: { b: 'bar', aws_key: 'test-secret-value' }, + }), + ); + }); + + it('should separate task secrets from environment secrets', async () => { + const task = createMockTaskWithSpec( + { + steps: [ + { + id: 'test', + name: 'name', + action: 'jest-mock-action', + input: { + b: '${{ secrets.foo }}', + aws_key: '${{ secrets.AWS_ACCESS_KEY }}', + env_aws_key: '${{ environment.secrets.AWS_ACCESS_KEY }}', + }, + }, + ], + }, + { foo: 'bar', AWS_ACCESS_KEY: 'another-value-from-task' }, + ); + + await runner.execute(task); + + expect(fakeActionHandler).toHaveBeenCalledWith( + expect.objectContaining({ + input: { + b: 'bar', + aws_key: 'another-value-from-task', + env_aws_key: 'test-secret-value', + }, + }), ); }); @@ -1133,6 +1817,7 @@ describe('NunjucksWorkflowRunner', () => { ], output: { b: '${{ secrets.foo }}', + c: '${{ environment.secrets.AWS_ACCESS_KEY }}', }, }, { foo: 'bar' }, @@ -1141,6 +1826,7 @@ describe('NunjucksWorkflowRunner', () => { const executedTask = await runner.execute(task); expect(executedTask.output.b).toBeUndefined(); + expect(executedTask.output.c).toBeUndefined(); }); }); @@ -1501,6 +2187,42 @@ describe('NunjucksWorkflowRunner', () => { expect(fakeActionHandler.mock.calls[0][0].step.id).toEqual('test'); expect(fakeActionHandler.mock.calls[0][0].step.name).toEqual('name'); }); + + it('should not pass environment secrets or task secrets to action inputs during dry-run', async () => { + const dryRunHandler = jest.fn(); + actionRegistry.register( + createTemplateAction({ + id: 'jest-dryrun-action', + description: 'Mock action with dry-run support', + supportsDryRun: true, + handler: dryRunHandler, + }), + ); + + const task = createMockTaskWithSpec( + { + steps: [ + { + id: 'test', + name: 'name', + action: 'jest-dryrun-action', + input: { + envSecret: '${{ environment.secrets.AWS_ACCESS_KEY }}', + taskSecret: '${{ secrets.mySecret }}', + }, + }, + ], + }, + { mySecret: 'task-secret-value', backstageToken: token }, + true, + ); + + await runner.execute(task); + + const handlerCall = dryRunHandler.mock.calls[0][0]; + expect(handlerCall.input.envSecret).toBeUndefined(); + expect(handlerCall.input.taskSecret).toBeUndefined(); + }); }); describe('permissions', () => { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index 51e41c7add..57c829a499 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -23,18 +23,21 @@ import { TaskStep, } from '@backstage/plugin-scaffolder-common'; import { JsonArray, JsonObject, JsonValue } from '@backstage/types'; -import { metrics } from '@opentelemetry/api'; import fs from 'fs-extra'; import { validate as validateJsonSchema } from 'jsonschema'; +import path from 'node:path'; import nunjucks from 'nunjucks'; -import path from 'path'; import * as winston from 'winston'; import { SecureTemplater, SecureTemplateRenderer, } from '../../lib/templating/SecureTemplater'; import { TemplateActionRegistry } from '../actions/TemplateActionRegistry'; -import { generateExampleOutput, isTruthy } from './helper'; +import { + filterConditionalItems, + generateExampleOutput, + isTruthy, +} from './helper'; import { TaskTrackType, WorkflowResponse, WorkflowRunner } from './types'; import type { @@ -42,7 +45,9 @@ import type { LoggerService, PermissionsService, } from '@backstage/backend-plugin-api'; +import type { MetricsService } from '@backstage/backend-plugin-api/alpha'; import { UserEntity } from '@backstage/catalog-model'; +import { Config } from '@backstage/config'; import { AuthorizeResult, PolicyDecision, @@ -51,6 +56,7 @@ import { createConditionAuthorizer } from '@backstage/plugin-permission-node'; import { actionExecutePermission } from '@backstage/plugin-scaffolder-common/alpha'; import { TaskContext, + TaskSecrets, TemplateAction, TemplateFilter, TemplateGlobal, @@ -59,11 +65,7 @@ import { CheckpointContext, CheckpointState, } from '@backstage/plugin-scaffolder-node/alpha'; -import { createDefaultFilters } from '../../lib/templating/filters/createDefaultFilters'; -import { scaffolderActionRules } from '../../service/rules'; -import { createCounterMetric, createHistogramMetric } from '../../util/metrics'; -import { convertFiltersToRecord } from '../../util/templating'; -import { BackstageLoggerTransport, WinstonLogger } from './logger'; +import { resolveDefaultEnvironment } from '../../lib/defaultEnvironment'; type NunjucksWorkflowRunnerOptions = { workingDirectory: string; @@ -74,10 +76,13 @@ type NunjucksWorkflowRunnerOptions = { additionalTemplateFilters?: Record; additionalTemplateGlobals?: Record; permissions?: PermissionsService; + config?: Config; + metrics: MetricsService; }; type TemplateContext = { parameters: JsonObject; + environment: JsonObject; EXPERIMENTAL_recovery?: TaskRecovery; steps: { [stepName: string]: { output: { [outputName: string]: JsonValue } }; @@ -103,10 +108,12 @@ const createStepLogger = ({ task, step, rootLogger, + redactions, }: { task: TaskContext; step: TaskStep; rootLogger: LoggerService; + redactions?: Record; }) => { const taskLogger = WinstonLogger.create({ level: process.env.LOG_LEVEL || 'info', @@ -117,11 +124,58 @@ const createStepLogger = ({ transports: [new BackstageLoggerTransport(rootLogger, task, step.id)], }); - taskLogger.addRedactions(Object.values(task.secrets ?? {})); + taskLogger.addRedactions(Object.values(redactions ?? {})); return { taskLogger }; }; +/** + * Recursively compares two rendered objects and returns string values from + * `withSecrets` that differ from their counterpart in `withoutSecrets`. + * These are values that were influenced by secret interpolation and should + * be added as log redactions. + */ +function collectSecretRedactions( + withSecrets: unknown, + withoutSecrets: unknown, +): string[] { + if (typeof withSecrets === 'string') { + return withSecrets !== withoutSecrets ? [withSecrets] : []; + } + if (Array.isArray(withSecrets)) { + const other = Array.isArray(withoutSecrets) ? withoutSecrets : []; + return withSecrets.flatMap((val, i) => + collectSecretRedactions(val, other[i]), + ); + } + if (withSecrets && typeof withSecrets === 'object') { + const other = + withoutSecrets && typeof withoutSecrets === 'object' + ? (withoutSecrets as Record) + : {}; + return Object.entries(withSecrets as Record).flatMap( + ([key, val]) => collectSecretRedactions(val, other[key]), + ); + } + return []; +} + +/** + * Extracts all string values from a nested object structure. + * Used as a fallback when the comparison render fails. + */ +function extractStringValues(obj: unknown): string[] { + if (typeof obj === 'string') return [obj]; + if (Array.isArray(obj)) return obj.flatMap(extractStringValues); + if (obj && typeof obj === 'object') { + return Object.entries(obj).flatMap(([key, val]) => [ + key, + ...extractStringValues(val), + ]); + } + return []; +} + const isActionAuthorized = createConditionAuthorizer( Object.values(scaffolderActionRules), ); @@ -129,6 +183,12 @@ const isActionAuthorized = createConditionAuthorizer( export class NunjucksWorkflowRunner implements WorkflowRunner { private readonly defaultTemplateFilters: Record; private readonly options: NunjucksWorkflowRunnerOptions; + private environment: { + parameters: JsonObject; + secrets?: Record; + } = { parameters: {}, secrets: {} }; + + private readonly tracker: ReturnType; constructor(options: NunjucksWorkflowRunnerOptions) { this.options = options; @@ -137,9 +197,26 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { integrations: this.options.integrations, }), ); + this.tracker = scaffoldingTracker(options.metrics); } - private readonly tracker = scaffoldingTracker(); + async getEnvironmentConfig(): Promise<{ + parameters: JsonObject; + secrets?: TaskSecrets; + }> { + if (this.options.config) { + const defaultEnvironment = resolveDefaultEnvironment(this.options.config); + return { + parameters: defaultEnvironment.parameters, + secrets: defaultEnvironment.secrets, + }; + } + + return { + parameters: {}, + secrets: {}, + }; + } private isSingleTemplateString(input: string) { const { parser, nodes } = nunjucks as unknown as { @@ -241,6 +318,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { if ( step.if === false || (typeof step.if === 'string' && + step.each === undefined && !isTruthy(this.render(step.if, context, renderTemplate))) ) { await stepTrack.skipFalsy(); @@ -254,26 +332,40 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { task, step, rootLogger: this.options.logger, + redactions: { + ...task.secrets, + ...this.environment?.secrets, + }, }); if (task.isDryRun) { const redactedSecrets = Object.fromEntries( Object.entries(task.secrets ?? {}).map(secret => [secret[0], '***']), ); + + const redactedEnvironmentSecrets = Object.fromEntries( + Object.entries(this.environment?.secrets ?? {}).map(secret => [ + secret[0], + '***', + ]), + ); const debugInput = (step.input && this.render( step.input, { ...context, + environment: { + parameters: this.environment?.parameters || {}, + secrets: redactedEnvironmentSecrets, + }, secrets: redactedSecrets, }, renderTemplate, )) ?? {}; taskLogger.info( - `Running ${ - action.id + `Running ${action.id } in dry-run mode with inputs (secrets redacted): ${JSON.stringify( debugInput, undefined, @@ -296,13 +388,18 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { } } + const preIterationContext = { + ...context, + environment: { + parameters: this.environment?.parameters ?? {}, + secrets: task.isDryRun ? {} : this.environment?.secrets ?? {}, + }, + secrets: task.isDryRun ? {} : task.secrets ?? {}, + }; + const resolvedEach = step.each && - this.render( - step.each, - { ...context, secrets: task.secrets ?? {} }, - renderTemplate, - ); + this.render(step.each, preIterationContext, renderTemplate); if (step.each && !resolvedEach) { throw new InputError( @@ -313,24 +410,34 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { const iterations = ( resolvedEach ? Object.entries(resolvedEach).map(([key, value]) => ({ - each: { key, value }, - })) + each: { key, value }, + })) : [{}] - ).map(i => ({ - ...i, - // Secrets are only passed when templating the input to actions for security reasons - input: step.input - ? this.render( - step.input, - { ...context, secrets: task.secrets ?? {}, ...i }, - renderTemplate, - ) - : {}, - })); + ).map(i => { + const fullContext = { ...preIterationContext, ...i }; + // Evaluate if condition once per iteration, only when using 'each' + const shouldRun = + !('each' in i) || + !step.if || + isTruthy(this.render(step.if, fullContext, renderTemplate)); + + return { + ...i, + shouldRun, + // Secrets are only passed when templating the input to actions for security reasons + input: step.input + ? this.render(step.input, fullContext, renderTemplate) + : {}, + }; + }); for (const iteration of iterations) { - const actionId = `${action.id}${ - iteration.each ? `[${iteration.each.key}]` : '' - }`; + if (!iteration.shouldRun) { + // No need to check schema or authorization for iterations that will not run + continue; + } + + const actionId = `${action.id}${iteration.each ? `[${iteration.each.key}]` : '' + }`; if (action.schema?.input) { const validateResult = validateJsonSchema( @@ -365,15 +472,58 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { for (const iteration of iterations) { if (iteration.each) { + if (!iteration.shouldRun) { + taskLogger.info( + `Skipping step each: ${JSON.stringify( + iteration.each, + (k, v) => (k ? String(v) : v), + 0, + )}`, + ); + continue; + } taskLogger.info( `Running step each: ${JSON.stringify( iteration.each, - (k, v) => (k ? v.toString() : v), + (k, v) => (k ? String(v) : v), 0, )}`, ); } + // Redact any rendered values that were influenced by secrets. + // Re-render the input without secrets and diff against the real render + // to find values that changed due to secret interpolation. + if (step.input) { + const hasSecrets = + Object.keys(task.secrets ?? {}).length > 0 || + Object.keys(this.environment?.secrets ?? {}).length > 0; + + if (hasSecrets) { + try { + const contextNoSecrets = { + ...preIterationContext, + ...(iteration.each ? { each: iteration.each } : {}), + secrets: {}, + environment: { + ...preIterationContext.environment, + secrets: {}, + }, + }; + const inputWithoutSecrets = this.render( + step.input, + contextNoSecrets, + renderTemplate, + ); + taskLogger.addRedactions( + collectSecretRedactions(iteration.input, inputWithoutSecrets), + ); + } catch { + taskLogger.addRedactions(extractStringValues(iteration.input)); + } + } + } + await action.handler({ input: iteration.input, task: { @@ -485,6 +635,8 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { const { additionalTemplateFilters, additionalTemplateGlobals } = this.options; + this.environment = await this.getEnvironmentConfig(); + const { render: renderTemplate, dispose } = await SecureTemplater.loadRenderer({ templateFilters: { @@ -502,6 +654,10 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { const context: TemplateContext = { parameters: task.spec.parameters, + environment: { + parameters: this.environment?.parameters || {}, + secrets: {}, + }, steps: {}, user: task.spec.user, context: { @@ -514,9 +670,9 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { const [decision]: PolicyDecision[] = this.options.permissions && task.spec.steps.length ? await this.options.permissions.authorizeConditional( - [{ permission: actionExecutePermission }], - { credentials: await task.getInitiatorCredentials() }, - ) + [{ permission: actionExecutePermission }], + { credentials: await task.getInitiatorCredentials() }, + ) : [{ result: AuthorizeResult.ALLOW }]; for (const step of task.spec.steps) { @@ -532,6 +688,15 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { } const output = this.render(task.spec.output, context, renderTemplate); + + // Filter output links and text items based on their `if` condition + if (Array.isArray(output?.links)) { + output.links = filterConditionalItems(output.links); + } + if (Array.isArray(output?.text)) { + output.text = filterConditionalItems(output.text); + } + await taskTrack.markSuccessful(); await task.cleanWorkspace?.(); @@ -549,7 +714,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { } } -function scaffoldingTracker() { +function scaffoldingTracker(metrics: MetricsService) { // prom-client metrics are deprecated in favour of OpenTelemetry metrics. const promTaskCount = createCounterMetric({ name: 'scaffolder_task_count', @@ -572,23 +737,22 @@ function scaffoldingTracker() { labelNames: ['template', 'step', 'result'], }); - const meter = metrics.getMeter('default'); - const taskCount = meter.createCounter('scaffolder.task.count', { - description: 'Count of task runs', + const taskCount = metrics.createCounter('scaffolder.task.count', { + description: 'Total number of scaffolder tasks executed', }); - const taskDuration = meter.createHistogram('scaffolder.task.duration', { - description: 'Duration of a task run', - unit: 'seconds', + const taskDuration = metrics.createHistogram('scaffolder.task.duration', { + description: 'Time taken to complete a scaffolder task end-to-end', + unit: 's', }); - const stepCount = meter.createCounter('scaffolder.step.count', { - description: 'Count of step runs', + const stepCount = metrics.createCounter('scaffolder.step.count', { + description: 'Total number of individual scaffolder action steps executed', }); - const stepDuration = meter.createHistogram('scaffolder.step.duration', { - description: 'Duration of a step runs', - unit: 'seconds', + const stepDuration = metrics.createHistogram('scaffolder.step.duration', { + description: 'Time taken to complete a single scaffolder action step', + unit: 's', }); async function taskStart(task: TaskContext) { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts index 2b13130908..d162f2abbf 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts @@ -103,6 +103,22 @@ describe('StorageTaskBroker', () => { expect(task.secrets).toEqual(fakeSecrets); }, 10000); + it('should return secrets with priority over defaults', async () => { + const broker = new StorageTaskBroker(storage, logger); + await broker.dispatch(emptyTaskWithFakeSecretsSpec); + const task = await broker.claim(); + + expect(task.secrets).toEqual(fakeSecrets); + }, 10000); + + it('should return all secrets', async () => { + const broker = new StorageTaskBroker(storage, logger); + await broker.dispatch(emptyTaskWithFakeSecretsSpec); + const task = await broker.claim(); + + expect(task.secrets).toEqual({ ...fakeSecrets }); + }, 10000); + it('should complete a task', async () => { const broker = new StorageTaskBroker(storage, logger); const dispatchResult = await broker.dispatch(emptyTaskSpec); @@ -232,7 +248,7 @@ describe('StorageTaskBroker', () => { id: taskId, }), ]), - totalTasks: 13, + totalTasks: 15, }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts index 7fe0618af2..71ed6b9eb4 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import os from 'os'; +import os from 'node:os'; import { DatabaseManager } from '@backstage/backend-defaults/database'; import { ConfigReader } from '@backstage/config'; import { DatabaseTaskStore } from './DatabaseTaskStore'; @@ -36,6 +36,7 @@ import { WorkflowRunner } from './types'; import ObservableImpl from 'zen-observable'; import waitForExpect from 'wait-for-expect'; import { mockServices } from '@backstage/backend-test-utils'; +import { metricsServiceMock } from '@backstage/backend-test-utils/alpha'; import { loggerToWinstonLogger } from '../../util/loggerToWinstonLogger'; jest.mock('./NunjucksWorkflowRunner'); @@ -93,6 +94,7 @@ describe('TaskWorker', () => { integrations, taskBroker: broker, actionRegistry, + metrics: metricsServiceMock.mock(), }); await broker.dispatch({ @@ -124,6 +126,7 @@ describe('TaskWorker', () => { integrations, taskBroker: broker, actionRegistry, + metrics: metricsServiceMock.mock(), }); const { taskId } = await broker.dispatch({ @@ -174,6 +177,7 @@ describe('TaskWorker', () => { }, }, }), + metrics: metricsServiceMock.mock(), }); await taskWorker.runOneTask({ @@ -261,6 +265,7 @@ describe('Concurrent TaskWorker', () => { taskBroker: broker, actionRegistry, concurrentTasksLimit: expectedConcurrentTasks, + metrics: metricsServiceMock.mock(), }); taskWorker.start(); @@ -307,6 +312,7 @@ describe('Cancellable TaskWorker', () => { integrations, taskBroker, actionRegistry, + metrics: metricsServiceMock.mock(), }); const steps = [...Array(10)].map(n => ({ diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index 3a15b345db..ca3ef3918e 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -15,7 +15,8 @@ */ import { AuditorService, LoggerService } from '@backstage/backend-plugin-api'; -import { assertError, InputError, stringifyError } from '@backstage/errors'; +import type { MetricsService } from '@backstage/backend-plugin-api/alpha'; +import { InputError, stringifyError, toError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { @@ -28,7 +29,7 @@ import PQueue from 'p-queue'; import { TemplateActionRegistry } from '../actions/TemplateActionRegistry'; import { NunjucksWorkflowRunner } from './NunjucksWorkflowRunner'; import { WorkflowRunner } from './types'; -import { setTimeout } from 'timers/promises'; +import { setTimeout } from 'node:timers/promises'; import { JsonObject } from '@backstage/types'; import { Config } from '@backstage/config'; @@ -78,6 +79,7 @@ export type CreateWorkerOptions = { additionalTemplateGlobals?: Record; permissions?: PermissionEvaluator; gracefulShutdown?: boolean; + metrics: MetricsService; }; /** @@ -123,6 +125,7 @@ export class TaskWorker { additionalTemplateGlobals, permissions, gracefulShutdown, + metrics, } = options; const workflowRunner = new NunjucksWorkflowRunner({ @@ -134,6 +137,8 @@ export class TaskWorker { additionalTemplateFilters, additionalTemplateGlobals, permissions, + config, + metrics, }); return new TaskWorker({ @@ -223,12 +228,12 @@ export class TaskWorker { await task.complete('completed', { output }); await auditorEvent?.success(); } catch (error) { - assertError(error); + const err = toError(error); await auditorEvent?.fail({ - error, + error: err, }); await task.complete('failed', { - error: { name: error.name, message: error.message }, + error: { name: err.name, message: err.message }, }); } } diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/helper.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/helper.ts index da47bfd563..1b14fe6798 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/helper.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/helper.ts @@ -15,7 +15,7 @@ */ import { Config, readDurationFromConfig } from '@backstage/config'; -import { HumanDuration } from '@backstage/types'; +import { HumanDuration, JsonObject, JsonValue } from '@backstage/types'; import { isArray } from 'lodash'; import { Schema } from 'jsonschema'; @@ -29,6 +29,25 @@ export function isTruthy(value: any): boolean { return isArray(value) ? value.length > 0 : !!value; } +function isPlainObject(item: JsonValue): item is JsonObject { + return typeof item === 'object' && item !== null && !Array.isArray(item); +} + +/** + * Filters an output array by evaluating the `if` condition on each item, + * and strips the `if` field from items that pass. Non-plain-object items + * are passed through unchanged. + */ +export function filterConditionalItems(items: readonly T[]): T[] { + return items.flatMap(item => { + if (!isPlainObject(item as JsonValue)) return [item]; + const obj = item as JsonObject; + if ('if' in obj && !isTruthy(obj.if)) return []; + const { if: _if, ...rest } = obj; + return [rest as T]; + }); +} + export function generateExampleOutput(schema: Schema): unknown { const { examples } = schema as { examples?: unknown }; if (examples && Array.isArray(examples)) { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts index 6ca24663b6..5958aacf41 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts @@ -84,9 +84,10 @@ export class BackstageLoggerTransport extends Transport { break; default: this.backstageLogger.info(String(message)); + break; } - this.taskContext.emitLog(message, { stepId: this.stepId }); + this.taskContext.emitLog(String(message), { stepId: this.stepId }); callback(); } } @@ -131,7 +132,9 @@ export class WinstonLogger implements RootLoggerService { return obj; } - obj[MESSAGE] = obj[MESSAGE]?.replace?.(redactionPattern, '***'); + if (typeof obj[MESSAGE] === 'string') { + obj[MESSAGE] = obj[MESSAGE].replace(redactionPattern, '***'); + } return obj; })(), @@ -189,10 +192,13 @@ export class WinstonLogger implements RootLoggerService { const level = info[LEVEL]; const fields = info[SPLAT]; const prefix = plugin || service; - const timestampColor = colorizer.colorize('timestamp', timestamp); - const prefixColor = colorizer.colorize('prefix', prefix); + const timestampColor = colorizer.colorize( + 'timestamp', + String(timestamp), + ); + const prefixColor = colorizer.colorize('prefix', String(prefix)); - const extraFields = Object.entries(fields) + const extraFields = Object.entries(fields as any) .map( ([key, value]) => `${colorizer.colorize('field', `${key}`)}=${value}`, diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts index 874e32b838..22882e8f71 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts @@ -160,6 +160,10 @@ export type WorkflowResponse = { output: { [key: string]: JsonValue } }; export interface WorkflowRunner { execute(task: TaskContext): Promise; + getEnvironmentConfig?(): Promise<{ + parameters: JsonObject; + secrets?: TaskSecrets; + }>; } export type TaskTrackType = { diff --git a/plugins/scaffolder-backend/src/schema/openapi.yaml b/plugins/scaffolder-backend/src/schema/openapi.yaml index b89f54603c..2baf9fbba3 100644 --- a/plugins/scaffolder-backend/src/schema/openapi.yaml +++ b/plugins/scaffolder-backend/src/schema/openapi.yaml @@ -1,4 +1,4 @@ -openapi: 3.0.3 +openapi: 3.1.0 info: title: scaffolder version: '1' @@ -35,7 +35,6 @@ components: name: kind in: path required: true - allowReserved: true schema: type: string limit: @@ -51,14 +50,12 @@ components: name: namespace in: path required: true - allowReserved: true schema: type: string name: name: name in: path required: true - allowReserved: true schema: type: string offset: @@ -94,7 +91,6 @@ components: name: taskId in: path required: true - allowReserved: true schema: type: string requestBodies: {} @@ -113,7 +109,7 @@ components: type: string description: type: string - "'examples'": + examples: type: array items: $ref: '#/components/schemas/ActionExample' @@ -224,8 +220,7 @@ components: - type: boolean - type: number - type: string - - type: object - nullable: true + - type: 'null' description: A type representing all allowed JSON primitive values. JsonValue: oneOf: @@ -417,7 +412,7 @@ components: $ref: '#/components/schemas/JsonObject' output: $ref: '#/components/schemas/JsonObject' - "'examples'": + examples: type: array items: $ref: '#/components/schemas/ScaffolderUsageExample' @@ -428,8 +423,9 @@ components: description: type: string value: - type: object - nullable: true + anyOf: + - type: object + - type: 'null' required: - value description: The response shape for a single global value in the `listTemplatingExtensions` call to the `scaffolder-backend` @@ -449,7 +445,7 @@ components: $ref: '#/components/schemas/JsonObject' output: $ref: '#/components/schemas/JsonObject' - "'examples'": + examples: type: array items: $ref: '#/components/schemas/ScaffolderUsageExample' @@ -690,6 +686,19 @@ paths: type: string required: - id + '400': + description: Validation errors. + content: + application/json: + schema: + type: object + properties: + errors: + type: array + items: + $ref: '#/components/schemas/ValidationError' + required: + - errors security: - {} - JWT: [] @@ -860,13 +869,11 @@ paths: - in: path name: provider required: true - allowReserved: true schema: type: string - in: path name: resource required: true - allowReserved: true schema: type: string diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/apis/Api.server.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/apis/Api.server.ts index c5bd9f0ac2..af824321b2 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/apis/Api.server.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/apis/Api.server.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -116,7 +116,7 @@ export type Retry = { taskId: string; }; body: RetryRequest; - response: Scaffold201Response; + response: Scaffold201Response | Scaffold400Response; }; /** * @public diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/apis/index.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/apis/index.ts index 8d81cbaf39..9a0ffed740 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/apis/index.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/apis/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/index.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/index.ts index dec4b8804e..58fc52487a 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/index.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/Action.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/Action.model.ts index f9c028f3e3..a29e2f704d 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/Action.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/Action.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ActionExample.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ActionExample.model.ts index 50550e2883..c7a5cf5075 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ActionExample.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ActionExample.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ActionSchema.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ActionSchema.model.ts index 35d2014181..e8cc85c647 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ActionSchema.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ActionSchema.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/Autocomplete200Response.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/Autocomplete200Response.model.ts index e3eec0aa20..eb59d6a061 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/Autocomplete200Response.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/Autocomplete200Response.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/Autocomplete200ResponseResultsInner.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/Autocomplete200ResponseResultsInner.model.ts index 00ccb41b0f..fe329488d5 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/Autocomplete200ResponseResultsInner.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/Autocomplete200ResponseResultsInner.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/Autocomplete400Response.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/Autocomplete400Response.model.ts index 1456babb96..3dae0fd0f1 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/Autocomplete400Response.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/Autocomplete400Response.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/AutocompleteRequest.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/AutocompleteRequest.model.ts index 769a929918..197582c944 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/AutocompleteRequest.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/AutocompleteRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/CancelTask200Response.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/CancelTask200Response.model.ts index f29c509d41..b23fafc892 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/CancelTask200Response.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/CancelTask200Response.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRun200Response.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRun200Response.model.ts index 62e3689d5d..d0c1450785 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRun200Response.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRun200Response.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRun200ResponseAllOfDirectoryContentsInner.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRun200ResponseAllOfDirectoryContentsInner.model.ts index 2646ca49f9..ced04f2ae6 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRun200ResponseAllOfDirectoryContentsInner.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRun200ResponseAllOfDirectoryContentsInner.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRun200ResponseAllOfStepsInner.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRun200ResponseAllOfStepsInner.model.ts index 2a462edefa..9818ca581e 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRun200ResponseAllOfStepsInner.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRun200ResponseAllOfStepsInner.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,6 @@ */ export interface DryRun200ResponseAllOfStepsInner { [key: string]: any; - id: string; name: string; action: string; diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunRequest.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunRequest.model.ts index 212b36c26b..254605a6b4 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunRequest.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunRequestDirectoryContentsInner.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunRequestDirectoryContentsInner.model.ts index ddcae4fd9d..f01e2d18c8 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunRequestDirectoryContentsInner.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunRequestDirectoryContentsInner.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunResult.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunResult.model.ts index 6e2ad23ef4..cfdcf163bd 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunResult.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunResult.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunResultLogInner.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunResultLogInner.model.ts index 4c6b786d82..496650d8ba 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunResultLogInner.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunResultLogInner.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunResultLogInnerBody.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunResultLogInnerBody.model.ts index e3b19be045..a56e7b1107 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunResultLogInnerBody.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunResultLogInnerBody.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ErrorError.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ErrorError.model.ts index e0265e95d7..853f88cc14 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ErrorError.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ErrorError.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ErrorRequest.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ErrorRequest.model.ts index 3eb5e15740..1591911bc9 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ErrorRequest.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ErrorRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ErrorResponse.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ErrorResponse.model.ts index edbcc32df7..1eff0557ed 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ErrorResponse.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ErrorResponse.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/JsonPrimitive.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/JsonPrimitive.model.ts index 6aa6394c14..0142269c5b 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/JsonPrimitive.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/JsonPrimitive.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,4 +22,4 @@ * A type representing all allowed JSON primitive values. * @public */ -export type JsonPrimitive = any | boolean | number | string; +export type JsonPrimitive = boolean | number | string; diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/JsonValue.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/JsonValue.model.ts index 9fe2ac9de3..a706c3c93e 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/JsonValue.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/JsonValue.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ListTasksResponse.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ListTasksResponse.model.ts index 9347c766a9..0f641660fe 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ListTasksResponse.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ListTasksResponse.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ListTemplatingExtensionsResponse.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ListTemplatingExtensionsResponse.model.ts index 74b7cd3780..6c65af534c 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ListTemplatingExtensionsResponse.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ListTemplatingExtensionsResponse.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ListTemplatingExtensionsResponseGlobals.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ListTemplatingExtensionsResponseGlobals.model.ts index 1f022a0675..b827a9f083 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ListTemplatingExtensionsResponseGlobals.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ListTemplatingExtensionsResponseGlobals.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ModelError.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ModelError.model.ts index 958fde7d0b..9d5c36325e 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ModelError.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ModelError.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,6 @@ import { ErrorResponse } from '../models/ErrorResponse.model'; */ export interface ModelError { [key: string]: any; - error: ErrorError; request?: ErrorRequest; response: ErrorResponse; diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/RetryRequest.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/RetryRequest.model.ts index 858fa67566..b25d0a2ed5 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/RetryRequest.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/RetryRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/Scaffold201Response.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/Scaffold201Response.model.ts index 1271642e81..b69aa7b00c 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/Scaffold201Response.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/Scaffold201Response.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/Scaffold400Response.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/Scaffold400Response.model.ts index e3cb7eaa45..d42caec5a4 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/Scaffold400Response.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/Scaffold400Response.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ScaffolderScaffoldOptions.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ScaffolderScaffoldOptions.model.ts index 93f4ae71c0..dbcc372253 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ScaffolderScaffoldOptions.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ScaffolderScaffoldOptions.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ScaffolderUsageExample.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ScaffolderUsageExample.model.ts index 3e0f4ef866..7e664963eb 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ScaffolderUsageExample.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ScaffolderUsageExample.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/SerializedFile.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/SerializedFile.model.ts index 65a3c714a7..74ee62f05a 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/SerializedFile.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/SerializedFile.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/SerializedTask.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/SerializedTask.model.ts index 67cbf14279..779efcab87 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/SerializedTask.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/SerializedTask.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/SerializedTaskEvent.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/SerializedTaskEvent.model.ts index 6e4ddaa296..da412a5839 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/SerializedTaskEvent.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/SerializedTaskEvent.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TaskEventType.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TaskEventType.model.ts index d32d77a448..daa28d1d62 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TaskEventType.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TaskEventType.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TaskSecrets.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TaskSecrets.model.ts index c78c39c1ec..0cbaa38a55 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TaskSecrets.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TaskSecrets.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TaskStatus.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TaskStatus.model.ts index 1f209a3656..75df8d3679 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TaskStatus.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TaskStatus.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateFilter.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateFilter.model.ts index 7c631978e6..3339c61a77 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateFilter.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateFilter.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateFilterSchema.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateFilterSchema.model.ts index 5a95a58ead..930a9760d9 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateFilterSchema.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateFilterSchema.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateGlobalFunction.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateGlobalFunction.model.ts index ee19ce723b..7567b3112d 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateGlobalFunction.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateGlobalFunction.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateGlobalFunctionSchema.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateGlobalFunctionSchema.model.ts index b3a39c3b24..731cf53496 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateGlobalFunctionSchema.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateGlobalFunctionSchema.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateGlobalValue.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateGlobalValue.model.ts index 95d177a05a..d85fe0c395 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateGlobalValue.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateGlobalValue.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateParameterSchema.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateParameterSchema.model.ts index 8638ee8114..dd71410c86 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateParameterSchema.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateParameterSchema.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,6 @@ import { TemplateParameterSchemaStepsInner } from '../models/TemplateParameterSc */ export interface TemplateParameterSchema { [key: string]: any; - title: string; description?: string; steps: Array; diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateParameterSchemaStepsInner.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateParameterSchemaStepsInner.model.ts index 2f7892fd41..ef94e58643 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateParameterSchemaStepsInner.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateParameterSchemaStepsInner.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ValidationError.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ValidationError.model.ts index 7e762f616c..43c45c8351 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ValidationError.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ValidationError.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,6 @@ import { ValidationErrorPathInner } from '../models/ValidationErrorPathInner.mod */ export interface ValidationError { [key: string]: any; - path: Array; property: string; message: string; diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ValidationErrorArgument.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ValidationErrorArgument.model.ts index 85c9576fd0..123b2a0d76 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ValidationErrorArgument.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ValidationErrorArgument.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ValidationErrorPathInner.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ValidationErrorPathInner.model.ts index 4535c735db..53d3b9d2e7 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ValidationErrorPathInner.model.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ValidationErrorPathInner.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/index.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/index.ts index 1bc447fa3d..3d5ceab140 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/models/index.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,6 @@ export * from '../models/Autocomplete400Response.model'; export * from '../models/AutocompleteRequest.model'; export * from '../models/CancelTask200Response.model'; export * from '../models/DryRun200Response.model'; -export * from '../models/DryRun200ResponseAllOf.model'; export * from '../models/DryRun200ResponseAllOfDirectoryContentsInner.model'; export * from '../models/DryRun200ResponseAllOfStepsInner.model'; export * from '../models/DryRunRequest.model'; @@ -31,7 +30,6 @@ export * from '../models/DryRunRequestDirectoryContentsInner.model'; export * from '../models/DryRunResult.model'; export * from '../models/DryRunResultLogInner.model'; export * from '../models/DryRunResultLogInnerBody.model'; -export * from '../models/DryRunResultLogInnerBodyAllOf.model'; export * from '../models/ErrorError.model'; export * from '../models/ErrorRequest.model'; export * from '../models/ErrorResponse.model'; @@ -51,7 +49,6 @@ export * from '../models/SerializedTask.model'; export * from '../models/SerializedTaskEvent.model'; export * from '../models/TaskEventType.model'; export * from '../models/TaskSecrets.model'; -export * from '../models/TaskSecretsAllOf.model'; export * from '../models/TaskStatus.model'; export * from '../models/TemplateFilter.model'; export * from '../models/TemplateFilterSchema.model'; diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/router.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/router.ts index 669dd63878..1f12056a69 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/router.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/router.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import { createValidatedOpenApiRouterFromGeneratedEndpointMap } from '@backstage import { EndpointMap } from './apis'; export const spec = { - openapi: '3.0.3', + openapi: '3.1.0', info: { title: 'scaffolder', version: '1', @@ -69,7 +69,6 @@ export const spec = { name: 'kind', in: 'path', required: true, - allowReserved: true, schema: { type: 'string', }, @@ -89,7 +88,6 @@ export const spec = { name: 'namespace', in: 'path', required: true, - allowReserved: true, schema: { type: 'string', }, @@ -98,7 +96,6 @@ export const spec = { name: 'name', in: 'path', required: true, - allowReserved: true, schema: { type: 'string', }, @@ -144,7 +141,6 @@ export const spec = { name: 'taskId', in: 'path', required: true, - allowReserved: true, schema: { type: 'string', }, @@ -173,7 +169,7 @@ export const spec = { description: { type: 'string', }, - "'examples'": { + examples: { type: 'array', items: { $ref: '#/components/schemas/ActionExample', @@ -325,8 +321,7 @@ export const spec = { type: 'string', }, { - type: 'object', - nullable: true, + type: 'null', }, ], description: 'A type representing all allowed JSON primitive values.', @@ -588,7 +583,7 @@ export const spec = { }, }, }, - "'examples'": { + examples: { type: 'array', items: { $ref: '#/components/schemas/ScaffolderUsageExample', @@ -605,8 +600,14 @@ export const spec = { type: 'string', }, value: { - type: 'object', - nullable: true, + anyOf: [ + { + type: 'object', + }, + { + type: 'null', + }, + ], }, }, required: ['value'], @@ -636,7 +637,7 @@ export const spec = { }, }, }, - "'examples'": { + examples: { type: 'array', items: { $ref: '#/components/schemas/ScaffolderUsageExample', @@ -1022,6 +1023,25 @@ export const spec = { }, }, }, + '400': { + description: 'Validation errors.', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + errors: { + type: 'array', + items: { + $ref: '#/components/schemas/ValidationError', + }, + }, + }, + required: ['errors'], + }, + }, + }, + }, }, security: [ {}, @@ -1272,7 +1292,6 @@ export const spec = { in: 'path', name: 'provider', required: true, - allowReserved: true, schema: { type: 'string', }, @@ -1281,7 +1300,6 @@ export const spec = { in: 'path', name: 'resource', required: true, - allowReserved: true, schema: { type: 'string', }, diff --git a/plugins/scaffolder-backend/src/schema/openapi/index.ts b/plugins/scaffolder-backend/src/schema/openapi/index.ts index 196aad553a..fb49a2b9c0 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/index.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-backend/src/service/helpers.ts b/plugins/scaffolder-backend/src/service/helpers.ts index e29eec4d64..b99129d92c 100644 --- a/plugins/scaffolder-backend/src/service/helpers.ts +++ b/plugins/scaffolder-backend/src/service/helpers.ts @@ -27,11 +27,11 @@ import { stringifyEntityRef, } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; -import { assertError, InputError, NotFoundError } from '@backstage/errors'; +import { InputError, NotFoundError, toError } from '@backstage/errors'; import { CatalogService } from '@backstage/plugin-catalog-node'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import fs from 'fs-extra'; -import os from 'os'; +import os from 'node:os'; export async function getWorkingDirectory( config: Config, @@ -47,13 +47,13 @@ export async function getWorkingDirectory( await fs.access(workingDirectory, fs.constants.F_OK | fs.constants.W_OK); logger.info(`using working directory: ${workingDirectory}`); } catch (err) { - assertError(err); + const error = toError(err); logger.error( `working directory ${workingDirectory} ${ - err.code === 'ENOENT' ? 'does not exist' : 'is not writable' + error.code === 'ENOENT' ? 'does not exist' : 'is not writable' }`, ); - throw err; + throw error; } return workingDirectory; } diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index 16a6000224..5b463ee75b 100644 --- a/plugins/scaffolder-backend/src/service/router.test.ts +++ b/plugins/scaffolder-backend/src/service/router.test.ts @@ -20,17 +20,7 @@ import express from 'express'; import request from 'supertest'; import ObservableImpl from 'zen-observable'; -/** - * TODO: The following should import directly from the router file. - * Due to a circular dependency between this plugin and the - * plugin-scaffolder-backend-module-cookiecutter plugin, it results in an error: - * TypeError: _pluginscaffolderbackend.createTemplateAction is not a function - */ -import { - parseEntityRef, - stringifyEntityRef, - UserEntity, -} from '@backstage/catalog-model'; +import { stringifyEntityRef, UserEntity } from '@backstage/catalog-model'; import { createTemplateAction, TaskBroker, @@ -68,7 +58,10 @@ import { import { createDefaultFilters } from '../lib/templating/filters/createDefaultFilters'; import { createRouter } from './router'; import { DatabaseTaskStore } from '../scaffolder/tasks/DatabaseTaskStore'; -import { actionsRegistryServiceMock } from '@backstage/backend-test-utils/alpha'; +import { + actionsRegistryServiceMock, + metricsServiceMock, +} from '@backstage/backend-test-utils/alpha'; import { ActionsService } from '@backstage/backend-plugin-api/alpha'; function createDatabase(): DatabaseService { @@ -89,9 +82,9 @@ function createDatabase(): DatabaseService { const config = new ConfigReader({}); -// todo: this needs to return a new object every time as there seems to -// be some mutation in the tests. -const generateMockTemplate = () => ({ +// Returns a new mock template object each time to avoid mutation issues. +// Accepts optional spec overrides that are merged with the base spec. +const generateMockTemplate = (specOverrides?: Record) => ({ apiVersion: 'scaffolder.backstage.io/v1beta3', kind: 'Template', metadata: { @@ -152,6 +145,7 @@ const generateMockTemplate = () => ({ }, }, ], + ...specOverrides, }, }); @@ -159,7 +153,7 @@ const mockUser: UserEntity = { apiVersion: 'backstage.io/v1alpha1', kind: 'User', metadata: { - name: 'guest', + name: 'mock', annotations: { 'google.com/email': 'bobby@tables.com', }, @@ -181,6 +175,7 @@ const createTestRouter = async ( | CreatedTemplateGlobal[]; autocompleteHandlers?: Record; actionsRegistry?: ActionsService; + entities?: any[]; } = {}, ) => { const logger = mockServices.logger.mock({ @@ -202,26 +197,14 @@ const createTestRouter = async ( jest.spyOn(taskBroker, 'vacuumTasks'); jest.spyOn(taskBroker, 'event$'); - const catalog = catalogServiceMock.mock(); + const entities = overrides.entities ?? [generateMockTemplate(), mockUser]; + const catalog = catalogServiceMock({ entities }); const permissions = mockServices.permissions(); const auth = mockServices.auth(); const httpAuth = mockServices.httpAuth(); const events = mockServices.events(); - catalog.getEntityByRef.mockImplementation(async ref => { - const { kind } = parseEntityRef(ref); - - if (kind.toLocaleLowerCase() === 'template') { - return generateMockTemplate(); - } - - if (kind.toLocaleLowerCase() === 'user') { - return mockUser; - } - - throw new Error(`no mock found for kind: ${kind}`); - }); - + const permissionsRegistry = mockServices.permissionsRegistry.mock(); const router = await createRouter({ logger, config: new ConfigReader({}), @@ -229,6 +212,7 @@ const createTestRouter = async ( catalog, taskBroker, permissions, + permissionsRegistry, auth, httpAuth, events, @@ -250,6 +234,7 @@ const createTestRouter = async ( createDebugLogAction(), ], actionsRegistry: overrides.actionsRegistry ?? actionsRegistryServiceMock(), + metrics: metricsServiceMock.mock(), }); router.use(mockErrorHandler()); @@ -302,7 +287,6 @@ describe('scaffolder router', () => { expect(response.body).toContainEqual({ description: 'Test', - examples: [], id: 'test:my-demo-action', schema: { input: { @@ -657,6 +641,132 @@ describe('scaffolder router', () => { expect(response.status).toEqual(400); }); + it('rejects when required secrets are missing', async () => { + const templateWithSecrets = generateMockTemplate({ + secrets: { + schema: { + type: 'object', + required: ['NPM_TOKEN'], + properties: { + NPM_TOKEN: { type: 'string' }, + }, + }, + }, + }); + + const { router } = await createTestRouter({ + entities: [templateWithSecrets, mockUser], + }); + + const response = await request(router) + .post('/v2/tasks') + .send({ + templateRef: stringifyEntityRef({ + kind: 'template', + name: 'create-react-app-template', + }), + values: { + requiredParameter1: 'required-value-1', + requiredParameter2: 'required-value-2', + }, + // No secrets provided + }); + + expect(response.status).toEqual(400); + expect(response.body.errors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + property: 'secrets', + message: 'secrets.NPM_TOKEN is required', + }), + ]), + ); + }); + + it('rejects when required secrets are missing without explicit type', async () => { + const templateWithSecrets = generateMockTemplate({ + secrets: { + schema: { + // No explicit type: 'object' - should still work + required: ['NPM_TOKEN'], + properties: { + NPM_TOKEN: { type: 'string' }, + }, + }, + }, + }); + + const { router } = await createTestRouter({ + entities: [templateWithSecrets, mockUser], + }); + + const response = await request(router) + .post('/v2/tasks') + .send({ + templateRef: stringifyEntityRef({ + kind: 'template', + name: 'create-react-app-template', + }), + values: { + requiredParameter1: 'required-value-1', + requiredParameter2: 'required-value-2', + }, + // No secrets provided + }); + + expect(response.status).toEqual(400); + expect(response.body.errors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + property: 'secrets', + message: 'secrets.NPM_TOKEN is required', + }), + ]), + ); + }); + + it('accepts valid secrets matching the schema', async () => { + const templateWithSecrets = generateMockTemplate({ + secrets: { + schema: { + type: 'object', + required: ['NPM_TOKEN'], + properties: { + NPM_TOKEN: { type: 'string' }, + }, + }, + }, + }); + + const { router, taskBroker } = await createTestRouter({ + entities: [templateWithSecrets, mockUser], + }); + const broker = taskBroker.dispatch as jest.Mocked['dispatch']; + + broker.mockResolvedValue({ + taskId: 'a-random-id', + }); + + const response = await request(router) + .post('/v2/tasks') + .send({ + templateRef: stringifyEntityRef({ + kind: 'template', + name: 'create-react-app-template', + }), + values: { + requiredParameter1: 'required-value-1', + requiredParameter2: 'required-value-2', + }, + secrets: { + NPM_TOKEN: 'my-secret-token', + }, + }); + + expect(response.status).toEqual(201); + expect(response.body.id).toBe('a-random-id'); + }); + it('return the template id', async () => { const { router, taskBroker } = await createTestRouter(); const broker = taskBroker.dispatch as jest.Mocked['dispatch']; @@ -1093,6 +1203,97 @@ describe('scaffolder router', () => { }); }); + describe('POST /v2/tasks/:taskId/retry', () => { + it('rejects when required secrets are missing', async () => { + const templateWithSecrets = generateMockTemplate({ + secrets: { + schema: { + type: 'object', + required: ['NPM_TOKEN'], + properties: { + NPM_TOKEN: { type: 'string' }, + }, + }, + }, + }); + + const { router, taskBroker } = await createTestRouter({ + entities: [templateWithSecrets, mockUser], + }); + + (taskBroker.get as jest.Mocked['get']).mockResolvedValue({ + id: 'a-random-id', + spec: { + templateInfo: { + entityRef: 'template:default/create-react-app-template', + baseUrl: 'https://example.com', + entity: { metadata: templateWithSecrets.metadata }, + }, + } as any, + status: 'failed', + createdAt: '', + createdBy: 'user:default/mock', + }); + + const response = await request(router) + .post('/v2/tasks/a-random-id/retry') + .send({}); + + expect(response.status).toEqual(400); + expect(response.body.errors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + property: 'secrets', + message: 'secrets.NPM_TOKEN is required', + }), + ]), + ); + }); + + it('accepts valid secrets on retry', async () => { + const templateWithSecrets = generateMockTemplate({ + secrets: { + schema: { + type: 'object', + required: ['NPM_TOKEN'], + properties: { + NPM_TOKEN: { type: 'string' }, + }, + }, + }, + }); + + const { router, taskBroker } = await createTestRouter({ + entities: [templateWithSecrets, mockUser], + }); + + (taskBroker.get as jest.Mocked['get']).mockResolvedValue({ + id: 'a-random-id', + spec: { + templateInfo: { + entityRef: 'template:default/create-react-app-template', + baseUrl: 'https://example.com', + entity: { metadata: templateWithSecrets.metadata }, + }, + } as any, + status: 'failed', + createdAt: '', + createdBy: 'user:default/mock', + }); + + const response = await request(router) + .post('/v2/tasks/a-random-id/retry') + .send({ + secrets: { + NPM_TOKEN: 'my-secret-token', + }, + }); + + expect(response.status).toEqual(201); + expect(taskBroker.retry).toHaveBeenCalled(); + }); + }); + describe('GET /v2/tasks/:taskId/eventstream', () => { it('should return log messages', async () => { const { unwrappedRouter: router, taskBroker } = await createTestRouter(); @@ -1371,6 +1572,46 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ }); expect(subscriber!.closed).toBe(true); }); + + it('should handle after=0 query param correctly', async () => { + const { router, taskBroker } = await createTestRouter(); + (taskBroker.get as jest.Mocked['get']).mockResolvedValue({ + id: 'a-random-id', + spec: {} as any, + status: 'completed', + createdAt: '', + secrets: { + __initiatorCredentials: JSON.stringify(credentials), + }, + createdBy: '', + }); + let subscriber: ZenObservable.SubscriptionObserver<{ + events: SerializedTaskEvent[]; + }>; + ( + taskBroker.event$ as jest.Mocked['event$'] + ).mockImplementation(() => { + return new ObservableImpl(observer => { + subscriber = observer; + observer.next({ events: [] }); + }); + }); + + const response = await request(router) + .get('/v2/tasks/a-random-id/events') + .query({ after: 0 }); + + expect(response.status).toEqual(200); + expect(response.body).toEqual([]); + + expect(taskBroker.event$).toHaveBeenCalledTimes(1); + expect(taskBroker.event$).toHaveBeenCalledWith({ + taskId: 'a-random-id', + after: 0, + }); + expect(subscriber!.closed).toBe(true); + }); + it('disallows users from seeing events for tasks they do not own', async () => { const { permissions, router, taskBroker } = await createTestRouter(); @@ -1413,6 +1654,9 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ const mockToken = mockCredentials.user.token(); const mockTemplate = generateMockTemplate(); + // Spy on the catalog method to verify it's called correctly + const getEntityByRefSpy = jest.spyOn(catalog, 'getEntityByRef'); + await request(router) .post('/v2/dry-run') .set('Authorization', `Bearer ${mockToken}`) @@ -1425,13 +1669,54 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ directoryContents: [], }); - expect(catalog.getEntityByRef).toHaveBeenCalledTimes(1); + expect(getEntityByRefSpy).toHaveBeenCalledTimes(1); - expect(catalog.getEntityByRef).toHaveBeenCalledWith( + expect(getEntityByRefSpy).toHaveBeenCalledWith( 'user:default/mock', expect.anything(), ); }); + + it('rejects when required secrets are missing', async () => { + const { router } = await createTestRouter(); + const mockToken = mockCredentials.user.token(); + + const templateWithSecrets = generateMockTemplate({ + secrets: { + schema: { + type: 'object', + required: ['NPM_TOKEN'], + properties: { + NPM_TOKEN: { type: 'string' }, + }, + }, + }, + }); + + const response = await request(router) + .post('/v2/dry-run') + .set('Authorization', `Bearer ${mockToken}`) + .send({ + template: templateWithSecrets, + values: { + requiredParameter1: 'required-value-1', + requiredParameter2: 'required-value-2', + }, + directoryContents: [], + // No secrets provided + }); + + expect(response.status).toEqual(400); + expect(response.body.errors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + property: 'secrets', + message: 'secrets.NPM_TOKEN is required', + }), + ]), + ); + }); + it('allows payloads up to 10MB', async () => { const { unwrappedRouter } = await createTestRouter(); const mockToken = mockCredentials.user.token(); diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 3894ffb876..10d81cc515 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -16,17 +16,19 @@ import { AuditorService, + AuditorServiceEvent, AuthService, BackstageCredentials, DatabaseService, HttpAuthService, LifecycleService, LoggerService, + PermissionsRegistryService, PermissionsService, resolveSafeChildPath, SchedulerService, } from '@backstage/backend-plugin-api'; -import { validate } from 'jsonschema'; +import { validate, ValidatorResult } from 'jsonschema'; import { CompoundEntityRef, Entity, @@ -44,7 +46,6 @@ import { ConditionTransformer, createConditionAuthorizer, createConditionTransformer, - createPermissionIntegrationRouter, } from '@backstage/plugin-permission-node'; import { TaskSpec, @@ -52,16 +53,13 @@ import { templateEntityV1beta3Validator, } from '@backstage/plugin-scaffolder-common'; import { - RESOURCE_TYPE_SCAFFOLDER_ACTION, - RESOURCE_TYPE_SCAFFOLDER_TASK, - RESOURCE_TYPE_SCAFFOLDER_TEMPLATE, scaffolderActionPermissions, - scaffolderPermissions, scaffolderTaskPermissions, scaffolderTemplatePermissions, taskCancelPermission, taskCreatePermission, taskReadPermission, + templateManagementPermission, templateParameterReadPermission, templateStepReadPermission, } from '@backstage/plugin-scaffolder-common/alpha'; @@ -77,14 +75,17 @@ import { AutocompleteHandler, CreatedTemplateFilter, CreatedTemplateGlobal, + scaffolderActionPermissionResourceRef, + scaffolderTaskPermissionResourceRef, + scaffolderTemplatePermissionResourceRef, WorkspaceProvider, } from '@backstage/plugin-scaffolder-node/alpha'; import { HumanDuration, JsonObject } from '@backstage/types'; import express from 'express'; import { Duration } from 'luxon'; -import { pathToFileURL } from 'url'; +import { pathToFileURL } from 'node:url'; import { v4 as uuid } from 'uuid'; -import { z } from 'zod'; +import { z } from 'zod/v3'; import { DatabaseTaskStore, DefaultTemplateActionRegistry, @@ -130,7 +131,10 @@ import { scaffolderTaskRules, scaffolderTemplateRules, } from './rules'; -import { ActionsService } from '@backstage/backend-plugin-api/alpha'; +import { + ActionsService, + MetricsService, +} from '@backstage/backend-plugin-api/alpha'; /** * RouterOptions @@ -157,6 +161,7 @@ export interface RouterOptions { | CreatedTemplateGlobal[]; additionalWorkspaceProviders?: Record; permissions?: PermissionsService; + permissionsRegistry: PermissionsRegistryService; permissionRules?: Array; auth: AuthService; httpAuth: HttpAuthService; @@ -164,6 +169,7 @@ export interface RouterOptions { auditor?: AuditorService; autocompleteHandlers?: Record; actionsRegistry: ActionsService; + metrics: MetricsService; } function isSupportedTemplate(entity: TemplateEntityV1beta3) { @@ -181,6 +187,49 @@ const readDuration = ( return defaultValue; }; +function formatSecretsValidationErrors(result: ValidatorResult) { + return result.errors.map(err => { + const property = err.property.replace(/^instance/, 'secrets'); + const secretName = err.argument; + const message = + err.name === 'required' + ? `secrets.${secretName} is required` + : `${property} ${err.message}`; + return { + ...err, + property, + message, + instance: {}, + }; + }); +} + +async function validateSecrets(options: { + template: TemplateEntityV1beta3; + secrets: Record; + res: express.Response; + auditorEvent?: AuditorServiceEvent; +}): Promise { + const { template, secrets, res, auditorEvent } = options; + if (!template.spec.secrets?.schema) { + return true; + } + + const result = validate(secrets, template.spec.secrets.schema); + if (result.valid) { + return true; + } + + await auditorEvent?.fail({ + error: new InputError('Secrets validation failed'), + }); + + res.status(400).json({ + errors: formatSecretsValidationErrors(result), + }); + return false; +} + /** * A method to create a router for the scaffolder backend plugin. */ @@ -205,6 +254,7 @@ export async function createRouter( additionalTemplateGlobals, additionalWorkspaceProviders, permissions, + permissionsRegistry, permissionRules, autocompleteHandlers = {}, events: eventsService, @@ -212,6 +262,7 @@ export async function createRouter( httpAuth, auditor, actionsRegistry, + metrics, } = options; const concurrentTasksLimit = @@ -300,6 +351,7 @@ export async function createRouter( concurrentTasksLimit, permissions, gracefulShutdown, + metrics, ...templateExtensions, }); @@ -330,6 +382,8 @@ export async function createRouter( auditor, workingDirectory, permissions, + config, + metrics, ...templateExtensions, }); @@ -358,35 +412,35 @@ export async function createRouter( const taskTransformConditions: ConditionTransformer = createConditionTransformer(Object.values(taskRules)); - const permissionIntegrationRouter = createPermissionIntegrationRouter({ - resources: [ - { - resourceType: RESOURCE_TYPE_SCAFFOLDER_TEMPLATE, - permissions: scaffolderTemplatePermissions, - rules: templateRules, - }, - { - resourceType: RESOURCE_TYPE_SCAFFOLDER_ACTION, - permissions: scaffolderActionPermissions, - rules: actionRules, - }, - { - resourceType: RESOURCE_TYPE_SCAFFOLDER_TASK, - permissions: scaffolderTaskPermissions, - rules: taskRules, - getResources: async resourceRefs => { - return Promise.all( - resourceRefs.map(async taskId => { - return await taskBroker.get(taskId); - }), - ); - }, - }, - ], - permissions: scaffolderPermissions, + permissionsRegistry.addResourceType({ + resourceRef: scaffolderTemplatePermissionResourceRef, + permissions: scaffolderTemplatePermissions, + rules: templateRules, }); - router.use(permissionIntegrationRouter); + permissionsRegistry.addResourceType({ + resourceRef: scaffolderActionPermissionResourceRef, + permissions: scaffolderActionPermissions, + rules: actionRules, + }); + + permissionsRegistry.addResourceType({ + resourceRef: scaffolderTaskPermissionResourceRef, + permissions: scaffolderTaskPermissions, + rules: taskRules, + getResources: async resourceRefs => { + return Promise.all( + resourceRefs.map(async taskId => { + return await taskBroker.get(taskId); + }), + ); + }, + }); + + permissionsRegistry.addPermissions([ + taskCreatePermission, + templateManagementPermission, + ]); router .get( @@ -526,6 +580,16 @@ export async function createRouter( } } + const secretsValid = await validateSecrets({ + template, + secrets: req.body.secrets ?? {}, + res, + auditorEvent, + }); + if (!secretsValid) { + return; + } + const baseUrl = getEntityBaseUrl(template); const taskSpec: TaskSpec = { @@ -746,6 +810,28 @@ export async function createRouter( isTaskAuthorized, }); + // Validate secrets against template schema if defined + if (task.spec.templateInfo?.entityRef) { + const templateEntityRef = parseEntityRef( + task.spec.templateInfo.entityRef, + { defaultKind: 'template' }, + ); + const template = await authorizeTemplate( + templateEntityRef, + credentials, + ); + + const secretsValid = await validateSecrets({ + template, + secrets: req.body.secrets ?? {}, + res, + auditorEvent, + }); + if (!secretsValid) { + return; + } + } + await auditorEvent?.success(); const { token } = await auth.getPluginRequestToken({ @@ -874,7 +960,8 @@ export async function createRouter( isTaskAuthorized, }); - const after = Number(req.query.after) || undefined; + const after = + req.query.after !== undefined ? Number(req.query.after) : undefined; // cancel the request after 30 seconds. this aligns with the recommendations of RFC 6202. const timeout = setTimeout(() => { @@ -974,6 +1061,16 @@ export async function createRouter( } } + const secretsValid = await validateSecrets({ + template, + secrets: body.secrets ?? {}, + res, + auditorEvent, + }); + if (!secretsValid) { + return; + } + const steps = template.spec.steps.map((step, index) => ({ ...step, id: step.id ?? `step-${index + 1}`, diff --git a/plugins/scaffolder-backend/src/service/rules.test.ts b/plugins/scaffolder-backend/src/service/rules.test.ts index 85e393247a..04892b79f5 100644 --- a/plugins/scaffolder-backend/src/service/rules.test.ts +++ b/plugins/scaffolder-backend/src/service/rules.test.ts @@ -14,19 +14,15 @@ * limitations under the License. */ -import { JsonObject, JsonPrimitive } from '@backstage/types'; +import { JsonObject } from '@backstage/types'; import { hasActionId, hasBooleanProperty, hasNumberProperty, - hasProperty, hasStringProperty, hasTag, isTaskOwner, } from './rules'; -import { createConditionAuthorizer } from '@backstage/plugin-permission-node'; -import { RESOURCE_TYPE_SCAFFOLDER_ACTION } from '@backstage/plugin-scaffolder-common/alpha'; -import { AuthorizeResult } from '@backstage/plugin-permission-common'; import { SerializedTask } from '@backstage/plugin-scaffolder-node'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; @@ -137,162 +133,6 @@ const input: JsonObject = { nested: { propwithstring: '1', nested: { propwithnumber: 1 } }, }; -describe('hasProperty', () => { - describe('apply', () => { - it.each([ - 'foo', - 'bar', - 'prop.prop', - 'nested.nonexisting', - '', - 'propwitharray.100', - ])(`returns false when a property doesn't exist in the input`, key => { - expect(hasProperty.apply({ action: 'action', input }, { key })).toEqual( - false, - ); - }); - - it.each([ - 'propwithstring', - 'propwithnumber', - 'propwithobject', - 'propwithnull', - 'propwithfalse', - 'propwithtrue', - 'propwitharray', - 'propwitharray.1', - 'nested.propwithstring', - 'nested.nested', - 'nested.nested.propwithnumber', - ])(`returns true when a property exists, property=%s`, key => { - expect(hasProperty.apply({ action: 'action', input }, { key })).toEqual( - true, - ); - }); - - it.each([ - ['propwithstring', 1], - ['propwithnumber', '2'], - ['propwithnumber', true], - ['propwithobject', [{}]], - ['propwithnull', false], - ['propwithfalse', true], - ['propwithtrue', null], - ['propwitharray', 'nonexistingitem'], - ['propwitharray.0', 'nonmatchingitem'], - ['nested.propwithstring', 'x'], - ['nested.nested', '1'], - ['nested.nested.propwithnumber', 'ops'], - ])( - `returns false when a property exists but the value doesn't match, key=%s value=%o`, - (key, value) => { - expect( - hasProperty.apply( - { action: 'action', input }, - { key, value: value as JsonPrimitive }, - ), - ).toEqual(false); - }, - ); - - it.each([ - ['propwithstring', '1'], - ['propwithnumber', 2], - ['propwithnull', null], - ['propwithfalse', false], - ['propwithtrue', true], - ['propwitharray.0', 'item'], - ['nested.propwithstring', '1'], - ['nested.nested.propwithnumber', 1], - ])( - `returns true when a property exists and the value matches, key=%s value=%o`, - (key, value) => { - expect( - hasProperty.apply({ action: 'action', input }, { key, value }), - ).toEqual(true); - }, - ); - - it('should throw if params are invalid', () => { - const isActionAuthorized = createConditionAuthorizer([hasProperty]); - - expect(() => - isActionAuthorized( - { - resourceType: RESOURCE_TYPE_SCAFFOLDER_ACTION, - pluginId: 'scaffolder', - result: AuthorizeResult.CONDITIONAL, - conditions: { - resourceType: RESOURCE_TYPE_SCAFFOLDER_ACTION, - rule: 'HAS_PROPERTY', - params: { - key: 1, - }, - }, - }, - { action: 'an-action', input: {} }, - ), - ).toThrow(); - expect(() => - isActionAuthorized( - { - resourceType: RESOURCE_TYPE_SCAFFOLDER_ACTION, - pluginId: 'scaffolder', - result: AuthorizeResult.CONDITIONAL, - conditions: { - resourceType: RESOURCE_TYPE_SCAFFOLDER_ACTION, - rule: 'HAS_PROPERTY', - params: {}, - }, - }, - { action: 'an-action', input: {} }, - ), - ).toThrow(); - }); - - it('should not throw if params are valid', () => { - const isActionAuthorized = createConditionAuthorizer([hasProperty]); - - expect(() => - isActionAuthorized( - { - resourceType: RESOURCE_TYPE_SCAFFOLDER_ACTION, - pluginId: 'scaffolder', - result: AuthorizeResult.CONDITIONAL, - conditions: { - resourceType: RESOURCE_TYPE_SCAFFOLDER_ACTION, - rule: 'HAS_PROPERTY', - params: { - key: 'key', - }, - }, - }, - { action: 'an-action', input: {} }, - ), - ).not.toThrow(); - - expect(() => - isActionAuthorized( - { - resourceType: RESOURCE_TYPE_SCAFFOLDER_ACTION, - pluginId: 'scaffolder', - result: AuthorizeResult.CONDITIONAL, - conditions: { - resourceType: RESOURCE_TYPE_SCAFFOLDER_ACTION, - rule: 'HAS_PROPERTY', - params: { - key: 'key', - value: 'value', - }, - }, - }, - { action: 'an-action', input: {} }, - ), - ).not.toThrow(); - }); - }); -}); - describe('hasBooleanProperty', () => { describe('apply', () => { it.each(['foo', 'bar', 'prop.prop', 'nested.nonexisting', ''])( diff --git a/plugins/scaffolder-backend/src/service/rules.ts b/plugins/scaffolder-backend/src/service/rules.ts index 5753668089..a163933c53 100644 --- a/plugins/scaffolder-backend/src/service/rules.ts +++ b/plugins/scaffolder-backend/src/service/rules.ts @@ -29,7 +29,7 @@ import { import { SerializedTask, TaskFilter } from '@backstage/plugin-scaffolder-node'; -import { z } from 'zod'; +import { z } from 'zod/v3'; import { JsonObject, JsonPrimitive } from '@backstage/types'; import { get } from 'lodash'; @@ -74,12 +74,6 @@ export const hasActionId = createActionPermissionRule({ toQuery: () => ({}), }); -export const hasProperty = buildHasProperty({ - name: 'HAS_PROPERTY', - valueSchema: z.union([z.string(), z.number(), z.boolean(), z.null()]), - validateProperty: false, -}); - export const hasBooleanProperty = buildHasProperty({ name: 'HAS_BOOLEAN_PROPERTY', valueSchema: z.boolean(), diff --git a/plugins/scaffolder-backend/src/util/templating.ts b/plugins/scaffolder-backend/src/util/templating.ts index 3318b64fdf..a088c95525 100644 --- a/plugins/scaffolder-backend/src/util/templating.ts +++ b/plugins/scaffolder-backend/src/util/templating.ts @@ -26,7 +26,7 @@ import { } from '@backstage/plugin-scaffolder-node/alpha'; import { JsonValue } from '@backstage/types'; import { Schema } from 'jsonschema'; -import { ZodType, z } from 'zod'; +import { ZodType, z } from 'zod/v3'; import zodToJsonSchema from 'zod-to-json-schema'; /** diff --git a/plugins/scaffolder-common/CHANGELOG.md b/plugins/scaffolder-common/CHANGELOG.md index 628085e4e1..886e52a809 100644 --- a/plugins/scaffolder-common/CHANGELOG.md +++ b/plugins/scaffolder-common/CHANGELOG.md @@ -1,5 +1,157 @@ # @backstage/plugin-scaffolder-common +## 2.1.0 + +### Minor Changes + +- e5fcfcb: Added `scaffolderCatalogModelLayer`, a Template kind model layer with JSON Schema definition, serving as an example of how plugins can declare their own catalog entity kinds. + +### Patch Changes + +- 3ef6078: Added optional `if` property to `ScaffolderOutputLink` and `ScaffolderOutputText` types, allowing template authors to conditionally include output links and text items. +- Updated dependencies + - @backstage/errors@1.3.0 + - @backstage/catalog-model@1.8.0 + - @backstage/integration@2.0.1 + - @backstage/plugin-permission-common@0.9.8 + +## 2.0.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/integration@2.0.1-next.0 + - @backstage/catalog-model@1.7.8-next.0 + - @backstage/plugin-permission-common@0.9.8-next.0 + +## 2.0.0 + +### Major Changes + +- 527cf88: **BREAKING** Removed deprecated `bitbucket` integration from being registered in the `ScaffolderClient`. Use the `bitbucketCloud` or `bitbucketServer` integrations instead. + +### Minor Changes + +- f598909: **BREAKING PRODUCERS**: Made `retry`, `listTasks`, `listTemplatingExtensions`, `dryRun`, and `autocomplete` required methods on the `ScaffolderApi` interface. Implementations of `ScaffolderApi` must now provide these methods. +- e8736ea: Added an optional `secrets` field to `TemplateEntityV1beta3` for configuring secrets validation. The schema for validating secrets is defined under `secrets.schema` as a JSON Schema object. + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/catalog-model@1.7.7 + +## 2.0.0-next.2 + +### Minor Changes + +- e8736ea: Added an optional `secrets` field to `TemplateEntityV1beta3` for configuring secrets validation. The schema for validating secrets is defined under `secrets.schema` as a JSON Schema object. + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.2 + +## 2.0.0-next.1 + +### Major Changes + +- 527cf88: **BREAKING** Removed deprecated `bitbucket` integration from being registered in the `ScaffolderClient`. Use the `bitbucketCloud` or `bitbucketServer` integrations instead. + +### Minor Changes + +- f598909: **BREAKING PRODUCERS**: Made `retry`, `listTasks`, `listTemplatingExtensions`, `dryRun`, and `autocomplete` required methods on the `ScaffolderApi` interface. Implementations of `ScaffolderApi` must now provide these methods. + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.6 + +## 1.7.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.6 + +## 1.7.6 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/plugin-permission-common@0.9.6 + +## 1.7.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + +## 1.7.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.3-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 1.7.5 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2 + - @backstage/plugin-permission-common@0.9.4 + +## 1.7.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + +## 1.7.4 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + +## 1.7.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + +## 1.7.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/catalog-model@1.7.6 + ## 1.7.3-next.0 ### Patch Changes diff --git a/plugins/scaffolder-common/package.json b/plugins/scaffolder-common/package.json index 142603d7c5..73bdf010c4 100644 --- a/plugins/scaffolder-common/package.json +++ b/plugins/scaffolder-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-common", - "version": "1.7.3-next.0", + "version": "2.1.0", "description": "Common functionalities for the scaffolder, to be shared between scaffolder and scaffolder-backend plugin", "backstage": { "role": "common-library", diff --git a/plugins/scaffolder-common/report-alpha.api.md b/plugins/scaffolder-common/report-alpha.api.md index 0bed459cc8..c7ea24100f 100644 --- a/plugins/scaffolder-common/report-alpha.api.md +++ b/plugins/scaffolder-common/report-alpha.api.md @@ -4,6 +4,7 @@ ```ts import { BasicPermission } from '@backstage/plugin-permission-common'; +import { CatalogModelLayer } from '@backstage/catalog-model/alpha'; import { ResourcePermission } from '@backstage/plugin-permission-common'; // @alpha @@ -21,6 +22,9 @@ export const RESOURCE_TYPE_SCAFFOLDER_TEMPLATE = 'scaffolder-template'; // @alpha export const scaffolderActionPermissions: ResourcePermission<'scaffolder-action'>[]; +// @alpha +export const scaffolderCatalogModelLayer: CatalogModelLayer; + // @alpha export const scaffolderPermissions: ( | BasicPermission diff --git a/plugins/scaffolder-common/report.api.md b/plugins/scaffolder-common/report.api.md index c6c5141cf2..2c6c928920 100644 --- a/plugins/scaffolder-common/report.api.md +++ b/plugins/scaffolder-common/report.api.md @@ -58,7 +58,7 @@ export type LogEvent = { // @public export interface ScaffolderApi { // (undocumented) - autocomplete?( + autocomplete( request: { token: string; provider: string; @@ -79,99 +79,6 @@ export interface ScaffolderApi { status?: ScaffolderTaskStatus; }>; // (undocumented) - dryRun?( - request: ScaffolderDryRunOptions, - options?: ScaffolderRequestOptions, - ): Promise; - // (undocumented) - getIntegrationsList( - options: ScaffolderGetIntegrationsListOptions, - ): Promise; - // (undocumented) - getTask( - taskId: string, - options?: ScaffolderRequestOptions, - ): Promise; - // (undocumented) - getTemplateParameterSchema( - templateRef: string, - options?: ScaffolderRequestOptions, - ): Promise; - listActions(options?: ScaffolderRequestOptions): Promise; - // (undocumented) - listTasks?( - request: { - filterByOwnership: 'owned' | 'all'; - limit?: number; - offset?: number; - }, - options?: ScaffolderRequestOptions, - ): Promise<{ - tasks: ScaffolderTask[]; - totalTasks?: number; - }>; - listTemplatingExtensions?( - options?: ScaffolderRequestOptions, - ): Promise; - retry?( - taskId: string, - options?: ScaffolderRequestOptions, - ): Promise<{ - id: string; - }>; - scaffold( - request: ScaffolderScaffoldOptions, - options?: ScaffolderRequestOptions, - ): Promise; - // (undocumented) - streamLogs( - request: ScaffolderStreamLogsOptions, - options?: ScaffolderRequestOptions, - ): Observable; -} - -// @public -export class ScaffolderClient implements ScaffolderApi { - constructor(options: { - discoveryApi: { - getBaseUrl(pluginId: string): Promise; - }; - fetchApi: { - fetch: typeof fetch; - }; - identityApi?: { - getBackstageIdentity(): Promise<{ - type: 'user'; - userEntityRef: string; - ownershipEntityRefs: string[]; - }>; - }; - scmIntegrationsApi: ScmIntegrationRegistry; - useLongPollingLogs?: boolean; - }); - autocomplete({ - token, - resource, - provider, - context, - }: { - token: string; - provider: string; - resource: string; - context: Record; - }): Promise<{ - results: { - title?: string; - id: string; - }[]; - }>; - cancelTask( - taskId: string, - options?: ScaffolderRequestOptions, - ): Promise<{ - status?: ScaffolderTaskStatus; - }>; - // (undocumented) dryRun( request: ScaffolderDryRunOptions, options?: ScaffolderRequestOptions, @@ -206,7 +113,98 @@ export class ScaffolderClient implements ScaffolderApi { listTemplatingExtensions( options?: ScaffolderRequestOptions, ): Promise; - retry?( + retry( + taskId: string, + options?: ScaffolderRequestOptions, + ): Promise<{ + id: string; + }>; + scaffold( + request: ScaffolderScaffoldOptions, + options?: ScaffolderRequestOptions, + ): Promise; + // (undocumented) + streamLogs( + request: ScaffolderStreamLogsOptions, + options?: ScaffolderRequestOptions, + ): Observable; +} + +// @public +export class ScaffolderClient implements ScaffolderApi { + constructor(options: { + discoveryApi: { + getBaseUrl(pluginId: string): Promise; + }; + fetchApi: { + fetch: typeof fetch; + }; + identityApi?: { + getBackstageIdentity(): Promise<{ + type: 'user'; + userEntityRef: string; + ownershipEntityRefs: string[]; + }>; + }; + scmIntegrationsApi: ScmIntegrationRegistry; + useLongPollingLogs?: boolean; + }); + autocomplete( + input: { + token: string; + provider: string; + resource: string; + context: Record; + }, + options?: ScaffolderRequestOptions, + ): Promise<{ + results: { + title?: string; + id: string; + }[]; + }>; + cancelTask( + taskId: string, + options?: ScaffolderRequestOptions, + ): Promise<{ + status?: ScaffolderTaskStatus; + }>; + // (undocumented) + dryRun( + request: ScaffolderDryRunOptions, + options?: ScaffolderRequestOptions, + ): Promise; + // (undocumented) + getIntegrationsList( + options: ScaffolderGetIntegrationsListOptions, + ): Promise; + // (undocumented) + getTask( + taskId: string, + options?: ScaffolderRequestOptions, + ): Promise; + // (undocumented) + getTemplateParameterSchema( + templateRef: string, + options?: ScaffolderRequestOptions, + ): Promise; + listActions(options?: ScaffolderRequestOptions): Promise; + // (undocumented) + listTasks( + request: { + filterByOwnership: 'owned' | 'all'; + limit?: number; + offset?: number; + }, + options?: ScaffolderRequestOptions, + ): Promise<{ + tasks: ScaffolderTask[]; + totalTasks?: number; + }>; + listTemplatingExtensions( + options?: ScaffolderRequestOptions, + ): Promise; + retry( taskId: string, options?: ScaffolderRequestOptions, ): Promise<{ @@ -272,6 +270,7 @@ export interface ScaffolderGetIntegrationsListResponse { // @public (undocumented) export type ScaffolderOutputLink = { + if?: string | boolean; title?: string; icon?: string; url?: string; @@ -280,6 +279,7 @@ export type ScaffolderOutputLink = { // @public (undocumented) export type ScaffolderOutputText = { + if?: string | boolean; title?: string; icon?: string; content?: string; @@ -420,6 +420,9 @@ export interface TemplateEntityV1beta3 extends Entity { input?: JsonObject; }[]; parameters?: TemplateParametersV1beta3 | TemplateParametersV1beta3[]; + secrets?: { + schema?: JsonObject; + }; steps: Array; output?: { [name: string]: string; diff --git a/plugins/scaffolder-common/src/ScaffolderClient.ts b/plugins/scaffolder-common/src/ScaffolderClient.ts index d40f7a4637..3aaa7ebefc 100644 --- a/plugins/scaffolder-common/src/ScaffolderClient.ts +++ b/plugins/scaffolder-common/src/ScaffolderClient.ts @@ -125,13 +125,6 @@ export class ScaffolderClient implements ScaffolderApi { ): Promise { const integrations = [ ...this.scmIntegrationsApi.azure.list(), - ...this.scmIntegrationsApi.bitbucket - .list() - .filter( - item => - !this.scmIntegrationsApi.bitbucketCloud.byHost(item.config.host) && - !this.scmIntegrationsApi.bitbucketServer.byHost(item.config.host), - ), ...this.scmIntegrationsApi.bitbucketCloud.list(), ...this.scmIntegrationsApi.bitbucketServer.list(), ...this.scmIntegrationsApi.gerrit.list(), @@ -381,7 +374,7 @@ export class ScaffolderClient implements ScaffolderApi { /** * {@inheritdoc ScaffolderApi.retry} */ - async retry?( + async retry( taskId: string, options?: ScaffolderRequestOptions, ): Promise<{ id: string }> { @@ -393,22 +386,28 @@ export class ScaffolderClient implements ScaffolderApi { /** * {@inheritdoc ScaffolderApi.retry} */ - async autocomplete({ - token, - resource, - provider, - context, - }: { - token: string; - provider: string; - resource: string; - context: Record; - }): Promise<{ results: { title?: string; id: string }[] }> { + async autocomplete( + { + token, + resource, + provider, + context, + }: { + token: string; + provider: string; + resource: string; + context: Record; + }, + options?: ScaffolderRequestOptions, + ): Promise<{ results: { title?: string; id: string }[] }> { return await this.requestRequired( - await this.apiClient.autocomplete({ - path: { provider, resource }, - body: { token, context }, - }), + await this.apiClient.autocomplete( + { + path: { provider, resource }, + body: { token, context }, + }, + options, + ), ); } diff --git a/plugins/scaffolder-common/src/Template.v1beta3.schema.json b/plugins/scaffolder-common/src/Template.v1beta3.schema.json index a9f846068c..683115bf78 100644 --- a/plugins/scaffolder-common/src/Template.v1beta3.schema.json +++ b/plugins/scaffolder-common/src/Template.v1beta3.schema.json @@ -146,6 +146,16 @@ } ] }, + "secrets": { + "type": "object", + "description": "Configuration for secrets that are passed during task creation.", + "properties": { + "schema": { + "type": "object", + "description": "A JSONSchema for validating secrets passed during task creation." + } + } + }, "presentation": { "type": "object", "description": "A way to redefine the presentation of the scaffolder.", @@ -251,6 +261,10 @@ "type": "object", "required": [], "properties": { + "if": { + "type": ["string", "boolean"], + "description": "A templated condition that excludes this link when evaluated to false. If the condition is true or not defined, the link is included. The condition is true, if the input is not `false`, `undefined`, `null`, `\"\"`, `0`, or `[]`." + }, "url": { "type": "string", "description": "A url in a standard uri format.", @@ -285,6 +299,10 @@ "type": "object", "required": [], "properties": { + "if": { + "type": ["string", "boolean"], + "description": "A templated condition that excludes this text when evaluated to false. If the condition is true or not defined, the text is included. The condition is true, if the input is not `false`, `undefined`, `null`, `\"\"`, `0`, or `[]`." + }, "title": { "type": "string", "description": "A user friendly display name for the text.", diff --git a/plugins/scaffolder-common/src/TemplateEntityV1beta3.ts b/plugins/scaffolder-common/src/TemplateEntityV1beta3.ts index 1851b7e70f..d48049946c 100644 --- a/plugins/scaffolder-common/src/TemplateEntityV1beta3.ts +++ b/plugins/scaffolder-common/src/TemplateEntityV1beta3.ts @@ -67,6 +67,13 @@ export interface TemplateEntityV1beta3 extends Entity { * variables passed from the user into each action in the template. */ parameters?: TemplateParametersV1beta3 | TemplateParametersV1beta3[]; + /** + * Configuration for secrets that are passed during task creation. + * The schema field contains a JSONSchema used to validate secrets. + */ + secrets?: { + schema?: JsonObject; + }; /** * A list of steps to be executed in sequence which are defined by the template. These steps are a list of the underlying * javascript action and some optional input parameters that may or may not have been collected from the end user. diff --git a/plugins/scaffolder-common/src/alpha.ts b/plugins/scaffolder-common/src/alpha.ts index 82585fcf1d..3c7d6f1372 100644 --- a/plugins/scaffolder-common/src/alpha.ts +++ b/plugins/scaffolder-common/src/alpha.ts @@ -15,3 +15,4 @@ */ export * from './permissions'; +export { scaffolderCatalogModelLayer } from './catalogModel'; diff --git a/plugins/scaffolder-common/src/api.ts b/plugins/scaffolder-common/src/api.ts index 5425607d34..7a3fe5afbf 100644 --- a/plugins/scaffolder-common/src/api.ts +++ b/plugins/scaffolder-common/src/api.ts @@ -136,6 +136,7 @@ export type ListTemplatingExtensionsResponse = { /** @public */ export type ScaffolderOutputLink = { + if?: string | boolean; title?: string; icon?: string; url?: string; @@ -144,6 +145,7 @@ export type ScaffolderOutputLink = { /** @public */ export type ScaffolderOutputText = { + if?: string | boolean; title?: string; icon?: string; content?: string; @@ -285,12 +287,12 @@ export interface ScaffolderApi { * * @param taskId - the id of the task */ - retry?( + retry( taskId: string, options?: ScaffolderRequestOptions, ): Promise<{ id: string }>; - listTasks?( + listTasks( request: { filterByOwnership: 'owned' | 'all'; limit?: number; @@ -311,7 +313,7 @@ export interface ScaffolderApi { /** * Returns a structure describing the available templating extensions. */ - listTemplatingExtensions?( + listTemplatingExtensions( options?: ScaffolderRequestOptions, ): Promise; @@ -320,12 +322,12 @@ export interface ScaffolderApi { options?: ScaffolderRequestOptions, ): Observable; - dryRun?( + dryRun( request: ScaffolderDryRunOptions, options?: ScaffolderRequestOptions, ): Promise; - autocomplete?( + autocomplete( request: { token: string; provider: string; diff --git a/plugins/scaffolder-common/src/catalogModel.ts b/plugins/scaffolder-common/src/catalogModel.ts new file mode 100644 index 0000000000..d360e857cb --- /dev/null +++ b/plugins/scaffolder-common/src/catalogModel.ts @@ -0,0 +1,57 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createCatalogModelLayer } from '@backstage/catalog-model/alpha'; +import type { JsonObject } from '@backstage/types'; +import jsonSchema from './Template.v1beta3.schema.json'; + +/** + * Extends the catalog model with the Template kind. + * + * @alpha + */ +export const scaffolderCatalogModelLayer = createCatalogModelLayer({ + layerId: 'scaffolder.backstage.io/kind-template', + builder: model => { + model.addKind({ + group: 'scaffolder.backstage.io', + names: { + kind: 'Template', + singular: 'template', + plural: 'templates', + }, + description: 'A template for scaffolding a new component', + versions: [ + { + name: 'v1beta3', + relationFields: [ + { + selector: { path: 'spec.owner' }, + relation: 'ownedBy', + defaultKind: 'Group', + // TODO: This was inherit since before, but should ownership in general be default instead? + defaultNamespace: 'inherit', + allowedKinds: ['Group', 'User'], + }, + ], + schema: { + jsonSchema: jsonSchema as JsonObject, + }, + }, + ], + }); + }, +}); diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/apis/Api.client.ts b/plugins/scaffolder-common/src/schema/openapi/generated/apis/Api.client.ts index ca5ae10b8f..e87c838d1a 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/apis/Api.client.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/apis/Api.client.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ // ****************************************************************** // * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * // ****************************************************************** + import { DiscoveryApi } from '../types/discovery'; import { FetchApi } from '../types/fetch'; import crossFetch from 'cross-fetch'; diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/apis/index.ts b/plugins/scaffolder-common/src/schema/openapi/generated/apis/index.ts index fc7c83b736..c2e931caf8 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/apis/index.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/apis/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/index.ts b/plugins/scaffolder-common/src/schema/openapi/generated/index.ts index dc3055033d..19501a5dcb 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/index.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/Action.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/Action.model.ts index f9c028f3e3..a29e2f704d 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/Action.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/Action.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/ActionExample.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/ActionExample.model.ts index 50550e2883..c7a5cf5075 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/ActionExample.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/ActionExample.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/ActionSchema.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/ActionSchema.model.ts index 35d2014181..e8cc85c647 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/ActionSchema.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/ActionSchema.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/Autocomplete200Response.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/Autocomplete200Response.model.ts index e3eec0aa20..eb59d6a061 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/Autocomplete200Response.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/Autocomplete200Response.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/Autocomplete200ResponseResultsInner.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/Autocomplete200ResponseResultsInner.model.ts index 00ccb41b0f..fe329488d5 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/Autocomplete200ResponseResultsInner.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/Autocomplete200ResponseResultsInner.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/Autocomplete400Response.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/Autocomplete400Response.model.ts index 1456babb96..3dae0fd0f1 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/Autocomplete400Response.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/Autocomplete400Response.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/AutocompleteRequest.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/AutocompleteRequest.model.ts index 769a929918..197582c944 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/AutocompleteRequest.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/AutocompleteRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/CancelTask200Response.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/CancelTask200Response.model.ts index f29c509d41..b23fafc892 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/CancelTask200Response.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/CancelTask200Response.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRun200Response.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRun200Response.model.ts index 62e3689d5d..d0c1450785 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRun200Response.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRun200Response.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRun200ResponseAllOfDirectoryContentsInner.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRun200ResponseAllOfDirectoryContentsInner.model.ts index 2646ca49f9..ced04f2ae6 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRun200ResponseAllOfDirectoryContentsInner.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRun200ResponseAllOfDirectoryContentsInner.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRun200ResponseAllOfStepsInner.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRun200ResponseAllOfStepsInner.model.ts index 2a462edefa..9818ca581e 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRun200ResponseAllOfStepsInner.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRun200ResponseAllOfStepsInner.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,6 @@ */ export interface DryRun200ResponseAllOfStepsInner { [key: string]: any; - id: string; name: string; action: string; diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunRequest.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunRequest.model.ts index 212b36c26b..254605a6b4 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunRequest.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunRequestDirectoryContentsInner.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunRequestDirectoryContentsInner.model.ts index ddcae4fd9d..f01e2d18c8 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunRequestDirectoryContentsInner.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunRequestDirectoryContentsInner.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunResult.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunResult.model.ts index 6e2ad23ef4..cfdcf163bd 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunResult.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunResult.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunResultLogInner.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunResultLogInner.model.ts index 4c6b786d82..496650d8ba 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunResultLogInner.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunResultLogInner.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunResultLogInnerBody.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunResultLogInnerBody.model.ts index e3b19be045..a56e7b1107 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunResultLogInnerBody.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunResultLogInnerBody.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/ErrorError.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/ErrorError.model.ts index e0265e95d7..853f88cc14 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/ErrorError.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/ErrorError.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/ErrorRequest.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/ErrorRequest.model.ts index 3eb5e15740..1591911bc9 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/ErrorRequest.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/ErrorRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/ErrorResponse.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/ErrorResponse.model.ts index edbcc32df7..1eff0557ed 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/ErrorResponse.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/ErrorResponse.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/JsonPrimitive.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/JsonPrimitive.model.ts index 6aa6394c14..0142269c5b 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/JsonPrimitive.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/JsonPrimitive.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,4 +22,4 @@ * A type representing all allowed JSON primitive values. * @public */ -export type JsonPrimitive = any | boolean | number | string; +export type JsonPrimitive = boolean | number | string; diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/JsonValue.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/JsonValue.model.ts index 9fe2ac9de3..a706c3c93e 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/JsonValue.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/JsonValue.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/ListTasksResponse.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/ListTasksResponse.model.ts index 9347c766a9..0f641660fe 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/ListTasksResponse.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/ListTasksResponse.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/ListTemplatingExtensionsResponse.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/ListTemplatingExtensionsResponse.model.ts index 74b7cd3780..6c65af534c 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/ListTemplatingExtensionsResponse.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/ListTemplatingExtensionsResponse.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/ListTemplatingExtensionsResponseGlobals.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/ListTemplatingExtensionsResponseGlobals.model.ts index 1f022a0675..b827a9f083 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/ListTemplatingExtensionsResponseGlobals.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/ListTemplatingExtensionsResponseGlobals.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/ModelError.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/ModelError.model.ts index 958fde7d0b..9d5c36325e 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/ModelError.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/ModelError.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,6 @@ import { ErrorResponse } from '../models/ErrorResponse.model'; */ export interface ModelError { [key: string]: any; - error: ErrorError; request?: ErrorRequest; response: ErrorResponse; diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/RetryRequest.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/RetryRequest.model.ts index 858fa67566..b25d0a2ed5 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/RetryRequest.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/RetryRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/Scaffold201Response.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/Scaffold201Response.model.ts index 1271642e81..b69aa7b00c 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/Scaffold201Response.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/Scaffold201Response.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/Scaffold400Response.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/Scaffold400Response.model.ts index e3cb7eaa45..d42caec5a4 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/Scaffold400Response.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/Scaffold400Response.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/ScaffolderScaffoldOptions.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/ScaffolderScaffoldOptions.model.ts index 93f4ae71c0..dbcc372253 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/ScaffolderScaffoldOptions.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/ScaffolderScaffoldOptions.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/ScaffolderUsageExample.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/ScaffolderUsageExample.model.ts index 3e0f4ef866..7e664963eb 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/ScaffolderUsageExample.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/ScaffolderUsageExample.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/SerializedFile.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/SerializedFile.model.ts index 65a3c714a7..74ee62f05a 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/SerializedFile.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/SerializedFile.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/SerializedTask.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/SerializedTask.model.ts index 67cbf14279..779efcab87 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/SerializedTask.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/SerializedTask.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/SerializedTaskEvent.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/SerializedTaskEvent.model.ts index 6e4ddaa296..da412a5839 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/SerializedTaskEvent.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/SerializedTaskEvent.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/TaskEventType.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/TaskEventType.model.ts index d32d77a448..daa28d1d62 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/TaskEventType.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/TaskEventType.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/TaskSecrets.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/TaskSecrets.model.ts index c78c39c1ec..0cbaa38a55 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/TaskSecrets.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/TaskSecrets.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/TaskStatus.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/TaskStatus.model.ts index 1f209a3656..75df8d3679 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/TaskStatus.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/TaskStatus.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateFilter.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateFilter.model.ts index 7c631978e6..3339c61a77 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateFilter.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateFilter.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateFilterSchema.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateFilterSchema.model.ts index 5a95a58ead..930a9760d9 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateFilterSchema.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateFilterSchema.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateGlobalFunction.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateGlobalFunction.model.ts index ee19ce723b..7567b3112d 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateGlobalFunction.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateGlobalFunction.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateGlobalFunctionSchema.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateGlobalFunctionSchema.model.ts index b3a39c3b24..731cf53496 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateGlobalFunctionSchema.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateGlobalFunctionSchema.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateGlobalValue.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateGlobalValue.model.ts index 95d177a05a..d85fe0c395 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateGlobalValue.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateGlobalValue.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateParameterSchema.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateParameterSchema.model.ts index 8638ee8114..dd71410c86 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateParameterSchema.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateParameterSchema.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,6 @@ import { TemplateParameterSchemaStepsInner } from '../models/TemplateParameterSc */ export interface TemplateParameterSchema { [key: string]: any; - title: string; description?: string; steps: Array; diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateParameterSchemaStepsInner.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateParameterSchemaStepsInner.model.ts index 2f7892fd41..ef94e58643 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateParameterSchemaStepsInner.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateParameterSchemaStepsInner.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/ValidationError.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/ValidationError.model.ts index 7e762f616c..43c45c8351 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/ValidationError.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/ValidationError.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,6 @@ import { ValidationErrorPathInner } from '../models/ValidationErrorPathInner.mod */ export interface ValidationError { [key: string]: any; - path: Array; property: string; message: string; diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/ValidationErrorArgument.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/ValidationErrorArgument.model.ts index 85c9576fd0..123b2a0d76 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/ValidationErrorArgument.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/ValidationErrorArgument.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/ValidationErrorPathInner.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/ValidationErrorPathInner.model.ts index 4535c735db..53d3b9d2e7 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/ValidationErrorPathInner.model.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/ValidationErrorPathInner.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/index.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/index.ts index 1bc447fa3d..3d5ceab140 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/models/index.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,6 @@ export * from '../models/Autocomplete400Response.model'; export * from '../models/AutocompleteRequest.model'; export * from '../models/CancelTask200Response.model'; export * from '../models/DryRun200Response.model'; -export * from '../models/DryRun200ResponseAllOf.model'; export * from '../models/DryRun200ResponseAllOfDirectoryContentsInner.model'; export * from '../models/DryRun200ResponseAllOfStepsInner.model'; export * from '../models/DryRunRequest.model'; @@ -31,7 +30,6 @@ export * from '../models/DryRunRequestDirectoryContentsInner.model'; export * from '../models/DryRunResult.model'; export * from '../models/DryRunResultLogInner.model'; export * from '../models/DryRunResultLogInnerBody.model'; -export * from '../models/DryRunResultLogInnerBodyAllOf.model'; export * from '../models/ErrorError.model'; export * from '../models/ErrorRequest.model'; export * from '../models/ErrorResponse.model'; @@ -51,7 +49,6 @@ export * from '../models/SerializedTask.model'; export * from '../models/SerializedTaskEvent.model'; export * from '../models/TaskEventType.model'; export * from '../models/TaskSecrets.model'; -export * from '../models/TaskSecretsAllOf.model'; export * from '../models/TaskStatus.model'; export * from '../models/TemplateFilter.model'; export * from '../models/TemplateFilterSchema.model'; diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/pluginId.ts b/plugins/scaffolder-common/src/schema/openapi/generated/pluginId.ts index b898028be5..fcfaf1d7c0 100644 --- a/plugins/scaffolder-common/src/schema/openapi/generated/pluginId.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/pluginId.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-common/src/schema/openapi/index.ts b/plugins/scaffolder-common/src/schema/openapi/index.ts index 196aad553a..fb49a2b9c0 100644 --- a/plugins/scaffolder-common/src/schema/openapi/index.ts +++ b/plugins/scaffolder-common/src/schema/openapi/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/scaffolder-node-test-utils/CHANGELOG.md b/plugins/scaffolder-node-test-utils/CHANGELOG.md index b51b201727..1a3633c666 100644 --- a/plugins/scaffolder-node-test-utils/CHANGELOG.md +++ b/plugins/scaffolder-node-test-utils/CHANGELOG.md @@ -1,5 +1,168 @@ # @backstage/plugin-scaffolder-node-test-utils +## 0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/backend-test-utils@1.11.2 + - @backstage/plugin-scaffolder-node@0.13.2 + +## 0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-test-utils@1.11.2-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + +## 0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/backend-test-utils@1.11.2-next.0 + - @backstage/plugin-scaffolder-node@0.13.1-next.0 + - @backstage/types@1.2.2 + +## 0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/backend-test-utils@1.11.1 + - @backstage/plugin-scaffolder-node@0.13.0 + +## 0.3.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-test-utils@1.11.1-next.2 + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.2 + +## 0.3.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-test-utils@1.11.1-next.1 + - @backstage/plugin-scaffolder-node@0.13.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/types@1.2.2 + +## 0.3.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/backend-test-utils@1.11.1-next.0 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.6-next.0 + +## 0.3.8 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/backend-test-utils@1.11.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-scaffolder-node@0.12.5 + +## 0.3.8-next.1 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/backend-test-utils@1.11.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.5-next.1 + +## 0.3.8-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-test-utils@1.10.4-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.12.4-next.0 + - @backstage/types@1.2.2 + +## 0.3.7 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-scaffolder-node@0.12.3 + - @backstage/backend-test-utils@1.10.3 + +## 0.3.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.12.3-next.0 + +## 0.3.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/backend-test-utils@1.10.3-next.0 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.3.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-test-utils@1.10.2 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.3.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-test-utils@1.10.2-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## 0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/backend-test-utils@1.10.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/types@1.2.2 + +## 0.3.5 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-test-utils@1.10.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-scaffolder-node@0.12.1 + ## 0.3.5-next.1 ### Patch Changes diff --git a/plugins/scaffolder-node-test-utils/knip-report.md b/plugins/scaffolder-node-test-utils/knip-report.md index 9a0eed890a..928c3ccdb2 100644 --- a/plugins/scaffolder-node-test-utils/knip-report.md +++ b/plugins/scaffolder-node-test-utils/knip-report.md @@ -2,9 +2,9 @@ ## Unused devDependencies (3) -| Name | Location | Severity | -| :--------------- | :----------- | :------- | -| react-router-dom | plugins/scaffolder-node-test-utils/package.json | error | -| react-dom | plugins/scaffolder-node-test-utils/package.json | error | -| react | plugins/scaffolder-node-test-utils/package.json | error | +| Name | Location | Severity | +| :--------------- | :---------------- | :------- | +| react-router-dom | package.json:55:6 | error | +| react-dom | package.json:54:6 | error | +| react | package.json:53:6 | error | diff --git a/plugins/scaffolder-node-test-utils/package.json b/plugins/scaffolder-node-test-utils/package.json index 374b19f914..cd95c467b7 100644 --- a/plugins/scaffolder-node-test-utils/package.json +++ b/plugins/scaffolder-node-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-node-test-utils", - "version": "0.3.5-next.1", + "version": "0.3.10", "backstage": { "role": "node-library", "pluginId": "scaffolder", @@ -52,13 +52,13 @@ "@types/react": "^18.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts b/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts index 6d2fa4f4d5..5f8d71a9d0 100644 --- a/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts +++ b/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { PassThrough } from 'stream'; +import { PassThrough } from 'node:stream'; import { createMockDirectory, mockCredentials, diff --git a/plugins/scaffolder-node/CHANGELOG.md b/plugins/scaffolder-node/CHANGELOG.md index 144a7a6cfb..e300a6c6e9 100644 --- a/plugins/scaffolder-node/CHANGELOG.md +++ b/plugins/scaffolder-node/CHANGELOG.md @@ -1,5 +1,224 @@ # @backstage/plugin-scaffolder-node +## 0.13.2 + +### Patch Changes + +- 5af48e7: Added `PermissionResourceRef` definitions for scaffolder resource types: `scaffolderTemplatePermissionResourceRef`, `scaffolderActionPermissionResourceRef`, and `scaffolderTaskPermissionResourceRef`. These are exported from `@backstage/plugin-scaffolder-node/alpha`. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/backend-test-utils@1.11.2 + - @backstage/errors@1.3.0 + - @backstage/catalog-model@1.8.0 + - @backstage/plugin-scaffolder-common@2.1.0 + - @backstage/integration@2.0.1 + - @backstage/plugin-permission-node@0.10.12 + - @backstage/plugin-permission-common@0.9.8 + +## 0.13.2-next.2 + +### Patch Changes + +- 5af48e7: Added `PermissionResourceRef` definitions for scaffolder resource types: `scaffolderTemplatePermissionResourceRef`, `scaffolderActionPermissionResourceRef`, and `scaffolderTaskPermissionResourceRef`. These are exported from `@backstage/plugin-scaffolder-node/alpha`. +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/integration@2.0.1-next.0 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/backend-test-utils@1.11.2-next.2 + - @backstage/catalog-model@1.7.8-next.0 + - @backstage/plugin-permission-common@0.9.8-next.0 + - @backstage/plugin-permission-node@0.10.12-next.2 + - @backstage/plugin-scaffolder-common@2.0.1-next.0 + +## 0.13.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-test-utils@1.11.2-next.1 + +## 0.13.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/backend-test-utils@1.11.2-next.0 + - @backstage/catalog-model@1.7.7 + - @backstage/errors@1.2.7 + - @backstage/integration@2.0.0 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/plugin-scaffolder-common@2.0.0 + +## 0.13.0 + +### Minor Changes + +- e27bd4e: **BREAKING** Removed deprecated `bitbucket` integration from being used in the `parseRepoUrl` function. It will use the `bitbucketCloud` or `bitbucketServer` integrations instead. + +### Patch Changes + +- cd0ecc5: Added `removeFiles` helper function for staging file removals in Git. +- f598909: Added `scaffolderServiceRef` and `ScaffolderService` interface for backend plugins that need to interact with the scaffolder API using `BackstageCredentials` instead of raw tokens. +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/backend-test-utils@1.11.1 + - @backstage/integration@2.0.0 + - @backstage/plugin-scaffolder-common@2.0.0 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/catalog-model@1.7.7 + +## 0.13.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-test-utils@1.11.1-next.2 + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-scaffolder-common@2.0.0-next.2 + +## 0.13.0-next.1 + +### Minor Changes + +- e27bd4e: **BREAKING** Removed deprecated `bitbucket` integration from being used in the `parseRepoUrl` function. It will use the `bitbucketCloud` or `bitbucketServer` integrations instead. + +### Patch Changes + +- f598909: Added `scaffolderServiceRef` and `ScaffolderService` interface for backend plugins that need to interact with the scaffolder API using `BackstageCredentials` instead of raw tokens. +- Updated dependencies + - @backstage/backend-test-utils@1.11.1-next.1 + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-scaffolder-common@2.0.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.6 + +## 0.12.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-scaffolder-common@1.7.7-next.0 + +## 0.12.5 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 4fc7bf0: Bump to tar v7 +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-scaffolder-common@1.7.6 + +## 0.12.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-scaffolder-common@1.7.6-next.1 + +## 0.12.4-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 4fc7bf0: Bump to tar v7 +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-common@1.7.6-next.0 + +## 0.12.3 + +### Patch Changes + +- c641c14: Wrap some of the action logic with `resolveSafeChildPath` and improve symlink handling when fetching remote and local files +- 27f9061: REwrite] +- 872eb91: Upgrade `zod-to-json-schema` to latest version +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/integration@1.19.2 + - @backstage/plugin-permission-common@0.9.4 + - @backstage/plugin-scaffolder-common@1.7.5 + +## 0.12.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + - @backstage/plugin-scaffolder-common@1.7.5-next.0 + +## 0.12.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-common@1.7.4 + +## 0.12.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + +## 0.12.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + +## 0.12.1 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-scaffolder-common@1.7.3 + ## 0.12.1-next.1 ### Patch Changes diff --git a/plugins/scaffolder-node/package.json b/plugins/scaffolder-node/package.json index 9eba07b978..1df2a16e19 100644 --- a/plugins/scaffolder-node/package.json +++ b/plugins/scaffolder-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-node", - "version": "0.12.1-next.1", + "version": "0.13.2", "description": "The plugin-scaffolder-node module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "node-library", @@ -27,6 +27,7 @@ "exports": { ".": "./src/index.ts", "./alpha": "./src/alpha/index.ts", + "./testUtils": "./src/testUtils.ts", "./package.json": "./package.json" }, "main": "src/index.ts", @@ -36,6 +37,9 @@ "alpha": [ "src/alpha/index.ts" ], + "testUtils": [ + "src/testUtils.ts" + ], "package.json": [ "package.json" ] @@ -59,6 +63,7 @@ "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", + "@backstage/plugin-permission-node": "workspace:^", "@backstage/plugin-scaffolder-common": "workspace:^", "@backstage/types": "workspace:^", "@isomorphic-git/pgp-plugin": "^0.0.7", @@ -69,16 +74,25 @@ "jsonschema": "^1.5.0", "lodash": "^4.17.21", "p-limit": "^3.1.0", - "tar": "^6.1.12", + "tar": "^7.5.6", "winston": "^3.2.1", "winston-transport": "^4.7.0", - "zod": "^3.22.4", - "zod-to-json-schema": "^3.20.4" + "zod": "^3.25.76 || ^4.0.0", + "zod-to-json-schema": "^3.25.1" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/config": "workspace:^", - "@types/lodash": "^4.14.151" + "@types/lodash": "^4.14.151", + "msw": "^1.0.0" + }, + "peerDependencies": { + "@backstage/backend-test-utils": "workspace:^" + }, + "peerDependenciesMeta": { + "@backstage/backend-test-utils": { + "optional": true + } } } diff --git a/plugins/scaffolder-node/report-alpha.api.md b/plugins/scaffolder-node/report-alpha.api.md index e1d49f571a..56372dcf2a 100644 --- a/plugins/scaffolder-node/report-alpha.api.md +++ b/plugins/scaffolder-node/report-alpha.api.md @@ -4,18 +4,20 @@ ```ts import { ExtensionPoint } from '@backstage/backend-plugin-api'; +import type { JsonObject } from '@backstage/types'; import { JsonValue } from '@backstage/types'; +import { PermissionResourceRef } from '@backstage/plugin-permission-node'; +import type { SerializedTask } from '@backstage/plugin-scaffolder-node'; import { TaskBroker } from '@backstage/plugin-scaffolder-node'; +import type { TaskFilter } from '@backstage/plugin-scaffolder-node'; +import type { TemplateEntityStepV1beta3 } from '@backstage/plugin-scaffolder-common'; import { TemplateFilter as TemplateFilter_2 } from '@backstage/plugin-scaffolder-node'; import { TemplateGlobal as TemplateGlobal_2 } from '@backstage/plugin-scaffolder-node'; -import { z } from 'zod'; +import type { TemplateParametersV1beta3 } from '@backstage/plugin-scaffolder-common'; +import { z } from 'zod/v3'; // @alpha -export type AutocompleteHandler = ({ - resource, - token, - context, -}: { +export type AutocompleteHandler = (input: { resource: string; token: string; context: Record; @@ -122,13 +124,18 @@ export const restoreWorkspace: (opts: { buffer?: Buffer; }) => Promise; +// @alpha +export const scaffolderActionPermissionResourceRef: PermissionResourceRef< + JsonObject, + {}, + 'scaffolder-action', + 'scaffolder' +>; + // @alpha export interface ScaffolderAutocompleteExtensionPoint { // (undocumented) - addAutocompleteProvider({ - id, - handler, - }: { + addAutocompleteProvider(input: { id: string; handler: AutocompleteHandler; }): void; @@ -146,6 +153,22 @@ export interface ScaffolderTaskBrokerExtensionPoint { // @alpha @deprecated export const scaffolderTaskBrokerExtensionPoint: ExtensionPoint; +// @alpha +export const scaffolderTaskPermissionResourceRef: PermissionResourceRef< + SerializedTask, + TaskFilter, + 'scaffolder-task', + 'scaffolder' +>; + +// @alpha +export const scaffolderTemplatePermissionResourceRef: PermissionResourceRef< + TemplateParametersV1beta3 | TemplateEntityStepV1beta3, + {}, + 'scaffolder-template', + 'scaffolder' +>; + // @alpha export interface ScaffolderTemplatingExtensionPoint { // (undocumented) @@ -217,13 +240,7 @@ export interface WorkspaceProvider { targetPath: string; }): Promise; // (undocumented) - serializeWorkspace({ - path, - taskId, - }: { - path: string; - taskId: string; - }): Promise; + serializeWorkspace(input: { path: string; taskId: string }): Promise; } // @alpha (undocumented) diff --git a/plugins/scaffolder-node/report-testUtils.api.md b/plugins/scaffolder-node/report-testUtils.api.md new file mode 100644 index 0000000000..e4f9c55ee2 --- /dev/null +++ b/plugins/scaffolder-node/report-testUtils.api.md @@ -0,0 +1,122 @@ +## API Report File for "@backstage/plugin-scaffolder-node" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { BackstageCredentials } from '@backstage/backend-plugin-api'; +import { ListActionsResponse } from '@backstage/plugin-scaffolder-common'; +import { ListTemplatingExtensionsResponse } from '@backstage/plugin-scaffolder-common'; +import { LogEvent } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderDryRunOptions } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderDryRunResponse } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderScaffoldOptions } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderScaffoldResponse } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderTask } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderTaskStatus } from '@backstage/plugin-scaffolder-common'; +import { ServiceMock } from '@backstage/backend-test-utils'; +import type { TemplateParameterSchema } from '@backstage/plugin-scaffolder-common'; + +// @public +export interface ScaffolderService { + // (undocumented) + autocomplete( + request: { + token: string; + provider: string; + resource: string; + context: Record; + }, + options: ScaffolderServiceRequestOptions, + ): Promise<{ + results: { + title?: string; + id: string; + }[]; + }>; + // (undocumented) + cancelTask( + request: { + taskId: string; + }, + options: ScaffolderServiceRequestOptions, + ): Promise<{ + status?: ScaffolderTaskStatus; + }>; + // (undocumented) + dryRun( + request: ScaffolderDryRunOptions, + options: ScaffolderServiceRequestOptions, + ): Promise; + // (undocumented) + getLogs( + request: { + taskId: string; + after?: number; + }, + options: ScaffolderServiceRequestOptions, + ): Promise; + // (undocumented) + getTask( + request: { + taskId: string; + }, + options: ScaffolderServiceRequestOptions, + ): Promise; + // (undocumented) + getTemplateParameterSchema( + request: { + templateRef: string; + }, + options: ScaffolderServiceRequestOptions, + ): Promise; + // (undocumented) + listActions( + request?: {}, + options?: ScaffolderServiceRequestOptions, + ): Promise; + // (undocumented) + listTasks( + request: { + createdBy?: string; + limit?: number; + offset?: number; + }, + options: ScaffolderServiceRequestOptions, + ): Promise<{ + items: ScaffolderTask[]; + totalItems: number; + }>; + // (undocumented) + listTemplatingExtensions( + request?: {}, + options?: ScaffolderServiceRequestOptions, + ): Promise; + // (undocumented) + retry( + request: { + taskId: string; + }, + options: ScaffolderServiceRequestOptions, + ): Promise<{ + id: string; + }>; + // (undocumented) + scaffold( + request: ScaffolderScaffoldOptions, + options: ScaffolderServiceRequestOptions, + ): Promise; +} + +// @public +export namespace scaffolderServiceMock { + const mock: ( + partialImpl?: Partial | undefined, + ) => ServiceMock; +} + +// @public (undocumented) +export interface ScaffolderServiceRequestOptions { + // (undocumented) + credentials: BackstageCredentials; +} +``` diff --git a/plugins/scaffolder-node/report.api.md b/plugins/scaffolder-node/report.api.md index 7f70c9d786..538f19cd1e 100644 --- a/plugins/scaffolder-node/report.api.md +++ b/plugins/scaffolder-node/report.api.md @@ -9,20 +9,31 @@ import { Expand } from '@backstage/types'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; import { JsonObject } from '@backstage/types'; import { JsonValue } from '@backstage/types'; +import { ListActionsResponse } from '@backstage/plugin-scaffolder-common'; +import { ListTemplatingExtensionsResponse } from '@backstage/plugin-scaffolder-common'; +import { LogEvent } from '@backstage/plugin-scaffolder-common'; import { LoggerService } from '@backstage/backend-plugin-api'; import { Observable } from '@backstage/types'; import { PermissionCriteria } from '@backstage/plugin-permission-common'; +import { ScaffolderDryRunOptions } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderDryRunResponse } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderScaffoldOptions } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderScaffoldResponse } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderTask } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderTaskStatus } from '@backstage/plugin-scaffolder-common'; import { Schema } from 'jsonschema'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { ScmIntegrations } from '@backstage/integration'; -import { SpawnOptionsWithoutStdio } from 'child_process'; +import { ServiceRef } from '@backstage/backend-plugin-api'; +import { SpawnOptionsWithoutStdio } from 'node:child_process'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { TemplateInfo } from '@backstage/plugin-scaffolder-common'; +import type { TemplateParameterSchema } from '@backstage/plugin-scaffolder-common'; import { UpdateTaskCheckpointOptions } from '@backstage/plugin-scaffolder-node/alpha'; import { UrlReaderService } from '@backstage/backend-plugin-api'; import { UserEntity } from '@backstage/catalog-model'; -import { Writable } from 'stream'; -import { z } from 'zod'; +import { Writable } from 'node:stream'; +import { z } from 'zod/v3'; // @public export type ActionContext< @@ -307,6 +318,21 @@ export const parseRepoUrl: ( project?: string; }; +// @public (undocumented) +export function removeFiles(options: { + dir: string; + filepath: string; + auth: + | { + username: string; + password: string; + } + | { + token: string; + }; + logger?: LoggerService | undefined; +}): Promise; + // @public export interface ScaffolderActionsExtensionPoint { // (undocumented) @@ -316,6 +342,110 @@ export interface ScaffolderActionsExtensionPoint { // @public export const scaffolderActionsExtensionPoint: ExtensionPoint; +// @public +export interface ScaffolderService { + // (undocumented) + autocomplete( + request: { + token: string; + provider: string; + resource: string; + context: Record; + }, + options: ScaffolderServiceRequestOptions, + ): Promise<{ + results: { + title?: string; + id: string; + }[]; + }>; + // (undocumented) + cancelTask( + request: { + taskId: string; + }, + options: ScaffolderServiceRequestOptions, + ): Promise<{ + status?: ScaffolderTaskStatus; + }>; + // (undocumented) + dryRun( + request: ScaffolderDryRunOptions, + options: ScaffolderServiceRequestOptions, + ): Promise; + // (undocumented) + getLogs( + request: { + taskId: string; + after?: number; + }, + options: ScaffolderServiceRequestOptions, + ): Promise; + // (undocumented) + getTask( + request: { + taskId: string; + }, + options: ScaffolderServiceRequestOptions, + ): Promise; + // (undocumented) + getTemplateParameterSchema( + request: { + templateRef: string; + }, + options: ScaffolderServiceRequestOptions, + ): Promise; + // (undocumented) + listActions( + request?: {}, + options?: ScaffolderServiceRequestOptions, + ): Promise; + // (undocumented) + listTasks( + request: { + createdBy?: string; + limit?: number; + offset?: number; + }, + options: ScaffolderServiceRequestOptions, + ): Promise<{ + items: ScaffolderTask[]; + totalItems: number; + }>; + // (undocumented) + listTemplatingExtensions( + request?: {}, + options?: ScaffolderServiceRequestOptions, + ): Promise; + // (undocumented) + retry( + request: { + taskId: string; + }, + options: ScaffolderServiceRequestOptions, + ): Promise<{ + id: string; + }>; + // (undocumented) + scaffold( + request: ScaffolderScaffoldOptions, + options: ScaffolderServiceRequestOptions, + ): Promise; +} + +// @public +export const scaffolderServiceRef: ServiceRef< + ScaffolderService, + 'plugin', + 'singleton' +>; + +// @public (undocumented) +export interface ScaffolderServiceRequestOptions { + // (undocumented) + credentials: BackstageCredentials; +} + // @public (undocumented) export interface SerializedFile { // (undocumented) diff --git a/plugins/scaffolder-node/src/actions/createTemplateAction.ts b/plugins/scaffolder-node/src/actions/createTemplateAction.ts index edf3e203b9..a3dfe3f469 100644 --- a/plugins/scaffolder-node/src/actions/createTemplateAction.ts +++ b/plugins/scaffolder-node/src/actions/createTemplateAction.ts @@ -15,7 +15,7 @@ */ import { ActionContext, TemplateAction } from './types'; -import { z } from 'zod'; +import { z } from 'zod/v3'; import { Expand, JsonObject } from '@backstage/types'; import { parseSchemas } from './util'; diff --git a/plugins/scaffolder-node/src/actions/executeShellCommand.test.ts b/plugins/scaffolder-node/src/actions/executeShellCommand.test.ts index 67951f915c..ca8ea0a37a 100644 --- a/plugins/scaffolder-node/src/actions/executeShellCommand.test.ts +++ b/plugins/scaffolder-node/src/actions/executeShellCommand.test.ts @@ -14,8 +14,8 @@ * limitations under the License. */ import { LoggerService } from '@backstage/backend-plugin-api'; -import { spawn } from 'child_process'; -import { PassThrough, Writable } from 'stream'; +import { spawn } from 'node:child_process'; +import { PassThrough, Writable } from 'node:stream'; import { executeShellCommand } from './executeShellCommand'; jest.mock('child_process', () => ({ diff --git a/plugins/scaffolder-node/src/actions/executeShellCommand.ts b/plugins/scaffolder-node/src/actions/executeShellCommand.ts index 1d45d74327..454e66bcad 100644 --- a/plugins/scaffolder-node/src/actions/executeShellCommand.ts +++ b/plugins/scaffolder-node/src/actions/executeShellCommand.ts @@ -15,8 +15,8 @@ */ import { LoggerService } from '@backstage/backend-plugin-api'; -import { spawn, SpawnOptionsWithoutStdio } from 'child_process'; -import { PassThrough, Writable } from 'stream'; +import { spawn, SpawnOptionsWithoutStdio } from 'node:child_process'; +import { PassThrough, Writable } from 'node:stream'; /** * Options for {@link executeShellCommand}. diff --git a/plugins/scaffolder-node/src/actions/fetch.test.ts b/plugins/scaffolder-node/src/actions/fetch.test.ts index f831d20f35..cc65ba2cfd 100644 --- a/plugins/scaffolder-node/src/actions/fetch.test.ts +++ b/plugins/scaffolder-node/src/actions/fetch.test.ts @@ -17,12 +17,12 @@ jest.mock('fs-extra'); import fs from 'fs-extra'; -import { resolve as resolvePath } from 'path'; +import { resolve as resolvePath } from 'node:path'; import { UrlReaderService } from '@backstage/backend-plugin-api'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import { fetchContents, fetchFile } from './fetch'; -import os from 'os'; +import os from 'node:os'; describe('fetchContents helper', () => { beforeEach(() => { @@ -86,6 +86,7 @@ describe('fetchContents helper', () => { expect(fs.copy).toHaveBeenCalledWith( resolvePath('/some/foo'), 'somepath', + expect.objectContaining({ filter: expect.any(Function) }), ); }); diff --git a/plugins/scaffolder-node/src/actions/fetch.ts b/plugins/scaffolder-node/src/actions/fetch.ts index a0e8dd9f85..505d6fab38 100644 --- a/plugins/scaffolder-node/src/actions/fetch.ts +++ b/plugins/scaffolder-node/src/actions/fetch.ts @@ -15,11 +15,14 @@ */ import { UrlReaderService } from '@backstage/backend-plugin-api'; -import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; +import { + isChildPath, + resolveSafeChildPath, +} from '@backstage/backend-plugin-api'; import { InputError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; import fs from 'fs-extra'; -import path from 'path'; +import path from 'node:path'; /** * A helper function that reads the contents of a directory from the given URL. @@ -50,7 +53,9 @@ export async function fetchContents(options: { if (!fetchUrlIsAbsolute && baseUrl?.startsWith('file://')) { const basePath = baseUrl.slice('file://'.length); const srcDir = resolveSafeChildPath(path.dirname(basePath), fetchUrl); - await fs.copy(srcDir, outputPath); + await fs.copy(srcDir, outputPath, { + filter: src => isChildPath(srcDir, src), + }); } else { const readUrl = getReadUrl(fetchUrl, baseUrl, integrations); diff --git a/plugins/scaffolder-node/src/actions/gitHelpers.test.ts b/plugins/scaffolder-node/src/actions/gitHelpers.test.ts index d70ddf3bc0..96a8b96242 100644 --- a/plugins/scaffolder-node/src/actions/gitHelpers.test.ts +++ b/plugins/scaffolder-node/src/actions/gitHelpers.test.ts @@ -17,6 +17,7 @@ import { Git } from '../scm'; import { addFiles, + removeFiles, cloneRepo, commitAndPushBranch, commitAndPushRepo, @@ -31,6 +32,7 @@ jest.mock('../scm', () => ({ fromAuth: jest.fn().mockReturnValue({ init: jest.fn(), add: jest.fn(), + remove: jest.fn(), checkout: jest.fn(), branch: jest.fn(), commit: jest @@ -528,6 +530,47 @@ describe('addFiles', () => { }); }); +describe('removeFiles', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('with minimal parameters', () => { + beforeEach(async () => { + await removeFiles({ + dir: '/tmp/repo/dir/', + filepath: 'file-to-remove.txt', + auth: { + username: 'test-user', + password: 'test-password', + }, + }); + }); + + it('removes the file', () => { + expect(mockedGit.remove).toHaveBeenCalledWith({ + filepath: 'file-to-remove.txt', + dir: '/tmp/repo/dir/', + }); + }); + }); + + it('with token', async () => { + await removeFiles({ + dir: '/tmp/repo/dir/', + filepath: 'file-to-remove.txt', + auth: { + token: 'test-token', + }, + }); + + expect(mockedGit.remove).toHaveBeenCalledWith({ + filepath: 'file-to-remove.txt', + dir: '/tmp/repo/dir/', + }); + }); +}); + describe('commitAndPushBranch', () => { afterEach(() => { jest.clearAllMocks(); diff --git a/plugins/scaffolder-node/src/actions/gitHelpers.ts b/plugins/scaffolder-node/src/actions/gitHelpers.ts index 49650de92e..8ae4ec6a4f 100644 --- a/plugins/scaffolder-node/src/actions/gitHelpers.ts +++ b/plugins/scaffolder-node/src/actions/gitHelpers.ts @@ -204,6 +204,27 @@ export async function addFiles(options: { await git.add({ dir, filepath }); } +/** + * @public + */ +export async function removeFiles(options: { + dir: string; + filepath: string; + // For use cases where token has to be used with Basic Auth + // it has to be provided as password together with a username + // which may be a fixed value defined by the provider. + auth: { username: string; password: string } | { token: string }; + logger?: LoggerService | undefined; +}): Promise { + const { dir, filepath, auth, logger } = options; + const git = Git.fromAuth({ + ...auth, + logger, + }); + + await git.remove({ dir, filepath }); +} + /** * @public */ diff --git a/plugins/scaffolder-node/src/actions/index.ts b/plugins/scaffolder-node/src/actions/index.ts index 686d89d844..47939f34cc 100644 --- a/plugins/scaffolder-node/src/actions/index.ts +++ b/plugins/scaffolder-node/src/actions/index.ts @@ -30,6 +30,7 @@ export { commitAndPushRepo, commitAndPushBranch, addFiles, + removeFiles, createBranch, cloneRepo, } from './gitHelpers'; diff --git a/plugins/scaffolder-node/src/actions/util.test.ts b/plugins/scaffolder-node/src/actions/util.test.ts index 792e306d00..8afbbf553a 100644 --- a/plugins/scaffolder-node/src/actions/util.test.ts +++ b/plugins/scaffolder-node/src/actions/util.test.ts @@ -54,11 +54,11 @@ describe('scaffolder action utils', () => { /No matching integration configuration for host/, ); }); - describe('bitbucket', () => { - beforeEach(() => byHost.mockReturnValue({ type: 'bitbucket' })); + describe('bitbucketCloud', () => { + beforeEach(() => byHost.mockReturnValue({ type: 'bitbucketCloud' })); describe('cloud', () => { const [host, workspace, project, repo] = [ - 'www.bitbucket.org', + 'bitbucket.org', 'foo', 'bar', 'baz', @@ -97,6 +97,9 @@ describe('scaffolder action utils', () => { repo, })); }); + }); + describe('bitbucketServer', () => { + beforeEach(() => byHost.mockReturnValue({ type: 'bitbucketServer' })); describe('other', () => { const [host, project, repo] = ['bitbucket.other', 'foo', 'bar']; it('requires project', () => diff --git a/plugins/scaffolder-node/src/actions/util.ts b/plugins/scaffolder-node/src/actions/util.ts index 34cc74c811..bddf3ba0f3 100644 --- a/plugins/scaffolder-node/src/actions/util.ts +++ b/plugins/scaffolder-node/src/actions/util.ts @@ -16,11 +16,11 @@ import { InputError } from '@backstage/errors'; import { isChildPath } from '@backstage/backend-plugin-api'; -import { join as joinPath, normalize as normalizePath } from 'path'; +import { join as joinPath, normalize as normalizePath } from 'node:path'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { TemplateActionOptions } from './createTemplateAction'; import zodToJsonSchema from 'zod-to-json-schema'; -import { z } from 'zod'; +import { z } from 'zod/v3'; import { Schema } from 'jsonschema'; import { trim } from 'lodash'; @@ -84,10 +84,11 @@ export const parseRepoUrl = ( ]), ); switch (type) { - case 'bitbucket': { - if (host === 'www.bitbucket.org') { - checkRequiredParams(parsed, 'workspace'); - } + case 'bitbucketCloud': { + checkRequiredParams(parsed, 'workspace', 'project', 'repo'); + break; + } + case 'bitbucketServer': { checkRequiredParams(parsed, 'project', 'repo'); break; } diff --git a/plugins/scaffolder-node/src/alpha/filters/createTemplateFilter.ts b/plugins/scaffolder-node/src/alpha/filters/createTemplateFilter.ts index f648658588..ce8bd517ee 100644 --- a/plugins/scaffolder-node/src/alpha/filters/createTemplateFilter.ts +++ b/plugins/scaffolder-node/src/alpha/filters/createTemplateFilter.ts @@ -16,7 +16,7 @@ import { ZodFunctionSchema } from '../types'; import { CreatedTemplateFilter, TemplateFilterExample } from './types'; -import { z } from 'zod'; +import { z } from 'zod/v3'; /** * This function is used to create new template filters in type-safe manner. diff --git a/plugins/scaffolder-node/src/alpha/filters/types.ts b/plugins/scaffolder-node/src/alpha/filters/types.ts index 1906ba1c9c..e721618f7a 100644 --- a/plugins/scaffolder-node/src/alpha/filters/types.ts +++ b/plugins/scaffolder-node/src/alpha/filters/types.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { z } from 'zod'; +import { z } from 'zod/v3'; import { ZodFunctionSchema } from '../types'; export type { TemplateFilter } from '../../types'; diff --git a/plugins/scaffolder-node/src/alpha/globals/createTemplateGlobal.ts b/plugins/scaffolder-node/src/alpha/globals/createTemplateGlobal.ts index 2687b83bb4..0d5a01650c 100644 --- a/plugins/scaffolder-node/src/alpha/globals/createTemplateGlobal.ts +++ b/plugins/scaffolder-node/src/alpha/globals/createTemplateGlobal.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { z } from 'zod'; +import { z } from 'zod/v3'; import { CreatedTemplateGlobalFunction, CreatedTemplateGlobalValue, diff --git a/plugins/scaffolder-node/src/alpha/globals/types.ts b/plugins/scaffolder-node/src/alpha/globals/types.ts index 27a3a700e5..977f733673 100644 --- a/plugins/scaffolder-node/src/alpha/globals/types.ts +++ b/plugins/scaffolder-node/src/alpha/globals/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { JsonValue } from '@backstage/types'; -import { z } from 'zod'; +import { z } from 'zod/v3'; import { ZodFunctionSchema } from '../types'; export type { TemplateGlobal } from '../../types'; diff --git a/plugins/scaffolder-node/src/alpha/index.ts b/plugins/scaffolder-node/src/alpha/index.ts index 9d0b9efafd..1df5b5bdce 100644 --- a/plugins/scaffolder-node/src/alpha/index.ts +++ b/plugins/scaffolder-node/src/alpha/index.ts @@ -153,3 +153,54 @@ export const scaffolderWorkspaceProviderExtensionPoint = createExtensionPoint({ id: 'scaffolder.workspace.provider', }); + +import { createPermissionResourceRef } from '@backstage/plugin-permission-node'; +import { + RESOURCE_TYPE_SCAFFOLDER_TEMPLATE, + RESOURCE_TYPE_SCAFFOLDER_ACTION, + RESOURCE_TYPE_SCAFFOLDER_TASK, +} from '@backstage/plugin-scaffolder-common/alpha'; +import type { + TemplateEntityStepV1beta3, + TemplateParametersV1beta3, +} from '@backstage/plugin-scaffolder-common'; +import type { JsonObject } from '@backstage/types'; +import type { + SerializedTask, + TaskFilter, +} from '@backstage/plugin-scaffolder-node'; + +/** + * Permission resource ref for scaffolder templates. + * @alpha + */ +export const scaffolderTemplatePermissionResourceRef = + createPermissionResourceRef< + TemplateEntityStepV1beta3 | TemplateParametersV1beta3, + {} + >().with({ + pluginId: 'scaffolder', + resourceType: RESOURCE_TYPE_SCAFFOLDER_TEMPLATE, + }); + +/** + * Permission resource ref for scaffolder actions. + * @alpha + */ +export const scaffolderActionPermissionResourceRef = + createPermissionResourceRef().with({ + pluginId: 'scaffolder', + resourceType: RESOURCE_TYPE_SCAFFOLDER_ACTION, + }); + +/** + * Permission resource ref for scaffolder tasks. + * @alpha + */ +export const scaffolderTaskPermissionResourceRef = createPermissionResourceRef< + SerializedTask, + TaskFilter +>().with({ + pluginId: 'scaffolder', + resourceType: RESOURCE_TYPE_SCAFFOLDER_TASK, +}); diff --git a/plugins/scaffolder-node/src/alpha/types.ts b/plugins/scaffolder-node/src/alpha/types.ts index ca52973775..171c8e85f9 100644 --- a/plugins/scaffolder-node/src/alpha/types.ts +++ b/plugins/scaffolder-node/src/alpha/types.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { z } from 'zod'; +import { z } from 'zod/v3'; /** * @alpha diff --git a/plugins/scaffolder-node/src/files/deserializeDirectoryContents.ts b/plugins/scaffolder-node/src/files/deserializeDirectoryContents.ts index 4c6ae251ee..5e3c6f139f 100644 --- a/plugins/scaffolder-node/src/files/deserializeDirectoryContents.ts +++ b/plugins/scaffolder-node/src/files/deserializeDirectoryContents.ts @@ -15,7 +15,7 @@ */ import fs from 'fs-extra'; -import { dirname } from 'path'; +import { dirname } from 'node:path'; import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { SerializedFile } from './types'; diff --git a/plugins/scaffolder-node/src/files/serializeDirectoryContents.ts b/plugins/scaffolder-node/src/files/serializeDirectoryContents.ts index 1b83a8cf32..c0eb1fc6ae 100644 --- a/plugins/scaffolder-node/src/files/serializeDirectoryContents.ts +++ b/plugins/scaffolder-node/src/files/serializeDirectoryContents.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { promises as fs } from 'fs'; +import { promises as fs } from 'node:fs'; import globby from 'globby'; import limiterFactory from 'p-limit'; import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; diff --git a/plugins/scaffolder-node/src/index.ts b/plugins/scaffolder-node/src/index.ts index a9aa77ba4e..5a518c8628 100644 --- a/plugins/scaffolder-node/src/index.ts +++ b/plugins/scaffolder-node/src/index.ts @@ -25,3 +25,4 @@ export * from './tasks'; export * from './files'; export * from './types'; export * from './extensions'; +export * from './scaffolderService'; diff --git a/plugins/scaffolder-node/src/scaffolderService.test.ts b/plugins/scaffolder-node/src/scaffolderService.test.ts new file mode 100644 index 0000000000..de0870751f --- /dev/null +++ b/plugins/scaffolder-node/src/scaffolderService.test.ts @@ -0,0 +1,205 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + createBackendModule, + createServiceFactory, + createServiceRef, +} from '@backstage/backend-plugin-api'; +import { + ServiceFactoryTester, + mockCredentials, + mockServices, + registerMswTestHooks, + startTestBackend, +} from '@backstage/backend-test-utils'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; +import { scaffolderServiceRef } from './scaffolderService'; + +describe('scaffolderServiceRef', () => { + const server = setupServer(); + registerMswTestHooks(server); + + it('should return a scaffolder service', async () => { + expect.assertions(1); + const testModule = createBackendModule({ + moduleId: 'test', + pluginId: 'test', + register(env) { + env.registerInit({ + deps: { + scaffolder: scaffolderServiceRef, + }, + async init({ scaffolder }) { + expect(scaffolder.getTask).toBeDefined(); + }, + }); + }, + }); + + await startTestBackend({ + features: [testModule], + }); + }); + + it('should inject token from user credentials', async () => { + expect.assertions(1); + + server.use( + rest.get('*/api/scaffolder/v2/tasks/:taskId', (req, res, ctx) => { + expect(req.headers.get('authorization')).toBe( + mockCredentials.service.header({ + onBehalfOf: mockCredentials.user(), + targetPluginId: 'scaffolder', + }), + ); + return res( + ctx.json({ + id: 'task-1', + spec: {}, + status: 'completed', + createdAt: '2025-01-01T00:00:00Z', + }), + ); + }), + ); + + const tester = ServiceFactoryTester.from( + createServiceFactory({ + service: createServiceRef({ id: 'unused-dummy' }), + deps: {}, + factory() {}, + }), + { dependencies: [mockServices.discovery.factory()] }, + ); + + const scaffolder = await tester.getService(scaffolderServiceRef); + + await scaffolder.getTask( + { taskId: 'task-1' }, + { + credentials: mockCredentials.user(), + }, + ); + }); + + it('should inject token from service credentials', async () => { + expect.assertions(1); + + server.use( + rest.get('*/api/scaffolder/v2/tasks/:taskId', (req, res, ctx) => { + expect(req.headers.get('authorization')).toBe( + mockCredentials.service.header({ + onBehalfOf: mockCredentials.service(), + targetPluginId: 'scaffolder', + }), + ); + return res( + ctx.json({ + id: 'task-1', + spec: {}, + status: 'completed', + createdAt: '2025-01-01T00:00:00Z', + }), + ); + }), + ); + + const tester = ServiceFactoryTester.from( + createServiceFactory({ + service: createServiceRef({ id: 'unused-dummy' }), + deps: {}, + factory() {}, + }), + { dependencies: [mockServices.discovery.factory()] }, + ); + + const scaffolder = await tester.getService(scaffolderServiceRef); + + await scaffolder.getTask( + { taskId: 'task-1' }, + { + credentials: mockCredentials.service(), + }, + ); + }); + + it('should pass credentials for direct HTTP calls like getLogs', async () => { + expect.assertions(1); + + server.use( + rest.get('*/api/scaffolder/v2/tasks/:taskId/events', (req, res, ctx) => { + expect(req.headers.get('authorization')).toBe( + mockCredentials.service.header({ + onBehalfOf: mockCredentials.user(), + targetPluginId: 'scaffolder', + }), + ); + return res(ctx.json([])); + }), + ); + + const tester = ServiceFactoryTester.from( + createServiceFactory({ + service: createServiceRef({ id: 'unused-dummy' }), + deps: {}, + factory() {}, + }), + { dependencies: [mockServices.discovery.factory()] }, + ); + + const scaffolder = await tester.getService(scaffolderServiceRef); + + await scaffolder.getLogs( + { taskId: 'task-1' }, + { credentials: mockCredentials.user() }, + ); + }); + + it('should pass createdBy and pagination params for listTasks', async () => { + expect.assertions(4); + + server.use( + rest.get('*/api/scaffolder/v2/tasks', (req, res, ctx) => { + expect(req.url.searchParams.get('createdBy')).toBe( + 'user:default/guest', + ); + expect(req.url.searchParams.get('limit')).toBe('10'); + expect(req.url.searchParams.get('offset')).toBe('5'); + return res(ctx.json({ tasks: [], totalTasks: 0 })); + }), + ); + + const tester = ServiceFactoryTester.from( + createServiceFactory({ + service: createServiceRef({ id: 'unused-dummy' }), + deps: {}, + factory() {}, + }), + { dependencies: [mockServices.discovery.factory()] }, + ); + + const scaffolder = await tester.getService(scaffolderServiceRef); + + const result = await scaffolder.listTasks( + { createdBy: 'user:default/guest', limit: 10, offset: 5 }, + { credentials: mockCredentials.user() }, + ); + + expect(result).toEqual({ items: [], totalItems: 0 }); + }); +}); diff --git a/plugins/scaffolder-node/src/scaffolderService.ts b/plugins/scaffolder-node/src/scaffolderService.ts new file mode 100644 index 0000000000..b978164c7f --- /dev/null +++ b/plugins/scaffolder-node/src/scaffolderService.ts @@ -0,0 +1,338 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + AuthService, + BackstageCredentials, + coreServices, + createServiceFactory, + createServiceRef, + DiscoveryService, +} from '@backstage/backend-plugin-api'; +import { ResponseError } from '@backstage/errors'; +import { ScmIntegrations } from '@backstage/integration'; +import { + ListActionsResponse, + ListTemplatingExtensionsResponse, + LogEvent, + ScaffolderClient, + ScaffolderDryRunOptions, + ScaffolderDryRunResponse, + ScaffolderRequestOptions, + ScaffolderScaffoldOptions, + ScaffolderScaffoldResponse, + ScaffolderTask, + ScaffolderTaskStatus, +} from '@backstage/plugin-scaffolder-common'; +import type { TemplateParameterSchema } from '@backstage/plugin-scaffolder-common'; + +/** + * @public + */ +export interface ScaffolderServiceRequestOptions { + credentials: BackstageCredentials; +} + +/** + * A backend service interface for the scaffolder that uses + * {@link @backstage/backend-plugin-api#BackstageCredentials} instead of tokens. + * + * @public + */ +export interface ScaffolderService { + getTemplateParameterSchema( + request: { templateRef: string }, + options: ScaffolderServiceRequestOptions, + ): Promise; + + scaffold( + request: ScaffolderScaffoldOptions, + options: ScaffolderServiceRequestOptions, + ): Promise; + + getTask( + request: { taskId: string }, + options: ScaffolderServiceRequestOptions, + ): Promise; + + cancelTask( + request: { taskId: string }, + options: ScaffolderServiceRequestOptions, + ): Promise<{ status?: ScaffolderTaskStatus }>; + + retry( + request: { taskId: string }, + options: ScaffolderServiceRequestOptions, + ): Promise<{ id: string }>; + + listTasks( + request: { + createdBy?: string; + limit?: number; + offset?: number; + }, + options: ScaffolderServiceRequestOptions, + ): Promise<{ items: ScaffolderTask[]; totalItems: number }>; + + listActions( + request?: {}, + options?: ScaffolderServiceRequestOptions, + ): Promise; + + listTemplatingExtensions( + request?: {}, + options?: ScaffolderServiceRequestOptions, + ): Promise; + + getLogs( + request: { + taskId: string; + after?: number; + }, + options: ScaffolderServiceRequestOptions, + ): Promise; + + dryRun( + request: ScaffolderDryRunOptions, + options: ScaffolderServiceRequestOptions, + ): Promise; + + autocomplete( + request: { + token: string; + provider: string; + resource: string; + context: Record; + }, + options: ScaffolderServiceRequestOptions, + ): Promise<{ results: { title?: string; id: string }[] }>; +} + +class DefaultScaffolderService implements ScaffolderService { + readonly #auth: AuthService; + readonly #client: ScaffolderClient; + readonly #discovery: DiscoveryService; + + constructor(options: { + auth: AuthService; + client: ScaffolderClient; + discovery: DiscoveryService; + }) { + this.#auth = options.auth; + this.#client = options.client; + this.#discovery = options.discovery; + } + + async getTemplateParameterSchema( + request: { templateRef: string }, + options: ScaffolderServiceRequestOptions, + ): Promise { + return this.#client.getTemplateParameterSchema( + request.templateRef, + await this.#getOptions(options), + ); + } + + async scaffold( + request: ScaffolderScaffoldOptions, + options: ScaffolderServiceRequestOptions, + ): Promise { + return this.#client.scaffold(request, await this.#getOptions(options)); + } + + async getTask( + request: { taskId: string }, + options: ScaffolderServiceRequestOptions, + ): Promise { + return this.#client.getTask( + request.taskId, + await this.#getOptions(options), + ); + } + + async cancelTask( + request: { taskId: string }, + options: ScaffolderServiceRequestOptions, + ): Promise<{ status?: ScaffolderTaskStatus }> { + return this.#client.cancelTask( + request.taskId, + await this.#getOptions(options), + ); + } + + async retry( + request: { taskId: string }, + options: ScaffolderServiceRequestOptions, + ): Promise<{ id: string }> { + return this.#client.retry(request.taskId, await this.#getOptions(options)); + } + + async listTasks( + request: { + createdBy?: string; + limit?: number; + offset?: number; + }, + options: ScaffolderServiceRequestOptions, + ): Promise<{ items: ScaffolderTask[]; totalItems: number }> { + const { token } = await this.#getOptions(options); + const baseUrl = await this.#discovery.getBaseUrl('scaffolder'); + + const params = new URLSearchParams(); + if (request.createdBy) { + params.set('createdBy', request.createdBy); + } + if (request.limit !== undefined) { + params.set('limit', String(request.limit)); + } + if (request.offset !== undefined) { + params.set('offset', String(request.offset)); + } + + const query = params.toString(); + const url = `${baseUrl}/v2/tasks${query ? `?${query}` : ''}`; + + const response = await fetch(url, { + headers: { + 'Content-Type': 'application/json', + ...(token && { Authorization: `Bearer ${token}` }), + }, + }); + + if (!response.ok) { + throw await ResponseError.fromResponse(response); + } + + const body = await response.json(); + return { + items: body.tasks, + totalItems: body.totalTasks ?? 0, + }; + } + + async listActions( + _request?: {}, + options?: ScaffolderServiceRequestOptions, + ): Promise { + return this.#client.listActions( + options ? await this.#getOptions(options) : {}, + ); + } + + async listTemplatingExtensions( + _request?: {}, + options?: ScaffolderServiceRequestOptions, + ): Promise { + return this.#client.listTemplatingExtensions( + options ? await this.#getOptions(options) : {}, + ); + } + + async getLogs( + request: { + taskId: string; + after?: number; + }, + options: ScaffolderServiceRequestOptions, + ): Promise { + const { token } = await this.#getOptions(options); + const baseUrl = await this.#discovery.getBaseUrl('scaffolder'); + + const params = new URLSearchParams(); + if (request.after !== undefined) { + params.set('after', String(request.after)); + } + + const query = params.toString(); + const taskId = encodeURIComponent(request.taskId); + const url = `${baseUrl}/v2/tasks/${taskId}/events${ + query ? `?${query}` : '' + }`; + + const response = await fetch(url, { + headers: { + 'Content-Type': 'application/json', + ...(token && { Authorization: `Bearer ${token}` }), + }, + }); + + if (!response.ok) { + throw await ResponseError.fromResponse(response); + } + + return response.json(); + } + + async dryRun( + request: ScaffolderDryRunOptions, + options: ScaffolderServiceRequestOptions, + ): Promise { + return this.#client.dryRun(request, await this.#getOptions(options)); + } + + async autocomplete( + request: { + token: string; + provider: string; + resource: string; + context: Record; + }, + options: ScaffolderServiceRequestOptions, + ): Promise<{ results: { title?: string; id: string }[] }> { + return this.#client.autocomplete(request, await this.#getOptions(options)); + } + + async #getOptions( + options: ScaffolderServiceRequestOptions, + ): Promise { + return this.#auth.getPluginRequestToken({ + onBehalfOf: options.credentials, + targetPluginId: 'scaffolder', + }); + } +} + +/** + * A service ref for the scaffolder client, to be used by backend plugins + * and modules that need to interact with the scaffolder API. + * + * @public + */ +export const scaffolderServiceRef = createServiceRef({ + id: 'scaffolder-client', + defaultFactory: async service => + createServiceFactory({ + service, + deps: { + auth: coreServices.auth, + discovery: coreServices.discovery, + config: coreServices.rootConfig, + }, + async factory({ auth, discovery, config }) { + const integrations = ScmIntegrations.fromConfig(config); + const client = new ScaffolderClient({ + discoveryApi: discovery, + fetchApi: { fetch }, + scmIntegrationsApi: integrations, + }); + return new DefaultScaffolderService({ + auth, + client, + discovery, + }); + }, + }), +}); diff --git a/plugins/scaffolder-node/src/tasks/serializer.ts b/plugins/scaffolder-node/src/tasks/serializer.ts index baf1b42157..ac5f66b722 100644 --- a/plugins/scaffolder-node/src/tasks/serializer.ts +++ b/plugins/scaffolder-node/src/tasks/serializer.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import tar from 'tar'; +import * as tar from 'tar'; import concatStream from 'concat-stream'; -import { promisify } from 'util'; -import { pipeline as pipelineCb, Readable } from 'stream'; +import { promisify } from 'node:util'; +import { pipeline as pipelineCb, Readable } from 'node:stream'; const pipeline = promisify(pipelineCb); /** diff --git a/packages/backend-defaults/src/entrypoints/auditor/types.ts b/plugins/scaffolder-node/src/testUtils.ts similarity index 69% rename from packages/backend-defaults/src/entrypoints/auditor/types.ts rename to plugins/scaffolder-node/src/testUtils.ts index 7827a31d1f..7b1c7fb61d 100644 --- a/packages/backend-defaults/src/entrypoints/auditor/types.ts +++ b/plugins/scaffolder-node/src/testUtils.ts @@ -14,13 +14,14 @@ * limitations under the License. */ -import { z } from 'zod'; +/** + * Backend test helpers for the Scaffolder plugin. + * + * @packageDocumentation + */ -/** @internal */ -export const severityLogLevelMappingsSchema = z.record( - z.enum(['low', 'medium', 'high', 'critical']), - z.enum(['debug', 'info', 'warn', 'error']), -); - -/** @internal */ -export const CONFIG_ROOT_KEY = 'backend.auditor'; +export type { + ScaffolderService, + ScaffolderServiceRequestOptions, +} from './scaffolderService'; +export { scaffolderServiceMock } from './testUtils/scaffolderServiceMock'; diff --git a/plugins/scaffolder-node/src/testUtils/scaffolderServiceMock.test.ts b/plugins/scaffolder-node/src/testUtils/scaffolderServiceMock.test.ts new file mode 100644 index 0000000000..7145d12e2a --- /dev/null +++ b/plugins/scaffolder-node/src/testUtils/scaffolderServiceMock.test.ts @@ -0,0 +1,35 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { scaffolderServiceMock } from './scaffolderServiceMock'; + +describe('scaffolderServiceMock', () => { + it('creates a mock with all methods as jest.fn()', () => { + const mock = scaffolderServiceMock.mock(); + + expect(mock.getTask).toHaveBeenCalledTimes(0); + }); + + it('supports overriding individual methods', async () => { + const mock = scaffolderServiceMock.mock({ + getTask: jest.fn().mockResolvedValue({ id: 'task-1' }), + }); + + await expect( + mock.getTask({ taskId: 'task-1' }, { credentials: expect.anything() }), + ).resolves.toEqual(expect.objectContaining({ id: 'task-1' })); + }); +}); diff --git a/plugins/scaffolder-node/src/testUtils/scaffolderServiceMock.ts b/plugins/scaffolder-node/src/testUtils/scaffolderServiceMock.ts new file mode 100644 index 0000000000..7b18c01298 --- /dev/null +++ b/plugins/scaffolder-node/src/testUtils/scaffolderServiceMock.ts @@ -0,0 +1,46 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createServiceMock } from '@backstage/backend-test-utils'; +import { scaffolderServiceRef, ScaffolderService } from '../scaffolderService'; + +/** + * A collection of mock functionality for the scaffolder service. + * + * @public + */ +export namespace scaffolderServiceMock { + /** + * Creates a scaffolder service whose methods are mock functions, possibly with + * some of them overloaded by the caller. + */ + export const mock = createServiceMock( + scaffolderServiceRef, + () => ({ + getTemplateParameterSchema: jest.fn(), + scaffold: jest.fn(), + getTask: jest.fn(), + cancelTask: jest.fn(), + retry: jest.fn(), + listTasks: jest.fn(), + listActions: jest.fn(), + listTemplatingExtensions: jest.fn(), + getLogs: jest.fn(), + dryRun: jest.fn(), + autocomplete: jest.fn(), + }), + ); +} diff --git a/plugins/scaffolder-react/CHANGELOG.md b/plugins/scaffolder-react/CHANGELOG.md index 530b2466cc..a8fbc6a8e5 100644 --- a/plugins/scaffolder-react/CHANGELOG.md +++ b/plugins/scaffolder-react/CHANGELOG.md @@ -1,5 +1,339 @@ # @backstage/plugin-scaffolder-react +## 1.20.1 + +### Patch Changes + +- bdbbf00: build(deps): bump `flatted` from 3.4.1 to 3.4.2 +- Updated dependencies + - @backstage/theme@0.7.3 + - @backstage/catalog-model@1.8.0 + - @backstage/plugin-catalog-react@2.1.2 + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/core-components@0.18.9 + - @backstage/catalog-client@1.15.0 + - @backstage/plugin-scaffolder-common@2.1.0 + - @backstage/plugin-permission-react@0.5.0 + - @backstage/frontend-test-utils@0.5.2 + - @backstage/core-plugin-api@1.12.5 + +## 1.20.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.7.3-next.0 + - @backstage/core-components@0.18.9-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.2 + - @backstage/catalog-client@1.14.1-next.0 + - @backstage/catalog-model@1.7.8-next.0 + - @backstage/core-plugin-api@1.12.5-next.2 + - @backstage/frontend-plugin-api@0.16.0-next.2 + - @backstage/plugin-scaffolder-common@2.0.1-next.0 + - @backstage/frontend-test-utils@0.5.2-next.2 + - @backstage/plugin-permission-react@0.4.42-next.1 + +## 1.20.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/frontend-test-utils@0.5.2-next.1 + +## 1.20.1-next.0 + +### Patch Changes + +- bdbbf00: build(deps): bump `flatted` from 3.4.1 to 3.4.2 +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.1-next.0 + - @backstage/core-components@0.18.9-next.0 + - @backstage/frontend-plugin-api@0.15.2-next.0 + - @backstage/frontend-test-utils@0.5.2-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + - @backstage/catalog-client@1.14.0 + - @backstage/catalog-model@1.7.7 + - @backstage/theme@0.7.2 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-permission-react@0.4.42-next.0 + - @backstage/plugin-scaffolder-common@2.0.0 + +## 1.20.0 + +### Minor Changes + +- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point. +- 470f72d: The `LogViewer` component from `@backstage/core-components` now supports downloading logs if a callback is passed to `onDownloadLogs` + +### Patch Changes + +- 004b5c1: Added back `formFieldsApiRef` and `ScaffolderFormFieldsApi` as alpha exports. +- bd31ddd: Updated dependency `flatted` to `3.3.4`. +- f598909: Added `scaffolderApiMock` test utility, exported from `@backstage/plugin-scaffolder-react/testUtils`. +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/catalog-client@1.14.0 + - @backstage/frontend-test-utils@0.5.1 + - @backstage/plugin-permission-react@0.4.41 + - @backstage/plugin-scaffolder-common@2.0.0 + - @backstage/catalog-model@1.7.7 + +## 1.20.0-next.2 + +### Minor Changes + +- 470f72d: The `LogViewer` component from `@backstage/core-components` now supports downloading logs if a callback is passed to `onDownloadLogs` + +### Patch Changes + +- bd31ddd: Updated dependency `flatted` to `3.3.4`. +- Updated dependencies + - @backstage/frontend-test-utils@0.5.1-next.2 + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/catalog-client@1.14.0-next.2 + - @backstage/plugin-catalog-react@2.1.0-next.2 + - @backstage/core-components@0.18.8-next.1 + - @backstage/plugin-scaffolder-common@2.0.0-next.2 + +## 1.19.8-next.1 + +### Patch Changes + +- 004b5c1: Added back `formFieldsApiRef` and `ScaffolderFormFieldsApi` as alpha exports. +- f598909: Added `scaffolderApiMock` test utility, exported from `@backstage/plugin-scaffolder-react/testUtils`. +- Updated dependencies + - @backstage/catalog-client@1.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/frontend-test-utils@0.5.1-next.1 + - @backstage/plugin-scaffolder-common@2.0.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/theme@0.7.2 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-permission-react@0.4.41-next.0 + +## 1.19.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/catalog-client@1.13.1-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/theme@0.7.2 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-permission-react@0.4.41-next.0 + - @backstage/plugin-scaffolder-common@1.7.7-next.0 + +## 1.19.7 + +### Patch Changes + +- 2eeca03: Scaffolder form fields in the new frontend system now use a Utility API pattern instead of multiple attachment points. The `FormFieldBlueprint` now uses this new approach, and while form fields created with older versions still work, they will produce a deprecation warning and will stop working in a future release. + + As part of this change, the following alpha exports were removed: + + - `formFieldsApi` + - `formFieldsApiRef` + - `ScaffolderFormFieldsApi` + +- b9d90a7: Added `@backstage/frontend-test-utils` as a dev dependency for mock API usage in tests. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/core-components@0.18.7 + - @backstage/theme@0.7.2 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/catalog-client@1.13.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-permission-react@0.4.40 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-scaffolder-common@1.7.6 + +## 1.19.7-next.2 + +### Patch Changes + +- b9d90a7: Added `@backstage/frontend-test-utils` as a dev dependency for mock API usage in tests. +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/catalog-client@1.12.2-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-permission-react@0.4.40-next.1 + - @backstage/version-bridge@1.0.12-next.0 + - @backstage/theme@0.7.2-next.1 + +## 1.19.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/theme@0.7.2-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/core-components@0.18.7-next.1 + - @backstage/plugin-scaffolder-common@1.7.6-next.1 + +## 1.19.6-next.0 + +### Patch Changes + +- 2eeca03: Scaffolder form fields in the new frontend system now use a Utility API pattern instead of multiple attachment points. The `FormFieldBlueprint` now uses this new approach, and while form fields created with older versions still work, they will produce a deprecation warning and will stop working in a future release. + + As part of this change, the following alpha exports were removed: + + - `formFieldsApi` + - `formFieldsApiRef` + - `ScaffolderFormFieldsApi` + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/theme@0.7.1 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-permission-react@0.4.40-next.0 + - @backstage/plugin-scaffolder-common@1.7.6-next.0 + +## 1.19.5 + +### Patch Changes + +- 872eb91: Upgrade `zod-to-json-schema` to latest version +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + - @backstage/plugin-catalog-react@1.21.5 + - @backstage/plugin-scaffolder-common@1.7.5 + +## 1.19.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.1 + +## 1.19.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## 1.19.4 + +### Patch Changes + +- fb029b6: Updated luxon types +- 5ca461e: Fixed bug where custom `review.name` values were incorrectly formatted by `startCase`, preserving them exactly as written. +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- 9b38f22: Updated dependency `use-immer` to `^0.11.0`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/plugin-permission-react@0.4.39 + - @backstage/plugin-scaffolder-common@1.7.4 + +## 1.19.4-next.2 + +### Patch Changes + +- fb029b6: Updated luxon types +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- 9b38f22: Updated dependency `use-immer` to `^0.11.0`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + +## 1.19.4-next.1 + +### Patch Changes + +- 5ca461e: Fixed bug where custom `review.name` values were incorrectly formatted by `startCase`, preserving them exactly as written. +- Updated dependencies + - @backstage/core-components@0.18.4-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.1 + +## 1.19.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## 1.19.3 + +### Patch Changes + +- 886a8a1: Fixed a bug in the Scaffolder's template parsing in the `useTemplateSchema` hook by removing the title instead of setting it to `undefined` +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/catalog-model@1.7.6 + - @backstage/catalog-client@1.12.1 + - @backstage/plugin-scaffolder-common@1.7.3 + ## 1.19.3-next.0 ### Patch Changes diff --git a/plugins/scaffolder-react/knip-report.md b/plugins/scaffolder-react/knip-report.md index c1c8bd6b32..119e774c91 100644 --- a/plugins/scaffolder-react/knip-report.md +++ b/plugins/scaffolder-react/knip-report.md @@ -2,13 +2,13 @@ ## Unused dependencies (1) -| Name | Location | Severity | -| :------------------------ | :----------- | :------- | -| @backstage/catalog-client | plugins/scaffolder-react/package.json | error | +| Name | Location | Severity | +| :------------------------ | :---------------- | :------- | +| @backstage/catalog-client | package.json:65:6 | error | ## Unused devDependencies (1) -| Name | Location | Severity | -| :------------------------------- | :----------- | :------- | -| @backstage/plugin-catalog-common | plugins/scaffolder-react/package.json | error | +| Name | Location | Severity | +| :------------------------------- | :----------------- | :------- | +| @backstage/plugin-catalog-common | package.json:107:6 | error | diff --git a/plugins/scaffolder-react/package.json b/plugins/scaffolder-react/package.json index 5ae1e56ebb..8e718deb27 100644 --- a/plugins/scaffolder-react/package.json +++ b/plugins/scaffolder-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-react", - "version": "1.19.3-next.0", + "version": "1.20.1", "description": "A frontend library that helps other Backstage plugins interact with the Scaffolder", "backstage": { "role": "web-library", @@ -31,6 +31,7 @@ "exports": { ".": "./src/index.ts", "./alpha": "./src/alpha.ts", + "./testUtils": "./src/testUtils.ts", "./package.json": "./package.json" }, "main": "src/index.ts", @@ -40,6 +41,9 @@ "alpha": [ "src/alpha.ts" ], + "testUtils": [ + "src/testUtils.ts" + ], "package.json": [ "package.json" ] @@ -73,15 +77,15 @@ "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", "@react-hookz/web": "^24.0.0", - "@rjsf/core": "5.23.2", - "@rjsf/material-ui": "5.23.2", - "@rjsf/utils": "5.23.2", - "@rjsf/validator-ajv8": "5.23.2", + "@rjsf/core": "5.24.13", + "@rjsf/material-ui": "5.24.13", + "@rjsf/utils": "5.24.13", + "@rjsf/validator-ajv8": "5.24.13", "@types/json-schema": "^7.0.9", "ajv": "^8.0.1", "ajv-errors": "^3.0.0", "classnames": "^2.2.6", - "flatted": "3.3.3", + "flatted": "^3.4.2", "humanize-duration": "^3.25.1", "immer": "^9.0.6", "json-schema": "^0.4.0", @@ -90,14 +94,15 @@ "luxon": "^3.0.0", "qs": "^6.9.4", "react-use": "^17.2.4", - "use-immer": "^0.10.0", + "use-immer": "^0.11.0", "zen-observable": "^0.10.0", - "zod": "^3.22.4", - "zod-to-json-schema": "^3.20.4" + "zod": "^3.25.76 || ^4.0.0", + "zod-to-json-schema": "^3.25.1" }, "devDependencies": { "@backstage/cli": "workspace:^", "@backstage/core-app-api": "workspace:^", + "@backstage/frontend-test-utils": "workspace:^", "@backstage/plugin-catalog": "workspace:^", "@backstage/plugin-catalog-common": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", @@ -107,20 +112,24 @@ "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.0.0", "@types/humanize-duration": "^3.18.1", - "@types/luxon": "^3.0.0", "@types/react": "^18.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0", + "react-router-dom": "^6.30.2", "swr": "^2.0.0" }, "peerDependencies": { + "@backstage/frontend-test-utils": "workspace:^", "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router": "^6.30.3", + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { + "@backstage/frontend-test-utils": { + "optional": true + }, "@types/react": { "optional": true } diff --git a/plugins/scaffolder-react/report-alpha.api.md b/plugins/scaffolder-react/report-alpha.api.md index 7497902e5b..80b9d9bde3 100644 --- a/plugins/scaffolder-react/report-alpha.api.md +++ b/plugins/scaffolder-react/report-alpha.api.md @@ -3,9 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyApiRef } from '@backstage/core-plugin-api'; -import { ApiFactory } from '@backstage/frontend-plugin-api'; import { ApiHolder } from '@backstage/core-plugin-api'; import { ApiRef } from '@backstage/frontend-plugin-api'; import { ComponentType } from 'react'; @@ -13,9 +11,7 @@ import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { CustomFieldValidator } from '@backstage/plugin-scaffolder-react'; import { Dispatch } from 'react'; import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; -import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; -import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { FieldExtensionComponentProps } from '@backstage/plugin-scaffolder-react'; import { FieldExtensionOptions } from '@backstage/plugin-scaffolder-react'; import { FieldSchema } from '@backstage/plugin-scaffolder-react'; @@ -26,7 +22,6 @@ import { JsonObject } from '@backstage/types'; import { JsonValue } from '@backstage/types'; import { JSX as JSX_2 } from 'react/jsx-runtime'; import { LayoutOptions } from '@backstage/plugin-scaffolder-react'; -import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { Overrides } from '@material-ui/core/styles/overrides'; import { PropsWithChildren } from 'react'; import { ReactElement } from 'react'; @@ -42,10 +37,10 @@ import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { TemplateGroupFilter } from '@backstage/plugin-scaffolder-react'; import { TemplateParameterSchema } from '@backstage/plugin-scaffolder-react'; import { TemplatePresentationV1beta3 } from '@backstage/plugin-scaffolder-common'; -import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; import { UiSchema } from '@rjsf/utils'; import { WidgetProps } from '@rjsf/utils'; -import { z } from 'zod'; +import { z } from 'zod/v3'; // @alpha (undocumented) export type BackstageOverrides = Overrides & { @@ -205,36 +200,9 @@ export type FormFieldExtensionData< }; // @alpha (undocumented) -export const formFieldsApi: OverridableExtensionDefinition<{ - config: {}; - configInput: {}; - output: ExtensionDataRef; - inputs: { - formFields: ExtensionInput< - ConfigurableExtensionDataRef< - () => Promise, - 'scaffolder.form-field-loader', - {} - >, - { - singleton: false; - optional: false; - } - >; - }; - kind: 'api'; - name: 'form-fields'; - params: < - TApi, - TImpl extends TApi, - TDeps extends { [name in string]: unknown }, - >( - params: ApiFactory, - ) => ExtensionBlueprintParams; -}>; - -// @alpha @deprecated (undocumented) -export const formFieldsApiRef: ApiRef; +export const formFieldsApiRef: ApiRef & { + readonly $$type: '@backstage/ApiRef'; +}; // @alpha (undocumented) export type FormValidation = { @@ -310,10 +278,10 @@ export type ScaffolderFormDecoratorContext< ) => void; }; -// @alpha @deprecated (undocumented) +// @alpha (undocumented) export interface ScaffolderFormFieldsApi { // (undocumented) - getFormFields(): Promise; + loadFormFields(): Promise; } // @alpha (undocumented) @@ -339,7 +307,7 @@ export type ScaffolderReactComponentsNameToClassKey = { // @alpha (undocumented) export type ScaffolderReactTemplateCategoryPickerClassKey = 'root' | 'label'; -// @alpha (undocumented) +// @alpha @deprecated (undocumented) export const scaffolderReactTranslationRef: TranslationRef< 'scaffolder-react', { @@ -352,9 +320,9 @@ export const scaffolderReactTranslationRef: TranslationRef< readonly 'scaffolderPageContextMenu.tasksLabel': 'Task List'; readonly 'scaffolderPageContextMenu.templatingExtensionsLabel': 'Templating Extensions'; readonly 'stepper.backButtonText': 'Back'; + readonly 'stepper.nextButtonText': 'Next'; readonly 'stepper.createButtonText': 'Create'; readonly 'stepper.reviewButtonText': 'Review'; - readonly 'stepper.nextButtonText': 'Next'; readonly 'stepper.stepIndexLabel': 'Step {{index, number}}'; readonly 'templateCategoryPicker.title': 'Categories'; readonly 'templateCard.noDescription': 'No description'; diff --git a/plugins/scaffolder-react/report-testUtils.api.md b/plugins/scaffolder-react/report-testUtils.api.md new file mode 100644 index 0000000000..c33675d93d --- /dev/null +++ b/plugins/scaffolder-react/report-testUtils.api.md @@ -0,0 +1,15 @@ +## API Report File for "@backstage/plugin-scaffolder-react" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { ApiMock } from '@backstage/frontend-test-utils'; +import { ScaffolderApi } from '@backstage/plugin-scaffolder-common'; + +// @public +export namespace scaffolderApiMock { + const mock: ( + partialImpl?: Partial | undefined, + ) => ApiMock; +} +``` diff --git a/plugins/scaffolder-react/report.api.md b/plugins/scaffolder-react/report.api.md index daec52a7e3..9b9b00ad02 100644 --- a/plugins/scaffolder-react/report.api.md +++ b/plugins/scaffolder-react/report.api.md @@ -56,10 +56,11 @@ import { TemplateGlobalFunction as TemplateGlobalFunction_2 } from '@backstage/p import { TemplateGlobalValue as TemplateGlobalValue_2 } from '@backstage/plugin-scaffolder-common'; import { TemplateParameterSchema as TemplateParameterSchema_2 } from '@backstage/plugin-scaffolder-common'; import { TemplatesType } from '@rjsf/utils'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; import { UIOptionsType } from '@rjsf/utils'; import { UiSchema } from '@rjsf/utils'; import { ValidatorType } from '@rjsf/utils'; -import { z } from 'zod'; +import { z } from 'zod/v3'; // @public @deprecated export type Action = Action_2; @@ -208,7 +209,9 @@ export type ReviewStepProps = { export type ScaffolderApi = ScaffolderApi_2; // @public (undocumented) -export const scaffolderApiRef: ApiRef; +export const scaffolderApiRef: ApiRef & { + readonly $$type: '@backstage/ApiRef'; +}; // @public @deprecated (undocumented) export type ScaffolderDryRunOptions = ScaffolderDryRunOptions_2; @@ -238,6 +241,31 @@ export type ScaffolderOutputLink = ScaffolderOutputLink_2; // @public @deprecated (undocumented) export type ScaffolderOutputText = ScaffolderOutputText_2; +// @public (undocumented) +export const scaffolderReactTranslationRef: TranslationRef< + 'scaffolder-react', + { + readonly 'workflow.noDescription': 'No description'; + readonly 'passwordWidget.content': 'This widget is insecure. Please use [`ui:field: Secret`](https://backstage.io/docs/features/software-templates/writing-templates/#using-secrets) instead of `ui:widget: password`'; + readonly 'scaffolderPageContextMenu.createLabel': 'Create'; + readonly 'scaffolderPageContextMenu.moreLabel': 'more'; + readonly 'scaffolderPageContextMenu.editorLabel': 'Manage Templates'; + readonly 'scaffolderPageContextMenu.actionsLabel': 'Installed Actions'; + readonly 'scaffolderPageContextMenu.tasksLabel': 'Task List'; + readonly 'scaffolderPageContextMenu.templatingExtensionsLabel': 'Templating Extensions'; + readonly 'stepper.backButtonText': 'Back'; + readonly 'stepper.nextButtonText': 'Next'; + readonly 'stepper.createButtonText': 'Create'; + readonly 'stepper.reviewButtonText': 'Review'; + readonly 'stepper.stepIndexLabel': 'Step {{index, number}}'; + 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'; + } +>; + // @public export type ScaffolderRJSFField< T = any, diff --git a/plugins/scaffolder-react/src/alpha.ts b/plugins/scaffolder-react/src/alpha.ts index e18c758b3d..dc51183658 100644 --- a/plugins/scaffolder-react/src/alpha.ts +++ b/plugins/scaffolder-react/src/alpha.ts @@ -16,4 +16,10 @@ export * from './next'; -export { scaffolderReactTranslationRef } from './translation'; +import { scaffolderReactTranslationRef as _scaffolderReactTranslationRef } from './translation'; + +/** + * @alpha + * @deprecated Import from `@backstage/plugin-scaffolder-react` instead. + */ +export const scaffolderReactTranslationRef = _scaffolderReactTranslationRef; diff --git a/plugins/scaffolder-react/src/hooks/useCustomFieldExtensions.test.tsx b/plugins/scaffolder-react/src/hooks/useCustomFieldExtensions.test.tsx index 3be482d684..8b8251d4eb 100644 --- a/plugins/scaffolder-react/src/hooks/useCustomFieldExtensions.test.tsx +++ b/plugins/scaffolder-react/src/hooks/useCustomFieldExtensions.test.tsx @@ -16,9 +16,8 @@ import { PropsWithChildren } from 'react'; import { createPlugin } from '@backstage/core-plugin-api'; -import { TestApiProvider, wrapInTestApp } from '@backstage/test-utils'; -import { renderHook, waitFor } from '@testing-library/react'; -import { ScaffolderFormFieldsApi, formFieldsApiRef } from '../alpha'; +import { wrapInTestApp } from '@backstage/test-utils'; +import { renderHook } from '@testing-library/react'; import { useCustomFieldExtensions } from './useCustomFieldExtensions'; import { ScaffolderFieldExtensions, @@ -33,22 +32,10 @@ const plugin = createPlugin({ }); describe('useCustomFieldExtensions', () => { - const mockFormFieldsApi: jest.Mocked = { - getFormFields: jest.fn(), - }; const wrapper = ({ children }: PropsWithChildren<{}>) => - wrapInTestApp( - - {children} - , - ); - - beforeEach(() => { - jest.resetAllMocks(); - }); + wrapInTestApp(<>{children}); it('should return field extensions from the React tree', async () => { - mockFormFieldsApi.getFormFields.mockResolvedValue([]); const CustomFieldExtension = plugin.provide( createScaffolderFieldExtension({ name: 'test', @@ -70,60 +57,4 @@ describe('useCustomFieldExtensions', () => { expect(result.current).toEqual([expect.objectContaining({ name: 'test' })]); }); - - it('should return field extensions from formFieldsApi', async () => { - mockFormFieldsApi.getFormFields.mockResolvedValue([ - { - name: 'blueprint', - component: () =>
    Test
    , - }, - ]); - - const { result } = renderHook(() => useCustomFieldExtensions(
    ), { - wrapper, - }); - - await waitFor(() => { - expect(result.current.length).toBeGreaterThan(0); - }); - - expect(result.current).toEqual([ - expect.objectContaining({ name: 'blueprint' }), - ]); - }); - - it('should return field extensions from both sources', async () => { - mockFormFieldsApi.getFormFields.mockResolvedValue([ - { - name: 'blueprint', - component: () =>
    Test
    , - }, - ]); - - const CustomFieldExtension = plugin.provide( - createScaffolderFieldExtension({ - name: 'test', - component: () =>
    Test
    , - }), - ); - - const { result } = renderHook( - () => - useCustomFieldExtensions( - - - , - ), - { - wrapper, - }, - ); - - await waitFor(() => { - expect(result.current).toHaveLength(2); - }); - - const fieldNames = result.current.map(field => field.name); - expect(fieldNames).toEqual(expect.arrayContaining(['test', 'blueprint'])); - }); }); diff --git a/plugins/scaffolder-react/src/hooks/useCustomFieldExtensions.ts b/plugins/scaffolder-react/src/hooks/useCustomFieldExtensions.ts index be24e4186d..47a91e5d63 100644 --- a/plugins/scaffolder-react/src/hooks/useCustomFieldExtensions.ts +++ b/plugins/scaffolder-react/src/hooks/useCustomFieldExtensions.ts @@ -13,9 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { useAsync, useMountEffect } from '@react-hookz/web'; -import { useApi, useElementFilter } from '@backstage/core-plugin-api'; -import { formFieldsApiRef } from '../next'; +import { useElementFilter } from '@backstage/core-plugin-api'; import { FieldExtensionOptions } from '../extensions'; import { FIELD_EXTENSION_KEY, @@ -32,14 +30,6 @@ export const useCustomFieldExtensions = < >( outlet: React.ReactNode, ) => { - // Get custom fields created with FormFieldBlueprint - const formFieldsApi = useApi(formFieldsApiRef); - const [{ result: blueprintFields }, { execute }] = useAsync( - () => formFieldsApi.getFormFields(), - [], - ); - useMountEffect(execute); - // Get custom fields created with ScaffolderFieldExtensions const outletFields = useElementFilter(outlet, elements => elements @@ -51,17 +41,5 @@ export const useCustomFieldExtensions = < }), ); - // This should really be a different type moving forward, but we do this to keep type compatibility. - // should probably also move the defaults into the API eventually too, but that will come with the move - // to the new frontend system. - const blueprintsToLegacy: FieldExtensionOptions[] = blueprintFields?.map( - field => ({ - component: field.component, - name: field.name, - validation: field.validation, - schema: field.schema?.schema, - }), - ); - - return [...blueprintsToLegacy, ...outletFields] as TComponentDataType[]; + return outletFields as TComponentDataType[]; }; diff --git a/plugins/scaffolder-react/src/index.ts b/plugins/scaffolder-react/src/index.ts index e73df50fce..bfffa342a1 100644 --- a/plugins/scaffolder-react/src/index.ts +++ b/plugins/scaffolder-react/src/index.ts @@ -22,3 +22,4 @@ export * from './api'; export * from './hooks'; export * from './layouts'; export * from './utils'; +export { scaffolderReactTranslationRef } from './translation'; diff --git a/plugins/scaffolder-react/src/next/api/index.ts b/plugins/scaffolder-react/src/next/api/index.ts index 52c40ad99a..1ecddeaf41 100644 --- a/plugins/scaffolder-react/src/next/api/index.ts +++ b/plugins/scaffolder-react/src/next/api/index.ts @@ -14,6 +14,12 @@ * limitations under the License. */ -export { formFieldsApi } from './FormFieldsApi'; -export { formFieldsApiRef } from './ref'; -export type { ScaffolderFormFieldsApi, FormField } from './types'; +import { createApiRef } from '@backstage/frontend-plugin-api'; +import { ScaffolderFormFieldsApi } from './types'; + +export { type FormField, type ScaffolderFormFieldsApi } from './types'; + +/** @alpha */ +export const formFieldsApiRef = createApiRef({ + id: 'plugin.scaffolder.form-fields-loader', +}); diff --git a/plugins/scaffolder-react/src/next/api/types.ts b/plugins/scaffolder-react/src/next/api/types.ts index 9e028567f6..e3ab855708 100644 --- a/plugins/scaffolder-react/src/next/api/types.ts +++ b/plugins/scaffolder-react/src/next/api/types.ts @@ -14,17 +14,12 @@ * limitations under the License. */ -import { FormFieldExtensionData } from '../blueprints'; - -/** - * @alpha - * @deprecated This API is no longer necessary and will be removed - */ -export interface ScaffolderFormFieldsApi { - getFormFields(): Promise; -} - /** @alpha */ export interface FormField { readonly $$type: '@backstage/scaffolder/FormField'; } + +/** @alpha */ +export interface ScaffolderFormFieldsApi { + loadFormFields(): Promise; +} diff --git a/plugins/scaffolder-react/src/next/blueprints/FormFieldBlueprint.tsx b/plugins/scaffolder-react/src/next/blueprints/FormFieldBlueprint.tsx index afbf2b501d..35eeb28c17 100644 --- a/plugins/scaffolder-react/src/next/blueprints/FormFieldBlueprint.tsx +++ b/plugins/scaffolder-react/src/next/blueprints/FormFieldBlueprint.tsx @@ -17,7 +17,7 @@ import { createExtensionBlueprint, createExtensionDataRef, } from '@backstage/frontend-plugin-api'; -import { z } from 'zod'; +import { z } from 'zod/v3'; import { OpaqueFormField } from '@internal/scaffolder'; import { FormFieldExtensionData } from './types'; @@ -35,10 +35,7 @@ const formFieldExtensionDataRef = createExtensionDataRef< * */ export const FormFieldBlueprint = createExtensionBlueprint({ kind: 'scaffolder-form-field', - attachTo: [ - { id: 'page:scaffolder', input: 'formFields' }, - { id: 'api:scaffolder/form-fields', input: 'formFields' }, - ], + attachTo: { id: 'api:scaffolder/form-fields', input: 'formFields' }, dataRefs: { formFieldLoader: formFieldExtensionDataRef, }, diff --git a/plugins/scaffolder-react/src/next/blueprints/types.ts b/plugins/scaffolder-react/src/next/blueprints/types.ts index eccf7e4606..4f3de47e5e 100644 --- a/plugins/scaffolder-react/src/next/blueprints/types.ts +++ b/plugins/scaffolder-react/src/next/blueprints/types.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { z } from 'zod'; +import { z } from 'zod/v3'; import { CustomFieldValidator, FieldExtensionComponentProps, diff --git a/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.test.tsx b/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.test.tsx index b5f2afc74d..8c4cdcf6e7 100644 --- a/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.test.tsx +++ b/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.test.tsx @@ -466,6 +466,7 @@ describe('ReviewState', () => { it('should allow custom review name', async () => { const formState = { foo: 'test', + providedAPIs: 'api1,api2', }; const schemas: ParsedTemplateSchema[] = [ @@ -477,7 +478,15 @@ describe('ReviewState', () => { type: 'string', 'ui:backstage': { review: { - name: 'bar', + name: 'Bar', + }, + }, + }, + providedAPIs: { + type: 'string', + 'ui:backstage': { + review: { + name: 'Provided APIs', }, }, }, @@ -495,6 +504,9 @@ describe('ReviewState', () => { expect(queryByRole('row', { name: 'Bar test' })).toBeInTheDocument(); expect(queryByRole('row', { name: 'Foo test' })).not.toBeInTheDocument(); + expect( + queryByRole('row', { name: 'Provided APIs api1,api2' }), + ).toBeInTheDocument(); }); it('should handle options in multiple schemas', async () => { diff --git a/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.tsx b/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.tsx index b5b72dcc98..2588d25cab 100644 --- a/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.tsx +++ b/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.tsx @@ -43,7 +43,7 @@ function processSchema( const name = definitionInSchema?.['ui:backstage']?.review?.name ?? definitionInSchema?.title ?? - key; + formatKey(key); if (definitionInSchema) { const backstageReviewOptions = definitionInSchema['ui:backstage']?.review; @@ -96,12 +96,14 @@ export const ReviewState = (props: ReviewStateProps) => { const schema = findSchemaForKey(key, props.schemas, props.formState); return schema ? processSchema(key, value, schema, props.formState) - : [[key, value]]; + : [[formatKey(key), value]]; }) .filter(prop => prop.length > 0), ); - const options = { - titleFormat: formatKey, - }; - return ; + return ( + key }} + /> + ); }; diff --git a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx index b0746e0672..6a542396de 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx @@ -514,11 +514,12 @@ describe('Stepper', () => { const mockFormData = { firstName: 'John' }; - Object.defineProperty(window, 'location', { - value: { - search: `?formData=${JSON.stringify(mockFormData)}`, - }, - }); + // Use history.replaceState to set the query string (jsdom 27 doesn't allow redefining window.location) + window.history.replaceState( + {}, + '', + `?formData=${JSON.stringify(mockFormData)}`, + ); const { getByRole } = await renderInTestApp( diff --git a/plugins/scaffolder-react/src/next/components/TaskLogStream/TaskLogStream.test.tsx b/plugins/scaffolder-react/src/next/components/TaskLogStream/TaskLogStream.test.tsx index 4466abdbd9..f2063068f1 100644 --- a/plugins/scaffolder-react/src/next/components/TaskLogStream/TaskLogStream.test.tsx +++ b/plugins/scaffolder-react/src/next/components/TaskLogStream/TaskLogStream.test.tsx @@ -15,6 +15,7 @@ */ import { renderInTestApp } from '@backstage/test-utils'; import { ReactNode } from 'react'; +import userEvent from '@testing-library/user-event'; import { TaskLogStream } from './TaskLogStream'; // The inside needs mocking to render in jsdom @@ -25,6 +26,22 @@ jest.mock('react-virtualized-auto-sizer', () => ({ }) => <>{props.children({ width: 400, height: 200 })}, })); +beforeAll(() => { + Reflect.defineProperty(window.URL, 'createObjectURL', { + writable: true, + value: jest.fn((_blob: any) => 'blob:mock-url'), + }); + Reflect.defineProperty(window.URL, 'revokeObjectURL', { + writable: true, + value: jest.fn(), + }); +}); + +afterAll(() => { + Reflect.deleteProperty(window.URL, 'createObjectURL'); + Reflect.deleteProperty(window.URL, 'revokeObjectURL'); +}); + describe('TaskLogStream', () => { it('should render a log stream with the correct log lines', async () => { const logs = { step: ['line 1', 'line 2'], step2: ['line 3'] }; @@ -47,4 +64,99 @@ describe('TaskLogStream', () => { await expect(findAllByRole('row')).resolves.toHaveLength(2); }); + + it('should render download button', async () => { + const logs = { step: ['line 1', 'line 2'], step2: ['line 3'] }; + + const { getByRole } = await renderInTestApp(); + + const downloadButton = getByRole('button', { name: /download/i }); + expect(downloadButton).toBeInTheDocument(); + }); + + it('should download logs when download button is clicked', async () => { + const logs = { + step1: ['line 1', 'line 2'], + step2: ['line 3', 'line 4'], + }; + + const { getByRole } = await renderInTestApp(); + + // Mock only the anchor element creation + const mockAnchor = document.createElement('a'); + const clickSpy = jest.spyOn(mockAnchor, 'click'); + const removeSpy = jest.spyOn(mockAnchor, 'remove'); + + const originalCreateElement = document.createElement.bind(document); + const createElementSpy = jest + .spyOn(document, 'createElement') + .mockImplementation((tagName: string) => { + if (tagName === 'a') { + return mockAnchor; + } + return originalCreateElement(tagName); + }); + + const downloadButton = getByRole('button', { name: /download/i }); + await userEvent.click(downloadButton); + + // Verify file download was triggered + expect(createElementSpy).toHaveBeenCalledWith('a'); + expect(clickSpy).toHaveBeenCalled(); + expect(removeSpy).toHaveBeenCalled(); + + // Verify the download filename contains .log extension + expect(mockAnchor.download).toMatch(/\.log$/); + + // Verify href was set + expect(mockAnchor.href).toContain('blob:'); + + // Restore mocks + createElementSpy.mockRestore(); + }); + + it('should create blob with correct log content when downloading', async () => { + const logs = { + step1: ['line 1', 'line 2'], + step2: ['line 3'], + }; + + const { getByRole } = await renderInTestApp(); + + let capturedBlob: Blob | null = null; + const createObjectURLSpy = jest + .spyOn(URL, 'createObjectURL') + .mockImplementation((blob: any) => { + capturedBlob = blob; + return 'blob:mock-url'; + }); + + const mockAnchor = document.createElement('a'); + const clickSpy = jest.spyOn(mockAnchor, 'click'); + + const originalCreateElement = document.createElement.bind(document); + const createElementSpy = jest + .spyOn(document, 'createElement') + .mockImplementation((tagName: string) => { + if (tagName === 'a') { + return mockAnchor; + } + return originalCreateElement(tagName); + }); + + const downloadButton = getByRole('button', { name: /download/i }); + await userEvent.click(downloadButton); + + // Verify file download was triggered + expect(createElementSpy).toHaveBeenCalledWith('a'); + expect(clickSpy).toHaveBeenCalled(); + + // Verify blob was created with correct content + expect(capturedBlob).toBeInstanceOf(Blob); + expect(capturedBlob!?.type).toBe('text/plain'); + + // Restore mocks + createElementSpy.mockRestore(); + createObjectURLSpy.mockRestore(); + }); }); diff --git a/plugins/scaffolder-react/src/next/components/TaskLogStream/TaskLogStream.tsx b/plugins/scaffolder-react/src/next/components/TaskLogStream/TaskLogStream.tsx index b88d96510e..e8396167fc 100644 --- a/plugins/scaffolder-react/src/next/components/TaskLogStream/TaskLogStream.tsx +++ b/plugins/scaffolder-react/src/next/components/TaskLogStream/TaskLogStream.tsx @@ -15,6 +15,7 @@ */ import { LogViewer } from '@backstage/core-components'; import { makeStyles } from '@material-ui/core/styles'; +import { useDownloadLogs } from '../../hooks/useDownloadLogs'; const useStyles = makeStyles({ root: { @@ -32,9 +33,13 @@ const useStyles = makeStyles({ */ export const TaskLogStream = (props: { logs: { [k: string]: string[] } }) => { const styles = useStyles(); + + const onDownloadLogs = useDownloadLogs(props.logs); + return (
    l.join('\n')) .filter(Boolean) diff --git a/plugins/scaffolder-react/src/next/components/TemplateCategoryPicker/TemplateCategoryPicker.test.tsx b/plugins/scaffolder-react/src/next/components/TemplateCategoryPicker/TemplateCategoryPicker.test.tsx index cfbe8a40b4..9489321854 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateCategoryPicker/TemplateCategoryPicker.test.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateCategoryPicker/TemplateCategoryPicker.test.tsx @@ -17,6 +17,7 @@ import { useEntityTypeFilter } from '@backstage/plugin-catalog-react'; import { TemplateCategoryPicker } from './TemplateCategoryPicker'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { mockApis } from '@backstage/frontend-test-utils'; import { alertApiRef } from '@backstage/core-plugin-api'; import { fireEvent } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; @@ -26,10 +27,10 @@ jest.mock('@backstage/plugin-catalog-react', () => ({ })); describe('TemplateCategoryPicker', () => { - const mockAlertApi = { post: jest.fn() }; + const mockAlertApi = mockApis.alert(); beforeEach(() => { - mockAlertApi.post.mockClear(); + mockAlertApi.clearAlerts(); }); it('should post the error to errorApi if an errors is returned', async () => { @@ -37,13 +38,16 @@ describe('TemplateCategoryPicker', () => { error: new Error('something broked'), }); + mockAlertApi.clearAlerts(); + await renderInTestApp( , ); - expect(mockAlertApi.post).toHaveBeenCalledWith({ + expect(mockAlertApi.getAlerts().length).toBeGreaterThanOrEqual(1); + expect(mockAlertApi.getAlerts()[0]).toMatchObject({ message: expect.stringContaining('something broked'), severity: 'error', }); diff --git a/plugins/scaffolder-react/src/next/components/Workflow/Workflow.test.tsx b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.test.tsx index b3dc78dba5..96ca2217b9 100644 --- a/plugins/scaffolder-react/src/next/components/Workflow/Workflow.test.tsx +++ b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.test.tsx @@ -37,6 +37,9 @@ const scaffolderApiMock: jest.Mocked = { listActions: jest.fn(), listTasks: jest.fn(), autocomplete: jest.fn(), + retry: jest.fn(), + listTemplatingExtensions: jest.fn(), + dryRun: jest.fn(), }; const catalogApi = catalogApiMock.mock(); diff --git a/plugins/scaffolder-react/src/next/extensions/createScaffolderFormDecorator.ts b/plugins/scaffolder-react/src/next/extensions/createScaffolderFormDecorator.ts index 0064ae0397..51963072b4 100644 --- a/plugins/scaffolder-react/src/next/extensions/createScaffolderFormDecorator.ts +++ b/plugins/scaffolder-react/src/next/extensions/createScaffolderFormDecorator.ts @@ -16,7 +16,7 @@ import { AnyApiRef } from '@backstage/core-plugin-api'; import { JsonObject, JsonValue } from '@backstage/types'; import { OpaqueFormDecorator } from '@internal/scaffolder'; -import { z } from 'zod'; +import { z } from 'zod/v3'; /** @alpha */ export type ScaffolderFormDecoratorContext< diff --git a/plugins/scaffolder-react/src/next/hooks/useDownloadLogs.ts b/plugins/scaffolder-react/src/next/hooks/useDownloadLogs.ts new file mode 100644 index 0000000000..1399e2f285 --- /dev/null +++ b/plugins/scaffolder-react/src/next/hooks/useDownloadLogs.ts @@ -0,0 +1,39 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useCallback } from 'react'; +import { useParams } from 'react-router'; + +export const useDownloadLogs = (logs: { [k: string]: string[] }) => { + const { taskId } = useParams<{ taskId: string }>(); + return useCallback(() => { + const element = document.createElement('a'); + const file = new Blob( + [ + Object.values(logs) + .map(l => l.join('\n')) + .filter(Boolean) + .join('\n'), + ], + { type: 'text/plain' }, + ); + element.href = URL.createObjectURL(file); + element.download = `${taskId}.log`; + element.click(); + URL.revokeObjectURL(element.href); + element.remove(); + }, [logs, taskId]); +}; diff --git a/plugins/scaffolder-react/src/next/hooks/useFilteredSchemaProperties.test.tsx b/plugins/scaffolder-react/src/next/hooks/useFilteredSchemaProperties.test.tsx index 78c2064c4b..293ef54bc2 100644 --- a/plugins/scaffolder-react/src/next/hooks/useFilteredSchemaProperties.test.tsx +++ b/plugins/scaffolder-react/src/next/hooks/useFilteredSchemaProperties.test.tsx @@ -18,11 +18,10 @@ import { renderHook } from '@testing-library/react'; import { useFilteredSchemaProperties } from './useFilteredSchemaProperties'; import { TemplateParameterSchema } from '../../types'; import { TestApiProvider } from '@backstage/test-utils'; +import { mockApis } from '@backstage/frontend-test-utils'; import { featureFlagsApiRef } from '@backstage/core-plugin-api'; -const mockFeatureFlagApi = { - isActive: jest.fn(), -}; +const mockFeatureFlagApi = mockApis.featureFlags.mock(); describe('useFilteredSchemaProperties', () => { it('should return the same manifest if no feature flag is set', () => { diff --git a/plugins/scaffolder-react/src/next/hooks/useTemplateSchema.test.tsx b/plugins/scaffolder-react/src/next/hooks/useTemplateSchema.test.tsx index dff5500b51..b51e3ff147 100644 --- a/plugins/scaffolder-react/src/next/hooks/useTemplateSchema.test.tsx +++ b/plugins/scaffolder-react/src/next/hooks/useTemplateSchema.test.tsx @@ -15,9 +15,9 @@ */ import { useTemplateSchema } from './useTemplateSchema'; import { renderHook } from '@testing-library/react'; -import { TestApiProvider } from '@backstage/test-utils'; +import { TestApiProvider } from '@backstage/frontend-test-utils'; +import { mockApis } from '@backstage/frontend-test-utils'; import { PropsWithChildren } from 'react'; -import { featureFlagsApiRef } from '@backstage/core-plugin-api'; import { TemplateParameterSchema } from '../../types'; describe('useTemplateSchema', () => { @@ -49,11 +49,13 @@ describe('useTemplateSchema', () => { ], }; + const mockFeatureFlagsApi = mockApis.featureFlags.mock({ + isActive: jest.fn(() => false), + }); + const { result } = renderHook(() => useTemplateSchema(manifest), { wrapper: ({ children }: PropsWithChildren<{}>) => ( - false }]]} - > + {children} ), @@ -119,7 +121,9 @@ describe('useTemplateSchema', () => { const { result } = renderHook(() => useTemplateSchema(manifest), { wrapper: ({ children }: PropsWithChildren<{}>) => ( false }]]} + apis={[ + mockApis.featureFlags.mock({ isActive: jest.fn(() => false) }), + ]} > {children} @@ -163,7 +167,9 @@ describe('useTemplateSchema', () => { const { result } = renderHook(() => useTemplateSchema(manifest), { wrapper: ({ children }: PropsWithChildren<{}>) => ( true }]]} + apis={[ + mockApis.featureFlags.mock({ isActive: jest.fn(() => true) }), + ]} > {children} @@ -214,7 +220,9 @@ describe('useTemplateSchema', () => { const { result } = renderHook(() => useTemplateSchema(manifest), { wrapper: ({ children }: PropsWithChildren<{}>) => ( false }]]} + apis={[ + mockApis.featureFlags.mock({ isActive: jest.fn(() => false) }), + ]} > {children} @@ -252,7 +260,9 @@ describe('useTemplateSchema', () => { const { result } = renderHook(() => useTemplateSchema(manifest), { wrapper: ({ children }: PropsWithChildren<{}>) => ( false }]]} + apis={[ + mockApis.featureFlags.mock({ isActive: jest.fn(() => false) }), + ]} > {children} @@ -356,7 +366,9 @@ describe('useTemplateSchema', () => { const { result } = renderHook(() => useTemplateSchema(manifest), { wrapper: ({ children }: PropsWithChildren<{}>) => ( false }]]} + apis={[ + mockApis.featureFlags.mock({ isActive: jest.fn(() => false) }), + ]} > {children} diff --git a/plugins/scaffolder-react/src/testUtils.ts b/plugins/scaffolder-react/src/testUtils.ts new file mode 100644 index 0000000000..bc94b5447e --- /dev/null +++ b/plugins/scaffolder-react/src/testUtils.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Frontend test helpers for the Scaffolder plugin. + * + * @packageDocumentation + */ + +export { scaffolderApiMock } from './testUtils/scaffolderApiMock'; diff --git a/plugins/scaffolder-react/src/testUtils/scaffolderApiMock.test.ts b/plugins/scaffolder-react/src/testUtils/scaffolderApiMock.test.ts new file mode 100644 index 0000000000..4d2ae09d35 --- /dev/null +++ b/plugins/scaffolder-react/src/testUtils/scaffolderApiMock.test.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 { scaffolderApiMock } from './scaffolderApiMock'; + +describe('scaffolderApiMock', () => { + it('creates a mock with all methods as jest.fn()', () => { + const mock = scaffolderApiMock.mock(); + + expect(mock.getTask).toHaveBeenCalledTimes(0); + expect(mock.scaffold).toHaveBeenCalledTimes(0); + expect(mock.listActions).toHaveBeenCalledTimes(0); + }); + + it('supports overriding individual methods', async () => { + const mock = scaffolderApiMock.mock({ + getTask: jest.fn().mockResolvedValue({ id: 'task-1' }), + }); + + await expect(mock.getTask('task-1')).resolves.toEqual( + expect.objectContaining({ id: 'task-1' }), + ); + }); +}); diff --git a/plugins/scaffolder-react/src/testUtils/scaffolderApiMock.ts b/plugins/scaffolder-react/src/testUtils/scaffolderApiMock.ts new file mode 100644 index 0000000000..5103301079 --- /dev/null +++ b/plugins/scaffolder-react/src/testUtils/scaffolderApiMock.ts @@ -0,0 +1,44 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createApiMock } from '@backstage/frontend-test-utils'; +import { scaffolderApiRef } from '../api/ref'; + +/** + * A collection of mock functionality for the scaffolder API. + * + * @public + */ +export namespace scaffolderApiMock { + /** + * Creates a scaffolder API whose methods are mock functions, possibly with + * some of them overloaded by the caller. + */ + export const mock = createApiMock(scaffolderApiRef, () => ({ + getTemplateParameterSchema: jest.fn(), + scaffold: jest.fn(), + getTask: jest.fn(), + cancelTask: jest.fn(), + retry: jest.fn(), + listTasks: jest.fn(), + getIntegrationsList: jest.fn(), + listActions: jest.fn(), + listTemplatingExtensions: jest.fn(), + streamLogs: jest.fn(), + dryRun: jest.fn(), + autocomplete: jest.fn(), + })); +} diff --git a/plugins/scaffolder-react/src/translation.ts b/plugins/scaffolder-react/src/translation.ts index 18da2f58e7..367710f214 100644 --- a/plugins/scaffolder-react/src/translation.ts +++ b/plugins/scaffolder-react/src/translation.ts @@ -15,7 +15,7 @@ */ import { createTranslationRef } from '@backstage/core-plugin-api/alpha'; -/** @alpha */ +/** @public */ export const scaffolderReactTranslationRef = createTranslationRef({ id: 'scaffolder-react', messages: { diff --git a/plugins/scaffolder-react/src/utils.ts b/plugins/scaffolder-react/src/utils.ts index dc24df759d..c3ca898556 100644 --- a/plugins/scaffolder-react/src/utils.ts +++ b/plugins/scaffolder-react/src/utils.ts @@ -16,7 +16,7 @@ import zodToJsonSchema from 'zod-to-json-schema'; import { JSONSchema7 } from 'json-schema'; -import { z } from 'zod'; +import { z } from 'zod/v3'; import { CustomFieldExtensionSchema, FieldExtensionComponentProps, diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index 601064140f..7ccd95a40d 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,427 @@ # @backstage/plugin-scaffolder +## 1.36.2 + +### Patch Changes + +- 297302e: Fixed the NFS custom field explorer so loaded form fields render field options and previews correctly. +- 864a799: Fix the display of the description in `GitlabRepoPicker`: + + - Move `owner.description` helper text outside the `allowedOwners` conditional so it renders for both `Select` and `Autocomplete` modes. + - Update the `Autocomplete` label to use `fields.gitlabRepoPicker.owner.inputTitle` instead of `fields.gitlabRepoPicker.owner.title`. + +- e5af44c: Replaced deprecated `humanizeEntityRef` usage with the Catalog Presentation API. +- 5d8112e: Migrated the actions page to use `@backstage/ui` list and search components. Actions are now presented in a sidebar list with a separate detail panel for the selected action, along with built-in search filtering. The selected action is also reflected in the URL hash, allowing deep-linking to a specific action. +- 4cc9af2: Fixed the layout of the scaffolder plugin in the new frontend system to use the new page layout. +- a7a14b7: Removed custom `IterableDirectoryHandle` and `WritableFileHandle` types in favor of the standard DOM `FileSystemDirectoryHandle` and `FileSystemFileHandle` types, which are now available through the `DOM.AsyncIterable` lib added to the shared TypeScript configuration. +- Updated dependencies + - @backstage/ui@0.14.0 + - @backstage/errors@1.3.0 + - @backstage/catalog-model@1.8.0 + - @backstage/plugin-catalog-react@2.1.2 + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/core-components@0.18.9 + - @backstage/plugin-scaffolder-react@1.20.1 + - @backstage/catalog-client@1.15.0 + - @backstage/plugin-scaffolder-common@2.1.0 + - @backstage/plugin-permission-react@0.5.0 + - @backstage/integration@2.0.1 + - @backstage/core-plugin-api@1.12.5 + - @backstage/integration-react@1.2.17 + - @backstage/plugin-catalog-common@1.1.9 + - @backstage/plugin-techdocs-react@1.3.10 + +## 1.36.2-next.2 + +### Patch Changes + +- e5af44c: Replaced deprecated `humanizeEntityRef` usage with the Catalog Presentation API. +- Updated dependencies + - @backstage/ui@0.14.0-next.2 + - @backstage/errors@1.3.0-next.0 + - @backstage/core-components@0.18.9-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.2 + - @backstage/integration@2.0.1-next.0 + - @backstage/catalog-client@1.14.1-next.0 + - @backstage/catalog-model@1.7.8-next.0 + - @backstage/core-plugin-api@1.12.5-next.2 + - @backstage/frontend-plugin-api@0.16.0-next.2 + - @backstage/plugin-scaffolder-common@2.0.1-next.0 + - @backstage/plugin-scaffolder-react@1.20.1-next.2 + - @backstage/plugin-techdocs-react@1.3.10-next.2 + - @backstage/integration-react@1.2.17-next.1 + - @backstage/plugin-catalog-common@1.1.9-next.0 + - @backstage/plugin-permission-react@0.4.42-next.1 + +## 1.36.2-next.1 + +### Patch Changes + +- 864a799: Fix the display of the description in `GitlabRepoPicker`: + + - Move `owner.description` helper text outside the `allowedOwners` conditional so it renders for both `Select` and `Autocomplete` modes. + - Update the `Autocomplete` label to use `fields.gitlabRepoPicker.owner.inputTitle` instead of `fields.gitlabRepoPicker.owner.title`. + +- a7a14b7: Removed custom `IterableDirectoryHandle` and `WritableFileHandle` types in favor of the standard DOM `FileSystemDirectoryHandle` and `FileSystemFileHandle` types, which are now available through the `DOM.AsyncIterable` lib added to the shared TypeScript configuration. +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-scaffolder-react@1.20.1-next.1 + - @backstage/plugin-techdocs-react@1.3.10-next.1 + +## 1.36.2-next.0 + +### Patch Changes + +- 4cc9af2: Fixed the layout of the scaffolder plugin in the new frontend system to use the new page layout. +- Updated dependencies + - @backstage/ui@0.14.0-next.0 + - @backstage/plugin-scaffolder-react@1.20.1-next.0 + - @backstage/plugin-catalog-react@2.1.1-next.0 + - @backstage/core-components@0.18.9-next.0 + - @backstage/frontend-plugin-api@0.15.2-next.0 + - @backstage/integration-react@1.2.17-next.0 + - @backstage/plugin-techdocs-react@1.3.10-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + - @backstage/catalog-client@1.14.0 + - @backstage/catalog-model@1.7.7 + - @backstage/errors@1.2.7 + - @backstage/integration@2.0.0 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-react@0.4.42-next.0 + - @backstage/plugin-scaffolder-common@2.0.0 + - @backstage/plugin-techdocs-common@0.1.1 + +## 1.36.0 + +### Minor Changes + +- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point. + +### Patch Changes + +- e27bd4e: Removed check for deprecated `bitbucket` integration from `repoPickerValidation` function used by the `RepoUrlPicker`, it now validates the `bitbucketServer` and `bitbucketCloud` integrations instead. +- 538c985: Updated installation documentation to use feature discovery as the default. +- bd5b842: Added a new `ui:autoSelect` option to the EntityPicker field that controls whether an entity is automatically selected when the field loses focus. When set to `false`, the field will remain empty if the user closes it without explicitly selecting an entity, preventing unintentional selections. Defaults to `true` for backward compatibility. +- 3f36ce1: Updated alpha plugin icons to follow the new frontend icon sizing rules when rendered in plugin and navigation surfaces. +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/catalog-client@1.14.0 + - @backstage/plugin-scaffolder-react@1.20.0 + - @backstage/integration@2.0.0 + - @backstage/plugin-permission-react@0.4.41 + - @backstage/plugin-scaffolder-common@2.0.0 + - @backstage/catalog-model@1.7.7 + - @backstage/integration-react@1.2.16 + - @backstage/plugin-techdocs-react@1.3.9 + +## 1.35.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/catalog-client@1.14.0-next.2 + - @backstage/plugin-catalog-react@2.1.0-next.2 + - @backstage/integration@2.0.0-next.2 + - @backstage/core-components@0.18.8-next.1 + - @backstage/plugin-scaffolder-react@1.20.0-next.2 + - @backstage/plugin-scaffolder-common@2.0.0-next.2 + - @backstage/plugin-techdocs-react@1.3.9-next.1 + +## 1.35.5-next.1 + +### Patch Changes + +- e27bd4e: Removed check for deprecated `bitbucket` integration from `repoPickerValidation` function used by the `RepoUrlPicker`, it now validates the `bitbucketServer` and `bitbucketCloud` integrations instead. +- Updated dependencies + - @backstage/catalog-client@1.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/plugin-scaffolder-react@1.19.8-next.1 + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-scaffolder-common@2.0.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/integration-react@1.2.16-next.1 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-react@0.4.41-next.0 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-react@1.3.9-next.0 + +## 1.35.5-next.0 + +### Patch Changes + +- bd5b842: Added a new `ui:autoSelect` option to the EntityPicker field that controls whether an entity is automatically selected when the field loses focus. When set to `false`, the field will remain empty if the user closes it without explicitly selecting an entity, preventing unintentional selections. Defaults to `true` for backward compatibility. +- ee87720: Added back the `formFieldsApiRef` and `ScaffolderFormFieldsApi` alpha exports that were unintentionally removed. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/integration@1.21.0-next.0 + - @backstage/catalog-client@1.13.1-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.16-next.0 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-react@0.4.41-next.0 + - @backstage/plugin-scaffolder-common@1.7.7-next.0 + - @backstage/plugin-scaffolder-react@1.19.8-next.0 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-react@1.3.9-next.0 + +## 1.35.3 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 4e581a6: Updated the browser tab title on the template wizard page to display the specific template title instead of the generic "Create a new component" text. +- 7feb83b: Adjusted to use the new `@backstage/filter-predicates` types for predicate expressions. +- 018ca87: Added `title` and `icon` to the plugin definition for the new frontend system. +- 2eeca03: Scaffolder form fields in the new frontend system now use a Utility API pattern instead of multiple attachment points. The `FormFieldBlueprint` now uses this new approach, and while form fields created with older versions still work, they will produce a deprecation warning and will stop working in a future release. + + As part of this change, the following alpha exports were removed: + + - `formFieldsApiRef` + - `ScaffolderFormFieldsApi` + +- b9d90a7: Added `@backstage/frontend-test-utils` as a dev dependency for mock API usage in tests. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/integration@1.20.0 + - @backstage/core-components@0.18.7 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/catalog-client@1.13.0 + - @backstage/plugin-scaffolder-react@1.19.7 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-techdocs-react@1.3.8 + - @backstage/integration-react@1.2.15 + - @backstage/plugin-permission-react@0.4.40 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-scaffolder-common@1.7.6 + +## 1.35.3-next.2 + +### Patch Changes + +- 7feb83b: Adjusted to use the new `@backstage/filter-predicates` types for predicate expressions. +- b9d90a7: Added `@backstage/frontend-test-utils` as a dev dependency for mock API usage in tests. +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/integration@1.20.0-next.2 + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/catalog-client@1.12.2-next.0 + - @backstage/plugin-scaffolder-react@1.19.7-next.2 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/integration-react@1.2.15-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-permission-react@0.4.40-next.1 + - @backstage/plugin-techdocs-react@1.3.8-next.1 + +## 1.35.3-next.1 + +### Patch Changes + +- 4e581a6: Updated the browser tab title on the template wizard page to display the specific template title instead of the generic "Create a new component" text. +- Updated dependencies + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/integration@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/plugin-scaffolder-react@1.19.7-next.1 + - @backstage/core-components@0.18.7-next.1 + - @backstage/plugin-techdocs-react@1.3.8-next.0 + - @backstage/integration-react@1.2.15-next.1 + - @backstage/plugin-scaffolder-common@1.7.6-next.1 + +## 1.35.2-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 2eeca03: Scaffolder form fields in the new frontend system now use a Utility API pattern instead of multiple attachment points. The `FormFieldBlueprint` now uses this new approach, and while form fields created with older versions still work, they will produce a deprecation warning and will stop working in a future release. + + As part of this change, the following alpha exports were removed: + + - `formFieldsApiRef` + - `ScaffolderFormFieldsApi` + +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/plugin-scaffolder-react@1.19.6-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/plugin-techdocs-react@1.3.8-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.15-next.0 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.8-next.0 + - @backstage/plugin-permission-react@0.4.40-next.0 + - @backstage/plugin-scaffolder-common@1.7.6-next.0 + - @backstage/plugin-techdocs-common@0.1.1 + +## 1.35.1 + +### Patch Changes + +- 9d75495: Fixed bug in RepoUrlPickerComponent component where repository names were not being autocompleted. +- 872eb91: Upgrade `zod-to-json-schema` to latest version +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + - @backstage/integration@1.19.2 + - @backstage/plugin-scaffolder-react@1.19.5 + - @backstage/integration-react@1.2.14 + - @backstage/plugin-catalog-react@1.21.5 + - @backstage/plugin-scaffolder-common@1.7.5 + - @backstage/plugin-techdocs-react@1.3.7 + +## 1.35.1-next.2 + +### Patch Changes + +- 9d75495: Fixed bug in RepoUrlPickerComponent component where repository names were not being autocompleted. + +## 1.35.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.1 + - @backstage/plugin-scaffolder-react@1.19.5-next.1 + - @backstage/plugin-techdocs-react@1.3.7-next.0 + +## 1.35.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-scaffolder-common@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.0 + - @backstage/plugin-scaffolder-react@1.19.5-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## 1.35.0 + +### Minor Changes + +- dab3d3f: Added field extension `RepoOwnerPicker` for retrieving GitHub repository owners. + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/plugin-scaffolder-react@1.19.4 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-permission-react@0.4.39 + - @backstage/plugin-scaffolder-common@1.7.4 + - @backstage/plugin-techdocs-react@1.3.6 + +## 1.34.4-next.1 + +### Patch Changes + +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-scaffolder-react@1.19.4-next.2 + - @backstage/integration@1.18.3-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + +## 1.34.4-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/plugin-scaffolder-react@1.19.4-next.0 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-techdocs-common@0.1.1 + +## 1.34.3 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/integration@1.18.2 + - @backstage/plugin-scaffolder-react@1.19.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-techdocs-react@1.3.5 + - @backstage/catalog-client@1.12.1 + - @backstage/integration-react@1.2.12 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-scaffolder-common@1.7.3 + ## 1.34.3-next.0 ### Patch Changes diff --git a/plugins/scaffolder/README.md b/plugins/scaffolder/README.md index 0a79d8afc0..9c9ca15dfa 100644 --- a/plugins/scaffolder/README.md +++ b/plugins/scaffolder/README.md @@ -15,13 +15,72 @@ To check if you already have the package, look under `@backstage/plugin-scaffolder`. The instructions below walk through restoring the plugin, if you previously removed it. -### Install the package - ```bash # From your Backstage root directory yarn --cwd packages/app add @backstage/plugin-scaffolder ``` +Once installed, the plugin is automatically available in your app through the default feature discovery. For more details and alternative installation methods, see [installing plugins](https://backstage.io/docs/frontend-system/building-apps/installing-plugins). + +### Troubleshooting + +If you encounter [issues with early closure of the `EventStream`](https://github.com/backstage/backstage/issues/5535) +used to auto-update logs during task execution, you can work around them by enabling +long polling. To do so, update your `packages/app/src/apis.ts` file to register a +`ScaffolderClient` with `useLongPollingLogs` set to `true`. By default, it is `false`. + +```typescript +import { + AnyApiFactory, + createApiFactory, + discoveryApiRef, + fetchApiRef, + identityApiRef, +} from '@backstage/core-plugin-api'; +import { scmIntegrationsApiRef } from '@backstage/integration-react'; +import { + scaffolderApiRef, + ScaffolderClient, +} from '@backstage/plugin-scaffolder'; + +export const apis: AnyApiFactory[] = [ + createApiFactory({ + api: scaffolderApiRef, + deps: { + discoveryApi: discoveryApiRef, + identityApi: identityApiRef, + scmIntegrationsApi: scmIntegrationsApiRef, + fetchApi: fetchApiRef, + }, + factory: ({ scmIntegrationsApi, discoveryApi, identityApi, fetchApi }) => + new ScaffolderClient({ + discoveryApi, + identityApi, + scmIntegrationsApi, + fetchApi, + useLongPollingLogs: true, + }), + }), + // ... other factories +]; +``` + +This replaces the default implementation of the `scaffolderApiRef`. + +### Local development + +When you develop a new template, action or new ``, then we recommend +to launch the plugin locally using the `createDevApp` of the `./dev/index.tsx` file for testing/Debugging purposes + +To play with it, open a terminal and run the command: `yarn start` within the `./plugins/scaffolder` folder + +**NOTE:** Don't forget to open a second terminal, start your Backstage backend there, +and configure the template locations that you want to test. + +## Old Frontend System + +If your Backstage app uses the old frontend system, you need to manually wire the plugin into your app as outlined in this section. If you are on the new frontend system, you can skip this. + ### Add the plugin to your `packages/app` Add the root page that the scaffolder plugin provides to your app. You can @@ -78,57 +137,6 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( ``` -### Troubleshooting - -If you encounter the [issue of closing EventStream](https://github.com/backstage/backstage/issues/5535) -which auto-updates logs during task execution, you can enable long polling. To do so, -update your `packages/app/src/apis.ts` file to register a `ScaffolderClient` with the -`useLongPollingLogs` set to `true`. By default, it is `false`. - -```typescript -import { - createApiFactory, - discoveryApiRef, - fetchApiRef, - identityApiRef, -} from '@backstage/core-plugin-api'; -import { - scaffolderApiRef, - ScaffolderClient, -} from '@backstage/plugin-scaffolder'; - -export const apis: AnyApiFactory[] = [ - createApiFactory({ - api: scaffolderApiRef, - deps: { - discoveryApi: discoveryApiRef, - identityApi: identityApiRef, - scmIntegrationsApi: scmIntegrationsApiRef, - fetchApi: fetchApiRef, - }, - factory: ({ scmIntegrationsApi, discoveryApi, identityApi, fetchApi }) => - new ScaffolderClient({ - discoveryApi, - identityApi, - scmIntegrationsApi, - fetchApi, - useLongPollingLogs: true, - }), - }), - // ... other factories -``` - -This replaces the default implementation of the `scaffolderApiRef`. - -### Local development - -When you develop a new template, action or new ``, then we recommend -to launch the plugin locally using the `createDevApp` of the `./dev/index.tsx` file for testing/Debugging purposes - -To play with it, open a terminal and run the command: `yarn start` within the `./plugins/scaffolder` folder - -**NOTE:** Don't forget to open a second terminal and to launch the backend or [backend-next](../../docs/backend-system/index.md) there, using `yarn start` and to specify the locations of the templates to play with ! - ## Links - [scaffolder-backend](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend) diff --git a/plugins/scaffolder/knip-report.md b/plugins/scaffolder/knip-report.md index 38ae11af79..8fdea711bc 100644 --- a/plugins/scaffolder/knip-report.md +++ b/plugins/scaffolder/knip-report.md @@ -2,16 +2,17 @@ ## Unused dependencies (4) -| Name | Location | Severity | -| :------------------ | :----------- | :------- | -| json-schema-library | plugins/scaffolder/package.json | error | -| @rjsf/material-ui | plugins/scaffolder/package.json | error | -| react-resizable | plugins/scaffolder/package.json | error | -| git-url-parse | plugins/scaffolder/package.json | error | +| Name | Location | Severity | +| :------------------ | :----------------- | :------- | +| react-resizable | package.json:101:6 | error | +| json-schema-library | package.json:96:6 | error | +| @rjsf/material-ui | package.json:87:6 | error | +| git-url-parse | package.json:92:6 | error | -## Unused devDependencies (1) +## Unused devDependencies (2) -| Name | Location | Severity | -| :--------------------- | :----------- | :------- | -| @types/react-resizable | plugins/scaffolder/package.json | error | +| Name | Location | Severity | +| :------------------------- | :----------------- | :------- | +| @backstage/plugin-techdocs | package.json:116:6 | error | +| @types/react-resizable | package.json:125:6 | error | diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index a332ac1950..d377d93e70 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "1.34.3-next.0", + "version": "1.36.2", "description": "The Backstage plugin that helps you create new things", "backstage": { "role": "frontend-plugin", @@ -60,7 +60,6 @@ "dependencies": { "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", - "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", @@ -75,6 +74,7 @@ "@backstage/plugin-techdocs-common": "workspace:^", "@backstage/plugin-techdocs-react": "workspace:^", "@backstage/types": "workspace:^", + "@backstage/ui": "workspace:^", "@codemirror/language": "^6.0.0", "@codemirror/legacy-modes": "^6.1.0", "@codemirror/view": "^6.0.0", @@ -82,10 +82,11 @@ "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", "@react-hookz/web": "^24.0.0", - "@rjsf/core": "5.23.2", - "@rjsf/material-ui": "5.23.2", - "@rjsf/utils": "5.23.2", - "@rjsf/validator-ajv8": "5.23.2", + "@remixicon/react": "^4.6.0", + "@rjsf/core": "5.24.13", + "@rjsf/material-ui": "5.24.13", + "@rjsf/utils": "5.24.13", + "@rjsf/validator-ajv8": "5.24.13", "@uiw/react-codemirror": "^4.9.3", "classnames": "^2.2.6", "git-url-parse": "^15.0.0", @@ -102,13 +103,14 @@ "react-use": "^17.2.4", "react-window": "^1.8.10", "yaml": "^2.0.0", - "zod": "^3.22.4", - "zod-to-json-schema": "^3.20.4" + "zod": "^3.25.76 || ^4.0.0", + "zod-to-json-schema": "^3.25.1" }, "devDependencies": { "@backstage/cli": "workspace:^", "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", + "@backstage/frontend-test-utils": "workspace:^", "@backstage/plugin-catalog": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", "@backstage/plugin-techdocs": "workspace:^", @@ -124,14 +126,14 @@ "@types/react-window": "^1.8.8", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0", + "react-router-dom": "^6.30.2", "swr": "^2.0.0" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/plugins/scaffolder/report-alpha.api.md b/plugins/scaffolder/report-alpha.api.md index b6c5af9f17..10066f3fd3 100644 --- a/plugins/scaffolder/report-alpha.api.md +++ b/plugins/scaffolder/report-alpha.api.md @@ -10,17 +10,18 @@ import { ApiRef } from '@backstage/frontend-plugin-api'; import { ComponentType } from 'react'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { Entity } from '@backstage/catalog-model'; -import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; -import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; +import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { FieldExtensionOptions } from '@backstage/plugin-scaffolder-react'; +import { FilterPredicate } from '@backstage/filter-predicates'; import { FormField } from '@backstage/plugin-scaffolder-react/alpha'; import { formFieldsApiRef } from '@backstage/plugin-scaffolder-react/alpha'; import type { FormProps as FormProps_2 } from '@rjsf/core'; import { FormProps as FormProps_3 } from '@backstage/plugin-scaffolder-react'; -import { IconComponent } from '@backstage/core-plugin-api'; +import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { IconLinkVerticalProps } from '@backstage/core-components'; import { JSX as JSX_2 } from 'react'; import { LayoutOptions } from '@backstage/plugin-scaffolder-react'; @@ -28,13 +29,14 @@ import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; import { PathParams } from '@backstage/core-plugin-api'; import { ReviewStepProps } from '@backstage/plugin-scaffolder-react'; -import { RouteRef } from '@backstage/frontend-plugin-api'; +import { RouteRef } from '@backstage/core-plugin-api'; +import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api'; import { ScaffolderFormDecorator } from '@backstage/plugin-scaffolder-react/alpha'; import { ScaffolderFormFieldsApi } from '@backstage/plugin-scaffolder-react/alpha'; -import { SubRouteRef } from '@backstage/frontend-plugin-api'; +import { SubRouteRef } from '@backstage/core-plugin-api'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { TemplateGroupFilter } from '@backstage/plugin-scaffolder-react'; -import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) const _default: OverridableFrontendPlugin< @@ -50,12 +52,15 @@ const _default: OverridableFrontendPlugin< templatingExtensions: SubRouteRef; }, { - registerComponent: ExternalRouteRef; - viewTechDoc: ExternalRouteRef<{ - name: string; - kind: string; - namespace: string; - }>; + registerComponent: ExternalRouteRef; + viewTechDoc: ExternalRouteRef< + { + name: string; + kind: string; + namespace: string; + }, + true + >; }, { 'api:scaffolder': OverridableExtensionDefinition<{ @@ -87,6 +92,7 @@ const _default: OverridableFrontendPlugin< { singleton: false; optional: false; + internal: false; } >; }; @@ -114,6 +120,7 @@ const _default: OverridableFrontendPlugin< { singleton: false; optional: false; + internal: false; } >; }; @@ -133,12 +140,12 @@ const _default: OverridableFrontendPlugin< config: { label: string | undefined; title: string | undefined; - filter: EntityPredicate | undefined; + filter: FilterPredicate | undefined; }; configInput: { - filter?: EntityPredicate | undefined; label?: string | undefined; title?: string | undefined; + filter?: FilterPredicate | undefined; }; output: | ExtensionDataRef< @@ -163,19 +170,23 @@ const _default: OverridableFrontendPlugin< inputs: {}; params: { useProps: () => Omit; - filter?: EntityPredicate | ((entity: Entity) => boolean); + filter?: FilterPredicate | ((entity: Entity) => boolean); }; }>; 'nav-item:scaffolder': OverridableExtensionDefinition<{ kind: 'nav-item'; name: undefined; - config: {}; - configInput: {}; + config: { + title: string | undefined; + }; + configInput: { + title?: string | undefined; + }; output: ExtensionDataRef< { title: string; icon: IconComponent; - routeRef: RouteRef; + routeRef: RouteRef_2; }, 'core.nav-item.target', {} @@ -184,27 +195,73 @@ const _default: OverridableFrontendPlugin< params: { title: string; icon: IconComponent; - routeRef: RouteRef; + routeRef: RouteRef_2; }; }>; 'page:scaffolder': OverridableExtensionDefinition<{ config: { path: string | undefined; + title: string | undefined; }; configInput: { path?: string | undefined; + title?: string | undefined; }; output: | ExtensionDataRef + | ExtensionDataRef< + RouteRef_2, + 'core.routing.ref', + { + optional: true; + } + > | ExtensionDataRef | ExtensionDataRef< - RouteRef, - 'core.routing.ref', + string, + 'core.title', + { + optional: true; + } + > + | ExtensionDataRef< + IconElement, + 'core.icon', { optional: true; } >; inputs: { + pages: ExtensionInput< + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef_2, + 'core.routing.ref', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >, + { + singleton: false; + optional: false; + internal: false; + } + >; formFields: ExtensionInput< ConfigurableExtensionDataRef< () => Promise, @@ -214,16 +271,19 @@ const _default: OverridableFrontendPlugin< { singleton: false; optional: false; + internal: false; } >; }; kind: 'page'; name: undefined; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; - loader: () => Promise; - routeRef?: RouteRef; + title?: string; + icon?: IconElement; + loader?: () => Promise; + routeRef?: RouteRef_2; + noHeader?: boolean; }; }>; 'scaffolder-form-field:scaffolder/entity-name-picker': OverridableExtensionDefinition<{ @@ -346,6 +406,21 @@ const _default: OverridableFrontendPlugin< field: () => Promise; }; }>; + 'scaffolder-form-field:scaffolder/repo-owner-picker': OverridableExtensionDefinition<{ + kind: 'scaffolder-form-field'; + name: 'repo-owner-picker'; + config: {}; + configInput: {}; + output: ExtensionDataRef< + () => Promise, + 'scaffolder.form-field-loader', + {} + >; + inputs: {}; + params: { + field: () => Promise; + }; + }>; 'scaffolder-form-field:scaffolder/repo-url-picker': OverridableExtensionDefinition<{ kind: 'scaffolder-form-field'; name: 'repo-url-picker'; @@ -361,6 +436,196 @@ const _default: OverridableFrontendPlugin< field: () => Promise; }; }>; + 'sub-page:scaffolder/actions': OverridableExtensionDefinition<{ + kind: 'sub-page'; + name: 'actions'; + config: { + path: string | undefined; + title: string | undefined; + }; + configInput: { + path?: string | undefined; + title?: string | undefined; + }; + output: + | ExtensionDataRef + | ExtensionDataRef< + RouteRef_2, + 'core.routing.ref', + { + optional: true; + } + > + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >; + inputs: {}; + params: { + path: string; + title: string; + icon?: IconElement; + loader: () => Promise; + routeRef?: RouteRef_2; + }; + }>; + 'sub-page:scaffolder/editor': OverridableExtensionDefinition<{ + kind: 'sub-page'; + name: 'editor'; + config: { + path: string | undefined; + title: string | undefined; + }; + configInput: { + path?: string | undefined; + title?: string | undefined; + }; + output: + | ExtensionDataRef + | ExtensionDataRef< + RouteRef_2, + 'core.routing.ref', + { + optional: true; + } + > + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >; + inputs: {}; + params: { + path: string; + title: string; + icon?: IconElement; + loader: () => Promise; + routeRef?: RouteRef_2; + }; + }>; + 'sub-page:scaffolder/tasks': OverridableExtensionDefinition<{ + kind: 'sub-page'; + name: 'tasks'; + config: { + path: string | undefined; + title: string | undefined; + }; + configInput: { + path?: string | undefined; + title?: string | undefined; + }; + output: + | ExtensionDataRef + | ExtensionDataRef< + RouteRef_2, + 'core.routing.ref', + { + optional: true; + } + > + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >; + inputs: {}; + params: { + path: string; + title: string; + icon?: IconElement; + loader: () => Promise; + routeRef?: RouteRef_2; + }; + }>; + 'sub-page:scaffolder/templates': OverridableExtensionDefinition<{ + config: { + path: string | undefined; + title: string | undefined; + }; + configInput: { + path?: string | undefined; + title?: string | undefined; + }; + output: + | ExtensionDataRef + | ExtensionDataRef< + RouteRef_2, + 'core.routing.ref', + { + optional: true; + } + > + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >; + inputs: {}; + kind: 'sub-page'; + name: 'templates'; + params: { + path: string; + title: string; + icon?: IconElement; + loader: () => Promise; + routeRef?: RouteRef_2; + }; + }>; + 'sub-page:scaffolder/templating-extensions': OverridableExtensionDefinition<{ + kind: 'sub-page'; + name: 'templating-extensions'; + config: { + path: string | undefined; + title: string | undefined; + }; + configInput: { + path?: string | undefined; + title?: string | undefined; + }; + output: + | ExtensionDataRef + | ExtensionDataRef< + RouteRef_2, + 'core.routing.ref', + { + optional: true; + } + > + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >; + inputs: {}; + params: { + path: string; + title: string; + icon?: IconElement; + loader: () => Promise; + routeRef?: RouteRef_2; + }; + }>; } >; export default _default; @@ -392,6 +657,7 @@ export const formDecoratorsApi: OverridableExtensionDefinition<{ { singleton: false; optional: false; + internal: false; } >; }; @@ -407,7 +673,9 @@ export const formDecoratorsApi: OverridableExtensionDefinition<{ }>; // @alpha (undocumented) -export const formDecoratorsApiRef: ApiRef; +export const formDecoratorsApiRef: ApiRef & { + readonly $$type: '@backstage/ApiRef'; +}; export { formFieldsApiRef }; @@ -449,7 +717,7 @@ export type ScaffolderTemplateFormPreviewerClassKey = | 'textArea' | 'preview'; -// @alpha (undocumented) +// @alpha @deprecated (undocumented) export const scaffolderTranslationRef: TranslationRef< 'scaffolder', { @@ -495,6 +763,8 @@ export const scaffolderTranslationRef: TranslationRef< readonly 'fields.repoUrlPicker.repository.title': 'Repositories Available'; readonly 'fields.repoUrlPicker.repository.description': 'The name of the repository'; readonly 'fields.repoUrlPicker.repository.inputTitle': 'Repository'; + readonly 'fields.repoOwnerPicker.title': 'Owner'; + readonly 'fields.repoOwnerPicker.description': 'The owner of the repository'; readonly 'aboutCard.launchTemplate': 'Launch Template'; readonly 'actionsPage.content.emptyState.title': 'No information to display'; readonly 'actionsPage.content.emptyState.description': 'There are no actions installed or there was an issue communicating with backend.'; @@ -521,8 +791,9 @@ export const scaffolderTranslationRef: TranslationRef< readonly 'ownerListPicker.options.owned': 'Owned'; readonly 'ongoingTask.title': 'Run of'; readonly 'ongoingTask.contextMenu.cancel': 'Cancel'; - readonly 'ongoingTask.contextMenu.startOver': 'Start Over'; readonly 'ongoingTask.contextMenu.retry': 'Retry'; + readonly 'ongoingTask.contextMenu.startOver': 'Start Over'; + readonly 'ongoingTask.contextMenu.moreOptions': 'More options'; readonly 'ongoingTask.contextMenu.hideLogs': 'Hide Logs'; readonly 'ongoingTask.contextMenu.showLogs': 'Show Logs'; readonly 'ongoingTask.contextMenu.hideButtonBar': 'Hide Button Bar'; @@ -539,6 +810,7 @@ export const scaffolderTranslationRef: TranslationRef< readonly 'renderSchema.undefined': 'No schema defined'; readonly 'renderSchema.tableCell.name': 'Name'; readonly 'renderSchema.tableCell.type': 'Type'; + readonly 'renderSchema.tableCell.value': 'Value'; readonly 'renderSchema.tableCell.title': 'Title'; readonly 'renderSchema.tableCell.description': 'Description'; readonly 'templatingExtensions.content.values.title': 'Values'; @@ -552,13 +824,13 @@ export const scaffolderTranslationRef: TranslationRef< readonly 'templatingExtensions.content.filters.examples': 'Examples'; readonly 'templatingExtensions.content.filters.notAvailable': 'There are no template filters defined.'; readonly 'templatingExtensions.content.filters.metadataAbsent': 'Filter metadata unavailable'; - readonly 'templatingExtensions.content.searchFieldPlaceholder': 'Search for an extension'; readonly 'templatingExtensions.content.functions.title': 'Functions'; readonly 'templatingExtensions.content.functions.schema.output': 'Output'; readonly 'templatingExtensions.content.functions.schema.arguments': 'Arguments'; readonly 'templatingExtensions.content.functions.examples': 'Examples'; readonly 'templatingExtensions.content.functions.notAvailable': 'There are no global template functions defined.'; readonly 'templatingExtensions.content.functions.metadataAbsent': 'Function metadata unavailable'; + readonly 'templatingExtensions.content.searchFieldPlaceholder': 'Search for an extension'; readonly 'templatingExtensions.title': 'Templating Extensions'; readonly 'templatingExtensions.subtitle': 'This is the collection of available templating extensions'; readonly 'templatingExtensions.pageTitle': 'Templating Extensions'; @@ -614,6 +886,7 @@ export const scaffolderTranslationRef: TranslationRef< readonly 'templateWizardPage.title': 'Create a new component'; readonly 'templateWizardPage.subtitle': 'Create new software components using standard templates in your organization'; readonly 'templateWizardPage.pageTitle': 'Create a new component'; + readonly 'templateWizardPage.templateWithTitle': 'Create new {{templateTitle}}'; readonly 'templateWizardPage.pageContextMenu.editConfigurationTitle': 'Edit Configuration'; readonly 'templateEditorToolbar.customFieldExplorerTooltip': 'Custom Fields Explorer'; readonly 'templateEditorToolbar.installedActionsDocumentationTooltip': 'Installed Actions Documentation'; diff --git a/plugins/scaffolder/report.api.md b/plugins/scaffolder/report.api.md index 27fa47a184..a0c35e1b5e 100644 --- a/plugins/scaffolder/report.api.md +++ b/plugins/scaffolder/report.api.md @@ -4,7 +4,7 @@ ```ts import { ApiHolder } from '@backstage/core-plugin-api'; -import { ApiRef } from '@backstage/core-plugin-api'; +import { ApiRef } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { ComponentType } from 'react'; import { createScaffolderFieldExtension as createScaffolderFieldExtension_2 } from '@backstage/plugin-scaffolder-react'; @@ -49,7 +49,8 @@ import { TemplateGroupFilter } from '@backstage/plugin-scaffolder-react'; import { TemplateListPageProps } from '@backstage/plugin-scaffolder/alpha'; import { TemplateParameterSchema as TemplateParameterSchema_2 } from '@backstage/plugin-scaffolder-common'; import { TemplateWizardPageProps } from '@backstage/plugin-scaffolder/alpha'; -import { z } from 'zod'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; +import { z } from 'zod/v3'; // @public @deprecated (undocumented) export const createScaffolderFieldExtension: typeof createScaffolderFieldExtension_2; @@ -74,8 +75,10 @@ export const EntityNamePickerFieldExtension: FieldExtensionComponent_2< export const EntityPickerFieldExtension: FieldExtensionComponent_2< string, { + autoSelect?: boolean | undefined; defaultKind?: string | undefined; defaultNamespace?: string | false | undefined; + allowedKinds?: string[] | undefined; catalogFilter?: | Record< string, @@ -94,7 +97,6 @@ export const EntityPickerFieldExtension: FieldExtensionComponent_2< } >[] | undefined; - allowedKinds?: string[] | undefined; allowArbitraryValues?: boolean | undefined; } >; @@ -103,8 +105,10 @@ export const EntityPickerFieldExtension: FieldExtensionComponent_2< export const EntityPickerFieldSchema: FieldSchema_2< string, { + autoSelect?: boolean | undefined; defaultKind?: string | undefined; defaultNamespace?: string | false | undefined; + allowedKinds?: string[] | undefined; catalogFilter?: | Record< string, @@ -123,7 +127,6 @@ export const EntityPickerFieldSchema: FieldSchema_2< } >[] | undefined; - allowedKinds?: string[] | undefined; allowArbitraryValues?: boolean | undefined; } >; @@ -249,8 +252,10 @@ export type MyGroupsPickerUiOptions = NonNullable< export const OwnedEntityPickerFieldExtension: FieldExtensionComponent_2< string, { + autoSelect?: boolean | undefined; defaultKind?: string | undefined; defaultNamespace?: string | false | undefined; + allowedKinds?: string[] | undefined; catalogFilter?: | Record< string, @@ -269,7 +274,6 @@ export const OwnedEntityPickerFieldExtension: FieldExtensionComponent_2< } >[] | undefined; - allowedKinds?: string[] | undefined; allowArbitraryValues?: boolean | undefined; } >; @@ -278,8 +282,10 @@ export const OwnedEntityPickerFieldExtension: FieldExtensionComponent_2< export const OwnedEntityPickerFieldSchema: FieldSchema_2< string, { + autoSelect?: boolean | undefined; defaultKind?: string | undefined; defaultNamespace?: string | false | undefined; + allowedKinds?: string[] | undefined; catalogFilter?: | Record< string, @@ -298,7 +304,6 @@ export const OwnedEntityPickerFieldSchema: FieldSchema_2< } >[] | undefined; - allowedKinds?: string[] | undefined; allowArbitraryValues?: boolean | undefined; } >; @@ -313,6 +318,7 @@ export const OwnerPickerFieldExtension: FieldExtensionComponent_2< string, { defaultNamespace?: string | false | undefined; + allowedKinds?: string[] | undefined; catalogFilter?: | Record< string, @@ -331,7 +337,6 @@ export const OwnerPickerFieldExtension: FieldExtensionComponent_2< } >[] | undefined; - allowedKinds?: string[] | undefined; allowArbitraryValues?: boolean | undefined; } >; @@ -341,6 +346,7 @@ export const OwnerPickerFieldSchema: FieldSchema_2< string, { defaultNamespace?: string | false | undefined; + allowedKinds?: string[] | undefined; catalogFilter?: | Record< string, @@ -359,7 +365,6 @@ export const OwnerPickerFieldSchema: FieldSchema_2< } >[] | undefined; - allowedKinds?: string[] | undefined; allowArbitraryValues?: boolean | undefined; } >; @@ -391,6 +396,30 @@ export const RepoBranchPickerFieldExtension: FieldExtensionComponent_2< } >; +// @public +export const RepoOwnerPickerFieldExtension: FieldExtensionComponent_2< + string, + { + host?: string | undefined; + requestUserCredentials?: + | { + secretsKey: string; + additionalScopes?: + | { + azure?: string[] | undefined; + github?: string[] | undefined; + gitlab?: string[] | undefined; + bitbucket?: string[] | undefined; + gerrit?: string[] | undefined; + gitea?: string[] | undefined; + } + | undefined; + } + | undefined; + excludedOwners?: string[] | undefined; + } +>; + // @public export const repoPickerValidation: ( value: string, @@ -500,7 +529,9 @@ export type RouterProps = { export type ScaffolderApi = ScaffolderApi_2; // @public @deprecated (undocumented) -export const scaffolderApiRef: ApiRef; +export const scaffolderApiRef: ApiRef & { + readonly $$type: '@backstage/ApiRef'; +}; // @public @deprecated export class ScaffolderClient extends ScaffolderClient_2 {} @@ -584,6 +615,194 @@ export type ScaffolderTaskOutput = ScaffolderTaskOutput_3; // @public @deprecated (undocumented) export type ScaffolderTaskStatus = ScaffolderTaskStatus_2; +// @public (undocumented) +export const scaffolderTranslationRef: TranslationRef< + 'scaffolder', + { + readonly 'fields.entityNamePicker.title': 'Name'; + readonly 'fields.entityNamePicker.description': 'Unique name of the component'; + readonly 'fields.entityPicker.title': 'Entity'; + readonly 'fields.entityPicker.description': 'An entity from the catalog'; + readonly 'fields.entityTagsPicker.title': 'Tags'; + readonly 'fields.entityTagsPicker.description': "Add any relevant tags, hit 'Enter' to add new tags. Valid format: [a-z0-9+#] separated by [-], at most 63 characters"; + readonly 'fields.multiEntityPicker.title': 'Entity'; + readonly 'fields.multiEntityPicker.description': 'An entity from the catalog'; + readonly 'fields.myGroupsPicker.title': 'Entity'; + readonly 'fields.myGroupsPicker.description': 'An entity from the catalog'; + readonly 'fields.ownedEntityPicker.title': 'Entity'; + readonly 'fields.ownedEntityPicker.description': 'An entity from the catalog'; + readonly 'fields.ownerPicker.title': 'Owner'; + readonly 'fields.ownerPicker.description': 'The owner of the component'; + readonly 'fields.azureRepoPicker.organization.title': 'Organization'; + readonly 'fields.azureRepoPicker.organization.description': 'The Organization that this repo will belong to'; + readonly 'fields.azureRepoPicker.project.title': 'Project'; + readonly 'fields.azureRepoPicker.project.description': 'The Project that this repo will belong to'; + readonly 'fields.bitbucketRepoPicker.project.title': 'Allowed Projects'; + readonly 'fields.bitbucketRepoPicker.project.description': 'The Project that this repo will belong to'; + readonly 'fields.bitbucketRepoPicker.project.inputTitle': 'Projects'; + readonly 'fields.bitbucketRepoPicker.workspaces.title': 'Allowed Workspaces'; + readonly 'fields.bitbucketRepoPicker.workspaces.description': 'The Workspace that this repo will belong to'; + readonly 'fields.bitbucketRepoPicker.workspaces.inputTitle': 'Workspaces'; + readonly 'fields.gerritRepoPicker.parent.title': 'Parent'; + readonly 'fields.gerritRepoPicker.parent.description': 'The project parent that the repo will belong to'; + readonly 'fields.gerritRepoPicker.owner.title': 'Owner'; + readonly 'fields.gerritRepoPicker.owner.description': 'The owner of the project (optional)'; + readonly 'fields.giteaRepoPicker.owner.title': 'Owner Available'; + readonly 'fields.giteaRepoPicker.owner.description': 'Gitea namespace where this repository will belong to. It can be the name of organization, group, subgroup, user, or the project.'; + readonly 'fields.giteaRepoPicker.owner.inputTitle': 'Owner'; + readonly 'fields.githubRepoPicker.owner.title': 'Owner Available'; + readonly 'fields.githubRepoPicker.owner.description': 'The organization, user or project that this repo will belong to'; + readonly 'fields.githubRepoPicker.owner.inputTitle': 'Owner'; + readonly 'fields.gitlabRepoPicker.owner.title': 'Owner Available'; + readonly 'fields.gitlabRepoPicker.owner.description': 'GitLab namespace where this repository will belong to. It can be the name of organization, group, subgroup, user, or the project.'; + readonly 'fields.gitlabRepoPicker.owner.inputTitle': 'Owner'; + readonly 'fields.repoUrlPicker.host.title': 'Host'; + readonly 'fields.repoUrlPicker.host.description': 'The host where the repository will be created'; + readonly 'fields.repoUrlPicker.repository.title': 'Repositories Available'; + readonly 'fields.repoUrlPicker.repository.description': 'The name of the repository'; + readonly 'fields.repoUrlPicker.repository.inputTitle': 'Repository'; + readonly 'fields.repoOwnerPicker.title': 'Owner'; + readonly 'fields.repoOwnerPicker.description': 'The owner of the repository'; + readonly 'aboutCard.launchTemplate': 'Launch Template'; + readonly 'actionsPage.content.emptyState.title': 'No information to display'; + readonly 'actionsPage.content.emptyState.description': 'There are no actions installed or there was an issue communicating with backend.'; + readonly 'actionsPage.content.searchFieldPlaceholder': 'Search for an action'; + readonly 'actionsPage.title': 'Installed actions'; + readonly 'actionsPage.action.input': 'Input'; + readonly 'actionsPage.action.output': 'Output'; + readonly 'actionsPage.action.examples': 'Examples'; + readonly 'actionsPage.subtitle': 'This is the collection of all installed actions'; + readonly 'actionsPage.pageTitle': 'Create a New Component'; + readonly 'listTaskPage.content.emptyState.title': 'No information to display'; + readonly 'listTaskPage.content.emptyState.description': 'There are no tasks or there was an issue communicating with backend.'; + readonly 'listTaskPage.content.tableCell.template': 'Template'; + readonly 'listTaskPage.content.tableCell.status': 'Status'; + readonly 'listTaskPage.content.tableCell.owner': 'Owner'; + readonly 'listTaskPage.content.tableCell.created': 'Created'; + readonly 'listTaskPage.content.tableCell.taskID': 'Task ID'; + readonly 'listTaskPage.content.tableTitle': 'Tasks'; + readonly 'listTaskPage.title': 'List template tasks'; + readonly 'listTaskPage.subtitle': 'All tasks that have been started'; + readonly 'listTaskPage.pageTitle': 'Templates Tasks'; + readonly 'ownerListPicker.title': 'Task Owner'; + readonly 'ownerListPicker.options.all': 'All'; + readonly 'ownerListPicker.options.owned': 'Owned'; + readonly 'ongoingTask.title': 'Run of'; + readonly 'ongoingTask.contextMenu.cancel': 'Cancel'; + readonly 'ongoingTask.contextMenu.retry': 'Retry'; + readonly 'ongoingTask.contextMenu.startOver': 'Start Over'; + readonly 'ongoingTask.contextMenu.moreOptions': 'More options'; + readonly 'ongoingTask.contextMenu.hideLogs': 'Hide Logs'; + readonly 'ongoingTask.contextMenu.showLogs': 'Show Logs'; + readonly 'ongoingTask.contextMenu.hideButtonBar': 'Hide Button Bar'; + readonly 'ongoingTask.contextMenu.showButtonBar': 'Show Button Bar'; + readonly 'ongoingTask.subtitle': 'Task {{taskId}}'; + readonly 'ongoingTask.pageTitle.hasTemplateName': 'Run of {{templateName}}'; + readonly 'ongoingTask.pageTitle.noTemplateName': 'Scaffolder Run'; + readonly 'ongoingTask.cancelButtonTitle': 'Cancel'; + readonly 'ongoingTask.retryButtonTitle': 'Retry'; + readonly 'ongoingTask.startOverButtonTitle': 'Start Over'; + readonly 'ongoingTask.hideLogsButtonTitle': 'Hide Logs'; + readonly 'ongoingTask.showLogsButtonTitle': 'Show Logs'; + readonly 'templateEditorForm.stepper.emptyText': 'There are no spec parameters in the template to preview.'; + readonly 'renderSchema.undefined': 'No schema defined'; + readonly 'renderSchema.tableCell.name': 'Name'; + readonly 'renderSchema.tableCell.type': 'Type'; + readonly 'renderSchema.tableCell.value': 'Value'; + readonly 'renderSchema.tableCell.title': 'Title'; + readonly 'renderSchema.tableCell.description': 'Description'; + readonly 'templatingExtensions.content.values.title': 'Values'; + readonly 'templatingExtensions.content.values.notAvailable': 'There are no global template values defined.'; + readonly 'templatingExtensions.content.emptyState.title': 'No information to display'; + readonly 'templatingExtensions.content.emptyState.description': 'There are no templating extensions available or there was an issue communicating with the backend.'; + readonly 'templatingExtensions.content.filters.title': 'Filters'; + readonly 'templatingExtensions.content.filters.schema.input': 'Input'; + readonly 'templatingExtensions.content.filters.schema.output': 'Output'; + readonly 'templatingExtensions.content.filters.schema.arguments': 'Arguments'; + readonly 'templatingExtensions.content.filters.examples': 'Examples'; + readonly 'templatingExtensions.content.filters.notAvailable': 'There are no template filters defined.'; + readonly 'templatingExtensions.content.filters.metadataAbsent': 'Filter metadata unavailable'; + readonly 'templatingExtensions.content.functions.title': 'Functions'; + readonly 'templatingExtensions.content.functions.schema.output': 'Output'; + readonly 'templatingExtensions.content.functions.schema.arguments': 'Arguments'; + readonly 'templatingExtensions.content.functions.examples': 'Examples'; + readonly 'templatingExtensions.content.functions.notAvailable': 'There are no global template functions defined.'; + readonly 'templatingExtensions.content.functions.metadataAbsent': 'Function metadata unavailable'; + readonly 'templatingExtensions.content.searchFieldPlaceholder': 'Search for an extension'; + readonly 'templatingExtensions.title': 'Templating Extensions'; + readonly 'templatingExtensions.subtitle': 'This is the collection of available templating extensions'; + readonly 'templatingExtensions.pageTitle': 'Templating Extensions'; + readonly 'templateTypePicker.title': 'Categories'; + readonly 'templateIntroPage.title': 'Manage Templates'; + readonly 'templateIntroPage.subtitle': 'Edit, preview, and try out templates, forms, and custom fields'; + readonly 'templateFormPage.title': 'Template Editor'; + readonly 'templateFormPage.subtitle': 'Edit, preview, and try out templates forms'; + readonly 'templateCustomFieldPage.title': 'Custom Field Explorer'; + readonly 'templateCustomFieldPage.subtitle': 'Edit, preview, and try out custom fields'; + readonly 'templateEditorPage.title': 'Template Editor'; + readonly 'templateEditorPage.subtitle': 'Edit, preview, and try out templates and template forms'; + readonly 'templateEditorPage.dryRunResults.title': 'Dry-run results'; + readonly 'templateEditorPage.dryRunResultsList.title': 'Result {{resultId}}'; + readonly 'templateEditorPage.dryRunResultsList.deleteButtonTitle': 'Delete result'; + readonly 'templateEditorPage.dryRunResultsList.downloadButtonTitle': 'Download as .zip'; + readonly 'templateEditorPage.dryRunResultsView.tab.output': 'Output'; + readonly 'templateEditorPage.dryRunResultsView.tab.log': 'Log'; + readonly 'templateEditorPage.dryRunResultsView.tab.files': 'Files'; + readonly 'templateEditorPage.taskStatusStepper.skippedStepTitle': 'Skipped'; + readonly 'templateEditorPage.customFieldExplorer.preview.title': 'Template Spec'; + readonly 'templateEditorPage.customFieldExplorer.fieldForm.title': 'Field Options'; + readonly 'templateEditorPage.customFieldExplorer.fieldForm.applyButtonTitle': 'Apply'; + readonly 'templateEditorPage.customFieldExplorer.selectFieldLabel': 'Choose Custom Field Extension'; + readonly 'templateEditorPage.customFieldExplorer.fieldPreview.title': 'Field Preview'; + readonly 'templateEditorPage.templateEditorBrowser.closeConfirmMessage': 'Are you sure? Unsaved changes will be lost'; + readonly 'templateEditorPage.templateEditorBrowser.saveIconTooltip': 'Save all files'; + readonly 'templateEditorPage.templateEditorBrowser.reloadIconTooltip': 'Reload directory'; + readonly 'templateEditorPage.templateEditorBrowser.closeIconTooltip': 'Close directory'; + readonly 'templateEditorPage.templateEditorIntro.title': 'Get started by choosing one of the options below'; + readonly 'templateEditorPage.templateEditorIntro.loadLocal.title': 'Load Template Directory'; + readonly 'templateEditorPage.templateEditorIntro.loadLocal.description': 'Load a local template directory, allowing you to both edit and try executing your own template.'; + readonly 'templateEditorPage.templateEditorIntro.loadLocal.unsupportedTooltip': 'Only supported in some Chromium-based browsers with the page loaded over HTTPS'; + readonly 'templateEditorPage.templateEditorIntro.createLocal.title': 'Create New Template'; + readonly 'templateEditorPage.templateEditorIntro.createLocal.description': 'Create a local template directory, allowing you to both edit and try executing your own template.'; + readonly 'templateEditorPage.templateEditorIntro.createLocal.unsupportedTooltip': 'Only supported in some Chromium-based browsers with the page loaded over HTTPS'; + readonly 'templateEditorPage.templateEditorIntro.formEditor.title': 'Template Form Playground'; + readonly 'templateEditorPage.templateEditorIntro.formEditor.description': 'Preview and edit a template form, either using a sample template or by loading a template from the catalog.'; + readonly 'templateEditorPage.templateEditorIntro.fieldExplorer.title': 'Custom Field Explorer'; + readonly 'templateEditorPage.templateEditorIntro.fieldExplorer.description': 'View and play around with available installed custom field extensions.'; + readonly 'templateEditorPage.templateEditorTextArea.saveIconTooltip': 'Save file'; + readonly 'templateEditorPage.templateEditorTextArea.refreshIconTooltip': 'Reload file'; + readonly 'templateEditorPage.templateEditorTextArea.emptyStateParagraph': 'Please select an action on the file menu.'; + readonly 'templateEditorPage.templateFormPreviewer.title': 'Load Existing Template'; + readonly 'templateListPage.title': 'Create a new component'; + readonly 'templateListPage.subtitle': 'Create new software components using standard templates in your organization'; + readonly 'templateListPage.pageTitle': 'Create a new component'; + readonly 'templateListPage.templateGroups.defaultTitle': 'Templates'; + readonly 'templateListPage.templateGroups.otherTitle': 'Other Templates'; + readonly 'templateListPage.contentHeader.supportButtonTitle': 'Create new software components using standard templates. Different templates create different kinds of components (services, websites, documentation, ...).'; + readonly 'templateListPage.contentHeader.registerExistingButtonTitle': 'Register Existing Component'; + readonly 'templateListPage.additionalLinksForEntity.viewTechDocsTitle': 'View TechDocs'; + readonly 'templateWizardPage.title': 'Create a new component'; + readonly 'templateWizardPage.subtitle': 'Create new software components using standard templates in your organization'; + readonly 'templateWizardPage.pageTitle': 'Create a new component'; + readonly 'templateWizardPage.templateWithTitle': 'Create new {{templateTitle}}'; + readonly 'templateWizardPage.pageContextMenu.editConfigurationTitle': 'Edit Configuration'; + readonly 'templateEditorToolbar.customFieldExplorerTooltip': 'Custom Fields Explorer'; + readonly 'templateEditorToolbar.installedActionsDocumentationTooltip': 'Installed Actions Documentation'; + readonly 'templateEditorToolbar.templatingExtensionsDocumentationTooltip': 'Templating Extensions Documentation'; + readonly 'templateEditorToolbar.addToCatalogButton': 'Publish'; + readonly 'templateEditorToolbar.addToCatalogDialogTitle': 'Publish changes'; + readonly 'templateEditorToolbar.addToCatalogDialogContent.stepsIntroduction': 'Follow the instructions below to create or update a template:'; + readonly 'templateEditorToolbar.addToCatalogDialogContent.stepsListItems': 'Save the template files in a local directory\nCreate a pull request to a new or existing git repository\nIf the template already exists, the changes will be reflected in the software catalog once the pull request gets merged\nBut if you are creating a new template, follow the documentation linked below to register the new template repository in software catalog'; + readonly 'templateEditorToolbar.addToCatalogDialogActions.documentationUrl': 'https://backstage.io/docs/features/software-templates/adding-templates/'; + readonly 'templateEditorToolbar.addToCatalogDialogActions.documentationButton': 'Go to the documentation'; + readonly 'templateEditorToolbarFileMenu.button': 'File'; + readonly 'templateEditorToolbarFileMenu.options.openDirectory': 'Open template directory'; + readonly 'templateEditorToolbarFileMenu.options.createDirectory': 'Create template directory'; + readonly 'templateEditorToolbarFileMenu.options.closeEditor': 'Close template editor'; + readonly 'templateEditorToolbarTemplatesMenu.button': 'Templates'; + } +>; + // @public @deprecated (undocumented) export type ScaffolderUseTemplateSecrets = ScaffolderUseTemplateSecrets_2; diff --git a/plugins/scaffolder/src/alpha/components/EditorSubPage.test.tsx b/plugins/scaffolder/src/alpha/components/EditorSubPage.test.tsx new file mode 100644 index 0000000000..4dc01095e7 --- /dev/null +++ b/plugins/scaffolder/src/alpha/components/EditorSubPage.test.tsx @@ -0,0 +1,69 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FieldExtensionOptions } from '@backstage/plugin-scaffolder-react'; +import { DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS } from '../../extensions/default'; +import { + buildEditorFieldExtensions, + toFieldExtensionOptions, +} from './EditorSubPage'; + +describe('buildEditorFieldExtensions', () => { + it('includes default field extensions when no custom fields are loaded', () => { + expect(buildEditorFieldExtensions()).toEqual( + DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS, + ); + }); + + it('keeps loaded field extensions ahead of defaults and de-duplicates by name', () => { + const customField: FieldExtensionOptions = { + ...DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS[0], + component: jest.fn(() => null), + } as FieldExtensionOptions; + + const fieldExtensions = buildEditorFieldExtensions([customField]); + + expect(fieldExtensions[0]).toBe(customField); + expect( + fieldExtensions.filter(field => field.name === customField.name), + ).toHaveLength(1); + }); +}); + +describe('toFieldExtensionOptions', () => { + it('flattens FieldSchema wrappers from form field blueprints', () => { + const field = { + $$type: '@backstage/scaffolder/FormField', + version: 'v1', + name: 'EntityPicker', + component: jest.fn(() => null), + schema: { + schema: { + returnValue: { type: 'string' }, + uiOptions: { type: 'object' }, + }, + }, + } as any; + + expect(toFieldExtensionOptions(field)).toMatchObject({ + name: 'EntityPicker', + schema: { + returnValue: { type: 'string' }, + uiOptions: { type: 'object' }, + }, + }); + }); +}); diff --git a/plugins/scaffolder/src/alpha/components/EditorSubPage.tsx b/plugins/scaffolder/src/alpha/components/EditorSubPage.tsx new file mode 100644 index 0000000000..9c955d69bc --- /dev/null +++ b/plugins/scaffolder/src/alpha/components/EditorSubPage.tsx @@ -0,0 +1,184 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useAsync, useMountEffect } from '@react-hookz/web'; +import { useCallback, useMemo } from 'react'; +import { Routes, Route, useNavigate } from 'react-router-dom'; +import { Content } from '@backstage/core-components'; +import { useApi } from '@backstage/core-plugin-api'; +import { makeStyles } from '@material-ui/core/styles'; +import { RequirePermission } from '@backstage/plugin-permission-react'; +import { templateManagementPermission } from '@backstage/plugin-scaffolder-common/alpha'; +import { + type FieldExtensionOptions, + SecretsContextProvider, +} from '@backstage/plugin-scaffolder-react'; +import type { FormField } from '@backstage/plugin-scaffolder-react/alpha'; +import { OpaqueFormField } from '@internal/scaffolder'; +import { DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS } from '../../extensions/default'; +import { formFieldsApiRef } from '../formFieldsApi'; +import { TemplateEditorIntro } from './TemplateEditorPage/TemplateEditorIntro'; +import { TemplateEditor } from './TemplateEditorPage/TemplateEditor'; +import { TemplateFormPreviewer } from './TemplateEditorPage/TemplateFormPreviewer'; +import { CustomFieldExplorer } from './TemplateEditorPage/CustomFieldExplorer'; +import { useTemplateDirectory } from './TemplateEditorPage/useTemplateDirectory'; + +const useEditorStyles = makeStyles({ + editorContent: { + padding: 0, + height: 'calc(100dvh - var(--bui-header-height, 0px))', + }, + formContent: { + padding: 0, + height: 'calc(100dvh - var(--bui-header-height, 0px))', + }, +}); + +function EditorIntroContent() { + const navigate = useNavigate(); + const { openDirectory, createDirectory } = useTemplateDirectory(); + + const handleSelect = useCallback( + (option: 'create-template' | 'local' | 'form' | 'field-explorer') => { + if (option === 'local') { + openDirectory() + .then(() => navigate('template')) + .catch(() => {}); + } else if (option === 'create-template') { + createDirectory() + .then(() => navigate('template')) + .catch(() => {}); + } else if (option === 'form') { + navigate('template-form'); + } else if (option === 'field-explorer') { + navigate('custom-fields'); + } + }, + [openDirectory, createDirectory, navigate], + ); + + return ( + + + + ); +} + +export function buildEditorFieldExtensions( + formFields: FieldExtensionOptions[] = [], +): FieldExtensionOptions[] { + return [ + ...formFields, + ...(DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS.filter( + ({ name }) => !formFields.some(formField => formField.name === name), + ) as FieldExtensionOptions[]), + ]; +} + +export function toFieldExtensionOptions( + formField: FormField, +): FieldExtensionOptions { + const internal = OpaqueFormField.toInternal(formField); + + return { + ...internal, + schema: internal.schema?.schema ?? internal.schema, + } as FieldExtensionOptions; +} + +function EditorContent(props: { fieldExtensions: FieldExtensionOptions[] }) { + const classes = useEditorStyles(); + return ( + + + + ); +} + +function FormPreviewContent(props: { + fieldExtensions: FieldExtensionOptions[]; +}) { + const classes = useEditorStyles(); + const navigate = useNavigate(); + + const handleClose = useCallback(() => { + navigate('..'); + }, [navigate]); + + return ( + + + + ); +} + +function CustomFieldsContent(props: { + fieldExtensions: FieldExtensionOptions[]; +}) { + return ( + + + + ); +} + +/** + * Sub-page for the template editor tab. Renders the editor intro at the index, + * with sub-routes for the full editor, form previewer, and custom fields explorer. + * + * @internal + */ +export function EditorSubPage() { + const formFieldsApi = useApi(formFieldsApiRef); + const [{ result: customFieldExtensions = [] }, { execute }] = useAsync( + async () => { + const formFields = await formFieldsApi.loadFormFields(); + return formFields.map(toFieldExtensionOptions); + }, + ); + + useMountEffect(execute); + + const fieldExtensions = useMemo( + () => buildEditorFieldExtensions(customFieldExtensions), + [customFieldExtensions], + ); + + return ( + + + + } /> + } + /> + } + /> + } + /> + + + + ); +} diff --git a/plugins/scaffolder/src/alpha/components/TasksSubPage.tsx b/plugins/scaffolder/src/alpha/components/TasksSubPage.tsx new file mode 100644 index 0000000000..82b7fc250f --- /dev/null +++ b/plugins/scaffolder/src/alpha/components/TasksSubPage.tsx @@ -0,0 +1,42 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Routes, Route } from 'react-router-dom'; +import { Content } from '@backstage/core-components'; +import { OngoingTaskBody } from '../../components/OngoingTask'; +import { ListTaskPageContent } from '../../components/ListTasksPage'; + +/** + * Sub-page for the tasks tab. Renders the task list at the index route + * and the ongoing task detail at the parameterized route. + * + * @internal + */ +export function TasksSubPage() { + return ( + + + + + } + /> + } /> + + ); +} diff --git a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/CustomFieldPlayground.tsx b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/CustomFieldPlayground.tsx index 88e192d510..fcc77449af 100644 --- a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/CustomFieldPlayground.tsx +++ b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/CustomFieldPlayground.tsx @@ -68,24 +68,26 @@ export const CustomFieldPlayground = ({ const [refreshKey, setRefreshKey] = useState(Date.now()); const [fieldFormState, setFieldFormState] = useState({}); const [selectedField, setSelectedField] = useState(fieldOptions[0]); - const sampleFieldTemplate = useMemo( - () => - yaml.stringify({ - parameters: [ - { - title: `${selectedField.name} Example`, - properties: { - [selectedField.name]: { - type: selectedField.schema?.returnValue?.type, - 'ui:field': selectedField.name, - 'ui:options': fieldFormState, - }, + const sampleFieldTemplate = useMemo(() => { + if (!selectedField) { + return ''; + } + + return yaml.stringify({ + parameters: [ + { + title: `${selectedField.name} Example`, + properties: { + [selectedField.name]: { + type: selectedField.schema?.returnValue?.type, + 'ui:field': selectedField.name, + 'ui:options': fieldFormState, }, }, - ], - }), - [fieldFormState, selectedField], - ); + }, + ], + }); + }, [fieldFormState, selectedField]); const fieldComponents = useMemo(() => { return Object.fromEntries( @@ -98,18 +100,15 @@ export const CustomFieldPlayground = ({ setSelectedField(selection); setFieldFormState({}); }, - [setFieldFormState, setSelectedField], + [], ); - const handleFieldConfigChange = useCallback( - (state: {}) => { - setFieldFormState(state); - // Force TemplateEditorForm to re-render since some fields - // may not be responsive to ui:option changes - setRefreshKey(Date.now()); - }, - [setFieldFormState, setRefreshKey], - ); + const handleFieldConfigChange = useCallback((state: {}) => { + setFieldFormState(state); + // Force TemplateEditorForm to re-render since some fields + // may not be responsive to ui:option changes + setRefreshKey(Date.now()); + }, []); return (
    @@ -211,7 +210,7 @@ export const CustomFieldPlayground = ({ formContext={{ fieldFormState }} onSubmit={e => handleFieldConfigChange(e.formData)} validator={validator} - schema={selectedField.schema?.uiOptions || {}} + schema={selectedField?.schema?.uiOptions || {}} experimental_defaultFormStateBehavior={{ allOf: 'populateDefaults', }} @@ -220,7 +219,7 @@ export const CustomFieldPlayground = ({ variant="contained" color="primary" type="submit" - disabled={!selectedField.schema?.uiOptions} + disabled={!selectedField?.schema?.uiOptions} > {t( 'templateEditorPage.customFieldExplorer.fieldForm.applyButtonTitle', diff --git a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/DryRunContext.test.tsx b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/DryRunContext.test.tsx index 9b4bdb9032..873ddcaa75 100644 --- a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/DryRunContext.test.tsx +++ b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/DryRunContext.test.tsx @@ -16,7 +16,7 @@ import { TestApiProvider } from '@backstage/test-utils'; // eslint-disable-next-line no-restricted-imports -import { TextEncoder } from 'util'; +import { TextEncoder } from 'node:util'; import { base64EncodeContent, DryRunProvider, diff --git a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditorBrowser.test.tsx b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditorBrowser.test.tsx index 427f679188..86400fcfde 100644 --- a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditorBrowser.test.tsx +++ b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditorBrowser.test.tsx @@ -20,14 +20,6 @@ import { MockFileSystemAccess } from '../../../lib/filesystem/MockFileSystemAcce import { DirectoryEditorProvider } from './DirectoryEditorContext'; import { TemplateEditorBrowser } from './TemplateEditorBrowser'; -Blob.prototype.text = async function text() { - return new Promise(resolve => { - const reader = new FileReader(); - reader.onload = () => resolve(reader.result as string); - reader.readAsText(this); - }); -}; - describe('TemplateEditorBrowser', () => { it('should render files and expand dirs without exploding', async () => { await renderInTestApp( diff --git a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditorToolbar.test.tsx b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditorToolbar.test.tsx index 016d775c43..346aa9dd10 100644 --- a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditorToolbar.test.tsx +++ b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditorToolbar.test.tsx @@ -38,6 +38,9 @@ describe('TemplateEditorToolbar', () => { listActions: jest.fn(), listTasks: jest.fn(), autocomplete: jest.fn(), + retry: jest.fn(), + listTemplatingExtensions: jest.fn(), + dryRun: jest.fn(), }; scaffolderApiMock.listActions.mockResolvedValue([ @@ -99,6 +102,21 @@ describe('TemplateEditorToolbar', () => { ).toBeInTheDocument(); }); + it('should not crash when no custom fields are available', async () => { + await renderInTestApp(); + + await userEvent.click( + screen.getByRole('button', { name: 'Custom Fields Explorer' }), + ); + + expect( + screen.getByPlaceholderText('Choose Custom Field Extension'), + ).toHaveValue(''); + expect( + screen.getByRole('heading', { name: 'Template Spec' }), + ).toBeInTheDocument(); + }); + it('should open the installed actions documentation', async () => { await renderInTestApp( @@ -111,14 +129,16 @@ describe('TemplateEditorToolbar', () => { expect(screen.getByLabelText('Search for an action')).toBeInTheDocument(); expect(screen.getByText('action:example')).toBeInTheDocument(); - expect(screen.getByText('Example description')).toBeInTheDocument(); - expect(screen.getByText('Inform the title')).toBeInTheDocument(); + + await userEvent.click(screen.getByText('action:example')); + + expect(await screen.findByText('Inform the title')).toBeInTheDocument(); }); it('should accept custom toolbar actions', async () => { await renderInTestApp( - + , ); diff --git a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateFormPage.test.tsx b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateFormPage.test.tsx index 1174d84b11..1fd7544ea4 100644 --- a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateFormPage.test.tsx +++ b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateFormPage.test.tsx @@ -18,14 +18,34 @@ import { screen } from '@testing-library/react'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import { TemplateFormPage } from './TemplateFormPage'; import { rootRouteRef } from '../../../routes'; -import { catalogApiRef } from '@backstage/plugin-catalog-react'; +import { + catalogApiRef, + defaultEntityPresentation, + entityPresentationApiRef, +} from '@backstage/plugin-catalog-react'; describe('TemplateFormPage', () => { const catalogApiMock = { getEntities: jest.fn().mockResolvedValue([]) }; + const entityPresentationApi: typeof entityPresentationApiRef.T = { + forEntity(entityOrRef, context) { + const presentation = defaultEntityPresentation(entityOrRef, context); + return { + snapshot: presentation, + update$: { subscribe: () => ({ unsubscribe: () => {} }) } as any, + promise: Promise.resolve(presentation), + }; + }, + }; + it('Should render without exploding', async () => { await renderInTestApp( - + , { @@ -41,7 +61,12 @@ describe('TemplateFormPage', () => { it('Should have an link back to the edit page', async () => { await renderInTestApp( - + , { diff --git a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateFormPreviewer.tsx b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateFormPreviewer.tsx index cb40ff2766..efb1a06822 100644 --- a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateFormPreviewer.tsx +++ b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateFormPreviewer.tsx @@ -24,7 +24,7 @@ import { makeStyles } from '@material-ui/core/styles'; import { alertApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api'; import { catalogApiRef, - humanizeEntityRef, + entityPresentationApiRef, } from '@backstage/plugin-catalog-react'; import { LayoutOptions, @@ -139,6 +139,7 @@ export const TemplateFormPreviewer = ({ const classes = useStyles(); const alertApi = useApi(alertApiRef); const catalogApi = useApi(catalogApiRef); + const entityPresentationApi = useApi(entityPresentationApiRef); const navigate = useNavigate(); const editLink = useRouteRef(editRouteRef); @@ -166,23 +167,26 @@ export const TemplateFormPreviewer = ({ 'spec.output', ], }) - .then(({ items }) => - setTemplateOptions( - items.map(template => ({ - label: - template.metadata.title ?? - humanizeEntityRef(template, { defaultKind: 'template' }), + .then(async ({ items }) => { + const options = await Promise.all( + items.map(async template => ({ + label: ( + await entityPresentationApi.forEntity(template, { + defaultKind: 'template', + }).promise + ).primaryTitle, value: template, })), - ), - ) + ); + setTemplateOptions(options); + }) .catch(e => alertApi.post({ message: `Error loading existing templates: ${e.message}`, severity: 'error', }), ), - [catalogApi], + [catalogApi, entityPresentationApi, alertApi], ); const handleSelectChange = useCallback( diff --git a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/useTemplateDirectory.test.ts b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/useTemplateDirectory.test.ts index bdc1534f27..f4e7d4d1a4 100644 --- a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/useTemplateDirectory.test.ts +++ b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/useTemplateDirectory.test.ts @@ -21,10 +21,7 @@ import { TemplateDirectoryAccess, WebFileSystemStore, } from '../../../lib/filesystem'; -import { - IterableDirectoryHandle, - WebFileSystemAccess, -} from '../../../lib/filesystem/WebFileSystemAccess'; +import { WebFileSystemAccess } from '../../../lib/filesystem/WebFileSystemAccess'; jest.mock('../../../lib/filesystem/createExampleTemplate'); @@ -49,7 +46,7 @@ describe('useTemplateDirectory', () => { }); it('should return an access when there is existing directory in the file system store', async () => { - const handle = {} as IterableDirectoryHandle; + const handle = {} as FileSystemDirectoryHandle; jest.spyOn(WebFileSystemStore, 'getDirectory').mockResolvedValue(handle); @@ -65,7 +62,7 @@ describe('useTemplateDirectory', () => { const handle = {}; jest .spyOn(WebFileSystemStore, 'getDirectory') - .mockResolvedValue(handle as IterableDirectoryHandle); + .mockResolvedValue(handle as FileSystemDirectoryHandle); const setDirectory = jest .spyOn(WebFileSystemStore, 'setDirectory') .mockResolvedValue(undefined); @@ -92,7 +89,7 @@ describe('useTemplateDirectory', () => { (createExampleTemplate as jest.Mock).mockResolvedValue(handle); jest .spyOn(WebFileSystemStore, 'getDirectory') - .mockResolvedValue(handle as IterableDirectoryHandle); + .mockResolvedValue(handle as FileSystemDirectoryHandle); const setDirectory = jest .spyOn(WebFileSystemStore, 'setDirectory') .mockResolvedValue(undefined); diff --git a/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPage.test.tsx b/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPage.test.tsx index 6836259dd8..b8d750c419 100644 --- a/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPage.test.tsx +++ b/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPage.test.tsx @@ -54,6 +54,9 @@ const scaffolderApiMock: jest.Mocked = { listActions: jest.fn(), listTasks: jest.fn(), autocomplete: jest.fn(), + retry: jest.fn(), + listTemplatingExtensions: jest.fn(), + dryRun: jest.fn(), }; const scaffolderDecoratorsMock: jest.Mocked = { diff --git a/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPage.tsx b/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPage.tsx index c86f793c5b..6b5d5424f8 100644 --- a/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPage.tsx +++ b/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPage.tsx @@ -71,7 +71,7 @@ export type TemplateWizardPageProps = { }; }; -export const TemplateWizardPage = (props: TemplateWizardPageProps) => { +function useTemplateWizard(_props: TemplateWizardPageProps) { const rootRef = useRouteRef(rootRouteRef); const taskRoute = useRouteRef(scaffolderTaskRouteRef); const { secrets: contextSecrets } = useTemplateSecrets(); @@ -134,11 +134,76 @@ export const TemplateWizardPage = (props: TemplateWizardPageProps) => { const onError = useCallback(() => , [rootRef]); + return { + templateRef, + templateName, + namespace, + manifest, + editUrl, + isCreating, + onCreate, + onError, + t, + }; +} + +/** + * Content-only version of the template wizard, for use within the NFS page layout + * where the header is provided by the framework. + * + * @internal + */ +export const TemplateWizardPageContent = (props: TemplateWizardPageProps) => { + const { + templateRef, + templateName, + namespace, + isCreating, + onCreate, + onError, + } = useTemplateWizard(props); + + return ( + + {isCreating && } + + + ); +}; + +export const TemplateWizardPage = (props: TemplateWizardPageProps) => { + const { + templateRef, + templateName, + namespace, + manifest, + editUrl, + isCreating, + onCreate, + onError, + t, + } = useTemplateWizard(props); + return (
    true, + }, + ]; + + const additionalLinksForEntity = useCallback( + (template: TemplateEntityV1beta3) => { + if ( + !( + template.metadata.annotations?.[TECHDOCS_ANNOTATION] || + template.metadata.annotations?.[TECHDOCS_EXTERNAL_ANNOTATION] + ) || + !viewTechDocsLink + ) { + return []; + } + + const url = buildTechDocsURL(template, viewTechDocsLink); + return url + ? [ + { + icon: app.getSystemIcon('docs') ?? DocsIcon, + text: t( + 'templateListPage.additionalLinksForEntity.viewTechDocsTitle', + ), + url, + }, + ] + : []; + }, + [app, viewTechDocsLink, t], + ); + + const onTemplateSelected = useCallback( + (template: TemplateEntityV1beta3) => { + const { namespace, name } = parseEntityRef(stringifyEntityRef(template)); + navigate(templateRoute({ namespace, templateName: name })); + }, + [navigate, templateRoute], + ); + + return ( + + + + + + {t('templateListPage.contentHeader.supportButtonTitle')} + + + + + + + + + + + + + + ); +} + +/** + * Sub-page for the templates tab. Renders the template list at the index route + * and the template wizard at the parameterized route. + * + * @internal + */ +export function TemplatesSubPage(props: { formFields?: Array }) { + const customFieldExtensions = useCustomFieldExtensions(undefined); + const customLayouts = useCustomLayouts(undefined); + + const fieldExtensions = [ + ...customFieldExtensions, + ...(props.formFields?.map(OpaqueFormField.toInternal) ?? []), + ...DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS.filter( + ({ name }) => + !customFieldExtensions.some( + (customFieldExtension: FieldExtensionOptions) => + customFieldExtension.name === name, + ), + ), + ] as FieldExtensionOptions[]; + + return ( + + } /> + + + + } + /> + + ); +} diff --git a/plugins/scaffolder/src/alpha/extensions.tsx b/plugins/scaffolder/src/alpha/extensions.tsx index cb69e691b8..712b97a2c9 100644 --- a/plugins/scaffolder/src/alpha/extensions.tsx +++ b/plugins/scaffolder/src/alpha/extensions.tsx @@ -14,10 +14,6 @@ * limitations under the License. */ -import { - compatWrapper, - convertLegacyRouteRef, -} from '@backstage/core-compat-api'; import { ApiBlueprint, createExtensionInput, @@ -26,10 +22,14 @@ import { identityApiRef, NavItemBlueprint, PageBlueprint, + SubPageBlueprint, } from '@backstage/frontend-plugin-api'; import { rootRouteRef } from '../routes'; import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; -import { FormFieldBlueprint } from '@backstage/plugin-scaffolder-react/alpha'; +import { + FormFieldBlueprint, + formFieldsApiRef, +} from '@backstage/plugin-scaffolder-react/alpha'; import { scmIntegrationsApiRef } from '@backstage/integration-react'; import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react'; import { ScaffolderClient } from '../api'; @@ -40,26 +40,91 @@ export const scaffolderPage = PageBlueprint.makeWithOverrides({ FormFieldBlueprint.dataRefs.formFieldLoader, ]), }, - factory(originalFactory, { inputs }) { - const formFieldLoaders = inputs.formFields.map(i => - i.get(FormFieldBlueprint.dataRefs.formFieldLoader), - ); + factory(originalFactory) { return originalFactory({ - routeRef: convertLegacyRouteRef(rootRouteRef), + routeRef: rootRouteRef, path: '/create', - loader: () => - import('../components/Router/Router').then(m => - compatWrapper( - , - ), - ), + title: 'Create', }); }, }); +export const scaffolderTemplatesSubPage = SubPageBlueprint.makeWithOverrides({ + name: 'templates', + factory(originalFactory, { apis }) { + const formFieldsApi = apis.get(formFieldsApiRef); + + return originalFactory({ + path: 'templates', + title: 'Templates', + loader: async () => { + const formFields = (await formFieldsApi?.loadFormFields()) ?? []; + + return import('./components/TemplatesSubPage').then(m => ( + + )); + }, + }); + }, +}); + +export const scaffolderTasksSubPage = SubPageBlueprint.make({ + name: 'tasks', + params: { + path: 'tasks', + title: 'Tasks', + loader: () => + import('./components/TasksSubPage').then(m => ), + }, +}); + +export const scaffolderActionsSubPage = SubPageBlueprint.make({ + name: 'actions', + params: { + path: 'actions', + title: 'Actions', + loader: () => + Promise.all([ + import('../components/ActionsPage'), + import('@backstage/core-components'), + ]).then(([m, { Content }]) => ( + + + + )), + }, +}); + +export const scaffolderEditorSubPage = SubPageBlueprint.make({ + name: 'editor', + params: { + path: 'edit', + title: 'Template Editor', + loader: () => + import('./components/EditorSubPage').then(m => ), + }, +}); + +export const scaffolderTemplatingExtensionsSubPage = SubPageBlueprint.make({ + name: 'templating-extensions', + params: { + path: 'templating-extensions', + title: 'Templating Extensions', + loader: () => + Promise.all([ + import('../components/TemplatingExtensionsPage'), + import('@backstage/core-components'), + ]).then(([m, { Content }]) => ( + + + + )), + }, +}); + export const scaffolderNavItem = NavItemBlueprint.make({ params: { - routeRef: convertLegacyRouteRef(rootRouteRef), + routeRef: rootRouteRef, title: 'Create...', icon: CreateComponentIcon, }, @@ -133,6 +198,14 @@ export const repoBranchPickerFormField = FormFieldBlueprint.make({ }, }); +export const repoOwnerPickerFormField = FormFieldBlueprint.make({ + name: 'repo-owner-picker', + params: { + field: () => + import('./fields/RepoOwnerPicker').then(m => m.RepoOwnerPicker), + }, +}); + export const scaffolderApi = ApiBlueprint.make({ params: defineParams => defineParams({ diff --git a/plugins/scaffolder/src/alpha/fields/RepoOwnerPicker.ts b/plugins/scaffolder/src/alpha/fields/RepoOwnerPicker.ts new file mode 100644 index 0000000000..059d71fb83 --- /dev/null +++ b/plugins/scaffolder/src/alpha/fields/RepoOwnerPicker.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createFormField } from '@backstage/plugin-scaffolder-react/alpha'; +import { + RepoOwnerPicker as Component, + RepoOwnerPickerFieldSchema, +} from '../../components/fields/RepoOwnerPicker'; + +export const RepoOwnerPicker = createFormField({ + component: Component, + name: 'RepoOwnerPicker', + schema: RepoOwnerPickerFieldSchema, +}); diff --git a/plugins/scaffolder-react/src/next/api/FormFieldsApi.ts b/plugins/scaffolder/src/alpha/formFieldsApi.ts similarity index 50% rename from plugins/scaffolder-react/src/next/api/FormFieldsApi.ts rename to plugins/scaffolder/src/alpha/formFieldsApi.ts index 08ccc662d4..03d6f0796a 100644 --- a/plugins/scaffolder-react/src/next/api/FormFieldsApi.ts +++ b/plugins/scaffolder/src/alpha/formFieldsApi.ts @@ -16,32 +16,15 @@ import { ApiBlueprint, + appTreeApiRef, createExtensionInput, } from '@backstage/frontend-plugin-api'; -import { formFieldsApiRef } from './ref'; -import { FormField, ScaffolderFormFieldsApi } from './types'; -import { FormFieldBlueprint } from '../blueprints'; +import { + FormFieldBlueprint, + formFieldsApiRef, +} from '@backstage/plugin-scaffolder-react/alpha'; import { OpaqueFormField } from '@internal/scaffolder'; -class DefaultScaffolderFormFieldsApi implements ScaffolderFormFieldsApi { - private readonly formFieldLoaders: Array<() => Promise>; - - constructor(formFieldLoaders: Array<() => Promise> = []) { - this.formFieldLoaders = formFieldLoaders; - } - - async getFormFields() { - const formFields = await Promise.all( - this.formFieldLoaders.map(loader => loader()), - ); - - const internalFormFields = formFields.map(OpaqueFormField.toInternal); - - return internalFormFields; - } -} - -/** @alpha */ export const formFieldsApi = ApiBlueprint.makeWithOverrides({ name: 'form-fields', inputs: { @@ -57,9 +40,44 @@ export const formFieldsApi = ApiBlueprint.makeWithOverrides({ return originalFactory(defineParams => defineParams({ api: formFieldsApiRef, - deps: {}, - factory: () => new DefaultScaffolderFormFieldsApi(formFieldLoaders), + deps: { appTreeApi: appTreeApiRef }, + factory: ({ appTreeApi }) => ({ + async loadFormFields() { + const pageFormFieldLoaders = getPageFormFieldLoaders(appTreeApi); + + const formFields = await Promise.all( + [...formFieldLoaders, ...pageFormFieldLoaders].map(loader => + loader(), + ), + ); + + return formFields.map(OpaqueFormField.toInternal); + }, + }), }), ); }, }); + +function getPageFormFieldLoaders(appTreeApi: typeof appTreeApiRef.T) { + const { tree } = appTreeApi.getTree(); + const pageNode = tree.nodes.get('page:scaffolder'); + if (!pageNode?.instance) { + return []; + } + const formFieldNodes = pageNode.edges.attachments.get('formFields') ?? []; + return formFieldNodes.flatMap(node => { + if (!node.instance) { + return []; + } + const loader = node.instance.getData( + FormFieldBlueprint.dataRefs.formFieldLoader, + ); + return loader ? [loader] : []; + }); +} + +export { + formFieldsApiRef, + type ScaffolderFormFieldsApi, +} from '@backstage/plugin-scaffolder-react/alpha'; diff --git a/plugins/scaffolder/src/alpha/index.ts b/plugins/scaffolder/src/alpha/index.ts index d239f44b4f..286101f942 100644 --- a/plugins/scaffolder/src/alpha/index.ts +++ b/plugins/scaffolder/src/alpha/index.ts @@ -22,12 +22,17 @@ export { type ScaffolderTemplateFormPreviewerClassKey, } from './components'; -export { scaffolderTranslationRef } from '../translation'; -export * from './api'; +import { scaffolderTranslationRef as _scaffolderTranslationRef } from '../translation'; +/** + * @alpha + * @deprecated Import from `@backstage/plugin-scaffolder` instead. + */ +export const scaffolderTranslationRef = _scaffolderTranslationRef; +export * from './api'; export { formFieldsApiRef, type ScaffolderFormFieldsApi, -} from '@backstage/plugin-scaffolder-react/alpha'; +} from './formFieldsApi'; export { default } from './plugin'; diff --git a/plugins/scaffolder/src/alpha/plugin.tsx b/plugins/scaffolder/src/alpha/plugin.tsx index 3b41aa3893..af79ea416a 100644 --- a/plugins/scaffolder/src/alpha/plugin.tsx +++ b/plugins/scaffolder/src/alpha/plugin.tsx @@ -14,8 +14,8 @@ * limitations under the License. */ -import { convertLegacyRouteRefs } from '@backstage/core-compat-api'; import { createFrontendPlugin } from '@backstage/frontend-plugin-api'; +import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; import { actionsRouteRef, editRouteRef, @@ -36,13 +36,19 @@ import { ownedEntityPickerFormField, ownerPickerFormField, repoBranchPickerFormField, + repoOwnerPickerFormField, repoUrlPickerFormField, scaffolderApi, scaffolderNavItem, scaffolderPage, + scaffolderTemplatesSubPage, + scaffolderTasksSubPage, + scaffolderActionsSubPage, + scaffolderEditorSubPage, + scaffolderTemplatingExtensionsSubPage, } from './extensions'; import { isTemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; -import { formFieldsApi } from '@backstage/plugin-scaffolder-react/alpha'; +import { formFieldsApi } from './formFieldsApi'; import { formDecoratorsApi } from './api'; import { EntityIconLinkBlueprint } from '@backstage/plugin-catalog-react/alpha'; import { useScaffolderTemplateIconLinkProps } from './hooks/useScaffolderTemplateIconLinkProps'; @@ -59,8 +65,10 @@ const scaffolderEntityIconLink = EntityIconLinkBlueprint.make({ /** @alpha */ export default createFrontendPlugin({ pluginId: 'scaffolder', + title: 'Create', + icon: , info: { packageJson: () => import('../../package.json') }, - routes: convertLegacyRouteRefs({ + routes: { root: rootRouteRef, selectedTemplate: selectedTemplateRouteRef, ongoingTask: scaffolderTaskRouteRef, @@ -68,14 +76,19 @@ export default createFrontendPlugin({ listTasks: scaffolderListTaskRouteRef, edit: editRouteRef, templatingExtensions: templatingExtensionsRouteRef, - }), - externalRoutes: convertLegacyRouteRefs({ + }, + externalRoutes: { registerComponent: registerComponentRouteRef, viewTechDoc: viewTechDocRouteRef, - }), + }, extensions: [ scaffolderApi, scaffolderPage, + scaffolderTemplatesSubPage, + scaffolderTasksSubPage, + scaffolderActionsSubPage, + scaffolderEditorSubPage, + scaffolderTemplatingExtensionsSubPage, scaffolderNavItem, scaffolderEntityIconLink, formDecoratorsApi, @@ -89,5 +102,6 @@ export default createFrontendPlugin({ myGroupsPickerFormField, ownedEntityPickerFormField, repoBranchPickerFormField, + repoOwnerPickerFormField, ], }); diff --git a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.test.tsx b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.test.tsx index a86b44e724..9c004ab8a6 100644 --- a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.test.tsx +++ b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.test.tsx @@ -22,6 +22,7 @@ import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils'; import { ApiProvider } from '@backstage/core-app-api'; import { rootRouteRef } from '../../routes'; import { userEvent } from '@testing-library/user-event'; +import { screen } from '@testing-library/react'; import { permissionApiRef } from '@backstage/plugin-permission-react'; const scaffolderApiMock: jest.Mocked = { @@ -34,6 +35,9 @@ const scaffolderApiMock: jest.Mocked = { listActions: jest.fn(), listTasks: jest.fn(), autocomplete: jest.fn(), + retry: jest.fn(), + listTemplatingExtensions: jest.fn(), + dryRun: jest.fn(), }; const mockPermissionApi = { authorize: jest.fn() }; @@ -42,10 +46,20 @@ const apis = TestApiRegistry.from( [permissionApiRef, mockPermissionApi], ); -describe('TemplatePage', () => { - beforeEach(() => jest.resetAllMocks()); +async function selectAction(actionId: string) { + const row = await screen.findByRole('row', { + name: new RegExp(actionId), + }); + await userEvent.click(row); +} - it('renders action with input', async () => { +describe('ActionsPage', () => { + beforeEach(() => { + jest.resetAllMocks(); + window.location.hash = ''; + }); + + it('renders actions in a table and shows detail on row click', async () => { scaffolderApiMock.listActions.mockResolvedValue([ { id: 'test', @@ -64,7 +78,7 @@ describe('TemplatePage', () => { }, }, ]); - const rendered = await renderInTestApp( + await renderInTestApp( , @@ -74,13 +88,17 @@ describe('TemplatePage', () => { }, }, ); - expect(rendered.getByText('Test title')).toBeInTheDocument(); - expect(rendered.getByText('example description')).toBeInTheDocument(); - expect(rendered.getByText('foobar')).toBeInTheDocument(); - expect(rendered.queryByText('output')).not.toBeInTheDocument(); + + expect( + await screen.findByRole('row', { name: /test/ }), + ).toBeInTheDocument(); + + await selectAction('test'); + + expect(await screen.findByText('foobar *')).toBeInTheDocument(); }); - it('renders action with input and output', async () => { + it('renders action with input and output on row click', async () => { scaffolderApiMock.listActions.mockResolvedValue([ { id: 'test', @@ -108,7 +126,7 @@ describe('TemplatePage', () => { }, }, ]); - const rendered = await renderInTestApp( + await renderInTestApp( , @@ -118,13 +136,14 @@ describe('TemplatePage', () => { }, }, ); - expect(rendered.getByText('Test title')).toBeInTheDocument(); - expect(rendered.getByText('example description')).toBeInTheDocument(); - expect(rendered.getByText('foobar')).toBeInTheDocument(); - expect(rendered.getByText('Test output')).toBeInTheDocument(); + + await selectAction('test'); + + expect(await screen.findByText('foobar *')).toBeInTheDocument(); + expect(screen.getByText('buzz')).toBeInTheDocument(); }); - it('renders action with oneOf output', async () => { + it('renders action with oneOf output on row click', async () => { scaffolderApiMock.listActions.mockResolvedValue([ { id: 'test', @@ -165,7 +184,7 @@ describe('TemplatePage', () => { }, }, ]); - const rendered = await renderInTestApp( + await renderInTestApp( , @@ -175,13 +194,16 @@ describe('TemplatePage', () => { }, }, ); - expect(rendered.getByText('oneOf')).toBeInTheDocument(); - expect(rendered.getByText('Test title')).toBeInTheDocument(); - expect(rendered.getByText('Test output1')).toBeInTheDocument(); - expect(rendered.getByText('Test output2')).toBeInTheDocument(); + + await selectAction('test'); + + expect( + await screen.findByRole('button', { name: /oneOf/ }), + ).toBeInTheDocument(); + expect(screen.getByText('foobar *')).toBeInTheDocument(); }); - it('renders action with multiple input types', async () => { + it('renders action with multiple input types on row click', async () => { scaffolderApiMock.listActions.mockResolvedValue([ { id: 'test', @@ -209,7 +231,7 @@ describe('TemplatePage', () => { }, }, ]); - const rendered = await renderInTestApp( + await renderInTestApp( , @@ -219,11 +241,14 @@ describe('TemplatePage', () => { }, }, ); - expect(rendered.getByText('array')).toBeInTheDocument(); - expect(rendered.getByText('number')).toBeInTheDocument(); + + await selectAction('test'); + + expect(await screen.findByText('array')).toBeInTheDocument(); + expect(screen.getByText('number')).toBeInTheDocument(); }); - it('renders action with oneOf input', async () => { + it('renders action with oneOf input on row click', async () => { scaffolderApiMock.listActions.mockResolvedValue([ { id: 'test', @@ -258,7 +283,7 @@ describe('TemplatePage', () => { }, }, ]); - const rendered = await renderInTestApp( + await renderInTestApp( , @@ -268,14 +293,23 @@ describe('TemplatePage', () => { }, }, ); - expect(rendered.getByText('oneOf')).toBeInTheDocument(); - expect(rendered.getByText('Foo title')).toBeInTheDocument(); - expect(rendered.getByText('Foo description')).toBeInTheDocument(); - expect(rendered.getByText('Bar title')).toBeInTheDocument(); - expect(rendered.getByText('Bar description')).toBeInTheDocument(); + + await selectAction('test'); + + const oneOfButton = await screen.findByRole('button', { name: /oneOf/ }); + expect(oneOfButton).toBeInTheDocument(); + + expect(screen.queryByText('foo *')).not.toBeInTheDocument(); + + await userEvent.click(oneOfButton); + + expect(await screen.findByText('foo *')).toBeInTheDocument(); + expect(screen.getByText('Foo description')).toBeInTheDocument(); + expect(screen.getByText('bar *')).toBeInTheDocument(); + expect(screen.getByText('Bar description')).toBeInTheDocument(); }); - it('renders action with object input type', async () => { + it('renders action with expandable object input type', async () => { scaffolderApiMock.listActions.mockResolvedValue([ { id: 'test', @@ -304,7 +338,7 @@ describe('TemplatePage', () => { }, }, ]); - const rendered = await renderInTestApp( + await renderInTestApp( , @@ -315,21 +349,20 @@ describe('TemplatePage', () => { }, ); - expect(rendered.getByText('Test object')).toBeInTheDocument(); - const objectChip = rendered.getByText('object'); - expect(objectChip).toBeInTheDocument(); + await selectAction('test'); - expect(rendered.queryByText('nested prop a')).not.toBeInTheDocument(); - expect(rendered.queryByText('string')).not.toBeInTheDocument(); - expect(rendered.queryByText('nested prop b')).not.toBeInTheDocument(); - expect(rendered.queryByText('number')).not.toBeInTheDocument(); + const objectButton = await screen.findByRole('button', { name: /object/ }); + expect(objectButton).toBeInTheDocument(); - await userEvent.click(objectChip); + expect(screen.queryByText(/^string$/)).not.toBeInTheDocument(); + expect(screen.queryByText(/^number$/)).not.toBeInTheDocument(); - expect(rendered.queryByText('nested prop a')).toBeInTheDocument(); - expect(rendered.queryByText('string')).toBeInTheDocument(); - expect(rendered.queryByText('nested prop b')).toBeInTheDocument(); - expect(rendered.queryByText('number')).toBeInTheDocument(); + await userEvent.click(objectButton); + + expect(screen.queryByText('a')).toBeInTheDocument(); + expect(screen.queryByText(/^string$/)).toBeInTheDocument(); + expect(screen.queryByText('b')).toBeInTheDocument(); + expect(screen.queryByText(/^number$/)).toBeInTheDocument(); }); it('renders action with nested object input type', async () => { @@ -367,7 +400,7 @@ describe('TemplatePage', () => { }, }, ]); - const rendered = await renderInTestApp( + await renderInTestApp( , @@ -378,27 +411,22 @@ describe('TemplatePage', () => { }, ); - expect(rendered.getByText('Test object')).toBeInTheDocument(); - const objectChip = rendered.getByText('object'); - expect(objectChip).toBeInTheDocument(); + await selectAction('test'); - expect(rendered.queryByText('nested object a')).not.toBeInTheDocument(); - expect(rendered.queryByText('nested prop b')).not.toBeInTheDocument(); - expect(rendered.queryByText('nested object c')).not.toBeInTheDocument(); + const objectButton = await screen.findByRole('button', { name: /object/ }); + expect(objectButton).toBeInTheDocument(); - await userEvent.click(objectChip); + await userEvent.click(objectButton); - expect(rendered.queryByText('nested object a')).toBeInTheDocument(); - expect(rendered.queryByText('nested prop b')).toBeInTheDocument(); - expect(rendered.queryByText('nested object c')).not.toBeInTheDocument(); + expect(screen.queryByText('a')).toBeInTheDocument(); + expect(screen.queryByText('b')).toBeInTheDocument(); + expect(screen.queryByText('c')).not.toBeInTheDocument(); - const allObjectChips = rendered.getAllByText('object'); - expect(allObjectChips.length).toBe(2); - await userEvent.click(allObjectChips[1]); + const allObjectButtons = screen.getAllByRole('button', { name: /object/ }); + expect(allObjectButtons.length).toBe(2); + await userEvent.click(allObjectButtons[1]); - expect(rendered.queryByText('nested object a')).toBeInTheDocument(); - expect(rendered.queryByText('nested prop b')).toBeInTheDocument(); - expect(rendered.queryByText('nested object c')).toBeInTheDocument(); + expect(screen.queryByText('c')).toBeInTheDocument(); }); it('renders action with object input type and no properties', async () => { @@ -420,7 +448,7 @@ describe('TemplatePage', () => { }, }, ]); - const rendered = await renderInTestApp( + await renderInTestApp( , @@ -431,15 +459,16 @@ describe('TemplatePage', () => { }, ); - expect(rendered.getByText('Test object')).toBeInTheDocument(); - const objectChip = rendered.getByText('object'); - expect(objectChip).toBeInTheDocument(); + await selectAction('test'); - expect(rendered.queryByText('No schema defined')).not.toBeInTheDocument(); + const objectButton = await screen.findByRole('button', { name: /object/ }); + expect(objectButton).toBeInTheDocument(); - await userEvent.click(objectChip); + expect(screen.queryByText('No schema defined')).not.toBeInTheDocument(); - expect(rendered.queryByText('No schema defined')).toBeInTheDocument(); + await userEvent.click(objectButton); + + expect(screen.queryByText('No schema defined')).toBeInTheDocument(); }); it('renders action with array(string) input type', async () => { @@ -463,7 +492,7 @@ describe('TemplatePage', () => { }, }, ]); - const rendered = await renderInTestApp( + await renderInTestApp( , @@ -474,8 +503,9 @@ describe('TemplatePage', () => { }, ); - expect(rendered.getByText('Test array')).toBeInTheDocument(); - expect(rendered.getByText('array(string)')).toBeInTheDocument(); + await selectAction('test'); + + expect(await screen.findByText('array(string)')).toBeInTheDocument(); }); it('renders action with array(object) input type', async () => { @@ -516,7 +546,7 @@ describe('TemplatePage', () => { }, }, ]); - const rendered = await renderInTestApp( + await renderInTestApp( , @@ -527,17 +557,19 @@ describe('TemplatePage', () => { }, ); - expect(rendered.getByText('Test array')).toBeInTheDocument(); - const objectChip = rendered.getByText('array(object)'); - expect(objectChip).toBeInTheDocument(); + await selectAction('test'); - expect(rendered.queryByText('nested object a')).not.toBeInTheDocument(); - expect(rendered.queryByText('nested prop b')).not.toBeInTheDocument(); + const arrayButton = await screen.findByRole('button', { + name: /array\(object\)/, + }); + expect(arrayButton).toBeInTheDocument(); - await userEvent.click(objectChip); + expect(screen.queryByText('b')).not.toBeInTheDocument(); - expect(rendered.queryByText('nested object a')).toBeInTheDocument(); - expect(rendered.queryByText('nested prop b')).toBeInTheDocument(); + await userEvent.click(arrayButton); + + expect(screen.queryByText('a')).toBeInTheDocument(); + expect(screen.queryByText('b')).toBeInTheDocument(); }); it('renders action with array input type and no items', async () => { @@ -557,7 +589,7 @@ describe('TemplatePage', () => { }, }, ]); - const rendered = await renderInTestApp( + await renderInTestApp( , @@ -568,14 +600,16 @@ describe('TemplatePage', () => { }, ); - expect(rendered.getByText('array(unknown)')).toBeInTheDocument(); + await selectAction('test'); + + expect(await screen.findByText('array(unknown)')).toBeInTheDocument(); }); - it('should filter an action', async () => { + it('should filter actions via the search field', async () => { scaffolderApiMock.listActions.mockResolvedValue([ { - id: 'githut:repo:create', - description: 'Create a new Github repository', + id: 'github:repo:create', + description: 'Create a new GitHub repository', schema: { input: { type: 'object', @@ -590,8 +624,8 @@ describe('TemplatePage', () => { }, }, { - id: 'githut:repo:push', - description: 'Push to a Github repository', + id: 'github:repo:push', + description: 'Push to a GitHub repository', schema: { input: { type: 'object', @@ -607,7 +641,7 @@ describe('TemplatePage', () => { }, ]); - const rendered = await renderInTestApp( + await renderInTestApp( , @@ -619,32 +653,141 @@ describe('TemplatePage', () => { ); expect( - rendered.getByRole('heading', { name: 'githut:repo:create' }), + await screen.findByRole('row', { name: /github:repo:create/ }), ).toBeInTheDocument(); expect( - rendered.getByRole('heading', { name: 'githut:repo:push' }), + screen.getByRole('row', { name: /github:repo:push/ }), ).toBeInTheDocument(); - // should filter actions when searching await userEvent.type( - rendered.getByPlaceholderText('Search for an action'), + screen.getByPlaceholderText('Search for an action'), 'create', ); - await userEvent.keyboard('[ArrowDown][Enter]'); + expect( - rendered.getByRole('heading', { name: 'githut:repo:create' }), + await screen.findByRole('row', { name: /github:repo:create/ }), ).toBeInTheDocument(); expect( - rendered.queryByRole('heading', { name: 'githut:repo:push' }), + screen.queryByRole('row', { name: /github:repo:push/ }), ).not.toBeInTheDocument(); - // should show all actions when clearing the search - await userEvent.click(rendered.getByTitle('Clear')); + await userEvent.click(screen.getByLabelText('Clear search')); + expect( - rendered.getByRole('heading', { name: 'githut:repo:create' }), + await screen.findByRole('row', { name: /github:repo:create/ }), ).toBeInTheDocument(); expect( - rendered.getByRole('heading', { name: 'githut:repo:push' }), + screen.getByRole('row', { name: /github:repo:push/ }), + ).toBeInTheDocument(); + }); + + it('should pre-select the action matching the URL hash on load', async () => { + scaffolderApiMock.listActions.mockResolvedValue([ + { + id: 'publish:github', + description: 'Publish to GitHub', + schema: { + input: { + type: 'object', + properties: { + repo: { title: 'Repo name', type: 'string' }, + }, + }, + }, + }, + { + id: 'fetch:plain', + description: 'Fetch plain content', + schema: {}, + }, + ]); + + window.location.hash = '#publish:github'; + + await renderInTestApp( + + + , + { + mountedRoutes: { + '/create/actions': rootRouteRef, + }, + routeEntries: ['/create/actions#publish:github'], + }, + ); + + expect( + await screen.findByRole('heading', { name: 'publish:github' }), + ).toBeInTheDocument(); + }); + + it('should update the URL hash when selecting and deselecting actions', async () => { + scaffolderApiMock.listActions.mockResolvedValue([ + { + id: 'publish:github', + description: 'Publish to GitHub', + schema: {}, + }, + { + id: 'fetch:plain', + description: 'Fetch plain content', + schema: {}, + }, + ]); + + await renderInTestApp( + + + , + { + mountedRoutes: { + '/create/actions': rootRouteRef, + }, + }, + ); + + await selectAction('publish:github'); + expect(window.location.hash).toBe('#publish:github'); + + await selectAction('publish:github'); + expect(window.location.hash).toBe(''); + }); + + it('should keep search field focused when filtering causes empty then non-empty results', async () => { + scaffolderApiMock.listActions.mockResolvedValue([ + { + id: 'github:repo:create', + description: 'Create a new GitHub repository', + schema: {}, + }, + ]); + + await renderInTestApp( + + + , + { + mountedRoutes: { + '/create/actions': rootRouteRef, + }, + }, + ); + + const searchField = screen.getByPlaceholderText('Search for an action'); + await userEvent.click(searchField); + expect(searchField).toHaveFocus(); + + await userEvent.type(searchField, 'zzz-no-match'); + expect(searchField).toHaveFocus(); + expect( + screen.queryByRole('row', { name: /github:repo:create/ }), + ).not.toBeInTheDocument(); + + await userEvent.clear(searchField); + await userEvent.type(searchField, 'create'); + expect(searchField).toHaveFocus(); + expect( + await screen.findByRole('row', { name: /github:repo:create/ }), ).toBeInTheDocument(); }); }); diff --git a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx index 6be502659a..227943e467 100644 --- a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx +++ b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx @@ -13,21 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import useAsync from 'react-use/esm/useAsync'; import { Action, scaffolderApiRef } from '@backstage/plugin-scaffolder-react'; -import Accordion from '@material-ui/core/Accordion'; -import AccordionDetails from '@material-ui/core/AccordionDetails'; -import AccordionSummary from '@material-ui/core/AccordionSummary'; -import Box from '@material-ui/core/Box'; -import Typography from '@material-ui/core/Typography'; -import { makeStyles } from '@material-ui/core/styles'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; -import LinkIcon from '@material-ui/icons/Link'; -import Autocomplete from '@material-ui/lab/Autocomplete'; -import TextField from '@material-ui/core/TextField'; -import InputAdornment from '@material-ui/core/InputAdornment'; -import SearchIcon from '@material-ui/icons/Search'; import { useApi, useRouteRef } from '@backstage/core-plugin-api'; import { @@ -35,11 +23,10 @@ import { EmptyState, ErrorPanel, Header, - Link, MarkdownContent, Page, - Progress, } from '@backstage/core-components'; +import { Flex, List, ListRow, SearchField, Text } from '@backstage/ui'; import { ScaffolderPageContextMenu } from '@backstage/plugin-scaffolder-react/alpha'; import { useNavigate } from 'react-router-dom'; import { @@ -53,60 +40,119 @@ import { scaffolderTranslationRef } from '../../translation'; import { Expanded, RenderSchema, SchemaRenderContext } from '../RenderSchema'; import { ScaffolderUsageExamplesTable } from '../ScaffolderUsageExamplesTable'; -const useStyles = makeStyles(theme => ({ - code: { - fontFamily: 'Menlo, monospace', - padding: theme.spacing(1), - backgroundColor: - theme.palette.type === 'dark' - ? theme.palette.grey[700] - : theme.palette.grey[300], - display: 'inline-block', - borderRadius: 5, - border: `1px solid ${theme.palette.grey[500]}`, - position: 'relative', - }, +function ActionDetail({ action }: { action: Action }) { + const { t } = useTranslationRef(scaffolderTranslationRef); + const expanded = useState({}); - codeRequired: { - '&::after': { - position: 'absolute', - content: '"*"', - top: 0, - right: theme.spacing(0.5), - fontWeight: 'bolder', - color: theme.palette.error.light, - }, - }, - link: { - paddingLeft: theme.spacing(1), - }, -})); + const partialSchemaRenderContext: Omit = { + expanded, + }; + + const hasInput = !!action.schema?.input; + const hasOutput = !!action.schema?.output; + const hasExamples = !!action.examples; + + if (!hasInput && !hasOutput && !hasExamples) { + return null; + } + + return ( + + {hasInput && ( + + + {t('actionsPage.action.input')} + + + + )} + {hasOutput && ( + + + {t('actionsPage.action.output')} + + + + )} + {hasExamples && ( + + + {t('actionsPage.action.examples')} + + + + )} + + ); +} export const ActionPageContent = () => { const api = useApi(scaffolderApiRef); const { t } = useTranslationRef(scaffolderTranslationRef); - const classes = useStyles(); const { loading, - value = [], + value: actions, error, } = useAsync(async () => { return api.listActions(); }, [api]); - const [selectedAction, setSelectedAction] = useState(null); - const expanded = useState({}); + const [selectedActionId, setSelectedActionId] = useState< + string | undefined + >(); + const [searchQuery, setSearchQuery] = useState(''); + const initialHashHandled = useRef(false); useEffect(() => { - if (value.length && window.location.hash) { - document.querySelector(window.location.hash)?.scrollIntoView(); + if (initialHashHandled.current || !actions) { + return; } - }, [value]); + const hash = window.location.hash.slice(1); + if (hash && actions.some(a => a.id === hash)) { + initialHashHandled.current = true; + setSelectedActionId(hash); + requestAnimationFrame(() => { + const row = document.querySelector(`[data-key="${CSS.escape(hash)}"]`); + if (row && typeof row.scrollIntoView === 'function') { + row.scrollIntoView({ block: 'nearest' }); + } + }); + } + }, [actions]); - if (loading) { - return ; - } + const filteredActions = useMemo(() => { + const nonLegacy = + actions?.filter(action => !action.id.startsWith('legacy:')) ?? []; + if (!searchQuery) { + return nonLegacy; + } + const lowerQuery = searchQuery.toLowerCase(); + return nonLegacy.filter( + action => + action.id.toLowerCase().includes(lowerQuery) || + action.description?.toLowerCase().includes(lowerQuery), + ); + }, [actions, searchQuery]); + + const selectedAction = useMemo( + () => filteredActions.find(a => a.id === selectedActionId), + [filteredActions, selectedActionId], + ); if (error) { return ( @@ -122,116 +168,80 @@ export const ActionPageContent = () => { } return ( - <> - - option.id} - renderInput={params => ( - - - - ), - }} - /> - )} - onChange={(_event, option) => { - setSelectedAction(option); - }} - fullWidth +
    + + {!loading && !filteredActions.length ? ( + - - {(selectedAction ? [selectedAction] : value).map(action => { - if (action.id.startsWith('legacy:')) { - return undefined; - } - const partialSchemaRenderContext: Omit< - SchemaRenderContext, - 'parentId' - > = { - classes, - expanded, - headings: [], - }; - return ( - - - - {action.id} - - - - - - {action.description && ( - + ) : ( + { + if (selection === 'all') { + return; + } + const selected = [...selection][0] as string | undefined; + setSelectedActionId(prev => { + const next = prev === selected ? undefined : selected; + const hash = next ? `#${next}` : ''; + window.history.replaceState( + null, + '', + `${window.location.pathname}${window.location.search}${hash}`, + ); + return next; + }); + }} + > + {filteredActions.map(action => ( + + {action.id} + + ))} + + )} + {selectedAction && ( + + + + {selectedAction.id} + + {selectedAction.description && ( + )} - {action.schema?.input && ( - - - {t('actionsPage.action.input')} - - - - )} - {action.schema?.output && ( - - - {t('actionsPage.action.output')} - - - - )} - {action.examples && ( - - }> - - {t('actionsPage.action.examples')} - - - - - - - - - )} - - ); - })} - + + + + )} +
    ); }; diff --git a/plugins/scaffolder/src/components/ActionsPage/index.ts b/plugins/scaffolder/src/components/ActionsPage/index.ts index e3c9a296a0..f9b240d4f7 100644 --- a/plugins/scaffolder/src/components/ActionsPage/index.ts +++ b/plugins/scaffolder/src/components/ActionsPage/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { ActionsPage } from './ActionsPage'; +export { ActionsPage, ActionPageContent } from './ActionsPage'; diff --git a/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx b/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx index 6cce6c3727..b0ccabd455 100644 --- a/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx +++ b/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx @@ -59,7 +59,7 @@ export interface MyTaskPageProps { }; } -const ListTaskPageContent = (props: MyTaskPageProps) => { +export const ListTaskPageContent = (props: MyTaskPageProps) => { const { initiallySelectedFilter = 'owned' } = props; const { t } = useTranslationRef(scaffolderTranslationRef); const [limit, setLimit] = useState(5); diff --git a/plugins/scaffolder/src/components/ListTasksPage/index.tsx b/plugins/scaffolder/src/components/ListTasksPage/index.tsx index 56fc7d3d28..d1a4932425 100644 --- a/plugins/scaffolder/src/components/ListTasksPage/index.tsx +++ b/plugins/scaffolder/src/components/ListTasksPage/index.tsx @@ -13,4 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { ListTasksPage } from './ListTasksPage'; +export { ListTasksPage, ListTaskPageContent } from './ListTasksPage'; diff --git a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx index bc0d5fe1aa..f40aeb545b 100644 --- a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx +++ b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx @@ -15,6 +15,7 @@ */ import { ComponentType, + ReactNode, useCallback, useEffect, useMemo, @@ -57,6 +58,7 @@ import { scaffolderTranslationRef } from '../../translation'; import { entityPresentationApiRef } from '@backstage/plugin-catalog-react'; import { default as reactUseAsync } from 'react-use/esm/useAsync'; import { stringifyEntityRef } from '@backstage/catalog-model'; +import { OngoingTaskContextMenu } from './OngoingTaskContextMenu'; const useStyles = makeStyles(theme => ({ contentWrapper: { @@ -79,15 +81,7 @@ const useStyles = makeStyles(theme => ({ }, })); -/** - * @public - */ -export const OngoingTask = (props: { - TemplateOutputsComponent?: ComponentType<{ - output?: ScaffolderTaskOutput; - }>; -}) => { - // todo(blam): check that task Id actually exists, and that it's valid. otherwise redirect to something more useful. +function OngoingTaskAnalyticsContext(props: { children: ReactNode }) { const { taskId } = useParams(); const taskStream = useTaskEventStream(taskId!); const { namespace, name } = @@ -106,34 +100,42 @@ export const OngoingTask = (props: { taskId, }} > - - - + {props.children} ); +} + +/** + * @public + */ +export const OngoingTask = (props: { + TemplateOutputsComponent?: ComponentType<{ + output?: ScaffolderTaskOutput; + }>; +}) => { + return ( + + + + + + ); }; -function OngoingTaskContent(props: { +/** + * Content-only version of the ongoing task, for use within the NFS page layout + * where the header is provided by the framework. + * + * @internal + */ +export function OngoingTaskBody(props: { TemplateOutputsComponent?: ComponentType<{ output?: ScaffolderTaskOutput; }>; }) { const { taskId } = useParams(); - const templateRouteRef = useRouteRef(selectedTemplateRouteRef); - const navigate = useNavigate(); - const analytics = useAnalytics(); - const scaffolderApi = useApi(scaffolderApiRef); - const entityPresentationApi = useApi(entityPresentationApiRef); const taskStream = useTaskEventStream(taskId!); - const classes = useStyles(); - const steps = useMemo( - () => - taskStream.task?.spec.steps.map(step => ({ - ...step, - ...taskStream?.steps?.[step.id], - })) ?? [], - [taskStream], - ); + const entityPresentationApi = useApi(entityPresentationApiRef); const { t } = useTranslationRef(scaffolderTranslationRef); const [logsVisible, setLogVisibleState] = useState(false); @@ -153,21 +155,14 @@ function OngoingTaskContent(props: { permission: taskCreatePermission, }); - // Start Over endpoint requires user to have both read (to grab parameters) and create (to create new task) permissions - const canStartOver = canReadTask && canCreateTask; + const isRetryableTask = + taskStream.task?.spec.EXPERIMENTAL_recovery?.EXPERIMENTAL_strategy === + 'startOver'; - useEffect(() => { - if (taskStream.error) { - setLogVisibleState(true); - } - }, [taskStream.error]); + const canRetry = canReadTask && canCreateTask && isRetryableTask; - useEffect(() => { - if (taskStream.completed && !taskStream.error) { - setLogVisibleState(true); - setButtonBarVisibleState(false); - } - }, [taskStream.error, taskStream.completed]); + const cancelEnabled = !(taskStream.cancelled || taskStream.completed); + const isCancelButtonDisabled = !cancelEnabled || !canCancelTask; const { value: presentation } = reactUseAsync(async () => { const templateEntityRef = taskStream.task?.spec.templateInfo?.entityRef; @@ -177,6 +172,292 @@ function OngoingTaskContent(props: { return entityPresentationApi.forEntity(templateEntityRef).promise; }, [entityPresentationApi, taskStream.task?.spec.templateInfo?.entityRef]); + const templateRouteRef = useRouteRef(selectedTemplateRouteRef); + const navigate = useNavigate(); + const analytics = useAnalytics(); + const scaffolderApi = useApi(scaffolderApiRef); + + const startOver = useCallback(() => { + const { namespace, name } = + taskStream.task?.spec.templateInfo?.entity?.metadata ?? {}; + + const formData = taskStream.task?.spec.parameters ?? {}; + + if (!namespace || !name) { + return; + } + + analytics.captureEvent('click', `Task has been started over`); + + navigate({ + pathname: templateRouteRef({ + namespace, + templateName: name, + }), + search: `?${qs.stringify({ formData: JSON.stringify(formData) })}`, + }); + }, [ + analytics, + navigate, + taskStream.task?.spec.parameters, + taskStream.task?.spec.templateInfo?.entity?.metadata, + templateRouteRef, + ]); + + const [, { execute: triggerRetry }] = useAsync(async () => { + if (taskId) { + analytics.captureEvent('retried', 'Template has been retried'); + await scaffolderApi.retry?.(taskId); + } + }); + + const [{ status: cancelStatus }, { execute: triggerCancel }] = useAsync( + async () => { + if (taskId) { + analytics.captureEvent('cancelled', 'Template has been cancelled'); + await scaffolderApi.cancelTask(taskId); + } + }, + ); + + return ( + + + + + ); +} + +function OngoingTaskChrome(props: { + TemplateOutputsComponent?: ComponentType<{ + output?: ScaffolderTaskOutput; + }>; +}) { + const { taskId } = useParams(); + const taskStream = useTaskEventStream(taskId!); + const entityPresentationApi = useApi(entityPresentationApiRef); + const { t } = useTranslationRef(scaffolderTranslationRef); + + const [logsVisible, setLogVisibleState] = useState(false); + const [buttonBarVisible, setButtonBarVisibleState] = useState(true); + + const { allowed: canCancelTask } = usePermission({ + permission: taskCancelPermission, + resourceRef: taskId, + }); + + const { allowed: canReadTask } = usePermission({ + permission: taskReadPermission, + resourceRef: taskId, + }); + + const { allowed: canCreateTask } = usePermission({ + permission: taskCreatePermission, + }); + + const isRetryableTask = + taskStream.task?.spec.EXPERIMENTAL_recovery?.EXPERIMENTAL_strategy === + 'startOver'; + + const canRetry = canReadTask && canCreateTask && isRetryableTask; + + const cancelEnabled = !(taskStream.cancelled || taskStream.completed); + const isCancelButtonDisabled = !cancelEnabled || !canCancelTask; + + const { value: presentation } = reactUseAsync(async () => { + const templateEntityRef = taskStream.task?.spec.templateInfo?.entityRef; + if (!templateEntityRef) { + return undefined; + } + return entityPresentationApi.forEntity(templateEntityRef).promise; + }, [entityPresentationApi, taskStream.task?.spec.templateInfo?.entityRef]); + + const templateRouteRef = useRouteRef(selectedTemplateRouteRef); + const navigate = useNavigate(); + const analytics = useAnalytics(); + const scaffolderApi = useApi(scaffolderApiRef); + + const startOver = useCallback(() => { + const { namespace, name } = + taskStream.task?.spec.templateInfo?.entity?.metadata ?? {}; + + const formData = taskStream.task?.spec.parameters ?? {}; + + if (!namespace || !name) { + return; + } + + analytics.captureEvent('click', `Task has been started over`); + + navigate({ + pathname: templateRouteRef({ + namespace, + templateName: name, + }), + search: `?${qs.stringify({ formData: JSON.stringify(formData) })}`, + }); + }, [ + analytics, + navigate, + taskStream.task?.spec.parameters, + taskStream.task?.spec.templateInfo?.entity?.metadata, + templateRouteRef, + ]); + + const [, { execute: triggerRetry }] = useAsync(async () => { + if (taskId) { + analytics.captureEvent('retried', 'Template has been retried'); + await scaffolderApi.retry?.(taskId); + } + }); + + const [{ status: cancelStatus }, { execute: triggerCancel }] = useAsync( + async () => { + if (taskId) { + analytics.captureEvent('cancelled', 'Template has been cancelled'); + await scaffolderApi.cancelTask(taskId); + } + }, + ); + + return ( + <> +
    + {t('ongoingTask.title')}{' '} + {presentation ? presentation.primaryTitle : ''} +
    + } + subtitle={t('ongoingTask.subtitle', { taskId: taskId as string })} + > + + + + + ); +} + +function OngoingTaskContent(props: { + TemplateOutputsComponent?: ComponentType<{ + output?: ScaffolderTaskOutput; + }>; + logsVisibleOverride?: boolean; + buttonBarVisibleOverride?: boolean; + onToggleLogs?: (state: boolean) => void; + onToggleButtonBar?: (state: boolean) => void; +}) { + const { taskId } = useParams(); + const templateRouteRef = useRouteRef(selectedTemplateRouteRef); + const navigate = useNavigate(); + const analytics = useAnalytics(); + const scaffolderApi = useApi(scaffolderApiRef); + const taskStream = useTaskEventStream(taskId!); + const classes = useStyles(); + const steps = useMemo( + () => + taskStream.task?.spec.steps.map(step => ({ + ...step, + ...taskStream?.steps?.[step.id], + })) ?? [], + [taskStream], + ); + const { t } = useTranslationRef(scaffolderTranslationRef); + + const [logsVisibleLocal, setLogVisibleStateLocal] = useState(false); + const [buttonBarVisibleLocal, setButtonBarVisibleStateLocal] = useState(true); + + const logsVisible = props.logsVisibleOverride ?? logsVisibleLocal; + const setLogVisibleState = props.onToggleLogs ?? setLogVisibleStateLocal; + const buttonBarVisible = + props.buttonBarVisibleOverride ?? buttonBarVisibleLocal; + const setButtonBarVisibleState = + props.onToggleButtonBar ?? setButtonBarVisibleStateLocal; + + const { allowed: canCancelTask } = usePermission({ + permission: taskCancelPermission, + resourceRef: taskId, + }); + + const { allowed: canReadTask } = usePermission({ + permission: taskReadPermission, + resourceRef: taskId, + }); + + const { allowed: canCreateTask } = usePermission({ + permission: taskCreatePermission, + }); + + const canStartOver = canReadTask && canCreateTask; + + useEffect(() => { + if (taskStream.error) { + setLogVisibleState(true); + } + }, [taskStream.error, setLogVisibleState]); + + useEffect(() => { + if (taskStream.completed && !taskStream.error) { + setLogVisibleState(true); + setButtonBarVisibleState(false); + } + }, [ + taskStream.error, + taskStream.completed, + setLogVisibleState, + setButtonBarVisibleState, + ]); + const activeStep = useMemo(() => { for (let i = steps.length - 1; i >= 0; i--) { if (steps[i].status !== 'open') { @@ -239,124 +520,89 @@ function OngoingTaskContent(props: { const Outputs = props.TemplateOutputsComponent ?? DefaultTemplateOutputs; const cancelEnabled = !(taskStream.cancelled || taskStream.completed); - const isCancelButtonDisabled = - !cancelEnabled || cancelStatus !== 'not-executed' || !canCancelTask; return ( - <> -
    - {t('ongoingTask.title')}{' '} - {presentation ? presentation.primaryTitle : ''} -
    - } - subtitle={t('ongoingTask.subtitle', { taskId: taskId as string })} - > - - - - {taskStream.error ? ( - - - - ) : null} - + + {taskStream.error ? ( - + ) : null} - + + + - {buttonBarVisible ? ( - - - -
    - - {isRetryableTask && ( - - )} - - -
    -
    -
    -
    - ) : null} + - {logsVisible ? ( - - - + {buttonBarVisible ? ( + + + +
    + + {isRetryableTask && ( + + )} + + +
    - ) : null} -
    - +
    + ) : null} + + {logsVisible ? ( + + + + + + ) : null} + ); } diff --git a/plugins/scaffolder/src/components/OngoingTask/OngoingTaskContextMenu.tsx b/plugins/scaffolder/src/components/OngoingTask/OngoingTaskContextMenu.tsx new file mode 100644 index 0000000000..86f2238a38 --- /dev/null +++ b/plugins/scaffolder/src/components/OngoingTask/OngoingTaskContextMenu.tsx @@ -0,0 +1,133 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Header, MenuTrigger, ButtonIcon, Menu, MenuItem } from '@backstage/ui'; +import { + RiMore2Line, + RiFileListLine, + RiAddCircleLine, + RiRepeatLine, + RiReplay10Line, + RiCloseCircleLine, +} from '@remixicon/react'; +import { usePermission } from '@backstage/plugin-permission-react'; +import { + taskReadPermission, + taskCreatePermission, +} from '@backstage/plugin-scaffolder-common/alpha'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../translation'; + +type OngoingTaskContextMenuProps = { + title: string; + cancelEnabled?: boolean; + canRetry: boolean; + isRetryableTask: boolean; + logsVisible?: boolean; + buttonBarVisible?: boolean; + onRetry?: () => void; + onStartOver?: () => void; + onToggleLogs?: (state: boolean) => void; + onToggleButtonBar?: (state: boolean) => void; + taskId?: string; + isCancelButtonDisabled: boolean; + onCancel: () => void; +}; + +export function OngoingTaskContextMenu(props: OngoingTaskContextMenuProps) { + const { + title, + cancelEnabled, + canRetry, + isRetryableTask, + logsVisible, + buttonBarVisible, + onRetry, + onStartOver, + onToggleLogs, + onToggleButtonBar, + taskId, + } = props; + const { t } = useTranslationRef(scaffolderTranslationRef); + + const { allowed: canReadTask } = usePermission({ + permission: taskReadPermission, + resourceRef: taskId, + }); + + const { allowed: canCreateTask } = usePermission({ + permission: taskCreatePermission, + }); + + const canStartOver = canReadTask && canCreateTask; + + return ( +
    + } + aria-label={t('ongoingTask.contextMenu.moreOptions')} + /> + + onToggleLogs?.(!logsVisible)} + iconStart={} + > + {logsVisible + ? t('ongoingTask.contextMenu.hideLogs') + : t('ongoingTask.contextMenu.showLogs')} + + onToggleButtonBar?.(!buttonBarVisible)} + iconStart={} + > + {buttonBarVisible + ? t('ongoingTask.contextMenu.hideButtonBar') + : t('ongoingTask.contextMenu.showButtonBar')} + + } + > + {t('ongoingTask.contextMenu.startOver')} + + {isRetryableTask && ( + } + > + {t('ongoingTask.contextMenu.retry')} + + )} + } + color="danger" + > + {t('ongoingTask.contextMenu.cancel')} + + + + } + /> + ); +} diff --git a/plugins/scaffolder/src/components/OngoingTask/index.ts b/plugins/scaffolder/src/components/OngoingTask/index.ts index f0c7e2a6cc..cd3ed9a5b3 100644 --- a/plugins/scaffolder/src/components/OngoingTask/index.ts +++ b/plugins/scaffolder/src/components/OngoingTask/index.ts @@ -13,4 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { OngoingTask } from './OngoingTask'; +export { OngoingTask, OngoingTaskBody } from './OngoingTask'; diff --git a/plugins/scaffolder/src/components/RenderSchema/RenderSchema.test.tsx b/plugins/scaffolder/src/components/RenderSchema/RenderSchema.test.tsx index ec49f414f9..4c91befb55 100644 --- a/plugins/scaffolder/src/components/RenderSchema/RenderSchema.test.tsx +++ b/plugins/scaffolder/src/components/RenderSchema/RenderSchema.test.tsx @@ -14,8 +14,6 @@ * limitations under the License. */ import { renderInTestApp } from '@backstage/test-utils'; -import Typography from '@material-ui/core/Typography'; -import { makeStyles } from '@material-ui/core/styles'; import { BoundFunction, queries, @@ -28,42 +26,14 @@ import { JSONSchema7Definition, JSONSchema7Type, } from 'json-schema'; -import { capitalize } from 'lodash'; import { useState } from 'react'; import { Expanded, SchemaRenderStrategy } from '.'; import { RenderEnum, RenderSchema } from './RenderSchema'; /* eslint jest/expect-expect: ["error", { "assertFunctionNames": ["expect", "assert*"] }] */ -const useStyles = makeStyles(theme => ({ - code: { - fontFamily: 'Menlo, monospace', - padding: theme.spacing(1), - backgroundColor: - theme.palette.type === 'dark' - ? theme.palette.grey[700] - : theme.palette.grey[300], - display: 'inline-block', - borderRadius: 5, - border: `1px solid ${theme.palette.grey[500]}`, - position: 'relative', - }, - - codeRequired: { - '&::after': { - position: 'absolute', - content: '"*"', - top: 0, - right: theme.spacing(0.5), - fontWeight: 'bolder', - color: theme.palette.error.light, - }, - }, -})); - const LocalRenderEnum = ({ e }: { e: JSONSchema7Type[] }) => { - const classes = useStyles(); - return ; + return ; }; const LocalRenderSchema = ({ @@ -73,7 +43,6 @@ const LocalRenderSchema = ({ strategy: SchemaRenderStrategy; schema?: JSONSchema7Definition; }) => { - const classes = useStyles(); const expanded = useState({}); return ( ], }, }} /> @@ -119,19 +86,18 @@ it('enum rendering', async () => { expect(el).toBeInTheDocument(); expect(el).toHaveTextContent(JSON.stringify(each.value)); - const wrapTextIcon = getByTestId(`wrap-text_${each.index}`); - expect(wrapTextIcon).toBeInTheDocument(); + const wrapTextButton = getByTestId(`wrap-text_${each.index}`); + expect(wrapTextButton).toBeInTheDocument(); expect(queryByTestId(`pretty_${each.index}`)).toBeNull(); - await userEvent.hover(wrapTextIcon); + wrapTextButton.focus(); const pretty = await findByTestId(`pretty_${each.index}`); expect(pretty).toBeInTheDocument(); - // test textContent directly to avoid ws normalization: expect(pretty.textContent).toBe(JSON.stringify(each.value, null, 2)); - await userEvent.unhover(wrapTextIcon); + wrapTextButton.blur(); await waitFor(() => expect(queryByTestId(`pretty_${each.index}`)).toBeNull(), @@ -162,36 +128,55 @@ describe('JSON schema UI rendering', () => { }, }; type qt = typeof queries; + + const getRowById = (container: HTMLElement, rowId: string): HTMLElement => { + const el = container.querySelector(`[data-key="${rowId}"]`); + if (!el) { + throw new Error(`Could not find row with data-key="${rowId}"`); + } + return el as HTMLElement; + }; + + const queryRowById = ( + container: HTMLElement, + rowId: string, + ): HTMLElement | null => { + return container.querySelector(`[data-key="${rowId}"]`); + }; + + const findRowById = ( + container: HTMLElement, + rowId: string, + ): Promise => { + return waitFor(() => getRowById(container, rowId)); + }; + const assertBasicSchemaProperties = ( q: { [P in keyof qt]: BoundFunction; - }, + } & { container: HTMLElement }, id: string, ) => { - const { getByTestId, queryByTestId } = q; - const t = getByTestId(`properties_${id}`); - expect(t).toBeInTheDocument(); - expect(t.tagName).toBe('TABLE'); - - expect( - Array.from(t.querySelectorAll('thead > tr > th'), e => e.textContent), - ).toEqual(['Name', 'Title', 'Description', 'Type']); + const { container, queryByTestId } = q; for (const p of Object.keys(basic.properties!)) { - const tr = getByTestId(`properties-row_${id}.${p}`); + const tr = getRowById(container, `properties-row_${id}.${p}`); expect(tr).toBeInTheDocument(); - expect(tr.tagName).toBe('TR'); const pt = (basic.properties![p] as JSONSchema7).type; + const headerCells = within(tr).queryAllByRole('rowheader'); + const dataCells = within(tr).getAllByRole('gridcell'); + const allCells = [...headerCells, ...dataCells]; + const cellTexts = allCells.map(c => c.textContent); - expect(Array.from(tr.querySelectorAll('td'), n => n.textContent)).toEqual( - [p, capitalize(p), `the ${pt}`, pt], - ); - expect( - Array.from(within(tr).getByText(p).classList).some(c => - c.includes('codeRequired'), - ), - ).toBe(basic.required?.includes(p)); + expect(cellTexts[0]).toContain(p); + const valueCell = cellTexts[1]; + expect(valueCell).toContain(String(pt)); + expect(valueCell).toContain(`the ${pt}`); + + if (basic.required?.includes(p)) { + expect(cellTexts[0]).toContain('*'); + } expect(queryByTestId(`expand_${id}.${p}`)).not.toBeInTheDocument(); } @@ -235,26 +220,18 @@ describe('JSON schema UI rendering', () => { const assertDeepSchemaProperties = async ( q: { [P in keyof qt]: BoundFunction; - }, + } & { container: HTMLElement }, id: string, ) => { - const { findByTestId, getByTestId, queryByTestId } = q; - const t = getByTestId(`properties_${id}`); - expect(t).toBeInTheDocument(); - expect(t.tagName).toBe('TABLE'); - - expect( - Array.from(t.querySelectorAll('thead > tr > th'), n => n.textContent), - ).toEqual(['Name', 'Title', 'Description', 'Type']); + const { container, findByTestId, getByTestId, queryByTestId } = q; const xp: (k: string) => Promise<{ expander: HTMLElement; expansionId: string; expansion: HTMLElement; }> = async (k: string) => { - const r = getByTestId(`properties-row_${id}.${k}`); + const r = getRowById(container, `properties-row_${id}.${k}`); expect(r).toBeInTheDocument(); - expect(r.tagName).toBe('TR'); const expansionId = `expansion_${id}.${k}`; @@ -272,7 +249,10 @@ describe('JSON schema UI rendering', () => { const b = await xp('basic'); const m = await xp('msvs'); - assertBasicSchemaProperties(within(b.expansion), `${id}.basic`); + assertBasicSchemaProperties( + { ...within(b.expansion), container: b.expansion }, + `${id}.basic`, + ); assertMsv(within(m.expansion), `${id}.msvs`); await userEvent.click(b.expander); @@ -311,19 +291,6 @@ describe('JSON schema UI rendering', () => { const t = getByTestId('root_test'); expect(t).toBeInTheDocument(); - expect(t.tagName).toBe('TABLE'); - - expect( - Array.from(t.querySelectorAll('thead > tr > th'), n => n.textContent), - ).toEqual(['Type']); - - const tr = getByTestId('root-row_test'); - expect(tr).toBeInTheDocument(); - expect(tr.tagName).toBe('TR'); - - expect(Array.from(tr.querySelectorAll('td'), n => n.textContent)).toEqual( - ['object'], - ); expect(queryByTestId('expansion_test')).not.toBeInTheDocument(); @@ -335,7 +302,10 @@ describe('JSON schema UI rendering', () => { const expanded = await findByTestId('expansion_test'); expect(expanded).toBeInTheDocument(); - assertBasicSchemaProperties(within(expanded), 'test'); + assertBasicSchemaProperties( + { ...within(expanded), container: expanded }, + 'test', + ); await userEvent.click(expand); @@ -349,18 +319,6 @@ describe('JSON schema UI rendering', () => { const t = getByTestId('root_test'); expect(t).toBeInTheDocument(); - expect(t.tagName).toBe('TABLE'); - - expect( - Array.from(t.querySelectorAll('thead > tr > th'), n => n.textContent), - ).toEqual(['Title', 'Description', 'Type']); - const tr = getByTestId('root-row_test'); - expect(tr).toBeInTheDocument(); - expect(tr.tagName).toBe('TR'); - - expect(Array.from(tr.querySelectorAll('td'), n => n.textContent)).toEqual( - [msv.title, msv.description, 'string'], - ); expect(queryByTestId('expansion_test')).not.toBeInTheDocument(); @@ -385,18 +343,7 @@ describe('JSON schema UI rendering', () => { const t = getByTestId('root_test'); expect(t).toBeInTheDocument(); - expect(t.tagName).toBe('TABLE'); - expect( - Array.from(t.querySelectorAll('thead > tr > th'), n => n.textContent), - ).toEqual(['Title', 'Description', 'Type']); - const tr = getByTestId('root-row_test'); - expect(tr).toBeInTheDocument(); - expect(tr.tagName).toBe('TR'); - - expect(Array.from(tr.querySelectorAll('td'), n => n.textContent)).toEqual( - [deep.title, deep.description, 'object'], - ); expect(queryByTestId('expansion_test')).not.toBeInTheDocument(); const expand = getByTestId('expand_test'); @@ -407,7 +354,10 @@ describe('JSON schema UI rendering', () => { const expansion = await findByTestId('expansion_test'); expect(expansion).toBeInTheDocument(); - await assertDeepSchemaProperties(within(expansion), 'test'); + await assertDeepSchemaProperties( + { ...within(expansion), container: expansion }, + 'test', + ); await userEvent.click(expand); @@ -469,25 +419,44 @@ describe('JSON schema UI rendering', () => { const rendered = await renderInTestApp( , ); - const { getByTestId } = rendered; + const { container, getByTestId } = rendered; + + for (const k of Object.keys(schema.properties!)) { + const tr = getRowById(container, `properties-row_test.${k}`); + expect(tr).toBeInTheDocument(); + } + + expect( + queryRowById(container, 'properties-row_test_oneOf0.foo'), + ).not.toBeInTheDocument(); + + const expandOneOf = getByTestId('expand_test_oneOf'); + await userEvent.click(expandOneOf); for (const [i, k] of Object.keys(schema.properties!).entries()) { - const tr = getByTestId(`properties-row_test.${k}`); - expect(tr).toBeInTheDocument(); - - const sub = getByTestId(`properties-row_test_oneOf${i}.${k}`); + const sub = await findRowById( + container, + `properties-row_test_oneOf${i}.${k}`, + ); expect(sub).toBeInTheDocument(); - expect( - Array.from(within(sub).getByText(k).classList).some(c => - c.includes('codeRequired'), - ), - ).toBe(true); + const nameCell = + within(sub).queryAllByRole('rowheader')[0] ?? + within(sub).getAllByRole('gridcell')[0]; + expect(nameCell.textContent).toContain('*'); expect( - getByTestId(`properties-row_test_oneOf${i}.${k}Flag`), + getRowById(container, `properties-row_test_oneOf${i}.${k}Flag`), ).toBeInTheDocument(); } + + await userEvent.click(expandOneOf); + + await waitFor(() => { + expect( + queryRowById(container, 'properties-row_test_oneOf0.foo'), + ).not.toBeInTheDocument(); + }); }); it('full oneOf', async () => { const schema: JSONSchema7 = { @@ -524,11 +493,18 @@ describe('JSON schema UI rendering', () => { const rendered = await renderInTestApp( , ); + + const expandOneOf = rendered.getByTestId('expand_test_oneOf'); + await userEvent.click(expandOneOf); + const subs = schema.oneOf as JSONSchema7[]; for (const i of subs.keys()) { for (const k of Object.keys(subs[i].properties!)) { expect( - rendered.getByTestId(`properties-row_test_oneOf${i}.${k}`), + await findRowById( + rendered.container, + `properties-row_test_oneOf${i}.${k}`, + ), ).toBeInTheDocument(); } } @@ -551,11 +527,19 @@ describe('JSON schema UI rendering', () => { const rendered = await renderInTestApp( , ); + expect( - rendered.getByTestId(`root-row_test.bs_anyOf0`), + queryRowById(rendered.container, 'root-row_test.bs_anyOf0'), + ).not.toBeInTheDocument(); + + const expandAnyOf = rendered.getByTestId('expand_test.bs_anyOf'); + await userEvent.click(expandAnyOf); + + expect( + await findRowById(rendered.container, 'root-row_test.bs_anyOf0'), ).toBeInTheDocument(); expect( - rendered.getByTestId(`root-row_test.bs_anyOf1`), + getRowById(rendered.container, 'root-row_test.bs_anyOf1'), ).toBeInTheDocument(); }); }); diff --git a/plugins/scaffolder/src/components/RenderSchema/RenderSchema.tsx b/plugins/scaffolder/src/components/RenderSchema/RenderSchema.tsx index 2d4ebd6fca..0383d46c81 100644 --- a/plugins/scaffolder/src/components/RenderSchema/RenderSchema.tsx +++ b/plugins/scaffolder/src/components/RenderSchema/RenderSchema.tsx @@ -15,36 +15,25 @@ */ import { MarkdownContent } from '@backstage/core-components'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; -import Box from '@material-ui/core/Box'; -import Chip from '@material-ui/core/Chip'; -import Collapse from '@material-ui/core/Collapse'; -import IconButton from '@material-ui/core/IconButton'; -import List from '@material-ui/core/List'; -import ListItem from '@material-ui/core/ListItem'; -import Paper from '@material-ui/core/Paper'; -import Table from '@material-ui/core/Table'; -import TableBody from '@material-ui/core/TableBody'; -import TableCell from '@material-ui/core/TableCell'; -import TableContainer from '@material-ui/core/TableContainer'; -import TableHead from '@material-ui/core/TableHead'; -import TableRow from '@material-ui/core/TableRow'; -import Tooltip from '@material-ui/core/Tooltip'; -import Typography from '@material-ui/core/Typography'; -import { makeStyles } from '@material-ui/core/styles'; -import { ClassNameMap } from '@material-ui/core/styles/withStyles'; -import ExpandLessIcon from '@material-ui/icons/ExpandLess'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; -import WrapText from '@material-ui/icons/WrapText'; -import classNames from 'classnames'; +import { + Button, + Cell, + CellText, + ColumnConfig, + Flex, + Table, + Text, + Tooltip, + TooltipTrigger, +} from '@backstage/ui'; import { JSONSchema7, JSONSchema7Definition, JSONSchema7Type, } from 'json-schema'; -import { FC, JSX, cloneElement, Fragment, ReactElement } from 'react'; +import { FC } from 'react'; import { scaffolderTranslationRef } from '../../translation'; import { SchemaRenderContext, SchemaRenderStrategy } from './types'; -import { TranslationMessages } from '../TemplatingExtensionsPage/types'; const compositeSchemaProperties = ['allOf', 'anyOf', 'not', 'oneOf'] as const; @@ -131,80 +120,22 @@ const getSubschemas = (schema: JSONSchema7Definition): subSchemasType => { ); }; -const useColumnStyles = makeStyles({ - description: { - width: '40%', - whiteSpace: 'normal', - wordWrap: 'break-word', - '&.MuiTableCell-root': { - whiteSpace: 'normal', - }, - }, - standard: { - whiteSpace: 'normal', - }, -}); - -type SchemaRenderElement = { +interface SchemaTableItem { + id: string; schema: JSONSchema7Definition; - key?: string; + propKey?: string; required?: boolean; -}; - -type RenderColumn = ( - element: SchemaRenderElement, - context: SchemaRenderContext, -) => JSX.Element; - -type Column = { - key: string; - title: (t: TranslationMessages) => string; - render: RenderColumn; - className?: keyof ReturnType; -}; +} const generateId = ( - element: SchemaRenderElement, + item: { propKey?: string }, context: SchemaRenderContext, ) => { - return element.key ? `${context.parentId}.${element.key}` : context.parentId; + return item.propKey + ? `${context.parentId}.${item.propKey}` + : context.parentId; }; -const nameColumn = { - key: 'name', - title: t => t('renderSchema.tableCell.name'), - render: (element: SchemaRenderElement, context: SchemaRenderContext) => { - return ( -
    - {element.key} -
    - ); - }, -} as Column; - -const titleColumn = { - key: 'title', - title: t => t('renderSchema.tableCell.title'), - render: (element: SchemaRenderElement) => ( - - ), -} as Column; - -const descriptionColumn = { - key: 'description', - title: t => t('renderSchema.tableCell.description'), - render: (element: SchemaRenderElement) => ( - - ), - className: 'description', -} as Column; - const enumFrom = (schema: JSONSchema7) => { if (schema.type === 'array') { if (schema.items && typeof schema.items !== 'boolean') { @@ -242,107 +173,58 @@ const inspectSchema = ( }; }; -const typeColumn = { - key: 'type', - title: t => t('renderSchema.tableCell.type'), - render: (element: SchemaRenderElement, context: SchemaRenderContext) => { - if (typeof element.schema === 'boolean') { - return {element.schema ? 'any' : 'none'}; - } - const types = getTypes(element.schema); - const [isExpanded, setIsExpanded] = context.expanded; - const id = generateId(element, context); - const info = inspectSchema(element.schema); - return ( - <> - {types?.map((type, index) => - info.canSubschema || (info.hasEnum && index === 0) ? ( - : } - variant="outlined" - onClick={() => - setIsExpanded(prevState => { - return { - ...prevState, - [id]: !!!prevState[id], - }; - }) - } - /> - ) : ( - - ), - )} - - ); - }, -} as Column; - export const RenderEnum: FC<{ e: JSONSchema7Type[]; - classes: ClassNameMap; [key: string]: any; -}> = ({ - e, - classes, - ...props -}: { - e: JSONSchema7Type[]; - classes: ClassNameMap; -}) => { +}> = ({ e, ...props }: { e: JSONSchema7Type[] }) => { return ( - +
      {e.map((v, i) => { - let inner: JSX.Element = ( - - {JSON.stringify(v)} - - ); - if (v !== null && ['object', 'array'].includes(typeof v)) { - inner = ( - <> - {inner} - - {JSON.stringify(v, undefined, 2)} - - } + const text = JSON.stringify(v); + const isComplex = v !== null && ['object', 'array'].includes(typeof v); + return ( +
    • + + - - - - - - ); - } - return {inner}; + {text} + + {isComplex && ( + + + + + {JSON.stringify(v, undefined, 2)} + + + + )} + +
    • + ); })} - +
    ); }; -const useTableStyles = makeStyles({ - schema: { - width: '100%', - overflowX: 'hidden', - '& table': { - width: '100%', - tableLayout: 'fixed', - }, - }, -}); - export const RenderSchema = ({ strategy, context, @@ -353,165 +235,228 @@ export const RenderSchema = ({ schema?: JSONSchema7Definition; }) => { const { t } = useTranslationRef(scaffolderTranslationRef); - const tableStyles = useTableStyles(); - const columnStyles = useColumnStyles(); + + const columnConfig: ColumnConfig[] = + strategy === 'root' + ? [ + { + id: 'value', + label: t('renderSchema.tableCell.value'), + isRowHeader: true, + defaultWidth: '3fr', + cell: item => , + }, + ] + : [ + { + id: 'name', + label: t('renderSchema.tableCell.name'), + isRowHeader: true, + defaultWidth: 300, + cell: item => { + const name = item.propKey ?? ''; + return ( + + + {item.required ? `${name} *` : name} + + + ); + }, + }, + { + id: 'value', + label: t('renderSchema.tableCell.value'), + defaultWidth: '1fr', + cell: item => , + }, + ]; + const result = (() => { if (typeof schema === 'object') { const subschemas = getSubschemas(schema); - let columns: Column[] | undefined; - let elements: SchemaRenderElement[] | undefined; + let data: SchemaTableItem[] | undefined; if (strategy === 'root') { if ('type' in schema || !Object.keys(subschemas).length) { - elements = [{ schema }]; - columns = [typeColumn]; - if (schema.description) { - columns.unshift(descriptionColumn); - } - if (schema.title) { - columns.unshift(titleColumn); - } + data = [{ id: `root-row_${context.parentId}`, schema }]; } } else if (schema.properties) { - columns = [nameColumn, titleColumn, descriptionColumn, typeColumn]; - elements = Object.entries(schema.properties!).map(([key, v]) => ({ + data = Object.entries(schema.properties).map(([key, v]) => ({ + id: `${strategy}-row_${context.parentId}.${key}`, schema: v, - key, + propKey: key, required: schema.required?.includes(key), })); } else if (!Object.keys(subschemas).length) { return undefined; } - const [isExpanded] = context.expanded; + const [isExpanded, setIsExpanded] = context.expanded; return ( - <> - {columns && elements && ( - + + {data && ( +
    - - - {columns.map((col, index) => ( - - {col.title(t)} - - ))} - - - - {elements.map(el => { - const id = generateId(el, context); - const info = inspectSchema(el.schema); - const rows = [ - - {columns!.map(col => ( - - {col.render(el, context)} - - ))} - , - ]; - if ( - typeof el.schema !== 'boolean' && - (info.canSubschema || info.hasEnum) - ) { - let details: ReactElement = ( - - {info.canSubschema && ( - - )} - {info.hasEnum && ( - <> - {cloneElement( - context.headings[0], - {}, - 'Valid values:', - )} - - - )} - - ); - if (getTypes(el.schema)) { - details = ( - - {details} - - ); - } - rows.push( - - - {details} - - , - ); - } - return {rows}; - })} - -
    - + columnConfig={columnConfig} + data={data} + pagination={{ type: 'none' }} + /> +
    )} - {(Object.keys(subschemas) as Array).map(sk => ( - - {cloneElement(context.headings[0], {}, sk)} - {subschemas[sk]!.map((sub, index) => ( - ).map(sk => { + const subId = `${context.parentId}_${sk}`; + const isSubOpen = isExpanded[subId]; + return ( + + + {isSubOpen && + subschemas[sk]!.map((sub, index) => ( + + ))} + + ); + })} +
    ); } return undefined; })(); - return result ?? No schema defined; + return result ?? {t('renderSchema.undefined')}; }; + +function RenderExpansion({ + item, + context, +}: { + item: SchemaTableItem; + context: SchemaRenderContext; +}) { + const id = generateId(item, context); + const info = inspectSchema(item.schema); + const hasDetails = + typeof item.schema !== 'boolean' && (info.canSubschema || info.hasEnum); + const s = + typeof item.schema !== 'boolean' ? (item.schema as JSONSchema7) : undefined; + const [isExpanded] = context.expanded; + const isOpen = hasDetails && s && (!getTypes(s) || isExpanded[id]); + + if (!isOpen) { + return null; + } + + return ( +
    + {info.canSubschema && ( + + )} + {info.hasEnum && ( + <> + + Valid values: + + + + )} +
    + ); +} + +function ValueCell({ + item, + context, +}: { + item: SchemaTableItem; + context: SchemaRenderContext; +}) { + if (typeof item.schema === 'boolean') { + return ; + } + const types = getTypes(item.schema); + const [isExpanded, setIsExpanded] = context.expanded; + const id = generateId(item, context); + const info = inspectSchema(item.schema); + const { title, description } = item.schema; + return ( + + + + {types?.map((type, index) => + (info.canSubschema || info.hasEnum) && index === 0 ? ( + + ) : ( + + {type} + + ), + )} + + {title && } + {description && } + + + + ); +} diff --git a/plugins/scaffolder/src/components/RenderSchema/types.ts b/plugins/scaffolder/src/components/RenderSchema/types.ts index 70f3c27dfd..f12f0765ea 100644 --- a/plugins/scaffolder/src/components/RenderSchema/types.ts +++ b/plugins/scaffolder/src/components/RenderSchema/types.ts @@ -13,16 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ClassNameMap } from '@material-ui/core/styles/withStyles'; export type Expanded = { [key: string]: boolean }; export type SchemaRenderContext = { parent?: SchemaRenderContext; parentId: string; - classes: ClassNameMap; expanded: [Expanded, React.Dispatch>]; - headings: [React.JSX.Element, ...React.JSX.Element[]]; }; export type SchemaRenderStrategy = 'root' | 'properties'; diff --git a/plugins/scaffolder/src/components/Router/Router.test.tsx b/plugins/scaffolder/src/components/Router/Router.test.tsx index 20b21fabc7..04f6cac690 100644 --- a/plugins/scaffolder/src/components/Router/Router.test.tsx +++ b/plugins/scaffolder/src/components/Router/Router.test.tsx @@ -13,13 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ReactElement } from 'react'; import { Router } from './Router'; -import { - renderInTestApp, - TestApiProvider, - TestAppOptions, -} from '@backstage/test-utils'; +import { renderInTestApp } from '@backstage/test-utils'; import { createScaffolderFieldExtension, ScaffolderFieldExtensions, @@ -30,23 +25,12 @@ import { ScaffolderLayouts, } from '@backstage/plugin-scaffolder-react'; import { TemplateListPage, TemplateWizardPage } from '../../alpha/components'; -import { formFieldsApiRef } from '@backstage/plugin-scaffolder-react/alpha'; jest.mock('../../alpha/components', () => ({ TemplateWizardPage: jest.fn(() => null), TemplateListPage: jest.fn(() => null), })); -const wrapInApisAndRender = (element: ReactElement, opts?: TestAppOptions) => - renderInTestApp( - [] }]]} - > - {element} - , - opts, - ); - describe('Router', () => { beforeEach(() => { (TemplateWizardPage as jest.Mock).mockClear(); @@ -54,13 +38,13 @@ describe('Router', () => { }); describe('/', () => { it('should render the TemplateListPage', async () => { - await wrapInApisAndRender(); + await renderInTestApp(); expect(TemplateListPage).toHaveBeenCalled(); }); it('should render user-provided TemplateListPage', async () => { - const { getByText } = await wrapInApisAndRender( + const { getByText } = await renderInTestApp( <>foobar, @@ -76,7 +60,7 @@ describe('Router', () => { it('should render not found error page', async () => { await expect( - wrapInApisAndRender(, { + renderInTestApp(, { routeEntries: ['/foonotfounderror'], }), ).rejects.toThrow('Reached NotFound Page'); @@ -85,7 +69,7 @@ describe('Router', () => { describe('/templates/:templateName', () => { it('should render the TemplateWizard page', async () => { - await wrapInApisAndRender(, { + await renderInTestApp(, { routeEntries: ['/templates/default/foo'], }); @@ -93,7 +77,7 @@ describe('Router', () => { }); it('should render user-provided TemplateWizardPage', async () => { - const { getByText } = await wrapInApisAndRender( + const { getByText } = await renderInTestApp( <>foobar, @@ -110,7 +94,7 @@ describe('Router', () => { it('should pass through the FormProps property', async () => { const transformErrorsMock = jest.fn(); - await wrapInApisAndRender( + await renderInTestApp( { }), ); - await wrapInApisAndRender( + await renderInTestApp( @@ -170,7 +154,7 @@ describe('Router', () => { }), ); - await wrapInApisAndRender( + await renderInTestApp( diff --git a/plugins/scaffolder/src/components/Router/Router.tsx b/plugins/scaffolder/src/components/Router/Router.tsx index 7a2496c046..0fa43901bf 100644 --- a/plugins/scaffolder/src/components/Router/Router.tsx +++ b/plugins/scaffolder/src/components/Router/Router.tsx @@ -63,7 +63,6 @@ import { RequirePermission } from '@backstage/plugin-permission-react'; import { templateManagementPermission } from '@backstage/plugin-scaffolder-common/alpha'; import { useApp } from '@backstage/core-plugin-api'; import { OpaqueFormField } from '@internal/scaffolder'; -import { useAsync, useMountEffect } from '@react-hookz/web'; import { TemplatingExtensionsPage } from '../TemplatingExtensionsPage'; import { FormField } from '@backstage/plugin-scaffolder-react/alpha'; @@ -116,7 +115,9 @@ export type RouterProps = { */ export const InternalRouter = ( props: PropsWithChildren< - RouterProps & { formFieldLoaders?: Array<() => Promise> } + RouterProps & { + formFields?: Array; + } >, ) => { const { @@ -133,14 +134,13 @@ export const InternalRouter = ( } = props; const outlet = useOutlet() || props.children; const customFieldExtensions = useCustomFieldExtensions(outlet); - const loadedFieldExtensions = useFormFieldLoaders(props.formFieldLoaders); const app = useApp(); const { NotFoundErrorPage } = app.getComponents(); const fieldExtensions = [ ...customFieldExtensions, - ...loadedFieldExtensions, + ...(props.formFields?.map(OpaqueFormField.toInternal) ?? []), ...DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS.filter( ({ name }) => !customFieldExtensions.some( @@ -261,17 +261,3 @@ export const InternalRouter = ( export const Router = (props: PropsWithChildren) => { return ; }; - -function useFormFieldLoaders( - formFieldLoaders?: Array<() => Promise>, -) { - const [{ result: loadedFieldExtensions }, { execute }] = - useAsync(async () => { - const loaded = await Promise.all( - (formFieldLoaders ?? []).map(loader => loader()), - ); - return loaded.map(f => OpaqueFormField.toInternal(f)); - }, []); - useMountEffect(execute); - return loadedFieldExtensions; -} diff --git a/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx b/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx index 5968f1da4f..cc45547827 100644 --- a/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx +++ b/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx @@ -23,9 +23,10 @@ import { EntityKindFilter, } from '@backstage/plugin-catalog-react'; import { MockEntityListContextProvider } from '@backstage/plugin-catalog-react/testUtils'; -import { AlertApi, alertApiRef } from '@backstage/core-plugin-api'; +import { alertApiRef } from '@backstage/core-plugin-api'; import { ApiProvider } from '@backstage/core-app-api'; import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils'; +import { mockApis } from '@backstage/frontend-test-utils'; import { GetEntityFacetsResponse } from '@backstage/catalog-client'; const entities: Entity[] = [ @@ -75,12 +76,7 @@ const apis = TestApiRegistry.from( } as GetEntityFacetsResponse), }, ], - [ - alertApiRef, - { - post: jest.fn(), - } as unknown as AlertApi, - ], + [alertApiRef, mockApis.alert()], ); describe('', () => { diff --git a/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplateFilters.tsx b/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplateFilters.tsx index 05f3043dd1..c3d425ae44 100644 --- a/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplateFilters.tsx +++ b/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplateFilters.tsx @@ -55,9 +55,7 @@ const FilterDetailContent = ({ } const schema = filter.schema; const partialSchemaRenderContext: Omit = { - classes, expanded, - headings: [], }; return ( @@ -97,7 +95,6 @@ const FilterDetailContent = ({ context={{ parentId: `${name}.arg${i}`, ...partialSchemaRenderContext, - headings: [], }} schema={argSchema} /> diff --git a/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplateGlobals.tsx b/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplateGlobals.tsx index 249e2a581d..e274b11944 100644 --- a/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplateGlobals.tsx +++ b/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplateGlobals.tsx @@ -58,9 +58,7 @@ const FunctionDetailContent = ({ } const schema = fn.schema; const partialSchemaRenderContext: Omit = { - classes, expanded, - headings: [], }; return ( @@ -88,7 +86,6 @@ const FunctionDetailContent = ({ context={{ parentId: `${name}.arg${i}`, ...partialSchemaRenderContext, - headings: [], }} schema={argSchema} /> diff --git a/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplatingExtensionsPage.test.tsx b/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplatingExtensionsPage.test.tsx index 2c0680b5f8..e125c96c98 100644 --- a/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplatingExtensionsPage.test.tsx +++ b/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplatingExtensionsPage.test.tsx @@ -40,6 +40,8 @@ const scaffolderApiMock: jest.Mocked = { listTemplatingExtensions, listTasks: jest.fn(), autocomplete: jest.fn(), + retry: jest.fn(), + dryRun: jest.fn(), }; const mockPermissionApi = { authorize: jest.fn() }; diff --git a/plugins/scaffolder/src/components/TemplatingExtensionsPage/index.ts b/plugins/scaffolder/src/components/TemplatingExtensionsPage/index.ts index 2573c9f0b5..497d7f9248 100644 --- a/plugins/scaffolder/src/components/TemplatingExtensionsPage/index.ts +++ b/plugins/scaffolder/src/components/TemplatingExtensionsPage/index.ts @@ -13,4 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { TemplatingExtensionsPage } from './TemplatingExtensionsPage'; +export { + TemplatingExtensionsPage, + TemplatingExtensionsPageContent, +} from './TemplatingExtensionsPage'; diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx index 97c90b2aa5..4b272f1aa0 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx @@ -378,6 +378,100 @@ describe('', () => { }); }); + describe('ui:autoSelect behavior', () => { + beforeEach(() => { + uiSchema = { + 'ui:options': { + defaultKind: 'Group', + }, + }; + props = { + onChange, + schema, + required, + uiSchema, + rawErrors, + formData, + } as unknown as FieldProps; + + catalogApi.getEntities.mockResolvedValue({ items: entities }); + }); + + it('default behavior', async () => { + const { getByRole } = await renderInTestApp( + + + , + ); + + const input = getByRole('textbox'); + + // Type partial match and blur + fireEvent.change(input, { target: { value: 'team' } }); + fireEvent.blur(input); + + // Default behavior with freeSolo enabled processes the typed value + expect(onChange).toHaveBeenCalledWith('group:default/team'); + }); + + it('does not autoSelect value onBlur', async () => { + uiSchema = { + 'ui:options': { + defaultKind: 'Group', + autoSelect: false, + }, + }; + props = { + ...props, + uiSchema, + } as unknown as FieldProps; + + const { getByRole } = await renderInTestApp( + + + , + ); + + const input = getByRole('textbox'); + + // Type and blur - with autoSelect=false, the autocomplete won't auto-select on blur + fireEvent.change(input, { target: { value: 'team' } }); + fireEvent.blur(input); + + // With autoSelect=false, onChange should not be called on blur + // This is the key difference - users must explicitly select an option + expect(onChange).not.toHaveBeenCalled(); + }); + + it('autoSelects entity onBlur', async () => { + uiSchema = { + 'ui:options': { + defaultKind: 'Group', + autoSelect: true, + }, + }; + props = { + ...props, + uiSchema, + } as unknown as FieldProps; + + const { getByRole } = await renderInTestApp( + + + , + ); + + const input = getByRole('textbox'); + + // Type and blur + fireEvent.change(input, { target: { value: 'squad' } }); + fireEvent.blur(input); + + // With autoSelect=true and freeSolo, processes the typed value + expect(onChange).toHaveBeenCalledWith('group:default/squad'); + }); + }); + describe('uses full entity ref', () => { beforeEach(() => { uiSchema = { diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx index d2197d4426..9a3dbd0009 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx @@ -74,6 +74,7 @@ export const EntityPicker = (props: EntityPickerProps) => { const defaultKind = uiSchema['ui:options']?.defaultKind; const defaultNamespace = uiSchema['ui:options']?.defaultNamespace || undefined; + const autoSelect = uiSchema?.['ui:options']?.autoSelect ?? true; const isDisabled = uiSchema?.['ui:disabled'] ?? false; const catalogApi = useApi(catalogApiRef); @@ -209,7 +210,7 @@ export const EntityPicker = (props: EntityPickerProps) => { : entities?.entityRefToPresentation.get(stringifyEntityRef(option)) ?.entityRef! } - autoSelect + autoSelect={autoSelect} freeSolo={allowArbitraryValues} renderInput={params => ( @@ -61,6 +61,12 @@ export const EntityPickerFieldSchema = makeFieldSchema({ ) .optional() .describe('List of key-value filter expression for entities'), + autoSelect: z + .boolean() + .optional() + .describe( + 'Whether to automatically select an option on blur. Defaults to true.', + ), }), }); diff --git a/plugins/scaffolder/src/components/fields/MultiEntityPicker/schema.ts b/plugins/scaffolder/src/components/fields/MultiEntityPicker/schema.ts index 667b4638b8..d07e2d031c 100644 --- a/plugins/scaffolder/src/components/fields/MultiEntityPicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/MultiEntityPicker/schema.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { z as zod } from 'zod'; +import { z as zod } from 'zod/v3'; import { makeFieldSchema } from '@backstage/plugin-scaffolder-react'; export const entityQueryFilterExpressionSchema = zod.record( diff --git a/plugins/scaffolder/src/components/fields/RepoOwnerPicker/DefaultRepoOwnerPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/DefaultRepoOwnerPicker.test.tsx new file mode 100644 index 0000000000..2e335a44a3 --- /dev/null +++ b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/DefaultRepoOwnerPicker.test.tsx @@ -0,0 +1,72 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { fireEvent, screen } from '@testing-library/react'; +import { renderInTestApp } from '@backstage/test-utils'; + +import { DefaultRepoOwnerPicker } from './DefaultRepoOwnerPicker'; + +describe('DefaultRepoOwnerPicker', () => { + it('renders an input field', async () => { + const { getByRole } = await renderInTestApp( + , + ); + + expect(getByRole('textbox')).toBeInTheDocument(); + expect(getByRole('textbox')).toHaveValue('owner1'); + }); + + it('input field disabled', async () => { + await renderInTestApp( + , + ); + + const input = screen.getByRole('textbox'); + + // Expect input to be disabled + expect(input).toBeDisabled(); + expect(input).toHaveValue('owner1'); + }); + + it('calls onChange when the input field changes', async () => { + const onChange = jest.fn(); + + const { getByRole } = await renderInTestApp( + , + ); + + const input = getByRole('textbox'); + + fireEvent.change(input, { + target: { value: 'owner2' }, + }); + + expect(onChange).toHaveBeenCalledWith({ owner: 'owner2' }); + }); +}); diff --git a/plugins/scaffolder/src/components/fields/RepoOwnerPicker/DefaultRepoOwnerPicker.tsx b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/DefaultRepoOwnerPicker.tsx new file mode 100644 index 0000000000..48d7da8ba8 --- /dev/null +++ b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/DefaultRepoOwnerPicker.tsx @@ -0,0 +1,62 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import FormControl from '@material-ui/core/FormControl'; +import FormHelperText from '@material-ui/core/FormHelperText'; +import TextField from '@material-ui/core/TextField'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; + +import { BaseRepoOwnerPickerProps } from './types'; +import { scaffolderTranslationRef } from '../../../translation'; + +/** + * The underlying component that is rendered in the form for the `DefaultRepoOwnerPicker` + * field extension. + * + * @public + * + */ +export const DefaultRepoOwnerPicker = ({ + onChange, + state, + rawErrors, + isDisabled, + required, + schema, +}: BaseRepoOwnerPickerProps) => { + const { owner } = state; + + const { t } = useTranslationRef(scaffolderTranslationRef); + + return ( + 0 && !owner} + > + onChange({ owner: e.target.value })} + value={owner} + /> + + {schema?.description ?? t('fields.repoOwnerPicker.description')} + + + ); +}; diff --git a/plugins/scaffolder/src/components/fields/RepoOwnerPicker/GitHubRepoOwnerPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/GitHubRepoOwnerPicker.test.tsx new file mode 100644 index 0000000000..bfc6c76768 --- /dev/null +++ b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/GitHubRepoOwnerPicker.test.tsx @@ -0,0 +1,149 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + ScaffolderApi, + scaffolderApiRef, +} from '@backstage/plugin-scaffolder-react'; +import { GitHubRepoOwnerPicker } from './GitHubRepoOwnerPicker'; +import { act, fireEvent, waitFor, screen } from '@testing-library/react'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import userEvent from '@testing-library/user-event'; + +describe('GitHubRepoOwnerPicker', () => { + const scaffolderApiMock: Partial = { + autocomplete: jest + .fn() + .mockResolvedValue({ results: [{ id: 'owner1' }, { id: 'owner2' }] }), + }; + + it('renders an input field', async () => { + const { getByRole } = await renderInTestApp( + + + , + ); + + expect(getByRole('textbox')).toBeInTheDocument(); + expect(getByRole('textbox')).toHaveValue('owner1'); + }); + + it('input field disabled', async () => { + await renderInTestApp( + + + , + ); + + const input = screen.getByRole('textbox'); + + // Expect input to be disabled + expect(input).toBeDisabled(); + expect(input).toHaveValue('owner1'); + }); + + it('calls onChange when the input field changes', async () => { + const onChange = jest.fn(); + + const { getByRole } = await renderInTestApp( + + + , + ); + + const input = getByRole('textbox'); + + act(() => { + input.focus(); + fireEvent.change(input, { + target: { value: 'owner2' }, + }); + input.blur(); + }); + + expect(onChange).toHaveBeenCalledWith({ owner: 'owner2' }); + }); + + it('should populate owners', async () => { + const onChange = jest.fn(); + + const { getByRole, getByText } = await renderInTestApp( + + + , + ); + + // Open the Autocomplete dropdown + const input = getByRole('textbox'); + await userEvent.click(input); + + // Verify that the available owners are shown + await waitFor(() => expect(getByText('owner1')).toBeInTheDocument()); + + // Verify that selecting an option calls onChange + await userEvent.click(getByText('owner1')); + expect(onChange).toHaveBeenCalledWith({ + owner: 'owner1', + }); + }); + + it('should filter out excluded owners', async () => { + const onChange = jest.fn(); + + const { getByRole, getByText } = await renderInTestApp( + + + , + ); + + // Open the Autocomplete dropdown + const input = getByRole('textbox'); + await userEvent.click(input); + + // Verify that the excluded owners are not shown + await waitFor(() => expect(getByText('owner2')).toBeInTheDocument()); + expect(screen.queryByText('owner1')).not.toBeInTheDocument(); + }); +}); diff --git a/plugins/scaffolder/src/components/fields/RepoOwnerPicker/GitHubRepoOwnerPicker.tsx b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/GitHubRepoOwnerPicker.tsx new file mode 100644 index 0000000000..9258ef312d --- /dev/null +++ b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/GitHubRepoOwnerPicker.tsx @@ -0,0 +1,113 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useApi } from '@backstage/core-plugin-api'; +import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react'; +import FormControl from '@material-ui/core/FormControl'; +import FormHelperText from '@material-ui/core/FormHelperText'; +import TextField from '@material-ui/core/TextField'; +import Autocomplete from '@material-ui/lab/Autocomplete'; +import { useCallback, useState } from 'react'; +import useDebounce from 'react-use/esm/useDebounce'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; + +import { BaseRepoOwnerPickerProps } from './types'; +import { scaffolderTranslationRef } from '../../../translation'; + +/** + * The underlying component that is rendered in the form for the `GitHubRepoOwnerPicker` + * field extension. + * + * @public + * + */ +export const GitHubRepoOwnerPicker = ({ + onChange, + state, + rawErrors, + accessToken, + isDisabled, + required, + schema, + excludedOwners = [], +}: BaseRepoOwnerPickerProps<{ + accessToken?: string; + excludedOwners?: string[]; +}>) => { + const { host, owner } = state; + + const [availableOwners, setAvailableOwners] = useState([]); + + const scaffolderApi = useApi(scaffolderApiRef); + const { t } = useTranslationRef(scaffolderTranslationRef); + + const updateAvailableOwners = useCallback(() => { + if (!scaffolderApi.autocomplete || !accessToken || !host) { + setAvailableOwners([]); + return; + } + + scaffolderApi + .autocomplete({ + token: accessToken, + resource: 'owners', + context: { host }, + provider: 'github', + }) + .then(({ results }) => { + const owners = results + .map(r => r.id) + .filter(id => !excludedOwners.includes(id)); + + setAvailableOwners(owners); + }) + .catch(() => { + setAvailableOwners([]); + }); + }, [host, accessToken, scaffolderApi, excludedOwners]); + + useDebounce(updateAvailableOwners, 500, [updateAvailableOwners]); + + return ( + 0 && !owner} + > + { + onChange({ owner: newValue || '' }); + }} + disabled={isDisabled} + options={availableOwners} + renderInput={params => ( + + )} + freeSolo + autoSelect + /> + + {schema?.description ?? t('fields.repoOwnerPicker.description')} + + + ); +}; diff --git a/plugins/scaffolder/src/components/fields/RepoOwnerPicker/RepoOwnerPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/RepoOwnerPicker.test.tsx new file mode 100644 index 0000000000..c51d74416b --- /dev/null +++ b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/RepoOwnerPicker.test.tsx @@ -0,0 +1,308 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Form } from '@backstage/plugin-scaffolder-react/alpha'; +import validator from '@rjsf/validator-ajv8'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { + scmIntegrationsApiRef, + ScmIntegrationsApi, + scmAuthApiRef, + ScmAuthApi, +} from '@backstage/integration-react'; + +import { + SecretsContextProvider, + scaffolderApiRef, + useTemplateSecrets, + ScaffolderRJSFField, +} from '@backstage/plugin-scaffolder-react'; +import { act, fireEvent, screen } from '@testing-library/react'; +import { RepoOwnerPicker } from './RepoOwnerPicker'; + +describe('RepoOwnerPicker', () => { + const mockIntegrationsApi: Partial = { + byHost: () => ({ type: 'github' }), + }; + + let mockScmAuthApi: Partial; + + beforeEach(() => { + mockScmAuthApi = { + getCredentials: jest.fn().mockResolvedValue({ token: 'abc123' }), + }; + }); + + describe('happy path rendering', () => { + it('should render the repo owner picker with minimal props', async () => { + const onSubmit = jest.fn(); + + const { getByRole, getByText } = await renderInTestApp( + + + , + }} + onSubmit={onSubmit} + /> + + , + ); + + const input = getByRole('textbox'); + const submitButton = getByRole('button'); + + fireEvent.change(input, { target: { value: 'owner1' } }); + + fireEvent.click(submitButton); + + expect(onSubmit).toHaveBeenCalledWith( + expect.objectContaining({ + formData: 'owner1', + }), + expect.anything(), + ); + + expect(getByText('Owner')).toBeInTheDocument(); + expect(getByText('The owner of the repository')).toBeInTheDocument(); + }); + + it('should disable the picker when ui:disabled', async () => { + const onSubmit = jest.fn(); + + await renderInTestApp( + + + , + }} + onSubmit={onSubmit} + /> + + , + ); + + const input = screen.getByRole('textbox'); + + expect(input).toBeDisabled(); + }); + + it('should render properly with title and description', async () => { + const { getByText } = await renderInTestApp( + + + , + }} + /> + + , + ); + + expect(getByText('test title')).toBeInTheDocument(); + expect(getByText('test description')).toBeInTheDocument(); + }); + }); + + describe('requestUserCredentials', () => { + it('should call the scmAuthApi with the correct params', async () => { + const secretsKey = 'testKey'; + + const SecretsComponent = () => { + const { secrets } = useTemplateSecrets(); + const secret = secrets[secretsKey]; + return secret ?
    {secret}
    : null; + }; + + const { getByText } = await renderInTestApp( + + + , + }} + /> + + + , + ); + + await act(async () => { + // need to wait for the debounce to finish + await new Promise(resolve => setTimeout(resolve, 600)); + }); + + expect(mockScmAuthApi.getCredentials).toHaveBeenCalledWith({ + url: 'https://github.com', + additionalScope: { + repoWrite: true, + customScopes: { + github: ['workflow'], + }, + }, + }); + + expect(getByText('abc123')).toBeInTheDocument(); + }); + + it('should call the scmAuthApi with the correct params if workspace is nested', async () => { + await renderInTestApp( + + + , + }} + /> + + , + ); + + await act(async () => { + // need to wait for the debounce to finish + await new Promise(resolve => setTimeout(resolve, 600)); + }); + + expect(mockScmAuthApi.getCredentials).toHaveBeenCalledWith({ + url: 'https://github.com', + additionalScope: { + repoWrite: true, + }, + }); + }); + + it('should not call the scmAuthApi if secret is available in the state', async () => { + const secretsKey = 'testKey'; + + const SecretsComponent = () => { + const { secrets } = useTemplateSecrets(); + const secret = secrets[secretsKey]; + return secret ?
    {secret}
    : null; + }; + + const { getByText } = await renderInTestApp( + + + , + }} + /> + + + , + ); + + await act(async () => { + // need to wait for the debounce to finish + await new Promise(resolve => setTimeout(resolve, 600)); + }); + + // as we already have a secret in the state, getCredentials should not be called again. + expect(mockScmAuthApi.getCredentials).toHaveBeenCalledTimes(0); + + expect(getByText('abc123')).toBeInTheDocument(); + }); + }); +}); diff --git a/plugins/scaffolder/src/components/fields/RepoOwnerPicker/RepoOwnerPicker.tsx b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/RepoOwnerPicker.tsx new file mode 100644 index 0000000000..bcb1a0001c --- /dev/null +++ b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/RepoOwnerPicker.tsx @@ -0,0 +1,138 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useApi } from '@backstage/core-plugin-api'; +import { + scmIntegrationsApiRef, + scmAuthApiRef, +} from '@backstage/integration-react'; +import { useEffect, useState, useCallback, useMemo } from 'react'; +import useDebounce from 'react-use/esm/useDebounce'; +import { useTemplateSecrets } from '@backstage/plugin-scaffolder-react'; + +import { RepoOwnerPickerProps } from './schema'; +import { RepoOwnerPickerState } from './types'; +import { DefaultRepoOwnerPicker } from './DefaultRepoOwnerPicker'; +import { GitHubRepoOwnerPicker } from './GitHubRepoOwnerPicker'; + +/** + * The underlying component that is rendered in the form for the `RepoOwnerPicker` + * field extension. + * + * @public + */ +export const RepoOwnerPicker = (props: RepoOwnerPickerProps) => { + const { uiSchema, onChange, rawErrors, formData, schema, required } = props; + const [state, setState] = useState({ + owner: formData || '', + }); + const excludedOwners = useMemo( + () => uiSchema?.['ui:options']?.excludedOwners ?? [], + [uiSchema], + ); + const { host, owner } = state; + + const integrationApi = useApi(scmIntegrationsApiRef); + const scmAuthApi = useApi(scmAuthApiRef); + + const { secrets, setSecrets } = useTemplateSecrets(); + + useDebounce( + async () => { + const { requestUserCredentials } = uiSchema?.['ui:options'] ?? {}; + + if (!requestUserCredentials || !host) { + return; + } + + // don't show login prompt if secret value is already in state + if (secrets[requestUserCredentials.secretsKey]) { + return; + } + + // user has requested that we use the users credentials + // so lets grab them using the scmAuthApi and pass through + // any additional scopes from the ui:options + const { token } = await scmAuthApi.getCredentials({ + url: `https://${host}`, + additionalScope: { + repoWrite: true, + customScopes: requestUserCredentials.additionalScopes, + }, + }); + + // set the secret using the key provided in the ui:options for use + // in the templating the manifest with ${{ secrets[secretsKey] }} + setSecrets({ [requestUserCredentials.secretsKey]: token }); + }, + 500, + [host, uiSchema], + ); + + useEffect(() => { + if (uiSchema?.['ui:options']?.host) { + const hostUiOption = uiSchema['ui:options'].host; + setState(prevState => ({ ...prevState, host: hostUiOption })); + } + }, [uiSchema]); + + useEffect(() => { + onChange(owner); + }, [owner, onChange]); + + const updateLocalState = useCallback( + (newState: RepoOwnerPickerState) => { + setState(prevState => ({ ...prevState, ...newState })); + }, + [setState], + ); + + const hostType = (host && integrationApi.byHost(host)?.type) ?? null; + + const renderRepoOwnerPicker = () => { + switch (hostType) { + case 'github': + return ( + + ); + default: + return ( + + ); + } + }; + + return renderRepoOwnerPicker(); +}; diff --git a/packages/cli/src/lib/versioning/index.ts b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/index.ts similarity index 73% rename from packages/cli/src/lib/versioning/index.ts rename to plugins/scaffolder/src/components/fields/RepoOwnerPicker/index.ts index e0b9280ee6..d93e5e22e5 100644 --- a/packages/cli/src/lib/versioning/index.ts +++ b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * Copyright 2025 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,9 @@ * limitations under the License. */ -export { Lockfile } from './Lockfile'; -export { fetchPackageInfo, mapDependencies } from './packages'; -export type { YarnInfoInspectData } from './packages'; +export { RepoOwnerPicker } from './RepoOwnerPicker'; +export { + RepoOwnerPickerFieldSchema, + type RepoOwnerPickerUiOptions, + RepoOwnerPickerSchema, +} from './schema'; diff --git a/plugins/scaffolder/src/components/fields/RepoOwnerPicker/schema.ts b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/schema.ts new file mode 100644 index 0000000000..7931ca1eec --- /dev/null +++ b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/schema.ts @@ -0,0 +1,86 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { makeFieldSchema } from '@backstage/plugin-scaffolder-react'; + +export const RepoOwnerPickerFieldSchema = makeFieldSchema({ + output: z => z.string(), + uiOptions: z => + z.object({ + host: z.string().optional(), + excludedOwners: z + .array(z.string()) + .optional() + .describe('List of excluded owners'), + requestUserCredentials: z + .object({ + secretsKey: z + .string() + .describe( + 'Key used within the template secrets context to store the credential', + ), + additionalScopes: z + .object({ + gitea: z + .array(z.string()) + .optional() + .describe('Additional Gitea scopes to request'), + gerrit: z + .array(z.string()) + .optional() + .describe('Additional Gerrit scopes to request'), + github: z + .array(z.string()) + .optional() + .describe('Additional GitHub scopes to request'), + gitlab: z + .array(z.string()) + .optional() + .describe('Additional GitLab scopes to request'), + bitbucket: z + .array(z.string()) + .optional() + .describe('Additional BitBucket scopes to request'), + azure: z + .array(z.string()) + .optional() + .describe('Additional Azure scopes to request'), + }) + .optional() + .describe('Additional permission scopes to request'), + }) + .optional() + .describe( + 'If defined will request user credentials to auth against the given SCM platform', + ), + }), +}); + +/** + * The input props that can be specified under `ui:options` for the + * `RepoOwnerPicker` field extension. + */ +export type RepoOwnerPickerUiOptions = NonNullable< + (typeof RepoOwnerPickerFieldSchema.TProps.uiSchema)['ui:options'] +>; + +export type RepoOwnerPickerProps = typeof RepoOwnerPickerFieldSchema.TProps; + +// This has been duplicated from /plugins/scaffolder/src/components/fields/RepoUrlPicker/schema.ts +// NOTE: There is a bug with this failing validation in the custom field explorer due +// to https://github.com/rjsf-team/react-jsonschema-form/issues/675 even if +// requestUserCredentials is not defined +export const RepoOwnerPickerSchema = RepoOwnerPickerFieldSchema.schema; diff --git a/plugins/scaffolder/src/components/fields/RepoOwnerPicker/types.ts b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/types.ts new file mode 100644 index 0000000000..eceed58df5 --- /dev/null +++ b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/types.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 { RJSFSchema } from '@rjsf/utils'; + +export interface RepoOwnerPickerState { + host?: string; + owner?: string; +} + +export type BaseRepoOwnerPickerProps = T & { + onChange: (state: RepoOwnerPickerState) => void; + state: RepoOwnerPickerState; + rawErrors: string[]; + isDisabled?: boolean; + required?: boolean; + schema?: RJSFSchema; +}; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.test.tsx index 0e461d745a..6cf1825460 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.test.tsx @@ -176,5 +176,42 @@ describe('GitlabRepoPicker', () => { expect(onChange).toHaveBeenCalledWith({ owner: 'my-mock-owner' }); }); + + it('should render description if allowed owners are passed', async () => { + const { findByText } = await renderInTestApp( + + + , + ); + + expect( + await findByText( + /GitLab namespace where this repository will belong to./, + ), + ).toBeInTheDocument(); + }); + + it('should render description if no allowed owners are passed', async () => { + const { findByText } = await renderInTestApp( + + + , + ); + + expect( + await findByText( + /GitLab namespace where this repository will belong to./, + ), + ).toBeInTheDocument(); + }); }); }); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.tsx index 0e869b44bf..0df4914983 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.tsx @@ -139,9 +139,6 @@ export const GitlabRepoPicker = ( selected={owner} items={ownerItems} /> - - {t('fields.gitlabRepoPicker.owner.description')} - ) : ( ( @@ -163,6 +160,9 @@ export const GitlabRepoPicker = ( autoSelect /> )} + + {t('fields.gitlabRepoPicker.owner.description')} + ); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerRepoName.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerRepoName.tsx index 0427f44241..f3c65a02bc 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerRepoName.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerRepoName.tsx @@ -78,7 +78,7 @@ export const RepoUrlPickerRepoName = (props: { ) : ( { + onChange={(_, newValue) => { const selectedRepo = availableRepos?.find( r => r.name === newValue, ); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.test.ts b/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.test.ts index 2b59775d3a..16552c1292 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.test.ts +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.test.ts @@ -28,10 +28,12 @@ describe('RepoPicker Validation', () => { const config = new ConfigReader({ integrations: { - bitbucket: [ + bitbucketCloud: [ { host: 'bitbucket.org', }, + ], + bitbucketServer: [ { host: 'server.bitbucket.com', }, diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.ts b/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.ts index 682fc6bb2a..2f3c5deeb9 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.ts +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.ts @@ -39,9 +39,16 @@ export const repoPickerValidation = ( 'Incomplete repository location provided, host not provided', ); } else { - if (integrationApi?.byHost(host)?.type === 'bitbucket') { + const integrationType = integrationApi?.byHost(host)?.type; + if ( + integrationType === 'bitbucketCloud' || + integrationType === 'bitbucketServer' + ) { // workspace is only applicable for bitbucket cloud - if (host === 'bitbucket.org' && !searchParams.get('workspace')) { + if ( + integrationType === 'bitbucketCloud' && + !searchParams.get('workspace') + ) { validation.addError( 'Incomplete repository location provided, workspace not provided', ); @@ -52,7 +59,7 @@ export const repoPickerValidation = ( 'Incomplete repository location provided, project not provided', ); } - } else if (integrationApi?.byHost(host)?.type === 'azure') { + } else if (integrationType === 'azure') { if (!searchParams.get('project')) { validation.addError( 'Incomplete repository location provided, project not provided', diff --git a/plugins/scaffolder/src/components/fields/VirtualizedListbox.test.tsx b/plugins/scaffolder/src/components/fields/VirtualizedListbox.test.tsx index 59f311fe88..8a058d5320 100644 --- a/plugins/scaffolder/src/components/fields/VirtualizedListbox.test.tsx +++ b/plugins/scaffolder/src/components/fields/VirtualizedListbox.test.tsx @@ -33,7 +33,7 @@ describe('', () => { aria-expanded="true" class="MuiAutocomplete-root MuiAutocomplete-hasClearIcon MuiAutocomplete-hasPopupIcon" role="combobox" - style="position: relative; height: 18px; width: 100%; overflow: auto; will-change: transform; direction: ltr;" + style="position: relative; height: 18px; width: 100%; overflow: auto; -webkit-overflow-scrolling: touch; will-change: transform; direction: ltr;" >
    ', () => {
    ', () => {
    ', () => {
    ', () => {
    ; - close(): Promise; - }>; -}; +function isFileHandle( + handle: FileSystemHandle, +): handle is FileSystemFileHandle { + return handle.kind === 'file'; +} -// A nicer type than the one from the TS lib -export interface IterableDirectoryHandle extends FileSystemDirectoryHandle { - values(): AsyncIterable< - | ({ kind: 'file' } & WritableFileHandle) - | ({ kind: 'directory' } & IterableDirectoryHandle) - >; +function isDirectoryHandle( + handle: FileSystemHandle, +): handle is FileSystemDirectoryHandle { + return handle.kind === 'directory'; } const showDirectoryPicker = (window as any).showDirectoryPicker as - | (() => Promise) + | (() => Promise) | undefined; class WebFileAccess implements TemplateFileAccess { readonly path: string; - private readonly handle: WritableFileHandle; + private readonly handle: FileSystemFileHandle; - constructor(path: string, handle: WritableFileHandle) { + constructor(path: string, handle: FileSystemFileHandle) { this.path = path; this.handle = handle; } @@ -57,9 +54,9 @@ class WebFileAccess implements TemplateFileAccess { /** @internal */ export class WebDirectoryAccess implements TemplateDirectoryAccess { - private readonly handle: IterableDirectoryHandle; + private readonly handle: FileSystemDirectoryHandle; - constructor(handle: IterableDirectoryHandle) { + constructor(handle: FileSystemDirectoryHandle) { this.handle = handle; } @@ -72,13 +69,13 @@ export class WebDirectoryAccess implements TemplateDirectoryAccess { } private async *listDirectoryContents( - dirHandle: IterableDirectoryHandle, + dirHandle: FileSystemDirectoryHandle, basePath: string[] = [], ): AsyncIterable { for await (const handle of dirHandle.values()) { - if (handle.kind === 'file') { + if (isFileHandle(handle)) { yield new WebFileAccess([...basePath, handle.name].join('/'), handle); - } else if (handle.kind === 'directory') { + } else if (isDirectoryHandle(handle)) { // Skip git storage directory if (handle.name === '.git') { continue; @@ -116,7 +113,7 @@ export class WebFileSystemAccess { return Boolean(showDirectoryPicker); } - static fromHandle(handle: IterableDirectoryHandle) { + static fromHandle(handle: FileSystemDirectoryHandle) { return new WebDirectoryAccess(handle); } diff --git a/plugins/scaffolder/src/lib/filesystem/WebFileSystemStore.ts b/plugins/scaffolder/src/lib/filesystem/WebFileSystemStore.ts index 1ea90dcf76..8b741d36d3 100644 --- a/plugins/scaffolder/src/lib/filesystem/WebFileSystemStore.ts +++ b/plugins/scaffolder/src/lib/filesystem/WebFileSystemStore.ts @@ -16,12 +16,11 @@ import { get, set } from 'idb-keyval'; import { TemplateDirectoryAccess } from './types'; -import { IterableDirectoryHandle } from './WebFileSystemAccess'; export class WebFileSystemStore { private static readonly key = 'scalfolder-template-editor-directory'; - static async getDirectory(): Promise { + static async getDirectory(): Promise { const directory = await get(WebFileSystemStore.key); return directory.handle; } diff --git a/plugins/scaffolder/src/lib/filesystem/index.ts b/plugins/scaffolder/src/lib/filesystem/index.ts index 4f44ab1ccd..6ab570715f 100644 --- a/plugins/scaffolder/src/lib/filesystem/index.ts +++ b/plugins/scaffolder/src/lib/filesystem/index.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -export { blobToBase64 } from './helpers'; export { createExampleTemplate } from './createExampleTemplate'; export type { TemplateFileAccess, TemplateDirectoryAccess } from './types'; export { WebFileSystemStore } from './WebFileSystemStore'; diff --git a/plugins/scaffolder/src/plugin.tsx b/plugins/scaffolder/src/plugin.tsx index cda4ee461c..50a5d29280 100644 --- a/plugins/scaffolder/src/plugin.tsx +++ b/plugins/scaffolder/src/plugin.tsx @@ -81,7 +81,10 @@ import { RepoBranchPicker } from './components/fields/RepoBranchPicker/RepoBranc import { RepoBranchPickerSchema } from './components/fields/RepoBranchPicker/schema'; import { formDecoratorsApiRef } from './alpha/api/ref'; import { DefaultScaffolderFormDecoratorsApi } from './alpha/api/FormDecoratorsApi'; -import { formFieldsApiRef } from '@backstage/plugin-scaffolder-react/alpha'; +import { + RepoOwnerPicker, + RepoOwnerPickerSchema, +} from './components/fields/RepoOwnerPicker'; /** * The main plugin export for the scaffolder. @@ -111,11 +114,6 @@ export const scaffolderPlugin = createPlugin({ deps: {}, factory: () => DefaultScaffolderFormDecoratorsApi.create(), }), - createApiFactory({ - api: formFieldsApiRef, - deps: {}, - factory: () => ({ getFormFields: async () => [] }), - }), ], routes: { root: rootRouteRef, @@ -267,3 +265,16 @@ export const RepoBranchPickerFieldExtension = scaffolderPlugin.provide( schema: RepoBranchPickerSchema, }), ); + +/** + * A field extension to select an owner from a repository. + * + * @public + */ +export const RepoOwnerPickerFieldExtension = scaffolderPlugin.provide( + createScaffolderFieldExtension({ + component: RepoOwnerPicker, + name: 'RepoOwnerPicker', + schema: RepoOwnerPickerSchema, + }), +); diff --git a/plugins/scaffolder/src/translation.ts b/plugins/scaffolder/src/translation.ts index 78ca15e2ff..ea934e9ffd 100644 --- a/plugins/scaffolder/src/translation.ts +++ b/plugins/scaffolder/src/translation.ts @@ -15,7 +15,7 @@ */ import { createTranslationRef } from '@backstage/core-plugin-api/alpha'; -/** @alpha */ +/** @public */ export const scaffolderTranslationRef = createTranslationRef({ id: 'scaffolder', messages: { @@ -137,6 +137,10 @@ export const scaffolderTranslationRef = createTranslationRef({ description: 'The name of the repository', }, }, + repoOwnerPicker: { + title: 'Owner', + description: 'The owner of the repository', + }, }, listTaskPage: { title: 'List template tasks', @@ -178,6 +182,7 @@ export const scaffolderTranslationRef = createTranslationRef({ hideLogsButtonTitle: 'Hide Logs', showLogsButtonTitle: 'Show Logs', contextMenu: { + moreOptions: 'More options', hideLogs: 'Hide Logs', showLogs: 'Show Logs', hideButtonBar: 'Hide Button Bar', @@ -198,6 +203,7 @@ export const scaffolderTranslationRef = createTranslationRef({ title: 'Title', description: 'Description', type: 'Type', + value: 'Value', }, undefined: 'No schema defined', }, @@ -354,6 +360,7 @@ export const scaffolderTranslationRef = createTranslationRef({ subtitle: 'Create new software components using standard templates in your organization', pageTitle: 'Create a new component', + templateWithTitle: 'Create new {{templateTitle}}', pageContextMenu: { editConfigurationTitle: 'Edit Configuration', }, diff --git a/plugins/search-backend-module-catalog/CHANGELOG.md b/plugins/search-backend-module-catalog/CHANGELOG.md index 9fe8a8fa8e..58be0928a7 100644 --- a/plugins/search-backend-module-catalog/CHANGELOG.md +++ b/plugins/search-backend-module-catalog/CHANGELOG.md @@ -1,5 +1,213 @@ # @backstage/plugin-search-backend-module-catalog +## 0.3.14 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/errors@1.3.0 + - @backstage/catalog-model@1.8.0 + - @backstage/plugin-catalog-node@2.2.0 + - @backstage/plugin-search-backend-node@1.4.3 + - @backstage/catalog-client@1.15.0 + - @backstage/config@1.3.7 + - @backstage/plugin-catalog-common@1.1.9 + - @backstage/plugin-permission-common@0.9.8 + - @backstage/plugin-search-common@1.2.23 + +## 0.3.14-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/plugin-catalog-node@2.2.0-next.2 + - @backstage/plugin-search-backend-node@1.4.3-next.2 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/catalog-client@1.14.1-next.0 + - @backstage/catalog-model@1.7.8-next.0 + - @backstage/config@1.3.7-next.0 + - @backstage/plugin-permission-common@0.9.8-next.0 + - @backstage/plugin-catalog-common@1.1.9-next.0 + - @backstage/plugin-search-common@1.2.23-next.0 + +## 0.3.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-search-backend-node@1.4.3-next.1 + +## 0.3.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-search-backend-node@1.4.3-next.0 + - @backstage/plugin-catalog-node@2.1.1-next.0 + - @backstage/catalog-client@1.14.0 + - @backstage/catalog-model@1.7.7 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/plugin-search-common@1.2.22 + +## 0.3.13 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/catalog-client@1.14.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/catalog-model@1.7.7 + - @backstage/plugin-search-backend-node@1.4.2 + +## 0.3.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/catalog-client@1.14.0-next.2 + - @backstage/plugin-catalog-node@2.1.0-next.2 + - @backstage/plugin-search-backend-node@1.4.2-next.1 + +## 0.3.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.14.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-search-backend-node@1.4.2-next.0 + - @backstage/plugin-search-common@1.2.22 + +## 0.3.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-client@1.13.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-search-backend-node@1.4.2-next.0 + - @backstage/plugin-search-common@1.2.22 + +## 0.3.12 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-search-backend-node@1.4.1 + - @backstage/plugin-search-common@1.2.22 + - @backstage/catalog-client@1.13.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-catalog-common@1.1.8 + +## 0.3.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/catalog-client@1.12.2-next.0 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-search-backend-node@1.4.1-next.0 + +## 0.3.12-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-search-backend-node@1.4.1-next.0 + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.8-next.0 + +## 0.3.11 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## 0.3.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-backend-node@1.4.0-next.1 + - @backstage/plugin-search-common@1.2.21 + +## 0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-common@1.2.21 + +## 0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-backend-node@1.3.17 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/catalog-client@1.12.1 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-search-common@1.2.21 + ## 0.3.10-next.1 ### Patch Changes diff --git a/plugins/search-backend-module-catalog/package.json b/plugins/search-backend-module-catalog/package.json index f688b4e1c5..d70c5e3481 100644 --- a/plugins/search-backend-module-catalog/package.json +++ b/plugins/search-backend-module-catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-catalog", - "version": "0.3.10-next.1", + "version": "0.3.14", "description": "A module for the search backend that exports catalog modules", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/search-backend-module-catalog/src/collators/DefaultCatalogCollatorFactory.test.ts b/plugins/search-backend-module-catalog/src/collators/DefaultCatalogCollatorFactory.test.ts index 68aedbdef8..2cc87e2ef5 100644 --- a/plugins/search-backend-module-catalog/src/collators/DefaultCatalogCollatorFactory.test.ts +++ b/plugins/search-backend-module-catalog/src/collators/DefaultCatalogCollatorFactory.test.ts @@ -18,7 +18,7 @@ import { mockServices } from '@backstage/backend-test-utils'; import { Entity } from '@backstage/catalog-model'; import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; import { TestPipeline } from '@backstage/plugin-search-backend-node'; -import { Readable } from 'stream'; +import { Readable } from 'node:stream'; import { DefaultCatalogCollatorFactory } from './DefaultCatalogCollatorFactory'; const expectedEntities: Entity[] = [ diff --git a/plugins/search-backend-module-catalog/src/collators/DefaultCatalogCollatorFactory.ts b/plugins/search-backend-module-catalog/src/collators/DefaultCatalogCollatorFactory.ts index f023efbf54..9c76712ef7 100644 --- a/plugins/search-backend-module-catalog/src/collators/DefaultCatalogCollatorFactory.ts +++ b/plugins/search-backend-module-catalog/src/collators/DefaultCatalogCollatorFactory.ts @@ -23,7 +23,7 @@ import { catalogEntityReadPermission } from '@backstage/plugin-catalog-common/al import { CatalogService } from '@backstage/plugin-catalog-node'; import { Permission } from '@backstage/plugin-permission-common'; import { DocumentCollatorFactory } from '@backstage/plugin-search-common'; -import { Readable } from 'stream'; +import { Readable } from 'node:stream'; import { CatalogCollatorEntityTransformer } from './CatalogCollatorEntityTransformer'; import { readCollatorConfigOptions } from './config'; import { defaultCatalogCollatorEntityTransformer } from './defaultCatalogCollatorEntityTransformer'; diff --git a/plugins/search-backend-module-elasticsearch/CHANGELOG.md b/plugins/search-backend-module-elasticsearch/CHANGELOG.md index 9b76d49de2..83b2fdfcd7 100644 --- a/plugins/search-backend-module-elasticsearch/CHANGELOG.md +++ b/plugins/search-backend-module-elasticsearch/CHANGELOG.md @@ -1,5 +1,156 @@ # @backstage/plugin-search-backend-module-elasticsearch +## 1.8.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/plugin-search-backend-node@1.4.3 + - @backstage/config@1.3.7 + - @backstage/integration-aws-node@0.1.21 + - @backstage/plugin-search-common@1.2.23 + +## 1.8.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.3-next.2 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/config@1.3.7-next.0 + - @backstage/integration-aws-node@0.1.21-next.0 + - @backstage/plugin-search-common@1.2.23-next.0 + +## 1.8.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-search-backend-node@1.4.3-next.1 + +## 1.8.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-search-backend-node@1.4.3-next.0 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.20 + - @backstage/plugin-search-common@1.2.22 + +## 1.8.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-search-backend-node@1.4.2 + +## 1.8.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-search-backend-node@1.4.2-next.1 + +## 1.8.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.20 + - @backstage/plugin-search-backend-node@1.4.2-next.0 + - @backstage/plugin-search-common@1.2.22 + +## 1.8.0 + +### Minor Changes + +- 583bd3a: Added `elasticsearchAuthExtensionPoint` to enable dynamic authentication mechanisms such as bearer tokens with automatic rotation. + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 7021165: Fixed bulk indexing to refresh only the target index instead of all indexes, improving performance in multi-index deployments. +- Updated dependencies + - @backstage/integration-aws-node@0.1.20 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-search-backend-node@1.4.1 + - @backstage/plugin-search-common@1.2.22 + +## 1.8.0-next.1 + +### Minor Changes + +- 583bd3a: Added `elasticsearchAuthExtensionPoint` to enable dynamic authentication mechanisms such as bearer tokens with automatic rotation. + +### Patch Changes + +- 7021165: Fixed bulk indexing to refresh only the target index instead of all indexes, improving performance in multi-index deployments. +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.1 + +## 1.7.10-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/integration-aws-node@0.1.20-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-search-backend-node@1.4.1-next.0 + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/config@1.3.6 + +## 1.7.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/backend-plugin-api@1.6.0 + +## 1.7.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/plugin-search-backend-node@1.4.0-next.1 + - @backstage/plugin-search-common@1.2.21 + +## 1.7.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/plugin-search-common@1.2.21 + +## 1.7.8 + +### Patch Changes + +- 71c22f3: Removed/moved unused dependencies +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-search-backend-node@1.3.17 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/plugin-search-common@1.2.21 + ## 1.7.8-next.1 ### Patch Changes diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json index 6118231889..e1b3d3fb88 100644 --- a/plugins/search-backend-module-elasticsearch/package.json +++ b/plugins/search-backend-module-elasticsearch/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-elasticsearch", - "version": "1.7.8-next.1", + "version": "1.8.2", "description": "A module for the search backend that implements search using ElasticSearch", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/search-backend-module-elasticsearch/report.api.md b/plugins/search-backend-module-elasticsearch/report.api.md index c2fb23efd6..afe06dd45e 100644 --- a/plugins/search-backend-module-elasticsearch/report.api.md +++ b/plugins/search-backend-module-elasticsearch/report.api.md @@ -10,15 +10,15 @@ import { BatchSearchEngineIndexer } from '@backstage/plugin-search-backend-node' import { BulkHelper } from '@elastic/elasticsearch/lib/Helpers'; import { BulkStats } from '@elastic/elasticsearch/lib/Helpers'; import { Config } from '@backstage/config'; -import type { ConnectionOptions } from 'tls'; +import type { ConnectionOptions } from 'node:tls'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; import { IndexableDocument } from '@backstage/plugin-search-common'; import { IndexableResultSet } from '@backstage/plugin-search-common'; import { LoggerService } from '@backstage/backend-plugin-api'; -import { Readable } from 'stream'; +import { Readable } from 'node:stream'; import { SearchEngine } from '@backstage/plugin-search-backend-node'; import { SearchQuery } from '@backstage/plugin-search-common'; -import { TransportRequestPromise } from '@opensearch-project/opensearch/lib/Transport'; +import { TransportRequestPromise } from '@opensearch-project/opensearch/lib/Transport.js'; import { TransportRequestPromise as TransportRequestPromise_2 } from '@elastic/elasticsearch/lib/Transport'; // @public @@ -127,6 +127,19 @@ export type ElasticSearchAuth = }; }; +// @public +export interface ElasticSearchAuthExtensionPoint { + setAuthProvider(provider: ElasticSearchAuthProvider): void; +} + +// @public +export const elasticsearchAuthExtensionPoint: ExtensionPoint; + +// @public +export interface ElasticSearchAuthProvider { + getAuthHeaders(): Promise>; +} + // @public export type ElasticSearchClientOptions = | ElasticSearchElasticSearchClientOptions @@ -314,6 +327,7 @@ export type ElasticSearchOptions = { aliasPostfix?: string; indexPrefix?: string; translator?: ElasticSearchQueryTranslator; + authProvider?: ElasticSearchAuthProvider; }; // @public (undocumented) diff --git a/plugins/search-backend-module-elasticsearch/src/auth.ts b/plugins/search-backend-module-elasticsearch/src/auth.ts new file mode 100644 index 0000000000..af7da316d4 --- /dev/null +++ b/plugins/search-backend-module-elasticsearch/src/auth.ts @@ -0,0 +1,117 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createExtensionPoint } from '@backstage/backend-plugin-api'; + +/** + * A provider that supplies authentication headers for Elasticsearch/OpenSearch requests. + * + * @remarks + * + * This interface allows for dynamic authentication mechanisms such as bearer tokens + * that need to be refreshed or rotated. The `getAuthHeaders` method is called before + * each request to the Elasticsearch/OpenSearch cluster, allowing for just-in-time + * token retrieval and automatic rotation. + * + * @example + * + * ```ts + * const authProvider: ElasticSearchAuthProvider = { + * async getAuthHeaders() { + * const token = await myTokenService.getToken(); + * return { Authorization: `Bearer ${token}` }; + * }, + * }; + * ``` + * + * @public + */ +export interface ElasticSearchAuthProvider { + /** + * Returns authentication headers to be included in requests to Elasticsearch/OpenSearch. + * + * @remarks + * + * This method is called before each request, allowing for dynamic token refresh + * and rotation. Implementations should handle caching internally if needed to + * avoid excessive token generation. + * + * @returns A promise that resolves to a record of header names and values + */ + getAuthHeaders(): Promise>; +} + +/** + * Extension point for providing custom authentication to the Elasticsearch search engine. + * + * @remarks + * + * Use this extension point to provide dynamic authentication mechanisms such as + * bearer tokens with automatic rotation. When an auth provider is set, it takes + * precedence over any static authentication configured in app-config.yaml. + * + * @example + * + * ```ts + * import { createBackendModule } from '@backstage/backend-plugin-api'; + * import { elasticsearchAuthExtensionPoint } from '@backstage/plugin-search-backend-module-elasticsearch'; + * + * export default createBackendModule({ + * pluginId: 'search', + * moduleId: 'elasticsearch-custom-auth', + * register(env) { + * env.registerInit({ + * deps: { + * elasticsearchAuth: elasticsearchAuthExtensionPoint, + * }, + * async init({ elasticsearchAuth }) { + * elasticsearchAuth.setAuthProvider({ + * async getAuthHeaders() { + * const token = await fetchTokenFromIdentityService(); + * return { Authorization: `Bearer ${token}` }; + * }, + * }); + * }, + * }); + * }, + * }); + * ``` + * + * @public + */ +export interface ElasticSearchAuthExtensionPoint { + /** + * Sets the authentication provider for the Elasticsearch search engine. + * + * @remarks + * + * This method can only be called once. Subsequent calls will throw an error. + * The auth provider takes precedence over static authentication configuration. + * + * @param provider - The authentication provider to use + */ + setAuthProvider(provider: ElasticSearchAuthProvider): void; +} + +/** + * Extension point used to customize Elasticsearch/OpenSearch authentication. + * + * @public + */ +export const elasticsearchAuthExtensionPoint = + createExtensionPoint({ + id: 'search.elasticsearchEngine.auth', + }); diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchAuthTransport.test.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchAuthTransport.test.ts new file mode 100644 index 0000000000..0264f9b93c --- /dev/null +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchAuthTransport.test.ts @@ -0,0 +1,194 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + createOpenSearchAuthTransport, + createElasticSearchAuthTransport, +} from './ElasticSearchAuthTransport'; +import { ElasticSearchAuthProvider } from '../auth'; + +describe('ElasticSearchAuthTransport', () => { + describe('createOpenSearchAuthTransport', () => { + it('should return a Transport class', () => { + const authProvider: ElasticSearchAuthProvider = { + getAuthHeaders: jest + .fn() + .mockResolvedValue({ Authorization: 'Bearer test' }), + }; + + const Transport = createOpenSearchAuthTransport(authProvider); + + expect(Transport).toBeDefined(); + expect(typeof Transport).toBe('function'); + }); + + it('should call getAuthHeaders when request is made', async () => { + const authProvider: ElasticSearchAuthProvider = { + getAuthHeaders: jest + .fn() + .mockResolvedValue({ Authorization: 'Bearer test-token' }), + }; + + const Transport = createOpenSearchAuthTransport(authProvider); + + // Create a mock parent class request method + const mockParentRequest = jest.fn().mockResolvedValue({ body: {} }); + + // Create an instance with mocked internals + const transportInstance = Object.create(Transport.prototype); + Object.getPrototypeOf(Object.getPrototypeOf(transportInstance)).request = + mockParentRequest; + + // Call request method (promise style) + const requestPromise = transportInstance.request( + { method: 'GET', path: '/' }, + {}, + ); + + // The auth provider should be called + await requestPromise.catch(() => {}); // Ignore errors from incomplete mock + expect(authProvider.getAuthHeaders).toHaveBeenCalled(); + }); + + it('should be usable as Transport option', () => { + const authProvider: ElasticSearchAuthProvider = { + getAuthHeaders: jest + .fn() + .mockResolvedValue({ Authorization: 'Bearer test' }), + }; + + const Transport = createOpenSearchAuthTransport(authProvider); + + // Verify the Transport has the expected static properties + expect(Transport).toHaveProperty('sniffReasons'); + }); + }); + + describe('createElasticSearchAuthTransport', () => { + it('should return a Transport class', () => { + const authProvider: ElasticSearchAuthProvider = { + getAuthHeaders: jest + .fn() + .mockResolvedValue({ Authorization: 'Bearer test' }), + }; + + const Transport = createElasticSearchAuthTransport(authProvider); + + expect(Transport).toBeDefined(); + expect(typeof Transport).toBe('function'); + }); + + it('should call getAuthHeaders when request is made', async () => { + const authProvider: ElasticSearchAuthProvider = { + getAuthHeaders: jest + .fn() + .mockResolvedValue({ Authorization: 'Bearer test-token' }), + }; + + const Transport = createElasticSearchAuthTransport(authProvider); + + // Create a mock parent class request method + const mockParentRequest = jest.fn().mockResolvedValue({ body: {} }); + + // Create an instance with mocked internals + const transportInstance = Object.create(Transport.prototype); + Object.getPrototypeOf(Object.getPrototypeOf(transportInstance)).request = + mockParentRequest; + + // Call request method (promise style) + const requestPromise = transportInstance.request( + { method: 'GET', path: '/' }, + {}, + ); + + // The auth provider should be called + await requestPromise.catch(() => {}); // Ignore errors from incomplete mock + expect(authProvider.getAuthHeaders).toHaveBeenCalled(); + }); + + it('should be usable as Transport option', () => { + const authProvider: ElasticSearchAuthProvider = { + getAuthHeaders: jest + .fn() + .mockResolvedValue({ Authorization: 'Bearer test' }), + }; + + const Transport = createElasticSearchAuthTransport(authProvider); + + // Verify the Transport has the expected static properties + expect(Transport).toHaveProperty('sniffReasons'); + }); + }); + + describe('auth provider integration', () => { + it('should support async token retrieval', async () => { + let tokenRetrievalCount = 0; + const authProvider: ElasticSearchAuthProvider = { + getAuthHeaders: jest.fn().mockImplementation(async () => { + tokenRetrievalCount++; + // Simulate async token fetch + await new Promise(resolve => setTimeout(resolve, 10)); + return { Authorization: `Bearer token-${tokenRetrievalCount}` }; + }), + }; + + // Just verify the provider works correctly + const headers1 = await authProvider.getAuthHeaders(); + const headers2 = await authProvider.getAuthHeaders(); + + expect(headers1.Authorization).toBe('Bearer token-1'); + expect(headers2.Authorization).toBe('Bearer token-2'); + expect(authProvider.getAuthHeaders).toHaveBeenCalledTimes(2); + }); + + it('should support token rotation pattern', async () => { + const tokens = ['initial-token', 'rotated-token', 'final-token']; + let currentTokenIndex = 0; + + const authProvider: ElasticSearchAuthProvider = { + getAuthHeaders: jest.fn().mockImplementation(async () => { + const token = tokens[currentTokenIndex]; + currentTokenIndex = Math.min( + currentTokenIndex + 1, + tokens.length - 1, + ); + return { Authorization: `Bearer ${token}` }; + }), + }; + + // Simulate multiple requests that would trigger token rotation + const headers1 = await authProvider.getAuthHeaders(); + const headers2 = await authProvider.getAuthHeaders(); + const headers3 = await authProvider.getAuthHeaders(); + + expect(headers1.Authorization).toBe('Bearer initial-token'); + expect(headers2.Authorization).toBe('Bearer rotated-token'); + expect(headers3.Authorization).toBe('Bearer final-token'); + }); + + it('should handle errors in getAuthHeaders gracefully', async () => { + const authProvider: ElasticSearchAuthProvider = { + getAuthHeaders: jest + .fn() + .mockRejectedValue(new Error('Token fetch failed')), + }; + + await expect(authProvider.getAuthHeaders()).rejects.toThrow( + 'Token fetch failed', + ); + }); + }); +}); diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchAuthTransport.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchAuthTransport.ts new file mode 100644 index 0000000000..991fdac71a --- /dev/null +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchAuthTransport.ts @@ -0,0 +1,153 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Transport as OpenSearchTransport } from '@opensearch-project/opensearch'; +import { Transport as ElasticSearchTransport } from '@elastic/elasticsearch'; +import { ElasticSearchAuthProvider } from '../auth'; +import { ElasticSearchTransportConstructor } from './ElasticSearchClientOptions'; + +/** + * Creates a custom OpenSearch Transport class that injects authentication headers + * from the provided auth provider before each request. + * + * @param authProvider - The authentication provider to use for getting headers + * @returns A Transport class constructor that can be used with the OpenSearch client + * + * @internal + */ +export function createOpenSearchAuthTransport( + authProvider: ElasticSearchAuthProvider, +): ElasticSearchTransportConstructor { + class AuthTransport extends OpenSearchTransport { + request( + params: any, + optionsOrCallback: any = {}, + maybeCallback?: any, + ): any { + // options is optional, so if it's a function, it's the callback + const isOptionsCallback = typeof optionsOrCallback === 'function'; + const options = isOptionsCallback ? {} : optionsOrCallback; + const callback = isOptionsCallback ? optionsOrCallback : maybeCallback; + + // If callback style, wrap it to inject headers + if (callback !== undefined) { + authProvider + .getAuthHeaders() + .then(authHeaders => { + const mergedOptions = { + ...options, + headers: { + ...options.headers, + ...authHeaders, + }, + }; + return super.request(params, mergedOptions, callback); + }) + .catch(callback); + + // Return an abort-able object similar to the AWS transport + return { + abort: () => {}, + }; + } + + // Promise style + return authProvider.getAuthHeaders().then(authHeaders => { + const mergedOptions = { + ...options, + headers: { + ...options.headers, + ...authHeaders, + }, + }; + return super.request(params, mergedOptions); + }); + } + } + + return AuthTransport as unknown as ElasticSearchTransportConstructor; +} + +/** + * Creates a custom Elasticsearch Transport class that injects authentication headers + * from the provided auth provider before each request. + * + * @param authProvider - The authentication provider to use for getting headers + * @returns A Transport class constructor that can be used with the Elasticsearch client + * + * @internal + */ +export function createElasticSearchAuthTransport( + authProvider: ElasticSearchAuthProvider, +): ElasticSearchTransportConstructor { + class AuthTransport extends ElasticSearchTransport { + request(params: any, options?: any, callback?: any): any { + // Handle overloaded signatures + if (typeof options === 'function') { + // Callback style without options + const cb = options; + authProvider + .getAuthHeaders() + .then(authHeaders => { + const mergedOptions = { + headers: authHeaders, + }; + return super.request(params, mergedOptions, cb); + }) + .catch(cb); + + return { abort: () => {} }; + } + + if (callback !== undefined) { + // Callback style with options + authProvider + .getAuthHeaders() + .then(authHeaders => { + const mergedOptions = { + ...options, + headers: { + ...options?.headers, + ...authHeaders, + }, + }; + return super.request(params, mergedOptions, callback); + }) + .catch(callback); + + return { abort: () => {} }; + } + + // Promise style + const result = authProvider.getAuthHeaders().then(authHeaders => { + const mergedOptions = { + ...options, + headers: { + ...options?.headers, + ...authHeaders, + }, + }; + return super.request(params, mergedOptions); + }); + + // Add abort method to match TransportRequestPromise interface + (result as any).abort = () => {}; + return result; + } + } + + return AuthTransport as unknown as ElasticSearchTransportConstructor; +} diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchClientOptions.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchClientOptions.ts index 2974f03331..203f24e3b3 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchClientOptions.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchClientOptions.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import type { ConnectionOptions as TLSConnectionOptions } from 'tls'; +import type { ConnectionOptions as TLSConnectionOptions } from 'node:tls'; /** * Typeguard to differentiate ElasticSearch client options which are compatible diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchClientWrapper.test.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchClientWrapper.test.ts index dd5a37e5f0..cb7c8ffcd3 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchClientWrapper.test.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchClientWrapper.test.ts @@ -17,7 +17,7 @@ import { Client as ElasticSearchClient } from '@elastic/elasticsearch'; import { Client as OpenSearchClient } from '@opensearch-project/opensearch'; import Mock from '@short.io/opensearch-mock'; -import { Readable } from 'stream'; +import { Readable } from 'node:stream'; import { ElasticSearchClientWrapper } from './ElasticSearchClientWrapper'; import { ElasticSearchClientOptions } from './ElasticSearchSearchEngine'; diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchClientWrapper.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchClientWrapper.ts index 14539b716f..86e71254ab 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchClientWrapper.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchClientWrapper.ts @@ -16,7 +16,7 @@ import { Client as ElasticSearchClient } from '@elastic/elasticsearch'; import { Client as OpenSearchClient } from '@opensearch-project/opensearch'; -import { Readable } from 'stream'; +import { Readable } from 'node:stream'; import { ElasticSearchClientOptions, isOpenSearchCompatible, diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.test.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.test.ts index 9b1f32c0ba..a17d6850ab 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.test.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.test.ts @@ -996,6 +996,114 @@ describe('ElasticSearchSearchEngine', () => { }), ).not.toThrow(); }); + + it('should accept an authProvider and not require auth config', async () => { + const config = new ConfigReader({ + search: { + elasticsearch: { + node: 'http://test-node', + // No auth config - using authProvider instead + }, + }, + }); + + const authProvider = { + getAuthHeaders: jest + .fn() + .mockResolvedValue({ Authorization: 'Bearer test-token' }), + }; + + const engine = await ElasticSearchSearchEngine.fromConfig({ + logger: mockServices.logger.mock(), + config, + authProvider, + }); + + expect(engine).toBeDefined(); + }); + + it('should accept an authProvider with opensearch provider', async () => { + const config = new ConfigReader({ + search: { + elasticsearch: { + provider: 'opensearch', + node: 'http://test-node', + // No auth config - using authProvider instead + }, + }, + }); + + const authProvider = { + getAuthHeaders: jest + .fn() + .mockResolvedValue({ Authorization: 'Bearer test-token' }), + }; + + const engine = await ElasticSearchSearchEngine.fromConfig({ + logger: mockServices.logger.mock(), + config, + authProvider, + }); + + expect(engine).toBeDefined(); + }); + + it('should accept an authProvider with elastic provider', async () => { + // cloudId format: : + // The base64 part decodes to: $ + const cloudId = + 'test:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvJGFiY2QkZWZnaA=='; + const config = new ConfigReader({ + search: { + elasticsearch: { + provider: 'elastic', + cloudId, + // No auth config - using authProvider instead + }, + }, + }); + + const authProvider = { + getAuthHeaders: jest + .fn() + .mockResolvedValue({ Authorization: 'Bearer test-token' }), + }; + + const engine = await ElasticSearchSearchEngine.fromConfig({ + logger: mockServices.logger.mock(), + config, + authProvider, + }); + + expect(engine).toBeDefined(); + }); + + it('should throw error when using authProvider with aws provider', async () => { + const config = new ConfigReader({ + search: { + elasticsearch: { + provider: 'aws', + node: 'http://test-node.us-east-1.es.amazonaws.com', + }, + }, + }); + + const authProvider = { + getAuthHeaders: jest + .fn() + .mockResolvedValue({ Authorization: 'Bearer test-token' }), + }; + + await expect( + ElasticSearchSearchEngine.fromConfig({ + logger: mockServices.logger.mock(), + config, + authProvider, + }), + ).rejects.toThrow( + 'Custom auth provider is not supported with AWS provider', + ); + }); }); }); diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts index c03541d9d0..96c2b99d84 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts @@ -41,6 +41,11 @@ import { DefaultAwsCredentialsManager, } from '@backstage/integration-aws-node'; import { LoggerService } from '@backstage/backend-plugin-api'; +import { ElasticSearchAuthProvider } from '../auth'; +import { + createOpenSearchAuthTransport, + createElasticSearchAuthTransport, +} from './ElasticSearchAuthTransport'; export type { ElasticSearchClientOptions }; @@ -82,6 +87,19 @@ export type ElasticSearchOptions = { aliasPostfix?: string; indexPrefix?: string; translator?: ElasticSearchQueryTranslator; + /** + * An optional authentication provider for dynamic authentication. + * + * @remarks + * + * When provided, this auth provider will be used to inject authentication + * headers into each request to Elasticsearch/OpenSearch. This is useful + * for scenarios requiring dynamic tokens (e.g., bearer tokens with rotation). + * + * The auth provider takes precedence over static authentication configured + * in app-config.yaml. + */ + authProvider?: ElasticSearchAuthProvider; }; /** @@ -180,11 +198,13 @@ export class ElasticSearchSearchEngine implements SearchEngine { aliasPostfix = `search`, indexPrefix = ``, translator, + authProvider, } = options; const credentialProvider = DefaultAwsCredentialsManager.fromConfig(config); const clientOptions = await this.createElasticSearchClientOptions( await credentialProvider?.getCredentialProvider(), config.getConfig('search.elasticsearch'), + authProvider, ); if (clientOptions.provider === 'elastic') { logger.info('Initializing Elastic.co ElasticSearch search engine.'); @@ -504,6 +524,7 @@ export class ElasticSearchSearchEngine implements SearchEngine { private static async createElasticSearchClientOptions( credentialProvider: AwsCredentialProvider, config?: Config, + authProvider?: ElasticSearchAuthProvider, ): Promise { if (!config) { throw new Error('No elastic search config found'); @@ -512,16 +533,26 @@ export class ElasticSearchSearchEngine implements SearchEngine { const sslConfig = clientOptionsConfig?.getOptionalConfig('ssl'); if (config.getOptionalString('provider') === 'elastic') { - const authConfig = config.getConfig('auth'); + const authConfig = authProvider ? undefined : config.getConfig('auth'); return { provider: 'elastic', cloud: { id: config.getString('cloudId'), }, - auth: { - username: authConfig.getString('username'), - password: authConfig.getString('password'), - }, + // When using authProvider, we inject auth via custom Transport, not static auth + ...(authConfig + ? { + auth: { + username: authConfig.getString('username'), + password: authConfig.getString('password'), + }, + } + : {}), + ...(authProvider + ? { + Transport: createElasticSearchAuthTransport(authProvider), + } + : {}), ...(sslConfig ? { ssl: { @@ -533,6 +564,12 @@ export class ElasticSearchSearchEngine implements SearchEngine { }; } if (config.getOptionalString('provider') === 'aws') { + // AWS provider uses SigV4 signing; authProvider is not applicable here + if (authProvider) { + throw new Error( + 'Custom auth provider is not supported with AWS provider. AWS uses SigV4 signing.', + ); + } const requestSigner = new RequestSigner(config.getString('node')); const service = config.getOptionalString('service') ?? requestSigner.service; @@ -560,14 +597,26 @@ export class ElasticSearchSearchEngine implements SearchEngine { }; } if (config.getOptionalString('provider') === 'opensearch') { - const authConfig = config.getConfig('auth'); + const authConfig = authProvider + ? undefined + : config.getOptionalConfig('auth'); return { provider: 'opensearch', node: config.getString('node'), - auth: { - username: authConfig.getString('username'), - password: authConfig.getString('password'), - }, + // When using authProvider, we inject auth via custom Transport, not static auth + ...(authConfig + ? { + auth: { + username: authConfig.getString('username'), + password: authConfig.getString('password'), + }, + } + : {}), + ...(authProvider + ? { + Transport: createOpenSearchAuthTransport(authProvider), + } + : {}), ...(sslConfig ? { ssl: { @@ -578,7 +627,10 @@ export class ElasticSearchSearchEngine implements SearchEngine { : {}), }; } - const authConfig = config.getOptionalConfig('auth'); + // Default provider (standard Elasticsearch) + const authConfig = authProvider + ? undefined + : config.getOptionalConfig('auth'); const auth = authConfig && (authConfig.has('apiKey') @@ -591,7 +643,12 @@ export class ElasticSearchSearchEngine implements SearchEngine { }); return { node: config.getString('node'), - auth, + ...(auth ? { auth } : {}), + ...(authProvider + ? { + Transport: createElasticSearchAuthTransport(authProvider), + } + : {}), ...(sslConfig ? { ssl: { diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.test.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.test.ts index 0ba2882c61..c36d81eb14 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.test.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.test.ts @@ -60,10 +60,11 @@ describe('ElasticSearchSearchEngineIndexer', () => { bulkSpy, ); refreshSpy = jest.fn().mockReturnValue({}); + // Use specific pattern to avoid conflict with /some-type-index__* wildcard mock mock.add( { method: 'GET', - path: '/:index/_refresh', + path: '/some-type-index__:ts/_refresh', }, refreshSpy, ); diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts index b57524e13a..30e19093a7 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts @@ -17,7 +17,7 @@ import { BatchSearchEngineIndexer } from '@backstage/plugin-search-backend-node'; import { ElasticSearchClientWrapper } from './ElasticSearchClientWrapper'; import { IndexableDocument } from '@backstage/plugin-search-common'; -import { Readable } from 'stream'; +import { Readable } from 'node:stream'; import { LoggerService } from '@backstage/backend-plugin-api'; /** @@ -97,7 +97,8 @@ export class ElasticSearchSearchEngineIndexer extends BatchSearchEngineIndexer { : {}), }; }, - refreshOnCompletion: options.skipRefresh !== true, + refreshOnCompletion: + options.skipRefresh !== true ? that.indexName : false, }); // Safely catch errors thrown by the bulk helper client, e.g. HTTP timeouts diff --git a/plugins/search-backend-module-elasticsearch/src/module.ts b/plugins/search-backend-module-elasticsearch/src/module.ts index d4ce249093..d4db5ce90a 100644 --- a/plugins/search-backend-module-elasticsearch/src/module.ts +++ b/plugins/search-backend-module-elasticsearch/src/module.ts @@ -23,6 +23,15 @@ import { ElasticSearchQueryTranslator, ElasticSearchSearchEngine, } from './engines/ElasticSearchSearchEngine'; +import { + type ElasticSearchAuthProvider, + type ElasticSearchAuthExtensionPoint, + elasticsearchAuthExtensionPoint, +} from './auth'; + +// Re-export auth types and extension point +export type { ElasticSearchAuthProvider, ElasticSearchAuthExtensionPoint }; +export { elasticsearchAuthExtensionPoint }; /** @public */ export interface ElasticSearchQueryTranslatorExtensionPoint { @@ -49,6 +58,7 @@ export default createBackendModule({ moduleId: 'elasticsearch-engine', register(env) { let translator: ElasticSearchQueryTranslator | undefined; + let authProvider: ElasticSearchAuthProvider | undefined; env.registerExtensionPoint(elasticsearchTranslatorExtensionPoint, { setTranslator(newTranslator) { @@ -61,6 +71,15 @@ export default createBackendModule({ }, }); + env.registerExtensionPoint(elasticsearchAuthExtensionPoint, { + setAuthProvider(provider) { + if (authProvider) { + throw new Error('ElasticSearch auth provider may only be set once'); + } + authProvider = provider; + }, + }); + env.registerInit({ deps: { searchEngineRegistry: searchEngineRegistryExtensionPoint, @@ -82,11 +101,17 @@ export default createBackendModule({ if (indexPrefix) { logger.info(`Index prefix will be used for indices: ${indexPrefix}`); } + if (authProvider) { + logger.info( + 'Using custom auth provider for ElasticSearch authentication.', + ); + } searchEngineRegistry.setSearchEngine( await ElasticSearchSearchEngine.fromConfig({ logger, config, translator, + authProvider, indexPrefix, }), ); diff --git a/plugins/search-backend-module-explore/CHANGELOG.md b/plugins/search-backend-module-explore/CHANGELOG.md index 8e9b6cfc73..2118645a55 100644 --- a/plugins/search-backend-module-explore/CHANGELOG.md +++ b/plugins/search-backend-module-explore/CHANGELOG.md @@ -1,5 +1,140 @@ # @backstage/plugin-search-backend-module-explore +## 0.3.13 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/plugin-search-backend-node@1.4.3 + - @backstage/config@1.3.7 + - @backstage/plugin-search-common@1.2.23 + +## 0.3.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.3-next.2 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/config@1.3.7-next.0 + - @backstage/plugin-search-common@1.2.23-next.0 + +## 0.3.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-search-backend-node@1.4.3-next.1 + +## 0.3.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-search-backend-node@1.4.3-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-search-common@1.2.22 + +## 0.3.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-search-backend-node@1.4.2 + +## 0.3.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-search-backend-node@1.4.2-next.1 + +## 0.3.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-search-backend-node@1.4.2-next.0 + - @backstage/plugin-search-common@1.2.22 + +## 0.3.11 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- df27350: Updated dependency `@backstage-community/plugin-explore-common` to `^0.12.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-search-backend-node@1.4.1 + - @backstage/plugin-search-common@1.2.22 + +## 0.3.11-next.1 + +### Patch Changes + +- df27350: Updated dependency `@backstage-community/plugin-explore-common` to `^0.12.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-search-backend-node@1.4.1-next.0 + +## 0.3.11-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-search-backend-node@1.4.1-next.0 + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/config@1.3.6 + +## 0.3.10 + +### Patch Changes + +- 9b69262: Updated dependency `@backstage-community/plugin-explore-common` to `^0.9.0`. +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/backend-plugin-api@1.6.0 + +## 0.3.10-next.1 + +### Patch Changes + +- 9b69262: Updated dependency `@backstage-community/plugin-explore-common` to `^0.9.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/plugin-search-backend-node@1.4.0-next.1 + - @backstage/plugin-search-common@1.2.21 + +## 0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-search-common@1.2.21 + +## 0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-search-backend-node@1.3.17 + - @backstage/config@1.3.6 + - @backstage/plugin-search-common@1.2.21 + ## 0.3.9-next.1 ### Patch Changes diff --git a/plugins/search-backend-module-explore/package.json b/plugins/search-backend-module-explore/package.json index e728a0418d..82a1d10ffb 100644 --- a/plugins/search-backend-module-explore/package.json +++ b/plugins/search-backend-module-explore/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-explore", - "version": "0.3.9-next.1", + "version": "0.3.13", "description": "A module for the search backend that exports explore modules", "backstage": { "moved": "@backstage-community/plugin-search-backend-module-explore", @@ -49,7 +49,7 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage-community/plugin-explore-common": "^0.5.0", + "@backstage-community/plugin-explore-common": "^0.12.0", "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", "@backstage/plugin-search-backend-node": "workspace:^", diff --git a/plugins/search-backend-module-explore/src/collators/ToolDocumentCollatorFactory.test.ts b/plugins/search-backend-module-explore/src/collators/ToolDocumentCollatorFactory.test.ts index 380c22ada1..8b55b07bbb 100644 --- a/plugins/search-backend-module-explore/src/collators/ToolDocumentCollatorFactory.test.ts +++ b/plugins/search-backend-module-explore/src/collators/ToolDocumentCollatorFactory.test.ts @@ -21,7 +21,7 @@ import { } from '@backstage/backend-test-utils'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; -import { Readable } from 'stream'; +import { Readable } from 'node:stream'; import { ToolDocumentCollatorFactory } from './ToolDocumentCollatorFactory'; const logger = mockServices.logger.mock(); diff --git a/plugins/search-backend-module-explore/src/collators/ToolDocumentCollatorFactory.ts b/plugins/search-backend-module-explore/src/collators/ToolDocumentCollatorFactory.ts index 4f86e37fb3..6f8685e637 100644 --- a/plugins/search-backend-module-explore/src/collators/ToolDocumentCollatorFactory.ts +++ b/plugins/search-backend-module-explore/src/collators/ToolDocumentCollatorFactory.ts @@ -25,7 +25,7 @@ import { DocumentCollatorFactory, IndexableDocument, } from '@backstage/plugin-search-common'; -import { Readable } from 'stream'; +import { Readable } from 'node:stream'; /** * Extended IndexableDocument with explore tool specific properties diff --git a/plugins/search-backend-module-pg/CHANGELOG.md b/plugins/search-backend-module-pg/CHANGELOG.md index a8dd1af566..832471dd21 100644 --- a/plugins/search-backend-module-pg/CHANGELOG.md +++ b/plugins/search-backend-module-pg/CHANGELOG.md @@ -1,5 +1,139 @@ # @backstage/plugin-search-backend-module-pg +## 0.5.54 + +### Patch Changes + +- aa08b7f: Fix a bug in large document indexing logic by using sub-transaction rollbacks +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/plugin-search-backend-node@1.4.3 + - @backstage/config@1.3.7 + - @backstage/plugin-search-common@1.2.23 + +## 0.5.54-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.3-next.2 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/config@1.3.7-next.0 + - @backstage/plugin-search-common@1.2.23-next.0 + +## 0.5.54-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-search-backend-node@1.4.3-next.1 + +## 0.5.54-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-search-backend-node@1.4.3-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-search-common@1.2.22 + +## 0.5.53 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-search-backend-node@1.4.2 + +## 0.5.53-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-search-backend-node@1.4.2-next.1 + +## 0.5.53-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-search-backend-node@1.4.2-next.0 + - @backstage/plugin-search-common@1.2.22 + +## 0.5.52 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 2ee354a: Return `numberOfResults` count with search query responses +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-search-backend-node@1.4.1 + - @backstage/plugin-search-common@1.2.22 + +## 0.5.52-next.1 + +### Patch Changes + +- 2ee354a: Return `numberOfResults` count with search query responses +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.1 + +## 0.5.52-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-search-backend-node@1.4.1-next.0 + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/config@1.3.6 + +## 0.5.51 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/backend-plugin-api@1.6.0 + +## 0.5.51-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/plugin-search-backend-node@1.4.0-next.1 + - @backstage/plugin-search-common@1.2.21 + +## 0.5.51-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-search-common@1.2.21 + +## 0.5.50 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-search-backend-node@1.3.17 + - @backstage/config@1.3.6 + - @backstage/plugin-search-common@1.2.21 + ## 0.5.50-next.1 ### Patch Changes diff --git a/plugins/search-backend-module-pg/package.json b/plugins/search-backend-module-pg/package.json index f61111adee..fa31b63da6 100644 --- a/plugins/search-backend-module-pg/package.json +++ b/plugins/search-backend-module-pg/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-pg", - "version": "0.5.50-next.1", + "version": "0.5.54", "description": "A module for the search backend that implements search using PostgreSQL", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/search-backend-module-pg/report.api.md b/plugins/search-backend-module-pg/report.api.md index 67e60b7822..317547cbdc 100644 --- a/plugins/search-backend-module-pg/report.api.md +++ b/plugins/search-backend-module-pg/report.api.md @@ -89,6 +89,7 @@ export interface DocumentResultRow { document: IndexableDocument; // (undocumented) highlight: IndexableDocument; + total_count: string; // (undocumented) type: string; } diff --git a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.test.ts b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.test.ts index bc3df76692..9c77a04e44 100644 --- a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.test.ts +++ b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.test.ts @@ -193,6 +193,7 @@ describe('PgSearchEngine', () => { database.transaction.mockImplementation(fn => fn(tx)); database.query.mockResolvedValue([ { + total_count: '1', document: { title: 'Hello World', text: 'Lorem Ipsum', @@ -212,6 +213,7 @@ describe('PgSearchEngine', () => { }); expect(results).toEqual({ + numberOfResults: 1, results: [ { document: { @@ -251,6 +253,7 @@ describe('PgSearchEngine', () => { Array(30) .fill(0) .map((_, i) => ({ + total_count: '30', document: { title: 'Hello World', text: 'Lorem Ipsum', @@ -270,6 +273,7 @@ describe('PgSearchEngine', () => { }); expect(results).toEqual({ + numberOfResults: 30, results: Array(25) .fill(0) .map((_, i) => ({ @@ -309,6 +313,7 @@ describe('PgSearchEngine', () => { Array(30) .fill(0) .map((_, i) => ({ + total_count: '30', document: { title: 'Hello World', text: 'Lorem Ipsum', @@ -330,6 +335,7 @@ describe('PgSearchEngine', () => { }); expect(results).toEqual({ + numberOfResults: 30, results: Array(30) .fill(0) .map((_, i) => ({ diff --git a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts index 1a024be33c..ea75b1a739 100644 --- a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts +++ b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts @@ -269,7 +269,12 @@ export class PgSearchEngine implements SearchEngine { }), ); - return { results, nextPageCursor, previousPageCursor }; + return { + results, + numberOfResults: rows.length > 0 ? parseInt(rows[0].total_count, 10) : 0, + nextPageCursor, + previousPageCursor, + }; } } diff --git a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngineIndexer.test.ts b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngineIndexer.test.ts index 7554cebfd7..7a83a8d563 100644 --- a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngineIndexer.test.ts +++ b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngineIndexer.test.ts @@ -15,15 +15,23 @@ */ import { TestPipeline } from '@backstage/plugin-search-backend-node'; import { range } from 'lodash'; -import { Transform } from 'stream'; +import { Transform } from 'node:stream'; import { PgSearchEngineIndexer } from './PgSearchEngineIndexer'; import { DatabaseStore } from '../database'; describe('PgSearchEngineIndexer', () => { + const subTx = { + rollback: jest.fn(), + commit: jest.fn(), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any; + const tx = { rollback: jest.fn(), commit: jest.fn(), + transaction: jest.fn().mockResolvedValue(subTx), isCompleted: jest.fn(), + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any; let database: jest.Mocked; let indexer: PgSearchEngineIndexer; @@ -65,8 +73,14 @@ describe('PgSearchEngineIndexer', () => { 'my-type', documents, ); - expect(database.completeInsert).toHaveBeenCalledWith(tx, 'my-type', false); + expect(database.completeInsert).toHaveBeenCalledWith( + subTx, + 'my-type', + false, + ); + expect(subTx.commit).toHaveBeenCalled(); expect(tx.commit).toHaveBeenCalled(); + expect(subTx.rollback).not.toHaveBeenCalled(); expect(tx.rollback).not.toHaveBeenCalled(); }); @@ -95,18 +109,20 @@ describe('PgSearchEngineIndexer', () => { expect(database.completeInsert).toHaveBeenCalledTimes(2); expect(database.completeInsert).toHaveBeenNthCalledWith( 1, - tx, + subTx, 'my-type', false, ); expect(database.completeInsert).toHaveBeenNthCalledWith( 2, - tx, + subTx, 'my-type', true, ); + expect(subTx.commit).toHaveBeenCalled(); expect(tx.commit).toHaveBeenCalled(); - expect(tx.rollback).toHaveBeenCalledTimes(1); + expect(subTx.rollback).toHaveBeenCalledTimes(1); + expect(tx.rollback).not.toHaveBeenCalled(); }); it('should batch insert documents', async () => { @@ -121,7 +137,11 @@ describe('PgSearchEngineIndexer', () => { expect(database.getTransaction).toHaveBeenCalledTimes(1); expect(database.prepareInsert).toHaveBeenCalledTimes(1); expect(database.insertDocuments).toHaveBeenCalledTimes(4); - expect(database.completeInsert).toHaveBeenCalledWith(tx, 'my-type', false); + expect(database.completeInsert).toHaveBeenCalledWith( + subTx, + 'my-type', + false, + ); }); it('should rollback transaction if no documents indexed', async () => { diff --git a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngineIndexer.ts b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngineIndexer.ts index ad8c4f093f..f601ed2668 100644 --- a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngineIndexer.ts +++ b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngineIndexer.ts @@ -89,15 +89,28 @@ export class PgSearchEngineIndexer extends BatchSearchEngineIndexer { let retryTruncated = false; do { + // Sub-transaction allows a partial rollback if a truncated retry is needed + let subTx: Knex.Transaction; + try { + subTx = await this.tx!.transaction(); + } catch (e) { + // If sub-transaction creation fails, rollback the outer transaction + // and re-throw the error so that the stream can be closed and + // destroyed properly. + this.tx!.rollback!(e); + throw e; + } + // Attempt to complete and commit the transaction. try { - await this.store.completeInsert(this.tx!, this.type, retryTruncated); - this.tx!.commit(); + await this.store.completeInsert(subTx, this.type, retryTruncated); + + // Commit both transactions if successful + await subTx.commit(); + await this.tx!.commit(); retryTruncated = false; } catch (e) { - // Otherwise, rollback the transaction and re-throw the error so that the - // stream can be closed and destroyed properly. - this.tx!.rollback!(e); + await subTx.rollback(e); // Rollback the first completeInsert attempt if ( e instanceof Error && @@ -108,6 +121,9 @@ export class PgSearchEngineIndexer extends BatchSearchEngineIndexer { // retry the operation with truncated text. retryTruncated = true; } else { + // Otherwise, rollback the outer transaction and re-throw the error so that the + // stream can be closed and destroyed properly. + this.tx!.rollback!(e); throw e; } } diff --git a/plugins/search-backend-module-pg/src/database/DatabaseDocumentStore.test.ts b/plugins/search-backend-module-pg/src/database/DatabaseDocumentStore.test.ts index ef249fa33d..7ef48d8398 100644 --- a/plugins/search-backend-module-pg/src/database/DatabaseDocumentStore.test.ts +++ b/plugins/search-backend-module-pg/src/database/DatabaseDocumentStore.test.ts @@ -274,6 +274,7 @@ describe('DatabaseDocumentStore', () => { expect(rows).toEqual([ { + total_count: '2', document: { location: 'LOCATION-1', text: 'Hello World', @@ -320,6 +321,7 @@ describe('DatabaseDocumentStore', () => { expect(rows).toEqual([ { + total_count: '2', document: { location: 'LOCATION-1', text: 'Around the world', @@ -329,6 +331,7 @@ describe('DatabaseDocumentStore', () => { type: 'test', }, { + total_count: '2', document: { location: 'LOCATION-1', text: 'Hello World', @@ -382,6 +385,7 @@ describe('DatabaseDocumentStore', () => { expect(rows).toEqual([ { + total_count: '1', document: { location: 'LOCATION-1', text: 'Hello World', @@ -436,6 +440,7 @@ describe('DatabaseDocumentStore', () => { expect(rows).toEqual([ { + total_count: '1', document: { location: 'LOCATION-1', text: 'Hello World', @@ -503,6 +508,7 @@ describe('DatabaseDocumentStore', () => { expect(rows).toEqual([ { + total_count: '4', document: { location: 'LOCATION-1', text: 'Hello World', @@ -513,6 +519,7 @@ describe('DatabaseDocumentStore', () => { type: 'my-type', }, { + total_count: '4', document: { location: 'LOCATION-1', text: 'Hello World', @@ -523,6 +530,7 @@ describe('DatabaseDocumentStore', () => { type: 'my-type', }, { + total_count: '4', document: { location: 'LOCATION-1', text: 'Hello World', @@ -533,6 +541,7 @@ describe('DatabaseDocumentStore', () => { type: 'my-type', }, { + total_count: '4', document: { location: 'LOCATION-1', text: 'Hello World', @@ -585,6 +594,7 @@ describe('DatabaseDocumentStore', () => { expect(rows).toEqual([ { + total_count: '1', document: { location: 'LOCATION-1', text: 'Hello World', @@ -635,6 +645,7 @@ describe('DatabaseDocumentStore', () => { expect(rows).toEqual([ { + total_count: '2', document: { title: 'Lorem Ipsum', text: 'Hello World', @@ -645,6 +656,7 @@ describe('DatabaseDocumentStore', () => { type: 'my-type', }, { + total_count: '2', document: { title: 'Dolor sit amet', text: 'Hello World', diff --git a/plugins/search-backend-module-pg/src/database/DatabaseDocumentStore.ts b/plugins/search-backend-module-pg/src/database/DatabaseDocumentStore.ts index 21512361a9..bd63756b06 100644 --- a/plugins/search-backend-module-pg/src/database/DatabaseDocumentStore.ts +++ b/plugins/search-backend-module-pg/src/database/DatabaseDocumentStore.ts @@ -215,6 +215,7 @@ export class DatabaseDocumentStore implements DatabaseStore { } query.select('type', 'document'); + query.select(tx.raw('COUNT(*) OVER() AS total_count')); if (pgTerm && options.useHighlight) { const headlineOptions = `MaxWords=${options.maxWords}, MinWords=${options.minWords}, ShortWord=${options.shortWord}, HighlightAll=${options.highlightAll}, MaxFragments=${options.maxFragments}, FragmentDelimiter=${options.fragmentDelimiter}, StartSel=${options.preTag}, StopSel=${options.postTag}`; diff --git a/plugins/search-backend-module-pg/src/database/types.ts b/plugins/search-backend-module-pg/src/database/types.ts index f8529fbebe..5012e1d792 100644 --- a/plugins/search-backend-module-pg/src/database/types.ts +++ b/plugins/search-backend-module-pg/src/database/types.ts @@ -58,6 +58,10 @@ export interface RawDocumentRow { /** @public */ export interface DocumentResultRow { + /** + * Total number of documents matching the query, regardless of pagination. + */ + total_count: string; document: IndexableDocument; type: string; highlight: IndexableDocument; diff --git a/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md b/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md index b0e4f709f0..a87d19cfd8 100644 --- a/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md +++ b/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md @@ -1,5 +1,128 @@ # @backstage/plugin-search-backend-module-stack-overflow-collator +## 0.3.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/plugin-search-backend-node@1.4.3 + - @backstage/config@1.3.7 + - @backstage/plugin-search-common@1.2.23 + +## 0.3.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.3-next.2 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/config@1.3.7-next.0 + - @backstage/plugin-search-common@1.2.23-next.0 + +## 0.3.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-search-backend-node@1.4.3-next.1 + +## 0.3.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-search-backend-node@1.4.3-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-search-common@1.2.22 + +## 0.3.18 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-search-backend-node@1.4.2 + +## 0.3.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-search-backend-node@1.4.2-next.1 + +## 0.3.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-search-backend-node@1.4.2-next.0 + - @backstage/plugin-search-common@1.2.22 + +## 0.3.17 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-search-backend-node@1.4.1 + - @backstage/plugin-search-common@1.2.22 + +## 0.3.17-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-search-backend-node@1.4.1-next.0 + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/config@1.3.6 + +## 0.3.16 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/backend-plugin-api@1.6.0 + +## 0.3.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/plugin-search-backend-node@1.4.0-next.1 + - @backstage/plugin-search-common@1.2.21 + +## 0.3.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-search-common@1.2.21 + +## 0.3.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-search-backend-node@1.3.17 + - @backstage/config@1.3.6 + - @backstage/plugin-search-common@1.2.21 + ## 0.3.15-next.1 ### Patch Changes diff --git a/plugins/search-backend-module-stack-overflow-collator/package.json b/plugins/search-backend-module-stack-overflow-collator/package.json index 7246c6f50c..b728e4bbe0 100644 --- a/plugins/search-backend-module-stack-overflow-collator/package.json +++ b/plugins/search-backend-module-stack-overflow-collator/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-stack-overflow-collator", - "version": "0.3.15-next.1", + "version": "0.3.19", "description": "A module for the search backend that exports stack overflow modules", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/search-backend-module-stack-overflow-collator/report.api.md b/plugins/search-backend-module-stack-overflow-collator/report.api.md index a4b731d52e..ab5834b69b 100644 --- a/plugins/search-backend-module-stack-overflow-collator/report.api.md +++ b/plugins/search-backend-module-stack-overflow-collator/report.api.md @@ -8,7 +8,7 @@ import { Config } from '@backstage/config'; import { DocumentCollatorFactory } from '@backstage/plugin-search-common'; import { IndexableDocument } from '@backstage/plugin-search-common'; import { LoggerService } from '@backstage/backend-plugin-api'; -import { Readable } from 'stream'; +import { Readable } from 'node:stream'; // @public const searchStackOverflowCollatorModule: BackendFeature; diff --git a/plugins/search-backend-module-stack-overflow-collator/src/collators/StackOverflowQuestionsCollatorFactory.test.ts b/plugins/search-backend-module-stack-overflow-collator/src/collators/StackOverflowQuestionsCollatorFactory.test.ts index 6a040ad3e8..f4e0135468 100644 --- a/plugins/search-backend-module-stack-overflow-collator/src/collators/StackOverflowQuestionsCollatorFactory.test.ts +++ b/plugins/search-backend-module-stack-overflow-collator/src/collators/StackOverflowQuestionsCollatorFactory.test.ts @@ -23,7 +23,7 @@ import { } from '@backstage/backend-test-utils'; import { TestPipeline } from '@backstage/plugin-search-backend-node'; import { ConfigReader } from '@backstage/config'; -import { Readable } from 'stream'; +import { Readable } from 'node:stream'; import { setupServer } from 'msw/node'; import { rest, RestRequest } from 'msw'; diff --git a/plugins/search-backend-module-stack-overflow-collator/src/collators/StackOverflowQuestionsCollatorFactory.ts b/plugins/search-backend-module-stack-overflow-collator/src/collators/StackOverflowQuestionsCollatorFactory.ts index af9a2a852b..ef7103c75c 100644 --- a/plugins/search-backend-module-stack-overflow-collator/src/collators/StackOverflowQuestionsCollatorFactory.ts +++ b/plugins/search-backend-module-stack-overflow-collator/src/collators/StackOverflowQuestionsCollatorFactory.ts @@ -19,7 +19,7 @@ import { IndexableDocument, } from '@backstage/plugin-search-common'; import { Config } from '@backstage/config'; -import { Readable } from 'stream'; +import { Readable } from 'node:stream'; import qs from 'qs'; import { LoggerService } from '@backstage/backend-plugin-api'; diff --git a/plugins/search-backend-module-techdocs/CHANGELOG.md b/plugins/search-backend-module-techdocs/CHANGELOG.md index 004dcdeaac..37a3e4503f 100644 --- a/plugins/search-backend-module-techdocs/CHANGELOG.md +++ b/plugins/search-backend-module-techdocs/CHANGELOG.md @@ -1,5 +1,238 @@ # @backstage/plugin-search-backend-module-techdocs +## 0.4.13 + +### Patch Changes + +- 5e32f77: Migrated internal usage of the deprecated `catalogServiceRef` from `@backstage/plugin-catalog-node/alpha` to the stable `catalogServiceRef` from `@backstage/plugin-catalog-node`. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/catalog-model@1.8.0 + - @backstage/plugin-catalog-node@2.2.0 + - @backstage/plugin-search-backend-node@1.4.3 + - @backstage/plugin-techdocs-node@1.14.5 + - @backstage/catalog-client@1.15.0 + - @backstage/config@1.3.7 + - @backstage/plugin-catalog-common@1.1.9 + - @backstage/plugin-permission-common@0.9.8 + - @backstage/plugin-search-common@1.2.23 + +## 0.4.13-next.2 + +### Patch Changes + +- 5e32f77: Migrated internal usage of the deprecated `catalogServiceRef` from `@backstage/plugin-catalog-node/alpha` to the stable `catalogServiceRef` from `@backstage/plugin-catalog-node`. +- Updated dependencies + - @backstage/plugin-catalog-node@2.2.0-next.2 + - @backstage/plugin-search-backend-node@1.4.3-next.2 + - @backstage/plugin-techdocs-node@1.14.5-next.2 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/catalog-client@1.14.1-next.0 + - @backstage/catalog-model@1.7.8-next.0 + - @backstage/config@1.3.7-next.0 + - @backstage/plugin-permission-common@0.9.8-next.0 + - @backstage/plugin-catalog-common@1.1.9-next.0 + - @backstage/plugin-search-common@1.2.23-next.0 + +## 0.4.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-search-backend-node@1.4.3-next.1 + - @backstage/plugin-techdocs-node@1.14.5-next.1 + +## 0.4.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-search-backend-node@1.4.3-next.0 + - @backstage/plugin-catalog-node@2.1.1-next.0 + - @backstage/plugin-techdocs-node@1.14.5-next.0 + - @backstage/catalog-client@1.14.0 + - @backstage/catalog-model@1.7.7 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/plugin-search-common@1.2.22 + +## 0.4.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/catalog-client@1.14.0 + - @backstage/plugin-techdocs-node@1.14.4 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/catalog-model@1.7.7 + - @backstage/plugin-search-backend-node@1.4.2 + +## 0.4.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-techdocs-node@1.14.4-next.2 + - @backstage/catalog-client@1.14.0-next.2 + - @backstage/plugin-catalog-node@2.1.0-next.2 + - @backstage/plugin-search-backend-node@1.4.2-next.1 + +## 0.4.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.14.0-next.1 + - @backstage/plugin-techdocs-node@1.14.3-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-search-backend-node@1.4.2-next.0 + - @backstage/plugin-search-common@1.2.22 + +## 0.4.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-client@1.13.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.8 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-search-backend-node@1.4.2-next.0 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-techdocs-node@1.14.3-next.0 + +## 0.4.11 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-search-backend-node@1.4.1 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-techdocs-node@1.14.2 + - @backstage/catalog-client@1.13.0 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-catalog-common@1.1.8 + +## 0.4.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/catalog-client@1.12.2-next.0 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-search-backend-node@1.4.1-next.0 + - @backstage/plugin-techdocs-node@1.14.2-next.1 + +## 0.4.11-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-search-backend-node@1.4.1-next.0 + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/plugin-techdocs-node@1.14.1-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.8-next.0 + +## 0.4.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/plugin-techdocs-node@1.14.0 + - @backstage/plugin-permission-common@0.9.4 + +## 0.4.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-node@1.13.11-next.0 + +## 0.4.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/plugin-techdocs-node@1.13.10 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## 0.4.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-node@1.13.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-backend-node@1.4.0-next.1 + - @backstage/plugin-search-common@1.2.21 + +## 0.4.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-techdocs-node@1.13.10-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-common@1.2.21 + +## 0.4.8 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-backend-node@1.3.17 + - @backstage/plugin-techdocs-node@1.13.9 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/catalog-client@1.12.1 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-search-common@1.2.21 + ## 0.4.8-next.1 ### Patch Changes diff --git a/plugins/search-backend-module-techdocs/package.json b/plugins/search-backend-module-techdocs/package.json index 7a03085c5c..fa07a5c2f5 100644 --- a/plugins/search-backend-module-techdocs/package.json +++ b/plugins/search-backend-module-techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-techdocs", - "version": "0.4.8-next.1", + "version": "0.4.13", "description": "A module for the search backend that exports techdocs modules", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts index 829927a315..76fd9a08e1 100644 --- a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts +++ b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts @@ -21,9 +21,10 @@ import { mockServices, registerMswTestHooks, } from '@backstage/backend-test-utils'; +import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; -import { Readable } from 'stream'; +import { Readable } from 'node:stream'; import { DefaultTechDocsCollatorFactory } from './DefaultTechDocsCollatorFactory'; import { TechDocsCollatorEntityTransformer } from './TechDocsCollatorEntityTransformer'; import { @@ -85,10 +86,12 @@ describe('DefaultTechDocsCollatorFactory', () => { const mockDiscoveryApi = mockServices.discovery.mock({ getBaseUrl: async () => 'http://test-backend', }); + const mockCatalog = catalogServiceMock({ entities: expectedEntities }); const options = { logger, discovery: mockDiscoveryApi, auth: mockServices.auth(), + catalog: mockCatalog, }; it('has expected type', () => { @@ -112,31 +115,6 @@ describe('DefaultTechDocsCollatorFactory', () => { 'http://test-backend/static/docs/default/Component/test-entity-with-docs/search/search_index.json', (_, res, ctx) => res(ctx.status(200), ctx.json(mockSearchDocIndex)), ), - rest.get('http://test-backend/entities', (req, res, ctx) => { - // Imitate offset/limit pagination. - const offset = parseInt( - req.url.searchParams.get('offset') || '0', - 10, - ); - const limit = parseInt( - req.url.searchParams.get('limit') || '500', - 10, - ); - - // Limit 50 corresponds to a case testing pagination. - if (limit === 50) { - // Return 50 copies of invalid entities on the first request. - if (offset === 0) { - return res(ctx.status(200), ctx.json(Array(50).fill({}))); - } - // Then just the regular 2 on the second. - return res(ctx.status(200), ctx.json(expectedEntities)); - } - return res( - ctx.status(200), - ctx.json(expectedEntities.slice(offset, limit + offset)), - ); - }), ); }); @@ -147,7 +125,6 @@ describe('DefaultTechDocsCollatorFactory', () => { it('fetches from the configured catalog and tech docs services', async () => { const pipeline = TestPipeline.fromCollator(collator); const { documents } = await pipeline.execute(); - expect(mockDiscoveryApi.getBaseUrl).toHaveBeenCalledWith('catalog'); expect(mockDiscoveryApi.getBaseUrl).toHaveBeenCalledWith('techdocs'); expect(documents).toHaveLength(mockSearchDocIndex.docs.length); }); @@ -184,6 +161,7 @@ describe('DefaultTechDocsCollatorFactory', () => { discovery: mockDiscoveryApi, logger, auth: mockServices.auth(), + catalog: mockCatalog, }); collator = await factory.getCollator(); @@ -208,9 +186,9 @@ describe('DefaultTechDocsCollatorFactory', () => { }); it('paginates through catalog entities using batchSize', async () => { - // A parallelismLimit of 1 is a catalog limit of 50 per request. Code - // above in the /entities handler ensures valid entities are only - // returned on the second page. + // parallelismLimit of 1 → batchSize of 50 per request. + // First page returns exactly 50 (no techdocs annotation) triggering a + // second request; second page returns the real entity with annotation. const _config = new ConfigReader({ ...config.get(), search: { @@ -221,17 +199,24 @@ describe('DefaultTechDocsCollatorFactory', () => { }, }, }); - factory = DefaultTechDocsCollatorFactory.fromConfig(_config, options); + const paginationCatalog = catalogServiceMock({ entities: [] }); + jest + .spyOn(paginationCatalog, 'getEntities') + .mockResolvedValueOnce({ items: Array(50).fill({}) }) + .mockResolvedValueOnce({ items: expectedEntities }); + factory = DefaultTechDocsCollatorFactory.fromConfig(_config, { + ...options, + catalog: paginationCatalog, + }); collator = await factory.getCollator(); const pipeline = TestPipeline.fromCollator(collator); const { documents } = await pipeline.execute(); - // Only 1 entity with TechDocs configured multiplied by 3 pages. + expect(paginationCatalog.getEntities).toHaveBeenCalledTimes(2); + // First page: 50 entities with no techdocs annotation → 0 docs + // Second page: 1 entity × 3 search index docs → 3 docs expect(documents).toHaveLength(3); - expect(_config.get('search.collators.techdocs.parallelismLimit')).toEqual( - 1, - ); }); describe('with legacyPathCasing configuration', () => { diff --git a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts index b2701bf04e..7d81b681ba 100644 --- a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts +++ b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts @@ -16,8 +16,6 @@ import { CATALOG_FILTER_EXISTS, - CatalogApi, - CatalogClient, EntityFilterQuery, } from '@backstage/catalog-client'; import { @@ -32,7 +30,7 @@ import { Permission } from '@backstage/plugin-permission-common'; import { DocumentCollatorFactory } from '@backstage/plugin-search-common'; import { TechDocsDocument } from '@backstage/plugin-techdocs-node'; import pLimit from 'p-limit'; -import { Readable } from 'stream'; +import { Readable } from 'node:stream'; import { TechDocsCollatorEntityTransformer } from './TechDocsCollatorEntityTransformer'; import { MkSearchIndexDoc, @@ -45,6 +43,7 @@ import { DiscoveryService, LoggerService, } from '@backstage/backend-plugin-api'; +import { CatalogService } from '@backstage/plugin-catalog-node'; /** * Options to configure the TechDocs collator factory @@ -56,7 +55,7 @@ export type TechDocsCollatorFactoryOptions = { logger: LoggerService; auth: AuthService; locationTemplate?: string; - catalogClient?: CatalogApi; + catalog: CatalogService; parallelismLimit?: number; legacyPathCasing?: boolean; entityTransformer?: TechDocsCollatorEntityTransformer; @@ -86,7 +85,7 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory { private locationTemplate: string; private readonly logger: LoggerService; private readonly auth: AuthService; - private readonly catalogClient: CatalogApi; + private readonly catalog: CatalogService; private readonly parallelismLimit: number; private readonly legacyPathCasing: boolean; private entityTransformer: TechDocsCollatorEntityTransformer; @@ -99,9 +98,7 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory { this.locationTemplate = options.locationTemplate || '/docs/:namespace/:kind/:name/:path'; this.logger = options.logger.child({ documentType: this.type }); - this.catalogClient = - options.catalogClient || - new CatalogClient({ discoveryApi: options.discovery }); + this.catalog = options.catalog; this.parallelismLimit = options.parallelismLimit ?? 10; this.legacyPathCasing = options.legacyPathCasing ?? false; this.entityTransformer = options.entityTransformer ?? (() => ({})); @@ -147,13 +144,9 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory { // parallelism limit to simplify configuration. const batchSize = this.parallelismLimit * 50; while (moreEntitiesToGet) { - const { token: catalogToken } = await this.auth.getPluginRequestToken({ - onBehalfOf: await this.auth.getOwnServiceCredentials(), - targetPluginId: 'catalog', - }); - + const credentials = await this.auth.getOwnServiceCredentials(); const entities = ( - await this.catalogClient.getEntities( + await this.catalog.getEntities( { filter: { 'metadata.annotations.backstage.io/techdocs-ref': @@ -163,7 +156,7 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory { limit: batchSize, offset: entitiesRetrieved, }, - { token: catalogToken }, + { credentials }, ) ).items; diff --git a/plugins/search-backend-module-techdocs/src/module.ts b/plugins/search-backend-module-techdocs/src/module.ts index db419169f9..d6149ca81a 100644 --- a/plugins/search-backend-module-techdocs/src/module.ts +++ b/plugins/search-backend-module-techdocs/src/module.ts @@ -27,7 +27,7 @@ import { } from '@backstage/backend-plugin-api'; import { EntityFilterQuery } from '@backstage/catalog-client'; import { Entity } from '@backstage/catalog-model'; -import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha'; +import { catalogServiceRef } from '@backstage/plugin-catalog-node'; import { searchIndexRegistryExtensionPoint } from '@backstage/plugin-search-backend-node/alpha'; import { DefaultTechDocsCollatorFactory } from './collators/DefaultTechDocsCollatorFactory'; import { @@ -162,7 +162,7 @@ export default createBackendModule({ discovery, auth, logger, - catalogClient: catalog, + catalog, entityTransformer, documentTransformer, customCatalogApiFilters, diff --git a/plugins/search-backend-node/CHANGELOG.md b/plugins/search-backend-node/CHANGELOG.md index 9878e07a14..d3f8912a80 100644 --- a/plugins/search-backend-node/CHANGELOG.md +++ b/plugins/search-backend-node/CHANGELOG.md @@ -1,5 +1,143 @@ # @backstage/plugin-search-backend-node +## 1.4.3 + +### Patch Changes + +- 482ceed: Migrated from `assertError` to `toError` for error handling. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/errors@1.3.0 + - @backstage/config@1.3.7 + - @backstage/plugin-permission-common@0.9.8 + - @backstage/plugin-search-common@1.2.23 + +## 1.4.3-next.2 + +### Patch Changes + +- 482ceed: Migrated from `assertError` to `toError` for error handling. +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/config@1.3.7-next.0 + - @backstage/plugin-permission-common@0.9.8-next.0 + - @backstage/plugin-search-common@1.2.23-next.0 + +## 1.4.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + +## 1.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/plugin-search-common@1.2.22 + +## 1.4.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-permission-common@0.9.7 + +## 1.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + +## 1.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-search-common@1.2.22 + +## 1.4.1 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-permission-common@0.9.6 + +## 1.4.1-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 1.4.0 + +### Minor Changes + +- 4d3ddb9: Improving method that search tokenizer breaks apart entity names + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + +## 1.4.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-common@1.2.21 + +## 1.4.0-next.0 + +### Minor Changes + +- 4d3ddb9: Improving method that search tokenizer breaks apart entity names + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-common@1.2.21 + +## 1.3.17 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/config@1.3.6 + - @backstage/plugin-search-common@1.2.21 + ## 1.3.17-next.1 ### Patch Changes diff --git a/plugins/search-backend-node/package.json b/plugins/search-backend-node/package.json index d7477bef7d..3b92601e23 100644 --- a/plugins/search-backend-node/package.json +++ b/plugins/search-backend-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-node", - "version": "1.3.17-next.1", + "version": "1.4.3", "description": "A library for Backstage backend plugins that want to interact with the search backend plugin", "backstage": { "role": "node-library", diff --git a/plugins/search-backend-node/report.api.md b/plugins/search-backend-node/report.api.md index a5e3dea7d4..f701def70d 100644 --- a/plugins/search-backend-node/report.api.md +++ b/plugins/search-backend-node/report.api.md @@ -13,13 +13,13 @@ import { IndexableResultSet } from '@backstage/plugin-search-common'; import { LoggerService } from '@backstage/backend-plugin-api'; import { default as lunr_2 } from 'lunr'; import { Permission } from '@backstage/plugin-permission-common'; -import { Readable } from 'stream'; +import { Readable } from 'node:stream'; import { SchedulerServiceTaskFunction } from '@backstage/backend-plugin-api'; import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; import { SearchQuery } from '@backstage/plugin-search-common'; -import { Transform } from 'stream'; +import { Transform } from 'node:stream'; import { UrlReaderService } from '@backstage/backend-plugin-api'; -import { Writable } from 'stream'; +import { Writable } from 'node:stream'; // @public export abstract class BatchSearchEngineIndexer extends Writable { diff --git a/plugins/search-backend-node/src/IndexBuilder.test.ts b/plugins/search-backend-node/src/IndexBuilder.test.ts index d34a3ef567..aec4e7bced 100644 --- a/plugins/search-backend-node/src/IndexBuilder.test.ts +++ b/plugins/search-backend-node/src/IndexBuilder.test.ts @@ -22,7 +22,7 @@ import { DocumentCollatorFactory, DocumentDecoratorFactory, } from '@backstage/plugin-search-common'; -import { Readable, Transform } from 'stream'; +import { Readable, Transform } from 'node:stream'; import { IndexBuilder } from './IndexBuilder'; import { LunrSearchEngine, SearchEngine } from './index'; import { mockServices } from '@backstage/backend-test-utils'; diff --git a/plugins/search-backend-node/src/IndexBuilder.ts b/plugins/search-backend-node/src/IndexBuilder.ts index 6f29e4de57..1867b52921 100644 --- a/plugins/search-backend-node/src/IndexBuilder.ts +++ b/plugins/search-backend-node/src/IndexBuilder.ts @@ -18,7 +18,7 @@ import { DocumentDecoratorFactory, DocumentTypeInfo, } from '@backstage/plugin-search-common'; -import { pipeline, Transform } from 'stream'; +import { pipeline, Transform } from 'node:stream'; import { Scheduler } from './Scheduler'; import { IndexBuilderOptions, diff --git a/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.test.ts b/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.test.ts index ce640636f9..4999d5b6c6 100644 --- a/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.test.ts +++ b/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { ConfigReader } from '@backstage/config'; -import { Readable } from 'stream'; +import { Readable } from 'node:stream'; import { NewlineDelimitedJsonCollatorFactory } from './NewlineDelimitedJsonCollatorFactory'; import { TestPipeline } from '../test-utils'; import { mockServices } from '@backstage/backend-test-utils'; diff --git a/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.ts b/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.ts index 2771f7bf29..7f4cd25522 100644 --- a/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.ts +++ b/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.ts @@ -17,7 +17,7 @@ import { Config } from '@backstage/config'; import { DocumentCollatorFactory } from '@backstage/plugin-search-common'; import { Permission } from '@backstage/plugin-permission-common'; -import { Readable } from 'stream'; +import { Readable } from 'node:stream'; import { parse as parseNdjson } from 'ndjson'; import { LoggerService, UrlReaderService } from '@backstage/backend-plugin-api'; diff --git a/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts b/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts index 407fa779ab..acd18771c8 100644 --- a/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts +++ b/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts @@ -520,7 +520,7 @@ describe('LunrSearchEngine', () => { fields: { title: `${highlightTags.pre}testTitle${highlightTags.post}`, text: `${highlightTags.pre}testText${highlightTags.post}`, - location: `${highlightTags.pre}test/location${highlightTags.post}`, + location: `${highlightTags.pre}test${highlightTags.post}/location`, }, }, rank: 1, diff --git a/plugins/search-backend-node/src/engines/LunrSearchEngineIndexer.test.ts b/plugins/search-backend-node/src/engines/LunrSearchEngineIndexer.test.ts index ff76f0fa7c..46f0317d33 100644 --- a/plugins/search-backend-node/src/engines/LunrSearchEngineIndexer.test.ts +++ b/plugins/search-backend-node/src/engines/LunrSearchEngineIndexer.test.ts @@ -106,4 +106,31 @@ describe('LunrSearchEngineIndexer', () => { ...[lunr.trimmer, lunr.stopWordFilter, lunr.stemmer], ); }); + + it('should tokenize input on non-alphanumeric characters', () => { + const input = + "Tokenize_test string, entity-name. Doesn't break abc123def - also Unicode support also!三 stjärna عربي"; + const expectedTokens = [ + 'tokenize', + 'test', + 'string', + 'entity', + 'name', + 'doesn', + 't', + 'break', + 'abc123def', + 'also', + 'unicode', + 'support', + 'also', + '三', + 'stjärna', + 'عربي', + ]; + + const tokens = lunr.tokenizer(input).map(token => token.toString()); + + expect(tokens).toEqual(expectedTokens); + }); }); diff --git a/plugins/search-backend-node/src/engines/LunrSearchEngineIndexer.ts b/plugins/search-backend-node/src/engines/LunrSearchEngineIndexer.ts index e723957077..ac7f7b1c0d 100644 --- a/plugins/search-backend-node/src/engines/LunrSearchEngineIndexer.ts +++ b/plugins/search-backend-node/src/engines/LunrSearchEngineIndexer.ts @@ -29,8 +29,8 @@ export class LunrSearchEngineIndexer extends BatchSearchEngineIndexer { constructor() { super({ batchSize: 1000 }); - this.builder = new lunr.Builder(); + this.builder.tokenizer.separator = /[^\p{L}\p{N}]+/u; this.builder.pipeline.add(lunr.trimmer, lunr.stopWordFilter, lunr.stemmer); this.builder.searchPipeline.add(lunr.stemmer); this.builder.metadataWhitelist = ['position']; diff --git a/plugins/search-backend-node/src/indexing/BatchSearchEngineIndexer.ts b/plugins/search-backend-node/src/indexing/BatchSearchEngineIndexer.ts index b41dd524a9..5e4284e205 100644 --- a/plugins/search-backend-node/src/indexing/BatchSearchEngineIndexer.ts +++ b/plugins/search-backend-node/src/indexing/BatchSearchEngineIndexer.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import { assertError } from '@backstage/errors'; +import { toError } from '@backstage/errors'; import { IndexableDocument } from '@backstage/plugin-search-common'; -import { Writable } from 'stream'; +import { Writable } from 'node:stream'; /** * Options for {@link BatchSearchEngineIndexer} @@ -66,8 +66,7 @@ export abstract class BatchSearchEngineIndexer extends Writable { await this.initialize(); done(); } catch (e) { - assertError(e); - done(e); + done(toError(e)); } } @@ -91,8 +90,7 @@ export abstract class BatchSearchEngineIndexer extends Writable { this.currentBatch = []; done(); } catch (e) { - assertError(e); - done(e); + done(toError(e)); } } @@ -110,8 +108,7 @@ export abstract class BatchSearchEngineIndexer extends Writable { await this.finalize(); done(); } catch (e) { - assertError(e); - done(e); + done(toError(e)); } } } diff --git a/plugins/search-backend-node/src/indexing/DecoratorBase.ts b/plugins/search-backend-node/src/indexing/DecoratorBase.ts index 9ed8dfe0a7..75e99bb23c 100644 --- a/plugins/search-backend-node/src/indexing/DecoratorBase.ts +++ b/plugins/search-backend-node/src/indexing/DecoratorBase.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import { assertError } from '@backstage/errors'; +import { toError } from '@backstage/errors'; import { IndexableDocument } from '@backstage/plugin-search-common'; -import { Transform } from 'stream'; +import { Transform } from 'node:stream'; /** * Base class encapsulating simple async transformations. Useful as a base @@ -60,8 +60,7 @@ export abstract class DecoratorBase extends Transform { await this.initialize(); done(); } catch (e) { - assertError(e); - done(e); + done(toError(e)); } } @@ -96,8 +95,7 @@ export abstract class DecoratorBase extends Transform { this.push(decorated); done(); } catch (e) { - assertError(e); - done(e); + done(toError(e)); } } @@ -110,8 +108,7 @@ export abstract class DecoratorBase extends Transform { await this.finalize(); done(); } catch (e) { - assertError(e); - done(e); + done(toError(e)); } } } diff --git a/plugins/search-backend-node/src/test-utils/TestPipeline.ts b/plugins/search-backend-node/src/test-utils/TestPipeline.ts index 33097daae9..d38913f68a 100644 --- a/plugins/search-backend-node/src/test-utils/TestPipeline.ts +++ b/plugins/search-backend-node/src/test-utils/TestPipeline.ts @@ -15,7 +15,7 @@ */ import { IndexableDocument } from '@backstage/plugin-search-common'; -import { pipeline, Readable, Transform, Writable } from 'stream'; +import { pipeline, Readable, Transform, Writable } from 'node:stream'; /** * Object resolved after a test pipeline is executed. diff --git a/plugins/search-backend-node/src/types.ts b/plugins/search-backend-node/src/types.ts index 66ff2e491e..fa00b46b55 100644 --- a/plugins/search-backend-node/src/types.ts +++ b/plugins/search-backend-node/src/types.ts @@ -25,7 +25,7 @@ import { IndexableResultSet, SearchQuery, } from '@backstage/plugin-search-common'; -import { Writable } from 'stream'; +import { Writable } from 'node:stream'; /** * Options required to instantiate the index builder. diff --git a/plugins/search-backend/CHANGELOG.md b/plugins/search-backend/CHANGELOG.md index 151a611a2c..19c045aa5c 100644 --- a/plugins/search-backend/CHANGELOG.md +++ b/plugins/search-backend/CHANGELOG.md @@ -1,5 +1,258 @@ # @backstage/plugin-search-backend +## 2.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/errors@1.3.0 + - @backstage/plugin-search-backend-node@1.4.3 + - @backstage/backend-openapi-utils@0.6.8 + - @backstage/plugin-permission-node@0.10.12 + - @backstage/config@1.3.7 + - @backstage/plugin-permission-common@0.9.8 + - @backstage/plugin-search-common@1.2.23 + +## 2.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/plugin-search-backend-node@1.4.3-next.2 + - @backstage/backend-openapi-utils@0.6.8-next.2 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/config@1.3.7-next.0 + - @backstage/plugin-permission-common@0.9.8-next.0 + - @backstage/plugin-permission-node@0.10.12-next.2 + - @backstage/plugin-search-common@1.2.23-next.0 + +## 2.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-openapi-utils@0.6.8-next.1 + - @backstage/plugin-permission-node@0.10.12-next.1 + - @backstage/plugin-search-backend-node@1.4.3-next.1 + +## 2.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-permission-node@0.10.12-next.0 + - @backstage/plugin-search-backend-node@1.4.3-next.0 + - @backstage/backend-openapi-utils@0.6.8-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/plugin-search-common@1.2.22 + +## 2.1.0 + +### Minor Changes + +- 0fbcf23: Migrated OpenAPI schemas to 3.1. + +### Patch Changes + +- a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-permission-common@0.9.7 + - @backstage/plugin-permission-node@0.10.11 + - @backstage/backend-openapi-utils@0.6.7 + - @backstage/plugin-search-backend-node@1.4.2 + +## 2.1.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/backend-openapi-utils@0.6.7-next.1 + - @backstage/plugin-permission-node@0.10.11-next.1 + - @backstage/plugin-search-backend-node@1.4.2-next.1 + +## 2.1.0-next.1 + +### Minor Changes + +- 0fbcf23: Migrated OpenAPI schemas to 3.1. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.7-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.11-next.0 + - @backstage/plugin-search-backend-node@1.4.2-next.0 + - @backstage/plugin-search-common@1.2.22 + +## 2.0.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/backend-openapi-utils@0.6.7-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.11-next.0 + - @backstage/plugin-search-backend-node@1.4.2-next.0 + - @backstage/plugin-search-common@1.2.22 + +## 2.0.12 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 8148621: Moved `@backstage/backend-defaults` from `dependencies` to `devDependencies`. +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.6 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-search-backend-node@1.4.1 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-permission-common@0.9.6 + - @backstage/plugin-permission-node@0.10.10 + +## 2.0.12-next.1 + +### Patch Changes + +- 8148621: Moved `@backstage/backend-defaults` from `dependencies` to `devDependencies`. +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-permission-node@0.10.10-next.0 + - @backstage/plugin-search-backend-node@1.4.1-next.0 + +## 2.0.11-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 69d880e: Bump to latest zod to ensure it has the latest features +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.6-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-search-backend-node@1.4.1-next.0 + - @backstage/backend-defaults@0.15.1-next.0 + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/plugin-permission-node@0.10.9-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 2.0.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0 + - @backstage/backend-plugin-api@1.6.1 + - @backstage/backend-openapi-utils@0.6.5 + - @backstage/plugin-permission-common@0.9.4 + - @backstage/plugin-permission-node@0.10.8 + +## 2.0.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/plugin-search-backend-node@1.4.0 + +## 2.0.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.5-next.0 + - @backstage/backend-defaults@0.14.1-next.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/plugin-search-common@1.2.21 + +## 2.0.9 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/backend-defaults@0.14.0 + - @backstage/backend-openapi-utils@0.6.4 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + +## 2.0.9-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/backend-openapi-utils@0.6.4-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-backend-node@1.4.0-next.1 + - @backstage/plugin-search-common@1.2.21 + +## 2.0.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0-next.0 + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/backend-openapi-utils@0.6.4-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-common@1.2.21 + +## 2.0.8 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-backend-node@1.3.17 + - @backstage/config@1.3.6 + - @backstage/backend-openapi-utils@0.6.3 + - @backstage/plugin-permission-node@0.10.6 + - @backstage/plugin-search-common@1.2.21 + ## 2.0.8-next.1 ### Patch Changes diff --git a/plugins/search-backend/knip-report.md b/plugins/search-backend/knip-report.md index 406f00e7a8..8815ed9a39 100644 --- a/plugins/search-backend/knip-report.md +++ b/plugins/search-backend/knip-report.md @@ -1,9 +1,8 @@ # Knip report -## Unused dependencies (2) +## Unused dependencies (1) -| Name | Location | Severity | -| :-------------------------------- | :----------- | :------- | -| @backstage/plugin-permission-node | plugins/search-backend/package.json | error | -| yn | plugins/search-backend/package.json | error | +| Name | Location | Severity | +| :-------------------------------- | :---------------- | :------- | +| @backstage/plugin-permission-node | package.json:64:6 | error | diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index 1ce5fe271a..07860afe25 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend", - "version": "2.0.8-next.1", + "version": "2.1.1", "description": "The Backstage backend plugin that provides your backstage app with search", "backstage": { "role": "backend-plugin", @@ -56,7 +56,6 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/backend-defaults": "workspace:^", "@backstage/backend-openapi-utils": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", @@ -67,13 +66,13 @@ "@backstage/plugin-search-common": "workspace:^", "@backstage/types": "workspace:^", "dataloader": "^2.0.0", - "express": "^4.17.1", + "express": "^4.22.0", "lodash": "^4.17.21", "qs": "^6.10.1", - "yn": "^4.0.0", - "zod": "^3.22.4" + "zod": "^3.25.76 || ^4.0.0" }, "devDependencies": { + "@backstage/backend-defaults": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/repo-tools": "workspace:^", diff --git a/plugins/search-backend/src/schema/openapi.yaml b/plugins/search-backend/src/schema/openapi.yaml index d0257a7ce0..7cb9da72c1 100644 --- a/plugins/search-backend/src/schema/openapi.yaml +++ b/plugins/search-backend/src/schema/openapi.yaml @@ -1,4 +1,4 @@ -openapi: 3.0.3 +openapi: 3.1.0 info: title: search version: '1' diff --git a/plugins/search-backend/src/schema/openapi/generated/apis/Api.server.ts b/plugins/search-backend/src/schema/openapi/generated/apis/Api.server.ts index 26652de2cd..d3aa0447a8 100644 --- a/plugins/search-backend/src/schema/openapi/generated/apis/Api.server.ts +++ b/plugins/search-backend/src/schema/openapi/generated/apis/Api.server.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/search-backend/src/schema/openapi/generated/apis/index.ts b/plugins/search-backend/src/schema/openapi/generated/apis/index.ts index 8d81cbaf39..9a0ffed740 100644 --- a/plugins/search-backend/src/schema/openapi/generated/apis/index.ts +++ b/plugins/search-backend/src/schema/openapi/generated/apis/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/search-backend/src/schema/openapi/generated/index.ts b/plugins/search-backend/src/schema/openapi/generated/index.ts index dec4b8804e..58fc52487a 100644 --- a/plugins/search-backend/src/schema/openapi/generated/index.ts +++ b/plugins/search-backend/src/schema/openapi/generated/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/search-backend/src/schema/openapi/generated/models/ErrorError.model.ts b/plugins/search-backend/src/schema/openapi/generated/models/ErrorError.model.ts index 809cd3fa80..d72822b815 100644 --- a/plugins/search-backend/src/schema/openapi/generated/models/ErrorError.model.ts +++ b/plugins/search-backend/src/schema/openapi/generated/models/ErrorError.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/search-backend/src/schema/openapi/generated/models/ErrorRequest.model.ts b/plugins/search-backend/src/schema/openapi/generated/models/ErrorRequest.model.ts index 3eb5e15740..1591911bc9 100644 --- a/plugins/search-backend/src/schema/openapi/generated/models/ErrorRequest.model.ts +++ b/plugins/search-backend/src/schema/openapi/generated/models/ErrorRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/search-backend/src/schema/openapi/generated/models/ErrorResponse.model.ts b/plugins/search-backend/src/schema/openapi/generated/models/ErrorResponse.model.ts index edbcc32df7..1eff0557ed 100644 --- a/plugins/search-backend/src/schema/openapi/generated/models/ErrorResponse.model.ts +++ b/plugins/search-backend/src/schema/openapi/generated/models/ErrorResponse.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/search-backend/src/schema/openapi/generated/models/ModelError.model.ts b/plugins/search-backend/src/schema/openapi/generated/models/ModelError.model.ts index 3914d73531..c4af31b48c 100644 --- a/plugins/search-backend/src/schema/openapi/generated/models/ModelError.model.ts +++ b/plugins/search-backend/src/schema/openapi/generated/models/ModelError.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/search-backend/src/schema/openapi/generated/models/Query200Response.model.ts b/plugins/search-backend/src/schema/openapi/generated/models/Query200Response.model.ts index 190ab8dc59..08abce9f46 100644 --- a/plugins/search-backend/src/schema/openapi/generated/models/Query200Response.model.ts +++ b/plugins/search-backend/src/schema/openapi/generated/models/Query200Response.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/search-backend/src/schema/openapi/generated/models/Query200ResponseResultsInner.model.ts b/plugins/search-backend/src/schema/openapi/generated/models/Query200ResponseResultsInner.model.ts index 7a1c9c155a..f7c9951f50 100644 --- a/plugins/search-backend/src/schema/openapi/generated/models/Query200ResponseResultsInner.model.ts +++ b/plugins/search-backend/src/schema/openapi/generated/models/Query200ResponseResultsInner.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/search-backend/src/schema/openapi/generated/models/Query200ResponseResultsInnerDocument.model.ts b/plugins/search-backend/src/schema/openapi/generated/models/Query200ResponseResultsInnerDocument.model.ts index 004ce76587..99f36fd9a4 100644 --- a/plugins/search-backend/src/schema/openapi/generated/models/Query200ResponseResultsInnerDocument.model.ts +++ b/plugins/search-backend/src/schema/openapi/generated/models/Query200ResponseResultsInnerDocument.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/search-backend/src/schema/openapi/generated/models/index.ts b/plugins/search-backend/src/schema/openapi/generated/models/index.ts index fc9f1408f7..e8ada0c030 100644 --- a/plugins/search-backend/src/schema/openapi/generated/models/index.ts +++ b/plugins/search-backend/src/schema/openapi/generated/models/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/search-backend/src/schema/openapi/generated/router.ts b/plugins/search-backend/src/schema/openapi/generated/router.ts index a2510346ca..17f0f727e9 100644 --- a/plugins/search-backend/src/schema/openapi/generated/router.ts +++ b/plugins/search-backend/src/schema/openapi/generated/router.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import { createValidatedOpenApiRouterFromGeneratedEndpointMap } from '@backstage import { EndpointMap } from './apis'; export const spec = { - openapi: '3.0.3', + openapi: '3.1.0', info: { title: 'search', version: '1', diff --git a/plugins/search-backend/src/schema/openapi/index.ts b/plugins/search-backend/src/schema/openapi/index.ts index 196aad553a..fb49a2b9c0 100644 --- a/plugins/search-backend/src/schema/openapi/index.ts +++ b/plugins/search-backend/src/schema/openapi/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/search-backend/src/service/AuthorizedSearchEngine.ts b/plugins/search-backend/src/service/AuthorizedSearchEngine.ts index ed65e1b684..df9cbd184d 100644 --- a/plugins/search-backend/src/service/AuthorizedSearchEngine.ts +++ b/plugins/search-backend/src/service/AuthorizedSearchEngine.ts @@ -38,7 +38,7 @@ import { } from '@backstage/plugin-search-backend-node'; import { Config } from '@backstage/config'; import { InputError } from '@backstage/errors'; -import { Writable } from 'stream'; +import { Writable } from 'node:stream'; import { AuthService, PermissionsService } from '@backstage/backend-plugin-api'; export function decodePageCursor(pageCursor?: string): { page: number } { diff --git a/plugins/search-backend/src/service/router.test.ts b/plugins/search-backend/src/service/router.test.ts index 623b78016a..92083ba0a4 100644 --- a/plugins/search-backend/src/service/router.test.ts +++ b/plugins/search-backend/src/service/router.test.ts @@ -24,7 +24,7 @@ import express from 'express'; import request from 'supertest'; import { createRouter } from './router'; import { wrapServer } from '@backstage/backend-openapi-utils/testUtils'; -import { Server } from 'http'; +import { Server } from 'node:http'; import { mockCredentials, mockErrorHandler, diff --git a/plugins/search-backend/src/service/router.ts b/plugins/search-backend/src/service/router.ts index eedd84105a..2be6612c8b 100644 --- a/plugins/search-backend/src/service/router.ts +++ b/plugins/search-backend/src/service/router.ts @@ -15,7 +15,7 @@ */ import express from 'express'; -import { z } from 'zod'; +import { z } from 'zod/v3'; import { InputError } from '@backstage/errors'; import { Config } from '@backstage/config'; import { JsonObject, JsonValue } from '@backstage/types'; diff --git a/plugins/search-common/CHANGELOG.md b/plugins/search-common/CHANGELOG.md index c9adfbde48..2bab4466cf 100644 --- a/plugins/search-common/CHANGELOG.md +++ b/plugins/search-common/CHANGELOG.md @@ -1,5 +1,43 @@ # @backstage/plugin-search-common +## 1.2.23 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.8 + +## 1.2.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.8-next.0 + +## 1.2.22 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-permission-common@0.9.6 + +## 1.2.22-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-permission-common@0.9.5-next.0 + - @backstage/types@1.2.2 + +## 1.2.21 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.3 + ## 1.2.21-next.0 ### Patch Changes diff --git a/plugins/search-common/package.json b/plugins/search-common/package.json index 4cb8eee4f2..c07a6ed78e 100644 --- a/plugins/search-common/package.json +++ b/plugins/search-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-common", - "version": "1.2.21-next.0", + "version": "1.2.23", "description": "Common functionalities for Search, to be shared between various search-enabled plugins", "backstage": { "role": "common-library", diff --git a/plugins/search-common/report.api.md b/plugins/search-common/report.api.md index dac1ae1b25..767edaee4e 100644 --- a/plugins/search-common/report.api.md +++ b/plugins/search-common/report.api.md @@ -5,9 +5,9 @@ ```ts import { JsonObject } from '@backstage/types'; import { Permission } from '@backstage/plugin-permission-common'; -import { Readable } from 'stream'; -import { Transform } from 'stream'; -import { Writable } from 'stream'; +import { Readable } from 'node:stream'; +import { Transform } from 'node:stream'; +import { Writable } from 'node:stream'; // @public export interface DocumentCollatorFactory { diff --git a/plugins/search-common/src/deprecated.ts b/plugins/search-common/src/deprecated.ts index 8d76d3a22d..471846dd5e 100644 --- a/plugins/search-common/src/deprecated.ts +++ b/plugins/search-common/src/deprecated.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Writable } from 'stream'; +import { Writable } from 'node:stream'; import { SearchQuery, IndexableResultSet } from './types'; /** diff --git a/plugins/search-common/src/types.ts b/plugins/search-common/src/types.ts index 9d1f9aa887..6339ab3d53 100644 --- a/plugins/search-common/src/types.ts +++ b/plugins/search-common/src/types.ts @@ -16,7 +16,7 @@ import { Permission } from '@backstage/plugin-permission-common'; import { JsonObject } from '@backstage/types'; -import { Readable, Transform } from 'stream'; +import { Readable, Transform } from 'node:stream'; /** * @public diff --git a/plugins/search-react/CHANGELOG.md b/plugins/search-react/CHANGELOG.md index 6c4090276c..394958a79b 100644 --- a/plugins/search-react/CHANGELOG.md +++ b/plugins/search-react/CHANGELOG.md @@ -1,5 +1,187 @@ # @backstage/plugin-search-react +## 1.11.1 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.7.3 + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/core-components@0.18.9 + - @backstage/core-plugin-api@1.12.5 + - @backstage/plugin-search-common@1.2.23 + +## 1.11.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.7.3-next.0 + - @backstage/core-components@0.18.9-next.1 + - @backstage/core-plugin-api@1.12.5-next.2 + - @backstage/frontend-plugin-api@0.16.0-next.2 + - @backstage/plugin-search-common@1.2.23-next.0 + +## 1.11.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + +## 1.11.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.9-next.0 + - @backstage/frontend-plugin-api@0.15.2-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + - @backstage/theme@0.7.2 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-search-common@1.2.22 + +## 1.11.0 + +### Minor Changes + +- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point. + +### Patch Changes + +- d5eb954: Fixes the search component not registering the first search on navigate to the search page. +- Updated dependencies + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + +## 1.10.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/core-components@0.18.8-next.1 + +## 1.10.5-next.0 + +### Patch Changes + +- d5eb954: Fixes the search component not registering the first search on navigate to the search page. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/theme@0.7.2 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-search-common@1.2.22 + +## 1.10.3 + +### Patch Changes + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/core-components@0.18.7 + - @backstage/plugin-search-common@1.2.22 + - @backstage/theme@0.7.2 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/version-bridge@1.0.12 + +## 1.10.3-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/version-bridge@1.0.12-next.0 + - @backstage/theme@0.7.2-next.1 + +## 1.10.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.7.2-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/core-components@0.18.7-next.1 + +## 1.10.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.6-next.0 + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/theme@0.7.1 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## 1.10.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + +## 1.10.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + +## 1.10.1 + +### Patch Changes + +- 8947a4e: Skip the very first empty search when going to the landing page +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + +## 1.10.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.21 + +## 1.10.0 + +### Minor Changes + +- a521911: Add support for customizable icons in `SearchResultListItemBlueprint` and related components + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-search-common@1.2.21 + ## 1.10.0-next.1 ### Minor Changes diff --git a/plugins/search-react/knip-report.md b/plugins/search-react/knip-report.md index 74ef5ba5c2..3a2000e860 100644 --- a/plugins/search-react/knip-report.md +++ b/plugins/search-react/knip-report.md @@ -2,7 +2,7 @@ ## Unused devDependencies (1) -| Name | Location | Severity | -| :-------------------------- | :----------- | :------- | -| @backstage/frontend-app-api | plugins/search-react/package.json | error | +| Name | Location | Severity | +| :-------------------------- | :---------------- | :------- | +| @backstage/frontend-app-api | package.json:78:6 | error | diff --git a/plugins/search-react/package.json b/plugins/search-react/package.json index 5c11191dcc..79184a73dd 100644 --- a/plugins/search-react/package.json +++ b/plugins/search-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-react", - "version": "1.10.0-next.1", + "version": "1.11.1", "backstage": { "role": "web-library", "pluginId": "search", @@ -69,7 +69,8 @@ "lodash": "^4.17.21", "qs": "^6.9.4", "react-use": "^17.3.2", - "uuid": "^11.0.2" + "uuid": "^11.0.2", + "zod": "^4.0.0" }, "devDependencies": { "@backstage/cli": "workspace:^", @@ -84,13 +85,13 @@ "@types/react": "^18.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/plugins/search-react/report-alpha.api.md b/plugins/search-react/report-alpha.api.md index 3ddcde9076..c8a51f0155 100644 --- a/plugins/search-react/report-alpha.api.md +++ b/plugins/search-react/report-alpha.api.md @@ -10,7 +10,7 @@ import { JSX as JSX_2 } from 'react'; import { ListItemProps } from '@material-ui/core/ListItem'; import { SearchDocument } from '@backstage/plugin-search-common'; import { SearchResult } from '@backstage/plugin-search-common'; -import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) export type BaseSearchResultListItemProps = T & { @@ -96,7 +96,7 @@ export interface SearchFilterResultTypeBlueprintParams { value: string; } -// @alpha (undocumented) +// @alpha @deprecated (undocumented) export const searchReactTranslationRef: TranslationRef< 'search-react', { diff --git a/plugins/search-react/report.api.md b/plugins/search-react/report.api.md index 96b45776bc..21fc5e2e94 100644 --- a/plugins/search-react/report.api.md +++ b/plugins/search-react/report.api.md @@ -3,7 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { ApiRef } from '@backstage/core-plugin-api'; +import { ApiRef } from '@backstage/frontend-plugin-api'; import { AsyncState } from 'react-use/esm/useAsync'; import { AutocompleteProps } from '@material-ui/lab/Autocomplete'; import { Dispatch } from 'react'; @@ -28,6 +28,7 @@ import { SearchResult as SearchResult_2 } from '@backstage/plugin-search-common' import { SearchResultSet } from '@backstage/plugin-search-common'; import { SetStateAction } from 'react'; import { TextFieldProps } from '@material-ui/core/TextField'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; import { TypographyProps } from '@material-ui/core/Typography'; // @public (undocumented) @@ -296,6 +297,24 @@ export type SearchPaginationProps = Omit< | 'hasNextPage' >; +// @public (undocumented) +export const searchReactTranslationRef: TranslationRef< + 'search-react', + { + readonly 'searchBar.title': 'Search'; + readonly 'searchBar.placeholder': 'Search in {{org}}'; + readonly 'searchBar.clearButtonTitle': 'Clear'; + readonly 'searchFilter.allOptionTitle': 'All'; + readonly 'searchPagination.limitLabel': 'Results per page:'; + readonly 'searchPagination.limitText': 'of {{num}}'; + readonly noResultsDescription: 'Sorry, no results were found'; + readonly 'searchResultGroup.linkTitle': 'See All'; + readonly 'searchResultGroup.addFilterButtonTitle': 'Add filter'; + readonly 'searchResultPager.next': 'Next'; + readonly 'searchResultPager.previous': 'Previous'; + } +>; + // @public export const SearchResult: (props: SearchResultProps) => JSX_2.Element; diff --git a/plugins/search-react/src/alpha/blueprints/SearchFilterBlueprint.test.tsx b/plugins/search-react/src/alpha/blueprints/SearchFilterBlueprint.test.tsx index adfceeec47..2d48454003 100644 --- a/plugins/search-react/src/alpha/blueprints/SearchFilterBlueprint.test.tsx +++ b/plugins/search-react/src/alpha/blueprints/SearchFilterBlueprint.test.tsx @@ -45,6 +45,7 @@ describe('SearchFilterBlueprint', () => { "configSchema": undefined, "disabled": false, "factory": [Function], + "if": undefined, "inputs": {}, "kind": "search-filter", "name": "test", diff --git a/plugins/search-react/src/alpha/blueprints/SearchFilterResultTypeBlueprint.test.tsx b/plugins/search-react/src/alpha/blueprints/SearchFilterResultTypeBlueprint.test.tsx index d9f8e97080..d324956068 100644 --- a/plugins/search-react/src/alpha/blueprints/SearchFilterResultTypeBlueprint.test.tsx +++ b/plugins/search-react/src/alpha/blueprints/SearchFilterResultTypeBlueprint.test.tsx @@ -47,6 +47,7 @@ describe('SearchFilterResultTypeBlueprint', () => { "configSchema": undefined, "disabled": false, "factory": [Function], + "if": undefined, "inputs": {}, "kind": "search-filter-result-type", "name": "test", diff --git a/plugins/search-react/src/alpha/blueprints/SearchResultListItemBlueprint.test.tsx b/plugins/search-react/src/alpha/blueprints/SearchResultListItemBlueprint.test.tsx index 112c0a8470..78cfaa12bd 100644 --- a/plugins/search-react/src/alpha/blueprints/SearchResultListItemBlueprint.test.tsx +++ b/plugins/search-react/src/alpha/blueprints/SearchResultListItemBlueprint.test.tsx @@ -46,20 +46,11 @@ describe('SearchResultListItemBlueprint', () => { }, "configSchema": { "parse": [Function], - "schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "noTrack": { - "default": false, - "type": "boolean", - }, - }, - "type": "object", - }, + "schema": [Function], }, "disabled": false, "factory": [Function], + "if": undefined, "inputs": {}, "kind": "search-result-list-item", "name": "test", diff --git a/plugins/search-react/src/alpha/blueprints/SearchResultListItemBlueprint.tsx b/plugins/search-react/src/alpha/blueprints/SearchResultListItemBlueprint.tsx index b442246199..4e74730128 100644 --- a/plugins/search-react/src/alpha/blueprints/SearchResultListItemBlueprint.tsx +++ b/plugins/search-react/src/alpha/blueprints/SearchResultListItemBlueprint.tsx @@ -15,6 +15,7 @@ */ import { lazy, JSX } from 'react'; +import { z } from 'zod/v4'; import { createExtensionBlueprint, ExtensionBoundary, @@ -59,10 +60,8 @@ export const SearchResultListItemBlueprint = createExtensionBlueprint({ id: 'page:search', input: 'items', }, - config: { - schema: { - noTrack: z => z.boolean().default(false), - }, + configSchema: { + noTrack: z.boolean().default(false), }, output: [searchResultListItemDataRef], dataRefs: { diff --git a/plugins/search-react/src/alpha/index.ts b/plugins/search-react/src/alpha/index.ts index 7c44f59d56..528b49d7dd 100644 --- a/plugins/search-react/src/alpha/index.ts +++ b/plugins/search-react/src/alpha/index.ts @@ -14,4 +14,10 @@ * limitations under the License. */ export * from './blueprints'; -export { searchReactTranslationRef } from '../translation'; +import { searchReactTranslationRef as _searchReactTranslationRef } from '../translation'; + +/** + * @alpha + * @deprecated Import from `@backstage/plugin-search-react` instead. + */ +export const searchReactTranslationRef = _searchReactTranslationRef; diff --git a/plugins/search-react/src/components/DefaultResultListItem/DefaultResultListItem.stories.tsx b/plugins/search-react/src/components/DefaultResultListItem/DefaultResultListItem.stories.tsx index 9ea575139d..616dc3a4fe 100644 --- a/plugins/search-react/src/components/DefaultResultListItem/DefaultResultListItem.stories.tsx +++ b/plugins/search-react/src/components/DefaultResultListItem/DefaultResultListItem.stories.tsx @@ -38,6 +38,7 @@ export default { ), ], + tags: ['!manifest'], }; const mockSearchResult = { diff --git a/plugins/search-react/src/components/SearchAutocomplete/SearchAutocomplete.stories.tsx b/plugins/search-react/src/components/SearchAutocomplete/SearchAutocomplete.stories.tsx index da504497e0..961acfac03 100644 --- a/plugins/search-react/src/components/SearchAutocomplete/SearchAutocomplete.stories.tsx +++ b/plugins/search-react/src/components/SearchAutocomplete/SearchAutocomplete.stories.tsx @@ -44,6 +44,7 @@ export default { , ), ], + tags: ['!manifest'], }; export const Default = () => { diff --git a/plugins/search-react/src/components/SearchAutocomplete/SearchAutocompleteDefaultOption.stories.tsx b/plugins/search-react/src/components/SearchAutocomplete/SearchAutocompleteDefaultOption.stories.tsx index ca22974cc7..da5065752e 100644 --- a/plugins/search-react/src/components/SearchAutocomplete/SearchAutocompleteDefaultOption.stories.tsx +++ b/plugins/search-react/src/components/SearchAutocomplete/SearchAutocompleteDefaultOption.stories.tsx @@ -45,6 +45,7 @@ export default { ), ], + tags: ['!manifest'], }; export const Default = () => ( diff --git a/plugins/search-react/src/components/SearchBar/SearchBar.stories.tsx b/plugins/search-react/src/components/SearchBar/SearchBar.stories.tsx index e2cbfc6bc8..8b96811666 100644 --- a/plugins/search-react/src/components/SearchBar/SearchBar.stories.tsx +++ b/plugins/search-react/src/components/SearchBar/SearchBar.stories.tsx @@ -45,6 +45,7 @@ export default { , ), ], + tags: ['!manifest'], }; export const Default = () => { diff --git a/plugins/search-react/src/components/SearchFilter/SearchFilter.stories.tsx b/plugins/search-react/src/components/SearchFilter/SearchFilter.stories.tsx index 4d66bd073f..de9f137ebd 100644 --- a/plugins/search-react/src/components/SearchFilter/SearchFilter.stories.tsx +++ b/plugins/search-react/src/components/SearchFilter/SearchFilter.stories.tsx @@ -40,6 +40,7 @@ export default { ), ], + tags: ['!manifest'], }; export const CheckBoxFilter = () => { diff --git a/plugins/search-react/src/components/SearchPagination/SearchPagination.stories.tsx b/plugins/search-react/src/components/SearchPagination/SearchPagination.stories.tsx index a8318f8789..8618639462 100644 --- a/plugins/search-react/src/components/SearchPagination/SearchPagination.stories.tsx +++ b/plugins/search-react/src/components/SearchPagination/SearchPagination.stories.tsx @@ -40,6 +40,7 @@ export default { ), ], + tags: ['!manifest'], }; export const Default = () => { diff --git a/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx b/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx index e482dc3a75..d9cd564ae2 100644 --- a/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx +++ b/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx @@ -69,7 +69,7 @@ describe('SearchPagination', () => { expect(screen.getByText('Results per page:')).toBeInTheDocument(); expect(screen.getByText('25')).toBeInTheDocument(); expect(screen.getByText('1-25')).toBeInTheDocument(); - expect(screen.getByLabelText('Next page')).toBeEnabled(); + expect(screen.getByLabelText('Next page')).toBeDisabled(); expect(screen.getByLabelText('Previous page')).toBeDisabled(); }); @@ -176,6 +176,12 @@ describe('SearchPagination', () => { }); it('Set page limit in the context', async () => { + const initialState = { + term: 'a', + types: [], + filters: {}, + }; + await renderInTestApp( { [configApiRef, configApiMock], ]} > - + , @@ -205,7 +211,7 @@ describe('SearchPagination', () => { it('Set page cursor in the context', async () => { const initialState = { - term: '', + term: 'a', types: [], filters: {}, pageCursor: 'MQ==', // page: 1 @@ -253,7 +259,7 @@ describe('SearchPagination', () => { it('Resets page cursor when page limit changes', async () => { const initialState = { - term: '', + term: 'a', types: [], filters: {}, pageCursor: 'Mg==', // page: 2 @@ -280,9 +286,7 @@ describe('SearchPagination', () => { pageCursor: undefined, pageLimit: 10, }), - { - signal: expect.any(AbortSignal), - }, + { signal: expect.any(AbortSignal) }, ); }); }); diff --git a/plugins/search-react/src/components/SearchResult/SearchResult.stories.tsx b/plugins/search-react/src/components/SearchResult/SearchResult.stories.tsx index 9bf39a5069..3dfc4c18ef 100644 --- a/plugins/search-react/src/components/SearchResult/SearchResult.stories.tsx +++ b/plugins/search-react/src/components/SearchResult/SearchResult.stories.tsx @@ -80,6 +80,7 @@ export default { , ), ], + tags: ['!manifest'], }; const CustomResultListItem = (props: { result: SearchDocument }) => { diff --git a/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.stories.tsx b/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.stories.tsx index 37c6a4b8fb..16cfbdfccc 100644 --- a/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.stories.tsx +++ b/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.stories.tsx @@ -82,6 +82,7 @@ export default { { mountedRoutes: { '/': routeRef } }, ), ], + tags: ['!manifest'], }; export const Default = () => { diff --git a/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.test.tsx b/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.test.tsx index cb274be0a0..0f46f15ed4 100644 --- a/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.test.tsx +++ b/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.test.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { screen, waitFor } from '@testing-library/react'; +import { screen, waitFor, fireEvent } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import MenuItem from '@material-ui/core/MenuItem'; @@ -66,7 +66,7 @@ describe('SearchResultGroup', () => { }); it('Renders without exploding', async () => { - query.mockResolvedValueOnce({ + query.mockResolvedValue({ results, }); @@ -96,7 +96,7 @@ describe('SearchResultGroup', () => { }); it('Renders search results from context', async () => { - query.mockResolvedValueOnce({ + query.mockResolvedValue({ results, }); @@ -107,7 +107,9 @@ describe('SearchResultGroup', () => { [analyticsApiRef, analyticsApiMock], ]} > - + } title="Documentation" @@ -128,7 +130,7 @@ describe('SearchResultGroup', () => { }); it('Renders search results using extensions', async () => { - query.mockResolvedValueOnce({ + query.mockResolvedValue({ results, }); @@ -166,7 +168,7 @@ describe('SearchResultGroup', () => { }); it('Defines a default link', async () => { - query.mockResolvedValueOnce({ + query.mockResolvedValue({ results, }); @@ -190,7 +192,7 @@ describe('SearchResultGroup', () => { }); it('Defines a default render result item', async () => { - query.mockResolvedValueOnce({ + query.mockResolvedValue({ results, }); @@ -221,6 +223,10 @@ describe('SearchResultGroup', () => { }); it('Could be customized with no results text', async () => { + query.mockResolvedValue({ + results: [], + }); + await renderInTestApp( { }); it('Could be customized with filters', async () => { - query.mockResolvedValueOnce({ + query.mockResolvedValue({ results, }); @@ -272,7 +278,7 @@ describe('SearchResultGroup', () => { }); it('Could have a text search filter field', async () => { - query.mockResolvedValueOnce({ + query.mockResolvedValue({ results, }); @@ -312,10 +318,10 @@ describe('SearchResultGroup', () => { await userEvent.click(screen.getByText('owner')); - await userEvent.type( - screen.getByRole('textbox'), - '{backspace}{backspace}{backspace}{backspace}techdocs-core', - ); + // Use fireEvent.blur for contentEditable elements since userEvent.type with + // backspace doesn't work properly in jsdom (jsdom limitation, not a bug) + const textbox = screen.getByRole('textbox'); + fireEvent.blur(textbox, { target: { textContent: 'techdocs-core' } }); await waitFor(() => { expect(screen.getByText('techdocs-core')).toBeInTheDocument(); @@ -323,7 +329,7 @@ describe('SearchResultGroup', () => { }); it('Could have a select search filter field', async () => { - query.mockResolvedValueOnce({ + query.mockResolvedValue({ results, }); @@ -376,7 +382,7 @@ describe('SearchResultGroup', () => { }); it('Shows a progress bar when loading results', async () => { - query.mockReturnValueOnce(new Promise(() => {})); + query.mockReturnValue(new Promise(() => {})); await renderInTestApp( { }); it('Does not render result group if no results returned and disableRenderingWithNoResults prop is provided', async () => { - query.mockResolvedValueOnce({ results: [] }); + query.mockResolvedValue({ results: [] }); await renderInTestApp( { }); it('Should render custom component when no results returned', async () => { - query.mockResolvedValueOnce({ results: [] }); + query.mockResolvedValue({ results: [] }); await renderInTestApp( { }); it('Shows an error panel when results rendering fails', async () => { - query.mockRejectedValueOnce(new Error()); + query.mockRejectedValue(new Error()); await renderInTestApp( { diff --git a/plugins/search-react/src/context/SearchContext.tsx b/plugins/search-react/src/context/SearchContext.tsx index ba06010789..0d18b888e5 100644 --- a/plugins/search-react/src/context/SearchContext.tsx +++ b/plugins/search-react/src/context/SearchContext.tsx @@ -130,12 +130,23 @@ const useSearchContextValue = ( const [pageCursor, setPageCursor] = useState( initialValue.pageCursor, ); + const isFirstEmptyMount = useRef(true); const prevTerm = usePrevious(term); const prevFilters = usePrevious(filters); const abortControllerRef = useRef(null); - const result = useAsync(async () => { + const result = useAsync(async (): Promise => { + if (isFirstEmptyMount.current) { + isFirstEmptyMount.current = false; + if (!term && !types.length && !Object.keys(filters).length) { + return { + results: [], + numberOfResults: 0, + }; + } + } + // Here we cancel the previous request before making a new one if (abortControllerRef.current) { abortControllerRef.current.abort(); diff --git a/plugins/search-react/src/index.ts b/plugins/search-react/src/index.ts index 8b234e3c05..e376c67185 100644 --- a/plugins/search-react/src/index.ts +++ b/plugins/search-react/src/index.ts @@ -34,3 +34,4 @@ export type { SearchContextState, SearchContextValue, } from './context'; +export { searchReactTranslationRef } from './translation'; diff --git a/plugins/search-react/src/translation.ts b/plugins/search-react/src/translation.ts index e4c394d952..6105f74651 100644 --- a/plugins/search-react/src/translation.ts +++ b/plugins/search-react/src/translation.ts @@ -17,7 +17,7 @@ import { createTranslationRef } from '@backstage/core-plugin-api/alpha'; /** - * @alpha + * @public */ export const searchReactTranslationRef = createTranslationRef({ id: 'search-react', diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index 934d178b77..e3bc53cbe0 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,277 @@ # @backstage/plugin-search +## 1.7.1 + +### Patch Changes + +- 34aebcc: Fixed the `SearchModal` leaving the page in a broken state by not restoring body overflow and aria-hidden attributes when closing. +- Updated dependencies + - @backstage/ui@0.14.0 + - @backstage/errors@1.3.0 + - @backstage/plugin-catalog-react@2.1.2 + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/core-components@0.18.9 + - @backstage/core-plugin-api@1.12.5 + - @backstage/plugin-search-common@1.2.23 + - @backstage/plugin-search-react@1.11.1 + +## 1.7.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.2 + - @backstage/errors@1.3.0-next.0 + - @backstage/core-components@0.18.9-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.2 + - @backstage/core-plugin-api@1.12.5-next.2 + - @backstage/frontend-plugin-api@0.16.0-next.2 + - @backstage/plugin-search-react@1.11.1-next.2 + - @backstage/plugin-search-common@1.2.23-next.0 + +## 1.7.1-next.1 + +### Patch Changes + +- 34aebcc: Fixed the `SearchModal` leaving the page in a broken state by not restoring body overflow and aria-hidden attributes when closing. +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-search-react@1.11.1-next.1 + +## 1.7.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.0 + - @backstage/plugin-catalog-react@2.1.1-next.0 + - @backstage/core-components@0.18.9-next.0 + - @backstage/frontend-plugin-api@0.15.2-next.0 + - @backstage/plugin-search-react@1.11.1-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-search-common@1.2.22 + +## 1.7.0 + +### Minor Changes + +- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point. + +### Patch Changes + +- d5eb954: Fixes the search component not registering the first search on navigate to the search page. +- aa29b50: New frontend system pages now use the default plugin header together with `HeaderPage` instead of the legacy core page header pattern. +- 3f36ce1: Updated alpha plugin icons to follow the new frontend icon sizing rules when rendered in plugin and navigation surfaces. +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/ui@0.13.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/plugin-search-react@1.11.0 + +## 1.6.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.2 + - @backstage/core-components@0.18.8-next.1 + - @backstage/plugin-search-react@1.10.5-next.1 + +## 1.6.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-search-react@1.10.5-next.0 + +## 1.6.2-next.0 + +### Patch Changes + +- d5eb954: Fixes the search component not registering the first search on navigate to the search page. +- Updated dependencies + - @backstage/plugin-search-react@1.10.5-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-search-common@1.2.22 + +## 1.6.0 + +### Minor Changes + +- feef8d9: Added support for configuring the default search type in the search page via the `search.defaultType` option in `app-config.yaml`. This applies to both the legacy and new frontend systems. If not set, the default is empty, which means searching for "all" types. + +### Patch Changes + +- 018ca87: Added `title` and `icon` to the plugin definition for the new frontend system. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/core-components@0.18.7 + - @backstage/plugin-search-common@1.2.22 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-search-react@1.10.3 + +## 1.6.0-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/version-bridge@1.0.12-next.0 + - @backstage/plugin-search-react@1.10.3-next.2 + +## 1.6.0-next.1 + +### Minor Changes + +- feef8d9: Added support for configuring the default search type in the search page via the `search.defaultType` option in `app-config.yaml`. This applies to both the legacy and new frontend systems. If not set, the default is empty, which means searching for "all" types. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/plugin-search-react@1.10.3-next.1 + - @backstage/core-components@0.18.7-next.1 + +## 1.5.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/plugin-search-react@1.10.3-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## 1.5.3 + +### Patch Changes + +- 5c49a00: Update for the `qs` library bump: the old array limit setting has changed to be more strict; you can no longer just give a zero to mean unlimited. So we choose an arbitrary high value, to at least go higher than the default 20. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + - @backstage/plugin-search-react@1.10.2 + - @backstage/plugin-catalog-react@1.21.5 + +## 1.5.2-next.1 + +### Patch Changes + +- 5c49a00: Update for the `qs` library bump: the old array limit setting has changed to be more strict; you can no longer just give a zero to mean unlimited. So we choose an arbitrary high value, to at least go higher than the default 20. +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.1 + - @backstage/plugin-search-react@1.10.2-next.0 + +## 1.5.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.5-next.0 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/plugin-search-react@1.10.1 + +## 1.5.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-search-react@1.10.1 + - @backstage/core-plugin-api@1.12.1 + +## 1.5.1-next.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-search-react@1.10.1-next.0 + +## 1.5.1-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/plugin-search-react@1.10.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.21 + +## 1.5.0 + +### Minor Changes + +- a521911: Add support for customizable icons in `SearchResultListItemBlueprint` and related components + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-search-react@1.10.0 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-search-common@1.2.21 + ## 1.5.0-next.1 ### Minor Changes diff --git a/plugins/search/README.md b/plugins/search/README.md index beab698b2e..d0b1aa78e3 100644 --- a/plugins/search/README.md +++ b/plugins/search/README.md @@ -15,6 +15,7 @@ Configure the search query values via `app-config.yaml` to define how it behaves ```yaml # app-config.yaml search: + defaultType: 'software-catalog' query: pageLimit: 50 ``` diff --git a/plugins/search/config.d.ts b/plugins/search/config.d.ts index d9a5ce4a1b..0c35082e58 100644 --- a/plugins/search/config.d.ts +++ b/plugins/search/config.d.ts @@ -17,6 +17,11 @@ export interface Config { /** Configuration options for the search plugin */ search?: { + /** + * The default search type to be used when no specific type is selected. + * @visibility frontend + */ + defaultType?: string; /** * An object representing the default search query configuration. * By configuring and modifying the values of this object, diff --git a/plugins/search/knip-report.md b/plugins/search/knip-report.md index 2661c35327..d161ec5c9a 100644 --- a/plugins/search/knip-report.md +++ b/plugins/search/knip-report.md @@ -1,2 +1,8 @@ # Knip report +## Unused dependencies (1) + +| Name | Location | Severity | +| :------------ | :---------------- | :------- | +| @backstage/ui | package.json:69:6 | error | + diff --git a/plugins/search/package.json b/plugins/search/package.json index e8a8a2b567..67a5e98ddc 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search", - "version": "1.5.0-next.1", + "version": "1.7.1", "description": "The Backstage plugin that provides your backstage app with search", "backstage": { "role": "frontend-plugin", @@ -58,7 +58,6 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", @@ -67,11 +66,13 @@ "@backstage/plugin-search-common": "workspace:^", "@backstage/plugin-search-react": "workspace:^", "@backstage/types": "workspace:^", + "@backstage/ui": "workspace:^", "@backstage/version-bridge": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "qs": "^6.9.4", - "react-use": "^17.2.4" + "react-use": "^17.2.4", + "zod": "^4.0.0" }, "devDependencies": { "@backstage/cli": "workspace:^", @@ -86,13 +87,13 @@ "history": "^5.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/plugins/search/report-alpha.api.md b/plugins/search/report-alpha.api.md index 5ac036ca68..9639741350 100644 --- a/plugins/search/report-alpha.api.md +++ b/plugins/search/report-alpha.api.md @@ -3,27 +3,29 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { AnyApiFactory } from '@backstage/core-plugin-api'; +import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; -import { ApiFactory } from '@backstage/core-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; -import { IconComponent } from '@backstage/core-plugin-api'; +import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; import { RouteRef } from '@backstage/frontend-plugin-api'; +import { RouteRef as RouteRef_2 } from '@backstage/core-plugin-api'; import { SearchFilterExtensionComponent } from '@backstage/plugin-search-react/alpha'; import { SearchResultItemExtensionComponent } from '@backstage/plugin-search-react/alpha'; import { SearchResultItemExtensionPredicate } from '@backstage/plugin-search-react/alpha'; -import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) const _default: OverridableFrontendPlugin< { - root: RouteRef; + root: RouteRef_2; }, {}, { @@ -45,8 +47,12 @@ const _default: OverridableFrontendPlugin< 'nav-item:search': OverridableExtensionDefinition<{ kind: 'nav-item'; name: undefined; - config: {}; - configInput: {}; + config: { + title: string | undefined; + }; + configInput: { + title?: string | undefined; + }; output: ExtensionDataRef< { title: string; @@ -66,25 +72,69 @@ const _default: OverridableFrontendPlugin< 'page:search': OverridableExtensionDefinition<{ config: { noTrack: boolean; - } & { path: string | undefined; + title: string | undefined; }; configInput: { noTrack?: boolean | undefined; - } & { path?: string | undefined; + title?: string | undefined; }; output: | ExtensionDataRef - | ExtensionDataRef | ExtensionDataRef< RouteRef, 'core.routing.ref', { optional: true; } + > + | ExtensionDataRef + | ExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } >; inputs: { + pages: ExtensionInput< + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >, + { + singleton: false; + optional: false; + internal: false; + } + >; items: ExtensionInput< ConfigurableExtensionDataRef< { @@ -98,6 +148,7 @@ const _default: OverridableFrontendPlugin< { singleton: false; optional: false; + internal: false; } >; resultTypes: ExtensionInput< @@ -113,6 +164,7 @@ const _default: OverridableFrontendPlugin< { singleton: false; optional: false; + internal: false; } >; searchFilters: ExtensionInput< @@ -126,16 +178,19 @@ const _default: OverridableFrontendPlugin< { singleton: false; optional: false; + internal: false; } >; }; kind: 'page'; name: undefined; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; - loader: () => Promise; + title?: string; + icon?: IconElement; + loader?: () => Promise; routeRef?: RouteRef; + noHeader?: boolean; }; }>; } @@ -163,8 +218,12 @@ export const searchApi: OverridableExtensionDefinition<{ export const searchNavItem: OverridableExtensionDefinition<{ kind: 'nav-item'; name: undefined; - config: {}; - configInput: {}; + config: { + title: string | undefined; + }; + configInput: { + title?: string | undefined; + }; output: ExtensionDataRef< { title: string; @@ -186,25 +245,69 @@ export const searchNavItem: OverridableExtensionDefinition<{ export const searchPage: OverridableExtensionDefinition<{ config: { noTrack: boolean; - } & { path: string | undefined; + title: string | undefined; }; configInput: { noTrack?: boolean | undefined; - } & { path?: string | undefined; + title?: string | undefined; }; output: | ExtensionDataRef - | ExtensionDataRef | ExtensionDataRef< RouteRef, 'core.routing.ref', { optional: true; } + > + | ExtensionDataRef + | ExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } >; inputs: { + pages: ExtensionInput< + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >, + { + singleton: false; + optional: false; + internal: false; + } + >; items: ExtensionInput< ConfigurableExtensionDataRef< { @@ -218,6 +321,7 @@ export const searchPage: OverridableExtensionDefinition<{ { singleton: false; optional: false; + internal: false; } >; resultTypes: ExtensionInput< @@ -233,6 +337,7 @@ export const searchPage: OverridableExtensionDefinition<{ { singleton: false; optional: false; + internal: false; } >; searchFilters: ExtensionInput< @@ -246,20 +351,23 @@ export const searchPage: OverridableExtensionDefinition<{ { singleton: false; optional: false; + internal: false; } >; }; kind: 'page'; name: undefined; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; - loader: () => Promise; + title?: string; + icon?: IconElement; + loader?: () => Promise; routeRef?: RouteRef; + noHeader?: boolean; }; }>; -// @alpha (undocumented) +// @alpha @deprecated (undocumented) export const searchTranslationRef: TranslationRef< 'search', { @@ -267,8 +375,8 @@ export const searchTranslationRef: TranslationRef< readonly 'searchType.tabs.allTitle': 'All'; readonly 'searchType.allResults': 'All Results'; readonly 'searchType.accordion.collapse': 'Collapse'; - readonly 'searchType.accordion.allTitle': 'All'; readonly 'searchType.accordion.numberOfResults': '{{number}} results'; + readonly 'searchType.accordion.allTitle': 'All'; readonly 'sidebarSearchModal.title': 'Search'; } >; diff --git a/plugins/search/report.api.md b/plugins/search/report.api.md index c066d0ff47..73d5a948c3 100644 --- a/plugins/search/report.api.md +++ b/plugins/search/report.api.md @@ -10,6 +10,7 @@ import { ReactNode } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; import { SearchBarBaseProps } from '@backstage/plugin-search-react'; import { SearchResultSet } from '@backstage/plugin-search-common'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; // @public (undocumented) export const HomePageSearchBar: ( @@ -78,6 +79,20 @@ const searchPlugin: BackstagePlugin< export { searchPlugin as plugin }; export { searchPlugin }; +// @public (undocumented) +export const searchTranslationRef: TranslationRef< + 'search', + { + readonly 'searchModal.viewFullResults': 'View Full Results'; + readonly 'searchType.tabs.allTitle': 'All'; + readonly 'searchType.allResults': 'All Results'; + readonly 'searchType.accordion.collapse': 'Collapse'; + readonly 'searchType.accordion.numberOfResults': '{{number}} results'; + readonly 'searchType.accordion.allTitle': 'All'; + readonly 'sidebarSearchModal.title': 'Search'; + } +>; + // @public (undocumented) export const SearchType: { (props: SearchTypeProps): JSX_2.Element; diff --git a/plugins/search/src/alpha.tsx b/plugins/search/src/alpha.tsx index 97bd7dfe87..a981afa1de 100644 --- a/plugins/search/src/alpha.tsx +++ b/plugins/search/src/alpha.tsx @@ -18,13 +18,12 @@ import Grid from '@material-ui/core/Grid'; import Paper from '@material-ui/core/Paper'; import { makeStyles, Theme } from '@material-ui/core/styles'; import SearchIcon from '@material-ui/icons/Search'; +import { z } from 'zod/v4'; import { CatalogIcon, Content, DocsIcon, - Header, - Page, useSidebarPinState, } from '@backstage/core-components'; import { @@ -39,6 +38,7 @@ import { createExtensionInput, PageBlueprint, NavItemBlueprint, + configApiRef, } from '@backstage/frontend-plugin-api'; import { @@ -68,11 +68,6 @@ import { rootRouteRef } from './plugin'; import { SearchClient } from './apis'; import { SearchType } from './components/SearchType'; import { UrlUpdater } from './components/SearchPage/SearchPage'; -import { - compatWrapper, - convertLegacyRouteRef, - convertLegacyRouteRefs, -} from '@backstage/core-compat-api'; /** @alpha */ export const searchApi = ApiBlueprint.make({ @@ -99,10 +94,8 @@ const useSearchPageStyles = makeStyles((theme: Theme) => ({ /** @alpha */ export const searchPage = PageBlueprint.makeWithOverrides({ - config: { - schema: { - noTrack: z => z.boolean().default(false), - }, + configSchema: { + noTrack: z.boolean().default(false), }, inputs: { items: createExtensionInput([SearchResultListItemBlueprint.dataRefs.item]), @@ -116,7 +109,7 @@ export const searchPage = PageBlueprint.makeWithOverrides({ factory(originalFactory, { config, inputs }) { return originalFactory({ path: '/search', - routeRef: convertLegacyRouteRef(rootRouteRef), + routeRef: rootRouteRef, loader: async () => { const getResultItemComponent = (result: SearchResult) => { const value = inputs.items.find(item => @@ -144,121 +137,121 @@ export const searchPage = PageBlueprint.makeWithOverrides({ const { isMobile } = useSidebarPinState(); const { types } = useSearch(); const catalogApi = useApi(catalogApiRef); + const configApi = useApi(configApiRef); return ( - - {!isMobile &&
    } - - - - - - {!isMobile && ( - - , - }, - { - value: 'techdocs', - name: 'Documentation', - icon: , - }, - ].concat(resultTypes)} - /> - - {types.includes('techdocs') && ( - { - // Return a list of entities which are documented. - const { items } = await catalogApi.getEntities({ - fields: ['metadata.name'], - filter: { - 'metadata.annotations.backstage.io/techdocs-ref': - CATALOG_FILTER_EXISTS, - }, - }); - - const names = items.map( - entity => entity.metadata.name, - ); - names.sort(); - return names; - }} - /> - )} + + + + + + {!isMobile && ( + + , + }, + { + value: 'techdocs', + name: 'Documentation', + icon: , + }, + ].concat(resultTypes)} + /> + + {types.includes('techdocs') && ( - - {additionalSearchFilters.map(SearchFilterComponent => ( - - ))} - - - )} - - - - {({ results }) => ( - <> - {results.map((result, index) => { - const { noTrack } = config; - const { document, ...rest } = result; - const SearchResultListItem = - getResultItemComponent(result); - return ( - + label="Entity" + name="name" + values={async () => { + // Return a list of entities which are documented. + const { items } = await catalogApi.getEntities({ + fields: ['metadata.name'], + filter: { + 'metadata.annotations.backstage.io/techdocs-ref': + CATALOG_FILTER_EXISTS, + }, + }); + + const names = items.map( + entity => entity.metadata.name, ); - })} - + names.sort(); + return names; + }} + /> )} - - + + + {additionalSearchFilters.map(SearchFilterComponent => ( + + ))} + + )} + + + + {({ results }) => ( + <> + {results.map((result, index) => { + const { noTrack } = config; + const { document, ...rest } = result; + const SearchResultListItem = + getResultItemComponent(result); + return ( + + ); + })} + + )} + + - - + + ); }; - return compatWrapper( + return ( - , + ); }, }); @@ -268,7 +261,7 @@ export const searchPage = PageBlueprint.makeWithOverrides({ /** @alpha */ export const searchNavItem = NavItemBlueprint.make({ params: { - routeRef: convertLegacyRouteRef(rootRouteRef), + routeRef: rootRouteRef, title: 'Search', icon: SearchIcon, }, @@ -277,12 +270,19 @@ export const searchNavItem = NavItemBlueprint.make({ /** @alpha */ export default createFrontendPlugin({ pluginId: 'search', + title: 'Search', + icon: , info: { packageJson: () => import('../package.json') }, extensions: [searchApi, searchPage, searchNavItem], - routes: convertLegacyRouteRefs({ + routes: { root: rootRouteRef, - }), + }, }); -/** @alpha */ -export { searchTranslationRef } from './translation'; +import { searchTranslationRef as _searchTranslationRef } from './translation'; + +/** + * @alpha + * @deprecated Import from `@backstage/plugin-search` instead. + */ +export const searchTranslationRef = _searchTranslationRef; diff --git a/plugins/search/src/components/HomePageComponent/HomePageSearchBar.stories.tsx b/plugins/search/src/components/HomePageComponent/HomePageSearchBar.stories.tsx index a8331396a0..a42eb93468 100644 --- a/plugins/search/src/components/HomePageComponent/HomePageSearchBar.stories.tsx +++ b/plugins/search/src/components/HomePageComponent/HomePageSearchBar.stories.tsx @@ -40,6 +40,7 @@ export default { }, ), ], + tags: ['!manifest'], }; export const Default = () => { diff --git a/plugins/search/src/components/HomePageComponent/HomePageSearchBar.test.tsx b/plugins/search/src/components/HomePageComponent/HomePageSearchBar.test.tsx index e7c47b7d30..270bdbb2eb 100644 --- a/plugins/search/src/components/HomePageComponent/HomePageSearchBar.test.tsx +++ b/plugins/search/src/components/HomePageComponent/HomePageSearchBar.test.tsx @@ -46,12 +46,7 @@ describe('', () => { }, ); - expect(searchApiMock.query).toHaveBeenCalledWith( - expect.objectContaining({ term: '' }), - { - signal: expect.any(AbortSignal), - }, - ); + expect(searchApiMock.query).not.toHaveBeenCalled(); await userEvent.type(screen.getByLabelText('Search'), 'term{enter}'); diff --git a/plugins/search/src/components/SearchModal/SearchModal.stories.tsx b/plugins/search/src/components/SearchModal/SearchModal.stories.tsx index 266fadeb87..5847904f51 100644 --- a/plugins/search/src/components/SearchModal/SearchModal.stories.tsx +++ b/plugins/search/src/components/SearchModal/SearchModal.stories.tsx @@ -85,6 +85,7 @@ export default { { mountedRoutes: { '/search': rootRouteRef } }, ), ], + tags: ['!manifest'], }; export const Default = () => { diff --git a/plugins/search/src/components/SearchModal/SearchModal.test.tsx b/plugins/search/src/components/SearchModal/SearchModal.test.tsx index 8d8e1bb9b9..6235b5a511 100644 --- a/plugins/search/src/components/SearchModal/SearchModal.test.tsx +++ b/plugins/search/src/components/SearchModal/SearchModal.test.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { screen } from '@testing-library/react'; +import { screen, waitFor } from '@testing-library/react'; import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils'; import userEvent from '@testing-library/user-event'; import { configApiRef } from '@backstage/core-plugin-api'; @@ -63,7 +63,6 @@ describe('SearchModal', () => { ); expect(screen.getByRole('dialog')).toBeInTheDocument(); - expect(searchApiMock.query).toHaveBeenCalledTimes(1); }); it('Should use parent search context if defined', async () => { @@ -106,15 +105,21 @@ describe('SearchModal', () => { ); expect(screen.getByRole('dialog')).toBeInTheDocument(); - expect(searchApiMock.query).toHaveBeenCalledWith( - { - term: '', - filters: {}, - types: [], - pageCursor: undefined, - }, - { signal: expect.any(AbortSignal) }, - ); + + const input = screen.getByLabelText('Search'); + await userEvent.type(input, 'text'); + + await waitFor(() => { + expect(searchApiMock.query).toHaveBeenCalledWith( + { + term: 'text', + filters: {}, + types: [], + pageCursor: undefined, + }, + { signal: expect.any(AbortSignal) }, + ); + }); }); it('Should render a custom Modal correctly', async () => { @@ -146,27 +151,10 @@ describe('SearchModal', () => { }, ); - expect(searchApiMock.query).toHaveBeenCalledTimes(1); await userEvent.keyboard('{Escape}'); expect(toggleModal).toHaveBeenCalledTimes(1); }); - it('should render SearchModal hiding its content', async () => { - const { getByTestId } = await renderInTestApp( - - , - { - mountedRoutes: { - '/search': rootRouteRef, - }, - }, - ); - - expect(getByTestId('search-bar-next')).toBeInTheDocument(); - expect(getByTestId('search-bar-next')).not.toBeVisible(); - }); - it('should focus on its search bar when opened', async () => { await renderInTestApp( @@ -248,4 +236,36 @@ describe('SearchModal', () => { expect(navigate).toHaveBeenCalledWith('/search?query=term'); }); + + it('should completely unmount the Dialog from DOM when open prop is false', async () => { + await renderInTestApp( + + , + { + mountedRoutes: { + '/search': rootRouteRef, + }, + }, + ); + + // Dialog should not exist in the DOM at all (unmounted, not just hidden) + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + }); + + it('should completely unmount the Dialog from DOM when hidden prop is true', async () => { + await renderInTestApp( + + , + { + mountedRoutes: { + '/search': rootRouteRef, + }, + }, + ); + + // Dialog should not exist in the DOM at all (unmounted, not just hidden) + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + }); }); diff --git a/plugins/search/src/components/SearchModal/SearchModal.tsx b/plugins/search/src/components/SearchModal/SearchModal.tsx index af7e1c6e10..86fa4f1b73 100644 --- a/plugins/search/src/components/SearchModal/SearchModal.tsx +++ b/plugins/search/src/components/SearchModal/SearchModal.tsx @@ -211,6 +211,8 @@ export const SearchModal = (props: SearchModalProps) => { const classes = useStyles(); + const isDialogOpen = open && !hidden; + return ( { aria-modal="true" fullWidth maxWidth="lg" - open={open} - hidden={hidden} + open={isDialogOpen} > - {open && ( + {isDialogOpen && ( {(children && children({ diff --git a/plugins/search/src/components/SearchPage/SearchPage.tsx b/plugins/search/src/components/SearchPage/SearchPage.tsx index 9baee0d8f9..92b327207f 100644 --- a/plugins/search/src/components/SearchPage/SearchPage.tsx +++ b/plugins/search/src/components/SearchPage/SearchPage.tsx @@ -45,7 +45,7 @@ export const UrlUpdater = () => { } const query = - qs.parse(location.search.substring(1), { arrayLimit: 0 }) || {}; + qs.parse(location.search.substring(1), { arrayLimit: 10000 }) || {}; if (query.filters) { setFilters(query.filters as JsonObject); @@ -59,9 +59,7 @@ export const UrlUpdater = () => { setPageCursor(query.pageCursor as string); } - if (query.types) { - setTypes(query.types as string[]); - } + setTypes(query.types ? (query.types as string[]) : []); }, [prevQueryParams, location, setTerm, setTypes, setPageCursor, setFilters]); useEffect(() => { diff --git a/plugins/search/src/components/SearchPage/UrlUpdater.tsx b/plugins/search/src/components/SearchPage/UrlUpdater.tsx deleted file mode 100644 index d4bfe057a3..0000000000 --- a/plugins/search/src/components/SearchPage/UrlUpdater.tsx +++ /dev/null @@ -1,85 +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 { useEffect } from 'react'; -import usePrevious from 'react-use/esm/usePrevious'; -import qs from 'qs'; -import { useLocation } from 'react-router-dom'; -import { useSearch } from '@backstage/plugin-search-react'; -import { JsonObject } from '@backstage/types'; - -export const UrlUpdater = () => { - const location = useLocation(); - const { - term, - setTerm, - types, - setTypes, - pageCursor, - setPageCursor, - filters, - setFilters, - } = useSearch(); - - const prevQueryParams = usePrevious(location.search); - - useEffect(() => { - // Only respond to changes to url query params - if (location.search === prevQueryParams) { - return; - } - - const query = - qs.parse(location.search.substring(1), { arrayLimit: 0 }) || {}; - - if (query.filters) { - setFilters(query.filters as JsonObject); - } - - if (query.query) { - setTerm(query.query as string); - } - - if (query.pageCursor) { - setPageCursor(query.pageCursor as string); - } - - if (query.types) { - setTypes(query.types as string[]); - } - }, [prevQueryParams, location, setTerm, setTypes, setPageCursor, setFilters]); - - useEffect(() => { - const newParams = qs.stringify( - { - query: term, - types, - pageCursor, - filters, - }, - { arrayFormat: 'brackets' }, - ); - const newUrl = `${window.location.pathname}?${newParams}`; - - // We directly manipulate window history here in order to not re-render - // infinitely (state => location => state => etc). The intention of this - // code is just to ensure the right query/filters are loaded when a user - // clicks the "back" button after clicking a result. - window.history.replaceState(null, document.title, newUrl); - }, [term, types, pageCursor, filters]); - - return null; -}; diff --git a/plugins/search/src/components/SearchType/SearchType.stories.tsx b/plugins/search/src/components/SearchType/SearchType.stories.tsx index 7ace231b40..d892d2da7a 100644 --- a/plugins/search/src/components/SearchType/SearchType.stories.tsx +++ b/plugins/search/src/components/SearchType/SearchType.stories.tsx @@ -44,6 +44,7 @@ export default { ), ], + tags: ['!manifest'], }; const values = ['value-1', 'value-2', 'value-3']; diff --git a/plugins/search/src/index.ts b/plugins/search/src/index.ts index 66b032c83c..fc579c55cc 100644 --- a/plugins/search/src/index.ts +++ b/plugins/search/src/index.ts @@ -51,3 +51,4 @@ export { searchPlugin as plugin, searchPlugin, } from './plugin'; +export { searchTranslationRef } from './translation'; diff --git a/plugins/search/src/translation.ts b/plugins/search/src/translation.ts index 37f86b9218..58b5b72c77 100644 --- a/plugins/search/src/translation.ts +++ b/plugins/search/src/translation.ts @@ -17,7 +17,7 @@ import { createTranslationRef } from '@backstage/core-plugin-api/alpha'; /** - * @alpha + * @public */ export const searchTranslationRef = createTranslationRef({ id: 'search', diff --git a/plugins/signals-backend/CHANGELOG.md b/plugins/signals-backend/CHANGELOG.md index 989623b70f..e804b1968f 100644 --- a/plugins/signals-backend/CHANGELOG.md +++ b/plugins/signals-backend/CHANGELOG.md @@ -1,5 +1,140 @@ # @backstage/plugin-signals-backend +## 0.3.14 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/plugin-signals-node@0.2.0 + - @backstage/config@1.3.7 + - @backstage/plugin-events-node@0.4.21 + +## 0.3.14-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/config@1.3.7-next.0 + - @backstage/plugin-events-node@0.4.21-next.2 + - @backstage/plugin-signals-node@0.1.30-next.2 + +## 0.3.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + - @backstage/plugin-signals-node@0.1.30-next.1 + +## 0.3.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-events-node@0.4.21-next.0 + - @backstage/plugin-signals-node@0.1.30-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## 0.3.13 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-events-node@0.4.20 + - @backstage/plugin-signals-node@0.1.29 + +## 0.3.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-events-node@0.4.20-next.1 + - @backstage/plugin-signals-node@0.1.29-next.1 + +## 0.3.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.20-next.0 + - @backstage/plugin-signals-node@0.1.29-next.0 + +## 0.3.12 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-events-node@0.4.19 + - @backstage/plugin-signals-node@0.1.28 + +## 0.3.12-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + - @backstage/plugin-signals-node@0.1.28-next.0 + +## 0.3.11 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/plugin-signals-node@0.1.27 + - @backstage/backend-plugin-api@1.6.0 + +## 0.3.11-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/plugin-signals-node@0.1.27-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## 0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-signals-node@0.1.27-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## 0.3.10 + +### Patch Changes + +- 71c22f3: Removed/moved unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + - @backstage/plugin-signals-node@0.1.26 + ## 0.3.10-next.1 ### Patch Changes diff --git a/plugins/signals-backend/knip-report.md b/plugins/signals-backend/knip-report.md index 97d5b385fd..2661c35327 100644 --- a/plugins/signals-backend/knip-report.md +++ b/plugins/signals-backend/knip-report.md @@ -1,3 +1,2 @@ # Knip report - diff --git a/plugins/signals-backend/package.json b/plugins/signals-backend/package.json index 632d9d32ad..485925de36 100644 --- a/plugins/signals-backend/package.json +++ b/plugins/signals-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals-backend", - "version": "0.3.10-next.1", + "version": "0.3.14", "backstage": { "role": "backend-plugin", "pluginId": "signals", @@ -42,7 +42,7 @@ "@backstage/plugin-events-node": "workspace:^", "@backstage/plugin-signals-node": "workspace:^", "@backstage/types": "workspace:^", - "express": "^4.17.1", + "express": "^4.22.0", "express-promise-router": "^4.1.0", "uuid": "^11.0.0", "ws": "^8.18.0" diff --git a/plugins/signals-backend/src/service/SignalManager.ts b/plugins/signals-backend/src/service/SignalManager.ts index ec9cb57900..352d54d628 100644 --- a/plugins/signals-backend/src/service/SignalManager.ts +++ b/plugins/signals-backend/src/service/SignalManager.ts @@ -16,7 +16,7 @@ import { EventParams, EventsService } from '@backstage/plugin-events-node'; import { SignalPayload } from '@backstage/plugin-signals-node'; -import crypto from 'crypto'; +import crypto from 'node:crypto'; import { RawData, WebSocket } from 'ws'; import { v4 as uuid } from 'uuid'; import { JsonObject } from '@backstage/types'; diff --git a/plugins/signals-backend/src/service/router.ts b/plugins/signals-backend/src/service/router.ts index eb1c24f383..cdbfa1de72 100644 --- a/plugins/signals-backend/src/service/router.ts +++ b/plugins/signals-backend/src/service/router.ts @@ -24,12 +24,12 @@ import { LoggerService, UserInfoService, } from '@backstage/backend-plugin-api'; -import * as https from 'https'; -import http, { IncomingMessage } from 'http'; +import * as https from 'node:https'; +import http, { IncomingMessage } from 'node:http'; import { SignalManager } from './SignalManager'; import { EventsService } from '@backstage/plugin-events-node'; import { WebSocket, WebSocketServer } from 'ws'; -import { Duplex } from 'stream'; +import { Duplex } from 'node:stream'; import { Config } from '@backstage/config'; export interface RouterOptions { diff --git a/plugins/signals-node/CHANGELOG.md b/plugins/signals-node/CHANGELOG.md index 4a2080b665..d96747467b 100644 --- a/plugins/signals-node/CHANGELOG.md +++ b/plugins/signals-node/CHANGELOG.md @@ -1,5 +1,141 @@ # @backstage/plugin-signals-node +## 0.2.0 + +### Minor Changes + +- 8397a9c: **BREAKING**: Removed the deprecated `SignalService` and `DefaultSignalService` exports. Use `SignalsService` and `DefaultSignalsService` instead. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/plugin-auth-node@0.7.0 + - @backstage/config@1.3.7 + - @backstage/plugin-events-node@0.4.21 + +## 0.1.30-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.7.0-next.2 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/config@1.3.7-next.0 + - @backstage/plugin-events-node@0.4.21-next.2 + +## 0.1.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## 0.1.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-auth-node@0.6.15-next.0 + - @backstage/plugin-events-node@0.4.21-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## 0.1.29 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/plugin-auth-node@0.6.14 + - @backstage/plugin-events-node@0.4.20 + +## 0.1.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-auth-node@0.6.14-next.2 + - @backstage/plugin-events-node@0.4.20-next.1 + +## 0.1.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + - @backstage/plugin-auth-node@0.6.14-next.0 + - @backstage/plugin-events-node@0.4.20-next.0 + +## 0.1.28 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-auth-node@0.6.13 + - @backstage/plugin-events-node@0.4.19 + +## 0.1.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-auth-node@0.6.12-next.0 + - @backstage/plugin-events-node@0.4.19-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## 0.1.27 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## 0.1.27-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## 0.1.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## 0.1.26 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config@1.3.6 + ## 0.1.26-next.1 ### Patch Changes diff --git a/plugins/signals-node/knip-report.md b/plugins/signals-node/knip-report.md index 42ea194792..0ec14813da 100644 --- a/plugins/signals-node/knip-report.md +++ b/plugins/signals-node/knip-report.md @@ -1,18 +1,8 @@ # Knip report -## Unused dependencies (5) - -| Name | Location | Severity | -| :-------------------------- | :----------- | :------- | -| @backstage/plugin-auth-node | plugins/signals-node/package.json | error | -| @backstage/config | plugins/signals-node/package.json | error | -| express | plugins/signals-node/package.json | error | -| uuid | plugins/signals-node/package.json | error | -| ws | plugins/signals-node/package.json | error | - ## Unused devDependencies (1) -| Name | Location | Severity | -| :------------- | :----------- | :------- | -| @types/express | plugins/signals-node/package.json | error | +| Name | Location | Severity | +| :------------- | :---------------- | :------- | +| @types/express | package.json:46:6 | error | diff --git a/plugins/signals-node/package.json b/plugins/signals-node/package.json index 5c222e4fc0..96b91be214 100644 --- a/plugins/signals-node/package.json +++ b/plugins/signals-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals-node", - "version": "0.1.26-next.1", + "version": "0.2.0", "description": "Node.js library for the signals plugin", "backstage": { "role": "node-library", @@ -38,13 +38,8 @@ }, "dependencies": { "@backstage/backend-plugin-api": "workspace:^", - "@backstage/config": "workspace:^", - "@backstage/plugin-auth-node": "workspace:^", "@backstage/plugin-events-node": "workspace:^", - "@backstage/types": "workspace:^", - "express": "^4.17.1", - "uuid": "^11.0.0", - "ws": "^8.18.0" + "@backstage/types": "workspace:^" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/plugins/signals-node/report.api.md b/plugins/signals-node/report.api.md index a368dd2ece..29fb4ab1f1 100644 --- a/plugins/signals-node/report.api.md +++ b/plugins/signals-node/report.api.md @@ -7,9 +7,6 @@ import { EventsService } from '@backstage/plugin-events-node'; import { JsonObject } from '@backstage/types'; import { ServiceRef } from '@backstage/backend-plugin-api'; -// @public @deprecated (undocumented) -export const DefaultSignalService: typeof DefaultSignalsService; - // @public (undocumented) export class DefaultSignalsService implements SignalsService { // (undocumented) @@ -33,9 +30,6 @@ export type SignalPayload = { message: TMessage; }; -// @public @deprecated (undocumented) -export interface SignalService extends SignalsService {} - // @public @deprecated (undocumented) export const signalService: ServiceRef; diff --git a/plugins/signals-node/src/DefaultSignalsService.ts b/plugins/signals-node/src/DefaultSignalsService.ts index ca1412c6fa..4975f60a05 100644 --- a/plugins/signals-node/src/DefaultSignalsService.ts +++ b/plugins/signals-node/src/DefaultSignalsService.ts @@ -43,9 +43,3 @@ export class DefaultSignalsService implements SignalsService { }); } } - -/** - * @public - * @deprecated Use `DefaultSignalsService` instead - */ -export const DefaultSignalService = DefaultSignalsService; diff --git a/plugins/signals-node/src/SignalsService.ts b/plugins/signals-node/src/SignalsService.ts index f338ceaaad..348109b868 100644 --- a/plugins/signals-node/src/SignalsService.ts +++ b/plugins/signals-node/src/SignalsService.ts @@ -26,9 +26,3 @@ export interface SignalsService { signal: SignalPayload, ): Promise; } - -/** - * @public - * @deprecated Use `SignalsService` instead - */ -export interface SignalService extends SignalsService {} diff --git a/plugins/signals-react/CHANGELOG.md b/plugins/signals-react/CHANGELOG.md index a73cc7dfb5..f8bb26b600 100644 --- a/plugins/signals-react/CHANGELOG.md +++ b/plugins/signals-react/CHANGELOG.md @@ -1,5 +1,81 @@ # @backstage/plugin-signals-react +## 0.0.21 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.5 + +## 0.0.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.5-next.0 + - @backstage/types@1.2.2 + +## 0.0.20 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.4 + +## 0.0.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/types@1.2.2 + +## 0.0.19 + +### Patch Changes + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/core-plugin-api@1.12.3 + +## 0.0.19-next.1 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/core-plugin-api@1.12.3-next.1 + +## 0.0.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/types@1.2.2 + +## 0.0.18 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1 + +## 0.0.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/types@1.2.2 + +## 0.0.17 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.0 + ## 0.0.17-next.0 ### Patch Changes diff --git a/plugins/signals-react/knip-report.md b/plugins/signals-react/knip-report.md index e210ba0b51..8bdaf6396c 100644 --- a/plugins/signals-react/knip-report.md +++ b/plugins/signals-react/knip-report.md @@ -2,14 +2,14 @@ ## Unused dependencies (1) -| Name | Location | Severity | -| :---------------- | :----------- | :------- | -| @material-ui/core | plugins/signals-react/package.json | error | +| Name | Location | Severity | +| :---------------- | :---------------- | :------- | +| @material-ui/core | package.json:44:6 | error | ## Unused devDependencies (2) -| Name | Location | Severity | -| :--------------------- | :----------- | :------- | -| @testing-library/react | plugins/signals-react/package.json | error | -| @backstage/test-utils | plugins/signals-react/package.json | error | +| Name | Location | Severity | +| :--------------------- | :---------------- | :------- | +| @testing-library/react | package.json:50:6 | error | +| @backstage/test-utils | package.json:48:6 | error | diff --git a/plugins/signals-react/package.json b/plugins/signals-react/package.json index d11c0d5f7b..fd147163f8 100644 --- a/plugins/signals-react/package.json +++ b/plugins/signals-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals-react", - "version": "0.0.17-next.0", + "version": "0.0.21", "description": "Web library for the signals plugin", "backstage": { "role": "web-library", @@ -51,13 +51,13 @@ "@types/react": "^18.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/plugins/signals-react/report.api.md b/plugins/signals-react/report.api.md index 87d856288b..23a81af4fc 100644 --- a/plugins/signals-react/report.api.md +++ b/plugins/signals-react/report.api.md @@ -3,7 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { ApiRef } from '@backstage/core-plugin-api'; +import { ApiRef } from '@backstage/frontend-plugin-api'; import { JsonObject } from '@backstage/types'; // @public (undocumented) diff --git a/plugins/signals/CHANGELOG.md b/plugins/signals/CHANGELOG.md index 6aa44ebfbc..38b0709d81 100644 --- a/plugins/signals/CHANGELOG.md +++ b/plugins/signals/CHANGELOG.md @@ -1,5 +1,179 @@ # @backstage/plugin-signals +## 0.0.30 + +### Patch Changes + +- 79453c0: Updated dependency `wait-for-expect` to `^4.0.0`. +- d156cf4: Added `title` and `icon` to the new frontend system plugin definition. +- Updated dependencies + - @backstage/theme@0.7.3 + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/core-components@0.18.9 + - @backstage/core-plugin-api@1.12.5 + - @backstage/plugin-signals-react@0.0.21 + +## 0.0.30-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.7.3-next.0 + - @backstage/core-components@0.18.9-next.1 + - @backstage/core-plugin-api@1.12.5-next.2 + - @backstage/frontend-plugin-api@0.16.0-next.2 + +## 0.0.30-next.1 + +### Patch Changes + +- d156cf4: Added `title` and `icon` to the new frontend system plugin definition. +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + +## 0.0.30-next.0 + +### Patch Changes + +- 79453c0: Updated dependency `wait-for-expect` to `^4.0.0`. +- Updated dependencies + - @backstage/core-components@0.18.9-next.0 + - @backstage/frontend-plugin-api@0.15.2-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + - @backstage/theme@0.7.2 + - @backstage/types@1.2.2 + - @backstage/plugin-signals-react@0.0.21-next.0 + +## 0.0.29 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/plugin-signals-react@0.0.20 + +## 0.0.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/core-components@0.18.8-next.1 + +## 0.0.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/theme@0.7.2 + - @backstage/types@1.2.2 + - @backstage/plugin-signals-react@0.0.20-next.0 + +## 0.0.28 + +### Patch Changes + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/core-components@0.18.7 + - @backstage/theme@0.7.2 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-signals-react@0.0.19 + +## 0.0.28-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-signals-react@0.0.19-next.1 + - @backstage/theme@0.7.2-next.1 + +## 0.0.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.7.2-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/core-components@0.18.7-next.1 + +## 0.0.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.6-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/theme@0.7.1 + - @backstage/types@1.2.2 + - @backstage/plugin-signals-react@0.0.19-next.0 + +## 0.0.27 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + +## 0.0.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + +## 0.0.26 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/plugin-signals-react@0.0.18 + +## 0.0.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-signals-react@0.0.18-next.0 + - @backstage/types@1.2.2 + +## 0.0.25 + +### Patch Changes + +- 71c22f3: Removed/moved unused dependencies +- f0f006e: Fixes a bug where the `SignalClient` would try to subscribe to the same channel twice after an error, instead of just once. +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-signals-react@0.0.17 + ## 0.0.25-next.1 ### Patch Changes diff --git a/plugins/signals/knip-report.md b/plugins/signals/knip-report.md index 97d5b385fd..2661c35327 100644 --- a/plugins/signals/knip-report.md +++ b/plugins/signals/knip-report.md @@ -1,3 +1,2 @@ # Knip report - diff --git a/plugins/signals/package.json b/plugins/signals/package.json index 8f688081c0..c3a517dfcd 100644 --- a/plugins/signals/package.json +++ b/plugins/signals/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals", - "version": "0.0.25-next.1", + "version": "0.0.30", "backstage": { "role": "frontend-plugin", "pluginId": "signals", @@ -51,7 +51,6 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", @@ -59,6 +58,7 @@ "@backstage/theme": "workspace:^", "@backstage/types": "workspace:^", "@material-ui/core": "^4.12.4", + "@remixicon/react": "^4.6.0", "uuid": "^11.0.0" }, "devDependencies": { @@ -71,14 +71,14 @@ "jest-websocket-mock": "^2.5.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0", - "wait-for-expect": "^3.0.2" + "react-router-dom": "^6.30.2", + "wait-for-expect": "^4.0.0" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/plugins/signals/src/alpha.tsx b/plugins/signals/src/alpha.tsx index c32bff140e..3cab21f00c 100644 --- a/plugins/signals/src/alpha.tsx +++ b/plugins/signals/src/alpha.tsx @@ -20,6 +20,7 @@ import { discoveryApiRef, identityApiRef, } from '@backstage/frontend-plugin-api'; +import { RiBroadcastLine } from '@remixicon/react'; import { signalApiRef } from '@backstage/plugin-signals-react'; import { SignalClient } from './api/SignalClient'; @@ -43,6 +44,8 @@ const api = ApiBlueprint.make({ /** @alpha */ export default createFrontendPlugin({ pluginId: 'signals', + title: 'Signals', + icon: , info: { packageJson: () => import('../package.json') }, extensions: [api], }); diff --git a/plugins/techdocs-addons-test-utils/CHANGELOG.md b/plugins/techdocs-addons-test-utils/CHANGELOG.md index b0b0864402..e61e705cf8 100644 --- a/plugins/techdocs-addons-test-utils/CHANGELOG.md +++ b/plugins/techdocs-addons-test-utils/CHANGELOG.md @@ -1,5 +1,295 @@ # @backstage/plugin-techdocs-addons-test-utils +## 2.0.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@2.0.2 + - @backstage/plugin-catalog-react@2.1.2 + - @backstage/core-app-api@1.20.0 + - @backstage/plugin-techdocs@1.17.3 + - @backstage/test-utils@1.7.17 + - @backstage/core-plugin-api@1.12.5 + - @backstage/integration-react@1.2.17 + - @backstage/plugin-search-react@1.11.1 + - @backstage/plugin-techdocs-react@1.3.10 + +## 2.0.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/test-utils@1.7.17-next.1 + - @backstage/plugin-catalog@2.0.2-next.1 + - @backstage/plugin-search-react@1.11.1-next.1 + - @backstage/plugin-techdocs@1.17.3-next.1 + - @backstage/plugin-techdocs-react@1.3.10-next.1 + +## 2.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@2.0.2-next.0 + - @backstage/plugin-catalog-react@2.1.1-next.0 + - @backstage/plugin-techdocs@1.17.3-next.0 + - @backstage/core-app-api@1.19.7-next.0 + - @backstage/integration-react@1.2.17-next.0 + - @backstage/plugin-search-react@1.11.1-next.0 + - @backstage/plugin-techdocs-react@1.3.10-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + - @backstage/test-utils@1.7.17-next.0 + +## 2.0.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/plugin-search-react@1.11.0 + - @backstage/core-app-api@1.19.6 + - @backstage/plugin-techdocs@1.17.1 + - @backstage/plugin-catalog@2.0.0 + - @backstage/integration-react@1.2.16 + - @backstage/test-utils@1.7.16 + - @backstage/plugin-techdocs-react@1.3.9 + +## 2.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.6-next.1 + - @backstage/plugin-techdocs@1.17.1-next.2 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.2 + - @backstage/plugin-catalog@2.0.0-next.2 + - @backstage/plugin-search-react@1.10.5-next.1 + - @backstage/plugin-techdocs-react@1.3.9-next.1 + +## 2.0.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.17.1-next.1 + - @backstage/plugin-catalog@1.34.0-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/integration-react@1.2.16-next.1 + - @backstage/test-utils@1.7.16-next.0 + - @backstage/plugin-search-react@1.10.5-next.0 + - @backstage/plugin-techdocs-react@1.3.9-next.0 + +## 2.0.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-react@1.10.5-next.0 + - @backstage/plugin-catalog@1.33.1-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/plugin-techdocs@1.17.1-next.0 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/integration-react@1.2.16-next.0 + - @backstage/test-utils@1.7.16-next.0 + - @backstage/plugin-techdocs-react@1.3.9-next.0 + +## 2.0.2 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/plugin-catalog@1.33.0 + - @backstage/core-app-api@1.19.5 + - @backstage/plugin-techdocs@1.17.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-techdocs-react@1.3.8 + - @backstage/integration-react@1.2.15 + - @backstage/plugin-search-react@1.10.3 + - @backstage/test-utils@1.7.15 + +## 2.0.2-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/plugin-catalog@1.33.0-next.2 + - @backstage/core-app-api@1.19.5-next.1 + - @backstage/plugin-techdocs@1.16.3-next.2 + - @backstage/integration-react@1.2.15-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-techdocs-react@1.3.8-next.1 + - @backstage/plugin-search-react@1.10.3-next.2 + - @backstage/test-utils@1.7.15-next.2 + +## 2.0.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/plugin-catalog@1.33.0-next.1 + - @backstage/plugin-search-react@1.10.3-next.1 + - @backstage/plugin-techdocs@1.16.3-next.1 + - @backstage/test-utils@1.7.15-next.1 + - @backstage/plugin-techdocs-react@1.3.8-next.0 + - @backstage/integration-react@1.2.15-next.1 + +## 2.0.2-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/plugin-techdocs@1.16.3-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/plugin-techdocs-react@1.3.8-next.0 + - @backstage/core-app-api@1.19.4-next.0 + - @backstage/plugin-search-react@1.10.3-next.0 + - @backstage/plugin-catalog@1.32.3-next.0 + - @backstage/integration-react@1.2.15-next.0 + - @backstage/test-utils@1.7.15-next.0 + +## 2.0.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.32.2 + - @backstage/plugin-techdocs@1.16.2 + - @backstage/plugin-search-react@1.10.2 + - @backstage/integration-react@1.2.14 + - @backstage/plugin-catalog-react@1.21.5 + - @backstage/plugin-techdocs-react@1.3.7 + +## 2.0.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-catalog@1.32.2-next.1 + - @backstage/plugin-catalog-react@1.21.5-next.1 + - @backstage/plugin-search-react@1.10.2-next.0 + - @backstage/plugin-techdocs@1.16.2-next.1 + - @backstage/plugin-techdocs-react@1.3.7-next.0 + +## 2.0.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-techdocs@1.16.2-next.0 + - @backstage/plugin-catalog@1.32.2-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.0 + - @backstage/plugin-search-react@1.10.1 + +## 2.0.0 + +### Major Changes + +- 8d6709e: **BREAKING**: `TechDocsAddonTester.renderWithEffects()` no longer returns a screen; this means that you can no longer grab assertions such as `getByText` from its return value. + + Newer versions of `@testing-library` recommends using the `screen` export for assertions - and removing this from the addon tester contract allows us to more freely iterate on which underlying version of the testing library is being used. + + One notable effect of this, however, is that the `@testing-library` `screen` does NOT support assertions on the shadow DOM, which techdocs relies on. You will therefore want to add a dependency on [the `shadow-dom-testing-library` package](https://github.com/konnorrogers/shadow-dom-testing-library/) in your tests, and using its `screen` and its dedicated `*Shadow*` methods. As an example, if you keep doing `getByText` you will not get matches inside the shadow DOM - switch to `getByShadowText` instead. + + ```ts + import { screen } from 'shadow-dom-testing-library'; + + // ... render the addon ... + await TechDocsAddonTester.buildAddonsInTechDocs([]) + .withDom(TEST_CONTENT) + .renderWithEffects(); + + expect(screen.getByShadowText('TEST_CONTENT')).toBeInTheDocument(); + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/plugin-techdocs@1.16.1 + - @backstage/plugin-catalog@1.32.1 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-search-react@1.10.1 + - @backstage/test-utils@1.7.14 + - @backstage/core-plugin-api@1.12.1 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-techdocs-react@1.3.6 + +## 2.0.0-next.1 + +### Major Changes + +- 8d6709e: **BREAKING**: `TechDocsAddonTester.renderWithEffects()` no longer returns a screen; this means that you can no longer grab assertions such as `getByText` from its return value. + + Newer versions of `@testing-library` recommends using the `screen` export for assertions - and removing this from the addon tester contract allows us to more freely iterate on which underlying version of the testing library is being used. + + One notable effect of this, however, is that the `@testing-library` `screen` does NOT support assertions on the shadow DOM, which techdocs relies on. You will therefore want to add a dependency on [the `shadow-dom-testing-library` package](https://github.com/konnorrogers/shadow-dom-testing-library/) in your tests, and using its `screen` and its dedicated `*Shadow*` methods. As an example, if you keep doing `getByText` you will not get matches inside the shadow DOM - switch to `getByShadowText` instead. + + ```ts + import { screen } from 'shadow-dom-testing-library'; + + // ... render the addon ... + await TechDocsAddonTester.buildAddonsInTechDocs([]) + .withDom(TEST_CONTENT) + .renderWithEffects(); + + expect(screen.getByShadowText('TEST_CONTENT')).toBeInTheDocument(); + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.4-next.1 + - @backstage/plugin-techdocs@1.16.1-next.1 + +## 1.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/test-utils@1.7.14-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/plugin-techdocs@1.16.1-next.0 + - @backstage/plugin-catalog@1.32.1-next.0 + - @backstage/plugin-search-react@1.10.1-next.0 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/integration-react@1.2.13-next.0 + +## 1.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.16.0 + - @backstage/plugin-search-react@1.10.0 + - @backstage/plugin-catalog@1.32.0 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/plugin-techdocs-react@1.3.5 + - @backstage/integration-react@1.2.12 + - @backstage/test-utils@1.7.13 + ## 1.1.2-next.1 ### Patch Changes diff --git a/plugins/techdocs-addons-test-utils/knip-report.md b/plugins/techdocs-addons-test-utils/knip-report.md index 2d84f97e2f..341150b3a0 100644 --- a/plugins/techdocs-addons-test-utils/knip-report.md +++ b/plugins/techdocs-addons-test-utils/knip-report.md @@ -2,13 +2,7 @@ ## Unused dependencies (1) -| Name | Location | Severity | -| :------------------------ | :----------- | :------- | -| @backstage/plugin-catalog | plugins/techdocs-addons-test-utils/package.json | error | - -## Unused devDependencies (1) - -| Name | Location | Severity | -| :------------------- | :----------- | :------- | -| @testing-library/dom | plugins/techdocs-addons-test-utils/package.json | error | +| Name | Location | Severity | +| :------------------------ | :---------------- | :------- | +| @backstage/plugin-catalog | package.json:46:6 | error | diff --git a/plugins/techdocs-addons-test-utils/package.json b/plugins/techdocs-addons-test-utils/package.json index 4bbdf6e73b..b90ed950b1 100644 --- a/plugins/techdocs-addons-test-utils/package.json +++ b/plugins/techdocs-addons-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-addons-test-utils", - "version": "1.1.2-next.1", + "version": "2.0.4", "backstage": { "role": "web-library", "pluginId": "techdocs-addons", @@ -49,7 +49,7 @@ "@backstage/plugin-techdocs": "workspace:^", "@backstage/plugin-techdocs-react": "workspace:^", "@backstage/test-utils": "workspace:^", - "testing-library__dom": "^7.29.4-beta.1" + "shadow-dom-testing-library": "^1.13.1" }, "devDependencies": { "@backstage/cli": "workspace:^", @@ -58,14 +58,14 @@ "@types/react": "^18.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@testing-library/react": "^16.0.0", "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/plugins/techdocs-addons-test-utils/report.api.md b/plugins/techdocs-addons-test-utils/report.api.md index a963c239e2..a3326779d7 100644 --- a/plugins/techdocs-addons-test-utils/report.api.md +++ b/plugins/techdocs-addons-test-utils/report.api.md @@ -6,7 +6,6 @@ import { ApiRef } from '@backstage/core-plugin-api'; import { JSXElementConstructor } from 'react'; import { ReactElement } from 'react'; -import { screen as screen_2 } from 'testing-library__dom'; import { TechDocsEntityMetadata } from '@backstage/plugin-techdocs-react'; import { TechDocsMetadata } from '@backstage/plugin-techdocs-react'; @@ -16,11 +15,9 @@ export class TechDocsAddonTester { atPath(path: string): this; build(): ReactElement>; static buildAddonsInTechDocs(addons: ReactElement[]): TechDocsAddonTester; - renderWithEffects(): Promise< - typeof screen_2 & { - shadowRoot: ShadowRoot | null; - } - >; + renderWithEffects(): Promise<{ + shadowRoot: ShadowRoot | null; + }>; withApis(apis: TechdocsAddonTesterApis): this; withDom(dom: ReactElement): this; withEntity(entity: Partial): this; diff --git a/plugins/techdocs-addons-test-utils/src/test-utils.tsx b/plugins/techdocs-addons-test-utils/src/test-utils.tsx index a7cb74cb93..b30b9afa5e 100644 --- a/plugins/techdocs-addons-test-utils/src/test-utils.tsx +++ b/plugins/techdocs-addons-test-utils/src/test-utils.tsx @@ -18,7 +18,7 @@ import { cloneElement, ReactElement } from 'react'; // Shadow DOM support for the simple and complete DOM testing utilities // https://github.com/testing-library/dom-testing-library/issues/742#issuecomment-674987855 -import { screen } from 'testing-library__dom'; +import { screen } from 'shadow-dom-testing-library'; import { Route } from 'react-router-dom'; import { act, render } from '@testing-library/react'; @@ -50,7 +50,7 @@ import { scmIntegrationsApiRef } from '@backstage/integration-react'; // Since React 18 react-dom/server eagerly uses TextEncoder, so lazy load and make it available globally first if (!global.TextEncoder) { - global.TextEncoder = require('util').TextEncoder; + global.TextEncoder = require('node:util').TextEncoder; } const { renderToStaticMarkup } = require('react-dom/server') as typeof import('react-dom/server'); @@ -320,6 +320,25 @@ export class TechDocsAddonTester { * Render the Addon within a fully configured and mocked TechDocs reader. * * @remarks + * + * Note that to make assertions on the shadow dom, add a dependency on + * [the `shadow-dom-testing-library` package](https://github.com/konnorrogers/shadow-dom-testing-library/) + * and use its screen as follows: + * + * ```ts + * import { screen } from 'shadow-dom-testing-library'; + * + * // ... render the addon ... + * await TechDocsAddonTester.buildAddonsInTechDocs([]) + * .withDom(TEST_CONTENT) + * .renderWithEffects(); + * + * expect(screen.getByShadowText('TEST_CONTENT')).toBeInTheDocument(); + * ``` + * + * For items outside of the shadow dom, you can still use the regular screen + * from `@testing-library/react`. + * * Components using useEffect to perform an asynchronous action (such as * fetch) must be rendered within an async act call to properly get the final * state, even with mocked responses. This utility method makes the signature @@ -329,17 +348,16 @@ export class TechDocsAddonTester { * @see https://github.com/testing-library/react-testing-library/issues/281 * @see https://github.com/facebook/react/pull/14853 */ - async renderWithEffects(): Promise< - typeof screen & { shadowRoot: ShadowRoot | null } - > { + async renderWithEffects(): Promise<{ shadowRoot: ShadowRoot | null }> { await act(async () => { render(this.build()); }); - const shadowHost = await screen.findByTestId('techdocs-native-shadowroot'); + const shadowHost = await screen.findByShadowTestId( + 'techdocs-native-shadowroot', + ); return { - ...screen, shadowRoot: shadowHost?.shadowRoot || null, }; } diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index b461b653a8..05f4063c09 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,283 @@ # @backstage/plugin-techdocs-backend +## 2.1.7 + +### Patch Changes + +- 482ceed: Migrated from `assertError` to `toError` for error handling. +- 5e32f77: Migrated internal usage of the deprecated `catalogServiceRef` from `@backstage/plugin-catalog-node/alpha` to the stable `catalogServiceRef` from `@backstage/plugin-catalog-node`. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/errors@1.3.0 + - @backstage/catalog-model@1.8.0 + - @backstage/plugin-catalog-node@2.2.0 + - @backstage/plugin-techdocs-node@1.14.5 + - @backstage/catalog-client@1.15.0 + - @backstage/integration@2.0.1 + - @backstage/config@1.3.7 + +## 2.1.7-next.2 + +### Patch Changes + +- 482ceed: Migrated from `assertError` to `toError` for error handling. +- 5e32f77: Migrated internal usage of the deprecated `catalogServiceRef` from `@backstage/plugin-catalog-node/alpha` to the stable `catalogServiceRef` from `@backstage/plugin-catalog-node`. +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/plugin-catalog-node@2.2.0-next.2 + - @backstage/plugin-techdocs-node@1.14.5-next.2 + - @backstage/integration@2.0.1-next.0 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/catalog-client@1.14.1-next.0 + - @backstage/catalog-model@1.7.8-next.0 + - @backstage/config@1.3.7-next.0 + +## 2.1.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-techdocs-node@1.14.5-next.1 + +## 2.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/plugin-catalog-node@2.1.1-next.0 + - @backstage/plugin-techdocs-node@1.14.5-next.0 + - @backstage/catalog-client@1.14.0 + - @backstage/catalog-model@1.7.7 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration@2.0.0 + - @backstage/types@1.2.2 + +## 2.1.6 + +### Patch Changes + +- cb7c6b1: Added `techdocs.generator.mkdocs.dangerouslyAllowAdditionalKeys` configuration option to explicitly bypass MkDocs configuration key restrictions. This enables support for additional MkDocs configuration keys beyond the default safe allow list, such as the `hooks` key which some MkDocs plugins require. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/catalog-client@1.14.0 + - @backstage/plugin-techdocs-node@1.14.4 + - @backstage/integration@2.0.0 + - @backstage/plugin-catalog-node@2.1.0 + - @backstage/catalog-model@1.7.7 + +## 2.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/plugin-techdocs-node@1.14.4-next.2 + - @backstage/catalog-client@1.14.0-next.2 + - @backstage/integration@2.0.0-next.2 + - @backstage/plugin-catalog-node@2.1.0-next.2 + +## 2.1.6-next.1 + +### Patch Changes + +- cb7c6b1: Added `techdocs.generator.mkdocs.dangerouslyAllowAdditionalKeys` configuration option to explicitly bypass MkDocs configuration key restrictions. This enables support for additional MkDocs configuration keys beyond the default safe allow list, such as the `hooks` key which some MkDocs plugins require. +- Updated dependencies + - @backstage/catalog-client@1.14.0-next.1 + - @backstage/plugin-techdocs-node@1.14.3-next.1 + - @backstage/integration@2.0.0-next.1 + - @backstage/plugin-catalog-node@2.1.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 2.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/plugin-catalog-node@2.1.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-client@1.13.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-techdocs-node@1.14.3-next.0 + +## 2.1.5 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 5e3ef57: Added `peerModules` metadata declaring recommended modules for cross-plugin integrations. +- 8148621: Moved `@backstage/backend-defaults` from `dependencies` to `devDependencies`. +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/plugin-catalog-node@2.0.0 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-techdocs-node@1.14.2 + - @backstage/catalog-client@1.13.0 + +## 2.1.5-next.2 + +### Patch Changes + +- 8148621: Moved `@backstage/backend-defaults` from `dependencies` to `devDependencies`. +- Updated dependencies + - @backstage/integration@1.20.0-next.2 + - @backstage/plugin-catalog-node@2.0.0-next.1 + - @backstage/catalog-client@1.12.2-next.0 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/plugin-techdocs-node@1.14.2-next.1 + +## 2.1.5-next.1 + +### Patch Changes + +- 5e3ef57: Added `peerModules` metadata declaring recommended modules for cross-plugin integrations. +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/plugin-techdocs-node@1.14.2-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + - @backstage/backend-defaults@0.15.2-next.1 + +## 2.1.5-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/plugin-catalog-node@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/backend-defaults@0.15.1-next.0 + - @backstage/plugin-techdocs-node@1.14.1-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 2.1.4 + +### Patch Changes + +- b6ff2a5: Some AWS `publisher` config options such as `region`, `endpoint`, `accountId` are now marked as `@visibility backend` instead of `secret`. +- Updated dependencies + - @backstage/backend-defaults@0.15.0 + - @backstage/backend-plugin-api@1.6.1 + - @backstage/integration@1.19.2 + - @backstage/plugin-techdocs-node@1.14.0 + +## 2.1.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + +## 2.1.4-next.1 + +### Patch Changes + +- b6ff2a5: Some AWS `publisher` config options such as `region`, `endpoint`, `accountId` are now marked as `@visibility backend` instead of `secret`. +- Updated dependencies + - @backstage/backend-defaults@0.14.1-next.1 + - @backstage/integration@1.19.2-next.0 + - @backstage/plugin-techdocs-node@1.13.11-next.0 + +## 2.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.1-next.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration@1.19.0 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-techdocs-node@1.13.10 + +## 2.1.3 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 2bae83a: Corrected `ErrorCallback` type to work with Node 22 types +- 592361e: The `techdocs` config is now marked as optional. +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-defaults@0.14.0 + - @backstage/plugin-techdocs-node@1.13.10 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## 2.1.3-next.2 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 2bae83a: Corrected `ErrorCallback` type to work with Node 22 types +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/plugin-techdocs-node@1.13.10-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## 2.1.3-next.1 + +### Patch Changes + +- 592361e: The `techdocs` config is now marked as optional. + +## 2.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-techdocs-node@1.13.10-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 2.1.2 + +### Patch Changes + +- 71c22f3: Removed/moved unused dependencies +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-techdocs-node@1.13.9 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/catalog-client@1.12.1 + ## 2.1.2-next.1 ### Patch Changes diff --git a/plugins/techdocs-backend/config.d.ts b/plugins/techdocs-backend/config.d.ts index 4954392828..87369f2ccd 100644 --- a/plugins/techdocs-backend/config.d.ts +++ b/plugins/techdocs-backend/config.d.ts @@ -21,7 +21,7 @@ export interface Config { * Configuration options for the techdocs-backend plugin * @see http://backstage.io/docs/features/techdocs/configuration */ - techdocs: { + techdocs?: { /** * Documentation building process depends on the builder attr * @visibility frontend @@ -64,6 +64,18 @@ export interface Config { * List of mkdocs plugins which should be added as default to all mkdocs.yml files. */ defaultPlugins?: string[]; + + /** + * List of additional MkDocs configuration keys to allow beyond + * the default safe allowlist. This can introduce security vulnerabilities. + * + * WARNING: Some MkDocs configuration keys can execute arbitrary code. For example, the + * 'hooks' key allows running arbitrary Python code during documentation generation. + * Only use this in trusted environments where all mkdocs.yml files are audited. + * + * @see https://www.mkdocs.org/user-guide/configuration/#hooks + */ + dangerouslyAllowAdditionalKeys?: string[]; }; }; @@ -100,7 +112,6 @@ export interface Config { * If account ID is not set and no credentials are set, environment variables or aws config file will be used to authenticate. * @see https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/loading-node-credentials-environment.html * @see https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/loading-node-credentials-shared.html - * @visibility secret */ accountId?: string; /** @@ -141,14 +152,12 @@ export interface Config { * (Optional) AWS Region. * If not set, AWS_REGION environment variable or aws config file will be used. * @see https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-region.html - * @visibility secret */ region?: string; /** * (Optional) AWS Endpoint. * The endpoint URI to send requests to. The default endpoint is built from the configured region. * @see https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#constructor-property - * @visibility secret */ endpoint?: string; /** diff --git a/plugins/techdocs-backend/examples/documented-component-uffizzi/docs/code/code-sample.md b/plugins/techdocs-backend/examples/documented-component-uffizzi/docs/code/code-sample.md index f41ab1d506..d1172aaceb 100644 --- a/plugins/techdocs-backend/examples/documented-component-uffizzi/docs/code/code-sample.md +++ b/plugins/techdocs-backend/examples/documented-component-uffizzi/docs/code/code-sample.md @@ -10,7 +10,7 @@ const serviceEntityPage = ( - + diff --git a/plugins/techdocs-backend/examples/documented-component/docs/code/code-sample.md b/plugins/techdocs-backend/examples/documented-component/docs/code/code-sample.md index f41ab1d506..d1172aaceb 100644 --- a/plugins/techdocs-backend/examples/documented-component/docs/code/code-sample.md +++ b/plugins/techdocs-backend/examples/documented-component/docs/code/code-sample.md @@ -10,7 +10,7 @@ const serviceEntityPage = ( - + diff --git a/plugins/techdocs-backend/knip-report.md b/plugins/techdocs-backend/knip-report.md index 97d5b385fd..ba66c48402 100644 --- a/plugins/techdocs-backend/knip-report.md +++ b/plugins/techdocs-backend/knip-report.md @@ -1,3 +1,8 @@ # Knip report +## Unused dependencies (1) + +| Name | Location | Severity | +| :------------------------ | :---------------- | :------- | +| @backstage/catalog-client | package.json:65:6 | error | diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index c4c8d4d036..7677f02ad5 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-backend", - "version": "2.1.2-next.1", + "version": "2.1.7", "description": "The Backstage backend plugin that renders technical documentation for your components", "backstage": { "role": "backend-plugin", @@ -11,6 +11,9 @@ "@backstage/plugin-techdocs-common", "@backstage/plugin-techdocs-node", "@backstage/plugin-techdocs-react" + ], + "peerModules": [ + "@backstage/plugin-search-backend-module-techdocs" ] }, "publishConfig": { @@ -58,7 +61,6 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/backend-defaults": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", @@ -68,7 +70,7 @@ "@backstage/plugin-catalog-node": "workspace:^", "@backstage/plugin-techdocs-node": "workspace:^", "@backstage/types": "workspace:^", - "express": "^4.17.1", + "express": "^4.22.0", "express-promise-router": "^4.1.0", "fs-extra": "^11.2.0", "knex": "^3.0.0", diff --git a/plugins/techdocs-backend/src/DocsBuilder/builder.ts b/plugins/techdocs-backend/src/DocsBuilder/builder.ts index 446d692bad..f481b4e23c 100644 --- a/plugins/techdocs-backend/src/DocsBuilder/builder.ts +++ b/plugins/techdocs-backend/src/DocsBuilder/builder.ts @@ -19,7 +19,7 @@ import { stringifyEntityRef, } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; -import { assertError, isError } from '@backstage/errors'; +import { isError, toError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { GeneratorBase, @@ -30,9 +30,9 @@ import { PublisherBase, } from '@backstage/plugin-techdocs-node'; import fs from 'fs-extra'; -import os from 'os'; -import path from 'path'; -import { Writable } from 'stream'; +import os from 'node:os'; +import path from 'node:path'; +import { Writable } from 'node:stream'; import { Logger } from 'winston'; import { BuildMetadataStorage } from './BuildMetadataStorage'; import { TechDocsCache } from '../cache'; @@ -225,9 +225,8 @@ export class DocsBuilder { // Not a blocker hence no need to await this. fs.remove(preparedDir); } catch (error) { - assertError(error); this.logger.debug( - `Error removing prepared directory ${error.message}`, + `Error removing prepared directory ${toError(error).message}`, ); } } @@ -238,9 +237,8 @@ export class DocsBuilder { // Not a blocker hence no need to await this. fs.remove(outputDir); } catch (error) { - assertError(error); this.logger.debug( - `Error removing generated directory ${error.message}`, + `Error removing generated directory ${toError(error).message}`, ); } } diff --git a/plugins/techdocs-backend/src/cache/TechDocsCache.ts b/plugins/techdocs-backend/src/cache/TechDocsCache.ts index d5cbdaae36..9a321f6ed7 100644 --- a/plugins/techdocs-backend/src/cache/TechDocsCache.ts +++ b/plugins/techdocs-backend/src/cache/TechDocsCache.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { assertError, CustomErrorBase } from '@backstage/errors'; +import { CustomErrorBase, toError } from '@backstage/errors'; import { Config, readDurationFromConfig } from '@backstage/config'; import { CacheService, LoggerService } from '@backstage/backend-plugin-api'; import { durationToMilliseconds } from '@backstage/types'; @@ -77,9 +77,9 @@ export class TechDocsCache { this.logger.debug(`Cache miss: ${path}`); return response; } catch (e) { - assertError(e); - this.logger.warn(`Error getting cache entry ${path}: ${e.message}`); - this.logger.debug(e.message, e); + const error = toError(e); + this.logger.warn(`Error getting cache entry ${path}: ${error.message}`); + this.logger.debug(error.message, error); return undefined; } } diff --git a/plugins/techdocs-backend/src/cache/cacheMiddleware.ts b/plugins/techdocs-backend/src/cache/cacheMiddleware.ts index 78660d3c39..72f910a10a 100644 --- a/plugins/techdocs-backend/src/cache/cacheMiddleware.ts +++ b/plugins/techdocs-backend/src/cache/cacheMiddleware.ts @@ -23,7 +23,7 @@ type CacheMiddlewareOptions = { logger: LoggerService; }; -type ErrorCallback = (err?: Error) => void; +type ErrorCallback = (err?: Error | null) => void; export const createCacheMiddleware = ({ cache, diff --git a/plugins/techdocs-backend/src/plugin.ts b/plugins/techdocs-backend/src/plugin.ts index ac16e43dab..ad26967804 100644 --- a/plugins/techdocs-backend/src/plugin.ts +++ b/plugins/techdocs-backend/src/plugin.ts @@ -34,7 +34,7 @@ import { techdocsPreparerExtensionPoint, techdocsPublisherExtensionPoint, } from '@backstage/plugin-techdocs-node'; -import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha'; +import { catalogServiceRef } from '@backstage/plugin-catalog-node'; import * as winston from 'winston'; import { createRouter } from './service/router'; @@ -166,7 +166,7 @@ export const techdocsPlugin = createBackendPlugin({ discovery, httpAuth, auth, - catalogClient: catalog, + catalog, }), ); diff --git a/plugins/techdocs-backend/src/service/CachedEntityLoader.test.ts b/plugins/techdocs-backend/src/service/CachedEntityLoader.test.ts index df5c038dce..104efcefcf 100644 --- a/plugins/techdocs-backend/src/service/CachedEntityLoader.test.ts +++ b/plugins/techdocs-backend/src/service/CachedEntityLoader.test.ts @@ -16,13 +16,11 @@ import { CachedEntityLoader } from './CachedEntityLoader'; import { CompoundEntityRef } from '@backstage/catalog-model'; -import { mockServices } from '@backstage/backend-test-utils'; +import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; -import { BackstageCredentials } from '@backstage/backend-plugin-api'; describe('CachedEntityLoader', () => { const cache = mockServices.cache.mock(); - const auth = mockServices.auth.mock(); const entityName: CompoundEntityRef = { kind: 'component', @@ -39,23 +37,8 @@ describe('CachedEntityLoader', () => { }, }; - const token = 'test-token'; - - const userCredentials: BackstageCredentials = { - $$type: '@backstage/BackstageCredentials', - principal: { - type: 'user', - userEntityRef: 'user:default/test-user', - }, - }; - - const pluginCredentials: BackstageCredentials = { - $$type: '@backstage/BackstageCredentials', - principal: { - type: 'plugin', - subject: 'plugin:test-plugin', - }, - }; + const userCredentials = mockCredentials.user('user:default/test-user'); + const serviceCredentials = mockCredentials.service('plugin:test-plugin'); afterEach(() => { jest.resetAllMocks(); @@ -64,14 +47,17 @@ describe('CachedEntityLoader', () => { it('writes entities to cache for user credentials', async () => { cache.get.mockResolvedValue(undefined); const catalog = catalogServiceMock({ entities: [entity] }); - auth.isPrincipal.mockReturnValue(true); + jest.spyOn(catalog, 'getEntityByRef'); - const loader = new CachedEntityLoader({ auth, catalog, cache }); - const result = await loader.load(userCredentials, entityName, token); + const loader = new CachedEntityLoader({ catalog, cache }); + const result = await loader.load(userCredentials, entityName); expect(result).toEqual(entity); + expect(catalog.getEntityByRef).toHaveBeenCalledWith(entityName, { + credentials: userCredentials, + }); expect(cache.set).toHaveBeenCalledWith( - 'catalog:component:default/test:user:default/test-user', + `catalog:component:default/test:${userCredentials}`, entity, { ttl: 5000 }, ); @@ -81,10 +67,9 @@ describe('CachedEntityLoader', () => { const catalog = catalogServiceMock(); jest.spyOn(catalog, 'getEntityByRef'); cache.get.mockResolvedValue(entity); - auth.isPrincipal.mockReturnValue(true); - const loader = new CachedEntityLoader({ auth, catalog, cache }); - const result = await loader.load(userCredentials, entityName, token); + const loader = new CachedEntityLoader({ catalog, cache }); + const result = await loader.load(userCredentials, entityName); expect(result).toEqual(entity); expect(catalog.getEntityByRef).not.toHaveBeenCalled(); @@ -93,33 +78,14 @@ describe('CachedEntityLoader', () => { it('does not cache missing entities', async () => { const catalog = catalogServiceMock({ entities: [] }); cache.get.mockResolvedValue(undefined); - auth.isPrincipal.mockReturnValue(true); - const loader = new CachedEntityLoader({ auth, catalog, cache }); - const result = await loader.load(userCredentials, entityName, token); + const loader = new CachedEntityLoader({ catalog, cache }); + const result = await loader.load(userCredentials, entityName); expect(result).toBeUndefined(); expect(cache.set).not.toHaveBeenCalled(); }); - it('uses entity ref as cache key for service credentials', async () => { - const catalog = catalogServiceMock({ entities: [entity] }); - cache.get.mockResolvedValue(undefined); - auth.isPrincipal.mockReturnValueOnce(false).mockReturnValueOnce(true); - - const loader = new CachedEntityLoader({ auth, catalog, cache }); - const result = await loader.load(pluginCredentials, entityName, undefined); - - expect(result).toEqual(entity); - expect(cache.set).toHaveBeenCalledWith( - 'catalog:component:default/test:plugin:test-plugin', - entity, - { - ttl: 5000, - }, - ); - }); - it('calls the catalog if the cache read takes too long', async () => { cache.get.mockImplementation( () => @@ -128,80 +94,48 @@ describe('CachedEntityLoader', () => { }), ); const catalog = catalogServiceMock({ entities: [entity] }); - auth.isPrincipal.mockReturnValue(true); - const loader = new CachedEntityLoader({ auth, catalog, cache }); - const result = await loader.load(userCredentials, entityName, token); + const loader = new CachedEntityLoader({ catalog, cache }); + const result = await loader.load(userCredentials, entityName); expect(result).toEqual(entity); }); - it('creates different cache keys for different users', async () => { + it('creates different cache keys for different credentials', async () => { const catalog = catalogServiceMock({ entities: [entity] }); cache.get.mockResolvedValue(undefined); - auth.isPrincipal.mockReturnValue(true); - const loader = new CachedEntityLoader({ auth, catalog, cache }); + const loader = new CachedEntityLoader({ catalog, cache }); - const anotherUserCredentials: BackstageCredentials = { - $$type: '@backstage/BackstageCredentials', - principal: { - type: 'user', - userEntityRef: 'user:default/another-user', - }, - }; + const anotherUserCredentials = mockCredentials.user( + 'user:default/another-user', + ); - await loader.load(userCredentials, entityName, token); - await loader.load(anotherUserCredentials, entityName, token); + await loader.load(userCredentials, entityName); + await loader.load(anotherUserCredentials, entityName); expect(cache.set).toHaveBeenCalledWith( - 'catalog:component:default/test:user:default/test-user', + `catalog:component:default/test:${userCredentials}`, entity, { ttl: 5000 }, ); expect(cache.set).toHaveBeenCalledWith( - 'catalog:component:default/test:user:default/another-user', + `catalog:component:default/test:${anotherUserCredentials}`, entity, { ttl: 5000 }, ); }); - it('creates cache key with service subject for service credentials', async () => { + it('uses service credentials as cache key for service credentials', async () => { const catalog = catalogServiceMock({ entities: [entity] }); cache.get.mockResolvedValue(undefined); - auth.isPrincipal.mockReturnValueOnce(false).mockReturnValueOnce(true); - const loader = new CachedEntityLoader({ auth, catalog, cache }); - const result = await loader.load(pluginCredentials, entityName, token); + const loader = new CachedEntityLoader({ catalog, cache }); + const result = await loader.load(serviceCredentials, entityName); expect(result).toEqual(entity); expect(cache.set).toHaveBeenCalledWith( - 'catalog:component:default/test:plugin:test-plugin', - entity, - { ttl: 5000 }, - ); - expect(auth.isPrincipal).toHaveBeenCalledWith(pluginCredentials, 'user'); - expect(auth.isPrincipal).toHaveBeenCalledWith(pluginCredentials, 'service'); - }); - - it('handles credentials that are neither user nor service', async () => { - const catalog = catalogServiceMock({ entities: [entity] }); - cache.get.mockResolvedValue(undefined); - auth.isPrincipal.mockReturnValue(false); - - const unknownCredentials: BackstageCredentials = { - $$type: '@backstage/BackstageCredentials', - principal: { - type: 'unknown' as any, - }, - }; - - const loader = new CachedEntityLoader({ auth, catalog, cache }); - const result = await loader.load(unknownCredentials, entityName, token); - - expect(result).toEqual(entity); - expect(cache.set).toHaveBeenCalledWith( - 'catalog:component:default/test', + `catalog:component:default/test:${serviceCredentials}`, entity, { ttl: 5000 }, ); diff --git a/plugins/techdocs-backend/src/service/CachedEntityLoader.ts b/plugins/techdocs-backend/src/service/CachedEntityLoader.ts index 9f97ed2cd4..9672f14fdc 100644 --- a/plugins/techdocs-backend/src/service/CachedEntityLoader.ts +++ b/plugins/techdocs-backend/src/service/CachedEntityLoader.ts @@ -15,31 +15,27 @@ */ import { - AuthService, BackstageCredentials, CacheService, } from '@backstage/backend-plugin-api'; -import { CatalogApi } from '@backstage/catalog-client'; import { Entity, CompoundEntityRef, stringifyEntityRef, } from '@backstage/catalog-model'; +import { CatalogService } from '@backstage/plugin-catalog-node'; -export type CachedEntityLoaderOptions = { - auth: AuthService; - catalog: CatalogApi; +type CachedEntityLoaderOptions = { + catalog: CatalogService; cache: CacheService; }; export class CachedEntityLoader { - private readonly auth: AuthService; - private readonly catalog: CatalogApi; + private readonly catalog: CatalogService; private readonly cache: CacheService; private readonly readTimeout = 1000; - constructor({ auth, catalog, cache }: CachedEntityLoaderOptions) { - this.auth = auth; + constructor({ catalog, cache }: CachedEntityLoaderOptions) { this.catalog = catalog; this.cache = cache; } @@ -47,7 +43,6 @@ export class CachedEntityLoader { async load( credentials: BackstageCredentials, entityRef: CompoundEntityRef, - token: string | undefined, ): Promise { const cacheKey = this.getCacheKey(entityRef, credentials); let result = await this.getFromCache(cacheKey); @@ -56,7 +51,7 @@ export class CachedEntityLoader { return result; } - result = await this.catalog.getEntityByRef(entityRef, { token }); + result = await this.catalog.getEntityByRef(entityRef, { credentials }); if (result) { this.cache.set(cacheKey, result, { ttl: 5000 }); @@ -78,14 +73,10 @@ export class CachedEntityLoader { entityName: CompoundEntityRef, credentials: BackstageCredentials, ): string { - const key = ['catalog', stringifyEntityRef(entityName)]; - - if (this.auth.isPrincipal(credentials, 'user')) { - key.push(credentials.principal.userEntityRef); - } else if (this.auth.isPrincipal(credentials, 'service')) { - key.push(credentials.principal.subject); - } - - return key.join(':'); + return [ + 'catalog', + stringifyEntityRef(entityName), + String(credentials), // these have a well defined toString method + ].join(':'); } } diff --git a/plugins/techdocs-backend/src/service/DocsSynchronizer.test.ts b/plugins/techdocs-backend/src/service/DocsSynchronizer.test.ts index 201f93dbf9..c1d29abfd7 100644 --- a/plugins/techdocs-backend/src/service/DocsSynchronizer.test.ts +++ b/plugins/techdocs-backend/src/service/DocsSynchronizer.test.ts @@ -21,7 +21,7 @@ import { PreparerBuilder, PublisherBase, } from '@backstage/plugin-techdocs-node'; -import { PassThrough } from 'stream'; +import { PassThrough } from 'node:stream'; import * as winston from 'winston'; import { TechDocsCache } from '../cache'; import { DocsBuilder, shouldCheckForUpdate } from '../DocsBuilder'; diff --git a/plugins/techdocs-backend/src/service/DocsSynchronizer.ts b/plugins/techdocs-backend/src/service/DocsSynchronizer.ts index 21192b29e7..0104ac7500 100644 --- a/plugins/techdocs-backend/src/service/DocsSynchronizer.ts +++ b/plugins/techdocs-backend/src/service/DocsSynchronizer.ts @@ -20,7 +20,7 @@ import { stringifyEntityRef, } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; -import { assertError, NotFoundError } from '@backstage/errors'; +import { NotFoundError, toError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { GeneratorBuilder, @@ -28,7 +28,7 @@ import { PublisherBase, } from '@backstage/plugin-techdocs-node'; import pLimit, { Limit } from 'p-limit'; -import { PassThrough } from 'stream'; +import { PassThrough } from 'node:stream'; import * as winston from 'winston'; import { TechDocsCache } from '../cache'; import { @@ -146,13 +146,13 @@ export class DocsSynchronizer { return; } } catch (e) { - assertError(e); + const buildError = toError(e); const msg = `Failed to build the docs page for entity ${stringifyEntityRef( entity, - )}: ${e.message}`; + )}: ${buildError.message}`; taskLogger.error(msg); - this.logger.error(msg, e); - error(e); + this.logger.error(msg, buildError); + error(buildError); return; } @@ -241,10 +241,9 @@ export class DocsSynchronizer { finish({ updated: false }); } } catch (e) { - assertError(e); // In case of error, log and allow the user to go about their business. this.logger.error( - `Error syncing cache for ${entityTripletPath}: ${e.message}`, + `Error syncing cache for ${entityTripletPath}: ${toError(e).message}`, ); finish({ updated: false }); } finally { diff --git a/plugins/techdocs-backend/src/service/router.test.ts b/plugins/techdocs-backend/src/service/router.test.ts index 89c1009c3f..40e8fe450d 100644 --- a/plugins/techdocs-backend/src/service/router.test.ts +++ b/plugins/techdocs-backend/src/service/router.test.ts @@ -28,8 +28,8 @@ import { CachedEntityLoader } from './CachedEntityLoader'; import { createEventStream, createRouter, RouterOptions } from './router'; import { TechDocsCache } from '../cache'; import { mockErrorHandler, mockServices } from '@backstage/backend-test-utils'; +import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; -jest.mock('@backstage/catalog-client'); jest.mock('./CachedEntityLoader'); jest.mock('./DocsSynchronizer'); jest.mock('../cache/TechDocsCache'); @@ -107,6 +107,7 @@ describe('createRouter', () => { const docsBuildStrategy: jest.Mocked = { shouldBuild: jest.fn(), }; + const mockCatalogService = catalogServiceMock(); const outOfTheBoxOptions = { preparers, generators, @@ -124,6 +125,7 @@ describe('createRouter', () => { docsBuildStrategy, auth: mockServices.auth(), httpAuth: mockServices.httpAuth(), + catalog: mockCatalogService, }; const recommendedOptions = { publisher, @@ -134,6 +136,7 @@ describe('createRouter', () => { docsBuildStrategy, auth: mockServices.auth(), httpAuth: mockServices.httpAuth(), + catalog: mockCatalogService, }; beforeEach(() => { diff --git a/plugins/techdocs-backend/src/service/router.ts b/plugins/techdocs-backend/src/service/router.ts index 4312e1a5af..30a279ea27 100644 --- a/plugins/techdocs-backend/src/service/router.ts +++ b/plugins/techdocs-backend/src/service/router.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { CatalogApi, CatalogClient } from '@backstage/catalog-client'; import { stringifyEntityRef } from '@backstage/catalog-model'; import { Config, readDurationFromConfig } from '@backstage/config'; import { NotFoundError } from '@backstage/errors'; @@ -41,6 +40,7 @@ import { HttpAuthService, LoggerService, } from '@backstage/backend-plugin-api'; +import { CatalogService } from '@backstage/plugin-catalog-node'; import { durationToMilliseconds } from '@backstage/types'; /** @@ -60,7 +60,7 @@ export type OutOfTheBoxDeploymentOptions = { cache: CacheService; docsBuildStrategy?: DocsBuildStrategy; buildLogTransport?: winston.transport; - catalogClient?: CatalogApi; + catalog: CatalogService; httpAuth: HttpAuthService; auth: AuthService; }; @@ -79,7 +79,7 @@ export type RecommendedDeploymentOptions = { cache: CacheService; docsBuildStrategy?: DocsBuildStrategy; buildLogTransport?: winston.transport; - catalogClient?: CatalogApi; + catalog: CatalogService; httpAuth: HttpAuthService; auth: AuthService; }; @@ -114,10 +114,9 @@ export async function createRouter( options: RouterOptions, ): Promise { const router = Router(); - const { publisher, config, logger, discovery, httpAuth, auth } = options; + const { publisher, config, logger, discovery, httpAuth, auth, catalog } = + options; - const catalogClient = - options.catalogClient ?? new CatalogClient({ discoveryApi: discovery }); const docsBuildStrategy = options.docsBuildStrategy ?? DefaultDocsBuildStrategy.fromConfig(config); const buildLogTransport = options.buildLogTransport; @@ -125,8 +124,7 @@ export async function createRouter( // Entities are cached to optimize the /static/docs request path, which can be called many times // when loading a single techdocs page. const entityLoader = new CachedEntityLoader({ - auth, - catalog: catalogClient, + catalog, cache: options.cache, }); @@ -163,13 +161,8 @@ export async function createRouter( const credentials = await httpAuth.credentials(req); - const { token } = await auth.getPluginRequestToken({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); - // Verify that the related entity exists and the current user has permission to view it. - const entity = await entityLoader.load(credentials, entityName, token); + const entity = await entityLoader.load(credentials, entityName); if (!entity) { throw new NotFoundError( @@ -202,12 +195,7 @@ export async function createRouter( const credentials = await httpAuth.credentials(req); - const { token } = await auth.getPluginRequestToken({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); - - const entity = await entityLoader.load(credentials, entityName, token); + const entity = await entityLoader.load(credentials, entityName); if (!entity) { throw new NotFoundError( @@ -240,17 +228,12 @@ export async function createRouter( const credentials = await httpAuth.credentials(req); - const { token } = await auth.getPluginRequestToken({ - onBehalfOf: credentials, - targetPluginId: 'catalog', + const entity = await entityLoader.load(credentials, { + kind, + namespace, + name, }); - const entity = await entityLoader.load( - credentials, - { kind, namespace, name }, - token, - ); - if (!entity?.metadata?.uid) { throw new NotFoundError('Entity metadata UID missing'); } @@ -315,12 +298,7 @@ export async function createRouter( allowLimitedAccess: true, }); - const { token } = await auth.getPluginRequestToken({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); - - const entity = await entityLoader.load(credentials, entityName, token); + const entity = await entityLoader.load(credentials, entityName); if (!entity) { throw new NotFoundError( diff --git a/plugins/techdocs-module-addons-contrib/CHANGELOG.md b/plugins/techdocs-module-addons-contrib/CHANGELOG.md index 56027da723..195bf480f3 100644 --- a/plugins/techdocs-module-addons-contrib/CHANGELOG.md +++ b/plugins/techdocs-module-addons-contrib/CHANGELOG.md @@ -1,5 +1,223 @@ # @backstage/plugin-techdocs-module-addons-contrib +## 1.1.35 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/core-components@0.18.9 + - @backstage/integration@2.0.1 + - @backstage/core-plugin-api@1.12.5 + - @backstage/integration-react@1.2.17 + - @backstage/plugin-techdocs-react@1.3.10 + +## 1.1.35-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.9-next.1 + - @backstage/integration@2.0.1-next.0 + - @backstage/core-plugin-api@1.12.5-next.2 + - @backstage/frontend-plugin-api@0.16.0-next.2 + - @backstage/plugin-techdocs-react@1.3.10-next.2 + - @backstage/integration-react@1.2.17-next.1 + +## 1.1.35-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-techdocs-react@1.3.10-next.1 + +## 1.1.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.9-next.0 + - @backstage/frontend-plugin-api@0.15.2-next.0 + - @backstage/integration-react@1.2.17-next.0 + - @backstage/plugin-techdocs-react@1.3.10-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + - @backstage/integration@2.0.0 + +## 1.1.34 + +### Patch Changes + +- 895563a: Avoid enabling the TechDocs LightBox addon for images wrapped in links, so image links keep working. +- Updated dependencies + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/integration@2.0.0 + - @backstage/integration-react@1.2.16 + - @backstage/plugin-techdocs-react@1.3.9 + +## 1.1.34-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/integration@2.0.0-next.2 + - @backstage/core-components@0.18.8-next.1 + - @backstage/plugin-techdocs-react@1.3.9-next.1 + +## 1.1.34-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/integration-react@1.2.16-next.1 + - @backstage/plugin-techdocs-react@1.3.9-next.0 + +## 1.1.34-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/integration@1.21.0-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/integration-react@1.2.16-next.0 + - @backstage/plugin-techdocs-react@1.3.9-next.0 + +## 1.1.33 + +### Patch Changes + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/core-components@0.18.7 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-techdocs-react@1.3.8 + - @backstage/integration-react@1.2.15 + +## 1.1.33-next.2 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/integration@1.20.0-next.2 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/integration-react@1.2.15-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-techdocs-react@1.3.8-next.1 + +## 1.1.33-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/core-components@0.18.7-next.1 + - @backstage/plugin-techdocs-react@1.3.8-next.0 + - @backstage/integration-react@1.2.15-next.1 + +## 1.1.33-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.6-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/plugin-techdocs-react@1.3.8-next.0 + - @backstage/integration-react@1.2.15-next.0 + +## 1.1.32 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + - @backstage/integration@1.19.2 + - @backstage/integration-react@1.2.14 + - @backstage/plugin-techdocs-react@1.3.7 + +## 1.1.32-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-techdocs-react@1.3.7-next.0 + +## 1.1.32-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + - @backstage/integration-react@1.2.14-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## 1.1.31 + +### Patch Changes + +- 8d6709e: Updated tests to match test-utils change +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-techdocs-react@1.3.6 + +## 1.1.31-next.1 + +### Patch Changes + +- 8d6709e: Updated tests to match test-utils change +- Updated dependencies + - @backstage/core-components@0.18.4-next.1 + +## 1.1.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/core-components@0.18.4-next.0 + +## 1.1.30 + +### Patch Changes + +- 6929480: ExpandableCollapse Techdocs Addon was breaking native sidebar collapse on Firefox +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/integration@1.18.2 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-techdocs-react@1.3.5 + - @backstage/integration-react@1.2.12 + ## 1.1.30-next.1 ### Patch Changes diff --git a/plugins/techdocs-module-addons-contrib/package.json b/plugins/techdocs-module-addons-contrib/package.json index 6fd5682b43..d0b0dae848 100644 --- a/plugins/techdocs-module-addons-contrib/package.json +++ b/plugins/techdocs-module-addons-contrib/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-module-addons-contrib", - "version": "1.1.30-next.1", + "version": "1.1.35", "description": "Plugin module for contributed TechDocs Addons", "backstage": { "role": "frontend-plugin-module", @@ -75,13 +75,14 @@ "@types/react": "^18.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2", + "shadow-dom-testing-library": "^1.13.1" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/plugins/techdocs-module-addons-contrib/src/ExpandableNavigation/ExpandableNavigation.test.tsx b/plugins/techdocs-module-addons-contrib/src/ExpandableNavigation/ExpandableNavigation.test.tsx index 90a3fa7378..c4d07df7ee 100644 --- a/plugins/techdocs-module-addons-contrib/src/ExpandableNavigation/ExpandableNavigation.test.tsx +++ b/plugins/techdocs-module-addons-contrib/src/ExpandableNavigation/ExpandableNavigation.test.tsx @@ -17,6 +17,7 @@ import { TechDocsAddonTester } from '@backstage/plugin-techdocs-addons-test-utils'; import { fireEvent, waitFor } from '@testing-library/react'; +import { screen } from 'shadow-dom-testing-library'; import { ExpandableNavigation } from '../plugin'; import { entityPresentationApiRef } from '@backstage/plugin-catalog-react'; @@ -94,25 +95,24 @@ describe('ExpandableNavigation', () => { }); it('renders without exploding', async () => { - const { getByRole } = await TechDocsAddonTester.buildAddonsInTechDocs([ + await TechDocsAddonTester.buildAddonsInTechDocs([]) + .withDom(mockNavWithSublevels) + .withApis([[entityPresentationApiRef, entityPresentationApiMock]]) + .renderWithEffects(); + + expect( + screen.getByShadowRole('button', { name: 'expand-nav' }), + ).toBeInTheDocument(); + }); + + it('expands and collapses navigation', async () => { + const { shadowRoot } = await TechDocsAddonTester.buildAddonsInTechDocs([ , ]) .withDom(mockNavWithSublevels) .withApis([[entityPresentationApiRef, entityPresentationApiMock]]) .renderWithEffects(); - expect(getByRole('button', { name: 'expand-nav' })).toBeInTheDocument(); - }); - - it('expands and collapses navigation', async () => { - const { getByRole, shadowRoot } = - await TechDocsAddonTester.buildAddonsInTechDocs([ - , - ]) - .withDom(mockNavWithSublevels) - .withApis([[entityPresentationApiRef, entityPresentationApiMock]]) - .renderWithEffects(); - const toggles = shadowRoot!.querySelectorAll('.md-toggle'); @@ -121,18 +121,24 @@ describe('ExpandableNavigation', () => { expect(item).not.toBeChecked(); }); - const expandButton = getByRole('button', { name: 'expand-nav' }); + const expandButton = screen.getByShadowRole('button', { + name: 'expand-nav', + }); fireEvent.click(expandButton); await waitFor(() => { - expect(getByRole('button', { name: 'collapse-nav' })).toBeInTheDocument(); + expect( + screen.getByShadowRole('button', { name: 'collapse-nav' }), + ).toBeInTheDocument(); toggles.forEach(item => { expect(item).toBeChecked(); }); }); - const collapseButton = getByRole('button', { name: 'collapse-nav' }); + const collapseButton = screen.getByShadowRole('button', { + name: 'collapse-nav', + }); fireEvent.click(collapseButton); @@ -144,15 +150,13 @@ describe('ExpandableNavigation', () => { }); it('does not render when navigation has no sublevels', async () => { - const { queryByRole } = await TechDocsAddonTester.buildAddonsInTechDocs([ - , - ]) + await TechDocsAddonTester.buildAddonsInTechDocs([]) .withDom(mockNavWithoutSublevels) .withApis([[entityPresentationApiRef, entityPresentationApiMock]]) .renderWithEffects(); expect( - queryByRole('button', { name: 'expand-nav' }), + screen.queryByShadowRole('button', { name: 'expand-nav' }), ).not.toBeInTheDocument(); }); }); diff --git a/plugins/techdocs-module-addons-contrib/src/LightBox/LightBox.test.tsx b/plugins/techdocs-module-addons-contrib/src/LightBox/LightBox.test.tsx index 7171629d89..b4084c6411 100644 --- a/plugins/techdocs-module-addons-contrib/src/LightBox/LightBox.test.tsx +++ b/plugins/techdocs-module-addons-contrib/src/LightBox/LightBox.test.tsx @@ -15,6 +15,7 @@ */ import { TechDocsAddonTester } from '@backstage/plugin-techdocs-addons-test-utils'; +import { screen } from 'shadow-dom-testing-library'; import { LightBox } from '../plugin'; import { entityPresentationApiRef } from '@backstage/plugin-catalog-react'; @@ -30,20 +31,16 @@ describe('LightBox', () => { }); it('renders without exploding', async () => { - const { getByText } = await TechDocsAddonTester.buildAddonsInTechDocs([ - , - ]) + await TechDocsAddonTester.buildAddonsInTechDocs([]) .withDom(TEST_CONTENT) .withApis([[entityPresentationApiRef, entityPresentationApiMock]]) .renderWithEffects(); - expect(getByText('TEST_CONTENT')).toBeInTheDocument(); + expect(screen.getByShadowText('TEST_CONTENT')).toBeInTheDocument(); }); it('Add onclick event to images', async () => { - const { getByTestId } = await TechDocsAddonTester.buildAddonsInTechDocs([ - , - ]) + await TechDocsAddonTester.buildAddonsInTechDocs([]) .withDom( { .withApis([[entityPresentationApiRef, entityPresentationApiMock]]) .renderWithEffects(); - expect(getByTestId('fixture').onclick).not.toBeUndefined(); - expect(getByTestId('fixture').onclick).toEqual(expect.any(Function)); + expect(screen.getByShadowTestId('fixture').onclick).not.toBeUndefined(); + expect(screen.getByShadowTestId('fixture').onclick).toEqual( + expect.any(Function), + ); + }); + + it('does not add onclick event to linked images', async () => { + await TechDocsAddonTester.buildAddonsInTechDocs([]) + .withDom( + , + ) + .withApis([[entityPresentationApiRef, entityPresentationApiMock]]) + .renderWithEffects(); + + expect(screen.getByShadowTestId('linked-fixture').onclick).toBeNull(); + expect( + screen.getByShadowTestId('linked-indirect-fixture').onclick, + ).toBeNull(); + expect(screen.getByShadowTestId('plain-fixture').onclick).toEqual( + expect.any(Function), + ); }); }); diff --git a/plugins/techdocs-module-addons-contrib/src/LightBox/LightBox.tsx b/plugins/techdocs-module-addons-contrib/src/LightBox/LightBox.tsx index 47409f47c7..89fe842a15 100644 --- a/plugins/techdocs-module-addons-contrib/src/LightBox/LightBox.tsx +++ b/plugins/techdocs-module-addons-contrib/src/LightBox/LightBox.tsx @@ -27,6 +27,7 @@ export const LightBoxAddon = () => { useEffect(() => { let dataSourceImages: DataSource | null = null; + const lightboxImages = images.filter(image => !image.closest('a')); let lightbox: PhotoSwipeLightbox | null = new PhotoSwipeLightbox({ pswpModule: PhotoSwipe, @@ -57,10 +58,10 @@ export const LightBoxAddon = () => { `, }); - images.forEach((image, index) => { + lightboxImages.forEach((image, index) => { image.onclick = () => { if (dataSourceImages === null) { - dataSourceImages = images.map(dataSourceImage => { + dataSourceImages = lightboxImages.map(dataSourceImage => { return { element: dataSourceImage, src: dataSourceImage.src, diff --git a/plugins/techdocs-module-addons-contrib/src/ReportIssue/ReportIssue.test.tsx b/plugins/techdocs-module-addons-contrib/src/ReportIssue/ReportIssue.test.tsx index 1ee9e87e05..153c1c603f 100644 --- a/plugins/techdocs-module-addons-contrib/src/ReportIssue/ReportIssue.test.tsx +++ b/plugins/techdocs-module-addons-contrib/src/ReportIssue/ReportIssue.test.tsx @@ -17,6 +17,7 @@ import { TechDocsAddonTester } from '@backstage/plugin-techdocs-addons-test-utils'; import { fireEvent, waitFor } from '@testing-library/react'; +import { screen } from 'shadow-dom-testing-library'; import { scmIntegrationsApiRef } from '@backstage/integration-react'; import { ReportIssue } from '../plugin'; @@ -67,58 +68,57 @@ describe('ReportIssue', () => { it('renders github link without exploding', async () => { byUrl.mockReturnValue({ type: 'github' }); - const { shadowRoot, getByText } = - await TechDocsAddonTester.buildAddonsInTechDocs([ - , - ]) - .withDom( - - - -
    -
    -
    -
    + const { shadowRoot } = await TechDocsAddonTester.buildAddonsInTechDocs([ + , + ]) + .withDom( + + + +
    +
    +
    +
    -
    - + - - , - ) - .withApis([ - [scmIntegrationsApiRef, { byUrl }], - [entityPresentationApiRef, entityPresentationApiMock], - ]) - .renderWithEffects(); +
    + + , + ) + .withApis([ + [scmIntegrationsApiRef, { byUrl }], + [entityPresentationApiRef, entityPresentationApiMock], + ]) + .renderWithEffects(); (shadowRoot as ShadowRoot & Pick).getSelection = () => selection; await waitFor(() => { - expect(getByText('Edit page')).toBeInTheDocument(); + expect(screen.getByShadowText('Edit page')).toBeInTheDocument(); }); fireSelectionChangeEvent(window); await waitFor(() => { - const link = getByText('Open new Github issue'); + const link = screen.getByShadowText('Open new Github issue'); expect(link).toHaveAttribute( 'href', 'https://github.com/backstage/backstage/issues/new?title=Documentation%20feedback%3A%20his%20&body=%23%23%20Documentation%20Feedback%20%F0%9F%93%9D%0A%0A%20%23%23%23%23%20The%20highlighted%20text%3A%20%0A%0A%20%3E%20his%0A%0A%20%23%23%23%23%20The%20comment%20on%20the%20text%3A%20%0A%20_%3Ereplace%20this%20line%20with%20your%20comment%3C_%0A%0A%20___%0ABackstage%20URL%3A%20%3Chttp%3A%2F%2Flocalhost%2F%3E%20%0AMarkdown%20URL%3A%20%3Chttps%3A%2F%2Fgithub.com%2Fbackstage%2Fbackstage%2Fblob%2Fmaster%2Fdocs%2FREADME.md%3E', @@ -128,60 +128,61 @@ describe('ReportIssue', () => { it('renders gitlab link without exploding', async () => { byUrl.mockReturnValue({ type: 'gitlab' }); - const { shadowRoot, getByText, queryByTestId } = - await TechDocsAddonTester.buildAddonsInTechDocs([ - , - ]) - .withDom( - - - -
    -
    -
    -
    + const { shadowRoot } = await TechDocsAddonTester.buildAddonsInTechDocs([ + , + ]) + .withDom( + + + +
    +
    +
    +
    -
    - + - - , - ) - .withApis([ - [scmIntegrationsApiRef, { byUrl }], - [entityPresentationApiRef, entityPresentationApiMock], - ]) - .renderWithEffects(); +
    + + , + ) + .withApis([ + [scmIntegrationsApiRef, { byUrl }], + [entityPresentationApiRef, entityPresentationApiMock], + ]) + .renderWithEffects(); (shadowRoot as ShadowRoot & Pick).getSelection = () => selection; await waitFor(() => { - expect(getByText('Edit page')).toBeInTheDocument(); + expect(screen.getByShadowText('Edit page')).toBeInTheDocument(); }); fireSelectionChangeEvent(window); await waitFor(() => { - expect(queryByTestId('report-issue-addon')).toBeInTheDocument(); + expect( + screen.getByShadowTestId('report-issue-addon'), + ).toBeInTheDocument(); - const link = getByText('Open new Gitlab issue'); + const link = screen.getByShadowText('Open new Gitlab issue'); expect(link).toHaveAttribute( 'href', 'https://gitlab.com/backstage/backstage/issues/new?issue[title]=Documentation%20feedback%3A%20his%20&issue[description]=%23%23%20Documentation%20Feedback%20%F0%9F%93%9D%0A%0A%20%23%23%23%23%20The%20highlighted%20text%3A%20%0A%0A%20%3E%20his%0A%0A%20%23%23%23%23%20The%20comment%20on%20the%20text%3A%20%0A%20_%3Ereplace%20this%20line%20with%20your%20comment%3C_%0A%0A%20___%0ABackstage%20URL%3A%20%3Chttp%3A%2F%2Flocalhost%2F%3E%20%0AMarkdown%20URL%3A%20%3Chttps%3A%2F%2Fgitlab.com%2Fbackstage%2Fbackstage%2F-%2Fblob%2Fmaster%2Fdocs%2FREADME.md%3E', @@ -197,60 +198,61 @@ describe('ReportIssue', () => { body: options.selection.toString().trim(), }); - const { shadowRoot, getByText, queryByTestId } = - await TechDocsAddonTester.buildAddonsInTechDocs([ - , - ]) - .withDom( - - - -
    -
    -
    -
    + const { shadowRoot } = await TechDocsAddonTester.buildAddonsInTechDocs([ + , + ]) + .withDom( + + + +
    +
    +
    +
    -
    - + - - , - ) - .withApis([ - [scmIntegrationsApiRef, { byUrl }], - [entityPresentationApiRef, entityPresentationApiMock], - ]) - .renderWithEffects(); +
    + + , + ) + .withApis([ + [scmIntegrationsApiRef, { byUrl }], + [entityPresentationApiRef, entityPresentationApiMock], + ]) + .renderWithEffects(); (shadowRoot as ShadowRoot & Pick).getSelection = () => selection; await waitFor(() => { - expect(getByText('Edit page')).toBeInTheDocument(); + expect(screen.getByShadowText('Edit page')).toBeInTheDocument(); }); fireSelectionChangeEvent(window); await waitFor(() => { - expect(queryByTestId('report-issue-addon')).toBeInTheDocument(); + expect( + screen.getByShadowTestId('report-issue-addon'), + ).toBeInTheDocument(); - const link = getByText('Open new Gitlab issue'); + const link = screen.getByShadowText('Open new Gitlab issue'); expect(link).toHaveAttribute( 'href', 'https://gitlab.com/backstage/backstage/issues/new?issue[title]=Custom&issue[description]=his', @@ -261,45 +263,46 @@ describe('ReportIssue', () => { it('does not render report issue link for unsupported repository type', async () => { byUrl.mockReturnValue({ type: 'gerrit', resource: 'gerrit.example.com' }); - const { shadowRoot, getByText, queryByTestId } = - await TechDocsAddonTester.buildAddonsInTechDocs([ - , - ]) - .withDom( - - - -
    -
    - + const { shadowRoot } = await TechDocsAddonTester.buildAddonsInTechDocs([ + , + ]) + .withDom( + + + +
    + - - , - ) - .withApis([[scmIntegrationsApiRef, { byUrl }]]) - .renderWithEffects(); +
    + + , + ) + .withApis([[scmIntegrationsApiRef, { byUrl }]]) + .renderWithEffects(); (shadowRoot as ShadowRoot & Pick).getSelection = () => selection; await waitFor(() => { - expect(getByText('Edit page')).toBeInTheDocument(); + expect(screen.getByShadowText('Edit page')).toBeInTheDocument(); }); fireSelectionChangeEvent(window); await waitFor(() => { - expect(queryByTestId('report-issue-addon')).not.toBeInTheDocument(); + expect( + screen.queryByShadowTestId('report-issue-addon'), + ).not.toBeInTheDocument(); }); }); }); diff --git a/plugins/techdocs-module-addons-contrib/src/TextSize/TextSize.test.tsx b/plugins/techdocs-module-addons-contrib/src/TextSize/TextSize.test.tsx index 92cab255a7..03571c1cd1 100644 --- a/plugins/techdocs-module-addons-contrib/src/TextSize/TextSize.test.tsx +++ b/plugins/techdocs-module-addons-contrib/src/TextSize/TextSize.test.tsx @@ -16,6 +16,7 @@ import { TechDocsAddonTester } from '@backstage/plugin-techdocs-addons-test-utils'; import { act, fireEvent, waitFor } from '@testing-library/react'; +import { screen } from 'shadow-dom-testing-library'; import { TextSize } from '../plugin'; import { useShadowRootElements } from '@backstage/plugin-techdocs-react'; import { entityPresentationApiRef } from '@backstage/plugin-catalog-react'; @@ -42,33 +43,30 @@ describe('TextSize', () => { }); it('renders without exploding', async () => { - const { getByText } = await TechDocsAddonTester.buildAddonsInTechDocs([ - , - ]) + await TechDocsAddonTester.buildAddonsInTechDocs([]) .withDom(TEST_CONTENT) .withApis([[entityPresentationApiRef, entityPresentationApiMock]]) .renderWithEffects(); - expect(getByText('TEST_CONTENT')).toBeInTheDocument(); + expect(screen.getByShadowText('TEST_CONTENT')).toBeInTheDocument(); }); it('changes content text size using slider', async () => { - const { getByTitle, getByText, getByRole, getByDisplayValue } = - await TechDocsAddonTester.buildAddonsInTechDocs([]) - .withDom(TEST_CONTENT) - .withApis([[entityPresentationApiRef, entityPresentationApiMock]]) - .renderWithEffects(); + await TechDocsAddonTester.buildAddonsInTechDocs([]) + .withDom(TEST_CONTENT) + .withApis([[entityPresentationApiRef, entityPresentationApiMock]]) + .renderWithEffects(); - const content = getByText('TEST_CONTENT'); + const content = screen.getByShadowText('TEST_CONTENT'); useShadowRootElementsMock.mockReturnValue([content]); - fireEvent.click(getByTitle('Settings')); + fireEvent.click(screen.getByShadowTitle('Settings')); await waitFor(() => { - expect(getByText('Text size')).toBeInTheDocument(); + expect(screen.getByShadowText('Text size')).toBeInTheDocument(); }); - const slider = getByRole('slider'); + const slider = screen.getByShadowRole('slider'); act(() => { slider.focus(); @@ -79,12 +77,12 @@ describe('TextSize', () => { }); await waitFor(() => { - expect(getByDisplayValue('115')).toBeInTheDocument(); + expect(screen.getByShadowDisplayValue('115')).toBeInTheDocument(); }); expect(slider).toHaveTextContent('115%'); - let style = window.getComputedStyle(getByText('TEST_CONTENT')); + let style = window.getComputedStyle(screen.getByShadowText('TEST_CONTENT')); await waitFor(() => { expect(style.getPropertyValue('--md-typeset-font-size')).toBe('18.4px'); @@ -95,60 +93,54 @@ describe('TextSize', () => { }); await waitFor(() => { - expect(getByDisplayValue('100')).toBeInTheDocument(); + expect(screen.getByShadowDisplayValue('100')).toBeInTheDocument(); }); expect(slider).toHaveTextContent('100%'); - style = window.getComputedStyle(getByText('TEST_CONTENT')); + style = window.getComputedStyle(screen.getByShadowText('TEST_CONTENT')); expect(style.getPropertyValue('--md-typeset-font-size')).toBe('16px'); }); it('changes content text size using buttons', async () => { - const { - getByTitle, - getByText, - getByRole, - getByLabelText, - getByDisplayValue, - } = await TechDocsAddonTester.buildAddonsInTechDocs([]) + await TechDocsAddonTester.buildAddonsInTechDocs([]) .withDom(TEST_CONTENT) .withApis([[entityPresentationApiRef, entityPresentationApiMock]]) .renderWithEffects(); - const content = getByText('TEST_CONTENT'); + const content = screen.getByShadowText('TEST_CONTENT'); useShadowRootElementsMock.mockReturnValue([content]); - fireEvent.click(getByTitle('Settings')); + fireEvent.click(screen.getByShadowTitle('Settings')); await waitFor(() => { - expect(getByText('Text size')).toBeInTheDocument(); + expect(screen.getByShadowText('Text size')).toBeInTheDocument(); }); - fireEvent.click(getByLabelText('Increase text size')); + fireEvent.click(screen.getByShadowLabelText('Increase text size')); await waitFor(() => { - expect(getByDisplayValue('115')).toBeInTheDocument(); + expect(screen.getByShadowDisplayValue('115')).toBeInTheDocument(); }); - const slider = getByRole('slider'); + const slider = screen.getByShadowRole('slider'); expect(slider).toHaveTextContent('115%'); - let style = window.getComputedStyle(getByText('TEST_CONTENT')); + let style = window.getComputedStyle(screen.getByShadowText('TEST_CONTENT')); expect(style.getPropertyValue('--md-typeset-font-size')).toBe('18.4px'); - fireEvent.click(getByLabelText('Decrease text size')); + fireEvent.click(screen.getByShadowLabelText('Decrease text size')); await waitFor(() => { - expect(getByDisplayValue('100')).toBeInTheDocument(); + expect(screen.getByShadowDisplayValue('100')).toBeInTheDocument(); }); expect(slider).toHaveTextContent('100%'); - style = window.getComputedStyle(getByText('TEST_CONTENT')); + style = window.getComputedStyle(screen.getByShadowText('TEST_CONTENT')); expect(style.getPropertyValue('--md-typeset-font-size')).toBe('16px'); }); diff --git a/plugins/techdocs-module-addons-contrib/src/plugin.ts b/plugins/techdocs-module-addons-contrib/src/plugin.ts index ac19d682de..6ed09a7db9 100644 --- a/plugins/techdocs-module-addons-contrib/src/plugin.ts +++ b/plugins/techdocs-module-addons-contrib/src/plugin.ts @@ -211,6 +211,8 @@ export const TextSize = techdocsModuleAddonsContribPlugin.provide( * @remarks * The image size of the lightbox image is the same as the image size on the document page. * + * Images that are wrapped in links are ignored to avoid blocking navigation. + * * @example * Here's a simple example: * ``` diff --git a/plugins/techdocs-node/CHANGELOG.md b/plugins/techdocs-node/CHANGELOG.md index ff5eda772c..49f8e39e17 100644 --- a/plugins/techdocs-node/CHANGELOG.md +++ b/plugins/techdocs-node/CHANGELOG.md @@ -1,5 +1,227 @@ # @backstage/plugin-techdocs-node +## 1.14.5 + +### Patch Changes + +- 482ceed: Migrated from `assertError` to `toError` for error handling. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0 + - @backstage/errors@1.3.0 + - @backstage/catalog-model@1.8.0 + - @backstage/integration@2.0.1 + - @backstage/config@1.3.7 + - @backstage/integration-aws-node@0.1.21 + - @backstage/plugin-search-common@1.2.23 + +## 1.14.5-next.2 + +### Patch Changes + +- 482ceed: Migrated from `assertError` to `toError` for error handling. +- Updated dependencies + - @backstage/errors@1.3.0-next.0 + - @backstage/integration@2.0.1-next.0 + - @backstage/backend-plugin-api@1.9.0-next.2 + - @backstage/catalog-model@1.7.8-next.0 + - @backstage/config@1.3.7-next.0 + - @backstage/integration-aws-node@0.1.21-next.0 + - @backstage/plugin-search-common@1.2.23-next.0 + +## 1.14.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + +## 1.14.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.8.1-next.0 + - @backstage/catalog-model@1.7.7 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration@2.0.0 + - @backstage/integration-aws-node@0.1.20 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-techdocs-common@0.1.1 + +## 1.14.4 + +### Patch Changes + +- cb7c6b1: Added `techdocs.generator.mkdocs.dangerouslyAllowAdditionalKeys` configuration option to explicitly bypass MkDocs configuration key restrictions. This enables support for additional MkDocs configuration keys beyond the default safe allow list, such as the `hooks` key which some MkDocs plugins require. +- e96f6d9: Removed `INHERIT` from the `ALLOWED_MKDOCS_KEYS` set to address a security concern with MkDocs configuration inheritance. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0 + - @backstage/integration@2.0.0 + - @backstage/catalog-model@1.7.7 + +## 1.14.4-next.2 + +### Patch Changes + +- e96f6d9: Removed `INHERIT` from the `ALLOWED_MKDOCS_KEYS` set to address a security concern with MkDocs configuration inheritance. +- Updated dependencies + - @backstage/backend-plugin-api@1.8.0-next.1 + - @backstage/integration@2.0.0-next.2 + +## 1.14.3-next.1 + +### Patch Changes + +- cb7c6b1: Added `techdocs.generator.mkdocs.dangerouslyAllowAdditionalKeys` configuration option to explicitly bypass MkDocs configuration key restrictions. This enables support for additional MkDocs configuration keys beyond the default safe allow list, such as the `hooks` key which some MkDocs plugins require. +- Updated dependencies + - @backstage/integration@2.0.0-next.1 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.20 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-techdocs-common@0.1.1 + +## 1.14.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.21.0-next.0 + - @backstage/backend-plugin-api@1.7.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.20 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-techdocs-common@0.1.1 + +## 1.14.2 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- 3c455d4: Some security fixes +- Updated dependencies + - @backstage/integration@1.20.0 + - @backstage/integration-aws-node@0.1.20 + - @backstage/backend-plugin-api@1.7.0 + - @backstage/plugin-search-common@1.2.22 + +## 1.14.2-next.1 + +### Patch Changes + +- 3c455d4: Some security fixes +- Updated dependencies + - @backstage/integration@1.20.0-next.1 + - @backstage/backend-plugin-api@1.7.0-next.1 + +## 1.14.1-next.0 + +### Patch Changes + +- 7455dae: Use node prefix on native imports +- Updated dependencies + - @backstage/integration-aws-node@0.1.20-next.0 + - @backstage/backend-plugin-api@1.7.0-next.0 + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-techdocs-common@0.1.1 + +## 1.14.0 + +### Minor Changes + +- 63c459c: **BREAKING:** It's now possible to use the credentials from the `integrations.awsS3` config to authenticate with AWS S3. The new priority is: + + 1. `aws.accounts` + 2. `techdocs.publisher.awsS3.credentials` + 3. `integrations.awsS3` + 4. Default credential chain + + In case of multiple `integrations.awsS3` are present, the target integration is determined by the `accessKeyId` in `techdocs.publisher.awsS3.credentials` if provided. Otherwise, the default credential chain is used. + + This means that depending on your setup, this feature may break your existing setup. + In general: + + - if you are configuring `aws.accounts`, no action is required + - if you are configuring `techdocs.publisher.awsS3.credentials`, no action is required + - if you are configuring multiple integrations under `integrations.awsS3`, no action is required + - if you are configuring a single integration under `integrations.awsS3`, make sure that the integration has access to the bucket you are using for TechDocs + +### Patch Changes + +- f0951aa: Updated the `defaultDockerImage` to reflect the latest TechDocs Container version of v1.2.8 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.1 + - @backstage/integration@1.19.2 + +## 1.13.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + +## 1.13.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 703f8c0: There was an issue in the uploading of large size files to the AWS S3. We have modified the logic by adding retry along with multipart uploading functionality. +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + +## 1.13.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 703f8c0: There was an issue in the uploading of large size files to the AWS S3. We have modified the logic by adding retry along with multipart uploading functionality. +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.19 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-techdocs-common@0.1.1 + +## 1.13.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-techdocs-common@0.1.1 + +## 1.13.9 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/plugin-search-common@1.2.21 + ## 1.13.9-next.1 ### Patch Changes diff --git a/plugins/techdocs-node/package.json b/plugins/techdocs-node/package.json index d0a1d69ea9..584ab8a0d4 100644 --- a/plugins/techdocs-node/package.json +++ b/plugins/techdocs-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-node", - "version": "1.13.9-next.1", + "version": "1.14.5", "description": "Common node.js functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli", "backstage": { "role": "node-library", @@ -66,7 +66,7 @@ "@trendyol-js/openstack-swift-sdk": "^0.0.7", "@types/express": "^4.17.6", "dockerode": "^4.0.0", - "express": "^4.17.1", + "express": "^4.22.0", "fs-extra": "^11.2.0", "git-url-parse": "^15.0.0", "hpagent": "^1.2.0", diff --git a/plugins/techdocs-node/report.api.md b/plugins/techdocs-node/report.api.md index 0e208edff1..bc2657a1fe 100644 --- a/plugins/techdocs-node/report.api.md +++ b/plugins/techdocs-node/report.api.md @@ -16,7 +16,7 @@ import { ScmIntegrationRegistry } from '@backstage/integration'; import { StorageOptions } from '@google-cloud/storage'; import { UrlReaderService } from '@backstage/backend-plugin-api'; import * as winston from 'winston'; -import { Writable } from 'stream'; +import { Writable } from 'node:stream'; // @public export class DirectoryPreparer implements PreparerBase { @@ -309,7 +309,7 @@ export class TechdocsGenerator implements GeneratorBase { config: Config; scmIntegrations: ScmIntegrationRegistry; }); - static readonly defaultDockerImage = 'spotify/techdocs:v1.2.6'; + static readonly defaultDockerImage = 'spotify/techdocs:v1.2.8'; static fromConfig( config: Config, options: GeneratorOptions, diff --git a/plugins/techdocs-node/src/helpers.test.ts b/plugins/techdocs-node/src/helpers.test.ts index aa1aa69d30..abde894ca7 100644 --- a/plugins/techdocs-node/src/helpers.test.ts +++ b/plugins/techdocs-node/src/helpers.test.ts @@ -17,9 +17,9 @@ import { Entity, getEntitySourceLocation } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; -import os from 'os'; -import path from 'path'; -import { Readable } from 'stream'; +import os from 'node:os'; +import path from 'node:path'; +import { Readable } from 'node:stream'; import { TECHDOCS_ANNOTATION } from '@backstage/plugin-techdocs-common'; import { getDocFilesFromRepository, diff --git a/plugins/techdocs-node/src/helpers.ts b/plugins/techdocs-node/src/helpers.ts index f1655aa9a6..354d05fb24 100644 --- a/plugins/techdocs-node/src/helpers.ts +++ b/plugins/techdocs-node/src/helpers.ts @@ -27,7 +27,7 @@ import { import { InputError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { TECHDOCS_ANNOTATION } from '@backstage/plugin-techdocs-common'; -import path from 'path'; +import path from 'node:path'; import { PreparerResponse, RemoteProtocol } from './stages/prepare/types'; /** diff --git a/plugins/techdocs-node/src/stages/generate/DockerContainerRunner.test.ts b/plugins/techdocs-node/src/stages/generate/DockerContainerRunner.test.ts index 4a9e364af3..72a708722b 100644 --- a/plugins/techdocs-node/src/stages/generate/DockerContainerRunner.test.ts +++ b/plugins/techdocs-node/src/stages/generate/DockerContainerRunner.test.ts @@ -15,7 +15,7 @@ */ import fs from 'fs-extra'; -import Stream, { PassThrough } from 'stream'; +import Stream, { PassThrough } from 'node:stream'; import { DockerContainerRunner, UserOptions } from './DockerContainerRunner'; import { createMockDirectory } from '@backstage/backend-test-utils'; diff --git a/plugins/techdocs-node/src/stages/generate/DockerContainerRunner.ts b/plugins/techdocs-node/src/stages/generate/DockerContainerRunner.ts index 076504308c..d7d9ffe364 100644 --- a/plugins/techdocs-node/src/stages/generate/DockerContainerRunner.ts +++ b/plugins/techdocs-node/src/stages/generate/DockerContainerRunner.ts @@ -17,11 +17,11 @@ import Docker from 'dockerode'; import fs from 'fs-extra'; import { ForwardedError } from '@backstage/errors'; -import { PassThrough } from 'stream'; -import { pipeline as pipelineStream } from 'stream'; -import { promisify } from 'util'; +import { PassThrough } from 'node:stream'; +import { pipeline as pipelineStream } from 'node:stream'; +import { promisify } from 'node:util'; import { TechDocsContainerRunner } from './types'; -import { Writable } from 'stream'; +import { Writable } from 'node:stream'; const pipeline = promisify(pipelineStream); diff --git a/plugins/techdocs-node/src/stages/generate/__fixtures__/mkdocs_with_duplicate_merge_hooks.yml b/plugins/techdocs-node/src/stages/generate/__fixtures__/mkdocs_with_duplicate_merge_hooks.yml new file mode 100644 index 0000000000..145412e730 --- /dev/null +++ b/plugins/techdocs-node/src/stages/generate/__fixtures__/mkdocs_with_duplicate_merge_hooks.yml @@ -0,0 +1,12 @@ +# This file uses duplicate merge keys (<<) which causes parser differential: +# - js-yaml: hooks: null (first merge key wins) +# - PyYAML: hooks: [hello.py] (last merge key wins) +site_name: Test +plugins: + - techdocs-core +array_base: &array_base + hooks: [hello.py] +null_base: &null_base + hooks: null +<<: *null_base +<<: *array_base diff --git a/plugins/techdocs-node/src/stages/generate/__fixtures__/mkdocs_with_hooks.yml b/plugins/techdocs-node/src/stages/generate/__fixtures__/mkdocs_with_hooks.yml new file mode 100644 index 0000000000..333ba6db36 --- /dev/null +++ b/plugins/techdocs-node/src/stages/generate/__fixtures__/mkdocs_with_hooks.yml @@ -0,0 +1,5 @@ +site_name: Test site name +site_description: Test site description +hooks: + - hooks/my_hook.py + - hooks/another_hook.py diff --git a/plugins/techdocs-node/src/stages/generate/__fixtures__/mkdocs_with_merge_key_hooks.yml b/plugins/techdocs-node/src/stages/generate/__fixtures__/mkdocs_with_merge_key_hooks.yml new file mode 100644 index 0000000000..f3b5dbdc93 --- /dev/null +++ b/plugins/techdocs-node/src/stages/generate/__fixtures__/mkdocs_with_merge_key_hooks.yml @@ -0,0 +1,8 @@ +site_name: Test +plugins: + - techdocs-core +extra: + array_hooks: &array_hooks + hooks: + - custom_hook.py +<<: *array_hooks diff --git a/plugins/techdocs-node/src/stages/generate/__fixtures__/mkdocs_with_parser_differential_hooks.yml b/plugins/techdocs-node/src/stages/generate/__fixtures__/mkdocs_with_parser_differential_hooks.yml new file mode 100644 index 0000000000..42f574dcaf --- /dev/null +++ b/plugins/techdocs-node/src/stages/generate/__fixtures__/mkdocs_with_parser_differential_hooks.yml @@ -0,0 +1,16 @@ +# This file intentionally uses duplicate merge keys (<<) which is technically +# invalid YAML, but is accepted by most parsers with different behaviors: +# - js-yaml: uses the first merge key value (hooks: null) +# - PyYAML: uses the last merge key value (hooks: [custom_hook.py]) +# This tests that sanitization catches hooks regardless of parser interpretation. +site_name: Test +plugins: + - techdocs-core +extra: + null_hooks: &null_hooks + hooks: null + array_hooks: &array_hooks + hooks: + - custom_hook.py +<<: *null_hooks +<<: *array_hooks diff --git a/plugins/techdocs-node/src/stages/generate/helpers.test.ts b/plugins/techdocs-node/src/stages/generate/helpers.test.ts index af61dae936..1f198b43e9 100644 --- a/plugins/techdocs-node/src/stages/generate/helpers.test.ts +++ b/plugins/techdocs-node/src/stages/generate/helpers.test.ts @@ -21,7 +21,7 @@ import { mockServices, } from '@backstage/backend-test-utils'; import fs from 'fs-extra'; -import path, { resolve as resolvePath } from 'path'; +import path, { resolve as resolvePath } from 'node:path'; import { ParsedLocationAnnotation } from '../../helpers'; import { createOrUpdateMetadata, @@ -30,11 +30,13 @@ import { getRepoUrlFromLocationAnnotation, patchIndexPreBuild, storeEtagMetadata, + validateDocsDirectory, validateMkdocsYaml, } from './helpers'; import { patchMkdocsYmlPreBuild, patchMkdocsYmlWithPlugins, + sanitizeMkdocsYml, } from './mkdocsPatchers'; import yaml from 'js-yaml'; @@ -92,6 +94,24 @@ const mkdocsYmlWithAdditionalPluginsWithConfig = fs.readFileSync( const mkdocsYmlWithEnvTag = fs.readFileSync( resolvePath(__filename, '../__fixtures__/mkdocs_with_env_tag.yml'), ); +const mkdocsYmlWithHooks = fs.readFileSync( + resolvePath(__filename, '../__fixtures__/mkdocs_with_hooks.yml'), +); +const mkdocsYmlWithMergeKeyHooks = fs.readFileSync( + resolvePath(__filename, '../__fixtures__/mkdocs_with_merge_key_hooks.yml'), +); +const mkdocsYmlWithParserDifferentialHooks = fs.readFileSync( + resolvePath( + __filename, + '../__fixtures__/mkdocs_with_parser_differential_hooks.yml', + ), +); +const mkdocsYmlWithDuplicateMergeHooks = fs.readFileSync( + resolvePath( + __filename, + '../__fixtures__/mkdocs_with_duplicate_merge_hooks.yml', + ), +); const mockLogger = mockServices.logger.mock(); const warn = jest.spyOn(mockLogger, 'warn'); @@ -441,8 +461,6 @@ describe('helpers', () => { }; expect(parsedYml.plugins).toHaveLength(4); expect(parsedYml.plugins).toContain('techdocs-core'); - // we want our original object with its properties to be preserved, and for the basic string form of the plugin - // to NOT be added as well. expect(parsedYml.plugins).not.toContain('custom-plugin'); expect(parsedYml.plugins).toContainEqual({ 'custom-plugin': { with: { configuration: 1 } }, @@ -727,4 +745,413 @@ describe('helpers', () => { ).resolves.toBeUndefined(); }); }); + + describe('sanitizeMkdocsYml', () => { + beforeEach(() => { + warn.mockClear(); + mockDir.setContent({ + 'mkdocs_with_hooks.yml': mkdocsYmlWithHooks, + 'mkdocs.yml': mkdocsYml, + }); + }); + + it('should remove disallowed keys from mkdocs.yml and log a warning', async () => { + await sanitizeMkdocsYml( + mockDir.resolve('mkdocs_with_hooks.yml'), + mockLogger, + ); + + const updatedMkdocsYml = await fs.readFile( + mockDir.resolve('mkdocs_with_hooks.yml'), + ); + const parsedYml = yaml.load(updatedMkdocsYml.toString()) as { + hooks?: string[]; + site_name: string; + }; + expect(parsedYml.hooks).toBeUndefined(); + expect(parsedYml.site_name).toBe('Test site name'); + expect(warn).toHaveBeenCalledWith( + expect.stringContaining( + 'Removed the following unsupported configuration keys from mkdocs.yml: hooks', + ), + ); + }); + + it('should not modify mkdocs.yml when no disallowed keys are present', async () => { + await sanitizeMkdocsYml(mockDir.resolve('mkdocs.yml'), mockLogger); + + const updatedMkdocsYml = await fs.readFile(mockDir.resolve('mkdocs.yml')); + const parsedYml = yaml.load(updatedMkdocsYml.toString()) as { + hooks?: string[]; + site_name: string; + }; + expect(parsedYml.hooks).toBeUndefined(); + expect(parsedYml.site_name).toBe('Test site name'); + expect(warn).not.toHaveBeenCalled(); + }); + + it('should remove multiple disallowed keys and list them all in the warning', async () => { + const mkdocsWithMultipleDisallowed = `site_name: Test +hooks: + - hook.py +some_unknown_key: value +another_unknown: true +`; + mockDir.setContent({ + 'mkdocs_multiple.yml': mkdocsWithMultipleDisallowed, + }); + + await sanitizeMkdocsYml( + mockDir.resolve('mkdocs_multiple.yml'), + mockLogger, + ); + + const updatedMkdocsYml = await fs.readFile( + mockDir.resolve('mkdocs_multiple.yml'), + ); + const parsedYml = yaml.load(updatedMkdocsYml.toString()) as Record< + string, + unknown + >; + expect(parsedYml.hooks).toBeUndefined(); + expect(parsedYml.some_unknown_key).toBeUndefined(); + expect(parsedYml.another_unknown).toBeUndefined(); + expect(parsedYml.site_name).toBe('Test'); + expect(warn).toHaveBeenCalledWith( + expect.stringMatching( + /Removed the following unsupported configuration keys.*hooks.*some_unknown_key.*another_unknown|Removed the following unsupported configuration keys.*hooks.*another_unknown.*some_unknown_key/, + ), + ); + }); + + it('should remove hooks introduced via YAML merge keys', async () => { + mockDir.setContent({ + 'mkdocs_merge_keys.yml': mkdocsYmlWithMergeKeyHooks, + }); + + await sanitizeMkdocsYml( + mockDir.resolve('mkdocs_merge_keys.yml'), + mockLogger, + ); + + const updatedMkdocsYml = await fs.readFile( + mockDir.resolve('mkdocs_merge_keys.yml'), + ); + const parsedYml = yaml.load(updatedMkdocsYml.toString()) as Record< + string, + unknown + >; + + expect(parsedYml.hooks).toBeUndefined(); + expect(parsedYml.site_name).toBe('Test'); + expect(parsedYml.plugins).toEqual(['techdocs-core']); + expect(updatedMkdocsYml.toString()).not.toContain('<<:'); + + expect(warn).toHaveBeenCalledWith( + expect.stringContaining( + 'Removed the following unsupported configuration keys from mkdocs.yml: hooks', + ), + ); + }); + + it('should remove hooks when parsers interpret duplicate merge keys differently', async () => { + mockDir.setContent({ + 'mkdocs_parser_diff.yml': mkdocsYmlWithParserDifferentialHooks, + }); + + await sanitizeMkdocsYml( + mockDir.resolve('mkdocs_parser_diff.yml'), + mockLogger, + ); + + const updatedMkdocsYml = await fs.readFile( + mockDir.resolve('mkdocs_parser_diff.yml'), + ); + const parsedYml = yaml.load(updatedMkdocsYml.toString()) as Record< + string, + unknown + >; + + expect(parsedYml.hooks).toBeUndefined(); + expect(parsedYml.site_name).toBe('Test'); + expect(parsedYml.plugins).toEqual(['techdocs-core']); + expect(parsedYml.extra).toBeDefined(); + expect(updatedMkdocsYml.toString()).not.toContain('<<:'); + + expect(warn).toHaveBeenCalledWith( + expect.stringContaining( + 'Removed the following unsupported configuration keys from mkdocs.yml: hooks', + ), + ); + }); + + it('should remove the INHERIT key to prevent loading unsanitized parent configs', async () => { + const mkdocsWithInherit = `INHERIT: ../parent.yml +site_name: Test +`; + mockDir.setContent({ + 'mkdocs_inherit.yml': mkdocsWithInherit, + }); + + await sanitizeMkdocsYml( + mockDir.resolve('mkdocs_inherit.yml'), + mockLogger, + ); + + const updatedMkdocsYml = await fs.readFile( + mockDir.resolve('mkdocs_inherit.yml'), + ); + const parsedYml = yaml.load(updatedMkdocsYml.toString()) as Record< + string, + unknown + >; + + expect(parsedYml.INHERIT).toBeUndefined(); + expect(parsedYml.site_name).toBe('Test'); + expect(warn).toHaveBeenCalledWith(expect.stringContaining('INHERIT')); + }); + + it('should remove hooks with duplicate merge keys and top-level anchors', async () => { + mockDir.setContent({ + 'mkdocs_duplicate_merge.yml': mkdocsYmlWithDuplicateMergeHooks, + }); + + await sanitizeMkdocsYml( + mockDir.resolve('mkdocs_duplicate_merge.yml'), + mockLogger, + ); + + const updatedMkdocsYml = await fs.readFile( + mockDir.resolve('mkdocs_duplicate_merge.yml'), + ); + const parsedYml = yaml.load(updatedMkdocsYml.toString()) as Record< + string, + unknown + >; + + expect(parsedYml.hooks).toBeUndefined(); + expect(parsedYml.site_name).toBe('Test'); + expect(parsedYml.plugins).toEqual(['techdocs-core']); + expect(updatedMkdocsYml.toString()).not.toContain('<<:'); + + expect(warn).toHaveBeenCalledWith(expect.stringContaining('hooks')); + }); + + it('should allow additional keys when configured via dangerouslyAllowAdditionalKeys', async () => { + const mkdocsWithHooksAndCustom = `site_name: Test +hooks: + - hook.py +custom_dir: custom +some_unknown_key: value +`; + mockDir.setContent({ + 'mkdocs_allowed.yml': mkdocsWithHooksAndCustom, + }); + + // Allow 'hooks' and 'custom_dir' but not 'some_unknown_key' + await sanitizeMkdocsYml( + mockDir.resolve('mkdocs_allowed.yml'), + mockLogger, + ['hooks', 'custom_dir'], + ); + + const updatedMkdocsYml = await fs.readFile( + mockDir.resolve('mkdocs_allowed.yml'), + ); + const parsedYml = yaml.load(updatedMkdocsYml.toString()) as Record< + string, + unknown + >; + + // 'hooks' and 'custom_dir' should be preserved + expect(parsedYml.hooks).toEqual(['hook.py']); + expect(parsedYml.custom_dir).toBe('custom'); + // 'some_unknown_key' should still be removed + expect(parsedYml.some_unknown_key).toBeUndefined(); + expect(parsedYml.site_name).toBe('Test'); + + // Should warn about the dangerous configuration AND the removed key + expect(warn).toHaveBeenCalledWith( + expect.stringContaining( + 'DANGEROUS: Allowing additional MkDocs configuration keys beyond the default safe allowlist: hooks, custom_dir', + ), + ); + expect(warn).toHaveBeenCalledWith( + expect.stringContaining( + 'Removed the following unsupported configuration keys from mkdocs.yml: some_unknown_key', + ), + ); + }); + + it('should warn about dangerous keys even when no keys are removed', async () => { + mockDir.setContent({ + 'mkdocs_with_hooks.yml': mkdocsYmlWithHooks, + }); + + await sanitizeMkdocsYml( + mockDir.resolve('mkdocs_with_hooks.yml'), + mockLogger, + ['hooks'], + ); + + const updatedMkdocsYml = await fs.readFile( + mockDir.resolve('mkdocs_with_hooks.yml'), + ); + const parsedYml = yaml.load(updatedMkdocsYml.toString()) as { + hooks?: string[]; + site_name: string; + }; + + // Hooks should be preserved + expect(parsedYml.hooks).toBeDefined(); + expect(parsedYml.site_name).toBe('Test site name'); + + // Should warn about dangerous configuration + expect(warn).toHaveBeenCalledWith( + expect.stringContaining( + 'DANGEROUS: Allowing additional MkDocs configuration keys beyond the default safe allowlist: hooks', + ), + ); + }); + }); + + describe('validateDocsDirectory', () => { + it('should pass for a valid docs directory with no symlinks', async () => { + mockDir.setContent({ + docs: { + 'index.md': 'Hello', + 'guide.md': 'Guide content', + }, + }); + + await expect( + validateDocsDirectory(mockDir.resolve('docs'), mockDir.path), + ).resolves.toBeUndefined(); + }); + + it('should pass for symlinks pointing within the input directory', async () => { + mockDir.setContent({ + docs: { + 'index.md': 'Hello', + }, + 'other.md': 'Other content', + }); + + // Create a symlink within the input directory + await fs.symlink( + mockDir.resolve('other.md'), + mockDir.resolve('docs/link.md'), + ); + + await expect( + validateDocsDirectory(mockDir.resolve('docs'), mockDir.path), + ).resolves.toBeUndefined(); + }); + + it('should reject symlinks pointing outside the input directory', async () => { + const anotherMockDir = createMockDirectory(); + + mockDir.setContent({ + docs: { + 'index.md': 'Hello', + }, + }); + + anotherMockDir.setContent({ + tmp: { + secret: 'password', + }, + }); + + // Create a symlink pointing outside the input directory + await fs.symlink( + anotherMockDir.resolve('tmp/secret'), + mockDir.resolve('docs/escape.md'), + ); + + await expect( + validateDocsDirectory(mockDir.resolve('docs'), mockDir.path), + ).rejects.toThrow(/not allowed to refer to a location outside/i); + }); + + it('should reject symlinks to sensitive files like /etc/passwd', async () => { + mockDir.setContent({ + docs: { + 'index.md': 'Hello', + }, + }); + + // Create a symlink to /etc/passwd + await fs.symlink('/etc/passwd', mockDir.resolve('docs/passwd.md')); + + await expect( + validateDocsDirectory(mockDir.resolve('docs'), mockDir.path), + ).rejects.toThrow(/not allowed to refer to a location outside/i); + }); + + it('should reject symlinks in nested directories', async () => { + mockDir.setContent({ + docs: { + 'index.md': 'Hello', + nested: { + 'page.md': 'Nested page', + }, + }, + }); + + // Create a symlink in a nested directory pointing outside + await fs.symlink('/etc/passwd', mockDir.resolve('docs/nested/escape.md')); + + await expect( + validateDocsDirectory(mockDir.resolve('docs'), mockDir.path), + ).rejects.toThrow(/not allowed to refer to a location outside/i); + }); + + it('should reject directory symlinks pointing outside', async () => { + const anotherMockDir = createMockDirectory(); + + mockDir.setContent({ + docs: { + 'index.md': 'Hello', + }, + }); + + anotherMockDir.setContent({ + tmp: { + secret: 'password', + }, + }); + + // Create a directory symlink pointing outside + await fs.symlink( + anotherMockDir.path, + mockDir.resolve('docs/external-dir'), + ); + + await expect( + validateDocsDirectory(mockDir.resolve('docs'), mockDir.path), + ).rejects.toThrow(/not allowed to refer to a location outside/i); + }); + + it('should pass for directory symlinks within input directory', async () => { + mockDir.setContent({ + docs: { + 'index.md': 'Hello', + }, + assets: { + 'image.png': 'binary content', + }, + }); + + // Create a directory symlink within input directory + await fs.symlink( + mockDir.resolve('assets'), + mockDir.resolve('docs/assets'), + ); + + await expect( + validateDocsDirectory(mockDir.resolve('docs'), mockDir.path), + ).resolves.toBeUndefined(); + }); + }); }); diff --git a/plugins/techdocs-node/src/stages/generate/helpers.ts b/plugins/techdocs-node/src/stages/generate/helpers.ts index 0930ec89c1..59e51ba9a6 100644 --- a/plugins/techdocs-node/src/stages/generate/helpers.ts +++ b/plugins/techdocs-node/src/stages/generate/helpers.ts @@ -16,14 +16,14 @@ import { isChildPath, LoggerService } from '@backstage/backend-plugin-api'; import { Entity } from '@backstage/catalog-model'; -import { assertError, ForwardedError } from '@backstage/errors'; +import { ForwardedError, NotAllowedError, toError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; -import { SpawnOptionsWithoutStdio, spawn } from 'child_process'; +import { SpawnOptionsWithoutStdio, spawn } from 'node:child_process'; import fs from 'fs-extra'; import gitUrlParse from 'git-url-parse'; import yaml, { DEFAULT_SCHEMA, Type } from 'js-yaml'; -import path, { resolve as resolvePath } from 'path'; -import { PassThrough, Writable } from 'stream'; +import path, { resolve as resolvePath } from 'node:path'; +import { PassThrough, Writable } from 'node:stream'; import { ParsedLocationAnnotation } from '../../helpers'; import { DefaultMkdocsContent, SupportedGeneratorKey } from './types'; import { getFileTreeRecursively } from '../publish/helpers'; @@ -266,6 +266,50 @@ export const getMkdocsYml = async ( }; }; +/** + * Allowlist of MkDocs configuration keys supported by TechDocs. + * + * @see https://www.mkdocs.org/user-guide/configuration/ + */ +export const ALLOWED_MKDOCS_KEYS = new Set([ + // Site information + 'site_name', + 'site_url', + 'site_description', + 'site_author', + // Repository + 'repo_url', + 'repo_name', + 'edit_uri', + 'edit_uri_template', + // Build directories + 'docs_dir', + 'site_dir', + // Documentation layout + 'nav', + 'exclude_docs', + 'not_in_nav', + // Build settings + 'theme', + 'plugins', + 'markdown_extensions', + 'extra', + 'extra_css', + 'extra_templates', + // Preview controls + 'use_directory_urls', + 'strict', + 'dev_addr', + 'watch', + // Metadata + 'copyright', + 'remote_branch', + 'remote_name', + 'validation', + // Deprecated + 'google_analytics', +]); + /** * Validating mkdocs config file for incorrect/insecure values * Throws on invalid configs @@ -288,6 +332,7 @@ export const validateMkdocsYaml = async ( } const parsedMkdocsYml: Record = mkdocsYml; + if ( parsedMkdocsYml.docs_dir && !isChildPath(inputDir, resolvePath(inputDir, parsedMkdocsYml.docs_dir)) @@ -300,6 +345,29 @@ export const validateMkdocsYaml = async ( return parsedMkdocsYml.docs_dir; }; +/** + * Validates that the docs directory doesn't contain symlinks pointing outside + * the input directory. This prevents path traversal attacks where malicious + * symlinks could be used to read arbitrary files from the host filesystem. + * + * @param docsDir - The docs directory to validate (absolute path) + * @param inputDir - The root input directory that symlinks must stay within + */ +export const validateDocsDirectory = async ( + docsDir: string, + inputDir: string, +): Promise => { + const files = await getFileTreeRecursively(docsDir); + + for (const file of files) { + if (!isChildPath(inputDir, file)) { + throw new NotAllowedError( + `Path ${file} is not allowed to refer to a location outside ${inputDir}`, + ); + } + } +}; + /** * Update docs/index.md file before TechDocs generator uses it to generate docs site, * falling back to docs/README.md or README.md in case a default docs/index.md @@ -373,8 +441,9 @@ export const createOrUpdateMetadata = async ( try { json = await fs.readJson(techdocsMetadataPath); } catch (err) { - assertError(err); - const message = `Invalid JSON at ${techdocsMetadataPath} with error ${err.message}`; + const message = `Invalid JSON at ${techdocsMetadataPath} with error ${ + toError(err).message + }`; logger.error(message); throw new Error(message); } @@ -388,9 +457,10 @@ export const createOrUpdateMetadata = async ( file.replace(`${techdocsMetadataDir}${path.sep}`, ''), ); } catch (err) { - assertError(err); json.files = []; - logger.warn(`Unable to add files list to metadata: ${err.message}`); + logger.warn( + `Unable to add files list to metadata: ${toError(err).message}`, + ); } await fs.writeJson(techdocsMetadataPath, json); diff --git a/plugins/techdocs-node/src/stages/generate/mkdocsPatchers.ts b/plugins/techdocs-node/src/stages/generate/mkdocsPatchers.ts index 804c06f129..c8cd7e32f6 100644 --- a/plugins/techdocs-node/src/stages/generate/mkdocsPatchers.ts +++ b/plugins/techdocs-node/src/stages/generate/mkdocsPatchers.ts @@ -16,8 +16,12 @@ import fs from 'fs-extra'; import yaml from 'js-yaml'; import { ParsedLocationAnnotation } from '../../helpers'; -import { getRepoUrlFromLocationAnnotation, MKDOCS_SCHEMA } from './helpers'; -import { assertError } from '@backstage/errors'; +import { + ALLOWED_MKDOCS_KEYS, + getRepoUrlFromLocationAnnotation, + MKDOCS_SCHEMA, +} from './helpers'; +import { toError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { LoggerService } from '@backstage/backend-plugin-api'; @@ -41,9 +45,10 @@ const patchMkdocsFile = async ( try { mkdocsYmlFileString = await fs.readFile(mkdocsYmlPath, 'utf8'); } catch (error) { - assertError(error); logger.warn( - `Could not read MkDocs YAML config file ${mkdocsYmlPath} before running the generator: ${error.message}`, + `Could not read MkDocs YAML config file ${mkdocsYmlPath} before running the generator: ${ + toError(error).message + }`, ); return; } @@ -58,9 +63,10 @@ const patchMkdocsFile = async ( throw new Error('Bad YAML format.'); } } catch (error) { - assertError(error); logger.warn( - `Error in parsing YAML at ${mkdocsYmlPath} before running the generator. ${error.message}`, + `Error in parsing YAML at ${mkdocsYmlPath} before running the generator. ${ + toError(error).message + }`, ); return; } @@ -76,9 +82,10 @@ const patchMkdocsFile = async ( ); } } catch (error) { - assertError(error); logger.warn( - `Could not write to ${mkdocsYmlPath} after updating it before running the generator. ${error.message}`, + `Could not write to ${mkdocsYmlPath} after updating it before running the generator. ${ + toError(error).message + }`, ); return; } @@ -180,3 +187,68 @@ export const patchMkdocsYmlWithPlugins = async ( return changesMade; }); }; + +/** + * Sanitize mkdocs.yml by keeping only allowed configuration keys. + * + * TechDocs only supports a subset of MkDocs configuration options. + * This function reconstructs the config with only allowed keys, + * discarding everything else. This approach ensures that any unknown + * or potentially dangerous configuration options are not passed to MkDocs. + * + * The file is always rewritten to ensure YAML features like merge keys + * and anchors are resolved into plain configuration. + * + * @param mkdocsYmlPath - Absolute path to mkdocs.yml or equivalent of a docs site + * @param logger - A logger instance + * @param additionalAllowedKeys - Optional array of additional keys to allow beyond the default allowlist + */ +export const sanitizeMkdocsYml = async ( + mkdocsYmlPath: string, + logger: LoggerService, + additionalAllowedKeys?: string[], +) => { + await patchMkdocsFile(mkdocsYmlPath, logger, mkdocsYml => { + // Combine default allowed keys with additional keys + const allowedKeys = new Set(ALLOWED_MKDOCS_KEYS); + if (additionalAllowedKeys && additionalAllowedKeys.length > 0) { + logger.warn( + `DANGEROUS: Allowing additional MkDocs configuration keys beyond the default safe allowlist: ${additionalAllowedKeys.join( + ', ', + )}. This may introduce security vulnerabilities. Only use in trusted environments.`, + ); + additionalAllowedKeys.forEach(key => allowedKeys.add(key)); + } + + // Identify keys that will be removed for logging + const removedKeys = Object.keys(mkdocsYml).filter( + key => !allowedKeys.has(key), + ); + + if (removedKeys.length > 0) { + logger.warn( + `Removed the following unsupported configuration keys from mkdocs.yml: ${removedKeys.join( + ', ', + )}. ` + + `TechDocs only supports a subset of MkDocs configuration options.`, + ); + } + + // Build a new object with only allowed keys + const sanitized: Record = {}; + for (const key of allowedKeys) { + if (key in mkdocsYml) { + sanitized[key] = (mkdocsYml as Record)[key]; + } + } + + // Clear the original object and copy sanitized values back + for (const key of Object.keys(mkdocsYml)) { + delete (mkdocsYml as Record)[key]; + } + Object.assign(mkdocsYml, sanitized); + + // Always rewrite to ensure clean YAML output (resolves merge keys, anchors, etc.) + return true; + }); +}; diff --git a/plugins/techdocs-node/src/stages/generate/techdocs.ts b/plugins/techdocs-node/src/stages/generate/techdocs.ts index 1722bda838..8a764eb391 100644 --- a/plugins/techdocs-node/src/stages/generate/techdocs.ts +++ b/plugins/techdocs-node/src/stages/generate/techdocs.ts @@ -15,7 +15,7 @@ */ import { Config } from '@backstage/config'; -import path from 'path'; +import path from 'node:path'; import { ScmIntegrationRegistry, ScmIntegrations, @@ -26,12 +26,14 @@ import { patchIndexPreBuild, runCommand, storeEtagMetadata, + validateDocsDirectory, validateMkdocsYaml, } from './helpers'; import { patchMkdocsYmlPreBuild, patchMkdocsYmlWithPlugins, + sanitizeMkdocsYml, } from './mkdocsPatchers'; import { GeneratorBase, @@ -53,8 +55,10 @@ export class TechdocsGenerator implements GeneratorBase { /** * The default docker image (and version) used to generate content. Public * and static so that techdocs-node consumers can use the same version. + * + * See {@link https://hub.docker.com/r/spotify/techdocs/tags} for list of available versions. */ - public static readonly defaultDockerImage = 'spotify/techdocs:v1.2.6'; + public static readonly defaultDockerImage = 'spotify/techdocs:v1.2.8'; private readonly logger: LoggerService; private readonly containerRunner?: TechDocsContainerRunner; private readonly options: GeneratorConfig; @@ -110,6 +114,18 @@ export class TechdocsGenerator implements GeneratorBase { // validate the docs_dir first const docsDir = await validateMkdocsYaml(inputDir, content); + // Remove unsupported configuration keys + await sanitizeMkdocsYml( + mkdocsYmlPath, + childLogger, + this.options.dangerouslyAllowAdditionalKeys, + ); + + // Validate that no symlinks in the docs directory point outside the input directory + // This prevents path traversal attacks where malicious symlinks could leak host files + const resolvedDocsDir = path.join(inputDir, docsDir ?? 'docs'); + await validateDocsDirectory(resolvedDocsDir, inputDir); + if (parsedLocationAnnotation) { await patchMkdocsYmlPreBuild( mkdocsYmlPath, @@ -245,5 +261,8 @@ export function readGeneratorConfig( defaultPlugins: config.getOptionalStringArray( 'techdocs.generator.mkdocs.defaultPlugins', ), + dangerouslyAllowAdditionalKeys: config.getOptionalStringArray( + 'techdocs.generator.mkdocs.dangerouslyAllowAdditionalKeys', + ), }; } diff --git a/plugins/techdocs-node/src/stages/generate/types.ts b/plugins/techdocs-node/src/stages/generate/types.ts index fd4cb62820..b716e9510b 100644 --- a/plugins/techdocs-node/src/stages/generate/types.ts +++ b/plugins/techdocs-node/src/stages/generate/types.ts @@ -15,7 +15,7 @@ */ import { Entity } from '@backstage/catalog-model'; -import { Writable } from 'stream'; +import { Writable } from 'node:stream'; import { Logger } from 'winston'; import { ParsedLocationAnnotation } from '../../helpers'; import { LoggerService } from '@backstage/backend-plugin-api'; @@ -45,6 +45,7 @@ export type GeneratorConfig = { omitTechdocsCoreMkdocsPlugin?: boolean; legacyCopyReadmeMdToIndexMd?: boolean; defaultPlugins?: string[]; + dangerouslyAllowAdditionalKeys?: string[]; }; /** diff --git a/plugins/techdocs-node/src/stages/prepare/url.ts b/plugins/techdocs-node/src/stages/prepare/url.ts index 260ed454e8..3b5b1f6d29 100644 --- a/plugins/techdocs-node/src/stages/prepare/url.ts +++ b/plugins/techdocs-node/src/stages/prepare/url.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { assertError } from '@backstage/errors'; +import { toError } from '@backstage/errors'; import { Entity } from '@backstage/catalog-model'; import { getDocFilesFromRepository } from '../../helpers'; import { @@ -62,13 +62,13 @@ export class UrlPreparer implements PreparerBase { logger: this.logger, }); } catch (error) { - assertError(error); + const err = toError(error); // NotModifiedError means that etag based cache is still valid. - if (error.name === 'NotModifiedError') { + if (err.name === 'NotModifiedError') { this.logger.debug(`Cache is valid for etag ${options?.etag}`); } else { this.logger.debug( - `Unable to fetch files for building docs ${error.message}`, + `Unable to fetch files for building docs ${err.message}`, ); } diff --git a/plugins/techdocs-node/src/stages/publish/awsS3.test.ts b/plugins/techdocs-node/src/stages/publish/awsS3.test.ts index 6fb855d199..7c272d0ed6 100644 --- a/plugins/techdocs-node/src/stages/publish/awsS3.test.ts +++ b/plugins/techdocs-node/src/stages/publish/awsS3.test.ts @@ -22,6 +22,7 @@ import { ListObjectsV2Command, PutObjectCommand, S3Client, + S3ServiceException, UploadPartCommand, } from '@aws-sdk/client-s3'; import { Entity, DEFAULT_NAMESPACE } from '@backstage/catalog-model'; @@ -34,19 +35,22 @@ import { import { mockClient, AwsClientStub } from 'aws-sdk-client-mock'; import express from 'express'; import request from 'supertest'; -import path from 'path'; +import path from 'node:path'; import fs from 'fs-extra'; import { AwsS3Publish } from './awsS3'; -import { Readable } from 'stream'; +import { Readable } from 'node:stream'; import { createMockDirectory, mockServices, } from '@backstage/backend-test-utils'; +jest.setTimeout(30_000); + const env = process.env; let s3Mock: AwsClientStub; -const mockDir = createMockDirectory(); +// Create a new MockDirectory for each test to avoid Windows file locking issues +let mockDir: ReturnType; function getMockCredentialProvider(): Promise { return Promise.resolve({ @@ -155,7 +159,7 @@ describe('AwsS3Publish', () => { build_timestamp: 612741599, }; - const directory = getEntityRootDir(entity); + let directory: string; const files = { 'index.html': '', @@ -176,7 +180,7 @@ describe('AwsS3Publish', () => { }, }; - beforeEach(() => { + beforeEach(async () => { process.env = { ...env }; process.env.AWS_REGION = 'us-west-2'; @@ -185,6 +189,12 @@ describe('AwsS3Publish', () => { getMockCredentialProvider(), ); + // Create a fresh mockdirectory for each test to avoid windows file locking + mockDir = createMockDirectory(); + // Calculate directory path with the new mockDir instance + directory = getEntityRootDir(entity); + + // Set up the test files mockDir.setContent({ [directory]: files, }); @@ -270,25 +280,385 @@ describe('AwsS3Publish', () => { expect(getCredProviderMock).toHaveBeenCalledTimes(1); }); - it('should fall back to deprecated method of retrieving credentials', async () => { + it('should use aws.accounts over integrations.awsS3 if both are provided', async () => { + await jest.isolateModulesAsync(async () => { + jest.doMock('@aws-sdk/client-s3', () => ({ + ...jest.requireActual('@aws-sdk/client-s3'), + S3Client: jest.fn(), + })); + + const { S3Client: MockS3Client } = require('@aws-sdk/client-s3'); + const { AwsS3Publish: IsolatedAwsS3Publish } = require('./awsS3'); + const mockConfig = new ConfigReader({ + techdocs: { + publisher: { + type: 'awsS3', + awsS3: { + accountId: '111111111111', + bucketName: 'bucketName', + bucketRootPath: '/', + }, + }, + }, + integrations: { + awsS3: [ + { + accessKeyId: 'access-key-from-integrations', + secretAccessKey: 'secret-access-key-from-integrations', + }, + ], + }, + aws: { + accounts: [ + { + accountId: '111111111111', + accessKeyId: 'access-key-from-aws', + secretAccessKey: 'secret-access-key-from-aws', + }, + ], + }, + }); + + await IsolatedAwsS3Publish.fromConfig(mockConfig, logger); + expect(getCredProviderMock).toHaveBeenCalledTimes(0); + expect(MockS3Client).toHaveBeenCalledTimes(1); + await expect( + MockS3Client.mock.calls[0][0]!.credentialDefaultProvider!( + undefined!, + )(), + ).resolves.toEqual({ + accessKeyId: 'access-key-from-aws', + secretAccessKey: 'secret-access-key-from-aws', + }); + }); + }); + + it('should use awsS3.credentials if they are provided', async () => { + await jest.isolateModulesAsync(async () => { + jest.doMock('@aws-sdk/client-s3', () => ({ + ...jest.requireActual('@aws-sdk/client-s3'), + S3Client: jest.fn(), + })); + + const { S3Client: MockS3Client } = require('@aws-sdk/client-s3'); + const { AwsS3Publish: IsolatedAwsS3Publish } = require('./awsS3'); + const mockConfig = new ConfigReader({ + techdocs: { + publisher: { + type: 'awsS3', + awsS3: { + credentials: { + accessKeyId: 'accessKeyId', + secretAccessKey: 'secretAccessKey', + }, + bucketName: 'bucketName', + bucketRootPath: '/', + }, + }, + }, + integrations: { + awsS3: [ + { + accessKeyId: 'access-key-from-integrations', + secretAccessKey: 'secret-access-key-from-integrations', + }, + ], + }, + }); + + await IsolatedAwsS3Publish.fromConfig(mockConfig, logger); + expect(getCredProviderMock).toHaveBeenCalledTimes(0); + expect(MockS3Client).toHaveBeenCalledTimes(1); + await expect( + MockS3Client.mock.calls[0][0]!.credentialDefaultProvider!( + undefined!, + )(), + ).resolves.toEqual({ + accessKeyId: 'accessKeyId', + secretAccessKey: 'secretAccessKey', + }); + }); + }); + + it('should use credentials from integrations if awsS3.credentials is not provided', async () => { + await jest.isolateModulesAsync(async () => { + jest.doMock('@aws-sdk/client-s3', () => ({ + ...jest.requireActual('@aws-sdk/client-s3'), + S3Client: jest.fn(), + })); + + const { S3Client: MockS3Client } = require('@aws-sdk/client-s3'); + const { AwsS3Publish: IsolatedAwsS3Publish } = require('./awsS3'); + + const mockConfig = new ConfigReader({ + techdocs: { + publisher: { + type: 'awsS3', + awsS3: { + credentials: {}, + bucketName: 'bucketName', + bucketRootPath: '/', + }, + }, + }, + integrations: { + awsS3: [ + { + accessKeyId: 'access-key-from-integrations', + secretAccessKey: 'secret-access-key-from-integrations', + }, + ], + }, + }); + + await IsolatedAwsS3Publish.fromConfig(mockConfig, logger); + expect(getCredProviderMock).toHaveBeenCalledTimes(0); + expect(MockS3Client).toHaveBeenCalledTimes(1); + await expect( + MockS3Client.mock.calls[0][0]!.credentialDefaultProvider!( + undefined!, + )(), + ).resolves.toEqual({ + accessKeyId: 'access-key-from-integrations', + secretAccessKey: 'secret-access-key-from-integrations', + }); + }); + }); + + it('should retrieve default credentials if multiple integrations are present', async () => { const mockConfig = new ConfigReader({ techdocs: { publisher: { type: 'awsS3', awsS3: { - credentials: { - accessKeyId: 'accessKeyId', - secretAccessKey: 'secretAccessKey', - }, + credentials: {}, bucketName: 'bucketName', bucketRootPath: '/', }, }, }, + integrations: { + awsS3: [ + { + accessKeyId: 'access-key-from-integrations', + secretAccessKey: 'secret-access-key-from-integrations', + }, + { + accessKeyId: 'access-key-from-integrations-2', + secretAccessKey: 'secret-access-key-from-integrations-2', + }, + ], + }, }); await AwsS3Publish.fromConfig(mockConfig, logger); - expect(getCredProviderMock).toHaveBeenCalledTimes(0); + expect(getCredProviderMock).toHaveBeenCalledTimes(1); + }); + + it('should retrieve the target integration if multiple integrations are provided and credentials are not provided', async () => { + await jest.isolateModulesAsync(async () => { + jest.doMock('@aws-sdk/client-s3', () => ({ + ...jest.requireActual('@aws-sdk/client-s3'), + S3Client: jest.fn(), + })); + + const { S3Client: MockS3Client } = require('@aws-sdk/client-s3'); + const { AwsS3Publish: IsolatedAwsS3Publish } = require('./awsS3'); + + const mockConfig = new ConfigReader({ + techdocs: { + publisher: { + type: 'awsS3', + awsS3: { + credentials: { + accessKeyId: 'access-key-from-integrations-2', + }, + bucketName: 'bucketName', + bucketRootPath: '/', + }, + }, + }, + integrations: { + awsS3: [ + { + accessKeyId: 'access-key-from-integrations', + secretAccessKey: 'secret-access-key-from-integrations', + }, + { + accessKeyId: 'access-key-from-integrations-2', + secretAccessKey: 'secret-access-key-from-integrations-2', + }, + ], + }, + }); + + await IsolatedAwsS3Publish.fromConfig(mockConfig, logger); + expect(getCredProviderMock).toHaveBeenCalledTimes(0); + expect(MockS3Client).toHaveBeenCalledTimes(1); + await expect( + MockS3Client.mock.calls[0][0]!.credentialDefaultProvider!( + undefined!, + )(), + ).resolves.toEqual({ + accessKeyId: 'access-key-from-integrations-2', + secretAccessKey: 'secret-access-key-from-integrations-2', + }); + }); + }); + }); + + describe('retry mechanism', () => { + it('should retry with custom retry strategy', async () => { + const publisher = await createPublisherFromConfig(); + const customRetryStrategy = jest.fn(error => { + return error.name === 'NetworkingError'; + }); + + s3Mock + .on(ListObjectsV2Command) + .rejectsOnce( + new S3ServiceException({ + name: 'NetworkingError', + $fault: 'client', + $metadata: {}, + }), + ) + .resolvesOnce({ Contents: [] }); + + await (publisher as AwsS3Publish).retryOperation( + async () => { + const command = new ListObjectsV2Command({ Bucket: 'bucketName' }); + return (publisher as AwsS3Publish).storageClient.send(command); + }, + 'TestOperation', + 3, + customRetryStrategy, + ); + + expect(customRetryStrategy).toHaveBeenCalled(); + }); + + it('should use default retry strategy when no custom strategy provided', async () => { + const publisher = await createPublisherFromConfig(); + s3Mock + .on(ListObjectsV2Command) + .rejectsOnce( + new S3ServiceException({ + name: 'RequestTimeout', + $fault: 'client', + $metadata: {}, + }), + ) + .resolvesOnce({ Contents: [] }); + + await (publisher as AwsS3Publish).retryOperation( + async () => { + const command = new ListObjectsV2Command({ Bucket: 'bucketName' }); + return (publisher as AwsS3Publish).storageClient.send(command); + }, + 'TestOperation', + 3, + ); + }); + + it('should retry on server errors (5xx)', async () => { + const publisher = await createPublisherFromConfig(); + s3Mock + .on(ListObjectsV2Command) + .rejectsOnce( + new S3ServiceException({ + name: 'InternalError', + $fault: 'server', + $metadata: { httpStatusCode: 500 }, + }), + ) + .resolvesOnce({ Contents: [] }); + + await (publisher as AwsS3Publish).retryOperation( + async () => { + const command = new ListObjectsV2Command({ Bucket: 'bucketName' }); + return (publisher as AwsS3Publish).storageClient.send(command); + }, + 'TestOperation', + 3, + ); + }); + + it('should retry on specific 4xx errors that are transient', async () => { + const publisher = await createPublisherFromConfig(); + s3Mock + .on(ListObjectsV2Command) + .rejectsOnce( + new S3ServiceException({ + name: 'RequestTimeout', + $fault: 'client', + $metadata: { httpStatusCode: 408 }, + }), + ) + .resolvesOnce({ Contents: [] }); + + await (publisher as AwsS3Publish).retryOperation( + async () => { + const command = new ListObjectsV2Command({ Bucket: 'bucketName' }); + return (publisher as AwsS3Publish).storageClient.send(command); + }, + 'TestOperation', + 3, + ); + }); + + it('should use exact error code matching for transient errors', async () => { + const publisher = await createPublisherFromConfig(); + // Test that ConnectionError (exact match) is retried, but ConnectionErrorSomething (substring) is not + s3Mock + .on(ListObjectsV2Command) + .rejectsOnce( + new S3ServiceException({ + name: 'ConnectionError', + $fault: 'client', + $metadata: {}, + }), + ) + .resolvesOnce({ Contents: [] }); + + await (publisher as AwsS3Publish).retryOperation( + async () => { + const command = new ListObjectsV2Command({ Bucket: 'bucketName' }); + return (publisher as AwsS3Publish).storageClient.send(command); + }, + 'TestOperation', + 3, + ); + }); + + it('should apply exponential backoff with correct calculation', async () => { + const publisher = await createPublisherFromConfig(); + const startTime = Date.now(); + + s3Mock + .on(ListObjectsV2Command) + .rejectsOnce( + new S3ServiceException({ + name: 'SlowDown', + $fault: 'server', + $metadata: {}, + }), + ) + .resolvesOnce({ Contents: [] }); + + await (publisher as AwsS3Publish).retryOperation( + async () => { + const command = new ListObjectsV2Command({ Bucket: 'bucketName' }); + return (publisher as AwsS3Publish).storageClient.send(command); + }, + 'TestOperation', + 3, + ); + + const elapsedTime = Date.now() - startTime; + // First attempt fails, then backoff with baseDelay * 2^(attempt-1) = 1000 * 2^0 = 1000ms minimum + // Adding jitter (0-1000ms), so we expect at least 1000ms total + expect(elapsedTime).toBeGreaterThanOrEqual(900); }); }); @@ -405,7 +775,7 @@ describe('AwsS3Publish', () => { bucketName: bucketName, }); await publisher.publish({ entity, directory }); - expect(loggerInfoSpy).toHaveBeenLastCalledWith( + expect(loggerInfoSpy).toHaveBeenCalledWith( `Successfully deleted stale files for Entity ${entity.metadata.name}. Total number of files: 1`, ); }); @@ -549,7 +919,7 @@ describe('AwsS3Publish', () => { }); it('should return an error if the techdocs_metadata.json file cannot be read from stream', async () => { - s3Mock.on(GetObjectCommand).callsFake(_ => { + s3Mock.on(GetObjectCommand).callsFake(() => { return { Body: new ErrorReadable('No stream!'), }; @@ -688,7 +1058,7 @@ describe('AwsS3Publish', () => { }); it('should return 404 if file cannot be read from stream', async () => { - s3Mock.on(GetObjectCommand).callsFake(_ => { + s3Mock.on(GetObjectCommand).callsFake(() => { return { Body: new ErrorReadable('No stream!'), }; diff --git a/plugins/techdocs-node/src/stages/publish/awsS3.ts b/plugins/techdocs-node/src/stages/publish/awsS3.ts index bd8b96ee14..28c0529ea1 100644 --- a/plugins/techdocs-node/src/stages/publish/awsS3.ts +++ b/plugins/techdocs-node/src/stages/publish/awsS3.ts @@ -15,7 +15,11 @@ */ import { Entity, CompoundEntityRef } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; -import { assertError, ForwardedError } from '@backstage/errors'; +import { ForwardedError, toError } from '@backstage/errors'; + +// Maximum size in bytes for a single upload part (5MB) +const MAX_SINGLE_UPLOAD_BYTES = 5 * 1024 * 1024; + import { AwsCredentialsManager, DefaultAwsCredentialsManager, @@ -26,10 +30,12 @@ import { DeleteObjectCommand, HeadBucketCommand, HeadObjectCommand, + PutObjectCommand, PutObjectCommandInput, ListObjectsV2CommandOutput, ListObjectsV2Command, S3Client, + S3ServiceException, } from '@aws-sdk/client-s3'; import { fromTemporaryCredentials } from '@aws-sdk/credential-providers'; import { NodeHttpHandler } from '@smithy/node-http-handler'; @@ -40,8 +46,8 @@ import express from 'express'; import fs from 'fs-extra'; import JSON5 from 'json5'; import createLimiter from 'p-limit'; -import path from 'path'; -import { Readable } from 'stream'; +import path from 'node:path'; +import { Readable } from 'node:stream'; import { bulkStorageOperation, getCloudPathForLocalPath, @@ -61,6 +67,7 @@ import { TechDocsMetadata, } from './types'; import { LoggerService } from '@backstage/backend-plugin-api'; +import { AwsS3Integration, ScmIntegrations } from '@backstage/integration'; const streamToBuffer = (stream: Readable): Promise => { return new Promise((resolve, reject) => { @@ -78,12 +85,13 @@ const streamToBuffer = (stream: Readable): Promise => { }; export class AwsS3Publish implements PublisherBase { - private readonly storageClient: S3Client; + public readonly storageClient: S3Client; private readonly bucketName: string; private readonly legacyPathCasing: boolean; private readonly logger: LoggerService; private readonly bucketRootPath: string; private readonly sse?: 'aws:kms' | 'AES256'; + private readonly maxAttempts: number; constructor(options: { storageClient: S3Client; @@ -92,6 +100,7 @@ export class AwsS3Publish implements PublisherBase { logger: LoggerService; bucketRootPath: string; sse?: 'aws:kms' | 'AES256'; + maxAttempts: number; }) { this.storageClient = options.storageClient; this.bucketName = options.bucketName; @@ -99,6 +108,7 @@ export class AwsS3Publish implements PublisherBase { this.logger = options.logger; this.bucketRootPath = options.bucketRootPath; this.sse = options.sse; + this.maxAttempts = options.maxAttempts; } static async fromConfig( @@ -139,9 +149,16 @@ export class AwsS3Publish implements PublisherBase { const credentialsConfig = config.getOptionalConfig( 'techdocs.publisher.awsS3.credentials', ); + const credsManager = DefaultAwsCredentialsManager.fromConfig(config); + + const scmIntegrations = ScmIntegrations.fromConfig(config); + const awsS3Integrations = scmIntegrations.awsS3.list(); + const sdkCredentialProvider = await AwsS3Publish.buildCredentials( credsManager, + logger, + awsS3Integrations, accountId, credentialsConfig, region, @@ -164,7 +181,7 @@ export class AwsS3Publish implements PublisherBase { 'techdocs.publisher.awsS3.s3ForcePathStyle', ); - // AWS MAX ATTEMPTS is an optional config. If missing, default value of 3 is used + // AWS MAX ATTEMPTS is an optional config. If missing, default value of 5 is used const maxAttempts = config.getOptionalNumber( 'techdocs.publisher.awsS3.maxAttempts', ); @@ -175,11 +192,16 @@ export class AwsS3Publish implements PublisherBase { ...(region && { region }), ...(endpoint && { endpoint }), ...(forcePathStyle && { forcePathStyle }), - ...(maxAttempts && { maxAttempts }), - ...(httpsProxy && { - requestHandler: new NodeHttpHandler({ + // Enhanced retry configuration for better reliability + maxAttempts: maxAttempts || 5, + retryMode: 'adaptive', + // Enhanced connection settings for large file uploads + requestHandler: new NodeHttpHandler({ + ...(httpsProxy && { httpsAgent: new HttpsProxyAgent({ proxy: httpsProxy }), }), + connectionTimeout: 60000, + socketTimeout: 120000, }), }); @@ -195,6 +217,7 @@ export class AwsS3Publish implements PublisherBase { legacyPathCasing, logger, sse, + maxAttempts: maxAttempts || 5, }); } @@ -212,8 +235,10 @@ export class AwsS3Publish implements PublisherBase { private static async buildCredentials( credsManager: AwsCredentialsManager, + logger: LoggerService, + awsS3Integrations: AwsS3Integration[], accountId?: string, - config?: Config, + credentialsConfig?: Config, region?: string, ): Promise { // Pull credentials for the specified account ID from the 'aws' config section @@ -222,21 +247,14 @@ export class AwsS3Publish implements PublisherBase { .sdkCredentialProvider; } - // Fall back to the default credential chain if neither account ID - // nor explicit credentials are provided - if (!config) { - return (await credsManager.getCredentialProvider()).sdkCredentialProvider; - } + const explicitCredentials = await AwsS3Publish.getExplicitCredentials({ + credsManager, + credentialsConfig, + awsS3Integrations, + logger, + }); - // Pull credentials from the techdocs config section (deprecated) - const accessKeyId = config.getOptionalString('accessKeyId'); - const secretAccessKey = config.getOptionalString('secretAccessKey'); - const explicitCredentials: AwsCredentialIdentityProvider = - accessKeyId && secretAccessKey - ? AwsS3Publish.buildStaticCredentials(accessKeyId, secretAccessKey) - : (await credsManager.getCredentialProvider()).sdkCredentialProvider; - - const roleArn = config.getOptionalString('roleArn'); + const roleArn = credentialsConfig?.getOptionalString('roleArn'); if (roleArn) { return fromTemporaryCredentials({ masterCredentials: explicitCredentials, @@ -250,6 +268,157 @@ export class AwsS3Publish implements PublisherBase { return explicitCredentials; } + /** + * Custom retry wrapper for S3 operations with detailed error handling. + */ + public async retryOperation( + operation: () => Promise, + operationName: string, + maxAttempts: number = 3, + shouldRetry: ( + error: S3ServiceException, + ) => boolean = this.defaultShouldRetry.bind(this), + ): Promise { + for (let attempt = 1; attempt < maxAttempts; attempt++) { + try { + return await operation(); + } catch (error) { + const e = error as S3ServiceException; + if (!shouldRetry(e)) { + this.logger.error(`${operationName} failed: ${e.message}`); + throw e; + } + + this.logger.warn(`${operationName} failed, retrying...`, { + attempt, + maxAttempts, + error: e.message, + errorCode: e.name, + httpStatusCode: e.$metadata?.httpStatusCode, + }); + + // Enhanced exponential backoff with jitter + const baseDelay = operationName.startsWith('Upload-') ? 2000 : 1000; + const backoffDelay = Math.min( + baseDelay * Math.pow(2, attempt - 1), + 30000, + ); + const jitter = Math.random() * 1000; + await new Promise(resolve => + setTimeout(resolve, backoffDelay + jitter), + ); + } + } + return await operation(); + } + + /** + * Determines if an S3 operation should be retried based on the error details. + */ + private defaultShouldRetry(error: S3ServiceException): boolean { + const httpStatusCode = error.$metadata?.httpStatusCode; + const errorCode = error.name; + + // Truly transient errors that should always be retried + const transientErrors = [ + 'NetworkingError', + 'TimeoutError', + 'ConnectionError', + 'RequestTimeout', + 'ServiceUnavailable', + 'SlowDown', + 'ThrottlingException', + ]; + + // Server errors are always considered transient + if (httpStatusCode && httpStatusCode >= 500) { + return true; + } + + // Specific 4xx errors that are known to be transient + if (httpStatusCode && httpStatusCode >= 400 && httpStatusCode < 500) { + const retriable4xxErrors = [ + 'RequestTimeout', + 'RequestTimeoutException', + 'PriorRequestNotComplete', + ]; + return retriable4xxErrors.includes(errorCode); + } + + // Check against known transient errors + return transientErrors.some( + retriableError => + errorCode === retriableError || error.message.includes(retriableError), + ); + } + + private static async getExplicitCredentials({ + credentialsConfig, + awsS3Integrations, + credsManager, + logger, + }: { + credentialsConfig?: Config; + awsS3Integrations: AwsS3Integration[]; + credsManager: AwsCredentialsManager; + logger: LoggerService; + }): Promise { + const accessKeyId = credentialsConfig?.getOptionalString('accessKeyId'); + const secretAccessKey = + credentialsConfig?.getOptionalString('secretAccessKey'); + + if (accessKeyId && secretAccessKey) { + return AwsS3Publish.buildStaticCredentials(accessKeyId, secretAccessKey); + } + + if (awsS3Integrations.length > 0) { + if (awsS3Integrations.length === 1) { + const singleAwsS3IntegrationConfig = awsS3Integrations[0].config; + + const singleAwsS3IntegrationAccessKeyId = + singleAwsS3IntegrationConfig.accessKeyId; + + const singleAwsS3IntegrationSecretAccessKey = + singleAwsS3IntegrationConfig.secretAccessKey; + + if ( + singleAwsS3IntegrationAccessKeyId && + singleAwsS3IntegrationSecretAccessKey + ) { + return AwsS3Publish.buildStaticCredentials( + singleAwsS3IntegrationAccessKeyId, + singleAwsS3IntegrationSecretAccessKey, + ); + } + } else { + if (accessKeyId) { + const targetAwsS3IntegrationConfig = awsS3Integrations.find( + c => c.config.accessKeyId === accessKeyId, + ); + + if (!targetAwsS3IntegrationConfig) { + logger.warn( + `No AWS S3 integration config under integrations.awsS3 found for access key id ${accessKeyId}.`, + ); + } + const targetAwsS3IntegrationAccessKeyId = + targetAwsS3IntegrationConfig?.config.accessKeyId; + const targetAwsS3IntegrationSecretAccessKey = + targetAwsS3IntegrationConfig?.config.secretAccessKey; + if ( + targetAwsS3IntegrationAccessKeyId && + targetAwsS3IntegrationSecretAccessKey + ) { + return AwsS3Publish.buildStaticCredentials( + targetAwsS3IntegrationAccessKeyId, + targetAwsS3IntegrationSecretAccessKey, + ); + } + } + } + } + return (await credsManager.getCredentialProvider()).sdkCredentialProvider; + } /** * Check if the defined bucket exists. Being able to connect means the configuration is good @@ -273,13 +442,15 @@ export class AwsS3Publish implements PublisherBase { 'explicitly defining credentials and region in techdocs.publisher.awsS3 in app config or ' + 'by using environment variables. Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage', ); - this.logger.error(`from AWS client library`, error); + this.logger.error( + `from AWS client library`, + error instanceof Error ? error : new Error(String(error)), + ); return { isAvailable: false, }; } } - /** * Upload all the files from the generated `directory` to the S3 bucket. * Directory structure used in the bucket is - entityNamespace/entityKind/entityName/index.html @@ -293,6 +464,9 @@ export class AwsS3Publish implements PublisherBase { const bucketRootPath = this.bucketRootPath; const sse = this.sse; + // Track timing for performance monitoring + const publishStartTime = Date.now(); + // First, try to retrieve a list of all individual files currently existing let existingFiles: string[] = []; try { @@ -302,13 +476,25 @@ export class AwsS3Publish implements PublisherBase { useLegacyPathCasing, bucketRootPath, ); - existingFiles = await this.getAllObjectsFromBucket({ - prefix: remoteFolder, - }); + const response = await this.retryOperation( + async () => { + const listCommand = new ListObjectsV2Command({ + Bucket: this.bucketName, + Prefix: remoteFolder, + }); + return this.storageClient.send(listCommand); + }, + 'ListObjects', + this.maxAttempts, + ); + existingFiles = (response.Contents || []) + .map(f => f.Key || '') + .filter(f => !!f); } catch (e) { - assertError(e); this.logger.error( - `Unable to list files for Entity ${entity.metadata.name}: ${e.message}`, + `Unable to list files for Entity ${entity.metadata.name}: ${ + toError(e).message + }`, ); } @@ -323,27 +509,99 @@ export class AwsS3Publish implements PublisherBase { await bulkStorageOperation( async absoluteFilePath => { const relativeFilePath = path.relative(directory, absoluteFilePath); - const fileStream = fs.createReadStream(absoluteFilePath); - + const s3Key = getCloudPathForLocalPath( + entity, + relativeFilePath, + useLegacyPathCasing, + bucketRootPath, + ); + // Create params without the Body because the body must be the + // actual file contents (Buffer or Readable), not the path string. + // For multipart uploads we attach a Readable stream to avoid + // buffering large files in memory. For simple uploads we attach + // a Buffer read from disk. const params: PutObjectCommandInput = { Bucket: this.bucketName, - Key: getCloudPathForLocalPath( - entity, - relativeFilePath, - useLegacyPathCasing, - bucketRootPath, - ), - Body: fileStream, + Key: s3Key, ...(sse && { ServerSideEncryption: sse }), }; objects.push(params.Key!); + // Get file stats before upload + const stats = await fs.stat(absoluteFilePath); + const fileSizeInBytes = stats.size; - const upload = new Upload({ - client: this.storageClient, - params, - }); - return upload.done(); + // Check if this is a large file that requires multipart upload + if (fileSizeInBytes >= MAX_SINGLE_UPLOAD_BYTES) { + // Try multipart upload for large files + try { + // Create stream and Upload inside retry closure so stream is + // recreated on each retry attempt (streams are consumable once). + await this.retryOperation( + () => { + // Create a fresh stream on each attempt + const fileStream = fs.createReadStream(absoluteFilePath); + const uploadParams = { ...params, Body: fileStream }; + + const upload = new Upload({ + client: this.storageClient, + params: uploadParams, + partSize: MAX_SINGLE_UPLOAD_BYTES, + queueSize: 3, + leavePartsOnError: false, + }); + return upload.done(); + }, + `Upload-${params.Key}`, + this.maxAttempts, + ); + return; + } catch (multipartError) { + const s3Error = multipartError as any; + const errorName = s3Error?.name || 'Unknown'; + + // For specific multipart errors, attempt simple upload fallback + if (errorName === 'InvalidPart' || errorName === 'NoSuchUpload') { + this.logger.warn( + `Multipart upload failed for ${params.Key}, attempting simple upload fallback.`, + ); + } else { + // Non-recoverable multipart error, throw it + this.logger.error( + `Multipart upload failed for ${params.Key}: ${ + multipartError instanceof Error + ? multipartError.message + : String(multipartError) + }`, + ); + throw multipartError; + } + } + } + + // Use simple upload for small files or as fallback from multipart + try { + const fileContent = await fs.readFile(absoluteFilePath); + const putParams = { ...params, Body: fileContent }; + await this.retryOperation( + () => this.storageClient.send(new PutObjectCommand(putParams)), + `Upload-${params.Key}`, + this.maxAttempts, + ); + + if (fileSizeInBytes >= MAX_SINGLE_UPLOAD_BYTES) { + this.logger.info( + `Simple upload fallback succeeded for ${params.Key}`, + ); + } + } catch (error) { + this.logger.error( + `Upload failed for ${params.Key}: ${ + error instanceof Error ? error.message : String(error) + }`, + ); + throw error; + } }, absoluteFilesToUpload, { concurrencyLimit: 10 }, @@ -373,17 +631,21 @@ export class AwsS3Publish implements PublisherBase { await bulkStorageOperation( async relativeFilePath => { - return await this.storageClient.send( - new DeleteObjectCommand({ - Bucket: this.bucketName, - Key: relativeFilePath, - }), + return this.retryOperation( + async () => { + const deleteCommand = new DeleteObjectCommand({ + Bucket: this.bucketName, + Key: relativeFilePath, + }); + return this.storageClient.send(deleteCommand); + }, + 'DeleteObject', + this.maxAttempts, ); }, staleFiles, { concurrencyLimit: 10 }, ); - this.logger.info( `Successfully deleted stale files for Entity ${entity.metadata.name}. Total number of files: ${staleFiles.length}`, ); @@ -391,6 +653,13 @@ export class AwsS3Publish implements PublisherBase { const errorMessage = `Unable to delete file(s) from AWS S3. ${error}`; this.logger.error(errorMessage); } + const publishEndTime = Date.now(); + const publishDurationMs = publishEndTime - publishStartTime; + this.logger.info( + `Successfully published ${objects.length} files for ${ + entity.metadata.name + } in ${Math.round(publishDurationMs / 1000)}s`, + ); return { objects }; } @@ -413,11 +682,16 @@ export class AwsS3Publish implements PublisherBase { } try { - const resp = await this.storageClient.send( - new GetObjectCommand({ - Bucket: this.bucketName, - Key: `${entityRootDir}/techdocs_metadata.json`, - }), + const resp = await this.retryOperation( + async () => { + const getCommand = new GetObjectCommand({ + Bucket: this.bucketName, + Key: `${entityRootDir}/techdocs_metadata.json`, + }); + return this.storageClient.send(getCommand); + }, + 'GetTechDocsMetadata', + this.maxAttempts, ); const techdocsMetadataJson = await streamToBuffer( @@ -435,9 +709,8 @@ export class AwsS3Publish implements PublisherBase { resolve(techdocsMetadata); } catch (err) { - assertError(err); - this.logger.error(err.message); - reject(new Error(err.message)); + this.logger.error(toError(err).message); + reject(new Error(toError(err).message)); } }); } catch (e) { @@ -496,9 +769,10 @@ export class AwsS3Publish implements PublisherBase { }) .pipe(res); } catch (err) { - assertError(err); this.logger.warn( - `TechDocs S3 router failed to serve static files from bucket ${this.bucketName} at key ${filePath}: ${err.message}`, + `TechDocs S3 router failed to serve static files from bucket ${ + this.bucketName + } at key ${filePath}: ${toError(err).message}`, ); res.status(404).send('File Not Found'); } @@ -550,8 +824,7 @@ export class AwsS3Publish implements PublisherBase { try { newPath = lowerCaseEntityTripletInStoragePath(file); } catch (e) { - assertError(e); - this.logger.warn(e.message); + this.logger.warn(toError(e).message); return; } @@ -579,8 +852,9 @@ export class AwsS3Publish implements PublisherBase { ); } } catch (e) { - assertError(e); - this.logger.warn(`Unable to migrate ${file}: ${e.message}`); + this.logger.warn( + `Unable to migrate ${file}: ${toError(e).message}`, + ); } }, f), ), @@ -598,12 +872,18 @@ export class AwsS3Publish implements PublisherBase { let allObjects: ListObjectsV2CommandOutput; // Iterate through every file in the root of the publisher. do { - allObjects = await this.storageClient.send( - new ListObjectsV2Command({ - Bucket: this.bucketName, - ContinuationToken: nextContinuation, - ...(prefix ? { Prefix: prefix } : {}), - }), + const currentToken = nextContinuation; + allObjects = await this.retryOperation( + async () => { + const listCommand = new ListObjectsV2Command({ + Bucket: this.bucketName, + ContinuationToken: currentToken, + ...(prefix ? { Prefix: prefix } : {}), + }); + return this.storageClient.send(listCommand); + }, + 'GetAllObjects', + this.maxAttempts, ); objects.push( ...(allObjects.Contents || []).map(f => f.Key || '').filter(f => !!f), diff --git a/plugins/techdocs-node/src/stages/publish/azureBlobStorage.test.ts b/plugins/techdocs-node/src/stages/publish/azureBlobStorage.test.ts index 87ac87ff06..8f6435af60 100644 --- a/plugins/techdocs-node/src/stages/publish/azureBlobStorage.test.ts +++ b/plugins/techdocs-node/src/stages/publish/azureBlobStorage.test.ts @@ -18,7 +18,7 @@ import { Entity, DEFAULT_NAMESPACE } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import express from 'express'; import request from 'supertest'; -import path from 'path'; +import path from 'node:path'; import fs from 'fs-extra'; import { AzureBlobStoragePublish } from './azureBlobStorage'; import { diff --git a/plugins/techdocs-node/src/stages/publish/azureBlobStorage.ts b/plugins/techdocs-node/src/stages/publish/azureBlobStorage.ts index 41d2a96bab..1a7e10aa61 100644 --- a/plugins/techdocs-node/src/stages/publish/azureBlobStorage.ts +++ b/plugins/techdocs-node/src/stages/publish/azureBlobStorage.ts @@ -21,11 +21,11 @@ import { } from '@azure/storage-blob'; import { Entity, CompoundEntityRef } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; -import { assertError, ForwardedError } from '@backstage/errors'; +import { ForwardedError, toError } from '@backstage/errors'; import express from 'express'; import JSON5 from 'json5'; import limiterFactory from 'p-limit'; -import { default as path, default as platformPath } from 'path'; +import { default as path, default as platformPath } from 'node:path'; import { bulkStorageOperation, getCloudPathForLocalPath, @@ -152,8 +152,9 @@ export class AzureBlobStoragePublish implements PublisherBase { ); } } catch (e) { - assertError(e); - this.logger.error(`from Azure Blob Storage client library: ${e.message}`); + this.logger.error( + `from Azure Blob Storage client library: ${toError(e).message}`, + ); } this.logger.error( @@ -190,9 +191,10 @@ export class AzureBlobStoragePublish implements PublisherBase { maxPageSize: BATCH_CONCURRENCY, }); } catch (e) { - assertError(e); this.logger.error( - `Unable to list files for Entity ${entity.metadata.name}: ${e.message}`, + `Unable to list files for Entity ${entity.metadata.name}: ${ + toError(e).message + }`, ); } @@ -421,8 +423,7 @@ export class AzureBlobStoragePublish implements PublisherBase { try { newPath = lowerCaseEntityTripletInStoragePath(originalPath); } catch (e) { - assertError(e); - this.logger.warn(e.message); + this.logger.warn(toError(e).message); return; } @@ -431,8 +432,9 @@ export class AzureBlobStoragePublish implements PublisherBase { this.logger.debug(`Migrating ${originalPath}`); await this.renameBlob(originalPath, newPath, removeOriginal); } catch (e) { - assertError(e); - this.logger.warn(`Unable to migrate ${originalPath}: ${e.message}`); + this.logger.warn( + `Unable to migrate ${originalPath}: ${toError(e).message}`, + ); } } diff --git a/plugins/techdocs-node/src/stages/publish/googleStorage.test.ts b/plugins/techdocs-node/src/stages/publish/googleStorage.test.ts index 7b6888fabf..7a16f454a7 100644 --- a/plugins/techdocs-node/src/stages/publish/googleStorage.test.ts +++ b/plugins/techdocs-node/src/stages/publish/googleStorage.test.ts @@ -18,9 +18,9 @@ import { Entity, DEFAULT_NAMESPACE } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import express from 'express'; import request from 'supertest'; -import path from 'path'; +import path from 'node:path'; import fs from 'fs-extra'; -import { Readable } from 'stream'; +import { Readable } from 'node:stream'; import { GoogleGCSPublish } from './googleStorage'; import { createMockDirectory, diff --git a/plugins/techdocs-node/src/stages/publish/googleStorage.ts b/plugins/techdocs-node/src/stages/publish/googleStorage.ts index 3eac9834d2..7f94bb54da 100644 --- a/plugins/techdocs-node/src/stages/publish/googleStorage.ts +++ b/plugins/techdocs-node/src/stages/publish/googleStorage.ts @@ -15,7 +15,7 @@ */ import { Entity, CompoundEntityRef } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; -import { assertError } from '@backstage/errors'; +import { toError } from '@backstage/errors'; import { File, FileExistsResponse, @@ -24,8 +24,8 @@ import { } from '@google-cloud/storage'; import express from 'express'; import JSON5 from 'json5'; -import path from 'path'; -import { Readable } from 'stream'; +import path from 'node:path'; +import { Readable } from 'node:stream'; import { getFileTreeRecursively, getHeadersForFileExtension, @@ -149,14 +149,13 @@ export class GoogleGCSPublish implements PublisherBase { isAvailable: true, }; } catch (err) { - assertError(err); this.logger.error( `Could not retrieve metadata about the GCS bucket ${this.bucketName}. ` + 'Make sure the bucket exists. Also make sure that authentication is setup either by explicitly defining ' + 'techdocs.publisher.googleGcs.credentials in app config or by using environment variables. ' + 'Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage', ); - this.logger.error(`from GCS client library: ${err.message}`); + this.logger.error(`from GCS client library: ${toError(err).message}`); return { isAvailable: false }; } @@ -186,9 +185,10 @@ export class GoogleGCSPublish implements PublisherBase { ); existingFiles = await this.getFilesForFolder(remoteFolder); } catch (e) { - assertError(e); this.logger.error( - `Unable to list files for Entity ${entity.metadata.name}: ${e.message}`, + `Unable to list files for Entity ${entity.metadata.name}: ${ + toError(e).message + }`, ); } diff --git a/plugins/techdocs-node/src/stages/publish/helpers.ts b/plugins/techdocs-node/src/stages/publish/helpers.ts index 8fab446fae..f205e06133 100644 --- a/plugins/techdocs-node/src/stages/publish/helpers.ts +++ b/plugins/techdocs-node/src/stages/publish/helpers.ts @@ -15,7 +15,7 @@ */ import { Entity, DEFAULT_NAMESPACE } from '@backstage/catalog-model'; import mime from 'mime-types'; -import path from 'path'; +import path from 'node:path'; import createLimiter from 'p-limit'; import recursiveReadDir from 'recursive-readdir'; diff --git a/plugins/techdocs-node/src/stages/publish/local.test.ts b/plugins/techdocs-node/src/stages/publish/local.test.ts index be6f3b2fa8..41d14e821f 100644 --- a/plugins/techdocs-node/src/stages/publish/local.test.ts +++ b/plugins/techdocs-node/src/stages/publish/local.test.ts @@ -18,9 +18,9 @@ import { overridePackagePathResolution } from '@backstage/backend-plugin-api/tes import { ConfigReader } from '@backstage/config'; import express from 'express'; import request from 'supertest'; -import * as os from 'os'; +import * as os from 'node:os'; import { LocalPublish } from './local'; -import path from 'path'; +import path from 'node:path'; import { createMockDirectory, mockServices, diff --git a/plugins/techdocs-node/src/stages/publish/local.ts b/plugins/techdocs-node/src/stages/publish/local.ts index 863bf12560..9165972dc1 100644 --- a/plugins/techdocs-node/src/stages/publish/local.ts +++ b/plugins/techdocs-node/src/stages/publish/local.ts @@ -28,9 +28,9 @@ import { import { Config } from '@backstage/config'; import express from 'express'; import fs from 'fs-extra'; -import os from 'os'; +import os from 'node:os'; import createLimiter from 'p-limit'; -import path from 'path'; +import path from 'node:path'; import { PublisherBase, PublishRequest, diff --git a/plugins/techdocs-node/src/stages/publish/migrations/GoogleMigration.ts b/plugins/techdocs-node/src/stages/publish/migrations/GoogleMigration.ts index f8ae8c316e..73089e45a6 100644 --- a/plugins/techdocs-node/src/stages/publish/migrations/GoogleMigration.ts +++ b/plugins/techdocs-node/src/stages/publish/migrations/GoogleMigration.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import { assertError } from '@backstage/errors'; +import { toError } from '@backstage/errors'; import { File } from '@google-cloud/storage'; -import { Writable } from 'stream'; +import { Writable } from 'node:stream'; import { lowerCaseEntityTripletInStoragePath } from '../helpers'; import { LoggerService } from '@backstage/backend-plugin-api'; @@ -47,8 +47,7 @@ export class MigrateWriteStream extends Writable { try { newFile = lowerCaseEntityTripletInStoragePath(file.name); } catch (e) { - assertError(e); - this.logger.warn(e.message); + this.logger.warn(toError(e).message); next(); return; } diff --git a/plugins/techdocs-node/src/stages/publish/openStackSwift.test.ts b/plugins/techdocs-node/src/stages/publish/openStackSwift.test.ts index 4d9972390f..9feab464e8 100644 --- a/plugins/techdocs-node/src/stages/publish/openStackSwift.test.ts +++ b/plugins/techdocs-node/src/stages/publish/openStackSwift.test.ts @@ -23,10 +23,10 @@ import { ConfigReader } from '@backstage/config'; import express from 'express'; import request from 'supertest'; import fs from 'fs-extra'; -import path from 'path'; +import path from 'node:path'; import { OpenStackSwiftPublish } from './openStackSwift'; import { PublisherBase, TechDocsMetadata } from './types'; -import { Stream, Readable } from 'stream'; +import { Stream, Readable } from 'node:stream'; import { createMockDirectory, mockServices, diff --git a/plugins/techdocs-node/src/stages/publish/openStackSwift.ts b/plugins/techdocs-node/src/stages/publish/openStackSwift.ts index 43e7deb2f9..f6ca5e9977 100644 --- a/plugins/techdocs-node/src/stages/publish/openStackSwift.ts +++ b/plugins/techdocs-node/src/stages/publish/openStackSwift.ts @@ -19,10 +19,10 @@ import express from 'express'; import fs from 'fs-extra'; import JSON5 from 'json5'; import createLimiter from 'p-limit'; -import path from 'path'; +import path from 'node:path'; import { SwiftClient } from '@trendyol-js/openstack-swift-sdk'; import { NotFound } from '@trendyol-js/openstack-swift-sdk/lib/types'; -import { Stream, Readable } from 'stream'; +import { Stream, Readable } from 'node:stream'; import { getFileTreeRecursively, @@ -36,7 +36,7 @@ import { ReadinessResponse, TechDocsMetadata, } from './types'; -import { assertError, ForwardedError } from '@backstage/errors'; +import { ForwardedError, toError } from '@backstage/errors'; import { LoggerService } from '@backstage/backend-plugin-api'; const streamToBuffer = (stream: Stream | Readable): Promise => { @@ -129,8 +129,9 @@ export class OpenStackSwiftPublish implements PublisherBase { isAvailable: false, }; } catch (err) { - assertError(err); - this.logger.error(`from OpenStack client library: ${err.message}`); + this.logger.error( + `from OpenStack client library: ${toError(err).message}`, + ); return { isAvailable: false, }; @@ -221,9 +222,9 @@ export class OpenStackSwiftPublish implements PublisherBase { resolve(techdocsMetadata); } catch (err) { - assertError(err); - this.logger.error(err.message); - reject(new Error(err.message)); + const error = toError(err); + this.logger.error(error.message); + reject(error); } } else { reject({ @@ -264,9 +265,10 @@ export class OpenStackSwiftPublish implements PublisherBase { res.send(await streamToBuffer(stream)); } catch (err) { - assertError(err); this.logger.warn( - `TechDocs OpenStack swift router failed to serve content from container ${this.containerName} at path ${filePath}: ${err.message}`, + `TechDocs OpenStack swift router failed to serve content from container ${ + this.containerName + } at path ${filePath}: ${toError(err).message}`, ); res.status(404).send('File Not Found'); } @@ -296,8 +298,7 @@ export class OpenStackSwiftPublish implements PublisherBase { } return false; } catch (err) { - assertError(err); - this.logger.warn(err.message); + this.logger.warn(toError(err).message); return false; } } @@ -316,8 +317,7 @@ export class OpenStackSwiftPublish implements PublisherBase { try { newPath = lowerCaseEntityTripletInStoragePath(file); } catch (e) { - assertError(e); - this.logger.warn(e.message); + this.logger.warn(toError(e).message); return; } @@ -338,8 +338,9 @@ export class OpenStackSwiftPublish implements PublisherBase { await this.storageClient.delete(this.containerName, file); } } catch (e) { - assertError(e); - this.logger.warn(`Unable to migrate ${file}: ${e.message}`); + this.logger.warn( + `Unable to migrate ${file}: ${toError(e).message}`, + ); } }, f), ), diff --git a/plugins/techdocs-react/CHANGELOG.md b/plugins/techdocs-react/CHANGELOG.md index 4c99666ba3..5167aa1241 100644 --- a/plugins/techdocs-react/CHANGELOG.md +++ b/plugins/techdocs-react/CHANGELOG.md @@ -1,5 +1,172 @@ # @backstage/plugin-techdocs-react +## 1.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.8.0 + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/core-components@0.18.9 + - @backstage/config@1.3.7 + - @backstage/core-plugin-api@1.12.5 + +## 1.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.9-next.1 + - @backstage/catalog-model@1.7.8-next.0 + - @backstage/config@1.3.7-next.0 + - @backstage/core-plugin-api@1.12.5-next.2 + - @backstage/frontend-plugin-api@0.16.0-next.2 + +## 1.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + +## 1.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.9-next.0 + - @backstage/frontend-plugin-api@0.15.2-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + - @backstage/catalog-model@1.7.7 + - @backstage/config@1.3.6 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-techdocs-common@0.1.1 + +## 1.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/catalog-model@1.7.7 + +## 1.3.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/core-components@0.18.8-next.1 + +## 1.3.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/version-bridge@1.0.12 + - @backstage/plugin-techdocs-common@0.1.1 + +## 1.3.8 + +### Patch Changes + +- 22dce2b: TechDocs addons in the new frontend system now use a Utility API pattern instead of multiple attachment points. The `AddonBlueprint` now uses this new approach, and while addons created with older versions still work, they will produce a deprecation warning and will stop working in a future release. + + As part of this change, the `techDocsAddonDataRef` alpha export was removed. + +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/core-components@0.18.7 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/version-bridge@1.0.12 + +## 1.3.8-next.1 + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/version-bridge@1.0.12-next.0 + +## 1.3.8-next.0 + +### Patch Changes + +- 22dce2b: TechDocs addons in the new frontend system now use a Utility API pattern instead of multiple attachment points. The `AddonBlueprint` now uses this new approach, and while addons created with older versions still work, they will produce a deprecation warning and will stop working in a future release. + + As part of this change, the `techDocsAddonDataRef` alpha export was removed. + +- Updated dependencies + - @backstage/core-components@0.18.6-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-techdocs-common@0.1.1 + +## 1.3.7 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + +## 1.3.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + +## 1.3.6 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + +## 1.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-techdocs-common@0.1.1 + +## 1.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + ## 1.3.5-next.0 ### Patch Changes diff --git a/plugins/techdocs-react/knip-report.md b/plugins/techdocs-react/knip-report.md index 3e7476ef55..099ee4ff49 100644 --- a/plugins/techdocs-react/knip-report.md +++ b/plugins/techdocs-react/knip-report.md @@ -1,9 +1,8 @@ # Knip report -## Unused dependencies (2) +## Unused dependencies (1) -| Name | Location | Severity | -| :------------------------- | :----------- | :------- | -| @backstage/core-components | plugins/techdocs-react/package.json | error | -| react-helmet | plugins/techdocs-react/package.json | error | +| Name | Location | Severity | +| :------------------------- | :---------------- | :------- | +| @backstage/core-components | package.json:63:6 | error | diff --git a/plugins/techdocs-react/package.json b/plugins/techdocs-react/package.json index a68551568e..ce46de2f3b 100644 --- a/plugins/techdocs-react/package.json +++ b/plugins/techdocs-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-react", - "version": "1.3.5-next.0", + "version": "1.3.10", "description": "Shared frontend utilities for TechDocs and Addons", "backstage": { "role": "web-library", @@ -69,7 +69,6 @@ "@material-ui/styles": "^4.11.0", "jss": "~10.10.0", "lodash": "^4.17.21", - "react-helmet": "6.1.0", "react-use": "^17.2.4" }, "devDependencies": { @@ -81,13 +80,13 @@ "@types/react": "^18.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/plugins/techdocs-react/report-alpha.api.md b/plugins/techdocs-react/report-alpha.api.md index 1fed274f3d..cb2a86a037 100644 --- a/plugins/techdocs-react/report-alpha.api.md +++ b/plugins/techdocs-react/report-alpha.api.md @@ -31,13 +31,6 @@ export const attachTechDocsAddonComponentData:

    ( data: TechDocsAddonOptions, ) => void; -// @alpha (undocumented) -export const techDocsAddonDataRef: ConfigurableExtensionDataRef< - TechDocsAddonOptions, - 'techdocs.addon', - {} ->; - // @public export const TechDocsAddonLocations: Readonly<{ readonly Header: 'Header'; diff --git a/plugins/techdocs-react/report.api.md b/plugins/techdocs-react/report.api.md index 62ac6375fa..eb6d0dccb3 100644 --- a/plugins/techdocs-react/report.api.md +++ b/plugins/techdocs-react/report.api.md @@ -3,7 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { ApiRef } from '@backstage/core-plugin-api'; +import { ApiRef } from '@backstage/frontend-plugin-api'; import { AsyncState } from 'react-use/esm/useAsync'; import { ComponentType } from 'react'; import { CompoundEntityRef } from '@backstage/catalog-model'; diff --git a/plugins/techdocs-react/src/alpha.ts b/plugins/techdocs-react/src/alpha.ts index 83c0444753..031ec6711f 100644 --- a/plugins/techdocs-react/src/alpha.ts +++ b/plugins/techdocs-react/src/alpha.ts @@ -29,8 +29,7 @@ import { /** @alpha */ export type { TechDocsAddonOptions, TechDocsAddonLocations } from './types'; -/** @alpha */ -export const techDocsAddonDataRef = +const techDocsAddonDataRef = createExtensionDataRef().with({ id: 'techdocs.addon', }); @@ -41,10 +40,7 @@ export const techDocsAddonDataRef = */ export const AddonBlueprint = createExtensionBlueprint({ kind: 'addon', - attachTo: [ - { id: 'page:techdocs/reader', input: 'addons' }, - { id: 'entity-content:techdocs', input: 'addons' }, - ], + attachTo: { id: 'api:techdocs/addons', input: 'addons' }, output: [techDocsAddonDataRef], factory: (params: TechDocsAddonOptions) => [techDocsAddonDataRef(params)], dataRefs: { diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index 59e4b25d98..72660055a7 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,407 @@ # @backstage/plugin-techdocs +## 1.17.3 + +### Patch Changes + +- dc3cc87: Migrated the TechDocs alpha plugin pages to use BUI components. The index page and reader page now use BUI `Header` and `Container` instead of legacy `Page`/`Content`/`ContentHeader` wrappers. Added a `SupportButton` as a plugin header action. Changed plugin title to "Documentation" and icon to `RiArticleLine`. +- e5af44c: Replaced deprecated `humanizeEntityRef` usage with the Catalog Presentation API. +- Updated dependencies + - @backstage/ui@0.14.0 + - @backstage/errors@1.3.0 + - @backstage/theme@0.7.3 + - @backstage/catalog-model@1.8.0 + - @backstage/plugin-catalog-react@2.1.2 + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/core-components@0.18.9 + - @backstage/catalog-client@1.15.0 + - @backstage/integration@2.0.1 + - @backstage/config@1.3.7 + - @backstage/core-plugin-api@1.12.5 + - @backstage/integration-react@1.2.17 + - @backstage/plugin-auth-react@0.1.26 + - @backstage/plugin-search-common@1.2.23 + - @backstage/plugin-search-react@1.11.1 + - @backstage/plugin-techdocs-react@1.3.10 + +## 1.17.3-next.2 + +### Patch Changes + +- e5af44c: Replaced deprecated `humanizeEntityRef` usage with the Catalog Presentation API. +- Updated dependencies + - @backstage/ui@0.14.0-next.2 + - @backstage/errors@1.3.0-next.0 + - @backstage/theme@0.7.3-next.0 + - @backstage/core-components@0.18.9-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.2 + - @backstage/integration@2.0.1-next.0 + - @backstage/catalog-client@1.14.1-next.0 + - @backstage/catalog-model@1.7.8-next.0 + - @backstage/config@1.3.7-next.0 + - @backstage/core-plugin-api@1.12.5-next.2 + - @backstage/frontend-plugin-api@0.16.0-next.2 + - @backstage/plugin-auth-react@0.1.26-next.1 + - @backstage/plugin-search-react@1.11.1-next.2 + - @backstage/plugin-techdocs-react@1.3.10-next.2 + - @backstage/integration-react@1.2.17-next.1 + - @backstage/plugin-search-common@1.2.23-next.0 + +## 1.17.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-search-react@1.11.1-next.1 + - @backstage/plugin-techdocs-react@1.3.10-next.1 + +## 1.17.3-next.0 + +### Patch Changes + +- dc3cc87: Migrated the TechDocs alpha plugin pages to use BUI components. The index page and reader page now use BUI `Header` and `Container` instead of legacy `Page`/`Content`/`ContentHeader` wrappers. Added a `SupportButton` as a plugin header action. Changed plugin title to "Documentation" and icon to `RiArticleLine`. +- Updated dependencies + - @backstage/ui@0.14.0-next.0 + - @backstage/plugin-catalog-react@2.1.1-next.0 + - @backstage/core-components@0.18.9-next.0 + - @backstage/frontend-plugin-api@0.15.2-next.0 + - @backstage/integration-react@1.2.17-next.0 + - @backstage/plugin-auth-react@0.1.26-next.0 + - @backstage/plugin-search-react@1.11.1-next.0 + - @backstage/plugin-techdocs-react@1.3.10-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + - @backstage/catalog-client@1.14.0 + - @backstage/catalog-model@1.7.7 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration@2.0.0 + - @backstage/theme@0.7.2 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-techdocs-common@0.1.1 + +## 1.17.1 + +### Patch Changes + +- 9795d30: chore(deps): bump `dompurify` from 3.3.1 to 3.3.2 +- 30e08df: Added `documentation` as the default entity content group for the TechDocs entity content tab. +- 3f36ce1: Updated alpha plugin icons to follow the new frontend icon sizing rules when rendered in plugin and navigation surfaces. +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/plugin-search-react@1.11.0 + - @backstage/catalog-client@1.14.0 + - @backstage/integration@2.0.0 + - @backstage/catalog-model@1.7.7 + - @backstage/integration-react@1.2.16 + - @backstage/plugin-auth-react@0.1.25 + - @backstage/plugin-techdocs-react@1.3.9 + +## 1.17.1-next.2 + +### Patch Changes + +- 9795d30: chore(deps): bump `dompurify` from 3.3.1 to 3.3.2 +- Updated dependencies + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/catalog-client@1.14.0-next.2 + - @backstage/plugin-catalog-react@2.1.0-next.2 + - @backstage/integration@2.0.0-next.2 + - @backstage/core-components@0.18.8-next.1 + - @backstage/plugin-search-react@1.10.5-next.1 + - @backstage/plugin-techdocs-react@1.3.9-next.1 + +## 1.17.1-next.1 + +### Patch Changes + +- 30e08df: Added `documentation` as the default entity content group for the TechDocs entity content tab. +- Updated dependencies + - @backstage/catalog-client@1.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/integration@2.0.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/integration-react@1.2.16-next.1 + - @backstage/theme@0.7.2 + - @backstage/plugin-auth-react@0.1.25-next.0 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-search-react@1.10.5-next.0 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-react@1.3.9-next.0 + +## 1.17.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-react@1.10.5-next.0 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/integration@1.21.0-next.0 + - @backstage/catalog-client@1.13.1-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.16-next.0 + - @backstage/theme@0.7.2 + - @backstage/plugin-auth-react@0.1.25-next.0 + - @backstage/plugin-search-common@1.2.22 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-react@1.3.9-next.0 + +## 1.17.0 + +### Minor Changes + +- 27798df: Add two config values to the `page:techdocs/reader` extension that configure default layout, `withoutSearch` and `withoutHeader`. Default are unchanged to `false`. + + E.g. to disable the search and header on the Techdocs Reader Page: + + ```yaml + app: + extensions: + - page:techdocs/reader: + config: + withoutSearch: true + withoutHeader: true + ``` + +### Patch Changes + +- 7feb83b: Adjusted to use the new `@backstage/filter-predicates` types for predicate expressions. +- 491a06c: Add the ability to show icons for the tabs on the entity page (new frontend) +- 018ca87: Added `title` and `icon` to the plugin definition for the new frontend system. +- 9e29545: Improve sidebars (nav/TOC) layout and scrolling +- 22dce2b: TechDocs addons in the new frontend system now use a Utility API pattern instead of multiple attachment points. The `AddonBlueprint` now uses this new approach, and while addons created with older versions still work, they will produce a deprecation warning and will stop working in a future release. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- 0a88779: Added title prop to OffsetPaginatedDocsTable for proper display +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/integration@1.20.0 + - @backstage/core-components@0.18.7 + - @backstage/plugin-search-common@1.2.22 + - @backstage/theme@0.7.2 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/catalog-client@1.13.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-techdocs-react@1.3.8 + - @backstage/integration-react@1.2.15 + - @backstage/plugin-search-react@1.10.3 + - @backstage/plugin-auth-react@0.1.24 + +## 1.16.3-next.2 + +### Patch Changes + +- 7feb83b: Adjusted to use the new `@backstage/filter-predicates` types for predicate expressions. +- 491a06c: Add the ability to show icons for the tabs on the entity page (new frontend) +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/integration@1.20.0-next.2 + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/catalog-client@1.12.2-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/integration-react@1.2.15-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-techdocs-react@1.3.8-next.1 + - @backstage/plugin-search-react@1.10.3-next.2 + - @backstage/plugin-auth-react@0.1.24-next.1 + - @backstage/theme@0.7.2-next.1 + +## 1.16.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/theme@0.7.2-next.0 + - @backstage/integration@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/plugin-search-react@1.10.3-next.1 + - @backstage/core-components@0.18.7-next.1 + - @backstage/plugin-techdocs-react@1.3.8-next.0 + - @backstage/integration-react@1.2.15-next.1 + +## 1.16.3-next.0 + +### Patch Changes + +- 9e29545: Improve sidebars (nav/TOC) layout and scrolling +- 22dce2b: TechDocs addons in the new frontend system now use a Utility API pattern instead of multiple attachment points. The `AddonBlueprint` now uses this new approach, and while addons created with older versions still work, they will produce a deprecation warning and will stop working in a future release. +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/plugin-search-common@1.2.22-next.0 + - @backstage/integration@1.19.3-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/plugin-techdocs-react@1.3.8-next.0 + - @backstage/plugin-search-react@1.10.3-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.15-next.0 + - @backstage/theme@0.7.1 + - @backstage/plugin-auth-react@0.1.24-next.0 + - @backstage/plugin-techdocs-common@0.1.1 + +## 1.16.2 + +### Patch Changes + +- 0afb8a6: Corrected color of some elements such as Grid cards and Tables. +- 94ff7ab: Code block "Copy to clipboard" button was not positioned correctly for docs built with `mkdocs-material>=9.7` +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + - @backstage/integration@1.19.2 + - @backstage/plugin-search-react@1.10.2 + - @backstage/integration-react@1.2.14 + - @backstage/plugin-auth-react@0.1.23 + - @backstage/plugin-catalog-react@1.21.5 + - @backstage/plugin-techdocs-react@1.3.7 + +## 1.16.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-auth-react@0.1.23-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.1 + - @backstage/plugin-search-react@1.10.2-next.0 + - @backstage/plugin-techdocs-react@1.3.7-next.0 + +## 1.16.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.2-next.0 + - @backstage/integration-react@1.2.14-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.0 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/plugin-search-react@1.10.1 + +## 1.16.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- 5c33821: Fixed schema type for additionalAllowedURIProtocols +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- 592361e: The `techdocs` config is now marked as optional. +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-search-react@1.10.1 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-auth-react@0.1.22 + - @backstage/plugin-techdocs-react@1.3.6 + +## 1.16.1-next.2 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/plugin-auth-react@0.1.22-next.0 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-search-react@1.10.1-next.0 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + +## 1.16.1-next.1 + +### Patch Changes + +- 592361e: The `techdocs` config is now marked as optional. +- Updated dependencies + - @backstage/core-components@0.18.4-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.1 + +## 1.16.1-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/plugin-search-react@1.10.1-next.0 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-auth-react@0.1.22-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-techdocs-common@0.1.1 + +## 1.16.0 + +### Minor Changes + +- a521911: Add support for customizable icons in `SearchResultListItemBlueprint` and related components + +### Patch Changes + +- 71c22f3: Removed/moved unused dependencies +- a4d4a70: Fixed an issue where the entire TechDocs page would re-render when navigating between pages within the same entity's documentation. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-search-react@1.10.0 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/integration@1.18.2 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-techdocs-react@1.3.5 + - @backstage/catalog-client@1.12.1 + - @backstage/integration-react@1.2.12 + - @backstage/plugin-auth-react@0.1.21 + - @backstage/plugin-search-common@1.2.21 + ## 1.16.0-next.1 ### Minor Changes diff --git a/plugins/techdocs/config.d.ts b/plugins/techdocs/config.d.ts index 7294f52816..0e9b7abdda 100644 --- a/plugins/techdocs/config.d.ts +++ b/plugins/techdocs/config.d.ts @@ -19,7 +19,7 @@ export interface Config { * Configuration options for the techdocs plugin * @see http://backstage.io/docs/features/techdocs/configuration */ - techdocs: { + techdocs?: { /** * Documentation building process depends on the builder attr * @visibility frontend @@ -67,7 +67,7 @@ export interface Config { * @see: https://raw.githubusercontent.com/cure53/DOMPurify/master/src/regexp.ts * @visibility frontend */ - additionalAllowedURIProtocols?: string; + additionalAllowedURIProtocols?: string[]; }; }; } diff --git a/plugins/techdocs/knip-report.md b/plugins/techdocs/knip-report.md index 97d5b385fd..f16915c5ae 100644 --- a/plugins/techdocs/knip-report.md +++ b/plugins/techdocs/knip-report.md @@ -1,3 +1,8 @@ # Knip report +## Unused devDependencies (1) + +| Name | Location | Severity | +| :--------------- | :----------------- | :------- | +| @types/dompurify | package.json:103:6 | error | diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 29b6e843d6..de9b4897c0 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs", - "version": "1.16.0-next.1", + "version": "1.17.3", "description": "The Backstage plugin that renders technical documentation for your components", "backstage": { "role": "frontend-plugin", @@ -62,7 +62,6 @@ "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", - "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", @@ -76,16 +75,19 @@ "@backstage/plugin-techdocs-common": "workspace:^", "@backstage/plugin-techdocs-react": "workspace:^", "@backstage/theme": "workspace:^", + "@backstage/ui": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", "@material-ui/styles": "^4.10.0", "@microsoft/fetch-event-source": "^2.0.1", - "dompurify": "^3.2.4", + "@remixicon/react": "^4.6.0", + "dompurify": "^3.3.2", "git-url-parse": "^15.0.0", "lodash": "^4.17.21", "react-helmet": "6.1.0", - "react-use": "^17.2.4" + "react-use": "^17.2.4", + "zod": "^4.0.0" }, "devDependencies": { "@backstage/cli": "workspace:^", @@ -102,13 +104,13 @@ "@types/react": "^18.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/plugins/techdocs/report-alpha.api.md b/plugins/techdocs/report-alpha.api.md index 8a17625ac2..81d6b5a547 100644 --- a/plugins/techdocs/report-alpha.api.md +++ b/plugins/techdocs/report-alpha.api.md @@ -9,16 +9,20 @@ import { ApiFactory } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { defaultEntityContentGroups } from '@backstage/plugin-catalog-react/alpha'; import { Entity } from '@backstage/catalog-model'; -import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; -import { IconComponent } from '@backstage/core-plugin-api'; +import { FilterPredicate } from '@backstage/filter-predicates'; +import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { IconLinkVerticalProps } from '@backstage/core-components'; import { JSX as JSX_2 } from 'react'; +import { JSXElementConstructor } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; -import { RouteRef } from '@backstage/frontend-plugin-api'; +import { ReactElement } from 'react'; +import { RouteRef } from '@backstage/core-plugin-api'; +import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api'; import { SearchResultItemExtensionComponent } from '@backstage/plugin-search-react/alpha'; import { SearchResultItemExtensionPredicate } from '@backstage/plugin-search-react/alpha'; import { SearchResultListItemBlueprintParams } from '@backstage/plugin-search-react/alpha'; @@ -52,6 +56,34 @@ const _default: OverridableFrontendPlugin< params: ApiFactory, ) => ExtensionBlueprintParams; }>; + 'api:techdocs/addons': OverridableExtensionDefinition<{ + config: {}; + configInput: {}; + output: ExtensionDataRef; + inputs: { + addons: ExtensionInput< + ConfigurableExtensionDataRef< + TechDocsAddonOptions, + 'techdocs.addon', + {} + >, + { + singleton: false; + optional: false; + internal: false; + } + >; + }; + kind: 'api'; + name: 'addons'; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; + }>; 'api:techdocs/storage': OverridableExtensionDefinition<{ kind: 'api'; name: 'storage'; @@ -83,6 +115,7 @@ const _default: OverridableFrontendPlugin< { singleton: boolean; optional: boolean; + internal?: boolean; } >; }; @@ -94,25 +127,27 @@ const _default: OverridableFrontendPlugin< config: { path: string | undefined; title: string | undefined; - filter: EntityPredicate | undefined; + filter: FilterPredicate | undefined; group: string | false | undefined; + icon: string | undefined; }; configInput: { - filter?: EntityPredicate | undefined; - title?: string | undefined; path?: string | undefined; + title?: string | undefined; + filter?: FilterPredicate | undefined; group?: string | false | undefined; + icon?: string | undefined; }; output: | ExtensionDataRef - | ExtensionDataRef | ExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; } > + | ExtensionDataRef | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', @@ -134,6 +169,13 @@ const _default: OverridableFrontendPlugin< { optional: true; } + > + | ExtensionDataRef< + string | ReactElement>, + 'catalog.entity-content-icon', + { + optional: true; + } >; inputs: { addons: ExtensionInput< @@ -145,6 +187,7 @@ const _default: OverridableFrontendPlugin< { singleton: false; optional: false; + internal: false; } >; emptyState: ExtensionInput< @@ -158,6 +201,7 @@ const _default: OverridableFrontendPlugin< { singleton: true; optional: true; + internal: false; } >; }; @@ -170,9 +214,10 @@ const _default: OverridableFrontendPlugin< title: string; defaultGroup?: [Error: `Use the 'group' param instead`]; group?: keyof defaultEntityContentGroups | (string & {}); + icon?: string | ReactElement; loader: () => Promise; - routeRef?: RouteRef; - filter?: string | EntityPredicate | ((entity: Entity) => boolean); + routeRef?: RouteRef_2; + filter?: string | FilterPredicate | ((entity: Entity) => boolean); }; }>; 'entity-icon-link:techdocs/read-docs': OverridableExtensionDefinition<{ @@ -181,12 +226,12 @@ const _default: OverridableFrontendPlugin< config: { label: string | undefined; title: string | undefined; - filter: EntityPredicate | undefined; + filter: FilterPredicate | undefined; }; configInput: { - filter?: EntityPredicate | undefined; label?: string | undefined; title?: string | undefined; + filter?: FilterPredicate | undefined; }; output: | ExtensionDataRef< @@ -211,19 +256,23 @@ const _default: OverridableFrontendPlugin< inputs: {}; params: { useProps: () => Omit; - filter?: EntityPredicate | ((entity: Entity) => boolean); + filter?: FilterPredicate | ((entity: Entity) => boolean); }; }>; 'nav-item:techdocs': OverridableExtensionDefinition<{ kind: 'nav-item'; name: undefined; - config: {}; - configInput: {}; + config: { + title: string | undefined; + }; + configInput: { + title?: string | undefined; + }; output: ExtensionDataRef< { title: string; icon: IconComponent; - routeRef: RouteRef; + routeRef: RouteRef_2; }, 'core.nav-item.target', {} @@ -232,7 +281,7 @@ const _default: OverridableFrontendPlugin< params: { title: string; icon: IconComponent; - routeRef: RouteRef; + routeRef: RouteRef_2; }; }>; 'page:techdocs': OverridableExtensionDefinition<{ @@ -240,46 +289,145 @@ const _default: OverridableFrontendPlugin< name: undefined; config: { path: string | undefined; + title: string | undefined; }; configInput: { path?: string | undefined; + title?: string | undefined; }; output: | ExtensionDataRef - | ExtensionDataRef | ExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; } - >; - inputs: {}; - params: { - defaultPath?: [Error: `Use the 'path' param instead`]; - path: string; - loader: () => Promise; - routeRef?: RouteRef; - }; - }>; - 'page:techdocs/reader': OverridableExtensionDefinition<{ - config: { - path: string | undefined; - }; - configInput: { - path?: string | undefined; - }; - output: - | ExtensionDataRef + > | ExtensionDataRef | ExtensionDataRef< - RouteRef, - 'core.routing.ref', + string, + 'core.title', + { + optional: true; + } + > + | ExtensionDataRef< + IconElement, + 'core.icon', { optional: true; } >; inputs: { + pages: ExtensionInput< + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef_2, + 'core.routing.ref', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >, + { + singleton: false; + optional: false; + internal: false; + } + >; + }; + params: { + path: string; + title?: string; + icon?: IconElement; + loader?: () => Promise; + routeRef?: RouteRef_2; + noHeader?: boolean; + }; + }>; + 'page:techdocs/reader': OverridableExtensionDefinition<{ + config: { + withoutSearch: boolean; + withoutHeader: boolean; + path: string | undefined; + title: string | undefined; + }; + configInput: { + withoutSearch?: boolean | undefined; + withoutHeader?: boolean | undefined; + path?: string | undefined; + title?: string | undefined; + }; + output: + | ExtensionDataRef + | ExtensionDataRef< + RouteRef_2, + 'core.routing.ref', + { + optional: true; + } + > + | ExtensionDataRef + | ExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >; + inputs: { + pages: ExtensionInput< + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef_2, + 'core.routing.ref', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >, + { + singleton: false; + optional: false; + internal: false; + } + >; addons: ExtensionInput< ConfigurableExtensionDataRef< TechDocsAddonOptions, @@ -289,16 +437,30 @@ const _default: OverridableFrontendPlugin< { singleton: false; optional: false; + internal: false; } >; }; kind: 'page'; name: 'reader'; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; + title?: string; + icon?: IconElement; + loader?: () => Promise; + routeRef?: RouteRef_2; + noHeader?: boolean; + }; + }>; + 'plugin-header-action:techdocs': OverridableExtensionDefinition<{ + kind: 'plugin-header-action'; + name: undefined; + config: {}; + configInput: {}; + output: ExtensionDataRef; + inputs: {}; + params: { loader: () => Promise; - routeRef?: RouteRef; }; }>; 'search-result-list-item:techdocs': OverridableExtensionDefinition<{ @@ -307,15 +469,13 @@ const _default: OverridableFrontendPlugin< lineClamp: number; asLink: boolean; asListItem: boolean; - } & { noTrack: boolean; }; configInput: { title?: string | undefined; lineClamp?: number | undefined; - asListItem?: boolean | undefined; asLink?: boolean | undefined; - } & { + asListItem?: boolean | undefined; noTrack?: boolean | undefined; }; output: ExtensionDataRef< @@ -327,15 +487,7 @@ const _default: OverridableFrontendPlugin< 'search.search-result-list-item.item', {} >; - inputs: { - [x: string]: ExtensionInput< - ExtensionDataRef, - { - singleton: boolean; - optional: boolean; - } - >; - }; + inputs: {}; kind: 'search-result-list-item'; name: undefined; params: SearchResultListItemBlueprintParams; @@ -351,15 +503,13 @@ export const techDocsSearchResultListItemExtension: OverridableExtensionDefiniti lineClamp: number; asLink: boolean; asListItem: boolean; - } & { noTrack: boolean; }; configInput: { title?: string | undefined; lineClamp?: number | undefined; - asListItem?: boolean | undefined; asLink?: boolean | undefined; - } & { + asListItem?: boolean | undefined; noTrack?: boolean | undefined; }; output: ExtensionDataRef< @@ -371,15 +521,7 @@ export const techDocsSearchResultListItemExtension: OverridableExtensionDefiniti 'search.search-result-list-item.item', {} >; - inputs: { - [x: string]: ExtensionInput< - ExtensionDataRef, - { - singleton: boolean; - optional: boolean; - } - >; - }; + inputs: {}; kind: 'search-result-list-item'; name: undefined; params: SearchResultListItemBlueprintParams; diff --git a/plugins/techdocs/report.api.md b/plugins/techdocs/report.api.md index 2369466674..9e4ed4ce45 100644 --- a/plugins/techdocs/report.api.md +++ b/plugins/techdocs/report.api.md @@ -3,7 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { ApiRef } from '@backstage/core-plugin-api'; +import { ApiRef } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { CompoundEntityRef } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; @@ -66,11 +66,7 @@ export type ContentStateTypes = | 'CONTENT_FRESH'; // @public -export const CustomDocsPanel: ({ - config, - entities, - index, -}: { +export const CustomDocsPanel: (input: { config: PanelConfig; entities: Entity[]; index: number; @@ -151,12 +147,11 @@ export type DocsTableRow = { }; // @public -export const EmbeddedDocsRouter: ({ - children, - withSearch, -}: PropsWithChildren<{ - withSearch?: boolean; -}>) => JSX_2.Element; +export const EmbeddedDocsRouter: ( + input: PropsWithChildren<{ + withSearch?: boolean; + }>, +) => JSX_2.Element; // @public export const EntityListDocsGrid: ( @@ -208,12 +203,11 @@ export type EntityListDocsTableProps = { }; // @public -export const EntityTechdocsContent: ({ - children, - withSearch, -}: PropsWithChildren<{ - withSearch?: boolean; -}>) => JSX_2.Element; +export const EntityTechdocsContent: ( + input: PropsWithChildren<{ + withSearch?: boolean; + }>, +) => JSX_2.Element; // @public export const InfoCardGrid: (props: InfoCardGridProps) => JSX_2.Element | null; diff --git a/plugins/techdocs/src/alpha/addonsApi.ts b/plugins/techdocs/src/alpha/addonsApi.ts new file mode 100644 index 0000000000..86d4739f46 --- /dev/null +++ b/plugins/techdocs/src/alpha/addonsApi.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 { + ApiBlueprint, + createApiRef, + createExtensionInput, +} from '@backstage/frontend-plugin-api'; +import { AddonBlueprint } from '@backstage/plugin-techdocs-react/alpha'; +import { TechDocsAddonOptions } from '@backstage/plugin-techdocs-react'; + +interface TechDocsAddonsApi { + getAddons(): TechDocsAddonOptions[]; +} + +export const techdocsAddonsApiRef = createApiRef({ + id: 'plugin.techdocs.addons', +}); + +export const TechDocsAddonsApiExtension = ApiBlueprint.makeWithOverrides({ + name: 'addons', + inputs: { + addons: createExtensionInput([AddonBlueprint.dataRefs.addon]), + }, + factory(originalFactory, { inputs }) { + const addons = inputs.addons.map(output => + output.get(AddonBlueprint.dataRefs.addon), + ); + return originalFactory(defineParams => + defineParams({ + api: techdocsAddonsApiRef, + deps: {}, + factory: () => ({ + getAddons: () => addons, + }), + }), + ); + }, +}); diff --git a/plugins/techdocs/src/alpha/components/TechDocsIndexPageContent.test.tsx b/plugins/techdocs/src/alpha/components/TechDocsIndexPageContent.test.tsx new file mode 100644 index 0000000000..579d624a7f --- /dev/null +++ b/plugins/techdocs/src/alpha/components/TechDocsIndexPageContent.test.tsx @@ -0,0 +1,75 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ApiProvider } from '@backstage/core-app-api'; +import { configApiRef, storageApiRef } from '@backstage/core-plugin-api'; +import { + MockStarredEntitiesApi, + catalogApiRef, + starredEntitiesApiRef, +} from '@backstage/plugin-catalog-react'; +import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils'; +import { + TestApiRegistry, + mockApis, + renderInTestApp, +} from '@backstage/test-utils'; +import { screen } from '@testing-library/react'; +import { rootDocsRouteRef } from '../../routes'; +import { TechDocsIndexPageContent } from './TechDocsIndexPageContent'; + +const mockCatalogApi = catalogApiMock({ + entities: [ + { + apiVersion: 'version', + kind: 'User', + metadata: { + name: 'owned', + namespace: 'default', + }, + }, + ], +}); + +describe('', () => { + const configApi = mockApis.config({ + data: { organization: { name: 'My Company' } }, + }); + + const apiRegistry = TestApiRegistry.from( + [catalogApiRef, mockCatalogApi], + [configApiRef, configApi], + [storageApiRef, mockApis.storage()], + [starredEntitiesApiRef, new MockStarredEntitiesApi()], + ); + + it('should render the filter layout with pickers', async () => { + await renderInTestApp( + + + , + { + mountedRoutes: { + '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, + }, + }, + ); + + // The UserListPicker renders filter options + expect(await screen.findByTestId('user-picker-owned')).toBeInTheDocument(); + expect(screen.getByTestId('user-picker-all')).toBeInTheDocument(); + }); +}); diff --git a/plugins/techdocs/src/alpha/components/TechDocsIndexPageContent.tsx b/plugins/techdocs/src/alpha/components/TechDocsIndexPageContent.tsx new file mode 100644 index 0000000000..16b866b0c0 --- /dev/null +++ b/plugins/techdocs/src/alpha/components/TechDocsIndexPageContent.tsx @@ -0,0 +1,60 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Container } from '@backstage/ui'; +import { + CatalogFilterLayout, + EntityListProvider, + EntityOwnerPicker, + EntityTagPicker, + UserListPicker, +} from '@backstage/plugin-catalog-react'; +import { TechDocsPicker } from '../../home/components/TechDocsPicker'; +import { EntityListDocsTable } from '../../home/components/Tables'; +import type { TechDocsIndexPageProps } from '../../home/components/TechDocsIndexPage'; + +export const TechDocsIndexPageContent = (props: TechDocsIndexPageProps) => { + const { + initialFilter = 'owned', + columns, + actions, + ownerPickerMode, + pagination, + options, + } = props; + + return ( + + + + + + + + + + + + + + + + ); +}; diff --git a/plugins/techdocs/src/alpha/components/TechDocsReaderContent.test.tsx b/plugins/techdocs/src/alpha/components/TechDocsReaderContent.test.tsx new file mode 100644 index 0000000000..cb443b3533 --- /dev/null +++ b/plugins/techdocs/src/alpha/components/TechDocsReaderContent.test.tsx @@ -0,0 +1,238 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ReactNode } from 'react'; +import { waitFor } from '@testing-library/react'; + +import { + CompoundEntityRef, + getCompoundEntityRef, +} from '@backstage/catalog-model'; +import { + techdocsApiRef, + TechDocsReaderPageProvider, + useShadowRootElements, +} from '@backstage/plugin-techdocs-react'; +import { + renderInTestApp, + TestApiProvider, + mockApis, +} from '@backstage/test-utils'; +import { configApiRef } from '@backstage/core-plugin-api'; + +const useTechDocsReaderDom = jest.fn(); +jest.mock('../../reader/components/TechDocsReaderPageContent/dom', () => ({ + ...jest.requireActual( + '../../reader/components/TechDocsReaderPageContent/dom', + ), + useTechDocsReaderDom: (...args: any[]) => useTechDocsReaderDom(...args), +})); +const useReaderState = jest.fn(); +jest.mock('../../reader/components/useReaderState', () => ({ + ...jest.requireActual('../../reader/components/useReaderState'), + useReaderState: (...args: any[]) => useReaderState(...args), +})); +const useShadowDomStylesLoading = jest.fn().mockReturnValue(false); +jest.mock('@backstage/plugin-techdocs-react', () => ({ + ...jest.requireActual('@backstage/plugin-techdocs-react'), + useShadowDomStylesLoading: (...args: any[]) => + useShadowDomStylesLoading(...args), + useShadowRootElements: jest.fn(), +})); + +import { TechDocsReaderContent } from './TechDocsReaderContent'; + +const mockEntityMetadata = { + locationMetadata: { + type: 'github', + target: 'https://example.com/', + }, + apiVersion: 'v1', + kind: 'test', + metadata: { + name: 'test-name', + namespace: 'test-namespace', + }, + spec: { + owner: 'test', + }, +}; + +const mockTechDocsMetadata = { + site_name: 'test-site-name', + site_description: 'test-site-desc', +}; + +const getEntityMetadata = jest.fn(); +const getTechDocsMetadata = jest.fn(); + +const techdocsApiMock = { + getEntityMetadata, + getTechDocsMetadata, +}; + +const Wrapper = ({ + entityRef = { + kind: mockEntityMetadata.kind, + name: mockEntityMetadata.metadata.name, + namespace: mockEntityMetadata.metadata.namespace!!, + }, + children, +}: { + entityRef?: CompoundEntityRef; + children: ReactNode; +}) => ( + + + {children} + + +); + +describe('', () => { + const useShadowRootElementsMock = useShadowRootElements as jest.Mock; + + beforeEach(() => { + useShadowRootElementsMock.mockReturnValue([]); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should render techdocs content', async () => { + getEntityMetadata.mockResolvedValue(mockEntityMetadata); + getTechDocsMetadata.mockResolvedValue(mockTechDocsMetadata); + useTechDocsReaderDom.mockReturnValue(document.createElement('html')); + useReaderState.mockReturnValue({ state: 'cached' }); + + const rendered = await renderInTestApp( + + + , + ); + + await waitFor(() => { + expect( + rendered.getByTestId('techdocs-native-shadowroot'), + ).toBeInTheDocument(); + }); + }); + + it('should render with default path', async () => { + getEntityMetadata.mockResolvedValue(mockEntityMetadata); + getTechDocsMetadata.mockResolvedValue(mockTechDocsMetadata); + useTechDocsReaderDom.mockReturnValue(document.createElement('html')); + useReaderState.mockReturnValue({ state: 'cached' }); + + const defaultPath = '/some/path'; + + const rendered = await renderInTestApp( + + + , + ); + + await waitFor(() => { + expect( + rendered.getByTestId('techdocs-native-shadowroot'), + ).toBeInTheDocument(); + }); + + const entityRef = getCompoundEntityRef(mockEntityMetadata); + expect(useTechDocsReaderDom).toHaveBeenCalledWith(entityRef, defaultPath); + }); + + it('should not render content if entity metadata is missing', async () => { + getEntityMetadata.mockResolvedValue(undefined); + useTechDocsReaderDom.mockReturnValue(document.createElement('html')); + useReaderState.mockReturnValue({ state: 'cached' }); + + await expect( + renderInTestApp( + + + , + ), + ).rejects.toThrow('Reached NotFound Page'); + + const shadowRoot = document.querySelector( + '[data-testid="techdocs-native-shadowroot"]', + ); + expect(shadowRoot).not.toBeInTheDocument(); + }); + + it('should render 404 if there is no dom and reader state is not found', async () => { + getEntityMetadata.mockResolvedValue(mockEntityMetadata); + getTechDocsMetadata.mockResolvedValue(mockTechDocsMetadata); + useTechDocsReaderDom.mockReturnValue(undefined); + useReaderState.mockReturnValue({ state: 'CONTENT_NOT_FOUND' }); + + const rendered = await renderInTestApp( + + + , + ); + + await waitFor(() => { + expect( + rendered.queryByTestId('techdocs-native-shadowroot'), + ).not.toBeInTheDocument(); + expect( + rendered.getByText('ERROR 404: Documentation not found'), + ).toBeInTheDocument(); + }); + }); + + it('should render progress bar when content is loading', async () => { + getEntityMetadata.mockResolvedValue(mockEntityMetadata); + getTechDocsMetadata.mockResolvedValue(mockTechDocsMetadata); + useTechDocsReaderDom.mockReturnValue(document.createElement('html')); + useReaderState.mockReturnValue({ state: 'CHECKING' }); + + const rendered = await renderInTestApp( + + + , + ); + + await waitFor(() => { + expect(rendered.queryByRole('progressbar')).toBeInTheDocument(); + }); + }); + + it('should render progress bar when styles are loading', async () => { + getEntityMetadata.mockResolvedValue(mockEntityMetadata); + getTechDocsMetadata.mockResolvedValue(mockTechDocsMetadata); + useTechDocsReaderDom.mockReturnValue(document.createElement('html')); + useReaderState.mockReturnValue({ state: 'cached' }); + useShadowDomStylesLoading.mockReturnValue(true); + + const rendered = await renderInTestApp( + + + , + ); + + await waitFor(() => { + expect(rendered.queryByRole('progressbar')).toBeInTheDocument(); + }); + }); +}); diff --git a/plugins/techdocs/src/alpha/components/TechDocsReaderContent.tsx b/plugins/techdocs/src/alpha/components/TechDocsReaderContent.tsx new file mode 100644 index 0000000000..fdb0be680e --- /dev/null +++ b/plugins/techdocs/src/alpha/components/TechDocsReaderContent.tsx @@ -0,0 +1,56 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { TechDocsShadowDom } from '@backstage/plugin-techdocs-react'; +import { Progress } from '@backstage/core-components'; + +import { TechDocsStateIndicator } from '../../reader/components/TechDocsStateIndicator'; +import { withTechDocsReaderProvider } from '../../reader/components/TechDocsReaderProvider'; +import { TechDocsReaderPageContentAddons } from '../../reader/components/TechDocsReaderPageContent/TechDocsReaderPageContentAddons'; +import type { TechDocsReaderPageContentProps } from '../../reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent'; +import { useTechDocsReaderContentData } from '../../hooks/useTechDocsReaderContentData'; + +export const TechDocsReaderContent = withTechDocsReaderProvider( + (props: TechDocsReaderPageContentProps) => { + const { + dom, + handleAppend, + isNotFound, + isDomReady, + showProgress, + NotFoundErrorPage, + } = useTechDocsReaderContentData({ + defaultPath: props.defaultPath, + onReady: props.onReady, + }); + + if (isNotFound) return ; + + if (!isDomReady) { + return ; + } + + return ( + <> + + {showProgress && } + + + + + ); + }, +); diff --git a/plugins/techdocs/src/alpha/components/TechDocsReaderEntityCard.module.css b/plugins/techdocs/src/alpha/components/TechDocsReaderEntityCard.module.css new file mode 100644 index 0000000000..14fa9082fb --- /dev/null +++ b/plugins/techdocs/src/alpha/components/TechDocsReaderEntityCard.module.css @@ -0,0 +1,36 @@ +.entityCard { + margin-bottom: var(--bui-space-6); +} + +.cardBody { + display: flex; + align-items: flex-start; + justify-content: space-between; + flex-wrap: wrap; + gap: var(--bui-space-4); +} + +.definitionList { + display: flex; + flex-wrap: wrap; + gap: var(--bui-space-6); + margin: 0; + padding: 0; + flex: 1 1 0; + align-self: center; +} + +.definitionGroup { + display: flex; + align-items: baseline; + gap: var(--bui-space-2); +} + +.definitionValue { + margin: 0; +} + +.searchWrapper { + flex: 1 1 200px; + max-width: 240px; +} diff --git a/plugins/techdocs/src/alpha/components/TechDocsReaderEntityCard.test.tsx b/plugins/techdocs/src/alpha/components/TechDocsReaderEntityCard.test.tsx new file mode 100644 index 0000000000..90fcc4b753 --- /dev/null +++ b/plugins/techdocs/src/alpha/components/TechDocsReaderEntityCard.test.tsx @@ -0,0 +1,215 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ReactNode } from 'react'; + +import { CompoundEntityRef, RELATION_OWNED_BY } from '@backstage/catalog-model'; +import { entityRouteRef } from '@backstage/plugin-catalog-react'; +import { + techdocsApiRef, + TechDocsReaderPageProvider, +} from '@backstage/plugin-techdocs-react'; +import { + renderInTestApp, + TestApiProvider, + mockApis, +} from '@backstage/test-utils'; +import { configApiRef } from '@backstage/core-plugin-api'; +import { searchApiRef, MockSearchApi } from '@backstage/plugin-search-react'; + +import { TechDocsReaderEntityCard } from './TechDocsReaderEntityCard'; + +const mockEntityMetadata = { + apiVersion: 'v1', + kind: 'Component', + metadata: { + name: 'test-component', + namespace: 'default', + title: 'Test Component Title', + }, + spec: { + owner: 'team-a', + lifecycle: 'production', + }, + relations: [ + { + type: RELATION_OWNED_BY, + targetRef: 'group:default/team-a', + }, + ], +}; + +const mockTechDocsMetadata = { + site_name: 'test-site-name', + site_description: 'test-site-desc', +}; + +const getEntityMetadata = jest.fn(); +const getTechDocsMetadata = jest.fn(); + +const techdocsApiMock = { + getEntityMetadata, + getTechDocsMetadata, +}; + +const Wrapper = ({ + entityRef = { + kind: mockEntityMetadata.kind, + name: mockEntityMetadata.metadata.name, + namespace: mockEntityMetadata.metadata.namespace!!, + }, + children, +}: { + entityRef?: CompoundEntityRef; + children: ReactNode; +}) => ( + + + {children} + + +); + +const mountedRoutes = { + '/catalog/:namespace/:kind/:name/*': entityRouteRef, +}; + +describe('', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should render entity kind, owner, and lifecycle', async () => { + getEntityMetadata.mockResolvedValue(mockEntityMetadata); + getTechDocsMetadata.mockResolvedValue(mockTechDocsMetadata); + + const rendered = await renderInTestApp( + + + , + { mountedRoutes }, + ); + + expect(await rendered.findByText('Component:')).toBeInTheDocument(); + expect(rendered.getByText('Owner:')).toBeInTheDocument(); + expect(rendered.getByText('Lifecycle:')).toBeInTheDocument(); + expect(rendered.getByText('production')).toBeInTheDocument(); + }); + + it('should not render when entity metadata is loading', async () => { + // Don't resolve the metadata — it stays in loading state + getEntityMetadata.mockReturnValue(new Promise(() => {})); + getTechDocsMetadata.mockReturnValue(new Promise(() => {})); + + const rendered = await renderInTestApp( + + + , + { mountedRoutes }, + ); + + expect(rendered.queryByText('Component:')).not.toBeInTheDocument(); + }); + + it('should not render when entity metadata is missing', async () => { + getEntityMetadata.mockResolvedValue(undefined); + getTechDocsMetadata.mockResolvedValue(mockTechDocsMetadata); + + const rendered = await renderInTestApp( + + + , + { mountedRoutes }, + ); + + expect(rendered.queryByText('Component:')).not.toBeInTheDocument(); + }); + + it('should not render lifecycle when not present', async () => { + getEntityMetadata.mockResolvedValue({ + ...mockEntityMetadata, + spec: { owner: 'team-a' }, + }); + getTechDocsMetadata.mockResolvedValue(mockTechDocsMetadata); + + const rendered = await renderInTestApp( + + + , + { mountedRoutes }, + ); + + expect(await rendered.findByText('Component:')).toBeInTheDocument(); + expect(rendered.queryByText('Lifecycle:')).not.toBeInTheDocument(); + }); + + it('should not render owner when no owned-by relations exist', async () => { + getEntityMetadata.mockResolvedValue({ + ...mockEntityMetadata, + relations: [], + }); + getTechDocsMetadata.mockResolvedValue(mockTechDocsMetadata); + + const rendered = await renderInTestApp( + + + , + { mountedRoutes }, + ); + + expect(await rendered.findByText('Component:')).toBeInTheDocument(); + expect(rendered.queryByText('Owner:')).not.toBeInTheDocument(); + }); + + it('should render search when withSearch is true', async () => { + getEntityMetadata.mockResolvedValue(mockEntityMetadata); + getTechDocsMetadata.mockResolvedValue(mockTechDocsMetadata); + + const rendered = await renderInTestApp( + + + , + { mountedRoutes }, + ); + + expect(await rendered.findByText('Component:')).toBeInTheDocument(); + expect( + rendered.getByRole('searchbox', { name: 'Search docs' }), + ).toBeInTheDocument(); + }); + + it('should not render search when withSearch is false', async () => { + getEntityMetadata.mockResolvedValue(mockEntityMetadata); + getTechDocsMetadata.mockResolvedValue(mockTechDocsMetadata); + + const rendered = await renderInTestApp( + + + , + { mountedRoutes }, + ); + + expect(await rendered.findByText('Component:')).toBeInTheDocument(); + expect( + rendered.queryByRole('searchbox', { name: 'Search docs' }), + ).not.toBeInTheDocument(); + }); +}); diff --git a/plugins/techdocs/src/alpha/components/TechDocsReaderEntityCard.tsx b/plugins/techdocs/src/alpha/components/TechDocsReaderEntityCard.tsx new file mode 100644 index 0000000000..1448eb3fda --- /dev/null +++ b/plugins/techdocs/src/alpha/components/TechDocsReaderEntityCard.tsx @@ -0,0 +1,105 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useTechDocsReaderPage } from '@backstage/plugin-techdocs-react'; +import { + EntityRefLink, + EntityRefLinks, + getEntityRelations, +} from '@backstage/plugin-catalog-react'; +import { RELATION_OWNED_BY } from '@backstage/catalog-model'; +import { Card, CardBody, Text } from '@backstage/ui'; +import capitalize from 'lodash/capitalize'; +import styles from './TechDocsReaderEntityCard.module.css'; +import { TechDocsReaderSearch } from './TechDocsReaderSearch'; + +export type TechDocsReaderEntityCardProps = { + withSearch?: boolean; +}; + +export const TechDocsReaderEntityCard = ( + props: TechDocsReaderEntityCardProps, +) => { + const { withSearch } = props; + const { + entityRef, + entityMetadata: { value: entityMetadata, loading: entityMetadataLoading }, + } = useTechDocsReaderPage(); + + if (entityMetadataLoading || !entityMetadata) return null; + + const { spec } = entityMetadata; + const lifecycle = spec?.lifecycle; + const ownedByRelations = getEntityRelations( + entityMetadata, + RELATION_OWNED_BY, + ); + + return ( + + +

    +
    +
    + + {capitalize(entityMetadata.kind)}: + +
    +
    + +
    +
    + {ownedByRelations.length > 0 && ( +
    +
    + + Owner: + +
    +
    + +
    +
    + )} + {lifecycle && ( +
    +
    + + Lifecycle: + +
    +
    + {String(lifecycle)} +
    +
    + )} +
    + {withSearch && ( +
    + +
    + )} + + + ); +}; diff --git a/plugins/techdocs/src/alpha/components/TechDocsReaderHeader.test.tsx b/plugins/techdocs/src/alpha/components/TechDocsReaderHeader.test.tsx new file mode 100644 index 0000000000..625ee832d9 --- /dev/null +++ b/plugins/techdocs/src/alpha/components/TechDocsReaderHeader.test.tsx @@ -0,0 +1,224 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ReactNode } from 'react'; +import { waitFor } from '@testing-library/react'; + +import { CompoundEntityRef } from '@backstage/catalog-model'; +import { + entityPresentationApiRef, + entityRouteRef, +} from '@backstage/plugin-catalog-react'; +import { + techdocsApiRef, + TechDocsReaderPageProvider, +} from '@backstage/plugin-techdocs-react'; +import { + renderInTestApp, + TestApiProvider, + mockApis, +} from '@backstage/test-utils'; +import { configApiRef } from '@backstage/core-plugin-api'; + +import { rootRouteRef } from '../../routes'; + +import { TechDocsReaderHeader } from './TechDocsReaderHeader'; + +const mockEntityMetadata = { + locationMetadata: { + type: 'github', + target: 'https://example.com/', + }, + apiVersion: 'v1', + kind: 'test', + metadata: { + name: 'test-name', + namespace: 'test-namespace', + }, + spec: { + owner: 'test', + }, +}; + +const mockTechDocsMetadata = { + site_name: 'test-site-name', + site_description: 'test-site-desc', +}; + +let useParamsPath = '/'; +jest.mock('react-router-dom', () => { + return { + ...(jest.requireActual('react-router-dom') as any), + useParams: () => ({ '*': useParamsPath }), + }; +}); + +const getEntityMetadata = jest.fn(); +const getTechDocsMetadata = jest.fn(); + +const techdocsApiMock = { + getEntityMetadata, + getTechDocsMetadata, +}; + +const forEntity = jest.fn(); + +forEntity.mockReturnValue({ + snapshot: { + primaryTitle: 'Test Entity', + }, +}); + +const entityPresentationApiMock = { + forEntity, +}; + +const configApiMock = mockApis.config(); + +const Wrapper = ({ + entityRef = { + kind: mockEntityMetadata.kind, + name: mockEntityMetadata.metadata.name, + namespace: mockEntityMetadata.metadata.namespace!!, + }, + children, +}: { + entityRef?: CompoundEntityRef; + children: ReactNode; +}) => ( + + + {children} + + +); + +const mountedRoutes = { + '/catalog/:namespace/:kind/:name/*': entityRouteRef, + '/docs': rootRouteRef, +}; + +describe('', () => { + beforeEach(() => { + useParamsPath = '/'; + jest.clearAllMocks(); + forEntity.mockReturnValue({ + snapshot: { primaryTitle: 'Test Entity' }, + }); + }); + + it('should render the header with site name as title', async () => { + getEntityMetadata.mockResolvedValue(mockEntityMetadata); + getTechDocsMetadata.mockResolvedValue(mockTechDocsMetadata); + + const rendered = await renderInTestApp( + + + , + { mountedRoutes }, + ); + + expect(await rendered.findByText('test-site-name')).toBeInTheDocument(); + }); + + it('should not render the header if entity metadata is missing', async () => { + getEntityMetadata.mockResolvedValue(undefined); + + const rendered = await renderInTestApp( + + + , + { mountedRoutes }, + ); + + expect(rendered.container.innerHTML).not.toContain('header'); + }); + + it('should not render the header if techdocs metadata is missing', async () => { + getTechDocsMetadata.mockResolvedValue(undefined); + + const rendered = await renderInTestApp( + + + , + { mountedRoutes }, + ); + + expect(rendered.container.innerHTML).not.toContain('header'); + }); + + it('should render a source link for remote docs', async () => { + getEntityMetadata.mockResolvedValue(mockEntityMetadata); + getTechDocsMetadata.mockResolvedValue(mockTechDocsMetadata); + + const rendered = await renderInTestApp( + + + , + { mountedRoutes }, + ); + + const sourceLink = await rendered.findByRole('link', { + name: 'View source', + }); + expect(sourceLink).toHaveAttribute('href', 'https://example.com/'); + expect(sourceLink).toHaveAttribute('target', '_blank'); + }); + + it('should not render a source link for local docs', async () => { + getEntityMetadata.mockResolvedValue({ + ...mockEntityMetadata, + locationMetadata: { type: 'dir', target: '/local/path' }, + }); + getTechDocsMetadata.mockResolvedValue(mockTechDocsMetadata); + + const rendered = await renderInTestApp( + + + , + { mountedRoutes }, + ); + + await rendered.findByText('test-site-name'); + expect( + rendered.queryByRole('link', { name: 'View source' }), + ).not.toBeInTheDocument(); + }); + + it('should set document title based on URL path segments', async () => { + getEntityMetadata.mockResolvedValue(mockEntityMetadata); + getTechDocsMetadata.mockResolvedValue(mockTechDocsMetadata); + + useParamsPath = 'foo/bar/baz/'; + await renderInTestApp( + + + , + { mountedRoutes }, + ); + + await waitFor(() => { + expect(document.title).toEqual( + 'Test Entity | Foo | Bar | Baz | Backstage', + ); + }); + }); +}); diff --git a/plugins/techdocs/src/alpha/components/TechDocsReaderHeader.tsx b/plugins/techdocs/src/alpha/components/TechDocsReaderHeader.tsx new file mode 100644 index 0000000000..becec9612d --- /dev/null +++ b/plugins/techdocs/src/alpha/components/TechDocsReaderHeader.tsx @@ -0,0 +1,69 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Helmet from 'react-helmet'; +import { Header, ButtonLink } from '@backstage/ui'; +import { RiCodeLine } from '@remixicon/react'; +import { TechDocsAddonLocations as locations } from '@backstage/plugin-techdocs-react'; +import { TechDocsReaderSearch } from './TechDocsReaderSearch'; +import { useTechDocsReaderHeaderData } from '../../hooks/useTechDocsReaderHeaderData'; + +export type TechDocsReaderHeaderProps = { + withSearch?: boolean; +}; + +export const TechDocsReaderHeader = (props: TechDocsReaderHeaderProps) => { + const { withSearch = true } = props; + const { + title, + entityRef, + tabTitle, + hidden, + showSourceLink, + sourceLink, + addons, + } = useTechDocsReaderHeaderData(); + + if (hidden) return null; + + return ( + <> + + {tabTitle} + +
    + {withSearch && } + {showSourceLink && ( + } + aria-label="View source" + /> + )} + {addons.renderComponentsByLocation(locations.Header)} + + } + /> + + ); +}; diff --git a/plugins/techdocs/src/alpha/components/TechDocsReaderLayout.tsx b/plugins/techdocs/src/alpha/components/TechDocsReaderLayout.tsx new file mode 100644 index 0000000000..b43d9c6d14 --- /dev/null +++ b/plugins/techdocs/src/alpha/components/TechDocsReaderLayout.tsx @@ -0,0 +1,40 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Container } from '@backstage/ui'; +import { TechDocsReaderHeader } from './TechDocsReaderHeader'; +import { TechDocsReaderEntityCard } from './TechDocsReaderEntityCard'; +import { TechDocsReaderPageSubheader } from '../../reader/components/TechDocsReaderPageSubheader'; +import { TechDocsReaderContent } from './TechDocsReaderContent'; + +export type TechDocsReaderLayoutProps = { + withHeader?: boolean; + withSearch?: boolean; +}; + +export const TechDocsReaderLayout = (props: TechDocsReaderLayoutProps) => { + const { withSearch, withHeader = true } = props; + return ( + <> + {withHeader && } + + + + + + + ); +}; diff --git a/plugins/techdocs/src/alpha/components/TechDocsReaderSearch.test.tsx b/plugins/techdocs/src/alpha/components/TechDocsReaderSearch.test.tsx new file mode 100644 index 0000000000..d5b3bbfb67 --- /dev/null +++ b/plugins/techdocs/src/alpha/components/TechDocsReaderSearch.test.tsx @@ -0,0 +1,73 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { searchApiRef, MockSearchApi } from '@backstage/plugin-search-react'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; + +import { TechDocsReaderSearch } from './TechDocsReaderSearch'; + +const entityId = { + kind: 'Component', + name: 'test-component', + namespace: 'default', +}; + +const mockSearchApi = new MockSearchApi({ + results: [ + { + type: 'techdocs', + document: { + location: '/docs/default/component/test-component/getting-started', + title: 'Getting Started', + text: 'This guide helps you get started.', + }, + }, + ], +}); + +describe('', () => { + it('should render the search input', async () => { + await renderInTestApp( + + + , + ); + + expect( + screen.getByRole('searchbox', { name: 'Search docs' }), + ).toBeInTheDocument(); + }); + + it('should render search results when user types', async () => { + const user = userEvent.setup(); + + await renderInTestApp( + + + , + ); + + const input = screen.getByRole('searchbox', { name: 'Search docs' }); + await user.type(input, 'getting'); + + expect(await screen.findByText('Getting Started')).toBeInTheDocument(); + expect( + screen.getByText('This guide helps you get started.'), + ).toBeInTheDocument(); + }); +}); diff --git a/plugins/techdocs/src/alpha/components/TechDocsReaderSearch.tsx b/plugins/techdocs/src/alpha/components/TechDocsReaderSearch.tsx new file mode 100644 index 0000000000..aba8a60c7c --- /dev/null +++ b/plugins/techdocs/src/alpha/components/TechDocsReaderSearch.tsx @@ -0,0 +1,120 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useNavigate } from 'react-router-dom'; +import { + Text, + Flex, + SearchAutocomplete, + SearchAutocompleteItem, +} from '@backstage/ui'; +import { SearchContextProvider } from '@backstage/plugin-search-react'; +import { CompoundEntityRef } from '@backstage/catalog-model'; +import { HighlightedSearchResultText } from '@backstage/plugin-search-react'; +import { useTechDocsSearch } from '../../hooks/useTechDocsSearch'; + +type TechDocsReaderSearchBarProps = { + entityId: CompoundEntityRef; +}; + +const TechDocsReaderSearchBar = (props: TechDocsReaderSearchBarProps) => { + const { entityId } = props; + const navigate = useNavigate(); + const { results, term, setTerm, deferredLoading } = + useTechDocsSearch(entityId); + + return ( + + {results.map((result, index) => ( + { + setTerm(''); + navigate(result.document.location); + requestAnimationFrame(() => { + (document.activeElement as HTMLElement)?.blur(); + }); + }} + > + + + {result.highlight?.fields.title ? ( + + ) : ( + result.document.title + )} + + {(result.highlight?.fields.text || result.document.text) && ( + + {result.highlight?.fields.text ? ( + + ) : ( + result.document.text + )} + + )} + + + ))} + + ); +}; + +export type TechDocsReaderSearchProps = { + entityId: CompoundEntityRef; +}; + +export const TechDocsReaderSearch = (props: TechDocsReaderSearchProps) => { + const initialState = { + term: '', + types: ['techdocs'], + pageCursor: '', + filters: props.entityId, + }; + return ( + + + + ); +}; diff --git a/plugins/techdocs/src/alpha/index.tsx b/plugins/techdocs/src/alpha/index.tsx index 96454600d9..6f77d1731e 100644 --- a/plugins/techdocs/src/alpha/index.tsx +++ b/plugins/techdocs/src/alpha/index.tsx @@ -14,12 +14,15 @@ * limitations under the License. */ -import LibraryBooks from '@material-ui/icons/LibraryBooks'; +import { Suspense } from 'react'; +import { z } from 'zod/v4'; +import { RiArticleLine } from '@remixicon/react'; import { createFrontendPlugin, ApiBlueprint, PageBlueprint, NavItemBlueprint, + PluginHeaderActionBlueprint, createExtensionInput, coreExtensionData, createExtension, @@ -29,25 +32,23 @@ import { discoveryApiRef, fetchApiRef, } from '@backstage/core-plugin-api'; -import { - compatWrapper, - convertLegacyRouteRef, - convertLegacyRouteRefs, -} from '@backstage/core-compat-api'; import { EntityContentBlueprint, EntityIconLinkBlueprint, } from '@backstage/plugin-catalog-react/alpha'; import { SearchResultListItemBlueprint } from '@backstage/plugin-search-react/alpha'; -import { AddonBlueprint } from '@backstage/plugin-techdocs-react/alpha'; +import { + AddonBlueprint, + attachTechDocsAddonComponentData, +} from '@backstage/plugin-techdocs-react/alpha'; +import { TechDocsAddonsApiExtension, techdocsAddonsApiRef } from './addonsApi'; import { TechDocsClient, TechDocsStorageClient } from '../client'; import { rootCatalogDocsRouteRef, rootDocsRouteRef, rootRouteRef, } from '../routes'; -import { TechDocsReaderLayout } from '../reader'; -import { attachTechDocsAddonComponentData } from '@backstage/plugin-techdocs-react/alpha'; +import { TechDocsReaderLayout } from './components/TechDocsReaderLayout'; import { TechDocsAddons, techdocsApiRef, @@ -55,7 +56,7 @@ import { } from '@backstage/plugin-techdocs-react'; import { useTechdocsReaderIconLinkProps } from './hooks/useTechdocsReaderIconLinkProps'; -import { DocsIcon } from '@backstage/core-components'; +import { DocsIcon, SupportButton } from '@backstage/core-components'; /** @alpha */ const techdocsEntityIconLink = EntityIconLinkBlueprint.make({ @@ -107,13 +108,11 @@ const techDocsClientApi = ApiBlueprint.make({ /** @alpha */ export const techDocsSearchResultListItemExtension = SearchResultListItemBlueprint.makeWithOverrides({ - config: { - schema: { - title: z => z.string().optional(), - lineClamp: z => z.number().default(5), - asLink: z => z.boolean().default(true), - asListItem: z => z.boolean().default(true), - }, + configSchema: { + title: z.string().optional(), + lineClamp: z.number().default(5), + asLink: z.boolean().default(true), + asListItem: z.boolean().default(true), }, factory(originalFactory, { config }) { return originalFactory({ @@ -123,10 +122,9 @@ export const techDocsSearchResultListItemExtension = const { TechDocsSearchResultListItem } = await import( '../search/components/TechDocsSearchResultListItem' ); - return props => - compatWrapper( - , - ); + return props => ( + + ); }, }); }, @@ -140,11 +138,11 @@ export const techDocsSearchResultListItemExtension = const techDocsPage = PageBlueprint.make({ params: { path: '/docs', - routeRef: convertLegacyRouteRef(rootRouteRef), + routeRef: rootRouteRef, loader: () => - import('../home/components/TechDocsIndexPage').then(m => - compatWrapper(), - ), + import('./components/TechDocsIndexPageContent').then(m => ( + + )), }, }); @@ -158,26 +156,44 @@ const techDocsReaderPage = PageBlueprint.makeWithOverrides({ inputs: { addons: createExtensionInput([AddonBlueprint.dataRefs.addon]), }, - factory(originalFactory, { inputs }) { - const addons = inputs.addons.map(output => { - const options = output.get(AddonBlueprint.dataRefs.addon); - const Addon = options.component; - attachTechDocsAddonComponentData(Addon, options); - return ; - }); + configSchema: { + withoutSearch: z.boolean().default(false), + withoutHeader: z.boolean().default(false), + }, + factory(originalFactory, { apis, inputs, config }) { + const addonsApi = apis.get(techdocsAddonsApiRef); return originalFactory({ path: '/docs/:namespace/:kind/:name', - routeRef: convertLegacyRouteRef(rootDocsRouteRef), - loader: async () => - await import('../Router').then(({ TechDocsReaderRouter }) => { - return compatWrapper( - - - {addons} - , + routeRef: rootDocsRouteRef, + loader: async () => { + // Merge addons from the API with old-style direct attachments + const apiAddons = addonsApi?.getAddons() ?? []; + const directAddons = inputs.addons.map(output => + output.get(AddonBlueprint.dataRefs.addon), + ); + const addonOptions = [...apiAddons, ...directAddons]; + + const addons = addonOptions.map(options => { + const Addon = options.component; + attachTechDocsAddonComponentData(Addon, options); + return ( + + + ); - }), + }); + + return import('../Router').then(({ TechDocsReaderRouter }) => ( + + + {addons} + + )); + }, }); }, }); @@ -199,29 +215,42 @@ const techDocsEntityContent = EntityContentBlueprint.makeWithOverrides({ ), }, factory(originalFactory, context) { + const addonsApi = context.apis.get(techdocsAddonsApiRef); + return originalFactory( { path: 'docs', title: 'TechDocs', - routeRef: convertLegacyRouteRef(rootCatalogDocsRouteRef), - loader: () => - import('../Router').then(({ EmbeddedDocsRouter }) => { - const addons = context.inputs.addons.map(output => { - const options = output.get(AddonBlueprint.dataRefs.addon); - const Addon = options.component; - attachTechDocsAddonComponentData(Addon, options); - return ; - }); - return compatWrapper( - - {addons} - , + group: 'documentation', + routeRef: rootCatalogDocsRouteRef, + loader: () => { + // Merge addons from the API with old-style direct attachments + const apiAddons = addonsApi?.getAddons() ?? []; + const directAddons = context.inputs.addons.map(output => + output.get(AddonBlueprint.dataRefs.addon), + ); + const addonOptions = [...apiAddons, ...directAddons]; + + const addons = addonOptions.map(options => { + const Addon = options.component; + attachTechDocsAddonComponentData(Addon, options); + return ( + + + ); - }), + }); + + return import('../Router').then(({ EmbeddedDocsRouter }) => ( + + {addons} + + )); + }, }, context, ); @@ -239,20 +268,33 @@ const techDocsEntityContentEmptyState = createExtension({ /** @alpha */ const techDocsNavItem = NavItemBlueprint.make({ params: { - icon: LibraryBooks, + icon: () => , title: 'Docs', - routeRef: convertLegacyRouteRef(rootRouteRef), + routeRef: rootRouteRef, }, }); +const techDocsSupportAction = PluginHeaderActionBlueprint.make({ + params: defineParams => + defineParams({ + loader: async () => ( + Discover documentation in your ecosystem. + ), + }), +}); + /** @alpha */ export default createFrontendPlugin({ pluginId: 'techdocs', + title: 'Documentation', + icon: , info: { packageJson: () => import('../../package.json') }, extensions: [ techDocsClientApi, techDocsStorageApi, + TechDocsAddonsApiExtension, techDocsNavItem, + techDocsSupportAction, techDocsPage, techDocsReaderPage, techdocsEntityIconLink, @@ -260,9 +302,9 @@ export default createFrontendPlugin({ techDocsEntityContentEmptyState, techDocsSearchResultListItemExtension, ], - routes: convertLegacyRouteRefs({ + routes: { root: rootRouteRef, docRoot: rootDocsRouteRef, entityContent: rootCatalogDocsRouteRef, - }), + }, }); diff --git a/plugins/techdocs/src/home/components/Tables/OffsetPaginatedDocsTable.test.tsx b/plugins/techdocs/src/home/components/Tables/OffsetPaginatedDocsTable.test.tsx index 6b75fa7aee..beeae2a957 100644 --- a/plugins/techdocs/src/home/components/Tables/OffsetPaginatedDocsTable.test.tsx +++ b/plugins/techdocs/src/home/components/Tables/OffsetPaginatedDocsTable.test.tsx @@ -52,6 +52,8 @@ describe('OffsetPaginatedDocsTable', () => { }, ]; + const tableTitle = 'All Docs'; + const wrapInContext = ( node: ReactNode, value?: Partial>, @@ -66,7 +68,11 @@ describe('OffsetPaginatedDocsTable', () => { it('should display all the items', async () => { await renderInTestApp( wrapInContext( - , + , { setOffset: jest.fn(), limit: Number.MAX_SAFE_INTEGER, @@ -76,6 +82,8 @@ describe('OffsetPaginatedDocsTable', () => { ), ); + expect(screen.getByText(tableTitle)).toBeInTheDocument(); + for (const item of data) { expect(screen.queryByText(item.entity.metadata.name)).toBeInTheDocument(); } diff --git a/plugins/techdocs/src/home/components/Tables/OffsetPaginatedDocsTable.tsx b/plugins/techdocs/src/home/components/Tables/OffsetPaginatedDocsTable.tsx index c287bc3d94..5de84bfe03 100644 --- a/plugins/techdocs/src/home/components/Tables/OffsetPaginatedDocsTable.tsx +++ b/plugins/techdocs/src/home/components/Tables/OffsetPaginatedDocsTable.tsx @@ -27,7 +27,7 @@ import { * @internal */ export function OffsetPaginatedDocsTable(props: TableProps) { - const { actions, columns, data, isLoading, options } = props; + const { actions, columns, data, isLoading, options, title } = props; const { updateFilters, setLimit, setOffset, limit, totalItems, offset } = useEntityList(); const [page, setPage] = useState( @@ -44,6 +44,7 @@ export function OffsetPaginatedDocsTable(props: TableProps) { return ( + title={title} columns={columns} data={data} options={{ diff --git a/plugins/techdocs/src/home/components/Tables/helpers.ts b/plugins/techdocs/src/home/components/Tables/helpers.ts index 4c6383c35e..b0b5c41fcd 100644 --- a/plugins/techdocs/src/home/components/Tables/helpers.ts +++ b/plugins/techdocs/src/home/components/Tables/helpers.ts @@ -16,8 +16,9 @@ import { RELATION_OWNED_BY, Entity } from '@backstage/catalog-model'; import { + entityPresentationSnapshot, getEntityRelations, - humanizeEntityRef, + type EntityPresentationApi, } from '@backstage/plugin-catalog-react'; import { toLowerMaybe } from '../../../helpers'; import { ConfigApi, RouteFunc } from '@backstage/core-plugin-api'; @@ -32,6 +33,7 @@ export function entitiesToDocsMapper( entities: Entity[], getRouteToReaderPageFor: getRouteFunc, config: ConfigApi, + entityPresentationApi?: EntityPresentationApi, ) { return entities.map(entity => { const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY); @@ -48,7 +50,14 @@ export function entitiesToDocsMapper( }), ownedByRelations, ownedByRelationsTitle: ownedByRelations - .map(r => humanizeEntityRef(r, { defaultKind: 'group' })) + .map( + r => + entityPresentationSnapshot( + r, + { defaultKind: 'group' }, + entityPresentationApi, + ).primaryTitle, + ) .join(', '), }, }; diff --git a/plugins/techdocs/src/hooks/useTechDocsReaderContentData.test.tsx b/plugins/techdocs/src/hooks/useTechDocsReaderContentData.test.tsx new file mode 100644 index 0000000000..324d50f0dc --- /dev/null +++ b/plugins/techdocs/src/hooks/useTechDocsReaderContentData.test.tsx @@ -0,0 +1,184 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ReactNode, useState, useEffect } from 'react'; +import { waitFor, screen } from '@testing-library/react'; + +import { CompoundEntityRef } from '@backstage/catalog-model'; +import { + techdocsApiRef, + TechDocsReaderPageProvider, + useShadowRootElements, +} from '@backstage/plugin-techdocs-react'; +import { + renderInTestApp, + TestApiProvider, + mockApis, +} from '@backstage/test-utils'; +import { configApiRef } from '@backstage/core-plugin-api'; + +const useTechDocsReaderDom = jest.fn(); +jest.mock('../reader/components/TechDocsReaderPageContent/dom', () => ({ + ...jest.requireActual('../reader/components/TechDocsReaderPageContent/dom'), + useTechDocsReaderDom: (...args: any[]) => useTechDocsReaderDom(...args), +})); +const useTechDocsReader = jest.fn(); +jest.mock('../reader/components/TechDocsReaderProvider', () => ({ + ...jest.requireActual('../reader/components/TechDocsReaderProvider'), + useTechDocsReader: (...args: any[]) => useTechDocsReader(...args), +})); +const useShadowDomStylesLoading = jest.fn().mockReturnValue(false); +jest.mock('@backstage/plugin-techdocs-react', () => ({ + ...jest.requireActual('@backstage/plugin-techdocs-react'), + useShadowDomStylesLoading: (...args: any[]) => + useShadowDomStylesLoading(...args), + useShadowRootElements: jest.fn(), +})); + +import { useTechDocsReaderContentData } from './useTechDocsReaderContentData'; + +const mockEntityMetadata = { + apiVersion: 'v1', + kind: 'test', + metadata: { + name: 'test-name', + namespace: 'test-namespace', + }, + spec: { owner: 'test' }, +}; + +const mockTechDocsMetadata = { + site_name: 'test-site-name', + site_description: 'test-site-desc', +}; + +const getEntityMetadata = jest.fn(); +const getTechDocsMetadata = jest.fn(); + +const Wrapper = ({ + entityRef = { + kind: mockEntityMetadata.kind, + name: mockEntityMetadata.metadata.name, + namespace: mockEntityMetadata.metadata.namespace!!, + }, + children, +}: { + entityRef?: CompoundEntityRef; + children: ReactNode; +}) => ( + + + {children} + + +); + +function HookRenderer({ defaultPath }: { defaultPath?: string }) { + const data = useTechDocsReaderContentData({ defaultPath }); + const [, setRender] = useState(0); + useEffect(() => setRender(r => r + 1), [data.isNotFound, data.isDomReady]); + return ( +
    + {String(data.isNotFound)} + {String(data.isDomReady)} + {String(data.showProgress)} +
    + ); +} + +describe('useTechDocsReaderContentData', () => { + const useShadowRootElementsMock = useShadowRootElements as jest.Mock; + + beforeEach(() => { + useShadowRootElementsMock.mockReturnValue([]); + jest.clearAllMocks(); + }); + + it('should return ready state when DOM is loaded', async () => { + getEntityMetadata.mockResolvedValue(mockEntityMetadata); + getTechDocsMetadata.mockResolvedValue(mockTechDocsMetadata); + useTechDocsReaderDom.mockReturnValue(document.createElement('html')); + useTechDocsReader.mockReturnValue({ state: 'cached' }); + + await renderInTestApp( + + + , + ); + + await waitFor(() => { + expect(screen.getByTestId('isDomReady')).toHaveTextContent('true'); + }); + + expect(screen.getByTestId('isNotFound')).toHaveTextContent('false'); + expect(screen.getByTestId('showProgress')).toHaveTextContent('false'); + }); + + it('should return not found when entity metadata is missing', async () => { + getEntityMetadata.mockResolvedValue(undefined); + useTechDocsReaderDom.mockReturnValue(document.createElement('html')); + useTechDocsReader.mockReturnValue({ state: 'cached' }); + + await renderInTestApp( + + + , + ); + + await waitFor(() => { + expect(screen.getByTestId('isNotFound')).toHaveTextContent('true'); + }); + }); + + it('should show progress when checking state', async () => { + getEntityMetadata.mockResolvedValue(mockEntityMetadata); + getTechDocsMetadata.mockResolvedValue(mockTechDocsMetadata); + useTechDocsReaderDom.mockReturnValue(document.createElement('html')); + useTechDocsReader.mockReturnValue({ state: 'CHECKING' }); + + await renderInTestApp( + + + , + ); + + await waitFor(() => { + expect(screen.getByTestId('showProgress')).toHaveTextContent('true'); + }); + }); + + it('should show progress when styles are loading', async () => { + getEntityMetadata.mockResolvedValue(mockEntityMetadata); + getTechDocsMetadata.mockResolvedValue(mockTechDocsMetadata); + useTechDocsReaderDom.mockReturnValue(document.createElement('html')); + useTechDocsReader.mockReturnValue({ state: 'cached' }); + useShadowDomStylesLoading.mockReturnValue(true); + + await renderInTestApp( + + + , + ); + + await waitFor(() => { + expect(screen.getByTestId('showProgress')).toHaveTextContent('true'); + }); + }); +}); diff --git a/plugins/techdocs/src/hooks/useTechDocsReaderContentData.ts b/plugins/techdocs/src/hooks/useTechDocsReaderContentData.ts new file mode 100644 index 0000000000..02a3422617 --- /dev/null +++ b/plugins/techdocs/src/hooks/useTechDocsReaderContentData.ts @@ -0,0 +1,94 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useCallback, useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; +import { + useShadowDomStylesLoading, + useShadowRootElements, + useTechDocsReaderPage, +} from '@backstage/plugin-techdocs-react'; +import { useApp } from '@backstage/core-plugin-api'; +import { useTechDocsReaderDom } from '../reader/components/TechDocsReaderPageContent/dom'; +import { useTechDocsReader } from '../reader/components/TechDocsReaderProvider'; + +/** + * Shared hook for TechDocs reader content data. + * Encapsulates DOM setup, hash scrolling, shadow root handling, + * 404 detection, and loading state. + */ +export function useTechDocsReaderContentData(options: { + defaultPath?: string; + onReady?: () => void; +}) { + const { defaultPath, onReady } = options; + + const { + entityMetadata: { value: entityMetadata, loading: entityMetadataLoading }, + entityRef, + setShadowRoot, + } = useTechDocsReaderPage(); + const { state } = useTechDocsReader(); + const dom = useTechDocsReaderDom(entityRef, defaultPath); + const location = useLocation(); + const path = location.pathname; + const hash = location.hash; + const isStyleLoading = useShadowDomStylesLoading(dom); + const [hashElement] = useShadowRootElements([`[id="${hash.slice(1)}"]`]); + const app = useApp(); + const { NotFoundErrorPage } = app.getComponents(); + + useEffect(() => { + if (isStyleLoading) return; + + if (hash) { + if (hashElement) { + hashElement.scrollIntoView(); + const link = hashElement.querySelector('a.headerlink'); + if (link) { + link.focus(); + } + } + } else { + document?.querySelector('header')?.scrollIntoView(); + } + }, [path, hash, hashElement, isStyleLoading]); + + const handleAppend = useCallback( + (newShadowRoot: ShadowRoot) => { + setShadowRoot(newShadowRoot); + if (onReady instanceof Function) { + onReady(); + } + }, + [setShadowRoot, onReady], + ); + + const isNotFound = entityMetadataLoading === false && !entityMetadata; + const isDomReady = !!dom; + const showProgress = state === 'CHECKING' || isStyleLoading; + + return { + entityRef, + entityMetadata, + dom, + handleAppend, + isNotFound, + isDomReady, + showProgress, + NotFoundErrorPage, + }; +} diff --git a/plugins/techdocs/src/hooks/useTechDocsReaderHeaderData.test.tsx b/plugins/techdocs/src/hooks/useTechDocsReaderHeaderData.test.tsx new file mode 100644 index 0000000000..6e1c14627d --- /dev/null +++ b/plugins/techdocs/src/hooks/useTechDocsReaderHeaderData.test.tsx @@ -0,0 +1,206 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ReactNode, useState, useEffect } from 'react'; +import { waitFor, screen } from '@testing-library/react'; + +import { CompoundEntityRef } from '@backstage/catalog-model'; +import { entityPresentationApiRef } from '@backstage/plugin-catalog-react'; +import { + techdocsApiRef, + TechDocsReaderPageProvider, +} from '@backstage/plugin-techdocs-react'; +import { + renderInTestApp, + TestApiProvider, + mockApis, +} from '@backstage/test-utils'; +import { configApiRef } from '@backstage/core-plugin-api'; + +import { useTechDocsReaderHeaderData } from './useTechDocsReaderHeaderData'; + +const mockEntityMetadata = { + locationMetadata: { + type: 'github', + target: 'https://example.com/', + }, + apiVersion: 'v1', + kind: 'test', + metadata: { + name: 'test-name', + namespace: 'test-namespace', + }, + spec: { + owner: 'test', + }, +}; + +const mockTechDocsMetadata = { + site_name: 'test-site-name', + site_description: 'test-site-desc', +}; + +let useParamsPath = '/'; +jest.mock('react-router-dom', () => { + return { + ...(jest.requireActual('react-router-dom') as any), + useParams: () => ({ '*': useParamsPath }), + }; +}); + +const getEntityMetadata = jest.fn(); +const getTechDocsMetadata = jest.fn(); + +const forEntity = jest.fn().mockReturnValue({ + snapshot: { primaryTitle: 'Test Entity' }, +}); + +const Wrapper = ({ + entityRef = { + kind: mockEntityMetadata.kind, + name: mockEntityMetadata.metadata.name, + namespace: mockEntityMetadata.metadata.namespace!!, + }, + children, +}: { + entityRef?: CompoundEntityRef; + children: ReactNode; +}) => ( + + + {children} + + +); + +function HookRenderer() { + const data = useTechDocsReaderHeaderData(); + const [, setRender] = useState(0); + useEffect(() => setRender(r => r + 1), [data.hidden, data.title]); + return ( +
    + {String(data.hidden)} + {data.title || ''} + {data.tabTitle} + {String(data.showSourceLink)} + {data.sourceLink || ''} +
    + ); +} + +describe('useTechDocsReaderHeaderData', () => { + beforeEach(() => { + useParamsPath = '/'; + jest.clearAllMocks(); + forEntity.mockReturnValue({ + snapshot: { primaryTitle: 'Test Entity' }, + }); + }); + + it('should return header data when metadata is loaded', async () => { + getEntityMetadata.mockResolvedValue(mockEntityMetadata); + getTechDocsMetadata.mockResolvedValue(mockTechDocsMetadata); + + await renderInTestApp( + + + , + ); + + await waitFor(() => { + expect(screen.getByTestId('hidden')).toHaveTextContent('false'); + }); + + expect(screen.getByTestId('title')).toHaveTextContent('test-site-name'); + expect(screen.getByTestId('showSourceLink')).toHaveTextContent('true'); + expect(screen.getByTestId('sourceLink')).toHaveTextContent( + 'https://example.com/', + ); + }); + + it('should be hidden when entity metadata is missing', async () => { + getEntityMetadata.mockResolvedValue(undefined); + + await renderInTestApp( + + + , + ); + + await waitFor(() => { + expect(screen.getByTestId('hidden')).toHaveTextContent('true'); + }); + }); + + it('should be hidden when techdocs metadata is missing', async () => { + getTechDocsMetadata.mockResolvedValue(undefined); + + await renderInTestApp( + + + , + ); + + await waitFor(() => { + expect(screen.getByTestId('hidden')).toHaveTextContent('true'); + }); + }); + + it('should not show source link for local docs', async () => { + getEntityMetadata.mockResolvedValue({ + ...mockEntityMetadata, + locationMetadata: { type: 'dir', target: '/local/path' }, + }); + getTechDocsMetadata.mockResolvedValue(mockTechDocsMetadata); + + await renderInTestApp( + + + , + ); + + await waitFor(() => { + expect(screen.getByTestId('hidden')).toHaveTextContent('false'); + }); + + expect(screen.getByTestId('showSourceLink')).toHaveTextContent('false'); + }); + + it('should compute tab title from URL path segments', async () => { + getEntityMetadata.mockResolvedValue(mockEntityMetadata); + getTechDocsMetadata.mockResolvedValue(mockTechDocsMetadata); + + useParamsPath = 'foo/bar/baz/'; + await renderInTestApp( + + + , + ); + + await waitFor(() => { + expect(screen.getByTestId('hidden')).toHaveTextContent('false'); + }); + + expect(screen.getByTestId('tabTitle')).toHaveTextContent( + 'Test Entity | Foo | Bar | Baz | Backstage', + ); + }); +}); diff --git a/plugins/techdocs/src/hooks/useTechDocsReaderHeaderData.ts b/plugins/techdocs/src/hooks/useTechDocsReaderHeaderData.ts new file mode 100644 index 0000000000..56958d05d0 --- /dev/null +++ b/plugins/techdocs/src/hooks/useTechDocsReaderHeaderData.ts @@ -0,0 +1,107 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useEffect } from 'react'; +import { useParams } from 'react-router-dom'; +import { stringifyEntityRef } from '@backstage/catalog-model'; +import { configApiRef, useApi } from '@backstage/core-plugin-api'; +import { + useTechDocsAddons, + useTechDocsReaderPage, +} from '@backstage/plugin-techdocs-react'; +import { entityPresentationApiRef } from '@backstage/plugin-catalog-react'; +import capitalize from 'lodash/capitalize'; + +/** + * Shared hook for TechDocs reader header data. + * Encapsulates title/subtitle sync, tab title computation, + * 404 detection, and source link visibility. + */ +export function useTechDocsReaderHeaderData() { + const addons = useTechDocsAddons(); + const configApi = useApi(configApiRef); + const entityPresentationApi = useApi(entityPresentationApiRef); + const { '*': path = '' } = useParams(); + + const { + title, + setTitle, + subtitle, + setSubtitle, + entityRef, + metadata: { value: metadata, loading: metadataLoading }, + entityMetadata: { value: entityMetadata, loading: entityMetadataLoading }, + } = useTechDocsReaderPage(); + + useEffect(() => { + if (!metadata) return; + setTitle(metadata.site_name); + setSubtitle(() => { + let { site_description } = metadata; + if (!site_description || site_description === 'None') { + site_description = ''; + } + return site_description; + }); + }, [metadata, setTitle, setSubtitle]); + + const appTitle = configApi.getOptionalString('app.title') || 'Backstage'; + const locationMetadata = entityMetadata?.locationMetadata; + const showSourceLink = + !!locationMetadata && + locationMetadata.type !== 'dir' && + locationMetadata.type !== 'file'; + + const noEntMetadata = !entityMetadataLoading && entityMetadata === undefined; + const noTdMetadata = !metadataLoading && metadata === undefined; + const hidden = noEntMetadata || noTdMetadata; + + const removeTrailingSlash = (str: string) => str.replace(/\/$/, ''); + const normalizeAndSpace = (str: string) => + str.replace(/[-_]/g, ' ').split(' ').map(capitalize).join(' '); + + let tabTitle = appTitle; + if (!hidden) { + const stringEntityRef = stringifyEntityRef(entityRef); + const entityDisplayName = + entityPresentationApi.forEntity(stringEntityRef).snapshot.primaryTitle; + + let techdocsTabTitleItems: string[] = []; + if (path !== '') + techdocsTabTitleItems = removeTrailingSlash(path) + .split('/') + .map(normalizeAndSpace); + + const tabTitleItems = [ + entityDisplayName, + ...techdocsTabTitleItems, + appTitle, + ]; + tabTitle = tabTitleItems.join(' | '); + } + + return { + title, + subtitle, + entityRef, + entityMetadata, + tabTitle, + hidden, + showSourceLink, + sourceLink: locationMetadata?.target, + addons, + }; +} diff --git a/plugins/techdocs/src/hooks/useTechDocsSearch.test.tsx b/plugins/techdocs/src/hooks/useTechDocsSearch.test.tsx new file mode 100644 index 0000000000..ff5fd7418e --- /dev/null +++ b/plugins/techdocs/src/hooks/useTechDocsSearch.test.tsx @@ -0,0 +1,105 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ReactNode, useState, useEffect } from 'react'; +import { waitFor, screen } from '@testing-library/react'; + +import { + searchApiRef, + MockSearchApi, + SearchContextProvider, +} from '@backstage/plugin-search-react'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; + +import { useTechDocsSearch } from './useTechDocsSearch'; + +const entityId = { + kind: 'Component', + name: 'test-component', + namespace: 'default', +}; + +const mockSearchApi = new MockSearchApi({ + results: [ + { + type: 'techdocs', + document: { + location: '/docs/default/component/test-component/getting-started', + title: 'Getting Started', + text: 'This guide helps you get started.', + }, + }, + ], +}); + +function HookRenderer() { + const data = useTechDocsSearch(entityId); + const [, setRender] = useState(0); + useEffect(() => setRender(r => r + 1), [data.results.length, data.loading]); + return ( +
    + {data.results.length} + {String(data.loading)} + {String(data.deferredLoading)} + {data.term} + {data.results.map((r, i) => ( + + {r.document.title} + + ))} +
    + ); +} + +const Wrapper = ({ children }: { children: ReactNode }) => ( + + + {children} + + +); + +describe('useTechDocsSearch', () => { + it('should return search results', async () => { + await renderInTestApp( + + + , + ); + + await waitFor(() => { + expect(screen.getByTestId('resultCount')).toHaveTextContent('1'); + }); + + expect(screen.getByTestId('result-0')).toHaveTextContent('Getting Started'); + }); + + it('should not show deferred loading initially', async () => { + await renderInTestApp( + + + , + ); + + expect(screen.getByTestId('deferredLoading')).toHaveTextContent('false'); + }); +}); diff --git a/plugins/techdocs/src/hooks/useTechDocsSearch.ts b/plugins/techdocs/src/hooks/useTechDocsSearch.ts new file mode 100644 index 0000000000..86fa89e864 --- /dev/null +++ b/plugins/techdocs/src/hooks/useTechDocsSearch.ts @@ -0,0 +1,86 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useEffect, useRef, useState } from 'react'; +import { useSearch } from '@backstage/plugin-search-react'; +import { CompoundEntityRef } from '@backstage/catalog-model'; +import { ResultHighlight } from '@backstage/plugin-search-common'; + +export type TechDocsDoc = { + namespace: string; + kind: string; + name: string; + path: string; + location: string; + title: string; + text: string; +}; + +export type TechDocsSearchResult = { + type: string; + document: TechDocsDoc; + highlight?: ResultHighlight; +}; + +/** + * Shared hook for TechDocs search logic. + * Encapsulates entity filter sync, results slicing, + * and deferred loading state. + */ +export function useTechDocsSearch(entityId: CompoundEntityRef) { + const { + setFilters, + setTerm, + term, + result: { loading, value: searchVal }, + } = useSearch(); + const [results, setResults] = useState([]); + const [deferredLoading, setDeferredLoading] = useState(false); + const loadingTimer = useRef>(); + + useEffect(() => { + if (searchVal) { + setResults(searchVal.results.slice(0, 10) as TechDocsSearchResult[]); + } + }, [loading, searchVal]); + + useEffect(() => { + clearTimeout(loadingTimer.current); + setDeferredLoading(false); + if (loading) { + loadingTimer.current = setTimeout(() => setDeferredLoading(true), 200); + } + return () => clearTimeout(loadingTimer.current); + }, [term, loading]); + + const { kind, name, namespace } = entityId; + useEffect(() => { + setFilters(prevFilters => ({ + ...prevFilters, + kind, + namespace, + name, + })); + }, [kind, namespace, name, setFilters]); + + return { + results, + term, + setTerm, + loading, + deferredLoading, + }; +} diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.test.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.test.tsx index 77bb3d134f..cc4735ed02 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.test.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.test.tsx @@ -233,12 +233,13 @@ describe('', () => { useTechDocsReaderDom.mockReturnValue(mockTechDocsPage); useReaderState.mockReturnValue({ state: 'cached' }); - window.location.hash = '#emojis'; - const rendered = await renderInTestApp( , + { + routeEntries: ['/#emojis'], + }, ); await waitFor(() => { @@ -248,8 +249,6 @@ describe('', () => { expect(mockScrollIntoView).toHaveBeenCalled(); expect(document.querySelector).not.toHaveBeenCalledWith('header'); }); - - window.location.hash = ''; }); it('should render progress bar when content is loading', async () => { diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.tsx index fe72b09073..0ee13703c3 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.tsx @@ -14,30 +14,19 @@ * limitations under the License. */ -import { useCallback, useEffect } from 'react'; - import Grid from '@material-ui/core/Grid'; import { makeStyles } from '@material-ui/core/styles'; -import { - TechDocsShadowDom, - useShadowDomStylesLoading, - useShadowRootElements, - useTechDocsReaderPage, -} from '@backstage/plugin-techdocs-react'; +import { TechDocsShadowDom } from '@backstage/plugin-techdocs-react'; import { CompoundEntityRef } from '@backstage/catalog-model'; import { Content, Progress } from '@backstage/core-components'; import { TechDocsSearch } from '../../../search'; import { TechDocsStateIndicator } from '../TechDocsStateIndicator'; -import { useTechDocsReaderDom } from './dom'; -import { - useTechDocsReader, - withTechDocsReaderProvider, -} from '../TechDocsReaderProvider'; +import { withTechDocsReaderProvider } from '../TechDocsReaderProvider'; import { TechDocsReaderPageContentAddons } from './TechDocsReaderPageContentAddons'; -import { useApp } from '@backstage/core-plugin-api'; +import { useTechDocsReaderContentData } from '../../../hooks/useTechDocsReaderContentData'; const useStyles = makeStyles({ search: { @@ -89,52 +78,26 @@ export type TechDocsReaderPageContentProps = { */ export const TechDocsReaderPageContent = withTechDocsReaderProvider( (props: TechDocsReaderPageContentProps) => { - const { withSearch = true, searchResultUrlMapper, onReady } = props; + const { withSearch = true, searchResultUrlMapper } = props; const classes = useStyles(); const { - entityMetadata: { value: entityMetadata, loading: entityMetadataLoading }, entityRef, - setShadowRoot, - } = useTechDocsReaderPage(); - const { state } = useTechDocsReader(); - const dom = useTechDocsReaderDom(entityRef, props.defaultPath); - const path = window.location.pathname; - const hash = window.location.hash; - const isStyleLoading = useShadowDomStylesLoading(dom); - const [hashElement] = useShadowRootElements([`[id="${hash.slice(1)}"]`]); - const app = useApp(); - const { NotFoundErrorPage } = app.getComponents(); + entityMetadata, + dom, + handleAppend, + isNotFound, + isDomReady, + showProgress, + NotFoundErrorPage, + } = useTechDocsReaderContentData({ + defaultPath: props.defaultPath, + onReady: props.onReady, + }); - useEffect(() => { - if (isStyleLoading) return; + if (isNotFound) return ; - if (hash) { - if (hashElement) { - hashElement.scrollIntoView(); - } - } else { - document?.querySelector('header')?.scrollIntoView(); - } - }, [path, hash, hashElement, isStyleLoading]); - - const handleAppend = useCallback( - (newShadowRoot: ShadowRoot) => { - setShadowRoot(newShadowRoot); - if (onReady instanceof Function) { - onReady(); - } - }, - [setShadowRoot, onReady], - ); - - // No entity metadata = 404. Don't render content at all. - if (entityMetadataLoading === false && !entityMetadata) - return ; - - // Do not return content until dom is ready; instead, render a state - // indicator, which handles progress and content errors on our behalf. - if (!dom) { + if (!isDomReady) { return ( @@ -162,10 +125,9 @@ export const TechDocsReaderPageContent = withTechDocsReaderProvider( )} - {/* Centers the styles loaded event to avoid having multiple locations setting the opacity style in Shadow Dom causing the screen to flash multiple times */} - {(state === 'CHECKING' || isStyleLoading) && } + {showProgress && } - + diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPageHeader/TechDocsReaderPageHeader.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPageHeader/TechDocsReaderPageHeader.tsx index 6328becc73..b646c5bfe8 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPageHeader/TechDocsReaderPageHeader.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPageHeader/TechDocsReaderPageHeader.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { PropsWithChildren, useEffect } from 'react'; +import { PropsWithChildren } from 'react'; import Helmet from 'react-helmet'; import Grid from '@material-ui/core/Grid'; @@ -23,29 +23,22 @@ import CodeIcon from '@material-ui/icons/Code'; import { TechDocsAddonLocations as locations, - useTechDocsAddons, - useTechDocsReaderPage, TechDocsEntityMetadata, TechDocsMetadata, } from '@backstage/plugin-techdocs-react'; import { - entityPresentationApiRef, EntityRefLink, EntityRefLinks, getEntityRelations, } from '@backstage/plugin-catalog-react'; -import { - RELATION_OWNED_BY, - CompoundEntityRef, - stringifyEntityRef, -} from '@backstage/catalog-model'; +import { RELATION_OWNED_BY, CompoundEntityRef } from '@backstage/catalog-model'; import { Header, HeaderLabel } from '@backstage/core-components'; -import { useRouteRef, configApiRef, useApi } from '@backstage/core-plugin-api'; +import { useRouteRef } from '@backstage/core-plugin-api'; import capitalize from 'lodash/capitalize'; import { rootRouteRef } from '../../../routes'; -import { useParams } from 'react-router-dom'; +import { useTechDocsReaderHeaderData } from '../../../hooks/useTechDocsReaderHeaderData'; const skeleton = ; @@ -71,45 +64,29 @@ export const TechDocsReaderPageHeader = ( props: TechDocsReaderPageHeaderProps, ) => { const { children } = props; - const addons = useTechDocsAddons(); - const configApi = useApi(configApiRef); - - const entityPresentationApi = useApi(entityPresentationApiRef); - const { '*': path = '' } = useParams(); - const { title, - setTitle, subtitle, - setSubtitle, entityRef, - metadata: { value: metadata, loading: metadataLoading }, - entityMetadata: { value: entityMetadata, loading: entityMetadataLoading }, - } = useTechDocsReaderPage(); + entityMetadata, + tabTitle, + hidden, + showSourceLink, + sourceLink, + addons, + } = useTechDocsReaderHeaderData(); - useEffect(() => { - if (!metadata) return; - setTitle(metadata.site_name); - setSubtitle(() => { - let { site_description } = metadata; - if (!site_description || site_description === 'None') { - site_description = ''; - } - return site_description; - }); - }, [metadata, setTitle, setSubtitle]); + const docsRootLink = useRouteRef(rootRouteRef)(); - const appTitle = configApi.getOptional('app.title') || 'Backstage'; + if (hidden) return null; - const { locationMetadata, spec } = entityMetadata || {}; + const { spec } = entityMetadata || {}; const lifecycle = spec?.lifecycle; const ownedByRelations = entityMetadata ? getEntityRelations(entityMetadata, RELATION_OWNED_BY) : []; - const docsRootLink = useRouteRef(rootRouteRef)(); - const labels = ( <> ) : null} - {locationMetadata && - locationMetadata.type !== 'dir' && - locationMetadata.type !== 'file' ? ( + {showSourceLink ? ( } - url={locationMetadata.target} + url={sourceLink} /> ) : null} ); - // If there is no entity or techdocs metadata, there's no reason to show the - // header (hides the header on 404 error pages). - const noEntMetadata = !entityMetadataLoading && entityMetadata === undefined; - const noTdMetadata = !metadataLoading && metadata === undefined; - if (noEntMetadata || noTdMetadata) return null; - - const stringEntityRef = stringifyEntityRef(entityRef); - - const entityDisplayName = - entityPresentationApi.forEntity(stringEntityRef).snapshot.primaryTitle; - - const removeTrailingSlash = (str: string) => str.replace(/\/$/, ''); - const normalizeAndSpace = (str: string) => - str.replace(/[-_]/g, ' ').split(' ').map(capitalize).join(' '); - - let techdocsTabTitleItems: string[] = []; - - if (path !== '') - techdocsTabTitleItems = removeTrailingSlash(path) - .split('/') - .map(normalizeAndSpace); - - const tabTitleItems = [entityDisplayName, ...techdocsTabTitleItems, appTitle]; - const tabTitle = tabTitleItems.join(' | '); - return (
    { }); it('should handle stale content', async () => { - techdocsStorageApi.getEntityDocs - .mockResolvedValueOnce('my content') - .mockImplementationOnce(async () => { - await new Promise(resolve => setTimeout(resolve, 1100)); - return 'my new content'; + jest.useFakeTimers(); + + try { + techdocsStorageApi.getEntityDocs + .mockResolvedValueOnce('my content') + .mockImplementationOnce(async () => { + await new Promise(resolve => setTimeout(resolve, 1100)); + return 'my new content'; + }); + techdocsStorageApi.syncEntityDocs.mockImplementation( + async (_, logHandler) => { + await 'a tick'; + logHandler?.call(this, 'Line 1'); + logHandler?.call(this, 'Line 2'); + await new Promise(resolve => setTimeout(resolve, 1100)); + return 'updated'; + }, + ); + + const { result } = renderHook( + () => useReaderState('Component', 'default', 'backstage', '/example'), + { wrapper: Wrapper }, + ); + + expect(result.current).toEqual({ + state: 'CHECKING', + path: '/example', + content: undefined, + contentErrorMessage: undefined, + syncErrorMessage: undefined, + buildLog: [], + contentReload: expect.any(Function), }); - techdocsStorageApi.syncEntityDocs.mockImplementation( - async (_, logHandler) => { - await 'a tick'; - logHandler?.call(this, 'Line 1'); - logHandler?.call(this, 'Line 2'); - await new Promise(resolve => setTimeout(resolve, 1100)); - return 'updated'; - }, - ); - const { result } = renderHook( - () => useReaderState('Component', 'default', 'backstage', '/example'), - { wrapper: Wrapper }, - ); + // flush microtasks: content loads (resolved promise) and sync progresses past 'a tick' + await act(async () => {}); - expect(result.current).toEqual({ - state: 'CHECKING', - path: '/example', - content: undefined, - contentErrorMessage: undefined, - syncErrorMessage: undefined, - buildLog: [], - contentReload: expect.any(Function), - }); - - // the content is returned but the sync is in progress - await waitFor(() => { + // the content is returned but the sync is in progress expect(result.current).toEqual({ state: 'CONTENT_FRESH', path: '/example', @@ -457,10 +462,12 @@ describe('useReaderState', () => { buildLog: ['Line 1', 'Line 2'], contentReload: expect.any(Function), }); - }); - // the sync takes longer than 1 seconds so the refreshing state starts - await waitFor(() => { + // the sync takes longer than 1 second so the refreshing state starts + await act(async () => { + jest.advanceTimersByTime(1000); + }); + expect(result.current).toEqual({ state: 'CONTENT_STALE_REFRESHING', path: '/example', @@ -470,10 +477,12 @@ describe('useReaderState', () => { buildLog: ['Line 1', 'Line 2'], contentReload: expect.any(Function), }); - }); - // the content is updated but not yet displayed - await waitFor(() => { + // the sync completes — content is updated but not yet displayed + await act(async () => { + jest.advanceTimersByTime(100); + }); + expect(result.current).toEqual({ state: 'CONTENT_STALE_READY', path: '/example', @@ -483,15 +492,13 @@ describe('useReaderState', () => { buildLog: ['Line 1', 'Line 2'], contentReload: expect.any(Function), }); - }); - // reload the content - await act(async () => { - result.current.contentReload(); - }); + // reload the content + await act(async () => { + result.current.contentReload(); + }); - // the new content refresh is triggered - await waitFor(() => { + // the new content refresh is triggered expect(result.current).toEqual({ state: 'CHECKING', path: '/example', @@ -501,39 +508,38 @@ describe('useReaderState', () => { buildLog: [], contentReload: expect.any(Function), }); - }); - // the new content is loaded - await waitFor( - () => { - expect(result.current).toEqual({ - state: 'CONTENT_FRESH', - path: '/example', - content: 'my new content', - contentErrorMessage: undefined, - syncErrorMessage: undefined, - buildLog: [], - contentReload: expect.any(Function), - }); - }, - { - timeout: 2000, - }, - ); + // the new content is loaded + await act(async () => { + jest.advanceTimersByTime(1100); + }); - expect(techdocsStorageApi.getEntityDocs).toHaveBeenCalledTimes(2); - expect(techdocsStorageApi.getEntityDocs).toHaveBeenCalledWith( - { kind: 'Component', namespace: 'default', name: 'backstage' }, - '/example', - ); - expect(techdocsStorageApi.syncEntityDocs).toHaveBeenCalledWith( - { - kind: 'Component', - namespace: 'default', - name: 'backstage', - }, - expect.any(Function), - ); + expect(result.current).toEqual({ + state: 'CONTENT_FRESH', + path: '/example', + content: 'my new content', + contentErrorMessage: undefined, + syncErrorMessage: undefined, + buildLog: [], + contentReload: expect.any(Function), + }); + + expect(techdocsStorageApi.getEntityDocs).toHaveBeenCalledTimes(2); + expect(techdocsStorageApi.getEntityDocs).toHaveBeenCalledWith( + { kind: 'Component', namespace: 'default', name: 'backstage' }, + '/example', + ); + expect(techdocsStorageApi.syncEntityDocs).toHaveBeenCalledWith( + { + kind: 'Component', + namespace: 'default', + name: 'backstage', + }, + expect.any(Function), + ); + } finally { + jest.useRealTimers(); + } }); it('should handle navigation', async () => { diff --git a/plugins/techdocs/src/reader/transformers/copyToClipboard.tsx b/plugins/techdocs/src/reader/transformers/copyToClipboard.tsx index d34a05fa06..004995937e 100644 --- a/plugins/techdocs/src/reader/transformers/copyToClipboard.tsx +++ b/plugins/techdocs/src/reader/transformers/copyToClipboard.tsx @@ -67,7 +67,12 @@ const CopyToClipboardButton = ({ text }: CopyToClipboardButtonProps) => { leaveDelay={1000} > { /** * Recreates copy-to-clipboard functionality attached to snippets that * is native to mkdocs-material theme. + * + * Unlike native mkdocs-material theme, this is always enabled and does not respect the mkdocs's config `theme.features` `content.code.copy` setting. */ export const copyToClipboard = (theme: Theme): Transformer => { return dom => { diff --git a/plugins/techdocs/src/reader/transformers/handleMetaRedirects.test.ts b/plugins/techdocs/src/reader/transformers/handleMetaRedirects.test.ts index 0be1773846..00180464cf 100644 --- a/plugins/techdocs/src/reader/transformers/handleMetaRedirects.test.ts +++ b/plugins/techdocs/src/reader/transformers/handleMetaRedirects.test.ts @@ -21,21 +21,13 @@ import { screen } from '@testing-library/react'; describe('handleMetaRedirects', () => { const navigate = jest.fn(); - const setUpNewTestShadowDom = async ( - html: string, - rootHref: string, - rootPath: string, - ) => { + const setUpNewTestShadowDom = async (html: string, rootHref: string) => { const entityName = 'testEntity'; - // Mock window.location.href for each test - Object.defineProperty(window, 'location', { - value: { - href: rootHref, - pathname: rootPath, - hostname: 'localhost', - }, - writable: true, - }); + // Use history.replaceState to change location (jsdom 27+ doesn't allow redefining location) + // Jest's jsdom starts at http://localhost/, so replaceState updates the pathname while + // keeping hostname and origin as 'localhost'. + const url = new URL(rootHref); + history.replaceState(null, '', `${url.pathname}${url.search}${url.hash}`); return await createTestShadowDom(html, { preTransformers: [], postTransformers: [handleMetaRedirects(navigate, entityName)], @@ -55,7 +47,6 @@ describe('handleMetaRedirects', () => { await setUpNewTestShadowDom( ``, 'http://localhost/docs/default/component/testEntity/subpath', - '/docs/default/component/testEntity/subpath', ); expect( @@ -73,7 +64,6 @@ describe('handleMetaRedirects', () => { await setUpNewTestShadowDom( ``, 'http://localhost/docs/default/component/testEntity/subpath', - '/docs/default/component/testEntity/subpath', ); expect( @@ -91,7 +81,6 @@ describe('handleMetaRedirects', () => { await setUpNewTestShadowDom( ``, 'http://localhost/docs/default/component/testEntity/subpath', - '/docs/default/component/testEntity/subpath', ); expect( @@ -107,7 +96,6 @@ describe('handleMetaRedirects', () => { await setUpNewTestShadowDom( ``, 'http://localhost/docs/default/component/testEntity/subpath', - '/docs/default/component/testEntity/subpath', ); jest.runAllTimers(); diff --git a/plugins/techdocs/src/reader/transformers/styles/rules/layout.ts b/plugins/techdocs/src/reader/transformers/styles/rules/layout.ts index b99fb0b020..51c683fc28 100644 --- a/plugins/techdocs/src/reader/transformers/styles/rules/layout.ts +++ b/plugins/techdocs/src/reader/transformers/styles/rules/layout.ts @@ -16,7 +16,9 @@ import { RuleOptions } from './types'; -const SIDEBAR_WIDTH = '224px'; +const TECHDOCS_SIDEBAR_WIDTH = '16rem'; +const APP_SIDEBAR_WIDTH_PINNED = '224px'; +const APP_SIDEBAR_WIDTH_COLLAPSED = '72px'; export default ({ theme, sidebar }: RuleOptions) => ` @@ -80,16 +82,13 @@ export default ({ theme, sidebar }: RuleOptions) => ` .md-sidebar { bottom: 75px; position: fixed; - width: 16rem; - overflow-y: auto; - overflow-x: hidden; - scrollbar-color: rgb(193, 193, 193) #eee; - scrollbar-width: thin; + width: ${TECHDOCS_SIDEBAR_WIDTH}; } .md-sidebar .md-sidebar__scrollwrap { - width: calc(16rem); - overflow-y: hidden; + width: calc(${TECHDOCS_SIDEBAR_WIDTH}); + height: 100% } + @supports selector(::-webkit-scrollbar) { [dir=ltr] .md-sidebar__inner { padding-right: calc(100% - 15.1rem); @@ -98,37 +97,15 @@ export default ({ theme, sidebar }: RuleOptions) => ` .md-sidebar--secondary { right: ${theme.spacing(3)}px; } -.md-sidebar::-webkit-scrollbar { - width: 5px; -} -.md-sidebar::-webkit-scrollbar-button { - width: 5px; - height: 5px; -} -.md-sidebar::-webkit-scrollbar-track { - background: #eee; - border: 1 px solid rgb(250, 250, 250); - box-shadow: 0px 0px 3px #dfdfdf inset; - border-radius: 3px; -} -.md-sidebar::-webkit-scrollbar-thumb { - width: 5px; - background: rgb(193, 193, 193); - border: transparent; - border-radius: 3px; -} -.md-sidebar::-webkit-scrollbar-thumb:hover { - background: rgb(125, 125, 125); -} .md-content { - max-width: calc(100% - 16rem * 2); - margin-left: 16rem; + max-width: calc(100% - ${TECHDOCS_SIDEBAR_WIDTH} * 2); + margin-left: ${TECHDOCS_SIDEBAR_WIDTH}; margin-bottom: 50px; } .md-content > .md-sidebar { - left: 16rem; + left: ${TECHDOCS_SIDEBAR_WIDTH}; } .md-footer { @@ -145,7 +122,7 @@ export default ({ theme, sidebar }: RuleOptions) => ` background-color: unset; } .md-footer-nav__link, .md-footer__link { - width: 16rem; + width: ${TECHDOCS_SIDEBAR_WIDTH}; } .md-dialog { @@ -155,6 +132,8 @@ export default ({ theme, sidebar }: RuleOptions) => ` @media screen and (min-width: 76.25em) { .md-sidebar { height: auto; + /* Less padding before the Previous / Next buttons */ + padding-bottom: 0 !important; } } @@ -208,12 +187,12 @@ export default ({ theme, sidebar }: RuleOptions) => ` height: 100%; } .md-sidebar--primary { - width: 16rem !important; + width: ${TECHDOCS_SIDEBAR_WIDTH} !important; z-index: 200; left: ${ sidebar.isPinned - ? `calc(-16rem + ${SIDEBAR_WIDTH})` - : 'calc(-16rem + 72px)' + ? `calc(-${TECHDOCS_SIDEBAR_WIDTH} + var(--techdocs-sidebar-closed-offset-pinned, ${APP_SIDEBAR_WIDTH_PINNED}))` + : `calc(-${TECHDOCS_SIDEBAR_WIDTH} + var(--techdocs-sidebar-closed-offset-collapsed, ${APP_SIDEBAR_WIDTH_COLLAPSED}))` } !important; } .md-sidebar--secondary:not([hidden]) { @@ -221,7 +200,7 @@ export default ({ theme, sidebar }: RuleOptions) => ` } [data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary { - transform: translateX(16rem); + transform: translateX(var(--techdocs-sidebar-open-translate, ${TECHDOCS_SIDEBAR_WIDTH})); } .md-content { @@ -251,8 +230,8 @@ export default ({ theme, sidebar }: RuleOptions) => ` @media screen and (max-width: 600px) { .md-sidebar--primary { - left: -16rem !important; - width: 16rem; + left: -${TECHDOCS_SIDEBAR_WIDTH} !important; + width: ${TECHDOCS_SIDEBAR_WIDTH}; } } diff --git a/plugins/techdocs/src/reader/transformers/styles/rules/typeset.ts b/plugins/techdocs/src/reader/transformers/styles/rules/typeset.ts index 680e58e182..f19c4ebdac 100644 --- a/plugins/techdocs/src/reader/transformers/styles/rules/typeset.ts +++ b/plugins/techdocs/src/reader/transformers/styles/rules/typeset.ts @@ -110,7 +110,7 @@ ${headings.reduce((style, heading) => { .md-typeset table:not([class]) { font-size: var(--md-typeset-font-size); - border: 1px solid var(--md-default-fg-color); + border: 1px solid var(--md-typeset-table-color); border-bottom: none; border-collapse: collapse; border-radius: ${theme.shape.borderRadius}px; @@ -119,7 +119,7 @@ ${headings.reduce((style, heading) => { font-weight: bold; } .md-typeset table:not([class]) td, .md-typeset table:not([class]) th { - border-bottom: 1px solid var(--md-default-fg-color); + border-bottom: 1px solid var(--md-typeset-table-color); } .md-typeset pre > code::-webkit-scrollbar-thumb { diff --git a/plugins/techdocs/src/reader/transformers/styles/rules/variables.ts b/plugins/techdocs/src/reader/transformers/styles/rules/variables.ts index c419d5f71b..f6d9cd7ea7 100644 --- a/plugins/techdocs/src/reader/transformers/styles/rules/variables.ts +++ b/plugins/techdocs/src/reader/transformers/styles/rules/variables.ts @@ -28,11 +28,8 @@ export default ({ theme }: RuleOptions) => ` /* FONT */ --md-default-fg-color: ${theme.palette.text.primary}; --md-default-fg-color--light: ${theme.palette.text.secondary}; - --md-default-fg-color--lighter: ${lighten(theme.palette.text.secondary, 0.7)}; - --md-default-fg-color--lightest: ${lighten( - theme.palette.text.secondary, - 0.3, - )}; + --md-default-fg-color--lighter: ${alpha(theme.palette.text.secondary, 0.3)}; + --md-default-fg-color--lightest: ${alpha(theme.palette.text.secondary, 0.15)}; /* BACKGROUND */ --md-default-bg-color:${theme.palette.background.default}; @@ -146,7 +143,7 @@ export default ({ theme }: RuleOptions) => ` --md-typeset-font-size: 1rem; --md-typeset-color: var(--md-default-fg-color); --md-typeset-a-color: ${theme.palette.link}; - --md-typeset-table-color: ${theme.palette.text.primary}; + --md-typeset-table-color: ${alpha(theme.palette.text.primary, 0.15)}; --md-typeset-table-color--light: ${alpha(theme.palette.text.primary, 0.05)}; --md-typeset-del-color: ${ theme.palette.type === 'dark' diff --git a/plugins/techdocs/src/search/components/TechDocsSearch.tsx b/plugins/techdocs/src/search/components/TechDocsSearch.tsx index a9834c8b69..daff34e153 100644 --- a/plugins/techdocs/src/search/components/TechDocsSearch.tsx +++ b/plugins/techdocs/src/search/components/TechDocsSearch.tsx @@ -15,15 +15,17 @@ */ import { CompoundEntityRef } from '@backstage/catalog-model'; -import { ResultHighlight } from '@backstage/plugin-search-common'; import { SearchAutocomplete, SearchContextProvider, - useSearch, } from '@backstage/plugin-search-react'; -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { TechDocsSearchResultListItem } from './TechDocsSearchResultListItem'; +import { + useTechDocsSearch, + TechDocsSearchResult, +} from '../../hooks/useTechDocsSearch'; /** * Props for {@link TechDocsSearch} @@ -37,21 +39,6 @@ export type TechDocsSearchProps = { searchResultUrlMapper?: (url: string) => string; }; -type TechDocsDoc = { - namespace: string; - kind: string; - name: string; - path: string; - location: string; - title: string; -}; - -type TechDocsSearchResult = { - type: string; - document: TechDocsDoc; - highlight?: ResultHighlight; -}; - const isTechDocsSearchResult = ( option: any, ): option is TechDocsSearchResult => { @@ -67,40 +54,7 @@ const TechDocsSearchBar = (props: TechDocsSearchProps) => { } = props; const [open, setOpen] = useState(false); const navigate = useNavigate(); - const { - setFilters, - term, - result: { loading, value: searchVal }, - } = useSearch(); - const [options, setOptions] = useState([]); - useEffect(() => { - let mounted = true; - - if (mounted && searchVal) { - // TODO: Change this into getting only subset of search results from the BE in the first place - // once pagination is implemented for search engines - // See: https://github.com/backstage/backstage/issues/6062 - const searchResults = searchVal.results.slice(0, 10); - setOptions(searchResults); - } - return () => { - mounted = false; - }; - }, [loading, searchVal]); - - // Update the filter context when the entityId changes, e.g. when the search - // bar continues to be rendered, navigating between different TechDocs sites. - const { kind, name, namespace } = entityId; - useEffect(() => { - setFilters(prevFilters => { - return { - ...prevFilters, - kind, - namespace, - name, - }; - }); - }, [kind, namespace, name, setFilters]); + const { results, term, loading } = useTechDocsSearch(entityId); const handleSelection = ( _: any, @@ -133,7 +87,7 @@ const TechDocsSearchBar = (props: TechDocsSearchProps) => { blurOnSelect noOptionsText="No results found" value={null} - options={options} + options={results} renderOption={({ document, highlight }) => ( { + it('should return empty', async () => { + await expect( + storage.multiget({ + userEntityRef: 'user-1', + items: [ + { + bucket: 'bucket-c', + key: 'key-c', + }, + ], + }), + ).resolves.toEqual([null]); + }); + + it('should handle large inputs and return existing values', async () => { + // Create 30 buckets with 300 keys in each. + const BUCKETS = 30; + const KEYS_PER_BUCKET = 300; + + const buckets = Array.from(Array(BUCKETS)).map( + (_, i) => `mget-bucket-${i}`, + ); + + const items = buckets.flatMap(bucket => { + const keys = Array.from(Array(KEYS_PER_BUCKET)).map( + (_, i) => `mget-key-${i}`, + ); + return keys.map(key => ({ bucket, key })); + }); + + const chunkify = (keys: Array, size: number): T[][] => { + const chunks: T[][] = []; + for (let i = 0; i < keys.length; i += size) { + chunks.push(keys.slice(i, i + size)); + } + return chunks; + }; + + const valueOfKey = (bucket: string, key: string) => ({ + theValue: `Value of ${bucket} / ${key}`, + }); + + const chunkedItems = chunkify(items, 100); + for (const chunk of chunkedItems) { + await insert( + chunk.map(({ bucket, key }) => ({ + user_entity_ref: 'user-1', + bucket, + key, + value: JSON.stringify(valueOfKey(bucket, key)), + })), + ); + } + + const result = await storage.multiget({ + userEntityRef: 'user-1', + // Include a missing key which shouldn't exist in the result + items: [...items, { bucket: 'missing', key: 'missing' }], + }); + expect(result).toEqual([ + ...items.map(({ bucket, key }) => ({ + value: valueOfKey(bucket, key), + })), + null, // The missing key + ]); + + const rows = await knex('user_settings') + .where('user_entity_ref', 'user-1') + .count<{ count: string }[]>('* as count'); + const savedRows = Number(rows[0].count); + expect(savedRows).toEqual(BUCKETS * KEYS_PER_BUCKET); + }); + }); + describe('set', () => { it('should insert a new setting', async () => { await storage.set({ diff --git a/plugins/user-settings-backend/src/database/DatabaseUserSettingsStore.ts b/plugins/user-settings-backend/src/database/DatabaseUserSettingsStore.ts index 8caa62c4cd..c631227ae3 100644 --- a/plugins/user-settings-backend/src/database/DatabaseUserSettingsStore.ts +++ b/plugins/user-settings-backend/src/database/DatabaseUserSettingsStore.ts @@ -21,6 +21,7 @@ import { import { NotFoundError } from '@backstage/errors'; import { JsonValue } from '@backstage/types'; import { Knex } from 'knex'; +import pLimit from 'p-limit'; import { UserSettingsStore, type UserSetting } from './UserSettingsStore'; const migrationsDir = resolvePackagePath( @@ -28,6 +29,8 @@ const migrationsDir = resolvePackagePath( 'migrations', ); +const dbLimit = pLimit(10); + /** * @public */ @@ -91,6 +94,75 @@ export class DatabaseUserSettingsStore implements UserSettingsStore { }; } + async multiget(options: { + userEntityRef: string; + items: Array<{ bucket: string; key: string }>; + }): Promise<({ value: JsonValue } | null)[]> { + if (options.items.length === 0) { + return []; + } + + // Split the items into a map of bucket -> keys + const bucketMap = new Map>(); + for (const item of options.items) { + let keys = bucketMap.get(item.bucket); + if (!keys) { + keys = new Set(); + bucketMap.set(item.bucket, keys); + } + keys.add(item.key); + } + + // Chunks the keys per bucket to avoid hitting SQL parameter limits + const chunkKeys = (keys: Array, size: number): string[][] => { + const chunks = []; + for (let i = 0; i < keys.length; i += size) { + chunks.push(keys.slice(i, i + size)); + } + return chunks; + }; + + // Store the database content into a map of bucket -> {key -> value} + const resultsMap = new Map>(); + + await Promise.all( + Array.from(bucketMap.entries()).map(([bucket, keySet]) => + dbLimit(async (): Promise => { + const keyMap = new Map(); + resultsMap.set(bucket, keyMap); + + const keyChunks = chunkKeys(Array.from(keySet), 100); + + for (const keys of keyChunks) { + const rows = await this.db('user_settings') + .where({ + user_entity_ref: options.userEntityRef, + bucket, + }) + .whereIn('key', keys) + .select(['bucket', 'key', 'value']); + + for (const row of rows) { + keyMap.set(row.key, JSON.parse(row.value)); + } + } + }), + ), + ); + + // For each exact bucket/key requested, return either the value or null if + // not found + return options.items.map(({ bucket, key }) => { + const value = resultsMap.get(bucket)?.get(key); + + if (typeof value === 'undefined') { + return null; + } + + return { value }; + }); + } + async set(options: { userEntityRef: string; bucket: string; diff --git a/plugins/user-settings-backend/src/database/UserSettingsStore.ts b/plugins/user-settings-backend/src/database/UserSettingsStore.ts index 289c37523e..9c0a5ebf5b 100644 --- a/plugins/user-settings-backend/src/database/UserSettingsStore.ts +++ b/plugins/user-settings-backend/src/database/UserSettingsStore.ts @@ -35,6 +35,16 @@ export interface UserSettingsStore { key: string; }): Promise; + /** + * Get multiple user settings at once. The result is an array corresponding + * index by index to the elements of the input, where the elements are either + * `null` (not found), or an object `{ value: JsonValue }`. + */ + multiget(options: { + userEntityRef: string; + items: Array<{ bucket: string; key: string }>; + }): Promise<({ value: JsonValue } | null)[]>; + set(options: { userEntityRef: string; bucket: string; diff --git a/plugins/user-settings-backend/src/service/router.test.ts b/plugins/user-settings-backend/src/service/router.test.ts index afc725025c..3503ba3c80 100644 --- a/plugins/user-settings-backend/src/service/router.test.ts +++ b/plugins/user-settings-backend/src/service/router.test.ts @@ -24,9 +24,11 @@ import { mockServices, mockErrorHandler, } from '@backstage/backend-test-utils'; +import { JsonValue } from '@backstage/types'; describe('createRouter', () => { const userSettingsStore: jest.Mocked = { + multiget: jest.fn(), get: jest.fn(), set: jest.fn(), delete: jest.fn(), @@ -74,6 +76,148 @@ describe('createRouter', () => { }); }); + it('returns ok for keys with forward slashes', async () => { + const setting = { + bucket: 'my-bucket', + key: 'my-key/with/slashes', + value: 'a', + }; + + userSettingsStore.get.mockResolvedValue(setting); + + const responses = await request(app).get( + '/buckets/my-bucket/keys/my-key/with/slashes', + ); + + expect(responses.status).toEqual(200); + expect(responses.body).toEqual(setting); + + expect(userSettingsStore.get).toHaveBeenCalledTimes(1); + expect(userSettingsStore.get).toHaveBeenCalledWith({ + userEntityRef: mockUserRef, + bucket: 'my-bucket', + key: 'my-key/with/slashes', + }); + }); + + it('returns an error if the Authorization header is missing', async () => { + const responses = await request(app) + .get('/buckets/my-bucket/keys/my-key') + .set('Authorization', mockCredentials.none.header()); + + expect(responses.status).toEqual(401); + expect(userSettingsStore.get).not.toHaveBeenCalled(); + }); + }); + + describe('GET /multi', () => { + const mockMultiget = (store: Record>) => { + userSettingsStore.multiget.mockImplementation(async ({ items }) => { + return items.map(({ bucket, key }) => { + const value = store[bucket]?.[key]; + if (typeof value !== 'undefined') { + return { value }; + } + return null; + }); + }); + }; + + it('returns single value', async () => { + const values = { 'key-1': 'a' }; + + mockMultiget({ 'my-bucket': values }); + + const responses = await request(app) + .post('/multiget') + .send({ + items: Object.keys(values).map(key => ({ + bucket: 'my-bucket', + key, + })), + }); + + expect(responses.status).toEqual(200); + expect(responses.body).toEqual({ items: [{ value: 'a' }] }); + + expect(userSettingsStore.multiget).toHaveBeenCalledTimes(1); + expect(userSettingsStore.multiget).toHaveBeenCalledWith({ + userEntityRef: mockUserRef, + items: [ + { + bucket: 'my-bucket', + key: 'key-1', + }, + ], + }); + }); + + it('returns single missing', async () => { + const values = { 'key-1': 'a' }; + + mockMultiget({ 'my-bucket': values }); + + const responses = await request(app) + .post('/multiget') + .send({ + items: [{ bucket: 'my-bucket', key: 'missing-key' }], + }); + + expect(responses.status).toEqual(200); + expect(responses.body).toEqual({ + items: [null], + }); + + expect(userSettingsStore.multiget).toHaveBeenCalledTimes(1); + expect(userSettingsStore.multiget).toHaveBeenCalledWith({ + userEntityRef: mockUserRef, + items: [ + { + bucket: 'my-bucket', + key: 'missing-key', + }, + ], + }); + }); + + it('returns existing and missing mixed', async () => { + const values = { + 'key-1': 'a', + 'key-2': 'b', + }; + + mockMultiget({ 'my-bucket': values }); + + const responses = await request(app) + .post('/multiget') + .send({ + items: [ + ...Object.keys(values).map(key => ({ bucket: 'my-bucket', key })), + { bucket: 'my-bucket', key: 'missing-key' }, + ], + }); + + expect(responses.status).toEqual(200); + expect(responses.body).toEqual({ + items: [{ value: 'a' }, { value: 'b' }, null], + }); + + expect(userSettingsStore.multiget).toHaveBeenCalledTimes(1); + expect(userSettingsStore.multiget).toHaveBeenCalledWith({ + userEntityRef: mockUserRef, + items: [ + ...Object.keys(values).map(key => ({ + bucket: 'my-bucket', + key, + })), + { + bucket: 'my-bucket', + key: 'missing-key', + }, + ], + }); + }); + it('returns an error if the Authorization header is missing', async () => { const responses = await request(app) .get('/buckets/my-bucket/keys/my-key') @@ -107,6 +251,28 @@ describe('createRouter', () => { }); }); + it('returns ok for keys with forward slashes', async () => { + userSettingsStore.delete.mockResolvedValue(); + + const responses = await request(app).delete( + '/buckets/my-bucket/keys/my-key/with/slashes', + ); + + expect(responses.status).toEqual(204); + + expect(userSettingsStore.delete).toHaveBeenCalledTimes(1); + expect(userSettingsStore.delete).toHaveBeenCalledWith({ + userEntityRef: mockUserRef, + bucket: 'my-bucket', + key: 'my-key/with/slashes', + }); + expect(signalService.publish).toHaveBeenCalledWith({ + recipients: { type: 'user', entityRef: mockUserRef }, + channel: `user-settings`, + message: { type: 'key-deleted', key: 'my-key/with/slashes' }, + }); + }); + it('returns an error if the Authorization header is missing', async () => { const responses = await request(app) .delete('/buckets/my-bucket/keys/my-key') @@ -151,6 +317,26 @@ describe('createRouter', () => { }); }); + it('returns ok for keys with forward slashes', async () => { + const setting = { + bucket: 'my-bucket', + key: 'my-key/with/slashes', + value: 'a', + }; + + userSettingsStore.set.mockResolvedValue(); + userSettingsStore.get.mockResolvedValue(setting); + + const responses = await request(app) + .put('/buckets/my-bucket/keys/my-key/with/slashes') + .send({ value: 'a' }); + + expect(responses.status).toEqual(200); + expect(responses.body).toEqual(setting); + + expect(userSettingsStore.set).toHaveBeenCalledTimes(1); + }); + it('returns an error if the value is not given', async () => { const responses = await request(app) .put('/buckets/my-bucket/keys/my-key') diff --git a/plugins/user-settings-backend/src/service/router.ts b/plugins/user-settings-backend/src/service/router.ts index 07b4443551..6d1518eb8e 100644 --- a/plugins/user-settings-backend/src/service/router.ts +++ b/plugins/user-settings-backend/src/service/router.ts @@ -17,9 +17,13 @@ import { InputError } from '@backstage/errors'; import express, { Request } from 'express'; import Router from 'express-promise-router'; +import { z } from 'zod/v3'; import { UserSettingsStore } from '../database/UserSettingsStore'; import { SignalsService } from '@backstage/plugin-signals-node'; -import { UserSettingsSignal } from '@backstage/plugin-user-settings-common'; +import { + MultiGetResponse, + UserSettingsSignal, +} from '@backstage/plugin-user-settings-common'; import { HttpAuthService } from '@backstage/backend-plugin-api'; export async function createRouter(options: { @@ -30,6 +34,10 @@ export async function createRouter(options: { const router = Router(); router.use(express.json()); + const multiGetRequestSchema = z.object({ + items: z.array(z.object({ bucket: z.string(), key: z.string() })), + }); + /** * Helper method to extract the userEntityRef from the request. */ @@ -40,8 +48,22 @@ export async function createRouter(options: { return credentials.principal.userEntityRef; }; + // get multiple values + router.post('/multiget', async (req, res) => { + const userEntityRef = await getUserEntityRef(req); + + const bucketsAndKeys = multiGetRequestSchema.parse(req.body).items; + + const items = await options.userSettingsStore.multiget({ + userEntityRef, + items: bucketsAndKeys, + }); + + res.json({ items } satisfies MultiGetResponse); + }); + // get a single value - router.get('/buckets/:bucket/keys/:key', async (req, res) => { + router.get('/buckets/:bucket/keys/:key(*)', async (req, res) => { const userEntityRef = await getUserEntityRef(req); const { bucket, key } = req.params; @@ -55,7 +77,7 @@ export async function createRouter(options: { }); // set a single value - router.put('/buckets/:bucket/keys/:key', async (req, res) => { + router.put('/buckets/:bucket/keys/:key(*)', async (req, res) => { const userEntityRef = await getUserEntityRef(req); const { bucket, key } = req.params; const { value } = req.body; @@ -87,8 +109,8 @@ export async function createRouter(options: { res.json(setting); }); - // get a single value - router.delete('/buckets/:bucket/keys/:key', async (req, res) => { + // delete a single value + router.delete('/buckets/:bucket/keys/:key(*)', async (req, res) => { const userEntityRef = await getUserEntityRef(req); const { bucket, key } = req.params; diff --git a/plugins/user-settings-common/CHANGELOG.md b/plugins/user-settings-common/CHANGELOG.md index 652a308252..55755c386b 100644 --- a/plugins/user-settings-common/CHANGELOG.md +++ b/plugins/user-settings-common/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-user-settings-common +## 0.1.0 + +### Minor Changes + +- 104ca74: User-settings will now use DataLoader to batch consecutive calls into one API call to improve performance + +## 0.1.0-next.0 + +### Minor Changes + +- 104ca74: User-settings will now use DataLoader to batch consecutive calls into one API call to improve performance + ## 0.0.1 ### Patch Changes diff --git a/plugins/user-settings-common/package.json b/plugins/user-settings-common/package.json index 82c5db84f1..161c370c70 100644 --- a/plugins/user-settings-common/package.json +++ b/plugins/user-settings-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-user-settings-common", - "version": "0.0.1", + "version": "0.1.0", "description": "Common functionalities for the user-settings plugin", "backstage": { "role": "common-library", @@ -37,6 +37,9 @@ "postpack": "backstage-cli package postpack", "test": "backstage-cli package test" }, + "dependencies": { + "@backstage/types": "workspace:^" + }, "devDependencies": { "@backstage/cli": "workspace:^" } diff --git a/plugins/user-settings-common/report.api.md b/plugins/user-settings-common/report.api.md index b69f4a4190..42b9bd3f04 100644 --- a/plugins/user-settings-common/report.api.md +++ b/plugins/user-settings-common/report.api.md @@ -3,6 +3,15 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import type { JsonValue } from '@backstage/types'; + +// @public +export type MultiGetResponse = { + items: ({ + value: JsonValue; + } | null)[]; +}; + // @public (undocumented) export type UserSettingsSignal = { type: 'key-changed' | 'key-deleted'; diff --git a/plugins/user-settings-common/src/types.ts b/plugins/user-settings-common/src/types.ts index 6a49a72c93..437d0c8e1c 100644 --- a/plugins/user-settings-common/src/types.ts +++ b/plugins/user-settings-common/src/types.ts @@ -14,8 +14,17 @@ * limitations under the License. */ +import type { JsonValue } from '@backstage/types'; + /** @public */ export type UserSettingsSignal = { type: 'key-changed' | 'key-deleted'; key: string; }; + +/** + * Response type from /multiget + * + * @public + */ +export type MultiGetResponse = { items: ({ value: JsonValue } | null)[] }; diff --git a/plugins/user-settings/CHANGELOG.md b/plugins/user-settings/CHANGELOG.md index cef606d443..55c7a83508 100644 --- a/plugins/user-settings/CHANGELOG.md +++ b/plugins/user-settings/CHANGELOG.md @@ -1,5 +1,269 @@ # @backstage/plugin-user-settings +## 0.9.2 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0 + - @backstage/errors@1.3.0 + - @backstage/theme@0.7.3 + - @backstage/catalog-model@1.8.0 + - @backstage/plugin-catalog-react@2.1.2 + - @backstage/core-app-api@1.20.0 + - @backstage/frontend-plugin-api@0.16.0 + - @backstage/core-components@0.18.9 + - @backstage/core-plugin-api@1.12.5 + - @backstage/plugin-signals-react@0.0.21 + +## 0.9.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.2 + - @backstage/errors@1.3.0-next.0 + - @backstage/theme@0.7.3-next.0 + - @backstage/core-app-api@1.20.0-next.2 + - @backstage/core-components@0.18.9-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.2 + - @backstage/catalog-model@1.7.8-next.0 + - @backstage/core-plugin-api@1.12.5-next.2 + - @backstage/frontend-plugin-api@0.16.0-next.2 + +## 0.9.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + +## 0.9.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.0 + - @backstage/plugin-catalog-react@2.1.1-next.0 + - @backstage/core-components@0.18.9-next.0 + - @backstage/frontend-plugin-api@0.15.2-next.0 + - @backstage/core-app-api@1.19.7-next.0 + - @backstage/core-plugin-api@1.12.5-next.0 + - @backstage/catalog-model@1.7.7 + - @backstage/errors@1.2.7 + - @backstage/theme@0.7.2 + - @backstage/types@1.2.2 + - @backstage/plugin-signals-react@0.0.21-next.0 + - @backstage/plugin-user-settings-common@0.1.0 + +## 0.9.1 + +### Patch Changes + +- e26e3de: The `ProviderSettingsItem` `icon` prop now accepts `IconElement` in addition to `IconComponent`. +- 538c985: Updated installation documentation to use feature discovery as the default. +- aa29b50: New frontend system pages now use the default plugin header together with `HeaderPage` instead of the legacy core page header pattern. +- 3f36ce1: Updated alpha plugin icons to follow the new frontend icon sizing rules when rendered in plugin and navigation surfaces. +- 0be2541: Promoted the plugin's translation ref to the stable package entry point. It was previously only available through the alpha entry point. +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0 + - @backstage/ui@0.13.0 + - @backstage/core-plugin-api@1.12.4 + - @backstage/core-components@0.18.8 + - @backstage/frontend-plugin-api@0.15.0 + - @backstage/core-app-api@1.19.6 + - @backstage/catalog-model@1.7.7 + - @backstage/plugin-signals-react@0.0.20 + +## 0.9.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.6-next.1 + - @backstage/frontend-plugin-api@0.15.0-next.1 + - @backstage/core-plugin-api@1.12.4-next.1 + - @backstage/plugin-catalog-react@2.1.0-next.2 + - @backstage/core-components@0.18.8-next.1 + +## 0.9.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/theme@0.7.2 + - @backstage/types@1.2.2 + - @backstage/plugin-signals-react@0.0.20-next.0 + - @backstage/plugin-user-settings-common@0.1.0 + +## 0.9.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.14.2-next.0 + - @backstage/plugin-catalog-react@2.0.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/core-app-api@1.19.6-next.0 + - @backstage/core-components@0.18.8-next.0 + - @backstage/core-plugin-api@1.12.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/theme@0.7.2 + - @backstage/types@1.2.2 + - @backstage/plugin-signals-react@0.0.20-next.0 + - @backstage/plugin-user-settings-common@0.1.0 + +## 0.9.0 + +### Minor Changes + +- 104ca74: User-settings will now use DataLoader to batch consecutive calls into one API call to improve performance + +### Patch Changes + +- 018ca87: Added `title` and `icon` to the plugin definition for the new frontend system. +- a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0 + - @backstage/core-components@0.18.7 + - @backstage/core-app-api@1.19.5 + - @backstage/theme@0.7.2 + - @backstage/frontend-plugin-api@0.14.0 + - @backstage/core-plugin-api@1.12.3 + - @backstage/plugin-user-settings-common@0.1.0 + - @backstage/plugin-signals-react@0.0.19 + +## 0.9.0-next.2 + +### Minor Changes + +- 104ca74: User-settings will now use DataLoader to batch consecutive calls into one API call to improve performance + +### Patch Changes + +- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. +- Updated dependencies + - @backstage/plugin-catalog-react@2.0.0-next.2 + - @backstage/core-app-api@1.19.5-next.1 + - @backstage/plugin-user-settings-common@0.1.0-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.2 + - @backstage/core-components@0.18.7-next.2 + - @backstage/core-plugin-api@1.12.3-next.1 + - @backstage/plugin-signals-react@0.0.19-next.1 + - @backstage/theme@0.7.2-next.1 + +## 0.8.32-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.22.0-next.1 + - @backstage/theme@0.7.2-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.1 + - @backstage/core-components@0.18.7-next.1 + +## 0.8.32-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.6-next.0 + - @backstage/core-components@0.18.6-next.0 + - @backstage/frontend-plugin-api@0.14.0-next.0 + - @backstage/core-plugin-api@1.12.2-next.0 + - @backstage/core-app-api@1.19.4-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/theme@0.7.1 + - @backstage/types@1.2.2 + - @backstage/plugin-signals-react@0.0.19-next.0 + - @backstage/plugin-user-settings-common@0.0.1 + +## 0.8.31 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.3 + - @backstage/core-components@0.18.5 + - @backstage/plugin-catalog-react@1.21.5 + +## 0.8.31-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.5-next.0 + - @backstage/plugin-catalog-react@1.21.5-next.1 + +## 0.8.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.5-next.0 + - @backstage/frontend-plugin-api@0.13.2 + +## 0.8.30 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/plugin-signals-react@0.0.18 + +## 0.8.30-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-signals-react@0.0.18-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-user-settings-common@0.0.1 + +## 0.8.29 + +### Patch Changes + +- 2b6fda3: Revert `storageApiRef` implementation +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-signals-react@0.0.17 + ## 0.8.29-next.0 ### Patch Changes diff --git a/plugins/user-settings/README.md b/plugins/user-settings/README.md index c0ee15845c..8644001fe1 100644 --- a/plugins/user-settings/README.md +++ b/plugins/user-settings/README.md @@ -11,7 +11,20 @@ be used in the frontend as a persistent alternative to the builtin `WebStorage`. Please see [the backend README](https://github.com/backstage/backstage/tree/master/plugins/user-settings-backend) for installation instructions. -## Components Usage +## Installation + +```bash +# From your Backstage root directory +yarn --cwd packages/app add @backstage/plugin-user-settings +``` + +Once installed, the plugin is automatically available in your app through the default feature discovery. For more details and alternative installation methods, see [installing plugins](https://backstage.io/docs/frontend-system/building-apps/installing-plugins). + +## Old Frontend System + +If your Backstage app uses the old frontend system, you need to manually wire the plugin into your app as outlined in this section. If you are on the new frontend system, you can skip this. + +### Components Usage Add the item to the Sidebar: @@ -90,7 +103,7 @@ import { To standardize the UI of all setting tabs, make sure you use a similar component structure as the other tabs. You can take a look at -[the example extra tab](https://github.com/backstage/backstage/blob/master/packages/app/src/components/advancedSettings/AdvancedSettings.tsx) +[the example extra tab](https://github.com/backstage/backstage/blob/master/packages/app-legacy/src/components/advancedSettings/AdvancedSettings.tsx) we have created in Backstage's example app. To change the layout altogether, create a custom page in `packages/app/src/components/user-settings/SettingsPage.tsx`: diff --git a/plugins/user-settings/knip-report.md b/plugins/user-settings/knip-report.md index 2661c35327..d161ec5c9a 100644 --- a/plugins/user-settings/knip-report.md +++ b/plugins/user-settings/knip-report.md @@ -1,2 +1,8 @@ # Knip report +## Unused dependencies (1) + +| Name | Location | Severity | +| :------------ | :---------------- | :------- | +| @backstage/ui | package.json:69:6 | error | + diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index e78c718fe2..12d003b96e 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-user-settings", - "version": "0.8.29-next.0", + "version": "0.9.2", "description": "A Backstage plugin that provides a settings page", "backstage": { "role": "frontend-plugin", @@ -57,7 +57,6 @@ "dependencies": { "@backstage/catalog-model": "workspace:^", "@backstage/core-app-api": "workspace:^", - "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", @@ -67,9 +66,11 @@ "@backstage/plugin-user-settings-common": "workspace:^", "@backstage/theme": "workspace:^", "@backstage/types": "workspace:^", + "@backstage/ui": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", + "dataloader": "^2.0.0", "react-use": "^17.2.4", "zen-observable": "^0.10.0" }, @@ -86,13 +87,13 @@ "msw": "^1.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.2" }, "peerDependenciesMeta": { "@types/react": { diff --git a/plugins/user-settings/report-alpha.api.md b/plugins/user-settings/report-alpha.api.md index a900e5600c..aa5b0e0237 100644 --- a/plugins/user-settings/report-alpha.api.md +++ b/plugins/user-settings/report-alpha.api.md @@ -7,25 +7,31 @@ import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; -import { IconComponent } from '@backstage/core-plugin-api'; +import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; import { RouteRef } from '@backstage/frontend-plugin-api'; -import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +import { RouteRef as RouteRef_2 } from '@backstage/core-plugin-api'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) const _default: OverridableFrontendPlugin< { - root: RouteRef; + root: RouteRef_2; }, {}, { 'nav-item:user-settings': OverridableExtensionDefinition<{ kind: 'nav-item'; name: undefined; - config: {}; - configInput: {}; + config: { + title: string | undefined; + }; + configInput: { + title?: string | undefined; + }; output: ExtensionDataRef< { title: string; @@ -43,21 +49,107 @@ const _default: OverridableFrontendPlugin< }; }>; 'page:user-settings': OverridableExtensionDefinition<{ + kind: 'page'; + name: undefined; config: { path: string | undefined; + title: string | undefined; }; configInput: { path?: string | undefined; + title?: string | undefined; }; output: | ExtensionDataRef - | ExtensionDataRef | ExtensionDataRef< RouteRef, 'core.routing.ref', { optional: true; } + > + | ExtensionDataRef + | ExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >; + inputs: { + pages: ExtensionInput< + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >, + { + singleton: false; + optional: false; + internal: false; + } + >; + }; + params: { + path: string; + title?: string; + icon?: IconElement; + loader?: () => Promise; + routeRef?: RouteRef; + noHeader?: boolean; + }; + }>; + 'sub-page:user-settings/auth-providers': OverridableExtensionDefinition<{ + config: { + path: string | undefined; + title: string | undefined; + }; + configInput: { + path?: string | undefined; + title?: string | undefined; + }; + output: + | ExtensionDataRef + | ExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + > + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } >; inputs: { providerSettings: ExtensionInput< @@ -65,14 +157,92 @@ const _default: OverridableFrontendPlugin< { singleton: true; optional: true; + internal: false; } >; }; - kind: 'page'; - name: undefined; + kind: 'sub-page'; + name: 'auth-providers'; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; + title: string; + icon?: IconElement; + loader: () => Promise; + routeRef?: RouteRef; + }; + }>; + 'sub-page:user-settings/feature-flags': OverridableExtensionDefinition<{ + kind: 'sub-page'; + name: 'feature-flags'; + config: { + path: string | undefined; + title: string | undefined; + }; + configInput: { + path?: string | undefined; + title?: string | undefined; + }; + output: + | ExtensionDataRef + | ExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + > + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >; + inputs: {}; + params: { + path: string; + title: string; + icon?: IconElement; + loader: () => Promise; + routeRef?: RouteRef; + }; + }>; + 'sub-page:user-settings/general': OverridableExtensionDefinition<{ + kind: 'sub-page'; + name: 'general'; + config: { + path: string | undefined; + title: string | undefined; + }; + configInput: { + path?: string | undefined; + title?: string | undefined; + }; + output: + | ExtensionDataRef + | ExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + > + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >; + inputs: {}; + params: { + path: string; + title: string; + icon?: IconElement; loader: () => Promise; routeRef?: RouteRef; }; @@ -85,8 +255,12 @@ export default _default; export const settingsNavItem: OverridableExtensionDefinition<{ kind: 'nav-item'; name: undefined; - config: {}; - configInput: {}; + config: { + title: string | undefined; + }; + configInput: { + title?: string | undefined; + }; output: ExtensionDataRef< { title: string; @@ -104,7 +278,7 @@ export const settingsNavItem: OverridableExtensionDefinition<{ }; }>; -// @alpha (undocumented) +// @alpha @deprecated (undocumented) export const userSettingsTranslationRef: TranslationRef< 'user-settings', { @@ -146,10 +320,10 @@ export const userSettingsTranslationRef: TranslationRef< readonly 'emptyProviders.action.title': 'Open app-config.yaml and make the changes as highlighted below:'; readonly 'emptyProviders.action.readMoreButtonTitle': 'Read More'; readonly 'emptyProviders.description': 'You can add Authentication Providers to Backstage which allows you to use these providers to authenticate yourself.'; - readonly 'providerSettingsItem.title.signIn': 'Sign in to {{title}}'; readonly 'providerSettingsItem.title.signOut': 'Sign out from {{title}}'; - readonly 'providerSettingsItem.buttonTitle.signIn': 'Sign in'; + readonly 'providerSettingsItem.title.signIn': 'Sign in to {{title}}'; readonly 'providerSettingsItem.buttonTitle.signOut': 'Sign out'; + readonly 'providerSettingsItem.buttonTitle.signIn': 'Sign in'; readonly 'authProviders.title': 'Available Providers'; readonly 'defaultSettingsPage.tabsTitle.featureFlags': 'Feature Flags'; readonly 'defaultSettingsPage.tabsTitle.authProviders': 'Authentication Providers'; diff --git a/plugins/user-settings/report.api.md b/plugins/user-settings/report.api.md index c99765bc67..0b2d683e7a 100644 --- a/plugins/user-settings/report.api.md +++ b/plugins/user-settings/report.api.md @@ -5,12 +5,13 @@ ```ts import { ApiRef } from '@backstage/core-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; -import { BackstageUserIdentity } from '@backstage/core-plugin-api'; +import { BackstageUserIdentity } from '@backstage/frontend-plugin-api'; import { DiscoveryApi } from '@backstage/core-plugin-api'; import { ElementType } from 'react'; import { ErrorApi } from '@backstage/core-plugin-api'; import { FetchApi } from '@backstage/core-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { IdentityApi } from '@backstage/core-plugin-api'; import { JsonValue } from '@backstage/types'; import { JSX as JSX_2 } from 'react/jsx-runtime'; @@ -25,6 +26,7 @@ import { SignalApi } from '@backstage/plugin-signals-react'; import { StorageApi } from '@backstage/core-plugin-api'; import { StorageValueSnapshot } from '@backstage/core-plugin-api'; import { TabProps } from '@material-ui/core/Tab'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; // @public (undocumented) export const DefaultProviderSettings: (props: { @@ -35,7 +37,7 @@ export const DefaultProviderSettings: (props: { export const ProviderSettingsItem: (props: { title: string; description: string; - icon: IconComponent; + icon: IconComponent | IconElement; apiRef: ApiRef; }) => JSX_2.Element; @@ -162,6 +164,63 @@ export type UserSettingsTabProps = PropsWithChildren<{ // @public (undocumented) export const UserSettingsThemeToggle: () => JSX_2.Element; +// @public (undocumented) +export const userSettingsTranslationRef: TranslationRef< + 'user-settings', + { + readonly 'featureFlags.title': 'Feature Flags'; + readonly 'featureFlags.description': 'Please refresh the page when toggling feature flags'; + readonly 'featureFlags.filterTitle': 'Filter'; + readonly 'featureFlags.clearFilter': 'Clear filter'; + readonly 'featureFlags.emptyFlags.title': 'No Feature Flags'; + readonly 'featureFlags.emptyFlags.action.title': 'An example for how to add a feature flag is highlighted below:'; + readonly 'featureFlags.emptyFlags.action.readMoreButtonTitle': 'Read More'; + readonly 'featureFlags.emptyFlags.description': 'Feature Flags make it possible for plugins to register features in Backstage for users to opt into. You can use this to split out logic in your code for manual A/B testing, etc.'; + readonly 'featureFlags.flagItem.title.disable': 'Disable'; + readonly 'featureFlags.flagItem.title.enable': 'Enable'; + readonly 'featureFlags.flagItem.subtitle.registeredInApplication': 'Registered in the application'; + readonly 'featureFlags.flagItem.subtitle.registeredInPlugin': 'Registered in {{pluginId}} plugin'; + readonly 'languageToggle.select': 'Select language {{language}}'; + readonly 'languageToggle.title': 'Language'; + readonly 'languageToggle.description': 'Change the language'; + readonly 'themeToggle.select': 'Select {{theme}}'; + readonly 'themeToggle.title': 'Theme'; + readonly 'themeToggle.description': 'Change the theme mode'; + readonly 'themeToggle.names.auto': 'Auto'; + readonly 'themeToggle.names.dark': 'Dark'; + readonly 'themeToggle.names.light': 'Light'; + readonly 'themeToggle.selectAuto': 'Select Auto Theme'; + readonly 'signOutMenu.title': 'Sign Out'; + readonly 'signOutMenu.moreIconTitle': 'more'; + readonly 'pinToggle.title': 'Pin Sidebar'; + readonly 'pinToggle.description': 'Prevent the sidebar from collapsing'; + readonly 'pinToggle.ariaLabelTitle': 'Pin Sidebar Switch'; + readonly 'pinToggle.switchTitles.unpin': 'Unpin Sidebar'; + readonly 'pinToggle.switchTitles.pin': 'Pin Sidebar'; + readonly 'identityCard.title': 'Backstage Identity'; + readonly 'identityCard.noIdentityTitle': 'No Backstage Identity'; + readonly 'identityCard.userEntity': 'User Entity'; + readonly 'identityCard.ownershipEntities': 'Ownership Entities'; + readonly 'defaultProviderSettings.description': 'Provides authentication towards {{provider}} APIs and identities'; + readonly 'emptyProviders.title': 'No Authentication Providers'; + readonly 'emptyProviders.action.title': 'Open app-config.yaml and make the changes as highlighted below:'; + readonly 'emptyProviders.action.readMoreButtonTitle': 'Read More'; + readonly 'emptyProviders.description': 'You can add Authentication Providers to Backstage which allows you to use these providers to authenticate yourself.'; + readonly 'providerSettingsItem.title.signOut': 'Sign out from {{title}}'; + readonly 'providerSettingsItem.title.signIn': 'Sign in to {{title}}'; + readonly 'providerSettingsItem.buttonTitle.signOut': 'Sign out'; + readonly 'providerSettingsItem.buttonTitle.signIn': 'Sign in'; + readonly 'authProviders.title': 'Available Providers'; + readonly 'defaultSettingsPage.tabsTitle.featureFlags': 'Feature Flags'; + readonly 'defaultSettingsPage.tabsTitle.authProviders': 'Authentication Providers'; + readonly 'defaultSettingsPage.tabsTitle.general': 'General'; + readonly 'settingsLayout.title': 'Settings'; + readonly sidebarTitle: 'Settings'; + readonly 'profileCard.title': 'Profile'; + readonly 'appearanceCard.title': 'Appearance'; + } +>; + // @public (undocumented) export const useUserProfile: () => | { diff --git a/plugins/user-settings/src/alpha.tsx b/plugins/user-settings/src/alpha.tsx index e71c61febc..482a9711f5 100644 --- a/plugins/user-settings/src/alpha.tsx +++ b/plugins/user-settings/src/alpha.tsx @@ -19,18 +19,44 @@ import { createFrontendPlugin, PageBlueprint, NavItemBlueprint, + SubPageBlueprint, } from '@backstage/frontend-plugin-api'; -import { - convertLegacyRouteRef, - convertLegacyRouteRefs, - compatWrapper, -} from '@backstage/core-compat-api'; +import { Content } from '@backstage/core-components'; import SettingsIcon from '@material-ui/icons/Settings'; import { settingsRouteRef } from './plugin'; -export * from './translation'; +import { userSettingsTranslationRef as _userSettingsTranslationRef } from './translation'; -const userSettingsPage = PageBlueprint.makeWithOverrides({ +/** + * @alpha + * @deprecated Import from `@backstage/plugin-user-settings` instead. + */ +export const userSettingsTranslationRef = _userSettingsTranslationRef; + +const userSettingsPage = PageBlueprint.make({ + params: { + path: '/settings', + routeRef: settingsRouteRef, + title: 'Settings', + }, +}); + +const generalSettingsPage = SubPageBlueprint.make({ + name: 'general', + params: { + path: 'general', + title: 'General', + loader: () => + import('./components/General').then(m => ( + + + + )), + }, +}); + +const authProvidersSettingsPage = SubPageBlueprint.makeWithOverrides({ + name: 'auth-providers', inputs: { providerSettings: createExtensionInput([coreExtensionData.reactElement], { singleton: true, @@ -39,26 +65,40 @@ const userSettingsPage = PageBlueprint.makeWithOverrides({ }, factory(originalFactory, { inputs }) { return originalFactory({ - path: '/settings', - routeRef: convertLegacyRouteRef(settingsRouteRef), + path: 'auth-providers', + title: 'Authentication Providers', loader: () => - import('./components/SettingsPage').then(m => - compatWrapper( - ( + + , - ), - ), + /> + + )), }); }, }); +const featureFlagsSettingsPage = SubPageBlueprint.make({ + name: 'feature-flags', + params: { + path: 'feature-flags', + title: 'Feature Flags', + loader: () => + import('./components/FeatureFlags').then(m => ( + + + + )), + }, +}); + /** @alpha */ export const settingsNavItem = NavItemBlueprint.make({ params: { - routeRef: convertLegacyRouteRef(settingsRouteRef), + routeRef: settingsRouteRef, title: 'Settings', icon: SettingsIcon, }, @@ -69,9 +109,17 @@ export const settingsNavItem = NavItemBlueprint.make({ */ export default createFrontendPlugin({ pluginId: 'user-settings', + title: 'Settings', + icon: , info: { packageJson: () => import('../package.json') }, - extensions: [userSettingsPage, settingsNavItem], - routes: convertLegacyRouteRefs({ + extensions: [ + userSettingsPage, + generalSettingsPage, + authProvidersSettingsPage, + featureFlagsSettingsPage, + settingsNavItem, + ], + routes: { root: settingsRouteRef, - }), + }, }); diff --git a/plugins/user-settings/src/apis/StorageApi/CacheMap.ts b/plugins/user-settings/src/apis/StorageApi/CacheMap.ts new file mode 100644 index 0000000000..271ee93f67 --- /dev/null +++ b/plugins/user-settings/src/apis/StorageApi/CacheMap.ts @@ -0,0 +1,56 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * A Map with TTL (time-to-live) support, for the Dataloader of user settings. + */ +export class CacheMap extends Map { + #ttlMs: number; + #timestamps: Map = new Map(); + + constructor(ttlMs: number) { + super(); + this.#ttlMs = ttlMs; + } + + set(key: K, value: V) { + const result = super.set(key, value); + this.#timestamps.set(key, Date.now()); + return result; + } + + get(key: K) { + if (!this.has(key)) { + return undefined; + } + const timestamp = this.#timestamps.get(key)!; + if (Date.now() - timestamp > this.#ttlMs) { + this.delete(key); + return undefined; + } + return super.get(key); + } + + delete(key: K) { + this.#timestamps.delete(key); + return super.delete(key); + } + + clear() { + this.#timestamps.clear(); + return super.clear(); + } +} diff --git a/plugins/user-settings/src/apis/StorageApi/UserSettingsStorage.test.ts b/plugins/user-settings/src/apis/StorageApi/UserSettingsStorage.test.ts index e54ac36624..0dd8aa4c36 100644 --- a/plugins/user-settings/src/apis/StorageApi/UserSettingsStorage.test.ts +++ b/plugins/user-settings/src/apis/StorageApi/UserSettingsStorage.test.ts @@ -25,6 +25,7 @@ import { mockApis, registerMswTestHooks, } from '@backstage/test-utils'; +import { createDeferred } from '@backstage/types'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; import { UserSettingsStorage } from './UserSettingsStorage'; @@ -41,14 +42,12 @@ describe('Persistent Storage API', () => { const mockIdentityApi = mockApis.identity({ token: 'a-token' }); const mockIdentityApiFallback = mockApis.identity(); - const createPersistentStorage = ( - args?: Partial<{ - fetchApi: FetchApi; - discoveryApi: DiscoveryApi; - errorApi: ErrorApi; - namespace?: string; - }>, - ): StorageApi => { + const createPersistentStorage = (args: { + fetchApi?: FetchApi; + discoveryApi?: DiscoveryApi; + errorApi?: ErrorApi; + namespace: string; + }): StorageApi => { return UserSettingsStorage.create({ errorApi: mockErrorApi, fetchApi: new MockFetchApi(), @@ -58,14 +57,12 @@ describe('Persistent Storage API', () => { }); }; - const createPersistentStorageFallback = ( - args?: Partial<{ - fetchApi: FetchApi; - discoveryApi: DiscoveryApi; - errorApi: ErrorApi; - namespace?: string; - }>, - ): StorageApi => { + const createPersistentStorageFallback = (args: { + fetchApi?: FetchApi; + discoveryApi?: DiscoveryApi; + errorApi?: ErrorApi; + namespace: string; + }): StorageApi => { return UserSettingsStorage.create({ errorApi: mockErrorApi, fetchApi: new MockFetchApi(), @@ -75,21 +72,17 @@ describe('Persistent Storage API', () => { }); }; - afterEach(() => { + afterEach(async () => { + // Wait for server callbacks to settle before clearing the handlers. + // DataLoader delay is 10ms, so this should be plenty. + await new Promise(resolve => setTimeout(resolve, 100)); + jest.clearAllMocks(); + server.resetHandlers(); }); it('should return undefined for values which are unset', async () => { - const storage = createPersistentStorage(); - - server.use( - rest.get( - `${mockBaseUrl}/buckets/:bucket/keys/:key`, - async (_req, res, ctx) => { - return res(ctx.json({ value: 'a' })); - }, - ), - ); + const storage = createPersistentStorage({ namespace: 'undefined' }); expect(storage.snapshot('myfakekey').value).toBeUndefined(); expect(storage.snapshot('myfakekey')).toEqual({ @@ -100,7 +93,7 @@ describe('Persistent Storage API', () => { }); it('should allow setting of a simple data structure', async () => { - const storage = createPersistentStorage(); + const storage = createPersistentStorage({ namespace: 'simple' }); const dummyValue = 'a'; server.use( @@ -121,7 +114,7 @@ describe('Persistent Storage API', () => { }); it('should allow setting of a complex data structure', async () => { - const storage = createPersistentStorage(); + const storage = createPersistentStorage({ namespace: 'complex' }); const dummyValue = { some: 'nice data', with: { nested: 'values', nice: true }, @@ -144,7 +137,9 @@ describe('Persistent Storage API', () => { }); it('should fallback set when user not logged in', async () => { - const storage = createPersistentStorageFallback(); + const storage = createPersistentStorageFallback({ + namespace: 'not-logged-in', + }); const selectedKeyNextHandler = jest.fn(); const dummyValue = 'my-value'; @@ -171,12 +166,14 @@ describe('Persistent Storage API', () => { }); it('should subscribe to key changes when setting a new value', async () => { - const storage = createPersistentStorage(); + const storage = createPersistentStorage({ namespace: 'key-change-set' }); const wrongKeyNextHandler = jest.fn(); const selectedKeyNextHandler = jest.fn(); const mockData = { hello: 'im a great new value' }; + const serverCall = createDeferred(); + server.use( rest.put( `${mockBaseUrl}/buckets/:bucket/keys/:key`, @@ -188,6 +185,10 @@ describe('Persistent Storage API', () => { return res(ctx.json(data)); }, ), + rest.post(`${mockBaseUrl}/multiget`, async (_req, res, ctx) => { + serverCall.resolve(); + return res(ctx.json([])); + }), ); await new Promise(resolve => { @@ -212,14 +213,18 @@ describe('Persistent Storage API', () => { presence: 'present', value: mockData, }); + + await serverCall; }); it('should subscribe to key changes when deleting a value', async () => { - const storage = createPersistentStorage(); + const storage = createPersistentStorage({ namespace: 'key-change-delete' }); const wrongKeyNextHandler = jest.fn(); const selectedKeyNextHandler = jest.fn(); + const serverCall = createDeferred(); + server.use( rest.delete( `${mockBaseUrl}/buckets/:bucket/keys/:key`, @@ -227,6 +232,10 @@ describe('Persistent Storage API', () => { return res(ctx.status(204)); }, ), + rest.post(`${mockBaseUrl}/multiget`, async (_req, res, ctx) => { + serverCall.resolve(); + return res(ctx.json([])); + }), ); await new Promise(resolve => { @@ -251,10 +260,12 @@ describe('Persistent Storage API', () => { presence: 'absent', value: undefined, }); + + await serverCall; }); it('should not clash with other namespaces when creating buckets', async () => { - const rootStorage = createPersistentStorage(); + const rootStorage = createPersistentStorage({ namespace: 'clash' }); const selectedKeyNextHandler = jest.fn(); server.use( @@ -264,23 +275,21 @@ describe('Persistent Storage API', () => { const { bucket, key } = req.params; const { value } = await req.json(); - expect(bucket).toEqual('default.profile.something.deep'); + expect(bucket).toEqual('clash.profile.something.deep'); expect(key).toEqual('test2'); return res(ctx.json({ value })); }, ), - rest.get( - `${mockBaseUrl}/buckets/:bucket/keys/:key`, - async (req, res, ctx) => { - const { bucket, key } = req.params; + rest.post(`${mockBaseUrl}/multiget`, async (req, res, ctx) => { + const payload = await req.json(); + const { bucket, key } = payload.items[0]; - expect(bucket).toEqual('default.profile/something'); - expect(key).toEqual('deep/test2'); + expect(bucket).toEqual('clash.profile/something'); + expect(key).toEqual('deep/test2'); - return res(ctx.status(404)); - }, - ), + return res(ctx.status(404)); + }), ); // when getting key test2 it will translate to default.profile.something.deep/test2 @@ -321,15 +330,13 @@ describe('Persistent Storage API', () => { }); server.use( - rest.get( - `${mockBaseUrl}/buckets/:bucket/keys/:key`, - async (req, res, ctx) => { - const { bucket, key } = req.params; - expect(bucket).toEqual('Test.Mock.Thing'); - expect(key).toEqual('key'); - return res(ctx.text('{ invalid: json string }')); - }, - ), + rest.post(`${mockBaseUrl}/multiget`, async (req, res, ctx) => { + const payload = await req.json(); + expect(payload).toEqual({ + items: [{ bucket: 'Test.Mock.Thing', key: 'key' }], + }); + return res(ctx.text('{ invalid: json string }')); + }), ); await new Promise(resolve => { @@ -353,17 +360,14 @@ describe('Persistent Storage API', () => { }); it('should freeze the snapshot value', async () => { - const storage = createPersistentStorage(); + const storage = createPersistentStorage({ namespace: 'freeze' }); const selectedKeyNextHandler = jest.fn(); const data = { foo: 'bar', baz: [{ foo: 'bar' }] }; server.use( - rest.get( - `${mockBaseUrl}/buckets/:bucket/keys/:key`, - async (_req, res, ctx) => { - return res(ctx.json({ value: data })); - }, - ), + rest.post(`${mockBaseUrl}/multiget`, async (_req, res, ctx) => { + return res(ctx.json({ items: [{ value: data }] })); + }), ); await new Promise(resolve => { @@ -396,4 +400,112 @@ describe('Persistent Storage API', () => { snapshot.value.baz.push({ foo: 'buzz' }); }).toThrow(/Cannot add property 1, object is not extensible/); }); + + it('should batch multiple calls into one', async () => { + const storage = createPersistentStorage({ namespace: 'multiget' }); + const selectedKeyNextHandler = jest.fn(); + const selectedKeyNextHandlerCached = jest.fn(); + const data1 = { foo: 'bar1', baz: [{ foo: 'bar1' }] }; + const data2 = { foo: 'bar2', baz: [{ foo: 'bar2' }] }; + + let serverCalls = 0; + + server.use( + rest.post(`${mockBaseUrl}/multiget`, async (req, res, ctx) => { + ++serverCalls; + const payload = (await req.json()) as { + items: { bucket: string; key: string }[]; + }; + const result = payload.items.map(item => { + if (item.key === 'key1') { + return { value: data1 }; + } else if (item.key === 'key2') { + return { value: data2 }; + } + return null; + }); + + return res(ctx.json({ items: result })); + }), + ); + + await Promise.all([ + new Promise(resolve => { + storage.observe$('key1').subscribe({ + next: snapshot => { + selectedKeyNextHandler(snapshot); + if (snapshot.presence === 'present') { + resolve(); + } + }, + }); + + storage.snapshot('key1'); + }), + + new Promise(resolve => { + storage.observe$('missing-key').subscribe({ + next: snapshot => { + selectedKeyNextHandler(snapshot); + if (snapshot.presence === 'absent') { + resolve(); + } + }, + }); + + storage.snapshot('missing-key'); + }), + + new Promise(resolve => { + storage.observe$('key2').subscribe({ + next: snapshot => { + selectedKeyNextHandler(snapshot); + if (snapshot.presence === 'present') { + resolve(); + } + }, + }); + + storage.snapshot('key2'); + }), + ]); + + expect(selectedKeyNextHandler).toHaveBeenCalledWith({ + key: 'key1', + presence: 'present', + value: data1, + }); + expect(selectedKeyNextHandler).toHaveBeenCalledWith({ + key: 'key2', + presence: 'present', + value: data2, + }); + expect(selectedKeyNextHandler).toHaveBeenCalledWith({ + key: 'missing-key', + presence: 'absent', + }); + expect(mockErrorApi.post).not.toHaveBeenCalled(); + expect(serverCalls).toBe(1); + + // Get key1 again, should use cached value + await new Promise(resolve => { + storage.observe$('key1').subscribe({ + next: snapshot => { + selectedKeyNextHandlerCached(snapshot); + if (snapshot.presence === 'present') { + resolve(); + } + }, + }); + + storage.snapshot('key1'); + }); + + expect(selectedKeyNextHandlerCached).toHaveBeenCalledWith({ + key: 'key1', + presence: 'present', + value: data1, + }); + expect(serverCalls).toBe(1); + }); }); diff --git a/plugins/user-settings/src/apis/StorageApi/UserSettingsStorage.ts b/plugins/user-settings/src/apis/StorageApi/UserSettingsStorage.ts index aa3080345c..9a5a0f22ed 100644 --- a/plugins/user-settings/src/apis/StorageApi/UserSettingsStorage.ts +++ b/plugins/user-settings/src/apis/StorageApi/UserSettingsStorage.ts @@ -27,7 +27,12 @@ import { ResponseError } from '@backstage/errors'; import { JsonValue, Observable } from '@backstage/types'; import { SignalApi, SignalSubscriber } from '@backstage/plugin-signals-react'; import ObservableImpl from 'zen-observable'; -import { UserSettingsSignal } from '@backstage/plugin-user-settings-common'; +import { + MultiGetResponse, + UserSettingsSignal, +} from '@backstage/plugin-user-settings-common'; +import DataLoader from 'dataloader'; +import { CacheMap } from './CacheMap'; const JSON_HEADERS = { 'Content-Type': 'application/json; charset=utf-8', @@ -36,6 +41,15 @@ const JSON_HEADERS = { const buckets = new Map(); +const DATALOADER_CACHE_TTL_MS = 2 * 1000; // 2 seconds cache +const DATALOADER_WINDOW_MS = 10; // 10 ms + +type DataLoaderType = DataLoader< + { bucket: string; key: string }, + StorageValueSnapshot, + string +>; + /** * An implementation of the storage API, that uses the user-settings backend to * persist the data in the DB. @@ -59,6 +73,7 @@ export class UserSettingsStorage implements StorageApi { private readonly identityApi: IdentityApi; private readonly fallback: WebStorage; private readonly signalApi?: SignalApi; + private readonly userSettingsLoader: DataLoaderType; private constructor( namespace: string, @@ -68,6 +83,7 @@ export class UserSettingsStorage implements StorageApi { identityApi: IdentityApi, fallback: WebStorage, signalApi?: SignalApi, + userSettingsLoader?: DataLoaderType, ) { this.namespace = namespace; this.fetchApi = fetchApi; @@ -76,6 +92,35 @@ export class UserSettingsStorage implements StorageApi { this.identityApi = identityApi; this.fallback = fallback; this.signalApi = signalApi; + + this.userSettingsLoader = + userSettingsLoader ?? + new DataLoader( + async bucketAndKeyList => this.getMulti(bucketAndKeyList), + { + name: 'UserSettingsStorage.userSettingsLoader', + cacheMap: new CacheMap< + string, + Promise> + >(DATALOADER_CACHE_TTL_MS), + cacheKeyFn: bucketAndKey => this.stringifyDataLoaderKey(bucketAndKey), + maxBatchSize: 100, + batchScheduleFn: cb => setTimeout(cb, DATALOADER_WINDOW_MS), + }, + ); + } + + private stringifyDataLoaderKey({ + bucket, + key, + }: { + bucket: string; + key: string; + }) { + return `${encodeURIComponent(bucket)}/${encodeURIComponent(key)}`; + } + private clearCacheKey(key: string) { + this.userSettingsLoader.clear({ bucket: this.namespace, key }); } static create(options: { @@ -114,6 +159,8 @@ export class UserSettingsStorage implements StorageApi { this.errorApi, this.identityApi, this.fallback, + this.signalApi, + this.userSettingsLoader, ), ); } @@ -132,6 +179,8 @@ export class UserSettingsStorage implements StorageApi { throw await ResponseError.fromResponse(response); } + this.clearCacheKey(key); + this.notifyChanges({ key, presence: 'absent' }); } @@ -139,9 +188,12 @@ export class UserSettingsStorage implements StorageApi { if (!(await this.isSignedIn())) { await this.fallback.set(key, data); this.notifyChanges({ key, presence: 'present', value: data }); + this.clearCacheKey(key); return; } + this.clearCacheKey(key); + const fetchUrl = await this.getFetchUrl(key); const response = await this.fetchApi.fetch(fetchUrl, { @@ -156,6 +208,15 @@ export class UserSettingsStorage implements StorageApi { const { value } = await response.json(); + this.userSettingsLoader.prime( + { bucket: this.namespace, key }, + { + key, + presence: 'present', + value, + }, + ); + this.notifyChanges({ key, value, presence: 'present' }); } @@ -171,7 +232,9 @@ export class UserSettingsStorage implements StorageApi { const updateSnapshot = () => { Promise.resolve() - .then(() => this.get(key)) + .then(() => + this.userSettingsLoader.load({ bucket: this.namespace, key }), + ) .then(snapshot => subscriber.next(snapshot)) .catch(error => this.errorApi.post(error)); }; @@ -206,38 +269,62 @@ export class UserSettingsStorage implements StorageApi { return { key, presence: 'unknown' }; } - private async get( - key: string, - ): Promise> { + private async getMulti( + bucketAndKeyList: readonly { bucket: string; key: string }[], + ): Promise[]> { + if (bucketAndKeyList.length === 0) return []; + if (!(await this.isSignedIn())) { // This explicitly uses WebStorage, which we know is synchronous and doesn't return presence: unknown - return this.fallback.snapshot(key); - } - - const fetchUrl = await this.getFetchUrl(key); - const response = await this.fetchApi.fetch(fetchUrl); - - if (response.status === 404) { - return { key, presence: 'absent' }; - } - - if (!response.ok) { - throw await ResponseError.fromResponse(response); + return bucketAndKeyList.map(bucketAndKey => + this.fallback.snapshot(bucketAndKey.key), + ); } try { - const { value: rawValue } = await response.json(); - const value = JSON.parse(JSON.stringify(rawValue), (_key, val) => { - if (typeof val === 'object' && val !== null) { - Object.freeze(val); - } - return val; + const baseUrl = await this.discoveryApi.getBaseUrl('user-settings'); + const response = await this.fetchApi.fetch(`${baseUrl}/multiget`, { + method: 'POST', + headers: JSON_HEADERS, + body: JSON.stringify({ items: bucketAndKeyList }), }); - return { key, presence: 'present', value }; - } catch { - // If the value is not valid JSON, we return an unknown presence. This should never happen - return { key, presence: 'absent' }; + if (response.status === 404) { + return bucketAndKeyList.map(bucketAndKey => ({ + key: bucketAndKey.key, + presence: 'absent', + })); + } + + if (!response.ok) { + throw await ResponseError.fromResponse(response); + } + + const { items: values } = (await response.json()) as MultiGetResponse; + + return bucketAndKeyList.map( + ({ key }, i): StorageValueSnapshot => { + if (!values[i]) { + return { key, presence: 'absent' }; + } + return { + key, + presence: 'present', + value: JSON.parse(JSON.stringify(values[i].value), (_key, val) => { + if (typeof val === 'object' && val !== null) { + Object.freeze(val); + } + return val; + }), + }; + }, + ); + } catch (e) { + this.errorApi.post(new Error(`Failed to fetch user settings, ${e}`)); + return bucketAndKeyList.map(bucketAndKey => ({ + key: bucketAndKey.key, + presence: 'absent', + })); } } diff --git a/plugins/user-settings/src/components/AuthProviders/ProviderSettingsItem.tsx b/plugins/user-settings/src/components/AuthProviders/ProviderSettingsItem.tsx index 32eaeb0cb9..534e1dc5ad 100644 --- a/plugins/user-settings/src/components/AuthProviders/ProviderSettingsItem.tsx +++ b/plugins/user-settings/src/components/AuthProviders/ProviderSettingsItem.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { useEffect, useState } from 'react'; +import { createElement, isValidElement, useEffect, useState } from 'react'; import Button from '@material-ui/core/Button'; import Grid from '@material-ui/core/Grid'; import ListItem from '@material-ui/core/ListItem'; @@ -33,6 +33,7 @@ import { errorApiRef, IconComponent, } from '@backstage/core-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; import { ProviderSettingsAvatar } from './ProviderSettingsAvatar'; import { useTranslationRef } from '@backstage/frontend-plugin-api'; import { userSettingsTranslationRef } from '../../translation'; @@ -43,10 +44,10 @@ const emptyProfile: ProfileInfo = {}; export const ProviderSettingsItem = (props: { title: string; description: string; - icon: IconComponent; + icon: IconComponent | IconElement; apiRef: ApiRef; }) => { - const { title, description, icon: Icon, apiRef } = props; + const { title, description, icon, apiRef } = props; const api = useApi(apiRef); const errorApi = useApi(errorApiRef); @@ -86,11 +87,14 @@ export const ProviderSettingsItem = (props: { }; }, [api]); + const iconElement = + icon === null || isValidElement(icon) + ? icon + : createElement(icon as IconComponent); + return ( - - - + {iconElement} f.trim()); + + const mdFiles = prFiles + .filter(f => f.endsWith('.md')) + .filter(f => !IGNORED_WHEN_LISTING.some(p => p.test(f))); + + if (mdFiles.length === 0) { + console.log('No documentation files to check.'); + return; + } + + console.log(`Checking ${mdFiles.length} changed documentation file(s)...`); + + const result = spawnSync( + 'vale', + [ + '--config', + resolvePath(rootDir, '.vale.ini'), + '--output=JSON', + ...mdFiles, + ], + { encoding: 'utf8', maxBuffer: 50 * 1024 * 1024 }, + ); + + if (result.error) { + console.error('Failed to run vale:', result.error.message); + process.exit(1); + } + + let issueCount = 0; + + if (result.stdout && result.stdout.trim()) { + try { + const data = JSON.parse(result.stdout); + for (const [file, alerts] of Object.entries(data)) { + for (const alert of alerts) { + const severityLevels = { + error: 'error', + warning: 'warning', + suggestion: 'notice', + }; + const level = severityLevels[alert.Severity] ?? 'notice'; + const col = alert.Span ? alert.Span[0] : 1; + const endCol = alert.Span ? `,endColumn=${alert.Span[1] + 1}` : ''; + console.log( + `::${level} file=${file},line=${alert.Line},col=${col}${endCol},title=${alert.Check}::${alert.Message}`, + ); + issueCount++; + } + } + } catch { + console.error('Failed to parse vale output:'); + console.error(result.stdout); + process.exit(1); + } + } + + if (result.stderr && result.stderr.trim()) { + console.error(result.stderr); + } + + if (issueCount > 0) { + console.log( + `\nFound ${issueCount} documentation quality issue(s). Please review the annotations above.`, + ); + } + + if (result.status !== 0) { + process.exit(1); + } +} + async function main() { + if (process.argv.includes('--ci')) { + const idx = process.argv.indexOf('--ci'); + const prFilesPath = process.argv[idx + 1]; + if (!prFilesPath) { + console.error('Usage: check-docs-quality.js --ci '); + process.exit(1); + } + await ciCheck(prFilesPath); + return; + } + if (process.argv.includes('--ci-args')) { const files = await listFiles(); diff --git a/scripts/check-if-release.js b/scripts/check-if-release.js index c12b27b0ef..c27d55708a 100755 --- a/scripts/check-if-release.js +++ b/scripts/check-if-release.js @@ -24,11 +24,11 @@ // // needs_release = 'true' | 'false' -const { execFile: execFileCb } = require('child_process'); -const { resolve: resolvePath } = require('path'); -const { promises: fs } = require('fs'); -const { promisify } = require('util'); -const { EOL } = require('os'); +const { execFile: execFileCb } = require('node:child_process'); +const { resolve: resolvePath } = require('node:path'); +const { promises: fs } = require('node:fs'); +const { promisify } = require('node:util'); +const { EOL } = require('node:os'); const parentRef = process.env.COMMIT_SHA_BEFORE || 'HEAD^'; diff --git a/scripts/create-release-tag.js b/scripts/create-release-tag.js index a65018e1c0..31bb03962f 100755 --- a/scripts/create-release-tag.js +++ b/scripts/create-release-tag.js @@ -17,9 +17,9 @@ */ const { Octokit } = require('@octokit/rest'); -const path = require('path'); +const path = require('node:path'); const fs = require('fs-extra'); -const { EOL } = require('os'); +const { EOL } = require('node:os'); const baseOptions = { owner: 'backstage', diff --git a/scripts/generate-merge-message.js b/scripts/generate-merge-message.js index 7592115e47..6be9a2d457 100755 --- a/scripts/generate-merge-message.js +++ b/scripts/generate-merge-message.js @@ -15,9 +15,9 @@ * limitations under the License. */ -const { execFile: execFileCb } = require('child_process'); -const { promisify } = require('util'); -const { resolve: resolvePath } = require('path'); +const { execFile: execFileCb } = require('node:child_process'); +const { promisify } = require('node:util'); +const { resolve: resolvePath } = require('node:path'); const execFile = promisify(execFileCb); diff --git a/scripts/list-backend-feature.js b/scripts/list-backend-feature.js index 91bce6852b..8d3d69cccd 100644 --- a/scripts/list-backend-feature.js +++ b/scripts/list-backend-feature.js @@ -17,7 +17,7 @@ const fs = require('fs-extra'); const { getPackages } = require('@manypkg/get-packages'); -const { resolve, join } = require('path'); +const { resolve, join } = require('node:path'); const arrayToTable = require('array-to-table'); async function main(args) { diff --git a/scripts/list-deprecations.js b/scripts/list-deprecations.js index 22aff96d67..1e8cbf87b3 100755 --- a/scripts/list-deprecations.js +++ b/scripts/list-deprecations.js @@ -20,9 +20,9 @@ const _ = require('lodash'); const fs = require('fs-extra'); const globby = require('globby'); -const { resolve: resolvePath, relative: relativePath } = require('path'); -const { execFile: execFileCb } = require('child_process'); -const { promisify } = require('util'); +const { resolve: resolvePath, relative: relativePath } = require('node:path'); +const { execFile: execFileCb } = require('node:child_process'); +const { promisify } = require('node:util'); const execFile = promisify(execFileCb); diff --git a/scripts/list-no-top-level-material-ui-4-imports.js b/scripts/list-no-top-level-material-ui-4-imports.js index a963b0c0b2..2f46651cef 100644 --- a/scripts/list-no-top-level-material-ui-4-imports.js +++ b/scripts/list-no-top-level-material-ui-4-imports.js @@ -17,7 +17,7 @@ const fs = require('fs-extra'); const { getPackages } = require('@manypkg/get-packages'); -const { resolve, join } = require('path'); +const { resolve, join } = require('node:path'); async function main() { const rootPath = resolve(__dirname, '..'); diff --git a/scripts/list-ownership.js b/scripts/list-ownership.js index 7f338ceed4..749304f775 100755 --- a/scripts/list-ownership.js +++ b/scripts/list-ownership.js @@ -19,7 +19,7 @@ const fs = require('fs-extra'); const globby = require('globby'); const sloc = require('sloc'); const codeownersUtils = require('codeowners-utils'); -const { resolve: resolvePath } = require('path'); +const { resolve: resolvePath } = require('node:path'); async function loadOwners(rootDir) { const codeowners = await codeownersUtils.loadOwners(rootDir); diff --git a/scripts/mui-to-bui/backstage-migration-analytics.js b/scripts/mui-to-bui/backstage-migration-analytics.js index 226c12559c..23f419f35b 100755 --- a/scripts/mui-to-bui/backstage-migration-analytics.js +++ b/scripts/mui-to-bui/backstage-migration-analytics.js @@ -27,8 +27,8 @@ * - Compares migration progress between OSS and Portal */ -const fs = require('fs'); -const path = require('path'); +const fs = require('node:fs'); +const path = require('node:path'); const { Project } = require('ts-morph'); // Configuration diff --git a/scripts/patch-release-for-pr.js b/scripts/patch-release-for-pr.js index d70eaa8ac5..71d9eac091 100755 --- a/scripts/patch-release-for-pr.js +++ b/scripts/patch-release-for-pr.js @@ -16,17 +16,18 @@ */ const fs = require('fs-extra'); -const path = require('path'); +const path = require('node:path'); const semver = require('semver'); const { Octokit } = require('@octokit/rest'); -const { execFile: execFileCb } = require('child_process'); -const { promisify } = require('util'); +const { execFile: execFileCb } = require('node:child_process'); +const { promisify, parseArgs } = require('node:util'); const execFile = promisify(execFileCb); const owner = 'backstage'; const repo = 'backstage'; const rootDir = path.resolve(__dirname, '..'); +const PATCH_FILE_PATTERN = /^pr-(\d+)\.txt$/; const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN, @@ -81,15 +82,120 @@ async function findCurrentReleaseVersion() { throw new Error('No stable release found'); } -async function main(args) { - const prNumbers = args.map(s => { - const num = parseInt(s, 10); - if (!Number.isInteger(num)) { - throw new Error(`Must provide valid PR number arguments, got ${s}`); - } - return num; +/** + * Parses command-line arguments and determines which patches to use. + * Returns an object with prNumbers and descriptions, or null if should exit early. + */ +async function parsePatchArguments(args) { + const { values, positionals } = parseArgs({ + args, + options: { + 'from-dir': { + type: 'boolean', + short: 'd', + }, + }, + allowPositionals: true, }); - console.log(`PR number(s): ${prNumbers.join(', ')}`); + + const useDirectory = values['from-dir'] === true; + + let prNumbers; + let descriptions; + + if (useDirectory) { + // Read patches from directory + const patchesFromDir = await readPatchesFromDirectory(); + + if (patchesFromDir) { + // Use patches from directory + prNumbers = patchesFromDir.map(p => p.prNumber); + descriptions = patchesFromDir.map(p => p.description); + console.log(`Reading patches from .patches/ directory`); + console.log(`PR number(s): ${prNumbers.join(', ')}`); + return { prNumbers, descriptions }; + } + // No patches found in directory - exit early + return null; + } else if (positionals.length > 0) { + // Use command-line arguments + prNumbers = positionals.map(s => { + const num = parseInt(s, 10); + if (!Number.isInteger(num)) { + throw new Error(`Must provide valid PR number arguments, got ${s}`); + } + return num; + }); + console.log(`Using command-line arguments`); + console.log(`PR number(s): ${prNumbers.join(', ')}`); + return { prNumbers, descriptions: undefined }; + } + // No patches found and no command-line args - exit early + return null; +} + +/** + * Reads patch files from .patches/ directory and returns PR numbers with descriptions. + * Returns null if directory doesn't exist or contains no matching files. + */ +async function readPatchesFromDirectory() { + const patchesDir = path.resolve(rootDir, '.patches'); + + try { + const exists = await fs.pathExists(patchesDir); + if (!exists) { + return null; + } + + const files = await fs.readdir(patchesDir); + const patchFiles = files.filter(file => PATCH_FILE_PATTERN.test(file)); + + if (patchFiles.length === 0) { + return null; + } + + const patches = []; + for (const file of patchFiles) { + const match = file.match(PATCH_FILE_PATTERN); + const prNumber = parseInt(match[1], 10); + const filePath = path.resolve(patchesDir, file); + const content = await fs.readFile(filePath, 'utf8'); + const description = content.trim() || ''; + patches.push({ prNumber, description }); + } + + // Sort by PR number for consistent ordering + patches.sort((a, b) => a.prNumber - b.prNumber); + + return patches; + } catch { + // If there's an error reading the directory, return null to fall back to CLI args + return null; + } +} + +async function ensureCommitExists(sha) { + try { + await run('git', 'cat-file', '-e', sha); + } catch { + console.log(`Commit ${sha} not found locally, fetching...`); + + try { + await run('git', 'fetch', 'origin', sha); + } catch (error) { + throw new Error(`Failed to fetch commit ${sha}: ${error}`); + } + } +} + +async function main(args) { + const patchInfo = await parsePatchArguments(args); + if (!patchInfo) { + // No patches found and no command-line args - exit early + return; + } + + const { prNumbers, descriptions } = patchInfo; if (await run('git', 'status', '--porcelain')) { throw new Error('Cannot run with a dirty working tree'); @@ -101,6 +207,16 @@ async function main(args) { await run('git', 'fetch'); const patchBranch = `patch/v${release}`; + + // Output patch branch for CI workflows to capture + if (process.env.PATCH_RELEASE_BRANCH && process.env.GITHUB_OUTPUT) { + // Use native fs for appendFileSync (fs-extra doesn't have sync version) + const nativeFs = require('node:fs'); + nativeFs.appendFileSync( + process.env.GITHUB_OUTPUT, + `patch_branch=${patchBranch}\n`, + ); + } try { await run('git', 'checkout', `origin/${patchBranch}`); } catch { @@ -109,10 +225,30 @@ async function main(args) { } // Create new branch, apply changes from all commits on PR branch, commit, push - const branchName = `patch-release-pr-${prNumbers.join('-')}`; - await run('git', 'checkout', '-b', branchName); + // Allow fixed branch name via environment variable for CI workflows + const branchName = + process.env.PATCH_RELEASE_BRANCH || + `patch-release-pr-${prNumbers.join('-')}`; + + // Check if branch already exists (for CI workflows using fixed branch name) + try { + await run( + 'git', + 'show-ref', + '--verify', + '--quiet', + `refs/heads/${branchName}`, + ); + await run('git', 'checkout', branchName); + } catch { + await run('git', 'checkout', '-b', branchName); + } + + const appliedPrNumbers = []; for (const prNumber of prNumbers) { + console.log(`Processing PR #${prNumber}...`); + const { data } = await octokit.pulls.get({ owner, repo, @@ -127,6 +263,9 @@ async function main(args) { if (!baseSha) { throw new Error('base sha not available'); } + + await ensureCommitExists(headSha); + const mergeBaseSha = await run('git', 'merge-base', headSha, baseSha); const logLines = await run( @@ -136,25 +275,85 @@ async function main(args) { '--reverse', '--pretty=%H', ); - for (const logSha of logLines.split(/\r?\n/)) { - await run('git', 'cherry-pick', '-n', logSha); + + const commitMessage = `Patch from PR #${prNumber}`; + + const commitMessagePattern = `^${commitMessage}$`; + + // Check if this patch has already been applied by looking for the commit message + try { + const existingCommit = await run( + 'git', + 'log', + '--extended-regexp', + '--grep', + commitMessagePattern, + '--format=%H', + '-1', + ); + + if (existingCommit) { + console.log( + `Patch from PR #${prNumber} has already been applied, skipping...`, + ); + continue; + } + } catch { + // No existing commit found, proceed with cherry-pick } - await run( - 'git', - 'commit', - '--signoff', - '--no-verify', - '-m', - `Patch from PR #${prNumber}`, - ); + + let hasChanges = false; + const commitShas = logLines.split(/\r?\n/).filter(Boolean); + for (const logSha of commitShas) { + try { + await run('git', 'cherry-pick', '-n', logSha); + hasChanges = true; + } catch (error) { + // Check if the cherry-pick failed because changes are already applied + const status = await run('git', 'status', '--porcelain'); + if (!status) { + console.log( + `Commit ${logSha} appears to be already applied, skipping...`, + ); + continue; + } + throw error; + } + } + + if (!hasChanges) { + console.log( + `All commits from PR #${prNumber} are already applied, skipping...`, + ); + continue; + } + + await run('git', 'commit', '--signoff', '--no-verify', '-m', commitMessage); + + appliedPrNumbers.push(prNumber); } + if (appliedPrNumbers.length === 0) { + console.log('All patches have already been applied, nothing to do.'); + return; + } + + console.log( + `Applied ${appliedPrNumbers.length} patch(es): ${appliedPrNumbers.join( + ', ', + )}`, + ); + console.log('Running "yarn install" ...'); await run('yarn', 'install'); console.log('Running "yarn release" ...'); await run('yarn', 'release'); + // Note: Patch files are not deleted here because this script runs in the patch + // release branch, not master. The cleanup_patch-files.yml workflow handles + // deletion from master after the patch release PR is merged. + await run('git', 'add', '.'); await run( 'git', @@ -165,18 +364,48 @@ async function main(args) { 'Generate Release', ); - await run('git', 'push', 'origin', '-u', branchName); + // Use force push if using a specific branch name (for CI workflows) + if (process.env.PATCH_RELEASE_BRANCH) { + await run('git', 'push', 'origin', '-u', '--force-with-lease', branchName); + } else { + await run('git', 'push', 'origin', '-u', branchName); + } + + // Generate PR body using only applied patches + let body; + if (descriptions) { + // Filter descriptions to only include applied patches + const appliedDescriptions = descriptions.filter((_, index) => + appliedPrNumbers.includes(prNumbers[index]), + ); + + const descriptionList = appliedDescriptions + .map((desc, index) => { + const prNumber = appliedPrNumbers[index]; + const prLink = `https://github.com/${owner}/${repo}/pull/${prNumber}`; + return `- ${desc} ([#${prNumber}](${prLink}))`; + }) + .join('\n'); + body = `This patch release includes the following fixes:\n\n${descriptionList}`; + } else { + body = 'This release fixes an issue where'; + } const params = new URLSearchParams({ expand: 1, - body: 'This release fixes an issue where', - title: `Patch release of ${prNumbers.map(nr => `#${nr}`).join(', ')}`, + body: body, + title: `Patch release of ${appliedPrNumbers + .map(nr => `#${nr}`) + .join(', ')}`, }); const url = `https://github.com/backstage/backstage/compare/${patchBranch}...${branchName}?${params}`; - console.log(`Opening ${url} ...`); + console.log(`PR URL: ${url}`); - await run('open', url); + // Only open URL if not in CI (no PATCH_RELEASE_BRANCH env var means manual run) + if (!process.env.PATCH_RELEASE_BRANCH) { + await run('open', url); + } } main(process.argv.slice(2)).catch(error => { diff --git a/scripts/pin-workspace-versions.js b/scripts/pin-workspace-versions.js index cbfb0c720b..c69df1ffd5 100755 --- a/scripts/pin-workspace-versions.js +++ b/scripts/pin-workspace-versions.js @@ -23,7 +23,7 @@ const fs = require('fs-extra'); const { getPackages } = require('@manypkg/get-packages'); -const { resolve } = require('path'); +const { resolve } = require('node:path'); const depTypes = ['dependencies', 'devDependencies', 'peerDependencies']; diff --git a/scripts/plugin-directory-audit.js b/scripts/plugin-directory-audit.js new file mode 100644 index 0000000000..c96b82354c --- /dev/null +++ b/scripts/plugin-directory-audit.js @@ -0,0 +1,147 @@ +#!/usr/bin/env node +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* eslint-disable @backstage/no-undeclared-imports */ + +// This script is used to audit the list of plugins in the Plugin Directory: https://backstage.io/plugins + +const { resolve } = require('node:path'); +const fs = require('fs-extra'); +const yaml = require('js-yaml'); + +async function getNpmPackage(npmPackageName) { + const response = await fetch(`https://registry.npmjs.com/${npmPackageName}`); + const json = await response.json(); + return json; +} + +function getAge(npmModified) { + const ageDif = Date.now() - new Date(npmModified).getTime(); + return Math.round(ageDif / (1000 * 60 * 60 * 24)); +} + +async function main(args) { + const rootPath = resolve(__dirname, '..'); + const pluginDataPath = resolve(rootPath, 'microsite/data/plugins'); + + const auditMode = args.includes('--audit'); + + const pluginDataFiles = fs.readdirSync(pluginDataPath); + + const pluginsData = []; + const updatedPlugins = []; + + for (const pluginDataFile of pluginDataFiles) { + const pluginDataFilePath = resolve(pluginDataPath, pluginDataFile); + const pluginDataYaml = yaml.load( + fs.readFileSync(pluginDataFilePath, { encoding: 'utf-8' }), + ); + + console.log( + `Auditing - ${pluginDataYaml.title} by ${pluginDataYaml.author} - ${pluginDataYaml.npmPackageName}`, + ); + + const npmPackage = await getNpmPackage(pluginDataYaml.npmPackageName); + const modifiedTime = npmPackage.time?.modified; + const age = getAge(npmPackage.time?.modified); + + if (!modifiedTime || isNaN(age)) { + console.warn( + `Skipping ${pluginDataYaml.title}: Could not calculate age (Data: ${modifiedTime})`, + ); + continue; // Skip to the next plugin in the loop + } + + const pluginData = { + npmPackageName: pluginDataYaml.npmPackageName, + npmCreated: npmPackage.time?.created, + npmModified: npmPackage.time?.modified, + age: age, + currentStatus: pluginDataYaml.status, + }; + + // Update plugin YAML if in audit mode + if (auditMode) { + let newStatus = pluginDataYaml.status; + let statusChanged = false; + + // If age < 365 and status is inactive or archived, change to active + if ( + age < 365 && + (pluginDataYaml.status === 'inactive' || + pluginDataYaml.status === 'archived') + ) { + newStatus = 'active'; + statusChanged = true; + } + // If status is inactive, change to archived + else if (age > 365 && pluginDataYaml.status === 'inactive') { + newStatus = 'archived'; + statusChanged = true; + } + // If age > 365 and status is active, change to inactive + else if (age > 365 && pluginDataYaml.status === 'active') { + newStatus = 'inactive'; + statusChanged = true; + } + + if (statusChanged) { + pluginDataYaml.status = newStatus; + pluginDataYaml.staleSince = new Date().toISOString().split('T')[0]; + + // Write updated YAML back to file + const yamlContent = yaml.dump(pluginDataYaml, { + lineWidth: -1, + quotingType: "'", + forceQuotes: false, + }); + fs.writeFileSync(pluginDataFilePath, `---\n${yamlContent}`); + + updatedPlugins.push({ + file: pluginDataFile, + plugin: pluginDataYaml.title, + oldStatus: pluginData.currentStatus, + newStatus: newStatus, + age: age, + }); + + console.log( + ` ✓ Updated ${pluginDataFile}: ${pluginData.currentStatus} → ${newStatus} (age: ${age} days)`, + ); + } + + pluginData.newStatus = newStatus; + } + + pluginsData.push(pluginData); + } + + console.table(pluginsData); + + if (auditMode && updatedPlugins.length > 0) { + console.log('\n=== Summary of Updates ==='); + console.table(updatedPlugins); + console.log(`\nTotal plugins updated: ${updatedPlugins.length}`); + } else if (auditMode) { + console.log('\nNo plugins required updates.'); + } +} + +main(process.argv.slice(2)).catch(error => { + console.error(error.stack || error); + process.exit(1); +}); diff --git a/scripts/prepare-release.js b/scripts/prepare-release.js index 4019a71cf3..bf34f17fd8 100755 --- a/scripts/prepare-release.js +++ b/scripts/prepare-release.js @@ -19,9 +19,9 @@ const fs = require('fs-extra'); const semver = require('semver'); const { getPackages } = require('@manypkg/get-packages'); -const path = require('path'); -const { execFile: execFileCb } = require('child_process'); -const { promisify } = require('util'); +const path = require('node:path'); +const { execFile: execFileCb } = require('node:child_process'); +const { promisify } = require('node:util'); const { default: parseChangeset } = require('@changesets/parse'); const execFile = promisify(execFileCb); diff --git a/scripts/run-fossa.js b/scripts/run-fossa.js index d7df74c89a..bde1678a75 100755 --- a/scripts/run-fossa.js +++ b/scripts/run-fossa.js @@ -18,10 +18,10 @@ // This script generates an appropriate fossa config, and wraps the running // of `fossa analyze` in a retry loop as it frequently fails with a 502 error -const { resolve: resolvePath, join: joinPath, basename } = require('path'); -const { promises: fs } = require('fs'); -const { execFile: execFileCb } = require('child_process'); -const { promisify } = require('util'); +const { resolve: resolvePath, join: joinPath, basename } = require('node:path'); +const { promises: fs } = require('node:fs'); +const { execFile: execFileCb } = require('node:child_process'); +const { promisify } = require('node:util'); const execFile = promisify(execFileCb); diff --git a/scripts/techdocs-cli.js b/scripts/techdocs-cli.js index 90dcc40f39..6191bd1c6e 100644 --- a/scripts/techdocs-cli.js +++ b/scripts/techdocs-cli.js @@ -15,7 +15,7 @@ * limitations under the License. */ -const { execSync } = require('child_process'); +const { execSync } = require('node:child_process'); const args = process.argv.slice(2); diff --git a/scripts/verify-api-reference.js b/scripts/verify-api-reference.js index 091e803c33..1b6db7b57c 100755 --- a/scripts/verify-api-reference.js +++ b/scripts/verify-api-reference.js @@ -17,8 +17,8 @@ /* eslint-disable @backstage/no-undeclared-imports */ -const { resolve: resolvePath } = require('path'); -const { promises: fs } = require('fs'); +const { resolve: resolvePath } = require('node:path'); +const { promises: fs } = require('node:fs'); async function main() { const indexContent = await fs.readFile( diff --git a/scripts/verify-changesets.js b/scripts/verify-changesets.js index ae3f07840a..09578af70d 100755 --- a/scripts/verify-changesets.js +++ b/scripts/verify-changesets.js @@ -17,7 +17,7 @@ /* eslint-disable @backstage/no-undeclared-imports */ -const { resolve: resolvePath } = require('path'); +const { resolve: resolvePath } = require('node:path'); const fs = require('fs-extra'); const { default: parseChangeset } = require('@changesets/parse'); diff --git a/scripts/verify-links.js b/scripts/verify-links.js index cec1b5545e..cddd4138cc 100755 --- a/scripts/verify-links.js +++ b/scripts/verify-links.js @@ -17,11 +17,96 @@ /* eslint-disable @backstage/no-undeclared-imports */ -const { resolve: resolvePath, join: joinPath, dirname } = require('path'); -const fs = require('fs').promises; -const { existsSync } = require('fs'); +const { resolve: resolvePath, join: joinPath, dirname } = require('node:path'); +const fs = require('node:fs').promises; +const { existsSync, statSync } = require('node:fs'); const IGNORED_DIRS = ['node_modules', 'dist', 'bin', '.git']; +const projectRoot = resolvePath(__dirname, '..'); + +// Zero-width and other invisible Unicode characters that shouldn't appear in URLs +const INVISIBLE_CHAR_PATTERN = + /[\u200B\u200C\u200D\u200E\u200F\uFEFF\u00AD\u2060\u2028\u2029]/; + +// Generates a GitHub/Docusaurus-compatible heading slug. +// Handles explicit {#custom-id} overrides and standard slugification. +function headingToSlug(headingText) { + const explicitId = headingText.match(/\{#([^}]+)\}\s*$/); + if (explicitId) { + return explicitId[1]; + } + + let slug = headingText + .toLowerCase() + // Remove inline code backticks + .replace(/`/g, '') + // Remove markdown bold/italic markers + .replace(/[*_]/g, '') + // Remove markdown links, keep link text + .replace(/\[([^\]]*)\]\([^)]*\)/g, '$1'); + + // Remove HTML tags in a loop to handle nested fragments like ipt> + let previous; + do { + previous = slug; + slug = slug.replace(/<[^>]+>/g, ''); + } while (slug !== previous); + + return ( + slug + // Replace special characters with hyphens (keeping alphanumeric, hyphens, spaces) + .replace(/[^\w\s-]/g, '') + .trim() + .replace(/\s+/g, '-') + ); +} + +// Extracts all heading anchors from a markdown file's content, +// handling duplicate headings with -1, -2, etc. suffixes (GitHub/Docusaurus behavior) +function extractHeadingAnchors(content) { + const anchors = new Set(); + const slugCounts = new Map(); + + // Strip fenced code blocks to avoid matching headings inside them + const stripped = content.replace(/^```[^\n]*\n[\s\S]*?^```/gm, ''); + + const headingPattern = /^#{1,6}\s+(.+)$/gm; + for ( + let match = headingPattern.exec(stripped); + match !== null; + match = headingPattern.exec(stripped) + ) { + const baseSlug = headingToSlug(match[1]); + const count = slugCounts.get(baseSlug) || 0; + slugCounts.set(baseSlug, count + 1); + + if (count === 0) { + anchors.add(baseSlug); + } else { + anchors.add(`${baseSlug}-${count}`); + } + } + return anchors; +} + +// Cache for file content and extracted anchors to avoid repeated reads +const anchorCache = new Map(); + +async function getAnchorsForFile(filePath) { + const absPath = resolvePath(projectRoot, filePath); + if (anchorCache.has(absPath)) { + return anchorCache.get(absPath); + } + try { + const content = await fs.readFile(absPath, 'utf8'); + const anchors = extractHeadingAnchors(content); + anchorCache.set(absPath, anchors); + return anchors; + } catch { + anchorCache.set(absPath, null); + return null; + } +} async function listFiles(dir) { const files = await fs.readdir(dir); @@ -40,15 +125,23 @@ async function listFiles(dir) { return paths.flat(); } -const projectRoot = resolvePath(__dirname, '..'); - async function verifyUrl(basePath, absUrl, docPages) { - const url = absUrl - .replace(/#.*$/, '') - .replace( - /https:\/\/github.com\/backstage\/backstage\/(tree|blob)\/master/, - '', + // Check for invisible/zero-width characters in the URL + if (INVISIBLE_CHAR_PATTERN.test(absUrl)) { + return { url: absUrl, basePath, problem: 'invisible-chars' }; + } + + const anchorMatch = absUrl.match(/#(.+)$/); + const anchor = anchorMatch ? anchorMatch[1] : undefined; + const urlWithoutAnchor = absUrl.replace(/#.*$/, ''); + const isGitHubUrl = + /https:\/\/github.com\/backstage\/backstage\/(tree|blob)\/master/.test( + urlWithoutAnchor, ); + const url = urlWithoutAnchor.replace( + /https:\/\/github.com\/backstage\/backstage\/(tree|blob)\/master/, + '', + ); // Avoid having absolute URL links within docs/, so that links work on the site if ( @@ -68,6 +161,15 @@ async function verifyUrl(basePath, absUrl, docPages) { return { url: absUrl, basePath, problem: 'github' }; } + // Same-file anchor reference (e.g. #some-heading) + if (!url && anchor) { + const anchors = await getAnchorsForFile(basePath); + if (anchors && !anchors.has(anchor)) { + return { url: absUrl, basePath, problem: 'bad-anchor' }; + } + return undefined; + } + if (!url) { return undefined; } @@ -132,12 +234,42 @@ async function verifyUrl(basePath, absUrl, docPages) { return { url, basePath, problem: 'missing' }; } + // Flag relative links to directories that are missing /index.md — + // these resolve as existing dirs but aren't valid doc links. + // Only check within docs/ since other directories (like microsite/) + // may legitimately link to directories in READMEs. + if ( + basePath.match(/^docs\//) && + !url.startsWith('/') && + existsSync(path) && + statSync(path).isDirectory() + ) { + return { url: absUrl, basePath, problem: 'directory-link' }; + } + + // Verify anchors in cross-file links, but skip rewritten GitHub URLs + // since their anchors may reference generated content we can't verify locally + if (anchor && path.endsWith('.md') && !isGitHubUrl) { + const targetAnchors = await getAnchorsForFile( + path.startsWith(projectRoot) ? path.slice(projectRoot.length + 1) : path, + ); + if (targetAnchors && !targetAnchors.has(anchor)) { + return { url: absUrl, basePath, problem: 'bad-anchor' }; + } + } + return undefined; } +// Strips fenced code blocks from markdown content so we don't check links inside them +function stripCodeBlocks(content) { + return content.replace(/^```[^\n]*\n[\s\S]*?^```/gm, ''); +} + async function verifyFile(filePath, docPages) { const content = await fs.readFile(filePath, 'utf8'); - const mdLinks = content.match(/\[.+?\]\(.+?\)/g) || []; + const strippedContent = stripCodeBlocks(content); + const mdLinks = strippedContent.match(/\[.+?\]\(.+?\)/g) || []; const badUrls = []; for (const mdLink of mdLinks) { @@ -149,7 +281,7 @@ async function verifyFile(filePath, docPages) { } const multiLineLinks = - content.match(/\[[^\]\n]+?\n[^\]\n]*?(?:\n[^\]\n]*?)?\]\(/g) || []; + strippedContent.match(/\[[^\]\n]+?\n[^\]\n]*?(?:\n[^\]\n]*?)?\]\(/g) || []; badUrls.push( ...multiLineLinks.map(url => ({ url, @@ -279,6 +411,20 @@ async function main() { console.error(`Links are not allowed to span multiple lines:`); console.error(` From: ${basePath}`); console.error(` To: ${url.replace(/\n/g, '\n ')}`); + } else if (problem === 'bad-anchor') { + console.error(`Anchor not found in target document`); + console.error(` From: ${basePath}`); + console.error(` To: ${url}`); + } else if (problem === 'directory-link') { + console.error( + `Link points to a directory instead of a file, use index.md suffix`, + ); + console.error(` From: ${basePath}`); + console.error(` To: ${url}`); + } else if (problem === 'invisible-chars') { + console.error(`Link contains invisible or zero-width characters`); + console.error(` From: ${basePath}`); + console.error(` To: ${JSON.stringify(url)}`); } } process.exit(1); diff --git a/scripts/verify-local-dependencies.js b/scripts/verify-local-dependencies.js index a09571b7fb..baf9564faf 100755 --- a/scripts/verify-local-dependencies.js +++ b/scripts/verify-local-dependencies.js @@ -17,7 +17,7 @@ const fs = require('fs-extra'); const { getPackages } = require('@manypkg/get-packages'); -const { resolve: resolvePath, join: joinPath } = require('path'); +const { resolve: resolvePath, join: joinPath } = require('node:path'); /** * This script checks that all local package dependencies within the repo @@ -62,6 +62,7 @@ const roleRules = [ // TODO(freben): Address these '@backstage/frontend-defaults', '@backstage/frontend-app-api', + '@backstage/frontend-dev-utils', '@backstage/frontend-test-utils', '@backstage/plugin-api-docs', '@backstage/plugin-techdocs-addons-test-utils', diff --git a/scripts/verify-lockfile-duplicates.js b/scripts/verify-lockfile-duplicates.js index 5c9adb72f0..e86b6f9722 100644 --- a/scripts/verify-lockfile-duplicates.js +++ b/scripts/verify-lockfile-duplicates.js @@ -17,9 +17,9 @@ /* eslint-disable @backstage/no-undeclared-imports */ -const { execFile: execFileCb } = require('child_process'); -const { resolve: resolvePath, dirname: dirnamePath } = require('path'); -const { promisify } = require('util'); +const { execFile: execFileCb } = require('node:child_process'); +const { resolve: resolvePath, dirname: dirnamePath } = require('node:path'); +const { promisify } = require('node:util'); const execFile = promisify(execFileCb); diff --git a/scripts/verify-plugin-directory.js b/scripts/verify-plugin-directory.js new file mode 100644 index 0000000000..70d3fbe9e9 --- /dev/null +++ b/scripts/verify-plugin-directory.js @@ -0,0 +1,89 @@ +#!/usr/bin/env node +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const fs = require('fs-extra'); +const { resolve, join } = require('node:path'); +const yaml = require('js-yaml'); +const z = require('zod/v3'); + +const configSchema = z.object({ + title: z.string(), + author: z.string(), + authorUrl: z.string().url(), + category: z.string(), + description: z.string(), + documentation: z.string().url(), + iconUrl: z.string().optional(), + npmPackageName: z.string(), + addedDate: z.coerce.date(), + order: z.number().optional(), + status: z.enum(['active', 'inactive', 'archived']), + staleSince: z.coerce.date().optional(), +}); + +async function main() { + const rootPath = resolve(__dirname, '..'); + const pluginDirectoryPath = resolve(rootPath, 'microsite', 'data', 'plugins'); + + const pluginDirectoryFiles = await fs.readdir(pluginDirectoryPath); + + let hasErrors = false; + for (const pluginDirectoryFile of pluginDirectoryFiles) { + const pluginDirectoryFilePath = join( + pluginDirectoryPath, + pluginDirectoryFile, + ); + + if (!pluginDirectoryFile.toLocaleLowerCase().includes('.yaml')) { + hasErrors = true; + console.log( + `${pluginDirectoryFilePath}: The '.yaml' extension is missing`, + ); + } + + const pluginDataYaml = yaml.load( + fs.readFileSync(pluginDirectoryFilePath, { encoding: 'utf-8' }), + ); + + try { + configSchema.parse(pluginDataYaml); + } catch (error) { + if (error instanceof z.ZodError) { + console.log( + `${pluginDirectoryFilePath}: YAML data validation failed`, + error.errors, + ); + } else { + console.log( + `${pluginDirectoryFilePath}: An unexpected error occurred`, + error, + ); + } + hasErrors = true; + } + } + if (hasErrors) { + throw new Error( + 'There was an error verifying the Plugin Directory data files, review the output for guidance on what corrections are needed', + ); + } +} + +main().catch(error => { + console.error(error.stack || error); + process.exit(1); +}); diff --git a/scripts/verify-release.js b/scripts/verify-release.js index fe181eddac..4f4913f9c8 100755 --- a/scripts/verify-release.js +++ b/scripts/verify-release.js @@ -18,10 +18,10 @@ // This script is used to verify that the branch that we're patching is correct, and that we're not patching branches like master -const { execFile: execFileCb } = require('child_process'); -const { resolve: resolvePath } = require('path'); -const { promises: fs } = require('fs'); -const { promisify } = require('util'); +const { execFile: execFileCb } = require('node:child_process'); +const { resolve: resolvePath } = require('node:path'); +const { promises: fs } = require('node:fs'); +const { promisify } = require('node:util'); const semver = require('semver'); const baseRef = process.env.GITHUB_BASE_REF || 'master'; diff --git a/tsconfig.json b/tsconfig.json index 26bfcf72e8..0c05669661 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,7 @@ "plugins/*/dev", "plugins/*/migrations" ], + "exclude": ["**/*.stories.ts", "**/*.stories.tsx"], "compilerOptions": { "outDir": "dist-types", "rootDir": ".", diff --git a/yarn.lock b/yarn.lock index df690c38b2..6802845bac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,6 +12,13 @@ __metadata: languageName: node linkType: hard +"@acemir/cssom@npm:^0.9.28": + version: 0.9.28 + resolution: "@acemir/cssom@npm:0.9.28" + checksum: 10/429faa2af7bffdcf3311e984f6b8ced77bf58d936e0e9a22c2fc892a84e54d2c8cbd71ae3a979ef54241509ac7c866cb880d919e3e713e64c865a1cd4ede3a1f + languageName: node + linkType: hard + "@adobe/css-tools@npm:^4.4.0": version: 4.4.0 resolution: "@adobe/css-tools@npm:4.4.0" @@ -193,7 +200,7 @@ __metadata: languageName: node linkType: hard -"@apidevtools/json-schema-ref-parser@npm:^14.0.3, @apidevtools/json-schema-ref-parser@npm:^14.2.1": +"@apidevtools/json-schema-ref-parser@npm:^14.2.1": version: 14.2.1 resolution: "@apidevtools/json-schema-ref-parser@npm:14.2.1" dependencies: @@ -245,7 +252,7 @@ __metadata: languageName: node linkType: hard -"@arcanis/slice-ansi@npm:^1.0.2, @arcanis/slice-ansi@npm:^1.1.1": +"@arcanis/slice-ansi@npm:^1.1.1": version: 1.1.1 resolution: "@arcanis/slice-ansi@npm:1.1.1" dependencies: @@ -254,12 +261,49 @@ __metadata: languageName: node linkType: hard -"@ardatan/sync-fetch@npm:^0.0.1": - version: 0.0.1 - resolution: "@ardatan/sync-fetch@npm:0.0.1" +"@asamuzakjp/css-color@npm:^3.2.0": + version: 3.2.0 + resolution: "@asamuzakjp/css-color@npm:3.2.0" dependencies: - node-fetch: "npm:^2.6.1" - checksum: 10/ee21741badecb18fb9a18a404275e25272f67ade914f98885de79ccecba3403b8a6357e6b033a028e24f0d902197dd541655309d7789ebacd7ad981bf1f12618 + "@csstools/css-calc": "npm:^2.1.3" + "@csstools/css-color-parser": "npm:^3.0.9" + "@csstools/css-parser-algorithms": "npm:^3.0.4" + "@csstools/css-tokenizer": "npm:^3.0.3" + lru-cache: "npm:^10.4.3" + checksum: 10/870f661460173174fef8bfebea0799ba26566f3aa7b307e5adabb7aae84fed2da68e40080104ed0c83b43c5be632ee409e65396af13bfe948a3ef4c2c729ecd9 + languageName: node + linkType: hard + +"@asamuzakjp/css-color@npm:^4.1.0": + version: 4.1.0 + resolution: "@asamuzakjp/css-color@npm:4.1.0" + dependencies: + "@csstools/css-calc": "npm:^2.1.4" + "@csstools/css-color-parser": "npm:^3.1.0" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + lru-cache: "npm:^11.2.2" + checksum: 10/e76b57fff34b958e224662d2afb19d23bc6c95d6ea40055e23a8f7417ae79904b7741655bbe6a883c09d5ee68ebb20e9313036aed3680fe85e383e0527397566 + languageName: node + linkType: hard + +"@asamuzakjp/dom-selector@npm:^6.7.6": + version: 6.7.6 + resolution: "@asamuzakjp/dom-selector@npm:6.7.6" + dependencies: + "@asamuzakjp/nwsapi": "npm:^2.3.9" + bidi-js: "npm:^1.0.3" + css-tree: "npm:^3.1.0" + is-potential-custom-element-name: "npm:^1.0.1" + lru-cache: "npm:^11.2.4" + checksum: 10/91a479f5f59a3b1b23f46407d874882c05b50e72316e57af105c88603fb1008120c46c7fe7504c5be556ea4c9a68ee10aee43433bd8d34f000b9aaf6dcae4d5c + languageName: node + linkType: hard + +"@asamuzakjp/nwsapi@npm:^2.3.9": + version: 2.3.9 + resolution: "@asamuzakjp/nwsapi@npm:2.3.9" + checksum: 10/95a6d1c102e1117fe818da087fcc5b914d23e0699855991bae50b891435dd1945ad7d384198f8bcf616207fd85b7ec32e3db6b96e9309d84c6903b8dc4151e34 languageName: node linkType: hard @@ -355,17 +399,6 @@ __metadata: languageName: node linkType: hard -"@aws-crypto/crc32@npm:3.0.0": - version: 3.0.0 - resolution: "@aws-crypto/crc32@npm:3.0.0" - dependencies: - "@aws-crypto/util": "npm:^3.0.0" - "@aws-sdk/types": "npm:^3.222.0" - tslib: "npm:^1.11.1" - checksum: 10/672d593fd98a88709a1b488db92aabf584b6dad3e8099e04b6d2870e34a2ee668cbbe0e5406e60c0d776b9c34a91cfc427999230ad959518fed56a3db037704c - languageName: node - linkType: hard - "@aws-crypto/crc32@npm:5.2.0": version: 5.2.0 resolution: "@aws-crypto/crc32@npm:5.2.0" @@ -437,18 +470,7 @@ __metadata: languageName: node linkType: hard -"@aws-crypto/util@npm:^3.0.0": - version: 3.0.0 - resolution: "@aws-crypto/util@npm:3.0.0" - dependencies: - "@aws-sdk/types": "npm:^3.222.0" - "@aws-sdk/util-utf8-browser": "npm:^3.0.0" - tslib: "npm:^1.11.1" - checksum: 10/92c835b83d7a888b37b2f2a37c82e58bb8fabb617e371173c488d2a71b916c69ee566f0ea0b3f7f4e16296226c49793f95b3d59fc07a7ca00af91f8f9f29e6c4 - languageName: node - linkType: hard - -"@aws-crypto/util@npm:^5.2.0": +"@aws-crypto/util@npm:5.2.0, @aws-crypto/util@npm:^5.2.0": version: 5.2.0 resolution: "@aws-crypto/util@npm:5.2.0" dependencies: @@ -470,872 +492,604 @@ __metadata: linkType: hard "@aws-sdk/client-codecommit@npm:^3.350.0": - version: 3.651.1 - resolution: "@aws-sdk/client-codecommit@npm:3.651.1" + version: 3.1020.0 + resolution: "@aws-sdk/client-codecommit@npm:3.1020.0" dependencies: "@aws-crypto/sha256-browser": "npm:5.2.0" "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/client-sso-oidc": "npm:3.651.1" - "@aws-sdk/client-sts": "npm:3.651.1" - "@aws-sdk/core": "npm:3.651.1" - "@aws-sdk/credential-provider-node": "npm:3.651.1" - "@aws-sdk/middleware-host-header": "npm:3.649.0" - "@aws-sdk/middleware-logger": "npm:3.649.0" - "@aws-sdk/middleware-recursion-detection": "npm:3.649.0" - "@aws-sdk/middleware-user-agent": "npm:3.649.0" - "@aws-sdk/region-config-resolver": "npm:3.649.0" - "@aws-sdk/types": "npm:3.649.0" - "@aws-sdk/util-endpoints": "npm:3.649.0" - "@aws-sdk/util-user-agent-browser": "npm:3.649.0" - "@aws-sdk/util-user-agent-node": "npm:3.649.0" - "@smithy/config-resolver": "npm:^3.0.6" - "@smithy/core": "npm:^2.4.1" - "@smithy/fetch-http-handler": "npm:^3.2.5" - "@smithy/hash-node": "npm:^3.0.4" - "@smithy/invalid-dependency": "npm:^3.0.4" - "@smithy/middleware-content-length": "npm:^3.0.6" - "@smithy/middleware-endpoint": "npm:^3.1.1" - "@smithy/middleware-retry": "npm:^3.0.16" - "@smithy/middleware-serde": "npm:^3.0.4" - "@smithy/middleware-stack": "npm:^3.0.4" - "@smithy/node-config-provider": "npm:^3.1.5" - "@smithy/node-http-handler": "npm:^3.2.0" - "@smithy/protocol-http": "npm:^4.1.1" - "@smithy/smithy-client": "npm:^3.3.0" - "@smithy/types": "npm:^3.4.0" - "@smithy/url-parser": "npm:^3.0.4" - "@smithy/util-base64": "npm:^3.0.0" - "@smithy/util-body-length-browser": "npm:^3.0.0" - "@smithy/util-body-length-node": "npm:^3.0.0" - "@smithy/util-defaults-mode-browser": "npm:^3.0.16" - "@smithy/util-defaults-mode-node": "npm:^3.0.16" - "@smithy/util-endpoints": "npm:^2.1.0" - "@smithy/util-middleware": "npm:^3.0.4" - "@smithy/util-retry": "npm:^3.0.4" - "@smithy/util-utf8": "npm:^3.0.0" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/credential-provider-node": "npm:^3.972.28" + "@aws-sdk/middleware-host-header": "npm:^3.972.8" + "@aws-sdk/middleware-logger": "npm:^3.972.8" + "@aws-sdk/middleware-recursion-detection": "npm:^3.972.9" + "@aws-sdk/middleware-user-agent": "npm:^3.972.27" + "@aws-sdk/region-config-resolver": "npm:^3.972.10" + "@aws-sdk/types": "npm:^3.973.6" + "@aws-sdk/util-endpoints": "npm:^3.996.5" + "@aws-sdk/util-user-agent-browser": "npm:^3.972.8" + "@aws-sdk/util-user-agent-node": "npm:^3.973.13" + "@smithy/config-resolver": "npm:^4.4.13" + "@smithy/core": "npm:^3.23.13" + "@smithy/fetch-http-handler": "npm:^5.3.15" + "@smithy/hash-node": "npm:^4.2.12" + "@smithy/invalid-dependency": "npm:^4.2.12" + "@smithy/middleware-content-length": "npm:^4.2.12" + "@smithy/middleware-endpoint": "npm:^4.4.28" + "@smithy/middleware-retry": "npm:^4.4.45" + "@smithy/middleware-serde": "npm:^4.2.16" + "@smithy/middleware-stack": "npm:^4.2.12" + "@smithy/node-config-provider": "npm:^4.3.12" + "@smithy/node-http-handler": "npm:^4.5.1" + "@smithy/protocol-http": "npm:^5.3.12" + "@smithy/smithy-client": "npm:^4.12.8" + "@smithy/types": "npm:^4.13.1" + "@smithy/url-parser": "npm:^4.2.12" + "@smithy/util-base64": "npm:^4.3.2" + "@smithy/util-body-length-browser": "npm:^4.2.2" + "@smithy/util-body-length-node": "npm:^4.2.3" + "@smithy/util-defaults-mode-browser": "npm:^4.3.44" + "@smithy/util-defaults-mode-node": "npm:^4.2.48" + "@smithy/util-endpoints": "npm:^3.3.3" + "@smithy/util-middleware": "npm:^4.2.12" + "@smithy/util-retry": "npm:^4.2.12" + "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - uuid: "npm:^9.0.1" - checksum: 10/bf10987e2a0b0078cc2fa32b8339cf76fff50f2608b475c1b9f02a8eb1074bd5755e9c61802e134bc2ccacc0f965e96063ff94552e01397434460578460aebe3 + checksum: 10/97aeb0bd0b2fa40e5bf5b1ebb4e4a04257f7c6d05c5480a1e07e9a1a9841d4a8799b5426b984181dc2aa77747cdb3523896fd02572dc985ef6f7e3d00a568ea4 languageName: node linkType: hard -"@aws-sdk/client-cognito-identity@npm:3.651.1": - version: 3.651.1 - resolution: "@aws-sdk/client-cognito-identity@npm:3.651.1" +"@aws-sdk/client-cognito-identity@npm:3.1022.0": + version: 3.1022.0 + resolution: "@aws-sdk/client-cognito-identity@npm:3.1022.0" dependencies: "@aws-crypto/sha256-browser": "npm:5.2.0" "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/client-sso-oidc": "npm:3.651.1" - "@aws-sdk/client-sts": "npm:3.651.1" - "@aws-sdk/core": "npm:3.651.1" - "@aws-sdk/credential-provider-node": "npm:3.651.1" - "@aws-sdk/middleware-host-header": "npm:3.649.0" - "@aws-sdk/middleware-logger": "npm:3.649.0" - "@aws-sdk/middleware-recursion-detection": "npm:3.649.0" - "@aws-sdk/middleware-user-agent": "npm:3.649.0" - "@aws-sdk/region-config-resolver": "npm:3.649.0" - "@aws-sdk/types": "npm:3.649.0" - "@aws-sdk/util-endpoints": "npm:3.649.0" - "@aws-sdk/util-user-agent-browser": "npm:3.649.0" - "@aws-sdk/util-user-agent-node": "npm:3.649.0" - "@smithy/config-resolver": "npm:^3.0.6" - "@smithy/core": "npm:^2.4.1" - "@smithy/fetch-http-handler": "npm:^3.2.5" - "@smithy/hash-node": "npm:^3.0.4" - "@smithy/invalid-dependency": "npm:^3.0.4" - "@smithy/middleware-content-length": "npm:^3.0.6" - "@smithy/middleware-endpoint": "npm:^3.1.1" - "@smithy/middleware-retry": "npm:^3.0.16" - "@smithy/middleware-serde": "npm:^3.0.4" - "@smithy/middleware-stack": "npm:^3.0.4" - "@smithy/node-config-provider": "npm:^3.1.5" - "@smithy/node-http-handler": "npm:^3.2.0" - "@smithy/protocol-http": "npm:^4.1.1" - "@smithy/smithy-client": "npm:^3.3.0" - "@smithy/types": "npm:^3.4.0" - "@smithy/url-parser": "npm:^3.0.4" - "@smithy/util-base64": "npm:^3.0.0" - "@smithy/util-body-length-browser": "npm:^3.0.0" - "@smithy/util-body-length-node": "npm:^3.0.0" - "@smithy/util-defaults-mode-browser": "npm:^3.0.16" - "@smithy/util-defaults-mode-node": "npm:^3.0.16" - "@smithy/util-endpoints": "npm:^2.1.0" - "@smithy/util-middleware": "npm:^3.0.4" - "@smithy/util-retry": "npm:^3.0.4" - "@smithy/util-utf8": "npm:^3.0.0" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/credential-provider-node": "npm:^3.972.29" + "@aws-sdk/middleware-host-header": "npm:^3.972.8" + "@aws-sdk/middleware-logger": "npm:^3.972.8" + "@aws-sdk/middleware-recursion-detection": "npm:^3.972.9" + "@aws-sdk/middleware-user-agent": "npm:^3.972.28" + "@aws-sdk/region-config-resolver": "npm:^3.972.10" + "@aws-sdk/types": "npm:^3.973.6" + "@aws-sdk/util-endpoints": "npm:^3.996.5" + "@aws-sdk/util-user-agent-browser": "npm:^3.972.8" + "@aws-sdk/util-user-agent-node": "npm:^3.973.14" + "@smithy/config-resolver": "npm:^4.4.13" + "@smithy/core": "npm:^3.23.13" + "@smithy/fetch-http-handler": "npm:^5.3.15" + "@smithy/hash-node": "npm:^4.2.12" + "@smithy/invalid-dependency": "npm:^4.2.12" + "@smithy/middleware-content-length": "npm:^4.2.12" + "@smithy/middleware-endpoint": "npm:^4.4.28" + "@smithy/middleware-retry": "npm:^4.4.46" + "@smithy/middleware-serde": "npm:^4.2.16" + "@smithy/middleware-stack": "npm:^4.2.12" + "@smithy/node-config-provider": "npm:^4.3.12" + "@smithy/node-http-handler": "npm:^4.5.1" + "@smithy/protocol-http": "npm:^5.3.12" + "@smithy/smithy-client": "npm:^4.12.8" + "@smithy/types": "npm:^4.13.1" + "@smithy/url-parser": "npm:^4.2.12" + "@smithy/util-base64": "npm:^4.3.2" + "@smithy/util-body-length-browser": "npm:^4.2.2" + "@smithy/util-body-length-node": "npm:^4.2.3" + "@smithy/util-defaults-mode-browser": "npm:^4.3.44" + "@smithy/util-defaults-mode-node": "npm:^4.2.48" + "@smithy/util-endpoints": "npm:^3.3.3" + "@smithy/util-middleware": "npm:^4.2.12" + "@smithy/util-retry": "npm:^4.2.13" + "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/0934d5f991a07f8008788431762b0f13f784d6ac224b34ece0e67e6a5545f7b93a778c1b70f8b3a16601f621987fb7c3b7df1bf2c7fe360d1f11ef965781007b + checksum: 10/569321ecde471b530ce4af6b2b2145a8500293de109ed9625ce1d09532723573b224c0e74bdcce5664d6fdbee9c0f8cd2ae2704c169b544054fed3c4dab35f02 languageName: node linkType: hard "@aws-sdk/client-eks@npm:^3.350.0": - version: 3.651.1 - resolution: "@aws-sdk/client-eks@npm:3.651.1" + version: 3.1020.0 + resolution: "@aws-sdk/client-eks@npm:3.1020.0" dependencies: "@aws-crypto/sha256-browser": "npm:5.2.0" "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/client-sso-oidc": "npm:3.651.1" - "@aws-sdk/client-sts": "npm:3.651.1" - "@aws-sdk/core": "npm:3.651.1" - "@aws-sdk/credential-provider-node": "npm:3.651.1" - "@aws-sdk/middleware-host-header": "npm:3.649.0" - "@aws-sdk/middleware-logger": "npm:3.649.0" - "@aws-sdk/middleware-recursion-detection": "npm:3.649.0" - "@aws-sdk/middleware-user-agent": "npm:3.649.0" - "@aws-sdk/region-config-resolver": "npm:3.649.0" - "@aws-sdk/types": "npm:3.649.0" - "@aws-sdk/util-endpoints": "npm:3.649.0" - "@aws-sdk/util-user-agent-browser": "npm:3.649.0" - "@aws-sdk/util-user-agent-node": "npm:3.649.0" - "@smithy/config-resolver": "npm:^3.0.6" - "@smithy/core": "npm:^2.4.1" - "@smithy/fetch-http-handler": "npm:^3.2.5" - "@smithy/hash-node": "npm:^3.0.4" - "@smithy/invalid-dependency": "npm:^3.0.4" - "@smithy/middleware-content-length": "npm:^3.0.6" - "@smithy/middleware-endpoint": "npm:^3.1.1" - "@smithy/middleware-retry": "npm:^3.0.16" - "@smithy/middleware-serde": "npm:^3.0.4" - "@smithy/middleware-stack": "npm:^3.0.4" - "@smithy/node-config-provider": "npm:^3.1.5" - "@smithy/node-http-handler": "npm:^3.2.0" - "@smithy/protocol-http": "npm:^4.1.1" - "@smithy/smithy-client": "npm:^3.3.0" - "@smithy/types": "npm:^3.4.0" - "@smithy/url-parser": "npm:^3.0.4" - "@smithy/util-base64": "npm:^3.0.0" - "@smithy/util-body-length-browser": "npm:^3.0.0" - "@smithy/util-body-length-node": "npm:^3.0.0" - "@smithy/util-defaults-mode-browser": "npm:^3.0.16" - "@smithy/util-defaults-mode-node": "npm:^3.0.16" - "@smithy/util-endpoints": "npm:^2.1.0" - "@smithy/util-middleware": "npm:^3.0.4" - "@smithy/util-retry": "npm:^3.0.4" - "@smithy/util-utf8": "npm:^3.0.0" - "@smithy/util-waiter": "npm:^3.1.3" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/credential-provider-node": "npm:^3.972.28" + "@aws-sdk/middleware-host-header": "npm:^3.972.8" + "@aws-sdk/middleware-logger": "npm:^3.972.8" + "@aws-sdk/middleware-recursion-detection": "npm:^3.972.9" + "@aws-sdk/middleware-user-agent": "npm:^3.972.27" + "@aws-sdk/region-config-resolver": "npm:^3.972.10" + "@aws-sdk/types": "npm:^3.973.6" + "@aws-sdk/util-endpoints": "npm:^3.996.5" + "@aws-sdk/util-user-agent-browser": "npm:^3.972.8" + "@aws-sdk/util-user-agent-node": "npm:^3.973.13" + "@smithy/config-resolver": "npm:^4.4.13" + "@smithy/core": "npm:^3.23.13" + "@smithy/fetch-http-handler": "npm:^5.3.15" + "@smithy/hash-node": "npm:^4.2.12" + "@smithy/invalid-dependency": "npm:^4.2.12" + "@smithy/middleware-content-length": "npm:^4.2.12" + "@smithy/middleware-endpoint": "npm:^4.4.28" + "@smithy/middleware-retry": "npm:^4.4.45" + "@smithy/middleware-serde": "npm:^4.2.16" + "@smithy/middleware-stack": "npm:^4.2.12" + "@smithy/node-config-provider": "npm:^4.3.12" + "@smithy/node-http-handler": "npm:^4.5.1" + "@smithy/protocol-http": "npm:^5.3.12" + "@smithy/smithy-client": "npm:^4.12.8" + "@smithy/types": "npm:^4.13.1" + "@smithy/url-parser": "npm:^4.2.12" + "@smithy/util-base64": "npm:^4.3.2" + "@smithy/util-body-length-browser": "npm:^4.2.2" + "@smithy/util-body-length-node": "npm:^4.2.3" + "@smithy/util-defaults-mode-browser": "npm:^4.3.44" + "@smithy/util-defaults-mode-node": "npm:^4.2.48" + "@smithy/util-endpoints": "npm:^3.3.3" + "@smithy/util-middleware": "npm:^4.2.12" + "@smithy/util-retry": "npm:^4.2.12" + "@smithy/util-utf8": "npm:^4.2.2" + "@smithy/util-waiter": "npm:^4.2.14" tslib: "npm:^2.6.2" - uuid: "npm:^9.0.1" - checksum: 10/6e1d563058139d97c86649c2ff0c19c60f142b7ef556577ac30b2e5181b5c6299a408c78c6288ab17408cb2f434e1dd321b1e755415ff7fc9f24bc0bac433c00 + checksum: 10/846706c02b8d54f186b76f03755c1da0ca95c470e52397d660a537617e5b71d53de915185ba1d4a6ce5c3c7473dd836ae99b2e4612cfcf58e487fadc2c9f84ef languageName: node linkType: hard "@aws-sdk/client-organizations@npm:^3.350.0": - version: 3.652.0 - resolution: "@aws-sdk/client-organizations@npm:3.652.0" + version: 3.1020.0 + resolution: "@aws-sdk/client-organizations@npm:3.1020.0" dependencies: "@aws-crypto/sha256-browser": "npm:5.2.0" "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/client-sso-oidc": "npm:3.651.1" - "@aws-sdk/client-sts": "npm:3.651.1" - "@aws-sdk/core": "npm:3.651.1" - "@aws-sdk/credential-provider-node": "npm:3.651.1" - "@aws-sdk/middleware-host-header": "npm:3.649.0" - "@aws-sdk/middleware-logger": "npm:3.649.0" - "@aws-sdk/middleware-recursion-detection": "npm:3.649.0" - "@aws-sdk/middleware-user-agent": "npm:3.649.0" - "@aws-sdk/region-config-resolver": "npm:3.649.0" - "@aws-sdk/types": "npm:3.649.0" - "@aws-sdk/util-endpoints": "npm:3.649.0" - "@aws-sdk/util-user-agent-browser": "npm:3.649.0" - "@aws-sdk/util-user-agent-node": "npm:3.649.0" - "@smithy/config-resolver": "npm:^3.0.6" - "@smithy/core": "npm:^2.4.1" - "@smithy/fetch-http-handler": "npm:^3.2.5" - "@smithy/hash-node": "npm:^3.0.4" - "@smithy/invalid-dependency": "npm:^3.0.4" - "@smithy/middleware-content-length": "npm:^3.0.6" - "@smithy/middleware-endpoint": "npm:^3.1.1" - "@smithy/middleware-retry": "npm:^3.0.16" - "@smithy/middleware-serde": "npm:^3.0.4" - "@smithy/middleware-stack": "npm:^3.0.4" - "@smithy/node-config-provider": "npm:^3.1.5" - "@smithy/node-http-handler": "npm:^3.2.0" - "@smithy/protocol-http": "npm:^4.1.1" - "@smithy/smithy-client": "npm:^3.3.0" - "@smithy/types": "npm:^3.4.0" - "@smithy/url-parser": "npm:^3.0.4" - "@smithy/util-base64": "npm:^3.0.0" - "@smithy/util-body-length-browser": "npm:^3.0.0" - "@smithy/util-body-length-node": "npm:^3.0.0" - "@smithy/util-defaults-mode-browser": "npm:^3.0.16" - "@smithy/util-defaults-mode-node": "npm:^3.0.16" - "@smithy/util-endpoints": "npm:^2.1.0" - "@smithy/util-middleware": "npm:^3.0.4" - "@smithy/util-retry": "npm:^3.0.4" - "@smithy/util-utf8": "npm:^3.0.0" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/credential-provider-node": "npm:^3.972.28" + "@aws-sdk/middleware-host-header": "npm:^3.972.8" + "@aws-sdk/middleware-logger": "npm:^3.972.8" + "@aws-sdk/middleware-recursion-detection": "npm:^3.972.9" + "@aws-sdk/middleware-user-agent": "npm:^3.972.27" + "@aws-sdk/region-config-resolver": "npm:^3.972.10" + "@aws-sdk/types": "npm:^3.973.6" + "@aws-sdk/util-endpoints": "npm:^3.996.5" + "@aws-sdk/util-user-agent-browser": "npm:^3.972.8" + "@aws-sdk/util-user-agent-node": "npm:^3.973.13" + "@smithy/config-resolver": "npm:^4.4.13" + "@smithy/core": "npm:^3.23.13" + "@smithy/fetch-http-handler": "npm:^5.3.15" + "@smithy/hash-node": "npm:^4.2.12" + "@smithy/invalid-dependency": "npm:^4.2.12" + "@smithy/middleware-content-length": "npm:^4.2.12" + "@smithy/middleware-endpoint": "npm:^4.4.28" + "@smithy/middleware-retry": "npm:^4.4.45" + "@smithy/middleware-serde": "npm:^4.2.16" + "@smithy/middleware-stack": "npm:^4.2.12" + "@smithy/node-config-provider": "npm:^4.3.12" + "@smithy/node-http-handler": "npm:^4.5.1" + "@smithy/protocol-http": "npm:^5.3.12" + "@smithy/smithy-client": "npm:^4.12.8" + "@smithy/types": "npm:^4.13.1" + "@smithy/url-parser": "npm:^4.2.12" + "@smithy/util-base64": "npm:^4.3.2" + "@smithy/util-body-length-browser": "npm:^4.2.2" + "@smithy/util-body-length-node": "npm:^4.2.3" + "@smithy/util-defaults-mode-browser": "npm:^4.3.44" + "@smithy/util-defaults-mode-node": "npm:^4.2.48" + "@smithy/util-endpoints": "npm:^3.3.3" + "@smithy/util-middleware": "npm:^4.2.12" + "@smithy/util-retry": "npm:^4.2.12" + "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/1bd38da5311a376ab8bf686277155424b945f207399f263a14d5b1e8bf49fd6edb1b739308ed15483939d0840ce4987bcd44cf8c99ad36d9e00cd8656e87e40f + checksum: 10/932a6a3d84af6e3526d9180813b20c5ae7e1dfdf46a277d3c3368f75fdcc57e6d35e0dcd5fc150db8787343d617023662ea010252613a30ff971b96b66d31747 languageName: node linkType: hard "@aws-sdk/client-s3@npm:^3.350.0": - version: 3.651.1 - resolution: "@aws-sdk/client-s3@npm:3.651.1" + version: 3.1020.0 + resolution: "@aws-sdk/client-s3@npm:3.1020.0" dependencies: "@aws-crypto/sha1-browser": "npm:5.2.0" "@aws-crypto/sha256-browser": "npm:5.2.0" "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/client-sso-oidc": "npm:3.651.1" - "@aws-sdk/client-sts": "npm:3.651.1" - "@aws-sdk/core": "npm:3.651.1" - "@aws-sdk/credential-provider-node": "npm:3.651.1" - "@aws-sdk/middleware-bucket-endpoint": "npm:3.649.0" - "@aws-sdk/middleware-expect-continue": "npm:3.649.0" - "@aws-sdk/middleware-flexible-checksums": "npm:3.651.1" - "@aws-sdk/middleware-host-header": "npm:3.649.0" - "@aws-sdk/middleware-location-constraint": "npm:3.649.0" - "@aws-sdk/middleware-logger": "npm:3.649.0" - "@aws-sdk/middleware-recursion-detection": "npm:3.649.0" - "@aws-sdk/middleware-sdk-s3": "npm:3.651.1" - "@aws-sdk/middleware-ssec": "npm:3.649.0" - "@aws-sdk/middleware-user-agent": "npm:3.649.0" - "@aws-sdk/region-config-resolver": "npm:3.649.0" - "@aws-sdk/signature-v4-multi-region": "npm:3.651.1" - "@aws-sdk/types": "npm:3.649.0" - "@aws-sdk/util-endpoints": "npm:3.649.0" - "@aws-sdk/util-user-agent-browser": "npm:3.649.0" - "@aws-sdk/util-user-agent-node": "npm:3.649.0" - "@aws-sdk/xml-builder": "npm:3.649.0" - "@smithy/config-resolver": "npm:^3.0.6" - "@smithy/core": "npm:^2.4.1" - "@smithy/eventstream-serde-browser": "npm:^3.0.7" - "@smithy/eventstream-serde-config-resolver": "npm:^3.0.4" - "@smithy/eventstream-serde-node": "npm:^3.0.6" - "@smithy/fetch-http-handler": "npm:^3.2.5" - "@smithy/hash-blob-browser": "npm:^3.1.3" - "@smithy/hash-node": "npm:^3.0.4" - "@smithy/hash-stream-node": "npm:^3.1.3" - "@smithy/invalid-dependency": "npm:^3.0.4" - "@smithy/md5-js": "npm:^3.0.4" - "@smithy/middleware-content-length": "npm:^3.0.6" - "@smithy/middleware-endpoint": "npm:^3.1.1" - "@smithy/middleware-retry": "npm:^3.0.16" - "@smithy/middleware-serde": "npm:^3.0.4" - "@smithy/middleware-stack": "npm:^3.0.4" - "@smithy/node-config-provider": "npm:^3.1.5" - "@smithy/node-http-handler": "npm:^3.2.0" - "@smithy/protocol-http": "npm:^4.1.1" - "@smithy/smithy-client": "npm:^3.3.0" - "@smithy/types": "npm:^3.4.0" - "@smithy/url-parser": "npm:^3.0.4" - "@smithy/util-base64": "npm:^3.0.0" - "@smithy/util-body-length-browser": "npm:^3.0.0" - "@smithy/util-body-length-node": "npm:^3.0.0" - "@smithy/util-defaults-mode-browser": "npm:^3.0.16" - "@smithy/util-defaults-mode-node": "npm:^3.0.16" - "@smithy/util-endpoints": "npm:^2.1.0" - "@smithy/util-middleware": "npm:^3.0.4" - "@smithy/util-retry": "npm:^3.0.4" - "@smithy/util-stream": "npm:^3.1.4" - "@smithy/util-utf8": "npm:^3.0.0" - "@smithy/util-waiter": "npm:^3.1.3" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/credential-provider-node": "npm:^3.972.28" + "@aws-sdk/middleware-bucket-endpoint": "npm:^3.972.8" + "@aws-sdk/middleware-expect-continue": "npm:^3.972.8" + "@aws-sdk/middleware-flexible-checksums": "npm:^3.974.6" + "@aws-sdk/middleware-host-header": "npm:^3.972.8" + "@aws-sdk/middleware-location-constraint": "npm:^3.972.8" + "@aws-sdk/middleware-logger": "npm:^3.972.8" + "@aws-sdk/middleware-recursion-detection": "npm:^3.972.9" + "@aws-sdk/middleware-sdk-s3": "npm:^3.972.27" + "@aws-sdk/middleware-ssec": "npm:^3.972.8" + "@aws-sdk/middleware-user-agent": "npm:^3.972.27" + "@aws-sdk/region-config-resolver": "npm:^3.972.10" + "@aws-sdk/signature-v4-multi-region": "npm:^3.996.15" + "@aws-sdk/types": "npm:^3.973.6" + "@aws-sdk/util-endpoints": "npm:^3.996.5" + "@aws-sdk/util-user-agent-browser": "npm:^3.972.8" + "@aws-sdk/util-user-agent-node": "npm:^3.973.13" + "@smithy/config-resolver": "npm:^4.4.13" + "@smithy/core": "npm:^3.23.13" + "@smithy/eventstream-serde-browser": "npm:^4.2.12" + "@smithy/eventstream-serde-config-resolver": "npm:^4.3.12" + "@smithy/eventstream-serde-node": "npm:^4.2.12" + "@smithy/fetch-http-handler": "npm:^5.3.15" + "@smithy/hash-blob-browser": "npm:^4.2.13" + "@smithy/hash-node": "npm:^4.2.12" + "@smithy/hash-stream-node": "npm:^4.2.12" + "@smithy/invalid-dependency": "npm:^4.2.12" + "@smithy/md5-js": "npm:^4.2.12" + "@smithy/middleware-content-length": "npm:^4.2.12" + "@smithy/middleware-endpoint": "npm:^4.4.28" + "@smithy/middleware-retry": "npm:^4.4.45" + "@smithy/middleware-serde": "npm:^4.2.16" + "@smithy/middleware-stack": "npm:^4.2.12" + "@smithy/node-config-provider": "npm:^4.3.12" + "@smithy/node-http-handler": "npm:^4.5.1" + "@smithy/protocol-http": "npm:^5.3.12" + "@smithy/smithy-client": "npm:^4.12.8" + "@smithy/types": "npm:^4.13.1" + "@smithy/url-parser": "npm:^4.2.12" + "@smithy/util-base64": "npm:^4.3.2" + "@smithy/util-body-length-browser": "npm:^4.2.2" + "@smithy/util-body-length-node": "npm:^4.2.3" + "@smithy/util-defaults-mode-browser": "npm:^4.3.44" + "@smithy/util-defaults-mode-node": "npm:^4.2.48" + "@smithy/util-endpoints": "npm:^3.3.3" + "@smithy/util-middleware": "npm:^4.2.12" + "@smithy/util-retry": "npm:^4.2.12" + "@smithy/util-stream": "npm:^4.5.21" + "@smithy/util-utf8": "npm:^4.2.2" + "@smithy/util-waiter": "npm:^4.2.14" tslib: "npm:^2.6.2" - checksum: 10/3531e9db5e7f174ea46b3a5ea7d94566660173e7f8a6c8df8bbfbf5659f7a8213e9f17d8135ee65d0d98168beb4dc875ab6dff2730d138df7875226e9f0ac5eb + checksum: 10/898815cd15bca7276090bc46ca065baba2d48796c7054bd61c9f5c7b76fc2f55fcfc3eb1b8784f82fec667eb26c7e3bc9af24bd47d04906ee1c7a6c1288f991c languageName: node linkType: hard "@aws-sdk/client-sesv2@npm:^3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/client-sesv2@npm:3.911.0" + version: 3.1020.0 + resolution: "@aws-sdk/client-sesv2@npm:3.1020.0" dependencies: "@aws-crypto/sha256-browser": "npm:5.2.0" "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/core": "npm:3.911.0" - "@aws-sdk/credential-provider-node": "npm:3.911.0" - "@aws-sdk/middleware-host-header": "npm:3.910.0" - "@aws-sdk/middleware-logger": "npm:3.910.0" - "@aws-sdk/middleware-recursion-detection": "npm:3.910.0" - "@aws-sdk/middleware-user-agent": "npm:3.911.0" - "@aws-sdk/region-config-resolver": "npm:3.910.0" - "@aws-sdk/signature-v4-multi-region": "npm:3.911.0" - "@aws-sdk/types": "npm:3.910.0" - "@aws-sdk/util-endpoints": "npm:3.910.0" - "@aws-sdk/util-user-agent-browser": "npm:3.910.0" - "@aws-sdk/util-user-agent-node": "npm:3.911.0" - "@smithy/config-resolver": "npm:^4.3.2" - "@smithy/core": "npm:^3.16.1" - "@smithy/fetch-http-handler": "npm:^5.3.3" - "@smithy/hash-node": "npm:^4.2.2" - "@smithy/invalid-dependency": "npm:^4.2.2" - "@smithy/middleware-content-length": "npm:^4.2.2" - "@smithy/middleware-endpoint": "npm:^4.3.3" - "@smithy/middleware-retry": "npm:^4.4.3" - "@smithy/middleware-serde": "npm:^4.2.2" - "@smithy/middleware-stack": "npm:^4.2.2" - "@smithy/node-config-provider": "npm:^4.3.2" - "@smithy/node-http-handler": "npm:^4.4.1" - "@smithy/protocol-http": "npm:^5.3.2" - "@smithy/smithy-client": "npm:^4.8.1" - "@smithy/types": "npm:^4.7.1" - "@smithy/url-parser": "npm:^4.2.2" - "@smithy/util-base64": "npm:^4.3.0" - "@smithy/util-body-length-browser": "npm:^4.2.0" - "@smithy/util-body-length-node": "npm:^4.2.1" - "@smithy/util-defaults-mode-browser": "npm:^4.3.2" - "@smithy/util-defaults-mode-node": "npm:^4.2.3" - "@smithy/util-endpoints": "npm:^3.2.2" - "@smithy/util-middleware": "npm:^4.2.2" - "@smithy/util-retry": "npm:^4.2.2" - "@smithy/util-utf8": "npm:^4.2.0" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/credential-provider-node": "npm:^3.972.28" + "@aws-sdk/middleware-host-header": "npm:^3.972.8" + "@aws-sdk/middleware-logger": "npm:^3.972.8" + "@aws-sdk/middleware-recursion-detection": "npm:^3.972.9" + "@aws-sdk/middleware-user-agent": "npm:^3.972.27" + "@aws-sdk/region-config-resolver": "npm:^3.972.10" + "@aws-sdk/signature-v4-multi-region": "npm:^3.996.15" + "@aws-sdk/types": "npm:^3.973.6" + "@aws-sdk/util-endpoints": "npm:^3.996.5" + "@aws-sdk/util-user-agent-browser": "npm:^3.972.8" + "@aws-sdk/util-user-agent-node": "npm:^3.973.13" + "@smithy/config-resolver": "npm:^4.4.13" + "@smithy/core": "npm:^3.23.13" + "@smithy/fetch-http-handler": "npm:^5.3.15" + "@smithy/hash-node": "npm:^4.2.12" + "@smithy/invalid-dependency": "npm:^4.2.12" + "@smithy/middleware-content-length": "npm:^4.2.12" + "@smithy/middleware-endpoint": "npm:^4.4.28" + "@smithy/middleware-retry": "npm:^4.4.45" + "@smithy/middleware-serde": "npm:^4.2.16" + "@smithy/middleware-stack": "npm:^4.2.12" + "@smithy/node-config-provider": "npm:^4.3.12" + "@smithy/node-http-handler": "npm:^4.5.1" + "@smithy/protocol-http": "npm:^5.3.12" + "@smithy/smithy-client": "npm:^4.12.8" + "@smithy/types": "npm:^4.13.1" + "@smithy/url-parser": "npm:^4.2.12" + "@smithy/util-base64": "npm:^4.3.2" + "@smithy/util-body-length-browser": "npm:^4.2.2" + "@smithy/util-body-length-node": "npm:^4.2.3" + "@smithy/util-defaults-mode-browser": "npm:^4.3.44" + "@smithy/util-defaults-mode-node": "npm:^4.2.48" + "@smithy/util-endpoints": "npm:^3.3.3" + "@smithy/util-middleware": "npm:^4.2.12" + "@smithy/util-retry": "npm:^4.2.12" + "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/27831e2fe821120a1a252f9bbc00bc8b9669d38cb112e369a1690c2f7aba1bb3e7b8743acd235db1b7cbf62e4d27abced22fa068b3962e1f5313665b33ed0084 + checksum: 10/45a2b87531d51de5b1856eb9d69c4add3704ad12cd4789ccb836cf4ddf1bd6e4c40251f5726a0c5974a30802d2f20825f7a68a106b650cc2f70df1f3426535e8 languageName: node linkType: hard "@aws-sdk/client-sqs@npm:^3.350.0": - version: 3.651.1 - resolution: "@aws-sdk/client-sqs@npm:3.651.1" + version: 3.1020.0 + resolution: "@aws-sdk/client-sqs@npm:3.1020.0" dependencies: "@aws-crypto/sha256-browser": "npm:5.2.0" "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/client-sso-oidc": "npm:3.651.1" - "@aws-sdk/client-sts": "npm:3.651.1" - "@aws-sdk/core": "npm:3.651.1" - "@aws-sdk/credential-provider-node": "npm:3.651.1" - "@aws-sdk/middleware-host-header": "npm:3.649.0" - "@aws-sdk/middleware-logger": "npm:3.649.0" - "@aws-sdk/middleware-recursion-detection": "npm:3.649.0" - "@aws-sdk/middleware-sdk-sqs": "npm:3.649.0" - "@aws-sdk/middleware-user-agent": "npm:3.649.0" - "@aws-sdk/region-config-resolver": "npm:3.649.0" - "@aws-sdk/types": "npm:3.649.0" - "@aws-sdk/util-endpoints": "npm:3.649.0" - "@aws-sdk/util-user-agent-browser": "npm:3.649.0" - "@aws-sdk/util-user-agent-node": "npm:3.649.0" - "@smithy/config-resolver": "npm:^3.0.6" - "@smithy/core": "npm:^2.4.1" - "@smithy/fetch-http-handler": "npm:^3.2.5" - "@smithy/hash-node": "npm:^3.0.4" - "@smithy/invalid-dependency": "npm:^3.0.4" - "@smithy/md5-js": "npm:^3.0.4" - "@smithy/middleware-content-length": "npm:^3.0.6" - "@smithy/middleware-endpoint": "npm:^3.1.1" - "@smithy/middleware-retry": "npm:^3.0.16" - "@smithy/middleware-serde": "npm:^3.0.4" - "@smithy/middleware-stack": "npm:^3.0.4" - "@smithy/node-config-provider": "npm:^3.1.5" - "@smithy/node-http-handler": "npm:^3.2.0" - "@smithy/protocol-http": "npm:^4.1.1" - "@smithy/smithy-client": "npm:^3.3.0" - "@smithy/types": "npm:^3.4.0" - "@smithy/url-parser": "npm:^3.0.4" - "@smithy/util-base64": "npm:^3.0.0" - "@smithy/util-body-length-browser": "npm:^3.0.0" - "@smithy/util-body-length-node": "npm:^3.0.0" - "@smithy/util-defaults-mode-browser": "npm:^3.0.16" - "@smithy/util-defaults-mode-node": "npm:^3.0.16" - "@smithy/util-endpoints": "npm:^2.1.0" - "@smithy/util-middleware": "npm:^3.0.4" - "@smithy/util-retry": "npm:^3.0.4" - "@smithy/util-utf8": "npm:^3.0.0" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/credential-provider-node": "npm:^3.972.28" + "@aws-sdk/middleware-host-header": "npm:^3.972.8" + "@aws-sdk/middleware-logger": "npm:^3.972.8" + "@aws-sdk/middleware-recursion-detection": "npm:^3.972.9" + "@aws-sdk/middleware-sdk-sqs": "npm:^3.972.18" + "@aws-sdk/middleware-user-agent": "npm:^3.972.27" + "@aws-sdk/region-config-resolver": "npm:^3.972.10" + "@aws-sdk/types": "npm:^3.973.6" + "@aws-sdk/util-endpoints": "npm:^3.996.5" + "@aws-sdk/util-user-agent-browser": "npm:^3.972.8" + "@aws-sdk/util-user-agent-node": "npm:^3.973.13" + "@smithy/config-resolver": "npm:^4.4.13" + "@smithy/core": "npm:^3.23.13" + "@smithy/fetch-http-handler": "npm:^5.3.15" + "@smithy/hash-node": "npm:^4.2.12" + "@smithy/invalid-dependency": "npm:^4.2.12" + "@smithy/md5-js": "npm:^4.2.12" + "@smithy/middleware-content-length": "npm:^4.2.12" + "@smithy/middleware-endpoint": "npm:^4.4.28" + "@smithy/middleware-retry": "npm:^4.4.45" + "@smithy/middleware-serde": "npm:^4.2.16" + "@smithy/middleware-stack": "npm:^4.2.12" + "@smithy/node-config-provider": "npm:^4.3.12" + "@smithy/node-http-handler": "npm:^4.5.1" + "@smithy/protocol-http": "npm:^5.3.12" + "@smithy/smithy-client": "npm:^4.12.8" + "@smithy/types": "npm:^4.13.1" + "@smithy/url-parser": "npm:^4.2.12" + "@smithy/util-base64": "npm:^4.3.2" + "@smithy/util-body-length-browser": "npm:^4.2.2" + "@smithy/util-body-length-node": "npm:^4.2.3" + "@smithy/util-defaults-mode-browser": "npm:^4.3.44" + "@smithy/util-defaults-mode-node": "npm:^4.2.48" + "@smithy/util-endpoints": "npm:^3.3.3" + "@smithy/util-middleware": "npm:^4.2.12" + "@smithy/util-retry": "npm:^4.2.12" + "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/3cea5f89bd41f6e77669e63268c99d951a7ee0fbd81f95bda9bdfaa0f9d631d4189a27cfa5b2a75644337458be5e2e301b207f407ad73326c1482c1afb0feff3 + checksum: 10/1e47584b68796aa52a63d4eb1d7333f6d9cb032522d7a157f3b88b1491b3bba457a1061362c9400bfe7ad26bbbe14e145dd737863e29425edd2f4f63643aaf78 languageName: node linkType: hard -"@aws-sdk/client-sso-oidc@npm:3.651.1": - version: 3.651.1 - resolution: "@aws-sdk/client-sso-oidc@npm:3.651.1" +"@aws-sdk/client-sts@npm:^3.350.0": + version: 3.1020.0 + resolution: "@aws-sdk/client-sts@npm:3.1020.0" dependencies: "@aws-crypto/sha256-browser": "npm:5.2.0" "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/core": "npm:3.651.1" - "@aws-sdk/credential-provider-node": "npm:3.651.1" - "@aws-sdk/middleware-host-header": "npm:3.649.0" - "@aws-sdk/middleware-logger": "npm:3.649.0" - "@aws-sdk/middleware-recursion-detection": "npm:3.649.0" - "@aws-sdk/middleware-user-agent": "npm:3.649.0" - "@aws-sdk/region-config-resolver": "npm:3.649.0" - "@aws-sdk/types": "npm:3.649.0" - "@aws-sdk/util-endpoints": "npm:3.649.0" - "@aws-sdk/util-user-agent-browser": "npm:3.649.0" - "@aws-sdk/util-user-agent-node": "npm:3.649.0" - "@smithy/config-resolver": "npm:^3.0.6" - "@smithy/core": "npm:^2.4.1" - "@smithy/fetch-http-handler": "npm:^3.2.5" - "@smithy/hash-node": "npm:^3.0.4" - "@smithy/invalid-dependency": "npm:^3.0.4" - "@smithy/middleware-content-length": "npm:^3.0.6" - "@smithy/middleware-endpoint": "npm:^3.1.1" - "@smithy/middleware-retry": "npm:^3.0.16" - "@smithy/middleware-serde": "npm:^3.0.4" - "@smithy/middleware-stack": "npm:^3.0.4" - "@smithy/node-config-provider": "npm:^3.1.5" - "@smithy/node-http-handler": "npm:^3.2.0" - "@smithy/protocol-http": "npm:^4.1.1" - "@smithy/smithy-client": "npm:^3.3.0" - "@smithy/types": "npm:^3.4.0" - "@smithy/url-parser": "npm:^3.0.4" - "@smithy/util-base64": "npm:^3.0.0" - "@smithy/util-body-length-browser": "npm:^3.0.0" - "@smithy/util-body-length-node": "npm:^3.0.0" - "@smithy/util-defaults-mode-browser": "npm:^3.0.16" - "@smithy/util-defaults-mode-node": "npm:^3.0.16" - "@smithy/util-endpoints": "npm:^2.1.0" - "@smithy/util-middleware": "npm:^3.0.4" - "@smithy/util-retry": "npm:^3.0.4" - "@smithy/util-utf8": "npm:^3.0.0" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/credential-provider-node": "npm:^3.972.28" + "@aws-sdk/middleware-host-header": "npm:^3.972.8" + "@aws-sdk/middleware-logger": "npm:^3.972.8" + "@aws-sdk/middleware-recursion-detection": "npm:^3.972.9" + "@aws-sdk/middleware-user-agent": "npm:^3.972.27" + "@aws-sdk/region-config-resolver": "npm:^3.972.10" + "@aws-sdk/types": "npm:^3.973.6" + "@aws-sdk/util-endpoints": "npm:^3.996.5" + "@aws-sdk/util-user-agent-browser": "npm:^3.972.8" + "@aws-sdk/util-user-agent-node": "npm:^3.973.13" + "@smithy/config-resolver": "npm:^4.4.13" + "@smithy/core": "npm:^3.23.13" + "@smithy/fetch-http-handler": "npm:^5.3.15" + "@smithy/hash-node": "npm:^4.2.12" + "@smithy/invalid-dependency": "npm:^4.2.12" + "@smithy/middleware-content-length": "npm:^4.2.12" + "@smithy/middleware-endpoint": "npm:^4.4.28" + "@smithy/middleware-retry": "npm:^4.4.45" + "@smithy/middleware-serde": "npm:^4.2.16" + "@smithy/middleware-stack": "npm:^4.2.12" + "@smithy/node-config-provider": "npm:^4.3.12" + "@smithy/node-http-handler": "npm:^4.5.1" + "@smithy/protocol-http": "npm:^5.3.12" + "@smithy/smithy-client": "npm:^4.12.8" + "@smithy/types": "npm:^4.13.1" + "@smithy/url-parser": "npm:^4.2.12" + "@smithy/util-base64": "npm:^4.3.2" + "@smithy/util-body-length-browser": "npm:^4.2.2" + "@smithy/util-body-length-node": "npm:^4.2.3" + "@smithy/util-defaults-mode-browser": "npm:^4.3.44" + "@smithy/util-defaults-mode-node": "npm:^4.2.48" + "@smithy/util-endpoints": "npm:^3.3.3" + "@smithy/util-middleware": "npm:^4.2.12" + "@smithy/util-retry": "npm:^4.2.12" + "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - peerDependencies: - "@aws-sdk/client-sts": ^3.651.1 - checksum: 10/6b02f72dfddffd76f111b9af5af1fa2109ea62d1d5f601be806303998f68736a63133869df9b261f115233b41cd5aa36658e234bef627186737a4cce65bbf29d + checksum: 10/6b6033e59d481c9805158dfb982a7a3229b9a08988ccea150d39a2363301aa0427a81273a052f6d6a3422cea6e29fa67fd89b5b0198453c4308c7ec04c63fc5f languageName: node linkType: hard -"@aws-sdk/client-sso@npm:3.651.1": - version: 3.651.1 - resolution: "@aws-sdk/client-sso@npm:3.651.1" +"@aws-sdk/core@npm:^3.973.26": + version: 3.973.26 + resolution: "@aws-sdk/core@npm:3.973.26" dependencies: - "@aws-crypto/sha256-browser": "npm:5.2.0" - "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/core": "npm:3.651.1" - "@aws-sdk/middleware-host-header": "npm:3.649.0" - "@aws-sdk/middleware-logger": "npm:3.649.0" - "@aws-sdk/middleware-recursion-detection": "npm:3.649.0" - "@aws-sdk/middleware-user-agent": "npm:3.649.0" - "@aws-sdk/region-config-resolver": "npm:3.649.0" - "@aws-sdk/types": "npm:3.649.0" - "@aws-sdk/util-endpoints": "npm:3.649.0" - "@aws-sdk/util-user-agent-browser": "npm:3.649.0" - "@aws-sdk/util-user-agent-node": "npm:3.649.0" - "@smithy/config-resolver": "npm:^3.0.6" - "@smithy/core": "npm:^2.4.1" - "@smithy/fetch-http-handler": "npm:^3.2.5" - "@smithy/hash-node": "npm:^3.0.4" - "@smithy/invalid-dependency": "npm:^3.0.4" - "@smithy/middleware-content-length": "npm:^3.0.6" - "@smithy/middleware-endpoint": "npm:^3.1.1" - "@smithy/middleware-retry": "npm:^3.0.16" - "@smithy/middleware-serde": "npm:^3.0.4" - "@smithy/middleware-stack": "npm:^3.0.4" - "@smithy/node-config-provider": "npm:^3.1.5" - "@smithy/node-http-handler": "npm:^3.2.0" - "@smithy/protocol-http": "npm:^4.1.1" - "@smithy/smithy-client": "npm:^3.3.0" - "@smithy/types": "npm:^3.4.0" - "@smithy/url-parser": "npm:^3.0.4" - "@smithy/util-base64": "npm:^3.0.0" - "@smithy/util-body-length-browser": "npm:^3.0.0" - "@smithy/util-body-length-node": "npm:^3.0.0" - "@smithy/util-defaults-mode-browser": "npm:^3.0.16" - "@smithy/util-defaults-mode-node": "npm:^3.0.16" - "@smithy/util-endpoints": "npm:^2.1.0" - "@smithy/util-middleware": "npm:^3.0.4" - "@smithy/util-retry": "npm:^3.0.4" - "@smithy/util-utf8": "npm:^3.0.0" + "@aws-sdk/types": "npm:^3.973.6" + "@aws-sdk/xml-builder": "npm:^3.972.16" + "@smithy/core": "npm:^3.23.13" + "@smithy/node-config-provider": "npm:^4.3.12" + "@smithy/property-provider": "npm:^4.2.12" + "@smithy/protocol-http": "npm:^5.3.12" + "@smithy/signature-v4": "npm:^5.3.12" + "@smithy/smithy-client": "npm:^4.12.8" + "@smithy/types": "npm:^4.13.1" + "@smithy/util-base64": "npm:^4.3.2" + "@smithy/util-middleware": "npm:^4.2.12" + "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/a04173235c46b331a22faa1f91d3bb7660f10ed20cd0e591f85c73e55056b06c91f22ec356515028efa8e2561fa3ce68d66d4a5e4c62ff48a47075965e353b9d + checksum: 10/6760e19f912034cf2e28c6fe9872613a3a32f6f66c5bef7c104fa00d74f3408acf0aa52711eb3f4df2889c0d4ca055be35dd7fd2fdbc0ed057f773bff8cffeba languageName: node linkType: hard -"@aws-sdk/client-sso@npm:3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/client-sso@npm:3.911.0" +"@aws-sdk/crc64-nvme@npm:^3.972.5": + version: 3.972.5 + resolution: "@aws-sdk/crc64-nvme@npm:3.972.5" dependencies: - "@aws-crypto/sha256-browser": "npm:5.2.0" - "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/core": "npm:3.911.0" - "@aws-sdk/middleware-host-header": "npm:3.910.0" - "@aws-sdk/middleware-logger": "npm:3.910.0" - "@aws-sdk/middleware-recursion-detection": "npm:3.910.0" - "@aws-sdk/middleware-user-agent": "npm:3.911.0" - "@aws-sdk/region-config-resolver": "npm:3.910.0" - "@aws-sdk/types": "npm:3.910.0" - "@aws-sdk/util-endpoints": "npm:3.910.0" - "@aws-sdk/util-user-agent-browser": "npm:3.910.0" - "@aws-sdk/util-user-agent-node": "npm:3.911.0" - "@smithy/config-resolver": "npm:^4.3.2" - "@smithy/core": "npm:^3.16.1" - "@smithy/fetch-http-handler": "npm:^5.3.3" - "@smithy/hash-node": "npm:^4.2.2" - "@smithy/invalid-dependency": "npm:^4.2.2" - "@smithy/middleware-content-length": "npm:^4.2.2" - "@smithy/middleware-endpoint": "npm:^4.3.3" - "@smithy/middleware-retry": "npm:^4.4.3" - "@smithy/middleware-serde": "npm:^4.2.2" - "@smithy/middleware-stack": "npm:^4.2.2" - "@smithy/node-config-provider": "npm:^4.3.2" - "@smithy/node-http-handler": "npm:^4.4.1" - "@smithy/protocol-http": "npm:^5.3.2" - "@smithy/smithy-client": "npm:^4.8.1" - "@smithy/types": "npm:^4.7.1" - "@smithy/url-parser": "npm:^4.2.2" - "@smithy/util-base64": "npm:^4.3.0" - "@smithy/util-body-length-browser": "npm:^4.2.0" - "@smithy/util-body-length-node": "npm:^4.2.1" - "@smithy/util-defaults-mode-browser": "npm:^4.3.2" - "@smithy/util-defaults-mode-node": "npm:^4.2.3" - "@smithy/util-endpoints": "npm:^3.2.2" - "@smithy/util-middleware": "npm:^4.2.2" - "@smithy/util-retry": "npm:^4.2.2" - "@smithy/util-utf8": "npm:^4.2.0" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/63c3f054251f9b2d823fa65c3c82887230a62bdf364af8f8b3ea5ad22995ea97058bb8ffbfacf2724c058acea0c437fc8b2bc5f21a098a03a3550df53a02e258 + checksum: 10/20a7ee21638976bcd28704e1e8d9f053fc6aa9c2bc82e42c5c1fbf8a20cd9de7381768081029bbeec9388e7c54127562245c49bda070ad0ec74bc48dbf5d2f10 languageName: node linkType: hard -"@aws-sdk/client-sts@npm:3.651.1, @aws-sdk/client-sts@npm:^3.350.0": - version: 3.651.1 - resolution: "@aws-sdk/client-sts@npm:3.651.1" +"@aws-sdk/credential-provider-cognito-identity@npm:^3.972.21": + version: 3.972.21 + resolution: "@aws-sdk/credential-provider-cognito-identity@npm:3.972.21" dependencies: - "@aws-crypto/sha256-browser": "npm:5.2.0" - "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/client-sso-oidc": "npm:3.651.1" - "@aws-sdk/core": "npm:3.651.1" - "@aws-sdk/credential-provider-node": "npm:3.651.1" - "@aws-sdk/middleware-host-header": "npm:3.649.0" - "@aws-sdk/middleware-logger": "npm:3.649.0" - "@aws-sdk/middleware-recursion-detection": "npm:3.649.0" - "@aws-sdk/middleware-user-agent": "npm:3.649.0" - "@aws-sdk/region-config-resolver": "npm:3.649.0" - "@aws-sdk/types": "npm:3.649.0" - "@aws-sdk/util-endpoints": "npm:3.649.0" - "@aws-sdk/util-user-agent-browser": "npm:3.649.0" - "@aws-sdk/util-user-agent-node": "npm:3.649.0" - "@smithy/config-resolver": "npm:^3.0.6" - "@smithy/core": "npm:^2.4.1" - "@smithy/fetch-http-handler": "npm:^3.2.5" - "@smithy/hash-node": "npm:^3.0.4" - "@smithy/invalid-dependency": "npm:^3.0.4" - "@smithy/middleware-content-length": "npm:^3.0.6" - "@smithy/middleware-endpoint": "npm:^3.1.1" - "@smithy/middleware-retry": "npm:^3.0.16" - "@smithy/middleware-serde": "npm:^3.0.4" - "@smithy/middleware-stack": "npm:^3.0.4" - "@smithy/node-config-provider": "npm:^3.1.5" - "@smithy/node-http-handler": "npm:^3.2.0" - "@smithy/protocol-http": "npm:^4.1.1" - "@smithy/smithy-client": "npm:^3.3.0" - "@smithy/types": "npm:^3.4.0" - "@smithy/url-parser": "npm:^3.0.4" - "@smithy/util-base64": "npm:^3.0.0" - "@smithy/util-body-length-browser": "npm:^3.0.0" - "@smithy/util-body-length-node": "npm:^3.0.0" - "@smithy/util-defaults-mode-browser": "npm:^3.0.16" - "@smithy/util-defaults-mode-node": "npm:^3.0.16" - "@smithy/util-endpoints": "npm:^2.1.0" - "@smithy/util-middleware": "npm:^3.0.4" - "@smithy/util-retry": "npm:^3.0.4" - "@smithy/util-utf8": "npm:^3.0.0" + "@aws-sdk/nested-clients": "npm:^3.996.18" + "@aws-sdk/types": "npm:^3.973.6" + "@smithy/property-provider": "npm:^4.2.12" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/3b8feea406ce54ebed9304fde68fdb1e2cacbf19eea5335214a378a513c3e30d2fdde8262123a90e5ba9935c9c3a6d3f0fcbc02cf0771068c847297a234f4632 + checksum: 10/8ce7358427e39502ad664e549233ee19ff766340894391bfe31fd500af3a6bd63d1d871a6d154c69edac8fb241465161eb9739ce57fb8a7de5689767e0e01878 languageName: node linkType: hard -"@aws-sdk/core@npm:3.651.1": - version: 3.651.1 - resolution: "@aws-sdk/core@npm:3.651.1" +"@aws-sdk/credential-provider-env@npm:^3.972.24": + version: 3.972.24 + resolution: "@aws-sdk/credential-provider-env@npm:3.972.24" dependencies: - "@smithy/core": "npm:^2.4.1" - "@smithy/node-config-provider": "npm:^3.1.5" - "@smithy/property-provider": "npm:^3.1.4" - "@smithy/protocol-http": "npm:^4.1.1" - "@smithy/signature-v4": "npm:^4.1.1" - "@smithy/smithy-client": "npm:^3.3.0" - "@smithy/types": "npm:^3.4.0" - "@smithy/util-middleware": "npm:^3.0.4" - fast-xml-parser: "npm:4.4.1" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/types": "npm:^3.973.6" + "@smithy/property-provider": "npm:^4.2.12" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/d394292b8b36de1784cb05acec676ae590b9786056eaf1fdfc0fe8e82c54c26f9e1f1a83329b6d237a59177d2121f22fb3ee9a22e824bf9881c032f7eaddaa63 + checksum: 10/5ca69d965747507f8af0851eaf66fb2b51eceaecbb36b8ea0b20c0a87693aff33b7378f4a0f18f92e64b685d8bcd52123922bb45f2f5f51b4994cc64f8b210c5 languageName: node linkType: hard -"@aws-sdk/core@npm:3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/core@npm:3.911.0" +"@aws-sdk/credential-provider-http@npm:^3.972.26": + version: 3.972.26 + resolution: "@aws-sdk/credential-provider-http@npm:3.972.26" dependencies: - "@aws-sdk/types": "npm:3.910.0" - "@aws-sdk/xml-builder": "npm:3.911.0" - "@smithy/core": "npm:^3.16.1" - "@smithy/node-config-provider": "npm:^4.3.2" - "@smithy/property-provider": "npm:^4.2.2" - "@smithy/protocol-http": "npm:^5.3.2" - "@smithy/signature-v4": "npm:^5.3.2" - "@smithy/smithy-client": "npm:^4.8.1" - "@smithy/types": "npm:^4.7.1" - "@smithy/util-base64": "npm:^4.3.0" - "@smithy/util-middleware": "npm:^4.2.2" - "@smithy/util-utf8": "npm:^4.2.0" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/types": "npm:^3.973.6" + "@smithy/fetch-http-handler": "npm:^5.3.15" + "@smithy/node-http-handler": "npm:^4.5.1" + "@smithy/property-provider": "npm:^4.2.12" + "@smithy/protocol-http": "npm:^5.3.12" + "@smithy/smithy-client": "npm:^4.12.8" + "@smithy/types": "npm:^4.13.1" + "@smithy/util-stream": "npm:^4.5.21" tslib: "npm:^2.6.2" - checksum: 10/d667250ebc1bf09c0e227f45b9d5c6036312b014f57c75c7f65165febc3f34d2f176a6d6042231eb3f7682f7a537d3c45f101d8c462026e380fa766a95ed70e6 + checksum: 10/d3b5904e168a43c53c32cfe16725c091bf3f41755afe910f50d2bede17f2682bed968ad1923a3dadd17e75ffdd53eda6ef790fb8700fed350066bdcdb160ad24 languageName: node linkType: hard -"@aws-sdk/credential-provider-cognito-identity@npm:3.651.1": - version: 3.651.1 - resolution: "@aws-sdk/credential-provider-cognito-identity@npm:3.651.1" +"@aws-sdk/credential-provider-ini@npm:^3.972.28": + version: 3.972.28 + resolution: "@aws-sdk/credential-provider-ini@npm:3.972.28" dependencies: - "@aws-sdk/client-cognito-identity": "npm:3.651.1" - "@aws-sdk/types": "npm:3.649.0" - "@smithy/property-provider": "npm:^3.1.4" - "@smithy/types": "npm:^3.4.0" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/credential-provider-env": "npm:^3.972.24" + "@aws-sdk/credential-provider-http": "npm:^3.972.26" + "@aws-sdk/credential-provider-login": "npm:^3.972.28" + "@aws-sdk/credential-provider-process": "npm:^3.972.24" + "@aws-sdk/credential-provider-sso": "npm:^3.972.28" + "@aws-sdk/credential-provider-web-identity": "npm:^3.972.28" + "@aws-sdk/nested-clients": "npm:^3.996.18" + "@aws-sdk/types": "npm:^3.973.6" + "@smithy/credential-provider-imds": "npm:^4.2.12" + "@smithy/property-provider": "npm:^4.2.12" + "@smithy/shared-ini-file-loader": "npm:^4.4.7" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/78706b7dcfe715bb108b772718fdf83b00cbdace667db9c3515312274cebd9d523f812e97c6a517b3d3f72866265b6aceeff54d5df510100831e044745be1906 + checksum: 10/4a961738da0487acc4b72de413321e35941550ca85d6b3afa2103a896c093ee530eacbf905a2286c40a3fab387ba62ef0fa05173d49f3aeef2a1fcd403a8b29b languageName: node linkType: hard -"@aws-sdk/credential-provider-env@npm:3.649.0": - version: 3.649.0 - resolution: "@aws-sdk/credential-provider-env@npm:3.649.0" +"@aws-sdk/credential-provider-login@npm:^3.972.28": + version: 3.972.28 + resolution: "@aws-sdk/credential-provider-login@npm:3.972.28" dependencies: - "@aws-sdk/types": "npm:3.649.0" - "@smithy/property-provider": "npm:^3.1.4" - "@smithy/types": "npm:^3.4.0" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/nested-clients": "npm:^3.996.18" + "@aws-sdk/types": "npm:^3.973.6" + "@smithy/property-provider": "npm:^4.2.12" + "@smithy/protocol-http": "npm:^5.3.12" + "@smithy/shared-ini-file-loader": "npm:^4.4.7" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/269f5a839ec1be59bb81c1acad7133597a19c3faa8b04c2687b6e7763145c3329f9c40c390b21da1752f5bc652100ba6164cbcf30a3467c833a2867ef8916ce6 + checksum: 10/075c680e352140fd678434cb29d05dfb5016f1a8e1eda8cfe6b39f0b72c52e374d53f63ccd9e35088d3bd26de9201c20020ea7dfbe2e3afc17cec52c139297b5 languageName: node linkType: hard -"@aws-sdk/credential-provider-env@npm:3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/credential-provider-env@npm:3.911.0" +"@aws-sdk/credential-provider-node@npm:^3.350.0, @aws-sdk/credential-provider-node@npm:^3.972.28, @aws-sdk/credential-provider-node@npm:^3.972.29": + version: 3.972.29 + resolution: "@aws-sdk/credential-provider-node@npm:3.972.29" dependencies: - "@aws-sdk/core": "npm:3.911.0" - "@aws-sdk/types": "npm:3.910.0" - "@smithy/property-provider": "npm:^4.2.2" - "@smithy/types": "npm:^4.7.1" + "@aws-sdk/credential-provider-env": "npm:^3.972.24" + "@aws-sdk/credential-provider-http": "npm:^3.972.26" + "@aws-sdk/credential-provider-ini": "npm:^3.972.28" + "@aws-sdk/credential-provider-process": "npm:^3.972.24" + "@aws-sdk/credential-provider-sso": "npm:^3.972.28" + "@aws-sdk/credential-provider-web-identity": "npm:^3.972.28" + "@aws-sdk/types": "npm:^3.973.6" + "@smithy/credential-provider-imds": "npm:^4.2.12" + "@smithy/property-provider": "npm:^4.2.12" + "@smithy/shared-ini-file-loader": "npm:^4.4.7" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/520736358644fc27a7f6b72ff2f4feafeef4a6a5ef045980f6399fb056b4b96aa0ca4ebced21c28c07aed6e54abd653a3254c56213842a879be6af975b0500f8 + checksum: 10/99e64f9719146104ef4f1484654ceefd7ca821b328f488c305159e4d9b81e9205e5ccafb5f7eb50a2872fcb58bc28131945d8dc71fc4d988009bc5c8c757251b languageName: node linkType: hard -"@aws-sdk/credential-provider-http@npm:3.649.0": - version: 3.649.0 - resolution: "@aws-sdk/credential-provider-http@npm:3.649.0" +"@aws-sdk/credential-provider-process@npm:^3.972.24": + version: 3.972.24 + resolution: "@aws-sdk/credential-provider-process@npm:3.972.24" dependencies: - "@aws-sdk/types": "npm:3.649.0" - "@smithy/fetch-http-handler": "npm:^3.2.5" - "@smithy/node-http-handler": "npm:^3.2.0" - "@smithy/property-provider": "npm:^3.1.4" - "@smithy/protocol-http": "npm:^4.1.1" - "@smithy/smithy-client": "npm:^3.3.0" - "@smithy/types": "npm:^3.4.0" - "@smithy/util-stream": "npm:^3.1.4" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/types": "npm:^3.973.6" + "@smithy/property-provider": "npm:^4.2.12" + "@smithy/shared-ini-file-loader": "npm:^4.4.7" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/d564fc6b2ca84bf51ad980020b63f6aaf08de573ff52de2e608031354cee24fa768549b45d5cb0a9c120158c039cf67aa0f31e97767025565dbcc283b8cbe381 + checksum: 10/aa2cc9ceefd0f717840fd460637710373624bd79ceb981cceff9065615a8538ceefbdaf5803372a590609e14657ad0e158f0d56b5f2c67abfc38df65c5c12432 languageName: node linkType: hard -"@aws-sdk/credential-provider-http@npm:3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/credential-provider-http@npm:3.911.0" +"@aws-sdk/credential-provider-sso@npm:^3.972.28": + version: 3.972.28 + resolution: "@aws-sdk/credential-provider-sso@npm:3.972.28" dependencies: - "@aws-sdk/core": "npm:3.911.0" - "@aws-sdk/types": "npm:3.910.0" - "@smithy/fetch-http-handler": "npm:^5.3.3" - "@smithy/node-http-handler": "npm:^4.4.1" - "@smithy/property-provider": "npm:^4.2.2" - "@smithy/protocol-http": "npm:^5.3.2" - "@smithy/smithy-client": "npm:^4.8.1" - "@smithy/types": "npm:^4.7.1" - "@smithy/util-stream": "npm:^4.5.2" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/nested-clients": "npm:^3.996.18" + "@aws-sdk/token-providers": "npm:3.1021.0" + "@aws-sdk/types": "npm:^3.973.6" + "@smithy/property-provider": "npm:^4.2.12" + "@smithy/shared-ini-file-loader": "npm:^4.4.7" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/2fa71970a2d48873df48dde8aa17e949b0c6e58a3369c16f5f14e2e4193c48bfd6d33ce97c6f4e091d2aafc4ff4c0eb112559286c88749729a17a02bbe479c2d + checksum: 10/440c07e329f5112a9ff637f2b595fbae21001fb0255183e3713630ac38e3de35cfa84beac49d78da4ef9684a9f78a3cbeac13e7f48c7da1df6e1390dbf3bc28b languageName: node linkType: hard -"@aws-sdk/credential-provider-ini@npm:3.651.1": - version: 3.651.1 - resolution: "@aws-sdk/credential-provider-ini@npm:3.651.1" +"@aws-sdk/credential-provider-web-identity@npm:^3.972.28": + version: 3.972.28 + resolution: "@aws-sdk/credential-provider-web-identity@npm:3.972.28" dependencies: - "@aws-sdk/credential-provider-env": "npm:3.649.0" - "@aws-sdk/credential-provider-http": "npm:3.649.0" - "@aws-sdk/credential-provider-process": "npm:3.649.0" - "@aws-sdk/credential-provider-sso": "npm:3.651.1" - "@aws-sdk/credential-provider-web-identity": "npm:3.649.0" - "@aws-sdk/types": "npm:3.649.0" - "@smithy/credential-provider-imds": "npm:^3.2.1" - "@smithy/property-provider": "npm:^3.1.4" - "@smithy/shared-ini-file-loader": "npm:^3.1.5" - "@smithy/types": "npm:^3.4.0" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/nested-clients": "npm:^3.996.18" + "@aws-sdk/types": "npm:^3.973.6" + "@smithy/property-provider": "npm:^4.2.12" + "@smithy/shared-ini-file-loader": "npm:^4.4.7" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - peerDependencies: - "@aws-sdk/client-sts": ^3.651.1 - checksum: 10/4abac498baec245295590e699215530662848324ba45027168b341900ea8cdc9bf74968435b7ced2161cb36fe91d30c99d2409e15a0302f977b4ebacb9b0f98c + checksum: 10/9578e46e5b2a08a0f49b02807f4dfe20e22da01ff9b2c70fa54680efda4106817497c461e2dd2caa7a5226658519ec6c98c808adb33993e61aac5061e3470f28 languageName: node linkType: hard -"@aws-sdk/credential-provider-ini@npm:3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/credential-provider-ini@npm:3.911.0" +"@aws-sdk/credential-providers@npm:3.1022.0, @aws-sdk/credential-providers@npm:^3.350.0": + version: 3.1022.0 + resolution: "@aws-sdk/credential-providers@npm:3.1022.0" dependencies: - "@aws-sdk/core": "npm:3.911.0" - "@aws-sdk/credential-provider-env": "npm:3.911.0" - "@aws-sdk/credential-provider-http": "npm:3.911.0" - "@aws-sdk/credential-provider-process": "npm:3.911.0" - "@aws-sdk/credential-provider-sso": "npm:3.911.0" - "@aws-sdk/credential-provider-web-identity": "npm:3.911.0" - "@aws-sdk/nested-clients": "npm:3.911.0" - "@aws-sdk/types": "npm:3.910.0" - "@smithy/credential-provider-imds": "npm:^4.2.2" - "@smithy/property-provider": "npm:^4.2.2" - "@smithy/shared-ini-file-loader": "npm:^4.3.2" - "@smithy/types": "npm:^4.7.1" + "@aws-sdk/client-cognito-identity": "npm:3.1022.0" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/credential-provider-cognito-identity": "npm:^3.972.21" + "@aws-sdk/credential-provider-env": "npm:^3.972.24" + "@aws-sdk/credential-provider-http": "npm:^3.972.26" + "@aws-sdk/credential-provider-ini": "npm:^3.972.28" + "@aws-sdk/credential-provider-login": "npm:^3.972.28" + "@aws-sdk/credential-provider-node": "npm:^3.972.29" + "@aws-sdk/credential-provider-process": "npm:^3.972.24" + "@aws-sdk/credential-provider-sso": "npm:^3.972.28" + "@aws-sdk/credential-provider-web-identity": "npm:^3.972.28" + "@aws-sdk/nested-clients": "npm:^3.996.18" + "@aws-sdk/types": "npm:^3.973.6" + "@smithy/config-resolver": "npm:^4.4.13" + "@smithy/core": "npm:^3.23.13" + "@smithy/credential-provider-imds": "npm:^4.2.12" + "@smithy/node-config-provider": "npm:^4.3.12" + "@smithy/property-provider": "npm:^4.2.12" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/8cd3f3747e149df49842a22e98ef952cdf730e05be10cd48f9bd21932d009d80d30587c880819174c69350bc7f2ef1929df5bebca76fb5197bb9fcfa7b3f5eb3 - languageName: node - linkType: hard - -"@aws-sdk/credential-provider-node@npm:3.651.1": - version: 3.651.1 - resolution: "@aws-sdk/credential-provider-node@npm:3.651.1" - dependencies: - "@aws-sdk/credential-provider-env": "npm:3.649.0" - "@aws-sdk/credential-provider-http": "npm:3.649.0" - "@aws-sdk/credential-provider-ini": "npm:3.651.1" - "@aws-sdk/credential-provider-process": "npm:3.649.0" - "@aws-sdk/credential-provider-sso": "npm:3.651.1" - "@aws-sdk/credential-provider-web-identity": "npm:3.649.0" - "@aws-sdk/types": "npm:3.649.0" - "@smithy/credential-provider-imds": "npm:^3.2.1" - "@smithy/property-provider": "npm:^3.1.4" - "@smithy/shared-ini-file-loader": "npm:^3.1.5" - "@smithy/types": "npm:^3.4.0" - tslib: "npm:^2.6.2" - checksum: 10/1edcf7b2f7fe4efd0f6bd784d30bb32e3da7e04e1cecb62a43f75fb56a1f398c9155ecc58fc03e43b4f1cb45a3f23603d0b8f898ce2f994977a1481a1e806832 - languageName: node - linkType: hard - -"@aws-sdk/credential-provider-node@npm:3.911.0, @aws-sdk/credential-provider-node@npm:^3.350.0": - version: 3.911.0 - resolution: "@aws-sdk/credential-provider-node@npm:3.911.0" - dependencies: - "@aws-sdk/credential-provider-env": "npm:3.911.0" - "@aws-sdk/credential-provider-http": "npm:3.911.0" - "@aws-sdk/credential-provider-ini": "npm:3.911.0" - "@aws-sdk/credential-provider-process": "npm:3.911.0" - "@aws-sdk/credential-provider-sso": "npm:3.911.0" - "@aws-sdk/credential-provider-web-identity": "npm:3.911.0" - "@aws-sdk/types": "npm:3.910.0" - "@smithy/credential-provider-imds": "npm:^4.2.2" - "@smithy/property-provider": "npm:^4.2.2" - "@smithy/shared-ini-file-loader": "npm:^4.3.2" - "@smithy/types": "npm:^4.7.1" - tslib: "npm:^2.6.2" - checksum: 10/fafa8408c97e1dd2262593be52de530d8718a4bf4f95b1efbde3f5b8b81a5e96f37bbccafa5216823228d0176665746f9d1810146b1e1f42b766d88ca5a0a083 - languageName: node - linkType: hard - -"@aws-sdk/credential-provider-process@npm:3.649.0": - version: 3.649.0 - resolution: "@aws-sdk/credential-provider-process@npm:3.649.0" - dependencies: - "@aws-sdk/types": "npm:3.649.0" - "@smithy/property-provider": "npm:^3.1.4" - "@smithy/shared-ini-file-loader": "npm:^3.1.5" - "@smithy/types": "npm:^3.4.0" - tslib: "npm:^2.6.2" - checksum: 10/15e46d147ca3da269ea6e8490d7e13206ce8346f862c5601a375ce933c379804aaeab2c7a3409a21586c96309fd9c7ca72bbc6cb65b07c1ad0a805dde6460ed5 - languageName: node - linkType: hard - -"@aws-sdk/credential-provider-process@npm:3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/credential-provider-process@npm:3.911.0" - dependencies: - "@aws-sdk/core": "npm:3.911.0" - "@aws-sdk/types": "npm:3.910.0" - "@smithy/property-provider": "npm:^4.2.2" - "@smithy/shared-ini-file-loader": "npm:^4.3.2" - "@smithy/types": "npm:^4.7.1" - tslib: "npm:^2.6.2" - checksum: 10/73707a2b54f7d4e90a4080ac2cce03aa3738a8684ed0851b874be304839c90b1d8c2167d2eb4d63015e9d79cd19f94f682f3e31f8ecb5c361fd228ebf7f1c726 - languageName: node - linkType: hard - -"@aws-sdk/credential-provider-sso@npm:3.651.1": - version: 3.651.1 - resolution: "@aws-sdk/credential-provider-sso@npm:3.651.1" - dependencies: - "@aws-sdk/client-sso": "npm:3.651.1" - "@aws-sdk/token-providers": "npm:3.649.0" - "@aws-sdk/types": "npm:3.649.0" - "@smithy/property-provider": "npm:^3.1.4" - "@smithy/shared-ini-file-loader": "npm:^3.1.5" - "@smithy/types": "npm:^3.4.0" - tslib: "npm:^2.6.2" - checksum: 10/bfc56118499549eac2fb6860205d52cc278a7c905b57516d90918e10a6374178db4452a4924af6b8e079ae6fa5ebbbc29668b3c50f6af24e3911e86bc3d26d0b - languageName: node - linkType: hard - -"@aws-sdk/credential-provider-sso@npm:3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/credential-provider-sso@npm:3.911.0" - dependencies: - "@aws-sdk/client-sso": "npm:3.911.0" - "@aws-sdk/core": "npm:3.911.0" - "@aws-sdk/token-providers": "npm:3.911.0" - "@aws-sdk/types": "npm:3.910.0" - "@smithy/property-provider": "npm:^4.2.2" - "@smithy/shared-ini-file-loader": "npm:^4.3.2" - "@smithy/types": "npm:^4.7.1" - tslib: "npm:^2.6.2" - checksum: 10/80d626057b992cdf891fc8eb3365fe45d0071e4024273a95a9699aaeda05751fc492926c230c8c6e99d6bf8dbf4e08a977e5e668326059d2aa936bf1246c1c23 - languageName: node - linkType: hard - -"@aws-sdk/credential-provider-web-identity@npm:3.649.0": - version: 3.649.0 - resolution: "@aws-sdk/credential-provider-web-identity@npm:3.649.0" - dependencies: - "@aws-sdk/types": "npm:3.649.0" - "@smithy/property-provider": "npm:^3.1.4" - "@smithy/types": "npm:^3.4.0" - tslib: "npm:^2.6.2" - peerDependencies: - "@aws-sdk/client-sts": ^3.649.0 - checksum: 10/cf6c0c92a701dafb39215f4c5e811b26aa71b676d1d5df0a32007eb8047acdeb965e54733418286039e096e27aabad82495ad8eade4f7584a2f70933262cc497 - languageName: node - linkType: hard - -"@aws-sdk/credential-provider-web-identity@npm:3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/credential-provider-web-identity@npm:3.911.0" - dependencies: - "@aws-sdk/core": "npm:3.911.0" - "@aws-sdk/nested-clients": "npm:3.911.0" - "@aws-sdk/types": "npm:3.910.0" - "@smithy/property-provider": "npm:^4.2.2" - "@smithy/shared-ini-file-loader": "npm:^4.3.2" - "@smithy/types": "npm:^4.7.1" - tslib: "npm:^2.6.2" - checksum: 10/1a67f0799bd44f0cfaf0260c41c8b08009d6df80bf3de1028d67e808e003d11ea474f47d4a5816f147a413b6e29f5e1b28e2f4567b6f7f16ab2cdf196b731895 - languageName: node - linkType: hard - -"@aws-sdk/credential-providers@npm:^3.350.0": - version: 3.651.1 - resolution: "@aws-sdk/credential-providers@npm:3.651.1" - dependencies: - "@aws-sdk/client-cognito-identity": "npm:3.651.1" - "@aws-sdk/client-sso": "npm:3.651.1" - "@aws-sdk/client-sts": "npm:3.651.1" - "@aws-sdk/credential-provider-cognito-identity": "npm:3.651.1" - "@aws-sdk/credential-provider-env": "npm:3.649.0" - "@aws-sdk/credential-provider-http": "npm:3.649.0" - "@aws-sdk/credential-provider-ini": "npm:3.651.1" - "@aws-sdk/credential-provider-node": "npm:3.651.1" - "@aws-sdk/credential-provider-process": "npm:3.649.0" - "@aws-sdk/credential-provider-sso": "npm:3.651.1" - "@aws-sdk/credential-provider-web-identity": "npm:3.649.0" - "@aws-sdk/types": "npm:3.649.0" - "@smithy/credential-provider-imds": "npm:^3.2.1" - "@smithy/property-provider": "npm:^3.1.4" - "@smithy/types": "npm:^3.4.0" - tslib: "npm:^2.6.2" - checksum: 10/ed23f905dfb8b4c78ad4b6a8871e4e9e3b63be2b3c3304e9fabdbc2ac8edf9393a6c73f14c80fcbd51b27e4c09cfbf40626c87ccf333fb7966184613b6e32561 - languageName: node - linkType: hard - -"@aws-sdk/eventstream-codec@npm:3.370.0": - version: 3.370.0 - resolution: "@aws-sdk/eventstream-codec@npm:3.370.0" - dependencies: - "@aws-crypto/crc32": "npm:3.0.0" - "@aws-sdk/types": "npm:3.370.0" - "@aws-sdk/util-hex-encoding": "npm:3.310.0" - tslib: "npm:^2.5.0" - checksum: 10/9ded697e5922972734899f2ac81941a5d1686de55eecadaaf36411d099f94708556ffc5a4adf9591299875b9f604dd4dea1846df9a94bdd0acc85f2d9e3f7a98 + checksum: 10/387292596f496320e8e74264acadf242eb3c6261caf4c8b5f94c9a15c6b8c0b9040e252e2fe7a1fa24b7cd2a68c8b8e9242fad00ed61e333b33d8003bf89c293 languageName: node linkType: hard @@ -1349,34 +1103,35 @@ __metadata: linkType: hard "@aws-sdk/lib-storage@npm:^3.350.0": - version: 3.651.1 - resolution: "@aws-sdk/lib-storage@npm:3.651.1" + version: 3.1020.0 + resolution: "@aws-sdk/lib-storage@npm:3.1020.0" dependencies: - "@smithy/abort-controller": "npm:^3.1.2" - "@smithy/middleware-endpoint": "npm:^3.1.1" - "@smithy/smithy-client": "npm:^3.3.0" + "@smithy/middleware-endpoint": "npm:^4.4.28" + "@smithy/protocol-http": "npm:^5.3.12" + "@smithy/smithy-client": "npm:^4.12.8" + "@smithy/types": "npm:^4.13.1" buffer: "npm:5.6.0" events: "npm:3.3.0" stream-browserify: "npm:3.0.0" tslib: "npm:^2.6.2" peerDependencies: - "@aws-sdk/client-s3": ^3.651.1 - checksum: 10/935dc8968acff6e125d0cf4dd2e38e7da1e75c6e6e1b314891bcacddb752c8a60f271c153c73229925c44f7d48d4c8fbf4bdda3546e869849658a5766a1f3c2b + "@aws-sdk/client-s3": ^3.1020.0 + checksum: 10/79073880519b2fde7bb1307d2d62b98b0b5fe3db81c3d8b2463c280fab957f52cae679e8442187af368314577461ceb81a577c9843a0e50b7c4da7e3b4df382d languageName: node linkType: hard -"@aws-sdk/middleware-bucket-endpoint@npm:3.649.0": - version: 3.649.0 - resolution: "@aws-sdk/middleware-bucket-endpoint@npm:3.649.0" +"@aws-sdk/middleware-bucket-endpoint@npm:^3.972.8": + version: 3.972.8 + resolution: "@aws-sdk/middleware-bucket-endpoint@npm:3.972.8" dependencies: - "@aws-sdk/types": "npm:3.649.0" - "@aws-sdk/util-arn-parser": "npm:3.568.0" - "@smithy/node-config-provider": "npm:^3.1.5" - "@smithy/protocol-http": "npm:^4.1.1" - "@smithy/types": "npm:^3.4.0" - "@smithy/util-config-provider": "npm:^3.0.0" + "@aws-sdk/types": "npm:^3.973.6" + "@aws-sdk/util-arn-parser": "npm:^3.972.3" + "@smithy/node-config-provider": "npm:^4.3.12" + "@smithy/protocol-http": "npm:^5.3.12" + "@smithy/types": "npm:^4.13.1" + "@smithy/util-config-provider": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/6d3bd04068a1b9dc77f2714895b9226f16dd1e37d9fc8f227c387b296a6743e3b2430a9d4b95ff9651a885e49ec86d3d745666bc2ba714f65cad0536b46a3290 + checksum: 10/d02078c46b63f97d5798156e9b07bc2bb60a4b7aadf2936605fdde6e75f7c4dc3a0de03af0033b374d64eedef257c689131758ec29a8107829e87712add49129 languageName: node linkType: hard @@ -1393,173 +1148,120 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/middleware-expect-continue@npm:3.649.0": - version: 3.649.0 - resolution: "@aws-sdk/middleware-expect-continue@npm:3.649.0" +"@aws-sdk/middleware-expect-continue@npm:^3.972.8": + version: 3.972.8 + resolution: "@aws-sdk/middleware-expect-continue@npm:3.972.8" dependencies: - "@aws-sdk/types": "npm:3.649.0" - "@smithy/protocol-http": "npm:^4.1.1" - "@smithy/types": "npm:^3.4.0" + "@aws-sdk/types": "npm:^3.973.6" + "@smithy/protocol-http": "npm:^5.3.12" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/96e390d4d18aa9cafdac99bd73c606b542bac5b5180c27a6ac09cce55d407bd005626d8b9a638b49f8506af20746b0509493ba78e6168139970190a63810517f + checksum: 10/1e440da2d8c4e77703f0b55fa49d121f064e3910ceb502719ee734a9899b8ff6750daf98795034d8b903fb66371964666c3c6b39b9822085f6e87fb708cec35a languageName: node linkType: hard -"@aws-sdk/middleware-flexible-checksums@npm:3.651.1": - version: 3.651.1 - resolution: "@aws-sdk/middleware-flexible-checksums@npm:3.651.1" +"@aws-sdk/middleware-flexible-checksums@npm:^3.974.6": + version: 3.974.6 + resolution: "@aws-sdk/middleware-flexible-checksums@npm:3.974.6" dependencies: "@aws-crypto/crc32": "npm:5.2.0" "@aws-crypto/crc32c": "npm:5.2.0" - "@aws-sdk/types": "npm:3.649.0" - "@smithy/is-array-buffer": "npm:^3.0.0" - "@smithy/node-config-provider": "npm:^3.1.5" - "@smithy/protocol-http": "npm:^4.1.1" - "@smithy/types": "npm:^3.4.0" - "@smithy/util-middleware": "npm:^3.0.4" - "@smithy/util-utf8": "npm:^3.0.0" + "@aws-crypto/util": "npm:5.2.0" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/crc64-nvme": "npm:^3.972.5" + "@aws-sdk/types": "npm:^3.973.6" + "@smithy/is-array-buffer": "npm:^4.2.2" + "@smithy/node-config-provider": "npm:^4.3.12" + "@smithy/protocol-http": "npm:^5.3.12" + "@smithy/types": "npm:^4.13.1" + "@smithy/util-middleware": "npm:^4.2.12" + "@smithy/util-stream": "npm:^4.5.21" + "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/06ee0c3110472eefba88aaf37bcfe532b3aed3467bd4dbca305fc372100897ff9ecc60055e6e51fb87ec778dce0ce584a8af3ba1c2c8ccb9dc0e634929181522 + checksum: 10/3f4d36d7793b39e52b394079548c7691273512dc8558cab0dac63a4fc3e75afad8390bdd7a35365fc2e82cddaa4afb269b57ca1e46b27c4c3f3a2f0cfed4674a languageName: node linkType: hard -"@aws-sdk/middleware-host-header@npm:3.649.0": - version: 3.649.0 - resolution: "@aws-sdk/middleware-host-header@npm:3.649.0" +"@aws-sdk/middleware-host-header@npm:^3.972.8": + version: 3.972.8 + resolution: "@aws-sdk/middleware-host-header@npm:3.972.8" dependencies: - "@aws-sdk/types": "npm:3.649.0" - "@smithy/protocol-http": "npm:^4.1.1" - "@smithy/types": "npm:^3.4.0" + "@aws-sdk/types": "npm:^3.973.6" + "@smithy/protocol-http": "npm:^5.3.12" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/6b0c14c6d250e8ecb9bab57d194ff12c5ff3e95db4295c02d03f5fa62db49288110773b2b9f3ed9ffcd97809160be391553a4aefe8706616bc4ff26629536d70 + checksum: 10/1af0015aa4e1f243d20054501148c27f62af974cc92735a38723ed909396bc98f7cddbf76a1237c8e08b46ab4b54cbf7f47c239263002a441aad4807feb41750 languageName: node linkType: hard -"@aws-sdk/middleware-host-header@npm:3.910.0": - version: 3.910.0 - resolution: "@aws-sdk/middleware-host-header@npm:3.910.0" +"@aws-sdk/middleware-location-constraint@npm:^3.972.8": + version: 3.972.8 + resolution: "@aws-sdk/middleware-location-constraint@npm:3.972.8" dependencies: - "@aws-sdk/types": "npm:3.910.0" - "@smithy/protocol-http": "npm:^5.3.2" - "@smithy/types": "npm:^4.7.1" + "@aws-sdk/types": "npm:^3.973.6" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/456442bc5ab2b632562203e5f0185da4c9b20129ba7caf8371b021f1f47ac83a06b8726791d9ac350ba60531c9eaff6e6783a4e5be1a5c44fe6a59e766937c84 + checksum: 10/a56483b2af2687c348220ad89e479dae485dd856be44cf0d3a3db23aa974a2674ec03b7953fa6b1b5d2865da94c3d3d4d339773cfa38f543e065bd45afe896f5 languageName: node linkType: hard -"@aws-sdk/middleware-location-constraint@npm:3.649.0": - version: 3.649.0 - resolution: "@aws-sdk/middleware-location-constraint@npm:3.649.0" +"@aws-sdk/middleware-logger@npm:^3.972.8": + version: 3.972.8 + resolution: "@aws-sdk/middleware-logger@npm:3.972.8" dependencies: - "@aws-sdk/types": "npm:3.649.0" - "@smithy/types": "npm:^3.4.0" + "@aws-sdk/types": "npm:^3.973.6" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/08be9ddccaf6d6cc0baa66f5d822d688388445a7a4840449f480512251239b53a38267ed1b41ef25583e4b0c363f6c1e3a2c6df797d6ca3fe4bcd3bc28bea680 + checksum: 10/e6f56fb83fd5bdbe6d354648596ce3ffff5293c87a48d06365e1a9fd41bdc6da4f53240908bc7d3b69e735905506ef89078a5812d1252e7f46bcb82255e5999b languageName: node linkType: hard -"@aws-sdk/middleware-logger@npm:3.649.0": - version: 3.649.0 - resolution: "@aws-sdk/middleware-logger@npm:3.649.0" +"@aws-sdk/middleware-recursion-detection@npm:^3.972.9": + version: 3.972.9 + resolution: "@aws-sdk/middleware-recursion-detection@npm:3.972.9" dependencies: - "@aws-sdk/types": "npm:3.649.0" - "@smithy/types": "npm:^3.4.0" + "@aws-sdk/types": "npm:^3.973.6" + "@aws/lambda-invoke-store": "npm:^0.2.2" + "@smithy/protocol-http": "npm:^5.3.12" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/3c1909eb49460e4c0f92de78ca7fdacc7d9f56a5ece0bcce63fce3da18620368d1f6d72106e928877af603e1c438acf914681fe44637481bcfb855c28c13ebc1 + checksum: 10/86eea869b5b157b4f411013c4dc138fb72e43ff9a10fe83890d0fe0a3ecc7f63af415b8af02f11543233ade78fdeecab271f78fff6b0bd58b29d3d2e6053e1d4 languageName: node linkType: hard -"@aws-sdk/middleware-logger@npm:3.910.0": - version: 3.910.0 - resolution: "@aws-sdk/middleware-logger@npm:3.910.0" +"@aws-sdk/middleware-sdk-s3@npm:^3.972.27": + version: 3.972.27 + resolution: "@aws-sdk/middleware-sdk-s3@npm:3.972.27" dependencies: - "@aws-sdk/types": "npm:3.910.0" - "@smithy/types": "npm:^4.7.1" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/types": "npm:^3.973.6" + "@aws-sdk/util-arn-parser": "npm:^3.972.3" + "@smithy/core": "npm:^3.23.13" + "@smithy/node-config-provider": "npm:^4.3.12" + "@smithy/protocol-http": "npm:^5.3.12" + "@smithy/signature-v4": "npm:^5.3.12" + "@smithy/smithy-client": "npm:^4.12.8" + "@smithy/types": "npm:^4.13.1" + "@smithy/util-config-provider": "npm:^4.2.2" + "@smithy/util-middleware": "npm:^4.2.12" + "@smithy/util-stream": "npm:^4.5.21" + "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/6153032647a6c5f79d4ef822a52079a6e56f56923d42d8f73b377ad4188df5078424fe21a88706ad27fe30906c602a1fcc68d91955f08ef4f55617bbb41d49e7 + checksum: 10/7163eb04379b2dbafe84adfabb3974b03dcd8af807341e03f49ed7e6d57c5307002492c833ba67697ca04731f0d3a399b2262435bbaf6f75f3f91b7e4d424a0d languageName: node linkType: hard -"@aws-sdk/middleware-recursion-detection@npm:3.649.0": - version: 3.649.0 - resolution: "@aws-sdk/middleware-recursion-detection@npm:3.649.0" +"@aws-sdk/middleware-sdk-sqs@npm:^3.972.18": + version: 3.972.18 + resolution: "@aws-sdk/middleware-sdk-sqs@npm:3.972.18" dependencies: - "@aws-sdk/types": "npm:3.649.0" - "@smithy/protocol-http": "npm:^4.1.1" - "@smithy/types": "npm:^3.4.0" + "@aws-sdk/types": "npm:^3.973.6" + "@smithy/smithy-client": "npm:^4.12.8" + "@smithy/types": "npm:^4.13.1" + "@smithy/util-hex-encoding": "npm:^4.2.2" + "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/20d593eedc5ddb0ebe778a05e056a152101e737fed7918889490fce4fae3b636ac0e7fe2ebd7ae70e2e7f6dfebd292072d7e46514050e49d682e43cf069e97b1 - languageName: node - linkType: hard - -"@aws-sdk/middleware-recursion-detection@npm:3.910.0": - version: 3.910.0 - resolution: "@aws-sdk/middleware-recursion-detection@npm:3.910.0" - dependencies: - "@aws-sdk/types": "npm:3.910.0" - "@aws/lambda-invoke-store": "npm:^0.0.1" - "@smithy/protocol-http": "npm:^5.3.2" - "@smithy/types": "npm:^4.7.1" - tslib: "npm:^2.6.2" - checksum: 10/3d727f874667baf1b8076690a17c658bc2260330e23d5fe95bdaa060b235becf1202fb7f3ca277db3530f36e136fbde37dbe2ec78d0a5191d9c5291652a7ae91 - languageName: node - linkType: hard - -"@aws-sdk/middleware-sdk-s3@npm:3.651.1": - version: 3.651.1 - resolution: "@aws-sdk/middleware-sdk-s3@npm:3.651.1" - dependencies: - "@aws-sdk/core": "npm:3.651.1" - "@aws-sdk/types": "npm:3.649.0" - "@aws-sdk/util-arn-parser": "npm:3.568.0" - "@smithy/core": "npm:^2.4.1" - "@smithy/node-config-provider": "npm:^3.1.5" - "@smithy/protocol-http": "npm:^4.1.1" - "@smithy/signature-v4": "npm:^4.1.1" - "@smithy/smithy-client": "npm:^3.3.0" - "@smithy/types": "npm:^3.4.0" - "@smithy/util-config-provider": "npm:^3.0.0" - "@smithy/util-middleware": "npm:^3.0.4" - "@smithy/util-stream": "npm:^3.1.4" - "@smithy/util-utf8": "npm:^3.0.0" - tslib: "npm:^2.6.2" - checksum: 10/d7da62619b2ec412fdde5071944d912da8291fb0cdf5d3ab4253f1601e59c5c9c1c19aa5b68a8599c38dedf0f5f3e295b63619d196d9d74fba76ce70aa7b69ec - languageName: node - linkType: hard - -"@aws-sdk/middleware-sdk-s3@npm:3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/middleware-sdk-s3@npm:3.911.0" - dependencies: - "@aws-sdk/core": "npm:3.911.0" - "@aws-sdk/types": "npm:3.910.0" - "@aws-sdk/util-arn-parser": "npm:3.893.0" - "@smithy/core": "npm:^3.16.1" - "@smithy/node-config-provider": "npm:^4.3.2" - "@smithy/protocol-http": "npm:^5.3.2" - "@smithy/signature-v4": "npm:^5.3.2" - "@smithy/smithy-client": "npm:^4.8.1" - "@smithy/types": "npm:^4.7.1" - "@smithy/util-config-provider": "npm:^4.2.0" - "@smithy/util-middleware": "npm:^4.2.2" - "@smithy/util-stream": "npm:^4.5.2" - "@smithy/util-utf8": "npm:^4.2.0" - tslib: "npm:^2.6.2" - checksum: 10/5b154c87e0bbb34fb8d137a0e613cb0b548150f181a44e316e140a3178facdf98cc5d7823c91999a0f26be27db12b23bddbdf380f7c0eae71772749944f1d548 - languageName: node - linkType: hard - -"@aws-sdk/middleware-sdk-sqs@npm:3.649.0": - version: 3.649.0 - resolution: "@aws-sdk/middleware-sdk-sqs@npm:3.649.0" - dependencies: - "@aws-sdk/types": "npm:3.649.0" - "@smithy/smithy-client": "npm:^3.3.0" - "@smithy/types": "npm:^3.4.0" - "@smithy/util-hex-encoding": "npm:^3.0.0" - "@smithy/util-utf8": "npm:^3.0.0" - tslib: "npm:^2.6.2" - checksum: 10/49ce831e3ec72f5330ddd7a95333d76c8814e7fd9bc3bf28d25178bb577df7e9790ab7909d763ca34655ba7154f29abfc4eb035491da61636b10075bd0b9e958 + checksum: 10/8e8dc79ed7c6213a157410f24ddc0349987be44b22f26c81a11d5c0eb37a86b18a50f030289b1656ba37ff797bb625321db6a1e050d8a8225f2385c3c06dabf5 languageName: node linkType: hard @@ -1573,88 +1275,76 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/middleware-ssec@npm:3.649.0": - version: 3.649.0 - resolution: "@aws-sdk/middleware-ssec@npm:3.649.0" +"@aws-sdk/middleware-ssec@npm:^3.972.8": + version: 3.972.8 + resolution: "@aws-sdk/middleware-ssec@npm:3.972.8" dependencies: - "@aws-sdk/types": "npm:3.649.0" - "@smithy/types": "npm:^3.4.0" + "@aws-sdk/types": "npm:^3.973.6" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/1a9292038d2ae005528cd2c633d083910d575c3055c8b530746c239664074b0775cb705a5b5800de19d4ed4b8af333c7f6d91eb718e93715552997c4386b0403 + checksum: 10/3e36f23ade6adbb54d91731d17d373781142e0e0c12361ffd049a7d20bc3947e81f18da8780680e587a9d0b485ffe499686555c4d1e97f016147afac3dad9c9d languageName: node linkType: hard -"@aws-sdk/middleware-user-agent@npm:3.649.0": - version: 3.649.0 - resolution: "@aws-sdk/middleware-user-agent@npm:3.649.0" +"@aws-sdk/middleware-user-agent@npm:^3.972.27, @aws-sdk/middleware-user-agent@npm:^3.972.28": + version: 3.972.28 + resolution: "@aws-sdk/middleware-user-agent@npm:3.972.28" dependencies: - "@aws-sdk/types": "npm:3.649.0" - "@aws-sdk/util-endpoints": "npm:3.649.0" - "@smithy/protocol-http": "npm:^4.1.1" - "@smithy/types": "npm:^3.4.0" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/types": "npm:^3.973.6" + "@aws-sdk/util-endpoints": "npm:^3.996.5" + "@smithy/core": "npm:^3.23.13" + "@smithy/protocol-http": "npm:^5.3.12" + "@smithy/types": "npm:^4.13.1" + "@smithy/util-retry": "npm:^4.2.13" tslib: "npm:^2.6.2" - checksum: 10/4e5c428c7869729850b1f7050071b82ac38f144c61f5580d6ee241b5ad87266df9001eb335e19802d5c318e23db0c5ee40fb5c00be14855e1f141f2bb22c6ef4 + checksum: 10/1f5668ea362c98cbfe2ec17d71a47792b8f78c1d5ba77b2a9ef10614528b8f704fb11df74fdf6d9199799341171eed6b7710b56ac927df43343c06df15dd2e74 languageName: node linkType: hard -"@aws-sdk/middleware-user-agent@npm:3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/middleware-user-agent@npm:3.911.0" - dependencies: - "@aws-sdk/core": "npm:3.911.0" - "@aws-sdk/types": "npm:3.910.0" - "@aws-sdk/util-endpoints": "npm:3.910.0" - "@smithy/core": "npm:^3.16.1" - "@smithy/protocol-http": "npm:^5.3.2" - "@smithy/types": "npm:^4.7.1" - tslib: "npm:^2.6.2" - checksum: 10/c6f2ea464bdc2cb084243ec0c4edf39e55e35ae47ac1c6baa7349f80c87ad8efda1b84587be4c75f21347e52229e82d41c3ac0ae48aaa6a71555f1c83bbdec1c - languageName: node - linkType: hard - -"@aws-sdk/nested-clients@npm:3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/nested-clients@npm:3.911.0" +"@aws-sdk/nested-clients@npm:^3.996.18": + version: 3.996.18 + resolution: "@aws-sdk/nested-clients@npm:3.996.18" dependencies: "@aws-crypto/sha256-browser": "npm:5.2.0" "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/core": "npm:3.911.0" - "@aws-sdk/middleware-host-header": "npm:3.910.0" - "@aws-sdk/middleware-logger": "npm:3.910.0" - "@aws-sdk/middleware-recursion-detection": "npm:3.910.0" - "@aws-sdk/middleware-user-agent": "npm:3.911.0" - "@aws-sdk/region-config-resolver": "npm:3.910.0" - "@aws-sdk/types": "npm:3.910.0" - "@aws-sdk/util-endpoints": "npm:3.910.0" - "@aws-sdk/util-user-agent-browser": "npm:3.910.0" - "@aws-sdk/util-user-agent-node": "npm:3.911.0" - "@smithy/config-resolver": "npm:^4.3.2" - "@smithy/core": "npm:^3.16.1" - "@smithy/fetch-http-handler": "npm:^5.3.3" - "@smithy/hash-node": "npm:^4.2.2" - "@smithy/invalid-dependency": "npm:^4.2.2" - "@smithy/middleware-content-length": "npm:^4.2.2" - "@smithy/middleware-endpoint": "npm:^4.3.3" - "@smithy/middleware-retry": "npm:^4.4.3" - "@smithy/middleware-serde": "npm:^4.2.2" - "@smithy/middleware-stack": "npm:^4.2.2" - "@smithy/node-config-provider": "npm:^4.3.2" - "@smithy/node-http-handler": "npm:^4.4.1" - "@smithy/protocol-http": "npm:^5.3.2" - "@smithy/smithy-client": "npm:^4.8.1" - "@smithy/types": "npm:^4.7.1" - "@smithy/url-parser": "npm:^4.2.2" - "@smithy/util-base64": "npm:^4.3.0" - "@smithy/util-body-length-browser": "npm:^4.2.0" - "@smithy/util-body-length-node": "npm:^4.2.1" - "@smithy/util-defaults-mode-browser": "npm:^4.3.2" - "@smithy/util-defaults-mode-node": "npm:^4.2.3" - "@smithy/util-endpoints": "npm:^3.2.2" - "@smithy/util-middleware": "npm:^4.2.2" - "@smithy/util-retry": "npm:^4.2.2" - "@smithy/util-utf8": "npm:^4.2.0" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/middleware-host-header": "npm:^3.972.8" + "@aws-sdk/middleware-logger": "npm:^3.972.8" + "@aws-sdk/middleware-recursion-detection": "npm:^3.972.9" + "@aws-sdk/middleware-user-agent": "npm:^3.972.28" + "@aws-sdk/region-config-resolver": "npm:^3.972.10" + "@aws-sdk/types": "npm:^3.973.6" + "@aws-sdk/util-endpoints": "npm:^3.996.5" + "@aws-sdk/util-user-agent-browser": "npm:^3.972.8" + "@aws-sdk/util-user-agent-node": "npm:^3.973.14" + "@smithy/config-resolver": "npm:^4.4.13" + "@smithy/core": "npm:^3.23.13" + "@smithy/fetch-http-handler": "npm:^5.3.15" + "@smithy/hash-node": "npm:^4.2.12" + "@smithy/invalid-dependency": "npm:^4.2.12" + "@smithy/middleware-content-length": "npm:^4.2.12" + "@smithy/middleware-endpoint": "npm:^4.4.28" + "@smithy/middleware-retry": "npm:^4.4.46" + "@smithy/middleware-serde": "npm:^4.2.16" + "@smithy/middleware-stack": "npm:^4.2.12" + "@smithy/node-config-provider": "npm:^4.3.12" + "@smithy/node-http-handler": "npm:^4.5.1" + "@smithy/protocol-http": "npm:^5.3.12" + "@smithy/smithy-client": "npm:^4.12.8" + "@smithy/types": "npm:^4.13.1" + "@smithy/url-parser": "npm:^4.2.12" + "@smithy/util-base64": "npm:^4.3.2" + "@smithy/util-body-length-browser": "npm:^4.2.2" + "@smithy/util-body-length-node": "npm:^4.2.3" + "@smithy/util-defaults-mode-browser": "npm:^4.3.44" + "@smithy/util-defaults-mode-node": "npm:^4.2.48" + "@smithy/util-endpoints": "npm:^3.3.3" + "@smithy/util-middleware": "npm:^4.2.12" + "@smithy/util-retry": "npm:^4.2.13" + "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/c666f08f4d9f3359b29852f64c1fcd1a837002cf546554e44ca23a4f2ba67ccfa356aae3e49d531b3481d1f9b982d61ad78e6a4779fab661aa0dfc522a90fd61 + checksum: 10/042d258e098e8e097374a5a5ef29fdebcd39ab6bdb76a06d21dc6291a03ccb1bcd6b17c6bf9cb011d2efa0483ccaf5c2e81f7b5f0febaae8fea6cbad8479dbec languageName: node linkType: hard @@ -1702,105 +1392,65 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/region-config-resolver@npm:3.649.0": - version: 3.649.0 - resolution: "@aws-sdk/region-config-resolver@npm:3.649.0" +"@aws-sdk/rds-signer@npm:^3.0.0": + version: 3.1022.0 + resolution: "@aws-sdk/rds-signer@npm:3.1022.0" dependencies: - "@aws-sdk/types": "npm:3.649.0" - "@smithy/node-config-provider": "npm:^3.1.5" - "@smithy/types": "npm:^3.4.0" - "@smithy/util-config-provider": "npm:^3.0.0" - "@smithy/util-middleware": "npm:^3.0.4" + "@aws-crypto/sha256-browser": "npm:5.2.0" + "@aws-crypto/sha256-js": "npm:5.2.0" + "@aws-sdk/credential-providers": "npm:3.1022.0" + "@aws-sdk/util-format-url": "npm:^3.972.8" + "@smithy/config-resolver": "npm:^4.4.13" + "@smithy/hash-node": "npm:^4.2.12" + "@smithy/invalid-dependency": "npm:^4.2.12" + "@smithy/node-config-provider": "npm:^4.3.12" + "@smithy/protocol-http": "npm:^5.3.12" + "@smithy/signature-v4": "npm:^5.3.12" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/f1bff1161ca1f506220e4f0d36ebbdec2e43382b21c4469cd2ad54f622e8cbd9ba385a0598a9c6a127431b1d056596cfa709f767b54fcf34c950d52778429d87 + checksum: 10/50a14b25f9cb7a496a8ba04e69ccbdd5d94e999a8a38b668252154d6c3fd7af09088201bd75fcf99bab76a9175703c0bebdab99cd014a1aee8e6829b07b5c79e languageName: node linkType: hard -"@aws-sdk/region-config-resolver@npm:3.910.0": - version: 3.910.0 - resolution: "@aws-sdk/region-config-resolver@npm:3.910.0" +"@aws-sdk/region-config-resolver@npm:^3.972.10": + version: 3.972.10 + resolution: "@aws-sdk/region-config-resolver@npm:3.972.10" dependencies: - "@aws-sdk/types": "npm:3.910.0" - "@smithy/node-config-provider": "npm:^4.3.2" - "@smithy/types": "npm:^4.7.1" - "@smithy/util-config-provider": "npm:^4.2.0" - "@smithy/util-middleware": "npm:^4.2.2" + "@aws-sdk/types": "npm:^3.973.6" + "@smithy/config-resolver": "npm:^4.4.13" + "@smithy/node-config-provider": "npm:^4.3.12" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/30c72ad18fa9440765ace4c4acfa26f6525573adff1fb9c12b251da709413a6e0d08894c62d011b463dfd414ead54095845c7225d2a2654adf3e5871743ac965 + checksum: 10/2c6c644953c038abc18a753807eef9b7c7eaa099d184f8e2721058a1edacc3d9d050fa278781df7f020da89ae976c553bb381d7c64119f2fb7b43e2b2869c828 languageName: node linkType: hard -"@aws-sdk/signature-v4-multi-region@npm:3.651.1": - version: 3.651.1 - resolution: "@aws-sdk/signature-v4-multi-region@npm:3.651.1" +"@aws-sdk/signature-v4-multi-region@npm:^3.996.15": + version: 3.996.15 + resolution: "@aws-sdk/signature-v4-multi-region@npm:3.996.15" dependencies: - "@aws-sdk/middleware-sdk-s3": "npm:3.651.1" - "@aws-sdk/types": "npm:3.649.0" - "@smithy/protocol-http": "npm:^4.1.1" - "@smithy/signature-v4": "npm:^4.1.1" - "@smithy/types": "npm:^3.4.0" + "@aws-sdk/middleware-sdk-s3": "npm:^3.972.27" + "@aws-sdk/types": "npm:^3.973.6" + "@smithy/protocol-http": "npm:^5.3.12" + "@smithy/signature-v4": "npm:^5.3.12" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/6653b7ef4793906e5726ff4cc93ccece0e07117daca4ebf9bf22299892884d9d2879cd480ccf317b6162797ad2a120c102a77d36fa11dcc63d3e73a014942f68 + checksum: 10/5449393486b058b0d6c041c2b44e0093869e5c26a9abda8fbdd5ba9d811b6d1ba518dce1e8068a7afb6e4931c08fb6598ac1d36a876dce66139d4ce20bcbf735 languageName: node linkType: hard -"@aws-sdk/signature-v4-multi-region@npm:3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/signature-v4-multi-region@npm:3.911.0" +"@aws-sdk/token-providers@npm:3.1021.0": + version: 3.1021.0 + resolution: "@aws-sdk/token-providers@npm:3.1021.0" dependencies: - "@aws-sdk/middleware-sdk-s3": "npm:3.911.0" - "@aws-sdk/types": "npm:3.910.0" - "@smithy/protocol-http": "npm:^5.3.2" - "@smithy/signature-v4": "npm:^5.3.2" - "@smithy/types": "npm:^4.7.1" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/nested-clients": "npm:^3.996.18" + "@aws-sdk/types": "npm:^3.973.6" + "@smithy/property-provider": "npm:^4.2.12" + "@smithy/shared-ini-file-loader": "npm:^4.4.7" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/da232ca896b90c6a8ae1370e29adb83af67a5ca4b660b61ba98ade2a31b173e5a31f4f535f342d5e4bac7f963641f4401c22616feca4dc622c538ef36982fa95 - languageName: node - linkType: hard - -"@aws-sdk/signature-v4@npm:^3.347.0": - version: 3.370.0 - resolution: "@aws-sdk/signature-v4@npm:3.370.0" - dependencies: - "@aws-sdk/eventstream-codec": "npm:3.370.0" - "@aws-sdk/is-array-buffer": "npm:3.310.0" - "@aws-sdk/types": "npm:3.370.0" - "@aws-sdk/util-hex-encoding": "npm:3.310.0" - "@aws-sdk/util-middleware": "npm:3.370.0" - "@aws-sdk/util-uri-escape": "npm:3.310.0" - "@aws-sdk/util-utf8": "npm:3.310.0" - tslib: "npm:^2.5.0" - checksum: 10/23a71ee99217d9eda1a5d3c9a4d614b76da46ba830d947a511a1609b71b6e426ef0a5503e394999a14c1e229c9ca720f3cd323c7567f101949a1207ef72f0dd3 - languageName: node - linkType: hard - -"@aws-sdk/token-providers@npm:3.649.0": - version: 3.649.0 - resolution: "@aws-sdk/token-providers@npm:3.649.0" - dependencies: - "@aws-sdk/types": "npm:3.649.0" - "@smithy/property-provider": "npm:^3.1.4" - "@smithy/shared-ini-file-loader": "npm:^3.1.5" - "@smithy/types": "npm:^3.4.0" - tslib: "npm:^2.6.2" - peerDependencies: - "@aws-sdk/client-sso-oidc": ^3.649.0 - checksum: 10/9ddbfeb1003e6ca629f385feb2e00ee7e0d90ea1047eed3e3b9a783dc0175f5fba59a836dff77ab983102df4dcd65c65f1d56797c998527daa4fc670e66a0be4 - languageName: node - linkType: hard - -"@aws-sdk/token-providers@npm:3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/token-providers@npm:3.911.0" - dependencies: - "@aws-sdk/core": "npm:3.911.0" - "@aws-sdk/nested-clients": "npm:3.911.0" - "@aws-sdk/types": "npm:3.910.0" - "@smithy/property-provider": "npm:^4.2.2" - "@smithy/shared-ini-file-loader": "npm:^4.3.2" - "@smithy/types": "npm:^4.7.1" - tslib: "npm:^2.6.2" - checksum: 10/88cdadde7dbddc88c5cf33a519f7dd108ce74254acdee6a6c3e4c6aa931350f873c37abe426f43238ae58b02ce9dd7bc33c8b214813938eb9db88e74172f484b + checksum: 10/8b64169f56e3b352656bdcea347269d979aa7422c09396e01ef97912db96584274b6aac1fa6d0e267536ef0f6b8acfa150c0eed722243af2235019d62f824870 languageName: node linkType: hard @@ -1814,23 +1464,13 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/types@npm:3.649.0": - version: 3.649.0 - resolution: "@aws-sdk/types@npm:3.649.0" +"@aws-sdk/types@npm:^3.222.0, @aws-sdk/types@npm:^3.347.0, @aws-sdk/types@npm:^3.973.6": + version: 3.973.6 + resolution: "@aws-sdk/types@npm:3.973.6" dependencies: - "@smithy/types": "npm:^3.4.0" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/9929e255909c7375c965e94e33871d38a841a9a13797b7b729d8c45b0aa91d5591d80ca18a879982a2387f12c9ce4bd5b3401182fb76046bf5f180c1e3c5e951 - languageName: node - linkType: hard - -"@aws-sdk/types@npm:3.910.0, @aws-sdk/types@npm:^3.222.0, @aws-sdk/types@npm:^3.347.0": - version: 3.910.0 - resolution: "@aws-sdk/types@npm:3.910.0" - dependencies: - "@smithy/types": "npm:^4.7.1" - tslib: "npm:^2.6.2" - checksum: 10/7865602184322eaa0588ae16ac3cda941d8aaceaa88a6c2ab723e37dc4017db84ba5cfdf6d3b42b01d132d3090c08a732598bf9d9734b66f1c82903e1602af6e + checksum: 10/74f0ef0da96aab5e9a2838c02a0ecbf3cda5cba09711378c1eb980f66ce511eb6929e3f46743538f64854d75fe27d23bf7b67790df6df09a10bedf0a7a5ccb7f languageName: node linkType: hard @@ -1845,21 +1485,12 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/util-arn-parser@npm:3.568.0": - version: 3.568.0 - resolution: "@aws-sdk/util-arn-parser@npm:3.568.0" +"@aws-sdk/util-arn-parser@npm:^3.310.0, @aws-sdk/util-arn-parser@npm:^3.972.3": + version: 3.972.3 + resolution: "@aws-sdk/util-arn-parser@npm:3.972.3" dependencies: tslib: "npm:^2.6.2" - checksum: 10/b1a7f93b4f47136ee8d71bcbbd2d5d19581007f0684aff252d3bee6b9ccc7c56e765255bb1bea847171b40cdbd2eca0fb102f24cba857d1c79c54747e8ee0855 - languageName: node - linkType: hard - -"@aws-sdk/util-arn-parser@npm:3.893.0, @aws-sdk/util-arn-parser@npm:^3.310.0": - version: 3.893.0 - resolution: "@aws-sdk/util-arn-parser@npm:3.893.0" - dependencies: - tslib: "npm:^2.6.2" - checksum: 10/f809777714618c63e92fd5881c9573081bc94df9c5cce53917b8e1db4efa1d44f1132f6c9179f9babc4964c648d42ebee5c1ad75641c6b336007be3df561053f + checksum: 10/140a30615c914bcb37a5bb6ff825e8b6d2bedea757c2b03a4f5abb986003683ceadc322c0ee9f9a3ba4d5925357515ed7be01ef13c56c3b0126d4e1bd7292a33 languageName: node linkType: hard @@ -1873,46 +1504,37 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/util-endpoints@npm:3.649.0": - version: 3.649.0 - resolution: "@aws-sdk/util-endpoints@npm:3.649.0" +"@aws-sdk/util-endpoints@npm:^3.996.5": + version: 3.996.5 + resolution: "@aws-sdk/util-endpoints@npm:3.996.5" dependencies: - "@aws-sdk/types": "npm:3.649.0" - "@smithy/types": "npm:^3.4.0" - "@smithy/util-endpoints": "npm:^2.1.0" + "@aws-sdk/types": "npm:^3.973.6" + "@smithy/types": "npm:^4.13.1" + "@smithy/url-parser": "npm:^4.2.12" + "@smithy/util-endpoints": "npm:^3.3.3" tslib: "npm:^2.6.2" - checksum: 10/865d39a0d76d2d131246184ffe3271e647d066a947c50dbd31e19990aa71fcc8fc036ed896ef562e02cfeafe1c47169b0bf20da840ad99553c7dc853df434b12 + checksum: 10/44c43c71f69981afc5238e25292fbe56a4552295ab0589a17215cf0d476ea63679a62142aeab9e30c76ae65b5f408a0d5e11513255be14c2d7dbeaaa2cc428c5 languageName: node linkType: hard -"@aws-sdk/util-endpoints@npm:3.910.0": - version: 3.910.0 - resolution: "@aws-sdk/util-endpoints@npm:3.910.0" +"@aws-sdk/util-format-url@npm:^3.972.8": + version: 3.972.8 + resolution: "@aws-sdk/util-format-url@npm:3.972.8" dependencies: - "@aws-sdk/types": "npm:3.910.0" - "@smithy/types": "npm:^4.7.1" - "@smithy/url-parser": "npm:^4.2.2" - "@smithy/util-endpoints": "npm:^3.2.2" + "@aws-sdk/types": "npm:^3.973.6" + "@smithy/querystring-builder": "npm:^4.2.12" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/c84a4f60cffad6db765e1f2a6e9a1d178adae1023f9c0594ed36504cfe9b9438073a4e81f668a1c9b27298ceb74800ff15088fa0d757cbddad41a179a160f879 - languageName: node - linkType: hard - -"@aws-sdk/util-hex-encoding@npm:3.310.0": - version: 3.310.0 - resolution: "@aws-sdk/util-hex-encoding@npm:3.310.0" - dependencies: - tslib: "npm:^2.5.0" - checksum: 10/9ec0388c9667d4d616c61530be88422a315e3d92bf93b941d6f6d8339d6e703f4cacb2e11402658d716b1166e90d0fddb497284220e11075a0c17821c468c44b + checksum: 10/37aa7d0e47b32fbfbb9b6d2c7ec03512191cac3eab7ce45c996dd19b77d5317cf2e1636d90de87313a5604c3c7fb2f8a426c8fbb6f39f672f3774ca8a5e72d19 languageName: node linkType: hard "@aws-sdk/util-locate-window@npm:^3.0.0": - version: 3.183.0 - resolution: "@aws-sdk/util-locate-window@npm:3.183.0" + version: 3.965.5 + resolution: "@aws-sdk/util-locate-window@npm:3.965.5" dependencies: - tslib: "npm:^2.3.1" - checksum: 10/6aefa7ae6aa585018d551e48b92cdacd9cd38904b62f4a604f4a45f91516c4d9351835fd6e25fbbeb21a9c110db23062f58e9a8aa204b07b6f036d3b760f8b4a + tslib: "npm:^2.6.2" + checksum: 10/66391a7f6d0c383d6bc3ea67e35b0b0164798d9acbe47271fbc676cf74e7a56690f48425a91cce70e764c53ca46619f4abc076b569d8f991c19dd7c1ac4b0a79 languageName: node linkType: hard @@ -1946,109 +1568,52 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/util-user-agent-browser@npm:3.649.0": - version: 3.649.0 - resolution: "@aws-sdk/util-user-agent-browser@npm:3.649.0" +"@aws-sdk/util-user-agent-browser@npm:^3.972.8": + version: 3.972.8 + resolution: "@aws-sdk/util-user-agent-browser@npm:3.972.8" dependencies: - "@aws-sdk/types": "npm:3.649.0" - "@smithy/types": "npm:^3.4.0" + "@aws-sdk/types": "npm:^3.973.6" + "@smithy/types": "npm:^4.13.1" bowser: "npm:^2.11.0" tslib: "npm:^2.6.2" - checksum: 10/54a30c9fb023091baeae64c45c91d8aed79a82a5cf107c2ab2ee722934cc9d2d3289cca8d58210eb6aaddce3d686e4d05873d5c9cdb8559885f92d162ec7ec7d + checksum: 10/cb0f9b1daa8ce90e174d75c60d0b42360d952a710fef0c3bed21d3bfbabf814e1137e94a0edb3fa4eb8f5d4ee166fd94b9f56deb5878495b818c1ddbfe04e543 languageName: node linkType: hard -"@aws-sdk/util-user-agent-browser@npm:3.910.0": - version: 3.910.0 - resolution: "@aws-sdk/util-user-agent-browser@npm:3.910.0" +"@aws-sdk/util-user-agent-node@npm:^3.973.13, @aws-sdk/util-user-agent-node@npm:^3.973.14": + version: 3.973.14 + resolution: "@aws-sdk/util-user-agent-node@npm:3.973.14" dependencies: - "@aws-sdk/types": "npm:3.910.0" - "@smithy/types": "npm:^4.7.1" - bowser: "npm:^2.11.0" - tslib: "npm:^2.6.2" - checksum: 10/0bd5becebf9de4c3a02f88be5cb1912fb2b82f8ecf17e82840dcacfeac96fef4f04188645fe5f4cc6ecdbdc67dc5c408bb9843a9a534afe6be68af39ae1e9440 - languageName: node - linkType: hard - -"@aws-sdk/util-user-agent-node@npm:3.649.0": - version: 3.649.0 - resolution: "@aws-sdk/util-user-agent-node@npm:3.649.0" - dependencies: - "@aws-sdk/types": "npm:3.649.0" - "@smithy/node-config-provider": "npm:^3.1.5" - "@smithy/types": "npm:^3.4.0" + "@aws-sdk/middleware-user-agent": "npm:^3.972.28" + "@aws-sdk/types": "npm:^3.973.6" + "@smithy/node-config-provider": "npm:^4.3.12" + "@smithy/types": "npm:^4.13.1" + "@smithy/util-config-provider": "npm:^4.2.2" tslib: "npm:^2.6.2" peerDependencies: aws-crt: ">=1.0.0" peerDependenciesMeta: aws-crt: optional: true - checksum: 10/24c055031299ffb250a84ffd562bd4ebcc74378a4bd1dbe43c073dc021b7c28dca16ec9758339e46d23fa8f3a0e5869ae20751147750db8a62030438cc97e96b + checksum: 10/61077c95e26478ee73b125cac0fb1b5d08d833c67be8209662c851c768641f9043e24ed8449f72ce6968faee976001a784b1ebc1f29170f4c07942260b2500ff languageName: node linkType: hard -"@aws-sdk/util-user-agent-node@npm:3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/util-user-agent-node@npm:3.911.0" +"@aws-sdk/xml-builder@npm:^3.972.16": + version: 3.972.16 + resolution: "@aws-sdk/xml-builder@npm:3.972.16" dependencies: - "@aws-sdk/middleware-user-agent": "npm:3.911.0" - "@aws-sdk/types": "npm:3.910.0" - "@smithy/node-config-provider": "npm:^4.3.2" - "@smithy/types": "npm:^4.7.1" + "@smithy/types": "npm:^4.13.1" + fast-xml-parser: "npm:5.5.8" tslib: "npm:^2.6.2" - peerDependencies: - aws-crt: ">=1.0.0" - peerDependenciesMeta: - aws-crt: - optional: true - checksum: 10/c87cd348edc6436f0788eaad4b05e15c4f2fe8f096f9e90d40c13f440241da4336192190be2720f9941f1c72ddfd1d8e317d616fe5db1b6d3780fafadc7941e4 + checksum: 10/6e67c69b4f101d60b3cb7394ea71c463bfeac68a3e4bd0a70773e59c00b0cbc87f808949c1d7cd72d4bb7e849373ba6af7ef5a27f1f9e87b1f2b4f79f9b6d4c0 languageName: node linkType: hard -"@aws-sdk/util-utf8-browser@npm:^3.0.0": - version: 3.188.0 - resolution: "@aws-sdk/util-utf8-browser@npm:3.188.0" - dependencies: - tslib: "npm:^2.3.1" - checksum: 10/ee5d2c005ca2bb6514526869c88b2d794572eb8de3d43a5bafd0843dfc93dd0060596f852159bc3fcfbb9b443c6d0fbf5613cfbcde0e43331a3a105d462f7788 - languageName: node - linkType: hard - -"@aws-sdk/util-utf8@npm:3.310.0": - version: 3.310.0 - resolution: "@aws-sdk/util-utf8@npm:3.310.0" - dependencies: - "@aws-sdk/util-buffer-from": "npm:3.310.0" - tslib: "npm:^2.5.0" - checksum: 10/88bf031527a1fe99712b4e2cb00bc47436b39aa29ce0ceeace3bd0bc7d436d14940f190c249b9a69e684f8d3f01f96847848f5d8ebab7e3103c0084c1e609ce4 - languageName: node - linkType: hard - -"@aws-sdk/xml-builder@npm:3.649.0": - version: 3.649.0 - resolution: "@aws-sdk/xml-builder@npm:3.649.0" - dependencies: - "@smithy/types": "npm:^3.4.0" - tslib: "npm:^2.6.2" - checksum: 10/b47c767169d4f440a687b0dd343c2016bd8e3ea023d3a9c64256fe472d5ffe9eccb4bb1fa42bee0693cae11ed86bd866a7a98fd62dcf65dab34926c5be82579f - languageName: node - linkType: hard - -"@aws-sdk/xml-builder@npm:3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/xml-builder@npm:3.911.0" - dependencies: - "@smithy/types": "npm:^4.7.1" - fast-xml-parser: "npm:5.2.5" - tslib: "npm:^2.6.2" - checksum: 10/0208021abe8ac9633c3d13f1b108de58496af152029e6b0e10fe621a1dabfa412b1f14c91d531aeaf5431335b278d354a07d96acb8bc30369cff591d073338d8 - languageName: node - linkType: hard - -"@aws/lambda-invoke-store@npm:^0.0.1": - version: 0.0.1 - resolution: "@aws/lambda-invoke-store@npm:0.0.1" - checksum: 10/e8f54d28aade8828962f2871a22aa4e960ebc40c8fa551414181dd9dd32d6258279013c42f88e57d17aa4252cb5ed00df6a49fc35185f9fa6b6f351ccf821bd6 +"@aws/lambda-invoke-store@npm:^0.2.2": + version: 0.2.3 + resolution: "@aws/lambda-invoke-store@npm:0.2.3" + checksum: 10/d0efa8ca73b2d8dc0bf634525eefa1b72cda85f5d47366264849343a6f2860cfa5c52b7f766a16b78da8406bbd3ee975da3abb1dbe38183f8af95413eafeb256 languageName: node linkType: hard @@ -2300,14 +1865,14 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.16.0, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.24.2, @babel/code-frame@npm:^7.27.1, @babel/code-frame@npm:^7.8.3": - version: 7.27.1 - resolution: "@babel/code-frame@npm:7.27.1" +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.16.0, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.27.1, @babel/code-frame@npm:^7.29.0, @babel/code-frame@npm:^7.8.3": + version: 7.29.0 + resolution: "@babel/code-frame@npm:7.29.0" dependencies: - "@babel/helper-validator-identifier": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.28.5" js-tokens: "npm:^4.0.0" picocolors: "npm:^1.1.1" - checksum: 10/721b8a6e360a1fa0f1c9fe7351ae6c874828e119183688b533c477aa378f1010f37cc9afbfc4722c686d1f5cdd00da02eab4ba7278a0c504fa0d7a321dcd4fdf + checksum: 10/199e15ff89007dd30675655eec52481cb245c9fdf4f81e4dc1f866603b0217b57aff25f5ffa0a95bbc8e31eb861695330cd7869ad52cc211aa63016320ef72c5 languageName: node linkType: hard @@ -2318,39 +1883,39 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.24.7, @babel/core@npm:^7.28.0": - version: 7.28.4 - resolution: "@babel/core@npm:7.28.4" +"@babel/core@npm:^7.23.9, @babel/core@npm:^7.24.7, @babel/core@npm:^7.27.4, @babel/core@npm:^7.28.0": + version: 7.28.5 + resolution: "@babel/core@npm:7.28.5" dependencies: "@babel/code-frame": "npm:^7.27.1" - "@babel/generator": "npm:^7.28.3" + "@babel/generator": "npm:^7.28.5" "@babel/helper-compilation-targets": "npm:^7.27.2" "@babel/helper-module-transforms": "npm:^7.28.3" "@babel/helpers": "npm:^7.28.4" - "@babel/parser": "npm:^7.28.4" + "@babel/parser": "npm:^7.28.5" "@babel/template": "npm:^7.27.2" - "@babel/traverse": "npm:^7.28.4" - "@babel/types": "npm:^7.28.4" + "@babel/traverse": "npm:^7.28.5" + "@babel/types": "npm:^7.28.5" "@jridgewell/remapping": "npm:^2.3.5" convert-source-map: "npm:^2.0.0" debug: "npm:^4.1.0" gensync: "npm:^1.0.0-beta.2" json5: "npm:^2.2.3" semver: "npm:^6.3.1" - checksum: 10/0593295241fac9be567145ef16f3858d34fc91390a9438c6d47476be9823af4cc0488c851c59702dd46b968e9fd46d17ddf0105ea30195ca85f5a66b4044c519 + checksum: 10/2f1e224125179f423f4300d605a0c5a3ef315003281a63b1744405b2605ee2a2ffc5b1a8349aa4f262c72eca31c7e1802377ee04ad2b852a2c88f8ace6cac324 languageName: node linkType: hard -"@babel/generator@npm:^7.28.3, @babel/generator@npm:^7.7.2": - version: 7.28.3 - resolution: "@babel/generator@npm:7.28.3" +"@babel/generator@npm:^7.27.5, @babel/generator@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/generator@npm:7.28.5" dependencies: - "@babel/parser": "npm:^7.28.3" - "@babel/types": "npm:^7.28.2" + "@babel/parser": "npm:^7.28.5" + "@babel/types": "npm:^7.28.5" "@jridgewell/gen-mapping": "npm:^0.3.12" "@jridgewell/trace-mapping": "npm:^0.3.28" jsesc: "npm:^3.0.2" - checksum: 10/d00d1e6b51059e47594aab7920b88ec6fcef6489954a9172235ab57ad2e91b39c95376963a6e2e4cc7e8b88fa4f931018f71f9ab32bbc9c0bc0de35a0231f26c + checksum: 10/ae618f0a17a6d76c3983e1fd5d9c2f5fdc07703a119efdb813a7d9b8ad4be0a07d4c6f0d718440d2de01a68e321f64e2d63c77fc5d43ae47ae143746ef28ac1f languageName: node linkType: hard @@ -2442,10 +2007,10 @@ __metadata: languageName: node linkType: hard -"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.24.7, @babel/helper-plugin-utils@npm:^7.24.8, @babel/helper-plugin-utils@npm:^7.25.9, @babel/helper-plugin-utils@npm:^7.8.0": - version: 7.25.9 - resolution: "@babel/helper-plugin-utils@npm:7.25.9" - checksum: 10/e347d87728b1ab10b6976d46403941c8f9008c045ea6d99997a7ffca7b852dc34b6171380f7b17edf94410e0857ff26f3a53d8618f11d73744db86e8ca9b8c64 +"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.24.7, @babel/helper-plugin-utils@npm:^7.24.8, @babel/helper-plugin-utils@npm:^7.27.1, @babel/helper-plugin-utils@npm:^7.8.0": + version: 7.27.1 + resolution: "@babel/helper-plugin-utils@npm:7.27.1" + checksum: 10/96136c2428888e620e2ec493c25888f9ceb4a21099dcf3dd4508ea64b58cdedbd5a9fb6c7b352546de84d6c24edafe482318646932a22c449ebd16d16c22d864 languageName: node linkType: hard @@ -2489,10 +2054,10 @@ __metadata: languageName: node linkType: hard -"@babel/helper-validator-identifier@npm:^7.24.7, @babel/helper-validator-identifier@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/helper-validator-identifier@npm:7.27.1" - checksum: 10/75041904d21bdc0cd3b07a8ac90b11d64cd3c881e89cb936fa80edd734bf23c35e6bd1312611e8574c4eab1f3af0f63e8a5894f4699e9cfdf70c06fcf4252320 +"@babel/helper-validator-identifier@npm:^7.24.7, @babel/helper-validator-identifier@npm:^7.27.1, @babel/helper-validator-identifier@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/helper-validator-identifier@npm:7.28.5" + checksum: 10/8e5d9b0133702cfacc7f368bf792f0f8ac0483794877c6dca5fcb73810ee138e27527701826fb58a40a004f3a5ec0a2f3c3dd5e326d262530b119918f3132ba7 languageName: node linkType: hard @@ -2525,14 +2090,14 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.24.7, @babel/parser@npm:^7.26.7, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.27.5, @babel/parser@npm:^7.28.3, @babel/parser@npm:^7.28.4": - version: 7.28.4 - resolution: "@babel/parser@npm:7.28.4" +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.24.7, @babel/parser@npm:^7.26.7, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.27.5, @babel/parser@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/parser@npm:7.28.5" dependencies: - "@babel/types": "npm:^7.28.4" + "@babel/types": "npm:^7.28.5" bin: parser: ./bin/babel-parser.js - checksum: 10/f54c46213ef180b149f6a17ea765bf40acc1aebe2009f594e2a283aec69a190c6dda1fdf24c61a258dbeb903abb8ffb7a28f1a378f8ab5d333846ce7b7e23bf1 + checksum: 10/8d9bfb437af6c97a7f6351840b9ac06b4529ba79d6d3def24d6c2996ab38ff7f1f9d301e868ca84a93a3050fadb3d09dbc5105b24634cd281671ac11eebe8df7 languageName: node linkType: hard @@ -2558,7 +2123,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-class-properties@npm:^7.8.3": +"@babel/plugin-syntax-class-properties@npm:^7.12.13": version: 7.12.13 resolution: "@babel/plugin-syntax-class-properties@npm:7.12.13" dependencies: @@ -2569,6 +2134,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-syntax-class-static-block@npm:^7.14.5": + version: 7.14.5 + resolution: "@babel/plugin-syntax-class-static-block@npm:7.14.5" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.14.5" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/3e80814b5b6d4fe17826093918680a351c2d34398a914ce6e55d8083d72a9bdde4fbaf6a2dcea0e23a03de26dc2917ae3efd603d27099e2b98380345703bf948 + languageName: node + linkType: hard + "@babel/plugin-syntax-flow@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-syntax-flow@npm:7.24.7" @@ -2580,7 +2156,18 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-import-meta@npm:^7.8.3": +"@babel/plugin-syntax-import-attributes@npm:^7.24.7": + version: 7.27.1 + resolution: "@babel/plugin-syntax-import-attributes@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/97973982fff1bbf86b3d1df13380567042887c50e2ae13a400d02a8ff2c9742a60a75e279bfb73019e1cd9710f04be5e6ab81f896e6678dcfcec8b135e8896cf + languageName: node + linkType: hard + +"@babel/plugin-syntax-import-meta@npm:^7.10.4": version: 7.10.4 resolution: "@babel/plugin-syntax-import-meta@npm:7.10.4" dependencies: @@ -2602,18 +2189,18 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-jsx@npm:^7.24.7, @babel/plugin-syntax-jsx@npm:^7.7.2": - version: 7.24.7 - resolution: "@babel/plugin-syntax-jsx@npm:7.24.7" +"@babel/plugin-syntax-jsx@npm:^7.24.7, @babel/plugin-syntax-jsx@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-syntax-jsx@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/a93516ae5b34868ab892a95315027d4e5e38e8bd1cfca6158f2974b0901cbb32bbe64ea10ad5b25f919ddc40c6d8113c4823372909c9c9922170c12b0b1acecb + checksum: 10/c6d1324cff286a369aa95d99b8abd21dd07821b5d3affd5fe7d6058c84cff9190743287826463ee57a7beecd10fa1e4bc99061df532ee14e188c1c8937b13e3a languageName: node linkType: hard -"@babel/plugin-syntax-logical-assignment-operators@npm:^7.8.3": +"@babel/plugin-syntax-logical-assignment-operators@npm:^7.10.4": version: 7.10.4 resolution: "@babel/plugin-syntax-logical-assignment-operators@npm:7.10.4" dependencies: @@ -2635,7 +2222,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-numeric-separator@npm:^7.8.3": +"@babel/plugin-syntax-numeric-separator@npm:^7.10.4": version: 7.10.4 resolution: "@babel/plugin-syntax-numeric-separator@npm:7.10.4" dependencies: @@ -2679,7 +2266,18 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-top-level-await@npm:^7.8.3": +"@babel/plugin-syntax-private-property-in-object@npm:^7.14.5": + version: 7.14.5 + resolution: "@babel/plugin-syntax-private-property-in-object@npm:7.14.5" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.14.5" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/b317174783e6e96029b743ccff2a67d63d38756876e7e5d0ba53a322e38d9ca452c13354a57de1ad476b4c066dbae699e0ca157441da611117a47af88985ecda + languageName: node + linkType: hard + +"@babel/plugin-syntax-top-level-await@npm:^7.14.5": version: 7.14.5 resolution: "@babel/plugin-syntax-top-level-await@npm:7.14.5" dependencies: @@ -2690,14 +2288,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-typescript@npm:^7.24.7, @babel/plugin-syntax-typescript@npm:^7.7.2": - version: 7.25.9 - resolution: "@babel/plugin-syntax-typescript@npm:7.25.9" +"@babel/plugin-syntax-typescript@npm:^7.24.7, @babel/plugin-syntax-typescript@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-syntax-typescript@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/0e9821e8ba7d660c36c919654e4144a70546942ae184e85b8102f2322451eae102cbfadbcadd52ce077a2b44b400ee52394c616feab7b5b9f791b910e933fd33 + checksum: 10/87836f7e32af624c2914c73cd6b9803cf324e07d43f61dbb973c6a86f75df725e12540d91fac7141c14b697aa9268fd064220998daced156e96ac3062d7afb41 languageName: node linkType: hard @@ -2842,14 +2440,14 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.26.0, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.0, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.26.0, @babel/runtime@npm:^7.28.4, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.0, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": version: 7.28.4 resolution: "@babel/runtime@npm:7.28.4" checksum: 10/6c9a70452322ea80b3c9b2a412bcf60771819213a67576c8cec41e88a95bb7bf01fc983754cda35dc19603eef52df22203ccbf7777b9d6316932f9fb77c25163 languageName: node linkType: hard -"@babel/template@npm:^7.27.2, @babel/template@npm:^7.3.3": +"@babel/template@npm:^7.27.2": version: 7.27.2 resolution: "@babel/template@npm:7.27.2" dependencies: @@ -2860,35 +2458,35 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.24.8, @babel/traverse@npm:^7.25.0, @babel/traverse@npm:^7.25.4, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.4, @babel/traverse@npm:^7.4.5": - version: 7.28.4 - resolution: "@babel/traverse@npm:7.28.4" +"@babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.24.8, @babel/traverse@npm:^7.25.0, @babel/traverse@npm:^7.25.4, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.5, @babel/traverse@npm:^7.4.5": + version: 7.28.5 + resolution: "@babel/traverse@npm:7.28.5" dependencies: "@babel/code-frame": "npm:^7.27.1" - "@babel/generator": "npm:^7.28.3" + "@babel/generator": "npm:^7.28.5" "@babel/helper-globals": "npm:^7.28.0" - "@babel/parser": "npm:^7.28.4" + "@babel/parser": "npm:^7.28.5" "@babel/template": "npm:^7.27.2" - "@babel/types": "npm:^7.28.4" + "@babel/types": "npm:^7.28.5" debug: "npm:^4.3.1" - checksum: 10/c3099364b7b1c36bcd111099195d4abeef16499e5defb1e56766b754e8b768c252e856ed9041665158aa1b31215fc6682632756803c8fa53405381ec08c4752b + checksum: 10/1fce426f5ea494913c40f33298ce219708e703f71cac7ac045ebde64b5a7b17b9275dfa4e05fb92c3f123136913dff62c8113172f4a5de66dab566123dbe7437 languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.27.1, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.4, @babel/types@npm:^7.3.3": - version: 7.28.4 - resolution: "@babel/types@npm:7.28.4" +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.4, @babel/types@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/types@npm:7.28.5" dependencies: "@babel/helper-string-parser": "npm:^7.27.1" - "@babel/helper-validator-identifier": "npm:^7.27.1" - checksum: 10/db50bf257aafa5d845ad16dae0587f57d596e4be4cbb233ea539976a4c461f9fbcc0bf3d37adae3f8ce5dcb4001462aa608f3558161258b585f6ce6ce21a2e45 + "@babel/helper-validator-identifier": "npm:^7.28.5" + checksum: 10/4256bb9fb2298c4f9b320bde56e625b7091ea8d2433d98dcf524d4086150da0b6555aabd7d0725162670614a9ac5bf036d1134ca13dedc9707f988670f1362d7 languageName: node linkType: hard -"@backstage-community/plugin-explore-common@npm:^0.5.0": - version: 0.5.0 - resolution: "@backstage-community/plugin-explore-common@npm:0.5.0" - checksum: 10/258dd4102f64220b28803db3825e52bb2974cad5704ca134a75b7924dd95a601c92fd8d6606cc3bcd949feace420a407a85ff32f4bd782c8e45ab728b62cda46 +"@backstage-community/plugin-explore-common@npm:^0.12.0": + version: 0.12.0 + resolution: "@backstage-community/plugin-explore-common@npm:0.12.0" + checksum: 10/744ffa86c794ed612a257b7eba018d8bd433e160db9c29e9d72cca791f58fbed1fbbcacf2b670b992b7f13eb0cd0a08014ba60108ac17196dbee4699b8b6f0f1 languageName: node linkType: hard @@ -2910,12 +2508,12 @@ __metadata: "@types/react": "npm:^18.0.0" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -2943,8 +2541,10 @@ __metadata: "@aws-sdk/client-codecommit": "npm:^3.350.0" "@aws-sdk/client-s3": "npm:^3.350.0" "@aws-sdk/credential-providers": "npm:^3.350.0" + "@aws-sdk/rds-signer": "npm:^3.0.0" "@aws-sdk/types": "npm:^3.347.0" "@aws-sdk/util-stream-node": "npm:^3.350.0" + "@azure/identity": "npm:^4.0.0" "@azure/storage-blob": "npm:^12.5.0" "@backstage/backend-app-api": "workspace:^" "@backstage/backend-dev-utils": "workspace:^" @@ -2959,6 +2559,7 @@ __metadata: "@backstage/integration-aws-node": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/plugin-events-node": "workspace:^" + "@backstage/plugin-permission-common": "workspace:^" "@backstage/plugin-permission-node": "workspace:^" "@backstage/types": "workspace:^" "@google-cloud/cloud-sql-connector": "npm:^1.4.0" @@ -2969,7 +2570,7 @@ __metadata: "@manypkg/get-packages": "npm:^1.1.3" "@octokit/rest": "npm:^19.0.3" "@opentelemetry/api": "npm:^1.9.0" - "@types/archiver": "npm:^6.0.0" + "@types/archiver": "npm:^7.0.0" "@types/base64-stream": "npm:^1.0.2" "@types/compression": "npm:^1.7.5" "@types/concat-stream": "npm:^2.0.0" @@ -2989,10 +2590,11 @@ __metadata: cookie: "npm:^0.7.0" cors: "npm:^2.8.5" cron: "npm:^3.0.0" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" - express-rate-limit: "npm:^7.5.0" + express-rate-limit: "npm:^8.2.2" fs-extra: "npm:^11.2.0" + get-port: "npm:^5.1.1" git-url-parse: "npm:^15.0.0" helmet: "npm:^6.0.0" http-errors: "npm:^2.0.0" @@ -3004,11 +2606,11 @@ __metadata: lodash: "npm:^4.17.21" logform: "npm:^2.3.2" luxon: "npm:^3.0.0" - minimatch: "npm:^9.0.0" + minimatch: "npm:^10.2.1" msw: "npm:^1.0.0" mysql2: "npm:^3.0.0" node-fetch: "npm:^2.7.0" - node-forge: "npm:^1.3.1" + node-forge: "npm:^1.3.2" node-mocks-http: "npm:^1.0.0" p-limit: "npm:^3.1.0" path-to-regexp: "npm:^8.0.0" @@ -3019,21 +2621,24 @@ __metadata: raw-body: "npm:^2.4.1" selfsigned: "npm:^2.0.0" supertest: "npm:^7.0.0" - tar: "npm:^6.1.12" + tar: "npm:^7.5.6" triple-beam: "npm:^1.4.1" uuid: "npm:^11.0.0" - wait-for-expect: "npm:^3.0.2" + wait-for-expect: "npm:^4.0.0" winston: "npm:^3.2.1" winston-transport: "npm:^4.5.0" - yauzl: "npm:^3.0.0" + yauzl: "npm:^3.2.1" yn: "npm:^4.0.0" - zod: "npm:^3.22.4" - zod-to-json-schema: "npm:^3.20.4" + zod: "npm:^3.25.76 || ^4.0.0" + zod-to-json-schema: "npm:^3.25.1" peerDependencies: "@google-cloud/cloud-sql-connector": ^1.4.0 + better-sqlite3: ^12.0.0 peerDependenciesMeta: "@google-cloud/cloud-sql-connector": optional: true + better-sqlite3: + optional: true languageName: unknown linkType: soft @@ -3074,15 +2679,15 @@ __metadata: "@backstage/repo-tools": "workspace:^" "@backstage/types": "workspace:^" "@manypkg/get-packages": "npm:^1.1.3" - "@module-federation/sdk": "npm:^0.9.0" + "@module-federation/sdk": "npm:^2.3.3" "@types/express": "npm:^4.17.6" chokidar: "npm:^3.5.3" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" fs-extra: "npm:^11.2.0" lodash: "npm:^4.17.21" triple-beam: "npm:^1.4.1" - wait-for-expect: "npm:^3.0.2" + wait-for-expect: "npm:^4.0.0" winston: "npm:^3.2.1" languageName: unknown linkType: soft @@ -3100,7 +2705,7 @@ __metadata: "@types/express": "npm:^4.17.6" "@types/express-serve-static-core": "npm:^4.17.5" ajv: "npm:^8.16.0" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-openapi-validator: "npm:^5.5.8" express-promise-router: "npm:^4.1.0" get-port: "npm:^5.1.1" @@ -3133,7 +2738,7 @@ __metadata: json-schema: "npm:^0.4.0" knex: "npm:^3.0.0" luxon: "npm:^3.0.0" - zod: "npm:^3.22.4" + zod: "npm:^3.25.76 || ^4.0.0" languageName: unknown linkType: soft @@ -3163,7 +2768,7 @@ __metadata: "@types/supertest": "npm:^2.0.8" better-sqlite3: "npm:^12.0.0" cookie: "npm:^0.7.0" - express: "npm:^4.17.1" + express: "npm:^4.22.0" fs-extra: "npm:^11.0.0" keyv: "npm:^5.2.1" knex: "npm:^3.0.0" @@ -3172,12 +2777,17 @@ __metadata: pg: "npm:^8.11.3" pg-connection-string: "npm:^2.3.0" supertest: "npm:^7.0.0" - testcontainers: "npm:^10.0.0" - textextensions: "npm:^5.16.0" + testcontainers: "npm:^11.9.0" + text-extensions: "npm:^2.4.0" uuid: "npm:^11.0.0" yn: "npm:^4.0.0" - zod: "npm:^3.22.4" - zod-to-json-schema: "npm:^3.20.4" + zod: "npm:^3.25.76 || ^4.0.0" + zod-to-json-schema: "npm:^3.25.1" + peerDependencies: + "@types/jest": "*" + peerDependenciesMeta: + "@types/jest": + optional: true languageName: unknown linkType: soft @@ -3188,8 +2798,11 @@ __metadata: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/errors": "workspace:^" + "@backstage/filter-predicates": "workspace:^" "@backstage/plugin-catalog-common": "workspace:^" + "@types/lodash": "npm:^4.14.151" cross-fetch: "npm:^4.0.0" + lodash: "npm:^4.17.21" msw: "npm:^1.0.0" uri-template: "npm:^2.0.0" languageName: unknown @@ -3205,8 +2818,10 @@ __metadata: "@types/json-schema": "npm:^7.0.5" "@types/lodash": "npm:^4.14.151" ajv: "npm:^8.10.0" + ajv-errors: "npm:^3.0.0" lodash: "npm:^4.17.21" yaml: "npm:^2.0.0" + zod: "npm:^3.25.76" languageName: unknown linkType: soft @@ -3215,7 +2830,364 @@ __metadata: resolution: "@backstage/cli-common@workspace:packages/cli-common" dependencies: "@backstage/cli": "workspace:^" - "@types/node": "npm:^20.16.0" + "@backstage/errors": "workspace:^" + "@types/cross-spawn": "npm:^6.0.2" + "@types/node": "npm:^22.13.14" + cross-spawn: "npm:^7.0.3" + global-agent: "npm:^3.0.0" + undici: "npm:^7.24.5" + languageName: unknown + linkType: soft + +"@backstage/cli-defaults@workspace:*, @backstage/cli-defaults@workspace:^, @backstage/cli-defaults@workspace:packages/cli-defaults": + version: 0.0.0-use.local + resolution: "@backstage/cli-defaults@workspace:packages/cli-defaults" + dependencies: + "@backstage/cli": "workspace:^" + "@backstage/cli-module-actions": "workspace:^" + "@backstage/cli-module-auth": "workspace:^" + "@backstage/cli-module-build": "workspace:^" + "@backstage/cli-module-config": "workspace:^" + "@backstage/cli-module-github": "workspace:^" + "@backstage/cli-module-info": "workspace:^" + "@backstage/cli-module-lint": "workspace:^" + "@backstage/cli-module-maintenance": "workspace:^" + "@backstage/cli-module-migrate": "workspace:^" + "@backstage/cli-module-new": "workspace:^" + "@backstage/cli-module-test-jest": "workspace:^" + "@backstage/cli-module-translations": "workspace:^" + languageName: unknown + linkType: soft + +"@backstage/cli-module-actions@workspace:^, @backstage/cli-module-actions@workspace:packages/cli-module-actions": + version: 0.0.0-use.local + resolution: "@backstage/cli-module-actions@workspace:packages/cli-module-actions" + dependencies: + "@backstage/backend-test-utils": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/cli-node": "workspace:^" + "@backstage/errors": "workspace:^" + chalk: "npm:^4.0.0" + cleye: "npm:^2.3.0" + marked: "npm:^15.0.12" + marked-terminal: "npm:^7.3.0" + strip-ansi: "npm:^7.1.0" + zod: "npm:^3.25.76 || ^4.0.0" + bin: + cli-module-actions: bin/backstage-cli-module-actions + languageName: unknown + linkType: soft + +"@backstage/cli-module-auth@workspace:^, @backstage/cli-module-auth@workspace:packages/cli-module-auth": + version: 0.0.0-use.local + resolution: "@backstage/cli-module-auth@workspace:packages/cli-module-auth" + dependencies: + "@backstage/backend-test-utils": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/cli-node": "workspace:^" + "@backstage/errors": "workspace:^" + "@types/fs-extra": "npm:^11.0.0" + "@types/proper-lockfile": "npm:^4" + cleye: "npm:^2.3.0" + fs-extra: "npm:^11.2.0" + glob: "npm:^13.0.0" + inquirer: "npm:^8.2.0" + keytar: "npm:^7.9.0" + proper-lockfile: "npm:^4.1.2" + yaml: "npm:^2.0.0" + zod: "npm:^3.25.76" + dependenciesMeta: + keytar: + optional: true + bin: + cli-module-auth: bin/backstage-cli-module-auth + languageName: unknown + linkType: soft + +"@backstage/cli-module-build@workspace:^, @backstage/cli-module-build@workspace:packages/cli-module-build": + version: 0.0.0-use.local + resolution: "@backstage/cli-module-build@workspace:packages/cli-module-build" + dependencies: + "@backstage/backend-test-utils": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/cli-common": "workspace:^" + "@backstage/cli-node": "workspace:^" + "@backstage/config": "workspace:^" + "@backstage/config-loader": "workspace:^" + "@backstage/errors": "workspace:^" + "@backstage/module-federation-common": "workspace:^" + "@manypkg/get-packages": "npm:^1.1.3" + "@module-federation/enhanced": "npm:^2.3.3" + "@pmmmwh/react-refresh-webpack-plugin": "npm:^0.6.0" + "@rollup/plugin-commonjs": "npm:^26.0.0" + "@rollup/plugin-json": "npm:^6.0.0" + "@rollup/plugin-node-resolve": "npm:^15.0.0" + "@rollup/plugin-yaml": "npm:^4.0.0" + "@rspack/core": "npm:^1.4.11" + "@rspack/dev-server": "npm:^1.1.4" + "@rspack/plugin-react-refresh": "npm:^1.4.3" + "@swc/core": "npm:^1.15.6" + "@types/fs-extra": "npm:^11.0.0" + "@types/lodash": "npm:^4.14.151" + "@types/npm-packlist": "npm:^3.0.0" + "@types/shell-quote": "npm:^1.7.5" + bfj: "npm:^9.0.2" + buffer: "npm:^6.0.3" + chalk: "npm:^4.0.0" + chokidar: "npm:^3.3.1" + cleye: "npm:^2.3.0" + cross-spawn: "npm:^7.0.3" + css-loader: "npm:^6.5.1" + ctrlc-windows: "npm:^2.1.0" + embedded-postgres: "npm:18.3.0-beta.16" + esbuild-loader: "npm:^4.0.0" + eslint-rspack-plugin: "npm:^4.2.1" + eslint-webpack-plugin: "npm:^4.2.0" + fork-ts-checker-webpack-plugin: "npm:^9.0.0" + fs-extra: "npm:^11.2.0" + glob: "npm:^13.0.0" + html-webpack-plugin: "npm:^5.6.3" + lodash: "npm:^4.17.21" + mini-css-extract-plugin: "npm:^2.4.2" + node-stdlib-browser: "npm:^1.3.1" + npm-packlist: "npm:^5.0.0" + p-queue: "npm:^6.6.2" + portfinder: "npm:^1.0.32" + postcss: "npm:^8.1.0" + postcss-import: "npm:^16.1.0" + process: "npm:^0.11.10" + raw-loader: "npm:^4.0.2" + react-dev-utils: "npm:^12.0.0-next.60" + react-refresh: "npm:^0.18.0" + rollup: "npm:^4.59.0" + rollup-plugin-dts: "npm:^6.1.0" + rollup-plugin-esbuild: "npm:^6.1.1" + rollup-plugin-postcss: "npm:^4.0.0" + rollup-pluginutils: "npm:^2.8.2" + shell-quote: "npm:^1.8.1" + style-loader: "npm:^3.3.1" + swc-loader: "npm:^0.2.3" + tar: "npm:^7.5.6" + ts-checker-rspack-plugin: "npm:^1.1.5" + ts-morph: "npm:^24.0.0" + util: "npm:^0.12.3" + webpack: "npm:~5.105.0" + webpack-dev-server: "npm:^5.0.0" + yml-loader: "npm:^2.1.0" + yn: "npm:^4.0.0" + peerDependencies: + embedded-postgres: ^18.3.0-beta.16 + peerDependenciesMeta: + embedded-postgres: + optional: true + bin: + cli-module-build: bin/backstage-cli-module-build + languageName: unknown + linkType: soft + +"@backstage/cli-module-config@workspace:^, @backstage/cli-module-config@workspace:packages/cli-module-config": + version: 0.0.0-use.local + resolution: "@backstage/cli-module-config@workspace:packages/cli-module-config" + dependencies: + "@backstage/cli": "workspace:^" + "@backstage/cli-common": "workspace:^" + "@backstage/cli-node": "workspace:^" + "@backstage/config": "workspace:^" + "@backstage/config-loader": "workspace:^" + "@backstage/types": "workspace:^" + "@manypkg/get-packages": "npm:^1.1.3" + "@types/json-schema": "npm:^7.0.6" + chalk: "npm:^4.0.0" + cleye: "npm:^2.3.0" + json-schema: "npm:^0.4.0" + react-dev-utils: "npm:^12.0.0-next.60" + yaml: "npm:^2.0.0" + bin: + cli-module-config: bin/backstage-cli-module-config + languageName: unknown + linkType: soft + +"@backstage/cli-module-github@workspace:^, @backstage/cli-module-github@workspace:packages/cli-module-github": + version: 0.0.0-use.local + resolution: "@backstage/cli-module-github@workspace:packages/cli-module-github" + dependencies: + "@backstage/cli": "workspace:^" + "@backstage/cli-common": "workspace:^" + "@backstage/cli-node": "workspace:^" + "@octokit/request": "npm:^8.0.0" + "@types/express": "npm:^4.17.6" + "@types/fs-extra": "npm:^11.0.0" + chalk: "npm:^4.0.0" + cleye: "npm:^2.3.0" + express: "npm:^4.22.0" + fs-extra: "npm:^11.2.0" + inquirer: "npm:^8.2.0" + react-dev-utils: "npm:^12.0.0-next.60" + yaml: "npm:^2.0.0" + bin: + cli-module-github: bin/backstage-cli-module-github + languageName: unknown + linkType: soft + +"@backstage/cli-module-info@workspace:^, @backstage/cli-module-info@workspace:packages/cli-module-info": + version: 0.0.0-use.local + resolution: "@backstage/cli-module-info@workspace:packages/cli-module-info" + dependencies: + "@backstage/cli": "workspace:^" + "@backstage/cli-common": "workspace:^" + "@backstage/cli-node": "workspace:^" + "@types/fs-extra": "npm:^11.0.0" + cleye: "npm:^2.3.0" + fs-extra: "npm:^11.2.0" + minimatch: "npm:^10.2.1" + bin: + cli-module-info: bin/backstage-cli-module-info + languageName: unknown + linkType: soft + +"@backstage/cli-module-lint@workspace:^, @backstage/cli-module-lint@workspace:packages/cli-module-lint": + version: 0.0.0-use.local + resolution: "@backstage/cli-module-lint@workspace:packages/cli-module-lint" + dependencies: + "@backstage/cli": "workspace:^" + "@backstage/cli-common": "workspace:^" + "@backstage/cli-node": "workspace:^" + "@types/fs-extra": "npm:^11.0.0" + "@types/shell-quote": "npm:^1.7.5" + chalk: "npm:^4.0.0" + cleye: "npm:^2.3.0" + eslint: "npm:^8.6.0" + eslint-formatter-friendly: "npm:^7.0.0" + fs-extra: "npm:^11.2.0" + globby: "npm:^11.1.0" + shell-quote: "npm:^1.8.1" + bin: + cli-module-lint: bin/backstage-cli-module-lint + languageName: unknown + linkType: soft + +"@backstage/cli-module-maintenance@workspace:^, @backstage/cli-module-maintenance@workspace:packages/cli-module-maintenance": + version: 0.0.0-use.local + resolution: "@backstage/cli-module-maintenance@workspace:packages/cli-module-maintenance" + dependencies: + "@backstage/cli": "workspace:^" + "@backstage/cli-common": "workspace:^" + "@backstage/cli-node": "workspace:^" + "@types/fs-extra": "npm:^11.0.0" + chalk: "npm:^4.0.0" + cleye: "npm:^2.3.0" + eslint: "npm:^8.6.0" + fs-extra: "npm:^11.2.0" + bin: + cli-module-maintenance: bin/backstage-cli-module-maintenance + languageName: unknown + linkType: soft + +"@backstage/cli-module-migrate@workspace:^, @backstage/cli-module-migrate@workspace:packages/cli-module-migrate": + version: 0.0.0-use.local + resolution: "@backstage/cli-module-migrate@workspace:packages/cli-module-migrate" + dependencies: + "@backstage/backend-test-utils": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/cli-common": "workspace:^" + "@backstage/cli-node": "workspace:^" + "@backstage/errors": "workspace:^" + "@backstage/release-manifests": "workspace:^" + "@backstage/test-utils": "workspace:^" + "@manypkg/get-packages": "npm:^1.1.3" + "@types/fs-extra": "npm:^11.0.0" + "@types/semver": "npm:^7" + chalk: "npm:^4.0.0" + cleye: "npm:^2.3.0" + fs-extra: "npm:^11.2.0" + minimatch: "npm:^10.2.1" + msw: "npm:^1.0.0" + ora: "npm:^5.3.0" + replace-in-file: "npm:^7.1.0" + semver: "npm:^7.5.3" + bin: + cli-module-migrate: bin/backstage-cli-module-migrate + languageName: unknown + linkType: soft + +"@backstage/cli-module-new@workspace:^, @backstage/cli-module-new@workspace:packages/cli-module-new": + version: 0.0.0-use.local + resolution: "@backstage/cli-module-new@workspace:packages/cli-module-new" + dependencies: + "@backstage/backend-test-utils": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/cli-common": "workspace:^" + "@backstage/cli-node": "workspace:^" + "@backstage/core-plugin-api": "workspace:^" + "@backstage/errors": "workspace:^" + "@backstage/test-utils": "workspace:^" + "@types/fs-extra": "npm:^11.0.0" + "@types/inquirer": "npm:^8.1.3" + "@types/lodash": "npm:^4.14.151" + "@types/recursive-readdir": "npm:^2.2.0" + chalk: "npm:^4.0.0" + cleye: "npm:^2.3.0" + fs-extra: "npm:^11.2.0" + handlebars: "npm:^4.7.3" + inquirer: "npm:^8.2.0" + lodash: "npm:^4.17.21" + ora: "npm:^5.3.0" + recursive-readdir: "npm:^2.2.2" + semver: "npm:^7.5.3" + yaml: "npm:^2.0.0" + zod: "npm:^3.25.76" + zod-validation-error: "npm:^4.0.2" + bin: + cli-module-new: bin/backstage-cli-module-new + languageName: unknown + linkType: soft + +"@backstage/cli-module-test-jest@workspace:^, @backstage/cli-module-test-jest@workspace:packages/cli-module-test-jest": + version: 0.0.0-use.local + resolution: "@backstage/cli-module-test-jest@workspace:packages/cli-module-test-jest" + dependencies: + "@backstage/cli": "workspace:^" + "@backstage/cli-common": "workspace:^" + "@backstage/cli-node": "workspace:^" + "@swc/core": "npm:^1.15.6" + "@swc/jest": "npm:^0.2.39" + cleye: "npm:^2.3.0" + cross-fetch: "npm:^4.0.0" + fs-extra: "npm:^11.2.0" + glob: "npm:^13.0.0" + jest-css-modules: "npm:^2.1.0" + sucrase: "npm:^3.20.2" + yargs: "npm:^16.2.0" + peerDependencies: + "@jest/environment-jsdom-abstract": ^30.0.0 + jest: ^29.0.0 || ^30.0.0 + jest-environment-jsdom: "*" + jsdom: ^27.1.0 + peerDependenciesMeta: + "@jest/environment-jsdom-abstract": + optional: true + jest-environment-jsdom: + optional: true + jsdom: + optional: true + bin: + cli-module-test-jest: bin/backstage-cli-module-test-jest + languageName: unknown + linkType: soft + +"@backstage/cli-module-translations@workspace:^, @backstage/cli-module-translations@workspace:packages/cli-module-translations": + version: 0.0.0-use.local + resolution: "@backstage/cli-module-translations@workspace:packages/cli-module-translations" + dependencies: + "@backstage/cli": "workspace:^" + "@backstage/cli-common": "workspace:^" + "@backstage/cli-node": "workspace:^" + "@types/fs-extra": "npm:^11.0.0" + cleye: "npm:^2.3.0" + fs-extra: "npm:^11.2.0" + ts-morph: "npm:^24.0.0" + bin: + cli-module-translations: bin/backstage-cli-module-translations languageName: unknown linkType: soft @@ -3230,10 +3202,27 @@ __metadata: "@backstage/test-utils": "workspace:^" "@backstage/types": "workspace:^" "@manypkg/get-packages": "npm:^1.1.3" + "@types/proper-lockfile": "npm:^4" + "@types/yarnpkg__lockfile": "npm:^1.1.4" + "@yarnpkg/lockfile": "npm:^1.1.0" "@yarnpkg/parsers": "npm:^3.0.0" + chalk: "npm:^4.0.0" + commander: "npm:^12.0.0" fs-extra: "npm:^11.2.0" + keytar: "npm:^7.9.0" + pirates: "npm:^4.0.6" + proper-lockfile: "npm:^4.1.2" semver: "npm:^7.5.3" - zod: "npm:^3.22.4" + yaml: "npm:^2.0.0" + zod: "npm:^3.25.76 || ^4.0.0" + peerDependencies: + "@swc/core": ^1.15.6 + dependenciesMeta: + keytar: + optional: true + peerDependenciesMeta: + "@swc/core": + optional: true languageName: unknown linkType: soft @@ -3244,81 +3233,43 @@ __metadata: "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-client": "workspace:^" - "@backstage/catalog-model": "workspace:^" "@backstage/cli-common": "workspace:^" + "@backstage/cli-defaults": "workspace:^" + "@backstage/cli-module-build": "workspace:^" + "@backstage/cli-module-test-jest": "workspace:^" "@backstage/cli-node": "workspace:^" "@backstage/config": "workspace:^" - "@backstage/config-loader": "workspace:^" "@backstage/core-app-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/eslint-plugin": "workspace:^" - "@backstage/integration": "workspace:^" "@backstage/plugin-auth-backend": "workspace:^" "@backstage/plugin-auth-backend-module-guest-provider": "workspace:^" "@backstage/plugin-catalog-node": "workspace:^" "@backstage/plugin-scaffolder-node": "workspace:^" "@backstage/plugin-scaffolder-node-test-utils": "workspace:^" - "@backstage/release-manifests": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/theme": "workspace:^" - "@backstage/types": "workspace:^" + "@jest/environment-jsdom-abstract": "npm:^30.0.0" "@manypkg/get-packages": "npm:^1.1.3" - "@module-federation/enhanced": "npm:^0.9.0" - "@octokit/request": "npm:^8.0.0" - "@pmmmwh/react-refresh-webpack-plugin": "npm:^0.5.7" - "@rollup/plugin-commonjs": "npm:^26.0.0" - "@rollup/plugin-json": "npm:^6.0.0" - "@rollup/plugin-node-resolve": "npm:^15.0.0" - "@rollup/plugin-yaml": "npm:^4.0.0" - "@rspack/core": "npm:^1.4.11" - "@rspack/dev-server": "npm:^1.1.4" - "@rspack/plugin-react-refresh": "npm:^1.4.3" "@spotify/eslint-config-base": "npm:^15.0.0" "@spotify/eslint-config-react": "npm:^15.0.0" "@spotify/eslint-config-typescript": "npm:^15.0.0" - "@swc/core": "npm:^1.3.46" - "@swc/helpers": "npm:^0.5.0" - "@swc/jest": "npm:^0.2.22" - "@types/cross-spawn": "npm:^6.0.2" - "@types/ejs": "npm:^3.1.3" - "@types/express": "npm:^4.17.6" + "@swc/core": "npm:^1.15.6" + "@swc/jest": "npm:^0.2.39" "@types/fs-extra": "npm:^11.0.0" - "@types/http-proxy": "npm:^1.17.4" - "@types/inquirer": "npm:^8.1.3" - "@types/jest": "npm:^29.5.11" - "@types/node": "npm:^20.16.0" - "@types/npm-packlist": "npm:^3.0.0" - "@types/recursive-readdir": "npm:^2.2.0" - "@types/rollup-plugin-peer-deps-external": "npm:^2.2.0" - "@types/rollup-plugin-postcss": "npm:^3.1.4" - "@types/svgo": "npm:^2.6.2" - "@types/tar": "npm:^6.1.1" - "@types/terser-webpack-plugin": "npm:^5.0.4" + "@types/jest": "npm:^30.0.0" + "@types/node": "npm:^22.13.14" "@types/webpack-env": "npm:^1.15.2" - "@types/webpack-sources": "npm:^3.2.3" - "@types/yarnpkg__lockfile": "npm:^1.1.4" "@typescript-eslint/eslint-plugin": "npm:^8.17.0" "@typescript-eslint/parser": "npm:^8.16.0" - "@yarnpkg/lockfile": "npm:^1.1.0" - "@yarnpkg/parsers": "npm:^3.0.0" - bfj: "npm:^8.0.0" - buffer: "npm:^6.0.3" chalk: "npm:^4.0.0" - chokidar: "npm:^3.3.1" - commander: "npm:^12.0.0" + commander: "npm:^14.0.3" cross-fetch: "npm:^4.0.0" - cross-spawn: "npm:^7.0.3" - css-loader: "npm:^6.5.1" - ctrlc-windows: "npm:^2.1.0" - del: "npm:^8.0.0" - esbuild: "npm:^0.25.0" - esbuild-loader: "npm:^4.0.0" eslint: "npm:^8.6.0" eslint-config-prettier: "npm:^9.0.0" - eslint-formatter-friendly: "npm:^7.0.0" eslint-plugin-deprecation: "npm:^3.0.0" eslint-plugin-import: "npm:^2.31.0" eslint-plugin-jest: "npm:^28.9.0" @@ -3326,92 +3277,27 @@ __metadata: eslint-plugin-react: "npm:^7.37.2" eslint-plugin-react-hooks: "npm:^5.0.0" eslint-plugin-unused-imports: "npm:^4.1.4" - eslint-rspack-plugin: "npm:^4.2.1" - eslint-webpack-plugin: "npm:^4.2.0" - express: "npm:^4.17.1" - fork-ts-checker-webpack-plugin: "npm:^9.0.0" fs-extra: "npm:^11.2.0" - git-url-parse: "npm:^15.0.0" - glob: "npm:^7.1.7" - global-agent: "npm:^3.0.0" - globby: "npm:^11.1.0" - handlebars: "npm:^4.7.3" - html-webpack-plugin: "npm:^5.6.3" - inquirer: "npm:^8.2.0" - jest: "npm:^29.7.0" - jest-cli: "npm:^29.7.0" + glob: "npm:^13.0.0" + jest: "npm:^30.2.0" jest-css-modules: "npm:^2.1.0" - jest-environment-jsdom: "npm:^29.0.2" - jest-runtime: "npm:^29.0.2" - json-schema: "npm:^0.4.0" - lodash: "npm:^4.17.21" - mini-css-extract-plugin: "npm:^2.4.2" - minimatch: "npm:^9.0.0" - msw: "npm:^1.0.0" - node-stdlib-browser: "npm:^1.3.1" + jsdom: "npm:^27.1.0" nodemon: "npm:^3.0.1" - npm-packlist: "npm:^5.0.0" - ora: "npm:^5.3.0" - p-queue: "npm:^6.6.2" pirates: "npm:^4.0.6" postcss: "npm:^8.1.0" - process: "npm:^0.11.10" - raw-loader: "npm:^4.0.2" - react-dev-utils: "npm:^12.0.0-next.60" - react-refresh: "npm:^0.17.0" - recursive-readdir: "npm:^2.2.2" - replace-in-file: "npm:^7.1.0" - rollup: "npm:^4.27.3" - rollup-plugin-dts: "npm:^6.1.0" - rollup-plugin-esbuild: "npm:^6.1.1" - rollup-plugin-postcss: "npm:^4.0.0" - rollup-pluginutils: "npm:^2.8.2" - semver: "npm:^7.5.3" - style-loader: "npm:^3.3.1" sucrase: "npm:^3.20.2" - swc-loader: "npm:^0.2.3" - tar: "npm:^6.1.12" - terser-webpack-plugin: "npm:^5.1.3" - ts-checker-rspack-plugin: "npm:^1.1.5" - ts-morph: "npm:^24.0.0" - undici: "npm:^7.2.3" - util: "npm:^0.12.3" - webpack: "npm:~5.96.0" - webpack-dev-server: "npm:^5.0.0" yaml: "npm:^2.0.0" - yargs: "npm:^16.2.0" - yml-loader: "npm:^2.1.0" - yn: "npm:^4.0.0" - zod: "npm:^3.22.4" - zod-validation-error: "npm:^3.4.0" peerDependencies: - "@module-federation/enhanced": ^0.9.0 - "@pmmmwh/react-refresh-webpack-plugin": ^0.5.7 - esbuild-loader: ^4.0.0 - eslint-webpack-plugin: ^4.2.0 - fork-ts-checker-webpack-plugin: ^9.0.0 - mini-css-extract-plugin: ^2.4.2 - terser-webpack-plugin: ^5.1.3 - webpack: ~5.96.0 - webpack-dev-server: ^5.0.0 + "@jest/environment-jsdom-abstract": ^30.0.0 + jest: ^29.0.0 || ^30.0.0 + jest-environment-jsdom: "*" + jsdom: ^27.1.0 peerDependenciesMeta: - "@module-federation/enhanced": + "@jest/environment-jsdom-abstract": optional: true - "@pmmmwh/react-refresh-webpack-plugin": + jest-environment-jsdom: optional: true - esbuild-loader: - optional: true - eslint-webpack-plugin: - optional: true - fork-ts-checker-webpack-plugin: - optional: true - mini-css-extract-plugin: - optional: true - terser-webpack-plugin: - optional: true - webpack: - optional: true - webpack-dev-server: + jsdom: optional: true bin: backstage-cli: bin/backstage-cli @@ -3425,9 +3311,9 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/cli-common": "workspace:^" "@types/jscodeshift": "npm:^0.12.0" - "@types/node": "npm:^20.16.0" + "@types/node": "npm:^22.13.14" chalk: "npm:^4.0.0" - commander: "npm:^12.0.0" + commander: "npm:^14.0.3" jscodeshift: "npm:^0.16.0" jscodeshift-add-imports: "npm:^1.0.10" bin: @@ -3456,8 +3342,8 @@ __metadata: json-schema-traverse: "npm:^1.0.0" lodash: "npm:^4.17.21" minimist: "npm:^1.2.5" - msw: "npm:^1.0.0" - typescript-json-schema: "npm:^0.65.0" + msw: "npm:^2.0.0" + typescript-json-schema: "npm:^0.67.0" yaml: "npm:^2.0.0" zen-observable: "npm:^0.10.0" languageName: unknown @@ -3484,6 +3370,7 @@ __metadata: "@backstage/core-plugin-api": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/types": "workspace:^" + "@backstage/ui": "workspace:^" "@backstage/version-bridge": "workspace:^" "@testing-library/dom": "npm:^10.0.0" "@testing-library/jest-dom": "npm:^6.0.0" @@ -3501,18 +3388,18 @@ __metadata: react: "npm:^18.0.2" react-dom: "npm:^18.0.2" react-router-beta: "npm:react-router@6.0.0-beta.0" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" react-router-dom-beta: "npm:react-router-dom@6.0.0-beta.0" react-router-dom-stable: "npm:react-router-dom@^6.3.0" react-router-stable: "npm:react-router@^6.3.0" react-use: "npm:^17.2.4" zen-observable: "npm:^0.10.0" - zod: "npm:^3.22.4" + zod: "npm:^3.25.76 || ^4.0.0" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -3526,9 +3413,12 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/core-app-api": "workspace:^" "@backstage/core-plugin-api": "workspace:^" + "@backstage/errors": "workspace:^" + "@backstage/filter-predicates": "workspace:^" "@backstage/frontend-app-api": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" "@backstage/frontend-test-utils": "workspace:^" + "@backstage/plugin-app-react": "workspace:^" "@backstage/plugin-catalog": "workspace:^" "@backstage/plugin-catalog-react": "workspace:^" "@backstage/test-utils": "workspace:^" @@ -3540,13 +3430,12 @@ __metadata: lodash: "npm:^4.17.21" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" - zod: "npm:^3.22.4" + react-router-dom: "npm:^6.30.2" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -3593,7 +3482,9 @@ __metadata: "@types/zen-observable": "npm:^0.8.0" ansi-regex: "npm:^6.0.1" classnames: "npm:^2.2.6" + copy-to-clipboard: "npm:^3.3.1" cross-fetch: "npm:^4.0.0" + csstype: "npm:^3.0.2" d3-selection: "npm:^3.0.0" d3-shape: "npm:^3.0.0" d3-zoom: "npm:^3.0.0" @@ -3603,7 +3494,7 @@ __metadata: linkifyjs: "npm:4.3.2" lodash: "npm:^4.17.21" msw: "npm:^1.0.0" - pluralize: "npm:^8.0.0" + parse5: "npm:^6.0.0" qs: "npm:^6.9.4" rc-progress: "npm:3.5.1" react: "npm:^18.0.2" @@ -3613,20 +3504,22 @@ __metadata: react-hook-form: "npm:^7.12.2" react-idle-timer: "npm:5.7.2" react-markdown: "npm:^8.0.0" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" react-sparklines: "npm:^1.7.0" react-syntax-highlighter: "npm:^15.4.5" react-use: "npm:^17.3.2" react-virtualized-auto-sizer: "npm:^1.0.11" react-window: "npm:^1.8.6" + rehype-raw: "npm:^6.0.0" + rehype-sanitize: "npm:^5.0.0" remark-gfm: "npm:^3.0.1" zen-observable: "npm:^0.10.0" - zod: "npm:^3.22.4" + zod: "npm:^3.25.76 || ^4.0.0" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -3641,6 +3534,7 @@ __metadata: "@backstage/config": "workspace:^" "@backstage/core-app-api": "workspace:^" "@backstage/errors": "workspace:^" + "@backstage/frontend-plugin-api": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/types": "workspace:^" "@backstage/version-bridge": "workspace:^" @@ -3652,12 +3546,13 @@ __metadata: history: "npm:^5.0.0" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" + zod: "npm:^3.25.76 || ^4.0.0" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -3674,10 +3569,10 @@ __metadata: "@types/command-exists": "npm:^1.2.0" "@types/fs-extra": "npm:^11.0.0" "@types/inquirer": "npm:^8.1.3" - "@types/node": "npm:^20.16.0" + "@types/node": "npm:^22.13.14" "@types/recursive-readdir": "npm:^2.2.0" chalk: "npm:^4.0.0" - commander: "npm:^12.0.0" + commander: "npm:^14.0.3" fs-extra: "npm:^11.2.0" handlebars: "npm:^4.7.3" inquirer: "npm:^8.2.0" @@ -3714,14 +3609,14 @@ __metadata: "@types/react": "npm:^18.0.0" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" react-use: "npm:^17.2.4" zen-observable: "npm:^0.10.0" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -3763,7 +3658,20 @@ __metadata: "@manypkg/get-packages": "npm:^1.1.3" "@types/estree": "npm:^1.0.5" eslint: "npm:^8.33.0" - minimatch: "npm:^9.0.0" + minimatch: "npm:^10.2.1" + languageName: unknown + linkType: soft + +"@backstage/filter-predicates@workspace:^, @backstage/filter-predicates@workspace:packages/filter-predicates": + version: 0.0.0-use.local + resolution: "@backstage/filter-predicates@workspace:packages/filter-predicates" + dependencies: + "@backstage/cli": "workspace:^" + "@backstage/config": "workspace:^" + "@backstage/errors": "workspace:^" + "@backstage/types": "workspace:^" + zod: "npm:^3.25.76 || ^4.0.0" + zod-validation-error: "npm:^4.0.2" languageName: unknown linkType: soft @@ -3776,10 +3684,12 @@ __metadata: "@backstage/core-app-api": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/errors": "workspace:^" + "@backstage/filter-predicates": "workspace:^" "@backstage/frontend-defaults": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" "@backstage/frontend-test-utils": "workspace:^" "@backstage/plugin-app": "workspace:^" + "@backstage/plugin-permission-common": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/types": "workspace:^" "@backstage/version-bridge": "workspace:^" @@ -3789,13 +3699,13 @@ __metadata: lodash: "npm:^4.17.21" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" - zod: "npm:^3.22.4" + react-router-dom: "npm:^6.30.2" + zod: "npm:^3.25.76 || ^4.0.0" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -3814,6 +3724,9 @@ __metadata: "@backstage/frontend-app-api": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" "@backstage/plugin-app": "workspace:^" + "@backstage/plugin-app-react": "workspace:^" + "@backstage/plugin-permission-common": "workspace:^" + "@backstage/plugin-permission-react": "workspace:^" "@backstage/test-utils": "workspace:^" "@react-hookz/web": "npm:^24.0.0" "@testing-library/jest-dom": "npm:^6.0.0" @@ -3821,12 +3734,39 @@ __metadata: "@types/react": "npm:^18.0.0" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 + peerDependenciesMeta: + "@types/react": + optional: true + languageName: unknown + linkType: soft + +"@backstage/frontend-dev-utils@workspace:^, @backstage/frontend-dev-utils@workspace:packages/frontend-dev-utils": + version: 0.0.0-use.local + resolution: "@backstage/frontend-dev-utils@workspace:packages/frontend-dev-utils" + dependencies: + "@backstage/cli": "workspace:^" + "@backstage/frontend-defaults": "workspace:^" + "@backstage/frontend-plugin-api": "workspace:^" + "@backstage/plugin-app": "workspace:^" + "@backstage/test-utils": "workspace:^" + "@backstage/ui": "workspace:^" + "@testing-library/jest-dom": "npm:^6.0.0" + "@testing-library/react": "npm:^16.0.0" + "@types/react": "npm:^18.0.0" + react: "npm:^18.0.2" + react-dom: "npm:^18.0.2" + react-router-dom: "npm:^6.30.2" + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -3840,10 +3780,11 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" + "@backstage/module-federation-common": "workspace:^" "@backstage/test-utils": "workspace:^" - "@module-federation/enhanced": "npm:^0.9.0" - "@module-federation/runtime": "npm:^0.9.0" - "@module-federation/sdk": "npm:^0.9.0" + "@module-federation/enhanced": "npm:^2.3.3" + "@module-federation/runtime": "npm:^2.3.3" + "@module-federation/sdk": "npm:^2.3.3" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" "@types/react": "npm:^18.0.0" @@ -3851,13 +3792,13 @@ __metadata: msw: "npm:^1.0.0" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" uri-template: "npm:^2.0.0" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -3869,29 +3810,29 @@ __metadata: resolution: "@backstage/frontend-plugin-api@workspace:packages/frontend-plugin-api" dependencies: "@backstage/cli": "workspace:^" - "@backstage/core-components": "workspace:^" - "@backstage/core-plugin-api": "workspace:^" + "@backstage/config": "workspace:^" + "@backstage/errors": "workspace:^" + "@backstage/filter-predicates": "workspace:^" "@backstage/frontend-app-api": "workspace:^" "@backstage/frontend-test-utils": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/types": "workspace:^" "@backstage/version-bridge": "workspace:^" - "@material-ui/core": "npm:^4.12.4" + "@standard-schema/spec": "npm:^1.1.0" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" "@types/react": "npm:^18.0.0" history: "npm:^5.3.0" - lodash: "npm:^4.17.21" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" - zod: "npm:^3.22.4" - zod-to-json-schema: "npm:^3.21.4" + react-router-dom: "npm:^6.30.2" + zod: "npm:^4.0.0" + zod-to-json-schema: "npm:^3.25.1" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -3904,25 +3845,39 @@ __metadata: dependencies: "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" + "@backstage/core-app-api": "workspace:^" + "@backstage/core-plugin-api": "workspace:^" + "@backstage/filter-predicates": "workspace:^" "@backstage/frontend-app-api": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" "@backstage/plugin-app": "workspace:^" + "@backstage/plugin-app-react": "workspace:^" + "@backstage/plugin-permission-common": "workspace:^" + "@backstage/plugin-permission-react": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/types": "workspace:^" "@backstage/version-bridge": "workspace:^" "@testing-library/jest-dom": "npm:^6.0.0" + "@types/jest": "npm:*" "@types/react": "npm:^18.0.0" + "@types/zen-observable": "npm:^0.8.0" + i18next: "npm:^22.4.15" + msw: "npm:^2.0.0" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" - zod: "npm:^3.22.4" + react-router-dom: "npm:^6.30.2" + zen-observable: "npm:^0.10.0" + zod: "npm:^3.25.76 || ^4.0.0" peerDependencies: "@testing-library/react": ^16.0.0 + "@types/jest": "*" "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: + "@types/jest": + optional: true "@types/react": optional: true languageName: unknown @@ -3966,12 +3921,12 @@ __metadata: msw: "npm:^1.0.0" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -3984,18 +3939,49 @@ __metadata: dependencies: "@azure/identity": "npm:^4.0.0" "@azure/storage-blob": "npm:^12.5.0" + "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/config-loader": "workspace:^" "@backstage/errors": "workspace:^" "@octokit/auth-app": "npm:^4.0.0" "@octokit/rest": "npm:^19.0.3" - "@types/luxon": "npm:^3.0.0" cross-fetch: "npm:^4.0.0" git-url-parse: "npm:^15.0.0" lodash: "npm:^4.17.21" luxon: "npm:^3.0.0" msw: "npm:^1.0.0" + p-throttle: "npm:^4.1.1" + languageName: unknown + linkType: soft + +"@backstage/module-federation-common@workspace:^, @backstage/module-federation-common@workspace:packages/module-federation-common": + version: 0.0.0-use.local + resolution: "@backstage/module-federation-common@workspace:packages/module-federation-common" + dependencies: + "@backstage/cli": "workspace:^" + "@backstage/config": "workspace:^" + "@backstage/errors": "workspace:^" + "@backstage/test-utils": "workspace:^" + "@backstage/types": "workspace:^" + "@module-federation/runtime": "npm:^2.3.3" + "@types/react": "npm:^18.0.0" + react: "npm:^18.0.2" + react-dom: "npm:^18.0.2" + react-router-dom: "npm:^6.30.2" + peerDependencies: + "@emotion/react": ^11.10.5 + "@material-ui/core": ^4.12.2 + "@material-ui/styles": ^4.10.0 + "@mui/material": ^5.12.2 + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + react-router: ^6.30.2 + react-router-dom: ^6.30.2 + peerDependenciesMeta: + "@types/react": + optional: true languageName: unknown linkType: soft @@ -4009,12 +3995,12 @@ __metadata: grpc-docs: "npm:^1.1.2" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -4029,40 +4015,42 @@ __metadata: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/core-app-api": "workspace:^" - "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" + "@backstage/frontend-test-utils": "workspace:^" "@backstage/plugin-catalog": "workspace:^" "@backstage/plugin-catalog-common": "workspace:^" "@backstage/plugin-catalog-react": "workspace:^" "@backstage/plugin-permission-react": "workspace:^" "@backstage/test-utils": "workspace:^" - "@graphiql/react": "npm:^0.23.0" + "@backstage/ui": "workspace:^" + "@graphiql/react": "npm:0.29.0" "@material-ui/core": "npm:^4.12.2" "@material-ui/icons": "npm:^4.9.1" "@material-ui/lab": "npm:4.0.0-alpha.61" + "@remixicon/react": "npm:^4.6.0" "@testing-library/dom": "npm:^10.0.0" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" "@testing-library/user-event": "npm:^14.0.0" "@types/highlightjs": "npm:^10.1.0" "@types/react": "npm:^18.0.0" - "@types/swagger-ui-react": "npm:^4.18.0" - graphiql: "npm:3.1.1" + "@types/swagger-ui-react": "npm:^5.0.0" + graphiql: "npm:^3.9.0" graphql: "npm:^16.0.0" - graphql-config: "npm:^5.0.2" + graphql-config: "npm:^5.1.6" graphql-ws: "npm:^5.4.1" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" swagger-ui-react: "npm:^5.27.1" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -4086,7 +4074,7 @@ __metadata: "@backstage/types": "workspace:^" "@types/express": "npm:^4.17.6" "@types/supertest": "npm:^2.0.8" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" fs-extra: "npm:^11.2.0" globby: "npm:^11.0.0" @@ -4095,7 +4083,6 @@ __metadata: lodash: "npm:^4.17.21" luxon: "npm:^3.0.0" supertest: "npm:^7.0.0" - yn: "npm:^4.0.0" languageName: unknown linkType: soft @@ -4107,11 +4094,38 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/config-loader": "workspace:^" "@types/express": "npm:^4.17.6" - express: "npm:^4.17.1" + express: "npm:^4.22.0" fs-extra: "npm:^11.2.0" languageName: unknown linkType: soft +"@backstage/plugin-app-react@workspace:^, @backstage/plugin-app-react@workspace:plugins/app-react": + version: 0.0.0-use.local + resolution: "@backstage/plugin-app-react@workspace:plugins/app-react" + dependencies: + "@backstage/cli": "workspace:^" + "@backstage/core-plugin-api": "workspace:^" + "@backstage/frontend-plugin-api": "workspace:^" + "@backstage/frontend-test-utils": "workspace:^" + "@backstage/test-utils": "workspace:^" + "@material-ui/core": "npm:^4.9.13" + "@testing-library/jest-dom": "npm:^6.0.0" + "@testing-library/react": "npm:^16.0.0" + "@types/react": "npm:^18.0.0" + react: "npm:^18.0.2" + react-dom: "npm:^18.0.2" + react-router-dom: "npm:^6.30.2" + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + react-router-dom: ^6.30.2 + peerDependenciesMeta: + "@types/react": + optional: true + languageName: unknown + linkType: soft + "@backstage/plugin-app-visualizer@workspace:^, @backstage/plugin-app-visualizer@workspace:plugins/app-visualizer": version: 0.0.0-use.local resolution: "@backstage/plugin-app-visualizer@workspace:plugins/app-visualizer" @@ -4119,20 +4133,20 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" - "@backstage/frontend-defaults": "workspace:^" + "@backstage/frontend-dev-utils": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" "@backstage/ui": "workspace:^" "@remixicon/react": "npm:^4.6.0" "@types/react": "npm:^18.0.0" react: "npm:^18.0.2" - react-aria-components: "npm:^1.13.0" + react-aria-components: "npm:~1.17.0" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -4147,34 +4161,42 @@ __metadata: "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" + "@backstage/filter-predicates": "workspace:^" "@backstage/frontend-defaults": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" "@backstage/frontend-test-utils": "workspace:^" "@backstage/integration-react": "workspace:^" + "@backstage/plugin-app-react": "workspace:^" "@backstage/plugin-permission-react": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/theme": "workspace:^" "@backstage/types": "workspace:^" + "@backstage/ui": "workspace:^" "@backstage/version-bridge": "workspace:^" "@material-ui/core": "npm:^4.9.13" "@material-ui/icons": "npm:^4.9.1" "@material-ui/lab": "npm:^4.0.0-alpha.61" "@react-hookz/web": "npm:^24.0.0" + "@remixicon/react": "npm:^4.6.0" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" "@testing-library/user-event": "npm:^14.0.0" "@types/react": "npm:^18.0.0" + motion: "npm:^12.0.0" msw: "npm:^1.0.0" react: "npm:^18.0.2" + react-aria: "npm:~3.48.0" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" + react-stately: "npm:~3.46.0" react-use: "npm:^17.2.4" - zod: "npm:^3.22.4" + zen-observable: "npm:^0.10.0" + zod: "npm:^4.0.0" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -4192,11 +4214,11 @@ __metadata: "@backstage/plugin-auth-backend": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/types": "workspace:^" - express: "npm:^4.18.2" + express: "npm:^4.22.0" passport: "npm:^0.7.0" passport-atlassian-oauth2: "npm:^2.1.0" supertest: "npm:^7.0.0" - zod: "npm:^3.22.4" + zod: "npm:^3.25.76 || ^4.0.0" languageName: unknown linkType: soft @@ -4208,12 +4230,16 @@ __metadata: "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" + "@backstage/errors": "workspace:^" "@backstage/plugin-auth-backend": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/types": "workspace:^" + "@types/passport": "npm:^1.0.3" "@types/passport-auth0": "npm:^1.0.5" "@types/passport-oauth2": "npm:^1.4.15" - express: "npm:^4.17.1" + express: "npm:^4.22.0" + jose: "npm:^5.0.0" + passport: "npm:^0.7.0" passport-auth0: "npm:^1.4.3" passport-oauth2: "npm:^1.6.1" supertest: "npm:^7.0.0" @@ -4232,11 +4258,11 @@ __metadata: "@backstage/plugin-auth-backend": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/types": "workspace:^" - express: "npm:^4.18.2" + express: "npm:^4.22.0" jose: "npm:^5.0.0" msw: "npm:^2.0.8" node-cache: "npm:^5.1.2" - zod: "npm:^3.22.4" + zod: "npm:^3.25.76 || ^4.0.0" languageName: unknown linkType: soft @@ -4252,10 +4278,10 @@ __metadata: "@backstage/plugin-auth-backend": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@types/passport": "npm:^1.0.16" - express: "npm:^4.19.2" + express: "npm:^4.22.0" jose: "npm:^5.0.0" passport: "npm:^0.7.0" - zod: "npm:^3.22.4" + zod: "npm:^3.25.76 || ^4.0.0" languageName: unknown linkType: soft @@ -4270,11 +4296,11 @@ __metadata: "@backstage/plugin-auth-backend": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/types": "workspace:^" - express: "npm:^4.18.2" + express: "npm:^4.22.0" passport: "npm:^0.7.0" passport-bitbucket-oauth2: "npm:^0.1.2" supertest: "npm:^7.0.0" - zod: "npm:^3.22.4" + zod: "npm:^3.25.76 || ^4.0.0" languageName: unknown linkType: soft @@ -4293,7 +4319,7 @@ __metadata: passport: "npm:^0.7.0" passport-oauth2: "npm:^1.6.1" supertest: "npm:^7.0.0" - zod: "npm:^3.22.4" + zod: "npm:^3.25.76 || ^4.0.0" languageName: unknown linkType: soft @@ -4310,12 +4336,12 @@ __metadata: "@backstage/plugin-auth-backend": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/types": "workspace:^" - express: "npm:^4.18.2" + express: "npm:^4.22.0" jose: "npm:^5.0.0" msw: "npm:^2.0.0" node-mocks-http: "npm:^1.0.0" uuid: "npm:^11.0.0" - zod: "npm:^3.22.4" + zod: "npm:^3.25.76 || ^4.0.0" languageName: unknown linkType: soft @@ -4329,9 +4355,9 @@ __metadata: "@backstage/errors": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/types": "workspace:^" - express: "npm:^4.18.2" + express: "npm:^4.22.0" google-auth-library: "npm:^9.0.0" - zod: "npm:^3.22.4" + zod: "npm:^3.25.76 || ^4.0.0" languageName: unknown linkType: soft @@ -4349,7 +4375,7 @@ __metadata: "@types/passport-github2": "npm:^1.2.4" passport-github2: "npm:^0.1.12" supertest: "npm:^7.0.0" - zod: "npm:^3.22.4" + zod: "npm:^3.25.76 || ^4.0.0" languageName: unknown linkType: soft @@ -4364,11 +4390,11 @@ __metadata: "@backstage/plugin-auth-backend": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/types": "workspace:^" - express: "npm:^4.18.2" + express: "npm:^4.22.0" passport: "npm:^0.7.0" passport-gitlab2: "npm:^5.0.0" supertest: "npm:^7.0.0" - zod: "npm:^3.22.4" + zod: "npm:^3.25.76 || ^4.0.0" languageName: unknown linkType: soft @@ -4386,7 +4412,7 @@ __metadata: google-auth-library: "npm:^9.0.0" passport-google-oauth20: "npm:^2.0.0" supertest: "npm:^7.0.0" - zod: "npm:^3.22.4" + zod: "npm:^3.25.76 || ^4.0.0" languageName: unknown linkType: soft @@ -4401,7 +4427,7 @@ __metadata: "@backstage/config": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" - express: "npm:^4.18.2" + express: "npm:^4.22.0" passport-oauth2: "npm:^1.7.0" languageName: unknown linkType: soft @@ -4419,12 +4445,12 @@ __metadata: "@backstage/plugin-auth-node": "workspace:^" "@backstage/types": "workspace:^" "@types/passport-microsoft": "npm:^1.0.0" - express: "npm:^4.18.2" + express: "npm:^4.22.0" jose: "npm:^5.0.0" msw: "npm:^1.0.0" passport-microsoft: "npm:^1.0.0" supertest: "npm:^7.0.0" - zod: "npm:^3.22.4" + zod: "npm:^3.25.76 || ^4.0.0" languageName: unknown linkType: soft @@ -4442,7 +4468,7 @@ __metadata: passport: "npm:^0.7.0" passport-oauth2: "npm:^1.6.1" supertest: "npm:^7.0.0" - zod: "npm:^3.22.4" + zod: "npm:^3.25.76 || ^4.0.0" languageName: unknown linkType: soft @@ -4456,7 +4482,7 @@ __metadata: "@backstage/errors": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" jose: "npm:^5.0.0" - zod: "npm:^3.22.4" + zod: "npm:^3.25.76 || ^4.0.0" languageName: unknown linkType: soft @@ -4473,7 +4499,7 @@ __metadata: "@backstage/plugin-auth-node": "workspace:^" "@backstage/types": "workspace:^" cookie-parser: "npm:^1.4.6" - express: "npm:^4.18.2" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.1" express-session: "npm:^1.17.3" jose: "npm:^5.0.0" @@ -4481,7 +4507,7 @@ __metadata: openid-client: "npm:^5.5.0" passport: "npm:^0.7.0" supertest: "npm:^7.0.0" - zod: "npm:^3.22.4" + zod: "npm:^3.25.76 || ^4.0.0" languageName: unknown linkType: soft @@ -4497,10 +4523,10 @@ __metadata: "@backstage/plugin-auth-node": "workspace:^" "@backstage/types": "workspace:^" "@davidzemon/passport-okta-oauth": "npm:^0.0.7" - express: "npm:^4.18.2" + express: "npm:^4.22.0" passport: "npm:^0.7.0" supertest: "npm:^7.0.0" - zod: "npm:^3.22.4" + zod: "npm:^3.25.76 || ^4.0.0" languageName: unknown linkType: soft @@ -4515,11 +4541,11 @@ __metadata: "@backstage/plugin-auth-backend": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/types": "workspace:^" - express: "npm:^4.18.2" + express: "npm:^4.22.0" passport: "npm:^0.7.0" passport-onelogin-oauth: "npm:^0.0.1" supertest: "npm:^7.0.0" - zod: "npm:^3.22.4" + zod: "npm:^3.25.76 || ^4.0.0" languageName: unknown linkType: soft @@ -4536,11 +4562,11 @@ __metadata: "@backstage/plugin-auth-backend": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/types": "workspace:^" - express: "npm:^4.18.2" + express: "npm:^4.22.0" msw: "npm:^2.7.3" passport-oauth2: "npm:^1.8.0" supertest: "npm:^7.1.0" - zod: "npm:^3.24.2" + zod: "npm:^3.25.76 || ^4.0.0" languageName: unknown linkType: soft @@ -4557,7 +4583,7 @@ __metadata: "@backstage/plugin-auth-node": "workspace:^" "@backstage/types": "workspace:^" cookie-parser: "npm:^1.4.6" - express: "npm:^4.18.2" + express: "npm:^4.22.0" express-session: "npm:^1.17.3" jose: "npm:^5.0.0" luxon: "npm:^3.4.3" @@ -4613,18 +4639,22 @@ __metadata: "@types/passport": "npm:^1.0.3" connect-session-knex: "npm:^4.0.0" cookie-parser: "npm:^1.4.5" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" express-session: "npm:^1.17.1" + ipaddr.js: "npm:^2.3.0" jose: "npm:^5.0.0" knex: "npm:^3.0.0" lodash: "npm:^4.17.21" luxon: "npm:^3.0.0" matcher: "npm:^4.0.0" - minimatch: "npm:^9.0.0" + minimatch: "npm:^10.2.1" + msw: "npm:^1.0.0" passport: "npm:^0.7.0" supertest: "npm:^7.0.0" uuid: "npm:^11.0.0" + zod: "npm:^3.25.76 || ^4.0.0" + zod-validation-error: "npm:^5.0.0" languageName: unknown linkType: soft @@ -4644,7 +4674,7 @@ __metadata: "@types/express": "npm:^4.17.6" "@types/passport": "npm:^1.0.3" cookie-parser: "npm:^1.4.6" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.1" jose: "npm:^5.0.0" lodash: "npm:^4.17.21" @@ -4652,9 +4682,9 @@ __metadata: passport: "npm:^0.7.0" supertest: "npm:^7.0.0" uuid: "npm:^11.0.0" - zod: "npm:^3.22.4" - zod-to-json-schema: "npm:^3.21.4" - zod-validation-error: "npm:^3.4.0" + zod: "npm:^3.25.76 || ^4.0.0" + zod-to-json-schema: "npm:^3.25.1" + zod-validation-error: "npm:^4.0.2" languageName: unknown linkType: soft @@ -4676,12 +4706,12 @@ __metadata: msw: "npm:^1.0.0" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -4693,16 +4723,14 @@ __metadata: resolution: "@backstage/plugin-auth@workspace:plugins/auth" dependencies: "@backstage/cli": "workspace:^" - "@backstage/core-components": "workspace:^" "@backstage/dev-utils": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/frontend-defaults": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/theme": "workspace:^" - "@material-ui/core": "npm:^4.12.2" - "@material-ui/icons": "npm:^4.9.1" - "@material-ui/lab": "npm:4.0.0-alpha.61" + "@backstage/ui": "workspace:^" + "@remixicon/react": "npm:^4.6.0" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" "@testing-library/user-event": "npm:^14.0.0" @@ -4710,13 +4738,13 @@ __metadata: msw: "npm:^1.0.0" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" react-use: "npm:^17.2.4" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -4776,9 +4804,11 @@ __metadata: "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" + "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/plugin-catalog-common": "workspace:^" "@backstage/plugin-catalog-node": "workspace:^" + "@backstage/plugin-events-node": "workspace:^" msw: "npm:^1.0.0" uuid: "npm:^11.0.0" languageName: unknown @@ -4813,6 +4843,7 @@ __metadata: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" + "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/plugin-bitbucket-cloud-common": "workspace:^" "@backstage/plugin-catalog-common": "workspace:^" @@ -4914,25 +4945,35 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-backend-module-github@workspace:plugins/catalog-backend-module-github" dependencies: + "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" + "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" + "@backstage/plugin-catalog-backend": "workspace:^" "@backstage/plugin-catalog-common": "workspace:^" "@backstage/plugin-catalog-node": "workspace:^" + "@backstage/plugin-events-backend": "workspace:^" + "@backstage/plugin-events-backend-module-github": "workspace:^" + "@backstage/plugin-events-backend-module-google-pubsub": "workspace:^" "@backstage/plugin-events-node": "workspace:^" + "@backstage/types": "workspace:^" + "@octokit/auth-callback": "npm:^5.0.0" "@octokit/core": "npm:^5.2.0" "@octokit/graphql": "npm:^7.0.2" + "@octokit/plugin-retry": "npm:^6.0.0" "@octokit/plugin-throttling": "npm:^8.1.3" "@octokit/rest": "npm:^19.0.3" + "@octokit/webhooks-types": "npm:^7.6.1" "@types/lodash": "npm:^4.14.151" git-url-parse: "npm:^15.0.0" lodash: "npm:^4.17.21" - minimatch: "npm:^9.0.0" + minimatch: "npm:^10.2.1" msw: "npm:^2.0.0" - type-fest: "npm:^4.41.0" + octokit: "npm:^3.0.0" uuid: "npm:^11.0.0" languageName: unknown linkType: soft @@ -4961,6 +5002,7 @@ __metadata: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" + "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/plugin-catalog-common": "workspace:^" "@backstage/plugin-catalog-node": "workspace:^" @@ -4970,7 +5012,6 @@ __metadata: "@types/lodash": "npm:^4.14.151" lodash: "npm:^4.17.21" msw: "npm:^1.0.0" - node-fetch: "npm:^2.7.0" uuid: "npm:^11.0.0" languageName: unknown linkType: soft @@ -4989,12 +5030,9 @@ __metadata: "@backstage/plugin-catalog-backend": "workspace:^" "@backstage/plugin-catalog-node": "workspace:^" "@backstage/plugin-events-node": "workspace:^" - "@backstage/plugin-permission-common": "workspace:^" "@backstage/types": "workspace:^" - "@opentelemetry/api": "npm:^1.9.0" "@types/express": "npm:^4.17.6" - "@types/luxon": "npm:^3.0.0" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" knex: "npm:^3.0.0" luxon: "npm:^3.0.0" @@ -5021,7 +5059,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-catalog-backend-module-logs@workspace:plugins/catalog-backend-module-logs": +"@backstage/plugin-catalog-backend-module-logs@workspace:^, @backstage/plugin-catalog-backend-module-logs@workspace:plugins/catalog-backend-module-logs": version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-backend-module-logs@workspace:plugins/catalog-backend-module-logs" dependencies: @@ -5138,43 +5176,48 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/errors": "workspace:^" + "@backstage/filter-predicates": "workspace:^" "@backstage/integration": "workspace:^" + "@backstage/plugin-catalog-backend-module-logs": "workspace:^" "@backstage/plugin-catalog-common": "workspace:^" "@backstage/plugin-catalog-node": "workspace:^" "@backstage/plugin-events-node": "workspace:^" "@backstage/plugin-permission-common": "workspace:^" "@backstage/plugin-permission-node": "workspace:^" + "@backstage/plugin-scaffolder-common": "workspace:^" "@backstage/repo-tools": "workspace:^" "@backstage/types": "workspace:^" "@opentelemetry/api": "npm:^1.9.0" "@types/core-js": "npm:^2.5.4" "@types/express": "npm:^4.17.6" "@types/git-url-parse": "npm:^9.0.0" - "@types/glob": "npm:^8.0.0" "@types/lodash": "npm:^4.14.151" "@types/supertest": "npm:^2.0.8" + ajv: "npm:^8.10.0" + ajv-errors: "npm:^3.0.0" better-sqlite3: "npm:^12.0.0" codeowners-utils: "npm:^1.0.2" core-js: "npm:^3.6.5" - express: "npm:^4.17.1" + express: "npm:^4.22.0" fast-json-stable-stringify: "npm:^2.1.0" fs-extra: "npm:^11.2.0" git-url-parse: "npm:^15.0.0" - glob: "npm:^7.1.6" + glob: "npm:^13.0.0" knex: "npm:^3.0.0" lodash: "npm:^4.17.21" luxon: "npm:^3.0.0" - minimatch: "npm:^9.0.0" + minimatch: "npm:^10.2.1" msw: "npm:^2.0.0" p-limit: "npm:^3.0.2" prom-client: "npm:^15.0.0" supertest: "npm:^7.0.0" uuid: "npm:^11.0.0" - wait-for-expect: "npm:^3.0.2" + wait-for-expect: "npm:^4.0.0" winston: "npm:^3.13.0" yaml: "npm:^2.0.0" yn: "npm:^4.0.0" - zod: "npm:^3.22.4" + zod: "npm:^3.25.76 || ^4.0.0" + zod-validation-error: "npm:^4.0.2" languageName: unknown linkType: soft @@ -5197,18 +5240,20 @@ __metadata: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/core-app-api": "workspace:^" - "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" + "@backstage/frontend-test-utils": "workspace:^" "@backstage/plugin-catalog": "workspace:^" "@backstage/plugin-catalog-react": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/types": "workspace:^" + "@backstage/ui": "workspace:^" "@material-ui/core": "npm:^4.12.2" "@material-ui/icons": "npm:^4.9.1" "@material-ui/lab": "npm:4.0.0-alpha.61" + "@remixicon/react": "npm:^4.6.0" "@testing-library/dom": "npm:^10.0.0" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" @@ -5216,17 +5261,17 @@ __metadata: "@types/react": "npm:^18.0.0" classnames: "npm:^2.3.1" lodash: "npm:^4.17.15" - p-limit: "npm:^3.1.0" qs: "npm:^6.9.4" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" react-use: "npm:^17.2.4" + zod: "npm:^4.0.0" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -5242,7 +5287,6 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/core-app-api": "workspace:^" - "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" @@ -5259,6 +5303,7 @@ __metadata: "@material-ui/icons": "npm:^4.9.1" "@material-ui/lab": "npm:4.0.0-alpha.61" "@octokit/rest": "npm:^19.0.3" + "@remixicon/react": "npm:^4.6.0" "@testing-library/dom": "npm:^10.0.0" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" @@ -5271,14 +5316,14 @@ __metadata: react: "npm:^18.0.2" react-dom: "npm:^18.0.2" react-hook-form: "npm:^7.12.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" react-use: "npm:^17.2.4" yaml: "npm:^2.0.0" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -5299,9 +5344,15 @@ __metadata: "@backstage/plugin-permission-common": "workspace:^" "@backstage/plugin-permission-node": "workspace:^" "@backstage/types": "workspace:^" + "@opentelemetry/api": "npm:^1.9.0" lodash: "npm:^4.17.21" msw: "npm:^1.0.0" yaml: "npm:^2.0.0" + peerDependencies: + "@backstage/backend-test-utils": "workspace:^" + peerDependenciesMeta: + "@backstage/backend-test-utils": + optional: true languageName: unknown linkType: soft @@ -5317,6 +5368,7 @@ __metadata: "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/errors": "workspace:^" + "@backstage/filter-predicates": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" "@backstage/frontend-test-utils": "workspace:^" "@backstage/integration-react": "workspace:^" @@ -5326,11 +5378,13 @@ __metadata: "@backstage/plugin-scaffolder-common": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/types": "workspace:^" + "@backstage/ui": "workspace:^" "@backstage/version-bridge": "workspace:^" "@material-ui/core": "npm:^4.12.2" "@material-ui/icons": "npm:^4.9.1" "@material-ui/lab": "npm:4.0.0-alpha.61" "@react-hookz/web": "npm:^24.0.0" + "@remixicon/react": "npm:^4.6.0" "@testing-library/dom": "npm:^10.0.0" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" @@ -5343,18 +5397,21 @@ __metadata: qs: "npm:^6.9.4" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" react-test-renderer: "npm:^16.13.1" react-use: "npm:^17.2.4" yaml: "npm:^2.0.0" zen-observable: "npm:^0.10.0" - zod: "npm:^3.22.4" + zod: "npm:^4.0.0" peerDependencies: + "@backstage/frontend-test-utils": "workspace:^" "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: + "@backstage/frontend-test-utils": + optional: true "@types/react": optional: true languageName: unknown @@ -5364,7 +5421,9 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-unprocessed-entities-common@workspace:plugins/catalog-unprocessed-entities-common" dependencies: + "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" + "@backstage/errors": "workspace:^" "@backstage/plugin-permission-common": "workspace:^" languageName: unknown linkType: soft @@ -5373,7 +5432,6 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-unprocessed-entities@workspace:plugins/catalog-unprocessed-entities" dependencies: - "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" @@ -5381,6 +5439,8 @@ __metadata: "@backstage/dev-utils": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" + "@backstage/plugin-catalog-unprocessed-entities-common": "workspace:^" + "@backstage/ui": "workspace:^" "@material-ui/core": "npm:^4.9.13" "@material-ui/icons": "npm:^4.9.1" "@material-ui/lab": "npm:^4.0.0-alpha.60" @@ -5390,13 +5450,13 @@ __metadata: luxon: "npm:^3.5.0" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" react-use: "npm:^17.2.4" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -5430,6 +5490,7 @@ __metadata: "@backstage/plugin-techdocs-react": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/types": "workspace:^" + "@backstage/ui": "workspace:^" "@backstage/version-bridge": "workspace:^" "@material-ui/core": "npm:^4.12.2" "@material-ui/icons": "npm:^4.9.1" @@ -5443,21 +5504,21 @@ __metadata: "@types/react": "npm:^18.0.0" classnames: "npm:^2.3.1" dataloader: "npm:^2.0.0" - history: "npm:^5.0.0" lodash: "npm:^4.17.21" pluralize: "npm:^8.0.0" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" react-helmet: "npm:6.1.0" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" react-use: "npm:^17.2.4" swr: "npm:^2.2.5" zen-observable: "npm:^0.10.0" + zod: "npm:^4.0.0" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -5484,14 +5545,14 @@ __metadata: jsonschema: "npm:^1.2.6" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" react-use: "npm:^17.2.4" zen-observable: "npm:^0.10.0" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -5521,14 +5582,12 @@ __metadata: "@types/yarnpkg__lockfile": "npm:^1.1.4" "@yarnpkg/lockfile": "npm:^1.1.0" "@yarnpkg/parsers": "npm:^3.0.0" - express: "npm:^4.18.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" fs-extra: "npm:^11.0.0" lodash: "npm:^4.17.21" ping: "npm:^0.4.1" - semver: "npm:^7.5.3" supertest: "npm:^7.0.0" - yn: "npm:^4.0.0" languageName: unknown linkType: soft @@ -5542,6 +5601,32 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-devtools-react@workspace:plugins/devtools-react": + version: 0.0.0-use.local + resolution: "@backstage/plugin-devtools-react@workspace:plugins/devtools-react" + dependencies: + "@backstage/cli": "workspace:^" + "@backstage/core-plugin-api": "workspace:^" + "@backstage/frontend-plugin-api": "workspace:^" + "@backstage/test-utils": "workspace:^" + "@material-ui/core": "npm:^4.9.13" + "@testing-library/jest-dom": "npm:^6.0.0" + "@testing-library/react": "npm:^16.0.0" + "@types/react": "npm:^18.0.0" + react: "npm:^18.0.2" + react-dom: "npm:^18.0.2" + react-router-dom: "npm:^6.30.2" + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + react-router-dom: ^6.30.2 + peerDependenciesMeta: + "@types/react": + optional: true + languageName: unknown + linkType: soft + "@backstage/plugin-devtools@workspace:^, @backstage/plugin-devtools@workspace:plugins/devtools": version: 0.0.0-use.local resolution: "@backstage/plugin-devtools@workspace:plugins/devtools" @@ -5555,21 +5640,24 @@ __metadata: "@backstage/frontend-plugin-api": "workspace:^" "@backstage/plugin-devtools-common": "workspace:^" "@backstage/plugin-permission-react": "workspace:^" + "@backstage/ui": "workspace:^" "@material-ui/core": "npm:^4.9.13" "@material-ui/icons": "npm:^4.9.1" "@material-ui/lab": "npm:^4.0.0-alpha.57" "@testing-library/jest-dom": "npm:^6.0.0" + "@types/lodash": "npm:^4.14.151" "@types/react": "npm:^18.0.0" + lodash: "npm:^4.17.21" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" react-json-view: "npm:^1.21.3" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" react-use: "npm:^17.2.4" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -5642,7 +5730,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-events-backend-module-github@workspace:plugins/events-backend-module-github": +"@backstage/plugin-events-backend-module-github@workspace:^, @backstage/plugin-events-backend-module-github@workspace:plugins/events-backend-module-github": version: 0.0.0-use.local resolution: "@backstage/plugin-events-backend-module-github@workspace:plugins/events-backend-module-github" dependencies: @@ -5684,12 +5772,12 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/errors": "workspace:^" + "@backstage/filter-predicates": "workspace:^" "@backstage/plugin-events-backend": "workspace:^" "@backstage/plugin-events-node": "workspace:^" "@backstage/types": "workspace:^" "@google-cloud/pubsub": "npm:^4.10.0" - "@opentelemetry/api": "npm:^1.9.0" - wait-for-expect: "npm:^3.0.2" + wait-for-expect: "npm:^4.0.0" languageName: unknown linkType: soft @@ -5736,7 +5824,7 @@ __metadata: "@types/content-type": "npm:^1.1.8" "@types/express": "npm:^4.17.6" content-type: "npm:^1.0.5" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" knex: "npm:^3.0.0" supertest: "npm:^7.0.0" @@ -5756,8 +5844,8 @@ __metadata: "@types/express": "npm:^4.17.6" content-type: "npm:^1.0.5" cross-fetch: "npm:^4.0.0" - express: "npm:^4.17.1" - msw: "npm:^1.0.0" + express: "npm:^4.22.0" + msw: "npm:^2.0.0" uri-template: "npm:^2.0.0" languageName: unknown linkType: soft @@ -5774,10 +5862,10 @@ __metadata: "@opentelemetry/api": "npm:^1.9.0" "@opentelemetry/core": "npm:^1.29.0" "@types/express": "npm:^4.17.6" - eventsource: "npm:^3.0.6" - express: "npm:^4.17.1" + eventsource: "npm:^4.0.0" + express: "npm:^4.22.0" http-proxy-middleware: "npm:^3.0.3" - wait-for-expect: "npm:^3.0.2" + wait-for-expect: "npm:^4.0.0" languageName: unknown linkType: soft @@ -5786,22 +5874,23 @@ __metadata: resolution: "@backstage/plugin-home-react@workspace:plugins/home-react" dependencies: "@backstage/cli": "workspace:^" + "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" "@material-ui/core": "npm:^4.12.2" "@material-ui/icons": "npm:^4.9.1" - "@rjsf/utils": "npm:5.23.2" + "@rjsf/utils": "npm:5.24.13" "@types/react": "npm:^18.0.0" "@types/react-grid-layout": "npm:^1.3.2" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -5821,7 +5910,9 @@ __metadata: "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" + "@backstage/frontend-defaults": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" + "@backstage/plugin-catalog": "workspace:^" "@backstage/plugin-catalog-react": "workspace:^" "@backstage/plugin-home-react": "workspace:^" "@backstage/test-utils": "workspace:^" @@ -5829,10 +5920,10 @@ __metadata: "@material-ui/core": "npm:^4.12.2" "@material-ui/icons": "npm:^4.9.1" "@material-ui/lab": "npm:4.0.0-alpha.61" - "@rjsf/core": "npm:5.23.2" - "@rjsf/material-ui": "npm:5.23.2" - "@rjsf/utils": "npm:5.23.2" - "@rjsf/validator-ajv8": "npm:5.23.2" + "@rjsf/core": "npm:5.24.13" + "@rjsf/material-ui": "npm:5.24.13" + "@rjsf/utils": "npm:5.24.13" + "@rjsf/validator-ajv8": "npm:5.24.13" "@testing-library/dom": "npm:^10.0.0" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" @@ -5845,14 +5936,14 @@ __metadata: react-dom: "npm:^18.0.2" react-grid-layout: "npm:1.3.4" react-resizable: "npm:^3.0.4" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" react-use: "npm:^17.2.4" - zod: "npm:^3.22.4" + zod: "npm:^3.25.76 || ^4.0.0" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -5865,7 +5956,6 @@ __metadata: dependencies: "@aws-crypto/sha256-js": "npm:^5.0.0" "@aws-sdk/credential-providers": "npm:^3.350.0" - "@aws-sdk/signature-v4": "npm:^3.347.0" "@azure/identity": "npm:^4.0.0" "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" @@ -5887,10 +5977,10 @@ __metadata: "@google-cloud/container": "npm:^5.0.0" "@jest-mock/express": "npm:^2.0.1" "@kubernetes/client-node": "npm:1.4.0" + "@smithy/signature-v4": "npm:^4.1.0" "@types/express": "npm:^4.17.6" "@types/http-proxy-middleware": "npm:^1.0.0" - "@types/luxon": "npm:^3.0.0" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" fs-extra: "npm:^11.2.0" http-proxy-middleware: "npm:^2.0.6" @@ -5921,18 +6011,18 @@ __metadata: "@material-ui/lab": "npm:4.0.0-alpha.61" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" - "@types/node": "npm:^20.16.0" + "@types/node": "npm:^22.13.14" "@types/react": "npm:^18.0.0" kubernetes-models: "npm:^4.1.0" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" react-use: "npm:^17.2.4" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -5968,10 +6058,10 @@ __metadata: "@backstage/plugin-kubernetes-common": "workspace:^" "@backstage/types": "workspace:^" "@kubernetes/client-node": "npm:1.4.0" + "@types/express": "npm:^4.17.6" msw: "npm:^1.3.1" node-fetch: "npm:^2.7.0" supertest: "npm:^7.0.0" - winston: "npm:^3.2.1" languageName: unknown linkType: soft @@ -5997,8 +6087,8 @@ __metadata: "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" "@types/react": "npm:^18.0.0" - "@xterm/addon-attach": "npm:^0.11.0" - "@xterm/addon-fit": "npm:^0.10.0" + "@xterm/addon-attach": "npm:^0.12.0" + "@xterm/addon-fit": "npm:^0.11.0" "@xterm/xterm": "npm:^5.5.0" cronstrue: "npm:^2.32.0" jest-websocket-mock: "npm:^2.5.0" @@ -6006,16 +6096,16 @@ __metadata: kubernetes-models: "npm:^4.3.1" lodash: "npm:^4.17.21" luxon: "npm:^3.0.0" - msw: "npm:^1.3.1" + msw: "npm:^2.0.0" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" react-use: "npm:^17.4.0" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -6028,7 +6118,6 @@ __metadata: dependencies: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" - "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" @@ -6045,12 +6134,12 @@ __metadata: "@types/react": "npm:^18.0.0" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -6066,14 +6155,19 @@ __metadata: "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-client": "workspace:^" "@backstage/cli": "workspace:^" + "@backstage/config": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/plugin-catalog-node": "workspace:^" "@backstage/types": "workspace:^" - "@modelcontextprotocol/sdk": "npm:^1.12.3" + "@cfworker/json-schema": "npm:^4.1.1" + "@modelcontextprotocol/sdk": "npm:^1.25.2" "@types/express": "npm:^4.17.6" - express: "npm:^4.17.1" + "@types/supertest": "npm:^2.0.8" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" - zod: "npm:^3.22.4" + minimatch: "npm:^10.2.1" + supertest: "npm:^7.0.0" + zod: "npm:^3.25.76 || ^4.0.0" languageName: unknown linkType: soft @@ -6082,7 +6176,6 @@ __metadata: resolution: "@backstage/plugin-mui-to-bui@workspace:plugins/mui-to-bui" dependencies: "@backstage/cli": "workspace:^" - "@backstage/core-compat-api": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" @@ -6096,12 +6189,12 @@ __metadata: "@types/react": "npm:^18.0.0" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -6126,9 +6219,9 @@ __metadata: "@backstage/plugin-notifications-common": "workspace:^" "@backstage/plugin-notifications-node": "workspace:^" "@backstage/types": "workspace:^" - "@types/nodemailer": "npm:^6.4.14" + "@types/nodemailer": "npm:^7.0.0" lodash: "npm:^4.17.21" - nodemailer: "npm:^7.0.7" + nodemailer: "npm:^8.0.4" p-throttle: "npm:^4.1.1" languageName: unknown linkType: soft @@ -6148,12 +6241,12 @@ __metadata: "@backstage/plugin-notifications-node": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/types": "workspace:^" - "@faker-js/faker": "npm:^8.4.1" - "@opentelemetry/api": "npm:^1.9.0" + "@faker-js/faker": "npm:^10.0.0" "@slack/bolt": "npm:^3.21.4" "@slack/types": "npm:^2.14.0" "@slack/web-api": "npm:^7.5.0" dataloader: "npm:^2.0.0" + knex: "npm:^3.0.0" msw: "npm:^2.0.0" p-throttle: "npm:^4.1.1" languageName: unknown @@ -6181,7 +6274,7 @@ __metadata: "@backstage/types": "workspace:^" "@types/express": "npm:^4.17.6" "@types/supertest": "npm:^2.0.8" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" knex: "npm:^3.0.0" p-throttle: "npm:^4.1.1" @@ -6197,7 +6290,6 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/types": "workspace:^" - "@material-ui/icons": "npm:^4.9.1" languageName: unknown linkType: soft @@ -6207,15 +6299,10 @@ __metadata: dependencies: "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" - "@backstage/catalog-client": "workspace:^" - "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/plugin-notifications-common": "workspace:^" - "@backstage/plugin-signals-node": "workspace:^" "@backstage/test-utils": "workspace:^" - knex: "npm:^3.0.0" msw: "npm:^1.0.0" - uuid: "npm:^11.0.0" languageName: unknown linkType: soft @@ -6224,7 +6311,6 @@ __metadata: resolution: "@backstage/plugin-notifications@workspace:plugins/notifications" dependencies: "@backstage/cli": "workspace:^" - "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" @@ -6235,25 +6321,25 @@ __metadata: "@backstage/plugin-signals-react": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/theme": "workspace:^" - "@material-ui/core": "npm:^4.9.13" - "@material-ui/icons": "npm:^4.9.1" + "@backstage/ui": "workspace:^" + "@remixicon/react": "npm:^4.6.0" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" "@types/react": "npm:^18.0.0" lodash: "npm:^4.17.21" - material-ui-confirm: "npm:^3.0.12" msw: "npm:^1.0.0" notistack: "npm:^3.0.1" react: "npm:^18.0.2" + react-aria-components: "npm:~1.17.0" react-dom: "npm:^18.0.2" react-relative-time: "npm:^0.0.9" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" react-use: "npm:^17.2.4" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -6282,13 +6368,13 @@ __metadata: "@types/react": "npm:^18.0.0" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" react-use: "npm:^17.2.4" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -6303,11 +6389,11 @@ __metadata: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/core-app-api": "workspace:^" - "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" + "@backstage/frontend-test-utils": "workspace:^" "@backstage/plugin-catalog": "workspace:^" "@backstage/plugin-catalog-common": "workspace:^" "@backstage/plugin-catalog-react": "workspace:^" @@ -6315,9 +6401,11 @@ __metadata: "@backstage/plugin-permission-react": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/types": "workspace:^" + "@backstage/ui": "workspace:^" "@material-ui/core": "npm:^4.12.2" "@material-ui/icons": "npm:^4.9.1" "@material-ui/lab": "npm:4.0.0-alpha.61" + "@remixicon/react": "npm:^4.6.0" "@testing-library/dom": "npm:^10.0.0" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" @@ -6329,13 +6417,14 @@ __metadata: qs: "npm:^6.10.1" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" react-use: "npm:^17.2.4" + zod: "npm:^4.0.0" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -6372,13 +6461,12 @@ __metadata: "@types/lodash": "npm:^4.14.151" "@types/supertest": "npm:^2.0.8" dataloader: "npm:^2.0.0" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" lodash: "npm:^4.17.21" msw: "npm:^1.0.0" supertest: "npm:^7.0.0" - yn: "npm:^4.0.0" - zod: "npm:^3.22.4" + zod: "npm:^3.25.76 || ^4.0.0" languageName: unknown linkType: soft @@ -6393,8 +6481,8 @@ __metadata: cross-fetch: "npm:^4.0.0" msw: "npm:^1.0.0" uuid: "npm:^11.0.0" - zod: "npm:^3.22.4" - zod-to-json-schema: "npm:^3.20.4" + zod: "npm:^3.25.76 || ^4.0.0" + zod-to-json-schema: "npm:^3.25.1" languageName: unknown linkType: soft @@ -6412,12 +6500,12 @@ __metadata: "@backstage/plugin-permission-common": "workspace:^" "@types/express": "npm:^4.17.6" "@types/supertest": "npm:^2.0.8" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" msw: "npm:^1.0.0" supertest: "npm:^7.0.0" - zod: "npm:^3.22.4" - zod-to-json-schema: "npm:^3.20.4" + zod: "npm:^3.25.76 || ^4.0.0" + zod-to-json-schema: "npm:^3.25.1" languageName: unknown linkType: soft @@ -6433,15 +6521,14 @@ __metadata: "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" "@types/react": "npm:^18.0.0" + dataloader: "npm:^2.0.0" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" swr: "npm:^2.0.0" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 peerDependenciesMeta: "@types/react": optional: true @@ -6463,7 +6550,7 @@ __metadata: "@backstage/types": "workspace:^" "@types/express": "npm:^4.17.6" "@types/http-proxy-middleware": "npm:^1.0.0" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" http-proxy-middleware: "npm:^2.0.0" msw: "npm:^2.0.0" @@ -6483,7 +6570,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-scaffolder-backend-module-azure@workspace:^, @backstage/plugin-scaffolder-backend-module-azure@workspace:plugins/scaffolder-backend-module-azure": +"@backstage/plugin-scaffolder-backend-module-azure@workspace:plugins/scaffolder-backend-module-azure": version: 0.0.0-use.local resolution: "@backstage/plugin-scaffolder-backend-module-azure@workspace:plugins/scaffolder-backend-module-azure" dependencies: @@ -6499,7 +6586,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-scaffolder-backend-module-bitbucket-cloud@workspace:^, @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@workspace:plugins/scaffolder-backend-module-bitbucket-cloud": +"@backstage/plugin-scaffolder-backend-module-bitbucket-cloud@workspace:plugins/scaffolder-backend-module-bitbucket-cloud": version: 0.0.0-use.local resolution: "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud@workspace:plugins/scaffolder-backend-module-bitbucket-cloud" dependencies: @@ -6516,11 +6603,11 @@ __metadata: fs-extra: "npm:^11.2.0" msw: "npm:^1.0.0" yaml: "npm:^2.0.0" - zod: "npm:^3.22.4" + zod: "npm:^3.25.76 || ^4.0.0" languageName: unknown linkType: soft -"@backstage/plugin-scaffolder-backend-module-bitbucket-server@workspace:^, @backstage/plugin-scaffolder-backend-module-bitbucket-server@workspace:plugins/scaffolder-backend-module-bitbucket-server": +"@backstage/plugin-scaffolder-backend-module-bitbucket-server@workspace:plugins/scaffolder-backend-module-bitbucket-server": version: 0.0.0-use.local resolution: "@backstage/plugin-scaffolder-backend-module-bitbucket-server@workspace:plugins/scaffolder-backend-module-bitbucket-server" dependencies: @@ -6538,26 +6625,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-scaffolder-backend-module-bitbucket@workspace:^, @backstage/plugin-scaffolder-backend-module-bitbucket@workspace:plugins/scaffolder-backend-module-bitbucket": - version: 0.0.0-use.local - resolution: "@backstage/plugin-scaffolder-backend-module-bitbucket@workspace:plugins/scaffolder-backend-module-bitbucket" - dependencies: - "@backstage/backend-plugin-api": "workspace:^" - "@backstage/backend-test-utils": "workspace:^" - "@backstage/cli": "workspace:^" - "@backstage/config": "workspace:^" - "@backstage/errors": "workspace:^" - "@backstage/integration": "workspace:^" - "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud": "workspace:^" - "@backstage/plugin-scaffolder-backend-module-bitbucket-server": "workspace:^" - "@backstage/plugin-scaffolder-node": "workspace:^" - "@backstage/plugin-scaffolder-node-test-utils": "workspace:^" - fs-extra: "npm:^11.2.0" - msw: "npm:^1.0.0" - yaml: "npm:^2.0.0" - languageName: unknown - linkType: soft - "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown@workspace:plugins/scaffolder-backend-module-confluence-to-markdown": version: 0.0.0-use.local resolution: "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown@workspace:plugins/scaffolder-backend-module-confluence-to-markdown" @@ -6582,11 +6649,9 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-scaffolder-backend-module-cookiecutter@workspace:plugins/scaffolder-backend-module-cookiecutter" dependencies: - "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" - "@backstage/config": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/plugin-scaffolder-node": "workspace:^" @@ -6596,9 +6661,7 @@ __metadata: "@types/fs-extra": "npm:^11.0.0" command-exists: "npm:^1.2.9" fs-extra: "npm:^11.2.0" - winston: "npm:^3.2.1" yaml: "npm:^2.0.0" - yn: "npm:^4.0.0" languageName: unknown linkType: soft @@ -6618,7 +6681,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-scaffolder-backend-module-gerrit@workspace:^, @backstage/plugin-scaffolder-backend-module-gerrit@workspace:plugins/scaffolder-backend-module-gerrit": +"@backstage/plugin-scaffolder-backend-module-gerrit@workspace:plugins/scaffolder-backend-module-gerrit": version: 0.0.0-use.local resolution: "@backstage/plugin-scaffolder-backend-module-gerrit@workspace:plugins/scaffolder-backend-module-gerrit" dependencies: @@ -6635,7 +6698,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-scaffolder-backend-module-gitea@workspace:^, @backstage/plugin-scaffolder-backend-module-gitea@workspace:plugins/scaffolder-backend-module-gitea": +"@backstage/plugin-scaffolder-backend-module-gitea@workspace:plugins/scaffolder-backend-module-gitea": version: 0.0.0-use.local resolution: "@backstage/plugin-scaffolder-backend-module-gitea@workspace:plugins/scaffolder-backend-module-gitea" dependencies: @@ -6667,19 +6730,20 @@ __metadata: "@backstage/plugin-scaffolder-node": "workspace:^" "@backstage/plugin-scaffolder-node-test-utils": "workspace:^" "@backstage/types": "workspace:^" + "@octokit/core": "npm:^5.0.0" "@octokit/webhooks": "npm:^10.9.2" - "@types/libsodium-wrappers": "npm:^0.7.10" + "@types/libsodium-wrappers": "npm:^0.8.0" fs-extra: "npm:^11.2.0" jsonschema: "npm:^1.2.6" - libsodium-wrappers: "npm:^0.7.11" + libsodium-wrappers: "npm:^0.8.0" octokit: "npm:^3.0.0" octokit-plugin-create-pull-request: "npm:^5.0.0" yaml: "npm:^2.0.0" - zod: "npm:^3.22.4" + zod: "npm:^3.25.76 || ^4.0.0" languageName: unknown linkType: soft -"@backstage/plugin-scaffolder-backend-module-gitlab@workspace:^, @backstage/plugin-scaffolder-backend-module-gitlab@workspace:plugins/scaffolder-backend-module-gitlab": +"@backstage/plugin-scaffolder-backend-module-gitlab@workspace:plugins/scaffolder-backend-module-gitlab": version: 0.0.0-use.local resolution: "@backstage/plugin-scaffolder-backend-module-gitlab@workspace:plugins/scaffolder-backend-module-gitlab" dependencies: @@ -6691,11 +6755,12 @@ __metadata: "@backstage/integration": "workspace:^" "@backstage/plugin-scaffolder-node": "workspace:^" "@backstage/plugin-scaffolder-node-test-utils": "workspace:^" - "@gitbeaker/requester-utils": "npm:^41.2.0" - "@gitbeaker/rest": "npm:^41.2.0" + "@gitbeaker/core": "npm:^43.8.0" + "@gitbeaker/requester-utils": "npm:^43.8.0" + "@gitbeaker/rest": "npm:^43.8.0" luxon: "npm:^3.0.0" yaml: "npm:^2.0.0" - zod: "npm:^3.22.4" + zod: "npm:^3.25.76 || ^4.0.0" languageName: unknown linkType: soft @@ -6728,10 +6793,9 @@ __metadata: "@backstage/types": "workspace:^" "@types/command-exists": "npm:^1.2.0" "@types/fs-extra": "npm:^11.0.0" - "@types/node": "npm:^20.16.0" + "@types/node": "npm:^22.13.14" command-exists: "npm:^1.2.9" fs-extra: "npm:^11.0.0" - jest-when: "npm:^3.1.0" yaml: "npm:^2.0.0" languageName: unknown linkType: soft @@ -6762,7 +6826,6 @@ __metadata: "@backstage/plugin-scaffolder-node": "workspace:^" "@backstage/plugin-scaffolder-node-test-utils": "workspace:^" "@backstage/types": "workspace:^" - winston: "npm:^3.2.1" yaml: "npm:^2.0.0" yeoman-environment: "npm:^3.9.1" languageName: unknown @@ -6772,7 +6835,6 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-scaffolder-backend@workspace:plugins/scaffolder-backend" dependencies: - "@backstage/backend-app-api": "workspace:^" "@backstage/backend-defaults": "workspace:^" "@backstage/backend-openapi-utils": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" @@ -6782,61 +6844,46 @@ __metadata: "@backstage/config": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" - "@backstage/plugin-auth-node": "workspace:^" - "@backstage/plugin-bitbucket-cloud-common": "workspace:^" - "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "workspace:^" "@backstage/plugin-catalog-node": "workspace:^" "@backstage/plugin-events-node": "workspace:^" "@backstage/plugin-permission-common": "workspace:^" "@backstage/plugin-permission-node": "workspace:^" - "@backstage/plugin-scaffolder-backend-module-azure": "workspace:^" - "@backstage/plugin-scaffolder-backend-module-bitbucket": "workspace:^" - "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud": "workspace:^" - "@backstage/plugin-scaffolder-backend-module-bitbucket-server": "workspace:^" - "@backstage/plugin-scaffolder-backend-module-gerrit": "workspace:^" - "@backstage/plugin-scaffolder-backend-module-gitea": "workspace:^" - "@backstage/plugin-scaffolder-backend-module-github": "workspace:^" - "@backstage/plugin-scaffolder-backend-module-gitlab": "workspace:^" "@backstage/plugin-scaffolder-common": "workspace:^" "@backstage/plugin-scaffolder-node": "workspace:^" "@backstage/plugin-scaffolder-node-test-utils": "workspace:^" "@backstage/repo-tools": "workspace:^" "@backstage/types": "workspace:^" - "@opentelemetry/api": "npm:^1.9.0" "@types/express": "npm:^4.17.6" "@types/fs-extra": "npm:^11.0.0" "@types/luxon": "npm:^3.0.0" "@types/nunjucks": "npm:^3.1.4" "@types/supertest": "npm:^2.0.8" "@types/zen-observable": "npm:^0.8.0" - concat-stream: "npm:^2.0.0" - esbuild: "npm:^0.25.0" - express: "npm:^4.17.1" + esbuild: "npm:^0.27.0" + express: "npm:^4.22.0" fs-extra: "npm:^11.2.0" globby: "npm:^11.0.0" isbinaryfile: "npm:^5.0.0" - isolated-vm: "npm:^5.0.1" + isolated-vm: "npm:^6.0.1" jsonschema: "npm:^1.5.0" knex: "npm:^3.0.0" lodash: "npm:^4.17.21" logform: "npm:^2.3.2" luxon: "npm:^3.0.0" nunjucks: "npm:^3.2.3" - p-limit: "npm:^3.1.0" p-queue: "npm:^6.6.2" prom-client: "npm:^15.0.0" strip-ansi: "npm:^7.1.0" supertest: "npm:^7.0.0" - tar: "npm:^6.1.12" triple-beam: "npm:^1.4.1" uuid: "npm:^11.0.0" - wait-for-expect: "npm:^3.0.2" + wait-for-expect: "npm:^4.0.0" winston: "npm:^3.2.1" winston-transport: "npm:^4.7.0" yaml: "npm:^2.0.0" zen-observable: "npm:^0.10.0" - zod: "npm:^3.22.4" - zod-to-json-schema: "npm:^3.20.4" + zod: "npm:^3.25.76 || ^4.0.0" + zod-to-json-schema: "npm:^3.25.1" languageName: unknown linkType: soft @@ -6873,14 +6920,14 @@ __metadata: "@types/react": "npm:^18.0.0" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" winston: "npm:^3.2.1" winston-transport: "npm:^4.7.0" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -6899,6 +6946,7 @@ __metadata: "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/plugin-permission-common": "workspace:^" + "@backstage/plugin-permission-node": "workspace:^" "@backstage/plugin-scaffolder-common": "workspace:^" "@backstage/types": "workspace:^" "@isomorphic-git/pgp-plugin": "npm:^0.0.7" @@ -6909,12 +6957,18 @@ __metadata: isomorphic-git: "npm:^1.23.0" jsonschema: "npm:^1.5.0" lodash: "npm:^4.17.21" + msw: "npm:^1.0.0" p-limit: "npm:^3.1.0" - tar: "npm:^6.1.12" + tar: "npm:^7.5.6" winston: "npm:^3.2.1" winston-transport: "npm:^4.7.0" - zod: "npm:^3.22.4" - zod-to-json-schema: "npm:^3.20.4" + zod: "npm:^3.25.76 || ^4.0.0" + zod-to-json-schema: "npm:^3.25.1" + peerDependencies: + "@backstage/backend-test-utils": "workspace:^" + peerDependenciesMeta: + "@backstage/backend-test-utils": + optional: true languageName: unknown linkType: soft @@ -6929,6 +6983,7 @@ __metadata: "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" + "@backstage/frontend-test-utils": "workspace:^" "@backstage/plugin-catalog": "workspace:^" "@backstage/plugin-catalog-common": "workspace:^" "@backstage/plugin-catalog-react": "workspace:^" @@ -6943,22 +6998,21 @@ __metadata: "@material-ui/icons": "npm:^4.9.1" "@material-ui/lab": "npm:4.0.0-alpha.61" "@react-hookz/web": "npm:^24.0.0" - "@rjsf/core": "npm:5.23.2" - "@rjsf/material-ui": "npm:5.23.2" - "@rjsf/utils": "npm:5.23.2" - "@rjsf/validator-ajv8": "npm:5.23.2" + "@rjsf/core": "npm:5.24.13" + "@rjsf/material-ui": "npm:5.24.13" + "@rjsf/utils": "npm:5.24.13" + "@rjsf/validator-ajv8": "npm:5.24.13" "@testing-library/dom": "npm:^10.0.0" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" "@testing-library/user-event": "npm:^14.0.0" "@types/humanize-duration": "npm:^3.18.1" "@types/json-schema": "npm:^7.0.9" - "@types/luxon": "npm:^3.0.0" "@types/react": "npm:^18.0.0" ajv: "npm:^8.0.1" ajv-errors: "npm:^3.0.0" classnames: "npm:^2.2.6" - flatted: "npm:3.3.3" + flatted: "npm:^3.4.2" humanize-duration: "npm:^3.25.1" immer: "npm:^9.0.6" json-schema: "npm:^0.4.0" @@ -6968,19 +7022,23 @@ __metadata: qs: "npm:^6.9.4" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" react-use: "npm:^17.2.4" swr: "npm:^2.0.0" - use-immer: "npm:^0.10.0" + use-immer: "npm:^0.11.0" zen-observable: "npm:^0.10.0" - zod: "npm:^3.22.4" - zod-to-json-schema: "npm:^3.20.4" + zod: "npm:^3.25.76 || ^4.0.0" + zod-to-json-schema: "npm:^3.25.1" peerDependencies: + "@backstage/frontend-test-utils": "workspace:^" "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router: ^6.30.3 + react-router-dom: ^6.30.2 peerDependenciesMeta: + "@backstage/frontend-test-utils": + optional: true "@types/react": optional: true languageName: unknown @@ -6994,12 +7052,12 @@ __metadata: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/core-app-api": "workspace:^" - "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" + "@backstage/frontend-test-utils": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/integration-react": "workspace:^" "@backstage/plugin-catalog": "workspace:^" @@ -7014,6 +7072,7 @@ __metadata: "@backstage/plugin-techdocs-react": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/types": "workspace:^" + "@backstage/ui": "workspace:^" "@codemirror/language": "npm:^6.0.0" "@codemirror/legacy-modes": "npm:^6.1.0" "@codemirror/view": "npm:^6.0.0" @@ -7021,10 +7080,11 @@ __metadata: "@material-ui/icons": "npm:^4.9.1" "@material-ui/lab": "npm:4.0.0-alpha.61" "@react-hookz/web": "npm:^24.0.0" - "@rjsf/core": "npm:5.23.2" - "@rjsf/material-ui": "npm:5.23.2" - "@rjsf/utils": "npm:5.23.2" - "@rjsf/validator-ajv8": "npm:5.23.2" + "@remixicon/react": "npm:^4.6.0" + "@rjsf/core": "npm:5.24.13" + "@rjsf/material-ui": "npm:5.24.13" + "@rjsf/utils": "npm:5.24.13" + "@rjsf/validator-ajv8": "npm:5.24.13" "@testing-library/dom": "npm:^10.0.0" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" @@ -7049,18 +7109,18 @@ __metadata: react-dom: "npm:^18.0.2" react-resizable: "npm:^3.0.5" react-resizable-panels: "npm:^3.0.4" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" react-use: "npm:^17.2.4" react-window: "npm:^1.8.10" swr: "npm:^2.0.0" yaml: "npm:^2.0.0" - zod: "npm:^3.22.4" - zod-to-json-schema: "npm:^3.20.4" + zod: "npm:^3.25.76 || ^4.0.0" + zod-to-json-schema: "npm:^3.25.1" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -7113,7 +7173,7 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-search-backend-module-explore@workspace:plugins/search-backend-module-explore" dependencies: - "@backstage-community/plugin-explore-common": "npm:^0.5.0" + "@backstage-community/plugin-explore-common": "npm:^0.12.0" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" @@ -7218,12 +7278,11 @@ __metadata: "@types/express": "npm:^4.17.6" "@types/supertest": "npm:^2.0.8" dataloader: "npm:^2.0.0" - express: "npm:^4.17.1" + express: "npm:^4.22.0" lodash: "npm:^4.17.21" qs: "npm:^6.10.1" supertest: "npm:^7.0.0" - yn: "npm:^4.0.0" - zod: "npm:^3.22.4" + zod: "npm:^3.25.76 || ^4.0.0" languageName: unknown linkType: soft @@ -7265,14 +7324,15 @@ __metadata: qs: "npm:^6.9.4" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" react-use: "npm:^17.3.2" uuid: "npm:^11.0.2" + zod: "npm:^4.0.0" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -7285,7 +7345,6 @@ __metadata: dependencies: "@backstage/cli": "workspace:^" "@backstage/core-app-api": "workspace:^" - "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" @@ -7296,6 +7355,7 @@ __metadata: "@backstage/plugin-search-react": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/types": "workspace:^" + "@backstage/ui": "workspace:^" "@backstage/version-bridge": "workspace:^" "@material-ui/core": "npm:^4.12.2" "@material-ui/icons": "npm:^4.9.1" @@ -7308,13 +7368,14 @@ __metadata: qs: "npm:^6.9.4" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" react-use: "npm:^17.2.4" + zod: "npm:^4.0.0" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -7339,7 +7400,7 @@ __metadata: "@types/express": "npm:^4.17.6" "@types/supertest": "npm:^2.0.8" "@types/ws": "npm:^8.5.10" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" supertest: "npm:^7.0.0" uuid: "npm:^11.0.0" @@ -7353,14 +7414,9 @@ __metadata: dependencies: "@backstage/backend-plugin-api": "workspace:^" "@backstage/cli": "workspace:^" - "@backstage/config": "workspace:^" - "@backstage/plugin-auth-node": "workspace:^" "@backstage/plugin-events-node": "workspace:^" "@backstage/types": "workspace:^" "@types/express": "npm:^4.17.21" - express: "npm:^4.17.1" - uuid: "npm:^11.0.0" - ws: "npm:^8.18.0" languageName: unknown linkType: soft @@ -7378,12 +7434,12 @@ __metadata: "@types/react": "npm:^18.0.0" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -7395,7 +7451,6 @@ __metadata: resolution: "@backstage/plugin-signals@workspace:plugins/signals" dependencies: "@backstage/cli": "workspace:^" - "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" @@ -7405,20 +7460,21 @@ __metadata: "@backstage/theme": "workspace:^" "@backstage/types": "workspace:^" "@material-ui/core": "npm:^4.12.4" + "@remixicon/react": "npm:^4.6.0" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" "@types/react": "npm:^18.0.0" jest-websocket-mock: "npm:^2.5.0" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" uuid: "npm:^11.0.0" - wait-for-expect: "npm:^3.0.2" + wait-for-expect: "npm:^4.0.0" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -7444,14 +7500,14 @@ __metadata: "@types/react": "npm:^18.0.0" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" - testing-library__dom: "npm:^7.29.4-beta.1" + react-router-dom: "npm:^6.30.2" + shadow-dom-testing-library: "npm:^1.13.1" peerDependencies: "@testing-library/react": ^16.0.0 "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -7475,7 +7531,7 @@ __metadata: "@backstage/plugin-techdocs-node": "workspace:^" "@backstage/types": "workspace:^" "@types/express": "npm:^4.17.6" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" fs-extra: "npm:^11.2.0" knex: "npm:^3.0.0" @@ -7519,12 +7575,13 @@ __metadata: photoswipe: "npm:^5.3.7" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" + shadow-dom-testing-library: "npm:^1.13.1" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -7562,7 +7619,7 @@ __metadata: "@types/supertest": "npm:^2.0.8" aws-sdk-client-mock: "npm:^4.0.0" dockerode: "npm:^4.0.0" - express: "npm:^4.17.1" + express: "npm:^4.22.0" fs-extra: "npm:^11.2.0" git-url-parse: "npm:^15.0.0" hpagent: "npm:^1.2.0" @@ -7599,14 +7656,13 @@ __metadata: lodash: "npm:^4.17.21" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-helmet: "npm:6.1.0" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" react-use: "npm:^17.2.4" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -7622,7 +7678,6 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/core-app-api": "workspace:^" - "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" @@ -7640,30 +7695,33 @@ __metadata: "@backstage/plugin-techdocs-react": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/theme": "workspace:^" + "@backstage/ui": "workspace:^" "@material-ui/core": "npm:^4.12.2" "@material-ui/icons": "npm:^4.9.1" "@material-ui/lab": "npm:4.0.0-alpha.61" "@material-ui/styles": "npm:^4.10.0" "@microsoft/fetch-event-source": "npm:^2.0.1" + "@remixicon/react": "npm:^4.6.0" "@testing-library/dom": "npm:^10.0.0" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" "@testing-library/user-event": "npm:^14.0.0" "@types/dompurify": "npm:^3.0.0" "@types/react": "npm:^18.0.0" - dompurify: "npm:^3.2.4" + dompurify: "npm:^3.3.2" git-url-parse: "npm:^15.0.0" lodash: "npm:^4.17.21" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" react-helmet: "npm:6.1.0" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" react-use: "npm:^17.2.4" + zod: "npm:^4.0.0" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -7685,10 +7743,12 @@ __metadata: "@backstage/types": "workspace:^" "@types/express": "npm:^4.17.6" "@types/supertest": "npm:^2.0.8" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" knex: "npm:^3.0.0" + p-limit: "npm:^3.1.0" supertest: "npm:^7.0.0" + zod: "npm:^3.25.76 || ^4.0.0" languageName: unknown linkType: soft @@ -7697,6 +7757,7 @@ __metadata: resolution: "@backstage/plugin-user-settings-common@workspace:plugins/user-settings-common" dependencies: "@backstage/cli": "workspace:^" + "@backstage/types": "workspace:^" languageName: unknown linkType: soft @@ -7707,7 +7768,6 @@ __metadata: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/core-app-api": "workspace:^" - "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" @@ -7720,6 +7780,7 @@ __metadata: "@backstage/test-utils": "workspace:^" "@backstage/theme": "workspace:^" "@backstage/types": "workspace:^" + "@backstage/ui": "workspace:^" "@material-ui/core": "npm:^4.12.2" "@material-ui/icons": "npm:^4.9.1" "@material-ui/lab": "npm:4.0.0-alpha.61" @@ -7728,17 +7789,18 @@ __metadata: "@testing-library/react": "npm:^16.0.0" "@testing-library/user-event": "npm:^14.0.0" "@types/react": "npm:^18.0.0" + dataloader: "npm:^2.0.0" msw: "npm:^1.0.0" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" react-use: "npm:^17.2.4" zen-observable: "npm:^0.10.0" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -7772,9 +7834,10 @@ __metadata: "@backstage/types": "workspace:^" "@electric-sql/pglite": "npm:^0.3.0" "@manypkg/get-packages": "npm:^1.1.3" - "@microsoft/api-documenter": "npm:^7.25.7" - "@microsoft/api-extractor": "npm:^7.47.2" + "@microsoft/api-documenter": "npm:^7.28.1" + "@microsoft/api-extractor": "npm:^7.57.3" "@openapitools/openapi-generator-cli": "npm:^2.7.0" + "@prettier/sync": "npm:^0.6.1" "@stoplight/spectral-core": "npm:^1.18.0" "@stoplight/spectral-formatters": "npm:^1.1.0" "@stoplight/spectral-functions": "npm:^1.7.2" @@ -7783,16 +7846,16 @@ __metadata: "@stoplight/spectral-runtime": "npm:^1.1.2" "@stoplight/types": "npm:^14.0.0" "@types/is-glob": "npm:^4.0.2" - "@types/node": "npm:^20.16.0" + "@types/node": "npm:^22.13.14" "@types/prettier": "npm:^2.0.0" "@useoptic/openapi-utilities": "npm:^0.55.0" chalk: "npm:^4.0.0" chokidar: "npm:^3.5.3" codeowners-utils: "npm:^1.0.2" command-exists: "npm:^1.2.9" - commander: "npm:^12.0.0" + commander: "npm:^14.0.3" fs-extra: "npm:^11.2.0" - glob: "npm:^8.0.3" + glob: "npm:^13.0.0" globby: "npm:^11.0.0" is-glob: "npm:^4.0.3" js-yaml: "npm:^4.1.0" @@ -7801,20 +7864,20 @@ __metadata: knex-pglite: "npm:^0.11.0" knip: "npm:^5.42.0" lodash: "npm:^4.17.21" - minimatch: "npm:^9.0.0" + minimatch: "npm:^10.2.1" p-limit: "npm:^3.0.2" portfinder: "npm:^1.0.32" - tar: "npm:^6.1.12" + tar: "npm:^7.5.6" ts-morph: "npm:^24.0.0" typedoc: "npm:^0.28.0" yaml-diff-patch: "npm:^2.0.0" - zod: "npm:^3.22.4" + zod: "npm:^3.25.76 || ^4.0.0" peerDependencies: "@microsoft/api-extractor-model": "*" "@microsoft/tsdoc": "*" "@microsoft/tsdoc-config": "*" "@useoptic/optic": ^1.0.0 - prettier: ^2.8.1 + prettier: ^2.8.1 || ^3.8.1 typedoc: ^0.28.0 typescript: "> 3.0.0" peerDependenciesMeta: @@ -7849,15 +7912,18 @@ __metadata: msw: "npm:^1.0.0" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" zen-observable: "npm:^0.10.0" peerDependencies: "@testing-library/react": ^16.0.0 + "@types/jest": "*" "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: + "@types/jest": + optional: true "@types/react": optional: true languageName: unknown @@ -7877,13 +7943,13 @@ __metadata: "@types/react": "npm:^18.0.0" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" peerDependencies: "@material-ui/core": ^4.12.2 "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -7906,27 +7972,30 @@ __metadata: resolution: "@backstage/ui@workspace:packages/ui" dependencies: "@backstage/cli": "workspace:^" + "@backstage/version-bridge": "workspace:^" "@remixicon/react": "npm:^4.6.0" + "@storybook/react-vite": "npm:^10.3.3" "@tanstack/react-table": "npm:^8.21.3" "@types/react": "npm:^18.0.0" "@types/react-dom": "npm:^18.0.0" - chalk: "npm:^5.4.1" + "@types/use-sync-external-store": "npm:^1.0.0" clsx: "npm:^2.1.1" - eslint-plugin-storybook: "npm:^9.1.7" - glob: "npm:^11.0.1" - globals: "npm:^15.11.0" - lightningcss: "npm:^1.29.1" - mini-css-extract-plugin: "npm:^2.9.2" + eslint-plugin-storybook: "npm:^10.3.3" + glob: "npm:^13.0.0" + globals: "npm:^17.0.0" react: "npm:^18.0.2" - react-aria-components: "npm:^1.13.0" + react-aria: "npm:~3.48.0" + react-aria-components: "npm:~1.17.0" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" - storybook: "npm:^9.1.7" + react-router-dom: "npm:^6.30.2" + react-stately: "npm:~3.46.0" + storybook: "npm:^10.3.3" + use-sync-external-store: "npm:^1.4.0" peerDependencies: - "@types/react": ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + "@types/react": ^18.0.0 + react: ^18.0.0 + react-dom: ^18.0.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -7943,12 +8012,12 @@ __metadata: "@types/react": "npm:^18.0.0" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -7969,6 +8038,13 @@ __metadata: languageName: node linkType: hard +"@borewit/text-codec@npm:^0.2.1": + version: 0.2.1 + resolution: "@borewit/text-codec@npm:0.2.1" + checksum: 10/3d7e824ac4d3ea16e6e910a7f2bac79f262602c3dbc2f525fd9b86786269c5d7bbd673090a0277d7f92652e534f263e292d5ace080bc9bdf57dc6921c1973f70 + languageName: node + linkType: hard + "@bundled-es-modules/cookie@npm:^2.0.1": version: 2.0.1 resolution: "@bundled-es-modules/cookie@npm:2.0.1" @@ -7987,11 +8063,18 @@ __metadata: languageName: node linkType: hard -"@changesets/apply-release-plan@npm:^7.0.13": - version: 7.0.13 - resolution: "@changesets/apply-release-plan@npm:7.0.13" +"@cfworker/json-schema@npm:^4.1.1": + version: 4.1.1 + resolution: "@cfworker/json-schema@npm:4.1.1" + checksum: 10/62fd08bb2e6b4f0fe7c2b8f8c19f17f94b6a34feba7f455f228898ab435eda8aae082fcf6b0fe8a235a72e0ec0041922fdcd4c526acc32d45084272f000c1af9 + languageName: node + linkType: hard + +"@changesets/apply-release-plan@npm:^7.1.0": + version: 7.1.0 + resolution: "@changesets/apply-release-plan@npm:7.1.0" dependencies: - "@changesets/config": "npm:^3.1.1" + "@changesets/config": "npm:^3.1.3" "@changesets/get-version-range-type": "npm:^0.4.0" "@changesets/git": "npm:^3.0.4" "@changesets/should-skip-package": "npm:^0.1.2" @@ -8004,7 +8087,7 @@ __metadata: prettier: "npm:^2.7.1" resolve-from: "npm:^5.0.0" semver: "npm:^7.5.3" - checksum: 10/b2ef4fc9a68ffd5c0543f0a98b8ea2321ff58519d541720646692a03844a2cd8e860ebcb93846be1e062926414dc343333196bfd8806fab26f637e8db8adbb9e + checksum: 10/2ad86efb4b4218540e1ff17414436edcf7b801727dc4ec6cd1de42b5bf206e1fc0a29b14872e9635a9e991d6342ec9fb9a8b63c9b50679afd716f8bb3c1c847e languageName: node linkType: hard @@ -8032,31 +8115,29 @@ __metadata: linkType: hard "@changesets/cli@npm:^2.14.0": - version: 2.29.7 - resolution: "@changesets/cli@npm:2.29.7" + version: 2.30.0 + resolution: "@changesets/cli@npm:2.30.0" dependencies: - "@changesets/apply-release-plan": "npm:^7.0.13" + "@changesets/apply-release-plan": "npm:^7.1.0" "@changesets/assemble-release-plan": "npm:^6.0.9" "@changesets/changelog-git": "npm:^0.2.1" - "@changesets/config": "npm:^3.1.1" + "@changesets/config": "npm:^3.1.3" "@changesets/errors": "npm:^0.2.0" "@changesets/get-dependents-graph": "npm:^2.1.3" - "@changesets/get-release-plan": "npm:^4.0.13" + "@changesets/get-release-plan": "npm:^4.0.15" "@changesets/git": "npm:^3.0.4" "@changesets/logger": "npm:^0.1.1" "@changesets/pre": "npm:^2.0.2" - "@changesets/read": "npm:^0.6.5" + "@changesets/read": "npm:^0.6.7" "@changesets/should-skip-package": "npm:^0.1.2" "@changesets/types": "npm:^6.1.0" "@changesets/write": "npm:^0.4.0" - "@inquirer/external-editor": "npm:^1.0.0" + "@inquirer/external-editor": "npm:^1.0.2" "@manypkg/get-packages": "npm:^1.1.3" ansi-colors: "npm:^4.1.3" - ci-info: "npm:^3.7.0" enquirer: "npm:^2.4.1" fs-extra: "npm:^7.0.1" mri: "npm:^1.2.0" - p-limit: "npm:^2.2.0" package-manager-detector: "npm:^0.2.0" picocolors: "npm:^1.1.0" resolve-from: "npm:^5.0.0" @@ -8065,22 +8146,23 @@ __metadata: term-size: "npm:^2.1.0" bin: changeset: bin.js - checksum: 10/e44ee8e9a09ffc990707ec272b03f5724890e6d8833815b80265a9e62f2784ee3fa76c858469fa95c53e1dabd9a0500a6c36b1343211fc0a38902d8fd1b1fce5 + checksum: 10/0ffd121b0349cfa3390de581905d3d7db9a650fcefe80e4cfbf9f4b55ddad53afc09ea2f2e78abd848db53479e2b54568d99e6d09d5d06f556adb45bb66aec53 languageName: node linkType: hard -"@changesets/config@npm:^3.1.1": - version: 3.1.1 - resolution: "@changesets/config@npm:3.1.1" +"@changesets/config@npm:^3.1.3": + version: 3.1.3 + resolution: "@changesets/config@npm:3.1.3" dependencies: "@changesets/errors": "npm:^0.2.0" "@changesets/get-dependents-graph": "npm:^2.1.3" "@changesets/logger": "npm:^0.1.1" + "@changesets/should-skip-package": "npm:^0.1.2" "@changesets/types": "npm:^6.1.0" "@manypkg/get-packages": "npm:^1.1.3" fs-extra: "npm:^7.0.1" micromatch: "npm:^4.0.8" - checksum: 10/9500e02b68801f052478b3e10523bd3a39b9e5e989e718832832537c9da965580f496262c2bc3f6e23a4e6fb4303f730a69dcbf2041f68d2fa7bd03dd1f82db0 + checksum: 10/278699f2b1673e07b9744fcd83948149647f55f295dc9d4bd22e9a1e80309863a58513bb1429376959b66358156b77a50a8d60eb70bf0ba9a3fab5402ccd5980 languageName: node linkType: hard @@ -8105,17 +8187,17 @@ __metadata: languageName: node linkType: hard -"@changesets/get-release-plan@npm:^4.0.13": - version: 4.0.13 - resolution: "@changesets/get-release-plan@npm:4.0.13" +"@changesets/get-release-plan@npm:^4.0.15": + version: 4.0.15 + resolution: "@changesets/get-release-plan@npm:4.0.15" dependencies: "@changesets/assemble-release-plan": "npm:^6.0.9" - "@changesets/config": "npm:^3.1.1" + "@changesets/config": "npm:^3.1.3" "@changesets/pre": "npm:^2.0.2" - "@changesets/read": "npm:^0.6.5" + "@changesets/read": "npm:^0.6.7" "@changesets/types": "npm:^6.1.0" "@manypkg/get-packages": "npm:^1.1.3" - checksum: 10/9983fae5a68012c4c418ddd62f2fb3d325363f21160252ff7b868503a1a2effb8fdd32e4a0289b72653afc3605ce19d163ff69205c942a0004efb571a5f78fd0 + checksum: 10/eba3de04c03131604ab717af66c5d645e1fe4823034864e28eccf99fb64fbeb4cf2c4c11b8f0e7ef78a916f96eeaebdf4569df583cbfe0f3928bb0195baf27c7 languageName: node linkType: hard @@ -8148,13 +8230,13 @@ __metadata: languageName: node linkType: hard -"@changesets/parse@npm:^0.4.1": - version: 0.4.1 - resolution: "@changesets/parse@npm:0.4.1" +"@changesets/parse@npm:^0.4.3": + version: 0.4.3 + resolution: "@changesets/parse@npm:0.4.3" dependencies: "@changesets/types": "npm:^6.1.0" - js-yaml: "npm:^3.13.1" - checksum: 10/2973ab8f38592a80efea589e148e5bdfd6ed3af86aa9206f941b5b3955f68464bf70a5965349f642667c708ebae60e4266be538328cd27075cace3f7cc1022e3 + js-yaml: "npm:^4.1.1" + checksum: 10/f5742266a4ecb90a8283868f2bec740ce7be94745dee7df0b2f47882775d700bdfa3b660c2ec8d06b64153cf9b02cb3d46064f391c62933c9c60a9570763f928 languageName: node linkType: hard @@ -8170,18 +8252,18 @@ __metadata: languageName: node linkType: hard -"@changesets/read@npm:^0.6.5": - version: 0.6.5 - resolution: "@changesets/read@npm:0.6.5" +"@changesets/read@npm:^0.6.7": + version: 0.6.7 + resolution: "@changesets/read@npm:0.6.7" dependencies: "@changesets/git": "npm:^3.0.4" "@changesets/logger": "npm:^0.1.1" - "@changesets/parse": "npm:^0.4.1" + "@changesets/parse": "npm:^0.4.3" "@changesets/types": "npm:^6.1.0" fs-extra: "npm:^7.0.1" p-filter: "npm:^2.1.0" picocolors: "npm:^1.1.0" - checksum: 10/fec0ac28801e0560fae0eb1d21250dd2a48aaff67bddd1b446a960afd761690d5873dca6eff369d43763bec61f1023d38a38876d5824e316e6de622dc52a24f3 + checksum: 10/6bf3fd4b0c743d2ef53cb39c4e52731622949a695f031412ff6ea9f30860620a1d9deb1cfd1af0c92d2e2d275b6d949c0cc1e83c192f2094e0071d690c48a42e languageName: node linkType: hard @@ -8251,16 +8333,16 @@ __metadata: linkType: hard "@codemirror/language@npm:^6.0.0": - version: 6.11.3 - resolution: "@codemirror/language@npm:6.11.3" + version: 6.12.2 + resolution: "@codemirror/language@npm:6.12.2" dependencies: "@codemirror/state": "npm:^6.0.0" "@codemirror/view": "npm:^6.23.0" - "@lezer/common": "npm:^1.1.0" + "@lezer/common": "npm:^1.5.0" "@lezer/highlight": "npm:^1.0.0" "@lezer/lr": "npm:^1.0.0" style-mod: "npm:^4.0.0" - checksum: 10/8538a2835c1de6ca2d520ff66449185f2ea3a93e7d69382d9db3b4db6460f4c46b44f19724458c50230abfa87cf2c225834d39c3fe3119c48370db6b3de0b772 + checksum: 10/9afc704e17cad4782b68ad02869503a27103c4c3ac6927d2ffa3c6decfad627ade40b0ed78d1d6e941b2e879a7a79bda0d573e58604f3923f9e1f3311108ec36 languageName: node linkType: hard @@ -8317,14 +8399,21 @@ __metadata: linkType: hard "@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.23.0": - version: 6.38.6 - resolution: "@codemirror/view@npm:6.38.6" + version: 6.39.16 + resolution: "@codemirror/view@npm:6.39.16" dependencies: "@codemirror/state": "npm:^6.5.0" crelt: "npm:^1.0.6" style-mod: "npm:^4.1.0" w3c-keyname: "npm:^2.2.4" - checksum: 10/5a047337a98de111817ce8c8d39e6429c90ca0b0a4d2678d6e161e9e5961b1d476a891f447ab7a05cac395d4a93530e7c68bedd93191285265f0742a308ad00b + checksum: 10/199576febda2a91fe7676b8708627ed2e38d7e964ec8258331422fe7c7f89003eee2de7dec828e09c046de005742fd476cae6ceebc7bd994744f771253bfcbf3 + languageName: node + linkType: hard + +"@colors/colors@npm:1.5.0": + version: 1.5.0 + resolution: "@colors/colors@npm:1.5.0" + checksum: 10/9d226461c1e91e95f067be2bdc5e6f99cfe55a721f45afb44122e23e4b8602eeac4ff7325af6b5a369f36396ee1514d3809af3f57769066d80d83790d8e53339 languageName: node linkType: hard @@ -8344,14 +8433,69 @@ __metadata: languageName: node linkType: hard -"@dabh/diagnostics@npm:^2.0.2": - version: 2.0.2 - resolution: "@dabh/diagnostics@npm:2.0.2" +"@csstools/color-helpers@npm:^5.1.0": + version: 5.1.0 + resolution: "@csstools/color-helpers@npm:5.1.0" + checksum: 10/0138b3d5ccbe77aeccf6721fd008a53523c70e932f0c82dca24a1277ca780447e1d8357da47512ebf96358476f8764de57002f3e491920d67e69202f5a74c383 + languageName: node + linkType: hard + +"@csstools/css-calc@npm:^2.1.3, @csstools/css-calc@npm:^2.1.4": + version: 2.1.4 + resolution: "@csstools/css-calc@npm:2.1.4" + peerDependencies: + "@csstools/css-parser-algorithms": ^3.0.5 + "@csstools/css-tokenizer": ^3.0.4 + checksum: 10/06975b650c0f44c60eeb7afdb3fd236f2dd607b2c622e0bc908d3f54de39eb84e0692833320d03dac04bd6c1ab0154aa3fa0dd442bd9e5f917cf14d8e2ba8d74 + languageName: node + linkType: hard + +"@csstools/css-color-parser@npm:^3.0.9, @csstools/css-color-parser@npm:^3.1.0": + version: 3.1.0 + resolution: "@csstools/css-color-parser@npm:3.1.0" dependencies: - colorspace: "npm:1.1.x" + "@csstools/color-helpers": "npm:^5.1.0" + "@csstools/css-calc": "npm:^2.1.4" + peerDependencies: + "@csstools/css-parser-algorithms": ^3.0.5 + "@csstools/css-tokenizer": ^3.0.4 + checksum: 10/4741095fdc4501e8e7ada4ed14fbf9dbbe6fea9b989818790ebca15657c29c62defbebacf18592cde2aa638a1d098bbe86d742d2c84ba932fbc00fac51cb8805 + languageName: node + linkType: hard + +"@csstools/css-parser-algorithms@npm:^3.0.4, @csstools/css-parser-algorithms@npm:^3.0.5": + version: 3.0.5 + resolution: "@csstools/css-parser-algorithms@npm:3.0.5" + peerDependencies: + "@csstools/css-tokenizer": ^3.0.4 + checksum: 10/e93083b5cb36a3c1e7a47ce10cf62961d05bd1e4c608bb3ee50186ff740157ab0ec16a3956f7b86251efd10703034d849693201eea858ae904848c68d2d46ada + languageName: node + linkType: hard + +"@csstools/css-syntax-patches-for-csstree@npm:1.0.14": + version: 1.0.14 + resolution: "@csstools/css-syntax-patches-for-csstree@npm:1.0.14" + peerDependencies: + postcss: ^8.4 + checksum: 10/c783d5db307552f483d95266452a7765ca138a9e64f12d013c63e960c9c8abbf82c899a34028af1f5ad714e0e94edd97b1aa31784923c1d7d1756d775c3c1d0a + languageName: node + linkType: hard + +"@csstools/css-tokenizer@npm:^3.0.3, @csstools/css-tokenizer@npm:^3.0.4": + version: 3.0.4 + resolution: "@csstools/css-tokenizer@npm:3.0.4" + checksum: 10/eb6c84c086312f6bb8758dfe2c85addd7475b0927333c5e39a4d59fb210b9810f8c346972046f95e60a721329cffe98895abe451e51de753ad1ca7a8c24ec65f + languageName: node + linkType: hard + +"@dabh/diagnostics@npm:^2.0.8": + version: 2.0.8 + resolution: "@dabh/diagnostics@npm:2.0.8" + dependencies: + "@so-ric/colorspace": "npm:^1.1.6" enabled: "npm:2.0.x" kuler: "npm:^2.0.0" - checksum: 10/d0c7ae32da9fc6061272ef56cf2c5af9c255e034783b68cfe3b0d47e806145d0723e6f5743e4c9fc0abae73658ca309498572688da2fbcfc56c16a8671dbe707 + checksum: 10/ac2267a4ee1874f608493f21d386ea29f0acac6716124e26e3e48e01ce5706b095585a14adce1bee14b6567d3b8fdd0c5a0bbb7ab0e15c9a743d55eb02f093ce languageName: node linkType: hard @@ -8423,14 +8567,14 @@ __metadata: linkType: hard "@elastic/elasticsearch@npm:^7.13.0": - version: 7.17.0 - resolution: "@elastic/elasticsearch@npm:7.17.0" + version: 7.17.14 + resolution: "@elastic/elasticsearch@npm:7.17.14" dependencies: debug: "npm:^4.3.1" hpagent: "npm:^0.1.1" ms: "npm:^2.1.3" secure-json-parse: "npm:^2.4.0" - checksum: 10/d54330ce50b4951b7b9db15349413b4961040fb0b73a09d3f07cef5cb2873fd22af17307e07b6c8b1b1e0844e76e9aeb78ce1e01d67a940e3190763a875648be + checksum: 10/ef26489eb8db667f0a6c2be4641e9b388bc5badd2ab7791eb82dffa138653642678f7e6ab2741b914747e95e0d43bf9daa0821bc509318c14cb40869cc9ebbc9 languageName: node linkType: hard @@ -8442,28 +8586,84 @@ __metadata: linkType: hard "@electric-sql/pglite@npm:^0.3.0": - version: 0.3.14 - resolution: "@electric-sql/pglite@npm:0.3.14" - checksum: 10/7cd072cd6a5c4314b47e7d26cc2ac191fcca5ba204f3bb1908bdb34746e51315cbde3e53646020d6a7c19c087d7845d2908850d9f4312285be10b3aefe28d621 + version: 0.3.16 + resolution: "@electric-sql/pglite@npm:0.3.16" + checksum: 10/1feba052caef61cbca6f8f8d9cdc5807e9d70bc2499a6b74ac722bd4f33fac87ccbd7a2efe9607d2632ca61805e1e57327fc97741858ba85100a09043bcc2a0f languageName: node linkType: hard -"@emnapi/core@npm:^1.5.0": - version: 1.7.0 - resolution: "@emnapi/core@npm:1.7.0" +"@embedded-postgres/darwin-arm64@npm:^18.3.0-beta.16": + version: 18.3.0-beta.16 + resolution: "@embedded-postgres/darwin-arm64@npm:18.3.0-beta.16" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@embedded-postgres/darwin-x64@npm:^18.3.0-beta.16": + version: 18.3.0-beta.16 + resolution: "@embedded-postgres/darwin-x64@npm:18.3.0-beta.16" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@embedded-postgres/linux-arm64@npm:^18.3.0-beta.16": + version: 18.3.0-beta.16 + resolution: "@embedded-postgres/linux-arm64@npm:18.3.0-beta.16" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@embedded-postgres/linux-arm@npm:^18.3.0-beta.16": + version: 18.3.0-beta.16 + resolution: "@embedded-postgres/linux-arm@npm:18.3.0-beta.16" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@embedded-postgres/linux-ia32@npm:^18.3.0-beta.16": + version: 18.3.0-beta.16 + resolution: "@embedded-postgres/linux-ia32@npm:18.3.0-beta.16" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@embedded-postgres/linux-ppc64@npm:^18.3.0-beta.16": + version: 18.3.0-beta.16 + resolution: "@embedded-postgres/linux-ppc64@npm:18.3.0-beta.16" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@embedded-postgres/linux-x64@npm:^18.3.0-beta.16": + version: 18.3.0-beta.16 + resolution: "@embedded-postgres/linux-x64@npm:18.3.0-beta.16" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@embedded-postgres/windows-x64@npm:^18.3.0-beta.16": + version: 18.3.0-beta.16 + resolution: "@embedded-postgres/windows-x64@npm:18.3.0-beta.16" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@emnapi/core@npm:^1.4.3, @emnapi/core@npm:^1.5.0, @emnapi/core@npm:^1.7.1": + version: 1.7.1 + resolution: "@emnapi/core@npm:1.7.1" dependencies: "@emnapi/wasi-threads": "npm:1.1.0" tslib: "npm:^2.4.0" - checksum: 10/43bb6fd7419b9589fe8190a09fce84e52c04cef171bcb1c411c278fb5380c4a76081776e5094f42c220deaf49e2e41905de5569209790ffee9b8efa59c987829 + checksum: 10/260841f6dd2a7823a964d9de6da3a5e6f565dac8d21a5bd8f6215b87c45c22a4dc371b9ad877961579ee3cca8a76e55e3dd033ae29cba1998999cda6d794bdab languageName: node linkType: hard -"@emnapi/runtime@npm:^1.5.0": - version: 1.7.0 - resolution: "@emnapi/runtime@npm:1.7.0" +"@emnapi/runtime@npm:^1.4.3, @emnapi/runtime@npm:^1.5.0, @emnapi/runtime@npm:^1.7.1": + version: 1.7.1 + resolution: "@emnapi/runtime@npm:1.7.1" dependencies: tslib: "npm:^2.4.0" - checksum: 10/4dc726eb42fe2c7777fd32090f3e5e006c630e1a732538139caa18daf586e883e81c562cd69b0622db16e76bb572a2dde30711494edcee4a34059b62f5f46267 + checksum: 10/6fc83f938e3c70e32e84c1fbe5cab6cb9340b8107cee4048384ad5b8f2998a06502b4bed342acaf6e44f473f2c14c4ab1e3fd5083bd7823fc63abfca9eff0175 languageName: node linkType: hard @@ -8495,16 +8695,16 @@ __metadata: languageName: node linkType: hard -"@emotion/cache@npm:^11.13.5": - version: 11.13.5 - resolution: "@emotion/cache@npm:11.13.5" +"@emotion/cache@npm:^11.13.5, @emotion/cache@npm:^11.14.0": + version: 11.14.0 + resolution: "@emotion/cache@npm:11.14.0" dependencies: "@emotion/memoize": "npm:^0.9.0" "@emotion/sheet": "npm:^1.4.0" "@emotion/utils": "npm:^1.4.2" "@emotion/weak-memoize": "npm:^0.4.0" stylis: "npm:4.2.0" - checksum: 10/d91139453d279cfd6f6f38180d3af2fdcee8c0fc6d9a6faa2cdce9a1211b294a8019ef45365bf1171e0687d1744a70ff760637b88ed46f7a9fe74db9dc36f4df + checksum: 10/52336b28a27b07dde8fcdfd80851cbd1487672bbd4db1e24cca1440c95d8a6a968c57b0453c2b7c88d9b432b717f99554dbecc05b5cdef27933299827e69fd8e languageName: node linkType: hard @@ -8555,14 +8755,14 @@ __metadata: linkType: hard "@emotion/react@npm:^11.10.5": - version: 11.13.5 - resolution: "@emotion/react@npm:11.13.5" + version: 11.14.0 + resolution: "@emotion/react@npm:11.14.0" dependencies: "@babel/runtime": "npm:^7.18.3" "@emotion/babel-plugin": "npm:^11.13.5" - "@emotion/cache": "npm:^11.13.5" + "@emotion/cache": "npm:^11.14.0" "@emotion/serialize": "npm:^1.3.3" - "@emotion/use-insertion-effect-with-fallbacks": "npm:^1.1.0" + "@emotion/use-insertion-effect-with-fallbacks": "npm:^1.2.0" "@emotion/utils": "npm:^1.4.2" "@emotion/weak-memoize": "npm:^0.4.0" hoist-non-react-statics: "npm:^3.3.1" @@ -8571,7 +8771,7 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10/c85bde9c6144be6f243e44d6834b16df8151b91c5136ea28d9d917155d64d1bbfba000ae98866fd9f5089546f0b0655c20b9e4280cdc5ccf39acc4a2fde37a39 + checksum: 10/3356c1d66f37f4e7abf88a2be843f6023b794b286c9c99a0aaf1cd1b2b7c50f8d80a2ef77183da737de70150f638e698ff4a2a38ab2d922f868615f1d5761c37 languageName: node linkType: hard @@ -8596,14 +8796,14 @@ __metadata: linkType: hard "@emotion/styled@npm:^11.10.5": - version: 11.13.5 - resolution: "@emotion/styled@npm:11.13.5" + version: 11.14.1 + resolution: "@emotion/styled@npm:11.14.1" dependencies: "@babel/runtime": "npm:^7.18.3" "@emotion/babel-plugin": "npm:^11.13.5" "@emotion/is-prop-valid": "npm:^1.3.0" "@emotion/serialize": "npm:^1.3.3" - "@emotion/use-insertion-effect-with-fallbacks": "npm:^1.1.0" + "@emotion/use-insertion-effect-with-fallbacks": "npm:^1.2.0" "@emotion/utils": "npm:^1.4.2" peerDependencies: "@emotion/react": ^11.0.0-rc.0 @@ -8611,7 +8811,7 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10/cf6c47f9e9292b817aaec4e34cedc996727318018b621525576a1f541cfb3ecf2443729bfba503d4467d8183ca7732fe817bd7202386f4206a9d218e6e2202d8 + checksum: 10/b20ffaaac76e16538051da8d417f1da75f47f0974000edf0999f39f309b23ee0a91ba7dc1d5f60c4017d29fadfed48631ae4a8f697e3662a88318c667d072117 languageName: node linkType: hard @@ -8636,12 +8836,12 @@ __metadata: languageName: node linkType: hard -"@emotion/use-insertion-effect-with-fallbacks@npm:^1.1.0": - version: 1.1.0 - resolution: "@emotion/use-insertion-effect-with-fallbacks@npm:1.1.0" +"@emotion/use-insertion-effect-with-fallbacks@npm:^1.2.0": + version: 1.2.0 + resolution: "@emotion/use-insertion-effect-with-fallbacks@npm:1.2.0" peerDependencies: react: ">=16.8.0" - checksum: 10/33a10f44a873b3f5ccd2a1a3d13c2f34ed628f5a2be1ccf28540a86535a14d3a930afcbef209d48346a22ec60ff48f43c86ee9c846b9480d23a55a17145da66c + checksum: 10/2374999db8d53ef661d61ed1026c42a849632e4f03826f7eba0314c1d92ae342161d737f5045453aa46dd4008e13ccefeba68d3165b667dfad8e5784fcb0c643 languageName: node linkType: hard @@ -8659,364 +8859,242 @@ __metadata: languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/aix-ppc64@npm:0.21.5" +"@envelop/core@npm:^5.4.0": + version: 5.5.1 + resolution: "@envelop/core@npm:5.5.1" + dependencies: + "@envelop/instrumentation": "npm:^1.0.0" + "@envelop/types": "npm:^5.2.1" + "@whatwg-node/promise-helpers": "npm:^1.2.4" + tslib: "npm:^2.5.0" + checksum: 10/1dfac266702047d07533a893a33010fa31c5056c87196db2fcd5d44b0856d50d3410ac8aeb8f3c1b65f3a227f5fb7d1f1782051e71a9197978defe51ebcc7366 + languageName: node + linkType: hard + +"@envelop/instrumentation@npm:^1.0.0": + version: 1.0.0 + resolution: "@envelop/instrumentation@npm:1.0.0" + dependencies: + "@whatwg-node/promise-helpers": "npm:^1.2.1" + tslib: "npm:^2.5.0" + checksum: 10/4e3c9670c17e7fcf4a5654d145c64ba905abca7e71c8d8fae1ad88b1eb4a68f3a2a0aff4efd41e3d997015e5e763a7c1c335826c2b7b5f9dc691e59cd4ea472e + languageName: node + linkType: hard + +"@envelop/types@npm:^5.2.1": + version: 5.2.1 + resolution: "@envelop/types@npm:5.2.1" + dependencies: + "@whatwg-node/promise-helpers": "npm:^1.0.0" + tslib: "npm:^2.5.0" + checksum: 10/dc320a53dab896cef43de99ff972cf35da2671a248cd94fe6ab45f96954c9e505dd141cb8a3afb5fbab3d41bf4d22d30d823effb9a6fec0e7c3bb95d4c3726d1 + languageName: node + linkType: hard + +"@epic-web/invariant@npm:^1.0.0": + version: 1.0.0 + resolution: "@epic-web/invariant@npm:1.0.0" + checksum: 10/28b36a7447f60b84f9d6a23571480042170ef4239a577577ad8447f64a2e4f1a4e57e6fe1b592e61534c5ab53ff67776130e6c88a68cbd997eb6e9c9759a5934 + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.27.4": + version: 0.27.4 + resolution: "@esbuild/aix-ppc64@npm:0.27.4" conditions: os=aix & cpu=ppc64 languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/aix-ppc64@npm:0.25.8" - conditions: os=aix & cpu=ppc64 - languageName: node - linkType: hard - -"@esbuild/android-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/android-arm64@npm:0.21.5" +"@esbuild/android-arm64@npm:0.27.4": + version: 0.27.4 + resolution: "@esbuild/android-arm64@npm:0.27.4" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/android-arm64@npm:0.25.8" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/android-arm@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/android-arm@npm:0.21.5" +"@esbuild/android-arm@npm:0.27.4": + version: 0.27.4 + resolution: "@esbuild/android-arm@npm:0.27.4" conditions: os=android & cpu=arm languageName: node linkType: hard -"@esbuild/android-arm@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/android-arm@npm:0.25.8" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - -"@esbuild/android-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/android-x64@npm:0.21.5" +"@esbuild/android-x64@npm:0.27.4": + version: 0.27.4 + resolution: "@esbuild/android-x64@npm:0.27.4" conditions: os=android & cpu=x64 languageName: node linkType: hard -"@esbuild/android-x64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/android-x64@npm:0.25.8" - conditions: os=android & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/darwin-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/darwin-arm64@npm:0.21.5" +"@esbuild/darwin-arm64@npm:0.27.4": + version: 0.27.4 + resolution: "@esbuild/darwin-arm64@npm:0.27.4" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/darwin-arm64@npm:0.25.8" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/darwin-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/darwin-x64@npm:0.21.5" +"@esbuild/darwin-x64@npm:0.27.4": + version: 0.27.4 + resolution: "@esbuild/darwin-x64@npm:0.27.4" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/darwin-x64@npm:0.25.8" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/freebsd-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/freebsd-arm64@npm:0.21.5" +"@esbuild/freebsd-arm64@npm:0.27.4": + version: 0.27.4 + resolution: "@esbuild/freebsd-arm64@npm:0.27.4" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/freebsd-arm64@npm:0.25.8" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/freebsd-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/freebsd-x64@npm:0.21.5" +"@esbuild/freebsd-x64@npm:0.27.4": + version: 0.27.4 + resolution: "@esbuild/freebsd-x64@npm:0.27.4" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/freebsd-x64@npm:0.25.8" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/linux-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-arm64@npm:0.21.5" +"@esbuild/linux-arm64@npm:0.27.4": + version: 0.27.4 + resolution: "@esbuild/linux-arm64@npm:0.27.4" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/linux-arm64@npm:0.25.8" - conditions: os=linux & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/linux-arm@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-arm@npm:0.21.5" +"@esbuild/linux-arm@npm:0.27.4": + version: 0.27.4 + resolution: "@esbuild/linux-arm@npm:0.27.4" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/linux-arm@npm:0.25.8" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - -"@esbuild/linux-ia32@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-ia32@npm:0.21.5" +"@esbuild/linux-ia32@npm:0.27.4": + version: 0.27.4 + resolution: "@esbuild/linux-ia32@npm:0.27.4" conditions: os=linux & cpu=ia32 languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/linux-ia32@npm:0.25.8" - conditions: os=linux & cpu=ia32 - languageName: node - linkType: hard - -"@esbuild/linux-loong64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-loong64@npm:0.21.5" +"@esbuild/linux-loong64@npm:0.27.4": + version: 0.27.4 + resolution: "@esbuild/linux-loong64@npm:0.27.4" conditions: os=linux & cpu=loong64 languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/linux-loong64@npm:0.25.8" - conditions: os=linux & cpu=loong64 - languageName: node - linkType: hard - -"@esbuild/linux-mips64el@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-mips64el@npm:0.21.5" +"@esbuild/linux-mips64el@npm:0.27.4": + version: 0.27.4 + resolution: "@esbuild/linux-mips64el@npm:0.27.4" conditions: os=linux & cpu=mips64el languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/linux-mips64el@npm:0.25.8" - conditions: os=linux & cpu=mips64el - languageName: node - linkType: hard - -"@esbuild/linux-ppc64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-ppc64@npm:0.21.5" +"@esbuild/linux-ppc64@npm:0.27.4": + version: 0.27.4 + resolution: "@esbuild/linux-ppc64@npm:0.27.4" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/linux-ppc64@npm:0.25.8" - conditions: os=linux & cpu=ppc64 - languageName: node - linkType: hard - -"@esbuild/linux-riscv64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-riscv64@npm:0.21.5" +"@esbuild/linux-riscv64@npm:0.27.4": + version: 0.27.4 + resolution: "@esbuild/linux-riscv64@npm:0.27.4" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/linux-riscv64@npm:0.25.8" - conditions: os=linux & cpu=riscv64 - languageName: node - linkType: hard - -"@esbuild/linux-s390x@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-s390x@npm:0.21.5" +"@esbuild/linux-s390x@npm:0.27.4": + version: 0.27.4 + resolution: "@esbuild/linux-s390x@npm:0.27.4" conditions: os=linux & cpu=s390x languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/linux-s390x@npm:0.25.8" - conditions: os=linux & cpu=s390x - languageName: node - linkType: hard - -"@esbuild/linux-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-x64@npm:0.21.5" +"@esbuild/linux-x64@npm:0.27.4": + version: 0.27.4 + resolution: "@esbuild/linux-x64@npm:0.27.4" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/linux-x64@npm:0.25.8" - conditions: os=linux & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/netbsd-arm64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/netbsd-arm64@npm:0.25.8" +"@esbuild/netbsd-arm64@npm:0.27.4": + version: 0.27.4 + resolution: "@esbuild/netbsd-arm64@npm:0.27.4" conditions: os=netbsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/netbsd-x64@npm:0.21.5" +"@esbuild/netbsd-x64@npm:0.27.4": + version: 0.27.4 + resolution: "@esbuild/netbsd-x64@npm:0.27.4" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/netbsd-x64@npm:0.25.8" - conditions: os=netbsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/openbsd-arm64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/openbsd-arm64@npm:0.25.8" +"@esbuild/openbsd-arm64@npm:0.27.4": + version: 0.27.4 + resolution: "@esbuild/openbsd-arm64@npm:0.27.4" conditions: os=openbsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/openbsd-x64@npm:0.21.5" +"@esbuild/openbsd-x64@npm:0.27.4": + version: 0.27.4 + resolution: "@esbuild/openbsd-x64@npm:0.27.4" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/openbsd-x64@npm:0.25.8" - conditions: os=openbsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/openharmony-arm64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/openharmony-arm64@npm:0.25.8" +"@esbuild/openharmony-arm64@npm:0.27.4": + version: 0.27.4 + resolution: "@esbuild/openharmony-arm64@npm:0.27.4" conditions: os=openharmony & cpu=arm64 languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/sunos-x64@npm:0.21.5" +"@esbuild/sunos-x64@npm:0.27.4": + version: 0.27.4 + resolution: "@esbuild/sunos-x64@npm:0.27.4" conditions: os=sunos & cpu=x64 languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/sunos-x64@npm:0.25.8" - conditions: os=sunos & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/win32-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/win32-arm64@npm:0.21.5" +"@esbuild/win32-arm64@npm:0.27.4": + version: 0.27.4 + resolution: "@esbuild/win32-arm64@npm:0.27.4" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/win32-arm64@npm:0.25.8" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/win32-ia32@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/win32-ia32@npm:0.21.5" +"@esbuild/win32-ia32@npm:0.27.4": + version: 0.27.4 + resolution: "@esbuild/win32-ia32@npm:0.27.4" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/win32-ia32@npm:0.25.8" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"@esbuild/win32-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/win32-x64@npm:0.21.5" +"@esbuild/win32-x64@npm:0.27.4": + version: 0.27.4 + resolution: "@esbuild/win32-x64@npm:0.27.4" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.25.8": - version: 0.25.8 - resolution: "@esbuild/win32-x64@npm:0.25.8" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": - version: 4.4.0 - resolution: "@eslint-community/eslint-utils@npm:4.4.0" +"@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0, @eslint-community/eslint-utils@npm:^4.9.1": + version: 4.9.1 + resolution: "@eslint-community/eslint-utils@npm:4.9.1" dependencies: - eslint-visitor-keys: "npm:^3.3.0" + eslint-visitor-keys: "npm:^3.4.3" peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - checksum: 10/8d70bcdcd8cd279049183aca747d6c2ed7092a5cf0cf5916faac1ef37ffa74f0c245c2a3a3d3b9979d9dfdd4ca59257b4c5621db699d637b847a2c5e02f491c2 + checksum: 10/863b5467868551c9ae34d03eefe634633d08f623fc7b19d860f8f26eb6f303c1a5934253124163bee96181e45ed22bf27473dccc295937c3078493a4a8c9eddd languageName: node linkType: hard -"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.6.1": - version: 4.12.1 - resolution: "@eslint-community/regexpp@npm:4.12.1" - checksum: 10/c08f1dd7dd18fbb60bdd0d85820656d1374dd898af9be7f82cb00451313402a22d5e30569c150315b4385907cdbca78c22389b2a72ab78883b3173be317620cc +"@eslint-community/regexpp@npm:^4.12.2, @eslint-community/regexpp@npm:^4.6.1": + version: 4.12.2 + resolution: "@eslint-community/regexpp@npm:4.12.2" + checksum: 10/049b280fddf71dd325514e0a520024969431dc3a8b02fa77476e6820e9122f28ab4c9168c11821f91a27982d2453bcd7a66193356ea84e84fb7c8d793be1ba0c languageName: node linkType: hard @@ -9044,6 +9122,18 @@ __metadata: languageName: node linkType: hard +"@exodus/bytes@npm:^1.6.0": + version: 1.11.0 + resolution: "@exodus/bytes@npm:1.11.0" + peerDependencies: + "@noble/hashes": ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + "@noble/hashes": + optional: true + checksum: 10/a8c815fc15a6f16e05ae2da11b210c3bec7d75c7595478c4b8bb8f3a7bc7fde4b44dc9f160b15c9d7a5b064abd6f38731d7817c132c9da60a4164038601ff54f + languageName: node + linkType: hard + "@faker-js/faker@npm:5.5.3": version: 5.5.3 resolution: "@faker-js/faker@npm:5.5.3" @@ -9051,10 +9141,10 @@ __metadata: languageName: node linkType: hard -"@faker-js/faker@npm:^8.4.1": - version: 8.4.1 - resolution: "@faker-js/faker@npm:8.4.1" - checksum: 10/5983c2ea64f26055ad6648de748878e11ebe2fb751e3c7435ae141cdffabc2dccfe4c4f49da69a3d2add71e21b415c683ac5fba196fab0d5ed6779fbec436c80 +"@faker-js/faker@npm:^10.0.0": + version: 10.4.0 + resolution: "@faker-js/faker@npm:10.4.0" + checksum: 10/3eaaa501435584f8164cfdaac8cd666abe6186eaccb171592f559d942211f01cba60d00616307fbcf980cb8d19512b2b8c9e926e7472fc93df580d18483f8f17 languageName: node linkType: hard @@ -9065,6 +9155,13 @@ __metadata: languageName: node linkType: hard +"@fastify/busboy@npm:^3.1.1": + version: 3.2.0 + resolution: "@fastify/busboy@npm:3.2.0" + checksum: 10/7d42b23eed18b1aaf2d2b1c77a5b76ec3606d59f5ddec31c04b144536be57478ed0c73e04768f11535b11a37b48aaaa0aed4904d5f18391ff90045c258e41acc + languageName: node + linkType: hard + "@floating-ui/core@npm:^1.6.0": version: 1.6.8 resolution: "@floating-ui/core@npm:1.6.8" @@ -9103,54 +9200,12 @@ __metadata: languageName: node linkType: hard -"@formatjs/ecma402-abstract@npm:2.3.2": - version: 2.3.2 - resolution: "@formatjs/ecma402-abstract@npm:2.3.2" +"@gar/promise-retry@npm:^1.0.0": + version: 1.0.2 + resolution: "@gar/promise-retry@npm:1.0.2" dependencies: - "@formatjs/fast-memoize": "npm:2.2.6" - "@formatjs/intl-localematcher": "npm:0.5.10" - decimal.js: "npm:10" - tslib: "npm:2" - checksum: 10/db31d3d9b36033ea11ec905638ac0c1d2282f5bf53c9c06ee1d0ffd924f4bf64030702c92b56261756c4998dfa6235462689d8eda82d5913f2d7cf636a9416ae - languageName: node - linkType: hard - -"@formatjs/fast-memoize@npm:2.2.6": - version: 2.2.6 - resolution: "@formatjs/fast-memoize@npm:2.2.6" - dependencies: - tslib: "npm:2" - checksum: 10/efa5601dddbd94412ee567d5d067dfd206afa2d08553435f6938e69acba3309b83b9b15021cd30550d5fb93817a53b7691098a11a73f621c2d9318efad49fd76 - languageName: node - linkType: hard - -"@formatjs/icu-messageformat-parser@npm:2.9.8": - version: 2.9.8 - resolution: "@formatjs/icu-messageformat-parser@npm:2.9.8" - dependencies: - "@formatjs/ecma402-abstract": "npm:2.3.2" - "@formatjs/icu-skeleton-parser": "npm:1.8.12" - tslib: "npm:2" - checksum: 10/dbaf500676cb48ec4491b01d6688800e480f8f5c788ea658da06b6e56f83d54efbcafea1ca12ba83a5485a567c7ab393e36c04b59e6d194c0f6eba22aa44bc4c - languageName: node - linkType: hard - -"@formatjs/icu-skeleton-parser@npm:1.8.12": - version: 1.8.12 - resolution: "@formatjs/icu-skeleton-parser@npm:1.8.12" - dependencies: - "@formatjs/ecma402-abstract": "npm:2.3.2" - tslib: "npm:2" - checksum: 10/3026d45fc4edf1c1e934dfeec6c82bd60d6a9cf87416bb2d38a46d04956c835f456155295d138cceaaedd767bcff039f9041a1d45a55ec1ec2352f2d9db74fda - languageName: node - linkType: hard - -"@formatjs/intl-localematcher@npm:0.5.10": - version: 0.5.10 - resolution: "@formatjs/intl-localematcher@npm:0.5.10" - dependencies: - tslib: "npm:2" - checksum: 10/119e36974607d5d3586570db93358c1f316c0736b83acc81dce88468435a9519a9e58a5abe6ed3078ffe40d6b4ad4613c6371e2a39cd570c9b6f561372ffb14d + retry: "npm:^0.13.1" + checksum: 10/b91326999ce94677cbe91973079eabc689761a93a045f6a2d34d4070e9305b27f6c54e4021688c7080cb14caf89eafa0c0f300af741b94c20d18608bdb66ca46 languageName: node linkType: hard @@ -9161,16 +9216,16 @@ __metadata: languageName: node linkType: hard -"@gerrit0/mini-shiki@npm:^3.12.0": - version: 3.14.0 - resolution: "@gerrit0/mini-shiki@npm:3.14.0" +"@gerrit0/mini-shiki@npm:^3.23.0": + version: 3.23.0 + resolution: "@gerrit0/mini-shiki@npm:3.23.0" dependencies: - "@shikijs/engine-oniguruma": "npm:^3.14.0" - "@shikijs/langs": "npm:^3.14.0" - "@shikijs/themes": "npm:^3.14.0" - "@shikijs/types": "npm:^3.14.0" + "@shikijs/engine-oniguruma": "npm:^3.23.0" + "@shikijs/langs": "npm:^3.23.0" + "@shikijs/themes": "npm:^3.23.0" + "@shikijs/types": "npm:^3.23.0" "@shikijs/vscode-textmate": "npm:^10.0.2" - checksum: 10/339fd706f2bf30193e87fe25e4d2bb676c7fd85f0abddf859c055d9ffdbfc9c5a7cd113555e990be409fb115ee24fbcb335b17e74fa71903430b4e3a619379b1 + checksum: 10/d44dac7c3f58ba136285b3c15c8a89eba120f7a493dddcf6178601ab8c0a5286da6a19515100297934288c41ae225f21013a4c395fe2f656b58f812dd13ccc1e languageName: node linkType: hard @@ -9185,14 +9240,14 @@ __metadata: languageName: node linkType: hard -"@gitbeaker/core@npm:^41.3.0": - version: 41.3.0 - resolution: "@gitbeaker/core@npm:41.3.0" +"@gitbeaker/core@npm:^43.8.0": + version: 43.8.0 + resolution: "@gitbeaker/core@npm:43.8.0" dependencies: - "@gitbeaker/requester-utils": "npm:^41.3.0" - qs: "npm:^6.12.2" + "@gitbeaker/requester-utils": "npm:^43.8.0" + qs: "npm:^6.14.0" xcase: "npm:^2.0.1" - checksum: 10/db60bfedcd541bd9826f7292630109934a4c667c240e4e914cdb8034c7628a841ebef8701f3d8ed5ac7a64b67479ae7415049a45071b12e40787332653f0f99f + checksum: 10/1c935ffec246c854d7d543cc4ccb97271c322ea506c02ccf60774bffd42462c36cd3324d6876082da706d25e444878f76714746b1fbd77c202b0e3766ef5f4ae languageName: node linkType: hard @@ -9208,15 +9263,15 @@ __metadata: languageName: node linkType: hard -"@gitbeaker/requester-utils@npm:^41.2.0, @gitbeaker/requester-utils@npm:^41.3.0": - version: 41.3.0 - resolution: "@gitbeaker/requester-utils@npm:41.3.0" +"@gitbeaker/requester-utils@npm:^43.8.0": + version: 43.8.0 + resolution: "@gitbeaker/requester-utils@npm:43.8.0" dependencies: picomatch-browser: "npm:^2.2.6" - qs: "npm:^6.12.2" - rate-limiter-flexible: "npm:^4.0.1" + qs: "npm:^6.14.0" + rate-limiter-flexible: "npm:^8.0.1" xcase: "npm:^2.0.1" - checksum: 10/10e3d6a87368f7111493a86f17dc969f0e8abb1aae07ac577b204acc297efa1711bfb4d6ba1f8299273e2bc410a755bd329111943fc6337c573cdbb38ab5f899 + checksum: 10/529685994676068a18f4bf6329252c66a62c1ef160f6e9d4b3a916330189cc1628fb30c983625d38295cef2999438e14ecb611fcd8a484b7c479b6dcf29f7679 languageName: node linkType: hard @@ -9230,25 +9285,25 @@ __metadata: languageName: node linkType: hard -"@gitbeaker/rest@npm:^41.2.0": - version: 41.3.0 - resolution: "@gitbeaker/rest@npm:41.3.0" +"@gitbeaker/rest@npm:^43.8.0": + version: 43.8.0 + resolution: "@gitbeaker/rest@npm:43.8.0" dependencies: - "@gitbeaker/core": "npm:^41.3.0" - "@gitbeaker/requester-utils": "npm:^41.3.0" - checksum: 10/8f280919303e2a57f9cf7070cc3bb74966be73c24448ab6cdd59c7ca6ad9f79f34a1454fb47138fddf91cc743ba769ead42513e5efb06e2be9d40f1f92432cc7 + "@gitbeaker/core": "npm:^43.8.0" + "@gitbeaker/requester-utils": "npm:^43.8.0" + checksum: 10/a29493ee1e0e789a0fa4307108c616bb287f33a390262a57025461140a284ae1284918ff4d4e91d3351e61b050eea55ffe2ca7551ffcb6be29ae22c67136a463 languageName: node linkType: hard "@google-cloud/cloud-sql-connector@npm:^1.4.0": - version: 1.8.4 - resolution: "@google-cloud/cloud-sql-connector@npm:1.8.4" + version: 1.9.2 + resolution: "@google-cloud/cloud-sql-connector@npm:1.9.2" dependencies: - "@googleapis/sqladmin": "npm:^31.1.0" - gaxios: "npm:^7.1.2" - google-auth-library: "npm:^10.4.0" + "@googleapis/sqladmin": "npm:^35.2.0" + gaxios: "npm:^7.1.4" + google-auth-library: "npm:^10.6.1" p-throttle: "npm:^7.0.0" - checksum: 10/39d68ed3d6c65eff4af198711c723d6c82ec529fb6a2f7a5e2457f5b12731103796d27b9ce788b3326f710ffbb642147b1e4a5b7e83ff2109fcc119da7f601e9 + checksum: 10/975790e26126f8f14a64e5361b44362c02b8d6340da56de2fa6f6132c844199d274c356ac35e3fa36e13261aedd982a045e9e728e52e33382255f56b1fdae28d languageName: node linkType: hard @@ -9328,8 +9383,8 @@ __metadata: linkType: hard "@google-cloud/storage@npm:^7.0.0": - version: 7.17.3 - resolution: "@google-cloud/storage@npm:7.17.3" + version: 7.19.0 + resolution: "@google-cloud/storage@npm:7.19.0" dependencies: "@google-cloud/paginator": "npm:^5.0.0" "@google-cloud/projectify": "npm:^4.0.0" @@ -9337,7 +9392,7 @@ __metadata: abort-controller: "npm:^3.0.0" async-retry: "npm:^1.3.3" duplexify: "npm:^4.1.3" - fast-xml-parser: "npm:^4.4.1" + fast-xml-parser: "npm:^5.3.4" gaxios: "npm:^6.0.2" google-auth-library: "npm:^9.6.3" html-entities: "npm:^2.5.2" @@ -9346,24 +9401,24 @@ __metadata: retry-request: "npm:^7.0.0" teeny-request: "npm:^9.0.0" uuid: "npm:^8.0.0" - checksum: 10/e38069a08541757781d7f5ae3f7633e78b4e9b5be1937a7f8f3a4df09c0f6b6b2746ade419ec0e4ffe751ddcc6eb9069cadfc14bbc869edcc4cbc0b1bf6adb4a + checksum: 10/6fa0621ff702f3e8e8e4edd25c1f7582b5f2a0b44866ad0b94cd3f074ff58b9164d1c470222bf9c29fcf54c27fabb4ff6706ee073a706c6a67673a8e19b17c30 languageName: node linkType: hard -"@googleapis/sqladmin@npm:^31.1.0": - version: 31.1.0 - resolution: "@googleapis/sqladmin@npm:31.1.0" +"@googleapis/sqladmin@npm:^35.2.0": + version: 35.2.0 + resolution: "@googleapis/sqladmin@npm:35.2.0" dependencies: - googleapis-common: "npm:^8.0.2-rc.0" - checksum: 10/720365e99bff8b7de161dc629ffb097624b98a3ff42febca47d9a9c73480b66faff39f3601614dce3178b3acf9d49c6e006633e1847cceaa7d21bc8437fa1936 + googleapis-common: "npm:^8.0.0" + checksum: 10/f5931b91626a29173732381ffa7ec3c674397d3192b30da8f4b3639fef3cd88bfa3b21f7e5aa084cab1d4f90bef6860617d2d1f68e1c3692fa1c5835fc89c25a languageName: node linkType: hard -"@graphiql/react@npm:^0.20.3": - version: 0.20.3 - resolution: "@graphiql/react@npm:0.20.3" +"@graphiql/react@npm:0.29.0, @graphiql/react@npm:^0.29.0": + version: 0.29.0 + resolution: "@graphiql/react@npm:0.29.0" dependencies: - "@graphiql/toolkit": "npm:^0.9.1" + "@graphiql/toolkit": "npm:^0.11.2" "@headlessui/react": "npm:^1.7.15" "@radix-ui/react-dialog": "npm:^1.0.4" "@radix-ui/react-dropdown-menu": "npm:^2.0.5" @@ -9372,209 +9427,210 @@ __metadata: "@types/codemirror": "npm:^5.60.8" clsx: "npm:^1.2.1" codemirror: "npm:^5.65.3" - codemirror-graphql: "npm:^2.0.10" + codemirror-graphql: "npm:^2.2.1" copy-to-clipboard: "npm:^3.2.0" framer-motion: "npm:^6.5.1" - graphql-language-service: "npm:^5.2.0" - markdown-it: "npm:^12.2.0" - set-value: "npm:^4.1.0" - peerDependencies: - graphql: ^15.5.0 || ^16.0.0 - react: ^16.8.0 || ^17 || ^18 - react-dom: ^16.8.0 || ^17 || ^18 - checksum: 10/cc9bda25eaf977544c2ab9db0616d80e1e448239063c4a84aab4234de192b5fe6630a0ac2614c9f1c79ae4f42612b52ba1523cd91c98f6f21e1e01936e1fb819 - languageName: node - linkType: hard - -"@graphiql/react@npm:^0.23.0": - version: 0.23.0 - resolution: "@graphiql/react@npm:0.23.0" - dependencies: - "@graphiql/toolkit": "npm:^0.9.2" - "@headlessui/react": "npm:^1.7.15" - "@radix-ui/react-dialog": "npm:^1.0.4" - "@radix-ui/react-dropdown-menu": "npm:^2.0.5" - "@radix-ui/react-tooltip": "npm:^1.0.6" - "@radix-ui/react-visually-hidden": "npm:^1.0.3" - "@types/codemirror": "npm:^5.60.8" - clsx: "npm:^1.2.1" - codemirror: "npm:^5.65.3" - codemirror-graphql: "npm:^2.0.13" - copy-to-clipboard: "npm:^3.2.0" - framer-motion: "npm:^6.5.1" - graphql-language-service: "npm:^5.2.2" + get-value: "npm:^3.0.1" + graphql-language-service: "npm:^5.3.1" markdown-it: "npm:^14.1.0" + react-compiler-runtime: "npm:19.1.0-rc.1" set-value: "npm:^4.1.0" peerDependencies: - graphql: ^15.5.0 || ^16.0.0 + graphql: ^15.5.0 || ^16.0.0 || ^17.0.0 react: ^16.8.0 || ^17 || ^18 react-dom: ^16.8.0 || ^17 || ^18 - checksum: 10/d3e10e5e61cdc766b4b2c3597c26595c234974383e0942112174d5782bd5da3ac1be8091eea5dc7a91f475ba84f7f559f6eb36ebca87abb2d134f2e8f06a0356 + checksum: 10/c906bf3175ae042aaa035872015ea5d60991dba633e2e44f707faee9dad9f3b2b6896ca6fcef7f54e65a1b707db9a44538137e5e7037e132d0f8a456df641c49 languageName: node linkType: hard -"@graphiql/toolkit@npm:^0.9.1, @graphiql/toolkit@npm:^0.9.2": - version: 0.9.2 - resolution: "@graphiql/toolkit@npm:0.9.2" +"@graphiql/toolkit@npm:^0.11.2": + version: 0.11.3 + resolution: "@graphiql/toolkit@npm:0.11.3" dependencies: "@n1ru4l/push-pull-async-iterable-iterator": "npm:^3.1.0" meros: "npm:^1.1.4" peerDependencies: - graphql: ^15.5.0 || ^16.0.0 + graphql: ^15.5.0 || ^16.0.0 || ^17.0.0 graphql-ws: ">= 4.5.0" peerDependenciesMeta: graphql-ws: optional: true - checksum: 10/cecf99652acbe3f263a08c2d895a6de99065308428458b051cb8d09e8303af24d5c62d9d6be3cc2ffa88b81f49a825696be8bd019eacb5693217c0018f812de1 + checksum: 10/a28d5261900bff00ca550963f9b0fba035e63b87dbed5c4c1fd1dd5a8963d2c614a9161af58fd4782027b86874be2403cb3e86514567e25dce171ba9823ddc62 languageName: node linkType: hard -"@graphql-tools/batch-execute@npm:^9.0.1": - version: 9.0.2 - resolution: "@graphql-tools/batch-execute@npm:9.0.2" +"@graphql-hive/signal@npm:^2.0.0": + version: 2.0.0 + resolution: "@graphql-hive/signal@npm:2.0.0" + checksum: 10/51b1b3527300e9d67366ead3c505a1abeabbf4cf764b125c21c11bb163201b2432e4c1af49ff94b13737c30e62c84a43558fd3c4f8b9b9d9e530be08a2a6f5fc + languageName: node + linkType: hard + +"@graphql-tools/batch-execute@npm:^10.0.5": + version: 10.0.5 + resolution: "@graphql-tools/batch-execute@npm:10.0.5" dependencies: - "@graphql-tools/utils": "npm:^10.0.5" - dataloader: "npm:^2.2.2" - tslib: "npm:^2.4.0" - value-or-promise: "npm:^1.0.12" + "@graphql-tools/utils": "npm:^11.0.0" + "@whatwg-node/promise-helpers": "npm:^1.3.2" + dataloader: "npm:^2.2.3" + tslib: "npm:^2.8.1" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/ec0be4f8790c6041b4d8e796b662ce3f6ffde304ee780bf91db1912da49bed096db0e7a3b62a797920997e38d4489cd7e2ed4ee26d605a3c343ee51309ebb736 + checksum: 10/4384b095b636c4ef0cb32f4145856aa1378e92e4b892fd4bbc9ce612626806b89112482126f0fe1d89175e9954d786ec3b446a023996c4fa0c0de732735eabe0 languageName: node linkType: hard -"@graphql-tools/delegate@npm:^10.0.0, @graphql-tools/delegate@npm:^10.0.3": - version: 10.0.3 - resolution: "@graphql-tools/delegate@npm:10.0.3" +"@graphql-tools/delegate@npm:^12.0.8": + version: 12.0.8 + resolution: "@graphql-tools/delegate@npm:12.0.8" dependencies: - "@graphql-tools/batch-execute": "npm:^9.0.1" - "@graphql-tools/executor": "npm:^1.0.0" - "@graphql-tools/schema": "npm:^10.0.0" - "@graphql-tools/utils": "npm:^10.0.5" - dataloader: "npm:^2.2.2" - tslib: "npm:^2.5.0" + "@graphql-tools/batch-execute": "npm:^10.0.5" + "@graphql-tools/executor": "npm:^1.4.13" + "@graphql-tools/schema": "npm:^10.0.29" + "@graphql-tools/utils": "npm:^11.0.0" + "@repeaterjs/repeater": "npm:^3.0.6" + "@whatwg-node/promise-helpers": "npm:^1.3.2" + dataloader: "npm:^2.2.3" + tslib: "npm:^2.8.1" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/d19e15d4bdfd1878c3f18ec6f325e62872765ef5bbbf59f074e898f8967600ae7a39b75284d69c67f1de73bcfe3a5fdb63c6a130cb1680b6277c8794c9e27ab7 + checksum: 10/c747ef3cc14141a67f275e21f8ddcbd924972670c2b50b16938ef3f3014f42aee1340973428515e62d063282083b88b8a5ff3ff9a9f459558f4f5184a5ad342c languageName: node linkType: hard -"@graphql-tools/executor-graphql-ws@npm:^1.0.0": - version: 1.1.1 - resolution: "@graphql-tools/executor-graphql-ws@npm:1.1.1" +"@graphql-tools/executor-common@npm:^1.0.6": + version: 1.0.6 + resolution: "@graphql-tools/executor-common@npm:1.0.6" dependencies: - "@graphql-tools/utils": "npm:^10.0.2" - "@types/ws": "npm:^8.0.0" - graphql-ws: "npm:^5.14.0" - isomorphic-ws: "npm:^5.0.0" - tslib: "npm:^2.4.0" - ws: "npm:^8.13.0" + "@envelop/core": "npm:^5.4.0" + "@graphql-tools/utils": "npm:^11.0.0" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/30d29e2ef8fbedf07d7c279218f31a7279e714328f6c24d28ea76536fb4c5ed857ab5e486922000fcf9f85b83a9f3e995b8fd066b01ea4ab31d35efaa770c133 + checksum: 10/9daf04a3d1d139d27ed8957a7166aba01a9df6c9d0a459229493c69415726afa1901e234b1bb946144a4794ba67fc4740dd791eddee1117a9c5af1d0c4423007 languageName: node linkType: hard -"@graphql-tools/executor-http@npm:^1.0.0": - version: 1.0.3 - resolution: "@graphql-tools/executor-http@npm:1.0.3" +"@graphql-tools/executor-graphql-ws@npm:^3.1.4": + version: 3.1.4 + resolution: "@graphql-tools/executor-graphql-ws@npm:3.1.4" dependencies: - "@graphql-tools/utils": "npm:^10.0.2" + "@graphql-tools/executor-common": "npm:^1.0.6" + "@graphql-tools/utils": "npm:^11.0.0" + "@whatwg-node/disposablestack": "npm:^0.0.6" + graphql-ws: "npm:^6.0.6" + isows: "npm:^1.0.7" + tslib: "npm:^2.8.1" + ws: "npm:^8.18.3" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/c5b1576e4ceb928e8d47b2c9a25585fb575dcc76fbe1cabb0e743b89361367a10858e84daddb5f742c19ae44bd69a53c8d96809bc38a8147fab18bbc66b4a067 + languageName: node + linkType: hard + +"@graphql-tools/executor-http@npm:^3.1.0": + version: 3.1.0 + resolution: "@graphql-tools/executor-http@npm:3.1.0" + dependencies: + "@graphql-hive/signal": "npm:^2.0.0" + "@graphql-tools/executor-common": "npm:^1.0.6" + "@graphql-tools/utils": "npm:^11.0.0" "@repeaterjs/repeater": "npm:^3.0.4" - "@whatwg-node/fetch": "npm:^0.9.0" - extract-files: "npm:^11.0.0" - meros: "npm:^1.2.1" - tslib: "npm:^2.4.0" - value-or-promise: "npm:^1.0.12" + "@whatwg-node/disposablestack": "npm:^0.0.6" + "@whatwg-node/fetch": "npm:^0.10.13" + "@whatwg-node/promise-helpers": "npm:^1.3.2" + meros: "npm:^1.3.2" + tslib: "npm:^2.8.1" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/0fda267df3ebe3afd44cb86e779fcab17ab9d8045670b15ceb1b06ab10a576686beb0aa786781dae29e5bbe6ab04259e73e0fe5de2a45853fda6a19f122a12f5 + checksum: 10/6e288c0c0d57ebc217860a085e98361a527997b6a6f2d96d4d4881ae9951e94720c065d34c7ed10b6ec9ea5b632fcd32e015f1b12349e7720ae15874baf678e1 languageName: node linkType: hard -"@graphql-tools/executor-legacy-ws@npm:^1.0.0": - version: 1.1.0 - resolution: "@graphql-tools/executor-legacy-ws@npm:1.1.0" +"@graphql-tools/executor-legacy-ws@npm:^1.1.25": + version: 1.1.25 + resolution: "@graphql-tools/executor-legacy-ws@npm:1.1.25" dependencies: - "@graphql-tools/utils": "npm:^10.3.0" + "@graphql-tools/utils": "npm:^11.0.0" "@types/ws": "npm:^8.0.0" isomorphic-ws: "npm:^5.0.0" tslib: "npm:^2.4.0" - ws: "npm:^8.17.1" + ws: "npm:^8.19.0" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/b7bfe6c1e2229beef35fb175102d0a5ab45e679834c45d579c3d0efe8b7d1e22836d3773e03b0c920bbc94a58d490d54d62f1484e043eef6a54cc426b4b92f98 + checksum: 10/3ebcd004e6583c3f2e078e3caee7a51b707d125c0993f8a4529aa6e2a6b1aaad46b8eee16e14fde135dbaa1ec3783bc4257a1fd9466673a37e7e66a05a7fbf20 languageName: node linkType: hard -"@graphql-tools/executor@npm:^1.0.0": - version: 1.2.0 - resolution: "@graphql-tools/executor@npm:1.2.0" +"@graphql-tools/executor@npm:^1.4.13": + version: 1.5.1 + resolution: "@graphql-tools/executor@npm:1.5.1" dependencies: - "@graphql-tools/utils": "npm:^10.0.0" - "@graphql-typed-document-node/core": "npm:3.2.0" + "@graphql-tools/utils": "npm:^11.0.0" + "@graphql-typed-document-node/core": "npm:^3.2.0" "@repeaterjs/repeater": "npm:^3.0.4" + "@whatwg-node/disposablestack": "npm:^0.0.6" + "@whatwg-node/promise-helpers": "npm:^1.0.0" tslib: "npm:^2.4.0" - value-or-promise: "npm:^1.0.12" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/a13c9fb2b1e15661c469a3f048f19f96191e802842e48a1ba058a2422c30f3de64b5089f57f81ef38d49ed2261f7e72f5e34834cc322188c33306d2ae711a00d + checksum: 10/b73e4ba7b19c2ebf0d5943006626388575fa023400c2b17e96e5fe0b321dabbbc116fcf2e951a3b05398dcd078805e28d12821ddce89c088bb52c6a38c2dd2f7 languageName: node linkType: hard "@graphql-tools/graphql-file-loader@npm:^8.0.0": - version: 8.0.0 - resolution: "@graphql-tools/graphql-file-loader@npm:8.0.0" + version: 8.1.10 + resolution: "@graphql-tools/graphql-file-loader@npm:8.1.10" dependencies: - "@graphql-tools/import": "npm:7.0.0" - "@graphql-tools/utils": "npm:^10.0.0" + "@graphql-tools/import": "npm:7.1.10" + "@graphql-tools/utils": "npm:^11.0.0" globby: "npm:^11.0.3" tslib: "npm:^2.4.0" unixify: "npm:^1.0.0" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/bf1248593123f6aa740da8b58746e2a60f5a1f413da1dcff8890daae0f2eeeac1837a2d419bdbdfb6ccb2877e03103d335ae0d1696e392f6af247414b0ad8406 + checksum: 10/200d48bed7c3a3320f4193e3258e5d9bdacd578e0db87d914d0a9f16b7b8884e5089975382d6c1d6e6244ddef42c53528fee0325f865bcf63dbd5269b25e4cae languageName: node linkType: hard -"@graphql-tools/import@npm:7.0.0": - version: 7.0.0 - resolution: "@graphql-tools/import@npm:7.0.0" +"@graphql-tools/import@npm:7.1.10": + version: 7.1.10 + resolution: "@graphql-tools/import@npm:7.1.10" dependencies: - "@graphql-tools/utils": "npm:^10.0.0" + "@graphql-tools/utils": "npm:^11.0.0" + "@theguild/federation-composition": "npm:^0.21.3" resolve-from: "npm:5.0.0" tslib: "npm:^2.4.0" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/74741f670fb028526c363cd83871eeb9a1f51ecae27d1640914b0d5ddc482dc0a74d96b996244c726a12e80f63a4f8ec15fc71098e3b87ed3c463fa06ce8ac6c + checksum: 10/4a97c7740395ca398cf537d12b3f6a1b1784cda2d7dcc42f4f3c98a9ee6d965125cc2e500add2f9e25392444896719d16f9b05f0572f2e0c54ef8f218b86f038 languageName: node linkType: hard "@graphql-tools/json-file-loader@npm:^8.0.0": - version: 8.0.0 - resolution: "@graphql-tools/json-file-loader@npm:8.0.0" + version: 8.0.26 + resolution: "@graphql-tools/json-file-loader@npm:8.0.26" dependencies: - "@graphql-tools/utils": "npm:^10.0.0" + "@graphql-tools/utils": "npm:^11.0.0" globby: "npm:^11.0.3" tslib: "npm:^2.4.0" unixify: "npm:^1.0.0" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/a023466e261599803d1f8e1af3bb7b0007a5206c29df4fb14a448c1dacc04807482b97374c2bbb82bd286523f6a032c355d74f39bffb866325651f1a0f0412a2 + checksum: 10/6e4dc44a9aa3cdd3f7b958b50d98a26e56fb5cc9fab2667011ec12c82685016f269350d6ba2e45dcc7e6d71f8df7d1c4002edb55ce8896ba3e705cad14645ac1 languageName: node linkType: hard "@graphql-tools/load@npm:^8.1.0": - version: 8.1.0 - resolution: "@graphql-tools/load@npm:8.1.0" + version: 8.1.8 + resolution: "@graphql-tools/load@npm:8.1.8" dependencies: - "@graphql-tools/schema": "npm:^10.0.23" - "@graphql-tools/utils": "npm:^10.8.6" + "@graphql-tools/schema": "npm:^10.0.31" + "@graphql-tools/utils": "npm:^11.0.0" p-limit: "npm:3.1.0" tslib: "npm:^2.4.0" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/4601dda7eb32cb8afed2379102ad82f8a948e478f42c7b1f354a3468ca8dfcdcc2a89e6c6ebcbb574c77eaa80d47f20c27230bdcd6c2d0a3600fa1d6a450cc95 + checksum: 10/a9b24c8d9fc52ebf2b5e0d5dc99212e61704cfe0a07b17a5be3329c391ae01f710e0a2ca6b73b41379e9bee55210c0466d5dfb378a9e3cbe051062a69a07d616 languageName: node linkType: hard @@ -9590,28 +9646,28 @@ __metadata: languageName: node linkType: hard -"@graphql-tools/merge@npm:^9.0.0, @graphql-tools/merge@npm:^9.0.24": - version: 9.0.24 - resolution: "@graphql-tools/merge@npm:9.0.24" +"@graphql-tools/merge@npm:^9.0.0, @graphql-tools/merge@npm:^9.1.7": + version: 9.1.7 + resolution: "@graphql-tools/merge@npm:9.1.7" dependencies: - "@graphql-tools/utils": "npm:^10.8.6" + "@graphql-tools/utils": "npm:^11.0.0" tslib: "npm:^2.4.0" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/95f77ff141f10d5d726cd8d1ae1ad84ed944c84346bf20461adca9b1543bb94cb524b0347885fe61d3158ccf5ffe1dddec361787ae40bfcc3449aad51528dd77 + checksum: 10/e0b77dfc16e91d7c2450df0b57a85a93e11f0f67e37e396bcf04275d1db8ed1b7257c763ebe6e7f122041d81f00d6aa954fbec531fa6c0b449d195a9aff199cc languageName: node linkType: hard -"@graphql-tools/schema@npm:^10.0.0, @graphql-tools/schema@npm:^10.0.23": - version: 10.0.23 - resolution: "@graphql-tools/schema@npm:10.0.23" +"@graphql-tools/schema@npm:^10.0.29, @graphql-tools/schema@npm:^10.0.31": + version: 10.0.31 + resolution: "@graphql-tools/schema@npm:10.0.31" dependencies: - "@graphql-tools/merge": "npm:^9.0.24" - "@graphql-tools/utils": "npm:^10.8.6" + "@graphql-tools/merge": "npm:^9.1.7" + "@graphql-tools/utils": "npm:^11.0.0" tslib: "npm:^2.4.0" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/f0960dae161a478941276df1802af09844825c8135e4695b36f5f7e7384f43ff8e1288a67546023fc861951d783327f239112ccf563cb4be1f22038fc78acf21 + checksum: 10/5b775736f8b8454319e07cadc7d41bc5c9cc804a393490aaffd1bb7f59afddb02b498837e870bf98db4a1a989a721d5d8e2fd2b97409d078bac14503c2d4f9cb languageName: node linkType: hard @@ -9629,26 +9685,25 @@ __metadata: languageName: node linkType: hard -"@graphql-tools/url-loader@npm:^8.0.0": - version: 8.0.0 - resolution: "@graphql-tools/url-loader@npm:8.0.0" +"@graphql-tools/url-loader@npm:^9.0.0": + version: 9.0.7 + resolution: "@graphql-tools/url-loader@npm:9.0.7" dependencies: - "@ardatan/sync-fetch": "npm:^0.0.1" - "@graphql-tools/delegate": "npm:^10.0.0" - "@graphql-tools/executor-graphql-ws": "npm:^1.0.0" - "@graphql-tools/executor-http": "npm:^1.0.0" - "@graphql-tools/executor-legacy-ws": "npm:^1.0.0" - "@graphql-tools/utils": "npm:^10.0.0" - "@graphql-tools/wrap": "npm:^10.0.0" + "@graphql-tools/executor-graphql-ws": "npm:^3.1.4" + "@graphql-tools/executor-http": "npm:^3.1.0" + "@graphql-tools/executor-legacy-ws": "npm:^1.1.25" + "@graphql-tools/utils": "npm:^11.0.0" + "@graphql-tools/wrap": "npm:^11.1.1" "@types/ws": "npm:^8.0.0" - "@whatwg-node/fetch": "npm:^0.9.0" + "@whatwg-node/fetch": "npm:^0.10.13" + "@whatwg-node/promise-helpers": "npm:^1.0.0" isomorphic-ws: "npm:^5.0.0" + sync-fetch: "npm:0.6.0" tslib: "npm:^2.4.0" - value-or-promise: "npm:^1.0.11" - ws: "npm:^8.12.0" + ws: "npm:^8.19.0" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/206065c2490e0747f6f9d756171b151017f9e5ad2d5f4c82c1644af8da3bf03e0075e4c55e6317e1823e74e32d307af5dd102f58851c7c361022578aa52ca8c1 + checksum: 10/292a43974e471a556697aa3ac170d9da7e6e3cc16e9462933f634aa8983cf5752805ba0c580bff1dbd7891168fe5e238b22c02782f88bee5923fa640e451e92e languageName: node linkType: hard @@ -9663,18 +9718,17 @@ __metadata: languageName: node linkType: hard -"@graphql-tools/utils@npm:^10.0.0, @graphql-tools/utils@npm:^10.0.2, @graphql-tools/utils@npm:^10.0.5, @graphql-tools/utils@npm:^10.3.0, @graphql-tools/utils@npm:^10.8.6": - version: 10.8.6 - resolution: "@graphql-tools/utils@npm:10.8.6" +"@graphql-tools/utils@npm:^11.0.0": + version: 11.0.0 + resolution: "@graphql-tools/utils@npm:11.0.0" dependencies: "@graphql-typed-document-node/core": "npm:^3.1.1" "@whatwg-node/promise-helpers": "npm:^1.0.0" cross-inspect: "npm:1.0.1" - dset: "npm:^3.1.4" tslib: "npm:^2.4.0" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/98329aef966b489d3674eb086b784f6fb4500afaf9bc46fbe6a14ca32e98fec480c7395d3488c5eb2f450b75a538e98edf0527ed4bf24af352230e850c914389 + checksum: 10/4cc7577ab85d60908a1d5d448071b318791b798f571cd4b8e4289e0e0eeae9d7183b661a1a7d5da3cedaf5f9b62b936031e3a90d2e17a1c50acbd95d9106ba3c languageName: node linkType: hard @@ -9689,22 +9743,22 @@ __metadata: languageName: node linkType: hard -"@graphql-tools/wrap@npm:^10.0.0": - version: 10.0.1 - resolution: "@graphql-tools/wrap@npm:10.0.1" +"@graphql-tools/wrap@npm:^11.1.1": + version: 11.1.8 + resolution: "@graphql-tools/wrap@npm:11.1.8" dependencies: - "@graphql-tools/delegate": "npm:^10.0.3" - "@graphql-tools/schema": "npm:^10.0.0" - "@graphql-tools/utils": "npm:^10.0.0" - tslib: "npm:^2.4.0" - value-or-promise: "npm:^1.0.12" + "@graphql-tools/delegate": "npm:^12.0.8" + "@graphql-tools/schema": "npm:^10.0.29" + "@graphql-tools/utils": "npm:^11.0.0" + "@whatwg-node/promise-helpers": "npm:^1.3.2" + tslib: "npm:^2.8.1" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/0ccbb72a6d7bd67c10c86d4cc6da575b84ec2ba6488044dc7dfd6a046f09613d532f0af5549d481f2bfaf2f48c56cb087ea97021ebfe5e104531f80ec72e42a5 + checksum: 10/3ed15d0c92e5deed954a7e117b307af18ef8926132d356465938bbc66669da186aef1cd29cf72135d00ec86a3a149e216f83eaee83eb1fd2a0684a1ba1df473d languageName: node linkType: hard -"@graphql-typed-document-node/core@npm:3.2.0, @graphql-typed-document-node/core@npm:^3.1.1": +"@graphql-typed-document-node/core@npm:^3.1.1, @graphql-typed-document-node/core@npm:^3.2.0": version: 3.2.0 resolution: "@graphql-typed-document-node/core@npm:3.2.0" peerDependencies: @@ -9713,13 +9767,13 @@ __metadata: languageName: node linkType: hard -"@grpc/grpc-js@npm:^1.10.9, @grpc/grpc-js@npm:^1.11.1, @grpc/grpc-js@npm:^1.7.1": - version: 1.12.5 - resolution: "@grpc/grpc-js@npm:1.12.5" +"@grpc/grpc-js@npm:^1.10.9, @grpc/grpc-js@npm:^1.11.1, @grpc/grpc-js@npm:^1.14.3": + version: 1.14.3 + resolution: "@grpc/grpc-js@npm:1.14.3" dependencies: - "@grpc/proto-loader": "npm:^0.7.13" + "@grpc/proto-loader": "npm:^0.8.0" "@js-sdsl/ordered-map": "npm:^4.4.2" - checksum: 10/4f8ead236dcab4d94e15e62d65ad2d93732d37f5cc52ffafe67ae00f69eae4a4c97d6d34a1b9eac9f30206468f2d15302ea6649afcba1d38929afa9d1e7c12d5 + checksum: 10/bb9bfe2f749179ae5ac7774d30486dfa2e0b004518c28de158b248e0f6f65f40138f01635c48266fa540670220f850216726e3724e1eb29d078817581c96e4db languageName: node linkType: hard @@ -9737,6 +9791,20 @@ __metadata: languageName: node linkType: hard +"@grpc/proto-loader@npm:^0.8.0": + version: 0.8.0 + resolution: "@grpc/proto-loader@npm:0.8.0" + dependencies: + lodash.camelcase: "npm:^4.3.0" + long: "npm:^5.0.0" + protobufjs: "npm:^7.5.3" + yargs: "npm:^17.7.2" + bin: + proto-loader-gen-types: build/bin/proto-loader-gen-types.js + checksum: 10/216813bdca52cd3a84ac355ad93c2c3f54252be47327692fe666fd85baa5b1d50aa681ebc5626ab08926564fb2deae3b2ea435aa5bd883197650bbe56f2ae108 + languageName: node + linkType: hard + "@headlessui/react@npm:^1.7.15": version: 1.7.17 resolution: "@headlessui/react@npm:1.7.17" @@ -9749,6 +9817,15 @@ __metadata: languageName: node linkType: hard +"@hono/node-server@npm:^1.19.9": + version: 1.19.10 + resolution: "@hono/node-server@npm:1.19.10" + peerDependencies: + hono: ^4 + checksum: 10/3f4a386bf51d2eb0224522e7485052f8ca6485e9f59fb46cef8fe9578c0d2f35ae26900ed69b6c368250e79944239c70934912f85b833045b14ef19be557aa13 + languageName: node + linkType: hard + "@httptoolkit/httpolyglot@npm:^2.0.1, @httptoolkit/httpolyglot@npm:^2.2.1": version: 2.2.1 resolution: "@httptoolkit/httpolyglot@npm:2.2.1" @@ -9865,18 +9942,40 @@ __metadata: languageName: node linkType: hard -"@inquirer/external-editor@npm:^1.0.0": - version: 1.0.2 - resolution: "@inquirer/external-editor@npm:1.0.2" +"@inquirer/core@npm:^6.0.0": + version: 6.0.0 + resolution: "@inquirer/core@npm:6.0.0" dependencies: - chardet: "npm:^2.1.0" + "@inquirer/type": "npm:^1.1.6" + "@types/mute-stream": "npm:^0.0.4" + "@types/node": "npm:^20.10.7" + "@types/wrap-ansi": "npm:^3.0.0" + ansi-escapes: "npm:^4.3.2" + chalk: "npm:^4.1.2" + cli-spinners: "npm:^2.9.2" + cli-width: "npm:^4.1.0" + figures: "npm:^3.2.0" + mute-stream: "npm:^1.0.0" + run-async: "npm:^3.0.0" + signal-exit: "npm:^4.1.0" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^6.2.0" + checksum: 10/a9f79fe538deab439afc845e16fa8832872b4562be6e39a5de8b50eca3e2b27be0e470fc4ee014f202a750213adc8a06068402d51d6d7b34b118b12b08200f85 + languageName: node + linkType: hard + +"@inquirer/external-editor@npm:^1.0.0, @inquirer/external-editor@npm:^1.0.2": + version: 1.0.3 + resolution: "@inquirer/external-editor@npm:1.0.3" + dependencies: + chardet: "npm:^2.1.1" iconv-lite: "npm:^0.7.0" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/d0c5c73249b8153f4cf872c4fba01c57a7653142a4cad496f17ed03ef3769330a4b3c519b68d70af69d4bb33003d2599b66b2242be85411c0b027ff383619666 + checksum: 10/c95d7237a885b32031715089f92820525731d4d3c2bd7afdb826307dc296cc2b39e7a644b0bb265441963348cca42e7785feb29c3aaf18fd2b63131769bf6587 languageName: node linkType: hard @@ -9887,6 +9986,28 @@ __metadata: languageName: node linkType: hard +"@inquirer/select@npm:1.3.3": + version: 1.3.3 + resolution: "@inquirer/select@npm:1.3.3" + dependencies: + "@inquirer/core": "npm:^6.0.0" + "@inquirer/type": "npm:^1.1.6" + ansi-escapes: "npm:^4.3.2" + chalk: "npm:^4.1.2" + figures: "npm:^3.2.0" + checksum: 10/0f33c51ab69c156b96092bfb107d08dd0f4227274917b9406aa23877e3ba94fd6738800fb973c44c051aaebdba72d07dc328df4b76e6e1285f68aa01a7ed0ed8 + languageName: node + linkType: hard + +"@inquirer/type@npm:^1.1.6": + version: 1.5.5 + resolution: "@inquirer/type@npm:1.5.5" + dependencies: + mute-stream: "npm:^1.0.0" + checksum: 10/bd3f3d7510785af4ad599e042e99e4be6380f52f79f3db140fe6fed0a605acf27b1a0a20fb5cc688eaf7b8aa0c36dacb1d89c7bba4586f38cbf58ba9f159e7b5 + languageName: node + linkType: hard + "@inquirer/type@npm:^3.0.0": version: 3.0.0 resolution: "@inquirer/type@npm:3.0.0" @@ -9896,11 +10017,29 @@ __metadata: languageName: node linkType: hard +"@internal/backend@workspace:packages/backend-internal": + version: 0.0.0-use.local + resolution: "@internal/backend@workspace:packages/backend-internal" + dependencies: + "@backstage/cli": "workspace:^" + languageName: unknown + linkType: soft + +"@internal/cli@workspace:packages/cli-internal": + version: 0.0.0-use.local + resolution: "@internal/cli@workspace:packages/cli-internal" + dependencies: + "@backstage/cli": "workspace:^" + "@backstage/cli-node": "workspace:^" + languageName: unknown + linkType: soft + "@internal/frontend@workspace:packages/frontend-internal": version: 0.0.0-use.local resolution: "@internal/frontend@workspace:packages/frontend-internal" dependencies: "@backstage/cli": "workspace:^" + "@backstage/filter-predicates": "workspace:^" "@backstage/frontend-app-api": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" "@backstage/frontend-test-utils": "workspace:^" @@ -9909,7 +10048,6 @@ __metadata: "@backstage/version-bridge": "workspace:^" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" - zod: "npm:^3.22.4" languageName: unknown linkType: soft @@ -9934,7 +10072,7 @@ __metadata: "@backstage/errors": "workspace:^" "@types/express": "npm:^4.17.6" "@types/supertest": "npm:^2.0.8" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" supertest: "npm:^7.0.0" uuid: "npm:^11.0.0" @@ -9965,13 +10103,13 @@ __metadata: "@types/react": "npm:^18.0.0" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" react-use: "npm:^17.2.4" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true @@ -9985,44 +10123,34 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" "@backstage/plugin-scaffolder-react": "workspace:^" - zod: "npm:^3.22.4" + zod: "npm:^3.25.76 || ^4.0.0" languageName: unknown linkType: soft -"@internationalized/date@npm:^3.10.0": - version: 3.10.0 - resolution: "@internationalized/date@npm:3.10.0" +"@internationalized/date@npm:^3.12.1": + version: 3.12.1 + resolution: "@internationalized/date@npm:3.12.1" dependencies: "@swc/helpers": "npm:^0.5.0" - checksum: 10/5940667ea2b0be54b798088ef995f787508bf4305d3741babc8b1f48de008c872555a5440ae61fa7399461a7339d3257fb8e86cc2111e72e185fc37d99e06392 + checksum: 10/a8178a73e65cb86357008e39e589bf5899b47a4ebd6123d96b54e3b19aade31c136d8e5f9c48c4627110f26d857e15aa4be9e189e56386a4b26c616df4ea1795 languageName: node linkType: hard -"@internationalized/message@npm:^3.1.8": - version: 3.1.8 - resolution: "@internationalized/message@npm:3.1.8" +"@internationalized/number@npm:^3.6.6": + version: 3.6.6 + resolution: "@internationalized/number@npm:3.6.6" dependencies: "@swc/helpers": "npm:^0.5.0" - intl-messageformat: "npm:^10.1.0" - checksum: 10/8f27a31f5d1eef7084447ed141e896e12cc19d786a1346ba60137de5b8bcc58a9da978d79954e2a74302aa673f942fb772130ebd6195565e33db30bd7eb4ef47 + checksum: 10/7a7c8290a91bae3c1b22ab006c036b50f041162a383446360d0dd8194aa491a370057df1b2aa2cdfbccefd335cf6f4679e14608f5c24031b6852375654fa59df languageName: node linkType: hard -"@internationalized/number@npm:^3.6.5": - version: 3.6.5 - resolution: "@internationalized/number@npm:3.6.5" +"@internationalized/string@npm:^3.2.8": + version: 3.2.8 + resolution: "@internationalized/string@npm:3.2.8" dependencies: "@swc/helpers": "npm:^0.5.0" - checksum: 10/f80618842b9b8ea04e235277911eeb8e2ee129f1b266b94704a9f773df7536486c25db41ae708f6c2f4845cdb0a25e7d1856332370daba6c530528ac0ee997e3 - languageName: node - linkType: hard - -"@internationalized/string@npm:^3.2.7": - version: 3.2.7 - resolution: "@internationalized/string@npm:3.2.7" - dependencies: - "@swc/helpers": "npm:^0.5.0" - checksum: 10/38b54817cf125ba88d1136a6bca4fb57c46672d26d21490f838efe928049546800df6d9c8048411696455fc8caacb8ac23c2de2a1b61f2258b1302c1c97cc128 + checksum: 10/2054baf8b2d5f32c7904b5a584e724d00ae781b3efb22c113c18d6a604f700569faf006be28929032831972272693d7dd863d324550a7385068715e3a67b8a56 languageName: node linkType: hard @@ -10033,22 +10161,6 @@ __metadata: languageName: node linkType: hard -"@isaacs/balanced-match@npm:^4.0.1": - version: 4.0.1 - resolution: "@isaacs/balanced-match@npm:4.0.1" - checksum: 10/102fbc6d2c0d5edf8f6dbf2b3feb21695a21bc850f11bc47c4f06aa83bd8884fde3fe9d6d797d619901d96865fdcb4569ac2a54c937992c48885c5e3d9967fe8 - languageName: node - linkType: hard - -"@isaacs/brace-expansion@npm:^5.0.0": - version: 5.0.0 - resolution: "@isaacs/brace-expansion@npm:5.0.0" - dependencies: - "@isaacs/balanced-match": "npm:^4.0.1" - checksum: 10/cf3b7f206aff12128214a1df764ac8cdbc517c110db85249b945282407e3dfc5c6e66286383a7c9391a059fc8e6e6a8ca82262fc9d2590bd615376141fbebd2d - languageName: node - linkType: hard - "@isaacs/cliui@npm:^8.0.2": version: 8.0.2 resolution: "@isaacs/cliui@npm:8.0.2" @@ -10063,6 +10175,13 @@ __metadata: languageName: node linkType: hard +"@isaacs/cliui@npm:^9.0.0": + version: 9.0.0 + resolution: "@isaacs/cliui@npm:9.0.0" + checksum: 10/8ea3d1009fd29071419209bb91ede20cf27e6e2a1630c5e0702d8b3f47f9e1a3f1c5a587fa2cb96d22d18219790327df49db1bcced573346bbaf4577cf46b643 + languageName: node + linkType: hard + "@isaacs/fs-minipass@npm:^4.0.0": version: 4.0.1 resolution: "@isaacs/fs-minipass@npm:4.0.1" @@ -10144,10 +10263,10 @@ __metadata: languageName: node linkType: hard -"@istanbuljs/schema@npm:^0.1.2": - version: 0.1.2 - resolution: "@istanbuljs/schema@npm:0.1.2" - checksum: 10/e4a7fffc72fb2cfe2edfee8a09f68b2da18b1ab328a29d8be2933681f9e36103f0b083a5ae07129f4de26296eb7c2a1cc144cfea6cdb47f871a69766947144d3 +"@istanbuljs/schema@npm:^0.1.2, @istanbuljs/schema@npm:^0.1.3": + version: 0.1.3 + resolution: "@istanbuljs/schema@npm:0.1.3" + checksum: 10/a9b1e49acdf5efc2f5b2359f2df7f90c5c725f2656f16099e8b2cd3a000619ecca9fc48cf693ba789cf0fd989f6e0df6a22bc05574be4223ecdbb7997d04384b languageName: node linkType: hard @@ -10160,161 +10279,214 @@ __metadata: languageName: node linkType: hard -"@jest/console@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/console@npm:29.7.0" +"@jest/console@npm:30.2.0": + version: 30.2.0 + resolution: "@jest/console@npm:30.2.0" dependencies: - "@jest/types": "npm:^29.6.3" + "@jest/types": "npm:30.2.0" "@types/node": "npm:*" - chalk: "npm:^4.0.0" - jest-message-util: "npm:^29.7.0" - jest-util: "npm:^29.7.0" + chalk: "npm:^4.1.2" + jest-message-util: "npm:30.2.0" + jest-util: "npm:30.2.0" slash: "npm:^3.0.0" - checksum: 10/4a80c750e8a31f344233cb9951dee9b77bf6b89377cb131f8b3cde07ff218f504370133a5963f6a786af4d2ce7f85642db206ff7a15f99fe58df4c38ac04899e + checksum: 10/7cda9793962afa5c7fcfdde0ff5012694683b17941ee3c6a55ea9fd9a02f1c51ec4b4c767b867e1226f85a26af1d0f0d72c6a344e34c5bc4300312ebffd6e50b languageName: node linkType: hard -"@jest/core@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/core@npm:29.7.0" +"@jest/core@npm:30.2.0": + version: 30.2.0 + resolution: "@jest/core@npm:30.2.0" dependencies: - "@jest/console": "npm:^29.7.0" - "@jest/reporters": "npm:^29.7.0" - "@jest/test-result": "npm:^29.7.0" - "@jest/transform": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" + "@jest/console": "npm:30.2.0" + "@jest/pattern": "npm:30.0.1" + "@jest/reporters": "npm:30.2.0" + "@jest/test-result": "npm:30.2.0" + "@jest/transform": "npm:30.2.0" + "@jest/types": "npm:30.2.0" "@types/node": "npm:*" - ansi-escapes: "npm:^4.2.1" - chalk: "npm:^4.0.0" - ci-info: "npm:^3.2.0" - exit: "npm:^0.1.2" - graceful-fs: "npm:^4.2.9" - jest-changed-files: "npm:^29.7.0" - jest-config: "npm:^29.7.0" - jest-haste-map: "npm:^29.7.0" - jest-message-util: "npm:^29.7.0" - jest-regex-util: "npm:^29.6.3" - jest-resolve: "npm:^29.7.0" - jest-resolve-dependencies: "npm:^29.7.0" - jest-runner: "npm:^29.7.0" - jest-runtime: "npm:^29.7.0" - jest-snapshot: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - jest-validate: "npm:^29.7.0" - jest-watcher: "npm:^29.7.0" - micromatch: "npm:^4.0.4" - pretty-format: "npm:^29.7.0" + ansi-escapes: "npm:^4.3.2" + chalk: "npm:^4.1.2" + ci-info: "npm:^4.2.0" + exit-x: "npm:^0.2.2" + graceful-fs: "npm:^4.2.11" + jest-changed-files: "npm:30.2.0" + jest-config: "npm:30.2.0" + jest-haste-map: "npm:30.2.0" + jest-message-util: "npm:30.2.0" + jest-regex-util: "npm:30.0.1" + jest-resolve: "npm:30.2.0" + jest-resolve-dependencies: "npm:30.2.0" + jest-runner: "npm:30.2.0" + jest-runtime: "npm:30.2.0" + jest-snapshot: "npm:30.2.0" + jest-util: "npm:30.2.0" + jest-validate: "npm:30.2.0" + jest-watcher: "npm:30.2.0" + micromatch: "npm:^4.0.8" + pretty-format: "npm:30.2.0" slash: "npm:^3.0.0" - strip-ansi: "npm:^6.0.0" peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 peerDependenciesMeta: node-notifier: optional: true - checksum: 10/ab6ac2e562d083faac7d8152ec1cc4eccc80f62e9579b69ed40aedf7211a6b2d57024a6cd53c4e35fd051c39a236e86257d1d99ebdb122291969a0a04563b51e + checksum: 10/6763bb1efd937778f009821cd94c3705d3c31a156258a224b8745c1e0887976683f5413745ffb361b526f0fa2692e36aaa963aa197cc77ba932cff9d6d28af9d languageName: node linkType: hard -"@jest/create-cache-key-function@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/create-cache-key-function@npm:29.7.0" +"@jest/create-cache-key-function@npm:^30.0.0": + version: 30.2.0 + resolution: "@jest/create-cache-key-function@npm:30.2.0" dependencies: - "@jest/types": "npm:^29.6.3" - checksum: 10/061ef63b13ec8c8e5d08e4456f03b5cf8c7f9c1cab4fed8402e1479153cafce6eea80420e308ef62027abb7e29b825fcfa06551856bd021d98e92e381bf91723 + "@jest/types": "npm:30.2.0" + checksum: 10/7a2dd0efe747c1b2e61825e51ace11e42f278203fae37a3b9462c8d2132394978682ed7094f5ce3d9f5a9e5f2855e6d1d933e5f3ac5165b127c36591f3d98d85 languageName: node linkType: hard -"@jest/environment@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/environment@npm:29.7.0" +"@jest/diff-sequences@npm:30.0.1": + version: 30.0.1 + resolution: "@jest/diff-sequences@npm:30.0.1" + checksum: 10/0ddb7c7ba92d6057a2ee51a9cfc2155b77cca707fe959167466ea02dcb0687018cc3c22b9622f25f3a417d6ad370e2d4dcfedf9f1410dc9c02954a7484423cc7 + languageName: node + linkType: hard + +"@jest/environment-jsdom-abstract@npm:^30, @jest/environment-jsdom-abstract@npm:^30.0.0": + version: 30.2.0 + resolution: "@jest/environment-jsdom-abstract@npm:30.2.0" dependencies: - "@jest/fake-timers": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" + "@jest/environment": "npm:30.2.0" + "@jest/fake-timers": "npm:30.2.0" + "@jest/types": "npm:30.2.0" + "@types/jsdom": "npm:^21.1.7" "@types/node": "npm:*" - jest-mock: "npm:^29.7.0" - checksum: 10/90b5844a9a9d8097f2cf107b1b5e57007c552f64315da8c1f51217eeb0a9664889d3f145cdf8acf23a84f4d8309a6675e27d5b059659a004db0ea9546d1c81a8 + jest-mock: "npm:30.2.0" + jest-util: "npm:30.2.0" + peerDependencies: + canvas: ^3.0.0 + jsdom: "*" + peerDependenciesMeta: + canvas: + optional: true + checksum: 10/65a9c8504f213f4d125956383ffe6c4e566cfb0ff2fe67783adf9ebde33f772339e61fdd98ddc2bbae3029e3356d2386abedb9d101aa95d6fd51fabac38bebe0 languageName: node linkType: hard -"@jest/expect-utils@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/expect-utils@npm:29.7.0" +"@jest/environment@npm:30.2.0": + version: 30.2.0 + resolution: "@jest/environment@npm:30.2.0" dependencies: - jest-get-type: "npm:^29.6.3" - checksum: 10/ef8d379778ef574a17bde2801a6f4469f8022a46a5f9e385191dc73bb1fc318996beaed4513fbd7055c2847227a1bed2469977821866534593a6e52a281499ee - languageName: node - linkType: hard - -"@jest/expect@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/expect@npm:29.7.0" - dependencies: - expect: "npm:^29.7.0" - jest-snapshot: "npm:^29.7.0" - checksum: 10/fea6c3317a8da5c840429d90bfe49d928e89c9e89fceee2149b93a11b7e9c73d2f6e4d7cdf647163da938fc4e2169e4490be6bae64952902bc7a701033fd4880 - languageName: node - linkType: hard - -"@jest/fake-timers@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/fake-timers@npm:29.7.0" - dependencies: - "@jest/types": "npm:^29.6.3" - "@sinonjs/fake-timers": "npm:^10.0.2" + "@jest/fake-timers": "npm:30.2.0" + "@jest/types": "npm:30.2.0" "@types/node": "npm:*" - jest-message-util: "npm:^29.7.0" - jest-mock: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - checksum: 10/9b394e04ffc46f91725ecfdff34c4e043eb7a16e1d78964094c9db3fde0b1c8803e45943a980e8c740d0a3d45661906de1416ca5891a538b0660481a3a828c27 + jest-mock: "npm:30.2.0" + checksum: 10/e168a4ff328980eb9fde5e43aea80807fd0b2dbd4579ae8f68a03415a1e58adf5661db298054fa2351c7cb2b5a74bf67b8ab996656cf5927d0b0d0b6e2c2966b languageName: node linkType: hard -"@jest/globals@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/globals@npm:29.7.0" +"@jest/expect-utils@npm:30.2.0": + version: 30.2.0 + resolution: "@jest/expect-utils@npm:30.2.0" dependencies: - "@jest/environment": "npm:^29.7.0" - "@jest/expect": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - jest-mock: "npm:^29.7.0" - checksum: 10/97dbb9459135693ad3a422e65ca1c250f03d82b2a77f6207e7fa0edd2c9d2015fbe4346f3dc9ebff1678b9d8da74754d4d440b7837497f8927059c0642a22123 + "@jest/get-type": "npm:30.1.0" + checksum: 10/f2442f1bceb3411240d0f16fd0074377211b4373d3b8b2dc28929e861b6527a6deb403a362c25afa511d933cda4dfbdc98d4a08eeb51ee4968f7cb0299562349 languageName: node linkType: hard -"@jest/reporters@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/reporters@npm:29.7.0" +"@jest/expect@npm:30.2.0": + version: 30.2.0 + resolution: "@jest/expect@npm:30.2.0" + dependencies: + expect: "npm:30.2.0" + jest-snapshot: "npm:30.2.0" + checksum: 10/d950d95a64d5c6a39d56171dabb8dbe59423096231bb4f21d8ee0019878e6626701ac9d782803dc2589e2799ed39704031f818533f8a3e571b57032eafa85d12 + languageName: node + linkType: hard + +"@jest/fake-timers@npm:30.2.0": + version: 30.2.0 + resolution: "@jest/fake-timers@npm:30.2.0" + dependencies: + "@jest/types": "npm:30.2.0" + "@sinonjs/fake-timers": "npm:^13.0.0" + "@types/node": "npm:*" + jest-message-util: "npm:30.2.0" + jest-mock: "npm:30.2.0" + jest-util: "npm:30.2.0" + checksum: 10/c2df66576ba8049b07d5f239777243e21fcdaa09a446be1e55fac709d6273e2a926c1562e0372c3013142557ed9d386381624023549267a667b6e1b656e37fe6 + languageName: node + linkType: hard + +"@jest/get-type@npm:30.1.0": + version: 30.1.0 + resolution: "@jest/get-type@npm:30.1.0" + checksum: 10/e2a95fbb49ce2d15547db8af5602626caf9b05f62a5e583b4a2de9bd93a2bfe7175f9bbb2b8a5c3909ce261d467b6991d7265bb1d547cb60e7e97f571f361a70 + languageName: node + linkType: hard + +"@jest/globals@npm:30.2.0": + version: 30.2.0 + resolution: "@jest/globals@npm:30.2.0" + dependencies: + "@jest/environment": "npm:30.2.0" + "@jest/expect": "npm:30.2.0" + "@jest/types": "npm:30.2.0" + jest-mock: "npm:30.2.0" + checksum: 10/d4a331d3847cebb3acefe120350d8a6bb5517c1403de7cd2b4dc67be425f37ba0511beee77d6837b4da2d93a25a06d6f829ad7837da365fae45e1da57523525c + languageName: node + linkType: hard + +"@jest/pattern@npm:30.0.1": + version: 30.0.1 + resolution: "@jest/pattern@npm:30.0.1" + dependencies: + "@types/node": "npm:*" + jest-regex-util: "npm:30.0.1" + checksum: 10/afd03b4d3eadc9c9970cf924955dee47984a7e767901fe6fa463b17b246f0ddeec07b3e82c09715c54bde3c8abb92074160c0d79967bd23778724f184e7f5b7b + languageName: node + linkType: hard + +"@jest/reporters@npm:30.2.0": + version: 30.2.0 + resolution: "@jest/reporters@npm:30.2.0" dependencies: "@bcoe/v8-coverage": "npm:^0.2.3" - "@jest/console": "npm:^29.7.0" - "@jest/test-result": "npm:^29.7.0" - "@jest/transform": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - "@jridgewell/trace-mapping": "npm:^0.3.18" + "@jest/console": "npm:30.2.0" + "@jest/test-result": "npm:30.2.0" + "@jest/transform": "npm:30.2.0" + "@jest/types": "npm:30.2.0" + "@jridgewell/trace-mapping": "npm:^0.3.25" "@types/node": "npm:*" - chalk: "npm:^4.0.0" - collect-v8-coverage: "npm:^1.0.0" - exit: "npm:^0.1.2" - glob: "npm:^7.1.3" - graceful-fs: "npm:^4.2.9" + chalk: "npm:^4.1.2" + collect-v8-coverage: "npm:^1.0.2" + exit-x: "npm:^0.2.2" + glob: "npm:^10.3.10" + graceful-fs: "npm:^4.2.11" istanbul-lib-coverage: "npm:^3.0.0" istanbul-lib-instrument: "npm:^6.0.0" istanbul-lib-report: "npm:^3.0.0" - istanbul-lib-source-maps: "npm:^4.0.0" + istanbul-lib-source-maps: "npm:^5.0.0" istanbul-reports: "npm:^3.1.3" - jest-message-util: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - jest-worker: "npm:^29.7.0" + jest-message-util: "npm:30.2.0" + jest-util: "npm:30.2.0" + jest-worker: "npm:30.2.0" slash: "npm:^3.0.0" - string-length: "npm:^4.0.1" - strip-ansi: "npm:^6.0.0" + string-length: "npm:^4.0.2" v8-to-istanbul: "npm:^9.0.1" peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 peerDependenciesMeta: node-notifier: optional: true - checksum: 10/a17d1644b26dea14445cedd45567f4ba7834f980be2ef74447204e14238f121b50d8b858fde648083d2cd8f305f81ba434ba49e37a5f4237a6f2a61180cc73dc + checksum: 10/3848b59bf740c10c4e5c234dcc41c54adbd74932bf05d1d1582d09d86e9baa86ddaf3c43903505fd042ba1203c2889a732137d08058ce9dc0069ba33b5d5373d + languageName: node + linkType: hard + +"@jest/schemas@npm:30.0.5": + version: 30.0.5 + resolution: "@jest/schemas@npm:30.0.5" + dependencies: + "@sinclair/typebox": "npm:^0.34.0" + checksum: 10/40df4db55d4aeed09d1c7e19caf23788309cea34490a1c5d584c913494195e698b9967e996afc27226cac6d76e7512fe73ae6b9584480695c60dd18a5459cdba languageName: node linkType: hard @@ -10327,61 +10499,88 @@ __metadata: languageName: node linkType: hard -"@jest/source-map@npm:^29.6.3": - version: 29.6.3 - resolution: "@jest/source-map@npm:29.6.3" +"@jest/snapshot-utils@npm:30.2.0": + version: 30.2.0 + resolution: "@jest/snapshot-utils@npm:30.2.0" dependencies: - "@jridgewell/trace-mapping": "npm:^0.3.18" - callsites: "npm:^3.0.0" - graceful-fs: "npm:^4.2.9" - checksum: 10/bcc5a8697d471396c0003b0bfa09722c3cd879ad697eb9c431e6164e2ea7008238a01a07193dfe3cbb48b1d258eb7251f6efcea36f64e1ebc464ea3c03ae2deb + "@jest/types": "npm:30.2.0" + chalk: "npm:^4.1.2" + graceful-fs: "npm:^4.2.11" + natural-compare: "npm:^1.4.0" + checksum: 10/6b30ab2b0682117e3ce775e70b5be1eb01e1ea53a74f12ac7090cd1a5f37e9b795cd8de83853afa7b4b799c96b1c482499aa993ca2034ea0679525d32b7f9625 languageName: node linkType: hard -"@jest/test-result@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/test-result@npm:29.7.0" +"@jest/source-map@npm:30.0.1": + version: 30.0.1 + resolution: "@jest/source-map@npm:30.0.1" dependencies: - "@jest/console": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - "@types/istanbul-lib-coverage": "npm:^2.0.0" - collect-v8-coverage: "npm:^1.0.0" - checksum: 10/c073ab7dfe3c562bff2b8fee6cc724ccc20aa96bcd8ab48ccb2aa309b4c0c1923a9e703cea386bd6ae9b71133e92810475bb9c7c22328fc63f797ad3324ed189 + "@jridgewell/trace-mapping": "npm:^0.3.25" + callsites: "npm:^3.1.0" + graceful-fs: "npm:^4.2.11" + checksum: 10/161b27cdf8d9d80fd99374d55222b90478864c6990514be6ebee72b7184a034224c9aceed12c476f3a48d48601bf8ed2e0c047a5a81bd907dc192ebe71365ed4 languageName: node linkType: hard -"@jest/test-sequencer@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/test-sequencer@npm:29.7.0" +"@jest/test-result@npm:30.2.0": + version: 30.2.0 + resolution: "@jest/test-result@npm:30.2.0" dependencies: - "@jest/test-result": "npm:^29.7.0" - graceful-fs: "npm:^4.2.9" - jest-haste-map: "npm:^29.7.0" + "@jest/console": "npm:30.2.0" + "@jest/types": "npm:30.2.0" + "@types/istanbul-lib-coverage": "npm:^2.0.6" + collect-v8-coverage: "npm:^1.0.2" + checksum: 10/f58f79c3c3ba6dd15325e05b0b5a300777cd8cc38327f622608b6fe849b1073ee9633e33d1e5d7ef5b97a1ce71543d0ad92674b7a279f53033143e8dd7c22959 + languageName: node + linkType: hard + +"@jest/test-sequencer@npm:30.2.0": + version: 30.2.0 + resolution: "@jest/test-sequencer@npm:30.2.0" + dependencies: + "@jest/test-result": "npm:30.2.0" + graceful-fs: "npm:^4.2.11" + jest-haste-map: "npm:30.2.0" slash: "npm:^3.0.0" - checksum: 10/4420c26a0baa7035c5419b0892ff8ffe9a41b1583ec54a10db3037cd46a7e29dd3d7202f8aa9d376e9e53be5f8b1bc0d16e1de6880a6d319b033b01dc4c8f639 + checksum: 10/7923964b27048b2233858b32aa1b34d4dd9e404311626d944a706bcdcaa0b1585f43f2ffa3fa893ecbf133566f31ba2b79ab5eaaaf674b8558c6c7029ecbea5e languageName: node linkType: hard -"@jest/transform@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/transform@npm:29.7.0" +"@jest/transform@npm:30.2.0": + version: 30.2.0 + resolution: "@jest/transform@npm:30.2.0" dependencies: - "@babel/core": "npm:^7.11.6" - "@jest/types": "npm:^29.6.3" - "@jridgewell/trace-mapping": "npm:^0.3.18" - babel-plugin-istanbul: "npm:^6.1.1" - chalk: "npm:^4.0.0" + "@babel/core": "npm:^7.27.4" + "@jest/types": "npm:30.2.0" + "@jridgewell/trace-mapping": "npm:^0.3.25" + babel-plugin-istanbul: "npm:^7.0.1" + chalk: "npm:^4.1.2" convert-source-map: "npm:^2.0.0" fast-json-stable-stringify: "npm:^2.1.0" - graceful-fs: "npm:^4.2.9" - jest-haste-map: "npm:^29.7.0" - jest-regex-util: "npm:^29.6.3" - jest-util: "npm:^29.7.0" - micromatch: "npm:^4.0.4" - pirates: "npm:^4.0.4" + graceful-fs: "npm:^4.2.11" + jest-haste-map: "npm:30.2.0" + jest-regex-util: "npm:30.0.1" + jest-util: "npm:30.2.0" + micromatch: "npm:^4.0.8" + pirates: "npm:^4.0.7" slash: "npm:^3.0.0" - write-file-atomic: "npm:^4.0.2" - checksum: 10/30f42293545ab037d5799c81d3e12515790bb58513d37f788ce32d53326d0d72ebf5b40f989e6896739aa50a5f77be44686e510966370d58511d5ad2637c68c1 + write-file-atomic: "npm:^5.0.1" + checksum: 10/c75d72d524c2a50ea6c05778a9b76a6e48bc228a3390896a6edd4416f7b4954ee0a07e229ed7b4949ce8889324b70034c784751e3fc455a25648bd8dcad17d0d + languageName: node + linkType: hard + +"@jest/types@npm:30.2.0": + version: 30.2.0 + resolution: "@jest/types@npm:30.2.0" + dependencies: + "@jest/pattern": "npm:30.0.1" + "@jest/schemas": "npm:30.0.5" + "@types/istanbul-lib-coverage": "npm:^2.0.6" + "@types/istanbul-reports": "npm:^3.0.4" + "@types/node": "npm:*" + "@types/yargs": "npm:^17.0.33" + chalk: "npm:^4.1.2" + checksum: 10/f50fcaea56f873a51d19254ab16762f2ea8ca88e3e08da2e496af5da2b67c322915a4fcd0153803cc05063ffe87ebef2ab4330e0a1b06ab984a26c916cbfc26b languageName: node linkType: hard @@ -10399,12 +10598,11 @@ __metadata: languageName: node linkType: hard -"@joshwooding/vite-plugin-react-docgen-typescript@npm:0.6.1": - version: 0.6.1 - resolution: "@joshwooding/vite-plugin-react-docgen-typescript@npm:0.6.1" +"@joshwooding/vite-plugin-react-docgen-typescript@npm:^0.6.4": + version: 0.6.4 + resolution: "@joshwooding/vite-plugin-react-docgen-typescript@npm:0.6.4" dependencies: - glob: "npm:^10.0.0" - magic-string: "npm:^0.30.0" + glob: "npm:^13.0.1" react-docgen-typescript: "npm:^2.2.2" peerDependencies: typescript: ">= 4.3.x" @@ -10412,7 +10610,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10/a82b6005378ccda13250fcfeaa04ec2ba17d1c2923b5bba5907d5b2fd658c661b29c1215eb1c0fe305b390bee89ec77c684aa68506262734c3bb3cd76d8a6963 + checksum: 10/9e1ead0120036e3acb2bfe4772c5c1f352ca43e19e3e59ef37aa7d08843302118a8d2313124c6058af60246058a6aa9c8895683e6d56d56b5c8cf20102078e04 languageName: node linkType: hard @@ -10453,7 +10651,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.15, @jridgewell/sourcemap-codec@npm:^1.5.0": +"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.15, @jridgewell/sourcemap-codec@npm:^1.5.0, @jridgewell/sourcemap-codec@npm:^1.5.5": version: 1.5.5 resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" checksum: 10/5d9d207b462c11e322d71911e55e21a4e2772f71ffe8d6f1221b8eb5ae6774458c1d242f897fb0814e8714ca9a6b498abfa74dfe4f434493342902b1a48b33a5 @@ -10470,13 +10668,13 @@ __metadata: languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.28, @jridgewell/trace-mapping@npm:^0.3.9": - version: 0.3.30 - resolution: "@jridgewell/trace-mapping@npm:0.3.30" +"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.23, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.28, @jridgewell/trace-mapping@npm:^0.3.9": + version: 0.3.31 + resolution: "@jridgewell/trace-mapping@npm:0.3.31" dependencies: "@jridgewell/resolve-uri": "npm:^3.1.0" "@jridgewell/sourcemap-codec": "npm:^1.4.14" - checksum: 10/f9fabe1122058f4fedc242bdc43fcaacb6b222c6fb712b7904c37704dcb16e50e07ca137ff99043da44292b18a8720296ff97f7703e960678b8ef51d0db4d250 + checksum: 10/da0283270e691bdb5543806077548532791608e52386cfbbf3b9e8fb00457859d1bd01d512851161c886eb3a2f3ce6fd9bcf25db8edf3bddedd275bd4a88d606 languageName: node linkType: hard @@ -10521,6 +10719,15 @@ __metadata: languageName: node linkType: hard +"@jsonjoy.com/base64@npm:17.67.0": + version: 17.67.0 + resolution: "@jsonjoy.com/base64@npm:17.67.0" + peerDependencies: + tslib: 2 + checksum: 10/ae44b0c4c83ecc5c0ee1911706a665e18e89d64a2b705cc458d7f6fc3c3c7db0e621261e978d02b74ded6a9fe1aafc8e708eb8a133e794a92bb033c50a0c4ccd + languageName: node + linkType: hard + "@jsonjoy.com/base64@npm:^1.1.2": version: 1.1.2 resolution: "@jsonjoy.com/base64@npm:1.1.2" @@ -10530,12 +10737,30 @@ __metadata: languageName: node linkType: hard -"@jsonjoy.com/buffers@npm:^1.0.0": - version: 1.0.0 - resolution: "@jsonjoy.com/buffers@npm:1.0.0" +"@jsonjoy.com/buffers@npm:17.67.0, @jsonjoy.com/buffers@npm:^17.65.0": + version: 17.67.0 + resolution: "@jsonjoy.com/buffers@npm:17.67.0" peerDependencies: tslib: 2 - checksum: 10/3347a16a555398c19265203877b8e325be5facc1d875c0e85db0cc20b53418302257ed5af15bf53b84ff8337bd024a10be25ff3a02ebc54ae8e643577ca29e63 + checksum: 10/6c8f6c4c73ec4ddab538a88be0bf72d8a934752755d43b0289fbe19ce9fa6123f082d1cd5ae179495e121a2f50267d26d36641f6dadedd8d5d2a2f980426e8ff + languageName: node + linkType: hard + +"@jsonjoy.com/buffers@npm:^1.0.0, @jsonjoy.com/buffers@npm:^1.2.0": + version: 1.2.1 + resolution: "@jsonjoy.com/buffers@npm:1.2.1" + peerDependencies: + tslib: 2 + checksum: 10/8ef4784d05c0fb4d0f27a1f78f5b0ae1f3b537d237f978d10be0b88f59a534ae44db2a4bde28eee0eb461ede31dc194aab5927ac001ed2b764629fa43ae9b60b + languageName: node + linkType: hard + +"@jsonjoy.com/codegen@npm:17.67.0": + version: 17.67.0 + resolution: "@jsonjoy.com/codegen@npm:17.67.0" + peerDependencies: + tslib: 2 + checksum: 10/e2462836c708999d045c4a15099f12e721089a3731f0ad33da210559a52ed763b8bddbec3c181857341984ef12ea355290609f37f0dc6f8de1545c028090adf5 languageName: node linkType: hard @@ -10548,35 +10773,181 @@ __metadata: languageName: node linkType: hard -"@jsonjoy.com/json-pack@npm:^1.0.3": - version: 1.10.0 - resolution: "@jsonjoy.com/json-pack@npm:1.10.0" +"@jsonjoy.com/fs-core@npm:4.56.11": + version: 4.56.11 + resolution: "@jsonjoy.com/fs-core@npm:4.56.11" dependencies: - "@jsonjoy.com/base64": "npm:^1.1.2" - "@jsonjoy.com/buffers": "npm:^1.0.0" - "@jsonjoy.com/codegen": "npm:^1.0.0" - "@jsonjoy.com/json-pointer": "npm:^1.0.1" - "@jsonjoy.com/util": "npm:^1.9.0" - hyperdyperid: "npm:^1.2.0" + "@jsonjoy.com/fs-node-builtins": "npm:4.56.11" + "@jsonjoy.com/fs-node-utils": "npm:4.56.11" thingies: "npm:^2.5.0" peerDependencies: tslib: 2 - checksum: 10/5ad1eb008cf5f681dbc1301f5a7c33ca3a101d22773dbd2a9db4b02e5847c2f8b0c5f074c69f4ced5c24b3dc1dd85ddbc1688f0002fee9416869c579c34c4a8e + checksum: 10/46adecf53d79246eed43503e999f3068b38d6c5b209e305c5b6ca55cfbb68fd2780a2fe3ddc2de3535c51a6db76e4acaca67dc127d1ce54399cf786a5ccbd4ca languageName: node linkType: hard -"@jsonjoy.com/json-pointer@npm:^1.0.1": - version: 1.0.1 - resolution: "@jsonjoy.com/json-pointer@npm:1.0.1" +"@jsonjoy.com/fs-fsa@npm:4.56.11": + version: 4.56.11 + resolution: "@jsonjoy.com/fs-fsa@npm:4.56.11" dependencies: - "@jsonjoy.com/util": "npm:^1.3.0" + "@jsonjoy.com/fs-core": "npm:4.56.11" + "@jsonjoy.com/fs-node-builtins": "npm:4.56.11" + "@jsonjoy.com/fs-node-utils": "npm:4.56.11" + thingies: "npm:^2.5.0" peerDependencies: tslib: 2 - checksum: 10/ff15df95df38677a09fe2155cf4521844c9f0653c77a7090bb6e726d275fb78ab6c3543c4ebdc14aff07e44da08b1545631a63e42e217c18059194957e060ed9 + checksum: 10/ed322ec4596f0b385d17dfeca6f2e1c2de51aa7c356f092700ad7b8930816f27f7f49234ba5eb544f03d173ea3cf5f9509597a5c64f0c03d2c4c456b751e2a16 languageName: node linkType: hard -"@jsonjoy.com/util@npm:^1.3.0, @jsonjoy.com/util@npm:^1.9.0": +"@jsonjoy.com/fs-node-builtins@npm:4.56.11": + version: 4.56.11 + resolution: "@jsonjoy.com/fs-node-builtins@npm:4.56.11" + peerDependencies: + tslib: 2 + checksum: 10/04ca8c56cd3b883149b42cce5d9376d18445a071b6d88c5366f930c2ba93505a7d708ad8f5af1a15e691dd87ffb971048b906450287d5faf363472ced43ea884 + languageName: node + linkType: hard + +"@jsonjoy.com/fs-node-to-fsa@npm:4.56.11": + version: 4.56.11 + resolution: "@jsonjoy.com/fs-node-to-fsa@npm:4.56.11" + dependencies: + "@jsonjoy.com/fs-fsa": "npm:4.56.11" + "@jsonjoy.com/fs-node-builtins": "npm:4.56.11" + "@jsonjoy.com/fs-node-utils": "npm:4.56.11" + peerDependencies: + tslib: 2 + checksum: 10/e4e8b6ea18969665925b97055cb06636fbd74e5025cd126280ede2245ba2efe328146e0b7a5e45c45a64175dc74231caa982de4dda51bffb061c028c931f2fad + languageName: node + linkType: hard + +"@jsonjoy.com/fs-node-utils@npm:4.56.11": + version: 4.56.11 + resolution: "@jsonjoy.com/fs-node-utils@npm:4.56.11" + dependencies: + "@jsonjoy.com/fs-node-builtins": "npm:4.56.11" + peerDependencies: + tslib: 2 + checksum: 10/72ca92de077c34d26c935b1ec6011de587bc2f382e822d9e57c76318d506f3c40f5075d04ede666b59d45e275e577ea77032a10b48161483040f3e3f7c381232 + languageName: node + linkType: hard + +"@jsonjoy.com/fs-node@npm:4.56.11": + version: 4.56.11 + resolution: "@jsonjoy.com/fs-node@npm:4.56.11" + dependencies: + "@jsonjoy.com/fs-core": "npm:4.56.11" + "@jsonjoy.com/fs-node-builtins": "npm:4.56.11" + "@jsonjoy.com/fs-node-utils": "npm:4.56.11" + "@jsonjoy.com/fs-print": "npm:4.56.11" + "@jsonjoy.com/fs-snapshot": "npm:4.56.11" + glob-to-regex.js: "npm:^1.0.0" + thingies: "npm:^2.5.0" + peerDependencies: + tslib: 2 + checksum: 10/a6a7e84de467d644bd2b23f34afaf61e93eb05245f40325a067403d2af5b4b22adce806639b80ff0f8431049dda706a803e6f73f2bf944d73a1ad78ac04bbd15 + languageName: node + linkType: hard + +"@jsonjoy.com/fs-print@npm:4.56.11": + version: 4.56.11 + resolution: "@jsonjoy.com/fs-print@npm:4.56.11" + dependencies: + "@jsonjoy.com/fs-node-utils": "npm:4.56.11" + tree-dump: "npm:^1.1.0" + peerDependencies: + tslib: 2 + checksum: 10/352fb4932c0ed44b5fb148b253754748b6d3e9859452ef1b03cebb6b9601e9f84336ef0932ca4de9687b2f3ec703083d4572c7bf2036537f787673229c3ed68b + languageName: node + linkType: hard + +"@jsonjoy.com/fs-snapshot@npm:4.56.11": + version: 4.56.11 + resolution: "@jsonjoy.com/fs-snapshot@npm:4.56.11" + dependencies: + "@jsonjoy.com/buffers": "npm:^17.65.0" + "@jsonjoy.com/fs-node-utils": "npm:4.56.11" + "@jsonjoy.com/json-pack": "npm:^17.65.0" + "@jsonjoy.com/util": "npm:^17.65.0" + peerDependencies: + tslib: 2 + checksum: 10/97665962c5bf609720ac2913111da540235fa8aed52241e017da16ae2ac0228ae9e1938906d51e61f85fdaa0511c46db22b3c8212c9ba77a0ae4082cd20e4d57 + languageName: node + linkType: hard + +"@jsonjoy.com/json-pack@npm:^1.11.0": + version: 1.21.0 + resolution: "@jsonjoy.com/json-pack@npm:1.21.0" + dependencies: + "@jsonjoy.com/base64": "npm:^1.1.2" + "@jsonjoy.com/buffers": "npm:^1.2.0" + "@jsonjoy.com/codegen": "npm:^1.0.0" + "@jsonjoy.com/json-pointer": "npm:^1.0.2" + "@jsonjoy.com/util": "npm:^1.9.0" + hyperdyperid: "npm:^1.2.0" + thingies: "npm:^2.5.0" + tree-dump: "npm:^1.1.0" + peerDependencies: + tslib: 2 + checksum: 10/138b7eb8c96e6e435b0218c8f2eb5554e4eb49198a8718673a65e81da53b4617553ffa7124b51d6ea00fdfb868d6ff8b5ad6365e8336380ca7025f04d0412ee7 + languageName: node + linkType: hard + +"@jsonjoy.com/json-pack@npm:^17.65.0": + version: 17.67.0 + resolution: "@jsonjoy.com/json-pack@npm:17.67.0" + dependencies: + "@jsonjoy.com/base64": "npm:17.67.0" + "@jsonjoy.com/buffers": "npm:17.67.0" + "@jsonjoy.com/codegen": "npm:17.67.0" + "@jsonjoy.com/json-pointer": "npm:17.67.0" + "@jsonjoy.com/util": "npm:17.67.0" + hyperdyperid: "npm:^1.2.0" + thingies: "npm:^2.5.0" + tree-dump: "npm:^1.1.0" + peerDependencies: + tslib: 2 + checksum: 10/9ff4403862e49433fe607175e90af749d64902640d63919ba559e5748d1a3db60d7366cc3b84dcc4a57ad478540e5eecb22fed80766e293482a0ab8e583b1b0b + languageName: node + linkType: hard + +"@jsonjoy.com/json-pointer@npm:17.67.0": + version: 17.67.0 + resolution: "@jsonjoy.com/json-pointer@npm:17.67.0" + dependencies: + "@jsonjoy.com/util": "npm:17.67.0" + peerDependencies: + tslib: 2 + checksum: 10/5a27c6b5b1276d357cfc3e8a05112d6305ccd17bf672190f25dfac2f4108ced170e784451d64728f60f93305c0007e3f832ddd175b8a47f3eb652cbabcec31ad + languageName: node + linkType: hard + +"@jsonjoy.com/json-pointer@npm:^1.0.2": + version: 1.0.2 + resolution: "@jsonjoy.com/json-pointer@npm:1.0.2" + dependencies: + "@jsonjoy.com/codegen": "npm:^1.0.0" + "@jsonjoy.com/util": "npm:^1.9.0" + peerDependencies: + tslib: 2 + checksum: 10/f22baeb3abc8ace2d8902d06ec297343431d4486dcf399aaaffd26ace7e62e194fe0efb4b7880e45b3b7939224ee838d3213448ef654fc8a61c91a76fe994d94 + languageName: node + linkType: hard + +"@jsonjoy.com/util@npm:17.67.0, @jsonjoy.com/util@npm:^17.65.0": + version: 17.67.0 + resolution: "@jsonjoy.com/util@npm:17.67.0" + dependencies: + "@jsonjoy.com/buffers": "npm:17.67.0" + "@jsonjoy.com/codegen": "npm:17.67.0" + peerDependencies: + tslib: 2 + checksum: 10/b0facf65c3190d6ed1ada7e5b7679d80fa5da73bfbd02f2bb2f3af1c28c0d854b6ee2350824313b7ba82c0e5191da94903b4af61255bc232dbb7feedd2f31e0c + languageName: node + linkType: hard + +"@jsonjoy.com/util@npm:^1.9.0": version: 1.9.0 resolution: "@jsonjoy.com/util@npm:1.9.0" dependencies: @@ -10621,12 +10992,10 @@ __metadata: languageName: node linkType: hard -"@keyv/serialize@npm:^1.0.3": - version: 1.0.3 - resolution: "@keyv/serialize@npm:1.0.3" - dependencies: - buffer: "npm:^6.0.3" - checksum: 10/d6a9194dd781bc26cc4d55f392d843810c1fdc0da81e69203e633cb289fc0a8edc8bc6466f66c4cbb55da0a5b405e89f14a68b48d6e73919ae82f8249fb5e444 +"@keyv/serialize@npm:^1.0.3, @keyv/serialize@npm:^1.1.1": + version: 1.1.1 + resolution: "@keyv/serialize@npm:1.1.1" + checksum: 10/e3b2cb1377863342acedd5ff785af3e69269bee9b44707c617d1c8bc14eeb5ac763159d6455903ffe92f143c2238e1e783c4f113f9c8910eacccf172894472da languageName: node linkType: hard @@ -10639,7 +11008,7 @@ __metadata: languageName: node linkType: hard -"@kubernetes-models/apimachinery@npm:^2.0.0, @kubernetes-models/apimachinery@npm:^2.0.2": +"@kubernetes-models/apimachinery@npm:^2.0.0, @kubernetes-models/apimachinery@npm:^2.2.0": version: 2.2.0 resolution: "@kubernetes-models/apimachinery@npm:2.2.0" dependencies: @@ -10703,6 +11072,15 @@ __metadata: languageName: node linkType: hard +"@kwsites/file-exists@npm:^1.1.1": + version: 1.1.1 + resolution: "@kwsites/file-exists@npm:1.1.1" + dependencies: + debug: "npm:^4.1.1" + checksum: 10/4ff945de7293285133aeae759caddc71e73c4a44a12fac710fdd4f574cce2671a3f89d8165fdb03d383cfc97f3f96f677d8de3c95133da3d0e12a123a23109fe + languageName: node + linkType: hard + "@leichtgewicht/ip-codec@npm:^2.0.1": version: 2.0.3 resolution: "@leichtgewicht/ip-codec@npm:2.0.3" @@ -10710,28 +11088,28 @@ __metadata: languageName: node linkType: hard -"@lezer/common@npm:^1.0.0, @lezer/common@npm:^1.1.0": - version: 1.1.0 - resolution: "@lezer/common@npm:1.1.0" - checksum: 10/cabe34758bb41c6c7e38aaabdc8a6f51469b1307ed9b5760dd1fc7777d77a012e3e3f37c970a91319c10cc5e4d355db5c0e5515cc9392b9d0b833a66a8cc4120 +"@lezer/common@npm:^1.0.0, @lezer/common@npm:^1.3.0, @lezer/common@npm:^1.5.0": + version: 1.5.0 + resolution: "@lezer/common@npm:1.5.0" + checksum: 10/d99a45947c5033476f7c16f475b364e5b276e89a351641d8d785ceac88e8175f7b7b7d43dda80c3d9097f5e3379f018404bbe59a41d15992df23a03bbef3519b languageName: node linkType: hard "@lezer/highlight@npm:^1.0.0": - version: 1.0.0 - resolution: "@lezer/highlight@npm:1.0.0" + version: 1.2.3 + resolution: "@lezer/highlight@npm:1.2.3" dependencies: - "@lezer/common": "npm:^1.0.0" - checksum: 10/ed362dd4c27218bd941807f14ee691f1ad73613f96041538f5d2cc06521490a346a74044f32da573c1c6529bea2f5c190064c4bca92918732cb580c298880947 + "@lezer/common": "npm:^1.3.0" + checksum: 10/8f787d464f8a036f117a0b23e73ac034d224a57d72501c6559089098a28f127c9e495b90ac7d132acc86199e0b64d4c038f75f9293a37c7c61add52fa1acdb4e languageName: node linkType: hard "@lezer/lr@npm:^1.0.0": - version: 1.0.0 - resolution: "@lezer/lr@npm:1.0.0" + version: 1.4.4 + resolution: "@lezer/lr@npm:1.4.4" dependencies: "@lezer/common": "npm:^1.0.0" - checksum: 10/c1c60beef143008b5fbc64a6713be7656b51c858b83f9199cdafb439fc4028f35544990dd7b1fc3d89a518eecb10c90f5f3bdf6804f0a4429dfddb9210275add + checksum: 10/3485153107863075fc9c813977dd1a26b43114624e440e33c9e906d5afc6ec6b236b19439196b5baf7dd2d4c34f10642aa280250758ed3d60de5cad54590b1f2 languageName: node linkType: hard @@ -11002,54 +11380,66 @@ __metadata: languageName: node linkType: hard -"@microsoft/api-documenter@npm:^7.25.7": - version: 7.25.14 - resolution: "@microsoft/api-documenter@npm:7.25.14" +"@microsoft/api-documenter@npm:^7.28.1": + version: 7.28.2 + resolution: "@microsoft/api-documenter@npm:7.28.2" dependencies: - "@microsoft/api-extractor-model": "npm:7.29.8" - "@microsoft/tsdoc": "npm:~0.15.0" - "@rushstack/node-core-library": "npm:5.9.0" - "@rushstack/terminal": "npm:0.14.2" - "@rushstack/ts-command-line": "npm:4.22.8" - js-yaml: "npm:~3.13.1" + "@microsoft/api-extractor-model": "npm:7.32.2" + "@microsoft/tsdoc": "npm:~0.16.0" + "@rushstack/node-core-library": "npm:5.19.1" + "@rushstack/terminal": "npm:0.19.5" + "@rushstack/ts-command-line": "npm:5.1.5" + js-yaml: "npm:~4.1.0" resolve: "npm:~1.22.1" bin: api-documenter: bin/api-documenter - checksum: 10/7ab46b4e8f1d84220d599339e87a518fcd95cc87fa402e39667dd2fe73a3b6f8814f22bb7f374ccb7b0a87982a6a77b96f4061c4d338ef7eee3112dd7a89700a + checksum: 10/32d40e048894d1b96dc8a8855afdd55fdd0e3c599d624c73daf616c50c965dfd0e174d380222ec9de5a5bab105029944d82c50c0b1488920233876d7077a148a languageName: node linkType: hard -"@microsoft/api-extractor-model@npm:7.29.8": - version: 7.29.8 - resolution: "@microsoft/api-extractor-model@npm:7.29.8" +"@microsoft/api-extractor-model@npm:7.32.2": + version: 7.32.2 + resolution: "@microsoft/api-extractor-model@npm:7.32.2" dependencies: - "@microsoft/tsdoc": "npm:~0.15.0" - "@microsoft/tsdoc-config": "npm:~0.17.0" - "@rushstack/node-core-library": "npm:5.9.0" - checksum: 10/06932e61f0a1979dbacc716e143f9d34d856338504cd8d016d98a3d3c83d60e7c25bfdc64d011c6ddf94de2351b52872e8722d970fce44c49bd630aeca32c987 + "@microsoft/tsdoc": "npm:~0.16.0" + "@microsoft/tsdoc-config": "npm:~0.18.0" + "@rushstack/node-core-library": "npm:5.19.1" + checksum: 10/89760055c7d3074cd903e3694c411eafa8704f048781bb2db0f0ba6e6a9a1bb12a722419ddd99141562cc8d13c98e2deee6eba118853eed630918705b223107b languageName: node linkType: hard -"@microsoft/api-extractor@npm:^7.47.2": - version: 7.47.9 - resolution: "@microsoft/api-extractor@npm:7.47.9" +"@microsoft/api-extractor-model@npm:7.33.1": + version: 7.33.1 + resolution: "@microsoft/api-extractor-model@npm:7.33.1" dependencies: - "@microsoft/api-extractor-model": "npm:7.29.8" - "@microsoft/tsdoc": "npm:~0.15.0" - "@microsoft/tsdoc-config": "npm:~0.17.0" - "@rushstack/node-core-library": "npm:5.9.0" - "@rushstack/rig-package": "npm:0.5.3" - "@rushstack/terminal": "npm:0.14.2" - "@rushstack/ts-command-line": "npm:4.22.8" - lodash: "npm:~4.17.15" - minimatch: "npm:~3.0.3" + "@microsoft/tsdoc": "npm:~0.16.0" + "@microsoft/tsdoc-config": "npm:~0.18.0" + "@rushstack/node-core-library": "npm:5.20.1" + checksum: 10/cb267ca0020a68b84570bc99e974d050acf8b17a47f1999998a9dbc2ef81453f8188a93970a6b2274890a5dd5015502b6cebe94da06d3583e65ca490dabf4c1e + languageName: node + linkType: hard + +"@microsoft/api-extractor@npm:^7.57.3": + version: 7.57.3 + resolution: "@microsoft/api-extractor@npm:7.57.3" + dependencies: + "@microsoft/api-extractor-model": "npm:7.33.1" + "@microsoft/tsdoc": "npm:~0.16.0" + "@microsoft/tsdoc-config": "npm:~0.18.0" + "@rushstack/node-core-library": "npm:5.20.1" + "@rushstack/rig-package": "npm:0.7.1" + "@rushstack/terminal": "npm:0.22.1" + "@rushstack/ts-command-line": "npm:5.3.1" + diff: "npm:~8.0.2" + lodash: "npm:~4.17.23" + minimatch: "npm:10.2.1" resolve: "npm:~1.22.1" semver: "npm:~7.5.4" source-map: "npm:~0.6.1" - typescript: "npm:5.4.2" + typescript: "npm:5.8.2" bin: api-extractor: bin/api-extractor - checksum: 10/1814708284ed95a45969060f794b24b21a5a5e17aa88ec95099a8fc052d5c435523f465f60c4128288e210a39ff056a564fb2b8f291959ef0fc3d7423759f837 + checksum: 10/5b2a8c1833b97db4df49aa0d326b4591474a241da2c71489b5de9f24cc42f7579a2ff45d44bc52302c8f4ffb9dd0fbd6e101f19231003dafd31c0efc8100e2ba languageName: node linkType: hard @@ -11067,88 +11457,114 @@ __metadata: languageName: node linkType: hard -"@microsoft/tsdoc-config@npm:~0.17.0": - version: 0.17.0 - resolution: "@microsoft/tsdoc-config@npm:0.17.0" +"@microsoft/tsdoc-config@npm:~0.18.0": + version: 0.18.0 + resolution: "@microsoft/tsdoc-config@npm:0.18.0" dependencies: - "@microsoft/tsdoc": "npm:0.15.0" + "@microsoft/tsdoc": "npm:0.16.0" ajv: "npm:~8.12.0" jju: "npm:~1.4.0" resolve: "npm:~1.22.2" - checksum: 10/6e20f9b917d20e517b6752cbb46c84ccc4c8be7ce82d7424e413bd7111a2f1497714a72e61ac1a96df97d0050cb98b3a53006316eeb0cfea9bb6d7131432c7a8 + checksum: 10/0470df5326181d876faba51617d011a632b2e3b420953e521bb865715d0db2fb0b8bfb3ccbcaef267356a1a7150d2ffba40022db5930db6b15fcb348bf846a4b languageName: node linkType: hard -"@microsoft/tsdoc@npm:0.15.0, @microsoft/tsdoc@npm:~0.15.0": - version: 0.15.0 - resolution: "@microsoft/tsdoc@npm:0.15.0" - checksum: 10/fd025e5e3966248cd5477b9ddad4e9aa0dd69291f372a207f18a686b3097dcf5ecf38325caf0f4ad2697f1f39fd45b536e4ada6756008b8bcc5eccbc3201313d +"@microsoft/tsdoc@npm:0.16.0, @microsoft/tsdoc@npm:~0.16.0": + version: 0.16.0 + resolution: "@microsoft/tsdoc@npm:0.16.0" + checksum: 10/1eaad3605234dc7e44898c15d1ba3c97fb968af1117025400cba572ce268da05afc36634d1fb9e779457af3ff7f13330aee07a962510a4d9c6612c13f71ee41e languageName: node linkType: hard -"@modelcontextprotocol/sdk@npm:^1.12.3": - version: 1.13.1 - resolution: "@modelcontextprotocol/sdk@npm:1.13.1" +"@modelcontextprotocol/sdk@npm:^1.25.2": + version: 1.29.0 + resolution: "@modelcontextprotocol/sdk@npm:1.29.0" dependencies: - ajv: "npm:^6.12.6" + "@hono/node-server": "npm:^1.19.9" + ajv: "npm:^8.17.1" + ajv-formats: "npm:^3.0.1" content-type: "npm:^1.0.5" cors: "npm:^2.8.5" cross-spawn: "npm:^7.0.5" eventsource: "npm:^3.0.2" - express: "npm:^5.0.1" - express-rate-limit: "npm:^7.5.0" + eventsource-parser: "npm:^3.0.0" + express: "npm:^5.2.1" + express-rate-limit: "npm:^8.2.1" + hono: "npm:^4.11.4" + jose: "npm:^6.1.3" + json-schema-typed: "npm:^8.0.2" pkce-challenge: "npm:^5.0.0" raw-body: "npm:^3.0.0" - zod: "npm:^3.23.8" - zod-to-json-schema: "npm:^3.24.1" - checksum: 10/b516d72e1cd14c67c8a2e5cb95fcc1c03c50be989850e3e963a7ed11000acb604e65efeaad47ea93c847130536ee51859a8d34e6dbe99d408e1a24224592e57f + zod: "npm:^3.25 || ^4.0" + zod-to-json-schema: "npm:^3.25.1" + peerDependencies: + "@cfworker/json-schema": ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + "@cfworker/json-schema": + optional: true + zod: + optional: false + checksum: 10/ff551b97e06b661f95fec8fd34e112c446e69894a84a9979cdac369fb5de27f0a1a5c1f4e2a1f270cc60f93e54c28a8059a94ca51c3d528d2670ade874b244f9 languageName: node linkType: hard -"@module-federation/bridge-react-webpack-plugin@npm:0.9.1": - version: 0.9.1 - resolution: "@module-federation/bridge-react-webpack-plugin@npm:0.9.1" +"@module-federation/bridge-react-webpack-plugin@npm:2.3.3": + version: 2.3.3 + resolution: "@module-federation/bridge-react-webpack-plugin@npm:2.3.3" dependencies: - "@module-federation/sdk": "npm:0.9.1" + "@module-federation/sdk": "npm:2.3.3" "@types/semver": "npm:7.5.8" semver: "npm:7.6.3" - checksum: 10/4ff197741b1bdccf8f9e2236781e5ce3ef434e4207c5462b4b95b044c4c57a3ab3dd4dce48490d9fde5bd06fb855b9918841c7d04306726ee224d3e288074091 + checksum: 10/ed32fd13898aa838c04e2b9e319d217454b94794935062889f4fce31825dc503f07189b82203db267a6423314658ea76b8fe54f8a5ec2cbe5ef1f5108f0535dc languageName: node linkType: hard -"@module-federation/data-prefetch@npm:0.9.1": - version: 0.9.1 - resolution: "@module-federation/data-prefetch@npm:0.9.1" +"@module-federation/cli@npm:2.3.3": + version: 2.3.3 + resolution: "@module-federation/cli@npm:2.3.3" dependencies: - "@module-federation/runtime": "npm:0.9.1" - "@module-federation/sdk": "npm:0.9.1" - fs-extra: "npm:9.1.0" + "@module-federation/dts-plugin": "npm:2.3.3" + "@module-federation/sdk": "npm:2.3.3" + commander: "npm:11.1.0" + jiti: "npm:2.4.2" + bin: + mf: bin/mf.js + checksum: 10/3a2551b697373c149f64e6cfa58466eeb7c13fce971d76bf693d0388ae21e9f014f7e34df2873015e297dbbcff7a3ce7960d9db7b961a7aa85fc6fc0e0f23e08 + languageName: node + linkType: hard + +"@module-federation/data-prefetch@npm:2.3.3": + version: 2.3.3 + resolution: "@module-federation/data-prefetch@npm:2.3.3" + dependencies: + "@module-federation/runtime": "npm:2.3.3" + "@module-federation/sdk": "npm:2.3.3" peerDependencies: react: ">=16.9.0" react-dom: ">=16.9.0" - checksum: 10/a3c0e8d77f4d06e3851d041175ef3f55d47ff4069a526b9ac773c8aba71a520a0490653c87cdcf3761e96f9eb9ea16d45026f3aafdf0851c34ff4db6d71ec113 + peerDependenciesMeta: + react: + optional: true + react-dom: + optional: true + checksum: 10/d9d4fa7e507abba60a76fb2fb09392483bf89e41a06984b59f1872f2d9e29bb5e0b6616415f99ed9dfe59bea5b7ecd14bc023edb5ee7c1ab33f27eb28263f2a8 languageName: node linkType: hard -"@module-federation/dts-plugin@npm:0.9.1": - version: 0.9.1 - resolution: "@module-federation/dts-plugin@npm:0.9.1" +"@module-federation/dts-plugin@npm:2.3.3": + version: 2.3.3 + resolution: "@module-federation/dts-plugin@npm:2.3.3" dependencies: - "@module-federation/error-codes": "npm:0.9.1" - "@module-federation/managers": "npm:0.9.1" - "@module-federation/sdk": "npm:0.9.1" - "@module-federation/third-party-dts-extractor": "npm:0.9.1" - adm-zip: "npm:^0.5.10" - ansi-colors: "npm:^4.1.3" - axios: "npm:^1.7.4" - chalk: "npm:3.0.0" - fs-extra: "npm:9.1.0" + "@module-federation/error-codes": "npm:2.3.3" + "@module-federation/managers": "npm:2.3.3" + "@module-federation/sdk": "npm:2.3.3" + "@module-federation/third-party-dts-extractor": "npm:2.3.3" + adm-zip: "npm:0.5.10" + ansi-colors: "npm:4.1.3" isomorphic-ws: "npm:5.0.0" - koa: "npm:2.15.4" - lodash.clonedeepwith: "npm:4.5.0" - log4js: "npm:6.9.1" node-schedule: "npm:2.1.1" - rambda: "npm:^9.1.0" + undici: "npm:7.24.7" ws: "npm:8.18.0" peerDependencies: typescript: ^4.9.0 || ^5.0.0 @@ -11156,25 +11572,28 @@ __metadata: peerDependenciesMeta: vue-tsc: optional: true - checksum: 10/e9fd11b150456f2621636587d181f6456fe5cd60a0720843fe1310e350c3ff8ebef02ea87d1bf40dc04a4b4cd65cfb2e9007ca1c20e4e5664142b7c670c4a57d + checksum: 10/65d1e3e94e87ff38b89a2e47a0f8284f291ae210a957a926037f98da9c1546935ad754deaf2a9cf87499103ec1e887425e5b3c6b88f4e4f0f5f150ab36995e93 languageName: node linkType: hard -"@module-federation/enhanced@npm:^0.9.0": - version: 0.9.1 - resolution: "@module-federation/enhanced@npm:0.9.1" +"@module-federation/enhanced@npm:^2.3.3": + version: 2.3.3 + resolution: "@module-federation/enhanced@npm:2.3.3" dependencies: - "@module-federation/bridge-react-webpack-plugin": "npm:0.9.1" - "@module-federation/data-prefetch": "npm:0.9.1" - "@module-federation/dts-plugin": "npm:0.9.1" - "@module-federation/error-codes": "npm:0.9.1" - "@module-federation/inject-external-runtime-core-plugin": "npm:0.9.1" - "@module-federation/managers": "npm:0.9.1" - "@module-federation/manifest": "npm:0.9.1" - "@module-federation/rspack": "npm:0.9.1" - "@module-federation/runtime-tools": "npm:0.9.1" - "@module-federation/sdk": "npm:0.9.1" - btoa: "npm:^1.2.1" + "@module-federation/bridge-react-webpack-plugin": "npm:2.3.3" + "@module-federation/cli": "npm:2.3.3" + "@module-federation/data-prefetch": "npm:2.3.3" + "@module-federation/dts-plugin": "npm:2.3.3" + "@module-federation/error-codes": "npm:2.3.3" + "@module-federation/inject-external-runtime-core-plugin": "npm:2.3.3" + "@module-federation/managers": "npm:2.3.3" + "@module-federation/manifest": "npm:2.3.3" + "@module-federation/rspack": "npm:2.3.3" + "@module-federation/runtime-tools": "npm:2.3.3" + "@module-federation/sdk": "npm:2.3.3" + "@module-federation/webpack-bundler-runtime": "npm:2.3.3" + schema-utils: "npm:4.3.0" + tapable: "npm:2.3.0" upath: "npm:2.0.1" peerDependencies: typescript: ^4.9.0 || ^5.0.0 @@ -11187,70 +11606,70 @@ __metadata: optional: true webpack: optional: true - checksum: 10/a7955711f37ba02a18f3570289dfd8fbed30511b82fcf593e56808559cfd49d8852028aa67950db5e45617d0917536d067d85847892e62c20b68ba6ab40e17c4 + bin: + mf: bin/mf.js + checksum: 10/3a211602ae1e928b1174fe63915cd313d0a8b751c497bf80a327f5ff12cad2d262d4ee9ea652b3d95076bb7b90e204131a0834efcf4f69c2e3c89dab3cae196d languageName: node linkType: hard -"@module-federation/error-codes@npm:0.21.4": - version: 0.21.4 - resolution: "@module-federation/error-codes@npm:0.21.4" - checksum: 10/18b0ecdba0de4cd5b202f1f5e5aa76273de0e4513ba91b8e39a2a5b00ff6471f694c88d591624b0913a0c3b4c9a6112da29dc7eebbdc562e5455b591f1d70b31 +"@module-federation/error-codes@npm:0.22.0": + version: 0.22.0 + resolution: "@module-federation/error-codes@npm:0.22.0" + checksum: 10/4edb269e9f3039899f879788c84d2bfecff94ca8e87ffcd80dbf8589d8543ec32558b3fa05c8549a8abd3ac33e856ff2aacf458dea5c0d7bea608bf12bb13359 languageName: node linkType: hard -"@module-federation/error-codes@npm:0.9.1": - version: 0.9.1 - resolution: "@module-federation/error-codes@npm:0.9.1" - checksum: 10/545aecc606a506ee47f061835e0eaa41b8d1b02f6bf71b36ec9ae85a1b0370af1f7b7cf92a8f52c3c4b35da858653244316de5ab06bea5dac5b92995467631cc +"@module-federation/error-codes@npm:2.3.3": + version: 2.3.3 + resolution: "@module-federation/error-codes@npm:2.3.3" + checksum: 10/fa1e30c919b8697bcc59b631da39d2f7a6a8ef095d323912d43b12d8c526c5aac778d962041767f0d247aeefe407361fd32f2fcbe957051845685a71845e9702 languageName: node linkType: hard -"@module-federation/inject-external-runtime-core-plugin@npm:0.9.1": - version: 0.9.1 - resolution: "@module-federation/inject-external-runtime-core-plugin@npm:0.9.1" +"@module-federation/inject-external-runtime-core-plugin@npm:2.3.3": + version: 2.3.3 + resolution: "@module-federation/inject-external-runtime-core-plugin@npm:2.3.3" peerDependencies: - "@module-federation/runtime-tools": 0.9.1 - checksum: 10/931eef6292c278450fc8cdb017073fa0b721796461eee12a254fc60a88a2f17e91395b12371f2c8b3b25b4056fc2dd2b76d1022e679be7353947c3d797e75ea5 + "@module-federation/runtime-tools": 2.3.3 + checksum: 10/36fb6274a3e6983256476c363b8024ca8014921888c44c762748157fb82988be5738e22fce645e34e66262ca4307b2ff36883aabae136c8e19bbde05a3999154 languageName: node linkType: hard -"@module-federation/managers@npm:0.9.1": - version: 0.9.1 - resolution: "@module-federation/managers@npm:0.9.1" +"@module-federation/managers@npm:2.3.3": + version: 2.3.3 + resolution: "@module-federation/managers@npm:2.3.3" dependencies: - "@module-federation/sdk": "npm:0.9.1" + "@module-federation/sdk": "npm:2.3.3" find-pkg: "npm:2.0.0" - fs-extra: "npm:9.1.0" - checksum: 10/32c1666244ba98644ab6eccdc844415d1aece1c105f6ba2ff17a3836866e7cdb2f61e556cb5a2b506b898ec709951f318d397f4d153efeff377067237968462f + checksum: 10/9c21d2718da5664a808047d94e3fd1b35107f6eea48a904f82fccda5d0a30fdd67e6688696aaf773b1e1dacf1c5bc12bb6f53c70d2e1f882fe79160d73c2e4f7 languageName: node linkType: hard -"@module-federation/manifest@npm:0.9.1": - version: 0.9.1 - resolution: "@module-federation/manifest@npm:0.9.1" +"@module-federation/manifest@npm:2.3.3": + version: 2.3.3 + resolution: "@module-federation/manifest@npm:2.3.3" dependencies: - "@module-federation/dts-plugin": "npm:0.9.1" - "@module-federation/managers": "npm:0.9.1" - "@module-federation/sdk": "npm:0.9.1" - chalk: "npm:3.0.0" + "@module-federation/dts-plugin": "npm:2.3.3" + "@module-federation/managers": "npm:2.3.3" + "@module-federation/sdk": "npm:2.3.3" find-pkg: "npm:2.0.0" - checksum: 10/539b86bd5388296fb35a34c7b732b92788600ab6625d53f11588ad67c3a603ac3c1974541d9cf04db2d24635be1610414c29388e849d96bbb812cad3b1c79425 + checksum: 10/0f8a9bf815e27be02c0a79e68dddcbc8a8193f558d67182c1a5220377721ce203b328f1d3ba34b116954e7fab5e4bb910dd339c2743f0a67b744a2d6ee1a25c7 languageName: node linkType: hard -"@module-federation/rspack@npm:0.9.1": - version: 0.9.1 - resolution: "@module-federation/rspack@npm:0.9.1" +"@module-federation/rspack@npm:2.3.3": + version: 2.3.3 + resolution: "@module-federation/rspack@npm:2.3.3" dependencies: - "@module-federation/bridge-react-webpack-plugin": "npm:0.9.1" - "@module-federation/dts-plugin": "npm:0.9.1" - "@module-federation/inject-external-runtime-core-plugin": "npm:0.9.1" - "@module-federation/managers": "npm:0.9.1" - "@module-federation/manifest": "npm:0.9.1" - "@module-federation/runtime-tools": "npm:0.9.1" - "@module-federation/sdk": "npm:0.9.1" + "@module-federation/bridge-react-webpack-plugin": "npm:2.3.3" + "@module-federation/dts-plugin": "npm:2.3.3" + "@module-federation/inject-external-runtime-core-plugin": "npm:2.3.3" + "@module-federation/managers": "npm:2.3.3" + "@module-federation/manifest": "npm:2.3.3" + "@module-federation/runtime-tools": "npm:2.3.3" + "@module-federation/sdk": "npm:2.3.3" peerDependencies: - "@rspack/core": ">=0.7" + "@rspack/core": ^0.7.0 || ^1.0.0 || ^2.0.0-0 typescript: ^4.9.0 || ^5.0.0 vue-tsc: ">=1.0.24" peerDependenciesMeta: @@ -11258,114 +11677,119 @@ __metadata: optional: true vue-tsc: optional: true - checksum: 10/e6deb7236ccc66d7f0475a09916ec714c9855847bfd3d3ec6cdbee991016a222a22f6ae3fd14d71351d55e22e8949140f37ce9ce79dece5416731c6698726096 + checksum: 10/9e3f5eb4ce2086f53e92214e3d694e19ec33de5ff3fe50b9c9bdf89791ffcf63cd8c213da1b4f263b499f5ff6a6609910aa42cfdac3170d5b3407098e81d1011 languageName: node linkType: hard -"@module-federation/runtime-core@npm:0.21.4": - version: 0.21.4 - resolution: "@module-federation/runtime-core@npm:0.21.4" +"@module-federation/runtime-core@npm:0.22.0": + version: 0.22.0 + resolution: "@module-federation/runtime-core@npm:0.22.0" dependencies: - "@module-federation/error-codes": "npm:0.21.4" - "@module-federation/sdk": "npm:0.21.4" - checksum: 10/b90fe4147cd3302ea6b02098ff2bed7437425609ba0eb4d7b649b3643837810239f06f532af092a53f9b47e8fae1aeef84cfd964431fa4ab2fd4a800ad796d23 + "@module-federation/error-codes": "npm:0.22.0" + "@module-federation/sdk": "npm:0.22.0" + checksum: 10/d21969198322b6f79e0513b702d0af5097613d47819724c849b6c677c163cd10fb8c89e3ff62b798bec498ee4d8e95dec71861071bc4ed74bd86a7e43193bc05 languageName: node linkType: hard -"@module-federation/runtime-core@npm:0.9.1": - version: 0.9.1 - resolution: "@module-federation/runtime-core@npm:0.9.1" +"@module-federation/runtime-core@npm:2.3.3": + version: 2.3.3 + resolution: "@module-federation/runtime-core@npm:2.3.3" dependencies: - "@module-federation/error-codes": "npm:0.9.1" - "@module-federation/sdk": "npm:0.9.1" - checksum: 10/6f9edbe23013395d7896fc2a24cb4055bc78df5a335f090e079df951835c1cf91c567228f19879eee3fddb0b34128abd0b50feaca1cf3fb2828c7b9bacc22169 + "@module-federation/error-codes": "npm:2.3.3" + "@module-federation/sdk": "npm:2.3.3" + checksum: 10/3bc3ed55543380a9041962ab7f26232a955a3957bfd8da706c69806c8cf8595e858be6b834b9045a3a2718b5396dd80f191abd244ea9aca09264046f52903b18 languageName: node linkType: hard -"@module-federation/runtime-tools@npm:0.21.4": - version: 0.21.4 - resolution: "@module-federation/runtime-tools@npm:0.21.4" +"@module-federation/runtime-tools@npm:0.22.0": + version: 0.22.0 + resolution: "@module-federation/runtime-tools@npm:0.22.0" dependencies: - "@module-federation/runtime": "npm:0.21.4" - "@module-federation/webpack-bundler-runtime": "npm:0.21.4" - checksum: 10/1e453268122070e5512c1d74cb8b4efb87cd2c1b46daba1736dfee16b2e8332a779f8168dcb3f84e17eade31f965168df63dae487ccfb74b0469c32af1895675 + "@module-federation/runtime": "npm:0.22.0" + "@module-federation/webpack-bundler-runtime": "npm:0.22.0" + checksum: 10/0e7693c1ec02fc5bef770b478c8757cad9cfefb2310d1943151d0ad079b72472d9b2c8a087299e9124dfcd6b649c83290c7fdfa333865baab4ba193f39e7b6bd languageName: node linkType: hard -"@module-federation/runtime-tools@npm:0.9.1": - version: 0.9.1 - resolution: "@module-federation/runtime-tools@npm:0.9.1" +"@module-federation/runtime-tools@npm:2.3.3": + version: 2.3.3 + resolution: "@module-federation/runtime-tools@npm:2.3.3" dependencies: - "@module-federation/runtime": "npm:0.9.1" - "@module-federation/webpack-bundler-runtime": "npm:0.9.1" - checksum: 10/9436e814a4ab72839b8aed1aa097f32cf7d4d49728dd863c9ce6dc4fb149fe49da6dc9cdeb97814f0023cc91489c9aca06cbfdf9fb4e43d20498ab6ee78c95dd + "@module-federation/runtime": "npm:2.3.3" + "@module-federation/webpack-bundler-runtime": "npm:2.3.3" + checksum: 10/edae023ac1df42dafdf47b417c740f6f7a324151a70c57c301b606b47d5fadb67034dcd2f4c7a019e8116a95a288f127c8e36af8ef947a68a597399ae4c1a538 languageName: node linkType: hard -"@module-federation/runtime@npm:0.21.4": - version: 0.21.4 - resolution: "@module-federation/runtime@npm:0.21.4" +"@module-federation/runtime@npm:0.22.0": + version: 0.22.0 + resolution: "@module-federation/runtime@npm:0.22.0" dependencies: - "@module-federation/error-codes": "npm:0.21.4" - "@module-federation/runtime-core": "npm:0.21.4" - "@module-federation/sdk": "npm:0.21.4" - checksum: 10/ae262bfe1643a381e571d7dff459108da3046eea04cc3dae85dce745dd294ef8a30f70098ffa602c266b8c5878c859dd2fcde787773303dac35f77fa6ed32ae4 + "@module-federation/error-codes": "npm:0.22.0" + "@module-federation/runtime-core": "npm:0.22.0" + "@module-federation/sdk": "npm:0.22.0" + checksum: 10/eca608be999d7d2e83abc1169643c2f795a5ed950f9e2bdf7000400a30b3e1e0ca4bdaa5daa09f55e44868383d444707e40236cec1aaa7b40432b0cce800b7f3 languageName: node linkType: hard -"@module-federation/runtime@npm:0.9.1, @module-federation/runtime@npm:^0.9.0": - version: 0.9.1 - resolution: "@module-federation/runtime@npm:0.9.1" +"@module-federation/runtime@npm:2.3.3, @module-federation/runtime@npm:^2.3.3": + version: 2.3.3 + resolution: "@module-federation/runtime@npm:2.3.3" dependencies: - "@module-federation/error-codes": "npm:0.9.1" - "@module-federation/runtime-core": "npm:0.9.1" - "@module-federation/sdk": "npm:0.9.1" - checksum: 10/71eb1c3e81b307ebfe06c43ce70bfa56b217e9dfbed27f0b4235d3d0d05cc0fe2eb1dc57fffb3260ed9e0239257ef117ae13924c642b5ff97d9c65bdf48206fe + "@module-federation/error-codes": "npm:2.3.3" + "@module-federation/runtime-core": "npm:2.3.3" + "@module-federation/sdk": "npm:2.3.3" + checksum: 10/c39b117aa77400dd32394dbdb17028f641c3c2c35d010565dc9d7d0be0ce33c32369173b28b24d82ceee0fea0fc488dc2bb34b9f4efe67b6df39cf601ca3da45 languageName: node linkType: hard -"@module-federation/sdk@npm:0.21.4": - version: 0.21.4 - resolution: "@module-federation/sdk@npm:0.21.4" - checksum: 10/74c9ee2a057babf4f2638f8644a6eee6bd2c76441440dcc3855fb01a0e527e88518b8cc9c2d6d8f6b28858e34e40a3a966c03bb5d42897b9ea9163985edfa159 +"@module-federation/sdk@npm:0.22.0": + version: 0.22.0 + resolution: "@module-federation/sdk@npm:0.22.0" + checksum: 10/d7085d883730a33145052520787a7e59cf9c54b51b2946bebc7c63a6bb668bcc6cbdc27fa0b7354a62f5a7ee4e8829a66b84e644607498f2e37cfd5eb4ded0da languageName: node linkType: hard -"@module-federation/sdk@npm:0.9.1, @module-federation/sdk@npm:^0.9.0": - version: 0.9.1 - resolution: "@module-federation/sdk@npm:0.9.1" - checksum: 10/ea0320feff328a05405e65503d1df28e46a9ad17ef99b77f1428db5c89efbadd2a76ec82d99a54ac1d21286cee6d9ddbba881a9c46748dfe8abdb11e9afef7da +"@module-federation/sdk@npm:2.3.3, @module-federation/sdk@npm:^2.3.3": + version: 2.3.3 + resolution: "@module-federation/sdk@npm:2.3.3" + peerDependencies: + node-fetch: ^3.3.2 + peerDependenciesMeta: + node-fetch: + optional: true + checksum: 10/e4b77a9461f309580561fb4b2cfbc876ae870ea21f37db961117ba572af4f05b41ec5cda84635a2821f39cbfa31469341b09705a9b79b2526b0cc633cffca6fd languageName: node linkType: hard -"@module-federation/third-party-dts-extractor@npm:0.9.1": - version: 0.9.1 - resolution: "@module-federation/third-party-dts-extractor@npm:0.9.1" +"@module-federation/third-party-dts-extractor@npm:2.3.3": + version: 2.3.3 + resolution: "@module-federation/third-party-dts-extractor@npm:2.3.3" dependencies: find-pkg: "npm:2.0.0" - fs-extra: "npm:9.1.0" resolve: "npm:1.22.8" - checksum: 10/68c70c79573cd927212d95879aa7b35490519d0ec9f58dd264c9d61e22fea8d452e45a52a94155128d8378e5cdcaecb229707b5b0f4e4c0d53ae96e2fbac366a + checksum: 10/815bbf77dac817c593349168e3b45e14fd362c481aaba096a9d8d90ceb613be899d0115be4c49de3475dd1c65cca6a6d0664202203f38c1cce0a696e4b58dd1c languageName: node linkType: hard -"@module-federation/webpack-bundler-runtime@npm:0.21.4": - version: 0.21.4 - resolution: "@module-federation/webpack-bundler-runtime@npm:0.21.4" +"@module-federation/webpack-bundler-runtime@npm:0.22.0": + version: 0.22.0 + resolution: "@module-federation/webpack-bundler-runtime@npm:0.22.0" dependencies: - "@module-federation/runtime": "npm:0.21.4" - "@module-federation/sdk": "npm:0.21.4" - checksum: 10/a4f2a7ca7765651023af88f38ded9580b553cd8c6a88bc9056ec4dc58656a3d438f0498750462f424bf3aeaa5a3c7b6fd8189b7c7c76d084736474455838cb55 + "@module-federation/runtime": "npm:0.22.0" + "@module-federation/sdk": "npm:0.22.0" + checksum: 10/afd24406817dfc6474ebcf5be714ccf26690eb3f6f5172bda711c8f23dba149fe47293f7aa2d0733dfed0334c98d4d3d9e7c2da2be78750cae5a72d72f32ce93 languageName: node linkType: hard -"@module-federation/webpack-bundler-runtime@npm:0.9.1": - version: 0.9.1 - resolution: "@module-federation/webpack-bundler-runtime@npm:0.9.1" +"@module-federation/webpack-bundler-runtime@npm:2.3.3": + version: 2.3.3 + resolution: "@module-federation/webpack-bundler-runtime@npm:2.3.3" dependencies: - "@module-federation/runtime": "npm:0.9.1" - "@module-federation/sdk": "npm:0.9.1" - checksum: 10/430cac0a770b3c46bc195088eb4c1892e1e29a69238dbe72423d64b2b67050afeca2b5026b1a30659b4fe8d9faa038ff97cceba7e2ddf6193b93763f270d9df6 + "@module-federation/error-codes": "npm:2.3.3" + "@module-federation/runtime": "npm:2.3.3" + "@module-federation/sdk": "npm:2.3.3" + checksum: 10/2dfddf7338dd499ad2d2a8381d93f3493f348593f502a87e941a210c8bb4cab3eda02b7566e8b2e6357b5a3c6de31cb11f1370443c0ce8b8c2a0f625d1b37b47 languageName: node linkType: hard @@ -11461,8 +11885,8 @@ __metadata: linkType: hard "@mswjs/interceptors@npm:^0.39.1": - version: 0.39.6 - resolution: "@mswjs/interceptors@npm:0.39.6" + version: 0.39.8 + resolution: "@mswjs/interceptors@npm:0.39.8" dependencies: "@open-draft/deferred-promise": "npm:^2.2.0" "@open-draft/logger": "npm:^0.3.0" @@ -11470,26 +11894,26 @@ __metadata: is-node-process: "npm:^1.2.0" outvariant: "npm:^1.4.3" strict-event-emitter: "npm:^0.5.1" - checksum: 10/c87d3edf08353bde825c87b151b24d538070540ab419206cef1774c932e888af0f920183182fb7c94c3eee42068da5a0a5855853fded8514f33c870921ef37ec + checksum: 10/d92546cf9bf670ddb927c53f5fa19f0554b7475a264ead4e1ae2339874f4312fe4ada5d42588f27eea3577bee29fa8f46889d398f0e7ecb3f7a4c1d3e0b71bdc languageName: node linkType: hard -"@mui/core-downloads-tracker@npm:^5.16.14": - version: 5.16.14 - resolution: "@mui/core-downloads-tracker@npm:5.16.14" - checksum: 10/a25658362a69a89f35cdc12ded01b998b7f02df43648029f2523813fc7f259cc85f62bd1877059359d462e7c163e82308bd4cc74fa2d35651d302c5d8bbbc7f4 +"@mui/core-downloads-tracker@npm:^5.18.0": + version: 5.18.0 + resolution: "@mui/core-downloads-tracker@npm:5.18.0" + checksum: 10/065b46739d2bd84b880ad2f6a0a2062d60e3a296ce18ff380cad22ab5b2cb3de396755f322f4bea3a422ffffe1a9244536fc3c9623056ff3873c996e6664b1b9 languageName: node linkType: hard "@mui/material@npm:^5.12.2": - version: 5.16.14 - resolution: "@mui/material@npm:5.16.14" + version: 5.18.0 + resolution: "@mui/material@npm:5.18.0" dependencies: "@babel/runtime": "npm:^7.23.9" - "@mui/core-downloads-tracker": "npm:^5.16.14" - "@mui/system": "npm:^5.16.14" - "@mui/types": "npm:^7.2.15" - "@mui/utils": "npm:^5.16.14" + "@mui/core-downloads-tracker": "npm:^5.18.0" + "@mui/system": "npm:^5.18.0" + "@mui/types": "npm:~7.2.15" + "@mui/utils": "npm:^5.17.1" "@popperjs/core": "npm:^2.11.8" "@types/react-transition-group": "npm:^4.4.10" clsx: "npm:^2.1.0" @@ -11510,16 +11934,16 @@ __metadata: optional: true "@types/react": optional: true - checksum: 10/4fe36ebe4d5f65e420895d114db81c0b8a5061e39bc18cdbebf6204953dae34cdc04af9827b65eb136e5a6853f4500a736ed3d52cce4ea37057a749eca5c3fad + checksum: 10/4b72e07c76c7c4b1076db82ef42a06dfab7d73d73f0d272019b2e0b200fc25c27bb295a8672577e1094168054159bed387cf9af74fec30e98aead7d97fad0a57 languageName: node linkType: hard -"@mui/private-theming@npm:^5.16.14": - version: 5.16.14 - resolution: "@mui/private-theming@npm:5.16.14" +"@mui/private-theming@npm:^5.17.1": + version: 5.17.1 + resolution: "@mui/private-theming@npm:5.17.1" dependencies: "@babel/runtime": "npm:^7.23.9" - "@mui/utils": "npm:^5.16.14" + "@mui/utils": "npm:^5.17.1" prop-types: "npm:^15.8.1" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -11527,16 +11951,17 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10/19cb67ccb7f9702cc2c3de99861607cc9d4109c7df578d39e6cf662f9b0108a2f4a9bf59f6c23c4e5c30a269ad7964ebd7dc2342b7f469fb9abea762a4b00bbc + checksum: 10/f8b849f545e8ab29eac959f174f56702e5b72ffda85c3b0621750e294a3f64d15873ebdb792cf478564db1c3cf4b366eabcd4156897d811949f2df079b424c8c languageName: node linkType: hard -"@mui/styled-engine@npm:^5.16.14": - version: 5.16.14 - resolution: "@mui/styled-engine@npm:5.16.14" +"@mui/styled-engine@npm:^5.18.0": + version: 5.18.0 + resolution: "@mui/styled-engine@npm:5.18.0" dependencies: "@babel/runtime": "npm:^7.23.9" "@emotion/cache": "npm:^11.13.5" + "@emotion/serialize": "npm:^1.3.3" csstype: "npm:^3.1.3" prop-types: "npm:^15.8.1" peerDependencies: @@ -11548,19 +11973,19 @@ __metadata: optional: true "@emotion/styled": optional: true - checksum: 10/d1cf2c713bab684313c6993ce63e12928f88a5033a562fa039dec4d1ce33eef3b94767470979f608b3a993dcb0ed01ef5a5a2dd9c4d4fd80419d989607ba8d75 + checksum: 10/8468a82bafb6dba40b7a3add845dd49868bcbcda3e9a0226a08f74b715dcbe2360186944ef94c44b2abe85f79335a470c0634195b9e48ccf6b5439df4bc17a90 languageName: node linkType: hard "@mui/styles@npm:^5.14.18": - version: 5.16.14 - resolution: "@mui/styles@npm:5.16.14" + version: 5.18.0 + resolution: "@mui/styles@npm:5.18.0" dependencies: "@babel/runtime": "npm:^7.23.9" "@emotion/hash": "npm:^0.9.1" - "@mui/private-theming": "npm:^5.16.14" - "@mui/types": "npm:^7.2.15" - "@mui/utils": "npm:^5.16.14" + "@mui/private-theming": "npm:^5.17.1" + "@mui/types": "npm:~7.2.15" + "@mui/utils": "npm:^5.17.1" clsx: "npm:^2.1.0" csstype: "npm:^3.1.3" hoist-non-react-statics: "npm:^3.3.2" @@ -11579,19 +12004,19 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10/53036720e7cd928c32d23c771679ce155be47e4bd5595a13abe33a4e5cf1378809f9b93609ea6307479e5a02ad82c6ca71699f74fd70c7a7993471c765e2f004 + checksum: 10/b02cc02f7c8eafe0bd5b8bc1121ee0cee50bb48e876b652313aad4c61ae466c411423e8e82f603a386ecf569c537ecfa92b6c7dabd0c798d6727e935df7fa1e6 languageName: node linkType: hard -"@mui/system@npm:^5.16.14": - version: 5.16.14 - resolution: "@mui/system@npm:5.16.14" +"@mui/system@npm:^5.16.14, @mui/system@npm:^5.18.0": + version: 5.18.0 + resolution: "@mui/system@npm:5.18.0" dependencies: "@babel/runtime": "npm:^7.23.9" - "@mui/private-theming": "npm:^5.16.14" - "@mui/styled-engine": "npm:^5.16.14" - "@mui/types": "npm:^7.2.15" - "@mui/utils": "npm:^5.16.14" + "@mui/private-theming": "npm:^5.17.1" + "@mui/styled-engine": "npm:^5.18.0" + "@mui/types": "npm:~7.2.15" + "@mui/utils": "npm:^5.17.1" clsx: "npm:^2.1.0" csstype: "npm:^3.1.3" prop-types: "npm:^15.8.1" @@ -11607,28 +12032,28 @@ __metadata: optional: true "@types/react": optional: true - checksum: 10/71892070ffe1d7b626b894776c395a748d0d8fb37c11bd22f79559d889c7b83fcbb095fab74b930d2a704d3b575720b6be4675473e7a50c92bd86411f6740232 + checksum: 10/4584a4d4f62ddaecc8b047f1a3b24ecde2ea4198963b5db3c006fd8109cd16085099862dbf935fad545ee146a3c06f119e0dd9bdc987cf45f900bab611a4afe7 languageName: node linkType: hard -"@mui/types@npm:^7.2.15": - version: 7.2.19 - resolution: "@mui/types@npm:7.2.19" +"@mui/types@npm:~7.2.15": + version: 7.2.24 + resolution: "@mui/types@npm:7.2.24" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: "@types/react": optional: true - checksum: 10/a23bc280c0722527ce5e264b0dcb44271441e4016eb2285acc1f0d236cf78c73ecc3ec7abba81876c2eadf45b905b55eb26e0e824ea6afc233efce2ef5a34f7d + checksum: 10/5ed4f90ec62c7df901e58b53011bf6b377b48e13b07de9eeb15c7a6f3f759310f0682b64685c7762f660fad6edf4c8e05595313c93810fc63c54270b899b4a75 languageName: node linkType: hard -"@mui/utils@npm:^5.14.15, @mui/utils@npm:^5.16.14": - version: 5.16.14 - resolution: "@mui/utils@npm:5.16.14" +"@mui/utils@npm:^5.14.15, @mui/utils@npm:^5.17.1": + version: 5.17.1 + resolution: "@mui/utils@npm:5.17.1" dependencies: "@babel/runtime": "npm:^7.23.9" - "@mui/types": "npm:^7.2.15" + "@mui/types": "npm:~7.2.15" "@types/prop-types": "npm:^15.7.12" clsx: "npm:^2.1.1" prop-types: "npm:^15.8.1" @@ -11639,7 +12064,7 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10/29bb7ca0a6e9be7bc5ab5505621566ace279fd7a2da149f0937984502d349f2b78dd42f475c5e22b546b1b27d063bd8eb8e92093b0530a814169a535dc250cdc + checksum: 10/26efae9a9f84a817b016a93ab3e3c3d08533947f62b19d4a5f8cd67ebf6932b1f68c4e4ae677dc0d3397ecd1bf1cc8cb47ab83a345bcaa9b4f45c401ec9d3926 languageName: node linkType: hard @@ -11661,6 +12086,28 @@ __metadata: languageName: node linkType: hard +"@napi-rs/wasm-runtime@npm:^0.2.11": + version: 0.2.12 + resolution: "@napi-rs/wasm-runtime@npm:0.2.12" + dependencies: + "@emnapi/core": "npm:^1.4.3" + "@emnapi/runtime": "npm:^1.4.3" + "@tybys/wasm-util": "npm:^0.10.0" + checksum: 10/5fd518182427980c28bc724adf06c5f32f9a8915763ef560b5f7d73607d30cd15ac86d0cbd2eb80d4cfab23fc80d0876d89ca36a9daadcb864bc00917c94187c + languageName: node + linkType: hard + +"@napi-rs/wasm-runtime@npm:^1.1.1": + version: 1.1.1 + resolution: "@napi-rs/wasm-runtime@npm:1.1.1" + dependencies: + "@emnapi/core": "npm:^1.7.1" + "@emnapi/runtime": "npm:^1.7.1" + "@tybys/wasm-util": "npm:^0.10.1" + checksum: 10/080e7f2aefb84e09884d21c650a2cbafdf25bfd2634693791b27e36eec0ddaa3c1656a943f8c913ac75879a0b04e68f8a827897ee655ab54a93169accf05b194 + languageName: node + linkType: hard + "@nestjs/axios@npm:4.0.1": version: 4.0.1 resolution: "@nestjs/axios@npm:4.0.1" @@ -11672,13 +12119,13 @@ __metadata: languageName: node linkType: hard -"@nestjs/common@npm:11.1.6": - version: 11.1.6 - resolution: "@nestjs/common@npm:11.1.6" +"@nestjs/common@npm:11.1.17": + version: 11.1.17 + resolution: "@nestjs/common@npm:11.1.17" dependencies: - file-type: "npm:21.0.0" + file-type: "npm:21.3.2" iterare: "npm:1.2.1" - load-esm: "npm:1.0.2" + load-esm: "npm:1.0.3" tslib: "npm:2.8.1" uid: "npm:2.0.2" peerDependencies: @@ -11691,18 +12138,18 @@ __metadata: optional: true class-validator: optional: true - checksum: 10/bb8a8f040ecaad5ea4938d166afa5e077060372001def3694999bdd8695dd6d69845c4569ade94aecb4f6b625198d3e848270ecc29be7ba5d73aefe5edd76984 + checksum: 10/5cf09b83373ac1090a3f81ffcf34b91237f062b7e48ff37453e7c2d0cb4373e2e8dc33ed13636ff0e944a900ab19508436a76c8a5e73702d57f930ed5a0cc9c2 languageName: node linkType: hard -"@nestjs/core@npm:11.1.6": - version: 11.1.6 - resolution: "@nestjs/core@npm:11.1.6" +"@nestjs/core@npm:11.1.17": + version: 11.1.17 + resolution: "@nestjs/core@npm:11.1.17" dependencies: "@nuxt/opencollective": "npm:0.4.1" fast-safe-stringify: "npm:2.1.1" iterare: "npm:1.2.1" - path-to-regexp: "npm:8.2.0" + path-to-regexp: "npm:8.3.0" tslib: "npm:2.8.1" uid: "npm:2.0.2" peerDependencies: @@ -11719,7 +12166,14 @@ __metadata: optional: true "@nestjs/websockets": optional: true - checksum: 10/d38c32eea96f9a475907e352b1f9f0441bf1bd4de2fa479836930e9670072deb1c1986253da324eb85bbcb884515d90ad2f10e88e49e491ac888b6893cc1faf3 + checksum: 10/287e14b4d4a662071c9ad41d8f864010561108241da4c2b9df2f7d41962a0a9e5be6e5bcdf9b04087955ef62b7ff56f4c892da25827566dbe5bd06a921c39f8a + languageName: node + linkType: hard + +"@noble/hashes@npm:1.4.0": + version: 1.4.0 + resolution: "@noble/hashes@npm:1.4.0" + checksum: 10/e156e65794c473794c52fa9d06baf1eb20903d0d96719530f523cc4450f6c721a957c544796e6efd0197b2296e7cd70efeb312f861465e17940a3e3c7e0febc6 languageName: node linkType: hard @@ -11740,16 +12194,6 @@ __metadata: languageName: node linkType: hard -"@nodelib/fs.scandir@npm:4.0.1": - version: 4.0.1 - resolution: "@nodelib/fs.scandir@npm:4.0.1" - dependencies: - "@nodelib/fs.stat": "npm:4.0.0" - run-parallel: "npm:^1.2.0" - checksum: 10/44b2b2b34e48ca88ee004413f5033db31cd6d5ecf8c7bbef0e33b6672d603f3e23b57d5fbb1bd5f83f8992df58381be6600006d92a903f085e698a37bdfe3c89 - languageName: node - linkType: hard - "@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": version: 2.0.5 resolution: "@nodelib/fs.stat@npm:2.0.5" @@ -11757,23 +12201,6 @@ __metadata: languageName: node linkType: hard -"@nodelib/fs.stat@npm:4.0.0": - version: 4.0.0 - resolution: "@nodelib/fs.stat@npm:4.0.0" - checksum: 10/1f87199fdab938d2ed6f5e10debc006f7965081e2cd147ed3d2333049a030cad1949bd76556a5f5364f062c3e1edcc3d0981189b065336fc92c503ead463f4e1 - languageName: node - linkType: hard - -"@nodelib/fs.walk@npm:3.0.1": - version: 3.0.1 - resolution: "@nodelib/fs.walk@npm:3.0.1" - dependencies: - "@nodelib/fs.scandir": "npm:4.0.1" - fastq: "npm:^1.15.0" - checksum: 10/7b76a0139dec52e3f2a3a0bb4f13dbf72a6b79d8076ec4b5deea9e75bd1b79d7abda53776f93b5aefda9a5e40f0e31f49f6e35bf5460a402f0aee7bcf3b26d85 - languageName: node - linkType: hard - "@nodelib/fs.walk@npm:^1.2.3, @nodelib/fs.walk@npm:^1.2.8": version: 1.2.8 resolution: "@nodelib/fs.walk@npm:1.2.8" @@ -11784,19 +12211,6 @@ __metadata: languageName: node linkType: hard -"@npmcli/agent@npm:^2.0.0": - version: 2.2.0 - resolution: "@npmcli/agent@npm:2.2.0" - dependencies: - agent-base: "npm:^7.1.0" - http-proxy-agent: "npm:^7.0.0" - https-proxy-agent: "npm:^7.0.1" - lru-cache: "npm:^10.0.1" - socks-proxy-agent: "npm:^8.0.1" - checksum: 10/822ea077553cd9cfc5cbd6d92380b0950fcb054a7027cd1b63a33bd0cbb16b0c6626ea75d95ec0e804643c8904472d3361d2da8c2444b1fb02a9b525d9c07c41 - languageName: node - linkType: hard - "@npmcli/agent@npm:^3.0.0": version: 3.0.0 resolution: "@npmcli/agent@npm:3.0.0" @@ -11810,6 +12224,19 @@ __metadata: languageName: node linkType: hard +"@npmcli/agent@npm:^4.0.0": + version: 4.0.0 + resolution: "@npmcli/agent@npm:4.0.0" + dependencies: + agent-base: "npm:^7.1.0" + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.1" + lru-cache: "npm:^11.2.1" + socks-proxy-agent: "npm:^8.0.3" + checksum: 10/1a81573becc60515031accc696e6405e9b894e65c12b98ef4aeee03b5617c41948633159dbf6caf5dde5b47367eeb749bdc7b7dfb21960930a9060a935c6f636 + languageName: node + linkType: hard + "@npmcli/arborist@npm:^4.0.4": version: 4.3.1 resolution: "@npmcli/arborist@npm:4.3.1" @@ -11872,15 +12299,6 @@ __metadata: languageName: node linkType: hard -"@npmcli/fs@npm:^3.1.0": - version: 3.1.0 - resolution: "@npmcli/fs@npm:3.1.0" - dependencies: - semver: "npm:^7.3.5" - checksum: 10/f3a7ab3a31de65e42aeb6ed03ed035ef123d2de7af4deb9d4a003d27acc8618b57d9fb9d259fe6c28ca538032a028f37337264388ba27d26d37fff7dde22476e - languageName: node - linkType: hard - "@npmcli/fs@npm:^4.0.0": version: 4.0.0 resolution: "@npmcli/fs@npm:4.0.0" @@ -11890,6 +12308,15 @@ __metadata: languageName: node linkType: hard +"@npmcli/fs@npm:^5.0.0": + version: 5.0.0 + resolution: "@npmcli/fs@npm:5.0.0" + dependencies: + semver: "npm:^7.3.5" + checksum: 10/4935c7719d17830d0f9fa46c50be17b2a3c945cec61760f6d0909bce47677c42e1810ca673305890f9e84f008ec4d8e841182f371e42100a8159d15f22249208 + languageName: node + linkType: hard + "@npmcli/git@npm:^2.1.0": version: 2.1.0 resolution: "@npmcli/git@npm:2.1.0" @@ -12675,7 +13102,7 @@ __metadata: languageName: node linkType: hard -"@octokit/webhooks-types@npm:7.6.1": +"@octokit/webhooks-types@npm:7.6.1, @octokit/webhooks-types@npm:^7.6.1": version: 7.6.1 resolution: "@octokit/webhooks-types@npm:7.6.1" checksum: 10/0b11bd7e8d13b5a9cf14214421298a423d0180a5e1aaaea876ee4db6f97b5cca536f48d89af63105db75419d777a2402733eb0e110002d4dd59581ef36037bdc @@ -12747,29 +13174,29 @@ __metadata: linkType: hard "@openapitools/openapi-generator-cli@npm:^2.4.26, @openapitools/openapi-generator-cli@npm:^2.7.0": - version: 2.25.0 - resolution: "@openapitools/openapi-generator-cli@npm:2.25.0" + version: 2.31.0 + resolution: "@openapitools/openapi-generator-cli@npm:2.31.0" dependencies: + "@inquirer/select": "npm:1.3.3" "@nestjs/axios": "npm:4.0.1" - "@nestjs/common": "npm:11.1.6" - "@nestjs/core": "npm:11.1.6" + "@nestjs/common": "npm:11.1.17" + "@nestjs/core": "npm:11.1.17" "@nuxtjs/opencollective": "npm:0.3.2" - axios: "npm:1.12.2" + axios: "npm:^1.13.6" chalk: "npm:4.1.2" commander: "npm:8.3.0" compare-versions: "npm:6.1.1" concurrently: "npm:9.2.1" console.table: "npm:0.10.0" - fs-extra: "npm:11.3.2" - glob: "npm:11.0.3" - inquirer: "npm:8.2.7" + fs-extra: "npm:11.3.4" + glob: "npm:13.0.6" proxy-agent: "npm:6.5.0" reflect-metadata: "npm:0.2.2" rxjs: "npm:7.8.2" tslib: "npm:2.8.1" bin: openapi-generator-cli: main.js - checksum: 10/6456debc95accaded1a85517634fc9cc61bd517ebe45d45f6a138de8d1fe11e100ec5a09c1e3607acb7469398625169fe52035550f8e32e61893cd9c537b402c + checksum: 10/8b09cd463eaf748f65a8a5cd93b07f507e945e1389192f5f169db8e42baf5a207e6f3cfa47593dc2c77b2af3981370744261b757b95edbe19f16e5e619946b82 languageName: node linkType: hard @@ -12787,21 +13214,12 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/api-logs@npm:0.202.0, @opentelemetry/api-logs@npm:^0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/api-logs@npm:0.202.0" +"@opentelemetry/api-logs@npm:0.213.0, @opentelemetry/api-logs@npm:^0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/api-logs@npm:0.213.0" dependencies: "@opentelemetry/api": "npm:^1.3.0" - checksum: 10/22171137ad1d876a79a6f046b4adcc44a13941a07f2172948e4c8dbb6cacfe276b1fa5087f15e4acdd567f748010d531321c93ebf93a6c09586351c9048bf6c7 - languageName: node - linkType: hard - -"@opentelemetry/api-logs@npm:0.54.2": - version: 0.54.2 - resolution: "@opentelemetry/api-logs@npm:0.54.2" - dependencies: - "@opentelemetry/api": "npm:^1.3.0" - checksum: 10/97d887be03ca4a2e69574cc9160464bda00f2a167cc850656ade44b6690a75855d9334983b73827dc44c3672958bc478197f261eae11c2ac68a6df9260c9c3df + checksum: 10/9b2d030d8534520f23e3903ccf64dc479fb4d37fcf5ca265ca7de439ec8dd5c849aaa62068278cd97879da7c9528e34c7162cc7bbd025dd8d74667843adc151f languageName: node linkType: hard @@ -12812,102 +13230,95 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/auto-instrumentations-node@npm:^0.61.0": - version: 0.61.0 - resolution: "@opentelemetry/auto-instrumentations-node@npm:0.61.0" +"@opentelemetry/auto-instrumentations-node@npm:^0.71.0": + version: 0.71.0 + resolution: "@opentelemetry/auto-instrumentations-node@npm:0.71.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/instrumentation-amqplib": "npm:^0.49.0" - "@opentelemetry/instrumentation-aws-lambda": "npm:^0.53.1" - "@opentelemetry/instrumentation-aws-sdk": "npm:^0.55.0" - "@opentelemetry/instrumentation-bunyan": "npm:^0.48.0" - "@opentelemetry/instrumentation-cassandra-driver": "npm:^0.48.0" - "@opentelemetry/instrumentation-connect": "npm:^0.46.0" - "@opentelemetry/instrumentation-cucumber": "npm:^0.17.1" - "@opentelemetry/instrumentation-dataloader": "npm:^0.20.0" - "@opentelemetry/instrumentation-dns": "npm:^0.46.0" - "@opentelemetry/instrumentation-express": "npm:^0.51.1" - "@opentelemetry/instrumentation-fastify": "npm:^0.47.1" - "@opentelemetry/instrumentation-fs": "npm:^0.22.0" - "@opentelemetry/instrumentation-generic-pool": "npm:^0.46.1" - "@opentelemetry/instrumentation-graphql": "npm:^0.50.0" - "@opentelemetry/instrumentation-grpc": "npm:^0.202.0" - "@opentelemetry/instrumentation-hapi": "npm:^0.49.0" - "@opentelemetry/instrumentation-http": "npm:^0.202.0" - "@opentelemetry/instrumentation-ioredis": "npm:^0.50.1" - "@opentelemetry/instrumentation-kafkajs": "npm:^0.11.0" - "@opentelemetry/instrumentation-knex": "npm:^0.47.0" - "@opentelemetry/instrumentation-koa": "npm:^0.50.2" - "@opentelemetry/instrumentation-lru-memoizer": "npm:^0.47.0" - "@opentelemetry/instrumentation-memcached": "npm:^0.46.0" - "@opentelemetry/instrumentation-mongodb": "npm:^0.55.1" - "@opentelemetry/instrumentation-mongoose": "npm:^0.49.0" - "@opentelemetry/instrumentation-mysql": "npm:^0.48.1" - "@opentelemetry/instrumentation-mysql2": "npm:^0.48.1" - "@opentelemetry/instrumentation-nestjs-core": "npm:^0.48.1" - "@opentelemetry/instrumentation-net": "npm:^0.46.1" - "@opentelemetry/instrumentation-oracledb": "npm:^0.28.0" - "@opentelemetry/instrumentation-pg": "npm:^0.54.1" - "@opentelemetry/instrumentation-pino": "npm:^0.49.1" - "@opentelemetry/instrumentation-redis": "npm:^0.50.0" - "@opentelemetry/instrumentation-restify": "npm:^0.48.2" - "@opentelemetry/instrumentation-router": "npm:^0.47.0" - "@opentelemetry/instrumentation-runtime-node": "npm:^0.16.0" - "@opentelemetry/instrumentation-socket.io": "npm:^0.49.0" - "@opentelemetry/instrumentation-tedious": "npm:^0.21.1" - "@opentelemetry/instrumentation-undici": "npm:^0.13.2" - "@opentelemetry/instrumentation-winston": "npm:^0.47.0" - "@opentelemetry/resource-detector-alibaba-cloud": "npm:^0.31.2" - "@opentelemetry/resource-detector-aws": "npm:^2.2.0" - "@opentelemetry/resource-detector-azure": "npm:^0.9.0" - "@opentelemetry/resource-detector-container": "npm:^0.7.2" - "@opentelemetry/resource-detector-gcp": "npm:^0.36.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" + "@opentelemetry/instrumentation-amqplib": "npm:^0.60.0" + "@opentelemetry/instrumentation-aws-lambda": "npm:^0.65.0" + "@opentelemetry/instrumentation-aws-sdk": "npm:^0.68.0" + "@opentelemetry/instrumentation-bunyan": "npm:^0.58.0" + "@opentelemetry/instrumentation-cassandra-driver": "npm:^0.58.0" + "@opentelemetry/instrumentation-connect": "npm:^0.56.0" + "@opentelemetry/instrumentation-cucumber": "npm:^0.29.0" + "@opentelemetry/instrumentation-dataloader": "npm:^0.30.0" + "@opentelemetry/instrumentation-dns": "npm:^0.56.0" + "@opentelemetry/instrumentation-express": "npm:^0.61.0" + "@opentelemetry/instrumentation-fastify": "npm:^0.57.0" + "@opentelemetry/instrumentation-fs": "npm:^0.32.0" + "@opentelemetry/instrumentation-generic-pool": "npm:^0.56.0" + "@opentelemetry/instrumentation-graphql": "npm:^0.61.0" + "@opentelemetry/instrumentation-grpc": "npm:^0.213.0" + "@opentelemetry/instrumentation-hapi": "npm:^0.59.0" + "@opentelemetry/instrumentation-http": "npm:^0.213.0" + "@opentelemetry/instrumentation-ioredis": "npm:^0.61.0" + "@opentelemetry/instrumentation-kafkajs": "npm:^0.22.0" + "@opentelemetry/instrumentation-knex": "npm:^0.57.0" + "@opentelemetry/instrumentation-koa": "npm:^0.61.0" + "@opentelemetry/instrumentation-lru-memoizer": "npm:^0.57.0" + "@opentelemetry/instrumentation-memcached": "npm:^0.56.0" + "@opentelemetry/instrumentation-mongodb": "npm:^0.66.0" + "@opentelemetry/instrumentation-mongoose": "npm:^0.59.0" + "@opentelemetry/instrumentation-mysql": "npm:^0.59.0" + "@opentelemetry/instrumentation-mysql2": "npm:^0.59.0" + "@opentelemetry/instrumentation-nestjs-core": "npm:^0.59.0" + "@opentelemetry/instrumentation-net": "npm:^0.57.0" + "@opentelemetry/instrumentation-openai": "npm:^0.11.0" + "@opentelemetry/instrumentation-oracledb": "npm:^0.38.0" + "@opentelemetry/instrumentation-pg": "npm:^0.65.0" + "@opentelemetry/instrumentation-pino": "npm:^0.59.0" + "@opentelemetry/instrumentation-redis": "npm:^0.61.0" + "@opentelemetry/instrumentation-restify": "npm:^0.58.0" + "@opentelemetry/instrumentation-router": "npm:^0.57.0" + "@opentelemetry/instrumentation-runtime-node": "npm:^0.26.0" + "@opentelemetry/instrumentation-socket.io": "npm:^0.60.0" + "@opentelemetry/instrumentation-tedious": "npm:^0.32.0" + "@opentelemetry/instrumentation-undici": "npm:^0.23.0" + "@opentelemetry/instrumentation-winston": "npm:^0.57.0" + "@opentelemetry/resource-detector-alibaba-cloud": "npm:^0.33.3" + "@opentelemetry/resource-detector-aws": "npm:^2.13.0" + "@opentelemetry/resource-detector-azure": "npm:^0.21.0" + "@opentelemetry/resource-detector-container": "npm:^0.8.4" + "@opentelemetry/resource-detector-gcp": "npm:^0.48.0" "@opentelemetry/resources": "npm:^2.0.0" - "@opentelemetry/sdk-node": "npm:^0.202.0" + "@opentelemetry/sdk-node": "npm:^0.213.0" peerDependencies: "@opentelemetry/api": ^1.4.1 "@opentelemetry/core": ^2.0.0 - checksum: 10/845728e050aa40f501ca3c67ad10ef64859413cb3af6926c6e64dbec206dc4ee7c8cff4d93df95345cebf8970427a03665032f97941a6a1153699f0fbbac1a6f + checksum: 10/a31c3a9118b9cdf8e9a44f9a26c2c4d3bebc9f3c23f3f7bb37a6de0d9ab93bf3f1f46ddc4862998ac9cf11c6a4d169a05dfb1a5b02984335bfba5b6348122c3d languageName: node linkType: hard -"@opentelemetry/context-async-hooks@npm:1.27.0": - version: 1.27.0 - resolution: "@opentelemetry/context-async-hooks@npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/a72fdf5754f6e6d829b81031afe1a8e48a66bb02b13014e05c3fbb9c31fc736f7d303b0bb3491d200ce951582fe04a2d1c6246359683e8fe1b544929d5fd16c5 - languageName: node - linkType: hard - -"@opentelemetry/context-async-hooks@npm:2.0.1": - version: 2.0.1 - resolution: "@opentelemetry/context-async-hooks@npm:2.0.1" - peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/198dacdce36377f6ded7062eb9fc77f86c9fcc8c86dd395e9cb276e14a01c7906ea2f0271999cae21bf251dbec44641286b8bd34a39c67c88c08ed925a986f4b - languageName: node - linkType: hard - -"@opentelemetry/core@npm:1.27.0": - version: 1.27.0 - resolution: "@opentelemetry/core@npm:1.27.0" +"@opentelemetry/configuration@npm:0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/configuration@npm:0.213.0" dependencies: - "@opentelemetry/semantic-conventions": "npm:1.27.0" + "@opentelemetry/core": "npm:2.6.0" + yaml: "npm:^2.0.0" peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/2e64f35f7f8a53c035eb7e2335c73a6bca0f12a0d45cd8171646492d5efb73f82fb29aae77f34b2d6e93498b38172dee8e5cf769727c44ac08be0d5b21da7512 + "@opentelemetry/api": ^1.9.0 + checksum: 10/2bf7ef3a73849d6c618cf04f469009cf46c004df43c014e2f2a60b17692b85eec261fb362f1d4d7276fd128681d35ac07493cb4ef743505fec5d6cbff1a446e2 languageName: node linkType: hard -"@opentelemetry/core@npm:2.0.1, @opentelemetry/core@npm:^2.0.0": - version: 2.0.1 - resolution: "@opentelemetry/core@npm:2.0.1" +"@opentelemetry/context-async-hooks@npm:2.6.0": + version: 2.6.0 + resolution: "@opentelemetry/context-async-hooks@npm:2.6.0" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/a1f746fb9bb25b4c40c0da4cc68a7412e82f120a6ddc80dcf0117432418e64c947527bca87895ebce4211a09992863a75a0f5b5f8e7185a573244cccb4809c42 + languageName: node + linkType: hard + +"@opentelemetry/core@npm:2.6.0, @opentelemetry/core@npm:^2.0.0": + version: 2.6.0 + resolution: "@opentelemetry/core@npm:2.6.0" dependencies: "@opentelemetry/semantic-conventions": "npm:^1.29.0" peerDependencies: "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/dd891afd427067a9e6c610c36ab5638b0b9e5303ccca7c75ad744f5db53c6162a4b5d9cd2f5a77cdc3e4bda2eae850a4e29983ea244c929b7b872b7e086fc61c + checksum: 10/21c017cc68fe7836d06ecac31abeba6ad610dd42e9c1cb9562cd13eed3f644c48111a1fc7d00dfc2b7cc179d40f059482c69c978c24352ac0c605f30686a01a4 languageName: node linkType: hard @@ -12922,1230 +13333,934 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/exporter-logs-otlp-grpc@npm:0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/exporter-logs-otlp-grpc@npm:0.202.0" +"@opentelemetry/exporter-logs-otlp-grpc@npm:0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/exporter-logs-otlp-grpc@npm:0.213.0" dependencies: - "@grpc/grpc-js": "npm:^1.7.1" - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/otlp-exporter-base": "npm:0.202.0" - "@opentelemetry/otlp-grpc-exporter-base": "npm:0.202.0" - "@opentelemetry/otlp-transformer": "npm:0.202.0" - "@opentelemetry/sdk-logs": "npm:0.202.0" + "@grpc/grpc-js": "npm:^1.14.3" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/otlp-exporter-base": "npm:0.213.0" + "@opentelemetry/otlp-grpc-exporter-base": "npm:0.213.0" + "@opentelemetry/otlp-transformer": "npm:0.213.0" + "@opentelemetry/sdk-logs": "npm:0.213.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/b293a3778b6d5b1fe07b9203fe83d0dde3ea57e48efec8d4ccfc27d007fdb5cdafe7446ff10ea3eca69eb2fba8bdb97546a7dfcfc21b73d303e94e15f917b253 + checksum: 10/5c852a8dbf9f1fc00b8af22689a1971af8db6a4d1c3ddcce516877a07b5b40c28dbb6c71549a73c8e8953db3602a8671666f22d1f0aca40fa1f0c1fbbbfbff33 languageName: node linkType: hard -"@opentelemetry/exporter-logs-otlp-grpc@npm:0.54.2": - version: 0.54.2 - resolution: "@opentelemetry/exporter-logs-otlp-grpc@npm:0.54.2" +"@opentelemetry/exporter-logs-otlp-http@npm:0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/exporter-logs-otlp-http@npm:0.213.0" dependencies: - "@grpc/grpc-js": "npm:^1.7.1" - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/otlp-grpc-exporter-base": "npm:0.54.2" - "@opentelemetry/otlp-transformer": "npm:0.54.2" - "@opentelemetry/sdk-logs": "npm:0.54.2" + "@opentelemetry/api-logs": "npm:0.213.0" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/otlp-exporter-base": "npm:0.213.0" + "@opentelemetry/otlp-transformer": "npm:0.213.0" + "@opentelemetry/sdk-logs": "npm:0.213.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/eacb27329ad5a8154f0f075e20de146cd18b673dfc3db88596678f95afa8939bc853e98e397a817dcf4e5cc024ff7e53320041576b2ce7c0dffd1067e7229b9e + checksum: 10/f9ee8f8d3d7113b96416c658ba17c9183e23365ba1ee1f3e3124a435f0d989367b6569cce201defcc295650af22ffbe766614704f19c5361caf4a058817184b6 languageName: node linkType: hard -"@opentelemetry/exporter-logs-otlp-http@npm:0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/exporter-logs-otlp-http@npm:0.202.0" +"@opentelemetry/exporter-logs-otlp-proto@npm:0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/exporter-logs-otlp-proto@npm:0.213.0" dependencies: - "@opentelemetry/api-logs": "npm:0.202.0" - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/otlp-exporter-base": "npm:0.202.0" - "@opentelemetry/otlp-transformer": "npm:0.202.0" - "@opentelemetry/sdk-logs": "npm:0.202.0" + "@opentelemetry/api-logs": "npm:0.213.0" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/otlp-exporter-base": "npm:0.213.0" + "@opentelemetry/otlp-transformer": "npm:0.213.0" + "@opentelemetry/resources": "npm:2.6.0" + "@opentelemetry/sdk-logs": "npm:0.213.0" + "@opentelemetry/sdk-trace-base": "npm:2.6.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/850be5f7a2b0468887357845ffb7f8ca53dfedcd8fc79fbd4d8825cabbdc9a27be107e7aecb6b21ba544cead796e13f116396fcc66ba58954e47ac8fc1eaf270 + checksum: 10/4229f62eb802da49e75820188f77b12c3fdde4f0522e1df01cc097307baba8b141587d78695edd71343f40e811e946909f5c648436edfeef6b03e367e340b7e6 languageName: node linkType: hard -"@opentelemetry/exporter-logs-otlp-http@npm:0.54.2": - version: 0.54.2 - resolution: "@opentelemetry/exporter-logs-otlp-http@npm:0.54.2" +"@opentelemetry/exporter-metrics-otlp-grpc@npm:0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/exporter-metrics-otlp-grpc@npm:0.213.0" dependencies: - "@opentelemetry/api-logs": "npm:0.54.2" - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/otlp-exporter-base": "npm:0.54.2" - "@opentelemetry/otlp-transformer": "npm:0.54.2" - "@opentelemetry/sdk-logs": "npm:0.54.2" + "@grpc/grpc-js": "npm:^1.14.3" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/exporter-metrics-otlp-http": "npm:0.213.0" + "@opentelemetry/otlp-exporter-base": "npm:0.213.0" + "@opentelemetry/otlp-grpc-exporter-base": "npm:0.213.0" + "@opentelemetry/otlp-transformer": "npm:0.213.0" + "@opentelemetry/resources": "npm:2.6.0" + "@opentelemetry/sdk-metrics": "npm:2.6.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/88e99302310cef38002c5cc2c682197b8bc5bee5a1dd139d7a9fb5b2645464071e86a4f55607dbf1afae5a4d91c41ddfc982e7230c7ed6e4667e5709b7a9e3c0 + checksum: 10/b1914ec0a2017312b69320214f09c3c2319baf4da5da3377c2d9baf6ec85ebaf7c4cb6ea1d129224bf40f8fb8225072b8fbfa089524ab7383eda0251d4f4c724 languageName: node linkType: hard -"@opentelemetry/exporter-logs-otlp-proto@npm:0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/exporter-logs-otlp-proto@npm:0.202.0" +"@opentelemetry/exporter-metrics-otlp-http@npm:0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/exporter-metrics-otlp-http@npm:0.213.0" dependencies: - "@opentelemetry/api-logs": "npm:0.202.0" - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/otlp-exporter-base": "npm:0.202.0" - "@opentelemetry/otlp-transformer": "npm:0.202.0" - "@opentelemetry/resources": "npm:2.0.1" - "@opentelemetry/sdk-logs": "npm:0.202.0" - "@opentelemetry/sdk-trace-base": "npm:2.0.1" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/otlp-exporter-base": "npm:0.213.0" + "@opentelemetry/otlp-transformer": "npm:0.213.0" + "@opentelemetry/resources": "npm:2.6.0" + "@opentelemetry/sdk-metrics": "npm:2.6.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/b69e19a035ae7d2d3f99f5ef40760e9f874685de9679b09a143ae9c120a248d9b5995eeec869ebeed6df1a4ebb4e312b5a569f4189410ad02e6ec4438f8fd63f + checksum: 10/1cbb059b2b3d7b1a183c5ce1b4aba1a7a7e43dd6983af30191be19f406930914fc666dc20e0f8db4422cf2a83b0c6416612663e74c5cfedd8e2487fbd20257ea languageName: node linkType: hard -"@opentelemetry/exporter-logs-otlp-proto@npm:0.54.2": - version: 0.54.2 - resolution: "@opentelemetry/exporter-logs-otlp-proto@npm:0.54.2" +"@opentelemetry/exporter-metrics-otlp-proto@npm:0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/exporter-metrics-otlp-proto@npm:0.213.0" dependencies: - "@opentelemetry/api-logs": "npm:0.54.2" - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/otlp-exporter-base": "npm:0.54.2" - "@opentelemetry/otlp-transformer": "npm:0.54.2" - "@opentelemetry/resources": "npm:1.27.0" - "@opentelemetry/sdk-logs": "npm:0.54.2" - "@opentelemetry/sdk-trace-base": "npm:1.27.0" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/exporter-metrics-otlp-http": "npm:0.213.0" + "@opentelemetry/otlp-exporter-base": "npm:0.213.0" + "@opentelemetry/otlp-transformer": "npm:0.213.0" + "@opentelemetry/resources": "npm:2.6.0" + "@opentelemetry/sdk-metrics": "npm:2.6.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/2b2d6bfe1d0799d0f96fac5a15712c8dd3ef132380ee1856d03672c54802fa9f7664a0897310de708cd42942f916d0fdf20916cf4c5459ef7148db836cd5b1f2 + checksum: 10/c1d9d94cf2ccf4daf495046f2d00675985fcfe02f1ed5db538c9ea040123cae767c7ae29f470dca138bcbe7dbea2c92f9accd8e4e27f540792c6f411169c7bb1 languageName: node linkType: hard -"@opentelemetry/exporter-metrics-otlp-grpc@npm:0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/exporter-metrics-otlp-grpc@npm:0.202.0" +"@opentelemetry/exporter-prometheus@npm:0.213.0, @opentelemetry/exporter-prometheus@npm:^0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/exporter-prometheus@npm:0.213.0" dependencies: - "@grpc/grpc-js": "npm:^1.7.1" - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/exporter-metrics-otlp-http": "npm:0.202.0" - "@opentelemetry/otlp-exporter-base": "npm:0.202.0" - "@opentelemetry/otlp-grpc-exporter-base": "npm:0.202.0" - "@opentelemetry/otlp-transformer": "npm:0.202.0" - "@opentelemetry/resources": "npm:2.0.1" - "@opentelemetry/sdk-metrics": "npm:2.0.1" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/resources": "npm:2.6.0" + "@opentelemetry/sdk-metrics": "npm:2.6.0" + "@opentelemetry/semantic-conventions": "npm:^1.29.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/383e6eee1016dbdfe78ed8c96fb6e8e314e0103a11353373f794637594b2b935efc5707eee33712a4ed242b12c808a09584105db1b2e2deab477d80e3d2d06ed + checksum: 10/037631feebd40b1866f1eafd0e16fa09b11da5ab869a744768aad4defba26db1c03e1d1e84c806c478123719ec2e7390b41e31e3049a3a4ad8d5dafbc51e34af languageName: node linkType: hard -"@opentelemetry/exporter-metrics-otlp-http@npm:0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/exporter-metrics-otlp-http@npm:0.202.0" +"@opentelemetry/exporter-trace-otlp-grpc@npm:0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/exporter-trace-otlp-grpc@npm:0.213.0" dependencies: - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/otlp-exporter-base": "npm:0.202.0" - "@opentelemetry/otlp-transformer": "npm:0.202.0" - "@opentelemetry/resources": "npm:2.0.1" - "@opentelemetry/sdk-metrics": "npm:2.0.1" + "@grpc/grpc-js": "npm:^1.14.3" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/otlp-exporter-base": "npm:0.213.0" + "@opentelemetry/otlp-grpc-exporter-base": "npm:0.213.0" + "@opentelemetry/otlp-transformer": "npm:0.213.0" + "@opentelemetry/resources": "npm:2.6.0" + "@opentelemetry/sdk-trace-base": "npm:2.6.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/ed694b8369bb2f979f0aa3552716b267060e5d8d7853fd645e0ae776888091a08c079e9f1e88d8f7eb93d9fe31f0fbb887ebb9469177c2ec2af16c247f8faf5d + checksum: 10/f3b7e98f28427f2459c635581f659bc9b1494620001a23d2ef5ee1b48e4f6730a5fae7d958692aa24445ea084b9938e9bc6ea2ca24bde6ae75419005e5d4d49a languageName: node linkType: hard -"@opentelemetry/exporter-metrics-otlp-proto@npm:0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/exporter-metrics-otlp-proto@npm:0.202.0" +"@opentelemetry/exporter-trace-otlp-http@npm:0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/exporter-trace-otlp-http@npm:0.213.0" dependencies: - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/exporter-metrics-otlp-http": "npm:0.202.0" - "@opentelemetry/otlp-exporter-base": "npm:0.202.0" - "@opentelemetry/otlp-transformer": "npm:0.202.0" - "@opentelemetry/resources": "npm:2.0.1" - "@opentelemetry/sdk-metrics": "npm:2.0.1" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/otlp-exporter-base": "npm:0.213.0" + "@opentelemetry/otlp-transformer": "npm:0.213.0" + "@opentelemetry/resources": "npm:2.6.0" + "@opentelemetry/sdk-trace-base": "npm:2.6.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/22ecc54c4a19a36d5f6ba8cc061e25fba6ff9f2bbaff9a84f8c1e7e5253b2b0dc07ac18e21884eafcb31e6693ce59912baeb19c722a07ad4da9f22e2a6f94549 + checksum: 10/e8f6ad2eee843625e9e1efd9e47b1325dde1e3e9e7a4e2cfe4f5a6bece4e2e422431270b4530a87383c36d365f65a2ebee6ca5bab797d86ec585407dd3cd675f languageName: node linkType: hard -"@opentelemetry/exporter-prometheus@npm:0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/exporter-prometheus@npm:0.202.0" +"@opentelemetry/exporter-trace-otlp-proto@npm:0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/exporter-trace-otlp-proto@npm:0.213.0" dependencies: - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/resources": "npm:2.0.1" - "@opentelemetry/sdk-metrics": "npm:2.0.1" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/otlp-exporter-base": "npm:0.213.0" + "@opentelemetry/otlp-transformer": "npm:0.213.0" + "@opentelemetry/resources": "npm:2.6.0" + "@opentelemetry/sdk-trace-base": "npm:2.6.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/e6cf4dfdc3bd6ccd402a00e0cb1bd2fa5cc4c813a86b23d27bf851e38a22788dac234c10d643a1506acf10415b1cba9bd68da7554358ba32a0ad8b978bc2d467 + checksum: 10/839d7670e1598a25e5a76bf066cdd0951b5eff794f3f1393b1eb32a7d4b9e3b7a53cdadfd5279a0631dacff7a2a9c034de21562a65a4520b7493e5f07409891f languageName: node linkType: hard -"@opentelemetry/exporter-prometheus@npm:^0.54.0": - version: 0.54.2 - resolution: "@opentelemetry/exporter-prometheus@npm:0.54.2" +"@opentelemetry/exporter-zipkin@npm:2.6.0": + version: 2.6.0 + resolution: "@opentelemetry/exporter-zipkin@npm:2.6.0" dependencies: - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/resources": "npm:1.27.0" - "@opentelemetry/sdk-metrics": "npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/1e2e549ad0b0659c92e5ba7b4fff58754f0199f01d968e5031f4bb6b6ef83c4791f7c74527408ed4661e68908ee728c27ab041c03d62bf2ed16398a7e646afb0 - languageName: node - linkType: hard - -"@opentelemetry/exporter-trace-otlp-grpc@npm:0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/exporter-trace-otlp-grpc@npm:0.202.0" - dependencies: - "@grpc/grpc-js": "npm:^1.7.1" - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/otlp-exporter-base": "npm:0.202.0" - "@opentelemetry/otlp-grpc-exporter-base": "npm:0.202.0" - "@opentelemetry/otlp-transformer": "npm:0.202.0" - "@opentelemetry/resources": "npm:2.0.1" - "@opentelemetry/sdk-trace-base": "npm:2.0.1" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/28cd0d2a0d3ef08cd7830b044690c5e6d86eb36f0d1298d9cd921f89c382cfbd3f80272c9fb8c12ece7eb417a48f852f6b3c299c21b0eba5d340d244cd41e6e2 - languageName: node - linkType: hard - -"@opentelemetry/exporter-trace-otlp-grpc@npm:0.54.2": - version: 0.54.2 - resolution: "@opentelemetry/exporter-trace-otlp-grpc@npm:0.54.2" - dependencies: - "@grpc/grpc-js": "npm:^1.7.1" - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/otlp-grpc-exporter-base": "npm:0.54.2" - "@opentelemetry/otlp-transformer": "npm:0.54.2" - "@opentelemetry/resources": "npm:1.27.0" - "@opentelemetry/sdk-trace-base": "npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/4fd0f50f1faca2ddc8167420517b0f03760fe113e3240fcfc1324d2e0779065b92f2f08775cae020e7a51bf80d202328e44159e89830b160f41e7c1b507debaf - languageName: node - linkType: hard - -"@opentelemetry/exporter-trace-otlp-http@npm:0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/exporter-trace-otlp-http@npm:0.202.0" - dependencies: - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/otlp-exporter-base": "npm:0.202.0" - "@opentelemetry/otlp-transformer": "npm:0.202.0" - "@opentelemetry/resources": "npm:2.0.1" - "@opentelemetry/sdk-trace-base": "npm:2.0.1" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/f9e60e51b5dcca3d4d32f2091f04faa2bcaf677b9f8a88990c8cdc7c6345e4244e46c4e8df1c336fc6f741aea092407032ade1236089eb729196e6fcdc055ad0 - languageName: node - linkType: hard - -"@opentelemetry/exporter-trace-otlp-http@npm:0.54.2": - version: 0.54.2 - resolution: "@opentelemetry/exporter-trace-otlp-http@npm:0.54.2" - dependencies: - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/otlp-exporter-base": "npm:0.54.2" - "@opentelemetry/otlp-transformer": "npm:0.54.2" - "@opentelemetry/resources": "npm:1.27.0" - "@opentelemetry/sdk-trace-base": "npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/0826980f3509795b48b694bc5c1035e1800ddc0697e29662dd02a6ec09ebe40a57f3c977482cf91b90a6eb5ed99b6a9b1401a144c868c6f4196c6c50da84600a - languageName: node - linkType: hard - -"@opentelemetry/exporter-trace-otlp-proto@npm:0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/exporter-trace-otlp-proto@npm:0.202.0" - dependencies: - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/otlp-exporter-base": "npm:0.202.0" - "@opentelemetry/otlp-transformer": "npm:0.202.0" - "@opentelemetry/resources": "npm:2.0.1" - "@opentelemetry/sdk-trace-base": "npm:2.0.1" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/884d1d097f3defee57dbb64281815a062603f64ce81e2f4460477925f4fb23c1d42aac2bb9d3b188e00cacaa9d231ef11e2e0f36e4b26863d009e795953191d8 - languageName: node - linkType: hard - -"@opentelemetry/exporter-trace-otlp-proto@npm:0.54.2": - version: 0.54.2 - resolution: "@opentelemetry/exporter-trace-otlp-proto@npm:0.54.2" - dependencies: - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/otlp-exporter-base": "npm:0.54.2" - "@opentelemetry/otlp-transformer": "npm:0.54.2" - "@opentelemetry/resources": "npm:1.27.0" - "@opentelemetry/sdk-trace-base": "npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/3f128e60833c6cc896209ee787234be930ea326d9006973f1f0bc61903715c4ae8d60e46ab4b66cd9baa008c8682b9f8ddef6b99ea8b2929aa4745075a593630 - languageName: node - linkType: hard - -"@opentelemetry/exporter-zipkin@npm:1.27.0": - version: 1.27.0 - resolution: "@opentelemetry/exporter-zipkin@npm:1.27.0" - dependencies: - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/resources": "npm:1.27.0" - "@opentelemetry/sdk-trace-base": "npm:1.27.0" - "@opentelemetry/semantic-conventions": "npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.0.0 - checksum: 10/c1cf75eae527b9159b9e52b9b073d3a62a8e407812832a10bb1c110aad2764692239cb99e46f56b2d4d72fe548494dd829bbf2c25611827fc1b031d8af69e8ef - languageName: node - linkType: hard - -"@opentelemetry/exporter-zipkin@npm:2.0.1": - version: 2.0.1 - resolution: "@opentelemetry/exporter-zipkin@npm:2.0.1" - dependencies: - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/resources": "npm:2.0.1" - "@opentelemetry/sdk-trace-base": "npm:2.0.1" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/resources": "npm:2.6.0" + "@opentelemetry/sdk-trace-base": "npm:2.6.0" "@opentelemetry/semantic-conventions": "npm:^1.29.0" peerDependencies: "@opentelemetry/api": ^1.0.0 - checksum: 10/67fa9c4e33276218fd8a4c6fb04bab55aaf35383e396056034ab1f826f3d00c672b91ec890a9859df13c9f2890d3dc3d9f1a2b2904f0855272fc0cb48157e874 + checksum: 10/dcae93429cf76880e422634a9da41b6c026d968e1a18413d5318bf55540539dc3ec949976b391f8254e92567dbfa144bc3db208dfd79489c318d24d10062c234 languageName: node linkType: hard -"@opentelemetry/instrumentation-amqplib@npm:^0.49.0": - version: 0.49.0 - resolution: "@opentelemetry/instrumentation-amqplib@npm:0.49.0" +"@opentelemetry/instrumentation-amqplib@npm:^0.60.0": + version: 0.60.0 + resolution: "@opentelemetry/instrumentation-amqplib@npm:0.60.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" + "@opentelemetry/semantic-conventions": "npm:^1.33.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/891f201f476d77b960b6f3dc0186b3af52f7c249b69da365da184b01cf2056052fa93f85196e8811156908b2f532adf6a13773f102b4a78626046275827df2b1 + checksum: 10/fcf55c6489dd59575bd33de44ff2c6dd1ce2af06d71188df7d81791bd1c6b54414c63518a8c645f56cf08e535cfca211afa023df48b8ce168d3069190e769666 languageName: node linkType: hard -"@opentelemetry/instrumentation-aws-lambda@npm:^0.53.1": - version: 0.53.1 - resolution: "@opentelemetry/instrumentation-aws-lambda@npm:0.53.1" +"@opentelemetry/instrumentation-aws-lambda@npm:^0.65.0": + version: 0.65.0 + resolution: "@opentelemetry/instrumentation-aws-lambda@npm:0.65.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" - "@types/aws-lambda": "npm:8.10.150" + "@types/aws-lambda": "npm:^8.10.155" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/a305988e8332e416c5fd81ffbdad71ffd68eeb623c55ea26fa25212f3c0c254ce30022461067981e4a7c5cfb1e25d1f1ed306d09f09d68c17fbbe693ec2ed4a1 + checksum: 10/08eff3816df64f6dfb189070abf10163f98f837751e2311720d422a0f5b56fc26100eacccc607a639912ac2e58d0e4e9babb486b33629c6971cede4190c73d0e languageName: node linkType: hard -"@opentelemetry/instrumentation-aws-sdk@npm:^0.55.0": - version: 0.55.0 - resolution: "@opentelemetry/instrumentation-aws-sdk@npm:0.55.0" +"@opentelemetry/instrumentation-aws-sdk@npm:^0.68.0": + version: 0.68.0 + resolution: "@opentelemetry/instrumentation-aws-sdk@npm:0.68.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/propagation-utils": "npm:^0.31.2" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.34.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/c2ce4063f4761baa6e12d1830270952d67d9bd0fdba3d15b97ec107df54d2a21c01be6960e0c2e3dcb2358bc57ba977b694e02c78a01c0f193377d0e4557dd7e + checksum: 10/bb5851b446067c05aef200cc28ca9b4ca941ee7308ab82039c915aec8fc97bafd5ea8ed1b6f01053a1e7b31caa21ad8a013779f7db77018115b2be630ddfb623 languageName: node linkType: hard -"@opentelemetry/instrumentation-bunyan@npm:^0.48.0": - version: 0.48.0 - resolution: "@opentelemetry/instrumentation-bunyan@npm:0.48.0" +"@opentelemetry/instrumentation-bunyan@npm:^0.58.0": + version: 0.58.0 + resolution: "@opentelemetry/instrumentation-bunyan@npm:0.58.0" dependencies: - "@opentelemetry/api-logs": "npm:^0.202.0" - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/api-logs": "npm:^0.213.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@types/bunyan": "npm:1.8.11" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/c0147693e5e6202198448b0ca8b2550ca972d9a72a78b3c95edb1942b6e0a1b295351c205dbfc7562e11a50b815a64b3b920ceb474d6a59daa4cfbea01d8f621 + checksum: 10/5ba6b704793c02bc51c3fa4507c7ac943e4adf69e0e62f6d63bab13c3cbadb724b79b8d9d385518efd6e8165ad723e7d2d14631d3d6a1630ec92b8da4d82fe5c languageName: node linkType: hard -"@opentelemetry/instrumentation-cassandra-driver@npm:^0.48.0": - version: 0.48.0 - resolution: "@opentelemetry/instrumentation-cassandra-driver@npm:0.48.0" +"@opentelemetry/instrumentation-cassandra-driver@npm:^0.58.0": + version: 0.58.0 + resolution: "@opentelemetry/instrumentation-cassandra-driver@npm:0.58.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" + "@opentelemetry/semantic-conventions": "npm:^1.37.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/7a3b2ba1b472108f0b1e1e799d7f8f8d0eb10034378b4b06fbec0e1fc59e095f3e374dd7739047856d27f00d8706603db08cfb90aa37b427fce18b43b0731932 + checksum: 10/c6bdc86e7c24737a0d08c031b88f5e863482d3da126bce7711f5b92fd840f49f31834a38a6f530100042f5d0cea6da549b813489665331d01a15ce4e034242e3 languageName: node linkType: hard -"@opentelemetry/instrumentation-connect@npm:^0.46.0": - version: 0.46.0 - resolution: "@opentelemetry/instrumentation-connect@npm:0.46.0" +"@opentelemetry/instrumentation-connect@npm:^0.56.0": + version: 0.56.0 + resolution: "@opentelemetry/instrumentation-connect@npm:0.56.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" "@types/connect": "npm:3.4.38" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/e5b96bde0b8fbcf2edc22ed3a5ac655758440e9a3f8c4f39ae3dcf88edda8be6dd0bb121b3cffcf8df8b53fb0aef3c5bc62549cf5c24439fb9ba15a03f93280c + checksum: 10/7c3c71e205aa748f3f3b24ca3a9bd9e07ee965175a0cdd672fca9604a97a668962190f9db80518ae94c6ce21f7b13b9dcadc37dea0d749b12988bb273c3f400a languageName: node linkType: hard -"@opentelemetry/instrumentation-cucumber@npm:^0.17.1": - version: 0.17.1 - resolution: "@opentelemetry/instrumentation-cucumber@npm:0.17.1" +"@opentelemetry/instrumentation-cucumber@npm:^0.29.0": + version: 0.29.0 + resolution: "@opentelemetry/instrumentation-cucumber@npm:0.29.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.0.0 - checksum: 10/ef65ef03f4aa79a3b4ae223aebf714b62b7d96261680932df02ba4590685253d4e7bf44ac58109e39a951bb0217ffef11f12224ede62ce65ac93a46f2b65e517 + checksum: 10/03498a50448ea0ce3a7e96db4903697791b13b0bb42a933a92dffb4f499acaceb7cc899365c8208d12107d1fc34b65b19be3b1d03184c312b1358ad629ec8b55 languageName: node linkType: hard -"@opentelemetry/instrumentation-dataloader@npm:^0.20.0": - version: 0.20.0 - resolution: "@opentelemetry/instrumentation-dataloader@npm:0.20.0" +"@opentelemetry/instrumentation-dataloader@npm:^0.30.0": + version: 0.30.0 + resolution: "@opentelemetry/instrumentation-dataloader@npm:0.30.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/4d9e5600644e5d941860d492be9bd1b3966acd69e281f491f151347d482464e1481f7ce8c6898e744e235d052c16868774b6353d606a58a3538b9e32d43c2b92 + checksum: 10/9c45ba18866980e898fd8a7060d655e5c37dfa7dd5878a56e6ba5de22e88273215fc4fa02f8971b403abfeb79fa28dc938d5ca071ef812526dedd3774caf8587 languageName: node linkType: hard -"@opentelemetry/instrumentation-dns@npm:^0.46.0": - version: 0.46.0 - resolution: "@opentelemetry/instrumentation-dns@npm:0.46.0" +"@opentelemetry/instrumentation-dns@npm:^0.56.0": + version: 0.56.0 + resolution: "@opentelemetry/instrumentation-dns@npm:0.56.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/68c72e76a5c53d7d49632fb0126c097aa717c173fc0a2502f4b03be382844fbcdcc385f49dab502853006e3e64555879e5c26a24d126a87d7825ad01be1a9f01 + checksum: 10/9cc729e47dd6e22e8c363eeb335fe9d90f98be4c81307bbbd1b504062a9b1a8e53189ae210490f222d10da15486e287796e8f69c40108914a746a7200fe90456 languageName: node linkType: hard -"@opentelemetry/instrumentation-express@npm:^0.51.1": - version: 0.51.1 - resolution: "@opentelemetry/instrumentation-express@npm:0.51.1" +"@opentelemetry/instrumentation-express@npm:^0.61.0": + version: 0.61.0 + resolution: "@opentelemetry/instrumentation-express@npm:0.61.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/a0ecb32a8599d826a032df0c5558d43bf5024b131b93e6e57c659f208e265d40bafbad15c46a5621c698e7b6383ef2a02f4600633d52ba2f4b6fb75c08eed631 + checksum: 10/039273c9985238ac74a3576c7a8e8be6b63a962f89e289eed2ee6eeca14821c82d384c6015b536bc42a082a39e5d4db10abcdaf47235e2ca0f2cc0ff47525b14 languageName: node linkType: hard -"@opentelemetry/instrumentation-fastify@npm:^0.47.1": - version: 0.47.1 - resolution: "@opentelemetry/instrumentation-fastify@npm:0.47.1" +"@opentelemetry/instrumentation-fastify@npm:^0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/instrumentation-fastify@npm:0.57.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/89ffbe54deaa90a33deb873db24967f7e0380c4dc448703ed6b7abbadd345687902e3511e23c1af622ce55e9b70a12e724d3773ad61d7007e4d3d8a47615591d + checksum: 10/74fb7b4d01bbabe82da3a81c11bdbbb86a043f2eb47bbfac62791b0ee029d2d7613ec9d1e34aa0db884711f6d3b92391ad4eabffabaa7fa7c8ab042f0169a13f languageName: node linkType: hard -"@opentelemetry/instrumentation-fs@npm:^0.22.0": - version: 0.22.0 - resolution: "@opentelemetry/instrumentation-fs@npm:0.22.0" +"@opentelemetry/instrumentation-fs@npm:^0.32.0": + version: 0.32.0 + resolution: "@opentelemetry/instrumentation-fs@npm:0.32.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/56090810d8c31949ddbd9f1e2c9f91ca38e7214ada60bc9abc72cfac94fb303e5cc905a63e0c1ab2b524aaf80e7a152a3ab286d792fe82bb4e814cd5eb1d2f16 + checksum: 10/6a72169c4410700add4d2f7ff3c6e31e5a03040771932eb5392c0ab31c13b660a498727c876b95f0f8b88caa2d3af2b4c0d785bfc709c607b775f8797d76de85 languageName: node linkType: hard -"@opentelemetry/instrumentation-generic-pool@npm:^0.46.1": - version: 0.46.1 - resolution: "@opentelemetry/instrumentation-generic-pool@npm:0.46.1" +"@opentelemetry/instrumentation-generic-pool@npm:^0.56.0": + version: 0.56.0 + resolution: "@opentelemetry/instrumentation-generic-pool@npm:0.56.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/10f8cbf5d052c3cb895857390f6a53ec292a1d0e8ec82df94bfb6cd49a0d0a0db0ba66cbb4127298418be4b68444d048f70833257d37cad404edf17578d477e5 + checksum: 10/fae98b198728f06a323b552070c8f6b41a184e1e86c8f3a1868e3f100ac4e80d8985faa89a1ef42549d3e29d6ee9ec59781cfe49ddfddfa94de0e397da0cc46c languageName: node linkType: hard -"@opentelemetry/instrumentation-graphql@npm:^0.50.0": - version: 0.50.0 - resolution: "@opentelemetry/instrumentation-graphql@npm:0.50.0" +"@opentelemetry/instrumentation-graphql@npm:^0.61.0": + version: 0.61.0 + resolution: "@opentelemetry/instrumentation-graphql@npm:0.61.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/a359e0839381d74dacf0b3acc8c951686cf73d6201fd305583e1c58ed6f4dcf1421efa322075808ed9c9298af3e579cbe4ac275960ceb7aceb8639b7346b5990 + checksum: 10/ba5270bc5b98523d3865f277d5f45b077ab02b81e84f1d8ee34994ff5ac4d28b6810c87dd1f52daf57a4983929a09d6b02419a26b068a50990a34fa864e31aa5 languageName: node linkType: hard -"@opentelemetry/instrumentation-grpc@npm:^0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/instrumentation-grpc@npm:0.202.0" +"@opentelemetry/instrumentation-grpc@npm:^0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/instrumentation-grpc@npm:0.213.0" dependencies: - "@opentelemetry/instrumentation": "npm:0.202.0" + "@opentelemetry/instrumentation": "npm:0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.29.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/4b59bb35de3b900e9fc5e9ba44d1828b6b4eea15f1ce65a63ad9e047cff5989a9d1d23180e8c221c7bd375c104ecd23f2e7650405e37cf8d8a46edda53d447ee + checksum: 10/6ac17dbed347571e954846c30b97d649800dab79718c04846813fc2358f6679e3050575801c0c5cbfda632220dfec673e4c695ca104d0e72637ce8604528959d languageName: node linkType: hard -"@opentelemetry/instrumentation-hapi@npm:^0.49.0": - version: 0.49.0 - resolution: "@opentelemetry/instrumentation-hapi@npm:0.49.0" +"@opentelemetry/instrumentation-hapi@npm:^0.59.0": + version: 0.59.0 + resolution: "@opentelemetry/instrumentation-hapi@npm:0.59.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/2e2640e26ae193000b63fb62aea5dc03eff2c4896442361ebc7e3e482713166bad49616a5d3c157fb8dd0fab908d0c4412e24f4d806b071435890436ccf8fd9f + checksum: 10/bcca71c35d525fe54abb3315cfad19dff635cdb1e72b7e15d083a468e05ae90eb966bbf1538bc4fdb0f16fb46057dd61780057c43fdcdb1a1ee5d94aa200cc27 languageName: node linkType: hard -"@opentelemetry/instrumentation-http@npm:^0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/instrumentation-http@npm:0.202.0" +"@opentelemetry/instrumentation-http@npm:^0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/instrumentation-http@npm:0.213.0" dependencies: - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/instrumentation": "npm:0.202.0" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/instrumentation": "npm:0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.29.0" forwarded-parse: "npm:2.1.2" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/fb36be7a08a33d07bb51e8c8dc64be937117f03d93a1425bc9ddb041464da1f01914adc1bc8173a145b61a7fd8b6151db8e284db1bcce12135d6b27b01c2ad0a + checksum: 10/cbb8c2d639ddeff70f13a4781b2bb39067ce34ad9fd02a5f20f32865fb3b2e028c46dd29296a9190f42e1e7965877d9b73941f969de093c0d5e92ad0f4ae3aec languageName: node linkType: hard -"@opentelemetry/instrumentation-ioredis@npm:^0.50.1": - version: 0.50.1 - resolution: "@opentelemetry/instrumentation-ioredis@npm:0.50.1" +"@opentelemetry/instrumentation-ioredis@npm:^0.61.0": + version: 0.61.0 + resolution: "@opentelemetry/instrumentation-ioredis@npm:0.61.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/redis-common": "npm:^0.38.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" + "@opentelemetry/redis-common": "npm:^0.38.2" + "@opentelemetry/semantic-conventions": "npm:^1.33.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/08e6aed4aa6d0ad0d1dd4a6d02b260f46d1ce5168b8dd1f6b2d3f8c71f0204378e1b52f175817e53cdb6f98841a6ddfc5ca2005592ea678f046316dc4edff281 + checksum: 10/598d2a6763019a5b7b1c1139e12bb8199e7fc29aea54af3e78f77da9ea3ea45b98165581c74613923590ee242f6a90a30f5849b208f9e9268efdcc30d9a7d7cb languageName: node linkType: hard -"@opentelemetry/instrumentation-kafkajs@npm:^0.11.0": - version: 0.11.0 - resolution: "@opentelemetry/instrumentation-kafkajs@npm:0.11.0" +"@opentelemetry/instrumentation-kafkajs@npm:^0.22.0": + version: 0.22.0 + resolution: "@opentelemetry/instrumentation-kafkajs@npm:0.22.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.30.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/6998633cda7260fde928f8691482bdbf5478d7f4c7c0be4c199c366345525733ec87e94b0a38b5b65a719489998f377ad52a065ebe0eeafc010e237533f1ce55 + checksum: 10/ac0012cd95a29b7c29de27de57b1c3981de72a95e549fba99dc7ff3e1b1168a668b07f43cde32438890a150ad058af82063df233939338ecce05d60c1281bccf languageName: node linkType: hard -"@opentelemetry/instrumentation-knex@npm:^0.47.0": - version: 0.47.0 - resolution: "@opentelemetry/instrumentation-knex@npm:0.47.0" +"@opentelemetry/instrumentation-knex@npm:^0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/instrumentation-knex@npm:0.57.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.33.1" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/f3a753505fd6499d05022fbddb0977b781ad41a857df9cde7410273ca3ad9f2343afc6136138bbdde16e5358d77bba66e2ddf7f93d4dab878b6e5b85d72fc6d8 + checksum: 10/bf8a5b9030d51e50387c92ac91dcb1d8be6b829236a245ee834a9bd6c9206bf8f9ddf267e7f2ccb1095adc08ec28624144aae2e5a010523e7ef5479b978e3c49 languageName: node linkType: hard -"@opentelemetry/instrumentation-koa@npm:^0.50.2": - version: 0.50.2 - resolution: "@opentelemetry/instrumentation-koa@npm:0.50.2" +"@opentelemetry/instrumentation-koa@npm:^0.61.0": + version: 0.61.0 + resolution: "@opentelemetry/instrumentation-koa@npm:0.61.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" + "@opentelemetry/semantic-conventions": "npm:^1.36.0" peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/3598afe454d6df1861dd0b7f4a8b617f4d3b89789961577912151df9a2d159523490f25c3e8d03234b5faa62d5f8f8985e8a52b65122cae4c58016debf109edb + "@opentelemetry/api": ^1.9.0 + checksum: 10/5426d58dd0c0498a5cdea2996a8840308ce9d7c48e8894f8df2abe9aa7fbd8b130a89d32c2465e660d19a9dc9772d4f74517da3d3702869c45ac0105d4877e65 languageName: node linkType: hard -"@opentelemetry/instrumentation-lru-memoizer@npm:^0.47.0": - version: 0.47.0 - resolution: "@opentelemetry/instrumentation-lru-memoizer@npm:0.47.0" +"@opentelemetry/instrumentation-lru-memoizer@npm:^0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/instrumentation-lru-memoizer@npm:0.57.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/91fd11ce2f012b715a1bffb5c81ca5d681e5c2d9f638a2e459f2d4bead587ed755e438c234132484d6d030f0b3104a0747596708f51df109449cd970e965e6ae + checksum: 10/f4b41d072251be5d9a296a179b8ce09a734204bacacbb1f8e6f5ebc2e798a999ead9806a3944b203d35f50421e86f4f19069e0f2d211f37ac8c372cd89defbb3 languageName: node linkType: hard -"@opentelemetry/instrumentation-memcached@npm:^0.46.0": - version: 0.46.0 - resolution: "@opentelemetry/instrumentation-memcached@npm:0.46.0" +"@opentelemetry/instrumentation-memcached@npm:^0.56.0": + version: 0.56.0 + resolution: "@opentelemetry/instrumentation-memcached@npm:0.56.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" + "@opentelemetry/semantic-conventions": "npm:^1.33.0" "@types/memcached": "npm:^2.2.6" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/12bbcc4b9dcc62b0b48ba83f555112881cc9723f174335bbf7695374ae17582fb12cb2f72295656c84608b73ef5a88afcf551d5c392afebddbf97d7a64230080 + checksum: 10/9c3cf65410c232d61edc1e8ea95c83927d77554b269e3bdbeef31bad6ed03387f7ce838ad5968af20c8abca140730005ad68505f6a36a6d976dae354c88c07d9 languageName: node linkType: hard -"@opentelemetry/instrumentation-mongodb@npm:^0.55.1": - version: 0.55.1 - resolution: "@opentelemetry/instrumentation-mongodb@npm:0.55.1" +"@opentelemetry/instrumentation-mongodb@npm:^0.66.0": + version: 0.66.0 + resolution: "@opentelemetry/instrumentation-mongodb@npm:0.66.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" + "@opentelemetry/semantic-conventions": "npm:^1.33.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/233822b1886b5693923430fb126363005424bcabd03681bf2cf4c035a52dbc6475718417b2a284d38f2fdcac1423625f5a25b767018a3686e9da54f4313f4da9 + checksum: 10/50baa175386bc71daf9098ddb39460bde17fac1a7c5bc347f07fd3e0a752dc1c56cc1cd5a09048b51892b1cb29b244d4865da8deec34f3ea6c645e6e97d93b9c languageName: node linkType: hard -"@opentelemetry/instrumentation-mongoose@npm:^0.49.0": - version: 0.49.0 - resolution: "@opentelemetry/instrumentation-mongoose@npm:0.49.0" +"@opentelemetry/instrumentation-mongoose@npm:^0.59.0": + version: 0.59.0 + resolution: "@opentelemetry/instrumentation-mongoose@npm:0.59.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" + "@opentelemetry/semantic-conventions": "npm:^1.33.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/4b2b320346a0d00a56438c041ddda6cbd8b34d37de613daab618892fcba380b0cbd5396886028e9e2ac81c497f25e66f7668ccbf57c3f870678d30fc16e4ae24 + checksum: 10/121506d6e4941145c7066b2b723eaa2e0182a5d0b565d61cbf6269454d86c180ec417c66cf4320ee82d173099d59b098c924bb17e72a873722aa54f14aaa5e7e languageName: node linkType: hard -"@opentelemetry/instrumentation-mysql2@npm:^0.48.1": - version: 0.48.1 - resolution: "@opentelemetry/instrumentation-mysql2@npm:0.48.1" +"@opentelemetry/instrumentation-mysql2@npm:^0.59.0": + version: 0.59.0 + resolution: "@opentelemetry/instrumentation-mysql2@npm:0.59.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - "@opentelemetry/sql-common": "npm:^0.41.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" + "@opentelemetry/semantic-conventions": "npm:^1.33.0" + "@opentelemetry/sql-common": "npm:^0.41.2" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/d52d0950201adb3c4ce8f82edc544eee489de0971045f1e907832d6e7424d372da3a1e62143f6fcde698d883dfbe7adbea243cef9653f467c96d011d98225676 + checksum: 10/7fa1941bdad8cbfcf6084ba2a291c68d101050c738bad06915f2cce4d475548353fdd99ba8f41bcc26267cd092eaa485849343256bd48043bc2dfa0412570f6b languageName: node linkType: hard -"@opentelemetry/instrumentation-mysql@npm:^0.48.1": - version: 0.48.1 - resolution: "@opentelemetry/instrumentation-mysql@npm:0.48.1" +"@opentelemetry/instrumentation-mysql@npm:^0.59.0": + version: 0.59.0 + resolution: "@opentelemetry/instrumentation-mysql@npm:0.59.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" + "@opentelemetry/semantic-conventions": "npm:^1.33.0" "@types/mysql": "npm:2.15.27" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/8e4493e5a685d98f0b7797b6f3996f7949cdae758e68c12e2edb57d8388be47a67e668c12340f1a2e18c692c8db990a765913120e402215c8d473695c9a281b8 + checksum: 10/87833280fa53a691d95263066b04458db33b6bb49a15fb97add7505fcae0cb574da5ac9e53c7788d2044ae108cbc42308eb2684c2fa05751098cb54d6bd0d315 languageName: node linkType: hard -"@opentelemetry/instrumentation-nestjs-core@npm:^0.48.1": - version: 0.48.1 - resolution: "@opentelemetry/instrumentation-nestjs-core@npm:0.48.1" +"@opentelemetry/instrumentation-nestjs-core@npm:^0.59.0": + version: 0.59.0 + resolution: "@opentelemetry/instrumentation-nestjs-core@npm:0.59.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.30.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/fbbd5dd411b79371725e9075bca0c492a524ebd90d5e17434c3608bae6ec1cbbd5cca4dc0385c423e59963bfc1b945efdff5cd02f09a3f2a06391fc176d52c50 + checksum: 10/4a86f6658e45993381051352c61ef904899675c641210bd30210298c86585881212b4db3fe769ef7bbe05dc20c4a2019a6ecddd6d7ddfb10b01cb330bc727b43 languageName: node linkType: hard -"@opentelemetry/instrumentation-net@npm:^0.46.1": - version: 0.46.1 - resolution: "@opentelemetry/instrumentation-net@npm:0.46.1" +"@opentelemetry/instrumentation-net@npm:^0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/instrumentation-net@npm:0.57.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" + "@opentelemetry/semantic-conventions": "npm:^1.33.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/3372ac7b8643c4adea2d8ffd762a2c872e02d000d5cc5afca922f422033eea251adeeac1c5d1bd49014d76c9167a0dfa98cd41dda7b3950395c179a8a8c6de81 + checksum: 10/7aff49402d16ce3d501060759d1c729a5e2d514f5ebb8a39a0190f828a3b402886aa2cefa4012bc93f3a1c002b67b4729ed193905c67556fbebb2863ae18370a languageName: node linkType: hard -"@opentelemetry/instrumentation-oracledb@npm:^0.28.0": - version: 0.28.0 - resolution: "@opentelemetry/instrumentation-oracledb@npm:0.28.0" +"@opentelemetry/instrumentation-openai@npm:^0.11.0": + version: 0.11.0 + resolution: "@opentelemetry/instrumentation-openai@npm:0.11.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/api-logs": "npm:^0.213.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" + "@opentelemetry/semantic-conventions": "npm:^1.36.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/8cfa799a96b9d815f7a2c1ab9c68251826077ee9fa77f974bcf031490acb432095f280e17745e14eefae60782a12d3dbebbb8798889873ed6c4c9e7eae72f650 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-oracledb@npm:^0.38.0": + version: 0.38.0 + resolution: "@opentelemetry/instrumentation-oracledb@npm:0.38.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.213.0" + "@opentelemetry/semantic-conventions": "npm:^1.34.0" "@types/oracledb": "npm:6.5.2" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/820a6e760d2137ad00b27ff78014316fc37e92f2be94ad4c6082e92ace8fcd89a6d2bcc744e6330358ebd0f127d7993b65843b167b0c1ea0f8d5cb0fe201c634 + checksum: 10/5ca691e164a4fc8ef686553e062e67342d7e929484bdb6f2adef219e1e50d55a6dd5570381a4347345e1fd514d5667cbe29a2b7bb9801eda77bd532b04341307 languageName: node linkType: hard -"@opentelemetry/instrumentation-pg@npm:^0.54.1": - version: 0.54.1 - resolution: "@opentelemetry/instrumentation-pg@npm:0.54.1" +"@opentelemetry/instrumentation-pg@npm:^0.65.0": + version: 0.65.0 + resolution: "@opentelemetry/instrumentation-pg@npm:0.65.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - "@opentelemetry/sql-common": "npm:^0.41.0" - "@types/pg": "npm:8.15.4" - "@types/pg-pool": "npm:2.0.6" + "@opentelemetry/instrumentation": "npm:^0.213.0" + "@opentelemetry/semantic-conventions": "npm:^1.34.0" + "@opentelemetry/sql-common": "npm:^0.41.2" + "@types/pg": "npm:8.15.6" + "@types/pg-pool": "npm:2.0.7" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/eb88b8bd4417db8188af44e62659a7bc35b3f6a4b155d12c69357fc88185f3d719b3dd93e5562896088d07041c882a71a06d9672c14332846ae4f5abd26ec99a + checksum: 10/835953ae633fe0efa3ad0a39579f7860c59ec066330a592682ceea0eb14eb770d94c309a4fd64ff2162e804fe53cfcb38f1fd3d19116786473cfd8e6a1eaee7f languageName: node linkType: hard -"@opentelemetry/instrumentation-pino@npm:^0.49.1": - version: 0.49.1 - resolution: "@opentelemetry/instrumentation-pino@npm:0.49.1" +"@opentelemetry/instrumentation-pino@npm:^0.59.0": + version: 0.59.0 + resolution: "@opentelemetry/instrumentation-pino@npm:0.59.0" dependencies: - "@opentelemetry/api-logs": "npm:^0.202.0" + "@opentelemetry/api-logs": "npm:^0.213.0" "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/2e2bff24bef74f68da8f4b4655d198c3fba7f7425f2f6317077385bd34dfbfb44c3747165859fec7a9f90aa0c77f830148e112e3d8257f6ac35dc28bbe157041 + checksum: 10/9198c2bc9ab17fabef251d69feb9f3199b39e878792a9a9fb5b7e0936faf860d4706170db8578399bcb7e8826d7d544199dfeabec16cfb4b55df02a537f8c1e9 languageName: node linkType: hard -"@opentelemetry/instrumentation-redis@npm:^0.50.0": - version: 0.50.0 - resolution: "@opentelemetry/instrumentation-redis@npm:0.50.0" +"@opentelemetry/instrumentation-redis@npm:^0.61.0": + version: 0.61.0 + resolution: "@opentelemetry/instrumentation-redis@npm:0.61.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/redis-common": "npm:^0.38.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" + "@opentelemetry/redis-common": "npm:^0.38.2" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/94a05b1401fa071c2483d241a0cffb3fc79492c44ee7a2d2e320118cbc948eb7972c70e93f128bae29febd4958fc95904afff56b5c49da8b5407666e8f26e748 + checksum: 10/667eacf85fcf3de51e5052f66826aed90b62e565099c7cea6b465830ee0fd9f149a7ddf208a00e34f3bbb65213ed9d24b1601c242234e78db21447f9f9fd56fe languageName: node linkType: hard -"@opentelemetry/instrumentation-restify@npm:^0.48.2": - version: 0.48.2 - resolution: "@opentelemetry/instrumentation-restify@npm:0.48.2" +"@opentelemetry/instrumentation-restify@npm:^0.58.0": + version: 0.58.0 + resolution: "@opentelemetry/instrumentation-restify@npm:0.58.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/b2a0917248ae9defaff8dd9d5c650fb627241b56355474a776a54fd9541a29b3bbb487b54dba7042ac2f7e15cb0a79f738360fe8a3322e4066ff06728e027292 + checksum: 10/c6b1aaa19e9d4f2520a4d2ac61b6125b2abafa5bba81331fc263b59e5256d2ce8b016dc8cd12e2ac1fcebd6068844d894450f45aa2ed7dc4bb051916d055954d languageName: node linkType: hard -"@opentelemetry/instrumentation-router@npm:^0.47.0": - version: 0.47.0 - resolution: "@opentelemetry/instrumentation-router@npm:0.47.0" +"@opentelemetry/instrumentation-router@npm:^0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/instrumentation-router@npm:0.57.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/620a54e97e311692c197e86f12bf43bf1a04f2b3009f71dfdf6e0972b5c282606cc649739ba0041f60aea3fae9b027b315378e71a5f764c85952ece2e5b28078 + checksum: 10/71b0c108708ac214377e96769c7863f3172e17a8a24afd2dc82ac48aebd4f0bf9ed73266b7549ac9d05221df66af35e9bd6a493f8fd546cd59f52b24f8bfae5b languageName: node linkType: hard -"@opentelemetry/instrumentation-runtime-node@npm:^0.16.0": - version: 0.16.0 - resolution: "@opentelemetry/instrumentation-runtime-node@npm:0.16.0" +"@opentelemetry/instrumentation-runtime-node@npm:^0.26.0": + version: 0.26.0 + resolution: "@opentelemetry/instrumentation-runtime-node@npm:0.26.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/85d5a1302558fc99d5ec411ef4ea7306bb2c5295807ef3e75f61272b46be1752a19de145d22dbe530803ddfef65a4811ecccb5944183302ff60b8762005de70d + checksum: 10/b3ea3c4dece7dc3d35c2dc7de38cccc3b73c34175c4fb28a8442f72b312b7f83417d37c9f1864feb02ed2c8d8940f0525fe0e2e0c7d0615bcfe77825b8125928 languageName: node linkType: hard -"@opentelemetry/instrumentation-socket.io@npm:^0.49.0": - version: 0.49.0 - resolution: "@opentelemetry/instrumentation-socket.io@npm:0.49.0" +"@opentelemetry/instrumentation-socket.io@npm:^0.60.0": + version: 0.60.0 + resolution: "@opentelemetry/instrumentation-socket.io@npm:0.60.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/16ab1664c3692267a6b9d0fdaf9256d66d9b705c056c019fb15d261d1f09296d325ddbbc9402b60b0d3b5722b55cdb6c7d0980c8ee965ef4346461742cdf87a1 + checksum: 10/7bc82b036ad37fc1361e7f44d97036e8e456f27e133139d21bbdd1194d32ff9ff3a8aa7101874d966e44ac39472e5ef339145a372e8338690ba65a18aaa4b606 languageName: node linkType: hard -"@opentelemetry/instrumentation-tedious@npm:^0.21.1": - version: 0.21.1 - resolution: "@opentelemetry/instrumentation-tedious@npm:0.21.1" +"@opentelemetry/instrumentation-tedious@npm:^0.32.0": + version: 0.32.0 + resolution: "@opentelemetry/instrumentation-tedious@npm:0.32.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" + "@opentelemetry/semantic-conventions": "npm:^1.33.0" "@types/tedious": "npm:^4.0.14" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/5347d20e2cb4e07a1d25275edb25c47dcf87700a19de630fb33b7fc06bda0eb5934fb058fcb15b33be2eeb49a7430720c011f8540405b5ee10d22e689c93b94a + checksum: 10/25b7c42e75ee393990ff1502ec7e4dea33e9615b35ff32bded2634824c0278dc55cccd6b0d6dbecdfff91d3f998be5d9e89526469287ea5df4637810dfbfc1c6 languageName: node linkType: hard -"@opentelemetry/instrumentation-undici@npm:^0.13.2": - version: 0.13.2 - resolution: "@opentelemetry/instrumentation-undici@npm:0.13.2" +"@opentelemetry/instrumentation-undici@npm:^0.23.0": + version: 0.23.0 + resolution: "@opentelemetry/instrumentation-undici@npm:0.23.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" + "@opentelemetry/semantic-conventions": "npm:^1.24.0" peerDependencies: "@opentelemetry/api": ^1.7.0 - checksum: 10/9f806706096d629796d1205a0f2a9f18cf3542f61dfb60f20199ca5ba269044145a593f5ee0ad495b046eb01b8a14448a61a8bbe6a18611f6967f3473923d97e + checksum: 10/bfb6ed86e8098827725f5cb2f21e21d5c0b13254ab6da84418de68b3c1cadc2f503e4fc213cd6f2a661819e8327c9af9b87c32468444a772e8b82b966cb616a6 languageName: node linkType: hard -"@opentelemetry/instrumentation-winston@npm:^0.47.0": - version: 0.47.0 - resolution: "@opentelemetry/instrumentation-winston@npm:0.47.0" +"@opentelemetry/instrumentation-winston@npm:^0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/instrumentation-winston@npm:0.57.0" dependencies: - "@opentelemetry/api-logs": "npm:^0.202.0" - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/api-logs": "npm:^0.213.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/1a046490c1509f77ae70552ef6cdbf6c21baba4f5d6f45e0c473ffa102156b009acd20036dafbf5d29e92132df88c4aad078b62606af3bda0fbdd4ab90073937 + checksum: 10/0972e3ac889d5408687ddb139c49f69de78b6eebba30bdade3b4b605a240248523971ffd987b9bec976c7dba4dc1f67dfef63fe145937ee9061e4c0b15daa7f6 languageName: node linkType: hard -"@opentelemetry/instrumentation@npm:0.202.0, @opentelemetry/instrumentation@npm:^0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/instrumentation@npm:0.202.0" +"@opentelemetry/instrumentation@npm:0.213.0, @opentelemetry/instrumentation@npm:^0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/instrumentation@npm:0.213.0" dependencies: - "@opentelemetry/api-logs": "npm:0.202.0" - import-in-the-middle: "npm:^1.8.1" - require-in-the-middle: "npm:^7.1.1" + "@opentelemetry/api-logs": "npm:0.213.0" + import-in-the-middle: "npm:^3.0.0" + require-in-the-middle: "npm:^8.0.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/da1db1ebc4ca847cc68d894b2e3a6c6552851d93af8ea793d42474e920f710664575c8991dc269e1a83fcf8f5abdda2cc724fa24e9cc4ae19fa6f70eb68ffc0e + checksum: 10/69baeaae0c5836ede140485530954d32c8d20d864340f7d57b43a6e3ef9c10394d29a1884dd2b076512aec896039a1ea02f698282649b5aa3fa59b13bca00f97 languageName: node linkType: hard -"@opentelemetry/instrumentation@npm:0.54.2": - version: 0.54.2 - resolution: "@opentelemetry/instrumentation@npm:0.54.2" +"@opentelemetry/otlp-exporter-base@npm:0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/otlp-exporter-base@npm:0.213.0" dependencies: - "@opentelemetry/api-logs": "npm:0.54.2" - "@types/shimmer": "npm:^1.2.0" - import-in-the-middle: "npm:^1.8.1" - require-in-the-middle: "npm:^7.1.1" - semver: "npm:^7.5.2" - shimmer: "npm:^1.2.1" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/otlp-transformer": "npm:0.213.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/1c570fb2e55d2ea7dcc45103afb53ffc331efb675dc404783639c0ed4c93e4e0fa04751672f75ca2a633ca03943e520cf802ee0291e79fa33be54a097af46fc6 + checksum: 10/ff36dbdae9ae3b4b7e25fb650e9b351851ae9d50cd09b90d2be709a10dd62a8fef522adad9b27010825e9d22d96238ab37f38530cf92eaf3340d2730d38ad301 languageName: node linkType: hard -"@opentelemetry/otlp-exporter-base@npm:0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/otlp-exporter-base@npm:0.202.0" +"@opentelemetry/otlp-grpc-exporter-base@npm:0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/otlp-grpc-exporter-base@npm:0.213.0" dependencies: - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/otlp-transformer": "npm:0.202.0" + "@grpc/grpc-js": "npm:^1.14.3" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/otlp-exporter-base": "npm:0.213.0" + "@opentelemetry/otlp-transformer": "npm:0.213.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/229778895ba1971451a8b1ec8a4787b0cdba337c87f3ca6aaf6d649f5e2b26439ba1befa36d31824fffa3cef62382792680ec70194f57aac6ebe98e95acecc69 + checksum: 10/dc6aa2414b4f24349a749423504dc0aba00164241b30bb468cfab39b7936408fb00ede7fa675ea95ac131249764a1320a8657765c4509d0d93245879a49905f9 languageName: node linkType: hard -"@opentelemetry/otlp-exporter-base@npm:0.54.2": - version: 0.54.2 - resolution: "@opentelemetry/otlp-exporter-base@npm:0.54.2" +"@opentelemetry/otlp-transformer@npm:0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/otlp-transformer@npm:0.213.0" dependencies: - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/otlp-transformer": "npm:0.54.2" + "@opentelemetry/api-logs": "npm:0.213.0" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/resources": "npm:2.6.0" + "@opentelemetry/sdk-logs": "npm:0.213.0" + "@opentelemetry/sdk-metrics": "npm:2.6.0" + "@opentelemetry/sdk-trace-base": "npm:2.6.0" + protobufjs: "npm:^7.0.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/f4eb3009ab87c072b52d33106b60a507b6debbc40ad6762275f6bb48144e1dc401d1100ea87540df08dbdb542f4ea6779aa805313d0b3966eae34ca3dded1436 + checksum: 10/3f4caa24f9b4d57d9290f9090701031641e8aff33bca13711e2a2766c1acd8ee3c348342f9d81fcb73bbd48fe910444c10990b148d70437f0508bb8ad3751b1b languageName: node linkType: hard -"@opentelemetry/otlp-grpc-exporter-base@npm:0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/otlp-grpc-exporter-base@npm:0.202.0" +"@opentelemetry/propagator-b3@npm:2.6.0": + version: 2.6.0 + resolution: "@opentelemetry/propagator-b3@npm:2.6.0" dependencies: - "@grpc/grpc-js": "npm:^1.7.1" - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/otlp-exporter-base": "npm:0.202.0" - "@opentelemetry/otlp-transformer": "npm:0.202.0" + "@opentelemetry/core": "npm:2.6.0" peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/ba52acee1d46c3bca38d6eba8fea41d5c7389c26a34dd7ed4f4fbc46da5fcbf109ae139c5149c9730cd076ba51d4933d9bc06c223470ee8a463a31fed75b4ed6 + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/8cc50658cc5a31a06ae97b15cda6f0d27fe8e19a06b841ff07b84e458f2e9d81347bbff823819eb8fccd387335475091d0834b9bbe1295b77a1d6a253fcd522d languageName: node linkType: hard -"@opentelemetry/otlp-grpc-exporter-base@npm:0.54.2": - version: 0.54.2 - resolution: "@opentelemetry/otlp-grpc-exporter-base@npm:0.54.2" +"@opentelemetry/propagator-jaeger@npm:2.6.0": + version: 2.6.0 + resolution: "@opentelemetry/propagator-jaeger@npm:2.6.0" dependencies: - "@grpc/grpc-js": "npm:^1.7.1" - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/otlp-exporter-base": "npm:0.54.2" - "@opentelemetry/otlp-transformer": "npm:0.54.2" + "@opentelemetry/core": "npm:2.6.0" peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/d7647ff72fab06ed410f3782c0199a24a98da61bcc5fbc19aa8afcd2fac2b376aad48f9206504d40bee6070de3a7e4bf03423d438a3daaa3f71c3a95f261f48b + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/d9f6f971994aa4cbf5495368e17e248631fbf3c3eeb2405f0c7ce987c830388e6d6987a5f85ba24cfbf1cacdff204cf1b8338703c4845158f602407ec99b64f0 languageName: node linkType: hard -"@opentelemetry/otlp-transformer@npm:0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/otlp-transformer@npm:0.202.0" +"@opentelemetry/redis-common@npm:^0.38.2": + version: 0.38.2 + resolution: "@opentelemetry/redis-common@npm:0.38.2" + checksum: 10/2a4f992572b1990a407ac92c7db941aecb6e8d71f034f4ea0b00b2b1739ad07c22767198a6759ab634cbbe9eebfbea062e2b79a25484289c226c665558041503 + languageName: node + linkType: hard + +"@opentelemetry/resource-detector-alibaba-cloud@npm:^0.33.3": + version: 0.33.3 + resolution: "@opentelemetry/resource-detector-alibaba-cloud@npm:0.33.3" dependencies: - "@opentelemetry/api-logs": "npm:0.202.0" - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/resources": "npm:2.0.1" - "@opentelemetry/sdk-logs": "npm:0.202.0" - "@opentelemetry/sdk-metrics": "npm:2.0.1" - "@opentelemetry/sdk-trace-base": "npm:2.0.1" - protobufjs: "npm:^7.3.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/67e189af60bf8308a5b93deb85bef9709a5604b8c7915d0d42a7a2bff932b7257dfdfc071812c77913e136955a985ffe838c72b5f10059021087ea0bc52d84cd - languageName: node - linkType: hard - -"@opentelemetry/otlp-transformer@npm:0.54.2": - version: 0.54.2 - resolution: "@opentelemetry/otlp-transformer@npm:0.54.2" - dependencies: - "@opentelemetry/api-logs": "npm:0.54.2" - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/resources": "npm:1.27.0" - "@opentelemetry/sdk-logs": "npm:0.54.2" - "@opentelemetry/sdk-metrics": "npm:1.27.0" - "@opentelemetry/sdk-trace-base": "npm:1.27.0" - protobufjs: "npm:^7.3.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/316fcdeca02666dfb2919746a83b1523f729875efac45075d513789fc534a3aa4a467c9c519c7fd8a16db78274534373bee0f56f95c89ba09dc4868e2fb42c10 - languageName: node - linkType: hard - -"@opentelemetry/propagation-utils@npm:^0.31.2": - version: 0.31.2 - resolution: "@opentelemetry/propagation-utils@npm:0.31.2" + "@opentelemetry/core": "npm:^2.0.0" + "@opentelemetry/resources": "npm:^2.0.0" peerDependencies: "@opentelemetry/api": ^1.0.0 - checksum: 10/13828caae31651a1c4fa736f254b49a46b62348ff9ece945b19253fb52562db08bd268b5b0944ef3d2cb951f20cd4118ba14158b34a192e63868f0f3aa9c7cc9 + checksum: 10/600d67e1a910d8278a97171a1a5bf403da34ca070348cb8f7af09b19bedfd48464dc8c794775723058b60137c9a48cb3b7894e6afb81ae62e99be9b4b24f4f62 languageName: node linkType: hard -"@opentelemetry/propagator-b3@npm:1.27.0": - version: 1.27.0 - resolution: "@opentelemetry/propagator-b3@npm:1.27.0" - dependencies: - "@opentelemetry/core": "npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/21af4d3416155071d49351087f16ba66d255fba33f7bf6ffded538646e5a2efc53228733466c4419761a8f83aa200a940d6e30a27cdcb45ebd2665351ef4175e - languageName: node - linkType: hard - -"@opentelemetry/propagator-b3@npm:2.0.1": - version: 2.0.1 - resolution: "@opentelemetry/propagator-b3@npm:2.0.1" - dependencies: - "@opentelemetry/core": "npm:2.0.1" - peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/9871854b3d7516273c1f867c7a6a8eac5791f77954edf7509cc320ee6c109de8ef44778076edd4a64421b84f31632e4d283cb685f9220c5e7f1fe823c0e81a06 - languageName: node - linkType: hard - -"@opentelemetry/propagator-jaeger@npm:1.27.0": - version: 1.27.0 - resolution: "@opentelemetry/propagator-jaeger@npm:1.27.0" - dependencies: - "@opentelemetry/core": "npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/896cf18c3278083caec05b63a622b0e3da9abf658670f9170c4c47fcc3a121d878f7c6708b012490bd466e39f361a58773631fbc4784b4c03ad680002c1df50d - languageName: node - linkType: hard - -"@opentelemetry/propagator-jaeger@npm:2.0.1": - version: 2.0.1 - resolution: "@opentelemetry/propagator-jaeger@npm:2.0.1" - dependencies: - "@opentelemetry/core": "npm:2.0.1" - peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/d6a51e7dd58dde7ada4c0fe510a14f9a33f1972f0db11b64fccb867be23652d4d8a55012dd5a295753ad27c456b21d502eb3a24dfbce6a0a3304e1fa6e1f4896 - languageName: node - linkType: hard - -"@opentelemetry/redis-common@npm:^0.38.0": - version: 0.38.0 - resolution: "@opentelemetry/redis-common@npm:0.38.0" - checksum: 10/c7caa450ed27ad02aeefa3e4d643e7d065d76e5f1cd275c8c3d0c7d324bf7d06434666dbd836d219f6b2e1057285f30ccaa89b949438b38118a70786ec31fa52 - languageName: node - linkType: hard - -"@opentelemetry/resource-detector-alibaba-cloud@npm:^0.31.2": - version: 0.31.2 - resolution: "@opentelemetry/resource-detector-alibaba-cloud@npm:0.31.2" +"@opentelemetry/resource-detector-aws@npm:^2.13.0": + version: 2.13.0 + resolution: "@opentelemetry/resource-detector-aws@npm:2.13.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" "@opentelemetry/resources": "npm:^2.0.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.0.0 - checksum: 10/1e2498d5dc970addaeb88c9a1c4be85248438fe69876f2223ee526535ccb50d1ffaca366bfc5c03d0c84933050fd5d821726d8ce92ac5829da18f4a91025ce2f + checksum: 10/b0d057684f83ca666b3ac61d04a53e0129c3a434b38e3870db071133cc9d3552c8ff9f9b50e9f326f1a450ffd22af292ba4fc594cf429207966ce00a89286ba4 languageName: node linkType: hard -"@opentelemetry/resource-detector-aws@npm:^2.2.0": - version: 2.2.0 - resolution: "@opentelemetry/resource-detector-aws@npm:2.2.0" +"@opentelemetry/resource-detector-azure@npm:^0.21.0": + version: 0.21.0 + resolution: "@opentelemetry/resource-detector-azure@npm:0.21.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" "@opentelemetry/resources": "npm:^2.0.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/semantic-conventions": "npm:^1.37.0" peerDependencies: "@opentelemetry/api": ^1.0.0 - checksum: 10/91e6b67936d6846ba98b31438a9f32e5486f27153cfcd57978c8b6c5df9cc400c821ea63b06fff158c6a7e97b3ded3396f91f02410f28d4113d6b5bdf75f2bec + checksum: 10/cfc4f8e419ec7b077c87599d0d842aeb741413f33e66e97ab093f79b292eaa9eb8dc54ea4e644550f31363ebf4efc8182c03df302143d6e89552427f575e2177 languageName: node linkType: hard -"@opentelemetry/resource-detector-azure@npm:^0.9.0": - version: 0.9.0 - resolution: "@opentelemetry/resource-detector-azure@npm:0.9.0" +"@opentelemetry/resource-detector-container@npm:^0.8.4": + version: 0.8.4 + resolution: "@opentelemetry/resource-detector-container@npm:0.8.4" dependencies: "@opentelemetry/core": "npm:^2.0.0" "@opentelemetry/resources": "npm:^2.0.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.0.0 - checksum: 10/93d85781e73f37ab186b8d39ad144c944d80e69d19361ef4f846499e2bf0ba27df2cd00e334b1408415c80488f1633764696e1d29b639ed7829631ec29c92286 + checksum: 10/65fb62e9e414ac941e94f5befeaca5fd2d2e475e414c9e4521b66729c6a2d9fc3b6e32bb316f95459c6c12910feaa124498827faa466017bd0d3004a26a6630a languageName: node linkType: hard -"@opentelemetry/resource-detector-container@npm:^0.7.2": - version: 0.7.2 - resolution: "@opentelemetry/resource-detector-container@npm:0.7.2" +"@opentelemetry/resource-detector-gcp@npm:^0.48.0": + version: 0.48.0 + resolution: "@opentelemetry/resource-detector-gcp@npm:0.48.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" "@opentelemetry/resources": "npm:^2.0.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" + gcp-metadata: "npm:^8.0.0" peerDependencies: "@opentelemetry/api": ^1.0.0 - checksum: 10/d556c4f64737726e31f211c6163ea359e403b4b0c65bca9fff565ec6a579a18340f4c6dc68d78ac707413c44bcb5f2a362eab2794fbd99524faf161517793e0e + checksum: 10/2c11431c7be28620868d091c4bf9d646ce524587056e94c04b8d9b31bdec77f54ca132a1ac8ff1e6f894009d8cb776527eb3639d3f31b11eeee576b736b3d56a languageName: node linkType: hard -"@opentelemetry/resource-detector-gcp@npm:^0.36.0": - version: 0.36.0 - resolution: "@opentelemetry/resource-detector-gcp@npm:0.36.0" +"@opentelemetry/resources@npm:2.6.0, @opentelemetry/resources@npm:^2.0.0": + version: 2.6.0 + resolution: "@opentelemetry/resources@npm:2.6.0" dependencies: - "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/resources": "npm:^2.0.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - gcp-metadata: "npm:^6.0.0" - peerDependencies: - "@opentelemetry/api": ^1.0.0 - checksum: 10/9bfffd5d21a90509bc8d4dd3ae27554dd56719ca1607fcc20900034cc5776ff4c0025a3d4fddbd7c12e7962dd747516bdca9a9da6ccc59223c8d485609aea732 - languageName: node - linkType: hard - -"@opentelemetry/resources@npm:1.27.0": - version: 1.27.0 - resolution: "@opentelemetry/resources@npm:1.27.0" - dependencies: - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/semantic-conventions": "npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/654141ea65854bba84c22eeecc5af0054f14462f2664f36ac1ad8a170404e3218fccb98cafaaff4ec45e85523230e58eafbf222c25d00de8a60141ce77a34bbf - languageName: node - linkType: hard - -"@opentelemetry/resources@npm:2.0.1, @opentelemetry/resources@npm:^2.0.0": - version: 2.0.1 - resolution: "@opentelemetry/resources@npm:2.0.1" - dependencies: - "@opentelemetry/core": "npm:2.0.1" + "@opentelemetry/core": "npm:2.6.0" "@opentelemetry/semantic-conventions": "npm:^1.29.0" peerDependencies: "@opentelemetry/api": ">=1.3.0 <1.10.0" - checksum: 10/282f3831de2755d0fda2d8b6e37f9587ea248066d50c7d2f14c803ac9d5262a0f1db98a4185bcdc5acaeeece0b61f4fce43bc3896a79f1da79045ae4928618bf + checksum: 10/837e76911d013e52c1c0cd8da6f2912818fcc107fd1c6fcb2ec8faa6e617b802e0eef1aa53bd4417c7a77c96ea02b6f5bbdccb9ff411ef8efeff49c2e02d9443 languageName: node linkType: hard -"@opentelemetry/sdk-logs@npm:0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/sdk-logs@npm:0.202.0" +"@opentelemetry/sdk-logs@npm:0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/sdk-logs@npm:0.213.0" dependencies: - "@opentelemetry/api-logs": "npm:0.202.0" - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/resources": "npm:2.0.1" + "@opentelemetry/api-logs": "npm:0.213.0" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/resources": "npm:2.6.0" + "@opentelemetry/semantic-conventions": "npm:^1.29.0" peerDependencies: "@opentelemetry/api": ">=1.4.0 <1.10.0" - checksum: 10/e1b76647282a41ad7004c86c0058b0e9be70fdcf34aa2761929401e274af79bc5e3c6610a63e14fde4d51d96d8c96adaf3247c10b9a64cb70086d2ede2e421ca + checksum: 10/c443b0f2c88582713c291ca86106679ffdadee96ea428b5058e0cb7470b387828e78d6614e7e01e5ad82f4c44571c1080aa2cf69e621bd2e07089105794cacdf languageName: node linkType: hard -"@opentelemetry/sdk-logs@npm:0.54.2": - version: 0.54.2 - resolution: "@opentelemetry/sdk-logs@npm:0.54.2" +"@opentelemetry/sdk-metrics@npm:2.6.0": + version: 2.6.0 + resolution: "@opentelemetry/sdk-metrics@npm:2.6.0" dependencies: - "@opentelemetry/api-logs": "npm:0.54.2" - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/resources": "npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ">=1.4.0 <1.10.0" - checksum: 10/c00a01de156d529674ed9b607fbabffd3306aa51b7b1897a670214cab7ba7d07da1f2bc5e8d957b0f441448b8ec22ac0526b1b368e36a61edf2bd54bfe571d2d - languageName: node - linkType: hard - -"@opentelemetry/sdk-metrics@npm:1.27.0": - version: 1.27.0 - resolution: "@opentelemetry/sdk-metrics@npm:1.27.0" - dependencies: - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/resources": "npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ">=1.3.0 <1.10.0" - checksum: 10/0d6061f42879170e4b4cf4847aa658520a7574f287b33562299ddeb97b0145a4ec91abec63d2292af0d632fce3e63b42f6e6e9a0e9698dc01950b6063363bf98 - languageName: node - linkType: hard - -"@opentelemetry/sdk-metrics@npm:2.0.1": - version: 2.0.1 - resolution: "@opentelemetry/sdk-metrics@npm:2.0.1" - dependencies: - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/resources": "npm:2.0.1" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/resources": "npm:2.6.0" peerDependencies: "@opentelemetry/api": ">=1.9.0 <1.10.0" - checksum: 10/eb23d0657ce7ef0784f6c89af650de83530099782758fce574316a8e82ff2bca0eb3adffa88c5fdd04eaced6150deb53ea0ea05aae06d2783795691734e85473 + checksum: 10/5fd1254ab86cdb6573999f3c5d60b8332fb3a2b7d50d1befcfd9d8ef021d5e9e405e31394f7aa4a106a4546bd6308a652a78cd9a35d6fbe56e44984d605cb5d5 languageName: node linkType: hard -"@opentelemetry/sdk-node@npm:^0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/sdk-node@npm:0.202.0" +"@opentelemetry/sdk-node@npm:^0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/sdk-node@npm:0.213.0" dependencies: - "@opentelemetry/api-logs": "npm:0.202.0" - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/exporter-logs-otlp-grpc": "npm:0.202.0" - "@opentelemetry/exporter-logs-otlp-http": "npm:0.202.0" - "@opentelemetry/exporter-logs-otlp-proto": "npm:0.202.0" - "@opentelemetry/exporter-metrics-otlp-grpc": "npm:0.202.0" - "@opentelemetry/exporter-metrics-otlp-http": "npm:0.202.0" - "@opentelemetry/exporter-metrics-otlp-proto": "npm:0.202.0" - "@opentelemetry/exporter-prometheus": "npm:0.202.0" - "@opentelemetry/exporter-trace-otlp-grpc": "npm:0.202.0" - "@opentelemetry/exporter-trace-otlp-http": "npm:0.202.0" - "@opentelemetry/exporter-trace-otlp-proto": "npm:0.202.0" - "@opentelemetry/exporter-zipkin": "npm:2.0.1" - "@opentelemetry/instrumentation": "npm:0.202.0" - "@opentelemetry/propagator-b3": "npm:2.0.1" - "@opentelemetry/propagator-jaeger": "npm:2.0.1" - "@opentelemetry/resources": "npm:2.0.1" - "@opentelemetry/sdk-logs": "npm:0.202.0" - "@opentelemetry/sdk-metrics": "npm:2.0.1" - "@opentelemetry/sdk-trace-base": "npm:2.0.1" - "@opentelemetry/sdk-trace-node": "npm:2.0.1" + "@opentelemetry/api-logs": "npm:0.213.0" + "@opentelemetry/configuration": "npm:0.213.0" + "@opentelemetry/context-async-hooks": "npm:2.6.0" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/exporter-logs-otlp-grpc": "npm:0.213.0" + "@opentelemetry/exporter-logs-otlp-http": "npm:0.213.0" + "@opentelemetry/exporter-logs-otlp-proto": "npm:0.213.0" + "@opentelemetry/exporter-metrics-otlp-grpc": "npm:0.213.0" + "@opentelemetry/exporter-metrics-otlp-http": "npm:0.213.0" + "@opentelemetry/exporter-metrics-otlp-proto": "npm:0.213.0" + "@opentelemetry/exporter-prometheus": "npm:0.213.0" + "@opentelemetry/exporter-trace-otlp-grpc": "npm:0.213.0" + "@opentelemetry/exporter-trace-otlp-http": "npm:0.213.0" + "@opentelemetry/exporter-trace-otlp-proto": "npm:0.213.0" + "@opentelemetry/exporter-zipkin": "npm:2.6.0" + "@opentelemetry/instrumentation": "npm:0.213.0" + "@opentelemetry/propagator-b3": "npm:2.6.0" + "@opentelemetry/propagator-jaeger": "npm:2.6.0" + "@opentelemetry/resources": "npm:2.6.0" + "@opentelemetry/sdk-logs": "npm:0.213.0" + "@opentelemetry/sdk-metrics": "npm:2.6.0" + "@opentelemetry/sdk-trace-base": "npm:2.6.0" + "@opentelemetry/sdk-trace-node": "npm:2.6.0" "@opentelemetry/semantic-conventions": "npm:^1.29.0" peerDependencies: "@opentelemetry/api": ">=1.3.0 <1.10.0" - checksum: 10/80e3c6493bb5d87e5ce9c8b25dc5e2614c879b2ebe4f79f1a373d8237085b6e0bcaaff06acf01528e45849fb27e38cc0961fde6c87d7d2addf90cbcfbea43af7 + checksum: 10/2175a8ba71d3657b2a19972a2f45cca34c908e710b16b27aec1beb918c39b64170ade4c2c2059b49aeb3726044e176ce2caa4f4c6349af19ba8c74401601c843 languageName: node linkType: hard -"@opentelemetry/sdk-node@npm:^0.54.0": - version: 0.54.2 - resolution: "@opentelemetry/sdk-node@npm:0.54.2" +"@opentelemetry/sdk-trace-base@npm:2.6.0": + version: 2.6.0 + resolution: "@opentelemetry/sdk-trace-base@npm:2.6.0" dependencies: - "@opentelemetry/api-logs": "npm:0.54.2" - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/exporter-logs-otlp-grpc": "npm:0.54.2" - "@opentelemetry/exporter-logs-otlp-http": "npm:0.54.2" - "@opentelemetry/exporter-logs-otlp-proto": "npm:0.54.2" - "@opentelemetry/exporter-trace-otlp-grpc": "npm:0.54.2" - "@opentelemetry/exporter-trace-otlp-http": "npm:0.54.2" - "@opentelemetry/exporter-trace-otlp-proto": "npm:0.54.2" - "@opentelemetry/exporter-zipkin": "npm:1.27.0" - "@opentelemetry/instrumentation": "npm:0.54.2" - "@opentelemetry/resources": "npm:1.27.0" - "@opentelemetry/sdk-logs": "npm:0.54.2" - "@opentelemetry/sdk-metrics": "npm:1.27.0" - "@opentelemetry/sdk-trace-base": "npm:1.27.0" - "@opentelemetry/sdk-trace-node": "npm:1.27.0" - "@opentelemetry/semantic-conventions": "npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ">=1.3.0 <1.10.0" - checksum: 10/36de50763eb13ce720ab50670870562f9d12583d9b18d0d7c9dce8b8d5fa08bee1d0c12e8a3b17b5208e934e65091ab2f21f1828a593ed431d6dde51429b28a3 - languageName: node - linkType: hard - -"@opentelemetry/sdk-trace-base@npm:1.27.0": - version: 1.27.0 - resolution: "@opentelemetry/sdk-trace-base@npm:1.27.0" - dependencies: - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/resources": "npm:1.27.0" - "@opentelemetry/semantic-conventions": "npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/e0023dedbf5a50265729dd5467be0504f04f6b43d4cc4b914a9a8c082cca1aec9250a1f31c615f3d4ad03124af6e0ba1e14b39fe745d14291198f2a0990edc9c - languageName: node - linkType: hard - -"@opentelemetry/sdk-trace-base@npm:2.0.1": - version: 2.0.1 - resolution: "@opentelemetry/sdk-trace-base@npm:2.0.1" - dependencies: - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/resources": "npm:2.0.1" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/resources": "npm:2.6.0" "@opentelemetry/semantic-conventions": "npm:^1.29.0" peerDependencies: "@opentelemetry/api": ">=1.3.0 <1.10.0" - checksum: 10/9de1e36bbce9bd7c0563e6395765fffc0f8c78806cb33cc95267e98dffd82de33857a51288073a104c10418b934e51560bcb5dcaf4e63e5c9e096f65cadd42cd + checksum: 10/8ca3c1c4d7a95ec8a28ab5237162e31334216a59408e9d9d10ad51f5709911a405699ad69f445c212aad55fb6cc2c70f473b835e9e52bf4ed63f237c4d1813af languageName: node linkType: hard -"@opentelemetry/sdk-trace-node@npm:1.27.0": - version: 1.27.0 - resolution: "@opentelemetry/sdk-trace-node@npm:1.27.0" +"@opentelemetry/sdk-trace-node@npm:2.6.0": + version: 2.6.0 + resolution: "@opentelemetry/sdk-trace-node@npm:2.6.0" dependencies: - "@opentelemetry/context-async-hooks": "npm:1.27.0" - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/propagator-b3": "npm:1.27.0" - "@opentelemetry/propagator-jaeger": "npm:1.27.0" - "@opentelemetry/sdk-trace-base": "npm:1.27.0" - semver: "npm:^7.5.2" + "@opentelemetry/context-async-hooks": "npm:2.6.0" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/sdk-trace-base": "npm:2.6.0" peerDependencies: "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/53cff496312f0dedd819d2998219596a0d700348bf8ad315ff1d48a9a5e9be070623ed850633436f4ad006263b9cb55ffb6681d4174de99547d4edaa4125c98d - languageName: node - linkType: hard - -"@opentelemetry/sdk-trace-node@npm:2.0.1": - version: 2.0.1 - resolution: "@opentelemetry/sdk-trace-node@npm:2.0.1" - dependencies: - "@opentelemetry/context-async-hooks": "npm:2.0.1" - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/sdk-trace-base": "npm:2.0.1" - peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/7c5b4a902ba88ef03f02da83f6900080d81bb3264752419981dc44faabfe2bf4dbe91d25fec4a671afd5212151eee535a5205137772a8fd2c8d247950eb6556f - languageName: node - linkType: hard - -"@opentelemetry/semantic-conventions@npm:1.27.0": - version: 1.27.0 - resolution: "@opentelemetry/semantic-conventions@npm:1.27.0" - checksum: 10/98166522f299e2fe3d43376adbdeb92679b75ebb172e2a3c4c71f2942bd91585e9537618efbbae6dc08177699e5719368edf66d7e69e8636f360b85217bbdbe1 + checksum: 10/b1576f44198ae18ab36dea92e5c254eb6a96ca5793a5adbb96a01c586dd992e3532eb291c9ea225e2cd6979f7f6dd3fca2fad56ab360ba573f8b3bfb462dd2aa languageName: node linkType: hard @@ -14156,10 +14271,10 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/semantic-conventions@npm:^1.27.0, @opentelemetry/semantic-conventions@npm:^1.29.0, @opentelemetry/semantic-conventions@npm:^1.30.0, @opentelemetry/semantic-conventions@npm:^1.33.1, @opentelemetry/semantic-conventions@npm:^1.34.0": - version: 1.34.0 - resolution: "@opentelemetry/semantic-conventions@npm:1.34.0" - checksum: 10/1892b4cc69c9e00456c809604a980e32696563e96463ff5f9d07e72d5aca73836a7378090509f28f54445ac6e072d2343a888c9d64d9ce287198e899082ff7aa +"@opentelemetry/semantic-conventions@npm:^1.24.0, @opentelemetry/semantic-conventions@npm:^1.27.0, @opentelemetry/semantic-conventions@npm:^1.29.0, @opentelemetry/semantic-conventions@npm:^1.30.0, @opentelemetry/semantic-conventions@npm:^1.33.0, @opentelemetry/semantic-conventions@npm:^1.33.1, @opentelemetry/semantic-conventions@npm:^1.34.0, @opentelemetry/semantic-conventions@npm:^1.36.0, @opentelemetry/semantic-conventions@npm:^1.37.0": + version: 1.38.0 + resolution: "@opentelemetry/semantic-conventions@npm:1.38.0" + checksum: 10/9d549f4896e900f644d5e70dd7142505daff88ed83c1cb7bcd976ac55e9496d4ddd686bb2815dd68655c739950514394c3b73ff51e53b2e4ff2d54a7f6d22521 languageName: node linkType: hard @@ -14170,14 +14285,156 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/sql-common@npm:^0.41.0": - version: 0.41.0 - resolution: "@opentelemetry/sql-common@npm:0.41.0" +"@opentelemetry/sql-common@npm:^0.41.2": + version: 0.41.2 + resolution: "@opentelemetry/sql-common@npm:0.41.2" dependencies: "@opentelemetry/core": "npm:^2.0.0" peerDependencies: "@opentelemetry/api": ^1.1.0 - checksum: 10/182915f050b8b685f5499e9fa4ed9a9730fcd404889e5066cfc7d94e9961b1f2780216d776070a4a5c1b94323c287ab426b06f5a8828d5fea3daf664bfd397bb + checksum: 10/3d57d5162c69c29484cb166e99ac733fff1dcefa26aea401e40035d5daa3aef21af78936af7943d0dfdab385ff053b879117c2fa3bd980412dd378222b10bea3 + languageName: node + linkType: hard + +"@oxc-resolver/binding-android-arm-eabi@npm:11.19.1": + version: 11.19.1 + resolution: "@oxc-resolver/binding-android-arm-eabi@npm:11.19.1" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@oxc-resolver/binding-android-arm64@npm:11.19.1": + version: 11.19.1 + resolution: "@oxc-resolver/binding-android-arm64@npm:11.19.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@oxc-resolver/binding-darwin-arm64@npm:11.19.1": + version: 11.19.1 + resolution: "@oxc-resolver/binding-darwin-arm64@npm:11.19.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@oxc-resolver/binding-darwin-x64@npm:11.19.1": + version: 11.19.1 + resolution: "@oxc-resolver/binding-darwin-x64@npm:11.19.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@oxc-resolver/binding-freebsd-x64@npm:11.19.1": + version: 11.19.1 + resolution: "@oxc-resolver/binding-freebsd-x64@npm:11.19.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-arm-gnueabihf@npm:11.19.1": + version: 11.19.1 + resolution: "@oxc-resolver/binding-linux-arm-gnueabihf@npm:11.19.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-arm-musleabihf@npm:11.19.1": + version: 11.19.1 + resolution: "@oxc-resolver/binding-linux-arm-musleabihf@npm:11.19.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-arm64-gnu@npm:11.19.1": + version: 11.19.1 + resolution: "@oxc-resolver/binding-linux-arm64-gnu@npm:11.19.1" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-arm64-musl@npm:11.19.1": + version: 11.19.1 + resolution: "@oxc-resolver/binding-linux-arm64-musl@npm:11.19.1" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-ppc64-gnu@npm:11.19.1": + version: 11.19.1 + resolution: "@oxc-resolver/binding-linux-ppc64-gnu@npm:11.19.1" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-riscv64-gnu@npm:11.19.1": + version: 11.19.1 + resolution: "@oxc-resolver/binding-linux-riscv64-gnu@npm:11.19.1" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-riscv64-musl@npm:11.19.1": + version: 11.19.1 + resolution: "@oxc-resolver/binding-linux-riscv64-musl@npm:11.19.1" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-s390x-gnu@npm:11.19.1": + version: 11.19.1 + resolution: "@oxc-resolver/binding-linux-s390x-gnu@npm:11.19.1" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-x64-gnu@npm:11.19.1": + version: 11.19.1 + resolution: "@oxc-resolver/binding-linux-x64-gnu@npm:11.19.1" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-x64-musl@npm:11.19.1": + version: 11.19.1 + resolution: "@oxc-resolver/binding-linux-x64-musl@npm:11.19.1" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@oxc-resolver/binding-openharmony-arm64@npm:11.19.1": + version: 11.19.1 + resolution: "@oxc-resolver/binding-openharmony-arm64@npm:11.19.1" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@oxc-resolver/binding-wasm32-wasi@npm:11.19.1": + version: 11.19.1 + resolution: "@oxc-resolver/binding-wasm32-wasi@npm:11.19.1" + dependencies: + "@napi-rs/wasm-runtime": "npm:^1.1.1" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@oxc-resolver/binding-win32-arm64-msvc@npm:11.19.1": + version: 11.19.1 + resolution: "@oxc-resolver/binding-win32-arm64-msvc@npm:11.19.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@oxc-resolver/binding-win32-ia32-msvc@npm:11.19.1": + version: 11.19.1 + resolution: "@oxc-resolver/binding-win32-ia32-msvc@npm:11.19.1" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@oxc-resolver/binding-win32-x64-msvc@npm:11.19.1": + version: 11.19.1 + resolution: "@oxc-resolver/binding-win32-x64-msvc@npm:11.19.1" + conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -14190,6 +14447,151 @@ __metadata: languageName: node linkType: hard +"@peculiar/asn1-cms@npm:^2.6.0": + version: 2.6.0 + resolution: "@peculiar/asn1-cms@npm:2.6.0" + dependencies: + "@peculiar/asn1-schema": "npm:^2.6.0" + "@peculiar/asn1-x509": "npm:^2.6.0" + "@peculiar/asn1-x509-attr": "npm:^2.6.0" + asn1js: "npm:^3.0.6" + tslib: "npm:^2.8.1" + checksum: 10/cc3f2c60d87ecd400fe5409dc0016578c8c80511ae1295747913c5704adeb571136f1b779362996879acdb81efb34735f3fae6f8513fae4542dd004ae4615b13 + languageName: node + linkType: hard + +"@peculiar/asn1-csr@npm:^2.6.0": + version: 2.6.0 + resolution: "@peculiar/asn1-csr@npm:2.6.0" + dependencies: + "@peculiar/asn1-schema": "npm:^2.6.0" + "@peculiar/asn1-x509": "npm:^2.6.0" + asn1js: "npm:^3.0.6" + tslib: "npm:^2.8.1" + checksum: 10/68653246ae56119722ca737bddd8a3edc1dd0e2f4bcc58d611b62512667073b9ccd61a0051ca8f0a67cf6d07245ecbdbf526e6f389c81ef81e845c46a2bc5bbb + languageName: node + linkType: hard + +"@peculiar/asn1-ecc@npm:^2.6.0": + version: 2.6.0 + resolution: "@peculiar/asn1-ecc@npm:2.6.0" + dependencies: + "@peculiar/asn1-schema": "npm:^2.6.0" + "@peculiar/asn1-x509": "npm:^2.6.0" + asn1js: "npm:^3.0.6" + tslib: "npm:^2.8.1" + checksum: 10/f31146a78c634440d49e0b1959c8ba59657e0fd172c2f9aff421627aee954cf3dcaa9c9b957390960d107cc460f277b9266c95cf32e434ba6b5475f87fad6436 + languageName: node + linkType: hard + +"@peculiar/asn1-pfx@npm:^2.6.0": + version: 2.6.0 + resolution: "@peculiar/asn1-pfx@npm:2.6.0" + dependencies: + "@peculiar/asn1-cms": "npm:^2.6.0" + "@peculiar/asn1-pkcs8": "npm:^2.6.0" + "@peculiar/asn1-rsa": "npm:^2.6.0" + "@peculiar/asn1-schema": "npm:^2.6.0" + asn1js: "npm:^3.0.6" + tslib: "npm:^2.8.1" + checksum: 10/623ffda2b04822140afd0a6e1846089a55f986686d72dfb1c6eaad67791a0aaa519e13a8528f72de1a930739318ea7a87eb11d7de0c3587f950cb45338ac302b + languageName: node + linkType: hard + +"@peculiar/asn1-pkcs8@npm:^2.6.0": + version: 2.6.0 + resolution: "@peculiar/asn1-pkcs8@npm:2.6.0" + dependencies: + "@peculiar/asn1-schema": "npm:^2.6.0" + "@peculiar/asn1-x509": "npm:^2.6.0" + asn1js: "npm:^3.0.6" + tslib: "npm:^2.8.1" + checksum: 10/f76334284f5c9833eff807948363f7835ac311cb59a82dd92757960c8f8b15c8ea52bcee20b4ff21dd0adb4c7db51411cb87a612c2e28c66b4426366a302f6d8 + languageName: node + linkType: hard + +"@peculiar/asn1-pkcs9@npm:^2.6.0": + version: 2.6.0 + resolution: "@peculiar/asn1-pkcs9@npm:2.6.0" + dependencies: + "@peculiar/asn1-cms": "npm:^2.6.0" + "@peculiar/asn1-pfx": "npm:^2.6.0" + "@peculiar/asn1-pkcs8": "npm:^2.6.0" + "@peculiar/asn1-schema": "npm:^2.6.0" + "@peculiar/asn1-x509": "npm:^2.6.0" + "@peculiar/asn1-x509-attr": "npm:^2.6.0" + asn1js: "npm:^3.0.6" + tslib: "npm:^2.8.1" + checksum: 10/ab2cfd497c1585fa89c2a257c61edbb6b5a4182fc3cec0c255782d6480932732bb3b9f47c3dc1706e65f7b683dab615d9ae9c235071bee366645a95b4cc70b06 + languageName: node + linkType: hard + +"@peculiar/asn1-rsa@npm:^2.6.0": + version: 2.6.0 + resolution: "@peculiar/asn1-rsa@npm:2.6.0" + dependencies: + "@peculiar/asn1-schema": "npm:^2.6.0" + "@peculiar/asn1-x509": "npm:^2.6.0" + asn1js: "npm:^3.0.6" + tslib: "npm:^2.8.1" + checksum: 10/9084becd2d06d3b0fc8baafcebb5bdd085cc3be5221ce7f7017ae575b0abc59cd1eaeb880b475aae5a28c97eda3551ab620ba0a0a5eb72b12b8db103182d8cf4 + languageName: node + linkType: hard + +"@peculiar/asn1-schema@npm:^2.6.0": + version: 2.6.0 + resolution: "@peculiar/asn1-schema@npm:2.6.0" + dependencies: + asn1js: "npm:^3.0.6" + pvtsutils: "npm:^1.3.6" + tslib: "npm:^2.8.1" + checksum: 10/af9b1094d0e020f0fd828777488578322d62a41f597ead7d80939dafcfe35b672fcb0ec7460ef66b2a155f9614d4340a98896d417a830aff1685cb4c21d5bbe4 + languageName: node + linkType: hard + +"@peculiar/asn1-x509-attr@npm:^2.6.0": + version: 2.6.0 + resolution: "@peculiar/asn1-x509-attr@npm:2.6.0" + dependencies: + "@peculiar/asn1-schema": "npm:^2.6.0" + "@peculiar/asn1-x509": "npm:^2.6.0" + asn1js: "npm:^3.0.6" + tslib: "npm:^2.8.1" + checksum: 10/9ecd7a2e57e2199cc937691fc6e8fdce0d25039e2d16b2261ba5b7550250b903fd9d9f8bf3f5290eb23332fdb289abb79656c97477bf711af6897235312e0535 + languageName: node + linkType: hard + +"@peculiar/asn1-x509@npm:^2.6.0": + version: 2.6.0 + resolution: "@peculiar/asn1-x509@npm:2.6.0" + dependencies: + "@peculiar/asn1-schema": "npm:^2.6.0" + asn1js: "npm:^3.0.6" + pvtsutils: "npm:^1.3.6" + tslib: "npm:^2.8.1" + checksum: 10/61e5ef1bb83fa4687b0c307b9e10d982bd4b2c09af7d66d1d4c486a190dab9ebc15ae382880229d3678743a6fa32081d21d6c2d18a7bbe9d634fc09c46c244b6 + languageName: node + linkType: hard + +"@peculiar/x509@npm:^1.14.2": + version: 1.14.3 + resolution: "@peculiar/x509@npm:1.14.3" + dependencies: + "@peculiar/asn1-cms": "npm:^2.6.0" + "@peculiar/asn1-csr": "npm:^2.6.0" + "@peculiar/asn1-ecc": "npm:^2.6.0" + "@peculiar/asn1-pkcs9": "npm:^2.6.0" + "@peculiar/asn1-rsa": "npm:^2.6.0" + "@peculiar/asn1-schema": "npm:^2.6.0" + "@peculiar/asn1-x509": "npm:^2.6.0" + pvtsutils: "npm:^1.3.6" + reflect-metadata: "npm:^0.2.2" + tslib: "npm:^2.8.1" + tsyringe: "npm:^4.10.0" + checksum: 10/d37c56fa5f2c644141948d85010e14f0e4963089e3b0b81edd0bfe85bdfea0eb3f38ab6ff20d322db2bd6977117824cc498a77b2d35af111983b4d58b5e2ccd1 + languageName: node + linkType: hard + "@pkgjs/parseargs@npm:^0.11.0": version: 0.11.0 resolution: "@pkgjs/parseargs@npm:0.11.0" @@ -14197,37 +14599,43 @@ __metadata: languageName: node linkType: hard -"@playwright/test@npm:^1.32.3": - version: 1.56.1 - resolution: "@playwright/test@npm:1.56.1" - dependencies: - playwright: "npm:1.56.1" - bin: - playwright: cli.js - checksum: 10/9933fa9f8eb9e775e792421b99c984c310b92092e65de57508ae1951a2589d87bbb5f1c4114bfdf7f69c15c0a3acb3f31259e143fa597aa28e97f4b223e37637 +"@pkgr/core@npm:^0.2.9": + version: 0.2.9 + resolution: "@pkgr/core@npm:0.2.9" + checksum: 10/bb2fb86977d63f836f8f5b09015d74e6af6488f7a411dcd2bfdca79d76b5a681a9112f41c45bdf88a9069f049718efc6f3900d7f1de66a2ec966068308ae517f languageName: node linkType: hard -"@pmmmwh/react-refresh-webpack-plugin@npm:^0.5.7": - version: 0.5.16 - resolution: "@pmmmwh/react-refresh-webpack-plugin@npm:0.5.16" +"@playwright/test@npm:^1.32.3, @playwright/test@npm:^1.58.2": + version: 1.58.2 + resolution: "@playwright/test@npm:1.58.2" dependencies: - ansi-html: "npm:^0.0.9" + playwright: "npm:1.58.2" + bin: + playwright: cli.js + checksum: 10/58bf90139280a0235eeeb6049e9fb4db6425e98be1bf0cc17913b068eef616cf67be57bfb36dc4cb56bcf116f498ffd0225c4916e85db404b343ea6c5efdae13 + languageName: node + linkType: hard + +"@pmmmwh/react-refresh-webpack-plugin@npm:^0.6.0": + version: 0.6.2 + resolution: "@pmmmwh/react-refresh-webpack-plugin@npm:0.6.2" + dependencies: + anser: "npm:^2.1.1" core-js-pure: "npm:^3.23.3" error-stack-parser: "npm:^2.0.6" html-entities: "npm:^2.1.0" - loader-utils: "npm:^2.0.4" schema-utils: "npm:^4.2.0" source-map: "npm:^0.7.3" peerDependencies: - "@types/webpack": 4.x || 5.x + "@types/webpack": 5.x react-refresh: ">=0.10.0 <1.0.0" sockjs-client: ^1.4.0 - type-fest: ">=0.17.0 <5.0.0" - webpack: ">=4.43.0 <6.0.0" - webpack-dev-server: 3.x || 4.x || 5.x + type-fest: ">=0.17.0 <6.0.0" + webpack: ^5.0.0 + webpack-dev-server: ^4.8.0 || 5.x webpack-hot-middleware: 2.x - webpack-plugin-serve: 0.x || 1.x + webpack-plugin-serve: 1.x peerDependenciesMeta: "@types/webpack": optional: true @@ -14241,7 +14649,7 @@ __metadata: optional: true webpack-plugin-serve: optional: true - checksum: 10/d7ac4c58a377d37a4dc2f19d1434a14f83ff73d38fd7e374bdab2b2b73b7dc398803c1fb6eb84e8787417bffdc1735f83f65f55d8acac30a800de7122c16428e + checksum: 10/157a20464b0bdca39b31e09450f6ce1d91cbe32bcce882a02df797482b0226247d13e5a2c750a3fb2e2758bffc25d49a468b33b66d8364aa4e1ed785596453c8 languageName: node linkType: hard @@ -14268,6 +14676,17 @@ __metadata: languageName: node linkType: hard +"@prettier/sync@npm:^0.6.1": + version: 0.6.1 + resolution: "@prettier/sync@npm:0.6.1" + dependencies: + make-synchronized: "npm:^0.8.0" + peerDependencies: + prettier: "*" + checksum: 10/2c53cd4ee718e2ebd2fb31aa5ec4773f743b9c29fcc6db6794dc3553bc87aa8fe7db47b51add6809cab655520b7550329d1cce2ca837f6f4643991eff44abad1 + languageName: node + linkType: hard + "@protobufjs/aspromise@npm:^1.1.1, @protobufjs/aspromise@npm:^1.1.2": version: 1.1.2 resolution: "@protobufjs/aspromise@npm:1.1.2" @@ -14341,1486 +14760,595 @@ __metadata: languageName: node linkType: hard -"@radix-ui/primitive@npm:1.0.1": - version: 1.0.1 - resolution: "@radix-ui/primitive@npm:1.0.1" - dependencies: - "@babel/runtime": "npm:^7.13.10" - checksum: 10/2b93e161d3fdabe9a64919def7fa3ceaecf2848341e9211520c401181c9eaebb8451c630b066fad2256e5c639c95edc41de0ba59c40eff37e799918d019822d1 +"@radix-ui/primitive@npm:1.1.3": + version: 1.1.3 + resolution: "@radix-ui/primitive@npm:1.1.3" + checksum: 10/ee27abbff0d6d305816e9314655eb35e72478ba47416bc9d5cb0581728be35e3408cfc0748313837561d635f0cb7dfaae26e61831f0e16c0fd7d669a612f2cb0 languageName: node linkType: hard -"@radix-ui/react-arrow@npm:1.0.3": - version: 1.0.3 - resolution: "@radix-ui/react-arrow@npm:1.0.3" +"@radix-ui/react-arrow@npm:1.1.7": + version: 1.1.7 + resolution: "@radix-ui/react-arrow@npm:1.1.7" dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/react-primitive": "npm:1.0.3" + "@radix-ui/react-primitive": "npm:2.1.3" peerDependencies: "@types/react": "*" "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true "@types/react-dom": optional: true - checksum: 10/8cca086f0dbb33360e3c0142adf72f99fc96352d7086d6c2356dbb2ea5944cfb720a87d526fc48087741c602cd8162ca02b0af5e6fdf5f56d20fddb44db8b4c3 + checksum: 10/6cdf74f06090f8994cdf6d3935a44ea3ac309163a4f59c476482c4907e8e0775f224045030abf10fa4f9e1cb7743db034429249b9e59354988e247eeb0f4fdcf languageName: node linkType: hard -"@radix-ui/react-collection@npm:1.0.3": - version: 1.0.3 - resolution: "@radix-ui/react-collection@npm:1.0.3" +"@radix-ui/react-collection@npm:1.1.7": + version: 1.1.7 + resolution: "@radix-ui/react-collection@npm:1.1.7" dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/react-compose-refs": "npm:1.0.1" - "@radix-ui/react-context": "npm:1.0.1" - "@radix-ui/react-primitive": "npm:1.0.3" - "@radix-ui/react-slot": "npm:1.0.2" + "@radix-ui/react-compose-refs": "npm:1.1.2" + "@radix-ui/react-context": "npm:1.1.2" + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-slot": "npm:1.2.3" peerDependencies: "@types/react": "*" "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true "@types/react-dom": optional: true - checksum: 10/2ac740ab746f411942dc95100f1eb60b9a3670960a805e266533fa1bc7dec31a6dabddd746ab788ebd5a9c22b468e38922f39d30447925515f8e44f0a3b2e56c + checksum: 10/cd53e2a2be82be7bc4014164cac0b42948401a203e5d0294d3947a5193f1d56bd23eb60e878a98dba50d08283254e79c3b873de5f935276b849686a868d51dd5 languageName: node linkType: hard -"@radix-ui/react-compose-refs@npm:1.0.1": - version: 1.0.1 - resolution: "@radix-ui/react-compose-refs@npm:1.0.1" - dependencies: - "@babel/runtime": "npm:^7.13.10" +"@radix-ui/react-compose-refs@npm:1.1.2": + version: 1.1.2 + resolution: "@radix-ui/react-compose-refs@npm:1.1.2" peerDependencies: "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10/2b9a613b6db5bff8865588b6bf4065f73021b3d16c0a90b2d4c23deceeb63612f1f15de188227ebdc5f88222cab031be617a9dd025874c0487b303be3e5cc2a8 + checksum: 10/9a91f0213014ffa40c5b8aae4debb993be5654217e504e35aa7422887eb2d114486d37e53c482d0fffb00cd44f51b5269fcdf397b280c71666fa11b7f32f165d languageName: node linkType: hard -"@radix-ui/react-context@npm:1.0.1": - version: 1.0.1 - resolution: "@radix-ui/react-context@npm:1.0.1" - dependencies: - "@babel/runtime": "npm:^7.13.10" +"@radix-ui/react-context@npm:1.1.2": + version: 1.1.2 + resolution: "@radix-ui/react-context@npm:1.1.2" peerDependencies: "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10/a02187a3bae3a0f1be5fab5ad19c1ef06ceff1028d957e4d9994f0186f594a9c3d93ee34bacb86d1fa8eb274493362944398e1c17054d12cb3b75384f9ae564b + checksum: 10/156088367de42afa3c7e3acf5f0ba7cad6b359f3d17485585e80c2418434a6ed7cac2602eb73bca265d0091a1ad380f9405c069f103983e53497097ff35ba8f2 languageName: node linkType: hard "@radix-ui/react-dialog@npm:^1.0.4": - version: 1.0.5 - resolution: "@radix-ui/react-dialog@npm:1.0.5" + version: 1.1.15 + resolution: "@radix-ui/react-dialog@npm:1.1.15" dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/primitive": "npm:1.0.1" - "@radix-ui/react-compose-refs": "npm:1.0.1" - "@radix-ui/react-context": "npm:1.0.1" - "@radix-ui/react-dismissable-layer": "npm:1.0.5" - "@radix-ui/react-focus-guards": "npm:1.0.1" - "@radix-ui/react-focus-scope": "npm:1.0.4" - "@radix-ui/react-id": "npm:1.0.1" - "@radix-ui/react-portal": "npm:1.0.4" - "@radix-ui/react-presence": "npm:1.0.1" - "@radix-ui/react-primitive": "npm:1.0.3" - "@radix-ui/react-slot": "npm:1.0.2" - "@radix-ui/react-use-controllable-state": "npm:1.0.1" - aria-hidden: "npm:^1.1.1" - react-remove-scroll: "npm:2.5.5" + "@radix-ui/primitive": "npm:1.1.3" + "@radix-ui/react-compose-refs": "npm:1.1.2" + "@radix-ui/react-context": "npm:1.1.2" + "@radix-ui/react-dismissable-layer": "npm:1.1.11" + "@radix-ui/react-focus-guards": "npm:1.1.3" + "@radix-ui/react-focus-scope": "npm:1.1.7" + "@radix-ui/react-id": "npm:1.1.1" + "@radix-ui/react-portal": "npm:1.1.9" + "@radix-ui/react-presence": "npm:1.1.5" + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-slot": "npm:1.2.3" + "@radix-ui/react-use-controllable-state": "npm:1.2.2" + aria-hidden: "npm:^1.2.4" + react-remove-scroll: "npm:^2.6.3" peerDependencies: "@types/react": "*" "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true "@types/react-dom": optional: true - checksum: 10/adbd7301586db712616a0f8dd54a25e7544853cbf61b5d6e279215d479f57ac35157847ee424d54a7e707969a926ca0a7c28934400c9ac224bd0c7cc19229aca + checksum: 10/90ad9ea36d927a05bcc2701b471c2965f6d5d4f446511cd471e62235fc674186997dea081f52e18cb17a1e593828d94da3848e68864fa3acebe29df9b068b240 languageName: node linkType: hard -"@radix-ui/react-direction@npm:1.0.1": - version: 1.0.1 - resolution: "@radix-ui/react-direction@npm:1.0.1" - dependencies: - "@babel/runtime": "npm:^7.13.10" +"@radix-ui/react-direction@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-direction@npm:1.1.1" peerDependencies: "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10/5336a8b0d4f1cde585d5c2b4448af7b3d948bb63a1aadb37c77771b0e5902dc6266e409cf35fd0edaca7f33e26424be19e64fb8f9d7f7be2d6f1714ea2764210 + checksum: 10/8cc330285f1d06829568042ca9aabd3295be4690ae93683033fc8632b5c4dfc60f5c1312f6e2cae27c196189c719de3cfbcf792ff74800f9ccae0ab4abc1bc92 languageName: node linkType: hard -"@radix-ui/react-dismissable-layer@npm:1.0.5": - version: 1.0.5 - resolution: "@radix-ui/react-dismissable-layer@npm:1.0.5" +"@radix-ui/react-dismissable-layer@npm:1.1.11": + version: 1.1.11 + resolution: "@radix-ui/react-dismissable-layer@npm:1.1.11" dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/primitive": "npm:1.0.1" - "@radix-ui/react-compose-refs": "npm:1.0.1" - "@radix-ui/react-primitive": "npm:1.0.3" - "@radix-ui/react-use-callback-ref": "npm:1.0.1" - "@radix-ui/react-use-escape-keydown": "npm:1.0.3" + "@radix-ui/primitive": "npm:1.1.3" + "@radix-ui/react-compose-refs": "npm:1.1.2" + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-use-callback-ref": "npm:1.1.1" + "@radix-ui/react-use-escape-keydown": "npm:1.1.1" peerDependencies: "@types/react": "*" "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true "@types/react-dom": optional: true - checksum: 10/f1626d69bb50ec226032bb7d8c5abaaf7359c2d7660309b0ed3daaedd91f30717573aac1a1cb82d589b7f915cf464b95a12da0a3b91b6acfefb6fbbc62b992de + checksum: 10/c20772588423379dee47fbe1d45c238c45a3bbe612eaf64a86576bf81821975e256d92ac71f9151e91b94a73068656143a11da9a3e77de7564d2a9926468e37a languageName: node linkType: hard "@radix-ui/react-dropdown-menu@npm:^2.0.5": - version: 2.0.6 - resolution: "@radix-ui/react-dropdown-menu@npm:2.0.6" + version: 2.1.16 + resolution: "@radix-ui/react-dropdown-menu@npm:2.1.16" dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/primitive": "npm:1.0.1" - "@radix-ui/react-compose-refs": "npm:1.0.1" - "@radix-ui/react-context": "npm:1.0.1" - "@radix-ui/react-id": "npm:1.0.1" - "@radix-ui/react-menu": "npm:2.0.6" - "@radix-ui/react-primitive": "npm:1.0.3" - "@radix-ui/react-use-controllable-state": "npm:1.0.1" + "@radix-ui/primitive": "npm:1.1.3" + "@radix-ui/react-compose-refs": "npm:1.1.2" + "@radix-ui/react-context": "npm:1.1.2" + "@radix-ui/react-id": "npm:1.1.1" + "@radix-ui/react-menu": "npm:2.1.16" + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-use-controllable-state": "npm:1.2.2" peerDependencies: "@types/react": "*" "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true "@types/react-dom": optional: true - checksum: 10/efa0728a25ea6689c6f31e02025528a21ca3bdc8a905c551ff356f3a66e024ef7fda62dc38564ac1310b211685357e37329616c72e371974d6bded4170ab43a2 + checksum: 10/da215196b5dde5619cdb424b1b5236159e4bb949974b7f4ffbf047d467c55116229a8f9cf07eae6457afefb4a2b07888bb30542f303045e05d90a4b072941ae2 languageName: node linkType: hard -"@radix-ui/react-focus-guards@npm:1.0.1": - version: 1.0.1 - resolution: "@radix-ui/react-focus-guards@npm:1.0.1" - dependencies: - "@babel/runtime": "npm:^7.13.10" - peerDependencies: - "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10/1f8ca8f83b884b3612788d0742f3f054e327856d90a39841a47897dbed95e114ee512362ae314177de226d05310047cabbf66b686ae86ad1b65b6b295be24ef7 - languageName: node - linkType: hard - -"@radix-ui/react-focus-scope@npm:1.0.4": - version: 1.0.4 - resolution: "@radix-ui/react-focus-scope@npm:1.0.4" - dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/react-compose-refs": "npm:1.0.1" - "@radix-ui/react-primitive": "npm:1.0.3" - "@radix-ui/react-use-callback-ref": "npm:1.0.1" - peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - "@types/react": - optional: true - "@types/react-dom": - optional: true - checksum: 10/3590e74c6b682737c7ac4bf8db41b3df7b09a0320f3836c619e487df9915451e5dafade9923a74383a7366c59e9436f5fff4301d70c0d15928e0e16b36e58bc9 - languageName: node - linkType: hard - -"@radix-ui/react-id@npm:1.0.1": - version: 1.0.1 - resolution: "@radix-ui/react-id@npm:1.0.1" - dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/react-use-layout-effect": "npm:1.0.1" - peerDependencies: - "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10/446a453d799cc790dd2a1583ff8328da88271bff64530b5a17c102fa7fb35eece3cf8985359d416f65e330cd81aa7b8fe984ea125fc4f4eaf4b3801d698e49fe - languageName: node - linkType: hard - -"@radix-ui/react-menu@npm:2.0.6": - version: 2.0.6 - resolution: "@radix-ui/react-menu@npm:2.0.6" - dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/primitive": "npm:1.0.1" - "@radix-ui/react-collection": "npm:1.0.3" - "@radix-ui/react-compose-refs": "npm:1.0.1" - "@radix-ui/react-context": "npm:1.0.1" - "@radix-ui/react-direction": "npm:1.0.1" - "@radix-ui/react-dismissable-layer": "npm:1.0.5" - "@radix-ui/react-focus-guards": "npm:1.0.1" - "@radix-ui/react-focus-scope": "npm:1.0.4" - "@radix-ui/react-id": "npm:1.0.1" - "@radix-ui/react-popper": "npm:1.1.3" - "@radix-ui/react-portal": "npm:1.0.4" - "@radix-ui/react-presence": "npm:1.0.1" - "@radix-ui/react-primitive": "npm:1.0.3" - "@radix-ui/react-roving-focus": "npm:1.0.4" - "@radix-ui/react-slot": "npm:1.0.2" - "@radix-ui/react-use-callback-ref": "npm:1.0.1" - aria-hidden: "npm:^1.1.1" - react-remove-scroll: "npm:2.5.5" - peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - "@types/react": - optional: true - "@types/react-dom": - optional: true - checksum: 10/8e8c41a46f4fab25b53c5400876f611372491e252d8ef763c3608e571df5aae5524c0a9c210780039b0de6d62affedaa16d189dd4c0148da0984f8f809311032 - languageName: node - linkType: hard - -"@radix-ui/react-popper@npm:1.1.3": +"@radix-ui/react-focus-guards@npm:1.1.3": version: 1.1.3 - resolution: "@radix-ui/react-popper@npm:1.1.3" + resolution: "@radix-ui/react-focus-guards@npm:1.1.3" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/b57878f6cf0ebc3e8d7c5c6bbaad44598daac19c921551ca541c104201048a9a902f3d69196e7a09995fd46e998c309aab64dc30fa184b3609d67d187a6a9c24 + languageName: node + linkType: hard + +"@radix-ui/react-focus-scope@npm:1.1.7": + version: 1.1.7 + resolution: "@radix-ui/react-focus-scope@npm:1.1.7" + dependencies: + "@radix-ui/react-compose-refs": "npm:1.1.2" + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-use-callback-ref": "npm:1.1.1" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/2a7cd00e39e01756999ebf0bdb3401d6a8efa489a7b19e6b629b40bad3022b7b1f616555ccb4b0505bc0ba53e13a1fb51be905db138b16ec39c4fe319fe701d3 + languageName: node + linkType: hard + +"@radix-ui/react-id@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-id@npm:1.1.1" + dependencies: + "@radix-ui/react-use-layout-effect": "npm:1.1.1" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/8d68e200778eb3038906870fc869b3d881f4a46715fb20cddd9c76cba42fdaaa4810a3365b6ec2daf0f185b9201fc99d009167f59c7921bc3a139722c2e976db + languageName: node + linkType: hard + +"@radix-ui/react-menu@npm:2.1.16": + version: 2.1.16 + resolution: "@radix-ui/react-menu@npm:2.1.16" + dependencies: + "@radix-ui/primitive": "npm:1.1.3" + "@radix-ui/react-collection": "npm:1.1.7" + "@radix-ui/react-compose-refs": "npm:1.1.2" + "@radix-ui/react-context": "npm:1.1.2" + "@radix-ui/react-direction": "npm:1.1.1" + "@radix-ui/react-dismissable-layer": "npm:1.1.11" + "@radix-ui/react-focus-guards": "npm:1.1.3" + "@radix-ui/react-focus-scope": "npm:1.1.7" + "@radix-ui/react-id": "npm:1.1.1" + "@radix-ui/react-popper": "npm:1.2.8" + "@radix-ui/react-portal": "npm:1.1.9" + "@radix-ui/react-presence": "npm:1.1.5" + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-roving-focus": "npm:1.1.11" + "@radix-ui/react-slot": "npm:1.2.3" + "@radix-ui/react-use-callback-ref": "npm:1.1.1" + aria-hidden: "npm:^1.2.4" + react-remove-scroll: "npm:^2.6.3" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/2ffdfa08822c8c4ffc265d02d16c83d725114f9c0e9b510e73e431306dedddd507ef2861ccd67ec8c0d21cb24cd6401e42f16f3e65b30be627c7e22159151e40 + languageName: node + linkType: hard + +"@radix-ui/react-popper@npm:1.2.8": + version: 1.2.8 + resolution: "@radix-ui/react-popper@npm:1.2.8" dependencies: - "@babel/runtime": "npm:^7.13.10" "@floating-ui/react-dom": "npm:^2.0.0" - "@radix-ui/react-arrow": "npm:1.0.3" - "@radix-ui/react-compose-refs": "npm:1.0.1" - "@radix-ui/react-context": "npm:1.0.1" - "@radix-ui/react-primitive": "npm:1.0.3" - "@radix-ui/react-use-callback-ref": "npm:1.0.1" - "@radix-ui/react-use-layout-effect": "npm:1.0.1" - "@radix-ui/react-use-rect": "npm:1.0.1" - "@radix-ui/react-use-size": "npm:1.0.1" - "@radix-ui/rect": "npm:1.0.1" + "@radix-ui/react-arrow": "npm:1.1.7" + "@radix-ui/react-compose-refs": "npm:1.1.2" + "@radix-ui/react-context": "npm:1.1.2" + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-use-callback-ref": "npm:1.1.1" + "@radix-ui/react-use-layout-effect": "npm:1.1.1" + "@radix-ui/react-use-rect": "npm:1.1.1" + "@radix-ui/react-use-size": "npm:1.1.1" + "@radix-ui/rect": "npm:1.1.1" peerDependencies: "@types/react": "*" "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true "@types/react-dom": optional: true - checksum: 10/1f70ca09b609122058a58f57fa6bce7e528d96552c9db1a1d214e8e4a9dd305e473dfa0ac7dd400d3d215e54b5cf31020199aca3c2728dc1a716f4c7510838a5 + checksum: 10/01366054e1e63dd9394f77afb9da3367709478a5adf4436c080fc5bbe9456170192ff9d1425d9fae5b246e1ba95173848f84b6f2a06b21b47d966367ec7cb997 languageName: node linkType: hard -"@radix-ui/react-portal@npm:1.0.4": - version: 1.0.4 - resolution: "@radix-ui/react-portal@npm:1.0.4" +"@radix-ui/react-portal@npm:1.1.9": + version: 1.1.9 + resolution: "@radix-ui/react-portal@npm:1.1.9" dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/react-primitive": "npm:1.0.3" + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-use-layout-effect": "npm:1.1.1" peerDependencies: "@types/react": "*" "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true "@types/react-dom": optional: true - checksum: 10/c4cf35e2f26a89703189d0eef3ceeeb706ae0832e98e558730a5e929ca7c72c7cb510413a24eca94c7732f8d659a1e81942bec7b90540cb73ce9e4885d040b64 + checksum: 10/bd6be39bf021d5c917e2474ecba411e2625171f7ef96862b9af04bbd68833bb3662a7f1fbdeb5a7a237111b10e811e76d2cd03e957dadd6e668ef16541bfbd68 languageName: node linkType: hard -"@radix-ui/react-presence@npm:1.0.1": - version: 1.0.1 - resolution: "@radix-ui/react-presence@npm:1.0.1" +"@radix-ui/react-presence@npm:1.1.5": + version: 1.1.5 + resolution: "@radix-ui/react-presence@npm:1.1.5" dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/react-compose-refs": "npm:1.0.1" - "@radix-ui/react-use-layout-effect": "npm:1.0.1" + "@radix-ui/react-compose-refs": "npm:1.1.2" + "@radix-ui/react-use-layout-effect": "npm:1.1.1" peerDependencies: "@types/react": "*" "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true "@types/react-dom": optional: true - checksum: 10/406f0b5a54ea4e7881e15bddc3863234bb14bf3abd4a6e56ea57c6df6f9265a9ad5cfa158e3a98614f0dcbbb7c5f537e1f7158346e57cc3f29b522d62cf28823 + checksum: 10/4cdb05844c18877efb4b9739b46b7e5850b81d7ede994e75b5d62e8153a43c6e16b3ff9e55ff716e20b74b99b9415a94e97fd636bcb8698d5bbf7ab7b8663f9b languageName: node linkType: hard -"@radix-ui/react-primitive@npm:1.0.3": - version: 1.0.3 - resolution: "@radix-ui/react-primitive@npm:1.0.3" +"@radix-ui/react-primitive@npm:2.1.3": + version: 2.1.3 + resolution: "@radix-ui/react-primitive@npm:2.1.3" dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/react-slot": "npm:1.0.2" + "@radix-ui/react-slot": "npm:1.2.3" peerDependencies: "@types/react": "*" "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true "@types/react-dom": optional: true - checksum: 10/bedb934ac07c710dc5550a7bfc7065d47e099d958cde1d37e4b1947ae5451f1b7e6f8ff5965e242578bf2c619065e6038c3a3aa779e5eafa7da3e3dbc685799f + checksum: 10/1dbbf932a3527f4e62f210bb72944eff605c3e38c8d3275ed5a5c570c02820ab156169756a65ad9a638d2089a828a04a7903795377384e98c87d0ca456303253 languageName: node linkType: hard -"@radix-ui/react-roving-focus@npm:1.0.4": - version: 1.0.4 - resolution: "@radix-ui/react-roving-focus@npm:1.0.4" +"@radix-ui/react-primitive@npm:2.1.4": + version: 2.1.4 + resolution: "@radix-ui/react-primitive@npm:2.1.4" dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/primitive": "npm:1.0.1" - "@radix-ui/react-collection": "npm:1.0.3" - "@radix-ui/react-compose-refs": "npm:1.0.1" - "@radix-ui/react-context": "npm:1.0.1" - "@radix-ui/react-direction": "npm:1.0.1" - "@radix-ui/react-id": "npm:1.0.1" - "@radix-ui/react-primitive": "npm:1.0.3" - "@radix-ui/react-use-callback-ref": "npm:1.0.1" - "@radix-ui/react-use-controllable-state": "npm:1.0.1" + "@radix-ui/react-slot": "npm:1.2.4" peerDependencies: "@types/react": "*" "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true "@types/react-dom": optional: true - checksum: 10/a23ffb1e3e29a8209b94ce3857bf559dcf2175c4f316169dc47d018e8e94cd018dc914331a1d1762f32448e2594b7c8945efaa7059056f9940ce92cc35cc7026 + checksum: 10/a718537c2e66d541e72cc6b92bcf8ba6a7a0b4a30072efadeac0a517eb36f8acc55f2983cc1e345dc0eae18166ebcace4dab0299ed87a79e8ac3f653df9ee437 languageName: node linkType: hard -"@radix-ui/react-slot@npm:1.0.2": - version: 1.0.2 - resolution: "@radix-ui/react-slot@npm:1.0.2" +"@radix-ui/react-roving-focus@npm:1.1.11": + version: 1.1.11 + resolution: "@radix-ui/react-roving-focus@npm:1.1.11" dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/react-compose-refs": "npm:1.0.1" + "@radix-ui/primitive": "npm:1.1.3" + "@radix-ui/react-collection": "npm:1.1.7" + "@radix-ui/react-compose-refs": "npm:1.1.2" + "@radix-ui/react-context": "npm:1.1.2" + "@radix-ui/react-direction": "npm:1.1.1" + "@radix-ui/react-id": "npm:1.1.1" + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-use-callback-ref": "npm:1.1.1" + "@radix-ui/react-use-controllable-state": "npm:1.2.2" peerDependencies: "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10/734866561e991438fbcf22af06e56b272ed6ee8f7b536489ee3bf2f736f8b53bf6bc14ebde94834aa0aceda854d018a0ce20bb171defffbaed1f566006cbb887 + "@types/react-dom": + optional: true + checksum: 10/0eddafa942332c95622ab8b53cce2fa25fd0dcaf4797218e9e6725da0734a81a438852cdcb3f588521018f68d38c6c5e50c64fda78c655f4e69dd45681ecc5e7 + languageName: node + linkType: hard + +"@radix-ui/react-slot@npm:1.2.3": + version: 1.2.3 + resolution: "@radix-ui/react-slot@npm:1.2.3" + dependencies: + "@radix-ui/react-compose-refs": "npm:1.1.2" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/fe484c2741e31d9c20a8fb53c5790a73c0664e2bea35e27f4d484a90c42135fcfffe11a08abfcacb7a8ee2faf013471f0e856818f3ddac8ac51ceb8869e0fd08 + languageName: node + linkType: hard + +"@radix-ui/react-slot@npm:1.2.4": + version: 1.2.4 + resolution: "@radix-ui/react-slot@npm:1.2.4" + dependencies: + "@radix-ui/react-compose-refs": "npm:1.1.2" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/b37e37455b92789758980359d73ab5a5f5d1c12af480c775519bd15c556b891642d472accf05b30d520751489ca74cdb8fd7866064abc7942f0437371be28e51 languageName: node linkType: hard "@radix-ui/react-tooltip@npm:^1.0.6": - version: 1.0.7 - resolution: "@radix-ui/react-tooltip@npm:1.0.7" + version: 1.2.8 + resolution: "@radix-ui/react-tooltip@npm:1.2.8" dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/primitive": "npm:1.0.1" - "@radix-ui/react-compose-refs": "npm:1.0.1" - "@radix-ui/react-context": "npm:1.0.1" - "@radix-ui/react-dismissable-layer": "npm:1.0.5" - "@radix-ui/react-id": "npm:1.0.1" - "@radix-ui/react-popper": "npm:1.1.3" - "@radix-ui/react-portal": "npm:1.0.4" - "@radix-ui/react-presence": "npm:1.0.1" - "@radix-ui/react-primitive": "npm:1.0.3" - "@radix-ui/react-slot": "npm:1.0.2" - "@radix-ui/react-use-controllable-state": "npm:1.0.1" - "@radix-ui/react-visually-hidden": "npm:1.0.3" + "@radix-ui/primitive": "npm:1.1.3" + "@radix-ui/react-compose-refs": "npm:1.1.2" + "@radix-ui/react-context": "npm:1.1.2" + "@radix-ui/react-dismissable-layer": "npm:1.1.11" + "@radix-ui/react-id": "npm:1.1.1" + "@radix-ui/react-popper": "npm:1.2.8" + "@radix-ui/react-portal": "npm:1.1.9" + "@radix-ui/react-presence": "npm:1.1.5" + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-slot": "npm:1.2.3" + "@radix-ui/react-use-controllable-state": "npm:1.2.2" + "@radix-ui/react-visually-hidden": "npm:1.2.3" peerDependencies: "@types/react": "*" "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true "@types/react-dom": optional: true - checksum: 10/8f075a78db9bfe3dac251266feeb771923176d388c3232f9bad8d85417b5d80d2470697e1c7cae6765d3af16e48552ab9810137c2db193bc37e61b97388e92e8 + checksum: 10/e31857628d998b69616b8994f9627d387ed7bfa453b94e3b18ad2c04de83caf5fcca0ef2f304b1d343e00f183e937d883247d81e386dcc76c7c7c268484bc47c languageName: node linkType: hard -"@radix-ui/react-use-callback-ref@npm:1.0.1": - version: 1.0.1 - resolution: "@radix-ui/react-use-callback-ref@npm:1.0.1" - dependencies: - "@babel/runtime": "npm:^7.13.10" +"@radix-ui/react-use-callback-ref@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-use-callback-ref@npm:1.1.1" peerDependencies: "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10/b9fd39911c3644bbda14a84e4fca080682bef84212b8d8931fcaa2d2814465de242c4cfd8d7afb3020646bead9c5e539d478cea0a7031bee8a8a3bb164f3bc4c + checksum: 10/cde8c40f1d4e79e6e71470218163a746858304bad03758ac84dc1f94247a046478e8e397518350c8d6609c84b7e78565441d7505bb3ed573afce82cfdcd19faf languageName: node linkType: hard -"@radix-ui/react-use-controllable-state@npm:1.0.1": - version: 1.0.1 - resolution: "@radix-ui/react-use-controllable-state@npm:1.0.1" +"@radix-ui/react-use-controllable-state@npm:1.2.2": + version: 1.2.2 + resolution: "@radix-ui/react-use-controllable-state@npm:1.2.2" dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/react-use-callback-ref": "npm:1.0.1" + "@radix-ui/react-use-effect-event": "npm:0.0.2" + "@radix-ui/react-use-layout-effect": "npm:1.1.1" peerDependencies: "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10/dee2be1937d293c3a492cb6d279fc11495a8f19dc595cdbfe24b434e917302f9ac91db24e8cc5af9a065f3f209c3423115b5442e65a5be9fd1e9091338972be9 + checksum: 10/a100bff3ddecb753dab17444147273c9f70046c5949712c52174b259622eaef12acbf7ebcf289bae4e714eb84d0a7317c1aa44064cd997f327d77b62bc732a7c languageName: node linkType: hard -"@radix-ui/react-use-escape-keydown@npm:1.0.3": - version: 1.0.3 - resolution: "@radix-ui/react-use-escape-keydown@npm:1.0.3" +"@radix-ui/react-use-effect-event@npm:0.0.2": + version: 0.0.2 + resolution: "@radix-ui/react-use-effect-event@npm:0.0.2" dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/react-use-callback-ref": "npm:1.0.1" + "@radix-ui/react-use-layout-effect": "npm:1.1.1" peerDependencies: "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10/c6ed0d9ce780f67f924980eb305af1f6cce2a8acbaf043a58abe0aa3cc551d9aa76ccee14531df89bbee302ead7ecc7fce330886f82d4672c5eda52f357ef9b8 + checksum: 10/5a1950a30a399ea7e4b98154da9f536737a610de80189b7aacd4f064a89a3cd0d2a48571d527435227252e72e872bdb544ff6ffcfbdd02de2efd011be4aaa902 languageName: node linkType: hard -"@radix-ui/react-use-layout-effect@npm:1.0.1": - version: 1.0.1 - resolution: "@radix-ui/react-use-layout-effect@npm:1.0.1" +"@radix-ui/react-use-escape-keydown@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-use-escape-keydown@npm:1.1.1" dependencies: - "@babel/runtime": "npm:^7.13.10" + "@radix-ui/react-use-callback-ref": "npm:1.1.1" peerDependencies: "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10/bed9c7e8de243a5ec3b93bb6a5860950b0dba359b6680c84d57c7a655e123dec9b5891c5dfe81ab970652e7779fe2ad102a23177c7896dde95f7340817d47ae5 + checksum: 10/0eb0756c2c55ddcde9ff01446ab01c085ab2bf799173e97db7ef5f85126f9e8600225570801a1f64740e6d14c39ffe8eed7c14d29737345a5797f4622ac96f6f languageName: node linkType: hard -"@radix-ui/react-use-rect@npm:1.0.1": - version: 1.0.1 - resolution: "@radix-ui/react-use-rect@npm:1.0.1" - dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/rect": "npm:1.0.1" +"@radix-ui/react-use-layout-effect@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-use-layout-effect@npm:1.1.1" peerDependencies: "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10/433f07e61e04eb222349825bb05f3591fca131313a1d03709565d6226d8660bd1d0423635553f95ee4fcc25c8f2050972d848808d753c388e2a9ae191ebf17f3 + checksum: 10/bad2ba4f206e6255263582bedfb7868773c400836f9a1b423c0b464ffe4a17e13d3f306d1ce19cf7a19a492e9d0e49747464f2656451bb7c6a99f5a57bd34de2 languageName: node linkType: hard -"@radix-ui/react-use-size@npm:1.0.1": - version: 1.0.1 - resolution: "@radix-ui/react-use-size@npm:1.0.1" +"@radix-ui/react-use-rect@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-use-rect@npm:1.1.1" dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/react-use-layout-effect": "npm:1.0.1" + "@radix-ui/rect": "npm:1.1.1" peerDependencies: "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10/6cc150ad1e9fa85019c225c5a5d50a0af6cdc4653dad0c21b4b40cd2121f36ee076db326c43e6bc91a69766ccff5a84e917d27970176b592577deea3c85a3e26 + checksum: 10/116461bebc49472f7497e66a9bd413541181b3d00c5e0aaeef45d790dc1fbd7c8dcea80b169ea273306228b9a3c2b70067e902d1fd5004b3057e3bbe35b9d55d languageName: node linkType: hard -"@radix-ui/react-visually-hidden@npm:1.0.3, @radix-ui/react-visually-hidden@npm:^1.0.3": - version: 1.0.3 - resolution: "@radix-ui/react-visually-hidden@npm:1.0.3" +"@radix-ui/react-use-size@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-use-size@npm:1.1.1" dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/react-primitive": "npm:1.0.3" + "@radix-ui/react-use-layout-effect": "npm:1.1.1" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/64e61f65feb67ffc80e1fc4a8d5e32480fb6d68475e2640377e021178dead101568cba5f936c9c33e6c142c7cf2fb5d76ad7b23ef80e556ba142d56cf306147b + languageName: node + linkType: hard + +"@radix-ui/react-visually-hidden@npm:1.2.3": + version: 1.2.3 + resolution: "@radix-ui/react-visually-hidden@npm:1.2.3" + dependencies: + "@radix-ui/react-primitive": "npm:2.1.3" peerDependencies: "@types/react": "*" "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true "@types/react-dom": optional: true - checksum: 10/2e9d0c8253f97e7d6ffb2e52a5cfd40ba719f813b39c3e2e42c496d54408abd09ef66b5aec4af9b8ab0553215e32452a5d0934597a49c51dd90dc39181ed0d57 + checksum: 10/42296bde1ddf4af4e7445e914c35d6bc8406d6ede49f0a959a553e75b3ed21da09fda80a81c48d8ec058ed8129ce7137499d02ee26f90f0d3eaa2417922d6509 languageName: node linkType: hard -"@radix-ui/rect@npm:1.0.1": - version: 1.0.1 - resolution: "@radix-ui/rect@npm:1.0.1" +"@radix-ui/react-visually-hidden@npm:^1.0.3": + version: 1.2.4 + resolution: "@radix-ui/react-visually-hidden@npm:1.2.4" dependencies: - "@babel/runtime": "npm:^7.13.10" - checksum: 10/e25492cb8a683246161d781f0f3205f79507280a60f50eb763f06e8b6fa211b940b784aa581131ed76695bd5df5d1033a6246b43a6996cf8959a326fe4d3eb00 - languageName: node - linkType: hard - -"@react-aria/autocomplete@npm:3.0.0-rc.3": - version: 3.0.0-rc.3 - resolution: "@react-aria/autocomplete@npm:3.0.0-rc.3" - dependencies: - "@react-aria/combobox": "npm:^3.14.0" - "@react-aria/focus": "npm:^3.21.2" - "@react-aria/i18n": "npm:^3.12.13" - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/listbox": "npm:^3.15.0" - "@react-aria/searchfield": "npm:^3.8.9" - "@react-aria/textfield": "npm:^3.18.2" - "@react-aria/utils": "npm:^3.31.0" - "@react-stately/autocomplete": "npm:3.0.0-beta.3" - "@react-stately/combobox": "npm:^3.12.0" - "@react-types/autocomplete": "npm:3.0.0-alpha.35" - "@react-types/button": "npm:^3.14.1" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" + "@radix-ui/react-primitive": "npm:2.1.4" peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/7acddd01b0f07bdbd2cf09d01a8b0fb9551d27656a92adf995411620a1599f8ff6421a72de1fcd56611d5eced49073cae48aee8127f3620fc1ca38d4ce3dcb62 + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/43bbb68532748fabe8bec52628a7876714889f67e1a21d627c30816e53b68042ece849eef61fbd854af9c528b157cdd483ad032668c95d627ef27edd2530253f languageName: node linkType: hard -"@react-aria/breadcrumbs@npm:^3.5.29": - version: 3.5.29 - resolution: "@react-aria/breadcrumbs@npm:3.5.29" - dependencies: - "@react-aria/i18n": "npm:^3.12.13" - "@react-aria/link": "npm:^3.8.6" - "@react-aria/utils": "npm:^3.31.0" - "@react-types/breadcrumbs": "npm:^3.7.17" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/875c2280539362e33200a9869dc52e5cec8a7407e068faa70cc190eaf266135448410acf9dd37b59bd86e53f330ac32e7598ceec468a2339cc04f18f826c4cbc - languageName: node - linkType: hard - -"@react-aria/button@npm:^3.14.2": - version: 3.14.2 - resolution: "@react-aria/button@npm:3.14.2" - dependencies: - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/toolbar": "npm:3.0.0-beta.21" - "@react-aria/utils": "npm:^3.31.0" - "@react-stately/toggle": "npm:^3.9.2" - "@react-types/button": "npm:^3.14.1" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/d6891ab39a81b349a80ac2c381a5663e33caf209d29aa536b0a8ca2963898b011913593c71f4bceb3f48a54a95cd5bc842b0ffbb33498d38a120d1a6d28f932b - languageName: node - linkType: hard - -"@react-aria/calendar@npm:^3.9.2": - version: 3.9.2 - resolution: "@react-aria/calendar@npm:3.9.2" - dependencies: - "@internationalized/date": "npm:^3.10.0" - "@react-aria/i18n": "npm:^3.12.13" - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/live-announcer": "npm:^3.4.4" - "@react-aria/utils": "npm:^3.31.0" - "@react-stately/calendar": "npm:^3.9.0" - "@react-types/button": "npm:^3.14.1" - "@react-types/calendar": "npm:^3.8.0" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/3697b14765895c864db1a9393b86e0370cda01fab44464e9c1966150d7c793bcc9fd1e7a50d34022b0730fcaf9fd1995d431ccb1b5518eb086ccfb4b35e47ff2 - languageName: node - linkType: hard - -"@react-aria/checkbox@npm:^3.16.2": - version: 3.16.2 - resolution: "@react-aria/checkbox@npm:3.16.2" - dependencies: - "@react-aria/form": "npm:^3.1.2" - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/label": "npm:^3.7.22" - "@react-aria/toggle": "npm:^3.12.2" - "@react-aria/utils": "npm:^3.31.0" - "@react-stately/checkbox": "npm:^3.7.2" - "@react-stately/form": "npm:^3.2.2" - "@react-stately/toggle": "npm:^3.9.2" - "@react-types/checkbox": "npm:^3.10.2" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/77058d41d78b8feea4801022ba56f225b69f9b6577edc42a747dea21e4b38ea8d343f3a38455e9ae47a95a41b77e37fa0929a7136507f7b446111919f5e34478 - languageName: node - linkType: hard - -"@react-aria/collections@npm:^3.0.0": - version: 3.0.0 - resolution: "@react-aria/collections@npm:3.0.0" - dependencies: - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/ssr": "npm:^3.9.10" - "@react-aria/utils": "npm:^3.31.0" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - use-sync-external-store: "npm:^1.4.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/2704d35e0cc67e8e2f7fa61f593160768a633e2ff3ced45f01806928dc15738dcd005c49ff7130c0c92fbaabf0b0624833e54b7d24a10181c7910a469a9da52b - languageName: node - linkType: hard - -"@react-aria/color@npm:^3.1.2": - version: 3.1.2 - resolution: "@react-aria/color@npm:3.1.2" - dependencies: - "@react-aria/i18n": "npm:^3.12.13" - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/numberfield": "npm:^3.12.2" - "@react-aria/slider": "npm:^3.8.2" - "@react-aria/spinbutton": "npm:^3.6.19" - "@react-aria/textfield": "npm:^3.18.2" - "@react-aria/utils": "npm:^3.31.0" - "@react-aria/visually-hidden": "npm:^3.8.28" - "@react-stately/color": "npm:^3.9.2" - "@react-stately/form": "npm:^3.2.2" - "@react-types/color": "npm:^3.1.2" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/8b2c29097e86e2fa30bb5a53777dd09a7b2b24f6fb44ac1847275345aa895bb663735dcdc94bac90ba6fee074f11fe571f7effc5ba4ac4adb0bc87f67705479c - languageName: node - linkType: hard - -"@react-aria/combobox@npm:^3.14.0": - version: 3.14.0 - resolution: "@react-aria/combobox@npm:3.14.0" - dependencies: - "@react-aria/focus": "npm:^3.21.2" - "@react-aria/i18n": "npm:^3.12.13" - "@react-aria/listbox": "npm:^3.15.0" - "@react-aria/live-announcer": "npm:^3.4.4" - "@react-aria/menu": "npm:^3.19.3" - "@react-aria/overlays": "npm:^3.30.0" - "@react-aria/selection": "npm:^3.26.0" - "@react-aria/textfield": "npm:^3.18.2" - "@react-aria/utils": "npm:^3.31.0" - "@react-stately/collections": "npm:^3.12.8" - "@react-stately/combobox": "npm:^3.12.0" - "@react-stately/form": "npm:^3.2.2" - "@react-types/button": "npm:^3.14.1" - "@react-types/combobox": "npm:^3.13.9" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/e583a99133b884e49b5848271a18d3dcbc6fdb4fb9aa9477d2164790b7386ada3316f6a4c19af3e8d989ed9c6207af12e2377c2f1644e8114afd916697a26c33 - languageName: node - linkType: hard - -"@react-aria/datepicker@npm:^3.15.2": - version: 3.15.2 - resolution: "@react-aria/datepicker@npm:3.15.2" - dependencies: - "@internationalized/date": "npm:^3.10.0" - "@internationalized/number": "npm:^3.6.5" - "@internationalized/string": "npm:^3.2.7" - "@react-aria/focus": "npm:^3.21.2" - "@react-aria/form": "npm:^3.1.2" - "@react-aria/i18n": "npm:^3.12.13" - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/label": "npm:^3.7.22" - "@react-aria/spinbutton": "npm:^3.6.19" - "@react-aria/utils": "npm:^3.31.0" - "@react-stately/datepicker": "npm:^3.15.2" - "@react-stately/form": "npm:^3.2.2" - "@react-types/button": "npm:^3.14.1" - "@react-types/calendar": "npm:^3.8.0" - "@react-types/datepicker": "npm:^3.13.2" - "@react-types/dialog": "npm:^3.5.22" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/05f9b7bda5e5aa304ea4eebfded8040ea040b12284f2aa67f6b2a9e5458526a1e8dfbe077a53989a8633debb8184123e0907186f5b511c5a4c85a48f4c755f4b - languageName: node - linkType: hard - -"@react-aria/dialog@npm:^3.5.31": - version: 3.5.31 - resolution: "@react-aria/dialog@npm:3.5.31" - dependencies: - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/overlays": "npm:^3.30.0" - "@react-aria/utils": "npm:^3.31.0" - "@react-types/dialog": "npm:^3.5.22" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/ea5d197b13d1441b45b5288382f9f25cf4ecec07ea6c8e989ce62b5f0ce2eaa7e6afe7e40fc808451b040eda2c41aa64f8e510d5205b26a5a07324b86468e53d - languageName: node - linkType: hard - -"@react-aria/disclosure@npm:^3.1.0": - version: 3.1.0 - resolution: "@react-aria/disclosure@npm:3.1.0" - dependencies: - "@react-aria/ssr": "npm:^3.9.10" - "@react-aria/utils": "npm:^3.31.0" - "@react-stately/disclosure": "npm:^3.0.8" - "@react-types/button": "npm:^3.14.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/34f5a0b5c7130fc686025f8015cefe219b84ed153dbca93ee4b47b2f598ca833ed161426dc76d2f09ee480b88741df046fcbb600516eb77b919afcb80b1b61a3 - languageName: node - linkType: hard - -"@react-aria/dnd@npm:^3.11.3": - version: 3.11.3 - resolution: "@react-aria/dnd@npm:3.11.3" - dependencies: - "@internationalized/string": "npm:^3.2.7" - "@react-aria/i18n": "npm:^3.12.13" - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/live-announcer": "npm:^3.4.4" - "@react-aria/overlays": "npm:^3.30.0" - "@react-aria/utils": "npm:^3.31.0" - "@react-stately/collections": "npm:^3.12.8" - "@react-stately/dnd": "npm:^3.7.1" - "@react-types/button": "npm:^3.14.1" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/48c58ab1fdb0e4dfce2e5933b580df3e46c0407535a28d2434fc6921d049bde45d0b3b94b7f3f4775e683e77c4a5b17ccfd9894c345230b7f49c7c0742663dce - languageName: node - linkType: hard - -"@react-aria/focus@npm:^3.21.2": - version: 3.21.2 - resolution: "@react-aria/focus@npm:3.21.2" - dependencies: - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/utils": "npm:^3.31.0" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - clsx: "npm:^2.0.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/4bce20d956c24ab08b707e84896afafd66b3496971efb6dd611dae1c4c1f47a5c99c786a96b2fd0eb083a7c9ba5368ac04ce3a937cc48fdcc8bb85f81e7a3098 - languageName: node - linkType: hard - -"@react-aria/form@npm:^3.1.2": - version: 3.1.2 - resolution: "@react-aria/form@npm:3.1.2" - dependencies: - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/utils": "npm:^3.31.0" - "@react-stately/form": "npm:^3.2.2" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/140d8cb830fab6fc4d885e79fbdfc92f06af43134b6ab68ec93b58fec25220b823b58698bd9bbd574bc21d1c0d443813df89b44f5ea33b5bcd314128560bd4fd - languageName: node - linkType: hard - -"@react-aria/grid@npm:^3.14.5": - version: 3.14.5 - resolution: "@react-aria/grid@npm:3.14.5" - dependencies: - "@react-aria/focus": "npm:^3.21.2" - "@react-aria/i18n": "npm:^3.12.13" - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/live-announcer": "npm:^3.4.4" - "@react-aria/selection": "npm:^3.26.0" - "@react-aria/utils": "npm:^3.31.0" - "@react-stately/collections": "npm:^3.12.8" - "@react-stately/grid": "npm:^3.11.6" - "@react-stately/selection": "npm:^3.20.6" - "@react-types/checkbox": "npm:^3.10.2" - "@react-types/grid": "npm:^3.3.6" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/7e8731b55366b05f619259fbeb149b63d7c17158e25a2ebaacbe3368f50e71f054e4cef3a943ca6076d3ebf0242cfcb155ff276ab984a7495dc46f6b5b13f1d2 - languageName: node - linkType: hard - -"@react-aria/gridlist@npm:^3.14.1": - version: 3.14.1 - resolution: "@react-aria/gridlist@npm:3.14.1" - dependencies: - "@react-aria/focus": "npm:^3.21.2" - "@react-aria/grid": "npm:^3.14.5" - "@react-aria/i18n": "npm:^3.12.13" - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/selection": "npm:^3.26.0" - "@react-aria/utils": "npm:^3.31.0" - "@react-stately/list": "npm:^3.13.1" - "@react-stately/tree": "npm:^3.9.3" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/1d7ff55a869a3157edd266b5d14a9e1380561bcb8d949fdd43e983e64932ec251aa7221e6156e93ea977b8942c5b9b8f9aae5f04ebc2ed27eef9be2b7e00b5ab - languageName: node - linkType: hard - -"@react-aria/i18n@npm:^3.12.13": - version: 3.12.13 - resolution: "@react-aria/i18n@npm:3.12.13" - dependencies: - "@internationalized/date": "npm:^3.10.0" - "@internationalized/message": "npm:^3.1.8" - "@internationalized/number": "npm:^3.6.5" - "@internationalized/string": "npm:^3.2.7" - "@react-aria/ssr": "npm:^3.9.10" - "@react-aria/utils": "npm:^3.31.0" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/a5464213e626c60d63e0dbab84907ab452c53022a27a5c482c8f86f23c96b53522e52005c5cfc829f388c807eaf246cded69d5bee24b9e0fc08f34f9b0359e82 - languageName: node - linkType: hard - -"@react-aria/interactions@npm:^3.25.6": - version: 3.25.6 - resolution: "@react-aria/interactions@npm:3.25.6" - dependencies: - "@react-aria/ssr": "npm:^3.9.10" - "@react-aria/utils": "npm:^3.31.0" - "@react-stately/flags": "npm:^3.1.2" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/fd30e14107d919be25909ca8f69fb99d88aa1f1ead523717a390a40be6d774e40de47dd7f9fcc7e6d66e0f4abc12238001a10d2128cbd7af9b141641cacf6da4 - languageName: node - linkType: hard - -"@react-aria/label@npm:^3.7.22": - version: 3.7.22 - resolution: "@react-aria/label@npm:3.7.22" - dependencies: - "@react-aria/utils": "npm:^3.31.0" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/2bbfca230465057230597fd23f47ebe591bb5307ff7e37a082a4922e132f1310b69a06dcb4905c356b674b159bd09c2ca690e0526d9f303bed8f66eeb8a61b13 - languageName: node - linkType: hard - -"@react-aria/landmark@npm:^3.0.7": - version: 3.0.7 - resolution: "@react-aria/landmark@npm:3.0.7" - dependencies: - "@react-aria/utils": "npm:^3.31.0" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - use-sync-external-store: "npm:^1.4.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/b86cbd8356fa08e01318644082b9ef948cb3d61695c3dfdb5bdc67a59ce3e3f774c91bf62795280c7ac620b87f9d7e66635c7e02a2c2eb2663b3e2b6a77a448a - languageName: node - linkType: hard - -"@react-aria/link@npm:^3.8.6": - version: 3.8.6 - resolution: "@react-aria/link@npm:3.8.6" - dependencies: - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/utils": "npm:^3.31.0" - "@react-types/link": "npm:^3.6.5" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/7f0fd02e7eb5c69ba5e280c57b0e7ec6568fbca231159f591201f9d753bd2f985613d0f75a54465f292a617684a55d8f5f10585538ce1fbc3f9f29fb3d339529 - languageName: node - linkType: hard - -"@react-aria/listbox@npm:^3.15.0": - version: 3.15.0 - resolution: "@react-aria/listbox@npm:3.15.0" - dependencies: - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/label": "npm:^3.7.22" - "@react-aria/selection": "npm:^3.26.0" - "@react-aria/utils": "npm:^3.31.0" - "@react-stately/collections": "npm:^3.12.8" - "@react-stately/list": "npm:^3.13.1" - "@react-types/listbox": "npm:^3.7.4" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/e361b11ab4ae521a3e4d41ea811f15e2863b97aa9e65c19eef06c38b9f378191c34dfedf545d1ea4667aa0b89aa157712ed6f78950d71cfc712643666cc59d7a - languageName: node - linkType: hard - -"@react-aria/live-announcer@npm:^3.4.4": - version: 3.4.4 - resolution: "@react-aria/live-announcer@npm:3.4.4" - dependencies: - "@swc/helpers": "npm:^0.5.0" - checksum: 10/058859f7c0895bccd902f038586333016d7a33d38508e5edaf0f4c809a00217c19db3aa00604e78f3a788e399c3701a8d7fe95e2eb29c8ae754ff4bb62da1f7a - languageName: node - linkType: hard - -"@react-aria/menu@npm:^3.19.3": - version: 3.19.3 - resolution: "@react-aria/menu@npm:3.19.3" - dependencies: - "@react-aria/focus": "npm:^3.21.2" - "@react-aria/i18n": "npm:^3.12.13" - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/overlays": "npm:^3.30.0" - "@react-aria/selection": "npm:^3.26.0" - "@react-aria/utils": "npm:^3.31.0" - "@react-stately/collections": "npm:^3.12.8" - "@react-stately/menu": "npm:^3.9.8" - "@react-stately/selection": "npm:^3.20.6" - "@react-stately/tree": "npm:^3.9.3" - "@react-types/button": "npm:^3.14.1" - "@react-types/menu": "npm:^3.10.5" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/04987d1fae12979335a755093b74e0a9c61d2bf1f207111bca2a8513ea43962b282315976d33e5f06d3e2e1fc1927e2f9030365c4e4a26024e156ebcb88a8d9c - languageName: node - linkType: hard - -"@react-aria/meter@npm:^3.4.27": - version: 3.4.27 - resolution: "@react-aria/meter@npm:3.4.27" - dependencies: - "@react-aria/progress": "npm:^3.4.27" - "@react-types/meter": "npm:^3.4.13" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/b79bc0f2a96b53ec427a25315cdec4a50dd1bc1cb1fbff9f84847c0bb2597851a514ecbbf4b0fbb18b165bc007142ad45bd432641301b8db990e09f281563d39 - languageName: node - linkType: hard - -"@react-aria/numberfield@npm:^3.12.2": - version: 3.12.2 - resolution: "@react-aria/numberfield@npm:3.12.2" - dependencies: - "@react-aria/i18n": "npm:^3.12.13" - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/spinbutton": "npm:^3.6.19" - "@react-aria/textfield": "npm:^3.18.2" - "@react-aria/utils": "npm:^3.31.0" - "@react-stately/form": "npm:^3.2.2" - "@react-stately/numberfield": "npm:^3.10.2" - "@react-types/button": "npm:^3.14.1" - "@react-types/numberfield": "npm:^3.8.15" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/7ec498b0d3bf6a55e891e61d1693a6395a3e7b89a04d81af433a0cb3dd673603ce5dd7f861d39658a5fe6c0cd48356c8120c39b9a4495563d9b13af0f8c0b3ca - languageName: node - linkType: hard - -"@react-aria/overlays@npm:^3.30.0": - version: 3.30.0 - resolution: "@react-aria/overlays@npm:3.30.0" - dependencies: - "@react-aria/focus": "npm:^3.21.2" - "@react-aria/i18n": "npm:^3.12.13" - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/ssr": "npm:^3.9.10" - "@react-aria/utils": "npm:^3.31.0" - "@react-aria/visually-hidden": "npm:^3.8.28" - "@react-stately/overlays": "npm:^3.6.20" - "@react-types/button": "npm:^3.14.1" - "@react-types/overlays": "npm:^3.9.2" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/32c25162b55078cf18ca6f514065d97982a9447b1ec9d75fda4db69fa909cd5dcc3d5b4c8917edecce7d8dd5b20bc51c3862945110c0badef021c86878e8fb4d - languageName: node - linkType: hard - -"@react-aria/progress@npm:^3.4.27": - version: 3.4.27 - resolution: "@react-aria/progress@npm:3.4.27" - dependencies: - "@react-aria/i18n": "npm:^3.12.13" - "@react-aria/label": "npm:^3.7.22" - "@react-aria/utils": "npm:^3.31.0" - "@react-types/progress": "npm:^3.5.16" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/7b0ac3c2da6205096e94b4d72a2b2b0250f864cace05b562e49b86986dbebbab633e62fcc39ffa915974318de37d5f6256b1f584d8e33ebca70e564d32b3aca2 - languageName: node - linkType: hard - -"@react-aria/radio@npm:^3.12.2": - version: 3.12.2 - resolution: "@react-aria/radio@npm:3.12.2" - dependencies: - "@react-aria/focus": "npm:^3.21.2" - "@react-aria/form": "npm:^3.1.2" - "@react-aria/i18n": "npm:^3.12.13" - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/label": "npm:^3.7.22" - "@react-aria/utils": "npm:^3.31.0" - "@react-stately/radio": "npm:^3.11.2" - "@react-types/radio": "npm:^3.9.2" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/709bdd0515e797f2bf37dc17f55e46103aa918a4549880c15ecca7c03adc0716e5650376b1cc877437b12e5df4e3050138699707e4ec9fa611614a1e38b98eb0 - languageName: node - linkType: hard - -"@react-aria/searchfield@npm:^3.8.9": - version: 3.8.9 - resolution: "@react-aria/searchfield@npm:3.8.9" - dependencies: - "@react-aria/i18n": "npm:^3.12.13" - "@react-aria/textfield": "npm:^3.18.2" - "@react-aria/utils": "npm:^3.31.0" - "@react-stately/searchfield": "npm:^3.5.16" - "@react-types/button": "npm:^3.14.1" - "@react-types/searchfield": "npm:^3.6.6" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/a088f7b1c434aea1d4603acb34e591c8f11a1532a41407b68336933a73e1e5f55b07bb6323642af5ebeebe39756e1e60943487d77d69882409c491fd816e86f4 - languageName: node - linkType: hard - -"@react-aria/select@npm:^3.17.0": - version: 3.17.0 - resolution: "@react-aria/select@npm:3.17.0" - dependencies: - "@react-aria/form": "npm:^3.1.2" - "@react-aria/i18n": "npm:^3.12.13" - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/label": "npm:^3.7.22" - "@react-aria/listbox": "npm:^3.15.0" - "@react-aria/menu": "npm:^3.19.3" - "@react-aria/selection": "npm:^3.26.0" - "@react-aria/utils": "npm:^3.31.0" - "@react-aria/visually-hidden": "npm:^3.8.28" - "@react-stately/select": "npm:^3.8.0" - "@react-types/button": "npm:^3.14.1" - "@react-types/select": "npm:^3.11.0" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/e986470eb5d8c675fbd5753e0051827c94dad00c5472015d4744e4e9233e7a4ff97f4634e94c5686ef9f65f2b6ffa894b24f3a07bde0437de6fe5802901969cd - languageName: node - linkType: hard - -"@react-aria/selection@npm:^3.26.0": - version: 3.26.0 - resolution: "@react-aria/selection@npm:3.26.0" - dependencies: - "@react-aria/focus": "npm:^3.21.2" - "@react-aria/i18n": "npm:^3.12.13" - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/utils": "npm:^3.31.0" - "@react-stately/selection": "npm:^3.20.6" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/6c3c59993c719cfdfd42ef9c753212e7bddf30c348117726e8abfe977876f583e0d0b6fa4ce73e46f1fa53e79e42f0866a333e257cef400c4a24bd5d1c81c379 - languageName: node - linkType: hard - -"@react-aria/separator@npm:^3.4.13": - version: 3.4.13 - resolution: "@react-aria/separator@npm:3.4.13" - dependencies: - "@react-aria/utils": "npm:^3.31.0" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/4326c36d002f3931c8c717b7aacbe30943d42ca190fe400a151d8f480dd65359a0bc9192da33213d4e8215bbfd47436de07fedfe957e83be4101a926d116862e - languageName: node - linkType: hard - -"@react-aria/slider@npm:^3.8.2": - version: 3.8.2 - resolution: "@react-aria/slider@npm:3.8.2" - dependencies: - "@react-aria/i18n": "npm:^3.12.13" - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/label": "npm:^3.7.22" - "@react-aria/utils": "npm:^3.31.0" - "@react-stately/slider": "npm:^3.7.2" - "@react-types/shared": "npm:^3.32.1" - "@react-types/slider": "npm:^3.8.2" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/3b9115eaf61f722929dcb739cd910b9e84f04644fdce8add21bc0f9305a3e7d0948c2e417afe424783b22738d52060505a32187aba1e7aca19dd572ebb6d10ed - languageName: node - linkType: hard - -"@react-aria/spinbutton@npm:^3.6.19": - version: 3.6.19 - resolution: "@react-aria/spinbutton@npm:3.6.19" - dependencies: - "@react-aria/i18n": "npm:^3.12.13" - "@react-aria/live-announcer": "npm:^3.4.4" - "@react-aria/utils": "npm:^3.31.0" - "@react-types/button": "npm:^3.14.1" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/9b87a1c87a09ddde141bb23e8e3ce1b1137cb1931df848d6fd3bbcd0773b3579045413b725686629da02c22733ad5b5dfc57e06c5fefaf07f8cfb8c4e2334997 - languageName: node - linkType: hard - -"@react-aria/ssr@npm:^3.9.10": - version: 3.9.10 - resolution: "@react-aria/ssr@npm:3.9.10" - dependencies: - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/3b414b5b174769e874014604749d9beaf2556f360f61d3df3223bca6150c16dd37fbf16800e669a2b0045bd221df70212756991a426a7a472c56aac6c0dabd1b - languageName: node - linkType: hard - -"@react-aria/switch@npm:^3.7.8": - version: 3.7.8 - resolution: "@react-aria/switch@npm:3.7.8" - dependencies: - "@react-aria/toggle": "npm:^3.12.2" - "@react-stately/toggle": "npm:^3.9.2" - "@react-types/shared": "npm:^3.32.1" - "@react-types/switch": "npm:^3.5.15" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/2a9cc90fb6f9593c8b41517d83e4a2e05f77ba544ee41d5beeb865329863ca70afa5beec94230512d3eab44d62f0c0c25110323ba89745ba64e05bee0f34c4ec - languageName: node - linkType: hard - -"@react-aria/table@npm:^3.17.8": - version: 3.17.8 - resolution: "@react-aria/table@npm:3.17.8" - dependencies: - "@react-aria/focus": "npm:^3.21.2" - "@react-aria/grid": "npm:^3.14.5" - "@react-aria/i18n": "npm:^3.12.13" - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/live-announcer": "npm:^3.4.4" - "@react-aria/utils": "npm:^3.31.0" - "@react-aria/visually-hidden": "npm:^3.8.28" - "@react-stately/collections": "npm:^3.12.8" - "@react-stately/flags": "npm:^3.1.2" - "@react-stately/table": "npm:^3.15.1" - "@react-types/checkbox": "npm:^3.10.2" - "@react-types/grid": "npm:^3.3.6" - "@react-types/shared": "npm:^3.32.1" - "@react-types/table": "npm:^3.13.4" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/39281b279c2efea9342877e4e2cb1af38f776a7349bcbe6e6daabf281e01445c28b8c7cd38c4f3cfeaea42a126552fd332fc242abd1e5e009c9252e3f1b88512 - languageName: node - linkType: hard - -"@react-aria/tabs@npm:^3.10.8": - version: 3.10.8 - resolution: "@react-aria/tabs@npm:3.10.8" - dependencies: - "@react-aria/focus": "npm:^3.21.2" - "@react-aria/i18n": "npm:^3.12.13" - "@react-aria/selection": "npm:^3.26.0" - "@react-aria/utils": "npm:^3.31.0" - "@react-stately/tabs": "npm:^3.8.6" - "@react-types/shared": "npm:^3.32.1" - "@react-types/tabs": "npm:^3.3.19" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/eae72fb6636ae1352ceec509605d35018d96f66aacab896748697da9f6b1e166c7bff88a0ffcfdee2869f653464df401d857906f44bdce5d5b9887b75f533287 - languageName: node - linkType: hard - -"@react-aria/tag@npm:^3.7.2": - version: 3.7.2 - resolution: "@react-aria/tag@npm:3.7.2" - dependencies: - "@react-aria/gridlist": "npm:^3.14.1" - "@react-aria/i18n": "npm:^3.12.13" - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/label": "npm:^3.7.22" - "@react-aria/selection": "npm:^3.26.0" - "@react-aria/utils": "npm:^3.31.0" - "@react-stately/list": "npm:^3.13.1" - "@react-types/button": "npm:^3.14.1" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/757daed07380eee13a05b1ba0b72e062db689526dd67087cd34726a6003b4791cb450c9765be3ea32208ce1a430903db3885d07700cb8775cd8bcd97b8124cb4 - languageName: node - linkType: hard - -"@react-aria/textfield@npm:^3.18.2": - version: 3.18.2 - resolution: "@react-aria/textfield@npm:3.18.2" - dependencies: - "@react-aria/form": "npm:^3.1.2" - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/label": "npm:^3.7.22" - "@react-aria/utils": "npm:^3.31.0" - "@react-stately/form": "npm:^3.2.2" - "@react-stately/utils": "npm:^3.10.8" - "@react-types/shared": "npm:^3.32.1" - "@react-types/textfield": "npm:^3.12.6" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/2713e3c42f502ecd1d8a505b01c404a6531d1450d8c422a12ac477d1d45dd5e11e9da48807e75c4915948f579969b4b894f1dda14d87ce3790ab67659b20a396 - languageName: node - linkType: hard - -"@react-aria/toast@npm:^3.0.8": - version: 3.0.8 - resolution: "@react-aria/toast@npm:3.0.8" - dependencies: - "@react-aria/i18n": "npm:^3.12.13" - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/landmark": "npm:^3.0.7" - "@react-aria/utils": "npm:^3.31.0" - "@react-stately/toast": "npm:^3.1.2" - "@react-types/button": "npm:^3.14.1" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/b8b21566723e9038f15517934c45c3e6406f28d476a682421aee270f01852e8a46f0170af13c3a5c8e80926169ebd7802d5def0115876e395dedad4fc9409f49 - languageName: node - linkType: hard - -"@react-aria/toggle@npm:^3.12.2": - version: 3.12.2 - resolution: "@react-aria/toggle@npm:3.12.2" - dependencies: - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/utils": "npm:^3.31.0" - "@react-stately/toggle": "npm:^3.9.2" - "@react-types/checkbox": "npm:^3.10.2" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/f6b9ce00d7a6bb0e7b6123826761cb7f40b9c7736ce8edc14b39f1865cd9165c086da8a3f6e27ddae8c8557f54f421bb0b4761c455dae81ed037a765b84b5cb7 - languageName: node - linkType: hard - -"@react-aria/toolbar@npm:3.0.0-beta.21": - version: 3.0.0-beta.21 - resolution: "@react-aria/toolbar@npm:3.0.0-beta.21" - dependencies: - "@react-aria/focus": "npm:^3.21.2" - "@react-aria/i18n": "npm:^3.12.13" - "@react-aria/utils": "npm:^3.31.0" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/ada5f50c4409997d46e4af0b739f1fada6d3c6ac0bdd3a7eef7ad2a440a53eaeebbd0685d19deb4b7c21832882dbe27a5a8162bf89135d2c930fbaf4bb5e0365 - languageName: node - linkType: hard - -"@react-aria/tooltip@npm:^3.8.8": - version: 3.8.8 - resolution: "@react-aria/tooltip@npm:3.8.8" - dependencies: - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/utils": "npm:^3.31.0" - "@react-stately/tooltip": "npm:^3.5.8" - "@react-types/shared": "npm:^3.32.1" - "@react-types/tooltip": "npm:^3.4.21" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/eddb4736b3813b26fdb21ce07cef170215a900a233d859605ad17a2c72c66ca8cc2fe105d3920f9b24fce0eb18e050551268dc25a9c5d4b987b7eca1e60b24e9 - languageName: node - linkType: hard - -"@react-aria/tree@npm:^3.1.4": - version: 3.1.4 - resolution: "@react-aria/tree@npm:3.1.4" - dependencies: - "@react-aria/gridlist": "npm:^3.14.1" - "@react-aria/i18n": "npm:^3.12.13" - "@react-aria/selection": "npm:^3.26.0" - "@react-aria/utils": "npm:^3.31.0" - "@react-stately/tree": "npm:^3.9.3" - "@react-types/button": "npm:^3.14.1" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/ed3e48d011b846026b4c3bf78a96fbe91ff892d995a94e94ff6f5218dcbd6736660507dee1e06aac9ce48f9f66fdc8898cf8486192cecad428242b5b4930fde8 - languageName: node - linkType: hard - -"@react-aria/utils@npm:^3.31.0": - version: 3.31.0 - resolution: "@react-aria/utils@npm:3.31.0" - dependencies: - "@react-aria/ssr": "npm:^3.9.10" - "@react-stately/flags": "npm:^3.1.2" - "@react-stately/utils": "npm:^3.10.8" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - clsx: "npm:^2.0.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/b021d2c3704ce934ec41cfc4d87fa6904fb3e007030e31b824cd8287053e866076cb7c7f072d6ed2fee82ca68f2e3f4677f3d58d7938e4b3315831f1fdea4d90 - languageName: node - linkType: hard - -"@react-aria/virtualizer@npm:^4.1.10": - version: 4.1.10 - resolution: "@react-aria/virtualizer@npm:4.1.10" - dependencies: - "@react-aria/i18n": "npm:^3.12.13" - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/utils": "npm:^3.31.0" - "@react-stately/virtualizer": "npm:^4.4.4" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/a9d1083ee2e424a33cbe5520fc86fd5194a77555b3be21792f648a723770998320220bc32e49ae1551ce5e00b024520ad0bca6ca0d7fdee86bc6216eb1cf78dc - languageName: node - linkType: hard - -"@react-aria/visually-hidden@npm:^3.8.28": - version: 3.8.28 - resolution: "@react-aria/visually-hidden@npm:3.8.28" - dependencies: - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/utils": "npm:^3.31.0" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/07e61b18d385127353014c2bd2bb9bba5035ac064988fa1bfc2f13d8050ccc9488891d4d3fbe6c79a808bfed7f06f1867b89ec1c975818712a36266040d76597 +"@radix-ui/rect@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/rect@npm:1.1.1" + checksum: 10/b6c5eb787640775b53dd52fa47218a089f0a0d8220d3ebff079c0b754e1fb82d89b6bdf08a82fd0d59549bdeb52678c0cca091c302da49dcf74c3c989cb55678 languageName: node linkType: hard @@ -15847,764 +15375,12 @@ __metadata: languageName: node linkType: hard -"@react-stately/autocomplete@npm:3.0.0-beta.3": - version: 3.0.0-beta.3 - resolution: "@react-stately/autocomplete@npm:3.0.0-beta.3" - dependencies: - "@react-stately/utils": "npm:^3.10.8" - "@swc/helpers": "npm:^0.5.0" +"@react-types/shared@npm:^3.34.0": + version: 3.34.0 + resolution: "@react-types/shared@npm:3.34.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/3920ce479e577730870bc6845ee888a44a9a1f90188cf68efb60c74a8264b5886c9059b0cedc07b82fcb26afd770bff5fc971bef0d93ff6ba7d07024a3e628cd - languageName: node - linkType: hard - -"@react-stately/calendar@npm:^3.9.0": - version: 3.9.0 - resolution: "@react-stately/calendar@npm:3.9.0" - dependencies: - "@internationalized/date": "npm:^3.10.0" - "@react-stately/utils": "npm:^3.10.8" - "@react-types/calendar": "npm:^3.8.0" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/6021927974cfaee9b48e07c4a4744105d1076fbad841aafdbd8633deb105c56f724a2a2f51285b426755873c3c2c8ae677649c9c38431433e7cf9e8dea7b334c - languageName: node - linkType: hard - -"@react-stately/checkbox@npm:^3.7.2": - version: 3.7.2 - resolution: "@react-stately/checkbox@npm:3.7.2" - dependencies: - "@react-stately/form": "npm:^3.2.2" - "@react-stately/utils": "npm:^3.10.8" - "@react-types/checkbox": "npm:^3.10.2" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/e0dda0c536ee43b2b925b9bf2932fdf5d27370b55d069633bb1e4d6a9dfb0db783ed0119ab0893ec192aa4099da0341f39fd2065bfd2e6c89ca3997e92e83ca1 - languageName: node - linkType: hard - -"@react-stately/collections@npm:^3.12.8": - version: 3.12.8 - resolution: "@react-stately/collections@npm:3.12.8" - dependencies: - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/da17c50d9323002f8dc05870265d54afa162575ceba83bc42d8ccd5d0ccf3bc0634d3896086e7975e1e7f1d7497de6f09ca0e5b82a697ad04349d5b59eca5a8f - languageName: node - linkType: hard - -"@react-stately/color@npm:^3.9.2": - version: 3.9.2 - resolution: "@react-stately/color@npm:3.9.2" - dependencies: - "@internationalized/number": "npm:^3.6.5" - "@internationalized/string": "npm:^3.2.7" - "@react-stately/form": "npm:^3.2.2" - "@react-stately/numberfield": "npm:^3.10.2" - "@react-stately/slider": "npm:^3.7.2" - "@react-stately/utils": "npm:^3.10.8" - "@react-types/color": "npm:^3.1.2" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/1aaea8dc241911b634ef072352076a8b3eec25300bb6c88ff2ce6147f05d2504688465a0342272b74a8484cdf272d10d2d9abc6cbbb64a111228046369f92ce5 - languageName: node - linkType: hard - -"@react-stately/combobox@npm:^3.12.0": - version: 3.12.0 - resolution: "@react-stately/combobox@npm:3.12.0" - dependencies: - "@react-stately/collections": "npm:^3.12.8" - "@react-stately/form": "npm:^3.2.2" - "@react-stately/list": "npm:^3.13.1" - "@react-stately/overlays": "npm:^3.6.20" - "@react-stately/utils": "npm:^3.10.8" - "@react-types/combobox": "npm:^3.13.9" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/3471a9c2f60e215784f63085b4ebcb2445ec804b3f0f50261933261afd7c9f7ed6d0675f00b5581aa43763c37561bcf6c6967a0ab485f403b1f8cf33a6b65d40 - languageName: node - linkType: hard - -"@react-stately/data@npm:^3.14.1": - version: 3.14.1 - resolution: "@react-stately/data@npm:3.14.1" - dependencies: - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/ac069e41036fd534a8ec4e77847234abc35aa02c1901b0abc3af3d5cede0baadb5ce0d5f648fed5d6b2e37f673669f2b21fbf74cfb760f0e99734f1d9e841d33 - languageName: node - linkType: hard - -"@react-stately/datepicker@npm:^3.15.2": - version: 3.15.2 - resolution: "@react-stately/datepicker@npm:3.15.2" - dependencies: - "@internationalized/date": "npm:^3.10.0" - "@internationalized/string": "npm:^3.2.7" - "@react-stately/form": "npm:^3.2.2" - "@react-stately/overlays": "npm:^3.6.20" - "@react-stately/utils": "npm:^3.10.8" - "@react-types/datepicker": "npm:^3.13.2" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/59ef011e4d56a3a40923d0a2ce02f6150ca4e5c7b9dac638d4f2b512af9a213ea6cda2d8d85b114758f2db2b06a8840a93c5b9c8c30859366249d58d960ed149 - languageName: node - linkType: hard - -"@react-stately/disclosure@npm:^3.0.8": - version: 3.0.8 - resolution: "@react-stately/disclosure@npm:3.0.8" - dependencies: - "@react-stately/utils": "npm:^3.10.8" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/8a36946589a199a788c07ef6ab04d0a30868db0484c660e5066ba38dd8bb8cef6e00fca324e7645b561fc7f3b3ffa152add555f2971c6be532d9cd3b67237b31 - languageName: node - linkType: hard - -"@react-stately/dnd@npm:^3.7.1": - version: 3.7.1 - resolution: "@react-stately/dnd@npm:3.7.1" - dependencies: - "@react-stately/selection": "npm:^3.20.6" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/d5500055a5398914556eeb767b141e4192be446990dfecb385b0348a35dc0baf0b0a72567dde806369995e09f5dc27a13fe4261739da7bc8ba483875bd88e694 - languageName: node - linkType: hard - -"@react-stately/flags@npm:^3.1.2": - version: 3.1.2 - resolution: "@react-stately/flags@npm:3.1.2" - dependencies: - "@swc/helpers": "npm:^0.5.0" - checksum: 10/a020c3680c36d9624f765c5916ce95d69959f64887928e8f380f11b5362bb0499a901a5842e4e12eb8e5a776af59212b1ee0c4c6a6681ce75f61dace8b2f9c40 - languageName: node - linkType: hard - -"@react-stately/form@npm:^3.2.2": - version: 3.2.2 - resolution: "@react-stately/form@npm:3.2.2" - dependencies: - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/e466309999cb2f28e892b11cdfc7cee2b7d08eefced41d9e346e2c4353dd5fc1864de145a6b332735cdc94b278ff8e6b599489d2aa6945ec5389171f89a633d1 - languageName: node - linkType: hard - -"@react-stately/grid@npm:^3.11.6": - version: 3.11.6 - resolution: "@react-stately/grid@npm:3.11.6" - dependencies: - "@react-stately/collections": "npm:^3.12.8" - "@react-stately/selection": "npm:^3.20.6" - "@react-types/grid": "npm:^3.3.6" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/b6bc2a24ff37bdf375caa0f73dd6812ec4368f314f7754472f8b5de47ed296c4d3aadab787768c50d17d5606732291f05448891d5b08c0facaf8a1c9f7a70d31 - languageName: node - linkType: hard - -"@react-stately/layout@npm:^4.5.1": - version: 4.5.1 - resolution: "@react-stately/layout@npm:4.5.1" - dependencies: - "@react-stately/collections": "npm:^3.12.8" - "@react-stately/table": "npm:^3.15.1" - "@react-stately/virtualizer": "npm:^4.4.4" - "@react-types/grid": "npm:^3.3.6" - "@react-types/shared": "npm:^3.32.1" - "@react-types/table": "npm:^3.13.4" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/a4413d766efdea8decf4549cd773f5a94ce086690195223e605b78ccb9e1c0dd25011bd37a011b805c72a7a9f3a27dc1dd6c8684d9cc2a0b6efaa084fbd337a4 - languageName: node - linkType: hard - -"@react-stately/list@npm:^3.13.1": - version: 3.13.1 - resolution: "@react-stately/list@npm:3.13.1" - dependencies: - "@react-stately/collections": "npm:^3.12.8" - "@react-stately/selection": "npm:^3.20.6" - "@react-stately/utils": "npm:^3.10.8" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/3da2ca14eaee5f0915c1c79df66268bf1e7cc5d60ce68170515180993941c4c8679033589a8bc38e08138b882a62a8ad69ca727a500c882bd0f4214ce9a69384 - languageName: node - linkType: hard - -"@react-stately/menu@npm:^3.9.8": - version: 3.9.8 - resolution: "@react-stately/menu@npm:3.9.8" - dependencies: - "@react-stately/overlays": "npm:^3.6.20" - "@react-types/menu": "npm:^3.10.5" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/7798aa91f0af2743d4d441be1a0bac54489413c187d0e926a336cf13482ad4d54c7215426cd6ea288b8bcf621406db6ebaca5209095be5bfb4dfe4464ece7875 - languageName: node - linkType: hard - -"@react-stately/numberfield@npm:^3.10.2": - version: 3.10.2 - resolution: "@react-stately/numberfield@npm:3.10.2" - dependencies: - "@internationalized/number": "npm:^3.6.5" - "@react-stately/form": "npm:^3.2.2" - "@react-stately/utils": "npm:^3.10.8" - "@react-types/numberfield": "npm:^3.8.15" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/5fd1d4cc1b90a7b03b98a82e47162044a78534291b9baa77847eddd4fb8040bddb91329dfcc7aa2a7cc38ace23817b6c9d52dd7b0f9bd7ce249f763006580797 - languageName: node - linkType: hard - -"@react-stately/overlays@npm:^3.6.20": - version: 3.6.20 - resolution: "@react-stately/overlays@npm:3.6.20" - dependencies: - "@react-stately/utils": "npm:^3.10.8" - "@react-types/overlays": "npm:^3.9.2" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/e9a3023b3fc3d9383d36dbd0a1e6a0d3d84350dee4f5ce6578464d613e063ca32dd6685c4fdfaa774ded24497c9e5c56363b9650c551d5d1a4dbb6d7d1d04382 - languageName: node - linkType: hard - -"@react-stately/radio@npm:^3.11.2": - version: 3.11.2 - resolution: "@react-stately/radio@npm:3.11.2" - dependencies: - "@react-stately/form": "npm:^3.2.2" - "@react-stately/utils": "npm:^3.10.8" - "@react-types/radio": "npm:^3.9.2" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/182946fb5e963fed62df59d8893ca92443e587bcf3d644b54b991b3acb2124b777af11884e6e31c953becc2dad2226a13238a219747fb596a7e08f0e31e97a22 - languageName: node - linkType: hard - -"@react-stately/searchfield@npm:^3.5.16": - version: 3.5.16 - resolution: "@react-stately/searchfield@npm:3.5.16" - dependencies: - "@react-stately/utils": "npm:^3.10.8" - "@react-types/searchfield": "npm:^3.6.6" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/879e736c6888e36e227efffa665420c7e4fd2b319c2fb11661d6689fbbdad549129adc1c218e531f79f5e52be5b4c075c3c8120363ca3238fec24195c1976527 - languageName: node - linkType: hard - -"@react-stately/select@npm:^3.8.0": - version: 3.8.0 - resolution: "@react-stately/select@npm:3.8.0" - dependencies: - "@react-stately/form": "npm:^3.2.2" - "@react-stately/list": "npm:^3.13.1" - "@react-stately/overlays": "npm:^3.6.20" - "@react-stately/utils": "npm:^3.10.8" - "@react-types/select": "npm:^3.11.0" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/d611bbd9d6b55a2dfb31455227efe54c9fc2684f37abd9ba5633b2d1b838bc8fc182e92e64e94ef17c8bbaf351a72daf6089c9d54ce8acad76d3f1be3bdb05aa - languageName: node - linkType: hard - -"@react-stately/selection@npm:^3.20.6": - version: 3.20.6 - resolution: "@react-stately/selection@npm:3.20.6" - dependencies: - "@react-stately/collections": "npm:^3.12.8" - "@react-stately/utils": "npm:^3.10.8" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/b72941f9d23d285b826aa0a5be55e299d934b335d8ebe50c6b061f72b59f860a37549ac4fc0f41a7357eab0d40edb404207cca201fc78915b001b5532e45a9db - languageName: node - linkType: hard - -"@react-stately/slider@npm:^3.7.2": - version: 3.7.2 - resolution: "@react-stately/slider@npm:3.7.2" - dependencies: - "@react-stately/utils": "npm:^3.10.8" - "@react-types/shared": "npm:^3.32.1" - "@react-types/slider": "npm:^3.8.2" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/c11a5374904434bfad5e87767900ed40e12d4341abf74d835b59ab26efb69ad669c86016926f83eae5fb3b3e39c175f775c4457b77eec64d73785c92369a79da - languageName: node - linkType: hard - -"@react-stately/table@npm:^3.15.1": - version: 3.15.1 - resolution: "@react-stately/table@npm:3.15.1" - dependencies: - "@react-stately/collections": "npm:^3.12.8" - "@react-stately/flags": "npm:^3.1.2" - "@react-stately/grid": "npm:^3.11.6" - "@react-stately/selection": "npm:^3.20.6" - "@react-stately/utils": "npm:^3.10.8" - "@react-types/grid": "npm:^3.3.6" - "@react-types/shared": "npm:^3.32.1" - "@react-types/table": "npm:^3.13.4" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/005292a0803de55179a9d27aa7bb68f44865fff64921141fac7aab0fbd5d2adff27a9a70db904f9a57bd6bc8db84036e8ad58b7a1ea57c990829500eb3f3ee6d - languageName: node - linkType: hard - -"@react-stately/tabs@npm:^3.8.6": - version: 3.8.6 - resolution: "@react-stately/tabs@npm:3.8.6" - dependencies: - "@react-stately/list": "npm:^3.13.1" - "@react-types/shared": "npm:^3.32.1" - "@react-types/tabs": "npm:^3.3.19" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/ed71a8351b6b9db7c191cbb60acf72121751864b20c1d9a552438a1f4f6c96fdd41469396a17a86ef52287463ef3c8f217167f9498bc5f36e77a621415fac388 - languageName: node - linkType: hard - -"@react-stately/toast@npm:^3.1.2": - version: 3.1.2 - resolution: "@react-stately/toast@npm:3.1.2" - dependencies: - "@swc/helpers": "npm:^0.5.0" - use-sync-external-store: "npm:^1.4.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/771eade3a6f84d7aac3f5766e9cc47826cdb179d58165d12650a843548c13cbf4b4bd3b804a1f367e884e0e28d1d51693cb6f5e7c8391ebdb233cb1dddc015c5 - languageName: node - linkType: hard - -"@react-stately/toggle@npm:^3.9.2": - version: 3.9.2 - resolution: "@react-stately/toggle@npm:3.9.2" - dependencies: - "@react-stately/utils": "npm:^3.10.8" - "@react-types/checkbox": "npm:^3.10.2" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/06f0e11cbbda0b479113df6c82e650db9ac772162566fd749da19a3ca274d25831849dc6c67383f4a440be82f61b5dff80e97513763f66bf3994a55febed5827 - languageName: node - linkType: hard - -"@react-stately/tooltip@npm:^3.5.8": - version: 3.5.8 - resolution: "@react-stately/tooltip@npm:3.5.8" - dependencies: - "@react-stately/overlays": "npm:^3.6.20" - "@react-types/tooltip": "npm:^3.4.21" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/fbddb1f0efc6a6275f8f558bf80b984aea0c49f6c0d17fc42fc27e48a02a3c36bc9ca3eed6de9347a3b12289d21e22dc3de159b8b4b8ab21fe18218ab8a0e4ec - languageName: node - linkType: hard - -"@react-stately/tree@npm:^3.9.3": - version: 3.9.3 - resolution: "@react-stately/tree@npm:3.9.3" - dependencies: - "@react-stately/collections": "npm:^3.12.8" - "@react-stately/selection": "npm:^3.20.6" - "@react-stately/utils": "npm:^3.10.8" - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/294cc70ffa08280a187a85f72c2402547d71295ddb95b2752fcfa395016947e7fe27116e970df597bab87319e18b3bf1f6b837124fb4bbda8686f7f0018b46ab - languageName: node - linkType: hard - -"@react-stately/utils@npm:^3.10.8": - version: 3.10.8 - resolution: "@react-stately/utils@npm:3.10.8" - dependencies: - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/7878ec47b132075566708bae630cb86d8237dde976eb3793bba43695abbb29fcaea9d00ea3f4f7244fcda253368f5b2b85263c37665c24e390500cdcc978c6fe - languageName: node - linkType: hard - -"@react-stately/virtualizer@npm:^4.4.4": - version: 4.4.4 - resolution: "@react-stately/virtualizer@npm:4.4.4" - dependencies: - "@react-types/shared": "npm:^3.32.1" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/c9d8d4b34250b8c0e91811bc618c654bac3e8eabe7a8ec119abc8f5dbbfa19faa4b31575eb0775773fda0f533fc2835e9a233b4ed0e4ae1835ddb5f0521a18aa - languageName: node - linkType: hard - -"@react-types/autocomplete@npm:3.0.0-alpha.35": - version: 3.0.0-alpha.35 - resolution: "@react-types/autocomplete@npm:3.0.0-alpha.35" - dependencies: - "@react-types/combobox": "npm:^3.13.9" - "@react-types/searchfield": "npm:^3.6.6" - "@react-types/shared": "npm:^3.32.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/2a51111a94f5df1ce0b1b137a69ca783fa2fa399b4c305ebc7f137b0aa6a420cfe2a3925d40fda9a7d0d3b832d390823015959eee41b6c6b31f7a6b30739a596 - languageName: node - linkType: hard - -"@react-types/breadcrumbs@npm:^3.7.17": - version: 3.7.17 - resolution: "@react-types/breadcrumbs@npm:3.7.17" - dependencies: - "@react-types/link": "npm:^3.6.5" - "@react-types/shared": "npm:^3.32.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/e9754d1b8010f3cdfdfcd8abf03cdb5b37dc1038496fcc103569b9f349269b39680979c2293cdc04c376cd1ea7b97b00571d46677b9d4939a60dd01b55f765a6 - languageName: node - linkType: hard - -"@react-types/button@npm:^3.14.1": - version: 3.14.1 - resolution: "@react-types/button@npm:3.14.1" - dependencies: - "@react-types/shared": "npm:^3.32.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/bbbf2e5db83f6b925af154199aaad438d950187862c4ca6f82122c0209632c0c358201ec9b0b503f9b96c8b5b765066af41c6472440ca7ea75b75031d7d9eacf - languageName: node - linkType: hard - -"@react-types/calendar@npm:^3.8.0": - version: 3.8.0 - resolution: "@react-types/calendar@npm:3.8.0" - dependencies: - "@internationalized/date": "npm:^3.10.0" - "@react-types/shared": "npm:^3.32.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/06fe32c8e5d78eedcb511fa3d18f66582b5664ab85c572538e24b953e8204a7c5f98407df0d028625a1dfdd94223153e8e04d9d7feefd96f7533b90fb82be907 - languageName: node - linkType: hard - -"@react-types/checkbox@npm:^3.10.2": - version: 3.10.2 - resolution: "@react-types/checkbox@npm:3.10.2" - dependencies: - "@react-types/shared": "npm:^3.32.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/3d2f8468ce3326825bc820c958dc52b68f11a18ff15a68de2663115b168fecb165a2381cfecf2d7a254bfba66760590d8d76dabae5f48626a15eddf309206516 - languageName: node - linkType: hard - -"@react-types/color@npm:^3.1.2": - version: 3.1.2 - resolution: "@react-types/color@npm:3.1.2" - dependencies: - "@react-types/shared": "npm:^3.32.1" - "@react-types/slider": "npm:^3.8.2" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/a41e786bd17b1815d5b7414f2dcbc79e655c3a4b8fe2dfa68c200c553f922bab5468a2ea272c76cdf6611d7d3f20518ca31b442729ef7b68141d91839e580410 - languageName: node - linkType: hard - -"@react-types/combobox@npm:^3.13.9": - version: 3.13.9 - resolution: "@react-types/combobox@npm:3.13.9" - dependencies: - "@react-types/shared": "npm:^3.32.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/0583d24f38b965e5c2cd2b436a7ad374635e183dc7c12145ce9ed0a166d7b6e46ed4685a68c9fb24aed065d1fca644ca3b0606cb360ae5fac3a9881e88ee2f2b - languageName: node - linkType: hard - -"@react-types/datepicker@npm:^3.13.2": - version: 3.13.2 - resolution: "@react-types/datepicker@npm:3.13.2" - dependencies: - "@internationalized/date": "npm:^3.10.0" - "@react-types/calendar": "npm:^3.8.0" - "@react-types/overlays": "npm:^3.9.2" - "@react-types/shared": "npm:^3.32.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/1f1686360f64051f5eff5e170ab78e890b20e07680b6031c96c77e47085ca022c55997018911095cd61076bdf63470f61c99ac25ef9b493420f69cde6e4763bd - languageName: node - linkType: hard - -"@react-types/dialog@npm:^3.5.22": - version: 3.5.22 - resolution: "@react-types/dialog@npm:3.5.22" - dependencies: - "@react-types/overlays": "npm:^3.9.2" - "@react-types/shared": "npm:^3.32.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/dedac6cf8a85568344242eb6e7f9c6f9dd9fff31711d23586374a1cfc4f3b7b7bed43fdff633d54135792dcdd9941a8a15a34bbbec1a9d4caff7b869b35dd71e - languageName: node - linkType: hard - -"@react-types/form@npm:^3.7.16": - version: 3.7.16 - resolution: "@react-types/form@npm:3.7.16" - dependencies: - "@react-types/shared": "npm:^3.32.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/3e8f156318746d31ef7470d366dcc1004f3a59b1a3a3f9e54331921a4b251ff51c6bf76a32be229f6d8c524b5b1292f3c1135597de6b0f52bae2d8c32a542a74 - languageName: node - linkType: hard - -"@react-types/grid@npm:^3.3.6": - version: 3.3.6 - resolution: "@react-types/grid@npm:3.3.6" - dependencies: - "@react-types/shared": "npm:^3.32.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/2b4be0fd8f6d07d3feca68039b89e0c13e1e566d27334a6ca7b6368f073ee55cf026833c5af664c78ccd137716aefd4f46b88f1b97709cd8080d7380c79454df - languageName: node - linkType: hard - -"@react-types/link@npm:^3.6.5": - version: 3.6.5 - resolution: "@react-types/link@npm:3.6.5" - dependencies: - "@react-types/shared": "npm:^3.32.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/59fb6011b146cffcdaae94e8fdd9241cb70697f7e518d39e3aacd14f244db6eea7ba3984d8b13d3ce601e6f160da36fd1bfa56827b2e1df728505e426a8db353 - languageName: node - linkType: hard - -"@react-types/listbox@npm:^3.7.4": - version: 3.7.4 - resolution: "@react-types/listbox@npm:3.7.4" - dependencies: - "@react-types/shared": "npm:^3.32.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/79dd3f6a7284b74f5156cc2265b384d2074ddfb7b38e608be855ec4ad1234aa0e7cdbfc0d7ef899e34113781381a6ee18b820dbe3dbee67364c72ac240144522 - languageName: node - linkType: hard - -"@react-types/menu@npm:^3.10.5": - version: 3.10.5 - resolution: "@react-types/menu@npm:3.10.5" - dependencies: - "@react-types/overlays": "npm:^3.9.2" - "@react-types/shared": "npm:^3.32.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/0466f5b7602ccfc8cb8fec3cd2482d587acbfd1701d427c7d79a121eb3f2b137feca70e62fd401aea95022fd616460575c4d5dd541cf3d69d8644ca54a2446c8 - languageName: node - linkType: hard - -"@react-types/meter@npm:^3.4.13": - version: 3.4.13 - resolution: "@react-types/meter@npm:3.4.13" - dependencies: - "@react-types/progress": "npm:^3.5.16" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/28b519a4640c72732719c056903fcf4037582984d235c388095319b5005eb9d5d2698306d9f79d1ad61d594b8ceda115acae27c22e95cb771d0e07da30305708 - languageName: node - linkType: hard - -"@react-types/numberfield@npm:^3.8.15": - version: 3.8.15 - resolution: "@react-types/numberfield@npm:3.8.15" - dependencies: - "@react-types/shared": "npm:^3.32.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/242584ab3ece90a5e26ebee9788c25fd44b85d6be405c5f31e08bda9d58ad8d7fcf199744a2fcd816275da13ae546ca0239175403e68788a43563f2a070c0574 - languageName: node - linkType: hard - -"@react-types/overlays@npm:^3.9.2": - version: 3.9.2 - resolution: "@react-types/overlays@npm:3.9.2" - dependencies: - "@react-types/shared": "npm:^3.32.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/6cab7f2cbb813f710696095db1169f902cfe7e4a9aeef496848343ff5116be3782bfea68dffbeaf3f984a0475c2fb6c4a26ad9fb563172c4ec3e47110ca1e672 - languageName: node - linkType: hard - -"@react-types/progress@npm:^3.5.16": - version: 3.5.16 - resolution: "@react-types/progress@npm:3.5.16" - dependencies: - "@react-types/shared": "npm:^3.32.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/315b34314dc135a6c9319f4ffd83570fac8c0da377b00d56f17c6eb3d416904682738b522b05d6d8cbb89d3ef92c5118bf8e58eca10fac121da6dcaa40563b8d - languageName: node - linkType: hard - -"@react-types/radio@npm:^3.9.2": - version: 3.9.2 - resolution: "@react-types/radio@npm:3.9.2" - dependencies: - "@react-types/shared": "npm:^3.32.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/3375d13bbeed28a1b959077e727b43324f4282cf43ec985edbaaa5b3e50b46199c083550882e2a9e4788c74555bfd25f5cc2b0351bf48a768f81a4ba6bf73222 - languageName: node - linkType: hard - -"@react-types/searchfield@npm:^3.6.6": - version: 3.6.6 - resolution: "@react-types/searchfield@npm:3.6.6" - dependencies: - "@react-types/shared": "npm:^3.32.1" - "@react-types/textfield": "npm:^3.12.6" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/942cb80234be5d61cae410c144873d75a243dec0bbf8bb0c696f13d66fd729dabb188bb6214d4bfadeb0d54e14eed3bb7e86c6721206bcb395658b613f22b53f - languageName: node - linkType: hard - -"@react-types/select@npm:^3.11.0": - version: 3.11.0 - resolution: "@react-types/select@npm:3.11.0" - dependencies: - "@react-types/shared": "npm:^3.32.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/6c431daa847a034ce06625fe75f220f301e8bdb50961c74e915f36919641f7907aa0d16d15127b9889853d197d66d964fd3726bd6c446b8aecf0e756e30e3925 - languageName: node - linkType: hard - -"@react-types/shared@npm:^3.32.1": - version: 3.32.1 - resolution: "@react-types/shared@npm:3.32.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/33c39d1e23fd73a18519679742ba548c128097831710af4803bec244ae96800271f88dcc4eab958734fc501bb65c17e590028596733761610fb0103c5dea6e36 - languageName: node - linkType: hard - -"@react-types/slider@npm:^3.8.2": - version: 3.8.2 - resolution: "@react-types/slider@npm:3.8.2" - dependencies: - "@react-types/shared": "npm:^3.32.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/cb9600a1842ace218be1a8bdb7b6785113c7165fde0cc76682b82cf7809927d2f45f95facb2570e7abb683f28f0a2ed0590c8ec8e948ed8dadffadb8905918d9 - languageName: node - linkType: hard - -"@react-types/switch@npm:^3.5.15": - version: 3.5.15 - resolution: "@react-types/switch@npm:3.5.15" - dependencies: - "@react-types/shared": "npm:^3.32.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/f35c075188b93aa07b6ab367a6d5bcc4316bcd5481d47e1b0319944411e3a12b15455c443079159280b6d168da36271f3df8bc55808a7d8249b239f5886f0253 - languageName: node - linkType: hard - -"@react-types/table@npm:^3.13.4": - version: 3.13.4 - resolution: "@react-types/table@npm:3.13.4" - dependencies: - "@react-types/grid": "npm:^3.3.6" - "@react-types/shared": "npm:^3.32.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/2f8c1878c8b9a6515a62c7592a335e9688c95a07e0445a891dd6069cbe26921573db6f7d6d2e0c22a8818ec5ef3f48f242ae24d955a1b18e7868f306fadfe7f5 - languageName: node - linkType: hard - -"@react-types/tabs@npm:^3.3.19": - version: 3.3.19 - resolution: "@react-types/tabs@npm:3.3.19" - dependencies: - "@react-types/shared": "npm:^3.32.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/cdc3217251502c6f89621ab366a4d23e32a334dc3ca2f5366cd980fb828bb413be1262a76f4c4794a0908bfd02c3237a3608e283b02ef472707fb7c22b80b44b - languageName: node - linkType: hard - -"@react-types/textfield@npm:^3.12.6": - version: 3.12.6 - resolution: "@react-types/textfield@npm:3.12.6" - dependencies: - "@react-types/shared": "npm:^3.32.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/e223531d29bbaad566f142b44a0f2e42b4ce08eab5661962ad3451391311a604077c49181f89a407aecf8697d59d887f5524f6eff8e9fe7416afda6b1f3ac7b3 - languageName: node - linkType: hard - -"@react-types/tooltip@npm:^3.4.21": - version: 3.4.21 - resolution: "@react-types/tooltip@npm:3.4.21" - dependencies: - "@react-types/overlays": "npm:^3.9.2" - "@react-types/shared": "npm:^3.32.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/50d1beea407a7e58ef8e3638c2517d8c90f6b15777d396b4adf7cffbd80d38dfb7d68d6a38127ed130bfe7b0c3116c5d499de3f7b6353f54d022a22739e0141d + checksum: 10/d28b0a3a3f68f94167fd7b4f474803430093b1a31f5f50cef6ddd755b923ba3af35dde40ffcc1f320926892744823a039b4a396c671f7c59aa49634811f0c43a languageName: node linkType: hard @@ -16619,10 +15395,10 @@ __metadata: languageName: node linkType: hard -"@remix-run/router@npm:1.21.1": - version: 1.21.1 - resolution: "@remix-run/router@npm:1.21.1" - checksum: 10/22a3dde5dd4ee131bddb5b589f974d6cb9e8378bda219bcdf592f399b9f10b8431d5f230bea507fc3b7f295998f8379942f5f07f920c087e2ecae65c76494e51 +"@remix-run/router@npm:1.23.2": + version: 1.23.2 + resolution: "@remix-run/router@npm:1.23.2" + checksum: 10/50eb497854881bbd2e1016d4eb83c935ecd618e1c3888b74718851317e3b04edbaae9fe1baa49ec08c5c52cfe7118f4664e37144813d9500f45f922d6602a782 languageName: node linkType: hard @@ -16634,53 +15410,52 @@ __metadata: linkType: hard "@remixicon/react@npm:^4.6.0": - version: 4.7.0 - resolution: "@remixicon/react@npm:4.7.0" + version: 4.9.0 + resolution: "@remixicon/react@npm:4.9.0" peerDependencies: react: ">=18.2.0" - checksum: 10/9ecee093dd4aec3744bcc7562eddb987bf9c7c059787dfecfbcc25a64c2f4833dc815dff715aa2a7f75011d65f877b3751a401d19cf2d725b5bcc685587d37f4 + checksum: 10/3d8f1d86b2bb20ab5e44d15f18811e928b0886f7710eb7a1516afb9913ba72e46facec5dfee382825139d800bcbb6704c15d0c760d0f977c12257d4af8db3295 languageName: node linkType: hard -"@repeaterjs/repeater@npm:^3.0.4": - version: 3.0.5 - resolution: "@repeaterjs/repeater@npm:3.0.5" - checksum: 10/7478df13bd4631729021b2f43524fe71a4ed04b3c1c2125315078e3954f059f2ff4da5776f9be8f76008df9849e866e5ec56120f41b8bf66d2ec1a7c7bc53229 +"@repeaterjs/repeater@npm:^3.0.4, @repeaterjs/repeater@npm:^3.0.6": + version: 3.0.6 + resolution: "@repeaterjs/repeater@npm:3.0.6" + checksum: 10/25698e822847b776006428f31e2d31fbcb4faccf30c1c8d68d6e1308e58b49afb08764d1dd15536ddd67775cd01fd6c2fb22f039c05a71865448fbcfb2246af2 languageName: node linkType: hard -"@rjsf/core@npm:5.23.2": - version: 5.23.2 - resolution: "@rjsf/core@npm:5.23.2" +"@rjsf/core@npm:5.24.13": + version: 5.24.13 + resolution: "@rjsf/core@npm:5.24.13" dependencies: lodash: "npm:^4.17.21" lodash-es: "npm:^4.17.21" markdown-to-jsx: "npm:^7.4.1" - nanoid: "npm:^3.3.7" prop-types: "npm:^15.8.1" peerDependencies: - "@rjsf/utils": ^5.23.x + "@rjsf/utils": ^5.24.x react: ^16.14.0 || >=17 - checksum: 10/a49cbd41bb8b499ad2ed521417f53bf3fa65c09bedbf0e02ce0f00a5287a3ae4df774e74f89a8dcb8a24359d382a585ea4d20c3a6e109f4aaad2cd74b7db2b01 + checksum: 10/80defb9ccbb563722dc0a613358122e86c775d10af04d1298e416e7069fa30cddf71823c82444fb1b5ab2f8e4706ff09f2606259fdd8ea59865373eb9493eb5e languageName: node linkType: hard -"@rjsf/material-ui@npm:5.23.2": - version: 5.23.2 - resolution: "@rjsf/material-ui@npm:5.23.2" +"@rjsf/material-ui@npm:5.24.13": + version: 5.24.13 + resolution: "@rjsf/material-ui@npm:5.24.13" peerDependencies: "@material-ui/core": ^4.12.3 "@material-ui/icons": ^4.11.2 - "@rjsf/core": ^5.23.x - "@rjsf/utils": ^5.23.x + "@rjsf/core": ^5.24.x + "@rjsf/utils": ^5.24.x react: ^16.14.0 || >=17 - checksum: 10/0c9ab33d4a2251bc4a3868fd09e6267ccd23e96b226204e1e2b5d9667fec375f64ac9674f3f5c8b013bb6e0b745f31854cab92b859b0c9ff2527338760664d5f + checksum: 10/b52e35973e81670cf57d8d3bda10b2b1bc57dfb5b6ab7be6176e6d5c384d40b86171bb30e7712e0d5971428e1553a70de08935fa680ef6877b0e97ad4c192059 languageName: node linkType: hard -"@rjsf/utils@npm:5.23.2": - version: 5.23.2 - resolution: "@rjsf/utils@npm:5.23.2" +"@rjsf/utils@npm:5.24.13": + version: 5.24.13 + resolution: "@rjsf/utils@npm:5.24.13" dependencies: json-schema-merge-allof: "npm:^0.8.1" jsonpointer: "npm:^5.0.1" @@ -16689,21 +15464,21 @@ __metadata: react-is: "npm:^18.2.0" peerDependencies: react: ^16.14.0 || >=17 - checksum: 10/739a65a40dede96dd1d202ad0c0df96ffe4c75cd3ebcf035da9589eecf2875100c9e28066e135ececa4c188abee436b9ccaa1536992ab5d8417bb6c9e43bd156 + checksum: 10/4bd6788c4d12c147bd26c67568267ce34d15f618c1f38f964e6981d202bf78e2101ab0d286987659840455115bd8f183aa133308bf0f8468ca44153723e3c6d0 languageName: node linkType: hard -"@rjsf/validator-ajv8@npm:5.23.2": - version: 5.23.2 - resolution: "@rjsf/validator-ajv8@npm:5.23.2" +"@rjsf/validator-ajv8@npm:5.24.13": + version: 5.24.13 + resolution: "@rjsf/validator-ajv8@npm:5.24.13" dependencies: ajv: "npm:^8.12.0" ajv-formats: "npm:^2.1.1" lodash: "npm:^4.17.21" lodash-es: "npm:^4.17.21" peerDependencies: - "@rjsf/utils": ^5.23.x - checksum: 10/568693ef0b93f21000b3b9352a65dd65001b4b85514fd94a1bf7562dc9ab8333f3a077c5335041fd7d250fac9a6c9cb77ea8539d8589a03afa55539898f896e6 + "@rjsf/utils": ^5.24.x + checksum: 10/a684862ded27792c8f40cfcf26436fa91f0666c6b95aa7281bb6448221a85cffb6a178fd99d172758aae2c41c58033047d902743962b7f3a2b46204ebdb0009d languageName: node linkType: hard @@ -16774,7 +15549,7 @@ __metadata: languageName: node linkType: hard -"@rollup/pluginutils@npm:^5.0.1, @rollup/pluginutils@npm:^5.0.2, @rollup/pluginutils@npm:^5.0.5, @rollup/pluginutils@npm:^5.1.0": +"@rollup/pluginutils@npm:^5.0.1, @rollup/pluginutils@npm:^5.0.2, @rollup/pluginutils@npm:^5.1.0": version: 5.2.0 resolution: "@rollup/pluginutils@npm:5.2.0" dependencies: @@ -16790,232 +15565,267 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-android-arm-eabi@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.46.2" +"@rollup/rollup-android-arm-eabi@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.60.1" conditions: os=android & cpu=arm languageName: node linkType: hard -"@rollup/rollup-android-arm64@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-android-arm64@npm:4.46.2" +"@rollup/rollup-android-arm64@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-android-arm64@npm:4.60.1" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-arm64@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-darwin-arm64@npm:4.46.2" +"@rollup/rollup-darwin-arm64@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-darwin-arm64@npm:4.60.1" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-x64@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-darwin-x64@npm:4.46.2" +"@rollup/rollup-darwin-x64@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-darwin-x64@npm:4.60.1" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-freebsd-arm64@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-freebsd-arm64@npm:4.46.2" +"@rollup/rollup-freebsd-arm64@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.60.1" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-freebsd-x64@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-freebsd-x64@npm:4.46.2" +"@rollup/rollup-freebsd-x64@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-freebsd-x64@npm:4.60.1" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-linux-arm-gnueabihf@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.46.2" +"@rollup/rollup-linux-arm-gnueabihf@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.60.1" conditions: os=linux & cpu=arm & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm-musleabihf@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.46.2" +"@rollup/rollup-linux-arm-musleabihf@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.60.1" conditions: os=linux & cpu=arm & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-arm64-gnu@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.46.2" +"@rollup/rollup-linux-arm64-gnu@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.60.1" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm64-musl@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.46.2" +"@rollup/rollup-linux-arm64-musl@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.60.1" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-loongarch64-gnu@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.46.2" +"@rollup/rollup-linux-loong64-gnu@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.60.1" conditions: os=linux & cpu=loong64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-ppc64-gnu@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.46.2" +"@rollup/rollup-linux-loong64-musl@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-loong64-musl@npm:4.60.1" + conditions: os=linux & cpu=loong64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-ppc64-gnu@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.60.1" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-gnu@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.46.2" +"@rollup/rollup-linux-ppc64-musl@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-ppc64-musl@npm:4.60.1" + conditions: os=linux & cpu=ppc64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-gnu@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.60.1" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-musl@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.46.2" +"@rollup/rollup-linux-riscv64-musl@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.60.1" conditions: os=linux & cpu=riscv64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-s390x-gnu@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.46.2" +"@rollup/rollup-linux-s390x-gnu@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.60.1" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-gnu@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.46.2" +"@rollup/rollup-linux-x64-gnu@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.60.1" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-musl@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.46.2" +"@rollup/rollup-linux-x64-musl@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.60.1" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-win32-arm64-msvc@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.46.2" +"@rollup/rollup-openbsd-x64@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-openbsd-x64@npm:4.60.1" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-openharmony-arm64@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-openharmony-arm64@npm:4.60.1" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-arm64-msvc@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.60.1" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-ia32-msvc@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.46.2" +"@rollup/rollup-win32-ia32-msvc@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.60.1" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@rollup/rollup-win32-x64-msvc@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.46.2" +"@rollup/rollup-win32-x64-gnu@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-win32-x64-gnu@npm:4.60.1" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@rspack/binding-darwin-arm64@npm:1.6.3": - version: 1.6.3 - resolution: "@rspack/binding-darwin-arm64@npm:1.6.3" +"@rollup/rollup-win32-x64-msvc@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.60.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@rspack/binding-darwin-arm64@npm:1.7.11": + version: 1.7.11 + resolution: "@rspack/binding-darwin-arm64@npm:1.7.11" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rspack/binding-darwin-x64@npm:1.6.3": - version: 1.6.3 - resolution: "@rspack/binding-darwin-x64@npm:1.6.3" +"@rspack/binding-darwin-x64@npm:1.7.11": + version: 1.7.11 + resolution: "@rspack/binding-darwin-x64@npm:1.7.11" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rspack/binding-linux-arm64-gnu@npm:1.6.3": - version: 1.6.3 - resolution: "@rspack/binding-linux-arm64-gnu@npm:1.6.3" +"@rspack/binding-linux-arm64-gnu@npm:1.7.11": + version: 1.7.11 + resolution: "@rspack/binding-linux-arm64-gnu@npm:1.7.11" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rspack/binding-linux-arm64-musl@npm:1.6.3": - version: 1.6.3 - resolution: "@rspack/binding-linux-arm64-musl@npm:1.6.3" +"@rspack/binding-linux-arm64-musl@npm:1.7.11": + version: 1.7.11 + resolution: "@rspack/binding-linux-arm64-musl@npm:1.7.11" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rspack/binding-linux-x64-gnu@npm:1.6.3": - version: 1.6.3 - resolution: "@rspack/binding-linux-x64-gnu@npm:1.6.3" +"@rspack/binding-linux-x64-gnu@npm:1.7.11": + version: 1.7.11 + resolution: "@rspack/binding-linux-x64-gnu@npm:1.7.11" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rspack/binding-linux-x64-musl@npm:1.6.3": - version: 1.6.3 - resolution: "@rspack/binding-linux-x64-musl@npm:1.6.3" +"@rspack/binding-linux-x64-musl@npm:1.7.11": + version: 1.7.11 + resolution: "@rspack/binding-linux-x64-musl@npm:1.7.11" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rspack/binding-wasm32-wasi@npm:1.6.3": - version: 1.6.3 - resolution: "@rspack/binding-wasm32-wasi@npm:1.6.3" +"@rspack/binding-wasm32-wasi@npm:1.7.11": + version: 1.7.11 + resolution: "@rspack/binding-wasm32-wasi@npm:1.7.11" dependencies: "@napi-rs/wasm-runtime": "npm:1.0.7" conditions: cpu=wasm32 languageName: node linkType: hard -"@rspack/binding-win32-arm64-msvc@npm:1.6.3": - version: 1.6.3 - resolution: "@rspack/binding-win32-arm64-msvc@npm:1.6.3" +"@rspack/binding-win32-arm64-msvc@npm:1.7.11": + version: 1.7.11 + resolution: "@rspack/binding-win32-arm64-msvc@npm:1.7.11" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rspack/binding-win32-ia32-msvc@npm:1.6.3": - version: 1.6.3 - resolution: "@rspack/binding-win32-ia32-msvc@npm:1.6.3" +"@rspack/binding-win32-ia32-msvc@npm:1.7.11": + version: 1.7.11 + resolution: "@rspack/binding-win32-ia32-msvc@npm:1.7.11" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@rspack/binding-win32-x64-msvc@npm:1.6.3": - version: 1.6.3 - resolution: "@rspack/binding-win32-x64-msvc@npm:1.6.3" +"@rspack/binding-win32-x64-msvc@npm:1.7.11": + version: 1.7.11 + resolution: "@rspack/binding-win32-x64-msvc@npm:1.7.11" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@rspack/binding@npm:1.6.3": - version: 1.6.3 - resolution: "@rspack/binding@npm:1.6.3" +"@rspack/binding@npm:1.7.11": + version: 1.7.11 + resolution: "@rspack/binding@npm:1.7.11" dependencies: - "@rspack/binding-darwin-arm64": "npm:1.6.3" - "@rspack/binding-darwin-x64": "npm:1.6.3" - "@rspack/binding-linux-arm64-gnu": "npm:1.6.3" - "@rspack/binding-linux-arm64-musl": "npm:1.6.3" - "@rspack/binding-linux-x64-gnu": "npm:1.6.3" - "@rspack/binding-linux-x64-musl": "npm:1.6.3" - "@rspack/binding-wasm32-wasi": "npm:1.6.3" - "@rspack/binding-win32-arm64-msvc": "npm:1.6.3" - "@rspack/binding-win32-ia32-msvc": "npm:1.6.3" - "@rspack/binding-win32-x64-msvc": "npm:1.6.3" + "@rspack/binding-darwin-arm64": "npm:1.7.11" + "@rspack/binding-darwin-x64": "npm:1.7.11" + "@rspack/binding-linux-arm64-gnu": "npm:1.7.11" + "@rspack/binding-linux-arm64-musl": "npm:1.7.11" + "@rspack/binding-linux-x64-gnu": "npm:1.7.11" + "@rspack/binding-linux-x64-musl": "npm:1.7.11" + "@rspack/binding-wasm32-wasi": "npm:1.7.11" + "@rspack/binding-win32-arm64-msvc": "npm:1.7.11" + "@rspack/binding-win32-ia32-msvc": "npm:1.7.11" + "@rspack/binding-win32-x64-msvc": "npm:1.7.11" dependenciesMeta: "@rspack/binding-darwin-arm64": optional: true @@ -17037,42 +15847,65 @@ __metadata: optional: true "@rspack/binding-win32-x64-msvc": optional: true - checksum: 10/c2ae95df065a3fc00fe81b8aef72dc1a4b80db274656e0c03db82fd12fba2d3a40514a741e0c0d3422d896d49eb34863d81b5c23d29df6d5f692e5b15a916510 + checksum: 10/750e779db78376a29275e22a20eb0342feb487ece923f433432c6c0f5348bb6a3ed015c32a8150cd61ad48e3a63ec664fe0bde376d40bfefc00eb17c969fea2d languageName: node linkType: hard "@rspack/core@npm:^1.4.11": - version: 1.6.3 - resolution: "@rspack/core@npm:1.6.3" + version: 1.7.11 + resolution: "@rspack/core@npm:1.7.11" dependencies: - "@module-federation/runtime-tools": "npm:0.21.4" - "@rspack/binding": "npm:1.6.3" + "@module-federation/runtime-tools": "npm:0.22.0" + "@rspack/binding": "npm:1.7.11" "@rspack/lite-tapable": "npm:1.1.0" peerDependencies: "@swc/helpers": ">=0.5.1" peerDependenciesMeta: "@swc/helpers": optional: true - checksum: 10/01bb10cf18331b2d6af841bc33bd15c1a01f01c5b16fd9b598e6b013ff138f91a9124c39f85530fd2a66dd693d0fa1252fc0331f0be9d7f2452c315c0a6e619a + checksum: 10/52a999c6bebe511495b05dbee4bd4400fe5575ed3d417f81b86109d385babebe0f4d80a8498c8ffe4d545dbe7afd2fa92066e1ef38ea75871d5550dbfbf66b73 languageName: node linkType: hard "@rspack/dev-server@npm:^1.1.4": - version: 1.1.4 - resolution: "@rspack/dev-server@npm:1.1.4" + version: 1.2.1 + resolution: "@rspack/dev-server@npm:1.2.1" dependencies: + "@types/bonjour": "npm:^3.5.13" + "@types/connect-history-api-fallback": "npm:^1.5.4" + "@types/express": "npm:^4.17.25" + "@types/express-serve-static-core": "npm:^4.17.21" + "@types/serve-index": "npm:^1.9.4" + "@types/serve-static": "npm:^1.15.5" + "@types/sockjs": "npm:^0.3.36" + "@types/ws": "npm:^8.5.10" + ansi-html-community: "npm:^0.0.8" + bonjour-service: "npm:^1.2.1" chokidar: "npm:^3.6.0" + colorette: "npm:^2.0.10" + compression: "npm:^1.8.1" + connect-history-api-fallback: "npm:^2.0.0" + express: "npm:^4.22.1" + graceful-fs: "npm:^4.2.6" http-proxy-middleware: "npm:^2.0.9" + ipaddr.js: "npm:^2.1.0" + launch-editor: "npm:^2.6.1" + open: "npm:^10.0.3" p-retry: "npm:^6.2.0" - webpack-dev-server: "npm:5.2.2" + schema-utils: "npm:^4.2.0" + selfsigned: "npm:^2.4.1" + serve-index: "npm:^1.9.1" + sockjs: "npm:^0.3.24" + spdy: "npm:^4.0.2" + webpack-dev-middleware: "npm:^7.4.2" ws: "npm:^8.18.0" peerDependencies: "@rspack/core": "*" - checksum: 10/9409af5b9cd6f3de70caea7f82595a5e99e39ec203ef93fc9973fefdcacf6b7d264d07e927a2331d73d7d606f211865d82386bdde410f6be506ff91a1af89d99 + checksum: 10/154808faef8079dc1d6eae1712455864cc7bc1ec686f3020f7117ad3e5f2906940f27ec514eb40230276132371570ecdf6b47f7ab117ad209462bcba7c2b0692 languageName: node linkType: hard -"@rspack/lite-tapable@npm:1.1.0, @rspack/lite-tapable@npm:^1.0.1": +"@rspack/lite-tapable@npm:1.1.0, @rspack/lite-tapable@npm:^1.1.0": version: 1.1.0 resolution: "@rspack/lite-tapable@npm:1.1.0" checksum: 10/41ff73fe5e1b8dccaad746c9c1bd36dd67649e1ad35776f311b5ba94333a397704e11158579e25a6a7e677c51abe35e66987b1b000faef48d4e4ad2470fea150 @@ -17080,8 +15913,8 @@ __metadata: linkType: hard "@rspack/plugin-react-refresh@npm:^1.4.3": - version: 1.5.3 - resolution: "@rspack/plugin-react-refresh@npm:1.5.3" + version: 1.6.1 + resolution: "@rspack/plugin-react-refresh@npm:1.6.1" dependencies: error-stack-parser: "npm:^2.1.4" html-entities: "npm:^2.6.0" @@ -17091,7 +15924,7 @@ __metadata: peerDependenciesMeta: webpack-hot-middleware: optional: true - checksum: 10/3886a13dca3d42e9245480283ab839c16015a48eceda0d7352114de26968e319e8f110c5ec4dbe4c20d4a8457b368401840c736ce49f774edd450cf3ba193f62 + checksum: 10/c55f6662b3d476ee1d89aa0ca2e00a4e968695713ee3768646364656bc383a2fda4216bbc8563fc750227839c4df9440de1ebe9cd059770947fd3289db82f067 languageName: node linkType: hard @@ -17102,14 +15935,14 @@ __metadata: languageName: node linkType: hard -"@rushstack/node-core-library@npm:5.9.0": - version: 5.9.0 - resolution: "@rushstack/node-core-library@npm:5.9.0" +"@rushstack/node-core-library@npm:5.19.1": + version: 5.19.1 + resolution: "@rushstack/node-core-library@npm:5.19.1" dependencies: ajv: "npm:~8.13.0" ajv-draft-04: "npm:~1.0.0" ajv-formats: "npm:~3.0.1" - fs-extra: "npm:~7.0.1" + fs-extra: "npm:~11.3.0" import-lazy: "npm:~4.0.0" jju: "npm:~1.4.0" resolve: "npm:~1.22.1" @@ -17119,44 +15952,118 @@ __metadata: peerDependenciesMeta: "@types/node": optional: true - checksum: 10/19d6c6fc6addfb27295d1d78e1027f5896ea43702c2e5168e8b235ee739f2e59a4dc3bb3996b083722fab4feae5f2c15c340900c6c1d517d56cc1ba945f532f1 + checksum: 10/a674c4ed4cf3c863ab6bfff0e3615e7f791d0312fa5942027b6e8070b4453464c0e77741a1ed13bc01fab66ddc89e8c068c6c58ce7d81c014966628b75223bd6 languageName: node linkType: hard -"@rushstack/rig-package@npm:0.5.3": - version: 0.5.3 - resolution: "@rushstack/rig-package@npm:0.5.3" +"@rushstack/node-core-library@npm:5.20.1": + version: 5.20.1 + resolution: "@rushstack/node-core-library@npm:5.20.1" + dependencies: + ajv: "npm:~8.13.0" + ajv-draft-04: "npm:~1.0.0" + ajv-formats: "npm:~3.0.1" + fs-extra: "npm:~11.3.0" + import-lazy: "npm:~4.0.0" + jju: "npm:~1.4.0" + resolve: "npm:~1.22.1" + semver: "npm:~7.5.4" + peerDependencies: + "@types/node": "*" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10/bd05a400fd96818a6382df7bc6a284adc78bbc8ae81c40760f2fee56a4124f0e56a38e007745bcf39c7449913e97f182860c1a344b56a31b8ba8445c21c6c012 + languageName: node + linkType: hard + +"@rushstack/problem-matcher@npm:0.1.1": + version: 0.1.1 + resolution: "@rushstack/problem-matcher@npm:0.1.1" + peerDependencies: + "@types/node": "*" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10/a47c2d5fd0e3bbe7336f06c29ef91061e36ab8dafd04c861392806e60a3366fd8c3921be217adc71d039c8749f6b70a06c874ff314501eed5b7f8fb7b42c7a39 + languageName: node + linkType: hard + +"@rushstack/problem-matcher@npm:0.2.1": + version: 0.2.1 + resolution: "@rushstack/problem-matcher@npm:0.2.1" + peerDependencies: + "@types/node": "*" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10/62fda91629577a2f57de19be357cd0990da145ff4933f4d2cd48f423cc03b92fca06dd8916dcbaf1d307a201c104847c77066d45d79fd3c323c4949f0c99bf44 + languageName: node + linkType: hard + +"@rushstack/rig-package@npm:0.7.1": + version: 0.7.1 + resolution: "@rushstack/rig-package@npm:0.7.1" dependencies: resolve: "npm:~1.22.1" strip-json-comments: "npm:~3.1.1" - checksum: 10/b58a3925a41d7a0e79f4fde7c400a379683cc7b0073c447aba6d36231529a37e7d2f4559f459be785ad862ecb01b618b2d0ff60661046e5223437356155ccb14 + checksum: 10/080a80e5c36b6861ee4a9a6e5ad9692cc3861cfb9edd0b02e9438aaaaa5a6e1b6f65275469d9f997696487841c7bb7daa69bba69c5e7301426056437bf544138 languageName: node linkType: hard -"@rushstack/terminal@npm:0.14.2": - version: 0.14.2 - resolution: "@rushstack/terminal@npm:0.14.2" +"@rushstack/terminal@npm:0.19.5": + version: 0.19.5 + resolution: "@rushstack/terminal@npm:0.19.5" dependencies: - "@rushstack/node-core-library": "npm:5.9.0" + "@rushstack/node-core-library": "npm:5.19.1" + "@rushstack/problem-matcher": "npm:0.1.1" supports-color: "npm:~8.1.1" peerDependencies: "@types/node": "*" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/4016499f3ed1eff7d870ff029bc51925f1435c0ed73d454385d6fdfe8f10e7e5fc2dba698a9aa671f2537603d2d08449782a0f329f321a7dcb87827c19385bca + checksum: 10/c5118df78045153aaf430de66e1d79befc729fd1f5df5dc8eef3dff1eb8f75a277f0dc8bad344b313d0485ace69400b1aaf69ad061c601a7bd8291ae5784b6a3 languageName: node linkType: hard -"@rushstack/ts-command-line@npm:4.22.8": - version: 4.22.8 - resolution: "@rushstack/ts-command-line@npm:4.22.8" +"@rushstack/terminal@npm:0.22.1": + version: 0.22.1 + resolution: "@rushstack/terminal@npm:0.22.1" dependencies: - "@rushstack/terminal": "npm:0.14.2" + "@rushstack/node-core-library": "npm:5.20.1" + "@rushstack/problem-matcher": "npm:0.2.1" + supports-color: "npm:~8.1.1" + peerDependencies: + "@types/node": "*" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10/fe4da212e11c60b8a6a2de9cb7658b03c510831d365c560eedf26a20fa85c62a45f1865cff99c2b252dcd773329fcd2347dd89e5e2efd5694d7746f0a8aec172 + languageName: node + linkType: hard + +"@rushstack/ts-command-line@npm:5.1.5": + version: 5.1.5 + resolution: "@rushstack/ts-command-line@npm:5.1.5" + dependencies: + "@rushstack/terminal": "npm:0.19.5" "@types/argparse": "npm:1.0.38" argparse: "npm:~1.0.9" string-argv: "npm:~0.3.1" - checksum: 10/f4d57a50b320f382929471258aaab6f38251a4febac981a503ae5217faec6fff68374d0126af59554d2f4a99e0ac67596649109fcc57608ef144b132de83cb63 + checksum: 10/4e3bc090b9c40ec89729aa96d45b08aeac725ce3fda9951d3b87b485fe48299db2dfa7a338acf7e89f380a5b98d44df8b4a18067962fecfb8ba64a8919e6dd2b + languageName: node + linkType: hard + +"@rushstack/ts-command-line@npm:5.3.1": + version: 5.3.1 + resolution: "@rushstack/ts-command-line@npm:5.3.1" + dependencies: + "@rushstack/terminal": "npm:0.22.1" + "@types/argparse": "npm:1.0.38" + argparse: "npm:~1.0.9" + string-argv: "npm:~0.3.1" + checksum: 10/51ca262eefbf07875f3e57fb402cba80e7ff36c14c0fc98c59af7be65407cafb4cbfe9b6738b549d91e687e40bb5720afb214efa1352a7a13c186241f92795f0 languageName: node linkType: hard @@ -17288,41 +16195,41 @@ __metadata: languageName: node linkType: hard -"@shikijs/engine-oniguruma@npm:^3.14.0": - version: 3.14.0 - resolution: "@shikijs/engine-oniguruma@npm:3.14.0" +"@shikijs/engine-oniguruma@npm:^3.23.0": + version: 3.23.0 + resolution: "@shikijs/engine-oniguruma@npm:3.23.0" dependencies: - "@shikijs/types": "npm:3.14.0" + "@shikijs/types": "npm:3.23.0" "@shikijs/vscode-textmate": "npm:^10.0.2" - checksum: 10/54fd65a8d4c2d85c170f324b122025b7c8f5f7d2e856e918ba8fed47efbca6ec976e92b37e463c8007527f597eec5403fcafad480b24588fc3304dcc66d06938 + checksum: 10/edd8983be86f6b055793813e80ecf31c3cefdd896f3742797ae03f2cbb9f467ac736c4b152d4a5c42dbd17da17d24ff798a15d37bcf6205d7f8cd8f44e2a11e0 languageName: node linkType: hard -"@shikijs/langs@npm:^3.14.0": - version: 3.14.0 - resolution: "@shikijs/langs@npm:3.14.0" +"@shikijs/langs@npm:^3.23.0": + version: 3.23.0 + resolution: "@shikijs/langs@npm:3.23.0" dependencies: - "@shikijs/types": "npm:3.14.0" - checksum: 10/fcbf350ab743dec154e0fedae40112d732930f8bf7e91829e492625c9e49067b7ee684ea276550894dcaea72ed0df63e97872cf7d2ed64fb6ab9f0cbc2431ce9 + "@shikijs/types": "npm:3.23.0" + checksum: 10/115b1afb9eb4eb300eb68b146e442f7e6d196878798c5b9d75dd53a6ef1e1c3b27e325353a10973e3fa47d88630e937b8a3cf3b37b6eef80bf2aec3d51657bb3 languageName: node linkType: hard -"@shikijs/themes@npm:^3.14.0": - version: 3.14.0 - resolution: "@shikijs/themes@npm:3.14.0" +"@shikijs/themes@npm:^3.23.0": + version: 3.23.0 + resolution: "@shikijs/themes@npm:3.23.0" dependencies: - "@shikijs/types": "npm:3.14.0" - checksum: 10/4ab89e6eaa1de0b41ab0fb3fefa65684cae87c6efab6cd75d951f3fee81faba04f022cdc45763956cc2415d08cf56ab2c3477655570ef8ef0a4ab0c0affe9190 + "@shikijs/types": "npm:3.23.0" + checksum: 10/741987380445b788aea6cc1e03492bc06950f1a0461edf45083bd226889c5ba78c7ed1af9724afacab7d6471777f0c0e8871577183dfb2cfbceb255663374835 languageName: node linkType: hard -"@shikijs/types@npm:3.14.0, @shikijs/types@npm:^3.14.0": - version: 3.14.0 - resolution: "@shikijs/types@npm:3.14.0" +"@shikijs/types@npm:3.23.0, @shikijs/types@npm:^3.23.0": + version: 3.23.0 + resolution: "@shikijs/types@npm:3.23.0" dependencies: "@shikijs/vscode-textmate": "npm:^10.0.2" "@types/hast": "npm:^3.0.4" - checksum: 10/361e6cd13a7c32f73ee056281fca19e53214450663ddf245c87f06e22c9d21e102cc5b8ef0778f0bf85146387b45347ae2f504591f5fd192f8ef3c5e2fcfccda + checksum: 10/18b5703d445d53dd6782a3e2c7332009302c6c046f301d9cd99f1a26b9c8329b3e502b096894bffac61f78835c533827bab2ce78a39666ab87b78f8d7ca11f7b languageName: node linkType: hard @@ -17416,6 +16323,13 @@ __metadata: languageName: node linkType: hard +"@sinclair/typebox@npm:^0.34.0": + version: 0.34.41 + resolution: "@sinclair/typebox@npm:0.34.41" + checksum: 10/5c04a7f42156a7813a159947a0c3fe7e9f11aa722141ac3ff32242faf031b443ef71763d8791ce8d01bd5856770de51fd6fcda94b3a51558ba1f6d5112fa33f4 + languageName: node + linkType: hard + "@sindresorhus/is@npm:^0.14.0": version: 0.14.0 resolution: "@sindresorhus/is@npm:0.14.0" @@ -17423,17 +16337,10 @@ __metadata: languageName: node linkType: hard -"@sindresorhus/is@npm:^4.0.0": - version: 4.0.0 - resolution: "@sindresorhus/is@npm:4.0.0" - checksum: 10/850804ccabb3e85b8c1395777a3b0e816bfcc308d4b7a65142bfd657f0753c31866ae5245f7a6d74d08191a4e63edab59c75b96bd19140d37b104efd683709d1 - languageName: node - linkType: hard - -"@sindresorhus/merge-streams@npm:^2.1.0": - version: 2.3.0 - resolution: "@sindresorhus/merge-streams@npm:2.3.0" - checksum: 10/798bcb53cd1ace9df84fcdd1ba86afdc9e0cd84f5758d26ae9b1eefd8e8887e5fc30051132b9e74daf01bb41fa5a2faf1369361f83d76a3b3d7ee938058fd71c +"@sindresorhus/is@npm:^4.0.0, @sindresorhus/is@npm:^4.6.0": + version: 4.6.0 + resolution: "@sindresorhus/is@npm:4.6.0" + checksum: 10/e7f36ed72abfcd5e0355f7423a72918b9748bb1ef370a59f3e5ad8d40b728b85d63b272f65f63eec1faf417cda89dcb0aeebe94015647b6054659c1442fe5ce0 languageName: node linkType: hard @@ -17464,21 +16371,12 @@ __metadata: languageName: node linkType: hard -"@sinonjs/fake-timers@npm:^10.0.2": - version: 10.3.0 - resolution: "@sinonjs/fake-timers@npm:10.3.0" - dependencies: - "@sinonjs/commons": "npm:^3.0.0" - checksum: 10/78155c7bd866a85df85e22028e046b8d46cf3e840f72260954f5e3ed5bd97d66c595524305a6841ffb3f681a08f6e5cef572a2cce5442a8a232dc29fb409b83e - languageName: node - linkType: hard - -"@sinonjs/fake-timers@npm:^13.0.1": - version: 13.0.2 - resolution: "@sinonjs/fake-timers@npm:13.0.2" +"@sinonjs/fake-timers@npm:^13.0.0, @sinonjs/fake-timers@npm:^13.0.1": + version: 13.0.5 + resolution: "@sinonjs/fake-timers@npm:13.0.5" dependencies: "@sinonjs/commons": "npm:^3.0.1" - checksum: 10/77cca5c548e2529931908c48ac375f162ee901bc52110197b4c470b2535c6c571f9ecd4fa12157f4d2ae174c5391f03940fb563a681a691fb44204a0ef3ded35 + checksum: 10/11ee417968fc4dce1896ab332ac13f353866075a9d2a88ed1f6258f17cc4f7d93e66031b51fcddb8c203aa4d53fd980b0ae18aba06269f4682164878a992ec3f languageName: node linkType: hard @@ -17531,12 +16429,12 @@ __metadata: languageName: node linkType: hard -"@slack/logger@npm:^4.0.0": - version: 4.0.0 - resolution: "@slack/logger@npm:4.0.0" +"@slack/logger@npm:^4.0.0, @slack/logger@npm:^4.0.1": + version: 4.0.1 + resolution: "@slack/logger@npm:4.0.1" dependencies: - "@types/node": "npm:>=18.0.0" - checksum: 10/dc79e9d2032c4bf9ce01d96cc72882f003dd376d036f172d4169662cfc2c9b384a80d5546b06021578dd473e7059f064303f0ba851eeb153387f2081a1e3062e + "@types/node": "npm:>=18" + checksum: 10/17e51749aa484262baa6cd8b057b7b2331ea6b140040d371c05ebe57ba61895c53b87e7e5bbfb1687c8e56199a78b36d675f06c35d8e20a363eabd9632cc8e96 languageName: node linkType: hard @@ -17569,10 +16467,10 @@ __metadata: languageName: node linkType: hard -"@slack/types@npm:^2.11.0, @slack/types@npm:^2.13.0, @slack/types@npm:^2.14.0, @slack/types@npm:^2.18.0": - version: 2.18.0 - resolution: "@slack/types@npm:2.18.0" - checksum: 10/c4a2a2ad0934503a9932779958c2d48af9227d6bb5b1f1568e2efcaac6e612467233962cec86069f13069820a0485d608380bea1dcb23705b1d34a78d108ca0a +"@slack/types@npm:^2.11.0, @slack/types@npm:^2.13.0, @slack/types@npm:^2.14.0, @slack/types@npm:^2.20.1": + version: 2.20.1 + resolution: "@slack/types@npm:2.20.1" + checksum: 10/5d13017df8c8468f34e4d24579a26f5ada934a6b7139e88d894c7d1d14f47da826a20c08e2537c6ab74d7624a4b193dc2b34458bae6466173fad1fd5c053b6f6 languageName: node linkType: hard @@ -17596,14 +16494,14 @@ __metadata: linkType: hard "@slack/web-api@npm:^7.5.0": - version: 7.12.0 - resolution: "@slack/web-api@npm:7.12.0" + version: 7.15.0 + resolution: "@slack/web-api@npm:7.15.0" dependencies: - "@slack/logger": "npm:^4.0.0" - "@slack/types": "npm:^2.18.0" - "@types/node": "npm:>=18.0.0" + "@slack/logger": "npm:^4.0.1" + "@slack/types": "npm:^2.20.1" + "@types/node": "npm:>=18" "@types/retry": "npm:0.12.0" - axios: "npm:^1.11.0" + axios: "npm:^1.13.5" eventemitter3: "npm:^5.0.1" form-data: "npm:^4.0.4" is-electron: "npm:2.2.2" @@ -17611,11 +16509,11 @@ __metadata: p-queue: "npm:^6" p-retry: "npm:^4" retry: "npm:^0.13.1" - checksum: 10/c8936187e1e99a758a13cb33d9aea0b4ecf9d5c78f6d8b7dd1e1cc3e0fb30f50ed718e5270ca6d3c9ccba259f3b1a058c95fc43d35366cdc6ac8cce07742344d + checksum: 10/de07791c2319ef2b2fb6acfc0e7ed35a7e52b078828a9a9875024498e8bd337b42980b6325603c3b9673338c50ead594a5438ab19a90acee1d15661e8e9c9bb8 languageName: node linkType: hard -"@smithy/abort-controller@npm:^3.1.2, @smithy/abort-controller@npm:^3.1.9": +"@smithy/abort-controller@npm:^3.1.9": version: 3.1.9 resolution: "@smithy/abort-controller@npm:3.1.9" dependencies: @@ -17625,268 +16523,180 @@ __metadata: languageName: node linkType: hard -"@smithy/abort-controller@npm:^4.2.3": +"@smithy/chunked-blob-reader-native@npm:^4.2.3": version: 4.2.3 - resolution: "@smithy/abort-controller@npm:4.2.3" + resolution: "@smithy/chunked-blob-reader-native@npm:4.2.3" dependencies: - "@smithy/types": "npm:^4.8.0" + "@smithy/util-base64": "npm:^4.3.2" tslib: "npm:^2.6.2" - checksum: 10/0e1b6ea58c876f953cfd953e1dbe8aa58f6fa6530fcec89522922d93a56426943c0b0b488438c12b8994bc50880b7386ad776d308963260f4c2b3edf5d2a7abf + checksum: 10/f1348b053c1d3e5bade4ea567795f6b87cc257e2bac2d76c44e6ea562dc18923f83fdae35b9cf0b97aa2b1d133086000144d970cb445a07a94c5620dd1dcb22a languageName: node linkType: hard -"@smithy/chunked-blob-reader-native@npm:^3.0.0": - version: 3.0.0 - resolution: "@smithy/chunked-blob-reader-native@npm:3.0.0" +"@smithy/chunked-blob-reader@npm:^5.2.2": + version: 5.2.2 + resolution: "@smithy/chunked-blob-reader@npm:5.2.2" dependencies: - "@smithy/util-base64": "npm:^3.0.0" tslib: "npm:^2.6.2" - checksum: 10/424aa83f4fc081625a03ec6c64e74ae38c740c0b202d0b998f2bf341b935613491b39c7bf701790a0625219424340d5cfb042b701bfdff4c1cbedc57ee3f2500 + checksum: 10/07f7eca6bed69b9ff1744d2f54acbeb05dfd534de535ee9d1ff75bc7c9c3725b32d91448990c5528bbacf893a96b969b683fdb473e871805fc38d8db40b1cb6f languageName: node linkType: hard -"@smithy/chunked-blob-reader@npm:^3.0.0": - version: 3.0.0 - resolution: "@smithy/chunked-blob-reader@npm:3.0.0" +"@smithy/config-resolver@npm:^4.4.13": + version: 4.4.13 + resolution: "@smithy/config-resolver@npm:4.4.13" dependencies: + "@smithy/node-config-provider": "npm:^4.3.12" + "@smithy/types": "npm:^4.13.1" + "@smithy/util-config-provider": "npm:^4.2.2" + "@smithy/util-endpoints": "npm:^3.3.3" + "@smithy/util-middleware": "npm:^4.2.12" tslib: "npm:^2.6.2" - checksum: 10/1c7955ae693aa098dd0839d7e8f9e742ab963de4ededa92f201f1982552c35ba625c1b90cf761de81deddd5002ed10f081ad46f6e0a5150066cee8b00f3f6058 + checksum: 10/30ee2ed270681144db3acf065a00a992fd0cd9bd2e7d2288f9341749bb92bae052ab5c2fdbe22b58c99bcec6e88d88ecbf7b7f04f2e94ba3342bcd4643c9bf37 languageName: node linkType: hard -"@smithy/config-resolver@npm:^3.0.6": - version: 3.0.6 - resolution: "@smithy/config-resolver@npm:3.0.6" +"@smithy/core@npm:^3.23.13": + version: 3.23.13 + resolution: "@smithy/core@npm:3.23.13" dependencies: - "@smithy/node-config-provider": "npm:^3.1.5" - "@smithy/types": "npm:^3.4.0" - "@smithy/util-config-provider": "npm:^3.0.0" - "@smithy/util-middleware": "npm:^3.0.4" + "@smithy/protocol-http": "npm:^5.3.12" + "@smithy/types": "npm:^4.13.1" + "@smithy/url-parser": "npm:^4.2.12" + "@smithy/util-base64": "npm:^4.3.2" + "@smithy/util-body-length-browser": "npm:^4.2.2" + "@smithy/util-middleware": "npm:^4.2.12" + "@smithy/util-stream": "npm:^4.5.21" + "@smithy/util-utf8": "npm:^4.2.2" + "@smithy/uuid": "npm:^1.1.2" tslib: "npm:^2.6.2" - checksum: 10/424c2e81ce0507bb232ece4ea2e74872ae553e134f1612c638811dfb2937cbce15c846ac1193f5872854f0a638761bacc1bddc4232df10bfed3becae48d4c897 + checksum: 10/a901d75cbf172023dc0abcc8ceacd518c18867c8887752675059282fd6862e08379f75b4c6f41f0b7dbd13a8c1d231aafa1b3b1e28fe79d1593f9c622fa560b7 languageName: node linkType: hard -"@smithy/config-resolver@npm:^4.3.2, @smithy/config-resolver@npm:^4.3.3": - version: 4.3.3 - resolution: "@smithy/config-resolver@npm:4.3.3" +"@smithy/credential-provider-imds@npm:^4.2.12": + version: 4.2.12 + resolution: "@smithy/credential-provider-imds@npm:4.2.12" dependencies: - "@smithy/node-config-provider": "npm:^4.3.3" - "@smithy/types": "npm:^4.8.0" - "@smithy/util-config-provider": "npm:^4.2.0" - "@smithy/util-middleware": "npm:^4.2.3" + "@smithy/node-config-provider": "npm:^4.3.12" + "@smithy/property-provider": "npm:^4.2.12" + "@smithy/types": "npm:^4.13.1" + "@smithy/url-parser": "npm:^4.2.12" tslib: "npm:^2.6.2" - checksum: 10/837e9a5f82aebbc114163e658462dd8a825e107d18687a404ceeb3650b992941f795dcef6b48f43875f6ff6262c7f435247d5c8c5b4c35615a198f3c3b8f7cc2 + checksum: 10/d68e72894cb4b3ac7fec28c03a95d8de884670ef26a4454e0d5f19454c4ccc50c1795eb3ea0ccae6c1ba8957f77743c614529d5f482b4d2ff3c529e310ae4746 languageName: node linkType: hard -"@smithy/core@npm:^2.4.1": - version: 2.4.1 - resolution: "@smithy/core@npm:2.4.1" - dependencies: - "@smithy/middleware-endpoint": "npm:^3.1.1" - "@smithy/middleware-retry": "npm:^3.0.16" - "@smithy/middleware-serde": "npm:^3.0.4" - "@smithy/protocol-http": "npm:^4.1.1" - "@smithy/smithy-client": "npm:^3.3.0" - "@smithy/types": "npm:^3.4.0" - "@smithy/util-body-length-browser": "npm:^3.0.0" - "@smithy/util-middleware": "npm:^3.0.4" - "@smithy/util-utf8": "npm:^3.0.0" - tslib: "npm:^2.6.2" - checksum: 10/82665c86e8e7629d11c759debd95bc827b871469d796d23bbb0328971d48e16460847c39a8bdf920c5a9fd87d2680bbdeaf2688931d7810bddf5e029b2393292 - languageName: node - linkType: hard - -"@smithy/core@npm:^3.16.1, @smithy/core@npm:^3.17.0": - version: 3.17.0 - resolution: "@smithy/core@npm:3.17.0" - dependencies: - "@smithy/middleware-serde": "npm:^4.2.3" - "@smithy/protocol-http": "npm:^5.3.3" - "@smithy/types": "npm:^4.8.0" - "@smithy/util-base64": "npm:^4.3.0" - "@smithy/util-body-length-browser": "npm:^4.2.0" - "@smithy/util-middleware": "npm:^4.2.3" - "@smithy/util-stream": "npm:^4.5.3" - "@smithy/util-utf8": "npm:^4.2.0" - "@smithy/uuid": "npm:^1.1.0" - tslib: "npm:^2.6.2" - checksum: 10/9f58db086801f69cba388a6c6e6af172c6f9dc7376497b608ddd25080b14a90eaa8de40cf9e5e3edb68135a3d9b0b35ce1dafb887a51781a59b4e2c2afc0223e - languageName: node - linkType: hard - -"@smithy/credential-provider-imds@npm:^3.2.1": - version: 3.2.1 - resolution: "@smithy/credential-provider-imds@npm:3.2.1" - dependencies: - "@smithy/node-config-provider": "npm:^3.1.5" - "@smithy/property-provider": "npm:^3.1.4" - "@smithy/types": "npm:^3.4.0" - "@smithy/url-parser": "npm:^3.0.4" - tslib: "npm:^2.6.2" - checksum: 10/8d413f24161549b6c7af55353844e2fb28f27836b12ce8ad884b8cd22a70aba0662a37215be021a2a00d962fd7a797ea9483a4df385d83b8b8c6f6716014a215 - languageName: node - linkType: hard - -"@smithy/credential-provider-imds@npm:^4.2.2, @smithy/credential-provider-imds@npm:^4.2.3": - version: 4.2.3 - resolution: "@smithy/credential-provider-imds@npm:4.2.3" - dependencies: - "@smithy/node-config-provider": "npm:^4.3.3" - "@smithy/property-provider": "npm:^4.2.3" - "@smithy/types": "npm:^4.8.0" - "@smithy/url-parser": "npm:^4.2.3" - tslib: "npm:^2.6.2" - checksum: 10/a654f9c5cd2be7a6faae6e71c0d69d1396037510eb843a3fd6297c81317ab03164a12582d6d24e132bb32f2a0b7c425e92ce963f7169329d8ef8245496a03ef1 - languageName: node - linkType: hard - -"@smithy/eventstream-codec@npm:^3.1.3": - version: 3.1.3 - resolution: "@smithy/eventstream-codec@npm:3.1.3" +"@smithy/eventstream-codec@npm:^4.2.12": + version: 4.2.12 + resolution: "@smithy/eventstream-codec@npm:4.2.12" dependencies: "@aws-crypto/crc32": "npm:5.2.0" - "@smithy/types": "npm:^3.4.0" - "@smithy/util-hex-encoding": "npm:^3.0.0" + "@smithy/types": "npm:^4.13.1" + "@smithy/util-hex-encoding": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/f4901b03cd8c0122519de8af48bdd3b586d3adc6837d7ff214b13d8981052a8ab8c284f551a75cfba58ff3741aac167cfa19ce24f254d067b64287951dcd6993 + checksum: 10/13fbc96cd968a2496d9f32f81127bd2b8e0ea9ce45c2eec145399ae5217996055b28a4bc96ae0fe79ca9250844c7bdb4be80bea4206a3910573b6d1f78034a25 languageName: node linkType: hard -"@smithy/eventstream-serde-browser@npm:^3.0.7": - version: 3.0.7 - resolution: "@smithy/eventstream-serde-browser@npm:3.0.7" +"@smithy/eventstream-serde-browser@npm:^4.2.12": + version: 4.2.12 + resolution: "@smithy/eventstream-serde-browser@npm:4.2.12" dependencies: - "@smithy/eventstream-serde-universal": "npm:^3.0.6" - "@smithy/types": "npm:^3.4.0" + "@smithy/eventstream-serde-universal": "npm:^4.2.12" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/203215491f2d1be688fbe3fe8c84d85c109cf703894707cff55e0f4720d1e7c81372f41f74d9a5da8733058311f201b73e80844b5d58bf03aab7c4a0c0967254 + checksum: 10/a8a071e2588b0c4bb4d37f1057309424b30e3fb7e45b25d5d2b6aa417afc821641416bef2371a66c8c6f5344324fbdd47e9943decf9200e76fe214817fd46769 languageName: node linkType: hard -"@smithy/eventstream-serde-config-resolver@npm:^3.0.4": - version: 3.0.4 - resolution: "@smithy/eventstream-serde-config-resolver@npm:3.0.4" +"@smithy/eventstream-serde-config-resolver@npm:^4.3.12": + version: 4.3.12 + resolution: "@smithy/eventstream-serde-config-resolver@npm:4.3.12" dependencies: - "@smithy/types": "npm:^3.4.0" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/073882d0b3b0b7f31261b5d900e147b5b193851affc46f89b1a737c6ab187e15de28a9521f84a39e649a6a6e8b8069cbc57f328e01d07a014c15da35f93bb38c + checksum: 10/0b3921b163e167a3567132da9fbfda2853d09b8b2bc1975da54115551bba458db63f90fd6cd40cf6961404947711c163a4e14f38651921df523d3de465debc79 languageName: node linkType: hard -"@smithy/eventstream-serde-node@npm:^3.0.6": - version: 3.0.6 - resolution: "@smithy/eventstream-serde-node@npm:3.0.6" +"@smithy/eventstream-serde-node@npm:^4.2.12": + version: 4.2.12 + resolution: "@smithy/eventstream-serde-node@npm:4.2.12" dependencies: - "@smithy/eventstream-serde-universal": "npm:^3.0.6" - "@smithy/types": "npm:^3.4.0" + "@smithy/eventstream-serde-universal": "npm:^4.2.12" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/635543e4312cc8cd95a5f1e79b728ea5e672aafba6d77f21700279ca9ba09a8cb30ea69eaa6cacf8176355e9db528b1bbb942566df92d2974ae927a951cf2321 + checksum: 10/93f082549aefe02ec74f35b837cc15cf7cde82c42b3e8acaee1b9b6cd3d497d4c678ad74924580e4a1c52394a01043ceea79e2d0b9a4e46038702769b2a8279b languageName: node linkType: hard -"@smithy/eventstream-serde-universal@npm:^3.0.6": - version: 3.0.6 - resolution: "@smithy/eventstream-serde-universal@npm:3.0.6" +"@smithy/eventstream-serde-universal@npm:^4.2.12": + version: 4.2.12 + resolution: "@smithy/eventstream-serde-universal@npm:4.2.12" dependencies: - "@smithy/eventstream-codec": "npm:^3.1.3" - "@smithy/types": "npm:^3.4.0" + "@smithy/eventstream-codec": "npm:^4.2.12" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/073eab5d5d1d421afa881ca7cf184cf28f64bc7234478338e28fe47b261701b3f9c671cdbdb7a03f5c8e4e03ece007e0c317c46f82558e03a4931771a103dcdb + checksum: 10/4b25447e153ceef292f8bcd27f49fbeb2e6d1d066b9d1904913db3254f5604709bed4c46b91d3f6e43d284bbdc33c55d29adcefe7063c771e05a36fd14ea0156 languageName: node linkType: hard -"@smithy/fetch-http-handler@npm:^3.2.5": - version: 3.2.5 - resolution: "@smithy/fetch-http-handler@npm:3.2.5" +"@smithy/fetch-http-handler@npm:^5.3.15": + version: 5.3.15 + resolution: "@smithy/fetch-http-handler@npm:5.3.15" dependencies: - "@smithy/protocol-http": "npm:^4.1.1" - "@smithy/querystring-builder": "npm:^3.0.4" - "@smithy/types": "npm:^3.4.0" - "@smithy/util-base64": "npm:^3.0.0" + "@smithy/protocol-http": "npm:^5.3.12" + "@smithy/querystring-builder": "npm:^4.2.12" + "@smithy/types": "npm:^4.13.1" + "@smithy/util-base64": "npm:^4.3.2" tslib: "npm:^2.6.2" - checksum: 10/d9f6387699fa83342efe41dc5f5a3a496afcdcfc5064b379f4e45be7b3809d2a5a5b6d794dbc90494f4b647b18100b972d8fb47066231ae8bd1146457405e09c + checksum: 10/5caed0a84eca6004bf83773c0e465b76237f5ac0b32794f147891d85b496f4ded0df42dead594cc5ec686545b7266de54c465bfc4d2d25288dea5df4686446bc languageName: node linkType: hard -"@smithy/fetch-http-handler@npm:^5.3.3, @smithy/fetch-http-handler@npm:^5.3.4": - version: 5.3.4 - resolution: "@smithy/fetch-http-handler@npm:5.3.4" +"@smithy/hash-blob-browser@npm:^4.2.13": + version: 4.2.13 + resolution: "@smithy/hash-blob-browser@npm:4.2.13" dependencies: - "@smithy/protocol-http": "npm:^5.3.3" - "@smithy/querystring-builder": "npm:^4.2.3" - "@smithy/types": "npm:^4.8.0" - "@smithy/util-base64": "npm:^4.3.0" + "@smithy/chunked-blob-reader": "npm:^5.2.2" + "@smithy/chunked-blob-reader-native": "npm:^4.2.3" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/d72a4981611a5ef7f7f2d9e0ab75c6b2d98dfc09eeab94849f60e61780642221509ff00ca85d73eb2f9d42ea99a14fc9804f25335104d015c57d573fed02262b + checksum: 10/d6790312b7fbcc3d7835114b14ac2bd272823a5807b4f09758ae420db91c4bcd3813359700fcc01d1c8e4e304cde3ad17b6a77ebcf48b63785babbc43ec46e59 languageName: node linkType: hard -"@smithy/hash-blob-browser@npm:^3.1.3": - version: 3.1.3 - resolution: "@smithy/hash-blob-browser@npm:3.1.3" +"@smithy/hash-node@npm:^4.2.12": + version: 4.2.12 + resolution: "@smithy/hash-node@npm:4.2.12" dependencies: - "@smithy/chunked-blob-reader": "npm:^3.0.0" - "@smithy/chunked-blob-reader-native": "npm:^3.0.0" - "@smithy/types": "npm:^3.4.0" + "@smithy/types": "npm:^4.13.1" + "@smithy/util-buffer-from": "npm:^4.2.2" + "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/26d1c76994d3c680ecb4e36f94a2f8c7d11054d685e200b420e7eda628415f58a293153b7dd1196e703fa0366c9fff3a74ff7024d95f2aeea5e72e9e4ab39521 + checksum: 10/d4ba8a5a02e875a52dca9effde822d227e0a762579275194a4e2ef1dff93eeb12f6e929bba973c21388f01e271df10c66dd986f83d59969ce6c7980d696e6e21 languageName: node linkType: hard -"@smithy/hash-node@npm:^3.0.4": - version: 3.0.4 - resolution: "@smithy/hash-node@npm:3.0.4" +"@smithy/hash-stream-node@npm:^4.2.12": + version: 4.2.12 + resolution: "@smithy/hash-stream-node@npm:4.2.12" dependencies: - "@smithy/types": "npm:^3.4.0" - "@smithy/util-buffer-from": "npm:^3.0.0" - "@smithy/util-utf8": "npm:^3.0.0" + "@smithy/types": "npm:^4.13.1" + "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/78d98c1d98e4f7595eb32368ffa15192314f9ee7874eccdef5e6f736a4afeb151f7e8d40d6d460705c58f32633bd9dc763ff9c9447813177c5e162df8e225abd + checksum: 10/810c67cf2c46922cce4b2ff45d732c2bbff3ed5e55aaf358869a4f48688fce730b6b22658030e445cbdd436e85f0379451aca828db453849df111081a19bfd14 languageName: node linkType: hard -"@smithy/hash-node@npm:^4.2.2": - version: 4.2.3 - resolution: "@smithy/hash-node@npm:4.2.3" +"@smithy/invalid-dependency@npm:^4.2.12": + version: 4.2.12 + resolution: "@smithy/invalid-dependency@npm:4.2.12" dependencies: - "@smithy/types": "npm:^4.8.0" - "@smithy/util-buffer-from": "npm:^4.2.0" - "@smithy/util-utf8": "npm:^4.2.0" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/7fad11066f36ac4d8c29f3676b26d8ad5aaf82b30ccf417a9eecf2cf8965c2fd0afd1f5f8e28d09112e350c84385fdaa5ed8596a11413a33794923cdc61253de - languageName: node - linkType: hard - -"@smithy/hash-stream-node@npm:^3.1.3": - version: 3.1.3 - resolution: "@smithy/hash-stream-node@npm:3.1.3" - dependencies: - "@smithy/types": "npm:^3.4.0" - "@smithy/util-utf8": "npm:^3.0.0" - tslib: "npm:^2.6.2" - checksum: 10/5c3faf2d6c846ceb914d5770823630e56f5b2c1a3fb2184d8a1a4668af23dcbc8337121ae3873bb86ba8f13f31434160a0d600fa1b60d9acdd150298959be901 - languageName: node - linkType: hard - -"@smithy/invalid-dependency@npm:^3.0.4": - version: 3.0.4 - resolution: "@smithy/invalid-dependency@npm:3.0.4" - dependencies: - "@smithy/types": "npm:^3.4.0" - tslib: "npm:^2.6.2" - checksum: 10/ad004c5fe4fe207714c39f3af7f9c0d240e9a782b0748eb08215317396d8da8b0329e4419e55acb4dcb072f200921af03697bc6f557109e0a27f0fa63a2eb78a - languageName: node - linkType: hard - -"@smithy/invalid-dependency@npm:^4.2.2": - version: 4.2.3 - resolution: "@smithy/invalid-dependency@npm:4.2.3" - dependencies: - "@smithy/types": "npm:^4.8.0" - tslib: "npm:^2.6.2" - checksum: 10/2aaac09946401b1665c686e43a08a09cd13fd9d4fac27849db70c685109b5419d06629b248a87921e939549fddc591a35d595855a753df990a0e9dc6ed807d77 + checksum: 10/94a4df6e1e71298d595b42e003979b9c256c8a1712e32ffba438608efe51b591e8c61b54e44bdaec7f009fb32ebb7b2116776a2fafd6fa72a05d16058f012b5a languageName: node linkType: hard @@ -17908,179 +16718,105 @@ __metadata: languageName: node linkType: hard -"@smithy/is-array-buffer@npm:^4.2.0": - version: 4.2.0 - resolution: "@smithy/is-array-buffer@npm:4.2.0" +"@smithy/is-array-buffer@npm:^4.2.2": + version: 4.2.2 + resolution: "@smithy/is-array-buffer@npm:4.2.2" dependencies: tslib: "npm:^2.6.2" - checksum: 10/fdc097ce6a8b241565e2d56460ec289730bcd734dcde17c23d1eaaa0996337f897217166276a3fd82491fe9fd17447aadf62e8d9056b3d2b9daf192b4b668af9 + checksum: 10/ebf9bac3daad0e1c3b201d41c4d2ab4be0c08c4c34604f87965b73cb052b1fd99133088f3b9837527f8fd6ed071b8684bb554ff381e5fdeacfc5907a66e4688b languageName: node linkType: hard -"@smithy/md5-js@npm:^3.0.4": - version: 3.0.4 - resolution: "@smithy/md5-js@npm:3.0.4" +"@smithy/md5-js@npm:^4.2.12": + version: 4.2.12 + resolution: "@smithy/md5-js@npm:4.2.12" dependencies: - "@smithy/types": "npm:^3.4.0" - "@smithy/util-utf8": "npm:^3.0.0" + "@smithy/types": "npm:^4.13.1" + "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/f84d4b37b713c6edd9e46f4e60a193fca4b6c882c4288683b334f0c3fc224abf884252e5fd9d55d04823192b1c0dca9c26f865599a8663397e51ce566c7df148 + checksum: 10/6e0b84bd5ecb1fd850ae78aabf37a67df329065ebdc54d9843a60e141abe31636ebf05e0f5458e7cfa7d3e8ff54b5849e43a38dbbad843fd54677d9cb1fde2e0 languageName: node linkType: hard -"@smithy/middleware-content-length@npm:^3.0.6": - version: 3.0.6 - resolution: "@smithy/middleware-content-length@npm:3.0.6" +"@smithy/middleware-content-length@npm:^4.2.12": + version: 4.2.12 + resolution: "@smithy/middleware-content-length@npm:4.2.12" dependencies: - "@smithy/protocol-http": "npm:^4.1.1" - "@smithy/types": "npm:^3.4.0" + "@smithy/protocol-http": "npm:^5.3.12" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/f9f3f3b6099ad5c5b697e5dca0016ff2f182ed0b66739c12c9cfa46f7c92761d9ffc8425097c929e094fd030a20f60ffbf6bd701d1062592dfcb0ba88a3fe53e + checksum: 10/899a1fecf72c116e8a86c64e0239479b2a4031ba5ec7f5e7606bf22ab80e4e6eb1f480bd3d99b90347b4ed5937bbdabc7689a1d79abc5bca2c4bb9b1db415836 languageName: node linkType: hard -"@smithy/middleware-content-length@npm:^4.2.2": - version: 4.2.3 - resolution: "@smithy/middleware-content-length@npm:4.2.3" +"@smithy/middleware-endpoint@npm:^4.4.28": + version: 4.4.28 + resolution: "@smithy/middleware-endpoint@npm:4.4.28" dependencies: - "@smithy/protocol-http": "npm:^5.3.3" - "@smithy/types": "npm:^4.8.0" + "@smithy/core": "npm:^3.23.13" + "@smithy/middleware-serde": "npm:^4.2.16" + "@smithy/node-config-provider": "npm:^4.3.12" + "@smithy/shared-ini-file-loader": "npm:^4.4.7" + "@smithy/types": "npm:^4.13.1" + "@smithy/url-parser": "npm:^4.2.12" + "@smithy/util-middleware": "npm:^4.2.12" tslib: "npm:^2.6.2" - checksum: 10/dc8dc197435e6a3b95812bfb57431b87c7d41175c8d24372f81e5eeda19448bca2fd2cb8829788e33abe3833e32da66e3fc44482ede3c929e04eec31bb74244d + checksum: 10/afbb873c955e4ca02a9c33f709f0dfc4e32f9b0a834ffb7860881167b611d80282955a220c3568a362efef47b273c7da533d0dafe4d3d0b5c3fa9258e4708e23 languageName: node linkType: hard -"@smithy/middleware-endpoint@npm:^3.1.1": - version: 3.1.1 - resolution: "@smithy/middleware-endpoint@npm:3.1.1" +"@smithy/middleware-retry@npm:^4.4.45, @smithy/middleware-retry@npm:^4.4.46": + version: 4.4.46 + resolution: "@smithy/middleware-retry@npm:4.4.46" dependencies: - "@smithy/middleware-serde": "npm:^3.0.4" - "@smithy/node-config-provider": "npm:^3.1.5" - "@smithy/shared-ini-file-loader": "npm:^3.1.5" - "@smithy/types": "npm:^3.4.0" - "@smithy/url-parser": "npm:^3.0.4" - "@smithy/util-middleware": "npm:^3.0.4" + "@smithy/node-config-provider": "npm:^4.3.12" + "@smithy/protocol-http": "npm:^5.3.12" + "@smithy/service-error-classification": "npm:^4.2.12" + "@smithy/smithy-client": "npm:^4.12.8" + "@smithy/types": "npm:^4.13.1" + "@smithy/util-middleware": "npm:^4.2.12" + "@smithy/util-retry": "npm:^4.2.13" + "@smithy/uuid": "npm:^1.1.2" tslib: "npm:^2.6.2" - checksum: 10/a4cc7321e1e38d99eb2a641f5a52c7c56e6bad6aecdceefb7200d97f26b0c146483d3b57c2e44330d730c445f416d26a6d2250ad7f39d6a98e9748bc8a358de7 + checksum: 10/6444f96e8355522b907d1a5a3d9743cc0ba41531a46392c1f085a9c44396befe0cb414d18716ec29c0bbdf5602b5844d17b6eebec92ad27f64ecd5261ebea5a8 languageName: node linkType: hard -"@smithy/middleware-endpoint@npm:^4.3.3, @smithy/middleware-endpoint@npm:^4.3.4": - version: 4.3.4 - resolution: "@smithy/middleware-endpoint@npm:4.3.4" +"@smithy/middleware-serde@npm:^4.2.16": + version: 4.2.16 + resolution: "@smithy/middleware-serde@npm:4.2.16" dependencies: - "@smithy/core": "npm:^3.17.0" - "@smithy/middleware-serde": "npm:^4.2.3" - "@smithy/node-config-provider": "npm:^4.3.3" - "@smithy/shared-ini-file-loader": "npm:^4.3.3" - "@smithy/types": "npm:^4.8.0" - "@smithy/url-parser": "npm:^4.2.3" - "@smithy/util-middleware": "npm:^4.2.3" + "@smithy/core": "npm:^3.23.13" + "@smithy/protocol-http": "npm:^5.3.12" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/018dae712791d553bf9484b4183b6b6162da0d9a9baf6d8c39fae5669a6adf764b294edcf63827c119fed1da0bbfa3f751f4796d35379d13b8ac36504c449685 + checksum: 10/57509a894067c111b1e71b5915d24d4ba5901616f016b308486d77bd18e71a1cec2eb5b73964486643f19d15a149d15f5db89b37f77a6bdcb65f11de789aed0f languageName: node linkType: hard -"@smithy/middleware-retry@npm:^3.0.16": - version: 3.0.16 - resolution: "@smithy/middleware-retry@npm:3.0.16" +"@smithy/middleware-stack@npm:^4.2.12": + version: 4.2.12 + resolution: "@smithy/middleware-stack@npm:4.2.12" dependencies: - "@smithy/node-config-provider": "npm:^3.1.5" - "@smithy/protocol-http": "npm:^4.1.1" - "@smithy/service-error-classification": "npm:^3.0.4" - "@smithy/smithy-client": "npm:^3.3.0" - "@smithy/types": "npm:^3.4.0" - "@smithy/util-middleware": "npm:^3.0.4" - "@smithy/util-retry": "npm:^3.0.4" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - uuid: "npm:^9.0.1" - checksum: 10/90a3ec6dd79f200f74f9a025a6acf5ca4c87342826d88ccec7668addad1961032ba10a6018d6a16c43779dc2f296c6baf9dc00679d1113649381d41089a4decb + checksum: 10/0202c4607273f23485b26e716c98ee60af2900a26dd8d8f1df2f0ffd59383ddd21f44f62d977e5d8b2893bb516e2c4fb0054f52645bd5c9f17a08f1e016e3a1e languageName: node linkType: hard -"@smithy/middleware-retry@npm:^4.4.3": - version: 4.4.4 - resolution: "@smithy/middleware-retry@npm:4.4.4" +"@smithy/node-config-provider@npm:^4.3.12": + version: 4.3.12 + resolution: "@smithy/node-config-provider@npm:4.3.12" dependencies: - "@smithy/node-config-provider": "npm:^4.3.3" - "@smithy/protocol-http": "npm:^5.3.3" - "@smithy/service-error-classification": "npm:^4.2.3" - "@smithy/smithy-client": "npm:^4.9.0" - "@smithy/types": "npm:^4.8.0" - "@smithy/util-middleware": "npm:^4.2.3" - "@smithy/util-retry": "npm:^4.2.3" - "@smithy/uuid": "npm:^1.1.0" + "@smithy/property-provider": "npm:^4.2.12" + "@smithy/shared-ini-file-loader": "npm:^4.4.7" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/850477842b63bf6b7d362a777eee68db0d11578b3437a3ced55b3c9531622bb126531e7b9b3b4ab3627e59780f86bb3be690eb2ba0aa34fc1bf13cb29dad54c1 + checksum: 10/687a2dcc454e791e05cd85e4d476cc67a83de4997e15687398fe7149f0748543bd8aeebc9bf7a0c38eee55cb6bb0f438879f974770d7f93fdf5b1b2d9ed3a99f languageName: node linkType: hard -"@smithy/middleware-serde@npm:^3.0.4": - version: 3.0.4 - resolution: "@smithy/middleware-serde@npm:3.0.4" - dependencies: - "@smithy/types": "npm:^3.4.0" - tslib: "npm:^2.6.2" - checksum: 10/a4d3dd79b6cc314245bfcb0e649e5869cabf6cfb8e9685ae797919ecaeb123eb0335c054c9278dba1304fe4a0cd06e71d6a77e0442f62530bfabd5df79c819da - languageName: node - linkType: hard - -"@smithy/middleware-serde@npm:^4.2.2, @smithy/middleware-serde@npm:^4.2.3": - version: 4.2.3 - resolution: "@smithy/middleware-serde@npm:4.2.3" - dependencies: - "@smithy/protocol-http": "npm:^5.3.3" - "@smithy/types": "npm:^4.8.0" - tslib: "npm:^2.6.2" - checksum: 10/725583757ed653628887c72a3924e11eda9de88a72ca337538fb3365f1cb732c0652129d15033560548178bcccf7964211926340f56fdd972a28dab549b32dc1 - languageName: node - linkType: hard - -"@smithy/middleware-stack@npm:^3.0.4": - version: 3.0.4 - resolution: "@smithy/middleware-stack@npm:3.0.4" - dependencies: - "@smithy/types": "npm:^3.4.0" - tslib: "npm:^2.6.2" - checksum: 10/63bf03bd512367ff781e3a220a5126cb3784e982891e1dcb00cf975da6801d454ffe9d244ac67f6db2ee2f34146089d113bc9ae25f190c12a318d121ccdd03fb - languageName: node - linkType: hard - -"@smithy/middleware-stack@npm:^4.2.2, @smithy/middleware-stack@npm:^4.2.3": - version: 4.2.3 - resolution: "@smithy/middleware-stack@npm:4.2.3" - dependencies: - "@smithy/types": "npm:^4.8.0" - tslib: "npm:^2.6.2" - checksum: 10/f6054dbb2b3f141390940caa93319c750f447da483b36d506208200023100eeda5a550b9d3415812ad194604c54cae48aa2e185f6c3ab625e87718089d75b3ab - languageName: node - linkType: hard - -"@smithy/node-config-provider@npm:^3.1.5": - version: 3.1.5 - resolution: "@smithy/node-config-provider@npm:3.1.5" - dependencies: - "@smithy/property-provider": "npm:^3.1.4" - "@smithy/shared-ini-file-loader": "npm:^3.1.5" - "@smithy/types": "npm:^3.4.0" - tslib: "npm:^2.6.2" - checksum: 10/1efa20757cb0d21eafc377d1820c2b644ef3a45ace4c2012e299ff4f9cb1f390c3ab72de5827d50302dccc5e6f1ef741778befcdb91ef5c319de9b1888cd1b96 - languageName: node - linkType: hard - -"@smithy/node-config-provider@npm:^4.3.2, @smithy/node-config-provider@npm:^4.3.3": - version: 4.3.3 - resolution: "@smithy/node-config-provider@npm:4.3.3" - dependencies: - "@smithy/property-provider": "npm:^4.2.3" - "@smithy/shared-ini-file-loader": "npm:^4.3.3" - "@smithy/types": "npm:^4.8.0" - tslib: "npm:^2.6.2" - checksum: 10/0c2191861f5bcb4a0e1d5b15968c4edeb33a910948ee8378a3504311ee4da8f53a29d223ee86433b693cbd6aac9977560e62a5ef2fae108904408f4bf8b89e55 - languageName: node - linkType: hard - -"@smithy/node-http-handler@npm:^3.0.0, @smithy/node-http-handler@npm:^3.2.0": +"@smithy/node-http-handler@npm:^3.0.0": version: 3.3.3 resolution: "@smithy/node-http-handler@npm:3.3.3" dependencies: @@ -18093,40 +16829,29 @@ __metadata: languageName: node linkType: hard -"@smithy/node-http-handler@npm:^4.4.1, @smithy/node-http-handler@npm:^4.4.2": - version: 4.4.2 - resolution: "@smithy/node-http-handler@npm:4.4.2" +"@smithy/node-http-handler@npm:^4.5.1": + version: 4.5.1 + resolution: "@smithy/node-http-handler@npm:4.5.1" dependencies: - "@smithy/abort-controller": "npm:^4.2.3" - "@smithy/protocol-http": "npm:^5.3.3" - "@smithy/querystring-builder": "npm:^4.2.3" - "@smithy/types": "npm:^4.8.0" + "@smithy/protocol-http": "npm:^5.3.12" + "@smithy/querystring-builder": "npm:^4.2.12" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/82d08f563dad787a2685c3dbc2ae8d15d11f02bce2a5ba3e078b8093e2324f6a3e15ff5730cacc0a5bbd1c096134497da15c03bd0e03c73041205f8d84621910 + checksum: 10/30a60b59759b8aee97588aecf121b0ab8b16f3040382a26247fe1f779d863f76e544cfc28d14f49a8705124ec58c73e37291602a4253055f3f2abf4f484c8e35 languageName: node linkType: hard -"@smithy/property-provider@npm:^3.1.4": - version: 3.1.4 - resolution: "@smithy/property-provider@npm:3.1.4" +"@smithy/property-provider@npm:^4.2.12": + version: 4.2.12 + resolution: "@smithy/property-provider@npm:4.2.12" dependencies: - "@smithy/types": "npm:^3.4.0" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/d1c1597be13f4ea43d61117e388a5ea574163d888bf55f1df182c978fa2d92231ec0ff98b4a6c338aa39b9f71e379e1ddb2567b3f9979c633c957165426c02c2 + checksum: 10/f5cad886f96bd8f8aff8f718f9f053166c8b61bcabeb93e085b5e3acae97000c73d89103e925c4b8dc2862d56e876eeb7b9d9bab540b198d52e95ba984e196d5 languageName: node linkType: hard -"@smithy/property-provider@npm:^4.2.2, @smithy/property-provider@npm:^4.2.3": - version: 4.2.3 - resolution: "@smithy/property-provider@npm:4.2.3" - dependencies: - "@smithy/types": "npm:^4.8.0" - tslib: "npm:^2.6.2" - checksum: 10/01368d05e878235fbda10f2d2b6c1d004d918ed84fcf01d26ad4765f4a6c7c6d889a0f3e08c786cf977f02c2eb53ae2fb01ac511c5f538ac6daa0170d41dbd7d - languageName: node - linkType: hard - -"@smithy/protocol-http@npm:^4.1.1, @smithy/protocol-http@npm:^4.1.8": +"@smithy/protocol-http@npm:^4.1.8": version: 4.1.8 resolution: "@smithy/protocol-http@npm:4.1.8" dependencies: @@ -18136,17 +16861,17 @@ __metadata: languageName: node linkType: hard -"@smithy/protocol-http@npm:^5.3.2, @smithy/protocol-http@npm:^5.3.3": - version: 5.3.3 - resolution: "@smithy/protocol-http@npm:5.3.3" +"@smithy/protocol-http@npm:^5.3.12": + version: 5.3.12 + resolution: "@smithy/protocol-http@npm:5.3.12" dependencies: - "@smithy/types": "npm:^4.8.0" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/224043361976827a5498809e0d9fe7c5fd8a8233da40f05b8113cf5358e6bc0d4a4e6160d74ffdadb0c9733fd88cab32625d324cfd45748beb19fa69201410fb + checksum: 10/05322f09dae5b0187446dd0fca7c52bf8dcacc7e85932579180984668e2849c7ab337faf2654f71238e3591d3bcf40c085a3694511c6a46d2125ab4440b3fd5c languageName: node linkType: hard -"@smithy/querystring-builder@npm:^3.0.11, @smithy/querystring-builder@npm:^3.0.4": +"@smithy/querystring-builder@npm:^3.0.11": version: 3.0.11 resolution: "@smithy/querystring-builder@npm:3.0.11" dependencies: @@ -18157,133 +16882,90 @@ __metadata: languageName: node linkType: hard -"@smithy/querystring-builder@npm:^4.2.3": - version: 4.2.3 - resolution: "@smithy/querystring-builder@npm:4.2.3" +"@smithy/querystring-builder@npm:^4.2.12": + version: 4.2.12 + resolution: "@smithy/querystring-builder@npm:4.2.12" dependencies: - "@smithy/types": "npm:^4.8.0" - "@smithy/util-uri-escape": "npm:^4.2.0" + "@smithy/types": "npm:^4.13.1" + "@smithy/util-uri-escape": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/b0f7733d66b9e821d1efe260dc44db5586c3f8b1ebe59d30ff1fcf5d3dab87a1fa133f005e9947e7127dbfa6e504581bd2507b37c6568889ab2a99dceacb4064 + checksum: 10/42876f4fb36c4fb83a31eea9c43b89e8ce45d3437ec1134455ac91a5a2fb1beb974cee30db0fbb93eb8a5211370957cf2e209affc6fcb1cc67da139eaa607051 languageName: node linkType: hard -"@smithy/querystring-parser@npm:^3.0.4": - version: 3.0.4 - resolution: "@smithy/querystring-parser@npm:3.0.4" +"@smithy/querystring-parser@npm:^4.2.12": + version: 4.2.12 + resolution: "@smithy/querystring-parser@npm:4.2.12" dependencies: - "@smithy/types": "npm:^3.4.0" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/a985e95590ea3acc84f4123aa951bd6fdcc4b3cb83ef179d906635f82589b025cdde6f73c6c3417b18a569c6aee82ff9345d34e3b3d976f71ea5d9132033adea + checksum: 10/8f5edea9e8f0fd777b401aa2e71535ec4c9cec9a769758cedce89a0bea53b759f79d89217ae70ed16ed12dcfc4c28e6d3f5ef7723a624618beb866b363076bd8 languageName: node linkType: hard -"@smithy/querystring-parser@npm:^4.2.3": - version: 4.2.3 - resolution: "@smithy/querystring-parser@npm:4.2.3" +"@smithy/service-error-classification@npm:^4.2.12": + version: 4.2.12 + resolution: "@smithy/service-error-classification@npm:4.2.12" dependencies: - "@smithy/types": "npm:^4.8.0" + "@smithy/types": "npm:^4.13.1" + checksum: 10/b552334c99cac79593307e679ac9a78c9ff9bf3c0f522c29bcb9a1d612c0b77c988e6ff945f51a233394ca588b8aaaa75de0365ff1982d0f68b7be01242bc45e + languageName: node + linkType: hard + +"@smithy/shared-ini-file-loader@npm:^4.4.7": + version: 4.4.7 + resolution: "@smithy/shared-ini-file-loader@npm:4.4.7" + dependencies: + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/39bd2f57cf1c695f2942b4dd5362dd80ad2d2fd40d03e90396721735637190bd05ea9e942639b67ae8817a9e99fff9b510f6ba04568f14ad62228e676994107a + checksum: 10/d64795a6386ef4a165876ef9df312421fdf8123efc1d926894322dd3aeff88b48432e98c188b782952c8dcb0513172cf3cfcc519ea3e55a87c82efcf03bec36c languageName: node linkType: hard -"@smithy/service-error-classification@npm:^3.0.4": - version: 3.0.4 - resolution: "@smithy/service-error-classification@npm:3.0.4" - dependencies: - "@smithy/types": "npm:^3.4.0" - checksum: 10/a06600871da110279b7e44901b395e95965391de5456ac747b972bd0330484270946c0eae796f1ba60b6ebe8c5e4a5447f1ccd5d6793b27b45dbc12d1776fad4 - languageName: node - linkType: hard - -"@smithy/service-error-classification@npm:^4.2.3": - version: 4.2.3 - resolution: "@smithy/service-error-classification@npm:4.2.3" - dependencies: - "@smithy/types": "npm:^4.8.0" - checksum: 10/fcb61c68fb820c379ef3e86d6b316c10b706ef76f23f19c66323555b9973cec34a237caabcb67fb5b5553f93ddb52acd1d2dbbe6a9f64806584089588c203c38 - languageName: node - linkType: hard - -"@smithy/shared-ini-file-loader@npm:^3.1.5": - version: 3.1.5 - resolution: "@smithy/shared-ini-file-loader@npm:3.1.5" - dependencies: - "@smithy/types": "npm:^3.4.0" - tslib: "npm:^2.6.2" - checksum: 10/722e375db736195c12680558a395129affbcf486219da80684363b7035bdef6394399881696dd4645ce7af28197a50aed66a26bb2a526195fab419a87eb324cb - languageName: node - linkType: hard - -"@smithy/shared-ini-file-loader@npm:^4.3.2, @smithy/shared-ini-file-loader@npm:^4.3.3": - version: 4.3.3 - resolution: "@smithy/shared-ini-file-loader@npm:4.3.3" - dependencies: - "@smithy/types": "npm:^4.8.0" - tslib: "npm:^2.6.2" - checksum: 10/2b724e27e7e7ff58be6a75985856f3cb11b3830cd04eab3375743ab961fa9a8a5bb5efa6cb40f89afc73702c6daa36c415153f460e4ec6b08394aa4101a1ec41 - languageName: node - linkType: hard - -"@smithy/signature-v4@npm:^4.1.1": - version: 4.1.1 - resolution: "@smithy/signature-v4@npm:4.1.1" +"@smithy/signature-v4@npm:^4.1.0": + version: 4.2.4 + resolution: "@smithy/signature-v4@npm:4.2.4" dependencies: "@smithy/is-array-buffer": "npm:^3.0.0" - "@smithy/protocol-http": "npm:^4.1.1" - "@smithy/types": "npm:^3.4.0" + "@smithy/protocol-http": "npm:^4.1.8" + "@smithy/types": "npm:^3.7.2" "@smithy/util-hex-encoding": "npm:^3.0.0" - "@smithy/util-middleware": "npm:^3.0.4" + "@smithy/util-middleware": "npm:^3.0.11" "@smithy/util-uri-escape": "npm:^3.0.0" "@smithy/util-utf8": "npm:^3.0.0" tslib: "npm:^2.6.2" - checksum: 10/f77d8b6cb384a0b9a0925ce6a693fbbcbf13c6f9ddf12cb9e1b39fb06452d001b17164d5e2618ea103edb427f2f1225d057827e2815e629b582115b2d533194f + checksum: 10/1389616fb798963fae989191ad044b821c7543d9df7bef4eaaf21d59e9b0a9e1e529a87f9013389408d0227e1acd32a916f694487ae6144b0d0217f16d65d06b languageName: node linkType: hard -"@smithy/signature-v4@npm:^5.3.2": - version: 5.3.3 - resolution: "@smithy/signature-v4@npm:5.3.3" +"@smithy/signature-v4@npm:^5.3.12": + version: 5.3.12 + resolution: "@smithy/signature-v4@npm:5.3.12" dependencies: - "@smithy/is-array-buffer": "npm:^4.2.0" - "@smithy/protocol-http": "npm:^5.3.3" - "@smithy/types": "npm:^4.8.0" - "@smithy/util-hex-encoding": "npm:^4.2.0" - "@smithy/util-middleware": "npm:^4.2.3" - "@smithy/util-uri-escape": "npm:^4.2.0" - "@smithy/util-utf8": "npm:^4.2.0" + "@smithy/is-array-buffer": "npm:^4.2.2" + "@smithy/protocol-http": "npm:^5.3.12" + "@smithy/types": "npm:^4.13.1" + "@smithy/util-hex-encoding": "npm:^4.2.2" + "@smithy/util-middleware": "npm:^4.2.12" + "@smithy/util-uri-escape": "npm:^4.2.2" + "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/75c22765749fbcfe34110d745afc346aa6b6d62d2ede74e062be48e65704ff92abf1695c1e1323b211a813f355849aaeb2caa0cab3c92a34112512c51728e9ab + checksum: 10/e56fb5e16bf672ff0773b33b4045b0fa657a90c625e7eb9e34860c625543ec3eb1f94e87389349bae9747cb06837b64dc7abe5954c8aa7c84a28216e52046349 languageName: node linkType: hard -"@smithy/smithy-client@npm:^3.3.0": - version: 3.3.0 - resolution: "@smithy/smithy-client@npm:3.3.0" +"@smithy/smithy-client@npm:^4.12.8": + version: 4.12.8 + resolution: "@smithy/smithy-client@npm:4.12.8" dependencies: - "@smithy/middleware-endpoint": "npm:^3.1.1" - "@smithy/middleware-stack": "npm:^3.0.4" - "@smithy/protocol-http": "npm:^4.1.1" - "@smithy/types": "npm:^3.4.0" - "@smithy/util-stream": "npm:^3.1.4" + "@smithy/core": "npm:^3.23.13" + "@smithy/middleware-endpoint": "npm:^4.4.28" + "@smithy/middleware-stack": "npm:^4.2.12" + "@smithy/protocol-http": "npm:^5.3.12" + "@smithy/types": "npm:^4.13.1" + "@smithy/util-stream": "npm:^4.5.21" tslib: "npm:^2.6.2" - checksum: 10/b060a7ae03ff9fc98a7478114709466a4d0813eda3d77e98567733f42be376d4935650abb34d6a9fe41610f5498096c06fa0677bdbe24f2d455c733f46ed48e6 - languageName: node - linkType: hard - -"@smithy/smithy-client@npm:^4.8.1, @smithy/smithy-client@npm:^4.9.0": - version: 4.9.0 - resolution: "@smithy/smithy-client@npm:4.9.0" - dependencies: - "@smithy/core": "npm:^3.17.0" - "@smithy/middleware-endpoint": "npm:^4.3.4" - "@smithy/middleware-stack": "npm:^4.2.3" - "@smithy/protocol-http": "npm:^5.3.3" - "@smithy/types": "npm:^4.8.0" - "@smithy/util-stream": "npm:^4.5.3" - tslib: "npm:^2.6.2" - checksum: 10/b1533d0faeaa810b87997ddc40bdda64a49af24b10fdb88ef37d1a45743b1d8eb292b7835e1da4c3f5facf47e4cc7caec7e2ccceae46ddb529257419b207086c + checksum: 10/c78efec622cb9596fdeccb18910a4dad63b9ededd85e49a216746cd787c1f683c7e8b41d508cde726fc75155b8607a199cafe7b90044f772e1a91dbcccd04105 languageName: node linkType: hard @@ -18296,7 +16978,7 @@ __metadata: languageName: node linkType: hard -"@smithy/types@npm:^3.4.0, @smithy/types@npm:^3.7.2": +"@smithy/types@npm:^3.7.2": version: 3.7.2 resolution: "@smithy/types@npm:3.7.2" dependencies: @@ -18305,92 +16987,52 @@ __metadata: languageName: node linkType: hard -"@smithy/types@npm:^4.7.1, @smithy/types@npm:^4.8.0": - version: 4.8.0 - resolution: "@smithy/types@npm:4.8.0" +"@smithy/types@npm:^4.13.1": + version: 4.13.1 + resolution: "@smithy/types@npm:4.13.1" dependencies: tslib: "npm:^2.6.2" - checksum: 10/12ee5d629cae55b696e870caefbdbebc774deffee4171d0543552b6f8ce06ac1259fa3cf93e9be767f5e1f3c59c7ffa28a6d3dbe0a8f0c8e7811d3acbf3ab87c + checksum: 10/d6cb8a4f4ee4d09625f2e5f8d4290d44254b7e8599a96cd5d528f9225447e1da1193caa62d1e9dee59d5c0b890fe0fd9d6d9439566fff746c46acb66935122db languageName: node linkType: hard -"@smithy/url-parser@npm:^3.0.4": - version: 3.0.4 - resolution: "@smithy/url-parser@npm:3.0.4" +"@smithy/url-parser@npm:^4.2.12": + version: 4.2.12 + resolution: "@smithy/url-parser@npm:4.2.12" dependencies: - "@smithy/querystring-parser": "npm:^3.0.4" - "@smithy/types": "npm:^3.4.0" + "@smithy/querystring-parser": "npm:^4.2.12" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/0b582094db501fb9f285fbc61503ac32191557ef6467f75e4019b1ea3ebaa2ceaa6bebd2b7db54b68ad244a67c29d3ea1262006dd252bb78acc95126b1035ba5 + checksum: 10/119395a684fdcfb58faf9647c1f0ec3c6475a5ec394e5bdebe3e9bb1f2f2b31c7f3c104d1a7525b29987792d8113cc92333b883cf98842659eaafa922d18120f languageName: node linkType: hard -"@smithy/url-parser@npm:^4.2.2, @smithy/url-parser@npm:^4.2.3": +"@smithy/util-base64@npm:^4.3.2": + version: 4.3.2 + resolution: "@smithy/util-base64@npm:4.3.2" + dependencies: + "@smithy/util-buffer-from": "npm:^4.2.2" + "@smithy/util-utf8": "npm:^4.2.2" + tslib: "npm:^2.6.2" + checksum: 10/9246e1874c2e96c2059e97c88e039bf40abb8b0d6fb229e1aca9df896371c44a9dc951c99d2febbd8267033920cef4180852e6655f3de8c4743d9a9bbf6d96b4 + languageName: node + linkType: hard + +"@smithy/util-body-length-browser@npm:^4.2.2": + version: 4.2.2 + resolution: "@smithy/util-body-length-browser@npm:4.2.2" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10/c3058710fddecee22d4bc03839310c13783a7593a0a1808998998c465f6408012b5ce34bbec379fff84fb56f4017747d7817c4a7f050b45834b2aada27a2e7a7 + languageName: node + linkType: hard + +"@smithy/util-body-length-node@npm:^4.2.3": version: 4.2.3 - resolution: "@smithy/url-parser@npm:4.2.3" - dependencies: - "@smithy/querystring-parser": "npm:^4.2.3" - "@smithy/types": "npm:^4.8.0" - tslib: "npm:^2.6.2" - checksum: 10/e8af96bb56b5cc18841a731ec01c16ea420628099c4c090c65f0a9867e693a53fa95a7b05fac60b9ac0ef1bfad9308bf13e68ac854d8fb62f599a3dcd164203d - languageName: node - linkType: hard - -"@smithy/util-base64@npm:^3.0.0": - version: 3.0.0 - resolution: "@smithy/util-base64@npm:3.0.0" - dependencies: - "@smithy/util-buffer-from": "npm:^3.0.0" - "@smithy/util-utf8": "npm:^3.0.0" - tslib: "npm:^2.6.2" - checksum: 10/3c883d63a33e1cfeebf7f846f167dfc54c832d1f5514d014fbfff06de3aecd5919f01637fc93668dca8a1029752f3a6fab0a94f455dcb7c88f1d472bde294eef - languageName: node - linkType: hard - -"@smithy/util-base64@npm:^4.3.0": - version: 4.3.0 - resolution: "@smithy/util-base64@npm:4.3.0" - dependencies: - "@smithy/util-buffer-from": "npm:^4.2.0" - "@smithy/util-utf8": "npm:^4.2.0" - tslib: "npm:^2.6.2" - checksum: 10/87065ca13e3745858e0bb0ab6374433b258c378ee2a5ef865b74f6a4208c56db7db2b9ee5f888e021de0107fae49e9957662c4c6847fe10529e2f6cc882426b4 - languageName: node - linkType: hard - -"@smithy/util-body-length-browser@npm:^3.0.0": - version: 3.0.0 - resolution: "@smithy/util-body-length-browser@npm:3.0.0" + resolution: "@smithy/util-body-length-node@npm:4.2.3" dependencies: tslib: "npm:^2.6.2" - checksum: 10/a0ab6a6d414a62d18e57f3581769379a54bb1fd93606c69bc8d96a3566fdecb8db7b57da9446568d03eef9f004f2a89d7e94bdda79ef280f28b19a71803c0309 - languageName: node - linkType: hard - -"@smithy/util-body-length-browser@npm:^4.2.0": - version: 4.2.0 - resolution: "@smithy/util-body-length-browser@npm:4.2.0" - dependencies: - tslib: "npm:^2.6.2" - checksum: 10/deeb689b52652651c11530a324e07725805533899215ad1f93c5e9a14931443e22b313491a3c2a6d7f61d6dd1e84f9154d0d32de62bf61e0bd8e6ab7bf5f81ed - languageName: node - linkType: hard - -"@smithy/util-body-length-node@npm:^3.0.0": - version: 3.0.0 - resolution: "@smithy/util-body-length-node@npm:3.0.0" - dependencies: - tslib: "npm:^2.6.2" - checksum: 10/aabac66d7111612fd375d67150f8787c5cdc828f7e6acb40ef0b18b4c354e64e0ef2e4b8da2d7f01e8abe931ff2ef8109a408164ce7e5662fd41b470c462f1e4 - languageName: node - linkType: hard - -"@smithy/util-body-length-node@npm:^4.2.1": - version: 4.2.1 - resolution: "@smithy/util-body-length-node@npm:4.2.1" - dependencies: - tslib: "npm:^2.6.2" - checksum: 10/efb1333d35120124ec0c751b7b7d5657eb9ad6d0bf6171ff61fde2504639883d36e9562613c70eca623b726193b22601c8ff60e40a8156102d4c5b12fae222f8 + checksum: 10/ad271366f72505f66ac82b87f0964211c79f40cb5bebeceeb23d74613ce33f1e77a9e81b03b48aced35c76ddd7184103886ce13dde96c8ee6e05dec59239798b languageName: node linkType: hard @@ -18414,108 +17056,60 @@ __metadata: languageName: node linkType: hard -"@smithy/util-buffer-from@npm:^4.2.0": - version: 4.2.0 - resolution: "@smithy/util-buffer-from@npm:4.2.0" +"@smithy/util-buffer-from@npm:^4.2.2": + version: 4.2.2 + resolution: "@smithy/util-buffer-from@npm:4.2.2" dependencies: - "@smithy/is-array-buffer": "npm:^4.2.0" + "@smithy/is-array-buffer": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/6a81e658554d7123fe089426a840b5e691aee4aa4f0d72b79af19dcf57ccb212dca518acb447714792d48c2dc99bda5e0e823dab05e450ee2393146706d476f9 + checksum: 10/111148eb7fb8c2913c0f09ca9991a409c69b2df643aa73378e64e14404ce040f67c716f7b4f55b76c0640f4357b649b9eb6a7f1539d7b37a2f0a7e0c3ba7062d languageName: node linkType: hard -"@smithy/util-config-provider@npm:^3.0.0": - version: 3.0.0 - resolution: "@smithy/util-config-provider@npm:3.0.0" +"@smithy/util-config-provider@npm:^4.2.2": + version: 4.2.2 + resolution: "@smithy/util-config-provider@npm:4.2.2" dependencies: tslib: "npm:^2.6.2" - checksum: 10/614c321c5a5a220d7d72d36359c41b4e390566719f7e01cefebffbe7034aae78b4533b27ab2030f93186c5f22893ddf056a3a2376a077d70ce89275f31e1ac46 + checksum: 10/ff3989fc07b5162674ccce6aacf7c74dbaea9e7a731c05399a80ed758a67adf83e87d47431a69aa2b1c325497670ff7d1390d9441e3c0c2cea66e830f61f965a languageName: node linkType: hard -"@smithy/util-config-provider@npm:^4.2.0": - version: 4.2.0 - resolution: "@smithy/util-config-provider@npm:4.2.0" +"@smithy/util-defaults-mode-browser@npm:^4.3.44": + version: 4.3.44 + resolution: "@smithy/util-defaults-mode-browser@npm:4.3.44" dependencies: + "@smithy/property-provider": "npm:^4.2.12" + "@smithy/smithy-client": "npm:^4.12.8" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/d65f36401c7a085660cf201a1b317d271e390258b619179fff88248c2db64fc35e6c62fe055f1e55be8935b06eb600379824dabf634fb26d528f54fe60c9d77b + checksum: 10/39ea98e4dd9a75390f07678982547e589386795cf939b3958c2da31b31e8f3f2009a6c65caf5831c5b40f51a461cb0e7d2aeb3199ba1e7e52768ce157e42a8b2 languageName: node linkType: hard -"@smithy/util-defaults-mode-browser@npm:^3.0.16": - version: 3.0.16 - resolution: "@smithy/util-defaults-mode-browser@npm:3.0.16" +"@smithy/util-defaults-mode-node@npm:^4.2.48": + version: 4.2.48 + resolution: "@smithy/util-defaults-mode-node@npm:4.2.48" dependencies: - "@smithy/property-provider": "npm:^3.1.4" - "@smithy/smithy-client": "npm:^3.3.0" - "@smithy/types": "npm:^3.4.0" - bowser: "npm:^2.11.0" + "@smithy/config-resolver": "npm:^4.4.13" + "@smithy/credential-provider-imds": "npm:^4.2.12" + "@smithy/node-config-provider": "npm:^4.3.12" + "@smithy/property-provider": "npm:^4.2.12" + "@smithy/smithy-client": "npm:^4.12.8" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/5a4a3a7b9df20e84ce9f7555860ebabd2afdb48614a4ef15c75ea1895f6567baa0dbe5670cce3f05d62a4fd080fad650623915e73ee90b5ab29afe4c4c55ed15 + checksum: 10/aa11fb8e9e6748e1788153d27c5313db020799f79f0546730d852d961c148dbbcc1cdf59803944731b47b6fab0a747a1d287741f145be7e2c79425129506176f languageName: node linkType: hard -"@smithy/util-defaults-mode-browser@npm:^4.3.2": - version: 4.3.3 - resolution: "@smithy/util-defaults-mode-browser@npm:4.3.3" +"@smithy/util-endpoints@npm:^3.3.3": + version: 3.3.3 + resolution: "@smithy/util-endpoints@npm:3.3.3" dependencies: - "@smithy/property-provider": "npm:^4.2.3" - "@smithy/smithy-client": "npm:^4.9.0" - "@smithy/types": "npm:^4.8.0" + "@smithy/node-config-provider": "npm:^4.3.12" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/74efd0f09dc5638c1f183eb976f7293706dc37d506e23f534a0042324515e57d0e34ac6b65b75af2d1cc2f81bcec6626167d1bd231b278bb2505ae7c29eb0578 - languageName: node - linkType: hard - -"@smithy/util-defaults-mode-node@npm:^3.0.16": - version: 3.0.16 - resolution: "@smithy/util-defaults-mode-node@npm:3.0.16" - dependencies: - "@smithy/config-resolver": "npm:^3.0.6" - "@smithy/credential-provider-imds": "npm:^3.2.1" - "@smithy/node-config-provider": "npm:^3.1.5" - "@smithy/property-provider": "npm:^3.1.4" - "@smithy/smithy-client": "npm:^3.3.0" - "@smithy/types": "npm:^3.4.0" - tslib: "npm:^2.6.2" - checksum: 10/4b2a8b982cc694c6cd43ed2c721d042093e3133aba4cf6c7bae92608905c5e764fda73730f56456d39cc2fe4b56a4e9eb2fb221435db9f9ff821167fff79628a - languageName: node - linkType: hard - -"@smithy/util-defaults-mode-node@npm:^4.2.3": - version: 4.2.4 - resolution: "@smithy/util-defaults-mode-node@npm:4.2.4" - dependencies: - "@smithy/config-resolver": "npm:^4.3.3" - "@smithy/credential-provider-imds": "npm:^4.2.3" - "@smithy/node-config-provider": "npm:^4.3.3" - "@smithy/property-provider": "npm:^4.2.3" - "@smithy/smithy-client": "npm:^4.9.0" - "@smithy/types": "npm:^4.8.0" - tslib: "npm:^2.6.2" - checksum: 10/2c89802f37ee74803d3aa151c628b6d6479c42546582e50924a93e232b3b3ea289a8373833479f92f940cfe57c48714a4be03e78cea6cdcaa001ef482126b785 - languageName: node - linkType: hard - -"@smithy/util-endpoints@npm:^2.1.0": - version: 2.1.0 - resolution: "@smithy/util-endpoints@npm:2.1.0" - dependencies: - "@smithy/node-config-provider": "npm:^3.1.5" - "@smithy/types": "npm:^3.4.0" - tslib: "npm:^2.6.2" - checksum: 10/813d5f60132315912c6170f25021db69c8f7ade602faa3be0a6d96d44fb87a399f5a21408f5435dbd9fe55cde8a5d9f26bf842eb1f9ef72c03c4febf30b8e33e - languageName: node - linkType: hard - -"@smithy/util-endpoints@npm:^3.2.2": - version: 3.2.3 - resolution: "@smithy/util-endpoints@npm:3.2.3" - dependencies: - "@smithy/node-config-provider": "npm:^4.3.3" - "@smithy/types": "npm:^4.8.0" - tslib: "npm:^2.6.2" - checksum: 10/55b94c1ad0b59ca92ba0fb8510bd981d9e82cc178425ed681172418993be4744371d8408fa5ac70a88019219534bed8cba4d42177bfadd0bb6d474e701161452 + checksum: 10/101de268f2acfd69f1c26732bc3f809439f9853f2d9246d6024c57b3cfbea9dc8f187841a0edece041def2a1a523dbd68dc4f55d5eb43e2c8bcfc68a423ec4fe languageName: node linkType: hard @@ -18528,86 +17122,59 @@ __metadata: languageName: node linkType: hard -"@smithy/util-hex-encoding@npm:^4.2.0": - version: 4.2.0 - resolution: "@smithy/util-hex-encoding@npm:4.2.0" +"@smithy/util-hex-encoding@npm:^4.2.2": + version: 4.2.2 + resolution: "@smithy/util-hex-encoding@npm:4.2.2" dependencies: tslib: "npm:^2.6.2" - checksum: 10/478773d73690e39167b67481116c4fd47cecfc97c3a935d88db9271fb0718627bec1cbc143efbf0cd49d1ac417bde7e76aa74139ea07e365b51e66797f63a45d + checksum: 10/2b1dfed0fcbe13c9a449b06adf805814fb3ec5d0d614704bfb250875cec7cf19f5a77a81013c91b81f45b7193038268f92d59de339192d578c9ef77a1b51c4d9 languageName: node linkType: hard -"@smithy/util-middleware@npm:^3.0.4": - version: 3.0.4 - resolution: "@smithy/util-middleware@npm:3.0.4" +"@smithy/util-middleware@npm:^3.0.11": + version: 3.0.11 + resolution: "@smithy/util-middleware@npm:3.0.11" dependencies: - "@smithy/types": "npm:^3.4.0" + "@smithy/types": "npm:^3.7.2" tslib: "npm:^2.6.2" - checksum: 10/d358c8e5ab462b749b32421135f1660302de8b2a942e5cf92b63b1489e87d0fc57aa4cc578fee46a7192472946b06a898d98b1f01c8cf4b9fccc4ad489ffde5a + checksum: 10/d8197dc223ddc5ed6e9b40bb44f58793b0fe09d39734e70729ed7606c5497047f1dbf0d616764767b3c8812d49d76d94679a29dc7c879211b0e918e68715bde4 languageName: node linkType: hard -"@smithy/util-middleware@npm:^4.2.2, @smithy/util-middleware@npm:^4.2.3": - version: 4.2.3 - resolution: "@smithy/util-middleware@npm:4.2.3" +"@smithy/util-middleware@npm:^4.2.12": + version: 4.2.12 + resolution: "@smithy/util-middleware@npm:4.2.12" dependencies: - "@smithy/types": "npm:^4.8.0" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/3f029d65bb40c7b4555590e502d51942bde0bd413ac691417f09b7cbe384ddb22cb12976ee6c652beac76a8652927caa64c00a743a441639df0d4eb1fecc9c79 + checksum: 10/03dce2320d0a06d96ecf310d703bdbef9f55be1d2008567a98ab326361c395adc4dcb5455a1a4035697ba8b92b8189331c6f6ac83a4253ee20bb3c8ba7732936 languageName: node linkType: hard -"@smithy/util-retry@npm:^3.0.4": - version: 3.0.4 - resolution: "@smithy/util-retry@npm:3.0.4" +"@smithy/util-retry@npm:^4.2.12, @smithy/util-retry@npm:^4.2.13": + version: 4.2.13 + resolution: "@smithy/util-retry@npm:4.2.13" dependencies: - "@smithy/service-error-classification": "npm:^3.0.4" - "@smithy/types": "npm:^3.4.0" + "@smithy/service-error-classification": "npm:^4.2.12" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/9d8e4ee8a3c71e3f7cd799a03fc8d2cb69e294d475f798d3fd9db85f64135ff545d333d9ca89fa7b621dfa17fa6f91f7c2d90a889089e0417484d821a627e225 + checksum: 10/8b3a467c77b09e662321dab459a9173a88969b90f8de60378bb6b20993767ff8c7069da9696eca16714a2a1a61639834c5968ff1992771c75dbcf5bdffa2c6de languageName: node linkType: hard -"@smithy/util-retry@npm:^4.2.2, @smithy/util-retry@npm:^4.2.3": - version: 4.2.3 - resolution: "@smithy/util-retry@npm:4.2.3" +"@smithy/util-stream@npm:^4.5.21": + version: 4.5.21 + resolution: "@smithy/util-stream@npm:4.5.21" dependencies: - "@smithy/service-error-classification": "npm:^4.2.3" - "@smithy/types": "npm:^4.8.0" + "@smithy/fetch-http-handler": "npm:^5.3.15" + "@smithy/node-http-handler": "npm:^4.5.1" + "@smithy/types": "npm:^4.13.1" + "@smithy/util-base64": "npm:^4.3.2" + "@smithy/util-buffer-from": "npm:^4.2.2" + "@smithy/util-hex-encoding": "npm:^4.2.2" + "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/7907a7ef3f766984c85438240b8a46776f5f0b385d1660b7c261e2ba50947dfbe70ba4924c47e11c2937add998c99cf97639c899b0bae476dfd18fe32125afe8 - languageName: node - linkType: hard - -"@smithy/util-stream@npm:^3.1.4": - version: 3.1.4 - resolution: "@smithy/util-stream@npm:3.1.4" - dependencies: - "@smithy/fetch-http-handler": "npm:^3.2.5" - "@smithy/node-http-handler": "npm:^3.2.0" - "@smithy/types": "npm:^3.4.0" - "@smithy/util-base64": "npm:^3.0.0" - "@smithy/util-buffer-from": "npm:^3.0.0" - "@smithy/util-hex-encoding": "npm:^3.0.0" - "@smithy/util-utf8": "npm:^3.0.0" - tslib: "npm:^2.6.2" - checksum: 10/37970467674b504aef7cd1a241bbe4227c27449647567d8f4b465cf2868b4d4c0b63fe29826f9b8f17a9440572f53cc6a1993e5c8872f5409115b073f4b3cd8f - languageName: node - linkType: hard - -"@smithy/util-stream@npm:^4.5.2, @smithy/util-stream@npm:^4.5.3": - version: 4.5.3 - resolution: "@smithy/util-stream@npm:4.5.3" - dependencies: - "@smithy/fetch-http-handler": "npm:^5.3.4" - "@smithy/node-http-handler": "npm:^4.4.2" - "@smithy/types": "npm:^4.8.0" - "@smithy/util-base64": "npm:^4.3.0" - "@smithy/util-buffer-from": "npm:^4.2.0" - "@smithy/util-hex-encoding": "npm:^4.2.0" - "@smithy/util-utf8": "npm:^4.2.0" - tslib: "npm:^2.6.2" - checksum: 10/d898b1c45f635fe606f53718f2e1c812a6c74f5e0e2524ec494816d4dee7635d1c59436a90b679f46419079e5053d311a84f1d707354a08d11c4708ba502c806 + checksum: 10/f1d188450923c75153737393cecbc6885fdc3ed992cdbc4912ee9c902df2519007de1f65231e3e00bfcbbae862d450db5c85b4ea532025b8125725f9b1e4cd00 languageName: node linkType: hard @@ -18620,12 +17187,12 @@ __metadata: languageName: node linkType: hard -"@smithy/util-uri-escape@npm:^4.2.0": - version: 4.2.0 - resolution: "@smithy/util-uri-escape@npm:4.2.0" +"@smithy/util-uri-escape@npm:^4.2.2": + version: 4.2.2 + resolution: "@smithy/util-uri-escape@npm:4.2.2" dependencies: tslib: "npm:^2.6.2" - checksum: 10/a838a3afe557d7087d4500735c79d5da72e0cd5a08f95d1a1c450ba29d9cd85c950228eedbd9b2494156f4eb8658afb0a9a5bd2df3fc4f297faed886c396242b + checksum: 10/2b649e431a92c89e4fb7cc817e7bfcbe547d07f725c401445ea81aaea37e4ede383e1e2b9c3f0dfb228dee597166e95805a2f3e57fa6ae1b5341abc48a397935 languageName: node linkType: hard @@ -18649,39 +17216,38 @@ __metadata: languageName: node linkType: hard -"@smithy/util-utf8@npm:^4.2.0": - version: 4.2.0 - resolution: "@smithy/util-utf8@npm:4.2.0" +"@smithy/util-utf8@npm:^4.2.2": + version: 4.2.2 + resolution: "@smithy/util-utf8@npm:4.2.2" dependencies: - "@smithy/util-buffer-from": "npm:^4.2.0" + "@smithy/util-buffer-from": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/d49f58fc6681255eecc3dee39c657b80ef8a4c5617e361bdaf6aaa22f02e378622376153cafc9f0655fb80162e88fc98bbf459f8dd5ba6d7c4b9a59e6eaa05f8 + checksum: 10/4dd23ac07cab78279a9f4f250b43df69d3303458b741e38c447ba7e92f7c6b1651076479023687b9eb3996aa3269ee1a0d363a1c320d15e78247ca9ea74aca58 languageName: node linkType: hard -"@smithy/util-waiter@npm:^3.1.3": - version: 3.1.3 - resolution: "@smithy/util-waiter@npm:3.1.3" +"@smithy/util-waiter@npm:^4.2.14": + version: 4.2.14 + resolution: "@smithy/util-waiter@npm:4.2.14" dependencies: - "@smithy/abort-controller": "npm:^3.1.2" - "@smithy/types": "npm:^3.4.0" + "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/6e0ae10a7c7d8284d5e91eb362f726fd0406c9ddae655d2c9a40f429ef7805f21b9688db9c249975be50d1ecd6311b7513c387af86e48a2c421fa258f27d7be7 + checksum: 10/b2ce66060db441105ecd5c4bf9a80752aa41e9d2b1c673b8c32e0c55963a0301d21e178caeb0ee6cd24ba69629b87bbbf36a6b8d036864d365c659ccfe0cacad languageName: node linkType: hard -"@smithy/uuid@npm:^1.1.0": - version: 1.1.0 - resolution: "@smithy/uuid@npm:1.1.0" +"@smithy/uuid@npm:^1.1.2": + version: 1.1.2 + resolution: "@smithy/uuid@npm:1.1.2" dependencies: tslib: "npm:^2.6.2" - checksum: 10/fe77b1cebbbf2d541ee2f07eec6d4573af16e08dd3228758f59dcbe85a504112cefe81b971818cf39e2e3fa0ed1fcc61d392cddc50fca13d9dc9bd835e366db0 + checksum: 10/35b77a2483a37755c2be1faf66036f5e0b7939a7c608b93982fce9d4f137f1778784f101a2874a6756d9fd25092c6a95dd07314df12dcb9a0a03244b4cc4d8c4 languageName: node linkType: hard -"@snyk/dep-graph@npm:^2.3.0": - version: 2.9.0 - resolution: "@snyk/dep-graph@npm:2.9.0" +"@snyk/dep-graph@npm:^2.12.0": + version: 2.14.0 + resolution: "@snyk/dep-graph@npm:2.14.0" dependencies: event-loop-spinner: "npm:^2.1.0" lodash.clone: "npm:^4.5.0" @@ -18699,10 +17265,10 @@ __metadata: lodash.union: "npm:^4.6.0" lodash.values: "npm:^4.3.0" object-hash: "npm:^3.0.0" - packageurl-js: "npm:1.2.0" + packageurl-js: "npm:2.0.1" semver: "npm:^7.0.0" tslib: "npm:^2" - checksum: 10/60228f3b0999b42139907f7be67aa6769a4885ca6a291ebaa4d4f296e653f60e0e291f7dc2696c658fea76f086583cc61d67f34fc1d1e5dacf00f6ca46362d21 + checksum: 10/e51882a2e566030a135d2fff477afbc7e4f8a14946089598fe56dd1e9b1b65fffe1ef8b3c8e2ff42629fc01d79b6d5dfeca1c505413731866c5b730728aee0a4 languageName: node linkType: hard @@ -18716,19 +17282,6 @@ __metadata: languageName: node linkType: hard -"@snyk/github-codeowners@npm:1.1.0": - version: 1.1.0 - resolution: "@snyk/github-codeowners@npm:1.1.0" - dependencies: - commander: "npm:^4.1.1" - ignore: "npm:^5.1.8" - p-map: "npm:^4.0.0" - bin: - github-codeowners: dist/cli.js - checksum: 10/34120ef622616fef1ed8af12869d8c1803842aafa3fbacca263805ee7c85f58d11bdc301ef698c9b41268b275b9fd090f5d9f6d89c556abe9d52196e72d1c510 - languageName: node - linkType: hard - "@snyk/graphlib@npm:2.1.9-patch.3": version: 2.1.9-patch.3 resolution: "@snyk/graphlib@npm:2.1.9-patch.3" @@ -18752,6 +17305,16 @@ __metadata: languageName: node linkType: hard +"@so-ric/colorspace@npm:^1.1.6": + version: 1.1.6 + resolution: "@so-ric/colorspace@npm:1.1.6" + dependencies: + color: "npm:^5.0.2" + text-hex: "npm:1.0.x" + checksum: 10/fc3285e5cb9a458d255aa678d9453174ca40689a4c692f1617907996ab8eb78839542439604ced484c4f674a5297f7ba8b0e63fcfe901174f43c3d9c3c881b52 + languageName: node + linkType: hard + "@spotify/eslint-config-base@npm:^15.0.0": version: 15.0.0 resolution: "@spotify/eslint-config-base@npm:15.0.0" @@ -18794,6 +17357,13 @@ __metadata: languageName: node linkType: hard +"@standard-schema/spec@npm:^1.0.0, @standard-schema/spec@npm:^1.1.0": + version: 1.1.0 + resolution: "@standard-schema/spec@npm:1.1.0" + checksum: 10/a209615c9e8b2ea535d7db0a5f6aa0f962fd4ab73ee86a46c100fb78116964af1f55a27c1794d4801e534a196794223daa25ff5135021e03c7828aa3d95e1763 + languageName: node + linkType: hard + "@stoplight/better-ajv-errors@npm:1.0.3": version: 1.0.3 resolution: "@stoplight/better-ajv-errors@npm:1.0.3" @@ -18877,8 +17447,8 @@ __metadata: linkType: hard "@stoplight/spectral-core@npm:^1.18.0, @stoplight/spectral-core@npm:^1.18.3, @stoplight/spectral-core@npm:^1.19.2, @stoplight/spectral-core@npm:^1.19.4, @stoplight/spectral-core@npm:^1.8.1": - version: 1.20.0 - resolution: "@stoplight/spectral-core@npm:1.20.0" + version: 1.21.0 + resolution: "@stoplight/spectral-core@npm:1.21.0" dependencies: "@stoplight/better-ajv-errors": "npm:1.0.3" "@stoplight/json": "npm:~3.21.0" @@ -18894,14 +17464,14 @@ __metadata: ajv-formats: "npm:~2.1.1" es-aggregate-error: "npm:^1.0.7" jsonpath-plus: "npm:^10.3.0" - lodash: "npm:~4.17.21" + lodash: "npm:~4.17.23" lodash.topath: "npm:^4.5.2" minimatch: "npm:3.1.2" nimma: "npm:0.2.3" pony-cause: "npm:^1.1.1" simple-eval: "npm:1.0.1" tslib: "npm:^2.8.1" - checksum: 10/63628dcf4b1556db166a020dd78d17033908fa7d1c67c62739e35848e5d0e095714524b4c142aa7d6524fe556152f4dbb8cb388b36bc6e37026bf07c31a0e69b + checksum: 10/1ee71d19c810e716ada542c4e838bbc8b20e7bdc46518739c16c2edbc446a34fcf4e977d79b89d68d1a41e295ef9abee6449e2260cbf5ed1c2ae486229e3bdfb languageName: node linkType: hard @@ -19069,82 +17639,140 @@ __metadata: languageName: node linkType: hard -"@storybook/addon-a11y@npm:^9.1.7": - version: 9.1.16 - resolution: "@storybook/addon-a11y@npm:9.1.16" +"@storybook/addon-a11y@npm:^10.3.3": + version: 10.3.3 + resolution: "@storybook/addon-a11y@npm:10.3.3" dependencies: "@storybook/global": "npm:^5.0.0" axe-core: "npm:^4.2.0" peerDependencies: - storybook: ^9.1.16 - checksum: 10/24645a9df98aa52f7e52ac0ba5a87eb5b3ac533130b490628925a657ddbaab9ac370930594f5a879b9c30a36f0d2f9b752e9643a57ac8f2f835379143afb2f06 + storybook: ^10.3.3 + checksum: 10/a012edb67007533c458ea91a57777749f46edc1e946ceff62080d6bd5c94cee18f9b7ff57ae421692400f772b65d36992c03071dadca6fbc263654b59c9a00a0 languageName: node linkType: hard -"@storybook/addon-docs@npm:^9.1.7": - version: 9.1.16 - resolution: "@storybook/addon-docs@npm:9.1.16" +"@storybook/addon-docs@npm:^10.3.3": + version: 10.3.3 + resolution: "@storybook/addon-docs@npm:10.3.3" dependencies: "@mdx-js/react": "npm:^3.0.0" - "@storybook/csf-plugin": "npm:9.1.16" - "@storybook/icons": "npm:^1.4.0" - "@storybook/react-dom-shim": "npm:9.1.16" + "@storybook/csf-plugin": "npm:10.3.3" + "@storybook/icons": "npm:^2.0.1" + "@storybook/react-dom-shim": "npm:10.3.3" react: "npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" react-dom: "npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^9.1.16 - checksum: 10/02081dd9c9e6273e8030b7ab24470d1d29513870245aaf2741cd97eea1350918db354fb78484f192c191942d75f834e81176717e59aefc28572c88909979e8a9 + storybook: ^10.3.3 + checksum: 10/634602f0dc3c57ed21f03b7dbae34d927bf63c7e3f2db3af6f7167390f1517fb66a258fd0bed2ad0b03927af2e0748c133b78362b0ef50c11ba79639e18da3ba languageName: node linkType: hard -"@storybook/addon-links@npm:^9.1.7": - version: 9.1.16 - resolution: "@storybook/addon-links@npm:9.1.16" +"@storybook/addon-links@npm:^10.3.3": + version: 10.3.3 + resolution: "@storybook/addon-links@npm:10.3.3" dependencies: "@storybook/global": "npm:^5.0.0" peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^9.1.16 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.3.3 peerDependenciesMeta: react: optional: true - checksum: 10/04eca604043d55c391512bf5e63cb742c84759eedb535f3deb8b1e9fcc68fb0cf8222df4990cefba34cae8321a6ecd380be34111ebc6b6a109dd5e910fe0a5a7 + checksum: 10/9b9d8141c37ec35855ff1514cedd8c31864e2c1589cd2a99e66394e7fad608d5bb761ccf1b87ce284e3b7cb68773f777d396f16bbb236d73c5f8d119e08f5afc languageName: node linkType: hard -"@storybook/addon-themes@npm:^9.1.7": - version: 9.1.16 - resolution: "@storybook/addon-themes@npm:9.1.16" +"@storybook/addon-mcp@npm:^0.4.2": + version: 0.4.2 + resolution: "@storybook/addon-mcp@npm:0.4.2" + dependencies: + "@storybook/mcp": "npm:0.6.1" + "@tmcp/adapter-valibot": "npm:^0.1.4" + "@tmcp/transport-http": "npm:^0.8.0" + picoquery: "npm:^2.5.0" + tmcp: "npm:^1.16.0" + valibot: "npm:1.2.0" + peerDependencies: + "@storybook/addon-vitest": ^9.1.16 || ^10.0.0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 || ^10.4.0-0 + storybook: ^9.1.16 || ^10.0.0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 || ^10.4.0-0 + peerDependenciesMeta: + "@storybook/addon-vitest": + optional: true + checksum: 10/42408a05ec7e9a7bfd5f168e7047b32a67d3a8cfcfaf68e74ddd820c9e188af006883e69173c4514f7845e29ebc58f6412be3c0ccab396590b3da4d1f3866bc6 + languageName: node + linkType: hard + +"@storybook/addon-themes@npm:^10.3.3": + version: 10.3.3 + resolution: "@storybook/addon-themes@npm:10.3.3" dependencies: ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^9.1.16 - checksum: 10/c596f386a6aad3638febcc2df4d53e5e185c8c7b7e1bef17dd12b83a9ad6102769b2244869065912fd5c563915baf52b631a872fff239c80539ac49e5daad85e + storybook: ^10.3.3 + checksum: 10/1a54cbb4bb38687fe62ac2f224faeb409a6487b56132e656068c8f57f3754f85703e54d7251917d8bcc5a377014c45dbb1940a345de86ec975fd222c3c6804f7 languageName: node linkType: hard -"@storybook/builder-vite@npm:9.1.16": - version: 9.1.16 - resolution: "@storybook/builder-vite@npm:9.1.16" +"@storybook/addon-vitest@npm:^10.3.3": + version: 10.3.3 + resolution: "@storybook/addon-vitest@npm:10.3.3" dependencies: - "@storybook/csf-plugin": "npm:9.1.16" + "@storybook/global": "npm:^5.0.0" + "@storybook/icons": "npm:^2.0.1" + peerDependencies: + "@vitest/browser": ^3.0.0 || ^4.0.0 + "@vitest/browser-playwright": ^4.0.0 + "@vitest/runner": ^3.0.0 || ^4.0.0 + storybook: ^10.3.3 + vitest: ^3.0.0 || ^4.0.0 + peerDependenciesMeta: + "@vitest/browser": + optional: true + "@vitest/browser-playwright": + optional: true + "@vitest/runner": + optional: true + vitest: + optional: true + checksum: 10/d33ffbe967ffea1c8b6f2f5e050083939156678946beb37dda5347ebc82d69210ced97ec31445815ceeaf254b8eada71732f93e3c4e54b1f130bbd9ac141b57a + languageName: node + linkType: hard + +"@storybook/builder-vite@npm:10.3.3": + version: 10.3.3 + resolution: "@storybook/builder-vite@npm:10.3.3" + dependencies: + "@storybook/csf-plugin": "npm:10.3.3" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^9.1.16 - vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10/bd30ad08a0222aa0d44a4127e959402058c7aedf7dddf3114a4ad54cd49384c24f738fe61e3e9be7be17943557e9ccbdd7311101f69af98d83c6eca5e433c855 + storybook: ^10.3.3 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 10/f266e14aff27633aa03089a34e4c5a67cf8ef0763817549abd62cd185c716ca1b62c3a92dd7b609231b27849858d56b75ec87bc708e6d148258a8593aa886c3b languageName: node linkType: hard -"@storybook/csf-plugin@npm:9.1.16": - version: 9.1.16 - resolution: "@storybook/csf-plugin@npm:9.1.16" +"@storybook/csf-plugin@npm:10.3.3": + version: 10.3.3 + resolution: "@storybook/csf-plugin@npm:10.3.3" dependencies: - unplugin: "npm:^1.3.1" + unplugin: "npm:^2.3.5" peerDependencies: - storybook: ^9.1.16 - checksum: 10/81612bfa904673d5a28094dce40cc0890091f6e3a82bdcedc34dd43c103d7f20d838c81744f87e65a9efb7b98779b4d89ecc95a14a5b550efe2aeff53de0c6ea + esbuild: "*" + rollup: "*" + storybook: ^10.3.3 + vite: "*" + webpack: "*" + peerDependenciesMeta: + esbuild: + optional: true + rollup: + optional: true + vite: + optional: true + webpack: + optional: true + checksum: 10/404c0f8c672c7f4f16eb3f1731fead8ba99c0232a0764aab88484fafbd22402c027af5364b403f491f1824206c927aca9b01121a07a749518af52e413d12fecc languageName: node linkType: hard @@ -19155,392 +17783,470 @@ __metadata: languageName: node linkType: hard -"@storybook/icons@npm:^1.4.0": - version: 1.4.0 - resolution: "@storybook/icons@npm:1.4.0" +"@storybook/icons@npm:^2.0.1": + version: 2.0.1 + resolution: "@storybook/icons@npm:2.0.1" 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 - checksum: 10/513d36188bb84f1f64dd982b6bd577de4c3d7bc2aa9d318a2acf8c654d16994bac0b669787a4ced6720e471251bcb0ec68b8d8dc2cc7245d11102bd617243700 + 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 + checksum: 10/04ffa285f4defc611def51f82492688bc49f6f4e8ce4e7ba5c99a1c1410b7e8820b5da65c33610a497df2409de7b48fae399052c5cacab6a4a4a9b48a36ebfd5 languageName: node linkType: hard -"@storybook/react-dom-shim@npm:9.1.16": - version: 9.1.16 - resolution: "@storybook/react-dom-shim@npm:9.1.16" - 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: ^9.1.16 - checksum: 10/05c1426a02416b353e61f267d901ce938919a2cc1e94eebe11938c15466e1d584034421798cc228a00cfafeb3aa2bf28cf2814bdf328896a117734c4c8a6890e - languageName: node - linkType: hard - -"@storybook/react-vite@npm:^9.1.7": - version: 9.1.16 - resolution: "@storybook/react-vite@npm:9.1.16" +"@storybook/mcp@npm:0.6.1": + version: 0.6.1 + resolution: "@storybook/mcp@npm:0.6.1" dependencies: - "@joshwooding/vite-plugin-react-docgen-typescript": "npm:0.6.1" + "@tmcp/adapter-valibot": "npm:^0.1.4" + "@tmcp/transport-http": "npm:^0.8.0" + tmcp: "npm:^1.16.0" + valibot: "npm:1.2.0" + checksum: 10/0f61c6a3866197ee894eda99642896d83dbbcc946dce089b8b5f099b6de3ec5ec4ec0fc40e0a36fd9e5ef9ff0d3241a49e371aaf5d9ef75ef4f50fd46b5787f8 + languageName: node + linkType: hard + +"@storybook/react-dom-shim@npm:10.3.3": + version: 10.3.3 + resolution: "@storybook/react-dom-shim@npm:10.3.3" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.3.3 + checksum: 10/bdf49c2b84f05cde9d74cdd3533472f6e23e5d133cc3484fa5b4775de48788f3122ce471c577d6854d1751e05d5211014d61cb562f7cf3a9104299226955f9ff + languageName: node + linkType: hard + +"@storybook/react-vite@npm:^10.3.3": + version: 10.3.3 + resolution: "@storybook/react-vite@npm:10.3.3" + dependencies: + "@joshwooding/vite-plugin-react-docgen-typescript": "npm:^0.6.4" "@rollup/pluginutils": "npm:^5.0.2" - "@storybook/builder-vite": "npm:9.1.16" - "@storybook/react": "npm:9.1.16" - find-up: "npm:^7.0.0" + "@storybook/builder-vite": "npm:10.3.3" + "@storybook/react": "npm:10.3.3" + empathic: "npm:^2.0.0" magic-string: "npm:^0.30.0" react-docgen: "npm:^8.0.0" resolve: "npm:^1.22.8" tsconfig-paths: "npm:^4.2.0" 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: ^9.1.16 - vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10/5d5e1ab752c2212d5c00d0150f46b1a9ef3d86ac3ee8ec359de301ba7801b0272eadd52a3e647b2d40db72ca1d321c981b8e392ec060d5e1ec39c4a3227c3aa5 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.3.3 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 10/6e1241d44059ef3ea7eacb037cad29e3934cf960365126c0a38a79f9212c1592016143406bced2ac85e68b39379760652628a9416db7f2d828e4f2f9076ebfe9 languageName: node linkType: hard -"@storybook/react@npm:9.1.16": - version: 9.1.16 - resolution: "@storybook/react@npm:9.1.16" +"@storybook/react@npm:10.3.3": + version: 10.3.3 + resolution: "@storybook/react@npm:10.3.3" dependencies: "@storybook/global": "npm:^5.0.0" - "@storybook/react-dom-shim": "npm:9.1.16" + "@storybook/react-dom-shim": "npm:10.3.3" + react-docgen: "npm:^8.0.2" + react-docgen-typescript: "npm:^2.2.2" peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^9.1.16 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.3.3 typescript: ">= 4.9.x" peerDependenciesMeta: typescript: optional: true - checksum: 10/ead1396a6fdd3cbb4170fbf1f390073d64a07fb4114243a5a6d7c975d8f35aae0220c0c731c62f5f6be2f6a6a92b2b8b150066da7b463ae848cc43dd4b8670f1 + checksum: 10/2c6a345e289727a79d30082841e2088987669803430670b5e72a621d4794a688d315ee4377e01ea87f89ac6f4a2a381419f9e91a4b8508a444ac18076a124b16 languageName: node linkType: hard -"@swagger-api/apidom-ast@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-ast@npm:1.0.0-beta.48" +"@swagger-api/apidom-ast@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-ast@npm:1.8.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-error": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-error": "npm:^1.8.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" unraw: "npm:^3.0.0" - checksum: 10/b3f2b91df2b9116db318ce44551d3846453b7d9f17eb809c19890ecbbc17166ad6a1fa54698b79e85600960e2f1dd2ea56487d9a50537ee362a1a9dd63f91840 + checksum: 10/9bcabab6a90cea9ae6f992f053c4fb3a9539a7b839a78589779bb4984bd2dd40925a202a3a04a47c4cccaed887409446d6638af3df5fc4d3114d71098633478c languageName: node linkType: hard -"@swagger-api/apidom-core@npm:>=1.0.0-beta.41 <1.0.0-rc.0, @swagger-api/apidom-core@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-core@npm:1.0.0-beta.48" +"@swagger-api/apidom-core@npm:^1.7.0, @swagger-api/apidom-core@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-core@npm:1.8.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-ast": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-error": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-ast": "npm:^1.8.0" + "@swagger-api/apidom-error": "npm:^1.8.0" "@types/ramda": "npm:~0.30.0" minim: "npm:~0.23.8" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" short-unique-id: "npm:^5.3.2" ts-mixer: "npm:^6.0.3" - checksum: 10/abf7328a4d821f6083daa80fe1e719511825ff855dc4be59a4147a100c1ae34254cba4050ecc29e5147877f7e9d797681e96931da0ce432c310aaae82cd5a371 + checksum: 10/a88df1c63af2665244a77d0982b0cc184ed73c522dc212f6cd7e4b37a568fc9b080f7f18bb42408e922252f75caaa264a91ecc1726300587c4a63bd87b7de850 languageName: node linkType: hard -"@swagger-api/apidom-error@npm:>=1.0.0-beta.41 <1.0.0-rc.0, @swagger-api/apidom-error@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-error@npm:1.0.0-beta.48" +"@swagger-api/apidom-error@npm:^1.7.0, @swagger-api/apidom-error@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-error@npm:1.8.0" dependencies: "@babel/runtime-corejs3": "npm:^7.20.7" - checksum: 10/ce6461f5b06a6297074949d1aabd605c272c3061241e4d47f3720707ac8bc41c272deed033ae131fb5620bd6d709e0744275353ed490f7eca81f75db94abfc7e + checksum: 10/72018a48454c5cd97edfd833726b1f7e4500ecff7497a27d412c6fe77435f849400f9e5a0f5825fa14d366fa05a136d7517e6127023be75ee4623a4bf14a863c languageName: node linkType: hard -"@swagger-api/apidom-json-pointer@npm:>=1.0.0-beta.41 <1.0.0-rc.0, @swagger-api/apidom-json-pointer@npm:^1.0.0-beta.40 <1.0.0-rc.0, @swagger-api/apidom-json-pointer@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-json-pointer@npm:1.0.0-beta.48" +"@swagger-api/apidom-json-pointer@npm:^1.7.0, @swagger-api/apidom-json-pointer@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-json-pointer@npm:1.8.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-error": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-error": "npm:^1.8.0" "@swaggerexpert/json-pointer": "npm:^2.10.1" - checksum: 10/2075f4d64813128ee80da21f16fd96bc51cf232505d920fc77af86acd2ea1db342936753b824153b232e9522d4a7f389fd870afdf3518b0af91f497119b6e1c5 + checksum: 10/3ccb19cba76133430dc0cf75a58c6055c3e0a9179feff7687387a12ff6d3926adce1e648c18f0100f8ff627179350c9c5041058db1754dba15969d2d9a536502 languageName: node linkType: hard -"@swagger-api/apidom-ns-api-design-systems@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-ns-api-design-systems@npm:1.0.0-beta.48" +"@swagger-api/apidom-ns-api-design-systems@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-ns-api-design-systems@npm:1.8.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-error": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-openapi-3-1": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-error": "npm:^1.8.0" + "@swagger-api/apidom-ns-openapi-3-1": "npm:^1.8.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.3" - checksum: 10/48e6a4f323572715f8990f83f6a1617af9d7ca0c6cd0e08f9b05c6f178664da233498c7c2461f636a97a525aaa60748c1da2a8d949ed43a7a14c1639d382f737 + checksum: 10/fd9b7584b9d02e50865927c8cbe1f3062e290f640c5cea4c2dedecf399c7346bb85b2c1995cf1cbe9b5b4a46c8bba8accb214b5efd4c416a680b5094fcf24de7 languageName: node linkType: hard -"@swagger-api/apidom-ns-arazzo-1@npm:^1.0.0-beta.40 <1.0.0-rc.0, @swagger-api/apidom-ns-arazzo-1@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-ns-arazzo-1@npm:1.0.0-beta.48" +"@swagger-api/apidom-ns-arazzo-1@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-ns-arazzo-1@npm:1.8.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-json-schema-2020-12": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-ns-json-schema-2020-12": "npm:^1.8.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.3" - checksum: 10/3b9f60ec9ca9dccc5e3d46a48a6d7b30915294bdb7dc6ee481aae57de707485885f887ad9b9daec469712c087e6d74ed748ac8d748ed6d19cb1fb6219141bb12 + checksum: 10/2caf7a8804096661a31268dbcd57b69a4e74818e35859ceb45a00169177e90ca56fcaa2ce6ba58421fd76d284e09b57be60bf48d51c3f51bdfc0eda68eadb917 languageName: node linkType: hard -"@swagger-api/apidom-ns-asyncapi-2@npm:^1.0.0-beta.40 <1.0.0-rc.0, @swagger-api/apidom-ns-asyncapi-2@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-ns-asyncapi-2@npm:1.0.0-beta.48" +"@swagger-api/apidom-ns-asyncapi-2@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-ns-asyncapi-2@npm:1.8.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-json-schema-draft-7": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-ns-json-schema-draft-7": "npm:^1.8.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.3" - checksum: 10/99bcd30eb7c14334b2ebe3adfdb7ad8cf93d9565fc72217863a82b1ba48d4b5839b639964cf830bf9ba64c41c7a7d7eedeb53d8d28e5379bde1fafa8b424ab02 + checksum: 10/7a1f3b1ff5e39f770e1d885e2eec30e06397a349465f8502a8ff37446fdf6e271d291b379195f55b7d303cf500ad909d54a912d8432bf2e439ea52b24d868c2c languageName: node linkType: hard -"@swagger-api/apidom-ns-json-schema-2019-09@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-ns-json-schema-2019-09@npm:1.0.0-beta.48" +"@swagger-api/apidom-ns-asyncapi-3@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-ns-asyncapi-3@npm:1.8.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-error": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-json-schema-draft-7": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-ns-asyncapi-2": "npm:^1.8.0" + "@types/ramda": "npm:~0.30.0" + ramda: "npm:~0.30.0" + ramda-adjunct: "npm:^5.0.0" + ts-mixer: "npm:^6.0.3" + checksum: 10/04d810a8146b9c3bfee7ed388810025b90d1edfce6ac100810fee687d8c167e28da76fcccd044c08d348331f5d8c836adf50abe8a333311ad22d5031e943c663 + languageName: node + linkType: hard + +"@swagger-api/apidom-ns-json-schema-2019-09@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-ns-json-schema-2019-09@npm:1.8.0" + dependencies: + "@babel/runtime-corejs3": "npm:^7.26.10" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-error": "npm:^1.8.0" + "@swagger-api/apidom-ns-json-schema-draft-7": "npm:^1.8.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.4" - checksum: 10/bed0ed8b2ef35def9da2b8cebb31dba0d83fab88a38ab1a1af4760d370f5b3d0a1dfda7f1f1991204eab97531ddc90c70c153cb3ba9a7a5b93be93f02d873097 + checksum: 10/5554cc9120a124336efc19672b8b9c7867da66a8c2c635d08d2ef746f48eb424a9e44717814637dd30c700c188718506c649dbcae8396926e614156a26d78cfd languageName: node linkType: hard -"@swagger-api/apidom-ns-json-schema-2020-12@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-ns-json-schema-2020-12@npm:1.0.0-beta.48" +"@swagger-api/apidom-ns-json-schema-2020-12@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-ns-json-schema-2020-12@npm:1.8.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-error": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-json-schema-2019-09": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-error": "npm:^1.8.0" + "@swagger-api/apidom-ns-json-schema-2019-09": "npm:^1.8.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.4" - checksum: 10/6bfad975a9db33224bc980edafab8e39218d6eb0acc384881ba14b35061d2c51b45f3e778529f5cf5875330501c892f433319dc4613a2d08ae22cf33f832d91e + checksum: 10/4d0d89a8a42f0b8ab6b03f54c142cff1ba4704fa84f51f407c845187449a349335774d852e68337a9045cf2a61523b2d2fd465b01eca50062563cf338932528a languageName: node linkType: hard -"@swagger-api/apidom-ns-json-schema-draft-4@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-ns-json-schema-draft-4@npm:1.0.0-beta.48" +"@swagger-api/apidom-ns-json-schema-draft-4@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-ns-json-schema-draft-4@npm:1.8.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-ast": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-ast": "npm:^1.8.0" + "@swagger-api/apidom-core": "npm:^1.8.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.4" - checksum: 10/860c09c46b6ef274afeaf4fb5aa0445dd9d394fb81f3d251d34c484ce31ca568e8d8e63acdfd207180dc28ac1a6fe082fe7139dbe1aadac2e5b81f42f80aefcd + checksum: 10/1618fbb5b70ae5d04da186f05b4e07e9a5ad3594005db76c1d9f072ae321ba948b71be3c57b4ff753cec3be39de36c296b3411c49d887b247b33f3356bd499d2 languageName: node linkType: hard -"@swagger-api/apidom-ns-json-schema-draft-6@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-ns-json-schema-draft-6@npm:1.0.0-beta.48" +"@swagger-api/apidom-ns-json-schema-draft-6@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-ns-json-schema-draft-6@npm:1.8.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-error": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-json-schema-draft-4": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-error": "npm:^1.8.0" + "@swagger-api/apidom-ns-json-schema-draft-4": "npm:^1.8.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.4" - checksum: 10/418d8c1cf20cc12841ee1d7e17bc57cbfa296d7833d4f656c650fcb5ade4bd646088177c9cc1c682b2ebd80fb3713de02b8dc562d155e3874c482e9d0d4edcc2 + checksum: 10/1edb081358576145d26cd82740d9c0c200903eb5f6306744c9a8f6020a43ddb7336fdc092c99564203f0fdd0eba8cade9c635c502af24b78eaeee7357307b2e4 languageName: node linkType: hard -"@swagger-api/apidom-ns-json-schema-draft-7@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-ns-json-schema-draft-7@npm:1.0.0-beta.48" +"@swagger-api/apidom-ns-json-schema-draft-7@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-ns-json-schema-draft-7@npm:1.8.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-error": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-json-schema-draft-6": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-error": "npm:^1.8.0" + "@swagger-api/apidom-ns-json-schema-draft-6": "npm:^1.8.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.4" - checksum: 10/6e683458465f221c490440f94a1d1328a39db87a27c257a5d700b2effc937b8568f98f5042d74f56fdf6692da4b4027074c33c59348199fc821c45de9598e473 + checksum: 10/f534d7d5e0653def665bb1e05373d66188ec794705152a334367262cf4b4835cd7c82dcbcba56ba57ea28bc763a59dd4ccf7875f59e9ae0194d71705e7940d20 languageName: node linkType: hard -"@swagger-api/apidom-ns-openapi-2@npm:^1.0.0-beta.40 <1.0.0-rc.0, @swagger-api/apidom-ns-openapi-2@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-ns-openapi-2@npm:1.0.0-beta.48" +"@swagger-api/apidom-ns-openapi-2@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-ns-openapi-2@npm:1.8.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-error": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-json-schema-draft-4": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-error": "npm:^1.8.0" + "@swagger-api/apidom-ns-json-schema-draft-4": "npm:^1.8.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.3" - checksum: 10/768fb7ec2c8506857837975529f311fe28359dd56a836c3b3cb1685bed623c489b5327e5fe58a41e70235ba525ba6401cc32fc9bd50bc5ec1cc0baf6fe144900 + checksum: 10/e905bf8bf0e5dbe41108f255f6fd2f735618a00bfb1c1de28dbb9acf05aef2f84dd61635cda0c71e8226ecb2496951afff06837c79c5a9cec7b547b67b88bc72 languageName: node linkType: hard -"@swagger-api/apidom-ns-openapi-3-0@npm:^1.0.0-beta.40 <1.0.0-rc.0, @swagger-api/apidom-ns-openapi-3-0@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-ns-openapi-3-0@npm:1.0.0-beta.48" +"@swagger-api/apidom-ns-openapi-3-0@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-ns-openapi-3-0@npm:1.8.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-error": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-json-schema-draft-4": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-error": "npm:^1.8.0" + "@swagger-api/apidom-ns-json-schema-draft-4": "npm:^1.8.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.3" - checksum: 10/a005d660c2e476a3d1bb74da9a2efa7fb0a68b1f52ddbde01514f4db9ef14051833f55b001c812da44fce3445c145ce5fd62a137477a71a6bcad220115c1e440 + checksum: 10/ebf37461a6cf23d4bb09d581ae6bfe8a6a557c29c83ef69de223528f3aabcbb064ef2624bd53b4d228f3d0aff1c01c6ec4c0c111f6a43db83a30e14cf024aecc languageName: node linkType: hard -"@swagger-api/apidom-ns-openapi-3-1@npm:>=1.0.0-beta.41 <1.0.0-rc.0, @swagger-api/apidom-ns-openapi-3-1@npm:^1.0.0-beta.40 <1.0.0-rc.0, @swagger-api/apidom-ns-openapi-3-1@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-ns-openapi-3-1@npm:1.0.0-beta.48" +"@swagger-api/apidom-ns-openapi-3-1@npm:^1.7.0, @swagger-api/apidom-ns-openapi-3-1@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-ns-openapi-3-1@npm:1.8.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-ast": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-json-pointer": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-json-schema-2020-12": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-openapi-3-0": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-ast": "npm:^1.8.0" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-json-pointer": "npm:^1.8.0" + "@swagger-api/apidom-ns-json-schema-2020-12": "npm:^1.8.0" + "@swagger-api/apidom-ns-openapi-3-0": "npm:^1.8.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.3" - checksum: 10/3423b3d642487d562376a572dac31fec760d3a64fe8bb4065ee0634a340c418656b2ea9cfc484059db5105e7389c74ac32620413243990141a7bbdc2e7cb1062 + checksum: 10/1ebac7aec16be5a4a23c2554e048750f6adaa987695bd34abadd104aa7ce6309774c9fd06f640b6194c45f58f4e847ed0ad9ce4270e693c4b95427faad06186a languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-api-design-systems-json@npm:^1.0.0-beta.40 <1.0.0-rc.0": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-parser-adapter-api-design-systems-json@npm:1.0.0-beta.48" +"@swagger-api/apidom-ns-openapi-3-2@npm:^1.7.0, @swagger-api/apidom-ns-openapi-3-2@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-ns-openapi-3-2@npm:1.8.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-api-design-systems": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-parser-adapter-json": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-ast": "npm:^1.8.0" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-json-pointer": "npm:^1.8.0" + "@swagger-api/apidom-ns-json-schema-2020-12": "npm:^1.8.0" + "@swagger-api/apidom-ns-openapi-3-0": "npm:^1.8.0" + "@swagger-api/apidom-ns-openapi-3-1": "npm:^1.8.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/ac86988052a7ad9f03c389cef02b22da36d6cbaae6c670c7642615de7f6efe921b3991f14a55793f803e5b5cb9eb0bcda30857a70a0a0d1c31d8f2d8023e78c6 + ts-mixer: "npm:^6.0.3" + checksum: 10/37c13a13253685c7c3652a4e07e552a85d5c0e7a8d49541c60d267cfde29ec254ff879ad2db983f2c93b632ddd243c8e1839040bc5ef820ba5dee03fbce8c2f8 languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-api-design-systems-yaml@npm:^1.0.0-beta.40 <1.0.0-rc.0": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-parser-adapter-api-design-systems-yaml@npm:1.0.0-beta.48" +"@swagger-api/apidom-parser-adapter-api-design-systems-json@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-parser-adapter-api-design-systems-json@npm:1.8.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-api-design-systems": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-ns-api-design-systems": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-json": "npm:^1.8.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/c0c9f35707afe05c559c8edacc0f548d3916f8f0cb0b4800b441258cbd871ba5c8ed083758b82bbe2589f022964bcd199f8d839648a78e979c94234e7ff22a08 + checksum: 10/c1fb6d2b75ddc2828c23e5804594233eb8d345a6891a5b76d843add5d2ea8d71e922174d2fd88d9eca9992c9aedbd79df5f02e7fc8f0fe54c82cd5c099e32080 languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-arazzo-json-1@npm:^1.0.0-beta.40 <1.0.0-rc.0": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-parser-adapter-arazzo-json-1@npm:1.0.0-beta.48" +"@swagger-api/apidom-parser-adapter-api-design-systems-yaml@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-parser-adapter-api-design-systems-yaml@npm:1.8.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-arazzo-1": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-parser-adapter-json": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-ns-api-design-systems": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.8.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/ec8bd9268b09d3446bb922d881ceab4f52164f995409a305dfa140b9c9790c2f75bf9d59bbc1d6859e1943bc1376ee72a6b7865eaf4b2043795c8576d489ce1b + checksum: 10/1afb67ddb959e8e91a0706637c9d06d89386f2e092dcb26a53200461d0cd2982aa180ca86d82b432c92bdeff5672ebb65d7b9be96b794ad686639e84067d678c languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-arazzo-yaml-1@npm:^1.0.0-beta.40 <1.0.0-rc.0": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-parser-adapter-arazzo-yaml-1@npm:1.0.0-beta.48" +"@swagger-api/apidom-parser-adapter-arazzo-json-1@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-parser-adapter-arazzo-json-1@npm:1.8.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-arazzo-1": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-ns-arazzo-1": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-json": "npm:^1.8.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/285a55e288475bd20a960fcc90d61bb0b29c8fc586a77ac3ba254c7eacc11db95a964648ae61e8deec20d90bfe32ea5eb11ec8857c6c4b44061c98045be75d5c + checksum: 10/7d8f3d0698c5339d972f738b381cfbe35bcae60c178dc3bd782afe6fb65a5f7c10d030c662ff68bcd819c9ebe2b4e8ee48549c633ad8b4aa0a7af7f4a1a36c13 languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-asyncapi-json-2@npm:^1.0.0-beta.40 <1.0.0-rc.0": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-parser-adapter-asyncapi-json-2@npm:1.0.0-beta.48" +"@swagger-api/apidom-parser-adapter-arazzo-yaml-1@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-parser-adapter-arazzo-yaml-1@npm:1.8.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-asyncapi-2": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-parser-adapter-json": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-ns-arazzo-1": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.8.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/1011a2be2fd576013ae5b725592c49f99dc42c50127d2707dc48893a33eb94a3820f62be6650f5bcbdb987034b5ef150905415eb44c7384a3376da97982bc467 + checksum: 10/89889d4c0105ab810d2bb91198770a4a841846d7e0d4033467e0ef96721261de28ce73ab67e25a3ca3a6b24902d1aa722dcc688f14651b32ee63e020cbfe93d8 languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-asyncapi-yaml-2@npm:^1.0.0-beta.40 <1.0.0-rc.0": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2@npm:1.0.0-beta.48" +"@swagger-api/apidom-parser-adapter-asyncapi-json-2@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-parser-adapter-asyncapi-json-2@npm:1.8.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-asyncapi-2": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-ns-asyncapi-2": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-json": "npm:^1.8.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/c6f28dc27fe796ef3c8440afaa3ce78163e1290fd3c73437541eb629b028d4552537fdeaf29cfd9415774ee2909321289f5c9eb4682e6c5cbf6981ddabaf78b5 + checksum: 10/1e248c0e2629c4e19b9e0893bd28ef09b5d9fadb012099c6af1576cf4aaf69b0f6234e52e0fabfefe4b9d27979d5891844b9d244856154cab4f8061f49f5e870 languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-json@npm:^1.0.0-beta.40 <1.0.0-rc.0, @swagger-api/apidom-parser-adapter-json@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-parser-adapter-json@npm:1.0.0-beta.48" +"@swagger-api/apidom-parser-adapter-asyncapi-json-3@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-parser-adapter-asyncapi-json-3@npm:1.8.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-ast": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-error": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-ns-asyncapi-3": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-json": "npm:^1.8.0" + "@types/ramda": "npm:~0.30.0" + ramda: "npm:~0.30.0" + ramda-adjunct: "npm:^5.0.0" + checksum: 10/2e9cb826dd9742af70f1c1a8b0b65362d631b69cd7fd1bbe9fdf7a5e47303bf6ef6fb849ea1d3461f0d02fd702f0b7a64c3c1e762e411bb78cebd9f8f0068d26 + languageName: node + linkType: hard + +"@swagger-api/apidom-parser-adapter-asyncapi-yaml-2@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2@npm:1.8.0" + dependencies: + "@babel/runtime-corejs3": "npm:^7.26.10" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-ns-asyncapi-2": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.8.0" + "@types/ramda": "npm:~0.30.0" + ramda: "npm:~0.30.0" + ramda-adjunct: "npm:^5.0.0" + checksum: 10/a7e899756d055b945e90144059fcae6333ce3dc363c69ad0dd543e3d0e0a4eacc7b5f64a164211259e1386ad9800d1bd062c3cf64123023df2f3172d76ce6afa + languageName: node + linkType: hard + +"@swagger-api/apidom-parser-adapter-asyncapi-yaml-3@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-parser-adapter-asyncapi-yaml-3@npm:1.8.0" + dependencies: + "@babel/runtime-corejs3": "npm:^7.26.10" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-ns-asyncapi-3": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.8.0" + "@types/ramda": "npm:~0.30.0" + ramda: "npm:~0.30.0" + ramda-adjunct: "npm:^5.0.0" + checksum: 10/108c6639e07787373fe38801c14e1ba173e77878efa9ae2dd19a8ea4c03d97ee2a1180eaec904c937002ca130bb9dcb9aa9772cda3dc743d29da9d0c5d7cfdb7 + languageName: node + linkType: hard + +"@swagger-api/apidom-parser-adapter-json@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-parser-adapter-json@npm:1.8.0" + dependencies: + "@babel/runtime-corejs3": "npm:^7.26.10" + "@swagger-api/apidom-ast": "npm:^1.8.0" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-error": "npm:^1.8.0" "@types/ramda": "npm:~0.30.0" node-gyp: "npm:latest" ramda: "npm:~0.30.0" @@ -19548,150 +18254,183 @@ __metadata: tree-sitter: "npm:=0.21.1" tree-sitter-json: "npm:=0.24.8" web-tree-sitter: "npm:=0.24.5" - checksum: 10/ddb74c62c2a28d359584d385027db17c9630186e6f007e28220379ac27b2875c236b5a748101ff0d46edc46a222287db3ba3e0f43345ce2cf978cda691879b04 + checksum: 10/24e371687f54e2ac5f4eacd2128bf870b078fbd581c62a9df5d2c7425a166b5ee5ef942b674bc49bc1cd6fb7627f1e246b24f42783fd96a40cc14be4f2f18528 languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-openapi-json-2@npm:^1.0.0-beta.40 <1.0.0-rc.0": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-parser-adapter-openapi-json-2@npm:1.0.0-beta.48" +"@swagger-api/apidom-parser-adapter-openapi-json-2@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-parser-adapter-openapi-json-2@npm:1.8.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-openapi-2": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-parser-adapter-json": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-ns-openapi-2": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-json": "npm:^1.8.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/c5d4ebfa13c58487626a9b2764995f690db5bbe25de80212d9f216451b20716facb67f3fccae03ed986810d5ecb832756716ba86fcd506b1a3e974aa0e3117dc + checksum: 10/a5674fba3bb33b6e5d2c7809716fa6b26b952bd371e307c62bdbd86cdd376c6cbef3202ebc4f9d6a59b3d9f1e8253ec9f2941bed0d034b766210a2437bc303b5 languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-openapi-json-3-0@npm:^1.0.0-beta.40 <1.0.0-rc.0": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-parser-adapter-openapi-json-3-0@npm:1.0.0-beta.48" +"@swagger-api/apidom-parser-adapter-openapi-json-3-0@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-parser-adapter-openapi-json-3-0@npm:1.8.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-openapi-3-0": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-parser-adapter-json": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-ns-openapi-3-0": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-json": "npm:^1.8.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/b2e2d71cbb862ec477ebdda8f0e71353c58cef40b1d709ab8cdcea364757f7ffd2643c13a8ff60d0993ffe6a9229deaba6654fea6f3a890730a0b598183de1db + checksum: 10/3a15b212be8c1300a59f20e7725bd5f0dada4c2bbd6a6c03a29ee799b782309ad1a90f4eab82fcdb146ec0285d9339ba85158bb8e4ef3be8b152557e73fc99ad languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-openapi-json-3-1@npm:^1.0.0-beta.40 <1.0.0-rc.0": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-parser-adapter-openapi-json-3-1@npm:1.0.0-beta.48" +"@swagger-api/apidom-parser-adapter-openapi-json-3-1@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-parser-adapter-openapi-json-3-1@npm:1.8.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-openapi-3-1": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-parser-adapter-json": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-ns-openapi-3-1": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-json": "npm:^1.8.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/a9db188dee99f38e0a91644362e87c526559ddfb35019ed69738a5bebdad645beab925f47d1f0ae2430ca10bd83f329a4007bb2b3b6d23c6fe32d23865fdd672 + checksum: 10/e1ec9596ca66457c8e53670eec8b4d13af154c140279525f3ddd215adf28f933fd595fd4c1e00113c9ea65625cd297719c6979f27b26922423f9e0695db81592 languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-openapi-yaml-2@npm:^1.0.0-beta.40 <1.0.0-rc.0": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-parser-adapter-openapi-yaml-2@npm:1.0.0-beta.48" +"@swagger-api/apidom-parser-adapter-openapi-json-3-2@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-parser-adapter-openapi-json-3-2@npm:1.8.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-openapi-2": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-ns-openapi-3-2": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-json": "npm:^1.8.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/ed29926c1b08108cae294bc100fd279b555731ebc23edba2461e4817f82273003cf650f1eac0d1f2382ad590d76255e23ebf08438c3017ddcec7149e7140761d + checksum: 10/58c3d88d7f7cdfe91b6a0b5207b694a525bc33f06ea56376a5a7966a8d50b317ef36dc09dc380b9d081ff9accf29416303793fe84de29c6d6501dae88732f413 languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-openapi-yaml-3-0@npm:^1.0.0-beta.40 <1.0.0-rc.0": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0@npm:1.0.0-beta.48" +"@swagger-api/apidom-parser-adapter-openapi-yaml-2@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-parser-adapter-openapi-yaml-2@npm:1.8.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-openapi-3-0": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-ns-openapi-2": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.8.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/10d28a60ec9b4197d0c75bf05832c07fc1e8f61f72875ebe1baf328671c53499b51a9c065e1f0310c7c22aa6cb8f51ab2e8ba2692eee2b52f609982e8fe316c5 + checksum: 10/214e3995ecb1f8ec8930b1865e299157deb2935e6502b07477025eb61abbddeb4111f51e31643eaf972f11d426a876c4253d85fe851858d884354256808bfb20 languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-openapi-yaml-3-1@npm:^1.0.0-beta.40 <1.0.0-rc.0": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1@npm:1.0.0-beta.48" +"@swagger-api/apidom-parser-adapter-openapi-yaml-3-0@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0@npm:1.8.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-openapi-3-1": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-ns-openapi-3-0": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.8.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/2f31d09f7aba46b94ad2891097c3e2b880f6f76fab298dbcd794dd42c49fc0d9194f5a73abeb4f3140c4b53deff769f3eddbf16e70a9d764c36cdb24b4998071 + checksum: 10/f5d9715dbeddf620e77a4ab66b849ebb544dc922f57f72cf2917b9a7d016124f901115fccdc4c47131f49f4c6fe17a2ec5ce2b31aa103fe4729cc50edb6cf937 languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-yaml-1-2@npm:^1.0.0-beta.40 <1.0.0-rc.0, @swagger-api/apidom-parser-adapter-yaml-1-2@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-parser-adapter-yaml-1-2@npm:1.0.0-beta.48" +"@swagger-api/apidom-parser-adapter-openapi-yaml-3-1@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1@npm:1.8.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-ast": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-error": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-ns-openapi-3-1": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.8.0" + "@types/ramda": "npm:~0.30.0" + ramda: "npm:~0.30.0" + ramda-adjunct: "npm:^5.0.0" + checksum: 10/a036586f80267d4715fa6769f5eda07db64c84b0af7513baf14d496d59c33c5d46d30c360020f432af9dc4d48325a8b36d830468d1e69556357ad8a27a7172c0 + languageName: node + linkType: hard + +"@swagger-api/apidom-parser-adapter-openapi-yaml-3-2@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-parser-adapter-openapi-yaml-3-2@npm:1.8.0" + dependencies: + "@babel/runtime-corejs3": "npm:^7.26.10" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-ns-openapi-3-2": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.8.0" + "@types/ramda": "npm:~0.30.0" + ramda: "npm:~0.30.0" + ramda-adjunct: "npm:^5.0.0" + checksum: 10/1f0451ac30a8e00ccf5d8d66b271d07aef6b47b98594a27925cc5a09750531667edb5bc411e8802a57f5af38890b2ef4be8efe6261b3880ef28e1d476abb4ad0 + languageName: node + linkType: hard + +"@swagger-api/apidom-parser-adapter-yaml-1-2@npm:^1.8.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-parser-adapter-yaml-1-2@npm:1.8.0" + dependencies: + "@babel/runtime-corejs3": "npm:^7.26.10" + "@swagger-api/apidom-ast": "npm:^1.8.0" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-error": "npm:^1.8.0" "@tree-sitter-grammars/tree-sitter-yaml": "npm:=0.7.1" "@types/ramda": "npm:~0.30.0" - node-gyp: "npm:latest" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" tree-sitter: "npm:=0.22.4" web-tree-sitter: "npm:=0.24.5" - checksum: 10/c032b088ffef50e119fb9cb60e7a6edfc17ec09e40c6d5ee62f5561c4da806f29ef5ba850f701763c755491f323754c23bb5a07da0cbbd3ed7d673caf5364559 + checksum: 10/2fc6e8adb4a9134c68f589b284c0fb4df4b3b93baf9cadf34ef7566e2bf462be8efa88e905ebcbc1c9e32fc8c15dc71d4fd5c1fb1616be93f9a0176dc19b4494 languageName: node linkType: hard -"@swagger-api/apidom-reference@npm:>=1.0.0-beta.41 <1.0.0-rc.0": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-reference@npm:1.0.0-beta.48" +"@swagger-api/apidom-reference@npm:^1.7.0": + version: 1.8.0 + resolution: "@swagger-api/apidom-reference@npm:1.8.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-error": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-json-pointer": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-ns-arazzo-1": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-ns-asyncapi-2": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-ns-openapi-2": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-ns-openapi-3-0": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-ns-openapi-3-1": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-parser-adapter-api-design-systems-json": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-parser-adapter-arazzo-json-1": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-parser-adapter-arazzo-yaml-1": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-parser-adapter-json": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-parser-adapter-openapi-json-2": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-parser-adapter-openapi-yaml-2": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.0.0-beta.40 <1.0.0-rc.0" + "@swagger-api/apidom-core": "npm:^1.8.0" + "@swagger-api/apidom-error": "npm:^1.8.0" + "@swagger-api/apidom-json-pointer": "npm:^1.8.0" + "@swagger-api/apidom-ns-arazzo-1": "npm:^1.8.0" + "@swagger-api/apidom-ns-asyncapi-2": "npm:^1.8.0" + "@swagger-api/apidom-ns-openapi-2": "npm:^1.8.0" + "@swagger-api/apidom-ns-openapi-3-0": "npm:^1.8.0" + "@swagger-api/apidom-ns-openapi-3-1": "npm:^1.8.0" + "@swagger-api/apidom-ns-openapi-3-2": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-api-design-systems-json": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-arazzo-json-1": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-arazzo-yaml-1": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-asyncapi-json-3": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-asyncapi-yaml-3": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-json": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-openapi-json-2": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-openapi-json-3-2": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-openapi-yaml-2": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-2": "npm:^1.8.0" + "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.8.0" "@types/ramda": "npm:~0.30.0" - axios: "npm:^1.9.0" - minimatch: "npm:^7.4.3" - process: "npm:^0.11.10" + axios: "npm:^1.12.2" + minimatch: "npm:^10.2.1" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" dependenciesMeta: @@ -19707,6 +18446,8 @@ __metadata: optional: true "@swagger-api/apidom-ns-openapi-3-1": optional: true + "@swagger-api/apidom-ns-openapi-3-2": + optional: true "@swagger-api/apidom-parser-adapter-api-design-systems-json": optional: true "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": @@ -19717,8 +18458,12 @@ __metadata: optional: true "@swagger-api/apidom-parser-adapter-asyncapi-json-2": optional: true + "@swagger-api/apidom-parser-adapter-asyncapi-json-3": + optional: true "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": optional: true + "@swagger-api/apidom-parser-adapter-asyncapi-yaml-3": + optional: true "@swagger-api/apidom-parser-adapter-json": optional: true "@swagger-api/apidom-parser-adapter-openapi-json-2": @@ -19727,15 +18472,19 @@ __metadata: optional: true "@swagger-api/apidom-parser-adapter-openapi-json-3-1": optional: true + "@swagger-api/apidom-parser-adapter-openapi-json-3-2": + optional: true "@swagger-api/apidom-parser-adapter-openapi-yaml-2": optional: true "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": optional: true "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": optional: true + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-2": + optional: true "@swagger-api/apidom-parser-adapter-yaml-1-2": optional: true - checksum: 10/fd86401690a3992123783c1b6044cdbb4a36b75d437e2519f6ec092d1b03273be45e884fc426e27a9413484047d40f819751f4f7174d9625de8a52a28d2d9e35 + checksum: 10/430ea29886299312424539cac9044bc17979b234375df91853d8fef72d83bb585c798db595f28f51b2028c961fd5b86510835efcae52d893d161fce271957635 languageName: node linkType: hard @@ -19757,92 +18506,92 @@ __metadata: languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-darwin-arm64@npm:1.11.24" +"@swc/core-darwin-arm64@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-darwin-arm64@npm:1.15.18" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-darwin-x64@npm:1.11.24" +"@swc/core-darwin-x64@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-darwin-x64@npm:1.15.18" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.11.24" +"@swc/core-linux-arm-gnueabihf@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.15.18" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-linux-arm64-gnu@npm:1.11.24" +"@swc/core-linux-arm64-gnu@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-linux-arm64-gnu@npm:1.15.18" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-linux-arm64-musl@npm:1.11.24" +"@swc/core-linux-arm64-musl@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-linux-arm64-musl@npm:1.15.18" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-linux-x64-gnu@npm:1.11.24" +"@swc/core-linux-x64-gnu@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-linux-x64-gnu@npm:1.15.18" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-linux-x64-musl@npm:1.11.24" +"@swc/core-linux-x64-musl@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-linux-x64-musl@npm:1.15.18" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-win32-arm64-msvc@npm:1.11.24" +"@swc/core-win32-arm64-msvc@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-win32-arm64-msvc@npm:1.15.18" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-win32-ia32-msvc@npm:1.11.24" +"@swc/core-win32-ia32-msvc@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-win32-ia32-msvc@npm:1.15.18" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-win32-x64-msvc@npm:1.11.24" +"@swc/core-win32-x64-msvc@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-win32-x64-msvc@npm:1.15.18" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@swc/core@npm:^1.3.46": - version: 1.11.24 - resolution: "@swc/core@npm:1.11.24" +"@swc/core@npm:^1.15.6": + version: 1.15.18 + resolution: "@swc/core@npm:1.15.18" dependencies: - "@swc/core-darwin-arm64": "npm:1.11.24" - "@swc/core-darwin-x64": "npm:1.11.24" - "@swc/core-linux-arm-gnueabihf": "npm:1.11.24" - "@swc/core-linux-arm64-gnu": "npm:1.11.24" - "@swc/core-linux-arm64-musl": "npm:1.11.24" - "@swc/core-linux-x64-gnu": "npm:1.11.24" - "@swc/core-linux-x64-musl": "npm:1.11.24" - "@swc/core-win32-arm64-msvc": "npm:1.11.24" - "@swc/core-win32-ia32-msvc": "npm:1.11.24" - "@swc/core-win32-x64-msvc": "npm:1.11.24" + "@swc/core-darwin-arm64": "npm:1.15.18" + "@swc/core-darwin-x64": "npm:1.15.18" + "@swc/core-linux-arm-gnueabihf": "npm:1.15.18" + "@swc/core-linux-arm64-gnu": "npm:1.15.18" + "@swc/core-linux-arm64-musl": "npm:1.15.18" + "@swc/core-linux-x64-gnu": "npm:1.15.18" + "@swc/core-linux-x64-musl": "npm:1.15.18" + "@swc/core-win32-arm64-msvc": "npm:1.15.18" + "@swc/core-win32-ia32-msvc": "npm:1.15.18" + "@swc/core-win32-x64-msvc": "npm:1.15.18" "@swc/counter": "npm:^0.1.3" - "@swc/types": "npm:^0.1.21" + "@swc/types": "npm:^0.1.25" peerDependencies: "@swc/helpers": ">=0.5.17" dependenciesMeta: @@ -19869,7 +18618,7 @@ __metadata: peerDependenciesMeta: "@swc/helpers": optional: true - checksum: 10/0b3e883f8a5652a7ab221a777386ccc8a65fc5b53d533bad15b703b22984eb3b449efd907b1872263f1a9990a9a50612f3c6deb619894a43f03cd974ec9bd1b7 + checksum: 10/ef198b9feb6eee034e3a912c37988ece2885fec35419e8245d467adbc1fc47a5c3e61869d1bdbe6fff76cbd9186ef278120cbb9746d5f7446576f4a7f15c2dcd languageName: node linkType: hard @@ -19881,33 +18630,33 @@ __metadata: linkType: hard "@swc/helpers@npm:^0.5.0, @swc/helpers@npm:^0.5.8": - version: 0.5.17 - resolution: "@swc/helpers@npm:0.5.17" + version: 0.5.19 + resolution: "@swc/helpers@npm:0.5.19" dependencies: tslib: "npm:^2.8.0" - checksum: 10/1fc8312a78f1f99c8ec838585445e99763eeebff2356100738cdfdb8ad47d2d38df678ee6edd93a90fe319ac52da67adc14ac00eb82b606c5fb8ebc5d06ec2a2 + checksum: 10/3fd365fb3265f97e1241bcbcea9bfa5e15e03c630424e1b54597e00d30be2c271cb0c74f45e1739c6bc5ae892647302fab412de5138941aa96e66aebf4586700 languageName: node linkType: hard -"@swc/jest@npm:^0.2.22": - version: 0.2.38 - resolution: "@swc/jest@npm:0.2.38" +"@swc/jest@npm:^0.2.39": + version: 0.2.39 + resolution: "@swc/jest@npm:0.2.39" dependencies: - "@jest/create-cache-key-function": "npm:^29.7.0" + "@jest/create-cache-key-function": "npm:^30.0.0" "@swc/counter": "npm:^0.1.3" jsonc-parser: "npm:^3.2.0" peerDependencies: "@swc/core": "*" - checksum: 10/3aaf557425e806890ebefea35334b7795e9f8ddf6f82d634d865ef917333cca4208190af1a9610c134c0e3b7a6a1aea4ec77a659e3ca5965be7aace65ce80c97 + checksum: 10/a2b7ed6fbb908867e673d1bbff9efde7eee225a57ad75735216ce2005e40c5cfb92285bd807d2058f1c0317e3d48ed71f5577fe85b28bebc80c1bc2c3a03306e languageName: node linkType: hard -"@swc/types@npm:^0.1.21": - version: 0.1.21 - resolution: "@swc/types@npm:0.1.21" +"@swc/types@npm:^0.1.25": + version: 0.1.25 + resolution: "@swc/types@npm:0.1.25" dependencies: "@swc/counter": "npm:^0.1.3" - checksum: 10/6554bf5c78519f49099a2ba448d170191a14b1c7a35df848f10ee4d6c03ecd681e5213884905187de1d1d221589ec8b5cb77f477d099dc1627c3ec9d7f2fcdb0 + checksum: 10/f6741450224892d12df43e5ca7f3cc0287df644dcd672626eb0cc2a3a8e3e875f4b29eb11336f37c7240cf6e010ba59eb3a79f4fb8bee5cbd168dfc1326ff369 languageName: node linkType: hard @@ -19958,16 +18707,14 @@ __metadata: "@backstage/cli-common": "workspace:^" "@backstage/config": "workspace:^" "@backstage/plugin-techdocs-node": "workspace:^" - "@types/commander": "npm:^2.12.2" "@types/fs-extra": "npm:^11.0.0" "@types/http-proxy": "npm:^1.17.4" - "@types/node": "npm:^20.16.0" + "@types/node": "npm:^22.13.14" "@types/serve-handler": "npm:^6.1.0" "@types/webpack-env": "npm:^1.15.3" - commander: "npm:^12.0.0" - find-process: "npm:^1.4.5" + commander: "npm:^14.0.3" + find-process: "npm:^2.0.0" fs-extra: "npm:^11.0.0" - global-agent: "npm:^3.0.0" http-proxy: "npm:^1.18.1" nodemon: "npm:^3.0.1" react-dev-utils: "npm:^12.0.0-next.60" @@ -19995,7 +18742,7 @@ __metadata: languageName: node linkType: hard -"@testing-library/jest-dom@npm:^6.0.0, @testing-library/jest-dom@npm:^6.6.3": +"@testing-library/jest-dom@npm:^6.0.0, @testing-library/jest-dom@npm:^6.9.1": version: 6.9.1 resolution: "@testing-library/jest-dom@npm:6.9.1" dependencies: @@ -20032,8 +18779,8 @@ __metadata: linkType: hard "@testing-library/react@npm:^16.0.0": - version: 16.3.0 - resolution: "@testing-library/react@npm:16.3.0" + version: 16.3.2 + resolution: "@testing-library/react@npm:16.3.2" dependencies: "@babel/runtime": "npm:^7.12.5" peerDependencies: @@ -20047,7 +18794,7 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10/0ee9e31dd0d2396a924682d0e61a4ecc6bfab8eaff23dbf8a72c3c2ce22c116fa578148baeb4de75b968ef99d22e6e6aa0a00dba40286f71184918bb6bb5b06a + checksum: 10/0ca88c6f672d00c2afd1bdedeff9b5382dd8157038efeb9762dc016731030075624be7106b92d2b5e5c52812faea85263e69272c14b6f8700eb48a4a8af6feef languageName: node linkType: hard @@ -20060,14 +18807,66 @@ __metadata: languageName: node linkType: hard -"@tokenizer/inflate@npm:^0.2.7": - version: 0.2.7 - resolution: "@tokenizer/inflate@npm:0.2.7" +"@theguild/federation-composition@npm:^0.21.3": + version: 0.21.3 + resolution: "@theguild/federation-composition@npm:0.21.3" dependencies: - debug: "npm:^4.4.0" - fflate: "npm:^0.8.2" - token-types: "npm:^6.0.0" - checksum: 10/6cee1857e47ca0fc053d6cd87773b7c21857ab84cb847c7d9437a76d923e265c88f8e99a4ac9643c2f989f4b9791259ca17128f0480191449e2b412821a1b9a7 + constant-case: "npm:^3.0.4" + debug: "npm:4.4.3" + json5: "npm:^2.2.3" + lodash.sortby: "npm:^4.7.0" + peerDependencies: + graphql: ^16.0.0 + checksum: 10/9c2f8a9e758e19e20443b146b88b1c1d8eaef4214988fe0cb33c57498090d1ab94f5bbff182d7a5f73c9613e997690093afd256c929718f8d3caea524f385a43 + languageName: node + linkType: hard + +"@tmcp/adapter-valibot@npm:^0.1.4": + version: 0.1.5 + resolution: "@tmcp/adapter-valibot@npm:0.1.5" + dependencies: + "@standard-schema/spec": "npm:^1.0.0" + "@valibot/to-json-schema": "npm:^1.3.0" + valibot: "npm:^1.1.0" + peerDependencies: + tmcp: ^1.17.0 + valibot: ^1.1.0 + checksum: 10/3affc173d69dd4e4bc502d9af1cc62951baf7505442883f888b4cf03278a9b50308dfce118adbbfa617a589976c0daf57e5043190f2d44595816dc98c0a26d66 + languageName: node + linkType: hard + +"@tmcp/session-manager@npm:^0.2.1": + version: 0.2.1 + resolution: "@tmcp/session-manager@npm:0.2.1" + peerDependencies: + tmcp: ^1.16.3 + checksum: 10/fc063ae9adaecb799c806c683f9ac4946ccf9c54de61f41825e6967473971da99545a061396c9d0ec40506d8f6eedcf7f134df42125476f2d76a4890f5dc0462 + languageName: node + linkType: hard + +"@tmcp/transport-http@npm:^0.8.0": + version: 0.8.3 + resolution: "@tmcp/transport-http@npm:0.8.3" + dependencies: + "@tmcp/session-manager": "npm:^0.2.1" + esm-env: "npm:^1.2.2" + peerDependencies: + "@tmcp/auth": ^0.3.3 || ^0.4.0 + tmcp: ^1.18.0 + peerDependenciesMeta: + "@tmcp/auth": + optional: true + checksum: 10/049516cd3525948bb2b40a400ceef9b4fb3ed0668e67cb88cf355644e8cf1743fa57fc5370de0fa3e085cd82ee39093752f2e503af22b04a55a28a70f644be9c + languageName: node + linkType: hard + +"@tokenizer/inflate@npm:^0.4.1": + version: 0.4.1 + resolution: "@tokenizer/inflate@npm:0.4.1" + dependencies: + debug: "npm:^4.4.3" + token-types: "npm:^6.1.1" + checksum: 10/27d58757e1a6c004e86f8a5f1a40fe47cb48aa6891864d03de6eab27d42fafc1456f396bc8bc300e16913b0a85f42034d011db0213d17e544ed201a7fc24244e languageName: node linkType: hard @@ -20127,13 +18926,6 @@ __metadata: languageName: node linkType: hard -"@trysound/sax@npm:0.2.0": - version: 0.2.0 - resolution: "@trysound/sax@npm:0.2.0" - checksum: 10/7379713eca480ac0d9b6c7b063e06b00a7eac57092354556c81027066eb65b61ea141a69d0cc2e15d32e05b2834d4c9c2184793a5e36bbf5daf05ee5676af18c - languageName: node - linkType: hard - "@ts-graphviz/adapter@npm:^2.0.6": version: 2.0.6 resolution: "@ts-graphviz/adapter@npm:2.0.6" @@ -20225,7 +19017,7 @@ __metadata: languageName: node linkType: hard -"@tybys/wasm-util@npm:^0.10.1": +"@tybys/wasm-util@npm:^0.10.0, @tybys/wasm-util@npm:^0.10.1": version: 0.10.1 resolution: "@tybys/wasm-util@npm:0.10.1" dependencies: @@ -20243,12 +19035,12 @@ __metadata: languageName: node linkType: hard -"@types/archiver@npm:^6.0.0": - version: 6.0.4 - resolution: "@types/archiver@npm:6.0.4" +"@types/archiver@npm:^7.0.0": + version: 7.0.0 + resolution: "@types/archiver@npm:7.0.0" dependencies: "@types/readdir-glob": "npm:*" - checksum: 10/93e500f55e3fbb200b10988dbd8407df78e8a3d921d09d97c114bde609ef75e478a2fdeddc1b8da465f569219dfb1fb7dc1ff83332d77c2a03806d68a027c909 + checksum: 10/ed9d2d259b3aa86c64c8b28cfffc01ee8199ff5db906b70ad500233cef032a22368ac456c19aa5ae7b9472e1ec587d834d46b4e85d45a3f1f16bf4c7060c5bc4 languageName: node linkType: hard @@ -20266,19 +19058,10 @@ __metadata: languageName: node linkType: hard -"@types/asn1@npm:>=0.2.4": - version: 0.2.4 - resolution: "@types/asn1@npm:0.2.4" - dependencies: - "@types/node": "npm:*" - checksum: 10/01fee5a896a650f92c95556c83678d66677060b5aba7936b714b5c8ca2945cc8cabf57441e323f1800a783a55958b6230d109e988ca45e83b2d7a0e071c24069 - languageName: node - linkType: hard - -"@types/aws-lambda@npm:8.10.150, @types/aws-lambda@npm:^8.10.83": - version: 8.10.150 - resolution: "@types/aws-lambda@npm:8.10.150" - checksum: 10/d837ae0b94fb16c7cfec3711126f0e7fb109fc3e6d14d994d083d2039c4f2bc52a75f256ee31a8010f023ce78ddcf3746ad43d552592a0f56720167f6d3111be +"@types/aws-lambda@npm:^8.10.155, @types/aws-lambda@npm:^8.10.83": + version: 8.10.159 + resolution: "@types/aws-lambda@npm:8.10.159" + checksum: 10/3fb4d2f5613e3526725676787770442df2cace0d4d4c528c17e791ccee3cc8626447529ed86d937aa709c82db53670835dcf6e0c704d6e3ae92832dcc886eac0 languageName: node linkType: hard @@ -20291,7 +19074,7 @@ __metadata: languageName: node linkType: hard -"@types/babel__core@npm:^7.1.14, @types/babel__core@npm:^7.20.5": +"@types/babel__core@npm:^7.20.5": version: 7.20.5 resolution: "@types/babel__core@npm:7.20.5" dependencies: @@ -20323,7 +19106,7 @@ __metadata: languageName: node linkType: hard -"@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.0.6, @types/babel__traverse@npm:^7.20.7": +"@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.20.7": version: 7.28.0 resolution: "@types/babel__traverse@npm:7.28.0" dependencies: @@ -20447,15 +19230,6 @@ __metadata: languageName: node linkType: hard -"@types/commander@npm:^2.12.2": - version: 2.12.5 - resolution: "@types/commander@npm:2.12.5" - dependencies: - commander: "npm:*" - checksum: 10/5b70bf09366f778f54cd0b831417aa1f749cdd9e95e81016d0537b801ad14275973b04c34ff4dfaa344986aa93b4518c2df12647ecf8b499b636585335ae3edc - languageName: node - linkType: hard - "@types/compression@npm:^1.7.5": version: 1.8.1 resolution: "@types/compression@npm:1.8.1" @@ -20587,11 +19361,11 @@ __metadata: linkType: hard "@types/d3-shape@npm:^3.0.1": - version: 3.1.7 - resolution: "@types/d3-shape@npm:3.1.7" + version: 3.1.8 + resolution: "@types/d3-shape@npm:3.1.8" dependencies: "@types/d3-path": "npm:*" - checksum: 10/b7ddda2a9c916ba438308bfa6e53fa2bb11c2ce13537ba2a7816c16f9432287b57901921c7231d2924f2d7d360535c3795f017865ab05abe5057c6ca06ca81df + checksum: 10/ebc161d49101d84409829fea516ba7ec71ad51a1e97438ca0fafc1c30b56b3feae802d220375323632723a338dda7237c652e831e0b53527a6222ab0d1bb7809 languageName: node linkType: hard @@ -20631,14 +19405,14 @@ __metadata: languageName: node linkType: hard -"@types/dockerode@npm:^3.3.29": - version: 3.3.38 - resolution: "@types/dockerode@npm:3.3.38" +"@types/dockerode@npm:^4.0.1": + version: 4.0.1 + resolution: "@types/dockerode@npm:4.0.1" dependencies: "@types/docker-modem": "npm:*" "@types/node": "npm:*" "@types/ssh2": "npm:*" - checksum: 10/439a2b905c44bac063ba707f01a35594eb1e44aba8d22d4a8029fd2c5e192d70eae1efe270f85633f4b178fe4223419e14d81b1227a5a6e1ed00fca48c8495dd + checksum: 10/d16b3a69a20fac269b2317a978442a6752dea158729a264511a3812dcb4c756e0ee079b39b61068cee182f268661e27e32aa7a28815262f0e088ffeb9f2f48c5 languageName: node linkType: hard @@ -20658,13 +19432,6 @@ __metadata: languageName: node linkType: hard -"@types/ejs@npm:^3.1.3": - version: 3.1.5 - resolution: "@types/ejs@npm:3.1.5" - checksum: 10/918898fd279108087722c1713e2ddb0c152ab839397946d164db8a18b5bbd732af9746373882a9bcf4843d35c6b191a8f569a7a4e51e90726d24501b39f40367 - languageName: node - linkType: hard - "@types/emscripten@npm:^1.39.6": version: 1.39.10 resolution: "@types/emscripten@npm:1.39.10" @@ -20701,7 +19468,7 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:*, @types/estree@npm:1.0.8, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.5, @types/estree@npm:^1.0.6, @types/estree@npm:^1.0.8": +"@types/estree@npm:*, @types/estree@npm:1.0.8, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.5, @types/estree@npm:^1.0.8": version: 1.0.8 resolution: "@types/estree@npm:1.0.8" checksum: 10/25a4c16a6752538ffde2826c2cc0c6491d90e69cd6187bef4a006dd2c3c45469f049e643d7e516c515f21484dc3d48fd5c870be158a5beb72f5baf3dc43e4099 @@ -20723,26 +19490,26 @@ __metadata: linkType: hard "@types/express-serve-static-core@npm:*": - version: 5.1.0 - resolution: "@types/express-serve-static-core@npm:5.1.0" + version: 5.1.1 + resolution: "@types/express-serve-static-core@npm:5.1.1" dependencies: "@types/node": "npm:*" "@types/qs": "npm:*" "@types/range-parser": "npm:*" "@types/send": "npm:*" - checksum: 10/c0b5b7ebc15b222f51e5705da2b8a5180335bf70927cc83c065784331aa9291984db1bfa4a14f5ba31b538dcb543561d9280046051fa4c9b7256eb971293e735 + checksum: 10/7f3d8cf7e68764c9f3e8f6a12825b69ccf5287347fc1c20b29803d4f08a4abc1153ae11d7258852c61aad50f62ef72d4c1b9c97092b0a90462c3dddec2f6026c languageName: node linkType: hard "@types/express-serve-static-core@npm:^4.17.21, @types/express-serve-static-core@npm:^4.17.33, @types/express-serve-static-core@npm:^4.17.5": - version: 4.19.7 - resolution: "@types/express-serve-static-core@npm:4.19.7" + version: 4.19.8 + resolution: "@types/express-serve-static-core@npm:4.19.8" dependencies: "@types/node": "npm:*" "@types/qs": "npm:*" "@types/range-parser": "npm:*" "@types/send": "npm:*" - checksum: 10/a87830df965fb52eec6390accdba918a6f33f3d6cb96853be2cc2f74829a0bc09a29bddd9699127dbc17a170c7eebbe1294a9db9843b5a34dbc768f9ee844c01 + checksum: 10/eb1b832343c0991395c9b10e124dc805921ea7c08efe01222d83912123b8c054119d009e9e55c91af6bdbeeec153c0d35411c9c6d80781bc8c0a43e8b1a84387 languageName: node linkType: hard @@ -20755,15 +19522,15 @@ __metadata: languageName: node linkType: hard -"@types/express@npm:*, @types/express@npm:^4.16.1, @types/express@npm:^4.17.21, @types/express@npm:^4.17.6": - version: 4.17.23 - resolution: "@types/express@npm:4.17.23" +"@types/express@npm:*, @types/express@npm:^4.16.1, @types/express@npm:^4.17.21, @types/express@npm:^4.17.25, @types/express@npm:^4.17.6": + version: 4.17.25 + resolution: "@types/express@npm:4.17.25" dependencies: "@types/body-parser": "npm:*" "@types/express-serve-static-core": "npm:^4.17.33" "@types/qs": "npm:*" - "@types/serve-static": "npm:*" - checksum: 10/cf4d540bbd90801cdc79a46107b8873404698a7fd0c3e8dd42989d52d3bd7f5b8768672e54c20835e41e27349c319bb47a404ad14c0f8db0e9d055ba1cb8a05b + "@types/serve-static": "npm:^1" + checksum: 10/c309fdb79fb8569b5d8d8f11268d0160b271f8b38f0a82c20a0733e526baf033eb7a921cd51d54fe4333c616de9e31caf7d4f3ef73baaf212d61f23f460b0369 languageName: node linkType: hard @@ -20791,16 +19558,6 @@ __metadata: languageName: node linkType: hard -"@types/glob@npm:^8.0.0": - version: 8.1.0 - resolution: "@types/glob@npm:8.1.0" - dependencies: - "@types/minimatch": "npm:^5.1.2" - "@types/node": "npm:*" - checksum: 10/9101f3a9061e40137190f70626aa0e202369b5ec4012c3fabe6f5d229cce04772db9a94fa5a0eb39655e2e4ad105c38afbb4af56a56c0996a8c7d4fc72350e3d - languageName: node - linkType: hard - "@types/global-agent@npm:^2.1.3": version: 2.1.3 resolution: "@types/global-agent@npm:2.1.3" @@ -20815,15 +19572,6 @@ __metadata: languageName: node linkType: hard -"@types/graceful-fs@npm:^4.1.3": - version: 4.1.5 - resolution: "@types/graceful-fs@npm:4.1.5" - dependencies: - "@types/node": "npm:*" - checksum: 10/d076bb61f45d0fc42dee496ef8b1c2f8742e15d5e47e90e20d0243386e426c04d4efd408a48875ab432f7960b4ce3414db20ed0fbbfc7bcc89d84e574f6e045a - languageName: node - linkType: hard - "@types/hast@npm:^2.0.0": version: 2.3.4 resolution: "@types/hast@npm:2.3.4" @@ -20833,7 +19581,7 @@ __metadata: languageName: node linkType: hard -"@types/hast@npm:^3.0.4": +"@types/hast@npm:^3.0.0, @types/hast@npm:^3.0.4": version: 3.0.4 resolution: "@types/hast@npm:3.0.4" dependencies: @@ -20933,10 +19681,10 @@ __metadata: languageName: node linkType: hard -"@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1": - version: 2.0.1 - resolution: "@types/istanbul-lib-coverage@npm:2.0.1" - checksum: 10/7de11cd954a764985722baddbc11da59109647d800455a7593d696b7763a867c9f1aa2777724cfa493058769617fe09bba90a813086dc322dd540f55fb8207d9 +"@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1, @types/istanbul-lib-coverage@npm:^2.0.6": + version: 2.0.6 + resolution: "@types/istanbul-lib-coverage@npm:2.0.6" + checksum: 10/3feac423fd3e5449485afac999dcfcb3d44a37c830af898b689fadc65d26526460bedb889db278e0d4d815a670331796494d073a10ee6e3a6526301fe7415778 languageName: node linkType: hard @@ -20949,31 +19697,29 @@ __metadata: languageName: node linkType: hard -"@types/istanbul-reports@npm:^3.0.0": - version: 3.0.0 - resolution: "@types/istanbul-reports@npm:3.0.0" +"@types/istanbul-reports@npm:^3.0.0, @types/istanbul-reports@npm:^3.0.4": + version: 3.0.4 + resolution: "@types/istanbul-reports@npm:3.0.4" dependencies: "@types/istanbul-lib-report": "npm:*" - checksum: 10/286a18cff19c4dac4321b9ea406a3560faf577fb2a4df5abf9d577fa81ba831c9baa7d40d03f1daf7fe613d468546b731c00b844b72fad9834c583311a35bb7b + checksum: 10/93eb18835770b3431f68ae9ac1ca91741ab85f7606f310a34b3586b5a34450ec038c3eed7ab19266635499594de52ff73723a54a72a75b9f7d6a956f01edee95 languageName: node linkType: hard -"@types/jest@npm:*, @types/jest@npm:^29.5.11": - version: 29.5.14 - resolution: "@types/jest@npm:29.5.14" +"@types/jest@npm:*, @types/jest@npm:^30, @types/jest@npm:^30.0.0": + version: 30.0.0 + resolution: "@types/jest@npm:30.0.0" dependencies: - expect: "npm:^29.0.0" - pretty-format: "npm:^29.0.0" - checksum: 10/59ec7a9c4688aae8ee529316c43853468b6034f453d08a2e1064b281af9c81234cec986be796288f1bbb29efe943bc950e70c8fa8faae1e460d50e3cf9760f9b + expect: "npm:^30.0.0" + pretty-format: "npm:^30.0.0" + checksum: 10/cdeaa924c68b5233d9ff92861a89e7042df2b0f197633729bcf3a31e65bd4e9426e751c5665b5ac2de0b222b33f100a5502da22aefce3d2c62931c715e88f209 languageName: node linkType: hard -"@types/jquery@npm:^3.3.34": - version: 3.5.33 - resolution: "@types/jquery@npm:3.5.33" - dependencies: - "@types/sizzle": "npm:*" - checksum: 10/9a9e2cddc584f9afa1970b0febac0b65bed6d8084baf9655346f5787ee1b25975a0f259b0c81edc7757fbb92d584ed2d1b39804ab78ab0238407c7e4b0376011 +"@types/jquery@npm:^4.0.0": + version: 4.0.0 + resolution: "@types/jquery@npm:4.0.0" + checksum: 10/3a0491a1a2b7d0a9d471c318dcf245607c8af0954658a5d2758b9f746cd85a1ce362c37c2a3ab2a41ab24d67574e167d2f4e6a873fdceeef359536d73e03a804 languageName: node linkType: hard @@ -21008,14 +19754,25 @@ __metadata: languageName: node linkType: hard -"@types/jsdom@npm:^20.0.0": - version: 20.0.0 - resolution: "@types/jsdom@npm:20.0.0" +"@types/jsdom@npm:^21.1.7": + version: 21.1.7 + resolution: "@types/jsdom@npm:21.1.7" dependencies: "@types/node": "npm:*" "@types/tough-cookie": "npm:*" parse5: "npm:^7.0.0" - checksum: 10/4cd814a948aa9b739652cde183acc6a9a10bcf04ed82c3a53f691bbc52d156b49269fa3046c70842680a29d240607fcd7be5f35a73ddbf571d95a664e8e49cec + checksum: 10/a5ee54aec813ac928ef783f69828213af4d81325f584e1fe7573a9ae139924c40768d1d5249237e62d51b9a34ed06bde059c86c6b0248d627457ec5e5d532dfa + languageName: node + linkType: hard + +"@types/jsdom@npm:^27": + version: 27.0.0 + resolution: "@types/jsdom@npm:27.0.0" + dependencies: + "@types/node": "npm:*" + "@types/tough-cookie": "npm:*" + parse5: "npm:^7.0.0" + checksum: 10/67dc284f74960e1ba0787599dfb0a46ca94ea4443242d3fc1e513b0c5313d2d4136176764acce2b03b8c66dec942ff4ab15322208ccc17b7c1dccc8cd01d4cc2 languageName: node linkType: hard @@ -21087,17 +19844,19 @@ __metadata: languageName: node linkType: hard -"@types/libsodium-wrappers@npm:^0.7.10": - version: 0.7.14 - resolution: "@types/libsodium-wrappers@npm:0.7.14" - checksum: 10/f29e4d2159c26ab00d7f0cc7a45bdccd086d9ece4c723f6d59f6d7e1dc35a7baf2b11de418b09dd69b94900bcfda621ef4f1ed9ffae51523978e432da67d911e +"@types/libsodium-wrappers@npm:^0.8.0": + version: 0.8.2 + resolution: "@types/libsodium-wrappers@npm:0.8.2" + dependencies: + libsodium-wrappers: "npm:*" + checksum: 10/c1603cc33465cf0d1e56c29ccbbdf4d0e1357324c29bf77e121277b91edaa0eaeab8c9263f8129f11036a5eab7a552028238b31746a7b5cf3ec2ad8001030377 languageName: node linkType: hard "@types/lodash@npm:^4.14.151": - version: 4.17.20 - resolution: "@types/lodash@npm:4.17.20" - checksum: 10/8cd8ad3bd78d2e06a93ae8d6c9907981d5673655fec7cb274a4d9a59549aab5bb5b3017361280773b8990ddfccf363e14d1b37c97af8a9fe363de677f9a61524 + version: 4.17.24 + resolution: "@types/lodash@npm:4.17.24" + checksum: 10/0f2082565f60f9787eefc046edc38458054512be5a8b3584ef0bad5fd9e85d0ab55ec5a1fbfae1ed6ba015cf1f9e837d5fb4da1f99fc60b8f74b2a46146fb00f languageName: node linkType: hard @@ -21115,7 +19874,14 @@ __metadata: languageName: node linkType: hard -"@types/luxon@npm:^3.0.0, @types/luxon@npm:~3.4.0": +"@types/luxon@npm:^3.0.0": + version: 3.7.1 + resolution: "@types/luxon@npm:3.7.1" + checksum: 10/c7bc164c278393ea0be938f986c74b4cddfab9013b1aff4495b016f771ded1d5b7b7b4825b2c7f0b8799edce19c5f531c28ff434ab3dedf994ac2d99a20fd4c4 + languageName: node + linkType: hard + +"@types/luxon@npm:~3.4.0": version: 3.4.2 resolution: "@types/luxon@npm:3.4.2" checksum: 10/fd89566e3026559f2bc4ddcc1e70a2c16161905ed50be9473ec0cfbbbe919165041408c4f6e06c4bcf095445535052e2c099087c76b1b38e368127e618fc968d @@ -21191,13 +19957,6 @@ __metadata: languageName: node linkType: hard -"@types/minimatch@npm:^5.1.2": - version: 5.1.2 - resolution: "@types/minimatch@npm:5.1.2" - checksum: 10/94db5060d20df2b80d77b74dd384df3115f01889b5b6c40fa2dfa27cfc03a68fb0ff7c1f2a0366070263eb2e9d6bfd8c87111d4bc3ae93c3f291297c1bf56c85 - languageName: node - linkType: hard - "@types/minimist@npm:^1.2.5": version: 1.2.5 resolution: "@types/minimist@npm:1.2.5" @@ -21212,12 +19971,21 @@ __metadata: languageName: node linkType: hard -"@types/multer@npm:^1.4.12": - version: 1.4.12 - resolution: "@types/multer@npm:1.4.12" +"@types/multer@npm:^2.0.0": + version: 2.0.0 + resolution: "@types/multer@npm:2.0.0" dependencies: "@types/express": "npm:*" - checksum: 10/3d2b32da58ddd67f972d4ef1021492f78d65f33f936b6fb25dd461bb6cc7b03bfd1de1a11562c4310680dac8054e4398038db51767a0ffbf1fe62457b3706e95 + checksum: 10/87ddd5b39fd572b1d98f84908738eed9fef53fc20628bff066d43b3bcf70481f51010a997554ed65c97121e7b8fe44b59a24cd68ccf59c3d7c64bbffb8c5a127 + languageName: node + linkType: hard + +"@types/mute-stream@npm:^0.0.4": + version: 0.0.4 + resolution: "@types/mute-stream@npm:0.0.4" + dependencies: + "@types/node": "npm:*" + checksum: 10/af8d83ad7b68ea05d9357985daf81b6c9b73af4feacb2f5c2693c7fd3e13e5135ef1bd083ce8d5bdc8e97acd28563b61bb32dec4e4508a8067fcd31b8a098632 languageName: node linkType: hard @@ -21259,12 +20027,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*, @types/node@npm:>=12, @types/node@npm:>=12.0.0, @types/node@npm:>=13.7.0, @types/node@npm:>=18.0.0, @types/node@npm:^24.0.0": - version: 24.10.0 - resolution: "@types/node@npm:24.10.0" +"@types/node@npm:*, @types/node@npm:>=12, @types/node@npm:>=12.0.0, @types/node@npm:>=13.7.0, @types/node@npm:>=18": + version: 25.5.0 + resolution: "@types/node@npm:25.5.0" dependencies: - undici-types: "npm:~7.16.0" - checksum: 10/bc3abd9adc61423b1a72324208f3fcfe4f957b9fc8b9e81bf3fed584deb0b40f0e2af63cd0e2afa8f408d06f14bdba899ac638ac619f646badf8e72de246c052 + undici-types: "npm:~7.18.0" + checksum: 10/b1e8116bd8c9ff62e458b76d28a59cf7631537bb17e8961464bf754dd5b07b46f1620f568b2f89970505af9eef478dd74c614651b454c1ea95949ec472c64fcb languageName: node linkType: hard @@ -21275,13 +20043,6 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^13.7.0": - version: 13.13.52 - resolution: "@types/node@npm:13.13.52" - checksum: 10/a1fbd080dd2462f6f0d0c10cb8328ee6b22e59941fb6beb8bca907f96e00798ce85e94320ccab3bf04f87d6c5443535a62e6896ac59c34c79a286821223e56cd - languageName: node - linkType: hard - "@types/node@npm:^15.6.1": version: 15.14.9 resolution: "@types/node@npm:15.14.9" @@ -21305,21 +20066,39 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^20.16.0": - version: 20.17.30 - resolution: "@types/node@npm:20.17.30" +"@types/node@npm:^20.10.7": + version: 20.19.37 + resolution: "@types/node@npm:20.19.37" dependencies: - undici-types: "npm:~6.19.2" - checksum: 10/69fd3b177417be77b459e8f1dd4e78c85c686167086920fbf35a9fda301709bbeee6a87ad2591fb1ddd96c65e725ec6bb527a06496626a1c94367d1361048f8d + undici-types: "npm:~6.21.0" + checksum: 10/7e0d561d0d980109a0b64c6f797584a69d706421f18c0b24850206a15a4286443e676032abfa438590f25e4dfc3561ffc8d7f5c0c90edf08b705f6f9c35febba languageName: node linkType: hard -"@types/nodemailer@npm:^6.4.14": - version: 6.4.17 - resolution: "@types/nodemailer@npm:6.4.17" +"@types/node@npm:^22.13.14": + version: 22.19.1 + resolution: "@types/node@npm:22.19.1" + dependencies: + undici-types: "npm:~6.21.0" + checksum: 10/40d5368faa6d9be6c27ebca2362734bc9e035a742e0b5cafee40ba3b355d7cfcaedbc93618c76465451e53f1af0c811b4b85ee9b85e2e942f34a4c5310fa047b + languageName: node + linkType: hard + +"@types/node@npm:^24.0.0": + version: 24.10.0 + resolution: "@types/node@npm:24.10.0" + dependencies: + undici-types: "npm:~7.16.0" + checksum: 10/bc3abd9adc61423b1a72324208f3fcfe4f957b9fc8b9e81bf3fed584deb0b40f0e2af63cd0e2afa8f408d06f14bdba899ac638ac619f646badf8e72de246c052 + languageName: node + linkType: hard + +"@types/nodemailer@npm:^7.0.0": + version: 7.0.11 + resolution: "@types/nodemailer@npm:7.0.11" dependencies: "@types/node": "npm:*" - checksum: 10/bd090c9a81f15ee5e1e2123de1004593bacc24d385460dd56c51ec657d61dc1cfd4f44fc71baac060a1abcb487aef5027509e0afd646e7118d7a8a13a95bad9d + checksum: 10/a657038e919603f1e7136bd47c631c4ef2857576f57d5024d37a2581d4993ef352681e9264604747db7055347e19818add8ab9f964be0f116dd437f5d31ff0e5 languageName: node linkType: hard @@ -21362,6 +20141,13 @@ __metadata: languageName: node linkType: hard +"@types/parse5@npm:^6.0.0": + version: 6.0.3 + resolution: "@types/parse5@npm:6.0.3" + checksum: 10/834d40c9b1a8a99a9574b0b3f6629cf48adcff2eda01a35d701f1de5dcf46ce24223684647890aba9f985d6c801b233f878168683de0ae425940403c383fba8f + languageName: node + linkType: hard + "@types/passport-auth0@npm:^1.0.5": version: 1.0.9 resolution: "@types/passport-auth0@npm:1.0.9" @@ -21430,23 +20216,23 @@ __metadata: languageName: node linkType: hard -"@types/pg-pool@npm:2.0.6": - version: 2.0.6 - resolution: "@types/pg-pool@npm:2.0.6" +"@types/pg-pool@npm:2.0.7": + version: 2.0.7 + resolution: "@types/pg-pool@npm:2.0.7" dependencies: "@types/pg": "npm:*" - checksum: 10/cc54ce97115effc982bd052f79901a78215e76554aca0ecc92e78eb907e4fb2962924039369cd9aaf48075f1637593ce14647c62d3a2eb03789ce5d1c6df750b + checksum: 10/b2ac51f1e98cd97ef8ee9c09f4db6bb369dfa406dc41533b13a3b7c6e3a5c8c1d52ee139f8bc453b5b5c0125d1fedea610c230696a722ec9176076455e6f267a languageName: node linkType: hard -"@types/pg@npm:*, @types/pg@npm:8.15.4": - version: 8.15.4 - resolution: "@types/pg@npm:8.15.4" +"@types/pg@npm:*, @types/pg@npm:8.15.6": + version: 8.15.6 + resolution: "@types/pg@npm:8.15.6" dependencies: "@types/node": "npm:*" pg-protocol: "npm:*" pg-types: "npm:^2.2.0" - checksum: 10/dd9203ae6732acad4892513fc99eb2bc699935a95b62e9fdbdcc6d1a90f63881b5fd89d4cac1729790ab3d0bb7c64130b144c65abef34e6bbed063ff43a739a6 + checksum: 10/4bc1bb274e0fc105be93e3a9cc8c9aa57fc50b78ed78a56348468157332daaecd71fcab762ee620c766510ffbc7018b56ca394787d6d41ff1726b152770aa532 languageName: node linkType: hard @@ -21471,6 +20257,13 @@ __metadata: languageName: node linkType: hard +"@types/prismjs@npm:^1.0.0": + version: 1.26.5 + resolution: "@types/prismjs@npm:1.26.5" + checksum: 10/617099479db9550119d0f84272dc79d64b2cf3e0d7a17167fe740d55fdf0f155697d935409464392d164e62080c2c88d649cf4bc4fdd30a87127337536657277 + languageName: node + linkType: hard + "@types/promise.allsettled@npm:^1.0.3": version: 1.0.6 resolution: "@types/promise.allsettled@npm:1.0.6" @@ -21485,6 +20278,15 @@ __metadata: languageName: node linkType: hard +"@types/proper-lockfile@npm:^4": + version: 4.1.4 + resolution: "@types/proper-lockfile@npm:4.1.4" + dependencies: + "@types/retry": "npm:*" + checksum: 10/b0d1b8e84a563b2c5f869f7ff7542b1d83dec03d1c9d980847cbb189865f44b4a854673cdde59767e41bcb8c31932e613ac43822d358a6f8eede6b79ccfceb1d + languageName: node + linkType: hard + "@types/protocol-buffers-schema@npm:^3.4.3": version: 3.4.3 resolution: "@types/protocol-buffers-schema@npm:3.4.3" @@ -21495,9 +20297,9 @@ __metadata: linkType: hard "@types/qs@npm:*, @types/qs@npm:^6.9.6": - version: 6.14.0 - resolution: "@types/qs@npm:6.14.0" - checksum: 10/1909205514d22b3cbc7c2314e2bd8056d5f05dfb21cf4377f0730ee5e338ea19957c41735d5e4806c746176563f50005bbab602d8358432e25d900bdf4970826 + version: 6.15.0 + resolution: "@types/qs@npm:6.15.0" + checksum: 10/871162881f1c83e61d0c8c243c65549be5dddf33a6911f3324edeebd4087207b1174644da9a3afaa20cf494c5288d2a1ece09e10e4822f755339f14a05c339ea languageName: node linkType: hard @@ -21527,11 +20329,11 @@ __metadata: linkType: hard "@types/react-grid-layout@npm:^1.3.2": - version: 1.3.5 - resolution: "@types/react-grid-layout@npm:1.3.5" + version: 1.3.6 + resolution: "@types/react-grid-layout@npm:1.3.6" dependencies: "@types/react": "npm:*" - checksum: 10/21599054dfa977ed8445b1ab3198a842531cb36bd620564c3f8a469688cbea051149eb644a99b2f8f6d02f8d9909a6145668f385781b5647601e9663de216946 + checksum: 10/80b9cd6ab14f5cd32a62451544cbbeff32d6c8c3fefd6e04f974429a0b1b0d2aded1f4767ae4ce9cd4caa395e514531f5e5d6a3c3e93dba4906a907f8d77ee21 languageName: node linkType: hard @@ -21673,6 +20475,13 @@ __metadata: languageName: node linkType: hard +"@types/retry@npm:*": + version: 0.12.5 + resolution: "@types/retry@npm:0.12.5" + checksum: 10/3fb6bf91835ca0eb2987567d6977585235a7567f8aeb38b34a8bb7bbee57ac050ed6f04b9998cda29701b8c893f5dfe315869bc54ac17e536c9235637fe351a2 + languageName: node + linkType: hard + "@types/retry@npm:0.12.0": version: 0.12.0 resolution: "@types/retry@npm:0.12.0" @@ -21687,24 +20496,6 @@ __metadata: languageName: node linkType: hard -"@types/rollup-plugin-peer-deps-external@npm:^2.2.0": - version: 2.2.6 - resolution: "@types/rollup-plugin-peer-deps-external@npm:2.2.6" - peerDependencies: - rollup: "*" - checksum: 10/9fcee30d60d9d8de0f4bbf7694c254a18798b0ff415563a60fb2ed8c6c46423c64e1a977e0344b8393c26221dbd6e74d9e872c2694b518fcf149ba85d94e7eac - languageName: node - linkType: hard - -"@types/rollup-plugin-postcss@npm:^3.1.4": - version: 3.1.4 - resolution: "@types/rollup-plugin-postcss@npm:3.1.4" - dependencies: - rollup-plugin-postcss: "npm:*" - checksum: 10/a94119c43db77ad10a96f34a4190b678e87fe63bdedfb2b989b64a9c0e15680c034d728daa4ade9cfd31d2fdc48827c79ff69ad2599d0b323c2c321896866cf5 - languageName: node - linkType: hard - "@types/sarif@npm:^2.1.4": version: 2.1.5 resolution: "@types/sarif@npm:2.1.5" @@ -21719,20 +20510,20 @@ __metadata: languageName: node linkType: hard -"@types/semver@npm:^7.1.0, @types/semver@npm:^7.3.12": - version: 7.7.0 - resolution: "@types/semver@npm:7.7.0" - checksum: 10/ee4514c6c852b1c38f951239db02f9edeea39f5310fad9396a00b51efa2a2d96b3dfca1ae84c88181ea5b7157c57d32d7ef94edacee36fbf975546396b85ba5b +"@types/semver@npm:^7, @types/semver@npm:^7.1.0": + version: 7.7.1 + resolution: "@types/semver@npm:7.7.1" + checksum: 10/8f09e7e6ca3ded67d78ba7a8f7535c8d9cf8ced83c52e7f3ac3c281fe8c689c3fe475d199d94390dc04fc681d51f2358b430bb7b2e21c62de24f2bee2c719068 languageName: node linkType: hard -"@types/send@npm:*": - version: 0.17.1 - resolution: "@types/send@npm:0.17.1" +"@types/send@npm:*, @types/send@npm:<1": + version: 0.17.6 + resolution: "@types/send@npm:0.17.6" dependencies: "@types/mime": "npm:^1" "@types/node": "npm:*" - checksum: 10/6420837887858f7aa82f2c0272f73edb42385bd0978f43095e83590a405d86c8cc6d918c30b2d542f1d8bddc9f3d16c2e8fdfca936940de71b97c45f228d1896 + checksum: 10/4948ab32ab84a81a0073f8243dd48ee766bc80608d5391060360afd1249f83c08a7476f142669ac0b0b8831c89d909a88bcb392d1b39ee48b276a91b50f3d8d1 languageName: node linkType: hard @@ -21754,14 +20545,14 @@ __metadata: languageName: node linkType: hard -"@types/serve-static@npm:*, @types/serve-static@npm:^1.15.5": - version: 1.15.7 - resolution: "@types/serve-static@npm:1.15.7" +"@types/serve-static@npm:^1, @types/serve-static@npm:^1.15.5": + version: 1.15.10 + resolution: "@types/serve-static@npm:1.15.10" dependencies: "@types/http-errors": "npm:*" "@types/node": "npm:*" - "@types/send": "npm:*" - checksum: 10/c5a7171d5647f9fbd096ed1a26105759f3153ccf683824d99fee4c7eb9cde2953509621c56a070dd9fb1159e799e86d300cbe4e42245ebc5b0c1767e8ca94a67 + "@types/send": "npm:<1" + checksum: 10/d9be72487540b9598e7d77260d533f241eb2e5db5181bb885ef2d6bc4592dad1c9e8c0e27f465d59478b2faf90edd2d535e834f20fbd9dd3c0928d43dc486404 languageName: node linkType: hard @@ -21774,10 +20565,10 @@ __metadata: languageName: node linkType: hard -"@types/shimmer@npm:^1.2.0": - version: 1.2.0 - resolution: "@types/shimmer@npm:1.2.0" - checksum: 10/f081a31d826ce7bfe8cc7ba8129d2b1dffae44fd580eba4fcf741237646c4c2494ae6de2cada4b7713d138f35f4bc512dbf01311d813dee82020f97d7d8c491c +"@types/shell-quote@npm:^1.7.5": + version: 1.7.5 + resolution: "@types/shell-quote@npm:1.7.5" + checksum: 10/32b4d697c7d23dbadf40713692c47f1595f083a3b3deea76cb18e30a05d197aa9205d2b87f6d92edb60cda120b51e35d32bda96ed9b0a7e32921eed2deb4559e languageName: node linkType: hard @@ -21797,13 +20588,6 @@ __metadata: languageName: node linkType: hard -"@types/sizzle@npm:*": - version: 2.3.2 - resolution: "@types/sizzle@npm:2.3.2" - checksum: 10/3247c553400c3daab142682520249d818c473a8cf77c648c1e3e6e9fc9c477f5423e2b4112dd3c564c65f96db7e2b5be8dbbc22acf4da6fa0c5a149edd2feda5 - languageName: node - linkType: hard - "@types/sockjs@npm:^0.3.36": version: 0.3.36 resolution: "@types/sockjs@npm:0.3.36" @@ -21813,13 +20597,6 @@ __metadata: languageName: node linkType: hard -"@types/source-list-map@npm:*": - version: 0.1.6 - resolution: "@types/source-list-map@npm:0.1.6" - checksum: 10/9cd294c121f1562062de5d241fe4d10780b1131b01c57434845fe50968e9dcf67ede444591c2b1ad6d3f9b6bc646ac02cc8f51a3577c795f9c64cf4573dcc6b1 - languageName: node - linkType: hard - "@types/ssh2-streams@npm:*": version: 0.1.8 resolution: "@types/ssh2-streams@npm:0.1.8" @@ -21839,10 +20616,10 @@ __metadata: languageName: node linkType: hard -"@types/stack-utils@npm:^2.0.0": - version: 2.0.0 - resolution: "@types/stack-utils@npm:2.0.0" - checksum: 10/b3fbae25b073116977ecb5c67d22f14567b51a7792403b0bf46e5de8f29bde3bd4ec1626afb22065495ca7f1c699c8bd66720050c94b8f8f9bcefbee79d161fd +"@types/stack-utils@npm:^2.0.3": + version: 2.0.3 + resolution: "@types/stack-utils@npm:2.0.3" + checksum: 10/72576cc1522090fe497337c2b99d9838e320659ac57fa5560fcbdcbafcf5d0216c6b3a0a8a4ee4fdb3b1f5e3420aa4f6223ab57b82fef3578bec3206425c6cf5 languageName: node linkType: hard @@ -21890,31 +20667,12 @@ __metadata: languageName: node linkType: hard -"@types/svgo@npm:^2.6.2": - version: 2.6.4 - resolution: "@types/svgo@npm:2.6.4" - dependencies: - "@types/node": "npm:*" - checksum: 10/9632b350949677fa68d6f13b4d45495a4af3108bb5f020a7257ae5a883e20b9efc0fada3bc3e012f215be312fabe5a28485fffaff5afd6da4daa8cb4fe5b04a2 - languageName: node - linkType: hard - -"@types/swagger-ui-react@npm:^4.18.0": - version: 4.18.3 - resolution: "@types/swagger-ui-react@npm:4.18.3" +"@types/swagger-ui-react@npm:^5.0.0": + version: 5.18.0 + resolution: "@types/swagger-ui-react@npm:5.18.0" dependencies: "@types/react": "npm:*" - checksum: 10/4927314f1b0d68edf200ef15bca7555f12ec1bb8cc699fa397ef2f4e1e1873d17880b663bbc6d70e30149e50c3e7055178c1e0aad110520dfb31d781ab326dcb - languageName: node - linkType: hard - -"@types/tar@npm:^6.1.1": - version: 6.1.13 - resolution: "@types/tar@npm:6.1.13" - dependencies: - "@types/node": "npm:*" - minipass: "npm:^4.0.0" - checksum: 10/d325223cf90399fd03f366d0eabe2383e75e550b3e40a006d5f062d006b894a475cd7c0968d258a8eb8eae5df30b6e7f4607d493a474f89134bbff65362b77ed + checksum: 10/002bb3e38eb5917f5da942affa357811ccb078816bf53ba9007e43603f9b9a839eb930380b08051b9cc9a95ca2ebec06599a825e4d0542e436ea2c2027fc3a0a languageName: node linkType: hard @@ -21936,15 +20694,6 @@ __metadata: languageName: node linkType: hard -"@types/terser-webpack-plugin@npm:^5.0.4": - version: 5.2.0 - resolution: "@types/terser-webpack-plugin@npm:5.2.0" - dependencies: - terser-webpack-plugin: "npm:*" - checksum: 10/475b0f160c9f83641255f6516b69d7908b9e3e8e8ab653f3a257690249f9614f9386c0897eba6e4e35182ec0d83f57454d62fb94b38ae7c171891641350072ee - languageName: node - linkType: hard - "@types/through@npm:*": version: 0.0.30 resolution: "@types/through@npm:0.0.30" @@ -22010,6 +20759,13 @@ __metadata: languageName: node linkType: hard +"@types/use-sync-external-store@npm:^1.0.0": + version: 1.5.0 + resolution: "@types/use-sync-external-store@npm:1.5.0" + checksum: 10/39e5be8dc2cca080b490f2f79fed4381ae7eebee3f981208e359856733eafb2479d229db07a552f6c99fe0b5c09b3e46a3e6a870e00a88b50f3e690e73d2649b + languageName: node + linkType: hard + "@types/vinyl@npm:^2.0.4": version: 2.0.6 resolution: "@types/vinyl@npm:2.0.6" @@ -22027,17 +20783,6 @@ __metadata: languageName: node linkType: hard -"@types/webpack-sources@npm:^3.2.3": - version: 3.2.3 - resolution: "@types/webpack-sources@npm:3.2.3" - dependencies: - "@types/node": "npm:*" - "@types/source-list-map": "npm:*" - source-map: "npm:^0.7.3" - checksum: 10/7b557f242efaa10e4e3e18cc4171a0c98e22898570caefdd4f7b076fe8534b5abfac92c953c6604658dcb7218507f970230352511840fe9fdea31a9af3b9a906 - languageName: node - linkType: hard - "@types/webpack@npm:^5.28.0": version: 5.28.5 resolution: "@types/webpack@npm:5.28.5" @@ -22049,6 +20794,13 @@ __metadata: languageName: node linkType: hard +"@types/wrap-ansi@npm:^3.0.0": + version: 3.0.0 + resolution: "@types/wrap-ansi@npm:3.0.0" + checksum: 10/8aa644946ca4e859668c36b8e2bcf2ac4bdee59dac760414730ea57be8a93ae9166ebd40a088f2ab714843aaea2a2a67f0e6e6ec11cfc9c8701b2466ca1c4089 + languageName: node + linkType: hard + "@types/ws@npm:*, @types/ws@npm:^8.0.0, @types/ws@npm:^8.5.10": version: 8.18.1 resolution: "@types/ws@npm:8.18.1" @@ -22074,12 +20826,12 @@ __metadata: languageName: node linkType: hard -"@types/yargs@npm:^17.0.8": - version: 17.0.12 - resolution: "@types/yargs@npm:17.0.12" +"@types/yargs@npm:^17.0.33, @types/yargs@npm:^17.0.8": + version: 17.0.35 + resolution: "@types/yargs@npm:17.0.35" dependencies: "@types/yargs-parser": "npm:*" - checksum: 10/ffbbfad0c75cc058e0518f202e3651b9fb60c7c1325240cc72ac0a022da746a759ba3c6e0099152076fed5012fb694eb4685e4520c04dc8399c22bb81221bff5 + checksum: 10/47bcd4476a4194ea11617ea71cba8a1eddf5505fc39c44336c1a08d452a0de4486aedbc13f47a017c8efbcb5a8aa358d976880663732ebcbc6dbcbbecadb0581 languageName: node linkType: hard @@ -22114,62 +20866,51 @@ __metadata: linkType: hard "@typescript-eslint/eslint-plugin@npm:^8.17.0": - version: 8.29.1 - resolution: "@typescript-eslint/eslint-plugin@npm:8.29.1" + version: 8.57.2 + resolution: "@typescript-eslint/eslint-plugin@npm:8.57.2" dependencies: - "@eslint-community/regexpp": "npm:^4.10.0" - "@typescript-eslint/scope-manager": "npm:8.29.1" - "@typescript-eslint/type-utils": "npm:8.29.1" - "@typescript-eslint/utils": "npm:8.29.1" - "@typescript-eslint/visitor-keys": "npm:8.29.1" - graphemer: "npm:^1.4.0" - ignore: "npm:^5.3.1" + "@eslint-community/regexpp": "npm:^4.12.2" + "@typescript-eslint/scope-manager": "npm:8.57.2" + "@typescript-eslint/type-utils": "npm:8.57.2" + "@typescript-eslint/utils": "npm:8.57.2" + "@typescript-eslint/visitor-keys": "npm:8.57.2" + ignore: "npm:^7.0.5" natural-compare: "npm:^1.4.0" - ts-api-utils: "npm:^2.0.1" + ts-api-utils: "npm:^2.4.0" peerDependencies: - "@typescript-eslint/parser": ^8.0.0 || ^8.0.0-alpha.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10/0568894f0ea50e67622605eb347d4e26a41571ad06234478ac695a097e9b3ff6252d6d60034853d7a6b8ec644b5c1d354be21075d691173dd7f2fbecb1102674 + "@typescript-eslint/parser": ^8.57.2 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.0.0" + checksum: 10/0735281c26b1e9b3b9ccce3b872ef9eedcfbbffa6b766470de55735cd7a796b064376189378a65c19c62fa59f24187cecb563fb56d194129673be5cd8d3949e9 languageName: node linkType: hard "@typescript-eslint/parser@npm:^8.16.0": - version: 8.29.1 - resolution: "@typescript-eslint/parser@npm:8.29.1" + version: 8.57.2 + resolution: "@typescript-eslint/parser@npm:8.57.2" dependencies: - "@typescript-eslint/scope-manager": "npm:8.29.1" - "@typescript-eslint/types": "npm:8.29.1" - "@typescript-eslint/typescript-estree": "npm:8.29.1" - "@typescript-eslint/visitor-keys": "npm:8.29.1" - debug: "npm:^4.3.4" + "@typescript-eslint/scope-manager": "npm:8.57.2" + "@typescript-eslint/types": "npm:8.57.2" + "@typescript-eslint/typescript-estree": "npm:8.57.2" + "@typescript-eslint/visitor-keys": "npm:8.57.2" + debug: "npm:^4.4.3" peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10/effb4cc24e375e4229e711b3ea8611a205bf81964cb487f518dd4a7d6cecde7324201ce4e2c3cd420e0ce7649899294307da2cd77f145af4efef3a0292189f20 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.0.0" + checksum: 10/c9a8a03767ac1b1e8d0bee938fb636b30b7043a776d20353b37be32011ed2b174aa906c27b574fe90bbc48d0235320a4339fb5868f02f5a262a58531e244e1f8 languageName: node linkType: hard -"@typescript-eslint/project-service@npm:8.35.0": - version: 8.35.0 - resolution: "@typescript-eslint/project-service@npm:8.35.0" +"@typescript-eslint/project-service@npm:8.57.2": + version: 8.57.2 + resolution: "@typescript-eslint/project-service@npm:8.57.2" dependencies: - "@typescript-eslint/tsconfig-utils": "npm:^8.35.0" - "@typescript-eslint/types": "npm:^8.35.0" - debug: "npm:^4.3.4" + "@typescript-eslint/tsconfig-utils": "npm:^8.57.2" + "@typescript-eslint/types": "npm:^8.57.2" + debug: "npm:^4.4.3" peerDependencies: - typescript: ">=4.8.4 <5.9.0" - checksum: 10/a9419da92231aa27f75078fcffab1d02398b50fdb7d5399775a414ba02570682b4b60cdfafb544a021b0dc2372f029c4195f5ae17c50deb11c25661b2ac18a74 - languageName: node - linkType: hard - -"@typescript-eslint/scope-manager@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/scope-manager@npm:5.62.0" - dependencies: - "@typescript-eslint/types": "npm:5.62.0" - "@typescript-eslint/visitor-keys": "npm:5.62.0" - checksum: 10/e827770baa202223bc0387e2fd24f630690809e460435b7dc9af336c77322290a770d62bd5284260fa881c86074d6a9fd6c97b07382520b115f6786b8ed499da + typescript: ">=4.8.4 <6.0.0" + checksum: 10/e7f47d5394c6ca3c92fa373bd3a149ad13549944d0d7a12f0ddef6dc921b017cf283876bb272b1f6c4f90e6c84bac7b140ac08acee3a3f26dfd8b43556e0949e languageName: node linkType: hard @@ -22183,44 +20924,38 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.29.1": - version: 8.29.1 - resolution: "@typescript-eslint/scope-manager@npm:8.29.1" +"@typescript-eslint/scope-manager@npm:8.57.2, @typescript-eslint/scope-manager@npm:^8.56.0": + version: 8.57.2 + resolution: "@typescript-eslint/scope-manager@npm:8.57.2" dependencies: - "@typescript-eslint/types": "npm:8.29.1" - "@typescript-eslint/visitor-keys": "npm:8.29.1" - checksum: 10/33a02f490b53436729f5ca2e6e0c5b8db72adb455274e5de43bdaada21033e7941aed1d92653321991e186af77f7794dc0ac35d2fce891cdf65a6d3fb192249e + "@typescript-eslint/types": "npm:8.57.2" + "@typescript-eslint/visitor-keys": "npm:8.57.2" + checksum: 10/2c1498e25481ee6b1de5c6cbf8fbbdfa9e5e940a91ddd12687dd17d68de5df81c286861f0378282fbe49172dd44094bfeb5b63f80faf37e54dce8fd2ddc7f733 languageName: node linkType: hard -"@typescript-eslint/tsconfig-utils@npm:8.35.0, @typescript-eslint/tsconfig-utils@npm:^8.35.0": - version: 8.35.0 - resolution: "@typescript-eslint/tsconfig-utils@npm:8.35.0" +"@typescript-eslint/tsconfig-utils@npm:8.57.2, @typescript-eslint/tsconfig-utils@npm:^8.57.2": + version: 8.57.2 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.57.2" peerDependencies: - typescript: ">=4.8.4 <5.9.0" - checksum: 10/4160928313ccbe8b169a009b9c1220826c7df7aab427f960c31f3b838931bc7a121ebee8040118481e4528e2e3cf1b26da047c6ac1d802ecff2ef7206026ea6b + typescript: ">=4.8.4 <6.0.0" + checksum: 10/2a6e27d541fd0071e5ca14116ba210eab77a8f74cedda32dfce4acce951632971b066261e4b151d34dd0cfad357cb4fd2f7d0b38c9e82a155c226e5c12bd41b3 languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:8.29.1": - version: 8.29.1 - resolution: "@typescript-eslint/type-utils@npm:8.29.1" +"@typescript-eslint/type-utils@npm:8.57.2": + version: 8.57.2 + resolution: "@typescript-eslint/type-utils@npm:8.57.2" dependencies: - "@typescript-eslint/typescript-estree": "npm:8.29.1" - "@typescript-eslint/utils": "npm:8.29.1" - debug: "npm:^4.3.4" - ts-api-utils: "npm:^2.0.1" + "@typescript-eslint/types": "npm:8.57.2" + "@typescript-eslint/typescript-estree": "npm:8.57.2" + "@typescript-eslint/utils": "npm:8.57.2" + debug: "npm:^4.4.3" + ts-api-utils: "npm:^2.4.0" peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10/3774d6fb32c058b3fa607480e5603fdd2919e07d8babbc00aa703f31c6fd6f7fbc25c25ff246e7e6ac6b77ad0e0b66838a7136aebc31503a58fbe509f68ab2f4 - languageName: node - linkType: hard - -"@typescript-eslint/types@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/types@npm:5.62.0" - checksum: 10/24e8443177be84823242d6729d56af2c4b47bfc664dd411a1d730506abf2150d6c31bdefbbc6d97c8f91043e3a50e0c698239dcb145b79bb6b0c34469aaf6c45 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.0.0" + checksum: 10/9df6b587143b2dd3f9fc17dab74e877013cc3ce21a7266af6d85bef05b4602103aa2c0d612a69f479225b67f089dad2a92f8ebbd474a9df0a256caeaa5aa165b languageName: node linkType: hard @@ -22231,35 +20966,10 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/types@npm:8.29.1": - version: 8.29.1 - resolution: "@typescript-eslint/types@npm:8.29.1" - checksum: 10/99ff59e7af3728858af9b7fdc0165955bcf5cd2eefeaafabfbdd7951fbc8ad869cbb7bed7bcbed6c3c50a8661333b33364dc1de0a0c8f3c64d5882339a5d30dd - languageName: node - linkType: hard - -"@typescript-eslint/types@npm:8.35.0, @typescript-eslint/types@npm:^8.35.0": - version: 8.35.0 - resolution: "@typescript-eslint/types@npm:8.35.0" - checksum: 10/34b5e6da2c59ea84cd528608fff0cc14b102fd23f5517dfee4ef38c9372861d80b5bf92445c9679674f0a4f8dc4ded5066c1bca2bc5569c47515f94568984f35 - languageName: node - linkType: hard - -"@typescript-eslint/typescript-estree@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/typescript-estree@npm:5.62.0" - dependencies: - "@typescript-eslint/types": "npm:5.62.0" - "@typescript-eslint/visitor-keys": "npm:5.62.0" - debug: "npm:^4.3.4" - globby: "npm:^11.1.0" - is-glob: "npm:^4.0.3" - semver: "npm:^7.3.7" - tsutils: "npm:^3.21.0" - peerDependenciesMeta: - typescript: - optional: true - checksum: 10/06c975eb5f44b43bd19fadc2e1023c50cf87038fe4c0dd989d4331c67b3ff509b17fa60a3251896668ab4d7322bdc56162a9926971218d2e1a1874d2bef9a52e +"@typescript-eslint/types@npm:8.57.2, @typescript-eslint/types@npm:^8.57.2": + version: 8.57.2 + resolution: "@typescript-eslint/types@npm:8.57.2" + checksum: 10/6d30ff13c8ebe01ba8bcce5f1a5ba52f2c6546056e056f50e010394461d2fffda46dad2a4ebdff59c179873abd274242d8f6a8246c60d7ce244d02ad87bb07f8 languageName: node linkType: hard @@ -22282,74 +20992,37 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:8.29.1": - version: 8.29.1 - resolution: "@typescript-eslint/typescript-estree@npm:8.29.1" +"@typescript-eslint/typescript-estree@npm:8.57.2, @typescript-eslint/typescript-estree@npm:^8.23.0": + version: 8.57.2 + resolution: "@typescript-eslint/typescript-estree@npm:8.57.2" dependencies: - "@typescript-eslint/types": "npm:8.29.1" - "@typescript-eslint/visitor-keys": "npm:8.29.1" - debug: "npm:^4.3.4" - fast-glob: "npm:^3.3.2" - is-glob: "npm:^4.0.3" - minimatch: "npm:^9.0.4" - semver: "npm:^7.6.0" - ts-api-utils: "npm:^2.0.1" + "@typescript-eslint/project-service": "npm:8.57.2" + "@typescript-eslint/tsconfig-utils": "npm:8.57.2" + "@typescript-eslint/types": "npm:8.57.2" + "@typescript-eslint/visitor-keys": "npm:8.57.2" + debug: "npm:^4.4.3" + minimatch: "npm:^10.2.2" + semver: "npm:^7.7.3" + tinyglobby: "npm:^0.2.15" + ts-api-utils: "npm:^2.4.0" peerDependencies: - typescript: ">=4.8.4 <5.9.0" - checksum: 10/dded2ebe4c3287443000e3b825e673d0eddb7c48eb2d373c5b7059ea7dbbeba488d7f1de2e42ed7a9299ccff926a65821f2b5594022b49564026ba01c0cc07ab + typescript: ">=4.8.4 <6.0.0" + checksum: 10/85d3f2799ee3915b4a06014d90db4aab28c0c0377b40aced5b49acb5f038862e5447f6c355d85368a8f587820f7840e945fa8a9d5347234f1fb070a97c2a9c1d languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:^8.23.0": - version: 8.35.0 - resolution: "@typescript-eslint/typescript-estree@npm:8.35.0" +"@typescript-eslint/utils@npm:8.57.2, @typescript-eslint/utils@npm:^6.0.0 || ^7.0.0 || ^8.0.0, @typescript-eslint/utils@npm:^8.48.0, @typescript-eslint/utils@npm:^8.56.0": + version: 8.57.2 + resolution: "@typescript-eslint/utils@npm:8.57.2" dependencies: - "@typescript-eslint/project-service": "npm:8.35.0" - "@typescript-eslint/tsconfig-utils": "npm:8.35.0" - "@typescript-eslint/types": "npm:8.35.0" - "@typescript-eslint/visitor-keys": "npm:8.35.0" - debug: "npm:^4.3.4" - fast-glob: "npm:^3.3.2" - is-glob: "npm:^4.0.3" - minimatch: "npm:^9.0.4" - semver: "npm:^7.6.0" - ts-api-utils: "npm:^2.1.0" + "@eslint-community/eslint-utils": "npm:^4.9.1" + "@typescript-eslint/scope-manager": "npm:8.57.2" + "@typescript-eslint/types": "npm:8.57.2" + "@typescript-eslint/typescript-estree": "npm:8.57.2" peerDependencies: - typescript: ">=4.8.4 <5.9.0" - checksum: 10/4dff7c5a8853c8f4e30d35565c62d3ad5bf8445309bd465d94e9bca725853012bb9f58896a04207c30e10b6669511caac8c0f080ed781c93a3db81d5808195aa - languageName: node - linkType: hard - -"@typescript-eslint/utils@npm:8.29.1, @typescript-eslint/utils@npm:^6.0.0 || ^7.0.0 || ^8.0.0, @typescript-eslint/utils@npm:^8.8.1": - version: 8.29.1 - resolution: "@typescript-eslint/utils@npm:8.29.1" - dependencies: - "@eslint-community/eslint-utils": "npm:^4.4.0" - "@typescript-eslint/scope-manager": "npm:8.29.1" - "@typescript-eslint/types": "npm:8.29.1" - "@typescript-eslint/typescript-estree": "npm:8.29.1" - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10/1d2c85c97a39e063fe490c0cdb6513716e4735bda0bc937475b44d9224074a5070f888dfed1e75f4a91c8f4d825b44fce90b685e07bda391dfeb2553b9b39a5a - languageName: node - linkType: hard - -"@typescript-eslint/utils@npm:^5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/utils@npm:5.62.0" - dependencies: - "@eslint-community/eslint-utils": "npm:^4.2.0" - "@types/json-schema": "npm:^7.0.9" - "@types/semver": "npm:^7.3.12" - "@typescript-eslint/scope-manager": "npm:5.62.0" - "@typescript-eslint/types": "npm:5.62.0" - "@typescript-eslint/typescript-estree": "npm:5.62.0" - eslint-scope: "npm:^5.1.1" - semver: "npm:^7.3.7" - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: 10/15ef13e43998a082b15f85db979f8d3ceb1f9ce4467b8016c267b1738d5e7cdb12aa90faf4b4e6dd6486c236cf9d33c463200465cf25ff997dbc0f12358550a1 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.0.0" + checksum: 10/48851aa53b403e67dce7504859f3e5a9e008d43fa8c98dc2b0fc900980c1f77dda60648f957fd69a78cea44e0a94d1d7e807fcbc30ffca3fb688d04be2acd898 languageName: node linkType: hard @@ -22367,16 +21040,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/visitor-keys@npm:5.62.0" - dependencies: - "@typescript-eslint/types": "npm:5.62.0" - eslint-visitor-keys: "npm:^3.3.0" - checksum: 10/dc613ab7569df9bbe0b2ca677635eb91839dfb2ca2c6fa47870a5da4f160db0b436f7ec0764362e756d4164e9445d49d5eb1ff0b87f4c058946ae9d8c92eb388 - languageName: node - linkType: hard - "@typescript-eslint/visitor-keys@npm:7.18.0": version: 7.18.0 resolution: "@typescript-eslint/visitor-keys@npm:7.18.0" @@ -22387,29 +21050,19 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.29.1": - version: 8.29.1 - resolution: "@typescript-eslint/visitor-keys@npm:8.29.1" +"@typescript-eslint/visitor-keys@npm:8.57.2": + version: 8.57.2 + resolution: "@typescript-eslint/visitor-keys@npm:8.57.2" dependencies: - "@typescript-eslint/types": "npm:8.29.1" - eslint-visitor-keys: "npm:^4.2.0" - checksum: 10/788290c369c13403692d857e0a464b5c2223e1fef28c717b7dbc61140d33697ce43c7d1a7cb7ac585f2012fc702ce2ec489363d34bf566303d3c48fa494803c5 + "@typescript-eslint/types": "npm:8.57.2" + eslint-visitor-keys: "npm:^5.0.0" + checksum: 10/8059dcb05694b31b3372f022e25493c4a3fd1a9952720bcb3eac1911828f0b81b3f8aeb11a30dc646880e16396317512747a9b2afee059c0472ea5c4d4546d29 languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.35.0": - version: 8.35.0 - resolution: "@typescript-eslint/visitor-keys@npm:8.35.0" - dependencies: - "@typescript-eslint/types": "npm:8.35.0" - eslint-visitor-keys: "npm:^4.2.1" - checksum: 10/c0acb13aac3a2be5e82844f7d2e86137347efdd04661dbf9fa69ef04a19dd2f1eb2f1eb6bfbfbaada78a46884308d2c0e0b5d0d1a094c84f2dfb670b67ac2b3b - languageName: node - linkType: hard - -"@uiw/codemirror-extensions-basic-setup@npm:4.24.2": - version: 4.24.2 - resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.24.2" +"@uiw/codemirror-extensions-basic-setup@npm:4.25.8": + version: 4.25.8 + resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.25.8" dependencies: "@codemirror/autocomplete": "npm:^6.0.0" "@codemirror/commands": "npm:^6.0.0" @@ -22426,19 +21079,19 @@ __metadata: "@codemirror/search": ">=6.0.0" "@codemirror/state": ">=6.0.0" "@codemirror/view": ">=6.0.0" - checksum: 10/077f64b4bbb6178038d30a3cb2961c0ff89c0836b73210d0ac3fca828c3ba8fbce61e42635ec2e2997cdffff5472b75ad58541c4e62f98d52b83d475da0c56eb + checksum: 10/a8d83465f9f3393b6e95d98ae7f3616ad57f819bce64224831d3db19647524538fc013973074a63551afa69daad9a8ab05f2e5c7441db7e30e722495d7e991d3 languageName: node linkType: hard "@uiw/react-codemirror@npm:^4.9.3": - version: 4.24.2 - resolution: "@uiw/react-codemirror@npm:4.24.2" + version: 4.25.8 + resolution: "@uiw/react-codemirror@npm:4.25.8" dependencies: "@babel/runtime": "npm:^7.18.6" "@codemirror/commands": "npm:^6.1.0" "@codemirror/state": "npm:^6.1.1" "@codemirror/theme-one-dark": "npm:^6.0.0" - "@uiw/codemirror-extensions-basic-setup": "npm:4.24.2" + "@uiw/codemirror-extensions-basic-setup": "npm:4.25.8" codemirror: "npm:^6.0.0" peerDependencies: "@babel/runtime": ">=7.11.0" @@ -22448,14 +21101,149 @@ __metadata: codemirror: ">=6.0.0" react: ">=17.0.0" react-dom: ">=17.0.0" - checksum: 10/f0816fcf40c451c8bc15319d62f5b8d62a04c12c2bc053569212b31edba7c7164e45fd161ebb683a03fd5a50697b91a6ac4cab2d2d1a45da5f2626d959c399a0 + checksum: 10/8c974e22dad1ad6231f33f7db42cd5b68caaf9bea545539b06b8a89dda3427eebadf47c8f48ee0d74cdf5a25000a8fcc02bac9fe560b624955eedf1f9bb47a85 languageName: node linkType: hard -"@ungap/structured-clone@npm:^1.2.0": - version: 1.2.0 - resolution: "@ungap/structured-clone@npm:1.2.0" - checksum: 10/c6fe89a505e513a7592e1438280db1c075764793a2397877ff1351721fe8792a966a5359769e30242b3cd023f2efb9e63ca2ca88019d73b564488cc20e3eab12 +"@ungap/structured-clone@npm:^1.2.0, @ungap/structured-clone@npm:^1.3.0": + version: 1.3.0 + resolution: "@ungap/structured-clone@npm:1.3.0" + checksum: 10/80d6910946f2b1552a2406650051c91bbd1f24a6bf854354203d84fe2714b3e8ce4618f49cc3410494173a1c1e8e9777372fe68dce74bd45faf0a7a1a6ccf448 + languageName: node + linkType: hard + +"@unrs/resolver-binding-android-arm-eabi@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-android-arm-eabi@npm:1.11.1" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@unrs/resolver-binding-android-arm64@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-android-arm64@npm:1.11.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@unrs/resolver-binding-darwin-arm64@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-darwin-arm64@npm:1.11.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@unrs/resolver-binding-darwin-x64@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-darwin-x64@npm:1.11.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@unrs/resolver-binding-freebsd-x64@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-freebsd-x64@npm:1.11.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-arm-gnueabihf@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-linux-arm-gnueabihf@npm:1.11.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-arm-musleabihf@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-linux-arm-musleabihf@npm:1.11.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-arm64-gnu@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-linux-arm64-gnu@npm:1.11.1" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-arm64-musl@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-linux-arm64-musl@npm:1.11.1" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-ppc64-gnu@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-linux-ppc64-gnu@npm:1.11.1" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-riscv64-gnu@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-linux-riscv64-gnu@npm:1.11.1" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-riscv64-musl@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-linux-riscv64-musl@npm:1.11.1" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-s390x-gnu@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-linux-s390x-gnu@npm:1.11.1" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-x64-gnu@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-linux-x64-gnu@npm:1.11.1" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-x64-musl@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-linux-x64-musl@npm:1.11.1" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@unrs/resolver-binding-wasm32-wasi@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-wasm32-wasi@npm:1.11.1" + dependencies: + "@napi-rs/wasm-runtime": "npm:^0.2.11" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@unrs/resolver-binding-win32-arm64-msvc@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-win32-arm64-msvc@npm:1.11.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@unrs/resolver-binding-win32-ia32-msvc@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-win32-ia32-msvc@npm:1.11.1" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@unrs/resolver-binding-win32-x64-msvc@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-win32-x64-msvc@npm:1.11.1" + conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -22672,6 +21460,15 @@ __metadata: languageName: node linkType: hard +"@valibot/to-json-schema@npm:^1.3.0": + version: 1.5.0 + resolution: "@valibot/to-json-schema@npm:1.5.0" + peerDependencies: + valibot: ^1.2.0 + checksum: 10/ea6285b258775f9112ad9b6a00bc6c7ab1af6491937e65edf43232a1ecf490cde3d14cd8474103684097ddcd9230bcd16d08805f3e0b62ecffa9d283f977c9b5 + languageName: node + linkType: hard + "@vitest/expect@npm:3.2.4, @vitest/expect@npm:>1.6.0": version: 3.2.4 resolution: "@vitest/expect@npm:3.2.4" @@ -22685,25 +21482,6 @@ __metadata: languageName: node linkType: hard -"@vitest/mocker@npm:3.2.4": - version: 3.2.4 - resolution: "@vitest/mocker@npm:3.2.4" - dependencies: - "@vitest/spy": "npm:3.2.4" - estree-walker: "npm:^3.0.3" - magic-string: "npm:^0.30.17" - peerDependencies: - msw: ^2.4.9 - vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - checksum: 10/5e92431b6ed9fc1679060e4caef3e4623f4750542a5d7cd944774f8217c4d231e273202e8aea00bab33260a5a9222ecb7005d80da0348c3c829bd37d123071a8 - languageName: node - linkType: hard - "@vitest/pretty-format@npm:3.2.4": version: 3.2.4 resolution: "@vitest/pretty-format@npm:3.2.4" @@ -22790,7 +21568,7 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/ast@npm:1.14.1, @webassemblyjs/ast@npm:^1.12.1, @webassemblyjs/ast@npm:^1.14.1": +"@webassemblyjs/ast@npm:1.14.1, @webassemblyjs/ast@npm:^1.14.1": version: 1.14.1 resolution: "@webassemblyjs/ast@npm:1.14.1" dependencies: @@ -22876,7 +21654,7 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/wasm-edit@npm:^1.12.1, @webassemblyjs/wasm-edit@npm:^1.14.1": +"@webassemblyjs/wasm-edit@npm:^1.14.1": version: 1.14.1 resolution: "@webassemblyjs/wasm-edit@npm:1.14.1" dependencies: @@ -22917,7 +21695,7 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/wasm-parser@npm:1.14.1, @webassemblyjs/wasm-parser@npm:^1.12.1, @webassemblyjs/wasm-parser@npm:^1.14.1": +"@webassemblyjs/wasm-parser@npm:1.14.1, @webassemblyjs/wasm-parser@npm:^1.14.1": version: 1.14.1 resolution: "@webassemblyjs/wasm-parser@npm:1.14.1" dependencies: @@ -22941,42 +21719,44 @@ __metadata: languageName: node linkType: hard -"@whatwg-node/events@npm:^0.1.0": - version: 0.1.1 - resolution: "@whatwg-node/events@npm:0.1.1" - checksum: 10/3a356ca23522190201e27446cfd7ebf1cf96815ddb9d1ba5da0a00bbe6c1d28b4094862104411101fbedd47c758b25fe3683033f6a3e80933029efd664c33567 - languageName: node - linkType: hard - -"@whatwg-node/fetch@npm:^0.9.0": - version: 0.9.14 - resolution: "@whatwg-node/fetch@npm:0.9.14" +"@whatwg-node/disposablestack@npm:^0.0.6": + version: 0.0.6 + resolution: "@whatwg-node/disposablestack@npm:0.0.6" dependencies: - "@whatwg-node/node-fetch": "npm:^0.5.0" - urlpattern-polyfill: "npm:^9.0.0" - checksum: 10/74cdaf82abc2eaa15790fe1a15c8d1208bed090956888c8f35ba622b977e75027edef6b85705b0e7680497f478bd90bf0b784b486de95c84a2806e19d65a1f0c + "@whatwg-node/promise-helpers": "npm:^1.0.0" + tslib: "npm:^2.6.3" + checksum: 10/1b73b7b95a7abad77f81fa30044239da6d5fe636c5c9f259571087437ed093996c484e0e0df00860a37244d7b9e518f7d156161f335040ddda27e7ed2e21ecb2 languageName: node linkType: hard -"@whatwg-node/node-fetch@npm:^0.5.0": - version: 0.5.0 - resolution: "@whatwg-node/node-fetch@npm:0.5.0" +"@whatwg-node/fetch@npm:^0.10.13": + version: 0.10.13 + resolution: "@whatwg-node/fetch@npm:0.10.13" dependencies: - "@whatwg-node/events": "npm:^0.1.0" - busboy: "npm:^1.6.0" - fast-querystring: "npm:^1.1.1" - fast-url-parser: "npm:^1.1.3" - tslib: "npm:^2.3.1" - checksum: 10/b779288b07296e0be60e90e338de46b9d0c892d2e38b99bd04d062bbd4acb429607afc92b9bfdbf10841cef6f4b531e63415197583677de69a07e7b2e39350b9 + "@whatwg-node/node-fetch": "npm:^0.8.3" + urlpattern-polyfill: "npm:^10.0.0" + checksum: 10/23e1dd0242962fa5d253a07ddf37fd4df31a5065d65c0e74edb11616415adb1456df76f9df9eeca28ed7454f0bf39304af5bae33c66d8b05399de4a7779675e8 languageName: node linkType: hard -"@whatwg-node/promise-helpers@npm:^1.0.0": - version: 1.3.0 - resolution: "@whatwg-node/promise-helpers@npm:1.3.0" +"@whatwg-node/node-fetch@npm:^0.8.3": + version: 0.8.5 + resolution: "@whatwg-node/node-fetch@npm:0.8.5" + dependencies: + "@fastify/busboy": "npm:^3.1.1" + "@whatwg-node/disposablestack": "npm:^0.0.6" + "@whatwg-node/promise-helpers": "npm:^1.3.2" + tslib: "npm:^2.6.3" + checksum: 10/6ba6c4ff8af66487a17abcc1b3cc6a6fc5b03268af6211db53da5585f7360924c09a19fb9814de33dde8dafa98708b367538c772fe7102c15804f1bace297ef1 + languageName: node + linkType: hard + +"@whatwg-node/promise-helpers@npm:^1.0.0, @whatwg-node/promise-helpers@npm:^1.2.1, @whatwg-node/promise-helpers@npm:^1.2.4, @whatwg-node/promise-helpers@npm:^1.3.2": + version: 1.3.2 + resolution: "@whatwg-node/promise-helpers@npm:1.3.2" dependencies: tslib: "npm:^2.6.3" - checksum: 10/4971319c340f4c16755dd3fe84772e28bcc550e1f723ae10f94e02910ded545f88e65da0a44499912e23c27ed5f7458e1303a99cdd5818792c5958205ee386b4 + checksum: 10/22513e7075d2e6e067399f6b3065a1f280d77aab2cc8699fe5bf9496a76ea7ede2cf4d46fad6f033d0ad686f974c52f85335c3dcddd656d1c8700636713f94a9 languageName: node linkType: hard @@ -23001,21 +21781,17 @@ __metadata: languageName: node linkType: hard -"@xterm/addon-attach@npm:^0.11.0": - version: 0.11.0 - resolution: "@xterm/addon-attach@npm:0.11.0" - peerDependencies: - "@xterm/xterm": ^5.0.0 - checksum: 10/f11e7ba68b8b1064ce545ecbf2beeaf3ebc94622fa3ca77e0ca0b8e8834964f634ee3897c031376abc7242ddfd305a40b10aa7b3e8056bfa59055967251523b5 +"@xterm/addon-attach@npm:^0.12.0": + version: 0.12.0 + resolution: "@xterm/addon-attach@npm:0.12.0" + checksum: 10/2a0107ac8a68ddf3ab39f1b7740796751b5a216cea0dd12d9fbb30bb1611f51f5ba99ec657efe94f267a803e68f5b31736a12d81c448a14d03f9393af614386b languageName: node linkType: hard -"@xterm/addon-fit@npm:^0.10.0": - version: 0.10.0 - resolution: "@xterm/addon-fit@npm:0.10.0" - peerDependencies: - "@xterm/xterm": ^5.0.0 - checksum: 10/8edfad561c0d0316c5883cbe2ce56109f105a2b2bf53b71d5f8c788e656a3205c1093a659dddcf4025a459e4b7ff8e07b6c6a19815c8711deeded560de5f1893 +"@xterm/addon-fit@npm:^0.11.0": + version: 0.11.0 + resolution: "@xterm/addon-fit@npm:0.11.0" + checksum: 10/daeb1cc2be794e0ee3b0f6b1cbec0ba70ac7c65f14093b405a2dfbf93bf267cd3f81490a72bb71364044ff7fd68ff0e8e124977dd812ff9914ad864c6a610c0b languageName: node linkType: hard @@ -23102,45 +21878,6 @@ __metadata: languageName: node linkType: hard -"@yarnpkg/core@npm:^2.4.0": - version: 2.4.0 - resolution: "@yarnpkg/core@npm:2.4.0" - dependencies: - "@arcanis/slice-ansi": "npm:^1.0.2" - "@types/semver": "npm:^7.1.0" - "@types/treeify": "npm:^1.0.0" - "@yarnpkg/fslib": "npm:^2.4.0" - "@yarnpkg/json-proxy": "npm:^2.1.0" - "@yarnpkg/libzip": "npm:^2.2.1" - "@yarnpkg/parsers": "npm:^2.3.0" - "@yarnpkg/pnp": "npm:^2.3.2" - "@yarnpkg/shell": "npm:^2.4.1" - binjumper: "npm:^0.1.4" - camelcase: "npm:^5.3.1" - chalk: "npm:^3.0.0" - ci-info: "npm:^2.0.0" - clipanion: "npm:^2.6.2" - cross-spawn: "npm:7.0.3" - diff: "npm:^4.0.1" - globby: "npm:^11.0.1" - got: "npm:^11.7.0" - json-file-plus: "npm:^3.3.1" - lodash: "npm:^4.17.15" - micromatch: "npm:^4.0.2" - mkdirp: "npm:^0.5.1" - p-limit: "npm:^2.2.0" - pluralize: "npm:^7.0.0" - pretty-bytes: "npm:^5.1.0" - semver: "npm:^7.1.2" - stream-to-promise: "npm:^2.2.0" - tar-stream: "npm:^2.0.1" - treeify: "npm:^1.1.0" - tslib: "npm:^1.13.0" - tunnel: "npm:^0.0.6" - checksum: 10/a963ddc09afdfd7dd40acf89bdffb0f6d134240fa2e9bd46dc1bcbe5501b96b7d65b6f30f36354386de097e0a43ea54f4dc4317ce75e2d5b3d6329e4ab0def67 - languageName: node - linkType: hard - "@yarnpkg/core@npm:^4.2.1, @yarnpkg/core@npm:^4.4.0, @yarnpkg/core@npm:^4.4.1": version: 4.4.1 resolution: "@yarnpkg/core@npm:4.4.1" @@ -23184,16 +21921,6 @@ __metadata: languageName: node linkType: hard -"@yarnpkg/fslib@npm:^2.4.0, @yarnpkg/fslib@npm:^2.5.0": - version: 2.10.4 - resolution: "@yarnpkg/fslib@npm:2.10.4" - dependencies: - "@yarnpkg/libzip": "npm:^2.3.0" - tslib: "npm:^1.13.0" - checksum: 10/c683b91a17138806f11db83af6e6aefd71f485570008effcd68cc39bf84e4243c0072c2a11d613c8289926bcd460e012b9476dd89e61018e21103bc3f42917ca - languageName: node - linkType: hard - "@yarnpkg/fslib@npm:^3.1.2": version: 3.1.2 resolution: "@yarnpkg/fslib@npm:3.1.2" @@ -23203,16 +21930,6 @@ __metadata: languageName: node linkType: hard -"@yarnpkg/json-proxy@npm:^2.1.0": - version: 2.1.1 - resolution: "@yarnpkg/json-proxy@npm:2.1.1" - dependencies: - "@yarnpkg/fslib": "npm:^2.5.0" - tslib: "npm:^1.13.0" - checksum: 10/22f41ac5c3ee201132c6519da88252d5eea7eda96f554cabb1cdc4b7ff951f3b30f727b8abf457a91b2c8a4d2e7679101347e0beb606350cb4d524fea1159e60 - languageName: node - linkType: hard - "@yarnpkg/libui@npm:^3.0.2": version: 3.0.2 resolution: "@yarnpkg/libui@npm:3.0.2" @@ -23225,16 +21942,6 @@ __metadata: languageName: node linkType: hard -"@yarnpkg/libzip@npm:^2.2.1, @yarnpkg/libzip@npm:^2.3.0": - version: 2.3.0 - resolution: "@yarnpkg/libzip@npm:2.3.0" - dependencies: - "@types/emscripten": "npm:^1.39.6" - tslib: "npm:^1.13.0" - checksum: 10/0eb147f39eab2830c29120d17e8bfba5aa15dedb940a7378070c67d4de08e9ba8d34068522e15e6b4db94ecaed4ad520e1e517588a36a348d1aa160bc36156ea - languageName: node - linkType: hard - "@yarnpkg/libzip@npm:^3.1.1, @yarnpkg/libzip@npm:^3.2.0, @yarnpkg/libzip@npm:^3.2.1": version: 3.2.1 resolution: "@yarnpkg/libzip@npm:3.2.1" @@ -23266,16 +21973,6 @@ __metadata: languageName: node linkType: hard -"@yarnpkg/parsers@npm:^2.3.0": - version: 2.6.0 - resolution: "@yarnpkg/parsers@npm:2.6.0" - dependencies: - js-yaml: "npm:^3.10.0" - tslib: "npm:^1.13.0" - checksum: 10/da2c22ce1271383af817b91286fd7532ca8d597a405005e777cb53e702bb7cf688b0b4637c3161351e4e76be43dba0694873cc7845cb9494b9060ddafc5bac3c - languageName: node - linkType: hard - "@yarnpkg/parsers@npm:^3.0.0, @yarnpkg/parsers@npm:^3.0.3": version: 3.0.3 resolution: "@yarnpkg/parsers@npm:3.0.3" @@ -23686,17 +22383,6 @@ __metadata: languageName: node linkType: hard -"@yarnpkg/pnp@npm:^2.3.2": - version: 2.3.2 - resolution: "@yarnpkg/pnp@npm:2.3.2" - dependencies: - "@types/node": "npm:^13.7.0" - "@yarnpkg/fslib": "npm:^2.4.0" - tslib: "npm:^1.13.0" - checksum: 10/be736c950e888e115a50043e684326fb965ce3ba946dada4a7657faf7a2858afef6b5166a366f095b9498ced114325ae3e0341d9cea83a575938e8a8859e74ef - languageName: node - linkType: hard - "@yarnpkg/pnp@npm:^4.0.9, @yarnpkg/pnp@npm:^4.1.0": version: 4.1.0 resolution: "@yarnpkg/pnp@npm:4.1.0" @@ -23707,24 +22393,6 @@ __metadata: languageName: node linkType: hard -"@yarnpkg/shell@npm:^2.4.1": - version: 2.4.1 - resolution: "@yarnpkg/shell@npm:2.4.1" - dependencies: - "@yarnpkg/fslib": "npm:^2.4.0" - "@yarnpkg/parsers": "npm:^2.3.0" - clipanion: "npm:^2.6.2" - cross-spawn: "npm:7.0.3" - fast-glob: "npm:^3.2.2" - micromatch: "npm:^4.0.2" - stream-buffers: "npm:^3.0.2" - tslib: "npm:^1.13.0" - bin: - shell: ./lib/cli.js - checksum: 10/ae7c07561ba4ec968b73385bbd4a9ed01a4f30b52f4fc1b46725dcbca29447e221e1c81ed1f94a6e1527397705e95f51489d3696be45a208a88c00d4c33b1da0 - languageName: node - linkType: hard - "@yarnpkg/shell@npm:^4.1.2": version: 4.1.2 resolution: "@yarnpkg/shell@npm:4.1.2" @@ -23778,13 +22446,6 @@ __metadata: languageName: node linkType: hard -"abab@npm:^2.0.6": - version: 2.0.6 - resolution: "abab@npm:2.0.6" - checksum: 10/ebe95d7278999e605823fc515a3b05d689bc72e7f825536e73c95ebf621636874c6de1b749b3c4bf866b96ccd4b3a2802efa313d0e45ad51a413c8c73247db20 - languageName: node - linkType: hard - "abbrev@npm:1": version: 1.1.1 resolution: "abbrev@npm:1.1.1" @@ -23799,6 +22460,13 @@ __metadata: languageName: node linkType: hard +"abbrev@npm:^4.0.0": + version: 4.0.0 + resolution: "abbrev@npm:4.0.0" + checksum: 10/e2f0c6a6708ad738b3e8f50233f4800de31ad41a6cdc50e0cbe51b76fed69fd0213516d92c15ce1a9985fca71a14606a9be22bf00f8475a58987b9bfb671c582 + languageName: node + linkType: hard + "abort-controller@npm:^3.0.0": version: 3.0.0 resolution: "abort-controller@npm:3.0.0" @@ -23808,7 +22476,7 @@ __metadata: languageName: node linkType: hard -"accepts@npm:^1.3.5, accepts@npm:^1.3.7, accepts@npm:~1.3.4, accepts@npm:~1.3.8": +"accepts@npm:^1.3.7, accepts@npm:~1.3.4, accepts@npm:~1.3.8": version: 1.3.8 resolution: "accepts@npm:1.3.8" dependencies: @@ -23828,16 +22496,6 @@ __metadata: languageName: node linkType: hard -"acorn-globals@npm:^6.0.0": - version: 6.0.0 - resolution: "acorn-globals@npm:6.0.0" - dependencies: - acorn: "npm:^7.1.1" - acorn-walk: "npm:^7.1.1" - checksum: 10/72d95e5b5e585f9acd019b993ab8bbba68bb3cbc9d9b5c1ebb3c2f1fe5981f11deababfb4949f48e6262f9c57878837f5958c0cca396f81023814680ca878042 - languageName: node - linkType: hard - "acorn-import-attributes@npm:^1.9.5": version: 1.9.5 resolution: "acorn-import-attributes@npm:1.9.5" @@ -23865,35 +22523,21 @@ __metadata: languageName: node linkType: hard -"acorn-walk@npm:^7.1.1": - version: 7.1.1 - resolution: "acorn-walk@npm:7.1.1" - checksum: 10/f3cbf2f7eba3bd27b6e2c0a68d10304de365581fa30b129d0420697135791f44a6d1b0da341d40a2e8af319574f4bd5cb970279dd35fdd1fc5beacaba29c6a4e +"acorn-walk@npm:^8.1.1, acorn-walk@npm:^8.3.4": + version: 8.3.4 + resolution: "acorn-walk@npm:8.3.4" + dependencies: + acorn: "npm:^8.11.0" + checksum: 10/871386764e1451c637bb8ab9f76f4995d408057e9909be6fb5ad68537ae3375d85e6a6f170b98989f44ab3ff6c74ad120bc2779a3d577606e7a0cd2b4efcaf77 languageName: node linkType: hard -"acorn-walk@npm:^8.1.1": - version: 8.2.0 - resolution: "acorn-walk@npm:8.2.0" - checksum: 10/e69f7234f2adfeb16db3671429a7c80894105bd7534cb2032acf01bb26e6a847952d11a062d071420b43f8d82e33d2e57f26fe87d9cce0853e8143d8910ff1de - languageName: node - linkType: hard - -"acorn@npm:^7.1.1": - version: 7.4.1 - resolution: "acorn@npm:7.4.1" +"acorn@npm:^8.11.0, acorn@npm:^8.14.1, acorn@npm:^8.15.0, acorn@npm:^8.16.0, acorn@npm:^8.4.1, acorn@npm:^8.8.2, acorn@npm:^8.9.0": + version: 8.16.0 + resolution: "acorn@npm:8.16.0" bin: acorn: bin/acorn - checksum: 10/8be2a40714756d713dfb62544128adce3b7102c6eb94bc312af196c2cc4af76e5b93079bd66b05e9ca31b35a9b0ce12171d16bc55f366cafdb794fdab9d753ec - languageName: node - linkType: hard - -"acorn@npm:^8.14.0, acorn@npm:^8.15.0, acorn@npm:^8.4.1, acorn@npm:^8.7.1, acorn@npm:^8.8.2, acorn@npm:^8.9.0": - version: 8.15.0 - resolution: "acorn@npm:8.15.0" - bin: - acorn: bin/acorn - checksum: 10/77f2de5051a631cf1729c090e5759148459cdb76b5f5c70f890503d629cf5052357b0ce783c0f976dd8a93c5150f59f6d18df1def3f502396a20f81282482fa4 + checksum: 10/690c673bb4d61b38ef82795fab58526471ad7f7e67c0e40c4ff1e10ecd80ce5312554ef633c9995bfc4e6d170cef165711f9ca9e49040b62c0c66fbf2dd3df2b languageName: node linkType: hard @@ -23904,10 +22548,10 @@ __metadata: languageName: node linkType: hard -"adm-zip@npm:^0.5.10": - version: 0.5.14 - resolution: "adm-zip@npm:0.5.14" - checksum: 10/64ccd4dca1195572fc185c42cdce8ebb9117dceaf590cc09536fd1dce2e7fc2afa6c7600095f7a6776db5ea638e855c2bc68c9bbaee47153e3d352324d10a27d +"adm-zip@npm:0.5.10": + version: 0.5.10 + resolution: "adm-zip@npm:0.5.10" + checksum: 10/c5ab79b77114d8277f0cbfd6cca830198d6c7ee4971f6960f48e08cd2375953b11dc71729b7f396abd51d2d6cce8c862fad185ea90cb2c84ab5161c37ed1b099 languageName: node linkType: hard @@ -24040,7 +22684,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:8.17.1, ajv@npm:^8.0.0, ajv@npm:^8.0.1, ajv@npm:^8.10.0, ajv@npm:^8.11.0, ajv@npm:^8.12.0, ajv@npm:^8.16.0, ajv@npm:^8.17.1, ajv@npm:^8.6.0, ajv@npm:^8.6.3, ajv@npm:^8.9.0": +"ajv@npm:8.17.1": version: 8.17.1 resolution: "ajv@npm:8.17.1" dependencies: @@ -24052,7 +22696,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^6.12.2, ajv@npm:^6.12.4, ajv@npm:^6.12.5, ajv@npm:^6.12.6": +"ajv@npm:^6.12.2, ajv@npm:^6.12.4, ajv@npm:^6.12.5": version: 6.12.6 resolution: "ajv@npm:6.12.6" dependencies: @@ -24064,6 +22708,18 @@ __metadata: languageName: node linkType: hard +"ajv@npm:^8.0.0, ajv@npm:^8.0.1, ajv@npm:^8.10.0, ajv@npm:^8.11.0, ajv@npm:^8.12.0, ajv@npm:^8.16.0, ajv@npm:^8.17.1, ajv@npm:^8.6.0, ajv@npm:^8.6.3, ajv@npm:^8.9.0": + version: 8.18.0 + resolution: "ajv@npm:8.18.0" + dependencies: + fast-deep-equal: "npm:^3.1.3" + fast-uri: "npm:^3.0.1" + json-schema-traverse: "npm:^1.0.0" + require-from-string: "npm:^2.0.2" + checksum: 10/bfed9de827a2b27c6d4084324eda76a4e32bdde27410b3e9b81d06e6f8f5c78370fc6b93fe1d869f1939ff1d7c4ae8896960995acb8425e3e9288c8884247c48 + languageName: node + linkType: hard + "ajv@npm:~8.12.0": version: 8.12.0 resolution: "ajv@npm:8.12.0" @@ -24118,6 +22774,13 @@ __metadata: languageName: node linkType: hard +"anser@npm:^2.1.1": + version: 2.3.3 + resolution: "anser@npm:2.3.3" + checksum: 10/fd42896c28defc2489f384930e277c6604651839469bef833a57dfd3ad4d7a02c0ddfc0e464c23f907c37724994e5f4aae5a0aa5fad50230e1c45582f3b87aa4 + languageName: node + linkType: hard + "ansi-align@npm:^3.0.0": version: 3.0.1 resolution: "ansi-align@npm:3.0.1" @@ -24127,7 +22790,7 @@ __metadata: languageName: node linkType: hard -"ansi-colors@npm:^4.1.1, ansi-colors@npm:^4.1.3": +"ansi-colors@npm:4.1.3, ansi-colors@npm:^4.1.1, ansi-colors@npm:^4.1.3": version: 4.1.3 resolution: "ansi-colors@npm:4.1.3" checksum: 10/43d6e2fc7b1c6e4dc373de708ee76311ec2e0433e7e8bd3194e7ff123ea6a747428fc61afdcf5969da5be3a5f0fd054602bec56fc0ebe249ce2fcde6e649e3c2 @@ -24161,16 +22824,7 @@ __metadata: languageName: node linkType: hard -"ansi-html@npm:^0.0.9": - version: 0.0.9 - resolution: "ansi-html@npm:0.0.9" - bin: - ansi-html: bin/ansi-html - checksum: 10/3e83fae364d323d9c453f74a21aa29da68ae152e996c66de45a49a445ea362c4e2e9abce0069558239ff23e3d6ae73b5d27993d631382aa83d85f44b687e0aa1 - languageName: node - linkType: hard - -"ansi-regex@npm:*, ansi-regex@npm:^6.0.1": +"ansi-regex@npm:*, ansi-regex@npm:^6.0.1, ansi-regex@npm:^6.1.0, ansi-regex@npm:^6.2.2": version: 6.2.2 resolution: "ansi-regex@npm:6.2.2" checksum: 10/9b17ce2c6daecc75bcd5966b9ad672c23b184dc3ed9bf3c98a0702f0d2f736c15c10d461913568f2cf527a5e64291c7473358885dd493305c84a1cfed66ba94f @@ -24209,34 +22863,34 @@ __metadata: languageName: node linkType: hard -"ansi-styles@npm:^5.0.0": +"ansi-styles@npm:^5.0.0, ansi-styles@npm:^5.2.0": version: 5.2.0 resolution: "ansi-styles@npm:5.2.0" checksum: 10/d7f4e97ce0623aea6bc0d90dcd28881ee04cba06c570b97fd3391bd7a268eedfd9d5e2dd4fdcbdd82b8105df5faf6f24aaedc08eaf3da898e702db5948f63469 languageName: node linkType: hard -"ansi-styles@npm:^6.0.0, ansi-styles@npm:^6.1.0, ansi-styles@npm:^6.2.1": - version: 6.2.1 - resolution: "ansi-styles@npm:6.2.1" - checksum: 10/70fdf883b704d17a5dfc9cde206e698c16bcd74e7f196ab821511651aee4f9f76c9514bdfa6ca3a27b5e49138b89cb222a28caf3afe4567570139577f991df32 +"ansi-styles@npm:^6.1.0, ansi-styles@npm:^6.2.1, ansi-styles@npm:^6.2.3": + version: 6.2.3 + resolution: "ansi-styles@npm:6.2.3" + checksum: 10/c49dad7639f3e48859bd51824c93b9eb0db628afc243c51c3dd2410c4a15ede1a83881c6c7341aa2b159c4f90c11befb38f2ba848c07c66c9f9de4bcd7cb9f30 languageName: node linkType: hard -"any-promise@npm:^1.0.0, any-promise@npm:^1.1.0, any-promise@npm:~1.3.0": +"any-promise@npm:^1.0.0, any-promise@npm:^1.1.0": version: 1.3.0 resolution: "any-promise@npm:1.3.0" checksum: 10/6737469ba353b5becf29e4dc3680736b9caa06d300bda6548812a8fee63ae7d336d756f88572fa6b5219aed36698d808fa55f62af3e7e6845c7a1dc77d240edb languageName: node linkType: hard -"anymatch@npm:^3.0.3, anymatch@npm:~3.1.2": - version: 3.1.2 - resolution: "anymatch@npm:3.1.2" +"anymatch@npm:^3.1.3, anymatch@npm:~3.1.2": + version: 3.1.3 + resolution: "anymatch@npm:3.1.3" dependencies: normalize-path: "npm:^3.0.0" picomatch: "npm:^2.0.4" - checksum: 10/985163db2292fac9e5a1e072bf99f1b5baccf196e4de25a0b0b81865ebddeb3b3eb4480734ef0a2ac8c002845396b91aa89121f5b84f93981a4658164a9ec6e9 + checksum: 10/3e044fd6d1d26545f235a9fe4d7a534e2029d8e59fa7fd9f2a6eb21230f6b5380ea1eaf55136e60cbf8e613544b3b766e7a6fa2102e2a3a117505466e3025dc2 languageName: node linkType: hard @@ -24247,16 +22901,9 @@ __metadata: languageName: node linkType: hard -"app-module-path@npm:^2.2.0": - version: 2.2.0 - resolution: "app-module-path@npm:2.2.0" - checksum: 10/9ed8c6ce6247a6b5d556039f29b4610869237bbb5b8f3d905b22bd2d314c30efcc0fb70c2626d7461ecc52ec7edec9908f660d0938d2bea5b8cfc6868a28806f - languageName: node - linkType: hard - -"app-next-example-plugin@workspace:packages/app-next-example-plugin": +"app-example-plugin@workspace:packages/app-example-plugin": version: 0.0.0-use.local - resolution: "app-next-example-plugin@workspace:packages/app-next-example-plugin" + resolution: "app-example-plugin@workspace:packages/app-example-plugin" dependencies: "@backstage/cli": "workspace:^" "@backstage/core-components": "workspace:^" @@ -24267,18 +22914,25 @@ __metadata: msw: "npm:^1.0.0" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 + react-router-dom: ^6.30.2 peerDependenciesMeta: "@types/react": optional: true languageName: unknown linkType: soft +"app-module-path@npm:^2.2.0": + version: 2.2.0 + resolution: "app-module-path@npm:2.2.0" + checksum: 10/9ed8c6ce6247a6b5d556039f29b4610869237bbb5b8f3d905b22bd2d314c30efcc0fb70c2626d7461ecc52ec7edec9908f660d0938d2bea5b8cfc6868a28806f + languageName: node + linkType: hard + "append-field@npm:^1.0.0": version: 1.0.0 resolution: "append-field@npm:1.0.0" @@ -24378,12 +23032,12 @@ __metadata: languageName: node linkType: hard -"aria-hidden@npm:^1.1.1": - version: 1.2.3 - resolution: "aria-hidden@npm:1.2.3" +"aria-hidden@npm:^1.2.3, aria-hidden@npm:^1.2.4": + version: 1.2.6 + resolution: "aria-hidden@npm:1.2.6" dependencies: tslib: "npm:^2.0.0" - checksum: 10/cd7f8474f1bef2dadce8fc74ef6d0fa8c9a477ee3c9e49fc3698e5e93a62014140c520266ee28969d63b5ab474144fe48b6182d010feb6a223f7a73928e6660a + checksum: 10/1914e5a36225dccdb29f0b88cc891eeca736cdc5b0c905ab1437b90b28b5286263ed3a221c75b7dc788f25b942367be0044b2ac8ccf073a72e07a50b1d964202 languageName: node linkType: hard @@ -24434,17 +23088,19 @@ __metadata: languageName: node linkType: hard -"array-includes@npm:^3.1.6, array-includes@npm:^3.1.8": - version: 3.1.8 - resolution: "array-includes@npm:3.1.8" +"array-includes@npm:^3.1.6, array-includes@npm:^3.1.8, array-includes@npm:^3.1.9": + version: 3.1.9 + resolution: "array-includes@npm:3.1.9" dependencies: - call-bind: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.2" - es-object-atoms: "npm:^1.0.0" - get-intrinsic: "npm:^1.2.4" - is-string: "npm:^1.0.7" - checksum: 10/290b206c9451f181fb2b1f79a3bf1c0b66bb259791290ffbada760c79b284eef6f5ae2aeb4bcff450ebc9690edd25732c4c73a3c2b340fcc0f4563aed83bf488 + es-abstract: "npm:^1.24.0" + es-object-atoms: "npm:^1.1.1" + get-intrinsic: "npm:^1.3.0" + is-string: "npm:^1.1.1" + math-intrinsics: "npm:^1.1.0" + checksum: 10/8bfe9a58df74f326b4a76b04ee05c13d871759e888b4ee8f013145297cf5eb3c02cfa216067ebdaac5d74eb9763ac5cad77cdf2773b8ab475833701e032173aa languageName: node linkType: hard @@ -24476,29 +23132,30 @@ __metadata: languageName: node linkType: hard -"array.prototype.findlastindex@npm:^1.2.5": - version: 1.2.5 - resolution: "array.prototype.findlastindex@npm:1.2.5" +"array.prototype.findlastindex@npm:^1.2.6": + version: 1.2.6 + resolution: "array.prototype.findlastindex@npm:1.2.6" dependencies: - call-bind: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.2" + es-abstract: "npm:^1.23.9" es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.0.0" - es-shim-unscopables: "npm:^1.0.2" - checksum: 10/7c5c821f357cd53ab6cc305de8086430dd8d7a2485db87b13f843e868055e9582b1fd338f02338f67fc3a1603ceaf9610dd2a470b0b506f9d18934780f95b246 + es-object-atoms: "npm:^1.1.1" + es-shim-unscopables: "npm:^1.1.0" + checksum: 10/5ddb6420e820bef6ddfdcc08ce780d0fd5e627e97457919c27e32359916de5a11ce12f7c55073555e503856618eaaa70845d6ca11dcba724766f38eb1c22f7a2 languageName: node linkType: hard -"array.prototype.flat@npm:^1.3.1, array.prototype.flat@npm:^1.3.2": - version: 1.3.2 - resolution: "array.prototype.flat@npm:1.3.2" +"array.prototype.flat@npm:^1.3.1, array.prototype.flat@npm:^1.3.3": + version: 1.3.3 + resolution: "array.prototype.flat@npm:1.3.3" dependencies: - call-bind: "npm:^1.0.2" - define-properties: "npm:^1.2.0" - es-abstract: "npm:^1.22.1" - es-shim-unscopables: "npm:^1.0.0" - checksum: 10/d9d2f6f27584de92ec7995bc931103e6de722cd2498bdbfc4cba814fc3e52f056050a93be883018811f7c0a35875f5056584a0e940603a5e5934f0279896aebe + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.5" + es-shim-unscopables: "npm:^1.0.2" + checksum: 10/f9b992fa0775d8f7c97abc91eb7f7b2f0ed8430dd9aeb9fdc2967ac4760cdd7fc2ef7ead6528fef40c7261e4d790e117808ce0d3e7e89e91514d4963a531cd01 languageName: node linkType: hard @@ -24564,7 +23221,7 @@ __metadata: languageName: node linkType: hard -"asap@npm:^2.0.0, asap@npm:^2.0.3, asap@npm:~2.0.3, asap@npm:~2.0.6": +"asap@npm:^2.0.0, asap@npm:^2.0.3, asap@npm:~2.0.3": version: 2.0.6 resolution: "asap@npm:2.0.6" checksum: 10/b244c0458c571945e4b3be0b14eb001bea5596f9868cc50cc711dc03d58a7e953517d3f0dad81ccde3ff37d1f074701fa76a6f07d41aaa992d7204a37b915dda @@ -24582,7 +23239,7 @@ __metadata: languageName: node linkType: hard -"asn1@npm:0.2.6, asn1@npm:^0.2.6": +"asn1@npm:^0.2.6": version: 0.2.6 resolution: "asn1@npm:0.2.6" dependencies: @@ -24591,6 +23248,17 @@ __metadata: languageName: node linkType: hard +"asn1js@npm:^3.0.6": + version: 3.0.7 + resolution: "asn1js@npm:3.0.7" + dependencies: + pvtsutils: "npm:^1.3.6" + pvutils: "npm:^1.1.3" + tslib: "npm:^2.8.1" + checksum: 10/1ae92cc6825ff002aed5b2a800e89db1fccd0775b42278431332fe3ee6839711e80e1ca504c72a35a03d94d417c3b315fb03bc6a6f4518c309b1dcb5385a1a93 + languageName: node + linkType: hard + "assert@npm:^2.0.0": version: 2.1.0 resolution: "assert@npm:2.1.0" @@ -24684,6 +23352,20 @@ __metadata: languageName: node linkType: hard +"async-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-function@npm:1.0.0" + checksum: 10/1a09379937d846f0ce7614e75071c12826945d4e417db634156bf0e4673c495989302f52186dfa9767a1d9181794554717badd193ca2bbab046ef1da741d8efd + languageName: node + linkType: hard + +"async-generator-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-generator-function@npm:1.0.0" + checksum: 10/3d49e7acbeee9e84537f4cb0e0f91893df8eba976759875ae8ee9e3d3c82f6ecdebdb347c2fad9926b92596d93cdfc78ecc988bcdf407e40433e8e8e6fe5d78e + languageName: node + linkType: hard + "async-lock@npm:^1.4.1": version: 1.4.1 resolution: "async-lock@npm:1.4.1" @@ -24823,10 +23505,10 @@ __metadata: languageName: node linkType: hard -"aws-ssl-profiles@npm:^1.1.1": - version: 1.1.1 - resolution: "aws-ssl-profiles@npm:1.1.1" - checksum: 10/7293640f29d7b9baf62bcc4e36228c0b7cd435053bbe5535a367dc7fcd76ffdf7f1c7078181147f5d666cc316abb65ce098e64e403f882a49937a4942080a8ea +"aws-ssl-profiles@npm:^1.1.2": + version: 1.1.2 + resolution: "aws-ssl-profiles@npm:1.1.2" + checksum: 10/af9e5c5e6e343e0f299106acaf03106a7458be69772d004f3e4cf0e3649bb41131b594126fcbc997ad89d73752d9e1d72886c72fcc8649ac5d590459d6b75827 languageName: node linkType: hard @@ -24865,25 +23547,14 @@ __metadata: languageName: node linkType: hard -"axios@npm:1.12.2": - version: 1.12.2 - resolution: "axios@npm:1.12.2" +"axios@npm:^1.0.0, axios@npm:^1.12.2, axios@npm:^1.13.5, axios@npm:^1.13.6, axios@npm:^1.15.0, axios@npm:^1.7.4": + version: 1.15.0 + resolution: "axios@npm:1.15.0" dependencies: - follow-redirects: "npm:^1.15.6" - form-data: "npm:^4.0.4" - proxy-from-env: "npm:^1.1.0" - checksum: 10/886a79770594eaad76493fecf90344b567bd956240609b5dcd09bd0afe8d3e6f1ad6d3257a93a483b6192b409d4b673d9515a34619e3e3ed1b2c0ec2a83b20ba - languageName: node - linkType: hard - -"axios@npm:^1.0.0, axios@npm:^1.11.0, axios@npm:^1.13.0, axios@npm:^1.7.4, axios@npm:^1.9.0": - version: 1.13.2 - resolution: "axios@npm:1.13.2" - dependencies: - follow-redirects: "npm:^1.15.6" - form-data: "npm:^4.0.4" - proxy-from-env: "npm:^1.1.0" - checksum: 10/ae4e06dcd18289f2fd18179256d550d27f9a53ecb2f9c59f2ccc4efd1d7151839ba8c3e0fb533dac793e4a59a576ca8689a19244dce5c396680837674a47a867 + follow-redirects: "npm:^1.15.11" + form-data: "npm:^4.0.5" + proxy-from-env: "npm:^2.1.0" + checksum: 10/d39a2c0ebc7ff4739401b282e726cc2673377949d6c46d60eb619458f8d7a2f7eadbcada7097f4dbc7d5c59abb4d3bf6fac33d474412bc3415d3f5aa7ed45530 languageName: node linkType: hard @@ -24911,45 +23582,42 @@ __metadata: languageName: node linkType: hard -"babel-jest@npm:^29.7.0": - version: 29.7.0 - resolution: "babel-jest@npm:29.7.0" +"babel-jest@npm:30.2.0": + version: 30.2.0 + resolution: "babel-jest@npm:30.2.0" dependencies: - "@jest/transform": "npm:^29.7.0" - "@types/babel__core": "npm:^7.1.14" - babel-plugin-istanbul: "npm:^6.1.1" - babel-preset-jest: "npm:^29.6.3" - chalk: "npm:^4.0.0" - graceful-fs: "npm:^4.2.9" + "@jest/transform": "npm:30.2.0" + "@types/babel__core": "npm:^7.20.5" + babel-plugin-istanbul: "npm:^7.0.1" + babel-preset-jest: "npm:30.2.0" + chalk: "npm:^4.1.2" + graceful-fs: "npm:^4.2.11" slash: "npm:^3.0.0" peerDependencies: - "@babel/core": ^7.8.0 - checksum: 10/8a0953bd813b3a8926008f7351611055548869e9a53dd36d6e7e96679001f71e65fd7dbfe253265c3ba6a4e630dc7c845cf3e78b17d758ef1880313ce8fba258 + "@babel/core": ^7.11.0 || ^8.0.0-0 + checksum: 10/4c7351a366cf8ac2b8a2e4e438867693eb9d83ed24c29c648da4576f700767aaf72a5d14337fc3f92c50b069f5025b26c7b89e3b7b867914b7cf8997fc15f095 languageName: node linkType: hard -"babel-plugin-istanbul@npm:^6.1.1": - version: 6.1.1 - resolution: "babel-plugin-istanbul@npm:6.1.1" +"babel-plugin-istanbul@npm:^7.0.1": + version: 7.0.1 + resolution: "babel-plugin-istanbul@npm:7.0.1" dependencies: "@babel/helper-plugin-utils": "npm:^7.0.0" "@istanbuljs/load-nyc-config": "npm:^1.0.0" - "@istanbuljs/schema": "npm:^0.1.2" - istanbul-lib-instrument: "npm:^5.0.4" + "@istanbuljs/schema": "npm:^0.1.3" + istanbul-lib-instrument: "npm:^6.0.2" test-exclude: "npm:^6.0.0" - checksum: 10/ffd436bb2a77bbe1942a33245d770506ab2262d9c1b3c1f1da7f0592f78ee7445a95bc2efafe619dd9c1b6ee52c10033d6c7d29ddefe6f5383568e60f31dfe8d + checksum: 10/fe9f865f975aaa7a033de9ccb2b63fdcca7817266c5e98d3e02ac7ffd774c695093d215302796cb3770a71ef4574e7a9b298504c3c0c104cf4b48c8eda67b2a6 languageName: node linkType: hard -"babel-plugin-jest-hoist@npm:^29.6.3": - version: 29.6.3 - resolution: "babel-plugin-jest-hoist@npm:29.6.3" +"babel-plugin-jest-hoist@npm:30.2.0": + version: 30.2.0 + resolution: "babel-plugin-jest-hoist@npm:30.2.0" dependencies: - "@babel/template": "npm:^7.3.3" - "@babel/types": "npm:^7.3.3" - "@types/babel__core": "npm:^7.1.14" - "@types/babel__traverse": "npm:^7.0.6" - checksum: 10/9bfa86ec4170bd805ab8ca5001ae50d8afcb30554d236ba4a7ffc156c1a92452e220e4acbd98daefc12bf0216fccd092d0a2efed49e7e384ec59e0597a926d65 + "@types/babel__core": "npm:^7.20.5" + checksum: 10/360e87a9aa35f4cf208a10ba79e1821ea906f9e3399db2a9762cbc5076fd59f808e571d88b5b1106738d22e23f9ddefbb8137b2780b2abd401c8573b85c8a2f5 languageName: node linkType: hard @@ -24997,37 +23665,40 @@ __metadata: languageName: node linkType: hard -"babel-preset-current-node-syntax@npm:^1.0.0": - version: 1.0.0 - resolution: "babel-preset-current-node-syntax@npm:1.0.0" +"babel-preset-current-node-syntax@npm:^1.2.0": + version: 1.2.0 + resolution: "babel-preset-current-node-syntax@npm:1.2.0" dependencies: "@babel/plugin-syntax-async-generators": "npm:^7.8.4" "@babel/plugin-syntax-bigint": "npm:^7.8.3" - "@babel/plugin-syntax-class-properties": "npm:^7.8.3" - "@babel/plugin-syntax-import-meta": "npm:^7.8.3" + "@babel/plugin-syntax-class-properties": "npm:^7.12.13" + "@babel/plugin-syntax-class-static-block": "npm:^7.14.5" + "@babel/plugin-syntax-import-attributes": "npm:^7.24.7" + "@babel/plugin-syntax-import-meta": "npm:^7.10.4" "@babel/plugin-syntax-json-strings": "npm:^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators": "npm:^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators": "npm:^7.10.4" "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" - "@babel/plugin-syntax-numeric-separator": "npm:^7.8.3" + "@babel/plugin-syntax-numeric-separator": "npm:^7.10.4" "@babel/plugin-syntax-object-rest-spread": "npm:^7.8.3" "@babel/plugin-syntax-optional-catch-binding": "npm:^7.8.3" "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" - "@babel/plugin-syntax-top-level-await": "npm:^7.8.3" + "@babel/plugin-syntax-private-property-in-object": "npm:^7.14.5" + "@babel/plugin-syntax-top-level-await": "npm:^7.14.5" peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10/2d45944dc78da3a7ec3bce2a48664bd33e21f5b718a724cc2860921d2e2152678a3de50df9a8d5add8c3c3a10b444925f03ead28090c9dde89ea695044a010db + "@babel/core": ^7.0.0 || ^8.0.0-0 + checksum: 10/3608fa671cfa46364ea6ec704b8fcdd7514b7b70e6ec09b1199e13ae73ed346c51d5ce2cb6d4d5b295f6a3f2cad1fdeec2308aa9e037002dd7c929194cc838ea languageName: node linkType: hard -"babel-preset-jest@npm:^29.6.3": - version: 29.6.3 - resolution: "babel-preset-jest@npm:29.6.3" +"babel-preset-jest@npm:30.2.0": + version: 30.2.0 + resolution: "babel-preset-jest@npm:30.2.0" dependencies: - babel-plugin-jest-hoist: "npm:^29.6.3" - babel-preset-current-node-syntax: "npm:^1.0.0" + babel-plugin-jest-hoist: "npm:30.2.0" + babel-preset-current-node-syntax: "npm:^1.2.0" peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10/aa4ff2a8a728d9d698ed521e3461a109a1e66202b13d3494e41eea30729a5e7cc03b3a2d56c594423a135429c37bf63a9fa8b0b9ce275298be3095a88c69f6fb + "@babel/core": ^7.11.0 || ^8.0.0-beta.1 + checksum: 10/f75e155a8cf63ea1c5ca942bf757b934427630a1eeafdf861e9117879b3367931fc521da3c41fd52f8d59d705d1093ffb46c9474b3fd4d765d194bea5659d7d9 languageName: node linkType: hard @@ -25062,6 +23733,15 @@ __metadata: languageName: node linkType: hard +"balanced-match@npm:^4.0.2": + version: 4.0.2 + resolution: "balanced-match@npm:4.0.2" + dependencies: + jackspeak: "npm:^4.2.3" + checksum: 10/862d6e14832a45558bdd2bc4663ba6d8e7ba60670a6cb1ef952a62348d5f086b16ee71a21b369f0fee808432590c724c823a80cb59eddd140b5ffa3f55497b62 + languageName: node + linkType: hard + "bare-events@npm:^2.5.4, bare-events@npm:^2.7.0": version: 2.8.2 resolution: "bare-events@npm:2.8.2" @@ -25169,10 +23849,19 @@ __metadata: languageName: node linkType: hard +"baseline-browser-mapping@npm:^2.9.0": + version: 2.9.19 + resolution: "baseline-browser-mapping@npm:2.9.19" + bin: + baseline-browser-mapping: dist/cli.js + checksum: 10/8d7bbb7fe3d1ad50e04b127c819ba6d059c01ed0d2a7a5fc3327e23a8c42855fa3a8b510550c1fe1e37916147e6a390243566d3ef85bf6130c8ddfe5cc3db530 + languageName: node + linkType: hard + "basic-ftp@npm:^5.0.2": - version: 5.0.3 - resolution: "basic-ftp@npm:5.0.3" - checksum: 10/8f69811a7f4088c5ae39025f1a662522aad517b4065365fbbe80676dc9ccf7a188463dfcb2d9573af2af859d7de70015f612c20a04a311b520efbf7c21dc1ff2 + version: 5.2.2 + resolution: "basic-ftp@npm:5.2.2" + checksum: 10/2bb208276bafbc3549992734b67247d78bfe6546e2eb1e9513c2761364c47e88b94171666359200279a738b36b152d2bbba0953d4f83f4677f1fd5b68aea3e29 languageName: node linkType: hard @@ -25199,15 +23888,6 @@ __metadata: languageName: node linkType: hard -"better-opn@npm:^3.0.2": - version: 3.0.2 - resolution: "better-opn@npm:3.0.2" - dependencies: - open: "npm:^8.0.4" - checksum: 10/24668e5a837d0d2c0edf17ad5ebcfeb00a8a5578a5eb09f7a409e1a60617cdfea40b8ebfc95e5f12d9568157930d033e6805788fcf0780413ac982c95d3745d1 - languageName: node - linkType: hard - "better-path-resolve@npm:1.0.0": version: 1.0.0 resolution: "better-path-resolve@npm:1.0.0" @@ -25218,26 +23898,33 @@ __metadata: linkType: hard "better-sqlite3@npm:^12.0.0": - version: 12.4.1 - resolution: "better-sqlite3@npm:12.4.1" + version: 12.8.0 + resolution: "better-sqlite3@npm:12.8.0" dependencies: bindings: "npm:^1.5.0" node-gyp: "npm:latest" prebuild-install: "npm:^7.1.1" - checksum: 10/e8133786677e88a2526c965658178e2e057e4b40ff554895a71ecb5e617e062fea619f31389ffbd6fc1f3396ef598812302be56c32e0f01fcb82610785d18186 + checksum: 10/63b943e5431ee2fed02796aa2bb452f19d08340bfcfee555c4865414d89262964b3b3e55324c5c5a0628247808b0ea25a357a62872a5e017455af6938b9a619a languageName: node linkType: hard -"bfj@npm:^8.0.0": - version: 8.0.0 - resolution: "bfj@npm:8.0.0" +"bfj@npm:^9.0.2": + version: 9.1.3 + resolution: "bfj@npm:9.1.3" dependencies: - bluebird: "npm:^3.7.2" check-types: "npm:^11.2.3" hoopy: "npm:^0.1.4" - jsonpath: "npm:^1.1.1" tryer: "npm:^1.0.1" - checksum: 10/3e79233e2ba30681a494470d664c654351d2f4fcba7c2972f7e8b6248e374a77a164141164ea32d23f805f0a235aa87dbf480ad0a5939c36f5efbf922de8beb4 + checksum: 10/33119ebc5237345f0032d6d5b1543306298022711891eb28bd7e592663c3a027dec2f561beebd491e6c2f3779883bcaae91df4d958920776e1db9659399e1b8e + languageName: node + linkType: hard + +"bidi-js@npm:^1.0.3": + version: 1.0.3 + resolution: "bidi-js@npm:1.0.3" + dependencies: + require-from-string: "npm:^2.0.2" + checksum: 10/c4341c7a98797efe3d186cd99d6f97e9030a4f959794ca200ef2ec0a678483a916335bba6c2c0608a21d04a221288a31c9fd0faa0cd9b3903b93594b42466a6a languageName: node linkType: hard @@ -25299,13 +23986,6 @@ __metadata: languageName: node linkType: hard -"binjumper@npm:^0.1.4": - version: 0.1.4 - resolution: "binjumper@npm:0.1.4" - checksum: 10/9ae6de33ca27b9cc40425227d3d6560ce63f8977855fed70788dc0492f9a048895d79617d8d8152b7b8f66f93d935f25a4bca94cc74d477c3c7cba2c15662dea - languageName: node - linkType: hard - "bintrees@npm:1.0.1": version: 1.0.1 resolution: "bintrees@npm:1.0.1" @@ -25345,9 +24025,9 @@ __metadata: linkType: hard "bn.js@npm:^4.0.0, bn.js@npm:^4.1.0, bn.js@npm:^4.11.8, bn.js@npm:^4.11.9": - version: 4.12.0 - resolution: "bn.js@npm:4.12.0" - checksum: 10/10f8db196d3da5adfc3207d35d0a42aa29033eb33685f20ba2c36cadfe2de63dad05df0a20ab5aae01b418d1c4b3d4d205273085262fa020d17e93ff32b67527 + version: 4.12.3 + resolution: "bn.js@npm:4.12.3" + checksum: 10/57ed5a055f946f3e009f1589c45a5242db07f3dddfc72e4506f0dd9d8b145f0dbee4edabc2499288f3fc338eb712fb96a1c623a2ed2bcd49781df1a64db64dd1 languageName: node linkType: hard @@ -25358,40 +24038,40 @@ __metadata: languageName: node linkType: hard -"body-parser@npm:1.20.3, body-parser@npm:^1.15.2": - version: 1.20.3 - resolution: "body-parser@npm:1.20.3" +"body-parser@npm:^1.15.2, body-parser@npm:~1.20.3": + version: 1.20.4 + resolution: "body-parser@npm:1.20.4" dependencies: - bytes: "npm:3.1.2" + bytes: "npm:~3.1.2" content-type: "npm:~1.0.5" debug: "npm:2.6.9" depd: "npm:2.0.0" - destroy: "npm:1.2.0" - http-errors: "npm:2.0.0" - iconv-lite: "npm:0.4.24" - on-finished: "npm:2.4.1" - qs: "npm:6.13.0" - raw-body: "npm:2.5.2" + destroy: "npm:~1.2.0" + http-errors: "npm:~2.0.1" + iconv-lite: "npm:~0.4.24" + on-finished: "npm:~2.4.1" + qs: "npm:~6.14.0" + raw-body: "npm:~2.5.3" type-is: "npm:~1.6.18" - unpipe: "npm:1.0.0" - checksum: 10/8723e3d7a672eb50854327453bed85ac48d045f4958e81e7d470c56bf111f835b97e5b73ae9f6393d0011cc9e252771f46fd281bbabc57d33d3986edf1e6aeca + unpipe: "npm:~1.0.0" + checksum: 10/ff67e28d3f426707be8697a75fdf8d564dc50c341b41f054264d8ab6e2924e519c7ce8acc9d0de05328fdc41e1d9f3f200aec9c1cfb1867d6b676a410d97c689 languageName: node linkType: hard -"body-parser@npm:^2.2.0": - version: 2.2.0 - resolution: "body-parser@npm:2.2.0" +"body-parser@npm:^2.2.1": + version: 2.2.2 + resolution: "body-parser@npm:2.2.2" dependencies: bytes: "npm:^3.1.2" content-type: "npm:^1.0.5" - debug: "npm:^4.4.0" + debug: "npm:^4.4.3" http-errors: "npm:^2.0.0" - iconv-lite: "npm:^0.6.3" + iconv-lite: "npm:^0.7.0" on-finished: "npm:^2.4.1" - qs: "npm:^6.14.0" - raw-body: "npm:^3.0.0" - type-is: "npm:^2.0.0" - checksum: 10/e9d844b036bd15970df00a16f373c7ed28e1ef870974a0a1d4d6ef60d70e01087cc20a0dbb2081c49a88e3c08ce1d87caf1e2898c615dffa193f63e8faa8a84e + qs: "npm:^6.14.1" + raw-body: "npm:^3.0.1" + type-is: "npm:^2.0.1" + checksum: 10/69671f67d4d5ae5974593901a92d639757231da1725ed6de4d35e86cde9ce7650afdf1cd28df9b6f7892ea7f9eb03ccb30c70fe27d679275ae4cb4aae5ce1b21 languageName: node linkType: hard @@ -25459,12 +24139,21 @@ __metadata: languageName: node linkType: hard -"brace-expansion@npm:^2.0.1": - version: 2.0.1 - resolution: "brace-expansion@npm:2.0.1" +"brace-expansion@npm:^2.0.1, brace-expansion@npm:^2.0.2": + version: 2.0.3 + resolution: "brace-expansion@npm:2.0.3" dependencies: balanced-match: "npm:^1.0.0" - checksum: 10/a61e7cd2e8a8505e9f0036b3b6108ba5e926b4b55089eeb5550cd04a471fe216c96d4fe7e4c7f995c728c554ae20ddfc4244cad10aef255e72b62930afd233d1 + checksum: 10/e9dd66caaf0784126e1654f1bc19adb28f3ef86f39f2226f833f7700ec727c141f6cd85eaa47bacf3426beda01c9fbc3a2f28174cf59330dc9b58ffaf9e09d96 + languageName: node + linkType: hard + +"brace-expansion@npm:^5.0.2, brace-expansion@npm:^5.0.5": + version: 5.0.5 + resolution: "brace-expansion@npm:5.0.5" + dependencies: + balanced-match: "npm:^4.0.2" + checksum: 10/f259b2ddf04489da9512ad637ba6b4ef2d77abd4445d20f7f1714585f153435200a53fa6a2e4a5ee974df14ddad4cd16421f6f803e96e8b452bd48598878d0ee languageName: node linkType: hard @@ -25505,13 +24194,6 @@ __metadata: languageName: node linkType: hard -"browser-process-hrtime@npm:^1.0.0": - version: 1.0.0 - resolution: "browser-process-hrtime@npm:1.0.0" - checksum: 10/e30f868cdb770b1201afb714ad1575dd86366b6e861900884665fb627109b3cc757c40067d3bfee1ff2a29c835257ea30725a8018a9afd02ac1c24b408b1e45f - languageName: node - linkType: hard - "browser-resolve@npm:^2.0.0": version: 2.0.0 resolution: "browser-resolve@npm:2.0.0" @@ -25595,17 +24277,18 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.0.0, browserslist@npm:^4.16.0, browserslist@npm:^4.16.6, browserslist@npm:^4.18.1, browserslist@npm:^4.24.0": - version: 4.24.2 - resolution: "browserslist@npm:4.24.2" +"browserslist@npm:^4.0.0, browserslist@npm:^4.16.0, browserslist@npm:^4.16.6, browserslist@npm:^4.18.1, browserslist@npm:^4.24.0, browserslist@npm:^4.28.1": + version: 4.28.1 + resolution: "browserslist@npm:4.28.1" dependencies: - caniuse-lite: "npm:^1.0.30001669" - electron-to-chromium: "npm:^1.5.41" - node-releases: "npm:^2.0.18" - update-browserslist-db: "npm:^1.1.1" + baseline-browser-mapping: "npm:^2.9.0" + caniuse-lite: "npm:^1.0.30001759" + electron-to-chromium: "npm:^1.5.263" + node-releases: "npm:^2.0.27" + update-browserslist-db: "npm:^1.2.0" bin: browserslist: cli.js - checksum: 10/f8a9d78bbabe466c57ffd5c50a9e5582a5df9aa68f43078ca62a9f6d0d6c70ba72eca72d0a574dbf177cf55cdca85a46f7eb474917a47ae5398c66f8b76f7d1c + checksum: 10/64f2a97de4bce8473c0e5ae0af8d76d1ead07a5b05fc6bc87b848678bb9c3a91ae787b27aa98cdd33fc00779607e6c156000bed58fefb9cf8e4c5a183b994cdb languageName: node linkType: hard @@ -25625,15 +24308,6 @@ __metadata: languageName: node linkType: hard -"btoa@npm:^1.2.1": - version: 1.2.1 - resolution: "btoa@npm:1.2.1" - bin: - btoa: bin/btoa.js - checksum: 10/29f2ca93837e10427184626bdfd5d00065dff28b604b822aa9849297dac8c8d6ad385cc96eed812ebf153d80c24a4556252afdbb97c7a712938baeaad7547705 - languageName: node - linkType: hard - "buffer-crc32@npm:^1.0.0": version: 1.0.0 resolution: "buffer-crc32@npm:1.0.0" @@ -25648,7 +24322,7 @@ __metadata: languageName: node linkType: hard -"buffer-equal-constant-time@npm:1.0.1": +"buffer-equal-constant-time@npm:^1.0.1": version: 1.0.1 resolution: "buffer-equal-constant-time@npm:1.0.1" checksum: 10/80bb945f5d782a56f374b292770901065bad21420e34936ecbe949e57724b4a13874f735850dd1cc61f078773c4fb5493a41391e7bda40d1fa388d6bd80daaab @@ -25761,13 +24435,20 @@ __metadata: languageName: node linkType: hard -"bytes@npm:3.1.2, bytes@npm:^3.1.2": +"bytes@npm:3.1.2, bytes@npm:^3.1.2, bytes@npm:~3.1.2": version: 3.1.2 resolution: "bytes@npm:3.1.2" checksum: 10/a10abf2ba70c784471d6b4f58778c0beeb2b5d405148e66affa91f23a9f13d07603d0a0354667310ae1d6dc141474ffd44e2a074be0f6e2254edb8fc21445388 languageName: node linkType: hard +"bytestreamjs@npm:^2.0.1": + version: 2.0.1 + resolution: "bytestreamjs@npm:2.0.1" + checksum: 10/523b1024e3f887cdc0b3db7c4fc14b8563aaeb75e6642a41991b3208277fd0ae9cd66003c73473fe706c42797bf0c3f1f498fb9880b431d75b332e5709d56a0c + languageName: node + linkType: hard + "cacache@npm:^15.0.3, cacache@npm:^15.0.5, cacache@npm:^15.2.0": version: 15.3.0 resolution: "cacache@npm:15.3.0" @@ -25820,26 +24501,6 @@ __metadata: languageName: node linkType: hard -"cacache@npm:^18.0.0": - version: 18.0.0 - resolution: "cacache@npm:18.0.0" - dependencies: - "@npmcli/fs": "npm:^3.1.0" - fs-minipass: "npm:^3.0.0" - glob: "npm:^10.2.2" - lru-cache: "npm:^10.0.1" - minipass: "npm:^7.0.3" - minipass-collect: "npm:^1.0.2" - minipass-flush: "npm:^1.0.5" - minipass-pipeline: "npm:^1.2.4" - p-map: "npm:^4.0.0" - ssri: "npm:^10.0.0" - tar: "npm:^6.1.11" - unique-filename: "npm:^3.0.0" - checksum: 10/b71fefe97b9799a863dc48ac79da2bd57a724ff0922fddd3aef4f3b70395ba00d1ef9547a0594d3d6d3cd57aeaeaf4d938c54f89695053eb2198cf8758b47511 - languageName: node - linkType: hard - "cacache@npm:^19.0.1": version: 19.0.1 resolution: "cacache@npm:19.0.1" @@ -25860,13 +24521,22 @@ __metadata: languageName: node linkType: hard -"cache-content-type@npm:^1.0.0": - version: 1.0.1 - resolution: "cache-content-type@npm:1.0.1" +"cacache@npm:^20.0.1": + version: 20.0.3 + resolution: "cacache@npm:20.0.3" dependencies: - mime-types: "npm:^2.1.18" - ylru: "npm:^1.2.0" - checksum: 10/18db4d59452669ccbfd7146a1510a37eb28e9eccf18ca7a4eb603dff2edc5cccdca7498fc3042a2978f76f11151fba486eb9eb69d9afa3fb124957870aef4fd3 + "@npmcli/fs": "npm:^5.0.0" + fs-minipass: "npm:^3.0.0" + glob: "npm:^13.0.0" + lru-cache: "npm:^11.1.0" + minipass: "npm:^7.0.3" + minipass-collect: "npm:^2.0.1" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + p-map: "npm:^7.0.2" + ssri: "npm:^13.0.0" + unique-filename: "npm:^5.0.0" + checksum: 10/388a0169970df9d051da30437f93f81b7e91efb570ad0ff2b8fde33279fbe726c1bc8e8e2b9c05053ffb4f563854c73db395e8712e3b62347a1bc4f7fb8899ff languageName: node linkType: hard @@ -25968,7 +24638,7 @@ __metadata: languageName: node linkType: hard -"callsites@npm:^3.0.0": +"callsites@npm:^3.0.0, callsites@npm:^3.1.0": version: 3.1.0 resolution: "callsites@npm:3.1.0" checksum: 10/072d17b6abb459c2ba96598918b55868af677154bec7e73d222ef95a8fdb9bbf7dae96a8421085cdad8cd190d86653b5b6dc55a4484f2e5b2e27d5e0c3fc15b3 @@ -25999,7 +24669,7 @@ __metadata: languageName: node linkType: hard -"camelcase@npm:^6.2.0": +"camelcase@npm:^6.2.0, camelcase@npm:^6.3.0": version: 6.3.0 resolution: "camelcase@npm:6.3.0" checksum: 10/8c96818a9076434998511251dcb2761a94817ea17dbdc37f47ac080bd088fc62c7369429a19e2178b993497132c8cbcf5cc1f44ba963e76782ba469c0474938d @@ -26025,10 +24695,10 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001669": - version: 1.0.30001717 - resolution: "caniuse-lite@npm:1.0.30001717" - checksum: 10/e47dfd8707ea305baa177f3d3d531df614f5a9ac6335363fc8f86f0be4caf79f5734f3f68b601fee4edd9d79f1e5ffc0931466bb894bf955ed6b1dd5a1c34b1d +"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001759": + version: 1.0.30001769 + resolution: "caniuse-lite@npm:1.0.30001769" + checksum: 10/4b7d087832d4330a8b1fa02cd9455bdb068ea67f1735f2aa6324d5b64b24f5079cf6349b13d209f268ffa59644b9f4f784266b780bafd877ceb83c9797ca80ba languageName: node linkType: hard @@ -26063,16 +24733,6 @@ __metadata: languageName: node linkType: hard -"chalk@npm:3.0.0, chalk@npm:^3.0.0": - version: 3.0.0 - resolution: "chalk@npm:3.0.0" - dependencies: - ansi-styles: "npm:^4.1.0" - supports-color: "npm:^7.1.0" - checksum: 10/37f90b31fd655fb49c2bd8e2a68aebefddd64522655d001ef417e6f955def0ed9110a867ffc878a533f2dafea5f2032433a37c8a7614969baa7f8a1cd424ddfc - languageName: node - linkType: hard - "chalk@npm:4.1.2, chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.0, chalk@npm:^4.1.1, chalk@npm:^4.1.2, chalk@npm:~4.1.2": version: 4.1.2 resolution: "chalk@npm:4.1.2" @@ -26083,6 +24743,16 @@ __metadata: languageName: node linkType: hard +"chalk@npm:^3.0.0": + version: 3.0.0 + resolution: "chalk@npm:3.0.0" + dependencies: + ansi-styles: "npm:^4.1.0" + supports-color: "npm:^7.1.0" + checksum: 10/37f90b31fd655fb49c2bd8e2a68aebefddd64522655d001ef417e6f955def0ed9110a867ffc878a533f2dafea5f2032433a37c8a7614969baa7f8a1cd424ddfc + languageName: node + linkType: hard + "chalk@npm:^5.4.1": version: 5.6.2 resolution: "chalk@npm:5.6.2" @@ -26111,6 +24781,13 @@ __metadata: languageName: node linkType: hard +"character-entities-legacy@npm:^3.0.0": + version: 3.0.0 + resolution: "character-entities-legacy@npm:3.0.0" + checksum: 10/7582af055cb488b626d364b7d7a4e46b06abd526fb63c0e4eb35bcb9c9799cc4f76b39f34fdccef2d1174ac95e53e9ab355aae83227c1a2505877893fce77731 + languageName: node + linkType: hard + "character-entities@npm:^1.0.0": version: 1.2.4 resolution: "character-entities@npm:1.2.4" @@ -26139,7 +24816,7 @@ __metadata: languageName: node linkType: hard -"chardet@npm:^2.1.0": +"chardet@npm:^2.1.1": version: 2.1.1 resolution: "chardet@npm:2.1.1" checksum: 10/d56913b65e45c5c86f331988e2ef6264c131bfeadaae098ee719bf6610546c77740e37221ffec802dde56b5e4466613a4c754786f4da6b5f6c5477243454d324 @@ -26186,6 +24863,15 @@ __metadata: languageName: node linkType: hard +"chokidar@npm:^4.0.1": + version: 4.0.3 + resolution: "chokidar@npm:4.0.3" + dependencies: + readdirp: "npm:^4.0.1" + checksum: 10/bf2a575ea5596000e88f5db95461a9d59ad2047e939d5a4aac59dd472d126be8f1c1ff3c7654b477cf532d18f42a97279ef80ee847972fd2a25410bf00b80b59 + languageName: node + linkType: hard + "chownr@npm:^1.1.1": version: 1.1.4 resolution: "chownr@npm:1.1.4" @@ -26223,17 +24909,17 @@ __metadata: languageName: node linkType: hard -"ci-info@npm:^3.2.0, ci-info@npm:^3.7.0": +"ci-info@npm:^3.2.0": version: 3.8.0 resolution: "ci-info@npm:3.8.0" checksum: 10/b00e9313c1f7042ca8b1297c157c920d6d69f0fbad7b867910235676df228c4b4f4df33d06cacae37f9efba7a160b0a167c6be85492b419ef71d85660e60606b languageName: node linkType: hard -"ci-info@npm:^4.0.0": - version: 4.0.0 - resolution: "ci-info@npm:4.0.0" - checksum: 10/c983bb7ff1b06648f4a47432201abbd58291147d8ab5043dbb5c03e1a0e3fb2347f40d29b66a3044f28ffeb5dade01ac35aa6bd4e7464a44d9a49a3d7532415a +"ci-info@npm:^4.0.0, ci-info@npm:^4.2.0": + version: 4.3.1 + resolution: "ci-info@npm:4.3.1" + checksum: 10/9dc952bef67e665ccde2e7a552d42d5d095529d21829ece060a00925ede2dfa136160c70ef2471ea6ed6c9b133218b47c007f56955c0f1734a2e57f240aa7445 languageName: node linkType: hard @@ -26256,10 +24942,10 @@ __metadata: languageName: node linkType: hard -"cjs-module-lexer@npm:^1.0.0, cjs-module-lexer@npm:^1.2.2": - version: 1.4.3 - resolution: "cjs-module-lexer@npm:1.4.3" - checksum: 10/d2b92f919a2dedbfd61d016964fce8da0035f827182ed6839c97cac56e8a8077cfa6a59388adfe2bc588a19cef9bbe830d683a76a6e93c51f65852062cfe2591 +"cjs-module-lexer@npm:^2.1.0, cjs-module-lexer@npm:^2.2.0": + version: 2.2.0 + resolution: "cjs-module-lexer@npm:2.2.0" + checksum: 10/fc8eb5c1919504366d8260a150d93c4e857740e770467dc59ca0cc34de4b66c93075559a5af65618f359187866b1be40e036f4e1a1bab2f1e06001c216415f74 languageName: node linkType: hard @@ -26293,6 +24979,16 @@ __metadata: languageName: node linkType: hard +"cleye@npm:^2.3.0": + version: 2.3.0 + resolution: "cleye@npm:2.3.0" + dependencies: + terminal-columns: "npm:^2.0.0" + type-flag: "npm:^4.1.0" + checksum: 10/a6fe9d8431db05907d1699099de338e2a227843ebb85d2b540d609d649dd35b482f6534105daa2930dd18649dd0bfd0526d43c1d746eec10195fda31784953c7 + languageName: node + linkType: hard + "cli-boxes@npm:^2.2.0, cli-boxes@npm:^2.2.1": version: 2.2.1 resolution: "cli-boxes@npm:2.2.1" @@ -26331,7 +25027,23 @@ __metadata: languageName: node linkType: hard -"cli-spinners@npm:^2.5.0": +"cli-highlight@npm:^2.1.11": + version: 2.1.11 + resolution: "cli-highlight@npm:2.1.11" + dependencies: + chalk: "npm:^4.0.0" + highlight.js: "npm:^10.7.1" + mz: "npm:^2.4.0" + parse5: "npm:^5.1.1" + parse5-htmlparser2-tree-adapter: "npm:^6.0.0" + yargs: "npm:^16.0.0" + bin: + highlight: bin/highlight + checksum: 10/05d2b5beb8a4d3259f693517d013bf53d04ad20f470b77c3d02e051963092fae388388e3127f67d3679884a0c32cb855bf590292017c5e68c0f8d86f4b8e146e + languageName: node + linkType: hard + +"cli-spinners@npm:^2.5.0, cli-spinners@npm:^2.9.2": version: 2.9.2 resolution: "cli-spinners@npm:2.9.2" checksum: 10/a0a863f442df35ed7294424f5491fa1756bd8d2e4ff0c8736531d886cec0ece4d85e8663b77a5afaf1d296e3cbbebff92e2e99f52bbea89b667cbe789b994794 @@ -26350,6 +25062,19 @@ __metadata: languageName: node linkType: hard +"cli-table3@npm:^0.6.5": + version: 0.6.5 + resolution: "cli-table3@npm:0.6.5" + dependencies: + "@colors/colors": "npm:1.5.0" + string-width: "npm:^4.2.0" + dependenciesMeta: + "@colors/colors": + optional: true + checksum: 10/8dca71256f6f1367bab84c33add3f957367c7c43750a9828a4212ebd31b8df76bd7419d386e3391ac7419698a8540c25f1a474584028f35b170841cde2e055c5 + languageName: node + linkType: hard + "cli-table@npm:^0.3.1, cli-table@npm:^0.3.11": version: 0.3.11 resolution: "cli-table@npm:0.3.11" @@ -26369,13 +25094,13 @@ __metadata: languageName: node linkType: hard -"cli-truncate@npm:^4.0.0": - version: 4.0.0 - resolution: "cli-truncate@npm:4.0.0" +"cli-truncate@npm:^5.0.0": + version: 5.2.0 + resolution: "cli-truncate@npm:5.2.0" dependencies: - slice-ansi: "npm:^5.0.0" - string-width: "npm:^7.0.0" - checksum: 10/d5149175fd25ca985731bdeec46a55ec237475cf74c1a5e103baea696aceb45e372ac4acbaabf1316f06bd62e348123060f8191ffadfeedebd2a70a2a7fb199d + slice-ansi: "npm:^8.0.0" + string-width: "npm:^8.2.0" + checksum: 10/b789b6c2caff1560259aedeb6aaafcf41167d478df418d718a8c92edd6bc5a0ece272b8fb7e7911fbd31cef7b1ac8a30f2b21d90c3174b55a018fe3f2604a137 languageName: node linkType: hard @@ -26400,13 +25125,6 @@ __metadata: languageName: node linkType: hard -"clipanion@npm:^2.6.2": - version: 2.6.2 - resolution: "clipanion@npm:2.6.2" - checksum: 10/f87ca32dd41a7e7898e72f425590c267818c81717c33ea52270354a3f9232a4c4d4f38a5acc0c4b52cb9f9b67962dcf3d326cd57ec2cc3d4345292f0b84e025b - languageName: node - linkType: hard - "clipanion@npm:^4.0.0-rc.2": version: 4.0.0-rc.3 resolution: "clipanion@npm:4.0.0-rc.3" @@ -26566,17 +25284,17 @@ __metadata: languageName: node linkType: hard -"codemirror-graphql@npm:^2.0.10, codemirror-graphql@npm:^2.0.13": - version: 2.0.13 - resolution: "codemirror-graphql@npm:2.0.13" +"codemirror-graphql@npm:^2.2.1": + version: 2.2.4 + resolution: "codemirror-graphql@npm:2.2.4" dependencies: "@types/codemirror": "npm:^0.0.90" - graphql-language-service: "npm:5.2.2" + graphql-language-service: "npm:5.5.0" peerDependencies: "@codemirror/language": 6.0.0 codemirror: ^5.65.3 - graphql: ^15.5.0 || ^16.0.0 - checksum: 10/1a759c3b549c5fa24fffaeb8799156bea0a37c5bf672b09af1a672c89bc4464409ead99e79189644e8c3b4b9746472ef9d5b9f6387cc3fa2f16bfb2c4b75d4db + graphql: ^15.5.0 || ^16.0.0 || ^17.0.0 + checksum: 10/89e8994588c6145578b707694509854ccdff40c19e82d15dff832c152cadc8978f3688b804b8e75f656d3b7dfa5dc5e6344e854eda5141f0d92c0a505fda1371 languageName: node linkType: hard @@ -26614,14 +25332,14 @@ __metadata: languageName: node linkType: hard -"collect-v8-coverage@npm:^1.0.0": - version: 1.0.0 - resolution: "collect-v8-coverage@npm:1.0.0" - checksum: 10/99d6059eb0e04bf792e27824d43d1139ede28914b7f55ddedd9c43594926221579fa6925ecc7e2f47dec14f5199e5ea3afbfc1da9a32ad542f859022d57f9d80 +"collect-v8-coverage@npm:^1.0.2": + version: 1.0.3 + resolution: "collect-v8-coverage@npm:1.0.3" + checksum: 10/656443261fb7b79cf79e89cba4b55622b07c1d4976c630829d7c5c585c73cda1c2ff101f316bfb19bb9e2c58d724c7db1f70a21e213dcd14099227c5e6019860 languageName: node linkType: hard -"color-convert@npm:^1.9.0, color-convert@npm:^1.9.1": +"color-convert@npm:^1.9.0": version: 1.9.3 resolution: "color-convert@npm:1.9.3" dependencies: @@ -26639,6 +25357,15 @@ __metadata: languageName: node linkType: hard +"color-convert@npm:^3.1.3": + version: 3.1.3 + resolution: "color-convert@npm:3.1.3" + dependencies: + color-name: "npm:^2.0.0" + checksum: 10/36b9b99c138f90eb11a28d1ad911054a9facd6cffde4f00dc49a34ebde7cae28454b2285ede64f273b6a8df9c3228b80e4352f4471978fa8b5005fe91341a67b + languageName: node + linkType: hard + "color-name@npm:1.1.3": version: 1.1.3 resolution: "color-name@npm:1.1.3" @@ -26646,20 +25373,26 @@ __metadata: languageName: node linkType: hard -"color-name@npm:^1.0.0, color-name@npm:^1.1.4, color-name@npm:~1.1.4": +"color-name@npm:^1.1.4, color-name@npm:~1.1.4": version: 1.1.4 resolution: "color-name@npm:1.1.4" checksum: 10/b0445859521eb4021cd0fb0cc1a75cecf67fceecae89b63f62b201cca8d345baf8b952c966862a9d9a2632987d4f6581f0ec8d957dfacece86f0a7919316f610 languageName: node linkType: hard -"color-string@npm:^1.5.2": - version: 1.9.0 - resolution: "color-string@npm:1.9.0" +"color-name@npm:^2.0.0": + version: 2.1.0 + resolution: "color-name@npm:2.1.0" + checksum: 10/eb014f71d87408e318e95d3f554f188370d354ba8e0ffa4341d0fd19de391bfe2bc96e563d4f6614644d676bc24f475560dffee3fe310c2d6865d007410a9a2b + languageName: node + linkType: hard + +"color-string@npm:^2.1.3": + version: 2.1.4 + resolution: "color-string@npm:2.1.4" dependencies: - color-name: "npm:^1.0.0" - simple-swizzle: "npm:^0.2.2" - checksum: 10/6e347b463aa8e40eb193d6ee21ef501c88dad9c20c4607f5394f3b3c4ce40d828c87a35ac4acdc94696d8dae00a04cb30f0bc73f001ccc812f1d58dccaf26591 + color-name: "npm:^2.0.0" + checksum: 10/689a8688ac3cd55247792c83a9db9bfe675343c7412fedba1eb748ac6a8867dd2bb3d406e309ebfe90336809ee5067c7f2cccfbd10133c5cc9ef1dba5aad58f2 languageName: node linkType: hard @@ -26672,13 +25405,13 @@ __metadata: languageName: node linkType: hard -"color@npm:3.0.x": - version: 3.0.0 - resolution: "color@npm:3.0.0" +"color@npm:^5.0.2": + version: 5.0.3 + resolution: "color@npm:5.0.3" dependencies: - color-convert: "npm:^1.9.1" - color-string: "npm:^1.5.2" - checksum: 10/17ca34cc7d1aa1b41cb414c388ed0376e70401b42e9ca0320ee095f664373bc76f5934d81cb8e118ab6795994ca8abe396261da6ad308d5151b4dcf03cfc9cf9 + color-convert: "npm:^3.1.3" + color-string: "npm:^2.1.3" + checksum: 10/88063ee058b995e5738092b5aa58888666275d1e967333f3814ff4fa334ce9a9e71de78a16fb1838f17c80793ea87f4878c20192037662809fe14eab2d474fd9 languageName: node linkType: hard @@ -26710,16 +25443,6 @@ __metadata: languageName: node linkType: hard -"colorspace@npm:1.1.x": - version: 1.1.2 - resolution: "colorspace@npm:1.1.2" - dependencies: - color: "npm:3.0.x" - text-hex: "npm:1.0.x" - checksum: 10/a959ec1669176aa72185067b7d04dae1cef2698456e1a452a035ce8adcac95673fbb1547e3240903355bcbaa67e031cca0b8b4f7d42c256b3dd94dcead8e1405 - languageName: node - linkType: hard - "combined-stream@npm:^1.0.8": version: 1.0.8 resolution: "combined-stream@npm:1.0.8" @@ -26750,10 +25473,10 @@ __metadata: languageName: node linkType: hard -"commander@npm:*, commander@npm:^13.1.0": - version: 13.1.0 - resolution: "commander@npm:13.1.0" - checksum: 10/d3b4b79e6be8471ddadacbb8cd441fe82154d7da7393b50e76165a9e29ccdb74fa911a186437b9a211d0fc071db6051915c94fb8ef16d77511d898e9dbabc6af +"commander@npm:11.1.0, commander@npm:^11.0.0": + version: 11.1.0 + resolution: "commander@npm:11.1.0" + checksum: 10/66bd2d8a0547f6cb1d34022efb25f348e433b0e04ad76a65279b1b09da108f59a4d3001ca539c60a7a46ea38bcf399fc17d91adad76a8cf43845d8dcbaf5cda1 languageName: node linkType: hard @@ -26785,13 +25508,6 @@ __metadata: languageName: node linkType: hard -"commander@npm:^11.0.0": - version: 11.1.0 - resolution: "commander@npm:11.1.0" - checksum: 10/66bd2d8a0547f6cb1d34022efb25f348e433b0e04ad76a65279b1b09da108f59a4d3001ca539c60a7a46ea38bcf399fc17d91adad76a8cf43845d8dcbaf5cda1 - languageName: node - linkType: hard - "commander@npm:^12.0.0, commander@npm:^12.1.0": version: 12.1.0 resolution: "commander@npm:12.1.0" @@ -26799,6 +25515,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^14.0.3": + version: 14.0.3 + resolution: "commander@npm:14.0.3" + checksum: 10/dfa9ebe2a433d277de5cb0252d23b10a543d245d892db858d23b516336a835c50fd4f52bee4cd13c705cc8acb6f03dc632c73dd806f7d06d3353eb09953dd17a + languageName: node + linkType: hard + "commander@npm:^2.19.0, commander@npm:^2.20.0": version: 2.20.3 resolution: "commander@npm:2.20.3" @@ -26806,7 +25529,7 @@ __metadata: languageName: node linkType: hard -"commander@npm:^4.0.0, commander@npm:^4.1.1": +"commander@npm:^4.0.0": version: 4.1.1 resolution: "commander@npm:4.1.1" checksum: 10/3b2dc4125f387dab73b3294dbcb0ab2a862f9c0ad748ee2b27e3544d25325b7a8cdfbcc228d103a98a716960b14478114a5206b5415bd48cdafa38797891562c @@ -26877,7 +25600,7 @@ __metadata: languageName: node linkType: hard -"compression@npm:^1.7.4": +"compression@npm:^1.7.4, compression@npm:^1.8.1": version: 1.8.1 resolution: "compression@npm:1.8.1" dependencies: @@ -27065,6 +25788,17 @@ __metadata: languageName: node linkType: hard +"constant-case@npm:^3.0.4": + version: 3.0.4 + resolution: "constant-case@npm:3.0.4" + dependencies: + no-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + upper-case: "npm:^2.0.2" + checksum: 10/6c3346d51afc28d9fae922e966c68eb77a19d94858dba230dd92d7b918b37d36db50f0311e9ecf6847e43e934b1c01406a0936973376ab17ec2c471fbcfb2cf3 + languageName: node + linkType: hard + "constants-browserify@npm:^1.0.0": version: 1.0.0 resolution: "constants-browserify@npm:1.0.0" @@ -27079,7 +25813,7 @@ __metadata: languageName: node linkType: hard -"content-disposition@npm:0.5.4, content-disposition@npm:^0.5.3, content-disposition@npm:~0.5.2": +"content-disposition@npm:^0.5.3, content-disposition@npm:~0.5.4": version: 0.5.4 resolution: "content-disposition@npm:0.5.4" dependencies: @@ -27097,7 +25831,7 @@ __metadata: languageName: node linkType: hard -"content-type@npm:^1.0.4, content-type@npm:^1.0.5, content-type@npm:~1.0.4, content-type@npm:~1.0.5": +"content-type@npm:^1.0.5, content-type@npm:~1.0.4, content-type@npm:~1.0.5": version: 1.0.5 resolution: "content-type@npm:1.0.5" checksum: 10/585847d98dc7fb8035c02ae2cb76c7a9bd7b25f84c447e5ed55c45c2175e83617c8813871b4ee22f368126af6b2b167df655829007b21aa10302873ea9c62662 @@ -27142,28 +25876,21 @@ __metadata: languageName: node linkType: hard -"cookie-signature@npm:1.0.7": - version: 1.0.7 - resolution: "cookie-signature@npm:1.0.7" - checksum: 10/1a62808cd30d15fb43b70e19829b64d04b0802d8ef00275b57d152de4ae6a3208ca05c197b6668d104c4d9de389e53ccc2d3bc6bcaaffd9602461417d8c40710 - languageName: node - linkType: hard - -"cookie-signature@npm:^1.2.1": +"cookie-signature@npm:^1.2.1, cookie-signature@npm:^1.2.2": version: 1.2.2 resolution: "cookie-signature@npm:1.2.2" checksum: 10/be44a3c9a56f3771aea3a8bd8ad8f0a8e2679bcb967478267f41a510b4eb5ec55085386ba79c706c4ac21605ca76f4251973444b90283e0eb3eeafe8a92c7708 languageName: node linkType: hard -"cookie@npm:0.7.1": - version: 0.7.1 - resolution: "cookie@npm:0.7.1" - checksum: 10/aec6a6aa0781761bf55d60447d6be08861d381136a0fe94aa084fddd4f0300faa2b064df490c6798adfa1ebaef9e0af9b08a189c823e0811b8b313b3d9a03380 +"cookie-signature@npm:~1.0.6, cookie-signature@npm:~1.0.7": + version: 1.0.7 + resolution: "cookie-signature@npm:1.0.7" + checksum: 10/1a62808cd30d15fb43b70e19829b64d04b0802d8ef00275b57d152de4ae6a3208ca05c197b6668d104c4d9de389e53ccc2d3bc6bcaaffd9602461417d8c40710 languageName: node linkType: hard -"cookie@npm:0.7.2, cookie@npm:^0.7.0, cookie@npm:^0.7.1, cookie@npm:^0.7.2": +"cookie@npm:0.7.2, cookie@npm:^0.7.0, cookie@npm:^0.7.1, cookie@npm:^0.7.2, cookie@npm:~0.7.1, cookie@npm:~0.7.2": version: 0.7.2 resolution: "cookie@npm:0.7.2" checksum: 10/24b286c556420d4ba4e9bc09120c9d3db7d28ace2bd0f8ccee82422ce42322f73c8312441271e5eefafbead725980e5996cc02766dbb89a90ac7f5636ede608f @@ -27184,16 +25911,6 @@ __metadata: languageName: node linkType: hard -"cookies@npm:~0.9.0": - version: 0.9.1 - resolution: "cookies@npm:0.9.1" - dependencies: - depd: "npm:~2.0.0" - keygrip: "npm:~1.1.0" - checksum: 10/4816461a38d907b20f3fb7a2bc4741fe580e7a195f3e248ef7025cb3be56a07638a0f4e72553a5f535554ca30172c8a3245c63ac72c9737cec034e9a47773392 - languageName: node - linkType: hard - "copy-to-clipboard@npm:^3.2.0, copy-to-clipboard@npm:^3.3.1": version: 3.3.1 resolution: "copy-to-clipboard@npm:3.3.1" @@ -27218,9 +25935,9 @@ __metadata: linkType: hard "core-js@npm:^3.6.5": - version: 3.46.0 - resolution: "core-js@npm:3.46.0" - checksum: 10/82993ca487c6cbbf8bbf00e45eeb9705eb63dc2f9c90d7f35696733efbc3f4b52426e1f8dbef0f0b68ea16caa21e4f44cc5490e08120e1cad4a72b031ed8adaa + version: 3.49.0 + resolution: "core-js@npm:3.49.0" + checksum: 10/31d018f9830b0240ae40869e380595f2d06a8800709ad63299a42a438ba0c8d5805045fa02a20a78f42761d83103b0b71eca982955f5e890fb7cf6b2fe6a9ab1 languageName: node linkType: hard @@ -27239,12 +25956,12 @@ __metadata: linkType: hard "cors@npm:^2.8.4, cors@npm:^2.8.5": - version: 2.8.5 - resolution: "cors@npm:2.8.5" + version: 2.8.6 + resolution: "cors@npm:2.8.6" dependencies: object-assign: "npm:^4" vary: "npm:^1" - checksum: 10/66e88e08edee7cbce9d92b4d28a2028c88772a4c73e02f143ed8ca76789f9b59444eed6b1c167139e76fa662998c151322720093ba229f9941365ada5a6fc2c6 + checksum: 10/aa7174305b21ceb90f9c84f4eaa32f04432d333addbfdc0d1eb7310393c48902e5364aada5ac2f5d054528d63b3179238444475426fcb74e1e345077de485727 languageName: node linkType: hard @@ -27382,23 +26099,6 @@ __metadata: languageName: node linkType: hard -"create-jest@npm:^29.7.0": - version: 29.7.0 - resolution: "create-jest@npm:29.7.0" - dependencies: - "@jest/types": "npm:^29.6.3" - chalk: "npm:^4.0.0" - exit: "npm:^0.1.2" - graceful-fs: "npm:^4.2.9" - jest-config: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - prompts: "npm:^2.0.1" - bin: - create-jest: bin/create-jest.js - checksum: 10/847b4764451672b4174be4d5c6d7d63442ec3aa5f3de52af924e4d996d87d7801c18e125504f25232fc75840f6625b3ac85860fac6ce799b5efae7bdcaf4a2b7 - languageName: node - linkType: hard - "create-require@npm:^1.1.0, create-require@npm:^1.1.1": version: 1.1.1 resolution: "create-require@npm:1.1.1" @@ -27441,33 +26141,34 @@ __metadata: languageName: node linkType: hard -"cross-env@npm:^7.0.0": - version: 7.0.3 - resolution: "cross-env@npm:7.0.3" +"cross-env@npm:^10.0.0": + version: 10.1.0 + resolution: "cross-env@npm:10.1.0" dependencies: - cross-spawn: "npm:^7.0.1" + "@epic-web/invariant": "npm:^1.0.0" + cross-spawn: "npm:^7.0.6" bin: - cross-env: src/bin/cross-env.js - cross-env-shell: src/bin/cross-env-shell.js - checksum: 10/e99911f0d31c20e990fd92d6fd001f4b01668a303221227cc5cb42ed155f086351b1b3bd2699b200e527ab13011b032801f8ce638e6f09f854bdf744095e604c + cross-env: dist/bin/cross-env.js + cross-env-shell: dist/bin/cross-env-shell.js + checksum: 10/0e5d8bdefbbcd000460b69755e0eeb22953510abac8375e4f8b638ff7c45406141acfd57b8a4c1d1cf0b5ea42f33451b302062fb9b34408753b4d465e901b845 languageName: node linkType: hard "cross-fetch@npm:^3.1.5": - version: 3.1.8 - resolution: "cross-fetch@npm:3.1.8" + version: 3.2.0 + resolution: "cross-fetch@npm:3.2.0" dependencies: - node-fetch: "npm:^2.6.12" - checksum: 10/ac8c4ca87d2ac0e17a19b6a293a67ee8934881aee5ec9a5a8323c30e9a9a60a0f5291d3c0d633ec2a2f970cbc60978d628804dfaf03add92d7e720b6d37f392c + node-fetch: "npm:^2.7.0" + checksum: 10/e4ab1d390a5b6ca8bb0605f028af2ffc1127d2e407b954654949f506d04873c4863ece264662c074865d7874060e35f938cec74fe7b5736d46d545e2685f6aec languageName: node linkType: hard "cross-fetch@npm:^4.0.0": - version: 4.0.0 - resolution: "cross-fetch@npm:4.0.0" + version: 4.1.0 + resolution: "cross-fetch@npm:4.1.0" dependencies: - node-fetch: "npm:^2.6.12" - checksum: 10/e231a71926644ef122d334a3a4e73d9ba3ba4b480a8a277fb9badc434c1ba905b3d60c8034e18b348361a09afbec40ba9371036801ba2b675a7b84588f9f55d8 + node-fetch: "npm:^2.7.0" + checksum: 10/07624940607b64777d27ec9c668ddb6649e8c59ee0a5a10e63a51ce857e2bbb1294a45854a31c10eccb91b65909a5b199fcb0217339b44156f85900a7384f489 languageName: node linkType: hard @@ -27480,17 +26181,6 @@ __metadata: languageName: node linkType: hard -"cross-spawn@npm:7.0.3": - version: 7.0.3 - resolution: "cross-spawn@npm:7.0.3" - dependencies: - path-key: "npm:^3.1.0" - shebang-command: "npm:^2.0.0" - which: "npm:^2.0.1" - checksum: 10/e1a13869d2f57d974de0d9ef7acbf69dc6937db20b918525a01dacb5032129bd552d290d886d981e99f1b624cb03657084cc87bd40f115c07ecf376821c729ce - languageName: node - linkType: hard - "cross-spawn@npm:^6.0.0": version: 6.0.5 resolution: "cross-spawn@npm:6.0.5" @@ -27504,7 +26194,7 @@ __metadata: languageName: node linkType: hard -"cross-spawn@npm:^7.0.1, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.5, cross-spawn@npm:^7.0.6": +"cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.5, cross-spawn@npm:^7.0.6": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" dependencies: @@ -27658,6 +26348,16 @@ __metadata: languageName: node linkType: hard +"css-tree@npm:^3.1.0": + version: 3.1.0 + resolution: "css-tree@npm:3.1.0" + dependencies: + mdn-data: "npm:2.12.2" + source-map-js: "npm:^1.0.1" + checksum: 10/e8c5c8e98e3aa4a620fda0b813ce57ccf99281652bf9d23e5cdfc9961c9a93a6769941f9a92e31e65d90f446f42fa83879ab0185206dc7a178d9f656d0913e14 + languageName: node + linkType: hard + "css-vendor@npm:^2.0.8": version: 2.0.8 resolution: "css-vendor@npm:2.0.8" @@ -27762,35 +26462,24 @@ __metadata: languageName: node linkType: hard -"cssom@npm:^0.5.0": - version: 0.5.0 - resolution: "cssom@npm:0.5.0" - checksum: 10/b502a315b1ce020a692036cc38cb36afa44157219b80deadfa040ab800aa9321fcfbecf02fd2e6ec87db169715e27978b4ab3701f916461e9cf7808899f23b54 - languageName: node - linkType: hard - -"cssom@npm:~0.3.6": - version: 0.3.8 - resolution: "cssom@npm:0.3.8" - checksum: 10/49eacc88077555e419646c0ea84ddc73c97e3a346ad7cb95e22f9413a9722d8964b91d781ce21d378bd5ae058af9a745402383fa4e35e9cdfd19654b63f892a9 - languageName: node - linkType: hard - -"cssstyle@npm:^2.3.0": - version: 2.3.0 - resolution: "cssstyle@npm:2.3.0" - dependencies: - cssom: "npm:~0.3.6" - checksum: 10/46f7f05a153446c4018b0454ee1464b50f606cb1803c90d203524834b7438eb52f3b173ba0891c618f380ced34ee12020675dc0052a7f1be755fe4ebc27ee977 - languageName: node - linkType: hard - "cssstyle@npm:^4.1.0": - version: 4.1.0 - resolution: "cssstyle@npm:4.1.0" + version: 4.6.0 + resolution: "cssstyle@npm:4.6.0" dependencies: - rrweb-cssom: "npm:^0.7.1" - checksum: 10/8ca9e2d1f1b24f93bb5f3f20a7a1e271e58060957880e985ee55614e196a798ffab309ec6bac105af8a439a6764546761813835ebb7f929d60823637ee838a8f + "@asamuzakjp/css-color": "npm:^3.2.0" + rrweb-cssom: "npm:^0.8.0" + checksum: 10/1cb25c9d66b87adb165f978b75cdeb6f225d7e31ba30a8934666046a0be037e4e7200d359bfa79d4f1a4aef1083ea09633b81bcdb36a2f2ac888e8c73ea3a289 + languageName: node + linkType: hard + +"cssstyle@npm:^5.3.4": + version: 5.3.4 + resolution: "cssstyle@npm:5.3.4" + dependencies: + "@asamuzakjp/css-color": "npm:^4.1.0" + "@csstools/css-syntax-patches-for-csstree": "npm:1.0.14" + css-tree: "npm:^3.1.0" + checksum: 10/4eeb85cbaba47c2e4eb9f9a9dea9633e311f1cb706258f582fc8d099edc0b8c0deea93a6943981960d7f920ac0f3bbb5277cc4c355915406c17a95d0049a073d languageName: node linkType: hard @@ -27816,9 +26505,9 @@ __metadata: linkType: hard "ctrlc-windows@npm:^2.1.0": - version: 2.1.0 - resolution: "ctrlc-windows@npm:2.1.0" - checksum: 10/518015cf2ac9a12bc327d4eb68f5d5c9d17b7ee369b80d8bc94473a08343587f7a39cf32074bd9b4d4e4b441fad6399475cf353e1470eb443e4d230b80fa9c5f + version: 2.2.0 + resolution: "ctrlc-windows@npm:2.2.0" + checksum: 10/3d044eda5aa8c58d35e34b05249f9330e7258bec88999e50f25fd21807c2bedabbcab28e10a24e60c15748267ef0ebf9a915d4e430f484d5ce2ec5c75663c352 languageName: node linkType: hard @@ -27951,17 +26640,6 @@ __metadata: languageName: node linkType: hard -"data-urls@npm:^3.0.2": - version: 3.0.2 - resolution: "data-urls@npm:3.0.2" - dependencies: - abab: "npm:^2.0.6" - whatwg-mimetype: "npm:^3.0.0" - whatwg-url: "npm:^11.0.0" - checksum: 10/033fc3dd0fba6d24bc9a024ddcf9923691dd24f90a3d26f6545d6a2f71ec6956f93462f2cdf2183cc46f10dc01ed3bcb36731a8208456eb1a08147e571fe2a76 - languageName: node - linkType: hard - "data-urls@npm:^5.0.0": version: 5.0.0 resolution: "data-urls@npm:5.0.0" @@ -27972,6 +26650,16 @@ __metadata: languageName: node linkType: hard +"data-urls@npm:^6.0.0": + version: 6.0.0 + resolution: "data-urls@npm:6.0.0" + dependencies: + whatwg-mimetype: "npm:^4.0.0" + whatwg-url: "npm:^15.0.0" + checksum: 10/a47f0dde184337c4f168d455aedf0b486fed87b6ca583b4b9ad55d1515f4836b418d4bdc5b5b6fc55e321feb826029586a0d47e1c9a9e7ac4d52a78faceb7fb0 + languageName: node + linkType: hard + "data-view-buffer@npm:^1.0.2": version: 1.0.2 resolution: "data-view-buffer@npm:1.0.2" @@ -28005,7 +26693,7 @@ __metadata: languageName: node linkType: hard -"dataloader@npm:^2.0.0, dataloader@npm:^2.2.2": +"dataloader@npm:^2.0.0, dataloader@npm:^2.2.3": version: 2.2.3 resolution: "dataloader@npm:2.2.3" checksum: 10/83fe6259abe00ae64c5f48252ef59d8e5fcabda9fd4d26685f14a76eeca596bf6f9500d9f22a0094c50c3ea782a0977728f9367e232dfa0fdb5c9d646de279b2 @@ -28065,7 +26753,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:2.6.9, debug@npm:^2.6.0": +"debug@npm:2.6.9, debug@npm:^2.6.0, debug@npm:~2.6.9": version: 2.6.9 resolution: "debug@npm:2.6.9" dependencies: @@ -28074,7 +26762,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:^4.3.7, debug@npm:^4.4.0": +"debug@npm:4, debug@npm:4.4.3, debug@npm:^4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:^4.3.7, debug@npm:^4.4.0, debug@npm:^4.4.3": version: 4.4.3 resolution: "debug@npm:4.4.3" dependencies: @@ -28098,18 +26786,6 @@ __metadata: languageName: node linkType: hard -"debug@npm:4.4.1": - version: 4.4.1 - resolution: "debug@npm:4.4.1" - dependencies: - ms: "npm:^2.1.3" - peerDependenciesMeta: - supports-color: - optional: true - checksum: 10/8e2709b2144f03c7950f8804d01ccb3786373df01e406a0f66928e47001cf2d336cbed9ee137261d4f90d68d8679468c755e3548ed83ddacdc82b194d2468afe - languageName: node - linkType: hard - "debug@npm:^3.1.1, debug@npm:^3.2.7": version: 3.2.7 resolution: "debug@npm:3.2.7" @@ -28126,10 +26802,10 @@ __metadata: languageName: node linkType: hard -"decimal.js@npm:10, decimal.js@npm:^10.3.1, decimal.js@npm:^10.4.3": - version: 10.4.3 - resolution: "decimal.js@npm:10.4.3" - checksum: 10/de663a7bc4d368e3877db95fcd5c87b965569b58d16cdc4258c063d231ca7118748738df17cd638f7e9dd0be8e34cec08d7234b20f1f2a756a52fc5a38b188d0 +"decimal.js@npm:^10.4.3, decimal.js@npm:^10.6.0": + version: 10.6.0 + resolution: "decimal.js@npm:10.6.0" + checksum: 10/c0d45842d47c311d11b38ce7ccc911121953d4df3ebb1465d92b31970eb4f6738a065426a06094af59bee4b0d64e42e7c8984abd57b6767c64ea90cf90bb4a69 languageName: node linkType: hard @@ -28160,15 +26836,15 @@ __metadata: languageName: node linkType: hard -"dedent@npm:^1.0.0": - version: 1.5.3 - resolution: "dedent@npm:1.5.3" +"dedent@npm:^1.6.0": + version: 1.7.0 + resolution: "dedent@npm:1.7.0" peerDependencies: babel-plugin-macros: ^3.1.0 peerDependenciesMeta: babel-plugin-macros: optional: true - checksum: 10/e5277f6268f288649503125b781a7b7a2c9b22d011139688c0b3619fe40121e600eb1f077c891938d4b2428bdb6326cc3c77a763e4b1cc681bd9666ab1bad2a1 + checksum: 10/c902f3e7e828923bd642c12c1d8996616ff5588f8279a2951790bd7c7e479fa4dd7f016b55ce2c9ea1aa2895fc503e7d6c0cde6ebc95ca683ac0230f7c911fd7 languageName: node linkType: hard @@ -28179,13 +26855,6 @@ __metadata: languageName: node linkType: hard -"deep-equal@npm:~1.0.1": - version: 1.0.1 - resolution: "deep-equal@npm:1.0.1" - checksum: 10/cbecc071afb2891334ced9e9de5834889b9a9992ae8d8369b7eb74c513529eb6d1f6c04d4e2b5f34d8386f7816cd7a6cda45edff847695faea45e43c23973f45 - languageName: node - linkType: hard - "deep-extend@npm:0.6.0, deep-extend@npm:^0.6.0": version: 0.6.0 resolution: "deep-extend@npm:0.6.0" @@ -28193,7 +26862,7 @@ __metadata: languageName: node linkType: hard -"deep-is@npm:^0.1.3, deep-is@npm:~0.1.3": +"deep-is@npm:^0.1.3": version: 0.1.3 resolution: "deep-is@npm:0.1.3" checksum: 10/dee1094e987a784a9a9c8549fc65eeca3422aef3bf2f9579f76c126085f280311d09273826c2f430d84fd09d64f6a578e5e7a4ac6ba1d50ea6cff0ddf605c025 @@ -28303,21 +26972,6 @@ __metadata: languageName: node linkType: hard -"del@npm:^8.0.0": - version: 8.0.1 - resolution: "del@npm:8.0.1" - dependencies: - globby: "npm:^14.0.2" - is-glob: "npm:^4.0.3" - is-path-cwd: "npm:^3.0.0" - is-path-inside: "npm:^4.0.0" - p-map: "npm:^7.0.2" - presentable-error: "npm:^0.0.1" - slash: "npm:^5.1.0" - checksum: 10/53ed4a379a68c90e7d6d3bcce09c49229e77de9a946d0a5fc25f45b16c950cb8665986b7d0d0423416c03bfd43e0f31e528c5a19c558fe47449be9d6fae7f846 - languageName: node - linkType: hard - "delayed-stream@npm:~1.0.0": version: 1.0.0 resolution: "delayed-stream@npm:1.0.0" @@ -28410,19 +27064,19 @@ __metadata: languageName: node linkType: hard -"destroy@npm:1.2.0, destroy@npm:^1.0.4": +"destroy@npm:1.2.0, destroy@npm:^1.0.4, destroy@npm:~1.2.0": version: 1.2.0 resolution: "destroy@npm:1.2.0" checksum: 10/0acb300b7478a08b92d810ab229d5afe0d2f4399272045ab22affa0d99dbaf12637659411530a6fcd597a9bdac718fc94373a61a95b4651bbc7b83684a565e38 languageName: node linkType: hard -"destroyable-server@npm:^1.0.0, destroyable-server@npm:^1.0.2": - version: 1.0.2 - resolution: "destroyable-server@npm:1.0.2" +"destroyable-server@npm:^1.0.0, destroyable-server@npm:^1.1.1": + version: 1.1.1 + resolution: "destroyable-server@npm:1.1.1" dependencies: "@types/node": "npm:*" - checksum: 10/c93c7651dac530bf9dd4263eab59f5724a2453dc35658d98e3707c4c9171d8496ef404655852bef257586b31850f4f6730397bc5ff496ed799352660b0495a0a + checksum: 10/483867c0ee3adb6f265e0163ea5f204135a30cd2b14ef07f58054186f63cc12ca7c591431b30cb664216e1bdc3156d50c74b56c3e17f67a5e75b0d32f0fe84f5 languageName: node linkType: hard @@ -28433,28 +27087,28 @@ __metadata: languageName: node linkType: hard -"detect-indent@npm:^7.0.1": - version: 7.0.1 - resolution: "detect-indent@npm:7.0.1" - checksum: 10/cbf3f0b1c3c881934ca94428e1179b26ab2a587e0d719031d37a67fb506d49d067de54ff057cb1e772e75975fed5155c01cd4518306fee60988b1486e3fc7768 +"detect-indent@npm:^7.0.2": + version: 7.0.2 + resolution: "detect-indent@npm:7.0.2" + checksum: 10/ef215d1b55a14f677ce03e840973b25362b6f8cd3f566bc82831fa1abb2be6a95423729bc573dc2334b1371ad7be18d9ec67e1a9611b71a04cb6d63f0d8e54cc languageName: node linkType: hard -"detect-libc@npm:^2.0.0, detect-libc@npm:^2.0.3": - version: 2.0.3 - resolution: "detect-libc@npm:2.0.3" - checksum: 10/b4ea018d623e077bd395f168a9e81db77370dde36a5b01d067f2ad7989924a81d31cb547ff764acb2aa25d50bb7fdde0b0a93bec02212b0cb430621623246d39 +"detect-libc@npm:^2.0.0": + version: 2.1.2 + resolution: "detect-libc@npm:2.1.2" + checksum: 10/b736c8d97d5d46164c0d1bed53eb4e6a3b1d8530d460211e2d52f1c552875e706c58a5376854e4e54f8b828c9cada58c855288c968522eb93ac7696d65970766 languageName: node linkType: hard -"detect-newline@npm:^3.0.0": +"detect-newline@npm:^3.1.0": version: 3.1.0 resolution: "detect-newline@npm:3.1.0" checksum: 10/ae6cd429c41ad01b164c59ea36f264a2c479598e61cba7c99da24175a7ab80ddf066420f2bec9a1c57a6bead411b4655ff15ad7d281c000a89791f48cbe939e7 languageName: node linkType: hard -"detect-newline@npm:^4.0.0": +"detect-newline@npm:^4.0.1": version: 4.0.1 resolution: "detect-newline@npm:4.0.1" checksum: 10/0409ecdfb93419591ccff24fccfe2ddddad29b66637d1ed898872125b25af05014fdeedc9306339577060f69f59fe6e9830cdd80948597f136dfbffefa60599c @@ -28615,9 +27269,9 @@ __metadata: linkType: hard "diff@npm:^4.0.1": - version: 4.0.2 - resolution: "diff@npm:4.0.2" - checksum: 10/ec09ec2101934ca5966355a229d77afcad5911c92e2a77413efda5455636c4cf2ce84057e2d7715227a2eeeda04255b849bd3ae3a4dd22eb22e86e76456df069 + version: 4.0.4 + resolution: "diff@npm:4.0.4" + checksum: 10/5019b3f5ae124ea9e95137119e1a83a59c252c75ddac873cc967832fd7a834570a58a4d58b941bdbd07832ebf98dcb232b27c561b7f5584357da6dae59bcac62 languageName: node linkType: hard @@ -28628,6 +27282,13 @@ __metadata: languageName: node linkType: hard +"diff@npm:~8.0.2": + version: 8.0.2 + resolution: "diff@npm:8.0.2" + checksum: 10/82a2120d3418f97822e17a6044ccd4b99a91e26e145e8698353673d7146bd2d092bbebb79c112aae7badc7b9c526f9098cbe342f96174feb6beabdd2587b3c42 + languageName: node + linkType: hard + "diffie-hellman@npm:^5.0.3": version: 5.0.3 resolution: "diffie-hellman@npm:5.0.3" @@ -28671,62 +27332,39 @@ __metadata: languageName: node linkType: hard -"docker-compose@npm:^0.24.8": - version: 0.24.8 - resolution: "docker-compose@npm:0.24.8" +"docker-compose@npm:^1.3.2": + version: 1.3.2 + resolution: "docker-compose@npm:1.3.2" dependencies: yaml: "npm:^2.2.2" - checksum: 10/2b8526f9797a55c819ff2d7dcea57085b012b3a3d77bc2e1a6b45c3fc9e82196312f5298cbe8299966462454a5ac8f68814bb407736b4385e0d226a2a39e877a + checksum: 10/4ba2d2090c4df3a3d3c8fd2dc086ddbddbc9f1079ab0c60b65fc9b7f7d0bac2ebb6ea366cc7324162a9d1eb85be1c8809f892cd9a2e46dfc994222f1bb3cdd95 languageName: node linkType: hard -"docker-modem@npm:^3.0.0": - version: 3.0.3 - resolution: "docker-modem@npm:3.0.3" - dependencies: - debug: "npm:^4.1.1" - readable-stream: "npm:^3.5.0" - split-ca: "npm:^1.0.1" - ssh2: "npm:^1.4.0" - checksum: 10/266030d49961a82810152150b13be459490eabb00ca319786930a31ca4a0491bf1ea89ac43b7972a7dbc807e1f73b92bb482be0682247cbf1180b58b80fc08d7 - languageName: node - linkType: hard - -"docker-modem@npm:^5.0.6": - version: 5.0.6 - resolution: "docker-modem@npm:5.0.6" +"docker-modem@npm:^5.0.7": + version: 5.0.7 + resolution: "docker-modem@npm:5.0.7" dependencies: debug: "npm:^4.1.1" readable-stream: "npm:^3.5.0" split-ca: "npm:^1.0.1" ssh2: "npm:^1.15.0" - checksum: 10/4977797814c29205f0762215f2e3e26600986bb65139018ff6840ff4c596e5d19f3002be1abcc5e73e3828870bb73bab28275a6458ad027ed56ab61fca014b6d + checksum: 10/8c0dc9908e10fbc91c35b187fc6a67a0dcbe4b33a2198dfa67cd8304e0f2452325e1639215674d6e441731d0bf27f06339550f6c3767585b877601d2f16e43e2 languageName: node linkType: hard -"dockerode@npm:^3.3.5": - version: 3.3.5 - resolution: "dockerode@npm:3.3.5" - dependencies: - "@balena/dockerignore": "npm:^1.0.2" - docker-modem: "npm:^3.0.0" - tar-fs: "npm:~2.0.1" - checksum: 10/1748e8d96f88fe71bb165a4c05726904937f5863b69eaeb4a3c1bb3bbf66940c7bef13b349ff757dc43664b4367611aab76f35c1ba468f07dcbaba567e6acd88 - languageName: node - linkType: hard - -"dockerode@npm:^4.0.0": - version: 4.0.9 - resolution: "dockerode@npm:4.0.9" +"dockerode@npm:^4.0.0, dockerode@npm:^4.0.9": + version: 4.0.10 + resolution: "dockerode@npm:4.0.10" dependencies: "@balena/dockerignore": "npm:^1.0.2" "@grpc/grpc-js": "npm:^1.11.1" "@grpc/proto-loader": "npm:^0.7.13" - docker-modem: "npm:^5.0.6" + docker-modem: "npm:^5.0.7" protobufjs: "npm:^7.3.2" tar-fs: "npm:^2.1.4" uuid: "npm:^10.0.0" - checksum: 10/58bb4f39652de88212c008d1156ab679ac561508ada0a86db4c2fc75dc13d40c0ba1afb725a28e0c899c93a76ad822b332e4d5207c36b8b929bae5730d0bd791 + checksum: 10/283c418cab04ef36dae7776baf1088d0eca3d47241d7de92af3f70f7a56e26bc4a1c8e36313c6a91681bdace01b2142203a411aed8011d8d83ec2fcbab3f2145 languageName: node linkType: hard @@ -28817,15 +27455,6 @@ __metadata: languageName: node linkType: hard -"domexception@npm:^4.0.0": - version: 4.0.0 - resolution: "domexception@npm:4.0.0" - dependencies: - webidl-conversions: "npm:^7.0.0" - checksum: 10/4ed443227d2871d76c58d852b2e93c68e0443815b2741348f20881bedee8c1ad4f9bfc5d30c7dec433cd026b57da63407c010260b1682fef4c8847e7181ea43f - languageName: node - linkType: hard - "domhandler@npm:^4.0.0, domhandler@npm:^4.2.0, domhandler@npm:^4.3.1": version: 4.3.1 resolution: "domhandler@npm:4.3.1" @@ -28844,27 +27473,15 @@ __metadata: languageName: node linkType: hard -"dompurify@npm:=3.2.4": - version: 3.2.4 - resolution: "dompurify@npm:3.2.4" +"dompurify@npm:^3.1.7, dompurify@npm:^3.3.2": + version: 3.4.0 + resolution: "dompurify@npm:3.4.0" dependencies: "@types/trusted-types": "npm:^2.0.7" dependenciesMeta: "@types/trusted-types": optional: true - checksum: 10/98570c53385518a2f9b617f796926338856acfdd3369c88b5905bddf96bd7d391bf8a5433127155e0046e6faa2bfb767185fcd571b865dfabe624c099e2537f5 - languageName: node - linkType: hard - -"dompurify@npm:^3.1.7, dompurify@npm:^3.2.4": - version: 3.2.6 - resolution: "dompurify@npm:3.2.6" - dependencies: - "@types/trusted-types": "npm:^2.0.7" - dependenciesMeta: - "@types/trusted-types": - optional: true - checksum: 10/b91631ed0e4d17fae950ef53613cc009ed7e73adc43ac94a41dd52f35483f7538d13caebdafa7626e0da145fc8184e7ac7935f14f25b7e841b32fda777e40447 + checksum: 10/ead40b78ec51cd451f2c74fada4233ee0afeafdbab54af2f4a4bd5d4d138ac04d0d85140e79f533803ecfd1c3758edc1176087039c1e7217824f9794a9d34d2c languageName: node linkType: hard @@ -28932,7 +27549,7 @@ __metadata: languageName: node linkType: hard -"dset@npm:^3.1.2, dset@npm:^3.1.4": +"dset@npm:^3.1.2": version: 3.1.4 resolution: "dset@npm:3.1.4" checksum: 10/6268c9e2049c8effe6e5a1952f02826e8e32468b5ced781f15f8f3b1c290da37626246fec014fbdd1503413f981dff6abd8a4c718ec9952fd45fccb6ac9de43f @@ -29007,9 +27624,9 @@ __metadata: "@backstage/create-app": "workspace:^" "@backstage/errors": "workspace:^" "@types/fs-extra": "npm:^11.0.0" - "@types/node": "npm:^20.16.0" + "@types/node": "npm:^22.13.14" chalk: "npm:^4.0.0" - commander: "npm:^12.0.0" + commander: "npm:^14.0.3" cross-fetch: "npm:^4.0.0" fs-extra: "npm:^11.2.0" handlebars: "npm:^4.7.3" @@ -29041,19 +27658,6 @@ __metadata: languageName: node linkType: hard -"easy-table@npm:1.2.0": - version: 1.2.0 - resolution: "easy-table@npm:1.2.0" - dependencies: - ansi-regex: "npm:^5.0.1" - wcwidth: "npm:^1.0.1" - dependenciesMeta: - wcwidth: - optional: true - checksum: 10/0d1be7cd9419cd1b56ca5a978646b3cff241ccd8cf95bdb2742f36854084b3aef2e9af6ec14142855aa80e4cab1f4baad0f610a99c77509f23676b8330730177 - languageName: node - linkType: hard - "ebnf@npm:^1.9.1": version: 1.9.1 resolution: "ebnf@npm:1.9.1" @@ -29091,25 +27695,18 @@ __metadata: linkType: hard "elastic-builder@npm:^2.16.0": - version: 2.30.0 - resolution: "elastic-builder@npm:2.30.0" + version: 2.31.1 + resolution: "elastic-builder@npm:2.31.1" dependencies: - lodash.has: "npm:^4.5.2" - lodash.hasin: "npm:^4.5.2" - lodash.head: "npm:^4.0.1" - lodash.isempty: "npm:^4.4.0" - lodash.isnil: "npm:^4.0.0" - lodash.isobject: "npm:^3.0.2" - lodash.isstring: "npm:^4.0.1" - lodash.omit: "npm:^4.5.0" - checksum: 10/10b3e0cd3b24c680a9be6a480e4eb7869daf52ff536b56132f9b906fd577cea8eabfd5baf691768ed92cbb23454575b85ec760a72f7a743ee35b43d199749a4e + lodash: "npm:^4.17.21" + checksum: 10/8aadd17710114ddd313c9ed3d023769985257877888e466783230d7bdcc5a7747ce0542ad27802af62a0a99791099e470e2f2b83c114007c445d6d0061496719 languageName: node linkType: hard -"electron-to-chromium@npm:^1.5.41": - version: 1.5.65 - resolution: "electron-to-chromium@npm:1.5.65" - checksum: 10/9d4e5609de75fc92aeff10976fd8fa2b9b6294edb3eb7c205e51e3e5f42c3d5f6d1e0323bb52ed4831121ee617962c9a8eafff5aee5651d33bee98677940b4be +"electron-to-chromium@npm:^1.5.263": + version: 1.5.286 + resolution: "electron-to-chromium@npm:1.5.286" + checksum: 10/530ae36571f3f737431dc1f97ab176d9ec38d78e7a14a78fff78540769ef139e9011200a886864111ee26d64e647136531ff004f368f5df8cdd755c45ad97649 languageName: node linkType: hard @@ -29128,6 +27725,41 @@ __metadata: languageName: node linkType: hard +"embedded-postgres@npm:18.3.0-beta.16": + version: 18.3.0-beta.16 + resolution: "embedded-postgres@npm:18.3.0-beta.16" + dependencies: + "@embedded-postgres/darwin-arm64": "npm:^18.3.0-beta.16" + "@embedded-postgres/darwin-x64": "npm:^18.3.0-beta.16" + "@embedded-postgres/linux-arm": "npm:^18.3.0-beta.16" + "@embedded-postgres/linux-arm64": "npm:^18.3.0-beta.16" + "@embedded-postgres/linux-ia32": "npm:^18.3.0-beta.16" + "@embedded-postgres/linux-ppc64": "npm:^18.3.0-beta.16" + "@embedded-postgres/linux-x64": "npm:^18.3.0-beta.16" + "@embedded-postgres/windows-x64": "npm:^18.3.0-beta.16" + async-exit-hook: "npm:^2.0.1" + pg: "npm:^8.7.3" + dependenciesMeta: + "@embedded-postgres/darwin-arm64": + optional: true + "@embedded-postgres/darwin-x64": + optional: true + "@embedded-postgres/linux-arm": + optional: true + "@embedded-postgres/linux-arm64": + optional: true + "@embedded-postgres/linux-ia32": + optional: true + "@embedded-postgres/linux-ppc64": + optional: true + "@embedded-postgres/linux-x64": + optional: true + "@embedded-postgres/windows-x64": + optional: true + checksum: 10/13ebdec978559d8d5496df521ec6d6a717a6a3e234a7daa1d3d85e8d050626cde927e5d1d382c70eec219afa721d3c28c26a39023de0a5919feb535470860b47 + languageName: node + linkType: hard + "emittery@npm:^0.13.1": version: 0.13.1 resolution: "emittery@npm:0.13.1" @@ -29156,6 +27788,13 @@ __metadata: languageName: node linkType: hard +"emojilib@npm:^2.4.0": + version: 2.4.0 + resolution: "emojilib@npm:2.4.0" + checksum: 10/bef767eca49acaa881388d91bee6936ea57ae367d603d5227ff0a9da3e2d1e774a61c447e5f2f4901797d023c4b5239bc208285b6172a880d3655024a0f44980 + languageName: node + linkType: hard + "emojis-list@npm:^3.0.0": version: 3.0.0 resolution: "emojis-list@npm:3.0.0" @@ -29163,6 +27802,13 @@ __metadata: languageName: node linkType: hard +"empathic@npm:^2.0.0": + version: 2.0.0 + resolution: "empathic@npm:2.0.0" + checksum: 10/90f47d93f8d1db3aa00ce1bfae2940bf76379dbb34bd562edbd92c3564a173cb1d6bd3cadb645fad0224839c25886abde801155d9b972dda6add7a5cc8b35d48 + languageName: node + linkType: hard + "enabled@npm:2.0.x": version: 2.0.0 resolution: "enabled@npm:2.0.0" @@ -29179,13 +27825,6 @@ __metadata: languageName: node linkType: hard -"encodeurl@npm:^1.0.2, encodeurl@npm:~1.0.2": - version: 1.0.2 - resolution: "encodeurl@npm:1.0.2" - checksum: 10/e50e3d508cdd9c4565ba72d2012e65038e5d71bdc9198cb125beb6237b5b1ade6c0d343998da9e170fb2eae52c1bed37d4d6d98a46ea423a0cddbed5ac3f780c - languageName: node - linkType: hard - "encodeurl@npm:^2.0.0, encodeurl@npm:~2.0.0": version: 2.0.0 resolution: "encodeurl@npm:2.0.0" @@ -29193,6 +27832,13 @@ __metadata: languageName: node linkType: hard +"encodeurl@npm:~1.0.2": + version: 1.0.2 + resolution: "encodeurl@npm:1.0.2" + checksum: 10/e50e3d508cdd9c4565ba72d2012e65038e5d71bdc9198cb125beb6237b5b1ade6c0d343998da9e170fb2eae52c1bed37d4d6d98a46ea423a0cddbed5ac3f780c + languageName: node + linkType: hard + "encoding@npm:^0.1.12, encoding@npm:^0.1.13": version: 0.1.13 resolution: "encoding@npm:0.1.13" @@ -29211,22 +27857,13 @@ __metadata: languageName: node linkType: hard -"end-of-stream@npm:~1.1.0": - version: 1.1.0 - resolution: "end-of-stream@npm:1.1.0" - dependencies: - once: "npm:~1.3.0" - checksum: 10/9fa637e259e50e5e3634e8e14064a183bd0d407733594631362f9df596409739bef5f7064840e6725212a9edc8b4a70a5a3088ac423e8564f9dc183dd098c719 - languageName: node - linkType: hard - -"enhanced-resolve@npm:^5.17.1, enhanced-resolve@npm:^5.17.3, enhanced-resolve@npm:^5.18.0": - version: 5.18.3 - resolution: "enhanced-resolve@npm:5.18.3" +"enhanced-resolve@npm:^5.18.0, enhanced-resolve@npm:^5.20.0": + version: 5.20.0 + resolution: "enhanced-resolve@npm:5.20.0" dependencies: graceful-fs: "npm:^4.2.4" - tapable: "npm:^2.2.0" - checksum: 10/a4d0a1eacba3079f617b68c8f7e17583c3cbc572055c2edca41c0fa0230a49f6e9b2c6ffd4128cc5f84e15ea6cc313ae2b01e1057fcd252fabef70220a5d9f6a + tapable: "npm:^2.3.0" + checksum: 10/ba22699e4b46dc1be6441c359636ebcdd5028229219a7d6ba10f39996401f950967f8297ddf3284d0ee8e33c8133a8742696154e383cc08d8bd2bf80ba87df97 languageName: node linkType: hard @@ -29254,10 +27891,10 @@ __metadata: languageName: node linkType: hard -"entities@npm:~2.1.0": - version: 2.1.0 - resolution: "entities@npm:2.1.0" - checksum: 10/fe71642e42e108540b0324dea03e00f3dbad93617c601bfcf292c3f852c236af3e58469219c4653f6f05df781a446f3b82105b8d26b936d0fa246b0103f2f951 +"entities@npm:^6.0.0": + version: 6.0.1 + resolution: "entities@npm:6.0.1" + checksum: 10/62af1307202884349d2867f0aac5c60d8b57102ea0b0e768b16246099512c28e239254ad772d6834e7e14cb1b6f153fc3d0c031934e3183b086c86d3838d874a languageName: node linkType: hard @@ -29307,25 +27944,26 @@ __metadata: languageName: node linkType: hard -"es-abstract@npm:^1.17.5, es-abstract@npm:^1.20.4, es-abstract@npm:^1.22.1, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.6": - version: 1.23.7 - resolution: "es-abstract@npm:1.23.7" +"es-abstract@npm:^1.17.5, es-abstract@npm:^1.20.4, es-abstract@npm:^1.22.1, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.6, es-abstract@npm:^1.23.9, es-abstract@npm:^1.24.0": + version: 1.24.0 + resolution: "es-abstract@npm:1.24.0" dependencies: array-buffer-byte-length: "npm:^1.0.2" arraybuffer.prototype.slice: "npm:^1.0.4" available-typed-arrays: "npm:^1.0.7" call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.3" + call-bound: "npm:^1.0.4" data-view-buffer: "npm:^1.0.2" data-view-byte-length: "npm:^1.0.2" data-view-byte-offset: "npm:^1.0.1" es-define-property: "npm:^1.0.1" es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.0.0" - es-set-tostringtag: "npm:^2.0.3" + es-object-atoms: "npm:^1.1.1" + es-set-tostringtag: "npm:^2.1.0" es-to-primitive: "npm:^1.3.0" function.prototype.name: "npm:^1.1.8" - get-intrinsic: "npm:^1.2.6" + get-intrinsic: "npm:^1.3.0" + get-proto: "npm:^1.0.1" get-symbol-description: "npm:^1.1.0" globalthis: "npm:^1.0.4" gopd: "npm:^1.2.0" @@ -29337,18 +27975,24 @@ __metadata: is-array-buffer: "npm:^3.0.5" is-callable: "npm:^1.2.7" is-data-view: "npm:^1.0.2" + is-negative-zero: "npm:^2.0.3" is-regex: "npm:^1.2.1" + is-set: "npm:^2.0.3" is-shared-array-buffer: "npm:^1.0.4" is-string: "npm:^1.1.1" is-typed-array: "npm:^1.1.15" - is-weakref: "npm:^1.1.0" + is-weakref: "npm:^1.1.1" math-intrinsics: "npm:^1.1.0" - object-inspect: "npm:^1.13.3" + object-inspect: "npm:^1.13.4" object-keys: "npm:^1.1.1" object.assign: "npm:^4.1.7" - regexp.prototype.flags: "npm:^1.5.3" + own-keys: "npm:^1.0.1" + regexp.prototype.flags: "npm:^1.5.4" safe-array-concat: "npm:^1.1.3" + safe-push-apply: "npm:^1.0.0" safe-regex-test: "npm:^1.1.0" + set-proto: "npm:^1.0.0" + stop-iteration-iterator: "npm:^1.1.0" string.prototype.trim: "npm:^1.2.10" string.prototype.trimend: "npm:^1.0.9" string.prototype.trimstart: "npm:^1.0.8" @@ -29357,8 +28001,8 @@ __metadata: typed-array-byte-offset: "npm:^1.0.4" typed-array-length: "npm:^1.0.7" unbox-primitive: "npm:^1.1.0" - which-typed-array: "npm:^1.1.18" - checksum: 10/c079f4e3fe25af0bccf483383a2dd170d69483f93cbd34ad534da0749d3634f59f907edfee898963a9fd5c507d16336fa3d5ce77e1d5d36eae6c00d835ebd423 + which-typed-array: "npm:^1.1.19" + checksum: 10/64e07a886f7439cf5ccfc100f9716e6173e10af6071a50a5031afbdde474a3dbc9619d5965da54e55f8908746a9134a46be02af8c732d574b7b81ed3124e2daf languageName: node linkType: hard @@ -29439,13 +28083,20 @@ __metadata: languageName: node linkType: hard -"es-module-lexer@npm:^1.2.1, es-module-lexer@npm:^1.3.1": +"es-module-lexer@npm:^1.6.0": version: 1.7.0 resolution: "es-module-lexer@npm:1.7.0" checksum: 10/b6f3e576a3fed4d82b0d0ad4bbf6b3a5ad694d2e7ce8c4a069560da3db6399381eaba703616a182b16dde50ce998af64e07dcf49f2ae48153b9e07be3f107087 languageName: node linkType: hard +"es-module-lexer@npm:^2.0.0": + version: 2.0.0 + resolution: "es-module-lexer@npm:2.0.0" + checksum: 10/b075855289b5f40ee496f3d7525c5c501d029c3da15c22298a0030d625bf36d1da0768b26278f7f4bada2a602459b505888e20b77c414fba5da5619b0e84dbd1 + languageName: node + linkType: hard + "es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": version: 1.1.1 resolution: "es-object-atoms@npm:1.1.1" @@ -29467,12 +28118,12 @@ __metadata: languageName: node linkType: hard -"es-shim-unscopables@npm:^1.0.0, es-shim-unscopables@npm:^1.0.2": - version: 1.0.2 - resolution: "es-shim-unscopables@npm:1.0.2" +"es-shim-unscopables@npm:^1.0.2, es-shim-unscopables@npm:^1.1.0": + version: 1.1.0 + resolution: "es-shim-unscopables@npm:1.1.0" dependencies: - hasown: "npm:^2.0.0" - checksum: 10/6d3bf91f658a27cc7217cd32b407a0d714393a84d125ad576319b9e83a893bea165cf41270c29e9ceaa56d3cf41608945d7e2a2c31fd51c0009b0c31402b91c7 + hasown: "npm:^2.0.2" + checksum: 10/c351f586c30bbabc62355be49564b2435468b52c3532b8a1663672e3d10dc300197e69c247869dd173e56d86423ab95fc0c10b0939cdae597094e0fdca078cba languageName: node linkType: hard @@ -29540,60 +28191,49 @@ __metadata: linkType: hard "esbuild-loader@npm:^4.0.0": - version: 4.2.2 - resolution: "esbuild-loader@npm:4.2.2" + version: 4.4.3 + resolution: "esbuild-loader@npm:4.4.3" dependencies: - esbuild: "npm:^0.21.0" - get-tsconfig: "npm:^4.7.0" + esbuild: "npm:^0.27.1" + get-tsconfig: "npm:^4.10.1" loader-utils: "npm:^2.0.4" - webpack-sources: "npm:^1.4.3" + webpack-sources: "npm:^3.3.4" peerDependencies: webpack: ^4.40.0 || ^5.0.0 - checksum: 10/235d06c60e26827333c3c66df6e2f13fef31e1b84b8310ba36ebdb6aaa31abdf7628a940326e4923824cdb501c8ce6a6e9129c4e465daaeab112f0b5db3423f4 + checksum: 10/e5f7c2152e06abba37612c1c30d3ae161cc0c3168f671f4e7d37b1925155c6a4f80a3fde452370164a6069ec7d3cf34d064443efce83ecb975b0576cb0b909ec languageName: node linkType: hard -"esbuild-register@npm:^3.5.0": - version: 3.6.0 - resolution: "esbuild-register@npm:3.6.0" +"esbuild@npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0, esbuild@npm:^0.27.0, esbuild@npm:^0.27.1": + version: 0.27.4 + resolution: "esbuild@npm:0.27.4" dependencies: - debug: "npm:^4.3.4" - peerDependencies: - esbuild: ">=0.12 <1" - checksum: 10/4ae1a016e3dad5b53c3d68cf07e31d8c1cec1a0b584038ece726097ac80bd33ab48fb224c766c9b341c04793837e652461eaca9327a116e7564f553b61ccca71 - languageName: node - linkType: hard - -"esbuild@npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0, esbuild@npm:^0.25.0": - version: 0.25.8 - resolution: "esbuild@npm:0.25.8" - dependencies: - "@esbuild/aix-ppc64": "npm:0.25.8" - "@esbuild/android-arm": "npm:0.25.8" - "@esbuild/android-arm64": "npm:0.25.8" - "@esbuild/android-x64": "npm:0.25.8" - "@esbuild/darwin-arm64": "npm:0.25.8" - "@esbuild/darwin-x64": "npm:0.25.8" - "@esbuild/freebsd-arm64": "npm:0.25.8" - "@esbuild/freebsd-x64": "npm:0.25.8" - "@esbuild/linux-arm": "npm:0.25.8" - "@esbuild/linux-arm64": "npm:0.25.8" - "@esbuild/linux-ia32": "npm:0.25.8" - "@esbuild/linux-loong64": "npm:0.25.8" - "@esbuild/linux-mips64el": "npm:0.25.8" - "@esbuild/linux-ppc64": "npm:0.25.8" - "@esbuild/linux-riscv64": "npm:0.25.8" - "@esbuild/linux-s390x": "npm:0.25.8" - "@esbuild/linux-x64": "npm:0.25.8" - "@esbuild/netbsd-arm64": "npm:0.25.8" - "@esbuild/netbsd-x64": "npm:0.25.8" - "@esbuild/openbsd-arm64": "npm:0.25.8" - "@esbuild/openbsd-x64": "npm:0.25.8" - "@esbuild/openharmony-arm64": "npm:0.25.8" - "@esbuild/sunos-x64": "npm:0.25.8" - "@esbuild/win32-arm64": "npm:0.25.8" - "@esbuild/win32-ia32": "npm:0.25.8" - "@esbuild/win32-x64": "npm:0.25.8" + "@esbuild/aix-ppc64": "npm:0.27.4" + "@esbuild/android-arm": "npm:0.27.4" + "@esbuild/android-arm64": "npm:0.27.4" + "@esbuild/android-x64": "npm:0.27.4" + "@esbuild/darwin-arm64": "npm:0.27.4" + "@esbuild/darwin-x64": "npm:0.27.4" + "@esbuild/freebsd-arm64": "npm:0.27.4" + "@esbuild/freebsd-x64": "npm:0.27.4" + "@esbuild/linux-arm": "npm:0.27.4" + "@esbuild/linux-arm64": "npm:0.27.4" + "@esbuild/linux-ia32": "npm:0.27.4" + "@esbuild/linux-loong64": "npm:0.27.4" + "@esbuild/linux-mips64el": "npm:0.27.4" + "@esbuild/linux-ppc64": "npm:0.27.4" + "@esbuild/linux-riscv64": "npm:0.27.4" + "@esbuild/linux-s390x": "npm:0.27.4" + "@esbuild/linux-x64": "npm:0.27.4" + "@esbuild/netbsd-arm64": "npm:0.27.4" + "@esbuild/netbsd-x64": "npm:0.27.4" + "@esbuild/openbsd-arm64": "npm:0.27.4" + "@esbuild/openbsd-x64": "npm:0.27.4" + "@esbuild/openharmony-arm64": "npm:0.27.4" + "@esbuild/sunos-x64": "npm:0.27.4" + "@esbuild/win32-arm64": "npm:0.27.4" + "@esbuild/win32-ia32": "npm:0.27.4" + "@esbuild/win32-x64": "npm:0.27.4" dependenciesMeta: "@esbuild/aix-ppc64": optional: true @@ -29649,87 +28289,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 10/9897411732768e652d90fa5dfadae965e8f420d24e5f23fa0604331a1441769e2c7ee4e41ca53e926f1fb51a53af52e01fc9070fdc1a4edf3e9ec9208ee41273 - languageName: node - linkType: hard - -"esbuild@npm:^0.21.0": - version: 0.21.5 - resolution: "esbuild@npm:0.21.5" - dependencies: - "@esbuild/aix-ppc64": "npm:0.21.5" - "@esbuild/android-arm": "npm:0.21.5" - "@esbuild/android-arm64": "npm:0.21.5" - "@esbuild/android-x64": "npm:0.21.5" - "@esbuild/darwin-arm64": "npm:0.21.5" - "@esbuild/darwin-x64": "npm:0.21.5" - "@esbuild/freebsd-arm64": "npm:0.21.5" - "@esbuild/freebsd-x64": "npm:0.21.5" - "@esbuild/linux-arm": "npm:0.21.5" - "@esbuild/linux-arm64": "npm:0.21.5" - "@esbuild/linux-ia32": "npm:0.21.5" - "@esbuild/linux-loong64": "npm:0.21.5" - "@esbuild/linux-mips64el": "npm:0.21.5" - "@esbuild/linux-ppc64": "npm:0.21.5" - "@esbuild/linux-riscv64": "npm:0.21.5" - "@esbuild/linux-s390x": "npm:0.21.5" - "@esbuild/linux-x64": "npm:0.21.5" - "@esbuild/netbsd-x64": "npm:0.21.5" - "@esbuild/openbsd-x64": "npm:0.21.5" - "@esbuild/sunos-x64": "npm:0.21.5" - "@esbuild/win32-arm64": "npm:0.21.5" - "@esbuild/win32-ia32": "npm:0.21.5" - "@esbuild/win32-x64": "npm:0.21.5" - dependenciesMeta: - "@esbuild/aix-ppc64": - optional: true - "@esbuild/android-arm": - optional: true - "@esbuild/android-arm64": - optional: true - "@esbuild/android-x64": - optional: true - "@esbuild/darwin-arm64": - optional: true - "@esbuild/darwin-x64": - optional: true - "@esbuild/freebsd-arm64": - optional: true - "@esbuild/freebsd-x64": - optional: true - "@esbuild/linux-arm": - optional: true - "@esbuild/linux-arm64": - optional: true - "@esbuild/linux-ia32": - optional: true - "@esbuild/linux-loong64": - optional: true - "@esbuild/linux-mips64el": - optional: true - "@esbuild/linux-ppc64": - optional: true - "@esbuild/linux-riscv64": - optional: true - "@esbuild/linux-s390x": - optional: true - "@esbuild/linux-x64": - optional: true - "@esbuild/netbsd-x64": - optional: true - "@esbuild/openbsd-x64": - optional: true - "@esbuild/sunos-x64": - optional: true - "@esbuild/win32-arm64": - optional: true - "@esbuild/win32-ia32": - optional: true - "@esbuild/win32-x64": - optional: true - bin: - esbuild: bin/esbuild - checksum: 10/d2ff2ca84d30cce8e871517374d6c2290835380dc7cd413b2d49189ed170d45e407be14de2cb4794cf76f75cf89955c4714726ebd3de7444b3046f5cab23ab6b + checksum: 10/32b46ec22ef78bae6cc141145022a4c0209852c07151f037fbefccc2033ca54e7f33705f8fca198eb7026f400142f64c2dbc9f0d0ce9c0a638ebc472a04abc4a languageName: node linkType: hard @@ -29791,26 +28351,7 @@ __metadata: languageName: node linkType: hard -"escodegen@npm:^1.8.1": - version: 1.14.3 - resolution: "escodegen@npm:1.14.3" - dependencies: - esprima: "npm:^4.0.1" - estraverse: "npm:^4.2.0" - esutils: "npm:^2.0.2" - optionator: "npm:^0.8.1" - source-map: "npm:~0.6.1" - dependenciesMeta: - source-map: - optional: true - bin: - escodegen: bin/escodegen.js - esgenerate: bin/esgenerate.js - checksum: 10/70f095ca9393535f9f1c145ef99dc0b3ff14cca6bc4a79d90ff3352f90c3f2e07f75af6d6c05174ea67c45271f75e80dd440dd7d04ed2cf44c9452c3042fa84a - languageName: node - linkType: hard - -"escodegen@npm:^2.0.0, escodegen@npm:^2.1.0": +"escodegen@npm:^2.1.0": version: 2.1.0 resolution: "escodegen@npm:2.1.0" dependencies: @@ -29863,15 +28404,15 @@ __metadata: languageName: node linkType: hard -"eslint-module-utils@npm:^2.12.0": - version: 2.12.0 - resolution: "eslint-module-utils@npm:2.12.0" +"eslint-module-utils@npm:^2.12.1": + version: 2.12.1 + resolution: "eslint-module-utils@npm:2.12.1" dependencies: debug: "npm:^3.2.7" peerDependenciesMeta: eslint: optional: true - checksum: 10/dd27791147eca17366afcb83f47d6825b6ce164abb256681e5de4ec1d7e87d8605641eb869298a0dbc70665e2446dbcc2f40d3e1631a9475dd64dd23d4ca5dee + checksum: 10/bd25d6610ec3abaa50e8f1beb0119541562bbb8dd02c035c7e887976fe1e0c5dd8175f4607ca8d86d1146df24d52a071bd3d1dd329f6902bd58df805a8ca16d3 languageName: node linkType: hard @@ -29890,37 +28431,37 @@ __metadata: linkType: hard "eslint-plugin-import@npm:^2.31.0": - version: 2.31.0 - resolution: "eslint-plugin-import@npm:2.31.0" + version: 2.32.0 + resolution: "eslint-plugin-import@npm:2.32.0" dependencies: "@rtsao/scc": "npm:^1.1.0" - array-includes: "npm:^3.1.8" - array.prototype.findlastindex: "npm:^1.2.5" - array.prototype.flat: "npm:^1.3.2" - array.prototype.flatmap: "npm:^1.3.2" + array-includes: "npm:^3.1.9" + array.prototype.findlastindex: "npm:^1.2.6" + array.prototype.flat: "npm:^1.3.3" + array.prototype.flatmap: "npm:^1.3.3" debug: "npm:^3.2.7" doctrine: "npm:^2.1.0" eslint-import-resolver-node: "npm:^0.3.9" - eslint-module-utils: "npm:^2.12.0" + eslint-module-utils: "npm:^2.12.1" hasown: "npm:^2.0.2" - is-core-module: "npm:^2.15.1" + is-core-module: "npm:^2.16.1" is-glob: "npm:^4.0.3" minimatch: "npm:^3.1.2" object.fromentries: "npm:^2.0.8" object.groupby: "npm:^1.0.3" - object.values: "npm:^1.2.0" + object.values: "npm:^1.2.1" semver: "npm:^6.3.1" - string.prototype.trimend: "npm:^1.0.8" + string.prototype.trimend: "npm:^1.0.9" tsconfig-paths: "npm:^3.15.0" peerDependencies: eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 - checksum: 10/6b76bd009ac2db0615d9019699d18e2a51a86cb8c1d0855a35fb1b418be23b40239e6debdc6e8c92c59f1468ed0ea8d7b85c817117a113d5cc225be8a02ad31c + checksum: 10/1bacf4967e9ebf99e12176a795f0d6d3a87d1c9a030c2207f27b267e10d96a1220be2647504c7fc13ab543cdf13ffef4b8f5620e0447032dba4ff0d3922f7c9e languageName: node linkType: hard "eslint-plugin-jest@npm:^28.9.0": - version: 28.9.0 - resolution: "eslint-plugin-jest@npm:28.9.0" + version: 28.14.0 + resolution: "eslint-plugin-jest@npm:28.14.0" dependencies: "@typescript-eslint/utils": "npm:^6.0.0 || ^7.0.0 || ^8.0.0" peerDependencies: @@ -29932,7 +28473,7 @@ __metadata: optional: true jest: optional: true - checksum: 10/399dd882205da996c2d46b768b744093346ffe4f730972825c72a3f85cc2e26bc08ccd73a7b487f8da4c39fa0178c58b571d5d611a22ced5518b5dfccb48d906 + checksum: 10/6032497bd97d6dd010450d5fdf535b8613a2789f4f83764ae04361c48d06d92f3d9b2e4350914b8fd857b6e611ba2b5282a1133ab8ec51b3e7053f9d336058e6 languageName: node linkType: hard @@ -29961,6 +28502,17 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-node-import@npm:^1.0.5": + version: 1.2.0 + resolution: "eslint-plugin-node-import@npm:1.2.0" + dependencies: + globals: "npm:^17.3.0" + peerDependencies: + eslint: ">=7" + checksum: 10/bfd36ad4e8d74cc0da900151c19cb40328e62218e730055327d7c35b53af103e37d414701b7de2269b1eabcc8fafab0972e98a48489ac9f367e39618138aeecd + languageName: node + linkType: hard + "eslint-plugin-notice@npm:^1.0.0": version: 1.0.0 resolution: "eslint-plugin-notice@npm:1.0.0" @@ -30011,58 +28563,59 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-storybook@npm:^9.1.7": - version: 9.1.16 - resolution: "eslint-plugin-storybook@npm:9.1.16" +"eslint-plugin-storybook@npm:^10.3.3": + version: 10.3.3 + resolution: "eslint-plugin-storybook@npm:10.3.3" dependencies: - "@typescript-eslint/utils": "npm:^8.8.1" + "@typescript-eslint/utils": "npm:^8.48.0" peerDependencies: eslint: ">=8" - storybook: ^9.1.16 - checksum: 10/3142e70ee2a8cead5c891b76861914dc5e675d85be758171e4c63db912351e57da0ce4cbccf5a3cf7aaf9cf8fc2f40cf6110ef9a459d0c61cef287b82570e88b + storybook: ^10.3.3 + checksum: 10/195ac728b9dded0820e02deaa808b66be87beaf206c11f4858c80c8aa4ddc86ee2a9e2d404b83e6776f92690c274d5268b2c01f77b87102995ee2b9bdb026857 languageName: node linkType: hard -"eslint-plugin-testing-library@npm:^6.0.0": - version: 6.5.0 - resolution: "eslint-plugin-testing-library@npm:6.5.0" +"eslint-plugin-testing-library@npm:^7.0.0": + version: 7.16.2 + resolution: "eslint-plugin-testing-library@npm:7.16.2" dependencies: - "@typescript-eslint/utils": "npm:^5.62.0" + "@typescript-eslint/scope-manager": "npm:^8.56.0" + "@typescript-eslint/utils": "npm:^8.56.0" peerDependencies: - eslint: ^7.5.0 || ^8.0.0 || ^9.0.0 - checksum: 10/5ce5f71aed5dc39315f7fa2e987c7e1ffc78f7d35164c2d8769ad29000558828dc13c04bfef289c28faf57749ce7720e5ab17869780b743bc1d8cd59a5052a43 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + checksum: 10/6aa06995f147986ce9b923479caca9b8a5f703c52ef3bf57d9757953ded448f940b2dbb12d85217a348fc5b754a20ecdb70b9ee39856b202e89f43e648f788a7 languageName: node linkType: hard "eslint-plugin-unused-imports@npm:^4.1.4": - version: 4.1.4 - resolution: "eslint-plugin-unused-imports@npm:4.1.4" + version: 4.4.1 + resolution: "eslint-plugin-unused-imports@npm:4.4.1" peerDependencies: "@typescript-eslint/eslint-plugin": ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0 - eslint: ^9.0.0 || ^8.0.0 + eslint: ^10.0.0 || ^9.0.0 || ^8.0.0 peerDependenciesMeta: "@typescript-eslint/eslint-plugin": optional: true - checksum: 10/8e987028ad925ce1e04c01dcae70adbf44c2878a8b15c4327b33a2861e471d7fe00f6fe213fbd2b936f3fcefc8ccabb0d778aa1d6e0e0387a3dc7fe150cd4ed4 + checksum: 10/b420fd55c393a6fdacfdbd0d1adf4cd44bed9a6584f05245091a6716272c57f38154d04b76f253619d8bf22823c0b9d630ef6b5b09edad6e51b8a1f7aec56c22 languageName: node linkType: hard "eslint-rspack-plugin@npm:^4.2.1": - version: 4.2.1 - resolution: "eslint-rspack-plugin@npm:4.2.1" + version: 4.4.1 + resolution: "eslint-rspack-plugin@npm:4.4.1" dependencies: "@types/eslint": "npm:^8.56.10" jest-worker: "npm:^29.7.0" micromatch: "npm:^4.0.8" normalize-path: "npm:^3.0.0" - schema-utils: "npm:^4.2.0" + tinyglobby: "npm:^0.2.15" peerDependencies: - eslint: ^8.0.0 || ^9.0.0 - checksum: 10/c45639e660d13af63c58f8f80103a033f607b9d635d9315d7d859408134cc35e2e3f981805f7abdd6061ec9d8207cfc7c0d5a097df9ac8c7ccd707ca5aa14583 + eslint: ^8.0.0 || ^9.0.0 || ^10.0.0 + checksum: 10/310bddfd2818d885d382e481b510e68648fff735ba1bbf93cb03f191d92c109baca4a0072c4fa00c28347fbba03aabfe890075d2e2cade557ab9e3d5feb7a149 languageName: node linkType: hard -"eslint-scope@npm:5.1.1, eslint-scope@npm:^5.1.1": +"eslint-scope@npm:5.1.1": version: 5.1.1 resolution: "eslint-scope@npm:5.1.1" dependencies: @@ -30082,17 +28635,17 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.1, eslint-visitor-keys@npm:^3.4.3": +"eslint-visitor-keys@npm:^3.4.1, eslint-visitor-keys@npm:^3.4.3": version: 3.4.3 resolution: "eslint-visitor-keys@npm:3.4.3" checksum: 10/3f357c554a9ea794b094a09bd4187e5eacd1bc0d0653c3adeb87962c548e6a1ab8f982b86963ae1337f5d976004146536dcee5d0e2806665b193fbfbf1a9231b languageName: node linkType: hard -"eslint-visitor-keys@npm:^4.2.0, eslint-visitor-keys@npm:^4.2.1": - version: 4.2.1 - resolution: "eslint-visitor-keys@npm:4.2.1" - checksum: 10/3ee00fc6a7002d4b0ffd9dc99e13a6a7882c557329e6c25ab254220d71e5c9c4f89dca4695352949ea678eb1f3ba912a18ef8aac0a7fe094196fd92f441bfce2 +"eslint-visitor-keys@npm:^5.0.0": + version: 5.0.1 + resolution: "eslint-visitor-keys@npm:5.0.1" + checksum: 10/f9cc1a57b75e0ef949545cac33d01e8367e302de4c1483266ed4d8646ee5c306376660196bbb38b004e767b7043d1e661cb4336b49eff634a1bbe75c1db709ec languageName: node linkType: hard @@ -30160,6 +28713,13 @@ __metadata: languageName: node linkType: hard +"esm-env@npm:^1.2.2": + version: 1.2.2 + resolution: "esm-env@npm:1.2.2" + checksum: 10/caf5f3cc2bc7107494585b4e38835787f48ef77b670aeb2d765a5b6b64c41102d20bbdd34bda32474291b6b8d819d4d02ce92570a0886baca6cef70f5fe689f3 + languageName: node + linkType: hard + "esm@npm:^3.2.25": version: 3.2.25 resolution: "esm@npm:3.2.25" @@ -30190,16 +28750,6 @@ __metadata: languageName: node linkType: hard -"esprima@npm:1.2.2": - version: 1.2.2 - resolution: "esprima@npm:1.2.2" - bin: - esparse: ./bin/esparse.js - esvalidate: ./bin/esvalidate.js - checksum: 10/7ed9274abb1fed861354239f0da8fa3ec6592617ad0fd2aab16b0beb0425137c46f05c82faa0de89b3bb3d704054815c2657658c7e221b1fb550e88c237eefd2 - languageName: node - linkType: hard - "esprima@npm:^4.0.0, esprima@npm:^4.0.1, esprima@npm:~4.0.0": version: 4.0.1 resolution: "esprima@npm:4.0.1" @@ -30228,7 +28778,7 @@ __metadata: languageName: node linkType: hard -"estraverse@npm:^4.1.1, estraverse@npm:^4.2.0": +"estraverse@npm:^4.1.1": version: 4.3.0 resolution: "estraverse@npm:4.3.0" checksum: 10/3f67ad02b6dbfaddd9ea459cf2b6ef4ecff9a6082a7af9d22e445b9abc082ad9ca47e1825557b293fcdae477f4714e561123e30bb6a5b2f184fb2bad4a9497eb @@ -30256,15 +28806,6 @@ __metadata: languageName: node linkType: hard -"estree-walker@npm:^3.0.3": - version: 3.0.3 - resolution: "estree-walker@npm:3.0.3" - dependencies: - "@types/estree": "npm:^1.0.0" - checksum: 10/a65728d5727b71de172c5df323385755a16c0fdab8234dc756c3854cfee343261ddfbb72a809a5660fac8c75d960bb3e21aa898c2d7e9b19bb298482ca58a3af - languageName: node - linkType: hard - "esutils@npm:^2.0.2": version: 2.0.3 resolution: "esutils@npm:2.0.3" @@ -30342,14 +28883,14 @@ __metadata: languageName: node linkType: hard -"eventsource-parser@npm:^3.0.1": - version: 3.0.1 - resolution: "eventsource-parser@npm:3.0.1" - checksum: 10/2730c54c3cb47d55d2967f2ece843f9fc95d8a11c2fef6fece8d17d9080193cbe3cd9ac7b04a325977f63cbf8c1664fdd0512dec1aec601666a5c5bd8564b61f +"eventsource-parser@npm:^3.0.0, eventsource-parser@npm:^3.0.1": + version: 3.0.6 + resolution: "eventsource-parser@npm:3.0.6" + checksum: 10/febf7058b9c2168ecbb33e92711a1646e06bd1568f60b6eb6a01a8bf9f8fcd29cc8320d57247059cacf657a296280159f21306d2e3ff33309a9552b2ef889387 languageName: node linkType: hard -"eventsource@npm:^3.0.2, eventsource@npm:^3.0.6": +"eventsource@npm:^3.0.2": version: 3.0.7 resolution: "eventsource@npm:3.0.7" dependencies: @@ -30358,6 +28899,15 @@ __metadata: languageName: node linkType: hard +"eventsource@npm:^4.0.0": + version: 4.1.0 + resolution: "eventsource@npm:4.1.0" + dependencies: + eventsource-parser: "npm:^3.0.1" + checksum: 10/d23b1c2a56a1dd7344a151ca57d70ebc624972150395c1c82110bda1d2918487260e8ec76c1c79677406184ecb1847509a675d4ee5367925ef77f03c9cae594d + languageName: node + linkType: hard + "evp_bytestokey@npm:^1.0.0, evp_bytestokey@npm:^1.0.3": version: 1.0.3 resolution: "evp_bytestokey@npm:1.0.3" @@ -30369,84 +28919,9 @@ __metadata: languageName: node linkType: hard -"example-app-next@workspace:packages/app-next": +"example-app-legacy@workspace:packages/app-legacy": version: 0.0.0-use.local - resolution: "example-app-next@workspace:packages/app-next" - dependencies: - "@backstage/app-defaults": "workspace:^" - "@backstage/catalog-model": "workspace:^" - "@backstage/cli": "workspace:^" - "@backstage/config": "workspace:^" - "@backstage/core-app-api": "workspace:^" - "@backstage/core-compat-api": "workspace:^" - "@backstage/core-components": "workspace:^" - "@backstage/core-plugin-api": "workspace:^" - "@backstage/frontend-app-api": "workspace:^" - "@backstage/frontend-defaults": "workspace:^" - "@backstage/frontend-plugin-api": "workspace:^" - "@backstage/integration-react": "workspace:^" - "@backstage/plugin-api-docs": "workspace:^" - "@backstage/plugin-app": "workspace:^" - "@backstage/plugin-app-visualizer": "workspace:^" - "@backstage/plugin-auth": "workspace:^" - "@backstage/plugin-auth-react": "workspace:^" - "@backstage/plugin-catalog": "workspace:^" - "@backstage/plugin-catalog-common": "workspace:^" - "@backstage/plugin-catalog-graph": "workspace:^" - "@backstage/plugin-catalog-import": "workspace:^" - "@backstage/plugin-catalog-react": "workspace:^" - "@backstage/plugin-catalog-unprocessed-entities": "workspace:^" - "@backstage/plugin-home": "workspace:^" - "@backstage/plugin-kubernetes": "workspace:^" - "@backstage/plugin-kubernetes-cluster": "workspace:^" - "@backstage/plugin-notifications": "workspace:^" - "@backstage/plugin-org": "workspace:^" - "@backstage/plugin-permission-react": "workspace:^" - "@backstage/plugin-scaffolder": "workspace:^" - "@backstage/plugin-scaffolder-react": "workspace:^" - "@backstage/plugin-search": "workspace:^" - "@backstage/plugin-search-common": "workspace:^" - "@backstage/plugin-search-react": "workspace:^" - "@backstage/plugin-signals": "workspace:^" - "@backstage/plugin-techdocs": "workspace:^" - "@backstage/plugin-techdocs-module-addons-contrib": "workspace:^" - "@backstage/plugin-techdocs-react": "workspace:^" - "@backstage/plugin-user-settings": "workspace:^" - "@backstage/test-utils": "workspace:^" - "@backstage/theme": "workspace:^" - "@backstage/ui": "workspace:^" - "@material-ui/core": "npm:^4.12.2" - "@material-ui/icons": "npm:^4.9.1" - "@material-ui/lab": "npm:4.0.0-alpha.61" - "@octokit/rest": "npm:^19.0.3" - "@testing-library/dom": "npm:^10.0.0" - "@testing-library/jest-dom": "npm:^6.0.0" - "@testing-library/react": "npm:^16.0.0" - "@testing-library/user-event": "npm:^14.0.0" - "@types/jquery": "npm:^3.3.34" - "@types/react": "npm:*" - "@types/react-dom": "npm:*" - "@types/zen-observable": "npm:^0.8.0" - cross-env: "npm:^7.0.0" - history: "npm:^5.0.0" - react: "npm:^18.0.2" - react-dom: "npm:^18.0.2" - react-router: "npm:^6.3.0" - react-router-dom: "npm:^6.3.0" - react-use: "npm:^17.2.4" - zen-observable: "npm:^0.10.0" - languageName: unknown - linkType: soft - -"example-app@link:../app::locator=example-backend%40workspace%3Apackages%2Fbackend": - version: 0.0.0-use.local - resolution: "example-app@link:../app::locator=example-backend%40workspace%3Apackages%2Fbackend" - languageName: node - linkType: soft - -"example-app@workspace:packages/app": - version: 0.0.0-use.local - resolution: "example-app@workspace:packages/app" + resolution: "example-app-legacy@workspace:packages/app-legacy" dependencies: "@backstage/app-defaults": "workspace:^" "@backstage/catalog-model": "workspace:^" @@ -30467,6 +28942,7 @@ __metadata: "@backstage/plugin-catalog-unprocessed-entities": "workspace:^" "@backstage/plugin-devtools": "workspace:^" "@backstage/plugin-home": "workspace:^" + "@backstage/plugin-home-react": "workspace:^" "@backstage/plugin-kubernetes": "workspace:^" "@backstage/plugin-kubernetes-cluster": "workspace:^" "@backstage/plugin-mui-to-bui": "workspace:^" @@ -30491,22 +28967,102 @@ __metadata: "@material-ui/lab": "npm:4.0.0-alpha.61" "@octokit/rest": "npm:^19.0.3" "@playwright/test": "npm:^1.32.3" + "@rjsf/utils": "npm:5.24.13" "@testing-library/dom": "npm:^10.0.0" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" "@testing-library/user-event": "npm:^14.0.0" - "@types/jquery": "npm:^3.3.34" + "@types/jquery": "npm:^4.0.0" "@types/react": "npm:*" "@types/react-dom": "npm:*" "@types/zen-observable": "npm:^0.8.0" - axios: "npm:^1.13.0" - cross-env: "npm:^7.0.0" + axios: "npm:^1.15.0" + cross-env: "npm:^10.0.0" history: "npm:^5.0.0" msw: "npm:^1.0.0" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router: "npm:^6.3.0" - react-router-dom: "npm:^6.3.0" + react-router: "npm:^6.30.2" + react-router-dom: "npm:^6.30.2" + react-use: "npm:^17.2.4" + zen-observable: "npm:^0.10.0" + languageName: unknown + linkType: soft + +"example-app@link:../app::locator=example-backend%40workspace%3Apackages%2Fbackend": + version: 0.0.0-use.local + resolution: "example-app@link:../app::locator=example-backend%40workspace%3Apackages%2Fbackend" + languageName: node + linkType: soft + +"example-app@workspace:packages/app": + version: 0.0.0-use.local + resolution: "example-app@workspace:packages/app" + dependencies: + "@backstage/app-defaults": "workspace:^" + "@backstage/catalog-model": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/config": "workspace:^" + "@backstage/core-app-api": "workspace:^" + "@backstage/core-compat-api": "workspace:^" + "@backstage/core-components": "workspace:^" + "@backstage/core-plugin-api": "workspace:^" + "@backstage/frontend-app-api": "workspace:^" + "@backstage/frontend-defaults": "workspace:^" + "@backstage/frontend-plugin-api": "workspace:^" + "@backstage/integration-react": "workspace:^" + "@backstage/plugin-api-docs": "workspace:^" + "@backstage/plugin-app": "workspace:^" + "@backstage/plugin-app-react": "workspace:^" + "@backstage/plugin-app-visualizer": "workspace:^" + "@backstage/plugin-auth": "workspace:^" + "@backstage/plugin-auth-react": "workspace:^" + "@backstage/plugin-catalog": "workspace:^" + "@backstage/plugin-catalog-common": "workspace:^" + "@backstage/plugin-catalog-graph": "workspace:^" + "@backstage/plugin-catalog-import": "workspace:^" + "@backstage/plugin-catalog-react": "workspace:^" + "@backstage/plugin-catalog-unprocessed-entities": "workspace:^" + "@backstage/plugin-devtools": "workspace:^" + "@backstage/plugin-home": "workspace:^" + "@backstage/plugin-home-react": "workspace:^" + "@backstage/plugin-kubernetes": "workspace:^" + "@backstage/plugin-kubernetes-cluster": "workspace:^" + "@backstage/plugin-notifications": "workspace:^" + "@backstage/plugin-org": "workspace:^" + "@backstage/plugin-permission-react": "workspace:^" + "@backstage/plugin-scaffolder": "workspace:^" + "@backstage/plugin-scaffolder-react": "workspace:^" + "@backstage/plugin-search": "workspace:^" + "@backstage/plugin-search-common": "workspace:^" + "@backstage/plugin-search-react": "workspace:^" + "@backstage/plugin-signals": "workspace:^" + "@backstage/plugin-techdocs": "workspace:^" + "@backstage/plugin-techdocs-module-addons-contrib": "workspace:^" + "@backstage/plugin-techdocs-react": "workspace:^" + "@backstage/plugin-user-settings": "workspace:^" + "@backstage/test-utils": "workspace:^" + "@backstage/theme": "workspace:^" + "@backstage/ui": "workspace:^" + "@material-ui/core": "npm:^4.12.2" + "@material-ui/icons": "npm:^4.9.1" + "@material-ui/lab": "npm:4.0.0-alpha.61" + "@octokit/rest": "npm:^19.0.3" + "@playwright/test": "npm:^1.58.2" + "@testing-library/dom": "npm:^10.0.0" + "@testing-library/jest-dom": "npm:^6.0.0" + "@testing-library/react": "npm:^16.0.0" + "@testing-library/user-event": "npm:^14.0.0" + "@types/jquery": "npm:^4.0.0" + "@types/react": "npm:*" + "@types/react-dom": "npm:*" + "@types/zen-observable": "npm:^0.8.0" + cross-env: "npm:^10.0.0" + history: "npm:^5.0.0" + react: "npm:^18.0.2" + react-dom: "npm:^18.0.2" + react-router: "npm:^6.30.2" + react-router-dom: "npm:^6.30.2" react-use: "npm:^17.2.4" zen-observable: "npm:^0.10.0" languageName: unknown @@ -30528,6 +29084,7 @@ __metadata: "@backstage/plugin-auth-node": "workspace:^" "@backstage/plugin-catalog-backend": "workspace:^" "@backstage/plugin-catalog-backend-module-backstage-openapi": "workspace:^" + "@backstage/plugin-catalog-backend-module-logs": "workspace:^" "@backstage/plugin-catalog-backend-module-openapi": "workspace:^" "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "workspace:^" "@backstage/plugin-catalog-backend-module-unprocessed": "workspace:^" @@ -30553,9 +29110,9 @@ __metadata: "@backstage/plugin-search-backend-node": "workspace:^" "@backstage/plugin-signals-backend": "workspace:^" "@backstage/plugin-techdocs-backend": "workspace:^" - "@opentelemetry/auto-instrumentations-node": "npm:^0.61.0" - "@opentelemetry/exporter-prometheus": "npm:^0.54.0" - "@opentelemetry/sdk-node": "npm:^0.54.0" + "@opentelemetry/auto-instrumentations-node": "npm:^0.71.0" + "@opentelemetry/exporter-prometheus": "npm:^0.213.0" + "@opentelemetry/sdk-node": "npm:^0.213.0" example-app: "link:../app" languageName: unknown linkType: soft @@ -30575,7 +29132,7 @@ __metadata: languageName: node linkType: hard -"execa@npm:^5.0.0": +"execa@npm:^5.0.0, execa@npm:^5.1.1": version: 5.1.1 resolution: "execa@npm:5.1.1" dependencies: @@ -30592,23 +29149,6 @@ __metadata: languageName: node linkType: hard -"execa@npm:^8.0.1": - version: 8.0.1 - resolution: "execa@npm:8.0.1" - dependencies: - cross-spawn: "npm:^7.0.3" - get-stream: "npm:^8.0.1" - human-signals: "npm:^5.0.0" - is-stream: "npm:^3.0.0" - merge-stream: "npm:^2.0.0" - npm-run-path: "npm:^5.1.0" - onetime: "npm:^6.0.0" - signal-exit: "npm:^4.1.0" - strip-final-newline: "npm:^3.0.0" - checksum: 10/d2ab5fe1e2bb92b9788864d0713f1fce9a07c4594e272c0c97bc18c90569897ab262e4ea58d27a694d288227a2e24f16f5e2575b44224ad9983b799dc7f1098d - languageName: node - linkType: hard - "exit-hook@npm:^2.2.1": version: 2.2.1 resolution: "exit-hook@npm:2.2.1" @@ -30623,10 +29163,10 @@ __metadata: languageName: node linkType: hard -"exit@npm:^0.1.2": - version: 0.1.2 - resolution: "exit@npm:0.1.2" - checksum: 10/387555050c5b3c10e7a9e8df5f43194e95d7737c74532c409910e585d5554eaff34960c166643f5e23d042196529daad059c292dcf1fb61b8ca878d3677f4b87 +"exit-x@npm:^0.2.2": + version: 0.2.2 + resolution: "exit-x@npm:0.2.2" + checksum: 10/ee043053e6c1e237adf5ad9c4faf9f085b606f64a4ff859e2b138fab63fe642711d00c9af452a9134c4c92c55f752e818bfabab78c24d345022db163f3137027 languageName: node linkType: hard @@ -30646,16 +29186,17 @@ __metadata: languageName: node linkType: hard -"expect@npm:>28.1.3, expect@npm:^29.0.0, expect@npm:^29.7.0": - version: 29.7.0 - resolution: "expect@npm:29.7.0" +"expect@npm:30.2.0, expect@npm:>28.1.3, expect@npm:^30.0.0": + version: 30.2.0 + resolution: "expect@npm:30.2.0" dependencies: - "@jest/expect-utils": "npm:^29.7.0" - jest-get-type: "npm:^29.6.3" - jest-matcher-utils: "npm:^29.7.0" - jest-message-util: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - checksum: 10/63f97bc51f56a491950fb525f9ad94f1916e8a014947f8d8445d3847a665b5471b768522d659f5e865db20b6c2033d2ac10f35fcbd881a4d26407a4f6f18451a + "@jest/expect-utils": "npm:30.2.0" + "@jest/get-type": "npm:30.1.0" + jest-matcher-utils: "npm:30.2.0" + jest-message-util: "npm:30.2.0" + jest-mock: "npm:30.2.0" + jest-util: "npm:30.2.0" + checksum: 10/cf98ab45ab2e9f2fb9943a3ae0097f72d63a94be179a19fd2818d8fdc3b7681d31cc8ef540606eb8dd967d9c44d73fef263a614e9de260c22943ffb122ad66fd languageName: node linkType: hard @@ -30667,11 +29208,11 @@ __metadata: linkType: hard "express-openapi-validator@npm:^5.5.8": - version: 5.5.8 - resolution: "express-openapi-validator@npm:5.5.8" + version: 5.6.2 + resolution: "express-openapi-validator@npm:5.6.2" dependencies: - "@apidevtools/json-schema-ref-parser": "npm:^14.0.3" - "@types/multer": "npm:^1.4.12" + "@apidevtools/json-schema-ref-parser": "npm:^14.2.1" + "@types/multer": "npm:^2.0.0" ajv: "npm:^8.17.1" ajv-draft-04: "npm:^1.0.0" ajv-formats: "npm:^3.0.1" @@ -30682,11 +29223,11 @@ __metadata: media-typer: "npm:^1.1.0" multer: "npm:^2.0.2" ono: "npm:^7.1.3" - path-to-regexp: "npm:^8.2.0" - qs: "npm:^6.14.0" + path-to-regexp: "npm:^8.3.0" + qs: "npm:^6.14.1" peerDependencies: express: "*" - checksum: 10/0396d4fd253fcd61725b632adcd805ac4ced940c34907775bedce39701d3b884e7a60357fa29c59d282407381c1fdcecceae6d0011684103e7ffe104038cb661 + checksum: 10/c0e08aab007a4a7b73aad87b3a52ac82e986af46cbe329ba1b9e2f9b896415441e73ed42d4297edc92e3f0267364d0677707613caff2c22b90e61744cfd96b37 languageName: node linkType: hard @@ -30707,81 +29248,84 @@ __metadata: languageName: node linkType: hard -"express-rate-limit@npm:^7.5.0": - version: 7.5.1 - resolution: "express-rate-limit@npm:7.5.1" +"express-rate-limit@npm:^8.2.1, express-rate-limit@npm:^8.2.2": + version: 8.3.2 + resolution: "express-rate-limit@npm:8.3.2" + dependencies: + ip-address: "npm:10.1.0" peerDependencies: express: ">= 4.11" - checksum: 10/357c3398450144ab7bbce2841d0bf4f93a0f3fd9d1d5ed9a0ee331b557af969cc790941dc37b47f8d9b5672964aa0e31666f770e1f48b334dc7d1e69f6433040 + checksum: 10/71afac0fff29bf03117a89902953e7feafc67402332910cfb601b27da079b98f04ba551a1ef4f0ad5cf2538d9599c2649450af6d3f7505bcc7d24f7eaf765a4e languageName: node linkType: hard "express-session@npm:^1.17.1, express-session@npm:^1.17.3": - version: 1.18.2 - resolution: "express-session@npm:1.18.2" + version: 1.19.0 + resolution: "express-session@npm:1.19.0" dependencies: - cookie: "npm:0.7.2" - cookie-signature: "npm:1.0.7" - debug: "npm:2.6.9" + cookie: "npm:~0.7.2" + cookie-signature: "npm:~1.0.7" + debug: "npm:~2.6.9" depd: "npm:~2.0.0" on-headers: "npm:~1.1.0" parseurl: "npm:~1.3.3" - safe-buffer: "npm:5.2.1" + safe-buffer: "npm:~5.2.1" uid-safe: "npm:~2.1.5" - checksum: 10/1a89a4d3e579e1d2a729dc604c254c3e942b24a279681371c2d8042b1080f9e6de4ca787a091ec1a9178c9f2cc5c19bcb9f3929632d44db8972263e26c2cc4b8 + checksum: 10/5f36b51d344ec40adb5b9cd8a915fd694cdaf64832632411d4f6e6de8e9a83d873a20389e9c3cce966fece76a18c0384c5f2763b3ac089f801e8d6e92cb543e6 languageName: node linkType: hard -"express@npm:^4.14.0, express@npm:^4.17.1, express@npm:^4.18.1, express@npm:^4.18.2, express@npm:^4.19.2, express@npm:^4.21.0, express@npm:^4.21.2": - version: 4.21.2 - resolution: "express@npm:4.21.2" +"express@npm:^4.14.0, express@npm:^4.21.0, express@npm:^4.22.0, express@npm:^4.22.1": + version: 4.22.1 + resolution: "express@npm:4.22.1" dependencies: accepts: "npm:~1.3.8" array-flatten: "npm:1.1.1" - body-parser: "npm:1.20.3" - content-disposition: "npm:0.5.4" + body-parser: "npm:~1.20.3" + content-disposition: "npm:~0.5.4" content-type: "npm:~1.0.4" - cookie: "npm:0.7.1" - cookie-signature: "npm:1.0.6" + cookie: "npm:~0.7.1" + cookie-signature: "npm:~1.0.6" debug: "npm:2.6.9" depd: "npm:2.0.0" encodeurl: "npm:~2.0.0" escape-html: "npm:~1.0.3" etag: "npm:~1.8.1" - finalhandler: "npm:1.3.1" - fresh: "npm:0.5.2" - http-errors: "npm:2.0.0" + finalhandler: "npm:~1.3.1" + fresh: "npm:~0.5.2" + http-errors: "npm:~2.0.0" merge-descriptors: "npm:1.0.3" methods: "npm:~1.1.2" - on-finished: "npm:2.4.1" + on-finished: "npm:~2.4.1" parseurl: "npm:~1.3.3" - path-to-regexp: "npm:0.1.12" + path-to-regexp: "npm:~0.1.12" proxy-addr: "npm:~2.0.7" - qs: "npm:6.13.0" + qs: "npm:~6.14.0" range-parser: "npm:~1.2.1" safe-buffer: "npm:5.2.1" - send: "npm:0.19.0" - serve-static: "npm:1.16.2" + send: "npm:~0.19.0" + serve-static: "npm:~1.16.2" setprototypeof: "npm:1.2.0" - statuses: "npm:2.0.1" + statuses: "npm:~2.0.1" type-is: "npm:~1.6.18" utils-merge: "npm:1.0.1" vary: "npm:~1.1.2" - checksum: 10/34571c442fc8c9f2c4b442d2faa10ea1175cf8559237fc6a278f5ce6254a8ffdbeb9a15d99f77c1a9f2926ab183e3b7ba560e3261f1ad4149799e3412ab66bd1 + checksum: 10/f33c1bd0c7d36e2a1f18de9cdc176469d32f68e20258d2941b8d296ab9a4fd9011872c246391bf87714f009fac5114c832ec5ac65cbee39421f1258801eb8470 languageName: node linkType: hard -"express@npm:^5.0.1": - version: 5.1.0 - resolution: "express@npm:5.1.0" +"express@npm:^5.2.1": + version: 5.2.1 + resolution: "express@npm:5.2.1" dependencies: accepts: "npm:^2.0.0" - body-parser: "npm:^2.2.0" + body-parser: "npm:^2.2.1" content-disposition: "npm:^1.0.0" content-type: "npm:^1.0.5" cookie: "npm:^0.7.1" cookie-signature: "npm:^1.2.1" debug: "npm:^4.4.0" + depd: "npm:^2.0.0" encodeurl: "npm:^2.0.0" escape-html: "npm:^1.0.3" etag: "npm:^1.8.1" @@ -30802,7 +29346,7 @@ __metadata: statuses: "npm:^2.0.1" type-is: "npm:^2.0.1" vary: "npm:^1.1.2" - checksum: 10/6dba00bbdf308f43a84ed3f07a7e9870d5208f2a0b8f60f39459dda089750379747819863fad250849d3c9163833f33f94ce69d73938df31e0c5a430800d7e56 + checksum: 10/4aa545d89702ac83f645c77abda1b57bcabe288f0b380fb5580fac4e323ea0eb533005c8e666b4e19152fb16d4abf11ba87b22aa9a10857a0485cd86b94639bd languageName: node linkType: hard @@ -30829,13 +29373,6 @@ __metadata: languageName: node linkType: hard -"extract-files@npm:^11.0.0": - version: 11.0.0 - resolution: "extract-files@npm:11.0.0" - checksum: 10/02bf0dde9617d67795e38a182d8bf58828a7c5d77762623ff05e72d461a0e980071a860e2503231db2cc8824d8da35cefb1750937dcbe018cb0e67e37f20a7be - languageName: node - linkType: hard - "fast-copy@npm:^3.0.2": version: 3.0.2 resolution: "fast-copy@npm:3.0.2" @@ -30898,7 +29435,7 @@ __metadata: languageName: node linkType: hard -"fast-levenshtein@npm:^2.0.6, fast-levenshtein@npm:~2.0.6": +"fast-levenshtein@npm:^2.0.6": version: 2.0.6 resolution: "fast-levenshtein@npm:2.0.6" checksum: 10/eb7e220ecf2bab5159d157350b81d01f75726a4382f5a9266f42b9150c4523b9795f7f5d9fbbbeaeac09a441b2369f05ee02db48ea938584205530fe5693cfe1 @@ -30912,7 +29449,7 @@ __metadata: languageName: node linkType: hard -"fast-querystring@npm:^1.0.0, fast-querystring@npm:^1.1.1": +"fast-querystring@npm:^1.0.0": version: 1.1.2 resolution: "fast-querystring@npm:1.1.2" dependencies: @@ -30956,34 +29493,36 @@ __metadata: languageName: node linkType: hard -"fast-url-parser@npm:^1.1.3": - version: 1.1.3 - resolution: "fast-url-parser@npm:1.1.3" +"fast-xml-builder@npm:^1.1.4": + version: 1.1.4 + resolution: "fast-xml-builder@npm:1.1.4" dependencies: - punycode: "npm:^1.3.2" - checksum: 10/6d33f46ce9776f7f3017576926207a950ca39bc5eb78fc794404f2288fe494720f9a119084b75569bd9eb09d2b46678bfaf39c191fb2c808ef3c833dc8982752 + path-expression-matcher: "npm:^1.1.3" + checksum: 10/32937866aaf5a90e69d1f4ee6e15e875248d5b5d2afd70277e9e8323074de4980cef24575a591b8e43c29f405d5f12377b3bad3842dc412b0c5c17a3eaee4b6b languageName: node linkType: hard -"fast-xml-parser@npm:4.4.1, fast-xml-parser@npm:^4.4.1": - version: 4.4.1 - resolution: "fast-xml-parser@npm:4.4.1" +"fast-xml-parser@npm:5.5.8, fast-xml-parser@npm:^5.3.4": + version: 5.5.8 + resolution: "fast-xml-parser@npm:5.5.8" + dependencies: + fast-xml-builder: "npm:^1.1.4" + path-expression-matcher: "npm:^1.2.0" + strnum: "npm:^2.2.0" + bin: + fxparser: src/cli/cli.js + checksum: 10/888f9a5d345e65e34b70d394798a1542603a216f06c140a9671d031b80b42c01ef2e68f2a0ceea45e7703fa80549f0e06da710f5a2faafdc910d1b6b354f0fa0 + languageName: node + linkType: hard + +"fast-xml-parser@npm:^4.4.1": + version: 4.5.6 + resolution: "fast-xml-parser@npm:4.5.6" dependencies: strnum: "npm:^1.0.5" bin: fxparser: src/cli/cli.js - checksum: 10/0c05ab8703630d8c857fafadbd78d0020d3a8e54310c3842179cd4a0d9d97e96d209ce885e91241f4aa9dd8dfc2fd924a682741a423d65153cad34da2032ec44 - languageName: node - linkType: hard - -"fast-xml-parser@npm:5.2.5": - version: 5.2.5 - resolution: "fast-xml-parser@npm:5.2.5" - dependencies: - strnum: "npm:^2.1.0" - bin: - fxparser: src/cli/cli.js - checksum: 10/305017cff6968a34cbac597317be1516e85c44f650f30d982c84f8c30043e81fd38d39a8810d570136c921399dd43b9ac4775bdfbbbcfee96456f3c086b48bdd + checksum: 10/dd77cce4b1b322400339147b72b2d315bddc12c6ed3ca82bfc87543ced6dd8d81a6e0b429489e2f048f39258aa2023580654c8934881c633b9d58a6e79672349 languageName: node linkType: hard @@ -30994,7 +29533,7 @@ __metadata: languageName: node linkType: hard -"fastq@npm:^1.15.0, fastq@npm:^1.6.0": +"fastq@npm:^1.6.0": version: 1.18.0 resolution: "fastq@npm:1.18.0" dependencies: @@ -31021,12 +29560,12 @@ __metadata: languageName: node linkType: hard -"fb-watchman@npm:^2.0.0": - version: 2.0.1 - resolution: "fb-watchman@npm:2.0.1" +"fb-watchman@npm:^2.0.2": + version: 2.0.2 + resolution: "fb-watchman@npm:2.0.2" dependencies: bser: "npm:2.1.1" - checksum: 10/9a03efc7d41ce3ca3d799d63505a1f7312caddf4e7737d39f2165bfe4872cbd4b87eccc9e6c57229ea08f14b4d7187896da31a7270b8da7a4aaa8fba2d3d1c42 + checksum: 10/4f95d336fb805786759e383fd7fff342ceb7680f53efcc0ef82f502eb479ce35b98e8b207b6dfdfeea0eba845862107dc73813775fc6b56b3098c6e90a2dad77 languageName: node linkType: hard @@ -31061,6 +29600,15 @@ __metadata: languageName: node linkType: hard +"fd-package-json@npm:^2.0.0": + version: 2.0.0 + resolution: "fd-package-json@npm:2.0.0" + dependencies: + walk-up-path: "npm:^4.0.0" + checksum: 10/e595a1a23f8e208815cdcf26c92218240da00acce80468324408dc4a5cb6c26b6efb5076f0458a02f044562a1e60253731187a627d5416b4961468ddfc0ae426 + languageName: node + linkType: hard + "fdir@npm:^6.5.0": version: 6.5.0 resolution: "fdir@npm:6.5.0" @@ -31090,14 +29638,7 @@ __metadata: languageName: node linkType: hard -"fflate@npm:^0.8.2": - version: 0.8.2 - resolution: "fflate@npm:0.8.2" - checksum: 10/2bd26ba6d235d428de793c6a0cd1aaa96a06269ebd4e21b46c8fd1bd136abc631acf27e188d47c3936db090bf3e1ede11d15ce9eae9bffdc4bfe1b9dc66ca9cb - languageName: node - linkType: hard - -"figures@npm:^3.0.0": +"figures@npm:^3.0.0, figures@npm:^3.2.0": version: 3.2.0 resolution: "figures@npm:3.2.0" dependencies: @@ -31115,15 +29656,15 @@ __metadata: languageName: node linkType: hard -"file-type@npm:21.0.0": - version: 21.0.0 - resolution: "file-type@npm:21.0.0" +"file-type@npm:21.3.2": + version: 21.3.2 + resolution: "file-type@npm:21.3.2" dependencies: - "@tokenizer/inflate": "npm:^0.2.7" - strtok3: "npm:^10.2.2" - token-types: "npm:^6.0.0" + "@tokenizer/inflate": "npm:^0.4.1" + strtok3: "npm:^10.3.4" + token-types: "npm:^6.1.1" uint8array-extras: "npm:^1.4.0" - checksum: 10/6980e8b0ef870a98b51ab2eac5db94a1884de8476fe49dc02d2f7e0c1d1d7d44d42b6c59e67867ae90f321ddf4edd00fcfda01821591e2fa05385d0e438a9dc1 + checksum: 10/3912271811e0c745d43ff1f6c97e66d4b0d890c68d1041de4ef0c8068ede46f725ef3ed0f92c97d0cd2a261f84c3b51881d60ab797e47fa9a15e7ed227f04c85 languageName: node linkType: hard @@ -31213,21 +29754,6 @@ __metadata: languageName: node linkType: hard -"finalhandler@npm:1.3.1": - version: 1.3.1 - resolution: "finalhandler@npm:1.3.1" - dependencies: - debug: "npm:2.6.9" - encodeurl: "npm:~2.0.0" - escape-html: "npm:~1.0.3" - on-finished: "npm:2.4.1" - parseurl: "npm:~1.3.3" - statuses: "npm:2.0.1" - unpipe: "npm:~1.0.0" - checksum: 10/4babe72969b7373b5842bc9f75c3a641a4d0f8eb53af6b89fa714d4460ce03fb92b28de751d12ba415e96e7e02870c436d67412120555e2b382640535697305b - languageName: node - linkType: hard - "finalhandler@npm:^2.1.0": version: 2.1.0 resolution: "finalhandler@npm:2.1.0" @@ -31242,6 +29768,21 @@ __metadata: languageName: node linkType: hard +"finalhandler@npm:~1.3.1": + version: 1.3.2 + resolution: "finalhandler@npm:1.3.2" + dependencies: + debug: "npm:2.6.9" + encodeurl: "npm:~2.0.0" + escape-html: "npm:~1.0.3" + on-finished: "npm:~2.4.1" + parseurl: "npm:~1.3.3" + statuses: "npm:~2.0.2" + unpipe: "npm:~1.0.0" + checksum: 10/6cb4f9f80eaeb5a0fac4fdbd27a65d39271f040a0034df16556d896bfd855fd42f09da886781b3102117ea8fceba97b903c1f8b08df1fb5740576d5e0f481eed + languageName: node + linkType: hard + "find-cache-dir@npm:^2.0.0": version: 2.1.0 resolution: "find-cache-dir@npm:2.1.0" @@ -31294,16 +29835,16 @@ __metadata: languageName: node linkType: hard -"find-process@npm:^1.4.5": - version: 1.4.11 - resolution: "find-process@npm:1.4.11" +"find-process@npm:^2.0.0": + version: 2.1.1 + resolution: "find-process@npm:2.1.1" dependencies: chalk: "npm:~4.1.2" - commander: "npm:^12.1.0" + commander: "npm:^14.0.3" loglevel: "npm:^1.9.2" bin: - find-process: bin/find-process.js - checksum: 10/95f7106877c86fc09804b29339cfac518a43228c1b5f4a4e9442e57ac5538369fbeafb746fa96390ecd7be6f5a9119b58c952b5c96a0cb9f2490ef19298dec1e + find-process: dist/cjs/bin/find-process.js + checksum: 10/15c88be4ee12cc224bf8edd1c10681b62136f1e97e42e20c0facef163eef3a0c039da0a5ca2dd5c8b3c0e72867e5316d2dd3e9847abad9dc5dad5d2779ed577c languageName: node linkType: hard @@ -31343,17 +29884,6 @@ __metadata: languageName: node linkType: hard -"find-up@npm:^7.0.0": - version: 7.0.0 - resolution: "find-up@npm:7.0.0" - dependencies: - locate-path: "npm:^7.2.0" - path-exists: "npm:^5.0.0" - unicorn-magic: "npm:^0.1.0" - checksum: 10/7e6b08fbc05a10677e25e74bb0a020054a86b31d1806c5e6a9e32e75472bbf177210bc16e5f97453be8bda7ae2e3d97669dbb2901f8c30b39ce53929cbea6746 - languageName: node - linkType: hard - "find-yarn-workspace-root2@npm:1.2.16": version: 1.2.16 resolution: "find-yarn-workspace-root2@npm:1.2.16" @@ -31397,10 +29927,10 @@ __metadata: languageName: node linkType: hard -"flatted@npm:3.3.3, flatted@npm:^3.1.0, flatted@npm:^3.2.7": - version: 3.3.3 - resolution: "flatted@npm:3.3.3" - checksum: 10/8c96c02fbeadcf4e8ffd0fa24983241e27698b0781295622591fc13585e2f226609d95e422bcf2ef044146ffacb6b68b1f20871454eddf75ab3caa6ee5f4a1fe +"flatted@npm:^3.1.0, flatted@npm:^3.2.7, flatted@npm:^3.4.2": + version: 3.4.2 + resolution: "flatted@npm:3.4.2" + checksum: 10/a9e78fe5c2c1fcd98209a015ccee3a6caa953e01729778e83c1fe92e68601a63e1e69cd4e573010ca99eaf585a581b80ccf1018b99283e6cbc2117bcba1e030f languageName: node linkType: hard @@ -31430,13 +29960,13 @@ __metadata: languageName: node linkType: hard -"follow-redirects@npm:^1.0.0, follow-redirects@npm:^1.15.6": - version: 1.15.6 - resolution: "follow-redirects@npm:1.15.6" +"follow-redirects@npm:^1.0.0, follow-redirects@npm:^1.15.11": + version: 1.15.11 + resolution: "follow-redirects@npm:1.15.11" peerDependenciesMeta: debug: optional: true - checksum: 10/70c7612c4cab18e546e36b991bbf8009a1a41cf85354afe04b113d1117569abf760269409cb3eb842d9f7b03d62826687086b081c566ea7b1e6613cf29030bf7 + checksum: 10/07372fd74b98c78cf4d417d68d41fdaa0be4dcacafffb9e67b1e3cf090bc4771515e65020651528faab238f10f9b9c0d9707d6c1574a6c0387c5de1042cde9ba languageName: node linkType: hard @@ -31456,7 +29986,7 @@ __metadata: languageName: node linkType: hard -"foreground-child@npm:^3.1.0, foreground-child@npm:^3.3.1": +"foreground-child@npm:^3.1.0": version: 3.3.1 resolution: "foreground-child@npm:3.3.1" dependencies: @@ -31467,8 +29997,8 @@ __metadata: linkType: hard "fork-ts-checker-webpack-plugin@npm:^6.5.0": - version: 6.5.0 - resolution: "fork-ts-checker-webpack-plugin@npm:6.5.0" + version: 6.5.3 + resolution: "fork-ts-checker-webpack-plugin@npm:6.5.3" dependencies: "@babel/code-frame": "npm:^7.8.3" "@types/json-schema": "npm:^7.0.5" @@ -31493,17 +30023,17 @@ __metadata: optional: true vue-template-compiler: optional: true - checksum: 10/165af8617ff639781d0da76de9899bfcfdf9db4758d8f1dde435aa81efcf963ff776bb5b16f2f371d919faaba7d1037e046ec985d2b2bae4031c2c10eae9b6c9 + checksum: 10/415263839afe11c291be60e3335ece3ccdc80c5e0d91eeecf0d3060cfb72c7b0cb33be326dd24b325939357d53215e10c41e8187edb5db8a08fe9aaa8aa6c510 languageName: node linkType: hard "fork-ts-checker-webpack-plugin@npm:^9.0.0": - version: 9.0.2 - resolution: "fork-ts-checker-webpack-plugin@npm:9.0.2" + version: 9.1.0 + resolution: "fork-ts-checker-webpack-plugin@npm:9.1.0" dependencies: "@babel/code-frame": "npm:^7.16.7" chalk: "npm:^4.1.2" - chokidar: "npm:^3.5.3" + chokidar: "npm:^4.0.1" cosmiconfig: "npm:^8.2.0" deepmerge: "npm:^4.2.2" fs-extra: "npm:^10.0.0" @@ -31516,7 +30046,7 @@ __metadata: peerDependencies: typescript: ">3.6.0" webpack: ^5.11.0 - checksum: 10/3399ea114d9397da5e51baaa1cf2ca704f4616c8255df672889cafc2a823f77e8bf9913133852d29d676657b1d40e4a2fba8eb9fafee09e00faeda924289220e + checksum: 10/1d24387224f7d49a17f7e44c9150971172f34ae30c4b1f581b8af967e73e8f36a434ed56f78aa45fd8cf0833c73a1b020102cc61070d7dc630b70c21c9770a1b languageName: node linkType: hard @@ -31548,16 +30078,16 @@ __metadata: languageName: node linkType: hard -"form-data@npm:^4.0.0, form-data@npm:^4.0.4": - version: 4.0.4 - resolution: "form-data@npm:4.0.4" +"form-data@npm:^4.0.0, form-data@npm:^4.0.4, form-data@npm:^4.0.5": + version: 4.0.5 + resolution: "form-data@npm:4.0.5" dependencies: asynckit: "npm:^0.4.0" combined-stream: "npm:^1.0.8" es-set-tostringtag: "npm:^2.1.0" hasown: "npm:^2.0.2" mime-types: "npm:^2.1.12" - checksum: 10/a4b62e21932f48702bc468cc26fb276d186e6b07b557e3dd7cc455872bdbb82db7db066844a64ad3cf40eaf3a753c830538183570462d3649fdfd705601cbcfb + checksum: 10/52ecd6e927c8c4e215e68a7ad5e0f7c1031397439672fd9741654b4a94722c4182e74cc815b225dcb5be3f4180f36428f67c6dd39eaa98af0dcfdd26c00c19cd languageName: node linkType: hard @@ -31568,6 +30098,17 @@ __metadata: languageName: node linkType: hard +"formatly@npm:^0.3.0": + version: 0.3.0 + resolution: "formatly@npm:0.3.0" + dependencies: + fd-package-json: "npm:^2.0.0" + bin: + formatly: bin/index.mjs + checksum: 10/0e5a9cbb826d93171b00c283e20e6a564a16e7bc3839e695790347a1f23e3536a88d613f5cabd07403d60b7bdffe179987c88b1fc2900a9be49eea01ffbe4244 + languageName: node + linkType: hard + "formdata-node@npm:^4.3.2, formdata-node@npm:^4.3.3": version: 4.4.1 resolution: "formdata-node@npm:4.4.1" @@ -31624,6 +30165,28 @@ __metadata: languageName: node linkType: hard +"framer-motion@npm:^12.38.0": + version: 12.38.0 + resolution: "framer-motion@npm:12.38.0" + dependencies: + motion-dom: "npm:^12.38.0" + motion-utils: "npm:^12.36.0" + tslib: "npm:^2.4.0" + peerDependencies: + "@emotion/is-prop-valid": "*" + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/is-prop-valid": + optional: true + react: + optional: true + react-dom: + optional: true + checksum: 10/4d529d1648a8e31ec9859e7ff1296b7e4ef0028eb09cbc7d626068766ab53e486038b431fac33b1438a1cc076a244e6843c5a8c0f38442885832308452b4b25e + languageName: node + linkType: hard + "framer-motion@npm:^6.5.1": version: 6.5.1 resolution: "framer-motion@npm:6.5.1" @@ -31685,26 +30248,14 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:11.3.2, fs-extra@npm:^11.0.0, fs-extra@npm:^11.1.0, fs-extra@npm:^11.2.0": - version: 11.3.2 - resolution: "fs-extra@npm:11.3.2" +"fs-extra@npm:11.3.4, fs-extra@npm:^11.0.0, fs-extra@npm:^11.1.0, fs-extra@npm:^11.2.0, fs-extra@npm:~11.3.0": + version: 11.3.4 + resolution: "fs-extra@npm:11.3.4" dependencies: graceful-fs: "npm:^4.2.0" jsonfile: "npm:^6.0.1" universalify: "npm:^2.0.0" - checksum: 10/d559545c73fda69c75aa786f345c2f738b623b42aea850200b1582e006a35278f63787179e3194ba19413c26a280441758952b0c7e88dd96762d497e365a6c3e - languageName: node - linkType: hard - -"fs-extra@npm:9.1.0, fs-extra@npm:^9.0.0": - version: 9.1.0 - resolution: "fs-extra@npm:9.1.0" - dependencies: - at-least-node: "npm:^1.0.0" - graceful-fs: "npm:^4.2.0" - jsonfile: "npm:^6.0.1" - universalify: "npm:^2.0.0" - checksum: 10/08600da1b49552ed23dfac598c8fc909c66776dd130fea54fbcad22e330f7fcc13488bb995f6bc9ce5651aa35b65702faf616fe76370ee56f1aade55da982dca + checksum: 10/1b8deea9c540a2efe63c750bc9e1ba6238115579d1571d67fe8fb58e3fb6df19aba29fd4ebb81217cf0bf5bce0df30ca68dbc3e06f6652b856edd385ce0ff649 languageName: node linkType: hard @@ -31719,7 +30270,7 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:^7.0.1, fs-extra@npm:~7.0.1": +"fs-extra@npm:^7.0.1": version: 7.0.1 resolution: "fs-extra@npm:7.0.1" dependencies: @@ -31741,6 +30292,18 @@ __metadata: languageName: node linkType: hard +"fs-extra@npm:^9.0.0": + version: 9.1.0 + resolution: "fs-extra@npm:9.1.0" + dependencies: + at-least-node: "npm:^1.0.0" + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^6.0.1" + universalify: "npm:^2.0.0" + checksum: 10/08600da1b49552ed23dfac598c8fc909c66776dd130fea54fbcad22e330f7fcc13488bb995f6bc9ce5651aa35b65702faf616fe76370ee56f1aade55da982dca + languageName: node + linkType: hard + "fs-minipass@npm:^2.0.0, fs-minipass@npm:^2.1.0": version: 2.1.0 resolution: "fs-minipass@npm:2.1.0" @@ -31790,7 +30353,7 @@ __metadata: languageName: node linkType: hard -"fsevents@npm:^2.3.2, fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": +"fsevents@npm:^2.3.3, fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": version: 2.3.3 resolution: "fsevents@npm:2.3.3" dependencies: @@ -31809,7 +30372,7 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": +"fsevents@patch:fsevents@npm%3A^2.3.3#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": version: 2.3.3 resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" dependencies: @@ -31900,29 +30463,18 @@ __metadata: languageName: node linkType: hard -"gaxios@npm:^7.0.0, gaxios@npm:^7.0.0-rc.4, gaxios@npm:^7.1.2": - version: 7.1.3 - resolution: "gaxios@npm:7.1.3" +"gaxios@npm:^7.0.0, gaxios@npm:^7.0.0-rc.4, gaxios@npm:^7.1.4": + version: 7.1.4 + resolution: "gaxios@npm:7.1.4" dependencies: extend: "npm:^3.0.2" https-proxy-agent: "npm:^7.0.1" node-fetch: "npm:^3.3.2" - rimraf: "npm:^5.0.1" - checksum: 10/234ae4d622c41472a0f1be252a9a0f0a6f4f6ae2418671ef83fd715b9e315100c621818d721fdf0e7471ef49a460f196eaf318c2b36ed5f51a1cf0f6a2639111 + checksum: 10/fbc303260ebdc77b891c51ed996a238175360cb1cfa5033ccd075ec1d8d5f0a91304df28741ed16902de6a6f5af58e3470cff131fa2f3ddccf88a4883339e243 languageName: node linkType: hard -"gcp-metadata@npm:^6.0.0, gcp-metadata@npm:^6.1.0": - version: 6.1.0 - resolution: "gcp-metadata@npm:6.1.0" - dependencies: - gaxios: "npm:^6.0.0" - json-bigint: "npm:^1.0.0" - checksum: 10/a0d12a9cb7499fdb9de0fff5406aa220310c1326b80056be8d9b747aae26414f99d14bd795c0ec52ef7d0473eef9d61bb657b8cd3d8186c8a84c4ddbff025fe9 - languageName: node - linkType: hard - -"gcp-metadata@npm:^8.0.0": +"gcp-metadata@npm:8.1.2, gcp-metadata@npm:^8.0.0": version: 8.1.2 resolution: "gcp-metadata@npm:8.1.2" dependencies: @@ -31933,6 +30485,16 @@ __metadata: languageName: node linkType: hard +"gcp-metadata@npm:^6.1.0": + version: 6.1.0 + resolution: "gcp-metadata@npm:6.1.0" + dependencies: + gaxios: "npm:^6.0.0" + json-bigint: "npm:^1.0.0" + checksum: 10/a0d12a9cb7499fdb9de0fff5406aa220310c1326b80056be8d9b747aae26414f99d14bd795c0ec52ef7d0473eef9d61bb657b8cd3d8186c8a84c4ddbff025fe9 + languageName: node + linkType: hard + "generate-function@npm:^2.3.1": version: 2.3.1 resolution: "generate-function@npm:2.3.1" @@ -31942,6 +30504,13 @@ __metadata: languageName: node linkType: hard +"generator-function@npm:^2.0.0": + version: 2.0.1 + resolution: "generator-function@npm:2.0.1" + checksum: 10/eb7e7eb896c5433f3d40982b2ccacdb3dd990dd3499f14040e002b5d54572476513be8a2e6f9609f6e41ab29f2c4469307611ddbfc37ff4e46b765c326663805 + languageName: node + linkType: hard + "generic-names@npm:^2.0.1": version: 2.0.1 resolution: "generic-names@npm:2.0.1" @@ -31982,28 +30551,31 @@ __metadata: languageName: node linkType: hard -"get-east-asian-width@npm:^1.0.0": - version: 1.2.0 - resolution: "get-east-asian-width@npm:1.2.0" - checksum: 10/c9b280e7c7c67fb89fa17e867c4a9d1c9f1321aba2a9ee27bff37fb6ca9552bccda328c70a80c1f83a0e39ba1b7e3427e60f47823402d19e7a41b83417ec047a +"get-east-asian-width@npm:^1.0.0, get-east-asian-width@npm:^1.3.1, get-east-asian-width@npm:^1.5.0": + version: 1.5.0 + resolution: "get-east-asian-width@npm:1.5.0" + checksum: 10/60bc34cd1e975055ab99f0f177e31bed3e516ff7cee9c536474383954a976abaa6b94a51d99ad158ef1e372790fa096cab7d07f166bb0778f6587954c0fbe946 languageName: node linkType: hard "get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.3.0": - version: 1.3.0 - resolution: "get-intrinsic@npm:1.3.0" + version: 1.3.1 + resolution: "get-intrinsic@npm:1.3.1" dependencies: + async-function: "npm:^1.0.0" + async-generator-function: "npm:^1.0.0" call-bind-apply-helpers: "npm:^1.0.2" es-define-property: "npm:^1.0.1" es-errors: "npm:^1.3.0" es-object-atoms: "npm:^1.1.1" function-bind: "npm:^1.1.2" + generator-function: "npm:^2.0.0" get-proto: "npm:^1.0.1" gopd: "npm:^1.2.0" has-symbols: "npm:^1.1.0" hasown: "npm:^2.0.2" math-intrinsics: "npm:^1.1.0" - checksum: 10/6e9dd920ff054147b6f44cb98104330e87caafae051b6d37b13384a45ba15e71af33c3baeac7cb630a0aaa23142718dcf25b45cfdd86c184c5dcb4e56d953a10 + checksum: 10/bb579dda84caa4a3a41611bdd483dade7f00f246f2a7992eb143c5861155290df3fdb48a8406efa3dfb0b434e2c8fafa4eebd469e409d0439247f85fc3fa2cc1 languageName: node linkType: hard @@ -32035,6 +30607,13 @@ __metadata: languageName: node linkType: hard +"get-port@npm:^7.1.0": + version: 7.1.0 + resolution: "get-port@npm:7.1.0" + checksum: 10/f4d23b43026124007663a899578cc87ff37bfcf645c5c72651e9810ebafc759857784e409fb8e0ada9b90e5c5db089b0ae2f5f6b49fba1ce2e0aff86094ab17d + languageName: node + linkType: hard + "get-proto@npm:^1.0.1": version: 1.0.1 resolution: "get-proto@npm:1.0.1" @@ -32045,13 +30624,6 @@ __metadata: languageName: node linkType: hard -"get-stdin@npm:^9.0.0": - version: 9.0.0 - resolution: "get-stdin@npm:9.0.0" - checksum: 10/5972bc34d05932b45512c8e2d67b040f1c1ca8afb95c56cbc480985f2d761b7e37fe90dc8abd22527f062cc5639a6930ff346e9952ae4c11a2d4275869459594 - languageName: node - linkType: hard - "get-stream@npm:^4.0.0, get-stream@npm:^4.1.0": version: 4.1.0 resolution: "get-stream@npm:4.1.0" @@ -32077,13 +30649,6 @@ __metadata: languageName: node linkType: hard -"get-stream@npm:^8.0.1": - version: 8.0.1 - resolution: "get-stream@npm:8.0.1" - checksum: 10/dde5511e2e65a48e9af80fea64aff11b4921b14b6e874c6f8294c50975095af08f41bfb0b680c887f28b566dd6ec2cb2f960f9d36a323359be324ce98b766e9e - languageName: node - linkType: hard - "get-stream@npm:^9.0.1": version: 9.0.1 resolution: "get-stream@npm:9.0.1" @@ -32105,12 +30670,12 @@ __metadata: languageName: node linkType: hard -"get-tsconfig@npm:^4.7.0, get-tsconfig@npm:^4.7.2": - version: 4.7.3 - resolution: "get-tsconfig@npm:4.7.3" +"get-tsconfig@npm:^4.10.0, get-tsconfig@npm:^4.10.1": + version: 4.13.0 + resolution: "get-tsconfig@npm:4.13.0" dependencies: resolve-pkg-maps: "npm:^1.0.0" - checksum: 10/7397bb4f8aef936df4d9016555b662dcf5279f3c46428b7c7c1ff5e94ab2b87d018b3dda0f4bc1a28b154d5affd0eac5d014511172c085fd8a9cdff9ea7fe043 + checksum: 10/3603c6da30e312636e4c20461e779114c9126601d1eca70ee4e36e3e3c00e3c21892d2d920027333afa2cc9e20998a436b14abe03a53cde40742581cb0e9ceb2 languageName: node linkType: hard @@ -32126,6 +30691,15 @@ __metadata: languageName: node linkType: hard +"get-value@npm:^3.0.1": + version: 3.0.1 + resolution: "get-value@npm:3.0.1" + dependencies: + isobject: "npm:^3.0.1" + checksum: 10/3ba777d33448181d8b6c21ce11f31194257119d32dbd632b27db6e3f27fe78100405b4dd93137eea9f4aa2d0a9c623b13747d728d472a7ca6cb93046c3f521a1 + languageName: node + linkType: hard + "getopts@npm:2.3.0": version: 2.3.0 resolution: "getopts@npm:2.3.0" @@ -32133,10 +30707,10 @@ __metadata: languageName: node linkType: hard -"git-hooks-list@npm:^3.0.0": - version: 3.1.0 - resolution: "git-hooks-list@npm:3.1.0" - checksum: 10/05cbdb29e1e14f3b6fde78c876a34383e4476b1be32e8486ad03293f01add884c1a8df8c2dce2ca5d99119c94951b2ff9fa9cbd51d834ae6477b6813cefb998f +"git-hooks-list@npm:^4.1.1": + version: 4.2.1 + resolution: "git-hooks-list@npm:4.2.1" + checksum: 10/39449520045539c03b1d45d3a010424849152d6f723b638c6f19cb8c8b0993bb30ed5ef09653f4d1c1356f5fb51a396059096d8bdfcb6ca7ccb54f6e26efc338 languageName: node linkType: hard @@ -32202,6 +30776,15 @@ __metadata: languageName: node linkType: hard +"glob-to-regex.js@npm:^1.0.0, glob-to-regex.js@npm:^1.0.1": + version: 1.2.0 + resolution: "glob-to-regex.js@npm:1.2.0" + peerDependencies: + tslib: 2 + checksum: 10/13034e642db479d75448bdd9f37de7451bef2879c394bfe3f8df6588e0479893e94059eaee77cdf50dce675607fb2395c132dcca0c9a559a6192e89b2ad0f134 + languageName: node + linkType: hard + "glob-to-regexp@npm:^0.4.1": version: 0.4.1 resolution: "glob-to-regexp@npm:0.4.1" @@ -32209,25 +30792,20 @@ __metadata: languageName: node linkType: hard -"glob@npm:11.0.3, glob@npm:^11.0.1": - version: 11.0.3 - resolution: "glob@npm:11.0.3" +"glob@npm:13.0.6, glob@npm:^13.0.0, glob@npm:^13.0.1": + version: 13.0.6 + resolution: "glob@npm:13.0.6" dependencies: - foreground-child: "npm:^3.3.1" - jackspeak: "npm:^4.1.1" - minimatch: "npm:^10.0.3" - minipass: "npm:^7.1.2" - package-json-from-dist: "npm:^1.0.0" - path-scurry: "npm:^2.0.0" - bin: - glob: dist/esm/bin.mjs - checksum: 10/2ae536c1360c0266b523b2bfa6aadc10144a8b7e08869b088e37ac3c27cd30774f82e4bfb291cde796776e878f9e13200c7ff44010eb7054e00f46f649397893 + minimatch: "npm:^10.2.2" + minipass: "npm:^7.1.3" + path-scurry: "npm:^2.0.2" + checksum: 10/201ad69e5f0aa74e1d8c00a481581f8b8c804b6a4fbfabeeb8541f5d756932800331daeba99b58fb9e4cd67e12ba5a7eba5b82fb476691588418060b84353214 languageName: node linkType: hard -"glob@npm:^10.0.0, glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.3.7, glob@npm:^10.4.1": - version: 10.4.5 - resolution: "glob@npm:10.4.5" +"glob@npm:^10.0.0, glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.4.1": + version: 10.5.0 + resolution: "glob@npm:10.5.0" dependencies: foreground-child: "npm:^3.1.0" jackspeak: "npm:^3.1.2" @@ -32237,7 +30815,7 @@ __metadata: path-scurry: "npm:^1.11.1" bin: glob: dist/esm/bin.mjs - checksum: 10/698dfe11828b7efd0514cd11e573eaed26b2dff611f0400907281ce3eab0c1e56143ef9b35adc7c77ecc71fba74717b510c7c223d34ca8a98ec81777b293d4ac + checksum: 10/ab3bccfefcc0afaedbd1f480cd0c4a2c0e322eb3f0aa7ceaa31b3f00b825069f17cf0f1fc8b6f256795074b903f37c0ade37ddda6a176aa57f1c2bbfe7240653 languageName: node linkType: hard @@ -32255,7 +30833,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^8.0.1, glob@npm:^8.0.3, glob@npm:^8.1.0": +"glob@npm:^8.0.1, glob@npm:^8.1.0": version: 8.1.0 resolution: "glob@npm:8.1.0" dependencies: @@ -32344,10 +30922,10 @@ __metadata: languageName: node linkType: hard -"globals@npm:^15.11.0": - version: 15.15.0 - resolution: "globals@npm:15.15.0" - checksum: 10/7f561c87b2fd381b27fc2db7df8a4ea7a9bb378667b8a7193e61fd2ca3a876479174e2a303a74345fbea6e1242e16db48915c1fd3bf35adcf4060a795b425e18 +"globals@npm:^17.0.0, globals@npm:^17.3.0": + version: 17.4.0 + resolution: "globals@npm:17.4.0" + checksum: 10/ffad244617e94efcb3da72b7beefc941167c21316148ce378f322db7af72db06468f370e23224b3c7b17b5173a7c75b134e5e7b0949f2828519054a76892508d languageName: node linkType: hard @@ -32382,20 +30960,6 @@ __metadata: languageName: node linkType: hard -"globby@npm:^14.0.2": - version: 14.0.2 - resolution: "globby@npm:14.0.2" - dependencies: - "@sindresorhus/merge-streams": "npm:^2.1.0" - fast-glob: "npm:^3.3.2" - ignore: "npm:^5.2.4" - path-type: "npm:^5.0.0" - slash: "npm:^5.1.0" - unicorn-magic: "npm:^0.1.0" - checksum: 10/67660da70fc1223f7170c1a62ba6c373385e9e39765d952b6518606dec15ed8c7958e9dae6ba5752a31dbc1e9126f146938b830ad680fe794141734ffc3fbb75 - languageName: node - linkType: hard - "globrex@npm:^0.1.2": version: 0.1.2 resolution: "globrex@npm:0.1.2" @@ -32423,18 +30987,17 @@ __metadata: languageName: node linkType: hard -"google-auth-library@npm:^10.0.0-rc.1, google-auth-library@npm:^10.4.0": - version: 10.5.0 - resolution: "google-auth-library@npm:10.5.0" +"google-auth-library@npm:^10.1.0, google-auth-library@npm:^10.6.1": + version: 10.6.2 + resolution: "google-auth-library@npm:10.6.2" dependencies: base64-js: "npm:^1.3.0" ecdsa-sig-formatter: "npm:^1.0.11" - gaxios: "npm:^7.0.0" - gcp-metadata: "npm:^8.0.0" - google-logging-utils: "npm:^1.0.0" - gtoken: "npm:^8.0.0" + gaxios: "npm:^7.1.4" + gcp-metadata: "npm:8.1.2" + google-logging-utils: "npm:1.1.3" jws: "npm:^4.0.0" - checksum: 10/f9cec00f17f1082bc2e1e342043425063e347a06c4b4ae40c5d644f4491755b18690cb7f50daa9aa056539b14f993febdc009b0dada0c9b5bcf35c7e0caf78a3 + checksum: 10/06837fc3746b657cdc199525dc91c1f1737aae0ce7d2d4f60893cb9b6e1364eb2891eb123339eaaef62f133616695214cf31995b6faf85edef4645595a8b45e8 languageName: node linkType: hard @@ -32472,10 +31035,10 @@ __metadata: languageName: node linkType: hard -"google-logging-utils@npm:^1.0.0": - version: 1.1.1 - resolution: "google-logging-utils@npm:1.1.1" - checksum: 10/51f915d97a812af9edeccf24d43bbec5f4d98dab4f874a08083e847d495fc94185d309f21ae7f590c9f7951bc25bc3095fa4ae73113aba47e86482f69860a806 +"google-logging-utils@npm:1.1.3, google-logging-utils@npm:^1.0.0": + version: 1.1.3 + resolution: "google-logging-utils@npm:1.1.3" + checksum: 10/5a6c090399545e0f1f2c92fbda316479dc5d573b2f4b54f0deb570dc31d8b254537894fd4e7c275ce7d352482e40d5857fa4b960c1b3d869584b5216dc2076e2 languageName: node linkType: hard @@ -32486,16 +31049,16 @@ __metadata: languageName: node linkType: hard -"googleapis-common@npm:^8.0.2-rc.0": - version: 8.0.2-rc.0 - resolution: "googleapis-common@npm:8.0.2-rc.0" +"googleapis-common@npm:^8.0.0": + version: 8.0.1 + resolution: "googleapis-common@npm:8.0.1" dependencies: extend: "npm:^3.0.2" gaxios: "npm:^7.0.0-rc.4" - google-auth-library: "npm:^10.0.0-rc.1" + google-auth-library: "npm:^10.1.0" qs: "npm:^6.7.0" url-template: "npm:^2.0.8" - checksum: 10/f630e8f37b2baa4980e3a66fb7736caa2a47f1f915bc6082ef844aa24be776ad2fbff39631346dcccc01c99dd5707fca02c786c6ba23ae459bcb23b125b4fb63 + checksum: 10/732cd95913cc44e0f543eb686a32588953e3ee1d84628e1616093f78604a0c7c5a5b0d4e085ece94ac0d33edfb9bcdb9ba3d8763e133bab4f3a610711e40305b languageName: node linkType: hard @@ -32565,35 +31128,33 @@ __metadata: languageName: node linkType: hard -"graphiql@npm:3.1.1": - version: 3.1.1 - resolution: "graphiql@npm:3.1.1" +"graphiql@npm:^3.9.0": + version: 3.9.0 + resolution: "graphiql@npm:3.9.0" dependencies: - "@graphiql/react": "npm:^0.20.3" - "@graphiql/toolkit": "npm:^0.9.1" - graphql-language-service: "npm:^5.2.0" - markdown-it: "npm:^12.2.0" + "@graphiql/react": "npm:^0.29.0" + react-compiler-runtime: "npm:19.1.0-rc.1" peerDependencies: - graphql: ^15.5.0 || ^16.0.0 + graphql: ^15.5.0 || ^16.0.0 || ^17.0.0 react: ^16.8.0 || ^17 || ^18 react-dom: ^16.8.0 || ^17 || ^18 - checksum: 10/c234f24e3bb568f210a45f6a408cc12adb2a82cc7be8ecbdf81cadc7ae398f35474a443fbc20e99d6125acf20268e6a666b05b928725542ea3d133c51798687f + checksum: 10/33a17064594fc883262de07e8cd3012563cf5965e401a8ab3e1aed9c38e69b4222a3cd1cefa58e3c0e5ba9e01dcd403d0452c4991814702da4d6fb37acc9a8ae languageName: node linkType: hard -"graphql-config@npm:^5.0.2": - version: 5.1.5 - resolution: "graphql-config@npm:5.1.5" +"graphql-config@npm:^5.1.6": + version: 5.1.6 + resolution: "graphql-config@npm:5.1.6" dependencies: "@graphql-tools/graphql-file-loader": "npm:^8.0.0" "@graphql-tools/json-file-loader": "npm:^8.0.0" "@graphql-tools/load": "npm:^8.1.0" "@graphql-tools/merge": "npm:^9.0.0" - "@graphql-tools/url-loader": "npm:^8.0.0" - "@graphql-tools/utils": "npm:^10.0.0" + "@graphql-tools/url-loader": "npm:^9.0.0" + "@graphql-tools/utils": "npm:^11.0.0" cosmiconfig: "npm:^8.1.0" jiti: "npm:^2.0.0" - minimatch: "npm:^9.0.5" + minimatch: "npm:^10.0.0" string-env-interpolation: "npm:^1.0.1" tslib: "npm:^2.4.0" peerDependencies: @@ -32602,7 +31163,7 @@ __metadata: peerDependenciesMeta: cosmiconfig-toml-loader: optional: true - checksum: 10/f33a4e73265f84790888d05d7dbf50a0b3454adf6e184ac018165e679ecab7fca746fbf4069dea601261f193dcb5a015a1679403ea1fd4eab109e79d8fed306d + checksum: 10/941b3166d395d22ae6f4048c564b09364d2234c88f8ad596188599d4650c28a35a2484b549e0ef239ae461a23874efc69e0234f7f8977a8a6f39640867086003 languageName: node linkType: hard @@ -32615,18 +31176,18 @@ __metadata: languageName: node linkType: hard -"graphql-language-service@npm:5.2.2, graphql-language-service@npm:^5.2.0, graphql-language-service@npm:^5.2.2": - version: 5.2.2 - resolution: "graphql-language-service@npm:5.2.2" +"graphql-language-service@npm:5.5.0, graphql-language-service@npm:^5.3.1": + version: 5.5.0 + resolution: "graphql-language-service@npm:5.5.0" dependencies: debounce-promise: "npm:^3.1.2" nullthrows: "npm:^1.0.0" vscode-languageserver-types: "npm:^3.17.1" peerDependencies: - graphql: ^15.5.0 || ^16.0.0 + graphql: ^15.5.0 || ^16.0.0 || ^17.0.0 bin: graphql: dist/temp-bin.js - checksum: 10/3148b0a49fb2784eaf363a2d7e4fe0800f69c11d24528c2daaff1bf3d07be24db61e1d84b6bec94c68068f7ad1fc6d459edfbda964418ad3735bc353a636a690 + checksum: 10/7f398395cab50faaad717d107413df5424ac102bf33ac4a5e4ef34bf7c2e13a27d51fd93456601f560874801bf93bec281589164d901bab3aef56f4518782f60 languageName: node linkType: hard @@ -32652,7 +31213,7 @@ __metadata: languageName: node linkType: hard -"graphql-ws@npm:^5.14.0, graphql-ws@npm:^5.4.1": +"graphql-ws@npm:^5.4.1": version: 5.16.2 resolution: "graphql-ws@npm:5.16.2" peerDependencies: @@ -32661,17 +31222,36 @@ __metadata: languageName: node linkType: hard +"graphql-ws@npm:^6.0.6": + version: 6.0.7 + resolution: "graphql-ws@npm:6.0.7" + peerDependencies: + "@fastify/websocket": ^10 || ^11 + crossws: ~0.3 + graphql: ^15.10.1 || ^16 + ws: ^8 + peerDependenciesMeta: + "@fastify/websocket": + optional: true + crossws: + optional: true + ws: + optional: true + checksum: 10/633b142a7a8683f900f1f3590a30ff696076d94d17cc8c0a42d069288cd8ca77b4967e87a9f7ac884c026106be42055b9e2bda5e7de28579bd2fc8e65d0b0424 + languageName: node + linkType: hard + "graphql@npm:^14.0.2 || ^15.5": - version: 15.9.0 - resolution: "graphql@npm:15.9.0" - checksum: 10/ce1f50672bcb369395d07a47048bcbb429ed1ce06dbcafb7a0999df791cb7aa7206be21497907973dbc8a01df3cd7f632f43c583f248538f186f5adfa1a0d1c5 + version: 15.10.2 + resolution: "graphql@npm:15.10.2" + checksum: 10/41abccb2621a9dc7ca9cba26272734c298ced50c8a4ee1412dacfaa0e2e9ac927d2e498a66e443c1dab0ac28825cb6e528d5f47f4b9019623c1f8a2189bcbe37 languageName: node linkType: hard "graphql@npm:^16.0.0, graphql@npm:^16.8.1": - version: 16.10.0 - resolution: "graphql@npm:16.10.0" - checksum: 10/d42cf81ddcf3a61dfb213217576bf33c326f15b02c4cee369b373dc74100cbdcdc4479b3b797e79b654dabd8fddf50ef65ff75420e9ce5596c02e21f24c9126a + version: 16.13.2 + resolution: "graphql@npm:16.13.2" + checksum: 10/9ede86f0a7227d47a41e2076e0132839f66fbd14899abfd818d7de2ab81076ee249c8788ad42243367d68a027a8763cc4c7be2e441ccfb03a8549490c7b66c2f languageName: node linkType: hard @@ -32716,16 +31296,6 @@ __metadata: languageName: node linkType: hard -"gtoken@npm:^8.0.0": - version: 8.0.0 - resolution: "gtoken@npm:8.0.0" - dependencies: - gaxios: "npm:^7.0.0" - jws: "npm:^4.0.0" - checksum: 10/b921430395dcd06ee63c3fc5a5e339ca4d6dcb38b6d618beb0f260bae1088d53d130f86029a9d578f1601c64685f49a65dba57bbd617c4b14039180b67b6c5ce - languageName: node - linkType: hard - "gzip-size@npm:^6.0.0": version: 6.0.0 resolution: "gzip-size@npm:6.0.0" @@ -32743,8 +31313,8 @@ __metadata: linkType: hard "handlebars@npm:^4.7.3, handlebars@npm:^4.7.8": - version: 4.7.8 - resolution: "handlebars@npm:4.7.8" + version: 4.7.9 + resolution: "handlebars@npm:4.7.9" dependencies: minimist: "npm:^1.2.5" neo-async: "npm:^2.6.2" @@ -32756,7 +31326,7 @@ __metadata: optional: true bin: handlebars: bin/handlebars - checksum: 10/bd528f4dd150adf67f3f857118ef0fa43ff79a153b1d943fa0a770f2599e38b25a7a0dbac1a3611a4ec86970fd2325a81310fb788b5c892308c9f8743bd02e11 + checksum: 10/e755433d652e8a15fc02f83d7478e652359e7a4d354c4328818853ed4f8a39d4a09e1d22dad3c7213c5240864a65b3c840970b8b181745575dd957dd258f2b8d languageName: node linkType: hard @@ -32872,7 +31442,7 @@ __metadata: languageName: node linkType: hard -"hasown@npm:^2.0.0, hasown@npm:^2.0.2": +"hasown@npm:^2.0.2": version: 2.0.2 resolution: "hasown@npm:2.0.2" dependencies: @@ -32881,6 +31451,21 @@ __metadata: languageName: node linkType: hard +"hast-util-from-parse5@npm:^7.0.0": + version: 7.1.2 + resolution: "hast-util-from-parse5@npm:7.1.2" + dependencies: + "@types/hast": "npm:^2.0.0" + "@types/unist": "npm:^2.0.0" + hastscript: "npm:^7.0.0" + property-information: "npm:^6.0.0" + vfile: "npm:^5.0.0" + vfile-location: "npm:^4.0.0" + web-namespaces: "npm:^2.0.0" + checksum: 10/7a90a16430a1482ed1be5c2f8b182e8b12aee8834781245b101700b5a04cea8b569cf40ef08214e1eb333249432e861b17e6fe46d0447b5281827c8798e86f1a + languageName: node + linkType: hard + "hast-util-parse-selector@npm:^2.0.0": version: 2.2.4 resolution: "hast-util-parse-selector@npm:2.2.4" @@ -32888,6 +31473,66 @@ __metadata: languageName: node linkType: hard +"hast-util-parse-selector@npm:^3.0.0": + version: 3.1.1 + resolution: "hast-util-parse-selector@npm:3.1.1" + dependencies: + "@types/hast": "npm:^2.0.0" + checksum: 10/511d373465f60dd65e924f88bf0954085f4fb6e3a2b062a4b5ac43b93cbfd36a8dce6234b5d1e3e63499d936375687e83fc5da55628b22bd6b581b5ee167d1c4 + languageName: node + linkType: hard + +"hast-util-parse-selector@npm:^4.0.0": + version: 4.0.0 + resolution: "hast-util-parse-selector@npm:4.0.0" + dependencies: + "@types/hast": "npm:^3.0.0" + checksum: 10/76087670d3b0b50b23a6cb70bca53a6176d6608307ccdbb3ed18b650b82e7c3513bfc40348f1389dc0c5ae872b9a768851f4335f44654abd7deafd6974c52402 + languageName: node + linkType: hard + +"hast-util-raw@npm:^7.2.0": + version: 7.2.3 + resolution: "hast-util-raw@npm:7.2.3" + dependencies: + "@types/hast": "npm:^2.0.0" + "@types/parse5": "npm:^6.0.0" + hast-util-from-parse5: "npm:^7.0.0" + hast-util-to-parse5: "npm:^7.0.0" + html-void-elements: "npm:^2.0.0" + parse5: "npm:^6.0.0" + unist-util-position: "npm:^4.0.0" + unist-util-visit: "npm:^4.0.0" + vfile: "npm:^5.0.0" + web-namespaces: "npm:^2.0.0" + zwitch: "npm:^2.0.0" + checksum: 10/fe39d4b9e68de7131ec61e9abe887cc0579dc7491f738735150c6021565fc998e37c9d096e97fc35c769e986e04b721d4724835ee82fcc22076d778acf6c4832 + languageName: node + linkType: hard + +"hast-util-sanitize@npm:^4.0.0": + version: 4.1.0 + resolution: "hast-util-sanitize@npm:4.1.0" + dependencies: + "@types/hast": "npm:^2.0.0" + checksum: 10/8258ba32be9e57e871f894d3c6b3187dd98e0682bf1382cd1306c20045cd5dc209589919817dc079803d9a259b420d9cd88771535088c9fe0eea122028c348eb + languageName: node + linkType: hard + +"hast-util-to-parse5@npm:^7.0.0": + version: 7.1.0 + resolution: "hast-util-to-parse5@npm:7.1.0" + dependencies: + "@types/hast": "npm:^2.0.0" + comma-separated-tokens: "npm:^2.0.0" + property-information: "npm:^6.0.0" + space-separated-tokens: "npm:^2.0.0" + web-namespaces: "npm:^2.0.0" + zwitch: "npm:^2.0.0" + checksum: 10/695539881431f9713ca4a0be7d06bf3e57ae4d9f930eccba371534c50cff11855d345f8ec30099d04482637ad82e3c70d480269bfa4c109f37993536e8ea690d + languageName: node + linkType: hard + "hast-util-whitespace@npm:^2.0.0": version: 2.0.0 resolution: "hast-util-whitespace@npm:2.0.0" @@ -32908,6 +31553,32 @@ __metadata: languageName: node linkType: hard +"hastscript@npm:^7.0.0": + version: 7.2.0 + resolution: "hastscript@npm:7.2.0" + dependencies: + "@types/hast": "npm:^2.0.0" + comma-separated-tokens: "npm:^2.0.0" + hast-util-parse-selector: "npm:^3.0.0" + property-information: "npm:^6.0.0" + space-separated-tokens: "npm:^2.0.0" + checksum: 10/98740e0b69b4765a23d0174fb93eb1c1bdcae6a9f1c9e1b07de6aca2d578427a42e1d45ee98eda26463ac58ff73a8ce45af19c4eb8b5f6f768a9c8543964d28f + languageName: node + linkType: hard + +"hastscript@npm:^9.0.0": + version: 9.0.1 + resolution: "hastscript@npm:9.0.1" + dependencies: + "@types/hast": "npm:^3.0.0" + comma-separated-tokens: "npm:^2.0.0" + hast-util-parse-selector: "npm:^4.0.0" + property-information: "npm:^7.0.0" + space-separated-tokens: "npm:^2.0.0" + checksum: 10/9aa8135faf0307807cca4075bef4e3403ae1ce959ad4b9e6720892ba957d58ff98b2f60b5eb3ac67d88ae897dc918997299cd4249d7ac602a0066dd46442c5d4 + languageName: node + linkType: hard + "he@npm:1.2.0, he@npm:^1.2.0": version: 1.2.0 resolution: "he@npm:1.2.0" @@ -32939,9 +31610,9 @@ __metadata: linkType: hard "helmet@npm:^6.0.0": - version: 6.0.1 - resolution: "helmet@npm:6.0.1" - checksum: 10/0eec311e1a3820e206226563e55d7115fa66e9a34922229472c1dac407c5d00f0d5c4f5f7afed714fba40287aac1c30dcbfa9b3c096f1146f9d6a55c4527d65d + version: 6.2.0 + resolution: "helmet@npm:6.2.0" + checksum: 10/f112fcd0d8494e6c8ad10e9307e182f1be9c9c4917a3f9a3718c13ae120d4c4e1f251e735297d6a9266e068dcc0463ab101c8d7f2b809c0ceabcef4681f81a2a languageName: node linkType: hard @@ -32952,7 +31623,7 @@ __metadata: languageName: node linkType: hard -"highlight.js@npm:^10.1.0, highlight.js@npm:^10.4.1, highlight.js@npm:^10.7.2, highlight.js@npm:~10.7.0": +"highlight.js@npm:^10.1.0, highlight.js@npm:^10.4.1, highlight.js@npm:^10.7.1, highlight.js@npm:^10.7.2, highlight.js@npm:~10.7.0": version: 10.7.3 resolution: "highlight.js@npm:10.7.3" checksum: 10/db8d10a541936b058e221dbde77869664b2b45bca75d660aa98065be2cd29f3924755fbc7348213f17fd931aefb6e6597448ba6fe82afba6d8313747a91983ee @@ -33004,6 +31675,13 @@ __metadata: languageName: node linkType: hard +"hono@npm:^4.11.4": + version: 4.12.7 + resolution: "hono@npm:4.12.7" + checksum: 10/fed37e612730491ba9456f8f68f1b8727a5298cd839fff9641a0b7a95b1e8567a05abb819d32621b40988f166b01140cf7d573c9218dee2741004f48e09564d5 + languageName: node + linkType: hard + "hookified@npm:^1.10.0": version: 1.12.2 resolution: "hookified@npm:1.12.2" @@ -33053,15 +31731,6 @@ __metadata: languageName: node linkType: hard -"html-encoding-sniffer@npm:^3.0.0": - version: 3.0.0 - resolution: "html-encoding-sniffer@npm:3.0.0" - dependencies: - whatwg-encoding: "npm:^2.0.0" - checksum: 10/707a812ec2acaf8bb5614c8618dc81e2fb6b4399d03e95ff18b65679989a072f4e919b9bef472039301a1bbfba64063ba4c79ea6e851c653ac9db80dbefe8fe5 - languageName: node - linkType: hard - "html-encoding-sniffer@npm:^4.0.0": version: 4.0.0 resolution: "html-encoding-sniffer@npm:4.0.0" @@ -33071,6 +31740,15 @@ __metadata: languageName: node linkType: hard +"html-encoding-sniffer@npm:^6.0.0": + version: 6.0.0 + resolution: "html-encoding-sniffer@npm:6.0.0" + dependencies: + "@exodus/bytes": "npm:^1.6.0" + checksum: 10/97392e45d8aff57f180f62a1b12e62201c8451af68424b8bc3196f78e273891f2df285e5be43a3f28c7ba4badf9524ef305db65c4e4935a9e796afc86d9654b8 + languageName: node + linkType: hard + "html-entities@npm:^2.1.0, html-entities@npm:^2.5.2, html-entities@npm:^2.6.0": version: 2.6.0 resolution: "html-entities@npm:2.6.0" @@ -33102,9 +31780,16 @@ __metadata: languageName: node linkType: hard +"html-void-elements@npm:^2.0.0": + version: 2.0.1 + resolution: "html-void-elements@npm:2.0.1" + checksum: 10/06d41f13b9d5d6e0f39861c4bec9a9196fa4906d56cd5cf6cf54ad2e52a85bf960cca2bf9600026bde16c8331db171bedba5e5a35e2e43630c8f1d497b2fb658 + languageName: node + linkType: hard + "html-webpack-plugin@npm:^5.6.3": - version: 5.6.4 - resolution: "html-webpack-plugin@npm:5.6.4" + version: 5.6.6 + resolution: "html-webpack-plugin@npm:5.6.6" dependencies: "@types/html-minifier-terser": "npm:^6.0.0" html-minifier-terser: "npm:^6.0.2" @@ -33119,7 +31804,7 @@ __metadata: optional: true webpack: optional: true - checksum: 10/781074dd8bacf48236911462201fa440c5f79525665ce5de0c35f262a323765e103caab32a7466db262c4af3d98e869ae6a9b484e967345d85aff80d25a4ca79 + checksum: 10/819ebee079466029a771236fdadcbcfe0aaf110eac1b74c0983a0318a7f99f3c69adcf1d617e218769a8c8e96ea17de2df30759bd950ec391d0c5676d480894e languageName: node linkType: hard @@ -33135,16 +31820,6 @@ __metadata: languageName: node linkType: hard -"http-assert@npm:^1.3.0": - version: 1.5.0 - resolution: "http-assert@npm:1.5.0" - dependencies: - deep-equal: "npm:~1.0.1" - http-errors: "npm:~1.8.0" - checksum: 10/69c9b3c14cf8b2822916360a365089ce936c883c49068f91c365eccba5c141a9964d19fdda589150a480013bf503bf37d8936c732e9635819339e730ab0e7527 - languageName: node - linkType: hard - "http-cache-semantics@npm:^4.0.0, http-cache-semantics@npm:^4.1.0, http-cache-semantics@npm:^4.1.1": version: 4.1.1 resolution: "http-cache-semantics@npm:4.1.1" @@ -33181,7 +31856,7 @@ __metadata: languageName: node linkType: hard -"http-errors@npm:2.0.0, http-errors@npm:^2.0.0": +"http-errors@npm:2.0.0": version: 2.0.0 resolution: "http-errors@npm:2.0.0" dependencies: @@ -33194,16 +31869,16 @@ __metadata: languageName: node linkType: hard -"http-errors@npm:^1.6.3, http-errors@npm:~1.8.0": - version: 1.8.1 - resolution: "http-errors@npm:1.8.1" +"http-errors@npm:^2.0.0, http-errors@npm:~2.0.0, http-errors@npm:~2.0.1": + version: 2.0.1 + resolution: "http-errors@npm:2.0.1" dependencies: - depd: "npm:~1.1.2" - inherits: "npm:2.0.4" - setprototypeof: "npm:1.2.0" - statuses: "npm:>= 1.5.0 < 2" - toidentifier: "npm:1.0.1" - checksum: 10/76fc491bd8df2251e21978e080d5dae20d9736cfb29bb72b5b76ec1bcebb1c14f0f58a3a128dd89288934379d2173cfb0421c571d54103e93dd65ef6243d64d8 + depd: "npm:~2.0.0" + inherits: "npm:~2.0.4" + setprototypeof: "npm:~1.2.0" + statuses: "npm:~2.0.2" + toidentifier: "npm:~1.0.1" + checksum: 10/9fe31bc0edf36566c87048aed1d3d0cbe03552564adc3541626a0613f542d753fbcb13bdfcec0a3a530dbe1714bb566c89d46244616b66bddd26ac413b06a207 languageName: node linkType: hard @@ -33371,17 +32046,10 @@ __metadata: languageName: node linkType: hard -"human-signals@npm:^5.0.0": - version: 5.0.0 - resolution: "human-signals@npm:5.0.0" - checksum: 10/30f8870d831cdcd2d6ec0486a7d35d49384996742052cee792854273fa9dd9e7d5db06bb7985d4953e337e10714e994e0302e90dc6848069171b05ec836d65b0 - languageName: node - linkType: hard - "humanize-duration@npm:^3.25.1": - version: 3.32.2 - resolution: "humanize-duration@npm:3.32.2" - checksum: 10/2dfe847085c7586d2cc83613b8643a4cca70966a16522e3786be07f0283087502e5340e98a79867c7b019ea22ae85c3fa9d1ca1be587eb19ccf03710c986d41d + version: 3.33.2 + resolution: "humanize-duration@npm:3.33.2" + checksum: 10/ac6bc74b095d53e6ff320126d64c0998de213d0e38d0996f23389603c034c8f825915d4318fc19cfdb0612c4963d72d200da43cd7c28ac865bbf9a61f53418f4 languageName: node linkType: hard @@ -33426,16 +32094,7 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:0.4.24": - version: 0.4.24 - resolution: "iconv-lite@npm:0.4.24" - dependencies: - safer-buffer: "npm:>= 2.1.2 < 3" - checksum: 10/6d3a2dac6e5d1fb126d25645c25c3a1209f70cceecc68b8ef51ae0da3cdc078c151fade7524a30b12a3094926336831fca09c666ef55b37e2c69638b5d6bd2e3 - languageName: node - linkType: hard - -"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2, iconv-lite@npm:^0.6.3": +"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2": version: 0.6.3 resolution: "iconv-lite@npm:0.6.3" dependencies: @@ -33444,12 +32103,21 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:^0.7.0": - version: 0.7.0 - resolution: "iconv-lite@npm:0.7.0" +"iconv-lite@npm:^0.7.0, iconv-lite@npm:^0.7.2, iconv-lite@npm:~0.7.0": + version: 0.7.2 + resolution: "iconv-lite@npm:0.7.2" dependencies: safer-buffer: "npm:>= 2.1.2 < 3.0.0" - checksum: 10/5bfc897fedfb7e29991ae5ef1c061ed4f864005f8c6d61ef34aba6a3885c04bd207b278c0642b041383aeac2d11645b4319d0ca7b863b0be4be0cde1c9238ca7 + checksum: 10/24c937b532f868e938386b62410b303b7c767ce3d08dc2829cbe59464d5a26ef86ae5ad1af6b34eec43ddfea39e7d101638644b0178d67262fa87015d59f983a + languageName: node + linkType: hard + +"iconv-lite@npm:~0.4.24": + version: 0.4.24 + resolution: "iconv-lite@npm:0.4.24" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3" + checksum: 10/6d3a2dac6e5d1fb126d25645c25c3a1209f70cceecc68b8ef51ae0da3cdc078c151fade7524a30b12a3094926336831fca09c666ef55b37e2c69638b5d6bd2e3 languageName: node linkType: hard @@ -33519,13 +32187,20 @@ __metadata: languageName: node linkType: hard -"ignore@npm:^5.1.4, ignore@npm:^5.1.8, ignore@npm:^5.2.0, ignore@npm:^5.2.4, ignore@npm:^5.3.1": +"ignore@npm:^5.1.4, ignore@npm:^5.2.0": version: 5.3.2 resolution: "ignore@npm:5.3.2" checksum: 10/cceb6a457000f8f6a50e1196429750d782afce5680dd878aa4221bd79972d68b3a55b4b1458fc682be978f4d3c6a249046aa0880637367216444ab7b014cfc98 languageName: node linkType: hard +"ignore@npm:^7.0.5": + version: 7.0.5 + resolution: "ignore@npm:7.0.5" + checksum: 10/f134b96a4de0af419196f52c529d5c6120c4456ff8a6b5a14ceaaa399f883e15d58d2ce651c9b69b9388491d4669dda47285d307e827de9304a53a1824801bc6 + languageName: node + linkType: hard + "immediate@npm:~3.0.5": version: 3.0.6 resolution: "immediate@npm:3.0.6" @@ -33541,9 +32216,9 @@ __metadata: linkType: hard "immutable@npm:^3.x.x": - version: 3.8.2 - resolution: "immutable@npm:3.8.2" - checksum: 10/8a94647c769e97c9685be1b89d5e1b3171e8c1361fb9061fbcf78f630f70bf60e4de0bfca8bdd24a54b1fb814a945a76a30b11b7ee08967f9802a138a54498a2 + version: 3.8.3 + resolution: "immutable@npm:3.8.3" + checksum: 10/29db366d4dff83b0b296150c351b08db24837005909a71c123d860c1d2a40605f19a3ecd56d1e96be9799e947ddf7906897a28fa2e81f0b8c63c652b6bfe5550 languageName: node linkType: hard @@ -33575,15 +32250,15 @@ __metadata: languageName: node linkType: hard -"import-in-the-middle@npm:^1.8.1": - version: 1.11.0 - resolution: "import-in-the-middle@npm:1.11.0" +"import-in-the-middle@npm:^3.0.0": + version: 3.0.0 + resolution: "import-in-the-middle@npm:3.0.0" dependencies: - acorn: "npm:^8.8.2" + acorn: "npm:^8.15.0" acorn-import-attributes: "npm:^1.9.5" - cjs-module-lexer: "npm:^1.2.2" - module-details-from-path: "npm:^1.0.3" - checksum: 10/e6f79c9de3f1c1907856fb48b99cd2273c5f9d78eb72124ddd142382e41b6bdf1f64c028ced9e5dbfd015f282e6e3b48bd1f53dd0452e2f0a26436ee42b005d8 + cjs-module-lexer: "npm:^2.2.0" + module-details-from-path: "npm:^1.0.4" + checksum: 10/0bf1f22d00a080e7f651db8c5d136aa4aca6829397769f22fb544a67d9117b1c78590f180a690eb19eb0cfb4a558beac66b6508d346cccc91e1e5f75c934e9de languageName: node linkType: hard @@ -33601,15 +32276,15 @@ __metadata: languageName: node linkType: hard -"import-local@npm:^3.0.2": - version: 3.0.2 - resolution: "import-local@npm:3.0.2" +"import-local@npm:^3.2.0": + version: 3.2.0 + resolution: "import-local@npm:3.2.0" dependencies: pkg-dir: "npm:^4.2.0" resolve-cwd: "npm:^3.0.0" bin: import-local-fixture: fixtures/cli.js - checksum: 10/c74d9f9484c878cda1de3434613c7ff72d5dadcf20e5482542232d7c2575b713ff88701d6675fcf09a3684cb23fb407c8b333b9cbc59438712723d058d8e976c + checksum: 10/0b0b0b412b2521739fbb85eeed834a3c34de9bc67e670b3d0b86248fc460d990a7b116ad056c084b87a693ef73d1f17268d6a5be626bb43c998a8b1c8a230004 languageName: node linkType: hard @@ -33751,7 +32426,7 @@ __metadata: languageName: node linkType: hard -"inquirer@npm:8.2.7, inquirer@npm:^8.0.0, inquirer@npm:^8.2.0": +"inquirer@npm:^8.0.0, inquirer@npm:^8.2.0": version: 8.2.7 resolution: "inquirer@npm:8.2.7" dependencies: @@ -33799,18 +32474,6 @@ __metadata: languageName: node linkType: hard -"intl-messageformat@npm:^10.1.0": - version: 10.7.11 - resolution: "intl-messageformat@npm:10.7.11" - dependencies: - "@formatjs/ecma402-abstract": "npm:2.3.2" - "@formatjs/fast-memoize": "npm:2.2.6" - "@formatjs/icu-messageformat-parser": "npm:2.9.8" - tslib: "npm:2" - checksum: 10/0f1ad856725984bc141fa41b43104393634dec6344375b3566786eabc141b26c4bed1e9df6464f056a7b02d59d998208b65419a878d72b2486b16b35174ffc20 - languageName: node - linkType: hard - "into-stream@npm:^6.0.0": version: 6.0.0 resolution: "into-stream@npm:6.0.0" @@ -33821,7 +32484,7 @@ __metadata: languageName: node linkType: hard -"invariant@npm:^2.2.2, invariant@npm:^2.2.4": +"invariant@npm:^2.2.2": version: 2.2.4 resolution: "invariant@npm:2.2.4" dependencies: @@ -33847,6 +32510,13 @@ __metadata: languageName: node linkType: hard +"ip-address@npm:10.1.0": + version: 10.1.0 + resolution: "ip-address@npm:10.1.0" + checksum: 10/a6979629d1ad9c1fb424bc25182203fad739b40225aebc55ec6243bbff5035faf7b9ed6efab3a097de6e713acbbfde944baacfa73e11852bb43989c45a68d79e + languageName: node + linkType: hard + "ip-address@npm:^9.0.5": version: 9.0.5 resolution: "ip-address@npm:9.0.5" @@ -33871,10 +32541,10 @@ __metadata: languageName: node linkType: hard -"ipaddr.js@npm:^2.1.0": - version: 2.2.0 - resolution: "ipaddr.js@npm:2.2.0" - checksum: 10/9e1cdd9110b3bca5d910ab70d7fb1933e9c485d9b92cb14ef39f30c412ba3fe02a553921bf696efc7149cc653453c48ccf173adb996ec27d925f1f340f872986 +"ipaddr.js@npm:^2.1.0, ipaddr.js@npm:^2.3.0": + version: 2.3.0 + resolution: "ipaddr.js@npm:2.3.0" + checksum: 10/be3d01bc2e20fc2dc5349b489ea40883954b816ce3e57aa48ad943d4e7c4ace501f28a7a15bde4b96b6b97d0fbb28d599ff2f87399f3cda7bd728889402eed3b languageName: node linkType: hard @@ -33956,13 +32626,6 @@ __metadata: languageName: node linkType: hard -"is-arrayish@npm:^0.3.1": - version: 0.3.2 - resolution: "is-arrayish@npm:0.3.2" - checksum: 10/81a78d518ebd8b834523e25d102684ee0f7e98637136d3bdc93fd09636350fa06f1d8ca997ea28143d4d13cb1b69c0824f082db0ac13e1ab3311c10ffea60ade - languageName: node - linkType: hard - "is-async-function@npm:^2.0.0": version: 2.0.0 resolution: "is-async-function@npm:2.0.0" @@ -34007,7 +32670,7 @@ __metadata: languageName: node linkType: hard -"is-callable@npm:^1.1.5, is-callable@npm:^1.2.7": +"is-callable@npm:^1.2.7": version: 1.2.7 resolution: "is-callable@npm:1.2.7" checksum: 10/48a9297fb92c99e9df48706241a189da362bff3003354aea4048bd5f7b2eb0d823cd16d0a383cece3d76166ba16d85d9659165ac6fcce1ac12e6c649d66dbdb9 @@ -34034,7 +32697,7 @@ __metadata: languageName: node linkType: hard -"is-core-module@npm:^2.13.0, is-core-module@npm:^2.15.1, is-core-module@npm:^2.16.0": +"is-core-module@npm:^2.13.0, is-core-module@npm:^2.16.1": version: 2.16.1 resolution: "is-core-module@npm:2.16.1" dependencies: @@ -34136,23 +32799,16 @@ __metadata: languageName: node linkType: hard -"is-fullwidth-code-point@npm:^4.0.0": - version: 4.0.0 - resolution: "is-fullwidth-code-point@npm:4.0.0" - checksum: 10/8ae89bf5057bdf4f57b346fb6c55e9c3dd2549983d54191d722d5c739397a903012cc41a04ee3403fd872e811243ef91a7c5196da7b5841dc6b6aae31a264a8d - languageName: node - linkType: hard - -"is-fullwidth-code-point@npm:^5.0.0": - version: 5.0.0 - resolution: "is-fullwidth-code-point@npm:5.0.0" +"is-fullwidth-code-point@npm:^5.0.0, is-fullwidth-code-point@npm:^5.1.0": + version: 5.1.0 + resolution: "is-fullwidth-code-point@npm:5.1.0" dependencies: - get-east-asian-width: "npm:^1.0.0" - checksum: 10/8dfb2d2831b9e87983c136f5c335cd9d14c1402973e357a8ff057904612ed84b8cba196319fabedf9aefe4639e14fe3afe9d9966d1d006ebeb40fe1fed4babe5 + get-east-asian-width: "npm:^1.3.1" + checksum: 10/4700d8a82cb71bd2a2955587b2823c36dc4660eadd4047bfbd070821ddbce8504fc5f9b28725567ecddf405b1e06c6692c9b719f65df6af9ec5262bc11393a6a languageName: node linkType: hard -"is-generator-fn@npm:^2.0.0": +"is-generator-fn@npm:^2.1.0": version: 2.1.0 resolution: "is-generator-fn@npm:2.1.0" checksum: 10/a6ad5492cf9d1746f73b6744e0c43c0020510b59d56ddcb78a91cbc173f09b5e6beff53d75c9c5a29feb618bfef2bf458e025ecf3a57ad2268e2fb2569f56215 @@ -34257,6 +32913,13 @@ __metadata: languageName: node linkType: hard +"is-negative-zero@npm:^2.0.3": + version: 2.0.3 + resolution: "is-negative-zero@npm:2.0.3" + checksum: 10/8fe5cffd8d4fb2ec7b49d657e1691889778d037494c6f40f4d1a524cadd658b4b53ad7b6b73a59bcb4b143ae9a3d15829af864b2c0f9d65ac1e678c4c80f17e5 + languageName: node + linkType: hard + "is-network-error@npm:^1.0.0": version: 1.0.1 resolution: "is-network-error@npm:1.0.1" @@ -34309,13 +32972,6 @@ __metadata: languageName: node linkType: hard -"is-path-cwd@npm:^3.0.0": - version: 3.0.0 - resolution: "is-path-cwd@npm:3.0.0" - checksum: 10/bc34d13b6a03dfca4a3ab6a8a5ba78ae4b24f4f1db4b2b031d2760c60d0913bd16a4b980dcb4e590adfc906649d5f5132684079a3972bd219da49deebb9adea8 - languageName: node - linkType: hard - "is-path-inside@npm:^3.0.2, is-path-inside@npm:^3.0.3": version: 3.0.3 resolution: "is-path-inside@npm:3.0.3" @@ -34323,13 +32979,6 @@ __metadata: languageName: node linkType: hard -"is-path-inside@npm:^4.0.0": - version: 4.0.0 - resolution: "is-path-inside@npm:4.0.0" - checksum: 10/8810fa11c58e6360b82c3e0d6cd7d9c7d0392d3ac9eb10f980b81f9839f40ac6d1d6d6f05d069db0d227759801228f0b072e1b6c343e4469b065ab5fe0b68fe5 - languageName: node - linkType: hard - "is-plain-obj@npm:^3.0.0": version: 3.0.0 resolution: "is-plain-obj@npm:3.0.0" @@ -34499,13 +33148,6 @@ __metadata: languageName: node linkType: hard -"is-stream@npm:^3.0.0": - version: 3.0.0 - resolution: "is-stream@npm:3.0.0" - checksum: 10/172093fe99119ffd07611ab6d1bcccfe8bc4aa80d864b15f43e63e54b7abc71e779acd69afdb854c4e2a67fdc16ae710e370eda40088d1cfc956a50ed82d8f16 - languageName: node - linkType: hard - "is-stream@npm:^4.0.1": version: 4.0.1 resolution: "is-stream@npm:4.0.1" @@ -34594,12 +33236,12 @@ __metadata: languageName: node linkType: hard -"is-weakref@npm:^1.0.2, is-weakref@npm:^1.1.0": - version: 1.1.0 - resolution: "is-weakref@npm:1.1.0" +"is-weakref@npm:^1.0.2, is-weakref@npm:^1.1.1": + version: 1.1.1 + resolution: "is-weakref@npm:1.1.1" dependencies: - call-bound: "npm:^1.0.2" - checksum: 10/89e627cc1763ea110574bb408fcf060ede47e70437d9278858bc939e3b3f7e4b7c558610b733da5f2ad6084d9f12b9c714b011ccf3fa771ec87e221c22bed910 + call-bound: "npm:^1.0.3" + checksum: 10/543506fd8259038b371bb083aac25b16cb4fd8b12fc58053aa3d45ac28dfd001cd5c6dffbba7aeea4213c74732d46b6cb2cfb5b412eed11f2db524f3f97d09a0 languageName: node linkType: hard @@ -34645,13 +33287,6 @@ __metadata: languageName: node linkType: hard -"is@npm:^3.2.1, is@npm:^3.3.0": - version: 3.3.0 - resolution: "is@npm:3.3.0" - checksum: 10/f77dc5a05a1e8fd1f1de282add9bb01c44dae27af72b883bf0ce342151dec48f125b0b8923efa78c1e93c4fb866095629b2c7de3e5e3853aea4ed17c82c5cd8d - languageName: node - linkType: hard - "isarray@npm:^2.0.5": version: 2.0.5 resolution: "isarray@npm:2.0.5" @@ -34694,6 +33329,13 @@ __metadata: languageName: node linkType: hard +"isexe@npm:^4.0.0": + version: 4.0.0 + resolution: "isexe@npm:4.0.0" + checksum: 10/2ead327ef596042ef9c9ec5f236b316acfaedb87f4bb61b3c3d574fb2e9c8a04b67305e04733bde52c24d9622fdebd3270aadb632adfbf9cadef88fe30f479e5 + languageName: node + linkType: hard + "isobject@npm:^3.0.1": version: 3.0.1 resolution: "isobject@npm:3.0.1" @@ -34701,13 +33343,13 @@ __metadata: languageName: node linkType: hard -"isolated-vm@npm:^5.0.1": - version: 5.0.4 - resolution: "isolated-vm@npm:5.0.4" +"isolated-vm@npm:^6.0.1": + version: 6.1.2 + resolution: "isolated-vm@npm:6.1.2" dependencies: node-gyp: "npm:latest" - prebuild-install: "npm:^7.1.2" - checksum: 10/f48e69ecf907645711d0a372cb6adb28cf72499e34b6e008ed597994bfd90d41dd11dc478a41fc21a25aaef424ab5a95a372286e4daf7f61e231d028c0fd64ec + node-gyp-build: "npm:^4.8.4" + checksum: 10/705c93375a6342d54d2381efebba45d965147d5a6e534dc5c1e622b625ad02ee8878cacd03a8fd09f9929a3fc2d039b26a900d976632b14a51a94005371ec1d9 languageName: node linkType: hard @@ -34723,8 +33365,8 @@ __metadata: linkType: hard "isomorphic-git@npm:^1.23.0": - version: 1.27.2 - resolution: "isomorphic-git@npm:1.27.2" + version: 1.37.4 + resolution: "isomorphic-git@npm:1.37.4" dependencies: async-lock: "npm:^1.4.1" clean-git-ref: "npm:^2.0.1" @@ -34733,14 +33375,13 @@ __metadata: ignore: "npm:^5.1.4" minimisted: "npm:^2.0.0" pako: "npm:^1.0.10" - path-browserify: "npm:^1.0.1" pify: "npm:^4.0.1" - readable-stream: "npm:^3.4.0" - sha.js: "npm:^2.4.9" + readable-stream: "npm:^4.0.0" + sha.js: "npm:^2.4.12" simple-get: "npm:^4.0.1" bin: isogit: cli.cjs - checksum: 10/da4fa5ae6180e4a528b84f24ad4144c1a7587d5e79ed3f323b936809dc23b133615b96d7ecbb363009a30a2c9518bdf5efd572f918ddad801edc81693ddbb175 + checksum: 10/1065476ceef9b48e3c95aafb9809424c36874938b8af00d221a953087e4cce7d9292cb8358c99c6ed56dbbb9b96bd19117cb5ca8bcc062c7ed49329063987200 languageName: node linkType: hard @@ -34778,6 +33419,15 @@ __metadata: languageName: node linkType: hard +"isows@npm:^1.0.7": + version: 1.0.7 + resolution: "isows@npm:1.0.7" + peerDependencies: + ws: "*" + checksum: 10/044b949b369872882af07b60b613b5801ae01b01a23b5b72b78af80c8103bbeed38352c3e8ceff13a7834bc91fd2eb41cf91ec01d59a041d8705680e6b0ec546 + languageName: node + linkType: hard + "istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": version: 3.2.0 resolution: "istanbul-lib-coverage@npm:3.2.0" @@ -34785,29 +33435,16 @@ __metadata: languageName: node linkType: hard -"istanbul-lib-instrument@npm:^5.0.4": - version: 5.1.0 - resolution: "istanbul-lib-instrument@npm:5.1.0" +"istanbul-lib-instrument@npm:^6.0.0, istanbul-lib-instrument@npm:^6.0.2": + version: 6.0.3 + resolution: "istanbul-lib-instrument@npm:6.0.3" dependencies: - "@babel/core": "npm:^7.12.3" - "@babel/parser": "npm:^7.14.7" - "@istanbuljs/schema": "npm:^0.1.2" - istanbul-lib-coverage: "npm:^3.2.0" - semver: "npm:^6.3.0" - checksum: 10/7447ba3f8049f331d5b4a1c450183e88c2fdad044149ad0d9830f71bc8da90d841c393b830bc33237ae75122c3b0e03ca845701873d6c51690bc25caa1f13a94 - languageName: node - linkType: hard - -"istanbul-lib-instrument@npm:^6.0.0": - version: 6.0.1 - resolution: "istanbul-lib-instrument@npm:6.0.1" - dependencies: - "@babel/core": "npm:^7.12.3" - "@babel/parser": "npm:^7.14.7" - "@istanbuljs/schema": "npm:^0.1.2" + "@babel/core": "npm:^7.23.9" + "@babel/parser": "npm:^7.23.9" + "@istanbuljs/schema": "npm:^0.1.3" istanbul-lib-coverage: "npm:^3.2.0" semver: "npm:^7.5.4" - checksum: 10/95fd8c66e586840989cb3c7819c6da66c4742a6fedbf16b51a5c7f1898941ad07b79ddff020f479d3a1d76743ecdbf255d93c35221875687477d4b118026e7e7 + checksum: 10/aa5271c0008dfa71b6ecc9ba1e801bf77b49dc05524e8c30d58aaf5b9505e0cd12f25f93165464d4266a518c5c75284ecb598fbd89fec081ae77d2c9d3327695 languageName: node linkType: hard @@ -34822,14 +33459,14 @@ __metadata: languageName: node linkType: hard -"istanbul-lib-source-maps@npm:^4.0.0": - version: 4.0.0 - resolution: "istanbul-lib-source-maps@npm:4.0.0" +"istanbul-lib-source-maps@npm:^5.0.0": + version: 5.0.6 + resolution: "istanbul-lib-source-maps@npm:5.0.6" dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.23" debug: "npm:^4.1.1" istanbul-lib-coverage: "npm:^3.0.0" - source-map: "npm:^0.6.1" - checksum: 10/765252abc6b5c9d29905fc97ce04b92da87d198f2c0161e62fe0aac8bb74fb7bd472a5e1d90fe3e78723d8cad43913f08d8eefa0339536fcc33b3a1922cf5fc3 + checksum: 10/569dd0a392ee3464b1fe1accbaef5cc26de3479eacb5b91d8c67ebb7b425d39fd02247d85649c3a0e9c29b600809fa60b5af5a281a75a89c01f385b1e24823a2 languageName: node linkType: hard @@ -34901,12 +33538,12 @@ __metadata: languageName: node linkType: hard -"jackspeak@npm:^4.1.1": - version: 4.1.1 - resolution: "jackspeak@npm:4.1.1" +"jackspeak@npm:^4.2.3": + version: 4.2.3 + resolution: "jackspeak@npm:4.2.3" dependencies: - "@isaacs/cliui": "npm:^8.0.2" - checksum: 10/ffceb270ec286841f48413bfb4a50b188662dfd599378ce142b6540f3f0a66821dc9dcb1e9ebc55c6c3b24dc2226c96e5819ba9bd7a241bd29031b61911718c7 + "@isaacs/cliui": "npm:^9.0.0" + checksum: 10/b88e3fe5fa04d34f0f939a15b7cef4a8589999b7a366ef89a3e0f2c45d2a7666066b67cbf46d57c3a4796a76d27b9d869b23d96a803dd834200d222c2a70de7e languageName: node linkType: hard @@ -34924,106 +33561,110 @@ __metadata: languageName: node linkType: hard -"jest-changed-files@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-changed-files@npm:29.7.0" +"jest-changed-files@npm:30.2.0": + version: 30.2.0 + resolution: "jest-changed-files@npm:30.2.0" dependencies: - execa: "npm:^5.0.0" - jest-util: "npm:^29.7.0" + execa: "npm:^5.1.1" + jest-util: "npm:30.2.0" p-limit: "npm:^3.1.0" - checksum: 10/3d93742e56b1a73a145d55b66e96711fbf87ef89b96c2fab7cfdfba8ec06612591a982111ca2b712bb853dbc16831ec8b43585a2a96b83862d6767de59cbf83d + checksum: 10/ff2275ed5839b88c12ffa66fdc5c17ba02d3e276be6b558bed92872c282d050c3fdd1a275a81187cbe35c16d6d40337b85838772836463c7a2fbd1cba9785ca0 languageName: node linkType: hard -"jest-circus@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-circus@npm:29.7.0" +"jest-circus@npm:30.2.0": + version: 30.2.0 + resolution: "jest-circus@npm:30.2.0" dependencies: - "@jest/environment": "npm:^29.7.0" - "@jest/expect": "npm:^29.7.0" - "@jest/test-result": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" + "@jest/environment": "npm:30.2.0" + "@jest/expect": "npm:30.2.0" + "@jest/test-result": "npm:30.2.0" + "@jest/types": "npm:30.2.0" "@types/node": "npm:*" - chalk: "npm:^4.0.0" + chalk: "npm:^4.1.2" co: "npm:^4.6.0" - dedent: "npm:^1.0.0" - is-generator-fn: "npm:^2.0.0" - jest-each: "npm:^29.7.0" - jest-matcher-utils: "npm:^29.7.0" - jest-message-util: "npm:^29.7.0" - jest-runtime: "npm:^29.7.0" - jest-snapshot: "npm:^29.7.0" - jest-util: "npm:^29.7.0" + dedent: "npm:^1.6.0" + is-generator-fn: "npm:^2.1.0" + jest-each: "npm:30.2.0" + jest-matcher-utils: "npm:30.2.0" + jest-message-util: "npm:30.2.0" + jest-runtime: "npm:30.2.0" + jest-snapshot: "npm:30.2.0" + jest-util: "npm:30.2.0" p-limit: "npm:^3.1.0" - pretty-format: "npm:^29.7.0" - pure-rand: "npm:^6.0.0" + pretty-format: "npm:30.2.0" + pure-rand: "npm:^7.0.0" slash: "npm:^3.0.0" - stack-utils: "npm:^2.0.3" - checksum: 10/716a8e3f40572fd0213bcfc1da90274bf30d856e5133af58089a6ce45089b63f4d679bd44e6be9d320e8390483ebc3ae9921981993986d21639d9019b523123d + stack-utils: "npm:^2.0.6" + checksum: 10/68bfc65d92385db1017643988215e4ff5af0b10bcab86fb749a063be6bb7d5eb556dc53dd21bedf833a19aa6ae1a781a8d27b2bea25562de02d294b3017435a9 languageName: node linkType: hard -"jest-cli@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-cli@npm:29.7.0" +"jest-cli@npm:30.2.0": + version: 30.2.0 + resolution: "jest-cli@npm:30.2.0" dependencies: - "@jest/core": "npm:^29.7.0" - "@jest/test-result": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - chalk: "npm:^4.0.0" - create-jest: "npm:^29.7.0" - exit: "npm:^0.1.2" - import-local: "npm:^3.0.2" - jest-config: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - jest-validate: "npm:^29.7.0" - yargs: "npm:^17.3.1" + "@jest/core": "npm:30.2.0" + "@jest/test-result": "npm:30.2.0" + "@jest/types": "npm:30.2.0" + chalk: "npm:^4.1.2" + exit-x: "npm:^0.2.2" + import-local: "npm:^3.2.0" + jest-config: "npm:30.2.0" + jest-util: "npm:30.2.0" + jest-validate: "npm:30.2.0" + yargs: "npm:^17.7.2" peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 peerDependenciesMeta: node-notifier: optional: true bin: - jest: bin/jest.js - checksum: 10/6cc62b34d002c034203065a31e5e9a19e7c76d9e8ef447a6f70f759c0714cb212c6245f75e270ba458620f9c7b26063cd8cf6cd1f7e3afd659a7cc08add17307 + jest: ./bin/jest.js + checksum: 10/1cc8304f0e2608801c84cdecce9565a6178f668a6475aed3767a1d82cc539915f98e7404d7c387510313684011dc3095c15397d6725f73aac80fbd96c4155faa languageName: node linkType: hard -"jest-config@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-config@npm:29.7.0" +"jest-config@npm:30.2.0": + version: 30.2.0 + resolution: "jest-config@npm:30.2.0" dependencies: - "@babel/core": "npm:^7.11.6" - "@jest/test-sequencer": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - babel-jest: "npm:^29.7.0" - chalk: "npm:^4.0.0" - ci-info: "npm:^3.2.0" - deepmerge: "npm:^4.2.2" - glob: "npm:^7.1.3" - graceful-fs: "npm:^4.2.9" - jest-circus: "npm:^29.7.0" - jest-environment-node: "npm:^29.7.0" - jest-get-type: "npm:^29.6.3" - jest-regex-util: "npm:^29.6.3" - jest-resolve: "npm:^29.7.0" - jest-runner: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - jest-validate: "npm:^29.7.0" - micromatch: "npm:^4.0.4" + "@babel/core": "npm:^7.27.4" + "@jest/get-type": "npm:30.1.0" + "@jest/pattern": "npm:30.0.1" + "@jest/test-sequencer": "npm:30.2.0" + "@jest/types": "npm:30.2.0" + babel-jest: "npm:30.2.0" + chalk: "npm:^4.1.2" + ci-info: "npm:^4.2.0" + deepmerge: "npm:^4.3.1" + glob: "npm:^10.3.10" + graceful-fs: "npm:^4.2.11" + jest-circus: "npm:30.2.0" + jest-docblock: "npm:30.2.0" + jest-environment-node: "npm:30.2.0" + jest-regex-util: "npm:30.0.1" + jest-resolve: "npm:30.2.0" + jest-runner: "npm:30.2.0" + jest-util: "npm:30.2.0" + jest-validate: "npm:30.2.0" + micromatch: "npm:^4.0.8" parse-json: "npm:^5.2.0" - pretty-format: "npm:^29.7.0" + pretty-format: "npm:30.2.0" slash: "npm:^3.0.0" strip-json-comments: "npm:^3.1.1" peerDependencies: "@types/node": "*" + esbuild-register: ">=3.4.0" ts-node: ">=9.0.0" peerDependenciesMeta: "@types/node": optional: true + esbuild-register: + optional: true ts-node: optional: true - checksum: 10/6bdf570e9592e7d7dd5124fc0e21f5fe92bd15033513632431b211797e3ab57eaa312f83cc6481b3094b72324e369e876f163579d60016677c117ec4853cf02b + checksum: 10/296786b0a3d62de77e2f691f208d54ab541c1a73f87747d922eda643c6f25b89125ef3150170c07a6c8a316a30c15428e46237d499f688b0777f38de8a61ad16 languageName: node linkType: hard @@ -35036,7 +33677,19 @@ __metadata: languageName: node linkType: hard -"jest-diff@npm:^29.2.0, jest-diff@npm:^29.7.0": +"jest-diff@npm:30.2.0": + version: 30.2.0 + resolution: "jest-diff@npm:30.2.0" + dependencies: + "@jest/diff-sequences": "npm:30.0.1" + "@jest/get-type": "npm:30.1.0" + chalk: "npm:^4.1.2" + pretty-format: "npm:30.2.0" + checksum: 10/1fb9e4fb7dff81814b4f69eaa7db28e184d62306a3a8ea2447d02ca53d2cfa771e83ede513f67ec5239dffacfaac32ff2b49866d211e4c7516f51c1fc06ede42 + languageName: node + linkType: hard + +"jest-diff@npm:^29.2.0": version: 29.7.0 resolution: "jest-diff@npm:29.7.0" dependencies: @@ -35048,60 +33701,40 @@ __metadata: languageName: node linkType: hard -"jest-docblock@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-docblock@npm:29.7.0" +"jest-docblock@npm:30.2.0": + version: 30.2.0 + resolution: "jest-docblock@npm:30.2.0" dependencies: - detect-newline: "npm:^3.0.0" - checksum: 10/8d48818055bc96c9e4ec2e217a5a375623c0d0bfae8d22c26e011074940c202aa2534a3362294c81d981046885c05d304376afba9f2874143025981148f3e96d + detect-newline: "npm:^3.1.0" + checksum: 10/e01a7d1193947ed0f9713c26bfc7852e51cb758cafec807e5665a0a8d582473a43778bee099f8aa5c70b2941963e5341f4b10bd86b036a4fa3bcec0f4c04e099 languageName: node linkType: hard -"jest-each@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-each@npm:29.7.0" +"jest-each@npm:30.2.0": + version: 30.2.0 + resolution: "jest-each@npm:30.2.0" dependencies: - "@jest/types": "npm:^29.6.3" - chalk: "npm:^4.0.0" - jest-get-type: "npm:^29.6.3" - jest-util: "npm:^29.7.0" - pretty-format: "npm:^29.7.0" - checksum: 10/bd1a077654bdaa013b590deb5f7e7ade68f2e3289180a8c8f53bc8a49f3b40740c0ec2d3a3c1aee906f682775be2bebbac37491d80b634d15276b0aa0f2e3fda + "@jest/get-type": "npm:30.1.0" + "@jest/types": "npm:30.2.0" + chalk: "npm:^4.1.2" + jest-util: "npm:30.2.0" + pretty-format: "npm:30.2.0" + checksum: 10/f95e7dc1cef4b6a77899325702a214834ae25d01276cc31279654dc7e04f63c1925a37848dd16a0d16508c0fd3d182145f43c10af93952b7a689df3aeac198e9 languageName: node linkType: hard -"jest-environment-jsdom@npm:^29.0.2": - version: 29.7.0 - resolution: "jest-environment-jsdom@npm:29.7.0" +"jest-environment-node@npm:30.2.0": + version: 30.2.0 + resolution: "jest-environment-node@npm:30.2.0" dependencies: - "@jest/environment": "npm:^29.7.0" - "@jest/fake-timers": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - "@types/jsdom": "npm:^20.0.0" + "@jest/environment": "npm:30.2.0" + "@jest/fake-timers": "npm:30.2.0" + "@jest/types": "npm:30.2.0" "@types/node": "npm:*" - jest-mock: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - jsdom: "npm:^20.0.0" - peerDependencies: - canvas: ^2.5.0 - peerDependenciesMeta: - canvas: - optional: true - checksum: 10/23bbfc9bca914baef4b654f7983175a4d49b0f515a5094ebcb8f819f28ec186f53c0ba06af1855eac04bab1457f4ea79dae05f70052cf899863e8096daa6e0f5 - languageName: node - linkType: hard - -"jest-environment-node@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-environment-node@npm:29.7.0" - dependencies: - "@jest/environment": "npm:^29.7.0" - "@jest/fake-timers": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - "@types/node": "npm:*" - jest-mock: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - checksum: 10/9cf7045adf2307cc93aed2f8488942e39388bff47ec1df149a997c6f714bfc66b2056768973770d3f8b1bf47396c19aa564877eb10ec978b952c6018ed1bd637 + jest-mock: "npm:30.2.0" + jest-util: "npm:30.2.0" + jest-validate: "npm:30.2.0" + checksum: 10/7918bfea7367bd3e12dbbc4ea5afb193b5c47e480a6d1382512f051e2f028458fc9f5ef2f6260737ad41a0b1894661790ff3aaf3cbb4148a33ce2ce7aec64847 languageName: node linkType: hard @@ -35112,232 +33745,223 @@ __metadata: languageName: node linkType: hard -"jest-haste-map@npm:29.7.0": - version: 29.7.0 - resolution: "jest-haste-map@npm:29.7.0" +"jest-haste-map@npm:30.2.0": + version: 30.2.0 + resolution: "jest-haste-map@npm:30.2.0" dependencies: - "@jest/types": "npm:^29.6.3" - "@types/graceful-fs": "npm:^4.1.3" + "@jest/types": "npm:30.2.0" "@types/node": "npm:*" - anymatch: "npm:^3.0.3" - fb-watchman: "npm:^2.0.0" - fsevents: "npm:^2.3.2" - graceful-fs: "npm:^4.2.9" - jest-regex-util: "npm:^29.6.3" - jest-util: "npm:^29.7.0" - jest-worker: "npm:^29.7.0" - micromatch: "npm:^4.0.4" + anymatch: "npm:^3.1.3" + fb-watchman: "npm:^2.0.2" + fsevents: "npm:^2.3.3" + graceful-fs: "npm:^4.2.11" + jest-regex-util: "npm:30.0.1" + jest-util: "npm:30.2.0" + jest-worker: "npm:30.2.0" + micromatch: "npm:^4.0.8" walker: "npm:^1.0.8" dependenciesMeta: fsevents: optional: true - checksum: 10/8531b42003581cb18a69a2774e68c456fb5a5c3280b1b9b77475af9e346b6a457250f9d756bfeeae2fe6cbc9ef28434c205edab9390ee970a919baddfa08bb85 + checksum: 10/a88be6b0b672144aa30fe2d72e630d639c8d8729ee2cef84d0f830eac2005ac021cd8354f8ed8ecd74223f6a8b281efb62f466f5c9e01ed17650e38761051f4c languageName: node linkType: hard -"jest-haste-map@patch:jest-haste-map@npm%3A29.7.0#./.yarn/patches/jest-haste-map-npm-29.7.0-e3be419eff.patch::locator=root%40workspace%3A.": - version: 29.7.0 - resolution: "jest-haste-map@patch:jest-haste-map@npm%3A29.7.0#./.yarn/patches/jest-haste-map-npm-29.7.0-e3be419eff.patch::version=29.7.0&hash=feed88&locator=root%40workspace%3A." +"jest-leak-detector@npm:30.2.0": + version: 30.2.0 + resolution: "jest-leak-detector@npm:30.2.0" dependencies: - "@jest/types": "npm:^29.6.3" - "@types/graceful-fs": "npm:^4.1.3" - "@types/node": "npm:*" - anymatch: "npm:^3.0.3" - fb-watchman: "npm:^2.0.0" - fsevents: "npm:^2.3.2" - graceful-fs: "npm:^4.2.9" - jest-regex-util: "npm:^29.6.3" - jest-util: "npm:^29.7.0" - jest-worker: "npm:^29.7.0" - micromatch: "npm:^4.0.4" - walker: "npm:^1.0.8" - dependenciesMeta: - fsevents: - optional: true - checksum: 10/e93fab6316f0cdb5e930491efe87e9957a83200a58c37acedbea84472fbf67472675e3235d249b04726aaf09a23a5eadc4a7890e85969e2a8c7bf6b402c1ff5e + "@jest/get-type": "npm:30.1.0" + pretty-format: "npm:30.2.0" + checksum: 10/c430d6ed7910b2174738fbdca4ea64cbfe805216414c0d143c1090148f1389fec99d0733c0a8ed0a86709c89b4a4085b4749ac3a2cbc7deaf3ca87457afd24fc languageName: node linkType: hard -"jest-leak-detector@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-leak-detector@npm:29.7.0" +"jest-matcher-utils@npm:30.2.0": + version: 30.2.0 + resolution: "jest-matcher-utils@npm:30.2.0" dependencies: - jest-get-type: "npm:^29.6.3" - pretty-format: "npm:^29.7.0" - checksum: 10/e3950e3ddd71e1d0c22924c51a300a1c2db6cf69ec1e51f95ccf424bcc070f78664813bef7aed4b16b96dfbdeea53fe358f8aeaaea84346ae15c3735758f1605 + "@jest/get-type": "npm:30.1.0" + chalk: "npm:^4.1.2" + jest-diff: "npm:30.2.0" + pretty-format: "npm:30.2.0" + checksum: 10/f3f1ecf68ca63c9d1d80a175637a8fc655edfd1ee83220f6e3f6bd464ecbe2f93148fdd440a5a5e5a2b0b2cc8ee84ddc3dcef58a6dbc66821c792f48d260c6d4 languageName: node linkType: hard -"jest-matcher-utils@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-matcher-utils@npm:29.7.0" +"jest-message-util@npm:30.2.0": + version: 30.2.0 + resolution: "jest-message-util@npm:30.2.0" dependencies: - chalk: "npm:^4.0.0" - jest-diff: "npm:^29.7.0" - jest-get-type: "npm:^29.6.3" - pretty-format: "npm:^29.7.0" - checksum: 10/981904a494299cf1e3baed352f8a3bd8b50a8c13a662c509b6a53c31461f94ea3bfeffa9d5efcfeb248e384e318c87de7e3baa6af0f79674e987482aa189af40 - languageName: node - linkType: hard - -"jest-message-util@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-message-util@npm:29.7.0" - dependencies: - "@babel/code-frame": "npm:^7.12.13" - "@jest/types": "npm:^29.6.3" - "@types/stack-utils": "npm:^2.0.0" - chalk: "npm:^4.0.0" - graceful-fs: "npm:^4.2.9" - micromatch: "npm:^4.0.4" - pretty-format: "npm:^29.7.0" + "@babel/code-frame": "npm:^7.27.1" + "@jest/types": "npm:30.2.0" + "@types/stack-utils": "npm:^2.0.3" + chalk: "npm:^4.1.2" + graceful-fs: "npm:^4.2.11" + micromatch: "npm:^4.0.8" + pretty-format: "npm:30.2.0" slash: "npm:^3.0.0" - stack-utils: "npm:^2.0.3" - checksum: 10/31d53c6ed22095d86bab9d14c0fa70c4a92c749ea6ceece82cf30c22c9c0e26407acdfbdb0231435dc85a98d6d65ca0d9cbcd25cd1abb377fe945e843fb770b9 + stack-utils: "npm:^2.0.6" + checksum: 10/e29ec76e8c8e4da5f5b25198be247535626ccf3a940e93fdd51fc6a6bcf70feaa2921baae3806182a090431d90b08c939eb13fb64249b171d2e9ae3a452a8fd2 languageName: node linkType: hard -"jest-mock@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-mock@npm:29.7.0" +"jest-mock@npm:30.2.0": + version: 30.2.0 + resolution: "jest-mock@npm:30.2.0" dependencies: - "@jest/types": "npm:^29.6.3" + "@jest/types": "npm:30.2.0" "@types/node": "npm:*" - jest-util: "npm:^29.7.0" - checksum: 10/ae51d1b4f898724be5e0e52b2268a68fcd876d9b20633c864a6dd6b1994cbc48d62402b0f40f3a1b669b30ebd648821f086c26c08ffde192ced951ff4670d51c + jest-util: "npm:30.2.0" + checksum: 10/cde9b56805f90bf811a9231873ee88a0fb83bf4bf50972ae76960725da65220fcb119688f2e90e1ef33fbfd662194858d7f43809d881f1c41bb55d94e62adeab languageName: node linkType: hard -"jest-pnp-resolver@npm:^1.2.2": - version: 1.2.2 - resolution: "jest-pnp-resolver@npm:1.2.2" +"jest-pnp-resolver@npm:^1.2.3": + version: 1.2.3 + resolution: "jest-pnp-resolver@npm:1.2.3" peerDependencies: jest-resolve: "*" peerDependenciesMeta: jest-resolve: optional: true - checksum: 10/bd85dcc0e76e0eb0c3d56382ec140f08d25ff4068cda9d0e360bb78fb176cb726d0beab82dc0e8694cafd09f55fee7622b8bcb240afa5fad301f4ed3eebb4f47 + checksum: 10/db1a8ab2cb97ca19c01b1cfa9a9c8c69a143fde833c14df1fab0766f411b1148ff0df878adea09007ac6a2085ec116ba9a996a6ad104b1e58c20adbf88eed9b2 languageName: node linkType: hard -"jest-regex-util@npm:^29.6.3": - version: 29.6.3 - resolution: "jest-regex-util@npm:29.6.3" - checksum: 10/0518beeb9bf1228261695e54f0feaad3606df26a19764bc19541e0fc6e2a3737191904607fb72f3f2ce85d9c16b28df79b7b1ec9443aa08c3ef0e9efda6f8f2a +"jest-regex-util@npm:30.0.1": + version: 30.0.1 + resolution: "jest-regex-util@npm:30.0.1" + checksum: 10/fa8dac80c3e94db20d5e1e51d1bdf101cf5ede8f4e0b8f395ba8b8ea81e71804ffd747452a6bb6413032865de98ac656ef8ae43eddd18d980b6442a2764ed562 languageName: node linkType: hard -"jest-resolve-dependencies@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-resolve-dependencies@npm:29.7.0" +"jest-resolve-dependencies@npm:30.2.0": + version: 30.2.0 + resolution: "jest-resolve-dependencies@npm:30.2.0" dependencies: - jest-regex-util: "npm:^29.6.3" - jest-snapshot: "npm:^29.7.0" - checksum: 10/1e206f94a660d81e977bcfb1baae6450cb4a81c92e06fad376cc5ea16b8e8c6ea78c383f39e95591a9eb7f925b6a1021086c38941aa7c1b8a6a813c2f6e93675 + jest-regex-util: "npm:30.0.1" + jest-snapshot: "npm:30.2.0" + checksum: 10/0ff1a574f8c07f2e54a4ac8ab17aea00dfe2982e99b03fbd44f4211a94b8e5a59fdc43a59f9d6c0578a10a7b56a0611ad5ab40e4893973ff3f40dd414433b194 languageName: node linkType: hard -"jest-resolve@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-resolve@npm:29.7.0" +"jest-resolve@npm:30.2.0": + version: 30.2.0 + resolution: "jest-resolve@npm:30.2.0" dependencies: - chalk: "npm:^4.0.0" - graceful-fs: "npm:^4.2.9" - jest-haste-map: "npm:^29.7.0" - jest-pnp-resolver: "npm:^1.2.2" - jest-util: "npm:^29.7.0" - jest-validate: "npm:^29.7.0" - resolve: "npm:^1.20.0" - resolve.exports: "npm:^2.0.0" + chalk: "npm:^4.1.2" + graceful-fs: "npm:^4.2.11" + jest-haste-map: "npm:30.2.0" + jest-pnp-resolver: "npm:^1.2.3" + jest-util: "npm:30.2.0" + jest-validate: "npm:30.2.0" slash: "npm:^3.0.0" - checksum: 10/faa466fd9bc69ea6c37a545a7c6e808e073c66f46ab7d3d8a6ef084f8708f201b85d5fe1799789578b8b47fa1de47b9ee47b414d1863bc117a49e032ba77b7c7 + unrs-resolver: "npm:^1.7.11" + checksum: 10/e1f03da6811a946f5d885ea739a973975d099cc760641f9e1f90ac9c6621408538ba1e909f789d45d6e8d2411b78fb09230f16f15669621aa407aed7511fdf01 languageName: node linkType: hard -"jest-runner@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-runner@npm:29.7.0" +"jest-runner@npm:30.2.0": + version: 30.2.0 + resolution: "jest-runner@npm:30.2.0" dependencies: - "@jest/console": "npm:^29.7.0" - "@jest/environment": "npm:^29.7.0" - "@jest/test-result": "npm:^29.7.0" - "@jest/transform": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" + "@jest/console": "npm:30.2.0" + "@jest/environment": "npm:30.2.0" + "@jest/test-result": "npm:30.2.0" + "@jest/transform": "npm:30.2.0" + "@jest/types": "npm:30.2.0" "@types/node": "npm:*" - chalk: "npm:^4.0.0" + chalk: "npm:^4.1.2" emittery: "npm:^0.13.1" - graceful-fs: "npm:^4.2.9" - jest-docblock: "npm:^29.7.0" - jest-environment-node: "npm:^29.7.0" - jest-haste-map: "npm:^29.7.0" - jest-leak-detector: "npm:^29.7.0" - jest-message-util: "npm:^29.7.0" - jest-resolve: "npm:^29.7.0" - jest-runtime: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - jest-watcher: "npm:^29.7.0" - jest-worker: "npm:^29.7.0" + exit-x: "npm:^0.2.2" + graceful-fs: "npm:^4.2.11" + jest-docblock: "npm:30.2.0" + jest-environment-node: "npm:30.2.0" + jest-haste-map: "npm:30.2.0" + jest-leak-detector: "npm:30.2.0" + jest-message-util: "npm:30.2.0" + jest-resolve: "npm:30.2.0" + jest-runtime: "npm:30.2.0" + jest-util: "npm:30.2.0" + jest-watcher: "npm:30.2.0" + jest-worker: "npm:30.2.0" p-limit: "npm:^3.1.0" source-map-support: "npm:0.5.13" - checksum: 10/9d8748a494bd90f5c82acea99be9e99f21358263ce6feae44d3f1b0cd90991b5df5d18d607e73c07be95861ee86d1cbab2a3fc6ca4b21805f07ac29d47c1da1e + checksum: 10/d3706aa70e64a7ef8b38360d34ea6c261ba4d0b42136d7fb603c4fa71c24fa81f22c39ed2e39ee0db2363a42827810291f3ceb6a299e5996b41d701ad9b24184 languageName: node linkType: hard -"jest-runtime@npm:^29.0.2, jest-runtime@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-runtime@npm:29.7.0" +"jest-runtime@npm:30.2.0": + version: 30.2.0 + resolution: "jest-runtime@npm:30.2.0" dependencies: - "@jest/environment": "npm:^29.7.0" - "@jest/fake-timers": "npm:^29.7.0" - "@jest/globals": "npm:^29.7.0" - "@jest/source-map": "npm:^29.6.3" - "@jest/test-result": "npm:^29.7.0" - "@jest/transform": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" + "@jest/environment": "npm:30.2.0" + "@jest/fake-timers": "npm:30.2.0" + "@jest/globals": "npm:30.2.0" + "@jest/source-map": "npm:30.0.1" + "@jest/test-result": "npm:30.2.0" + "@jest/transform": "npm:30.2.0" + "@jest/types": "npm:30.2.0" "@types/node": "npm:*" - chalk: "npm:^4.0.0" - cjs-module-lexer: "npm:^1.0.0" - collect-v8-coverage: "npm:^1.0.0" - glob: "npm:^7.1.3" - graceful-fs: "npm:^4.2.9" - jest-haste-map: "npm:^29.7.0" - jest-message-util: "npm:^29.7.0" - jest-mock: "npm:^29.7.0" - jest-regex-util: "npm:^29.6.3" - jest-resolve: "npm:^29.7.0" - jest-snapshot: "npm:^29.7.0" - jest-util: "npm:^29.7.0" + chalk: "npm:^4.1.2" + cjs-module-lexer: "npm:^2.1.0" + collect-v8-coverage: "npm:^1.0.2" + glob: "npm:^10.3.10" + graceful-fs: "npm:^4.2.11" + jest-haste-map: "npm:30.2.0" + jest-message-util: "npm:30.2.0" + jest-mock: "npm:30.2.0" + jest-regex-util: "npm:30.0.1" + jest-resolve: "npm:30.2.0" + jest-snapshot: "npm:30.2.0" + jest-util: "npm:30.2.0" slash: "npm:^3.0.0" strip-bom: "npm:^4.0.0" - checksum: 10/59eb58eb7e150e0834a2d0c0d94f2a0b963ae7182cfa6c63f2b49b9c6ef794e5193ef1634e01db41420c36a94cefc512cdd67a055cd3e6fa2f41eaf0f82f5a20 + checksum: 10/81a3a9951420863f001e74c510bf35b85ae983f636f43ee1ffa1618b5a8ddafb681bc2810f71814bc8c8373e9593c89576b2325daf3c765e50057e48d5941df3 languageName: node linkType: hard -"jest-snapshot@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-snapshot@npm:29.7.0" +"jest-snapshot@npm:30.2.0": + version: 30.2.0 + resolution: "jest-snapshot@npm:30.2.0" dependencies: - "@babel/core": "npm:^7.11.6" - "@babel/generator": "npm:^7.7.2" - "@babel/plugin-syntax-jsx": "npm:^7.7.2" - "@babel/plugin-syntax-typescript": "npm:^7.7.2" - "@babel/types": "npm:^7.3.3" - "@jest/expect-utils": "npm:^29.7.0" - "@jest/transform": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - babel-preset-current-node-syntax: "npm:^1.0.0" - chalk: "npm:^4.0.0" - expect: "npm:^29.7.0" - graceful-fs: "npm:^4.2.9" - jest-diff: "npm:^29.7.0" - jest-get-type: "npm:^29.6.3" - jest-matcher-utils: "npm:^29.7.0" - jest-message-util: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - natural-compare: "npm:^1.4.0" - pretty-format: "npm:^29.7.0" - semver: "npm:^7.5.3" - checksum: 10/cb19a3948256de5f922d52f251821f99657339969bf86843bd26cf3332eae94883e8260e3d2fba46129a27c3971c1aa522490e460e16c7fad516e82d10bbf9f8 + "@babel/core": "npm:^7.27.4" + "@babel/generator": "npm:^7.27.5" + "@babel/plugin-syntax-jsx": "npm:^7.27.1" + "@babel/plugin-syntax-typescript": "npm:^7.27.1" + "@babel/types": "npm:^7.27.3" + "@jest/expect-utils": "npm:30.2.0" + "@jest/get-type": "npm:30.1.0" + "@jest/snapshot-utils": "npm:30.2.0" + "@jest/transform": "npm:30.2.0" + "@jest/types": "npm:30.2.0" + babel-preset-current-node-syntax: "npm:^1.2.0" + chalk: "npm:^4.1.2" + expect: "npm:30.2.0" + graceful-fs: "npm:^4.2.11" + jest-diff: "npm:30.2.0" + jest-matcher-utils: "npm:30.2.0" + jest-message-util: "npm:30.2.0" + jest-util: "npm:30.2.0" + pretty-format: "npm:30.2.0" + semver: "npm:^7.7.2" + synckit: "npm:^0.11.8" + checksum: 10/119390b49f397ed622ba7c375fc15f97af67c4fc49a34cf829c86ee732be2b06ad3c7171c76bb842a0e84a234783f1a4c721909aa316fbe00c6abc7c5962dfbc + languageName: node + linkType: hard + +"jest-util@npm:30.2.0": + version: 30.2.0 + resolution: "jest-util@npm:30.2.0" + dependencies: + "@jest/types": "npm:30.2.0" + "@types/node": "npm:*" + chalk: "npm:^4.1.2" + ci-info: "npm:^4.2.0" + graceful-fs: "npm:^4.2.11" + picomatch: "npm:^4.0.2" + checksum: 10/cf2f2fb83417ea69f9992121561c95cf4e9aad7946819b771b8b52addf78811101b33b51d0a39fa0c305f2751dab262feed7699de052659ff03d51827c8862f5 languageName: node linkType: hard @@ -35355,33 +33979,33 @@ __metadata: languageName: node linkType: hard -"jest-validate@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-validate@npm:29.7.0" +"jest-validate@npm:30.2.0": + version: 30.2.0 + resolution: "jest-validate@npm:30.2.0" dependencies: - "@jest/types": "npm:^29.6.3" - camelcase: "npm:^6.2.0" - chalk: "npm:^4.0.0" - jest-get-type: "npm:^29.6.3" + "@jest/get-type": "npm:30.1.0" + "@jest/types": "npm:30.2.0" + camelcase: "npm:^6.3.0" + chalk: "npm:^4.1.2" leven: "npm:^3.1.0" - pretty-format: "npm:^29.7.0" - checksum: 10/8ee1163666d8eaa16d90a989edba2b4a3c8ab0ffaa95ad91b08ca42b015bfb70e164b247a5b17f9de32d096987cada63ed8491ab82761bfb9a28bc34b27ae161 + pretty-format: "npm:30.2.0" + checksum: 10/61e66c6df29a1e181f8de063678dd2096bb52cc8a8ead3c9a3f853d54eca458ad04c7fb81931d9274affb67d0504a91a2a520456a139a26665810c3bf039b677 languageName: node linkType: hard -"jest-watcher@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-watcher@npm:29.7.0" +"jest-watcher@npm:30.2.0": + version: 30.2.0 + resolution: "jest-watcher@npm:30.2.0" dependencies: - "@jest/test-result": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" + "@jest/test-result": "npm:30.2.0" + "@jest/types": "npm:30.2.0" "@types/node": "npm:*" - ansi-escapes: "npm:^4.2.1" - chalk: "npm:^4.0.0" + ansi-escapes: "npm:^4.3.2" + chalk: "npm:^4.1.2" emittery: "npm:^0.13.1" - jest-util: "npm:^29.7.0" - string-length: "npm:^4.0.1" - checksum: 10/4f616e0345676631a7034b1d94971aaa719f0cd4a6041be2aa299be437ea047afd4fe05c48873b7963f5687a2f6c7cbf51244be8b14e313b97bfe32b1e127e55 + jest-util: "npm:30.2.0" + string-length: "npm:^4.0.2" + checksum: 10/fa38d06dcc59dbbd6a9ff22dea499d3c81ed376d9993b82d01797a99bf466d48641a99b9f3670a4b5480ca31144c5e017b96b7059e4d7541358fb48cf517a2db languageName: node linkType: hard @@ -35395,12 +34019,16 @@ __metadata: languageName: node linkType: hard -"jest-when@npm:^3.1.0": - version: 3.7.0 - resolution: "jest-when@npm:3.7.0" - peerDependencies: - jest: ">= 25" - checksum: 10/b5b88d077ed467aab220c71c885dbc5f448604f06e68f761ce9f479c99bb74e0dbf553d1cc980751d88401b034a578e1c44eec5e4095743b7586f02bb7c6313d +"jest-worker@npm:30.2.0": + version: 30.2.0 + resolution: "jest-worker@npm:30.2.0" + dependencies: + "@types/node": "npm:*" + "@ungap/structured-clone": "npm:^1.3.0" + jest-util: "npm:30.2.0" + merge-stream: "npm:^2.0.0" + supports-color: "npm:^8.1.1" + checksum: 10/9354b0c71c80173f673da6bbc0ddaad26e4395b06532f7332e0c1e93e855b873b10139b040e01eda77f3dc5a0b67613e2bd7c56c4947ee771acfc3611de2ca29 languageName: node linkType: hard @@ -35427,26 +34055,26 @@ __metadata: languageName: node linkType: hard -"jest@npm:^29.7.0": - version: 29.7.0 - resolution: "jest@npm:29.7.0" +"jest@npm:^30, jest@npm:^30.2.0": + version: 30.2.0 + resolution: "jest@npm:30.2.0" dependencies: - "@jest/core": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - import-local: "npm:^3.0.2" - jest-cli: "npm:^29.7.0" + "@jest/core": "npm:30.2.0" + "@jest/types": "npm:30.2.0" + import-local: "npm:^3.2.0" + jest-cli: "npm:30.2.0" peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 peerDependenciesMeta: node-notifier: optional: true bin: - jest: bin/jest.js - checksum: 10/97023d78446098c586faaa467fbf2c6b07ff06e2c85a19e3926adb5b0effe9ac60c4913ae03e2719f9c01ae8ffd8d92f6b262cedb9555ceeb5d19263d8c6362a + jest: ./bin/jest.js + checksum: 10/61c9d100750e4354cd7305d1f3ba253ffde4deaf12cb4be4d42d54f2dd5986e383a39c4a8691dbdc3839c69094a52413ed36f1886540ac37b71914a990b810d0 languageName: node linkType: hard -"jiti@npm:^2.0.0, jiti@npm:^2.4.2": +"jiti@npm:2.4.2": version: 2.4.2 resolution: "jiti@npm:2.4.2" bin: @@ -35455,6 +34083,15 @@ __metadata: languageName: node linkType: hard +"jiti@npm:^2.0.0, jiti@npm:^2.6.0": + version: 2.6.1 + resolution: "jiti@npm:2.6.1" + bin: + jiti: lib/jiti-cli.mjs + checksum: 10/8cd72c5fd03a0502564c3f46c49761090f6dadead21fa191b73535724f095ad86c2fa89ee6fe4bc3515337e8d406cc8fb2d37b73fa0c99a34584bac35cd4a4de + languageName: node + linkType: hard + "jju@npm:~1.4.0": version: 1.4.0 resolution: "jju@npm:1.4.0" @@ -35477,16 +34114,16 @@ __metadata: linkType: hard "jose@npm:^5.0.0": - version: 5.8.0 - resolution: "jose@npm:5.8.0" - checksum: 10/d5a11135754aeb7e3dcfbfb33ae1087541a081002cedb533e4705a9d71237a2c22f21085a291a0847939257a7b2f2374b589d306be17c3330049aefb62719c5e + version: 5.10.0 + resolution: "jose@npm:5.10.0" + checksum: 10/03881d1dfb390dcf50926402edcfe233bf557b5a77321fcb1bdb53453bc1cdd26d2d0a9ab28c7445cbb826881f84fdf5074179700f10c2711ccb9880f51065d7 languageName: node linkType: hard -"jose@npm:^6.0.10": - version: 6.0.10 - resolution: "jose@npm:6.0.10" - checksum: 10/8ba0d1aca94bdf780247c737328114fab3e394e1531e881dfe3fdd1d4a293b08cf7c0242769e72c8c8eee15c95761b542cf10730c3dfbb108084762df7d25cec +"jose@npm:^6.0.10, jose@npm:^6.1.3": + version: 6.1.3 + resolution: "jose@npm:6.1.3" + checksum: 10/9626c51e8c3792b505e954f3094698c182208617b62dfb27269230f31e57560b083985ed8128b8a9753aa92daf18d3a2341cc826d149503f14569abe87d42389 languageName: node linkType: hard @@ -35532,38 +34169,26 @@ __metadata: languageName: node linkType: hard -"js-yaml@npm:=4.1.0, js-yaml@npm:^4.0.0, js-yaml@npm:^4.1.0": - version: 4.1.0 - resolution: "js-yaml@npm:4.1.0" +"js-yaml@npm:=4.1.1, js-yaml@npm:^4.0.0, js-yaml@npm:^4.1.0, js-yaml@npm:^4.1.1, js-yaml@npm:~4.1.0": + version: 4.1.1 + resolution: "js-yaml@npm:4.1.1" dependencies: argparse: "npm:^2.0.1" bin: js-yaml: bin/js-yaml.js - checksum: 10/c138a34a3fd0d08ebaf71273ad4465569a483b8a639e0b118ff65698d257c2791d3199e3f303631f2cb98213fa7b5f5d6a4621fd0fff819421b990d30d967140 + checksum: 10/a52d0519f0f4ef5b4adc1cde466cb54c50d56e2b4a983b9d5c9c0f2f99462047007a6274d7e95617a21d3c91fde3ee6115536ed70991cd645ba8521058b78f77 languageName: node linkType: hard "js-yaml@npm:^3.10.0, js-yaml@npm:^3.13.0, js-yaml@npm:^3.13.1, js-yaml@npm:^3.6.1, js-yaml@npm:^3.8.3": - version: 3.14.1 - resolution: "js-yaml@npm:3.14.1" + version: 3.14.2 + resolution: "js-yaml@npm:3.14.2" dependencies: argparse: "npm:^1.0.7" esprima: "npm:^4.0.0" bin: js-yaml: bin/js-yaml.js - checksum: 10/9e22d80b4d0105b9899135365f746d47466ed53ef4223c529b3c0f7a39907743fdbd3c4379f94f1106f02755b5e90b2faaf84801a891135544e1ea475d1a1379 - languageName: node - linkType: hard - -"js-yaml@npm:~3.13.1": - version: 3.13.1 - resolution: "js-yaml@npm:3.13.1" - dependencies: - argparse: "npm:^1.0.7" - esprima: "npm:^4.0.0" - bin: - js-yaml: bin/js-yaml.js - checksum: 10/cec89175b065743875fce53e63adc8b89aded77e18d00e54ff80c57ab730f22ccfddaf2fe3e6adab1d6dff59a3d55dd9ae6fc711d46335b7e94c32d3583a5627 + checksum: 10/172e0b6007b0bf0fc8d2469c94424f7dd765c64a047d2b790831fecef2204a4054eabf4d911eb73ab8c9a3256ab8ba1ee8d655b789bf24bf059c772acc2075a1 languageName: node linkType: hard @@ -35629,46 +34254,6 @@ __metadata: languageName: node linkType: hard -"jsdom@npm:^20.0.0": - version: 20.0.0 - resolution: "jsdom@npm:20.0.0" - dependencies: - abab: "npm:^2.0.6" - acorn: "npm:^8.7.1" - acorn-globals: "npm:^6.0.0" - cssom: "npm:^0.5.0" - cssstyle: "npm:^2.3.0" - data-urls: "npm:^3.0.2" - decimal.js: "npm:^10.3.1" - domexception: "npm:^4.0.0" - escodegen: "npm:^2.0.0" - form-data: "npm:^4.0.0" - html-encoding-sniffer: "npm:^3.0.0" - http-proxy-agent: "npm:^5.0.0" - https-proxy-agent: "npm:^5.0.1" - is-potential-custom-element-name: "npm:^1.0.1" - nwsapi: "npm:^2.2.0" - parse5: "npm:^7.0.0" - saxes: "npm:^6.0.0" - symbol-tree: "npm:^3.2.4" - tough-cookie: "npm:^4.0.0" - w3c-hr-time: "npm:^1.0.2" - w3c-xmlserializer: "npm:^3.0.0" - webidl-conversions: "npm:^7.0.0" - whatwg-encoding: "npm:^2.0.0" - whatwg-mimetype: "npm:^3.0.0" - whatwg-url: "npm:^11.0.0" - ws: "npm:^8.8.0" - xml-name-validator: "npm:^4.0.0" - peerDependencies: - canvas: ^2.5.0 - peerDependenciesMeta: - canvas: - optional: true - checksum: 10/5492ef8f496183bbce738977a0fb43b57a269d9d79fe12ea03d33ce9821a5ef54cb1580b94a662bf2d5ba463790fb61a9e892a87eb95d02e72bdf4068280a737 - languageName: node - linkType: hard - "jsdom@npm:^25.0.1": version: 25.0.1 resolution: "jsdom@npm:25.0.1" @@ -35703,6 +34288,39 @@ __metadata: languageName: node linkType: hard +"jsdom@npm:^27, jsdom@npm:^27.1.0": + version: 27.4.0 + resolution: "jsdom@npm:27.4.0" + dependencies: + "@acemir/cssom": "npm:^0.9.28" + "@asamuzakjp/dom-selector": "npm:^6.7.6" + "@exodus/bytes": "npm:^1.6.0" + cssstyle: "npm:^5.3.4" + data-urls: "npm:^6.0.0" + decimal.js: "npm:^10.6.0" + html-encoding-sniffer: "npm:^6.0.0" + http-proxy-agent: "npm:^7.0.2" + https-proxy-agent: "npm:^7.0.6" + is-potential-custom-element-name: "npm:^1.0.1" + parse5: "npm:^8.0.0" + saxes: "npm:^6.0.0" + symbol-tree: "npm:^3.2.4" + tough-cookie: "npm:^6.0.0" + w3c-xmlserializer: "npm:^5.0.0" + webidl-conversions: "npm:^8.0.0" + whatwg-mimetype: "npm:^4.0.0" + whatwg-url: "npm:^15.1.0" + ws: "npm:^8.18.3" + xml-name-validator: "npm:^5.0.0" + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + checksum: 10/7c6db85ab91183b95204648e086cfc09ecee36d9e8fee0bb5d68e27543eca632de0af6d43de461176a7823820543d5c53561778af5f712b1a1cd28bfac084d51 + languageName: node + linkType: hard + "jsep@npm:^1.2.0, jsep@npm:^1.3.6, jsep@npm:^1.4.0": version: 1.4.0 resolution: "jsep@npm:1.4.0" @@ -35742,19 +34360,6 @@ __metadata: languageName: node linkType: hard -"json-file-plus@npm:^3.3.1": - version: 3.3.1 - resolution: "json-file-plus@npm:3.3.1" - dependencies: - is: "npm:^3.2.1" - node.extend: "npm:^2.0.0" - object.assign: "npm:^4.1.0" - promiseback: "npm:^2.0.2" - safer-buffer: "npm:^2.0.2" - checksum: 10/6b71dad39e0fd8d0a23a82ca70b7c94adfcd59986e63165935d2adba5502076b75f3267e357372dd118f9d680ecc142f0f67617de9f27139c3c8b113cdd9c574 - languageName: node - linkType: hard - "json-parse-even-better-errors@npm:^2.3.0, json-parse-even-better-errors@npm:^2.3.1": version: 2.3.1 resolution: "json-parse-even-better-errors@npm:2.3.1" @@ -35771,6 +34376,13 @@ __metadata: languageName: node linkType: hard +"json-rpc-2.0@npm:^1.7.1": + version: 1.7.1 + resolution: "json-rpc-2.0@npm:1.7.1" + checksum: 10/d5b91fc7fede0ff3f1fde915fa9b142cf0980fa01282572cf9373a45184b34b377e0738d05b41d3859eaebdd20f692073eebc0c5da4990a3c4633132eab52f08 + languageName: node + linkType: hard + "json-schema-compare@npm:^0.2.2": version: 0.2.2 resolution: "json-schema-compare@npm:0.2.2" @@ -35837,6 +34449,13 @@ __metadata: languageName: node linkType: hard +"json-schema-typed@npm:^8.0.2": + version: 8.0.2 + resolution: "json-schema-typed@npm:8.0.2" + checksum: 10/fa866d1fe91e3a94aa4fe007861475cd03dcaf47b719861cab171ef2f8598478007c634d29ae45de94ee34ddff4e13414c63ea5ff06c5b868b613142c699d511 + languageName: node + linkType: hard + "json-schema@npm:^0.4.0": version: 0.4.0 resolution: "json-schema@npm:0.4.0" @@ -35907,9 +34526,9 @@ __metadata: linkType: hard "jsonc-parser@npm:^3.2.0": - version: 3.2.0 - resolution: "jsonc-parser@npm:3.2.0" - checksum: 10/bd68b902e5f9394f01da97921f49c5084b2dc03a0c5b4fdb2a429f8d6f292686c1bf87badaeb0a8148d024192a88f5ad2e57b2918ba43fe25cf15f3371db64d4 + version: 3.3.1 + resolution: "jsonc-parser@npm:3.3.1" + checksum: 10/9b0dc391f20b47378f843ef1e877e73ec652a5bdc3c5fa1f36af0f119a55091d147a86c1ee86a232296f55c929bba174538c2bf0312610e0817a22de131cc3f4 languageName: node linkType: hard @@ -35960,8 +34579,8 @@ __metadata: linkType: hard "jsonpath-plus@npm:^10.0.0, jsonpath-plus@npm:^10.3.0, jsonpath-plus@npm:^6.0.1 || ^10.1.0": - version: 10.3.0 - resolution: "jsonpath-plus@npm:10.3.0" + version: 10.4.0 + resolution: "jsonpath-plus@npm:10.4.0" dependencies: "@jsep-plugin/assignment": "npm:^1.3.0" "@jsep-plugin/regex": "npm:^1.0.4" @@ -35969,18 +34588,7 @@ __metadata: bin: jsonpath: bin/jsonpath-cli.js jsonpath-plus: bin/jsonpath-cli.js - checksum: 10/082302334414c7c5ab0cc8239563118f7f14bb2949d001b009f436491d00f94a7a293eed3eaf61ffdaf72f6fda9d25198a4280c4f68a4c403154ca7ed2bd0dc9 - languageName: node - linkType: hard - -"jsonpath@npm:^1.1.1": - version: 1.1.1 - resolution: "jsonpath@npm:1.1.1" - dependencies: - esprima: "npm:1.2.2" - static-eval: "npm:2.0.2" - underscore: "npm:1.12.1" - checksum: 10/aa6c2fea9c05eeba4a37870cbbcf30c20de5211d0fd967786b6c59b8546c9f80182328ee2428daf989c8d5c6e6bf97fed28eefc790144258b1238707c30706eb + checksum: 10/0ff33c7eb6500d7c8d789ce15a63ac2c46cb01b855f1c53729ca9e3833e0253af70277fc1799ebfe0b3130ddc03c127562669b999729dd11f2621b81472248d4 languageName: node linkType: hard @@ -36148,45 +34756,45 @@ __metadata: languageName: node linkType: hard -"jwa@npm:^1.4.1": - version: 1.4.1 - resolution: "jwa@npm:1.4.1" +"jwa@npm:^1.4.2": + version: 1.4.2 + resolution: "jwa@npm:1.4.2" dependencies: - buffer-equal-constant-time: "npm:1.0.1" + buffer-equal-constant-time: "npm:^1.0.1" ecdsa-sig-formatter: "npm:1.0.11" safe-buffer: "npm:^5.0.1" - checksum: 10/0bc002b71dd70480fedc7d442a4d2b9185a9947352a027dcb4935864ad2323c57b5d391adf968a3622b61e940cef4f3484d5813b95864539272d41cac145d6f3 + checksum: 10/a46c9ddbcc226d9e85e13ef96328c7d331abddd66b5a55ec44bcf4350464a6125385ac9c1e64faa0fae8d586d90a14d6b5e96c73f0388970a3918d5252efb0f3 languageName: node linkType: hard -"jwa@npm:^2.0.0": - version: 2.0.0 - resolution: "jwa@npm:2.0.0" +"jwa@npm:^2.0.1": + version: 2.0.1 + resolution: "jwa@npm:2.0.1" dependencies: - buffer-equal-constant-time: "npm:1.0.1" + buffer-equal-constant-time: "npm:^1.0.1" ecdsa-sig-formatter: "npm:1.0.11" safe-buffer: "npm:^5.0.1" - checksum: 10/ab983f6685d99d13ddfbffef9b1c66309a536362a8412d49ba6e687d834a1240ce39290f30ac7dbe241e0ab6c76fee7ff795776ce534e11d148158c9b7193498 + checksum: 10/b04312a1de85f912b96aa3a7211717b8336945fab5b4f7cbc7800f4c80934060c0a3111576fad8d76e41ad62887d6da4b21fd4c47e45c174197f8be7dc0c1694 languageName: node linkType: hard "jws@npm:^3.2.2": - version: 3.2.2 - resolution: "jws@npm:3.2.2" + version: 3.2.3 + resolution: "jws@npm:3.2.3" dependencies: - jwa: "npm:^1.4.1" + jwa: "npm:^1.4.2" safe-buffer: "npm:^5.0.1" - checksum: 10/70b016974af8a76d25030c80a0097b24ed5b17a9cf10f43b163c11cb4eb248d5d04a3fe48c0d724d2884c32879d878ccad7be0663720f46b464f662f7ed778fe + checksum: 10/707387dd1cabcc3d9c2818f773cfaac7ede66e79ca11bbd159285a88cf5d8e8f355afcb8ee373e7bb0fcf9b7a2df015b22c50f27842f2c77453f04cd9f8f4009 languageName: node linkType: hard "jws@npm:^4.0.0": - version: 4.0.0 - resolution: "jws@npm:4.0.0" + version: 4.0.1 + resolution: "jws@npm:4.0.1" dependencies: - jwa: "npm:^2.0.0" + jwa: "npm:^2.0.1" safe-buffer: "npm:^5.0.1" - checksum: 10/1d15f4cdea376c6bd6a81002bd2cb0bf3d51d83da8f0727947b5ba3e10cf366721b8c0d099bf8c1eb99eb036e2c55e5fd5efd378ccff75a2b4e0bd10002348b9 + checksum: 10/75d7b157489fa9a72023712c58a7a7706c7e2b10eec27fabd3bb9cae0c9e492251ab72527d20a8a5f5726196f0508c320c643fddff7076657f6bca16d0ceeeeb languageName: node linkType: hard @@ -36204,21 +34812,23 @@ __metadata: languageName: node linkType: hard -"keygrip@npm:~1.1.0": - version: 1.1.0 - resolution: "keygrip@npm:1.1.0" +"keytar@npm:^7.9.0": + version: 7.9.0 + resolution: "keytar@npm:7.9.0" dependencies: - tsscmp: "npm:1.0.6" - checksum: 10/078cd16a463d187121f0a27c1c9c95c52ad392b620f823431689f345a0501132cee60f6e96914b07d570105af470b96960402accd6c48a0b1f3cd8fac4fa2cae + node-addon-api: "npm:^4.3.0" + node-gyp: "npm:latest" + prebuild-install: "npm:^7.0.1" + checksum: 10/904795bc304f8ad89b80f915c869a941a383312b58584212a199473d18647035cfda92a9c53e6c53bf13ea0fed23037c9597eb418a5c71ee9454f140f026fac9 languageName: node linkType: hard "keyv@npm:*, keyv@npm:^5.2.1": - version: 5.3.4 - resolution: "keyv@npm:5.3.4" + version: 5.6.0 + resolution: "keyv@npm:5.6.0" dependencies: - "@keyv/serialize": "npm:^1.0.3" - checksum: 10/3e294eb1168af78ad3430d0cc47b6839fd3e70593238d35226a0a1e4094abe397ea378b7bce35cfcb314e55b5d1b4fcdae3c19bee5610e78d283b3cb5b279c8b + "@keyv/serialize": "npm:^1.1.1" + checksum: 10/f1de999fdf635d703d091981a0a3844fb20081e01d45c6743ada7d01f3a6570dc47d1882e10d0a98d31075b342db8e62b4ebe4f5bf473dcd0903064d718709c1 languageName: node linkType: hard @@ -36311,92 +34921,41 @@ __metadata: linkType: hard "knip@npm:^5.42.0": - version: 5.42.2 - resolution: "knip@npm:5.42.2" + version: 5.88.1 + resolution: "knip@npm:5.88.1" dependencies: - "@nodelib/fs.walk": "npm:3.0.1" - "@snyk/github-codeowners": "npm:1.1.0" - easy-table: "npm:1.2.0" - enhanced-resolve: "npm:^5.18.0" + "@nodelib/fs.walk": "npm:^1.2.3" fast-glob: "npm:^3.3.3" - jiti: "npm:^2.4.2" - js-yaml: "npm:^4.1.0" + formatly: "npm:^0.3.0" + jiti: "npm:^2.6.0" minimist: "npm:^1.2.8" - picocolors: "npm:^1.1.0" + oxc-resolver: "npm:^11.19.1" + picocolors: "npm:^1.1.1" picomatch: "npm:^4.0.1" - pretty-ms: "npm:^9.0.0" - smol-toml: "npm:^1.3.1" - strip-json-comments: "npm:5.0.1" - summary: "npm:2.1.0" - zod: "npm:^3.22.4" - zod-validation-error: "npm:^3.0.3" + smol-toml: "npm:^1.5.2" + strip-json-comments: "npm:5.0.3" + unbash: "npm:^2.2.0" + yaml: "npm:^2.8.2" + zod: "npm:^4.1.11" peerDependencies: "@types/node": ">=18" - typescript: ">=5.0.4" + typescript: ">=5.0.4 <7" bin: knip: bin/knip.js knip-bun: bin/knip-bun.js - checksum: 10/1e540ad66e8e5cd2dfceb0c333ca46446300f4e40a51599ca6cd8de705bf0f928332ca108de28306ae7b54cc8fc1f66667135193baf32d4ccd30560797825935 - languageName: node - linkType: hard - -"koa-compose@npm:^4.1.0": - version: 4.1.0 - resolution: "koa-compose@npm:4.1.0" - checksum: 10/46cb16792d96425e977c2ae4e5cb04930280740e907242ec9c25e3fb8b4a1d7b54451d7432bc24f40ec62255edea71894d2ceeb8238501842b4e48014f2e83db - languageName: node - linkType: hard - -"koa-convert@npm:^2.0.0": - version: 2.0.0 - resolution: "koa-convert@npm:2.0.0" - dependencies: - co: "npm:^4.6.0" - koa-compose: "npm:^4.1.0" - checksum: 10/7385b3391995f59c1312142e110d5dff677f9850dbfbcf387cd36a7b0af03b5d26e82b811eb9bb008b4f3e661cdab1f8817596e46b1929da2cf6e97a2f7456ed - languageName: node - linkType: hard - -"koa@npm:2.15.4": - version: 2.15.4 - resolution: "koa@npm:2.15.4" - dependencies: - accepts: "npm:^1.3.5" - cache-content-type: "npm:^1.0.0" - content-disposition: "npm:~0.5.2" - content-type: "npm:^1.0.4" - cookies: "npm:~0.9.0" - debug: "npm:^4.3.2" - delegates: "npm:^1.0.0" - depd: "npm:^2.0.0" - destroy: "npm:^1.0.4" - encodeurl: "npm:^1.0.2" - escape-html: "npm:^1.0.3" - fresh: "npm:~0.5.2" - http-assert: "npm:^1.3.0" - http-errors: "npm:^1.6.3" - is-generator-function: "npm:^1.0.7" - koa-compose: "npm:^4.1.0" - koa-convert: "npm:^2.0.0" - on-finished: "npm:^2.3.0" - only: "npm:~0.0.2" - parseurl: "npm:^1.3.2" - statuses: "npm:^1.5.0" - type-is: "npm:^1.6.16" - vary: "npm:^1.1.2" - checksum: 10/98de77173822f0a28c0f5d1ebd261ab02f3f905d40602e51957a0c7202122647a60c5b6c59be03361dd24bf6a5685eac97af84b306914efd057751e71f93cb0f + checksum: 10/2356c5725bdf2cc90b51cc39563812db0f23aa45857c64992bbe9b13705a6b4a8a50eb28713795ffc212713d2e9ccc7ef6d5c80fd387a118b717b13e036b8de3 languageName: node linkType: hard "kubernetes-models@npm:^4.1.0, kubernetes-models@npm:^4.3.1": - version: 4.4.2 - resolution: "kubernetes-models@npm:4.4.2" + version: 4.5.1 + resolution: "kubernetes-models@npm:4.5.1" dependencies: - "@kubernetes-models/apimachinery": "npm:^2.0.2" + "@kubernetes-models/apimachinery": "npm:^2.2.0" "@kubernetes-models/base": "npm:^5.0.1" "@kubernetes-models/validate": "npm:^4.0.0" "@swc/helpers": "npm:^0.5.8" - checksum: 10/67956c3be831d02a0de3742809f1f8c89382eb11a9d66e7161f314413a1ddfce963513aa4eeba451a07a814b2f2afa959984a891f6e504e23e12825285a70a4e + checksum: 10/65a7b2064dcfbc719a80da7fd24b44775f68423170eb7abef3d4946604acdb5c8a3b2c8fded1d7212e2725b10ccf13abc94adf3091dc8005e25cd5321577ae92 languageName: node linkType: hard @@ -36452,16 +35011,11 @@ __metadata: linkType: hard "ldapts@npm:^8.0.6": - version: 8.0.9 - resolution: "ldapts@npm:8.0.9" + version: 8.1.7 + resolution: "ldapts@npm:8.1.7" dependencies: - "@types/asn1": "npm:>=0.2.4" - asn1: "npm:0.2.6" - debug: "npm:4.4.1" strict-event-emitter-types: "npm:2.0.0" - uuid: "npm:11.1.0" - whatwg-url: "npm:14.2.0" - checksum: 10/512bfec21e2c5d8a257ffda0c426221023043718b235dcc21d5ea81ba43d1999a2f14c1e9d6f7db7af1af2c467b46ee7016cac8da74ebae900fe208c54435b0e + checksum: 10/6dc0002df51a62de5391066df981fc3dab5471dc733fdedd698b58dea4c802ca20d17498518757555d979abc93a320b3e88bbcf3b380c1635930e48aebf3847c languageName: node linkType: hard @@ -36489,29 +35043,19 @@ __metadata: languageName: node linkType: hard -"levn@npm:~0.3.0": - version: 0.3.0 - resolution: "levn@npm:0.3.0" +"libsodium-wrappers@npm:*, libsodium-wrappers@npm:^0.8.0": + version: 0.8.2 + resolution: "libsodium-wrappers@npm:0.8.2" dependencies: - prelude-ls: "npm:~1.1.2" - type-check: "npm:~0.3.2" - checksum: 10/e1c3e75b5c430d9aa4c32c83c8a611e4ca53608ca78e3ea3bf6bbd9d017e4776d05d86e27df7901baebd3afa732abede9f26f715b8c1be19e95505c7a3a7b589 + libsodium: "npm:^0.8.0" + checksum: 10/0116b4deeea9ec21f1e4dcbf665ccaa41e6143161f16b5345c10ea995bd1f930155d4aca1783bee99d42854dda3265a22f48e5edb7ddfe1159bf460416e11117 languageName: node linkType: hard -"libsodium-wrappers@npm:^0.7.11": - version: 0.7.15 - resolution: "libsodium-wrappers@npm:0.7.15" - dependencies: - libsodium: "npm:^0.7.15" - checksum: 10/a97e72da2de9b9294df35aaa053c7943b7eab0e805e5c4cbdc37bd7190221875b57d485f41595b217e80cbe44f96033a756ea12bc6214d3710b2be3d16ac1f9b - languageName: node - linkType: hard - -"libsodium@npm:^0.7.15": - version: 0.7.15 - resolution: "libsodium@npm:0.7.15" - checksum: 10/e91af900277c850359706e2dc05aca0b82d259244f5dfdbfc1aaa8a0d373d41cc62232545f7da10f8b3b85f1ae46782265891dd4f5671bc65d0287acdcfe1b56 +"libsodium@npm:^0.8.0": + version: 0.8.2 + resolution: "libsodium@npm:0.8.2" + checksum: 10/ce129d552ceaf8c4cfcd96fce8a25b9a1118445e72a46b08237d9ac0c3bf7ba6e586fba2285837deaeb05f342e3fb9fa58be153e0c616ce298798c7b170d494c languageName: node linkType: hard @@ -36533,126 +35077,6 @@ __metadata: languageName: node linkType: hard -"lightningcss-android-arm64@npm:1.30.2": - version: 1.30.2 - resolution: "lightningcss-android-arm64@npm:1.30.2" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - -"lightningcss-darwin-arm64@npm:1.30.2": - version: 1.30.2 - resolution: "lightningcss-darwin-arm64@npm:1.30.2" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"lightningcss-darwin-x64@npm:1.30.2": - version: 1.30.2 - resolution: "lightningcss-darwin-x64@npm:1.30.2" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"lightningcss-freebsd-x64@npm:1.30.2": - version: 1.30.2 - resolution: "lightningcss-freebsd-x64@npm:1.30.2" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - -"lightningcss-linux-arm-gnueabihf@npm:1.30.2": - version: 1.30.2 - resolution: "lightningcss-linux-arm-gnueabihf@npm:1.30.2" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - -"lightningcss-linux-arm64-gnu@npm:1.30.2": - version: 1.30.2 - resolution: "lightningcss-linux-arm64-gnu@npm:1.30.2" - conditions: os=linux & cpu=arm64 & libc=glibc - languageName: node - linkType: hard - -"lightningcss-linux-arm64-musl@npm:1.30.2": - version: 1.30.2 - resolution: "lightningcss-linux-arm64-musl@npm:1.30.2" - conditions: os=linux & cpu=arm64 & libc=musl - languageName: node - linkType: hard - -"lightningcss-linux-x64-gnu@npm:1.30.2": - version: 1.30.2 - resolution: "lightningcss-linux-x64-gnu@npm:1.30.2" - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - -"lightningcss-linux-x64-musl@npm:1.30.2": - version: 1.30.2 - resolution: "lightningcss-linux-x64-musl@npm:1.30.2" - conditions: os=linux & cpu=x64 & libc=musl - languageName: node - linkType: hard - -"lightningcss-win32-arm64-msvc@npm:1.30.2": - version: 1.30.2 - resolution: "lightningcss-win32-arm64-msvc@npm:1.30.2" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"lightningcss-win32-x64-msvc@npm:1.30.2": - version: 1.30.2 - resolution: "lightningcss-win32-x64-msvc@npm:1.30.2" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"lightningcss@npm:^1.29.1": - version: 1.30.2 - resolution: "lightningcss@npm:1.30.2" - dependencies: - detect-libc: "npm:^2.0.3" - lightningcss-android-arm64: "npm:1.30.2" - lightningcss-darwin-arm64: "npm:1.30.2" - lightningcss-darwin-x64: "npm:1.30.2" - lightningcss-freebsd-x64: "npm:1.30.2" - lightningcss-linux-arm-gnueabihf: "npm:1.30.2" - lightningcss-linux-arm64-gnu: "npm:1.30.2" - lightningcss-linux-arm64-musl: "npm:1.30.2" - lightningcss-linux-x64-gnu: "npm:1.30.2" - lightningcss-linux-x64-musl: "npm:1.30.2" - lightningcss-win32-arm64-msvc: "npm:1.30.2" - lightningcss-win32-x64-msvc: "npm:1.30.2" - dependenciesMeta: - lightningcss-android-arm64: - optional: true - lightningcss-darwin-arm64: - optional: true - lightningcss-darwin-x64: - optional: true - lightningcss-freebsd-x64: - optional: true - lightningcss-linux-arm-gnueabihf: - optional: true - lightningcss-linux-arm64-gnu: - optional: true - lightningcss-linux-arm64-musl: - optional: true - lightningcss-linux-x64-gnu: - optional: true - lightningcss-linux-x64-musl: - optional: true - lightningcss-win32-arm64-msvc: - optional: true - lightningcss-win32-x64-msvc: - optional: true - checksum: 10/d6cc06d9bac295589a49446e9c45a241dfa16f4f81a7318c26cbc0be3e189003ec0da5d9a0fd9bdffc63a3ce05878cc7329277eaac77a826e8b68c73dc96cfda - languageName: node - linkType: hard - "lilconfig@npm:^2.0.3": version: 2.1.0 resolution: "lilconfig@npm:2.1.0" @@ -36660,13 +35084,6 @@ __metadata: languageName: node linkType: hard -"lilconfig@npm:^3.1.3": - version: 3.1.3 - resolution: "lilconfig@npm:3.1.3" - checksum: 10/b932ce1af94985f0efbe8896e57b1f814a48c8dbd7fc0ef8469785c6303ed29d0090af3ccad7e36b626bfca3a4dc56cc262697e9a8dd867623cf09a39d54e4c3 - languageName: node - linkType: hard - "lines-and-columns@npm:^1.1.6": version: 1.2.4 resolution: "lines-and-columns@npm:1.2.4" @@ -36674,15 +35091,6 @@ __metadata: languageName: node linkType: hard -"linkify-it@npm:^3.0.1": - version: 3.0.2 - resolution: "linkify-it@npm:3.0.2" - dependencies: - uc.micro: "npm:^1.0.1" - checksum: 10/c1fcabd105c2bb97ce993dd9c6474b2f29ab759176f4fddb82fa01f497a0cea268c67276c7bd43a5cf390a6af54e25ead77ea70f75a648fc9736caff6aa6f2f4 - languageName: node - linkType: hard - "linkify-it@npm:^5.0.0": version: 5.0.0 resolution: "linkify-it@npm:5.0.0" @@ -36709,23 +35117,19 @@ __metadata: languageName: node linkType: hard -"lint-staged@npm:^15.0.0": - version: 15.5.2 - resolution: "lint-staged@npm:15.5.2" +"lint-staged@npm:^16.0.0": + version: 16.4.0 + resolution: "lint-staged@npm:16.4.0" dependencies: - chalk: "npm:^5.4.1" - commander: "npm:^13.1.0" - debug: "npm:^4.4.0" - execa: "npm:^8.0.1" - lilconfig: "npm:^3.1.3" - listr2: "npm:^8.2.5" - micromatch: "npm:^4.0.8" - pidtree: "npm:^0.6.0" + commander: "npm:^14.0.3" + listr2: "npm:^9.0.5" + picomatch: "npm:^4.0.3" string-argv: "npm:^0.3.2" - yaml: "npm:^2.7.0" + tinyexec: "npm:^1.0.4" + yaml: "npm:^2.8.2" bin: lint-staged: bin/lint-staged.js - checksum: 10/523c332d6cb6e34972a6530a7a2487307555e784df9466c82f2b8d17c8090a3db561a6362065ae6b63048c25fcb85c9e32057cd0bfb756bf7ab185bea1dbb89c + checksum: 10/eb7aa0d43e321bbf282ce0c693a3db762aa9b8e5bf29419a49c8877a247cd3a14cf8d519f914f8c8dcd2aea73ac83c0bb44fadb97a30004219e060a780dda74e languageName: node linkType: hard @@ -36736,24 +35140,24 @@ __metadata: languageName: node linkType: hard -"listr2@npm:^8.2.5": - version: 8.2.5 - resolution: "listr2@npm:8.2.5" +"listr2@npm:^9.0.5": + version: 9.0.5 + resolution: "listr2@npm:9.0.5" dependencies: - cli-truncate: "npm:^4.0.0" + cli-truncate: "npm:^5.0.0" colorette: "npm:^2.0.20" eventemitter3: "npm:^5.0.1" log-update: "npm:^6.1.0" rfdc: "npm:^1.4.1" wrap-ansi: "npm:^9.0.0" - checksum: 10/c76542f18306195e464fe10203ee679a7beafa9bf0dc679ebacb416387cca8f5307c1d8ba35483d26ba611dc2fac5a1529733dce28f2660556082fb7eebb79f9 + checksum: 10/b78ffd60443aed9a8e0fc9162eb941ea4d63210700d61a895eb29348f23fc668327e26bbca87a9e6a6208e7fa96c475fe1f1c6c19b46f376f547e0cba3b935ae languageName: node linkType: hard -"load-esm@npm:1.0.2": - version: 1.0.2 - resolution: "load-esm@npm:1.0.2" - checksum: 10/1b4adb40c28c6fdbd4ca8c97942c04debddb3c93ae91413540ff5a21ca3511a651988c835cb80cad7288d1ecb869c4794b8a787ab02e09cc07ec951ad1eefcf9 +"load-esm@npm:1.0.3": + version: 1.0.3 + resolution: "load-esm@npm:1.0.3" + checksum: 10/6949e8c253dddccca2a0ded1e9e0bbbc81924439d99e3a7d0f946ba6cdcd16de22c3cef28d997fd950befda0826ca65c3f913d9ba893d50e9cfc0bbd9a2a1e90 languageName: node linkType: hard @@ -36769,10 +35173,10 @@ __metadata: languageName: node linkType: hard -"loader-runner@npm:^4.2.0": - version: 4.2.0 - resolution: "loader-runner@npm:4.2.0" - checksum: 10/89a648e0418f23edf2f310bf74a8adb0710548e8d8d47040def081e1b822bdc27b664b796ce43ceb7921fa56485e1f5046417e425714730dc6ea4242e7a176fa +"loader-runner@npm:^4.3.1": + version: 4.3.1 + resolution: "loader-runner@npm:4.3.1" + checksum: 10/d77127497c3f91fdba351e3e91156034e6e590e9f050b40df6c38ac16c54b5c903f7e2e141e09fefd046ee96b26fb50773c695ebc0aa205a4918683b124b04ba languageName: node linkType: hard @@ -36842,19 +35246,10 @@ __metadata: languageName: node linkType: hard -"locate-path@npm:^7.2.0": - version: 7.2.0 - resolution: "locate-path@npm:7.2.0" - dependencies: - p-locate: "npm:^6.0.0" - checksum: 10/1c6d269d4efec555937081be964e8a9b4a136319c79ca1d45ac6382212a8466113c75bd89e44521ca8ecd1c47fb08523b56eee5c0712bc7d14fec5f729deeb42 - languageName: node - linkType: hard - "lodash-es@npm:^4.17.21": - version: 4.17.21 - resolution: "lodash-es@npm:4.17.21" - checksum: 10/03f39878ea1e42b3199bd3f478150ab723f93cc8730ad86fec1f2804f4a07c6e30deaac73cad53a88e9c3db33348bb8ceeb274552390e7a75d7849021c02df43 + version: 4.18.1 + resolution: "lodash-es@npm:4.18.1" + checksum: 10/8bfad225ef09ef42b04283cdaf7830efcc2ba29ae41b56501c74422155ee1ccaa1f0f6e8319def3451a1fe54dec501c8e4bee622bae2b2d98ac993731e0a5cce languageName: node linkType: hard @@ -36886,13 +35281,6 @@ __metadata: languageName: node linkType: hard -"lodash.clonedeepwith@npm:4.5.0": - version: 4.5.0 - resolution: "lodash.clonedeepwith@npm:4.5.0" - checksum: 10/4bc1db18374addc891cab4c0b5d263f60801a6339831289f5745d9dc8b6d9487f82c4413a08de944d3d6bef3bf19e19c03d56bbe4d95e137916fa114577864ff - languageName: node - linkType: hard - "lodash.constant@npm:^3.0.0": version: 3.0.0 resolution: "lodash.constant@npm:3.0.0" @@ -36977,20 +35365,6 @@ __metadata: languageName: node linkType: hard -"lodash.hasin@npm:^4.5.2": - version: 4.5.2 - resolution: "lodash.hasin@npm:4.5.2" - checksum: 10/d491be2eb53bcd004ac7eeefdc072fe6b76c701088bf95e8ca406a0b00e45739507ca6ffbce3829bdc9bc13e034877a2bbc08229e3f24d4095c3aa75f927d96a - languageName: node - linkType: hard - -"lodash.head@npm:^4.0.1": - version: 4.0.1 - resolution: "lodash.head@npm:4.0.1" - checksum: 10/37666c4f96ad907025754a0e76db49adab148bd2d1a1f4597c4bf7a006d4daad802453e162dffe08d2a488c1283a29f5366c1fc3a1bd2300423c32b0ecb26156 - languageName: node - linkType: hard - "lodash.isarguments@npm:^3.1.0": version: 3.1.0 resolution: "lodash.isarguments@npm:3.1.0" @@ -37019,20 +35393,6 @@ __metadata: languageName: node linkType: hard -"lodash.isnil@npm:^4.0.0": - version: 4.0.0 - resolution: "lodash.isnil@npm:4.0.0" - checksum: 10/ebf8df69879badd6ad99c4f64c54c470248df5cf92b208ca730861b1d8ac058da7b632ac811d18b0929d93cbac8d8fc866e781ee816b0142c56952e85edc682f - languageName: node - linkType: hard - -"lodash.isobject@npm:^3.0.2": - version: 3.0.2 - resolution: "lodash.isobject@npm:3.0.2" - checksum: 10/6c1667cbc4494d0a13a3617a4b23278d6d02dac520311f2bbb43f16f2cf71d2e6eb9dec8057315b77459df4890c756a256a087d3f4baa44a79ab5d6c968b060e - languageName: node - linkType: hard - "lodash.isstring@npm:^4.0.1": version: 4.0.1 resolution: "lodash.isstring@npm:4.0.1" @@ -37166,13 +35526,27 @@ __metadata: languageName: node linkType: hard -"lodash@npm:4.17.21, lodash@npm:^4.15.0, lodash@npm:^4.16.4, lodash@npm:^4.17.10, lodash@npm:^4.17.11, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.17.4, lodash@npm:~4.17.15, lodash@npm:~4.17.21": +"lodash@npm:4.17.21": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: 10/c08619c038846ea6ac754abd6dd29d2568aa705feb69339e836dfa8d8b09abbb2f859371e86863eda41848221f9af43714491467b5b0299122431e202bb0c532 languageName: node linkType: hard +"lodash@npm:^4.15.0, lodash@npm:^4.16.4, lodash@npm:^4.17.10, lodash@npm:^4.17.11, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.17.4": + version: 4.18.1 + resolution: "lodash@npm:4.18.1" + checksum: 10/306fea53dfd39dad1f03d45ba654a2405aebd35797b673077f401edb7df2543623dc44b9effbb98f69b32152295fff725a4cec99c684098947430600c6af0c3f + languageName: node + linkType: hard + +"lodash@npm:~4.17.21, lodash@npm:~4.17.23": + version: 4.17.23 + resolution: "lodash@npm:4.17.23" + checksum: 10/82504c88250f58da7a5a4289f57a4f759c44946c005dd232821c7688b5fcfbf4a6268f6a6cdde4b792c91edd2f3b5398c1d2a0998274432cff76def48735e233 + languageName: node + linkType: hard + "log-node@npm:^8.0.3": version: 8.0.3 resolution: "log-node@npm:8.0.3" @@ -37214,7 +35588,7 @@ __metadata: languageName: node linkType: hard -"log4js@npm:6.9.1, log4js@npm:^6.4.6": +"log4js@npm:^6.4.6": version: 6.9.1 resolution: "log4js@npm:6.9.1" dependencies: @@ -37242,9 +35616,9 @@ __metadata: languageName: node linkType: hard -"logform@npm:^2.3.2, logform@npm:^2.6.0, logform@npm:^2.6.1": - version: 2.6.1 - resolution: "logform@npm:2.6.1" +"logform@npm:^2.3.2, logform@npm:^2.7.0": + version: 2.7.0 + resolution: "logform@npm:2.7.0" dependencies: "@colors/colors": "npm:1.6.0" "@types/triple-beam": "npm:^1.3.2" @@ -37252,7 +35626,7 @@ __metadata: ms: "npm:^2.1.1" safe-stable-stringify: "npm:^2.3.1" triple-beam: "npm:^1.3.0" - checksum: 10/e67f414787fbfe1e6a997f4c84300c7e06bee3d0bd579778af667e24b36db3ea200ed195d41b61311ff738dab7faabc615a07b174b22fe69e0b2f39e985be64b + checksum: 10/4b861bfd67efe599ab41113ae3ffe92b1873bf86793fb442f58971852430d8f416f9904da69e5043071fb3725690e2499a13acbfe92a57ba7d21690004f9edc0 languageName: node linkType: hard @@ -37270,10 +35644,10 @@ __metadata: languageName: node linkType: hard -"long@npm:^5.0.0, long@npm:^5.2.1": - version: 5.2.3 - resolution: "long@npm:5.2.3" - checksum: 10/9167ec6947a825b827c30da169a7384eec6c0c9ec2f0b9c74da2e93d81159bbe39fb09c3f13dae9721d4b807ccfa09797a7dd1012f5d478e3e33ca3c78b608e6 +"long@npm:^5.0.0, long@npm:^5.3.2": + version: 5.3.2 + resolution: "long@npm:5.3.2" + checksum: 10/b6b55ddae56fcce2864d37119d6b02fe28f6dd6d9e44fd22705f86a9254b9321bd69e9ffe35263b4846d54aba197c64882adcb8c543f2383c1e41284b321ea64 languageName: node linkType: hard @@ -37342,17 +35716,17 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^10.0.0, lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": +"lru-cache@npm:^10.0.0, lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0, lru-cache@npm:^10.4.3": version: 10.4.3 resolution: "lru-cache@npm:10.4.3" checksum: 10/e6e90267360476720fa8e83cc168aa2bf0311f3f2eea20a6ba78b90a885ae72071d9db132f40fda4129c803e7dcec3a6b6a6fbb44ca90b081630b810b5d6a41a languageName: node linkType: hard -"lru-cache@npm:^11.0.0": - version: 11.0.2 - resolution: "lru-cache@npm:11.0.2" - checksum: 10/25fcb66e9d91eaf17227c6abfe526a7bed5903de74f93bfde380eb8a13410c5e8d3f14fe447293f3f322a7493adf6f9f015c6f1df7a235ff24ec30f366e1c058 +"lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1, lru-cache@npm:^11.2.2, lru-cache@npm:^11.2.4": + version: 11.2.6 + resolution: "lru-cache@npm:11.2.6" + checksum: 10/91222bbd59f793a0a0ad57789388f06b34ac9bb1613433c1d1810457d09db5cd3ec8943227ce2e1f5d6a0a15d6f1a9f129cb2c49ae9b6b10e82d4965fddecbef languageName: node linkType: hard @@ -37397,10 +35771,10 @@ __metadata: languageName: node linkType: hard -"lru.min@npm:^1.0.0": - version: 1.1.0 - resolution: "lru.min@npm:1.1.0" - checksum: 10/af2f74d6434634e229599116040fc72e4ea42b973de45ad87bf061786894b818f3c3cc3e70e6e0cc55e2df205a68bd060407281e79dfc437a0211320e840dd61 +"lru.min@npm:^1.1.0, lru.min@npm:^1.1.4": + version: 1.1.4 + resolution: "lru.min@npm:1.1.4" + checksum: 10/c7041fb558fa0c39b3e14b8c711716af729a4df96af25e6a2d54f5d60d936a1e33edbabcfa3a71359f73f746763dc51da3fc679d60e2f087aa722f139f56ccd4 languageName: node linkType: hard @@ -37411,7 +35785,14 @@ __metadata: languageName: node linkType: hard -"luxon@npm:^3.0.0, luxon@npm:^3.2.1, luxon@npm:^3.4.3, luxon@npm:^3.5.0, luxon@npm:~3.5.0": +"luxon@npm:^3.0.0, luxon@npm:^3.2.1, luxon@npm:^3.4.3, luxon@npm:^3.5.0": + version: 3.7.2 + resolution: "luxon@npm:3.7.2" + checksum: 10/b24cd205ed306ce7415991687897dcc4027921ae413c9116590bc33a95f93b86ce52cf74ba72b4f5c5ab1c10090517f54ac8edfb127c049e0bf55b90dc2260be + languageName: node + linkType: hard + +"luxon@npm:~3.5.0": version: 3.5.0 resolution: "luxon@npm:3.5.0" checksum: 10/48f86e6c1c96815139f8559456a3354a276ba79bcef0ae0d4f2172f7652f3ba2be2237b0e103b8ea0b79b47715354ac9fac04eb1db3485dcc72d5110491dd47f @@ -37463,12 +35844,12 @@ __metadata: languageName: node linkType: hard -"magic-string@npm:^0.30.0, magic-string@npm:^0.30.10, magic-string@npm:^0.30.17, magic-string@npm:^0.30.3": - version: 0.30.17 - resolution: "magic-string@npm:0.30.17" +"magic-string@npm:^0.30.0, magic-string@npm:^0.30.17, magic-string@npm:^0.30.21, magic-string@npm:^0.30.3": + version: 0.30.21 + resolution: "magic-string@npm:0.30.21" dependencies: - "@jridgewell/sourcemap-codec": "npm:^1.5.0" - checksum: 10/2f71af2b0afd78c2e9012a29b066d2c8ba45a9cd0c8070f7fd72de982fb1c403b4e3afdb1dae00691d56885ede66b772ef6bedf765e02e3a7066208fe2fec4aa + "@jridgewell/sourcemap-codec": "npm:^1.5.5" + checksum: 10/57d5691f41ed40d962d8bd300148114f53db67fadbff336207db10a99f2bdf4a1be9cac3a68ee85dba575912ee1d4402e4396408196ec2d3afd043b076156221 languageName: node linkType: hard @@ -37522,25 +35903,6 @@ __metadata: languageName: node linkType: hard -"make-fetch-happen@npm:^13.0.0": - version: 13.0.0 - resolution: "make-fetch-happen@npm:13.0.0" - dependencies: - "@npmcli/agent": "npm:^2.0.0" - cacache: "npm:^18.0.0" - http-cache-semantics: "npm:^4.1.1" - is-lambda: "npm:^1.0.1" - minipass: "npm:^7.0.2" - minipass-fetch: "npm:^3.0.0" - minipass-flush: "npm:^1.0.5" - minipass-pipeline: "npm:^1.2.4" - negotiator: "npm:^0.6.3" - promise-retry: "npm:^2.0.1" - ssri: "npm:^10.0.0" - checksum: 10/ded5a91a02b76381b06a4ec4d5c1d23ebbde15d402b3c3e4533b371dac7e2f7ca071ae71ae6dae72aa261182557b7b1b3fd3a705b39252dc17f74fa509d3e76f - languageName: node - linkType: hard - "make-fetch-happen@npm:^14.0.1, make-fetch-happen@npm:^14.0.2, make-fetch-happen@npm:^14.0.3": version: 14.0.3 resolution: "make-fetch-happen@npm:14.0.3" @@ -37560,6 +35922,25 @@ __metadata: languageName: node linkType: hard +"make-fetch-happen@npm:^15.0.0": + version: 15.0.4 + resolution: "make-fetch-happen@npm:15.0.4" + dependencies: + "@gar/promise-retry": "npm:^1.0.0" + "@npmcli/agent": "npm:^4.0.0" + cacache: "npm:^20.0.1" + http-cache-semantics: "npm:^4.1.1" + minipass: "npm:^7.0.2" + minipass-fetch: "npm:^5.0.0" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + negotiator: "npm:^1.0.0" + proc-log: "npm:^6.0.0" + ssri: "npm:^13.0.0" + checksum: 10/4aa75baab500eff4259f2e1a3e76cf01ab3a3cd750037e4bd7b5e22bc5a60f12cc766b3c45e6288accb5ab609e88de5019a8014e0f96f6594b7b03cb504f4b81 + languageName: node + linkType: hard + "make-fetch-happen@npm:^9.1.0": version: 9.1.0 resolution: "make-fetch-happen@npm:9.1.0" @@ -37584,6 +35965,13 @@ __metadata: languageName: node linkType: hard +"make-synchronized@npm:^0.8.0": + version: 0.8.0 + resolution: "make-synchronized@npm:0.8.0" + checksum: 10/e744bafcd61ee1ecabe6fb2c295ecb4b06a7bfe4e844222b80b7a5ae80a4d27ba657abc4892d1c702fa2f6ae568d8505e801c1498fe1379dd824ded5483d978c + languageName: node + linkType: hard + "makeerror@npm:1.0.12": version: 1.0.12 resolution: "makeerror@npm:1.0.12" @@ -37609,24 +35997,9 @@ __metadata: languageName: node linkType: hard -"markdown-it@npm:^12.2.0": - version: 12.3.2 - resolution: "markdown-it@npm:12.3.2" - dependencies: - argparse: "npm:^2.0.1" - entities: "npm:~2.1.0" - linkify-it: "npm:^3.0.1" - mdurl: "npm:^1.0.1" - uc.micro: "npm:^1.0.5" - bin: - markdown-it: bin/markdown-it.js - checksum: 10/d83d794bfb9f5e05750b25db401d9c1f9b97c6bbabb6cfd78988bb98652c62c24417435487238e2b91fd4e495547ae8c9429fb4c69e9f5bf49bd0dd292d53f24 - languageName: node - linkType: hard - -"markdown-it@npm:^14.1.0": - version: 14.1.0 - resolution: "markdown-it@npm:14.1.0" +"markdown-it@npm:^14.1.0, markdown-it@npm:^14.1.1": + version: 14.1.1 + resolution: "markdown-it@npm:14.1.1" dependencies: argparse: "npm:^2.0.1" entities: "npm:^4.4.0" @@ -37636,7 +36009,7 @@ __metadata: uc.micro: "npm:^2.1.0" bin: markdown-it: bin/markdown-it.mjs - checksum: 10/f34f921be178ed0607ba9e3e27c733642be445e9bb6b1dba88da7aafe8ba1bc5d2f1c3aa8f3fc33b49a902da4e4c08c2feadfafb290b8c7dda766208bb6483a9 + checksum: 10/088822c8aa9346ba4af6a205f6ee0f4baae55e3314f040dc5c28c897d57d0f979840c71872b3582a6a6e572d8c851c54e323c82f4559011dfa2e96224fc20fc2 languageName: node linkType: hard @@ -37656,6 +36029,32 @@ __metadata: languageName: node linkType: hard +"marked-terminal@npm:^7.3.0": + version: 7.3.0 + resolution: "marked-terminal@npm:7.3.0" + dependencies: + ansi-escapes: "npm:^7.0.0" + ansi-regex: "npm:^6.1.0" + chalk: "npm:^5.4.1" + cli-highlight: "npm:^2.1.11" + cli-table3: "npm:^0.6.5" + node-emoji: "npm:^2.2.0" + supports-hyperlinks: "npm:^3.1.0" + peerDependencies: + marked: ">=1 <16" + checksum: 10/1dfdfe752a4ebe6aec8de4a51180612a5f29982026b104a86215efb46b82b2a1942531a6bb840163c8d827e3eadc5cf93272e6eb29ec549f72b73b8b2eb97cfe + languageName: node + linkType: hard + +"marked@npm:^15.0.12": + version: 15.0.12 + resolution: "marked@npm:15.0.12" + bin: + marked: bin/marked.js + checksum: 10/deeb619405c0c46af00c99b18b3365450abeb309104b24e3658f46142344f6b7c4117608c3b5834084d8738e92f81240c19f596e6ee369260f96e52b3457eaee + languageName: node + linkType: hard + "marked@npm:^4.0.14": version: 4.3.0 resolution: "marked@npm:4.3.0" @@ -37683,17 +36082,6 @@ __metadata: languageName: node linkType: hard -"material-ui-confirm@npm:^3.0.12": - version: 3.0.18 - resolution: "material-ui-confirm@npm:3.0.18" - peerDependencies: - "@mui/material": ">= 5.0.0" - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 - checksum: 10/c2013449b9af11bed9fbf63cb15022d8399379547fedc8ce9f9c4039606536b34cce8b737b82b9628df95291d7b631dba90dd59c6ccbc7c17f9b8dcab50fdccf - languageName: node - linkType: hard - "material-ui-popup-state@npm:^5.3.6": version: 5.3.6 resolution: "material-ui-popup-state@npm:5.3.6" @@ -37886,7 +36274,14 @@ __metadata: languageName: node linkType: hard -"mdurl@npm:^1.0.0, mdurl@npm:^1.0.1": +"mdn-data@npm:2.12.2": + version: 2.12.2 + resolution: "mdn-data@npm:2.12.2" + checksum: 10/854e41715a9358e69f9a530117cd6ca7e71d06176469de8d70b1e629753b6827f5bd730995c16ad3750f3c9bad92230f8e4e178de2b34926b05f5205d27d76af + languageName: node + linkType: hard + +"mdurl@npm:^1.0.0": version: 1.0.1 resolution: "mdurl@npm:1.0.1" checksum: 10/ada367d01c9e81d07328101f187d5bd8641b71f33eab075df4caed935a24fa679e625f07108801d8250a5e4a99e5cd4be7679957a11424a3aa3e740d2bb2d5cb @@ -37968,15 +36363,27 @@ __metadata: languageName: node linkType: hard -"memfs@npm:^4.28.0, memfs@npm:^4.6.0": - version: 4.36.0 - resolution: "memfs@npm:4.36.0" +"memfs@npm:^4.56.10, memfs@npm:^4.6.0": + version: 4.56.11 + resolution: "memfs@npm:4.56.11" dependencies: - "@jsonjoy.com/json-pack": "npm:^1.0.3" - "@jsonjoy.com/util": "npm:^1.3.0" - tree-dump: "npm:^1.0.1" + "@jsonjoy.com/fs-core": "npm:4.56.11" + "@jsonjoy.com/fs-fsa": "npm:4.56.11" + "@jsonjoy.com/fs-node": "npm:4.56.11" + "@jsonjoy.com/fs-node-builtins": "npm:4.56.11" + "@jsonjoy.com/fs-node-to-fsa": "npm:4.56.11" + "@jsonjoy.com/fs-node-utils": "npm:4.56.11" + "@jsonjoy.com/fs-print": "npm:4.56.11" + "@jsonjoy.com/fs-snapshot": "npm:4.56.11" + "@jsonjoy.com/json-pack": "npm:^1.11.0" + "@jsonjoy.com/util": "npm:^1.9.0" + glob-to-regex.js: "npm:^1.0.1" + thingies: "npm:^2.5.0" + tree-dump: "npm:^1.0.3" tslib: "npm:^2.0.0" - checksum: 10/4898187c3278bd127340ec3d6c51d1fa77faa50aad71a8a1e4b7c02abbccd8485dccc7f565291b386b0cc96e5ac06902e4645108fad44e458fe8c1dfee3caf42 + peerDependencies: + tslib: 2 + checksum: 10/2d99bce59dee0f5c96a039851892e39c09c1eee11d8978cf37dbb93462d706d842d6a5b0b3d5081d47f19883ed4b6d973c8086301a9155b098b80d66c3e79cb7 languageName: node linkType: hard @@ -38038,15 +36445,15 @@ __metadata: languageName: node linkType: hard -"meros@npm:^1.1.4, meros@npm:^1.2.1": - version: 1.3.0 - resolution: "meros@npm:1.3.0" +"meros@npm:^1.1.4, meros@npm:^1.3.2": + version: 1.3.2 + resolution: "meros@npm:1.3.2" peerDependencies: "@types/node": ">=13" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/1893d226866058a32161ab069294a1a16975c765416a2b05165dfafba07cd958ca12503e35c621ffe736c62d935ccb1ce60cb723e2a9e0b85e02bb3236722ef6 + checksum: 10/9269b243f91b714b75169f63231af81bcd4c049c1308f6e78fc08214af89323ce2e36a7c1603cde6f32cf4ba79075365335c0d6b549e997b53124568f213f0a5 languageName: node linkType: hard @@ -38065,8 +36472,8 @@ __metadata: linkType: hard "micromark-core-commonmark@npm:^1.0.0, micromark-core-commonmark@npm:^1.0.1": - version: 1.0.6 - resolution: "micromark-core-commonmark@npm:1.0.6" + version: 1.1.0 + resolution: "micromark-core-commonmark@npm:1.1.0" dependencies: decode-named-character-reference: "npm:^1.0.0" micromark-factory-destination: "npm:^1.0.0" @@ -38084,25 +36491,25 @@ __metadata: micromark-util-symbol: "npm:^1.0.0" micromark-util-types: "npm:^1.0.1" uvu: "npm:^0.5.0" - checksum: 10/20daa4b78b88afea7658c2bd428c830734c72fbb2184c1f0761bb4c1e5fcf266509e7d46ad5f7b2a2aeb32cd17951788733cad458632457b52397534d930030a + checksum: 10/a73694d223ac8baad8ff00597a3c39d61f5b32bfd56fe4bcf295d75b2a4e8e67fb2edbfc7cc287b362b9d7f6d24fce08b6a7e8b5b155d79bcc1e4d9b2756ffb2 languageName: node linkType: hard "micromark-extension-gfm-autolink-literal@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-extension-gfm-autolink-literal@npm:1.0.0" + version: 1.0.5 + resolution: "micromark-extension-gfm-autolink-literal@npm:1.0.5" dependencies: micromark-util-character: "npm:^1.0.0" micromark-util-sanitize-uri: "npm:^1.0.0" micromark-util-symbol: "npm:^1.0.0" micromark-util-types: "npm:^1.0.0" - checksum: 10/4bb5841980725bbbe59b311b5b5efbab74b363da00669b96e7744ea76532d972720d1ce92a024baeafbac2c92dbf90b5595a18212415a59ced7610202304ce13 + checksum: 10/1e0ccc758baef3cd0478ba84ff86fa1ec2b389042421c7cade9485b775456c1a9c3bd797393002b2c6f6abd9bdf829cb114874557bbcb8e43d16d06a464811c0 languageName: node linkType: hard "micromark-extension-gfm-footnote@npm:^1.0.0": - version: 1.0.3 - resolution: "micromark-extension-gfm-footnote@npm:1.0.3" + version: 1.1.2 + resolution: "micromark-extension-gfm-footnote@npm:1.1.2" dependencies: micromark-core-commonmark: "npm:^1.0.0" micromark-factory-space: "npm:^1.0.0" @@ -38110,60 +36517,64 @@ __metadata: micromark-util-normalize-identifier: "npm:^1.0.0" micromark-util-sanitize-uri: "npm:^1.0.0" micromark-util-symbol: "npm:^1.0.0" + micromark-util-types: "npm:^1.0.0" uvu: "npm:^0.5.0" - checksum: 10/1e8920582a2d365eebe48239daaeade3b88d7f6b800f1dd7e7b0d6add730105048d7d1fd68138955b397d572d410c280345560e407328c07fa334e17a965a578 + checksum: 10/8777073fb76d2fd01f6b2405106af6c349c1e25660c4d37cadcc61c187d71c8444870f73cefaaa67f12884d5e45c78ee3c5583561a0b330bd91c6d997113584a languageName: node linkType: hard "micromark-extension-gfm-strikethrough@npm:^1.0.0": - version: 1.0.1 - resolution: "micromark-extension-gfm-strikethrough@npm:1.0.1" + version: 1.0.7 + resolution: "micromark-extension-gfm-strikethrough@npm:1.0.7" dependencies: micromark-util-chunked: "npm:^1.0.0" micromark-util-classify-character: "npm:^1.0.0" micromark-util-resolve-all: "npm:^1.0.0" micromark-util-symbol: "npm:^1.0.0" micromark-util-types: "npm:^1.0.0" - checksum: 10/720aa4e47ac4701faf5640be40a97a249b880d5f8f2787106a0d12bd11dd8ab9e29a513af738743be07d42a02734e024184114b719435160e012c4ba9f7464bd + uvu: "npm:^0.5.0" + checksum: 10/8411ef1aa5dc83f662e8b45b085f70ddff29deb3c4259269e8a1ff656397abb755d8ea841a14be23e8585a31d3c0a5de1bd2c05f3453b66670e499d4a0004f5e languageName: node linkType: hard "micromark-extension-gfm-table@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-extension-gfm-table@npm:1.0.0" + version: 1.0.7 + resolution: "micromark-extension-gfm-table@npm:1.0.7" dependencies: micromark-factory-space: "npm:^1.0.0" micromark-util-character: "npm:^1.0.0" micromark-util-symbol: "npm:^1.0.0" micromark-util-types: "npm:^1.0.0" - checksum: 10/220e2d5c66e4dcd8565cc067d584136fc91dbf249bcee69c6a6cb5656b536144d29734d922602bdfd82242dad2a3d128b25f57e93c50091fd96d7071618eedd9 + uvu: "npm:^0.5.0" + checksum: 10/f05d86a099c941a2a309d60bf4839d16a00a93cb880cda4ab8faeb831647763fff6e03197ec15b80e1f195002afcca6afe2b95c3622b049b82d7ff8ef1c1c776 languageName: node linkType: hard "micromark-extension-gfm-tagfilter@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-extension-gfm-tagfilter@npm:1.0.0" + version: 1.0.2 + resolution: "micromark-extension-gfm-tagfilter@npm:1.0.2" dependencies: micromark-util-types: "npm:^1.0.0" - checksum: 10/2950168c4329486da0d411afebae47601dbbdb8ba51892d88ef451dd62e4d412afaba267f1e4a1d0c93b1b25723cfcdcbb013c458f652e8ab2a1046dd1b5c352 + checksum: 10/55c7d9019d6a39efaaed2c2e40b0aaa137d2c4f9c94cac82e93f509a806c3a775e4c815b5d8e986617450b68861a19776e4b886307e83db452b393f15a837b39 languageName: node linkType: hard "micromark-extension-gfm-task-list-item@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-extension-gfm-task-list-item@npm:1.0.0" + version: 1.0.5 + resolution: "micromark-extension-gfm-task-list-item@npm:1.0.5" dependencies: micromark-factory-space: "npm:^1.0.0" micromark-util-character: "npm:^1.0.0" micromark-util-symbol: "npm:^1.0.0" micromark-util-types: "npm:^1.0.0" - checksum: 10/a8bc41ce4c5599cde45804737381ca5136b43c64c05034e396e476958232916d3bc23e585d3f71c59f03666c568bced41f177a76497267259b813ab4987dd3a5 + uvu: "npm:^0.5.0" + checksum: 10/46bb1baa10bfb785a2e3e2f975e5509260b9995d5c3aeddf77051957d218ce1af4ea737bcb6a56a930e62d42b05307b20632a400eff25cdb290789ff3170cad5 languageName: node linkType: hard "micromark-extension-gfm@npm:^2.0.0": - version: 2.0.1 - resolution: "micromark-extension-gfm@npm:2.0.1" + version: 2.0.3 + resolution: "micromark-extension-gfm@npm:2.0.3" dependencies: micromark-extension-gfm-autolink-literal: "npm:^1.0.0" micromark-extension-gfm-footnote: "npm:^1.0.0" @@ -38173,200 +36584,204 @@ __metadata: micromark-extension-gfm-task-list-item: "npm:^1.0.0" micromark-util-combine-extensions: "npm:^1.0.0" micromark-util-types: "npm:^1.0.0" - checksum: 10/701d065102685a338c7eb87520587575dffbc240639a379c64f83971b3ffbeaf0165aa43ce72b0255856e991b395a5fc71dc436ec76d723f462a39ec16eade5e + checksum: 10/3ffd06ced4314abd0f0c72ec227f034f38dd47facbb62439ef3216d42f32433f3901d14675cf806e8d73689802a11849958b330bb5b55dd4fd5cdc64ebaf345c languageName: node linkType: hard "micromark-factory-destination@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-factory-destination@npm:1.0.0" + version: 1.1.0 + resolution: "micromark-factory-destination@npm:1.1.0" dependencies: micromark-util-character: "npm:^1.0.0" micromark-util-symbol: "npm:^1.0.0" micromark-util-types: "npm:^1.0.0" - checksum: 10/8e733ae9c1c2342f14ff290bf09946e20f6f540117d80342377a765cac48df2ea5e748f33c8b07501ad7a43414b1a6597c8510ede2052b6bf1251fab89748e20 + checksum: 10/9e2b5fb5fedbf622b687e20d51eb3d56ae90c0e7ecc19b37bd5285ec392c1e56f6e21aa7cfcb3c01eda88df88fe528f3acb91a5f57d7f4cba310bc3cd7f824fa languageName: node linkType: hard "micromark-factory-label@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-factory-label@npm:1.0.0" + version: 1.1.0 + resolution: "micromark-factory-label@npm:1.1.0" dependencies: micromark-util-character: "npm:^1.0.0" micromark-util-symbol: "npm:^1.0.0" micromark-util-types: "npm:^1.0.0" - checksum: 10/37f34d8e42e3a606a636419ffe0f99fd7f6054778365aac56d1cc11585d1cd46000dbf938319abb2ea5e245803679d3cfa46afb5aecf2702bfd963113481f14c + uvu: "npm:^0.5.0" + checksum: 10/fcda48f1287d9b148c562c627418a2ab759cdeae9c8e017910a0cba94bb759a96611e1fc6df33182e97d28fbf191475237298983bb89ef07d5b02464b1ad28d5 languageName: node linkType: hard "micromark-factory-space@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-factory-space@npm:1.0.0" + version: 1.1.0 + resolution: "micromark-factory-space@npm:1.1.0" dependencies: micromark-util-character: "npm:^1.0.0" micromark-util-types: "npm:^1.0.0" - checksum: 10/70d3aafde4e68ef4e509a3b644e9a29e4aada00801279e346577b008cbca06d78051bcd62aa7ea7425856ed73f09abd2b36607803055f726f52607ee7cb706b0 + checksum: 10/b58435076b998a7e244259a4694eb83c78915581206b6e7fc07b34c6abd36a1726ade63df8972fbf6c8fa38eecb9074f4e17be8d53f942e3b3d23d1a0ecaa941 languageName: node linkType: hard "micromark-factory-title@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-factory-title@npm:1.0.0" + version: 1.1.0 + resolution: "micromark-factory-title@npm:1.1.0" dependencies: micromark-factory-space: "npm:^1.0.0" micromark-util-character: "npm:^1.0.0" micromark-util-symbol: "npm:^1.0.0" micromark-util-types: "npm:^1.0.0" - checksum: 10/d9bf0779c49f013fea5fc5daadcee91e1703c81c1b5091337e806c24f1ea224d69f534d174aba2eccff7826620af71bb3db83b1caf14cbb89bea53f4e8e38bc3 + checksum: 10/4432d3dbc828c81f483c5901b0c6591a85d65a9e33f7d96ba7c3ae821617a0b3237ff5faf53a9152d00aaf9afb3a9f185b205590f40ed754f1d9232e0e9157b1 languageName: node linkType: hard "micromark-factory-whitespace@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-factory-whitespace@npm:1.0.0" + version: 1.1.0 + resolution: "micromark-factory-whitespace@npm:1.1.0" dependencies: micromark-factory-space: "npm:^1.0.0" micromark-util-character: "npm:^1.0.0" micromark-util-symbol: "npm:^1.0.0" micromark-util-types: "npm:^1.0.0" - checksum: 10/0888386e6ea2dd665a5182c570d9b3d0a172d3f11694ca5a2a84e552149c9f1429f5b975ec26e1f0fa4388c55a656c9f359ce5e0603aff6175ba3e255076f20b + checksum: 10/ef0fa682c7d593d85a514ee329809dee27d10bc2a2b65217d8ef81173e33b8e83c549049764b1ad851adfe0a204dec5450d9d20a4ca8598f6c94533a73f73fcd languageName: node linkType: hard "micromark-util-character@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-util-character@npm:1.1.0" + version: 1.2.0 + resolution: "micromark-util-character@npm:1.2.0" dependencies: micromark-util-symbol: "npm:^1.0.0" micromark-util-types: "npm:^1.0.0" - checksum: 10/81a1e4ee996e89966f58620088ca1ad49a6b1474fa488992be9b6f62d783d621c33f74c01f8560a2960412a43e83c7d991c711620ff3ee49169eb77de0bb2e3a + checksum: 10/88cf80f9b4c95266f24814ef587fb4180454668dcc3be4ac829e1227188cf349c8981bfca29e3eab1682f324c2c47544c0b0b799a26fbf9df5f156c6a84c970c languageName: node linkType: hard "micromark-util-chunked@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-util-chunked@npm:1.0.0" + version: 1.1.0 + resolution: "micromark-util-chunked@npm:1.1.0" dependencies: micromark-util-symbol: "npm:^1.0.0" - checksum: 10/c1efd56e8c4217bcf1c6f1a9fb9912b4a2a5503b00d031da902be922fb3fee60409ac53f11739991291357b2784fb0647ddfc74c94753a068646c0cb0fd71421 + checksum: 10/c435bde9110cb595e3c61b7f54c2dc28ee03e6a57fa0fc1e67e498ad8bac61ee5a7457a2b6a73022ddc585676ede4b912d28dcf57eb3bd6951e54015e14dc20b languageName: node linkType: hard "micromark-util-classify-character@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-util-classify-character@npm:1.0.0" + version: 1.1.0 + resolution: "micromark-util-classify-character@npm:1.1.0" dependencies: micromark-util-character: "npm:^1.0.0" micromark-util-symbol: "npm:^1.0.0" micromark-util-types: "npm:^1.0.0" - checksum: 10/180446e6a1dec653f625ded028f244784e1db8d10ad05c5d70f08af9de393b4a03dc6cf6fa5ed8ccc9c24bbece7837abf3bf66681c0b4adf159364b7d5236dfd + checksum: 10/8499cb0bb1f7fb946f5896285fcca65cd742f66cd3e79ba7744792bd413ec46834f932a286de650349914d02e822946df3b55d03e6a8e1d245d1ddbd5102e5b0 languageName: node linkType: hard "micromark-util-combine-extensions@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-util-combine-extensions@npm:1.0.0" + version: 1.1.0 + resolution: "micromark-util-combine-extensions@npm:1.1.0" dependencies: micromark-util-chunked: "npm:^1.0.0" micromark-util-types: "npm:^1.0.0" - checksum: 10/5304a820ef75340e1be69d6ad167055b6ba9a3bafe8171e5945a935752f462415a9dd61eb3490220c055a8a11167209a45bfa73f278338b7d3d61fa1464d3f35 + checksum: 10/ee78464f5d4b61ccb437850cd2d7da4d690b260bca4ca7a79c4bb70291b84f83988159e373b167181b6716cb197e309bc6e6c96a68cc3ba9d50c13652774aba9 languageName: node linkType: hard "micromark-util-decode-numeric-character-reference@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-util-decode-numeric-character-reference@npm:1.0.0" + version: 1.1.0 + resolution: "micromark-util-decode-numeric-character-reference@npm:1.1.0" dependencies: micromark-util-symbol: "npm:^1.0.0" - checksum: 10/f3ae2bb582a80f1e9d3face026f585c0c472335c064bd850bde152376f0394cb2831746749b6be6e0160f7d73626f67d10716026c04c87f402c0dd45a1a28633 + checksum: 10/4733fe75146e37611243f055fc6847137b66f0cde74d080e33bd26d0408c1d6f44cabc984063eee5968b133cb46855e729d555b9ff8d744652262b7b51feec73 languageName: node linkType: hard "micromark-util-decode-string@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-util-decode-string@npm:1.0.0" + version: 1.1.0 + resolution: "micromark-util-decode-string@npm:1.1.0" dependencies: + decode-named-character-reference: "npm:^1.0.0" micromark-util-character: "npm:^1.0.0" micromark-util-decode-numeric-character-reference: "npm:^1.0.0" - parse-entities: "npm:^3.0.0" - checksum: 10/9c7e6e3950e675f39058935e065e8774a1ef26a0d386eb45abd96fb8c0312c8a72464c672f41a3c7668974ac2a7cb2ad23b84eee3775b0bd4c47947d9fc64888 + micromark-util-symbol: "npm:^1.0.0" + checksum: 10/f1625155db452f15aa472918499689ba086b9c49d1322a08b22bfbcabe918c61b230a3002c8bc3ea9b1f52ca7a9bb1c3dd43ccb548c7f5f8b16c24a1ae77a813 languageName: node linkType: hard "micromark-util-encode@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-util-encode@npm:1.0.0" - checksum: 10/16985a6b355721307553d1893da364e83144ef068f84978071a9b4b3d884b65c3138f8330fb039aac10f75766b4906e03c5e62baafb1bf5e731f959878277712 + version: 1.1.0 + resolution: "micromark-util-encode@npm:1.1.0" + checksum: 10/4ef29d02b12336918cea6782fa87c8c578c67463925221d4e42183a706bde07f4b8b5f9a5e1c7ce8c73bb5a98b261acd3238fecd152e6dd1cdfa2d1ae11b60a0 languageName: node linkType: hard "micromark-util-html-tag-name@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-util-html-tag-name@npm:1.0.0" - checksum: 10/ed07ce9b9bb30cc4ea57f733089b3a253a6132c0608ccfc105eadb32f1f80bbd2347bf8a74f897fe039d7805a59f602fd4dd15f6adc7926d40b3646da2888d0f + version: 1.2.0 + resolution: "micromark-util-html-tag-name@npm:1.2.0" + checksum: 10/ccf0fa99b5c58676dc5192c74665a3bfd1b536fafaf94723bd7f31f96979d589992df6fcf2862eba290ef18e6a8efb30ec8e1e910d9f3fc74f208871e9f84750 languageName: node linkType: hard "micromark-util-normalize-identifier@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-util-normalize-identifier@npm:1.0.0" + version: 1.1.0 + resolution: "micromark-util-normalize-identifier@npm:1.1.0" dependencies: micromark-util-symbol: "npm:^1.0.0" - checksum: 10/d7c09d5e8318fb72f194af72664bd84a48a2928e3550b2b21c8fbc0ec22524f2a72e0f6663d2b95dc189a6957d3d7759b60716e888909710767cd557be821f8b + checksum: 10/8655bea41ffa4333e03fc22462cb42d631bbef9c3c07b625fd852b7eb442a110f9d2e5902a42e65188d85498279569502bf92f3434a1180fc06f7c37edfbaee2 languageName: node linkType: hard "micromark-util-resolve-all@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-util-resolve-all@npm:1.0.0" + version: 1.1.0 + resolution: "micromark-util-resolve-all@npm:1.1.0" dependencies: micromark-util-types: "npm:^1.0.0" - checksum: 10/409667f2bd126ef8acce009270d2aecaaa5584c5807672bc657b09e50aa91bd2e552cf41e5be1e6469244a83349cbb71daf6059b746b1c44e3f35446fef63e50 + checksum: 10/1ce6c0237cd3ca061e76fae6602cf95014e764a91be1b9f10d36cb0f21ca88f9a07de8d49ab8101efd0b140a4fbfda6a1efb72027ab3f4d5b54c9543271dc52c languageName: node linkType: hard "micromark-util-sanitize-uri@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-util-sanitize-uri@npm:1.0.0" + version: 1.2.0 + resolution: "micromark-util-sanitize-uri@npm:1.2.0" dependencies: micromark-util-character: "npm:^1.0.0" micromark-util-encode: "npm:^1.0.0" micromark-util-symbol: "npm:^1.0.0" - checksum: 10/198e91b9f86d429ebc74737c0378764c8dc2e988f46a6846f59223938bc9f3a5d4ae97ce9ce801ac6f3a76cce3da1196cbb3d5ba13bb4cce53026b06891a61da + checksum: 10/0d024100d95ffb88bf75f3360e305b545c1eb745430959b8633f7aa93f37ec401fc7094c90c97298409a9e30d94d53b895bae224e1bb966bea114976cfa0fd48 languageName: node linkType: hard "micromark-util-subtokenize@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-util-subtokenize@npm:1.0.0" + version: 1.1.0 + resolution: "micromark-util-subtokenize@npm:1.1.0" dependencies: micromark-util-chunked: "npm:^1.0.0" micromark-util-symbol: "npm:^1.0.0" micromark-util-types: "npm:^1.0.0" - checksum: 10/0b19352a05c958c38782be7be6ca1c5ce004f24b4d5fdb70666d2dfa4c75d859a0fc44ef3e899ad085fe3022b0cd1201748f96b5617d09e43ea455ba2e2d0f9a + uvu: "npm:^0.5.0" + checksum: 10/075a1db6ea586d65827d3eead33dbfc520c4e43659c93fcd8fd82f44a7b75cfe61dcde967a3dfcc2ffd999347440ba5aa6698e65a04f3fc627e13e9f12a1a910 languageName: node linkType: hard "micromark-util-symbol@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-util-symbol@npm:1.0.0" - checksum: 10/93945fd4863bcf9e4186ee6f234a1ef790f884e927287aed6741d41d37dae4c0783d36f3f811e94e1634ea86c6bb484b6d2691d5e87a471aee32500cb1a4c136 + version: 1.1.0 + resolution: "micromark-util-symbol@npm:1.1.0" + checksum: 10/a26b6b1efd77a715a4d9bbe0a5338eaf3d04ea5e85733e34fee56dfeabf64495c0afc5438fe5220316884cd3a5eae1f17768e0ff4e117827ea4a653897466f86 languageName: node linkType: hard "micromark-util-types@npm:^1.0.0, micromark-util-types@npm:^1.0.1": - version: 1.0.1 - resolution: "micromark-util-types@npm:1.0.1" - checksum: 10/cc270381bb1035e610592d76a9889ec7d2d8f613bd6c2cbbfc09cae8232602af132ca4d2a5d498b0f9e30db6bc33e0320bb7386ee2abc7267d6a1c25dadcbd85 + version: 1.1.0 + resolution: "micromark-util-types@npm:1.1.0" + checksum: 10/287ac5de4a3802bb6f6c3842197c294997a488db1c0486e03c7a8e674d9eb7720c17dda1bcb814814b8343b338c4826fcbc0555f3e75463712a60dcdb53a028e languageName: node linkType: hard "micromark@npm:^3.0.0": - version: 3.0.5 - resolution: "micromark@npm:3.0.5" + version: 3.2.0 + resolution: "micromark@npm:3.2.0" dependencies: "@types/debug": "npm:^4.0.0" debug: "npm:^4.0.0" + decode-named-character-reference: "npm:^1.0.0" micromark-core-commonmark: "npm:^1.0.1" micromark-factory-space: "npm:^1.0.0" micromark-util-character: "npm:^1.0.0" @@ -38380,12 +36795,12 @@ __metadata: micromark-util-subtokenize: "npm:^1.0.0" micromark-util-symbol: "npm:^1.0.0" micromark-util-types: "npm:^1.0.1" - parse-entities: "npm:^3.0.0" - checksum: 10/cb6c259849562f69f901150f37343ed7f952b75d01a3f0c312b5a0fe58c5f3304067c66be5dcf4a781a4da51073a631c2db095d3647f5d6d7895b44f181197b5 + uvu: "npm:^0.5.0" + checksum: 10/560a4a501efc3859d622461aaa9345fb95b99a2f34d3d3f2a775ab04de1dd857cb0f642083a6b28ab01bd817f5f0741a1be9857fd702f45e04a3752927a66719 languageName: node linkType: hard -"micromatch@npm:^4.0.2, micromatch@npm:^4.0.4, micromatch@npm:^4.0.5, micromatch@npm:^4.0.7, micromatch@npm:^4.0.8": +"micromatch@npm:^4.0.2, micromatch@npm:^4.0.5, micromatch@npm:^4.0.7, micromatch@npm:^4.0.8": version: 4.0.8 resolution: "micromatch@npm:4.0.8" dependencies: @@ -38446,7 +36861,7 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:2.1.35, mime-types@npm:^2.1.12, mime-types@npm:^2.1.18, mime-types@npm:^2.1.27, mime-types@npm:^2.1.31, mime-types@npm:^2.1.35, mime-types@npm:~2.1.17, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": +"mime-types@npm:2.1.35, mime-types@npm:^2.1.12, mime-types@npm:^2.1.27, mime-types@npm:^2.1.31, mime-types@npm:^2.1.35, mime-types@npm:~2.1.17, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": version: 2.1.35 resolution: "mime-types@npm:2.1.35" dependencies: @@ -38505,13 +36920,6 @@ __metadata: languageName: node linkType: hard -"mimic-fn@npm:^4.0.0": - version: 4.0.0 - resolution: "mimic-fn@npm:4.0.0" - checksum: 10/995dcece15ee29aa16e188de6633d43a3db4611bcf93620e7e62109ec41c79c0f34277165b8ce5e361205049766e371851264c21ac64ca35499acb5421c2ba56 - languageName: node - linkType: hard - "mimic-function@npm:^5.0.0": version: 5.0.1 resolution: "mimic-function@npm:5.0.1" @@ -38547,15 +36955,15 @@ __metadata: languageName: node linkType: hard -"mini-css-extract-plugin@npm:^2.4.2, mini-css-extract-plugin@npm:^2.9.2": - version: 2.9.4 - resolution: "mini-css-extract-plugin@npm:2.9.4" +"mini-css-extract-plugin@npm:^2.4.2": + version: 2.10.2 + resolution: "mini-css-extract-plugin@npm:2.10.2" dependencies: schema-utils: "npm:^4.0.0" tapable: "npm:^2.2.1" peerDependencies: webpack: ^5.0.0 - checksum: 10/24a0418dc49baed58a10a8b81e4d2fe474e89ccdc9370a7c69bd0aeeb5a70d2b4ee12f33b98c95a68423c79c1de7cc2a25aaa2105600b712e7d594cb0d56c9d4 + checksum: 10/d2b01f25e229d04263274fceccfcb3ec0937a448859e4cb65e32652e26dde46ae6ecf7d15a52a4bb700bea6e47675db88691d3abc418901ec8542e1cfdc62b85 languageName: node linkType: hard @@ -38582,7 +36990,16 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:3.1.2, minimatch@npm:^3.0.2, minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": +"minimatch@npm:10.2.1": + version: 10.2.1 + resolution: "minimatch@npm:10.2.1" + dependencies: + brace-expansion: "npm:^5.0.2" + checksum: 10/d41c195ee1f2c70a75641088e36d0fa5fa286cb6fe48558e6d3bf3d95f640eda453c217707215389b12234df12175f65f338c0b841b36b0125177dbd6a80d026 + languageName: node + linkType: hard + +"minimatch@npm:3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" dependencies: @@ -38591,6 +37008,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:3.1.5, minimatch@npm:^3.0.2, minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": + version: 3.1.5 + resolution: "minimatch@npm:3.1.5" + dependencies: + brace-expansion: "npm:^1.1.7" + checksum: 10/b11a7ee5773cd34c1a0c8436cdbe910901018fb4b6cb47aa508a18d567f6efd2148507959e35fba798389b161b8604a2d704ccef751ea36bd4582f9852b7d63f + languageName: node + linkType: hard + "minimatch@npm:9.0.3": version: 9.0.3 resolution: "minimatch@npm:9.0.3" @@ -38600,48 +37026,30 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^10.0.3": - version: 10.0.3 - resolution: "minimatch@npm:10.0.3" +"minimatch@npm:^10.0.0, minimatch@npm:^10.2.1, minimatch@npm:^10.2.2, minimatch@npm:^10.2.4": + version: 10.2.5 + resolution: "minimatch@npm:10.2.5" dependencies: - "@isaacs/brace-expansion": "npm:^5.0.0" - checksum: 10/d5b8b2538b367f2cfd4aeef27539fddeee58d1efb692102b848e4a968a09780a302c530eb5aacfa8c57f7299155fb4b4e85219ad82664dcef5c66f657111d9b8 + brace-expansion: "npm:^5.0.5" + checksum: 10/19e87a931aff60ee7b9d80f39f817b8bfc54f61f8356ee3549fbf636dbccacacfec8d803eac73293955c4527cd085247dfc064bce4a5e349f8f3b85e2bf5da0f languageName: node linkType: hard "minimatch@npm:^5.0.1, minimatch@npm:^5.1.0": - version: 5.1.6 - resolution: "minimatch@npm:5.1.6" + version: 5.1.9 + resolution: "minimatch@npm:5.1.9" dependencies: brace-expansion: "npm:^2.0.1" - checksum: 10/126b36485b821daf96d33b5c821dac600cc1ab36c87e7a532594f9b1652b1fa89a1eebcaad4dff17c764dce1a7ac1531327f190fed5f97d8f6e5f889c116c429 + checksum: 10/23b4feb64dcb77ba93b70a72be551eb2e2677ac02178cf1ed3d38836cc4cd84802d90b77f60ef87f2bac64d270d2d8eba242e428f0554ea4e36bfdb7e9d25d0c languageName: node linkType: hard -"minimatch@npm:^7.4.3": - version: 7.4.6 - resolution: "minimatch@npm:7.4.6" +"minimatch@npm:^9.0.4, minimatch@npm:^9.0.5": + version: 9.0.9 + resolution: "minimatch@npm:9.0.9" dependencies: - brace-expansion: "npm:^2.0.1" - checksum: 10/0046ba1161ac6414bde1b07c440792ebcdb2ed93e6714c85c73974332b709b7e692801550bc9da22028a8613407b3f13861e17dd0dd44f4babdeacd44950430b - languageName: node - linkType: hard - -"minimatch@npm:^9.0.0, minimatch@npm:^9.0.4, minimatch@npm:^9.0.5": - version: 9.0.5 - resolution: "minimatch@npm:9.0.5" - dependencies: - brace-expansion: "npm:^2.0.1" - checksum: 10/dd6a8927b063aca6d910b119e1f2df6d2ce7d36eab91de83167dd136bb85e1ebff97b0d3de1cb08bd1f7e018ca170b4962479fefab5b2a69e2ae12cb2edc8348 - languageName: node - linkType: hard - -"minimatch@npm:~3.0.3": - version: 3.0.8 - resolution: "minimatch@npm:3.0.8" - dependencies: - brace-expansion: "npm:^1.1.7" - checksum: 10/6df5373cb1ea79020beb6887ff5576c58cfabcfd32c5a65c2cf58f326e4ee8eae84f129e5fa50b8a4347fa1d1e583f931285c9fb3040d984bdfb5109ef6607ec + brace-expansion: "npm:^2.0.2" + checksum: 10/b91fad937deaffb68a45a2cb731ff3cff1c3baf9b6469c879477ed16f15c8f4ce39d63a3f75c2455107c2fdff0f3ab597d97dc09e2e93b883aafcf926ef0c8f9 languageName: node linkType: hard @@ -38709,21 +37117,6 @@ __metadata: languageName: node linkType: hard -"minipass-fetch@npm:^3.0.0": - version: 3.0.3 - resolution: "minipass-fetch@npm:3.0.3" - dependencies: - encoding: "npm:^0.1.13" - minipass: "npm:^5.0.0" - minipass-sized: "npm:^1.0.3" - minizlib: "npm:^2.1.2" - dependenciesMeta: - encoding: - optional: true - checksum: 10/045339fa8fa2f2a544da203c38e91e6329a6c8d0d563db42db2e32bd863b0d7127692f456dcdd171bcd3123af12ce04072d3fc276571c85085a9870db7dea69a - languageName: node - linkType: hard - "minipass-fetch@npm:^4.0.0": version: 4.0.0 resolution: "minipass-fetch@npm:4.0.0" @@ -38739,6 +37132,21 @@ __metadata: languageName: node linkType: hard +"minipass-fetch@npm:^5.0.0": + version: 5.0.2 + resolution: "minipass-fetch@npm:5.0.2" + dependencies: + iconv-lite: "npm:^0.7.2" + minipass: "npm:^7.0.3" + minipass-sized: "npm:^2.0.0" + minizlib: "npm:^3.0.1" + dependenciesMeta: + iconv-lite: + optional: true + checksum: 10/4f3f65ea5b20a3a287765ebf21cc73e62031f754944272df2a3039296cc75a8fc2dc50b8a3c4f39ce3ac6e5cc583e8dc664d12c6ab98e0883d263e49f344bc86 + languageName: node + linkType: hard + "minipass-flush@npm:^1.0.5": version: 1.0.5 resolution: "minipass-flush@npm:1.0.5" @@ -38776,6 +37184,15 @@ __metadata: languageName: node linkType: hard +"minipass-sized@npm:^2.0.0": + version: 2.0.0 + resolution: "minipass-sized@npm:2.0.0" + dependencies: + minipass: "npm:^7.1.2" + checksum: 10/3b89adf64ca705662f77481e278eff5ec0a57aeffb5feba7cc8843722b1e7770efc880f2a17d1d4877b2d7bf227873cd46afb4da44c0fd18088b601ea50f96bb + languageName: node + linkType: hard + "minipass@npm:^3.0.0, minipass@npm:^3.1.0, minipass@npm:^3.1.1, minipass@npm:^3.1.3, minipass@npm:^3.1.6": version: 3.3.5 resolution: "minipass@npm:3.3.5" @@ -38785,13 +37202,6 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^4.0.0": - version: 4.2.8 - resolution: "minipass@npm:4.2.8" - checksum: 10/e148eb6dcb85c980234cad889139ef8ddf9d5bdac534f4f0268446c8792dd4c74f4502479be48de3c1cce2f6450f6da4d0d4a86405a8a12be04c1c36b339569a - languageName: node - linkType: hard - "minipass@npm:^5.0.0": version: 5.0.0 resolution: "minipass@npm:5.0.0" @@ -38799,10 +37209,10 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": - version: 7.1.2 - resolution: "minipass@npm:7.1.2" - checksum: 10/c25f0ee8196d8e6036661104bacd743785b2599a21de5c516b32b3fa2b83113ac89a2358465bc04956baab37ffb956ae43be679b2262bf7be15fce467ccd7950 +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2, minipass@npm:^7.1.3": + version: 7.1.3 + resolution: "minipass@npm:7.1.3" + checksum: 10/175e4d5e20980c3cd316ae82d2c031c42f6c746467d8b1905b51060a0ba4461441a0c25bb67c025fd9617f9a3873e152c7b543c6b5ac83a1846be8ade80dffd6 languageName: node linkType: hard @@ -38816,13 +37226,12 @@ __metadata: languageName: node linkType: hard -"minizlib@npm:^3.0.1": - version: 3.0.1 - resolution: "minizlib@npm:3.0.1" +"minizlib@npm:^3.0.1, minizlib@npm:^3.1.0": + version: 3.1.0 + resolution: "minizlib@npm:3.1.0" dependencies: - minipass: "npm:^7.0.4" - rimraf: "npm:^5.0.5" - checksum: 10/622cb85f51e5c206a080a62d20db0d7b4066f308cb6ce82a9644da112367c3416ae7062017e631eb7ac8588191cfa4a9a279b8651c399265202b298e98c4acef + minipass: "npm:^7.1.2" + checksum: 10/f47365cc2cb7f078cbe7e046eb52655e2e7e97f8c0a9a674f4da60d94fb0624edfcec9b5db32e8ba5a99a5f036f595680ae6fe02a262beaa73026e505cc52f99 languageName: node linkType: hard @@ -38844,7 +37253,7 @@ __metadata: languageName: node linkType: hard -"mkdirp@npm:^0.5.1, mkdirp@npm:^0.5.5, mkdirp@npm:^0.5.6": +"mkdirp@npm:^0.5.1, mkdirp@npm:^0.5.5": version: 0.5.6 resolution: "mkdirp@npm:0.5.6" dependencies: @@ -38930,8 +37339,8 @@ __metadata: linkType: hard "mockttp@npm:^3.13.0": - version: 3.15.5 - resolution: "mockttp@npm:3.15.5" + version: 3.17.1 + resolution: "mockttp@npm:3.17.1" dependencies: "@graphql-tools/schema": "npm:^8.5.0" "@graphql-tools/utils": "npm:^8.8.0" @@ -38949,7 +37358,7 @@ __metadata: cors: "npm:^2.8.4" cors-gate: "npm:^1.1.3" cross-fetch: "npm:^3.1.5" - destroyable-server: "npm:^1.0.2" + destroyable-server: "npm:^1.1.1" express: "npm:^4.14.0" fast-json-patch: "npm:^3.1.1" graphql: "npm:^14.0.2 || ^15.5" @@ -38968,7 +37377,7 @@ __metadata: parse-multipart-data: "npm:^1.4.0" performance-now: "npm:^2.1.0" portfinder: "npm:^1.0.32" - read-tls-client-hello: "npm:^1.0.0" + read-tls-client-hello: "npm:^1.1.0" semver: "npm:^7.5.3" socks-proxy-agent: "npm:^7.0.0" typed-error: "npm:^3.0.2" @@ -38977,7 +37386,7 @@ __metadata: ws: "npm:^8.8.0" bin: mockttp: dist/admin/admin-bin.js - checksum: 10/93eca69baa35db11ae7f541d712bfd208edf0017db3091e874f59fc64a7c036179906b4e9b66936f054ad1c258645d085452dafab58a1c2df9fd6d67330f8328 + checksum: 10/09278221361b9896636bbf9e6a6d6427afe2375c035dcc6ddd56d308a7b197ae3a5196fba7b00b9fb1bfb0b4c22daacc046bdc207e0ab9ef15bb2a79637cdfbf languageName: node linkType: hard @@ -38993,10 +37402,10 @@ __metadata: languageName: node linkType: hard -"module-details-from-path@npm:^1.0.3": - version: 1.0.3 - resolution: "module-details-from-path@npm:1.0.3" - checksum: 10/f93226e9154fc8cb91f4609b639167ec7ad9155b30be4924d9717656648a3ae5f181d4e2338434d4c5afc7b5f4c10dd3b64109e5b89a4be70b20a25ba3573d54 +"module-details-from-path@npm:^1.0.3, module-details-from-path@npm:^1.0.4": + version: 1.0.4 + resolution: "module-details-from-path@npm:1.0.4" + checksum: 10/2ebfada5358492f6ab496b70f70a1042f2ee7a4c79d29467f59ed6704f741fb4461d7cecb5082144ed39a05fec4d19e9ff38b731c76228151be97227240a05b2 languageName: node linkType: hard @@ -39021,6 +37430,43 @@ __metadata: languageName: node linkType: hard +"motion-dom@npm:^12.38.0": + version: 12.38.0 + resolution: "motion-dom@npm:12.38.0" + dependencies: + motion-utils: "npm:^12.36.0" + checksum: 10/78c040b46d93273932cf80c70e39845be5a442dcaf18d4345b45a9193de9dfa87c885b609943cb652115e4eac5d46ef40b452185073dd43fc328b134f9975e90 + languageName: node + linkType: hard + +"motion-utils@npm:^12.36.0": + version: 12.36.0 + resolution: "motion-utils@npm:12.36.0" + checksum: 10/c4a2a7ffac48ca44082d6d31b115f245025060a7e69d70dac062646d8f96c39e5662a7c8a51f255566fdf8e719ef1269a8e9aa3a04fc263bb65b5a7b61331901 + languageName: node + linkType: hard + +"motion@npm:^12.0.0": + version: 12.38.0 + resolution: "motion@npm:12.38.0" + dependencies: + framer-motion: "npm:^12.38.0" + tslib: "npm:^2.4.0" + peerDependencies: + "@emotion/is-prop-valid": "*" + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/is-prop-valid": + optional: true + react: + optional: true + react-dom: + optional: true + checksum: 10/d7ae2ba3cc112c4467822956b92065239640b9c62204d3bee1780da9fc0147185373534138d39975e82bf73b5f1b28d3fb3581031e4e7e0cfb230472767bd10d + languageName: node + linkType: hard + "mri@npm:1.1.4": version: 1.1.4 resolution: "mri@npm:1.1.4" @@ -39125,17 +37571,14 @@ __metadata: linkType: hard "multer@npm:^2.0.2": - version: 2.0.2 - resolution: "multer@npm:2.0.2" + version: 2.1.1 + resolution: "multer@npm:2.1.1" dependencies: append-field: "npm:^1.0.0" busboy: "npm:^1.6.0" concat-stream: "npm:^2.0.0" - mkdirp: "npm:^0.5.6" - object-assign: "npm:^4.1.1" type-is: "npm:^1.6.18" - xtend: "npm:^4.0.2" - checksum: 10/4bdcb07138cf72f93adc08a0dc27c058faab9f6721067a58f394fa546d73d11b7f100cdd66e733d649d184f9d1b402065f6888b31ec427409f056ee92c4367a6 + checksum: 10/fb22868caaed37d725715c14c60b740b81665265da3a026bb61954414f65b99f76b360128413b8a2a7cc1a95ecae28a42bf831fe172bb79682d19ec105b556bd languageName: node linkType: hard @@ -39171,6 +37614,13 @@ __metadata: languageName: node linkType: hard +"mute-stream@npm:^1.0.0": + version: 1.0.0 + resolution: "mute-stream@npm:1.0.0" + checksum: 10/36fc968b0e9c9c63029d4f9dc63911950a3bdf55c9a87f58d3a266289b67180201cade911e7699f8b2fa596b34c9db43dad37649e3f7fdd13c3bb9edb0017ee7 + languageName: node + linkType: hard + "mute-stream@npm:^2.0.0": version: 2.0.0 resolution: "mute-stream@npm:2.0.0" @@ -39179,23 +37629,24 @@ __metadata: linkType: hard "mysql2@npm:^3.0.0": - version: 3.11.5 - resolution: "mysql2@npm:3.11.5" + version: 3.20.0 + resolution: "mysql2@npm:3.20.0" dependencies: - aws-ssl-profiles: "npm:^1.1.1" + aws-ssl-profiles: "npm:^1.1.2" denque: "npm:^2.1.0" generate-function: "npm:^2.3.1" - iconv-lite: "npm:^0.6.3" - long: "npm:^5.2.1" - lru.min: "npm:^1.0.0" - named-placeholders: "npm:^1.1.3" - seq-queue: "npm:^0.0.5" - sqlstring: "npm:^2.3.2" - checksum: 10/912dc364f6f9684721add5474c732f5d7c2403ec17cb05b06f95adafab3cb32daa2798b283d48d0951d11af94a58558581ed22635803fc6baab651d3651c1cc9 + iconv-lite: "npm:^0.7.2" + long: "npm:^5.3.2" + lru.min: "npm:^1.1.4" + named-placeholders: "npm:^1.1.6" + sql-escaper: "npm:^1.3.3" + peerDependencies: + "@types/node": ">= 8" + checksum: 10/4345c6689220878bb37c35ac5c093183c67420590bc70136e6642758610ccd004373cb3341292df8fedff52f8a8b9a22ad203bf6eda02b341550fdb704eca7a8 languageName: node linkType: hard -"mz@npm:^2.7.0": +"mz@npm:^2.4.0, mz@npm:^2.7.0": version: 2.7.0 resolution: "mz@npm:2.7.0" dependencies: @@ -39206,12 +37657,12 @@ __metadata: languageName: node linkType: hard -"named-placeholders@npm:^1.1.3": - version: 1.1.3 - resolution: "named-placeholders@npm:1.1.3" +"named-placeholders@npm:^1.1.6": + version: 1.1.6 + resolution: "named-placeholders@npm:1.1.6" dependencies: - lru-cache: "npm:^7.14.1" - checksum: 10/7834adc91e92ae1b9c4413384e3ccd297de5168bb44017ff0536705ddc4db421723bd964607849265feb3f6ded390f84cf138e5925f22f7c13324f87a803dc73 + lru.min: "npm:^1.1.0" + checksum: 10/959d44f2a00e87baa2c2b2d43c2be76c6e841a0ed67af4bf4a8d91c54082e2b2db9ac8461b16db413dcb133c6f34669516d755f9a1a7956851ce81196a717f65 languageName: node linkType: hard @@ -39243,7 +37694,7 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.3.11, nanoid@npm:^3.3.7": +"nanoid@npm:^3.3.11": version: 3.3.11 resolution: "nanoid@npm:3.3.11" bin: @@ -39252,10 +37703,19 @@ __metadata: languageName: node linkType: hard -"napi-build-utils@npm:^1.0.1": - version: 1.0.2 - resolution: "napi-build-utils@npm:1.0.2" - checksum: 10/276feb8e30189fe18718e85b6f82e4f952822baa2e7696f771cc42571a235b789dc5907a14d9ffb6838c3e4ff4c25717c2575e5ce1cf6e02e496e204c11e57f6 +"napi-build-utils@npm:^2.0.0": + version: 2.0.0 + resolution: "napi-build-utils@npm:2.0.0" + checksum: 10/69adcdb828481737f1ec64440286013f6479d5b264e24d5439ba795f65293d0bb6d962035de07c65fae525ed7d2fcd0baab6891d8e3734ea792fec43918acf83 + languageName: node + linkType: hard + +"napi-postinstall@npm:^0.3.0": + version: 0.3.4 + resolution: "napi-postinstall@npm:0.3.4" + bin: + napi-postinstall: lib/cli.js + checksum: 10/5541381508f9e1051ff3518701c7130ebac779abb3a1ffe9391fcc3cab4cc0569b0ba0952357db3f6b12909c3bb508359a7a60261ffd795feebbdab967175832 languageName: node linkType: hard @@ -39419,6 +37879,15 @@ __metadata: languageName: node linkType: hard +"node-addon-api@npm:^4.3.0": + version: 4.3.0 + resolution: "node-addon-api@npm:4.3.0" + dependencies: + node-gyp: "npm:latest" + checksum: 10/d3b38d16cb9ad0714d965331d0e38cef1c27750c2c3343cd3464a9ed8158501a2910ccbf2fd9fdc476e806a19dbc9e0524ff9d66a7c779d42a9752a63ba30b80 + languageName: node + linkType: hard + "node-addon-api@npm:^8.0.0, node-addon-api@npm:^8.2.2, node-addon-api@npm:^8.3.0, node-addon-api@npm:^8.3.1": version: 8.5.0 resolution: "node-addon-api@npm:8.5.0" @@ -39453,6 +37922,18 @@ __metadata: languageName: node linkType: hard +"node-emoji@npm:^2.2.0": + version: 2.2.0 + resolution: "node-emoji@npm:2.2.0" + dependencies: + "@sindresorhus/is": "npm:^4.6.0" + char-regex: "npm:^1.0.2" + emojilib: "npm:^2.4.0" + skin-tone: "npm:^2.0.0" + checksum: 10/2548668f5cc9f781c94dc39971a630b2887111e0970c29fc523e924819d1b39b53a2694a4d1046861adf538c4462d06ee0269c48717ccad30336a918d9a911d5 + languageName: node + linkType: hard + "node-fetch-commonjs@npm:^3.3.2": version: 3.3.2 resolution: "node-fetch-commonjs@npm:3.3.2" @@ -39477,7 +37958,7 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.12, node-fetch@npm:^2.6.7, node-fetch@npm:^2.6.9, node-fetch@npm:^2.7.0": +"node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.7, node-fetch@npm:^2.6.9, node-fetch@npm:^2.7.0": version: 2.7.0 resolution: "node-fetch@npm:2.7.0" dependencies: @@ -39502,10 +37983,10 @@ __metadata: languageName: node linkType: hard -"node-forge@npm:^1, node-forge@npm:^1.2.1, node-forge@npm:^1.3.1": - version: 1.3.1 - resolution: "node-forge@npm:1.3.1" - checksum: 10/05bab6868633bf9ad4c3b1dd50ec501c22ffd69f556cdf169a00998ca1d03e8107a6032ba013852f202035372021b845603aeccd7dfcb58cdb7430013b3daa8d +"node-forge@npm:^1, node-forge@npm:^1.2.1, node-forge@npm:^1.3.2": + version: 1.4.0 + resolution: "node-forge@npm:1.4.0" + checksum: 10/d70fd769768e646eda73343d4d4105ccb6869315d975905a22117431c04ae5b6df6c488e34ed275b1a66b50195a09b84b5c8aeca3b8605c20605fcb8e9f109d9 languageName: node linkType: hard @@ -39520,23 +38001,23 @@ __metadata: languageName: node linkType: hard -"node-gyp@npm:^10.0.0": - version: 10.3.1 - resolution: "node-gyp@npm:10.3.1" +"node-gyp@npm:^12.0.0": + version: 12.2.0 + resolution: "node-gyp@npm:12.2.0" dependencies: env-paths: "npm:^2.2.0" exponential-backoff: "npm:^3.1.1" - glob: "npm:^10.3.10" graceful-fs: "npm:^4.2.6" - make-fetch-happen: "npm:^13.0.0" - nopt: "npm:^7.0.0" - proc-log: "npm:^4.1.0" + make-fetch-happen: "npm:^15.0.0" + nopt: "npm:^9.0.0" + proc-log: "npm:^6.0.0" semver: "npm:^7.3.5" - tar: "npm:^6.2.1" - which: "npm:^4.0.0" + tar: "npm:^7.5.4" + tinyglobby: "npm:^0.2.12" + which: "npm:^6.0.0" bin: node-gyp: bin/node-gyp.js - checksum: 10/d3004f648559e42d7ec8791ea75747fe8a163a6061c202e311e5d7a5f6266baa9a5f5c6fde7be563974c88b030c5d0855fd945364f52fcd230d2a2ceee7be80d + checksum: 10/4ebab5b77585a637315e969c2274b5520562473fe75de850639a580c2599652fb9f33959ec782ea45a2e149d8f04b548030f472eeeb3dbdf19a7f2ccbc30b908 languageName: node linkType: hard @@ -39646,10 +38127,10 @@ __metadata: languageName: node linkType: hard -"node-releases@npm:^2.0.18": - version: 2.0.18 - resolution: "node-releases@npm:2.0.18" - checksum: 10/241e5fa9556f1c12bafb83c6c3e94f8cf3d8f2f8f904906ecef6e10bcaa1d59aa61212d4651bec70052015fc54bd3fdcdbe7fc0f638a17e6685aa586c076ec4e +"node-releases@npm:^2.0.27": + version: 2.0.27 + resolution: "node-releases@npm:2.0.27" + checksum: 10/f6c78ddb392ae500719644afcbe68a9ea533242c02312eb6a34e8478506eb7482a3fb709c70235b01c32fe65625b68dfa9665113f816d87f163bc3819b62b106 languageName: node linkType: hard @@ -39725,31 +38206,21 @@ __metadata: languageName: node linkType: hard -"node.extend@npm:^2.0.0": - version: 2.0.3 - resolution: "node.extend@npm:2.0.3" - dependencies: - hasown: "npm:^2.0.0" - is: "npm:^3.3.0" - checksum: 10/f500ace16d0b90e9db3919676de593eb37e7b82d8d9b67d95a40e5856ef5842592df3364b4d01fc2c3f4c0dea6dd9d627444dd85fe18581b7a22caad5ffab249 - languageName: node - linkType: hard - -"nodemailer@npm:^7.0.7": - version: 7.0.10 - resolution: "nodemailer@npm:7.0.10" - checksum: 10/b9b8794ffc6c0d84440a9dd422664908e9c2003a15cb0e6bdd240a3625121de3978323c2ba4af78080fd735ef514d6caed376bd5f5dd6c17cf0d2c399d0dc354 +"nodemailer@npm:^8.0.4": + version: 8.0.5 + resolution: "nodemailer@npm:8.0.5" + checksum: 10/84d44afb0cd20ce02d5e4f0f8465b02aa0af0e11c1b0d40a4e9658d07321655a8ea528533840a38f213a45ca7865ec85f0aed6dc3ac3038d7b2c9405db6cd353 languageName: node linkType: hard "nodemon@npm:^3.0.1": - version: 3.1.11 - resolution: "nodemon@npm:3.1.11" + version: 3.1.14 + resolution: "nodemon@npm:3.1.14" dependencies: chokidar: "npm:^3.5.2" debug: "npm:^4" ignore-by-default: "npm:^1.0.1" - minimatch: "npm:^3.1.2" + minimatch: "npm:^10.2.1" pstree.remy: "npm:^1.1.8" semver: "npm:^7.5.3" simple-update-notifier: "npm:^2.0.0" @@ -39758,7 +38229,7 @@ __metadata: undefsafe: "npm:^2.0.5" bin: nodemon: bin/nodemon.js - checksum: 10/0f43d2c70abe0764e26e438dbe0c78bc429746a558dabf5dd94b13f6f3a79b4bd7d5793347acafddaee5eab594a806c2ad43efc999d342e28d185661718da8dc + checksum: 10/187aa93fc461bb001bac5de63df49a38a7b1cb3c60a5fd6a3b0f2b15da68fe5adfd0f91244be2fd1f57263cbd351e3113cb04c4edf4af23699dd95b2782fbf9b languageName: node linkType: hard @@ -39773,17 +38244,6 @@ __metadata: languageName: node linkType: hard -"nopt@npm:^7.0.0": - version: 7.2.0 - resolution: "nopt@npm:7.2.0" - dependencies: - abbrev: "npm:^2.0.0" - bin: - nopt: bin/nopt.js - checksum: 10/1e7489f17cbda452c8acaf596a8defb4ae477d2a9953b76eb96f4ec3f62c6b421cd5174eaa742f88279871fde9586d8a1d38fb3f53fa0c405585453be31dff4c - languageName: node - linkType: hard - "nopt@npm:^8.0.0": version: 8.0.0 resolution: "nopt@npm:8.0.0" @@ -39795,6 +38255,17 @@ __metadata: languageName: node linkType: hard +"nopt@npm:^9.0.0": + version: 9.0.0 + resolution: "nopt@npm:9.0.0" + dependencies: + abbrev: "npm:^4.0.0" + bin: + nopt: bin/nopt.js + checksum: 10/56a1ccd2ad711fb5115918e2c96828703cddbe12ba2c3bd00591758f6fa30e6f47dd905c59dbfcf9b773f3a293b45996609fb6789ae29d6bfcc3cf3a6f7d9fda + languageName: node + linkType: hard + "nopt@npm:~1.0.10": version: 1.0.10 resolution: "nopt@npm:1.0.10" @@ -39980,15 +38451,6 @@ __metadata: languageName: node linkType: hard -"npm-run-path@npm:^5.1.0": - version: 5.1.0 - resolution: "npm-run-path@npm:5.1.0" - dependencies: - path-key: "npm:^4.0.0" - checksum: 10/dc184eb5ec239d6a2b990b43236845332ef12f4e0beaa9701de724aa797fe40b6bbd0157fb7639d24d3ab13f5d5cf22d223a19c6300846b8126f335f788bee66 - languageName: node - linkType: hard - "npmlog@npm:^5.0.1": version: 5.0.1 resolution: "npmlog@npm:5.0.1" @@ -40047,10 +38509,10 @@ __metadata: languageName: node linkType: hard -"nwsapi@npm:^2.2.0, nwsapi@npm:^2.2.12": - version: 2.2.13 - resolution: "nwsapi@npm:2.2.13" - checksum: 10/f7f30a236f2ee513ea8042f1a987481dc2b900167c47f7163882f0fcfe7ccb57b5c8daaf2c91008dc20a204fcd79e050aee25001433ad99990bbed5a8c74121c +"nwsapi@npm:^2.2.12": + version: 2.2.23 + resolution: "nwsapi@npm:2.2.23" + checksum: 10/aa4a570039c33d70b51436d1bb533f3e2c33c488ccbe9b09285c46a6cee5ef266fd60103461085c6954ba52460786a8138f042958328c7c1b4763898eb3dadfa languageName: node linkType: hard @@ -40096,10 +38558,10 @@ __metadata: languageName: node linkType: hard -"object-inspect@npm:^1.13.3": - version: 1.13.3 - resolution: "object-inspect@npm:1.13.3" - checksum: 10/14cb973d8381c69e14d7f1c8c75044eb4caf04c6dabcf40ca5c2ce42dc2073ae0bb2a9939eeca142b0c05215afaa1cd5534adb7c8879c32cba2576e045ed8368 +"object-inspect@npm:^1.13.3, object-inspect@npm:^1.13.4": + version: 1.13.4 + resolution: "object-inspect@npm:1.13.4" + checksum: 10/aa13b1190ad3e366f6c83ad8a16ed37a19ed57d267385aa4bfdccda833d7b90465c057ff6c55d035a6b2e52c1a2295582b294217a0a3a1ae7abdd6877ef781fb languageName: node linkType: hard @@ -40120,7 +38582,7 @@ __metadata: languageName: node linkType: hard -"object.assign@npm:^4.1.0, object.assign@npm:^4.1.4, object.assign@npm:^4.1.7": +"object.assign@npm:^4.1.4, object.assign@npm:^4.1.7": version: 4.1.7 resolution: "object.assign@npm:4.1.7" dependencies: @@ -40169,7 +38631,7 @@ __metadata: languageName: node linkType: hard -"object.values@npm:^1.1.6, object.values@npm:^1.2.0, object.values@npm:^1.2.1": +"object.values@npm:^1.1.6, object.values@npm:^1.2.1": version: 1.2.1 resolution: "object.values@npm:1.2.1" dependencies: @@ -40223,7 +38685,7 @@ __metadata: languageName: node linkType: hard -"on-finished@npm:2.4.1, on-finished@npm:^2.3.0, on-finished@npm:^2.4.1": +"on-finished@npm:2.4.1, on-finished@npm:^2.4.1, on-finished@npm:~2.4.1": version: 2.4.1 resolution: "on-finished@npm:2.4.1" dependencies: @@ -40257,15 +38719,6 @@ __metadata: languageName: node linkType: hard -"once@npm:~1.3.0": - version: 1.3.3 - resolution: "once@npm:1.3.3" - dependencies: - wrappy: "npm:1" - checksum: 10/8e832de08b1d73b470e01690c211cb4fcefccab1fd1bd19e706d572d74d3e9b7e38a8bfcdabdd364f9f868757d9e8e5812a59817dc473eaf698ff3bfae2219f2 - languageName: node - linkType: hard - "one-time@npm:^1.0.0": version: 1.0.0 resolution: "one-time@npm:1.0.0" @@ -40284,15 +38737,6 @@ __metadata: languageName: node linkType: hard -"onetime@npm:^6.0.0": - version: 6.0.0 - resolution: "onetime@npm:6.0.0" - dependencies: - mimic-fn: "npm:^4.0.0" - checksum: 10/0846ce78e440841335d4e9182ef69d5762e9f38aa7499b19f42ea1c4cd40f0b4446094c455c713f9adac3f4ae86f613bb5e30c99e52652764d06a89f709b3788 - languageName: node - linkType: hard - "onetime@npm:^7.0.0": version: 7.0.0 resolution: "onetime@npm:7.0.0" @@ -40302,13 +38746,6 @@ __metadata: languageName: node linkType: hard -"only@npm:~0.0.2": - version: 0.0.2 - resolution: "only@npm:0.0.2" - checksum: 10/e2ad03e486534dc6bfb983393be83125a4669052b4a19a353eb00475b46971fb238a18223f2b609fe0d1bcb61ff8373964ccac64d05cbf970865299f655ed0ba - languageName: node - linkType: hard - "ono@npm:^7.1.3": version: 7.1.3 resolution: "ono@npm:7.1.3" @@ -40318,19 +38755,19 @@ __metadata: languageName: node linkType: hard -"open@npm:^10.0.3": - version: 10.0.3 - resolution: "open@npm:10.0.3" +"open@npm:^10.0.3, open@npm:^10.2.0": + version: 10.2.0 + resolution: "open@npm:10.2.0" dependencies: default-browser: "npm:^5.2.1" define-lazy-prop: "npm:^3.0.0" is-inside-container: "npm:^1.0.0" - is-wsl: "npm:^3.1.0" - checksum: 10/4dc757ad1d3d63490822f991e9cbe3a7c05b7249fca2eaa571cb7d191e5cec88bc37e15d8ef4fd740d8989a288b661d8da253caa8d98e8c97430ddbbb0ae4ed1 + wsl-utils: "npm:^0.1.0" + checksum: 10/e6ad9474734eac3549dcc7d85e952394856ccaee48107c453bd6a725b82e3b8ed5f427658935df27efa76b411aeef62888edea8a9e347e8e7c82632ec966b30e languageName: node linkType: hard -"open@npm:^8.0.0, open@npm:^8.0.4, open@npm:^8.4.0": +"open@npm:^8.0.0, open@npm:^8.4.0": version: 8.4.2 resolution: "open@npm:8.4.2" dependencies: @@ -40449,20 +38886,6 @@ __metadata: languageName: node linkType: hard -"optionator@npm:^0.8.1": - version: 0.8.3 - resolution: "optionator@npm:0.8.3" - dependencies: - deep-is: "npm:~0.1.3" - fast-levenshtein: "npm:~2.0.6" - levn: "npm:~0.3.0" - prelude-ls: "npm:~1.1.2" - type-check: "npm:~0.3.2" - word-wrap: "npm:~1.2.3" - checksum: 10/6fa3c841b520f10aec45563962922215180e8cfbc59fde3ecd4ba2644ad66ca96bd19ad0e853f22fefcb7fc10e7612a5215b412cc66c5588f9a3138b38f6b5ff - languageName: node - linkType: hard - "optionator@npm:^0.9.3": version: 0.9.3 resolution: "optionator@npm:0.9.3" @@ -40538,6 +38961,86 @@ __metadata: languageName: node linkType: hard +"own-keys@npm:^1.0.1": + version: 1.0.1 + resolution: "own-keys@npm:1.0.1" + dependencies: + get-intrinsic: "npm:^1.2.6" + object-keys: "npm:^1.1.1" + safe-push-apply: "npm:^1.0.0" + checksum: 10/ab4bb3b8636908554fc19bf899e225444195092864cb61503a0d048fdaf662b04be2605b636a4ffeaf6e8811f6fcfa8cbb210ec964c0eb1a41eb853e1d5d2f41 + languageName: node + linkType: hard + +"oxc-resolver@npm:^11.19.1": + version: 11.19.1 + resolution: "oxc-resolver@npm:11.19.1" + dependencies: + "@oxc-resolver/binding-android-arm-eabi": "npm:11.19.1" + "@oxc-resolver/binding-android-arm64": "npm:11.19.1" + "@oxc-resolver/binding-darwin-arm64": "npm:11.19.1" + "@oxc-resolver/binding-darwin-x64": "npm:11.19.1" + "@oxc-resolver/binding-freebsd-x64": "npm:11.19.1" + "@oxc-resolver/binding-linux-arm-gnueabihf": "npm:11.19.1" + "@oxc-resolver/binding-linux-arm-musleabihf": "npm:11.19.1" + "@oxc-resolver/binding-linux-arm64-gnu": "npm:11.19.1" + "@oxc-resolver/binding-linux-arm64-musl": "npm:11.19.1" + "@oxc-resolver/binding-linux-ppc64-gnu": "npm:11.19.1" + "@oxc-resolver/binding-linux-riscv64-gnu": "npm:11.19.1" + "@oxc-resolver/binding-linux-riscv64-musl": "npm:11.19.1" + "@oxc-resolver/binding-linux-s390x-gnu": "npm:11.19.1" + "@oxc-resolver/binding-linux-x64-gnu": "npm:11.19.1" + "@oxc-resolver/binding-linux-x64-musl": "npm:11.19.1" + "@oxc-resolver/binding-openharmony-arm64": "npm:11.19.1" + "@oxc-resolver/binding-wasm32-wasi": "npm:11.19.1" + "@oxc-resolver/binding-win32-arm64-msvc": "npm:11.19.1" + "@oxc-resolver/binding-win32-ia32-msvc": "npm:11.19.1" + "@oxc-resolver/binding-win32-x64-msvc": "npm:11.19.1" + dependenciesMeta: + "@oxc-resolver/binding-android-arm-eabi": + optional: true + "@oxc-resolver/binding-android-arm64": + optional: true + "@oxc-resolver/binding-darwin-arm64": + optional: true + "@oxc-resolver/binding-darwin-x64": + optional: true + "@oxc-resolver/binding-freebsd-x64": + optional: true + "@oxc-resolver/binding-linux-arm-gnueabihf": + optional: true + "@oxc-resolver/binding-linux-arm-musleabihf": + optional: true + "@oxc-resolver/binding-linux-arm64-gnu": + optional: true + "@oxc-resolver/binding-linux-arm64-musl": + optional: true + "@oxc-resolver/binding-linux-ppc64-gnu": + optional: true + "@oxc-resolver/binding-linux-riscv64-gnu": + optional: true + "@oxc-resolver/binding-linux-riscv64-musl": + optional: true + "@oxc-resolver/binding-linux-s390x-gnu": + optional: true + "@oxc-resolver/binding-linux-x64-gnu": + optional: true + "@oxc-resolver/binding-linux-x64-musl": + optional: true + "@oxc-resolver/binding-openharmony-arm64": + optional: true + "@oxc-resolver/binding-wasm32-wasi": + optional: true + "@oxc-resolver/binding-win32-arm64-msvc": + optional: true + "@oxc-resolver/binding-win32-ia32-msvc": + optional: true + "@oxc-resolver/binding-win32-x64-msvc": + optional: true + checksum: 10/a6c8fdb2ef4bf9bb84f28e58685457de427d31f74373c0fbd6d1106010cab33027fa3b4336b1b86d0df0a089cd73a6060b730b1b24974d56c59f6fa29c559f9d + languageName: node + linkType: hard + "p-cancelable@npm:^1.0.0": version: 1.1.0 resolution: "p-cancelable@npm:1.1.0" @@ -40607,15 +39110,6 @@ __metadata: languageName: node linkType: hard -"p-limit@npm:^4.0.0": - version: 4.0.0 - resolution: "p-limit@npm:4.0.0" - dependencies: - yocto-queue: "npm:^1.0.0" - checksum: 10/01d9d70695187788f984226e16c903475ec6a947ee7b21948d6f597bed788e3112cc7ec2e171c1d37125057a5f45f3da21d8653e04a3a793589e12e9e80e756b - languageName: node - linkType: hard - "p-locate@npm:^3.0.0": version: 3.0.0 resolution: "p-locate@npm:3.0.0" @@ -40643,15 +39137,6 @@ __metadata: languageName: node linkType: hard -"p-locate@npm:^6.0.0": - version: 6.0.0 - resolution: "p-locate@npm:6.0.0" - dependencies: - p-limit: "npm:^4.0.0" - checksum: 10/2bfe5234efa5e7a4e74b30a5479a193fdd9236f8f6b4d2f3f69e3d286d9a7d7ab0c118a2a50142efcf4e41625def635bd9332d6cbf9cc65d85eb0718c579ab38 - languageName: node - linkType: hard - "p-map@npm:^2.0.0": version: 2.1.0 resolution: "p-map@npm:2.1.0" @@ -40798,10 +39283,10 @@ __metadata: languageName: node linkType: hard -"packageurl-js@npm:1.2.0": - version: 1.2.0 - resolution: "packageurl-js@npm:1.2.0" - checksum: 10/b780ad6cf9f75055effafe8fbed37617eb1924e3dc5b055fb3ecceaaaa93da73ea1508a3874b04bd13342a77bd852b70a4e52596c171cbc57840c4b8452d2d56 +"packageurl-js@npm:2.0.1": + version: 2.0.1 + resolution: "packageurl-js@npm:2.0.1" + checksum: 10/5fdb2b89e5c39dbb87806ef303bc558da0f8c178b8afb2647979d49212039f2cccc6c0135816819d061c6b12b47e8c6bb8c34a2b9fdd8684b9fb975dcf3bc73b languageName: node linkType: hard @@ -40913,6 +39398,21 @@ __metadata: languageName: node linkType: hard +"parse-entities@npm:^4.0.0": + version: 4.0.2 + resolution: "parse-entities@npm:4.0.2" + dependencies: + "@types/unist": "npm:^2.0.0" + character-entities-legacy: "npm:^3.0.0" + character-reference-invalid: "npm:^2.0.0" + decode-named-character-reference: "npm:^1.0.0" + is-alphanumerical: "npm:^2.0.0" + is-decimal: "npm:^2.0.0" + is-hexadecimal: "npm:^2.0.0" + checksum: 10/b0ce693d0b3d7ed1cea6fe814e6e077c71532695f01178e846269e9a2bc2f7ff34ca4bb8db80b48af0451100f25bb010df6591c9bb6306e4680ccb423d1e4038 + languageName: node + linkType: hard + "parse-json@npm:^5.0.0, parse-json@npm:^5.2.0": version: 5.2.0 resolution: "parse-json@npm:5.2.0" @@ -40932,13 +39432,6 @@ __metadata: languageName: node linkType: hard -"parse-ms@npm:^4.0.0": - version: 4.0.0 - resolution: "parse-ms@npm:4.0.0" - checksum: 10/673c801d9f957ff79962d71ed5a24850163f4181a90dd30c4e3666b3a804f53b77f1f0556792e8b2adbb5d58757907d1aa51d7d7dc75997c2a56d72937cbc8b7 - languageName: node - linkType: hard - "parse-multipart-data@npm:^1.4.0": version: 1.5.0 resolution: "parse-multipart-data@npm:1.5.0" @@ -40971,16 +39464,48 @@ __metadata: languageName: node linkType: hard -"parse5@npm:^7.0.0, parse5@npm:^7.1.2": - version: 7.2.0 - resolution: "parse5@npm:7.2.0" +"parse5-htmlparser2-tree-adapter@npm:^6.0.0": + version: 6.0.1 + resolution: "parse5-htmlparser2-tree-adapter@npm:6.0.1" dependencies: - entities: "npm:^4.5.0" - checksum: 10/49dabfe848f00e8cad8d9198a094d667fbdecbfa5143ddf8fb708e499b5ba76426c16135c8993b1d8e01827b92e8cfab0a9a248afa6ad7cc6f38aecf5bd017e6 + parse5: "npm:^6.0.1" + checksum: 10/3400a2cd1ad450b2fe148544154f86ea53d3ed6b6eab56c78bb43b9629d3dfe9f580dffd75bbf32be134ffef645b68081fc764bf75c210f236ab9c5c8c38c252 languageName: node linkType: hard -"parseurl@npm:^1.3.2, parseurl@npm:^1.3.3, parseurl@npm:~1.3.2, parseurl@npm:~1.3.3": +"parse5@npm:^5.1.1": + version: 5.1.1 + resolution: "parse5@npm:5.1.1" + checksum: 10/5b509744cfe81488a33be05578df490c460690e64519fa67f0a0acb9c1bca05914e8acad17a977e2cf5964a000e43959b40024f0c243dd6595dd0cca8a32f71b + languageName: node + linkType: hard + +"parse5@npm:^6.0.0, parse5@npm:^6.0.1": + version: 6.0.1 + resolution: "parse5@npm:6.0.1" + checksum: 10/dfb110581f62bd1425725a7c784ae022a24669bd0efc24b58c71fc731c4d868193e2ebd85b74cde2dbb965e4dcf07059b1e651adbec1b3b5267531bd132fdb75 + languageName: node + linkType: hard + +"parse5@npm:^7.0.0, parse5@npm:^7.1.2": + version: 7.3.0 + resolution: "parse5@npm:7.3.0" + dependencies: + entities: "npm:^6.0.0" + checksum: 10/b0e48be20b820c655b138b86fa6fb3a790de6c891aa2aba536524f8027b4dca4fe538f11a0e5cf2f6f847d120dbb9e4822dcaeb933ff1e10850a2ef0154d1d88 + languageName: node + linkType: hard + +"parse5@npm:^8.0.0": + version: 8.0.0 + resolution: "parse5@npm:8.0.0" + dependencies: + entities: "npm:^6.0.0" + checksum: 10/1973850932bb1cbd52ab64502761489fbe1bb43a52dee7ce41aac0b6c33a51a92aaee04661590b0912b739ae9ee316bce4c78c8ea34af42a7e522c983c3c6cf5 + languageName: node + linkType: hard + +"parseurl@npm:^1.3.3, parseurl@npm:~1.3.2, parseurl@npm:~1.3.3": version: 1.3.3 resolution: "parseurl@npm:1.3.3" checksum: 10/407cee8e0a3a4c5cd472559bca8b6a45b82c124e9a4703302326e9ab60fc1081442ada4e02628efef1eb16197ddc7f8822f5a91fd7d7c86b51f530aedb17dfa2 @@ -41161,10 +39686,10 @@ __metadata: languageName: node linkType: hard -"path-exists@npm:^5.0.0": - version: 5.0.0 - resolution: "path-exists@npm:5.0.0" - checksum: 10/8ca842868cab09423994596eb2c5ec2a971c17d1a3cb36dbf060592c730c725cd524b9067d7d2a1e031fef9ba7bd2ac6dc5ec9fb92aa693265f7be3987045254 +"path-expression-matcher@npm:^1.1.3, path-expression-matcher@npm:^1.2.0": + version: 1.2.0 + resolution: "path-expression-matcher@npm:1.2.0" + checksum: 10/eab23babd9a97d6cf4841a99825c3e990b70b2b29ea6529df9fb6a1f3953befbc68e9e282a373d7a75aff5dc6542d05a09ee2df036ff9bfddf5e1627b769875b languageName: node linkType: hard @@ -41196,13 +39721,6 @@ __metadata: languageName: node linkType: hard -"path-key@npm:^4.0.0": - version: 4.0.0 - resolution: "path-key@npm:4.0.0" - checksum: 10/8e6c314ae6d16b83e93032c61020129f6f4484590a777eed709c4a01b50e498822b00f76ceaf94bc64dbd90b327df56ceadce27da3d83393790f1219e07721d7 - languageName: node - linkType: hard - "path-parse@npm:^1.0.7": version: 1.0.7 resolution: "path-parse@npm:1.0.7" @@ -41220,20 +39738,13 @@ __metadata: languageName: node linkType: hard -"path-scurry@npm:^2.0.0": - version: 2.0.0 - resolution: "path-scurry@npm:2.0.0" +"path-scurry@npm:^2.0.2": + version: 2.0.2 + resolution: "path-scurry@npm:2.0.2" dependencies: lru-cache: "npm:^11.0.0" minipass: "npm:^7.1.2" - checksum: 10/285ae0c2d6c34ae91dc1d5378ede21981c9a2f6de1ea9ca5a88b5a270ce9763b83dbadc7a324d512211d8d36b0c540427d3d0817030849d97a60fa840a2c59ec - languageName: node - linkType: hard - -"path-to-regexp@npm:0.1.12": - version: 0.1.12 - resolution: "path-to-regexp@npm:0.1.12" - checksum: 10/2e30f6a0144679c1f95c98e166b96e6acd1e72be9417830fefc8de7ac1992147eb9a4c7acaa59119fb1b3c34eec393b2129ef27e24b2054a3906fc4fb0d1398e + checksum: 10/2b4257422bcb870a4c2d205b3acdbb213a72f5e2250f61c80f79c9d014d010f82bdf8584441612c8e1fa4eb098678f5704a66fa8377d72646bad4be38e57a2c3 languageName: node linkType: hard @@ -41244,10 +39755,10 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:8.2.0, path-to-regexp@npm:^8.0.0, path-to-regexp@npm:^8.1.0, path-to-regexp@npm:^8.2.0": - version: 8.2.0 - resolution: "path-to-regexp@npm:8.2.0" - checksum: 10/23378276a172b8ba5f5fb824475d1818ca5ccee7bbdb4674701616470f23a14e536c1db11da9c9e6d82b82c556a817bbf4eee6e41b9ed20090ef9427cbb38e13 +"path-to-regexp@npm:8.3.0": + version: 8.3.0 + resolution: "path-to-regexp@npm:8.3.0" + checksum: 10/568f148fc64f5fd1ecebf44d531383b28df924214eabf5f2570dce9587a228e36c37882805ff02d71c6209b080ea3ee6a4d2b712b5df09741b67f1f3cf91e55a languageName: node linkType: hard @@ -41258,6 +39769,20 @@ __metadata: languageName: node linkType: hard +"path-to-regexp@npm:^8.0.0, path-to-regexp@npm:^8.1.0, path-to-regexp@npm:^8.3.0": + version: 8.4.0 + resolution: "path-to-regexp@npm:8.4.0" + checksum: 10/6864561ceacaece330a2213c6eb21505519673a65b4249e0e5d518984528f93b302b8bf85ccafc4f9d7f359f919d8b118dc00fdb0b26ded3d21e8802cffdfcb8 + languageName: node + linkType: hard + +"path-to-regexp@npm:~0.1.12": + version: 0.1.13 + resolution: "path-to-regexp@npm:0.1.13" + checksum: 10/f1e4bdedc4fd41a3b8dd76e8b2e1183105348c6b205badc072581ca63dc6aa7976a8a67feaffcf0e505f51ac12cb1a2de7f3fef3e9085b6849e76232d73ddcba + languageName: node + linkType: hard + "path-type@npm:^4.0.0": version: 4.0.0 resolution: "path-type@npm:4.0.0" @@ -41265,10 +39790,10 @@ __metadata: languageName: node linkType: hard -"path-type@npm:^5.0.0": - version: 5.0.0 - resolution: "path-type@npm:5.0.0" - checksum: 10/15ec24050e8932c2c98d085b72cfa0d6b4eeb4cbde151a0a05726d8afae85784fc5544f733d8dfc68536587d5143d29c0bd793623fad03d7e61cc00067291cd5 +"pathe@npm:^2.0.3": + version: 2.0.3 + resolution: "pathe@npm:2.0.3" + checksum: 10/01e9a69928f39087d96e1751ce7d6d50da8c39abf9a12e0ac2389c42c83bc76f78c45a475bd9026a02e6a6f79be63acc75667df855862fe567d99a00a540d23d languageName: node linkType: hard @@ -41337,10 +39862,10 @@ __metadata: languageName: node linkType: hard -"pg-cloudflare@npm:^1.1.1": - version: 1.1.1 - resolution: "pg-cloudflare@npm:1.1.1" - checksum: 10/45ca0c7926967ec9e66a9efc73ca57e3e933671b541bc774631a02ce683e7f658d0a4e881119b3f61486f38e344ae1b008d3a20eb5e21701c5fa8ff8382c5538 +"pg-cloudflare@npm:^1.3.0": + version: 1.3.0 + resolution: "pg-cloudflare@npm:1.3.0" + checksum: 10/04007ebbd314bdc8e5983d5d0f71a942f1915d2312ed84894d55475010559665bcbb9941d55523d36be6386cd83490dffb5b1ea98ffbbc82a140ef5dfb68ab8d languageName: node linkType: hard @@ -41351,10 +39876,10 @@ __metadata: languageName: node linkType: hard -"pg-connection-string@npm:^2.3.0, pg-connection-string@npm:^2.5.0, pg-connection-string@npm:^2.7.0": - version: 2.7.0 - resolution: "pg-connection-string@npm:2.7.0" - checksum: 10/68015a8874b7ca5dad456445e4114af3d2602bac2fdb8069315ecad0ff9660ec93259b9af7186606529ac4f6f72a06831e6f20897a689b16cc7fda7ca0e247fd +"pg-connection-string@npm:^2.12.0, pg-connection-string@npm:^2.3.0, pg-connection-string@npm:^2.5.0": + version: 2.12.0 + resolution: "pg-connection-string@npm:2.12.0" + checksum: 10/03e5462c1f4da57166344a9eae105f95af6887a501dfe4ca2feb85ca8d351162afe9fc581cb27d44ecdaecc59a8bd55a1ec35b66714b01a9db148f0a91c0b36c languageName: node linkType: hard @@ -41372,23 +39897,23 @@ __metadata: languageName: node linkType: hard -"pg-pool@npm:^3.8.0": - version: 3.8.0 - resolution: "pg-pool@npm:3.8.0" +"pg-pool@npm:^3.13.0": + version: 3.13.0 + resolution: "pg-pool@npm:3.13.0" peerDependencies: pg: ">=8.0" - checksum: 10/be6b7c6932fa177dc69ca8980d959f24b4979911465ac11656b8ff86707cba035e776416d8aac1029e8cc4d8eb8989ed2cc85a33ac6b76e266b20a81284436d8 + checksum: 10/0addd11b3f3f49fb1d16bf181583411e8a9809730dbfc5edb9bbf5110bc02beb4ac346c52a121f446ff76bcd420a76a3409952ea743aa5f6a04705ea953bd8aa languageName: node linkType: hard -"pg-protocol@npm:*, pg-protocol@npm:^1.8.0": - version: 1.8.0 - resolution: "pg-protocol@npm:1.8.0" - checksum: 10/52f67d8161ae4afb1dbf96f6ad12a2ecf478dbb0b80baa239047cd562dee378961fd446f0a1cfc1fd323e052fbb3df47e886c5d9d86a2803a000d36682b29094 +"pg-protocol@npm:*, pg-protocol@npm:^1.13.0": + version: 1.13.0 + resolution: "pg-protocol@npm:1.13.0" + checksum: 10/302cd3920df00f178519693557c34949d64c8b3af7e2c12772b14f61547b947e4c761b4ca2319dbba5b0906207bb1b535cbfc7006d40d47fd823e277c2690a71 languageName: node linkType: hard -"pg-types@npm:^2.1.0, pg-types@npm:^2.2.0": +"pg-types@npm:2.2.0, pg-types@npm:^2.2.0": version: 2.2.0 resolution: "pg-types@npm:2.2.0" dependencies: @@ -41401,16 +39926,16 @@ __metadata: languageName: node linkType: hard -"pg@npm:^8.11.3, pg@npm:^8.9.0": - version: 8.14.1 - resolution: "pg@npm:8.14.1" +"pg@npm:^8.11.3, pg@npm:^8.7.3, pg@npm:^8.9.0": + version: 8.20.0 + resolution: "pg@npm:8.20.0" dependencies: - pg-cloudflare: "npm:^1.1.1" - pg-connection-string: "npm:^2.7.0" - pg-pool: "npm:^3.8.0" - pg-protocol: "npm:^1.8.0" - pg-types: "npm:^2.1.0" - pgpass: "npm:1.x" + pg-cloudflare: "npm:^1.3.0" + pg-connection-string: "npm:^2.12.0" + pg-pool: "npm:^3.13.0" + pg-protocol: "npm:^1.13.0" + pg-types: "npm:2.2.0" + pgpass: "npm:1.0.5" peerDependencies: pg-native: ">=3.0.1" dependenciesMeta: @@ -41419,16 +39944,16 @@ __metadata: peerDependenciesMeta: pg-native: optional: true - checksum: 10/45f2d5719fd74a6a4784c5115c0ff482af92d1e5b101bf423160b6a983e37cc2fad4a7eea2a06f27e6f8bdb8abce23486d2d522c8c52c90f68a2bc897f0553c4 + checksum: 10/a30b99c799eddbd4f7f98bef906fe25e17c17d7d8918bc7545108947388df0d9c490858b04dabd37ce0063f40b1e58987b0079f99d5a661fb66f40c17f172bfc languageName: node linkType: hard -"pgpass@npm:1.x": - version: 1.0.2 - resolution: "pgpass@npm:1.0.2" +"pgpass@npm:1.0.5": + version: 1.0.5 + resolution: "pgpass@npm:1.0.5" dependencies: - split: "npm:^1.0.0" - checksum: 10/4f09d81d5aa9e3e409e2a58dc71f889ca92233d66772adcb810e53e637b49013ae84b8dd831aa5e6f1413bffb4b9b1eafd1a05227c8cf05c39f5cd276d4e9492 + split2: "npm:^4.1.0" + checksum: 10/0a6f3bf76e36bdb3c20a7e8033140c732767bba7e81f845f7489fc3123a2bd6e3b8e704f08cba86b117435414b5d2422e20ba9d5f2efb6f0c75c9efca73e8e87 languageName: node linkType: hard @@ -41468,9 +39993,9 @@ __metadata: linkType: hard "picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.2.3, picomatch@npm:^2.3.0, picomatch@npm:^2.3.1": - version: 2.3.1 - resolution: "picomatch@npm:2.3.1" - checksum: 10/60c2595003b05e4535394d1da94850f5372c9427ca4413b71210f437f7b2ca091dbd611c45e8b37d10036fa8eade25c1b8951654f9d3973bfa66a2ff4d3b08bc + version: 2.3.2 + resolution: "picomatch@npm:2.3.2" + checksum: 10/b788ef8148a2415b9dec12f0bb350ae6a5830f8f1950e472abc2f5225494debf7d1b75eb031df0ceaea9e8ec3e7bad599e8dbf3c60d61b42be429ba41bff4426 languageName: node linkType: hard @@ -41481,12 +40006,10 @@ __metadata: languageName: node linkType: hard -"pidtree@npm:^0.6.0": - version: 0.6.0 - resolution: "pidtree@npm:0.6.0" - bin: - pidtree: bin/pidtree.js - checksum: 10/ea67fb3159e170fd069020e0108ba7712df9f0fd13c8db9b2286762856ddce414fb33932e08df4bfe36e91fe860b51852aee49a6f56eb4714b69634343add5df +"picoquery@npm:^2.5.0": + version: 2.5.0 + resolution: "picoquery@npm:2.5.0" + checksum: 10/58e123cd497a5a1a5fd368f58c2825cd97814be5ddf85f59b7a11e87a836f95c9bacdd43b3be9d3e7aa4f16c43a5e7c0f5af4227d1a5616c69b3ab966fe0b3a6 languageName: node linkType: hard @@ -41560,7 +40083,7 @@ __metadata: languageName: node linkType: hard -"pirates@npm:^4.0.1, pirates@npm:^4.0.4, pirates@npm:^4.0.6": +"pirates@npm:^4.0.1, pirates@npm:^4.0.6, pirates@npm:^4.0.7": version: 4.0.7 resolution: "pirates@npm:4.0.7" checksum: 10/2427f371366081ae42feb58214f04805d6b41d6b84d74480ebcc9e0ddbd7105a139f7c653daeaf83ad8a1a77214cf07f64178e76de048128fec501eab3305a96 @@ -41624,27 +40147,41 @@ __metadata: languageName: node linkType: hard -"playwright-core@npm:1.56.1": - version: 1.56.1 - resolution: "playwright-core@npm:1.56.1" - bin: - playwright-core: cli.js - checksum: 10/df785eb3b3a8392b10dcde5f768e09b7fe459a7b06ed81180da69e048f2154b761f86d79572c2b62037a1f18a44e4ace72f5b6547f4f473b4ab13ab1d94007d2 +"pkijs@npm:^3.3.3": + version: 3.3.3 + resolution: "pkijs@npm:3.3.3" + dependencies: + "@noble/hashes": "npm:1.4.0" + asn1js: "npm:^3.0.6" + bytestreamjs: "npm:^2.0.1" + pvtsutils: "npm:^1.3.6" + pvutils: "npm:^1.1.3" + tslib: "npm:^2.8.1" + checksum: 10/51ef42d8332fcb9e81f5cca9355b1a2aeacdbd77483cacc2a1c589036887a5a1dc6e7a68abd7cca2f0d605f68e038cc877e1fae4c38b3d358fa84a327dccaadd languageName: node linkType: hard -"playwright@npm:1.56.1": - version: 1.56.1 - resolution: "playwright@npm:1.56.1" +"playwright-core@npm:1.58.2": + version: 1.58.2 + resolution: "playwright-core@npm:1.58.2" + bin: + playwright-core: cli.js + checksum: 10/8a98fcf122167e8703d525db2252de0e3da4ab9110ab6ea9951247e52d846310eb25ea2c805e1b7ccb54b4010c44e5adc3a76aae6da02f34324ccc3e76683bb1 + languageName: node + linkType: hard + +"playwright@npm:1.58.2": + version: 1.58.2 + resolution: "playwright@npm:1.58.2" dependencies: fsevents: "npm:2.3.2" - playwright-core: "npm:1.56.1" + playwright-core: "npm:1.58.2" dependenciesMeta: fsevents: optional: true bin: playwright: cli.js - checksum: 10/f1743f93b26f1d497257771428d93f3c9ed2d75b00d935f0cd1556ff2dc61d47f2df8b381d752fbd2c47082b685f0ffe4cc4b7ba440d7b4ba3a08572aec58fba + checksum: 10/d89d6c8a32388911b9aff9ee0f1a90076219f15c804f2b287db048b9e9cde182aea3131fac1959051d25189ed4218ec4272b137c83cd7f9cd24781cbc77edd86 languageName: node linkType: hard @@ -41655,13 +40192,6 @@ __metadata: languageName: node linkType: hard -"pluralize@npm:^7.0.0": - version: 7.0.0 - resolution: "pluralize@npm:7.0.0" - checksum: 10/905274e679d3802650fdfdd977434757d4680082da7a23c0938a608d1d5c8246790b62dc15ff1f737b0d57baa6ad2f6ebb0857b1950435a583e32af76ee58e1f - languageName: node - linkType: hard - "pony-cause@npm:^1.1.1": version: 1.1.1 resolution: "pony-cause@npm:1.1.1" @@ -41789,6 +40319,19 @@ __metadata: languageName: node linkType: hard +"postcss-import@npm:^16.1.0": + version: 16.1.1 + resolution: "postcss-import@npm:16.1.1" + dependencies: + postcss-value-parser: "npm:^4.0.0" + read-cache: "npm:^1.0.0" + resolve: "npm:^1.1.7" + peerDependencies: + postcss: ^8.0.0 + checksum: 10/2afac2b6e25f263f45ac2168c5f5e4b2e49e3d44c620338138fe89cf81bd83e6056784a26d54c58611d05dda18bc56069212484ec750bbf6d2e29b623460a7f9 + languageName: node + linkType: hard + "postcss-load-config@npm:^3.0.0": version: 3.0.1 resolution: "postcss-load-config@npm:3.0.1" @@ -42110,7 +40653,7 @@ __metadata: languageName: node linkType: hard -"postcss-value-parser@npm:^4.0.2, postcss-value-parser@npm:^4.1.0, postcss-value-parser@npm:^4.2.0": +"postcss-value-parser@npm:^4.0.0, postcss-value-parser@npm:^4.0.2, postcss-value-parser@npm:^4.1.0, postcss-value-parser@npm:^4.2.0": version: 4.2.0 resolution: "postcss-value-parser@npm:4.2.0" checksum: 10/e4e4486f33b3163a606a6ed94f9c196ab49a37a7a7163abfcd469e5f113210120d70b8dd5e33d64636f41ad52316a3725655421eb9a1094f1bcab1db2f555c62 @@ -42131,13 +40674,13 @@ __metadata: linkType: hard "postcss@npm:^8.1.0, postcss@npm:^8.4.33, postcss@npm:^8.5.1, postcss@npm:^8.5.6": - version: 8.5.6 - resolution: "postcss@npm:8.5.6" + version: 8.5.8 + resolution: "postcss@npm:8.5.8" dependencies: nanoid: "npm:^3.3.11" picocolors: "npm:^1.1.1" source-map-js: "npm:^1.2.1" - checksum: 10/9e4fbe97574091e9736d0e82a591e29aa100a0bf60276a926308f8c57249698935f35c5d2f4e80de778d0cbb8dcffab4f383d85fd50c5649aca421c3df729b86 + checksum: 10/cbacbfd7f767e2c820d4bf09a3a744834dd7d14f69ff08d1f57b1a7defce9ae5efcf31981890d9697a972a64e9965de677932ef28e4c8ba23a87aad45b82c459 languageName: node linkType: hard @@ -42199,16 +40742,16 @@ __metadata: languageName: node linkType: hard -"prebuild-install@npm:^7.1.1, prebuild-install@npm:^7.1.2": - version: 7.1.2 - resolution: "prebuild-install@npm:7.1.2" +"prebuild-install@npm:^7.0.1, prebuild-install@npm:^7.1.1": + version: 7.1.3 + resolution: "prebuild-install@npm:7.1.3" dependencies: detect-libc: "npm:^2.0.0" expand-template: "npm:^2.0.3" github-from-package: "npm:0.0.0" minimist: "npm:^1.2.3" mkdirp-classic: "npm:^0.5.3" - napi-build-utils: "npm:^1.0.1" + napi-build-utils: "npm:^2.0.0" node-abi: "npm:^3.3.0" pump: "npm:^3.0.0" rc: "npm:^1.2.7" @@ -42217,7 +40760,7 @@ __metadata: tunnel-agent: "npm:^0.6.0" bin: prebuild-install: bin.js - checksum: 10/32d5c026cc978dd02762b9ad3c765178aee8383aeac4303fed3cd226eff53100db038d4791b03ae1ebc7d213a7af392d26e32095579cedb8dba1d00ad08ecd46 + checksum: 10/1b7e4c00d2750b532a4fc2a83ffb0c5fefa1b6f2ad071896ead15eeadc3255f5babd816949991af083cf7429e375ae8c7d1c51f73658559da36f948a020a3a11 languageName: node linkType: hard @@ -42265,13 +40808,6 @@ __metadata: languageName: node linkType: hard -"prelude-ls@npm:~1.1.2": - version: 1.1.2 - resolution: "prelude-ls@npm:1.1.2" - checksum: 10/946a9f60d3477ca6b7d4c5e8e452ad1b98dc8aaa992cea939a6b926ac16cc4129d7217c79271dc808b5814b1537ad0af37f29a942e2eafbb92cfc5a1c87c38cb - languageName: node - linkType: hard - "prepend-http@npm:^2.0.0": version: 2.0.0 resolution: "prepend-http@npm:2.0.0" @@ -42279,13 +40815,6 @@ __metadata: languageName: node linkType: hard -"presentable-error@npm:^0.0.1": - version: 0.0.1 - resolution: "presentable-error@npm:0.0.1" - checksum: 10/013809ee7a47ced847a8d860e9b89a56cdd8c4f1ad04ad8da1e58fd60843f77f497d204146bb15aaa9793d3b94ad8626eed01256fc9eb5839a545af2000a5fa4 - languageName: node - linkType: hard - "prettier@npm:^2.2.1, prettier@npm:^2.7.1": version: 2.8.8 resolution: "prettier@npm:2.8.8" @@ -42295,7 +40824,7 @@ __metadata: languageName: node linkType: hard -"pretty-bytes@npm:^5.1.0, pretty-bytes@npm:^5.3.0": +"pretty-bytes@npm:^5.3.0": version: 5.6.0 resolution: "pretty-bytes@npm:5.6.0" checksum: 10/9c082500d1e93434b5b291bd651662936b8bd6204ec9fa17d563116a192d6d86b98f6d328526b4e8d783c07d5499e2614a807520249692da9ec81564b2f439cd @@ -42312,6 +40841,17 @@ __metadata: languageName: node linkType: hard +"pretty-format@npm:30.2.0, pretty-format@npm:^30.0.0": + version: 30.2.0 + resolution: "pretty-format@npm:30.2.0" + dependencies: + "@jest/schemas": "npm:30.0.5" + ansi-styles: "npm:^5.2.0" + react-is: "npm:^18.3.1" + checksum: 10/725890d648e3400575eebc99a334a4cd1498e0d36746313913706bbeea20ada27e17c184a3cd45c50f705c16111afa829f3450233fc0fda5eed293c69757e926 + languageName: node + linkType: hard + "pretty-format@npm:^27.0.2": version: 27.5.1 resolution: "pretty-format@npm:27.5.1" @@ -42323,7 +40863,7 @@ __metadata: languageName: node linkType: hard -"pretty-format@npm:^29.0.0, pretty-format@npm:^29.7.0": +"pretty-format@npm:^29.7.0": version: 29.7.0 resolution: "pretty-format@npm:29.7.0" dependencies: @@ -42343,15 +40883,6 @@ __metadata: languageName: node linkType: hard -"pretty-ms@npm:^9.0.0": - version: 9.2.0 - resolution: "pretty-ms@npm:9.2.0" - dependencies: - parse-ms: "npm:^4.0.0" - checksum: 10/a65a1d81560867f4f7128862fdbf0e1c2d3c5607bf75cae7758bf8111e2c4b744be46e084704125a38ba918bb43defa7a53aaff0f48c5c2d95367d3148c980d9 - languageName: node - linkType: hard - "printj@npm:~1.1.0": version: 1.1.2 resolution: "printj@npm:1.1.2" @@ -42382,13 +40913,6 @@ __metadata: languageName: node linkType: hard -"proc-log@npm:^4.1.0": - version: 4.2.0 - resolution: "proc-log@npm:4.2.0" - checksum: 10/4e1394491b717f6c1ade15c570ecd4c2b681698474d3ae2d303c1e4b6ab9455bd5a81566211e82890d5a5ae9859718cc6954d5150bb18b09b72ecb297beae90a - languageName: node - linkType: hard - "proc-log@npm:^5.0.0": version: 5.0.0 resolution: "proc-log@npm:5.0.0" @@ -42396,6 +40920,13 @@ __metadata: languageName: node linkType: hard +"proc-log@npm:^6.0.0": + version: 6.1.0 + resolution: "proc-log@npm:6.1.0" + checksum: 10/9033f30f168ed5a0991b773d0c50ff88384c4738e9a0a67d341de36bf7293771eed648ab6a0562f62276da12fde91f3bbfc75ffff6e71ad49aafd74fc646be66 + languageName: node + linkType: hard + "process-nextick-args@npm:^2.0.0, process-nextick-args@npm:~2.0.0": version: 2.0.1 resolution: "process-nextick-args@npm:2.0.1" @@ -42434,15 +40965,6 @@ __metadata: languageName: node linkType: hard -"promise-deferred@npm:^2.0.3": - version: 2.0.4 - resolution: "promise-deferred@npm:2.0.4" - dependencies: - promise: "npm:^8.3.0" - checksum: 10/1d0e306d54a7436e288836c0784abdf11798011a6c3309f4ce8e24564ba958c41ca0d21bb7ec95386f04ac8f9691fdd8e3dd0af5176b496a2303d00db96acf5a - languageName: node - linkType: hard - "promise-inflight@npm:^1.0.1": version: 1.0.1 resolution: "promise-inflight@npm:1.0.1" @@ -42490,26 +41012,7 @@ __metadata: languageName: node linkType: hard -"promise@npm:^8.3.0": - version: 8.3.0 - resolution: "promise@npm:8.3.0" - dependencies: - asap: "npm:~2.0.6" - checksum: 10/55e9d0d723c66810966bc055c6c77a3658c0af7e4a8cc88ea47aeaf2949ca0bd1de327d9c631df61236f5406ad478384fa19a77afb3f88c0303eba9e5eb0a8d8 - languageName: node - linkType: hard - -"promiseback@npm:^2.0.2": - version: 2.0.3 - resolution: "promiseback@npm:2.0.3" - dependencies: - is-callable: "npm:^1.1.5" - promise-deferred: "npm:^2.0.3" - checksum: 10/39716e64ac75b3a5c58532493f594d4788267ee13e2aeee5c60b448eb17e8f98c8ff4778c5497aed1594e29c428710ae21c83671c87c24b3d2c42f0c359d6e55 - languageName: node - linkType: hard - -"prompts@npm:^2.0.1, prompts@npm:^2.4.2": +"prompts@npm:^2.4.2": version: 2.4.2 resolution: "prompts@npm:2.4.2" dependencies: @@ -42541,12 +41044,13 @@ __metadata: languageName: node linkType: hard -"properties-reader@npm:^2.3.0": - version: 2.3.0 - resolution: "properties-reader@npm:2.3.0" +"properties-reader@npm:^3.0.1": + version: 3.0.1 + resolution: "properties-reader@npm:3.0.1" dependencies: - mkdirp: "npm:^1.0.4" - checksum: 10/0b41eb4136dc278ae0d97968ccce8de2d48d321655b319192e31f2424f1c6e052182204671e65aa8967216360cb3e7cbd9129830062e058fe9d6a1d74964c29a + "@kwsites/file-exists": "npm:^1.1.1" + mkdirp: "npm:^3.0.1" + checksum: 10/5a96c33dad9925c399bf3c3e343f3eb801fa1b4802330d1bfbf1e7d7639dafa935deb6c60be38b292544b57793b03ba1eaf5191cc3a7d018cda7bd6ead19a15c languageName: node linkType: hard @@ -42566,6 +41070,13 @@ __metadata: languageName: node linkType: hard +"property-information@npm:^7.0.0": + version: 7.1.0 + resolution: "property-information@npm:7.1.0" + checksum: 10/896d38a52ad7170de73f832d277c69e76a9605d941ebb3f0d6e56271414a7fdf95ff6d2819e68036b8a0c7d2d4d88bf1d4a5765c032cb19c2343567ee3a14b15 + languageName: node + linkType: hard + "proto3-json-serializer@npm:^2.0.2": version: 2.0.2 resolution: "proto3-json-serializer@npm:2.0.2" @@ -42575,9 +41086,9 @@ __metadata: languageName: node linkType: hard -"protobufjs@npm:^7.0.0, protobufjs@npm:^7.2.5, protobufjs@npm:^7.2.6, protobufjs@npm:^7.3.0, protobufjs@npm:^7.3.2, protobufjs@npm:^7.4.0": - version: 7.5.3 - resolution: "protobufjs@npm:7.5.3" +"protobufjs@npm:^7.0.0, protobufjs@npm:^7.2.5, protobufjs@npm:^7.2.6, protobufjs@npm:^7.3.2, protobufjs@npm:^7.4.0, protobufjs@npm:^7.5.3": + version: 7.5.4 + resolution: "protobufjs@npm:7.5.4" dependencies: "@protobufjs/aspromise": "npm:^1.1.2" "@protobufjs/base64": "npm:^1.1.2" @@ -42591,7 +41102,7 @@ __metadata: "@protobufjs/utf8": "npm:^1.1.0" "@types/node": "npm:>=13.7.0" long: "npm:^5.0.0" - checksum: 10/3e412d2e2f799875dcdac1b43508f465a499dd3ffd9d24073669702589ef016529905617a12a967b1a8cfbe803a638b494cc3ed8db035605c7570d1a7fc094c9 + checksum: 10/88d677bb6f11a2ecec63fdd053dfe6d31120844d04e865efa9c8fbe0674cd077d6624ecfdf014018a20dcb114ae2a59c1b21966dd8073e920650c71370966439 languageName: node linkType: hard @@ -42635,10 +41146,10 @@ __metadata: languageName: node linkType: hard -"psl@npm:^1.1.33": - version: 1.8.0 - resolution: "psl@npm:1.8.0" - checksum: 10/5f62a8eca06cb4a017983d15b92b0d38dc8699d637eabc8cb482c59b4106c9760f59cc8afabcb8bb7b98f0322907680d8f0f59226386fffab5248d180bc04578 +"proxy-from-env@npm:^2.1.0": + version: 2.1.0 + resolution: "proxy-from-env@npm:2.1.0" + checksum: 10/fbbaf4dab2a6231dc9e394903a5f66f20475e36b734335790b46feb9da07c37d6b32e2c02e3e2ea4d4b23774c53d8562e5b7cc73282cb43f4a597b7eacaee2ee languageName: node linkType: hard @@ -42680,7 +41191,7 @@ __metadata: languageName: node linkType: hard -"punycode@npm:^1.3.2, punycode@npm:^1.4.1": +"punycode@npm:^1.4.1": version: 1.4.1 resolution: "punycode@npm:1.4.1" checksum: 10/af2700dde1a116791ff8301348ff344c47d6c224e875057237d1b5112035655fb07a6175cfdb8bf0e3a8cdfd2dc82b3a622e0aefd605566c0e949a6d0d1256a4 @@ -42710,28 +41221,44 @@ __metadata: languageName: node linkType: hard -"pure-rand@npm:^6.0.0": - version: 6.0.4 - resolution: "pure-rand@npm:6.0.4" - checksum: 10/34fed0abe99d3db7ddc459c12e1eda6bff05db6a17f2017a1ae12202271ccf276fb223b442653518c719671c1b339bbf97f27ba9276dba0997c89e45c4e6a3bf +"pure-rand@npm:^7.0.0": + version: 7.0.1 + resolution: "pure-rand@npm:7.0.1" + checksum: 10/c61a576fda5032ec9763ecb000da4a8f19263b9e2f9ae9aa2759c8fbd9dc6b192b2ce78391ebd41abb394a5fedb7bcc4b03c9e6141ac8ab20882dd5717698b80 languageName: node linkType: hard -"qs@npm:6.13.0": - version: 6.13.0 - resolution: "qs@npm:6.13.0" +"pvtsutils@npm:^1.3.6": + version: 1.3.6 + resolution: "pvtsutils@npm:1.3.6" dependencies: - side-channel: "npm:^1.0.6" - checksum: 10/f548b376e685553d12e461409f0d6e5c59ec7c7d76f308e2a888fd9db3e0c5e89902bedd0754db3a9038eda5f27da2331a6f019c8517dc5e0a16b3c9a6e9cef8 + tslib: "npm:^2.8.1" + checksum: 10/d45b12f8526e13ecf15fe09b30cde65501f3300fd2a07c11b28a966d434d1f767c8a61597ecba2e19c7eb19ca0c740341a6babc67a4f741e08b1ef1095c71663 languageName: node linkType: hard -"qs@npm:^6.10.1, qs@npm:^6.10.3, qs@npm:^6.11.2, qs@npm:^6.12.2, qs@npm:^6.12.3, qs@npm:^6.14.0, qs@npm:^6.7.0, qs@npm:^6.9.4": - version: 6.14.0 - resolution: "qs@npm:6.14.0" +"pvutils@npm:^1.1.3": + version: 1.1.5 + resolution: "pvutils@npm:1.1.5" + checksum: 10/9a5a71603c72bf9ea3a4501e8251e3f7a56026ed059bf63a18bd9a30cac6c35cc8250b39eb6291c1cb204cdeb6660663ab9bb2c74e85a512919bb2d614e340ea + languageName: node + linkType: hard + +"qs@npm:^6.10.1, qs@npm:^6.10.3, qs@npm:^6.11.2, qs@npm:^6.12.2, qs@npm:^6.12.3, qs@npm:^6.14.0, qs@npm:^6.14.1, qs@npm:^6.7.0, qs@npm:^6.9.4": + version: 6.15.0 + resolution: "qs@npm:6.15.0" dependencies: side-channel: "npm:^1.1.0" - checksum: 10/a60e49bbd51c935a8a4759e7505677b122e23bf392d6535b8fc31c1e447acba2c901235ecb192764013cd2781723dc1f61978b5fdd93cc31d7043d31cdc01974 + checksum: 10/a3458f2f389285c3512e0ebc55522ee370ac7cb720ba9f0eff3e30fb2bb07631caf556c08e2a3d4481a371ac14faa9ceb7442a0610c5a7e55b23a5bdee7b701c + languageName: node + linkType: hard + +"qs@npm:~6.14.0": + version: 6.14.2 + resolution: "qs@npm:6.14.2" + dependencies: + side-channel: "npm:^1.1.0" + checksum: 10/682933a85bb4b7bd0d66e13c0a40d9e612b5e4bcc2cb9238f711a9368cd22d91654097a74fff93551e58146db282c56ac094957dfdc60ce64ea72c3c9d7779ac languageName: node linkType: hard @@ -42804,13 +41331,6 @@ __metadata: languageName: node linkType: hard -"rambda@npm:^9.1.0": - version: 9.2.1 - resolution: "rambda@npm:9.2.1" - checksum: 10/d33f766a42bb19cb19f4e3b20b645cdef9d2496685603a66d5dba83324cd991e9d4c152bceaf56df19ea87ed5a1d322f8b8982b227a8119b2e95a1c2034e04d6 - languageName: node - linkType: hard - "ramda-adjunct@npm:^5.0.0, ramda-adjunct@npm:^5.1.0": version: 5.1.0 resolution: "ramda-adjunct@npm:5.1.0" @@ -42888,11 +41408,11 @@ __metadata: linkType: hard "rate-limit-redis@npm:^4.2.0": - version: 4.2.3 - resolution: "rate-limit-redis@npm:4.2.3" + version: 4.3.1 + resolution: "rate-limit-redis@npm:4.3.1" peerDependencies: express-rate-limit: ">= 6" - checksum: 10/00c95868e48214b926272b2f01418695acd0487f766d36fec68992922037d43e67eeacc6854f38b8a511c69b9385b2de6994d08802a6aa422686dfd4c55e5ba0 + checksum: 10/6e02374832ced889a61829e327d03d021c50c0fc3ac86674d8c922cd3a50c0451091106811c308ed5553776a28b6acbc5313e2c9d37aecb198ab306f249de21b languageName: node linkType: hard @@ -42903,27 +41423,34 @@ __metadata: languageName: node linkType: hard -"raw-body@npm:2.5.2, raw-body@npm:^2.3.3, raw-body@npm:^2.4.1": - version: 2.5.2 - resolution: "raw-body@npm:2.5.2" - dependencies: - bytes: "npm:3.1.2" - http-errors: "npm:2.0.0" - iconv-lite: "npm:0.4.24" - unpipe: "npm:1.0.0" - checksum: 10/863b5171e140546a4d99f349b720abac4410338e23df5e409cfcc3752538c9caf947ce382c89129ba976f71894bd38b5806c774edac35ebf168d02aa1ac11a95 +"rate-limiter-flexible@npm:^8.0.1": + version: 8.3.0 + resolution: "rate-limiter-flexible@npm:8.3.0" + checksum: 10/9c8d7a3224e3a57fdf7da721dabdb4942eb7dd7b5f3aa4cd57e2185928d5842855a8dc2e1db5e0403987d369972d0567e98aade2c2daa71436ebdae704e5a8ff languageName: node linkType: hard -"raw-body@npm:^3.0.0": - version: 3.0.0 - resolution: "raw-body@npm:3.0.0" +"raw-body@npm:^2.3.3, raw-body@npm:^2.4.1, raw-body@npm:~2.5.3": + version: 2.5.3 + resolution: "raw-body@npm:2.5.3" dependencies: - bytes: "npm:3.1.2" - http-errors: "npm:2.0.0" - iconv-lite: "npm:0.6.3" - unpipe: "npm:1.0.0" - checksum: 10/2443429bbb2f9ae5c50d3d2a6c342533dfbde6b3173740b70fa0302b30914ff400c6d31a46b3ceacbe7d0925dc07d4413928278b494b04a65736fc17ca33e30c + bytes: "npm:~3.1.2" + http-errors: "npm:~2.0.1" + iconv-lite: "npm:~0.4.24" + unpipe: "npm:~1.0.0" + checksum: 10/f35759fe5a6548e7c529121ead1de4dd163f899749a5896c42e278479df2d9d7f98b5bb17312737c03617765e5a1433e586f717616e5cfbebc13b4738b820601 + languageName: node + linkType: hard + +"raw-body@npm:^3.0.0, raw-body@npm:^3.0.1": + version: 3.0.2 + resolution: "raw-body@npm:3.0.2" + dependencies: + bytes: "npm:~3.1.2" + http-errors: "npm:~2.0.1" + iconv-lite: "npm:~0.7.0" + unpipe: "npm:~1.0.0" + checksum: 10/4168c82157bd69175d5bd960e59b74e253e237b358213694946a427a6f750a18b8e150f036fed3421b3e83294b071a4e2bb01037a79ccacdac05360c63d3ebba languageName: node linkType: hard @@ -42988,96 +41515,40 @@ __metadata: languageName: node linkType: hard -"react-aria-components@npm:^1.13.0": - version: 1.13.0 - resolution: "react-aria-components@npm:1.13.0" +"react-aria-components@npm:~1.17.0": + version: 1.17.0 + resolution: "react-aria-components@npm:1.17.0" dependencies: - "@internationalized/date": "npm:^3.10.0" - "@internationalized/string": "npm:^3.2.7" - "@react-aria/autocomplete": "npm:3.0.0-rc.3" - "@react-aria/collections": "npm:^3.0.0" - "@react-aria/dnd": "npm:^3.11.3" - "@react-aria/focus": "npm:^3.21.2" - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/live-announcer": "npm:^3.4.4" - "@react-aria/overlays": "npm:^3.30.0" - "@react-aria/ssr": "npm:^3.9.10" - "@react-aria/textfield": "npm:^3.18.2" - "@react-aria/toolbar": "npm:3.0.0-beta.21" - "@react-aria/utils": "npm:^3.31.0" - "@react-aria/virtualizer": "npm:^4.1.10" - "@react-stately/autocomplete": "npm:3.0.0-beta.3" - "@react-stately/layout": "npm:^4.5.1" - "@react-stately/selection": "npm:^3.20.6" - "@react-stately/table": "npm:^3.15.1" - "@react-stately/utils": "npm:^3.10.8" - "@react-stately/virtualizer": "npm:^4.4.4" - "@react-types/form": "npm:^3.7.16" - "@react-types/grid": "npm:^3.3.6" - "@react-types/shared": "npm:^3.32.1" - "@react-types/table": "npm:^3.13.4" + "@internationalized/date": "npm:^3.12.1" + "@react-types/shared": "npm:^3.34.0" "@swc/helpers": "npm:^0.5.0" client-only: "npm:^0.0.1" - react-aria: "npm:^3.44.0" - react-stately: "npm:^3.42.0" - use-sync-external-store: "npm:^1.4.0" + react-aria: "npm:3.48.0" + react-stately: "npm:3.46.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/8bff1890ef342b0c32d6a50329ce4657bce2fcc91f2a2f903351f375354b186b47f265e7718caaff0ed9a5f630037f17649d85d00bc20a4928a6417204d75ff2 + checksum: 10/f2d22aa1ca31da1697c4f364c9fda670c889a959661051a7f1d4f2f96ea4db3ce232fca81613cbf82411eef7d10bd3c05fc628756d9b5ca807729e02e0b12d6b languageName: node linkType: hard -"react-aria@npm:^3.44.0": - version: 3.44.0 - resolution: "react-aria@npm:3.44.0" +"react-aria@npm:3.48.0, react-aria@npm:~3.48.0": + version: 3.48.0 + resolution: "react-aria@npm:3.48.0" dependencies: - "@internationalized/string": "npm:^3.2.7" - "@react-aria/breadcrumbs": "npm:^3.5.29" - "@react-aria/button": "npm:^3.14.2" - "@react-aria/calendar": "npm:^3.9.2" - "@react-aria/checkbox": "npm:^3.16.2" - "@react-aria/color": "npm:^3.1.2" - "@react-aria/combobox": "npm:^3.14.0" - "@react-aria/datepicker": "npm:^3.15.2" - "@react-aria/dialog": "npm:^3.5.31" - "@react-aria/disclosure": "npm:^3.1.0" - "@react-aria/dnd": "npm:^3.11.3" - "@react-aria/focus": "npm:^3.21.2" - "@react-aria/gridlist": "npm:^3.14.1" - "@react-aria/i18n": "npm:^3.12.13" - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/label": "npm:^3.7.22" - "@react-aria/landmark": "npm:^3.0.7" - "@react-aria/link": "npm:^3.8.6" - "@react-aria/listbox": "npm:^3.15.0" - "@react-aria/menu": "npm:^3.19.3" - "@react-aria/meter": "npm:^3.4.27" - "@react-aria/numberfield": "npm:^3.12.2" - "@react-aria/overlays": "npm:^3.30.0" - "@react-aria/progress": "npm:^3.4.27" - "@react-aria/radio": "npm:^3.12.2" - "@react-aria/searchfield": "npm:^3.8.9" - "@react-aria/select": "npm:^3.17.0" - "@react-aria/selection": "npm:^3.26.0" - "@react-aria/separator": "npm:^3.4.13" - "@react-aria/slider": "npm:^3.8.2" - "@react-aria/ssr": "npm:^3.9.10" - "@react-aria/switch": "npm:^3.7.8" - "@react-aria/table": "npm:^3.17.8" - "@react-aria/tabs": "npm:^3.10.8" - "@react-aria/tag": "npm:^3.7.2" - "@react-aria/textfield": "npm:^3.18.2" - "@react-aria/toast": "npm:^3.0.8" - "@react-aria/tooltip": "npm:^3.8.8" - "@react-aria/tree": "npm:^3.1.4" - "@react-aria/utils": "npm:^3.31.0" - "@react-aria/visually-hidden": "npm:^3.8.28" - "@react-types/shared": "npm:^3.32.1" + "@internationalized/date": "npm:^3.12.1" + "@internationalized/number": "npm:^3.6.6" + "@internationalized/string": "npm:^3.2.8" + "@react-types/shared": "npm:^3.34.0" + "@swc/helpers": "npm:^0.5.0" + aria-hidden: "npm:^1.2.3" + clsx: "npm:^2.0.0" + react-stately: "npm:3.46.0" + use-sync-external-store: "npm:^1.6.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/46b428af3f68703c03d7836a79f288dae1e945cb34164220a61f57d77df0a5adfc6c4a4c993282f9dbf57f159c9d249e1d644b57b61caca3dec8af1285404ec1 + checksum: 10/e70ba3a21f99967daffcb7399e6c4cc33fe9ae0ba4b13216ac3fbc150f37416d882b68ecd52f3c59852b87ef61a1c4b184066083d699d5afda1ad8b38fab8b99 languageName: node linkType: hard @@ -43111,6 +41582,15 @@ __metadata: languageName: node linkType: hard +"react-compiler-runtime@npm:19.1.0-rc.1": + version: 19.1.0-rc.1 + resolution: "react-compiler-runtime@npm:19.1.0-rc.1" + peerDependencies: + react: ^17.0.0 || ^18.0.0 || ^19.0.0 || ^0.0.0-experimental + checksum: 10/f64a63f8a6eacaebf87b2065dea9b2adc90d061c94361b31233284cbacc3fcf5f7dce6b5353ac8862ca5aa0a0d92240e3d6d695afa73d6bc5ce6d347b4c66870 + languageName: node + linkType: hard + "react-copy-to-clipboard@npm:5.1.0, react-copy-to-clipboard@npm:^5.0.4": version: 5.1.0 resolution: "react-copy-to-clipboard@npm:5.1.0" @@ -43186,9 +41666,9 @@ __metadata: languageName: node linkType: hard -"react-docgen@npm:^8.0.0": - version: 8.0.1 - resolution: "react-docgen@npm:8.0.1" +"react-docgen@npm:^8.0.0, react-docgen@npm:^8.0.2": + version: 8.0.2 + resolution: "react-docgen@npm:8.0.2" dependencies: "@babel/core": "npm:^7.28.0" "@babel/traverse": "npm:^7.28.0" @@ -43200,7 +41680,7 @@ __metadata: doctrine: "npm:^3.0.0" resolve: "npm:^1.22.1" strip-indent: "npm:^4.0.0" - checksum: 10/6eb9ec3870c0f527fd05b048a842e2338825d99703312d6f4e84f3ae0e5eda427e6b4c7c07a93b21d8740e2be23e779e0237c4b2783160e285f0121711e2f47c + checksum: 10/b56f594237a0bdf1356dee6a416ab6f2f38f60a7330cbdaf1da93d366f29f6bfbbfedecd51d47f6ba2a898985c3205c369e7f6cad528478560d9363717243ff8 languageName: node linkType: hard @@ -43250,16 +41730,16 @@ __metadata: languageName: node linkType: hard -"react-draggable@npm:^4.0.0, react-draggable@npm:^4.0.3": - version: 4.4.6 - resolution: "react-draggable@npm:4.4.6" +"react-draggable@npm:^4.0.0, react-draggable@npm:^4.5.0": + version: 4.5.0 + resolution: "react-draggable@npm:4.5.0" dependencies: - clsx: "npm:^1.1.1" + clsx: "npm:^2.1.1" prop-types: "npm:^15.8.1" peerDependencies: react: ">= 16.3.0" react-dom: ">= 16.3.0" - checksum: 10/51b9ac7f913797fc1cebc30ae383f346883033c45eb91e9b0b92e9ebd224bb1545b4ae2391825b649b798cc711a38351a5f41be24d949c64c6703ebc24eba661 + checksum: 10/fd4080a082fad199103c6978c8a78c80154a0205dbd14ca2c7bb75634132a6afd83a3e4669f2d8fe54efe68569ef24a992439183289d83e71030b0a1791d3476 languageName: node linkType: hard @@ -43341,11 +41821,11 @@ __metadata: linkType: hard "react-hook-form@npm:^7.12.2": - version: 7.55.0 - resolution: "react-hook-form@npm:7.55.0" + version: 7.72.0 + resolution: "react-hook-form@npm:7.72.0" peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 - checksum: 10/900e06064c1b4977f2e3e249a0f91742ae6bd1f2109701b80a9280b1c2b4bee8aac26aed7104371101eadad724f0bbe0215499e84623e6375007489ae850fe8e + checksum: 10/25268c510aa5943adfeb2cffe497f5b4b506974f60b4404b42f1b73eaa6db63a1efdb51f3e5f0fa6c0239cbd0f66a2dddf06ec45141d1894a266e77e2dbefb2d languageName: node linkType: hard @@ -43404,7 +41884,7 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^18.0.0, react-is@npm:^18.2.0": +"react-is@npm:^18.0.0, react-is@npm:^18.2.0, react-is@npm:^18.3.1": version: 18.3.1 resolution: "react-is@npm:18.3.1" checksum: 10/d5f60c87d285af24b1e1e7eaeb123ec256c3c8bdea7061ab3932e3e14685708221bf234ec50b21e10dd07f008f1b966a2730a0ce4ff67905b3872ff2042aec22 @@ -43519,10 +41999,10 @@ __metadata: languageName: node linkType: hard -"react-refresh@npm:^0.17.0": - version: 0.17.0 - resolution: "react-refresh@npm:0.17.0" - checksum: 10/5e94f07d43bb1cfdc9b0c6e0c8c73e754005489950dcff1edb53aa8451d1d69a47b740b195c7c80fb4eb511c56a3585dc55eddd83f0097fb5e015116a1460467 +"react-refresh@npm:^0.18.0": + version: 0.18.0 + resolution: "react-refresh@npm:0.18.0" + checksum: 10/504c331c19776bf8320c23bad7f80b3a28de03301ed7523b0dd21d3f02bf2b53bbdd5aa52469b187bc90f358614b2ba303c088a0765c95f4f0a68c43a7d67b1d languageName: node linkType: hard @@ -43535,38 +42015,38 @@ __metadata: languageName: node linkType: hard -"react-remove-scroll-bar@npm:^2.3.3": - version: 2.3.4 - resolution: "react-remove-scroll-bar@npm:2.3.4" +"react-remove-scroll-bar@npm:^2.3.7": + version: 2.3.8 + resolution: "react-remove-scroll-bar@npm:2.3.8" dependencies: - react-style-singleton: "npm:^2.2.1" + react-style-singleton: "npm:^2.2.2" tslib: "npm:^2.0.0" peerDependencies: - "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: "@types/react": optional: true - checksum: 10/ac028b3ed12e66972cab8656747736729b219dff5a600178d1650300a2a750ace37f7ec82146147d37b092b19874f45cf7a45edceff68ac1f59607a828ca089f + checksum: 10/6c0f8cff98b9f49a4ee2263f1eedf12926dced5ce220fbe83bd93544460e2a7ec8ec39b35d1b2a75d2fced0b2d64afeb8e66f830431ca896e05a20585f9fc350 languageName: node linkType: hard -"react-remove-scroll@npm:2.5.5": - version: 2.5.5 - resolution: "react-remove-scroll@npm:2.5.5" +"react-remove-scroll@npm:^2.6.3": + version: 2.7.2 + resolution: "react-remove-scroll@npm:2.7.2" dependencies: - react-remove-scroll-bar: "npm:^2.3.3" - react-style-singleton: "npm:^2.2.1" + react-remove-scroll-bar: "npm:^2.3.7" + react-style-singleton: "npm:^2.2.3" tslib: "npm:^2.1.0" - use-callback-ref: "npm:^1.3.0" - use-sidecar: "npm:^1.1.2" + use-callback-ref: "npm:^1.3.3" + use-sidecar: "npm:^1.1.3" peerDependencies: - "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10/f0646ac384ce3852d1f41e30a9f9e251b11cf3b430d1d114c937c8fa7f90a895c06378d0d6b6ff0b2d00cbccf15e845921944fd6074ae67a0fb347a718106d88 + checksum: 10/6a7858f9a3eb57128abb64479ced78283b0cbee0b43c2b87e42711e75c564c27d1d3dd2b81f2ea95c80cb9e6b6965d1c9e2332f6a7e7b753cd8aeb02b9b97704 languageName: node linkType: hard @@ -43581,14 +42061,15 @@ __metadata: linkType: hard "react-resizable@npm:^3.0.4, react-resizable@npm:^3.0.5": - version: 3.0.5 - resolution: "react-resizable@npm:3.0.5" + version: 3.1.3 + resolution: "react-resizable@npm:3.1.3" dependencies: prop-types: "npm:15.x" - react-draggable: "npm:^4.0.3" + react-draggable: "npm:^4.5.0" peerDependencies: react: ">= 16.3" - checksum: 10/745fad6ac827857b3a80d1d648b8d6723aa72fc17d5410a01707073f3d37b4adf6e0354dfe3cc33dee34d6e546a3fbd5603ef73e385dfc5218a425a39bf96275 + react-dom: ">= 16.3" + checksum: 10/bc7126fabbd2f0ba422fa0da77fcfcebf2b4f28141d3faf02067632d258e345bc5f63891f9d5eca0e9ea31dc461b74ec69073a18f905b25bdd392ca6283577f9 languageName: node linkType: hard @@ -43631,16 +42112,16 @@ __metadata: languageName: node linkType: hard -"react-router-dom@npm:^6.3.0": - version: 6.28.2 - resolution: "react-router-dom@npm:6.28.2" +"react-router-dom@npm:^6.30.2": + version: 6.30.3 + resolution: "react-router-dom@npm:6.30.3" dependencies: - "@remix-run/router": "npm:1.21.1" - react-router: "npm:6.28.2" + "@remix-run/router": "npm:1.23.2" + react-router: "npm:6.30.3" peerDependencies: react: ">=16.8" react-dom: ">=16.8" - checksum: 10/4775cb484c497be5833ef5e048378d685f970a72c75a595c8b74fce147eda9e705d885b71d888b1090a8f22e7630adb851766d34e771c3a649f73171f0fa4c2b + checksum: 10/db974d801070e9967a076b31edca902e127793e02dc79f364461b94e81846a588c241d72e069f5b586b4a90ffd99798f5cb97753ac9d22fe90afa6dc008ab520 languageName: node linkType: hard @@ -43655,14 +42136,14 @@ __metadata: languageName: node linkType: hard -"react-router@npm:6.28.2, react-router@npm:^6.3.0": - version: 6.28.2 - resolution: "react-router@npm:6.28.2" +"react-router@npm:6.30.3, react-router@npm:^6.30.2": + version: 6.30.3 + resolution: "react-router@npm:6.30.3" dependencies: - "@remix-run/router": "npm:1.21.1" + "@remix-run/router": "npm:1.23.2" peerDependencies: react: ">=16.8" - checksum: 10/4cf150e3762acff8a087d6b474861fdb73efdf829ce0619bc980f3d8fc5d9e45e67333ab7d62af5b775fba8efe8f8d342f089bec75f9b41f3162e139c0187efd + checksum: 10/1a51bdcc42b8d7979228dea8b5c44a28a4add9b681781f75b74f5f920d20058a92ffe5f1d0ba0621f03abe1384b36025b53b402515ecb35f27a6a2f2f25d6fbe languageName: node linkType: hard @@ -43687,60 +42168,39 @@ __metadata: languageName: node linkType: hard -"react-stately@npm:^3.42.0": - version: 3.42.0 - resolution: "react-stately@npm:3.42.0" +"react-stately@npm:3.46.0, react-stately@npm:~3.46.0": + version: 3.46.0 + resolution: "react-stately@npm:3.46.0" dependencies: - "@react-stately/calendar": "npm:^3.9.0" - "@react-stately/checkbox": "npm:^3.7.2" - "@react-stately/collections": "npm:^3.12.8" - "@react-stately/color": "npm:^3.9.2" - "@react-stately/combobox": "npm:^3.12.0" - "@react-stately/data": "npm:^3.14.1" - "@react-stately/datepicker": "npm:^3.15.2" - "@react-stately/disclosure": "npm:^3.0.8" - "@react-stately/dnd": "npm:^3.7.1" - "@react-stately/form": "npm:^3.2.2" - "@react-stately/list": "npm:^3.13.1" - "@react-stately/menu": "npm:^3.9.8" - "@react-stately/numberfield": "npm:^3.10.2" - "@react-stately/overlays": "npm:^3.6.20" - "@react-stately/radio": "npm:^3.11.2" - "@react-stately/searchfield": "npm:^3.5.16" - "@react-stately/select": "npm:^3.8.0" - "@react-stately/selection": "npm:^3.20.6" - "@react-stately/slider": "npm:^3.7.2" - "@react-stately/table": "npm:^3.15.1" - "@react-stately/tabs": "npm:^3.8.6" - "@react-stately/toast": "npm:^3.1.2" - "@react-stately/toggle": "npm:^3.9.2" - "@react-stately/tooltip": "npm:^3.5.8" - "@react-stately/tree": "npm:^3.9.3" - "@react-types/shared": "npm:^3.32.1" + "@internationalized/date": "npm:^3.12.1" + "@internationalized/number": "npm:^3.6.6" + "@internationalized/string": "npm:^3.2.8" + "@react-types/shared": "npm:^3.34.0" + "@swc/helpers": "npm:^0.5.0" + use-sync-external-store: "npm:^1.6.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/4913f8ae9beb909d07669b97c0960dadad72c3c9e572051158a64acac3eadc73ff495b3c145b3673c15494751506e5b8a666a59f65364900fa66917412971d32 + checksum: 10/ee2d8b0633c6ba82eb159197ddaaeb0832d318c6ed1304c7e14273d0c3dc3156c48aef0c8cf4207481dbca1cf054c6726c42c089259605213d1f08f35bebf321 languageName: node linkType: hard -"react-style-singleton@npm:^2.2.1": - version: 2.2.1 - resolution: "react-style-singleton@npm:2.2.1" +"react-style-singleton@npm:^2.2.2, react-style-singleton@npm:^2.2.3": + version: 2.2.3 + resolution: "react-style-singleton@npm:2.2.3" dependencies: get-nonce: "npm:^1.0.0" - invariant: "npm:^2.2.4" tslib: "npm:^2.0.0" peerDependencies: - "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10/80c58fd6aac3594e351e2e7b048d8a5b09508adb21031a38b3c40911fe58295572eddc640d4b20a7be364842c8ed1120fe30097e22ea055316b375b88d4ff02a + checksum: 10/62498094ff3877a37f351b29e6cad9e38b2eb1ac3c0cb27ebf80aee96554f80b35e17bdb552bcd7ac8b7cb9904fea93ea5668f2057c73d38f90b5d46bb9b27ab languageName: node linkType: hard -"react-syntax-highlighter@npm:^15.4.5, react-syntax-highlighter@npm:^15.6.1": +"react-syntax-highlighter@npm:^15.4.5": version: 15.6.6 resolution: "react-syntax-highlighter@npm:15.6.6" dependencies: @@ -43756,6 +42216,22 @@ __metadata: languageName: node linkType: hard +"react-syntax-highlighter@npm:^16.0.0": + version: 16.1.0 + resolution: "react-syntax-highlighter@npm:16.1.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + highlight.js: "npm:^10.4.1" + highlightjs-vue: "npm:^1.0.0" + lowlight: "npm:^1.17.0" + prismjs: "npm:^1.30.0" + refractor: "npm:^5.0.0" + peerDependencies: + react: ">= 0.14.0" + checksum: 10/d55bc96802357de2102703fb3ffe86a67d52099e93da9624a900609e4d8b6f5f95472d42423951631348fdee2679d661b17aaf41039035dc3f1bbabf0423d62e + languageName: node + linkType: hard + "react-test-renderer@npm:^16.13.1": version: 16.14.0 resolution: "react-test-renderer@npm:16.14.0" @@ -43809,8 +42285,8 @@ __metadata: linkType: hard "react-use@npm:^17.2.4, react-use@npm:^17.3.2, react-use@npm:^17.4.0": - version: 17.5.1 - resolution: "react-use@npm:17.5.1" + version: 17.6.0 + resolution: "react-use@npm:17.6.0" dependencies: "@types/js-cookie": "npm:^2.2.6" "@xobotyi/scrollbar-width": "npm:^1.9.5" @@ -43829,7 +42305,7 @@ __metadata: peerDependencies: react: "*" react-dom: "*" - checksum: 10/2da403a9949dbd964b9b8e20dcd354db66b7f7d5ca1f42572fbcdb06bd49ee828c295be4912cb87abc163d1b54820bb8c5fa85314a16c4579d9e30bf9cbd5759 + checksum: 10/a817b74e82b481a39d3539bfe8d3b535c08d59d44a75ea91f65e56a7ccaedb0de185159e50b44ea4a635dda0c1c7159f07530e81a1d64b57130e0a715a107795 languageName: node linkType: hard @@ -43882,6 +42358,15 @@ __metadata: languageName: node linkType: hard +"read-cache@npm:^1.0.0": + version: 1.0.0 + resolution: "read-cache@npm:1.0.0" + dependencies: + pify: "npm:^2.3.0" + checksum: 10/83a39149d9dfa38f0c482ea0d77b34773c92fef07fe7599cdd914d255b14d0453e0229ef6379d8d27d6947f42d7581635296d0cfa7708f05a9bd8e789d398b31 + languageName: node + linkType: hard + "read-cmd-shim@npm:^2.0.0": version: 2.0.0 resolution: "read-cmd-shim@npm:2.0.0" @@ -43899,12 +42384,12 @@ __metadata: languageName: node linkType: hard -"read-tls-client-hello@npm:^1.0.0": - version: 1.0.1 - resolution: "read-tls-client-hello@npm:1.0.1" +"read-tls-client-hello@npm:^1.0.0, read-tls-client-hello@npm:^1.1.0": + version: 1.1.0 + resolution: "read-tls-client-hello@npm:1.1.0" dependencies: "@types/node": "npm:*" - checksum: 10/fefa63605eebb197b6046cb26d7efde16eb24c026edbbca789c30b7b5be7c0dd8e45fb4d32707513dfc4dd468b787b2a46ed8be846f29eac844d6d80710688c6 + checksum: 10/c3a15164065e33509b3dee05e70865e68b7b061804e70f864d6afdc55b30663ce6fed2730d9508a44672757622a454c2a38b696c262bdd0ebcbb39f28acee621 languageName: node linkType: hard @@ -43920,7 +42405,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:3, readable-stream@npm:^3.0.0, readable-stream@npm:^3.0.2, readable-stream@npm:^3.0.6, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.5.0, readable-stream@npm:^3.6.0": +"readable-stream@npm:3, readable-stream@npm:^3.0.0, readable-stream@npm:^3.0.2, readable-stream@npm:^3.0.6, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.5.0, readable-stream@npm:^3.6.0, readable-stream@npm:^3.6.2": version: 3.6.2 resolution: "readable-stream@npm:3.6.2" dependencies: @@ -43946,7 +42431,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^4.0.0, readable-stream@npm:^4.3.0, readable-stream@npm:^4.5.2": +"readable-stream@npm:^4.0.0, readable-stream@npm:^4.3.0": version: 4.5.2 resolution: "readable-stream@npm:4.5.2" dependencies: @@ -43998,6 +42483,13 @@ __metadata: languageName: node linkType: hard +"readdirp@npm:^4.0.1": + version: 4.1.2 + resolution: "readdirp@npm:4.1.2" + checksum: 10/7b817c265940dba90bb9c94d82920d76c3a35ea2d67f9f9d8bd936adcfe02d50c802b14be3dd2e725e002dddbe2cc1c7a0edfb1bc3a365c9dfd5a61e612eea1e + languageName: node + linkType: hard + "readline-sync@npm:1.4.9": version: 1.4.9 resolution: "readline-sync@npm:1.4.9" @@ -44108,7 +42600,7 @@ __metadata: languageName: node linkType: hard -"reflect-metadata@npm:0.2.2": +"reflect-metadata@npm:0.2.2, reflect-metadata@npm:^0.2.2": version: 0.2.2 resolution: "reflect-metadata@npm:0.2.2" checksum: 10/1c93f9ac790fea1c852fde80c91b2760420069f4862f28e6fae0c00c6937a56508716b0ed2419ab02869dd488d123c4ab92d062ae84e8739ea7417fae10c4745 @@ -44142,6 +42634,18 @@ __metadata: languageName: node linkType: hard +"refractor@npm:^5.0.0": + version: 5.0.0 + resolution: "refractor@npm:5.0.0" + dependencies: + "@types/hast": "npm:^3.0.0" + "@types/prismjs": "npm:^1.0.0" + hastscript: "npm:^9.0.0" + parse-entities: "npm:^4.0.0" + checksum: 10/a14ceb938ed6f7f17dfa44660d3f3aff33e8896710bd76af0b411778791918bf20f923a191ff197fbc6a8e3f06c4371b83882f46ce76736f804dbe2b82b0be94 + languageName: node + linkType: hard + "regenerator-runtime@npm:^0.10.5": version: 0.10.5 resolution: "regenerator-runtime@npm:0.10.5" @@ -44156,7 +42660,7 @@ __metadata: languageName: node linkType: hard -"regexp.prototype.flags@npm:^1.5.3": +"regexp.prototype.flags@npm:^1.5.3, regexp.prototype.flags@npm:^1.5.4": version: 1.5.4 resolution: "regexp.prototype.flags@npm:1.5.4" dependencies: @@ -44188,6 +42692,28 @@ __metadata: languageName: node linkType: hard +"rehype-raw@npm:^6.0.0": + version: 6.1.1 + resolution: "rehype-raw@npm:6.1.1" + dependencies: + "@types/hast": "npm:^2.0.0" + hast-util-raw: "npm:^7.2.0" + unified: "npm:^10.0.0" + checksum: 10/3599d22c45264bea52c93eec2136f50f119282c0bd4e9604aeb2421fe20db84f9c4536caebf64f29158d8c2403b6fd3b3da634211393fdda9cdd500149d00ae4 + languageName: node + linkType: hard + +"rehype-sanitize@npm:^5.0.0": + version: 5.0.1 + resolution: "rehype-sanitize@npm:5.0.1" + dependencies: + "@types/hast": "npm:^2.0.0" + hast-util-sanitize: "npm:^4.0.0" + unified: "npm:^10.0.0" + checksum: 10/b9d9efc0b3c894fe5da84558147148c355c933794c3411dc7c02278b28ea251d241020cd26836e5cc5c979e0e058b45ca51a0eb87824ffdb7241efecd65b6d29 + languageName: node + linkType: hard + "relateurl@npm:^0.2.7": version: 0.2.7 resolution: "relateurl@npm:0.2.7" @@ -44303,14 +42829,13 @@ __metadata: languageName: node linkType: hard -"require-in-the-middle@npm:^7.1.1": - version: 7.3.0 - resolution: "require-in-the-middle@npm:7.3.0" +"require-in-the-middle@npm:^8.0.0": + version: 8.0.1 + resolution: "require-in-the-middle@npm:8.0.1" dependencies: - debug: "npm:^4.1.1" + debug: "npm:^4.3.5" module-details-from-path: "npm:^1.0.3" - resolve: "npm:^1.22.1" - checksum: 10/883343b9ba15d42dd443b20fba5f9135cc4b7c2c2af3ae87f0105c28c499f98438a414e49bbfafd3f607dbe60a91c0da3610d31c9c3f61d222e565a8e8dd161e + checksum: 10/4ce98c681489d383a0ffccb79b06df7a1dffbb31c13f3b713ae2c5a1967597a259e67612507ef69748d83d531bba7c9bb0477211771fe78c685e1d52b1a44b64 languageName: node linkType: hard @@ -44409,13 +42934,6 @@ __metadata: languageName: node linkType: hard -"resolve.exports@npm:^2.0.0": - version: 2.0.0 - resolution: "resolve.exports@npm:2.0.0" - checksum: 10/a94fe2c064c57b4cc64de8c13eab28f744ee2327af4e458f2db00b5bbc3e7bf91b59fca043e35e2a3cf70bdae2263825c926b62200e2eb78d5c55a88d14932a8 - languageName: node - linkType: hard - "resolve@npm:1.22.8": version: 1.22.8 resolution: "resolve@npm:1.22.8" @@ -44429,16 +42947,16 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.1.6, resolve@npm:^1.17.0, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.1, resolve@npm:^1.22.10, resolve@npm:^1.22.4, resolve@npm:^1.22.8, resolve@npm:~1.22.1, resolve@npm:~1.22.2": - version: 1.22.10 - resolution: "resolve@npm:1.22.10" +"resolve@npm:^1.1.6, resolve@npm:^1.1.7, resolve@npm:^1.17.0, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.1, resolve@npm:^1.22.10, resolve@npm:^1.22.4, resolve@npm:^1.22.8, resolve@npm:~1.22.1, resolve@npm:~1.22.2": + version: 1.22.11 + resolution: "resolve@npm:1.22.11" dependencies: - is-core-module: "npm:^2.16.0" + is-core-module: "npm:^2.16.1" path-parse: "npm:^1.0.7" supports-preserve-symlinks-flag: "npm:^1.0.0" bin: resolve: bin/resolve - checksum: 10/0a398b44da5c05e6e421d70108822c327675febb880eebe905587628de401854c61d5df02866ff34fc4cb1173a51c9f0e84a94702738df3611a62e2acdc68181 + checksum: 10/e1b2e738884a08de03f97ee71494335eba8c2b0feb1de9ae065e82c48997f349f77a2b10e8817e147cf610bfabc4b1cb7891ee8eaf5bf80d4ad514a34c4fab0a languageName: node linkType: hard @@ -44468,16 +42986,16 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@npm%3A^1.1.6#optional!builtin, resolve@patch:resolve@npm%3A^1.17.0#optional!builtin, resolve@patch:resolve@npm%3A^1.19.0#optional!builtin, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin, resolve@patch:resolve@npm%3A^1.22.10#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin, resolve@patch:resolve@npm%3A~1.22.1#optional!builtin, resolve@patch:resolve@npm%3A~1.22.2#optional!builtin": - version: 1.22.10 - resolution: "resolve@patch:resolve@npm%3A1.22.10#optional!builtin::version=1.22.10&hash=c3c19d" +"resolve@patch:resolve@npm%3A^1.1.6#optional!builtin, resolve@patch:resolve@npm%3A^1.1.7#optional!builtin, resolve@patch:resolve@npm%3A^1.17.0#optional!builtin, resolve@patch:resolve@npm%3A^1.19.0#optional!builtin, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin, resolve@patch:resolve@npm%3A^1.22.10#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin, resolve@patch:resolve@npm%3A~1.22.1#optional!builtin, resolve@patch:resolve@npm%3A~1.22.2#optional!builtin": + version: 1.22.11 + resolution: "resolve@patch:resolve@npm%3A1.22.11#optional!builtin::version=1.22.11&hash=c3c19d" dependencies: - is-core-module: "npm:^2.16.0" + is-core-module: "npm:^2.16.1" path-parse: "npm:^1.0.7" supports-preserve-symlinks-flag: "npm:^1.0.0" bin: resolve: bin/resolve - checksum: 10/d4d878bfe3702d215ea23e75e0e9caf99468e3db76f5ca100d27ebdc527366fee3877e54bce7d47cc72ca8952fc2782a070d238bfa79a550eeb0082384c3b81a + checksum: 10/fd342cad25e52cd6f4f3d1716e189717f2522bfd6641109fe7aa372f32b5714a296ed7c238ddbe7ebb0c1ddfe0b7f71c9984171024c97cf1b2073e3e40ff71a8 languageName: node linkType: hard @@ -44638,17 +43156,6 @@ __metadata: languageName: node linkType: hard -"rimraf@npm:^5.0.1, rimraf@npm:^5.0.5": - version: 5.0.10 - resolution: "rimraf@npm:5.0.10" - dependencies: - glob: "npm:^10.3.7" - bin: - rimraf: dist/esm/bin.mjs - checksum: 10/f3b8ce81eecbde4628b07bdf9e2fa8b684e0caea4999acb1e3b0402c695cd41f28cd075609a808e61ce2672f528ca079f675ab1d8e8d5f86d56643a03e0b8d2e - languageName: node - linkType: hard - "rimraf@npm:~2.6.2": version: 2.6.3 resolution: "rimraf@npm:2.6.3" @@ -44695,37 +43202,40 @@ __metadata: linkType: hard "rollup-plugin-dts@npm:^6.1.0": - version: 6.1.1 - resolution: "rollup-plugin-dts@npm:6.1.1" + version: 6.4.1 + resolution: "rollup-plugin-dts@npm:6.4.1" dependencies: - "@babel/code-frame": "npm:^7.24.2" - magic-string: "npm:^0.30.10" + "@babel/code-frame": "npm:^7.29.0" + "@jridgewell/remapping": "npm:^2.3.5" + "@jridgewell/sourcemap-codec": "npm:^1.5.5" + convert-source-map: "npm:^2.0.0" + magic-string: "npm:^0.30.21" peerDependencies: rollup: ^3.29.4 || ^4 - typescript: ^4.5 || ^5.0 + typescript: ^4.5 || ^5.0 || ^6.0 dependenciesMeta: "@babel/code-frame": optional: true - checksum: 10/8a66833a5af32f77d9bbc746339097d4af2382e5160f7629d85dcecb4efad12cbfebd37c79147fa688f073c333d71f53135e08a225a3fc3e9a3b3f92c46b2381 + checksum: 10/a18685fde4c59339cacb47e084ff589b39fc680d186b496b83197370295002f748066e3c9c62ced2eca1af0bca77d3096bb24905c40c0478a6d845d63531293d languageName: node linkType: hard "rollup-plugin-esbuild@npm:^6.1.1": - version: 6.1.1 - resolution: "rollup-plugin-esbuild@npm:6.1.1" + version: 6.2.1 + resolution: "rollup-plugin-esbuild@npm:6.2.1" dependencies: - "@rollup/pluginutils": "npm:^5.0.5" - debug: "npm:^4.3.4" - es-module-lexer: "npm:^1.3.1" - get-tsconfig: "npm:^4.7.2" + debug: "npm:^4.4.0" + es-module-lexer: "npm:^1.6.0" + get-tsconfig: "npm:^4.10.0" + unplugin-utils: "npm:^0.2.4" peerDependencies: esbuild: ">=0.18.0" rollup: ^1.20.0 || ^2.0.0 || ^3.0.0 || ^4.0.0 - checksum: 10/bba2d1dfb92a193823ac9dd1cdd44a8fd8cd9f25868e9a22ca077e1b7445feb4eaaf6df051148e367fc902d7d59c9f50efab49086c24c367972f05c86f3a656d + checksum: 10/e5731b86c4e01c6ccd3998a2f90794bd738fe83a3a809b9e82456e4b1d173c296ef302c2a6669b966b12c757c1787f0585ae88cd73b98f3c0ad6ad7f779940aa languageName: node linkType: hard -"rollup-plugin-postcss@npm:*, rollup-plugin-postcss@npm:^4.0.0": +"rollup-plugin-postcss@npm:^4.0.0": version: 4.0.2 resolution: "rollup-plugin-postcss@npm:4.0.2" dependencies: @@ -44784,30 +43294,35 @@ __metadata: languageName: node linkType: hard -"rollup@npm:^4.27.3, rollup@npm:^4.43.0": - version: 4.46.2 - resolution: "rollup@npm:4.46.2" +"rollup@npm:^4.43.0, rollup@npm:^4.59.0": + version: 4.60.1 + resolution: "rollup@npm:4.60.1" dependencies: - "@rollup/rollup-android-arm-eabi": "npm:4.46.2" - "@rollup/rollup-android-arm64": "npm:4.46.2" - "@rollup/rollup-darwin-arm64": "npm:4.46.2" - "@rollup/rollup-darwin-x64": "npm:4.46.2" - "@rollup/rollup-freebsd-arm64": "npm:4.46.2" - "@rollup/rollup-freebsd-x64": "npm:4.46.2" - "@rollup/rollup-linux-arm-gnueabihf": "npm:4.46.2" - "@rollup/rollup-linux-arm-musleabihf": "npm:4.46.2" - "@rollup/rollup-linux-arm64-gnu": "npm:4.46.2" - "@rollup/rollup-linux-arm64-musl": "npm:4.46.2" - "@rollup/rollup-linux-loongarch64-gnu": "npm:4.46.2" - "@rollup/rollup-linux-ppc64-gnu": "npm:4.46.2" - "@rollup/rollup-linux-riscv64-gnu": "npm:4.46.2" - "@rollup/rollup-linux-riscv64-musl": "npm:4.46.2" - "@rollup/rollup-linux-s390x-gnu": "npm:4.46.2" - "@rollup/rollup-linux-x64-gnu": "npm:4.46.2" - "@rollup/rollup-linux-x64-musl": "npm:4.46.2" - "@rollup/rollup-win32-arm64-msvc": "npm:4.46.2" - "@rollup/rollup-win32-ia32-msvc": "npm:4.46.2" - "@rollup/rollup-win32-x64-msvc": "npm:4.46.2" + "@rollup/rollup-android-arm-eabi": "npm:4.60.1" + "@rollup/rollup-android-arm64": "npm:4.60.1" + "@rollup/rollup-darwin-arm64": "npm:4.60.1" + "@rollup/rollup-darwin-x64": "npm:4.60.1" + "@rollup/rollup-freebsd-arm64": "npm:4.60.1" + "@rollup/rollup-freebsd-x64": "npm:4.60.1" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.60.1" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.60.1" + "@rollup/rollup-linux-arm64-gnu": "npm:4.60.1" + "@rollup/rollup-linux-arm64-musl": "npm:4.60.1" + "@rollup/rollup-linux-loong64-gnu": "npm:4.60.1" + "@rollup/rollup-linux-loong64-musl": "npm:4.60.1" + "@rollup/rollup-linux-ppc64-gnu": "npm:4.60.1" + "@rollup/rollup-linux-ppc64-musl": "npm:4.60.1" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.60.1" + "@rollup/rollup-linux-riscv64-musl": "npm:4.60.1" + "@rollup/rollup-linux-s390x-gnu": "npm:4.60.1" + "@rollup/rollup-linux-x64-gnu": "npm:4.60.1" + "@rollup/rollup-linux-x64-musl": "npm:4.60.1" + "@rollup/rollup-openbsd-x64": "npm:4.60.1" + "@rollup/rollup-openharmony-arm64": "npm:4.60.1" + "@rollup/rollup-win32-arm64-msvc": "npm:4.60.1" + "@rollup/rollup-win32-ia32-msvc": "npm:4.60.1" + "@rollup/rollup-win32-x64-gnu": "npm:4.60.1" + "@rollup/rollup-win32-x64-msvc": "npm:4.60.1" "@types/estree": "npm:1.0.8" fsevents: "npm:~2.3.2" dependenciesMeta: @@ -44831,10 +43346,14 @@ __metadata: optional: true "@rollup/rollup-linux-arm64-musl": optional: true - "@rollup/rollup-linux-loongarch64-gnu": + "@rollup/rollup-linux-loong64-gnu": + optional: true + "@rollup/rollup-linux-loong64-musl": optional: true "@rollup/rollup-linux-ppc64-gnu": optional: true + "@rollup/rollup-linux-ppc64-musl": + optional: true "@rollup/rollup-linux-riscv64-gnu": optional: true "@rollup/rollup-linux-riscv64-musl": @@ -44845,17 +43364,23 @@ __metadata: optional: true "@rollup/rollup-linux-x64-musl": optional: true + "@rollup/rollup-openbsd-x64": + optional: true + "@rollup/rollup-openharmony-arm64": + optional: true "@rollup/rollup-win32-arm64-msvc": optional: true "@rollup/rollup-win32-ia32-msvc": optional: true + "@rollup/rollup-win32-x64-gnu": + optional: true "@rollup/rollup-win32-x64-msvc": optional: true fsevents: optional: true bin: rollup: dist/bin/rollup - checksum: 10/3acc425a9828e8ba75eaec9cb506a680d3deaa09a753635140cc4a3c98445ba6e3f631b32ca1c98965b3f60672d2815cd560ea844bba497d69e65c58c02327cf + checksum: 10/6866a35efc999990e191fc954a859ba802d13be63ca13b04746459455982f6b8784d92e5eea8db3ef8acf8baba8c43e8e6cb741f3233ba4c46adf148d3708a9c languageName: node linkType: hard @@ -44864,6 +43389,7 @@ __metadata: resolution: "root@workspace:." dependencies: "@backstage/cli": "workspace:*" + "@backstage/cli-defaults": "workspace:*" "@backstage/codemods": "workspace:*" "@backstage/create-app": "workspace:*" "@backstage/e2e-test-utils": "workspace:*" @@ -44871,47 +43397,57 @@ __metadata: "@backstage/eslint-plugin": "workspace:*" "@backstage/repo-tools": "workspace:*" "@changesets/cli": "npm:^2.14.0" + "@jest/environment-jsdom-abstract": "npm:^30" "@manypkg/get-packages": "npm:^1.1.3" "@octokit/rest": "npm:^19.0.3" "@playwright/test": "npm:^1.32.3" "@spotify/eslint-plugin": "npm:^15.0.0" - "@storybook/addon-a11y": "npm:^9.1.7" - "@storybook/addon-docs": "npm:^9.1.7" - "@storybook/addon-links": "npm:^9.1.7" - "@storybook/addon-themes": "npm:^9.1.7" - "@storybook/react-vite": "npm:^9.1.7" + "@storybook/addon-a11y": "npm:^10.3.3" + "@storybook/addon-docs": "npm:^10.3.3" + "@storybook/addon-links": "npm:^10.3.3" + "@storybook/addon-mcp": "npm:^0.4.2" + "@storybook/addon-themes": "npm:^10.3.3" + "@storybook/addon-vitest": "npm:^10.3.3" + "@storybook/react-vite": "npm:^10.3.3" "@techdocs/cli": "workspace:*" "@types/cacheable-request": "npm:^8.3.6" "@types/global-agent": "npm:^2.1.3" + "@types/jest": "npm:^30" + "@types/jsdom": "npm:^27" "@types/memjs": "npm:^1.3.3" - "@types/node": "npm:^20.16.0" + "@types/node": "npm:^22.13.14" "@types/webpack": "npm:^5.28.0" "@useoptic/optic": "npm:^1.0.0" array-to-table: "npm:^1.0.1" command-exists: "npm:^1.2.9" - cross-env: "npm:^7.0.0" + cross-env: "npm:^10.0.0" e2e-test: "workspace:*" eslint: "npm:^8.6.0" + eslint-plugin-node-import: "npm:^1.0.5" eslint-plugin-notice: "npm:^1.0.0" eslint-plugin-react: "npm:^7.28.0" - eslint-plugin-testing-library: "npm:^6.0.0" + eslint-plugin-testing-library: "npm:^7.0.0" fs-extra: "npm:^11.2.0" husky: "npm:^9.0.0" - lint-staged: "npm:^15.0.0" + jest: "npm:^30" + js-yaml: "npm:^4.1.1" + jsdom: "npm:^27" + lint-staged: "npm:^16.0.0" madge: "npm:^8.0.0" minimist: "npm:^1.2.5" - node-gyp: "npm:^10.0.0" + node-gyp: "npm:^12.0.0" prettier: "npm:^2.2.1" semver: "npm:^7.5.3" shx: "npm:^0.4.0" sloc: "npm:^0.3.1" - sort-package-json: "npm:^2.8.0" - storybook: "npm:^9.1.7" + sort-package-json: "npm:^3.0.0" + storybook: "npm:^10.3.3" ts-morph: "npm:^24.0.0" typedoc: "npm:^0.28.0" typescript: "npm:~5.7.0" vite: "npm:^7.1.5" yaml: "npm:^2.7.0" + zod: "npm:^3.25.76 || ^4.0.0" languageName: unknown linkType: soft @@ -44935,6 +43471,13 @@ __metadata: languageName: node linkType: hard +"rrweb-cssom@npm:^0.8.0": + version: 0.8.0 + resolution: "rrweb-cssom@npm:0.8.0" + checksum: 10/07521ee36fb6569c17906afad1ac7ff8f099d49ade9249e190693ac36cdf27f88d9acf0cc66978935d5d0a23fca105643d7e9125b9a9d91ed9db9e02d31d7d80 + languageName: node + linkType: hard + "rtl-css-js@npm:^1.16.1": version: 1.16.1 resolution: "rtl-css-js@npm:1.16.1" @@ -44960,7 +43503,14 @@ __metadata: languageName: node linkType: hard -"run-parallel@npm:^1.1.9, run-parallel@npm:^1.2.0": +"run-async@npm:^3.0.0": + version: 3.0.0 + resolution: "run-async@npm:3.0.0" + checksum: 10/97fb8747f7765b77ebcd311d3a33548099336f04c6434e0763039b98c1de0f1b4421000695aff8751f309c0b995d8dfd620c1f1e4c35572da38c101488165305 + languageName: node + linkType: hard + +"run-parallel@npm:^1.1.9": version: 1.2.0 resolution: "run-parallel@npm:1.2.0" dependencies: @@ -45007,7 +43557,7 @@ __metadata: languageName: node linkType: hard -"safe-buffer@npm:5.2.1, safe-buffer@npm:>=5.1.0, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0": +"safe-buffer@npm:5.2.1, safe-buffer@npm:>=5.1.0, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0, safe-buffer@npm:~5.2.1": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" checksum: 10/32872cd0ff68a3ddade7a7617b8f4c2ae8764d8b7d884c651b74457967a9e0e886267d3ecc781220629c44a865167b61c375d2da6c720c840ecd73f45d5d9451 @@ -45028,6 +43578,16 @@ __metadata: languageName: node linkType: hard +"safe-push-apply@npm:^1.0.0": + version: 1.0.0 + resolution: "safe-push-apply@npm:1.0.0" + dependencies: + es-errors: "npm:^1.3.0" + isarray: "npm:^2.0.5" + checksum: 10/2bd4e53b6694f7134b9cf93631480e7fafc8637165f0ee91d5a4af5e7f33d37de9562d1af5021178dd4217d0230cde8d6530fa28cfa1ebff9a431bf8fff124b4 + languageName: node + linkType: hard + "safe-regex-test@npm:^1.0.3, safe-regex-test@npm:^1.1.0": version: 1.1.0 resolution: "safe-regex-test@npm:1.1.0" @@ -45071,7 +43631,7 @@ __metadata: languageName: node linkType: hard -"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0, safer-buffer@npm:^2.0.2, safer-buffer@npm:~2.1.0": +"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0, safer-buffer@npm:~2.1.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" checksum: 10/7eaf7a0cf37cc27b42fb3ef6a9b1df6e93a1c6d98c6c6702b02fe262d5fcbd89db63320793b99b21cb5348097d0a53de81bd5f4e8b86e20cc9412e3f1cfb4e83 @@ -45090,6 +43650,13 @@ __metadata: languageName: node linkType: hard +"sax@npm:^1.5.0": + version: 1.5.0 + resolution: "sax@npm:1.5.0" + checksum: 10/9012ff37dda7a7ac5da45db2143b04036103e8bef8d586c3023afd5df6caf0ebd7f38017eee344ad2e2247eded7d38e9c42cf291d8dd91781352900ac0fd2d9f + languageName: node + linkType: hard + "saxes@npm:^6.0.0": version: 6.0.0 resolution: "saxes@npm:6.0.0" @@ -45146,7 +43713,19 @@ __metadata: languageName: node linkType: hard -"schema-utils@npm:^3.0.0, schema-utils@npm:^3.1.1, schema-utils@npm:^3.2.0": +"schema-utils@npm:4.3.0": + version: 4.3.0 + resolution: "schema-utils@npm:4.3.0" + dependencies: + "@types/json-schema": "npm:^7.0.9" + ajv: "npm:^8.9.0" + ajv-formats: "npm:^2.1.1" + ajv-keywords: "npm:^5.1.0" + checksum: 10/86c5a7c72a275c56f140bc3cdd832d56efb11428c88ad588127db12cb9b2c83ccaa9540e115d7baa9c6175b5e360094457e29c44e6fb76787c9498c2eb6df5d6 + languageName: node + linkType: hard + +"schema-utils@npm:^3.0.0, schema-utils@npm:^3.1.1": version: 3.3.0 resolution: "schema-utils@npm:3.3.0" dependencies: @@ -45157,15 +43736,15 @@ __metadata: languageName: node linkType: hard -"schema-utils@npm:^4.0.0, schema-utils@npm:^4.2.0, schema-utils@npm:^4.3.0, schema-utils@npm:^4.3.2": - version: 4.3.2 - resolution: "schema-utils@npm:4.3.2" +"schema-utils@npm:^4.0.0, schema-utils@npm:^4.2.0, schema-utils@npm:^4.3.0, schema-utils@npm:^4.3.3": + version: 4.3.3 + resolution: "schema-utils@npm:4.3.3" dependencies: "@types/json-schema": "npm:^7.0.9" ajv: "npm:^8.9.0" ajv-formats: "npm:^2.1.1" ajv-keywords: "npm:^5.1.0" - checksum: 10/02c32c34aae762d48468f98465a96a167fede637772871c7c7d8923671ddb9f20b2cc6f6e8448ae6bef5363e3597493c655212c8b06a4ee73aa099d9452fbd8b + checksum: 10/dba77a46ad7ff0c906f7f09a1a61109e6cb56388f15a68070b93c47a691f516c6a3eb454f81a8cceb0a0e55b87f8b05770a02bfb1f4e0a3143b5887488b2f900 languageName: node linkType: hard @@ -45223,6 +43802,16 @@ __metadata: languageName: node linkType: hard +"selfsigned@npm:^5.5.0": + version: 5.5.0 + resolution: "selfsigned@npm:5.5.0" + dependencies: + "@peculiar/x509": "npm:^1.14.2" + pkijs: "npm:^3.3.3" + checksum: 10/fe9be2647507c3ee21dcaf5cab20e1ae4b8b84eac83d2fe4d82f9a3b6c70636f9aaeeba0089e3343dcb13fbb31ef70c2e72c41f2e2dcf38368040b49830c670e + languageName: node + linkType: hard + "semver-compare@npm:^1.0.0": version: 1.0.0 resolution: "semver-compare@npm:1.0.0" @@ -45284,12 +43873,12 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.1.2, semver@npm:^7.1.3, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.2": - version: 7.7.3 - resolution: "semver@npm:7.7.3" +"semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.1.2, semver@npm:^7.1.3, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.8, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.7.2, semver@npm:^7.7.3": + version: 7.7.4 + resolution: "semver@npm:7.7.4" bin: semver: bin/semver.js - checksum: 10/8dbc3168e057a38fc322af909c7f5617483c50caddba135439ff09a754b20bdd6482a5123ff543dad4affa488ecf46ec5fb56d61312ad20bb140199b88dfaea9 + checksum: 10/26bdc6d58b29528f4142d29afb8526bc335f4fc04c4a10f2b98b217f277a031c66736bf82d3d3bb354a2f6a3ae50f18fd62b053c4ac3f294a3d10a61f5075b75 languageName: node linkType: hard @@ -45333,10 +43922,24 @@ __metadata: languageName: node linkType: hard -"seq-queue@npm:^0.0.5": - version: 0.0.5 - resolution: "seq-queue@npm:0.0.5" - checksum: 10/fa302e3b2aaece644532603ae42d675f9b8750e395a98740dd58dc5e02985ce6f0c2b78715b5984d6f6a807893735a14212a70d6ec591e6fba410397269588a0 +"send@npm:~0.19.0": + version: 0.19.1 + resolution: "send@npm:0.19.1" + dependencies: + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + encodeurl: "npm:~2.0.0" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" + mime: "npm:1.6.0" + ms: "npm:2.1.3" + on-finished: "npm:2.4.1" + range-parser: "npm:~1.2.1" + statuses: "npm:2.0.1" + checksum: 10/360bf50a839c7bbc181f67c3a0f3424a7ad8016dfebcd9eb90891f4b762b4377da14414c32250d67b53872e884171c27469110626f6c22765caa7c38c207ee1d languageName: node linkType: hard @@ -45358,27 +43961,18 @@ __metadata: languageName: node linkType: hard -"serialize-javascript@npm:^6.0.2": - version: 6.0.2 - resolution: "serialize-javascript@npm:6.0.2" - dependencies: - randombytes: "npm:^2.1.0" - checksum: 10/445a420a6fa2eaee4b70cbd884d538e259ab278200a2ededd73253ada17d5d48e91fb1f4cd224a236ab62ea7ba0a70c6af29fc93b4f3d3078bf7da1c031fde58 - languageName: node - linkType: hard - "serve-handler@npm:^6.1.3": - version: 6.1.6 - resolution: "serve-handler@npm:6.1.6" + version: 6.1.7 + resolution: "serve-handler@npm:6.1.7" dependencies: bytes: "npm:3.0.0" content-disposition: "npm:0.5.2" mime-types: "npm:2.1.18" - minimatch: "npm:3.1.2" + minimatch: "npm:3.1.5" path-is-inside: "npm:1.0.2" path-to-regexp: "npm:3.3.0" range-parser: "npm:1.2.0" - checksum: 10/7e7d93eb7e69fcd9f9c5afc2ef2b46cb0072b4af13cbabef9bca725afb350ddae6857d8c8be2c256f7ce1f7677c20347801399c11caa5805c0090339f894e8f2 + checksum: 10/2366e53cc8e8376d58abb289293b930111fa5da6d14bb31eafac5b1162f332c45c6f394c7d78fdcf6b5736e12caf9370b02d05c7e8a75291d2fc6a55b52b14ea languageName: node linkType: hard @@ -45397,18 +43991,6 @@ __metadata: languageName: node linkType: hard -"serve-static@npm:1.16.2": - version: 1.16.2 - resolution: "serve-static@npm:1.16.2" - dependencies: - encodeurl: "npm:~2.0.0" - escape-html: "npm:~1.0.3" - parseurl: "npm:~1.3.3" - send: "npm:0.19.0" - checksum: 10/7fa9d9c68090f6289976b34fc13c50ac8cd7f16ae6bce08d16459300f7fc61fbc2d7ebfa02884c073ec9d6ab9e7e704c89561882bbe338e99fcacb2912fde737 - languageName: node - linkType: hard - "serve-static@npm:^2.2.0": version: 2.2.0 resolution: "serve-static@npm:2.2.0" @@ -45421,6 +44003,18 @@ __metadata: languageName: node linkType: hard +"serve-static@npm:~1.16.2": + version: 1.16.2 + resolution: "serve-static@npm:1.16.2" + dependencies: + encodeurl: "npm:~2.0.0" + escape-html: "npm:~1.0.3" + parseurl: "npm:~1.3.3" + send: "npm:0.19.0" + checksum: 10/7fa9d9c68090f6289976b34fc13c50ac8cd7f16ae6bce08d16459300f7fc61fbc2d7ebfa02884c073ec9d6ab9e7e704c89561882bbe338e99fcacb2912fde737 + languageName: node + linkType: hard + "set-blocking@npm:^2.0.0": version: 2.0.0 resolution: "set-blocking@npm:2.0.0" @@ -45468,6 +44062,17 @@ __metadata: languageName: node linkType: hard +"set-proto@npm:^1.0.0": + version: 1.0.0 + resolution: "set-proto@npm:1.0.0" + dependencies: + dunder-proto: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + checksum: 10/b87f8187bca595ddc3c0721ece4635015fd9d7cb294e6dd2e394ce5186a71bbfa4dc8a35010958c65e43ad83cde09642660e61a952883c24fd6b45ead15f045c + languageName: node + linkType: hard + "set-value@npm:^4.1.0": version: 4.1.0 resolution: "set-value@npm:4.1.0" @@ -45492,14 +44097,14 @@ __metadata: languageName: node linkType: hard -"setprototypeof@npm:1.2.0": +"setprototypeof@npm:1.2.0, setprototypeof@npm:~1.2.0": version: 1.2.0 resolution: "setprototypeof@npm:1.2.0" checksum: 10/fde1630422502fbbc19e6844346778f99d449986b2f9cdcceb8326730d2f3d9964dbcb03c02aaadaefffecd0f2c063315ebea8b3ad895914bf1afc1747fc172e languageName: node linkType: hard -"sha.js@npm:^2.4.0, sha.js@npm:^2.4.11, sha.js@npm:^2.4.12, sha.js@npm:^2.4.8, sha.js@npm:^2.4.9": +"sha.js@npm:^2.4.0, sha.js@npm:^2.4.11, sha.js@npm:^2.4.12, sha.js@npm:^2.4.8": version: 2.4.12 resolution: "sha.js@npm:2.4.12" dependencies: @@ -45512,6 +44117,15 @@ __metadata: languageName: node linkType: hard +"shadow-dom-testing-library@npm:^1.13.1": + version: 1.13.1 + resolution: "shadow-dom-testing-library@npm:1.13.1" + peerDependencies: + "@testing-library/dom": ">= 8" + checksum: 10/a10f3466592691368f260c15230d9e830cc5122d20002ad9b6c2918fa245089219d1f45c026153fa6ccf8848b546da6f985c0e461458c8c1e09786a1de626287 + languageName: node + linkType: hard + "shallow-clone@npm:^3.0.0": version: 3.0.1 resolution: "shallow-clone@npm:3.0.1" @@ -45581,13 +44195,6 @@ __metadata: languageName: node linkType: hard -"shimmer@npm:^1.2.1": - version: 1.2.1 - resolution: "shimmer@npm:1.2.1" - checksum: 10/aa0d6252ad1c682a4fdfda69e541be987f7a265ac7b00b1208e5e48cc68dc55f293955346ea4c71a169b7324b82c70f8400b3d3d2d60b2a7519f0a3522423250 - languageName: node - linkType: hard - "short-unique-id@npm:^5.3.2": version: 5.3.2 resolution: "short-unique-id@npm:5.3.2" @@ -45645,7 +44252,7 @@ __metadata: languageName: node linkType: hard -"side-channel@npm:^1.0.6, side-channel@npm:^1.1.0": +"side-channel@npm:^1.1.0": version: 1.1.0 resolution: "side-channel@npm:1.1.0" dependencies: @@ -45713,15 +44320,6 @@ __metadata: languageName: node linkType: hard -"simple-swizzle@npm:^0.2.2": - version: 0.2.2 - resolution: "simple-swizzle@npm:0.2.2" - dependencies: - is-arrayish: "npm:^0.3.1" - checksum: 10/c6dffff17aaa383dae7e5c056fbf10cf9855a9f79949f20ee225c04f06ddde56323600e0f3d6797e82d08d006e93761122527438ee9531620031c08c9e0d73cc - languageName: node - linkType: hard - "simple-update-notifier@npm:^2.0.0": version: 2.0.0 resolution: "simple-update-notifier@npm:2.0.0" @@ -45752,6 +44350,15 @@ __metadata: languageName: node linkType: hard +"skin-tone@npm:^2.0.0": + version: 2.0.0 + resolution: "skin-tone@npm:2.0.0" + dependencies: + unicode-emoji-modifier-base: "npm:^1.0.0" + checksum: 10/19de157586b8019cacc55eb25d9d640f00fc02415761f3e41a4527142970fd4e7f6af0333bc90e879858766c20a976107bb386ffd4c812289c01d51f2c8d182c + languageName: node + linkType: hard + "slash@npm:^3.0.0": version: 3.0.0 resolution: "slash@npm:3.0.0" @@ -45759,13 +44366,6 @@ __metadata: languageName: node linkType: hard -"slash@npm:^5.1.0": - version: 5.1.0 - resolution: "slash@npm:5.1.0" - checksum: 10/2c41ec6fb1414cd9bba0fa6b1dd00e8be739e3fe85d079c69d4b09ca5f2f86eafd18d9ce611c0c0f686428638a36c272a6ac14799146a8295f259c10cc45cde4 - languageName: node - linkType: hard - "slice-ansi@npm:^3.0.0": version: 3.0.0 resolution: "slice-ansi@npm:3.0.0" @@ -45788,16 +44388,6 @@ __metadata: languageName: node linkType: hard -"slice-ansi@npm:^5.0.0": - version: 5.0.0 - resolution: "slice-ansi@npm:5.0.0" - dependencies: - ansi-styles: "npm:^6.0.0" - is-fullwidth-code-point: "npm:^4.0.0" - checksum: 10/7e600a2a55e333a21ef5214b987c8358fe28bfb03c2867ff2cbf919d62143d1812ac27b4297a077fdaf27a03da3678e49551c93e35f9498a3d90221908a1180e - languageName: node - linkType: hard - "slice-ansi@npm:^7.1.0": version: 7.1.0 resolution: "slice-ansi@npm:7.1.0" @@ -45808,6 +44398,16 @@ __metadata: languageName: node linkType: hard +"slice-ansi@npm:^8.0.0": + version: 8.0.0 + resolution: "slice-ansi@npm:8.0.0" + dependencies: + ansi-styles: "npm:^6.2.3" + is-fullwidth-code-point: "npm:^5.1.0" + checksum: 10/6a7e146852047e26dd5857b35c767e52906549c580cce0ad2287cc32f54f5a582494f674817fc9ac21b2e4ac1ddeaa85b3dee409782681b465330278890c73a8 + languageName: node + linkType: hard + "sloc@npm:^0.3.1": version: 0.3.2 resolution: "sloc@npm:0.3.2" @@ -45829,10 +44429,10 @@ __metadata: languageName: node linkType: hard -"smol-toml@npm:^1.3.1": - version: 1.3.1 - resolution: "smol-toml@npm:1.3.1" - checksum: 10/b999828ea46cf44ae90b6293884d6a139dfb4545ac6f86cbd1002568a943a43d8895ad82413855d095eec0c0bc21d23413c0a25a26c7fad6395c2ce42c2fdbd0 +"smol-toml@npm:^1.5.2": + version: 1.6.1 + resolution: "smol-toml@npm:1.6.1" + checksum: 10/9a0d86cc7f8abef429c915b373b9a1f369fe57a87efbbec46b967fb41dc28af753a2fa62c9c4848907c3b47c282be15c8854aa4e2942ef1fa86ff95a76d13856 languageName: node linkType: hard @@ -45857,14 +44457,14 @@ __metadata: languageName: node linkType: hard -"snyk-nodejs-lockfile-parser@npm:^1.58.14": - version: 1.60.1 - resolution: "snyk-nodejs-lockfile-parser@npm:1.60.1" +"snyk-nodejs-lockfile-parser@npm:^2.0.0": + version: 2.6.1 + resolution: "snyk-nodejs-lockfile-parser@npm:2.6.1" dependencies: - "@snyk/dep-graph": "npm:^2.3.0" + "@snyk/dep-graph": "npm:^2.12.0" "@snyk/error-catalog-nodejs-public": "npm:^5.16.0" "@snyk/graphlib": "npm:2.1.9-patch.3" - "@yarnpkg/core": "npm:^2.4.0" + "@yarnpkg/core": "npm:^4.4.1" "@yarnpkg/lockfile": "npm:^1.1.0" dependency-path: "npm:^9.2.8" event-loop-spinner: "npm:^2.0.0" @@ -45881,7 +44481,7 @@ __metadata: uuid: "npm:^8.3.0" bin: parse-nodejs-lockfile: bin/index.js - checksum: 10/2186abf1a7930ff12c5a3929c514300a0ecb47f576f64b84b265292c106aba9ec13a98cdae0b2f01449e53311d361a67cbb13ed631e718d28faaafa97971adc5 + checksum: 10/6573f7379f0005f632678b8518fd5559f91548fc3fb5f2004a92defb44e99acc46097382c7017f6f901df89733e521486229b5fcf9e6d1715b508ca1f9d46fff languageName: node linkType: hard @@ -45918,7 +44518,7 @@ __metadata: languageName: node linkType: hard -"socks-proxy-agent@npm:^8.0.1, socks-proxy-agent@npm:^8.0.3, socks-proxy-agent@npm:^8.0.4, socks-proxy-agent@npm:^8.0.5": +"socks-proxy-agent@npm:^8.0.3, socks-proxy-agent@npm:^8.0.4, socks-proxy-agent@npm:^8.0.5": version: 8.0.5 resolution: "socks-proxy-agent@npm:8.0.5" dependencies: @@ -45949,28 +44549,27 @@ __metadata: languageName: node linkType: hard -"sort-object-keys@npm:^1.1.3": - version: 1.1.3 - resolution: "sort-object-keys@npm:1.1.3" - checksum: 10/abea944d6722a1710a1aa6e4f9509da085d93d5fc0db23947cb411eedc7731f80022ce8fa68ed83a53dd2ac7441fcf72a3f38c09b3d9bbc4ff80546aa2e151ad +"sort-object-keys@npm:^2.0.1": + version: 2.1.0 + resolution: "sort-object-keys@npm:2.1.0" + checksum: 10/a0a18a6f4ab601adb889204a83e2664a4810042dd9680e588cabb47fffd40473939ce3fda5a28fdb58bac0762a7b45ad442815465b978d4b243bab840df3d7cd languageName: node linkType: hard -"sort-package-json@npm:^2.8.0": - version: 2.15.1 - resolution: "sort-package-json@npm:2.15.1" +"sort-package-json@npm:^3.0.0": + version: 3.6.1 + resolution: "sort-package-json@npm:3.6.1" dependencies: - detect-indent: "npm:^7.0.1" - detect-newline: "npm:^4.0.0" - get-stdin: "npm:^9.0.0" - git-hooks-list: "npm:^3.0.0" + detect-indent: "npm:^7.0.2" + detect-newline: "npm:^4.0.1" + git-hooks-list: "npm:^4.1.1" is-plain-obj: "npm:^4.1.0" - semver: "npm:^7.6.0" - sort-object-keys: "npm:^1.1.3" - tinyglobby: "npm:^0.2.9" + semver: "npm:^7.7.3" + sort-object-keys: "npm:^2.0.1" + tinyglobby: "npm:^0.2.15" bin: sort-package-json: cli.js - checksum: 10/3378565a07368e00eeb625e6b85d1edf9a3bf9f88ced32423bd3036ddb8c674fb8c0fb559044ef939e6de20bb7550423e992f4abd19cff2398d006f0fe8afc82 + checksum: 10/e4bcf8432b570143e94b6b302fc941fd0b564e8f9f423bc55ae469c81e6f84c35e456f024d8d186a2ec6ca120407444e6ceb5a5f0feb7c877c159ef716f651ed languageName: node linkType: hard @@ -45981,14 +44580,7 @@ __metadata: languageName: node linkType: hard -"source-list-map@npm:^2.0.0": - version: 2.0.1 - resolution: "source-list-map@npm:2.0.1" - checksum: 10/3918ffba5fe8447bc816800026fe707aab233d9d05a3487225d880e23b7e37ed455b4e1b844e05644f6ecc7c9b837c0cc32da54dd37f77c993370ebcdb049246 - languageName: node - linkType: hard - -"source-map-js@npm:^1.2.1": +"source-map-js@npm:^1.0.1, source-map-js@npm:^1.2.1": version: 1.2.1 resolution: "source-map-js@npm:1.2.1" checksum: 10/ff9d8c8bf096d534a5b7707e0382ef827b4dd360a577d3f34d2b9f48e12c9d230b5747974ee7c607f0df65113732711bb701fe9ece3c7edbd43cb2294d707df3 @@ -46117,12 +44709,10 @@ __metadata: languageName: node linkType: hard -"split@npm:^1.0.0": - version: 1.0.1 - resolution: "split@npm:1.0.1" - dependencies: - through: "npm:2" - checksum: 10/12f4554a5792c7e98bb3e22b53c63bfa5ef89aa704353e1db608a55b51f5b12afaad6e4a8ecf7843c15f273f43cdadd67b3705cc43d48a75c2cf4641d51f7e7a +"split2@npm:^4.1.0": + version: 4.2.0 + resolution: "split2@npm:4.2.0" + checksum: 10/09bbefc11bcf03f044584c9764cd31a252d8e52cea29130950b26161287c11f519807c5e54bd9e5804c713b79c02cefe6a98f4688630993386be353e03f534ab languageName: node linkType: hard @@ -46149,10 +44739,17 @@ __metadata: languageName: node linkType: hard -"sqlstring@npm:^2.3.2": - version: 2.3.2 - resolution: "sqlstring@npm:2.3.2" - checksum: 10/56fd2851ce6054f1185722bf7da4dbbd1ed24af64073ce58f142d5f6c62451d4a6dbad80999880debcdf764b37340f8f93155d641983e619a13abe5531849255 +"sqids@npm:^0.3.0": + version: 0.3.0 + resolution: "sqids@npm:0.3.0" + checksum: 10/68f30434dae278a3ec030c2b16447cefd32129f73350aa3312ca55df48a8ba36bac0d9d2bef625020f13d57568586b6fa2bbbffa3051e55b6a3654caec31a8b6 + languageName: node + linkType: hard + +"sql-escaper@npm:^1.3.3": + version: 1.3.3 + resolution: "sql-escaper@npm:1.3.3" + checksum: 10/bc53ff84eba322ba76bd10f95c2a180b477e13453236583dbed3bed48d6ade3ca73922eaa860372c2b3a9427e3345fd1a19d1e3b8d0e36fa1d79c450f113b962 languageName: node linkType: hard @@ -46183,15 +44780,6 @@ __metadata: languageName: node linkType: hard -"ssri@npm:^10.0.0": - version: 10.0.5 - resolution: "ssri@npm:10.0.5" - dependencies: - minipass: "npm:^7.0.3" - checksum: 10/453f9a1c241c13f5dfceca2ab7b4687bcff354c3ccbc932f35452687b9ef0ccf8983fd13b8a3baa5844c1a4882d6e3ddff48b0e7fd21d743809ef33b80616d79 - languageName: node - linkType: hard - "ssri@npm:^12.0.0": version: 12.0.0 resolution: "ssri@npm:12.0.0" @@ -46201,6 +44789,15 @@ __metadata: languageName: node linkType: hard +"ssri@npm:^13.0.0": + version: 13.0.1 + resolution: "ssri@npm:13.0.1" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10/ae560d0378d074006a71b06af71bfbe84a3fe1ac6e16c1f07575f69e670d40170507fe52b21bcc23399429bc6a15f4bc3ea8d9bc88e9dfd7e87de564e6da6a72 + languageName: node + linkType: hard + "ssri@npm:^8.0.0, ssri@npm:^8.0.1": version: 8.0.1 resolution: "ssri@npm:8.0.1" @@ -46242,7 +44839,7 @@ __metadata: languageName: node linkType: hard -"stack-utils@npm:^2.0.2, stack-utils@npm:^2.0.3": +"stack-utils@npm:^2.0.2, stack-utils@npm:^2.0.6": version: 2.0.6 resolution: "stack-utils@npm:2.0.6" dependencies: @@ -46286,30 +44883,28 @@ __metadata: languageName: node linkType: hard -"static-eval@npm:2.0.2": - version: 2.0.2 - resolution: "static-eval@npm:2.0.2" - dependencies: - escodegen: "npm:^1.8.1" - checksum: 10/2e2faf1b23bad5d9d5b2407b18945c7b97f8706b6d65f06bb3583a2d4fd1994cf5890c5779a1bfa2a02905dc860e077e4f045d7413d289d8993f605758f8992f - languageName: node - linkType: hard - -"statuses@npm:2.0.1, statuses@npm:^2.0.1": +"statuses@npm:2.0.1": version: 2.0.1 resolution: "statuses@npm:2.0.1" checksum: 10/18c7623fdb8f646fb213ca4051be4df7efb3484d4ab662937ca6fbef7ced9b9e12842709872eb3020cc3504b93bde88935c9f6417489627a7786f24f8031cbcb languageName: node linkType: hard -"statuses@npm:>= 1.4.0 < 2, statuses@npm:>= 1.5.0 < 2, statuses@npm:^1.5.0, statuses@npm:~1.5.0": +"statuses@npm:>= 1.4.0 < 2, statuses@npm:~1.5.0": version: 1.5.0 resolution: "statuses@npm:1.5.0" checksum: 10/c469b9519de16a4bb19600205cffb39ee471a5f17b82589757ca7bd40a8d92ebb6ed9f98b5a540c5d302ccbc78f15dc03cc0280dd6e00df1335568a5d5758a5c languageName: node linkType: hard -"stop-iteration-iterator@npm:^1.0.0": +"statuses@npm:^2.0.1, statuses@npm:~2.0.1, statuses@npm:~2.0.2": + version: 2.0.2 + resolution: "statuses@npm:2.0.2" + checksum: 10/6927feb50c2a75b2a4caab2c565491f7a93ad3d8dbad7b1398d52359e9243a20e2ebe35e33726dee945125ef7a515e9097d8a1b910ba2bbd818265a2f6c39879 + languageName: node + linkType: hard + +"stop-iteration-iterator@npm:^1.0.0, stop-iteration-iterator@npm:^1.1.0": version: 1.1.0 resolution: "stop-iteration-iterator@npm:1.1.0" dependencies: @@ -46326,21 +44921,21 @@ __metadata: languageName: node linkType: hard -"storybook@npm:^9.1.7": - version: 9.1.16 - resolution: "storybook@npm:9.1.16" +"storybook@npm:^10.3.3": + version: 10.3.3 + resolution: "storybook@npm:10.3.3" dependencies: "@storybook/global": "npm:^5.0.0" - "@testing-library/jest-dom": "npm:^6.6.3" + "@storybook/icons": "npm:^2.0.1" + "@testing-library/jest-dom": "npm:^6.9.1" "@testing-library/user-event": "npm:^14.6.1" "@vitest/expect": "npm:3.2.4" - "@vitest/mocker": "npm:3.2.4" "@vitest/spy": "npm:3.2.4" - better-opn: "npm:^3.0.2" - esbuild: "npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0" - esbuild-register: "npm:^3.5.0" + esbuild: "npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0" + open: "npm:^10.2.0" recast: "npm:^0.23.5" - semver: "npm:^7.6.2" + semver: "npm:^7.7.3" + use-sync-external-store: "npm:^1.5.0" ws: "npm:^8.18.0" peerDependencies: prettier: ^2 || ^3 @@ -46348,8 +44943,8 @@ __metadata: prettier: optional: true bin: - storybook: ./bin/index.cjs - checksum: 10/62a79c47bd0ac65af8d2de4123332a578aaff76e94ffba45293633db0c316488d7b603ffe47b23f998a872e6428c25b9fd944bc8eb203318394f421490021e96 + storybook: ./dist/bin/dispatcher.js + checksum: 10/031b71d97bb8fdb695095e38f172556f36a0748c8ff6762fed64ab0e9a7036fd8a4ae40142c394b1028d053b4edb5949b43734eb737b628c2a60996756cbd0dc languageName: node linkType: hard @@ -46414,7 +45009,7 @@ __metadata: languageName: node linkType: hard -"stream-to-array@npm:^2.3.0, stream-to-array@npm:~2.3.0": +"stream-to-array@npm:^2.3.0": version: 2.3.0 resolution: "stream-to-array@npm:2.3.0" dependencies: @@ -46423,17 +45018,6 @@ __metadata: languageName: node linkType: hard -"stream-to-promise@npm:^2.2.0": - version: 2.2.0 - resolution: "stream-to-promise@npm:2.2.0" - dependencies: - any-promise: "npm:~1.3.0" - end-of-stream: "npm:~1.1.0" - stream-to-array: "npm:~2.3.0" - checksum: 10/e4d3253c68dae65c51c5aa1bd657a072267869fd61b57068e74cee7a8e45d67fe154b56918cf546b38cb5be1fa042e632b7267abc9676bb75bba55952d2d57d1 - languageName: node - linkType: hard - "streamroller@npm:^3.1.5": version: 3.1.5 resolution: "streamroller@npm:3.1.5" @@ -46514,13 +45098,13 @@ __metadata: languageName: node linkType: hard -"string-length@npm:^4.0.1": - version: 4.0.1 - resolution: "string-length@npm:4.0.1" +"string-length@npm:^4.0.2": + version: 4.0.2 + resolution: "string-length@npm:4.0.2" dependencies: char-regex: "npm:^1.0.2" strip-ansi: "npm:^6.0.0" - checksum: 10/7bd3191668ddafa6f574a8b17a1bd1b085737d64ceefa51f72cdd19c45a730422cd70d984eee7584d6e5b5c84b6318633c6d6a720a4bfd7c58769985fa77573e + checksum: 10/ce85533ef5113fcb7e522bcf9e62cb33871aa99b3729cec5595f4447f660b0cefd542ca6df4150c97a677d58b0cb727a3fe09ac1de94071d05526c73579bf505 languageName: node linkType: hard @@ -46557,6 +45141,16 @@ __metadata: languageName: node linkType: hard +"string-width@npm:^8.2.0": + version: 8.2.0 + resolution: "string-width@npm:8.2.0" + dependencies: + get-east-asian-width: "npm:^1.5.0" + strip-ansi: "npm:^7.1.2" + checksum: 10/c4f62877ec08fca155e84a260eb4f58f473cfe5169bd1c1e21ffb563d8e0b7f6d705cc3d250f2ed6bb4f30ee9732ad026f54afaac77aa487e3d1dc1b1969e51b + languageName: node + linkType: hard + "string.prototype.includes@npm:^2.0.1": version: 2.0.1 resolution: "string.prototype.includes@npm:2.0.1" @@ -46614,7 +45208,7 @@ __metadata: languageName: node linkType: hard -"string.prototype.trimend@npm:^1.0.8, string.prototype.trimend@npm:^1.0.9": +"string.prototype.trimend@npm:^1.0.9": version: 1.0.9 resolution: "string.prototype.trimend@npm:1.0.9" dependencies: @@ -46684,12 +45278,12 @@ __metadata: languageName: node linkType: hard -"strip-ansi@npm:^7.0.1, strip-ansi@npm:^7.1.0": - version: 7.1.2 - resolution: "strip-ansi@npm:7.1.2" +"strip-ansi@npm:^7.0.1, strip-ansi@npm:^7.1.0, strip-ansi@npm:^7.1.2": + version: 7.2.0 + resolution: "strip-ansi@npm:7.2.0" dependencies: - ansi-regex: "npm:^6.0.1" - checksum: 10/db0e3f9654e519c8a33c50fc9304d07df5649388e7da06d3aabf66d29e5ad65d5e6315d8519d409c15b32fa82c1df7e11ed6f8cd50b0e4404463f0c9d77c8d0b + ansi-regex: "npm:^6.2.2" + checksum: 10/96da3bc6d73cfba1218625a3d66cf7d37a69bf0920d8735b28f9eeaafcdb6c1fe8440e1ae9eb1ba0ca355dbe8702da872e105e2e939fa93e7851b3cb5dd7d316 languageName: node linkType: hard @@ -46749,13 +45343,6 @@ __metadata: languageName: node linkType: hard -"strip-final-newline@npm:^3.0.0": - version: 3.0.0 - resolution: "strip-final-newline@npm:3.0.0" - checksum: 10/23ee263adfa2070cd0f23d1ac14e2ed2f000c9b44229aec9c799f1367ec001478469560abefd00c5c99ee6f0b31c137d53ec6029c53e9f32a93804e18c201050 - languageName: node - linkType: hard - "strip-indent@npm:^3.0.0": version: 3.0.0 resolution: "strip-indent@npm:3.0.0" @@ -46774,10 +45361,10 @@ __metadata: languageName: node linkType: hard -"strip-json-comments@npm:5.0.1": - version: 5.0.1 - resolution: "strip-json-comments@npm:5.0.1" - checksum: 10/b314af70c6666a71133e309a571bdb87687fc878d9fd8b38ebed393a77b89835b92f191aa6b0bc10dfd028ba99eed6b6365985001d64c5aef32a4a82456a156b +"strip-json-comments@npm:5.0.3": + version: 5.0.3 + resolution: "strip-json-comments@npm:5.0.3" + checksum: 10/3ccbf26f278220f785e4b71f8a719a6a063d72558cc63cb450924254af258a4f4c008b8c9b055373a680dc7bd525be9e543ad742c177f8a7667e0b726258e0e4 languageName: node linkType: hard @@ -46802,19 +45389,19 @@ __metadata: languageName: node linkType: hard -"strnum@npm:^2.1.0": - version: 2.1.1 - resolution: "strnum@npm:2.1.1" - checksum: 10/d5fe6e4333cddc17569331048e403e876ffcf629989815f0359b0caf05dae9441b7eef3d7dd07427313ac8b3f05a8f60abc1f61efc15f97245dbc24028362bc9 +"strnum@npm:^2.2.0": + version: 2.2.2 + resolution: "strnum@npm:2.2.2" + checksum: 10/c55813cfded750dc84556b4881ffc7cee91382ff15a48f1fba0ff7a678e1640ed96ca40806fbd55724940fd7d51cf752469b2d862e196e4adefb6c7d5d9cd73b languageName: node linkType: hard -"strtok3@npm:^10.2.2": - version: 10.3.1 - resolution: "strtok3@npm:10.3.1" +"strtok3@npm:^10.3.4": + version: 10.3.4 + resolution: "strtok3@npm:10.3.4" dependencies: "@tokenizer/token": "npm:^0.3.0" - checksum: 10/bb7950cc9ce98ec742a5db360630f0b004f16197959ae28d8c8dad4f8f0e405d71cfdc992483038ba29a0b4cbd7227618ad2492005b510d84a3fc5903df0c13f + checksum: 10/53be14a567dca149be56cb072eaa3c0fffd70d066acf800cf588b91558c6d475364ff8d550524ce0499fc4873a4b0d42ad8c542bfdb9fb39cba520ef2e2e9818 languageName: node linkType: hard @@ -46937,54 +45524,48 @@ __metadata: linkType: hard "sucrase@npm:^3.20.2": - version: 3.35.0 - resolution: "sucrase@npm:3.35.0" + version: 3.35.1 + resolution: "sucrase@npm:3.35.1" dependencies: "@jridgewell/gen-mapping": "npm:^0.3.2" commander: "npm:^4.0.0" - glob: "npm:^10.3.10" lines-and-columns: "npm:^1.1.6" mz: "npm:^2.7.0" pirates: "npm:^4.0.1" + tinyglobby: "npm:^0.2.11" ts-interface-checker: "npm:^0.1.9" bin: sucrase: bin/sucrase sucrase-node: bin/sucrase-node - checksum: 10/bc601558a62826f1c32287d4fdfa4f2c09fe0fec4c4d39d0e257fd9116d7d6227a18309721d4185ec84c9dc1af0d5ec0e05a42a337fbb74fc293e068549aacbe + checksum: 10/539f5c6ebc1ff8d449a89eb52b8c8944a730b9840ddadbd299a7d89ebcf16c3f4bc9aa59e1f2e112a502e5cf1508f7e02065f0e97c0435eb9a7058e997dfff5a languageName: node linkType: hard -"summary@npm:2.1.0": - version: 2.1.0 - resolution: "summary@npm:2.1.0" - checksum: 10/10ac12ce12c013b56ad44c37cfac206961f0993d98867b33b1b03a27b38a1cf8dd2db0b788883356c5335bbbb37d953772ef4a381d6fc8f408faf99f2bc54af5 - languageName: node - linkType: hard - -"superagent@npm:^10.2.3": - version: 10.2.3 - resolution: "superagent@npm:10.2.3" +"superagent@npm:^10.3.0": + version: 10.3.0 + resolution: "superagent@npm:10.3.0" dependencies: component-emitter: "npm:^1.3.1" cookiejar: "npm:^2.1.4" debug: "npm:^4.3.7" fast-safe-stringify: "npm:^2.1.1" - form-data: "npm:^4.0.4" + form-data: "npm:^4.0.5" formidable: "npm:^3.5.4" methods: "npm:^1.1.2" mime: "npm:2.6.0" - qs: "npm:^6.11.2" - checksum: 10/377bf938e68927dd772169c5285be27872bf6e84fac01c52bcd9396bc5b348c9ded8f8be54649510ec09a67bc5096055847b37cb01b3bca0eb06ff1856170e35 + qs: "npm:^6.14.1" + checksum: 10/e62b13281403ef9a1f716465cf5d83c12dbb418afa8197d8ca3cc35c8dd1825a81bd5c627512ae289684f6b7c48fc9d76ce321c28de6142f69bbe905e866427f languageName: node linkType: hard "supertest@npm:^7.0.0, supertest@npm:^7.1.0": - version: 7.1.4 - resolution: "supertest@npm:7.1.4" + version: 7.2.2 + resolution: "supertest@npm:7.2.2" dependencies: + cookie-signature: "npm:^1.2.2" methods: "npm:^1.1.2" - superagent: "npm:^10.2.3" - checksum: 10/ecb5d41f2b62b257dbdcabac245c32b8e8fb264fe2636dd85c2c883569d23dc14adc0a471abb84187cbdb49bc36ad870ad355b4a0b85973f510fd57fc229e6cc + superagent: "npm:^10.3.0" + checksum: 10/41b29e005b7f0fec8e062df1e43037d5a29b40c1172331d1b448e8b39ed3cc020d2aac342aad6d6e75c1e07b2f08c32bff6472cd726ab4fc9b3f10969937979d languageName: node linkType: hard @@ -47015,7 +45596,7 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^7, supports-color@npm:^7.1.0": +"supports-color@npm:^7, supports-color@npm:^7.0.0, supports-color@npm:^7.1.0": version: 7.2.0 resolution: "supports-color@npm:7.2.0" dependencies: @@ -47024,6 +45605,16 @@ __metadata: languageName: node linkType: hard +"supports-hyperlinks@npm:^3.1.0": + version: 3.2.0 + resolution: "supports-hyperlinks@npm:3.2.0" + dependencies: + has-flag: "npm:^4.0.0" + supports-color: "npm:^7.0.0" + checksum: 10/f7924de6049fc30bc808f98d3561318c1a4e3d55d786f9fede5e23dc5a7b0f625485bd1143135b496d521ccd0110463f2c077eb71a4ce0cf783b8b31f7909242 + languageName: node + linkType: hard + "supports-preserve-symlinks-flag@npm:^1.0.0": version: 1.0.0 resolution: "supports-preserve-symlinks-flag@npm:1.0.0" @@ -47032,33 +45623,34 @@ __metadata: linkType: hard "svgo@npm:^2.7.0": - version: 2.8.0 - resolution: "svgo@npm:2.8.0" + version: 2.8.2 + resolution: "svgo@npm:2.8.2" dependencies: - "@trysound/sax": "npm:0.2.0" commander: "npm:^7.2.0" css-select: "npm:^4.1.3" css-tree: "npm:^1.1.3" csso: "npm:^4.2.0" picocolors: "npm:^1.0.0" + sax: "npm:^1.5.0" stable: "npm:^0.1.8" bin: - svgo: bin/svgo - checksum: 10/2b74544da1a9521852fe2784252d6083b336e32528d0e424ee54d1613f17312edc7020c29fa399086560e96cba42ede4a2205328a08edeefa26de84cd769a64a + svgo: ./bin/svgo + checksum: 10/a0922a2cbbbc51c0162ea7a7d6c5b660fb4fb65e0f05e226ba571cfe8b651fd870072aed2722ef96715fb77829562b351eb72f2b7bf09038ffc88969acaffd0f languageName: node linkType: hard -"swagger-client@npm:^3.35.5": - version: 3.35.6 - resolution: "swagger-client@npm:3.35.6" +"swagger-client@npm:^3.37.1": + version: 3.37.1 + resolution: "swagger-client@npm:3.37.1" dependencies: "@babel/runtime-corejs3": "npm:^7.22.15" "@scarf/scarf": "npm:=1.4.0" - "@swagger-api/apidom-core": "npm:>=1.0.0-beta.41 <1.0.0-rc.0" - "@swagger-api/apidom-error": "npm:>=1.0.0-beta.41 <1.0.0-rc.0" - "@swagger-api/apidom-json-pointer": "npm:>=1.0.0-beta.41 <1.0.0-rc.0" - "@swagger-api/apidom-ns-openapi-3-1": "npm:>=1.0.0-beta.41 <1.0.0-rc.0" - "@swagger-api/apidom-reference": "npm:>=1.0.0-beta.41 <1.0.0-rc.0" + "@swagger-api/apidom-core": "npm:^1.7.0" + "@swagger-api/apidom-error": "npm:^1.7.0" + "@swagger-api/apidom-json-pointer": "npm:^1.7.0" + "@swagger-api/apidom-ns-openapi-3-1": "npm:^1.7.0" + "@swagger-api/apidom-ns-openapi-3-2": "npm:^1.7.0" + "@swagger-api/apidom-reference": "npm:^1.7.0" "@swaggerexpert/cookie": "npm:^2.0.2" deepmerge: "npm:~4.3.0" fast-json-patch: "npm:^3.0.0-1" @@ -47070,13 +45662,13 @@ __metadata: openapi-server-url-templating: "npm:^1.3.0" ramda: "npm:^0.30.1" ramda-adjunct: "npm:^5.1.0" - checksum: 10/fb6582dc45c2ae6a26ae4cc80f0a3d97e31f3b058676951f93670828f2f30f5d44ecd7c029465f5452b251a533be329a955c92cec11be7128baa5e2e963e6f9d + checksum: 10/51d9091bdb72e2d1544469d6f3b04d40de7864cfd5973b72a0efb05b9072cc7ad2a1efa0b05d6de3862e1392737c4e614ad2d7c1045bf5621dc04ab3f152a931 languageName: node linkType: hard "swagger-ui-react@npm:^5.27.1": - version: 5.29.0 - resolution: "swagger-ui-react@npm:5.29.0" + version: 5.32.1 + resolution: "swagger-ui-react@npm:5.32.1" dependencies: "@babel/runtime-corejs3": "npm:^7.27.1" "@scarf/scarf": "npm:=1.4.0" @@ -47085,11 +45677,11 @@ __metadata: classnames: "npm:^2.5.1" css.escape: "npm:1.5.1" deep-extend: "npm:0.6.0" - dompurify: "npm:=3.2.4" + dompurify: "npm:^3.3.2" ieee754: "npm:^1.2.1" immutable: "npm:^3.x.x" js-file-download: "npm:^0.4.12" - js-yaml: "npm:=4.1.0" + js-yaml: "npm:=4.1.1" lodash: "npm:^4.17.21" prop-types: "npm:^15.8.1" randexp: "npm:^0.5.3" @@ -47100,14 +45692,14 @@ __metadata: react-immutable-pure-component: "npm:^2.2.0" react-inspector: "npm:^6.0.1" react-redux: "npm:^9.2.0" - react-syntax-highlighter: "npm:^15.6.1" + react-syntax-highlighter: "npm:^16.0.0" redux: "npm:^5.0.1" redux-immutable: "npm:^4.0.0" remarkable: "npm:^2.0.1" reselect: "npm:^5.1.1" serialize-error: "npm:^8.1.0" sha.js: "npm:^2.4.12" - swagger-client: "npm:^3.35.5" + swagger-client: "npm:^3.37.1" url-parse: "npm:^1.5.10" xml: "npm:=1.0.1" xml-but-prettier: "npm:^1.0.1" @@ -47115,31 +45707,31 @@ __metadata: peerDependencies: react: ">=16.8.0 <20" react-dom: ">=16.8.0 <20" - checksum: 10/994a7b06b17fe12693a7a09057161723f5e2ab2c80c747879951a54f0d7a0cd4c65c139bdaf229f13d61f5dd61c2b63218881fc23081ced0cc91745b48d806d5 + checksum: 10/348ebfcf81a97363802528649297c9f37edad90a0276992da149e461b57e3301091f129dacaa297e747b8a1b0aac158aedc19b00047b63cc9d108e20eb9c264e languageName: node linkType: hard "swc-loader@npm:^0.2.3": - version: 0.2.6 - resolution: "swc-loader@npm:0.2.6" + version: 0.2.7 + resolution: "swc-loader@npm:0.2.7" dependencies: "@swc/counter": "npm:^0.1.3" peerDependencies: "@swc/core": ^1.2.147 webpack: ">=2" - checksum: 10/fe90948c02a51bb8ffcff1ce3590e01dc12860b0bb7c9e22052b14fa846ed437781ae265614a5e14344bea22001108780f00a6e350e28c0b3499bc4cd11335fb + checksum: 10/15cbc3769209f7e2f927e49c19a5ef30fe03663bd34688289c003d966c7dfe782eec39de77c454aa7465ce84138d8bc31257b736b32ff3be10191f1de88cbbf5 languageName: node linkType: hard "swr@npm:^2.0.0, swr@npm:^2.2.5": - version: 2.2.5 - resolution: "swr@npm:2.2.5" + version: 2.4.1 + resolution: "swr@npm:2.4.1" dependencies: - client-only: "npm:^0.0.1" - use-sync-external-store: "npm:^1.2.0" + dequal: "npm:^2.0.3" + use-sync-external-store: "npm:^1.6.0" peerDependencies: - react: ^16.11.0 || ^17.0.0 || ^18.0.0 - checksum: 10/f02b3bd5a198a0f62f9a53d7c0528c4a58aa61a43310bea169614b6e873dadb52599e856ef0775405b6aa7409835343da0cf328948aa892aa309bf4b7e7d6902 + react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10/611c0a39d42abb7d7eae37eb36e6a30a8a2c387bab6026d8db13ae41b9f8f3c2a187486008781de3f083ef5b39e6d90af1f275a73ade882a567e338166266356 languageName: node linkType: hard @@ -47157,6 +45749,33 @@ __metadata: languageName: node linkType: hard +"sync-fetch@npm:0.6.0": + version: 0.6.0 + resolution: "sync-fetch@npm:0.6.0" + dependencies: + node-fetch: "npm:^3.3.2" + timeout-signal: "npm:^2.0.0" + whatwg-mimetype: "npm:^4.0.0" + checksum: 10/524f4d45e903d208351d9bc43802002394cbf5e12699fb57471594f433cef5dfaf0c41e7f3484255865683f64125aeb6c279d264be123fc93bb9c6ae3f064bb4 + languageName: node + linkType: hard + +"synckit@npm:^0.11.8": + version: 0.11.11 + resolution: "synckit@npm:0.11.11" + dependencies: + "@pkgr/core": "npm:^0.2.9" + checksum: 10/6ecd88212b5be80004376b6ea74babcba284566ff59a50d8803afcaa78c165b5d268635c1dd84532ee3f690a979409e1eda225a8a35bed2d135ffdcea06ce7b0 + languageName: node + linkType: hard + +"tapable@npm:2.3.0, tapable@npm:^2.0.0, tapable@npm:^2.2.0, tapable@npm:^2.2.1, tapable@npm:^2.3.0": + version: 2.3.0 + resolution: "tapable@npm:2.3.0" + checksum: 10/496a841039960533bb6e44816a01fffc2a1eb428bb2051ecab9e87adf07f19e1f937566cbbbb09dceff31163c0ffd81baafcad84db900b601f0155dd0b37e9f2 + languageName: node + linkType: hard + "tapable@npm:^1.0.0": version: 1.1.3 resolution: "tapable@npm:1.1.3" @@ -47164,13 +45783,6 @@ __metadata: languageName: node linkType: hard -"tapable@npm:^2.0.0, tapable@npm:^2.1.1, tapable@npm:^2.2.0, tapable@npm:^2.2.1": - version: 2.2.1 - resolution: "tapable@npm:2.2.1" - checksum: 10/1769336dd21481ae6347611ca5fca47add0962fd8e80466515032125eca0084a4f0ede11e65341b9c0018ef4e1cf1ad820adbb0fba7cc99865c6005734000b0a - languageName: node - linkType: hard - "tar-fs@npm:^2.0.0, tar-fs@npm:^2.1.4": version: 2.1.4 resolution: "tar-fs@npm:2.1.4" @@ -47183,9 +45795,9 @@ __metadata: languageName: node linkType: hard -"tar-fs@npm:^3.0.6, tar-fs@npm:^3.0.9": - version: 3.1.1 - resolution: "tar-fs@npm:3.1.1" +"tar-fs@npm:^3.0.9, tar-fs@npm:^3.1.2": + version: 3.1.2 + resolution: "tar-fs@npm:3.1.2" dependencies: bare-fs: "npm:^4.0.1" bare-path: "npm:^3.0.0" @@ -47196,23 +45808,11 @@ __metadata: optional: true bare-path: optional: true - checksum: 10/f7f7540b563e10541dc0b95f710c68fc1fccde0c1177b4d3bab2023c6d18da19d941a8697fdc1abff54914b71b6e5f2dfb0455572b5c8993b2ab76571cbbc923 + checksum: 10/b358fb7061eebb42bfa6f122cf62d1bdd40dc619117863f3b59eeaa4f880dc03707014905bdb592e77176703d9045956d1ba27adda4458805f9f7cbf62015cbd languageName: node linkType: hard -"tar-fs@npm:~2.0.1": - version: 2.0.1 - resolution: "tar-fs@npm:2.0.1" - dependencies: - chownr: "npm:^1.1.1" - mkdirp-classic: "npm:^0.5.2" - pump: "npm:^3.0.0" - tar-stream: "npm:^2.0.0" - checksum: 10/85ceac6fce0e9175b5b67c0eca8864b7d29a940cae8b7657c60b66e8a252319d701c3df12814162a6839e6120f9e1975757293bdeaf294ad5b15721d236c4d32 - languageName: node - linkType: hard - -"tar-stream@npm:^2.0.0, tar-stream@npm:^2.0.1, tar-stream@npm:^2.1.4": +"tar-stream@npm:^2.0.1, tar-stream@npm:^2.1.4": version: 2.2.0 resolution: "tar-stream@npm:2.2.0" dependencies: @@ -47236,7 +45836,7 @@ __metadata: languageName: node linkType: hard -"tar@npm:^6.0.2, tar@npm:^6.0.5, tar@npm:^6.1.0, tar@npm:^6.1.11, tar@npm:^6.1.12, tar@npm:^6.1.2, tar@npm:^6.2.1": +"tar@npm:^6.0.2, tar@npm:^6.0.5, tar@npm:^6.1.0, tar@npm:^6.1.11, tar@npm:^6.1.2": version: 6.2.1 resolution: "tar@npm:6.2.1" dependencies: @@ -47250,17 +45850,16 @@ __metadata: languageName: node linkType: hard -"tar@npm:^7.4.3": - version: 7.4.3 - resolution: "tar@npm:7.4.3" +"tar@npm:^7.4.3, tar@npm:^7.5.4, tar@npm:^7.5.6": + version: 7.5.13 + resolution: "tar@npm:7.5.13" dependencies: "@isaacs/fs-minipass": "npm:^4.0.0" chownr: "npm:^3.0.0" minipass: "npm:^7.1.2" - minizlib: "npm:^3.0.1" - mkdirp: "npm:^3.0.1" + minizlib: "npm:^3.1.0" yallist: "npm:^5.0.0" - checksum: 10/12a2a4fc6dee23e07cc47f1aeb3a14a1afd3f16397e1350036a8f4cdfee8dcac7ef5978337a4e7b2ac2c27a9a6d46388fc2088ea7c80cb6878c814b1425f8ecf + checksum: 10/2bc2b6f0349038a6621dbba1c4522d45752d5071b2994692257113c2050cd23fafc30308f820e5f8ad6fda3f7d7f92adc9a432aa733daa04c42af2061c021c3f languageName: node linkType: hard @@ -47300,14 +45899,15 @@ __metadata: version: 0.0.0-use.local resolution: "techdocs-cli-embedded-app@workspace:packages/techdocs-cli-embedded-app" dependencies: - "@backstage/app-defaults": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/core-app-api": "workspace:^" "@backstage/core-components": "workspace:^" - "@backstage/core-plugin-api": "workspace:^" + "@backstage/frontend-defaults": "workspace:^" + "@backstage/frontend-plugin-api": "workspace:^" "@backstage/integration-react": "workspace:^" + "@backstage/plugin-app-react": "workspace:^" "@backstage/plugin-catalog": "workspace:^" "@backstage/plugin-techdocs": "workspace:^" "@backstage/plugin-techdocs-react": "workspace:^" @@ -47322,11 +45922,10 @@ __metadata: "@testing-library/user-event": "npm:^14.0.0" "@types/react": "npm:*" "@types/react-dom": "npm:*" - cross-env: "npm:^7.0.0" - history: "npm:^5.0.0" + cross-env: "npm:^10.0.0" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" + react-router-dom: "npm:^6.30.2" react-use: "npm:^17.2.4" languageName: unknown linkType: soft @@ -47361,14 +45960,20 @@ __metadata: languageName: node linkType: hard -"terser-webpack-plugin@npm:*, terser-webpack-plugin@npm:^5.1.3, terser-webpack-plugin@npm:^5.3.10, terser-webpack-plugin@npm:^5.3.11": - version: 5.3.14 - resolution: "terser-webpack-plugin@npm:5.3.14" +"terminal-columns@npm:^2.0.0": + version: 2.0.0 + resolution: "terminal-columns@npm:2.0.0" + checksum: 10/f958993886e09d7c0780f47833316532a0c7dd7db2fb43da9d34a88c821633408a6e7084af59f99c20b045776814748f14a8e33573886186be7a30174092964f + languageName: node + linkType: hard + +"terser-webpack-plugin@npm:^5.3.17": + version: 5.3.17 + resolution: "terser-webpack-plugin@npm:5.3.17" dependencies: "@jridgewell/trace-mapping": "npm:^0.3.25" jest-worker: "npm:^27.4.5" schema-utils: "npm:^4.3.0" - serialize-javascript: "npm:^6.0.2" terser: "npm:^5.31.1" peerDependencies: webpack: ^5.1.0 @@ -47379,7 +45984,7 @@ __metadata: optional: true uglify-js: optional: true - checksum: 10/5b7290f7edb179b83cefb8827c12371ddddc088cf251cf58a1c738d82628331ae6604273b61fe991d77411d4bb6b7178c3826aa47edf01b4ee21f973d6c8b8fb + checksum: 10/e51b00fe5e54beff82e8c2bea72b5104a2608e375d612ee56887e521210f840f2297fda2e0778c26bbd726b3c77ba2ec3234b2bdaa20b2f9174733745a457b33 languageName: node linkType: hard @@ -47408,33 +46013,26 @@ __metadata: languageName: node linkType: hard -"testcontainers@npm:^10.0.0": - version: 10.14.0 - resolution: "testcontainers@npm:10.14.0" +"testcontainers@npm:^11.9.0": + version: 11.13.0 + resolution: "testcontainers@npm:11.13.0" dependencies: "@balena/dockerignore": "npm:^1.0.2" - "@types/dockerode": "npm:^3.3.29" + "@types/dockerode": "npm:^4.0.1" archiver: "npm:^7.0.1" async-lock: "npm:^1.4.1" byline: "npm:^5.0.0" - debug: "npm:^4.3.5" - docker-compose: "npm:^0.24.8" - dockerode: "npm:^3.3.5" - get-port: "npm:^5.1.1" + debug: "npm:^4.4.3" + docker-compose: "npm:^1.3.2" + dockerode: "npm:^4.0.9" + get-port: "npm:^7.1.0" proper-lockfile: "npm:^4.1.2" - properties-reader: "npm:^2.3.0" + properties-reader: "npm:^3.0.1" ssh-remote-port-forward: "npm:^1.0.4" - tar-fs: "npm:^3.0.6" - tmp: "npm:^0.2.3" - undici: "npm:^5.28.4" - checksum: 10/09d983b4881a6460c9167c25a0367569feefcf89f69557511c07a0d0d89be5be2e3509c0b1fe0b8c6fae80d163064eefb71d691e1a0b33465b0e8ab5c7fb5313 - languageName: node - linkType: hard - -"testing-library__dom@npm:^7.29.4-beta.1": - version: 7.29.4-beta.1 - resolution: "testing-library__dom@npm:7.29.4-beta.1" - checksum: 10/d912418803b77df672c0894d053327a4b366cf7e5312dfb73bd2089d36272e2b38bccc1bd7284d9ed2f9f6530797778d0f72e94b8a44fe5d0327d11e7f757f2b + tar-fs: "npm:^3.1.2" + tmp: "npm:^0.2.5" + undici: "npm:^7.24.3" + checksum: 10/92ebd000e28146e134556cdf7e37bea9d90124a7ee217052f20400e5b4690e1161153e2d25f6c49a100b782570f83f816d66513a6a5092d4631724e3ce674daa languageName: node linkType: hard @@ -47447,6 +46045,13 @@ __metadata: languageName: node linkType: hard +"text-extensions@npm:^2.4.0": + version: 2.4.0 + resolution: "text-extensions@npm:2.4.0" + checksum: 10/9bdbc9959e004ccc86a6ec076d6c5bb6765978263e9d0d5febb640d7675c09919ea912f3fe9d50b68c3c7c43cc865610a7cb24954343abb31f74c205fbae4e45 + languageName: node + linkType: hard + "text-hex@npm:1.0.x": version: 1.0.0 resolution: "text-hex@npm:1.0.0" @@ -47461,7 +46066,7 @@ __metadata: languageName: node linkType: hard -"textextensions@npm:^5.12.0, textextensions@npm:^5.13.0, textextensions@npm:^5.16.0": +"textextensions@npm:^5.12.0, textextensions@npm:^5.13.0": version: 5.16.0 resolution: "textextensions@npm:5.16.0" checksum: 10/d41e9265e9d74d192d4fb26fc89a2f4dbe7a6d85cc5c14f99f1df68d07bce5346f8abe0ed680a91ef805b91e9972e5787c7365a03f3a5489e16ca350d28a3879 @@ -47511,7 +46116,7 @@ __metadata: languageName: node linkType: hard -"through@npm:2, through@npm:^2.3.6, through@npm:~2.3": +"through@npm:^2.3.6, through@npm:~2.3": version: 2.3.8 resolution: "through@npm:2.3.8" checksum: 10/5da78346f70139a7d213b65a0106f3c398d6bc5301f9248b5275f420abc2c4b1e77c2abc72d218dedc28c41efb2e7c312cb76a7730d04f9c2d37d247da3f4198 @@ -47532,6 +46137,13 @@ __metadata: languageName: node linkType: hard +"timeout-signal@npm:^2.0.0": + version: 2.0.0 + resolution: "timeout-signal@npm:2.0.0" + checksum: 10/5f022c225bac6542716478edf7bb5fc871d985cfa398b4f2eaa3d13fa6fda1225ce77cc65c5a92ae23b58882e2c14b83532a7c6f2f7710d06c9605b48ece4fe2 + languageName: node + linkType: hard + "timers-browserify@npm:^2.0.4": version: 2.0.11 resolution: "timers-browserify@npm:2.0.11" @@ -47582,7 +46194,14 @@ __metadata: languageName: node linkType: hard -"tinyglobby@npm:^0.2.15, tinyglobby@npm:^0.2.9": +"tinyexec@npm:^1.0.4": + version: 1.0.4 + resolution: "tinyexec@npm:1.0.4" + checksum: 10/ccebe4044eef6fa5050929df7862fda70b4fb700f15d94aef8ae6109b9d194dbc3a990125d99944fd25b90fe2115e1927f055b909a604c571a81b647ede5757a + languageName: node + linkType: hard + +"tinyglobby@npm:^0.2.11, tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.15, tinyglobby@npm:^0.2.9": version: 0.2.15 resolution: "tinyglobby@npm:0.2.15" dependencies: @@ -47613,10 +46232,10 @@ __metadata: languageName: node linkType: hard -"tldts-core@npm:^6.1.51": - version: 6.1.51 - resolution: "tldts-core@npm:6.1.51" - checksum: 10/861d5736d714abfd649c0545b9aae27058cd76f28f83077328fde6ba5ba9f7f107227d09406cfd38baf6215fc7840545ed0b17352f2f364182265887774394dc +"tldts-core@npm:^6.1.86": + version: 6.1.86 + resolution: "tldts-core@npm:6.1.86" + checksum: 10/cb5dff9cc15661ac773a2099e98c99a5cb3cebc35909c23cc4261ff7992032c7501995ae995de3574dbbf3431e59c47496534d52f5e96abcb231f0e72144c020 languageName: node linkType: hard @@ -47628,13 +46247,13 @@ __metadata: linkType: hard "tldts@npm:^6.1.32": - version: 6.1.51 - resolution: "tldts@npm:6.1.51" + version: 6.1.86 + resolution: "tldts@npm:6.1.86" dependencies: - tldts-core: "npm:^6.1.51" + tldts-core: "npm:^6.1.86" bin: tldts: bin/cli.js - checksum: 10/42e664469b1497af2fc6ac4cbf94f8a6507fcd23bf59521ace34223e36cb74ec7a003cf42834fcc4ba159c7448aa7e6f0f79f1f2fb8145b2e72ef44e87f035ec + checksum: 10/f7e66824e44479ccdda55ea556af14ce61c4d27708be403e3f90631defde49f82a580e1ca07187cc7e3b349e257a30c2808a22903f3a0548e136ebb609ccc109 languageName: node linkType: hard @@ -47649,10 +46268,23 @@ __metadata: languageName: node linkType: hard -"tmp@npm:^0.2.3": - version: 0.2.3 - resolution: "tmp@npm:0.2.3" - checksum: 10/7b13696787f159c9754793a83aa79a24f1522d47b87462ddb57c18ee93ff26c74cbb2b8d9138f571d2e0e765c728fb2739863a672b280528512c6d83d511c6fa +"tmcp@npm:^1.16.0": + version: 1.19.0 + resolution: "tmcp@npm:1.19.0" + dependencies: + "@standard-schema/spec": "npm:^1.0.0" + json-rpc-2.0: "npm:^1.7.1" + sqids: "npm:^0.3.0" + uri-template-matcher: "npm:^1.1.1" + valibot: "npm:^1.1.0" + checksum: 10/f5a3f0ae48b6fcccc7b1bd0261706c92c43941f4b9498048736b2fa847da6d1547ca425f9dc296af1bd37a0bbd6f0190eee3d7f89555325056ade8d84d97d68b + languageName: node + linkType: hard + +"tmp@npm:^0.2.5": + version: 0.2.5 + resolution: "tmp@npm:0.2.5" + checksum: 10/dd4b78b32385eab4899d3ae296007b34482b035b6d73e1201c4a9aede40860e90997a1452c65a2d21aee73d53e93cd167d741c3db4015d90e63b6d568a93d7ec languageName: node linkType: hard @@ -47697,7 +46329,7 @@ __metadata: languageName: node linkType: hard -"toidentifier@npm:1.0.1": +"toidentifier@npm:1.0.1, toidentifier@npm:~1.0.1": version: 1.0.1 resolution: "toidentifier@npm:1.0.1" checksum: 10/952c29e2a85d7123239b5cfdd889a0dde47ab0497f0913d70588f19c53f7e0b5327c95f4651e413c74b785147f9637b17410ac8c846d5d4a20a5a33eb6dc3a45 @@ -47714,13 +46346,14 @@ __metadata: languageName: node linkType: hard -"token-types@npm:^6.0.0": - version: 6.0.0 - resolution: "token-types@npm:6.0.0" +"token-types@npm:^6.1.1": + version: 6.1.2 + resolution: "token-types@npm:6.1.2" dependencies: + "@borewit/text-codec": "npm:^0.2.1" "@tokenizer/token": "npm:^0.3.0" ieee754: "npm:^1.2.1" - checksum: 10/b541b605d602e8e6495745badb35f90ee8f997e43dc29bc51aee7e9a0bc3c6bc7372a305bd45f3e80d75223c2b6a5c7e65cb5159d8c4e49fa25cdbaae531fad4 + checksum: 10/0c7811a2da5a0ca474c795d883d871a184d1d54f67058d66084110f0b246fff66151885dbcb91d66533e776478bf57f3b4fac69ce03b805a0e1060def87947de languageName: node linkType: hard @@ -47742,24 +46375,12 @@ __metadata: languageName: node linkType: hard -"tough-cookie@npm:^4.0.0": - version: 4.1.4 - resolution: "tough-cookie@npm:4.1.4" - dependencies: - psl: "npm:^1.1.33" - punycode: "npm:^2.1.1" - universalify: "npm:^0.2.0" - url-parse: "npm:^1.5.3" - checksum: 10/75663f4e2cd085f16af0b217e4218772adf0617fb3227171102618a54ce0187a164e505d61f773ed7d65988f8ff8a8f935d381f87da981752c1171b076b4afac - languageName: node - linkType: hard - "tough-cookie@npm:^5.0.0": - version: 5.0.0 - resolution: "tough-cookie@npm:5.0.0" + version: 5.1.2 + resolution: "tough-cookie@npm:5.1.2" dependencies: tldts: "npm:^6.1.32" - checksum: 10/a98d3846ed386e399e8b470c1eb08a6a296944246eabc55c9fe79d629bd2cdaa62f5a6572f271fe0060987906bd20468d72a219a3b4cbe51086bea48d2d677b6 + checksum: 10/de430e6e6d34b794137e05b8ac2aa6b74ebbe6cdceb4126f168cf1e76101162a4b2e0e7587c3b70e728bd8654fc39958b2035be7619ee6f08e7257610ba4cd04 languageName: node linkType: hard @@ -47772,15 +46393,6 @@ __metadata: languageName: node linkType: hard -"tr46@npm:^3.0.0": - version: 3.0.0 - resolution: "tr46@npm:3.0.0" - dependencies: - punycode: "npm:^2.1.1" - checksum: 10/b09a15886cbfaee419a3469081223489051ce9dca3374dd9500d2378adedbee84a3c73f83bfdd6bb13d53657753fc0d4e20a46bfcd3f1b9057ef528426ad7ce4 - languageName: node - linkType: hard - "tr46@npm:^5.1.0": version: 5.1.1 resolution: "tr46@npm:5.1.1" @@ -47790,6 +46402,15 @@ __metadata: languageName: node linkType: hard +"tr46@npm:^6.0.0": + version: 6.0.0 + resolution: "tr46@npm:6.0.0" + dependencies: + punycode: "npm:^2.3.1" + checksum: 10/e6d402eb2b780a40042f327f77b4ae316da1d2b18a29c16e48c239f5267c6005bbf780f854179cfae62b02dfaa70b0e9aad8f0078ccc4225f5b3b3b131928e8f + languageName: node + linkType: hard + "tr46@npm:~0.0.3": version: 0.0.3 resolution: "tr46@npm:0.0.3" @@ -47797,12 +46418,12 @@ __metadata: languageName: node linkType: hard -"tree-dump@npm:^1.0.1": - version: 1.0.3 - resolution: "tree-dump@npm:1.0.3" +"tree-dump@npm:^1.0.3, tree-dump@npm:^1.1.0": + version: 1.1.0 + resolution: "tree-dump@npm:1.1.0" peerDependencies: tslib: 2 - checksum: 10/cf382e61cfb5e3ff8f03425b5bc1923e8f0e385b3a02f43d9d0a32d09da9984477e0f2a7698628662263d1d3f1af17e33486c77ff454978f0f9f07fb5d1fe9a2 + checksum: 10/2c20118d2671996aa6f1ba1310cef1404fb525bde5d989ab542013f62b23a3633c0f0b32cbd516ee6205051ec21912b2470dabca006d19c9eba0740b567e2b60 languageName: node linkType: hard @@ -47904,33 +46525,30 @@ __metadata: languageName: node linkType: hard -"ts-api-utils@npm:^2.0.1, ts-api-utils@npm:^2.1.0": - version: 2.1.0 - resolution: "ts-api-utils@npm:2.1.0" +"ts-api-utils@npm:^2.4.0": + version: 2.4.0 + resolution: "ts-api-utils@npm:2.4.0" peerDependencies: typescript: ">=4.8.4" - checksum: 10/02e55b49d9617c6eebf8aadfa08d3ca03ca0cd2f0586ad34117fdfc7aa3cd25d95051843fde9df86665ad907f99baed179e7a117b11021417f379e4d2614eacd + checksum: 10/d6b2b3b6caad8d2f4ddc0c3785d22bb1a6041773335a1c71d73a5d67d11d993763fe8e4faefc4a4d03bb42b26c6126bbcf2e34826baed1def5369d0ebad358fa languageName: node linkType: hard "ts-checker-rspack-plugin@npm:^1.1.5": - version: 1.1.5 - resolution: "ts-checker-rspack-plugin@npm:1.1.5" + version: 1.3.0 + resolution: "ts-checker-rspack-plugin@npm:1.3.0" dependencies: - "@babel/code-frame": "npm:^7.27.1" - "@rspack/lite-tapable": "npm:^1.0.1" + "@rspack/lite-tapable": "npm:^1.1.0" chokidar: "npm:^3.6.0" - is-glob: "npm:^4.0.3" - memfs: "npm:^4.28.0" - minimatch: "npm:^9.0.5" + memfs: "npm:^4.56.10" picocolors: "npm:^1.1.1" peerDependencies: - "@rspack/core": ^1.0.0 + "@rspack/core": ^1.0.0 || ^2.0.0-0 typescript: ">=3.8.0" peerDependenciesMeta: "@rspack/core": optional: true - checksum: 10/d43f87a5860f3b8f528bf8f43c78c2d958a18fa484d791a204047fc5672134fd689665021ee25b07f9f9d8c807cd291978f58b789bf531b4704a9a4e3ad84735 + checksum: 10/dfd19f2c210b8c6ee2255a6a6c7db30bcff08cb4664982f3c3edfc21ee78017b25ad1ce074e1a5ee9f7551291d4302debc53856d24dfcef1ca26209a9d997c18 languageName: node linkType: hard @@ -48075,35 +46693,33 @@ __metadata: languageName: node linkType: hard -"tslib@npm:2, tslib@npm:2.8.1, tslib@npm:^2, tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.2.0, tslib@npm:^2.3.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0, tslib@npm:^2.4.1, tslib@npm:^2.5.0, tslib@npm:^2.6.0, tslib@npm:^2.6.2, tslib@npm:^2.6.3, tslib@npm:^2.8.0, tslib@npm:^2.8.1": +"tslib@npm:2.8.1, tslib@npm:^2, tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.2.0, tslib@npm:^2.3.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0, tslib@npm:^2.4.1, tslib@npm:^2.5.0, tslib@npm:^2.6.0, tslib@npm:^2.6.2, tslib@npm:^2.6.3, tslib@npm:^2.8.0, tslib@npm:^2.8.1": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 10/3e2e043d5c2316461cb54e5c7fe02c30ef6dccb3384717ca22ae5c6b5bc95232a6241df19c622d9c73b809bea33b187f6dbc73030963e29950c2141bc32a79f7 languageName: node linkType: hard -"tslib@npm:^1.11.1, tslib@npm:^1.13.0, tslib@npm:^1.14.1, tslib@npm:^1.8.1, tslib@npm:^1.9.0, tslib@npm:^1.9.3": +"tslib@npm:^1.14.1, tslib@npm:^1.9.0, tslib@npm:^1.9.3": version: 1.14.1 resolution: "tslib@npm:1.14.1" checksum: 10/7dbf34e6f55c6492637adb81b555af5e3b4f9cc6b998fb440dac82d3b42bdc91560a35a5fb75e20e24a076c651438234da6743d139e4feabf0783f3cdfe1dddb languageName: node linkType: hard -"tsscmp@npm:1.0.6, tsscmp@npm:^1.0.6": +"tsscmp@npm:^1.0.6": version: 1.0.6 resolution: "tsscmp@npm:1.0.6" checksum: 10/850405080ea3ecb158e9e01bc4e87c9edb94a829d8ad8747f30ba103fcc41a287d7949ab84d7b27c36294036a2c9878f050db15b73a1a1961abfb7688b82ac53 languageName: node linkType: hard -"tsutils@npm:^3.21.0": - version: 3.21.0 - resolution: "tsutils@npm:3.21.0" +"tsyringe@npm:^4.10.0": + version: 4.10.0 + resolution: "tsyringe@npm:4.10.0" dependencies: - tslib: "npm:^1.8.1" - peerDependencies: - typescript: ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - checksum: 10/ea036bec1dd024e309939ffd49fda7a351c0e87a1b8eb049570dd119d447250e2c56e0e6c00554e8205760e7417793fdebff752a46e573fbe07d4f375502a5b2 + tslib: "npm:^1.9.3" + checksum: 10/b42660dc112cee2db02b3d69f2ef6a6a9d185afd96b18d8f88e47c1e62be94b69a9f5a58fcfdb2a3fbb7c6c175b8162ea00f7db6499bf333ce945e570e31615c languageName: node linkType: hard @@ -48164,15 +46780,6 @@ __metadata: languageName: node linkType: hard -"type-check@npm:~0.3.2": - version: 0.3.2 - resolution: "type-check@npm:0.3.2" - dependencies: - prelude-ls: "npm:~1.1.2" - checksum: 10/11dec0b50d7c3fd2e630b4b074ba36918ed2b1efbc87dfbd40ba9429d49c58d12dad5c415ece69fcf358fa083f33466fc370f23ab91aa63295c45d38b3a60dda - languageName: node - linkType: hard - "type-detect@npm:4.0.8, type-detect@npm:^4.0.8": version: 4.0.8 resolution: "type-detect@npm:4.0.8" @@ -48222,14 +46829,21 @@ __metadata: languageName: node linkType: hard -"type-fest@npm:^4.26.1, type-fest@npm:^4.3.1, type-fest@npm:^4.41.0": +"type-fest@npm:^4.26.1, type-fest@npm:^4.3.1": version: 4.41.0 resolution: "type-fest@npm:4.41.0" checksum: 10/617ace794ac0893c2986912d28b3065ad1afb484cad59297835a0807dc63286c39e8675d65f7de08fafa339afcb8fe06a36e9a188b9857756ae1e92ee8bda212 languageName: node linkType: hard -"type-is@npm:^1.6.16, type-is@npm:^1.6.18, type-is@npm:~1.6.18": +"type-flag@npm:^4.1.0": + version: 4.1.0 + resolution: "type-flag@npm:4.1.0" + checksum: 10/d3af106dab651751426f778095b3963791d15482b2154f92f4f98e5d6b72d593ae9873120d2de24f34191ab42c0d71c0259743162e543aee8d55b528ed1c6e78 + languageName: node + linkType: hard + +"type-is@npm:^1.6.18, type-is@npm:~1.6.18": version: 1.6.18 resolution: "type-is@npm:1.6.18" dependencies: @@ -48239,7 +46853,7 @@ __metadata: languageName: node linkType: hard -"type-is@npm:^2.0.0, type-is@npm:^2.0.1": +"type-is@npm:^2.0.1": version: 2.0.1 resolution: "type-is@npm:2.0.1" dependencies: @@ -48354,19 +46968,19 @@ __metadata: linkType: hard "typedoc@npm:^0.28.0": - version: 0.28.14 - resolution: "typedoc@npm:0.28.14" + version: 0.28.18 + resolution: "typedoc@npm:0.28.18" dependencies: - "@gerrit0/mini-shiki": "npm:^3.12.0" + "@gerrit0/mini-shiki": "npm:^3.23.0" lunr: "npm:^2.3.9" - markdown-it: "npm:^14.1.0" - minimatch: "npm:^9.0.5" - yaml: "npm:^2.8.1" + markdown-it: "npm:^14.1.1" + minimatch: "npm:^10.2.4" + yaml: "npm:^2.8.2" peerDependencies: - typescript: 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x + typescript: 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x || 6.0.x bin: typedoc: bin/typedoc - checksum: 10/6b2bb87c85de96475f60ba2a92e0cb7169fef138cce4d91c949267ca9268c4fc97c2db9702f306ad5e594aa5244cc9aa563791ec31dd1b1d5288cf1ab08aa7a0 + checksum: 10/d7342aeeda34a69e853463e5300a7a06ec42757e2942f3de4a2c882f08f1d4e4d03662a4e8e8259e14c1a0b948b57a6e833d6e391ba41987c4cd400d4d76aeab languageName: node linkType: hard @@ -48379,9 +46993,9 @@ __metadata: languageName: node linkType: hard -"typescript-json-schema@npm:^0.65.0": - version: 0.65.1 - resolution: "typescript-json-schema@npm:0.65.1" +"typescript-json-schema@npm:^0.67.0": + version: 0.67.1 + resolution: "typescript-json-schema@npm:0.67.1" dependencies: "@types/json-schema": "npm:^7.0.9" "@types/node": "npm:^18.11.9" @@ -48390,20 +47004,21 @@ __metadata: safe-stable-stringify: "npm:^2.2.0" ts-node: "npm:^10.9.1" typescript: "npm:~5.5.0" + vm2: "npm:^3.10.0" yargs: "npm:^17.1.1" bin: typescript-json-schema: bin/typescript-json-schema - checksum: 10/50a1935378639d5d47e452702766a3fdab22e1d06192f26f81b79e0da504e71af987ff21cb13909479a202aad8d1216a654f16ebda2ee2056b5f859584b4c7d2 + checksum: 10/b7c89c380ad3d8bd281fc5714ae140c8e8f9c491b8666ec3cb28bf16fa8f72c8f7b52c89a7ecef7dc52a233c4bc57d3c0f2c3c3f9d4b096c3432f45209696f0a languageName: node linkType: hard -"typescript@npm:5.4.2": - version: 5.4.2 - resolution: "typescript@npm:5.4.2" +"typescript@npm:5.8.2": + version: 5.8.2 + resolution: "typescript@npm:5.8.2" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/f8cfdc630ab1672f004e9561eb2916935b2d267792d07ce93e97fc601c7a65191af32033d5e9c0169b7dc37da7db9bf320f7432bc84527cb7697effaa4e4559d + checksum: 10/dbc2168a55d56771f4d581997be52bab5cbc09734fec976cfbaabd787e61fb4c6cf9125fd48c6f98054ce549c77ecedefc7f64252a830dd8e9c3381f61fbeb78 languageName: node linkType: hard @@ -48437,13 +47052,13 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A5.4.2#optional!builtin": - version: 5.4.2 - resolution: "typescript@patch:typescript@npm%3A5.4.2#optional!builtin::version=5.4.2&hash=5adc0c" +"typescript@patch:typescript@npm%3A5.8.2#optional!builtin": + version: 5.8.2 + resolution: "typescript@patch:typescript@npm%3A5.8.2#optional!builtin::version=5.8.2&hash=5786d5" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/f5f9a4133c2670761f0166eae5b3bafbc4a3fc24f0f42a93c9c893d9e9d6e66ea066969c5e7483fa66b4ae0e99125592553f3b92fd3599484de8be13b0615176 + checksum: 10/97920a082ffc57583b1cb6bc4faa502acc156358e03f54c7fc7fdf0b61c439a717f4c9070c449ee9ee683d4cfc3bb203127c2b9794b2950f66d9d307a4ff262c languageName: node linkType: hard @@ -48484,13 +47099,6 @@ __metadata: languageName: node linkType: hard -"uc.micro@npm:^1.0.1, uc.micro@npm:^1.0.5": - version: 1.0.6 - resolution: "uc.micro@npm:1.0.6" - checksum: 10/6898bb556319a38e9cf175e3628689347bd26fec15fc6b29fa38e0045af63075ff3fea4cf1fdba9db46c9f0cbf07f2348cd8844889dd31ebd288c29fe0d27e7a - languageName: node - linkType: hard - "uc.micro@npm:^2.0.0, uc.micro@npm:^2.1.0": version: 2.1.0 resolution: "uc.micro@npm:2.1.0" @@ -48546,6 +47154,13 @@ __metadata: languageName: node linkType: hard +"unbash@npm:^2.2.0": + version: 2.2.0 + resolution: "unbash@npm:2.2.0" + checksum: 10/dc169c6cacff0364c3d46dbe3f08f4c812cee63359f94ebace483b1a382585d5a1bf4007c12ed3c73671a0256d0026efc23e4732583ae76b42d8570fc4680667 + languageName: node + linkType: hard + "unbox-primitive@npm:^1.1.0": version: 1.1.0 resolution: "unbox-primitive@npm:1.1.0" @@ -48565,17 +47180,10 @@ __metadata: languageName: node linkType: hard -"underscore@npm:1.12.1": - version: 1.12.1 - resolution: "underscore@npm:1.12.1" - checksum: 10/c3bb50c61ef7dea4b9add6d48f98316f65de1075801dc8cd01ecd82122b99eb17ce0f068f00f6cc10245d5cf75c443ce96f8efc5458d7773b44af9bd7c2ccc56 - languageName: node - linkType: hard - "underscore@npm:^1.12.1, underscore@npm:^1.13.3": - version: 1.13.7 - resolution: "underscore@npm:1.13.7" - checksum: 10/1ce3368dbe73d1e99678fa5d341a9682bd27316032ad2de7883901918f0f5d50e80320ccc543f53c1862ab057a818abc560462b5f83578afe2dd8dd7f779766c + version: 1.13.8 + resolution: "underscore@npm:1.13.8" + checksum: 10/b50ac5806d059cc180b1bd9adea6f7ed500021f4dc782dfc75d66a90337f6f0506623c1b37863f4a9bf64ffbeb5769b638a54b7f2f5966816189955815953139 languageName: node linkType: hard @@ -48586,10 +47194,10 @@ __metadata: languageName: node linkType: hard -"undici-types@npm:~6.19.2": - version: 6.19.8 - resolution: "undici-types@npm:6.19.8" - checksum: 10/cf0b48ed4fc99baf56584afa91aaffa5010c268b8842f62e02f752df209e3dea138b372a60a963b3b2576ed932f32329ce7ddb9cb5f27a6c83040d8cd74b7a70 +"undici-types@npm:~6.21.0": + version: 6.21.0 + resolution: "undici-types@npm:6.21.0" + checksum: 10/ec8f41aa4359d50f9b59fa61fe3efce3477cc681908c8f84354d8567bb3701fafdddf36ef6bff307024d3feb42c837cf6f670314ba37fc8145e219560e473d14 languageName: node linkType: hard @@ -48600,7 +47208,21 @@ __metadata: languageName: node linkType: hard -"undici@npm:^5.28.2, undici@npm:^5.28.4": +"undici-types@npm:~7.18.0": + version: 7.18.2 + resolution: "undici-types@npm:7.18.2" + checksum: 10/e61a5918f624d68420c3ca9d301e9f15b61cba6e97be39fe2ce266dd6151e4afe424d679372638826cb506be33952774e0424141200111a9857e464216c009af + languageName: node + linkType: hard + +"undici@npm:7.24.7, undici@npm:^7.24.3, undici@npm:^7.24.5": + version: 7.24.7 + resolution: "undici@npm:7.24.7" + checksum: 10/bce7b75fe2656bbd1f9c9d5d1b6b89670773281343be25d0b1f4d808dcce97d81509987d1f3183d37a63d3a57f5f217ed8ed15ee3e103384c54e190f4e360c48 + languageName: node + linkType: hard + +"undici@npm:^5.28.2": version: 5.29.0 resolution: "undici@npm:5.29.0" dependencies: @@ -48609,13 +47231,6 @@ __metadata: languageName: node linkType: hard -"undici@npm:^7.2.3": - version: 7.9.0 - resolution: "undici@npm:7.9.0" - checksum: 10/fa92d3d9106612be566e79942174e00426c2e1f9a688fb86c8d1809c84a032c02fc57bd601d2051a45d94bbd312c50221644b58c8186c4f0d64f8ecc7f18b806 - languageName: node - linkType: hard - "uni-global@npm:^1.0.0": version: 1.0.0 resolution: "uni-global@npm:1.0.0" @@ -48625,10 +47240,10 @@ __metadata: languageName: node linkType: hard -"unicorn-magic@npm:^0.1.0": - version: 0.1.0 - resolution: "unicorn-magic@npm:0.1.0" - checksum: 10/9b4d0e9809807823dc91d0920a4a4c0cff2de3ebc54ee87ac1ee9bc75eafd609b09d1f14495e0173aef26e01118706196b6ab06a75fe0841028b3983a8af313f +"unicode-emoji-modifier-base@npm:^1.0.0": + version: 1.0.0 + resolution: "unicode-emoji-modifier-base@npm:1.0.0" + checksum: 10/6e1521d35fa69493207eb8b41f8edb95985d8b3faf07c01d820a1830b5e8403e20002563e2f84683e8e962a49beccae789f0879356bf92a4ec7a4dd8e2d16fdb languageName: node linkType: hard @@ -48656,15 +47271,6 @@ __metadata: languageName: node linkType: hard -"unique-filename@npm:^3.0.0": - version: 3.0.0 - resolution: "unique-filename@npm:3.0.0" - dependencies: - unique-slug: "npm:^4.0.0" - checksum: 10/8e2f59b356cb2e54aab14ff98a51ac6c45781d15ceaab6d4f1c2228b780193dc70fae4463ce9e1df4479cb9d3304d7c2043a3fb905bdeca71cc7e8ce27e063df - languageName: node - linkType: hard - "unique-filename@npm:^4.0.0": version: 4.0.0 resolution: "unique-filename@npm:4.0.0" @@ -48674,6 +47280,15 @@ __metadata: languageName: node linkType: hard +"unique-filename@npm:^5.0.0": + version: 5.0.0 + resolution: "unique-filename@npm:5.0.0" + dependencies: + unique-slug: "npm:^6.0.0" + checksum: 10/a5f67085caef74bdd2a6869a200ed5d68d171f5cc38435a836b5fd12cce4e4eb55e6a190298035c325053a5687ed7a3c96f0a91e82215fd14729769d9ac57d9b + languageName: node + linkType: hard + "unique-slug@npm:^2.0.0": version: 2.0.2 resolution: "unique-slug@npm:2.0.2" @@ -48683,15 +47298,6 @@ __metadata: languageName: node linkType: hard -"unique-slug@npm:^4.0.0": - version: 4.0.0 - resolution: "unique-slug@npm:4.0.0" - dependencies: - imurmurhash: "npm:^0.1.4" - checksum: 10/40912a8963fc02fb8b600cf50197df4a275c602c60de4cac4f75879d3c48558cfac48de08a25cc10df8112161f7180b3bbb4d662aadb711568602f9eddee54f0 - languageName: node - linkType: hard - "unique-slug@npm:^5.0.0": version: 5.0.0 resolution: "unique-slug@npm:5.0.0" @@ -48701,6 +47307,15 @@ __metadata: languageName: node linkType: hard +"unique-slug@npm:^6.0.0": + version: 6.0.0 + resolution: "unique-slug@npm:6.0.0" + dependencies: + imurmurhash: "npm:^0.1.4" + checksum: 10/b78ed9d5b01ff465f80975f17387750ed3639909ac487fa82c4ae4326759f6de87c2131c0c39eca4c68cf06c537a8d104fba1dfc8a30308f99bc505345e1eba3 + languageName: node + linkType: hard + "unique-string@npm:^2.0.0": version: 2.0.0 resolution: "unique-string@npm:2.0.0" @@ -48815,13 +47430,6 @@ __metadata: languageName: node linkType: hard -"universalify@npm:^0.2.0": - version: 0.2.0 - resolution: "universalify@npm:0.2.0" - checksum: 10/e86134cb12919d177c2353196a4cc09981524ee87abf621f7bc8d249dbbbebaec5e7d1314b96061497981350df786e4c5128dbf442eba104d6e765bc260678b5 - languageName: node - linkType: hard - "universalify@npm:^2.0.0": version: 2.0.0 resolution: "universalify@npm:2.0.0" @@ -48838,20 +47446,32 @@ __metadata: languageName: node linkType: hard -"unpipe@npm:1.0.0, unpipe@npm:~1.0.0": +"unpipe@npm:~1.0.0": version: 1.0.0 resolution: "unpipe@npm:1.0.0" checksum: 10/4fa18d8d8d977c55cb09715385c203197105e10a6d220087ec819f50cb68870f02942244f1017565484237f1f8c5d3cd413631b1ae104d3096f24fdfde1b4aa2 languageName: node linkType: hard -"unplugin@npm:^1.3.1": - version: 1.16.1 - resolution: "unplugin@npm:1.16.1" +"unplugin-utils@npm:^0.2.4": + version: 0.2.5 + resolution: "unplugin-utils@npm:0.2.5" dependencies: - acorn: "npm:^8.14.0" + pathe: "npm:^2.0.3" + picomatch: "npm:^4.0.3" + checksum: 10/f9ff443089de3159ccab001d075fa4245a741696182dbe6b9708aa01945ded163bd846a3f4099132a2195de3fcbefe404a05380e4697faa4c5917c89f97a2361 + languageName: node + linkType: hard + +"unplugin@npm:^2.3.5": + version: 2.3.11 + resolution: "unplugin@npm:2.3.11" + dependencies: + "@jridgewell/remapping": "npm:^2.3.5" + acorn: "npm:^8.15.0" + picomatch: "npm:^4.0.3" webpack-virtual-modules: "npm:^0.6.2" - checksum: 10/4b46d7d2a63d334a45111ba57a266b3f8993ef12a72b77d7b31ffc455e8a9bef9c0e37ea463eb409dbf7ccec0b9868aeb845dd42c690d9288e4b8ac2d90fbefd + checksum: 10/7b4adbfaac8894e8491c452c0b67c612b57e103761e842d9013ebea89a4ae92a78df4ec0aa30e5e3eaaefd47dd287973d5a662271624b7346a15d9236d257f9d languageName: node linkType: hard @@ -48862,6 +47482,73 @@ __metadata: languageName: node linkType: hard +"unrs-resolver@npm:^1.7.11": + version: 1.11.1 + resolution: "unrs-resolver@npm:1.11.1" + dependencies: + "@unrs/resolver-binding-android-arm-eabi": "npm:1.11.1" + "@unrs/resolver-binding-android-arm64": "npm:1.11.1" + "@unrs/resolver-binding-darwin-arm64": "npm:1.11.1" + "@unrs/resolver-binding-darwin-x64": "npm:1.11.1" + "@unrs/resolver-binding-freebsd-x64": "npm:1.11.1" + "@unrs/resolver-binding-linux-arm-gnueabihf": "npm:1.11.1" + "@unrs/resolver-binding-linux-arm-musleabihf": "npm:1.11.1" + "@unrs/resolver-binding-linux-arm64-gnu": "npm:1.11.1" + "@unrs/resolver-binding-linux-arm64-musl": "npm:1.11.1" + "@unrs/resolver-binding-linux-ppc64-gnu": "npm:1.11.1" + "@unrs/resolver-binding-linux-riscv64-gnu": "npm:1.11.1" + "@unrs/resolver-binding-linux-riscv64-musl": "npm:1.11.1" + "@unrs/resolver-binding-linux-s390x-gnu": "npm:1.11.1" + "@unrs/resolver-binding-linux-x64-gnu": "npm:1.11.1" + "@unrs/resolver-binding-linux-x64-musl": "npm:1.11.1" + "@unrs/resolver-binding-wasm32-wasi": "npm:1.11.1" + "@unrs/resolver-binding-win32-arm64-msvc": "npm:1.11.1" + "@unrs/resolver-binding-win32-ia32-msvc": "npm:1.11.1" + "@unrs/resolver-binding-win32-x64-msvc": "npm:1.11.1" + napi-postinstall: "npm:^0.3.0" + dependenciesMeta: + "@unrs/resolver-binding-android-arm-eabi": + optional: true + "@unrs/resolver-binding-android-arm64": + optional: true + "@unrs/resolver-binding-darwin-arm64": + optional: true + "@unrs/resolver-binding-darwin-x64": + optional: true + "@unrs/resolver-binding-freebsd-x64": + optional: true + "@unrs/resolver-binding-linux-arm-gnueabihf": + optional: true + "@unrs/resolver-binding-linux-arm-musleabihf": + optional: true + "@unrs/resolver-binding-linux-arm64-gnu": + optional: true + "@unrs/resolver-binding-linux-arm64-musl": + optional: true + "@unrs/resolver-binding-linux-ppc64-gnu": + optional: true + "@unrs/resolver-binding-linux-riscv64-gnu": + optional: true + "@unrs/resolver-binding-linux-riscv64-musl": + optional: true + "@unrs/resolver-binding-linux-s390x-gnu": + optional: true + "@unrs/resolver-binding-linux-x64-gnu": + optional: true + "@unrs/resolver-binding-linux-x64-musl": + optional: true + "@unrs/resolver-binding-wasm32-wasi": + optional: true + "@unrs/resolver-binding-win32-arm64-msvc": + optional: true + "@unrs/resolver-binding-win32-ia32-msvc": + optional: true + "@unrs/resolver-binding-win32-x64-msvc": + optional: true + checksum: 10/4de653508cbaae47883a896bd5cdfef0e5e87b428d62620d16fd35cd534beaebf08ebf0cf2f8b4922aa947b2fe745180facf6cc3f39ba364f7ce0f974cb06a70 + languageName: node + linkType: hard + "untildify@npm:^4.0.0": version: 4.0.0 resolution: "untildify@npm:4.0.0" @@ -48876,17 +47563,17 @@ __metadata: languageName: node linkType: hard -"update-browserslist-db@npm:^1.1.1": - version: 1.1.1 - resolution: "update-browserslist-db@npm:1.1.1" +"update-browserslist-db@npm:^1.2.0": + version: 1.2.3 + resolution: "update-browserslist-db@npm:1.2.3" dependencies: escalade: "npm:^3.2.0" - picocolors: "npm:^1.1.0" + picocolors: "npm:^1.1.1" peerDependencies: browserslist: ">= 4.21.0" bin: update-browserslist-db: cli.js - checksum: 10/7678dd8609750588d01aa7460e8eddf2ff9d16c2a52fb1811190e0d056390f1fdffd94db3cf8fb209cf634ab4fa9407886338711c71cc6ccade5eeb22b093734 + checksum: 10/059f774300efb4b084a49293143c511f3ae946d40397b5c30914e900cd5691a12b8e61b41dd54ed73d3b56c8204165a0333107dd784ccf8f8c81790bcc423175 languageName: node linkType: hard @@ -48912,6 +47599,15 @@ __metadata: languageName: node linkType: hard +"upper-case@npm:^2.0.2": + version: 2.0.2 + resolution: "upper-case@npm:2.0.2" + dependencies: + tslib: "npm:^2.0.3" + checksum: 10/508723a2b03ab90cf1d6b7e0397513980fab821cbe79c87341d0e96cedefadf0d85f9d71eac24ab23f526a041d585a575cfca120a9f920e44eb4f8a7cf89121c + languageName: node + linkType: hard + "uri-js@npm:^4.2.2, uri-js@npm:^4.4.1": version: 4.4.1 resolution: "uri-js@npm:4.4.1" @@ -48921,6 +47617,13 @@ __metadata: languageName: node linkType: hard +"uri-template-matcher@npm:^1.1.1": + version: 1.1.2 + resolution: "uri-template-matcher@npm:1.1.2" + checksum: 10/fe9214a1cd3a6769ea9d1e16fdb1edcc74f3aa24cbdde4da02fbf6dbc358645cd08b818ec94eff7a15310e08e9dbfb48f73659bf121a9f6517ca9182defcb0ba + languageName: node + linkType: hard + "uri-template@npm:^2.0.0": version: 2.0.0 resolution: "uri-template@npm:2.0.0" @@ -48953,7 +47656,7 @@ __metadata: languageName: node linkType: hard -"url-parse@npm:^1.5.10, url-parse@npm:^1.5.3": +"url-parse@npm:^1.5.10": version: 1.5.10 resolution: "url-parse@npm:1.5.10" dependencies: @@ -48999,6 +47702,13 @@ __metadata: languageName: node linkType: hard +"urlpattern-polyfill@npm:^10.0.0": + version: 10.1.0 + resolution: "urlpattern-polyfill@npm:10.1.0" + checksum: 10/a3a5cb577f40e9d14a2bc0e23e540d088ec8c18e52932337c2499a8118f32aa8fa32578f0cddaa189de671964b0f33ba7e008d0784b99804a09a9ff924f87f5a + languageName: node + linkType: hard + "urlpattern-polyfill@npm:^8.0.0": version: 8.0.2 resolution: "urlpattern-polyfill@npm:8.0.2" @@ -49006,25 +47716,18 @@ __metadata: languageName: node linkType: hard -"urlpattern-polyfill@npm:^9.0.0": - version: 9.0.0 - resolution: "urlpattern-polyfill@npm:9.0.0" - checksum: 10/63d59e08d58189d340e3acb0fb69c11d8f06da5e38c091cdac66cac07e4ca81378ad19cd1a923d5593a899603a0e607fe3ef793ef368fefbc1b2b840b24839b8 - languageName: node - linkType: hard - -"use-callback-ref@npm:^1.3.0": - version: 1.3.0 - resolution: "use-callback-ref@npm:1.3.0" +"use-callback-ref@npm:^1.3.3": + version: 1.3.3 + resolution: "use-callback-ref@npm:1.3.3" dependencies: tslib: "npm:^2.0.0" peerDependencies: - "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10/f9f1b217db60419b033228ba17cee5c521123e7c7f35577258a1abdce6d9623e5880f0bed3a0504eff35fdf6c761a2b2e020337a34218fb86229b8641772654a + checksum: 10/adf06a7b6a27d3651c325ac9b66d2b82ccacaed7450b85b211d123e91d9a23cb5a587fcc6db5b4fd07ac7233e5abf024d30cf02ddc2ec46bca712151c0836151 languageName: node linkType: hard @@ -49037,13 +47740,13 @@ __metadata: languageName: node linkType: hard -"use-immer@npm:^0.10.0": - version: 0.10.0 - resolution: "use-immer@npm:0.10.0" +"use-immer@npm:^0.11.0": + version: 0.11.0 + resolution: "use-immer@npm:0.11.0" peerDependencies: immer: ">=8.0.0" - react: ^16.8.0 || ^17.0.1 || ^18.0.0 - checksum: 10/372b0eea0a05e9435f5dc57a877ec619ea9a479fc82423f502b4a498d5697f8b06b85d089058db5a056bc8bbdb8e6f9ea8c9850b51a6b05d5c63ab0c8eeb2b7e + react: ^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 + checksum: 10/09ddbddec5bd5a939dc462de4c9d1ce47989bc60daa712c5235664650b132e246b8e61b6bd3dc8179649445b7cf389065373c75f23829a676dbda6b5d2428928 languageName: node linkType: hard @@ -49094,28 +47797,28 @@ __metadata: languageName: node linkType: hard -"use-sidecar@npm:^1.1.2": - version: 1.1.2 - resolution: "use-sidecar@npm:1.1.2" +"use-sidecar@npm:^1.1.3": + version: 1.1.3 + resolution: "use-sidecar@npm:1.1.3" dependencies: detect-node-es: "npm:^1.1.0" tslib: "npm:^2.0.0" peerDependencies: - "@types/react": ^16.9.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10/ec99e31aefeb880f6dc4d02cb19a01d123364954f857811470ece32872f70d6c3eadbe4d073770706a9b7db6136f2a9fbf1bb803e07fbb21e936a47479281690 + checksum: 10/2fec05eb851cdfc4a4657b1dfb434e686f346c3265ffc9db8a974bb58f8128bd4a708a3cc00e8f51655fccf81822ed4419ebed42f41610589e3aab0cf2492edb languageName: node linkType: hard -"use-sync-external-store@npm:^1.2.0, use-sync-external-store@npm:^1.4.0": - version: 1.4.0 - resolution: "use-sync-external-store@npm:1.4.0" +"use-sync-external-store@npm:^1.4.0, use-sync-external-store@npm:^1.5.0, use-sync-external-store@npm:^1.6.0": + version: 1.6.0 + resolution: "use-sync-external-store@npm:1.6.0" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - checksum: 10/08bf581a8a2effaefc355e9d18ed025d436230f4cc973db2f593166df357cf63e47b9097b6e5089b594758bde322e1737754ad64905e030d70f8ff7ee671fd01 + checksum: 10/b40ad2847ba220695bff2d4ba4f4d60391c0fb4fb012faa7a4c18eb38b69181936f5edc55a522c4d20a788d1a879b73c3810952c9d0fd128d01cb3f22042c09e languageName: node linkType: hard @@ -49160,15 +47863,6 @@ __metadata: languageName: node linkType: hard -"uuid@npm:11.1.0, uuid@npm:^11.0.0, uuid@npm:^11.0.2, uuid@npm:^11.0.3": - version: 11.1.0 - resolution: "uuid@npm:11.1.0" - bin: - uuid: dist/esm/bin/uuid - checksum: 10/d2da43b49b154d154574891ced66d0c83fc70caaad87e043400cf644423b067542d6f3eb641b7c819224a7cd3b4c2f21906acbedd6ec9c6a05887aa9115a9cf5 - languageName: node - linkType: hard - "uuid@npm:8.3.2, uuid@npm:^8.0.0, uuid@npm:^8.3.0, uuid@npm:^8.3.2": version: 8.3.2 resolution: "uuid@npm:8.3.2" @@ -49187,6 +47881,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^11.0.0, uuid@npm:^11.0.2, uuid@npm:^11.0.3": + version: 11.1.0 + resolution: "uuid@npm:11.1.0" + bin: + uuid: dist/esm/bin/uuid + checksum: 10/d2da43b49b154d154574891ced66d0c83fc70caaad87e043400cf644423b067542d6f3eb641b7c819224a7cd3b4c2f21906acbedd6ec9c6a05887aa9115a9cf5 + languageName: node + linkType: hard + "uuid@npm:^3.4.0": version: 3.4.0 resolution: "uuid@npm:3.4.0" @@ -49237,6 +47940,18 @@ __metadata: languageName: node linkType: hard +"valibot@npm:1.2.0, valibot@npm:^1.1.0": + version: 1.2.0 + resolution: "valibot@npm:1.2.0" + peerDependencies: + typescript: ">=5" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10/5f9c15e6f5a2b8eae75332a3317e46e995a1763efe1b91e57bc5064e36f0feba734367c88013d53255bdf09fb9204bf3598d2ca0c3f468c8726095b1c3551926 + languageName: node + linkType: hard + "valid-url@npm:^1.0.9": version: 1.0.9 resolution: "valid-url@npm:1.0.9" @@ -49300,13 +48015,6 @@ __metadata: languageName: node linkType: hard -"value-or-promise@npm:^1.0.11, value-or-promise@npm:^1.0.12": - version: 1.0.12 - resolution: "value-or-promise@npm:1.0.12" - checksum: 10/a4cc31fc9c3826b8a216ef2037b676904324c00c4acd903aaec2fe0c08516a189345261dd3cc822ec108532b2ea36b7c99bbdee1c3ddcb7f4b3d57d7e61b2064 - languageName: node - linkType: hard - "vary@npm:^1, vary@npm:^1.1.2, vary@npm:~1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" @@ -49314,6 +48022,16 @@ __metadata: languageName: node linkType: hard +"vfile-location@npm:^4.0.0": + version: 4.1.0 + resolution: "vfile-location@npm:4.1.0" + dependencies: + "@types/unist": "npm:^2.0.0" + vfile: "npm:^5.0.0" + checksum: 10/c894e8e5224170d1f85288f4a1d1ebcee0780823ea2b49d881648ab360ebf01b37ecb09b1c4439a75f9a51f31a9f9742cd045e987763e367c352a1ef7c50d446 + languageName: node + linkType: hard + "vfile-message@npm:^3.0.0": version: 3.0.2 resolution: "vfile-message@npm:3.0.2" @@ -49364,10 +48082,10 @@ __metadata: linkType: hard "vite@npm:^7.1.5": - version: 7.1.5 - resolution: "vite@npm:7.1.5" + version: 7.3.2 + resolution: "vite@npm:7.3.2" dependencies: - esbuild: "npm:^0.25.0" + esbuild: "npm:^0.27.0" fdir: "npm:^6.5.0" fsevents: "npm:~2.3.3" picomatch: "npm:^4.0.3" @@ -49414,7 +48132,7 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 10/59edeef7e98757a668b2ad8a1731a5657fa83e22a165a36b7359225ea98a9be39b2f486710c0cf5085edb85daee7c8b6b6b0bd85d0ef32a1aa84aef71aabd0f0 + checksum: 10/c5f7a9a60011c41c836cedf31c8ee7624102aff9b6a7f3aab2ff47639721bba0916f81994c3a3ea6577a16c4f0dfee1e7dbd244e0da8edd5954e3c6d48daaaa2 languageName: node linkType: hard @@ -49425,6 +48143,18 @@ __metadata: languageName: node linkType: hard +"vm2@npm:^3.10.0": + version: 3.10.3 + resolution: "vm2@npm:3.10.3" + dependencies: + acorn: "npm:^8.14.1" + acorn-walk: "npm:^8.3.4" + bin: + vm2: bin/vm2 + checksum: 10/015edd8adda9530b081dacc0eff1e1c7118a03a204337b7ab4b708f435c625aedb0b95637a171fe1022b434ca655dc2b92372784b2c26347d3fc57264fab5824 + languageName: node + linkType: hard + "vscode-languageserver-types@npm:^3.17.1": version: 3.17.5 resolution: "vscode-languageserver-types@npm:3.17.5" @@ -49432,15 +48162,6 @@ __metadata: languageName: node linkType: hard -"w3c-hr-time@npm:^1.0.2": - version: 1.0.2 - resolution: "w3c-hr-time@npm:1.0.2" - dependencies: - browser-process-hrtime: "npm:^1.0.0" - checksum: 10/03851d90c236837c24c2983f5a8806a837c6515b21d52e5f29776b07cc08695779303d481454d768308489f00dd9d3232d595acaa5b2686d199465a4d9f7b283 - languageName: node - linkType: hard - "w3c-keyname@npm:^2.2.4": version: 2.2.4 resolution: "w3c-keyname@npm:2.2.4" @@ -49448,15 +48169,6 @@ __metadata: languageName: node linkType: hard -"w3c-xmlserializer@npm:^3.0.0": - version: 3.0.0 - resolution: "w3c-xmlserializer@npm:3.0.0" - dependencies: - xml-name-validator: "npm:^4.0.0" - checksum: 10/b4d73e20be283cc9975573a88979d15c08daa9c00911f8c777ef2af74eea11ba635fec18647ff0374ce880ec32ae573d17bd0f787053fc3085a530345b2feab6 - languageName: node - linkType: hard - "w3c-xmlserializer@npm:^5.0.0": version: 5.0.0 resolution: "w3c-xmlserializer@npm:5.0.0" @@ -49466,10 +48178,10 @@ __metadata: languageName: node linkType: hard -"wait-for-expect@npm:^3.0.2": - version: 3.0.2 - resolution: "wait-for-expect@npm:3.0.2" - checksum: 10/023333b36e962e23a56b4f4ce2ab7166bdba036f096a59f1c531fbfcf1341b43b102ad54f3b684b37a492a53c03ba92829276101070563940be6e29901a532e0 +"wait-for-expect@npm:^4.0.0": + version: 4.0.0 + resolution: "wait-for-expect@npm:4.0.0" + checksum: 10/f9a07b1cdc1dc203030bb9f24dfa6772839db825535519cc47263a497a5a8436dcb2e9a81934574ea6699c51e128c8512edf87cd873a9b611e8e09240853f927 languageName: node linkType: hard @@ -49480,6 +48192,13 @@ __metadata: languageName: node linkType: hard +"walk-up-path@npm:^4.0.0": + version: 4.0.0 + resolution: "walk-up-path@npm:4.0.0" + checksum: 10/6a230b20e5de296895116dc12b09dafaec1f72b8060c089533d296e241aff059dfaebe0d015c77467f857e4b40c78e08f7481add76f340233a1f34fa8af9ed63 + languageName: node + linkType: hard + "walkdir@npm:^0.4.1": version: 0.4.1 resolution: "walkdir@npm:0.4.1" @@ -49496,13 +48215,13 @@ __metadata: languageName: node linkType: hard -"watchpack@npm:^2.4.1": - version: 2.4.1 - resolution: "watchpack@npm:2.4.1" +"watchpack@npm:^2.5.1": + version: 2.5.1 + resolution: "watchpack@npm:2.5.1" dependencies: glob-to-regexp: "npm:^0.4.1" graceful-fs: "npm:^4.1.2" - checksum: 10/0736ebd20b75d3931f9b6175c819a66dee29297c1b389b2e178bc53396a6f867ecc2fd5d87a713ae92dcb73e487daec4905beee20ca00a9e27f1184a7c2bca5e + checksum: 10/9c9cdd4a9f9ae146b10d15387f383f52589e4cc27b324da6be8e7e3e755255b062a69dd7f00eef2ce67b2c01e546aae353456e74f8c1350bba00462cc6375549 languageName: node linkType: hard @@ -49537,6 +48256,13 @@ __metadata: languageName: node linkType: hard +"web-namespaces@npm:^2.0.0": + version: 2.0.1 + resolution: "web-namespaces@npm:2.0.1" + checksum: 10/b6d9f02f1a43d0ef0848a812d89c83801d5bbad57d8bb61f02eb6d7eb794c3736f6cc2e1191664bb26136594c8218ac609f4069722c6f56d9fc2d808fa9271c6 + languageName: node + linkType: hard + "web-streams-polyfill@npm:4.0.0-beta.3": version: 4.0.0-beta.3 resolution: "web-streams-polyfill@npm:4.0.0-beta.3" @@ -49572,6 +48298,13 @@ __metadata: languageName: node linkType: hard +"webidl-conversions@npm:^8.0.0": + version: 8.0.1 + resolution: "webidl-conversions@npm:8.0.1" + checksum: 10/0f7007311f1fc257a8e406dd236f13b61fb57cf0fddb476aec33457d2d0add2d012d6df0eeb00934399238e3f3b9dad30f59dc6ac83024ae0ebd5a518bf365e8 + languageName: node + linkType: hard + "webpack-dev-middleware@npm:^7.4.2": version: 7.4.2 resolution: "webpack-dev-middleware@npm:7.4.2" @@ -49591,13 +48324,13 @@ __metadata: languageName: node linkType: hard -"webpack-dev-server@npm:5.2.2, webpack-dev-server@npm:^5.0.0": - version: 5.2.2 - resolution: "webpack-dev-server@npm:5.2.2" +"webpack-dev-server@npm:^5.0.0": + version: 5.2.3 + resolution: "webpack-dev-server@npm:5.2.3" dependencies: "@types/bonjour": "npm:^3.5.13" "@types/connect-history-api-fallback": "npm:^1.5.4" - "@types/express": "npm:^4.17.21" + "@types/express": "npm:^4.17.25" "@types/express-serve-static-core": "npm:^4.17.21" "@types/serve-index": "npm:^1.9.4" "@types/serve-static": "npm:^1.15.5" @@ -49607,9 +48340,9 @@ __metadata: bonjour-service: "npm:^1.2.1" chokidar: "npm:^3.6.0" colorette: "npm:^2.0.10" - compression: "npm:^1.7.4" + compression: "npm:^1.8.1" connect-history-api-fallback: "npm:^2.0.0" - express: "npm:^4.21.2" + express: "npm:^4.22.1" graceful-fs: "npm:^4.2.6" http-proxy-middleware: "npm:^2.0.9" ipaddr.js: "npm:^2.1.0" @@ -49617,7 +48350,7 @@ __metadata: open: "npm:^10.0.3" p-retry: "npm:^6.2.0" schema-utils: "npm:^4.2.0" - selfsigned: "npm:^2.4.1" + selfsigned: "npm:^5.5.0" serve-index: "npm:^1.9.1" sockjs: "npm:^0.3.24" spdy: "npm:^4.0.2" @@ -49632,24 +48365,14 @@ __metadata: optional: true bin: webpack-dev-server: bin/webpack-dev-server.js - checksum: 10/59517409cd38c01a875a03b9658f3d20d492b5b8bead9ded4a0f3d33e6857daf2d352fe89f0181dcaea6d0fbe84b0494cb4750a87120fe81cdbb3c32b499451c + checksum: 10/6a3d55c5d84d10b5e23a0638e0031ef85b262947fdacc86d96b7392538ad5894ac14aebef1e2b877f8d27302c71247215b7aa6713e01b1c37d31342415b1a150 languageName: node linkType: hard -"webpack-sources@npm:^1.4.3": - version: 1.4.3 - resolution: "webpack-sources@npm:1.4.3" - dependencies: - source-list-map: "npm:^2.0.0" - source-map: "npm:~0.6.1" - checksum: 10/6237c5d1ba639a5d67bd1135c9bba487eadbd04c5e75a2849508013f13cb4b57387e689e0991c19a14a87085be7cc0b8dd1515422ae351f6e3f813ed100ccbb8 - languageName: node - linkType: hard - -"webpack-sources@npm:^3.2.3, webpack-sources@npm:^3.3.3": - version: 3.3.3 - resolution: "webpack-sources@npm:3.3.3" - checksum: 10/ec5d72607e8068467370abccbfff855c596c098baedbe9d198a557ccf198e8546a322836a6f74241492576adba06100286592993a62b63196832cdb53c8bae91 +"webpack-sources@npm:^3.3.4": + version: 3.3.4 + resolution: "webpack-sources@npm:3.3.4" + checksum: 10/714427b235b04c2d7cf229f204b9e65145ea3643da3c7b139ebfa8a51056238d1e3a2a47c3cc3fc8eab71ed4300f66405cdc7cff29cd2f7f6b71086252f81cf1 languageName: node linkType: hard @@ -49660,9 +48383,9 @@ __metadata: languageName: node linkType: hard -"webpack@npm:^5": - version: 5.101.1 - resolution: "webpack@npm:5.101.1" +"webpack@npm:^5, webpack@npm:~5.105.0": + version: 5.105.4 + resolution: "webpack@npm:5.105.4" dependencies: "@types/eslint-scope": "npm:^3.7.7" "@types/estree": "npm:^1.0.8" @@ -49670,67 +48393,31 @@ __metadata: "@webassemblyjs/ast": "npm:^1.14.1" "@webassemblyjs/wasm-edit": "npm:^1.14.1" "@webassemblyjs/wasm-parser": "npm:^1.14.1" - acorn: "npm:^8.15.0" + acorn: "npm:^8.16.0" acorn-import-phases: "npm:^1.0.3" - browserslist: "npm:^4.24.0" + browserslist: "npm:^4.28.1" chrome-trace-event: "npm:^1.0.2" - enhanced-resolve: "npm:^5.17.3" - es-module-lexer: "npm:^1.2.1" + enhanced-resolve: "npm:^5.20.0" + es-module-lexer: "npm:^2.0.0" eslint-scope: "npm:5.1.1" events: "npm:^3.2.0" glob-to-regexp: "npm:^0.4.1" graceful-fs: "npm:^4.2.11" json-parse-even-better-errors: "npm:^2.3.1" - loader-runner: "npm:^4.2.0" + loader-runner: "npm:^4.3.1" mime-types: "npm:^2.1.27" neo-async: "npm:^2.6.2" - schema-utils: "npm:^4.3.2" - tapable: "npm:^2.1.1" - terser-webpack-plugin: "npm:^5.3.11" - watchpack: "npm:^2.4.1" - webpack-sources: "npm:^3.3.3" + schema-utils: "npm:^4.3.3" + tapable: "npm:^2.3.0" + terser-webpack-plugin: "npm:^5.3.17" + watchpack: "npm:^2.5.1" + webpack-sources: "npm:^3.3.4" peerDependenciesMeta: webpack-cli: optional: true bin: webpack: bin/webpack.js - checksum: 10/2cbb76683a959bf7f1c11a593e45bb6a2522254eff78e58106da155d6b08da49a3aca584d57948b473a7a55178adcbd728e98cd572ab535d72694b327896a595 - languageName: node - linkType: hard - -"webpack@npm:~5.96.0": - version: 5.96.1 - resolution: "webpack@npm:5.96.1" - dependencies: - "@types/eslint-scope": "npm:^3.7.7" - "@types/estree": "npm:^1.0.6" - "@webassemblyjs/ast": "npm:^1.12.1" - "@webassemblyjs/wasm-edit": "npm:^1.12.1" - "@webassemblyjs/wasm-parser": "npm:^1.12.1" - acorn: "npm:^8.14.0" - browserslist: "npm:^4.24.0" - chrome-trace-event: "npm:^1.0.2" - enhanced-resolve: "npm:^5.17.1" - es-module-lexer: "npm:^1.2.1" - eslint-scope: "npm:5.1.1" - events: "npm:^3.2.0" - glob-to-regexp: "npm:^0.4.1" - graceful-fs: "npm:^4.2.11" - json-parse-even-better-errors: "npm:^2.3.1" - loader-runner: "npm:^4.2.0" - mime-types: "npm:^2.1.27" - neo-async: "npm:^2.6.2" - schema-utils: "npm:^3.2.0" - tapable: "npm:^2.1.1" - terser-webpack-plugin: "npm:^5.3.10" - watchpack: "npm:^2.4.1" - webpack-sources: "npm:^3.2.3" - peerDependenciesMeta: - webpack-cli: - optional: true - bin: - webpack: bin/webpack.js - checksum: 10/d3419ffd198252e1d0301bd0c072cee93172f3e47937c745aa8202691d2f5d529d4ba4a1965d1450ad89a1bcd3c1f70ae09e57232b0d01dd38d69c1060e964d5 + checksum: 10/ae8088dd1c995fa17b920009f864138297a9ea5089bc563601f661fa4a31bb24b000cc91ae122168ce9def79c49258b8aa1021c2754c3555205c29a0d6c9cc8d languageName: node linkType: hard @@ -49752,15 +48439,6 @@ __metadata: languageName: node linkType: hard -"whatwg-encoding@npm:^2.0.0": - version: 2.0.0 - resolution: "whatwg-encoding@npm:2.0.0" - dependencies: - iconv-lite: "npm:0.6.3" - checksum: 10/162d712d88fd134a4fe587e53302da812eb4215a1baa4c394dfd86eff31d0a079ff932c05233857997de07481093358d6e7587997358f49b8a580a777be22089 - languageName: node - linkType: hard - "whatwg-encoding@npm:^3.1.1": version: 3.1.1 resolution: "whatwg-encoding@npm:3.1.1" @@ -49784,7 +48462,7 @@ __metadata: languageName: node linkType: hard -"whatwg-url@npm:14.2.0, whatwg-url@npm:^14.0.0": +"whatwg-url@npm:^14.0.0": version: 14.2.0 resolution: "whatwg-url@npm:14.2.0" dependencies: @@ -49794,13 +48472,13 @@ __metadata: languageName: node linkType: hard -"whatwg-url@npm:^11.0.0": - version: 11.0.0 - resolution: "whatwg-url@npm:11.0.0" +"whatwg-url@npm:^15.0.0, whatwg-url@npm:^15.1.0": + version: 15.1.0 + resolution: "whatwg-url@npm:15.1.0" dependencies: - tr46: "npm:^3.0.0" - webidl-conversions: "npm:^7.0.0" - checksum: 10/dfcd51c6f4bfb54685528fb10927f3fd3d7c809b5671beef4a8cdd7b1408a7abf3343a35bc71dab83a1424f1c1e92cc2700d7930d95d231df0fac361de0c7648 + tr46: "npm:^6.0.0" + webidl-conversions: "npm:^8.0.0" + checksum: 10/9ae5ce70060f2a9ea73799062af6e796ec2477f44bf1a886953b405700e3ab11d15aa0fe7088c4215f839e56a845d5d1c44584ed292a832837a8c8549c566886 languageName: node linkType: hard @@ -49870,7 +48548,7 @@ __metadata: languageName: node linkType: hard -"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.18, which-typed-array@npm:^1.1.2": +"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.19, which-typed-array@npm:^1.1.2": version: 1.1.19 resolution: "which-typed-array@npm:1.1.19" dependencies: @@ -49907,17 +48585,6 @@ __metadata: languageName: node linkType: hard -"which@npm:^4.0.0": - version: 4.0.0 - resolution: "which@npm:4.0.0" - dependencies: - isexe: "npm:^3.1.1" - bin: - node-which: bin/which.js - checksum: 10/f17e84c042592c21e23c8195108cff18c64050b9efb8459589116999ea9da6dd1509e6a1bac3aeebefd137be00fabbb61b5c2bc0aa0f8526f32b58ee2f545651 - languageName: node - linkType: hard - "which@npm:^5.0.0": version: 5.0.0 resolution: "which@npm:5.0.0" @@ -49929,6 +48596,17 @@ __metadata: languageName: node linkType: hard +"which@npm:^6.0.0": + version: 6.0.1 + resolution: "which@npm:6.0.1" + dependencies: + isexe: "npm:^4.0.0" + bin: + node-which: bin/which.js + checksum: 10/dbea77c7d3058bf6c78bf9659d2dce4d2b57d39a15b826b2af6ac2e5a219b99dc8a831b79fdbc453c0598adb4f3f84cf9c2491fd52beb9f5d2dececcad117f68 + languageName: node + linkType: hard + "wide-align@npm:^1.1.2": version: 1.1.5 resolution: "wide-align@npm:1.1.5" @@ -49956,40 +48634,33 @@ __metadata: languageName: node linkType: hard -"winston-transport@npm:^4.5.0, winston-transport@npm:^4.7.0": - version: 4.8.0 - resolution: "winston-transport@npm:4.8.0" +"winston-transport@npm:^4.5.0, winston-transport@npm:^4.7.0, winston-transport@npm:^4.9.0": + version: 4.9.0 + resolution: "winston-transport@npm:4.9.0" dependencies: - logform: "npm:^2.6.1" - readable-stream: "npm:^4.5.2" + logform: "npm:^2.7.0" + readable-stream: "npm:^3.6.2" triple-beam: "npm:^1.3.0" - checksum: 10/930bdc0ec689d5c4f07a262721da80440336f64739d0ce33db801c7142b4fca5be8ef71b725b670bac609de8b6bce405e5c5f84d355f5176a611209b476cee18 + checksum: 10/5946918720baadd7447823929e94cf0935f92c4cff6d9451c6fcb009bd9d20a3b3df9ad606109e79d1e9f4d2ff678477bf09f81cfefce2025baaf27a617129bb languageName: node linkType: hard "winston@npm:^3.13.0, winston@npm:^3.2.1": - version: 3.16.0 - resolution: "winston@npm:3.16.0" + version: 3.19.0 + resolution: "winston@npm:3.19.0" dependencies: "@colors/colors": "npm:^1.6.0" - "@dabh/diagnostics": "npm:^2.0.2" + "@dabh/diagnostics": "npm:^2.0.8" async: "npm:^3.2.3" is-stream: "npm:^2.0.0" - logform: "npm:^2.6.0" + logform: "npm:^2.7.0" one-time: "npm:^1.0.0" readable-stream: "npm:^3.4.0" safe-stable-stringify: "npm:^2.3.1" stack-trace: "npm:0.0.x" triple-beam: "npm:^1.3.0" - winston-transport: "npm:^4.7.0" - checksum: 10/cacec5268a965dcd3752bfe223d36eb003ac86b0cabc3d794521dfa6f937f78c86c8da84a53956c0f32169a55b438f6db0ee2aa028d57a2bcd69e1895a2e9d97 - languageName: node - linkType: hard - -"word-wrap@npm:~1.2.3": - version: 1.2.4 - resolution: "word-wrap@npm:1.2.4" - checksum: 10/a749c0cf410724acde4bdb263dcb13de61489dde22889a6a408e8a57e5948477c5b7438a757e25bb92985ed02562ab271aade90d605a24f3ae78410b638fbbd8 + winston-transport: "npm:^4.9.0" + checksum: 10/8279e221d8017da601a725939d31d65de71504d8328051312a85b1b4d7ddc68634329f8d611fb1ff91cb797643409635f3e97ef5b4a650c587639e080af76b7b languageName: node linkType: hard @@ -50063,7 +48734,7 @@ __metadata: languageName: node linkType: hard -"write-file-atomic@npm:^4.0.0, write-file-atomic@npm:^4.0.2": +"write-file-atomic@npm:^4.0.0": version: 4.0.2 resolution: "write-file-atomic@npm:4.0.2" dependencies: @@ -50083,9 +48754,9 @@ __metadata: languageName: node linkType: hard -"ws@npm:*, ws@npm:^8.12.0, ws@npm:^8.13.0, ws@npm:^8.17.1, ws@npm:^8.18.0, ws@npm:^8.18.2, ws@npm:^8.8.0": - version: 8.18.3 - resolution: "ws@npm:8.18.3" +"ws@npm:*, ws@npm:^8.18.0, ws@npm:^8.18.2, ws@npm:^8.18.3, ws@npm:^8.19.0, ws@npm:^8.8.0": + version: 8.20.0 + resolution: "ws@npm:8.20.0" peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ">=5.0.2" @@ -50094,7 +48765,7 @@ __metadata: optional: true utf-8-validate: optional: true - checksum: 10/725964438d752f0ab0de582cd48d6eeada58d1511c3f613485b5598a83680bedac6187c765b0fe082e2d8cc4341fc57707c813ae780feee82d0c5efe6a4c61b6 + checksum: 10/b7ab934b21ffdea9f25a5af5097e8c1ec7625db553bca026c5a23e35b7c236f3fb89782f2b57fab9da553864512f9aa7d245827ef998d26ffa1b2187a19a6d10 languageName: node linkType: hard @@ -50128,6 +48799,15 @@ __metadata: languageName: node linkType: hard +"wsl-utils@npm:^0.1.0": + version: 0.1.0 + resolution: "wsl-utils@npm:0.1.0" + dependencies: + is-wsl: "npm:^3.1.0" + checksum: 10/de4c92187e04c3c27b4478f410a02e81c351dc85efa3447bf1666f34fc80baacd890a6698ec91995631714086992036013286aea3d77e6974020d40a08e00aec + languageName: node + linkType: hard + "xcase@npm:^2.0.1": version: 2.0.1 resolution: "xcase@npm:2.0.1" @@ -50151,13 +48831,6 @@ __metadata: languageName: node linkType: hard -"xml-name-validator@npm:^4.0.0": - version: 4.0.0 - resolution: "xml-name-validator@npm:4.0.0" - checksum: 10/f9582a3f281f790344a471c207516e29e293c6041b2c20d84dd6e58832cd7c19796c47e108fd4fd4b164a5e72ad94f2268f8ace8231cde4a2c6428d6aa220f92 - languageName: node - linkType: hard - "xml-name-validator@npm:^5.0.0": version: 5.0.0 resolution: "xml-name-validator@npm:5.0.0" @@ -50237,18 +48910,18 @@ __metadata: linkType: hard "yaml@npm:^1.10.0, yaml@npm:^1.10.2, yaml@npm:^1.7.2": - version: 1.10.2 - resolution: "yaml@npm:1.10.2" - checksum: 10/e088b37b4d4885b70b50c9fa1b7e54bd2e27f5c87205f9deaffd1fb293ab263d9c964feadb9817a7b129a5bf30a06582cb08750f810568ecc14f3cdbabb79cb3 + version: 1.10.3 + resolution: "yaml@npm:1.10.3" + checksum: 10/e2ef2feb92c708138f016c69777a0f1e45f6d3c5e7cbcda30807a98a37eda2e008bd4fa57352b043c65245a4c799d0c99d1f9b3425de40e70929e26d2ea38215 languageName: node linkType: hard -"yaml@npm:^2.0.0, yaml@npm:^2.0.0-10, yaml@npm:^2.1.1, yaml@npm:^2.2.1, yaml@npm:^2.2.2, yaml@npm:^2.3.2, yaml@npm:^2.3.3, yaml@npm:^2.3.4, yaml@npm:^2.7.0, yaml@npm:^2.8.1": - version: 2.8.1 - resolution: "yaml@npm:2.8.1" +"yaml@npm:^2.0.0, yaml@npm:^2.0.0-10, yaml@npm:^2.1.1, yaml@npm:^2.2.1, yaml@npm:^2.2.2, yaml@npm:^2.3.2, yaml@npm:^2.3.3, yaml@npm:^2.3.4, yaml@npm:^2.7.0, yaml@npm:^2.8.2": + version: 2.8.3 + resolution: "yaml@npm:2.8.3" bin: yaml: bin.mjs - checksum: 10/eae07b3947d405012672ec17ce27348aea7d1fa0534143355d24a43a58f5e05652157ea2182c4fe0604f0540be71f99f1173f9d61018379404507790dff17665 + checksum: 10/ecad41d39d34fae5cc17ea2d4b7f7f55faacd45cbce8983ba22d48d1ed1a92ed242ea49ea813a79ac39a69f75f9c5a03e7b5395fd954d55476f25e21a47c141d languageName: node linkType: hard @@ -50281,7 +48954,7 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^16.2.0": +"yargs@npm:^16.0.0, yargs@npm:^16.2.0": version: 16.2.0 resolution: "yargs@npm:16.2.0" dependencies: @@ -50314,18 +48987,18 @@ __metadata: fs-extra: "npm:^11.2.0" nodemon: "npm:^3.0.1" semver: "npm:^7.6.0" - snyk-nodejs-lockfile-parser: "npm:^1.58.14" + snyk-nodejs-lockfile-parser: "npm:^2.0.0" yaml: "npm:^2.0.0" languageName: unknown linkType: soft -"yauzl@npm:^3.0.0": - version: 3.2.0 - resolution: "yauzl@npm:3.2.0" +"yauzl@npm:^3.2.1": + version: 3.2.1 + resolution: "yauzl@npm:3.2.1" dependencies: buffer-crc32: "npm:~0.2.3" pend: "npm:~1.2.0" - checksum: 10/a3cd2bfcf7590673bb35750f2a4e5107e3cc939d32d98a072c0673fe42329e390f471b4a53dbbd72512229099b18aa3b79e6ddb87a73b3a17446080c903a2c4b + checksum: 10/15dfae75fbfe59c6a1b7a2cb27a995cda0ee70549d32d6b19937e84897436170f169f6bbefc34b9e9beb9c9114a1b8a8a40e7687a907909a19681ebcbf35a1f3 languageName: node linkType: hard @@ -50376,7 +49049,7 @@ __metadata: languageName: node linkType: hard -"ylru@npm:^1.2.0, ylru@npm:^1.3.2": +"ylru@npm:^1.3.2": version: 1.4.0 resolution: "ylru@npm:1.4.0" checksum: 10/5437f8eb2fb5dd515845c657dde3cecaa9f6bd4c6386d2a5212d3fafe02189c7d8ebfdfc84940a7811607cb3524eb362ce95d3180d355cd5deb610aa8c82c9bc @@ -50414,13 +49087,6 @@ __metadata: languageName: node linkType: hard -"yocto-queue@npm:^1.0.0": - version: 1.2.1 - resolution: "yocto-queue@npm:1.2.1" - checksum: 10/0843d6c2c0558e5c06e98edf9c17942f25c769e21b519303a5c2adefd5b738c9b2054204dc856ac0cd9d134b1bc27d928ce84fd23c9e2423b7e013d5a6f50577 - languageName: node - linkType: hard - "yoctocolors-cjs@npm:^2.1.2": version: 2.1.2 resolution: "yoctocolors-cjs@npm:2.1.2" @@ -50462,25 +49128,41 @@ __metadata: languageName: node linkType: hard -"zod-to-json-schema@npm:^3.20.4, zod-to-json-schema@npm:^3.21.4, zod-to-json-schema@npm:^3.24.1": - version: 3.24.6 - resolution: "zod-to-json-schema@npm:3.24.6" +"zod-to-json-schema@npm:^3.25.1": + version: 3.25.2 + resolution: "zod-to-json-schema@npm:3.25.2" peerDependencies: - zod: ^3.24.1 - checksum: 10/a2c30cf1f250aa79a7f975e65b4236d1abafafd63b43c43475057f28ce6e13f4c882391553c656fb426fd09665e6ae293c2439b4ed8600863beda43fb1a56922 + zod: ^3.25.28 || ^4 + checksum: 10/7035328654113f1a0b8e4c2d34a06f918c93650ef8a50d4fb30ad8f22e47d5762c163af9c82494756b34776bae3c41c26cfc6945105b0eee7dceb528cc07e665 languageName: node linkType: hard -"zod-validation-error@npm:^3.0.3, zod-validation-error@npm:^3.4.0": - version: 3.4.1 - resolution: "zod-validation-error@npm:3.4.1" +"zod-validation-error@npm:^4.0.2": + version: 4.0.2 + resolution: "zod-validation-error@npm:4.0.2" peerDependencies: - zod: ^3.24.4 - checksum: 10/4975aacc1a931acdbaa3eeaf92fc89a210c6fd14a260d17688ec6302ce478c268c008c61bcdeab51c76191ac627e580f319775a7862be7b9c54c94f2f7cb6ed2 + zod: ^3.25.0 || ^4.0.0 + checksum: 10/5e35ca8ebb4602dcb526e122d7e9fca695c4a479bd97535f3400a732d49160f24f7213a9ed64986fc9dc3a2e8a6c4e1241ec0c4d8a4e3e69ea91a0328ded2192 languageName: node linkType: hard -"zod@npm:^3.22.4, zod@npm:^3.23.8, zod@npm:^3.24.2": +"zod-validation-error@npm:^5.0.0": + version: 5.0.0 + resolution: "zod-validation-error@npm:5.0.0" + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + checksum: 10/d5713f6608f9b8fcb0d74c591cb5c5839fcab679adde5c668f8f95539a1b54fb6910ba577ec66ccda77645e9d2c1ae3fafca771a291f4648aaae8bdfed9b27a7 + languageName: node + linkType: hard + +"zod@npm:^3.25 || ^4.0, zod@npm:^3.25.76 || ^4.0.0, zod@npm:^4.0.0, zod@npm:^4.1.11": + version: 4.3.6 + resolution: "zod@npm:4.3.6" + checksum: 10/25fc0f62e01b557b4644bf0b393bbaf47542ab30877c37837ea8caf314a8713d220c7d7fe51f68ffa72f0e1018ddfa34d96f1973d23033f5a2a5a9b6b9d9da01 + languageName: node + linkType: hard + +"zod@npm:^3.25.76": version: 3.25.76 resolution: "zod@npm:3.25.76" checksum: 10/f0c963ec40cd96858451d1690404d603d36507c1fc9682f2dae59ab38b578687d542708a7fdbf645f77926f78c9ed558f57c3d3aa226c285f798df0c4da16995